From 1f7dcd73477eae559e0f5915aece1388a54d0b76 Mon Sep 17 00:00:00 2001 From: ErikasKontenis Date: Thu, 2 Jan 2020 19:39:21 +0200 Subject: [PATCH 1/3] commit newest tfs branch only for compare --- src/CMakeLists.txt | 14 +- src/account.h | 6 +- src/actions.cpp | 273 +- src/actions.h | 24 +- src/ban.cpp | 30 +- src/ban.h | 12 +- src/baseevents.cpp | 47 +- src/baseevents.h | 27 +- src/bed.cpp | 14 +- src/bed.h | 19 +- src/chat.cpp | 92 +- src/chat.h | 25 +- src/combat.cpp | 907 ++---- src/combat.h | 102 +- src/condition.cpp | 838 +++-- src/condition.h | 278 +- src/configmanager.cpp | 147 +- src/configmanager.h | 45 +- src/connection.cpp | 41 +- src/connection.h | 34 +- src/const.h | 414 ++- src/container.cpp | 126 +- src/container.h | 85 +- src/creature.cpp | 289 +- src/creature.h | 93 +- src/creatureevent.cpp | 173 +- src/creatureevent.h | 121 +- src/cylinder.cpp | 4 +- src/cylinder.h | 5 +- src/database.cpp | 8 +- src/database.h | 25 +- src/databasemanager.cpp | 99 +- src/databasemanager.h | 7 +- src/databasetasks.cpp | 16 +- src/databasetasks.h | 8 +- src/definitions.h | 17 +- src/depotchest.cpp | 82 + src/depotchest.h | 57 + src/depotlocker.cpp | 53 +- src/depotlocker.h | 21 +- src/enums.h | 356 ++- src/events.cpp | 432 +-- src/events.h | 124 +- src/fileloader.cpp | 427 +-- src/fileloader.h | 168 +- src/game.cpp | 2299 ++++++++++---- src/game.h | 118 +- src/globalevent.cpp | 106 +- src/globalevent.h | 40 +- src/groups.cpp | 61 +- src/groups.h | 4 +- src/guild.cpp | 21 +- src/guild.h | 20 +- src/house.cpp | 92 +- src/house.h | 38 +- src/housetile.cpp | 4 +- src/housetile.h | 12 +- src/inbox.cpp | 72 + src/inbox.h | 49 + src/ioguild.cpp | 42 +- src/ioguild.h | 10 +- src/iologindata.cpp | 379 ++- src/iologindata.h | 19 +- src/iomap.cpp | 546 ++-- src/iomap.h | 23 +- src/iomapserialize.cpp | 40 +- src/iomapserialize.h | 6 +- src/iomarket.cpp | 347 +++ src/iomarket.h | 63 + src/item.cpp | 991 ++++-- src/item.h | 529 ++-- src/itemloader.h | 198 ++ src/items.cpp | 1746 ++++++++--- src/items.h | 218 +- src/lockfree.h | 47 +- src/luascript.cpp | 6277 ++++++++++++++++++++++++++++++++------ src/luascript.h | 452 ++- src/mailbox.cpp | 67 +- src/mailbox.h | 32 +- src/map.cpp | 83 +- src/map.h | 25 +- src/monster.cpp | 683 ++--- src/monster.h | 146 +- src/monsters.cpp | 830 +++-- src/monsters.h | 112 +- src/mounts.cpp | 82 + src/mounts.h | 52 + src/movement.cpp | 379 ++- src/movement.h | 160 +- src/networkmessage.cpp | 33 +- src/networkmessage.h | 37 +- src/npc.cpp | 1150 ++++++- src/npc.h | 171 +- src/otpch.cpp | 4 +- src/otpch.h | 4 +- src/otserv.cpp | 99 +- src/outputmessage.cpp | 16 +- src/outputmessage.h | 116 +- src/party.cpp | 62 +- src/party.h | 15 +- src/player.cpp | 1686 +++++++--- src/player.h | 582 ++-- src/position.cpp | 4 +- src/position.h | 26 +- src/protocol.cpp | 70 +- src/protocol.h | 124 +- src/protocolgame.cpp | 1387 +++++++-- src/protocolgame.h | 113 +- src/protocollogin.cpp | 94 +- src/protocollogin.h | 7 +- src/protocolold.cpp | 77 + src/protocolold.h | 46 + src/protocolstatus.cpp | 25 +- src/protocolstatus.h | 9 +- src/pugicast.h | 4 +- src/quests.cpp | 228 ++ src/quests.h | 119 + src/raids.cpp | 16 +- src/raids.h | 31 +- src/rsa.cpp | 13 +- src/rsa.h | 24 +- src/scheduler.cpp | 62 +- src/scheduler.h | 18 +- src/script.cpp | 499 +-- src/script.h | 297 +- src/scriptmanager.cpp | 26 +- src/scriptmanager.h | 8 +- src/server.cpp | 38 +- src/server.h | 135 +- src/signals.cpp | 212 ++ src/signals.h | 42 + src/spawn.cpp | 52 +- src/spawn.h | 12 +- src/spectators.h | 83 + src/spells.cpp | 1058 ++----- src/spells.h | 306 +- src/talkaction.cpp | 49 +- src/talkaction.h | 71 +- src/tasks.cpp | 16 +- src/tasks.h | 25 +- src/teleport.cpp | 4 +- src/teleport.h | 34 +- src/thing.cpp | 4 +- src/thing.h | 11 +- src/thread_holder_base.h | 4 +- src/tile.cpp | 388 ++- src/tile.h | 175 +- src/tools.cpp | 480 +-- src/tools.h | 38 +- src/town.h | 6 +- src/trashholder.cpp | 102 + src/trashholder.h | 57 + src/vocation.cpp | 37 +- src/vocation.h | 20 +- src/waitlist.cpp | 132 +- src/waitlist.h | 33 +- src/weapons.cpp | 944 ++++++ src/weapons.h | 311 ++ src/wildcardtree.cpp | 6 +- src/wildcardtree.h | 4 +- 160 files changed, 24900 insertions(+), 10996 deletions(-) create mode 100644 src/depotchest.cpp create mode 100644 src/depotchest.h create mode 100644 src/inbox.cpp create mode 100644 src/inbox.h create mode 100644 src/iomarket.cpp create mode 100644 src/iomarket.h create mode 100644 src/itemloader.h create mode 100644 src/mounts.cpp create mode 100644 src/mounts.h create mode 100644 src/protocolold.cpp create mode 100644 src/protocolold.h create mode 100644 src/quests.cpp create mode 100644 src/quests.h create mode 100644 src/signals.cpp create mode 100644 src/signals.h create mode 100644 src/spectators.h create mode 100644 src/trashholder.cpp create mode 100644 src/trashholder.h create mode 100644 src/weapons.cpp create mode 100644 src/weapons.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5beaa34..b375b16 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,7 +4,6 @@ set(tfs_SRC ${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}/condition.cpp @@ -17,6 +16,7 @@ set(tfs_SRC ${CMAKE_CURRENT_LIST_DIR}/database.cpp ${CMAKE_CURRENT_LIST_DIR}/databasemanager.cpp ${CMAKE_CURRENT_LIST_DIR}/databasetasks.cpp + ${CMAKE_CURRENT_LIST_DIR}/depotchest.cpp ${CMAKE_CURRENT_LIST_DIR}/depotlocker.cpp ${CMAKE_CURRENT_LIST_DIR}/events.cpp ${CMAKE_CURRENT_LIST_DIR}/fileloader.cpp @@ -26,10 +26,12 @@ set(tfs_SRC ${CMAKE_CURRENT_LIST_DIR}/groups.cpp ${CMAKE_CURRENT_LIST_DIR}/house.cpp ${CMAKE_CURRENT_LIST_DIR}/housetile.cpp + ${CMAKE_CURRENT_LIST_DIR}/inbox.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}/iomarket.cpp ${CMAKE_CURRENT_LIST_DIR}/item.cpp ${CMAKE_CURRENT_LIST_DIR}/items.cpp ${CMAKE_CURRENT_LIST_DIR}/luascript.cpp @@ -37,6 +39,7 @@ set(tfs_SRC ${CMAKE_CURRENT_LIST_DIR}/map.cpp ${CMAKE_CURRENT_LIST_DIR}/monster.cpp ${CMAKE_CURRENT_LIST_DIR}/monsters.cpp + ${CMAKE_CURRENT_LIST_DIR}/mounts.cpp ${CMAKE_CURRENT_LIST_DIR}/movement.cpp ${CMAKE_CURRENT_LIST_DIR}/networkmessage.cpp ${CMAKE_CURRENT_LIST_DIR}/npc.cpp @@ -49,24 +52,29 @@ set(tfs_SRC ${CMAKE_CURRENT_LIST_DIR}/protocol.cpp ${CMAKE_CURRENT_LIST_DIR}/protocolgame.cpp ${CMAKE_CURRENT_LIST_DIR}/protocollogin.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocolold.cpp ${CMAKE_CURRENT_LIST_DIR}/protocolstatus.cpp + ${CMAKE_CURRENT_LIST_DIR}/quests.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}/script.cpp ${CMAKE_CURRENT_LIST_DIR}/server.cpp + ${CMAKE_CURRENT_LIST_DIR}/signals.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}/trashholder.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 ${CMAKE_CURRENT_LIST_DIR}/xtea.cpp -) + PARENT_SCOPE) diff --git a/src/account.h b/src/account.h index 5bdc53a..7e14962 100644 --- a/src/account.h +++ b/src/account.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,6 +24,8 @@ struct Account { std::vector characters; + std::string name; + std::string key; time_t lastDay = 0; uint32_t id = 0; uint16_t premiumDays = 0; diff --git a/src/actions.cpp b/src/actions.cpp index c23df0d..9a02048 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,29 +40,27 @@ Actions::Actions() : Actions::~Actions() { - clear(); + clear(false); } -inline void Actions::clearMap(ActionUseMap& map) +void Actions::clearMap(ActionUseMap& map, bool fromLua) { - // Filter out duplicates to avoid double-free - std::unordered_set set; - for (const auto& it : map) { - set.insert(it.second); - } - map.clear(); - - for (Action* action : set) { - delete action; + for (auto it = map.begin(); it != map.end(); ) { + if (fromLua == it->second.fromLua) { + it = map.erase(it); + } else { + ++it; + } } } -void Actions::clear() +void Actions::clear(bool fromLua) { - clearMap(useItemMap); - clearMap(actionItemMap); + clearMap(useItemMap, fromLua); + clearMap(uniqueItemMap, fromLua); + clearMap(actionItemMap, fromLua); - scriptInterface.reInitState(); + reInitState(fromLua); } LuaScriptInterface& Actions::getScriptInterface() @@ -75,23 +73,23 @@ std::string Actions::getScriptBaseName() const return "actions"; } -Event* Actions::getEvent(const std::string& nodeName) +Event_ptr Actions::getEvent(const std::string& nodeName) { if (strcasecmp(nodeName.c_str(), "action") != 0) { return nullptr; } - return new Action(&scriptInterface); + return Event_ptr(new Action(&scriptInterface)); } -bool Actions::registerEvent(Event* event, const pugi::xml_node& node) +bool Actions::registerEvent(Event_ptr event, const pugi::xml_node& node) { - Action* action = static_cast(event); //event is guaranteed to be an Action + Action_ptr action{static_cast(event.release())}; //event is guaranteed to be an Action pugi::xml_attribute attr; if ((attr = node.attribute("itemid"))) { uint16_t id = pugi::cast(attr.value()); - auto result = useItemMap.emplace(id, action); + auto result = useItemMap.emplace(id, std::move(*action)); if (!result.second) { std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << id << std::endl; } @@ -107,14 +105,14 @@ bool Actions::registerEvent(Event* event, const pugi::xml_node& node) uint16_t iterId = fromId; uint16_t toId = pugi::cast(toIdAttribute.value()); - auto result = useItemMap.emplace(iterId, action); + 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); + 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; @@ -122,10 +120,44 @@ bool Actions::registerEvent(Event* event, const pugi::xml_node& node) success = true; } return success; + } else if ((attr = node.attribute("uniqueid"))) { + uint16_t uid = pugi::cast(attr.value()); + + auto result = uniqueItemMap.emplace(uid, std::move(*action)); + if (!result.second) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with uniqueid: " << uid << std::endl; + } + return result.second; + } else if ((attr = node.attribute("fromuid"))) { + pugi::xml_attribute toUidAttribute = node.attribute("touid"); + if (!toUidAttribute) { + std::cout << "[Warning - Actions::registerEvent] Missing touid in fromuid: " << attr.as_string() << std::endl; + return false; + } + + uint16_t fromUid = pugi::cast(attr.value()); + uint16_t iterUid = fromUid; + uint16_t toUid = pugi::cast(toUidAttribute.value()); + + auto result = uniqueItemMap.emplace(iterUid, *action); + if (!result.second) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with unique id: " << iterUid << " in fromuid: " << fromUid << ", touid: " << toUid << std::endl; + } + + bool success = result.second; + while (++iterUid <= toUid) { + result = uniqueItemMap.emplace(iterUid, *action); + if (!result.second) { + std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with unique id: " << iterUid << " in fromuid: " << fromUid << ", touid: " << toUid << std::endl; + continue; + } + success = true; + } + return success; } else if ((attr = node.attribute("actionid"))) { uint16_t aid = pugi::cast(attr.value()); - auto result = actionItemMap.emplace(aid, action); + auto result = actionItemMap.emplace(aid, std::move(*action)); if (!result.second) { std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with actionid: " << aid << std::endl; } @@ -141,14 +173,14 @@ bool Actions::registerEvent(Event* event, const pugi::xml_node& node) uint16_t iterAid = fromAid; uint16_t toAid = pugi::cast(toAidAttribute.value()); - auto result = actionItemMap.emplace(iterAid, action); + 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); + 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; @@ -160,6 +192,69 @@ bool Actions::registerEvent(Event* event, const pugi::xml_node& node) return false; } +bool Actions::registerLuaEvent(Action* event) +{ + Action_ptr action{ event }; + if (action->getItemIdRange().size() > 0) { + if (action->getItemIdRange().size() == 1) { + auto result = useItemMap.emplace(action->getItemIdRange().at(0), std::move(*action)); + if (!result.second) { + std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with id: " << action->getItemIdRange().at(0) << std::endl; + } + return result.second; + } else { + auto v = action->getItemIdRange(); + for (auto i = v.begin(); i != v.end(); i++) { + auto result = useItemMap.emplace(*i, std::move(*action)); + if (!result.second) { + std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with id: " << *i << " in range from id: " << v.at(0) << ", to id: " << v.at(v.size() - 1) << std::endl; + continue; + } + } + return true; + } + } else if (action->getUniqueIdRange().size() > 0) { + if (action->getUniqueIdRange().size() == 1) { + auto result = uniqueItemMap.emplace(action->getUniqueIdRange().at(0), std::move(*action)); + if (!result.second) { + std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with uid: " << action->getUniqueIdRange().at(0) << std::endl; + } + return result.second; + } else { + auto v = action->getUniqueIdRange(); + for (auto i = v.begin(); i != v.end(); i++) { + auto result = uniqueItemMap.emplace(*i, std::move(*action)); + if (!result.second) { + std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with uid: " << *i << " in range from uid: " << v.at(0) << ", to uid: " << v.at(v.size() - 1) << std::endl; + continue; + } + } + return true; + } + } else if (action->getActionIdRange().size() > 0) { + if (action->getActionIdRange().size() == 1) { + auto result = actionItemMap.emplace(action->getActionIdRange().at(0), std::move(*action)); + if (!result.second) { + std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with aid: " << action->getActionIdRange().at(0) << std::endl; + } + return result.second; + } else { + auto v = action->getActionIdRange(); + for (auto i = v.begin(); i != v.end(); i++) { + auto result = actionItemMap.emplace(*i, std::move(*action)); + if (!result.second) { + std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with aid: " << *i << " in range from aid: " << v.at(0) << ", to aid: " << v.at(v.size() - 1) << std::endl; + continue; + } + } + return true; + } + } else { + std::cout << "[Warning - Actions::registerLuaEvent] There is no id / aid / uid set for this event" << std::endl; + return false; + } +} + ReturnValue Actions::canUse(const Player* player, const Position& pos) { if (pos.x != 0xFFFF) { @@ -208,16 +303,23 @@ ReturnValue Actions::canUseFar(const Creature* creature, const Position& toPos, Action* Actions::getAction(const Item* item) { + if (item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { + auto it = uniqueItemMap.find(item->getUniqueId()); + if (it != uniqueItemMap.end()) { + return &it->second; + } + } + if (item->hasAttribute(ITEM_ATTRIBUTE_ACTIONID)) { auto it = actionItemMap.find(item->getActionId()); if (it != actionItemMap.end()) { - return it->second; + return &it->second; } } auto it = useItemMap.find(item->getID()); if (it != useItemMap.end()) { - return it->second; + return &it->second; } //rune items @@ -256,44 +358,41 @@ ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_ if (bed->trySleep(player)) { player->setBedItem(bed); - if (!bed->sleep(player)) { - return RETURNVALUE_CANNOTUSETHISOBJECT; - } + g_game.sendOfflineTrainingDialog(player); } return RETURNVALUE_NOERROR; } if (Container* container = item->getContainer()) { - if (!item->isChestQuest()) { - Container* openContainer; + 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; - } - - uint32_t corpseOwner = container->getCorpseOwner(); - if (corpseOwner != 0 && !player->canOpenCorpse(corpseOwner)) { - return RETURNVALUE_YOUARENOTTHEOWNER; - } - - //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; + //depot container + if (DepotLocker* depot = container->getDepotLocker()) { + DepotLocker* myDepotLocker = player->getDepotLocker(depot->getDepotId()); + myDepotLocker->setParent(depot->getParent()->getTile()); + openContainer = myDepotLocker; + player->setLastDepotId(depot->getDepotId()); + } else { + openContainer = container; } + + uint32_t corpseOwner = container->getCorpseOwner(); + if (corpseOwner != 0 && !player->canOpenCorpse(corpseOwner)) { + return RETURNVALUE_YOUARENOTTHEOWNER; + } + + //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()]; @@ -307,12 +406,6 @@ ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_ } 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; @@ -324,7 +417,7 @@ bool Actions::useItem(Player* player, const Position& pos, uint8_t index, Item* player->stopWalk(); if (isHotkey) { - showUseHotkeyMessage(player, item, player->getItemTypeCount(item->getID(), -1)); + showUseHotkeyMessage(player, item, player->getItemTypeCount(item->getID(), item->getSubType())); } ReturnValue ret = internalUseItem(player, pos, index, item, isHotkey); @@ -354,7 +447,7 @@ bool Actions::useItemEx(Player* player, const Position& fromPos, const Position& } if (isHotkey) { - showUseHotkeyMessage(player, item, player->getItemTypeCount(item->getID(), -1)); + showUseHotkeyMessage(player, item, player->getItemTypeCount(item->getID(), item->getSubType())); } if (!action->executeUse(player, item, fromPos, action->getTarget(player, creature, toPos, toStackPos), toPos, isHotkey)) { @@ -373,11 +466,9 @@ void Actions::showUseHotkeyMessage(Player* player, const Item* item, uint32_t co const ItemType& it = Item::items[item->getID()]; if (!it.showCount) { ss << "Using one of " << item->getName() << "..."; - } - else if (count == 1) { + } else if (count == 1) { ss << "Using the last " << item->getName() << "..."; - } - else { + } else { ss << "Using one of " << count << ' ' << item->getPluralName() << "..."; } player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); @@ -386,9 +477,6 @@ void Actions::showUseHotkeyMessage(Player* player, const Item* item, uint32_t co Action::Action(LuaScriptInterface* interface) : Event(interface), function(nullptr), 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"); @@ -409,6 +497,38 @@ bool Action::configureEvent(const pugi::xml_node& node) return true; } +namespace { + +bool enterMarket(Player* player, Item*, const Position&, Thing*, const Position&, bool) +{ + if (player->getLastDepotId() == -1) { + return false; + } + + player->sendMarketEnter(player->getLastDepotId()); + return true; +} + +} + +bool Action::loadFunction(const pugi::xml_attribute& attr, bool isScripted) +{ + const char* functionName = attr.as_string(); + if (strcasecmp(functionName, "market") == 0) { + function = enterMarket; + } else { + if (!isScripted) { + std::cout << "[Warning - Action::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; + return false; + } + } + + if (!isScripted) { + scripted = false; + } + return true; +} + std::string Action::getScriptEventName() const { return "onUse"; @@ -418,8 +538,7 @@ ReturnValue Action::canExecuteAction(const Player* player, const Position& toPos { if (!allowFarUse) { return g_actions->canUse(player, toPos); - } - else { + } else { return g_actions->canUseFar(player, toPos, checkLineOfSight, checkFloor); } } @@ -458,4 +577,4 @@ bool Action::executeUse(Player* player, Item* item, const Position& fromPosition LuaScriptInterface::pushBoolean(L, isHotkey); return scriptInterface->callFunction(6); -} \ No newline at end of file +} diff --git a/src/actions.h b/src/actions.h index 6f03677..69ca1a9 100644 --- a/src/actions.h +++ b/src/actions.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,15 +31,14 @@ using ActionFunction = std::function ActionUseMap; + using ActionUseMap = std::map; ActionUseMap useItemMap; + ActionUseMap uniqueItemMap; ActionUseMap actionItemMap; Action* getAction(const Item* item); - void clearMap(ActionUseMap& map); + void clearMap(ActionUseMap& map, bool fromLua); LuaScriptInterface scriptInterface; }; diff --git a/src/ban.cpp b/src/ban.cpp index 547ad29..0b75ceb 100644 --- a/src/ban.cpp +++ b/src/ban.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,15 +24,15 @@ #include "databasetasks.h" #include "tools.h" -bool Ban::acceptConnection(uint32_t clientip) +bool Ban::acceptConnection(uint32_t clientIP) { std::lock_guard lockClass(lock); uint64_t currentTime = OTSYS_TIME(); - auto it = ipConnectMap.find(clientip); + auto it = ipConnectMap.find(clientIP); if (it == ipConnectMap.end()) { - ipConnectMap.emplace(clientip, ConnectBlock(currentTime, 0, 1)); + ipConnectMap.emplace(clientIP, ConnectBlock(currentTime, 0, 1)); return true; } @@ -60,12 +60,12 @@ bool Ban::acceptConnection(uint32_t clientip) bool IOBan::isAccountBanned(uint32_t accountId, BanInfo& banInfo) { - Database* db = Database::getInstance(); + 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()); + DBResult_ptr result = db.storeQuery(query.str()); if (!result) { return false; } @@ -74,7 +74,7 @@ bool IOBan::isAccountBanned(uint32_t accountId, BanInfo& banInfo) 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("banned_at") << ',' << expiresAt << ',' << result->getNumber("banned_by") << ')'; + query << "INSERT INTO `account_ban_history` (`account_id`, `reason`, `banned_at`, `expired_at`, `banned_by`) VALUES (" << accountId << ',' << db.escapeString(result->getString("reason")) << ',' << result->getNumber("banned_at") << ',' << expiresAt << ',' << result->getNumber("banned_by") << ')'; g_databaseTasks.addTask(query.str()); query.str(std::string()); @@ -89,18 +89,18 @@ bool IOBan::isAccountBanned(uint32_t accountId, BanInfo& banInfo) return true; } -bool IOBan::isIpBanned(uint32_t clientip, BanInfo& banInfo) +bool IOBan::isIpBanned(uint32_t clientIP, BanInfo& banInfo) { - if (clientip == 0) { + if (clientIP == 0) { return false; } - Database* db = Database::getInstance(); + 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; + 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()); + DBResult_ptr result = db.storeQuery(query.str()); if (!result) { return false; } @@ -108,7 +108,7 @@ bool IOBan::isIpBanned(uint32_t clientip, BanInfo& banInfo) int64_t expiresAt = result->getNumber("expires_at"); if (expiresAt != 0 && time(nullptr) > expiresAt) { query.str(std::string()); - query << "DELETE FROM `ip_bans` WHERE `ip` = " << clientip; + query << "DELETE FROM `ip_bans` WHERE `ip` = " << clientIP; g_databaseTasks.addTask(query.str()); return false; } @@ -123,5 +123,5 @@ 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; + return Database::getInstance().storeQuery(query.str()).get() != nullptr; } diff --git a/src/ban.h b/src/ban.h index 86535a6..850d7cc 100644 --- a/src/ban.h +++ b/src/ban.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,14 +35,14 @@ struct ConnectBlock { uint32_t count; }; -typedef std::map IpConnectMap; +using IpConnectMap = std::map; class Ban { public: - bool acceptConnection(uint32_t clientip); + bool acceptConnection(uint32_t clientIP); - protected: + private: IpConnectMap ipConnectMap; std::recursive_mutex lock; }; @@ -51,7 +51,7 @@ class IOBan { public: static bool isAccountBanned(uint32_t accountId, BanInfo& banInfo); - static bool isIpBanned(uint32_t ip, BanInfo& banInfo); + static bool isIpBanned(uint32_t clientIP, BanInfo& banInfo); static bool isPlayerNamelocked(uint32_t playerId); }; diff --git a/src/baseevents.cpp b/src/baseevents.cpp index c99243e..4dc648c 100644 --- a/src/baseevents.cpp +++ b/src/baseevents.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -51,14 +51,13 @@ bool BaseEvents::loadFromXml() loaded = true; for (auto node : doc.child(scriptsName.c_str()).children()) { - Event* event = getEvent(node.name()); + Event_ptr 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; } @@ -68,12 +67,15 @@ bool BaseEvents::loadFromXml() if (scriptAttribute) { std::string scriptFile = "scripts/" + std::string(scriptAttribute.as_string()); success = event->checkScript(basePath, scriptsName, scriptFile) && event->loadScript(basePath + scriptFile); + if (node.attribute("function")) { + event->loadFunction(node.attribute("function"), true); + } } else { - success = event->loadFunction(node.attribute("function")); + success = event->loadFunction(node.attribute("function"), false); } - if (!success || !registerEvent(event, node)) { - delete event; + if (success) { + registerEvent(std::move(event), node); } } return true; @@ -82,14 +84,18 @@ bool BaseEvents::loadFromXml() bool BaseEvents::reload() { loaded = false; - clear(); + clear(false); return loadFromXml(); } -Event::Event(LuaScriptInterface* interface) : scriptInterface(interface) {} +void BaseEvents::reInitState(bool fromLua) +{ + if (!fromLua) { + getScriptInterface().reInitState(); + } +} -Event::Event(const Event* copy) : - scripted(copy->scripted), scriptId(copy->scriptId), scriptInterface(copy->scriptInterface) {} +Event::Event(LuaScriptInterface* interface) : scriptInterface(interface) {} bool Event::checkScript(const std::string& basePath, const std::string& scriptsName, const std::string& scriptFile) const { @@ -143,6 +149,24 @@ bool Event::loadScript(const std::string& scriptFile) return true; } +bool Event::loadCallback() +{ + if (!scriptInterface || scriptId != 0) { + std::cout << "Failure: [Event::loadCallback] scriptInterface == nullptr. scriptid = " << scriptId << std::endl; + return false; + } + + int32_t id = scriptInterface->getEvent(); + if (id == -1) { + std::cout << "[Warning - Event::loadCallback] Event " << getScriptEventName() << " not found. " << std::endl; + return false; + } + + scripted = true; + scriptId = id; + return true; +} + bool CallBack::loadCallBack(LuaScriptInterface* interface, const std::string& name) { if (!interface) { @@ -158,7 +182,6 @@ bool CallBack::loadCallBack(LuaScriptInterface* interface, const std::string& na return false; } - callbackName = name; scriptId = id; loaded = true; return true; diff --git a/src/baseevents.h b/src/baseevents.h index c5b528f..bf74c97 100644 --- a/src/baseevents.h +++ b/src/baseevents.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,18 +22,21 @@ #include "luascript.h" +class Event; +using Event_ptr = std::unique_ptr; + 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&) { + bool loadCallback(); + virtual bool loadFunction(const pugi::xml_attribute&, bool) { return false; } @@ -41,10 +44,12 @@ class Event return scripted; } + bool scripted = false; + bool fromLua = false; + protected: virtual std::string getScriptEventName() const = 0; - bool scripted = false; int32_t scriptId = 0; LuaScriptInterface* scriptInterface = nullptr; }; @@ -60,13 +65,14 @@ class BaseEvents bool isLoaded() const { return loaded; } + void reInitState(bool fromLua); - protected: + private: 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; + virtual Event_ptr getEvent(const std::string& nodeName) = 0; + virtual bool registerEvent(Event_ptr event, const pugi::xml_node& node) = 0; + virtual void clear(bool) = 0; bool loaded = false; }; @@ -82,9 +88,8 @@ class CallBack int32_t scriptId = 0; LuaScriptInterface* scriptInterface = nullptr; + private: bool loaded = false; - - std::string callbackName; }; #endif diff --git a/src/bed.cpp b/src/bed.cpp index 90629a9..a66a573 100644 --- a/src/bed.cpp +++ b/src/bed.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -159,8 +159,8 @@ bool BedItem::sleep(Player* player) // make the player walk onto the bed g_game.map.moveCreature(*player, *getTile()); - // display poff effect - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + // display 'Zzzz'/sleep effect + g_game.addMagicEffect(player->getPosition(), CONST_ME_SLEEP); // kick player after he sees himself walk onto the bed and it change id uint32_t playerId = player->getID(); @@ -246,10 +246,10 @@ 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 (player && it.transformToOnUse[player->getSex()] != 0) { + const ItemType& newType = Item::items[it.transformToOnUse[player->getSex()]]; if (newType.type == ITEM_TYPE_BED) { - g_game.transformItem(this, it.transformToOnUse); + g_game.transformItem(this, it.transformToOnUse[player->getSex()]); } } else if (it.transformToFree != 0) { const ItemType& newType = Item::items[it.transformToFree]; diff --git a/src/bed.h b/src/bed.h index f1f836d..ccc37cc 100644 --- a/src/bed.h +++ b/src/bed.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,17 +30,17 @@ class BedItem final : public Item public: explicit BedItem(uint16_t id); - BedItem* getBed() final { + BedItem* getBed() override { return this; } - const BedItem* getBed() const final { + const BedItem* getBed() const override { return this; } - Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) final; - void serializeAttr(PropWriteStream& propWriteStream) const final; + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; + void serializeAttr(PropWriteStream& propWriteStream) const override; - bool canRemove() const final { + bool canRemove() const override { return house == nullptr; } @@ -48,9 +48,6 @@ class BedItem final : public Item return sleeperGUID; } - House* getHouse() const { - return house; - } void setHouse(House* h) { house = h; } @@ -63,7 +60,7 @@ class BedItem final : public Item BedItem* getNextBedItem() const; - protected: + private: void updateAppearance(const Player* player); void regeneratePlayer(Player* player) const; void internalSetSleeper(const Player* player); diff --git a/src/chat.cpp b/src/chat.cpp index 59973c8..48f5338 100644 --- a/src/chat.cpp +++ b/src/chat.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -54,6 +54,10 @@ void PrivateChatChannel::invitePlayer(const Player& player, Player& invitePlayer ss.str(std::string()); ss << invitePlayer.getName() << " has been invited."; player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + + for (const auto& it : users) { + it.second->sendChannelEvent(id, invitePlayer.getName(), CHANNELEVENT_INVITE); + } } void PrivateChatChannel::excludePlayer(const Player& player, Player& excludePlayer) @@ -69,6 +73,10 @@ void PrivateChatChannel::excludePlayer(const Player& player, Player& excludePlay player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); excludePlayer.sendClosePrivate(id); + + for (const auto& it : users) { + it.second->sendChannelEvent(id, excludePlayer.getName(), CHANNELEVENT_EXCLUDE); + } } void PrivateChatChannel::closeChannel() const @@ -88,6 +96,20 @@ bool ChatChannel::addUser(Player& player) return false; } + // TODO: Move to script when guild channels can be scripted + if (id == CHANNEL_GUILD) { + Guild* guild = player.getGuild(); + if (guild && !guild->getMotd().empty()) { + g_scheduler.addEvent(createSchedulerTask(150, std::bind(&Game::sendGuildMotd, &g_game, player.getID()))); + } + } + + if (!publicChannel) { + for (const auto& it : users) { + it.second->sendChannelEvent(id, player.getName(), CHANNELEVENT_JOIN); + } + } + users[player.getID()] = &player; return true; } @@ -101,10 +123,27 @@ bool ChatChannel::removeUser(const Player& player) users.erase(iter); + if (!publicChannel) { + for (const auto& it : users) { + it.second->sendChannelEvent(id, player.getName(), CHANNELEVENT_LEAVE); + } + } + executeOnLeaveEvent(player); return true; } +bool ChatChannel::hasUser(const Player& player) { + return users.find(player.getID()) != users.end(); +} + +void ChatChannel::sendToAll(const std::string& message, SpeakClasses type) const +{ + for (const auto& it : users) { + it.second->sendChannelMessage("", message, type, id); + } +} + bool ChatChannel::talk(const Player& fromPlayer, SpeakClasses type, const std::string& text) { if (users.find(fromPlayer.getID()) == users.end()) { @@ -255,21 +294,39 @@ bool Chat::load() return false; } - std::forward_list 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(channelNode.attribute("id").value()), channelNode.attribute("name").as_string()); - channel.publicChannel = channelNode.attribute("public").as_bool(); - + uint16_t channelId = pugi::cast(channelNode.attribute("id").value()); + std::string channelName = channelNode.attribute("name").as_string(); + bool isPublic = channelNode.attribute("public").as_bool(); pugi::xml_attribute scriptAttribute = channelNode.attribute("script"); + + auto it = normalChannels.find(channelId); + if (it != normalChannels.end()) { + ChatChannel& channel = it->second; + channel.publicChannel = isPublic; + channel.name = channelName; + + 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; + } + } + + UsersMap tempUserMap = std::move(channel.users); + for (const auto& pair : tempUserMap) { + channel.addUser(*pair.second); + } + continue; + } + + ChatChannel channel(channelId, channelName); + channel.publicChannel = isPublic; + if (scriptAttribute) { if (scriptInterface.loadFile("data/chatchannels/scripts/" + std::string(scriptAttribute.as_string())) == 0) { channel.onSpeakEvent = scriptInterface.getEvent("onSpeak"); @@ -281,13 +338,8 @@ bool Chat::load() } } - removedChannels.remove(channel.id); normalChannels[channel.id] = channel; } - - for (uint16_t channelId : removedChannels) { - normalChannels.erase(channelId); - } return true; } diff --git a/src/chat.h b/src/chat.h index e181142..2f4c603 100644 --- a/src/chat.h +++ b/src/chat.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,23 +26,24 @@ class Party; class Player; -typedef std::map UsersMap; -typedef std::map InvitedMap; +using UsersMap = std::map; +using InvitedMap = std::map; class ChatChannel { public: ChatChannel() = default; ChatChannel(uint16_t channelId, std::string channelName): - name(std::move(channelName)), - id(channelId) {} + id{channelId}, name{std::move(channelName)} {} virtual ~ChatChannel() = default; bool addUser(Player& player); bool removeUser(const Player& player); + bool hasUser(const Player& player); bool talk(const Player& fromPlayer, SpeakClasses type, const std::string& text); + void sendToAll(const std::string& message, SpeakClasses type) const; const std::string& getName() const { return name; @@ -71,6 +72,9 @@ class ChatChannel protected: UsersMap users; + uint16_t id; + + private: std::string name; int32_t canJoinEvent = -1; @@ -78,7 +82,6 @@ class ChatChannel int32_t onLeaveEvent = -1; int32_t onSpeakEvent = -1; - uint16_t id; bool publicChannel = false; friend class Chat; @@ -89,7 +92,7 @@ class PrivateChatChannel final : public ChatChannel public: PrivateChatChannel(uint16_t channelId, std::string channelName) : ChatChannel(channelId, channelName) {} - uint32_t getOwner() const final { + uint32_t getOwner() const override { return owner; } void setOwner(uint32_t owner) { @@ -105,16 +108,16 @@ class PrivateChatChannel final : public ChatChannel void closeChannel() const; - const InvitedMap* getInvitedUsers() const final { + const InvitedMap* getInvitedUsers() const override { return &invites; } - protected: + private: InvitedMap invites; uint32_t owner = 0; }; -typedef std::list ChannelList; +using ChannelList = std::list; class Chat { diff --git a/src/combat.cpp b/src/combat.cpp index e63bc0f..f033057 100644 --- a/src/combat.cpp +++ b/src/combat.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,30 +22,61 @@ #include "combat.h" #include "game.h" +#include "weapons.h" #include "configmanager.h" -#include "monster.h" #include "events.h" extern Game g_game; +extern Weapons* g_weapons; extern ConfigManager g_config; extern Events* g_events; -CombatDamage Combat::getCombatDamage(Creature* creature) const +CombatDamage Combat::getCombatDamage(Creature* creature, Creature* target) const { CombatDamage damage; damage.origin = params.origin; - damage.type = params.combatType; + damage.primary.type = params.combatType; if (formulaType == COMBAT_FORMULA_DAMAGE) { - damage.min = static_cast(mina); - damage.max = static_cast(maxa); + damage.primary.value = normal_random( + static_cast(mina), + static_cast(maxa) + ); } else if (creature) { int32_t min, max; if (creature->getCombatValues(min, max)) { - damage.min = min; - damage.max = max; + damage.primary.value = normal_random(min, max); } else if (Player* player = creature->getPlayer()) { if (params.valueCallback) { params.valueCallback->getMinMaxValues(player, damage, params.useCharges); + } else if (formulaType == COMBAT_FORMULA_LEVELMAGIC) { + int32_t levelFormula = player->getLevel() * 2 + player->getMagicLevel() * 3; + damage.primary.value = normal_random( + static_cast(levelFormula * mina + minb), + static_cast(levelFormula * maxa + maxb) + ); + } else if (formulaType == COMBAT_FORMULA_SKILL) { + Item* tool = player->getWeapon(); + const Weapon* weapon = g_weapons->getWeapon(tool); + if (weapon) { + damage.primary.value = normal_random( + static_cast(minb), + static_cast(weapon->getWeaponDamage(player, target, tool, true) * maxa + maxb) + ); + + damage.secondary.type = weapon->getElementType(); + damage.secondary.value = weapon->getElementDamage(player, target, tool); + if (params.useCharges) { + uint16_t charges = tool->getCharges(); + if (charges != 0) { + g_game.transformItem(tool, tool->getID(), charges - 1); + } + } + } else { + damage.primary.value = normal_random( + static_cast(minb), + static_cast(maxb) + ); + } } } } @@ -79,12 +110,24 @@ CombatType_t Combat::ConditionToDamageType(ConditionType_t type) case CONDITION_ENERGY: return COMBAT_ENERGYDAMAGE; - case CONDITION_POISON: - return COMBAT_EARTHDAMAGE; + case CONDITION_BLEEDING: + return COMBAT_PHYSICALDAMAGE; case CONDITION_DROWN: return COMBAT_DROWNDAMAGE; + case CONDITION_POISON: + return COMBAT_EARTHDAMAGE; + + case CONDITION_FREEZING: + return COMBAT_ICEDAMAGE; + + case CONDITION_DAZZLED: + return COMBAT_HOLYDAMAGE; + + case CONDITION_CURSED: + return COMBAT_DEATHDAMAGE; + default: break; } @@ -101,11 +144,23 @@ ConditionType_t Combat::DamageToConditionType(CombatType_t type) case COMBAT_ENERGYDAMAGE: return CONDITION_ENERGY; + case COMBAT_DROWNDAMAGE: + return CONDITION_DROWN; + case COMBAT_EARTHDAMAGE: return CONDITION_POISON; - case COMBAT_DROWNDAMAGE: - return CONDITION_DROWN; + case COMBAT_ICEDAMAGE: + return CONDITION_FREEZING; + + case COMBAT_HOLYDAMAGE: + return CONDITION_DAZZLED; + + case COMBAT_DEATHDAMAGE: + return CONDITION_CURSED; + + case COMBAT_PHYSICALDAMAGE: + return CONDITION_BLEEDING; default: return CONDITION_NONE; @@ -125,15 +180,15 @@ bool Combat::isPlayerCombat(const Creature* target) return false; } -ReturnValue Combat::canTargetCreature(Player* player, Creature* target) +ReturnValue Combat::canTargetCreature(Player* attacker, Creature* target) { - if (player == target) { + if (attacker == target) { return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; } - if (!player->hasFlag(PlayerFlag_IgnoreProtectionZone)) { + if (!attacker->hasFlag(PlayerFlag_IgnoreProtectionZone)) { //pz-zone - if (player->getZone() == ZONE_PROTECTION) { + if (attacker->getZone() == ZONE_PROTECTION) { return RETURNVALUE_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE; } @@ -143,7 +198,7 @@ ReturnValue Combat::canTargetCreature(Player* player, Creature* target) //nopvp-zone if (isPlayerCombat(target)) { - if (player->getZone() == ZONE_NOPVP) { + if (attacker->getZone() == ZONE_NOPVP) { return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; } @@ -153,7 +208,7 @@ ReturnValue Combat::canTargetCreature(Player* player, Creature* target) } } - if (player->hasFlag(PlayerFlag_CannotUseCombat) || !target->isAttackable()) { + if (attacker->hasFlag(PlayerFlag_CannotUseCombat) || !target->isAttackable()) { if (target->getPlayer()) { return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; } else { @@ -162,16 +217,16 @@ ReturnValue Combat::canTargetCreature(Player* player, Creature* target) } if (target->getPlayer()) { - if (isProtected(player, target->getPlayer())) { + if (isProtected(attacker, target->getPlayer())) { return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; } - if (player->hasSecureMode() && !Combat::isInPvpZone(player, target) && player->getSkullClient(target->getPlayer()) == SKULL_NONE) { + if (attacker->hasSecureMode() && !Combat::isInPvpZone(attacker, target) && attacker->getSkullClient(target->getPlayer()) == SKULL_NONE) { return RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS; } } - return Combat::canDoCombat(player, target); + return Combat::canDoCombat(attacker, target); } ReturnValue Combat::canDoCombat(Creature* caster, Tile* tile, bool aggressive) @@ -180,11 +235,7 @@ ReturnValue Combat::canDoCombat(Creature* caster, Tile* tile, bool aggressive) return RETURNVALUE_NOTENOUGHROOM; } - if (tile->hasProperty(CONST_PROP_BLOCKPROJECTILE) && tile->hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID)) { - return RETURNVALUE_NOTENOUGHROOM; - } - - if (tile->hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID) && tile->hasProperty(CONST_PROP_UNLAY)) { + if (tile->hasFlag(TILESTATE_FLOORCHANGE)) { return RETURNVALUE_NOTENOUGHROOM; } @@ -197,8 +248,7 @@ ReturnValue Combat::canDoCombat(Creature* caster, Tile* tile, bool aggressive) const Position& tilePosition = tile->getPosition(); if (casterPosition.z < tilePosition.z) { return RETURNVALUE_FIRSTGODOWNSTAIRS; - } - else if (casterPosition.z > tilePosition.z) { + } else if (casterPosition.z > tilePosition.z) { return RETURNVALUE_FIRSTGOUPSTAIRS; } @@ -233,6 +283,10 @@ bool Combat::isProtected(const Player* attacker, const Player* target) return true; } + if (attacker->getSkull() == SKULL_BLACK && attacker->getSkullClient(target) == SKULL_NONE) { + return true; + } + return false; } @@ -260,8 +314,7 @@ ReturnValue Combat::canDoCombat(Creature* attacker, Creature* target) const Tile* targetPlayerTile = targetPlayer->getTile(); if (targetPlayerTile->hasFlag(TILESTATE_NOPVPZONE)) { return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; - } - else if (attackerPlayer->getTile()->hasFlag(TILESTATE_NOPVPZONE) && !targetPlayerTile->hasFlag(TILESTATE_NOPVPZONE | TILESTATE_PROTECTIONZONE)) { + } else if (attackerPlayer->getTile()->hasFlag(TILESTATE_NOPVPZONE) && !targetPlayerTile->hasFlag(TILESTATE_NOPVPZONE | TILESTATE_PROTECTIONZONE)) { return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; } } @@ -281,8 +334,7 @@ ReturnValue Combat::canDoCombat(Creature* attacker, Creature* target) } } } - } - else if (target->getMonster()) { + } else if (target->getMonster()) { if (const Player* attackerPlayer = attacker->getPlayer()) { if (attackerPlayer->hasFlag(PlayerFlag_CannotAttackMonster)) { return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE; @@ -291,6 +343,16 @@ ReturnValue Combat::canDoCombat(Creature* attacker, Creature* target) if (target->isSummon() && target->getMaster()->getPlayer() && target->getZone() == ZONE_NOPVP) { return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; } + } else if (attacker->getMonster()) { + const Creature* targetMaster = target->getMaster(); + + if (!targetMaster || !targetMaster->getPlayer()) { + const Creature* attackerMaster = attacker->getMaster(); + + if (!attackerMaster || !attackerMaster->getPlayer()) { + return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE; + } + } } } @@ -373,16 +435,6 @@ bool Combat::setParam(CombatParam_t param, uint32_t value) params.useCharges = (value != 0); return true; } - - case COMBAT_PARAM_DECREASEDAMAGE: { - params.decreaseDamage = static_cast(value); - return true; - } - - case COMBAT_PARAM_MAXIMUMDECREASEDDAMAGE: { - params.maximumDecreasedDamage = static_cast(value); - return true; - } } return false; } @@ -436,44 +488,35 @@ void Combat::CombatHealthFunc(Creature* caster, Creature* target, const CombatPa { assert(data); CombatDamage damage = *data; - - if (damage.value == 0) { - damage.value = normal_random(damage.min, damage.max); - } - - if (damage.value < 0 && caster) { - Player* targetPlayer = target->getPlayer(); - if (targetPlayer && caster->getPlayer()) { - damage.value /= 2; - } - } - if (g_game.combatBlockHit(damage, caster, target, params.blockedByShield, params.blockedByArmor, params.itemId != 0)) { return; } + if ((damage.primary.value < 0 || damage.secondary.value < 0) && caster) { + Player* targetPlayer = target->getPlayer(); + if (targetPlayer && caster->getPlayer() && targetPlayer->getSkull() != SKULL_BLACK) { + damage.primary.value /= 2; + damage.secondary.value /= 2; + } + } + if (g_game.combatChangeHealth(caster, target, damage)) { CombatConditionFunc(caster, target, params, &damage); CombatDispelFunc(caster, target, params, nullptr); } } -void Combat::CombatManaFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data) +void Combat::CombatManaFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* damage) { - assert(data); - CombatDamage damage = *data; - - if (damage.value == 0) { - damage.value = normal_random(damage.min, damage.max); - } - - if (damage.value < 0) { + assert(damage); + CombatDamage damageCopy = *damage; + if (damageCopy.primary.value < 0) { if (caster && caster->getPlayer() && target->getPlayer()) { - damage.value /= 2; + damageCopy.primary.value /= 2; } } - if (g_game.combatChangeMana(caster, target, damage)) { + if (g_game.combatChangeMana(caster, target, damageCopy)) { CombatConditionFunc(caster, target, params, nullptr); CombatDispelFunc(caster, target, params, nullptr); } @@ -481,7 +524,7 @@ void Combat::CombatManaFunc(Creature* caster, Creature* target, const CombatPara void Combat::CombatConditionFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data) { - if (params.origin == ORIGIN_MELEE && data && data->value == 0) { + if (params.origin == ORIGIN_MELEE && data && data->primary.value == 0 && data->secondary.value == 0) { return; } @@ -509,7 +552,7 @@ void Combat::CombatNullFunc(Creature* caster, Creature* target, const CombatPara CombatDispelFunc(caster, target, params, nullptr); } -void Combat::combatTileEffects(const SpectatorVec& list, Creature* caster, Tile* tile, const CombatParams& params) +void Combat::combatTileEffects(const SpectatorVec& spectators, Creature* caster, Tile* tile, const CombatParams& params) { if (params.itemId != 0) { uint16_t itemId = params.itemId; @@ -587,25 +630,51 @@ void Combat::combatTileEffects(const SpectatorVec& list, Creature* caster, Tile* } if (params.impactEffect != CONST_ME_NONE) { - Game::addMagicEffect(list, tile->getPosition(), params.impactEffect); + Game::addMagicEffect(spectators, tile->getPosition(), params.impactEffect); } } void Combat::postCombatEffects(Creature* caster, const Position& pos, const CombatParams& params) { if (caster && params.distanceEffect != CONST_ANI_NONE) { - addDistanceEffect(caster->getPosition(), pos, params.distanceEffect); + addDistanceEffect(caster, caster->getPosition(), pos, params.distanceEffect); } } -void Combat::addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect) +void Combat::addDistanceEffect(Creature* caster, const Position& fromPos, const Position& toPos, uint8_t effect) { + if (effect == CONST_ANI_WEAPONTYPE) { + if (!caster) { + return; + } + + Player* player = caster->getPlayer(); + if (!player) { + return; + } + + switch (player->getWeaponType()) { + case WEAPON_AXE: + effect = CONST_ANI_WHIRLWINDAXE; + break; + case WEAPON_SWORD: + effect = CONST_ANI_WHIRLWINDSWORD; + break; + case WEAPON_CLUB: + effect = CONST_ANI_WHIRLWINDCLUB; + break; + default: + effect = CONST_ANI_NONE; + break; + } + } + if (effect != CONST_ANI_NONE) { g_game.addDistanceEffect(fromPos, toPos, effect); } } -void Combat::CombatFunc(Creature* caster, const Position& pos, const AreaCombat* area, const CombatParams& params, COMBATFUNC func, CombatDamage* data) +void Combat::CombatFunc(Creature* caster, const Position& pos, const AreaCombat* area, const CombatParams& params, CombatFunction func, CombatDamage* data) { std::forward_list tileList; @@ -615,7 +684,6 @@ void Combat::CombatFunc(Creature* caster, const Position& pos, const AreaCombat* getCombatArea(pos, pos, area, tileList); } - SpectatorVec list; uint32_t maxX = 0; uint32_t maxY = 0; @@ -636,100 +704,18 @@ void Combat::CombatFunc(Creature* caster, const Position& pos, const AreaCombat* const int32_t rangeX = maxX + Map::maxViewportX; const int32_t rangeY = maxY + Map::maxViewportY; - g_game.map.getSpectators(list, pos, true, true, rangeX, rangeX, rangeY, rangeY); + + SpectatorVec spectators; + g_game.map.getSpectators(spectators, pos, true, true, rangeX, rangeX, rangeY, rangeY); postCombatEffects(caster, pos, params); - uint16_t decreasedDamage = 0; - const uint16_t maximumDecreasedDamage = params.maximumDecreasedDamage; - - bool firstCreature = true; - - if (params.decreaseDamage && data) { - for (Tile* tile : tileList) { - if (canDoCombat(caster, tile, params.aggressive) != RETURNVALUE_NOERROR) { - continue; - } - - if (CreatureVector* creatures = tile->getCreatures()) { - const Creature* topCreature = tile->getTopCreature(); - for (Creature* creature : *creatures) { - if (params.targetCasterOrTopMost) { - if (caster && caster->getTile() == tile) { - if (creature != caster) { - continue; - } - } else if (creature != topCreature) { - continue; - } - } - - if (!params.aggressive || (caster != creature && Combat::canDoCombat(caster, creature) == RETURNVALUE_NOERROR)) { - if (firstCreature) { - firstCreature = false; - continue; - } - - // only apply to players - if (creature->getPlayer()) { - if (maximumDecreasedDamage && decreasedDamage >= maximumDecreasedDamage) { - break; - } - - decreasedDamage += params.decreaseDamage; - } - } - } - } - } - - // actually decrease total damage output - if (data->value == 0) { - int32_t decreasedMinDamage = std::abs(data->min) * decreasedDamage / 100; - int32_t decreasedMaxDamage = std::abs(data->max) * decreasedDamage / 100; - - if (data->min < 0) { - // damaging spell, get as close as zero as we can get - // do not allow healing values - data->min += decreasedMinDamage; - data->max += decreasedMaxDamage; - - data->min = std::min(0, data->min); - data->max = std::min(0, data->max); - } else { - // healing spell, get as close as zero as we can get - // do not allow damaging values - data->min -= decreasedMinDamage; - data->max -= decreasedMaxDamage; - - data->min = std::max(0, data->min); - data->max = std::max(0, data->max); - } - } else { - int32_t decreasedValue = (std::abs(data->value) * decreasedDamage) / 100; - - if (data->value < 0) { - // damaging spell, get as close as zero as we can get - // do not allow healing values - data->value += decreasedValue; - - data->value = std::min(0, data->value); - } else { - // healing spell, get as close as zero as we can get - // do not allow damaging values - data->value -= decreasedValue; - - data->value = std::max(0, data->value); - } - } - } - for (Tile* tile : tileList) { if (canDoCombat(caster, tile, params.aggressive) != RETURNVALUE_NOERROR) { continue; } - combatTileEffects(list, caster, tile, params); + combatTileEffects(spectators, caster, tile, params); if (CreatureVector* creatures = tile->getCreatures()) { const Creature* topCreature = tile->getTopCreature(); @@ -763,8 +749,8 @@ void Combat::doCombat(Creature* caster, Creature* target) const { //target combat callback function if (params.combatType != COMBAT_NONE) { - CombatDamage damage = getCombatDamage(caster); - if (damage.type != COMBAT_MANADRAIN) { + CombatDamage damage = getCombatDamage(caster, target); + if (damage.primary.type != COMBAT_MANADRAIN) { doCombatHealth(caster, target, damage, params); } else { doCombatMana(caster, target, damage, params); @@ -778,8 +764,8 @@ void Combat::doCombat(Creature* caster, const Position& position) const { //area combat callback function if (params.combatType != COMBAT_NONE) { - CombatDamage damage = getCombatDamage(caster); - if (damage.type != COMBAT_MANADRAIN) { + CombatDamage damage = getCombatDamage(caster, nullptr); + if (damage.primary.type != COMBAT_MANADRAIN) { doCombatHealth(caster, position, area.get(), damage, params); } else { doCombatMana(caster, position, area.get(), damage, params); @@ -789,501 +775,7 @@ void Combat::doCombat(Creature* caster, const Position& position) const } } -int32_t Combat::computeDamage(Creature* creature, int32_t strength, int32_t variation) -{ - int32_t damage = strength; - if (variation) { - damage = normal_random(-variation, variation) + strength; - } - - if (creature) { - if (Player* player = creature->getPlayer()) { - int32_t formula = 3 * player->getMagicLevel() + 2 * player->getLevel(); - damage = formula * damage / 100; - } - } - - return damage; -} - -int32_t Combat::getTotalDamage(int32_t attackSkill, int32_t attackValue, fightMode_t fightMode) -{ - int32_t damage = attackValue; - - switch (fightMode) { - case FIGHTMODE_ATTACK: - damage += 2 * damage / 10; - break; - case FIGHTMODE_DEFENSE: - damage -= 4 * damage / 10; - break; - default: break; - } - - int32_t formula = (5 * (attackSkill) + 50) * damage; - int32_t randresult = rand() % 100; - int32_t totalDamage = -(ceil(formula * ((rand() % 100 + randresult) / 2) / 10000.)); - return totalDamage; -} - -bool Combat::attack(Creature* attacker, Creature* target) -{ - if (Player* player = attacker->getPlayer()) { - Item* weapon = player->getWeapon(); - if (weapon) { - if (weapon->getWeaponType() == WEAPON_DISTANCE || weapon->getWeaponType() == WEAPON_WAND) { - return rangeAttack(attacker, target, player->getFightMode()); - } - } - - return closeAttack(attacker, target, player->getFightMode()); - } - - return false; -} - -bool Combat::closeAttack(Creature* attacker, Creature* target, fightMode_t fightMode) -{ - const Position& attackerPos = attacker->getPosition(); - const Position& targetPos = target->getPosition(); - if (attackerPos.z != targetPos.z) { - return false; - } - - if (std::max(Position::getDistanceX(attackerPos, targetPos), Position::getDistanceY(attackerPos, targetPos)) > 1) { - return false; - } - - Item* weapon = nullptr; - - if (Player* player = attacker->getPlayer()) { - weapon = player->getWeapon(); - if (weapon && !Combat::canUseWeapon(player, weapon)) { - return false; - } - } - - uint32_t attackValue = 0; - uint32_t skillValue = 0; - uint8_t skill = SKILL_FIST; - - Combat::getAttackValue(attacker, attackValue, skillValue, skill); - - int32_t defense = target->getDefense(); - - if (OTSYS_TIME() < target->earliestDefendTime) { - defense = 0; - } - - CombatParams combatParams; - combatParams.blockedByArmor = true; - combatParams.blockedByShield = true; - combatParams.combatType = COMBAT_PHYSICALDAMAGE; - - CombatDamage combatDamage; - combatDamage.type = combatParams.combatType; - int32_t totalDamage = Combat::getTotalDamage(skillValue, attackValue, fightMode); - combatDamage.value = totalDamage; - combatDamage.origin = ORIGIN_MELEE; - - bool hit = Combat::doCombatHealth(attacker, target, combatDamage, combatParams); - - if (Monster* monster = attacker->getMonster()) { - int32_t poison = monster->mType->info.poison; - if (poison) { - int32_t randTest = rand(); - - if (hit || ((-totalDamage > defense) && (randTest == 5 * (randTest / 5)))) { - poison = normal_random(poison / 2, poison); - if (poison) { - ConditionDamage* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, CONDITION_POISON, 0, 0)); - condition->setParam(CONDITION_PARAM_OWNER, attacker->getID()); - condition->setParam(CONDITION_PARAM_CYCLE, poison); - condition->setParam(CONDITION_PARAM_COUNT, 3); - condition->setParam(CONDITION_PARAM_MAX_COUNT, 3); - target->addCombatCondition(condition); - } - } - } - } - - if (Player* player = attacker->getPlayer()) { - // skills advancing - if (!player->hasFlag(PlayerFlag_NotGainSkill)) { - if (player->getAddAttackSkill() && player->getLastAttackBlockType() != BLOCK_IMMUNITY) { - player->addSkillAdvance(static_cast(skill), 1); - } - } - - // weapon - if (weapon) { - if (weapon->getCharges() > 0) { - int32_t charges = weapon->getCharges() - 1; - if (charges <= 0) { - g_game.internalRemoveItem(weapon); - } else { - g_game.transformItem(weapon, weapon->getID(), charges); - } - } - } - } - - if (Player* player = attacker->getPlayer()) { - Combat::postWeaponEffects(player, weapon); - } - - return true; -} - -bool Combat::rangeAttack(Creature* attacker, Creature* target, fightMode_t fightMode) -{ - const Position& attackerPos = attacker->getPosition(); - const Position& targetPos = target->getPosition(); - if (attackerPos.z != targetPos.z) { - return false; - } - - uint8_t range = 0; - uint8_t hitChance = 0; - uint8_t distanceEffect = 0; - uint8_t specialEffect = 0; - int32_t attackStrength = 0; - int32_t attackVariation = 0; - - Item* weapon = nullptr; - Item* ammunition = nullptr; - - bool moveWeapon = true; - - if (Player* player = attacker->getPlayer()) { - weapon = player->getWeapon(); - if (!weapon) { - return false; - } - - if (!Combat::canUseWeapon(player, weapon)) { - return false; - } - - range = weapon->getShootRange(); - distanceEffect = weapon->getMissileType(); - - if (weapon->getWeaponType() == WEAPON_DISTANCE) { - ammunition = player->getAmmunition(); - if (weapon->getAmmoType() != AMMO_NONE) { - if (!ammunition || weapon->getAmmoType() != ammunition->getAmmoType()) { - // redirect to fist fighting - return closeAttack(attacker, target, fightMode); - } - - distanceEffect = ammunition->getMissileType(); - } - } - } - - if (std::max(Position::getDistanceX(attackerPos, targetPos), Position::getDistanceY(attackerPos, targetPos)) > range) { - return false; - } - - if (weapon->getWeaponType() == WEAPON_DISTANCE) { - uint32_t attackValue = 0; - uint32_t skillValue = 0; - uint8_t skill = SKILL_FIST; - - Combat::getAttackValue(attacker, attackValue, skillValue, skill); - - CombatParams combatParams; - combatParams.blockedByArmor = true; - combatParams.blockedByShield = false; - combatParams.combatType = COMBAT_PHYSICALDAMAGE; - - CombatDamage combatDamage; - combatDamage.type = combatParams.combatType; - combatDamage.value = Combat::getTotalDamage(skillValue, attackValue, fightMode); - combatDamage.origin = ORIGIN_RANGED; - - if (weapon) { - hitChance = 75; // throwables and such - specialEffect = weapon->getWeaponSpecialEffect(); - attackStrength = weapon->getAttackStrength(); - attackVariation = weapon->getAttackVariation(); - if (weapon->getFragility()) { - if (normal_random(0, 99) <= weapon->getFragility()) { - uint16_t count = weapon->getItemCount(); - if (count > 1) { - g_game.transformItem(weapon, weapon->getID(), count - 1); - } else { - g_game.internalRemoveItem(weapon); - } - - moveWeapon = false; - } - } - } - - if (ammunition && weapon->getAmmoType() != AMMO_NONE && weapon->getAmmoType() == ammunition->getAmmoType()) { - hitChance = 90; // bows and crossbows - specialEffect = ammunition->getWeaponSpecialEffect(); - attackStrength = ammunition->getAttackStrength(); - attackVariation = ammunition->getAttackVariation(); - if (normal_random(0, 100) <= ammunition->getFragility()) { - uint16_t count = ammunition->getItemCount(); - if (count > 1) { - g_game.transformItem(ammunition, ammunition->getID(), count - 1); - } else { - g_game.internalRemoveItem(ammunition); - } - } - - moveWeapon = false; - } - - int32_t distance = std::max(Position::getDistanceX(attackerPos, targetPos), Position::getDistanceY(attackerPos, targetPos)); - if (distance <= 1) { - distance = 5; - } - - distance *= 15; - - bool hit = false; - - int32_t random = rand(); - if (random % distance <= static_cast(skillValue)) { - hit = random % 100 <= hitChance; - } - - - if (Player* player = attacker->getPlayer()) { - if (player->getAddAttackSkill()) { - switch (player->getLastAttackBlockType()) { - case BLOCK_NONE: { - player->addSkillAdvance(SKILL_DISTANCE, 2); - break; - } - - case BLOCK_DEFENSE: - case BLOCK_ARMOR: { - player->addSkillAdvance(SKILL_DISTANCE, 1); - break; - } - - default: break; - } - } - } - - if (specialEffect == 1) { - if (hit) { - const int32_t rounds = ammunition ? ammunition->getAttackStrength() : weapon->getAttackStrength(); - - ConditionDamage* poisonDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_POISON); - poisonDamage->setParam(CONDITION_PARAM_OWNER, attacker->getID()); - poisonDamage->setParam(CONDITION_PARAM_CYCLE, rounds); - poisonDamage->setParam(CONDITION_PARAM_COUNT, 3); - poisonDamage->setParam(CONDITION_PARAM_MAX_COUNT, 3); - - target->addCombatCondition(poisonDamage); - } - } else if (specialEffect == 2) { - DamageImpact impact; - impact.actor = attacker; - impact.damage.type = COMBAT_PHYSICALDAMAGE; - impact.damage.value = -Combat::computeDamage(attacker, attackStrength, attackVariation); - impact.params.blockedByArmor = true; - impact.params.blockedByShield = false; - circleShapeSpell(attacker, target->getPosition(), 0xFF, 0, 3, &impact, 7); - } - - if (!hit) { - Tile* destTile = target->getTile(); - - if (!Position::areInRange<1, 1, 0>(attacker->getPosition(), target->getPosition())) { - static std::vector> destList{ - { -1, -1 },{ 0, -1 },{ 1, -1 }, - { -1, 0 },{ 0, 0 },{ 1, 0 }, - { -1, 1 },{ 0, 1 },{ 1, 1 } - }; - std::shuffle(destList.begin(), destList.end(), getRandomGenerator()); - - Position destPos = target->getPosition(); - - for (const auto& dir : destList) { - // Blocking tiles or tiles without ground ain't valid targets for spears - Tile* tmpTile = g_game.map.getTile(destPos.x + dir.first, destPos.y + dir.second, destPos.z); - if (tmpTile && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID) && tmpTile->getGround() != nullptr) { - destTile = tmpTile; - break; - } - } - } - - g_game.addMagicEffect(destTile->getPosition(), CONST_ME_POFF); - g_game.addDistanceEffect(attackerPos, destTile->getPosition(), distanceEffect); - - if (moveWeapon) { - g_game.internalMoveItem(weapon->getParent(), destTile, INDEX_WHEREEVER, weapon, 1, nullptr, FLAG_NOLIMIT); - } - - return true; - } - - g_game.addDistanceEffect(attackerPos, targetPos, distanceEffect); - Combat::doCombatHealth(attacker, target, combatDamage, combatParams); - - if (moveWeapon) { - g_game.internalMoveItem(weapon->getParent(), target->getTile(), INDEX_WHEREEVER, weapon, 1, nullptr, FLAG_NOLIMIT); - } - } else if (weapon->getWeaponType() == WEAPON_WAND) { - int32_t variation = normal_random(-weapon->getAttackVariation(), weapon->getAttackVariation()); - - CombatParams combatParams; - combatParams.combatType = weapon->getDamageType(); - - CombatDamage combatDamage; - combatDamage.type = combatParams.combatType; - combatDamage.value = -(variation + weapon->getAttackStrength()); - combatDamage.origin = ORIGIN_RANGED; - - g_game.addDistanceEffect(attackerPos, targetPos, distanceEffect); - Combat::doCombatHealth(attacker, target, combatDamage, combatParams); - } - - if (Player* player = attacker->getPlayer()) { - Combat::postWeaponEffects(player, weapon); - } - - return true; -} - -void Combat::circleShapeSpell(Creature* attacker, const Position& toPos, int32_t range, int32_t animation, int32_t radius, DamageImpact* impact, int32_t effect) -{ - const Position& fromPos = attacker->getPosition(); - if (fromPos.z != toPos.z) { - return; - } - - int32_t distance = std::max(Position::getDistanceX(fromPos, toPos), Position::getDistanceY(fromPos, toPos)); - if (distance > range) { - return; - } - - if (animation && fromPos != toPos) { - g_game.addDistanceEffect(fromPos, toPos, animation); - } - - std::forward_list tiles; - - AreaCombat areaCombat; - areaCombat.setupArea(radius); - - areaCombat.getList(toPos, toPos, tiles); - - for (Tile* tile : tiles) { - if (tile->hasFlag(TILESTATE_PROTECTIONZONE)) { - continue; - } - - if (CreatureVector* creatures = tile->getCreatures()) { - for (Creature* creature : *creatures) { - impact->handleCreature(creature); - } - } - - if (effect) { - g_game.addMagicEffect(tile->getPosition(), effect); - } - } -} - -void Combat::getAttackValue(Creature* creature, uint32_t& attackValue, uint32_t& skillValue, uint8_t& skill) -{ - skill = SKILL_FIST; - - if (Player* player = creature->getPlayer()) { - Item* weapon = player->getWeapon(); - if (weapon) { - switch (weapon->getWeaponType()) { - case WEAPON_AXE: { - skill = SKILL_AXE; - attackValue = weapon->getAttack(); - break; - } - case WEAPON_SWORD: { - skill = SKILL_SWORD; - attackValue = weapon->getAttack(); - break; - } - case WEAPON_CLUB: { - skill = SKILL_CLUB; - attackValue = weapon->getAttack(); - break; - } - case WEAPON_DISTANCE: { - skill = SKILL_DISTANCE; - attackValue = weapon->getAttack(); - - if (weapon->getAmmoType() != AMMO_NONE) { - Item* ammunition = player->getAmmunition(); - if (ammunition && ammunition->getAmmoType() == weapon->getAmmoType()) { - attackValue += ammunition->getAttack(); - } - } - break; - } - default: - attackValue = 7; - break; - } - - skillValue = player->getSkillLevel(skill); - } else { - attackValue = 7; - skillValue = player->getSkillLevel(skill); - } - } else if (Monster* monster = creature->getMonster()) { - attackValue = monster->mType->info.attack; - skillValue = monster->mType->info.skill; - } -} - -bool Combat::canUseWeapon(Player* player, Item* weapon) -{ - if (player->hasFlag(PlayerFlag_IgnoreWeaponCheck)) { - return true; - } - - if (player->getLevel() < weapon->getMinimumLevel()) { - return false; - } - - if (!player->hasFlag(PlayerFlag_HasInfiniteMana) && static_cast(player->getMana()) < weapon->getManaConsumption()) { - return false; - } - - const ItemType& itemType = Item::items[weapon->getID()]; - if (hasBitSet(WIELDINFO_VOCREQ, itemType.wieldInfo)) { - if (!hasBitSet(player->getVocationFlagId(), itemType.vocations)) { - return false; - } - } - - return true; -} - -void Combat::postWeaponEffects(Player* player, Item* weapon) -{ - if (!weapon || player->hasFlag(PlayerFlag_IgnoreWeaponCheck)) { - return; - } - - int32_t manaConsumption = weapon->getManaConsumption(); - if (manaConsumption) { - player->addManaSpent(manaConsumption); - player->changeMana(-manaConsumption); - } -} - -bool Combat::doCombatHealth(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params) +void Combat::doCombatHealth(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params) { bool canCombat = !params.aggressive || (caster != target && Combat::canDoCombat(caster, target) == RETURNVALUE_NOERROR); if ((caster == target || canCombat) && params.impactEffect != CONST_ME_NONE) { @@ -1292,7 +784,7 @@ bool Combat::doCombatHealth(Creature* caster, Creature* target, CombatDamage& da if (canCombat) { if (caster && params.distanceEffect != CONST_ANI_NONE) { - addDistanceEffect(caster->getPosition(), target->getPosition(), params.distanceEffect); + addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect); } CombatHealthFunc(caster, target, params, &damage); @@ -1300,8 +792,6 @@ bool Combat::doCombatHealth(Creature* caster, Creature* target, CombatDamage& da params.targetCallback->onTargetCombat(caster, target); } } - - return canCombat; } void Combat::doCombatHealth(Creature* caster, const Position& position, const AreaCombat* area, CombatDamage& damage, const CombatParams& params) @@ -1318,7 +808,7 @@ void Combat::doCombatMana(Creature* caster, Creature* target, CombatDamage& dama if (canCombat) { if (caster && params.distanceEffect != CONST_ANI_NONE) { - addDistanceEffect(caster->getPosition(), target->getPosition(), params.distanceEffect); + addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect); } CombatManaFunc(caster, target, params, &damage); @@ -1347,7 +837,7 @@ void Combat::doCombatCondition(Creature* caster, Creature* target, const CombatP if (canCombat) { if (caster && params.distanceEffect != CONST_ANI_NONE) { - addDistanceEffect(caster->getPosition(), target->getPosition(), params.distanceEffect); + addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect); } CombatConditionFunc(caster, target, params, nullptr); @@ -1376,7 +866,7 @@ void Combat::doCombatDispel(Creature* caster, Creature* target, const CombatPara } if (caster && params.distanceEffect != CONST_ANI_NONE) { - addDistanceEffect(caster->getPosition(), target->getPosition(), params.distanceEffect); + addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect); } } } @@ -1384,11 +874,11 @@ void Combat::doCombatDispel(Creature* caster, Creature* target, const CombatPara void Combat::doCombatDefault(Creature* caster, Creature* target, const CombatParams& params) { if (!params.aggressive || (caster != target && Combat::canDoCombat(caster, target) == RETURNVALUE_NOERROR)) { - SpectatorVec list; - g_game.map.getSpectators(list, target->getPosition(), true, true); + SpectatorVec spectators; + g_game.map.getSpectators(spectators, target->getPosition(), true, true); CombatNullFunc(caster, target, params, nullptr); - combatTileEffects(list, caster, target->getTile(), params); + combatTileEffects(spectators, caster, target->getTile(), params); if (params.targetCallback) { params.targetCallback->onTargetCombat(caster, target); @@ -1401,7 +891,7 @@ void Combat::doCombatDefault(Creature* caster, Creature* target, const CombatPar */ if (caster && params.distanceEffect != CONST_ANI_NONE) { - addDistanceEffect(caster->getPosition(), target->getPosition(), params.distanceEffect); + addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect); } } } @@ -1440,29 +930,33 @@ void ValueCallback::getMinMaxValues(Player* player, CombatDamage& damage, bool u } case COMBAT_FORMULA_SKILL: { - //onGetPlayerMinMaxValues(player, attackSkill, attackValue, fightMode) - uint32_t attackValue = 7; - uint32_t attackSkill = 0; - uint8_t skill = 0; + //onGetPlayerMinMaxValues(player, attackSkill, attackValue, attackFactor) + Item* tool = player->getWeapon(); + const Weapon* weapon = g_weapons->getWeapon(tool); - Combat::getAttackValue(player, attackValue, attackSkill, skill); + int32_t attackValue = 7; + if (weapon) { + attackValue = tool->getAttack(); + if (tool->getWeaponType() == WEAPON_AMMO) { + Item* item = player->getWeapon(true); + if (item) { + attackValue += item->getAttack(); + } + } - Item* weapon = player->getWeapon(); - if (useCharges && weapon) { - const ItemType& itemType = Item::items.getItemType(weapon->getID()); - if (itemType.charges) { - int32_t newCount = std::max(0, weapon->getCharges() - 1); - if (newCount <= 0) { - g_game.internalRemoveItem(weapon); - } else { - g_game.transformItem(weapon, weapon->getID(), newCount); + damage.secondary.type = weapon->getElementType(); + damage.secondary.value = weapon->getElementDamage(player, nullptr, tool); + if (useCharges) { + uint16_t charges = tool->getCharges(); + if (charges != 0) { + g_game.transformItem(tool, tool->getID(), charges - 1); } } } - lua_pushnumber(L, attackSkill); + lua_pushnumber(L, player->getWeaponSkill(tool)); lua_pushnumber(L, attackValue); - lua_pushnumber(L, player->getFightMode()); + lua_pushnumber(L, player->getAttackFactor()); parameters += 3; break; } @@ -1478,8 +972,10 @@ void ValueCallback::getMinMaxValues(Player* player, CombatDamage& damage, bool u if (lua_pcall(L, parameters, 2, 0) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); } else { - damage.min = LuaScriptInterface::getNumber(L, -2); - damage.max = LuaScriptInterface::getNumber(L, -1); + damage.primary.value = normal_random( + LuaScriptInterface::getNumber(L, -2), + LuaScriptInterface::getNumber(L, -1) + ); lua_pop(L, 2); } @@ -1616,7 +1112,7 @@ void AreaCombat::getList(const Position& centerPos, const Position& targetPos, s } } -void AreaCombat::copyArea(const MatrixArea* input, MatrixArea* output, MatrixOperation_t op) const +void AreaCombat::copyArea(const MatrixArea* input, MatrixArea* output, MatrixOperation_t op) { uint32_t centerY, centerX; input->getCenter(centerY, centerX); @@ -1742,17 +1238,17 @@ void AreaCombat::setupArea(const std::list& list, uint32_t rows) //SOUTH MatrixArea* southArea = new MatrixArea(maxOutput, maxOutput); - copyArea(area, southArea, MATRIXOPERATION_ROTATE180); + AreaCombat::copyArea(area, southArea, MATRIXOPERATION_ROTATE180); areas[DIRECTION_SOUTH] = southArea; //EAST MatrixArea* eastArea = new MatrixArea(maxOutput, maxOutput); - copyArea(area, eastArea, MATRIXOPERATION_ROTATE90); + AreaCombat::copyArea(area, eastArea, MATRIXOPERATION_ROTATE90); areas[DIRECTION_EAST] = eastArea; //WEST MatrixArea* westArea = new MatrixArea(maxOutput, maxOutput); - copyArea(area, westArea, MATRIXOPERATION_ROTATE270); + AreaCombat::copyArea(area, westArea, MATRIXOPERATION_ROTATE270); areas[DIRECTION_WEST] = westArea; } @@ -1842,17 +1338,17 @@ void AreaCombat::setupExtArea(const std::list& list, uint32_t rows) //NORTH-EAST MatrixArea* neArea = new MatrixArea(maxOutput, maxOutput); - copyArea(area, neArea, MATRIXOPERATION_MIRROR); + AreaCombat::copyArea(area, neArea, MATRIXOPERATION_MIRROR); areas[DIRECTION_NORTHEAST] = neArea; //SOUTH-WEST MatrixArea* swArea = new MatrixArea(maxOutput, maxOutput); - copyArea(area, swArea, MATRIXOPERATION_FLIP); + AreaCombat::copyArea(area, swArea, MATRIXOPERATION_FLIP); areas[DIRECTION_SOUTHWEST] = swArea; //SOUTH-EAST MatrixArea* seArea = new MatrixArea(maxOutput, maxOutput); - copyArea(swArea, seArea, MATRIXOPERATION_MIRROR); + AreaCombat::copyArea(swArea, seArea, MATRIXOPERATION_MIRROR); areas[DIRECTION_SOUTHEAST] = seArea; } @@ -1861,7 +1357,7 @@ void AreaCombat::setupExtArea(const std::list& list, uint32_t rows) void MagicField::onStepInField(Creature* creature) { //remove magic walls/wild growth - if (id == ITEM_MAGICWALL || id == ITEM_WILDGROWTH || isBlocking()) { + if (id == ITEM_MAGICWALL || id == ITEM_WILDGROWTH || id == ITEM_MAGICWALL_SAFE || id == ITEM_WILDGROWTH_SAFE || isBlocking()) { if (!creature->isInGhostMode()) { g_game.internalRemoveItem(this, 1); } @@ -1903,26 +1399,3 @@ void MagicField::onStepInField(Creature* creature) creature->addCondition(conditionCopy); } } - -void DamageImpact::handleCreature(Creature* target) -{ - Combat::doCombatHealth(actor, target, damage, params); -} - -void SpeedImpact::handleCreature(Creature* target) -{ - ConditionType_t conditionType = CONDITION_PARALYZE; - if (percent > 0) { - conditionType = CONDITION_HASTE; - } - - ConditionSpeed* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, duration)); - condition->setSpeedDelta(percent); - target->addCondition(condition); -} - -void DunkenImpact::handleCreature(Creature* target) -{ - Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_DRUNK, duration); - target->addCondition(condition); -} diff --git a/src/combat.h b/src/combat.h index a6b6b70..c6b4dff 100644 --- a/src/combat.h +++ b/src/combat.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -38,7 +38,7 @@ class ValueCallback final : public CallBack explicit ValueCallback(formulaType_t type): type(type) {} void getMinMaxValues(Player* player, CombatDamage& damage, bool useCharges) const; - protected: + private: formulaType_t type; }; @@ -46,18 +46,12 @@ 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 { @@ -68,8 +62,6 @@ struct CombatParams { std::unique_ptr targetCallback; uint16_t itemId = 0; - uint16_t decreaseDamage = 0; - uint16_t maximumDecreasedDamage = 0; ConditionType_t dispelType = CONDITION_NONE; CombatType_t combatType = COMBAT_NONE; @@ -85,39 +77,7 @@ struct CombatParams { 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 void(*COMBATFUNC)(Creature*, Creature*, const CombatParams&, CombatDamage*); +using CombatFunction = std::function; class MatrixArea { @@ -185,14 +145,14 @@ class MatrixArea return cols; } - inline const bool* operator[](uint32_t i) const { + const bool* operator[](uint32_t i) const { return data_[i]; } - inline bool* operator[](uint32_t i) { + bool* operator[](uint32_t i) { return data_[i]; } - protected: + private: uint32_t centerX; uint32_t centerY; @@ -222,7 +182,7 @@ class AreaCombat void setupExtArea(const std::list& list, uint32_t rows); void clear(); - protected: + private: enum MatrixOperation_t { MATRIXOPERATION_COPY, MATRIXOPERATION_MIRROR, @@ -233,7 +193,7 @@ class AreaCombat }; MatrixArea* createArea(const std::list& list, uint32_t rows); - void copyArea(const MatrixArea* input, MatrixArea* output, MatrixOperation_t op) const; + static void copyArea(const MatrixArea* input, MatrixArea* output, MatrixOperation_t op); MatrixArea* getArea(const Position& centerPos, const Position& targetPos) const { int32_t dx = Position::getOffsetX(targetPos, centerPos); @@ -282,18 +242,7 @@ class Combat 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, 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); @@ -317,10 +266,10 @@ class Combat 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); + static void addDistanceEffect(Creature* caster, const Position& fromPos, const Position& toPos, uint8_t effect); void doCombat(Creature* caster, Creature* target) const; - void doCombat(Creature* caster, const Position& pos) const; + void doCombat(Creature* caster, const Position& position) const; bool setCallback(CallBackParam_t key); CallBack* getCallback(CallBackParam_t key); @@ -332,9 +281,12 @@ class Combat bool hasArea() const { return area != nullptr; } - void setCondition(const Condition* condition) { + void addCondition(const Condition* condition) { params.conditionList.emplace_front(condition); } + void clearConditions() { + params.conditionList.clear(); + } 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); @@ -344,13 +296,10 @@ class Combat params.origin = origin; } - protected: - static bool canUseWeapon(Player* player, Item* weapon); - static void postWeaponEffects(Player* player, Item* weapon); - + private: 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 void CombatFunc(Creature* caster, const Position& pos, const AreaCombat* area, const CombatParams& params, CombatFunction func, CombatDamage* data); static void CombatHealthFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data); static void CombatManaFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* damage); @@ -358,8 +307,8 @@ class Combat static void CombatDispelFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data); static void 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; + static void combatTileEffects(const SpectatorVec& spectators, Creature* caster, Tile* tile, const CombatParams& params); + CombatDamage getCombatDamage(Creature* creature, Creature* target) const; //configureable CombatParams params; @@ -379,10 +328,10 @@ class MagicField final : public Item public: explicit MagicField(uint16_t type) : Item(type), createTime(OTSYS_TIME()) {} - MagicField* getMagicField() final { + MagicField* getMagicField() override { return this; } - const MagicField* getMagicField() const final { + const MagicField* getMagicField() const override { return this; } @@ -393,6 +342,13 @@ class MagicField final : public Item const ItemType& it = items[getID()]; return it.combatType; } + int32_t getDamage() const { + const ItemType& it = items[getID()]; + if (it.conditionDamage) { + return it.conditionDamage->getTotalDamage(); + } + return 0; + } void onStepInField(Creature* creature); private: diff --git a/src/condition.cpp b/src/condition.cpp index 7487e6a..8862e96 100644 --- a/src/condition.cpp +++ b/src/condition.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,6 +32,11 @@ bool Condition::setParam(ConditionParam_t param, int32_t value) return true; } + case CONDITION_PARAM_BUFF_SPELL: { + isBuff = (value != 0); + return true; + } + case CONDITION_PARAM_SUBID: { subId = value; return true; @@ -81,6 +86,16 @@ bool Condition::unserializeProp(ConditionAttr_t attr, PropStream& propStream) return propStream.read(ticks); } + case CONDITIONATTR_ISBUFF: { + uint8_t value; + if (!propStream.read(value)) { + return false; + } + + isBuff = (value != 0); + return true; + } + case CONDITIONATTR_SUBID: { return propStream.read(subId); } @@ -104,6 +119,9 @@ void Condition::serialize(PropWriteStream& propWriteStream) propWriteStream.write(CONDITIONATTR_TICKS); propWriteStream.write(ticks); + propWriteStream.write(CONDITIONATTR_ISBUFF); + propWriteStream.write(isBuff); + propWriteStream.write(CONDITIONATTR_SUBID); propWriteStream.write(subId); } @@ -125,47 +143,58 @@ bool Condition::executeCondition(Creature*, int32_t interval) return getEndTime() >= OTSYS_TIME(); } -Condition* Condition::createCondition(ConditionId_t id, ConditionType_t type, int32_t ticks, int32_t param/* = 0*/, uint32_t subId/* = 0*/) +Condition* Condition::createCondition(ConditionId_t id, ConditionType_t type, int32_t ticks, int32_t param/* = 0*/, bool buff/* = false*/, uint32_t subId/* = 0*/) { switch (type) { case CONDITION_POISON: case CONDITION_FIRE: case CONDITION_ENERGY: case CONDITION_DROWN: - return new ConditionDamage(id, type, subId); + case CONDITION_FREEZING: + case CONDITION_DAZZLED: + case CONDITION_CURSED: + case CONDITION_BLEEDING: + return new ConditionDamage(id, type, buff, subId); case CONDITION_HASTE: case CONDITION_PARALYZE: - return new ConditionSpeed(id, type, ticks, subId, param); + return new ConditionSpeed(id, type, ticks, buff, subId, param); case CONDITION_INVISIBLE: - return new ConditionInvisible(id, type, ticks, subId); + return new ConditionInvisible(id, type, ticks, buff, subId); case CONDITION_OUTFIT: - return new ConditionOutfit(id, type, ticks, subId); + return new ConditionOutfit(id, type, ticks, buff, subId); case CONDITION_LIGHT: - return new ConditionLight(id, type, ticks, subId, param & 0xFF, (param & 0xFF00) >> 8); + return new ConditionLight(id, type, ticks, buff, subId, param & 0xFF, (param & 0xFF00) >> 8); case CONDITION_REGENERATION: - return new ConditionRegeneration(id, type, ticks, subId); + return new ConditionRegeneration(id, type, ticks, buff, subId); case CONDITION_SOUL: - return new ConditionSoul(id, type, ticks, subId); + return new ConditionSoul(id, type, ticks, buff, subId); case CONDITION_ATTRIBUTES: - return new ConditionAttributes(id, type, ticks, subId); + return new ConditionAttributes(id, type, ticks, buff, subId); + + case CONDITION_SPELLCOOLDOWN: + return new ConditionSpellCooldown(id, type, ticks, buff, subId); + + case CONDITION_SPELLGROUPCOOLDOWN: + return new ConditionSpellGroupCooldown(id, type, ticks, buff, subId); case CONDITION_INFIGHT: case CONDITION_DRUNK: - case CONDITION_EXHAUST: + case CONDITION_EXHAUST_WEAPON: + case CONDITION_EXHAUST_COMBAT: + case CONDITION_EXHAUST_HEAL: case CONDITION_MUTED: case CONDITION_CHANNELMUTEDTICKS: case CONDITION_YELLTICKS: case CONDITION_PACIFIED: case CONDITION_MANASHIELD: - case CONDITION_AGGRESSIVE: - return new ConditionGeneric(id, type, ticks, subId); + return new ConditionGeneric(id, type, ticks, buff, subId); default: return nullptr; @@ -202,6 +231,15 @@ Condition* Condition::createCondition(PropStream& propStream) return nullptr; } + if (!propStream.read(attr) || attr != CONDITIONATTR_ISBUFF) { + return nullptr; + } + + uint8_t buff; + if (!propStream.read(buff)) { + return nullptr; + } + if (!propStream.read(attr) || attr != CONDITIONATTR_SUBID) { return nullptr; } @@ -211,7 +249,7 @@ Condition* Condition::createCondition(PropStream& propStream) return nullptr; } - return createCondition(static_cast(id), static_cast(type), ticks, 0, subId); + return createCondition(static_cast(id), static_cast(type), ticks, 0, buff != 0, subId); } bool Condition::startCondition(Creature*) @@ -237,7 +275,7 @@ bool Condition::isPersistent() const uint32_t Condition::getIcons() const { - return 0; + return isBuff ? ICON_PARTY_BUFF : 0; } bool Condition::updateCondition(const Condition* addCondition) @@ -272,10 +310,10 @@ void ConditionGeneric::endCondition(Creature*) // } -void ConditionGeneric::addCondition(Creature*, const Condition* addCondition) +void ConditionGeneric::addCondition(Creature*, const Condition* condition) { - if (updateCondition(addCondition)) { - setTicks(addCondition->getTicks()); + if (updateCondition(condition)) { + setTicks(condition->getTicks()); } } @@ -303,20 +341,22 @@ uint32_t ConditionGeneric::getIcons() const return icons; } -void ConditionAttributes::addCondition(Creature* creature, const Condition* addCondition) +void ConditionAttributes::addCondition(Creature* creature, const Condition* condition) { - if (updateCondition(addCondition)) { - setTicks(addCondition->getTicks()); + if (updateCondition(condition)) { + setTicks(condition->getTicks()); - const ConditionAttributes& conditionAttrs = static_cast(*addCondition); + const ConditionAttributes& conditionAttrs = static_cast(*condition); //Remove the old condition endCondition(creature); //Apply the new one memcpy(skills, conditionAttrs.skills, sizeof(skills)); + memcpy(specialSkills, conditionAttrs.specialSkills, sizeof(specialSkills)); memcpy(skillsPercent, conditionAttrs.skillsPercent, sizeof(skillsPercent)); memcpy(stats, conditionAttrs.stats, sizeof(stats)); memcpy(statsPercent, conditionAttrs.statsPercent, sizeof(statsPercent)); + disableDefense = conditionAttrs.disableDefense; if (Player* player = creature->getPlayer()) { updatePercentSkills(player); @@ -358,6 +398,8 @@ bool ConditionAttributes::startCondition(Creature* creature) return false; } + creature->setUseDefense(!disableDefense); + if (Player* player = creature->getPlayer()) { updatePercentSkills(player); updateSkills(player); @@ -385,7 +427,7 @@ void ConditionAttributes::updatePercentStats(Player* player) break; case STAT_MAGICPOINTS: - stats[i] = static_cast(player->getMagicLevel() * ((statsPercent[i] - 100) / 100.f)); + stats[i] = static_cast(player->getBaseMagicLevel() * ((statsPercent[i] - 100) / 100.f)); break; } } @@ -430,6 +472,13 @@ void ConditionAttributes::updateSkills(Player* player) } } + for (int32_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; ++i) { + if (specialSkills[i]) { + needUpdateSkills = true; + player->setVarSpecialSkill(static_cast(i), specialSkills[i]); + } + } + if (needUpdateSkills) { player->sendSkills(); } @@ -453,6 +502,13 @@ void ConditionAttributes::endCondition(Creature* creature) } } + for (int32_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; ++i) { + if (specialSkills[i]) { + needUpdateSkills = true; + player->setVarSpecialSkill(static_cast(i), -specialSkills[i]); + } + } + if (needUpdateSkills) { player->sendSkills(); } @@ -470,6 +526,10 @@ void ConditionAttributes::endCondition(Creature* creature) player->sendStats(); } } + + if (disableDefense) { + creature->setUseDefense(true); + } } bool ConditionAttributes::setParam(ConditionParam_t param, int32_t value) @@ -591,17 +651,52 @@ bool ConditionAttributes::setParam(ConditionParam_t param, int32_t value) return true; } + case CONDITION_PARAM_DISABLE_DEFENSE: { + disableDefense = (value != 0); + return true; + } + + case CONDITION_PARAM_SPECIALSKILL_CRITICALHITCHANCE: { + specialSkills[SPECIALSKILL_CRITICALHITCHANCE] = value; + return true; + } + + case CONDITION_PARAM_SPECIALSKILL_CRITICALHITAMOUNT: { + specialSkills[SPECIALSKILL_CRITICALHITAMOUNT] = value; + return true; + } + + case CONDITION_PARAM_SPECIALSKILL_LIFELEECHCHANCE: { + specialSkills[SPECIALSKILL_LIFELEECHCHANCE] = value; + return true; + } + + case CONDITION_PARAM_SPECIALSKILL_LIFELEECHAMOUNT: { + specialSkills[SPECIALSKILL_LIFELEECHAMOUNT] = value; + return true; + } + + case CONDITION_PARAM_SPECIALSKILL_MANALEECHCHANCE: { + specialSkills[SPECIALSKILL_MANALEECHCHANCE] = value; + return true; + } + + case CONDITION_PARAM_SPECIALSKILL_MANALEECHAMOUNT: { + specialSkills[SPECIALSKILL_MANALEECHAMOUNT] = value; + return true; + } + default: return ret; } } -void ConditionRegeneration::addCondition(Creature*, const Condition* addCondition) +void ConditionRegeneration::addCondition(Creature*, const Condition* condition) { - if (updateCondition(addCondition)) { - setTicks(addCondition->getTicks()); + if (updateCondition(condition)) { + setTicks(condition->getTicks()); - const ConditionRegeneration& conditionRegen = static_cast(*addCondition); + const ConditionRegeneration& conditionRegen = static_cast(*condition); healthTicks = conditionRegen.healthTicks; manaTicks = conditionRegen.manaTicks; @@ -647,17 +742,69 @@ bool ConditionRegeneration::executeCondition(Creature* creature, int32_t interva internalHealthTicks += interval; internalManaTicks += interval; - if (creature->getZone() != ZONE_PROTECTION) { - if (internalHealthTicks >= healthTicks) { - internalHealthTicks = 0; + if (creature->getZone() == ZONE_PROTECTION) { + return ConditionGeneric::executeCondition(creature, interval); + } - creature->changeHealth(healthGain); + if (internalHealthTicks >= healthTicks) { + internalHealthTicks = 0; + + int32_t realHealthGain = creature->getHealth(); + creature->changeHealth(healthGain); + realHealthGain = creature->getHealth() - realHealthGain; + + if (isBuff && realHealthGain > 0) { + Player* player = creature->getPlayer(); + if (player) { + std::string healString = std::to_string(realHealthGain) + (realHealthGain != 1 ? " hitpoints." : " hitpoint."); + + TextMessage message(MESSAGE_HEALED, "You were healed for " + healString); + message.position = player->getPosition(); + message.primary.value = realHealthGain; + message.primary.color = TEXTCOLOR_MAYABLUE; + player->sendTextMessage(message); + + SpectatorVec spectators; + g_game.map.getSpectators(spectators, player->getPosition(), false, true); + spectators.erase(player); + if (!spectators.empty()) { + message.type = MESSAGE_HEALED_OTHERS; + message.text = player->getName() + " was healed for " + healString; + for (Creature* spectator : spectators) { + spectator->getPlayer()->sendTextMessage(message); + } + } + } } + } - if (internalManaTicks >= manaTicks) { - internalManaTicks = 0; - if (Player* player = creature->getPlayer()) { - player->changeMana(manaGain); + if (internalManaTicks >= manaTicks) { + internalManaTicks = 0; + + if (Player* player = creature->getPlayer()) { + int32_t realManaGain = player->getMana(); + player->changeMana(manaGain); + realManaGain = player->getMana() - realManaGain; + + if (isBuff && realManaGain > 0) { + std::string manaGainString = std::to_string(realManaGain); + + TextMessage message(MESSAGE_HEALED, "You gained " + manaGainString + " mana."); + message.position = player->getPosition(); + message.primary.value = realManaGain; + message.primary.color = TEXTCOLOR_MAYABLUE; + player->sendTextMessage(message); + + SpectatorVec spectators; + g_game.map.getSpectators(spectators, player->getPosition(), false, true); + spectators.erase(player); + if (!spectators.empty()) { + message.type = MESSAGE_HEALED_OTHERS; + message.text = player->getName() + " gained " + manaGainString + " mana."; + for (Creature* spectator : spectators) { + spectator->getPlayer()->sendTextMessage(message); + } + } } } } @@ -691,12 +838,12 @@ bool ConditionRegeneration::setParam(ConditionParam_t param, int32_t value) } } -void ConditionSoul::addCondition(Creature*, const Condition* addCondition) +void ConditionSoul::addCondition(Creature*, const Condition* condition) { - if (updateCondition(addCondition)) { - setTicks(addCondition->getTicks()); + if (updateCondition(condition)) { + setTicks(condition->getTicks()); - const ConditionSoul& conditionSoul = static_cast(*addCondition); + const ConditionSoul& conditionSoul = static_cast(*condition); soulTicks = conditionSoul.soulTicks; soulGain = conditionSoul.soulGain; @@ -759,62 +906,78 @@ bool ConditionSoul::setParam(ConditionParam_t param, int32_t value) bool ConditionDamage::setParam(ConditionParam_t param, int32_t value) { + bool ret = Condition::setParam(param, value); + switch (param) { case CONDITION_PARAM_OWNER: owner = value; return true; - case CONDITION_PARAM_CYCLE: - cycle = value; + case CONDITION_PARAM_FORCEUPDATE: + forceUpdate = (value != 0); return true; - case CONDITION_PARAM_COUNT: - count = value; + case CONDITION_PARAM_DELAYED: + delayed = (value != 0); return true; - case CONDITION_PARAM_MAX_COUNT: - max_count = value; - return true; + case CONDITION_PARAM_MAXVALUE: + maxDamage = std::abs(value); + break; - case CONDITION_PARAM_HIT_DAMAGE: - hit_damage = value; - return true; + case CONDITION_PARAM_MINVALUE: + minDamage = std::abs(value); + break; + + case CONDITION_PARAM_STARTVALUE: + startDamage = std::abs(value); + break; + + case CONDITION_PARAM_TICKINTERVAL: + tickInterval = std::abs(value); + break; + + case CONDITION_PARAM_PERIODICDAMAGE: + periodDamage = value; + break; + + case CONDITION_PARAM_FIELD: + field = (value != 0); + break; default: return false; } + + return ret; } bool ConditionDamage::unserializeProp(ConditionAttr_t attr, PropStream& propStream) { - if (attr == CONDITIONATTR_OWNER) { + if (attr == CONDITIONATTR_DELAYED) { + uint8_t value; + if (!propStream.read(value)) { + return false; + } + + delayed = (value != 0); + return true; + } else if (attr == CONDITIONATTR_PERIODDAMAGE) { + return propStream.read(periodDamage); + } else if (attr == CONDITIONATTR_OWNER) { return propStream.skip(4); - } else if (attr == CONDITIONATTR_CYCLE) { - if (!propStream.read(cycle)) { + } else if (attr == CONDITIONATTR_INTERVALDATA) { + IntervalInfo damageInfo; + if (!propStream.read(damageInfo)) { return false; } - return true; - } else if (attr == CONDITIONATTR_COUNT) { - if (!propStream.read(count)) { - return false; + damageList.push_back(damageInfo); + if (ticks != -1) { + setTicks(ticks + damageInfo.interval); } - - return true; - } else if (attr == CONDITIONATTR_MAX_COUNT) { - if (!propStream.read(max_count)) { - return false; - } - - return true; - } else if (attr == CONDITIONATTR_FACTOR_PERCENT) { - if (!propStream.read(factor_percent)) { - return false; - } - return true; } - return Condition::unserializeProp(attr, propStream); } @@ -822,23 +985,91 @@ void ConditionDamage::serialize(PropWriteStream& propWriteStream) { Condition::serialize(propWriteStream); - propWriteStream.write(CONDITIONATTR_CYCLE); - propWriteStream.write(cycle); + propWriteStream.write(CONDITIONATTR_DELAYED); + propWriteStream.write(delayed); - propWriteStream.write(CONDITIONATTR_COUNT); - propWriteStream.write(count); + propWriteStream.write(CONDITIONATTR_PERIODDAMAGE); + propWriteStream.write(periodDamage); - propWriteStream.write(CONDITIONATTR_MAX_COUNT); - propWriteStream.write(max_count); - - propWriteStream.write(CONDITIONATTR_FACTOR_PERCENT); - propWriteStream.write(factor_percent); + for (const IntervalInfo& intervalInfo : damageList) { + propWriteStream.write(CONDITIONATTR_INTERVALDATA); + propWriteStream.write(intervalInfo); + } } bool ConditionDamage::updateCondition(const Condition* addCondition) { const ConditionDamage& conditionDamage = static_cast(*addCondition); - return conditionDamage.getTotalDamage() >= getTotalDamage(); + if (conditionDamage.doForceUpdate()) { + return true; + } + + if (ticks == -1 && conditionDamage.ticks > 0) { + return false; + } + + return conditionDamage.getTotalDamage() > getTotalDamage(); +} + +bool ConditionDamage::addDamage(int32_t rounds, int32_t time, int32_t value) +{ + time = std::max(time, EVENT_CREATURE_THINK_INTERVAL); + if (rounds == -1) { + //periodic damage + periodDamage = value; + setParam(CONDITION_PARAM_TICKINTERVAL, time); + setParam(CONDITION_PARAM_TICKS, -1); + return true; + } + + if (periodDamage > 0) { + return false; + } + + //rounds, time, damage + for (int32_t i = 0; i < rounds; ++i) { + IntervalInfo damageInfo; + damageInfo.interval = time; + damageInfo.timeLeft = time; + damageInfo.value = value; + + damageList.push_back(damageInfo); + + if (ticks != -1) { + setTicks(ticks + damageInfo.interval); + } + } + + return true; +} + +bool ConditionDamage::init() +{ + if (periodDamage != 0) { + return true; + } + + if (!damageList.empty()) { + return true; + } + + setTicks(0); + + int32_t amount = uniform_random(minDamage, maxDamage); + if (amount != 0) { + if (startDamage > maxDamage) { + startDamage = maxDamage; + } else if (startDamage == 0) { + startDamage = std::max(1, std::ceil(amount / 20.0)); + } + + std::list list; + ConditionDamage::generateDamageList(amount, startDamage, list); + for (int32_t value : list) { + addDamage(1, tickInterval, -value); + } + } + return !damageList.empty(); } bool ConditionDamage::startCondition(Creature* creature) @@ -847,112 +1078,73 @@ bool ConditionDamage::startCondition(Creature* creature) return false; } - creature->onAttacked(); - - setParam(CONDITION_PARAM_TICKINTERVAL, 1000); - - if (factor_percent == -1) { - factor_percent = 50; + if (!init()) { + return false; } - if (factor_percent <= 9) { - factor_percent = 10; + if (!delayed) { + int32_t damage; + if (getNextDamage(damage)) { + return doDamage(creature, damage); + } } - - if (factor_percent >= 1001) { - factor_percent = 1000; - } - - if (hit_damage) { - doDamage(creature, -hit_damage); - } - return true; } -bool ConditionDamage::executeCondition(Creature* creature, int32_t) +bool ConditionDamage::executeCondition(Creature* creature, int32_t interval) { - if (conditionType == CONDITION_FIRE) { - if (creature->isImmune(CONDITION_FIRE)) { - return false; - } + if (periodDamage != 0) { + periodDamageTick += interval; - const int32_t r_cycle = cycle; - if (r_cycle) { - if (count <= 0) { - count = max_count; - cycle = r_cycle + 2 * (r_cycle <= 0) - 1; - doDamage(creature, -10); + if (periodDamageTick >= tickInterval) { + periodDamageTick = 0; + doDamage(creature, periodDamage); + } + } else if (!damageList.empty()) { + IntervalInfo& damageInfo = damageList.front(); + + bool bRemove = (ticks != -1); + creature->onTickCondition(getType(), bRemove); + damageInfo.timeLeft -= interval; + + if (damageInfo.timeLeft <= 0) { + int32_t damage = damageInfo.value; + + if (bRemove) { + damageList.pop_front(); } else { - --count; + damageInfo.timeLeft = damageInfo.interval; } - } else { - return false; - } - } else if (conditionType == CONDITION_POISON) { - if (creature->isImmune(CONDITION_POISON)) { - return false; + + doDamage(creature, damage); } - const int32_t r_cycle = cycle; - if (r_cycle) { - if (count <= 0) { - count = max_count; - int32_t f = factor_percent * r_cycle / 1000; - if (!f) { - f = 2 * (r_cycle > 0) - 1; - } + if (!bRemove) { + if (ticks > 0) { + endTime += interval; + } - cycle = r_cycle - f; - doDamage(creature, -f); - } else { - --count; - } - } else { - return false; - } - } else if (conditionType == CONDITION_ENERGY) { - if (creature->isImmune(CONDITION_ENERGY)) { - return false; - } - - const int32_t r_cycle = cycle; - if (r_cycle) { - if (count <= 0) { - count = max_count; - cycle = r_cycle + 2 * (r_cycle <= 0) - 1; - doDamage(creature, -25); - } else { - --count; - } - } else { - return false; - } - } else if (conditionType == CONDITION_DROWN) { - if (isFirstCycle && count - max_count == -2) { - doDamage(creature, -20); - isFirstCycle = false; - count = max_count; - return true; - } - - const int32_t r_cycle = cycle; - if (r_cycle) { - if (count <= 0) { - count = max_count; - cycle = r_cycle + 2 * (r_cycle <= 0) - 1; - doDamage(creature, -20); - } - else { - --count; - } - } - else { - return false; + interval = 0; } } - return true; + return Condition::executeCondition(creature, interval); +} + +bool ConditionDamage::getNextDamage(int32_t& damage) +{ + if (periodDamage != 0) { + damage = periodDamage; + return true; + } else if (!damageList.empty()) { + IntervalInfo& damageInfo = damageList.front(); + damage = damageInfo.value; + if (ticks != -1) { + damageList.pop_front(); + } + return true; + } + return false; } bool ConditionDamage::doDamage(Creature* creature, int32_t healthChange) @@ -963,10 +1155,13 @@ bool ConditionDamage::doDamage(Creature* creature, int32_t healthChange) CombatDamage damage; damage.origin = ORIGIN_CONDITION; - damage.value = healthChange; - damage.type = Combat::ConditionToDamageType(conditionType); + damage.primary.value = healthChange; + damage.primary.type = Combat::ConditionToDamageType(conditionType); Creature* attacker = g_game.getCreatureByID(owner); + if (field && creature->getPlayer() && attacker && attacker->getPlayer()) { + damage.primary.value = static_cast(std::round(damage.primary.value / 2.)); + } if (!creature->isAttackable() || Combat::canDoCombat(attacker, creature) != RETURNVALUE_NOERROR) { if (!creature->isInGhostMode()) { @@ -975,7 +1170,7 @@ bool ConditionDamage::doDamage(Creature* creature, int32_t healthChange) return false; } - if (g_game.combatBlockHit(damage, attacker, creature, false, false, true)) { + if (g_game.combatBlockHit(damage, attacker, creature, false, false, field)) { return false; } return g_game.combatChangeHealth(attacker, creature, damage); @@ -986,31 +1181,64 @@ void ConditionDamage::endCondition(Creature*) // } -void ConditionDamage::addCondition(Creature* creature, const Condition* addCondition) +void ConditionDamage::addCondition(Creature* creature, const Condition* condition) { - if (addCondition->getType() != conditionType) { + if (condition->getType() != conditionType) { return; } - const ConditionDamage& conditionDamage = static_cast(*addCondition); - - if (hit_damage) { - doDamage(creature, -conditionDamage.hit_damage); - } - - if (!updateCondition(addCondition)) { + if (!updateCondition(condition)) { return; } + const ConditionDamage& conditionDamage = static_cast(*condition); + + setTicks(condition->getTicks()); owner = conditionDamage.owner; - cycle = conditionDamage.cycle; - count = conditionDamage.count; - max_count = conditionDamage.max_count; + maxDamage = conditionDamage.maxDamage; + minDamage = conditionDamage.minDamage; + startDamage = conditionDamage.startDamage; + tickInterval = conditionDamage.tickInterval; + periodDamage = conditionDamage.periodDamage; + int32_t nextTimeLeft = tickInterval; + + if (!damageList.empty()) { + //save previous timeLeft + IntervalInfo& damageInfo = damageList.front(); + nextTimeLeft = damageInfo.timeLeft; + damageList.clear(); + } + + damageList = conditionDamage.damageList; + + if (init()) { + if (!damageList.empty()) { + //restore last timeLeft + IntervalInfo& damageInfo = damageList.front(); + damageInfo.timeLeft = nextTimeLeft; + } + + if (!delayed) { + int32_t damage; + if (getNextDamage(damage)) { + doDamage(creature, damage); + } + } + } } int32_t ConditionDamage::getTotalDamage() const { - return cycle; + int32_t result; + if (!damageList.empty()) { + result = 0; + for (const IntervalInfo& intervalInfo : damageList) { + result += intervalInfo.value; + } + } else { + result = minDamage + (maxDamage - minDamage) / 2; + } + return std::abs(result); } uint32_t ConditionDamage::getIcons() const @@ -1025,12 +1253,28 @@ uint32_t ConditionDamage::getIcons() const icons |= ICON_ENERGY; break; + case CONDITION_DROWN: + icons |= ICON_DROWNING; + break; + case CONDITION_POISON: icons |= ICON_POISON; break; - case CONDITION_DROWN: - icons |= ICON_DROWNING; + case CONDITION_FREEZING: + icons |= ICON_FREEZING; + break; + + case CONDITION_DAZZLED: + icons |= ICON_DAZZLED; + break; + + case CONDITION_CURSED: + icons |= ICON_CURSED; + break; + + case CONDITION_BLEEDING: + icons |= ICON_BLEEDING; break; default: @@ -1039,12 +1283,69 @@ uint32_t ConditionDamage::getIcons() const return icons; } +void ConditionDamage::generateDamageList(int32_t amount, int32_t start, std::list& list) +{ + amount = std::abs(amount); + int32_t sum = 0; + double x1, x2; + + for (int32_t i = start; i > 0; --i) { + int32_t n = start + 1 - i; + int32_t med = (n * amount) / start; + + do { + sum += i; + list.push_back(i); + + x1 = std::fabs(1.0 - ((static_cast(sum)) + i) / med); + x2 = std::fabs(1.0 - (static_cast(sum) / med)); + } while (x1 < x2); + } +} + +void ConditionSpeed::setFormulaVars(float mina, float minb, float maxa, float maxb) +{ + this->mina = mina; + this->minb = minb; + this->maxa = maxa; + this->maxb = maxb; +} + +void ConditionSpeed::getFormulaValues(int32_t var, int32_t& min, int32_t& max) const +{ + min = (var * mina) + minb; + max = (var * maxa) + maxb; +} + +bool ConditionSpeed::setParam(ConditionParam_t param, int32_t value) +{ + Condition::setParam(param, value); + if (param != CONDITION_PARAM_SPEED) { + return false; + } + + speedDelta = value; + + if (value > 0) { + conditionType = CONDITION_HASTE; + } else { + conditionType = CONDITION_PARALYZE; + } + return true; +} + bool ConditionSpeed::unserializeProp(ConditionAttr_t attr, PropStream& propStream) { if (attr == CONDITIONATTR_SPEEDDELTA) { return propStream.read(speedDelta); - } else if (attr == CONDITIONATTR_APPLIEDSPEEDDELTA) { - return propStream.read(appliedSpeedDelta); + } else if (attr == CONDITIONATTR_FORMULA_MINA) { + return propStream.read(mina); + } else if (attr == CONDITIONATTR_FORMULA_MINB) { + return propStream.read(minb); + } else if (attr == CONDITIONATTR_FORMULA_MAXA) { + return propStream.read(maxa); + } else if (attr == CONDITIONATTR_FORMULA_MAXB) { + return propStream.read(maxb); } return Condition::unserializeProp(attr, propStream); } @@ -1056,8 +1357,17 @@ void ConditionSpeed::serialize(PropWriteStream& propWriteStream) propWriteStream.write(CONDITIONATTR_SPEEDDELTA); propWriteStream.write(speedDelta); - propWriteStream.write(CONDITIONATTR_APPLIEDSPEEDDELTA); - propWriteStream.write(appliedSpeedDelta); + propWriteStream.write(CONDITIONATTR_FORMULA_MINA); + propWriteStream.write(mina); + + propWriteStream.write(CONDITIONATTR_FORMULA_MINB); + propWriteStream.write(minb); + + propWriteStream.write(CONDITIONATTR_FORMULA_MAXA); + propWriteStream.write(maxa); + + propWriteStream.write(CONDITIONATTR_FORMULA_MAXB); + propWriteStream.write(maxb); } bool ConditionSpeed::startCondition(Creature* creature) @@ -1066,18 +1376,10 @@ bool ConditionSpeed::startCondition(Creature* creature) return false; } - if (appliedSpeedDelta == 0) { - speedDelta = normal_random(-variation, variation) + speedDelta; - - if (speedDelta >= -100) { - speedDelta = static_cast(creature->getBaseSpeed()) * speedDelta / 100; - } else { - speedDelta = -20 - creature->getBaseSpeed(); - } - - appliedSpeedDelta = speedDelta; - } else { - speedDelta = appliedSpeedDelta; + if (speedDelta == 0) { + int32_t min, max; + getFormulaValues(creature->getBaseSpeed(), min, max); + speedDelta = uniform_random(min, max); } g_game.changeSpeed(creature, speedDelta); @@ -1091,41 +1393,40 @@ bool ConditionSpeed::executeCondition(Creature* creature, int32_t interval) void ConditionSpeed::endCondition(Creature* creature) { - g_game.changeSpeed(creature, -appliedSpeedDelta); + g_game.changeSpeed(creature, -speedDelta); } -void ConditionSpeed::addCondition(Creature* creature, const Condition* addCondition) +void ConditionSpeed::addCondition(Creature* creature, const Condition* condition) { - if (conditionType != addCondition->getType()) { + if (conditionType != condition->getType()) { return; } - if (ticks == -1 && addCondition->getTicks() > 0) { + if (ticks == -1 && condition->getTicks() > 0) { return; } - const ConditionSpeed& conditionSpeed = static_cast(*addCondition); + setTicks(condition->getTicks()); - int32_t newVariation = conditionSpeed.variation; - int32_t newSpeedDelta = conditionSpeed.speedDelta; + const ConditionSpeed& conditionSpeed = static_cast(*condition); + int32_t oldSpeedDelta = speedDelta; + speedDelta = conditionSpeed.speedDelta; + mina = conditionSpeed.mina; + maxa = conditionSpeed.maxa; + minb = conditionSpeed.minb; + maxb = conditionSpeed.maxb; - newSpeedDelta = normal_random(-newVariation, newVariation) + newSpeedDelta; - - // update ticks - setTicks(addCondition->getTicks()); - - if (newSpeedDelta >= -100) { - newSpeedDelta = static_cast(creature->getBaseSpeed()) * newSpeedDelta / 100; - } else { - newSpeedDelta = -20 - creature->getBaseSpeed(); + if (speedDelta == 0) { + int32_t min; + int32_t max; + getFormulaValues(creature->getBaseSpeed(), min, max); + speedDelta = uniform_random(min, max); } - creature->setSpeed(-appliedSpeedDelta); - - appliedSpeedDelta = newSpeedDelta; - speedDelta = newSpeedDelta; - - g_game.changeSpeed(creature, newSpeedDelta); + int32_t newSpeedChange = (speedDelta - oldSpeedDelta); + if (newSpeedChange != 0) { + g_game.changeSpeed(creature, newSpeedChange); + } } uint32_t ConditionSpeed::getIcons() const @@ -1204,12 +1505,12 @@ void ConditionOutfit::endCondition(Creature* creature) g_game.internalCreatureChangeOutfit(creature, creature->getDefaultOutfit()); } -void ConditionOutfit::addCondition(Creature* creature, const Condition* addCondition) +void ConditionOutfit::addCondition(Creature* creature, const Condition* condition) { - if (updateCondition(addCondition)) { - setTicks(addCondition->getTicks()); + if (updateCondition(condition)) { + setTicks(condition->getTicks()); - const ConditionOutfit& conditionOutfit = static_cast(*addCondition); + const ConditionOutfit& conditionOutfit = static_cast(*condition); outfit = conditionOutfit.outfit; g_game.internalCreatureChangeOutfit(creature, outfit); @@ -1235,12 +1536,11 @@ bool ConditionLight::executeCondition(Creature* creature, int32_t interval) if (internalLightTicks >= lightChangeInterval) { internalLightTicks = 0; - LightInfo creatureLight; - creature->getCreatureLight(creatureLight); + LightInfo lightInfo = creature->getCreatureLight(); - if (creatureLight.level > 0) { - --creatureLight.level; - creature->setCreatureLight(creatureLight); + if (lightInfo.level > 0) { + --lightInfo.level; + creature->setCreatureLight(lightInfo); g_game.changeLight(creature); } } @@ -1254,12 +1554,12 @@ void ConditionLight::endCondition(Creature* creature) g_game.changeLight(creature); } -void ConditionLight::addCondition(Creature* creature, const Condition* addCondition) +void ConditionLight::addCondition(Creature* creature, const Condition* condition) { - if (updateCondition(addCondition)) { - setTicks(addCondition->getTicks()); + if (updateCondition(condition)) { + setTicks(condition->getTicks()); - const ConditionLight& conditionLight = static_cast(*addCondition); + const ConditionLight& conditionLight = static_cast(*condition); lightInfo.level = conditionLight.lightInfo.level; lightInfo.color = conditionLight.lightInfo.color; lightChangeInterval = ticks / lightInfo.level; @@ -1335,3 +1635,61 @@ void ConditionLight::serialize(PropWriteStream& propWriteStream) propWriteStream.write(CONDITIONATTR_LIGHTINTERVAL); propWriteStream.write(lightChangeInterval); } + +void ConditionSpellCooldown::addCondition(Creature* creature, const Condition* condition) +{ + if (updateCondition(condition)) { + setTicks(condition->getTicks()); + + if (subId != 0 && ticks > 0) { + Player* player = creature->getPlayer(); + if (player) { + player->sendSpellCooldown(subId, ticks); + } + } + } +} + +bool ConditionSpellCooldown::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + if (subId != 0 && ticks > 0) { + Player* player = creature->getPlayer(); + if (player) { + player->sendSpellCooldown(subId, ticks); + } + } + return true; +} + +void ConditionSpellGroupCooldown::addCondition(Creature* creature, const Condition* condition) +{ + if (updateCondition(condition)) { + setTicks(condition->getTicks()); + + if (subId != 0 && ticks > 0) { + Player* player = creature->getPlayer(); + if (player) { + player->sendSpellGroupCooldown(static_cast(subId), ticks); + } + } + } +} + +bool ConditionSpellGroupCooldown::startCondition(Creature* creature) +{ + if (!Condition::startCondition(creature)) { + return false; + } + + if (subId != 0 && ticks > 0) { + Player* player = creature->getPlayer(); + if (player) { + player->sendSpellGroupCooldown(static_cast(subId), ticks); + } + } + return true; +} diff --git a/src/condition.h b/src/condition.h index ed33877..cc99c5e 100644 --- a/src/condition.h +++ b/src/condition.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,13 +35,10 @@ enum ConditionAttr_t { CONDITIONATTR_HEALTHGAIN, CONDITIONATTR_MANATICKS, CONDITIONATTR_MANAGAIN, + CONDITIONATTR_DELAYED, CONDITIONATTR_OWNER, - CONDITIONATTR_CYCLE, - CONDITIONATTR_COUNT, - CONDITIONATTR_MAX_COUNT, - CONDITIONATTR_FACTOR_PERCENT, + CONDITIONATTR_INTERVALDATA, CONDITIONATTR_SPEEDDELTA, - CONDITIONATTR_APPLIEDSPEEDDELTA, CONDITIONATTR_FORMULA_MINA, CONDITIONATTR_FORMULA_MINB, CONDITIONATTR_FORMULA_MAXA, @@ -55,6 +52,8 @@ enum ConditionAttr_t { CONDITIONATTR_SKILLS, CONDITIONATTR_STATS, CONDITIONATTR_OUTFIT, + CONDITIONATTR_PERIODDAMAGE, + CONDITIONATTR_ISBUFF, CONDITIONATTR_SUBID, //reserved for serialization @@ -71,9 +70,9 @@ class Condition { public: Condition() = default; - Condition(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0) : + Condition(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0) : endTime(ticks == -1 ? std::numeric_limits::max() : 0), - subId(subId), ticks(ticks), conditionType(type), id(id) {} + subId(subId), ticks(ticks), conditionType(type), isBuff(buff), id(id) {} virtual ~Condition() = default; virtual bool startCondition(Creature* creature); @@ -101,7 +100,7 @@ class Condition } 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(ConditionId_t id, ConditionType_t type, int32_t ticks, int32_t param = 0, bool buff = false, uint32_t subId = 0); static Condition* createCondition(PropStream& propStream); virtual bool setParam(ConditionParam_t param, int32_t value); @@ -114,20 +113,23 @@ class Condition bool isPersistent() const; protected: + virtual bool updateCondition(const Condition* addCondition); + int64_t endTime; uint32_t subId; int32_t ticks; ConditionType_t conditionType; - ConditionId_t id; + bool isBuff; - virtual bool updateCondition(const Condition* addCondition); + private: + ConditionId_t id; }; class ConditionGeneric : public Condition { public: - ConditionGeneric(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0): - Condition(id, type, ticks, subId) {} + ConditionGeneric(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0): + Condition(id, type, ticks, buff, subId) {} bool startCondition(Creature* creature) override; bool executeCondition(Creature* creature, int32_t interval) override; @@ -143,32 +145,35 @@ class ConditionGeneric : public Condition 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) {} + ConditionAttributes(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0) : + ConditionGeneric(id, type, ticks, buff, 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 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; - bool setParam(ConditionParam_t param, int32_t value) final; + bool setParam(ConditionParam_t param, int32_t value) override; - ConditionAttributes* clone() const final { + ConditionAttributes* clone() const override { return new ConditionAttributes(*this); } //serialization - void serialize(PropWriteStream& propWriteStream) final; - bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; + void serialize(PropWriteStream& propWriteStream) override; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; - protected: + private: int32_t skills[SKILL_LAST + 1] = {}; int32_t skillsPercent[SKILL_LAST + 1] = {}; + int32_t specialSkills[SPECIALSKILL_LAST + 1] = {}; int32_t stats[STAT_LAST + 1] = {}; int32_t statsPercent[STAT_LAST + 1] = {}; int32_t currentSkill = 0; int32_t currentStat = 0; + bool disableDefense = false; + void updatePercentStats(Player* player); void updateStats(Player* player); void updatePercentSkills(Player* player); @@ -178,23 +183,23 @@ class ConditionAttributes final : public ConditionGeneric 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) {} + ConditionRegeneration(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0): + ConditionGeneric(id, type, ticks, buff, subId) {} - void addCondition(Creature* creature, const Condition* addCondition) final; - bool executeCondition(Creature* creature, int32_t interval) final; + void addCondition(Creature* creature, const Condition* condition) override; + bool executeCondition(Creature* creature, int32_t interval) override; - bool setParam(ConditionParam_t param, int32_t value) final; + bool setParam(ConditionParam_t param, int32_t value) override; - ConditionRegeneration* clone() const final { + ConditionRegeneration* clone() const override { return new ConditionRegeneration(*this); } //serialization - void serialize(PropWriteStream& propWriteStream) final; - bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; + void serialize(PropWriteStream& propWriteStream) override; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; - protected: + private: uint32_t internalHealthTicks = 0; uint32_t internalManaTicks = 0; @@ -207,23 +212,23 @@ class ConditionRegeneration final : public ConditionGeneric 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) {} + ConditionSoul(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0) : + ConditionGeneric(id, type, ticks, buff, subId) {} - void addCondition(Creature* creature, const Condition* addCondition) final; - bool executeCondition(Creature* creature, int32_t interval) final; + void addCondition(Creature* creature, const Condition* condition) override; + bool executeCondition(Creature* creature, int32_t interval) override; - bool setParam(ConditionParam_t param, int32_t value) final; + bool setParam(ConditionParam_t param, int32_t value) override; - ConditionSoul* clone() const final { + ConditionSoul* clone() const override { return new ConditionSoul(*this); } //serialization - void serialize(PropWriteStream& propWriteStream) final; - bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; + void serialize(PropWriteStream& propWriteStream) override; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; - protected: + private: uint32_t internalSoulTicks = 0; uint32_t soulTicks = 0; uint32_t soulGain = 0; @@ -232,13 +237,13 @@ class ConditionSoul final : public ConditionGeneric 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) {} + ConditionInvisible(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0) : + ConditionGeneric(id, type, ticks, buff, subId) {} - bool startCondition(Creature* creature) final; - void endCondition(Creature* creature) final; + bool startCondition(Creature* creature) override; + void endCondition(Creature* creature) override; - ConditionInvisible* clone() const final { + ConditionInvisible* clone() const override { return new ConditionInvisible(*this); } }; @@ -247,133 +252,170 @@ 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; - } else if (type == CONDITION_DROWN) { - count = max_count = 3; - } - } + ConditionDamage(ConditionId_t id, ConditionType_t type, bool buff = false, uint32_t subId = 0) : + Condition(id, type, 0, buff, 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; - uint32_t getIcons() const final; + static void generateDamageList(int32_t amount, int32_t start, std::list& list); - ConditionDamage* clone() const final { + 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; + + ConditionDamage* clone() const override { return new ConditionDamage(*this); } - bool setParam(ConditionParam_t param, int32_t value) final; + bool setParam(ConditionParam_t param, int32_t value) override; + bool addDamage(int32_t rounds, int32_t time, int32_t value); + bool doForceUpdate() const { + return forceUpdate; + } 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; - bool isFirstCycle = true; + void serialize(PropWriteStream& propWriteStream) override; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; + + private: + int32_t maxDamage = 0; + int32_t minDamage = 0; + int32_t startDamage = 0; + int32_t periodDamage = 0; + int32_t periodDamageTick = 0; + int32_t tickInterval = 2000; + + bool forceUpdate = false; + bool delayed = false; + bool field = false; uint32_t owner = 0; + bool init(); + + std::list damageList; + + bool getNextDamage(int32_t& damage); bool doDamage(Creature* creature, int32_t healthChange); - bool updateCondition(const Condition* addCondition) final; + bool updateCondition(const Condition* addCondition) override; }; 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) {} + ConditionSpeed(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff, uint32_t subId, int32_t changeSpeed) : + Condition(id, type, ticks, buff, 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; + 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; - ConditionSpeed* clone() const final { + ConditionSpeed* clone() const override { return new ConditionSpeed(*this); } - void setVariation(int32_t newVariation) { - variation = newVariation; - } - void setSpeedDelta(int32_t newSpeedDelta) { - speedDelta = newSpeedDelta; - } + bool setParam(ConditionParam_t param, int32_t value) override; + + void setFormulaVars(float mina, float minb, float maxa, float maxb); //serialization - void serialize(PropWriteStream& propWriteStream) final; - bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; + void serialize(PropWriteStream& propWriteStream) override; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; - protected: - int32_t appliedSpeedDelta = 0; - int32_t speedDelta = 0; - int32_t variation = 0; + private: + void getFormulaValues(int32_t var, int32_t& min, int32_t& max) const; + + int32_t speedDelta; + + //formula variables + float mina = 0.0f; + float minb = 0.0f; + float maxa = 0.0f; + float maxb = 0.0f; }; 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) {} + ConditionOutfit(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0) : + Condition(id, type, ticks, buff, 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 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; - ConditionOutfit* clone() const final { + ConditionOutfit* clone() const override { return new ConditionOutfit(*this); } void setOutfit(const Outfit_t& outfit); //serialization - void serialize(PropWriteStream& propWriteStream) final; - bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; + void serialize(PropWriteStream& propWriteStream) override; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; - protected: + private: 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) {} + ConditionLight(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff, uint32_t subId, uint8_t lightlevel, uint8_t lightcolor) : + Condition(id, type, ticks, buff, 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; + 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; - ConditionLight* clone() const final { + ConditionLight* clone() const override { return new ConditionLight(*this); } - bool setParam(ConditionParam_t param, int32_t value) final; + bool setParam(ConditionParam_t param, int32_t value) override; //serialization - void serialize(PropWriteStream& propWriteStream) final; - bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; + void serialize(PropWriteStream& propWriteStream) override; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; - protected: + private: LightInfo lightInfo; uint32_t internalLightTicks = 0; uint32_t lightChangeInterval = 0; }; +class ConditionSpellCooldown final : public ConditionGeneric +{ + public: + ConditionSpellCooldown(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0) : + ConditionGeneric(id, type, ticks, buff, subId) {} + + bool startCondition(Creature* creature) override; + void addCondition(Creature* creature, const Condition* condition) override; + + ConditionSpellCooldown* clone() const override { + return new ConditionSpellCooldown(*this); + } +}; + +class ConditionSpellGroupCooldown final : public ConditionGeneric +{ + public: + ConditionSpellGroupCooldown(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0) : + ConditionGeneric(id, type, ticks, buff, subId) {} + + bool startCondition(Creature* creature) override; + void addCondition(Creature* creature, const Condition* condition) override; + + ConditionSpellGroupCooldown* clone() const override { + return new ConditionSpellGroupCooldown(*this); + } +}; + #endif diff --git a/src/configmanager.cpp b/src/configmanager.cpp index 6610b74..3b41920 100644 --- a/src/configmanager.cpp +++ b/src/configmanager.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,6 +19,12 @@ #include "otpch.h" +#if __has_include("luajit/lua.hpp") +#include +#else +#include +#endif + #include "configmanager.h" #include "game.h" @@ -29,6 +35,57 @@ extern Game g_game; +namespace { + +std::string getGlobalString(lua_State* L, const char* identifier, const char* defaultValue) +{ + lua_getglobal(L, identifier); + if (!lua_isstring(L, -1)) { + lua_pop(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 getGlobalNumber(lua_State* L, const char* identifier, const int32_t defaultValue = 0) +{ + lua_getglobal(L, identifier); + if (!lua_isnumber(L, -1)) { + lua_pop(L, 1); + return defaultValue; + } + + int32_t val = lua_tonumber(L, -1); + lua_pop(L, 1); + return val; +} + +bool getGlobalBoolean(lua_State* L, const char* identifier, const bool defaultValue) +{ + lua_getglobal(L, identifier); + if (!lua_isboolean(L, -1)) { + if (!lua_isstring(L, -1)) { + lua_pop(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; +} + +} + bool ConfigManager::load() { lua_State* L = luaL_newstate(); @@ -63,9 +120,10 @@ bool ConfigManager::load() integer[GAME_PORT] = getGlobalNumber(L, "gameProtocolPort", 7172); integer[LOGIN_PORT] = getGlobalNumber(L, "loginProtocolPort", 7171); integer[STATUS_PORT] = getGlobalNumber(L, "statusProtocolPort", 7171); + + integer[MARKET_OFFER_DURATION] = getGlobalNumber(L, "marketOfferDuration", 30 * 24 * 60 * 60); } - 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[AIMBOT_HOTKEY_ENABLED] = getGlobalBoolean(L, "hotkeyAimbotEnabled", true); @@ -74,14 +132,19 @@ bool ConfigManager::load() boolean[FREE_PREMIUM] = getGlobalBoolean(L, "freePremium", false); boolean[REPLACE_KICK_ON_LOGIN] = getGlobalBoolean(L, "replaceKickOnLogin", true); boolean[ALLOW_CLONES] = getGlobalBoolean(L, "allowClones", false); + boolean[MARKET_PREMIUM] = getGlobalBoolean(L, "premiumToCreateMarketOffer", true); + boolean[EMOTE_SPELLS] = getGlobalBoolean(L, "emoteSpells", false); boolean[STAMINA_SYSTEM] = getGlobalBoolean(L, "staminaSystem", true); 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); - boolean[BLOCK_HEIGHT] = getGlobalBoolean(L, "blockHeight", false); - boolean[DROP_ITEMS] = getGlobalBoolean(L, "dropItems", false); - + boolean[CLASSIC_EQUIPMENT_SLOTS] = getGlobalBoolean(L, "classicEquipmentSlots", false); + boolean[CLASSIC_ATTACK_SPEED] = getGlobalBoolean(L, "classicAttackSpeed", false); + boolean[SCRIPTS_CONSOLE_LOGS] = getGlobalBoolean(L, "showScriptsLogInConsole", true); + boolean[SERVER_SAVE_NOTIFY_MESSAGE] = getGlobalBoolean(L, "serverSaveNotifyMessage", true); + boolean[SERVER_SAVE_CLEAN_MAP] = getGlobalBoolean(L, "serverSaveCleanMap", false); + boolean[SERVER_SAVE_CLOSE] = getGlobalBoolean(L, "serverSaveClose", false); + boolean[SERVER_SAVE_SHUTDOWN] = getGlobalBoolean(L, "serverSaveShutdown", true); + boolean[ONLINE_OFFLINE_CHARLIST] = getGlobalBoolean(L, "showOnlineStatusInCharlist", false); string[DEFAULT_PRIORITY] = getGlobalString(L, "defaultPriority", "high"); string[SERVER_NAME] = getGlobalString(L, "serverName", ""); @@ -101,7 +164,9 @@ bool ConfigManager::load() 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[HOUSE_PRICE] = getGlobalNumber(L, "housePriceEachSQM", 1000); + integer[KILLS_TO_RED] = getGlobalNumber(L, "killsToRedSkull", 3); + integer[KILLS_TO_BLACK] = getGlobalNumber(L, "killsToBlackSkull", 6); integer[ACTIONS_DELAY_INTERVAL] = getGlobalNumber(L, "timeBetweenActions", 200); integer[EX_ACTIONS_DELAY_INTERVAL] = getGlobalNumber(L, "timeBetweenExActions", 1000); integer[MAX_MESSAGEBUFFER] = getGlobalNumber(L, "maxMessageBuffer", 4); @@ -109,20 +174,14 @@ bool ConfigManager::load() 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[FRAG_TIME] = getGlobalNumber(L, "timeToDecreaseFrags", 24 * 60 * 60 * 1000); + integer[WHITE_SKULL_TIME] = getGlobalNumber(L, "whiteSkullTime", 15 * 60 * 1000); integer[STAIRHOP_DELAY] = getGlobalNumber(L, "stairJumpExhaustion", 2000); integer[EXP_FROM_PLAYERS_LEVEL_RANGE] = getGlobalNumber(L, "expFromPlayersLevelRange", 75); + integer[CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES] = getGlobalNumber(L, "checkExpiredMarketOffersEachMinutes", 60); + integer[MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER] = getGlobalNumber(L, "maxMarketOffersAtATimePerPlayer", 100); 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); + integer[SERVER_SAVE_NOTIFY_DURATION] = getGlobalNumber(L, "serverSaveNotifyDuration", 5); loaded = true; lua_close(L); @@ -138,11 +197,13 @@ bool ConfigManager::reload() return result; } +static std::string dummyStr; + 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 dummyStr; } return string[what]; } @@ -164,47 +225,3 @@ bool ConfigManager::getBoolean(boolean_config_t what) const } 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; -} diff --git a/src/configmanager.h b/src/configmanager.h index 2d7b0db..0b47566 100644 --- a/src/configmanager.h +++ b/src/configmanager.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,13 +20,10 @@ #ifndef FS_CONFIGMANAGER_H_6BDD23BD0B8344F4B7C40E8BE6AF6F39 #define FS_CONFIGMANAGER_H_6BDD23BD0B8344F4B7C40E8BE6AF6F39 -#include - class ConfigManager { public: enum boolean_config_t { - SHOW_MONSTER_LOOT, ALLOW_CHANGEOUTFIT, ONE_PLAYER_ON_ACCOUNT, AIMBOT_HOTKEY_ENABLED, @@ -37,19 +34,24 @@ class ConfigManager ALLOW_CLONES, BIND_ONLY_GLOBAL_ADDRESS, OPTIMIZE_DATABASE, + MARKET_PREMIUM, + EMOTE_SPELLS, STAMINA_SYSTEM, WARN_UNSAFE_SCRIPTS, CONVERT_UNSAFE_SCRIPTS, - TELEPORT_NEWBIES, - STACK_CUMULATIVES, - BLOCK_HEIGHT, - DROP_ITEMS, + CLASSIC_EQUIPMENT_SLOTS, + CLASSIC_ATTACK_SPEED, + SCRIPTS_CONSOLE_LOGS, + SERVER_SAVE_NOTIFY_MESSAGE, + SERVER_SAVE_CLEAN_MAP, + SERVER_SAVE_CLOSE, + SERVER_SAVE_SHUTDOWN, + ONLINE_OFFLINE_CHARLIST, LAST_BOOLEAN_CONFIG /* this must be the last one */ }; enum string_config_t { - DUMMY_STR, MAP_NAME, HOUSE_RENT_PERIOD, SERVER_NAME, @@ -82,7 +84,9 @@ class ConfigManager RATE_LOOT, RATE_MAGIC, RATE_SPAWN, - BAN_LENGTH, + HOUSE_PRICE, + KILLS_TO_RED, + KILLS_TO_BLACK, MAX_MESSAGEBUFFER, ACTIONS_DELAY_INTERVAL, EX_ACTIONS_DELAY_INTERVAL, @@ -90,23 +94,18 @@ class ConfigManager PROTECTION_LEVEL, DEATH_LOSE_PERCENT, STATUSQUERY_TIMEOUT, + FRAG_TIME, 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, + MARKET_OFFER_DURATION, + CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES, + MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER, EXP_FROM_PLAYERS_LEVEL_RANGE, MAX_PACKETS_PER_SECOND, - NEWBIE_TOWN, - NEWBIE_LEVEL_THRESHOLD, - MONEY_RATE, + SERVER_SAVE_NOTIFY_DURATION, LAST_INTEGER_CONFIG /* this must be the last one */ }; @@ -119,10 +118,6 @@ class ConfigManager 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] = {}; diff --git a/src/connection.cpp b/src/connection.cpp index dce33eb..2224742 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -182,51 +182,60 @@ void Connection::parsePacket(const boost::system::error_code& error) if (error) { close(FORCE_CLOSE); return; - } - else if (connectionState != CONNECTION_STATE_OPEN) { + } else if (connectionState != CONNECTION_STATE_OPEN) { return; } + //Check packet checksum + uint32_t checksum; + int32_t len = msg.getLength() - msg.getBufferPosition() - NetworkMessage::CHECKSUM_LENGTH; + if (len > 0) { + checksum = adlerChecksum(msg.getBuffer() + msg.getBufferPosition() + NetworkMessage::CHECKSUM_LENGTH, len); + } else { + checksum = 0; + } + + uint32_t recvChecksum = msg.get(); + if (recvChecksum != checksum) { + // it might not have been the checksum, step back + msg.skipBytes(-NetworkMessage::CHECKSUM_LENGTH); + } + 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()); + protocol = service_port->make_protocol(recvChecksum == checksum, msg, shared_from_this()); if (!protocol) { close(FORCE_CLOSE); return; } - } - else { + } else { msg.skipBytes(1); // Skip protocol ID } protocol->onRecvFirstMessage(msg); - } - else { + } 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(shared_from_this()), - std::placeholders::_1)); + 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) { + 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 lockClass(connectionLock); diff --git a/src/connection.h b/src/connection.h index b885749..36dc24d 100644 --- a/src/connection.h +++ b/src/connection.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,17 +28,17 @@ static constexpr int32_t CONNECTION_WRITE_TIMEOUT = 30; static constexpr int32_t CONNECTION_READ_TIMEOUT = 30; class Protocol; -typedef std::shared_ptr Protocol_ptr; +using Protocol_ptr = std::shared_ptr; class OutputMessage; -typedef std::shared_ptr OutputMessage_ptr; +using OutputMessage_ptr = std::shared_ptr; class Connection; -typedef std::shared_ptr Connection_ptr; -typedef std::weak_ptr ConnectionWeak_ptr; +using Connection_ptr = std::shared_ptr ; +using ConnectionWeak_ptr = std::weak_ptr; class ServiceBase; -typedef std::shared_ptr Service_ptr; +using Service_ptr = std::shared_ptr; class ServicePort; -typedef std::shared_ptr ServicePort_ptr; -typedef std::shared_ptr ConstServicePort_ptr; +using ServicePort_ptr = std::shared_ptr; +using ConstServicePort_ptr = std::shared_ptr; class ConnectionManager { @@ -52,7 +52,7 @@ class ConnectionManager void releaseConnection(const Connection_ptr& connection); void closeAll(); - protected: + private: ConnectionManager() = default; std::unordered_set connections; @@ -78,12 +78,8 @@ class Connection : public std::enable_shared_from_this 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); - } + socket(io_service), + timeConnected(time(nullptr)) {} ~Connection(); friend class ConnectionManager; @@ -128,10 +124,10 @@ class Connection : public std::enable_shared_from_this boost::asio::ip::tcp::socket socket; time_t timeConnected; - uint32_t packetsSent; + uint32_t packetsSent = 0; - bool connectionState; - bool receivedFirst; + bool connectionState = CONNECTION_STATE_OPEN; + bool receivedFirst = false; }; #endif diff --git a/src/const.h b/src/const.h index c4dffb2..412669a 100644 --- a/src/const.h +++ b/src/const.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -52,6 +52,67 @@ enum MagicEffectClasses : uint8_t { CONST_ME_SOUND_WHITE = 25, CONST_ME_BUBBLES = 26, CONST_ME_CRAPS = 27, + CONST_ME_GIFT_WRAPS = 28, + CONST_ME_FIREWORK_YELLOW = 29, + CONST_ME_FIREWORK_RED = 30, + CONST_ME_FIREWORK_BLUE = 31, + CONST_ME_STUN = 32, + CONST_ME_SLEEP = 33, + CONST_ME_WATERCREATURE = 34, + CONST_ME_GROUNDSHAKER = 35, + CONST_ME_HEARTS = 36, + CONST_ME_FIREATTACK = 37, + CONST_ME_ENERGYAREA = 38, + CONST_ME_SMALLCLOUDS = 39, + CONST_ME_HOLYDAMAGE = 40, + CONST_ME_BIGCLOUDS = 41, + CONST_ME_ICEAREA = 42, + CONST_ME_ICETORNADO = 43, + CONST_ME_ICEATTACK = 44, + CONST_ME_STONES = 45, + CONST_ME_SMALLPLANTS = 46, + CONST_ME_CARNIPHILA = 47, + CONST_ME_PURPLEENERGY = 48, + CONST_ME_YELLOWENERGY = 49, + CONST_ME_HOLYAREA = 50, + CONST_ME_BIGPLANTS = 51, + CONST_ME_CAKE = 52, + CONST_ME_GIANTICE = 53, + CONST_ME_WATERSPLASH = 54, + CONST_ME_PLANTATTACK = 55, + CONST_ME_TUTORIALARROW = 56, + CONST_ME_TUTORIALSQUARE = 57, + CONST_ME_MIRRORHORIZONTAL = 58, + CONST_ME_MIRRORVERTICAL = 59, + CONST_ME_SKULLHORIZONTAL = 60, + CONST_ME_SKULLVERTICAL = 61, + CONST_ME_ASSASSIN = 62, + CONST_ME_STEPSHORIZONTAL = 63, + CONST_ME_BLOODYSTEPS = 64, + CONST_ME_STEPSVERTICAL = 65, + CONST_ME_YALAHARIGHOST = 66, + CONST_ME_BATS = 67, + CONST_ME_SMOKE = 68, + CONST_ME_INSECTS = 69, + CONST_ME_DRAGONHEAD = 70, + CONST_ME_ORCSHAMAN = 71, + CONST_ME_ORCSHAMAN_FIRE = 72, + CONST_ME_THUNDER = 73, + CONST_ME_FERUMBRAS = 74, + CONST_ME_CONFETTI_HORIZONTAL = 75, + CONST_ME_CONFETTI_VERTICAL = 76, + // 77-157 are empty + CONST_ME_BLACKSMOKE = 158, + // 159-166 are empty + CONST_ME_REDSMOKE = 167, + CONST_ME_YELLOWSMOKE = 168, + CONST_ME_GREENSMOKE = 169, + CONST_ME_PURPLESMOKE = 170, + CONST_ME_EARLY_THUNDER = 171, + CONST_ME_RAGIAZ_BONECAPSULE = 172, + CONST_ME_CRITICAL_DAMAGE = 173, + // 174 is empty + CONST_ME_PLUNGING_FISH = 175, }; enum ShootType_t : uint8_t { @@ -72,75 +133,192 @@ enum ShootType_t : uint8_t { CONST_ANI_SNOWBALL = 13, CONST_ANI_POWERBOLT = 14, CONST_ANI_POISON = 15, + CONST_ANI_INFERNALBOLT = 16, + CONST_ANI_HUNTINGSPEAR = 17, + CONST_ANI_ENCHANTEDSPEAR = 18, + CONST_ANI_REDSTAR = 19, + CONST_ANI_GREENSTAR = 20, + CONST_ANI_ROYALSPEAR = 21, + CONST_ANI_SNIPERARROW = 22, + CONST_ANI_ONYXARROW = 23, + CONST_ANI_PIERCINGBOLT = 24, + CONST_ANI_WHIRLWINDSWORD = 25, + CONST_ANI_WHIRLWINDAXE = 26, + CONST_ANI_WHIRLWINDCLUB = 27, + CONST_ANI_ETHEREALSPEAR = 28, + CONST_ANI_ICE = 29, + CONST_ANI_EARTH = 30, + CONST_ANI_HOLY = 31, + CONST_ANI_SUDDENDEATH = 32, + CONST_ANI_FLASHARROW = 33, + CONST_ANI_FLAMMINGARROW = 34, + CONST_ANI_SHIVERARROW = 35, + CONST_ANI_ENERGYBALL = 36, + CONST_ANI_SMALLICE = 37, + CONST_ANI_SMALLHOLY = 38, + CONST_ANI_SMALLEARTH = 39, + CONST_ANI_EARTHARROW = 40, + CONST_ANI_EXPLOSION = 41, + CONST_ANI_CAKE = 42, + + CONST_ANI_TARSALARROW = 44, + CONST_ANI_VORTEXBOLT = 45, + + CONST_ANI_PRISMATICBOLT = 48, + CONST_ANI_CRYSTALLINEARROW = 49, + CONST_ANI_DRILLBOLT = 50, + CONST_ANI_ENVENOMEDARROW = 51, + + CONST_ANI_GLOOTHSPEAR = 53, + CONST_ANI_SIMPLEARROW = 54, + + // for internal use, don't send to client + CONST_ANI_WEAPONTYPE = 0xFE, // 254 }; 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, + TALKTYPE_PRIVATE_FROM = 4, + TALKTYPE_PRIVATE_TO = 5, + TALKTYPE_CHANNEL_Y = 7, + TALKTYPE_CHANNEL_O = 8, + TALKTYPE_PRIVATE_NP = 10, + TALKTYPE_PRIVATE_PN = 12, + TALKTYPE_BROADCAST = 13, + TALKTYPE_CHANNEL_R1 = 14, //red - #c text + TALKTYPE_PRIVATE_RED_FROM = 15, //@name@text + TALKTYPE_PRIVATE_RED_TO = 16, //@name@text + TALKTYPE_MONSTER_SAY = 36, + TALKTYPE_MONSTER_YELL = 37, + + TALKTYPE_CHANNEL_R2 = 0xFF, //#d }; 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_STATUS_CONSOLE_BLUE = 4, /*FIXME Blue message in the console*/ - MESSAGE_CLASS_FIRST = MESSAGE_STATUS_CONSOLE_YELLOW, - MESSAGE_CLASS_LAST = MESSAGE_STATUS_CONSOLE_RED, + MESSAGE_STATUS_CONSOLE_RED = 13, /*Red message in the console*/ + + MESSAGE_STATUS_DEFAULT = 17, /*White message at the bottom of the game window and in the console*/ + MESSAGE_STATUS_WARNING = 18, /*Red message in game window and in the console*/ + MESSAGE_EVENT_ADVANCE = 19, /*White message in game window and in the console*/ + + MESSAGE_STATUS_SMALL = 21, /*White message at the bottom of the game window"*/ + MESSAGE_INFO_DESCR = 22, /*Green message in game window and in the console*/ + MESSAGE_DAMAGE_DEALT = 23, + MESSAGE_DAMAGE_RECEIVED = 24, + MESSAGE_HEALED = 25, + MESSAGE_EXPERIENCE = 26, + MESSAGE_DAMAGE_OTHERS = 27, + MESSAGE_HEALED_OTHERS = 28, + MESSAGE_EXPERIENCE_OTHERS = 29, + MESSAGE_EVENT_DEFAULT = 30, /*White message at the bottom of the game window and in the console*/ + MESSAGE_LOOT = 31, + + MESSAGE_GUILD = 33, /*White message in channel (+ channelId)*/ + MESSAGE_PARTY_MANAGEMENT = 34, /*White message in channel (+ channelId)*/ + MESSAGE_PARTY = 35, /*White message in channel (+ channelId)*/ + MESSAGE_EVENT_ORANGE = 36, /*Orange message in the console*/ + MESSAGE_STATUS_CONSOLE_ORANGE = 37, /*Orange message in the console*/ }; -enum FluidTypes_t : uint8_t -{ - FLUID_NONE = 0, +enum FluidColors_t : uint8_t { + FLUID_EMPTY, + FLUID_BLUE, + FLUID_RED, + FLUID_BROWN, + FLUID_GREEN, + FLUID_YELLOW, + FLUID_WHITE, + FLUID_PURPLE, +}; + +enum FluidTypes_t : uint8_t { + FLUID_NONE = FLUID_EMPTY, + FLUID_WATER = FLUID_BLUE, + FLUID_BLOOD = FLUID_RED, + FLUID_BEER = FLUID_BROWN, + FLUID_SLIME = FLUID_GREEN, + FLUID_LEMONADE = FLUID_YELLOW, + FLUID_MILK = FLUID_WHITE, + FLUID_MANA = FLUID_PURPLE, + + FLUID_LIFE = FLUID_RED + 8, + FLUID_OIL = FLUID_BROWN + 8, + FLUID_URINE = FLUID_YELLOW + 8, + FLUID_COCONUTMILK = FLUID_WHITE + 8, + FLUID_WINE = FLUID_PURPLE + 8, + + FLUID_MUD = FLUID_BROWN + 16, + FLUID_FRUITJUICE = FLUID_YELLOW + 16, + + FLUID_LAVA = FLUID_RED + 24, + FLUID_RUM = FLUID_BROWN + 24, + FLUID_SWAMP = FLUID_GREEN + 24, + + FLUID_TEA = FLUID_BROWN + 32, + + FLUID_MEAD = FLUID_BROWN + 40, +}; + +const uint8_t reverseFluidMap[] = { + FLUID_EMPTY, FLUID_WATER, - FLUID_WINE, + FLUID_MANA, + FLUID_BEER, + FLUID_EMPTY, + FLUID_BLOOD, + FLUID_SLIME, + FLUID_EMPTY, + FLUID_LEMONADE, + FLUID_MILK, +}; + +const uint8_t clientToServerFluidMap[] = { + FLUID_EMPTY, + FLUID_WATER, + FLUID_MANA, FLUID_BEER, FLUID_MUD, FLUID_BLOOD, FLUID_SLIME, - FLUID_OIL, - FLUID_URINE, - FLUID_MILK, - FLUID_MANAFLUID, - FLUID_LIFEFLUID, - FLUID_LEMONADE, FLUID_RUM, - FLUID_COCONUTMILK, + FLUID_LEMONADE, + FLUID_MILK, + FLUID_WINE, + FLUID_LIFE, + FLUID_URINE, + FLUID_OIL, FLUID_FRUITJUICE, + FLUID_COCONUTMILK, + FLUID_TEA, + FLUID_MEAD, }; -enum FluidColor_t : uint8_t -{ - FLUID_COLOR_NONE = 0, - FLUID_COLOR_BLUE = 1, - FLUID_COLOR_PURPLE = 2, - FLUID_COLOR_BROWN = 3, - FLUID_COLOR_BROWN1 = 4, - FLUID_COLOR_RED = 5, - FLUID_COLOR_GREEN = 6, - FLUID_COLOR_BROWN2 = 7, - FLUID_COLOR_YELLOW = 8, - FLUID_COLOR_WHITE = 9, +enum ClientFluidTypes_t : uint8_t { + CLIENTFLUID_EMPTY = 0, + CLIENTFLUID_BLUE = 1, + CLIENTFLUID_PURPLE = 2, + CLIENTFLUID_BROWN_1 = 3, + CLIENTFLUID_BROWN_2 = 4, + CLIENTFLUID_RED = 5, + CLIENTFLUID_GREEN = 6, + CLIENTFLUID_BROWN = 7, + CLIENTFLUID_YELLOW = 8, + CLIENTFLUID_WHITE = 9, +}; + +const uint8_t fluidMap[] = { + CLIENTFLUID_EMPTY, + CLIENTFLUID_BLUE, + CLIENTFLUID_RED, + CLIENTFLUID_BROWN_1, + CLIENTFLUID_GREEN, + CLIENTFLUID_YELLOW, + CLIENTFLUID_WHITE, + CLIENTFLUID_PURPLE, }; enum SquareColor_t : uint8_t { @@ -155,8 +333,10 @@ enum TextColor_t : uint8_t { TEXTCOLOR_DARKRED = 108, TEXTCOLOR_LIGHTGREY = 129, TEXTCOLOR_SKYBLUE = 143, - TEXTCOLOR_PURPLE = 155, + TEXTCOLOR_PURPLE = 154, + TEXTCOLOR_ELECTRICPURPLE = 155, TEXTCOLOR_RED = 180, + TEXTCOLOR_PASTELRED = 194, TEXTCOLOR_ORANGE = 198, TEXTCOLOR_YELLOW = 210, TEXTCOLOR_WHITE_EXP = 215, @@ -173,6 +353,13 @@ enum Icons_t { ICON_HASTE = 1 << 6, ICON_SWORDS = 1 << 7, ICON_DROWNING = 1 << 8, + ICON_FREEZING = 1 << 9, + ICON_DAZZLED = 1 << 10, + ICON_CURSED = 1 << 11, + ICON_PARTY_BUFF = 1 << 12, + ICON_REDSWORDS = 1 << 13, + ICON_PIGEON = 1 << 14, + ICON_BLEEDING = 1 << 15, }; enum WeaponType_t : uint8_t { @@ -205,6 +392,7 @@ enum WeaponAction_t : uint8_t { }; enum WieldInfo_t { + WIELDINFO_NONE = 0 << 0, WIELDINFO_LEVEL = 1 << 0, WIELDINFO_MAGLV = 1 << 1, WIELDINFO_VOCREQ = 1 << 2, @@ -217,6 +405,8 @@ enum Skulls_t : uint8_t { SKULL_GREEN = 2, SKULL_WHITE = 3, SKULL_RED = 4, + SKULL_BLACK = 5, + SKULL_ORANGE = 6, }; enum PartyShields_t : uint8_t { @@ -224,54 +414,97 @@ enum PartyShields_t : uint8_t { SHIELD_WHITEYELLOW = 1, SHIELD_WHITEBLUE = 2, SHIELD_BLUE = 3, - SHIELD_YELLOW = 4 + SHIELD_YELLOW = 4, + SHIELD_BLUE_SHAREDEXP = 5, + SHIELD_YELLOW_SHAREDEXP = 6, + SHIELD_BLUE_NOSHAREDEXP_BLINK = 7, + SHIELD_YELLOW_NOSHAREDEXP_BLINK = 8, + SHIELD_BLUE_NOSHAREDEXP = 9, + SHIELD_YELLOW_NOSHAREDEXP = 10, + SHIELD_GRAY = 11, +}; + +enum GuildEmblems_t : uint8_t { + GUILDEMBLEM_NONE = 0, + GUILDEMBLEM_ALLY = 1, + GUILDEMBLEM_ENEMY = 2, + GUILDEMBLEM_NEUTRAL = 3, + GUILDEMBLEM_MEMBER = 4, + GUILDEMBLEM_OTHER = 5, }; 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_BROWSEFIELD = 460, // for internal use - ITEM_POISONFIELD_PVP = 2121, - ITEM_POISONFIELD_PERSISTENT = 2127, - ITEM_POISONFIELD_NOPVP = 2134, + ITEM_FIREFIELD_PVP_FULL = 1487, + ITEM_FIREFIELD_PVP_MEDIUM = 1488, + ITEM_FIREFIELD_PVP_SMALL = 1489, + ITEM_FIREFIELD_PERSISTENT_FULL = 1492, + ITEM_FIREFIELD_PERSISTENT_MEDIUM = 1493, + ITEM_FIREFIELD_PERSISTENT_SMALL = 1494, + ITEM_FIREFIELD_NOPVP = 1500, - ITEM_ENERGYFIELD_PVP = 2122, - ITEM_ENERGYFIELD_PERSISTENT = 2126, - ITEM_ENERGYFIELD_NOPVP = 2135, + ITEM_POISONFIELD_PVP = 1490, + ITEM_POISONFIELD_PERSISTENT = 1496, + ITEM_POISONFIELD_NOPVP = 1503, - ITEM_MAGICWALL = 2128, - ITEM_MAGICWALL_PERSISTENT = 2128, + ITEM_ENERGYFIELD_PVP = 1491, + ITEM_ENERGYFIELD_PERSISTENT = 1495, + ITEM_ENERGYFIELD_NOPVP = 1504, - ITEM_WILDGROWTH = 2130, - ITEM_WILDGROWTH_PERSISTENT = 2130, + ITEM_MAGICWALL = 1497, + ITEM_MAGICWALL_PERSISTENT = 1498, + ITEM_MAGICWALL_SAFE = 11098, - ITEM_GOLD_COIN = 3031, - ITEM_PLATINUM_COIN = 3035, - ITEM_CRYSTAL_COIN = 3043, + ITEM_WILDGROWTH = 1499, + ITEM_WILDGROWTH_PERSISTENT = 2721, + ITEM_WILDGROWTH_SAFE = 11099, - ITEM_DEPOT = 3502, - ITEM_LOCKER1 = 3497, + ITEM_BAG = 1987, + ITEM_SHOPPING_BAG = 23782, - ITEM_MALE_CORPSE = 4240, - ITEM_FEMALE_CORPSE = 4247, + ITEM_GOLD_COIN = 2148, + ITEM_PLATINUM_COIN = 2152, + ITEM_CRYSTAL_COIN = 2160, + ITEM_STORE_COIN = 24774, // in-game store currency - ITEM_FULLSPLASH = 2886, - ITEM_SMALLSPLASH = 2889, + ITEM_DEPOT = 2594, + ITEM_LOCKER1 = 2589, + ITEM_INBOX = 14404, + ITEM_MARKET = 14405, + ITEM_STORE_INBOX = 26052, + ITEM_DEPOT_BOX_I = 25453, + ITEM_DEPOT_BOX_II = 25454, + ITEM_DEPOT_BOX_III = 25455, + ITEM_DEPOT_BOX_IV = 25456, + ITEM_DEPOT_BOX_V = 25457, + ITEM_DEPOT_BOX_VI = 25458, + ITEM_DEPOT_BOX_VII = 25459, + ITEM_DEPOT_BOX_VIII = 25460, + ITEM_DEPOT_BOX_IX = 25461, + ITEM_DEPOT_BOX_X = 25462, + ITEM_DEPOT_BOX_XI = 25463, + ITEM_DEPOT_BOX_XII = 25464, + ITEM_DEPOT_BOX_XIII = 25465, + ITEM_DEPOT_BOX_XIV = 25466, + ITEM_DEPOT_BOX_XV = 25467, + ITEM_DEPOT_BOX_XVI = 25468, + ITEM_DEPOT_BOX_XVII = 25469, - ITEM_PARCEL = 3503, - ITEM_PARCEL_STAMPED = 3504, - ITEM_LETTER = 3505, - ITEM_LETTER_STAMPED = 3506, - ITEM_LABEL = 3507, + ITEM_MALE_CORPSE = 3058, + ITEM_FEMALE_CORPSE = 3065, - ITEM_AMULETOFLOSS = 3057, + ITEM_FULLSPLASH = 2016, + ITEM_SMALLSPLASH = 2019, - ITEM_DOCUMENT_RO = 2819, //read-only + ITEM_PARCEL = 2595, + ITEM_LETTER = 2597, + ITEM_LETTER_STAMPED = 2598, + ITEM_LABEL = 2599, + + ITEM_AMULETOFLOSS = 2173, + + ITEM_DOCUMENT_RO = 1968, //read-only }; enum PlayerFlags : uint64_t { @@ -313,14 +546,12 @@ enum PlayerFlags : uint64_t { PlayerFlag_IgnoreWeaponCheck = static_cast(1) << 35, PlayerFlag_CannotBeMuted = static_cast(1) << 36, PlayerFlag_IsAlwaysPremium = static_cast(1) << 37, - PlayerFlag_SpecialMoveUse = static_cast(1) << 38, }; -enum ReloadTypes_t : uint8_t { +enum ReloadTypes_t : uint8_t { RELOAD_TYPE_ALL, RELOAD_TYPE_ACTIONS, RELOAD_TYPE_CHAT, - RELOAD_TYPE_COMMANDS, RELOAD_TYPE_CONFIG, RELOAD_TYPE_CREATURESCRIPTS, RELOAD_TYPE_EVENTS, @@ -333,6 +564,7 @@ enum ReloadTypes_t : uint8_t { RELOAD_TYPE_NPCS, RELOAD_TYPE_QUESTS, RELOAD_TYPE_RAIDS, + RELOAD_TYPE_SCRIPTS, RELOAD_TYPE_SPELLS, RELOAD_TYPE_TALKACTIONS, RELOAD_TYPE_WEAPONS, @@ -340,7 +572,6 @@ enum ReloadTypes_t : uint8_t { 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; //Reserved player storage key ranges; @@ -350,6 +581,11 @@ static constexpr int32_t PSTRG_RESERVED_RANGE_SIZE = 10000000; //[1000 - 1500]; static constexpr int32_t PSTRG_OUTFITS_RANGE_START = (PSTRG_RESERVED_RANGE_START + 1000); static constexpr int32_t PSTRG_OUTFITS_RANGE_SIZE = 500; +//[2001 - 2011]; +static constexpr int32_t PSTRG_MOUNTS_RANGE_START = (PSTRG_RESERVED_RANGE_START + 2001); +static constexpr int32_t PSTRG_MOUNTS_RANGE_SIZE = 10; +static constexpr int32_t PSTRG_MOUNTS_CURRENTMOUNT = (PSTRG_MOUNTS_RANGE_START + 10); + #define IS_IN_KEYRANGE(key, range) (key >= PSTRG_##range##_START && ((key - PSTRG_##range##_START) <= PSTRG_##range##_SIZE)) diff --git a/src/container.cpp b/src/container.cpp index c694871..dfcecad 100644 --- a/src/container.cpp +++ b/src/container.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,16 +28,41 @@ extern Game g_game; Container::Container(uint16_t type) : Container(type, items[type].maxItems) {} -Container::Container(uint16_t type, uint16_t size) : +Container::Container(uint16_t type, uint16_t size, bool unlocked /*= true*/, bool pagination /*= false*/) : Item(type), - maxSize(size) + maxSize(size), + unlocked(unlocked), + pagination(pagination) {} +Container::Container(Tile* tile) : Container(ITEM_BROWSEFIELD, 30, false, true) +{ + TileItemVector* itemVector = tile->getItemList(); + if (itemVector) { + for (Item* item : *itemVector) { + if ((item->getContainer() || item->hasProperty(CONST_PROP_MOVEABLE)) && !item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { + itemlist.push_front(item); + item->setParent(this); + } + } + } + + setParent(tile); +} + Container::~Container() { - for (Item* item : itemlist) { - item->setParent(nullptr); - item->decrementReferenceCounter(); + if (getID() == ITEM_BROWSEFIELD) { + g_game.browseFields.erase(getTile()); + + for (Item* item : itemlist) { + item->setParent(parent); + } + } else { + for (Item* item : itemlist) { + item->setParent(nullptr); + item->decrementReferenceCounter(); + } } } @@ -62,7 +87,7 @@ Container* Container::getParentContainer() bool Container::hasParent() const { - return dynamic_cast(getParent()) != nullptr; + return getID() != ITEM_BROWSEFIELD && dynamic_cast(getParent()) == nullptr; } void Container::addItem(Item* item) @@ -82,24 +107,22 @@ Attr_ReadValue Container::readAttr(AttrTypes_t attr, PropStream& propStream) return Item::readAttr(attr, propStream); } -bool Container::unserializeItemNode(FileLoader& f, NODE node, PropStream& propStream) +bool Container::unserializeItemNode(OTB::Loader& loader, const OTB::Node& node, PropStream& propStream) { - bool ret = Item::unserializeItemNode(f, node, propStream); + bool ret = Item::unserializeItemNode(loader, node, propStream); if (!ret) { return false; } - uint32_t type; - NODE nodeItem = f.getChildNode(node, type); - while (nodeItem) { + for (auto& itemNode : node.children) { //load container items - if (type != OTBM_ITEM) { + if (itemNode.type != OTBM_ITEM) { // unknown type return false; } PropStream itemPropStream; - if (!f.getProps(nodeItem, itemPropStream)) { + if (!loader.getProps(itemNode, itemPropStream)) { return false; } @@ -108,14 +131,12 @@ bool Container::unserializeItemNode(FileLoader& f, NODE node, PropStream& propSt return false; } - if (!item->unserializeItemNode(f, nodeItem, itemPropStream)) { + if (!item->unserializeItemNode(loader, itemNode, itemPropStream)) { return false; } addItem(item); updateItemWeight(item->getWeight()); - - nodeItem = f.getNextNode(nodeItem, type); } return true; } @@ -194,48 +215,48 @@ bool Container::isHoldingItem(const Item* item) const void Container::onAddContainerItem(Item* item) { - SpectatorVec list; - g_game.map.getSpectators(list, getPosition(), false, true, 2, 2, 2, 2); + SpectatorVec spectators; + g_game.map.getSpectators(spectators, getPosition(), false, true, 2, 2, 2, 2); //send to client - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { spectator->getPlayer()->sendAddContainerItem(this, item); } //event methods - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { 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); + SpectatorVec spectators; + g_game.map.getSpectators(spectators, getPosition(), false, true, 2, 2, 2, 2); //send to client - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { spectator->getPlayer()->sendUpdateContainerItem(this, index, newItem); } //event methods - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { 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); + SpectatorVec spectators; + g_game.map.getSpectators(spectators, getPosition(), false, true, 2, 2, 2, 2); //send change to client - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { spectator->getPlayer()->sendRemoveContainerItem(this, index); } //event methods - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { spectator->getPlayer()->onRemoveContainerItem(this, item); } } @@ -250,6 +271,10 @@ ReturnValue Container::queryAdd(int32_t index, const Thing& thing, uint32_t coun return RETURNVALUE_NOERROR; } + if (!unlocked) { + return RETURNVALUE_NOTPOSSIBLE; + } + const Item* item = thing.getItem(); if (item == nullptr) { return RETURNVALUE_NOTPOSSIBLE; @@ -270,6 +295,10 @@ ReturnValue Container::queryAdd(int32_t index, const Thing& thing, uint32_t coun return RETURNVALUE_THISISIMPOSSIBLE; } + if (dynamic_cast(cylinder)) { + return RETURNVALUE_CONTAINERNOTENOUGHROOM; + } + cylinder = cylinder->getParent(); } @@ -326,7 +355,7 @@ ReturnValue Container::queryMaxCount(int32_t index, const Thing& thing, uint32_t } } else { const Item* destItem = getItemByIndex(index); - if (item->equals(destItem) && !destItem->isRune() && destItem->getItemCount() < 100) { + if (item->equals(destItem) && destItem->getItemCount() < 100) { uint32_t remainder = 100 - destItem->getItemCount(); if (queryAdd(index, *item, remainder, flags) == RETURNVALUE_NOERROR) { n = remainder; @@ -369,9 +398,14 @@ ReturnValue Container::queryRemove(const Thing& thing, uint32_t count, uint32_t return RETURNVALUE_NOERROR; } -Cylinder* Container::queryDestination(int32_t& index, const Thing &thing, Item** destItem, - uint32_t& flags) +Cylinder* Container::queryDestination(int32_t& index, const Thing& thing, Item** destItem, + uint32_t& flags) { + if (!unlocked) { + *destItem = nullptr; + return this; + } + if (index == 254 /*move up*/) { index = INDEX_WHEREEVER; *destItem = nullptr; @@ -386,8 +420,7 @@ Cylinder* Container::queryDestination(int32_t& index, const Thing &thing, Item** if (index == 255 /*add wherever*/) { index = INDEX_WHEREEVER; *destItem = nullptr; - } - else if (index >= static_cast(capacity())) { + } else if (index >= static_cast(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 @@ -418,22 +451,19 @@ Cylinder* Container::queryDestination(int32_t& index, const Thing &thing, Item** } } - 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; + 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; } } - return this; } diff --git a/src/container.h b/src/container.h index 2330c26..c4a6838 100644 --- a/src/container.h +++ b/src/container.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,6 +26,7 @@ #include "item.h" class Container; +class DepotChest; class DepotLocker; class ContainerIterator @@ -38,7 +39,7 @@ class ContainerIterator void advance(); Item* operator*(); - protected: + private: std::list over; ItemDeque::const_iterator cur; @@ -49,31 +50,32 @@ class Container : public Item, public Cylinder { public: explicit Container(uint16_t type); - Container(uint16_t type, uint16_t size); + Container(uint16_t type, uint16_t size, bool unlocked = true, bool pagination = false); + explicit Container(Tile* tile); ~Container(); // non-copyable Container(const Container&) = delete; Container& operator=(const Container&) = delete; - Item* clone() const final; + Item* clone() const override final; - Container* getContainer() final { + Container* getContainer() override final { return this; } - const Container* getContainer() const final { + const Container* getContainer() const override final { return this; } - virtual DepotLocker* getDepotLocker() override { + virtual DepotLocker* getDepotLocker() { return nullptr; } - virtual const DepotLocker* getDepotLocker() const override { + 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; + bool unserializeItemNode(OTB::Loader& loader, const OTB::Node& node, PropStream& propStream) override; std::string getContentDescription() const; size_t size() const { @@ -105,41 +107,60 @@ class Container : public Item, public Cylinder bool isHoldingItem(const Item* item) const; uint32_t getItemHoldingCount() const; - uint32_t getWeight() const final; + uint32_t getWeight() const override final; + + bool isUnlocked() const { + return unlocked; + } + bool hasPagination() const { + return pagination; + } //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; + uint32_t flags) const override final; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const override final; Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, - uint32_t& flags) final; + uint32_t& flags) override final; - void addThing(Thing* thing) final; - void addThing(int32_t index, Thing* thing) final; + void addThing(Thing* thing) override final; + void addThing(int32_t index, Thing* thing) override 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 updateThing(Thing* thing, uint16_t itemId, uint32_t count) override final; + void replaceThing(uint32_t index, Thing* thing) override final; - void removeThing(Thing* thing, uint32_t count) final; + void removeThing(Thing* thing, uint32_t count) override 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& getAllItemTypeCount(std::map& countMap) const final; - Thing* getThing(size_t index) const final; + int32_t getThingIndex(const Thing* thing) const override final; + size_t getFirstIndex() const override final; + size_t getLastIndex() const override final; + uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const override final; + std::map& getAllItemTypeCount(std::map& countMap) const override final; + Thing* getThing(size_t index) const override 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; + void internalAddThing(Thing* thing) override final; + void internalAddThing(uint32_t index, Thing* thing) override final; + void startDecaying() override final; + + protected: + ItemDeque itemlist; private: + std::ostringstream& getContentDescription(std::ostringstream& os) const; + + uint32_t maxSize; + uint32_t totalWeight = 0; + uint32_t serializationCount = 0; + + bool unlocked; + bool pagination; + void onAddContainerItem(Item* item); void onUpdateContainerItem(uint32_t index, Item* oldItem, Item* newItem); void onRemoveContainerItem(uint32_t index, Item* item); @@ -147,14 +168,6 @@ class Container : public Item, public Cylinder 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; }; diff --git a/src/creature.cpp b/src/creature.cpp index da8bf6d..81dd0ff 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,6 +25,10 @@ #include "configmanager.h" #include "scheduler.h" +double Creature::speedA = 857.36; +double Creature::speedB = 261.29; +double Creature::speedC = -4795.01; + extern Game g_game; extern ConfigManager g_config; extern CreatureEvents* g_creatureEvents; @@ -38,8 +42,7 @@ Creature::~Creature() { for (Creature* summon : summons) { summon->setAttackedCreature(nullptr); - summon->setMaster(nullptr); - summon->decrementReferenceCounter(); + summon->removeMaster(); } for (Condition* condition : conditions) { @@ -227,7 +230,7 @@ void Creature::onWalk(Direction& dir) if (r < DIRECTION_DIAGONAL_MASK) { dir = static_cast(r); } - g_game.internalCreatureSay(this, TALKTYPE_SAY, "Hicks!", false); + g_game.internalCreatureSay(this, TALKTYPE_MONSTER_SAY, "Hicks!", false); } } } @@ -307,7 +310,7 @@ void Creature::updateMapCache() void Creature::updateTileCache(const Tile* tile, int32_t dx, int32_t dy) { if (std::abs(dx) <= maxWalkCacheWidth && std::abs(dy) <= maxWalkCacheHeight) { - localMapCache[maxWalkCacheHeight + dy][maxWalkCacheWidth + dx] = tile && tile->queryAdd(0, *this, 1, FLAG_PATHFINDING) == RETURNVALUE_NOERROR; + localMapCache[maxWalkCacheHeight + dy][maxWalkCacheWidth + dx] = tile && tile->queryAdd(0, *this, 1, FLAG_PATHFINDING | FLAG_IGNOREFIELDDAMAGE) == RETURNVALUE_NOERROR; } } @@ -409,7 +412,7 @@ void Creature::onRemoveCreature(Creature* creature, bool) onCreatureDisappear(creature, true); if (creature == this) { if (master && !master->isRemoved()) { - master->removeSummon(this); + setMaster(nullptr); } } else if (isMapLoaded) { if (creature->getPosition().z == getPosition().z) { @@ -582,11 +585,6 @@ void Creature::onCreatureMove(Creature* creature, const Tile* newTile, const Pos if (creature == followCreature || (creature == this && followCreature)) { if (hasFollowPath) { isUpdatingPath = true; - // this updates following walking - if (lastWalkUpdate == 0 || OTSYS_TIME() - lastWalkUpdate >= 250) { - g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, &g_game, getID()))); - lastWalkUpdate = OTSYS_TIME(); - } } if (newPos.z != oldPos.z || !canSee(followCreature->getPosition())) { @@ -619,8 +617,7 @@ void Creature::onDeath() if (lastHitCreature) { lastHitUnjustified = lastHitCreature->onKilledCreature(this); lastHitCreatureMaster = lastHitCreature->getMaster(); - } - else { + } else { lastHitCreatureMaster = nullptr; } @@ -642,6 +639,7 @@ void Creature::onDeath() uint64_t gainExp = getGainedExperience(attacker); if (Player* attackerPlayer = attacker->getPlayer()) { attackerPlayer->removeAttacked(getPlayer()); + Party* party = attackerPlayer->getParty(); if (party && party->getLeader() && party->isSharedExperienceActive() && party->isSharedExperienceEnabled()) { attacker = party->getLeader(); @@ -651,8 +649,7 @@ void Creature::onDeath() auto tmpIt = experienceMap.find(attacker); if (tmpIt == experienceMap.end()) { experienceMap[attacker] = gainExp; - } - else { + } else { tmpIt->second += gainExp; } } @@ -676,7 +673,7 @@ void Creature::onDeath() death(lastHitCreature); if (master) { - master->removeSummon(this); + setMaster(nullptr); } if (droppedCorpse) { @@ -686,41 +683,53 @@ void Creature::onDeath() bool Creature::dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified) { - Item* splash; - switch (getRace()) { - case RACE_VENOM: - splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_SLIME); - break; + if (!lootDrop && getMonster()) { + if (master) { + //scripting event - onDeath + const CreatureEventList& deathEvents = getCreatureEvents(CREATURE_EVENT_DEATH); + for (CreatureEvent* deathEvent : deathEvents) { + deathEvent->executeOnDeath(this, nullptr, lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified); + } + } - case RACE_BLOOD: - splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_BLOOD); - break; + g_game.addMagicEffect(getPosition(), CONST_ME_POFF); + } else { + Item* splash; + switch (getRace()) { + case RACE_VENOM: + splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_SLIME); + break; - default: - splash = nullptr; - break; - } + case RACE_BLOOD: + splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_BLOOD); + break; - Tile* tile = getTile(); + default: + splash = nullptr; + break; + } - if (splash) { - g_game.internalAddItem(tile, splash, INDEX_WHEREEVER, FLAG_NOLIMIT); - g_game.startDecay(splash); - } + Tile* tile = getTile(); - Item* corpse = getCorpse(lastHitCreature, mostDamageCreature); - if (corpse) { - g_game.internalAddItem(tile, corpse, INDEX_WHEREEVER, FLAG_NOLIMIT); - g_game.startDecay(corpse); - } + if (splash) { + g_game.internalAddItem(tile, splash, INDEX_WHEREEVER, FLAG_NOLIMIT); + g_game.startDecay(splash); + } - //scripting event - onDeath - for (CreatureEvent* deathEvent : getCreatureEvents(CREATURE_EVENT_DEATH)) { - deathEvent->executeOnDeath(this, corpse, lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified); - } + Item* corpse = getCorpse(lastHitCreature, mostDamageCreature); + if (corpse) { + g_game.internalAddItem(tile, corpse, INDEX_WHEREEVER, FLAG_NOLIMIT); + g_game.startDecay(corpse); + } - if (corpse) { - dropLoot(corpse->getContainer(), lastHitCreature); + //scripting event - onDeath + for (CreatureEvent* deathEvent : getCreatureEvents(CREATURE_EVENT_DEATH)) { + deathEvent->executeOnDeath(this, corpse, lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified); + } + + if (corpse) { + dropLoot(corpse->getContainer(), lastHitCreature); + } } return true; @@ -781,28 +790,6 @@ BlockType_t Creature::blockHit(Creature* attacker, CombatType_t combatType, int3 damage = 0; blockType = BLOCK_IMMUNITY; } else if (checkDefense || checkArmor) { - if (checkDefense && OTSYS_TIME() >= earliestDefendTime) { - damage -= getDefense(); - - earliestDefendTime = lastDefendTime + 2000; - lastDefendTime = OTSYS_TIME(); - - if (damage <= 0) { - damage = 0; - blockType = BLOCK_DEFENSE; - } - } - - if (checkArmor) { - if (damage > 0 && combatType == COMBAT_PHYSICALDAMAGE) { - damage -= getArmor(); - if (damage <= 0) { - damage = 0; - blockType = BLOCK_ARMOR; - } - } - } - bool hasDefense = false; if (blockCount > 0) { @@ -810,6 +797,30 @@ BlockType_t Creature::blockHit(Creature* attacker, CombatType_t combatType, int3 hasDefense = true; } + if (checkDefense && hasDefense && canUseDefense) { + int32_t defense = getDefense(); + damage -= uniform_random(defense / 2, defense); + if (damage <= 0) { + damage = 0; + blockType = BLOCK_DEFENSE; + checkArmor = false; + } + } + + if (checkArmor) { + int32_t armor = getArmor(); + if (armor > 3) { + damage -= uniform_random(armor / 2, armor - (armor % 2 + 1)); + } else if (armor > 0) { + --damage; + } + + if (damage <= 0) { + damage = 0; + blockType = BLOCK_ARMOR; + } + } + if (hasDefense && blockType != BLOCK_NONE) { onBlockHit(); } @@ -833,16 +844,6 @@ bool Creature::setAttackedCreature(Creature* creature) return false; } - if (isSummon() && master) { - if (Monster* monster = master->getMonster()) { - if (monster->mType->info.targetDistance <= 1) { - if (!monster->hasFollowPath && !monster->followCreature) { - return false; - } - } - } - } - attackedCreature = creature; onAttackedCreature(attackedCreature); attackedCreature->onAttacked(); @@ -1026,9 +1027,21 @@ void Creature::onTickCondition(ConditionType_t type, bool& bRemove) case CONDITION_POISON: bRemove = (field->getCombatType() != COMBAT_EARTHDAMAGE); break; + case CONDITION_FREEZING: + bRemove = (field->getCombatType() != COMBAT_ICEDAMAGE); + break; + case CONDITION_DAZZLED: + bRemove = (field->getCombatType() != COMBAT_HOLYDAMAGE); + break; + case CONDITION_CURSED: + bRemove = (field->getCombatType() != COMBAT_DEATHDAMAGE); + break; case CONDITION_DROWN: bRemove = (field->getCombatType() != COMBAT_DROWNDAMAGE); break; + case CONDITION_BLEEDING: + bRemove = (field->getCombatType() != COMBAT_PHYSICALDAMAGE); + break; default: break; } @@ -1051,15 +1064,8 @@ void Creature::onAttackedCreatureDrainHealth(Creature* target, int32_t points) bool Creature::onKilledCreature(Creature* target, bool) { - if (latestKillEvent == target->getID()) { - return false; - } - - latestKillEvent = target->getID(); - if (master) { master->onKilledCreature(target); - return false; } //scripting event - onKill @@ -1079,28 +1085,43 @@ void Creature::onGainExperience(uint64_t gainExp, Creature* target) gainExp /= 2; master->onGainExperience(gainExp, target); - g_game.addAnimatedText(position, TEXTCOLOR_WHITE_EXP, std::to_string(gainExp)); -} - -void Creature::addSummon(Creature* creature) -{ - creature->setDropLoot(false); - creature->setLossSkill(false); - creature->setMaster(this); - creature->incrementReferenceCounter(); - summons.push_back(creature); -} - -void Creature::removeSummon(Creature* creature) -{ - auto cit = std::find(summons.begin(), summons.end(), creature); - if (cit != summons.end()) { - creature->setDropLoot(true); - creature->setLossSkill(true); - creature->setMaster(nullptr); - creature->decrementReferenceCounter(); - summons.erase(cit); + SpectatorVec spectators; + g_game.map.getSpectators(spectators, position, false, true); + if (spectators.empty()) { + return; } + + TextMessage message(MESSAGE_EXPERIENCE_OTHERS, ucfirst(getNameDescription()) + " gained " + std::to_string(gainExp) + (gainExp != 1 ? " experience points." : " experience point.")); + message.position = position; + message.primary.color = TEXTCOLOR_WHITE_EXP; + message.primary.value = gainExp; + + for (Creature* spectator : spectators) { + spectator->getPlayer()->sendTextMessage(message); + } +} + +bool Creature::setMaster(Creature* newMaster) { + if (!newMaster && !master) { + return false; + } + + if (newMaster) { + incrementReferenceCounter(); + newMaster->summons.push_back(this); + } + + Creature* oldMaster = master; + master = newMaster; + + if (oldMaster) { + auto summon = std::find(oldMaster->summons.begin(), oldMaster->summons.end(), this); + if (summon != oldMaster->summons.end()) { + oldMaster->summons.erase(summon); + decrementReferenceCounter(); + } + } + return true; } bool Creature::addCondition(Condition* condition, bool force/* = false*/) @@ -1259,20 +1280,21 @@ Condition* Creature::getCondition(ConditionType_t type, ConditionId_t conditionI void Creature::executeConditions(uint32_t interval) { - auto it = conditions.begin(), end = conditions.end(); - while (it != end) { - Condition* condition = *it; + ConditionList tempConditions{ conditions }; + for (Condition* condition : tempConditions) { + auto it = std::find(conditions.begin(), conditions.end(), condition); + if (it == conditions.end()) { + continue; + } + if (!condition->executeCondition(this, interval)) { - ConditionType_t type = condition->getType(); - - it = conditions.erase(it); - - condition->endCondition(this); - delete condition; - - onEndCondition(type); - } else { - ++it; + it = std::find(conditions.begin(), conditions.end(), condition); + if (it != conditions.end()) { + conditions.erase(it); + condition->endCondition(this); + onEndCondition(condition->getType()); + delete condition; + } } } } @@ -1289,7 +1311,7 @@ bool Creature::hasCondition(ConditionType_t type, uint32_t subId/* = 0*/) const continue; } - if (condition->getEndTime() >= timeNow) { + if (condition->getEndTime() >= timeNow || condition->getTicks() == -1) { return true; } } @@ -1326,8 +1348,18 @@ int64_t Creature::getStepDuration() const return 0; } + uint32_t calculatedStepSpeed; uint32_t groundSpeed; + int32_t stepSpeed = getStepSpeed(); + if (stepSpeed > -Creature::speedB) { + calculatedStepSpeed = floor((Creature::speedA * log((stepSpeed / 2) + Creature::speedB) + Creature::speedC) + 0.5); + if (calculatedStepSpeed == 0) { + calculatedStepSpeed = 1; + } + } else { + calculatedStepSpeed = 1; + } Item* ground = tile->getGround(); if (ground) { @@ -1339,12 +1371,12 @@ int64_t Creature::getStepDuration() const groundSpeed = 150; } - double duration = std::floor(1000 * groundSpeed) / stepSpeed; + double duration = std::floor(1000 * groundSpeed / calculatedStepSpeed); int64_t stepDuration = std::ceil(duration / 50) * 50; const Monster* monster = getMonster(); if (monster && monster->isTargetNearby() && !monster->isFleeing() && !monster->getMaster()) { - stepDuration *= 3; + stepDuration *= 2; } return stepDuration; @@ -1364,15 +1396,18 @@ int64_t Creature::getEventStepTicks(bool onlyDelay) const return ret; } -void Creature::getCreatureLight(LightInfo& light) const +LightInfo Creature::getCreatureLight() const { - light = internalLight; + return internalLight; +} + +void Creature::setCreatureLight(LightInfo lightInfo) { + internalLight = std::move(lightInfo); } void Creature::setNormalCreatureLight() { - internalLight.level = 0; - internalLight.color = 0; + internalLight = {}; } bool Creature::registerCreatureEvent(const std::string& name) @@ -1440,6 +1475,10 @@ CreatureEventList Creature::getCreatureEvents(CreatureEventType_t type) } for (CreatureEvent* creatureEvent : eventsList) { + if (!creatureEvent->isLoaded()) { + continue; + } + if (creatureEvent->getEventType() == type) { tmpEventList.push_back(creatureEvent); } diff --git a/src/creature.h b/src/creature.h index 26f5627..1d65ac2 100644 --- a/src/creature.h +++ b/src/creature.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,8 +28,8 @@ #include "enums.h" #include "creatureevent.h" -typedef std::list ConditionList; -typedef std::list CreatureEventList; +using ConditionList = std::list; +using CreatureEventList = std::list; enum slots_t : uint8_t { CONST_SLOT_WHEREEVER = 0, @@ -66,7 +66,6 @@ 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; @@ -83,7 +82,7 @@ class FrozenPathingConditionCall bool isInRange(const Position& startPos, const Position& testPos, const FindPathParams& fpp) const; - protected: + private: Position targetPos; }; @@ -97,16 +96,18 @@ class Creature : virtual public Thing Creature(); public: + static double speedA, speedB, speedC; + virtual ~Creature(); // non-copyable Creature(const Creature&) = delete; Creature& operator=(const Creature&) = delete; - Creature* getCreature() final { + Creature* getCreature() override final { return this; } - const Creature* getCreature() const final { + const Creature* getCreature() const override final { return this; } virtual Player* getPlayer() { @@ -131,6 +132,8 @@ class Creature : virtual public Thing virtual const std::string& getName() const = 0; virtual const std::string& getNameDescription() const = 0; + virtual CreatureType_t getType() const = 0; + virtual void setID() = 0; void setRemoved() { isInternalRemoved = true; @@ -169,13 +172,13 @@ class Creature : virtual public Thing hiddenHealth = b; } - int32_t getThrowRange() const final { + int32_t getThrowRange() const override final { return 1; } bool isPushable() const override { return getWalkDelay() <= 0; } - bool isRemoved() const final { + bool isRemoved() const override final { return isInternalRemoved; } virtual bool canSeeInvisibility() const { @@ -196,15 +199,11 @@ class Creature : virtual public Thing return getSpeed(); } int32_t getSpeed() const { - if (baseSpeed == 0) { - return 0; - } - - return (2 * (varSpeed + baseSpeed)) + 80; + return baseSpeed + varSpeed; } void setSpeed(int32_t varSpeedDelta) { int32_t oldSpeed = getSpeed(); - varSpeed += varSpeedDelta; + varSpeed = varSpeedDelta; if (getSpeed() <= 0) { stopEventWalk(); @@ -271,9 +270,15 @@ class Creature : virtual public Thing 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 setMaster(Creature* newMaster); + + void removeMaster() { + if (master) { + master = nullptr; + decrementReferenceCounter(); + } } + bool isSummon() const { return master != nullptr; } @@ -281,8 +286,6 @@ class Creature : virtual public Thing return master; } - void addSummon(Creature* creature); - void removeSummon(Creature* creature); const std::list& getSummons() const { return summons; } @@ -290,9 +293,19 @@ class Creature : virtual public Thing virtual int32_t getArmor() const { return 0; } - virtual int32_t getDefense() { + virtual int32_t getDefense() const { return 0; } + virtual float getAttackFactor() const { + return 1.0f; + } + virtual float getDefenseFactor() const { + return 1.0f; + } + + virtual uint8_t getSpeechBubble() const { + return SPEECHBUBBLE_NONE; + } bool addCondition(Condition* condition, bool force = false); bool addCombatCondition(Condition* condition); @@ -322,15 +335,12 @@ class Creature : virtual public Thing virtual void changeHealth(int32_t healthChange, bool sendHealthChange = true); - void gainHealth(Creature* attacker, int32_t healthGain); + void gainHealth(Creature* healer, int32_t healthGain); virtual void drainHealth(Creature* attacker, int32_t damage); virtual bool challengeCreature(Creature*) { return false; } - virtual bool convinceCreature(Creature*) { - return false; - } void onDeath(); virtual uint64_t getGainedExperience(Creature* attacker) const; @@ -355,11 +365,9 @@ class Creature : virtual public Thing virtual void onAttackedCreatureChangeZone(ZoneType_t zone); virtual void onIdleStatus(); - virtual void getCreatureLight(LightInfo& light) const; + virtual LightInfo getCreatureLight() const; virtual void setNormalCreatureLight(); - void setCreatureLight(LightInfo light) { - internalLight = light; - } + void setCreatureLight(LightInfo lightInfo); virtual void onThink(uint32_t interval); void onAttacking(uint32_t interval); @@ -382,7 +390,6 @@ class Creature : virtual public Thing 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&) { @@ -395,30 +402,33 @@ class Creature : virtual public Thing void setDropLoot(bool lootDrop) { this->lootDrop = lootDrop; } - void setLossSkill(bool skillLoss) { + void setSkillLoss(bool skillLoss) { this->skillLoss = skillLoss; } + void setUseDefense(bool useDefense) { + canUseDefense = useDefense; + } //creature script events bool registerCreatureEvent(const std::string& name); bool unregisterCreatureEvent(const std::string& name); - Cylinder* getParent() const final { + Cylinder* getParent() const override final { return tile; } - void setParent(Cylinder* cylinder) final { + void setParent(Cylinder* cylinder) override final { tile = static_cast(cylinder); position = tile->getPosition(); } - inline const Position& getPosition() const final { + const Position& getPosition() const override final { return position; } - Tile* getTile() final { + Tile* getTile() override final { return tile; } - const Tile* getTile() const final { + const Tile* getTile() const override final { return tile; } @@ -464,7 +474,7 @@ class Creature : virtual public Thing Position position; - typedef std::map CountMap; + using CountMap = std::map; CountMap damageMap; std::list summons; @@ -478,10 +488,6 @@ class Creature : virtual public Thing 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; @@ -492,8 +498,7 @@ class Creature : virtual public Thing uint32_t blockCount = 0; uint32_t blockTicks = 0; uint32_t lastStepCost = 1; - uint32_t baseSpeed = 70; - uint32_t latestKillEvent = 0; + uint32_t baseSpeed = 220; int32_t varSpeed = 0; int32_t health = 1000; int32_t healthMax = 1000; @@ -519,6 +524,7 @@ class Creature : virtual public Thing bool hasFollowPath = false; bool forceUpdateFollowPath = false; bool hiddenHealth = false; + bool canUseDefense = true; //creature script events bool hasEventRegistered(CreatureEventType_t event) const { @@ -550,7 +556,6 @@ class Creature : virtual public Thing friend class Game; friend class Map; friend class LuaScriptInterface; - friend class Combat; }; #endif diff --git a/src/creatureevent.cpp b/src/creatureevent.cpp index eb72b28..3e4e7b6 100644 --- a/src/creatureevent.cpp +++ b/src/creatureevent.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,22 +29,15 @@ CreatureEvents::CreatureEvents() : scriptInterface.initState(); } -CreatureEvents::~CreatureEvents() +void CreatureEvents::clear(bool fromLua) { - for (const auto& it : creatureEvents) { - delete it.second; - } -} - -void CreatureEvents::clear() -{ - //clear creature events - for (const auto& it : creatureEvents) { - it.second->clearEvent(); + for (auto it = creatureEvents.begin(); it != creatureEvents.end(); ++it) { + if (fromLua == it->second.fromLua) { + it->second.clearEvent(); + } } - //clear lua state - scriptInterface.reInitState(); + reInitState(fromLua); } LuaScriptInterface& CreatureEvents::getScriptInterface() @@ -57,17 +50,17 @@ std::string CreatureEvents::getScriptBaseName() const return "creaturescripts"; } -Event* CreatureEvents::getEvent(const std::string& nodeName) +Event_ptr CreatureEvents::getEvent(const std::string& nodeName) { if (strcasecmp(nodeName.c_str(), "event") != 0) { return nullptr; } - return new CreatureEvent(&scriptInterface); + return Event_ptr(new CreatureEvent(&scriptInterface)); } -bool CreatureEvents::registerEvent(Event* event, const pugi::xml_node&) +bool CreatureEvents::registerEvent(Event_ptr event, const pugi::xml_node&) { - CreatureEvent* creatureEvent = static_cast(event); //event is guaranteed to be a CreatureEvent + CreatureEvent_ptr creatureEvent{static_cast(event.release())}; //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; @@ -78,13 +71,37 @@ bool CreatureEvents::registerEvent(Event* event, const pugi::xml_node&) //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); + oldEvent->copyEvent(creatureEvent.get()); } return false; } else { //if not, register it normally - creatureEvents[creatureEvent->getName()] = creatureEvent; + creatureEvents.emplace(creatureEvent->getName(), std::move(*creatureEvent)); + return true; + } +} + +bool CreatureEvents::registerLuaEvent(CreatureEvent* event) +{ + CreatureEvent_ptr creatureEvent{ event }; + if (creatureEvent->getEventType() == CREATURE_EVENT_NONE) { + std::cout << "Error: [CreatureEvents::registerLuaEvent] 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.get()); + } + + return false; + } else { + //if not, register it normally + creatureEvents.emplace(creatureEvent->getName(), std::move(*creatureEvent)); return true; } } @@ -93,8 +110,8 @@ CreatureEvent* CreatureEvents::getEventByName(const std::string& name, bool forc { auto it = creatureEvents.find(name); if (it != creatureEvents.end()) { - if (!forceLoaded || it->second->isLoaded()) { - return it->second; + if (!forceLoaded || it->second.isLoaded()) { + return &it->second; } } return nullptr; @@ -104,8 +121,8 @@ 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)) { + if (it.second.getEventType() == CREATURE_EVENT_LOGIN) { + if (!it.second.executeOnLogin(player)) { return false; } } @@ -117,8 +134,8 @@ 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)) { + if (it.second.getEventType() == CREATURE_EVENT_LOGOUT) { + if (!it.second.executeOnLogout(player)) { return false; } } @@ -129,9 +146,9 @@ bool CreatureEvents::playerLogout(Player* player) const 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)) { + for (auto& it : creatureEvents) { + if (it.second.getEventType() == CREATURE_EVENT_ADVANCE) { + if (!it.second.executeAdvance(player, skill, oldLevel, newLevel)) { return false; } } @@ -177,6 +194,10 @@ bool CreatureEvent::configureEvent(const pugi::xml_node& node) type = CREATURE_EVENT_KILL; } else if (tmpStr == "advance") { type = CREATURE_EVENT_ADVANCE; + } else if (tmpStr == "modalwindow") { + type = CREATURE_EVENT_MODALWINDOW; + } else if (tmpStr == "textedit") { + type = CREATURE_EVENT_TEXTEDIT; } else if (tmpStr == "healthchange") { type = CREATURE_EVENT_HEALTHCHANGE; } else if (tmpStr == "manachange") { @@ -217,6 +238,12 @@ std::string CreatureEvent::getScriptEventName() const case CREATURE_EVENT_ADVANCE: return "onAdvance"; + case CREATURE_EVENT_MODALWINDOW: + return "onModalWindow"; + + case CREATURE_EVENT_TEXTEDIT: + return "onTextEdit"; + case CREATURE_EVENT_HEALTHCHANGE: return "onHealthChange"; @@ -248,7 +275,7 @@ void CreatureEvent::clearEvent() loaded = false; } -bool CreatureEvent::executeOnLogin(Player* player) +bool CreatureEvent::executeOnLogin(Player* player) const { //onLogin(player) if (!scriptInterface->reserveScriptEnv()) { @@ -267,7 +294,7 @@ bool CreatureEvent::executeOnLogin(Player* player) return scriptInterface->callFunction(1); } -bool CreatureEvent::executeOnLogout(Player* player) +bool CreatureEvent::executeOnLogout(Player* player) const { //onLogout(player) if (!scriptInterface->reserveScriptEnv()) { @@ -419,9 +446,56 @@ void CreatureEvent::executeOnKill(Creature* creature, Creature* target) scriptInterface->callVoidFunction(2); } +void CreatureEvent::executeModalWindow(Player* player, uint32_t modalWindowId, uint8_t buttonId, uint8_t choiceId) +{ + //onModalWindow(player, modalWindowId, buttonId, choiceId) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CreatureEvent::executeModalWindow] Call stack overflow" << std::endl; + return; + } + + 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, modalWindowId); + lua_pushnumber(L, buttonId); + lua_pushnumber(L, choiceId); + + scriptInterface->callVoidFunction(4); +} + +bool CreatureEvent::executeTextEdit(Player* player, Item* item, const std::string& text) +{ + //onTextEdit(player, item, text) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - CreatureEvent::executeTextEdit] 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"); + + LuaScriptInterface::pushThing(L, item); + LuaScriptInterface::pushString(L, text); + + return scriptInterface->callFunction(3); +} + void CreatureEvent::executeHealthChange(Creature* creature, Creature* attacker, CombatDamage& damage) { - //onHealthChange(creature, attacker, value, type, min, max, origin) + //onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeHealthChange] Call stack overflow" << std::endl; return; @@ -438,8 +512,7 @@ void CreatureEvent::executeHealthChange(Creature* creature, Creature* attacker, if (attacker) { LuaScriptInterface::pushUserdata(L, attacker); LuaScriptInterface::setCreatureMetatable(L, -1, attacker); - } - else { + } else { lua_pushnil(L); } @@ -447,16 +520,16 @@ void CreatureEvent::executeHealthChange(Creature* creature, Creature* attacker, if (scriptInterface->protectedCall(L, 7, 4) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); - } - else { - damage.value = std::abs(LuaScriptInterface::getNumber(L, -4)); - damage.type = LuaScriptInterface::getNumber(L, -3); - damage.min = std::abs(LuaScriptInterface::getNumber(L, -2)); - damage.max = LuaScriptInterface::getNumber(L, -1); + } else { + damage.primary.value = std::abs(LuaScriptInterface::getNumber(L, -4)); + damage.primary.type = LuaScriptInterface::getNumber(L, -3); + damage.secondary.value = std::abs(LuaScriptInterface::getNumber(L, -2)); + damage.secondary.type = LuaScriptInterface::getNumber(L, -1); lua_pop(L, 4); - if (damage.type != COMBAT_HEALING) { - damage.value = -damage.value; + if (damage.primary.type != COMBAT_HEALING) { + damage.primary.value = -damage.primary.value; + damage.secondary.value = -damage.secondary.value; } } @@ -464,7 +537,7 @@ void CreatureEvent::executeHealthChange(Creature* creature, Creature* attacker, } void CreatureEvent::executeManaChange(Creature* creature, Creature* attacker, CombatDamage& damage) { - //onManaChange(creature, attacker, value, type, min, max, origin) + //onManaChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeManaChange] Call stack overflow" << std::endl; return; @@ -481,8 +554,7 @@ void CreatureEvent::executeManaChange(Creature* creature, Creature* attacker, Co if (attacker) { LuaScriptInterface::pushUserdata(L, attacker); LuaScriptInterface::setCreatureMetatable(L, -1, attacker); - } - else { + } else { lua_pushnil(L); } @@ -490,9 +562,12 @@ void CreatureEvent::executeManaChange(Creature* creature, Creature* attacker, Co if (scriptInterface->protectedCall(L, 7, 4) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); - } - else { - damage = LuaScriptInterface::getCombatDamage(L); + } else { + damage.primary.value = LuaScriptInterface::getNumber(L, -4); + damage.primary.type = LuaScriptInterface::getNumber(L, -3); + damage.secondary.value = LuaScriptInterface::getNumber(L, -2); + damage.secondary.type = LuaScriptInterface::getNumber(L, -1); + lua_pop(L, 4); } scriptInterface->resetScriptEnv(); diff --git a/src/creatureevent.h b/src/creatureevent.h index 6dbdeac..643ac85 100644 --- a/src/creatureevent.h +++ b/src/creatureevent.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,6 +24,9 @@ #include "baseevents.h" #include "enums.h" +class CreatureEvent; +using CreatureEvent_ptr = std::unique_ptr; + enum CreatureEventType_t { CREATURE_EVENT_NONE, CREATURE_EVENT_LOGIN, @@ -33,18 +36,69 @@ enum CreatureEventType_t { CREATURE_EVENT_DEATH, CREATURE_EVENT_KILL, CREATURE_EVENT_ADVANCE, + CREATURE_EVENT_MODALWINDOW, + CREATURE_EVENT_TEXTEDIT, CREATURE_EVENT_HEALTHCHANGE, CREATURE_EVENT_MANACHANGE, CREATURE_EVENT_EXTENDED_OPCODE, // otclient additional network opcodes }; -class CreatureEvent; +class CreatureEvent final : public Event +{ + public: + explicit CreatureEvent(LuaScriptInterface* interface); + + bool configureEvent(const pugi::xml_node& node) override; + + CreatureEventType_t getEventType() const { + return type; + } + void setEventType(CreatureEventType_t eventType) { + type = eventType; + } + const std::string& getName() const { + return eventName; + } + void setName(const std::string& name) { + eventName = name; + } + bool isLoaded() const { + return loaded; + } + void setLoaded(bool b) { + loaded = b; + } + + void clearEvent(); + void copyEvent(CreatureEvent* creatureEvent); + + //scripting + bool executeOnLogin(Player* player) const; + bool executeOnLogout(Player* player) const; + 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 executeModalWindow(Player* player, uint32_t modalWindowId, uint8_t buttonId, uint8_t choiceId); + bool executeTextEdit(Player* player, Item* item, const std::string& text); + void executeHealthChange(Creature* creature, Creature* attacker, CombatDamage& damage); + void executeManaChange(Creature* creature, Creature* attacker, CombatDamage& damage); + void executeExtendedOpcode(Player* player, uint8_t opcode, const std::string& buffer); + // + + private: + std::string getScriptEventName() const override; + + std::string eventName; + CreatureEventType_t type; + bool loaded; +}; class CreatureEvents final : public BaseEvents { public: CreatureEvents(); - ~CreatureEvents(); // non-copyable CreatureEvents(const CreatureEvents&) = delete; @@ -57,59 +111,20 @@ class CreatureEvents final : public BaseEvents 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; + bool registerLuaEvent(CreatureEvent* event); + void clear(bool fromLua) override final; + + private: + LuaScriptInterface& getScriptInterface() override; + std::string getScriptBaseName() const override; + Event_ptr getEvent(const std::string& nodeName) override; + bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; //creature events - typedef std::map CreatureEventList; - CreatureEventList creatureEvents; + using CreatureEventMap = std::map; + CreatureEventMap 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 executeHealthChange(Creature* creature, Creature* attacker, CombatDamage& damage); - void executeManaChange(Creature* creature, Creature* attacker, CombatDamage& damage); - 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 diff --git a/src/cylinder.cpp b/src/cylinder.cpp index 406ca9a..4032343 100644 --- a/src/cylinder.cpp +++ b/src/cylinder.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/cylinder.h b/src/cylinder.h index 872609c..63a794e 100644 --- a/src/cylinder.h +++ b/src/cylinder.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -37,7 +37,6 @@ enum cylinderflags_t { 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 { diff --git a/src/database.cpp b/src/database.cpp index 0dfc590..8e74801 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -255,7 +255,7 @@ 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()) { + if (length > Database::getInstance().getMaxPacketSize() && !execute()) { return false; } @@ -288,7 +288,7 @@ bool DBInsert::execute() } // executes buffer - bool res = Database::getInstance()->executeQuery(query + values); + bool res = Database::getInstance().executeQuery(query + values); values.clear(); length = query.length(); return res; diff --git a/src/database.h b/src/database.h index e0168ba..51c3d78 100644 --- a/src/database.h +++ b/src/database.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,7 +25,7 @@ #include class DBResult; -typedef std::shared_ptr DBResult_ptr; +using DBResult_ptr = std::shared_ptr; class Database { @@ -42,10 +42,10 @@ class Database * * @return database connection handler singleton */ - static Database* getInstance() + static Database& getInstance() { static Database instance; - return &instance; + return instance; } /** @@ -117,7 +117,7 @@ class Database return maxPacketSize; } - protected: + private: /** * Transaction related methods. * @@ -129,7 +129,6 @@ class Database bool rollback(); bool commit(); - private: MYSQL* handle = nullptr; std::recursive_mutex databaseLock; uint64_t maxPacketSize = 1048576; @@ -195,7 +194,7 @@ class DBInsert bool addRow(std::ostringstream& row); bool execute(); - protected: + private: std::string query; std::string values; size_t length; @@ -208,7 +207,7 @@ class DBTransaction ~DBTransaction() { if (state == STATE_START) { - Database::getInstance()->rollback(); + Database::getInstance().rollback(); } } @@ -218,7 +217,7 @@ class DBTransaction bool begin() { state = STATE_START; - return Database::getInstance()->beginTransaction(); + return Database::getInstance().beginTransaction(); } bool commit() { @@ -226,15 +225,15 @@ class DBTransaction return false; } - state = STEATE_COMMIT; - return Database::getInstance()->commit(); + state = STATE_COMMIT; + return Database::getInstance().commit(); } private: enum TransactionStates_t { STATE_NO_START, STATE_START, - STEATE_COMMIT, + STATE_COMMIT, }; TransactionStates_t state = STATE_NO_START; diff --git a/src/databasemanager.cpp b/src/databasemanager.cpp index dc38b37..6a57652 100644 --- a/src/databasemanager.cpp +++ b/src/databasemanager.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,11 +27,11 @@ extern ConfigManager g_config; bool DatabaseManager::optimizeTables() { - Database* db = Database::getInstance(); + 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()); + 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; } @@ -43,7 +43,7 @@ bool DatabaseManager::optimizeTables() query.str(std::string()); query << "OPTIMIZE TABLE `" << tableName << '`'; - if (db->executeQuery(query.str())) { + if (db.executeQuery(query.str())) { std::cout << " [success]" << std::endl; } else { std::cout << " [failed]" << std::endl; @@ -54,27 +54,27 @@ bool DatabaseManager::optimizeTables() bool DatabaseManager::tableExists(const std::string& tableName) { - Database* db = Database::getInstance(); + 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; + 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(); + 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; + 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)"); + 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; } @@ -85,13 +85,68 @@ int32_t DatabaseManager::getDatabaseVersion() return -1; } +void DatabaseManager::updateDatabase() +{ + lua_State* L = luaL_newstate(); + if (!L) { + return; + } + + luaL_openlibs(L); + +#ifndef LUAJIT_VERSION + //bit operations for Lua, based on bitlib project release 24 + //bit.bnot, bit.band, bit.bor, bit.bxor, bit.lshift, bit.rshift + luaL_register(L, "bit", LuaScriptInterface::luaBitReg); +#endif + + //db table + luaL_register(L, "db", LuaScriptInterface::luaDatabaseTable); + + //result table + luaL_register(L, "result", LuaScriptInterface::luaResultTable); + + int32_t version = getDatabaseVersion(); + do { + std::ostringstream ss; + ss << "data/migrations/" << version << ".lua"; + if (luaL_dofile(L, ss.str().c_str()) != 0) { + std::cout << "[Error - DatabaseManager::updateDatabase - Version: " << version << "] " << lua_tostring(L, -1) << std::endl; + break; + } + + if (!LuaScriptInterface::reserveScriptEnv()) { + break; + } + + lua_getglobal(L, "onUpdateDatabase"); + if (lua_pcall(L, 0, 1, 0) != 0) { + LuaScriptInterface::resetScriptEnv(); + std::cout << "[Error - DatabaseManager::updateDatabase - Version: " << version << "] " << lua_tostring(L, -1) << std::endl; + break; + } + + if (!LuaScriptInterface::getBoolean(L, -1, false)) { + LuaScriptInterface::resetScriptEnv(); + break; + } + + version++; + std::cout << "> Database has been updated to version " << version << '.' << std::endl; + registerDatabaseConfig("db_version", version); + + LuaScriptInterface::resetScriptEnv(); + } while (true); + lua_close(L); +} + bool DatabaseManager::getDatabaseConfig(const std::string& config, int32_t& value) { - Database* db = Database::getInstance(); + Database& db = Database::getInstance(); std::ostringstream query; - query << "SELECT `value` FROM `server_config` WHERE `config` = " << db->escapeString(config); + query << "SELECT `value` FROM `server_config` WHERE `config` = " << db.escapeString(config); - DBResult_ptr result = db->storeQuery(query.str()); + DBResult_ptr result = db.storeQuery(query.str()); if (!result) { return false; } @@ -102,16 +157,16 @@ bool DatabaseManager::getDatabaseConfig(const std::string& config, int32_t& valu void DatabaseManager::registerDatabaseConfig(const std::string& config, int32_t value) { - Database* db = Database::getInstance(); + Database& db = Database::getInstance(); std::ostringstream query; int32_t tmp; if (!getDatabaseConfig(config, tmp)) { - query << "INSERT INTO `server_config` VALUES (" << db->escapeString(config) << ", '" << value << "')"; + query << "INSERT INTO `server_config` VALUES (" << db.escapeString(config) << ", '" << value << "')"; } else { - query << "UPDATE `server_config` SET `value` = '" << value << "' WHERE `config` = " << db->escapeString(config); + query << "UPDATE `server_config` SET `value` = '" << value << "' WHERE `config` = " << db.escapeString(config); } - db->executeQuery(query.str()); + db.executeQuery(query.str()); } diff --git a/src/databasemanager.h b/src/databasemanager.h index d15f1ec..2e60c44 100644 --- a/src/databasemanager.h +++ b/src/databasemanager.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,12 +24,13 @@ class DatabaseManager { public: - static bool tableExists(const std::string& table); + static bool tableExists(const std::string& tableName); static int32_t getDatabaseVersion(); static bool isDatabaseSetup(); static bool optimizeTables(); + static void updateDatabase(); static bool getDatabaseConfig(const std::string& config, int32_t& value); static void registerDatabaseConfig(const std::string& config, int32_t value); diff --git a/src/databasetasks.cpp b/src/databasetasks.cpp index 37f1176..60969b1 100644 --- a/src/databasetasks.cpp +++ b/src/databasetasks.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -51,13 +51,13 @@ void DatabaseTasks::threadMain() } } -void DatabaseTasks::addTask(const std::string& query, const std::function& callback/* = nullptr*/, bool store/* = false*/) +void DatabaseTasks::addTask(std::string query, std::function callback/* = nullptr*/, bool store/* = false*/) { bool signal = false; taskLock.lock(); if (getState() == THREAD_STATE_RUNNING) { signal = tasks.empty(); - tasks.emplace_back(query, callback, store); + tasks.emplace_back(std::move(query), std::move(callback), store); } taskLock.unlock(); @@ -85,9 +85,13 @@ void DatabaseTasks::runTask(const DatabaseTask& task) void DatabaseTasks::flush() { + std::unique_lock guard{ taskLock }; while (!tasks.empty()) { - runTask(tasks.front()); + auto task = std::move(tasks.front()); tasks.pop_front(); + guard.unlock(); + runTask(task); + guard.lock(); } } @@ -95,7 +99,7 @@ void DatabaseTasks::shutdown() { taskLock.lock(); setState(THREAD_STATE_TERMINATED); - flush(); taskLock.unlock(); + flush(); taskSignal.notify_one(); } diff --git a/src/databasetasks.h b/src/databasetasks.h index 42b36e2..2d19442 100644 --- a/src/databasetasks.h +++ b/src/databasetasks.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,7 +26,7 @@ #include "enums.h" struct DatabaseTask { - DatabaseTask(std::string query, std::function callback, bool store) : + DatabaseTask(std::string&& query, std::function&& callback, bool store) : query(std::move(query)), callback(std::move(callback)), store(store) {} std::string query; @@ -42,7 +42,7 @@ class DatabaseTasks : public ThreadHolder void flush(); void shutdown(); - void addTask(const std::string& query, const std::function& callback = nullptr, bool store = false); + void addTask(std::string query, std::function callback = nullptr, bool store = false); void threadMain(); private: diff --git a/src/definitions.h b/src/definitions.h index 63c9e08..b779f71 100644 --- a/src/definitions.h +++ b/src/definitions.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,13 +20,13 @@ #ifndef FS_DEFINITIONS_H_877452FEC245450C9F96B8FD268D8963 #define FS_DEFINITIONS_H_877452FEC245450C9F96B8FD268D8963 -static constexpr auto STATUS_SERVER_NAME = "Sabrehaven"; -static constexpr auto STATUS_SERVER_VERSION = "1.0"; -static constexpr auto STATUS_SERVER_DEVELOPERS = "OTLand community & Sabrehaven Developers Team"; +static constexpr auto STATUS_SERVER_NAME = "The Forgotten Server"; +static constexpr auto STATUS_SERVER_VERSION = "1.3"; +static constexpr auto STATUS_SERVER_DEVELOPERS = "Mark Samman"; -static constexpr auto CLIENT_VERSION_MIN = 781; -static constexpr auto CLIENT_VERSION_MAX = 781; -static constexpr auto CLIENT_VERSION_STR = "7.81"; +static constexpr auto CLIENT_VERSION_MIN = 1097; +static constexpr auto CLIENT_VERSION_MAX = 1098; +static constexpr auto CLIENT_VERSION_STR = "10.98"; static constexpr auto AUTHENTICATOR_DIGITS = 6U; static constexpr auto AUTHENTICATOR_PERIOD = 30U; @@ -60,7 +60,6 @@ static constexpr auto AUTHENTICATOR_PERIOD = 30U; #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 -#pragma warning(disable:4996) // inetpton warning #endif #define strcasecmp _stricmp diff --git a/src/depotchest.cpp b/src/depotchest.cpp new file mode 100644 index 0000000..8d28e22 --- /dev/null +++ b/src/depotchest.cpp @@ -0,0 +1,82 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "depotchest.h" +#include "tools.h" + +DepotChest::DepotChest(uint16_t type) : + Container(type), maxDepotItems(1500) {} + +ReturnValue DepotChest::queryAdd(int32_t index, const Thing& thing, uint32_t count, + uint32_t flags, Creature* actor/* = nullptr*/) 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 (getItemHoldingCount() + addCount > maxDepotItems) { + return RETURNVALUE_DEPOTISFULL; + } + } + + return Container::queryAdd(index, thing, count, flags, actor); +} + +void DepotChest::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t) +{ + Cylinder* parent = getParent(); + if (parent != nullptr) { + parent->postAddNotification(thing, oldParent, index, LINK_PARENT); + } +} + +void DepotChest::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) +{ + Cylinder* parent = getParent(); + if (parent != nullptr) { + parent->postRemoveNotification(thing, newParent, index, LINK_PARENT); + } +} + +Cylinder* DepotChest::getParent() const +{ + if (parent) { + return parent->getParent(); + } + return nullptr; +} diff --git a/src/depotchest.h b/src/depotchest.h new file mode 100644 index 0000000..b1db4ea --- /dev/null +++ b/src/depotchest.h @@ -0,0 +1,57 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_DEPOTCHEST_H_6538526014684E3DBC92CC12815B6766 +#define FS_DEPOTCHEST_H_6538526014684E3DBC92CC12815B6766 + +#include "container.h" + +class DepotChest final : public Container +{ + public: + explicit DepotChest(uint16_t type); + + //serialization + void setMaxDepotItems(uint32_t maxitems) { + maxDepotItems = maxitems; + } + + //cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, + uint32_t flags, Creature* actor = nullptr) const override; + + 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; + + //overrides + bool canRemove() const override { + return false; + } + + Cylinder* getParent() const override; + Cylinder* getRealParent() const override { + return parent; + } + + private: + uint32_t maxDepotItems; +}; + +#endif + diff --git a/src/depotlocker.cpp b/src/depotlocker.cpp index 09189a1..6cce856 100644 --- a/src/depotlocker.cpp +++ b/src/depotlocker.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,12 +20,9 @@ #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) {} + Container(type, 3), depotId(0) {} Attr_ReadValue DepotLocker::readAttr(AttrTypes_t attr, PropStream& propStream) { @@ -38,40 +35,9 @@ Attr_ReadValue DepotLocker::readAttr(AttrTypes_t attr, PropStream& propStream) return Item::readAttr(attr, propStream); } -ReturnValue DepotLocker::queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, Creature* actor) const +ReturnValue DepotLocker::queryAdd(int32_t, const Thing&, uint32_t, uint32_t, Creature*) 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); + return RETURNVALUE_NOTENOUGHROOM; } void DepotLocker::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t) @@ -87,3 +53,12 @@ void DepotLocker::postRemoveNotification(Thing* thing, const Cylinder* newParent parent->postRemoveNotification(thing, newParent, index, LINK_PARENT); } } + +void DepotLocker::removeInbox(Inbox* inbox) +{ + auto cit = std::find(itemlist.begin(), itemlist.end(), inbox); + if (cit == itemlist.end()) { + return; + } + itemlist.erase(cit); +} diff --git a/src/depotlocker.h b/src/depotlocker.h index 094defc..750ec42 100644 --- a/src/depotlocker.h +++ b/src/depotlocker.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,21 +21,24 @@ #define FS_DEPOTLOCKER_H_53AD8E0606A34070B87F792611F4F3F8 #include "container.h" +#include "inbox.h" class DepotLocker final : public Container { public: explicit DepotLocker(uint16_t type); - DepotLocker* getDepotLocker() final { + DepotLocker* getDepotLocker() override { return this; } - const DepotLocker* getDepotLocker() const final { + const DepotLocker* getDepotLocker() const override { return this; } + void removeInbox(Inbox* inbox); + //serialization - Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) final; + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; uint16_t getDepotId() const { return depotId; @@ -46,12 +49,12 @@ class DepotLocker final : public Container //cylinder implementations ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const final; + uint32_t flags, Creature* actor = nullptr) const override; - 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; + 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; - bool canRemove() const final { + bool canRemove() const override { return false; } diff --git a/src/enums.h b/src/enums.h index d6bf53a..579a3c9 100644 --- a/src/enums.h +++ b/src/enums.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,6 +20,43 @@ #ifndef FS_ENUMS_H_003445999FEE4A67BCECBE918B0124CE #define FS_ENUMS_H_003445999FEE4A67BCECBE918B0124CE +enum RuleViolationType_t : uint8_t { + REPORT_TYPE_NAME = 0, + REPORT_TYPE_STATEMENT = 1, + REPORT_TYPE_BOT = 2 +}; + +enum RuleViolationReasons_t : uint8_t { + REPORT_REASON_NAMEINAPPROPRIATE = 0, + REPORT_REASON_NAMEPOORFORMATTED = 1, + REPORT_REASON_NAMEADVERTISING = 2, + REPORT_REASON_NAMEUNFITTING = 3, + REPORT_REASON_NAMERULEVIOLATION = 4, + REPORT_REASON_INSULTINGSTATEMENT = 5, + REPORT_REASON_SPAMMING = 6, + REPORT_REASON_ADVERTISINGSTATEMENT = 7, + REPORT_REASON_UNFITTINGSTATEMENT = 8, + REPORT_REASON_LANGUAGESTATEMENT = 9, + REPORT_REASON_DISCLOSURE = 10, + REPORT_REASON_RULEVIOLATION = 11, + REPORT_REASON_STATEMENT_BUGABUSE = 12, + REPORT_REASON_UNOFFICIALSOFTWARE = 13, + REPORT_REASON_PRETENDING = 14, + REPORT_REASON_HARASSINGOWNERS = 15, + REPORT_REASON_FALSEINFO = 16, + REPORT_REASON_ACCOUNTSHARING = 17, + REPORT_REASON_STEALINGDATA = 18, + REPORT_REASON_SERVICEATTACKING = 19, + REPORT_REASON_SERVICEAGREEMENT = 20 +}; + +enum BugReportType_t : uint8_t { + BUG_CATEGORY_MAP = 0, + BUG_CATEGORY_TYPO = 1, + BUG_CATEGORY_TECHNICAL = 2, + BUG_CATEGORY_OTHER = 3 +}; + enum ThreadState { THREAD_STATE_RUNNING, THREAD_STATE_CLOSING, @@ -30,7 +67,7 @@ enum itemAttrTypes : uint32_t { ITEM_ATTRIBUTE_NONE, ITEM_ATTRIBUTE_ACTIONID = 1 << 0, - ITEM_ATTRIBUTE_MOVEMENTID = 1 << 1, + ITEM_ATTRIBUTE_UNIQUEID = 1 << 1, ITEM_ATTRIBUTE_DESCRIPTION = 1 << 2, ITEM_ATTRIBUTE_TEXT = 1 << 3, ITEM_ATTRIBUTE_DATE = 1 << 4, @@ -41,26 +78,60 @@ enum itemAttrTypes : uint32_t { 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, + ITEM_ATTRIBUTE_EXTRADEFENSE = 1 << 12, + ITEM_ATTRIBUTE_ARMOR = 1 << 13, + ITEM_ATTRIBUTE_HITCHANCE = 1 << 14, + ITEM_ATTRIBUTE_SHOOTRANGE = 1 << 15, + ITEM_ATTRIBUTE_OWNER = 1 << 16, + ITEM_ATTRIBUTE_DURATION = 1 << 17, + ITEM_ATTRIBUTE_DECAYSTATE = 1 << 18, + ITEM_ATTRIBUTE_CORPSEOWNER = 1 << 19, + ITEM_ATTRIBUTE_CHARGES = 1 << 20, + ITEM_ATTRIBUTE_FLUIDTYPE = 1 << 21, + ITEM_ATTRIBUTE_DOORID = 1 << 22, + ITEM_ATTRIBUTE_DECAYTO = 1 << 23, + + ITEM_ATTRIBUTE_CUSTOM = 1U << 31 }; enum VipStatus_t : uint8_t { VIPSTATUS_OFFLINE = 0, VIPSTATUS_ONLINE = 1, + VIPSTATUS_PENDING = 2 +}; + +enum MarketAction_t { + MARKETACTION_BUY = 0, + MARKETACTION_SELL = 1, +}; + +enum MarketRequest_t { + MARKETREQUEST_OWN_OFFERS = 0xFFFE, + MARKETREQUEST_OWN_HISTORY = 0xFFFF, +}; + +enum MarketOfferState_t { + OFFERSTATE_ACTIVE = 0, + OFFERSTATE_CANCELLED = 1, + OFFERSTATE_EXPIRED = 2, + OFFERSTATE_ACCEPTED = 3, + + OFFERSTATE_ACCEPTEDEX = 255, +}; + +enum ChannelEvent_t : uint8_t { + CHANNELEVENT_JOIN = 0, + CHANNELEVENT_LEAVE = 1, + CHANNELEVENT_INVITE = 2, + CHANNELEVENT_EXCLUDE = 3, +}; + +enum CreatureType_t : uint8_t { + CREATURETYPE_PLAYER = 0, + CREATURETYPE_MONSTER = 1, + CREATURETYPE_NPC = 2, + CREATURETYPE_SUMMON_OWN = 3, + CREATURETYPE_SUMMON_OTHERS = 4, }; enum OperatingSystem_t : uint8_t { @@ -75,6 +146,20 @@ enum OperatingSystem_t : uint8_t { CLIENTOS_OTCLIENT_MAC = 12, }; +enum SpellGroup_t : uint8_t { + SPELLGROUP_NONE = 0, + SPELLGROUP_ATTACK = 1, + SPELLGROUP_HEALING = 2, + SPELLGROUP_SUPPORT = 3, + SPELLGROUP_SPECIAL = 4, +}; + +enum SpellType_t : uint8_t { + SPELL_UNDEFINED = 0, + SPELL_INSTANT = 1, + SPELL_RUNE = 2, +}; + enum AccountType_t : uint8_t { ACCOUNT_TYPE_NORMAL = 1, ACCOUNT_TYPE_TUTOR = 2, @@ -89,6 +174,7 @@ enum RaceType_t : uint8_t { RACE_BLOOD, RACE_UNDEAD, RACE_FIRE, + RACE_ENERGY, }; enum CombatType_t : uint16_t { @@ -103,8 +189,11 @@ enum CombatType_t : uint16_t { COMBAT_MANADRAIN = 1 << 6, COMBAT_HEALING = 1 << 7, COMBAT_DROWNDAMAGE = 1 << 8, + COMBAT_ICEDAMAGE = 1 << 9, + COMBAT_HOLYDAMAGE = 1 << 10, + COMBAT_DEATHDAMAGE = 1 << 11, - COMBAT_COUNT = 10 + COMBAT_COUNT = 12 }; enum CombatParam_t { @@ -118,14 +207,6 @@ enum CombatParam_t { 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 { @@ -153,6 +234,7 @@ enum ConditionParam_t { CONDITION_PARAM_MAXVALUE = 15, CONDITION_PARAM_STARTVALUE = 16, CONDITION_PARAM_TICKINTERVAL = 17, + CONDITION_PARAM_FORCEUPDATE = 18, CONDITION_PARAM_SKILL_MELEE = 19, CONDITION_PARAM_SKILL_FIST = 20, CONDITION_PARAM_SKILL_CLUB = 21, @@ -178,13 +260,16 @@ enum ConditionParam_t { CONDITION_PARAM_SKILL_DISTANCEPERCENT = 41, CONDITION_PARAM_SKILL_SHIELDPERCENT = 42, CONDITION_PARAM_SKILL_FISHINGPERCENT = 43, - // CONDITION_PARAM_BUFF_SPELL = 44, + 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, + CONDITION_PARAM_DISABLE_DEFENSE = 47, + CONDITION_PARAM_SPECIALSKILL_CRITICALHITCHANCE = 48, + CONDITION_PARAM_SPECIALSKILL_CRITICALHITAMOUNT = 49, + CONDITION_PARAM_SPECIALSKILL_LIFELEECHCHANCE = 50, + CONDITION_PARAM_SPECIALSKILL_LIFELEECHAMOUNT = 51, + CONDITION_PARAM_SPECIALSKILL_MANALEECHCHANCE = 52, + CONDITION_PARAM_SPECIALSKILL_MANALEECHAMOUNT = 53, }; enum BlockType_t : uint8_t { @@ -220,6 +305,18 @@ enum stats_t { STAT_LAST = STAT_MAGICPOINTS }; +enum SpecialSkills_t { + SPECIALSKILL_CRITICALHITCHANCE, + SPECIALSKILL_CRITICALHITAMOUNT, + SPECIALSKILL_LIFELEECHCHANCE, + SPECIALSKILL_LIFELEECHAMOUNT, + SPECIALSKILL_MANALEECHCHANCE, + SPECIALSKILL_MANALEECHAMOUNT, + + SPECIALSKILL_FIRST = SPECIALSKILL_CRITICALHITCHANCE, + SPECIALSKILL_LAST = SPECIALSKILL_MANALEECHAMOUNT +}; + enum formulaType_t { COMBAT_FORMULA_UNDEFINED, COMBAT_FORMULA_LEVELMAGIC, @@ -233,24 +330,31 @@ enum ConditionType_t { 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, - CONDITION_DROWN = 1 << 20, + CONDITION_BLEEDING = 1 << 3, + CONDITION_HASTE = 1 << 4, + CONDITION_PARALYZE = 1 << 5, + CONDITION_OUTFIT = 1 << 6, + CONDITION_INVISIBLE = 1 << 7, + CONDITION_LIGHT = 1 << 8, + CONDITION_MANASHIELD = 1 << 9, + CONDITION_INFIGHT = 1 << 10, + CONDITION_DRUNK = 1 << 11, + CONDITION_EXHAUST_WEAPON = 1 << 12, // unused + CONDITION_REGENERATION = 1 << 13, + CONDITION_SOUL = 1 << 14, + CONDITION_DROWN = 1 << 15, + CONDITION_MUTED = 1 << 16, + CONDITION_CHANNELMUTEDTICKS = 1 << 17, + CONDITION_YELLTICKS = 1 << 18, + CONDITION_ATTRIBUTES = 1 << 19, + CONDITION_FREEZING = 1 << 20, + CONDITION_DAZZLED = 1 << 21, + CONDITION_CURSED = 1 << 22, + CONDITION_EXHAUST_COMBAT = 1 << 23, // unused + CONDITION_EXHAUST_HEAL = 1 << 24, // unused + CONDITION_PACIFIED = 1 << 25, + CONDITION_SPELLCOOLDOWN = 1 << 26, + CONDITION_SPELLGROUPCOOLDOWN = 1 << 27, }; enum ConditionId_t : int8_t { @@ -276,11 +380,7 @@ enum PlayerSex_t : uint8_t { }; enum Vocation_t : uint16_t { - VOCATION_NONE, - VOCATION_SORCERER = 1 << 0, - VOCATION_DRUID = 1 << 1, - VOCATION_PALADIN = 1 << 2, - VOCATION_KNIGHT = 1 << 3, + VOCATION_NONE = 0 }; enum ReturnValue { @@ -343,10 +443,12 @@ enum ReturnValue { RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL, RETURNVALUE_CANNOTCONJUREITEMHERE, RETURNVALUE_YOUNEEDTOSPLITYOURSPEARS, - RETURNVALUE_NAMEISTOOAMBIGIOUS, + RETURNVALUE_NAMEISTOOAMBIGUOUS, RETURNVALUE_CANONLYUSEONESHIELD, RETURNVALUE_NOPARTYMEMBERSINRANGE, RETURNVALUE_YOUARENOTTHEOWNER, + RETURNVALUE_NOSUCHRAIDEXISTS, + RETURNVALUE_ANOTHERRAIDISALREADYEXECUTING, RETURNVALUE_TRADEPLAYERFARAWAY, RETURNVALUE_YOUDONTOWNTHISHOUSE, RETURNVALUE_TRADEPLAYERALREADYOWNSAHOUSE, @@ -354,9 +456,43 @@ enum ReturnValue { RETURNVALUE_YOUCANNOTTRADETHISHOUSE, }; +enum SpeechBubble_t +{ + SPEECHBUBBLE_NONE = 0, + SPEECHBUBBLE_NORMAL = 1, + SPEECHBUBBLE_TRADE = 2, + SPEECHBUBBLE_QUEST = 3, + SPEECHBUBBLE_QUESTTRADER = 4, +}; + +enum MapMark_t +{ + MAPMARK_TICK = 0, + MAPMARK_QUESTION = 1, + MAPMARK_EXCLAMATION = 2, + MAPMARK_STAR = 3, + MAPMARK_CROSS = 4, + MAPMARK_TEMPLE = 5, + MAPMARK_KISS = 6, + MAPMARK_SHOVEL = 7, + MAPMARK_SWORD = 8, + MAPMARK_FLAG = 9, + MAPMARK_LOCK = 10, + MAPMARK_BAG = 11, + MAPMARK_SKULL = 12, + MAPMARK_DOLLAR = 13, + MAPMARK_REDNORTH = 14, + MAPMARK_REDSOUTH = 15, + MAPMARK_REDEAST = 16, + MAPMARK_REDWEST = 17, + MAPMARK_GREENNORTH = 18, + MAPMARK_GREENSOUTH = 19, +}; + struct Outfit_t { uint16_t lookType = 0; uint16_t lookTypeEx = 0; + uint16_t lookMount = 0; uint8_t lookHead = 0; uint8_t lookBody = 0; uint8_t lookLegs = 0; @@ -371,6 +507,85 @@ struct LightInfo { constexpr LightInfo(uint8_t level, uint8_t color) : level(level), color(color) {} }; +struct ShopInfo { + uint16_t itemId; + int32_t subType; + uint32_t buyPrice; + uint32_t sellPrice; + std::string realName; + + ShopInfo() { + itemId = 0; + subType = 1; + buyPrice = 0; + sellPrice = 0; + } + + ShopInfo(uint16_t itemId, int32_t subType = 0, uint32_t buyPrice = 0, uint32_t sellPrice = 0, std::string realName = "") + : itemId(itemId), subType(subType), buyPrice(buyPrice), sellPrice(sellPrice), realName(std::move(realName)) {} +}; + +struct MarketOffer { + uint32_t price; + uint32_t timestamp; + uint16_t amount; + uint16_t counter; + uint16_t itemId; + std::string playerName; +}; + +struct MarketOfferEx { + MarketOfferEx() = default; + MarketOfferEx(MarketOfferEx&& other) : + id(other.id), playerId(other.playerId), timestamp(other.timestamp), price(other.price), + amount(other.amount), counter(other.counter), itemId(other.itemId), type(other.type), + playerName(std::move(other.playerName)) {} + + uint32_t id; + uint32_t playerId; + uint32_t timestamp; + uint32_t price; + uint16_t amount; + uint16_t counter; + uint16_t itemId; + MarketAction_t type; + std::string playerName; +}; + +struct HistoryMarketOffer { + uint32_t timestamp; + uint32_t price; + uint16_t itemId; + uint16_t amount; + MarketOfferState_t state; +}; + +struct MarketStatistics { + MarketStatistics() { + numTransactions = 0; + highestPrice = 0; + totalPrice = 0; + lowestPrice = 0; + } + + uint32_t numTransactions; + uint32_t highestPrice; + uint64_t totalPrice; + uint32_t lowestPrice; +}; + +struct ModalWindow +{ + std::list> buttons, choices; + std::string title, message; + uint32_t id; + uint8_t defaultEnterButton, defaultEscapeButton; + bool priority; + + ModalWindow(uint32_t id, std::string title, std::string message) + : title(std::move(title)), message(std::move(message)), id(id), defaultEnterButton(0xFF), defaultEscapeButton(0xFF), priority(false) {} +}; + enum CombatOrigin { ORIGIN_NONE, @@ -382,20 +597,31 @@ enum CombatOrigin struct CombatDamage { - CombatType_t type; - int32_t value; - int32_t min; - int32_t max; - CombatOrigin origin; + struct { + CombatType_t type; + int32_t value; + } primary, secondary; + CombatOrigin origin; CombatDamage() { origin = ORIGIN_NONE; - type = COMBAT_NONE; - value = 0; - min = 0; - max = 0; + primary.type = secondary.type = COMBAT_NONE; + primary.value = secondary.value = 0; } }; +using MarketOfferList = std::list; +using HistoryMarketOfferList = std::list; +using ShopInfoList = std::list; + +enum MonstersEvent_t : uint8_t { + MONSTERS_EVENT_NONE = 0, + MONSTERS_EVENT_THINK = 1, + MONSTERS_EVENT_APPEAR = 2, + MONSTERS_EVENT_DISAPPEAR = 3, + MONSTERS_EVENT_MOVE = 4, + MONSTERS_EVENT_SAY = 5, +}; + #endif diff --git a/src/events.cpp b/src/events.cpp index 868901e..9b9c910 100644 --- a/src/events.cpp +++ b/src/events.cpp @@ -1,6 +1,6 @@ /** * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2016 Mark Samman + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,39 +29,9 @@ Events::Events() : scriptInterface("Event Interface") { - clear(); scriptInterface.initState(); } -void Events::clear() -{ - // Creature - creatureOnChangeOutfit = -1; - creatureOnAreaCombat = -1; - creatureOnTargetCombat = -1; - - // Party - partyOnJoin = -1; - partyOnLeave = -1; - partyOnDisband = -1; - - // Player - playerOnLook = -1; - playerOnLookInBattleList = -1; - playerOnLookInTrade = -1; - playerOnMoveItem = -1; - playerOnItemMoved = -1; - playerOnMoveCreature = -1; - playerOnTurn = -1; - playerOnTradeRequest = -1; - playerOnTradeAccept = -1; - playerOnGainExperience = -1; - playerOnLoseExperience = -1; - playerOnGainSkillTries = -1; - playerOnReportBug = -1; - -} - bool Events::load() { pugi::xml_document doc; @@ -71,6 +41,8 @@ bool Events::load() return false; } + info = {}; + std::set classes; for (auto eventNode : doc.child("events").children()) { if (!eventNode.attribute("enabled").as_bool()) { @@ -91,80 +63,69 @@ bool Events::load() const int32_t event = scriptInterface.getMetaEvent(className, methodName); if (className == "Creature") { if (methodName == "onChangeOutfit") { - creatureOnChangeOutfit = event; - } - else if (methodName == "onAreaCombat") { - creatureOnAreaCombat = event; - } - else if (methodName == "onTargetCombat") { - creatureOnTargetCombat = event; - } - else { + info.creatureOnChangeOutfit = event; + } else if (methodName == "onAreaCombat") { + info.creatureOnAreaCombat = event; + } else if (methodName == "onTargetCombat") { + info.creatureOnTargetCombat = event; + } else { std::cout << "[Warning - Events::load] Unknown creature method: " << methodName << std::endl; } - } - else if (className == "Party") { + } else if (className == "Party") { if (methodName == "onJoin") { - partyOnJoin = event; - } - else if (methodName == "onLeave") { - partyOnLeave = event; - } - else if (methodName == "onDisband") { - partyOnDisband = event; - } - else if (methodName == "onShareExperience") { - partyOnShareExperience = event; - } - else { + info.partyOnJoin = event; + } else if (methodName == "onLeave") { + info.partyOnLeave = event; + } else if (methodName == "onDisband") { + info.partyOnDisband = event; + } else if (methodName == "onShareExperience") { + info.partyOnShareExperience = event; + } else { std::cout << "[Warning - Events::load] Unknown party method: " << methodName << std::endl; } - } - else if (className == "Player") { - if (methodName == "onLook") { - playerOnLook = event; - } - else if (methodName == "onLookInBattleList") { - playerOnLookInBattleList = event; - } - else if (methodName == "onLookInTrade") { - playerOnLookInTrade = event; - } - else if (methodName == "onTradeRequest") { - playerOnTradeRequest = event; - } - else if (methodName == "onTradeAccept") { - playerOnTradeAccept = event; - } - else if (methodName == "onMoveItem") { - playerOnMoveItem = event; - } - else if (methodName == "onItemMoved") { - playerOnItemMoved = event; - } - else if (methodName == "onMoveCreature") { - playerOnMoveCreature = event; - } - else if (methodName == "onReportBug") { - playerOnReportBug = event; - } - else if (methodName == "onTurn") { - playerOnTurn = event; - } - else if (methodName == "onGainExperience") { - playerOnGainExperience = event; - } - else if (methodName == "onLoseExperience") { - playerOnLoseExperience = event; - } - else if (methodName == "onGainSkillTries") { - playerOnGainSkillTries = event; - } - else { + } else if (className == "Player") { + if (methodName == "onBrowseField") { + info.playerOnBrowseField = event; + } else if (methodName == "onLook") { + info.playerOnLook = event; + } else if (methodName == "onLookInBattleList") { + info.playerOnLookInBattleList = event; + } else if (methodName == "onLookInTrade") { + info.playerOnLookInTrade = event; + } else if (methodName == "onLookInShop") { + info.playerOnLookInShop = event; + } else if (methodName == "onTradeRequest") { + info.playerOnTradeRequest = event; + } else if (methodName == "onTradeAccept") { + info.playerOnTradeAccept = event; + } else if (methodName == "onMoveItem") { + info.playerOnMoveItem = event; + } else if (methodName == "onItemMoved") { + info.playerOnItemMoved = event; + } else if (methodName == "onMoveCreature") { + info.playerOnMoveCreature = event; + } else if (methodName == "onReportRuleViolation") { + info.playerOnReportRuleViolation = event; + } else if (methodName == "onReportBug") { + info.playerOnReportBug = event; + } else if (methodName == "onTurn") { + info.playerOnTurn = event; + } else if (methodName == "onGainExperience") { + info.playerOnGainExperience = event; + } else if (methodName == "onLoseExperience") { + info.playerOnLoseExperience = event; + } else if (methodName == "onGainSkillTries") { + info.playerOnGainSkillTries = event; + } else { std::cout << "[Warning - Events::load] Unknown player method: " << methodName << std::endl; } - } - else { + } else if (className == "Monster") { + if (methodName == "onDropLoot") { + info.monsterOnDropLoot = event; + } else { + std::cout << "[Warning - Events::load] Unknown monster method: " << methodName << std::endl; + } + } else { std::cout << "[Warning - Events::load] Unknown class: " << className << std::endl; } } @@ -175,7 +136,7 @@ bool Events::load() bool Events::eventCreatureOnChangeOutfit(Creature* creature, const Outfit_t& outfit) { // Creature:onChangeOutfit(outfit) or Creature.onChangeOutfit(self, outfit) - if (creatureOnChangeOutfit == -1) { + if (info.creatureOnChangeOutfit == -1) { return true; } @@ -185,10 +146,10 @@ bool Events::eventCreatureOnChangeOutfit(Creature* creature, const Outfit_t& out } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(creatureOnChangeOutfit, &scriptInterface); + env->setScriptId(info.creatureOnChangeOutfit, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(creatureOnChangeOutfit); + scriptInterface.pushFunction(info.creatureOnChangeOutfit); LuaScriptInterface::pushUserdata(L, creature); LuaScriptInterface::setCreatureMetatable(L, -1, creature); @@ -201,7 +162,7 @@ bool Events::eventCreatureOnChangeOutfit(Creature* creature, const Outfit_t& out ReturnValue Events::eventCreatureOnAreaCombat(Creature* creature, Tile* tile, bool aggressive) { // Creature:onAreaCombat(tile, aggressive) or Creature.onAreaCombat(self, tile, aggressive) - if (creatureOnAreaCombat == -1) { + if (info.creatureOnAreaCombat == -1) { return RETURNVALUE_NOERROR; } @@ -211,16 +172,15 @@ ReturnValue Events::eventCreatureOnAreaCombat(Creature* creature, Tile* tile, bo } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(creatureOnAreaCombat, &scriptInterface); + env->setScriptId(info.creatureOnAreaCombat, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(creatureOnAreaCombat); + scriptInterface.pushFunction(info.creatureOnAreaCombat); if (creature) { LuaScriptInterface::pushUserdata(L, creature); LuaScriptInterface::setCreatureMetatable(L, -1, creature); - } - else { + } else { lua_pushnil(L); } @@ -233,8 +193,7 @@ ReturnValue Events::eventCreatureOnAreaCombat(Creature* creature, Tile* tile, bo if (scriptInterface.protectedCall(L, 3, 1) != 0) { returnValue = RETURNVALUE_NOTPOSSIBLE; LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); - } - else { + } else { returnValue = LuaScriptInterface::getNumber(L, -1); lua_pop(L, 1); } @@ -246,7 +205,7 @@ ReturnValue Events::eventCreatureOnAreaCombat(Creature* creature, Tile* tile, bo ReturnValue Events::eventCreatureOnTargetCombat(Creature* creature, Creature* target) { // Creature:onTargetCombat(target) or Creature.onTargetCombat(self, target) - if (creatureOnTargetCombat == -1) { + if (info.creatureOnTargetCombat == -1) { return RETURNVALUE_NOERROR; } @@ -256,16 +215,15 @@ ReturnValue Events::eventCreatureOnTargetCombat(Creature* creature, Creature* ta } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(creatureOnTargetCombat, &scriptInterface); + env->setScriptId(info.creatureOnTargetCombat, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(creatureOnTargetCombat); + scriptInterface.pushFunction(info.creatureOnTargetCombat); if (creature) { LuaScriptInterface::pushUserdata(L, creature); LuaScriptInterface::setCreatureMetatable(L, -1, creature); - } - else { + } else { lua_pushnil(L); } @@ -276,8 +234,7 @@ ReturnValue Events::eventCreatureOnTargetCombat(Creature* creature, Creature* ta if (scriptInterface.protectedCall(L, 2, 1) != 0) { returnValue = RETURNVALUE_NOTPOSSIBLE; LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); - } - else { + } else { returnValue = LuaScriptInterface::getNumber(L, -1); lua_pop(L, 1); } @@ -290,7 +247,7 @@ ReturnValue Events::eventCreatureOnTargetCombat(Creature* creature, Creature* ta bool Events::eventPartyOnJoin(Party* party, Player* player) { // Party:onJoin(player) or Party.onJoin(self, player) - if (partyOnJoin == -1) { + if (info.partyOnJoin == -1) { return true; } @@ -300,10 +257,10 @@ bool Events::eventPartyOnJoin(Party* party, Player* player) } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(partyOnJoin, &scriptInterface); + env->setScriptId(info.partyOnJoin, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(partyOnJoin); + scriptInterface.pushFunction(info.partyOnJoin); LuaScriptInterface::pushUserdata(L, party); LuaScriptInterface::setMetatable(L, -1, "Party"); @@ -317,7 +274,7 @@ bool Events::eventPartyOnJoin(Party* party, Player* player) bool Events::eventPartyOnLeave(Party* party, Player* player) { // Party:onLeave(player) or Party.onLeave(self, player) - if (partyOnLeave == -1) { + if (info.partyOnLeave == -1) { return true; } @@ -327,10 +284,10 @@ bool Events::eventPartyOnLeave(Party* party, Player* player) } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(partyOnLeave, &scriptInterface); + env->setScriptId(info.partyOnLeave, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(partyOnLeave); + scriptInterface.pushFunction(info.partyOnLeave); LuaScriptInterface::pushUserdata(L, party); LuaScriptInterface::setMetatable(L, -1, "Party"); @@ -344,7 +301,7 @@ bool Events::eventPartyOnLeave(Party* party, Player* player) bool Events::eventPartyOnDisband(Party* party) { // Party:onDisband() or Party.onDisband(self) - if (partyOnDisband == -1) { + if (info.partyOnDisband == -1) { return true; } @@ -354,10 +311,10 @@ bool Events::eventPartyOnDisband(Party* party) } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(partyOnDisband, &scriptInterface); + env->setScriptId(info.partyOnDisband, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(partyOnDisband); + scriptInterface.pushFunction(info.partyOnDisband); LuaScriptInterface::pushUserdata(L, party); LuaScriptInterface::setMetatable(L, -1, "Party"); @@ -368,7 +325,7 @@ bool Events::eventPartyOnDisband(Party* party) void Events::eventPartyOnShareExperience(Party* party, uint64_t& exp) { // Party:onShareExperience(exp) or Party.onShareExperience(self, exp) - if (partyOnShareExperience == -1) { + if (info.partyOnShareExperience == -1) { return; } @@ -378,10 +335,10 @@ void Events::eventPartyOnShareExperience(Party* party, uint64_t& exp) } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(partyOnShareExperience, &scriptInterface); + env->setScriptId(info.partyOnShareExperience, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(partyOnShareExperience); + scriptInterface.pushFunction(info.partyOnShareExperience); LuaScriptInterface::pushUserdata(L, party); LuaScriptInterface::setMetatable(L, -1, "Party"); @@ -390,8 +347,7 @@ void Events::eventPartyOnShareExperience(Party* party, uint64_t& exp) if (scriptInterface.protectedCall(L, 2, 1) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); - } - else { + } else { exp = LuaScriptInterface::getNumber(L, -1); lua_pop(L, 1); } @@ -399,10 +355,37 @@ void Events::eventPartyOnShareExperience(Party* party, uint64_t& exp) scriptInterface.resetScriptEnv(); } +// Player +bool Events::eventPlayerOnBrowseField(Player* player, const Position& position) +{ + // Player:onBrowseField(position) or Player.onBrowseField(self, position) + if (info.playerOnBrowseField == -1) { + return true; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnBrowseField] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(info.playerOnBrowseField, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(info.playerOnBrowseField); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushPosition(L, position); + + return scriptInterface.callFunction(2); +} + void Events::eventPlayerOnLook(Player* player, const Position& position, Thing* thing, uint8_t stackpos, int32_t lookDistance) { // Player:onLook(thing, position, distance) or Player.onLook(self, thing, position, distance) - if (playerOnLook == -1) { + if (info.playerOnLook == -1) { return; } @@ -412,10 +395,10 @@ void Events::eventPlayerOnLook(Player* player, const Position& position, Thing* } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(playerOnLook, &scriptInterface); + env->setScriptId(info.playerOnLook, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(playerOnLook); + scriptInterface.pushFunction(info.playerOnLook); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -423,12 +406,10 @@ void Events::eventPlayerOnLook(Player* player, const Position& position, Thing* if (Creature* creature = thing->getCreature()) { LuaScriptInterface::pushUserdata(L, creature); LuaScriptInterface::setCreatureMetatable(L, -1, creature); - } - else if (Item* item = thing->getItem()) { + } else if (Item* item = thing->getItem()) { LuaScriptInterface::pushUserdata(L, item); LuaScriptInterface::setItemMetatable(L, -1, item); - } - else { + } else { lua_pushnil(L); } @@ -441,7 +422,7 @@ void Events::eventPlayerOnLook(Player* player, const Position& position, Thing* void Events::eventPlayerOnLookInBattleList(Player* player, Creature* creature, int32_t lookDistance) { // Player:onLookInBattleList(creature, position, distance) or Player.onLookInBattleList(self, creature, position, distance) - if (playerOnLookInBattleList == -1) { + if (info.playerOnLookInBattleList == -1) { return; } @@ -451,10 +432,10 @@ void Events::eventPlayerOnLookInBattleList(Player* player, Creature* creature, i } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(playerOnLookInBattleList, &scriptInterface); + env->setScriptId(info.playerOnLookInBattleList, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(playerOnLookInBattleList); + scriptInterface.pushFunction(info.playerOnLookInBattleList); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -470,7 +451,7 @@ void Events::eventPlayerOnLookInBattleList(Player* player, Creature* creature, i void Events::eventPlayerOnLookInTrade(Player* player, Player* partner, Item* item, int32_t lookDistance) { // Player:onLookInTrade(partner, item, distance) or Player.onLookInTrade(self, partner, item, distance) - if (playerOnLookInTrade == -1) { + if (info.playerOnLookInTrade == -1) { return; } @@ -480,10 +461,10 @@ void Events::eventPlayerOnLookInTrade(Player* player, Player* partner, Item* ite } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(playerOnLookInTrade, &scriptInterface); + env->setScriptId(info.playerOnLookInTrade, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(playerOnLookInTrade); + scriptInterface.pushFunction(info.playerOnLookInTrade); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -499,10 +480,39 @@ void Events::eventPlayerOnLookInTrade(Player* player, Player* partner, Item* ite scriptInterface.callVoidFunction(4); } +bool Events::eventPlayerOnLookInShop(Player* player, const ItemType* itemType, uint8_t count) +{ + // Player:onLookInShop(itemType, count) or Player.onLookInShop(self, itemType, count) + if (info.playerOnLookInShop == -1) { + return true; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnLookInShop] Call stack overflow" << std::endl; + return false; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(info.playerOnLookInShop, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(info.playerOnLookInShop); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushUserdata(L, itemType); + LuaScriptInterface::setMetatable(L, -1, "ItemType"); + + lua_pushnumber(L, count); + + return scriptInterface.callFunction(3); +} + bool Events::eventPlayerOnMoveItem(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder) { // Player:onMoveItem(item, count, fromPosition, toPosition) or Player.onMoveItem(self, item, count, fromPosition, toPosition, fromCylinder, toCylinder) - if (playerOnMoveItem == -1) { + if (info.playerOnMoveItem == -1) { return true; } @@ -512,10 +522,10 @@ bool Events::eventPlayerOnMoveItem(Player* player, Item* item, uint16_t count, c } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(playerOnMoveItem, &scriptInterface); + env->setScriptId(info.playerOnMoveItem, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(playerOnMoveItem); + scriptInterface.pushFunction(info.playerOnMoveItem); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -536,7 +546,7 @@ bool Events::eventPlayerOnMoveItem(Player* player, Item* item, uint16_t count, c void Events::eventPlayerOnItemMoved(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder) { // Player:onItemMoved(item, count, fromPosition, toPosition) or Player.onItemMoved(self, item, count, fromPosition, toPosition, fromCylinder, toCylinder) - if (playerOnItemMoved == -1) { + if (info.playerOnItemMoved == -1) { return; } @@ -546,10 +556,10 @@ void Events::eventPlayerOnItemMoved(Player* player, Item* item, uint16_t count, } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(playerOnItemMoved, &scriptInterface); + env->setScriptId(info.playerOnItemMoved, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(playerOnItemMoved); + scriptInterface.pushFunction(info.playerOnItemMoved); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -570,7 +580,7 @@ void Events::eventPlayerOnItemMoved(Player* player, Item* item, uint16_t count, bool Events::eventPlayerOnMoveCreature(Player* player, Creature* creature, const Position& fromPosition, const Position& toPosition) { // Player:onMoveCreature(creature, fromPosition, toPosition) or Player.onMoveCreature(self, creature, fromPosition, toPosition) - if (playerOnMoveCreature == -1) { + if (info.playerOnMoveCreature == -1) { return true; } @@ -580,10 +590,10 @@ bool Events::eventPlayerOnMoveCreature(Player* player, Creature* creature, const } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(playerOnMoveCreature, &scriptInterface); + env->setScriptId(info.playerOnMoveCreature, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(playerOnMoveCreature); + scriptInterface.pushFunction(info.playerOnMoveCreature); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -597,10 +607,42 @@ bool Events::eventPlayerOnMoveCreature(Player* player, Creature* creature, const return scriptInterface.callFunction(4); } -bool Events::eventPlayerOnReportBug(Player* player, const std::string& message, const Position& position) +void Events::eventPlayerOnReportRuleViolation(Player* player, const std::string& targetName, uint8_t reportType, uint8_t reportReason, const std::string& comment, const std::string& translation) +{ + // Player:onReportRuleViolation(targetName, reportType, reportReason, comment, translation) + if (info.playerOnReportRuleViolation == -1) { + return; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventPlayerOnReportRuleViolation] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(info.playerOnReportRuleViolation, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(info.playerOnReportRuleViolation); + + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + + LuaScriptInterface::pushString(L, targetName); + + lua_pushnumber(L, reportType); + lua_pushnumber(L, reportReason); + + LuaScriptInterface::pushString(L, comment); + LuaScriptInterface::pushString(L, translation); + + scriptInterface.callVoidFunction(6); +} + +bool Events::eventPlayerOnReportBug(Player* player, const std::string& message, const Position& position, uint8_t category) { // Player:onReportBug(message, position, category) - if (playerOnReportBug == -1) { + if (info.playerOnReportBug == -1) { return true; } @@ -610,24 +652,25 @@ bool Events::eventPlayerOnReportBug(Player* player, const std::string& message, } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(playerOnReportBug, &scriptInterface); + env->setScriptId(info.playerOnReportBug, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(playerOnReportBug); + scriptInterface.pushFunction(info.playerOnReportBug); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); LuaScriptInterface::pushString(L, message); LuaScriptInterface::pushPosition(L, position); + lua_pushnumber(L, category); - return scriptInterface.callFunction(3); + return scriptInterface.callFunction(4); } bool Events::eventPlayerOnTurn(Player* player, Direction direction) { // Player:onTurn(direction) or Player.onTurn(self, direction) - if (playerOnTurn == -1) { + if (info.playerOnTurn == -1) { return true; } @@ -637,10 +680,10 @@ bool Events::eventPlayerOnTurn(Player* player, Direction direction) } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(playerOnTurn, &scriptInterface); + env->setScriptId(info.playerOnTurn, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(playerOnTurn); + scriptInterface.pushFunction(info.playerOnTurn); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -653,7 +696,7 @@ bool Events::eventPlayerOnTurn(Player* player, Direction direction) bool Events::eventPlayerOnTradeRequest(Player* player, Player* target, Item* item) { // Player:onTradeRequest(target, item) - if (playerOnTradeRequest == -1) { + if (info.playerOnTradeRequest == -1) { return true; } @@ -663,10 +706,10 @@ bool Events::eventPlayerOnTradeRequest(Player* player, Player* target, Item* ite } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(playerOnTradeRequest, &scriptInterface); + env->setScriptId(info.playerOnTradeRequest, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(playerOnTradeRequest); + scriptInterface.pushFunction(info.playerOnTradeRequest); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -683,7 +726,7 @@ bool Events::eventPlayerOnTradeRequest(Player* player, Player* target, Item* ite bool Events::eventPlayerOnTradeAccept(Player* player, Player* target, Item* item, Item* targetItem) { // Player:onTradeAccept(target, item, targetItem) - if (playerOnTradeAccept == -1) { + if (info.playerOnTradeAccept == -1) { return true; } @@ -693,10 +736,10 @@ bool Events::eventPlayerOnTradeAccept(Player* player, Player* target, Item* item } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(playerOnTradeAccept, &scriptInterface); + env->setScriptId(info.playerOnTradeAccept, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(playerOnTradeAccept); + scriptInterface.pushFunction(info.playerOnTradeAccept); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -717,7 +760,7 @@ void Events::eventPlayerOnGainExperience(Player* player, Creature* source, uint6 { // Player:onGainExperience(source, exp, rawExp) // rawExp gives the original exp which is not multiplied - if (playerOnGainExperience == -1) { + if (info.playerOnGainExperience == -1) { return; } @@ -727,10 +770,10 @@ void Events::eventPlayerOnGainExperience(Player* player, Creature* source, uint6 } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(playerOnGainExperience, &scriptInterface); + env->setScriptId(info.playerOnGainExperience, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(playerOnGainExperience); + scriptInterface.pushFunction(info.playerOnGainExperience); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -738,8 +781,7 @@ void Events::eventPlayerOnGainExperience(Player* player, Creature* source, uint6 if (source) { LuaScriptInterface::pushUserdata(L, source); LuaScriptInterface::setCreatureMetatable(L, -1, source); - } - else { + } else { lua_pushnil(L); } @@ -748,8 +790,7 @@ void Events::eventPlayerOnGainExperience(Player* player, Creature* source, uint6 if (scriptInterface.protectedCall(L, 4, 1) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); - } - else { + } else { exp = LuaScriptInterface::getNumber(L, -1); lua_pop(L, 1); } @@ -760,7 +801,7 @@ void Events::eventPlayerOnGainExperience(Player* player, Creature* source, uint6 void Events::eventPlayerOnLoseExperience(Player* player, uint64_t& exp) { // Player:onLoseExperience(exp) - if (playerOnLoseExperience == -1) { + if (info.playerOnLoseExperience == -1) { return; } @@ -770,10 +811,10 @@ void Events::eventPlayerOnLoseExperience(Player* player, uint64_t& exp) } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(playerOnLoseExperience, &scriptInterface); + env->setScriptId(info.playerOnLoseExperience, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(playerOnLoseExperience); + scriptInterface.pushFunction(info.playerOnLoseExperience); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -782,8 +823,7 @@ void Events::eventPlayerOnLoseExperience(Player* player, uint64_t& exp) if (scriptInterface.protectedCall(L, 2, 1) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); - } - else { + } else { exp = LuaScriptInterface::getNumber(L, -1); lua_pop(L, 1); } @@ -794,7 +834,7 @@ void Events::eventPlayerOnLoseExperience(Player* player, uint64_t& exp) void Events::eventPlayerOnGainSkillTries(Player* player, skills_t skill, uint64_t& tries) { // Player:onGainSkillTries(skill, tries) - if (playerOnGainSkillTries == -1) { + if (info.playerOnGainSkillTries == -1) { return; } @@ -804,10 +844,10 @@ void Events::eventPlayerOnGainSkillTries(Player* player, skills_t skill, uint64_ } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(playerOnGainSkillTries, &scriptInterface); + env->setScriptId(info.playerOnGainSkillTries, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(playerOnGainSkillTries); + scriptInterface.pushFunction(info.playerOnGainSkillTries); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -817,11 +857,37 @@ void Events::eventPlayerOnGainSkillTries(Player* player, skills_t skill, uint64_ if (scriptInterface.protectedCall(L, 3, 1) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); - } - else { + } else { tries = LuaScriptInterface::getNumber(L, -1); lua_pop(L, 1); } scriptInterface.resetScriptEnv(); -} \ No newline at end of file +} + +void Events::eventMonsterOnDropLoot(Monster* monster, Container* corpse) +{ + // Monster:onDropLoot(corpse) + if (info.monsterOnDropLoot == -1) { + return; + } + + if (!scriptInterface.reserveScriptEnv()) { + std::cout << "[Error - Events::eventMonsterOnDropLoot] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface.getScriptEnv(); + env->setScriptId(info.monsterOnDropLoot, &scriptInterface); + + lua_State* L = scriptInterface.getLuaState(); + scriptInterface.pushFunction(info.monsterOnDropLoot); + + LuaScriptInterface::pushUserdata(L, monster); + LuaScriptInterface::setMetatable(L, -1, "Monster"); + + LuaScriptInterface::pushUserdata(L, corpse); + LuaScriptInterface::setMetatable(L, -1, "Container"); + + return scriptInterface.callVoidFunction(2); +} diff --git a/src/events.h b/src/events.h index 7582c29..2f81b6e 100644 --- a/src/events.h +++ b/src/events.h @@ -1,6 +1,6 @@ /** * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2016 Mark Samman + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,68 +28,80 @@ class Tile; class Events { -public: - Events(); + struct EventsInfo { + // Creature + int32_t creatureOnChangeOutfit = -1; + int32_t creatureOnAreaCombat = -1; + int32_t creatureOnTargetCombat = -1; - void clear(); - bool load(); + // Party + int32_t partyOnJoin = -1; + int32_t partyOnLeave = -1; + int32_t partyOnDisband = -1; + int32_t partyOnShareExperience = -1; - // Creature - bool eventCreatureOnChangeOutfit(Creature* creature, const Outfit_t& outfit); - ReturnValue eventCreatureOnAreaCombat(Creature* creature, Tile* tile, bool aggressive); - ReturnValue eventCreatureOnTargetCombat(Creature* creature, Creature* target); + // Player + int32_t playerOnBrowseField = -1; + int32_t playerOnLook = -1; + int32_t playerOnLookInBattleList = -1; + int32_t playerOnLookInTrade = -1; + int32_t playerOnLookInShop = -1; + int32_t playerOnMoveItem = -1; + int32_t playerOnItemMoved = -1; + int32_t playerOnMoveCreature = -1; + int32_t playerOnReportRuleViolation = -1; + int32_t playerOnReportBug = -1; + int32_t playerOnTurn = -1; + int32_t playerOnTradeRequest = -1; + int32_t playerOnTradeAccept = -1; + int32_t playerOnGainExperience = -1; + int32_t playerOnLoseExperience = -1; + int32_t playerOnGainSkillTries = -1; - // Party - bool eventPartyOnJoin(Party* party, Player* player); - bool eventPartyOnLeave(Party* party, Player* player); - bool eventPartyOnDisband(Party* party); - void eventPartyOnShareExperience(Party* party, uint64_t& exp); + // Monster + int32_t monsterOnDropLoot = -1; + }; - // Player - void eventPlayerOnLook(Player* player, const Position& position, Thing* thing, uint8_t stackpos, int32_t lookDistance); - void eventPlayerOnLookInBattleList(Player* player, Creature* creature, int32_t lookDistance); - void eventPlayerOnLookInTrade(Player* player, Player* partner, Item* item, int32_t lookDistance); - bool eventPlayerOnMoveItem(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder); - void eventPlayerOnItemMoved(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder); - bool eventPlayerOnMoveCreature(Player* player, Creature* creature, const Position& fromPosition, const Position& toPosition); - void eventPlayerOnReportRuleViolation(Player* player, const std::string& targetName, uint8_t reportType, uint8_t reportReason, const std::string& comment, const std::string& translation); - bool eventPlayerOnReportBug(Player* player, const std::string& message, const Position& position); - bool eventPlayerOnTurn(Player* player, Direction direction); - bool eventPlayerOnTradeRequest(Player* player, Player* target, Item* item); - bool eventPlayerOnTradeAccept(Player* player, Player* target, Item* item, Item* targetItem); - void eventPlayerOnGainExperience(Player* player, Creature* source, uint64_t& exp, uint64_t rawExp); - void eventPlayerOnLoseExperience(Player* player, uint64_t& exp); - void eventPlayerOnGainSkillTries(Player* player, skills_t skill, uint64_t& tries); + public: + Events(); -private: - LuaScriptInterface scriptInterface; + bool load(); - // Creature - int32_t creatureOnChangeOutfit; - int32_t creatureOnAreaCombat; - int32_t creatureOnTargetCombat; + // Creature + bool eventCreatureOnChangeOutfit(Creature* creature, const Outfit_t& outfit); + ReturnValue eventCreatureOnAreaCombat(Creature* creature, Tile* tile, bool aggressive); + ReturnValue eventCreatureOnTargetCombat(Creature* creature, Creature* target); - // Party - int32_t partyOnJoin; - int32_t partyOnLeave; - int32_t partyOnDisband; - int32_t partyOnShareExperience; + // Party + bool eventPartyOnJoin(Party* party, Player* player); + bool eventPartyOnLeave(Party* party, Player* player); + bool eventPartyOnDisband(Party* party); + void eventPartyOnShareExperience(Party* party, uint64_t& exp); - // Player - int32_t playerOnLook; - int32_t playerOnLookInBattleList; - int32_t playerOnLookInTrade; - int32_t playerOnMoveItem; - int32_t playerOnItemMoved; - int32_t playerOnMoveCreature; - int32_t playerOnReportRuleViolation; - int32_t playerOnReportBug; - int32_t playerOnTurn; - int32_t playerOnTradeRequest; - int32_t playerOnTradeAccept; - int32_t playerOnGainExperience; - int32_t playerOnLoseExperience; - int32_t playerOnGainSkillTries; + // Player + bool eventPlayerOnBrowseField(Player* player, const Position& position); + void eventPlayerOnLook(Player* player, const Position& position, Thing* thing, uint8_t stackpos, int32_t lookDistance); + void eventPlayerOnLookInBattleList(Player* player, Creature* creature, int32_t lookDistance); + void eventPlayerOnLookInTrade(Player* player, Player* partner, Item* item, int32_t lookDistance); + bool eventPlayerOnLookInShop(Player* player, const ItemType* itemType, uint8_t count); + bool eventPlayerOnMoveItem(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder); + void eventPlayerOnItemMoved(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder); + bool eventPlayerOnMoveCreature(Player* player, Creature* creature, const Position& fromPosition, const Position& toPosition); + void eventPlayerOnReportRuleViolation(Player* player, const std::string& targetName, uint8_t reportType, uint8_t reportReason, const std::string& comment, const std::string& translation); + bool eventPlayerOnReportBug(Player* player, const std::string& message, const Position& position, uint8_t category); + bool eventPlayerOnTurn(Player* player, Direction direction); + bool eventPlayerOnTradeRequest(Player* player, Player* target, Item* item); + bool eventPlayerOnTradeAccept(Player* player, Player* target, Item* item, Item* targetItem); + void eventPlayerOnGainExperience(Player* player, Creature* source, uint64_t& exp, uint64_t rawExp); + void eventPlayerOnLoseExperience(Player* player, uint64_t& exp); + void eventPlayerOnGainSkillTries(Player* player, skills_t skill, uint64_t& tries); + + // Monster + void eventMonsterOnDropLoot(Monster* monster, Container* corpse); + + private: + LuaScriptInterface scriptInterface; + EventsInfo info; }; -#endif \ No newline at end of file +#endif diff --git a/src/fileloader.cpp b/src/fileloader.cpp index be25ecf..5c754fd 100644 --- a/src/fileloader.cpp +++ b/src/fileloader.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,387 +19,106 @@ #include "otpch.h" +#include #include "fileloader.h" -FileLoader::~FileLoader() + +namespace OTB { + +constexpr Identifier wildcard = {{'\0', '\0', '\0', '\0'}}; + +Loader::Loader(const std::string& fileName, const Identifier& acceptedIdentifier): + fileContents(fileName) { - if (file) { - fclose(file); - file = nullptr; + constexpr auto minimalSize = sizeof(Identifier) + sizeof(Node::START) + sizeof(Node::type) + sizeof(Node::END); + if (fileContents.size() <= minimalSize) { + throw InvalidOTBFormat{}; } - NodeStruct::clearNet(root); - delete[] buffer; - - for (auto& i : cached_data) { - delete[] i.data; + Identifier fileIdentifier; + std::copy(fileContents.begin(), fileContents.begin() + fileIdentifier.size(), fileIdentifier.begin()); + if (fileIdentifier != acceptedIdentifier && fileIdentifier != wildcard) { + throw InvalidOTBFormat{}; } } -bool FileLoader::openFile(const char* filename, const char* accept_identifier) -{ - file = fopen(filename, "rb"); - if (!file) { - lastError = ERROR_CAN_NOT_OPEN; - return false; +using NodeStack = std::stack>; +static Node& getCurrentNode(const NodeStack& nodeStack) { + if (nodeStack.empty()) { + throw InvalidOTBFormat{}; } - - 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(32768, std::max(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; + return *nodeStack.top(); } -bool FileLoader::parseNode(NODE node) +const Node& Loader::parseTree() { - int32_t byte, pos; - NODE currentNode = node; + auto it = fileContents.begin() + sizeof(Identifier); + if (static_cast(*it) != Node::START) { + throw InvalidOTBFormat{}; + } + root.type = *(++it); + root.propsBegin = ++it; + NodeStack parseStack; + parseStack.push(&root); - while (readByte(byte)) { - currentNode->type = byte; - bool setPropsSize = false; - - while (true) { - if (!readByte(byte)) { - return false; + for (; it != fileContents.end(); ++it) { + switch(static_cast(*it)) { + case Node::START: { + auto& currentNode = getCurrentNode(parseStack); + if (currentNode.children.empty()) { + currentNode.propsEnd = it; + } + currentNode.children.emplace_back(); + auto& child = currentNode.children.back(); + if (++it == fileContents.end()) { + throw InvalidOTBFormat{}; + } + child.type = *it; + child.propsBegin = it + sizeof(Node::type); + parseStack.push(&child); + break; } - - 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: { + auto& currentNode = getCurrentNode(parseStack); + if (currentNode.children.empty()) { + currentNode.propsEnd = it; } - - 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; + parseStack.pop(); + break; } - - if (skipNode) { + case Node::ESCAPE: { + if (++it == fileContents.end()) { + throw InvalidOTBFormat{}; + } + break; + } + default: { break; } } } - return false; -} - -const uint8_t* FileLoader::getProps(const NODE node, size_t& size) -{ - if (!node) { - return nullptr; + if (!parseStack.empty()) { + throw InvalidOTBFormat{}; } - 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(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) +bool Loader::getProps(const Node& node, PropStream& props) { - 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; + auto size = std::distance(node.propsBegin, node.propsEnd); + if (size == 0) { return false; } + propBuffer.resize(size); + bool lastEscaped = 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++]; + auto escapedPropEnd = std::copy_if(node.propsBegin, node.propsEnd, propBuffer.begin(), [&lastEscaped](const char& byte) { + lastEscaped = byte == static_cast(Node::ESCAPE) && !lastEscaped; + return !lastEscaped; + }); + props.init(&propBuffer[0], std::distance(propBuffer.begin(), escapedPropEnd)); 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(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(cached_data[i].base) - base_pos) > static_cast(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; -} +} //namespace OTB diff --git a/src/fileloader.h b/src/fileloader.h index 74872b0..c55f624 100644 --- a/src/fileloader.h +++ b/src/fileloader.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,133 +22,53 @@ #include #include - -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, -}; +#include class PropStream; -class FileLoader +namespace OTB { +using MappedFile = boost::iostreams::mapped_file_source; +using ContentIt = MappedFile::iterator; +using Identifier = std::array; + +struct Node { - public: - FileLoader() = default; - ~FileLoader(); + using ChildrenVector = std::vector; - // 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::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); + ChildrenVector children; + ContentIt propsBegin; + ContentIt propsEnd; + uint8_t type; + enum NodeChar: uint8_t + { + ESCAPE = 0xFD, + START = 0xFE, + END = 0xFF, + }; }; +struct LoadError : std::exception { + const char* what() const noexcept override = 0; +}; + +struct InvalidOTBFormat final : LoadError { + const char* what() const noexcept override { + return "Invalid OTBM file format"; + } +}; + +class Loader { + MappedFile fileContents; + Node root; + std::vector propBuffer; +public: + Loader(const std::string& fileName, const Identifier& acceptedIdentifier); + bool getProps(const Node& node, PropStream& props); + const Node& parseTree(); +}; + +} //namespace OTB + class PropStream { public: @@ -200,7 +120,7 @@ class PropStream return true; } - protected: + private: const char* p = nullptr; const char* end = nullptr; }; @@ -240,7 +160,7 @@ class PropWriteStream std::copy(str.begin(), str.end(), std::back_inserter(buffer)); } - protected: + private: std::vector buffer; }; diff --git a/src/game.cpp b/src/game.cpp index 114b961..be8bf46 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,22 +21,26 @@ #include "pugicast.h" -#include "items.h" +#include "actions.h" +#include "bed.h" +#include "configmanager.h" #include "creature.h" -#include "monster.h" +#include "creatureevent.h" +#include "databasetasks.h" #include "events.h" #include "game.h" -#include "actions.h" -#include "iologindata.h" -#include "talkaction.h" -#include "spells.h" -#include "configmanager.h" -#include "server.h" #include "globalevent.h" -#include "bed.h" -#include "scheduler.h" -#include "databasetasks.h" +#include "iologindata.h" +#include "iomarket.h" +#include "items.h" +#include "monster.h" #include "movement.h" +#include "scheduler.h" +#include "server.h" +#include "spells.h" +#include "talkaction.h" +#include "weapons.h" +#include "script.h" extern ConfigManager g_config; extern Actions* g_actions; @@ -45,10 +49,26 @@ extern TalkActions* g_talkActions; extern Spells* g_spells; extern Vocations g_vocations; extern GlobalEvents* g_globalEvents; -extern Events* g_events; extern CreatureEvents* g_creatureEvents; +extern Events* g_events; extern Monsters g_monsters; extern MoveEvents* g_moveEvents; +extern Weapons* g_weapons; +extern Scripts* g_scripts; + +Game::Game() +{ + offlineTrainingWindow.choices.emplace_back("Sword Fighting and Shielding", SKILL_SWORD); + offlineTrainingWindow.choices.emplace_back("Axe Fighting and Shielding", SKILL_AXE); + offlineTrainingWindow.choices.emplace_back("Club Fighting and Shielding", SKILL_CLUB); + offlineTrainingWindow.choices.emplace_back("Distance Fighting and Shielding", SKILL_DISTANCE); + offlineTrainingWindow.choices.emplace_back("Magic Level and Shielding", SKILL_MAGLEVEL); + offlineTrainingWindow.buttons.emplace_back("Okay", 1); + offlineTrainingWindow.buttons.emplace_back("Cancel", 0); + offlineTrainingWindow.defaultEnterButton = 1; + offlineTrainingWindow.defaultEscapeButton = 0; + offlineTrainingWindow.priority = true; +} Game::~Game() { @@ -99,6 +119,9 @@ void Game::setGameState(GameState_t newState) raids.loadFromXml(); raids.startup(); + quests.loadFromXml(); + mounts.loadFromXml(); + loadMotdNum(); loadPlayersRecord(); @@ -164,6 +187,8 @@ void Game::saveGameState() Map::save(); + g_databaseTasks.flush(); + if (gameState == GAME_STATE_MAINTAIN) { setGameState(GAME_STATE_NORMAL); } @@ -232,7 +257,7 @@ Thing* Game::internalGetThing(Player* player, const Position& pos, int32_t index } case STACKPOS_USETARGET: { - thing = tile->getTopCreature(); + thing = tile->getTopVisibleCreature(player); if (!thing) { thing = tile->getUseItem(index); } @@ -271,17 +296,32 @@ Thing* Game::internalGetThing(Player* player, const Position& pos, int32_t index return nullptr; } + if (parentContainer->getID() == ITEM_BROWSEFIELD) { + Tile* tile = parentContainer->getTile(); + if (tile && tile->hasFlag(TILESTATE_SUPPORTS_HANGABLE)) { + if (tile->hasProperty(CONST_PROP_ISVERTICAL)) { + if (player->getPosition().x + 1 == tile->getPosition().x) { + return nullptr; + } + } else { // horizontal + if (player->getPosition().y + 1 == tile->getPosition().y) { + return nullptr; + } + } + } + } + uint8_t slot = pos.z; return parentContainer->getItemByIndex(player->getContainerIndex(fromCid) + slot); } else if (pos.y == 0 && pos.z == 0) { - const ItemType& it = Item::items.getItemType(spriteId); + const ItemType& it = Item::items.getItemIdByClientId(spriteId); if (it.id == 0) { return nullptr; } int32_t subType; - if (it.isFluidContainer()) { - subType = static_cast(index); + if (it.isFluidContainer() && index < static_cast(sizeof(reverseFluidMap) / sizeof(uint8_t))) { + subType = reverseFluidMap[index]; } else { subType = -1; } @@ -501,15 +541,15 @@ bool Game::placeCreature(Creature* creature, const Position& pos, bool extendedP return false; } - SpectatorVec list; - map.getSpectators(list, creature->getPosition(), true); - for (Creature* spectator : list) { + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), true); + for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendCreatureAppear(creature, creature->getPosition(), true); } } - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { spectator->onCreatureAppear(creature, true); } @@ -530,9 +570,9 @@ bool Game::removeCreature(Creature* creature, bool isLogout/* = true*/) std::vector oldStackPosVector; - SpectatorVec list; - map.getSpectators(list, tile->getPosition(), true); - for (Creature* spectator : list) { + SpectatorVec spectators; + map.getSpectators(spectators, tile->getPosition(), true); + for (Creature* spectator : spectators) { if (Player* player = spectator->getPlayer()) { oldStackPosVector.push_back(player->canSeeCreature(creature) ? tile->getStackposOfCreature(player, creature) : -1); } @@ -544,14 +584,14 @@ bool Game::removeCreature(Creature* creature, bool isLogout/* = true*/) //send to client size_t i = 0; - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { if (Player* player = spectator->getPlayer()) { player->sendRemoveTileThing(tilePosition, oldStackPosVector[i++]); } } //event method - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { spectator->onRemoveCreature(creature, isLogout); } @@ -564,7 +604,7 @@ bool Game::removeCreature(Creature* creature, bool isLogout/* = true*/) removeCreatureCheck(creature); for (Creature* summon : creature->summons) { - summon->setLossSkill(false); + summon->setSkillLoss(false); removeCreature(summon); } return true; @@ -654,13 +694,6 @@ void Game::playerMoveCreature(Player* player, Creature* movingCreature, const Po player->setNextActionTask(nullptr); - if (g_config.getBoolean(ConfigManager::BLOCK_HEIGHT)) { - if (toTile->getHeight() > 1) { - player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); - return; - } - } - if (!Position::areInRange<1, 1, 0>(movingCreatureOrigPos, player->getPosition())) { //need to walk to the creature first before moving it std::forward_list listDir; @@ -730,64 +763,45 @@ ReturnValue Game::internalMoveCreature(Creature* creature, Direction direction, creature->setLastPosition(creature->getPosition()); const Position& currentPos = creature->getPosition(); Position destPos = getNextPosition(direction, currentPos); + Player* player = creature->getPlayer(); bool diagonalMovement = (direction & DIRECTION_DIAGONAL_MASK) != 0; - if (creature->getPlayer() && !diagonalMovement) { + if (player && !diagonalMovement) { //try go up if (currentPos.z != 8 && creature->getTile()->hasHeight(3)) { Tile* tmpTile = map.getTile(currentPos.x, currentPos.y, currentPos.getZ() - 1); if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) { tmpTile = map.getTile(destPos.x, destPos.y, destPos.getZ() - 1); if (tmpTile && tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID)) { - destPos.z--; - internalCreatureTurn(creature, DIRECTION_NORTH); + flags |= FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE; + + if (!tmpTile->hasFlag(TILESTATE_FLOORCHANGE)) { + player->setDirection(direction); + destPos.z--; + } } } } - else { - //try go down + + //try go down + if (currentPos.z != 7 && currentPos.z == destPos.z) { Tile* tmpTile = map.getTile(destPos.x, destPos.y, destPos.z); - if (currentPos.z != 7 && (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID)))) { + if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) { tmpTile = map.getTile(destPos.x, destPos.y, destPos.z + 1); if (tmpTile && tmpTile->hasHeight(3)) { + flags |= FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE; + player->setDirection(direction); destPos.z++; - internalCreatureTurn(creature, DIRECTION_SOUTH); } } } } - ReturnValue ret = RETURNVALUE_NOTPOSSIBLE; Tile* toTile = map.getTile(destPos); - - Tile* toPos = map.getTile(destPos.x, destPos.y, destPos.z); - Tile* fromPos = map.getTile(currentPos.x, currentPos.y, currentPos.z); - - if (g_config.getBoolean(ConfigManager::BLOCK_HEIGHT)) { - if (toTile) { - if (currentPos.z > destPos.z && toPos->getHeight() > 1); - // not possible - else if ((((toPos->getHeight() - fromPos->getHeight()) < 2)) || - (fromPos->hasHeight(3) && (currentPos.z == destPos.z)) || - ((currentPos.z < destPos.z) && (toPos->hasHeight(3) && (fromPos->getHeight() < 2)))) - ret = internalMoveCreature(*creature, *toTile, flags); - } - - if (ret != RETURNVALUE_NOERROR) { - if (Player* player = creature->getPlayer()) { - player->sendCancelMessage(ret); - player->sendCancelWalk(); - } - } - - return ret; - } - else { - if (!toTile) { - return RETURNVALUE_NOTPOSSIBLE; - } - return internalMoveCreature(*creature, *toTile, flags); + if (!toTile) { + return RETURNVALUE_NOTPOSSIBLE; } + return internalMoveCreature(*creature, *toTile, flags); } ReturnValue Game::internalMoveCreature(Creature& creature, Tile& toTile, uint32_t flags /*= 0*/) @@ -886,7 +900,7 @@ void Game::playerMoveItem(Player* player, const Position& fromPos, item = thing->getItem(); } - if ((item->isDisguised() && item->getDisguiseId() != spriteId) || (!item->isDisguised() && item->getID() != spriteId)) { + if (item->getClientID() != spriteId) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } @@ -905,7 +919,7 @@ void Game::playerMoveItem(Player* player, const Position& fromPos, } } - if (!item->isPushable()) { + if (!item->isPushable() || item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { player->sendCancelMessage(RETURNVALUE_NOTMOVEABLE); return; } @@ -1018,17 +1032,25 @@ void Game::playerMoveItem(Player* player, const Position& fromPos, } } - ReturnValue ret = internalMoveItem(fromCylinder, toCylinder, toIndex, item, item->isRune() ? item->getItemCount() : count, nullptr, 0, player); + ReturnValue ret = internalMoveItem(fromCylinder, toCylinder, toIndex, item, count, nullptr, 0, player); if (ret != RETURNVALUE_NOERROR) { player->sendCancelMessage(ret); } else { - g_events->eventPlayerOnItemMoved(player, item, item->isRune() ? item->getItemCount() : count, fromPos, toPos, fromCylinder, toCylinder); + g_events->eventPlayerOnItemMoved(player, item, count, fromPos, toPos, fromCylinder, toCylinder); } } ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, int32_t index, Item* item, uint32_t count, Item** _moveItem, uint32_t flags /*= 0*/, Creature* actor/* = nullptr*/, Item* tradeItem/* = nullptr*/) { + Tile* fromTile = fromCylinder->getTile(); + if (fromTile) { + auto it = browseFields.find(fromTile); + if (it != browseFields.end() && it->second == fromCylinder) { + fromCylinder = fromTile; + } + } + Item* toItem = nullptr; Cylinder* subCylinder; @@ -1096,14 +1118,8 @@ ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, uint32_t m; if (item->isStackable()) { - if (item->isRune()) { - m = std::min(item->getItemCount(), maxQueryCount); - } - else { - m = std::min(count, maxQueryCount); - } - } - else { + m = std::min(count, maxQueryCount); + } else { m = maxQueryCount; } @@ -1139,12 +1155,11 @@ ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, if (item->isStackable()) { uint32_t n; - if (!item->isRune() && item->equals(toItem)) { + if (item->equals(toItem)) { n = std::min(100 - toItem->getItemCount(), m); toCylinder->updateThing(toItem, toItem->getID(), toItem->getItemCount() + n); updateItem = toItem; - } - else { + } else { n = 0; } @@ -1197,6 +1212,14 @@ ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, return retMaxCount; } + if (moveItem && moveItem->getDuration() > 0) { + if (moveItem->getDecaying() != DECAYING_TRUE) { + moveItem->incrementReferenceCounter(); + moveItem->setDecaying(DECAYING_TRUE); + toDecayItems.push_front(moveItem); + } + } + return ret; } @@ -1239,7 +1262,7 @@ ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t inde return RETURNVALUE_NOERROR; } - if (item->isStackable() && !item->isRune() && item->equals(toItem)) { + if (item->isStackable() && item->equals(toItem)) { uint32_t m = std::min(item->getItemCount(), maxQueryCount); uint32_t n = std::min(100 - toItem->getItemCount(), m); @@ -1254,8 +1277,7 @@ ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t inde ReleaseItem(remainderItem); remainderCount = count; } - } - else { + } else { toCylinder->addThing(index, item); int32_t itemIndex = toCylinder->getThingIndex(item); @@ -1263,8 +1285,7 @@ ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t inde toCylinder->postAddNotification(item, nullptr, itemIndex); } } - } - else { + } else { //fully merged with toItem, item will be destroyed item->onRemoved(); ReleaseItem(item); @@ -1274,8 +1295,7 @@ ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t inde toCylinder->postAddNotification(toItem, nullptr, itemIndex); } } - } - else { + } else { toCylinder->addThing(index, item); int32_t itemIndex = toCylinder->getThingIndex(item); @@ -1284,6 +1304,12 @@ ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t inde } } + if (item->getDuration() > 0) { + item->incrementReferenceCounter(); + item->setDecaying(DECAYING_TRUE); + toDecayItems.push_front(item); + } + return RETURNVALUE_NOERROR; } @@ -1294,6 +1320,14 @@ ReturnValue Game::internalRemoveItem(Item* item, int32_t count /*= -1*/, bool te return RETURNVALUE_NOTPOSSIBLE; } + Tile* fromTile = cylinder->getTile(); + if (fromTile) { + auto it = browseFields.find(fromTile); + if (it != browseFields.end() && it->second == cylinder) { + cylinder = fromTile; + } + } + if (count == -1) { count = item->getItemCount(); } @@ -1315,13 +1349,16 @@ ReturnValue Game::internalRemoveItem(Item* item, int32_t count /*= -1*/, bool te cylinder->removeThing(item, count); if (item->isRemoved()) { + item->onRemoved(); + if (item->canDecay()) { + decayItems->remove(item); + } ReleaseItem(item); } cylinder->postRemoveNotification(item, nullptr, index); } - item->onRemoved(); return RETURNVALUE_NOERROR; } @@ -1525,6 +1562,14 @@ Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/) return nullptr; } + Tile* fromTile = cylinder->getTile(); + if (fromTile) { + auto it = browseFields.find(fromTile); + if (it != browseFields.end() && it->second == cylinder) { + cylinder = fromTile; + } + } + int32_t itemIndex = cylinder->getThingIndex(item); if (itemIndex == -1) { return item; @@ -1571,7 +1616,7 @@ Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/) } else { int32_t newItemId = newId; if (curType.id == newType.id) { - newItemId = curType.decayTo; + newItemId = item->getDecayTo(); } if (newItemId < 0) { @@ -1637,6 +1682,14 @@ Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/) cylinder->postRemoveNotification(item, cylinder, itemIndex); ReleaseItem(item); + if (newItem->getDuration() > 0) { + if (newItem->getDecaying() != DECAYING_TRUE) { + newItem->incrementReferenceCounter(); + newItem->setDecaying(DECAYING_TRUE); + toDecayItems.push_front(newItem); + } + } + return newItem; } @@ -1659,19 +1712,6 @@ ReturnValue Game::internalTeleport(Thing* thing, const Position& newPos, bool pu return ret; } - Position fromPos = creature->getPosition(); - if (Position::getOffsetX(fromPos, newPos) <= 0) { - if (Position::getOffsetX(fromPos, newPos) < 0) { - internalCreatureTurn(creature, DIRECTION_EAST); - } else if (Position::getOffsetY(fromPos, newPos) < 0) { - internalCreatureTurn(creature, DIRECTION_SOUTH); - } else if (Position::getOffsetY(fromPos, newPos) > 0) { - internalCreatureTurn(creature, DIRECTION_NORTH); - } - } else { - internalCreatureTurn(creature, DIRECTION_WEST); - } - map.moveCreature(*creature, *toTile, !pushMove); return RETURNVALUE_NOERROR; } else if (Item* item = thing->getItem()) { @@ -1680,7 +1720,75 @@ ReturnValue Game::internalTeleport(Thing* thing, const Position& newPos, bool pu return RETURNVALUE_NOTPOSSIBLE; } +Item* searchForItem(Container* container, uint16_t itemId) +{ + for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { + if ((*it)->getID() == itemId) { + return *it; + } + } + + return nullptr; +} + +slots_t getSlotType(const ItemType& it) +{ + slots_t slot = CONST_SLOT_RIGHT; + if (it.weaponType != WeaponType_t::WEAPON_SHIELD) { + int32_t slotPosition = it.slotPosition; + + if (slotPosition & SLOTP_HEAD) { + slot = CONST_SLOT_HEAD; + } else if (slotPosition & SLOTP_NECKLACE) { + slot = CONST_SLOT_NECKLACE; + } else if (slotPosition & SLOTP_ARMOR) { + slot = CONST_SLOT_ARMOR; + } else if (slotPosition & SLOTP_LEGS) { + slot = CONST_SLOT_LEGS; + } else if (slotPosition & SLOTP_FEET) { + slot = CONST_SLOT_FEET ; + } else if (slotPosition & SLOTP_RING) { + slot = CONST_SLOT_RING; + } else if (slotPosition & SLOTP_AMMO) { + slot = CONST_SLOT_AMMO; + } else if (slotPosition & SLOTP_TWO_HAND || slotPosition & SLOTP_LEFT) { + slot = CONST_SLOT_LEFT; + } + } + + return slot; +} + //Implementation of player invoked events +void Game::playerEquipItem(uint32_t playerId, uint16_t spriteId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Item* item = player->getInventoryItem(CONST_SLOT_BACKPACK); + if (!item) { + return; + } + + Container* backpack = item->getContainer(); + if (!backpack) { + return; + } + + const ItemType& it = Item::items.getItemIdByClientId(spriteId); + slots_t slot = getSlotType(it); + + Item* slotItem = player->getInventoryItem(slot); + Item* equipItem = searchForItem(backpack, it.id); + if (slotItem && slotItem->getID() == it.id && (!it.stackable || slotItem->getItemCount() == 100 || !equipItem)) { + internalMoveItem(slotItem->getParent(), player, CONST_SLOT_WHEREEVER, slotItem, slotItem->getItemCount(), nullptr); + } else if (equipItem) { + internalMoveItem(equipItem->getParent(), player, slot, equipItem, equipItem->getItemCount(), nullptr); + } +} + void Game::playerMove(uint32_t playerId, Direction direction) { Player* player = getPlayerByID(playerId); @@ -1794,11 +1902,15 @@ void Game::playerOpenChannel(uint32_t playerId, uint16_t channelId) return; } - if (channel->getId() == CHANNEL_RULE_REP) { - player->sendRuleViolationsChannel(channel->getId()); + const InvitedMap* invitedUsers = channel->getInvitedUsers(); + const UsersMap* users; + if (!channel->isPublicChannel()) { + users = &channel->getUsers(); } else { - player->sendChannel(channel->getId(), channel->getName()); + users = nullptr; } + + player->sendChannel(channel->getId(), channel->getName(), users, invitedUsers); } void Game::playerCloseChannel(uint32_t playerId, uint16_t channelId) @@ -1823,9 +1935,30 @@ void Game::playerOpenPrivateChannel(uint32_t playerId, std::string& receiver) return; } + if (player->getName() == receiver) { + player->sendCancelMessage("You cannot set up a private message channel with yourself."); + return; + } + player->sendOpenPrivateChannel(receiver); } +void Game::playerCloseNpcChannel(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + SpectatorVec spectators; + map.getSpectators(spectators, player->getPosition()); + for (Creature* spectator : spectators) { + if (Npc* npc = spectator->getNpc()) { + npc->onPlayerCloseChannel(player); + } + } +} + void Game::playerReceivePing(uint32_t playerId) { Player* player = getPlayerByID(playerId); @@ -1888,7 +2021,7 @@ void Game::playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t f } Item* item = thing->getItem(); - if (!item || (item->isDisguised() && item->getDisguiseId() != fromSpriteId) || (!item->isDisguised() && item->getID() != fromSpriteId)) { + if (!item || !item->isUseable() || item->getClientID() != fromSpriteId) { player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); return; } @@ -1972,7 +2105,7 @@ void Game::playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPo } Item* item = thing->getItem(); - if (!item || (item->isDisguised() && item->getDisguiseId() != spriteId) || (!item->isDisguised() && item->getID() != spriteId)) { + if (!item || item->isUseable() || item->getClientID() != spriteId) { player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); return; } @@ -2043,7 +2176,7 @@ void Game::playerUseWithCreature(uint32_t playerId, const Position& fromPos, uin } Item* item = thing->getItem(); - if (!item || (item->isDisguised() && item->getDisguiseId() != spriteId) || (!item->isDisguised() && item->getID() != spriteId)) { + if (!item || !item->isUseable() || item->getClientID() != spriteId) { player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); return; } @@ -2132,7 +2265,20 @@ void Game::playerMoveUpContainer(uint32_t playerId, uint8_t cid) Container* parentContainer = dynamic_cast(container->getRealParent()); if (!parentContainer) { - return; + Tile* tile = container->getTile(); + if (!tile) { + return; + } + + auto it = browseFields.find(tile); + if (it == browseFields.end()) { + parentContainer = new Container(tile); + parentContainer->incrementReferenceCounter(); + browseFields[tile] = parentContainer; + g_scheduler.addEvent(createSchedulerTask(30000, std::bind(&Game::decreaseBrowseFieldRef, this, tile->getPosition()))); + } else { + parentContainer = it->second; + } } player->addContainer(cid, parentContainer); @@ -2167,7 +2313,7 @@ void Game::playerRotateItem(uint32_t playerId, const Position& pos, uint8_t stac } Item* item = thing->getItem(); - if (!item || (item->isDisguised() && item->getDisguiseId() != spriteId) || !item->isRotatable() || (!item->isDisguised() && item->getID() != spriteId)) { + if (!item || item->getClientID() != spriteId || !item->isRotatable() || item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } @@ -2226,6 +2372,13 @@ void Game::playerWriteItem(uint32_t playerId, uint32_t windowTextId, const std:: return; } + for (auto creatureEvent : player->getCreatureEvents(CREATURE_EVENT_TEXTEDIT)) { + if (!creatureEvent->executeTextEdit(player, writeItem, text)) { + player->setWriteItem(nullptr); + return; + } + } + if (!text.empty()) { if (writeItem->getText() != text) { writeItem->setText(text); @@ -2246,6 +2399,66 @@ void Game::playerWriteItem(uint32_t playerId, uint32_t windowTextId, const std:: player->setWriteItem(nullptr); } +void Game::playerBrowseField(uint32_t playerId, const Position& pos) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + const Position& playerPos = player->getPosition(); + if (playerPos.z != pos.z) { + player->sendCancelMessage(playerPos.z > pos.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS); + return; + } + + if (!Position::areInRange<1, 1>(playerPos, pos)) { + std::forward_list listDir; + if (player->getPathTo(pos, listDir, 0, 1, true, true)) { + g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, + this, player->getID(), listDir))); + SchedulerTask* task = createSchedulerTask(400, std::bind( + &Game::playerBrowseField, this, playerId, pos + )); + player->setNextWalkActionTask(task); + } else { + player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); + } + return; + } + + Tile* tile = map.getTile(pos); + if (!tile) { + return; + } + + if (!g_events->eventPlayerOnBrowseField(player, pos)) { + return; + } + + Container* container; + + auto it = browseFields.find(tile); + if (it == browseFields.end()) { + container = new Container(tile); + container->incrementReferenceCounter(); + browseFields[tile] = container; + g_scheduler.addEvent(createSchedulerTask(30000, std::bind(&Game::decreaseBrowseFieldRef, this, tile->getPosition()))); + } else { + container = it->second; + } + + uint8_t dummyContainerId = 0xF - ((pos.x % 3) * 3 + (pos.y % 3)); + Container* openContainer = player->getContainerByID(dummyContainerId); + if (openContainer) { + player->onCloseContainer(openContainer); + player->closeContainer(dummyContainerId); + } else { + player->addContainer(dummyContainerId, container); + player->sendContainer(dummyContainerId, container, false, 0); + } +} + void Game::playerSeekInContainer(uint32_t playerId, uint8_t containerId, uint16_t index) { Player* player = getPlayerByID(playerId); @@ -2254,7 +2467,7 @@ void Game::playerSeekInContainer(uint32_t playerId, uint8_t containerId, uint16_ } Container* container = player->getContainerByID(containerId); - if (!container) { + if (!container || !container->hasPagination()) { return; } @@ -2317,7 +2530,7 @@ void Game::playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t st } Item* tradeItem = tradeThing->getItem(); - if (!tradeItem->isPickupable() || (tradeItem->isDisguised() && tradeItem->getDisguiseId() != spriteId) || (!tradeItem->isDisguised() && tradeItem->getID() != spriteId)) { + if (tradeItem->getClientID() != spriteId || !tradeItem->isPickupable() || tradeItem->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } @@ -2450,10 +2663,10 @@ void Game::playerAcceptTrade(uint32_t playerId) player->setTradeState(TRADE_ACCEPT); if (tradePartner->getTradeState() == TRADE_ACCEPT) { - Item* tradeItem1 = player->tradeItem; - Item* tradeItem2 = tradePartner->tradeItem; + Item* playerTradeItem = player->tradeItem; + Item* partnerTradeItem = tradePartner->tradeItem; - if (!g_events->eventPlayerOnTradeAccept(player, tradePartner, tradeItem1, tradeItem2)) { + if (!g_events->eventPlayerOnTradeAccept(player, tradePartner, playerTradeItem, partnerTradeItem)) { internalCloseTrade(player); return; } @@ -2461,13 +2674,13 @@ void Game::playerAcceptTrade(uint32_t playerId) player->setTradeState(TRADE_TRANSFER); tradePartner->setTradeState(TRADE_TRANSFER); - auto it = tradeItems.find(tradeItem1); + auto it = tradeItems.find(playerTradeItem); if (it != tradeItems.end()) { ReleaseItem(it->first); tradeItems.erase(it); } - it = tradeItems.find(tradeItem2); + it = tradeItems.find(partnerTradeItem); if (it != tradeItems.end()) { ReleaseItem(it->first); tradeItems.erase(it); @@ -2475,25 +2688,17 @@ void Game::playerAcceptTrade(uint32_t playerId) bool isSuccess = false; - ReturnValue ret1 = internalAddItem(tradePartner, tradeItem1, INDEX_WHEREEVER, 0, true); - ReturnValue ret2 = internalAddItem(player, tradeItem2, INDEX_WHEREEVER, 0, true); - if (ret1 == RETURNVALUE_NOERROR && ret2 == RETURNVALUE_NOERROR) { - ret1 = internalRemoveItem(tradeItem1, tradeItem1->getItemCount(), true); - ret2 = internalRemoveItem(tradeItem2, tradeItem2->getItemCount(), true); - if (ret1 == RETURNVALUE_NOERROR && ret2 == RETURNVALUE_NOERROR) { - Cylinder* cylinder1 = tradeItem1->getParent(); - Cylinder* cylinder2 = tradeItem2->getParent(); - - uint32_t count1 = tradeItem1->getItemCount(); - uint32_t count2 = tradeItem2->getItemCount(); - - ret1 = internalMoveItem(cylinder1, tradePartner, INDEX_WHEREEVER, tradeItem1, count1, nullptr, FLAG_IGNOREAUTOSTACK, nullptr, tradeItem2); - if (ret1 == RETURNVALUE_NOERROR) { - internalMoveItem(cylinder2, player, INDEX_WHEREEVER, tradeItem2, count2, nullptr, FLAG_IGNOREAUTOSTACK); - - tradeItem1->onTradeEvent(ON_TRADE_TRANSFER, tradePartner); - tradeItem2->onTradeEvent(ON_TRADE_TRANSFER, player); - + ReturnValue tradePartnerRet = internalAddItem(tradePartner, playerTradeItem, INDEX_WHEREEVER, 0, true); + ReturnValue playerRet = internalAddItem(player, partnerTradeItem, INDEX_WHEREEVER, 0, true); + if (tradePartnerRet == RETURNVALUE_NOERROR && playerRet == RETURNVALUE_NOERROR) { + playerRet = internalRemoveItem(playerTradeItem, playerTradeItem->getItemCount(), true); + tradePartnerRet = internalRemoveItem(partnerTradeItem, partnerTradeItem->getItemCount(), true); + if (tradePartnerRet == RETURNVALUE_NOERROR && playerRet == RETURNVALUE_NOERROR) { + tradePartnerRet = internalMoveItem(playerTradeItem->getParent(), tradePartner, INDEX_WHEREEVER, playerTradeItem, playerTradeItem->getItemCount(), nullptr, FLAG_IGNOREAUTOSTACK, nullptr, partnerTradeItem); + if (tradePartnerRet == RETURNVALUE_NOERROR) { + internalMoveItem(partnerTradeItem->getParent(), player, INDEX_WHEREEVER, partnerTradeItem, partnerTradeItem->getItemCount(), nullptr, FLAG_IGNOREAUTOSTACK); + playerTradeItem->onTradeEvent(ON_TRADE_TRANSFER, tradePartner); + partnerTradeItem->onTradeEvent(ON_TRADE_TRANSFER, player); isSuccess = true; } } @@ -2503,13 +2708,13 @@ void Game::playerAcceptTrade(uint32_t playerId) std::string errorDescription; if (tradePartner->tradeItem) { - errorDescription = getTradeErrorDescription(ret1, tradeItem1); + errorDescription = getTradeErrorDescription(tradePartnerRet, playerTradeItem); tradePartner->sendTextMessage(MESSAGE_EVENT_ADVANCE, errorDescription); tradePartner->tradeItem->onTradeEvent(ON_TRADE_CANCEL, tradePartner); } if (player->tradeItem) { - errorDescription = getTradeErrorDescription(ret2, tradeItem2); + errorDescription = getTradeErrorDescription(playerRet, partnerTradeItem); player->sendTextMessage(MESSAGE_EVENT_ADVANCE, errorDescription); player->tradeItem->onTradeEvent(ON_TRADE_CANCEL, player); } @@ -2540,7 +2745,7 @@ std::string Game::getTradeErrorDescription(ReturnValue ret, Item* item) ss << " this object."; } - ss << std::endl << ' ' << item->getWeightDescription(); + ss << "\n " << item->getWeightDescription(); return ss.str(); } else if (ret == RETURNVALUE_NOTENOUGHROOM || ret == RETURNVALUE_CONTAINERNOTENOUGHROOM) { std::ostringstream ss; @@ -2586,7 +2791,6 @@ void Game::playerLookInTrade(uint32_t playerId, bool lookAtCounterOffer, uint8_t int32_t lookDistance = std::max(Position::getDistanceX(playerPosition, tradeItemPosition), Position::getDistanceY(playerPosition, tradeItemPosition)); - if (index == 0) { g_events->eventPlayerOnLookInTrade(player, tradePartner, tradeItem, lookDistance); return; @@ -2669,6 +2873,126 @@ void Game::internalCloseTrade(Player* player) } } +void Game::playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount, + bool ignoreCap/* = false*/, bool inBackpacks/* = false*/) +{ + if (amount == 0 || amount > 100) { + return; + } + + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + int32_t onBuy, onSell; + + Npc* merchant = player->getShopOwner(onBuy, onSell); + if (!merchant) { + return; + } + + const ItemType& it = Item::items.getItemIdByClientId(spriteId); + if (it.id == 0) { + return; + } + + uint8_t subType; + if (it.isSplash() || it.isFluidContainer()) { + subType = clientFluidToServer(count); + } else { + subType = count; + } + + if (!player->hasShopItemForSale(it.id, subType)) { + return; + } + + merchant->onPlayerTrade(player, onBuy, it.id, subType, amount, ignoreCap, inBackpacks); +} + +void Game::playerSellItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount, bool ignoreEquipped) +{ + if (amount == 0 || amount > 100) { + return; + } + + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + int32_t onBuy, onSell; + + Npc* merchant = player->getShopOwner(onBuy, onSell); + if (!merchant) { + return; + } + + const ItemType& it = Item::items.getItemIdByClientId(spriteId); + if (it.id == 0) { + return; + } + + uint8_t subType; + if (it.isSplash() || it.isFluidContainer()) { + subType = clientFluidToServer(count); + } else { + subType = count; + } + + merchant->onPlayerTrade(player, onSell, it.id, subType, amount, ignoreEquipped); +} + +void Game::playerCloseShop(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->closeShopWindow(); +} + +void Game::playerLookInShop(uint32_t playerId, uint16_t spriteId, uint8_t count) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + int32_t onBuy, onSell; + + Npc* merchant = player->getShopOwner(onBuy, onSell); + if (!merchant) { + return; + } + + const ItemType& it = Item::items.getItemIdByClientId(spriteId); + if (it.id == 0) { + return; + } + + int32_t subType; + if (it.isFluidContainer() || it.isSplash()) { + subType = clientFluidToServer(count); + } else { + subType = count; + } + + if (!player->hasShopItemForSale(it.id, subType)) { + return; + } + + if (!g_events->eventPlayerOnLookInShop(player, &it, subType)) { + return; + } + + std::ostringstream ss; + ss << "You see " << Item::getDescription(it, 1, nullptr, subType); + player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); +} + void Game::playerLookAt(uint32_t playerId, const Position& pos, uint8_t stackPos) { Player* player = getPlayerByID(playerId); @@ -2794,7 +3118,7 @@ void Game::playerFollowCreature(uint32_t playerId, uint32_t creatureId) player->setFollowCreature(getCreatureByID(creatureId)); } -void Game::playerSetFightModes(uint32_t playerId, fightMode_t fightMode, chaseMode_t chaseMode, bool secureMode) +void Game::playerSetFightModes(uint32_t playerId, fightMode_t fightMode, bool chaseMode, bool secureMode) { Player* player = getPlayerByID(playerId); if (!player) { @@ -2857,6 +3181,16 @@ void Game::playerRequestRemoveVip(uint32_t playerId, uint32_t guid) player->removeVIP(guid); } +void Game::playerRequestEditVip(uint32_t playerId, uint32_t guid, const std::string& description, uint32_t icon, bool notify) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->editVIP(guid, description, icon, notify); +} + void Game::playerTurn(uint32_t playerId, Direction dir) { Player* player = getPlayerByID(playerId); @@ -2886,6 +3220,16 @@ void Game::playerRequestOutfit(uint32_t playerId) player->sendOutfitWindow(); } +void Game::playerToggleMount(uint32_t playerId, bool mount) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->toggleMount(mount); +} + void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit) { if (!g_config.getBoolean(ConfigManager::ALLOW_CHANGEOUTFIT)) { @@ -2897,6 +3241,36 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit) return; } + const Outfit* playerOutfit = Outfits::getInstance().getOutfitByLookType(player->getSex(), outfit.lookType); + if (!playerOutfit) { + outfit.lookMount = 0; + } + + if (outfit.lookMount != 0) { + Mount* mount = mounts.getMountByClientID(outfit.lookMount); + if (!mount) { + return; + } + + if (!player->hasMount(mount)) { + return; + } + + if (player->isMounted()) { + Mount* prevMount = mounts.getMountByID(player->getCurrentMount()); + if (prevMount) { + changeSpeed(player, mount->speed - prevMount->speed); + } + + player->setCurrentMount(mount->id); + } else { + player->setCurrentMount(mount->id); + outfit.lookMount = 0; + } + } else if (player->isMounted()) { + player->dismount(); + } + if (player->canWear(outfit.lookType, outfit.lookAddons)) { player->defaultOutfit = outfit; @@ -2908,6 +3282,31 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit) } } +void Game::playerShowQuestLog(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->sendQuestLog(); +} + +void Game::playerShowQuestLine(uint32_t playerId, uint16_t questId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Quest* quest = quests.getQuestByID(questId); + if (!quest) { + return; + } + + player->sendQuestLine(quest); +} + void Game::playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, const std::string& receiver, const std::string& text) { @@ -2934,7 +3333,9 @@ void Game::playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, return; } - player->removeMessageBuffer(); + if (type != TALKTYPE_PRIVATE_PN) { + player->removeMessageBuffer(); + } switch (type) { case TALKTYPE_SAY: @@ -2949,35 +3350,25 @@ void Game::playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, playerYell(player, text); break; - case TALKTYPE_PRIVATE: - case TALKTYPE_PRIVATE_RED: - case TALKTYPE_RVR_ANSWER: + case TALKTYPE_PRIVATE_TO: + case TALKTYPE_PRIVATE_RED_TO: playerSpeakTo(player, type, receiver, text); break; case TALKTYPE_CHANNEL_O: case TALKTYPE_CHANNEL_Y: case TALKTYPE_CHANNEL_R1: - case TALKTYPE_CHANNEL_R2: - if (channelId == CHANNEL_RULE_REP) { - playerSay(playerId, 0, TALKTYPE_SAY, receiver, text); - } else { - g_chat->talkToChannel(*player, type, text, channelId); - } + g_chat->talkToChannel(*player, type, text, channelId); + break; + + case TALKTYPE_PRIVATE_PN: + playerSpeakToNpc(player, text); break; case TALKTYPE_BROADCAST: playerBroadcastMessage(player, text); break; - case TALKTYPE_RVR_CHANNEL: - playerReportRuleViolationReport(player, text); - break; - - case TALKTYPE_RVR_CONTINUE: - playerContinueRuleViolationReport(player, text); - break; - default: break; } @@ -2994,7 +3385,12 @@ bool Game::playerSaySpell(Player* player, SpeakClasses type, const std::string& result = g_spells->playerSaySpell(player, words); if (result == TALKACTION_BREAK) { - return internalCreatureSay(player, TALKTYPE_SAY, text, false); + if (!g_config.getBoolean(ConfigManager::EMOTE_SPELLS)) { + return internalCreatureSay(player, TALKTYPE_SAY, words, false); + } else { + return internalCreatureSay(player, TALKTYPE_MONSTER_SAY, words, false); + } + } else if (result == TALKACTION_FAILED) { return true; } @@ -3004,13 +3400,13 @@ bool Game::playerSaySpell(Player* player, SpeakClasses type, const std::string& void Game::playerWhisper(Player* player, const std::string& text) { - SpectatorVec list; - map.getSpectators(list, player->getPosition(), false, false, + SpectatorVec spectators; + map.getSpectators(spectators, player->getPosition(), false, false, Map::maxClientViewportX, Map::maxClientViewportX, Map::maxClientViewportY, Map::maxClientViewportY); //send to client - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { if (Player* spectatorPlayer = spectator->getPlayer()) { if (!Position::areInRange<1, 1>(player->getPosition(), spectatorPlayer->getPosition())) { spectatorPlayer->sendCreatureSay(player, TALKTYPE_WHISPER, "pspsps"); @@ -3021,7 +3417,7 @@ void Game::playerWhisper(Player* player, const std::string& text) } //event method - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { spectator->onCreatureSay(player, TALKTYPE_WHISPER, text); } } @@ -3056,8 +3452,10 @@ bool Game::playerSpeakTo(Player* player, SpeakClasses type, const std::string& r return false; } - if (type == TALKTYPE_PRIVATE_RED && (!player->hasFlag(PlayerFlag_CanTalkRedPrivate) || player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER)) { - type = TALKTYPE_PRIVATE; + if (type == TALKTYPE_PRIVATE_RED_TO && (player->hasFlag(PlayerFlag_CanTalkRedPrivate) || player->getAccountType() >= ACCOUNT_TYPE_GAMEMASTER)) { + type = TALKTYPE_PRIVATE_RED_FROM; + } else { + type = TALKTYPE_PRIVATE_FROM; } toPlayer->sendPrivateMessage(player, type, text); @@ -3073,6 +3471,17 @@ bool Game::playerSpeakTo(Player* player, SpeakClasses type, const std::string& r return true; } +void Game::playerSpeakToNpc(Player* player, const std::string& text) +{ + SpectatorVec spectators; + map.getSpectators(spectators, player->getPosition()); + for (Creature* spectator : spectators) { + if (spectator->getNpc()) { + spectator->onCreatureSay(player, TALKTYPE_PRIVATE_PN, text); + } + } +} + //-- bool Game::canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight /*= true*/, int32_t rangex /*= Map::maxClientViewportX*/, int32_t rangey /*= Map::maxClientViewportY*/) const @@ -3094,16 +3503,16 @@ bool Game::internalCreatureTurn(Creature* creature, Direction dir) creature->setDirection(dir); //send to client - SpectatorVec list; - map.getSpectators(list, creature->getPosition(), true, true); - for (Creature* spectator : list) { + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), true, true); + for (Creature* spectator : spectators) { spectator->getPlayer()->sendCreatureTurn(creature); } return true; } bool Game::internalCreatureSay(Creature* creature, SpeakClasses type, const std::string& text, - bool ghostMode, SpectatorVec* listPtr/* = nullptr*/, const Position* pos/* = nullptr*/) + bool ghostMode, SpectatorVec* spectatorsPtr/* = nullptr*/, const Position* pos/* = nullptr*/) { if (text.empty()) { return false; @@ -3113,26 +3522,26 @@ bool Game::internalCreatureSay(Creature* creature, SpeakClasses type, const std: pos = &creature->getPosition(); } - SpectatorVec list; + SpectatorVec spectators; - if (!listPtr || listPtr->empty()) { + if (!spectatorsPtr || spectatorsPtr->empty()) { // This somewhat complex construct ensures that the cached SpectatorVec // is used if available and if it can be used, else a local vector is // used (hopefully the compiler will optimize away the construction of // the temporary when it's not used). if (type != TALKTYPE_YELL && type != TALKTYPE_MONSTER_YELL) { - map.getSpectators(list, *pos, false, false, + map.getSpectators(spectators, *pos, false, false, Map::maxClientViewportX, Map::maxClientViewportX, Map::maxClientViewportY, Map::maxClientViewportY); } else { - map.getSpectators(list, *pos, true, false, 30, 30, 30, 30); + map.getSpectators(spectators, *pos, true, false, 18, 18, 14, 14); } } else { - list = (*listPtr); + spectators = (*spectatorsPtr); } //send to client - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { if (!ghostMode || tmpPlayer->canSeeCreature(creature)) { tmpPlayer->sendCreatureSay(creature, type, text, pos); @@ -3141,7 +3550,7 @@ bool Game::internalCreatureSay(Creature* creature, SpeakClasses type, const std: } //event method - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { spectator->onCreatureSay(creature, type, text); } return true; @@ -3222,12 +3631,15 @@ void Game::checkCreatures(size_t index) void Game::changeSpeed(Creature* creature, int32_t varSpeedDelta) { - creature->setSpeed(varSpeedDelta); + int32_t varSpeed = creature->getSpeed() - creature->getBaseSpeed(); + varSpeed += varSpeedDelta; + + creature->setSpeed(varSpeed); //send to clients - SpectatorVec list; - map.getSpectators(list, creature->getPosition(), false, true); - for (Creature* spectator : list) { + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), false, true); + for (Creature* spectator : spectators) { spectator->getPlayer()->sendChangeSpeed(creature, creature->getStepSpeed()); } } @@ -3245,9 +3657,9 @@ void Game::internalCreatureChangeOutfit(Creature* creature, const Outfit_t& outf } //send to clients - SpectatorVec list; - map.getSpectators(list, creature->getPosition(), true, true); - for (Creature* spectator : list) { + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), true, true); + for (Creature* spectator : spectators) { spectator->getPlayer()->sendCreatureChangeOutfit(creature, outfit); } } @@ -3255,9 +3667,9 @@ void Game::internalCreatureChangeOutfit(Creature* creature, const Outfit_t& outf void Game::internalCreatureChangeVisible(Creature* creature, bool visible) { //send to clients - SpectatorVec list; - map.getSpectators(list, creature->getPosition(), true, true); - for (Creature* spectator : list) { + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), true, true); + for (Creature* spectator : spectators) { spectator->getPlayer()->sendCreatureChangeVisible(creature, visible); } } @@ -3265,16 +3677,16 @@ void Game::internalCreatureChangeVisible(Creature* creature, bool visible) void Game::changeLight(const Creature* creature) { //send to clients - SpectatorVec list; - map.getSpectators(list, creature->getPosition(), true, true); - for (Creature* spectator : list) { + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), true, true); + for (Creature* spectator : spectators) { spectator->getPlayer()->sendCreatureLight(creature); } } bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* target, bool checkDefense, bool checkArmor, bool field) { - if (damage.type == COMBAT_NONE) { + if (damage.primary.type == COMBAT_NONE && damage.secondary.type == COMBAT_NONE) { return true; } @@ -3282,7 +3694,7 @@ bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* ta return true; } - if (damage.value > 0) { + if (damage.primary.value > 0) { return false; } @@ -3299,7 +3711,9 @@ bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* ta } case COMBAT_ENERGYDAMAGE: case COMBAT_FIREDAMAGE: - case COMBAT_PHYSICALDAMAGE: { + case COMBAT_PHYSICALDAMAGE: + case COMBAT_ICEDAMAGE: + case COMBAT_DEATHDAMAGE: { hitEffect = CONST_ME_BLOCKHIT; break; } @@ -3307,6 +3721,10 @@ bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* ta hitEffect = CONST_ME_GREEN_RINGS; break; } + case COMBAT_HOLYDAMAGE: { + hitEffect = CONST_ME_HOLYDAMAGE; + break; + } default: { hitEffect = CONST_ME_POFF; break; @@ -3316,18 +3734,27 @@ bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* ta } }; - BlockType_t primaryBlockType; - if (damage.type != COMBAT_NONE) { - damage.value = -damage.value; - primaryBlockType = target->blockHit(attacker, damage.type, damage.value, checkDefense, checkArmor, field); + BlockType_t primaryBlockType, secondaryBlockType; + if (damage.primary.type != COMBAT_NONE) { + damage.primary.value = -damage.primary.value; + primaryBlockType = target->blockHit(attacker, damage.primary.type, damage.primary.value, checkDefense, checkArmor, field); - damage.value = -damage.value; - sendBlockEffect(primaryBlockType, damage.type, target->getPosition()); + damage.primary.value = -damage.primary.value; + sendBlockEffect(primaryBlockType, damage.primary.type, target->getPosition()); } else { primaryBlockType = BLOCK_NONE; } - return (primaryBlockType != BLOCK_NONE); + if (damage.secondary.type != COMBAT_NONE) { + damage.secondary.value = -damage.secondary.value; + secondaryBlockType = target->blockHit(attacker, damage.secondary.type, damage.secondary.value, false, false, field); + + damage.secondary.value = -damage.secondary.value; + sendBlockEffect(secondaryBlockType, damage.secondary.type, target->getPosition()); + } else { + secondaryBlockType = BLOCK_NONE; + } + return (primaryBlockType != BLOCK_NONE) && (secondaryBlockType != BLOCK_NONE); } void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColor_t& color, uint8_t& effect) @@ -3344,7 +3771,11 @@ void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColo case RACE_BLOOD: color = TEXTCOLOR_RED; effect = CONST_ME_DRAWBLOOD; - splash = Item::CreateItem(ITEM_SMALLSPLASH, FLUID_BLOOD); + if (const Tile* tile = target->getTile()) { + if (!tile->hasFlag(TILESTATE_PROTECTIONZONE)) { + splash = Item::CreateItem(ITEM_SMALLSPLASH, FLUID_BLOOD); + } + } break; case RACE_UNDEAD: color = TEXTCOLOR_LIGHTGREY; @@ -3354,6 +3785,10 @@ void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColo color = TEXTCOLOR_ORANGE; effect = CONST_ME_DRAWBLOOD; break; + case RACE_ENERGY: + color = TEXTCOLOR_ELECTRICPURPLE; + effect = CONST_ME_ENERGYHIT; + break; default: color = TEXTCOLOR_NONE; effect = CONST_ME_NONE; @@ -3369,7 +3804,7 @@ void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColo } case COMBAT_ENERGYDAMAGE: { - color = TEXTCOLOR_LIGHTBLUE; + color = TEXTCOLOR_ELECTRICPURPLE; effect = CONST_ME_ENERGYHIT; break; } @@ -3380,23 +3815,36 @@ void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColo break; } + case COMBAT_DROWNDAMAGE: { + color = TEXTCOLOR_LIGHTBLUE; + effect = CONST_ME_LOSEENERGY; + break; + } case COMBAT_FIREDAMAGE: { color = TEXTCOLOR_ORANGE; effect = CONST_ME_HITBYFIRE; break; } - + case COMBAT_ICEDAMAGE: { + color = TEXTCOLOR_SKYBLUE; + effect = CONST_ME_ICEATTACK; + break; + } + case COMBAT_HOLYDAMAGE: { + color = TEXTCOLOR_YELLOW; + effect = CONST_ME_HOLYDAMAGE; + break; + } + case COMBAT_DEATHDAMAGE: { + color = TEXTCOLOR_DARKRED; + effect = CONST_ME_SMALLCLOUDS; + break; + } case COMBAT_LIFEDRAIN: { color = TEXTCOLOR_RED; effect = CONST_ME_MAGIC_RED; break; } - - case COMBAT_DROWNDAMAGE: { - color = TEXTCOLOR_LIGHTBLUE; - effect = CONST_ME_LOSEENERGY; - break; - } default: { color = TEXTCOLOR_NONE; effect = CONST_ME_NONE; @@ -3408,11 +3856,23 @@ void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColo bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage& damage) { const Position& targetPos = target->getPosition(); - if (damage.value > 0) { + if (damage.primary.value > 0) { if (target->getHealth() <= 0) { return false; } + Player* attackerPlayer; + if (attacker) { + attackerPlayer = attacker->getPlayer(); + } else { + attackerPlayer = nullptr; + } + + Player* targetPlayer = target->getPlayer(); + if (attackerPlayer && targetPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { + return false; + } + if (damage.origin != ORIGIN_NONE) { const auto& events = target->getCreatureEvents(CREATURE_EVENT_HEALTHCHANGE); if (!events.empty()) { @@ -3425,24 +3885,66 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } int32_t realHealthChange = target->getHealth(); - target->gainHealth(attacker, damage.value); + target->gainHealth(attacker, damage.primary.value); realHealthChange = target->getHealth() - realHealthChange; if (realHealthChange > 0 && !target->isInGhostMode()) { - addMagicEffect(targetPos, CONST_ME_MAGIC_BLUE); - } - } - else { - if (Monster* monster = target->getMonster()) { - // makes monsters aggressive when damaged - // basically stands for UNDERATTACK stance under CipSoft servers - // the attacker must be valid everytime (avoid field ticks damage to trigger condition) - if (!monster->hasCondition(CONDITION_AGGRESSIVE) && attacker) { - Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_AGGRESSIVE, 3000); - monster->addCondition(condition, true); + std::stringstream ss; + + ss << realHealthChange << (realHealthChange != 1 ? " hitpoints." : " hitpoint."); + std::string damageString = ss.str(); + + std::string spectatorMessage; + + TextMessage message; + message.position = targetPos; + message.primary.value = realHealthChange; + message.primary.color = TEXTCOLOR_PASTELRED; + + SpectatorVec spectators; + map.getSpectators(spectators, targetPos, false, true); + for (Creature* spectator : spectators) { + Player* tmpPlayer = spectator->getPlayer(); + if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) { + ss.str({}); + ss << "You heal " << target->getNameDescription() << " for " << damageString; + message.type = MESSAGE_HEALED; + message.text = ss.str(); + } else if (tmpPlayer == targetPlayer) { + ss.str({}); + if (!attacker) { + ss << "You were healed"; + } else if (targetPlayer == attackerPlayer) { + ss << "You healed yourself"; + } else { + ss << "You were healed by " << attacker->getNameDescription(); + } + ss << " for " << damageString; + message.type = MESSAGE_HEALED; + message.text = ss.str(); + } else { + if (spectatorMessage.empty()) { + ss.str({}); + if (!attacker) { + ss << ucfirst(target->getNameDescription()) << " was healed"; + } else { + ss << ucfirst(attacker->getNameDescription()) << " healed "; + if (attacker == target) { + ss << (targetPlayer ? (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "herself" : "himself") : "itself"); + } else { + ss << target->getNameDescription(); + } + } + ss << " for " << damageString; + spectatorMessage = ss.str(); + } + message.type = MESSAGE_HEALED_OTHERS; + message.text = spectatorMessage; + } + tmpPlayer->sendTextMessage(message); } } - + } else { if (!target->isAttackable()) { if (!target->isInGhostMode()) { addMagicEffect(targetPos, CONST_ME_POFF); @@ -3453,19 +3955,50 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage Player* attackerPlayer; if (attacker) { attackerPlayer = attacker->getPlayer(); - } - else { + } else { attackerPlayer = nullptr; } - damage.value = std::abs(damage.value); - int32_t healthChange = damage.value; + Player* targetPlayer = target->getPlayer(); + if (attackerPlayer && targetPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { + return false; + } + + damage.primary.value = std::abs(damage.primary.value); + damage.secondary.value = std::abs(damage.secondary.value); + + int32_t healthChange = damage.primary.value + damage.secondary.value; if (healthChange == 0) { return true; } - Player* targetPlayer = target->getPlayer(); - SpectatorVec list; - if (target->hasCondition(CONDITION_MANASHIELD) && damage.type != COMBAT_UNDEFINEDDAMAGE) { + + if (attackerPlayer) { + uint16_t chance = attackerPlayer->getSpecialSkill(SPECIALSKILL_LIFELEECHCHANCE); + if (chance != 0 && uniform_random(1, 100) <= chance) { + CombatDamage lifeLeech; + lifeLeech.primary.value = std::round(healthChange * (attackerPlayer->getSpecialSkill(SPECIALSKILL_LIFELEECHAMOUNT) / 100.)); + combatChangeHealth(nullptr, attackerPlayer, lifeLeech); + } + + chance = attackerPlayer->getSpecialSkill(SPECIALSKILL_MANALEECHCHANCE); + if (chance != 0 && uniform_random(1, 100) <= chance) { + CombatDamage manaLeech; + manaLeech.primary.value = std::round(healthChange * (attackerPlayer->getSpecialSkill(SPECIALSKILL_MANALEECHAMOUNT) / 100.)); + combatChangeMana(nullptr, attackerPlayer, manaLeech); + } + + chance = attackerPlayer->getSpecialSkill(SPECIALSKILL_CRITICALHITCHANCE); + if (chance != 0 && uniform_random(1, 100) <= chance) { + healthChange += std::round(healthChange * (attackerPlayer->getSpecialSkill(SPECIALSKILL_CRITICALHITAMOUNT) / 100.)); + addMagicEffect(target->getPosition(), CONST_ME_CRITICAL_DAMAGE); + } + } + + TextMessage message; + message.position = targetPos; + + SpectatorVec spectators; + if (targetPlayer && target->hasCondition(CONDITION_MANASHIELD) && damage.primary.type != COMBAT_UNDEFINEDDAMAGE) { int32_t manaDamage = std::min(targetPlayer->getMana(), healthChange); if (manaDamage != 0) { if (damage.origin != ORIGIN_NONE) { @@ -3474,93 +4007,186 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage for (CreatureEvent* creatureEvent : events) { creatureEvent->executeManaChange(target, attacker, damage); } - healthChange = damage.value; + healthChange = damage.primary.value + damage.secondary.value; if (healthChange == 0) { return true; } manaDamage = std::min(targetPlayer->getMana(), healthChange); } } + targetPlayer->drainMana(attacker, manaDamage); - map.getSpectators(list, targetPos, true, true); - addMagicEffect(list, targetPos, CONST_ME_LOSEENERGY); + map.getSpectators(spectators, targetPos, true, true); + addMagicEffect(spectators, targetPos, CONST_ME_LOSEENERGY); + + std::stringstream ss; std::string damageString = std::to_string(manaDamage); - if (targetPlayer) { - std::stringstream ss; - if (!attacker) { - ss << "You lose " << damageString << " mana."; - } - else if (targetPlayer == attackerPlayer) { - ss << "You lose " << damageString << " mana due to your own attack."; - } - else { - ss << "You lose " << damageString << " mana due to an attack by " << attacker->getNameDescription() << '.'; - } - targetPlayer->sendTextMessage(MESSAGE_EVENT_DEFAULT, ss.str()); - } + std::string spectatorMessage; - for (Creature* spectator : list) { + message.primary.value = manaDamage; + message.primary.color = TEXTCOLOR_BLUE; + + for (Creature* spectator : spectators) { Player* tmpPlayer = spectator->getPlayer(); - tmpPlayer->sendAnimatedText(targetPos, TEXTCOLOR_BLUE, damageString); + if (tmpPlayer->getPosition().z != targetPos.z) { + continue; + } + + if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) { + ss.str({}); + ss << ucfirst(target->getNameDescription()) << " loses " << damageString + " mana due to your attack."; + message.type = MESSAGE_DAMAGE_DEALT; + message.text = ss.str(); + } else if (tmpPlayer == targetPlayer) { + ss.str({}); + ss << "You lose " << damageString << " mana"; + if (!attacker) { + ss << '.'; + } else if (targetPlayer == attackerPlayer) { + ss << " due to your own attack."; + } else { + ss << " due to an attack by " << attacker->getNameDescription() << '.'; + } + message.type = MESSAGE_DAMAGE_RECEIVED; + message.text = ss.str(); + } else { + if (spectatorMessage.empty()) { + ss.str({}); + ss << ucfirst(target->getNameDescription()) << " loses " << damageString + " mana"; + if (attacker) { + ss << " due to "; + if (attacker == target) { + ss << (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her own attack" : "his own attack"); + } else { + ss << "an attack by " << attacker->getNameDescription(); + } + } + ss << '.'; + spectatorMessage = ss.str(); + } + message.type = MESSAGE_DAMAGE_OTHERS; + message.text = spectatorMessage; + } + tmpPlayer->sendTextMessage(message); } - damage.value -= manaDamage; - if (damage.value < 0) { - damage.value = 0; + damage.primary.value -= manaDamage; + if (damage.primary.value < 0) { + damage.secondary.value = std::max(0, damage.secondary.value + damage.primary.value); + damage.primary.value = 0; } } } - int32_t realDamage = damage.value; + int32_t realDamage = damage.primary.value + damage.secondary.value; if (realDamage == 0) { return true; } + if (damage.origin != ORIGIN_NONE) { + const auto& events = target->getCreatureEvents(CREATURE_EVENT_HEALTHCHANGE); + if (!events.empty()) { + for (CreatureEvent* creatureEvent : events) { + creatureEvent->executeHealthChange(target, attacker, damage); + } + damage.origin = ORIGIN_NONE; + return combatChangeHealth(attacker, target, damage); + } + } + int32_t targetHealth = target->getHealth(); - if (damage.value >= targetHealth) { - damage.value = targetHealth; + if (damage.primary.value >= targetHealth) { + damage.primary.value = targetHealth; + damage.secondary.value = 0; + } else if (damage.secondary.value) { + damage.secondary.value = std::min(damage.secondary.value, targetHealth - damage.primary.value); } - realDamage = damage.value; + realDamage = damage.primary.value + damage.secondary.value; if (realDamage == 0) { return true; } - if (list.empty()) { - map.getSpectators(list, targetPos, true, true); + if (spectators.empty()) { + map.getSpectators(spectators, targetPos, true, true); } - TextColor_t color = TEXTCOLOR_NONE; + message.primary.value = damage.primary.value; + message.secondary.value = damage.secondary.value; + uint8_t hitEffect; - if (damage.value) { - combatGetTypeInfo(damage.type, target, color, hitEffect); + if (message.primary.value) { + combatGetTypeInfo(damage.primary.type, target, message.primary.color, hitEffect); if (hitEffect != CONST_ME_NONE) { - addMagicEffect(list, targetPos, hitEffect); + addMagicEffect(spectators, targetPos, hitEffect); } } - if (color != TEXTCOLOR_NONE) { - std::string damageString = std::to_string(realDamage) + (realDamage != 1 ? " hitpoints" : " hitpoint"); - if (targetPlayer) { - std::stringstream ss; - if (!attacker) { - ss << "You lose " << damageString << "."; - } - else if (targetPlayer == attackerPlayer) { - ss << "You lose " << damageString << " due to your own attack."; - } - else { - ss << "You lose " << damageString << " due to an attack by " << attacker->getNameDescription() << '.'; - } - targetPlayer->sendTextMessage(MESSAGE_EVENT_DEFAULT, ss.str()); + if (message.secondary.value) { + combatGetTypeInfo(damage.secondary.type, target, message.secondary.color, hitEffect); + if (hitEffect != CONST_ME_NONE) { + addMagicEffect(spectators, targetPos, hitEffect); } + } - std::string realDamageStr = std::to_string(realDamage); - for (Creature* spectator : list) { + if (message.primary.color != TEXTCOLOR_NONE || message.secondary.color != TEXTCOLOR_NONE) { + std::stringstream ss; + + ss << realDamage << (realDamage != 1 ? " hitpoints" : " hitpoint"); + std::string damageString = ss.str(); + + std::string spectatorMessage; + + for (Creature* spectator : spectators) { Player* tmpPlayer = spectator->getPlayer(); - tmpPlayer->sendAnimatedText(targetPos, color, realDamageStr); + if (tmpPlayer->getPosition().z != targetPos.z) { + continue; + } + + if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) { + ss.str({}); + ss << ucfirst(target->getNameDescription()) << " loses " << damageString << " due to your attack."; + message.type = MESSAGE_DAMAGE_DEALT; + message.text = ss.str(); + } else if (tmpPlayer == targetPlayer) { + ss.str({}); + ss << "You lose " << damageString; + if (!attacker) { + ss << '.'; + } else if (targetPlayer == attackerPlayer) { + ss << " due to your own attack."; + } else { + ss << " due to an attack by " << attacker->getNameDescription() << '.'; + } + message.type = MESSAGE_DAMAGE_RECEIVED; + message.text = ss.str(); + } else { + message.type = MESSAGE_DAMAGE_OTHERS; + + if (spectatorMessage.empty()) { + ss.str({}); + ss << ucfirst(target->getNameDescription()) << " loses " << damageString; + if (attacker) { + ss << " due to "; + if (attacker == target) { + if (targetPlayer) { + ss << (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her own attack" : "his own attack"); + } else { + ss << "its own attack"; + } + } else { + ss << "an attack by " << attacker->getNameDescription(); + } + } + ss << '.'; + spectatorMessage = ss.str(); + } + + message.text = spectatorMessage; + } + tmpPlayer->sendTextMessage(message); } } @@ -3573,7 +4199,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } target->drainHealth(attacker, realDamage); - addCreatureHealth(list, target); + addCreatureHealth(spectators, target); } return true; @@ -3585,8 +4211,16 @@ bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& if (!targetPlayer) { return true; } - int32_t manaChange = damage.value; + + int32_t manaChange = damage.primary.value + damage.secondary.value; if (manaChange > 0) { + if (attacker) { + const Player* attackerPlayer = attacker->getPlayer(); + if (attackerPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(target) == SKULL_NONE) { + return false; + } + } + if (damage.origin != ORIGIN_NONE) { const auto& events = target->getCreatureEvents(CREATURE_EVENT_MANACHANGE); if (!events.empty()) { @@ -3597,9 +4231,19 @@ bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& return combatChangeMana(attacker, target, damage); } } + + int32_t realManaChange = targetPlayer->getMana(); targetPlayer->changeMana(manaChange); - } - else { + realManaChange = targetPlayer->getMana() - realManaChange; + + if (realManaChange > 0 && !targetPlayer->isInGhostMode()) { + TextMessage message(MESSAGE_HEALED, "You gained " + std::to_string(realManaChange) + " mana."); + message.position = target->getPosition(); + message.primary.value = realManaChange; + message.primary.color = TEXTCOLOR_MAYABLUE; + targetPlayer->sendTextMessage(message); + } + } else { const Position& targetPos = target->getPosition(); if (!target->isAttackable()) { if (!target->isInGhostMode()) { @@ -3611,11 +4255,14 @@ bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& Player* attackerPlayer; if (attacker) { attackerPlayer = attacker->getPlayer(); - } - else { + } else { attackerPlayer = nullptr; } + if (attackerPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { + return false; + } + int32_t manaLoss = std::min(targetPlayer->getMana(), -manaChange); BlockType_t blockType = target->blockHit(attacker, COMBAT_MANADRAIN, manaLoss); if (blockType != BLOCK_NONE) { @@ -3640,27 +4287,57 @@ bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& targetPlayer->drainMana(attacker, manaLoss); + std::stringstream ss; + std::string damageString = std::to_string(manaLoss); - SpectatorVec list; - map.getSpectators(list, targetPos, false, true); - if (targetPlayer) { - std::stringstream ss; - if (!attacker) { - ss << "You lose " << damageString << " mana."; - } - else if (targetPlayer == attackerPlayer) { - ss << "You lose " << damageString << " mana due to your own attack."; - } - else { - ss << "You lose " << damageString << " mana due to an attack by " << attacker->getNameDescription() << '.'; - } - targetPlayer->sendTextMessage(MESSAGE_EVENT_DEFAULT, ss.str()); - } + std::string spectatorMessage; - for (Creature* spectator : list) { + TextMessage message; + message.position = targetPos; + message.primary.value = manaLoss; + message.primary.color = TEXTCOLOR_BLUE; + + SpectatorVec spectators; + map.getSpectators(spectators, targetPos, false, true); + for (Creature* spectator : spectators) { Player* tmpPlayer = spectator->getPlayer(); - tmpPlayer->sendAnimatedText(targetPos, TEXTCOLOR_BLUE, damageString); + if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) { + ss.str({}); + ss << ucfirst(target->getNameDescription()) << " loses " << damageString << " mana due to your attack."; + message.type = MESSAGE_DAMAGE_DEALT; + message.text = ss.str(); + } else if (tmpPlayer == targetPlayer) { + ss.str({}); + ss << "You lose " << damageString << " mana"; + if (!attacker) { + ss << '.'; + } else if (targetPlayer == attackerPlayer) { + ss << " due to your own attack."; + } else { + ss << " mana due to an attack by " << attacker->getNameDescription() << '.'; + } + message.type = MESSAGE_DAMAGE_RECEIVED; + message.text = ss.str(); + } else { + if (spectatorMessage.empty()) { + ss.str({}); + ss << ucfirst(target->getNameDescription()) << " loses " << damageString << " mana"; + if (attacker) { + ss << " due to "; + if (attacker == target) { + ss << (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her own attack" : "his own attack"); + } else { + ss << "an attack by " << attacker->getNameDescription(); + } + } + ss << '.'; + spectatorMessage = ss.str(); + } + message.type = MESSAGE_DAMAGE_OTHERS; + message.text = spectatorMessage; + } + tmpPlayer->sendTextMessage(message); } } @@ -3669,14 +4346,14 @@ bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& void Game::addCreatureHealth(const Creature* target) { - SpectatorVec list; - map.getSpectators(list, target->getPosition(), true, true); - addCreatureHealth(list, target); + SpectatorVec spectators; + map.getSpectators(spectators, target->getPosition(), true, true); + addCreatureHealth(spectators, target); } -void Game::addCreatureHealth(const SpectatorVec& list, const Creature* target) +void Game::addCreatureHealth(const SpectatorVec& spectators, const Creature* target) { - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendCreatureHealth(target); } @@ -3685,14 +4362,14 @@ void Game::addCreatureHealth(const SpectatorVec& list, const Creature* target) void Game::addMagicEffect(const Position& pos, uint8_t effect) { - SpectatorVec list; - map.getSpectators(list, pos, true, true); - addMagicEffect(list, pos, effect); + SpectatorVec spectators; + map.getSpectators(spectators, pos, true, true); + addMagicEffect(spectators, pos, effect); } -void Game::addMagicEffect(const SpectatorVec& list, const Position& pos, uint8_t effect) +void Game::addMagicEffect(const SpectatorVec& spectators, const Position& pos, uint8_t effect) { - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendMagicEffect(pos, effect); } @@ -3701,49 +4378,23 @@ void Game::addMagicEffect(const SpectatorVec& list, const Position& pos, uint8_t void Game::addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect) { - SpectatorVec list; - map.getSpectators(list, fromPos, false, true); - map.getSpectators(list, toPos, false, true); - addDistanceEffect(list, fromPos, toPos, effect); + SpectatorVec spectators, toPosSpectators; + map.getSpectators(spectators, fromPos, false, true); + map.getSpectators(toPosSpectators, toPos, false, true); + spectators.addSpectators(toPosSpectators); + + addDistanceEffect(spectators, fromPos, toPos, effect); } -void Game::addDistanceEffect(const SpectatorVec& list, const Position& fromPos, const Position& toPos, uint8_t effect) +void Game::addDistanceEffect(const SpectatorVec& spectators, const Position& fromPos, const Position& toPos, uint8_t effect) { - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendDistanceShoot(fromPos, toPos, effect); } } } -void Game::addAnimatedText(const Position& pos, uint8_t color, const std::string& text) -{ - SpectatorVec list; - map.getSpectators(list, pos, false, true); - addAnimatedText(list, pos, color, text); -} - -void Game::addAnimatedText(const SpectatorVec& list, const Position& pos, uint8_t color, const std::string& text) -{ - for (Creature* spectator : list) { - if (Player* tmpPlayer = spectator->getPlayer()) { - tmpPlayer->sendAnimatedText(pos, color, text); - } - } -} - -void Game::addMonsterSayText(const Position& pos, const std::string& text) -{ - SpectatorVec list; - map.getSpectators(list, pos, false, true); - - for (Creature* spectator : list) { - if (Player* tmpPlayer = spectator->getPlayer()) { - tmpPlayer->sendCreatureSay(tmpPlayer, TALKTYPE_MONSTER_SAY, text, &pos); - } - } -} - void Game::startDecay(Item* item) { if (!item || !item->canDecay()) { @@ -3768,7 +4419,7 @@ void Game::internalDecayItem(Item* item) { const ItemType& it = Item::items[item->getID()]; if (it.decayTo != 0) { - Item* newItem = transformItem(item, it.decayTo); + Item* newItem = transformItem(item, item->getDecayTo()); startDecay(newItem); } else { ReturnValue ret = internalRemoveItem(item); @@ -3867,8 +4518,7 @@ void Game::checkLight() } if (lightChange) { - LightInfo lightInfo; - getWorldLightInfo(lightInfo); + LightInfo lightInfo = getWorldLightInfo(); for (const auto& it : players) { it.second->sendWorldLight(lightInfo); @@ -3876,15 +4526,13 @@ void Game::checkLight() } } -void Game::getWorldLightInfo(LightInfo& lightInfo) const +LightInfo Game::getWorldLightInfo() const { - lightInfo.level = lightLevel; - lightInfo.color = 0xD7; + return {lightLevel, 0xD7}; } void Game::shutdown() { - saveGameState(); std::cout << "Shutting down..." << std::flush; g_scheduler.shutdown(); @@ -3946,28 +4594,87 @@ void Game::broadcastMessage(const std::string& text, MessageClasses type) const } } +void Game::updateCreatureWalkthrough(const Creature* creature) +{ + //send to clients + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), true, true); + for (Creature* spectator : spectators) { + Player* tmpPlayer = spectator->getPlayer(); + tmpPlayer->sendCreatureWalkthrough(creature, tmpPlayer->canWalkthroughEx(creature)); + } +} + void Game::updateCreatureSkull(const Creature* creature) { if (getWorldType() != WORLD_TYPE_PVP) { return; } - SpectatorVec list; - map.getSpectators(list, creature->getPosition(), true, true); - for (Creature* spectator : list) { + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), true, true); + for (Creature* spectator : spectators) { spectator->getPlayer()->sendCreatureSkull(creature); } } void Game::updatePlayerShield(Player* player) { - SpectatorVec list; - map.getSpectators(list, player->getPosition(), true, true); - for (Creature* spectator : list) { + SpectatorVec spectators; + map.getSpectators(spectators, player->getPosition(), true, true); + for (Creature* spectator : spectators) { spectator->getPlayer()->sendCreatureShield(player); } } +void Game::updatePlayerHelpers(const Player& player) +{ + uint32_t creatureId = player.getID(); + uint16_t helpers = player.getHelpers(); + + SpectatorVec spectators; + map.getSpectators(spectators, player.getPosition(), true, true); + for (Creature* spectator : spectators) { + spectator->getPlayer()->sendCreatureHelpers(creatureId, helpers); + } +} + +void Game::updateCreatureType(Creature* creature) +{ + const Player* masterPlayer = nullptr; + + uint32_t creatureId = creature->getID(); + CreatureType_t creatureType = creature->getType(); + if (creatureType == CREATURETYPE_MONSTER) { + const Creature* master = creature->getMaster(); + if (master) { + masterPlayer = master->getPlayer(); + if (masterPlayer) { + creatureType = CREATURETYPE_SUMMON_OTHERS; + } + } + } + + //send to clients + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), true, true); + + if (creatureType == CREATURETYPE_SUMMON_OTHERS) { + for (Creature* spectator : spectators) { + Player* player = spectator->getPlayer(); + if (masterPlayer == player) { + player->sendCreatureType(creatureId, CREATURETYPE_SUMMON_OWN); + } else { + player->sendCreatureType(creatureId, creatureType); + } + } + } else { + for (Creature* spectator : spectators) { + spectator->getPlayer()->sendCreatureType(creatureId, creatureType); + } + } +} + void Game::updatePremium(Account& account) { bool save = false; @@ -3998,43 +4705,43 @@ void Game::updatePremium(Account& account) } if (save && !IOLoginData::saveAccount(account)) { - std::cout << "> ERROR: Failed to save account: " << account.id << "!" << std::endl; + std::cout << "> ERROR: Failed to save account: " << account.name << "!" << std::endl; } } void Game::loadMotdNum() { - Database* db = Database::getInstance(); + Database& db = Database::getInstance(); - DBResult_ptr result = db->storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'motd_num'"); + DBResult_ptr result = db.storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'motd_num'"); if (result) { motdNum = result->getNumber("value"); } else { - db->executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('motd_num', '0')"); + db.executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('motd_num', '0')"); } - result = db->storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'motd_hash'"); + result = db.storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'motd_hash'"); if (result) { motdHash = result->getString("value"); if (motdHash != transformToSHA1(g_config.getString(ConfigManager::MOTD))) { ++motdNum; } } else { - db->executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('motd_hash', '')"); + db.executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('motd_hash', '')"); } } void Game::saveMotdNum() const { - Database* db = Database::getInstance(); + Database& db = Database::getInstance(); std::ostringstream query; query << "UPDATE `server_config` SET `value` = '" << motdNum << "' WHERE `config` = 'motd_num'"; - db->executeQuery(query.str()); + db.executeQuery(query.str()); query.str(std::string()); query << "UPDATE `server_config` SET `value` = '" << transformToSHA1(g_config.getString(ConfigManager::MOTD)) << "' WHERE `config` = 'motd_hash'"; - db->executeQuery(query.str()); + db.executeQuery(query.str()); } void Game::checkPlayersRecord() @@ -4044,8 +4751,8 @@ void Game::checkPlayersRecord() uint32_t previousRecord = playersRecord; playersRecord = playersOnline; - for (const auto& it : g_globalEvents->getEventMap(GLOBALEVENT_RECORD)) { - it.second->executeRecord(playersRecord, previousRecord); + for (auto& it : g_globalEvents->getEventMap(GLOBALEVENT_RECORD)) { + it.second.executeRecord(playersRecord, previousRecord); } updatePlayersRecord(); } @@ -4053,22 +4760,22 @@ void Game::checkPlayersRecord() void Game::updatePlayersRecord() const { - Database* db = Database::getInstance(); + Database& db = Database::getInstance(); std::ostringstream query; query << "UPDATE `server_config` SET `value` = '" << playersRecord << "' WHERE `config` = 'players_record'"; - db->executeQuery(query.str()); + db.executeQuery(query.str()); } void Game::loadPlayersRecord() { - Database* db = Database::getInstance(); + Database& db = Database::getInstance(); - DBResult_ptr result = db->storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'players_record'"); + DBResult_ptr result = db.storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'players_record'"); if (result) { playersRecord = result->getNumber("value"); } else { - db->executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('players_record', '0')"); + db.executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('players_record', '0')"); } } @@ -4137,6 +4844,10 @@ bool Game::loadExperienceStages() void Game::playerInviteToParty(uint32_t playerId, uint32_t invitedId) { + if (playerId == invitedId) { + return; + } + Player* player = getPlayerByID(playerId); if (!player) { return; @@ -4252,113 +4963,24 @@ void Game::playerEnableSharedPartyExperience(uint32_t playerId, bool sharedExpAc } Party* party = player->getParty(); - if (!party || player->hasCondition(CONDITION_INFIGHT)) { + if (!party || (player->hasCondition(CONDITION_INFIGHT) && player->getZone() != ZONE_PROTECTION)) { return; } party->setSharedExperience(player, sharedExpActive); } -void Game::playerProcessRuleViolationReport(uint32_t playerId, const std::string& name) +void Game::sendGuildMotd(uint32_t playerId) { Player* player = getPlayerByID(playerId); if (!player) { return; } - if (player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER) { - return; + Guild* guild = player->getGuild(); + if (guild) { + player->sendChannelMessage("Message of the Day", guild->getMotd(), TALKTYPE_CHANNEL_R1, CHANNEL_GUILD); } - - Player* reporter = getPlayerByName(name); - if (!reporter) { - return; - } - - auto it = ruleViolations.find(reporter->getID()); - if (it == ruleViolations.end()) { - return; - } - - RuleViolation& ruleViolation = it->second; - if (!ruleViolation.pending) { - return; - } - - ruleViolation.gamemasterId = player->getID(); - ruleViolation.pending = false; - - ChatChannel* channel = g_chat->getChannelById(CHANNEL_RULE_REP); - if (channel) { - for (auto userPtr : channel->getUsers()) { - if (userPtr.second) { - userPtr.second->sendRemoveRuleViolationReport(reporter->getName()); - } - } - } -} - -void Game::playerCloseRuleViolationReport(uint32_t playerId, const std::string& name) -{ - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - Player* reporter = getPlayerByName(name); - if (!reporter) { - return; - } - - closeRuleViolationReport(reporter); -} - -void Game::playerCancelRuleViolationReport(uint32_t playerId) -{ - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - cancelRuleViolationReport(player); -} - -void Game::playerReportRuleViolationReport(Player* player, const std::string& text) -{ - auto it = ruleViolations.find(player->getID()); - if (it != ruleViolations.end()) { - player->sendCancelMessage("You already have a pending rule violation report. Close it before starting a new one."); - return; - } - - RuleViolation ruleViolation = RuleViolation(player->getID(), text); - ruleViolations[player->getID()] = ruleViolation; - - ChatChannel* channel = g_chat->getChannelById(CHANNEL_RULE_REP); - if (channel) { - for (auto userPtr : channel->getUsers()) { - if (userPtr.second) { - userPtr.second->sendToChannel(player, TALKTYPE_RVR_CHANNEL, text, CHANNEL_RULE_REP); - } - } - } -} - -void Game::playerContinueRuleViolationReport(Player* player, const std::string& text) -{ - auto it = ruleViolations.find(player->getID()); - if (it == ruleViolations.end()) { - return; - } - - RuleViolation& rvr = it->second; - Player* toPlayer = getPlayerByID(rvr.gamemasterId); - if (!toPlayer) { - return; - } - - toPlayer->sendCreatureSay(player, TALKTYPE_RVR_CONTINUE, text, 0); - player->sendTextMessage(MESSAGE_STATUS_SMALL, "Message sent to Counsellor."); } void Game::kickPlayer(uint32_t playerId, bool displayEffect) @@ -4371,15 +4993,24 @@ void Game::kickPlayer(uint32_t playerId, bool displayEffect) player->kickPlayer(displayEffect); } -void Game::playerReportBug(uint32_t playerId, const std::string& message) +void Game::playerReportRuleViolation(uint32_t playerId, const std::string& targetName, uint8_t reportType, uint8_t reportReason, const std::string& comment, const std::string& translation) { Player* player = getPlayerByID(playerId); if (!player) { return; } - const Position& position = player->getPosition(); - g_events->eventPlayerOnReportBug(player, message, position); + g_events->eventPlayerOnReportRuleViolation(player, targetName, reportType, reportReason, comment, translation); +} + +void Game::playerReportBug(uint32_t playerId, const std::string& message, const Position& position, uint8_t category) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + g_events->eventPlayerOnReportBug(player, message, position, category); } void Game::playerDebugAssert(uint32_t playerId, const std::string& assertLine, const std::string& date, const std::string& description, const std::string& comment) @@ -4398,6 +5029,411 @@ void Game::playerDebugAssert(uint32_t playerId, const std::string& assertLine, c } } +void Game::playerLeaveMarket(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + player->setInMarket(false); +} + +void Game::playerBrowseMarket(uint32_t playerId, uint16_t spriteId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + if (!player->isInMarket()) { + return; + } + + const ItemType& it = Item::items.getItemIdByClientId(spriteId); + if (it.id == 0) { + return; + } + + if (it.wareId == 0) { + return; + } + + const MarketOfferList& buyOffers = IOMarket::getActiveOffers(MARKETACTION_BUY, it.id); + const MarketOfferList& sellOffers = IOMarket::getActiveOffers(MARKETACTION_SELL, it.id); + player->sendMarketBrowseItem(it.id, buyOffers, sellOffers); + player->sendMarketDetail(it.id); +} + +void Game::playerBrowseMarketOwnOffers(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + if (!player->isInMarket()) { + return; + } + + const MarketOfferList& buyOffers = IOMarket::getOwnOffers(MARKETACTION_BUY, player->getGUID()); + const MarketOfferList& sellOffers = IOMarket::getOwnOffers(MARKETACTION_SELL, player->getGUID()); + player->sendMarketBrowseOwnOffers(buyOffers, sellOffers); +} + +void Game::playerBrowseMarketOwnHistory(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + if (!player->isInMarket()) { + return; + } + + const HistoryMarketOfferList& buyOffers = IOMarket::getOwnHistory(MARKETACTION_BUY, player->getGUID()); + const HistoryMarketOfferList& sellOffers = IOMarket::getOwnHistory(MARKETACTION_SELL, player->getGUID()); + player->sendMarketBrowseOwnHistory(buyOffers, sellOffers); +} + +void Game::playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t spriteId, uint16_t amount, uint32_t price, bool anonymous) +{ + if (amount == 0 || amount > 64000) { + return; + } + + if (price == 0 || price > 999999999) { + return; + } + + if (type != MARKETACTION_BUY && type != MARKETACTION_SELL) { + return; + } + + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + if (!player->isInMarket()) { + return; + } + + if (g_config.getBoolean(ConfigManager::MARKET_PREMIUM) && !player->isPremium()) { + player->sendMarketLeave(); + return; + } + + const ItemType& itt = Item::items.getItemIdByClientId(spriteId); + if (itt.id == 0 || itt.wareId == 0) { + return; + } + + const ItemType& it = Item::items.getItemIdByClientId(itt.wareId); + if (it.id == 0 || it.wareId == 0) { + return; + } + + if (!it.stackable && amount > 2000) { + return; + } + + const uint32_t maxOfferCount = g_config.getNumber(ConfigManager::MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER); + if (maxOfferCount != 0 && IOMarket::getPlayerOfferCount(player->getGUID()) >= maxOfferCount) { + return; + } + + uint64_t fee = (price / 100.) * amount; + if (fee < 20) { + fee = 20; + } else if (fee > 1000) { + fee = 1000; + } + + if (type == MARKETACTION_SELL) { + if (fee > player->bankBalance) { + return; + } + + DepotChest* depotChest = player->getDepotChest(player->getLastDepotId(), false); + if (!depotChest) { + return; + } + + std::forward_list itemList = getMarketItemList(it.wareId, amount, depotChest, player->getInbox()); + if (itemList.empty()) { + return; + } + + if (it.stackable) { + uint16_t tmpAmount = amount; + for (Item* item : itemList) { + uint16_t removeCount = std::min(tmpAmount, item->getItemCount()); + tmpAmount -= removeCount; + internalRemoveItem(item, removeCount); + + if (tmpAmount == 0) { + break; + } + } + } else { + for (Item* item : itemList) { + internalRemoveItem(item); + } + } + + player->bankBalance -= fee; + } else { + uint64_t totalPrice = static_cast(price) * amount; + totalPrice += fee; + if (totalPrice > player->bankBalance) { + return; + } + + player->bankBalance -= totalPrice; + } + + IOMarket::createOffer(player->getGUID(), static_cast(type), it.id, amount, price, anonymous); + + player->sendMarketEnter(player->getLastDepotId()); + const MarketOfferList& buyOffers = IOMarket::getActiveOffers(MARKETACTION_BUY, it.id); + const MarketOfferList& sellOffers = IOMarket::getActiveOffers(MARKETACTION_SELL, it.id); + player->sendMarketBrowseItem(it.id, buyOffers, sellOffers); +} + +void Game::playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + if (!player->isInMarket()) { + return; + } + + MarketOfferEx offer = IOMarket::getOfferByCounter(timestamp, counter); + if (offer.id == 0 || offer.playerId != player->getGUID()) { + return; + } + + if (offer.type == MARKETACTION_BUY) { + player->bankBalance += static_cast(offer.price) * offer.amount; + player->sendMarketEnter(player->getLastDepotId()); + } else { + const ItemType& it = Item::items[offer.itemId]; + if (it.id == 0) { + return; + } + + if (it.stackable) { + uint16_t tmpAmount = offer.amount; + while (tmpAmount > 0) { + int32_t stackCount = std::min(100, tmpAmount); + Item* item = Item::CreateItem(it.id, stackCount); + if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { + delete item; + break; + } + + tmpAmount -= stackCount; + } + } else { + int32_t subType; + if (it.charges != 0) { + subType = it.charges; + } else { + subType = -1; + } + + for (uint16_t i = 0; i < offer.amount; ++i) { + Item* item = Item::CreateItem(it.id, subType); + if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { + delete item; + break; + } + } + } + } + + IOMarket::moveOfferToHistory(offer.id, OFFERSTATE_CANCELLED); + offer.amount = 0; + offer.timestamp += g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); + player->sendMarketCancelOffer(offer); + player->sendMarketEnter(player->getLastDepotId()); +} + +void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter, uint16_t amount) +{ + if (amount == 0 || amount > 64000) { + return; + } + + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + if (!player->isInMarket()) { + return; + } + + MarketOfferEx offer = IOMarket::getOfferByCounter(timestamp, counter); + if (offer.id == 0) { + return; + } + + if (amount > offer.amount) { + return; + } + + const ItemType& it = Item::items[offer.itemId]; + if (it.id == 0) { + return; + } + + uint64_t totalPrice = static_cast(offer.price) * amount; + + if (offer.type == MARKETACTION_BUY) { + DepotChest* depotChest = player->getDepotChest(player->getLastDepotId(), false); + if (!depotChest) { + return; + } + + std::forward_list itemList = getMarketItemList(it.wareId, amount, depotChest, player->getInbox()); + if (itemList.empty()) { + return; + } + + Player* buyerPlayer = getPlayerByGUID(offer.playerId); + if (!buyerPlayer) { + buyerPlayer = new Player(nullptr); + if (!IOLoginData::loadPlayerById(buyerPlayer, offer.playerId)) { + delete buyerPlayer; + return; + } + } + + if (it.stackable) { + uint16_t tmpAmount = amount; + for (Item* item : itemList) { + uint16_t removeCount = std::min(tmpAmount, item->getItemCount()); + tmpAmount -= removeCount; + internalRemoveItem(item, removeCount); + + if (tmpAmount == 0) { + break; + } + } + } else { + for (Item* item : itemList) { + internalRemoveItem(item); + } + } + + player->bankBalance += totalPrice; + + if (it.stackable) { + uint16_t tmpAmount = amount; + while (tmpAmount > 0) { + uint16_t stackCount = std::min(100, tmpAmount); + Item* item = Item::CreateItem(it.id, stackCount); + if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { + delete item; + break; + } + + tmpAmount -= stackCount; + } + } else { + int32_t subType; + if (it.charges != 0) { + subType = it.charges; + } else { + subType = -1; + } + + for (uint16_t i = 0; i < amount; ++i) { + Item* item = Item::CreateItem(it.id, subType); + if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { + delete item; + break; + } + } + } + + if (buyerPlayer->isOffline()) { + IOLoginData::savePlayer(buyerPlayer); + delete buyerPlayer; + } else { + buyerPlayer->onReceiveMail(); + } + } else { + if (totalPrice > player->bankBalance) { + return; + } + + player->bankBalance -= totalPrice; + + if (it.stackable) { + uint16_t tmpAmount = amount; + while (tmpAmount > 0) { + uint16_t stackCount = std::min(100, tmpAmount); + Item* item = Item::CreateItem(it.id, stackCount); + if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { + delete item; + break; + } + + tmpAmount -= stackCount; + } + } else { + int32_t subType; + if (it.charges != 0) { + subType = it.charges; + } else { + subType = -1; + } + + for (uint16_t i = 0; i < amount; ++i) { + Item* item = Item::CreateItem(it.id, subType); + if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { + delete item; + break; + } + } + } + + Player* sellerPlayer = getPlayerByGUID(offer.playerId); + if (sellerPlayer) { + sellerPlayer->bankBalance += totalPrice; + } else { + IOLoginData::increaseBankBalance(offer.playerId, totalPrice); + } + + player->onReceiveMail(); + } + + const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); + + IOMarket::appendHistory(player->getGUID(), (offer.type == MARKETACTION_BUY ? MARKETACTION_SELL : MARKETACTION_BUY), offer.itemId, amount, offer.price, offer.timestamp + marketOfferDuration, OFFERSTATE_ACCEPTEDEX); + + IOMarket::appendHistory(offer.playerId, offer.type, offer.itemId, amount, offer.price, offer.timestamp + marketOfferDuration, OFFERSTATE_ACCEPTED); + + offer.amount -= amount; + + if (offer.amount == 0) { + IOMarket::deleteOffer(offer.id); + } else { + IOMarket::acceptOffer(offer.id, amount); + } + + player->sendMarketEnter(player->getLastDepotId()); + offer.timestamp += marketOfferDuration; + player->sendMarketAcceptOffer(offer); +} + void Game::parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const std::string& buffer) { Player* player = getPlayerByID(playerId); @@ -4410,52 +5446,45 @@ void Game::parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const st } } -void Game::closeRuleViolationReport(Player* player) +std::forward_list Game::getMarketItemList(uint16_t wareId, uint16_t sufficientCount, DepotChest* depotChest, Inbox* inbox) { - const auto it = ruleViolations.find(player->getID()); - if (it == ruleViolations.end()) { - return; - } + std::forward_list itemList; + uint16_t count = 0; - ruleViolations.erase(it); - player->sendLockRuleViolationReport(); + std::list containers { depotChest, inbox }; + do { + Container* container = containers.front(); + containers.pop_front(); - ChatChannel* channel = g_chat->getChannelById(CHANNEL_RULE_REP); - if (channel) { - for (UsersMap::const_iterator ut = channel->getUsers().begin(); ut != channel->getUsers().end(); ++ut) { - if (ut->second) { - ut->second->sendRemoveRuleViolationReport(player->getName()); + for (Item* item : container->getItemList()) { + Container* c = item->getContainer(); + if (c && !c->empty()) { + containers.push_back(c); + continue; + } + + const ItemType& itemType = Item::items[item->getID()]; + if (itemType.wareId != wareId) { + continue; + } + + if (c && (!itemType.isContainer() || c->capacity() != itemType.maxItems)) { + continue; + } + + if (!item->hasMarketAttributes()) { + continue; + } + + itemList.push_front(item); + + count += Item::countByType(item, -1); + if (count >= sufficientCount) { + return itemList; } } - } -} - -void Game::cancelRuleViolationReport(Player* player) -{ - const auto it = ruleViolations.find(player->getID()); - if (it == ruleViolations.end()) { - return; - } - - RuleViolation& ruleViolation = it->second; - Player* gamemaster = getPlayerByID(ruleViolation.gamemasterId); - if (!ruleViolation.pending && gamemaster) { - // Send to the responder - gamemaster->sendRuleViolationCancel(player->getName()); - } - - // Send to channel - ChatChannel* channel = g_chat->getChannelById(CHANNEL_RULE_REP); - if (channel) { - for (UsersMap::const_iterator ut = channel->getUsers().begin(); ut != channel->getUsers().end(); ++ut) { - if (ut->second) { - ut->second->sendRemoveRuleViolationReport(player->getName()); - } - } - } - - // Erase it - ruleViolations.erase(it); + } while (!containers.empty()); + return std::forward_list(); } void Game::forceAddCondition(uint32_t creatureId, Condition* condition) @@ -4479,6 +5508,52 @@ void Game::forceRemoveCondition(uint32_t creatureId, ConditionType_t type) creature->removeCondition(type, true); } +void Game::sendOfflineTrainingDialog(Player* player) +{ + if (!player) { + return; + } + + if (!player->hasModalWindowOpen(offlineTrainingWindow.id)) { + player->sendModalWindow(offlineTrainingWindow); + } +} + +void Game::playerAnswerModalWindow(uint32_t playerId, uint32_t modalWindowId, uint8_t button, uint8_t choice) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + if (!player->hasModalWindowOpen(modalWindowId)) { + return; + } + + player->onModalWindowHandled(modalWindowId); + + // offline training, hardcoded + if (modalWindowId == std::numeric_limits::max()) { + if (button == 1) { + if (choice == SKILL_SWORD || choice == SKILL_AXE || choice == SKILL_CLUB || choice == SKILL_DISTANCE || choice == SKILL_MAGLEVEL) { + BedItem* bedItem = player->getBedItem(); + if (bedItem && bedItem->sleep(player)) { + player->setOfflineTrainingSkill(choice); + return; + } + } + } else { + player->sendTextMessage(MESSAGE_EVENT_ADVANCE, "Offline training aborted."); + } + + player->setBedItem(nullptr); + } else { + for (auto creatureEvent : player->getCreatureEvents(CREATURE_EVENT_MODALWINDOW)) { + creatureEvent->executeModalWindow(player, modalWindowId, button, choice); + } + } +} + void Game::addPlayer(Player* player) { const std::string& lowercase_name = asLowerCaseString(player->getName()); @@ -4534,6 +5609,19 @@ void Game::removeGuild(uint32_t guildId) guilds.erase(guildId); } +void Game::decreaseBrowseFieldRef(const Position& pos) +{ + Tile* tile = map.getTile(pos.x, pos.y, pos.z); + if (!tile) { + return; + } + + auto it = browseFields.find(tile); + if (it != browseFields.end()) { + it->second->decrementReferenceCounter(); + } +} + void Game::internalRemoveItems(std::vector itemList, uint32_t amount, bool stackable) { if (stackable) { @@ -4575,63 +5663,132 @@ void Game::removeBedSleeper(uint32_t guid) } } +Item* Game::getUniqueItem(uint16_t uniqueId) +{ + auto it = uniqueItems.find(uniqueId); + if (it == uniqueItems.end()) { + return nullptr; + } + return it->second; +} + +bool Game::addUniqueItem(uint16_t uniqueId, Item* item) +{ + auto result = uniqueItems.emplace(uniqueId, item); + if (!result.second) { + std::cout << "Duplicate unique id: " << uniqueId << std::endl; + } + return result.second; +} + +void Game::removeUniqueItem(uint16_t uniqueId) +{ + auto it = uniqueItems.find(uniqueId); + if (it != uniqueItems.end()) { + uniqueItems.erase(it); + } +} + bool Game::reload(ReloadTypes_t reloadType) { switch (reloadType) { - case RELOAD_TYPE_ACTIONS: return g_actions->reload(); - case RELOAD_TYPE_CHAT: return g_chat->load(); - case RELOAD_TYPE_CONFIG: return g_config.reload(); - case RELOAD_TYPE_CREATURESCRIPTS: return g_creatureEvents->reload(); - case RELOAD_TYPE_EVENTS: return g_events->load(); - case RELOAD_TYPE_GLOBALEVENTS: return g_globalEvents->reload(); - case RELOAD_TYPE_ITEMS: return Item::items.reload(); - case RELOAD_TYPE_MONSTERS: return g_monsters.reload(); - case RELOAD_TYPE_MOVEMENTS: return g_moveEvents->reload(); - case RELOAD_TYPE_NPCS: { - Npcs::reload(); - return true; - } - case RELOAD_TYPE_RAIDS: return raids.reload() && raids.startup(); - - case RELOAD_TYPE_SPELLS: { - if (!g_spells->reload()) { - std::cout << "[Error - Game::reload] Failed to reload spells." << std::endl; - std::terminate(); - } - else if (!g_monsters.reload()) { - std::cout << "[Error - Game::reload] Failed to reload monsters." << std::endl; - std::terminate(); - } - return true; - } - - case RELOAD_TYPE_TALKACTIONS: return g_talkActions->reload(); - - default: { - if (!g_spells->reload()) { - std::cout << "[Error - Game::reload] Failed to reload spells." << std::endl; - std::terminate(); - return false; - } - else if (!g_monsters.reload()) { - std::cout << "[Error - Game::reload] Failed to reload monsters." << std::endl; - std::terminate(); - return false; + case RELOAD_TYPE_ACTIONS: return g_actions->reload(); + case RELOAD_TYPE_CHAT: return g_chat->load(); + case RELOAD_TYPE_CONFIG: return g_config.reload(); + case RELOAD_TYPE_CREATURESCRIPTS: return g_creatureEvents->reload(); + case RELOAD_TYPE_EVENTS: return g_events->load(); + case RELOAD_TYPE_GLOBALEVENTS: return g_globalEvents->reload(); + case RELOAD_TYPE_ITEMS: return Item::items.reload(); + case RELOAD_TYPE_MONSTERS: return g_monsters.reload(); + case RELOAD_TYPE_MOUNTS: return mounts.reload(); + case RELOAD_TYPE_MOVEMENTS: return g_moveEvents->reload(); + case RELOAD_TYPE_NPCS: { + Npcs::reload(); + return true; } - g_actions->reload(); - g_config.reload(); - g_creatureEvents->reload(); - g_monsters.reload(); - g_moveEvents->reload(); - Npcs::reload(); - raids.reload() && raids.startup(); - g_talkActions->reload(); - Item::items.reload(); - g_globalEvents->reload(); - g_events->load(); - g_chat->load(); - return true; - } + case RELOAD_TYPE_QUESTS: return quests.reload(); + case RELOAD_TYPE_RAIDS: return raids.reload() && raids.startup(); + + case RELOAD_TYPE_SPELLS: { + if (!g_spells->reload()) { + std::cout << "[Error - Game::reload] Failed to reload spells." << std::endl; + std::terminate(); + } else if (!g_monsters.reload()) { + std::cout << "[Error - Game::reload] Failed to reload monsters." << std::endl; + std::terminate(); + } + return true; + } + + case RELOAD_TYPE_TALKACTIONS: return g_talkActions->reload(); + + case RELOAD_TYPE_WEAPONS: { + bool results = g_weapons->reload(); + g_weapons->loadDefaults(); + return results; + } + + case RELOAD_TYPE_SCRIPTS: { + // commented out stuff is TODO, once we approach further in revscriptsys + g_actions->clear(true); + g_creatureEvents->clear(true); + g_moveEvents->clear(true); + g_talkActions->clear(true); + g_globalEvents->clear(true); + g_weapons->clear(true); + g_weapons->loadDefaults(); + g_spells->clear(true); + g_scripts->loadScripts("scripts", false, true); + /* + Npcs::reload(); + raids.reload() && raids.startup(); + Item::items.reload(); + quests.reload(); + mounts.reload(); + g_config.reload(); + g_events->load(); + g_chat->load(); + */ + return true; + } + + default: { + if (!g_spells->reload()) { + std::cout << "[Error - Game::reload] Failed to reload spells." << std::endl; + std::terminate(); + } else if (!g_monsters.reload()) { + std::cout << "[Error - Game::reload] Failed to reload monsters." << std::endl; + std::terminate(); + } + + g_actions->reload(); + g_config.reload(); + g_creatureEvents->reload(); + g_monsters.reload(); + g_moveEvents->reload(); + Npcs::reload(); + raids.reload() && raids.startup(); + g_talkActions->reload(); + Item::items.reload(); + g_weapons->reload(); + g_weapons->clear(true); + g_weapons->loadDefaults(); + quests.reload(); + mounts.reload(); + g_globalEvents->reload(); + g_events->load(); + g_chat->load(); + g_actions->clear(true); + g_creatureEvents->clear(true); + g_moveEvents->clear(true); + g_talkActions->clear(true); + g_globalEvents->clear(true); + g_spells->clear(true); + g_scripts->loadScripts("scripts", false, true); + return true; + } } + return true; } + diff --git a/src/game.h b/src/game.h index 633b6a7..83d5e55 100644 --- a/src/game.h +++ b/src/game.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,6 +31,7 @@ #include "raids.h" #include "npc.h" #include "wildcardtree.h" +#include "quests.h" class ServiceManager; class Creature; @@ -69,22 +70,6 @@ enum LightState_t { 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; @@ -97,7 +82,7 @@ static constexpr int32_t EVENT_DECAY_BUCKETS = 4; class Game { public: - Game() = default; + Game(); ~Game(); // non-copyable @@ -219,7 +204,7 @@ class Game * \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); + bool placeCreature(Creature* creature, const Position& pos, bool extendedPos = false, bool forced = false); /** * Remove Creature from the map. @@ -244,7 +229,7 @@ class Game return playersRecord; } - void getWorldLightInfo(LightInfo& lightInfo) const; + LightInfo getWorldLightInfo() const; ReturnValue internalMoveCreature(Creature* creature, Direction direction, uint32_t flags = 0); ReturnValue internalMoveCreature(Creature& creature, Tile& toTile, uint32_t flags = 0); @@ -322,16 +307,19 @@ class Game * \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); + bool ghostMode, SpectatorVec* spectatorsPtr = nullptr, const Position* pos = nullptr); void loadPlayersRecord(); void checkPlayersRecord(); + void sendGuildMotd(uint32_t playerId); void kickPlayer(uint32_t playerId, bool displayEffect); - void playerReportBug(uint32_t playerId, const std::string& message); + void playerReportBug(uint32_t playerId, const std::string& message, const Position& position, uint8_t category); void playerDebugAssert(uint32_t playerId, const std::string& assertLine, const std::string& date, const std::string& description, const std::string& comment); + void playerAnswerModalWindow(uint32_t playerId, uint32_t modalWindowId, uint8_t button, uint8_t choice); + void playerReportRuleViolation(uint32_t playerId, const std::string& targetName, uint8_t reportType, uint8_t reportReason, const std::string& comment, const std::string& translation); - bool internalStartTrade(Player* player, Player* partner, Item* tradeItem); + bool internalStartTrade(Player* player, Player* tradePartner, Item* tradeItem); void internalCloseTrade(Player* player); bool playerBroadcastMessage(Player* player, const std::string& text) const; void broadcastMessage(const std::string& text, MessageClasses type) const; @@ -340,10 +328,11 @@ class Game 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 playerMoveCreature(Player* player, 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 playerEquipItem(uint32_t playerId, uint16_t spriteId); void playerMove(uint32_t playerId, Direction direction); void playerCreatePrivateChannel(uint32_t playerId); void playerChannelInvite(uint32_t playerId, const std::string& name); @@ -352,6 +341,7 @@ class Game 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 playerCloseNpcChannel(uint32_t playerId); void playerReceivePing(uint32_t playerId); void playerReceivePingBack(uint32_t playerId); void playerAutoWalk(uint32_t playerId, const std::forward_list& listDir); @@ -365,23 +355,33 @@ class Game 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 playerBrowseField(uint32_t playerId, const Position& pos); 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 playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount, + bool ignoreCap = false, bool inBackpacks = false); + void playerSellItem(uint32_t playerId, uint16_t spriteId, uint8_t count, + uint8_t amount, bool ignoreEquipped = false); + void playerCloseShop(uint32_t playerId); + void playerLookInShop(uint32_t playerId, uint16_t spriteId, uint8_t count); 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 playerSetFightModes(uint32_t playerId, fightMode_t fightMode, bool 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 playerRequestEditVip(uint32_t playerId, uint32_t guid, const std::string& description, uint32_t icon, bool notify); void playerTurn(uint32_t playerId, Direction dir); void playerRequestOutfit(uint32_t playerId); + void playerShowQuestLog(uint32_t playerId); + void playerShowQuestLine(uint32_t playerId, uint16_t questId); 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); @@ -391,15 +391,18 @@ class Game 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 playerToggleMount(uint32_t playerId, bool mount); + void playerLeaveMarket(uint32_t playerId); + void playerBrowseMarket(uint32_t playerId, uint16_t spriteId); + void playerBrowseMarketOwnOffers(uint32_t playerId); + void playerBrowseMarketOwnHistory(uint32_t playerId); + void playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t spriteId, uint16_t amount, uint32_t price, bool anonymous); + void playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter); + void playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter, uint16_t amount); + void parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const std::string& buffer); - void closeRuleViolationReport(Player* player); - void cancelRuleViolationReport(Player* player); + std::forward_list getMarketItemList(uint16_t wareId, uint16_t sufficientCount, DepotChest* depotChest, Inbox* inbox); static void updatePremium(Account& account); @@ -410,14 +413,17 @@ class Game 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; + bool isSightClear(const Position& fromPos, const Position& toPos, bool floorCheck) const; void changeSpeed(Creature* creature, int32_t varSpeedDelta); - void internalCreatureChangeOutfit(Creature* creature, const Outfit_t& oufit); + void internalCreatureChangeOutfit(Creature* creature, const Outfit_t& outfit); void internalCreatureChangeVisible(Creature* creature, bool visible); void changeLight(const Creature* creature); - void updateCreatureSkull(const Creature* player); + void updateCreatureSkull(const Creature* creature); void updatePlayerShield(Player* player); + void updatePlayerHelpers(const Player& player); + void updateCreatureType(Creature* creature); + void updateCreatureWalkthrough(const Creature* creature); GameState_t getGameState() const; void setGameState(GameState_t newState); @@ -439,14 +445,11 @@ class Game //animation help functions void addCreatureHealth(const Creature* target); - static void addCreatureHealth(const SpectatorVec& list, const Creature* target); + static void addCreatureHealth(const SpectatorVec& spectators, const Creature* target); void addMagicEffect(const Position& pos, uint8_t effect); - static void addMagicEffect(const SpectatorVec& list, const Position& pos, uint8_t effect); + static void addMagicEffect(const SpectatorVec& spectators, 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); + static void addDistanceEffect(const SpectatorVec& spectators, const Position& fromPos, const Position& toPos, uint8_t effect); void startDecay(Item* item); int32_t getLightHour() const { @@ -462,7 +465,8 @@ class Game uint32_t getMotdNum() const { return motdNum; } void incrementMotdNum() { motdNum++; } - const std::unordered_map& getRuleViolationReports() const { return ruleViolations; } + void sendOfflineTrainingDialog(Player* player); + const std::unordered_map& getPlayers() const { return players; } const std::map& getNpcs() const { return npcs; } @@ -472,45 +476,55 @@ class Game void addNpc(Npc* npc); void removeNpc(Npc* npc); - void addMonster(Monster* npc); - void removeMonster(Monster* npc); + void addMonster(Monster* monster); + void removeMonster(Monster* monster); Guild* getGuild(uint32_t id) const; void addGuild(Guild* guild); void removeGuild(uint32_t guildId); + void decreaseBrowseFieldRef(const Position& pos); + + std::unordered_map browseFields; void internalRemoveItems(std::vector itemList, uint32_t amount, bool stackable); BedItem* getBedBySleeper(uint32_t guid) const; void setBedSleeper(BedItem* bed, uint32_t guid); void removeBedSleeper(uint32_t guid); + + Item* getUniqueItem(uint16_t uniqueId); + bool addUniqueItem(uint16_t uniqueId, Item* item); + void removeUniqueItem(uint16_t uniqueId); + bool reload(ReloadTypes_t reloadType); + Groups groups; Map map; + Mounts mounts; Raids raids; + Quests quests; - protected: + std::forward_list toDecayItems; + + private: 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 playerSpeakToNpc(Player* player, const std::string& text); void checkDecay(); void internalDecayItem(Item* item); - //list of reported rule violations, for correct channel listing - std::unordered_map ruleViolations; - std::unordered_map players; std::unordered_map mappedPlayerNames; std::unordered_map guilds; + std::unordered_map uniqueItems; std::map stages; std::list decayItems[EVENT_DECAY_BUCKETS]; std::list checkCreatureLists[EVENT_CREATURECOUNT]; - std::forward_list toDecayItems; - std::vector ToReleaseCreatures; std::vector ToReleaseItems; @@ -526,6 +540,8 @@ class Game std::map bedSleepersMap; + ModalWindow offlineTrainingWindow { std::numeric_limits::max(), "Choose a Skill", "Please choose a skill:" }; + static constexpr int32_t LIGHT_LEVEL_DAY = 250; static constexpr int32_t LIGHT_LEVEL_NIGHT = 40; static constexpr int32_t SUNSET = 1305; diff --git a/src/globalevent.cpp b/src/globalevent.cpp index 96c9d71..daa233e 100644 --- a/src/globalevent.cpp +++ b/src/globalevent.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,44 +35,47 @@ GlobalEvents::GlobalEvents() : GlobalEvents::~GlobalEvents() { - clear(); + clear(false); } -void GlobalEvents::clearMap(GlobalEventMap& map) +void GlobalEvents::clearMap(GlobalEventMap& map, bool fromLua) { - for (const auto& it : map) { - delete it.second; + for (auto it = map.begin(); it != map.end(); ) { + if (fromLua == it->second.fromLua) { + it = map.erase(it); + } else { + ++it; + } } - map.clear(); } -void GlobalEvents::clear() +void GlobalEvents::clear(bool fromLua) { g_scheduler.stopEvent(thinkEventId); thinkEventId = 0; g_scheduler.stopEvent(timerEventId); timerEventId = 0; - clearMap(thinkMap); - clearMap(serverMap); - clearMap(timerMap); + clearMap(thinkMap, fromLua); + clearMap(serverMap, fromLua); + clearMap(timerMap, fromLua); - scriptInterface.reInitState(); + reInitState(fromLua); } -Event* GlobalEvents::getEvent(const std::string& nodeName) +Event_ptr GlobalEvents::getEvent(const std::string& nodeName) { if (strcasecmp(nodeName.c_str(), "globalevent") != 0) { return nullptr; } - return new GlobalEvent(&scriptInterface); + return Event_ptr(new GlobalEvent(&scriptInterface)); } -bool GlobalEvents::registerEvent(Event* event, const pugi::xml_node&) +bool GlobalEvents::registerEvent(Event_ptr event, const pugi::xml_node&) { - GlobalEvent* globalEvent = static_cast(event); //event is guaranteed to be a GlobalEvent + GlobalEvent_ptr globalEvent{static_cast(event.release())}; //event is guaranteed to be a GlobalEvent if (globalEvent->getEventType() == GLOBALEVENT_TIMER) { - auto result = timerMap.emplace(globalEvent->getName(), globalEvent); + auto result = timerMap.emplace(globalEvent->getName(), std::move(*globalEvent)); if (result.second) { if (timerEventId == 0) { timerEventId = g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, std::bind(&GlobalEvents::timer, this))); @@ -80,12 +83,42 @@ bool GlobalEvents::registerEvent(Event* event, const pugi::xml_node&) return true; } } else if (globalEvent->getEventType() != GLOBALEVENT_NONE) { - auto result = serverMap.emplace(globalEvent->getName(), globalEvent); + auto result = serverMap.emplace(globalEvent->getName(), std::move(*globalEvent)); if (result.second) { return true; } } else { // think event - auto result = thinkMap.emplace(globalEvent->getName(), globalEvent); + auto result = thinkMap.emplace(globalEvent->getName(), std::move(*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; +} + +bool GlobalEvents::registerLuaEvent(GlobalEvent* event) +{ + GlobalEvent_ptr globalEvent{ event }; + if (globalEvent->getEventType() == GLOBALEVENT_TIMER) { + auto result = timerMap.emplace(globalEvent->getName(), std::move(*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(), std::move(*globalEvent)); + if (result.second) { + return true; + } + } else { // think event + auto result = thinkMap.emplace(globalEvent->getName(), std::move(*globalEvent)); if (result.second) { if (thinkEventId == 0) { thinkEventId = g_scheduler.addEvent(createSchedulerTask(SCHEDULER_MINTICKS, std::bind(&GlobalEvents::think, this))); @@ -111,9 +144,9 @@ void GlobalEvents::timer() auto it = timerMap.begin(); while (it != timerMap.end()) { - GlobalEvent* globalEvent = it->second; + GlobalEvent& globalEvent = it->second; - int64_t nextExecutionTime = globalEvent->getNextExecution() - now; + int64_t nextExecutionTime = globalEvent.getNextExecution() - now; if (nextExecutionTime > 0) { if (nextExecutionTime < nextScheduledTime) { nextScheduledTime = nextExecutionTime; @@ -123,7 +156,7 @@ void GlobalEvents::timer() continue; } - if (!globalEvent->executeEvent()) { + if (!globalEvent.executeEvent()) { it = timerMap.erase(it); continue; } @@ -133,7 +166,7 @@ void GlobalEvents::timer() nextScheduledTime = nextExecutionTime; } - globalEvent->setNextExecution(globalEvent->getNextExecution() + nextExecutionTime); + globalEvent.setNextExecution(globalEvent.getNextExecution() + nextExecutionTime); ++it; } @@ -149,10 +182,10 @@ void GlobalEvents::think() int64_t now = OTSYS_TIME(); int64_t nextScheduledTime = std::numeric_limits::max(); - for (const auto& it : thinkMap) { - GlobalEvent* globalEvent = it.second; + for (auto& it : thinkMap) { + GlobalEvent& globalEvent = it.second; - int64_t nextExecutionTime = globalEvent->getNextExecution() - now; + int64_t nextExecutionTime = globalEvent.getNextExecution() - now; if (nextExecutionTime > 0) { if (nextExecutionTime < nextScheduledTime) { nextScheduledTime = nextExecutionTime; @@ -160,16 +193,16 @@ void GlobalEvents::think() continue; } - if (!globalEvent->executeEvent()) { - std::cout << "[Error - GlobalEvents::think] Failed to execute event: " << globalEvent->getName() << std::endl; + if (!globalEvent.executeEvent()) { + std::cout << "[Error - GlobalEvents::think] Failed to execute event: " << globalEvent.getName() << std::endl; } - nextExecutionTime = globalEvent->getInterval(); + nextExecutionTime = globalEvent.getInterval(); if (nextExecutionTime < nextScheduledTime) { nextScheduledTime = nextExecutionTime; } - globalEvent->setNextExecution(globalEvent->getNextExecution() + nextExecutionTime); + globalEvent.setNextExecution(globalEvent.getNextExecution() + nextExecutionTime); } if (nextScheduledTime != std::numeric_limits::max()) { @@ -180,15 +213,16 @@ void GlobalEvents::think() void GlobalEvents::execute(GlobalEvent_t type) const { for (const auto& it : serverMap) { - GlobalEvent* globalEvent = it.second; - if (globalEvent->getEventType() == type) { - globalEvent->executeEvent(); + const GlobalEvent& globalEvent = it.second; + if (globalEvent.getEventType() == type) { + globalEvent.executeEvent(); } } } GlobalEventMap GlobalEvents::getEventMap(GlobalEvent_t type) { + // TODO: This should be better implemented. Maybe have a map for every type. switch (type) { case GLOBALEVENT_NONE: return thinkMap; case GLOBALEVENT_TIMER: return timerMap; @@ -197,8 +231,8 @@ GlobalEventMap GlobalEvents::getEventMap(GlobalEvent_t type) case GLOBALEVENT_RECORD: { GlobalEventMap retMap; for (const auto& it : serverMap) { - if (it.second->getEventType() == type) { - retMap[it.first] = it.second; + if (it.second.getEventType() == type) { + retMap.emplace(it.first, it.second); } } return retMap; @@ -315,7 +349,7 @@ bool GlobalEvent::executeRecord(uint32_t current, uint32_t old) return scriptInterface->callFunction(2); } -bool GlobalEvent::executeEvent() +bool GlobalEvent::executeEvent() const { if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - GlobalEvent::executeEvent] Call stack overflow" << std::endl; diff --git a/src/globalevent.h b/src/globalevent.h index b4ba1cb..6c65482 100644 --- a/src/globalevent.h +++ b/src/globalevent.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,7 +33,8 @@ enum GlobalEvent_t { }; class GlobalEvent; -typedef std::map GlobalEventMap; +using GlobalEvent_ptr = std::unique_ptr; +using GlobalEventMap = std::map; class GlobalEvents final : public BaseEvents { @@ -52,18 +53,20 @@ class GlobalEvents final : public BaseEvents void execute(GlobalEvent_t type) const; GlobalEventMap getEventMap(GlobalEvent_t type); - static void clearMap(GlobalEventMap& map); + static void clearMap(GlobalEventMap& map, bool fromLua); - protected: - std::string getScriptBaseName() const final { + bool registerLuaEvent(GlobalEvent* event); + void clear(bool fromLua) override final; + + private: + std::string getScriptBaseName() const override { return "globalevents"; } - void clear() final; - Event* getEvent(const std::string& nodeName) final; - bool registerEvent(Event* event, const pugi::xml_node& node) final; + Event_ptr getEvent(const std::string& nodeName) override; + bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; - LuaScriptInterface& getScriptInterface() final { + LuaScriptInterface& getScriptInterface() override { return scriptInterface; } LuaScriptInterface scriptInterface; @@ -77,22 +80,31 @@ class GlobalEvent final : public Event public: explicit GlobalEvent(LuaScriptInterface* interface); - bool configureEvent(const pugi::xml_node& node) final; + bool configureEvent(const pugi::xml_node& node) override; bool executeRecord(uint32_t current, uint32_t old); - bool executeEvent(); + bool executeEvent() const; GlobalEvent_t getEventType() const { return eventType; } + void setEventType(GlobalEvent_t type) { + eventType = type; + } const std::string& getName() const { return name; } + void setName(std::string eventName) { + name = eventName; + } uint32_t getInterval() const { return interval; } + void setInterval(uint32_t eventInterval) { + interval |= eventInterval; + } int64_t getNextExecution() const { return nextExecution; @@ -101,10 +113,10 @@ class GlobalEvent final : public Event nextExecution = time; } - protected: + private: GlobalEvent_t eventType = GLOBALEVENT_NONE; - std::string getScriptEventName() const final; + std::string getScriptEventName() const override; std::string name; int64_t nextExecution = 0; diff --git a/src/groups.cpp b/src/groups.cpp index 8b4460d..2908bd0 100644 --- a/src/groups.cpp +++ b/src/groups.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,6 +24,47 @@ #include "pugicast.h" #include "tools.h" +const std::unordered_map ParsePlayerFlagMap = { + {"cannotusecombat", PlayerFlag_CannotUseCombat}, + {"cannotattackplayer", PlayerFlag_CannotAttackPlayer}, + {"cannotattackmonster", PlayerFlag_CannotAttackMonster}, + {"cannotbeattacked", PlayerFlag_CannotBeAttacked}, + {"canconvinceall", PlayerFlag_CanConvinceAll}, + {"cansummonall", PlayerFlag_CanSummonAll}, + {"canillusionall", PlayerFlag_CanIllusionAll}, + {"cansenseinvisibility", PlayerFlag_CanSenseInvisibility}, + {"ignoredbymonsters", PlayerFlag_IgnoredByMonsters}, + {"notgaininfight", PlayerFlag_NotGainInFight}, + {"hasinfinitemana", PlayerFlag_HasInfiniteMana}, + {"hasinfinitesoul", PlayerFlag_HasInfiniteSoul}, + {"hasnoexhaustion", PlayerFlag_HasNoExhaustion}, + {"cannotusespells", PlayerFlag_CannotUseSpells}, + {"cannotpickupitem", PlayerFlag_CannotPickupItem}, + {"canalwayslogin", PlayerFlag_CanAlwaysLogin}, + {"canbroadcast", PlayerFlag_CanBroadcast}, + {"canedithouses", PlayerFlag_CanEditHouses}, + {"cannotbebanned", PlayerFlag_CannotBeBanned}, + {"cannotbepushed", PlayerFlag_CannotBePushed}, + {"hasinfinitecapacity", PlayerFlag_HasInfiniteCapacity}, + {"cannotpushallcreatures", PlayerFlag_CanPushAllCreatures}, + {"cantalkredprivate", PlayerFlag_CanTalkRedPrivate}, + {"cantalkredchannel", PlayerFlag_CanTalkRedChannel}, + {"talkorangehelpchannel", PlayerFlag_TalkOrangeHelpChannel}, + {"notgainexperience", PlayerFlag_NotGainExperience}, + {"notgainmana", PlayerFlag_NotGainMana}, + {"notgainhealth", PlayerFlag_NotGainHealth}, + {"notgainskill", PlayerFlag_NotGainSkill}, + {"setmaxspeed", PlayerFlag_SetMaxSpeed}, + {"specialvip", PlayerFlag_SpecialVIP}, + {"notgenerateloot", PlayerFlag_NotGenerateLoot}, + {"cantalkredchannelanonymous", PlayerFlag_CanTalkRedChannelAnonymous}, + {"ignoreprotectionzone", PlayerFlag_IgnoreProtectionZone}, + {"ignorespellcheck", PlayerFlag_IgnoreSpellCheck}, + {"ignoreweaponcheck", PlayerFlag_IgnoreWeaponCheck}, + {"cannotbemuted", PlayerFlag_CannotBeMuted}, + {"isalwayspremium", PlayerFlag_IsAlwaysPremium} +}; + bool Groups::load() { pugi::xml_document doc; @@ -37,10 +78,24 @@ bool Groups::load() Group group; group.id = pugi::cast(groupNode.attribute("id").value()); group.name = groupNode.attribute("name").as_string(); - group.flags = pugi::cast(groupNode.attribute("flags").value()); group.access = groupNode.attribute("access").as_bool(); group.maxDepotItems = pugi::cast(groupNode.attribute("maxdepotitems").value()); group.maxVipEntries = pugi::cast(groupNode.attribute("maxvipentries").value()); + group.flags = pugi::cast(groupNode.attribute("flags").value()); + if (pugi::xml_node node = groupNode.child("flags")) { + for (auto flagNode : node.children()) { + pugi::xml_attribute attr = flagNode.first_attribute(); + if (!attr || (attr && !attr.as_bool())) { + continue; + } + + auto parseFlag = ParsePlayerFlagMap.find(attr.name()); + if (parseFlag != ParsePlayerFlagMap.end()) { + group.flags |= parseFlag->second; + } + } + } + groups.push_back(group); } return true; diff --git a/src/groups.h b/src/groups.h index e16adfb..3297ed5 100644 --- a/src/groups.h +++ b/src/groups.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/guild.cpp b/src/guild.cpp index e6bed21..6e27c57 100644 --- a/src/guild.cpp +++ b/src/guild.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,11 +28,18 @@ extern Game g_game; void Guild::addMember(Player* player) { membersOnline.push_back(player); + for (Player* member : membersOnline) { + g_game.updatePlayerHelpers(*member); + } } void Guild::removeMember(Player* player) { membersOnline.remove(player); + for (Player* member : membersOnline) { + g_game.updatePlayerHelpers(*member); + } + g_game.updatePlayerHelpers(*player); if (membersOnline.empty()) { g_game.removeGuild(id); @@ -50,6 +57,16 @@ GuildRank* Guild::getRankById(uint32_t rankId) return nullptr; } +const GuildRank* Guild::getRankByName(const std::string& name) const +{ + for (const auto& rank : ranks) { + if (rank.name == name) { + return &rank; + } + } + return nullptr; +} + const GuildRank* Guild::getRankByLevel(uint8_t level) const { for (const auto& rank : ranks) { diff --git a/src/guild.h b/src/guild.h index 3085ec9..e0a6f12 100644 --- a/src/guild.h +++ b/src/guild.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -55,14 +55,26 @@ class Guild memberCount = count; } - GuildRank* getRankById(uint32_t id); + const std::vector& getRanks() const { + return ranks; + } + GuildRank* getRankById(uint32_t rankId); + const GuildRank* getRankByName(const std::string& name) const; const GuildRank* getRankByLevel(uint8_t level) const; - void addRank(uint32_t id, const std::string& name, uint8_t level); + void addRank(uint32_t rankId, const std::string& rankName, uint8_t level); + + const std::string& getMotd() const { + return motd; + } + void setMotd(const std::string& motd) { + this->motd = motd; + } private: std::list membersOnline; std::vector ranks; std::string name; + std::string motd; uint32_t id; uint32_t memberCount = 0; }; diff --git a/src/house.cpp b/src/house.cpp index 29f375c..e37befb 100644 --- a/src/house.cpp +++ b/src/house.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -41,11 +41,11 @@ void House::addTile(HouseTile* tile) void House::setOwner(uint32_t guid, bool updateDatabase/* = true*/, Player* player/* = nullptr*/) { if (updateDatabase && owner != guid) { - Database* db = Database::getInstance(); + 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()); + db.executeQuery(query.str()); } if (isLoaded && owner == guid) { @@ -85,12 +85,26 @@ void House::setOwner(uint32_t guid, bool updateDatabase/* = true*/, Player* play for (Door* door : doorSet) { door->setAccessList(""); } + } else { + std::string strRentPeriod = asLowerCaseString(g_config.getString(ConfigManager::HOUSE_RENT_PERIOD)); + time_t currentTime = time(nullptr); + if (strRentPeriod == "yearly") { + currentTime += 24 * 60 * 60 * 365; + } else if (strRentPeriod == "monthly") { + currentTime += 24 * 60 * 60 * 30; + } else if (strRentPeriod == "weekly") { + currentTime += 24 * 60 * 60 * 7; + } else if (strRentPeriod == "daily") { + currentTime += 24 * 60 * 60; + } else { + currentTime = 0; + } - //reset paid date - paidUntil = 0; - rentWarnings = 0; + paidUntil = currentTime; } + rentWarnings = 0; + if (guid != 0) { std::string name = IOLoginData::getNameByGuid(guid); if (!name.empty()) { @@ -110,9 +124,9 @@ void House::updateDoorDescription() const } else { ss << "It belongs to house '" << houseName << "'. Nobody owns this house."; - const int32_t housePrice = getRent(); + const int32_t housePrice = g_config.getNumber(ConfigManager::HOUSE_PRICE); if (housePrice != -1) { - ss << " It costs " << housePrice * 5 << " gold coins."; + ss << " It costs " << (houseTiles.size() * housePrice) << " gold coins."; } } @@ -216,7 +230,6 @@ bool House::transferToDepot() const transferToDepot(&tmpPlayer); IOLoginData::savePlayer(&tmpPlayer); } - return true; } @@ -245,9 +258,8 @@ bool House::transferToDepot(Player* player) const } for (Item* item : moveItemList) { - g_game.internalMoveItem(item->getParent(), player->getDepotLocker(getTownId(), true), INDEX_WHEREEVER, item, item->getItemCount(), nullptr, FLAG_NOLIMIT); + g_game.internalMoveItem(item->getParent(), player->getInbox(), INDEX_WHEREEVER, item, item->getItemCount(), nullptr, FLAG_NOLIMIT); } - return true; } @@ -396,7 +408,7 @@ bool House::executeTransfer(HouseTransferItem* item, Player* newOwner) void AccessList::parseList(const std::string& list) { playerList.clear(); - guildList.clear(); + guildRankList.clear(); expressionList.clear(); regExList.clear(); this->list = list; @@ -421,7 +433,11 @@ void AccessList::parseList(const std::string& list) std::string::size_type at_pos = line.find("@"); if (at_pos != std::string::npos) { - addGuild(line.substr(at_pos + 1)); + if (at_pos == 0) { + addGuild(line.substr(1)); + } else { + addGuildRank(line.substr(0, at_pos - 1), 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 { @@ -443,11 +459,43 @@ void AccessList::addPlayer(const std::string& name) } } -void AccessList::addGuild(const std::string& name) +namespace { + +const Guild* getGuildByName(const std::string& name) { uint32_t guildId = IOGuild::getGuildIdByName(name); - if (guildId != 0) { - guildList.insert(guildId); + if (guildId == 0) { + return nullptr; + } + + const Guild* guild = g_game.getGuild(guildId); + if (guild) { + return guild; + } + + return IOGuild::loadGuild(guildId); +} + +} + +void AccessList::addGuild(const std::string& name) +{ + const Guild* guild = getGuildByName(name); + if (guild) { + for (const auto& rank : guild->getRanks()) { + guildRankList.insert(rank.id); + } + } +} + +void AccessList::addGuildRank(const std::string& name, const std::string& rankName) +{ + const Guild* guild = getGuildByName(name); + if (guild) { + const GuildRank* rank = guild->getRankByName(rankName); + if (rank) { + guildRankList.insert(rank->id); + } } } @@ -502,8 +550,8 @@ bool AccessList::isInList(const Player* player) return true; } - const Guild* guild = player->getGuild(); - return guild && guildList.find(guild->getId()) != guildList.end(); + const GuildRank* rank = player->getGuildRank(); + return rank && guildRankList.find(rank->id) != guildRankList.end(); } void AccessList::getList(std::string& list) const @@ -667,7 +715,9 @@ void Houses::payHouses(RentPeriod_t rentPeriod) const continue; } - if (g_game.removeMoney(player.getDepotLocker(house->getTownId(), true), house->getRent(), FLAG_NOLIMIT)) { + if (player.getBankBalance() >= rent) { + player.setBankBalance(player.getBankBalance() - rent); + time_t paidUntil = currentTime; switch (rentPeriod) { case RENTPERIOD_DAILY: @@ -718,7 +768,7 @@ void Houses::payHouses(RentPeriod_t rentPeriod) const 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); + g_game.internalAddItem(player.getInbox(), letter, INDEX_WHEREEVER, FLAG_NOLIMIT); house->setPayRentWarnings(house->getPayRentWarnings() + 1); } else { house->setOwner(0, true, &player); diff --git a/src/house.h b/src/house.h index 3d9b543..386ef1f 100644 --- a/src/house.h +++ b/src/house.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,6 +22,7 @@ #include #include +#include #include "container.h" #include "housetile.h" @@ -37,6 +38,7 @@ class AccessList void parseList(const std::string& list); void addPlayer(const std::string& name); void addGuild(const std::string& name); + void addGuildRank(const std::string& name, const std::string& rankName); void addExpression(const std::string& expression); bool isInList(const Player* player); @@ -46,7 +48,7 @@ class AccessList private: std::string list; std::unordered_set playerList; - std::unordered_set guildList; // TODO: include ranks + std::unordered_set guildRankList; std::list expressionList; std::list> regExList; }; @@ -60,10 +62,10 @@ class Door final : public Item Door(const Door&) = delete; Door& operator=(const Door&) = delete; - Door* getDoor() final { + Door* getDoor() override { return this; } - const Door* getDoor() const final { + const Door* getDoor() const override { return this; } @@ -72,8 +74,8 @@ class Door final : public Item } //serialization - Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) final; - void serializeAttr(PropWriteStream&) const final {} + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; + void serializeAttr(PropWriteStream&) const override {} void setDoorId(uint32_t doorId) { setIntAttr(ITEM_ATTRIBUTE_DOORID, doorId); @@ -87,12 +89,11 @@ class Door final : public Item void setAccessList(const std::string& textlist); bool getAccessList(std::string& list) const; - void onRemoved() final; - - protected: - void setHouse(House* house); + void onRemoved() override; private: + void setHouse(House* house); + House* house = nullptr; std::unique_ptr accessList; friend class House; @@ -110,8 +111,8 @@ enum AccessHouseLevel_t { HOUSE_OWNER = 3, }; -typedef std::list HouseTileList; -typedef std::list HouseBedItemList; +using HouseTileList = std::list; +using HouseBedItemList = std::list; class HouseTransferItem final : public Item { @@ -120,12 +121,12 @@ class HouseTransferItem final : public Item explicit HouseTransferItem(House* house) : Item(0), house(house) {} - void onTradeEvent(TradeEvents_t event, Player* owner) final; - bool canTransform() const final { + void onTradeEvent(TradeEvents_t event, Player* owner) override; + bool canTransform() const override { return false; } - protected: + private: House* house; }; @@ -207,7 +208,7 @@ class House HouseTransferItem* getTransferItem(); void resetTransferItem(); - bool executeTransfer(HouseTransferItem* item, Player* player); + bool executeTransfer(HouseTransferItem* item, Player* newOwner); const HouseTileList& getTiles() const { return houseTiles; @@ -217,7 +218,6 @@ class House return doorSet; } - void addBed(BedItem* bed); const HouseBedItemList& getBeds() const { return bedsList; @@ -257,7 +257,7 @@ class House bool isLoaded = false; }; -typedef std::map HouseMap; +using HouseMap = std::map; enum RentPeriod_t { RENTPERIOD_DAILY, diff --git a/src/housetile.cpp b/src/housetile.cpp index f3fccf5..3cf8993 100644 --- a/src/housetile.cpp +++ b/src/housetile.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/housetile.h b/src/housetile.h index 17ef634..fcb6d22 100644 --- a/src/housetile.h +++ b/src/housetile.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,13 +31,13 @@ class HouseTile final : public DynamicTile //cylinder implementations ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const final; + uint32_t flags, Creature* actor = nullptr) const override; Tile* queryDestination(int32_t& index, const Thing& thing, Item** destItem, - uint32_t& flags) final; + uint32_t& flags) override; - void addThing(int32_t index, Thing* thing) final; - void internalAddThing(uint32_t index, Thing* thing) final; + void addThing(int32_t index, Thing* thing) override; + void internalAddThing(uint32_t index, Thing* thing) override; House* getHouse() { return house; diff --git a/src/inbox.cpp b/src/inbox.cpp new file mode 100644 index 0000000..42dff7d --- /dev/null +++ b/src/inbox.cpp @@ -0,0 +1,72 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "inbox.h" +#include "tools.h" + +Inbox::Inbox(uint16_t type) : Container(type, 30, false, true) {} + +ReturnValue Inbox::queryAdd(int32_t, const Thing& thing, uint32_t, + uint32_t flags, Creature*) const +{ + if (!hasBitSet(FLAG_NOLIMIT, flags)) { + return RETURNVALUE_CONTAINERNOTENOUGHROOM; + } + + const Item* item = thing.getItem(); + if (!item) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (item == this) { + return RETURNVALUE_THISISIMPOSSIBLE; + } + + if (!item->isPickupable()) { + return RETURNVALUE_CANNOTPICKUP; + } + + return RETURNVALUE_NOERROR; +} + +void Inbox::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t) +{ + Cylinder* parent = getParent(); + if (parent != nullptr) { + parent->postAddNotification(thing, oldParent, index, LINK_PARENT); + } +} + +void Inbox::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) +{ + Cylinder* parent = getParent(); + if (parent != nullptr) { + parent->postRemoveNotification(thing, newParent, index, LINK_PARENT); + } +} + +Cylinder* Inbox::getParent() const +{ + if (parent) { + return parent->getParent(); + } + return nullptr; +} diff --git a/src/inbox.h b/src/inbox.h new file mode 100644 index 0000000..8ad746a --- /dev/null +++ b/src/inbox.h @@ -0,0 +1,49 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_INBOX_H_C3EF10190329447883B9C3479234EE5C +#define FS_INBOX_H_C3EF10190329447883B9C3479234EE5C + +#include "container.h" + +class Inbox final : public Container +{ + public: + explicit Inbox(uint16_t type); + + //cylinder implementations + ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, + uint32_t flags, Creature* actor = nullptr) const override; + + 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; + + //overrides + bool canRemove() const override { + return false; + } + + Cylinder* getParent() const override; + Cylinder* getRealParent() const override { + return parent; + } +}; + +#endif + diff --git a/src/ioguild.cpp b/src/ioguild.cpp index b4ef9b5..ed48b14 100644 --- a/src/ioguild.cpp +++ b/src/ioguild.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,29 +19,51 @@ #include "otpch.h" -#include "ioguild.h" #include "database.h" +#include "guild.h" +#include "ioguild.h" + +Guild* IOGuild::loadGuild(uint32_t guildId) +{ + Database& db = Database::getInstance(); + std::ostringstream query; + query << "SELECT `name` FROM `guilds` WHERE `id` = " << guildId; + if (DBResult_ptr result = db.storeQuery(query.str())) { + Guild* guild = new Guild(guildId, result->getString("name")); + + 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("id"), result->getString("name"), result->getNumber("level")); + } while (result->next()); + } + return guild; + } + return nullptr; +} uint32_t IOGuild::getGuildIdByName(const std::string& name) { - Database* db = Database::getInstance(); + Database& db = Database::getInstance(); std::ostringstream query; - query << "SELECT `id` FROM `guilds` WHERE `name` = " << db->escapeString(name); + query << "SELECT `id` FROM `guilds` WHERE `name` = " << db.escapeString(name); - DBResult_ptr result = db->storeQuery(query.str()); + DBResult_ptr result = db.storeQuery(query.str()); if (!result) { return 0; } return result->getNumber("id"); } -void IOGuild::getWarList(uint32_t guildId, GuildWarList& guildWarList) +void IOGuild::getWarList(uint32_t guildId, GuildWarVector& guildWarVector) { 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()); + DBResult_ptr result = Database::getInstance().storeQuery(query.str()); if (!result) { return; } @@ -49,9 +71,9 @@ void IOGuild::getWarList(uint32_t guildId, GuildWarList& guildWarList) do { uint32_t guild1 = result->getNumber("guild1"); if (guildId != guild1) { - guildWarList.push_back(guild1); + guildWarVector.push_back(guild1); } else { - guildWarList.push_back(result->getNumber("guild2")); + guildWarVector.push_back(result->getNumber("guild2")); } } while (result->next()); } diff --git a/src/ioguild.h b/src/ioguild.h index e5f058c..66e4339 100644 --- a/src/ioguild.h +++ b/src/ioguild.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,13 +20,15 @@ #ifndef FS_IOGUILD_H_EF9ACEBA0B844C388B70FF52E69F1AFF #define FS_IOGUILD_H_EF9ACEBA0B844C388B70FF52E69F1AFF -typedef std::vector GuildWarList; +class Guild; +using GuildWarVector = std::vector; class IOGuild { public: + static Guild* loadGuild(uint32_t guildId); static uint32_t getGuildIdByName(const std::string& name); - static void getWarList(uint32_t guildId, GuildWarList& guildWarList); + static void getWarList(uint32_t guildId, GuildWarVector& guildWarVector); }; #endif diff --git a/src/iologindata.cpp b/src/iologindata.cpp index 74c6f88..040688c 100644 --- a/src/iologindata.cpp +++ b/src/iologindata.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,13 +31,14 @@ 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()); + query << "SELECT `id`, `name`, `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("id"); + account.name = result->getString("name"); account.accountType = static_cast(result->getNumber("type")); account.premiumDays = result->getNumber("premdays"); account.lastDay = result->getNumber("lastday"); @@ -48,16 +49,45 @@ 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()); + return Database::getInstance().executeQuery(query.str()); } -bool IOLoginData::loginserverAuthentication(uint32_t accountNumber, const std::string& password, Account& account) +std::string decodeSecret(const std::string& secret) { - Database* db = Database::getInstance(); + // simple base32 decoding + std::string key; + key.reserve(10); + + uint32_t buffer = 0, left = 0; + for (const auto& ch : secret) { + buffer <<= 5; + if (ch >= 'A' && ch <= 'Z') { + buffer |= (ch & 0x1F) - 1; + } else if (ch >= '2' && ch <= '7') { + buffer |= ch - 24; + } else { + // if a key is broken, return empty and the comparison + // will always be false since the token must not be empty + return {}; + } + + left += 5; + if (left >= 8) { + left -= 8; + key.push_back(static_cast(buffer >> left)); + } + } + + return key; +} + +bool IOLoginData::loginserverAuthentication(const std::string& name, 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()); + query << "SELECT `id`, `name`, `password`, `secret`, `type`, `premdays`, `lastday` FROM `accounts` WHERE `name` = " << db.escapeString(name); + DBResult_ptr result = db.storeQuery(query.str()); if (!result) { return false; } @@ -67,13 +97,15 @@ bool IOLoginData::loginserverAuthentication(uint32_t accountNumber, const std::s } account.id = result->getNumber("id"); + account.name = result->getString("name"); + account.key = decodeSecret(result->getString("secret")); account.accountType = static_cast(result->getNumber("type")); account.premiumDays = result->getNumber("premdays"); account.lastDay = result->getNumber("lastday"); query.str(std::string()); query << "SELECT `name`, `deletion` FROM `players` WHERE `account_id` = " << account.id; - result = db->storeQuery(query.str()); + result = db.storeQuery(query.str()); if (result) { do { if (result->getNumber("deletion") == 0) { @@ -85,17 +117,29 @@ bool IOLoginData::loginserverAuthentication(uint32_t accountNumber, const std::s return true; } -uint32_t IOLoginData::gameworldAuthentication(uint32_t accountNumber, const std::string& password, std::string& characterName) +uint32_t IOLoginData::gameworldAuthentication(const std::string& accountName, const std::string& password, std::string& characterName, std::string& token, uint32_t tokenTime) { - Database* db = Database::getInstance(); + Database& db = Database::getInstance(); std::ostringstream query; - query << "SELECT `id`, `password` FROM `accounts` WHERE `id` = " << accountNumber; - DBResult_ptr result = db->storeQuery(query.str()); + query << "SELECT `id`, `password`, `secret` FROM `accounts` WHERE `name` = " << db.escapeString(accountName); + DBResult_ptr result = db.storeQuery(query.str()); if (!result) { return 0; } + std::string secret = decodeSecret(result->getString("secret")); + if (!secret.empty()) { + if (token.empty()) { + return 0; + } + + bool tokenValid = token == generateToken(secret, tokenTime) || token == generateToken(secret, tokenTime - 1) || token == generateToken(secret, tokenTime + 1); + if (!tokenValid) { + return 0; + } + } + if (transformToSHA1(password) != result->getString("password")) { return 0; } @@ -103,8 +147,8 @@ uint32_t IOLoginData::gameworldAuthentication(uint32_t accountNumber, const std: uint32_t accountId = result->getNumber("id"); query.str(std::string()); - query << "SELECT `account_id`, `name`, `deletion` FROM `players` WHERE `name` = " << db->escapeString(characterName); - result = db->storeQuery(query.str()); + query << "SELECT `account_id`, `name`, `deletion` FROM `players` WHERE `name` = " << db.escapeString(characterName); + result = db.storeQuery(query.str()); if (!result) { return 0; } @@ -120,7 +164,7 @@ 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()); + DBResult_ptr result = Database::getInstance().storeQuery(query.str()); if (!result) { return ACCOUNT_TYPE_NORMAL; } @@ -131,7 +175,7 @@ void IOLoginData::setAccountType(uint32_t accountId, AccountType_t accountType) { std::ostringstream query; query << "UPDATE `accounts` SET `type` = " << static_cast(accountType) << " WHERE `id` = " << accountId; - Database::getInstance()->executeQuery(query.str()); + Database::getInstance().executeQuery(query.str()); } void IOLoginData::updateOnlineStatus(uint32_t guid, bool login) @@ -146,20 +190,20 @@ void IOLoginData::updateOnlineStatus(uint32_t guid, bool login) } else { query << "DELETE FROM `players_online` WHERE `player_id` = " << guid; } - Database::getInstance()->executeQuery(query.str()); + Database::getInstance().executeQuery(query.str()); } bool IOLoginData::preloadPlayer(Player* player, const std::string& name) { - Database* db = Database::getInstance(); + 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()); + query << " FROM `players` WHERE `name` = " << db.escapeString(name); + DBResult_ptr result = db.storeQuery(query.str()); if (!result) { return false; } @@ -187,17 +231,18 @@ bool IOLoginData::preloadPlayer(Player* player, const std::string& name) bool IOLoginData::loadPlayerById(Player* player, uint32_t id) { + 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`, `lookaddons`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `stamina`, `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())); + 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`, `lookaddons`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `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`, `direction` FROM `players` WHERE `id` = " << id; + return loadPlayer(player, db.storeQuery(query.str())); } bool IOLoginData::loadPlayerByName(Player* player, const std::string& name) { - Database* db = Database::getInstance(); + 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`, `lookaddons`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `stamina`, `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())); + 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`, `lookaddons`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `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`, `direction` FROM `players` WHERE `name` = " << db.escapeString(name); + return loadPlayer(player, db.storeQuery(query.str())); } bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) @@ -206,7 +251,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) return false; } - Database* db = Database::getInstance(); + Database& db = Database::getInstance(); uint32_t accno = result->getNumber("account_id"); Account acc = loadAccount(accno); @@ -252,7 +297,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) } player->soul = result->getNumber("soul"); - player->capacity = std::max(400, result->getNumber("cap")) * 100; + player->capacity = result->getNumber("cap") * 100; player->blessings = result->getNumber("blessings"); unsigned long conditionsSize; @@ -298,17 +343,20 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) player->defaultOutfit.lookFeet = result->getNumber("lookfeet"); player->defaultOutfit.lookAddons = result->getNumber("lookaddons"); player->currentOutfit = player->defaultOutfit; + player->direction = static_cast (result->getNumber("direction")); if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { - player->playerKillerEnd = result->getNumber("skulltime"); + const time_t skullSeconds = result->getNumber("skulltime") - time(nullptr); + if (skullSeconds > 0) { + //ensure that we round up the number of ticks + player->skullTicks = (skullSeconds + 2); - uint16_t skull = result->getNumber("skull"); - if (skull == SKULL_RED) { - player->skull = SKULL_RED; - } - - if (player->playerKillerEnd == 0) { - player->skull = SKULL_NONE; + uint16_t skull = result->getNumber("skull"); + if (skull == SKULL_RED) { + player->skull = SKULL_RED; + } else if (skull == SKULL_BLACK) { + player->skull = SKULL_BLACK; + } } } @@ -319,6 +367,9 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) player->lastLoginSaved = result->getNumber("lastlogin"); player->lastLogout = result->getNumber("lastlogout"); + player->offlineTrainingTime = result->getNumber("offlinetraining_time") * 1000; + player->offlineTrainingSkill = result->getNumber("offlinetraining_skill"); + Town* town = g_game.map.towns.getTown(result->getNumber("town_id")); if (!town) { std::cout << "[Error - IOLoginData::loadPlayer] " << player->name << " has Town ID " << result->getNumber("town_id") << " which doesn't exist" << std::endl; @@ -351,38 +402,16 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) } 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("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()))) { + if ((result = db.storeQuery(query.str()))) { uint32_t guildId = result->getNumber("guild_id"); uint32_t playerRankId = result->getNumber("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("id"), result->getString("name"), result->getNumber("level")); - } while (result->next()); - } - } + guild = IOGuild::loadGuild(guildId); + g_game.addGuild(guild); } if (guild) { @@ -392,7 +421,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) query.str(std::string()); query << "SELECT `id`, `name`, `level` FROM `guild_ranks` WHERE `id` = " << playerRankId; - if ((result = db->storeQuery(query.str()))) { + if ((result = db.storeQuery(query.str()))) { guild->addRank(result->getNumber("id"), result->getString("name"), result->getNumber("level")); } @@ -404,11 +433,11 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) player->guildRank = rank; - IOGuild::getWarList(guildId, player->guildWarList); + IOGuild::getWarList(guildId, player->guildWarVector); query.str(std::string()); query << "SELECT COUNT(*) AS `members` FROM `guild_membership` WHERE `guild_id` = " << guildId; - if ((result = db->storeQuery(query.str()))) { + if ((result = db.storeQuery(query.str()))) { guild->setMemberCount(result->getNumber("members")); } } @@ -416,7 +445,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) query.str(std::string()); query << "SELECT `player_id`, `name` FROM `player_spells` WHERE `player_id` = " << player->getGUID(); - if ((result = db->storeQuery(query.str()))) { + if ((result = db.storeQuery(query.str()))) { do { player->learnedInstantSpellList.emplace_front(result->getString("name")); } while (result->next()); @@ -427,7 +456,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) 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()))) { + if ((result = db.storeQuery(query.str()))) { loadItems(itemMap, result); for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { @@ -455,7 +484,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) 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()))) { + if ((result = db.storeQuery(query.str()))) { loadItems(itemMap, result); for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { @@ -464,15 +493,9 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) 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; - } - } + DepotChest* depotChest = player->getDepotChest(pid, true); + if (depotChest) { + depotChest->internalAddThing(item); } } else { ItemMap::const_iterator it2 = itemMap.find(pid); @@ -488,19 +511,49 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) } } + //load inbox items + itemMap.clear(); + + query.str(std::string()); + query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_inboxitems` 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& pair = it->second; + Item* item = pair.first; + int32_t pid = pair.second; + + if (pid >= 0 && pid < 100) { + player->getInbox()->internalAddThing(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 storage map query.str(std::string()); query << "SELECT `key`, `value` FROM `player_storage` WHERE `player_id` = " << player->getGUID(); - if ((result = db->storeQuery(query.str()))) { + if ((result = db.storeQuery(query.str()))) { do { - player->addStorageValue(result->getNumber("key"), result->getNumber("value")); + player->addStorageValue(result->getNumber("key"), result->getNumber("value"), true); } 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()))) { + if ((result = db.storeQuery(query.str()))) { do { player->addVIPInternal(result->getNumber("player_id")); } while (result->next()); @@ -516,12 +569,12 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, { std::ostringstream ss; - typedef std::pair containerBlock; - std::list queue; + using ContainerBlock = std::pair; + std::list queue; int32_t runningId = 100; - Database* db = Database::getInstance(); + Database& db = Database::getInstance(); for (const auto& it : itemList) { int32_t pid = it.first; Item* item = it.second; @@ -533,7 +586,7 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, size_t attributesSize; const char* attributes = propWriteStream.getStream(attributesSize); - ss << player->getGUID() << ',' << pid << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db->escapeBlob(attributes, attributesSize); + ss << player->getGUID() << ',' << pid << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db.escapeBlob(attributes, attributesSize); if (!query_insert.addRow(ss)) { return false; } @@ -544,7 +597,7 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, } while (!queue.empty()) { - const containerBlock& cb = queue.front(); + const ContainerBlock& cb = queue.front(); Container* container = cb.first; int32_t parentId = cb.second; queue.pop_front(); @@ -563,7 +616,7 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, size_t attributesSize; const char* attributes = propWriteStream.getStream(attributesSize); - ss << player->getGUID() << ',' << parentId << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db->escapeBlob(attributes, attributesSize); + ss << player->getGUID() << ',' << parentId << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db.escapeBlob(attributes, attributesSize); if (!query_insert.addRow(ss)) { return false; } @@ -578,11 +631,11 @@ bool IOLoginData::savePlayer(Player* player) player->changeHealth(1); } - Database* db = Database::getInstance(); + Database& db = Database::getInstance(); std::ostringstream query; query << "SELECT `save` FROM `players` WHERE `id` = " << player->getGUID(); - DBResult_ptr result = db->storeQuery(query.str()); + DBResult_ptr result = db.storeQuery(query.str()); if (!result) { return false; } @@ -590,7 +643,7 @@ bool IOLoginData::savePlayer(Player* player) if (result->getNumber("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()); + return db.executeQuery(query.str()); } //serialize conditions @@ -633,7 +686,7 @@ bool IOLoginData::savePlayer(Player* player) query << "`posz` = " << loginPosition.getZ() << ','; query << "`cap` = " << (player->capacity / 100) << ','; - query << "`sex` = " << player->sex << ','; + query << "`sex` = " << static_cast(player->sex) << ','; if (player->lastLoginSaved != 0) { query << "`lastlogin` = " << player->lastLoginSaved << ','; @@ -643,21 +696,29 @@ bool IOLoginData::savePlayer(Player* player) query << "`lastip` = " << player->lastIP << ','; } - query << "`conditions` = " << db->escapeBlob(conditions, conditionsSize) << ','; + query << "`conditions` = " << db.escapeBlob(conditions, conditionsSize) << ','; if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { - query << "`skulltime` = " << player->getPlayerKillerEnd() << ','; + int64_t skullTime = 0; + + if (player->skullTicks > 0) { + skullTime = time(nullptr) + player->skullTicks; + } + query << "`skulltime` = " << skullTime << ','; Skulls_t skull = SKULL_NONE; if (player->skull == SKULL_RED) { skull = SKULL_RED; + } else if (player->skull == SKULL_BLACK) { + skull = SKULL_BLACK; } - - query << "`skull` = " << static_cast(skull) << ','; + query << "`skull` = " << static_cast(skull) << ','; } query << "`lastlogout` = " << player->getLastLogout() << ','; query << "`balance` = " << player->bankBalance << ','; + query << "`offlinetraining_time` = " << player->getOfflineTrainingTime() / 1000 << ','; + query << "`offlinetraining_skill` = " << player->getOfflineTrainingSkill() << ','; query << "`stamina` = " << player->getStaminaMinutes() << ','; query << "`skill_fist` = " << player->skills[SKILL_FIST].level << ','; @@ -674,6 +735,7 @@ bool IOLoginData::savePlayer(Player* player) 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 << ','; + query << "`direction` = " << static_cast (player->getDirection()) << ','; if (!player->isOffline()) { query << "`onlinetime` = `onlinetime` + " << (time(nullptr) - player->lastLoginSaved) << ','; @@ -686,14 +748,14 @@ bool IOLoginData::savePlayer(Player* player) return false; } - if (!db->executeQuery(query.str())) { + 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())) { + if (!db.executeQuery(query.str())) { return false; } @@ -701,7 +763,7 @@ bool IOLoginData::savePlayer(Player* player) DBInsert spellsQuery("INSERT INTO `player_spells` (`player_id`, `name` ) VALUES "); for (const std::string& spellName : player->learnedInstantSpellList) { - query << player->getGUID() << ',' << db->escapeString(spellName); + query << player->getGUID() << ',' << db.escapeString(spellName); if (!spellsQuery.addRow(query)) { return false; } @@ -711,31 +773,9 @@ bool IOLoginData::savePlayer(Player* player) 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())) { + if (!db.executeQuery(query.str())) { return false; } @@ -753,28 +793,51 @@ bool IOLoginData::savePlayer(Player* player) return false; } - //save depot items - query.str(std::string()); - query << "DELETE FROM `player_depotitems` WHERE `player_id` = " << player->getGUID(); + if (player->lastDepotId != -1) { + //save depot items + query.str(std::string()); + query << "DELETE FROM `player_depotitems` WHERE `player_id` = " << player->getGUID(); - if (!db->executeQuery(query.str())) { + 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->depotChests) { + DepotChest* depotChest = it.second; + for (Item* item : depotChest->getItemList()) { + itemList.emplace_back(it.first, item); + } + } + + if (!saveItems(player, itemList, depotQuery, propWriteStream)) { + return false; + } + } + + //save inbox items + query.str(std::string()); + query << "DELETE FROM `player_inboxitems` 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 "); + DBInsert inboxQuery("INSERT INTO `player_inboxitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); itemList.clear(); - for (const auto& it : player->depotLockerMap) { - itemList.emplace_back(it.first, it.second); + for (Item* item : player->getInbox()->getItemList()) { + itemList.emplace_back(0, item); } - if (!saveItems(player, itemList, depotQuery, propWriteStream)) { + if (!saveItems(player, itemList, inboxQuery, propWriteStream)) { return false; } query.str(std::string()); query << "DELETE FROM `player_storage` WHERE `player_id` = " << player->getGUID(); - if (!db->executeQuery(query.str())) { + if (!db.executeQuery(query.str())) { return false; } @@ -802,7 +865,7 @@ 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()); + DBResult_ptr result = Database::getInstance().storeQuery(query.str()); if (!result) { return std::string(); } @@ -811,11 +874,11 @@ std::string IOLoginData::getNameByGuid(uint32_t guid) uint32_t IOLoginData::getGuidByName(const std::string& name) { - Database* db = Database::getInstance(); + Database& db = Database::getInstance(); std::ostringstream query; - query << "SELECT `id` FROM `players` WHERE `name` = " << db->escapeString(name); - DBResult_ptr result = db->storeQuery(query.str()); + query << "SELECT `id` FROM `players` WHERE `name` = " << db.escapeString(name); + DBResult_ptr result = db.storeQuery(query.str()); if (!result) { return 0; } @@ -824,11 +887,11 @@ uint32_t IOLoginData::getGuidByName(const std::string& name) bool IOLoginData::getGuidByNameEx(uint32_t& guid, bool& specialVip, std::string& name) { - Database* db = Database::getInstance(); + 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()); + 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; } @@ -850,12 +913,12 @@ bool IOLoginData::getGuidByNameEx(uint32_t& guid, bool& specialVip, std::string& bool IOLoginData::formatPlayerName(std::string& name) { - Database* db = Database::getInstance(); + Database& db = Database::getInstance(); std::ostringstream query; - query << "SELECT `name` FROM `players` WHERE `name` = " << db->escapeString(name); + query << "SELECT `name` FROM `players` WHERE `name` = " << db.escapeString(name); - DBResult_ptr result = db->storeQuery(query.str()); + DBResult_ptr result = db.storeQuery(query.str()); if (!result) { return false; } @@ -894,16 +957,16 @@ 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()); + Database::getInstance().executeQuery(query.str()); } bool IOLoginData::hasBiddedOnHouse(uint32_t guid) { - Database* db = Database::getInstance(); + 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; + return db.storeQuery(query.str()).get() != nullptr; } std::forward_list IOLoginData::getVIPEntries(uint32_t accountId) @@ -911,46 +974,58 @@ std::forward_list IOLoginData::getVIPEntries(uint32_t accountId) std::forward_list 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; + query << "SELECT `player_id`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `name`, `description`, `icon`, `notify` FROM `account_viplist` WHERE `account_id` = " << accountId; - DBResult_ptr result = Database::getInstance()->storeQuery(query.str()); + DBResult_ptr result = Database::getInstance().storeQuery(query.str()); if (result) { do { entries.emplace_front( result->getNumber("player_id"), - result->getString("name") + result->getString("name"), + result->getString("description"), + result->getNumber("icon"), + result->getNumber("notify") != 0 ); } while (result->next()); } return entries; } -void IOLoginData::addVIPEntry(uint32_t accountId, uint32_t guid) +void IOLoginData::addVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, bool notify) { - Database* db = Database::getInstance(); + Database& db = Database::getInstance(); std::ostringstream query; - query << "INSERT INTO `account_viplist` (`account_id`, `player_id`) VALUES (" << accountId << ',' << guid << ')'; - db->executeQuery(query.str()); + query << "INSERT INTO `account_viplist` (`account_id`, `player_id`, `description`, `icon`, `notify`) VALUES (" << accountId << ',' << guid << ',' << db.escapeString(description) << ',' << icon << ',' << notify << ')'; + db.executeQuery(query.str()); +} + +void IOLoginData::editVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, bool notify) +{ + Database& db = Database::getInstance(); + + std::ostringstream query; + query << "UPDATE `account_viplist` SET `description` = " << db.escapeString(description) << ", `icon` = " << icon << ", `notify` = " << notify << " WHERE `account_id` = " << accountId << " AND `player_id` = " << 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()); + 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()); + 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()); + Database::getInstance().executeQuery(query.str()); } diff --git a/src/iologindata.h b/src/iologindata.h index d207507..446e361 100644 --- a/src/iologindata.h +++ b/src/iologindata.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,7 +24,7 @@ #include "player.h" #include "database.h" -typedef std::list> ItemBlockList; +using ItemBlockList = std::list>; class IOLoginData { @@ -32,8 +32,8 @@ class IOLoginData 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 bool loginserverAuthentication(const std::string& name, const std::string& password, Account& account); + static uint32_t gameworldAuthentication(const std::string& accountName, const std::string& password, std::string& characterName, std::string& token, uint32_t tokenTime); static AccountType_t getAccountType(uint32_t accountId); static void setAccountType(uint32_t accountId, AccountType_t accountType); @@ -52,17 +52,18 @@ class IOLoginData static bool hasBiddedOnHouse(uint32_t guid); static std::forward_list getVIPEntries(uint32_t accountId); - static void addVIPEntry(uint32_t accountId, uint32_t guid); + static void addVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, bool notify); + static void editVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, bool notify); 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> ItemMap; + private: + using ItemMap = std::map>; static void loadItems(ItemMap& itemMap, DBResult_ptr result); - static bool saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& stream); + static bool saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream); }; #endif diff --git a/src/iomap.cpp b/src/iomap.cpp index 197bf97..9c40761 100644 --- a/src/iomap.cpp +++ b/src/iomap.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -66,23 +66,14 @@ Tile* IOMap::createTile(Item*& ground, Item* item, uint16_t x, uint16_t y, uint8 return tile; } -bool IOMap::loadMap(Map* map, const std::string& identifier) +bool IOMap::loadMap(Map* map, const std::string& fileName) { int64_t start = OTSYS_TIME(); + OTB::Loader loader{fileName, OTB::Identifier{{'O', 'T', 'B', 'M'}}}; + auto& root = loader.parseTree(); - 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)) { + if (!loader.getProps(root, propStream)) { setLastErrorString("Could not read root property."); return false; } @@ -94,7 +85,7 @@ bool IOMap::loadMap(Map* map, const std::string& identifier) } uint32_t headerVersion = root_header.version; - if (headerVersion <= 0) { + 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. @@ -107,17 +98,66 @@ bool IOMap::loadMap(Map* map, const std::string& identifier) return false; } + if (root_header.majorVersionItems < 3) { + setLastErrorString("This map need to be upgraded by using the latest map editor version to be able to load correctly."); + return false; + } + + if (root_header.majorVersionItems > Item::items.majorVersion) { + setLastErrorString("The map was saved with a different items.otb version, an upgraded items.otb is required."); + return false; + } + + if (root_header.minorVersionItems < CLIENT_VERSION_810) { + setLastErrorString("This map needs to be updated."); + return false; + } + + if (root_header.minorVersionItems > Item::items.minorVersion) { + std::cout << "[Warning - IOMap::loadMap] This map needs an updated items.otb." << std::endl; + } + 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) { + if (root.children.size() != 1 || root.children[0].type != OTBM_MAP_DATA) { setLastErrorString("Could not read data node."); return false; } - if (!f.getProps(nodeMap, propStream)) { + auto& mapNode = root.children[0]; + if (!parseMapDataAttributes(loader, mapNode, *map, fileName)) { + return false; + } + + for (auto& mapDataNode : mapNode.children) { + if (mapDataNode.type == OTBM_TILE_AREA) { + if (!parseTileArea(loader, mapDataNode, *map)) { + return false; + } + } else if (mapDataNode.type == OTBM_TOWNS) { + if (!parseTowns(loader, mapDataNode, *map)) { + return false; + } + } else if (mapDataNode.type == OTBM_WAYPOINTS && headerVersion > 1) { + if (!parseWaypoints(loader, mapDataNode, *map)) { + return false; + } + } else { + setLastErrorString("Unknown map node."); + return false; + } + } + + std::cout << "> Map loading time: " << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl; + return true; +} + +bool IOMap::parseMapDataAttributes(OTB::Loader& loader, const OTB::Node& mapNode, Map& map, const std::string& fileName) +{ + PropStream propStream; + if (!loader.getProps(mapNode, propStream)) { setLastErrorString("Could not read map data attributes."); return false; } @@ -141,8 +181,8 @@ bool IOMap::loadMap(Map* map, const std::string& identifier) return false; } - map->spawnfile = identifier.substr(0, identifier.rfind('/') + 1); - map->spawnfile += tmp; + map.spawnfile = fileName.substr(0, fileName.rfind('/') + 1); + map.spawnfile += tmp; break; case OTBM_ATTR_EXT_HOUSE_FILE: @@ -151,8 +191,8 @@ bool IOMap::loadMap(Map* map, const std::string& identifier) return false; } - map->housefile = identifier.substr(0, identifier.rfind('/') + 1); - map->housefile += tmp; + map.housefile = fileName.substr(0, fileName.rfind('/') + 1); + map.housefile += tmp; break; default: @@ -160,172 +200,104 @@ bool IOMap::loadMap(Map* map, const std::string& identifier) return false; } } + return true; +} - NODE nodeMapData = f.getChildNode(nodeMap, type); - while (nodeMapData != NO_NODE) { - if (f.getError() != ERROR_NONE) { - setLastErrorString("Invalid map node."); +bool IOMap::parseTileArea(OTB::Loader& loader, const OTB::Node& tileAreaNode, Map& map) +{ + PropStream propStream; + if (!loader.getProps(tileAreaNode, 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; + + for (auto& tileNode : tileAreaNode.children) { + if (tileNode.type != OTBM_TILE && tileNode.type != OTBM_HOUSETILE) { + setLastErrorString("Unknown tile node."); return false; } - if (type == OTBM_TILE_AREA) { - if (!f.getProps(nodeMapData, propStream)) { - setLastErrorString("Invalid map node."); + if (!loader.getProps(tileNode, 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 (tileNode.type == OTBM_HOUSETILE) { + uint32_t houseId; + if (!propStream.read(houseId)) { + std::ostringstream ss; + ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Could not read house id."; + setLastErrorString(ss.str()); return false; } - OTBM_Destination_coords area_coord; - if (!propStream.read(area_coord)) { - setLastErrorString("Invalid map node."); + 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; } - uint16_t base_x = area_coord.x; - uint16_t base_y = area_coord.y; - uint16_t z = area_coord.z; + tile = new HouseTile(x, y, z, house); + house->addTile(static_cast(tile)); + isHouseTile = true; + } - 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(houseId)) { + uint8_t attribute; + //read tile attributes + while (propStream.read(attribute)) { + switch (attribute) { + case OTBM_ATTR_TILE_FLAGS: { + uint32_t flags; + if (!propStream.read(flags)) { std::ostringstream ss; - ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Could not read house id."; + ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to read tile flags."; 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; + 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; } - tile = new HouseTile(x, y, z, house); - house->addTile(static_cast(tile)); - isHouseTile = true; + if ((flags & OTBM_TILEFLAG_NOLOGOUT) != 0) { + tileflags |= TILESTATE_NOLOGOUT; + } + break; } - //read tile attributes - while (propStream.read(attribute)) { - switch (attribute) { - case OTBM_ATTR_TILE_FLAGS: { - uint32_t flags; - if (!propStream.read(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); + 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."; @@ -333,19 +305,11 @@ bool IOMap::loadMap(Map* map, const std::string& identifier) 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; + 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) { + if (item->getItemCount() == 0) { item->setItemCount(1); } @@ -363,100 +327,156 @@ bool IOMap::loadMap(Map* map, const std::string& identifier) item->setLoadedFromMap(true); } } - - nodeItem = f.getNextNode(nodeItem, type); + break; } - if (!tile) { - tile = createTile(ground_item, nullptr, x, y, z); - } - - tile->setFlag(static_cast(tileflags)); - - map->setTile(x, y, z, tile); - - nodeTile = f.getNextNode(nodeTile, type); + default: + std::ostringstream ss; + ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Unknown tile attribute."; + setLastErrorString(ss.str()); + return false; } - } 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(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); + for (auto& itemNode : tileNode.children) { + if (itemNode.type != OTBM_ITEM) { + std::ostringstream ss; + ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Unknown node type."; + setLastErrorString(ss.str()); + return false; } - } 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); + PropStream stream; + if (!loader.getProps(itemNode, stream)) { + setLastErrorString("Invalid item node."); + return false; } - } else { - setLastErrorString("Unknown map node."); + + 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(loader, itemNode, 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); + } + } + } + + if (!tile) { + tile = createTile(ground_item, nullptr, x, y, z); + } + + tile->setFlag(static_cast(tileflags)); + + map.setTile(x, y, z, tile); + } + return true; +} + +bool IOMap::parseTowns(OTB::Loader& loader, const OTB::Node& townsNode, Map& map) +{ + for (auto& townNode : townsNode.children) { + PropStream propStream; + if (townNode.type != OTBM_TOWN) { + setLastErrorString("Unknown town node."); return false; } - nodeMapData = f.getNextNode(nodeMapData, type); - } + if (!loader.getProps(townNode, propStream)) { + setLastErrorString("Could not read town data."); + return false; + } - std::cout << "> Map loading time: " << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl; + uint32_t townId; + if (!propStream.read(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)); + } return true; } + + +bool IOMap::parseWaypoints(OTB::Loader& loader, const OTB::Node& waypointsNode, Map& map) +{ + PropStream propStream; + for (auto& node : waypointsNode.children) { + if (node.type != OTBM_WAYPOINT) { + setLastErrorString("Unknown waypoint node."); + return false; + } + + if (!loader.getProps(node, 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); + } + return true; +} + diff --git a/src/iomap.h b/src/iomap.h index 5062e98..a00d8a4 100644 --- a/src/iomap.h +++ b/src/iomap.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,7 +33,7 @@ enum OTBM_AttrTypes_t { OTBM_ATTR_EXT_FILE = 2, OTBM_ATTR_TILE_FLAGS = 3, OTBM_ATTR_ACTION_ID = 4, - OTBM_ATTR_MOVEMENT_ID = 5, + OTBM_ATTR_UNIQUE_ID = 5, OTBM_ATTR_TEXT = 6, OTBM_ATTR_DESC = 7, OTBM_ATTR_TELE_DEST = 8, @@ -51,12 +51,6 @@ enum OTBM_AttrTypes_t { 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 { @@ -82,8 +76,7 @@ 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, + OTBM_TILEFLAG_PVPZONE = 1 << 4 }; #pragma pack(1) @@ -114,7 +107,7 @@ 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); + bool loadMap(Map* map, const std::string& fileName); /* Load the spawns * \param map pointer to the Map class @@ -154,7 +147,11 @@ class IOMap errorString = error; } - protected: + private: + bool parseMapDataAttributes(OTB::Loader& loader, const OTB::Node& mapNode, Map& map, const std::string& fileName); + bool parseWaypoints(OTB::Loader& loader, const OTB::Node& waypointsNode, Map& map); + bool parseTowns(OTB::Loader& loader, const OTB::Node& townsNode, Map& map); + bool parseTileArea(OTB::Loader& loader, const OTB::Node& tileAreaNode, Map& map); std::string errorString; }; diff --git a/src/iomapserialize.cpp b/src/iomapserialize.cpp index a6a5b9d..af606c2 100644 --- a/src/iomapserialize.cpp +++ b/src/iomapserialize.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,7 +29,7 @@ void IOMapSerialize::loadHouseItems(Map* map) { int64_t start = OTSYS_TIME(); - DBResult_ptr result = Database::getInstance()->storeQuery("SELECT `data` FROM `tile_store`"); + DBResult_ptr result = Database::getInstance().storeQuery("SELECT `data` FROM `tile_store`"); if (!result) { return; } @@ -67,7 +67,7 @@ void IOMapSerialize::loadHouseItems(Map* map) bool IOMapSerialize::saveHouseItems() { int64_t start = OTSYS_TIME(); - Database* db = Database::getInstance(); + Database& db = Database::getInstance(); std::ostringstream query; //Start the transaction @@ -77,7 +77,7 @@ bool IOMapSerialize::saveHouseItems() } //clear old tile data - if (!db->executeQuery("DELETE FROM `tile_store`")) { + if (!db.executeQuery("DELETE FROM `tile_store`")) { return false; } @@ -93,7 +93,7 @@ bool IOMapSerialize::saveHouseItems() size_t attributesSize; const char* attributes = stream.getStream(attributesSize); if (attributesSize > 0) { - query << house->getId() << ',' << db->escapeBlob(attributes, attributesSize); + query << house->getId() << ',' << db.escapeBlob(attributes, attributesSize); if (!stmt.addRow(query)) { return false; } @@ -144,7 +144,7 @@ bool IOMapSerialize::loadItem(PropStream& propStream, Cylinder* parent) } const ItemType& iType = Item::items[id]; - if (iType.moveable || !tile) { + if (iType.moveable || iType.forceSerialize || !tile) { //create a new item Item* item = Item::CreateItem(id); if (item) { @@ -247,7 +247,7 @@ void IOMapSerialize::saveTile(PropWriteStream& stream, const Tile* tile) 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())) { + if (!(it.moveable || it.forceSerialize || item->getDoor() || (item->getContainer() && !item->getContainer()->empty()) || it.canWriteText || item->getBed())) { continue; } @@ -270,9 +270,9 @@ void IOMapSerialize::saveTile(PropWriteStream& stream, const Tile* tile) bool IOMapSerialize::loadHouseInfo() { - Database* db = Database::getInstance(); + Database& db = Database::getInstance(); - DBResult_ptr result = db->storeQuery("SELECT `id`, `owner`, `paid`, `warnings` FROM `houses`"); + DBResult_ptr result = db.storeQuery("SELECT `id`, `owner`, `paid`, `warnings` FROM `houses`"); if (!result) { return false; } @@ -286,7 +286,7 @@ bool IOMapSerialize::loadHouseInfo() } } while (result->next()); - result = db->storeQuery("SELECT `house_id`, `listid`, `list` FROM `house_lists`"); + result = db.storeQuery("SELECT `house_id`, `listid`, `list` FROM `house_lists`"); if (result) { do { House* house = g_game.map.houses.getHouse(result->getNumber("house_id")); @@ -300,14 +300,14 @@ bool IOMapSerialize::loadHouseInfo() bool IOMapSerialize::saveHouseInfo() { - Database* db = Database::getInstance(); + Database& db = Database::getInstance(); DBTransaction transaction; if (!transaction.begin()) { return false; } - if (!db->executeQuery("DELETE FROM `house_lists`")) { + if (!db.executeQuery("DELETE FROM `house_lists`")) { return false; } @@ -315,16 +315,16 @@ bool IOMapSerialize::saveHouseInfo() 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()); + 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(); + 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() << ')'; + 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()); + db.executeQuery(query.str()); query.str(std::string()); } @@ -335,7 +335,7 @@ bool IOMapSerialize::saveHouseInfo() std::string listText; if (house->getAccessList(GUEST_LIST, listText) && !listText.empty()) { - query << house->getId() << ',' << GUEST_LIST << ',' << db->escapeString(listText); + query << house->getId() << ',' << GUEST_LIST << ',' << db.escapeString(listText); if (!stmt.addRow(query)) { return false; } @@ -344,7 +344,7 @@ bool IOMapSerialize::saveHouseInfo() } if (house->getAccessList(SUBOWNER_LIST, listText) && !listText.empty()) { - query << house->getId() << ',' << SUBOWNER_LIST << ',' << db->escapeString(listText); + query << house->getId() << ',' << SUBOWNER_LIST << ',' << db.escapeString(listText); if (!stmt.addRow(query)) { return false; } @@ -354,7 +354,7 @@ bool IOMapSerialize::saveHouseInfo() for (Door* door : house->getDoors()) { if (door->getAccessList(listText) && !listText.empty()) { - query << house->getId() << ',' << door->getDoorId() << ',' << db->escapeString(listText); + query << house->getId() << ',' << door->getDoorId() << ',' << db.escapeString(listText); if (!stmt.addRow(query)) { return false; } diff --git a/src/iomapserialize.h b/src/iomapserialize.h index 1a87c28..9840f24 100644 --- a/src/iomapserialize.h +++ b/src/iomapserialize.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,7 +31,7 @@ class IOMapSerialize static bool loadHouseInfo(); static bool saveHouseInfo(); - protected: + private: static void saveItem(PropWriteStream& stream, const Item* item); static void saveTile(PropWriteStream& stream, const Tile* tile); diff --git a/src/iomarket.cpp b/src/iomarket.cpp new file mode 100644 index 0000000..7a651df --- /dev/null +++ b/src/iomarket.cpp @@ -0,0 +1,347 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "iomarket.h" + +#include "configmanager.h" +#include "databasetasks.h" +#include "iologindata.h" +#include "game.h" +#include "scheduler.h" + +extern ConfigManager g_config; +extern Game g_game; + +MarketOfferList IOMarket::getActiveOffers(MarketAction_t action, uint16_t itemId) +{ + MarketOfferList offerList; + + std::ostringstream query; + query << "SELECT `id`, `amount`, `price`, `created`, `anonymous`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `player_name` FROM `market_offers` WHERE `sale` = " << action << " AND `itemtype` = " << itemId; + + DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + if (!result) { + return offerList; + } + + const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); + + do { + MarketOffer offer; + offer.amount = result->getNumber("amount"); + offer.price = result->getNumber("price"); + offer.timestamp = result->getNumber("created") + marketOfferDuration; + offer.counter = result->getNumber("id") & 0xFFFF; + if (result->getNumber("anonymous") == 0) { + offer.playerName = result->getString("player_name"); + } else { + offer.playerName = "Anonymous"; + } + offerList.push_back(offer); + } while (result->next()); + return offerList; +} + +MarketOfferList IOMarket::getOwnOffers(MarketAction_t action, uint32_t playerId) +{ + MarketOfferList offerList; + + const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); + + std::ostringstream query; + query << "SELECT `id`, `amount`, `price`, `created`, `itemtype` FROM `market_offers` WHERE `player_id` = " << playerId << " AND `sale` = " << action; + + DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + if (!result) { + return offerList; + } + + do { + MarketOffer offer; + offer.amount = result->getNumber("amount"); + offer.price = result->getNumber("price"); + offer.timestamp = result->getNumber("created") + marketOfferDuration; + offer.counter = result->getNumber("id") & 0xFFFF; + offer.itemId = result->getNumber("itemtype"); + offerList.push_back(offer); + } while (result->next()); + return offerList; +} + +HistoryMarketOfferList IOMarket::getOwnHistory(MarketAction_t action, uint32_t playerId) +{ + HistoryMarketOfferList offerList; + + std::ostringstream query; + query << "SELECT `itemtype`, `amount`, `price`, `expires_at`, `state` FROM `market_history` WHERE `player_id` = " << playerId << " AND `sale` = " << action; + + DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + if (!result) { + return offerList; + } + + do { + HistoryMarketOffer offer; + offer.itemId = result->getNumber("itemtype"); + offer.amount = result->getNumber("amount"); + offer.price = result->getNumber("price"); + offer.timestamp = result->getNumber("expires_at"); + + MarketOfferState_t offerState = static_cast(result->getNumber("state")); + if (offerState == OFFERSTATE_ACCEPTEDEX) { + offerState = OFFERSTATE_ACCEPTED; + } + + offer.state = offerState; + + offerList.push_back(offer); + } while (result->next()); + return offerList; +} + +void IOMarket::processExpiredOffers(DBResult_ptr result, bool) +{ + if (!result) { + return; + } + + do { + if (!IOMarket::moveOfferToHistory(result->getNumber("id"), OFFERSTATE_EXPIRED)) { + continue; + } + + const uint32_t playerId = result->getNumber("player_id"); + const uint16_t amount = result->getNumber("amount"); + if (result->getNumber("sale") == 1) { + const ItemType& itemType = Item::items[result->getNumber("itemtype")]; + if (itemType.id == 0) { + continue; + } + + Player* player = g_game.getPlayerByGUID(playerId); + if (!player) { + player = new Player(nullptr); + if (!IOLoginData::loadPlayerById(player, playerId)) { + delete player; + continue; + } + } + + if (itemType.stackable) { + uint16_t tmpAmount = amount; + while (tmpAmount > 0) { + uint16_t stackCount = std::min(100, tmpAmount); + Item* item = Item::CreateItem(itemType.id, stackCount); + if (g_game.internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { + delete item; + break; + } + + tmpAmount -= stackCount; + } + } else { + int32_t subType; + if (itemType.charges != 0) { + subType = itemType.charges; + } else { + subType = -1; + } + + for (uint16_t i = 0; i < amount; ++i) { + Item* item = Item::CreateItem(itemType.id, subType); + if (g_game.internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { + delete item; + break; + } + } + } + + if (player->isOffline()) { + IOLoginData::savePlayer(player); + delete player; + } + } else { + uint64_t totalPrice = result->getNumber("price") * amount; + + Player* player = g_game.getPlayerByGUID(playerId); + if (player) { + player->setBankBalance(player->getBankBalance() + totalPrice); + } else { + IOLoginData::increaseBankBalance(playerId, totalPrice); + } + } + } while (result->next()); +} + +void IOMarket::checkExpiredOffers() +{ + const time_t lastExpireDate = time(nullptr) - g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); + + std::ostringstream query; + query << "SELECT `id`, `amount`, `price`, `itemtype`, `player_id`, `sale` FROM `market_offers` WHERE `created` <= " << lastExpireDate; + g_databaseTasks.addTask(query.str(), IOMarket::processExpiredOffers, true); + + int32_t checkExpiredMarketOffersEachMinutes = g_config.getNumber(ConfigManager::CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES); + if (checkExpiredMarketOffersEachMinutes <= 0) { + return; + } + + g_scheduler.addEvent(createSchedulerTask(checkExpiredMarketOffersEachMinutes * 60 * 1000, IOMarket::checkExpiredOffers)); +} + +uint32_t IOMarket::getPlayerOfferCount(uint32_t playerId) +{ + std::ostringstream query; + query << "SELECT COUNT(*) AS `count` FROM `market_offers` WHERE `player_id` = " << playerId; + + DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + if (!result) { + return 0; + } + return result->getNumber("count"); +} + +MarketOfferEx IOMarket::getOfferByCounter(uint32_t timestamp, uint16_t counter) +{ + MarketOfferEx offer; + + const int32_t created = timestamp - g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); + + std::ostringstream query; + query << "SELECT `id`, `sale`, `itemtype`, `amount`, `created`, `price`, `player_id`, `anonymous`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `player_name` FROM `market_offers` WHERE `created` = " << created << " AND (`id` & 65535) = " << counter << " LIMIT 1"; + + DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + if (!result) { + offer.id = 0; + return offer; + } + + offer.id = result->getNumber("id"); + offer.type = static_cast(result->getNumber("sale")); + offer.amount = result->getNumber("amount"); + offer.counter = result->getNumber("id") & 0xFFFF; + offer.timestamp = result->getNumber("created"); + offer.price = result->getNumber("price"); + offer.itemId = result->getNumber("itemtype"); + offer.playerId = result->getNumber("player_id"); + if (result->getNumber("anonymous") == 0) { + offer.playerName = result->getString("player_name"); + } else { + offer.playerName = "Anonymous"; + } + return offer; +} + +void IOMarket::createOffer(uint32_t playerId, MarketAction_t action, uint32_t itemId, uint16_t amount, uint32_t price, bool anonymous) +{ + std::ostringstream query; + query << "INSERT INTO `market_offers` (`player_id`, `sale`, `itemtype`, `amount`, `price`, `created`, `anonymous`) VALUES (" << playerId << ',' << action << ',' << itemId << ',' << amount << ',' << price << ',' << time(nullptr) << ',' << anonymous << ')'; + Database::getInstance().executeQuery(query.str()); +} + +void IOMarket::acceptOffer(uint32_t offerId, uint16_t amount) +{ + std::ostringstream query; + query << "UPDATE `market_offers` SET `amount` = `amount` - " << amount << " WHERE `id` = " << offerId; + Database::getInstance().executeQuery(query.str()); +} + +void IOMarket::deleteOffer(uint32_t offerId) +{ + std::ostringstream query; + query << "DELETE FROM `market_offers` WHERE `id` = " << offerId; + Database::getInstance().executeQuery(query.str()); +} + +void IOMarket::appendHistory(uint32_t playerId, MarketAction_t type, uint16_t itemId, uint16_t amount, uint32_t price, time_t timestamp, MarketOfferState_t state) +{ + std::ostringstream query; + query << "INSERT INTO `market_history` (`player_id`, `sale`, `itemtype`, `amount`, `price`, `expires_at`, `inserted`, `state`) VALUES (" + << playerId << ',' << type << ',' << itemId << ',' << amount << ',' << price << ',' + << timestamp << ',' << time(nullptr) << ',' << state << ')'; + g_databaseTasks.addTask(query.str()); +} + +bool IOMarket::moveOfferToHistory(uint32_t offerId, MarketOfferState_t state) +{ + const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); + + Database& db = Database::getInstance(); + + std::ostringstream query; + query << "SELECT `player_id`, `sale`, `itemtype`, `amount`, `price`, `created` FROM `market_offers` WHERE `id` = " << offerId; + + DBResult_ptr result = db.storeQuery(query.str()); + if (!result) { + return false; + } + + query.str(std::string()); + query << "DELETE FROM `market_offers` WHERE `id` = " << offerId; + if (!db.executeQuery(query.str())) { + return false; + } + + appendHistory(result->getNumber("player_id"), static_cast(result->getNumber("sale")), result->getNumber("itemtype"), result->getNumber("amount"), result->getNumber("price"), result->getNumber("created") + marketOfferDuration, state); + return true; +} + +void IOMarket::updateStatistics() +{ + std::ostringstream query; + query << "SELECT `sale` AS `sale`, `itemtype` AS `itemtype`, COUNT(`price`) AS `num`, MIN(`price`) AS `min`, MAX(`price`) AS `max`, SUM(`price`) AS `sum` FROM `market_history` WHERE `state` = " << OFFERSTATE_ACCEPTED << " GROUP BY `itemtype`, `sale`"; + DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + if (!result) { + return; + } + + do { + MarketStatistics* statistics; + if (result->getNumber("sale") == MARKETACTION_BUY) { + statistics = &purchaseStatistics[result->getNumber("itemtype")]; + } else { + statistics = &saleStatistics[result->getNumber("itemtype")]; + } + + statistics->numTransactions = result->getNumber("num"); + statistics->lowestPrice = result->getNumber("min"); + statistics->totalPrice = result->getNumber("sum"); + statistics->highestPrice = result->getNumber("max"); + } while (result->next()); +} + +MarketStatistics* IOMarket::getPurchaseStatistics(uint16_t itemId) +{ + auto it = purchaseStatistics.find(itemId); + if (it == purchaseStatistics.end()) { + return nullptr; + } + return &it->second; +} + +MarketStatistics* IOMarket::getSaleStatistics(uint16_t itemId) +{ + auto it = saleStatistics.find(itemId); + if (it == saleStatistics.end()) { + return nullptr; + } + return &it->second; +} diff --git a/src/iomarket.h b/src/iomarket.h new file mode 100644 index 0000000..3491e6f --- /dev/null +++ b/src/iomarket.h @@ -0,0 +1,63 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_IOMARKET_H_B981E52C218C42D3B9EF726EBF0E92C9 +#define FS_IOMARKET_H_B981E52C218C42D3B9EF726EBF0E92C9 + +#include "enums.h" +#include "database.h" + +class IOMarket +{ + public: + static IOMarket& getInstance() { + static IOMarket instance; + return instance; + } + + static MarketOfferList getActiveOffers(MarketAction_t action, uint16_t itemId); + static MarketOfferList getOwnOffers(MarketAction_t action, uint32_t playerId); + static HistoryMarketOfferList getOwnHistory(MarketAction_t action, uint32_t playerId); + + static void processExpiredOffers(DBResult_ptr result, bool); + static void checkExpiredOffers(); + + static uint32_t getPlayerOfferCount(uint32_t playerId); + static MarketOfferEx getOfferByCounter(uint32_t timestamp, uint16_t counter); + + static void createOffer(uint32_t playerId, MarketAction_t action, uint32_t itemId, uint16_t amount, uint32_t price, bool anonymous); + static void acceptOffer(uint32_t offerId, uint16_t amount); + static void deleteOffer(uint32_t offerId); + + static void appendHistory(uint32_t playerId, MarketAction_t type, uint16_t itemId, uint16_t amount, uint32_t price, time_t timestamp, MarketOfferState_t state); + static bool moveOfferToHistory(uint32_t offerId, MarketOfferState_t state); + + void updateStatistics(); + + MarketStatistics* getPurchaseStatistics(uint16_t itemId); + MarketStatistics* getSaleStatistics(uint16_t itemId); + + private: + IOMarket() = default; + + std::map purchaseStatistics; + std::map saleStatistics; +}; + +#endif diff --git a/src/item.cpp b/src/item.cpp index acf2f1b..69fa85c 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,6 +22,7 @@ #include "item.h" #include "container.h" #include "teleport.h" +#include "trashholder.h" #include "mailbox.h" #include "house.h" #include "game.h" @@ -52,7 +53,7 @@ Item* Item::CreateItem(const uint16_t type, uint16_t count /*= 0*/) if (it.id != 0) { if (it.isDepot()) { newItem = new DepotLocker(type); - } else if (it.isContainer() || it.isChest()) { + } else if (it.isContainer()) { newItem = new Container(type); } else if (it.isTeleport()) { newItem = new Teleport(type); @@ -60,10 +61,24 @@ Item* Item::CreateItem(const uint16_t type, uint16_t count /*= 0*/) newItem = new MagicField(type); } else if (it.isDoor()) { newItem = new Door(type); + } else if (it.isTrashHolder()) { + newItem = new TrashHolder(type); } else if (it.isMailbox()) { newItem = new Mailbox(type); } else if (it.isBed()) { newItem = new BedItem(type); + } else if (it.id >= 2210 && it.id <= 2212) { + newItem = new Item(type - 3, count); + } else if (it.id == 2215 || it.id == 2216) { + newItem = new Item(type - 2, count); + } else if (it.id >= 2202 && it.id <= 2206) { + newItem = new Item(type - 37, count); + } else if (it.id == 2640) { + newItem = new Item(6132, count); + } else if (it.id == 6301) { + newItem = new Item(6300, count); + } else if (it.id == 18528) { + newItem = new Item(18408, count); } else { newItem = new Item(type, count); } @@ -148,8 +163,6 @@ Item::Item(const uint16_t type, uint16_t count /*= 0*/) : } else { setCharges(it.charges); } - } else if (it.isKey()) { - setIntAttr(ITEM_ATTRIBUTE_KEYNUMBER, count); } setDefaultDuration(); @@ -168,6 +181,11 @@ Item* Item::clone() const Item* item = Item::CreateItem(id, count); if (attributes) { item->attributes.reset(new ItemAttributes(*attributes)); + if (item->getDuration() > 0) { + item->incrementReferenceCounter(); + item->setDecaying(DECAYING_TRUE); + g_game.toDecayItems.push_front(item); + } } return item; } @@ -225,6 +243,10 @@ void Item::setDefaultSubtype() void Item::onRemoved() { ScriptEnvironment::removeTempItem(this); + + if (hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { + g_game.removeUniqueItem(getUniqueId()); + } } void Item::setID(uint16_t newid) @@ -370,13 +392,13 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) break; } - case ATTR_MOVEMENT_ID: { - uint16_t movementId; - if (!propStream.read(movementId)) { + case ATTR_UNIQUE_ID: { + uint16_t uniqueId; + if (!propStream.read(uniqueId)) { return ATTR_READ_ERROR; } - setMovementID(movementId); + setUniqueId(uniqueId); break; } @@ -512,6 +534,16 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) break; } + case ATTR_EXTRADEFENSE: { + int32_t extraDefense; + if (!propStream.read(extraDefense)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_EXTRADEFENSE, extraDefense); + break; + } + case ATTR_ARMOR: { int32_t armor; if (!propStream.read(armor)) { @@ -522,6 +554,16 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) break; } + case ATTR_HITCHANCE: { + int8_t hitChance; + if (!propStream.read(hitChance)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_HITCHANCE, hitChance); + break; + } + case ATTR_SHOOTRANGE: { uint8_t shootRange; if (!propStream.read(shootRange)) { @@ -532,68 +574,13 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) break; } - case ATTR_KEYNUMBER: { - uint16_t keyNumber; - if (!propStream.read(keyNumber)) { + case ATTR_DECAYTO: { + int32_t decayTo; + if (!propStream.read(decayTo)) { return ATTR_READ_ERROR; } - setIntAttr(ITEM_ATTRIBUTE_KEYNUMBER, keyNumber); - break; - } - - case ATTR_KEYHOLENUMBER: - { - uint16_t keyHoleNumber; - if (!propStream.read(keyHoleNumber)) { - return ATTR_READ_ERROR; - } - - setIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER, keyHoleNumber); - break; - } - - case ATTR_DOORLEVEL: - { - uint16_t doorLevel; - if (!propStream.read(doorLevel)) { - return ATTR_READ_ERROR; - } - - setIntAttr(ITEM_ATTRIBUTE_DOORLEVEL, doorLevel); - break; - } - - case ATTR_DOORQUESTNUMBER: - { - uint16_t doorQuestNumber; - if (!propStream.read(doorQuestNumber)) { - return ATTR_READ_ERROR; - } - - setIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER, doorQuestNumber); - break; - } - - case ATTR_DOORQUESTVALUE: - { - uint16_t doorQuestValue; - if (!propStream.read(doorQuestValue)) { - return ATTR_READ_ERROR; - } - - setIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE, doorQuestValue); - break; - } - - case ATTR_CHESTQUESTNUMBER: - { - uint16_t chestQuestNumber; - if (!propStream.read(chestQuestNumber)) { - return ATTR_READ_ERROR; - } - - setIntAttr(ITEM_ATTRIBUTE_CHESTQUESTNUMBER, chestQuestNumber); + setIntAttr(ITEM_ATTRIBUTE_DECAYTO, decayTo); break; } @@ -645,6 +632,30 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) return ATTR_READ_ERROR; } + case ATTR_CUSTOM_ATTRIBUTES: { + uint64_t size; + if (!propStream.read(size)) { + return ATTR_READ_ERROR; + } + + for (uint64_t i = 0; i < size; i++) { + // Unserialize key type and value + std::string key; + if (!propStream.readString(key)) { + return ATTR_READ_ERROR; + }; + + // Unserialize value type and value + ItemAttributes::CustomAttribute val; + if (!val.unserialize(propStream)) { + return ATTR_READ_ERROR; + } + + setCustomAttribute(key, val); + } + break; + } + default: return ATTR_READ_ERROR; } @@ -666,7 +677,7 @@ bool Item::unserializeAttr(PropStream& propStream) return true; } -bool Item::unserializeItemNode(FileLoader&, NODE, PropStream& propStream) +bool Item::unserializeItemNode(OTB::Loader&, const OTB::Node&, PropStream& propStream) { return unserializeAttr(propStream); } @@ -758,44 +769,42 @@ void Item::serializeAttr(PropWriteStream& propWriteStream) const propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DEFENSE)); } + if (hasAttribute(ITEM_ATTRIBUTE_EXTRADEFENSE)) { + propWriteStream.write(ATTR_EXTRADEFENSE); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_EXTRADEFENSE)); + } + if (hasAttribute(ITEM_ATTRIBUTE_ARMOR)) { propWriteStream.write(ATTR_ARMOR); propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_ARMOR)); } + if (hasAttribute(ITEM_ATTRIBUTE_HITCHANCE)) { + propWriteStream.write(ATTR_HITCHANCE); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_HITCHANCE)); + } + if (hasAttribute(ITEM_ATTRIBUTE_SHOOTRANGE)) { propWriteStream.write(ATTR_SHOOTRANGE); propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_SHOOTRANGE)); } - if (hasAttribute(ITEM_ATTRIBUTE_KEYNUMBER)) { - propWriteStream.write(ATTR_KEYNUMBER); - propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_KEYNUMBER)); + if (hasAttribute(ITEM_ATTRIBUTE_DECAYTO)) { + propWriteStream.write(ATTR_DECAYTO); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DECAYTO)); } - if (hasAttribute(ITEM_ATTRIBUTE_KEYHOLENUMBER)) { - propWriteStream.write(ATTR_KEYHOLENUMBER); - propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER)); - } + if (hasAttribute(ITEM_ATTRIBUTE_CUSTOM)) { + const ItemAttributes::CustomAttributeMap* customAttrMap = attributes->getCustomAttributeMap(); + propWriteStream.write(ATTR_CUSTOM_ATTRIBUTES); + propWriteStream.write(static_cast(customAttrMap->size())); + for (const auto &entry : *customAttrMap) { + // Serializing key type and value + propWriteStream.writeString(entry.first); - if (hasAttribute(ITEM_ATTRIBUTE_DOORLEVEL)) { - propWriteStream.write(ATTR_DOORLEVEL); - propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DOORLEVEL)); - } - - if (hasAttribute(ITEM_ATTRIBUTE_DOORQUESTNUMBER)) { - propWriteStream.write(ATTR_DOORQUESTNUMBER); - propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER)); - } - - if (hasAttribute(ITEM_ATTRIBUTE_DOORQUESTVALUE)) { - propWriteStream.write(ATTR_DOORQUESTVALUE); - propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE)); - } - - if (hasAttribute(ITEM_ATTRIBUTE_CHESTQUESTNUMBER)) { - propWriteStream.write(ATTR_CHESTQUESTNUMBER); - propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_CHESTQUESTNUMBER)); + // Serializing value type and value + entry.second.serialize(propWriteStream); + } } } @@ -804,18 +813,17 @@ bool Item::hasProperty(ITEMPROPERTY prop) const const ItemType& it = items[id]; switch (prop) { case CONST_PROP_BLOCKSOLID: return it.blockSolid; - case CONST_PROP_MOVEABLE: return it.moveable; + case CONST_PROP_MOVEABLE: return it.moveable && !hasAttribute(ITEM_ATTRIBUTE_UNIQUEID); case CONST_PROP_HASHEIGHT: return it.hasHeight; case CONST_PROP_BLOCKPROJECTILE: return it.blockProjectile; case CONST_PROP_BLOCKPATH: return it.blockPathFind; case CONST_PROP_ISVERTICAL: return it.isVertical; case CONST_PROP_ISHORIZONTAL: return it.isHorizontal; - case CONST_PROP_IMMOVABLEBLOCKSOLID: return it.blockSolid && !it.moveable; - case CONST_PROP_IMMOVABLEBLOCKPATH: return it.blockPathFind && !it.moveable; - case CONST_PROP_IMMOVABLENOFIELDBLOCKPATH: return !it.isMagicField() && it.blockPathFind && !it.moveable; + case CONST_PROP_IMMOVABLEBLOCKSOLID: return it.blockSolid && (!it.moveable || hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)); + case CONST_PROP_IMMOVABLEBLOCKPATH: return it.blockPathFind && (!it.moveable || hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)); + case CONST_PROP_IMMOVABLENOFIELDBLOCKPATH: return !it.isMagicField() && it.blockPathFind && (!it.moveable || hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)); case CONST_PROP_NOFIELDBLOCKPATH: return !it.isMagicField() && it.blockPathFind; case CONST_PROP_SUPPORTHANGABLE: return it.isHorizontal || it.isVertical; - case CONST_PROP_UNLAY: return !it.allowPickupable; default: return false; } } @@ -830,8 +838,10 @@ uint32_t Item::getWeight() const } std::string Item::getDescription(const ItemType& it, int32_t lookDistance, - const Item* item /*= nullptr*/, int32_t subType /*= -1*/, bool addArticle /*= true*/) + const Item* item /*= nullptr*/, int32_t subType /*= -1*/, bool addArticle /*= true*/) { + const std::string* text = nullptr; + std::ostringstream s; s << getNameDescription(it, item, subType, addArticle); @@ -840,141 +850,567 @@ std::string Item::getDescription(const ItemType& it, int32_t lookDistance, } if (it.isRune()) { - uint32_t charges = std::max(static_cast(1), static_cast(item == nullptr ? it.charges : item->getCharges())); - - if (it.runeLevel > 0) { - s << " for level " << it.runeLevel; - } - - if (it.runeLevel > 0) { - s << " and"; - } - - s << " for magic level " << it.runeMagLevel; - s << ". It's an \"" << it.runeSpellName << "\"-spell (" << charges << "x). "; - } else if (it.isDoor() && item) { - if (item->hasAttribute(ITEM_ATTRIBUTE_DOORLEVEL)) { - s << " for level " << item->getIntAttr(ITEM_ATTRIBUTE_DOORLEVEL); - } - s << "."; - } else if (it.weaponType != WEAPON_NONE) { - if (it.weaponType == WEAPON_DISTANCE && it.ammoType != AMMO_NONE) { - if (it.attack != 0) { - s << ", Atk" << std::showpos << it.attack << std::noshowpos; - } - } else if (it.weaponType != WEAPON_AMMO && it.weaponType != WEAPON_WAND && (it.attack != 0 || it.defense != 0)) { - s << " ("; - if (it.attack != 0) { - s << "Atk:" << static_cast(it.attack); - } - - if (it.defense != 0) { - if (it.attack != 0) - s << " "; - - s << "Def:" << static_cast(it.defense); - } - - s << ")"; - } - s << "."; - } else if (it.armor != 0) { - if (it.charges > 0) { - if (subType > 1) { - s << " that has " << static_cast(subType) << " charges left"; - } else { - s << " that has " << it.charges << " charge left"; - } - } - - s << " (Arm:" << it.armor << ")."; - } else if (it.isFluidContainer()) { - if (item && item->getFluidType() != 0) { - s << " of " << items[item->getFluidType()].name << "."; - } else { - s << ". It is empty."; - } - } else if (it.isSplash()) { - s << " of "; - if (item && item->getFluidType() != 0) { - s << items[item->getFluidType()].name; - } else { - s << items[1].name; - } - s << "."; - } else if (it.isContainer() && !it.isChest()) { - s << " (Vol:" << static_cast(it.maxItems) << ")."; - } else if (it.isKey()) { - if (item) { - s << " (Key:" << static_cast(item->getIntAttr(ITEM_ATTRIBUTE_KEYNUMBER)) << ")."; - } else { - s << " (Key:0)."; - } - } else if (it.allowDistRead) { - s << "."; - s << std::endl; - - if (item && item->getText() != "") { - if (lookDistance <= 4) { - const std::string& writer = item->getWriter(); - if (!writer.empty()) { - s << writer << " wrote"; - time_t date = item->getDate(); - if (date != 0) { - s << " on " << formatDateShort(date); - } - s << ": "; - } else { - s << "You read: "; + if (it.runeLevel > 0 || it.runeMagLevel > 0) { + if (RuneSpell* rune = g_spells->getRuneSpell(it.id)) { + int32_t tmpSubType = subType; + if (item) { + tmpSubType = item->getSubType(); } - s << item->getText(); - } else { - s << "You are too far away to read it."; - } - } else { - s << "Nothing is written on it."; - } - } else if (it.charges > 0) { - uint32_t charges = (item == nullptr ? it.charges : item->getCharges()); - if (charges > 1) { - s << " that has " << static_cast(charges) << " charges left."; - } else { - s << " that has 1 charge left."; - } - } else if (it.showDuration) { - if (item && item->hasAttribute(ITEM_ATTRIBUTE_DURATION)) { - int32_t duration = item->getDuration() / 1000; - s << " that has energy for "; + s << ". " << (it.stackable && tmpSubType > 1 ? "They" : "It") << " can only be used by "; - if (duration >= 120) { - s << duration / 60 << " minutes left."; - } else if (duration > 60) { - s << "1 minute left."; - } else { - s << "less than a minute left."; + const VocSpellMap& vocMap = rune->getVocMap(); + std::vector showVocMap; + + // vocations are usually listed with the unpromoted and promoted version, the latter being + // hidden from description, so `total / 2` is most likely the amount of vocations to be shown. + showVocMap.reserve(vocMap.size() / 2); + for (const auto& voc : vocMap) { + if (voc.second) { + showVocMap.push_back(g_vocations.getVocation(voc.first)); + } + } + + if (!showVocMap.empty()) { + auto vocIt = showVocMap.begin(), vocLast = (showVocMap.end() - 1); + while (vocIt != vocLast) { + s << asLowerCaseString((*vocIt)->getVocName()) << "s"; + if (++vocIt == vocLast) { + s << " and "; + } else { + s << ", "; + } + } + s << asLowerCaseString((*vocLast)->getVocName()) << "s"; + } else { + s << "players"; + } + + s << " with"; + + if (it.runeLevel > 0) { + s << " level " << it.runeLevel; + } + + if (it.runeMagLevel > 0) { + if (it.runeLevel > 0) { + s << " and"; + } + + s << " magic level " << it.runeMagLevel; + } + + s << " or higher"; } - } else { - s << " that is brand-new."; + } + } else if (it.weaponType != WEAPON_NONE) { + bool begin = true; + if (it.weaponType == WEAPON_DISTANCE && it.ammoType != AMMO_NONE) { + s << " (Range:" << static_cast(item ? item->getShootRange() : it.shootRange); + + int32_t attack; + int8_t hitChance; + if (item) { + attack = item->getAttack(); + hitChance = item->getHitChance(); + } else { + attack = it.attack; + hitChance = it.hitChance; + } + + if (attack != 0) { + s << ", Atk" << std::showpos << attack << std::noshowpos; + } + + if (hitChance != 0) { + s << ", Hit%" << std::showpos << static_cast(hitChance) << std::noshowpos; + } + + begin = false; + } else if (it.weaponType != WEAPON_AMMO) { + + int32_t attack, defense, extraDefense; + if (item) { + attack = item->getAttack(); + defense = item->getDefense(); + extraDefense = item->getExtraDefense(); + } else { + attack = it.attack; + defense = it.defense; + extraDefense = it.extraDefense; + } + + if (attack != 0) { + begin = false; + s << " (Atk:" << attack; + + if (it.abilities && it.abilities->elementType != COMBAT_NONE && it.abilities->elementDamage != 0) { + s << " physical + " << it.abilities->elementDamage << ' ' << getCombatName(it.abilities->elementType); + } + } + + if (defense != 0 || extraDefense != 0) { + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "Def:" << defense; + if (extraDefense != 0) { + s << ' ' << std::showpos << extraDefense << std::noshowpos; + } + } + } + + if (it.abilities) { + for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; i++) { + if (!it.abilities->skills[i]) { + continue; + } + + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << getSkillName(i) << ' ' << std::showpos << it.abilities->skills[i] << std::noshowpos; + } + + for (uint8_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; i++) { + if (!it.abilities->specialSkills[i]) { + continue; + } + + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << getSpecialSkillName(i) << ' ' << std::showpos << it.abilities->specialSkills[i] << '%' << std::noshowpos; + } + + if (it.abilities->stats[STAT_MAGICPOINTS]) { + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "magic level " << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos; + } + + int16_t show = it.abilities->absorbPercent[0]; + if (show != 0) { + for (size_t i = 1; i < COMBAT_COUNT; ++i) { + if (it.abilities->absorbPercent[i] != show) { + show = 0; + break; + } + } + } + + if (show == 0) { + bool tmp = true; + + for (size_t i = 0; i < COMBAT_COUNT; ++i) { + if (it.abilities->absorbPercent[i] == 0) { + continue; + } + + if (tmp) { + tmp = false; + + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "protection "; + } else { + s << ", "; + } + + s << getCombatName(indexToCombatType(i)) << ' ' << std::showpos << it.abilities->absorbPercent[i] << std::noshowpos << '%'; + } + } else { + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "protection all " << std::showpos << show << std::noshowpos << '%'; + } + + show = it.abilities->fieldAbsorbPercent[0]; + if (show != 0) { + for (size_t i = 1; i < COMBAT_COUNT; ++i) { + if (it.abilities->absorbPercent[i] != show) { + show = 0; + break; + } + } + } + + if (show == 0) { + bool tmp = true; + + for (size_t i = 0; i < COMBAT_COUNT; ++i) { + if (it.abilities->fieldAbsorbPercent[i] == 0) { + continue; + } + + if (tmp) { + tmp = false; + + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "protection "; + } else { + s << ", "; + } + + s << getCombatName(indexToCombatType(i)) << " field " << std::showpos << it.abilities->fieldAbsorbPercent[i] << std::noshowpos << '%'; + } + } else { + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "protection all fields " << std::showpos << show << std::noshowpos << '%'; + } + + if (it.abilities->speed) { + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "speed " << std::showpos << (it.abilities->speed >> 1) << std::noshowpos; + } + } + + if (!begin) { + s << ')'; + } + } else if (it.armor != 0 || (item && item->getArmor() != 0) || it.showAttributes) { + bool begin = true; + + int32_t armor = (item ? item->getArmor() : it.armor); + if (armor != 0) { + s << " (Arm:" << armor; + begin = false; + } + + if (it.abilities) { + for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; i++) { + if (!it.abilities->skills[i]) { + continue; + } + + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << getSkillName(i) << ' ' << std::showpos << it.abilities->skills[i] << std::noshowpos; + } + + if (it.abilities->stats[STAT_MAGICPOINTS]) { + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "magic level " << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos; + } + + int16_t show = it.abilities->absorbPercent[0]; + if (show != 0) { + for (size_t i = 1; i < COMBAT_COUNT; ++i) { + if (it.abilities->absorbPercent[i] != show) { + show = 0; + break; + } + } + } + + if (!show) { + bool protectionBegin = true; + for (size_t i = 0; i < COMBAT_COUNT; ++i) { + if (it.abilities->absorbPercent[i] == 0) { + continue; + } + + if (protectionBegin) { + protectionBegin = false; + + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "protection "; + } else { + s << ", "; + } + + s << getCombatName(indexToCombatType(i)) << ' ' << std::showpos << it.abilities->absorbPercent[i] << std::noshowpos << '%'; + } + } else { + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "protection all " << std::showpos << show << std::noshowpos << '%'; + } + + show = it.abilities->fieldAbsorbPercent[0]; + if (show != 0) { + for (size_t i = 1; i < COMBAT_COUNT; ++i) { + if (it.abilities->absorbPercent[i] != show) { + show = 0; + break; + } + } + } + + if (!show) { + bool tmp = true; + + for (size_t i = 0; i < COMBAT_COUNT; ++i) { + if (it.abilities->fieldAbsorbPercent[i] == 0) { + continue; + } + + if (tmp) { + tmp = false; + + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "protection "; + } else { + s << ", "; + } + + s << getCombatName(indexToCombatType(i)) << " field " << std::showpos << it.abilities->fieldAbsorbPercent[i] << std::noshowpos << '%'; + } + } else { + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "protection all fields " << std::showpos << show << std::noshowpos << '%'; + } + + if (it.abilities->speed) { + if (begin) { + begin = false; + s << " ("; + } else { + s << ", "; + } + + s << "speed " << std::showpos << (it.abilities->speed >> 1) << std::noshowpos; + } + } + + if (!begin) { + s << ')'; + } + } else if (it.isContainer() || (item && item->getContainer())) { + uint32_t volume = 0; + if (!item || !item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { + if (it.isContainer()) { + volume = it.maxItems; + } else { + volume = item->getContainer()->capacity(); + } + } + + if (volume != 0) { + s << " (Vol:" << volume << ')'; } } else { - s << "."; + bool found = true; + + if (it.abilities) { + if (it.abilities->speed > 0) { + s << " (speed " << std::showpos << (it.abilities->speed / 2) << std::noshowpos << ')'; + } else if (hasBitSet(CONDITION_DRUNK, it.abilities->conditionSuppressions)) { + s << " (hard drinking)"; + } else if (it.abilities->invisible) { + s << " (invisibility)"; + } else if (it.abilities->regeneration) { + s << " (faster regeneration)"; + } else if (it.abilities->manaShield) { + s << " (mana shield)"; + } else { + found = false; + } + } else { + found = false; + } + + if (!found) { + if (it.isKey()) { + s << " (Key:" << std::setfill('0') << std::setw(4) << (item ? item->getActionId() : 0) << ')'; + } else if (it.isFluidContainer()) { + if (subType > 0) { + const std::string& itemName = items[subType].name; + s << " of " << (!itemName.empty() ? itemName : "unknown"); + } else { + s << ". It is empty"; + } + } else if (it.isSplash()) { + s << " of "; + + if (subType > 0 && !items[subType].name.empty()) { + s << items[subType].name; + } else { + s << "unknown"; + } + } else if (it.allowDistRead && (it.id < 7369 || it.id > 7371)) { + s << ".\n"; + + if (lookDistance <= 4) { + if (item) { + text = &item->getText(); + if (!text->empty()) { + const std::string& writer = item->getWriter(); + if (!writer.empty()) { + s << writer << " wrote"; + time_t date = item->getDate(); + if (date != 0) { + s << " on " << formatDateShort(date); + } + s << ": "; + } else { + s << "You read: "; + } + s << *text; + } else { + s << "Nothing is written on it"; + } + } else { + s << "Nothing is written on it"; + } + } else { + s << "You are too far away to read it"; + } + } else if (it.levelDoor != 0 && item) { + uint16_t actionId = item->getActionId(); + if (actionId >= it.levelDoor) { + s << " for level " << (actionId - it.levelDoor); + } + } + } + } + + if (it.showCharges) { + s << " that has " << subType << " charge" << (subType != 1 ? "s" : "") << " left"; + } + + if (it.showDuration) { + if (item && item->hasAttribute(ITEM_ATTRIBUTE_DURATION)) { + uint32_t duration = item->getDuration() / 1000; + s << " that will expire in "; + + if (duration >= 86400) { + uint16_t days = duration / 86400; + uint16_t hours = (duration % 86400) / 3600; + s << days << " day" << (days != 1 ? "s" : ""); + + if (hours > 0) { + s << " and " << hours << " hour" << (hours != 1 ? "s" : ""); + } + } else if (duration >= 3600) { + uint16_t hours = duration / 3600; + uint16_t minutes = (duration % 3600) / 60; + s << hours << " hour" << (hours != 1 ? "s" : ""); + + if (minutes > 0) { + s << " and " << minutes << " minute" << (minutes != 1 ? "s" : ""); + } + } else if (duration >= 60) { + uint16_t minutes = duration / 60; + s << minutes << " minute" << (minutes != 1 ? "s" : ""); + uint16_t seconds = duration % 60; + + if (seconds > 0) { + s << " and " << seconds << " second" << (seconds != 1 ? "s" : ""); + } + } else { + s << duration << " second" << (duration != 1 ? "s" : ""); + } + } else { + s << " that is brand-new"; + } + } + + if (!it.allowDistRead || (it.id >= 7369 && it.id <= 7371)) { + s << '.'; + } else { + if (!text && item) { + text = &item->getText(); + } + + if (!text || text->empty()) { + s << '.'; + } } if (it.wieldInfo != 0) { - s << std::endl << "It can only be wielded properly by "; + s << "\nIt can only be wielded properly by "; if (it.wieldInfo & WIELDINFO_PREMIUM) { s << "premium "; } - if (it.wieldInfo & WIELDINFO_VOCREQ) { + if (!it.vocationString.empty()) { s << it.vocationString; } else { s << "players"; } if (it.wieldInfo & WIELDINFO_LEVEL) { - s << " of level " << static_cast(it.minReqLevel) << " or higher"; + s << " of level " << it.minReqLevel << " or higher"; } if (it.wieldInfo & WIELDINFO_MAGLV) { @@ -984,25 +1420,43 @@ std::string Item::getDescription(const ItemType& it, int32_t lookDistance, s << " of"; } - s << " magic level " << static_cast(it.minReqMagicLevel) << " or higher"; + s << " magic level " << it.minReqMagicLevel << " or higher"; } - s << "."; + s << '.'; } - if (lookDistance <= 1 && !it.isChest() && it.pickupable) { - double weight = (item == nullptr ? it.weight : item->getWeight()); - if (weight > 0) { - s << std::endl << getWeightDescription(it, weight); + if (lookDistance <= 1) { + if (item) { + const uint32_t weight = item->getWeight(); + if (weight != 0 && it.pickupable) { + s << '\n' << getWeightDescription(it, weight, item->getItemCount()); + } + } else if (it.weight != 0 && it.pickupable) { + s << '\n' << getWeightDescription(it, it.weight); } } - if (item && item->getSpecialDescription() != "") { - s << std::endl << item->getSpecialDescription().c_str(); - } else if (it.description.length() && lookDistance <= 1) { - s << std::endl << it.description << "."; + if (item) { + const std::string& specialDescription = item->getSpecialDescription(); + if (!specialDescription.empty()) { + s << '\n' << specialDescription; + } else if (lookDistance <= 1 && !it.description.empty()) { + s << '\n' << it.description; + } + } else if (lookDistance <= 1 && !it.description.empty()) { + s << '\n' << it.description; } + if (it.allowDistRead && it.id >= 7369 && it.id <= 7371) { + if (!text && item) { + text = &item->getText(); + } + + if (text && !text->empty()) { + s << '\n' << *text; + } + } return s.str(); } @@ -1088,6 +1542,17 @@ std::string Item::getWeightDescription() const return getWeightDescription(weight); } +void Item::setUniqueId(uint16_t n) +{ + if (hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { + return; + } + + if (g_game.addUniqueItem(n, this)) { + getAttributes()->setUniqueId(n); + } +} + bool Item::canDecay() const { if (isRemoved()) { @@ -1095,7 +1560,11 @@ bool Item::canDecay() const } const ItemType& it = Item::items[id]; - if (it.decayTo < 0 || it.decayTime == 0) { + if (getDecayTo() < 0 || it.decayTime == 0) { + return false; + } + + if (hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { return false; } @@ -1119,14 +1588,16 @@ uint32_t Item::getWorth() const } } -void Item::getLight(LightInfo& lightInfo) const +LightInfo Item::getLightInfo() const { const ItemType& it = items[id]; - lightInfo.color = it.lightColor; - lightInfo.level = it.lightLevel; + return {it.lightLevel, it.lightColor}; } std::string ItemAttributes::emptyString; +int64_t ItemAttributes::emptyInt; +double ItemAttributes::emptyDouble; +bool ItemAttributes::emptyBool; const std::string& ItemAttributes::getStrAttr(itemAttrTypes type) const { @@ -1240,3 +1711,63 @@ void Item::startDecaying() { g_game.startDecay(this); } + +bool Item::hasMarketAttributes() const +{ + if (attributes == nullptr) { + return true; + } + + for (const auto& attr : attributes->getList()) { + if (attr.type == ITEM_ATTRIBUTE_CHARGES) { + uint16_t charges = static_cast(attr.value.integer); + if (charges != items[id].charges) { + return false; + } + } else if (attr.type == ITEM_ATTRIBUTE_DURATION) { + uint32_t duration = static_cast(attr.value.integer); + if (duration != getDefaultDuration()) { + return false; + } + } else { + return false; + } + } + return true; +} + +template<> +const std::string& ItemAttributes::CustomAttribute::get() { + if (value.type() == typeid(std::string)) { + return boost::get(value); + } + + return emptyString; +} + +template<> +const int64_t& ItemAttributes::CustomAttribute::get() { + if (value.type() == typeid(int64_t)) { + return boost::get(value); + } + + return emptyInt; +} + +template<> +const double& ItemAttributes::CustomAttribute::get() { + if (value.type() == typeid(double)) { + return boost::get(value); + } + + return emptyDouble; +} + +template<> +const bool& ItemAttributes::CustomAttribute::get() { + if (value.type() == typeid(bool)) { + return boost::get(value); + } + + return emptyBool; +} diff --git a/src/item.h b/src/item.h index 87b2a04..edbda91 100644 --- a/src/item.h +++ b/src/item.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,7 +23,12 @@ #include "cylinder.h" #include "thing.h" #include "items.h" +#include "luascript.h" +#include "tools.h" +#include +#include +#include #include class Creature; @@ -31,8 +36,8 @@ class Player; class Container; class Depot; class Teleport; +class TrashHolder; class Mailbox; -class DepotLocker; class Door; class MagicField; class BedItem; @@ -50,7 +55,6 @@ enum ITEMPROPERTY { CONST_PROP_IMMOVABLENOFIELDBLOCKPATH, CONST_PROP_NOFIELDBLOCKPATH, CONST_PROP_SUPPORTHANGABLE, - CONST_PROP_UNLAY, }; enum TradeEvents_t { @@ -69,7 +73,7 @@ enum AttrTypes_t { //ATTR_EXT_FILE = 2, ATTR_TILE_FLAGS = 3, ATTR_ACTION_ID = 4, - ATTR_MOVEMENT_ID = 5, + ATTR_UNIQUE_ID = 5, ATTR_TEXT = 6, ATTR_DESC = 7, ATTR_TELE_DEST = 8, @@ -87,22 +91,19 @@ enum AttrTypes_t { 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, + ATTR_CONTAINER_ITEMS = 23, + ATTR_NAME = 24, + ATTR_ARTICLE = 25, + ATTR_PLURALNAME = 26, + ATTR_WEIGHT = 27, + ATTR_ATTACK = 28, + ATTR_DEFENSE = 29, + ATTR_EXTRADEFENSE = 30, + ATTR_ARMOR = 31, + ATTR_HITCHANCE = 32, + ATTR_SHOOTRANGE = 33, + ATTR_CUSTOM_ATTRIBUTES = 34, + ATTR_DECAYTO = 35 }; enum Attr_ReadValue { @@ -160,53 +161,11 @@ class ItemAttributes return static_cast(getIntAttr(ITEM_ATTRIBUTE_ACTIONID)); } - void setMovementID(uint16_t n) { - setIntAttr(ITEM_ATTRIBUTE_MOVEMENTID, n); + void setUniqueId(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_UNIQUEID, n); } - uint16_t getMovementId() const { - return static_cast(getIntAttr(ITEM_ATTRIBUTE_MOVEMENTID)); - } - - void setKeyNumber(uint16_t n) { - setIntAttr(ITEM_ATTRIBUTE_KEYNUMBER, n); - } - uint16_t getKeyNumber() const { - return static_cast(getIntAttr(ITEM_ATTRIBUTE_KEYNUMBER)); - } - - void setKeyHoleNumber(uint16_t n) { - setIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER, n); - } - uint16_t getKeyHoleNumber() const { - return static_cast(getIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER)); - } - - void setDoorQuestNumber(uint16_t n) { - setIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER, n); - } - uint16_t getDoorQuestNumber() const { - return static_cast(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER)); - } - - void setDoorQuestValue(uint16_t n) { - setIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE, n); - } - uint16_t getDoorQuestValue() const { - return static_cast(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE)); - } - - void setDoorLevel(uint16_t n) { - setIntAttr(ITEM_ATTRIBUTE_DOORLEVEL, n); - } - uint16_t getDoorLevel() const { - return static_cast(getIntAttr(ITEM_ATTRIBUTE_DOORLEVEL)); - } - - void setChestQuestNumber(uint16_t n) { - setIntAttr(ITEM_ATTRIBUTE_CHESTQUESTNUMBER, n); - } - uint16_t getChestQuestNumber() const { - return static_cast(getIntAttr(ITEM_ATTRIBUTE_CHESTQUESTNUMBER)); + uint16_t getUniqueId() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_UNIQUEID)); } void setCharges(uint16_t n) { @@ -230,6 +189,13 @@ class ItemAttributes return getIntAttr(ITEM_ATTRIBUTE_OWNER); } + void setCorpseOwner(uint32_t corpseOwner) { + setIntAttr(ITEM_ATTRIBUTE_CORPSEOWNER, corpseOwner); + } + uint32_t getCorpseOwner() const { + return getIntAttr(ITEM_ATTRIBUTE_CORPSEOWNER); + } + void setDuration(int32_t time) { setIntAttr(ITEM_ATTRIBUTE_DURATION, time); } @@ -247,19 +213,149 @@ class ItemAttributes return static_cast(getIntAttr(ITEM_ATTRIBUTE_DECAYSTATE)); } - protected: - inline bool hasAttribute(itemAttrTypes type) const { + struct CustomAttribute + { + typedef boost::variant VariantAttribute; + VariantAttribute value; + + CustomAttribute() : value(boost::blank()) {} + + template + explicit CustomAttribute(const T& v) : value(v) {} + + template + void set(const T& v) { + value = v; + } + + template + const T& get(); + + struct PushLuaVisitor : public boost::static_visitor<> { + lua_State* L; + + explicit PushLuaVisitor(lua_State* L) : boost::static_visitor<>(), L(L) {} + + void operator()(const boost::blank&) const { + lua_pushnil(L); + } + + void operator()(const std::string& v) const { + LuaScriptInterface::pushString(L, v); + } + + void operator()(bool v) const { + LuaScriptInterface::pushBoolean(L, v); + } + + void operator()(const int64_t& v) const { + lua_pushnumber(L, v); + } + + void operator()(const double& v) const { + lua_pushnumber(L, v); + } + }; + + void pushToLua(lua_State* L) const { + boost::apply_visitor(PushLuaVisitor(L), value); + } + + struct SerializeVisitor : public boost::static_visitor<> { + PropWriteStream& propWriteStream; + + explicit SerializeVisitor(PropWriteStream& propWriteStream) : boost::static_visitor<>(), propWriteStream(propWriteStream) {} + + void operator()(const boost::blank&) const { + } + + void operator()(const std::string& v) const { + propWriteStream.writeString(v); + } + + template + void operator()(const T& v) const { + propWriteStream.write(v); + } + }; + + void serialize(PropWriteStream& propWriteStream) const { + propWriteStream.write(static_cast(value.which())); + boost::apply_visitor(SerializeVisitor(propWriteStream), value); + } + + bool unserialize(PropStream& propStream) { + // This is hard coded so it's not general, depends on the position of the variants. + uint8_t pos; + if (!propStream.read(pos)) { + return false; + } + + switch (pos) { + case 1: { // std::string + std::string tmp; + if (!propStream.readString(tmp)) { + return false; + } + value = tmp; + break; + } + + case 2: { // int64_t + int64_t tmp; + if (!propStream.read(tmp)) { + return false; + } + value = tmp; + break; + } + + case 3: { // double + double tmp; + if (!propStream.read(tmp)) { + return false; + } + value = tmp; + break; + } + + case 4: { // bool + bool tmp; + if (!propStream.read(tmp)) { + return false; + } + value = tmp; + break; + } + + default: { + value = boost::blank(); + return false; + } + } + return true; + } + }; + + private: + bool hasAttribute(itemAttrTypes type) const { return (type & attributeBits) != 0; } void removeAttribute(itemAttrTypes type); static std::string emptyString; + static int64_t emptyInt; + static double emptyDouble; + static bool emptyBool; + + typedef std::unordered_map CustomAttributeMap; struct Attribute { union { int64_t integer; std::string* string; + CustomAttributeMap* custom; } value; itemAttrTypes type; @@ -272,6 +368,8 @@ class ItemAttributes value.integer = i.value.integer; } else if (ItemAttributes::isStrAttrType(type)) { value.string = new std::string(*i.value.string); + } else if (ItemAttributes::isCustomAttrType(type)) { + value.custom = new CustomAttributeMap(*i.value.custom); } else { memset(&value, 0, sizeof(value)); } @@ -283,6 +381,8 @@ class ItemAttributes ~Attribute() { if (ItemAttributes::isStrAttrType(type)) { delete value.string; + } else if (ItemAttributes::isCustomAttrType(type)) { + delete value.custom; } } Attribute& operator=(Attribute other) { @@ -293,6 +393,8 @@ class ItemAttributes if (this != &other) { if (ItemAttributes::isStrAttrType(type)) { delete value.string; + } else if (ItemAttributes::isCustomAttrType(type)) { + delete value.custom; } value = other.value; @@ -323,12 +425,94 @@ class ItemAttributes const Attribute* getExistingAttr(itemAttrTypes type) const; Attribute& getAttr(itemAttrTypes type); - public: - inline static bool isIntAttrType(itemAttrTypes type) { - return (type & 0xFFFFE13) != 0; + CustomAttributeMap* getCustomAttributeMap() { + if (!hasAttribute(ITEM_ATTRIBUTE_CUSTOM)) { + return nullptr; + } + + return getAttr(ITEM_ATTRIBUTE_CUSTOM).value.custom; } - inline static bool isStrAttrType(itemAttrTypes type) { - return (type & 0x1EC) != 0; + + template + void setCustomAttribute(int64_t key, R value) { + std::string tmp = boost::lexical_cast(key); + setCustomAttribute(tmp, value); + } + + void setCustomAttribute(int64_t key, CustomAttribute& value) { + std::string tmp = boost::lexical_cast(key); + setCustomAttribute(tmp, value); + } + + template + void setCustomAttribute(std::string& key, R value) { + toLowerCaseString(key); + if (hasAttribute(ITEM_ATTRIBUTE_CUSTOM)) { + removeCustomAttribute(key); + } else { + getAttr(ITEM_ATTRIBUTE_CUSTOM).value.custom = new CustomAttributeMap(); + } + getAttr(ITEM_ATTRIBUTE_CUSTOM).value.custom->emplace(key, value); + } + + void setCustomAttribute(std::string& key, CustomAttribute& value) { + toLowerCaseString(key); + if (hasAttribute(ITEM_ATTRIBUTE_CUSTOM)) { + removeCustomAttribute(key); + } else { + getAttr(ITEM_ATTRIBUTE_CUSTOM).value.custom = new CustomAttributeMap(); + } + getAttr(ITEM_ATTRIBUTE_CUSTOM).value.custom->insert(std::make_pair(std::move(key), std::move(value))); + } + + const CustomAttribute* getCustomAttribute(int64_t key) { + std::string tmp = boost::lexical_cast(key); + return getCustomAttribute(tmp); + } + + const CustomAttribute* getCustomAttribute(const std::string& key) { + if (const CustomAttributeMap* customAttrMap = getCustomAttributeMap()) { + auto it = customAttrMap->find(asLowerCaseString(key)); + if (it != customAttrMap->end()) { + return &(it->second); + } + } + return nullptr; + } + + bool removeCustomAttribute(int64_t key) { + std::string tmp = boost::lexical_cast(key); + return removeCustomAttribute(tmp); + } + + bool removeCustomAttribute(const std::string& key) { + if (CustomAttributeMap* customAttrMap = getCustomAttributeMap()) { + auto it = customAttrMap->find(asLowerCaseString(key)); + if (it != customAttrMap->end()) { + customAttrMap->erase(it); + return true; + } + } + return false; + } + + const static uint32_t intAttributeTypes = ITEM_ATTRIBUTE_ACTIONID | ITEM_ATTRIBUTE_UNIQUEID | ITEM_ATTRIBUTE_DATE + | ITEM_ATTRIBUTE_WEIGHT | ITEM_ATTRIBUTE_ATTACK | ITEM_ATTRIBUTE_DEFENSE | ITEM_ATTRIBUTE_EXTRADEFENSE + | ITEM_ATTRIBUTE_ARMOR | ITEM_ATTRIBUTE_HITCHANCE | ITEM_ATTRIBUTE_SHOOTRANGE | ITEM_ATTRIBUTE_OWNER + | ITEM_ATTRIBUTE_DURATION | ITEM_ATTRIBUTE_DECAYSTATE | ITEM_ATTRIBUTE_CORPSEOWNER | ITEM_ATTRIBUTE_CHARGES + | ITEM_ATTRIBUTE_FLUIDTYPE | ITEM_ATTRIBUTE_DOORID | ITEM_ATTRIBUTE_DECAYTO; + const static uint32_t stringAttributeTypes = ITEM_ATTRIBUTE_DESCRIPTION | ITEM_ATTRIBUTE_TEXT | ITEM_ATTRIBUTE_WRITER + | ITEM_ATTRIBUTE_NAME | ITEM_ATTRIBUTE_ARTICLE | ITEM_ATTRIBUTE_PLURALNAME; + + public: + static bool isIntAttrType(itemAttrTypes type) { + return (type & intAttributeTypes) == type; + } + static bool isStrAttrType(itemAttrTypes type) { + return (type & stringAttributeTypes) == type; + } + inline static bool isCustomAttrType(itemAttrTypes type) { + return (type & ITEM_ATTRIBUTE_CUSTOM) == type; } const std::forward_list& getList() const { @@ -359,10 +543,10 @@ class Item : virtual public Thing bool equals(const Item* otherItem) const; - Item* getItem() final { + Item* getItem() override final { return this; } - const Item* getItem() const final { + const Item* getItem() const override final { return this; } virtual Teleport* getTeleport() { @@ -371,18 +555,18 @@ class Item : virtual public Thing virtual const Teleport* getTeleport() const { return nullptr; } + virtual TrashHolder* getTrashHolder() { + return nullptr; + } + virtual const TrashHolder* getTrashHolder() const { + return nullptr; + } virtual Mailbox* getMailbox() { return nullptr; } virtual const Mailbox* getMailbox() const { return nullptr; } - virtual DepotLocker* getDepotLocker() { - return nullptr; - } - virtual const DepotLocker* getDepotLocker() const { - return nullptr; - } virtual Door* getDoor() { return nullptr; } @@ -437,6 +621,31 @@ class Item : virtual public Thing return attributes->hasAttribute(type); } + template + void setCustomAttribute(std::string& key, R value) { + getAttributes()->setCustomAttribute(key, value); + } + + void setCustomAttribute(std::string& key, ItemAttributes::CustomAttribute& value) { + getAttributes()->setCustomAttribute(key, value); + } + + const ItemAttributes::CustomAttribute* getCustomAttribute(int64_t key) { + return getAttributes()->getCustomAttribute(key); + } + + const ItemAttributes::CustomAttribute* getCustomAttribute(const std::string& key) { + return getAttributes()->getCustomAttribute(key); + } + + bool removeCustomAttribute(int64_t key) { + return getAttributes()->removeCustomAttribute(key); + } + + bool removeCustomAttribute(const std::string& key) { + return getAttributes()->removeCustomAttribute(key); + } + void setSpecialDescription(const std::string& desc) { setStrAttr(ITEM_ATTRIBUTE_DESCRIPTION, desc); } @@ -475,6 +684,10 @@ class Item : virtual public Thing } void setActionId(uint16_t n) { + if (n < 100) { + n = 100; + } + setIntAttr(ITEM_ATTRIBUTE_ACTIONID, n); } uint16_t getActionId() const { @@ -484,14 +697,11 @@ class Item : virtual public Thing return static_cast(getIntAttr(ITEM_ATTRIBUTE_ACTIONID)); } - void setMovementID(uint16_t n) { - setIntAttr(ITEM_ATTRIBUTE_MOVEMENTID, n); - } - uint16_t getMovementId() const { + uint16_t getUniqueId() const { if (!attributes) { return 0; } - return static_cast(getIntAttr(ITEM_ATTRIBUTE_MOVEMENTID)); + return static_cast(getIntAttr(ITEM_ATTRIBUTE_UNIQUEID)); } void setCharges(uint16_t n) { @@ -557,42 +767,49 @@ class Item : virtual public Thing return static_cast(getIntAttr(ITEM_ATTRIBUTE_DECAYSTATE)); } + void setDecayTo(int32_t decayTo) { + setIntAttr(ITEM_ATTRIBUTE_DECAYTO, decayTo); + } + int32_t getDecayTo() const { + if (hasAttribute(ITEM_ATTRIBUTE_DECAYTO)) { + return getIntAttr(ITEM_ATTRIBUTE_DECAYTO); + } + return items[id].decayTo; + } + 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 getDescription(int32_t lookDistance) const override 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 bool unserializeItemNode(OTB::Loader&, const OTB::Node&, PropStream& propStream); virtual void serializeAttr(PropWriteStream& propWriteStream) const; - bool isPushable() const final { + bool isPushable() const override final { return isMoveable(); } - int32_t getThrowRange() const final { + int32_t getThrowRange() const override final { return (isPickupable() ? 15 : 2); } uint16_t getID() const { return id; } + uint16_t getClientID() const { + return items[id].clientId; + } 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; } @@ -605,28 +822,7 @@ class Item : virtual public Thing } 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)) { @@ -652,15 +848,24 @@ class Item : virtual public Thing } return items[id].defense; } + int32_t getExtraDefense() const { + if (hasAttribute(ITEM_ATTRIBUTE_EXTRADEFENSE)) { + return getIntAttr(ITEM_ATTRIBUTE_EXTRADEFENSE); + } + return items[id].extraDefense; + } int32_t getSlotPosition() const { return items[id].slotPosition; } - uint16_t getDisguiseId() const { - return items[id].disguiseId; + int8_t getHitChance() const { + if (hasAttribute(ITEM_ATTRIBUTE_HITCHANCE)) { + return getIntAttr(ITEM_ATTRIBUTE_HITCHANCE); + } + return items[id].hitChance; } uint32_t getWorth() const; - void getLight(LightInfo& lightInfo) const; + LightInfo getLightInfo() const; bool hasProperty(ITEMPROPERTY prop) const; bool isBlocking() const { @@ -678,15 +883,15 @@ class Item : virtual public Thing 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 isUseable() const { + return items[id].useable; + } bool isHangable() const { return items[id].isHangable; } @@ -694,33 +899,10 @@ class Item : virtual public Thing 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; - } - bool isRune() const { - return items[id].isRune(); + bool hasWalkStack() const { + return items[id].walkStack; } + const std::string& getName() const { if (hasAttribute(ITEM_ATTRIBUTE_NAME)) { return getStrAttr(ITEM_ATTRIBUTE_NAME); @@ -748,12 +930,20 @@ class Item : virtual public Thing count = n; } - static uint32_t countByType(const Item* i, int32_t subType); + static uint32_t countByType(const Item* i, int32_t subType) { + if (subType == -1 || subType == i->getSubType()) { + return i->getItemCount(); + } + + return 0; + } void setDefaultSubtype(); uint16_t getSubType() const; void setSubType(uint16_t n); + void setUniqueId(uint16_t n); + void setDefaultDuration() { uint32_t duration = getDefaultDuration(); if (duration != 0) { @@ -776,13 +966,18 @@ class Item : virtual public Thing virtual void startDecaying(); + bool isLoadedFromMap() const { + return loadedFromMap; + } void setLoadedFromMap(bool value) { loadedFromMap = value; } bool isCleanable() const { - return !loadedFromMap && canRemove() && isPickupable() && !hasAttribute(ITEM_ATTRIBUTE_ACTIONID); + return !loadedFromMap && canRemove() && isPickupable() && !hasAttribute(ITEM_ATTRIBUTE_UNIQUEID) && !hasAttribute(ITEM_ATTRIBUTE_ACTIONID); } + bool hasMarketAttributes() const; + std::unique_ptr& getAttributes() { if (!attributes) { attributes.reset(new ItemAttributes()); @@ -799,29 +994,32 @@ class Item : virtual public Thing } } - Cylinder* getParent() const { + Cylinder* getParent() const override { return parent; } - void setParent(Cylinder* cylinder) { + void setParent(Cylinder* cylinder) override { parent = cylinder; } Cylinder* getTopParent(); const Cylinder* getTopParent() const; - Tile* getTile(); - const Tile* getTile() const; - bool isRemoved() const { + Tile* getTile() override; + const Tile* getTile() const override; + bool isRemoved() const override { return !parent || parent->isRemoved(); } protected: + Cylinder* parent = nullptr; + + uint16_t id; // the same id as in ItemType + + private: std::string getWeightDescription(uint32_t weight) const; - Cylinder* parent = nullptr; std::unique_ptr 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; @@ -829,16 +1027,7 @@ class Item : virtual public Thing //Don't add variables here, use the ItemAttribute class. }; -typedef std::list ItemList; -typedef std::deque ItemDeque; - -inline uint32_t Item::countByType(const Item* i, int32_t subType) -{ - if (subType == -1 || subType == i->getSubType()) { - return i->getItemCount(); - } - - return 0; -} +using ItemList = std::list; +using ItemDeque = std::deque; #endif diff --git a/src/itemloader.h b/src/itemloader.h new file mode 100644 index 0000000..159ee2a --- /dev/null +++ b/src/itemloader.h @@ -0,0 +1,198 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_ITEMLOADER_H_107F1D3EECC94CD0A0F528843010D5D4 +#define FS_ITEMLOADER_H_107F1D3EECC94CD0A0F528843010D5D4 + +#include "fileloader.h" + +enum itemgroup_t { + ITEM_GROUP_NONE, + + ITEM_GROUP_GROUND, + ITEM_GROUP_CONTAINER, + ITEM_GROUP_WEAPON, //deprecated + ITEM_GROUP_AMMUNITION, //deprecated + ITEM_GROUP_ARMOR, //deprecated + ITEM_GROUP_CHARGES, + ITEM_GROUP_TELEPORT, //deprecated + ITEM_GROUP_MAGICFIELD, //deprecated + ITEM_GROUP_WRITEABLE, //deprecated + ITEM_GROUP_KEY, //deprecated + ITEM_GROUP_SPLASH, + ITEM_GROUP_FLUID, + ITEM_GROUP_DOOR, //deprecated + ITEM_GROUP_DEPRECATED, + + ITEM_GROUP_LAST +}; + +/////////OTB specific////////////// +enum clientVersion_t { + CLIENT_VERSION_750 = 1, + CLIENT_VERSION_755 = 2, + CLIENT_VERSION_760 = 3, + CLIENT_VERSION_770 = 3, + CLIENT_VERSION_780 = 4, + CLIENT_VERSION_790 = 5, + CLIENT_VERSION_792 = 6, + CLIENT_VERSION_800 = 7, + CLIENT_VERSION_810 = 8, + CLIENT_VERSION_811 = 9, + CLIENT_VERSION_820 = 10, + CLIENT_VERSION_830 = 11, + CLIENT_VERSION_840 = 12, + CLIENT_VERSION_841 = 13, + CLIENT_VERSION_842 = 14, + CLIENT_VERSION_850 = 15, + CLIENT_VERSION_854_BAD = 16, + CLIENT_VERSION_854 = 17, + CLIENT_VERSION_855 = 18, + CLIENT_VERSION_860_OLD = 19, + CLIENT_VERSION_860 = 20, + CLIENT_VERSION_861 = 21, + CLIENT_VERSION_862 = 22, + CLIENT_VERSION_870 = 23, + CLIENT_VERSION_871 = 24, + CLIENT_VERSION_872 = 25, + CLIENT_VERSION_873 = 26, + CLIENT_VERSION_900 = 27, + CLIENT_VERSION_910 = 28, + CLIENT_VERSION_920 = 29, + CLIENT_VERSION_940 = 30, + CLIENT_VERSION_944_V1 = 31, + CLIENT_VERSION_944_V2 = 32, + CLIENT_VERSION_944_V3 = 33, + CLIENT_VERSION_944_V4 = 34, + CLIENT_VERSION_946 = 35, + CLIENT_VERSION_950 = 36, + CLIENT_VERSION_952 = 37, + CLIENT_VERSION_953 = 38, + CLIENT_VERSION_954 = 39, + CLIENT_VERSION_960 = 40, + CLIENT_VERSION_961 = 41, + CLIENT_VERSION_963 = 42, + CLIENT_VERSION_970 = 43, + CLIENT_VERSION_980 = 44, + CLIENT_VERSION_981 = 45, + CLIENT_VERSION_982 = 46, + CLIENT_VERSION_983 = 47, + CLIENT_VERSION_985 = 48, + CLIENT_VERSION_986 = 49, + CLIENT_VERSION_1010 = 50, + CLIENT_VERSION_1020 = 51, + CLIENT_VERSION_1021 = 52, + CLIENT_VERSION_1030 = 53, + CLIENT_VERSION_1031 = 54, + CLIENT_VERSION_1035 = 55, + CLIENT_VERSION_1076 = 56, + CLIENT_VERSION_1098 = 57, +}; + +enum rootattrib_ { + ROOT_ATTR_VERSION = 0x01, +}; + +enum itemattrib_t { + ITEM_ATTR_FIRST = 0x10, + ITEM_ATTR_SERVERID = ITEM_ATTR_FIRST, + ITEM_ATTR_CLIENTID, + ITEM_ATTR_NAME, + ITEM_ATTR_DESCR, + ITEM_ATTR_SPEED, + ITEM_ATTR_SLOT, + ITEM_ATTR_MAXITEMS, + ITEM_ATTR_WEIGHT, + ITEM_ATTR_WEAPON, + ITEM_ATTR_AMU, + ITEM_ATTR_ARMOR, + ITEM_ATTR_MAGLEVEL, + ITEM_ATTR_MAGFIELDTYPE, + ITEM_ATTR_WRITEABLE, + ITEM_ATTR_ROTATETO, + ITEM_ATTR_DECAY, + ITEM_ATTR_SPRITEHASH, + ITEM_ATTR_MINIMAPCOLOR, + ITEM_ATTR_07, + ITEM_ATTR_08, + ITEM_ATTR_LIGHT, + + //1-byte aligned + ITEM_ATTR_DECAY2, //deprecated + ITEM_ATTR_WEAPON2, //deprecated + ITEM_ATTR_AMU2, //deprecated + ITEM_ATTR_ARMOR2, //deprecated + ITEM_ATTR_WRITEABLE2, //deprecated + ITEM_ATTR_LIGHT2, + ITEM_ATTR_TOPORDER, + ITEM_ATTR_WRITEABLE3, //deprecated + + ITEM_ATTR_WAREID, + + ITEM_ATTR_LAST +}; + +enum itemflags_t { + FLAG_BLOCK_SOLID = 1 << 0, + FLAG_BLOCK_PROJECTILE = 1 << 1, + FLAG_BLOCK_PATHFIND = 1 << 2, + FLAG_HAS_HEIGHT = 1 << 3, + FLAG_USEABLE = 1 << 4, + FLAG_PICKUPABLE = 1 << 5, + FLAG_MOVEABLE = 1 << 6, + FLAG_STACKABLE = 1 << 7, + FLAG_FLOORCHANGEDOWN = 1 << 8, // unused + FLAG_FLOORCHANGENORTH = 1 << 9, // unused + FLAG_FLOORCHANGEEAST = 1 << 10, // unused + FLAG_FLOORCHANGESOUTH = 1 << 11, // unused + FLAG_FLOORCHANGEWEST = 1 << 12, // unused + FLAG_ALWAYSONTOP = 1 << 13, + FLAG_READABLE = 1 << 14, + FLAG_ROTATABLE = 1 << 15, + FLAG_HANGABLE = 1 << 16, + FLAG_VERTICAL = 1 << 17, + FLAG_HORIZONTAL = 1 << 18, + FLAG_CANNOTDECAY = 1 << 19, // unused + FLAG_ALLOWDISTREAD = 1 << 20, + FLAG_UNUSED = 1 << 21, // unused + FLAG_CLIENTCHARGES = 1 << 22, /* deprecated */ + FLAG_LOOKTHROUGH = 1 << 23, + FLAG_ANIMATION = 1 << 24, + FLAG_FULLTILE = 1 << 25, // unused + FLAG_FORCEUSE = 1 << 26, +}; + +//1-byte aligned structs +#pragma pack(1) + +struct VERSIONINFO { + uint32_t dwMajorVersion; + uint32_t dwMinorVersion; + uint32_t dwBuildNumber; + uint8_t CSDVersion[128]; +}; + +struct lightBlock2 { + uint16_t lightLevel; + uint16_t lightColor; +}; + +#pragma pack() +/////////OTB specific////////////// +#endif diff --git a/src/items.cpp b/src/items.cpp index 8259ff0..fb5cc3c 100644 --- a/src/items.cpp +++ b/src/items.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,562 +22,1323 @@ #include "items.h" #include "spells.h" #include "movement.h" -#include "script.h" +#include "weapons.h" #include "pugicast.h" extern MoveEvents* g_moveEvents; +extern Weapons* g_weapons; + +const std::unordered_map ItemParseAttributesMap = { + {"type", ITEM_PARSE_TYPE}, + {"description", ITEM_PARSE_DESCRIPTION}, + {"runespellname", ITEM_PARSE_RUNESPELLNAME}, + {"weight", ITEM_PARSE_WEIGHT}, + {"showcount", ITEM_PARSE_SHOWCOUNT}, + {"armor", ITEM_PARSE_ARMOR}, + {"defense", ITEM_PARSE_DEFENSE}, + {"extradef", ITEM_PARSE_EXTRADEF}, + {"attack", ITEM_PARSE_ATTACK}, + {"rotateto", ITEM_PARSE_ROTATETO}, + {"moveable", ITEM_PARSE_MOVEABLE}, + {"movable", ITEM_PARSE_MOVEABLE}, + {"blockprojectile", ITEM_PARSE_BLOCKPROJECTILE}, + {"allowpickupable", ITEM_PARSE_PICKUPABLE}, + {"pickupable", ITEM_PARSE_PICKUPABLE}, + {"forceserialize", ITEM_PARSE_FORCESERIALIZE}, + {"forcesave", ITEM_PARSE_FORCESERIALIZE}, + {"floorchange", ITEM_PARSE_FLOORCHANGE}, + {"corpsetype", ITEM_PARSE_CORPSETYPE}, + {"containersize", ITEM_PARSE_CONTAINERSIZE}, + {"fluidsource", ITEM_PARSE_FLUIDSOURCE}, + {"readable", ITEM_PARSE_READABLE}, + {"writeable", ITEM_PARSE_WRITEABLE}, + {"maxtextlen", ITEM_PARSE_MAXTEXTLEN}, + {"writeonceitemid", ITEM_PARSE_WRITEONCEITEMID}, + {"weapontype", ITEM_PARSE_WEAPONTYPE}, + {"slottype", ITEM_PARSE_SLOTTYPE}, + {"ammotype", ITEM_PARSE_AMMOTYPE}, + {"shoottype", ITEM_PARSE_SHOOTTYPE}, + {"effect", ITEM_PARSE_EFFECT}, + {"range", ITEM_PARSE_RANGE}, + {"stopduration", ITEM_PARSE_STOPDURATION}, + {"decayto", ITEM_PARSE_DECAYTO}, + {"transformequipto", ITEM_PARSE_TRANSFORMEQUIPTO}, + {"transformdeequipto", ITEM_PARSE_TRANSFORMDEEQUIPTO}, + {"duration", ITEM_PARSE_DURATION}, + {"showduration", ITEM_PARSE_SHOWDURATION}, + {"charges", ITEM_PARSE_CHARGES}, + {"showcharges", ITEM_PARSE_SHOWCHARGES}, + {"showattributes", ITEM_PARSE_SHOWATTRIBUTES}, + {"hitchance", ITEM_PARSE_HITCHANCE}, + {"maxhitchance", ITEM_PARSE_MAXHITCHANCE}, + {"invisible", ITEM_PARSE_INVISIBLE}, + {"speed", ITEM_PARSE_SPEED}, + {"healthgain", ITEM_PARSE_HEALTHGAIN}, + {"healthticks", ITEM_PARSE_HEALTHTICKS}, + {"managain", ITEM_PARSE_MANAGAIN}, + {"manaticks", ITEM_PARSE_MANATICKS}, + {"manashield", ITEM_PARSE_MANASHIELD}, + {"skillsword", ITEM_PARSE_SKILLSWORD}, + {"skillaxe", ITEM_PARSE_SKILLAXE}, + {"skillclub", ITEM_PARSE_SKILLCLUB}, + {"skilldist", ITEM_PARSE_SKILLDIST}, + {"skillfish", ITEM_PARSE_SKILLFISH}, + {"skillshield", ITEM_PARSE_SKILLSHIELD}, + {"skillfist", ITEM_PARSE_SKILLFIST}, + {"maxhitpoints", ITEM_PARSE_MAXHITPOINTS}, + {"maxhitpointspercent", ITEM_PARSE_MAXHITPOINTSPERCENT}, + {"maxmanapoints", ITEM_PARSE_MAXMANAPOINTS}, + {"maxmanapointspercent", ITEM_PARSE_MAXMANAPOINTSPERCENT}, + {"magicpoints", ITEM_PARSE_MAGICPOINTS}, + {"magiclevelpoints", ITEM_PARSE_MAGICPOINTS}, + {"magicpointspercent", ITEM_PARSE_MAGICPOINTSPERCENT}, + {"criticalhitchance", ITEM_PARSE_CRITICALHITCHANCE}, + {"criticalhitamount", ITEM_PARSE_CRITICALHITAMOUNT}, + {"lifeleechchance", ITEM_PARSE_LIFELEECHCHANCE}, + {"lifeleechamount", ITEM_PARSE_LIFELEECHAMOUNT}, + {"manaleechchance", ITEM_PARSE_MANALEECHCHANCE}, + {"manaleechamount", ITEM_PARSE_MANALEECHAMOUNT}, + {"fieldabsorbpercentenergy", ITEM_PARSE_FIELDABSORBPERCENTENERGY}, + {"fieldabsorbpercentfire", ITEM_PARSE_FIELDABSORBPERCENTFIRE}, + {"fieldabsorbpercentpoison", ITEM_PARSE_FIELDABSORBPERCENTPOISON}, + {"fieldabsorbpercentearth", ITEM_PARSE_FIELDABSORBPERCENTPOISON}, + {"absorbpercentall", ITEM_PARSE_ABSORBPERCENTALL}, + {"absorbpercentallelements", ITEM_PARSE_ABSORBPERCENTALL}, + {"absorbpercentelements", ITEM_PARSE_ABSORBPERCENTELEMENTS}, + {"absorbpercentmagic", ITEM_PARSE_ABSORBPERCENTMAGIC}, + {"absorbpercentenergy", ITEM_PARSE_ABSORBPERCENTENERGY}, + {"absorbpercentfire", ITEM_PARSE_ABSORBPERCENTFIRE}, + {"absorbpercentpoison", ITEM_PARSE_ABSORBPERCENTPOISON}, + {"absorbpercentearth", ITEM_PARSE_ABSORBPERCENTPOISON}, + {"absorbpercentice", ITEM_PARSE_ABSORBPERCENTICE}, + {"absorbpercentholy", ITEM_PARSE_ABSORBPERCENTHOLY}, + {"absorbpercentdeath", ITEM_PARSE_ABSORBPERCENTDEATH}, + {"absorbpercentlifedrain", ITEM_PARSE_ABSORBPERCENTLIFEDRAIN}, + {"absorbpercentmanadrain", ITEM_PARSE_ABSORBPERCENTMANADRAIN}, + {"absorbpercentdrown", ITEM_PARSE_ABSORBPERCENTDROWN}, + {"absorbpercentphysical", ITEM_PARSE_ABSORBPERCENTPHYSICAL}, + {"absorbpercenthealing", ITEM_PARSE_ABSORBPERCENTHEALING}, + {"absorbpercentundefined", ITEM_PARSE_ABSORBPERCENTUNDEFINED}, + {"suppressdrunk", ITEM_PARSE_SUPPRESSDRUNK}, + {"suppressenergy", ITEM_PARSE_SUPPRESSENERGY}, + {"suppressfire", ITEM_PARSE_SUPPRESSFIRE}, + {"suppresspoison", ITEM_PARSE_SUPPRESSPOISON}, + {"suppressdrown", ITEM_PARSE_SUPPRESSDROWN}, + {"suppressphysical", ITEM_PARSE_SUPPRESSPHYSICAL}, + {"suppressfreeze", ITEM_PARSE_SUPPRESSFREEZE}, + {"suppressdazzle", ITEM_PARSE_SUPPRESSDAZZLE}, + {"suppresscurse", ITEM_PARSE_SUPPRESSCURSE}, + {"field", ITEM_PARSE_FIELD}, + {"replaceable", ITEM_PARSE_REPLACEABLE}, + {"partnerdirection", ITEM_PARSE_PARTNERDIRECTION}, + {"leveldoor", ITEM_PARSE_LEVELDOOR}, + {"maletransformto", ITEM_PARSE_MALETRANSFORMTO}, + {"malesleeper", ITEM_PARSE_MALETRANSFORMTO}, + {"femaletransformto", ITEM_PARSE_FEMALETRANSFORMTO}, + {"femalesleeper", ITEM_PARSE_FEMALETRANSFORMTO}, + {"transformto", ITEM_PARSE_TRANSFORMTO}, + {"destroyto", ITEM_PARSE_DESTROYTO}, + {"elementice", ITEM_PARSE_ELEMENTICE}, + {"elementearth", ITEM_PARSE_ELEMENTEARTH}, + {"elementfire", ITEM_PARSE_ELEMENTFIRE}, + {"elementenergy", ITEM_PARSE_ELEMENTENERGY}, + {"walkstack", ITEM_PARSE_WALKSTACK}, + {"blocking", ITEM_PARSE_BLOCKING}, + {"allowdistread", ITEM_PARSE_ALLOWDISTREAD}, +}; + +const std::unordered_map ItemTypesMap = { + {"key", ITEM_TYPE_KEY}, + {"magicfield", ITEM_TYPE_MAGICFIELD}, + {"container", ITEM_TYPE_CONTAINER}, + {"depot", ITEM_TYPE_DEPOT}, + {"mailbox", ITEM_TYPE_MAILBOX}, + {"trashholder", ITEM_TYPE_TRASHHOLDER}, + {"teleport", ITEM_TYPE_TELEPORT}, + {"door", ITEM_TYPE_DOOR}, + {"bed", ITEM_TYPE_BED}, + {"rune", ITEM_TYPE_RUNE}, +}; + +const std::unordered_map TileStatesMap = { + {"down", TILESTATE_FLOORCHANGE_DOWN}, + {"north", TILESTATE_FLOORCHANGE_NORTH}, + {"south", TILESTATE_FLOORCHANGE_SOUTH}, + {"southalt", TILESTATE_FLOORCHANGE_SOUTH_ALT}, + {"west", TILESTATE_FLOORCHANGE_WEST}, + {"east", TILESTATE_FLOORCHANGE_EAST}, + {"eastalt", TILESTATE_FLOORCHANGE_EAST_ALT}, +}; + +const std::unordered_map RaceTypesMap = { + {"venom", RACE_VENOM}, + {"blood", RACE_BLOOD}, + {"undead", RACE_UNDEAD}, + {"fire", RACE_FIRE}, + {"energy", RACE_ENERGY}, +}; + +const std::unordered_map WeaponTypesMap = { + {"sword", WEAPON_SWORD}, + {"club", WEAPON_CLUB}, + {"axe", WEAPON_AXE}, + {"shield", WEAPON_SHIELD}, + {"distance", WEAPON_DISTANCE}, + {"wand", WEAPON_WAND}, + {"ammunition", WEAPON_AMMO}, +}; + +const std::unordered_map FluidTypesMap = { + {"water", FLUID_WATER}, + {"blood", FLUID_BLOOD}, + {"beer", FLUID_BEER}, + {"slime", FLUID_SLIME}, + {"lemonade", FLUID_LEMONADE}, + {"milk", FLUID_MILK}, + {"mana", FLUID_MANA}, + {"life", FLUID_LIFE}, + {"oil", FLUID_OIL}, + {"urine", FLUID_URINE}, + {"coconut", FLUID_COCONUTMILK}, + {"wine", FLUID_WINE}, + {"mud", FLUID_MUD}, + {"fruitjuice", FLUID_FRUITJUICE}, + {"lava", FLUID_LAVA}, + {"rum", FLUID_RUM}, + {"swamp", FLUID_SWAMP}, + {"tea", FLUID_TEA}, + {"mead", FLUID_MEAD}, +}; + Items::Items() { - items.reserve(6000); - nameToItems.reserve(6000); + items.reserve(30000); + nameToItems.reserve(30000); } void Items::clear() { items.clear(); + reverseItemMap.clear(); nameToItems.clear(); } bool Items::reload() { clear(); - if (!loadItems()) { + loadFromOtb("data/items/items.otb"); + + if (!loadFromXml()) { return false; } g_moveEvents->reload(); + g_weapons->reload(); + g_weapons->loadDefaults(); return true; } -bool Items::loadItems() +constexpr auto OTBI = OTB::Identifier{{'O','T', 'B', 'I'}}; + +bool Items::loadFromOtb(const std::string& file) { - ScriptReader script; - if (!script.open("data/items/items.srv")) { - return false; - } + OTB::Loader loader{file, OTBI}; - std::string identifier; - uint16_t id = 0; - while (true) { - script.nextToken(); - if (script.Token == ENDOFFILE) { - break; - } + auto& root = loader.parseTree(); - if (script.Token != IDENTIFIER) { - script.error("Identifier expected"); + PropStream props; + if (loader.getProps(root, props)) { + //4 byte flags + //attributes + //0x01 = version data + uint32_t flags; + if (!props.read(flags)) { return false; } - identifier = script.getIdentifier(); - script.readSymbol('='); + uint8_t attr; + if (!props.read(attr)) { + return false; + } - if (identifier == "typeid") { - id = script.readNumber(); - if (id >= items.size()) { - items.resize(id + 1); - } - - if (items[id].id) { - script.error("item type already defined"); + if (attr == ROOT_ATTR_VERSION) { + uint16_t datalen; + if (!props.read(datalen)) { 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; - } + if (datalen != sizeof(VERSIONINFO)) { + return false; + } - 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"); + VERSIONINFO vi; + if (!props.read(vi)) { + return false; + } + + majorVersion = vi.dwMajorVersion; //items otb format file version + minorVersion = vi.dwMinorVersion; //client version + buildNumber = vi.dwBuildNumber; //revision + } + } + + if (majorVersion == 0xFFFFFFFF) { + std::cout << "[Warning - Items::loadFromOtb] items.otb using generic client version." << std::endl; + } else if (majorVersion != 3) { + std::cout << "Old version detected, a newer version of items.otb is required." << std::endl; + return false; + } else if (minorVersion < CLIENT_VERSION_1098) { + std::cout << "A newer version of items.otb is required." << std::endl; + return false; + } + + for (auto& itemNode : root.children) { + PropStream stream; + if (!loader.getProps(itemNode, stream)) { + return false; + } + + uint32_t flags; + if (!stream.read(flags)) { + return false; + } + + uint16_t serverId = 0; + uint16_t clientId = 0; + uint16_t speed = 0; + uint16_t wareId = 0; + uint8_t lightLevel = 0; + uint8_t lightColor = 0; + uint8_t alwaysOnTopOrder = 0; + + uint8_t attrib; + while (stream.read(attrib)) { + uint16_t datalen; + if (!stream.read(datalen)) { + return false; + } + + switch (attrib) { + case ITEM_ATTR_SERVERID: { + if (datalen != sizeof(uint16_t)) { return false; } - } - if (script.getSpecial() == '}') { + if (!stream.read(serverId)) { + return false; + } + + if (serverId > 30000 && serverId < 30100) { + serverId -= 30000; + } 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; + case ITEM_ATTR_CLIENTID: { + if (datalen != sizeof(uint16_t)) { + return false; } - identifier = script.getIdentifier(); - script.readSymbol('='); + if (!stream.read(clientId)) { + return false; + } + break; + } - 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; + case ITEM_ATTR_SPEED: { + if (datalen != sizeof(uint16_t)) { + return false; + } - std::list vocationStringList; + if (!stream.read(speed)) { + return false; + } + break; + } - if (hasBitSet(VOCATION_SORCERER, vocations)) { - vocationStringList.push_back("sorcerer"); + case ITEM_ATTR_LIGHT2: { + if (datalen != sizeof(lightBlock2)) { + return false; + } + + lightBlock2 lb2; + if (!stream.read(lb2)) { + return false; + } + + lightLevel = static_cast(lb2.lightLevel); + lightColor = static_cast(lb2.lightColor); + break; + } + + case ITEM_ATTR_TOPORDER: { + if (datalen != sizeof(uint8_t)) { + return false; + } + + if (!stream.read(alwaysOnTopOrder)) { + return false; + } + break; + } + + case ITEM_ATTR_WAREID: { + if (datalen != sizeof(uint16_t)) { + return false; + } + + if (!stream.read(wareId)) { + return false; + } + break; + } + + default: { + //skip unknown attributes + if (!stream.skip(datalen)) { + return false; + } + break; + } + } + } + + reverseItemMap.emplace(clientId, serverId); + + // store the found item + if (serverId >= items.size()) { + items.resize(serverId + 1); + } + ItemType& iType = items[serverId]; + + iType.group = static_cast(itemNode.type); + switch (itemNode.type) { + case ITEM_GROUP_CONTAINER: + iType.type = ITEM_TYPE_CONTAINER; + break; + case ITEM_GROUP_DOOR: + //not used + iType.type = ITEM_TYPE_DOOR; + break; + case ITEM_GROUP_MAGICFIELD: + //not used + iType.type = ITEM_TYPE_MAGICFIELD; + break; + case ITEM_GROUP_TELEPORT: + //not used + iType.type = ITEM_TYPE_TELEPORT; + break; + case ITEM_GROUP_NONE: + case ITEM_GROUP_GROUND: + case ITEM_GROUP_SPLASH: + case ITEM_GROUP_FLUID: + case ITEM_GROUP_CHARGES: + case ITEM_GROUP_DEPRECATED: + break; + default: + return false; + } + + iType.blockSolid = hasBitSet(FLAG_BLOCK_SOLID, flags); + iType.blockProjectile = hasBitSet(FLAG_BLOCK_PROJECTILE, flags); + iType.blockPathFind = hasBitSet(FLAG_BLOCK_PATHFIND, flags); + iType.hasHeight = hasBitSet(FLAG_HAS_HEIGHT, flags); + iType.useable = hasBitSet(FLAG_USEABLE, flags); + iType.pickupable = hasBitSet(FLAG_PICKUPABLE, flags); + iType.moveable = hasBitSet(FLAG_MOVEABLE, flags); + iType.stackable = hasBitSet(FLAG_STACKABLE, flags); + + iType.alwaysOnTop = hasBitSet(FLAG_ALWAYSONTOP, flags); + iType.isVertical = hasBitSet(FLAG_VERTICAL, flags); + iType.isHorizontal = hasBitSet(FLAG_HORIZONTAL, flags); + iType.isHangable = hasBitSet(FLAG_HANGABLE, flags); + iType.allowDistRead = hasBitSet(FLAG_ALLOWDISTREAD, flags); + iType.rotatable = hasBitSet(FLAG_ROTATABLE, flags); + iType.canReadText = hasBitSet(FLAG_READABLE, flags); + iType.lookThrough = hasBitSet(FLAG_LOOKTHROUGH, flags); + iType.isAnimation = hasBitSet(FLAG_ANIMATION, flags); + // iType.walkStack = !hasBitSet(FLAG_FULLTILE, flags); + iType.forceUse = hasBitSet(FLAG_FORCEUSE, flags); + + iType.id = serverId; + iType.clientId = clientId; + iType.speed = speed; + iType.lightLevel = lightLevel; + iType.lightColor = lightColor; + iType.wareId = wareId; + iType.alwaysOnTopOrder = alwaysOnTopOrder; + } + + items.shrink_to_fit(); + return true; +} + +bool Items::loadFromXml() +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file("data/items/items.xml"); + if (!result) { + printXMLError("Error - Items::loadFromXml", "data/items/items.xml", result); + return false; + } + + for (auto itemNode : doc.child("items").children()) { + pugi::xml_attribute idAttribute = itemNode.attribute("id"); + if (idAttribute) { + parseItemNode(itemNode, pugi::cast(idAttribute.value())); + continue; + } + + pugi::xml_attribute fromIdAttribute = itemNode.attribute("fromid"); + if (!fromIdAttribute) { + std::cout << "[Warning - Items::loadFromXml] No item id found" << std::endl; + continue; + } + + pugi::xml_attribute toIdAttribute = itemNode.attribute("toid"); + if (!toIdAttribute) { + std::cout << "[Warning - Items::loadFromXml] fromid (" << fromIdAttribute.value() << ") without toid" << std::endl; + continue; + } + + uint16_t id = pugi::cast(fromIdAttribute.value()); + uint16_t toId = pugi::cast(toIdAttribute.value()); + while (id <= toId) { + parseItemNode(itemNode, id++); + } + } + + buildInventoryList(); + return true; +} + +void Items::buildInventoryList() +{ + inventory.reserve(items.size()); + for (const auto& type: items) { + if (type.weaponType != WEAPON_NONE || type.ammoType != AMMO_NONE || + type.attack != 0 || type.defense != 0 || + type.extraDefense != 0 || type.armor != 0 || + type.slotPosition & SLOTP_NECKLACE || + type.slotPosition & SLOTP_RING || + type.slotPosition & SLOTP_AMMO || + type.slotPosition & SLOTP_FEET || + type.slotPosition & SLOTP_HEAD || + type.slotPosition & SLOTP_ARMOR || + type.slotPosition & SLOTP_LEGS) + { + inventory.push_back(type.clientId); + } + } + inventory.shrink_to_fit(); + std::sort(inventory.begin(), inventory.end()); +} + +void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) +{ + if (id > 30000 && id < 30100) { + id -= 30000; + + if (id >= items.size()) { + items.resize(id + 1); + } + ItemType& iType = items[id]; + iType.id = id; + } + + ItemType& it = getItemType(id); + if (it.id == 0) { + return; + } + + it.name = itemNode.attribute("name").as_string(); + + nameToItems.insert({ asLowerCaseString(it.name), id }); + + pugi::xml_attribute articleAttribute = itemNode.attribute("article"); + if (articleAttribute) { + it.article = articleAttribute.as_string(); + } + + pugi::xml_attribute pluralAttribute = itemNode.attribute("plural"); + if (pluralAttribute) { + it.pluralName = pluralAttribute.as_string(); + } + + Abilities& abilities = it.getAbilities(); + + for (auto attributeNode : itemNode.children()) { + pugi::xml_attribute keyAttribute = attributeNode.attribute("key"); + if (!keyAttribute) { + continue; + } + + pugi::xml_attribute valueAttribute = attributeNode.attribute("value"); + if (!valueAttribute) { + continue; + } + + std::string tmpStrValue = asLowerCaseString(keyAttribute.as_string()); + auto parseAttribute = ItemParseAttributesMap.find(tmpStrValue); + if (parseAttribute != ItemParseAttributesMap.end()) { + ItemParseAttributes_t parseType = parseAttribute->second; + switch (parseType) { + case ITEM_PARSE_TYPE: { + tmpStrValue = asLowerCaseString(valueAttribute.as_string()); + auto it2 = ItemTypesMap.find(tmpStrValue); + if (it2 != ItemTypesMap.end()) { + it.type = it2->second; + if (it.type == ITEM_TYPE_CONTAINER) { + it.group = ITEM_GROUP_CONTAINER; } + } else { + std::cout << "[Warning - Items::parseItemNode] Unknown type: " << valueAttribute.as_string() << std::endl; + } + break; + } - if (hasBitSet(VOCATION_DRUID, vocations)) { - vocationStringList.push_back("druid"); - } + case ITEM_PARSE_DESCRIPTION: { + it.description = valueAttribute.as_string(); + break; + } - if (hasBitSet(VOCATION_PALADIN, vocations)) { - vocationStringList.push_back("paladin"); - } + case ITEM_PARSE_RUNESPELLNAME: { + it.runeSpellName = valueAttribute.as_string(); + break; + } - if (hasBitSet(VOCATION_KNIGHT, vocations)) { - vocationStringList.push_back("knight"); - } + case ITEM_PARSE_WEIGHT: { + it.weight = pugi::cast(valueAttribute.value()); + break; + } - 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 "; - } + case ITEM_PARSE_SHOWCOUNT: { + it.showCount = valueAttribute.as_bool(); + break; + } + + case ITEM_PARSE_ARMOR: { + it.armor = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_DEFENSE: { + it.defense = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_EXTRADEF: { + it.extraDefense = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_ATTACK: { + it.attack = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_ROTATETO: { + it.rotateTo = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MOVEABLE: { + it.moveable = valueAttribute.as_bool(); + break; + } + + case ITEM_PARSE_BLOCKPROJECTILE: { + it.blockProjectile = valueAttribute.as_bool(); + break; + } + + case ITEM_PARSE_PICKUPABLE: { + it.allowPickupable = valueAttribute.as_bool(); + break; + } + + case ITEM_PARSE_FORCESERIALIZE: { + it.forceSerialize = valueAttribute.as_bool(); + break; + } + + case ITEM_PARSE_FLOORCHANGE: { + tmpStrValue = asLowerCaseString(valueAttribute.as_string()); + auto it2 = TileStatesMap.find(tmpStrValue); + if (it2 != TileStatesMap.end()) { + it.floorChange |= it2->second; + } else { + std::cout << "[Warning - Items::parseItemNode] Unknown floorChange: " << valueAttribute.as_string() << std::endl; + } + break; + } + + case ITEM_PARSE_CORPSETYPE: { + tmpStrValue = asLowerCaseString(valueAttribute.as_string()); + auto it2 = RaceTypesMap.find(tmpStrValue); + if (it2 != RaceTypesMap.end()) { + it.corpseType = it2->second; + } else { + std::cout << "[Warning - Items::parseItemNode] Unknown corpseType: " << valueAttribute.as_string() << std::endl; + } + break; + } + + case ITEM_PARSE_CONTAINERSIZE: { + it.maxItems = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_FLUIDSOURCE: { + tmpStrValue = asLowerCaseString(valueAttribute.as_string()); + auto it2 = FluidTypesMap.find(tmpStrValue); + if (it2 != FluidTypesMap.end()) { + it.fluidSource = it2->second; + } else { + std::cout << "[Warning - Items::parseItemNode] Unknown fluidSource: " << valueAttribute.as_string() << std::endl; + } + break; + } + + case ITEM_PARSE_READABLE: { + it.canReadText = valueAttribute.as_bool(); + break; + } + + case ITEM_PARSE_WRITEABLE: { + it.canWriteText = valueAttribute.as_bool(); + it.canReadText = it.canWriteText; + break; + } + + case ITEM_PARSE_MAXTEXTLEN: { + it.maxTextLen = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_WRITEONCEITEMID: { + it.writeOnceItemId = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_WEAPONTYPE: { + tmpStrValue = asLowerCaseString(valueAttribute.as_string()); + auto it2 = WeaponTypesMap.find(tmpStrValue); + if (it2 != WeaponTypesMap.end()) { + it.weaponType = it2->second; + } else { + std::cout << "[Warning - Items::parseItemNode] Unknown weaponType: " << valueAttribute.as_string() << std::endl; + } + break; + } + + case ITEM_PARSE_SLOTTYPE: { + tmpStrValue = asLowerCaseString(valueAttribute.as_string()); + if (tmpStrValue == "head") { + it.slotPosition |= SLOTP_HEAD; + } else if (tmpStrValue == "body") { + it.slotPosition |= SLOTP_ARMOR; + } else if (tmpStrValue == "legs") { + it.slotPosition |= SLOTP_LEGS; + } else if (tmpStrValue == "feet") { + it.slotPosition |= SLOTP_FEET; + } else if (tmpStrValue == "backpack") { + it.slotPosition |= SLOTP_BACKPACK; + } else if (tmpStrValue == "two-handed") { + it.slotPosition |= SLOTP_TWO_HAND; + } else if (tmpStrValue == "right-hand") { + it.slotPosition &= ~SLOTP_LEFT; + } else if (tmpStrValue == "left-hand") { + it.slotPosition &= ~SLOTP_RIGHT; + } else if (tmpStrValue == "necklace") { + it.slotPosition |= SLOTP_NECKLACE; + } else if (tmpStrValue == "ring") { + it.slotPosition |= SLOTP_RING; + } else if (tmpStrValue == "ammo") { + it.slotPosition |= SLOTP_AMMO; + } else if (tmpStrValue == "hand") { + it.slotPosition |= SLOTP_HAND; + } else { + std::cout << "[Warning - Items::parseItemNode] Unknown slotType: " << valueAttribute.as_string() << std::endl; + } + break; + } + + case ITEM_PARSE_AMMOTYPE: { + it.ammoType = getAmmoType(asLowerCaseString(valueAttribute.as_string())); + if (it.ammoType == AMMO_NONE) { + std::cout << "[Warning - Items::parseItemNode] Unknown ammoType: " << valueAttribute.as_string() << std::endl; + } + break; + } + + case ITEM_PARSE_SHOOTTYPE: { + ShootType_t shoot = getShootType(asLowerCaseString(valueAttribute.as_string())); + if (shoot != CONST_ANI_NONE) { + it.shootType = shoot; + } else { + std::cout << "[Warning - Items::parseItemNode] Unknown shootType: " << valueAttribute.as_string() << std::endl; + } + break; + } + + case ITEM_PARSE_EFFECT: { + MagicEffectClasses effect = getMagicEffect(asLowerCaseString(valueAttribute.as_string())); + if (effect != CONST_ME_NONE) { + it.magicEffect = effect; + } else { + std::cout << "[Warning - Items::parseItemNode] Unknown effect: " << valueAttribute.as_string() << std::endl; + } + break; + } + + case ITEM_PARSE_RANGE: { + it.shootRange = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_STOPDURATION: { + it.stopTime = valueAttribute.as_bool(); + break; + } + + case ITEM_PARSE_DECAYTO: { + it.decayTo = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_TRANSFORMEQUIPTO: { + it.transformEquipTo = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_TRANSFORMDEEQUIPTO: { + it.transformDeEquipTo = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_DURATION: { + it.decayTime = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_SHOWDURATION: { + it.showDuration = valueAttribute.as_bool(); + break; + } + + case ITEM_PARSE_CHARGES: { + it.charges = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_SHOWCHARGES: { + it.showCharges = valueAttribute.as_bool(); + break; + } + + case ITEM_PARSE_SHOWATTRIBUTES: { + it.showAttributes = valueAttribute.as_bool(); + break; + } + + case ITEM_PARSE_HITCHANCE: { + it.hitChance = std::min(100, std::max(-100, pugi::cast(valueAttribute.value()))); + break; + } + + case ITEM_PARSE_MAXHITCHANCE: { + it.maxHitChance = std::min(100, pugi::cast(valueAttribute.value())); + break; + } + + case ITEM_PARSE_INVISIBLE: { + abilities.invisible = valueAttribute.as_bool(); + break; + } + + case ITEM_PARSE_SPEED: { + abilities.speed = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_HEALTHGAIN: { + abilities.regeneration = true; + abilities.healthGain = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_HEALTHTICKS: { + abilities.regeneration = true; + abilities.healthTicks = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MANAGAIN: { + abilities.regeneration = true; + abilities.manaGain = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MANATICKS: { + abilities.regeneration = true; + abilities.manaTicks = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MANASHIELD: { + abilities.manaShield = valueAttribute.as_bool(); + break; + } + + case ITEM_PARSE_SKILLSWORD: { + abilities.skills[SKILL_SWORD] = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_SKILLAXE: { + abilities.skills[SKILL_AXE] = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_SKILLCLUB: { + abilities.skills[SKILL_CLUB] = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_SKILLDIST: { + abilities.skills[SKILL_DISTANCE] = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_SKILLFISH: { + abilities.skills[SKILL_FISHING] = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_SKILLSHIELD: { + abilities.skills[SKILL_SHIELD] = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_SKILLFIST: { + abilities.skills[SKILL_FIST] = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_CRITICALHITAMOUNT: { + abilities.specialSkills[SPECIALSKILL_CRITICALHITAMOUNT] = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_CRITICALHITCHANCE: { + abilities.specialSkills[SPECIALSKILL_CRITICALHITCHANCE] = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MANALEECHAMOUNT: { + abilities.specialSkills[SPECIALSKILL_MANALEECHAMOUNT] = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MANALEECHCHANCE: { + abilities.specialSkills[SPECIALSKILL_MANALEECHCHANCE] = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_LIFELEECHAMOUNT: { + abilities.specialSkills[SPECIALSKILL_LIFELEECHAMOUNT] = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_LIFELEECHCHANCE: { + abilities.specialSkills[SPECIALSKILL_LIFELEECHCHANCE] = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MAXHITPOINTS: { + abilities.stats[STAT_MAXHITPOINTS] = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MAXHITPOINTSPERCENT: { + abilities.statsPercent[STAT_MAXHITPOINTS] = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MAXMANAPOINTS: { + abilities.stats[STAT_MAXMANAPOINTS] = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MAXMANAPOINTSPERCENT: { + abilities.statsPercent[STAT_MAXMANAPOINTS] = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MAGICPOINTS: { + abilities.stats[STAT_MAGICPOINTS] = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MAGICPOINTSPERCENT: { + abilities.statsPercent[STAT_MAGICPOINTS] = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_FIELDABSORBPERCENTENERGY: { + abilities.fieldAbsorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_FIELDABSORBPERCENTFIRE: { + abilities.fieldAbsorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_FIELDABSORBPERCENTPOISON: { + abilities.fieldAbsorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_ABSORBPERCENTALL: { + int16_t value = pugi::cast(valueAttribute.value()); + for (auto& i : abilities.absorbPercent) { + i += value; + } + break; + } + + case ITEM_PARSE_ABSORBPERCENTELEMENTS: { + int16_t value = pugi::cast(valueAttribute.value()); + abilities.absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += value; + abilities.absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += value; + abilities.absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += value; + abilities.absorbPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += value; + break; + } + + case ITEM_PARSE_ABSORBPERCENTMAGIC: { + int16_t value = pugi::cast(valueAttribute.value()); + abilities.absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += value; + abilities.absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += value; + abilities.absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += value; + abilities.absorbPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += value; + abilities.absorbPercent[combatTypeToIndex(COMBAT_HOLYDAMAGE)] += value; + abilities.absorbPercent[combatTypeToIndex(COMBAT_DEATHDAMAGE)] += value; + break; + } + + case ITEM_PARSE_ABSORBPERCENTENERGY: { + abilities.absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_ABSORBPERCENTFIRE: { + abilities.absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_ABSORBPERCENTPOISON: { + abilities.absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_ABSORBPERCENTICE: { + abilities.absorbPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_ABSORBPERCENTHOLY: { + abilities.absorbPercent[combatTypeToIndex(COMBAT_HOLYDAMAGE)] += pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_ABSORBPERCENTDEATH: { + abilities.absorbPercent[combatTypeToIndex(COMBAT_DEATHDAMAGE)] += pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_ABSORBPERCENTLIFEDRAIN: { + abilities.absorbPercent[combatTypeToIndex(COMBAT_LIFEDRAIN)] += pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_ABSORBPERCENTMANADRAIN: { + abilities.absorbPercent[combatTypeToIndex(COMBAT_MANADRAIN)] += pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_ABSORBPERCENTDROWN: { + abilities.absorbPercent[combatTypeToIndex(COMBAT_DROWNDAMAGE)] += pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_ABSORBPERCENTPHYSICAL: { + abilities.absorbPercent[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)] += pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_ABSORBPERCENTHEALING: { + abilities.absorbPercent[combatTypeToIndex(COMBAT_HEALING)] += pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_ABSORBPERCENTUNDEFINED: { + abilities.absorbPercent[combatTypeToIndex(COMBAT_UNDEFINEDDAMAGE)] += pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_SUPPRESSDRUNK: { + if (valueAttribute.as_bool()) { + abilities.conditionSuppressions |= CONDITION_DRUNK; + } + break; + } + + case ITEM_PARSE_SUPPRESSENERGY: { + if (valueAttribute.as_bool()) { + abilities.conditionSuppressions |= CONDITION_ENERGY; + } + break; + } + + case ITEM_PARSE_SUPPRESSFIRE: { + if (valueAttribute.as_bool()) { + abilities.conditionSuppressions |= CONDITION_FIRE; + } + break; + } + + case ITEM_PARSE_SUPPRESSPOISON: { + if (valueAttribute.as_bool()) { + abilities.conditionSuppressions |= CONDITION_POISON; + } + break; + } + + case ITEM_PARSE_SUPPRESSDROWN: { + if (valueAttribute.as_bool()) { + abilities.conditionSuppressions |= CONDITION_DROWN; + } + break; + } + + case ITEM_PARSE_SUPPRESSPHYSICAL: { + if (valueAttribute.as_bool()) { + abilities.conditionSuppressions |= CONDITION_BLEEDING; + } + break; + } + + case ITEM_PARSE_SUPPRESSFREEZE: { + if (valueAttribute.as_bool()) { + abilities.conditionSuppressions |= CONDITION_FREEZING; + } + break; + } + + case ITEM_PARSE_SUPPRESSDAZZLE: { + if (valueAttribute.as_bool()) { + abilities.conditionSuppressions |= CONDITION_DAZZLED; + } + break; + } + + case ITEM_PARSE_SUPPRESSCURSE: { + if (valueAttribute.as_bool()) { + abilities.conditionSuppressions |= CONDITION_CURSED; + } + break; + } + + case ITEM_PARSE_FIELD: { + it.group = ITEM_GROUP_MAGICFIELD; + it.type = ITEM_TYPE_MAGICFIELD; + + CombatType_t combatType = COMBAT_NONE; + ConditionDamage* conditionDamage = nullptr; + + tmpStrValue = asLowerCaseString(valueAttribute.as_string()); + if (tmpStrValue == "fire") { + conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_FIRE); + combatType = COMBAT_FIREDAMAGE; + } else if (tmpStrValue == "energy") { + conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_ENERGY); + combatType = COMBAT_ENERGYDAMAGE; + } else if (tmpStrValue == "poison") { + conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_POISON); + combatType = COMBAT_EARTHDAMAGE; + } else if (tmpStrValue == "drown") { + conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_DROWN); + combatType = COMBAT_DROWNDAMAGE; + } else if (tmpStrValue == "physical") { + conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_BLEEDING); + combatType = COMBAT_PHYSICALDAMAGE; + } else { + std::cout << "[Warning - Items::parseItemNode] Unknown field value: " << valueAttribute.as_string() << std::endl; + } + + if (combatType != COMBAT_NONE) { + it.combatType = combatType; + it.conditionDamage.reset(conditionDamage); + + uint32_t ticks = 0; + int32_t start = 0; + int32_t count = 1; + for (auto subAttributeNode : attributeNode.children()) { + pugi::xml_attribute subKeyAttribute = subAttributeNode.attribute("key"); + if (!subKeyAttribute) { + continue; } - vocationString += str; - vocationString.push_back('s'); + pugi::xml_attribute subValueAttribute = subAttributeNode.attribute("value"); + if (!subValueAttribute) { + continue; + } + + tmpStrValue = asLowerCaseString(subKeyAttribute.as_string()); + if (tmpStrValue == "ticks") { + ticks = pugi::cast(subValueAttribute.value()); + } else if (tmpStrValue == "count") { + count = std::max(1, pugi::cast(subValueAttribute.value())); + } else if (tmpStrValue == "start") { + start = std::max(0, pugi::cast(subValueAttribute.value())); + } else if (tmpStrValue == "damage") { + int32_t damage = -pugi::cast(subValueAttribute.value()); + if (start > 0) { + std::list damageList; + ConditionDamage::generateDamageList(damage, start, damageList); + for (int32_t damageValue : damageList) { + conditionDamage->addDamage(1, ticks, -damageValue); + } + + start = 0; + } else { + conditionDamage->addDamage(count, ticks, damage); + } + } } - 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; + conditionDamage->setParam(CONDITION_PARAM_FIELD, 1); + + if (conditionDamage->getTotalDamage() > 0) { + conditionDamage->setParam(CONDITION_PARAM_FORCEUPDATE, 1); } - } 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 == "absorbdrown") { - items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_DROWNDAMAGE)] += 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(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(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(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); - 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() == '}') { + case ITEM_PARSE_REPLACEABLE: { + it.replaceable = valueAttribute.as_bool(); break; } - if (script.Token != SPECIAL || script.getSpecial() != ',') { - continue; + case ITEM_PARSE_PARTNERDIRECTION: { + it.bedPartnerDir = getDirection(valueAttribute.as_string()); + break; + } + + case ITEM_PARSE_LEVELDOOR: { + it.levelDoor = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_MALETRANSFORMTO: { + uint16_t value = pugi::cast(valueAttribute.value()); + it.transformToOnUse[PLAYERSEX_MALE] = value; + ItemType& other = getItemType(value); + if (other.transformToFree == 0) { + other.transformToFree = it.id; + } + + if (it.transformToOnUse[PLAYERSEX_FEMALE] == 0) { + it.transformToOnUse[PLAYERSEX_FEMALE] = value; + } + break; + } + + case ITEM_PARSE_FEMALETRANSFORMTO: { + uint16_t value = pugi::cast(valueAttribute.value()); + it.transformToOnUse[PLAYERSEX_FEMALE] = value; + + ItemType& other = getItemType(value); + if (other.transformToFree == 0) { + other.transformToFree = it.id; + } + + if (it.transformToOnUse[PLAYERSEX_MALE] == 0) { + it.transformToOnUse[PLAYERSEX_MALE] = value; + } + break; + } + + case ITEM_PARSE_TRANSFORMTO: { + it.transformToFree = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_DESTROYTO: { + it.destroyTo = pugi::cast(valueAttribute.value()); + break; + } + + case ITEM_PARSE_ELEMENTICE: { + abilities.elementDamage = pugi::cast(valueAttribute.value()); + abilities.elementType = COMBAT_ICEDAMAGE; + break; + } + + case ITEM_PARSE_ELEMENTEARTH: { + abilities.elementDamage = pugi::cast(valueAttribute.value()); + abilities.elementType = COMBAT_EARTHDAMAGE; + break; + } + + case ITEM_PARSE_ELEMENTFIRE: { + abilities.elementDamage = pugi::cast(valueAttribute.value()); + abilities.elementType = COMBAT_FIREDAMAGE; + break; + } + + case ITEM_PARSE_ELEMENTENERGY: { + abilities.elementDamage = pugi::cast(valueAttribute.value()); + abilities.elementType = COMBAT_ENERGYDAMAGE; + break; + } + + case ITEM_PARSE_WALKSTACK: { + it.walkStack = valueAttribute.as_bool(); + break; + } + + case ITEM_PARSE_BLOCKING: { + it.blockSolid = valueAttribute.as_bool(); + break; + } + + case ITEM_PARSE_ALLOWDISTREAD: { + it.allowDistRead = booleanString(valueAttribute.as_string()); + break; + } + + default: { + // It should not ever get to here, only if you add a new key to the map and don't configure a case for it. + std::cout << "[Warning - Items::parseItemNode] Not configured key value: " << keyAttribute.as_string() << std::endl; + break; } } - - 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); + } else { + std::cout << "[Warning - Items::parseItemNode] Unknown key value: " << keyAttribute.as_string() << std::endl; } } - 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); - } - } + //check bed items + if ((it.transformToFree != 0 || it.transformToOnUse[PLAYERSEX_FEMALE] != 0 || it.transformToOnUse[PLAYERSEX_MALE] != 0) && it.type != ITEM_TYPE_BED) { + std::cout << "[Warning - Items::parseItemNode] Item " << it.id << " is not set as a bed-type" << std::endl; } - - return true; } ItemType& Items::getItemType(size_t id) @@ -596,6 +1357,15 @@ const ItemType& Items::getItemType(size_t id) const return items.front(); } +const ItemType& Items::getItemIdByClientId(uint16_t spriteId) const +{ + auto it = reverseItemMap.find(spriteId); + if (it != reverseItemMap.end()) { + return getItemType(it->second); + } + return items.front(); +} + uint16_t Items::getItemIdByName(const std::string& name) { auto result = nameToItems.find(asLowerCaseString(name)); diff --git a/src/items.h b/src/items.h index 6411e99..0bc3d13 100644 --- a/src/items.h +++ b/src/items.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,8 +22,8 @@ #include "const.h" #include "enums.h" +#include "itemloader.h" #include "position.h" -#include "fileloader.h" enum SlotPositionBits : uint32_t { SLOTP_WHEREEVER = 0xFFFFFFFF, @@ -46,6 +46,7 @@ enum ItemTypes_t { ITEM_TYPE_NONE, ITEM_TYPE_DEPOT, ITEM_TYPE_MAILBOX, + ITEM_TYPE_TRASHHOLDER, ITEM_TYPE_CONTAINER, ITEM_TYPE_DOOR, ITEM_TYPE_MAGICFIELD, @@ -53,28 +54,117 @@ enum ItemTypes_t { 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 +enum ItemParseAttributes_t { + ITEM_PARSE_TYPE, + ITEM_PARSE_DESCRIPTION, + ITEM_PARSE_RUNESPELLNAME, + ITEM_PARSE_WEIGHT, + ITEM_PARSE_SHOWCOUNT, + ITEM_PARSE_ARMOR, + ITEM_PARSE_DEFENSE, + ITEM_PARSE_EXTRADEF, + ITEM_PARSE_ATTACK, + ITEM_PARSE_ROTATETO, + ITEM_PARSE_MOVEABLE, + ITEM_PARSE_BLOCKPROJECTILE, + ITEM_PARSE_PICKUPABLE, + ITEM_PARSE_FORCESERIALIZE, + ITEM_PARSE_FLOORCHANGE, + ITEM_PARSE_CORPSETYPE, + ITEM_PARSE_CONTAINERSIZE, + ITEM_PARSE_FLUIDSOURCE, + ITEM_PARSE_READABLE, + ITEM_PARSE_WRITEABLE, + ITEM_PARSE_MAXTEXTLEN, + ITEM_PARSE_WRITEONCEITEMID, + ITEM_PARSE_WEAPONTYPE, + ITEM_PARSE_SLOTTYPE, + ITEM_PARSE_AMMOTYPE, + ITEM_PARSE_SHOOTTYPE, + ITEM_PARSE_EFFECT, + ITEM_PARSE_RANGE, + ITEM_PARSE_STOPDURATION, + ITEM_PARSE_DECAYTO, + ITEM_PARSE_TRANSFORMEQUIPTO, + ITEM_PARSE_TRANSFORMDEEQUIPTO, + ITEM_PARSE_DURATION, + ITEM_PARSE_SHOWDURATION, + ITEM_PARSE_CHARGES, + ITEM_PARSE_SHOWCHARGES, + ITEM_PARSE_SHOWATTRIBUTES, + ITEM_PARSE_HITCHANCE, + ITEM_PARSE_MAXHITCHANCE, + ITEM_PARSE_INVISIBLE, + ITEM_PARSE_SPEED, + ITEM_PARSE_HEALTHGAIN, + ITEM_PARSE_HEALTHTICKS, + ITEM_PARSE_MANAGAIN, + ITEM_PARSE_MANATICKS, + ITEM_PARSE_MANASHIELD, + ITEM_PARSE_SKILLSWORD, + ITEM_PARSE_SKILLAXE, + ITEM_PARSE_SKILLCLUB, + ITEM_PARSE_SKILLDIST, + ITEM_PARSE_SKILLFISH, + ITEM_PARSE_SKILLSHIELD, + ITEM_PARSE_SKILLFIST, + ITEM_PARSE_MAXHITPOINTS, + ITEM_PARSE_MAXHITPOINTSPERCENT, + ITEM_PARSE_MAXMANAPOINTS, + ITEM_PARSE_MAXMANAPOINTSPERCENT, + ITEM_PARSE_MAGICPOINTS, + ITEM_PARSE_MAGICPOINTSPERCENT, + ITEM_PARSE_CRITICALHITCHANCE, + ITEM_PARSE_CRITICALHITAMOUNT, + ITEM_PARSE_LIFELEECHCHANCE, + ITEM_PARSE_LIFELEECHAMOUNT, + ITEM_PARSE_MANALEECHCHANCE, + ITEM_PARSE_MANALEECHAMOUNT, + ITEM_PARSE_FIELDABSORBPERCENTENERGY, + ITEM_PARSE_FIELDABSORBPERCENTFIRE, + ITEM_PARSE_FIELDABSORBPERCENTPOISON, + ITEM_PARSE_ABSORBPERCENTALL, + ITEM_PARSE_ABSORBPERCENTELEMENTS, + ITEM_PARSE_ABSORBPERCENTMAGIC, + ITEM_PARSE_ABSORBPERCENTENERGY, + ITEM_PARSE_ABSORBPERCENTFIRE, + ITEM_PARSE_ABSORBPERCENTPOISON, + ITEM_PARSE_ABSORBPERCENTICE, + ITEM_PARSE_ABSORBPERCENTHOLY, + ITEM_PARSE_ABSORBPERCENTDEATH, + ITEM_PARSE_ABSORBPERCENTLIFEDRAIN, + ITEM_PARSE_ABSORBPERCENTMANADRAIN, + ITEM_PARSE_ABSORBPERCENTDROWN, + ITEM_PARSE_ABSORBPERCENTPHYSICAL, + ITEM_PARSE_ABSORBPERCENTHEALING, + ITEM_PARSE_ABSORBPERCENTUNDEFINED, + ITEM_PARSE_SUPPRESSDRUNK, + ITEM_PARSE_SUPPRESSENERGY, + ITEM_PARSE_SUPPRESSFIRE, + ITEM_PARSE_SUPPRESSPOISON, + ITEM_PARSE_SUPPRESSDROWN, + ITEM_PARSE_SUPPRESSPHYSICAL, + ITEM_PARSE_SUPPRESSFREEZE, + ITEM_PARSE_SUPPRESSDAZZLE, + ITEM_PARSE_SUPPRESSCURSE, + ITEM_PARSE_FIELD, + ITEM_PARSE_REPLACEABLE, + ITEM_PARSE_PARTNERDIRECTION, + ITEM_PARSE_LEVELDOOR, + ITEM_PARSE_MALETRANSFORMTO, + ITEM_PARSE_FEMALETRANSFORMTO, + ITEM_PARSE_TRANSFORMTO, + ITEM_PARSE_DESTROYTO, + ITEM_PARSE_ELEMENTICE, + ITEM_PARSE_ELEMENTEARTH, + ITEM_PARSE_ELEMENTFIRE, + ITEM_PARSE_ELEMENTENERGY, + ITEM_PARSE_WALKSTACK, + ITEM_PARSE_BLOCKING, + ITEM_PARSE_ALLOWDISTREAD, }; struct Abilities { @@ -92,6 +182,7 @@ struct Abilities { //extra skill modifiers int32_t skills[SKILL_LAST + 1] = { 0 }; + int32_t specialSkills[SPECIALSKILL_LAST + 1] = { 0 }; int32_t speed = 0; @@ -101,6 +192,10 @@ struct Abilities { //damage abilities modifiers int16_t absorbPercent[COMBAT_COUNT] = { 0 }; + //elemental damage + uint16_t elementDamage = 0; + CombatType_t elementType = COMBAT_NONE; + bool manaShield = false; bool invisible = false; bool regeneration = false; @@ -124,10 +219,7 @@ class ItemType return group == ITEM_GROUP_GROUND; } bool isContainer() const { - return type == ITEM_TYPE_CONTAINER; - } - bool isChest() const { - return type == ITEM_TYPE_CHEST; + return group == ITEM_GROUP_CONTAINER; } bool isSplash() const { return group == ITEM_GROUP_SPLASH; @@ -154,11 +246,20 @@ class ItemType bool isMailbox() const { return (type == ITEM_TYPE_MAILBOX); } + bool isTrashHolder() const { + return (type == ITEM_TYPE_TRASHHOLDER); + } bool isBed() const { return (type == ITEM_TYPE_BED); } bool isRune() const { - return type == ITEM_TYPE_RUNE; + return (type == ITEM_TYPE_RUNE); + } + bool isPickupable() const { + return (allowPickupable || pickupable); + } + bool isUseable() const { + return (useable); } bool hasSubType() const { return (isFluidContainer() || isSplash() || stackable || charges != 0); @@ -190,7 +291,9 @@ class ItemType itemgroup_t group = ITEM_GROUP_NONE; ItemTypes_t type = ITEM_TYPE_NONE; uint16_t id = 0; + uint16_t clientId = 0; bool stackable = false; + bool isAnimation = false; std::string name; std::string article; @@ -203,40 +306,35 @@ class ItemType std::unique_ptr conditionDamage; uint32_t weight = 0; + uint32_t levelDoor = 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 maxHitChance = -1; int32_t decayTo = -1; int32_t attack = 0; int32_t defense = 0; int32_t extraDefense = 0; int32_t armor = 0; - int32_t rotateTo = 0; + uint16_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 transformToOnUse[2] = {0, 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 slotPosition = SLOTP_HAND; uint16_t speed = 0; + uint16_t wareId = 0; MagicEffectClasses magicEffect = CONST_ME_NONE; Direction bedPartnerDir = DIRECTION_NONE; @@ -246,30 +344,22 @@ class ItemType RaceType_t corpseType = RACE_NONE; FluidTypes_t fluidSource = FLUID_NONE; - uint8_t fragility = 0; + uint8_t floorChange = 0; uint8_t alwaysOnTopOrder = 0; uint8_t lightLevel = 0; uint8_t lightColor = 0; uint8_t shootRange = 1; - uint8_t weaponSpecialEffect = 0; + int8_t hitChance = 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 forceSerialize = false; bool hasHeight = false; bool walkStack = true; bool blockSolid = false; bool blockPickupable = false; bool blockProjectile = false; bool blockPathFind = false; - bool allowPickupable = true; + bool allowPickupable = false; bool showDuration = false; bool showCharges = false; bool showAttributes = false; @@ -277,7 +367,7 @@ class ItemType bool pickupable = false; bool rotatable = false; bool useable = false; - bool moveable = true; + bool moveable = false; bool alwaysOnTop = false; bool canReadText = false; bool canWriteText = false; @@ -293,7 +383,8 @@ class ItemType class Items { public: - using nameMap = std::unordered_multimap; + using NameMap = std::unordered_multimap; + using InventoryVector = std::vector; Items(); @@ -304,23 +395,38 @@ class Items bool reload(); void clear(); + bool loadFromOtb(const std::string& file); + const ItemType& operator[](size_t id) const { return getItemType(id); } const ItemType& getItemType(size_t id) const; ItemType& getItemType(size_t id); + const ItemType& getItemIdByClientId(uint16_t spriteId) const; uint16_t getItemIdByName(const std::string& name); - bool loadItems(); + uint32_t majorVersion = 0; + uint32_t minorVersion = 0; + uint32_t buildNumber = 0; - inline size_t size() const { + bool loadFromXml(); + void parseItemNode(const pugi::xml_node& itemNode, uint16_t id); + + void buildInventoryList(); + const InventoryVector& getInventory() const { + return inventory; + } + + size_t size() const { return items.size(); } - nameMap nameToItems; + NameMap nameToItems; - protected: + private: + std::map reverseItemMap; std::vector items; + InventoryVector inventory; }; #endif diff --git a/src/lockfree.h b/src/lockfree.h index 7c8cc7b..b555139 100644 --- a/src/lockfree.h +++ b/src/lockfree.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,37 +26,52 @@ #include +/* + * we use this to avoid instantiating multiple free lists for objects of the + * same size and it can be replaced by a variable template in C++14 + * + * template + * boost::lockfree::stack lockfreeFreeList; + */ +template +struct LockfreeFreeList +{ + using FreeList = boost::lockfree::stack>; + static FreeList& get() + { + static FreeList freeList; + return freeList; + } +}; + template class LockfreePoolingAllocator : public std::allocator { public: - template + LockfreePoolingAllocator() = default; + + template ::value>::type> explicit constexpr LockfreePoolingAllocator(const U&) {} - typedef T value_type; + using value_type = T; T* allocate(size_t) const { - T* p; // NOTE: p doesn't have to be initialized - if (!getFreeList().pop(p)) { + auto& inst = LockfreeFreeList::get(); + void* p; // NOTE: p doesn't have to be initialized + if (!inst.pop(p)) { //Acquire memory without calling the constructor of T - p = static_cast(operator new (sizeof(T))); + p = operator new (sizeof(T)); } - return p; + return static_cast(p); } void deallocate(T* p, size_t) const { - if (!getFreeList().bounded_push(p)) { + auto& inst = LockfreeFreeList::get(); + if (!inst.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> FreeList; - static FreeList& getFreeList() { - static FreeList freeList; - return freeList; - } }; #endif diff --git a/src/luascript.cpp b/src/luascript.cpp index c46e5d7..c1f05b5 100644 --- a/src/luascript.cpp +++ b/src/luascript.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,6 +35,10 @@ #include "monster.h" #include "scheduler.h" #include "databasetasks.h" +#include "movement.h" +#include "globalevent.h" +#include "script.h" +#include "weapons.h" extern Chat* g_chat; extern Game g_game; @@ -42,6 +46,13 @@ extern Monsters g_monsters; extern ConfigManager g_config; extern Vocations g_vocations; extern Spells* g_spells; +extern Actions* g_actions; +extern TalkActions* g_talkActions; +extern CreatureEvents* g_creatureEvents; +extern MoveEvents* g_moveEvents; +extern GlobalEvents* g_globalEvents; +extern Scripts* g_scripts; +extern Weapons* g_weapons; ScriptEnvironment::DBResultMap ScriptEnvironment::tempResults; uint32_t ScriptEnvironment::lastResultId = 0; @@ -115,6 +126,10 @@ uint32_t ScriptEnvironment::addThing(Thing* thing) } Item* item = thing->getItem(); + if (item && item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { + return item->getUniqueId(); + } + for (const auto& it : localMap) { if (it.second == item) { return it.first; @@ -139,6 +154,14 @@ Thing* ScriptEnvironment::getThingByUID(uint32_t uid) return g_game.getCreatureByID(uid); } + if (uid <= std::numeric_limits::max()) { + Item* item = g_game.getUniqueItem(uid); + if (item && !item->isRemoved()) { + return item; + } + return nullptr; + } + auto it = localMap.find(uid); if (it != localMap.end()) { Item* item = it->second; @@ -169,6 +192,11 @@ Container* ScriptEnvironment::getContainerByUID(uint32_t uid) void ScriptEnvironment::removeItemByUID(uint32_t uid) { + if (uid <= std::numeric_limits::max()) { + g_game.removeUniqueItem(uid); + return; + } + auto it = localMap.find(uid); if (it != localMap.end()) { localMap.erase(it); @@ -337,6 +365,29 @@ int32_t LuaScriptInterface::getEvent(const std::string& eventName) return runningEventId++; } +int32_t LuaScriptInterface::getEvent() +{ + //check if function is on the stack + if (!isFunction(luaState, -1)) { + return -1; + } + + //get our events table + lua_rawgeti(luaState, LUA_REGISTRYINDEX, eventTableRef); + if (!isTable(luaState, -1)) { + lua_pop(luaState, 1); + return -1; + } + + //save in our events table + lua_pushvalue(luaState, -2); + lua_rawseti(luaState, -2, runningEventId); + lua_pop(luaState, 2); + + cacheFiles[runningEventId] = loadingFile + ":callback"; + return runningEventId++; +} + int32_t LuaScriptInterface::getMetaEvent(const std::string& globalName, const std::string& eventName) { //get our events table @@ -676,18 +727,6 @@ void LuaScriptInterface::setCreatureMetatable(lua_State* L, int32_t index, const } // Get -CombatDamage LuaScriptInterface::getCombatDamage(lua_State* L) -{ - CombatDamage damage; - damage.value = getNumber(L, -4); - damage.type = getNumber(L, -3); - damage.min = getNumber(L, -2); - damage.max = getNumber(L, -1); - - lua_pop(L, 4); - return damage; -} - std::string LuaScriptInterface::getString(lua_State* L, int32_t arg) { size_t len; @@ -730,6 +769,7 @@ Position LuaScriptInterface::getPosition(lua_State* L, int32_t arg) Outfit_t LuaScriptInterface::getOutfit(lua_State* L, int32_t arg) { Outfit_t outfit; + outfit.lookMount = getField(L, arg, "lookMount"); outfit.lookAddons = getField(L, arg, "lookAddons"); outfit.lookFeet = getField(L, arg, "lookFeet"); @@ -737,10 +777,10 @@ Outfit_t LuaScriptInterface::getOutfit(lua_State* L, int32_t arg) outfit.lookBody = getField(L, arg, "lookBody"); outfit.lookHead = getField(L, arg, "lookHead"); - outfit.lookTypeEx = getField(L, arg, "lookTypeEx"); - outfit.lookType = getField(L, arg, "lookType"); + outfit.lookTypeEx = getField(L, arg, "lookTypeEx"); + outfit.lookType = getField(L, arg, "lookType"); - lua_pop(L, 6); + lua_pop(L, 8); return outfit; } @@ -777,6 +817,13 @@ LuaVariant LuaScriptInterface::getVariant(lua_State* L, int32_t arg) return var; } +InstantSpell* LuaScriptInterface::getInstantSpell(lua_State* L, int32_t arg) +{ + InstantSpell* spell = g_spells->getInstantSpellByName(getFieldString(L, arg, "name")); + lua_pop(L, 1); + return spell; +} + Thing* LuaScriptInterface::getThing(lua_State* L, int32_t arg) { Thing* thing; @@ -855,13 +902,26 @@ void LuaScriptInterface::pushBoolean(lua_State* L, bool value) void LuaScriptInterface::pushCombatDamage(lua_State* L, const CombatDamage& damage) { - lua_pushnumber(L, damage.value); - lua_pushnumber(L, damage.type); - lua_pushnumber(L, damage.min); - lua_pushnumber(L, damage.max); + lua_pushnumber(L, damage.primary.value); + lua_pushnumber(L, damage.primary.type); + lua_pushnumber(L, damage.secondary.value); + lua_pushnumber(L, damage.secondary.type); lua_pushnumber(L, damage.origin); } +void LuaScriptInterface::pushInstantSpell(lua_State* L, const InstantSpell& spell) +{ + lua_createtable(L, 0, 6); + + setField(L, "name", spell.getName()); + setField(L, "words", spell.getWords()); + setField(L, "level", spell.getLevel()); + setField(L, "mlevel", spell.getMagicLevel()); + setField(L, "mana", spell.getMana()); + setField(L, "manapercent", spell.getManaPercent()); + + setMetatable(L, -1, "Spell"); +} void LuaScriptInterface::pushPosition(lua_State* L, const Position& position, int32_t stackpos/* = 0*/) { @@ -877,7 +937,7 @@ void LuaScriptInterface::pushPosition(lua_State* L, const Position& position, in void LuaScriptInterface::pushOutfit(lua_State* L, const Outfit_t& outfit) { - lua_createtable(L, 0, 6); + lua_createtable(L, 0, 8); setField(L, "lookType", outfit.lookType); setField(L, "lookTypeEx", outfit.lookTypeEx); setField(L, "lookHead", outfit.lookHead); @@ -885,6 +945,29 @@ void LuaScriptInterface::pushOutfit(lua_State* L, const Outfit_t& outfit) setField(L, "lookLegs", outfit.lookLegs); setField(L, "lookFeet", outfit.lookFeet); setField(L, "lookAddons", outfit.lookAddons); + setField(L, "lookMount", outfit.lookMount); +} + +void LuaScriptInterface::pushLoot(lua_State* L, const std::vector& lootList) +{ + lua_createtable(L, lootList.size(), 0); + + int index = 0; + for (const auto& lootBlock : lootList) { + lua_createtable(L, 0, 7); + + setField(L, "itemId", lootBlock.id); + setField(L, "chance", lootBlock.chance); + setField(L, "subType", lootBlock.subType); + setField(L, "maxCount", lootBlock.countmax); + setField(L, "actionId", lootBlock.actionId); + setField(L, "text", lootBlock.text); + + pushLoot(L, lootBlock.childLoot); + lua_setfield(L, -2, "childLoot"); + + lua_rawseti(L, -2, ++index); + } } #define registerEnum(value) { std::string enumName = #value; registerGlobalVariable(enumName.substr(enumName.find_last_of(':') + 1), value); } @@ -892,39 +975,14 @@ void LuaScriptInterface::pushOutfit(lua_State* L, const Outfit_t& outfit) void LuaScriptInterface::registerFunctions() { - //getPlayerFlagValue(cid, flag) - lua_register(luaState, "getPlayerFlagValue", LuaScriptInterface::luaGetPlayerFlagValue); - - //getPlayerInstantSpellCount(cid) - lua_register(luaState, "getPlayerInstantSpellCount", LuaScriptInterface::luaGetPlayerInstantSpellCount); - - //getPlayerInstantSpellInfo(cid, index) - lua_register(luaState, "getPlayerInstantSpellInfo", LuaScriptInterface::luaGetPlayerInstantSpellInfo); - //doPlayerAddItem(uid, itemid, count/subtype) //doPlayerAddItem(cid, itemid, count, canDropOnMap, subtype) //Returns uid of the created item lua_register(luaState, "doPlayerAddItem", LuaScriptInterface::luaDoPlayerAddItem); - //doCreateItem(itemid, type/count, pos) - //Returns uid of the created item, only works on tiles. - lua_register(luaState, "doCreateItem", LuaScriptInterface::luaDoCreateItem); - - //doCreateItemEx(itemid, count/subtype) - lua_register(luaState, "doCreateItemEx", LuaScriptInterface::luaDoCreateItemEx); - - //doTileAddItemEx(pos, uid) - lua_register(luaState, "doTileAddItemEx", LuaScriptInterface::luaDoTileAddItemEx); - - //doMoveCreature(cid, direction) - lua_register(luaState, "doMoveCreature", LuaScriptInterface::luaDoMoveCreature); - //doSetCreatureLight(cid, lightLevel, lightColor, time) lua_register(luaState, "doSetCreatureLight", LuaScriptInterface::luaDoSetCreatureLight); - //getCreatureCondition(cid, condition[, subId]) - lua_register(luaState, "getCreatureCondition", LuaScriptInterface::luaGetCreatureCondition); - //isValidUID(uid) lua_register(luaState, "isValidUID", LuaScriptInterface::luaIsValidUID); @@ -979,18 +1037,6 @@ void LuaScriptInterface::registerFunctions() //doChallengeCreature(cid, target) lua_register(luaState, "doChallengeCreature", LuaScriptInterface::luaDoChallengeCreature); - //doSetMonsterOutfit(cid, name, time) - lua_register(luaState, "doSetMonsterOutfit", LuaScriptInterface::luaSetMonsterOutfit); - - //doSetItemOutfit(cid, item, time) - lua_register(luaState, "doSetItemOutfit", LuaScriptInterface::luaSetItemOutfit); - - //doSetCreatureOutfit(cid, outfit, time) - lua_register(luaState, "doSetCreatureOutfit", LuaScriptInterface::luaSetCreatureOutfit); - - //isInArray(array, value) - lua_register(luaState, "isInArray", LuaScriptInterface::luaIsInArray); - //addEvent(callback, delay, ...) lua_register(luaState, "addEvent", LuaScriptInterface::luaAddEvent); @@ -1012,6 +1058,12 @@ void LuaScriptInterface::registerFunctions() //getWaypointPosition(name) lua_register(luaState, "getWaypointPositionByName", LuaScriptInterface::luaGetWaypointPositionByName); + //sendChannelMessage(channelId, type, message) + lua_register(luaState, "sendChannelMessage", LuaScriptInterface::luaSendChannelMessage); + + //sendGuildChannelMessage(guildId, type, message) + lua_register(luaState, "sendGuildChannelMessage", LuaScriptInterface::luaSendGuildChannelMessage); + #ifndef LUAJIT_VERSION //bit operations for Lua, based on bitlib project release 24 //bit.bnot, bit.band, bit.bor, bit.bxor, bit.lshift, bit.rshift @@ -1045,6 +1097,11 @@ void LuaScriptInterface::registerFunctions() registerEnum(ACCOUNT_TYPE_GAMEMASTER) registerEnum(ACCOUNT_TYPE_GOD) + registerEnum(BUG_CATEGORY_MAP) + registerEnum(BUG_CATEGORY_TYPO) + registerEnum(BUG_CATEGORY_TECHNICAL) + registerEnum(BUG_CATEGORY_OTHER) + registerEnum(CALLBACK_PARAM_LEVELMAGICVALUE) registerEnum(CALLBACK_PARAM_SKILLVALUE) registerEnum(CALLBACK_PARAM_TARGETTILE) @@ -1069,11 +1126,14 @@ void LuaScriptInterface::registerFunctions() registerEnum(COMBAT_ENERGYDAMAGE) registerEnum(COMBAT_EARTHDAMAGE) registerEnum(COMBAT_FIREDAMAGE) - registerEnum(COMBAT_DROWNDAMAGE) registerEnum(COMBAT_UNDEFINEDDAMAGE) registerEnum(COMBAT_LIFEDRAIN) registerEnum(COMBAT_MANADRAIN) registerEnum(COMBAT_HEALING) + registerEnum(COMBAT_DROWNDAMAGE) + registerEnum(COMBAT_ICEDAMAGE) + registerEnum(COMBAT_HOLYDAMAGE) + registerEnum(COMBAT_DEATHDAMAGE) registerEnum(COMBAT_PARAM_TYPE) registerEnum(COMBAT_PARAM_EFFECT) @@ -1085,14 +1145,12 @@ void LuaScriptInterface::registerFunctions() registerEnum(COMBAT_PARAM_AGGRESSIVE) registerEnum(COMBAT_PARAM_DISPEL) registerEnum(COMBAT_PARAM_USECHARGES) - registerEnum(COMBAT_PARAM_DECREASEDAMAGE) - registerEnum(COMBAT_PARAM_MAXIMUMDECREASEDDAMAGE) registerEnum(CONDITION_NONE) registerEnum(CONDITION_POISON) registerEnum(CONDITION_FIRE) registerEnum(CONDITION_ENERGY) - registerEnum(CONDITION_DROWN) + registerEnum(CONDITION_BLEEDING) registerEnum(CONDITION_HASTE) registerEnum(CONDITION_PARALYZE) registerEnum(CONDITION_OUTFIT) @@ -1101,14 +1159,22 @@ void LuaScriptInterface::registerFunctions() registerEnum(CONDITION_MANASHIELD) registerEnum(CONDITION_INFIGHT) registerEnum(CONDITION_DRUNK) + registerEnum(CONDITION_EXHAUST_WEAPON) registerEnum(CONDITION_REGENERATION) registerEnum(CONDITION_SOUL) + registerEnum(CONDITION_DROWN) registerEnum(CONDITION_MUTED) registerEnum(CONDITION_CHANNELMUTEDTICKS) registerEnum(CONDITION_YELLTICKS) registerEnum(CONDITION_ATTRIBUTES) - registerEnum(CONDITION_EXHAUST) + registerEnum(CONDITION_FREEZING) + registerEnum(CONDITION_DAZZLED) + registerEnum(CONDITION_CURSED) + registerEnum(CONDITION_EXHAUST_COMBAT) + registerEnum(CONDITION_EXHAUST_HEAL) registerEnum(CONDITION_PACIFIED) + registerEnum(CONDITION_SPELLCOOLDOWN) + registerEnum(CONDITION_SPELLGROUPCOOLDOWN) registerEnum(CONDITIONID_DEFAULT) registerEnum(CONDITIONID_COMBAT) @@ -1139,6 +1205,7 @@ void LuaScriptInterface::registerFunctions() registerEnum(CONDITION_PARAM_MAXVALUE) registerEnum(CONDITION_PARAM_STARTVALUE) registerEnum(CONDITION_PARAM_TICKINTERVAL) + registerEnum(CONDITION_PARAM_FORCEUPDATE) registerEnum(CONDITION_PARAM_SKILL_MELEE) registerEnum(CONDITION_PARAM_SKILL_FIST) registerEnum(CONDITION_PARAM_SKILL_CLUB) @@ -1154,7 +1221,6 @@ void LuaScriptInterface::registerFunctions() registerEnum(CONDITION_PARAM_STAT_MAXMANAPOINTSPERCENT) registerEnum(CONDITION_PARAM_STAT_MAGICPOINTSPERCENT) registerEnum(CONDITION_PARAM_PERIODICDAMAGE) - registerEnum(CONDITION_PARAM_HIT_DAMAGE) registerEnum(CONDITION_PARAM_SKILL_MELEEPERCENT) registerEnum(CONDITION_PARAM_SKILL_FISTPERCENT) registerEnum(CONDITION_PARAM_SKILL_CLUBPERCENT) @@ -1163,8 +1229,16 @@ void LuaScriptInterface::registerFunctions() registerEnum(CONDITION_PARAM_SKILL_DISTANCEPERCENT) registerEnum(CONDITION_PARAM_SKILL_SHIELDPERCENT) registerEnum(CONDITION_PARAM_SKILL_FISHINGPERCENT) + registerEnum(CONDITION_PARAM_BUFF_SPELL) registerEnum(CONDITION_PARAM_SUBID) registerEnum(CONDITION_PARAM_FIELD) + registerEnum(CONDITION_PARAM_DISABLE_DEFENSE) + registerEnum(CONDITION_PARAM_SPECIALSKILL_CRITICALHITCHANCE) + registerEnum(CONDITION_PARAM_SPECIALSKILL_CRITICALHITAMOUNT) + registerEnum(CONDITION_PARAM_SPECIALSKILL_LIFELEECHCHANCE) + registerEnum(CONDITION_PARAM_SPECIALSKILL_LIFELEECHAMOUNT) + registerEnum(CONDITION_PARAM_SPECIALSKILL_MANALEECHCHANCE) + registerEnum(CONDITION_PARAM_SPECIALSKILL_MANALEECHAMOUNT) registerEnum(CONST_ME_NONE) registerEnum(CONST_ME_DRAWBLOOD) @@ -1194,6 +1268,65 @@ void LuaScriptInterface::registerFunctions() registerEnum(CONST_ME_SOUND_WHITE) registerEnum(CONST_ME_BUBBLES) registerEnum(CONST_ME_CRAPS) + registerEnum(CONST_ME_GIFT_WRAPS) + registerEnum(CONST_ME_FIREWORK_YELLOW) + registerEnum(CONST_ME_FIREWORK_RED) + registerEnum(CONST_ME_FIREWORK_BLUE) + registerEnum(CONST_ME_STUN) + registerEnum(CONST_ME_SLEEP) + registerEnum(CONST_ME_WATERCREATURE) + registerEnum(CONST_ME_GROUNDSHAKER) + registerEnum(CONST_ME_HEARTS) + registerEnum(CONST_ME_FIREATTACK) + registerEnum(CONST_ME_ENERGYAREA) + registerEnum(CONST_ME_SMALLCLOUDS) + registerEnum(CONST_ME_HOLYDAMAGE) + registerEnum(CONST_ME_BIGCLOUDS) + registerEnum(CONST_ME_ICEAREA) + registerEnum(CONST_ME_ICETORNADO) + registerEnum(CONST_ME_ICEATTACK) + registerEnum(CONST_ME_STONES) + registerEnum(CONST_ME_SMALLPLANTS) + registerEnum(CONST_ME_CARNIPHILA) + registerEnum(CONST_ME_PURPLEENERGY) + registerEnum(CONST_ME_YELLOWENERGY) + registerEnum(CONST_ME_HOLYAREA) + registerEnum(CONST_ME_BIGPLANTS) + registerEnum(CONST_ME_CAKE) + registerEnum(CONST_ME_GIANTICE) + registerEnum(CONST_ME_WATERSPLASH) + registerEnum(CONST_ME_PLANTATTACK) + registerEnum(CONST_ME_TUTORIALARROW) + registerEnum(CONST_ME_TUTORIALSQUARE) + registerEnum(CONST_ME_MIRRORHORIZONTAL) + registerEnum(CONST_ME_MIRRORVERTICAL) + registerEnum(CONST_ME_SKULLHORIZONTAL) + registerEnum(CONST_ME_SKULLVERTICAL) + registerEnum(CONST_ME_ASSASSIN) + registerEnum(CONST_ME_STEPSHORIZONTAL) + registerEnum(CONST_ME_BLOODYSTEPS) + registerEnum(CONST_ME_STEPSVERTICAL) + registerEnum(CONST_ME_YALAHARIGHOST) + registerEnum(CONST_ME_BATS) + registerEnum(CONST_ME_SMOKE) + registerEnum(CONST_ME_INSECTS) + registerEnum(CONST_ME_DRAGONHEAD) + registerEnum(CONST_ME_ORCSHAMAN) + registerEnum(CONST_ME_ORCSHAMAN_FIRE) + registerEnum(CONST_ME_THUNDER) + registerEnum(CONST_ME_FERUMBRAS) + registerEnum(CONST_ME_CONFETTI_HORIZONTAL) + registerEnum(CONST_ME_CONFETTI_VERTICAL) + registerEnum(CONST_ME_BLACKSMOKE) + registerEnum(CONST_ME_REDSMOKE) + registerEnum(CONST_ME_YELLOWSMOKE) + registerEnum(CONST_ME_GREENSMOKE) + registerEnum(CONST_ME_PURPLESMOKE) + registerEnum(CONST_ME_EARLY_THUNDER) + registerEnum(CONST_ME_RAGIAZ_BONECAPSULE) + registerEnum(CONST_ME_CRITICAL_DAMAGE) + registerEnum(CONST_ME_PLUNGING_FISH) + registerEnum(CONST_ANI_NONE) registerEnum(CONST_ANI_SPEAR) registerEnum(CONST_ANI_BOLT) @@ -1210,6 +1343,42 @@ void LuaScriptInterface::registerFunctions() registerEnum(CONST_ANI_SNOWBALL) registerEnum(CONST_ANI_POWERBOLT) registerEnum(CONST_ANI_POISON) + registerEnum(CONST_ANI_INFERNALBOLT) + registerEnum(CONST_ANI_HUNTINGSPEAR) + registerEnum(CONST_ANI_ENCHANTEDSPEAR) + registerEnum(CONST_ANI_REDSTAR) + registerEnum(CONST_ANI_GREENSTAR) + registerEnum(CONST_ANI_ROYALSPEAR) + registerEnum(CONST_ANI_SNIPERARROW) + registerEnum(CONST_ANI_ONYXARROW) + registerEnum(CONST_ANI_PIERCINGBOLT) + registerEnum(CONST_ANI_WHIRLWINDSWORD) + registerEnum(CONST_ANI_WHIRLWINDAXE) + registerEnum(CONST_ANI_WHIRLWINDCLUB) + registerEnum(CONST_ANI_ETHEREALSPEAR) + registerEnum(CONST_ANI_ICE) + registerEnum(CONST_ANI_EARTH) + registerEnum(CONST_ANI_HOLY) + registerEnum(CONST_ANI_SUDDENDEATH) + registerEnum(CONST_ANI_FLASHARROW) + registerEnum(CONST_ANI_FLAMMINGARROW) + registerEnum(CONST_ANI_SHIVERARROW) + registerEnum(CONST_ANI_ENERGYBALL) + registerEnum(CONST_ANI_SMALLICE) + registerEnum(CONST_ANI_SMALLHOLY) + registerEnum(CONST_ANI_SMALLEARTH) + registerEnum(CONST_ANI_EARTHARROW) + registerEnum(CONST_ANI_EXPLOSION) + registerEnum(CONST_ANI_CAKE) + registerEnum(CONST_ANI_TARSALARROW) + registerEnum(CONST_ANI_VORTEXBOLT) + registerEnum(CONST_ANI_PRISMATICBOLT) + registerEnum(CONST_ANI_CRYSTALLINEARROW) + registerEnum(CONST_ANI_DRILLBOLT) + registerEnum(CONST_ANI_ENVENOMEDARROW) + registerEnum(CONST_ANI_GLOOTHSPEAR) + registerEnum(CONST_ANI_SIMPLEARROW) + registerEnum(CONST_ANI_WEAPONTYPE) registerEnum(CONST_PROP_BLOCKSOLID) registerEnum(CONST_PROP_HASHEIGHT) @@ -1243,6 +1412,10 @@ void LuaScriptInterface::registerFunctions() registerEnum(CREATURE_EVENT_DEATH) registerEnum(CREATURE_EVENT_KILL) registerEnum(CREATURE_EVENT_ADVANCE) + registerEnum(CREATURE_EVENT_MODALWINDOW) + registerEnum(CREATURE_EVENT_TEXTEDIT) + registerEnum(CREATURE_EVENT_HEALTHCHANGE) + registerEnum(CREATURE_EVENT_MANACHANGE) registerEnum(CREATURE_EVENT_EXTENDED_OPCODE) registerEnum(GAME_STATE_STARTUP) @@ -1260,8 +1433,26 @@ void LuaScriptInterface::registerFunctions() registerEnum(MESSAGE_EVENT_ADVANCE) registerEnum(MESSAGE_STATUS_SMALL) registerEnum(MESSAGE_INFO_DESCR) + registerEnum(MESSAGE_DAMAGE_DEALT) + registerEnum(MESSAGE_DAMAGE_RECEIVED) + registerEnum(MESSAGE_HEALED) + registerEnum(MESSAGE_EXPERIENCE) + registerEnum(MESSAGE_DAMAGE_OTHERS) + registerEnum(MESSAGE_HEALED_OTHERS) + registerEnum(MESSAGE_EXPERIENCE_OTHERS) registerEnum(MESSAGE_EVENT_DEFAULT) + registerEnum(MESSAGE_GUILD) + registerEnum(MESSAGE_PARTY_MANAGEMENT) + registerEnum(MESSAGE_PARTY) + registerEnum(MESSAGE_EVENT_ORANGE) registerEnum(MESSAGE_STATUS_CONSOLE_ORANGE) + registerEnum(MESSAGE_LOOT) + + registerEnum(CREATURETYPE_PLAYER) + registerEnum(CREATURETYPE_MONSTER) + registerEnum(CREATURETYPE_NPC) + registerEnum(CREATURETYPE_SUMMON_OWN) + registerEnum(CREATURETYPE_SUMMON_OTHERS) registerEnum(CLIENTOS_LINUX) registerEnum(CLIENTOS_WINDOWS) @@ -1270,9 +1461,13 @@ void LuaScriptInterface::registerFunctions() registerEnum(CLIENTOS_OTCLIENT_WINDOWS) registerEnum(CLIENTOS_OTCLIENT_MAC) + registerEnum(FIGHTMODE_ATTACK) + registerEnum(FIGHTMODE_BALANCED) + registerEnum(FIGHTMODE_DEFENSE) + registerEnum(ITEM_ATTRIBUTE_NONE) registerEnum(ITEM_ATTRIBUTE_ACTIONID) - registerEnum(ITEM_ATTRIBUTE_MOVEMENTID) + registerEnum(ITEM_ATTRIBUTE_UNIQUEID) registerEnum(ITEM_ATTRIBUTE_DESCRIPTION) registerEnum(ITEM_ATTRIBUTE_TEXT) registerEnum(ITEM_ATTRIBUTE_DATE) @@ -1283,7 +1478,9 @@ void LuaScriptInterface::registerFunctions() registerEnum(ITEM_ATTRIBUTE_WEIGHT) registerEnum(ITEM_ATTRIBUTE_ATTACK) registerEnum(ITEM_ATTRIBUTE_DEFENSE) + registerEnum(ITEM_ATTRIBUTE_EXTRADEFENSE) registerEnum(ITEM_ATTRIBUTE_ARMOR) + registerEnum(ITEM_ATTRIBUTE_HITCHANCE) registerEnum(ITEM_ATTRIBUTE_SHOOTRANGE) registerEnum(ITEM_ATTRIBUTE_OWNER) registerEnum(ITEM_ATTRIBUTE_DURATION) @@ -1292,15 +1489,10 @@ void LuaScriptInterface::registerFunctions() registerEnum(ITEM_ATTRIBUTE_CHARGES) registerEnum(ITEM_ATTRIBUTE_FLUIDTYPE) registerEnum(ITEM_ATTRIBUTE_DOORID) - registerEnum(ITEM_ATTRIBUTE_KEYNUMBER) - registerEnum(ITEM_ATTRIBUTE_KEYHOLENUMBER) - registerEnum(ITEM_ATTRIBUTE_DOORQUESTNUMBER) - registerEnum(ITEM_ATTRIBUTE_DOORQUESTVALUE) - registerEnum(ITEM_ATTRIBUTE_DOORLEVEL) - registerEnum(ITEM_ATTRIBUTE_CHESTQUESTNUMBER) registerEnum(ITEM_TYPE_DEPOT) registerEnum(ITEM_TYPE_MAILBOX) + registerEnum(ITEM_TYPE_TRASHHOLDER) registerEnum(ITEM_TYPE_CONTAINER) registerEnum(ITEM_TYPE_DOOR) registerEnum(ITEM_TYPE_MAGICFIELD) @@ -1308,8 +1500,9 @@ void LuaScriptInterface::registerFunctions() registerEnum(ITEM_TYPE_BED) registerEnum(ITEM_TYPE_KEY) registerEnum(ITEM_TYPE_RUNE) - registerEnum(ITEM_TYPE_CHEST) + registerEnum(ITEM_BAG) + registerEnum(ITEM_SHOPPING_BAG) registerEnum(ITEM_GOLD_COIN) registerEnum(ITEM_PLATINUM_COIN) registerEnum(ITEM_CRYSTAL_COIN) @@ -1331,8 +1524,10 @@ void LuaScriptInterface::registerFunctions() registerEnum(ITEM_ENERGYFIELD_NOPVP) registerEnum(ITEM_MAGICWALL) registerEnum(ITEM_MAGICWALL_PERSISTENT) + registerEnum(ITEM_MAGICWALL_SAFE) registerEnum(ITEM_WILDGROWTH) registerEnum(ITEM_WILDGROWTH_PERSISTENT) + registerEnum(ITEM_WILDGROWTH_SAFE) registerEnum(PlayerFlag_CannotUseCombat) registerEnum(PlayerFlag_CannotAttackPlayer) @@ -1372,16 +1567,37 @@ void LuaScriptInterface::registerFunctions() registerEnum(PlayerFlag_IgnoreWeaponCheck) registerEnum(PlayerFlag_CannotBeMuted) registerEnum(PlayerFlag_IsAlwaysPremium) - registerEnum(PlayerFlag_SpecialMoveUse) registerEnum(PLAYERSEX_FEMALE) registerEnum(PLAYERSEX_MALE) - registerEnum(VOCATION_NONE) + registerEnum(REPORT_REASON_NAMEINAPPROPRIATE) + registerEnum(REPORT_REASON_NAMEPOORFORMATTED) + registerEnum(REPORT_REASON_NAMEADVERTISING) + registerEnum(REPORT_REASON_NAMEUNFITTING) + registerEnum(REPORT_REASON_NAMERULEVIOLATION) + registerEnum(REPORT_REASON_INSULTINGSTATEMENT) + registerEnum(REPORT_REASON_SPAMMING) + registerEnum(REPORT_REASON_ADVERTISINGSTATEMENT) + registerEnum(REPORT_REASON_UNFITTINGSTATEMENT) + registerEnum(REPORT_REASON_LANGUAGESTATEMENT) + registerEnum(REPORT_REASON_DISCLOSURE) + registerEnum(REPORT_REASON_RULEVIOLATION) + registerEnum(REPORT_REASON_STATEMENT_BUGABUSE) + registerEnum(REPORT_REASON_UNOFFICIALSOFTWARE) + registerEnum(REPORT_REASON_PRETENDING) + registerEnum(REPORT_REASON_HARASSINGOWNERS) + registerEnum(REPORT_REASON_FALSEINFO) + registerEnum(REPORT_REASON_ACCOUNTSHARING) + registerEnum(REPORT_REASON_STEALINGDATA) + registerEnum(REPORT_REASON_SERVICEATTACKING) + registerEnum(REPORT_REASON_SERVICEAGREEMENT) - registerEnum(FIGHTMODE_ATTACK) - registerEnum(FIGHTMODE_BALANCED) - registerEnum(FIGHTMODE_DEFENSE) + registerEnum(REPORT_TYPE_NAME) + registerEnum(REPORT_TYPE_STATEMENT) + registerEnum(REPORT_TYPE_BOT) + + registerEnum(VOCATION_NONE) registerEnum(SKILL_FIST) registerEnum(SKILL_CLUB) @@ -1393,36 +1609,34 @@ void LuaScriptInterface::registerFunctions() registerEnum(SKILL_MAGLEVEL) registerEnum(SKILL_LEVEL) + registerEnum(SPECIALSKILL_CRITICALHITCHANCE) + registerEnum(SPECIALSKILL_CRITICALHITAMOUNT) + registerEnum(SPECIALSKILL_LIFELEECHCHANCE) + registerEnum(SPECIALSKILL_LIFELEECHAMOUNT) + registerEnum(SPECIALSKILL_MANALEECHCHANCE) + registerEnum(SPECIALSKILL_MANALEECHAMOUNT) + registerEnum(SKULL_NONE) registerEnum(SKULL_YELLOW) registerEnum(SKULL_GREEN) registerEnum(SKULL_WHITE) registerEnum(SKULL_RED) - - registerEnum(FLUID_NONE) - registerEnum(FLUID_WATER) - registerEnum(FLUID_WINE) - registerEnum(FLUID_BEER) - registerEnum(FLUID_MUD) - registerEnum(FLUID_BLOOD) - registerEnum(FLUID_SLIME) - registerEnum(FLUID_OIL) - registerEnum(FLUID_URINE) - registerEnum(FLUID_MILK) - registerEnum(FLUID_MANAFLUID) - registerEnum(FLUID_LIFEFLUID) - registerEnum(FLUID_LEMONADE) - registerEnum(FLUID_RUM) - registerEnum(FLUID_COCONUTMILK) - registerEnum(FLUID_FRUITJUICE) + registerEnum(SKULL_BLACK) + registerEnum(SKULL_ORANGE) registerEnum(TALKTYPE_SAY) registerEnum(TALKTYPE_WHISPER) registerEnum(TALKTYPE_YELL) + registerEnum(TALKTYPE_PRIVATE_FROM) + registerEnum(TALKTYPE_PRIVATE_TO) registerEnum(TALKTYPE_CHANNEL_Y) registerEnum(TALKTYPE_CHANNEL_O) + registerEnum(TALKTYPE_PRIVATE_NP) + registerEnum(TALKTYPE_PRIVATE_PN) registerEnum(TALKTYPE_BROADCAST) registerEnum(TALKTYPE_CHANNEL_R1) + registerEnum(TALKTYPE_PRIVATE_RED_FROM) + registerEnum(TALKTYPE_PRIVATE_RED_TO) registerEnum(TALKTYPE_MONSTER_SAY) registerEnum(TALKTYPE_MONSTER_YELL) registerEnum(TALKTYPE_CHANNEL_R2) @@ -1435,7 +1649,9 @@ void LuaScriptInterface::registerFunctions() registerEnum(TEXTCOLOR_LIGHTGREY) registerEnum(TEXTCOLOR_SKYBLUE) registerEnum(TEXTCOLOR_PURPLE) + registerEnum(TEXTCOLOR_ELECTRICPURPLE) registerEnum(TEXTCOLOR_RED) + registerEnum(TEXTCOLOR_PASTELRED) registerEnum(TEXTCOLOR_ORANGE) registerEnum(TEXTCOLOR_YELLOW) registerEnum(TEXTCOLOR_WHITE_EXP) @@ -1446,10 +1662,16 @@ void LuaScriptInterface::registerFunctions() registerEnum(TILESTATE_NOPVPZONE) registerEnum(TILESTATE_NOLOGOUT) registerEnum(TILESTATE_PVPZONE) - registerEnum(TILESTATE_REFRESH) + registerEnum(TILESTATE_FLOORCHANGE) + registerEnum(TILESTATE_FLOORCHANGE_DOWN) + registerEnum(TILESTATE_FLOORCHANGE_NORTH) + registerEnum(TILESTATE_FLOORCHANGE_SOUTH) + registerEnum(TILESTATE_FLOORCHANGE_EAST) + registerEnum(TILESTATE_FLOORCHANGE_WEST) registerEnum(TILESTATE_TELEPORT) registerEnum(TILESTATE_MAGICFIELD) registerEnum(TILESTATE_MAILBOX) + registerEnum(TILESTATE_TRASHHOLDER) registerEnum(TILESTATE_BED) registerEnum(TILESTATE_DEPOT) registerEnum(TILESTATE_BLOCKSOLID) @@ -1458,6 +1680,8 @@ void LuaScriptInterface::registerFunctions() registerEnum(TILESTATE_IMMOVABLEBLOCKPATH) registerEnum(TILESTATE_IMMOVABLENOFIELDBLOCKPATH) registerEnum(TILESTATE_NOFIELDBLOCKPATH) + registerEnum(TILESTATE_FLOORCHANGE_SOUTH_ALT) + registerEnum(TILESTATE_FLOORCHANGE_EAST_ALT) registerEnum(TILESTATE_SUPPORTS_HANGABLE) registerEnum(WEAPON_NONE) @@ -1509,6 +1733,35 @@ void LuaScriptInterface::registerFunctions() registerEnum(GUEST_LIST) registerEnum(SUBOWNER_LIST) + // Use with npc:setSpeechBubble + registerEnum(SPEECHBUBBLE_NONE) + registerEnum(SPEECHBUBBLE_NORMAL) + registerEnum(SPEECHBUBBLE_TRADE) + registerEnum(SPEECHBUBBLE_QUEST) + registerEnum(SPEECHBUBBLE_QUESTTRADER) + + // Use with player:addMapMark + registerEnum(MAPMARK_TICK) + registerEnum(MAPMARK_QUESTION) + registerEnum(MAPMARK_EXCLAMATION) + registerEnum(MAPMARK_STAR) + registerEnum(MAPMARK_CROSS) + registerEnum(MAPMARK_TEMPLE) + registerEnum(MAPMARK_KISS) + registerEnum(MAPMARK_SHOVEL) + registerEnum(MAPMARK_SWORD) + registerEnum(MAPMARK_FLAG) + registerEnum(MAPMARK_LOCK) + registerEnum(MAPMARK_BAG) + registerEnum(MAPMARK_SKULL) + registerEnum(MAPMARK_DOLLAR) + registerEnum(MAPMARK_REDNORTH) + registerEnum(MAPMARK_REDSOUTH) + registerEnum(MAPMARK_REDEAST) + registerEnum(MAPMARK_REDWEST) + registerEnum(MAPMARK_GREENNORTH) + registerEnum(MAPMARK_GREENSOUTH) + // Use with Game.getReturnMessage registerEnum(RETURNVALUE_NOERROR) registerEnum(RETURNVALUE_NOTPOSSIBLE) @@ -1569,7 +1822,7 @@ void LuaScriptInterface::registerFunctions() registerEnum(RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL) registerEnum(RETURNVALUE_CANNOTCONJUREITEMHERE) registerEnum(RETURNVALUE_YOUNEEDTOSPLITYOURSPEARS) - registerEnum(RETURNVALUE_NAMEISTOOAMBIGIOUS) + registerEnum(RETURNVALUE_NAMEISTOOAMBIGUOUS) registerEnum(RETURNVALUE_CANONLYUSEONESHIELD) registerEnum(RETURNVALUE_NOPARTYMEMBERSINRANGE) registerEnum(RETURNVALUE_YOUARENOTTHEOWNER) @@ -1578,11 +1831,10 @@ void LuaScriptInterface::registerFunctions() registerEnum(RETURNVALUE_TRADEPLAYERALREADYOWNSAHOUSE) registerEnum(RETURNVALUE_TRADEPLAYERHIGHESTBIDDER) registerEnum(RETURNVALUE_YOUCANNOTTRADETHISHOUSE) - + registerEnum(RELOAD_TYPE_ALL) registerEnum(RELOAD_TYPE_ACTIONS) registerEnum(RELOAD_TYPE_CHAT) - registerEnum(RELOAD_TYPE_COMMANDS) registerEnum(RELOAD_TYPE_CONFIG) registerEnum(RELOAD_TYPE_CREATURESCRIPTS) registerEnum(RELOAD_TYPE_EVENTS) @@ -1595,10 +1847,28 @@ void LuaScriptInterface::registerFunctions() registerEnum(RELOAD_TYPE_NPCS) registerEnum(RELOAD_TYPE_QUESTS) registerEnum(RELOAD_TYPE_RAIDS) + registerEnum(RELOAD_TYPE_SCRIPTS) registerEnum(RELOAD_TYPE_SPELLS) registerEnum(RELOAD_TYPE_TALKACTIONS) registerEnum(RELOAD_TYPE_WEAPONS) + registerEnum(ZONE_PROTECTION) + registerEnum(ZONE_NOPVP) + registerEnum(ZONE_PVP) + registerEnum(ZONE_NOLOGOUT) + registerEnum(ZONE_NORMAL) + + registerEnum(MAX_LOOTCHANCE) + + registerEnum(SPELL_INSTANT) + registerEnum(SPELL_RUNE) + + registerEnum(MONSTERS_EVENT_THINK) + registerEnum(MONSTERS_EVENT_APPEAR) + registerEnum(MONSTERS_EVENT_DISAPPEAR) + registerEnum(MONSTERS_EVENT_MOVE) + registerEnum(MONSTERS_EVENT_SAY) + // _G registerGlobalVariable("INDEX_WHEREEVER", INDEX_WHEREEVER); registerGlobalBoolean("VIRTUAL_PARENT", true); @@ -1611,6 +1881,7 @@ void LuaScriptInterface::registerFunctions() registerEnumIn("configKeys", ConfigManager::ALLOW_CHANGEOUTFIT) registerEnumIn("configKeys", ConfigManager::ONE_PLAYER_ON_ACCOUNT) + registerEnumIn("configKeys", ConfigManager::AIMBOT_HOTKEY_ENABLED) registerEnumIn("configKeys", ConfigManager::REMOVE_RUNE_CHARGES) registerEnumIn("configKeys", ConfigManager::EXPERIENCE_FROM_PLAYERS) registerEnumIn("configKeys", ConfigManager::FREE_PREMIUM) @@ -1618,10 +1889,19 @@ void LuaScriptInterface::registerFunctions() registerEnumIn("configKeys", ConfigManager::ALLOW_CLONES) registerEnumIn("configKeys", ConfigManager::BIND_ONLY_GLOBAL_ADDRESS) registerEnumIn("configKeys", ConfigManager::OPTIMIZE_DATABASE) + registerEnumIn("configKeys", ConfigManager::MARKET_PREMIUM) + registerEnumIn("configKeys", ConfigManager::EMOTE_SPELLS) registerEnumIn("configKeys", ConfigManager::STAMINA_SYSTEM) registerEnumIn("configKeys", ConfigManager::WARN_UNSAFE_SCRIPTS) registerEnumIn("configKeys", ConfigManager::CONVERT_UNSAFE_SCRIPTS) - registerEnumIn("configKeys", ConfigManager::TELEPORT_NEWBIES) + registerEnumIn("configKeys", ConfigManager::CLASSIC_EQUIPMENT_SLOTS) + registerEnumIn("configKeys", ConfigManager::CLASSIC_ATTACK_SPEED) + registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_NOTIFY_MESSAGE) + registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_NOTIFY_DURATION) + registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_CLEAN_MAP) + registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_CLOSE) + registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_SHUTDOWN) + registerEnumIn("configKeys", ConfigManager::ONLINE_OFFLINE_CHARLIST) registerEnumIn("configKeys", ConfigManager::MAP_NAME) registerEnumIn("configKeys", ConfigManager::HOUSE_RENT_PERIOD) @@ -1651,6 +1931,9 @@ void LuaScriptInterface::registerFunctions() registerEnumIn("configKeys", ConfigManager::RATE_LOOT) registerEnumIn("configKeys", ConfigManager::RATE_MAGIC) registerEnumIn("configKeys", ConfigManager::RATE_SPAWN) + registerEnumIn("configKeys", ConfigManager::HOUSE_PRICE) + registerEnumIn("configKeys", ConfigManager::KILLS_TO_RED) + registerEnumIn("configKeys", ConfigManager::KILLS_TO_BLACK) registerEnumIn("configKeys", ConfigManager::MAX_MESSAGEBUFFER) registerEnumIn("configKeys", ConfigManager::ACTIONS_DELAY_INTERVAL) registerEnumIn("configKeys", ConfigManager::EX_ACTIONS_DELAY_INTERVAL) @@ -1658,24 +1941,17 @@ void LuaScriptInterface::registerFunctions() registerEnumIn("configKeys", ConfigManager::PROTECTION_LEVEL) registerEnumIn("configKeys", ConfigManager::DEATH_LOSE_PERCENT) registerEnumIn("configKeys", ConfigManager::STATUSQUERY_TIMEOUT) + registerEnumIn("configKeys", ConfigManager::FRAG_TIME) registerEnumIn("configKeys", ConfigManager::WHITE_SKULL_TIME) - registerEnumIn("configKeys", ConfigManager::RED_SKULL_TIME) - registerEnumIn("configKeys", ConfigManager::KILLS_DAY_RED_SKULL) - registerEnumIn("configKeys", ConfigManager::KILLS_WEEK_RED_SKULL) - registerEnumIn("configKeys", ConfigManager::KILLS_MONTH_RED_SKULL) - registerEnumIn("configKeys", ConfigManager::KILLS_DAY_BANISHMENT) - registerEnumIn("configKeys", ConfigManager::KILLS_WEEK_BANISHMENT) - registerEnumIn("configKeys", ConfigManager::KILLS_MONTH_BANISHMENT) registerEnumIn("configKeys", ConfigManager::GAME_PORT) registerEnumIn("configKeys", ConfigManager::LOGIN_PORT) registerEnumIn("configKeys", ConfigManager::STATUS_PORT) registerEnumIn("configKeys", ConfigManager::STAIRHOP_DELAY) + registerEnumIn("configKeys", ConfigManager::MARKET_OFFER_DURATION) + registerEnumIn("configKeys", ConfigManager::CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES) + registerEnumIn("configKeys", ConfigManager::MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER) registerEnumIn("configKeys", ConfigManager::EXP_FROM_PLAYERS_LEVEL_RANGE) registerEnumIn("configKeys", ConfigManager::MAX_PACKETS_PER_SECOND) - registerEnumIn("configKeys", ConfigManager::NEWBIE_TOWN) - registerEnumIn("configKeys", ConfigManager::NEWBIE_LEVEL_THRESHOLD) - registerEnumIn("configKeys", ConfigManager::BLOCK_HEIGHT) - registerEnumIn("configKeys", ConfigManager::DROP_ITEMS) // os registerMethod("os", "mtime", LuaScriptInterface::luaSystemTime); @@ -1711,9 +1987,12 @@ void LuaScriptInterface::registerFunctions() registerMethod("Game", "createMonster", LuaScriptInterface::luaGameCreateMonster); registerMethod("Game", "createNpc", LuaScriptInterface::luaGameCreateNpc); registerMethod("Game", "createTile", LuaScriptInterface::luaGameCreateTile); + registerMethod("Game", "createMonsterType", LuaScriptInterface::luaGameCreateMonsterType); registerMethod("Game", "startRaid", LuaScriptInterface::luaGameStartRaid); + registerMethod("Game", "getClientVersion", LuaScriptInterface::luaGameGetClientVersion); + registerMethod("Game", "reload", LuaScriptInterface::luaGameReload); // Variant @@ -1734,7 +2013,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("Position", "sendMagicEffect", LuaScriptInterface::luaPositionSendMagicEffect); registerMethod("Position", "sendDistanceEffect", LuaScriptInterface::luaPositionSendDistanceEffect); - registerMethod("Position", "sendMonsterSay", LuaScriptInterface::luaPositionSendMonsterSay); // Tile registerClass("Tile", "", LuaScriptInterface::luaTileCreate); @@ -1774,6 +2052,8 @@ void LuaScriptInterface::registerFunctions() registerMethod("Tile", "hasFlag", LuaScriptInterface::luaTileHasFlag); registerMethod("Tile", "queryAdd", LuaScriptInterface::luaTileQueryAdd); + registerMethod("Tile", "addItem", LuaScriptInterface::luaTileAddItem); + registerMethod("Tile", "addItemEx", LuaScriptInterface::luaTileAddItemEx); registerMethod("Tile", "getHouse", LuaScriptInterface::luaTileGetHouse); @@ -1804,6 +2084,36 @@ void LuaScriptInterface::registerFunctions() registerMethod("NetworkMessage", "skipBytes", LuaScriptInterface::luaNetworkMessageSkipBytes); registerMethod("NetworkMessage", "sendToPlayer", LuaScriptInterface::luaNetworkMessageSendToPlayer); + // ModalWindow + registerClass("ModalWindow", "", LuaScriptInterface::luaModalWindowCreate); + registerMetaMethod("ModalWindow", "__eq", LuaScriptInterface::luaUserdataCompare); + registerMetaMethod("ModalWindow", "__gc", LuaScriptInterface::luaModalWindowDelete); + registerMethod("ModalWindow", "delete", LuaScriptInterface::luaModalWindowDelete); + + registerMethod("ModalWindow", "getId", LuaScriptInterface::luaModalWindowGetId); + registerMethod("ModalWindow", "getTitle", LuaScriptInterface::luaModalWindowGetTitle); + registerMethod("ModalWindow", "getMessage", LuaScriptInterface::luaModalWindowGetMessage); + + registerMethod("ModalWindow", "setTitle", LuaScriptInterface::luaModalWindowSetTitle); + registerMethod("ModalWindow", "setMessage", LuaScriptInterface::luaModalWindowSetMessage); + + registerMethod("ModalWindow", "getButtonCount", LuaScriptInterface::luaModalWindowGetButtonCount); + registerMethod("ModalWindow", "getChoiceCount", LuaScriptInterface::luaModalWindowGetChoiceCount); + + registerMethod("ModalWindow", "addButton", LuaScriptInterface::luaModalWindowAddButton); + registerMethod("ModalWindow", "addChoice", LuaScriptInterface::luaModalWindowAddChoice); + + registerMethod("ModalWindow", "getDefaultEnterButton", LuaScriptInterface::luaModalWindowGetDefaultEnterButton); + registerMethod("ModalWindow", "setDefaultEnterButton", LuaScriptInterface::luaModalWindowSetDefaultEnterButton); + + registerMethod("ModalWindow", "getDefaultEscapeButton", LuaScriptInterface::luaModalWindowGetDefaultEscapeButton); + registerMethod("ModalWindow", "setDefaultEscapeButton", LuaScriptInterface::luaModalWindowSetDefaultEscapeButton); + + registerMethod("ModalWindow", "hasPriority", LuaScriptInterface::luaModalWindowHasPriority); + registerMethod("ModalWindow", "setPriority", LuaScriptInterface::luaModalWindowSetPriority); + + registerMethod("ModalWindow", "sendToPlayer", LuaScriptInterface::luaModalWindowSendToPlayer); + // Item registerClass("Item", "", LuaScriptInterface::luaItemCreate); registerMetaMethod("Item", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -1819,11 +2129,9 @@ void LuaScriptInterface::registerFunctions() registerMethod("Item", "split", LuaScriptInterface::luaItemSplit); registerMethod("Item", "remove", LuaScriptInterface::luaItemRemove); - registerMethod("Item", "getMovementId", LuaScriptInterface::luaItemGetMovementId); - registerMethod("Item", "setMovementId", LuaScriptInterface::luaItemSetMovementId); + registerMethod("Item", "getUniqueId", LuaScriptInterface::luaItemGetUniqueId); registerMethod("Item", "getActionId", LuaScriptInterface::luaItemGetActionId); registerMethod("Item", "setActionId", LuaScriptInterface::luaItemSetActionId); - registerMethod("Item", "getUniqueId", LuaScriptInterface::luaItemGetUniqueId); registerMethod("Item", "getCount", LuaScriptInterface::luaItemGetCount); registerMethod("Item", "getCharges", LuaScriptInterface::luaItemGetCharges); @@ -1843,6 +2151,9 @@ void LuaScriptInterface::registerFunctions() registerMethod("Item", "getAttribute", LuaScriptInterface::luaItemGetAttribute); registerMethod("Item", "setAttribute", LuaScriptInterface::luaItemSetAttribute); registerMethod("Item", "removeAttribute", LuaScriptInterface::luaItemRemoveAttribute); + registerMethod("Item", "getCustomAttribute", LuaScriptInterface::luaItemGetCustomAttribute); + registerMethod("Item", "setCustomAttribute", LuaScriptInterface::luaItemSetCustomAttribute); + registerMethod("Item", "removeCustomAttribute", LuaScriptInterface::luaItemRemoveCustomAttribute); registerMethod("Item", "moveTo", LuaScriptInterface::luaItemMoveTo); registerMethod("Item", "transform", LuaScriptInterface::luaItemTransform); @@ -1851,6 +2162,7 @@ void LuaScriptInterface::registerFunctions() registerMethod("Item", "getDescription", LuaScriptInterface::luaItemGetDescription); registerMethod("Item", "hasProperty", LuaScriptInterface::luaItemHasProperty); + registerMethod("Item", "isLoadedFromMap", LuaScriptInterface::luaItemIsLoadedFromMap); // Container registerClass("Container", "Item", LuaScriptInterface::luaContainerCreate); @@ -1859,7 +2171,7 @@ void LuaScriptInterface::registerFunctions() registerMethod("Container", "getSize", LuaScriptInterface::luaContainerGetSize); registerMethod("Container", "getCapacity", LuaScriptInterface::luaContainerGetCapacity); registerMethod("Container", "getEmptySlots", LuaScriptInterface::luaContainerGetEmptySlots); - + registerMethod("Container", "getContentDescription", LuaScriptInterface::luaContainerGetContentDescription); registerMethod("Container", "getItemHoldingCount", LuaScriptInterface::luaContainerGetItemHoldingCount); registerMethod("Container", "getItemCountById", LuaScriptInterface::luaContainerGetItemCountById); @@ -1867,6 +2179,7 @@ void LuaScriptInterface::registerFunctions() registerMethod("Container", "hasItem", LuaScriptInterface::luaContainerHasItem); registerMethod("Container", "addItem", LuaScriptInterface::luaContainerAddItem); registerMethod("Container", "addItemEx", LuaScriptInterface::luaContainerAddItemEx); + registerMethod("Container", "getCorpseOwner", LuaScriptInterface::luaContainerGetCorpseOwner); // Teleport registerClass("Teleport", "Item", LuaScriptInterface::luaTeleportCreate); @@ -1886,6 +2199,8 @@ void LuaScriptInterface::registerFunctions() registerMethod("Creature", "isRemoved", LuaScriptInterface::luaCreatureIsRemoved); registerMethod("Creature", "isCreature", LuaScriptInterface::luaCreatureIsCreature); registerMethod("Creature", "isInGhostMode", LuaScriptInterface::luaCreatureIsInGhostMode); + registerMethod("Creature", "isHealthHidden", LuaScriptInterface::luaCreatureIsHealthHidden); + registerMethod("Creature", "isImmune", LuaScriptInterface::luaCreatureIsImmune); registerMethod("Creature", "canSee", LuaScriptInterface::luaCreatureCanSee); registerMethod("Creature", "canSeeCreature", LuaScriptInterface::luaCreatureCanSeeCreature); @@ -1912,6 +2227,7 @@ void LuaScriptInterface::registerFunctions() registerMethod("Creature", "changeSpeed", LuaScriptInterface::luaCreatureChangeSpeed); registerMethod("Creature", "setDropLoot", LuaScriptInterface::luaCreatureSetDropLoot); + registerMethod("Creature", "setSkillLoss", LuaScriptInterface::luaCreatureSetSkillLoss); registerMethod("Creature", "getPosition", LuaScriptInterface::luaCreatureGetPosition); registerMethod("Creature", "getTile", LuaScriptInterface::luaCreatureGetTile); @@ -1919,6 +2235,7 @@ void LuaScriptInterface::registerFunctions() registerMethod("Creature", "setDirection", LuaScriptInterface::luaCreatureSetDirection); registerMethod("Creature", "getHealth", LuaScriptInterface::luaCreatureGetHealth); + registerMethod("Creature", "setHealth", LuaScriptInterface::luaCreatureSetHealth); registerMethod("Creature", "addHealth", LuaScriptInterface::luaCreatureAddHealth); registerMethod("Creature", "getMaxHealth", LuaScriptInterface::luaCreatureGetMaxHealth); registerMethod("Creature", "setMaxHealth", LuaScriptInterface::luaCreatureSetMaxHealth); @@ -1933,6 +2250,7 @@ void LuaScriptInterface::registerFunctions() registerMethod("Creature", "getCondition", LuaScriptInterface::luaCreatureGetCondition); registerMethod("Creature", "addCondition", LuaScriptInterface::luaCreatureAddCondition); registerMethod("Creature", "removeCondition", LuaScriptInterface::luaCreatureRemoveCondition); + registerMethod("Creature", "hasCondition", LuaScriptInterface::luaCreatureHasCondition); registerMethod("Creature", "remove", LuaScriptInterface::luaCreatureRemove); registerMethod("Creature", "teleportTo", LuaScriptInterface::luaCreatureTeleportTo); @@ -1945,6 +2263,9 @@ void LuaScriptInterface::registerFunctions() registerMethod("Creature", "getDescription", LuaScriptInterface::luaCreatureGetDescription); registerMethod("Creature", "getPathTo", LuaScriptInterface::luaCreatureGetPathTo); + registerMethod("Creature", "move", LuaScriptInterface::luaCreatureMove); + + registerMethod("Creature", "getZone", LuaScriptInterface::luaCreatureGetZone); // Player registerClass("Player", "Creature", LuaScriptInterface::luaPlayerCreate); @@ -1957,7 +2278,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "getAccountId", LuaScriptInterface::luaPlayerGetAccountId); registerMethod("Player", "getLastLoginSaved", LuaScriptInterface::luaPlayerGetLastLoginSaved); registerMethod("Player", "getLastLogout", LuaScriptInterface::luaPlayerGetLastLogout); - registerMethod("Player", "hasFlag", LuaScriptInterface::luaPlayerHasFlag); registerMethod("Player", "getAccountType", LuaScriptInterface::luaPlayerGetAccountType); registerMethod("Player", "setAccountType", LuaScriptInterface::luaPlayerSetAccountType); @@ -1968,10 +2288,10 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "getFreeCapacity", LuaScriptInterface::luaPlayerGetFreeCapacity); registerMethod("Player", "getDepotChest", LuaScriptInterface::luaPlayerGetDepotChest); + registerMethod("Player", "getInbox", LuaScriptInterface::luaPlayerGetInbox); - registerMethod("Player", "getMurderTimestamps", LuaScriptInterface::luaPlayerGetMurderTimestamps); - registerMethod("Player", "getPlayerKillerEnd", LuaScriptInterface::luaPlayerGetPlayerKillerEnd); - registerMethod("Player", "setPlayerKillerEnd", LuaScriptInterface::luaPlayerSetPlayerKillerEnd); + registerMethod("Player", "getSkullTime", LuaScriptInterface::luaPlayerGetSkullTime); + registerMethod("Player", "setSkullTime", LuaScriptInterface::luaPlayerSetSkullTime); registerMethod("Player", "getDeathPenalty", LuaScriptInterface::luaPlayerGetDeathPenalty); registerMethod("Player", "getExperience", LuaScriptInterface::luaPlayerGetExperience); @@ -1996,6 +2316,17 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "getSkillPercent", LuaScriptInterface::luaPlayerGetSkillPercent); registerMethod("Player", "getSkillTries", LuaScriptInterface::luaPlayerGetSkillTries); registerMethod("Player", "addSkillTries", LuaScriptInterface::luaPlayerAddSkillTries); + registerMethod("Player", "getSpecialSkill", LuaScriptInterface::luaPlayerGetSpecialSkill); + registerMethod("Player", "addSpecialSkill", LuaScriptInterface::luaPlayerAddSpecialSkill); + + registerMethod("Player", "addOfflineTrainingTime", LuaScriptInterface::luaPlayerAddOfflineTrainingTime); + registerMethod("Player", "getOfflineTrainingTime", LuaScriptInterface::luaPlayerGetOfflineTrainingTime); + registerMethod("Player", "removeOfflineTrainingTime", LuaScriptInterface::luaPlayerRemoveOfflineTrainingTime); + + registerMethod("Player", "addOfflineTrainingTries", LuaScriptInterface::luaPlayerAddOfflineTrainingTries); + + registerMethod("Player", "getOfflineTrainingSkill", LuaScriptInterface::luaPlayerGetOfflineTrainingSkill); + registerMethod("Player", "setOfflineTrainingSkill", LuaScriptInterface::luaPlayerSetOfflineTrainingSkill); registerMethod("Player", "getItemCount", LuaScriptInterface::luaPlayerGetItemCount); registerMethod("Player", "getItemById", LuaScriptInterface::luaPlayerGetItemById); @@ -2045,6 +2376,7 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "showTextDialog", LuaScriptInterface::luaPlayerShowTextDialog); registerMethod("Player", "sendTextMessage", LuaScriptInterface::luaPlayerSendTextMessage); + registerMethod("Player", "sendChannelMessage", LuaScriptInterface::luaPlayerSendChannelMessage); registerMethod("Player", "sendPrivateMessage", LuaScriptInterface::luaPlayerSendPrivateMessage); registerMethod("Player", "channelSay", LuaScriptInterface::luaPlayerChannelSay); registerMethod("Player", "openChannel", LuaScriptInterface::luaPlayerOpenChannel); @@ -2060,6 +2392,10 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "hasOutfit", LuaScriptInterface::luaPlayerHasOutfit); registerMethod("Player", "sendOutfitWindow", LuaScriptInterface::luaPlayerSendOutfitWindow); + registerMethod("Player", "addMount", LuaScriptInterface::luaPlayerAddMount); + registerMethod("Player", "removeMount", LuaScriptInterface::luaPlayerRemoveMount); + registerMethod("Player", "hasMount", LuaScriptInterface::luaPlayerHasMount); + registerMethod("Player", "getPremiumDays", LuaScriptInterface::luaPlayerGetPremiumDays); registerMethod("Player", "addPremiumDays", LuaScriptInterface::luaPlayerAddPremiumDays); registerMethod("Player", "removePremiumDays", LuaScriptInterface::luaPlayerRemovePremiumDays); @@ -2073,12 +2409,19 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "forgetSpell", LuaScriptInterface::luaPlayerForgetSpell); registerMethod("Player", "hasLearnedSpell", LuaScriptInterface::luaPlayerHasLearnedSpell); + registerMethod("Player", "sendTutorial", LuaScriptInterface::luaPlayerSendTutorial); + registerMethod("Player", "addMapMark", LuaScriptInterface::luaPlayerAddMapMark); + registerMethod("Player", "save", LuaScriptInterface::luaPlayerSave); + registerMethod("Player", "popupFYI", LuaScriptInterface::luaPlayerPopupFYI); registerMethod("Player", "isPzLocked", LuaScriptInterface::luaPlayerIsPzLocked); registerMethod("Player", "getClient", LuaScriptInterface::luaPlayerGetClient); + registerMethod("Player", "getHouse", LuaScriptInterface::luaPlayerGetHouse); + registerMethod("Player", "sendHouseWindow", LuaScriptInterface::luaPlayerSendHouseWindow); + registerMethod("Player", "setEditHouse", LuaScriptInterface::luaPlayerSetEditHouse); registerMethod("Player", "setGhostMode", LuaScriptInterface::luaPlayerSetGhostMode); @@ -2086,7 +2429,12 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "getContainerById", LuaScriptInterface::luaPlayerGetContainerById); registerMethod("Player", "getContainerIndex", LuaScriptInterface::luaPlayerGetContainerIndex); - registerMethod("Player", "getTotalDamage", LuaScriptInterface::luaPlayerGetTotalDamage); + registerMethod("Player", "getInstantSpells", LuaScriptInterface::luaPlayerGetInstantSpells); + registerMethod("Player", "canCast", LuaScriptInterface::luaPlayerCanCast); + + registerMethod("Player", "hasChaseMode", LuaScriptInterface::luaPlayerHasChaseMode); + registerMethod("Player", "hasSecureMode", LuaScriptInterface::luaPlayerHasSecureMode); + registerMethod("Player", "getFightMode", LuaScriptInterface::luaPlayerGetFightMode); // Monster registerClass("Monster", "Creature", LuaScriptInterface::luaMonsterCreate); @@ -2127,6 +2475,9 @@ void LuaScriptInterface::registerFunctions() registerMethod("Npc", "setMasterPos", LuaScriptInterface::luaNpcSetMasterPos); + registerMethod("Npc", "getSpeechBubble", LuaScriptInterface::luaNpcGetSpeechBubble); + registerMethod("Npc", "setSpeechBubble", LuaScriptInterface::luaNpcSetSpeechBubble); + // Guild registerClass("Guild", "", LuaScriptInterface::luaGuildCreate); registerMetaMethod("Guild", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -2139,6 +2490,9 @@ void LuaScriptInterface::registerFunctions() registerMethod("Guild", "getRankById", LuaScriptInterface::luaGuildGetRankById); registerMethod("Guild", "getRankByLevel", LuaScriptInterface::luaGuildGetRankByLevel); + registerMethod("Guild", "getMotd", LuaScriptInterface::luaGuildGetMotd); + registerMethod("Guild", "setMotd", LuaScriptInterface::luaGuildSetMotd); + // Group registerClass("Group", "", LuaScriptInterface::luaGroupCreate); registerMetaMethod("Group", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -2149,12 +2503,14 @@ void LuaScriptInterface::registerFunctions() registerMethod("Group", "getAccess", LuaScriptInterface::luaGroupGetAccess); registerMethod("Group", "getMaxDepotItems", LuaScriptInterface::luaGroupGetMaxDepotItems); registerMethod("Group", "getMaxVipEntries", LuaScriptInterface::luaGroupGetMaxVipEntries); + registerMethod("Group", "hasFlag", LuaScriptInterface::luaGroupHasFlag); // Vocation registerClass("Vocation", "", LuaScriptInterface::luaVocationCreate); registerMetaMethod("Vocation", "__eq", LuaScriptInterface::luaUserdataCompare); registerMethod("Vocation", "getId", LuaScriptInterface::luaVocationGetId); + registerMethod("Vocation", "getClientId", LuaScriptInterface::luaVocationGetClientId); registerMethod("Vocation", "getName", LuaScriptInterface::luaVocationGetName); registerMethod("Vocation", "getDescription", LuaScriptInterface::luaVocationGetDescription); @@ -2207,13 +2563,18 @@ void LuaScriptInterface::registerFunctions() registerMethod("House", "getDoors", LuaScriptInterface::luaHouseGetDoors); registerMethod("House", "getDoorCount", LuaScriptInterface::luaHouseGetDoorCount); + registerMethod("House", "getDoorIdByPosition", LuaScriptInterface::luaHouseGetDoorIdByPosition); registerMethod("House", "getTiles", LuaScriptInterface::luaHouseGetTiles); + registerMethod("House", "getItems", LuaScriptInterface::luaHouseGetItems); registerMethod("House", "getTileCount", LuaScriptInterface::luaHouseGetTileCount); + registerMethod("House", "canEditAccessList", LuaScriptInterface::luaHouseCanEditAccessList); registerMethod("House", "getAccessList", LuaScriptInterface::luaHouseGetAccessList); registerMethod("House", "setAccessList", LuaScriptInterface::luaHouseSetAccessList); + registerMethod("House", "kickPlayer", LuaScriptInterface::luaHouseKickPlayer); + // ItemType registerClass("ItemType", "", LuaScriptInterface::luaItemTypeCreate); registerMetaMethod("ItemType", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -2221,47 +2582,51 @@ void LuaScriptInterface::registerFunctions() registerMethod("ItemType", "isCorpse", LuaScriptInterface::luaItemTypeIsCorpse); registerMethod("ItemType", "isDoor", LuaScriptInterface::luaItemTypeIsDoor); registerMethod("ItemType", "isContainer", LuaScriptInterface::luaItemTypeIsContainer); - registerMethod("ItemType", "isChest", LuaScriptInterface::luaItemTypeIsChest); registerMethod("ItemType", "isFluidContainer", LuaScriptInterface::luaItemTypeIsFluidContainer); registerMethod("ItemType", "isMovable", LuaScriptInterface::luaItemTypeIsMovable); registerMethod("ItemType", "isRune", LuaScriptInterface::luaItemTypeIsRune); registerMethod("ItemType", "isStackable", LuaScriptInterface::luaItemTypeIsStackable); registerMethod("ItemType", "isReadable", LuaScriptInterface::luaItemTypeIsReadable); registerMethod("ItemType", "isWritable", LuaScriptInterface::luaItemTypeIsWritable); - registerMethod("ItemType", "isMagicField", LuaScriptInterface::luaItemTypeIsMagicField); - registerMethod("ItemType", "isSplash", LuaScriptInterface::luaItemTypeIsSplash); - registerMethod("ItemType", "isKey", LuaScriptInterface::luaItemTypeIsKey); - registerMethod("ItemType", "isDisguised", LuaScriptInterface::luaItemTypeIsDisguised); - registerMethod("ItemType", "isDestroyable", LuaScriptInterface::luaItemTypeIsDestroyable); + registerMethod("ItemType", "isBlocking", LuaScriptInterface::luaItemTypeIsBlocking); registerMethod("ItemType", "isGroundTile", LuaScriptInterface::luaItemTypeIsGroundTile); + registerMethod("ItemType", "isMagicField", LuaScriptInterface::luaItemTypeIsMagicField); + registerMethod("ItemType", "isUseable", LuaScriptInterface::luaItemTypeIsUseable); + registerMethod("ItemType", "isPickupable", LuaScriptInterface::luaItemTypeIsPickupable); registerMethod("ItemType", "getType", LuaScriptInterface::luaItemTypeGetType); registerMethod("ItemType", "getId", LuaScriptInterface::luaItemTypeGetId); - registerMethod("ItemType", "getDisguiseId", LuaScriptInterface::luaItemTypeGetDisguiseId); + registerMethod("ItemType", "getClientId", LuaScriptInterface::luaItemTypeGetClientId); registerMethod("ItemType", "getName", LuaScriptInterface::luaItemTypeGetName); registerMethod("ItemType", "getPluralName", LuaScriptInterface::luaItemTypeGetPluralName); registerMethod("ItemType", "getArticle", LuaScriptInterface::luaItemTypeGetArticle); registerMethod("ItemType", "getDescription", LuaScriptInterface::luaItemTypeGetDescription); registerMethod("ItemType", "getSlotPosition", LuaScriptInterface::luaItemTypeGetSlotPosition); - registerMethod("ItemType", "getDestroyTarget", LuaScriptInterface::luaItemTypeGetDestroyTarget); registerMethod("ItemType", "getCharges", LuaScriptInterface::luaItemTypeGetCharges); registerMethod("ItemType", "getFluidSource", LuaScriptInterface::luaItemTypeGetFluidSource); registerMethod("ItemType", "getCapacity", LuaScriptInterface::luaItemTypeGetCapacity); registerMethod("ItemType", "getWeight", LuaScriptInterface::luaItemTypeGetWeight); + registerMethod("ItemType", "getHitChance", LuaScriptInterface::luaItemTypeGetHitChance); registerMethod("ItemType", "getShootRange", LuaScriptInterface::luaItemTypeGetShootRange); registerMethod("ItemType", "getAttack", LuaScriptInterface::luaItemTypeGetAttack); registerMethod("ItemType", "getDefense", LuaScriptInterface::luaItemTypeGetDefense); + registerMethod("ItemType", "getExtraDefense", LuaScriptInterface::luaItemTypeGetExtraDefense); registerMethod("ItemType", "getArmor", LuaScriptInterface::luaItemTypeGetArmor); registerMethod("ItemType", "getWeaponType", LuaScriptInterface::luaItemTypeGetWeaponType); + registerMethod("ItemType", "getElementType", LuaScriptInterface::luaItemTypeGetElementType); + registerMethod("ItemType", "getElementDamage", LuaScriptInterface::luaItemTypeGetElementDamage); + registerMethod("ItemType", "getTransformEquipId", LuaScriptInterface::luaItemTypeGetTransformEquipId); registerMethod("ItemType", "getTransformDeEquipId", LuaScriptInterface::luaItemTypeGetTransformDeEquipId); + registerMethod("ItemType", "getDestroyId", LuaScriptInterface::luaItemTypeGetDestroyId); registerMethod("ItemType", "getDecayId", LuaScriptInterface::luaItemTypeGetDecayId); - registerMethod("ItemType", "getNutrition", LuaScriptInterface::luaItemTypeGetNutrition); registerMethod("ItemType", "getRequiredLevel", LuaScriptInterface::luaItemTypeGetRequiredLevel); + registerMethod("ItemType", "getAmmoType", LuaScriptInterface::luaItemTypeGetAmmoType); + registerMethod("ItemType", "getCorpseType", LuaScriptInterface::luaItemTypeGetCorpseType); registerMethod("ItemType", "hasSubType", LuaScriptInterface::luaItemTypeHasSubType); @@ -2273,7 +2638,8 @@ void LuaScriptInterface::registerFunctions() registerMethod("Combat", "setFormula", LuaScriptInterface::luaCombatSetFormula); registerMethod("Combat", "setArea", LuaScriptInterface::luaCombatSetArea); - registerMethod("Combat", "setCondition", LuaScriptInterface::luaCombatSetCondition); + registerMethod("Combat", "addCondition", LuaScriptInterface::luaCombatAddCondition); + registerMethod("Combat", "clearConditions", LuaScriptInterface::luaCombatClearConditions); registerMethod("Combat", "setCallback", LuaScriptInterface::luaCombatSetCallback); registerMethod("Combat", "setOrigin", LuaScriptInterface::luaCombatSetOrigin); @@ -2297,10 +2663,10 @@ void LuaScriptInterface::registerFunctions() registerMethod("Condition", "setTicks", LuaScriptInterface::luaConditionSetTicks); registerMethod("Condition", "setParameter", LuaScriptInterface::luaConditionSetParameter); - registerMethod("Condition", "setSpeedDelta", LuaScriptInterface::luaConditionSetSpeedDelta); + registerMethod("Condition", "setFormula", LuaScriptInterface::luaConditionSetFormula); registerMethod("Condition", "setOutfit", LuaScriptInterface::luaConditionSetOutfit); - registerMethod("Condition", "setTiming", LuaScriptInterface::luaConditionSetTiming); + registerMethod("Condition", "addDamage", LuaScriptInterface::luaConditionAddDamage); // MonsterType registerClass("MonsterType", "", LuaScriptInterface::luaMonsterTypeCreate); @@ -2312,48 +2678,109 @@ void LuaScriptInterface::registerFunctions() registerMethod("MonsterType", "isIllusionable", LuaScriptInterface::luaMonsterTypeIsIllusionable); registerMethod("MonsterType", "isHostile", LuaScriptInterface::luaMonsterTypeIsHostile); registerMethod("MonsterType", "isPushable", LuaScriptInterface::luaMonsterTypeIsPushable); - registerMethod("MonsterType", "isHealthShown", LuaScriptInterface::luaMonsterTypeIsHealthShown); + registerMethod("MonsterType", "isHealthHidden", LuaScriptInterface::luaMonsterTypeIsHealthHidden); registerMethod("MonsterType", "canPushItems", LuaScriptInterface::luaMonsterTypeCanPushItems); registerMethod("MonsterType", "canPushCreatures", LuaScriptInterface::luaMonsterTypeCanPushCreatures); - registerMethod("MonsterType", "getName", LuaScriptInterface::luaMonsterTypeGetName); - registerMethod("MonsterType", "getNameDescription", LuaScriptInterface::luaMonsterTypeGetNameDescription); + registerMethod("MonsterType", "name", LuaScriptInterface::luaMonsterTypeName); - registerMethod("MonsterType", "getHealth", LuaScriptInterface::luaMonsterTypeGetHealth); - registerMethod("MonsterType", "getMaxHealth", LuaScriptInterface::luaMonsterTypeGetMaxHealth); - registerMethod("MonsterType", "getRunHealth", LuaScriptInterface::luaMonsterTypeGetRunHealth); - registerMethod("MonsterType", "getExperience", LuaScriptInterface::luaMonsterTypeGetExperience); + registerMethod("MonsterType", "nameDescription", LuaScriptInterface::luaMonsterTypeNameDescription); - registerMethod("MonsterType", "getCombatImmunities", LuaScriptInterface::luaMonsterTypeGetCombatImmunities); - registerMethod("MonsterType", "getConditionImmunities", LuaScriptInterface::luaMonsterTypeGetConditionImmunities); + registerMethod("MonsterType", "health", LuaScriptInterface::luaMonsterTypeHealth); + registerMethod("MonsterType", "maxHealth", LuaScriptInterface::luaMonsterTypeMaxHealth); + registerMethod("MonsterType", "runHealth", LuaScriptInterface::luaMonsterTypeRunHealth); + registerMethod("MonsterType", "experience", LuaScriptInterface::luaMonsterTypeExperience); + + registerMethod("MonsterType", "combatImmunities", LuaScriptInterface::luaMonsterTypeCombatImmunities); + registerMethod("MonsterType", "conditionImmunities", LuaScriptInterface::luaMonsterTypeConditionImmunities); registerMethod("MonsterType", "getAttackList", LuaScriptInterface::luaMonsterTypeGetAttackList); + registerMethod("MonsterType", "addAttack", LuaScriptInterface::luaMonsterTypeAddAttack); + registerMethod("MonsterType", "getDefenseList", LuaScriptInterface::luaMonsterTypeGetDefenseList); + registerMethod("MonsterType", "addDefense", LuaScriptInterface::luaMonsterTypeAddDefense); + registerMethod("MonsterType", "getElementList", LuaScriptInterface::luaMonsterTypeGetElementList); + registerMethod("MonsterType", "addElement", LuaScriptInterface::luaMonsterTypeAddElement); registerMethod("MonsterType", "getVoices", LuaScriptInterface::luaMonsterTypeGetVoices); + registerMethod("MonsterType", "addVoice", LuaScriptInterface::luaMonsterTypeAddVoice); + registerMethod("MonsterType", "getLoot", LuaScriptInterface::luaMonsterTypeGetLoot); + registerMethod("MonsterType", "addLoot", LuaScriptInterface::luaMonsterTypeAddLoot); + registerMethod("MonsterType", "getCreatureEvents", LuaScriptInterface::luaMonsterTypeGetCreatureEvents); + registerMethod("MonsterType", "registerEvent", LuaScriptInterface::luaMonsterTypeRegisterEvent); + + registerMethod("MonsterType", "eventType", LuaScriptInterface::luaMonsterTypeEventType); + registerMethod("MonsterType", "onThink", LuaScriptInterface::luaMonsterTypeEventOnCallback); + registerMethod("MonsterType", "onAppear", LuaScriptInterface::luaMonsterTypeEventOnCallback); + registerMethod("MonsterType", "onDisappear", LuaScriptInterface::luaMonsterTypeEventOnCallback); + registerMethod("MonsterType", "onMove", LuaScriptInterface::luaMonsterTypeEventOnCallback); + registerMethod("MonsterType", "onSay", LuaScriptInterface::luaMonsterTypeEventOnCallback); registerMethod("MonsterType", "getSummonList", LuaScriptInterface::luaMonsterTypeGetSummonList); - registerMethod("MonsterType", "getMaxSummons", LuaScriptInterface::luaMonsterTypeGetMaxSummons); + registerMethod("MonsterType", "addSummon", LuaScriptInterface::luaMonsterTypeAddSummon); - registerMethod("MonsterType", "getArmor", LuaScriptInterface::luaMonsterTypeGetArmor); - registerMethod("MonsterType", "getDefense", LuaScriptInterface::luaMonsterTypeGetDefense); - registerMethod("MonsterType", "getOutfit", LuaScriptInterface::luaMonsterTypeGetOutfit); - registerMethod("MonsterType", "getRace", LuaScriptInterface::luaMonsterTypeGetRace); - registerMethod("MonsterType", "getCorpseId", LuaScriptInterface::luaMonsterTypeGetCorpseId); - registerMethod("MonsterType", "getManaCost", LuaScriptInterface::luaMonsterTypeGetManaCost); - registerMethod("MonsterType", "getBaseSpeed", LuaScriptInterface::luaMonsterTypeGetBaseSpeed); - registerMethod("MonsterType", "getLight", LuaScriptInterface::luaMonsterTypeGetLight); + registerMethod("MonsterType", "maxSummons", LuaScriptInterface::luaMonsterTypeMaxSummons); - registerMethod("MonsterType", "getTargetDistance", LuaScriptInterface::luaMonsterTypeGetTargetDistance); - registerMethod("MonsterType", "getChangeTargetChance", LuaScriptInterface::luaMonsterTypeGetChangeTargetChance); - registerMethod("MonsterType", "getChangeTargetSpeed", LuaScriptInterface::luaMonsterTypeGetChangeTargetSpeed); + registerMethod("MonsterType", "armor", LuaScriptInterface::luaMonsterTypeArmor); + registerMethod("MonsterType", "defense", LuaScriptInterface::luaMonsterTypeDefense); + registerMethod("MonsterType", "outfit", LuaScriptInterface::luaMonsterTypeOutfit); + registerMethod("MonsterType", "race", LuaScriptInterface::luaMonsterTypeRace); + registerMethod("MonsterType", "corpseId", LuaScriptInterface::luaMonsterTypeCorpseId); + registerMethod("MonsterType", "manaCost", LuaScriptInterface::luaMonsterTypeManaCost); + registerMethod("MonsterType", "baseSpeed", LuaScriptInterface::luaMonsterTypeBaseSpeed); + registerMethod("MonsterType", "light", LuaScriptInterface::luaMonsterTypeLight); + + registerMethod("MonsterType", "staticAttackChance", LuaScriptInterface::luaMonsterTypeStaticAttackChance); + registerMethod("MonsterType", "targetDistance", LuaScriptInterface::luaMonsterTypeTargetDistance); + registerMethod("MonsterType", "yellChance", LuaScriptInterface::luaMonsterTypeYellChance); + registerMethod("MonsterType", "yellSpeedTicks", LuaScriptInterface::luaMonsterTypeYellSpeedTicks); + registerMethod("MonsterType", "changeTargetChance", LuaScriptInterface::luaMonsterTypeChangeTargetChance); + registerMethod("MonsterType", "changeTargetSpeed", LuaScriptInterface::luaMonsterTypeChangeTargetSpeed); + + // Loot + registerClass("Loot", "", LuaScriptInterface::luaCreateLoot); + registerMetaMethod("Loot", "__gc", LuaScriptInterface::luaDeleteLoot); + registerMethod("Loot", "delete", LuaScriptInterface::luaDeleteLoot); + + registerMethod("Loot", "setId", LuaScriptInterface::luaLootSetId); + registerMethod("Loot", "setMaxCount", LuaScriptInterface::luaLootSetMaxCount); + registerMethod("Loot", "setSubType", LuaScriptInterface::luaLootSetSubType); + registerMethod("Loot", "setChance", LuaScriptInterface::luaLootSetChance); + registerMethod("Loot", "setActionId", LuaScriptInterface::luaLootSetActionId); + registerMethod("Loot", "setDescription", LuaScriptInterface::luaLootSetDescription); + registerMethod("Loot", "addChildLoot", LuaScriptInterface::luaLootAddChildLoot); + + // MonsterSpell + registerClass("MonsterSpell", "", LuaScriptInterface::luaCreateMonsterSpell); + registerMetaMethod("MonsterSpell", "__gc", LuaScriptInterface::luaDeleteMonsterSpell); + registerMethod("MonsterSpell", "delete", LuaScriptInterface::luaDeleteMonsterSpell); + + registerMethod("MonsterSpell", "setType", LuaScriptInterface::luaMonsterSpellSetType); + registerMethod("MonsterSpell", "setScriptName", LuaScriptInterface::luaMonsterSpellSetScriptName); + registerMethod("MonsterSpell", "setChance", LuaScriptInterface::luaMonsterSpellSetChance); + registerMethod("MonsterSpell", "setInterval", LuaScriptInterface::luaMonsterSpellSetInterval); + registerMethod("MonsterSpell", "setRange", LuaScriptInterface::luaMonsterSpellSetRange); + registerMethod("MonsterSpell", "setCombatValue", LuaScriptInterface::luaMonsterSpellSetCombatValue); + registerMethod("MonsterSpell", "setCombatType", LuaScriptInterface::luaMonsterSpellSetCombatType); + registerMethod("MonsterSpell", "setAttackValue", LuaScriptInterface::luaMonsterSpellSetAttackValue); + registerMethod("MonsterSpell", "setNeedTarget", LuaScriptInterface::luaMonsterSpellSetNeedTarget); + registerMethod("MonsterSpell", "setCombatLength", LuaScriptInterface::luaMonsterSpellSetCombatLength); + registerMethod("MonsterSpell", "setCombatSpread", LuaScriptInterface::luaMonsterSpellSetCombatSpread); + registerMethod("MonsterSpell", "setCombatRadius", LuaScriptInterface::luaMonsterSpellSetCombatRadius); + registerMethod("MonsterSpell", "setConditionType", LuaScriptInterface::luaMonsterSpellSetConditionType); + registerMethod("MonsterSpell", "setConditionDamage", LuaScriptInterface::luaMonsterSpellSetConditionDamage); + registerMethod("MonsterSpell", "setConditionSpeedChange", LuaScriptInterface::luaMonsterSpellSetConditionSpeedChange); + registerMethod("MonsterSpell", "setConditionDuration", LuaScriptInterface::luaMonsterSpellSetConditionDuration); + registerMethod("MonsterSpell", "setConditionTickInterval", LuaScriptInterface::luaMonsterSpellSetConditionTickInterval); + registerMethod("MonsterSpell", "setCombatShootEffect", LuaScriptInterface::luaMonsterSpellSetCombatShootEffect); + registerMethod("MonsterSpell", "setCombatEffect", LuaScriptInterface::luaMonsterSpellSetCombatEffect); // Party - registerClass("Party", "", nullptr); + registerClass("Party", "", LuaScriptInterface::luaPartyCreate); registerMetaMethod("Party", "__eq", LuaScriptInterface::luaUserdataCompare); registerMethod("Party", "disband", LuaScriptInterface::luaPartyDisband); @@ -2377,6 +2804,154 @@ void LuaScriptInterface::registerFunctions() registerMethod("Party", "isSharedExperienceEnabled", LuaScriptInterface::luaPartyIsSharedExperienceEnabled); registerMethod("Party", "shareExperience", LuaScriptInterface::luaPartyShareExperience); registerMethod("Party", "setSharedExperience", LuaScriptInterface::luaPartySetSharedExperience); + + // Spells + registerClass("Spell", "", LuaScriptInterface::luaSpellCreate); + registerMetaMethod("Spell", "__eq", LuaScriptInterface::luaUserdataCompare); + + registerMethod("Spell", "onCastSpell", LuaScriptInterface::luaSpellOnCastSpell); + registerMethod("Spell", "register", LuaScriptInterface::luaSpellRegister); + registerMethod("Spell", "name", LuaScriptInterface::luaSpellName); + registerMethod("Spell", "id", LuaScriptInterface::luaSpellId); + registerMethod("Spell", "group", LuaScriptInterface::luaSpellGroup); + registerMethod("Spell", "cooldown", LuaScriptInterface::luaSpellCooldown); + registerMethod("Spell", "groupCooldown", LuaScriptInterface::luaSpellGroupCooldown); + registerMethod("Spell", "level", LuaScriptInterface::luaSpellLevel); + registerMethod("Spell", "magicLevel", LuaScriptInterface::luaSpellMagicLevel); + registerMethod("Spell", "mana", LuaScriptInterface::luaSpellMana); + registerMethod("Spell", "manaPercent", LuaScriptInterface::luaSpellManaPercent); + registerMethod("Spell", "soul", LuaScriptInterface::luaSpellSoul); + registerMethod("Spell", "range", LuaScriptInterface::luaSpellRange); + registerMethod("Spell", "isPremium", LuaScriptInterface::luaSpellPremium); + registerMethod("Spell", "isEnabled", LuaScriptInterface::luaSpellEnabled); + registerMethod("Spell", "needTarget", LuaScriptInterface::luaSpellNeedTarget); + registerMethod("Spell", "needWeapon", LuaScriptInterface::luaSpellNeedWeapon); + registerMethod("Spell", "needLearn", LuaScriptInterface::luaSpellNeedLearn); + registerMethod("Spell", "isSelfTarget", LuaScriptInterface::luaSpellSelfTarget); + registerMethod("Spell", "isBlocking", LuaScriptInterface::luaSpellBlocking); + registerMethod("Spell", "isAggressive", LuaScriptInterface::luaSpellAggressive); + registerMethod("Spell", "vocation", LuaScriptInterface::luaSpellVocation); + + // only for InstantSpell + registerMethod("Spell", "words", LuaScriptInterface::luaSpellWords); + registerMethod("Spell", "needDirection", LuaScriptInterface::luaSpellNeedDirection); + registerMethod("Spell", "hasParams", LuaScriptInterface::luaSpellHasParams); + registerMethod("Spell", "hasPlayerNameParam", LuaScriptInterface::luaSpellHasPlayerNameParam); + registerMethod("Spell", "needCasterTargetOrDirection", LuaScriptInterface::luaSpellNeedCasterTargetOrDirection); + registerMethod("Spell", "isBlockingWalls", LuaScriptInterface::luaSpellIsBlockingWalls); + + // only for RuneSpells + registerMethod("Spell", "runeId", LuaScriptInterface::luaSpellRuneId); + registerMethod("Spell", "charges", LuaScriptInterface::luaSpellCharges); + registerMethod("Spell", "allowFarUse", LuaScriptInterface::luaSpellAllowFarUse); + registerMethod("Spell", "blockWalls", LuaScriptInterface::luaSpellBlockWalls); + registerMethod("Spell", "checkFloor", LuaScriptInterface::luaSpellCheckFloor); + + // Action + registerClass("Action", "", LuaScriptInterface::luaCreateAction); + registerMethod("Action", "onUse", LuaScriptInterface::luaActionOnUse); + registerMethod("Action", "register", LuaScriptInterface::luaActionRegister); + registerMethod("Action", "id", LuaScriptInterface::luaActionItemId); + registerMethod("Action", "aid", LuaScriptInterface::luaActionActionId); + registerMethod("Action", "uid", LuaScriptInterface::luaActionUniqueId); + registerMethod("Action", "allowFarUse", LuaScriptInterface::luaActionAllowFarUse); + registerMethod("Action", "blockWalls", LuaScriptInterface::luaActionBlockWalls); + registerMethod("Action", "checkFloor", LuaScriptInterface::luaActionCheckFloor); + + // TalkAction + registerClass("TalkAction", "", LuaScriptInterface::luaCreateTalkaction); + registerMethod("TalkAction", "onSay", LuaScriptInterface::luaTalkactionOnSay); + registerMethod("TalkAction", "register", LuaScriptInterface::luaTalkactionRegister); + registerMethod("TalkAction", "separator", LuaScriptInterface::luaTalkactionSeparator); + + // CreatureEvent + registerClass("CreatureEvent", "", LuaScriptInterface::luaCreateCreatureEvent); + registerMethod("CreatureEvent", "type", LuaScriptInterface::luaCreatureEventType); + registerMethod("CreatureEvent", "register", LuaScriptInterface::luaCreatureEventRegister); + registerMethod("CreatureEvent", "onLogin", LuaScriptInterface::luaCreatureEventOnCallback); + registerMethod("CreatureEvent", "onLogout", LuaScriptInterface::luaCreatureEventOnCallback); + registerMethod("CreatureEvent", "onThink", LuaScriptInterface::luaCreatureEventOnCallback); + registerMethod("CreatureEvent", "onPrepareDeath", LuaScriptInterface::luaCreatureEventOnCallback); + registerMethod("CreatureEvent", "onDeath", LuaScriptInterface::luaCreatureEventOnCallback); + registerMethod("CreatureEvent", "onKill", LuaScriptInterface::luaCreatureEventOnCallback); + registerMethod("CreatureEvent", "onAdvance", LuaScriptInterface::luaCreatureEventOnCallback); + registerMethod("CreatureEvent", "onModalWindow", LuaScriptInterface::luaCreatureEventOnCallback); + registerMethod("CreatureEvent", "onTextEdit", LuaScriptInterface::luaCreatureEventOnCallback); + registerMethod("CreatureEvent", "onHealthChange", LuaScriptInterface::luaCreatureEventOnCallback); + registerMethod("CreatureEvent", "onManaChange", LuaScriptInterface::luaCreatureEventOnCallback); + registerMethod("CreatureEvent", "onExtendedOpcode", LuaScriptInterface::luaCreatureEventOnCallback); + + // MoveEvent + registerClass("MoveEvent", "", LuaScriptInterface::luaCreateMoveEvent); + registerMethod("MoveEvent", "type", LuaScriptInterface::luaMoveEventType); + registerMethod("MoveEvent", "register", LuaScriptInterface::luaMoveEventRegister); + registerMethod("MoveEvent", "level", LuaScriptInterface::luaMoveEventLevel); + registerMethod("MoveEvent", "magicLevel", LuaScriptInterface::luaMoveEventMagLevel); + registerMethod("MoveEvent", "slot", LuaScriptInterface::luaMoveEventSlot); + registerMethod("MoveEvent", "id", LuaScriptInterface::luaMoveEventItemId); + registerMethod("MoveEvent", "aid", LuaScriptInterface::luaMoveEventActionId); + registerMethod("MoveEvent", "uid", LuaScriptInterface::luaMoveEventUniqueId); + registerMethod("MoveEvent", "position", LuaScriptInterface::luaMoveEventPosition); + registerMethod("MoveEvent", "premium", LuaScriptInterface::luaMoveEventPremium); + registerMethod("MoveEvent", "vocation", LuaScriptInterface::luaMoveEventVocation); + registerMethod("MoveEvent", "onEquip", LuaScriptInterface::luaMoveEventOnCallback); + registerMethod("MoveEvent", "onDeEquip", LuaScriptInterface::luaMoveEventOnCallback); + registerMethod("MoveEvent", "onStepIn", LuaScriptInterface::luaMoveEventOnCallback); + registerMethod("MoveEvent", "onStepOut", LuaScriptInterface::luaMoveEventOnCallback); + registerMethod("MoveEvent", "onAddItem", LuaScriptInterface::luaMoveEventOnCallback); + registerMethod("MoveEvent", "onRemoveItem", LuaScriptInterface::luaMoveEventOnCallback); + + // GlobalEvent + registerClass("GlobalEvent", "", LuaScriptInterface::luaCreateGlobalEvent); + registerMethod("GlobalEvent", "type", LuaScriptInterface::luaGlobalEventType); + registerMethod("GlobalEvent", "register", LuaScriptInterface::luaGlobalEventRegister); + registerMethod("GlobalEvent", "time", LuaScriptInterface::luaGlobalEventTime); + registerMethod("GlobalEvent", "interval", LuaScriptInterface::luaGlobalEventInterval); + registerMethod("GlobalEvent", "onThink", LuaScriptInterface::luaGlobalEventOnCallback); + registerMethod("GlobalEvent", "onTime", LuaScriptInterface::luaGlobalEventOnCallback); + registerMethod("GlobalEvent", "onStartup", LuaScriptInterface::luaGlobalEventOnCallback); + registerMethod("GlobalEvent", "onShutdown", LuaScriptInterface::luaGlobalEventOnCallback); + registerMethod("GlobalEvent", "onRecord", LuaScriptInterface::luaGlobalEventOnCallback); + + // Weapon + registerClass("Weapon", "", LuaScriptInterface::luaCreateWeapon); + registerMethod("Weapon", "action", LuaScriptInterface::luaWeaponAction); + registerMethod("Weapon", "register", LuaScriptInterface::luaWeaponRegister); + registerMethod("Weapon", "id", LuaScriptInterface::luaWeaponId); + registerMethod("Weapon", "level", LuaScriptInterface::luaWeaponLevel); + registerMethod("Weapon", "magicLevel", LuaScriptInterface::luaWeaponMagicLevel); + registerMethod("Weapon", "mana", LuaScriptInterface::luaWeaponMana); + registerMethod("Weapon", "manaPercent", LuaScriptInterface::luaWeaponManaPercent); + registerMethod("Weapon", "health", LuaScriptInterface::luaWeaponHealth); + registerMethod("Weapon", "healthPercent", LuaScriptInterface::luaWeaponHealthPercent); + registerMethod("Weapon", "soul", LuaScriptInterface::luaWeaponSoul); + registerMethod("Weapon", "breakChance", LuaScriptInterface::luaWeaponBreakChance); + registerMethod("Weapon", "premium", LuaScriptInterface::luaWeaponPremium); + registerMethod("Weapon", "wieldUnproperly", LuaScriptInterface::luaWeaponUnproperly); + registerMethod("Weapon", "vocation", LuaScriptInterface::luaWeaponVocation); + registerMethod("Weapon", "onUseWeapon", LuaScriptInterface::luaWeaponOnUseWeapon); + registerMethod("Weapon", "element", LuaScriptInterface::luaWeaponElement); + registerMethod("Weapon", "attack", LuaScriptInterface::luaWeaponAttack); + registerMethod("Weapon", "defense", LuaScriptInterface::luaWeaponDefense); + registerMethod("Weapon", "range", LuaScriptInterface::luaWeaponRange); + registerMethod("Weapon", "charges", LuaScriptInterface::luaWeaponCharges); + registerMethod("Weapon", "duration", LuaScriptInterface::luaWeaponDuration); + registerMethod("Weapon", "decayTo", LuaScriptInterface::luaWeaponDecayTo); + registerMethod("Weapon", "transformEquipTo", LuaScriptInterface::luaWeaponTransformEquipTo); + registerMethod("Weapon", "transformDeEquipTo", LuaScriptInterface::luaWeaponTransformDeEquipTo); + registerMethod("Weapon", "slotType", LuaScriptInterface::luaWeaponSlotType); + registerMethod("Weapon", "hitChance", LuaScriptInterface::luaWeaponHitChance); + registerMethod("Weapon", "extraElement", LuaScriptInterface::luaWeaponExtraElement); + + // exclusively for distance weapons + registerMethod("Weapon", "ammoType", LuaScriptInterface::luaWeaponAmmoType); + registerMethod("Weapon", "maxHitChance", LuaScriptInterface::luaWeaponMaxHitChance); + + // exclusively for wands + registerMethod("Weapon", "damage", LuaScriptInterface::luaWeaponWandDamage); + + // exclusively for wands & distance weapons + registerMethod("Weapon", "shootType", LuaScriptInterface::luaWeaponShootType); } #undef registerEnum @@ -2516,61 +3091,6 @@ void LuaScriptInterface::registerGlobalBoolean(const std::string& name, bool val lua_setglobal(luaState, name.c_str()); } -int LuaScriptInterface::luaGetPlayerFlagValue(lua_State* L) -{ - //getPlayerFlagValue(cid, flag) - Player* player = getPlayer(L, 1); - if (player) { - PlayerFlags flag = getNumber(L, 2); - pushBoolean(L, player->hasFlag(flag)); - } else { - reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); - pushBoolean(L, false); - } - return 1; -} - -int LuaScriptInterface::luaGetPlayerInstantSpellCount(lua_State* L) -{ - //getPlayerInstantSpellCount(cid) - Player* player = getPlayer(L, 1); - if (player) { - lua_pushnumber(L, g_spells->getInstantSpellCount(player)); - } else { - reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); - pushBoolean(L, false); - } - return 1; -} - -int LuaScriptInterface::luaGetPlayerInstantSpellInfo(lua_State* L) -{ - //getPlayerInstantSpellInfo(cid, index) - Player* player = getPlayer(L, 1); - if (!player) { - reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); - pushBoolean(L, false); - return 1; - } - - uint32_t index = getNumber(L, 2); - InstantSpell* spell = g_spells->getInstantSpellByIndex(player, index); - if (!spell) { - reportErrorFunc(getErrorDesc(LUA_ERROR_SPELL_NOT_FOUND)); - pushBoolean(L, false); - return 1; - } - - lua_createtable(L, 0, 6); - setField(L, "name", spell->getName()); - setField(L, "words", spell->getWords()); - setField(L, "level", spell->getLevel()); - setField(L, "mlevel", spell->getMagicLevel()); - setField(L, "mana", spell->getManaCost(player)); - setField(L, "manapercent", spell->getManaPercent()); - return 1; -} - int LuaScriptInterface::luaDoPlayerAddItem(lua_State* L) { //doPlayerAddItem(cid, itemid, count/subtype, canDropOnMap) @@ -2646,138 +3166,6 @@ int LuaScriptInterface::luaDoPlayerAddItem(lua_State* L) return 1; } -int LuaScriptInterface::luaDoTileAddItemEx(lua_State* L) -{ - //doTileAddItemEx(pos, uid) - const Position& pos = getPosition(L, 1); - - Tile* tile = g_game.map.getTile(pos); - if (!tile) { - std::ostringstream ss; - ss << pos << ' ' << getErrorDesc(LUA_ERROR_TILE_NOT_FOUND); - reportErrorFunc(ss.str()); - pushBoolean(L, false); - return 1; - } - - uint32_t uid = getNumber(L, 2); - Item* item = getScriptEnv()->getItemByUID(uid); - if (!item) { - reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); - pushBoolean(L, false); - return 1; - } - - if (item->getParent() != VirtualCylinder::virtualCylinder) { - reportErrorFunc("Item already has a parent"); - pushBoolean(L, false); - return 1; - } - - lua_pushnumber(L, g_game.internalAddItem(tile, item)); - return 1; -} - -int LuaScriptInterface::luaDoCreateItem(lua_State* L) -{ - //doCreateItem(itemid, type/count, pos) - //Returns uid of the created item, only works on tiles. - const Position& pos = getPosition(L, 3); - Tile* tile = g_game.map.getTile(pos); - if (!tile) { - std::ostringstream ss; - ss << pos << ' ' << getErrorDesc(LUA_ERROR_TILE_NOT_FOUND); - reportErrorFunc(ss.str()); - pushBoolean(L, false); - return 1; - } - - ScriptEnvironment* env = getScriptEnv(); - - int32_t itemCount = 1; - int32_t subType = 1; - - uint16_t itemId = getNumber(L, 1); - uint32_t count = getNumber(L, 2, 1); - - const ItemType& it = Item::items[itemId]; - if (it.hasSubType()) { - if (it.stackable) { - itemCount = static_cast(std::ceil(static_cast(count) / 100)); - } - - subType = count; - } else { - itemCount = std::max(1, count); - } - - while (itemCount > 0) { - int32_t stackCount = std::min(100, subType); - Item* newItem = Item::CreateItem(itemId, stackCount); - if (!newItem) { - reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); - pushBoolean(L, false); - return 1; - } - - if (it.stackable) { - subType -= stackCount; - } - - ReturnValue ret = g_game.internalAddItem(tile, newItem, INDEX_WHEREEVER, FLAG_NOLIMIT); - if (ret != RETURNVALUE_NOERROR) { - delete newItem; - pushBoolean(L, false); - return 1; - } - - if (--itemCount == 0) { - if (newItem->getParent()) { - uint32_t uid = env->addThing(newItem); - lua_pushnumber(L, uid); - return 1; - } else { - //stackable item stacked with existing object, newItem will be released - pushBoolean(L, false); - return 1; - } - } - } - - pushBoolean(L, false); - return 1; -} - -int LuaScriptInterface::luaDoCreateItemEx(lua_State* L) -{ - //doCreateItemEx(itemid, count/subtype) - //Returns uid of the created item - uint16_t itemId = getNumber(L, 1); - uint32_t count = getNumber(L, 2, 1); - - const ItemType& it = Item::items[itemId]; - if (it.stackable && count > 100) { - reportErrorFunc("Stack count cannot be higher than 100."); - count = 100; - } - - Item* newItem = Item::CreateItem(itemId, count); - if (!newItem) { - reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); - pushBoolean(L, false); - return 1; - } - - newItem->setParent(VirtualCylinder::virtualCylinder); - - ScriptEnvironment* env = getScriptEnv(); - env->addTempItem(newItem); - - uint32_t uid = env->addThing(newItem); - lua_pushnumber(L, uid); - return 1; -} - int LuaScriptInterface::luaDebugPrint(lua_State* L) { //debugPrint(text) @@ -2796,8 +3184,7 @@ int LuaScriptInterface::luaGetWorldTime(lua_State* L) int LuaScriptInterface::luaGetWorldLight(lua_State* L) { //getWorldLight() - LightInfo lightInfo; - g_game.getWorldLightInfo(lightInfo); + LightInfo lightInfo = g_game.getWorldLightInfo(); lua_pushnumber(L, lightInfo.level); lua_pushnumber(L, lightInfo.color); return 2; @@ -2894,8 +3281,8 @@ int LuaScriptInterface::luaDoAreaCombatHealth(lua_State* L) CombatDamage damage; damage.origin = getNumber(L, 8, ORIGIN_SPELL); - damage.type = combatType; - damage.value = normal_random(getNumber(L, 6), getNumber(L, 5)); + damage.primary.type = combatType; + damage.primary.value = normal_random(getNumber(L, 6), getNumber(L, 5)); Combat::doCombatHealth(creature, getPosition(L, 3), area, damage, params); pushBoolean(L, true); @@ -2931,8 +3318,8 @@ int LuaScriptInterface::luaDoTargetCombatHealth(lua_State* L) CombatDamage damage; damage.origin = getNumber(L, 7, ORIGIN_SPELL); - damage.type = combatType; - damage.value = normal_random(getNumber(L, 4), getNumber(L, 5)); + damage.primary.type = combatType; + damage.primary.value = normal_random(getNumber(L, 4), getNumber(L, 5)); Combat::doCombatHealth(creature, target, damage, params); pushBoolean(L, true); @@ -2957,8 +3344,8 @@ int LuaScriptInterface::luaDoAreaCombatMana(lua_State* L) CombatDamage damage; damage.origin = getNumber(L, 7, ORIGIN_SPELL); - damage.type = COMBAT_MANADRAIN; - damage.value = normal_random(getNumber(L, 4), getNumber(L, 5)); + damage.primary.type = COMBAT_MANADRAIN; + damage.primary.value = normal_random(getNumber(L, 4), getNumber(L, 5)); Position pos = getPosition(L, 2); Combat::doCombatMana(creature, pos, area, damage, params); @@ -2992,8 +3379,8 @@ int LuaScriptInterface::luaDoTargetCombatMana(lua_State* L) CombatDamage damage; damage.origin = getNumber(L, 6, ORIGIN_SPELL); - damage.type = COMBAT_MANADRAIN; - damage.value = normal_random(getNumber(L, 3), getNumber(L, 4)); + damage.primary.type = COMBAT_MANADRAIN; + damage.primary.value = normal_random(getNumber(L, 3), getNumber(L, 4)); Combat::doCombatMana(creature, target, damage, params); pushBoolean(L, true); @@ -3022,7 +3409,7 @@ int LuaScriptInterface::luaDoAreaCombatCondition(lua_State* L) if (area || areaId == 0) { CombatParams params; params.impactEffect = getNumber(L, 5); - params.conditionList.emplace_front(condition); + params.conditionList.emplace_front(condition->clone()); Combat::doCombatCondition(creature, getPosition(L, 2), area, params); pushBoolean(L, true); } else { @@ -3058,7 +3445,7 @@ int LuaScriptInterface::luaDoTargetCombatCondition(lua_State* L) CombatParams params; params.impactEffect = getNumber(L, 4); - params.conditionList.emplace_front(condition); + params.conditionList.emplace_front(condition->clone()); Combat::doCombatCondition(creature, target, params); pushBoolean(L, true); return 1; @@ -3137,76 +3524,6 @@ int LuaScriptInterface::luaDoChallengeCreature(lua_State* L) return 1; } -int LuaScriptInterface::luaSetCreatureOutfit(lua_State* L) -{ - //doSetCreatureOutfit(cid, outfit, time) - Creature* creature = getCreature(L, 1); - if (!creature) { - reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); - pushBoolean(L, false); - return 1; - } - - Outfit_t outfit = getOutfit(L, 2); - int32_t time = getNumber(L, 3); - pushBoolean(L, Spell::CreateIllusion(creature, outfit, time) == RETURNVALUE_NOERROR); - return 1; -} - -int LuaScriptInterface::luaSetMonsterOutfit(lua_State* L) -{ - //doSetMonsterOutfit(cid, name, time) - Creature* creature = getCreature(L, 1); - if (!creature) { - reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); - pushBoolean(L, false); - return 1; - } - - std::string name = getString(L, 2); - int32_t time = getNumber(L, 3); - pushBoolean(L, Spell::CreateIllusion(creature, name, time) == RETURNVALUE_NOERROR); - return 1; -} - -int LuaScriptInterface::luaSetItemOutfit(lua_State* L) -{ - //doSetItemOutfit(cid, item, time) - Creature* creature = getCreature(L, 1); - if (!creature) { - reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); - pushBoolean(L, false); - return 1; - } - - uint32_t item = getNumber(L, 2); - int32_t time = getNumber(L, 3); - pushBoolean(L, Spell::CreateIllusion(creature, item, time) == RETURNVALUE_NOERROR); - return 1; -} - -int LuaScriptInterface::luaDoMoveCreature(lua_State* L) -{ - //doMoveCreature(cid, direction) - Creature* creature = getCreature(L, 1); - if (!creature) { - reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); - pushBoolean(L, false); - return 1; - } - - Direction direction = getNumber(L, 2); - if (direction > DIRECTION_LAST) { - reportErrorFunc("No valid direction"); - pushBoolean(L, false); - return 1; - } - - ReturnValue ret = g_game.internalMoveCreature(creature, direction, FLAG_NOLIMIT); - lua_pushnumber(L, ret); - return 1; -} - int LuaScriptInterface::luaIsValidUID(lua_State* L) { //isValidUID(uid) @@ -3319,27 +3636,6 @@ int LuaScriptInterface::luaGetDepotId(lua_State* L) return 1; } -int LuaScriptInterface::luaIsInArray(lua_State* L) -{ - //isInArray(array, value) - if (!isTable(L, 1)) { - pushBoolean(L, false); - return 1; - } - - lua_pushnil(L); - while (lua_next(L, 1)) { - if (lua_equal(L, 2, -1) != 0) { - pushBoolean(L, true); - return 1; - } - lua_pop(L, 1); - } - - pushBoolean(L, false); - return 1; -} - int LuaScriptInterface::luaDoSetCreatureLight(lua_State* L) { //doSetCreatureLight(cid, lightLevel, lightColor, time) @@ -3430,7 +3726,7 @@ int LuaScriptInterface::luaAddEvent(lua_State* L) case LuaData_Container: case LuaData_Teleport: { lua_getglobal(globalState, "Item"); - lua_getfield(globalState, -1, "getMovementId"); + lua_getfield(globalState, -1, "getUniqueId"); break; } case LuaData_Player: @@ -3506,21 +3802,6 @@ int LuaScriptInterface::luaStopEvent(lua_State* L) return 1; } -int LuaScriptInterface::luaGetCreatureCondition(lua_State* L) -{ - Creature* creature = getCreature(L, 1); - if (!creature) { - reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); - pushBoolean(L, false); - return 1; - } - - ConditionType_t condition = getNumber(L, 2); - uint32_t subId = getNumber(L, 3, 0); - pushBoolean(L, creature->hasCondition(condition, subId)); - return 1; -} - int LuaScriptInterface::luaSaveServer(lua_State* L) { g_game.saveGameState(); @@ -3569,6 +3850,40 @@ int LuaScriptInterface::luaGetWaypointPositionByName(lua_State* L) return 1; } +int LuaScriptInterface::luaSendChannelMessage(lua_State* L) +{ + //sendChannelMessage(channelId, type, message) + uint32_t channelId = getNumber(L, 1); + ChatChannel* channel = g_chat->getChannelById(channelId); + if (!channel) { + pushBoolean(L, false); + return 1; + } + + SpeakClasses type = getNumber(L, 2); + std::string message = getString(L, 3); + channel->sendToAll(message, type); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaSendGuildChannelMessage(lua_State* L) +{ + //sendGuildChannelMessage(guildId, type, message) + uint32_t guildId = getNumber(L, 1); + ChatChannel* channel = g_chat->getGuildChannelById(guildId); + if (!channel) { + pushBoolean(L, false); + return 1; + } + + SpeakClasses type = getNumber(L, 2); + std::string message = getString(L, 3); + channel->sendToAll(message, type); + pushBoolean(L, true); + return 1; +} + std::string LuaScriptInterface::escapeString(const std::string& string) { std::string s = string; @@ -3668,7 +3983,7 @@ const luaL_Reg LuaScriptInterface::luaDatabaseTable[] = { int LuaScriptInterface::luaDatabaseExecute(lua_State* L) { - pushBoolean(L, Database::getInstance()->executeQuery(getString(L, -1))); + pushBoolean(L, Database::getInstance().executeQuery(getString(L, -1))); return 1; } @@ -3704,7 +4019,7 @@ int LuaScriptInterface::luaDatabaseAsyncExecute(lua_State* L) int LuaScriptInterface::luaDatabaseStoreQuery(lua_State* L) { - if (DBResult_ptr res = Database::getInstance()->storeQuery(getString(L, -1))) { + if (DBResult_ptr res = Database::getInstance().storeQuery(getString(L, -1))) { lua_pushnumber(L, ScriptEnvironment::addResult(res)); } else { pushBoolean(L, false); @@ -3748,20 +4063,20 @@ int LuaScriptInterface::luaDatabaseAsyncStoreQuery(lua_State* L) int LuaScriptInterface::luaDatabaseEscapeString(lua_State* L) { - pushString(L, Database::getInstance()->escapeString(getString(L, -1))); + pushString(L, Database::getInstance().escapeString(getString(L, -1))); return 1; } int LuaScriptInterface::luaDatabaseEscapeBlob(lua_State* L) { uint32_t length = getNumber(L, 2); - pushString(L, Database::getInstance()->escapeBlob(getString(L, 1).c_str(), length)); + pushString(L, Database::getInstance().escapeBlob(getString(L, 1).c_str(), length)); return 1; } int LuaScriptInterface::luaDatabaseLastInsertId(lua_State* L) { - lua_pushnumber(L, Database::getInstance()->getLastInsertId()); + lua_pushnumber(L, Database::getInstance().getLastInsertId()); return 1; } @@ -3941,7 +4256,15 @@ int LuaScriptInterface::luaGameLoadMap(lua_State* L) { // Game.loadMap(path) const std::string& path = getString(L, 1); - g_dispatcher.addTask(createTask(std::bind(&Game::loadMap, &g_game, path))); + g_dispatcher.addTask(createTask([path]() { + try { + g_game.loadMap(path); + } catch (const std::exception& e) { + // FIXME: Should only catch some exceptions + std::cout << "[Error - LuaScriptInterface::luaGameLoadMap] Failed to load map: " + << e.what() << std::endl; + } + })); return 0; } @@ -4207,6 +4530,37 @@ int LuaScriptInterface::luaGameCreateTile(lua_State* L) return 1; } +int LuaScriptInterface::luaGameCreateMonsterType(lua_State* L) +{ + // Game.createMonsterType(name) + if (getScriptEnv()->getScriptInterface() != &g_scripts->getScriptInterface()) { + reportErrorFunc("MonsterTypes can only be registered in the Scripts interface."); + lua_pushnil(L); + return 1; + } + + MonsterType* monsterType = g_monsters.getMonsterType(getString(L, 1)); + if (monsterType) { + monsterType->info.lootItems.clear(); + monsterType->info.attackSpells.clear(); + monsterType->info.defenseSpells.clear(); + pushUserdata(L, monsterType); + setMetatable(L, -1, "MonsterType"); + } else if (isString(L, 1)) { + monsterType = new MonsterType(); + std::string name = getString(L, 1); + g_monsters.addMonsterType(name, monsterType); + monsterType = g_monsters.getMonsterType(getString(L, 1)); + monsterType->name = name; + monsterType->nameDescription = "a " + name; + pushUserdata(L, monsterType); + setMetatable(L, -1, "MonsterType"); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaGameStartRaid(lua_State* L) { // Game.startRaid(raidName) @@ -4214,12 +4568,12 @@ int LuaScriptInterface::luaGameStartRaid(lua_State* L) Raid* raid = g_game.raids.getRaidByName(raidName); if (!raid || !raid->isLoaded()) { - lua_pushnil(L); + lua_pushnumber(L, RETURNVALUE_NOSUCHRAIDEXISTS); return 1; } if (g_game.raids.getRunning()) { - lua_pushnil(L); + lua_pushnumber(L, RETURNVALUE_ANOTHERRAIDISALREADYEXECUTING); return 1; } @@ -4229,19 +4583,24 @@ int LuaScriptInterface::luaGameStartRaid(lua_State* L) return 1; } +int LuaScriptInterface::luaGameGetClientVersion(lua_State* L) +{ + // Game.getClientVersion() + lua_createtable(L, 0, 3); + setField(L, "min", CLIENT_VERSION_MIN); + setField(L, "max", CLIENT_VERSION_MAX); + setField(L, "string", CLIENT_VERSION_STR); + return 1; +} + int LuaScriptInterface::luaGameReload(lua_State* L) { // Game.reload(reloadType) ReloadTypes_t reloadType = getNumber(L, 1); - if (!reloadType) { - lua_pushnil(L); - return 1; - } - if (reloadType == RELOAD_TYPE_GLOBAL) { pushBoolean(L, g_luaEnvironment.loadFile("data/global.lua") == 0); - } - else { + pushBoolean(L, g_scripts->loadScripts("scripts/lib", true, true)); + } else { pushBoolean(L, g_game.reload(reloadType)); } lua_gc(g_luaEnvironment.getLuaState(), LUA_GCCOLLECT, 0); @@ -4404,18 +4763,18 @@ int LuaScriptInterface::luaPositionIsSightClear(lua_State* L) int LuaScriptInterface::luaPositionSendMagicEffect(lua_State* L) { // position:sendMagicEffect(magicEffect[, player = nullptr]) - SpectatorVec list; + SpectatorVec spectators; if (lua_gettop(L) >= 3) { Player* player = getPlayer(L, 3); if (player) { - list.insert(player); + spectators.emplace_back(player); } } MagicEffectClasses magicEffect = getNumber(L, 2); const Position& position = getPosition(L, 1); - if (!list.empty()) { - Game::addMagicEffect(list, position, magicEffect); + if (!spectators.empty()) { + Game::addMagicEffect(spectators, position, magicEffect); } else { g_game.addMagicEffect(position, magicEffect); } @@ -4427,19 +4786,19 @@ int LuaScriptInterface::luaPositionSendMagicEffect(lua_State* L) int LuaScriptInterface::luaPositionSendDistanceEffect(lua_State* L) { // position:sendDistanceEffect(positionEx, distanceEffect[, player = nullptr]) - SpectatorVec list; + SpectatorVec spectators; if (lua_gettop(L) >= 4) { Player* player = getPlayer(L, 4); if (player) { - list.insert(player); + spectators.emplace_back(player); } } ShootType_t distanceEffect = getNumber(L, 3); const Position& positionEx = getPosition(L, 2); const Position& position = getPosition(L, 1); - if (!list.empty()) { - Game::addDistanceEffect(list, position, positionEx, distanceEffect); + if (!spectators.empty()) { + Game::addDistanceEffect(spectators, position, positionEx, distanceEffect); } else { g_game.addDistanceEffect(position, positionEx, distanceEffect); } @@ -4448,16 +4807,6 @@ int LuaScriptInterface::luaPositionSendDistanceEffect(lua_State* L) return 1; } -int LuaScriptInterface::luaPositionSendMonsterSay(lua_State * L) -{ - // position:sendMonsterSay(text) - const std::string& text = getString(L, 2); - const Position& position = getPosition(L, 1); - g_game.addMonsterSayText(position, text); - pushBoolean(L, true); - return 1; -} - // Tile int LuaScriptInterface::luaTileCreate(lua_State* L) { @@ -4685,6 +5034,9 @@ int LuaScriptInterface::luaTileGetItemByType(lua_State* L) case ITEM_TYPE_MAILBOX: found = tile->hasFlag(TILESTATE_MAILBOX); break; + case ITEM_TYPE_TRASHHOLDER: + found = tile->hasFlag(TILESTATE_TRASHHOLDER); + break; case ITEM_TYPE_BED: found = tile->hasFlag(TILESTATE_BED); break; @@ -5041,6 +5393,77 @@ int LuaScriptInterface::luaTileQueryAdd(lua_State* L) return 1; } +int LuaScriptInterface::luaTileAddItem(lua_State* L) +{ + // tile:addItem(itemId[, count/subType = 1[, flags = 0]]) + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + uint16_t itemId; + if (isNumber(L, 2)) { + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); + return 1; + } + } + + uint32_t subType = getNumber(L, 3, 1); + + Item* item = Item::CreateItem(itemId, std::min(subType, 100)); + if (!item) { + lua_pushnil(L); + return 1; + } + + uint32_t flags = getNumber(L, 4, 0); + + ReturnValue ret = g_game.internalAddItem(tile, item, INDEX_WHEREEVER, flags); + if (ret == RETURNVALUE_NOERROR) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + } else { + delete item; + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTileAddItemEx(lua_State* L) +{ + // tile:addItemEx(item[, flags = 0]) + Item* item = getUserdata(L, 2); + if (!item) { + lua_pushnil(L); + return 1; + } + + Tile* tile = getUserdata(L, 1); + if (!tile) { + lua_pushnil(L); + return 1; + } + + if (item->getParent() != VirtualCylinder::virtualCylinder) { + reportErrorFunc("Item already has a parent"); + lua_pushnil(L); + return 1; + } + + uint32_t flags = getNumber(L, 3, 0); + ReturnValue ret = g_game.internalAddItem(tile, item, INDEX_WHEREEVER, flags); + if (ret == RETURNVALUE_NOERROR) { + ScriptEnvironment::removeTempItem(item); + } + lua_pushnumber(L, ret); + return 1; +} + int LuaScriptInterface::luaTileGetHouse(lua_State* L) { // tile:getHouse() @@ -5340,6 +5763,243 @@ int LuaScriptInterface::luaNetworkMessageSendToPlayer(lua_State* L) return 1; } +// ModalWindow +int LuaScriptInterface::luaModalWindowCreate(lua_State* L) +{ + // ModalWindow(id, title, message) + const std::string& message = getString(L, 4); + const std::string& title = getString(L, 3); + uint32_t id = getNumber(L, 2); + + pushUserdata(L, new ModalWindow(id, title, message)); + setMetatable(L, -1, "ModalWindow"); + return 1; +} + +int LuaScriptInterface::luaModalWindowDelete(lua_State* L) +{ + ModalWindow** windowPtr = getRawUserdata(L, 1); + if (windowPtr && *windowPtr) { + delete *windowPtr; + *windowPtr = nullptr; + } + return 0; +} + +int LuaScriptInterface::luaModalWindowGetId(lua_State* L) +{ + // modalWindow:getId() + ModalWindow* window = getUserdata(L, 1); + if (window) { + lua_pushnumber(L, window->id); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaModalWindowGetTitle(lua_State* L) +{ + // modalWindow:getTitle() + ModalWindow* window = getUserdata(L, 1); + if (window) { + pushString(L, window->title); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaModalWindowGetMessage(lua_State* L) +{ + // modalWindow:getMessage() + ModalWindow* window = getUserdata(L, 1); + if (window) { + pushString(L, window->message); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaModalWindowSetTitle(lua_State* L) +{ + // modalWindow:setTitle(text) + const std::string& text = getString(L, 2); + ModalWindow* window = getUserdata(L, 1); + if (window) { + window->title = text; + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaModalWindowSetMessage(lua_State* L) +{ + // modalWindow:setMessage(text) + const std::string& text = getString(L, 2); + ModalWindow* window = getUserdata(L, 1); + if (window) { + window->message = text; + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaModalWindowGetButtonCount(lua_State* L) +{ + // modalWindow:getButtonCount() + ModalWindow* window = getUserdata(L, 1); + if (window) { + lua_pushnumber(L, window->buttons.size()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaModalWindowGetChoiceCount(lua_State* L) +{ + // modalWindow:getChoiceCount() + ModalWindow* window = getUserdata(L, 1); + if (window) { + lua_pushnumber(L, window->choices.size()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaModalWindowAddButton(lua_State* L) +{ + // modalWindow:addButton(id, text) + const std::string& text = getString(L, 3); + uint8_t id = getNumber(L, 2); + ModalWindow* window = getUserdata(L, 1); + if (window) { + window->buttons.emplace_back(text, id); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaModalWindowAddChoice(lua_State* L) +{ + // modalWindow:addChoice(id, text) + const std::string& text = getString(L, 3); + uint8_t id = getNumber(L, 2); + ModalWindow* window = getUserdata(L, 1); + if (window) { + window->choices.emplace_back(text, id); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaModalWindowGetDefaultEnterButton(lua_State* L) +{ + // modalWindow:getDefaultEnterButton() + ModalWindow* window = getUserdata(L, 1); + if (window) { + lua_pushnumber(L, window->defaultEnterButton); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaModalWindowSetDefaultEnterButton(lua_State* L) +{ + // modalWindow:setDefaultEnterButton(buttonId) + ModalWindow* window = getUserdata(L, 1); + if (window) { + window->defaultEnterButton = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaModalWindowGetDefaultEscapeButton(lua_State* L) +{ + // modalWindow:getDefaultEscapeButton() + ModalWindow* window = getUserdata(L, 1); + if (window) { + lua_pushnumber(L, window->defaultEscapeButton); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaModalWindowSetDefaultEscapeButton(lua_State* L) +{ + // modalWindow:setDefaultEscapeButton(buttonId) + ModalWindow* window = getUserdata(L, 1); + if (window) { + window->defaultEscapeButton = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaModalWindowHasPriority(lua_State* L) +{ + // modalWindow:hasPriority() + ModalWindow* window = getUserdata(L, 1); + if (window) { + pushBoolean(L, window->priority); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaModalWindowSetPriority(lua_State* L) +{ + // modalWindow:setPriority(priority) + ModalWindow* window = getUserdata(L, 1); + if (window) { + window->priority = getBoolean(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaModalWindowSendToPlayer(lua_State* L) +{ + // modalWindow:sendToPlayer(player) + Player* player = getPlayer(L, 2); + if (!player) { + lua_pushnil(L); + return 1; + } + + ModalWindow* window = getUserdata(L, 1); + if (window) { + if (!player->hasModalWindowOpen(window->id)) { + player->sendModalWindow(*window); + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + // Item int LuaScriptInterface::luaItemCreate(lua_State* L) { @@ -5460,6 +6120,8 @@ int LuaScriptInterface::luaItemSplit(lua_State* L) return 1; } + splitItem->setItemCount(count); + ScriptEnvironment* env = getScriptEnv(); uint32_t uid = env->addThing(item); @@ -5495,12 +6157,16 @@ int LuaScriptInterface::luaItemRemove(lua_State* L) return 1; } -int LuaScriptInterface::luaItemGetMovementId(lua_State* L) +int LuaScriptInterface::luaItemGetUniqueId(lua_State* L) { - // item:getMovementId() + // item:getUniqueId() Item* item = getUserdata(L, 1); if (item) { - lua_pushnumber(L, item->getMovementId()); + uint32_t uniqueId = item->getUniqueId(); + if (uniqueId == 0) { + uniqueId = getScriptEnv()->addThing(item); + } + lua_pushnumber(L, uniqueId); } else { lua_pushnil(L); } @@ -5533,32 +6199,6 @@ int LuaScriptInterface::luaItemSetActionId(lua_State* L) return 1; } -int LuaScriptInterface::luaItemSetMovementId(lua_State* L) -{ - // item:setMovementId(movementId) - uint16_t movementId = getNumber(L, 2); - Item* item = getUserdata(L, 1); - if (item) { - item->setMovementID(movementId); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaItemGetUniqueId(lua_State * L) -{ - // item:getUniqueId() - Item* item = getUserdata(L, 1); - if (item) { - lua_pushnumber(L, getScriptEnv()->addThing(item)); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaItemGetCount(lua_State* L) { // item:getCount() @@ -5755,6 +6395,12 @@ int LuaScriptInterface::luaItemSetAttribute(lua_State* L) } if (ItemAttributes::isIntAttrType(attribute)) { + if (attribute == ITEM_ATTRIBUTE_UNIQUEID) { + reportErrorFunc("Attempt to set protected key \"uid\""); + pushBoolean(L, false); + return 1; + } + item->setIntAttr(attribute, getNumber(L, 3)); pushBoolean(L, true); } else if (ItemAttributes::isStrAttrType(attribute)) { @@ -5784,14 +6430,103 @@ int LuaScriptInterface::luaItemRemoveAttribute(lua_State* L) attribute = ITEM_ATTRIBUTE_NONE; } - item->removeAttribute(attribute); + bool ret = attribute != ITEM_ATTRIBUTE_UNIQUEID; + if (ret) { + item->removeAttribute(attribute); + } else { + reportErrorFunc("Attempt to erase protected key \"uid\""); + } + pushBoolean(L, ret); + return 1; +} + +int LuaScriptInterface::luaItemGetCustomAttribute(lua_State* L) { + // item:getCustomAttribute(key) + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + const ItemAttributes::CustomAttribute* attr; + if (isNumber(L, 2)) { + attr = item->getCustomAttribute(getNumber(L, 2)); + } else if (isString(L, 2)) { + attr = item->getCustomAttribute(getString(L, 2)); + } else { + lua_pushnil(L); + return 1; + } + + if (attr) { + attr->pushToLua(L); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemSetCustomAttribute(lua_State* L) { + // item:setCustomAttribute(key, value) + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + std::string key; + if (isNumber(L, 2)) { + key = boost::lexical_cast(getNumber(L, 2)); + } else if (isString(L, 2)) { + key = getString(L, 2); + } else { + lua_pushnil(L); + return 1; + } + + ItemAttributes::CustomAttribute val; + if (isNumber(L, 3)) { + double tmp = getNumber(L, 3); + if (std::floor(tmp) < tmp) { + val.set(tmp); + } else { + val.set(tmp); + } + } else if (isString(L, 3)) { + val.set(getString(L, 3)); + } else if (isBoolean(L, 3)) { + val.set(getBoolean(L, 3)); + } else { + lua_pushnil(L); + return 1; + } + + item->setCustomAttribute(key, val); pushBoolean(L, true); return 1; } +int LuaScriptInterface::luaItemRemoveCustomAttribute(lua_State* L) { + // item:removeCustomAttribute(key) + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + if (isNumber(L, 2)) { + pushBoolean(L, item->removeCustomAttribute(getNumber(L, 2))); + } else if (isString(L, 2)) { + pushBoolean(L, item->removeCustomAttribute(getString(L, 2))); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaItemMoveTo(lua_State* L) { - // item:moveTo(position or cylinder) + // item:moveTo(position or cylinder[, flags]) Item** itemPtr = getRawUserdata(L, 1); if (!itemPtr) { lua_pushnil(L); @@ -5835,11 +6570,13 @@ int LuaScriptInterface::luaItemMoveTo(lua_State* L) return 1; } + uint32_t flags = getNumber(L, 3, FLAG_NOLIMIT | FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE | FLAG_IGNORENOTMOVEABLE); + if (item->getParent() == VirtualCylinder::virtualCylinder) { - pushBoolean(L, g_game.internalAddItem(toCylinder, item) == RETURNVALUE_NOERROR); + pushBoolean(L, g_game.internalAddItem(toCylinder, item, INDEX_WHEREEVER, flags) == RETURNVALUE_NOERROR); } else { Item* moveItem = nullptr; - ReturnValue ret = g_game.internalMoveItem(item->getParent(), toCylinder, INDEX_WHEREEVER, item, item->getItemCount(), &moveItem, FLAG_NOLIMIT | FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE | FLAG_IGNORENOTMOVEABLE); + ReturnValue ret = g_game.internalMoveItem(item->getParent(), toCylinder, INDEX_WHEREEVER, item, item->getItemCount(), &moveItem, flags); if (moveItem) { *itemPtr = moveItem; } @@ -5904,9 +6641,13 @@ int LuaScriptInterface::luaItemTransform(lua_State* L) int LuaScriptInterface::luaItemDecay(lua_State* L) { - // item:decay() + // item:decay(decayId) Item* item = getUserdata(L, 1); if (item) { + if (isNumber(L, 2)) { + item->setDecayTo(getNumber(L, 2)); + } + g_game.startDecay(item); pushBoolean(L, true); } else { @@ -5941,6 +6682,18 @@ int LuaScriptInterface::luaItemHasProperty(lua_State* L) return 1; } +int LuaScriptInterface::luaItemIsLoadedFromMap(lua_State* L) +{ + // item:isLoadedFromMap() + Item* item = getUserdata(L, 1); + if (item) { + pushBoolean(L, item->isLoadedFromMap()); + } else { + lua_pushnil(L); + } + return 1; +} + // Container int LuaScriptInterface::luaContainerCreate(lua_State* L) { @@ -6068,9 +6821,13 @@ int LuaScriptInterface::luaContainerAddItem(lua_State* L) } } - uint32_t subType = getNumber(L, 3, 1); + uint32_t count = getNumber(L, 3, 1); + const ItemType& it = Item::items[itemId]; + if (it.stackable) { + count = std::min(count, 100); + } - Item* item = Item::CreateItem(itemId, std::min(subType, 100)); + Item* item = Item::CreateItem(itemId, count); if (!item) { lua_pushnil(L); return 1; @@ -6121,6 +6878,18 @@ int LuaScriptInterface::luaContainerAddItemEx(lua_State* L) return 1; } +int LuaScriptInterface::luaContainerGetCorpseOwner(lua_State* L) +{ + // container:getCorpseOwner() + Container* container = getUserdata(L, 1); + if (container) { + lua_pushnumber(L, container->getCorpseOwner()); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaContainerGetItemCountById(lua_State* L) { // container:getItemCountById(itemId[, subType = -1]) @@ -6146,6 +6915,18 @@ int LuaScriptInterface::luaContainerGetItemCountById(lua_State* L) return 1; } +int LuaScriptInterface::luaContainerGetContentDescription(lua_State* L) +{ + // container:getContentDescription() + Container* container = getUserdata(L, 1); + if (container) { + pushString(L, container->getContentDescription()); + } else { + lua_pushnil(L); + } + return 1; +} + // Teleport int LuaScriptInterface::luaTeleportCreate(lua_State* L) { @@ -6294,6 +7075,18 @@ int LuaScriptInterface::luaCreatureIsInGhostMode(lua_State* L) return 1; } +int LuaScriptInterface::luaCreatureIsHealthHidden(lua_State* L) +{ + // creature:isHealthHidden() + const Creature* creature = getUserdata(L, 1); + if (creature) { + pushBoolean(L, creature->isHealthHidden()); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaCreatureCanSee(lua_State* L) { // creature:canSee(position) @@ -6456,19 +7249,8 @@ int LuaScriptInterface::luaCreatureSetMaster(lua_State* L) return 1; } - Creature* master = getCreature(L, 2); - if (master) { - pushBoolean(L, creature->convinceCreature(master)); - } else { - master = creature->getMaster(); - if (master) { - master->removeSummon(creature); - creature->incrementReferenceCounter(); - creature->setDropLoot(true); - } - pushBoolean(L, true); - } - + pushBoolean(L, creature->setMaster(getCreature(L, 2))); + g_game.updateCreatureType(creature); return 1; } @@ -6481,10 +7263,9 @@ int LuaScriptInterface::luaCreatureGetLight(lua_State* L) return 1; } - LightInfo light; - creature->getCreatureLight(light); - lua_pushnumber(L, light.level); - lua_pushnumber(L, light.color); + LightInfo lightInfo = creature->getCreatureLight(); + lua_pushnumber(L, lightInfo.level); + lua_pushnumber(L, lightInfo.color); return 2; } @@ -6559,6 +7340,19 @@ int LuaScriptInterface::luaCreatureSetDropLoot(lua_State* L) return 1; } +int LuaScriptInterface::luaCreatureSetSkillLoss(lua_State* L) +{ + // creature:setSkillLoss(skillLoss) + Creature* creature = getUserdata(L, 1); + if (creature) { + creature->setSkillLoss(getBoolean(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaCreatureGetPosition(lua_State* L) { // creature:getPosition() @@ -6626,6 +7420,26 @@ int LuaScriptInterface::luaCreatureGetHealth(lua_State* L) return 1; } +int LuaScriptInterface::luaCreatureSetHealth(lua_State* L) +{ + // creature:setHealth(health) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + creature->health = std::min(getNumber(L, 2), creature->healthMax); + g_game.addCreatureHealth(creature); + + Player* player = creature->getPlayer(); + if (player) { + player->sendStats(); + } + pushBoolean(L, true); + return 1; +} + int LuaScriptInterface::luaCreatureAddHealth(lua_State* L) { // creature:addHealth(healthChange) @@ -6636,11 +7450,11 @@ int LuaScriptInterface::luaCreatureAddHealth(lua_State* L) } CombatDamage damage; - damage.value = getNumber(L, 2); - if (damage.value >= 0) { - damage.type = COMBAT_HEALING; + damage.primary.value = getNumber(L, 2); + if (damage.primary.value >= 0) { + damage.primary.type = COMBAT_HEALING; } else { - damage.type = COMBAT_UNDEFINEDDAMAGE; + damage.primary.type = COMBAT_UNDEFINEDDAMAGE; } pushBoolean(L, g_game.combatChangeHealth(nullptr, creature, damage)); return 1; @@ -6687,8 +7501,7 @@ int LuaScriptInterface::luaCreatureSetHiddenHealth(lua_State* L) creature->setHiddenHealth(getBoolean(L, 2)); g_game.addCreatureHealth(creature); pushBoolean(L, true); - } - else { + } else { lua_pushnil(L); } return 1; @@ -6796,7 +7609,7 @@ int LuaScriptInterface::luaCreatureRemoveCondition(lua_State* L) uint32_t subId = getNumber(L, 4, 0); Condition* condition = creature->getCondition(conditionType, conditionId, subId); if (condition) { - bool force = getBoolean(L, 5, true); + bool force = getBoolean(L, 5, false); creature->removeCondition(condition, force); pushBoolean(L, true); } else { @@ -6805,6 +7618,40 @@ int LuaScriptInterface::luaCreatureRemoveCondition(lua_State* L) return 1; } +int LuaScriptInterface::luaCreatureHasCondition(lua_State* L) +{ + // creature:hasCondition(conditionType[, subId = 0]) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + ConditionType_t conditionType = getNumber(L, 2); + uint32_t subId = getNumber(L, 3, 0); + pushBoolean(L, creature->hasCondition(conditionType, subId)); + return 1; +} + +int LuaScriptInterface::luaCreatureIsImmune(lua_State* L) +{ + // creature:isImmune(condition or conditionType) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + if (isNumber(L, 2)) { + pushBoolean(L, creature->isImmune(getNumber(L, 2))); + } else if (Condition* condition = getUserdata(L, 2)) { + pushBoolean(L, creature->isImmune(condition->getType())); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaCreatureRemove(lua_State* L) { // creature:remove() @@ -6850,7 +7697,7 @@ int LuaScriptInterface::luaCreatureTeleportTo(lua_State* L) return 1; } - if (!pushMovement) { + if (pushMovement) { if (oldPosition.x == position.x) { if (oldPosition.y < position.y) { g_game.internalCreatureTurn(creature, DIRECTION_SOUTH); @@ -6869,7 +7716,7 @@ int LuaScriptInterface::luaCreatureTeleportTo(lua_State* L) int LuaScriptInterface::luaCreatureSay(lua_State* L) { - // creature:say(text, type[, ghost = false[, target = nullptr[, position]]]) + // creature:say(text[, type = TALKTYPE_MONSTER_SAY[, ghost = false[, target = nullptr[, position]]]]) int parameters = lua_gettop(L); Position position; @@ -6889,7 +7736,7 @@ int LuaScriptInterface::luaCreatureSay(lua_State* L) bool ghost = getBoolean(L, 4, false); - SpeakClasses type = getNumber(L, 3); + SpeakClasses type = getNumber(L, 3, TALKTYPE_MONSTER_SAY); const std::string& text = getString(L, 2); Creature* creature = getUserdata(L, 1); if (!creature) { @@ -6897,15 +7744,15 @@ int LuaScriptInterface::luaCreatureSay(lua_State* L) return 1; } - SpectatorVec list; + SpectatorVec spectators; if (target) { - list.insert(target); + spectators.emplace_back(target); } if (position.x != 0) { - pushBoolean(L, g_game.internalCreatureSay(creature, type, text, ghost, &list, &position)); + pushBoolean(L, g_game.internalCreatureSay(creature, type, text, ghost, &spectators, &position)); } else { - pushBoolean(L, g_game.internalCreatureSay(creature, type, text, ghost, &list)); + pushBoolean(L, g_game.internalCreatureSay(creature, type, text, ghost, &spectators)); } return 1; } @@ -6995,13 +7842,58 @@ int LuaScriptInterface::luaCreatureGetPathTo(lua_State* L) return 1; } +int LuaScriptInterface::luaCreatureMove(lua_State* L) +{ + // creature:move(direction) + // creature:move(tile[, flags = 0]) + Creature* creature = getUserdata(L, 1); + if (!creature) { + lua_pushnil(L); + return 1; + } + + if (isNumber(L, 2)) { + Direction direction = getNumber(L, 2); + if (direction > DIRECTION_LAST) { + lua_pushnil(L); + return 1; + } + lua_pushnumber(L, g_game.internalMoveCreature(creature, direction, FLAG_NOLIMIT)); + } else { + Tile* tile = getUserdata(L, 2); + if (!tile) { + lua_pushnil(L); + return 1; + } + lua_pushnumber(L, g_game.internalMoveCreature(*creature, *tile, getNumber(L, 3))); + } + return 1; +} + +int LuaScriptInterface::luaCreatureGetZone(lua_State* L) +{ + // creature:getZone() + Creature* creature = getUserdata(L, 1); + if (creature) { + lua_pushnumber(L, creature->getZone()); + } else { + lua_pushnil(L); + } + return 1; +} + // Player int LuaScriptInterface::luaPlayerCreate(lua_State* L) { - // Player(id or name or userdata) + // Player(id or guid or name or userdata) Player* player; if (isNumber(L, 2)) { - player = g_game.getPlayerByID(getNumber(L, 2)); + uint32_t id = getNumber(L, 2); + if (id >= 0x10000000 && id <= Player::playerAutoID) { + player = g_game.getPlayerByID(id); + } else { + player = g_game.getPlayerByGUID(id); + } } else if (isString(L, 2)) { ReturnValue ret = g_game.getPlayerByNameWildcard(getString(L, 2), player); if (ret != RETURNVALUE_NOERROR) { @@ -7095,19 +7987,6 @@ int LuaScriptInterface::luaPlayerGetLastLogout(lua_State* L) return 1; } -int LuaScriptInterface::luaPlayerHasFlag(lua_State * L) -{ - // player:hasFlag(flag) - Player* player = getUserdata(L, 1); - if (player) { - PlayerFlags flag = getNumber(L, 2); - pushBoolean(L, player->hasFlag(flag)); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaPlayerGetAccountType(lua_State* L) { // player:getAccountType() @@ -7183,56 +8062,54 @@ int LuaScriptInterface::luaPlayerGetDepotChest(lua_State* L) uint32_t depotId = getNumber(L, 2); bool autoCreate = getBoolean(L, 3, false); - DepotLocker* depotLocker = player->getDepotLocker(depotId, autoCreate); - if (depotLocker) { - if (!depotLocker->getParent() && player->getTile()) { - depotLocker->setParent(player->getTile()); - } - - pushUserdata(L, depotLocker); - setItemMetatable(L, -1, depotLocker); + DepotChest* depotChest = player->getDepotChest(depotId, autoCreate); + if (depotChest) { + player->setLastDepotId(depotId); // FIXME: workaround for #2251 + pushUserdata(L, depotChest); + setItemMetatable(L, -1, depotChest); } else { pushBoolean(L, false); } return 1; } -int LuaScriptInterface::luaPlayerGetMurderTimestamps(lua_State * L) +int LuaScriptInterface::luaPlayerGetInbox(lua_State* L) { - // player:getMurderTimestamps() + // player:getInbox() + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + Inbox* inbox = player->getInbox(); + if (inbox) { + pushUserdata(L, inbox); + setItemMetatable(L, -1, inbox); + } else { + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetSkullTime(lua_State* L) +{ + // player:getSkullTime() Player* player = getUserdata(L, 1); if (player) { - lua_createtable(L, player->murderTimeStamps.size(), 0); - - uint32_t i = 1; - for (time_t currentMurderTimestamp : player->murderTimeStamps) { - lua_pushnumber(L, static_cast(currentMurderTimestamp)); - lua_rawseti(L, -2, ++i); - } + lua_pushnumber(L, player->getSkullTicks()); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaPlayerGetPlayerKillerEnd(lua_State* L) +int LuaScriptInterface::luaPlayerSetSkullTime(lua_State* L) { - // player:getPlayerKillerEnd() + // player:setSkullTime(skullTime) Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getPlayerKillerEnd()); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaPlayerSetPlayerKillerEnd(lua_State* L) -{ - // player:setPlayerKillerEnd(skullTime) - Player* player = getUserdata(L, 1); - if (player) { - player->setPlayerKillerEnd(getNumber(L, 2)); + player->setSkullTicks(getNumber(L, 2)); pushBoolean(L, true); } else { lua_pushnil(L); @@ -7273,8 +8150,7 @@ int LuaScriptInterface::luaPlayerAddExperience(lua_State* L) bool sendText = getBoolean(L, 3, false); player->addExperience(nullptr, experience, sendText); pushBoolean(L, true); - } - else { + } else { lua_pushnil(L); } return 1; @@ -7282,11 +8158,12 @@ int LuaScriptInterface::luaPlayerAddExperience(lua_State* L) int LuaScriptInterface::luaPlayerRemoveExperience(lua_State* L) { - // player:removeExperience(experience) + // player:removeExperience(experience[, sendText = false]) Player* player = getUserdata(L, 1); if (player) { int64_t experience = getNumber(L, 2); - player->removeExperience(experience); + bool sendText = getBoolean(L, 3, false); + player->removeExperience(experience, sendText); pushBoolean(L, true); } else { lua_pushnil(L); @@ -7336,8 +8213,7 @@ int LuaScriptInterface::luaPlayerGetMana(lua_State* L) const Player* player = getUserdata(L, 1); if (player) { lua_pushnumber(L, player->getMana()); - } - else { + } else { lua_pushnil(L); } return 1; @@ -7356,10 +8232,9 @@ int LuaScriptInterface::luaPlayerAddMana(lua_State* L) bool animationOnLoss = getBoolean(L, 3, false); if (!animationOnLoss && manaChange < 0) { player->changeMana(manaChange); - } - else { + } else { CombatDamage damage; - damage.value = manaChange; + damage.primary.value = manaChange; damage.origin = ORIGIN_NONE; g_game.combatChangeMana(nullptr, player, damage); } @@ -7373,8 +8248,7 @@ int LuaScriptInterface::luaPlayerGetMaxMana(lua_State* L) const Player* player = getUserdata(L, 1); if (player) { lua_pushnumber(L, player->getMaxMana()); - } - else { + } else { lua_pushnil(L); } return 1; @@ -7511,6 +8385,123 @@ int LuaScriptInterface::luaPlayerAddSkillTries(lua_State* L) return 1; } +int LuaScriptInterface::luaPlayerGetSpecialSkill(lua_State* L) +{ + // player:getSpecialSkill(specialSkillType) + SpecialSkills_t specialSkillType = getNumber(L, 2); + Player* player = getUserdata(L, 1); + if (player && specialSkillType <= SPECIALSKILL_LAST) { + lua_pushnumber(L, player->getSpecialSkill(specialSkillType)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddSpecialSkill(lua_State* L) +{ + // player:addSpecialSkill(specialSkillType, value) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + SpecialSkills_t specialSkillType = getNumber(L, 2); + if (specialSkillType > SPECIALSKILL_LAST) { + lua_pushnil(L); + return 1; + } + + player->setVarSpecialSkill(specialSkillType, getNumber(L, 3)); + player->sendSkills(); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerAddOfflineTrainingTime(lua_State* L) +{ + // player:addOfflineTrainingTime(time) + Player* player = getUserdata(L, 1); + if (player) { + int32_t time = getNumber(L, 2); + player->addOfflineTrainingTime(time); + player->sendStats(); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + + +int LuaScriptInterface::luaPlayerGetOfflineTrainingTime(lua_State* L) +{ + // player:getOfflineTrainingTime() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getOfflineTrainingTime()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerRemoveOfflineTrainingTime(lua_State* L) +{ + // player:removeOfflineTrainingTime(time) + Player* player = getUserdata(L, 1); + if (player) { + int32_t time = getNumber(L, 2); + player->removeOfflineTrainingTime(time); + player->sendStats(); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddOfflineTrainingTries(lua_State* L) +{ + // player:addOfflineTrainingTries(skillType, tries) + Player* player = getUserdata(L, 1); + if (player) { + skills_t skillType = getNumber(L, 2); + uint64_t tries = getNumber(L, 3); + pushBoolean(L, player->addOfflineTrainingTries(skillType, tries)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetOfflineTrainingSkill(lua_State* L) +{ + // player:getOfflineTrainingSkill() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->getOfflineTrainingSkill()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetOfflineTrainingSkill(lua_State* L) +{ + // player:setOfflineTrainingSkill(skillId) + Player* player = getUserdata(L, 1); + if (player) { + uint32_t skillId = getNumber(L, 2); + player->setOfflineTrainingSkill(skillId); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaPlayerGetItemCount(lua_State* L) { // player:getItemCount(itemId[, subType = -1]) @@ -7800,8 +8791,7 @@ int LuaScriptInterface::luaPlayerGetStamina(lua_State* L) Player* player = getUserdata(L, 1); if (player) { lua_pushnumber(L, player->getStaminaMinutes()); - } - else { + } else { lua_pushnil(L); } return 1; @@ -7813,11 +8803,10 @@ int LuaScriptInterface::luaPlayerSetStamina(lua_State* L) uint16_t stamina = getNumber(L, 2); Player* player = getUserdata(L, 1); if (player) { - player->staminaMinutes = std::min(3360, stamina); + player->staminaMinutes = std::min(2520, stamina); player->sendStats(); pushBoolean(L, true); - } - else { + } else { lua_pushnil(L); } return 1; @@ -7908,7 +8897,7 @@ int LuaScriptInterface::luaPlayerGetStorageValue(lua_State* L) if (player->getStorageValue(key, value)) { lua_pushnumber(L, value); } else { - lua_pushnumber(L, 0); + lua_pushnumber(L, -1); } return 1; } @@ -7919,6 +8908,14 @@ int LuaScriptInterface::luaPlayerSetStorageValue(lua_State* L) int32_t value = getNumber(L, 3); uint32_t key = getNumber(L, 2); Player* player = getUserdata(L, 1); + if (IS_IN_KEYRANGE(key, RESERVED_RANGE)) { + std::ostringstream ss; + ss << "Accessing reserved range: " << key; + reportErrorFunc(ss.str()); + pushBoolean(L, false); + return 1; + } + if (player) { player->addStorageValue(key, value); pushBoolean(L, true); @@ -8123,7 +9120,7 @@ int LuaScriptInterface::luaPlayerRemoveMoney(lua_State* L) int LuaScriptInterface::luaPlayerShowTextDialog(lua_State* L) { - // player:showTextDialog(itemId[, text[, canWrite[, length]]]) + // player:showTextDialog(id or name or userdata[, text[, canWrite[, length]]]) Player* player = getUserdata(L, 1); if (!player) { lua_pushnil(L); @@ -8139,18 +9136,22 @@ int LuaScriptInterface::luaPlayerShowTextDialog(lua_State* L) text = getString(L, 3); } - uint16_t itemId; + Item* item; if (isNumber(L, 2)) { - itemId = getNumber(L, 2); - } else { - itemId = Item::items.getItemIdByName(getString(L, 2)); - if (itemId == 0) { - lua_pushnil(L); + item = Item::CreateItem(getNumber(L, 2)); + } else if (isString(L, 2)) { + item = Item::CreateItem(Item::items.getItemIdByName(getString(L, 2))); + } else if (isUserdata(L, 2)) { + if (getUserdataType(L, 2) != LuaData_Item) { + pushBoolean(L, false); return 1; } + + item = getUserdata(L, 2); + } else { + item = nullptr; } - Item* item = Item::CreateItem(itemId); if (!item) { reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); pushBoolean(L, false); @@ -8175,11 +9176,55 @@ int LuaScriptInterface::luaPlayerShowTextDialog(lua_State* L) int LuaScriptInterface::luaPlayerSendTextMessage(lua_State* L) { - // player:sendTextMessage(type, text) + // player:sendTextMessage(type, text[, position, primaryValue = 0, primaryColor = TEXTCOLOR_NONE[, secondaryValue = 0, secondaryColor = TEXTCOLOR_NONE]]) + // player:sendTextMessage(type, text, channelId) + + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + int parameters = lua_gettop(L); + TextMessage message(getNumber(L, 2), getString(L, 3)); + if (parameters == 4) { + uint16_t channelId = getNumber(L, 4); + ChatChannel* channel = g_chat->getChannel(*player, channelId); + if (!channel || !channel->hasUser(*player)) { + pushBoolean(L, false); + return 1; + } + message.channelId = channelId; + } else { + if (parameters >= 6) { + message.position = getPosition(L, 4); + message.primary.value = getNumber(L, 5); + message.primary.color = getNumber(L, 6); + } + + if (parameters >= 8) { + message.secondary.value = getNumber(L, 7); + message.secondary.color = getNumber(L, 8); + } + } + + player->sendTextMessage(message); + pushBoolean(L, true); + + return 1; +} + +int LuaScriptInterface::luaPlayerSendChannelMessage(lua_State* L) +{ + // player:sendChannelMessage(author, text, type, channelId) + uint16_t channelId = getNumber(L, 5); + SpeakClasses type = getNumber(L, 4); + const std::string& text = getString(L, 3); + const std::string& author = getString(L, 2); Player* player = getUserdata(L, 1); if (player) { - player->sendTextMessage(message); + player->sendChannelMessage(author, text, type, channelId); pushBoolean(L, true); } else { lua_pushnil(L); @@ -8198,7 +9243,7 @@ int LuaScriptInterface::luaPlayerSendPrivateMessage(lua_State* L) const Player* speaker = getUserdata(L, 2); const std::string& text = getString(L, 3); - SpeakClasses type = getNumber(L, 4, TALKTYPE_PRIVATE); + SpeakClasses type = getNumber(L, 4, TALKTYPE_PRIVATE_FROM); player->sendPrivateMessage(speaker, type, text); pushBoolean(L, true); return 1; @@ -8288,8 +9333,7 @@ int LuaScriptInterface::luaPlayerAddOutfit(lua_State* L) if (player) { player->addOutfit(getNumber(L, 2), 0); pushBoolean(L, true); - } - else { + } else { lua_pushnil(L); } return 1; @@ -8304,8 +9348,7 @@ int LuaScriptInterface::luaPlayerAddOutfitAddon(lua_State* L) uint8_t addon = getNumber(L, 3); player->addOutfit(lookType, addon); pushBoolean(L, true); - } - else { + } else { lua_pushnil(L); } return 1; @@ -8318,8 +9361,7 @@ int LuaScriptInterface::luaPlayerRemoveOutfit(lua_State* L) if (player) { uint16_t lookType = getNumber(L, 2); pushBoolean(L, player->removeOutfit(lookType)); - } - else { + } else { lua_pushnil(L); } return 1; @@ -8333,8 +9375,7 @@ int LuaScriptInterface::luaPlayerRemoveOutfitAddon(lua_State* L) uint16_t lookType = getNumber(L, 2); uint8_t addon = getNumber(L, 3); pushBoolean(L, player->removeOutfitAddon(lookType, addon)); - } - else { + } else { lua_pushnil(L); } return 1; @@ -8348,8 +9389,7 @@ int LuaScriptInterface::luaPlayerHasOutfit(lua_State* L) uint16_t lookType = getNumber(L, 2); uint8_t addon = getNumber(L, 3, 0); pushBoolean(L, player->canWear(lookType, addon)); - } - else { + } else { lua_pushnil(L); } return 1; @@ -8368,6 +9408,75 @@ int LuaScriptInterface::luaPlayerSendOutfitWindow(lua_State* L) return 1; } +int LuaScriptInterface::luaPlayerAddMount(lua_State* L) { + // player:addMount(mountId or mountName) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + uint8_t mountId; + if (isNumber(L, 2)) { + mountId = getNumber(L, 2); + } else { + Mount* mount = g_game.mounts.getMountByName(getString(L, 2)); + if (!mount) { + lua_pushnil(L); + return 1; + } + mountId = mount->id; + } + pushBoolean(L, player->tameMount(mountId)); + return 1; +} + +int LuaScriptInterface::luaPlayerRemoveMount(lua_State* L) { + // player:removeMount(mountId or mountName) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + uint8_t mountId; + if (isNumber(L, 2)) { + mountId = getNumber(L, 2); + } else { + Mount* mount = g_game.mounts.getMountByName(getString(L, 2)); + if (!mount) { + lua_pushnil(L); + return 1; + } + mountId = mount->id; + } + pushBoolean(L, player->untameMount(mountId)); + return 1; +} + +int LuaScriptInterface::luaPlayerHasMount(lua_State* L) { + // player:hasMount(mountId or mountName) + const Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + Mount* mount = nullptr; + if (isNumber(L, 2)) { + mount = g_game.mounts.getMountByID(getNumber(L, 2)); + } else { + mount = g_game.mounts.getMountByName(getString(L, 2)); + } + + if (mount) { + pushBoolean(L, player->hasMount(mount)); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaPlayerGetPremiumDays(lua_State* L) { // player:getPremiumDays() @@ -8551,6 +9660,36 @@ int LuaScriptInterface::luaPlayerHasLearnedSpell(lua_State* L) return 1; } +int LuaScriptInterface::luaPlayerSendTutorial(lua_State* L) +{ + // player:sendTutorial(tutorialId) + Player* player = getUserdata(L, 1); + if (player) { + uint8_t tutorialId = getNumber(L, 2); + player->sendTutorial(tutorialId); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerAddMapMark(lua_State* L) +{ + // player:addMapMark(position, type, description) + Player* player = getUserdata(L, 1); + if (player) { + const Position& position = getPosition(L, 2); + uint8_t type = getNumber(L, 3); + const std::string& description = getString(L, 4); + player->sendAddMarker(position, type, description); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaPlayerSave(lua_State* L) { // player:save() @@ -8564,6 +9703,20 @@ int LuaScriptInterface::luaPlayerSave(lua_State* L) return 1; } +int LuaScriptInterface::luaPlayerPopupFYI(lua_State* L) +{ + // player:popupFYI(message) + Player* player = getUserdata(L, 1); + if (player) { + const std::string& message = getString(L, 2); + player->sendFYIBox(message); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaPlayerIsPzLocked(lua_State* L) { // player:isPzLocked() @@ -8609,9 +9762,51 @@ int LuaScriptInterface::luaPlayerGetHouse(lua_State* L) return 1; } +int LuaScriptInterface::luaPlayerSendHouseWindow(lua_State* L) +{ + // player:sendHouseWindow(house, listId) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + House* house = getUserdata(L, 2); + if (!house) { + lua_pushnil(L); + return 1; + } + + uint32_t listId = getNumber(L, 3); + player->sendHouseWindow(house, listId); + pushBoolean(L, true); + return 1; +} + +int LuaScriptInterface::luaPlayerSetEditHouse(lua_State* L) +{ + // player:setEditHouse(house, listId) + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + House* house = getUserdata(L, 2); + if (!house) { + lua_pushnil(L); + return 1; + } + + uint32_t listId = getNumber(L, 3); + player->setEditHouse(house, listId); + pushBoolean(L, true); + return 1; +} + int LuaScriptInterface::luaPlayerSetGhostMode(lua_State* L) { - // player:setGhostMode(enabled) + // player:setGhostMode(enabled[, showEffect=true]) Player* player = getUserdata(L, 1); if (!player) { lua_pushnil(L); @@ -8624,20 +9819,22 @@ int LuaScriptInterface::luaPlayerSetGhostMode(lua_State* L) return 1; } + bool showEffect = getBoolean(L, 3, true); + player->switchGhostMode(); Tile* tile = player->getTile(); const Position& position = player->getPosition(); - SpectatorVec list; - g_game.map.getSpectators(list, position, true, true); - for (Creature* spectator : list) { + SpectatorVec spectators; + g_game.map.getSpectators(spectators, position, true, true); + for (Creature* spectator : spectators) { Player* tmpPlayer = spectator->getPlayer(); if (tmpPlayer != player && !tmpPlayer->isAccessPlayer()) { if (enabled) { tmpPlayer->sendRemoveTileThing(position, tile->getStackposOfCreature(tmpPlayer, player)); } else { - tmpPlayer->sendCreatureAppear(player, position, true); + tmpPlayer->sendCreatureAppear(player, position, showEffect); } } else { tmpPlayer->sendCreatureChangeVisible(player, !enabled); @@ -8712,16 +9909,75 @@ int LuaScriptInterface::luaPlayerGetContainerIndex(lua_State* L) return 1; } -int LuaScriptInterface::luaPlayerGetTotalDamage(lua_State * L) +int LuaScriptInterface::luaPlayerGetInstantSpells(lua_State* L) { - // player:getTotalDamage(attackSkill, attackValue, fightMode) + // player:getInstantSpells() + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + std::vector spells; + for (auto& spell : g_spells->getInstantSpells()) { + if (spell.second.canCast(player)) { + spells.push_back(&spell.second); + } + } + + lua_createtable(L, spells.size(), 0); + + int index = 0; + for (auto spell : spells) { + pushInstantSpell(L, *spell); + lua_rawseti(L, -2, ++index); + } + return 1; +} + +int LuaScriptInterface::luaPlayerCanCast(lua_State* L) +{ + // player:canCast(spell) + Player* player = getUserdata(L, 1); + InstantSpell* spell = getUserdata(L, 2); + if (player && spell) { + pushBoolean(L, spell->canCast(player)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerHasChaseMode(lua_State* L) +{ + // player:hasChaseMode() Player* player = getUserdata(L, 1); if (player) { - uint32_t attackSkill = getNumber(L, 2); - uint32_t attackValue = getNumber(L, 3); - fightMode_t fightMode = static_cast(getNumber(L, 4, FIGHTMODE_BALANCED)); - - lua_pushnumber(L, Combat::getTotalDamage(attackSkill, attackValue, fightMode)); + pushBoolean(L, player->chaseMode); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerHasSecureMode(lua_State* L) +{ + // player:hasSecureMode() + Player* player = getUserdata(L, 1); + if (player) { + pushBoolean(L, player->secureMode); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerGetFightMode(lua_State* L) +{ + // player:getFightMode() + Player* player = getUserdata(L, 1); + if (player) { + lua_pushnumber(L, player->fightMode); } else { lua_pushnil(L); } @@ -9002,10 +10258,10 @@ int LuaScriptInterface::luaMonsterSelectTarget(lua_State* L) int LuaScriptInterface::luaMonsterSearchTarget(lua_State* L) { - // monster:searchTarget([searchType = TARGETSEARCH_ANY]) + // monster:searchTarget([searchType = TARGETSEARCH_DEFAULT]) Monster* monster = getUserdata(L, 1); if (monster) { - TargetSearchType_t searchType = getNumber(L, 2, TARGETSEARCH_ANY); + TargetSearchType_t searchType = getNumber(L, 2, TARGETSEARCH_DEFAULT); pushBoolean(L, monster->searchTarget(searchType)); } else { lua_pushnil(L); @@ -9068,6 +10324,28 @@ int LuaScriptInterface::luaNpcSetMasterPos(lua_State* L) return 1; } +int LuaScriptInterface::luaNpcGetSpeechBubble(lua_State* L) +{ + // npc:getSpeechBubble() + Npc* npc = getUserdata(L, 1); + if (npc) { + lua_pushnumber(L, npc->getSpeechBubble()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaNpcSetSpeechBubble(lua_State* L) +{ + // npc:setSpeechBubble(speechBubble) + Npc* npc = getUserdata(L, 1); + if (npc) { + npc->setSpeechBubble(getNumber(L, 2)); + } + return 0; +} + // Guild int LuaScriptInterface::luaGuildCreate(lua_State* L) { @@ -9189,6 +10467,32 @@ int LuaScriptInterface::luaGuildGetRankByLevel(lua_State* L) return 1; } +int LuaScriptInterface::luaGuildGetMotd(lua_State* L) +{ + // guild:getMotd() + Guild* guild = getUserdata(L, 1); + if (guild) { + pushString(L, guild->getMotd()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGuildSetMotd(lua_State* L) +{ + // guild:setMotd(motd) + const std::string& motd = getString(L, 2); + Guild* guild = getUserdata(L, 1); + if (guild) { + guild->setMotd(motd); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + // Group int LuaScriptInterface::luaGroupCreate(lua_State* L) { @@ -9277,6 +10581,19 @@ int LuaScriptInterface::luaGroupGetMaxVipEntries(lua_State* L) return 1; } +int LuaScriptInterface::luaGroupHasFlag(lua_State* L) +{ + // group:hasFlag(flag) + Group* group = getUserdata(L, 1); + if (group) { + PlayerFlags flag = getNumber(L, 2); + pushBoolean(L, (group->flags & flag) != 0); + } else { + lua_pushnil(L); + } + return 1; +} + // Vocation int LuaScriptInterface::luaVocationCreate(lua_State* L) { @@ -9310,6 +10627,18 @@ int LuaScriptInterface::luaVocationGetId(lua_State* L) return 1; } +int LuaScriptInterface::luaVocationGetClientId(lua_State* L) +{ + // vocation:getClientId() + Vocation* vocation = getUserdata(L, 1); + if (vocation) { + lua_pushnumber(L, vocation->getClientId()); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaVocationGetName(lua_State* L) { // vocation:getName() @@ -9822,6 +11151,24 @@ int LuaScriptInterface::luaHouseGetDoorCount(lua_State* L) return 1; } +int LuaScriptInterface::luaHouseGetDoorIdByPosition(lua_State* L) +{ + // house:getDoorIdByPosition(position) + House* house = getUserdata(L, 1); + if (!house) { + lua_pushnil(L); + return 1; + } + + Door* door = house->getDoorByPosition(getPosition(L, 2)); + if (door) { + lua_pushnumber(L, door->getDoorId()); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaHouseGetTiles(lua_State* L) { // house:getTiles() @@ -9843,6 +11190,32 @@ int LuaScriptInterface::luaHouseGetTiles(lua_State* L) return 1; } +int LuaScriptInterface::luaHouseGetItems(lua_State* L) +{ + // house:getItems() + House* house = getUserdata(L, 1); + if (!house) { + lua_pushnil(L); + return 1; + } + + const auto& tiles = house->getTiles(); + lua_newtable(L); + + int index = 0; + for (Tile* tile : tiles) { + TileItemVector* itemVector = tile->getItemList(); + if(itemVector) { + for(Item* item : *itemVector) { + pushUserdata(L, item); + setItemMetatable(L, -1, item); + lua_rawseti(L, -2, ++index); + } + } + } + return 1; +} + int LuaScriptInterface::luaHouseGetTileCount(lua_State* L) { // house:getTileCount() @@ -9855,6 +11228,22 @@ int LuaScriptInterface::luaHouseGetTileCount(lua_State* L) return 1; } +int LuaScriptInterface::luaHouseCanEditAccessList(lua_State* L) +{ + // house:canEditAccessList(listId, player) + House* house = getUserdata(L, 1); + if (!house) { + lua_pushnil(L); + return 1; + } + + uint32_t listId = getNumber(L, 2); + Player* player = getPlayer(L, 3); + + pushBoolean(L, house->canEditAccessList(listId, player)); + return 1; +} + int LuaScriptInterface::luaHouseGetAccessList(lua_State* L) { // house:getAccessList(listId) @@ -9890,6 +11279,19 @@ int LuaScriptInterface::luaHouseSetAccessList(lua_State* L) return 1; } +int LuaScriptInterface::luaHouseKickPlayer(lua_State* L) +{ + // house:kickPlayer(player, targetPlayer) + House* house = getUserdata(L, 1); + if (!house) { + lua_pushnil(L); + return 1; + } + + pushBoolean(L, house->kickPlayer(getPlayer(L, 2), getPlayer(L, 3))); + return 1; +} + // ItemType int LuaScriptInterface::luaItemTypeCreate(lua_State* L) { @@ -9912,7 +11314,7 @@ int LuaScriptInterface::luaItemTypeIsCorpse(lua_State* L) // itemType:isCorpse() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - pushBoolean(L, itemType->corpse); + pushBoolean(L, itemType->corpseType != RACE_NONE); } else { lua_pushnil(L); } @@ -9943,18 +11345,6 @@ int LuaScriptInterface::luaItemTypeIsContainer(lua_State* L) return 1; } -int LuaScriptInterface::luaItemTypeIsChest(lua_State * L) -{ - // itemType:isChest() - const ItemType* itemType = getUserdata(L, 1); - if (itemType) { - pushBoolean(L, itemType->isChest()); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaItemTypeIsFluidContainer(lua_State* L) { // itemType:isFluidContainer() @@ -10027,7 +11417,31 @@ int LuaScriptInterface::luaItemTypeIsWritable(lua_State* L) return 1; } -int LuaScriptInterface::luaItemTypeIsMagicField(lua_State* L) +int LuaScriptInterface::luaItemTypeIsBlocking(lua_State* L) +{ + // itemType:isBlocking() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->blockProjectile || itemType->blockSolid); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsGroundTile(lua_State* L) +{ + // itemType:isGroundTile() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->isGroundTile()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsMagicField(lua_State* L) { // itemType:isMagicField() const ItemType* itemType = getUserdata(L, 1); @@ -10039,61 +11453,24 @@ int LuaScriptInterface::luaItemTypeIsMagicField(lua_State* L) return 1; } -int LuaScriptInterface::luaItemTypeIsSplash(lua_State* L) +int LuaScriptInterface::luaItemTypeIsUseable(lua_State* L) { - // itemType:isSplash() + // itemType:isUseable() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - pushBoolean(L, itemType->isSplash()); + pushBoolean(L, itemType->isUseable()); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaItemTypeIsKey(lua_State* L) +int LuaScriptInterface::luaItemTypeIsPickupable(lua_State* L) { - // itemType:isKey() + // itemType:isPickupable() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - pushBoolean(L, itemType->isKey()); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaItemTypeIsDisguised(lua_State* L) -{ - // itemType:isDisguised() - const ItemType* itemType = getUserdata(L, 1); - if (itemType) { - pushBoolean(L, itemType->disguise); - } - else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaItemTypeIsDestroyable(lua_State * L) -{ - // itemType:isDestroyable() - const ItemType* itemType = getUserdata(L, 1); - if (itemType) { - pushBoolean(L, itemType->destroy); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaItemTypeIsGroundTile(lua_State * L) -{ - // itemType:isGroundTile() - const ItemType* itemType = getUserdata(L, 1); - if (itemType) { - pushBoolean(L, itemType->group == ITEM_GROUP_GROUND); + pushBoolean(L, itemType->isPickupable()); } else { lua_pushnil(L); } @@ -10124,14 +11501,13 @@ int LuaScriptInterface::luaItemTypeGetId(lua_State* L) return 1; } -int LuaScriptInterface::luaItemTypeGetDisguiseId(lua_State * L) +int LuaScriptInterface::luaItemTypeGetClientId(lua_State* L) { - // itemType:getDisguiseId() + // itemType:getClientId() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->disguiseId); - } - else { + lua_pushnumber(L, itemType->clientId); + } else { lua_pushnil(L); } return 1; @@ -10197,18 +11573,6 @@ int LuaScriptInterface::luaItemTypeGetSlotPosition(lua_State *L) return 1; } -int LuaScriptInterface::luaItemTypeGetDestroyTarget(lua_State * L) -{ - // itemType:getDestroyTarget() - const ItemType* itemType = getUserdata(L, 1); - if (itemType) { - lua_pushnumber(L, itemType->destroyTarget); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaItemTypeGetCharges(lua_State* L) { // itemType:getCharges() @@ -10261,6 +11625,18 @@ int LuaScriptInterface::luaItemTypeGetWeight(lua_State* L) return 1; } +int LuaScriptInterface::luaItemTypeGetHitChance(lua_State* L) +{ + // itemType:getHitChance() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->hitChance); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaItemTypeGetShootRange(lua_State* L) { // itemType:getShootRange() @@ -10297,6 +11673,18 @@ int LuaScriptInterface::luaItemTypeGetDefense(lua_State* L) return 1; } +int LuaScriptInterface::luaItemTypeGetExtraDefense(lua_State* L) +{ + // itemType:getExtraDefense() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->extraDefense); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaItemTypeGetArmor(lua_State* L) { // itemType:getArmor() @@ -10321,6 +11709,66 @@ int LuaScriptInterface::luaItemTypeGetWeaponType(lua_State* L) return 1; } +int LuaScriptInterface::luaItemTypeGetAmmoType(lua_State* L) +{ + // itemType:getAmmoType() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->ammoType); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetCorpseType(lua_State* L) +{ + // itemType:getCorpseType() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->corpseType); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetElementType(lua_State* L) +{ + // itemType:getElementType() + const ItemType* itemType = getUserdata(L, 1); + if (!itemType) { + lua_pushnil(L); + return 1; + } + + auto& abilities = itemType->abilities; + if (abilities) { + lua_pushnumber(L, abilities->elementType); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeGetElementDamage(lua_State* L) +{ + // itemType:getElementDamage() + const ItemType* itemType = getUserdata(L, 1); + if (!itemType) { + lua_pushnil(L); + return 1; + } + + auto& abilities = itemType->abilities; + if (abilities) { + lua_pushnumber(L, abilities->elementDamage); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaItemTypeGetTransformEquipId(lua_State* L) { // itemType:getTransformEquipId() @@ -10345,24 +11793,24 @@ int LuaScriptInterface::luaItemTypeGetTransformDeEquipId(lua_State* L) return 1; } -int LuaScriptInterface::luaItemTypeGetDecayId(lua_State* L) +int LuaScriptInterface::luaItemTypeGetDestroyId(lua_State* L) { - // itemType:getDecayId() + // itemType:getDestroyId() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->decayTo); + lua_pushnumber(L, itemType->destroyTo); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaItemTypeGetNutrition(lua_State* L) +int LuaScriptInterface::luaItemTypeGetDecayId(lua_State* L) { - // itemType:getNutrition() + // itemType:getDecayId() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->nutrition); + lua_pushnumber(L, itemType->decayTo); } else { lua_pushnil(L); } @@ -10468,13 +11916,26 @@ int LuaScriptInterface::luaCombatSetArea(lua_State* L) return 1; } -int LuaScriptInterface::luaCombatSetCondition(lua_State* L) +int LuaScriptInterface::luaCombatAddCondition(lua_State* L) { - // combat:setCondition(condition) + // combat:addCondition(condition) Condition* condition = getUserdata(L, 2); Combat* combat = getUserdata(L, 1); if (combat && condition) { - combat->setCondition(condition->clone()); + combat->addCondition(condition->clone()); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCombatClearConditions(lua_State* L) +{ + // combat:clearConditions() + Combat* combat = getUserdata(L, 1); + if (combat) { + combat->clearConditions(); pushBoolean(L, true); } else { lua_pushnil(L); @@ -10515,8 +11976,7 @@ int LuaScriptInterface::luaCombatSetOrigin(lua_State* L) if (combat) { combat->setOrigin(getNumber(L, 2)); pushBoolean(L, true); - } - else { + } else { lua_pushnil(L); } return 1; @@ -10531,6 +11991,14 @@ int LuaScriptInterface::luaCombatExecute(lua_State* L) return 1; } + if (isUserdata(L, 2)) { + LuaDataType type = getUserdataType(L, 2); + if (type != LuaData_Player && type != LuaData_Monster && type != LuaData_Npc) { + pushBoolean(L, false); + return 1; + } + } + Creature* creature = getCreature(L, 2); const LuaVariant& variant = getVariant(L, 3); @@ -10739,13 +12207,16 @@ int LuaScriptInterface::luaConditionSetParameter(lua_State* L) return 1; } -int LuaScriptInterface::luaConditionSetSpeedDelta(lua_State* L) +int LuaScriptInterface::luaConditionSetFormula(lua_State* L) { - // condition:setSpeedDelta(speedDelta) - int32_t speedDelta = getNumber(L, 2); + // condition:setFormula(mina, minb, maxa, maxb) + double maxb = getNumber(L, 5); + double maxa = getNumber(L, 4); + double minb = getNumber(L, 3); + double mina = getNumber(L, 2); ConditionSpeed* condition = dynamic_cast(getUserdata(L, 1)); if (condition) { - condition->setSpeedDelta(speedDelta); + condition->setFormulaVars(mina, minb, maxa, maxb); pushBoolean(L, true); } else { lua_pushnil(L); @@ -10756,18 +12227,19 @@ int LuaScriptInterface::luaConditionSetSpeedDelta(lua_State* L) int LuaScriptInterface::luaConditionSetOutfit(lua_State* L) { // condition:setOutfit(outfit) - // condition:setOutfit(lookTypeEx, lookType, lookHead, lookBody, lookLegs, lookFeet[, lookAddons]) + // condition:setOutfit(lookTypeEx, lookType, lookHead, lookBody, lookLegs, lookFeet[, lookAddons[, lookMount]]) Outfit_t outfit; if (isTable(L, 2)) { outfit = getOutfit(L, 2); } else { + outfit.lookMount = getNumber(L, 9, outfit.lookMount); outfit.lookAddons = getNumber(L, 8, outfit.lookAddons); outfit.lookFeet = getNumber(L, 7); outfit.lookLegs = getNumber(L, 6); outfit.lookBody = getNumber(L, 5); outfit.lookHead = getNumber(L, 4); - outfit.lookType = getNumber(L, 3); - outfit.lookTypeEx = getNumber(L, 2); + outfit.lookType = getNumber(L, 3); + outfit.lookTypeEx = getNumber(L, 2); } ConditionOutfit* condition = dynamic_cast(getUserdata(L, 1)); @@ -10780,37 +12252,21 @@ int LuaScriptInterface::luaConditionSetOutfit(lua_State* L) return 1; } -int LuaScriptInterface::luaConditionSetTiming(lua_State* L) +int LuaScriptInterface::luaConditionAddDamage(lua_State* L) { - // condition:setTiming(count) - int32_t count = getNumber(L, 2); + // condition:addDamage(rounds, time, value) + int32_t value = getNumber(L, 4); + int32_t time = getNumber(L, 3); + int32_t rounds = getNumber(L, 2); ConditionDamage* condition = dynamic_cast(getUserdata(L, 1)); if (condition) { - if (condition->getType() == CONDITION_POISON) { - condition->setParam(CONDITION_PARAM_COUNT, 3); - condition->setParam(CONDITION_PARAM_MAX_COUNT, 3); - } else if (condition->getType() == CONDITION_FIRE) { - condition->setParam(CONDITION_PARAM_COUNT, 8); - condition->setParam(CONDITION_PARAM_MAX_COUNT, 8); - - count /= 10; - } else if (condition->getType() == CONDITION_ENERGY) { - condition->setParam(CONDITION_PARAM_COUNT, 10); - condition->setParam(CONDITION_PARAM_MAX_COUNT, 10); - - count /= 20; - } - - condition->setParam(CONDITION_PARAM_CYCLE, count); - - pushBoolean(L, true); + pushBoolean(L, condition->addDamage(rounds, time, value)); } else { lua_pushnil(L); } return 1; } - // MonsterType int LuaScriptInterface::luaMonsterTypeCreate(lua_State* L) { @@ -10827,10 +12283,15 @@ int LuaScriptInterface::luaMonsterTypeCreate(lua_State* L) int LuaScriptInterface::luaMonsterTypeIsAttackable(lua_State* L) { - // monsterType:isAttackable() + // get: monsterType:isAttackable() set: monsterType:isAttackable(bool) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - pushBoolean(L, monsterType->info.isAttackable); + if (lua_gettop(L) == 1) { + pushBoolean(L, monsterType->info.isAttackable); + } else { + monsterType->info.isAttackable = getBoolean(L, 2); + pushBoolean(L, true); + } } else { lua_pushnil(L); } @@ -10839,10 +12300,15 @@ int LuaScriptInterface::luaMonsterTypeIsAttackable(lua_State* L) int LuaScriptInterface::luaMonsterTypeIsConvinceable(lua_State* L) { - // monsterType:isConvinceable() + // get: monsterType:isConvinceable() set: monsterType:isConvinceable(bool) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - pushBoolean(L, monsterType->info.isConvinceable); + if (lua_gettop(L) == 1) { + pushBoolean(L, monsterType->info.isConvinceable); + } else { + monsterType->info.isConvinceable = getBoolean(L, 2); + pushBoolean(L, true); + } } else { lua_pushnil(L); } @@ -10851,10 +12317,15 @@ int LuaScriptInterface::luaMonsterTypeIsConvinceable(lua_State* L) int LuaScriptInterface::luaMonsterTypeIsSummonable(lua_State* L) { - // monsterType:isSummonable() + // get: monsterType:isSummonable() set: monsterType:isSummonable(bool) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - pushBoolean(L, monsterType->info.isSummonable); + if (lua_gettop(L) == 1) { + pushBoolean(L, monsterType->info.isSummonable); + } else { + monsterType->info.isSummonable = getBoolean(L, 2); + pushBoolean(L, true); + } } else { lua_pushnil(L); } @@ -10863,10 +12334,15 @@ int LuaScriptInterface::luaMonsterTypeIsSummonable(lua_State* L) int LuaScriptInterface::luaMonsterTypeIsIllusionable(lua_State* L) { - // monsterType:isIllusionable() + // get: monsterType:isIllusionable() set: monsterType:isIllusionable(bool) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - pushBoolean(L, monsterType->info.isIllusionable); + if (lua_gettop(L) == 1) { + pushBoolean(L, monsterType->info.isIllusionable); + } else { + monsterType->info.isIllusionable = getBoolean(L, 2); + pushBoolean(L, true); + } } else { lua_pushnil(L); } @@ -10875,10 +12351,15 @@ int LuaScriptInterface::luaMonsterTypeIsIllusionable(lua_State* L) int LuaScriptInterface::luaMonsterTypeIsHostile(lua_State* L) { - // monsterType:isHostile() + // get: monsterType:isHostile() set: monsterType:isHostile(bool) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - pushBoolean(L, monsterType->info.isHostile); + if (lua_gettop(L) == 1) { + pushBoolean(L, monsterType->info.isHostile); + } else { + monsterType->info.isHostile = getBoolean(L, 2); + pushBoolean(L, true); + } } else { lua_pushnil(L); } @@ -10887,24 +12368,33 @@ int LuaScriptInterface::luaMonsterTypeIsHostile(lua_State* L) int LuaScriptInterface::luaMonsterTypeIsPushable(lua_State* L) { - // monsterType:isPushable() + // get: monsterType:isPushable() set: monsterType:isPushable(bool) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - pushBoolean(L, monsterType->info.pushable); + if (lua_gettop(L) == 1) { + pushBoolean(L, monsterType->info.pushable); + } else { + monsterType->info.pushable = getBoolean(L, 2); + pushBoolean(L, true); + } } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeIsHealthShown(lua_State* L) +int LuaScriptInterface::luaMonsterTypeIsHealthHidden(lua_State* L) { - // monsterType:isHealthShown() + // get: monsterType:isHealthHidden() set: monsterType:isHealthHidden(bool) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - pushBoolean(L, !monsterType->info.hiddenHealth); - } - else { + if (lua_gettop(L) == 1) { + pushBoolean(L, monsterType->info.hiddenHealth); + } else { + monsterType->info.hiddenHealth = getBoolean(L, 2); + pushBoolean(L, true); + } + } else { lua_pushnil(L); } return 1; @@ -10912,10 +12402,15 @@ int LuaScriptInterface::luaMonsterTypeIsHealthShown(lua_State* L) int LuaScriptInterface::luaMonsterTypeCanPushItems(lua_State* L) { - // monsterType:canPushItems() + // get: monsterType:canPushItems() set: monsterType:canPushItems(bool) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - pushBoolean(L, monsterType->info.canPushItems); + if (lua_gettop(L) == 1) { + pushBoolean(L, monsterType->info.canPushItems); + } else { + monsterType->info.canPushItems = getBoolean(L, 2); + pushBoolean(L, true); + } } else { lua_pushnil(L); } @@ -10924,106 +12419,226 @@ int LuaScriptInterface::luaMonsterTypeCanPushItems(lua_State* L) int LuaScriptInterface::luaMonsterTypeCanPushCreatures(lua_State* L) { - // monsterType:canPushCreatures() + // get: monsterType:canPushCreatures() set: monsterType:canPushCreatures(bool) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - pushBoolean(L, monsterType->info.canPushCreatures); + if (lua_gettop(L) == 1) { + pushBoolean(L, monsterType->info.canPushCreatures); + } else { + monsterType->info.canPushCreatures = getBoolean(L, 2); + pushBoolean(L, true); + } } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeGetName(lua_State* L) +int32_t LuaScriptInterface::luaMonsterTypeName(lua_State* L) { - // monsterType:getName() + // get: monsterType:name() set: monsterType:name(name) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - pushString(L, monsterType->name); + if (lua_gettop(L) == 1) { + pushString(L, monsterType->name); + } else { + monsterType->name = getString(L, 2); + pushBoolean(L, true); + } } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeGetNameDescription(lua_State* L) +int LuaScriptInterface::luaMonsterTypeNameDescription(lua_State* L) { - // monsterType:getNameDescription() + // get: monsterType:nameDescription() set: monsterType:nameDescription(desc) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - pushString(L, monsterType->nameDescription); + if (lua_gettop(L) == 1) { + pushString(L, monsterType->nameDescription); + } else { + monsterType->nameDescription = getString(L, 2); + pushBoolean(L, true); + } } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeGetHealth(lua_State* L) +int LuaScriptInterface::luaMonsterTypeHealth(lua_State* L) { - // monsterType:getHealth() + // get: monsterType:health() set: monsterType:health(health) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - lua_pushnumber(L, monsterType->info.health); + if (lua_gettop(L) == 1) { + lua_pushnumber(L, monsterType->info.health); + } else { + monsterType->info.health = getNumber(L, 2); + pushBoolean(L, true); + } } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeGetMaxHealth(lua_State* L) +int LuaScriptInterface::luaMonsterTypeMaxHealth(lua_State* L) { - // monsterType:getMaxHealth() + // get: monsterType:maxHealth() set: monsterType:maxHealth(health) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - lua_pushnumber(L, monsterType->info.healthMax); + if (lua_gettop(L) == 1) { + lua_pushnumber(L, monsterType->info.healthMax); + } else { + monsterType->info.healthMax = getNumber(L, 2); + pushBoolean(L, true); + } } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeGetRunHealth(lua_State* L) +int LuaScriptInterface::luaMonsterTypeRunHealth(lua_State* L) { - // monsterType:getRunHealth() + // get: monsterType:runHealth() set: monsterType:runHealth(health) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - lua_pushnumber(L, monsterType->info.runAwayHealth); + if (lua_gettop(L) == 1) { + lua_pushnumber(L, monsterType->info.runAwayHealth); + } else { + monsterType->info.runAwayHealth = getNumber(L, 2); + pushBoolean(L, true); + } } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeGetExperience(lua_State* L) +int LuaScriptInterface::luaMonsterTypeExperience(lua_State* L) { - // monsterType:getExperience() + // get: monsterType:experience() set: monsterType:experience(exp) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - lua_pushnumber(L, monsterType->info.experience); + if (lua_gettop(L) == 1) { + lua_pushnumber(L, monsterType->info.experience); + } else { + monsterType->info.experience = getNumber(L, 2); + pushBoolean(L, true); + } } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeGetCombatImmunities(lua_State* L) +int LuaScriptInterface::luaMonsterTypeCombatImmunities(lua_State* L) { - // monsterType:getCombatImmunities() + // get: monsterType:combatImmunities() set: monsterType:combatImmunities(immunity) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - lua_pushnumber(L, monsterType->info.damageImmunities); + if (lua_gettop(L) == 1) { + lua_pushnumber(L, monsterType->info.damageImmunities); + } else { + std::string immunity = getString(L, 2); + if (immunity == "physical") { + monsterType->info.damageImmunities |= COMBAT_PHYSICALDAMAGE; + pushBoolean(L, true); + } else if (immunity == "energy") { + monsterType->info.damageImmunities |= COMBAT_ENERGYDAMAGE; + pushBoolean(L, true); + } else if (immunity == "fire") { + monsterType->info.damageImmunities |= COMBAT_FIREDAMAGE; + pushBoolean(L, true); + } else if (immunity == "poison" || immunity == "earth") { + monsterType->info.damageImmunities |= COMBAT_EARTHDAMAGE; + pushBoolean(L, true); + } else if (immunity == "drown") { + monsterType->info.damageImmunities |= COMBAT_DROWNDAMAGE; + pushBoolean(L, true); + } else if (immunity == "ice") { + monsterType->info.damageImmunities |= COMBAT_ICEDAMAGE; + pushBoolean(L, true); + } else if (immunity == "holy") { + monsterType->info.damageImmunities |= COMBAT_HOLYDAMAGE; + pushBoolean(L, true); + } else if (immunity == "death") { + monsterType->info.damageImmunities |= COMBAT_DEATHDAMAGE; + pushBoolean(L, true); + } else if (immunity == "lifedrain") { + monsterType->info.damageImmunities |= COMBAT_LIFEDRAIN; + pushBoolean(L, true); + } else if (immunity == "manadrain") { + monsterType->info.damageImmunities |= COMBAT_MANADRAIN; + pushBoolean(L, true); + } else { + std::cout << "[Warning - Monsters::loadMonster] Unknown immunity name " << immunity << " for monster: " << monsterType->name << std::endl; + lua_pushnil(L); + } + } } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeGetConditionImmunities(lua_State* L) +int LuaScriptInterface::luaMonsterTypeConditionImmunities(lua_State* L) { - // monsterType:getConditionImmunities() + // get: monsterType:conditionImmunities() set: monsterType:conditionImmunities(immunity) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - lua_pushnumber(L, monsterType->info.conditionImmunities); + if (lua_gettop(L) == 1) { + lua_pushnumber(L, monsterType->info.conditionImmunities); + } else { + std::string immunity = getString(L, 2); + if (immunity == "physical") { + monsterType->info.conditionImmunities |= CONDITION_BLEEDING; + pushBoolean(L, true); + } else if (immunity == "energy") { + monsterType->info.conditionImmunities |= CONDITION_ENERGY; + pushBoolean(L, true); + } else if (immunity == "fire") { + monsterType->info.conditionImmunities |= CONDITION_FIRE; + pushBoolean(L, true); + } else if (immunity == "poison" || immunity == "earth") { + monsterType->info.conditionImmunities |= CONDITION_POISON; + pushBoolean(L, true); + } else if (immunity == "drown") { + monsterType->info.conditionImmunities |= CONDITION_DROWN; + pushBoolean(L, true); + } else if (immunity == "ice") { + monsterType->info.conditionImmunities |= CONDITION_FREEZING; + pushBoolean(L, true); + } else if (immunity == "holy") { + monsterType->info.conditionImmunities |= CONDITION_DAZZLED; + pushBoolean(L, true); + } else if (immunity == "death") { + monsterType->info.conditionImmunities |= CONDITION_CURSED; + pushBoolean(L, true); + } else if (immunity == "paralyze") { + monsterType->info.conditionImmunities |= CONDITION_PARALYZE; + pushBoolean(L, true); + } else if (immunity == "outfit") { + monsterType->info.conditionImmunities |= CONDITION_OUTFIT; + pushBoolean(L, true); + } else if (immunity == "drunk") { + monsterType->info.conditionImmunities |= CONDITION_DRUNK; + pushBoolean(L, true); + } else if (immunity == "invisible" || immunity == "invisibility") { + monsterType->info.conditionImmunities |= CONDITION_INVISIBLE; + pushBoolean(L, true); + } else if (immunity == "bleed") { + monsterType->info.conditionImmunities |= CONDITION_BLEEDING; + pushBoolean(L, true); + } else { + std::cout << "[Warning - Monsters::loadMonster] Unknown immunity name " << immunity << " for monster: " << monsterType->name << std::endl; + lua_pushnil(L); + } + } } else { lua_pushnil(L); } @@ -11047,9 +12662,11 @@ int LuaScriptInterface::luaMonsterTypeGetAttackList(lua_State* L) setField(L, "chance", spellBlock.chance); setField(L, "isCombatSpell", spellBlock.combatSpell ? 1 : 0); + setField(L, "isMelee", spellBlock.isMelee ? 1 : 0); setField(L, "minCombatValue", spellBlock.minCombatValue); setField(L, "maxCombatValue", spellBlock.maxCombatValue); setField(L, "range", spellBlock.range); + setField(L, "speed", spellBlock.speed); pushUserdata(L, static_cast(spellBlock.spell)); lua_setfield(L, -2, "spell"); @@ -11058,6 +12675,29 @@ int LuaScriptInterface::luaMonsterTypeGetAttackList(lua_State* L) return 1; } +int LuaScriptInterface::luaMonsterTypeAddAttack(lua_State* L) +{ + // monsterType:addAttack(monsterspell) + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + MonsterSpell* spell = getUserdata(L, 2); + if (spell) { + spellBlock_t sb; + if (g_monsters.deserializeSpell(spell, sb, monsterType->name)) { + monsterType->info.attackSpells.push_back(std::move(sb)); + } else { + std::cout << monsterType->name << std::endl; + std::cout << "[Warning - Monsters::loadMonster] Cant load spell. " << spell->name << std::endl; + } + } else { + lua_pushnil(L); + } + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaMonsterTypeGetDefenseList(lua_State* L) { // monsterType:getDefenseList() @@ -11076,9 +12716,11 @@ int LuaScriptInterface::luaMonsterTypeGetDefenseList(lua_State* L) setField(L, "chance", spellBlock.chance); setField(L, "isCombatSpell", spellBlock.combatSpell ? 1 : 0); + setField(L, "isMelee", spellBlock.isMelee ? 1 : 0); setField(L, "minCombatValue", spellBlock.minCombatValue); setField(L, "maxCombatValue", spellBlock.maxCombatValue); setField(L, "range", spellBlock.range); + setField(L, "speed", spellBlock.speed); pushUserdata(L, static_cast(spellBlock.spell)); lua_setfield(L, -2, "spell"); @@ -11087,6 +12729,29 @@ int LuaScriptInterface::luaMonsterTypeGetDefenseList(lua_State* L) return 1; } +int LuaScriptInterface::luaMonsterTypeAddDefense(lua_State* L) +{ + // monsterType:addDefense(monsterspell) + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + MonsterSpell* spell = getUserdata(L, 2); + if (spell) { + spellBlock_t sb; + if (g_monsters.deserializeSpell(spell, sb, monsterType->name)) { + monsterType->info.defenseSpells.push_back(std::move(sb)); + } else { + std::cout << monsterType->name << std::endl; + std::cout << "[Warning - Monsters::loadMonster] Cant load spell. " << spell->name << std::endl; + } + } else { + lua_pushnil(L); + } + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaMonsterTypeGetElementList(lua_State* L) { // monsterType:getElementList() @@ -11104,6 +12769,20 @@ int LuaScriptInterface::luaMonsterTypeGetElementList(lua_State* L) return 1; } +int LuaScriptInterface::luaMonsterTypeAddElement(lua_State* L) +{ + // monsterType:addElement(type, percent) + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + CombatType_t element = getNumber(L, 2); + monsterType->info.elementMap[element] = getNumber(L, 3); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaMonsterTypeGetVoices(lua_State* L) { // monsterType:getVoices() @@ -11124,6 +12803,24 @@ int LuaScriptInterface::luaMonsterTypeGetVoices(lua_State* L) return 1; } +int LuaScriptInterface::luaMonsterTypeAddVoice(lua_State* L) +{ + // monsterType:addVoice(sentence, interval, chance, yell) + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + voiceBlock_t voice; + voice.text = getString(L, 2); + monsterType->info.yellSpeedTicks = getNumber(L, 3); + monsterType->info.yellChance = getNumber(L, 4); + voice.yellText = getBoolean(L, 5); + monsterType->info.voiceVector.push_back(voice); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaMonsterTypeGetLoot(lua_State* L) { // monsterType:getLoot() @@ -11133,27 +12830,25 @@ int LuaScriptInterface::luaMonsterTypeGetLoot(lua_State* L) return 1; } - static const std::function&)> parseLoot = [&](const std::vector& lootList) { - lua_createtable(L, lootList.size(), 0); + pushLoot(L, monsterType->info.lootItems); + return 1; +} - int index = 0; - for (const auto& lootBlock : lootList) { - lua_createtable(L, 0, 7); - - setField(L, "itemId", lootBlock.id); - setField(L, "chance", lootBlock.chance); - setField(L, "subType", lootBlock.subType); - setField(L, "maxCount", lootBlock.countmax); - setField(L, "actionId", lootBlock.actionId); - setField(L, "text", lootBlock.text); - - parseLoot(lootBlock.childLoot); - lua_setfield(L, -2, "childLoot"); - - lua_rawseti(L, -2, ++index); +int LuaScriptInterface::luaMonsterTypeAddLoot(lua_State* L) +{ + // monsterType:addLoot(loot) + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + Loot* loot = getUserdata(L, 2); + if (loot) { + monsterType->loadLoot(monsterType, loot->lootBlock); + pushBoolean(L, true); + } else { + lua_pushnil(L); } - }; - parseLoot(monsterType->info.lootItems); + } else { + lua_pushnil(L); + } return 1; } @@ -11175,6 +12870,52 @@ int LuaScriptInterface::luaMonsterTypeGetCreatureEvents(lua_State* L) return 1; } +int LuaScriptInterface::luaMonsterTypeRegisterEvent(lua_State* L) +{ + // monsterType:registerEvent(name) + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + monsterType->info.scripts.push_back(getString(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeEventOnCallback(lua_State* L) +{ + // monsterType:onThink(callback) + // monsterType:onAppear(callback) + // monsterType:onDisappear(callback) + // monsterType:onMove(callback) + // monsterType:onSay(callback) + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + if (monsterType->loadCallback(&g_scripts->getScriptInterface())) { + pushBoolean(L, true); + return 1; + } + pushBoolean(L, false); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeEventType(lua_State* L) +{ + // monstertype:eventType(event) + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + monsterType->info.eventType = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaMonsterTypeGetSummonList(lua_State* L) { // monsterType:getSummonList() @@ -11189,152 +12930,694 @@ int LuaScriptInterface::luaMonsterTypeGetSummonList(lua_State* L) for (const auto& summonBlock : monsterType->info.summons) { lua_createtable(L, 0, 3); setField(L, "name", summonBlock.name); + setField(L, "speed", summonBlock.speed); setField(L, "chance", summonBlock.chance); lua_rawseti(L, -2, ++index); } return 1; } -int LuaScriptInterface::luaMonsterTypeGetMaxSummons(lua_State* L) +int LuaScriptInterface::luaMonsterTypeAddSummon(lua_State* L) { - // monsterType:getMaxSummons() + // monsterType:addSummon(name, interval, chance) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - lua_pushnumber(L, monsterType->info.maxSummons); + summonBlock_t summon; + summon.name = getString(L, 2); + summon.chance = getNumber(L, 3); + summon.speed = getNumber(L, 4); + monsterType->info.summons.push_back(summon); + pushBoolean(L, true); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeGetArmor(lua_State* L) +int LuaScriptInterface::luaMonsterTypeMaxSummons(lua_State* L) { - // monsterType:getArmor() + // get: monsterType:maxSummons() set: monsterType:maxSummons(ammount) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - lua_pushnumber(L, monsterType->info.armor); + if (lua_gettop(L) == 1) { + lua_pushnumber(L, monsterType->info.maxSummons); + } else { + monsterType->info.maxSummons = getNumber(L, 2); + pushBoolean(L, true); + } } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeGetDefense(lua_State* L) +int LuaScriptInterface::luaMonsterTypeArmor(lua_State* L) { - // monsterType:getDefense() + // get: monsterType:armor() set: monsterType:armor(armor) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - lua_pushnumber(L, monsterType->info.defense); + if (lua_gettop(L) == 1) { + lua_pushnumber(L, monsterType->info.armor); + } else { + monsterType->info.armor = getNumber(L, 2); + pushBoolean(L, true); + } } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeGetOutfit(lua_State* L) +int LuaScriptInterface::luaMonsterTypeDefense(lua_State* L) { - // monsterType:getOutfit() + // get: monsterType:defense() set: monsterType:defense(defense) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - pushOutfit(L, monsterType->info.outfit); + if (lua_gettop(L) == 1) { + lua_pushnumber(L, monsterType->info.defense); + } else { + monsterType->info.defense = getNumber(L, 2); + pushBoolean(L, true); + } } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeGetRace(lua_State* L) +int LuaScriptInterface::luaMonsterTypeOutfit(lua_State* L) { - // monsterType:getRace() + // get: monsterType:outfit() set: monsterType:outfit(outfit) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - lua_pushnumber(L, monsterType->info.race); + if (lua_gettop(L) == 1) { + pushOutfit(L, monsterType->info.outfit); + } else { + monsterType->info.outfit = getOutfit(L, 2); + pushBoolean(L, true); + } } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeGetCorpseId(lua_State* L) +int LuaScriptInterface::luaMonsterTypeRace(lua_State* L) { - // monsterType:getCorpseId() + // get: monsterType:race() set: monsterType:race(race) MonsterType* monsterType = getUserdata(L, 1); + std::string race = getString(L, 2); if (monsterType) { - lua_pushnumber(L, monsterType->info.lookcorpse); + if (lua_gettop(L) == 1) { + lua_pushnumber(L, monsterType->info.race); + } else { + if (race == "venom") { + monsterType->info.race = RACE_VENOM; + } else if (race == "blood") { + monsterType->info.race = RACE_BLOOD; + } else if (race == "undead") { + monsterType->info.race = RACE_UNDEAD; + } else if (race == "fire") { + monsterType->info.race = RACE_FIRE; + } else if (race == "energy") { + monsterType->info.race = RACE_ENERGY; + } else { + std::cout << "[Warning - Monsters::loadMonster] Unknown race type " << race << "." << std::endl; + lua_pushnil(L); + return 1; + } + pushBoolean(L, true); + } } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeGetManaCost(lua_State* L) +int LuaScriptInterface::luaMonsterTypeCorpseId(lua_State* L) { - // monsterType:getManaCost() + // get: monsterType:corpseId() set: monsterType:corpseId(id) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - lua_pushnumber(L, monsterType->info.manaCost); + if (lua_gettop(L) == 1) { + lua_pushnumber(L, monsterType->info.lookcorpse); + } else { + monsterType->info.lookcorpse = getNumber(L, 2); + lua_pushboolean(L, true); + } } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeGetBaseSpeed(lua_State* L) +int LuaScriptInterface::luaMonsterTypeManaCost(lua_State* L) { - // monsterType:getBaseSpeed() + // get: monsterType:manaCost() set: monsterType:manaCost(mana) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - lua_pushnumber(L, monsterType->info.baseSpeed); + if (lua_gettop(L) == 1) { + lua_pushnumber(L, monsterType->info.manaCost); + } else { + monsterType->info.manaCost = getNumber(L, 2); + pushBoolean(L, true); + } } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeGetLight(lua_State* L) +int LuaScriptInterface::luaMonsterTypeBaseSpeed(lua_State* L) { - // monsterType:getLight() + // get: monsterType:baseSpeed() set: monsterType:baseSpeed(speed) + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + if (lua_gettop(L) == 1) { + lua_pushnumber(L, monsterType->info.baseSpeed); + } else { + monsterType->info.baseSpeed = getNumber(L, 2); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeLight(lua_State* L) +{ + // get: monsterType:light() set: monsterType:light(color, level) MonsterType* monsterType = getUserdata(L, 1); if (!monsterType) { lua_pushnil(L); return 1; } - - lua_pushnumber(L, monsterType->info.light.level); - lua_pushnumber(L, monsterType->info.light.color); - return 2; + if (lua_gettop(L) == 1) { + lua_pushnumber(L, monsterType->info.light.level); + lua_pushnumber(L, monsterType->info.light.color); + return 2; + } else { + monsterType->info.light.color = getNumber(L, 2); + monsterType->info.light.level = getNumber(L, 3); + pushBoolean(L, true); + } + return 1; } -int LuaScriptInterface::luaMonsterTypeGetTargetDistance(lua_State* L) +int LuaScriptInterface::luaMonsterTypeStaticAttackChance(lua_State* L) { - // monsterType:getTargetDistance() + // get: monsterType:staticAttackChance() set: monsterType:staticAttackChance(chance) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - lua_pushnumber(L, monsterType->info.targetDistance); + if (lua_gettop(L) == 1) { + lua_pushnumber(L, monsterType->info.staticAttackChance); + } else { + monsterType->info.staticAttackChance = getNumber(L, 2); + pushBoolean(L, true); + } } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeGetChangeTargetChance(lua_State* L) +int LuaScriptInterface::luaMonsterTypeTargetDistance(lua_State* L) { - // monsterType:getChangeTargetChance() + // get: monsterType:targetDistance() set: monsterType:targetDistance(distance) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - lua_pushnumber(L, monsterType->info.changeTargetChance); + if (lua_gettop(L) == 1) { + lua_pushnumber(L, monsterType->info.targetDistance); + } else { + monsterType->info.targetDistance = getNumber(L, 2); + pushBoolean(L, true); + } } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeGetChangeTargetSpeed(lua_State* L) +int LuaScriptInterface::luaMonsterTypeYellChance(lua_State* L) { - // monsterType:getChangeTargetSpeed() + // get: monsterType:yellChance() set: monsterType:yellChance(chance) MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - lua_pushnumber(L, monsterType->info.changeTargetSpeed); + if (lua_gettop(L) == 1) { + lua_pushnumber(L, monsterType->info.yellChance); + } else { + monsterType->info.yellChance = getNumber(L, 2); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeYellSpeedTicks(lua_State* L) +{ + // get: monsterType:yellSpeedTicks() set: monsterType:yellSpeedTicks(rate) + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + if (lua_gettop(L) == 1) { + lua_pushnumber(L, monsterType->info.yellSpeedTicks); + } else { + monsterType->info.yellSpeedTicks = getNumber(L, 2); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeChangeTargetChance(lua_State* L) +{ + // get: monsterType:changeTargetChance() set: monsterType:changeTargetChance(chance) + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + if (lua_gettop(L) == 1) { + lua_pushnumber(L, monsterType->info.changeTargetChance); + } else { + monsterType->info.changeTargetChance = getNumber(L, 2); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterTypeChangeTargetSpeed(lua_State* L) +{ + // get: monsterType:changeTargetSpeed() set: monsterType:changeTargetSpeed(speed) + MonsterType* monsterType = getUserdata(L, 1); + if (monsterType) { + if (lua_gettop(L) == 1) { + lua_pushnumber(L, monsterType->info.changeTargetSpeed); + } else { + monsterType->info.changeTargetSpeed = getNumber(L, 2); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +// Loot +int LuaScriptInterface::luaCreateLoot(lua_State* L) +{ + // Loot() will create a new loot item + Loot* loot = new Loot(); + if (loot) { + pushUserdata(L, loot); + setMetatable(L, -1, "Loot"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaDeleteLoot(lua_State* L) +{ + // loot:delete() loot:__gc() + Loot** lootPtr = getRawUserdata(L, 1); + if (lootPtr && *lootPtr) { + delete *lootPtr; + *lootPtr = nullptr; + } + return 0; +} + +int LuaScriptInterface::luaLootSetId(lua_State* L) +{ + // loot:setId(id or name) + Loot* loot = getUserdata(L, 1); + uint16_t item; + if (loot) { + if (isNumber(L, 2)) { + loot->lootBlock.id = getNumber(L, 2); + } else { + item = Item::items.getItemIdByName(getString(L, 2)); + loot->lootBlock.id = item; + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaLootSetSubType(lua_State* L) +{ + // loot:setSubType(type) + Loot* loot = getUserdata(L, 1); + if (loot) { + loot->lootBlock.subType = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaLootSetChance(lua_State* L) +{ + // loot:setChance(chance) + Loot* loot = getUserdata(L, 1); + if (loot) { + loot->lootBlock.chance = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaLootSetMaxCount(lua_State* L) +{ + // loot:setMaxCount(max) + Loot* loot = getUserdata(L, 1); + if (loot) { + loot->lootBlock.countmax = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaLootSetActionId(lua_State* L) +{ + // loot:setActionId(actionid) + Loot* loot = getUserdata(L, 1); + if (loot) { + loot->lootBlock.actionId = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaLootSetDescription(lua_State* L) +{ + // loot:setDescription(desc) + Loot* loot = getUserdata(L, 1); + if (loot) { + loot->lootBlock.text = getString(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaLootAddChildLoot(lua_State* L) +{ + // loot:addChildLoot(loot) + Loot* loot = getUserdata(L, 1); + if (loot) { + loot->lootBlock.childLoot.push_back(getUserdata(L, 2)->lootBlock); + } else { + lua_pushnil(L); + } + return 1; +} + +// MonsterSpell +int LuaScriptInterface::luaCreateMonsterSpell(lua_State* L) +{ + // MonsterSpell() will create a new Monster Spell + MonsterSpell* spell = new MonsterSpell(); + if (spell) { + pushUserdata(L, spell); + setMetatable(L, -1, "MonsterSpell"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaDeleteMonsterSpell(lua_State* L) +{ + // monsterSpell:delete() monsterSpell:__gc() + MonsterSpell** monsterSpellPtr = getRawUserdata(L, 1); + if (monsterSpellPtr && *monsterSpellPtr) { + delete *monsterSpellPtr; + *monsterSpellPtr = nullptr; + } + return 0; +} + +int LuaScriptInterface::luaMonsterSpellSetType(lua_State* L) +{ + // monsterSpell:setType(type) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->name = getString(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSpellSetScriptName(lua_State* L) +{ + // monsterSpell:setScriptName(name) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->scriptName = getString(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSpellSetChance(lua_State* L) +{ + // monsterSpell:setChance(chance) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->chance = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSpellSetInterval(lua_State* L) +{ + // monsterSpell:setInterval(interval) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->interval = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSpellSetRange(lua_State* L) +{ + // monsterSpell:setRange(range) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->range = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSpellSetCombatValue(lua_State* L) +{ + // monsterSpell:setCombatValue(min, max) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->minCombatValue = getNumber(L, 2); + spell->maxCombatValue = getNumber(L, 3); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSpellSetCombatType(lua_State* L) +{ + // monsterSpell:setCombatType(combatType_t) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->combatType = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSpellSetAttackValue(lua_State* L) +{ + // monsterSpell:setAttackValue(attack, skill) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->attack = getNumber(L, 2); + spell->skill = getNumber(L, 3); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSpellSetNeedTarget(lua_State* L) +{ + // monsterSpell:setNeedTarget(bool) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->needTarget = getBoolean(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSpellSetCombatLength(lua_State* L) +{ + // monsterSpell:setCombatLength(length) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->length = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSpellSetCombatSpread(lua_State* L) +{ + // monsterSpell:setCombatSpread(spread) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->spread = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSpellSetCombatRadius(lua_State* L) +{ + // monsterSpell:setCombatRadius(radius) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->radius = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSpellSetConditionType(lua_State* L) +{ + // monsterSpell:setConditionType(type) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->conditionType = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSpellSetConditionDamage(lua_State* L) +{ + // monsterSpell:setConditionDamage(min, max, start) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->conditionMinDamage = getNumber(L, 2); + spell->conditionMaxDamage = getNumber(L, 3); + spell->conditionStartDamage = getNumber(L, 4); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSpellSetConditionSpeedChange(lua_State* L) +{ + // monsterSpell:setConditionSpeedChange(speed) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->speedChange = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSpellSetConditionDuration(lua_State* L) +{ + // monsterSpell:setConditionDuration(duration) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->duration = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSpellSetConditionTickInterval(lua_State* L) +{ + // monsterSpell:setConditionTickInterval(interval) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->tickInterval = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSpellSetCombatShootEffect(lua_State* L) +{ + // monsterSpell:setCombatShootEffect(effect) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->shoot = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMonsterSpellSetCombatEffect(lua_State* L) +{ + // monsterSpell:setCombatEffect(effect) + MonsterSpell* spell = getUserdata(L, 1); + if (spell) { + spell->effect = getNumber(L, 2); + pushBoolean(L, true); } else { lua_pushnil(L); } @@ -11342,6 +13625,28 @@ int LuaScriptInterface::luaMonsterTypeGetChangeTargetSpeed(lua_State* L) } // Party +int32_t LuaScriptInterface::luaPartyCreate(lua_State* L) +{ + // Party(userdata) + Player* player = getUserdata(L, 2); + if (!player) { + lua_pushnil(L); + return 1; + } + + Party* party = player->getParty(); + if (!party) { + party = new Party(player); + g_game.updatePlayerShield(player); + player->sendCreatureSkull(player); + pushUserdata(L, party); + setMetatable(L, -1, "Party"); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaPartyDisband(lua_State* L) { // party:disband() @@ -11533,7 +13838,7 @@ int LuaScriptInterface::luaPartyShareExperience(lua_State* L) uint64_t experience = getNumber(L, 2); Party* party = getUserdata(L, 1); if (party) { - party->shareExperience(experience, nullptr); + party->shareExperience(experience); pushBoolean(L, true); } else { lua_pushnil(L); @@ -11554,6 +13859,2222 @@ int LuaScriptInterface::luaPartySetSharedExperience(lua_State* L) return 1; } +// Spells +int LuaScriptInterface::luaSpellCreate(lua_State* L) +{ + // Spell(words, name or id) to get an existing spell + // Spell(type) ex: Spell(SPELL_INSTANT) or Spell(SPELL_RUNE) to create a new spell + if (lua_gettop(L) == 1) { + std::cout << "[Error - Spell::luaSpellCreate] There is no parameter set!" << std::endl; + lua_pushnil(L); + return 1; + } + + SpellType_t type = getNumber(L, 2); + + if (isString(L, 2)) { + std::string tmp = asLowerCaseString(getString(L, 2)); + if (tmp == "instant") { + type = SPELL_INSTANT; + } else if (tmp == "rune") { + type = SPELL_RUNE; + } + } + + if (type == SPELL_INSTANT) { + InstantSpell* spell = new InstantSpell(getScriptEnv()->getScriptInterface()); + spell->fromLua = true; + pushUserdata(L, spell); + setMetatable(L, -1, "Spell"); + spell->spellType = SPELL_INSTANT; + return 1; + } else if (type == SPELL_RUNE) { + RuneSpell* spell = new RuneSpell(getScriptEnv()->getScriptInterface()); + spell->fromLua = true; + pushUserdata(L, spell); + setMetatable(L, -1, "Spell"); + spell->spellType = SPELL_RUNE; + return 1; + } + + // isNumber(L, 2) doesn't work here for some reason, maybe a bug? + if (getNumber(L, 2)) { + InstantSpell* instant = g_spells->getInstantSpellById(getNumber(L, 2)); + if (instant) { + pushUserdata(L, instant); + setMetatable(L, -1, "Spell"); + return 1; + } + RuneSpell* rune = g_spells->getRuneSpell(getNumber(L, 2)); + if (rune) { + pushUserdata(L, rune); + setMetatable(L, -1, "Spell"); + return 1; + } + } else if (isString(L, 2)) { + std::string arg = getString(L, 2); + InstantSpell* instant = g_spells->getInstantSpellByName(arg); + if (instant) { + pushUserdata(L, instant); + setMetatable(L, -1, "Spell"); + return 1; + } + instant = g_spells->getInstantSpell(arg); + if (instant) { + pushUserdata(L, instant); + setMetatable(L, -1, "Spell"); + return 1; + } + RuneSpell* rune = g_spells->getRuneSpellByName(arg); + if (rune) { + pushUserdata(L, rune); + setMetatable(L, -1, "Spell"); + return 1; + } + } + lua_pushnil(L); + return 1; +} + +int LuaScriptInterface::luaSpellOnCastSpell(lua_State* L) +{ + // spell:onCastSpell(callback) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (spell->spellType == SPELL_INSTANT) { + InstantSpell* instant = dynamic_cast(getUserdata(L, 1)); + if (!instant->loadCallback()) { + pushBoolean(L, false); + return 1; + } + instant->scripted = true; + pushBoolean(L, true); + } else if (spell->spellType == SPELL_RUNE) { + RuneSpell* rune = dynamic_cast(getUserdata(L, 1)); + if (!rune->loadCallback()) { + pushBoolean(L, false); + return 1; + } + rune->scripted = true; + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaSpellRegister(lua_State* L) +{ + // spell:register() + Spell* spell = getUserdata(L, 1); + if (spell) { + if (spell->spellType == SPELL_INSTANT) { + InstantSpell* instant = dynamic_cast(getUserdata(L, 1)); + if (!instant->isScripted()) { + pushBoolean(L, false); + return 1; + } + pushBoolean(L, g_spells->registerInstantLuaEvent(instant)); + } else if (spell->spellType == SPELL_RUNE) { + RuneSpell* rune = dynamic_cast(getUserdata(L, 1)); + if (rune->getMagicLevel() != 0 || rune->getLevel() != 0) { + //Change information in the ItemType to get accurate description + ItemType& iType = Item::items.getItemType(rune->getRuneItemId()); + iType.name = rune->getName(); + iType.runeMagLevel = rune->getMagicLevel(); + iType.runeLevel = rune->getLevel(); + iType.charges = rune->getCharges(); + } + if (!rune->isScripted()) { + pushBoolean(L, false); + return 1; + } + pushBoolean(L, g_spells->registerRuneLuaEvent(rune)); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaSpellName(lua_State* L) +{ + // spell:name(name) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (lua_gettop(L) == 1) { + pushString(L, spell->getName()); + } else { + spell->setName(getString(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaSpellId(lua_State* L) +{ + // spell:id(id) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (lua_gettop(L) == 1) { + lua_pushnumber(L, spell->getId()); + } else { + spell->setId(getNumber(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaSpellGroup(lua_State* L) +{ + // spell:group(primaryGroup[, secondaryGroup]) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (lua_gettop(L) == 1) { + lua_pushnumber(L, spell->getGroup()); + lua_pushnumber(L, spell->getSecondaryGroup()); + return 2; + } else if (lua_gettop(L) == 2) { + SpellGroup_t group = getNumber(L, 2); + if (group) { + spell->setGroup(group); + pushBoolean(L, true); + } else if (isString(L, 2)) { + group = stringToSpellGroup(getString(L, 2)); + if (group != SPELLGROUP_NONE) { + spell->setGroup(group); + } else { + std::cout << "[Warning - Spell::group] Unknown group: " << getString(L, 2) << std::endl; + pushBoolean(L, false); + return 1; + } + pushBoolean(L, true); + } else { + std::cout << "[Warning - Spell::group] Unknown group: " << getString(L, 2) << std::endl; + pushBoolean(L, false); + return 1; + } + } else { + SpellGroup_t primaryGroup = getNumber(L, 2); + SpellGroup_t secondaryGroup = getNumber(L, 2); + if (primaryGroup && secondaryGroup) { + spell->setGroup(primaryGroup); + spell->setSecondaryGroup(secondaryGroup); + pushBoolean(L, true); + } else if (isString(L, 2) && isString(L, 3)) { + primaryGroup = stringToSpellGroup(getString(L, 2)); + if (primaryGroup != SPELLGROUP_NONE) { + spell->setGroup(primaryGroup); + } else { + std::cout << "[Warning - Spell::group] Unknown primaryGroup: " << getString(L, 2) << std::endl; + pushBoolean(L, false); + return 1; + } + secondaryGroup = stringToSpellGroup(getString(L, 3)); + if (secondaryGroup != SPELLGROUP_NONE) { + spell->setSecondaryGroup(secondaryGroup); + } else { + std::cout << "[Warning - Spell::group] Unknown secondaryGroup: " << getString(L, 3) << std::endl; + pushBoolean(L, false); + return 1; + } + pushBoolean(L, true); + } else { + std::cout << "[Warning - Spell::group] Unknown primaryGroup: " << getString(L, 2) << " or secondaryGroup: " << getString(L, 3) << std::endl; + pushBoolean(L, false); + return 1; + } + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaSpellCooldown(lua_State* L) +{ + // spell:cooldown(cooldown) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (lua_gettop(L) == 1) { + lua_pushnumber(L, spell->getCooldown()); + } else { + spell->setCooldown(getNumber(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaSpellGroupCooldown(lua_State* L) +{ + // spell:groupCooldown(primaryGroupCd[, secondaryGroupCd]) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (lua_gettop(L) == 1) { + lua_pushnumber(L, spell->getGroupCooldown()); + lua_pushnumber(L, spell->getSecondaryCooldown()); + return 2; + } else if (lua_gettop(L) == 2) { + spell->setGroupCooldown(getNumber(L, 2)); + pushBoolean(L, true); + } else { + spell->setGroupCooldown(getNumber(L, 2)); + spell->setSecondaryCooldown(getNumber(L, 3)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaSpellLevel(lua_State* L) +{ + // spell:level(lvl) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (lua_gettop(L) == 1) { + lua_pushnumber(L, spell->getLevel()); + } else { + spell->setLevel(getNumber(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaSpellMagicLevel(lua_State* L) +{ + // spell:magicLevel(lvl) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (lua_gettop(L) == 1) { + lua_pushnumber(L, spell->getMagicLevel()); + } else { + spell->setMagicLevel(getNumber(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaSpellMana(lua_State* L) +{ + // spell:mana(mana) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (lua_gettop(L) == 1) { + lua_pushnumber(L, spell->getMana()); + } else { + spell->setMana(getNumber(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaSpellManaPercent(lua_State* L) +{ + // spell:manaPercent(percent) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (lua_gettop(L) == 1) { + lua_pushnumber(L, spell->getManaPercent()); + } else { + spell->setManaPercent(getNumber(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaSpellSoul(lua_State* L) +{ + // spell:soul(soul) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (lua_gettop(L) == 1) { + lua_pushnumber(L, spell->getSoulCost()); + } else { + spell->setSoulCost(getNumber(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaSpellRange(lua_State* L) +{ + // spell:range(range) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (lua_gettop(L) == 1) { + lua_pushnumber(L, spell->getRange()); + } else { + spell->setRange(getNumber(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaSpellPremium(lua_State* L) +{ + // spell:isPremium(bool) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (lua_gettop(L) == 1) { + pushBoolean(L, spell->isPremium()); + } else { + spell->setPremium(getBoolean(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaSpellEnabled(lua_State* L) +{ + // spell:isEnabled(bool) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (lua_gettop(L) == 1) { + pushBoolean(L, spell->isEnabled()); + } else { + spell->setEnabled(getBoolean(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaSpellNeedTarget(lua_State* L) +{ + // spell:needTarget(bool) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (lua_gettop(L) == 1) { + pushBoolean(L, spell->getNeedTarget()); + } else { + spell->setNeedTarget(getBoolean(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaSpellNeedWeapon(lua_State* L) +{ + // spell:needWeapon(bool) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (lua_gettop(L) == 1) { + pushBoolean(L, spell->getNeedWeapon()); + } else { + spell->setNeedWeapon(getBoolean(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaSpellNeedLearn(lua_State* L) +{ + // spell:needLearn(bool) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (lua_gettop(L) == 1) { + pushBoolean(L, spell->getNeedLearn()); + } else { + spell->setNeedLearn(getBoolean(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaSpellSelfTarget(lua_State* L) +{ + // spell:isSelfTarget(bool) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (lua_gettop(L) == 1) { + pushBoolean(L, spell->getSelfTarget()); + } else { + spell->setSelfTarget(getBoolean(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaSpellBlocking(lua_State* L) +{ + // spell:isBlocking(blockingSolid, blockingCreature) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (lua_gettop(L) == 1) { + pushBoolean(L, spell->getBlockingSolid()); + pushBoolean(L, spell->getBlockingCreature()); + return 2; + } else { + spell->setBlockingSolid(getBoolean(L, 2)); + spell->setBlockingCreature(getBoolean(L, 3)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaSpellAggressive(lua_State* L) +{ + // spell:isAggressive(bool) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (lua_gettop(L) == 1) { + pushBoolean(L, spell->getAggressive()); + } else { + spell->setAggressive(getBoolean(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaSpellVocation(lua_State* L) +{ + // spell:vocation(vocation) + Spell* spell = getUserdata(L, 1); + if (spell) { + if (lua_gettop(L) == 1) { + lua_createtable(L, 0, 0); + auto it = 0; + for (auto voc : spell->getVocMap()) { + ++it; + std::string s = std::to_string(it); + char const *pchar = s.c_str(); + std::string name = g_vocations.getVocation(voc.first)->getVocName(); + setField(L, pchar, name); + } + setMetatable(L, -1, "Spell"); + } else { + int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc + for (int i = 0; i < parameters; ++i) { + if (getString(L, 2 + i).find(";") != std::string::npos) { + std::vector vocList = explodeString(getString(L, 2 + i), ";"); + int32_t vocationId = g_vocations.getVocationId(vocList[0]); + if (vocList.size() > 0) { + if (vocList[1] == "true") { + spell->addVocMap(vocationId, true); + } else { + spell->addVocMap(vocationId, false); + } + } + } else { + int32_t vocationId = g_vocations.getVocationId(getString(L, 2 + i)); + spell->addVocMap(vocationId, false); + } + } + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +// only for InstantSpells +int LuaScriptInterface::luaSpellWords(lua_State* L) +{ + // spell:words(words[, separator = ""]) + InstantSpell* spell = dynamic_cast(getUserdata(L, 1)); + if (spell) { + // if spell != SPELL_INSTANT, it means that this actually is no InstantSpell, so we return nil + if (spell->spellType != SPELL_INSTANT) { + lua_pushnil(L); + return 1; + } + + if (lua_gettop(L) == 1) { + pushString(L, spell->getWords()); + pushString(L, spell->getSeparator()); + return 2; + } else { + std::string sep = ""; + if (lua_gettop(L) == 3) { + sep = getString(L, 3); + } + spell->setWords(getString(L, 2)); + spell->setSeparator(sep); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +// only for InstantSpells +int LuaScriptInterface::luaSpellNeedDirection(lua_State* L) +{ + // spell:needDirection(bool) + InstantSpell* spell = dynamic_cast(getUserdata(L, 1)); + if (spell) { + // if spell != SPELL_INSTANT, it means that this actually is no InstantSpell, so we return nil + if (spell->spellType != SPELL_INSTANT) { + lua_pushnil(L); + return 1; + } + + if (lua_gettop(L) == 1) { + pushBoolean(L, spell->getNeedDirection()); + } else { + spell->setNeedDirection(getBoolean(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +// only for InstantSpells +int LuaScriptInterface::luaSpellHasParams(lua_State* L) +{ + // spell:hasParams(bool) + InstantSpell* spell = dynamic_cast(getUserdata(L, 1)); + if (spell) { + // if spell != SPELL_INSTANT, it means that this actually is no InstantSpell, so we return nil + if (spell->spellType != SPELL_INSTANT) { + lua_pushnil(L); + return 1; + } + + if (lua_gettop(L) == 1) { + pushBoolean(L, spell->getHasParam()); + } else { + spell->setHasParam(getBoolean(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +// only for InstantSpells +int LuaScriptInterface::luaSpellHasPlayerNameParam(lua_State* L) +{ + // spell:hasPlayerNameParam(bool) + InstantSpell* spell = dynamic_cast(getUserdata(L, 1)); + if (spell) { + // if spell != SPELL_INSTANT, it means that this actually is no InstantSpell, so we return nil + if (spell->spellType != SPELL_INSTANT) { + lua_pushnil(L); + return 1; + } + + if (lua_gettop(L) == 1) { + pushBoolean(L, spell->getHasPlayerNameParam()); + } else { + spell->setHasPlayerNameParam(getBoolean(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +// only for InstantSpells +int LuaScriptInterface::luaSpellNeedCasterTargetOrDirection(lua_State* L) +{ + // spell:needCasterTargetOrDirection(bool) + InstantSpell* spell = dynamic_cast(getUserdata(L, 1)); + if (spell) { + // if spell != SPELL_INSTANT, it means that this actually is no InstantSpell, so we return nil + if (spell->spellType != SPELL_INSTANT) { + lua_pushnil(L); + return 1; + } + + if (lua_gettop(L) == 1) { + pushBoolean(L, spell->getNeedCasterTargetOrDirection()); + } else { + spell->setNeedCasterTargetOrDirection(getBoolean(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +// only for InstantSpells +int LuaScriptInterface::luaSpellIsBlockingWalls(lua_State* L) +{ + // spell:blockWalls(bool) + InstantSpell* spell = dynamic_cast(getUserdata(L, 1)); + if (spell) { + // if spell != SPELL_INSTANT, it means that this actually is no InstantSpell, so we return nil + if (spell->spellType != SPELL_INSTANT) { + lua_pushnil(L); + return 1; + } + + if (lua_gettop(L) == 1) { + pushBoolean(L, spell->getBlockWalls()); + } else { + spell->setBlockWalls(getBoolean(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +// only for RuneSpells +int LuaScriptInterface::luaSpellRuneId(lua_State* L) +{ + // spell:runeId(id) + RuneSpell* spell = dynamic_cast(getUserdata(L, 1)); + if (spell) { + // if spell != SPELL_RUNE, it means that this actually is no RuneSpell, so we return nil + if (spell->spellType != SPELL_RUNE) { + lua_pushnil(L); + return 1; + } + + if (lua_gettop(L) == 1) { + lua_pushnumber(L, spell->getRuneItemId()); + } else { + spell->setRuneItemId(getNumber(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +// only for RuneSpells +int LuaScriptInterface::luaSpellCharges(lua_State* L) +{ + // spell:charges(charges) + RuneSpell* spell = dynamic_cast(getUserdata(L, 1)); + if (spell) { + // if spell != SPELL_RUNE, it means that this actually is no RuneSpell, so we return nil + if (spell->spellType != SPELL_RUNE) { + lua_pushnil(L); + return 1; + } + + if (lua_gettop(L) == 1) { + lua_pushnumber(L, spell->getCharges()); + } else { + spell->setCharges(getNumber(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +// only for RuneSpells +int LuaScriptInterface::luaSpellAllowFarUse(lua_State* L) +{ + // spell:allowFarUse(bool) + RuneSpell* spell = dynamic_cast(getUserdata(L, 1)); + if (spell) { + // if spell != SPELL_RUNE, it means that this actually is no RuneSpell, so we return nil + if (spell->spellType != SPELL_RUNE) { + lua_pushnil(L); + return 1; + } + + if (lua_gettop(L) == 1) { + pushBoolean(L, spell->getAllowFarUse()); + } else { + spell->setAllowFarUse(getBoolean(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +// only for RuneSpells +int LuaScriptInterface::luaSpellBlockWalls(lua_State* L) +{ + // spell:blockWalls(bool) + RuneSpell* spell = dynamic_cast(getUserdata(L, 1)); + if (spell) { + // if spell != SPELL_RUNE, it means that this actually is no RuneSpell, so we return nil + if (spell->spellType != SPELL_RUNE) { + lua_pushnil(L); + return 1; + } + + if (lua_gettop(L) == 1) { + pushBoolean(L, spell->getCheckLineOfSight()); + } else { + spell->setCheckLineOfSight(getBoolean(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +// only for RuneSpells +int LuaScriptInterface::luaSpellCheckFloor(lua_State* L) +{ + // spell:checkFloor(bool) + RuneSpell* spell = dynamic_cast(getUserdata(L, 1)); + if (spell) { + // if spell != SPELL_RUNE, it means that this actually is no RuneSpell, so we return nil + if (spell->spellType != SPELL_RUNE) { + lua_pushnil(L); + return 1; + } + + if (lua_gettop(L) == 1) { + pushBoolean(L, spell->getCheckFloor()); + } else { + spell->setCheckFloor(getBoolean(L, 2)); + pushBoolean(L, true); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreateAction(lua_State* L) +{ + // Action() + if (getScriptEnv()->getScriptInterface() != &g_scripts->getScriptInterface()) { + reportErrorFunc("Actions can only be registered in the Scripts interface."); + lua_pushnil(L); + return 1; + } + + Action* action = new Action(getScriptEnv()->getScriptInterface()); + if (action) { + action->fromLua = true; + pushUserdata(L, action); + setMetatable(L, -1, "Action"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaActionOnUse(lua_State* L) +{ + // action:onUse(callback) + Action* action = getUserdata(L, 1); + if (action) { + if (!action->loadCallback()) { + pushBoolean(L, false); + return 1; + } + action->scripted = true; + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaActionRegister(lua_State* L) +{ + // action:register() + Action* action = getUserdata(L, 1); + if (action) { + if (!action->isScripted()) { + pushBoolean(L, false); + return 1; + } + pushBoolean(L, g_actions->registerLuaEvent(action)); + action->getActionIdRange().clear(); + action->getItemIdRange().clear(); + action->getUniqueIdRange().clear(); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaActionItemId(lua_State* L) +{ + // action:id(ids) + Action* action = getUserdata(L, 1); + if (action) { + int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc + if (parameters > 1) { + for (int i = 0; i < parameters; ++i) { + action->addItemId(getNumber(L, 2 + i)); + } + } else { + action->addItemId(getNumber(L, 2)); + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaActionActionId(lua_State* L) +{ + // action:aid(aids) + Action* action = getUserdata(L, 1); + if (action) { + int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc + if (parameters > 1) { + for (int i = 0; i < parameters; ++i) { + action->addActionId(getNumber(L, 2 + i)); + } + } else { + action->addActionId(getNumber(L, 2)); + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaActionUniqueId(lua_State* L) +{ + // action:uid(uids) + Action* action = getUserdata(L, 1); + if (action) { + int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc + if (parameters > 1) { + for (int i = 0; i < parameters; ++i) { + action->addUniqueId(getNumber(L, 2 + i)); + } + } else { + action->addUniqueId(getNumber(L, 2)); + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaActionAllowFarUse(lua_State* L) +{ + // action:allowFarUse(bool) + Action* action = getUserdata(L, 1); + if (action) { + action->setAllowFarUse(getBoolean(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaActionBlockWalls(lua_State* L) +{ + // action:blockWalls(bool) + Action* action = getUserdata(L, 1); + if (action) { + action->setCheckLineOfSight(getBoolean(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaActionCheckFloor(lua_State* L) +{ + // action:checkFloor(bool) + Action* action = getUserdata(L, 1); + if (action) { + action->setCheckFloor(getBoolean(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreateTalkaction(lua_State* L) +{ + // TalkAction(words) + if (getScriptEnv()->getScriptInterface() != &g_scripts->getScriptInterface()) { + reportErrorFunc("TalkActions can only be registered in the Scripts interface."); + lua_pushnil(L); + return 1; + } + + TalkAction* talk = new TalkAction(getScriptEnv()->getScriptInterface()); + if (talk) { + talk->setWords(getString(L, 2)); + talk->fromLua = true; + pushUserdata(L, talk); + setMetatable(L, -1, "TalkAction"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTalkactionOnSay(lua_State* L) +{ + // talkAction:onSay(callback) + TalkAction* talk = getUserdata(L, 1); + if (talk) { + if (!talk->loadCallback()) { + pushBoolean(L, false); + return 1; + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTalkactionRegister(lua_State* L) +{ + // talkAction:register() + TalkAction* talk = getUserdata(L, 1); + if (talk) { + if (!talk->isScripted()) { + pushBoolean(L, false); + return 1; + } + pushBoolean(L, g_talkActions->registerLuaEvent(talk)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaTalkactionSeparator(lua_State* L) +{ + // talkAction:separator(sep) + TalkAction* talk = getUserdata(L, 1); + if (talk) { + talk->setSeparator(getString(L, 2).c_str()); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreateCreatureEvent(lua_State* L) +{ + // CreatureEvent(eventName) + if (getScriptEnv()->getScriptInterface() != &g_scripts->getScriptInterface()) { + reportErrorFunc("CreatureEvents can only be registered in the Scripts interface."); + lua_pushnil(L); + return 1; + } + + CreatureEvent* creature = new CreatureEvent(getScriptEnv()->getScriptInterface()); + if (creature) { + creature->setName(getString(L, 2)); + creature->fromLua = true; + pushUserdata(L, creature); + setMetatable(L, -1, "CreatureEvent"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureEventType(lua_State* L) +{ + // creatureevent:type(callback) + CreatureEvent* creature = getUserdata(L, 1); + if (creature) { + std::string typeName = getString(L, 2); + std::string tmpStr = asLowerCaseString(typeName); + if (tmpStr == "login") { + creature->setEventType(CREATURE_EVENT_LOGIN); + } else if (tmpStr == "logout") { + creature->setEventType(CREATURE_EVENT_LOGOUT); + } else if (tmpStr == "think") { + creature->setEventType(CREATURE_EVENT_THINK); + } else if (tmpStr == "preparedeath") { + creature->setEventType(CREATURE_EVENT_PREPAREDEATH); + } else if (tmpStr == "death") { + creature->setEventType(CREATURE_EVENT_DEATH); + } else if (tmpStr == "kill") { + creature->setEventType(CREATURE_EVENT_KILL); + } else if (tmpStr == "advance") { + creature->setEventType(CREATURE_EVENT_ADVANCE); + } else if (tmpStr == "modalwindow") { + creature->setEventType(CREATURE_EVENT_MODALWINDOW); + } else if (tmpStr == "textedit") { + creature->setEventType(CREATURE_EVENT_TEXTEDIT); + } else if (tmpStr == "healthchange") { + creature->setEventType(CREATURE_EVENT_HEALTHCHANGE); + } else if (tmpStr == "manachange") { + creature->setEventType(CREATURE_EVENT_MANACHANGE); + } else if (tmpStr == "extendedopcode") { + creature->setEventType(CREATURE_EVENT_EXTENDED_OPCODE); + } else { + std::cout << "[Error - CreatureEvent::configureLuaEvent] Invalid type for creature event: " << typeName << std::endl; + pushBoolean(L, false); + } + creature->setLoaded(true); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureEventRegister(lua_State* L) +{ + // creatureevent:register() + CreatureEvent* creature = getUserdata(L, 1); + if (creature) { + if (!creature->isScripted()) { + pushBoolean(L, false); + return 1; + } + pushBoolean(L, g_creatureEvents->registerLuaEvent(creature)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreatureEventOnCallback(lua_State* L) +{ + // creatureevent:onLogin / logout / etc. (callback) + CreatureEvent* creature = getUserdata(L, 1); + if (creature) { + if (!creature->loadCallback()) { + pushBoolean(L, false); + return 1; + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreateMoveEvent(lua_State* L) +{ + // MoveEvent() + if (getScriptEnv()->getScriptInterface() != &g_scripts->getScriptInterface()) { + reportErrorFunc("MoveEvents can only be registered in the Scripts interface."); + lua_pushnil(L); + return 1; + } + + MoveEvent* moveevent = new MoveEvent(getScriptEnv()->getScriptInterface()); + if (moveevent) { + moveevent->fromLua = true; + pushUserdata(L, moveevent); + setMetatable(L, -1, "MoveEvent"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMoveEventType(lua_State* L) +{ + // moveevent:type(callback) + MoveEvent* moveevent = getUserdata(L, 1); + if (moveevent) { + std::string typeName = getString(L, 2); + std::string tmpStr = asLowerCaseString(typeName); + if (tmpStr == "stepin") { + moveevent->setEventType(MOVE_EVENT_STEP_IN); + moveevent->stepFunction = moveevent->StepInField; + } else if (tmpStr == "stepout") { + moveevent->setEventType(MOVE_EVENT_STEP_OUT); + moveevent->stepFunction = moveevent->StepOutField; + } else if (tmpStr == "equip") { + moveevent->setEventType(MOVE_EVENT_EQUIP); + moveevent->equipFunction = moveevent->EquipItem; + } else if (tmpStr == "deequip") { + moveevent->setEventType(MOVE_EVENT_DEEQUIP); + moveevent->equipFunction = moveevent->DeEquipItem; + } else if (tmpStr == "additem") { + moveevent->setEventType(MOVE_EVENT_ADD_ITEM_ITEMTILE); + moveevent->moveFunction = moveevent->AddItemField; + } else if (tmpStr == "removeitem") { + moveevent->setEventType(MOVE_EVENT_REMOVE_ITEM_ITEMTILE); + moveevent->moveFunction = moveevent->RemoveItemField; + } else { + std::cout << "Error: [MoveEvent::configureMoveEvent] No valid event name " << typeName << std::endl; + pushBoolean(L, false); + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMoveEventRegister(lua_State* L) +{ + // moveevent:register() + MoveEvent* moveevent = getUserdata(L, 1); + if (moveevent) { + if (!moveevent->isScripted()) { + pushBoolean(L, g_moveEvents->registerLuaFunction(moveevent)); + return 1; + } + pushBoolean(L, g_moveEvents->registerLuaEvent(moveevent)); + moveevent->getItemIdRange().clear(); + moveevent->getActionIdRange().clear(); + moveevent->getUniqueIdRange().clear(); + moveevent->getPosList().clear(); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMoveEventOnCallback(lua_State* L) +{ + // moveevent:onEquip / deEquip / etc. (callback) + MoveEvent* moveevent = getUserdata(L, 1); + if (moveevent) { + if (!moveevent->loadCallback()) { + pushBoolean(L, false); + return 1; + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMoveEventSlot(lua_State* L) +{ + // moveevent:slot(slot) + MoveEvent* moveevent = getUserdata(L, 1); + if (moveevent) { + if (moveevent->getEventType() == MOVE_EVENT_EQUIP || moveevent->getEventType() == MOVE_EVENT_DEEQUIP) { + if (!moveevent->getSlotName().empty()) { + std::string slotName = getString(L, 2); + std::string tmpStr = asLowerCaseString(slotName); + tmpStr = asLowerCaseString(moveevent->getSlotName()); + if (tmpStr == "head") { + moveevent->setSlot(SLOTP_HEAD); + } else if (tmpStr == "necklace") { + moveevent->setSlot(SLOTP_NECKLACE); + } else if (tmpStr == "backpack") { + moveevent->setSlot(SLOTP_BACKPACK); + } else if (tmpStr == "armor" || tmpStr == "body") { + moveevent->setSlot(SLOTP_ARMOR); + } else if (tmpStr == "right-hand") { + moveevent->setSlot(SLOTP_RIGHT); + } else if (tmpStr == "left-hand") { + moveevent->setSlot(SLOTP_LEFT); + } else if (tmpStr == "hand" || tmpStr == "shield") { + moveevent->setSlot(SLOTP_RIGHT | SLOTP_LEFT); + } else if (tmpStr == "legs") { + moveevent->setSlot(SLOTP_LEGS); + } else if (tmpStr == "feet") { + moveevent->setSlot(SLOTP_FEET); + } else if (tmpStr == "ring") { + moveevent->setSlot(SLOTP_RING); + } else if (tmpStr == "ammo") { + moveevent->setSlot(SLOTP_AMMO); + } else { + std::cout << "[Warning - MoveEvent::configureMoveEvent] Unknown slot type: " << moveevent->getSlotName() << std::endl; + } + } + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMoveEventLevel(lua_State* L) +{ + // moveevent:level(lvl) + MoveEvent* moveevent = getUserdata(L, 1); + if (moveevent) { + moveevent->setRequiredLevel(getNumber(L, 2)); + moveevent->setWieldInfo(WIELDINFO_LEVEL); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMoveEventMagLevel(lua_State* L) +{ + // moveevent:magicLevel(lvl) + MoveEvent* moveevent = getUserdata(L, 1); + if (moveevent) { + moveevent->setRequiredMagLevel(getNumber(L, 2)); + moveevent->setWieldInfo(WIELDINFO_MAGLV); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMoveEventPremium(lua_State* L) +{ + // moveevent:premium(bool) + MoveEvent* moveevent = getUserdata(L, 1); + if (moveevent) { + moveevent->setNeedPremium(getBoolean(L, 2)); + moveevent->setWieldInfo(WIELDINFO_PREMIUM); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMoveEventVocation(lua_State* L) +{ + // moveevent:vocation(vocName[, showInDescription = false, lastVoc = false]) + MoveEvent* moveevent = getUserdata(L, 1); + if (moveevent) { + moveevent->addVocEquipMap(getString(L, 2)); + moveevent->setWieldInfo(WIELDINFO_VOCREQ); + std::string tmp; + bool showInDescription = false; + bool lastVoc = false; + if (getBoolean(L, 3)) { + showInDescription = getBoolean(L, 3); + } + if (getBoolean(L, 4)) { + lastVoc = getBoolean(L, 4); + } + if (showInDescription) { + if (moveevent->getVocationString().empty()) { + tmp = asLowerCaseString(getString(L, 2)); + tmp += "s"; + moveevent->setVocationString(tmp); + } else { + tmp = moveevent->getVocationString(); + if (lastVoc) { + tmp += " and "; + } else { + tmp += ", "; + } + tmp += asLowerCaseString(getString(L, 2)); + tmp += "s"; + moveevent->setVocationString(tmp); + } + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMoveEventItemId(lua_State* L) +{ + // moveevent:id(ids) + MoveEvent* moveevent = getUserdata(L, 1); + if (moveevent) { + int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc + if (parameters > 1) { + for (int i = 0; i < parameters; ++i) { + moveevent->addItemId(getNumber(L, 2 + i)); + } + } else { + moveevent->addItemId(getNumber(L, 2)); + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMoveEventActionId(lua_State* L) +{ + // moveevent:aid(ids) + MoveEvent* moveevent = getUserdata(L, 1); + if (moveevent) { + int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc + if (parameters > 1) { + for (int i = 0; i < parameters; ++i) { + moveevent->addActionId(getNumber(L, 2 + i)); + } + } else { + moveevent->addActionId(getNumber(L, 2)); + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMoveEventUniqueId(lua_State* L) +{ + // moveevent:uid(ids) + MoveEvent* moveevent = getUserdata(L, 1); + if (moveevent) { + int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc + if (parameters > 1) { + for (int i = 0; i < parameters; ++i) { + moveevent->addUniqueId(getNumber(L, 2 + i)); + } + } else { + moveevent->addUniqueId(getNumber(L, 2)); + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaMoveEventPosition(lua_State* L) +{ + // moveevent:position(positions) + MoveEvent* moveevent = getUserdata(L, 1); + if (moveevent) { + int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc + if (parameters > 1) { + for (int i = 0; i < parameters; ++i) { + moveevent->addPosList(getPosition(L, 2 + i)); + } + } else { + moveevent->addPosList(getPosition(L, 2)); + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaCreateGlobalEvent(lua_State* L) +{ + // GlobalEvent(eventName) + if (getScriptEnv()->getScriptInterface() != &g_scripts->getScriptInterface()) { + reportErrorFunc("GlobalEvents can only be registered in the Scripts interface."); + lua_pushnil(L); + return 1; + } + + GlobalEvent* global = new GlobalEvent(getScriptEnv()->getScriptInterface()); + if (global) { + global->setName(getString(L, 2)); + global->setEventType(GLOBALEVENT_NONE); + global->fromLua = true; + pushUserdata(L, global); + setMetatable(L, -1, "GlobalEvent"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGlobalEventType(lua_State* L) +{ + // globalevent:type(callback) + GlobalEvent* global = getUserdata(L, 1); + if (global) { + std::string typeName = getString(L, 2); + std::string tmpStr = asLowerCaseString(typeName); + if (tmpStr == "startup") { + global->setEventType(GLOBALEVENT_STARTUP); + } else if (tmpStr == "shutdown") { + global->setEventType(GLOBALEVENT_SHUTDOWN); + } else if (tmpStr == "record") { + global->setEventType(GLOBALEVENT_RECORD); + } else { + std::cout << "[Error - CreatureEvent::configureLuaEvent] Invalid type for global event: " << typeName << std::endl; + pushBoolean(L, false); + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGlobalEventRegister(lua_State* L) +{ + // globalevent:register() + GlobalEvent* globalevent = getUserdata(L, 1); + if (globalevent) { + if (!globalevent->isScripted()) { + pushBoolean(L, false); + return 1; + } + pushBoolean(L, g_globalEvents->registerLuaEvent(globalevent)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGlobalEventOnCallback(lua_State* L) +{ + // globalevent:onThink / record / etc. (callback) + GlobalEvent* globalevent = getUserdata(L, 1); + if (globalevent) { + if (!globalevent->loadCallback()) { + pushBoolean(L, false); + return 1; + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGlobalEventTime(lua_State* L) +{ + // globalevent:time(time) + GlobalEvent* globalevent = getUserdata(L, 1); + if (globalevent) { + std::string timer = getString(L, 2); + std::vector params = vectorAtoi(explodeString(timer, ":")); + + int32_t hour = params.front(); + if (hour < 0 || hour > 23) { + std::cout << "[Error - GlobalEvent::configureEvent] Invalid hour \"" << timer << "\" for globalevent with name: " << globalevent->getName() << std::endl; + pushBoolean(L, false); + return 1; + } + + globalevent->setInterval(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 \"" << timer << "\" for globalevent with name: " << globalevent->getName() << std::endl; + pushBoolean(L, false); + return 1; + } + + if (params.size() > 2) { + sec = params[2]; + if (sec < 0 || sec > 59) { + std::cout << "[Error - GlobalEvent::configureEvent] Invalid second \"" << timer << "\" for globalevent with name: " << globalevent->getName() << std::endl; + pushBoolean(L, false); + return 1; + } + } + } + + time_t current_time = time(nullptr); + tm* timeinfo = localtime(¤t_time); + timeinfo->tm_hour = hour; + timeinfo->tm_min = min; + timeinfo->tm_sec = sec; + + time_t difference = static_cast(difftime(mktime(timeinfo), current_time)); + if (difference < 0) { + difference += 86400; + } + + globalevent->setNextExecution(current_time + difference); + globalevent->setEventType(GLOBALEVENT_TIMER); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaGlobalEventInterval(lua_State* L) +{ + // globalevent:interval(interval) + GlobalEvent* globalevent = getUserdata(L, 1); + if (globalevent) { + globalevent->setInterval(getNumber(L, 2)); + globalevent->setNextExecution(OTSYS_TIME() + getNumber(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +// Weapon +int LuaScriptInterface::luaCreateWeapon(lua_State* L) +{ + // Weapon(type) + if (getScriptEnv()->getScriptInterface() != &g_scripts->getScriptInterface()) { + reportErrorFunc("Weapons can only be registered in the Scripts interface."); + lua_pushnil(L); + return 1; + } + + WeaponType_t type = getNumber(L, 2); + switch (type) { + case WEAPON_SWORD: + case WEAPON_AXE: + case WEAPON_CLUB: { + WeaponMelee* weapon = new WeaponMelee(getScriptEnv()->getScriptInterface()); + if (weapon) { + pushUserdata(L, weapon); + setMetatable(L, -1, "Weapon"); + weapon->weaponType = type; + } else { + lua_pushnil(L); + } + break; + } + case WEAPON_DISTANCE: + case WEAPON_AMMO: { + WeaponDistance* weapon = new WeaponDistance(getScriptEnv()->getScriptInterface()); + if (weapon) { + pushUserdata(L, weapon); + setMetatable(L, -1, "Weapon"); + weapon->weaponType = type; + } else { + lua_pushnil(L); + } + break; + } + case WEAPON_WAND: { + WeaponWand* weapon = new WeaponWand(getScriptEnv()->getScriptInterface()); + if (weapon) { + pushUserdata(L, weapon); + setMetatable(L, -1, "Weapon"); + weapon->weaponType = type; + } else { + lua_pushnil(L); + } + break; + } + default: { + lua_pushnil(L); + break; + } + } + return 1; +} + +int LuaScriptInterface::luaWeaponAction(lua_State* L) +{ + // weapon:action(callback) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + std::string typeName = getString(L, 2); + std::string tmpStr = asLowerCaseString(typeName); + if (tmpStr == "removecount") { + weapon->action = WEAPONACTION_REMOVECOUNT; + } else if (tmpStr == "removecharge") { + weapon->action = WEAPONACTION_REMOVECHARGE; + } else if (tmpStr == "move") { + weapon->action = WEAPONACTION_MOVE; + } else { + std::cout << "Error: [Weapon::action] No valid action " << typeName << std::endl; + pushBoolean(L, false); + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponRegister(lua_State* L) +{ + // weapon:register() + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + if (weapon->weaponType == WEAPON_DISTANCE || weapon->weaponType == WEAPON_AMMO) { + weapon = getUserdata(L, 1); + } else if (weapon->weaponType == WEAPON_WAND) { + weapon = getUserdata(L, 1); + } else { + weapon = getUserdata(L, 1); + } + + uint16_t id = weapon->getID(); + ItemType& it = Item::items.getItemType(id); + it.weaponType = weapon->weaponType; + + if (weapon->getWieldInfo() != 0) { + it.wieldInfo = weapon->getWieldInfo(); + it.vocationString = weapon->getVocationString(); + it.minReqLevel = weapon->getReqLevel(); + it.minReqMagicLevel = weapon->getReqMagLv(); + } + + weapon->configureWeapon(it); + pushBoolean(L, g_weapons->registerLuaEvent(weapon)); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponOnUseWeapon(lua_State* L) +{ + // weapon:onUseWeapon(callback) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + if (!weapon->loadCallback()) { + pushBoolean(L, false); + return 1; + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponUnproperly(lua_State* L) +{ + // weapon:wieldedUnproperly(bool) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + weapon->setWieldUnproperly(getBoolean(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponLevel(lua_State* L) +{ + // weapon:level(lvl) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + weapon->setRequiredLevel(getNumber(L, 2)); + weapon->setWieldInfo(WIELDINFO_LEVEL); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponMagicLevel(lua_State* L) +{ + // weapon:magicLevel(lvl) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + weapon->setRequiredMagLevel(getNumber(L, 2)); + weapon->setWieldInfo(WIELDINFO_MAGLV); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponMana(lua_State* L) +{ + // weapon:mana(mana) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + weapon->setMana(getNumber(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponManaPercent(lua_State* L) +{ + // weapon:manaPercent(percent) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + weapon->setManaPercent(getNumber(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponHealth(lua_State* L) +{ + // weapon:health(health) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + weapon->setHealth(getNumber(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponHealthPercent(lua_State* L) +{ + // weapon:healthPercent(percent) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + weapon->setHealthPercent(getNumber(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponSoul(lua_State* L) +{ + // weapon:soul(soul) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + weapon->setSoul(getNumber(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponBreakChance(lua_State* L) +{ + // weapon:breakChance(percent) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + weapon->setBreakChance(getNumber(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponWandDamage(lua_State* L) +{ + // weapon:damage(damage[min, max]) only use this if the weapon is a wand! + WeaponWand* weapon = getUserdata(L, 1); + if (weapon) { + weapon->setMinChange(getNumber(L, 2)); + if (lua_gettop(L) > 2) { + weapon->setMaxChange(getNumber(L, 3)); + } else { + weapon->setMaxChange(getNumber(L, 2)); + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponElement(lua_State* L) +{ + // weapon:element(combatType) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + if (!getNumber(L, 2)) { + std::string element = getString(L, 2); + std::string tmpStrValue = asLowerCaseString(element); + if (tmpStrValue == "earth") { + weapon->params.combatType = COMBAT_EARTHDAMAGE; + } else if (tmpStrValue == "ice") { + weapon->params.combatType = COMBAT_ICEDAMAGE; + } else if (tmpStrValue == "energy") { + weapon->params.combatType = COMBAT_ENERGYDAMAGE; + } else if (tmpStrValue == "fire") { + weapon->params.combatType = COMBAT_FIREDAMAGE; + } else if (tmpStrValue == "death") { + weapon->params.combatType = COMBAT_DEATHDAMAGE; + } else if (tmpStrValue == "holy") { + weapon->params.combatType = COMBAT_HOLYDAMAGE; + } else { + std::cout << "[Warning - weapon:element] Type \"" << element << "\" does not exist." << std::endl; + } + } else { + weapon->params.combatType = getNumber(L, 2); + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponPremium(lua_State* L) +{ + // weapon:premium(bool) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + weapon->setNeedPremium(getBoolean(L, 2)); + weapon->setWieldInfo(WIELDINFO_PREMIUM); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponVocation(lua_State* L) +{ + // weapon:vocation(vocName[, showInDescription = false, lastVoc = false]) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + weapon->addVocWeaponMap(getString(L, 2)); + weapon->setWieldInfo(WIELDINFO_VOCREQ); + std::string tmp; + bool showInDescription = getBoolean(L, 3, false); + bool lastVoc = getBoolean(L, 4, false); + + if (showInDescription) { + if (weapon->getVocationString().empty()) { + tmp = asLowerCaseString(getString(L, 2)); + tmp += "s"; + weapon->setVocationString(tmp); + } else { + tmp = weapon->getVocationString(); + if (lastVoc) { + tmp += " and "; + } else { + tmp += ", "; + } + tmp += asLowerCaseString(getString(L, 2)); + tmp += "s"; + weapon->setVocationString(tmp); + } + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponId(lua_State* L) +{ + // weapon:id(id) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + weapon->setID(getNumber(L, 2)); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponAttack(lua_State* L) +{ + // weapon:attack(atk) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + uint16_t id = weapon->getID(); + ItemType& it = Item::items.getItemType(id); + it.attack = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponDefense(lua_State* L) +{ + // weapon:defense(defense[, extraDefense]) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + uint16_t id = weapon->getID(); + ItemType& it = Item::items.getItemType(id); + it.defense = getNumber(L, 2); + if (lua_gettop(L) > 2) { + it.extraDefense = getNumber(L, 3); + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponRange(lua_State* L) +{ + // weapon:range(range) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + uint16_t id = weapon->getID(); + ItemType& it = Item::items.getItemType(id); + it.shootRange = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponCharges(lua_State* L) +{ + // weapon:charges(charges[, showCharges = true]) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + bool showCharges = getBoolean(L, 3, true); + uint16_t id = weapon->getID(); + ItemType& it = Item::items.getItemType(id); + + it.charges = getNumber(L, 2); + it.showCharges = showCharges; + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponDuration(lua_State* L) +{ + // weapon:duration(duration[, showDuration = true]) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + bool showDuration = getBoolean(L, 3, true); + uint16_t id = weapon->getID(); + ItemType& it = Item::items.getItemType(id); + + it.decayTime = getNumber(L, 2); + it.showDuration = showDuration; + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponDecayTo(lua_State* L) +{ + // weapon:decayTo([itemid = 0] + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + uint16_t itemid = getNumber(L, 2, 0); + uint16_t id = weapon->getID(); + ItemType& it = Item::items.getItemType(id); + + it.decayTo = itemid; + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponTransformEquipTo(lua_State* L) +{ + // weapon:transformEquipTo(itemid) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + uint16_t id = weapon->getID(); + ItemType& it = Item::items.getItemType(id); + it.transformEquipTo = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponTransformDeEquipTo(lua_State* L) +{ + // weapon:transformDeEquipTo(itemid) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + uint16_t id = weapon->getID(); + ItemType& it = Item::items.getItemType(id); + it.transformDeEquipTo = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponShootType(lua_State* L) +{ + // weapon:shootType(type) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + uint16_t id = weapon->getID(); + ItemType& it = Item::items.getItemType(id); + it.shootType = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponSlotType(lua_State* L) +{ + // weapon:slotType(slot) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + uint16_t id = weapon->getID(); + ItemType& it = Item::items.getItemType(id); + std::string slot = getString(L, 2); + + if (slot == "two-handed") { + it.slotPosition |= SLOTP_TWO_HAND; + } else { + it.slotPosition |= SLOTP_HAND; + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponAmmoType(lua_State* L) +{ + // weapon:ammoType(type) + WeaponDistance* weapon = getUserdata(L, 1); + if (weapon) { + uint16_t id = weapon->getID(); + ItemType& it = Item::items.getItemType(id); + std::string type = getString(L, 2); + + if (type == "arrow") { + it.ammoType = AMMO_ARROW; + } else if (type == "bolt"){ + it.ammoType = AMMO_BOLT; + } else { + std::cout << "[Warning - weapon:ammoType] Type \"" << type << "\" does not exist." << std::endl; + lua_pushnil(L); + return 1; + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponHitChance(lua_State* L) +{ + // weapon:hitChance(chance) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + uint16_t id = weapon->getID(); + ItemType& it = Item::items.getItemType(id); + it.hitChance = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponMaxHitChance(lua_State* L) +{ + // weapon:maxHitChance(max) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + uint16_t id = weapon->getID(); + ItemType& it = Item::items.getItemType(id); + it.maxHitChance = getNumber(L, 2); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaWeaponExtraElement(lua_State* L) +{ + // weapon:extraElement(atk, combatType) + Weapon* weapon = getUserdata(L, 1); + if (weapon) { + uint16_t id = weapon->getID(); + ItemType& it = Item::items.getItemType(id); + it.abilities.get()->elementDamage = getNumber(L, 2); + + if (!getNumber(L, 3)) { + std::string element = getString(L, 3); + std::string tmpStrValue = asLowerCaseString(element); + if (tmpStrValue == "earth") { + it.abilities.get()->elementType = COMBAT_EARTHDAMAGE; + } else if (tmpStrValue == "ice") { + it.abilities.get()->elementType = COMBAT_ICEDAMAGE; + } else if (tmpStrValue == "energy") { + it.abilities.get()->elementType = COMBAT_ENERGYDAMAGE; + } else if (tmpStrValue == "fire") { + it.abilities.get()->elementType = COMBAT_FIREDAMAGE; + } else if (tmpStrValue == "death") { + it.abilities.get()->elementType = COMBAT_DEATHDAMAGE; + } else if (tmpStrValue == "holy") { + it.abilities.get()->elementType = COMBAT_HOLYDAMAGE; + } else { + std::cout << "[Warning - weapon:extraElement] Type \"" << element << "\" does not exist." << std::endl; + } + } else { + it.abilities.get()->elementType = getNumber(L, 3); + } + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + // LuaEnvironment::LuaEnvironment() : LuaScriptInterface("Main Interface") {} diff --git a/src/luascript.h b/src/luascript.h index 8fd8a1e..b2644cb 100644 --- a/src/luascript.h +++ b/src/luascript.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,7 +20,11 @@ #ifndef FS_LUASCRIPT_H_5344B2BC907E46E3943EA78574A212D8 #define FS_LUASCRIPT_H_5344B2BC907E46E3943EA78574A212D8 +#if __has_include("luajit/lua.hpp") #include +#else +#include +#endif #if LUA_VERSION_NUM >= 502 #ifndef LUA_COMPAT_ALL @@ -35,6 +39,7 @@ #include "database.h" #include "enums.h" #include "position.h" +#include class Thing; class Creature; @@ -46,6 +51,7 @@ class Combat; class Condition; class Npc; class Monster; +class InstantSpell; enum { EVENT_ID_LOADING = 1, @@ -93,7 +99,8 @@ struct LuaTimerEventDesc { class LuaScriptInterface; class Cylinder; class Game; -class Npc; + +struct LootBlock; class ScriptEnvironment { @@ -148,9 +155,9 @@ class ScriptEnvironment void removeItemByUID(uint32_t uid); private: - typedef std::vector VariantVector; - typedef std::map StorageMap; - typedef std::map DBResultMap; + using VariantVector = std::vector; + using StorageMap = std::map; + using DBResultMap = std::map; LuaScriptInterface* interface; @@ -209,6 +216,7 @@ class LuaScriptInterface const std::string& getFileById(int32_t scriptId); int32_t getEvent(const std::string& eventName); + int32_t getEvent(); int32_t getMetaEvent(const std::string& globalName, const std::string& eventName); static ScriptEnvironment* getScriptEnv() { @@ -271,13 +279,13 @@ class LuaScriptInterface // Get template - inline static typename std::enable_if::value, T>::type + static typename std::enable_if::value, T>::type getNumber(lua_State* L, int32_t arg) { return static_cast(static_cast(lua_tonumber(L, arg))); } template - inline static typename std::enable_if::value || std::is_floating_point::value, T>::type + static typename std::enable_if::value || std::is_floating_point::value, T>::type getNumber(lua_State* L, int32_t arg) { return static_cast(lua_tonumber(L, arg)); @@ -301,16 +309,16 @@ class LuaScriptInterface return *userdata; } template - inline static T** getRawUserdata(lua_State* L, int32_t arg) + static T** getRawUserdata(lua_State* L, int32_t arg) { return static_cast(lua_touserdata(L, arg)); } - inline static bool getBoolean(lua_State* L, int32_t arg) + static bool getBoolean(lua_State* L, int32_t arg) { return lua_toboolean(L, arg) != 0; } - inline static bool getBoolean(lua_State* L, int32_t arg, bool defaultValue) + static bool getBoolean(lua_State* L, int32_t arg, bool defaultValue) { const auto parameters = lua_gettop(L); if (parameters == 0 || arg > parameters) { @@ -320,11 +328,11 @@ class LuaScriptInterface } static std::string getString(lua_State* L, int32_t arg); - static CombatDamage getCombatDamage(lua_State* L); static Position getPosition(lua_State* L, int32_t arg, int32_t& stackpos); static Position getPosition(lua_State* L, int32_t arg); static Outfit_t getOutfit(lua_State* L, int32_t arg); static LuaVariant getVariant(lua_State* L, int32_t arg); + static InstantSpell* getInstantSpell(lua_State* L, int32_t arg); static Thing* getThing(lua_State* L, int32_t arg); static Creature* getCreature(lua_State* L, int32_t arg); @@ -342,27 +350,27 @@ class LuaScriptInterface static LuaDataType getUserdataType(lua_State* L, int32_t arg); // Is - inline static bool isNumber(lua_State* L, int32_t arg) + static bool isNumber(lua_State* L, int32_t arg) { return lua_type(L, arg) == LUA_TNUMBER; } - inline static bool isString(lua_State* L, int32_t arg) + static bool isString(lua_State* L, int32_t arg) { return lua_isstring(L, arg) != 0; } - inline static bool isBoolean(lua_State* L, int32_t arg) + static bool isBoolean(lua_State* L, int32_t arg) { return lua_isboolean(L, arg); } - inline static bool isTable(lua_State* L, int32_t arg) + static bool isTable(lua_State* L, int32_t arg) { return lua_istable(L, arg); } - inline static bool isFunction(lua_State* L, int32_t arg) + static bool isFunction(lua_State* L, int32_t arg) { return lua_isfunction(L, arg); } - inline static bool isUserdata(lua_State* L, int32_t arg) + static bool isUserdata(lua_State* L, int32_t arg) { return lua_isuserdata(L, arg) != 0; } @@ -370,17 +378,19 @@ class LuaScriptInterface // Push static void pushBoolean(lua_State* L, bool value); static void pushCombatDamage(lua_State* L, const CombatDamage& damage); + static void pushInstantSpell(lua_State* L, const InstantSpell& spell); static void pushPosition(lua_State* L, const Position& position, int32_t stackpos = 0); static void pushOutfit(lua_State* L, const Outfit_t& outfit); + static void pushLoot(lua_State* L, const std::vector& lootList); // - inline static void setField(lua_State* L, const char* index, lua_Number value) + static void setField(lua_State* L, const char* index, lua_Number value) { lua_pushnumber(L, value); lua_setfield(L, -2, index); } - inline static void setField(lua_State* L, const char* index, const std::string& value) + static void setField(lua_State* L, const char* index, const std::string& value) { pushString(L, value); lua_setfield(L, -2, index); @@ -402,9 +412,21 @@ class LuaScriptInterface void registerFunctions(); + void registerMethod(const std::string& globalName, const std::string& methodName, lua_CFunction func); + + static std::string getErrorDesc(ErrorCode_t code); + + lua_State* luaState = nullptr; + + int32_t eventTableRef = -1; + int32_t runningEventId = EVENT_ID_USER; + + //script file cache + std::map cacheFiles; + + private: void registerClass(const std::string& className, const std::string& baseClass, lua_CFunction newFunction = nullptr); void registerTable(const std::string& tableName); - void registerMethod(const std::string& className, const std::string& methodName, lua_CFunction func); void registerMetaMethod(const std::string& className, const std::string& methodName, lua_CFunction func); void registerGlobalMethod(const std::string& functionName, lua_CFunction func); void registerVariable(const std::string& tableName, const std::string& name, lua_Number value); @@ -413,28 +435,16 @@ class LuaScriptInterface std::string getStackTrace(const std::string& error_desc); - static std::string getErrorDesc(ErrorCode_t code); static bool getArea(lua_State* L, std::list& list, uint32_t& rows); //lua functions - static int luaDoCreateItem(lua_State* L); - static int luaDoCreateItemEx(lua_State* L); - static int luaDoMoveCreature(lua_State* L); - static int luaDoPlayerAddItem(lua_State* L); - static int luaDoTileAddItemEx(lua_State* L); static int luaDoSetCreatureLight(lua_State* L); //get item info static int luaGetDepotId(lua_State* L); - //get creature info functions - static int luaGetPlayerFlagValue(lua_State* L); - static int luaGetCreatureCondition(lua_State* L); - - static int luaGetPlayerInstantSpellInfo(lua_State* L); - static int luaGetPlayerInstantSpellCount(lua_State* L); - + //get world info static int luaGetWorldTime(lua_State* L); static int luaGetWorldLight(lua_State* L); static int luaGetWorldUpTime(lua_State* L); @@ -465,12 +475,7 @@ class LuaScriptInterface static int luaDoChallengeCreature(lua_State* L); - static int luaSetCreatureOutfit(lua_State* L); - static int luaSetMonsterOutfit(lua_State* L); - static int luaSetItemOutfit(lua_State* L); - static int luaDebugPrint(lua_State* L); - static int luaIsInArray(lua_State* L); static int luaAddEvent(lua_State* L); static int luaStopEvent(lua_State* L); @@ -481,6 +486,9 @@ class LuaScriptInterface static int luaGetWaypointPositionByName(lua_State* L); + static int luaSendChannelMessage(lua_State* L); + static int luaSendGuildChannelMessage(lua_State* L); + #ifndef LUAJIT_VERSION static int luaBitNot(lua_State* L); static int luaBitAnd(lua_State* L); @@ -548,9 +556,12 @@ class LuaScriptInterface static int luaGameCreateMonster(lua_State* L); static int luaGameCreateNpc(lua_State* L); static int luaGameCreateTile(lua_State* L); + static int luaGameCreateMonsterType(lua_State* L); static int luaGameStartRaid(lua_State* L); + static int luaGameGetClientVersion(lua_State* L); + static int luaGameReload(lua_State* L); // Variant @@ -571,7 +582,6 @@ class LuaScriptInterface static int luaPositionSendMagicEffect(lua_State* L); static int luaPositionSendDistanceEffect(lua_State* L); - static int luaPositionSendMonsterSay(lua_State* L); // Tile static int luaTileCreate(lua_State* L); @@ -610,6 +620,8 @@ class LuaScriptInterface static int luaTileGetThingIndex(lua_State* L); static int luaTileQueryAdd(lua_State* L); + static int luaTileAddItem(lua_State* L); + static int luaTileAddItemEx(lua_State* L); static int luaTileGetHouse(lua_State* L); @@ -638,6 +650,34 @@ class LuaScriptInterface static int luaNetworkMessageSkipBytes(lua_State* L); static int luaNetworkMessageSendToPlayer(lua_State* L); + // ModalWindow + static int luaModalWindowCreate(lua_State* L); + static int luaModalWindowDelete(lua_State* L); + + static int luaModalWindowGetId(lua_State* L); + static int luaModalWindowGetTitle(lua_State* L); + static int luaModalWindowGetMessage(lua_State* L); + + static int luaModalWindowSetTitle(lua_State* L); + static int luaModalWindowSetMessage(lua_State* L); + + static int luaModalWindowGetButtonCount(lua_State* L); + static int luaModalWindowGetChoiceCount(lua_State* L); + + static int luaModalWindowAddButton(lua_State* L); + static int luaModalWindowAddChoice(lua_State* L); + + static int luaModalWindowGetDefaultEnterButton(lua_State* L); + static int luaModalWindowSetDefaultEnterButton(lua_State* L); + + static int luaModalWindowGetDefaultEscapeButton(lua_State* L); + static int luaModalWindowSetDefaultEscapeButton(lua_State* L); + + static int luaModalWindowHasPriority(lua_State* L); + static int luaModalWindowSetPriority(lua_State* L); + + static int luaModalWindowSendToPlayer(lua_State* L); + // Item static int luaItemCreate(lua_State* L); @@ -652,11 +692,9 @@ class LuaScriptInterface static int luaItemSplit(lua_State* L); static int luaItemRemove(lua_State* L); - static int luaItemGetMovementId(lua_State* L); - static int luaItemSetMovementId(lua_State* L); + static int luaItemGetUniqueId(lua_State* L); static int luaItemGetActionId(lua_State* L); static int luaItemSetActionId(lua_State* L); - static int luaItemGetUniqueId(lua_State* L); static int luaItemGetCount(lua_State* L); static int luaItemGetCharges(lua_State* L); @@ -676,6 +714,9 @@ class LuaScriptInterface static int luaItemGetAttribute(lua_State* L); static int luaItemSetAttribute(lua_State* L); static int luaItemRemoveAttribute(lua_State* L); + static int luaItemGetCustomAttribute(lua_State* L); + static int luaItemSetCustomAttribute(lua_State* L); + static int luaItemRemoveCustomAttribute(lua_State* L); static int luaItemMoveTo(lua_State* L); static int luaItemTransform(lua_State* L); @@ -684,6 +725,7 @@ class LuaScriptInterface static int luaItemGetDescription(lua_State* L); static int luaItemHasProperty(lua_State* L); + static int luaItemIsLoadedFromMap(lua_State* L); // Container static int luaContainerCreate(lua_State* L); @@ -691,7 +733,7 @@ class LuaScriptInterface static int luaContainerGetSize(lua_State* L); static int luaContainerGetCapacity(lua_State* L); static int luaContainerGetEmptySlots(lua_State* L); - + static int luaContainerGetContentDescription(lua_State* L); static int luaContainerGetItemHoldingCount(lua_State* L); static int luaContainerGetItemCountById(lua_State* L); @@ -699,7 +741,8 @@ class LuaScriptInterface static int luaContainerHasItem(lua_State* L); static int luaContainerAddItem(lua_State* L); static int luaContainerAddItemEx(lua_State* L); - + static int luaContainerGetCorpseOwner(lua_State* L); + // Teleport static int luaTeleportCreate(lua_State* L); @@ -716,6 +759,8 @@ class LuaScriptInterface static int luaCreatureIsRemoved(lua_State* L); static int luaCreatureIsCreature(lua_State* L); static int luaCreatureIsInGhostMode(lua_State* L); + static int luaCreatureIsHealthHidden(lua_State* L); + static int luaCreatureIsImmune(lua_State* L); static int luaCreatureCanSee(lua_State* L); static int luaCreatureCanSeeCreature(lua_State* L); @@ -742,6 +787,7 @@ class LuaScriptInterface static int luaCreatureChangeSpeed(lua_State* L); static int luaCreatureSetDropLoot(lua_State* L); + static int luaCreatureSetSkillLoss(lua_State* L); static int luaCreatureGetPosition(lua_State* L); static int luaCreatureGetTile(lua_State* L); @@ -749,6 +795,7 @@ class LuaScriptInterface static int luaCreatureSetDirection(lua_State* L); static int luaCreatureGetHealth(lua_State* L); + static int luaCreatureSetHealth(lua_State* L); static int luaCreatureAddHealth(lua_State* L); static int luaCreatureGetMaxHealth(lua_State* L); static int luaCreatureSetMaxHealth(lua_State* L); @@ -763,6 +810,7 @@ class LuaScriptInterface static int luaCreatureGetCondition(lua_State* L); static int luaCreatureAddCondition(lua_State* L); static int luaCreatureRemoveCondition(lua_State* L); + static int luaCreatureHasCondition(lua_State* L); static int luaCreatureRemove(lua_State* L); static int luaCreatureTeleportTo(lua_State* L); @@ -775,6 +823,9 @@ class LuaScriptInterface static int luaCreatureGetDescription(lua_State* L); static int luaCreatureGetPathTo(lua_State* L); + static int luaCreatureMove(lua_State* L); + + static int luaCreatureGetZone(lua_State* L); // Player static int luaPlayerCreate(lua_State* L); @@ -786,7 +837,6 @@ class LuaScriptInterface static int luaPlayerGetAccountId(lua_State* L); static int luaPlayerGetLastLoginSaved(lua_State* L); static int luaPlayerGetLastLogout(lua_State* L); - static int luaPlayerHasFlag(lua_State* L); static int luaPlayerGetAccountType(lua_State* L); static int luaPlayerSetAccountType(lua_State* L); @@ -797,10 +847,10 @@ class LuaScriptInterface static int luaPlayerGetFreeCapacity(lua_State* L); static int luaPlayerGetDepotChest(lua_State* L); + static int luaPlayerGetInbox(lua_State* L); - static int luaPlayerGetMurderTimestamps(lua_State* L); - static int luaPlayerGetPlayerKillerEnd(lua_State* L); - static int luaPlayerSetPlayerKillerEnd(lua_State* L); + static int luaPlayerGetSkullTime(lua_State* L); + static int luaPlayerSetSkullTime(lua_State* L); static int luaPlayerGetDeathPenalty(lua_State* L); static int luaPlayerGetExperience(lua_State* L); @@ -825,6 +875,17 @@ class LuaScriptInterface static int luaPlayerGetSkillPercent(lua_State* L); static int luaPlayerGetSkillTries(lua_State* L); static int luaPlayerAddSkillTries(lua_State* L); + static int luaPlayerGetSpecialSkill(lua_State* L); + static int luaPlayerAddSpecialSkill(lua_State* L); + + static int luaPlayerAddOfflineTrainingTime(lua_State* L); + static int luaPlayerGetOfflineTrainingTime(lua_State* L); + static int luaPlayerRemoveOfflineTrainingTime(lua_State* L); + + static int luaPlayerAddOfflineTrainingTries(lua_State* L); + + static int luaPlayerGetOfflineTrainingSkill(lua_State* L); + static int luaPlayerSetOfflineTrainingSkill(lua_State* L); static int luaPlayerGetItemCount(lua_State* L); static int luaPlayerGetItemById(lua_State* L); @@ -874,6 +935,7 @@ class LuaScriptInterface static int luaPlayerShowTextDialog(lua_State* L); static int luaPlayerSendTextMessage(lua_State* L); + static int luaPlayerSendChannelMessage(lua_State* L); static int luaPlayerSendPrivateMessage(lua_State* L); static int luaPlayerChannelSay(lua_State* L); @@ -890,6 +952,10 @@ class LuaScriptInterface static int luaPlayerHasOutfit(lua_State* L); static int luaPlayerSendOutfitWindow(lua_State* L); + static int luaPlayerAddMount(lua_State* L); + static int luaPlayerRemoveMount(lua_State* L); + static int luaPlayerHasMount(lua_State* L); + static int luaPlayerGetPremiumDays(lua_State* L); static int luaPlayerAddPremiumDays(lua_State* L); static int luaPlayerRemovePremiumDays(lua_State* L); @@ -903,12 +969,19 @@ class LuaScriptInterface static int luaPlayerForgetSpell(lua_State* L); static int luaPlayerHasLearnedSpell(lua_State* L); + static int luaPlayerSendTutorial(lua_State* L); + static int luaPlayerAddMapMark(lua_State* L); + static int luaPlayerSave(lua_State* L); + static int luaPlayerPopupFYI(lua_State* L); static int luaPlayerIsPzLocked(lua_State* L); static int luaPlayerGetClient(lua_State* L); + static int luaPlayerGetHouse(lua_State* L); + static int luaPlayerSendHouseWindow(lua_State* L); + static int luaPlayerSetEditHouse(lua_State* L); static int luaPlayerSetGhostMode(lua_State* L); @@ -916,7 +989,12 @@ class LuaScriptInterface static int luaPlayerGetContainerById(lua_State* L); static int luaPlayerGetContainerIndex(lua_State* L); - static int luaPlayerGetTotalDamage(lua_State* L); + static int luaPlayerGetInstantSpells(lua_State* L); + static int luaPlayerCanCast(lua_State* L); + + static int luaPlayerHasChaseMode(lua_State* L); + static int luaPlayerHasSecureMode(lua_State* L); + static int luaPlayerGetFightMode(lua_State* L); // Monster static int luaMonsterCreate(lua_State* L); @@ -955,6 +1033,9 @@ class LuaScriptInterface static int luaNpcSetMasterPos(lua_State* L); + static int luaNpcGetSpeechBubble(lua_State* L); + static int luaNpcSetSpeechBubble(lua_State* L); + // Guild static int luaGuildCreate(lua_State* L); @@ -966,6 +1047,9 @@ class LuaScriptInterface static int luaGuildGetRankById(lua_State* L); static int luaGuildGetRankByLevel(lua_State* L); + static int luaGuildGetMotd(lua_State* L); + static int luaGuildSetMotd(lua_State* L); + // Group static int luaGroupCreate(lua_State* L); @@ -975,11 +1059,13 @@ class LuaScriptInterface static int luaGroupGetAccess(lua_State* L); static int luaGroupGetMaxDepotItems(lua_State* L); static int luaGroupGetMaxVipEntries(lua_State* L); + static int luaGroupHasFlag(lua_State* L); // Vocation static int luaVocationCreate(lua_State* L); static int luaVocationGetId(lua_State* L); + static int luaVocationGetClientId(lua_State* L); static int luaVocationGetName(lua_State* L); static int luaVocationGetDescription(lua_State* L); @@ -1030,59 +1116,68 @@ class LuaScriptInterface static int luaHouseGetDoors(lua_State* L); static int luaHouseGetDoorCount(lua_State* L); + static int luaHouseGetDoorIdByPosition(lua_State* L); static int luaHouseGetTiles(lua_State* L); + static int luaHouseGetItems(lua_State* L); static int luaHouseGetTileCount(lua_State* L); + static int luaHouseCanEditAccessList(lua_State* L); static int luaHouseGetAccessList(lua_State* L); static int luaHouseSetAccessList(lua_State* L); + static int luaHouseKickPlayer(lua_State* L); + // ItemType static int luaItemTypeCreate(lua_State* L); static int luaItemTypeIsCorpse(lua_State* L); static int luaItemTypeIsDoor(lua_State* L); static int luaItemTypeIsContainer(lua_State* L); - static int luaItemTypeIsChest(lua_State* L); static int luaItemTypeIsFluidContainer(lua_State* L); static int luaItemTypeIsMovable(lua_State* L); static int luaItemTypeIsRune(lua_State* L); static int luaItemTypeIsStackable(lua_State* L); static int luaItemTypeIsReadable(lua_State* L); static int luaItemTypeIsWritable(lua_State* L); - static int luaItemTypeIsMagicField(lua_State* L); - static int luaItemTypeIsSplash(lua_State* L); - static int luaItemTypeIsKey(lua_State* L); - static int luaItemTypeIsDisguised(lua_State* L); - static int luaItemTypeIsDestroyable(lua_State* L); + static int luaItemTypeIsBlocking(lua_State* L); static int luaItemTypeIsGroundTile(lua_State* L); + static int luaItemTypeIsMagicField(lua_State* L); + static int luaItemTypeIsUseable(lua_State* L); + static int luaItemTypeIsPickupable(lua_State* L); static int luaItemTypeGetType(lua_State* L); static int luaItemTypeGetId(lua_State* L); - static int luaItemTypeGetDisguiseId(lua_State* L); + static int luaItemTypeGetClientId(lua_State* L); static int luaItemTypeGetName(lua_State* L); static int luaItemTypeGetPluralName(lua_State* L); static int luaItemTypeGetArticle(lua_State* L); static int luaItemTypeGetDescription(lua_State* L); static int luaItemTypeGetSlotPosition(lua_State *L); - static int luaItemTypeGetDestroyTarget(lua_State* L); static int luaItemTypeGetCharges(lua_State* L); static int luaItemTypeGetFluidSource(lua_State* L); static int luaItemTypeGetCapacity(lua_State* L); static int luaItemTypeGetWeight(lua_State* L); + static int luaItemTypeGetHitChance(lua_State* L); static int luaItemTypeGetShootRange(lua_State* L); static int luaItemTypeGetAttack(lua_State* L); static int luaItemTypeGetDefense(lua_State* L); + static int luaItemTypeGetExtraDefense(lua_State* L); static int luaItemTypeGetArmor(lua_State* L); static int luaItemTypeGetWeaponType(lua_State* L); + static int luaItemTypeGetElementType(lua_State* L); + static int luaItemTypeGetElementDamage(lua_State* L); + static int luaItemTypeGetTransformEquipId(lua_State* L); static int luaItemTypeGetTransformDeEquipId(lua_State* L); + static int luaItemTypeGetDestroyId(lua_State* L); static int luaItemTypeGetDecayId(lua_State* L); - static int luaItemTypeGetNutrition(lua_State* L); static int luaItemTypeGetRequiredLevel(lua_State* L); + static int luaItemTypeGetAmmoType(lua_State* L); + static int luaItemTypeGetCorpseType(lua_State* L); static int luaItemTypeHasSubType(lua_State* L); @@ -1093,7 +1188,8 @@ class LuaScriptInterface static int luaCombatSetFormula(lua_State* L); static int luaCombatSetArea(lua_State* L); - static int luaCombatSetCondition(lua_State* L); + static int luaCombatAddCondition(lua_State* L); + static int luaCombatClearConditions(lua_State* L); static int luaCombatSetCallback(lua_State* L); static int luaCombatSetOrigin(lua_State* L); @@ -1115,10 +1211,10 @@ class LuaScriptInterface static int luaConditionSetTicks(lua_State* L); static int luaConditionSetParameter(lua_State* L); - static int luaConditionSetSpeedDelta(lua_State* L); + static int luaConditionSetFormula(lua_State* L); static int luaConditionSetOutfit(lua_State* L); - static int luaConditionSetTiming(lua_State* L); + static int luaConditionAddDamage(lua_State* L); // MonsterType static int luaMonsterTypeCreate(lua_State* L); @@ -1129,47 +1225,100 @@ class LuaScriptInterface static int luaMonsterTypeIsIllusionable(lua_State* L); static int luaMonsterTypeIsHostile(lua_State* L); static int luaMonsterTypeIsPushable(lua_State* L); - static int luaMonsterTypeIsHealthShown(lua_State* L); + static int luaMonsterTypeIsHealthHidden(lua_State* L); static int luaMonsterTypeCanPushItems(lua_State* L); static int luaMonsterTypeCanPushCreatures(lua_State* L); - static int luaMonsterTypeGetName(lua_State* L); - static int luaMonsterTypeGetNameDescription(lua_State* L); + static int luaMonsterTypeName(lua_State* L); + static int luaMonsterTypeNameDescription(lua_State* L); - static int luaMonsterTypeGetHealth(lua_State* L); - static int luaMonsterTypeGetMaxHealth(lua_State* L); - static int luaMonsterTypeGetRunHealth(lua_State* L); - static int luaMonsterTypeGetExperience(lua_State* L); + static int luaMonsterTypeHealth(lua_State* L); + static int luaMonsterTypeMaxHealth(lua_State* L); + static int luaMonsterTypeRunHealth(lua_State* L); + static int luaMonsterTypeExperience(lua_State* L); - static int luaMonsterTypeGetCombatImmunities(lua_State* L); - static int luaMonsterTypeGetConditionImmunities(lua_State* L); + static int luaMonsterTypeCombatImmunities(lua_State* L); + static int luaMonsterTypeConditionImmunities(lua_State* L); static int luaMonsterTypeGetAttackList(lua_State* L); + static int luaMonsterTypeAddAttack(lua_State* L); + static int luaMonsterTypeGetDefenseList(lua_State* L); + static int luaMonsterTypeAddDefense(lua_State* L); + static int luaMonsterTypeGetElementList(lua_State* L); + static int luaMonsterTypeAddElement(lua_State* L); static int luaMonsterTypeGetVoices(lua_State* L); + static int luaMonsterTypeAddVoice(lua_State* L); + static int luaMonsterTypeGetLoot(lua_State* L); + static int luaMonsterTypeAddLoot(lua_State* L); + static int luaMonsterTypeGetCreatureEvents(lua_State* L); + static int luaMonsterTypeRegisterEvent(lua_State* L); + + static int luaMonsterTypeEventOnCallback(lua_State* L); + static int luaMonsterTypeEventType(lua_State* L); static int luaMonsterTypeGetSummonList(lua_State* L); - static int luaMonsterTypeGetMaxSummons(lua_State* L); + static int luaMonsterTypeAddSummon(lua_State* L); - static int luaMonsterTypeGetArmor(lua_State* L); - static int luaMonsterTypeGetDefense(lua_State* L); - static int luaMonsterTypeGetOutfit(lua_State* L); - static int luaMonsterTypeGetRace(lua_State* L); - static int luaMonsterTypeGetCorpseId(lua_State* L); - static int luaMonsterTypeGetManaCost(lua_State* L); - static int luaMonsterTypeGetBaseSpeed(lua_State* L); - static int luaMonsterTypeGetLight(lua_State* L); + static int luaMonsterTypeMaxSummons(lua_State* L); - static int luaMonsterTypeGetTargetDistance(lua_State* L); - static int luaMonsterTypeGetChangeTargetChance(lua_State* L); - static int luaMonsterTypeGetChangeTargetSpeed(lua_State* L); + static int luaMonsterTypeArmor(lua_State* L); + static int luaMonsterTypeDefense(lua_State* L); + static int luaMonsterTypeOutfit(lua_State* L); + static int luaMonsterTypeRace(lua_State* L); + static int luaMonsterTypeCorpseId(lua_State* L); + static int luaMonsterTypeManaCost(lua_State* L); + static int luaMonsterTypeBaseSpeed(lua_State* L); + static int luaMonsterTypeLight(lua_State* L); + + static int luaMonsterTypeStaticAttackChance(lua_State* L); + static int luaMonsterTypeTargetDistance(lua_State* L); + static int luaMonsterTypeYellChance(lua_State* L); + static int luaMonsterTypeYellSpeedTicks(lua_State* L); + static int luaMonsterTypeChangeTargetChance(lua_State* L); + static int luaMonsterTypeChangeTargetSpeed(lua_State* L); + + // Loot + static int luaCreateLoot(lua_State* L); + static int luaDeleteLoot(lua_State* L); + static int luaLootSetId(lua_State* L); + static int luaLootSetMaxCount(lua_State* L); + static int luaLootSetSubType(lua_State* L); + static int luaLootSetChance(lua_State* L); + static int luaLootSetActionId(lua_State* L); + static int luaLootSetDescription(lua_State* L); + static int luaLootAddChildLoot(lua_State* L); + + // MonsterSpell + static int luaCreateMonsterSpell(lua_State* L); + static int luaDeleteMonsterSpell(lua_State* L); + static int luaMonsterSpellSetType(lua_State* L); + static int luaMonsterSpellSetScriptName(lua_State* L); + static int luaMonsterSpellSetChance(lua_State* L); + static int luaMonsterSpellSetInterval(lua_State* L); + static int luaMonsterSpellSetRange(lua_State* L); + static int luaMonsterSpellSetCombatValue(lua_State* L); + static int luaMonsterSpellSetCombatType(lua_State* L); + static int luaMonsterSpellSetAttackValue(lua_State* L); + static int luaMonsterSpellSetNeedTarget(lua_State* L); + static int luaMonsterSpellSetCombatLength(lua_State* L); + static int luaMonsterSpellSetCombatSpread(lua_State* L); + static int luaMonsterSpellSetCombatRadius(lua_State* L); + static int luaMonsterSpellSetConditionType(lua_State* L); + static int luaMonsterSpellSetConditionDamage(lua_State* L); + static int luaMonsterSpellSetConditionSpeedChange(lua_State* L); + static int luaMonsterSpellSetConditionDuration(lua_State* L); + static int luaMonsterSpellSetConditionTickInterval(lua_State* L); + static int luaMonsterSpellSetCombatShootEffect(lua_State* L); + static int luaMonsterSpellSetCombatEffect(lua_State* L); // Party + static int luaPartyCreate(lua_State* L); static int luaPartyDisband(lua_State* L); static int luaPartyGetLeader(lua_State* L); @@ -1192,21 +1341,142 @@ class LuaScriptInterface static int luaPartyShareExperience(lua_State* L); static int luaPartySetSharedExperience(lua_State* L); + // Spells + static int luaSpellCreate(lua_State* L); + + static int luaSpellOnCastSpell(lua_State* L); + static int luaSpellRegister(lua_State* L); + static int luaSpellName(lua_State* L); + static int luaSpellId(lua_State* L); + static int luaSpellGroup(lua_State* L); + static int luaSpellCooldown(lua_State* L); + static int luaSpellGroupCooldown(lua_State* L); + static int luaSpellLevel(lua_State* L); + static int luaSpellMagicLevel(lua_State* L); + static int luaSpellMana(lua_State* L); + static int luaSpellManaPercent(lua_State* L); + static int luaSpellSoul(lua_State* L); + static int luaSpellRange(lua_State* L); + static int luaSpellPremium(lua_State* L); + static int luaSpellEnabled(lua_State* L); + static int luaSpellNeedTarget(lua_State* L); + static int luaSpellNeedWeapon(lua_State* L); + static int luaSpellNeedLearn(lua_State* L); + static int luaSpellSelfTarget(lua_State* L); + static int luaSpellBlocking(lua_State* L); + static int luaSpellAggressive(lua_State* L); + static int luaSpellVocation(lua_State* L); + + // only for InstantSpells + static int luaSpellWords(lua_State* L); + static int luaSpellNeedDirection(lua_State* L); + static int luaSpellHasParams(lua_State* L); + static int luaSpellHasPlayerNameParam(lua_State* L); + static int luaSpellNeedCasterTargetOrDirection(lua_State* L); + static int luaSpellIsBlockingWalls(lua_State* L); + + // only for RuneSpells + static int luaSpellRuneId(lua_State* L); + static int luaSpellCharges(lua_State* L); + static int luaSpellAllowFarUse(lua_State* L); + static int luaSpellBlockWalls(lua_State* L); + static int luaSpellCheckFloor(lua_State* L); + + // Actions + static int luaCreateAction(lua_State* L); + static int luaActionOnUse(lua_State* L); + static int luaActionRegister(lua_State* L); + static int luaActionItemId(lua_State* L); + static int luaActionActionId(lua_State* L); + static int luaActionUniqueId(lua_State* L); + static int luaActionAllowFarUse(lua_State* L); + static int luaActionBlockWalls(lua_State* L); + static int luaActionCheckFloor(lua_State* L); + + // Talkactions + static int luaCreateTalkaction(lua_State* L); + static int luaTalkactionOnSay(lua_State* L); + static int luaTalkactionRegister(lua_State* L); + static int luaTalkactionSeparator(lua_State* L); + + // CreatureEvents + static int luaCreateCreatureEvent(lua_State* L); + static int luaCreatureEventType(lua_State* L); + static int luaCreatureEventRegister(lua_State* L); + static int luaCreatureEventOnCallback(lua_State* L); + + // MoveEvents + static int luaCreateMoveEvent(lua_State* L); + static int luaMoveEventType(lua_State* L); + static int luaMoveEventRegister(lua_State* L); + static int luaMoveEventOnCallback(lua_State* L); + static int luaMoveEventLevel(lua_State* L); + static int luaMoveEventSlot(lua_State* L); + static int luaMoveEventMagLevel(lua_State* L); + static int luaMoveEventPremium(lua_State* L); + static int luaMoveEventVocation(lua_State* L); + static int luaMoveEventItemId(lua_State* L); + static int luaMoveEventActionId(lua_State* L); + static int luaMoveEventUniqueId(lua_State* L); + static int luaMoveEventPosition(lua_State* L); + + // GlobalEvents + static int luaCreateGlobalEvent(lua_State* L); + static int luaGlobalEventType(lua_State* L); + static int luaGlobalEventRegister(lua_State* L); + static int luaGlobalEventOnCallback(lua_State* L); + static int luaGlobalEventTime(lua_State* L); + static int luaGlobalEventInterval(lua_State* L); + + // Weapon + static int luaCreateWeapon(lua_State* L); + static int luaWeaponId(lua_State* L); + static int luaWeaponLevel(lua_State* L); + static int luaWeaponMagicLevel(lua_State* L); + static int luaWeaponMana(lua_State* L); + static int luaWeaponManaPercent(lua_State* L); + static int luaWeaponHealth(lua_State* L); + static int luaWeaponHealthPercent(lua_State* L); + static int luaWeaponSoul(lua_State* L); + static int luaWeaponPremium(lua_State* L); + static int luaWeaponBreakChance(lua_State* L); + static int luaWeaponAction(lua_State* L); + static int luaWeaponUnproperly(lua_State* L); + static int luaWeaponVocation(lua_State* L); + static int luaWeaponOnUseWeapon(lua_State* L); + static int luaWeaponRegister(lua_State* L); + static int luaWeaponElement(lua_State* L); + static int luaWeaponAttack(lua_State* L); + static int luaWeaponDefense(lua_State* L); + static int luaWeaponRange(lua_State* L); + static int luaWeaponCharges(lua_State* L); + static int luaWeaponDuration(lua_State* L); + static int luaWeaponDecayTo(lua_State* L); + static int luaWeaponTransformEquipTo(lua_State* L); + static int luaWeaponTransformDeEquipTo(lua_State* L); + static int luaWeaponSlotType(lua_State* L); + static int luaWeaponHitChance(lua_State* L); + static int luaWeaponExtraElement(lua_State* L); + + // exclusively for distance weapons + static int luaWeaponMaxHitChance(lua_State* L); + static int luaWeaponAmmoType(lua_State* L); + + // exclusively for wands + static int luaWeaponWandDamage(lua_State* L); + + // exclusively for wands & distance weapons + static int luaWeaponShootType(lua_State* L); + // - lua_State* luaState = nullptr; std::string lastLuaError; std::string interfaceName; - int32_t eventTableRef = -1; static ScriptEnvironment scriptEnv[16]; static int32_t scriptEnvIndex; - int32_t runningEventId = EVENT_ID_USER; std::string loadingFile; - - //script file cache - std::map cacheFiles; }; class LuaEnvironment : public LuaScriptInterface @@ -1219,9 +1489,9 @@ class LuaEnvironment : public LuaScriptInterface LuaEnvironment(const LuaEnvironment&) = delete; LuaEnvironment& operator=(const LuaEnvironment&) = delete; - bool initState(); + bool initState() override; bool reInitState(); - bool closeState(); + bool closeState() override; LuaScriptInterface* getTestInterface(); diff --git a/src/mailbox.cpp b/src/mailbox.cpp index 31cd67f..98423da 100644 --- a/src/mailbox.cpp +++ b/src/mailbox.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,9 +21,7 @@ #include "mailbox.h" #include "game.h" -#include "player.h" #include "iologindata.h" -#include "town.h" extern Game g_game; @@ -93,30 +91,22 @@ void Mailbox::postRemoveNotification(Thing* thing, const Cylinder* newParent, in bool Mailbox::sendItem(Item* item) const { std::string receiver; - std::string townName; - if (!getDestination(item, receiver, townName)) { + if (!getReceiver(item, receiver)) { return false; } - if (receiver.empty() || townName.empty()) { - return false; - } - - Town* town = g_game.map.towns.getTown(townName); - if (!town) { + /**No need to continue if its still empty**/ + if (receiver.empty()) { 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(town->getID()); - return true; - } + if (g_game.internalMoveItem(item->getParent(), player->getInbox(), 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); @@ -124,30 +114,25 @@ bool Mailbox::sendItem(Item* item) const 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; - } + if (g_game.internalMoveItem(item->getParent(), tmpPlayer.getInbox(), 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 +bool Mailbox::getReceiver(Item* item, std::string& name) const { const Container* container = item->getContainer(); if (container) { for (Item* containerItem : container->getItemList()) { - if (containerItem->getID() == ITEM_LABEL && getDestination(containerItem, name, town)) { + if (containerItem->getID() == ITEM_LABEL && getReceiver(containerItem, name)) { return true; } } - return false; } @@ -156,24 +141,8 @@ bool Mailbox::getDestination(Item* item, std::string& name, std::string& town) c 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; - } - + name = getFirstLine(text); trimString(name); - trimString(town); return true; } diff --git a/src/mailbox.h b/src/mailbox.h index 084dc26..400b6c6 100644 --- a/src/mailbox.h +++ b/src/mailbox.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,35 +29,35 @@ class Mailbox final : public Item, public Cylinder public: explicit Mailbox(uint16_t itemId) : Item(itemId) {} - Mailbox* getMailbox() final { + Mailbox* getMailbox() override { return this; } - const Mailbox* getMailbox() const final { + const Mailbox* getMailbox() const override { return this; } //cylinder implementations ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const final; + 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; + uint32_t& maxQueryCount, uint32_t flags) const override; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const override; Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, - uint32_t& flags) final; + uint32_t& flags) override; - void addThing(Thing* thing) final; - void addThing(int32_t index, Thing* thing) final; + void addThing(Thing* thing) override; + void addThing(int32_t index, Thing* thing) override; - void updateThing(Thing* thing, uint16_t itemId, uint32_t count) final; - void replaceThing(uint32_t index, Thing* thing) final; + void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override; + void replaceThing(uint32_t index, Thing* thing) override; - void removeThing(Thing* thing, uint32_t count) final; + void removeThing(Thing* thing, uint32_t count) override; - 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; + 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; private: - bool getDestination(Item* item, std::string& name, std::string& town) const; + bool getReceiver(Item* item, std::string& name) const; bool sendItem(Item* item) const; static bool canSend(const Item* item); diff --git a/src/map.cpp b/src/map.cpp index 94bdeef..539e937 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,7 +23,6 @@ #include "iomapserialize.h" #include "combat.h" #include "creature.h" -#include "monster.h" #include "game.h" extern Game g_game; @@ -36,7 +35,6 @@ bool Map::loadMap(const std::string& identifier, bool loadHouses) return false; } - Npcs::loadNpcs(); if (!IOMap::loadSpawns(this)) { std::cout << "[Warning - Map::loadMap] Failed to load spawn data." << std::endl; } @@ -163,14 +161,7 @@ bool Map::placeCreature(const Position& centerPos, Creature* creature, bool exte Tile* tile = getTile(centerPos.x, centerPos.y, centerPos.z); if (tile) { placeInPZ = tile->hasFlag(TILESTATE_PROTECTIONZONE); - - ReturnValue ret; - if (creature->getPlayer()) { - ret = tile->queryAdd(0, *creature, 1, 0); - } else { - ret = tile->queryAdd(0, *creature, 1, (creature->getMonster() ? FLAG_PLACECHECK : FLAG_IGNOREBLOCKITEM)); - } - + ReturnValue ret = tile->queryAdd(0, *creature, 1, FLAG_IGNOREBLOCKITEM); foundTile = forceLogin || ret == RETURNVALUE_NOERROR || ret == RETURNVALUE_PLAYERISNOTINVITED; } else { placeInPZ = false; @@ -209,7 +200,7 @@ bool Map::placeCreature(const Position& centerPos, Creature* creature, bool exte continue; } - if (tile->queryAdd(0, *creature, 1, (creature->getMonster() ? FLAG_PLACECHECK : 0)) == RETURNVALUE_NOERROR) { + if (tile->queryAdd(0, *creature, 1, 0) == RETURNVALUE_NOERROR) { if (!extendedPos || isSightClear(centerPos, tryPos, false)) { foundTile = true; break; @@ -243,12 +234,13 @@ void Map::moveCreature(Creature& creature, Tile& newTile, bool forceTeleport/* = bool teleport = forceTeleport || !newTile.getGround() || !Position::areInRange<1, 1, 0>(oldPos, newPos); - SpectatorVec list; - getSpectators(list, oldPos, true); - getSpectators(list, newPos, true); + SpectatorVec spectators, newPosSpectators; + getSpectators(spectators, oldPos, true); + getSpectators(newPosSpectators, newPos, true); + spectators.addSpectators(newPosSpectators); std::vector oldStackPosVector; - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { if (tmpPlayer->canSeeCreature(&creature)) { oldStackPosVector.push_back(oldTile.getClientIndexOfCreature(tmpPlayer, &creature)); @@ -289,7 +281,7 @@ void Map::moveCreature(Creature& creature, Tile& newTile, bool forceTeleport/* = //send to client size_t i = 0; - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { //Use the correct stackpos int32_t stackpos = oldStackPosVector[i++]; @@ -300,7 +292,7 @@ void Map::moveCreature(Creature& creature, Tile& newTile, bool forceTeleport/* = } //event method - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { spectator->onCreatureMove(&creature, &newTile, newPos, &oldTile, oldPos, teleport); } @@ -308,7 +300,7 @@ void Map::moveCreature(Creature& creature, Tile& newTile, bool forceTeleport/* = newTile.postAddNotification(&creature, &oldTile, 0); } -void Map::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 +void Map::getSpectatorsInternal(SpectatorVec& spectators, const Position& centerPos, int32_t minRangeX, int32_t maxRangeX, int32_t minRangeY, int32_t maxRangeY, int32_t minRangeZ, int32_t maxRangeZ, bool onlyPlayers) const { int_fast16_t min_y = centerPos.y + minRangeY; int_fast16_t min_x = centerPos.x + minRangeX; @@ -348,7 +340,7 @@ void Map::getSpectatorsInternal(SpectatorVec& list, const Position& centerPos, i continue; } - list.insert(creature); + spectators.emplace_back(creature); } leafE = leafE->leafE; } else { @@ -364,7 +356,7 @@ void Map::getSpectatorsInternal(SpectatorVec& list, const Position& centerPos, i } } -void Map::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 Map::getSpectators(SpectatorVec& spectators, 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*/) { if (centerPos.z >= MAP_MAX_LAYERS) { return; @@ -382,11 +374,11 @@ void Map::getSpectators(SpectatorVec& list, const Position& centerPos, bool mult if (onlyPlayers) { auto it = playersSpectatorCache.find(centerPos); if (it != playersSpectatorCache.end()) { - if (!list.empty()) { - const SpectatorVec& cachedList = it->second; - list.insert(cachedList.begin(), cachedList.end()); + if (!spectators.empty()) { + const SpectatorVec& cachedSpectators = it->second; + spectators.insert(spectators.end(), cachedSpectators.begin(), cachedSpectators.end()); } else { - list = it->second; + spectators = it->second; } foundCache = true; @@ -397,17 +389,17 @@ void Map::getSpectators(SpectatorVec& list, const Position& centerPos, bool mult auto it = spectatorCache.find(centerPos); if (it != spectatorCache.end()) { if (!onlyPlayers) { - if (!list.empty()) { - const SpectatorVec& cachedList = it->second; - list.insert(cachedList.begin(), cachedList.end()); + if (!spectators.empty()) { + const SpectatorVec& cachedSpectators = it->second; + spectators.insert(spectators.end(), cachedSpectators.begin(), cachedSpectators.end()); } else { - list = it->second; + spectators = it->second; } } else { - const SpectatorVec& cachedList = it->second; - for (Creature* spectator : cachedList) { + const SpectatorVec& cachedSpectators = it->second; + for (Creature* spectator : cachedSpectators) { if (spectator->getPlayer()) { - list.insert(spectator); + spectators.emplace_back(spectator); } } } @@ -445,13 +437,13 @@ void Map::getSpectators(SpectatorVec& list, const Position& centerPos, bool mult maxRangeZ = centerPos.z; } - getSpectatorsInternal(list, centerPos, minRangeX, maxRangeX, minRangeY, maxRangeY, minRangeZ, maxRangeZ, onlyPlayers); + getSpectatorsInternal(spectators, centerPos, minRangeX, maxRangeX, minRangeY, maxRangeY, minRangeZ, maxRangeZ, onlyPlayers); if (cacheResult) { if (onlyPlayers) { - playersSpectatorCache[centerPos] = list; + playersSpectatorCache[centerPos] = spectators; } else { - spectatorCache[centerPos] = list; + spectatorCache[centerPos] = spectators; } } } @@ -460,6 +452,10 @@ void Map::getSpectators(SpectatorVec& list, const Position& centerPos, bool mult void Map::clearSpectatorCache() { spectatorCache.clear(); +} + +void Map::clearPlayersSpectatorCache() +{ playersSpectatorCache.clear(); } @@ -563,7 +559,7 @@ const Tile* Map::canWalkTo(const Creature& creature, const Position& pos) const //used for non-cached tiles Tile* tile = getTile(pos.x, pos.y, pos.z); if (creature.getTile() != tile) { - if (!tile || tile->queryAdd(0, creature, 1, FLAG_PATHFINDING) != RETURNVALUE_NOERROR) { + if (!tile || tile->queryAdd(0, creature, 1, FLAG_PATHFINDING | FLAG_IGNOREFIELDDAMAGE) != RETURNVALUE_NOERROR) { return nullptr; } } @@ -846,25 +842,16 @@ int_fast32_t AStarNodes::getTileWalkCost(const Creature& creature, const Tile* t { int_fast32_t cost = 0; if (tile->getTopVisibleCreature(&creature) != nullptr) { - if (const Monster* monster = creature.getMonster()) { - if (monster->canPushCreatures()) { - return cost; - } - } - //destroy creature cost cost += MAP_NORMALWALKCOST * 3; } if (const MagicField* field = tile->getFieldItem()) { CombatType_t combatType = field->getCombatType(); - if (combatType != COMBAT_NONE) { - if (!creature.isImmune(combatType) && !creature.hasCondition(Combat::DamageToConditionType(combatType))) { - cost += MAP_NORMALWALKCOST * 18; - } + if (!creature.isImmune(combatType) && !creature.hasCondition(Combat::DamageToConditionType(combatType))) { + cost += MAP_NORMALWALKCOST * 18; } } - return cost; } diff --git a/src/map.h b/src/map.h index 3e8df8d..390eb6d 100644 --- a/src/map.h +++ b/src/map.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -73,7 +73,7 @@ class AStarNodes int_fast32_t closedNodes; }; -typedef std::map SpectatorCache; +using SpectatorCache = std::map; static constexpr int32_t FLOOR_BITS = 3; static constexpr int32_t FLOOR_SIZE = (1 << FLOOR_BITS); @@ -110,7 +110,7 @@ class QTreeNode QTreeLeafNode* getLeaf(uint32_t x, uint32_t y); template - inline static Leaf getLeafStatic(Node node, uint32_t x, uint32_t y) + static Leaf getLeafStatic(Node node, uint32_t x, uint32_t y) { do { node = node->child[((x & 0x8000) >> 15) | ((y & 0x8000) >> 14)]; @@ -127,10 +127,11 @@ class QTreeNode QTreeLeafNode* createLeaf(uint32_t x, uint32_t y, uint32_t level); protected: - QTreeNode* child[4] = {}; - bool leaf = false; + private: + QTreeNode* child[4] = {}; + friend class Map; }; @@ -152,7 +153,7 @@ class QTreeLeafNode final : public QTreeNode void addCreature(Creature* c); void removeCreature(Creature* c); - protected: + private: static bool newLeaf; QTreeLeafNode* leafS = nullptr; QTreeLeafNode* leafE = nullptr; @@ -196,7 +197,7 @@ class Map * \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 { + Tile* getTile(const Position& pos) const { return getTile(pos.x, pos.y, pos.z); } @@ -219,11 +220,12 @@ class Map void moveCreature(Creature& creature, Tile& newTile, bool forceTeleport = false); - void getSpectators(SpectatorVec& list, const Position& centerPos, bool multifloor = false, bool onlyPlayers = false, + void getSpectators(SpectatorVec& spectators, 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(); + void clearPlayersSpectatorCache(); /** * Checks if you can throw an object to that position @@ -262,7 +264,8 @@ class Map Spawns spawns; Towns towns; Houses houses; - protected: + + private: SpectatorCache spectatorCache; SpectatorCache playersSpectatorCache; @@ -275,7 +278,7 @@ class Map uint32_t height = 0; // Actually scans the map for spectators - void getSpectatorsInternal(SpectatorVec& list, const Position& centerPos, + void getSpectatorsInternal(SpectatorVec& spectators, const Position& centerPos, int32_t minRangeX, int32_t maxRangeX, int32_t minRangeY, int32_t maxRangeY, int32_t minRangeZ, int32_t maxRangeZ, bool onlyPlayers) const; diff --git a/src/monster.cpp b/src/monster.cpp index 69718e4..58883a7 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,11 +22,11 @@ #include "monster.h" #include "game.h" #include "spells.h" -#include "configmanager.h" +#include "events.h" -extern ConfigManager g_config; extern Game g_game; extern Monsters g_monsters; +extern Events* g_events; int32_t Monster::despawnRange; int32_t Monster::despawnRadius; @@ -42,10 +42,10 @@ Monster* Monster::createMonster(const std::string& name) return new Monster(mType); } -Monster::Monster(MonsterType* mtype) : +Monster::Monster(MonsterType* mType) : Creature(), - strDescription(asLowerCaseString(mtype->nameDescription)), - mType(mtype) + strDescription(mType->nameDescription), + mType(mType) { defaultOutfit = mType->info.outfit; currentOutfit = mType->info.outfit; @@ -80,45 +80,31 @@ void Monster::removeList() g_game.removeMonster(this); } -int32_t Monster::getDefense() -{ - int32_t totalDefense = mType->info.defense + 1; - int32_t defenseSkill = mType->info.skill; - - fightMode_t attackMode = FIGHTMODE_BALANCED; - - if ((followCreature || !attackedCreature) && earliestAttackTime <= OTSYS_TIME()) { - attackMode = FIGHTMODE_DEFENSE; - } - - if (attackMode == FIGHTMODE_ATTACK) { - totalDefense -= 4 * totalDefense / 10; - } else if (attackMode == FIGHTMODE_DEFENSE) { - totalDefense += 8 * totalDefense / 10; - } - - if (totalDefense) { - int32_t formula = (5 * (defenseSkill) + 50) * totalDefense; - int32_t randresult = rand() % 100; - - totalDefense = formula * ((rand() % 100 + randresult) / 2) / 10000.; - } - - return totalDefense; -} - bool Monster::canSee(const Position& pos) const { return Creature::canSee(getPosition(), pos, 9, 9); } -void Monster::onAttackedCreature(Creature* creature) +bool Monster::canWalkOnFieldType(CombatType_t combatType) const { - if (isSummon() && getMaster()) { - master->onAttackedCreature(creature); + switch (combatType) { + case COMBAT_ENERGYDAMAGE: + return mType->info.canWalkOnEnergy; + case COMBAT_FIREDAMAGE: + return mType->info.canWalkOnFire; + case COMBAT_EARTHDAMAGE: + return mType->info.canWalkOnPoison; + default: + return true; } } +void Monster::onAttackedCreatureDisappear(bool) +{ + attackTicks = 0; + extraMeleeAttack = true; +} + void Monster::onCreatureAppear(Creature* creature, bool isLogin) { Creature::onCreatureAppear(creature, isLogin); @@ -252,7 +238,7 @@ void Monster::onCreatureMove(Creature* creature, const Tile* newTile, const Posi } if (canSeeNewPos && isSummon() && getMaster() == creature) { - isMasterInRange = true; //Follow master again + isMasterInRange = true; //Follow master again } updateIdleStatus(); @@ -264,7 +250,7 @@ void Monster::onCreatureMove(Creature* creature, const Tile* newTile, const Posi int32_t offset_x = Position::getDistanceX(followPosition, position); int32_t offset_y = Position::getDistanceY(followPosition, position); - if ((offset_x > 1 || offset_y > 1) && mType->info.changeTargetChance > 0 && targetChangeCooldown <= 0) { + if ((offset_x > 1 || offset_y > 1) && mType->info.changeTargetChance > 0) { Direction dir = getDirectionTo(position, followPosition); const Position& checkPosition = getNextPosition(dir, position); @@ -379,10 +365,10 @@ void Monster::updateTargetList() } } - SpectatorVec list; - g_game.map.getSpectators(list, position, true); - list.erase(this); - for (Creature* spectator : list) { + SpectatorVec spectators; + g_game.map.getSpectators(spectators, position, true); + spectators.erase(this); + for (Creature* spectator : spectators) { if (canSee(spectator->getPosition())) { onCreatureFound(spectator); } @@ -495,14 +481,14 @@ void Monster::onCreatureLeave(Creature* creature) } } -bool Monster::searchTarget(TargetSearchType_t searchType) +bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAULT*/) { std::list resultList; const Position& myPos = getPosition(); for (Creature* creature : targetList) { if (followCreature != creature && isTarget(creature)) { - if (searchType == TARGETSEARCH_ANY || canUseAttack(myPos, creature)) { + if (searchType == TARGETSEARCH_RANDOM || canUseAttack(myPos, creature)) { resultList.push_back(creature); } } @@ -511,31 +497,32 @@ bool Monster::searchTarget(TargetSearchType_t searchType) switch (searchType) { case TARGETSEARCH_NEAREST: { Creature* target = nullptr; - - int32_t minRange = 0; - if (attackedCreature) { - const Position& targetPosition = attackedCreature->getPosition(); - minRange = Position::getDistanceX(myPos, targetPosition) + Position::getDistanceY(myPos, targetPosition); - } - if (!resultList.empty()) { - for (Creature* creature : resultList) { - const Position& targetPosition = creature->getPosition(); + auto it = resultList.begin(); + target = *it; - int32_t distance = Position::getDistanceX(myPos, targetPosition) + Position::getDistanceY(myPos, targetPosition); - if (distance < minRange) { - target = creature; - minRange = distance; - } + if (++it != resultList.end()) { + const Position& targetPosition = target->getPosition(); + int32_t minRange = Position::getDistanceX(myPos, targetPosition) + Position::getDistanceY(myPos, targetPosition); + do { + const Position& pos = (*it)->getPosition(); + + int32_t distance = Position::getDistanceX(myPos, pos) + Position::getDistanceY(myPos, pos); + if (distance < minRange) { + target = *it; + minRange = distance; + } + } while (++it != resultList.end()); } } else { + int32_t minRange = std::numeric_limits::max(); for (Creature* creature : targetList) { if (!isTarget(creature)) { continue; } - const Position& targetPosition = creature->getPosition(); - int32_t distance = Position::getDistanceX(myPos, targetPosition) + Position::getDistanceY(myPos, targetPosition); + const Position& pos = creature->getPosition(); + int32_t distance = Position::getDistanceX(myPos, pos) + Position::getDistanceY(myPos, pos); if (distance < minRange) { target = creature; minRange = distance; @@ -548,79 +535,10 @@ bool Monster::searchTarget(TargetSearchType_t searchType) } break; } - case TARGETSEARCH_WEAKEST: { - Creature* target = nullptr; - int32_t health = 0; - if (attackedCreature) { - health = attackedCreature->getMaxHealth(); - } - - if (!resultList.empty()) { - for (Creature* creature : resultList) { - if (creature->getMaxHealth() < health) { - target = creature; - health = creature->getMaxHealth(); - } - } - } else { - for (Creature* creature : targetList) { - if (creature->getMaxHealth() < health) { - target = creature; - health = creature->getMaxHealth(); - } - } - } - - if (target && selectTarget(target)) { - return true; - } - break; - } - case TARGETSEARCH_MOSTDAMAGE: { - Creature* target = nullptr; - - int32_t maxDamage = 0; - - if (!resultList.empty()) { - for (Creature* creature : resultList) { - auto it = damageMap.find(creature->getID()); - if (it == damageMap.end()) { - continue; - } - - int32_t damage = it->second.total; - - if (OTSYS_TIME() - it->second.ticks <= g_config.getNumber(ConfigManager::PZ_LOCKED)) { - if (damage > maxDamage) { - target = creature; - maxDamage = damage; - } - } - } - } else { - for (Creature* creature : targetList) { - auto it = damageMap.find(creature->getID()); - if (it == damageMap.end()) { - continue; - } - - int32_t damage = it->second.total; - - if (OTSYS_TIME() - it->second.ticks <= g_config.getNumber(ConfigManager::PZ_LOCKED)) { - if (damage > maxDamage) { - target = creature; - maxDamage = damage; - } - } - } - } - - if (target && selectTarget(target)) { - return true; - } - break; - } + case TARGETSEARCH_DEFAULT: + case TARGETSEARCH_ATTACKRANGE: + case TARGETSEARCH_RANDOM: default: { if (!resultList.empty()) { auto it = resultList.begin(); @@ -628,19 +546,20 @@ bool Monster::searchTarget(TargetSearchType_t searchType) return selectTarget(*it); } + if (searchType == TARGETSEARCH_ATTACKRANGE) { + return false; + } + break; } } - //lets just pick the first target in the list if we do not have a target - if (!attackedCreature) { - for (Creature* target : targetList) { - if (followCreature != target && selectTarget(target)) { - return true; - } + //lets just pick the first target in the list + for (Creature* target : targetList) { + if (followCreature != target && selectTarget(target)) { + return true; } } - return false; } @@ -718,10 +637,6 @@ bool Monster::selectTarget(Creature* creature) g_dispatcher.addTask(createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID()))); } } - - // without this task, monster would randomly start dancing until next game round - g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, &g_game, getID()))); - targetChangeCooldown += 3000; return setFollowCreature(creature); } @@ -758,7 +673,7 @@ void Monster::updateIdleStatus() void Monster::onAddCondition(ConditionType_t type) { - if (type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON || type == CONDITION_AGGRESSIVE) { + if (type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON) { updateMapCache(); } @@ -767,7 +682,8 @@ void Monster::onAddCondition(ConditionType_t type) void Monster::onEndCondition(ConditionType_t type) { - if (type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON || type == CONDITION_AGGRESSIVE) { + if (type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON) { + ignoreFieldDamage = false; updateMapCache(); } @@ -776,10 +692,6 @@ void Monster::onEndCondition(ConditionType_t type) void Monster::onThink(uint32_t interval) { - if (OTSYS_TIME() < earliestWakeUpTime) { - return; - } - Creature::onThink(interval); if (mType->info.thinkEvent != -1) { @@ -806,10 +718,9 @@ void Monster::onThink(uint32_t interval) } } - if (!isInSpawnRange(position) || (lifetime > 0 && (OTSYS_TIME() >= lifetime))) { - // Despawn creatures if they are out of their spawn zone - g_game.removeCreature(this); - g_game.addMagicEffect(getPosition(), CONST_ME_POFF); + if (!isInSpawnRange(position)) { + g_game.internalTeleport(this, masterPos); + setIdle(true); } else { updateIdleStatus(); @@ -819,6 +730,7 @@ void Monster::onThink(uint32_t interval) if (isSummon()) { if (!attackedCreature) { if (getMaster() && getMaster()->getAttackedCreature()) { + //This happens if the monster is summoned during combat selectTarget(getMaster()->getAttackedCreature()); } else if (getMaster() != followCreature) { //Our master has not ordered us to attack anything, lets follow him around instead. @@ -827,23 +739,15 @@ void Monster::onThink(uint32_t interval) } else if (attackedCreature == this) { setFollowCreature(nullptr); } else if (followCreature != attackedCreature) { + //This happens just after a master orders an attack, so lets follow it aswell. setFollowCreature(attackedCreature); } - - if (master) { - if (Monster* monster = master->getMonster()) { - if (monster->mType->info.targetDistance <= 1 && !monster->hasFollowPath) { - setFollowCreature(master); - setAttackedCreature(nullptr); - } - } - } } else if (!targetList.empty()) { if (!followCreature || !hasFollowPath) { - searchTarget(TARGETSEARCH_ANY); + searchTarget(); } else if (isFleeing()) { if (attackedCreature && !canUseAttack(getPosition(), attackedCreature)) { - searchTarget(TARGETSEARCH_NEAREST); + searchTarget(TARGETSEARCH_ATTACKRANGE); } } } @@ -855,43 +759,56 @@ void Monster::onThink(uint32_t interval) } } -void Monster::doAttacking(uint32_t) +void Monster::doAttacking(uint32_t interval) { if (!attackedCreature || (isSummon() && attackedCreature == this)) { return; } + bool updateLook = true; + bool resetTicks = interval != 0; + attackTicks += interval; + const Position& myPos = getPosition(); const Position& targetPos = attackedCreature->getPosition(); - bool updateLook = false; - - if (OTSYS_TIME() >= earliestAttackTime && !isFleeing()) { - updateLook = true; - if (Combat::closeAttack(this, attackedCreature, FIGHTMODE_BALANCED)) { - egibleToDance = true; - earliestAttackTime = OTSYS_TIME() + 2000; - removeCondition(CONDITION_AGGRESSIVE, true); + for (const spellBlock_t& spellBlock : mType->info.attackSpells) { + bool inRange = false; + + if (attackedCreature == nullptr) { + break; } - } - - for (spellBlock_t& spellBlock : mType->info.attackSpells) { - if (spellBlock.range != 0 && std::max(Position::getDistanceX(myPos, targetPos), Position::getDistanceY(myPos, targetPos)) <= spellBlock.range) { - if (uniform_random(0, spellBlock.chance) == 0 && (master || health > mType->info.runAwayHealth || uniform_random(1, 3) == 1)) { - updateLookDirection(); + + if (canUseSpell(myPos, targetPos, spellBlock, interval, inRange, resetTicks)) { + if (spellBlock.chance >= static_cast(uniform_random(1, 100))) { + if (updateLook) { + updateLookDirection(); + updateLook = false; + } minCombatValue = spellBlock.minCombatValue; maxCombatValue = spellBlock.maxCombatValue; - spellBlock.spell->castSpell(this, attackedCreature); - egibleToDance = true; + + if (spellBlock.isMelee) { + extraMeleeAttack = false; + } } } + + if (!inRange && spellBlock.isMelee) { + //melee swing out of reach + extraMeleeAttack = true; + } } if (updateLook) { updateLookDirection(); } + + if (resetTicks) { + attackTicks = 0; + } } bool Monster::canUseAttack(const Position& pos, const Creature* target) const @@ -909,6 +826,40 @@ bool Monster::canUseAttack(const Position& pos, const Creature* target) const return true; } +bool Monster::canUseSpell(const Position& pos, const Position& targetPos, + const spellBlock_t& sb, uint32_t interval, bool& inRange, bool& resetTicks) +{ + inRange = true; + + if (sb.isMelee && isFleeing()) { + return false; + } + + if (extraMeleeAttack) { + lastMeleeAttack = OTSYS_TIME(); + } else if (sb.isMelee && (OTSYS_TIME() - lastMeleeAttack) < 1500) { + return false; + } + + if (!sb.isMelee || !extraMeleeAttack) { + if (sb.speed > attackTicks) { + resetTicks = false; + return false; + } + + if (attackTicks % sb.speed >= interval) { + //already used this spell for this round + return false; + } + } + + if (sb.range != 0 && std::max(Position::getDistanceX(pos, targetPos), Position::getDistanceY(pos, targetPos)) > sb.range) { + inRange = false; + return false; + } + return true; +} + void Monster::onThinkTarget(uint32_t interval) { if (!isSummon()) { @@ -931,42 +882,13 @@ void Monster::onThinkTarget(uint32_t interval) if (targetChangeTicks >= mType->info.changeTargetSpeed) { targetChangeTicks = 0; + targetChangeCooldown = mType->info.changeTargetSpeed; - if (mType->info.changeTargetChance > uniform_random(0, 99)) { - // search target strategies, if no strategy succeeds, target is not switched - int32_t random = uniform_random(0, 99); - int32_t current_strategy = 0; - - TargetSearchType_t searchType = TARGETSEARCH_ANY; - - do - { - int32_t strategy = 0; - - if (current_strategy == 0) { - strategy = mType->info.strategyNearestEnemy; - searchType = TARGETSEARCH_NEAREST; - } else if (current_strategy == 1) { - strategy = mType->info.strategyWeakestEnemy; - searchType = TARGETSEARCH_WEAKEST; - } else if (current_strategy == 2) { - strategy = mType->info.strategyMostDamageEnemy; - searchType = TARGETSEARCH_MOSTDAMAGE; - } else if (current_strategy == 3) { - strategy = mType->info.strategyRandomEnemy; - searchType = TARGETSEARCH_RANDOM; - } - - if (random < strategy) { - break; - } - - current_strategy++; - random -= strategy; - } while (current_strategy <= 3); - - if (searchType != TARGETSEARCH_ANY) { - searchTarget(searchType); + if (mType->info.changeTargetChance >= uniform_random(1, 100)) { + if (mType->info.targetDistance <= 1) { + searchTarget(TARGETSEARCH_RANDOM); + } else { + searchTarget(TARGETSEARCH_NEAREST); } } } @@ -975,10 +897,23 @@ void Monster::onThinkTarget(uint32_t interval) } } -void Monster::onThinkDefense(uint32_t) +void Monster::onThinkDefense(uint32_t interval) { + bool resetTicks = true; + defenseTicks += interval; + for (const spellBlock_t& spellBlock : mType->info.defenseSpells) { - if (uniform_random(0, spellBlock.chance) == 0 && (master || health > mType->info.runAwayHealth || uniform_random(1, 3) == 1)) { + if (spellBlock.speed > defenseTicks) { + resetTicks = false; + continue; + } + + if (defenseTicks % spellBlock.speed >= interval) { + //already used this spell for this round + continue; + } + + if ((spellBlock.chance >= static_cast(uniform_random(1, 100)))) { minCombatValue = spellBlock.minCombatValue; maxCombatValue = spellBlock.maxCombatValue; spellBlock.spell->castSpell(this, this); @@ -987,10 +922,20 @@ void Monster::onThinkDefense(uint32_t) if (!isSummon() && summons.size() < mType->info.maxSummons && hasFollowPath) { for (const summonBlock_t& summonBlock : mType->info.summons) { + if (summonBlock.speed > defenseTicks) { + resetTicks = false; + continue; + } + if (summons.size() >= mType->info.maxSummons) { continue; } + if (defenseTicks % summonBlock.speed >= interval) { + //already used this spell for this round + continue; + } + uint32_t summonCount = 0; for (Creature* summon : summons) { if (summon->getName() == summonBlock.name) { @@ -1002,41 +947,49 @@ void Monster::onThinkDefense(uint32_t) continue; } - if (normal_random(0, summonBlock.chance) == 0 && (health > mType->info.runAwayHealth || normal_random(1, 3) == 1)) { - Monster* summon = Monster::createMonster(summonBlock.name); - if (summon) { - const Position& summonPos = getPosition(); + if (summonBlock.chance < static_cast(uniform_random(1, 100))) { + continue; + } - addSummon(summon); - - if (!g_game.placeCreature(summon, summonPos, false, summonBlock.force)) { - removeSummon(summon); - } else { - g_game.addMagicEffect(getPosition(), CONST_ME_MAGIC_BLUE); - g_game.addMagicEffect(summon->getPosition(), CONST_ME_TELEPORT); - } + Monster* summon = Monster::createMonster(summonBlock.name); + if (summon) { + if (g_game.placeCreature(summon, getPosition(), false, summonBlock.force)) { + summon->setDropLoot(false); + summon->setSkillLoss(false); + summon->setMaster(this); + g_game.addMagicEffect(getPosition(), CONST_ME_MAGIC_BLUE); + g_game.addMagicEffect(summon->getPosition(), CONST_ME_TELEPORT); + } else { + delete summon; } } } } + + if (resetTicks) { + defenseTicks = 0; + } } -void Monster::onThinkYell(uint32_t) +void Monster::onThinkYell(uint32_t interval) { - if (mType->info.voiceVector.empty()) { + if (mType->info.yellSpeedTicks == 0) { return; } - int32_t randomResult = rand(); - if (rand() == 50 * (randomResult / 50)) { - int32_t totalVoices = mType->info.voiceVector.size(); - const voiceBlock_t& voice = mType->info.voiceVector[rand() % totalVoices + 1]; + yellTicks += interval; + if (yellTicks >= mType->info.yellSpeedTicks) { + yellTicks = 0; - if (voice.yellText) { - g_game.internalCreatureSay(this, TALKTYPE_MONSTER_YELL, voice.text, false); - } - else { - g_game.internalCreatureSay(this, TALKTYPE_MONSTER_SAY, voice.text, false); + if (!mType->info.voiceVector.empty() && (mType->info.yellChance >= static_cast(uniform_random(1, 100)))) { + uint32_t index = uniform_random(0, mType->info.voiceVector.size() - 1); + const voiceBlock_t& vb = mType->info.voiceVector[index]; + + if (vb.yellText) { + g_game.internalCreatureSay(this, TALKTYPE_MONSTER_YELL, vb.text, false); + } else { + g_game.internalCreatureSay(this, TALKTYPE_MONSTER_SAY, vb.text, false); + } } } } @@ -1159,117 +1112,27 @@ bool Monster::getNextStep(Direction& direction, uint32_t& flags) bool result = false; if ((!followCreature || !hasFollowPath) && (!isSummon() || !isMasterInRange)) { - if (OTSYS_TIME() >= nextDanceStepRound) { - updateLookDirection(); - nextDanceStepRound = OTSYS_TIME() + getStepDuration(); - + if (getTimeSinceLastMove() >= 1000) { + randomStepping = true; //choose a random direction result = getRandomStep(getPosition(), direction); } } else if ((isSummon() && isMasterInRange) || followCreature) { + randomStepping = false; result = Creature::getNextStep(direction, flags); if (result) { flags |= FLAG_PATHFINDING; } else { + if (ignoreFieldDamage) { + ignoreFieldDamage = false; + updateMapCache(); + } //target dancing if (attackedCreature && attackedCreature == followCreature) { if (isFleeing()) { result = getDanceStep(getPosition(), direction, false, false); - } else if (egibleToDance && OTSYS_TIME() >= earliestDanceTime) { - if (mType->info.targetDistance >= 4) { - const Position& myPos = getPosition(); - const Position targetPos = attackedCreature->getPosition(); - - if (Position::getDistanceX(myPos, targetPos) == 4 || Position::getDistanceY(myPos, targetPos) == 4) { - int32_t currentX = myPos.x; - int32_t currentY = myPos.y; - int32_t danceRandom = rand(); - int32_t danceRandomResult = danceRandom % 5; - - if (danceRandom % 5 == 1) { - direction = DIRECTION_EAST; - currentX++; - } else if (danceRandomResult <= 1) { - if (danceRandom == 5 * (danceRandom / 5)) { - direction = DIRECTION_WEST; - currentX--; - } - } else if (danceRandomResult == 2) { - direction = DIRECTION_NORTH; - currentY--; - } else if (danceRandomResult == 3) { - direction = DIRECTION_SOUTH; - currentY++; - } - - if (danceRandomResult <= 3 && canWalkTo(myPos, direction)) { - int32_t xTest = targetPos.x - currentX; - if (currentX - targetPos.x > -1) { - xTest = currentX - targetPos.x; - } - - int32_t yTest = targetPos.y - currentY; - if (currentY - targetPos.y > -1) { - yTest = currentY - targetPos.y; - } - - int32_t realTest = yTest; - - if (xTest >= yTest) { - realTest = xTest; - } - - if (realTest == 4) { - result = true; - egibleToDance = false; - earliestWakeUpTime = OTSYS_TIME() + 1000; - earliestDanceTime = OTSYS_TIME() + 1000 + getStepDuration(); - earliestAttackTime += 200; - } - } - } - } else { - const Position& myPos = getPosition(); - const Position targetPos = attackedCreature->getPosition(); - - if (Position::areInRange<1, 1>(myPos, targetPos)) { - int32_t danceRandom = rand(); - int32_t danceRandomResult = danceRandom % 5; - - int32_t currentX = myPos.x; - int32_t currentY = myPos.y; - - if (danceRandom % 5 == 1) { - direction = DIRECTION_EAST; - currentX++; - } else if (danceRandomResult <= 1) { - if (danceRandom == 5 * (danceRandom / 5)) { - direction = DIRECTION_WEST; - currentX--; - } - } else if (danceRandomResult == 2) { - direction = DIRECTION_NORTH; - currentY--; - } else if (danceRandomResult == 3) { - direction = DIRECTION_SOUTH; - currentY++; - } - - Position position = myPos; - position.x = currentX; - position.y = currentY; - - if (danceRandomResult <= 3 && - canWalkTo(myPos, direction) && - Position::areInRange<1, 1>(position, targetPos)) { - result = true; - egibleToDance = false; - earliestWakeUpTime = OTSYS_TIME() + 1000; - earliestDanceTime = OTSYS_TIME() + 1000 + getStepDuration(); - earliestAttackTime += 200; - } - } - } + } else if (mType->info.staticAttackChance < static_cast(uniform_random(1, 100))) { + result = getDanceStep(getPosition(), direction); } } } @@ -1480,7 +1343,7 @@ bool Monster::getDistanceStep(const Position& targetPos, Direction& direction, b //escape to NW , W or N [and some extra] bool w = canWalkTo(creaturePos, DIRECTION_WEST); bool n = canWalkTo(creaturePos, DIRECTION_NORTH); - + if (w && n) { direction = boolean_random() ? DIRECTION_WEST : DIRECTION_NORTH; return true; @@ -1913,8 +1776,7 @@ void Monster::death(Creature*) for (Creature* summon : summons) { summon->changeHealth(-summon->getHealth()); - summon->setMaster(nullptr); - summon->decrementReferenceCounter(); + summon->removeMaster(); } summons.clear(); @@ -1993,86 +1855,61 @@ void Monster::updateLookDirection() //look EAST/WEST if (offsetx < 0) { newDir = DIRECTION_WEST; - } - else { + } else { newDir = DIRECTION_EAST; } - } - else if (dx < dy) { + } else if (dx < dy) { //look NORTH/SOUTH if (offsety < 0) { newDir = DIRECTION_NORTH; - } - else { + } else { newDir = DIRECTION_SOUTH; } - } - else { + } else { Direction dir = getDirection(); if (offsetx < 0 && offsety < 0) { - if (offsetx == -1 && offsety == -1) { - if (dir == DIRECTION_NORTH) { - newDir = DIRECTION_WEST; - } - } if (dir == DIRECTION_SOUTH) { newDir = DIRECTION_WEST; - } - else if (dir == DIRECTION_EAST) { + } else if (dir == DIRECTION_NORTH) { + newDir = DIRECTION_WEST; + } else if (dir == DIRECTION_EAST) { newDir = DIRECTION_NORTH; } - } - else if (offsetx < 0 && offsety > 0) { - if (offsetx == -1 && offsety == 1) { - if (dir == DIRECTION_SOUTH) { - newDir = DIRECTION_WEST; - } - } + } else if (offsetx < 0 && offsety > 0) { if (dir == DIRECTION_NORTH) { newDir = DIRECTION_WEST; - } - else if (dir == DIRECTION_EAST) { + } else if (dir == DIRECTION_SOUTH) { + newDir = DIRECTION_WEST; + } else if (dir == DIRECTION_EAST) { newDir = DIRECTION_SOUTH; } - } - else if (offsetx > 0 && offsety < 0) { - if (offsetx == 1 && offsety == -1) { - if (dir == DIRECTION_NORTH) { - newDir = DIRECTION_EAST; - } - } + } else if (offsetx > 0 && offsety < 0) { if (dir == DIRECTION_SOUTH) { newDir = DIRECTION_EAST; - } - else if (dir == DIRECTION_WEST) { + } else if (dir == DIRECTION_NORTH) { + newDir = DIRECTION_EAST; + } else if (dir == DIRECTION_WEST) { newDir = DIRECTION_NORTH; } - } - else { - if (offsetx == 1 && offsety == 1) { - if (dir == DIRECTION_SOUTH) { - newDir = DIRECTION_EAST; - } - } + } else { if (dir == DIRECTION_NORTH) { newDir = DIRECTION_EAST; - } - else if (dir == DIRECTION_WEST) { + } else if (dir == DIRECTION_SOUTH) { + newDir = DIRECTION_EAST; + } else if (dir == DIRECTION_WEST) { newDir = DIRECTION_SOUTH; } } } } - if (direction != newDir) { - g_game.internalCreatureTurn(this, newDir); - } + g_game.internalCreatureTurn(this, newDir); } void Monster::dropLoot(Container* corpse, Creature*) { if (corpse && lootDrop) { - mType->createLoot(corpse); + g_events->eventMonsterOnDropLoot(this, corpse); } } @@ -2084,6 +1921,12 @@ void Monster::setNormalCreatureLight() void Monster::drainHealth(Creature* attacker, int32_t damage) { Creature::drainHealth(attacker, damage); + + if (damage > 0 && randomStepping) { + ignoreFieldDamage = true; + updateMapCache(); + } + if (isInvisible()) { removeCondition(CONDITION_INVISIBLE); } @@ -2104,70 +1947,12 @@ bool Monster::challengeCreature(Creature* creature) bool result = selectTarget(creature); if (result) { - targetChangeCooldown = 1000; + targetChangeCooldown = 8000; targetChangeTicks = 0; } return result; } -bool Monster::convinceCreature(Creature* creature) -{ - Player* player = creature->getPlayer(); - if (player && !player->hasFlag(PlayerFlag_CanConvinceAll)) { - if (!mType->info.isConvinceable) { - return false; - } - } - - if (isSummon()) { - if (getMaster() == creature) { - return false; - } - - Creature* oldMaster = getMaster(); - oldMaster->removeSummon(this); - } - - creature->addSummon(this); - - setFollowCreature(nullptr); - setAttackedCreature(nullptr); - - //destroy summons - for (Creature* summon : summons) { - summon->changeHealth(-summon->getHealth()); - summon->setMaster(nullptr); - summon->decrementReferenceCounter(); - } - summons.clear(); - - isMasterInRange = true; - updateTargetList(); - updateIdleStatus(); - - //Notify surrounding about the change - SpectatorVec list; - g_game.map.getSpectators(list, getPosition(), true); - g_game.map.getSpectators(list, creature->getPosition(), true); - for (Creature* spectator : list) { - spectator->onCreatureConvinced(creature, this); - } - - if (spawn) { - spawn->removeMonster(this); - spawn = nullptr; - } - return true; -} - -void Monster::onCreatureConvinced(const Creature* convincer, const Creature* creature) -{ - if (convincer != this && (isFriend(creature) || isOpponent(creature))) { - updateTargetList(); - updateIdleStatus(); - } -} - void Monster::getPathSearchParams(const Creature* creature, FindPathParams& fpp) const { Creature::getPathSearchParams(creature, fpp); diff --git a/src/monster.h b/src/monster.h index 8447df7..6d9f94f 100644 --- a/src/monster.h +++ b/src/monster.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,17 +26,15 @@ class Creature; class Game; class Spawn; -class Combat; -typedef std::unordered_set CreatureHashSet; -typedef std::list CreatureList; +using CreatureHashSet = std::unordered_set; +using CreatureList = std::list; enum TargetSearchType_t { - TARGETSEARCH_ANY, + TARGETSEARCH_DEFAULT, TARGETSEARCH_RANDOM, + TARGETSEARCH_ATTACKRANGE, TARGETSEARCH_NEAREST, - TARGETSEARCH_WEAKEST, - TARGETSEARCH_MOSTDAMAGE, }; class Monster final : public Creature @@ -46,39 +44,43 @@ class Monster final : public Creature static int32_t despawnRange; static int32_t despawnRadius; - explicit Monster(MonsterType* mtype); + explicit Monster(MonsterType* mType); ~Monster(); // non-copyable Monster(const Monster&) = delete; Monster& operator=(const Monster&) = delete; - Monster* getMonster() final { + Monster* getMonster() override { return this; } - const Monster* getMonster() const final { + const Monster* getMonster() const override { return this; } - void setID() final { + void setID() override { if (id == 0) { id = monsterAutoID++; } } - void removeList() final; - void addList() final; + void removeList() override; + void addList() override; - const std::string& getName() const final { + const std::string& getName() const override { return mType->name; } - const std::string& getNameDescription() const final { + const std::string& getNameDescription() const override { return mType->nameDescription; } - std::string getDescription(int32_t) const final { + std::string getDescription(int32_t) const override { return strDescription + '.'; } + CreatureType_t getType() const override { + return CREATURETYPE_MONSTER; + } + const Position& getMasterPos() const { return masterPos; } @@ -86,22 +88,19 @@ class Monster final : public Creature masterPos = pos; } - RaceType_t getRace() const final { + RaceType_t getRace() const override { 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 getArmor() const override { + return mType->info.armor; } - int32_t getDefense() final; - bool isPushable() const final { + int32_t getDefense() const override { + return mType->info.defense; + } + bool isPushable() const override { return mType->info.pushable && baseSpeed != 0; } - bool isAttackable() const final { + bool isAttackable() const override { return mType->info.isAttackable; } @@ -114,8 +113,8 @@ class Monster final : public Creature bool isHostile() const { return mType->info.isHostile; } - bool canSee(const Position& pos) const final; - bool canSeeInvisibility() const final { + bool canSee(const Position& pos) const override; + bool canSeeInvisibility() const override { return isImmune(CONDITION_INVISIBLE); } uint32_t getManaCost() const { @@ -124,31 +123,35 @@ class Monster final : public Creature void setSpawn(Spawn* spawn) { this->spawn = spawn; } + bool canWalkOnFieldType(CombatType_t combatType) const; - 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 onAttackedCreatureDisappear(bool isLogout) override; - 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 onCreatureAppear(Creature* creature, bool isLogin) override; + void onRemoveCreature(Creature* creature, bool isLogout) override; + void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, const Tile* oldTile, const Position& oldPos, bool teleport) override; + void onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) override; - void onThink(uint32_t interval) final; + void drainHealth(Creature* attacker, int32_t damage) override; + void changeHealth(int32_t healthChange, bool sendHealthChange = true) override; + void onWalk() override; + bool getNextStep(Direction& direction, uint32_t& flags) override; + void onFollowCreatureComplete(const Creature* creature) override; - bool challengeCreature(Creature* creature) final; - bool convinceCreature(Creature* creature) final; + void onThink(uint32_t interval) override; - void setNormalCreatureLight() final; - bool getCombatValues(int32_t& min, int32_t& max) final; + bool challengeCreature(Creature* creature) override; - void doAttacking(uint32_t interval) final; + void setNormalCreatureLight() override; + bool getCombatValues(int32_t& min, int32_t& max) override; - bool searchTarget(TargetSearchType_t searchType); + void doAttacking(uint32_t interval) override; + bool hasExtraSwing() override { + return extraMeleeAttack; + } + + bool searchTarget(TargetSearchType_t searchType = TARGETSEARCH_DEFAULT); bool selectTarget(Creature* creature); const CreatureList& getTargetList() const { @@ -167,9 +170,12 @@ class Monster final : public Creature bool isTargetNearby() const { return stepDuration >= 1; } + bool isIgnoringFieldDamage() const { + return ignoreFieldDamage; + } BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, - bool checkDefense = false, bool checkArmor = false, bool field = false); + bool checkDefense = false, bool checkArmor = false, bool field = false) override; static uint32_t monsterAutoID; @@ -182,13 +188,13 @@ class Monster final : public Creature 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; + int64_t lastMeleeAttack = 0; + uint32_t attackTicks = 0; + uint32_t targetTicks = 0; uint32_t targetChangeTicks = 0; + uint32_t defenseTicks = 0; + uint32_t yellTicks = 0; int32_t minCombatValue = 0; int32_t maxCombatValue = 0; int32_t targetChangeCooldown = 0; @@ -197,8 +203,10 @@ class Monster final : public Creature Position masterPos; bool isIdle = true; + bool extraMeleeAttack = false; bool isMasterInRange = false; - bool egibleToDance = true; + bool randomStepping = false; + bool ignoreFieldDamage = false; void onCreatureEnter(Creature* creature); void onCreatureLeave(Creature* creature); @@ -215,8 +223,8 @@ class Monster final : public Creature void clearTargetList(); void clearFriendList(); - void death(Creature* lastHitCreature) final; - Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) final; + void death(Creature* lastHitCreature) override; + Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) override; void setIdle(bool idle); void updateIdleStatus(); @@ -224,11 +232,12 @@ class Monster final : public Creature return isIdle; } - void onAddCondition(ConditionType_t type) final; - void onEndCondition(ConditionType_t type) final; - void onCreatureConvinced(const Creature* convincer, const Creature* creature) final; + void onAddCondition(ConditionType_t type) override; + void onEndCondition(ConditionType_t type) override; bool canUseAttack(const Position& pos, const Creature* target) const; + bool canUseSpell(const Position& pos, const Position& targetPos, + const spellBlock_t& sb, uint32_t interval, bool& inRange, bool& resetTicks); bool getRandomStep(const Position& creaturePos, Direction& direction) const; bool getDanceStep(const Position& creaturePos, Direction& direction, bool keepAttack = true, bool keepDistance = true); @@ -247,28 +256,25 @@ class Monster final : public Creature bool isFriend(const Creature* creature) const; bool isOpponent(const Creature* creature) const; - uint64_t getLostExperience() const final { + uint64_t getLostExperience() const override { return skillLoss ? mType->info.experience : 0; } - uint16_t getLookCorpse() const final { + uint16_t getLookCorpse() const override { return mType->info.lookcorpse; } - void dropLoot(Container* corpse, Creature* lastHitCreature) final; - uint32_t getDamageImmunities() const final { + void dropLoot(Container* corpse, Creature* lastHitCreature) override; + uint32_t getDamageImmunities() const override { return mType->info.damageImmunities; } - uint32_t getConditionImmunities() const final { + uint32_t getConditionImmunities() const override { return mType->info.conditionImmunities; } - void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const final; - bool useCacheMap() const final { - return true; + void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const override; + bool useCacheMap() const override { + return !randomStepping; } friend class LuaScriptInterface; - friend class AreaSpawnEvent; - friend class Combat; - friend class Creature; }; #endif diff --git a/src/monsters.cpp b/src/monsters.cpp index ca294e2..c135872 100644 --- a/src/monsters.cpp +++ b/src/monsters.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ #include "monster.h" #include "spells.h" #include "combat.h" +#include "weapons.h" #include "configmanager.h" #include "game.h" @@ -40,158 +41,24 @@ spellBlock_t::~spellBlock_t() } } -uint32_t Monsters::getLootRandom() +void MonsterType::loadLoot(MonsterType* monsterType, LootBlock lootBlock) { - return uniform_random(0, MAX_LOOTCHANCE); -} - -void MonsterType::createLoot(Container* corpse) -{ - if (g_config.getNumber(ConfigManager::RATE_LOOT) == 0) { - corpse->startDecaying(); - return; - } - - Item* bagItem = Item::CreateItem(2853, 1); - if (!bagItem) { - return; - } - - Container* bagContainer = bagItem->getContainer(); - if (!bagContainer) { - return; - } - - if (g_game.internalAddItem(corpse, bagItem) != RETURNVALUE_NOERROR) { - corpse->internalAddThing(bagItem); - } - - bool includeBagLoot = false; - for (auto it = info.lootItems.rbegin(), end = info.lootItems.rend(); it != end; ++it) { - std::vector itemList = createLootItem(*it); - if (itemList.empty()) { - continue; - } - - for (Item* item : itemList) { - //check containers - if (Container* container = item->getContainer()) { - if (!createLootContainer(container, *it)) { - delete container; - continue; - } - } - - const ItemType& itemType = Item::items[item->getID()]; - if (itemType.weaponType != WEAPON_NONE || - itemType.stopTime || - itemType.decayTime) { - includeBagLoot = true; - if (g_game.internalAddItem(bagContainer, item) != RETURNVALUE_NOERROR) { - corpse->internalAddThing(item); - } - } else { - if (g_game.internalAddItem(corpse, item) != RETURNVALUE_NOERROR) { - corpse->internalAddThing(item); - } + if (lootBlock.childLoot.empty()) { + bool isContainer = Item::items[lootBlock.id].isContainer(); + if (isContainer) { + for (LootBlock child : lootBlock.childLoot) { + lootBlock.childLoot.push_back(child); } } + monsterType->info.lootItems.push_back(lootBlock); + } else { + monsterType->info.lootItems.push_back(lootBlock); } - - if (!includeBagLoot) { - g_game.internalRemoveItem(bagItem); - } - - if (g_config.getBoolean(ConfigManager::SHOW_MONSTER_LOOT)) { - Player* owner = g_game.getPlayerByID(corpse->getCorpseOwner()); - if (owner) { - std::ostringstream ss; - ss << "Loot of " << nameDescription << ": " << corpse->getContentDescription(); - - if (owner->getParty()) { - owner->getParty()->broadcastPartyLoot(ss.str()); - } else { - owner->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); - } - } - } - - corpse->startDecaying(); -} - -std::vector MonsterType::createLootItem(const LootBlock& lootBlock) -{ - int32_t itemCount = 0; - - uint32_t randvalue = Monsters::getLootRandom(); - uint32_t extraMoney = g_config.getNumber(ConfigManager::MONEY_RATE); - uint32_t countMax = lootBlock.countmax + 1; - - if (randvalue < g_config.getNumber(ConfigManager::RATE_LOOT) * lootBlock.chance) { - if (Item::items[lootBlock.id].stackable) { - if (lootBlock.id == 3031) { - countMax *= extraMoney; - } - - itemCount = randvalue % countMax; - } else { - itemCount = 1; - } - } - - std::vector itemList; - while (itemCount > 0) { - uint16_t n = static_cast(std::min(itemCount, 100)); - Item* tmpItem = Item::CreateItem(lootBlock.id, n); - if (!tmpItem) { - break; - } - - itemCount -= n; - - if (lootBlock.subType != -1) { - tmpItem->setSubType(lootBlock.subType); - } - - if (lootBlock.actionId != -1) { - tmpItem->setActionId(lootBlock.actionId); - } - - if (!lootBlock.text.empty()) { - tmpItem->setText(lootBlock.text); - } - - itemList.push_back(tmpItem); - } - return itemList; -} - -bool MonsterType::createLootContainer(Container* parent, const LootBlock& lootblock) -{ - auto it = lootblock.childLoot.begin(), end = lootblock.childLoot.end(); - if (it == end) { - return true; - } - - for (; it != end && parent->size() < parent->capacity(); ++it) { - auto itemList = createLootItem(*it); - for (Item* tmpItem : itemList) { - if (Container* container = tmpItem->getContainer()) { - if (!createLootContainer(container, *it)) { - delete container; - } else { - parent->internalAddThing(container); - } - } else { - parent->internalAddThing(tmpItem); - } - } - } - return !parent->empty(); } bool Monsters::loadFromXml(bool reloading /*= false*/) { + unloadedMonsters = {}; pugi::xml_document doc; pugi::xml_parse_result result = doc.load_file("data/monster/monsters.xml"); if (!result) { @@ -201,30 +68,13 @@ bool Monsters::loadFromXml(bool reloading /*= false*/) loaded = true; - std::list> monsterScriptList; for (auto monsterNode : doc.child("monsters").children()) { - loadMonster("data/monster/" + std::string(monsterNode.attribute("file").as_string()), monsterNode.attribute("name").as_string(), monsterScriptList, reloading); - } - - if (!monsterScriptList.empty()) { - if (!scriptInterface) { - scriptInterface.reset(new LuaScriptInterface("Monster Interface")); - scriptInterface->initState(); - } - - for (const auto& scriptEntry : monsterScriptList) { - MonsterType* mType = scriptEntry.first; - if (scriptInterface->loadFile("data/monster/scripts/" + scriptEntry.second) == 0) { - mType->info.scriptInterface = scriptInterface.get(); - mType->info.creatureAppearEvent = scriptInterface->getEvent("onCreatureAppear"); - mType->info.creatureDisappearEvent = scriptInterface->getEvent("onCreatureDisappear"); - mType->info.creatureMoveEvent = scriptInterface->getEvent("onCreatureMove"); - mType->info.creatureSayEvent = scriptInterface->getEvent("onCreatureSay"); - mType->info.thinkEvent = scriptInterface->getEvent("onThink"); - } else { - std::cout << "[Warning - Monsters::loadMonster] Can not load script: " << scriptEntry.second << std::endl; - std::cout << scriptInterface->getLastLuaError() << std::endl; - } + std::string name = asLowerCaseString(monsterNode.attribute("name").as_string()); + std::string file = "data/monster/" + std::string(monsterNode.attribute("file").as_string()); + if (reloading && monsters.find(name) != monsters.end()) { + loadMonster(file, name, true); + } else { + unloadedMonsters.emplace(name, file); } } return true; @@ -239,13 +89,15 @@ bool Monsters::reload() return loadFromXml(true); } -ConditionDamage* Monsters::getDamageCondition(ConditionType_t conditionType, int32_t cycle, int32_t count, int32_t max_count) +ConditionDamage* Monsters::getDamageCondition(ConditionType_t conditionType, + int32_t maxDamage, int32_t minDamage, int32_t startDamage, uint32_t tickInterval) { ConditionDamage* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, 0, 0)); - - condition->setParam(CONDITION_PARAM_CYCLE, cycle); - condition->setParam(CONDITION_PARAM_COUNT, count); - condition->setParam(CONDITION_PARAM_MAX_COUNT, max_count); + condition->setParam(CONDITION_PARAM_TICKINTERVAL, tickInterval); + condition->setParam(CONDITION_PARAM_MINVALUE, minDamage); + condition->setParam(CONDITION_PARAM_MAXVALUE, maxDamage); + condition->setParam(CONDITION_PARAM_STARTVALUE, startDamage); + condition->setParam(CONDITION_PARAM_DELAYED, 1); return condition; } @@ -266,16 +118,15 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co return false; } + if ((attr = node.attribute("speed")) || (attr = node.attribute("interval"))) { + sb.speed = std::max(1, pugi::cast(attr.value())); + } + if ((attr = node.attribute("chance"))) { uint32_t chance = pugi::cast(attr.value()); if (chance > 100) { chance = 100; } - - if (chance == 0) { - std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - Spell chance is zero: " << name << std::endl; - } - sb.chance = chance; } @@ -285,8 +136,6 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co range = Map::maxViewportX * 2; } sb.range = range; - } else { - sb.range = Map::maxClientViewportX; } if ((attr = node.attribute("min"))) { @@ -368,22 +217,88 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co std::string tmpName = asLowerCaseString(name); - if (tmpName == "physical") { + if (tmpName == "melee") { + sb.isMelee = true; + + pugi::xml_attribute attackAttribute, skillAttribute; + if ((attackAttribute = node.attribute("attack")) && (skillAttribute = node.attribute("skill"))) { + sb.minCombatValue = 0; + sb.maxCombatValue = -Weapons::getMaxMeleeDamage(pugi::cast(skillAttribute.value()), pugi::cast(attackAttribute.value())); + } + + ConditionType_t conditionType = CONDITION_NONE; + int32_t minDamage = 0; + int32_t maxDamage = 0; + uint32_t tickInterval = 2000; + + if ((attr = node.attribute("fire"))) { + conditionType = CONDITION_FIRE; + + minDamage = pugi::cast(attr.value()); + maxDamage = minDamage; + tickInterval = 9000; + } else if ((attr = node.attribute("poison"))) { + conditionType = CONDITION_POISON; + + minDamage = pugi::cast(attr.value()); + maxDamage = minDamage; + tickInterval = 4000; + } else if ((attr = node.attribute("energy"))) { + conditionType = CONDITION_ENERGY; + + minDamage = pugi::cast(attr.value()); + maxDamage = minDamage; + tickInterval = 10000; + } else if ((attr = node.attribute("drown"))) { + conditionType = CONDITION_DROWN; + + minDamage = pugi::cast(attr.value()); + maxDamage = minDamage; + tickInterval = 5000; + } else if ((attr = node.attribute("freeze"))) { + conditionType = CONDITION_FREEZING; + + minDamage = pugi::cast(attr.value()); + maxDamage = minDamage; + tickInterval = 8000; + } else if ((attr = node.attribute("dazzle"))) { + conditionType = CONDITION_DAZZLED; + + minDamage = pugi::cast(attr.value()); + maxDamage = minDamage; + tickInterval = 10000; + } else if ((attr = node.attribute("curse"))) { + conditionType = CONDITION_CURSED; + + minDamage = pugi::cast(attr.value()); + maxDamage = minDamage; + tickInterval = 4000; + } else if ((attr = node.attribute("bleed")) || (attr = node.attribute("physical"))) { + conditionType = CONDITION_BLEEDING; + tickInterval = 4000; + } + + if ((attr = node.attribute("tick"))) { + int32_t value = pugi::cast(attr.value()); + if (value > 0) { + tickInterval = value; + } + } + + if (conditionType != CONDITION_NONE) { + Condition* condition = getDamageCondition(conditionType, maxDamage, minDamage, 0, tickInterval); + combat->addCondition(condition); + } + + sb.range = 1; combat->setParam(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE); combat->setParam(COMBAT_PARAM_BLOCKARMOR, 1); combat->setParam(COMBAT_PARAM_BLOCKSHIELD, 1); - uint32_t tD = this->getMonsterType(description)->info.targetDistance; - if (tD == 1) { - if (sb.range > 1) { - combat->setOrigin(ORIGIN_RANGED); - } - else { - combat->setOrigin(ORIGIN_MELEE); - } - } - else if (tD > 1 && sb.range > 1) { - combat->setOrigin(ORIGIN_RANGED); - } + combat->setOrigin(ORIGIN_MELEE); + } else if (tmpName == "physical") { + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE); + combat->setParam(COMBAT_PARAM_BLOCKARMOR, 1); + combat->setOrigin(ORIGIN_RANGED); } else if (tmpName == "bleed") { combat->setParam(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE); } else if (tmpName == "poison" || tmpName == "earth") { @@ -394,6 +309,12 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co combat->setParam(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE); } else if (tmpName == "drown") { combat->setParam(COMBAT_PARAM_TYPE, COMBAT_DROWNDAMAGE); + } else if (tmpName == "ice") { + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE); + } else if (tmpName == "holy") { + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_HOLYDAMAGE); + } else if (tmpName == "death") { + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_DEATHDAMAGE); } else if (tmpName == "lifedrain") { combat->setParam(COMBAT_PARAM_TYPE, COMBAT_LIFEDRAIN); } else if (tmpName == "manadrain") { @@ -403,7 +324,6 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); } else if (tmpName == "speed") { int32_t speedChange = 0; - int32_t variation = 0; int32_t duration = 10000; if ((attr = node.attribute("duration"))) { @@ -412,10 +332,10 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co if ((attr = node.attribute("speedchange"))) { speedChange = pugi::cast(attr.value()); - } - - if ((attr = node.attribute("variation"))) { - variation = pugi::cast(attr.value()); + if (speedChange < -1000) { + //cant be slower than 100% + speedChange = -1000; + } } ConditionType_t conditionType; @@ -427,9 +347,8 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co } ConditionSpeed* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, duration, 0)); - condition->setVariation(variation); - condition->setSpeedDelta(speedChange); - combat->setCondition(condition); + condition->setFormulaVars(speedChange / 1000.0, 0, speedChange / 1000.0, 0); + combat->addCondition(condition); } else if (tmpName == "outfit") { int32_t duration = 10000; @@ -443,7 +362,7 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co ConditionOutfit* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0)); condition->setOutfit(mType->info.outfit); combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); - combat->setCondition(condition); + combat->addCondition(condition); } } else if ((attr = node.attribute("item"))) { Outfit_t outfit; @@ -452,7 +371,7 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co ConditionOutfit* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0)); condition->setOutfit(outfit); combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); - combat->setCondition(condition); + combat->addCondition(condition); } } else if (tmpName == "invisible") { int32_t duration = 10000; @@ -463,7 +382,7 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_INVISIBLE, duration, 0); combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); - combat->setCondition(condition); + combat->addCondition(condition); } else if (tmpName == "drunk") { int32_t duration = 10000; @@ -472,7 +391,7 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co } Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_DRUNK, duration, 0); - combat->setCondition(condition); + combat->addCondition(condition); } else if (tmpName == "firefield") { combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_FIREFIELD_PVP_FULL); } else if (tmpName == "poisonfield") { @@ -482,42 +401,59 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co } else if (tmpName == "firecondition" || tmpName == "energycondition" || tmpName == "earthcondition" || tmpName == "poisoncondition" || tmpName == "icecondition" || tmpName == "freezecondition" || - tmpName == "physicalcondition" || tmpName == "drowncondition") { + tmpName == "deathcondition" || tmpName == "cursecondition" || + tmpName == "holycondition" || tmpName == "dazzlecondition" || + tmpName == "drowncondition" || tmpName == "bleedcondition" || + tmpName == "physicalcondition") { ConditionType_t conditionType = CONDITION_NONE; + uint32_t tickInterval = 2000; if (tmpName == "firecondition") { conditionType = CONDITION_FIRE; + tickInterval = 10000; } else if (tmpName == "poisoncondition" || tmpName == "earthcondition") { conditionType = CONDITION_POISON; + tickInterval = 4000; } else if (tmpName == "energycondition") { conditionType = CONDITION_ENERGY; + tickInterval = 10000; } else if (tmpName == "drowncondition") { conditionType = CONDITION_DROWN; + tickInterval = 5000; + } else if (tmpName == "freezecondition" || tmpName == "icecondition") { + conditionType = CONDITION_FREEZING; + tickInterval = 10000; + } else if (tmpName == "cursecondition" || tmpName == "deathcondition") { + conditionType = CONDITION_CURSED; + tickInterval = 4000; + } else if (tmpName == "dazzlecondition" || tmpName == "holycondition") { + conditionType = CONDITION_DAZZLED; + tickInterval = 10000; + } else if (tmpName == "physicalcondition" || tmpName == "bleedcondition") { + conditionType = CONDITION_BLEEDING; + tickInterval = 4000; } - int32_t cycle = 0; - if ((attr = node.attribute("count"))) { - cycle = std::abs(pugi::cast(attr.value())); - } else { - std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - missing count attribute" << std::endl; - delete combat; - return false; + if ((attr = node.attribute("tick"))) { + int32_t value = pugi::cast(attr.value()); + if (value > 0) { + tickInterval = value; + } } - int32_t count = 0; + int32_t minDamage = std::abs(sb.minCombatValue); + int32_t maxDamage = std::abs(sb.maxCombatValue); + int32_t startDamage = 0; - if (conditionType == CONDITION_POISON) { - count = 3; - } else if (conditionType == CONDITION_FIRE) { - count = 8; - cycle /= 10; - } else if (conditionType == CONDITION_ENERGY) { - count = 10; - cycle /= 20; + if ((attr = node.attribute("start"))) { + int32_t value = std::abs(pugi::cast(attr.value())); + if (value <= minDamage) { + startDamage = value; + } } - Condition* condition = getDamageCondition(conditionType, cycle, count, count); - combat->setCondition(condition); + Condition* condition = getDamageCondition(conditionType, maxDamage, minDamage, startDamage, tickInterval); + combat->addCondition(condition); } else if (tmpName == "strength") { // } else if (tmpName == "effect") { @@ -529,7 +465,6 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co } combat->setPlayerCombatValues(COMBAT_FORMULA_DAMAGE, sb.minCombatValue, 0, sb.maxCombatValue, 0); - combatSpell = new CombatSpell(combat, needTarget, needDirection); for (auto attributeNode : node.children()) { @@ -537,7 +472,7 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co const char* value = attr.value(); if (strcasecmp(value, "shooteffect") == 0) { if ((attr = attributeNode.attribute("value"))) { - ShootType_t shoot = getShootType(attr.as_string()); + ShootType_t shoot = getShootType(asLowerCaseString(attr.as_string())); if (shoot != CONST_ANI_NONE) { combat->setParam(COMBAT_PARAM_DISTANCEEFFECT, shoot); } else { @@ -546,7 +481,7 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co } } else if (strcasecmp(value, "areaeffect") == 0) { if ((attr = attributeNode.attribute("value"))) { - MagicEffectClasses effect = getMagicEffect(attr.as_string()); + MagicEffectClasses effect = getMagicEffect(asLowerCaseString(attr.as_string())); if (effect != CONST_ME_NONE) { combat->setParam(COMBAT_PARAM_EFFECT, effect); } else { @@ -567,39 +502,269 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co return true; } -bool Monsters::loadMonster(const std::string& file, const std::string& monsterName, std::list>& monsterScriptList, bool reloading /*= false*/) +bool Monsters::deserializeSpell(MonsterSpell* spell, spellBlock_t& sb, const std::string& description) +{ + if (!spell->scriptName.empty()) { + spell->isScripted = true; + } else if (!spell->name.empty()) { + spell->isScripted = false; + } else { + return false; + } + + sb.speed = spell->interval; + + if (spell->chance > 100) { + sb.chance = 100; + } else { + sb.chance = spell->chance; + } + + if (spell->range > (Map::maxViewportX * 2)) { + spell->range = Map::maxViewportX * 2; + } + sb.range = spell->range; + + sb.minCombatValue = spell->minCombatValue; + sb.maxCombatValue = spell->maxCombatValue; + if (std::abs(sb.minCombatValue) > std::abs(sb.maxCombatValue)) { + int32_t value = sb.maxCombatValue; + sb.maxCombatValue = sb.minCombatValue; + sb.minCombatValue = value; + } + + sb.spell = g_spells->getSpellByName(spell->name); + if (sb.spell) { + return true; + } + + CombatSpell* combatSpell = nullptr; + + if (spell->isScripted) { + std::unique_ptr combatSpellPtr(new CombatSpell(nullptr, spell->needTarget, spell->needDirection)); + if (!combatSpellPtr->loadScript("data/" + g_spells->getScriptBaseName() + "/scripts/" + spell->scriptName)) { + std::cout << "cannot find file" << std::endl; + return false; + } + + if (!combatSpellPtr->loadScriptCombat()) { + return false; + } + + combatSpell = combatSpellPtr.release(); + combatSpell->getCombat()->setPlayerCombatValues(COMBAT_FORMULA_DAMAGE, sb.minCombatValue, 0, sb.maxCombatValue, 0); + } else { + std::unique_ptr combat{ new Combat }; + sb.combatSpell = true; + + if (spell->length > 0) { + spell->spread = std::max(0, spell->spread); + + AreaCombat* area = new AreaCombat(); + area->setupArea(spell->length, spell->spread); + combat->setArea(area); + + spell->needDirection = true; + } + + if (spell->radius > 0) { + AreaCombat* area = new AreaCombat(); + area->setupArea(spell->radius); + combat->setArea(area); + } + + std::string tmpName = asLowerCaseString(spell->name); + + if (tmpName == "melee") { + sb.isMelee = true; + + if (spell->attack > 0 && spell->skill > 0) { + sb.minCombatValue = 0; + sb.maxCombatValue = -Weapons::getMaxMeleeDamage(spell->skill, spell->attack); + } + + ConditionType_t conditionType = CONDITION_NONE; + int32_t minDamage = 0; + int32_t maxDamage = 0; + uint32_t tickInterval = 2000; + + if (spell->conditionType != CONDITION_NONE) { + conditionType = spell->conditionType; + + minDamage = spell->conditionMinDamage; + maxDamage = minDamage; + if (spell->tickInterval != 0) { + tickInterval = spell->tickInterval; + } + + Condition* condition = getDamageCondition(conditionType, maxDamage, minDamage, spell->conditionStartDamage, tickInterval); + combat->addCondition(condition); + } + + sb.range = 1; + combat->setParam(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE); + combat->setParam(COMBAT_PARAM_BLOCKARMOR, 1); + combat->setParam(COMBAT_PARAM_BLOCKSHIELD, 1); + combat->setOrigin(ORIGIN_MELEE); + } else if (tmpName == "combat") { + if (spell->combatType == COMBAT_PHYSICALDAMAGE) { + combat->setParam(COMBAT_PARAM_BLOCKARMOR, 1); + combat->setOrigin(ORIGIN_RANGED); + } else if (spell->combatType == COMBAT_HEALING) { + combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); + } + combat->setParam(COMBAT_PARAM_TYPE, spell->combatType); + } else if (tmpName == "speed") { + int32_t speedChange = 0; + int32_t duration = 10000; + + if (spell->duration != 0) { + duration = spell->duration; + } + + if (spell->speedChange != 0) { + speedChange = spell->speedChange; + if (speedChange < -1000) { + //cant be slower than 100% + speedChange = -1000; + } + } + + ConditionType_t conditionType; + if (speedChange > 0) { + conditionType = CONDITION_HASTE; + combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); + } else { + conditionType = CONDITION_PARALYZE; + } + + ConditionSpeed* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, duration, 0)); + condition->setFormulaVars(speedChange / 1000.0, 0, speedChange / 1000.0, 0); + combat->addCondition(condition); + } else if (tmpName == "outfit") { + int32_t duration = 10000; + + if (spell->duration != 0) { + duration = spell->duration; + } + + ConditionOutfit* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0)); + condition->setOutfit(spell->outfit); + combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); + combat->addCondition(condition); + } else if (tmpName == "invisible") { + int32_t duration = 10000; + + if (spell->duration != 0) { + duration = spell->duration; + } + + Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_INVISIBLE, duration, 0); + combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); + combat->addCondition(condition); + } else if (tmpName == "drunk") { + int32_t duration = 10000; + + if (spell->duration != 0) { + duration = spell->duration; + } + + Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_DRUNK, duration, 0); + combat->addCondition(condition); + } else if (tmpName == "firefield") { + combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_FIREFIELD_PVP_FULL); + } else if (tmpName == "poisonfield") { + combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_POISONFIELD_PVP); + } else if (tmpName == "energyfield") { + combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_ENERGYFIELD_PVP); + } else if (tmpName == "condition") { + uint32_t tickInterval = 2000; + + if (spell->conditionType == CONDITION_NONE) { + std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - Condition is not set for: " << spell->name << std::endl; + } + + if (spell->tickInterval != 0) { + int32_t value = spell->tickInterval; + if (value > 0) { + tickInterval = value; + } + } + + int32_t minDamage = std::abs(spell->conditionMinDamage); + int32_t maxDamage = std::abs(spell->conditionMaxDamage); + int32_t startDamage = 0; + + if (spell->conditionStartDamage != 0) { + int32_t value = std::abs(spell->conditionStartDamage); + if (value <= minDamage) { + startDamage = value; + } + } + + Condition* condition = getDamageCondition(spell->conditionType, maxDamage, minDamage, startDamage, tickInterval); + combat->addCondition(condition); + } else if (tmpName == "strength") { + // + } else if (tmpName == "effect") { + // + } else { + std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - Unknown spell name: " << spell->name << std::endl; + } + + if (spell->needTarget) { + if (spell->shoot != CONST_ANI_NONE) { + combat->setParam(COMBAT_PARAM_DISTANCEEFFECT, spell->shoot); + } + } + + if (spell->effect != CONST_ME_NONE) { + combat->setParam(COMBAT_PARAM_EFFECT, spell->effect); + } + + combat->setPlayerCombatValues(COMBAT_FORMULA_DAMAGE, sb.minCombatValue, 0, sb.maxCombatValue, 0); + combatSpell = new CombatSpell(combat.release(), spell->needTarget, spell->needDirection); + } + + sb.spell = combatSpell; + if (combatSpell) { + sb.combatSpell = true; + } + return true; +} + +MonsterType* Monsters::loadMonster(const std::string& file, const std::string& monsterName, bool reloading /*= false*/) { MonsterType* mType = nullptr; - bool new_mType = true; pugi::xml_document doc; pugi::xml_parse_result result = doc.load_file(file.c_str()); if (!result) { printXMLError("Error - Monsters::loadMonster", file, result); - return false; + return nullptr; } pugi::xml_node monsterNode = doc.child("monster"); if (!monsterNode) { std::cout << "[Error - Monsters::loadMonster] Missing monster node in: " << file << std::endl; - return false; + return nullptr; } pugi::xml_attribute attr; if (!(attr = monsterNode.attribute("name"))) { std::cout << "[Error - Monsters::loadMonster] Missing name in: " << file << std::endl; - return false; + return nullptr; } if (reloading) { - mType = getMonsterType(monsterName); - if (mType != nullptr) { - new_mType = false; + auto it = monsters.find(asLowerCaseString(monsterName)); + if (it != monsters.end()) { + mType = &it->second; mType->info = {}; } } - if (new_mType) { + if (!mType) { mType = &monsters[asLowerCaseString(monsterName)]; } @@ -622,6 +787,8 @@ bool Monsters::loadMonster(const std::string& file, const std::string& monsterNa mType->info.race = RACE_UNDEAD; } else if (tmpStrValue == "fire" || tmpInt == 4) { mType->info.race = RACE_FIRE; + } else if (tmpStrValue == "energy" || tmpInt == 5) { + mType->info.race = RACE_ENERGY; } else { std::cout << "[Warning - Monsters::loadMonster] Unknown race type " << attr.as_string() << ". " << file << std::endl; } @@ -640,11 +807,27 @@ bool Monsters::loadMonster(const std::string& file, const std::string& monsterNa } if ((attr = monsterNode.attribute("skull"))) { - mType->info.skull = getSkullType(attr.as_string()); + mType->info.skull = getSkullType(asLowerCaseString(attr.as_string())); } if ((attr = monsterNode.attribute("script"))) { - monsterScriptList.emplace_back(mType, attr.as_string()); + if (!scriptInterface) { + scriptInterface.reset(new LuaScriptInterface("Monster Interface")); + scriptInterface->initState(); + } + + std::string script = attr.as_string(); + if (scriptInterface->loadFile("data/monster/scripts/" + script) == 0) { + mType->info.scriptInterface = scriptInterface.get(); + mType->info.creatureAppearEvent = scriptInterface->getEvent("onCreatureAppear"); + mType->info.creatureDisappearEvent = scriptInterface->getEvent("onCreatureDisappear"); + mType->info.creatureMoveEvent = scriptInterface->getEvent("onCreatureMove"); + mType->info.creatureSayEvent = scriptInterface->getEvent("onCreatureSay"); + mType->info.thinkEvent = scriptInterface->getEvent("onThink"); + } else { + std::cout << "[Warning - Monsters::loadMonster] Can not load script: " << script << std::endl; + std::cout << scriptInterface->getLastLuaError() << std::endl; + } } pugi::xml_node node; @@ -682,6 +865,14 @@ bool Monsters::loadMonster(const std::string& file, const std::string& monsterNa mType->info.canPushItems = attr.as_bool(); } else if (strcasecmp(attrName, "canpushcreatures") == 0) { mType->info.canPushCreatures = attr.as_bool(); + } else if (strcasecmp(attrName, "staticattack") == 0) { + uint32_t staticAttack = pugi::cast(attr.value()); + if (staticAttack > 100) { + std::cout << "[Warning - Monsters::loadMonster] staticattack greater than 100. " << file << std::endl; + staticAttack = 100; + } + + mType->info.staticAttackChance = staticAttack; } else if (strcasecmp(attrName, "lightlevel") == 0) { mType->info.light.level = pugi::cast(attr.value()); } else if (strcasecmp(attrName, "lightcolor") == 0) { @@ -692,6 +883,12 @@ bool Monsters::loadMonster(const std::string& file, const std::string& monsterNa mType->info.runAwayHealth = pugi::cast(attr.value()); } else if (strcasecmp(attrName, "hidehealth") == 0) { mType->info.hiddenHealth = attr.as_bool(); + } else if (strcasecmp(attrName, "canwalkonenergy") == 0) { + mType->info.canWalkOnEnergy = attr.as_bool(); + } else if (strcasecmp(attrName, "canwalkonfire") == 0) { + mType->info.canWalkOnFire = attr.as_bool(); + } else if (strcasecmp(attrName, "canwalkonpoison") == 0) { + mType->info.canWalkOnPoison = attr.as_bool(); } else { std::cout << "[Warning - Monsters::loadMonster] Unknown flag attribute: " << attrName << ". " << file << std::endl; } @@ -718,34 +915,6 @@ bool Monsters::loadMonster(const std::string& file, const std::string& monsterNa } } - if ((node = monsterNode.child("targetstrategy"))) { - if ((attr = node.attribute("nearest"))) { - mType->info.strategyNearestEnemy = pugi::cast(attr.value()); - } else { - std::cout << "[Warning - Monsters::loadMonster] Missing nearest enemy chance. " << file << std::endl; - } - - if ((attr = node.attribute("weakest"))) { - mType->info.strategyWeakestEnemy = pugi::cast(attr.value()); - } else { - std::cout << "[Warning - Monsters::loadMonster] Missing weakest enemy chance. " << file << std::endl; - } - - if ((attr = node.attribute("mostdamage"))) { - mType->info.strategyMostDamageEnemy = pugi::cast(attr.value()); - } else { - std::cout << "[Warning - Monsters::loadMonster] Missing most damage enemy chance. " << file << std::endl; - } - - if ((attr = node.attribute("random"))) { - mType->info.strategyRandomEnemy = pugi::cast(attr.value()); - } else { - std::cout << "[Warning - Monsters::loadMonster] Missing random enemy chance. " << file << std::endl; - } - } else { - std::cout << "[Warning - Monsters::loadMonster] Missing target change strategies. " << file << std::endl; - } - if ((node = monsterNode.child("look"))) { if ((attr = node.attribute("type"))) { mType->info.outfit.lookType = pugi::cast(attr.value()); @@ -775,24 +944,16 @@ bool Monsters::loadMonster(const std::string& file, const std::string& monsterNa std::cout << "[Warning - Monsters::loadMonster] Missing look type/typeex. " << file << std::endl; } + if ((attr = node.attribute("mount"))) { + mType->info.outfit.lookMount = pugi::cast(attr.value()); + } + if ((attr = node.attribute("corpse"))) { mType->info.lookcorpse = pugi::cast(attr.value()); } } if ((node = monsterNode.child("attacks"))) { - if ((attr = node.attribute("attack"))) { - mType->info.attack = pugi::cast(attr.value()); - } - - if ((attr = node.attribute("skill"))) { - mType->info.skill = pugi::cast(attr.value()); - } - - if ((attr = node.attribute("poison"))) { - mType->info.poison = pugi::cast(attr.value()); - } - for (auto attackNode : node.children()) { spellBlock_t sb; if (deserializeSpell(attackNode, sb, monsterName)) { @@ -828,19 +989,29 @@ bool Monsters::loadMonster(const std::string& file, const std::string& monsterNa std::string tmpStrValue = asLowerCaseString(attr.as_string()); if (tmpStrValue == "physical") { mType->info.damageImmunities |= COMBAT_PHYSICALDAMAGE; + mType->info.conditionImmunities |= CONDITION_BLEEDING; } else if (tmpStrValue == "energy") { mType->info.damageImmunities |= COMBAT_ENERGYDAMAGE; mType->info.conditionImmunities |= CONDITION_ENERGY; } else if (tmpStrValue == "fire") { mType->info.damageImmunities |= COMBAT_FIREDAMAGE; mType->info.conditionImmunities |= CONDITION_FIRE; - } else if (tmpStrValue == "drown") { - mType->info.damageImmunities |= COMBAT_DROWNDAMAGE; - mType->info.conditionImmunities |= CONDITION_DROWN; } else if (tmpStrValue == "poison" || tmpStrValue == "earth") { mType->info.damageImmunities |= COMBAT_EARTHDAMAGE; mType->info.conditionImmunities |= CONDITION_POISON; + } else if (tmpStrValue == "drown") { + mType->info.damageImmunities |= COMBAT_DROWNDAMAGE; + mType->info.conditionImmunities |= CONDITION_DROWN; + } else if (tmpStrValue == "ice") { + mType->info.damageImmunities |= COMBAT_ICEDAMAGE; + mType->info.conditionImmunities |= CONDITION_FREEZING; + } else if (tmpStrValue == "holy") { + mType->info.damageImmunities |= COMBAT_HOLYDAMAGE; + mType->info.conditionImmunities |= CONDITION_DAZZLED; + } else if (tmpStrValue == "death") { + mType->info.damageImmunities |= COMBAT_DEATHDAMAGE; + mType->info.conditionImmunities |= CONDITION_CURSED; } else if (tmpStrValue == "lifedrain") { mType->info.damageImmunities |= COMBAT_LIFEDRAIN; } else if (tmpStrValue == "manadrain") { @@ -853,12 +1024,15 @@ bool Monsters::loadMonster(const std::string& file, const std::string& monsterNa mType->info.conditionImmunities |= CONDITION_DRUNK; } else if (tmpStrValue == "invisible" || tmpStrValue == "invisibility") { mType->info.conditionImmunities |= CONDITION_INVISIBLE; + } else if (tmpStrValue == "bleed") { + mType->info.conditionImmunities |= CONDITION_BLEEDING; } else { std::cout << "[Warning - Monsters::loadMonster] Unknown immunity name " << attr.as_string() << ". " << file << std::endl; } } else if ((attr = immunityNode.attribute("physical"))) { if (attr.as_bool()) { mType->info.damageImmunities |= COMBAT_PHYSICALDAMAGE; + mType->info.conditionImmunities |= CONDITION_BLEEDING; } } else if ((attr = immunityNode.attribute("energy"))) { if (attr.as_bool()) { @@ -870,15 +1044,30 @@ bool Monsters::loadMonster(const std::string& file, const std::string& monsterNa mType->info.damageImmunities |= COMBAT_FIREDAMAGE; mType->info.conditionImmunities |= CONDITION_FIRE; } + } else if ((attr = immunityNode.attribute("poison")) || (attr = immunityNode.attribute("earth"))) { + if (attr.as_bool()) { + mType->info.damageImmunities |= COMBAT_EARTHDAMAGE; + mType->info.conditionImmunities |= CONDITION_POISON; + } } else if ((attr = immunityNode.attribute("drown"))) { if (attr.as_bool()) { mType->info.damageImmunities |= COMBAT_DROWNDAMAGE; mType->info.conditionImmunities |= CONDITION_DROWN; } - } else if ((attr = immunityNode.attribute("poison")) || (attr = immunityNode.attribute("earth"))) { + } else if ((attr = immunityNode.attribute("ice"))) { if (attr.as_bool()) { - mType->info.damageImmunities |= COMBAT_EARTHDAMAGE; - mType->info.conditionImmunities |= CONDITION_POISON; + mType->info.damageImmunities |= COMBAT_ICEDAMAGE; + mType->info.conditionImmunities |= CONDITION_FREEZING; + } + } else if ((attr = immunityNode.attribute("holy"))) { + if (attr.as_bool()) { + mType->info.damageImmunities |= COMBAT_HOLYDAMAGE; + mType->info.conditionImmunities |= CONDITION_DAZZLED; + } + } else if ((attr = immunityNode.attribute("death"))) { + if (attr.as_bool()) { + mType->info.damageImmunities |= COMBAT_DEATHDAMAGE; + mType->info.conditionImmunities |= CONDITION_CURSED; } } else if ((attr = immunityNode.attribute("lifedrain"))) { if (attr.as_bool()) { @@ -896,6 +1085,10 @@ bool Monsters::loadMonster(const std::string& file, const std::string& monsterNa if (attr.as_bool()) { mType->info.conditionImmunities |= CONDITION_OUTFIT; } + } else if ((attr = immunityNode.attribute("bleed"))) { + if (attr.as_bool()) { + mType->info.conditionImmunities |= CONDITION_BLEEDING; + } } else if ((attr = immunityNode.attribute("drunk"))) { if (attr.as_bool()) { mType->info.conditionImmunities |= CONDITION_DRUNK; @@ -911,6 +1104,18 @@ bool Monsters::loadMonster(const std::string& file, const std::string& monsterNa } if ((node = monsterNode.child("voices"))) { + if ((attr = node.attribute("speed")) || (attr = node.attribute("interval"))) { + mType->info.yellSpeedTicks = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing voices speed. " << file << std::endl; + } + + if ((attr = node.attribute("chance"))) { + mType->info.yellChance = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing voices chance. " << file << std::endl; + } + for (auto voiceNode : node.children()) { voiceBlock_t vb; if ((attr = voiceNode.attribute("sentence"))) { @@ -943,12 +1148,20 @@ bool Monsters::loadMonster(const std::string& file, const std::string& monsterNa for (auto elementNode : node.children()) { if ((attr = elementNode.attribute("physicalPercent"))) { mType->info.elementMap[COMBAT_PHYSICALDAMAGE] = pugi::cast(attr.value()); + } else if ((attr = elementNode.attribute("icePercent"))) { + mType->info.elementMap[COMBAT_ICEDAMAGE] = pugi::cast(attr.value()); } else if ((attr = elementNode.attribute("poisonPercent")) || (attr = elementNode.attribute("earthPercent"))) { mType->info.elementMap[COMBAT_EARTHDAMAGE] = pugi::cast(attr.value()); } else if ((attr = elementNode.attribute("firePercent"))) { mType->info.elementMap[COMBAT_FIREDAMAGE] = pugi::cast(attr.value()); } else if ((attr = elementNode.attribute("energyPercent"))) { - mType->info.elementMap[COMBAT_ENERGYDAMAGE] = pugi::cast(attr.value()); + mType->info.elementMap[COMBAT_ENERGYDAMAGE] = pugi::cast(attr.value()); + } else if ((attr = elementNode.attribute("holyPercent"))) { + mType->info.elementMap[COMBAT_HOLYDAMAGE] = pugi::cast(attr.value()); + } else if ((attr = elementNode.attribute("deathPercent"))) { + mType->info.elementMap[COMBAT_DEATHDAMAGE] = pugi::cast(attr.value()); + } else if ((attr = elementNode.attribute("drownPercent"))) { + mType->info.elementMap[COMBAT_DROWNDAMAGE] = pugi::cast(attr.value()); } else if ((attr = elementNode.attribute("lifedrainPercent"))) { mType->info.elementMap[COMBAT_LIFEDRAIN] = pugi::cast(attr.value()); } else if ((attr = elementNode.attribute("manadrainPercent"))) { @@ -968,9 +1181,14 @@ bool Monsters::loadMonster(const std::string& file, const std::string& monsterNa for (auto summonNode : node.children()) { int32_t chance = 100; + int32_t speed = 1000; int32_t max = mType->info.maxSummons; bool force = false; + if ((attr = summonNode.attribute("speed")) || (attr = summonNode.attribute("interval"))) { + speed = std::max(1, pugi::cast(attr.value())); + } + if ((attr = summonNode.attribute("chance"))) { chance = pugi::cast(attr.value()); } @@ -986,6 +1204,7 @@ bool Monsters::loadMonster(const std::string& file, const std::string& monsterNa if ((attr = summonNode.attribute("name"))) { summonBlock_t sb; sb.name = attr.as_string(); + sb.speed = speed; sb.chance = chance; sb.max = max; sb.force = force; @@ -1012,6 +1231,29 @@ bool Monsters::loadMonster(const std::string& file, const std::string& monsterNa mType->info.defenseSpells.shrink_to_fit(); mType->info.voiceVector.shrink_to_fit(); mType->info.scripts.shrink_to_fit(); + return mType; +} + +bool MonsterType::loadCallback(LuaScriptInterface* scriptInterface) +{ + int32_t id = scriptInterface->getEvent(); + if (id == -1) { + std::cout << "[Warning - MonsterType::loadCallback] Event not found. " << std::endl; + return false; + } + + info.scriptInterface = scriptInterface; + if (info.eventType == MONSTERS_EVENT_THINK) { + info.thinkEvent = id; + } else if (info.eventType == MONSTERS_EVENT_APPEAR) { + info.creatureAppearEvent = id; + } else if (info.eventType == MONSTERS_EVENT_DISAPPEAR) { + info.creatureDisappearEvent = id; + } else if (info.eventType == MONSTERS_EVENT_MOVE) { + info.creatureMoveEvent = id; + } else if (info.eventType == MONSTERS_EVENT_SAY) { + info.creatureSayEvent = id; + } return true; } @@ -1091,10 +1333,22 @@ void Monsters::loadLootContainer(const pugi::xml_node& node, LootBlock& lBlock) MonsterType* Monsters::getMonsterType(const std::string& name) { - auto it = monsters.find(asLowerCaseString(name)); + std::string lowerCaseName = asLowerCaseString(name); + auto it = monsters.find(lowerCaseName); if (it == monsters.end()) { - return nullptr; + auto it2 = unloadedMonsters.find(lowerCaseName); + if (it2 == unloadedMonsters.end()) { + return nullptr; + } + + return loadMonster(it2->second, name); } return &it->second; } + +void Monsters::addMonsterType(const std::string& name, MonsterType* mType) +{ + mType = &monsters[asLowerCaseString(name)]; +} + diff --git a/src/monsters.h b/src/monsters.h index 23906ab..992373e 100644 --- a/src/monsters.h +++ b/src/monsters.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,8 +22,9 @@ #include "creature.h" -#define MAX_LOOTCHANCE 1000 -#define MAX_STATICWALK 100 + +const uint32_t MAX_LOOTCHANCE = 100000; +const uint32_t MAX_STATICWALK = 100; struct LootBlock { uint16_t id; @@ -38,7 +39,7 @@ struct LootBlock { std::vector childLoot; LootBlock() { id = 0; - countmax = 0; + countmax = 1; chance = 0; subType = -1; @@ -46,9 +47,21 @@ struct LootBlock { } }; +class Loot { + public: + Loot() = default; + + // non-copyable + Loot(const Loot&) = delete; + Loot& operator=(const Loot&) = delete; + + LootBlock lootBlock; +}; + struct summonBlock_t { std::string name; uint32_t chance; + uint32_t speed; uint32_t max; bool force = false; }; @@ -62,23 +75,23 @@ struct spellBlock_t { spellBlock_t(spellBlock_t&& other) : spell(other.spell), chance(other.chance), + speed(other.speed), range(other.range), minCombatValue(other.minCombatValue), maxCombatValue(other.maxCombatValue), - attack(other.attack), - skill(other.skill), - combatSpell(other.combatSpell) { + combatSpell(other.combatSpell), + isMelee(other.isMelee) { other.spell = nullptr; } BaseSpell* spell = nullptr; uint32_t chance = 100; + uint32_t speed = 2000; uint32_t range = 0; int32_t minCombatValue = 0; int32_t maxCombatValue = 0; - int32_t attack = 0; - int32_t skill = 0; bool combatSpell = false; + bool isMelee = false; }; struct voiceBlock_t { @@ -111,11 +124,14 @@ class MonsterType uint64_t experience = 0; uint32_t manaCost = 0; + uint32_t yellChance = 0; + uint32_t yellSpeedTicks = 0; + uint32_t staticAttackChance = 95; uint32_t maxSummons = 0; uint32_t changeTargetSpeed = 0; uint32_t conditionImmunities = 0; uint32_t damageImmunities = 0; - uint32_t baseSpeed = 70; + uint32_t baseSpeed = 200; int32_t creatureAppearEvent = -1; int32_t creatureDisappearEvent = -1; @@ -127,15 +143,8 @@ class MonsterType 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; + int32_t armor = 0; bool canPushItems = false; bool canPushCreatures = false; @@ -146,6 +155,11 @@ class MonsterType bool isAttackable = true; bool isHostile = true; bool hiddenHealth = false; + bool canWalkOnEnergy = true; + bool canWalkOnFire = true; + bool canWalkOnPoison = true; + + MonstersEvent_t eventType = MONSTERS_EVENT_NONE; }; public: @@ -155,14 +169,57 @@ class MonsterType MonsterType(const MonsterType&) = delete; MonsterType& operator=(const MonsterType&) = delete; + bool loadCallback(LuaScriptInterface* scriptInterface); + std::string name; std::string nameDescription; MonsterInfo info; - void createLoot(Container* corpse); - bool createLootContainer(Container* parent, const LootBlock& lootblock); - std::vector createLootItem(const LootBlock& lootBlock); + void loadLoot(MonsterType* monsterType, LootBlock lootblock); +}; + +class MonsterSpell +{ + public: + MonsterSpell() = default; + + MonsterSpell(const MonsterSpell&) = delete; + MonsterSpell& operator=(const MonsterSpell&) = delete; + + std::string name = ""; + std::string scriptName = ""; + + uint8_t chance = 100; + uint8_t range = 0; + + uint16_t interval = 2000; + + int32_t minCombatValue = 0; + int32_t maxCombatValue = 0; + int32_t attack = 0; + int32_t skill = 0; + int32_t length = 0; + int32_t spread = 0; + int32_t radius = 0; + int32_t conditionMinDamage = 0; + int32_t conditionMaxDamage = 0; + int32_t conditionStartDamage = 0; + int32_t tickInterval = 0; + int32_t speedChange = 0; + int32_t duration = 0; + + bool isScripted = false; + bool needTarget = false; + bool needDirection = false; + bool combatSpell = false; + bool isMelee = false; + + Outfit_t outfit = {}; + ShootType_t shoot = CONST_ANI_NONE; + MagicEffectClasses effect = CONST_ME_NONE; + ConditionType_t conditionType = CONDITION_NONE; + CombatType_t combatType = COMBAT_UNDEFINEDDAMAGE; }; class Monsters @@ -180,20 +237,23 @@ class Monsters bool reload(); MonsterType* getMonsterType(const std::string& name); + void addMonsterType(const std::string& name, MonsterType* mType); + bool deserializeSpell(MonsterSpell* spell, spellBlock_t& sb, const std::string& description = ""); - static uint32_t getLootRandom(); + std::unique_ptr scriptInterface; private: - ConditionDamage* getDamageCondition(ConditionType_t conditionType, int32_t cycles, int32_t count, int32_t max_count); + ConditionDamage* getDamageCondition(ConditionType_t conditionType, + int32_t maxDamage, int32_t minDamage, int32_t startDamage, uint32_t tickInterval); 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>& monsterScriptList, bool reloading = false); + MonsterType* loadMonster(const std::string& file, const std::string& monsterName, bool reloading = false); void loadLootContainer(const pugi::xml_node& node, LootBlock&); bool loadLootItem(const pugi::xml_node& node, LootBlock&); std::map monsters; - std::unique_ptr scriptInterface; + std::map unloadedMonsters; bool loaded = false; }; diff --git a/src/mounts.cpp b/src/mounts.cpp new file mode 100644 index 0000000..ce10d97 --- /dev/null +++ b/src/mounts.cpp @@ -0,0 +1,82 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "mounts.h" + +#include "pugicast.h" +#include "tools.h" + +bool Mounts::reload() +{ + mounts.clear(); + return loadFromXml(); +} + +bool Mounts::loadFromXml() +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file("data/XML/mounts.xml"); + if (!result) { + printXMLError("Error - Mounts::loadFromXml", "data/XML/mounts.xml", result); + return false; + } + + for (auto mountNode : doc.child("mounts").children()) { + mounts.emplace_back( + static_cast(pugi::cast(mountNode.attribute("id").value())), + pugi::cast(mountNode.attribute("clientid").value()), + mountNode.attribute("name").as_string(), + pugi::cast(mountNode.attribute("speed").value()), + mountNode.attribute("premium").as_bool() + ); + } + mounts.shrink_to_fit(); + return true; +} + +Mount* Mounts::getMountByID(uint8_t id) +{ + auto it = std::find_if(mounts.begin(), mounts.end(), [id](const Mount& mount) { + return mount.id == id; + }); + + return it != mounts.end() ? &*it : nullptr; +} + +Mount* Mounts::getMountByName(const std::string& name) { + auto mountName = name.c_str(); + for (auto& it : mounts) { + if (strcasecmp(mountName, it.name.c_str()) == 0) { + return ⁢ + } + } + + return nullptr; +} + +Mount* Mounts::getMountByClientID(uint16_t clientId) +{ + auto it = std::find_if(mounts.begin(), mounts.end(), [clientId](const Mount& mount) { + return mount.clientId == clientId; + }); + + return it != mounts.end() ? &*it : nullptr; +} diff --git a/src/mounts.h b/src/mounts.h new file mode 100644 index 0000000..c63d425 --- /dev/null +++ b/src/mounts.h @@ -0,0 +1,52 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_MOUNTS_H_73716D11906A4C5C9F4A7B68D34C9BA6 +#define FS_MOUNTS_H_73716D11906A4C5C9F4A7B68D34C9BA6 + +struct Mount +{ + Mount(uint8_t id, uint16_t clientId, std::string name, int32_t speed, bool premium) : + name(std::move(name)), speed(speed), clientId(clientId), id(id), premium(premium) {} + + std::string name; + int32_t speed; + uint16_t clientId; + uint8_t id; + bool premium; +}; + +class Mounts +{ + public: + bool reload(); + bool loadFromXml(); + Mount* getMountByID(uint8_t id); + Mount* getMountByName(const std::string& name); + Mount* getMountByClientID(uint16_t clientId); + + const std::vector& getMounts() const { + return mounts; + } + + private: + std::vector mounts; +}; + +#endif diff --git a/src/movement.cpp b/src/movement.cpp index 0dc15bf..c59c772 100644 --- a/src/movement.cpp +++ b/src/movement.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,43 +36,49 @@ MoveEvents::MoveEvents() : MoveEvents::~MoveEvents() { - clear(); + clear(false); } -void MoveEvents::clearMap(MoveListMap& map) +void MoveEvents::clearMap(MoveListMap& map, bool fromLua) { - std::unordered_set set; - for (const auto& it : map) { - const MoveEventList& moveEventList = it.second; - for (const auto& i : moveEventList.moveEvent) { - for (MoveEvent* moveEvent : i) { - set.insert(moveEvent); + for (auto it = map.begin(); it != map.end(); ++it) { + for (int eventType = MOVE_EVENT_STEP_IN; eventType < MOVE_EVENT_LAST; ++eventType) { + auto& moveEvents = it->second.moveEvent[eventType]; + for (auto find = moveEvents.begin(); find != moveEvents.end(); ) { + if (fromLua == find->fromLua) { + find = moveEvents.erase(find); + } else { + ++find; + } } } } - map.clear(); - - for (MoveEvent* moveEvent : set) { - delete moveEvent; - } } -void MoveEvents::clear() +void MoveEvents::clearPosMap(MovePosListMap& map, bool fromLua) { - 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; + for (auto it = map.begin(); it != map.end(); ++it) { + for (int eventType = MOVE_EVENT_STEP_IN; eventType < MOVE_EVENT_LAST; ++eventType) { + auto& moveEvents = it->second.moveEvent[eventType]; + for (auto find = moveEvents.begin(); find != moveEvents.end(); ) { + if (fromLua == find->fromLua) { + find = moveEvents.erase(find); + } else { + ++find; + } } } } - positionMap.clear(); +} - scriptInterface.reInitState(); +void MoveEvents::clear(bool fromLua) +{ + clearMap(itemIdMap, fromLua); + clearMap(actionIdMap, fromLua); + clearMap(uniqueIdMap, fromLua); + clearPosMap(positionMap, fromLua); + + reInitState(fromLua); } LuaScriptInterface& MoveEvents::getScriptInterface() @@ -85,17 +91,17 @@ std::string MoveEvents::getScriptBaseName() const return "movements"; } -Event* MoveEvents::getEvent(const std::string& nodeName) +Event_ptr MoveEvents::getEvent(const std::string& nodeName) { if (strcasecmp(nodeName.c_str(), "movevent") != 0) { return nullptr; } - return new MoveEvent(&scriptInterface); + return Event_ptr(new MoveEvent(&scriptInterface)); } -bool MoveEvents::registerEvent(Event* event, const pugi::xml_node& node) +bool MoveEvents::registerEvent(Event_ptr event, const pugi::xml_node& node) { - MoveEvent* moveEvent = static_cast(event); //event is guaranteed to be a MoveEvent + MoveEvent_ptr moveEvent{static_cast(event.release())}; //event is guaranteed to be a MoveEvent const MoveEvent_t eventType = moveEvent->getEventType(); if (eventType == MOVE_EVENT_ADD_ITEM || eventType == MOVE_EVENT_REMOVE_ITEM) { @@ -117,7 +123,6 @@ bool MoveEvents::registerEvent(Event* event, const pugi::xml_node& node) pugi::xml_attribute attr; if ((attr = node.attribute("itemid"))) { int32_t id = pugi::cast(attr.value()); - addEvent(moveEvent, id, itemIdMap); if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) { ItemType& it = Item::items.getItemType(id); it.wieldInfo = moveEvent->getWieldInfo(); @@ -125,11 +130,12 @@ bool MoveEvents::registerEvent(Event* event, const pugi::xml_node& node) it.minReqMagicLevel = moveEvent->getReqMagLv(); it.vocationString = moveEvent->getVocationString(); } + addEvent(std::move(*moveEvent), id, itemIdMap); } else if ((attr = node.attribute("fromid"))) { uint32_t id = pugi::cast(attr.value()); uint32_t endId = pugi::cast(node.attribute("toid").value()); - addEvent(moveEvent, id, itemIdMap); + addEvent(*moveEvent, id, itemIdMap); if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) { ItemType& it = Item::items.getItemType(id); @@ -139,7 +145,7 @@ bool MoveEvents::registerEvent(Event* event, const pugi::xml_node& node) it.vocationString = moveEvent->getVocationString(); while (++id <= endId) { - addEvent(moveEvent, id, itemIdMap); + addEvent(*moveEvent, id, itemIdMap); ItemType& tit = Item::items.getItemType(id); tit.wieldInfo = moveEvent->getWieldInfo(); @@ -149,17 +155,26 @@ bool MoveEvents::registerEvent(Event* event, const pugi::xml_node& node) } } else { while (++id <= endId) { - addEvent(moveEvent, id, itemIdMap); + addEvent(*moveEvent, id, itemIdMap); } } - } else if ((attr = node.attribute("movementid"))) { - addEvent(moveEvent, pugi::cast(attr.value()), movementIdMap); - } else if ((attr = node.attribute("frommovementid"))) { + } else if ((attr = node.attribute("uniqueid"))) { + addEvent(std::move(*moveEvent), pugi::cast(attr.value()), uniqueIdMap); + } else if ((attr = node.attribute("fromuid"))) { uint32_t id = pugi::cast(attr.value()); - uint32_t endId = pugi::cast(node.attribute("tomovementid").value()); - addEvent(moveEvent, id, movementIdMap); + uint32_t endId = pugi::cast(node.attribute("touid").value()); + addEvent(*moveEvent, id, uniqueIdMap); while (++id <= endId) { - addEvent(moveEvent, id, movementIdMap); + addEvent(*moveEvent, id, uniqueIdMap); + } + } else if ((attr = node.attribute("actionid"))) { + addEvent(std::move(*moveEvent), pugi::cast(attr.value()), actionIdMap); + } else if ((attr = node.attribute("fromaid"))) { + uint32_t id = pugi::cast(attr.value()); + uint32_t endId = pugi::cast(node.attribute("toaid").value()); + addEvent(*moveEvent, id, actionIdMap); + while (++id <= endId) { + addEvent(*moveEvent, id, actionIdMap); } } else if ((attr = node.attribute("pos"))) { std::vector posList = vectorAtoi(explodeString(attr.as_string(), ";")); @@ -168,28 +183,125 @@ bool MoveEvents::registerEvent(Event* event, const pugi::xml_node& node) } Position pos(posList[0], posList[1], posList[2]); - addEvent(moveEvent, pos, positionMap); + addEvent(std::move(*moveEvent), pos, positionMap); } else { return false; } return true; } -void MoveEvents::addEvent(MoveEvent* moveEvent, int32_t id, MoveListMap& map) +bool MoveEvents::registerLuaFunction(MoveEvent* event) +{ + MoveEvent_ptr moveEvent{ event }; + if (moveEvent->getItemIdRange().size() > 0) { + if (moveEvent->getItemIdRange().size() == 1) { + uint32_t id = moveEvent->getItemIdRange().at(0); + 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 { + uint32_t iterId = 0; + while (++iterId < moveEvent->getItemIdRange().size()) { + if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) { + ItemType& it = Item::items.getItemType(moveEvent->getItemIdRange().at(iterId)); + it.wieldInfo = moveEvent->getWieldInfo(); + it.minReqLevel = moveEvent->getReqLevel(); + it.minReqMagicLevel = moveEvent->getReqMagLv(); + it.vocationString = moveEvent->getVocationString(); + } + addEvent(*moveEvent, moveEvent->getItemIdRange().at(iterId), itemIdMap); + } + } + } else { + return false; + } + return true; +} + +bool MoveEvents::registerLuaEvent(MoveEvent* event) +{ + MoveEvent_ptr moveEvent{ event }; + if (moveEvent->getItemIdRange().size() > 0) { + if (moveEvent->getItemIdRange().size() == 1) { + uint32_t id = moveEvent->getItemIdRange().at(0); + 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 { + auto v = moveEvent->getItemIdRange(); + for (auto i = v.begin(); i != v.end(); i++) { + if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) { + ItemType& it = Item::items.getItemType(*i); + it.wieldInfo = moveEvent->getWieldInfo(); + it.minReqLevel = moveEvent->getReqLevel(); + it.minReqMagicLevel = moveEvent->getReqMagLv(); + it.vocationString = moveEvent->getVocationString(); + } + addEvent(*moveEvent, *i, itemIdMap); + } + } + } else if (moveEvent->getActionIdRange().size() > 0) { + if (moveEvent->getActionIdRange().size() == 1) { + int32_t id = moveEvent->getActionIdRange().at(0); + addEvent(*moveEvent, id, actionIdMap); + } else { + auto v = moveEvent->getActionIdRange(); + for (auto i = v.begin(); i != v.end(); i++) { + addEvent(*moveEvent, *i, actionIdMap); + } + } + } else if (moveEvent->getUniqueIdRange().size() > 0) { + if (moveEvent->getUniqueIdRange().size() == 1) { + int32_t id = moveEvent->getUniqueIdRange().at(0); + addEvent(*moveEvent, id, uniqueIdMap); + } else { + auto v = moveEvent->getUniqueIdRange(); + for (auto i = v.begin(); i != v.end(); i++) { + addEvent(*moveEvent, *i, uniqueIdMap); + } + } + } else if (moveEvent->getPosList().size() > 0) { + if (moveEvent->getPosList().size() == 1) { + Position pos = moveEvent->getPosList().at(0); + addEvent(*moveEvent, pos, positionMap); + } else { + auto v = moveEvent->getPosList(); + for (auto i = v.begin(); i != v.end(); i++) { + addEvent(*moveEvent, *i, 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); + moveEventList.moveEvent[moveEvent.getEventType()].push_back(std::move(moveEvent)); map[id] = moveEventList; } else { - std::list& moveEventList = it->second.moveEvent[moveEvent->getEventType()]; - for (MoveEvent* existingMoveEvent : moveEventList) { - if (existingMoveEvent->getSlot() == moveEvent->getSlot()) { + std::list& 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); + moveEventList.push_back(std::move(moveEvent)); } } @@ -212,59 +324,64 @@ MoveEvent* MoveEvents::getEvent(Item* item, MoveEvent_t eventType, slots_t slot) auto it = itemIdMap.find(item->getID()); if (it != itemIdMap.end()) { - std::list& moveEventList = it->second.moveEvent[eventType]; - for (MoveEvent* moveEvent : moveEventList) { - if ((moveEvent->getSlot() & slotp) != 0) { - return moveEvent; + std::list& 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) +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& moveEventList = it->second.moveEvent[eventType]; + if (item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { + it = uniqueIdMap.find(item->getUniqueId()); + if (it != uniqueIdMap.end()) { + std::list& moveEventList = it->second.moveEvent[eventType]; if (!moveEventList.empty()) { - return *moveEventList.begin(); + return &(*moveEventList.begin()); } } } - if (!item->hasCollisionEvent() && !item->hasSeparationEvent()) { - return nullptr; + if (item->hasAttribute(ITEM_ATTRIBUTE_ACTIONID)) { + it = actionIdMap.find(item->getActionId()); + if (it != actionIdMap.end()) { + std::list& moveEventList = it->second.moveEvent[eventType]; + if (!moveEventList.empty()) { + return &(*moveEventList.begin()); + } + } } it = itemIdMap.find(item->getID()); if (it != itemIdMap.end()) { - std::list& moveEventList = it->second.moveEvent[eventType]; + std::list& moveEventList = it->second.moveEvent[eventType]; if (!moveEventList.empty()) { - return *moveEventList.begin(); + return &(*moveEventList.begin()); } } - return nullptr; } -void MoveEvents::addEvent(MoveEvent* moveEvent, const Position& pos, MovePosListMap& map) +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); + moveEventList.moveEvent[moveEvent.getEventType()].push_back(std::move(moveEvent)); map[pos] = moveEventList; } else { - std::list& moveEventList = it->second.moveEvent[moveEvent->getEventType()]; + std::list& 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); + moveEventList.push_back(std::move(moveEvent)); } } @@ -272,9 +389,9 @@ MoveEvent* MoveEvents::getEvent(const Tile* tile, MoveEvent_t eventType) { auto it = positionMap.find(tile->getPosition()); if (it != positionMap.end()) { - std::list& moveEventList = it->second.moveEvent[eventType]; + std::list& moveEventList = it->second.moveEvent[eventType]; if (!moveEventList.empty()) { - return *moveEventList.begin(); + return &(*moveEventList.begin()); } } return nullptr; @@ -325,7 +442,7 @@ uint32_t MoveEvents::onPlayerDeEquip(Player* player, Item* item, slots_t slot) if (!moveEvent) { return 1; } - return moveEvent->fireEquip(player, item, slot, true); + return moveEvent->fireEquip(player, item, slot, false); } uint32_t MoveEvents::onItemMove(Item* item, Tile* tile, bool isAdd) @@ -371,20 +488,6 @@ uint32_t MoveEvents::onItemMove(Item* item, Tile* tile, bool isAdd) 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) { @@ -521,40 +624,6 @@ bool MoveEvent::configureEvent(const pugi::xml_node& node) 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(); @@ -646,10 +715,6 @@ uint32_t MoveEvent::EquipItem(MoveEvent* moveEvent, Player* player, Item* item, player->sendIcons(); } - if (it.abilities->absorbPercent[combatTypeToIndex(COMBAT_DROWNDAMAGE)] == 100) { - player->removeCondition(CONDITION_DROWN); - } - if (it.abilities->regeneration) { Condition* condition = Condition::createCondition(static_cast(slot), CONDITION_REGENERATION, -1, 0); @@ -682,6 +747,13 @@ uint32_t MoveEvent::EquipItem(MoveEvent* moveEvent, Player* player, Item* item, } } + for (int32_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; ++i) { + if (it.abilities->specialSkills[i]) { + needUpdateSkills = true; + player->setVarSpecialSkill(static_cast(i), it.abilities->specialSkills[i]); + } + } + if (needUpdateSkills) { player->sendSkills(); } @@ -757,6 +829,13 @@ uint32_t MoveEvent::DeEquipItem(MoveEvent*, Player* player, Item* item, slots_t } } + for (int32_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; ++i) { + if (it.abilities->specialSkills[i] != 0) { + needUpdateSkills = true; + player->setVarSpecialSkill(static_cast(i), -it.abilities->specialSkills[i]); + } + } + if (needUpdateSkills) { player->sendSkills(); } @@ -783,6 +862,44 @@ uint32_t MoveEvent::DeEquipItem(MoveEvent*, Player* player, Item* item, slots_t return 1; } +bool MoveEvent::loadFunction(const pugi::xml_attribute& attr, bool isScripted) +{ + 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 { + if (!isScripted) { + std::cout << "[Warning - MoveEvent::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; + return false; + } + } + + if (!isScripted) { + scripted = false; + } + return true; +} + +MoveEvent_t MoveEvent::getEventType() const +{ + return eventType; +} + +void MoveEvent::setEventType(MoveEvent_t type) +{ + eventType = type; +} + uint32_t MoveEvent::fireStepEvent(Creature* creature, Item* item, const Position& pos) { if (scripted) { @@ -816,19 +933,24 @@ bool MoveEvent::executeStep(Creature* creature, Item* item, const Position& pos) return scriptInterface->callFunction(4); } -uint32_t MoveEvent::fireEquip(Player* player, Item* item, slots_t slot, bool boolean) +uint32_t MoveEvent::fireEquip(Player* player, Item* item, slots_t slot, bool isCheck) { if (scripted) { - return executeEquip(player, item, slot); + if (!equipFunction || equipFunction(this, player, item, slot, isCheck) == 1) { + if (executeEquip(player, item, slot, isCheck)) { + return 1; + } + } + return 0; } else { - return equipFunction(this, player, item, slot, boolean); + return equipFunction(this, player, item, slot, isCheck); } } -bool MoveEvent::executeEquip(Player* player, Item* item, slots_t slot) +bool MoveEvent::executeEquip(Player* player, Item* item, slots_t slot, bool isCheck) { - //onEquip(player, item, slot) - //onDeEquip(player, item, slot) + //onEquip(player, item, slot, isCheck) + //onDeEquip(player, item, slot, isCheck) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - MoveEvent::executeEquip] Call stack overflow" << std::endl; return false; @@ -844,8 +966,9 @@ bool MoveEvent::executeEquip(Player* player, Item* item, slots_t slot) LuaScriptInterface::setMetatable(L, -1, "Player"); LuaScriptInterface::pushThing(L, item); lua_pushnumber(L, slot); + LuaScriptInterface::pushBoolean(L, isCheck); - return scriptInterface->callFunction(3); + return scriptInterface->callFunction(4); } uint32_t MoveEvent::fireAddRemItem(Item* item, Item* tileItem, const Position& pos) diff --git a/src/movement.h b/src/movement.h index d8a67e4..27ed82f 100644 --- a/src/movement.h +++ b/src/movement.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,9 @@ #include "baseevents.h" #include "item.h" #include "luascript.h" +#include "vocation.h" + +extern Vocations g_vocations; enum MoveEvent_t { MOVE_EVENT_STEP_IN, @@ -39,12 +42,13 @@ enum MoveEvent_t { }; class MoveEvent; +using MoveEvent_ptr = std::unique_ptr; struct MoveEventList { - std::list moveEvent[MOVE_EVENT_LAST]; + std::list moveEvent[MOVE_EVENT_LAST]; }; -typedef std::map VocEquipMap; +using VocEquipMap = std::map; class MoveEvents final : public BaseEvents { @@ -63,51 +67,54 @@ class MoveEvents final : public BaseEvents MoveEvent* getEvent(Item* item, MoveEvent_t eventType); - protected: - typedef std::map MoveListMap; - void clearMap(MoveListMap& map); + bool registerLuaEvent(MoveEvent* event); + bool registerLuaFunction(MoveEvent* event); + void clear(bool fromLua) override final; - typedef std::map 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; + private: + using MoveListMap = std::map; + using MovePosListMap = std::map; + void clearMap(MoveListMap& map, bool fromLua); + void clearPosMap(MovePosListMap& map, bool fromLua); - void addEvent(MoveEvent* moveEvent, int32_t id, MoveListMap& map); + LuaScriptInterface& getScriptInterface() override; + std::string getScriptBaseName() const override; + Event_ptr getEvent(const std::string& nodeName) override; + bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; - void addEvent(MoveEvent* moveEvent, const Position& pos, MovePosListMap& map); + 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 uniqueIdMap; + MoveListMap actionIdMap; 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); +using StepFunction = std::function; +using MoveFunction = std::function; +using EquipFunction = std::function; 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; + bool configureEvent(const pugi::xml_node& node) override; + bool loadFunction(const pugi::xml_attribute& attr, bool isScripted) override; 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 fireEquip(Player* player, Item* item, slots_t slot, bool isCheck); uint32_t getSlot() const { return slot; @@ -115,7 +122,7 @@ class MoveEvent final : public Event //scripting bool executeStep(Creature* creature, Item* item, const Position& pos); - bool executeEquip(Player* player, Item* item, slots_t slot); + bool executeEquip(Player* player, Item* item, slots_t slot, bool isCheck); bool executeAddRemItem(Item* item, Item* tileItem, const Position& pos); // @@ -132,29 +139,104 @@ class MoveEvent final : public Event const std::string& getVocationString() const { return vocationString; } + void setVocationString(const std::string& str) { + vocationString = str; + } uint32_t getWieldInfo() const { return wieldInfo; } const VocEquipMap& getVocEquipMap() const { return vocEquipMap; } + void addVocEquipMap(std::string vocName) { + int32_t vocationId = g_vocations.getVocationId(vocName); + if (vocationId != -1) { + vocEquipMap[vocationId] = true; + } + } + bool getTileItem() const { + return tileItem; + } + void setTileItem(bool b) { + tileItem = b; + } + std::vector getItemIdRange() { + return itemIdRange; + } + void addItemId(uint32_t id) { + itemIdRange.emplace_back(id); + } + std::vector getActionIdRange() { + return actionIdRange; + } + void addActionId(uint32_t id) { + actionIdRange.emplace_back(id); + } + std::vector getUniqueIdRange() { + return uniqueIdRange; + } + void addUniqueId(uint32_t id) { + uniqueIdRange.emplace_back(id); + } + std::vector getPosList() { + return posList; + } + void addPosList(Position pos) { + posList.emplace_back(pos); + } + std::string getSlotName() { + return slotName; + } + void setSlotName(std::string name) { + slotName = name; + } + void setSlot(uint32_t s) { + slot = s; + } + uint32_t getRequiredLevel() { + return reqLevel; + } + void setRequiredLevel(uint32_t level) { + reqLevel = level; + } + uint32_t getRequiredMagLevel() { + return reqMagLevel; + } + void setRequiredMagLevel(uint32_t level) { + reqMagLevel = level; + } + bool needPremium() { + return premium; + } + void setNeedPremium(bool b) { + premium = b; + } + uint32_t getWieldInfo() { + return wieldInfo; + } + void setWieldInfo(WieldInfo_t info) { + wieldInfo |= info; + } - protected: - std::string getScriptEventName() const final; + static uint32_t StepInField(Creature* creature, Item* item, const Position& pos); + static uint32_t StepOutField(Creature* creature, Item* item, const Position& pos); - static StepFunction StepInField; - static StepFunction StepOutField; + static uint32_t AddItemField(Item* item, Item* tileItem, const Position& pos); + static uint32_t RemoveItemField(Item* item, Item* tileItem, const Position& pos); - static MoveFunction AddItemField; - static MoveFunction RemoveItemField; - static EquipFunction EquipItem; - static EquipFunction DeEquipItem; + static uint32_t EquipItem(MoveEvent* moveEvent, Player* player, Item* item, slots_t slot, bool boolean); + static uint32_t DeEquipItem(MoveEvent* moveEvent, Player* player, Item* item, slots_t slot, bool boolean); MoveEvent_t eventType = MOVE_EVENT_NONE; - StepFunction* stepFunction = nullptr; - MoveFunction* moveFunction = nullptr; - EquipFunction* equipFunction = nullptr; + StepFunction stepFunction; + MoveFunction moveFunction; + EquipFunction equipFunction; + + private: + std::string getScriptEventName() const override; + uint32_t slot = SLOTP_WHEREEVER; + std::string slotName; //onEquip information uint32_t reqLevel = 0; @@ -163,6 +245,12 @@ class MoveEvent final : public Event std::string vocationString; uint32_t wieldInfo = 0; VocEquipMap vocEquipMap; + bool tileItem = false; + + std::vector itemIdRange; + std::vector actionIdRange; + std::vector uniqueIdRange; + std::vector posList; }; #endif diff --git a/src/networkmessage.cpp b/src/networkmessage.cpp index 7f02803..b9be9f0 100644 --- a/src/networkmessage.cpp +++ b/src/networkmessage.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -99,16 +99,18 @@ void NetworkMessage::addItem(uint16_t id, uint8_t count) { const ItemType& it = Item::items[id]; - if (it.disguise) { - add(it.disguiseId); - } else { - add(it.id); - } + add(it.clientId); + + addByte(0xFF); // MARK_UNMARKED if (it.stackable) { addByte(count); } else if (it.isSplash() || it.isFluidContainer()) { - addByte(getLiquidColor(count)); + addByte(fluidMap[count & 7]); + } + + if (it.isAnimation) { + addByte(0xFE); // random phase (0xFF for async) } } @@ -116,20 +118,21 @@ void NetworkMessage::addItem(const Item* item) { const ItemType& it = Item::items[item->getID()]; - if (it.disguise) { - add(it.disguiseId); - } else { - add(it.id); - } + add(it.clientId); + addByte(0xFF); // MARK_UNMARKED if (it.stackable) { addByte(std::min(0xFF, item->getItemCount())); } else if (it.isSplash() || it.isFluidContainer()) { - addByte(getLiquidColor(item->getFluidType())); + addByte(fluidMap[item->getFluidType() & 7]); + } + + if (it.isAnimation) { + addByte(0xFE); // random phase (0xFF for async) } } void NetworkMessage::addItemId(uint16_t itemId) { - add(itemId); + add(Item::items[itemId].clientId); } diff --git a/src/networkmessage.h b/src/networkmessage.h index 7d11153..7118f44 100644 --- a/src/networkmessage.h +++ b/src/networkmessage.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,14 +31,16 @@ class RSA; class NetworkMessage { public: - typedef uint16_t MsgSize_t; + using MsgSize_t = uint16_t; // Headers: // 2 bytes for unencrypted message size + // 4 bytes for checksum // 2 bytes for encrypted message size - static constexpr MsgSize_t INITIAL_BUFFER_POSITION = 4; + static constexpr MsgSize_t INITIAL_BUFFER_POSITION = 8; enum { HEADER_LENGTH = 2 }; + enum { CHECKSUM_LENGTH = 4 }; enum { XTEA_MULTIPLE = 8 }; - enum { MAX_BODY_LENGTH = NETWORKMESSAGE_MAXSIZE - HEADER_LENGTH - XTEA_MULTIPLE }; + enum { MAX_BODY_LENGTH = NETWORKMESSAGE_MAXSIZE - HEADER_LENGTH - CHECKSUM_LENGTH - XTEA_MULTIPLE }; enum { MAX_PROTOCOL_BODY_LENGTH = MAX_BODY_LENGTH - 10 }; NetworkMessage() = default; @@ -148,18 +150,6 @@ class NetworkMessage } 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; @@ -168,6 +158,19 @@ class NetworkMessage NetworkMessageInfo info; uint8_t buffer[NETWORKMESSAGE_MAXSIZE]; + + private: + bool canAdd(size_t size) const { + return (size + info.position) < MAX_BODY_LENGTH; + } + + bool canRead(int32_t size) { + if ((info.position + size) > (info.length + 8) || size >= (NETWORKMESSAGE_MAXSIZE - info.position)) { + info.overrun = true; + return false; + } + return true; + } }; #endif // #ifndef __NETWORK_MESSAGE_H__ diff --git a/src/npc.cpp b/src/npc.cpp index 4d811e7..f090ff9 100644 --- a/src/npc.cpp +++ b/src/npc.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,43 +21,23 @@ #include "npc.h" #include "game.h" -#include "tools.h" -#include "position.h" -#include "player.h" -#include "spawn.h" -#include "script.h" -#include "behaviourdatabase.h" +#include "pugicast.h" extern Game g_game; +extern LuaEnvironment g_luaEnvironment; uint32_t Npc::npcAutoID = 0x80000000; - -void Npcs::loadNpcs() -{ - std::cout << ">> Loading npcs..." << std::endl; - - std::vector 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); - } -} +NpcScriptInterface* Npc::scriptInterface = nullptr; void Npcs::reload() { const std::map& npcs = g_game.getNpcs(); + for (const auto& it : npcs) { + it.second->closeAllShopWindows(); + } + + delete Npc::scriptInterface; + Npc::scriptInterface = nullptr; for (const auto& it : npcs) { it.second->reload(); @@ -67,23 +47,19 @@ void Npcs::reload() Npc* Npc::createNpc(const std::string& name) { std::unique_ptr 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) + filename("data/npc/" + name + ".xml"), + npcEventHandler(nullptr), + masterRadius(-1), + loaded(false) { - baseSpeed = 5; reset(); } @@ -110,89 +86,147 @@ bool Npc::load() reset(); - ScriptReader script; - if (!script.open(filename)) { - return false; + if (!scriptInterface) { + scriptInterface = new NpcScriptInterface(); + scriptInterface->loadNpcLib("data/npc/lib/npc.lua"); } - 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]; - currentOutfit.lookAddons = c[4]; - } 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; + loaded = loadFromXml(); + return loaded; } void Npc::reset() { loaded = false; + walkTicks = 1500; + pushable = true; + floorChange = false; + attackable = false; + ignoreHeight = true; focusCreature = 0; - conversationEndTime = 0; + speechBubble = SPEECHBUBBLE_NONE; - if (behaviourDatabase) { - delete behaviourDatabase; - behaviourDatabase = nullptr; - } + delete npcEventHandler; + npcEventHandler = nullptr; + + parameters.clear(); + shopPlayerSet.clear(); } void Npc::reload() { - loaded = false; - reset(); load(); - if (baseSpeed > 0) { + // Simulate that the creature is placed on the map again. + if (npcEventHandler) { + npcEventHandler->onCreatureAppear(this); + } + + if (walkTicks > 0) { addEventWalk(); } } +bool Npc::loadFromXml() +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file(filename.c_str()); + if (!result) { + printXMLError("Error - Npc::loadFromXml", filename, result); + return false; + } + + pugi::xml_node npcNode = doc.child("npc"); + if (!npcNode) { + std::cout << "[Error - Npc::loadFromXml] Missing npc tag in " << filename << std::endl; + return false; + } + + name = npcNode.attribute("name").as_string(); + attackable = npcNode.attribute("attackable").as_bool(); + floorChange = npcNode.attribute("floorchange").as_bool(); + + pugi::xml_attribute attr; + if ((attr = npcNode.attribute("speed"))) { + baseSpeed = pugi::cast(attr.value()); + } else { + baseSpeed = 100; + } + + if ((attr = npcNode.attribute("pushable"))) { + pushable = attr.as_bool(); + } + + if ((attr = npcNode.attribute("walkinterval"))) { + walkTicks = pugi::cast(attr.value()); + } + + if ((attr = npcNode.attribute("walkradius"))) { + masterRadius = pugi::cast(attr.value()); + } + + if ((attr = npcNode.attribute("ignoreheight"))) { + ignoreHeight = attr.as_bool(); + } + + if ((attr = npcNode.attribute("speechbubble"))) { + speechBubble = pugi::cast(attr.value()); + } + + if ((attr = npcNode.attribute("skull"))) { + setSkull(getSkullType(asLowerCaseString(attr.as_string()))); + } + + pugi::xml_node healthNode = npcNode.child("health"); + if (healthNode) { + if ((attr = healthNode.attribute("now"))) { + health = pugi::cast(attr.value()); + } else { + health = 100; + } + + if ((attr = healthNode.attribute("max"))) { + healthMax = pugi::cast(attr.value()); + } else { + healthMax = 100; + } + } + + pugi::xml_node lookNode = npcNode.child("look"); + if (lookNode) { + pugi::xml_attribute lookTypeAttribute = lookNode.attribute("type"); + if (lookTypeAttribute) { + defaultOutfit.lookType = pugi::cast(lookTypeAttribute.value()); + defaultOutfit.lookHead = pugi::cast(lookNode.attribute("head").value()); + defaultOutfit.lookBody = pugi::cast(lookNode.attribute("body").value()); + defaultOutfit.lookLegs = pugi::cast(lookNode.attribute("legs").value()); + defaultOutfit.lookFeet = pugi::cast(lookNode.attribute("feet").value()); + defaultOutfit.lookAddons = pugi::cast(lookNode.attribute("addons").value()); + } else if ((attr = lookNode.attribute("typeex"))) { + defaultOutfit.lookTypeEx = pugi::cast(attr.value()); + } + defaultOutfit.lookMount = pugi::cast(lookNode.attribute("mount").value()); + + currentOutfit = defaultOutfit; + } + + for (auto parameterNode : npcNode.child("parameters").children()) { + parameters[parameterNode.attribute("key").as_string()] = parameterNode.attribute("value").as_string(); + } + + pugi::xml_attribute scriptFile = npcNode.attribute("script"); + if (scriptFile) { + npcEventHandler = new NpcEventsHandler(scriptFile.as_string(), this); + if (!npcEventHandler->isLoaded()) { + delete npcEventHandler; + npcEventHandler = nullptr; + return false; + } + } + return true; +} + bool Npc::canSee(const Position& pos) const { if (pos.z != getPosition().z) { @@ -215,10 +249,18 @@ void Npc::onCreatureAppear(Creature* creature, bool isLogin) Creature::onCreatureAppear(creature, isLogin); if (creature == this) { - if (baseSpeed > 0) { + if (walkTicks > 0) { addEventWalk(); } + + if (npcEventHandler) { + npcEventHandler->onCreatureAppear(creature); + } } else if (Player* player = creature->getPlayer()) { + if (npcEventHandler) { + npcEventHandler->onCreatureAppear(creature); + } + spectators.insert(player); updateIdleStatus(); } @@ -228,14 +270,14 @@ 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, ""); + if (creature == this) { + closeAllShopWindows(); + if (npcEventHandler) { + npcEventHandler->onCreatureDisappear(creature); + } + } else if (Player* player = creature->getPlayer()) { + if (npcEventHandler) { + npcEventHandler->onCreatureDisappear(creature); } spectators.erase(player); @@ -248,19 +290,15 @@ void Npc::onCreatureMove(Creature* creature, const Tile* newTile, const Position { 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 || creature->getPlayer()) { + if (npcEventHandler) { + npcEventHandler->onCreatureMove(creature, oldPos, newPos); } - } - if (creature != this) { - if (player) { + if (creature != this) { + Player* player = creature->getPlayer(); + + // if player is now in range, add to spectators list, otherwise erase if (player->canSee(position)) { spectators.insert(player); } else { @@ -274,25 +312,23 @@ void Npc::onCreatureMove(Creature* creature, const Tile* newTile, const Position void Npc::onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) { - if (creature->getID() == id || type != TALKTYPE_SAY || !behaviourDatabase) { + if (creature->getID() == id) { return; } + //only players for script events Player* player = creature->getPlayer(); if (player) { - if (!Position::areInRange<3, 3>(creature->getPosition(), getPosition())) { - return; + if (npcEventHandler) { + npcEventHandler->onCreatureSay(player, type, text); } + } +} - 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::onPlayerCloseChannel(Player* player) +{ + if (npcEventHandler) { + npcEventHandler->onPlayerCloseChannel(player); } } @@ -300,35 +336,54 @@ void Npc::onThink(uint32_t interval) { Creature::onThink(interval); - if (!isIdle && focusCreature == 0 && baseSpeed > 0 && getTimeSinceLastMove() >= 100 + getStepDuration()) { + if (npcEventHandler) { + npcEventHandler->onThink(); + } + + if (!isIdle && getTimeSinceLastMove() >= walkTicks) { 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); +} + +void Npc::doSayToPlayer(Player* player, const std::string& text) +{ + if (player) { + player->sendCreatureSay(this, TALKTYPE_PRIVATE_NP, text); + player->onCreatureSay(this, TALKTYPE_PRIVATE_NP, text); + } +} + +void Npc::onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count, + uint8_t amount, bool ignore/* = false*/, bool inBackpacks/* = false*/) +{ + if (npcEventHandler) { + npcEventHandler->onPlayerTrade(player, callback, itemId, count, amount, ignore, inBackpacks); + } + player->sendSaleItemList(); +} + +void Npc::onPlayerEndTrade(Player* player, int32_t buyCallback, int32_t sellCallback) +{ + lua_State* L = getScriptInterface()->getLuaState(); + + if (buyCallback != -1) { + luaL_unref(L, LUA_REGISTRYINDEX, buyCallback); } - g_game.internalCreatureSay(this, TALKTYPE_SAY, text, false); + if (sellCallback != -1) { + luaL_unref(L, LUA_REGISTRYINDEX, sellCallback); + } + + removeShopPlayer(player); + + if (npcEventHandler) { + npcEventHandler->onPlayerEndTrade(player); + } } bool Npc::getNextStep(Direction& dir, uint32_t& flags) @@ -337,7 +392,7 @@ bool Npc::getNextStep(Direction& dir, uint32_t& flags) return true; } - if (baseSpeed <= 0) { + if (walkTicks == 0) { return false; } @@ -345,11 +400,7 @@ bool Npc::getNextStep(Direction& dir, uint32_t& flags) return false; } - if (OTSYS_TIME() < staticMovementTime) { - return false; - } - - if (getTimeSinceLastMove() < 100 + getStepDuration() + getStepSpeed()) { + if (getTimeSinceLastMove() < walkTicks) { return false; } @@ -393,11 +444,11 @@ bool Npc::canWalkTo(const Position& fromPos, Direction dir) const return false; } - if (tile->hasFlag(TILESTATE_BLOCKPATH)) { + if (!floorChange && (tile->hasFlag(TILESTATE_FLOORCHANGE) || tile->getTeleportItem())) { return false; } - if (tile->hasHeight(1)) { + if (!ignoreHeight && tile->hasHeight(1)) { return false; } @@ -433,10 +484,10 @@ bool Npc::getRandomStep(Direction& dir) const return true; } -void Npc::doMoveTo(const Position& target) +void Npc::doMoveTo(const Position& pos) { std::forward_list listDir; - if (getPathTo(target, listDir, 1, 1, true, true)) { + if (getPathTo(pos, listDir, 1, 1, true, true)) { startAutoWalk(listDir); } } @@ -480,4 +531,771 @@ void Npc::setCreatureFocus(Creature* creature) } else { focusCreature = 0; } -} \ No newline at end of file +} + +void Npc::addShopPlayer(Player* player) +{ + shopPlayerSet.insert(player); +} + +void Npc::removeShopPlayer(Player* player) +{ + shopPlayerSet.erase(player); +} + +void Npc::closeAllShopWindows() +{ + while (!shopPlayerSet.empty()) { + Player* player = *shopPlayerSet.begin(); + if (!player->closeShopWindow()) { + removeShopPlayer(player); + } + } +} + +NpcScriptInterface* Npc::getScriptInterface() +{ + return scriptInterface; +} + +NpcScriptInterface::NpcScriptInterface() : + LuaScriptInterface("Npc interface") +{ + libLoaded = false; + initState(); +} + +bool NpcScriptInterface::initState() +{ + luaState = g_luaEnvironment.getLuaState(); + if (!luaState) { + return false; + } + + registerFunctions(); + + lua_newtable(luaState); + eventTableRef = luaL_ref(luaState, LUA_REGISTRYINDEX); + runningEventId = EVENT_ID_USER; + return true; +} + +bool NpcScriptInterface::closeState() +{ + libLoaded = false; + LuaScriptInterface::closeState(); + return true; +} + +bool NpcScriptInterface::loadNpcLib(const std::string& file) +{ + if (libLoaded) { + return true; + } + + if (loadFile(file) == -1) { + std::cout << "[Warning - NpcScriptInterface::loadNpcLib] Can not load " << file << std::endl; + return false; + } + + libLoaded = true; + return true; +} + +void NpcScriptInterface::registerFunctions() +{ + //npc exclusive functions + lua_register(luaState, "selfSay", NpcScriptInterface::luaActionSay); + lua_register(luaState, "selfMove", NpcScriptInterface::luaActionMove); + lua_register(luaState, "selfMoveTo", NpcScriptInterface::luaActionMoveTo); + lua_register(luaState, "selfTurn", NpcScriptInterface::luaActionTurn); + lua_register(luaState, "selfFollow", NpcScriptInterface::luaActionFollow); + lua_register(luaState, "getDistanceTo", NpcScriptInterface::luagetDistanceTo); + lua_register(luaState, "doNpcSetCreatureFocus", NpcScriptInterface::luaSetNpcFocus); + lua_register(luaState, "getNpcCid", NpcScriptInterface::luaGetNpcCid); + lua_register(luaState, "getNpcParameter", NpcScriptInterface::luaGetNpcParameter); + lua_register(luaState, "openShopWindow", NpcScriptInterface::luaOpenShopWindow); + lua_register(luaState, "closeShopWindow", NpcScriptInterface::luaCloseShopWindow); + lua_register(luaState, "doSellItem", NpcScriptInterface::luaDoSellItem); + + // metatable + registerMethod("Npc", "getParameter", NpcScriptInterface::luaNpcGetParameter); + registerMethod("Npc", "setFocus", NpcScriptInterface::luaNpcSetFocus); + + registerMethod("Npc", "openShopWindow", NpcScriptInterface::luaNpcOpenShopWindow); + registerMethod("Npc", "closeShopWindow", NpcScriptInterface::luaNpcCloseShopWindow); +} + +int NpcScriptInterface::luaActionSay(lua_State* L) +{ + //selfSay(words[, target]) + Npc* npc = getScriptEnv()->getNpc(); + if (!npc) { + return 0; + } + + const std::string& text = getString(L, 1); + if (lua_gettop(L) >= 2) { + Player* target = getPlayer(L, 2); + if (target) { + npc->doSayToPlayer(target, text); + return 0; + } + } + + npc->doSay(text); + return 0; +} + +int NpcScriptInterface::luaActionMove(lua_State* L) +{ + //selfMove(direction) + Npc* npc = getScriptEnv()->getNpc(); + if (npc) { + g_game.internalMoveCreature(npc, getNumber(L, 1)); + } + return 0; +} + +int NpcScriptInterface::luaActionMoveTo(lua_State* L) +{ + //selfMoveTo(x,y,z) + Npc* npc = getScriptEnv()->getNpc(); + if (!npc) { + return 0; + } + + npc->doMoveTo(Position( + getNumber(L, 1), + getNumber(L, 2), + getNumber(L, 3) + )); + return 0; +} + +int NpcScriptInterface::luaActionTurn(lua_State* L) +{ + //selfTurn(direction) + Npc* npc = getScriptEnv()->getNpc(); + if (npc) { + g_game.internalCreatureTurn(npc, getNumber(L, 1)); + } + return 0; +} + +int NpcScriptInterface::luaActionFollow(lua_State* L) +{ + //selfFollow(player) + Npc* npc = getScriptEnv()->getNpc(); + if (!npc) { + pushBoolean(L, false); + return 1; + } + + pushBoolean(L, npc->setFollowCreature(getPlayer(L, 1))); + return 1; +} + +int NpcScriptInterface::luagetDistanceTo(lua_State* L) +{ + //getDistanceTo(uid) + ScriptEnvironment* env = getScriptEnv(); + + Npc* npc = env->getNpc(); + if (!npc) { + reportErrorFunc(getErrorDesc(LUA_ERROR_THING_NOT_FOUND)); + lua_pushnil(L); + return 1; + } + + uint32_t uid = getNumber(L, -1); + + Thing* thing = env->getThingByUID(uid); + if (!thing) { + reportErrorFunc(getErrorDesc(LUA_ERROR_THING_NOT_FOUND)); + lua_pushnil(L); + return 1; + } + + const Position& thingPos = thing->getPosition(); + const Position& npcPos = npc->getPosition(); + if (npcPos.z != thingPos.z) { + lua_pushnumber(L, -1); + } else { + int32_t dist = std::max(Position::getDistanceX(npcPos, thingPos), Position::getDistanceY(npcPos, thingPos)); + lua_pushnumber(L, dist); + } + return 1; +} + +int NpcScriptInterface::luaSetNpcFocus(lua_State* L) +{ + //doNpcSetCreatureFocus(cid) + Npc* npc = getScriptEnv()->getNpc(); + if (npc) { + npc->setCreatureFocus(getCreature(L, -1)); + } + return 0; +} + +int NpcScriptInterface::luaGetNpcCid(lua_State* L) +{ + //getNpcCid() + Npc* npc = getScriptEnv()->getNpc(); + if (npc) { + lua_pushnumber(L, npc->getID()); + } else { + lua_pushnil(L); + } + return 1; +} + +int NpcScriptInterface::luaGetNpcParameter(lua_State* L) +{ + //getNpcParameter(paramKey) + Npc* npc = getScriptEnv()->getNpc(); + if (!npc) { + lua_pushnil(L); + return 1; + } + + std::string paramKey = getString(L, -1); + + auto it = npc->parameters.find(paramKey); + if (it != npc->parameters.end()) { + LuaScriptInterface::pushString(L, it->second); + } else { + lua_pushnil(L); + } + return 1; +} + +int NpcScriptInterface::luaOpenShopWindow(lua_State* L) +{ + //openShopWindow(cid, items, onBuy callback, onSell callback) + int32_t sellCallback; + if (lua_isfunction(L, -1) == 0) { + sellCallback = -1; + lua_pop(L, 1); // skip it - use default value + } else { + sellCallback = popCallback(L); + } + + int32_t buyCallback; + if (lua_isfunction(L, -1) == 0) { + buyCallback = -1; + lua_pop(L, 1); // skip it - use default value + } else { + buyCallback = popCallback(L); + } + + if (lua_istable(L, -1) == 0) { + reportError(__FUNCTION__, "item list is not a table."); + pushBoolean(L, false); + return 1; + } + + std::list items; + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + const auto tableIndex = lua_gettop(L); + ShopInfo item; + + item.itemId = getField(L, tableIndex, "id"); + item.subType = getField(L, tableIndex, "subType"); + if (item.subType == 0) { + item.subType = getField(L, tableIndex, "subtype"); + lua_pop(L, 1); + } + + item.buyPrice = getField(L, tableIndex, "buy"); + item.sellPrice = getField(L, tableIndex, "sell"); + item.realName = getFieldString(L, tableIndex, "name"); + + items.push_back(item); + lua_pop(L, 6); + } + lua_pop(L, 1); + + Player* player = getPlayer(L, -1); + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + //Close any eventual other shop window currently open. + player->closeShopWindow(false); + + Npc* npc = getScriptEnv()->getNpc(); + if (!npc) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + npc->addShopPlayer(player); + player->setShopOwner(npc, buyCallback, sellCallback); + player->openShopWindow(npc, items); + + pushBoolean(L, true); + return 1; +} + +int NpcScriptInterface::luaCloseShopWindow(lua_State* L) +{ + //closeShopWindow(cid) + Npc* npc = getScriptEnv()->getNpc(); + if (!npc) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Player* player = getPlayer(L, 1); + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + int32_t buyCallback; + int32_t sellCallback; + + Npc* merchant = player->getShopOwner(buyCallback, sellCallback); + + //Check if we actually have a shop window with this player. + if (merchant == npc) { + player->sendCloseShop(); + + if (buyCallback != -1) { + luaL_unref(L, LUA_REGISTRYINDEX, buyCallback); + } + + if (sellCallback != -1) { + luaL_unref(L, LUA_REGISTRYINDEX, sellCallback); + } + + player->setShopOwner(nullptr, -1, -1); + npc->removeShopPlayer(player); + } + + pushBoolean(L, true); + return 1; +} + +int NpcScriptInterface::luaDoSellItem(lua_State* L) +{ + //doSellItem(cid, itemid, amount, subtype, actionid, canDropOnMap) + Player* player = getPlayer(L, 1); + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + uint32_t sellCount = 0; + + uint32_t itemId = getNumber(L, 2); + uint32_t amount = getNumber(L, 3); + uint32_t subType; + + int32_t n = getNumber(L, 4, -1); + if (n != -1) { + subType = n; + } else { + subType = 1; + } + + uint32_t actionId = getNumber(L, 5, 0); + bool canDropOnMap = getBoolean(L, 6, true); + + const ItemType& it = Item::items[itemId]; + if (it.stackable) { + while (amount > 0) { + int32_t stackCount = std::min(100, amount); + Item* item = Item::CreateItem(it.id, stackCount); + if (item && actionId != 0) { + item->setActionId(actionId); + } + + if (g_game.internalPlayerAddItem(player, item, canDropOnMap) != RETURNVALUE_NOERROR) { + delete item; + lua_pushnumber(L, sellCount); + return 1; + } + + amount -= stackCount; + sellCount += stackCount; + } + } else { + for (uint32_t i = 0; i < amount; ++i) { + Item* item = Item::CreateItem(it.id, subType); + if (item && actionId != 0) { + item->setActionId(actionId); + } + + if (g_game.internalPlayerAddItem(player, item, canDropOnMap) != RETURNVALUE_NOERROR) { + delete item; + lua_pushnumber(L, sellCount); + return 1; + } + + ++sellCount; + } + } + + lua_pushnumber(L, sellCount); + return 1; +} + +int NpcScriptInterface::luaNpcGetParameter(lua_State* L) +{ + // npc:getParameter(key) + const std::string& key = getString(L, 2); + Npc* npc = getUserdata(L, 1); + if (npc) { + auto it = npc->parameters.find(key); + if (it != npc->parameters.end()) { + pushString(L, it->second); + } else { + lua_pushnil(L); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int NpcScriptInterface::luaNpcSetFocus(lua_State* L) +{ + // npc:setFocus(creature) + Creature* creature = getCreature(L, 2); + Npc* npc = getUserdata(L, 1); + if (npc) { + npc->setCreatureFocus(creature); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int NpcScriptInterface::luaNpcOpenShopWindow(lua_State* L) +{ + // npc:openShopWindow(cid, items, buyCallback, sellCallback) + if (!isTable(L, 3)) { + reportErrorFunc("item list is not a table."); + pushBoolean(L, false); + return 1; + } + + Player* player = getPlayer(L, 2); + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Npc* npc = getUserdata(L, 1); + if (!npc) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + int32_t sellCallback = -1; + if (LuaScriptInterface::isFunction(L, 5)) { + sellCallback = luaL_ref(L, LUA_REGISTRYINDEX); + } + + int32_t buyCallback = -1; + if (LuaScriptInterface::isFunction(L, 4)) { + buyCallback = luaL_ref(L, LUA_REGISTRYINDEX); + } + + std::list items; + + lua_pushnil(L); + while (lua_next(L, 3) != 0) { + const auto tableIndex = lua_gettop(L); + ShopInfo item; + + item.itemId = getField(L, tableIndex, "id"); + item.subType = getField(L, tableIndex, "subType"); + if (item.subType == 0) { + item.subType = getField(L, tableIndex, "subtype"); + lua_pop(L, 1); + } + + item.buyPrice = getField(L, tableIndex, "buy"); + item.sellPrice = getField(L, tableIndex, "sell"); + item.realName = getFieldString(L, tableIndex, "name"); + + items.push_back(item); + lua_pop(L, 6); + } + lua_pop(L, 1); + + player->closeShopWindow(false); + npc->addShopPlayer(player); + + player->setShopOwner(npc, buyCallback, sellCallback); + player->openShopWindow(npc, items); + + pushBoolean(L, true); + return 1; +} + +int NpcScriptInterface::luaNpcCloseShopWindow(lua_State* L) +{ + // npc:closeShopWindow(player) + Player* player = getPlayer(L, 2); + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Npc* npc = getUserdata(L, 1); + if (!npc) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + int32_t buyCallback; + int32_t sellCallback; + + Npc* merchant = player->getShopOwner(buyCallback, sellCallback); + if (merchant == npc) { + player->sendCloseShop(); + if (buyCallback != -1) { + luaL_unref(L, LUA_REGISTRYINDEX, buyCallback); + } + + if (sellCallback != -1) { + luaL_unref(L, LUA_REGISTRYINDEX, sellCallback); + } + + player->setShopOwner(nullptr, -1, -1); + npc->removeShopPlayer(player); + } + + pushBoolean(L, true); + return 1; +} + +NpcEventsHandler::NpcEventsHandler(const std::string& file, Npc* npc) : + npc(npc), scriptInterface(npc->getScriptInterface()) +{ + loaded = scriptInterface->loadFile("data/npc/scripts/" + file, npc) == 0; + if (!loaded) { + std::cout << "[Warning - NpcScript::NpcScript] Can not load script: " << file << std::endl; + std::cout << scriptInterface->getLastLuaError() << std::endl; + } else { + creatureSayEvent = scriptInterface->getEvent("onCreatureSay"); + creatureDisappearEvent = scriptInterface->getEvent("onCreatureDisappear"); + creatureAppearEvent = scriptInterface->getEvent("onCreatureAppear"); + creatureMoveEvent = scriptInterface->getEvent("onCreatureMove"); + playerCloseChannelEvent = scriptInterface->getEvent("onPlayerCloseChannel"); + playerEndTradeEvent = scriptInterface->getEvent("onPlayerEndTrade"); + thinkEvent = scriptInterface->getEvent("onThink"); + } +} + +bool NpcEventsHandler::isLoaded() const +{ + return loaded; +} + +void NpcEventsHandler::onCreatureAppear(Creature* creature) +{ + if (creatureAppearEvent == -1) { + return; + } + + //onCreatureAppear(creature) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - NpcScript::onCreatureAppear] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(creatureAppearEvent, scriptInterface); + env->setNpc(npc); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(creatureAppearEvent); + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + scriptInterface->callFunction(1); +} + +void NpcEventsHandler::onCreatureDisappear(Creature* creature) +{ + if (creatureDisappearEvent == -1) { + return; + } + + //onCreatureDisappear(creature) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - NpcScript::onCreatureDisappear] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(creatureDisappearEvent, scriptInterface); + env->setNpc(npc); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(creatureDisappearEvent); + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + scriptInterface->callFunction(1); +} + +void NpcEventsHandler::onCreatureMove(Creature* creature, const Position& oldPos, const Position& newPos) +{ + if (creatureMoveEvent == -1) { + return; + } + + //onCreatureMove(creature, oldPos, newPos) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - NpcScript::onCreatureMove] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(creatureMoveEvent, scriptInterface); + env->setNpc(npc); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(creatureMoveEvent); + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + LuaScriptInterface::pushPosition(L, oldPos); + LuaScriptInterface::pushPosition(L, newPos); + scriptInterface->callFunction(3); +} + +void NpcEventsHandler::onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) +{ + if (creatureSayEvent == -1) { + return; + } + + //onCreatureSay(creature, type, msg) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - NpcScript::onCreatureSay] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(creatureSayEvent, scriptInterface); + env->setNpc(npc); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(creatureSayEvent); + LuaScriptInterface::pushUserdata(L, creature); + LuaScriptInterface::setCreatureMetatable(L, -1, creature); + lua_pushnumber(L, type); + LuaScriptInterface::pushString(L, text); + scriptInterface->callFunction(3); +} + +void NpcEventsHandler::onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, + uint8_t count, uint8_t amount, bool ignore, bool inBackpacks) +{ + if (callback == -1) { + return; + } + + //onBuy(player, itemid, count, amount, ignore, inbackpacks) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - NpcScript::onPlayerTrade] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(-1, scriptInterface); + env->setNpc(npc); + + lua_State* L = scriptInterface->getLuaState(); + LuaScriptInterface::pushCallback(L, callback); + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + lua_pushnumber(L, itemId); + lua_pushnumber(L, count); + lua_pushnumber(L, amount); + LuaScriptInterface::pushBoolean(L, ignore); + LuaScriptInterface::pushBoolean(L, inBackpacks); + scriptInterface->callFunction(6); +} + +void NpcEventsHandler::onPlayerCloseChannel(Player* player) +{ + if (playerCloseChannelEvent == -1) { + return; + } + + //onPlayerCloseChannel(player) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - NpcScript::onPlayerCloseChannel] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(playerCloseChannelEvent, scriptInterface); + env->setNpc(npc); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(playerCloseChannelEvent); + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + scriptInterface->callFunction(1); +} + +void NpcEventsHandler::onPlayerEndTrade(Player* player) +{ + if (playerEndTradeEvent == -1) { + return; + } + + //onPlayerEndTrade(player) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - NpcScript::onPlayerEndTrade] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(playerEndTradeEvent, scriptInterface); + env->setNpc(npc); + + lua_State* L = scriptInterface->getLuaState(); + scriptInterface->pushFunction(playerEndTradeEvent); + LuaScriptInterface::pushUserdata(L, player); + LuaScriptInterface::setMetatable(L, -1, "Player"); + scriptInterface->callFunction(1); +} + +void NpcEventsHandler::onThink() +{ + if (thinkEvent == -1) { + return; + } + + //onThink() + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - NpcScript::onThink] Call stack overflow" << std::endl; + return; + } + + ScriptEnvironment* env = scriptInterface->getScriptEnv(); + env->setScriptId(thinkEvent, scriptInterface); + env->setNpc(npc); + + scriptInterface->pushFunction(thinkEvent); + scriptInterface->callFunction(0); +} diff --git a/src/npc.h b/src/npc.h index acf95ec..cd463bb 100644 --- a/src/npc.h +++ b/src/npc.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,20 +21,86 @@ #define FS_NPC_H_B090D0CB549D4435AFA03647195D156F #include "creature.h" +#include "luascript.h" #include class Npc; class Player; -class BehaviourDatabase; class Npcs { public: - static void loadNpcs(); static void reload(); }; +class NpcScriptInterface final : public LuaScriptInterface +{ + public: + NpcScriptInterface(); + + bool loadNpcLib(const std::string& file); + + private: + void registerFunctions(); + + static int luaActionSay(lua_State* L); + static int luaActionMove(lua_State* L); + static int luaActionMoveTo(lua_State* L); + static int luaActionTurn(lua_State* L); + static int luaActionFollow(lua_State* L); + static int luagetDistanceTo(lua_State* L); + static int luaSetNpcFocus(lua_State* L); + static int luaGetNpcCid(lua_State* L); + static int luaGetNpcParameter(lua_State* L); + static int luaOpenShopWindow(lua_State* L); + static int luaCloseShopWindow(lua_State* L); + static int luaDoSellItem(lua_State* L); + + // metatable + static int luaNpcGetParameter(lua_State* L); + static int luaNpcSetFocus(lua_State* L); + + static int luaNpcOpenShopWindow(lua_State* L); + static int luaNpcCloseShopWindow(lua_State* L); + + private: + bool initState() override; + bool closeState() override; + + bool libLoaded; +}; + +class NpcEventsHandler +{ + public: + NpcEventsHandler(const std::string& file, Npc* npc); + + void onCreatureAppear(Creature* creature); + void onCreatureDisappear(Creature* creature); + void onCreatureMove(Creature* creature, const Position& oldPos, const Position& newPos); + void onCreatureSay(Creature* creature, SpeakClasses, const std::string& text); + void onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count, uint8_t amount, bool ignore = false, bool inBackpacks = false); + void onPlayerCloseChannel(Player* player); + void onPlayerEndTrade(Player* player); + void onThink(); + + bool isLoaded() const; + + private: + Npc* npc; + NpcScriptInterface* scriptInterface; + + int32_t creatureAppearEvent = -1; + int32_t creatureDisappearEvent = -1; + int32_t creatureMoveEvent = -1; + int32_t creatureSayEvent = -1; + int32_t playerCloseChannelEvent = -1; + int32_t playerEndTradeEvent = -1; + int32_t thinkEvent = -1; + bool loaded = false; +}; + class Npc final : public Creature { public: @@ -44,41 +110,53 @@ class Npc final : public Creature Npc(const Npc&) = delete; Npc& operator=(const Npc&) = delete; - Npc* getNpc() final { + Npc* getNpc() override { return this; } - const Npc* getNpc() const final { + const Npc* getNpc() const override { return this; } - bool isPushable() const final { - return baseSpeed > 0; + bool isPushable() const override { + return pushable && walkTicks != 0; } - void setID() final { + void setID() override { if (id == 0) { id = npcAutoID++; } } - void removeList() final; - void addList() final; + void removeList() override; + void addList() override; static Npc* createNpc(const std::string& name); - bool canSee(const Position& pos) const final; + bool canSee(const Position& pos) const override; bool load(); void reload(); - const std::string& getName() const final { + const std::string& getName() const override { return name; } - const std::string& getNameDescription() const final { + const std::string& getNameDescription() const override { return name; } + CreatureType_t getType() const override { + return CREATURETYPE_NPC; + } + + uint8_t getSpeechBubble() const override { + return speechBubble; + } + void setSpeechBubble(const uint8_t bubble) { + speechBubble = bubble; + } + void doSay(const std::string& text); + void doSayToPlayer(Player* player, const std::string& text); void doMoveTo(const Position& pos); @@ -90,38 +168,45 @@ class Npc final : public Creature } void setMasterPos(Position pos, int32_t radius = 1) { masterPos = pos; - if (masterRadius == 0) { + if (masterRadius == -1) { masterRadius = radius; } } + void onPlayerCloseChannel(Player* player); + void onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count, + uint8_t amount, bool ignore = false, bool inBackpacks = false); + void onPlayerEndTrade(Player* player, int32_t buyCallback, int32_t sellCallback); + void turnToCreature(Creature* creature); void setCreatureFocus(Creature* creature); + NpcScriptInterface* getScriptInterface(); + static uint32_t npcAutoID; - protected: + private: explicit Npc(const std::string& name); - void onCreatureAppear(Creature* creature, bool isLogin) final; - void onRemoveCreature(Creature* creature, bool isLogout) final; + void onCreatureAppear(Creature* creature, bool isLogin) override; + void onRemoveCreature(Creature* creature, bool isLogout) override; void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, - const Tile* oldTile, const Position& oldPos, bool teleport) final; + const Tile* oldTile, const Position& oldPos, bool teleport) override; - 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; + void onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) override; + void onThink(uint32_t interval) override; + std::string getDescription(int32_t lookDistance) const override; - bool isImmune(CombatType_t) const final { - return true; + bool isImmune(CombatType_t) const override { + return !attackable; } - bool isImmune(ConditionType_t) const final { - return true; + bool isImmune(ConditionType_t) const override { + return !attackable; } - bool isAttackable() const final { - return false; + bool isAttackable() const override { + return attackable; } - bool getNextStep(Direction& dir, uint32_t& flags) final; + bool getNextStep(Direction& dir, uint32_t& flags) override; void setIdle(bool idle); void updateIdleStatus(); @@ -130,29 +215,41 @@ class Npc final : public Creature bool getRandomStep(Direction& dir) const; void reset(); + bool loadFromXml(); + void addShopPlayer(Player* player); + void removeShopPlayer(Player* player); + void closeAllShopWindows(); + + std::map parameters; + + std::set shopPlayerSet; std::set spectators; std::string name; std::string filename; + NpcEventsHandler* npcEventHandler; + Position masterPos; - uint32_t lastTalkCreature; - uint32_t focusCreature; - uint32_t masterRadius; + uint32_t walkTicks; + int32_t focusCreature; + int32_t masterRadius; - int64_t conversationStartTime; - int64_t conversationEndTime; - int64_t staticMovementTime; + uint8_t speechBubble; + bool floorChange; + bool attackable; + bool ignoreHeight; bool loaded; bool isIdle; + bool pushable; - BehaviourDatabase* behaviourDatabase; + static NpcScriptInterface* scriptInterface; friend class Npcs; - friend class BehaviourDatabase; + friend class NpcScriptInterface; }; #endif diff --git a/src/otpch.cpp b/src/otpch.cpp index 5db3123..c60a95d 100644 --- a/src/otpch.cpp +++ b/src/otpch.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/otpch.h b/src/otpch.h index a2ff7c1..e154370 100644 --- a/src/otpch.h +++ b/src/otpch.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/otserv.cpp b/src/otserv.cpp index ce6e996..c47501c 100644 --- a/src/otserv.cpp +++ b/src/otserv.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,18 +23,19 @@ #include "game.h" -#ifndef _WIN32 -#include // for sigemptyset() -#endif +#include "iomarket.h" #include "configmanager.h" #include "scriptmanager.h" #include "rsa.h" +#include "protocolold.h" #include "protocollogin.h" #include "protocolstatus.h" #include "databasemanager.h" #include "scheduler.h" #include "databasetasks.h" +#include "script.h" +#include DatabaseTasks g_databaseTasks; Dispatcher g_dispatcher; @@ -44,6 +45,7 @@ Game g_game; ConfigManager g_config; Monsters g_monsters; Vocations g_vocations; +extern Scripts* g_scripts; RSA g_RSA; std::mutex g_loaderLock; @@ -56,9 +58,9 @@ void startupErrorMessage(const std::string& errorStr) g_loaderSignal.notify_all(); } -void mainLoader(int argc, char* argv[], ServiceManager* servicer); +void mainLoader(int argc, char* argv[], ServiceManager* services); -void badAllocationHandler() +[[noreturn]] 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"); @@ -71,15 +73,6 @@ 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(); @@ -91,19 +84,6 @@ int main(int argc, char* argv[]) 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; @@ -146,6 +126,21 @@ void mainLoader(int, char*[], ServiceManager* services) std::cout << "Visit our forum for updates, support, and resources: http://otland.net/." << std::endl; std::cout << std::endl; + // check if config.lua or config.lua.dist exist + std::ifstream c_test("./config.lua"); + if (!c_test.is_open()) { + std::ifstream config_lua_dist("./config.lua.dist"); + if (config_lua_dist.is_open()) { + std::cout << ">> copying config.lua.dist to config.lua" << std::endl; + std::ofstream config_lua("config.lua"); + config_lua << config_lua_dist.rdbuf(); + config_lua.close(); + config_lua_dist.close(); + } + } else { + c_test.close(); + } + // read global config std::cout << ">> Loading config" << std::endl; if (!g_config.load()) { @@ -165,16 +160,14 @@ void mainLoader(int, char*[], ServiceManager* services) //set RSA key try { g_RSA.loadPEM("key.pem"); - } - catch (const std::exception& e) { + } catch(const std::exception& e) { startupErrorMessage(e.what()); return; } std::cout << ">> Establishing database connection..." << std::flush; - Database* db = Database::getInstance(); - if (!db->connect()) { + if (!Database::getInstance().connect()) { startupErrorMessage("Failed to connect to database."); return; } @@ -190,6 +183,8 @@ void mainLoader(int, char*[], ServiceManager* services) } g_databaseTasks.start(); + DatabaseManager::updateDatabase(); + if (g_config.getBoolean(ConfigManager::OPTIMIZE_DATABASE) && !DatabaseManager::optimizeTables()) { std::cout << "> No tables were optimized." << std::endl; } @@ -203,26 +198,42 @@ void mainLoader(int, char*[], ServiceManager* services) // load item data std::cout << ">> Loading items" << std::endl; - if (!Item::items.loadItems()) { - startupErrorMessage("Unable to load items (SRV)!"); + if (!Item::items.loadFromOtb("data/items/items.otb")) { + startupErrorMessage("Unable to load items (OTB)!"); + return; + } + + if (!Item::items.loadFromXml()) { + startupErrorMessage("Unable to load items (XML)!"); return; } std::cout << ">> Loading script systems" << std::endl; - if (!ScriptingManager::getInstance()->loadScriptSystems()) { + if (!ScriptingManager::getInstance().loadScriptSystems()) { startupErrorMessage("Failed to load script systems"); return; } + std::cout << ">> Loading lua scripts" << std::endl; + if (!g_scripts->loadScripts("scripts", false, false)) { + startupErrorMessage("Failed to load lua scripts"); + return; + } + std::cout << ">> Loading monsters" << std::endl; if (!g_monsters.loadFromXml()) { startupErrorMessage("Unable to load monsters!"); return; } + std::cout << ">> Loading lua monsters" << std::endl; + if (!g_scripts->loadScripts("monster", false, false)) { + startupErrorMessage("Failed to load lua monsters"); + return; + } + std::cout << ">> Loading outfits" << std::endl; - auto& outfits = Outfits::getInstance(); - if (!outfits.loadFromXml()) { + if (!Outfits::getInstance().loadFromXml()) { startupErrorMessage("Unable to load outfits!"); return; } @@ -255,11 +266,14 @@ void mainLoader(int, char*[], ServiceManager* services) g_game.setGameState(GAME_STATE_INIT); // Game client protocols - services->add(g_config.getNumber(ConfigManager::GAME_PORT)); - services->add(g_config.getNumber(ConfigManager::LOGIN_PORT)); + services->add(static_cast(g_config.getNumber(ConfigManager::GAME_PORT))); + services->add(static_cast(g_config.getNumber(ConfigManager::LOGIN_PORT))); // OT protocols - services->add(g_config.getNumber(ConfigManager::STATUS_PORT)); + services->add(static_cast(g_config.getNumber(ConfigManager::STATUS_PORT))); + + // Legacy login protocol + services->add(static_cast(g_config.getNumber(ConfigManager::LOGIN_PORT))); RentPeriod_t rentPeriod; std::string strRentPeriod = asLowerCaseString(g_config.getString(ConfigManager::HOUSE_RENT_PERIOD)); @@ -278,6 +292,9 @@ void mainLoader(int, char*[], ServiceManager* services) g_game.map.houses.payHouses(rentPeriod); + IOMarket::checkExpiredOffers(); + IOMarket::getInstance().updateStatistics(); + std::cout << ">> Loaded all modules, server starting up..." << std::endl; #ifndef _WIN32 diff --git a/src/outputmessage.cpp b/src/outputmessage.cpp index 0d3d597..b4c0f77 100644 --- a/src/outputmessage.cpp +++ b/src/outputmessage.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,14 +29,6 @@ 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 - struct rebind {typedef LockfreePoolingAllocator other;}; -}; - void OutputMessagePool::scheduleSendAll() { auto functor = std::bind(&OutputMessagePool::sendAll, this); @@ -79,5 +71,7 @@ void OutputMessagePool::removeProtocolFromAutosend(const Protocol_ptr& protocol) OutputMessage_ptr OutputMessagePool::getOutputMessage() { - return std::allocate_shared(OutputMessageAllocator()); + // LockfreePoolingAllocator will leave (void* allocate) ill-formed because + // of sizeof(T), so this guaranatees that only one list will be initialized + return std::allocate_shared(LockfreePoolingAllocator()); } diff --git a/src/outputmessage.h b/src/outputmessage.h index 3dde508..1a78599 100644 --- a/src/outputmessage.h +++ b/src/outputmessage.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,76 +28,80 @@ class Protocol; class OutputMessage : public NetworkMessage { -public: - OutputMessage() = default; + public: + OutputMessage() = default; - // non-copyable - OutputMessage(const OutputMessage&) = delete; - OutputMessage& operator=(const OutputMessage&) = delete; + // non-copyable + OutputMessage(const OutputMessage&) = delete; + OutputMessage& operator=(const OutputMessage&) = delete; - uint8_t* getOutputBuffer() { - return buffer + outputBufferStart; - } + uint8_t* getOutputBuffer() { + return buffer + outputBufferStart; + } - void writeMessageLength() { - add_header(info.length); - } + void writeMessageLength() { + add_header(info.length); + } - void addCryptoHeader() { - writeMessageLength(); - } + void addCryptoHeader(bool addChecksum) { + if (addChecksum) { + add_header(adlerChecksum(buffer + outputBufferStart, info.length)); + } - inline void append(const NetworkMessage& msg) { - auto msgLen = msg.getLength(); - memcpy(buffer + info.position, msg.getBuffer() + 4, msgLen); - info.length += msgLen; - info.position += msgLen; - } + writeMessageLength(); + } - 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; - } + void append(const NetworkMessage& msg) { + auto msgLen = msg.getLength(); + memcpy(buffer + info.position, msg.getBuffer() + 8, msgLen); + info.length += msgLen; + info.position += msgLen; + } -protected: - template - 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); - } + void append(const OutputMessage_ptr& msg) { + auto msgLen = msg->getLength(); + memcpy(buffer + info.position, msg->getBuffer() + 8, msgLen); + info.length += msgLen; + info.position += msgLen; + } - MsgSize_t outputBufferStart = INITIAL_BUFFER_POSITION; + private: + template + 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; + public: + // non-copyable + OutputMessagePool(const OutputMessagePool&) = delete; + OutputMessagePool& operator=(const OutputMessagePool&) = delete; - static OutputMessagePool& getInstance() { - static OutputMessagePool instance; - return instance; - } + static OutputMessagePool& getInstance() { + static OutputMessagePool instance; + return instance; + } - void sendAll(); - void scheduleSendAll(); + void sendAll(); + void scheduleSendAll(); - static OutputMessage_ptr getOutputMessage(); + 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 bufferedProtocols; + 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 bufferedProtocols; }; diff --git a/src/party.cpp b/src/party.cpp index e0d80a4..1f1cd50 100644 --- a/src/party.cpp +++ b/src/party.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -39,13 +39,13 @@ void Party::disband() return; } - Player* currentLeader = leader; leader = nullptr; currentLeader->setParty(nullptr); currentLeader->sendClosePrivate(CHANNEL_PARTY); g_game.updatePlayerShield(currentLeader); + g_game.updatePlayerHelpers(*currentLeader); currentLeader->sendCreatureSkull(currentLeader); currentLeader->sendTextMessage(MESSAGE_INFO_DESCR, "Your party has been disbanded."); @@ -70,8 +70,8 @@ void Party::disband() member->sendCreatureSkull(currentLeader); currentLeader->sendCreatureSkull(member); + g_game.updatePlayerHelpers(*member); } - memberList.clear(); delete this; } @@ -112,10 +112,12 @@ bool Party::leaveParty(Player* player) player->setParty(nullptr); player->sendClosePrivate(CHANNEL_PARTY); g_game.updatePlayerShield(player); + g_game.updatePlayerHelpers(*player); for (Player* member : memberList) { member->sendCreatureSkull(player); player->sendPlayerPartyIcons(member); + g_game.updatePlayerHelpers(*member); } leader->sendCreatureSkull(player); @@ -125,7 +127,6 @@ bool Party::leaveParty(Player* player) player->sendTextMessage(MESSAGE_INFO_DESCR, "You have left the party."); updateSharedExperience(); - updateVocationsList(); clearPlayerPoints(player); @@ -212,9 +213,10 @@ bool Party::joinParty(Player& player) memberList.push_back(&player); + g_game.updatePlayerHelpers(player); + player.removePartyInvitation(this); updateSharedExperience(); - updateVocationsList(); const std::string& leaderName = leader->getName(); ss.str(std::string()); @@ -242,6 +244,12 @@ bool Party::removeInvite(Player& player, bool removeFromPlayer/* = true*/) if (empty()) { disband(); + } else { + for (Player* member : memberList) { + g_game.updatePlayerHelpers(*member); + } + + g_game.updatePlayerHelpers(*leader); } return true; @@ -269,7 +277,7 @@ bool Party::invitePlayer(Player& player) std::ostringstream ss; ss << player.getName() << " has been invited."; - if (memberList.empty() && inviteList.empty()) { + if (empty()) { ss << " Open the party channel to communicate with your members."; g_game.updatePlayerShield(leader); leader->sendCreatureSkull(leader); @@ -279,6 +287,11 @@ bool Party::invitePlayer(Player& player) inviteList.push_back(&player); + for (Player* member : memberList) { + g_game.updatePlayerHelpers(*member); + } + g_game.updatePlayerHelpers(*leader); + leader->sendCreatureShield(&player); player.sendCreatureShield(leader); @@ -323,15 +336,6 @@ void Party::broadcastPartyMessage(MessageClasses msgClass, const std::string& ms } } -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) { @@ -343,30 +347,6 @@ void Party::updateSharedExperience() } } -void Party::updateVocationsList() -{ - std::set 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(size * (10 + (size - 1) * 5)) / 100.f; - } else { - extraExpRate = 0.20f; - } -} - bool Party::setSharedExperience(Player* player, bool sharedExpActive) { if (!player || leader != player) { @@ -419,7 +399,7 @@ bool Party::canUseSharedExperience(const Player* player) const } } - uint32_t minLevel = static_cast(std::ceil((static_cast(highestLevel) * 2) / 3)); + uint32_t minLevel = static_cast(std::ceil((static_cast(highestLevel) * 2) / 3)); if (player->getLevel() < minLevel) { return false; } diff --git a/src/party.h b/src/party.h index 96fce94..50c10c0 100644 --- a/src/party.h +++ b/src/party.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,7 +26,7 @@ class Player; class Party; -typedef std::vector PlayerVector; +using PlayerVector = std::vector; class Party { @@ -61,13 +61,12 @@ class Party 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(); } bool canOpenCorpse(uint32_t ownerId) const; - void shareExperience(uint64_t experience, Creature* source/* = nullptr*/); + void shareExperience(uint64_t experience, Creature* source = nullptr); bool setSharedExperience(Player* player, bool sharedExpActive); bool isSharedExperienceActive() const { return sharedExpActive; @@ -78,12 +77,10 @@ class Party bool canUseSharedExperience(const Player* player) const; void updateSharedExperience(); - void updateVocationsList(); - void updatePlayerTicks(Player* player, uint32_t points); void clearPlayerPoints(Player* player); - protected: + private: bool canEnableSharedExperience(); std::map ticksMap; @@ -93,8 +90,6 @@ class Party Player* leader; - float extraExpRate = 0.20f; - bool sharedExpActive = false; bool sharedExpEnabled = false; }; diff --git a/src/player.cpp b/src/player.cpp index a4936c0..41a72ff 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,12 +32,14 @@ #include "monster.h" #include "movement.h" #include "scheduler.h" +#include "weapons.h" extern ConfigManager g_config; extern Game g_game; extern Chat* g_chat; extern Vocations g_vocations; extern MoveEvents* g_moveEvents; +extern Weapons* g_weapons; extern CreatureEvents* g_creatureEvents; extern Events* g_events; @@ -46,8 +48,9 @@ MuteCountMap Player::muteCountMap; uint32_t Player::playerAutoID = 0x10000000; Player::Player(ProtocolGame_ptr p) : - Creature(), lastPing(OTSYS_TIME()), lastPong(lastPing), client(std::move(p)) + Creature(), lastPing(OTSYS_TIME()), lastPong(lastPing), inbox(new Inbox(ITEM_INBOX)), client(std::move(p)) { + inbox->incrementReferenceCounter(); } Player::~Player() @@ -60,9 +63,12 @@ Player::~Player() } for (const auto& it : depotLockerMap) { + it.second->removeInbox(inbox); it.second->decrementReferenceCounter(); } + inbox->decrementReferenceCounter(); + setWriteItem(nullptr); setEditHouse(nullptr); } @@ -129,23 +135,53 @@ std::string Player::getDescription(int32_t lookDistance) const } } - if (guild && guildRank) { + if (party) { if (lookDistance == -1) { - s << " You are "; + s << " Your party has "; } else if (sex == PLAYERSEX_FEMALE) { - s << " She is "; + s << " She is in a party with "; } else { - s << " He is "; + s << " He is in a party with "; } - s << guildRank->name << " of the " << guild->getName(); - if (!guildNick.empty()) { - s << " (" << guildNick << ')'; + size_t memberCount = party->getMemberCount() + 1; + if (memberCount == 1) { + s << "1 member and "; + } else { + s << memberCount << " members and "; } - s << "."; + size_t invitationCount = party->getInvitationCount(); + if (invitationCount == 1) { + s << "1 pending invitation."; + } else { + s << invitationCount << " pending invitations."; + } } + if (!guild || !guildRank) { + return s.str(); + } + + if (lookDistance == -1) { + s << " You are "; + } else if (sex == PLAYERSEX_FEMALE) { + s << " She is "; + } else { + s << " He is "; + } + + s << guildRank->name << " of the " << guild->getName(); + if (!guildNick.empty()) { + s << " (" << guildNick << ')'; + } + + size_t memberCount = guild->getMemberCount(); + if (memberCount == 1) { + s << ", which has 1 member, " << guild->getMembersOnline().size() << " of them online."; + } else { + s << ", which has " << memberCount << " members, " << guild->getMembersOnline().size() << " of them online."; + } return s.str(); } @@ -167,43 +203,104 @@ void Player::removeConditionSuppressions(uint32_t conditions) conditionSuppressions &= ~conditions; } -Item* Player::getWeapon() const +Item* Player::getWeapon(slots_t slot, bool ignoreAmmo) const { - Item* item = inventory[CONST_SLOT_LEFT]; - if (item && item->getWeaponType() != WEAPON_NONE && item->getWeaponType() != WEAPON_SHIELD && item->getWeaponType() != WEAPON_AMMO) { + Item* item = inventory[slot]; + if (!item) { + return nullptr; + } + + WeaponType_t weaponType = item->getWeaponType(); + if (weaponType == WEAPON_NONE || weaponType == WEAPON_SHIELD || weaponType == WEAPON_AMMO) { + return nullptr; + } + + if (!ignoreAmmo && weaponType == WEAPON_DISTANCE) { + const ItemType& it = Item::items[item->getID()]; + if (it.ammoType != AMMO_NONE) { + Item* ammoItem = inventory[CONST_SLOT_AMMO]; + if (!ammoItem || ammoItem->getAmmoType() != it.ammoType) { + return nullptr; + } + item = ammoItem; + } + } + return item; +} + +Item* Player::getWeapon(bool ignoreAmmo/* = false*/) const +{ + Item* item = getWeapon(CONST_SLOT_LEFT, ignoreAmmo); + if (item) { return item; } - item = inventory[CONST_SLOT_RIGHT]; - if (item && item->getWeaponType() != WEAPON_NONE && item->getWeaponType() != WEAPON_SHIELD && item->getWeaponType() != WEAPON_AMMO) { + item = getWeapon(CONST_SLOT_RIGHT, ignoreAmmo); + if (item) { return item; } - return nullptr; } -Item* Player::getAmmunition() const +WeaponType_t Player::getWeaponType() const { - return inventory[CONST_SLOT_AMMO]; + Item* item = getWeapon(); + if (!item) { + return WEAPON_NONE; + } + return item->getWeaponType(); +} + +int32_t Player::getWeaponSkill(const Item* item) const +{ + if (!item) { + return getSkillLevel(SKILL_FIST); + } + + int32_t attackSkill; + + WeaponType_t weaponType = item->getWeaponType(); + switch (weaponType) { + case WEAPON_SWORD: { + attackSkill = getSkillLevel(SKILL_SWORD); + break; + } + + case WEAPON_CLUB: { + attackSkill = getSkillLevel(SKILL_CLUB); + break; + } + + case WEAPON_AXE: { + attackSkill = getSkillLevel(SKILL_AXE); + break; + } + + case WEAPON_DISTANCE: { + attackSkill = getSkillLevel(SKILL_DISTANCE); + break; + } + + default: { + attackSkill = 0; + break; + } + } + return attackSkill; } int32_t Player::getArmor() const { - int32_t armor = 0; // base armor + int32_t armor = 0; - static const slots_t armorSlots[] = { CONST_SLOT_HEAD, CONST_SLOT_NECKLACE, CONST_SLOT_ARMOR, CONST_SLOT_LEGS, CONST_SLOT_FEET, CONST_SLOT_RING }; + static const slots_t armorSlots[] = {CONST_SLOT_HEAD, CONST_SLOT_NECKLACE, CONST_SLOT_ARMOR, CONST_SLOT_LEGS, CONST_SLOT_FEET, CONST_SLOT_RING}; for (slots_t slot : armorSlots) { Item* inventoryItem = inventory[slot]; if (inventoryItem) { armor += inventoryItem->getArmor(); } } - - if (armor > 1) { - armor = rand() % (armor >> 1) + (armor >> 1); - } - - return armor; + return static_cast(armor * vocation->armorMultiplier); } void Player::getShieldAndWeapon(const Item*& shield, const Item*& weapon) const @@ -219,7 +316,7 @@ void Player::getShieldAndWeapon(const Item*& shield, const Item*& weapon) const switch (item->getWeaponType()) { case WEAPON_NONE: - break; + break; case WEAPON_SHIELD: { if (!shield || item->getDefense() > shield->getDefense()) { @@ -236,67 +333,56 @@ void Player::getShieldAndWeapon(const Item*& shield, const Item*& weapon) const } } -int32_t Player::getDefense() +int32_t Player::getDefense() const { - int32_t totalDefense = 5; int32_t defenseSkill = getSkillLevel(SKILL_FIST); - + int32_t defenseValue = 7; const Item* weapon; const Item* shield; getShieldAndWeapon(shield, weapon); if (weapon) { - totalDefense = weapon->getDefense() + 1; - - switch (weapon->getWeaponType()) { - case WEAPON_AXE: - defenseSkill = SKILL_AXE; - break; - case WEAPON_SWORD: - defenseSkill = SKILL_SWORD; - break; - case WEAPON_CLUB: - defenseSkill = SKILL_CLUB; - break; - case WEAPON_DISTANCE: - case WEAPON_AMMO: - defenseSkill = SKILL_DISTANCE; - break; - default: - break; - } + defenseValue = weapon->getDefense() + weapon->getExtraDefense(); + defenseSkill = getWeaponSkill(weapon); } if (shield) { - totalDefense = shield->getDefense() + 1; + defenseValue = weapon != nullptr ? shield->getDefense() + weapon->getExtraDefense() : shield->getDefense(); defenseSkill = getSkillLevel(SKILL_SHIELD); } - fightMode_t attackMode = getFightMode(); + if (defenseSkill == 0) { + switch (fightMode) { + case FIGHTMODE_ATTACK: + case FIGHTMODE_BALANCED: + return 1; - if ((followCreature || !attackedCreature) && earliestAttackTime <= OTSYS_TIME()) { - attackMode = FIGHTMODE_DEFENSE; + case FIGHTMODE_DEFENSE: + return 2; + } } - if (attackMode == FIGHTMODE_ATTACK) { - totalDefense -= 4 * totalDefense / 10; - } else if (attackMode == FIGHTMODE_DEFENSE) { - totalDefense += 8 * totalDefense / 10; - } - - if (totalDefense) { - int32_t formula = (5 * (defenseSkill) + 50) * totalDefense; - int32_t randresult = rand() % 100; - - totalDefense = formula * ((rand() % 100 + randresult) / 2) / 10000.; - } - - return totalDefense; + return (defenseSkill / 4. + 2.23) * defenseValue * 0.15 * getDefenseFactor() * vocation->defenseMultiplier; } -fightMode_t Player::getFightMode() const +float Player::getAttackFactor() const { - return fightMode; + switch (fightMode) { + case FIGHTMODE_ATTACK: return 1.0f; + case FIGHTMODE_BALANCED: return 1.2f; + case FIGHTMODE_DEFENSE: return 2.0f; + default: return 1.0f; + } +} + +float Player::getDefenseFactor() const +{ + switch (fightMode) { + case FIGHTMODE_ATTACK: return (OTSYS_TIME() - lastAttack) < getAttackSpeed() ? 0.5f : 1.0f; + case FIGHTMODE_BALANCED: return (OTSYS_TIME() - lastAttack) < getAttackSpeed() ? 0.75f : 1.0f; + case FIGHTMODE_DEFENSE: return 1.0f; + default: return 1.0f; + } } uint16_t Player::getClientIcons() const @@ -308,6 +394,19 @@ uint16_t Player::getClientIcons() const } } + if (pzLocked) { + icons |= ICON_REDSWORDS; + } + + if (tile->hasFlag(TILESTATE_PROTECTIONZONE)) { + icons |= ICON_PIGEON; + + // Don't show ICON_SWORDS if player is in protection zone. + if (hasBitSet(ICON_SWORDS, icons)) { + icons &= ~ICON_SWORDS; + } + } + // Game client debugs with 10 or more icons // so let's prevent that from happening. std::bitset<20> icon_bitset(static_cast(icons)); @@ -433,9 +532,18 @@ void Player::addContainer(uint8_t cid, Container* container) return; } + if (container->getID() == ITEM_BROWSEFIELD) { + container->incrementReferenceCounter(); + } + auto it = openContainers.find(cid); if (it != openContainers.end()) { OpenContainer& openContainer = it->second; + Container* oldContainer = openContainer.container; + if (oldContainer->getID() == ITEM_BROWSEFIELD) { + oldContainer->decrementReferenceCounter(); + } + openContainer.container = container; openContainer.index = 0; } else { @@ -453,7 +561,13 @@ void Player::closeContainer(uint8_t cid) return; } + OpenContainer openContainer = it->second; + Container* container = openContainer.container; openContainers.erase(it); + + if (container && container->getID() == ITEM_BROWSEFIELD) { + container->decrementReferenceCounter(); + } } void Player::setContainerIndex(uint8_t cid, uint16_t index) @@ -507,7 +621,7 @@ uint16_t Player::getLookCorpse() const } } -void Player::addStorageValue(const uint32_t key, const int32_t value) +void Player::addStorageValue(const uint32_t key, const int32_t value, const bool isLogin/* = false*/) { if (IS_IN_KEYRANGE(key, RESERVED_RANGE)) { if (IS_IN_KEYRANGE(key, OUTFITS_RANGE)) { @@ -516,15 +630,27 @@ void Player::addStorageValue(const uint32_t key, const int32_t value) value & 0xFF ); return; - } - else { + } else if (IS_IN_KEYRANGE(key, MOUNTS_RANGE)) { + // do nothing + } else { std::cout << "Warning: unknown reserved key: " << key << " player: " << getName() << std::endl; return; } } if (value != -1) { + int32_t oldValue; + getStorageValue(key, oldValue); + storageMap[key] = value; + + if (!isLogin) { + auto currentFrameTime = g_dispatcher.getDispatcherCycle(); + if (lastQuestlogUpdate != currentFrameTime && g_game.quests.isQuestStorage(key, value, oldValue)) { + lastQuestlogUpdate = currentFrameTime; + sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your questlog has been updated."); + } + } } else { storageMap.erase(key); } @@ -534,7 +660,7 @@ bool Player::getStorageValue(const uint32_t key, int32_t& value) const { auto it = storageMap.find(key); if (it == storageMap.end()) { - value = 0; + value = -1; return false; } @@ -563,18 +689,68 @@ bool Player::canSeeCreature(const Creature* creature) const if (!creature->getPlayer() && !canSeeInvisibility() && creature->isInvisible()) { return false; } - return true; } -void Player::onReceiveMail(uint32_t townId) const +bool Player::canWalkthrough(const Creature* creature) const { - if (isNearDepotBox(townId)) { + if (group->access || creature->isInGhostMode()) { + return true; + } + + const Player* player = creature->getPlayer(); + if (!player) { + return false; + } + + const Tile* playerTile = player->getTile(); + if (!playerTile || (!playerTile->hasFlag(TILESTATE_PROTECTIONZONE) && player->getLevel() > static_cast(g_config.getNumber(ConfigManager::PROTECTION_LEVEL)))) { + return false; + } + + const Item* playerTileGround = playerTile->getGround(); + if (!playerTileGround || !playerTileGround->hasWalkStack()) { + return false; + } + + Player* thisPlayer = const_cast(this); + if ((OTSYS_TIME() - lastWalkthroughAttempt) > 2000) { + thisPlayer->setLastWalkthroughAttempt(OTSYS_TIME()); + return false; + } + + if (creature->getPosition() != lastWalkthroughPosition) { + thisPlayer->setLastWalkthroughPosition(creature->getPosition()); + return false; + } + + thisPlayer->setLastWalkthroughPosition(creature->getPosition()); + return true; +} + +bool Player::canWalkthroughEx(const Creature* creature) const +{ + if (group->access) { + return true; + } + + const Player* player = creature->getPlayer(); + if (!player) { + return false; + } + + const Tile* playerTile = player->getTile(); + return playerTile && (playerTile->hasFlag(TILESTATE_PROTECTIONZONE) || player->getLevel() <= static_cast(g_config.getNumber(ConfigManager::PROTECTION_LEVEL))); +} + +void Player::onReceiveMail() const +{ + if (isNearDepotBox()) { sendTextMessage(MESSAGE_EVENT_ADVANCE, "New mail has arrived."); } } -bool Player::isNearDepotBox(uint32_t townId) const +bool Player::isNearDepotBox() const { const Position& pos = getPosition(); for (int32_t cx = -1; cx <= 1; ++cx) { @@ -584,35 +760,47 @@ bool Player::isNearDepotBox(uint32_t townId) const continue; } - if (DepotLocker* depotLocker = tile->getDepotLocker()) { - if (depotLocker->getDepotId() == townId) { - return true; - } + if (tile->hasFlag(TILESTATE_DEPOT)) { + return true; } } } return false; } -DepotLocker* Player::getDepotLocker(uint32_t depotId, bool autoCreate) +DepotChest* Player::getDepotChest(uint32_t depotId, bool autoCreate) { - auto it = depotLockerMap.find(depotId); - if (it != depotLockerMap.end()) { + auto it = depotChests.find(depotId); + if (it != depotChests.end()) { return it->second; } - if (autoCreate) { - DepotLocker* depotLocker = new DepotLocker(ITEM_LOCKER1); - depotLocker->setDepotId(depotId); - Item* depotItem = Item::CreateItem(ITEM_DEPOT); - if (depotItem) { - depotLocker->internalAddThing(depotItem); - } - depotLockerMap[depotId] = depotLocker; - return depotLocker; + if (!autoCreate) { + return nullptr; } - return nullptr; + DepotChest* depotChest = new DepotChest(ITEM_DEPOT); + depotChest->incrementReferenceCounter(); + depotChest->setMaxDepotItems(getMaxDepotItems()); + depotChests[depotId] = depotChest; + return depotChest; +} + +DepotLocker* Player::getDepotLocker(uint32_t depotId) +{ + auto it = depotLockerMap.find(depotId); + if (it != depotLockerMap.end()) { + inbox->setParent(it->second); + return it->second; + } + + DepotLocker* depotLocker = new DepotLocker(ITEM_LOCKER1); + depotLocker->setDepotId(depotId); + depotLocker->internalAddThing(Item::CreateItem(ITEM_MARKET)); + depotLocker->internalAddThing(inbox); + depotLocker->internalAddThing(getDepotChest(depotId, true)); + depotLockerMap[depotId] = depotLocker; + return depotLocker; } void Player::sendCancelMessage(ReturnValue message) const @@ -624,6 +812,7 @@ void Player::sendStats() { if (client) { client->sendStats(); + lastStatsTrainingTime = getOfflineTrainingTime() / 60 / 1000; } } @@ -721,10 +910,20 @@ void Player::sendAddContainerItem(const Container* container, const Item* item) continue; } - if (openContainer.index >= container->capacity()) { + uint16_t slot = openContainer.index; + if (container->getID() == ITEM_BROWSEFIELD) { + uint16_t containerSize = container->size() - 1; + uint16_t pageEnd = openContainer.index + container->capacity() - 1; + if (containerSize > pageEnd) { + slot = pageEnd; + item = container->getItemByIndex(pageEnd); + } else { + slot = containerSize; + } + } else if (openContainer.index >= container->capacity()) { item = container->getItemByIndex(openContainer.index - 1); } - client->sendAddContainerItem(it.first, item); + client->sendAddContainerItem(it.first, slot, item); } } @@ -771,12 +970,12 @@ void Player::sendRemoveContainerItem(const Container* container, uint16_t slot) sendContainer(it.first, container, false, firstIndex); } - client->sendRemoveContainerItem(it.first, std::max(slot, firstIndex)); + client->sendRemoveContainerItem(it.first, std::max(slot, firstIndex), container->getItemByIndex(container->capacity() + firstIndex)); } } void Player::onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, - const ItemType& oldType, const Item* newItem, const ItemType& newType) + const ItemType& oldType, const Item* newItem, const ItemType& newType) { Creature::onUpdateTileItem(tile, pos, oldItem, oldType, newItem, newType); @@ -792,7 +991,7 @@ void Player::onUpdateTileItem(const Tile* tile, const Position& pos, const Item* } void Player::onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, - const Item* item) + const Item* item) { Creature::onRemoveTileItem(tile, pos, iType, item); @@ -813,6 +1012,8 @@ void Player::onCreatureAppear(Creature* creature, bool isLogin) Creature::onCreatureAppear(creature, isLogin); if (isLogin && creature == this) { + sendItems(); + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { Item* item = inventory[slot]; if (item) { @@ -830,6 +1031,9 @@ void Player::onCreatureAppear(Creature* creature, bool isLogin) if (bed) { bed->wakeUp(this); } + + Account account = IOLoginData::loadAccount(accountNumber); + Game::updatePremium(account); std::cout << name << " has logged in." << std::endl; @@ -882,8 +1086,20 @@ void Player::onChangeZone(ZoneType_t zone) setAttackedCreature(nullptr); onAttackedCreatureDisappear(false); } + + if (!group->access && isMounted()) { + dismount(); + g_game.internalCreatureChangeOutfit(this, defaultOutfit); + wasMounted = true; + } + } else { + if (wasMounted) { + toggleMount(true); + wasMounted = false; + } } + g_game.updateCreatureWalkthrough(this); sendIcons(); } @@ -931,6 +1147,8 @@ void Player::onRemoveCreature(Creature* creature, bool isLogout) g_game.internalCloseTrade(this); } + closeShopWindow(); + clearPartyInvitations(); if (party) { @@ -961,6 +1179,36 @@ void Player::onRemoveCreature(Creature* creature, bool isLogout) } } +void Player::openShopWindow(Npc* npc, const std::list& shop) +{ + shopItemList = shop; + sendShop(npc); + sendSaleItemList(); +} + +bool Player::closeShopWindow(bool sendCloseShopWindow /*= true*/) +{ + //unreference callbacks + int32_t onBuy; + int32_t onSell; + + Npc* npc = getShopOwner(onBuy, onSell); + if (!npc) { + shopItemList.clear(); + return false; + } + + setShopOwner(nullptr, -1, -1); + npc->onPlayerEndTrade(this, onBuy, onSell); + + if (sendCloseShopWindow) { + sendCloseShop(); + } + + shopItemList.clear(); + return true; +} + void Player::onWalk(Direction& dir) { Creature::onWalk(dir); @@ -969,7 +1217,7 @@ void Player::onWalk(Direction& dir) } void Player::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, - const Tile* oldTile, const Position& oldPos, bool teleport) + const Tile* oldTile, const Position& oldPos, bool teleport) { Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport); @@ -993,6 +1241,23 @@ void Player::onCreatureMove(Creature* creature, const Tile* newTile, const Posit } } + // close modal windows + if (!modalWindows.empty()) { + // TODO: This shouldn't be hardcoded + for (uint32_t modalWindowId : modalWindows) { + if (modalWindowId == std::numeric_limits::max()) { + sendTextMessage(MESSAGE_EVENT_ADVANCE, "Offline training aborted."); + break; + } + } + modalWindows.clear(); + } + + // leave market + if (inMarket) { + inMarket = false; + } + if (party) { party->updateSharedExperience(); } @@ -1166,11 +1431,10 @@ void Player::onThink(uint32_t interval) addMessageBuffer(); } - lastWalkingTime += interval; if (!getTile()->hasFlag(TILESTATE_NOLOGOUT) && !isAccessPlayer()) { idleTime += interval; const int32_t kickAfterMinutes = g_config.getNumber(ConfigManager::KICK_AFTER_MINUTES); - if ((!pzLocked && OTSYS_TIME() - lastPong >= 60000) || idleTime > (kickAfterMinutes * 60000) + 60000) { + if (idleTime > (kickAfterMinutes * 60000) + 60000) { kickPlayer(true); } else if (client && idleTime == 60000 * kickAfterMinutes) { std::ostringstream ss; @@ -1180,7 +1444,12 @@ void Player::onThink(uint32_t interval) } if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { - checkSkullTicks(); + checkSkullTicks(interval / 1000); + } + + addOfflineTrainingTime(interval); + if (lastStatsTrainingTime != getOfflineTrainingTime() / 60 / 1000) { + sendStats(); } } @@ -1328,7 +1597,24 @@ void Player::addExperience(Creature* source, uint64_t exp, bool sendText/* = fal experience += exp; if (sendText) { - g_game.addAnimatedText(position, TEXTCOLOR_WHITE_EXP, std::to_string(exp)); + std::string expString = std::to_string(exp) + (exp != 1 ? " experience points." : " experience point."); + + TextMessage message(MESSAGE_EXPERIENCE, "You gained " + expString); + message.position = position; + message.primary.value = exp; + message.primary.color = TEXTCOLOR_WHITE_EXP; + sendTextMessage(message); + + SpectatorVec spectators; + g_game.map.getSpectators(spectators, position, false, true); + spectators.erase(this); + if (!spectators.empty()) { + message.type = MESSAGE_EXPERIENCE_OTHERS; + message.text = getName() + " gained " + expString; + for (Creature* spectator : spectators) { + spectator->getPlayer()->sendTextMessage(message); + } + } } uint32_t prevLevel = level; @@ -1349,6 +1635,9 @@ void Player::addExperience(Creature* source, uint64_t exp, bool sendText/* = fal } if (prevLevel != level) { + health = healthMax; + mana = manaMax; + updateBaseSpeed(); setBaseSpeed(getBaseSpeed()); @@ -1374,22 +1663,51 @@ void Player::addExperience(Creature* source, uint64_t exp, bool sendText/* = fal sendStats(); } -void Player::removeExperience(uint64_t exp) +void Player::removeExperience(uint64_t exp, bool sendText/* = false*/) { if (experience == 0 || exp == 0) { return; } + g_events->eventPlayerOnLoseExperience(this, exp); + if (exp == 0) { + return; + } + + uint64_t lostExp = experience; experience = std::max(0, experience - exp); + if (sendText) { + lostExp -= experience; + + std::string expString = std::to_string(lostExp) + (lostExp != 1 ? " experience points." : " experience point."); + + TextMessage message(MESSAGE_EXPERIENCE, "You lost " + expString); + message.position = position; + message.primary.value = lostExp; + message.primary.color = TEXTCOLOR_RED; + sendTextMessage(message); + + SpectatorVec spectators; + g_game.map.getSpectators(spectators, position, false, true); + spectators.erase(this); + if (!spectators.empty()) { + message.type = MESSAGE_EXPERIENCE_OTHERS; + message.text = getName() + " lost " + expString; + for (Creature* spectator : spectators) { + spectator->getPlayer()->sendTextMessage(message); + } + } + } + uint32_t oldLevel = level; uint64_t currLevelExp = Player::getExpForLevel(level); while (level > 1 && experience < currLevelExp) { --level; - healthMax = std::max(150, std::max(0, healthMax - vocation->getHPGain())); + healthMax = std::max(0, healthMax - vocation->getHPGain()); manaMax = std::max(0, manaMax - vocation->getManaGain()); - capacity = std::max(400, std::max(0, capacity - vocation->getCapGain())); + capacity = std::max(0, capacity - vocation->getCapGain()); currLevelExp = Player::getExpForLevel(level); } @@ -1434,11 +1752,6 @@ uint8_t Player::getPercentLevel(uint64_t count, uint64_t nextLevelCount) return result; } -uint16_t Player::getDropLootPercent() -{ - return 10; -} - void Player::onBlockHit() { if (shieldBlockCount > 0) { @@ -1496,7 +1809,7 @@ bool Player::hasShield() const } BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, - bool checkDefense /* = false*/, bool checkArmor /* = false*/, bool field /* = false*/) + bool checkDefense /* = false*/, bool checkArmor /* = false*/, bool field /* = false*/) { BlockType_t blockType = Creature::blockHit(attacker, combatType, damage, checkDefense, checkArmor, field); @@ -1508,55 +1821,58 @@ BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_ return blockType; } - if (damage > 0) { - for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { - if (!isItemAbilityEnabled(static_cast(slot))) { - continue; + if (damage <= 0) { + damage = 0; + return BLOCK_ARMOR; + } + + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { + if (!isItemAbilityEnabled(static_cast(slot))) { + continue; + } + + Item* item = inventory[slot]; + if (!item) { + continue; + } + + const ItemType& it = Item::items[item->getID()]; + if (!it.abilities) { + if (damage <= 0) { + damage = 0; + return BLOCK_ARMOR; } - Item* item = inventory[slot]; - if (!item) { - continue; - } + continue; + } - const ItemType& it = Item::items[item->getID()]; - if (it.abilities) { - const int16_t& absorbPercent = it.abilities->absorbPercent[combatTypeToIndex(combatType)]; - if (absorbPercent != 0) { - damage -= std::round(damage * (absorbPercent / 100.)); + const int16_t& absorbPercent = it.abilities->absorbPercent[combatTypeToIndex(combatType)]; + if (absorbPercent != 0) { + damage -= std::round(damage * (absorbPercent / 100.)); - uint16_t charges = item->getCharges() - 1; - if (charges != 0) { - g_game.transformItem(item, item->getID(), charges); - } else { - g_game.internalRemoveItem(item); - } - } - - if (field) { - const int16_t& fieldAbsorbPercent = it.abilities->fieldAbsorbPercent[combatTypeToIndex(combatType)]; - if (fieldAbsorbPercent != 0) { - damage -= std::round(damage * (fieldAbsorbPercent / 100.)); - - uint16_t charges = item->getCharges(); - if (charges != 0) { - if (charges - 1 == 0) { - g_game.internalRemoveItem(item); - } else { - g_game.transformItem(item, item->getID(), charges - 1); - } - } - } - } + uint16_t charges = item->getCharges(); + if (charges != 0) { + g_game.transformItem(item, item->getID(), charges - 1); } } - if (damage <= 0) { - damage = 0; - blockType = BLOCK_ARMOR; + if (field) { + const int16_t& fieldAbsorbPercent = it.abilities->fieldAbsorbPercent[combatTypeToIndex(combatType)]; + if (fieldAbsorbPercent != 0) { + damage -= std::round(damage * (fieldAbsorbPercent / 100.)); + + uint16_t charges = item->getCharges(); + if (charges != 0) { + g_game.transformItem(item, item->getID(), charges - 1); + } + } } } + if (damage <= 0) { + damage = 0; + blockType = BLOCK_ARMOR; + } return blockType; } @@ -1569,31 +1885,33 @@ uint32_t Player::getIP() const return 0; } -void Player::dropLoot(Container* corpse, Creature*) -{ - if (corpse && lootDrop) { - Skulls_t playerSkull = getSkull(); - if (inventory[CONST_SLOT_NECKLACE] && inventory[CONST_SLOT_NECKLACE]->getID() == ITEM_AMULETOFLOSS && playerSkull != SKULL_RED) { - g_game.internalRemoveItem(inventory[CONST_SLOT_NECKLACE], 1); - } else { - for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { - Item* item = inventory[i]; - if (item) { - if (playerSkull == SKULL_RED || item->getContainer() || uniform_random(1, 100) <= getDropLootPercent()) { - g_game.internalMoveItem(this, corpse, INDEX_WHEREEVER, item, item->getItemCount(), 0); - sendInventoryItem(static_cast(i), nullptr); - } - } - } - } - } -} - void Player::death(Creature* lastHitCreature) { loginPosition = town->getTemplePosition(); if (skillLoss) { + uint8_t unfairFightReduction = 100; + bool lastHitPlayer = Player::lastHitIsPlayer(lastHitCreature); + + if (lastHitPlayer) { + uint32_t sumLevels = 0; + uint32_t inFightTicks = g_config.getNumber(ConfigManager::PZ_LOCKED); + for (const auto& it : damageMap) { + CountBlock_t cb = it.second; + if ((OTSYS_TIME() - cb.ticks) <= inFightTicks) { + Player* damageDealer = g_game.getPlayerByID(it.first); + if (damageDealer) { + sumLevels += damageDealer->getLevel(); + } + } + } + + if (sumLevels > level) { + double reduce = level / static_cast(sumLevels); + unfairFightReduction = std::max(20, std::floor((reduce * 100) + 0.5)); + } + } + //Magic level loss uint64_t sumMana = 0; uint64_t lostMana = 0; @@ -1605,7 +1923,7 @@ void Player::death(Creature* lastHitCreature) sumMana += manaSpent; - double deathLossPercent = getLostPercent(); + double deathLossPercent = getLostPercent() * (unfairFightReduction / 100.); lostMana = static_cast(sumMana * deathLossPercent); @@ -1659,7 +1977,9 @@ void Player::death(Creature* lastHitCreature) if (expLoss != 0) { uint32_t oldLevel = level; - experience -= expLoss; + if (vocation->getId() == VOCATION_NONE || level > 7) { + experience -= expLoss; + } while (level > 1 && experience < Player::getExpForLevel(level)) { --level; @@ -1685,7 +2005,7 @@ void Player::death(Creature* lastHitCreature) std::bitset<6> bitset(blessings); if (bitset[5]) { - if (Player::lastHitIsPlayer(lastHitCreature)) { + if (lastHitPlayer) { bitset.reset(5); blessings = bitset.to_ulong(); } else { @@ -1697,9 +2017,15 @@ void Player::death(Creature* lastHitCreature) sendStats(); sendSkills(); + sendReLoginWindow(unfairFightReduction); - health = healthMax; - mana = manaMax; + if (getSkull() == SKULL_BLACK) { + health = 40; + mana = 0; + } else { + health = healthMax; + mana = manaMax; + } auto it = conditions.begin(), end = conditions.end(); while (it != end) { @@ -1714,61 +2040,8 @@ void Player::death(Creature* lastHitCreature) ++it; } } - - // Teleport newbies to newbie island - if (g_config.getBoolean(ConfigManager::TELEPORT_NEWBIES)) { - if (getVocationId() != VOCATION_NONE && level <= static_cast(g_config.getNumber(ConfigManager::NEWBIE_LEVEL_THRESHOLD))) { - Town* newbieTown = g_game.map.towns.getTown(g_config.getNumber(ConfigManager::NEWBIE_TOWN)); - if (newbieTown) { - // Restart stats - level = 1; - experience = 0; - levelPercent = 0; - capacity = 400; - health = 150; - healthMax = 150; - mana = 0; - manaMax = 0; - magLevel = 0; - magLevelPercent = 0; - manaSpent = 0; - staminaMinutes = 3360; - setVocation(0); - - // Restart skills - for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { //for each skill - skills[i].level = 10; - skills[i].tries = 0; - skills[i].percent = 0; - } - - // Restart town - setTown(newbieTown); - loginPosition = getTemplePosition(); - - // Restart first items - lastLoginSaved = 0; - lastLogout = 0; - - // Restart storages - storageMap.clear(); - outfits.clear(); - - // Restart items - for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; slot++) - { - Item* item = inventory[slot]; - if (item) { - g_game.internalRemoveItem(item, item->getItemCount()); - } - } - } else { - std::cout << "[Warning - Player:death] Newbie teletransportation is enabled, newbie town does not exist." << std::endl; - } - } - } } else { - setLossSkill(true); + setSkillLoss(true); auto it = conditions.begin(), end = conditions.end(); while (it != end) { @@ -1779,8 +2052,7 @@ void Player::death(Creature* lastHitCreature) condition->endCondition(this); onEndCondition(condition->getType()); delete condition; - } - else { + } else { ++it; } } @@ -1894,12 +2166,7 @@ bool Player::removeVIP(uint32_t vipGuid) bool Player::addVIP(uint32_t vipGuid, const std::string& vipName, VipStatus_t status) { - if (guid == vipGuid) { - sendTextMessage(MESSAGE_STATUS_SMALL, "You cannot add yourself."); - return false; - } - - if (VIPList.size() >= getMaxVIPEntries() || VIPList.size() == 100) { + if (VIPList.size() >= getMaxVIPEntries() || VIPList.size() == 200) { // max number of buddies is 200 in 9.53 sendTextMessage(MESSAGE_STATUS_SMALL, "You cannot add more buddies."); return false; } @@ -1910,26 +2177,33 @@ bool Player::addVIP(uint32_t vipGuid, const std::string& vipName, VipStatus_t st return false; } - IOLoginData::addVIPEntry(accountNumber, vipGuid); + IOLoginData::addVIPEntry(accountNumber, vipGuid, "", 0, false); if (client) { - client->sendVIP(vipGuid, vipName, status); + client->sendVIP(vipGuid, vipName, "", 0, false, status); } return true; } bool Player::addVIPInternal(uint32_t vipGuid) { - if (guid == vipGuid) { - return false; - } - - if (VIPList.size() >= getMaxVIPEntries() || VIPList.size() == 100) { + if (VIPList.size() >= getMaxVIPEntries() || VIPList.size() == 200) { // max number of buddies is 200 in 9.53 return false; } return VIPList.insert(vipGuid).second; } +bool Player::editVIP(uint32_t vipGuid, const std::string& description, uint32_t icon, bool notify) +{ + auto it = VIPList.find(vipGuid); + if (it == VIPList.end()) { + return false; // player is not in VIP + } + + IOLoginData::editVIPEntry(accountNumber, vipGuid, description, icon, notify); + return true; +} + //close container and its child containers void Player::autoCloseContainers(const Container* container) { @@ -1996,14 +2270,18 @@ ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, const int32_t& slotPosition = item->getSlotPosition(); if ((slotPosition & SLOTP_HEAD) || (slotPosition & SLOTP_NECKLACE) || - (slotPosition & SLOTP_BACKPACK) || (slotPosition & SLOTP_ARMOR) || - (slotPosition & SLOTP_LEGS) || (slotPosition & SLOTP_FEET) || - (slotPosition & SLOTP_RING)) { + (slotPosition & SLOTP_BACKPACK) || (slotPosition & SLOTP_ARMOR) || + (slotPosition & SLOTP_LEGS) || (slotPosition & SLOTP_FEET) || + (slotPosition & SLOTP_RING)) { ret = RETURNVALUE_CANNOTBEDRESSED; } else if (slotPosition & SLOTP_TWO_HAND) { ret = RETURNVALUE_PUTTHISOBJECTINBOTHHANDS; } else if ((slotPosition & SLOTP_RIGHT) || (slotPosition & SLOTP_LEFT)) { - ret = RETURNVALUE_PUTTHISOBJECTINYOURHAND; + if (!g_config.getBoolean(ConfigManager::CLASSIC_EQUIPMENT_SLOTS)) { + ret = RETURNVALUE_CANNOTBEDRESSED; + } else { + ret = RETURNVALUE_PUTTHISOBJECTINYOURHAND; + } } switch (index) { @@ -2037,7 +2315,22 @@ ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, case CONST_SLOT_RIGHT: { if (slotPosition & SLOTP_RIGHT) { - if (slotPosition & SLOTP_TWO_HAND) { + if (!g_config.getBoolean(ConfigManager::CLASSIC_EQUIPMENT_SLOTS)) { + if (item->getWeaponType() != WEAPON_SHIELD) { + ret = RETURNVALUE_CANNOTBEDRESSED; + } else { + const Item* leftItem = inventory[CONST_SLOT_LEFT]; + if (leftItem) { + if ((leftItem->getSlotPosition() | slotPosition) & SLOTP_TWO_HAND) { + ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE; + } else { + ret = RETURNVALUE_NOERROR; + } + } else { + ret = RETURNVALUE_NOERROR; + } + } + } else if (slotPosition & SLOTP_TWO_HAND) { if (inventory[CONST_SLOT_LEFT] && inventory[CONST_SLOT_LEFT] != item) { ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE; } else { @@ -2054,8 +2347,8 @@ ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, } else if (leftType == WEAPON_SHIELD && type == WEAPON_SHIELD) { ret = RETURNVALUE_CANONLYUSEONESHIELD; } else if (leftType == WEAPON_NONE || type == WEAPON_NONE || - leftType == WEAPON_SHIELD || leftType == WEAPON_AMMO - || type == WEAPON_SHIELD || type == WEAPON_AMMO) { + leftType == WEAPON_SHIELD || leftType == WEAPON_AMMO + || type == WEAPON_SHIELD || type == WEAPON_AMMO) { ret = RETURNVALUE_NOERROR; } else { ret = RETURNVALUE_CANONLYUSEONEWEAPON; @@ -2069,7 +2362,16 @@ ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, case CONST_SLOT_LEFT: { if (slotPosition & SLOTP_LEFT) { - if (slotPosition & SLOTP_TWO_HAND) { + if (!g_config.getBoolean(ConfigManager::CLASSIC_EQUIPMENT_SLOTS)) { + WeaponType_t type = item->getWeaponType(); + if (type == WEAPON_NONE || type == WEAPON_SHIELD) { + ret = RETURNVALUE_CANNOTBEDRESSED; + } else if (inventory[CONST_SLOT_RIGHT] && (slotPosition & SLOTP_TWO_HAND)) { + ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE; + } else { + ret = RETURNVALUE_NOERROR; + } + } else if (slotPosition & SLOTP_TWO_HAND) { if (inventory[CONST_SLOT_RIGHT] && inventory[CONST_SLOT_RIGHT] != item) { ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE; } else { @@ -2086,8 +2388,8 @@ ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, } else if (rightType == WEAPON_SHIELD && type == WEAPON_SHIELD) { ret = RETURNVALUE_CANONLYUSEONESHIELD; } else if (rightType == WEAPON_NONE || type == WEAPON_NONE || - rightType == WEAPON_SHIELD || rightType == WEAPON_AMMO - || type == WEAPON_SHIELD || type == WEAPON_AMMO) { + rightType == WEAPON_SHIELD || rightType == WEAPON_AMMO + || type == WEAPON_SHIELD || type == WEAPON_AMMO) { ret = RETURNVALUE_NOERROR; } else { ret = RETURNVALUE_CANONLYUSEONEWEAPON; @@ -2121,41 +2423,46 @@ ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, } case CONST_SLOT_AMMO: { - ret = RETURNVALUE_NOERROR; + if ((slotPosition & SLOTP_AMMO) || g_config.getBoolean(ConfigManager::CLASSIC_EQUIPMENT_SLOTS)) { + ret = RETURNVALUE_NOERROR; + } break; } case CONST_SLOT_WHEREEVER: case -1: - ret = RETURNVALUE_NOTENOUGHROOM; - break; + ret = RETURNVALUE_NOTENOUGHROOM; + break; default: - ret = RETURNVALUE_NOTPOSSIBLE; - break; + ret = RETURNVALUE_NOTPOSSIBLE; + break; } - if (ret == RETURNVALUE_NOERROR || ret == RETURNVALUE_NOTENOUGHROOM) { - //need an exchange with source? - const Item* inventoryItem = getInventoryItem(static_cast(index)); - if (inventoryItem && (!inventoryItem->isStackable() || inventoryItem->isRune() || inventoryItem->getID() != item->getID())) { - return RETURNVALUE_NEEDEXCHANGE; - } - //check if enough capacity - if (!hasCapacity(item, count)) { - return RETURNVALUE_NOTENOUGHCAPACITY; - } + if (ret != RETURNVALUE_NOERROR && ret != RETURNVALUE_NOTENOUGHROOM) { + return ret; + } - if (!g_moveEvents->onPlayerEquip(const_cast(this), const_cast(item), static_cast(index), true)) { - return RETURNVALUE_CANNOTBEDRESSED; - } + //need an exchange with source? + const Item* inventoryItem = getInventoryItem(static_cast(index)); + if (inventoryItem && (!inventoryItem->isStackable() || inventoryItem->getID() != item->getID())) { + return RETURNVALUE_NEEDEXCHANGE; + } + + //check if enough capacity + if (!hasCapacity(item, count)) { + return RETURNVALUE_NOTENOUGHCAPACITY; + } + + if (!g_moveEvents->onPlayerEquip(const_cast(this), const_cast(item), static_cast(index), true)) { + return RETURNVALUE_CANNOTBEDRESSED; } return ret; } ReturnValue Player::queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, - uint32_t flags) const + uint32_t flags) const { const Item* item = thing.getItem(); if (item == nullptr) { @@ -2207,18 +2514,15 @@ ReturnValue Player::queryMaxCount(int32_t index, const Thing& thing, uint32_t co } if (destItem) { - if (!destItem->isRune() && destItem->isStackable() && item->equals(destItem) && destItem->getItemCount() < 100) { + if (destItem->isStackable() && item->equals(destItem) && destItem->getItemCount() < 100) { maxQueryCount = 100 - destItem->getItemCount(); - } - else { + } else { maxQueryCount = 0; } - } - else if (queryAdd(index, *item, count, flags) == RETURNVALUE_NOERROR) { //empty slot + } else if (queryAdd(index, *item, count, flags) == RETURNVALUE_NOERROR) { //empty slot if (item->isStackable()) { maxQueryCount = 100; - } - else { + } else { maxQueryCount = 1; } @@ -2257,7 +2561,7 @@ ReturnValue Player::queryRemove(const Thing& thing, uint32_t count, uint32_t fla } Cylinder* Player::queryDestination(int32_t& index, const Thing& thing, Item** destItem, - uint32_t& flags) + uint32_t& flags) { if (index == 0 /*drop to capacity window*/ || index == INDEX_WHEREEVER) { *destItem = nullptr; @@ -2322,11 +2626,9 @@ Cylinder* Player::queryDestination(int32_t& index, const Thing& thing, Item** de n--; } - if (!g_config.getBoolean(ConfigManager::DROP_ITEMS)) { - for (Item* tmpContainerItem : tmpContainer->getItemList()) { - if (Container* subContainer = tmpContainerItem->getContainer()) { - containers.push_back(subContainer); - } + for (Item* tmpContainerItem : tmpContainer->getItemList()) { + if (Container* subContainer = tmpContainerItem->getContainer()) { + containers.push_back(subContainer); } } @@ -2351,10 +2653,8 @@ Cylinder* Player::queryDestination(int32_t& index, const Thing& thing, Item** de return tmpContainer; } - if (!g_config.getBoolean(ConfigManager::DROP_ITEMS)) { - if (Container* subContainer = tmpItem->getContainer()) { - containers.push_back(subContainer); - } + if (Container* subContainer = tmpItem->getContainer()) { + containers.push_back(subContainer); } n++; @@ -2568,23 +2868,7 @@ bool Player::removeItemOfType(uint16_t itemId, uint32_t amount, int32_t subType, g_game.internalRemoveItems(std::move(itemList), amount, Item::items[itemId].stackable); return true; } - } - else if (Container* container = item->getContainer()) { - if (container->getID() == itemId) { - uint32_t itemCount = Item::countByType(item, subType); - if (itemCount == 0) { - continue; - } - - itemList.push_back(item); - - count += itemCount; - if (count >= amount) { - g_game.internalRemoveItems(std::move(itemList), amount, Item::items[itemId].stackable); - return true; - } - } - + } else if (Container* container = item->getContainer()) { for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { Item* containerItem = *it; if (containerItem->getID() == itemId) { @@ -2634,14 +2918,28 @@ Thing* Player::getThing(size_t index) const return nullptr; } -void Player::postAddNotification(Thing* thing, const Cylinder*, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +void Player::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) { if (link == LINK_OWNER) { //calling movement scripts g_moveEvents->onPlayerEquip(this, thing->getItem(), static_cast(index), false); } + bool requireListUpdate = false; + if (link == LINK_OWNER || link == LINK_TOPPARENT) { + const Item* i = (oldParent ? oldParent->getItem() : nullptr); + + // Check if we owned the old container too, so we don't need to do anything, + // as the list was updated in postRemoveNotification + assert(i ? i->getContainer() != nullptr : true); + + if (i) { + requireListUpdate = i->getContainer()->getHoldingPlayer() != this; + } else { + requireListUpdate = oldParent != this; + } + updateInventoryWeight(); updateItemsLight(); sendStats(); @@ -2651,6 +2949,10 @@ void Player::postAddNotification(Thing* thing, const Cylinder*, int32_t index, c if (const Container* container = item->getContainer()) { onSendContainer(container); } + + if (shopOwner && requireListUpdate) { + updateSaleShopList(item); + } } else if (const Creature* creature = thing->getCreature()) { if (creature == this) { //check containers @@ -2670,14 +2972,28 @@ void Player::postAddNotification(Thing* thing, const Cylinder*, int32_t index, c } } -void Player::postRemoveNotification(Thing* thing, const Cylinder*, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +void Player::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) { if (link == LINK_OWNER) { //calling movement scripts g_moveEvents->onPlayerDeEquip(this, thing->getItem(), static_cast(index)); } + bool requireListUpdate = false; + if (link == LINK_OWNER || link == LINK_TOPPARENT) { + const Item* i = (newParent ? newParent->getItem() : nullptr); + + // Check if we owned the old container too, so we don't need to do anything, + // as the list was updated in postRemoveNotification + assert(i ? i->getContainer() != nullptr : true); + + if (i) { + requireListUpdate = i->getContainer()->getHoldingPlayer() != this; + } else { + requireListUpdate = newParent != this; + } + updateInventoryWeight(); updateItemsLight(); sendStats(); @@ -2690,11 +3006,11 @@ void Player::postRemoveNotification(Thing* thing, const Cylinder*, int32_t index } else if (container->getTopParent() == this) { onSendContainer(container); } else if (const Container* topContainer = dynamic_cast(container->getTopParent())) { - if (const DepotLocker* depotLocker = dynamic_cast(topContainer)) { + if (const DepotChest* depotChest = dynamic_cast(topContainer)) { bool isOwner = false; - for (const auto& it : depotLockerMap) { - if (it.second == depotLocker) { + for (const auto& it : depotChests) { + if (it.second == depotChest) { isOwner = true; onSendContainer(container); } @@ -2710,9 +3026,45 @@ void Player::postRemoveNotification(Thing* thing, const Cylinder*, int32_t index autoCloseContainers(container); } } + + if (shopOwner && requireListUpdate) { + updateSaleShopList(item); + } } } +bool Player::updateSaleShopList(const Item* item) +{ + uint16_t itemId = item->getID(); + if (itemId != ITEM_GOLD_COIN && itemId != ITEM_PLATINUM_COIN && itemId != ITEM_CRYSTAL_COIN) { + auto it = std::find_if(shopItemList.begin(), shopItemList.end(), [itemId](const ShopInfo& shopInfo) { return shopInfo.itemId == itemId && shopInfo.sellPrice != 0; }); + if (it == shopItemList.end()) { + const Container* container = item->getContainer(); + if (!container) { + return false; + } + + const auto& items = container->getItemList(); + return std::any_of(items.begin(), items.end(), [this](const Item* containerItem) { + return updateSaleShopList(containerItem); + }); + } + } + + if (client) { + client->sendSaleItemList(shopItemList); + } + return true; +} + +bool Player::hasShopItemForSale(uint32_t itemId, uint8_t subType) const +{ + const ItemType& itemType = Item::items[itemId]; + return std::any_of(shopItemList.begin(), shopItemList.end(), [&](const ShopInfo& shopInfo) { + return shopInfo.itemId == itemId && shopInfo.buyPrice != 0 && (!itemType.isFluidContainer() || shopInfo.subType == subType); + }); +} + void Player::internalAddThing(Thing* thing) { internalAddThing(0, thing); @@ -2736,51 +3088,6 @@ void Player::internalAddThing(uint32_t index, Thing* thing) } } -uint32_t Player::checkPlayerKilling() -{ - time_t today = std::time(nullptr); - int32_t lastDay = 0; - int32_t lastWeek = 0; - int32_t lastMonth = 0; - int64_t egibleMurders = 0; - - time_t dayTimestamp = today - (24 * 60 * 60); - time_t weekTimestamp = today - (7 * 24 * 60 * 60); - time_t monthTimestamp = today - (30 * 24 * 60 * 60); - - for (time_t currentMurderTimestamp : murderTimeStamps) { - if (currentMurderTimestamp > dayTimestamp) { - lastDay++; - } - - if (currentMurderTimestamp > weekTimestamp) { - lastWeek++; - } - - egibleMurders = lastMonth + 1; - - if (currentMurderTimestamp <= monthTimestamp) { - egibleMurders = lastMonth; - } - - lastMonth = egibleMurders; - } - - if (lastDay >= g_config.getNumber(ConfigManager::KILLS_DAY_BANISHMENT) || - lastWeek >= g_config.getNumber(ConfigManager::KILLS_WEEK_BANISHMENT) || - lastMonth >= g_config.getNumber(ConfigManager::KILLS_MONTH_BANISHMENT)) { - return 2; // banishment! - } - - if (lastDay >= g_config.getNumber(ConfigManager::KILLS_DAY_RED_SKULL) || - lastWeek >= g_config.getNumber(ConfigManager::KILLS_WEEK_RED_SKULL) || - lastMonth >= g_config.getNumber(ConfigManager::KILLS_MONTH_RED_SKULL)) { - return 1; // red skull! - } - - return 0; -} - bool Player::setFollowCreature(Creature* creature) { if (!Creature::setFollowCreature(creature)) { @@ -2802,7 +3109,7 @@ bool Player::setAttackedCreature(Creature* creature) return false; } - if (chaseMode == CHASEMODE_FOLLOW && creature) { + if (chaseMode && creature) { if (followCreature != creature) { //chase opponent setFollowCreature(creature); @@ -2849,8 +3156,33 @@ void Player::doAttacking(uint32_t) } if ((OTSYS_TIME() - lastAttack) >= getAttackSpeed()) { - if (Combat::attack(this, attackedCreature)) { - earliestAttackTime = OTSYS_TIME() + 2000; + bool result = false; + + Item* tool = getWeapon(); + const Weapon* weapon = g_weapons->getWeapon(tool); + uint32_t delay = getAttackSpeed(); + bool classicSpeed = g_config.getBoolean(ConfigManager::CLASSIC_ATTACK_SPEED); + + if (weapon) { + if (!weapon->interruptSwing()) { + result = weapon->useWeapon(this, tool, attackedCreature); + } else if (!classicSpeed && !canDoAction()) { + delay = getNextActionTime(); + } else { + result = weapon->useWeapon(this, tool, attackedCreature); + } + } else { + result = Weapon::useFist(this, attackedCreature); + } + + SchedulerTask* task = createSchedulerTask(std::max(SCHEDULER_MINTICKS, delay), std::bind(&Game::checkCreatureAttack, &g_game, getID())); + if (!classicSpeed) { + setNextActionTask(task); + } else { + g_scheduler.addEvent(task); + } + + if (result) { lastAttack = OTSYS_TIME(); } } @@ -2874,13 +3206,13 @@ void Player::onFollowCreature(const Creature* creature) } } -void Player::setChaseMode(chaseMode_t mode) +void Player::setChaseMode(bool mode) { - chaseMode_t prevChaseMode = chaseMode; + bool prevChaseMode = chaseMode; chaseMode = mode; if (prevChaseMode != chaseMode) { - if (chaseMode == CHASEMODE_FOLLOW) { + if (chaseMode) { if (!followCreature && attackedCreature) { //chase opponent setFollowCreature(attackedCreature); @@ -2911,27 +3243,25 @@ void Player::stopWalk() cancelNextWalk = true; } -void Player::getCreatureLight(LightInfo& light) const +LightInfo Player::getCreatureLight() const { if (internalLight.level > itemsLight.level) { - light = internalLight; - } else { - light = itemsLight; + return internalLight; } + return itemsLight; } void Player::updateItemsLight(bool internal /*=false*/) { LightInfo maxLight; - LightInfo curLight; for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { Item* item = inventory[i]; if (item) { - item->getLight(curLight); + LightInfo curLight = item->getLightInfo(); if (curLight.level > maxLight.level) { - maxLight = curLight; + maxLight = std::move(curLight); } } } @@ -2948,6 +3278,11 @@ void Player::updateItemsLight(bool internal /*=false*/) void Player::onAddCondition(ConditionType_t type) { Creature::onAddCondition(type); + + if (type == CONDITION_OUTFIT && isMounted()) { + dismount(); + } + sendIcons(); } @@ -2955,23 +3290,39 @@ void Player::onAddCombatCondition(ConditionType_t type) { switch (type) { case CONDITION_POISON: - sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are poisoned."); - break; + sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are poisoned."); + break; case CONDITION_DROWN: - sendTextMessage(MESSAGE_STATUS_SMALL, "You are drowning."); - break; + sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are drowning."); + break; case CONDITION_PARALYZE: - sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are paralyzed."); - break; + sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are paralyzed."); + break; case CONDITION_DRUNK: - sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are drunk."); - break; + sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are drunk."); + break; + + case CONDITION_CURSED: + sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are cursed."); + break; + + case CONDITION_FREEZING: + sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are freezing."); + break; + + case CONDITION_DAZZLED: + sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are dazzled."); + break; + + case CONDITION_BLEEDING: + sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are bleeding."); + break; default: - break; + break; } } @@ -2984,7 +3335,7 @@ void Player::onEndCondition(ConditionType_t type) pzLocked = false; clearAttacked(); - if (getSkull() != SKULL_RED) { + if (getSkull() != SKULL_RED && getSkull() != SKULL_BLACK) { setSkull(SKULL_NONE); } } @@ -3039,28 +3390,30 @@ void Player::onAttackedCreature(Creature* target) } Player* targetPlayer = target->getPlayer(); - if (targetPlayer) { - if (!pzLocked && g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { + if (targetPlayer && !isPartner(targetPlayer) && !isGuildMate(targetPlayer)) { + if (!pzLocked && g_game.getWorldType() == WORLD_TYPE_PVP_ENFORCED) { pzLocked = true; sendIcons(); } - if (!isPartner(targetPlayer)) { - if (getSkull() == SKULL_NONE && getSkullClient(targetPlayer) == SKULL_YELLOW) { + if (getSkull() == SKULL_NONE && getSkullClient(targetPlayer) == SKULL_YELLOW) { + addAttacked(targetPlayer); + targetPlayer->sendCreatureSkull(this); + } else if (!targetPlayer->hasAttacked(this)) { + if (!pzLocked) { + pzLocked = true; + sendIcons(); + } + + if (!Combat::isInPvpZone(this, targetPlayer) && !isInWar(targetPlayer)) { addAttacked(targetPlayer); - targetPlayer->sendCreatureSkull(this); - } else { - if (!targetPlayer->hasAttacked(this)) { - if (!Combat::isInPvpZone(this, targetPlayer) && !isInWar(targetPlayer)) { - addAttacked(targetPlayer); - if (targetPlayer->getSkull() == SKULL_NONE && getSkull() == SKULL_NONE) { - setSkull(SKULL_WHITE); - } - } - - if (getSkull() == SKULL_NONE) { - targetPlayer->sendCreatureSkull(this); - } + + if (targetPlayer->getSkull() == SKULL_NONE && getSkull() == SKULL_NONE) { + setSkull(SKULL_WHITE); + } + + if (getSkull() == SKULL_NONE) { + targetPlayer->sendCreatureSkull(this); } } } @@ -3137,23 +3490,26 @@ bool Player::onKilledCreature(Creature* target, bool lastHit/* = true*/) Creature::onKilledCreature(target, lastHit); - if (Player* targetPlayer = target->getPlayer()) { - if (targetPlayer && targetPlayer->getZone() == ZONE_PVP) { - targetPlayer->setDropLoot(false); - targetPlayer->setLossSkill(false); - } else if (!hasFlag(PlayerFlag_NotGainInFight) && !isPartner(targetPlayer)) { - if (!Combat::isInPvpZone(this, targetPlayer) && hasAttacked(targetPlayer) && !targetPlayer->hasAttacked(this) && targetPlayer != this) { - if (targetPlayer->getSkull() == SKULL_NONE && !isInWar(targetPlayer)) { - unjustified = true; - addUnjustifiedDead(targetPlayer); - } - } - } + Player* targetPlayer = target->getPlayer(); + if (!targetPlayer) { + return false; + } - if (lastHit && hasCondition(CONDITION_INFIGHT)) { - pzLocked = true; - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_INFIGHT, g_config.getNumber(ConfigManager::WHITE_SKULL_TIME) * 1000, 0); - addCondition(condition); + if (targetPlayer->getZone() == ZONE_PVP) { + targetPlayer->setDropLoot(false); + targetPlayer->setSkillLoss(false); + } else if (!hasFlag(PlayerFlag_NotGainInFight) && !isPartner(targetPlayer)) { + if (!Combat::isInPvpZone(this, targetPlayer) && hasAttacked(targetPlayer) && !targetPlayer->hasAttacked(this) && !isGuildMate(targetPlayer) && targetPlayer != this) { + if (targetPlayer->getSkull() == SKULL_NONE && !isInWar(targetPlayer)) { + unjustified = true; + addUnjustifiedDead(targetPlayer); + } + + if (lastHit && hasCondition(CONDITION_INFIGHT)) { + pzLocked = true; + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_INFIGHT, g_config.getNumber(ConfigManager::WHITE_SKULL_TIME), 0); + addCondition(condition); + } } } @@ -3236,8 +3592,7 @@ void Player::changeMana(int32_t manaChange) if (!hasFlag(PlayerFlag_HasInfiniteMana)) { if (manaChange > 0) { mana += std::min(manaChange, getMaxMana() - mana); - } - else { + } else { mana = std::max(0, mana + manaChange); } } @@ -3392,22 +3747,24 @@ Skulls_t Player::getSkullClient(const Creature* creature) const } const Player* player = creature->getPlayer(); - if (player && player->getSkull() == SKULL_NONE) { - if (isInWar(player)) { - return SKULL_GREEN; - } + if (!player || player->getSkull() != SKULL_NONE) { + return Creature::getSkullClient(creature); + } - if (!player->getGuildWarList().empty() && guild == player->getGuild()) { - return SKULL_GREEN; - } + if (isInWar(player)) { + return SKULL_GREEN; + } - if (player->hasAttacked(this)) { - return SKULL_YELLOW; - } + if (!player->getGuildWarVector().empty() && guild == player->getGuild()) { + return SKULL_GREEN; + } - if (isPartner(player)) { - return SKULL_GREEN; - } + if (player->hasAttacked(this)) { + return SKULL_YELLOW; + } + + if (isPartner(player)) { + return SKULL_GREEN; } return Creature::getSkullClient(creature); } @@ -3418,7 +3775,7 @@ bool Player::hasAttacked(const Player* attacked) const return false; } - return attackedSet.find(attacked->id) != attackedSet.end(); + return attackedSet.find(attacked->guid) != attackedSet.end(); } void Player::addAttacked(const Player* attacked) @@ -3427,7 +3784,7 @@ void Player::addAttacked(const Player* attacked) return; } - attackedSet.insert(attacked->id); + attackedSet.insert(attacked->guid); } void Player::removeAttacked(const Player* attacked) @@ -3453,48 +3810,29 @@ void Player::addUnjustifiedDead(const Player* attacked) return; } - // current unjustified kill! - murderTimeStamps.push_back(std::time(nullptr)); + sendTextMessage(MESSAGE_EVENT_ADVANCE, "Warning! The murder of " + attacked->getName() + " was not justified."); - sendTextMessage(MESSAGE_STATUS_WARNING, "Warning! The murder of " + attacked->getName() + " was not justified."); + skullTicks += g_config.getNumber(ConfigManager::FRAG_TIME); - if (playerKillerEnd == 0) { - // white skull time, it only sets on first kill! - playerKillerEnd = std::time(nullptr) + g_config.getNumber(ConfigManager::WHITE_SKULL_TIME); - } - - uint32_t murderResult = checkPlayerKilling(); - if (murderResult >= 1) { - // red skull player - playerKillerEnd = std::time(nullptr) + g_config.getNumber(ConfigManager::RED_SKULL_TIME); - setSkull(SKULL_RED); - - if (murderResult == 2) { - // banishment for too many unjustified kills - Database* db = Database::getInstance(); - - std::ostringstream ss; - ss << "INSERT INTO `account_bans` (`account_id`, `reason`, `banned_at`, `expires_at`, `banned_by`) VALUES ("; - ss << getAccount() << ", "; - ss << db->escapeString("Too many unjustified kills") << ", "; - ss << std::time(nullptr) << ", "; - ss << std::time(nullptr) + g_config.getNumber(ConfigManager::BAN_LENGTH) << ", "; - ss << "1);"; - - db->executeQuery(ss.str()); - - g_game.addMagicEffect(getPosition(), CONST_ME_GREEN_RINGS); - g_game.removeCreature(this); - disconnect(); + if (getSkull() != SKULL_BLACK) { + if (g_config.getNumber(ConfigManager::KILLS_TO_BLACK) != 0 && skullTicks > (g_config.getNumber(ConfigManager::KILLS_TO_BLACK) - 1) * static_cast(g_config.getNumber(ConfigManager::FRAG_TIME))) { + setSkull(SKULL_BLACK); + } else if (getSkull() != SKULL_RED && g_config.getNumber(ConfigManager::KILLS_TO_RED) != 0 && skullTicks > (g_config.getNumber(ConfigManager::KILLS_TO_RED) - 1) * static_cast(g_config.getNumber(ConfigManager::FRAG_TIME))) { + setSkull(SKULL_RED); } } } -void Player::checkSkullTicks() +void Player::checkSkullTicks(int64_t ticks) { - time_t today = std::time(nullptr); + int64_t newTicks = skullTicks - ticks; + if (newTicks < 0) { + skullTicks = 0; + } else { + skullTicks = newTicks; + } - if (!hasCondition(CONDITION_INFIGHT) && ((skull == SKULL_RED && today >= playerKillerEnd) || (skull == SKULL_WHITE))) { + if ((skull == SKULL_RED || skull == SKULL_BLACK) && skullTicks < 1 && !hasCondition(CONDITION_INFIGHT)) { setSkull(SKULL_NONE); } } @@ -3527,11 +3865,12 @@ double Player::getLostPercent() const lossPercent = 10; } + double percentReduction = 0; if (isPromoted()) { - lossPercent *= 0.7; + percentReduction += 30; } - - return lossPercent * pow(0.92, blessingCount) / 100; + percentReduction += blessingCount * 8; + return lossPercent * (1 - (percentReduction / 100.)) / 100.; } void Player::learnInstantSpell(const std::string& spellName) @@ -3580,7 +3919,7 @@ bool Player::isInWar(const Player* player) const bool Player::isInWarList(uint32_t guildId) const { - return std::find(guildWarList.begin(), guildWarList.end(), guildId) != guildWarList.end(); + return std::find(guildWarVector.begin(), guildWarVector.end(), guildId) != guildWarVector.end(); } bool Player::isPremium() const @@ -3595,6 +3934,7 @@ bool Player::isPremium() const void Player::setPremiumDays(int32_t v) { premiumDays = v; + sendBasicData(); } PartyShields_t Player::getPartyShield(const Player* player) const @@ -3605,10 +3945,34 @@ PartyShields_t Player::getPartyShield(const Player* player) const if (party) { if (party->getLeader() == player) { + if (party->isSharedExperienceActive()) { + if (party->isSharedExperienceEnabled()) { + return SHIELD_YELLOW_SHAREDEXP; + } + + if (party->canUseSharedExperience(player)) { + return SHIELD_YELLOW_NOSHAREDEXP; + } + + return SHIELD_YELLOW_NOSHAREDEXP_BLINK; + } + return SHIELD_YELLOW; } if (player->party == party) { + if (party->isSharedExperienceActive()) { + if (party->isSharedExperienceEnabled()) { + return SHIELD_BLUE_SHAREDEXP; + } + + if (party->canUseSharedExperience(player)) { + return SHIELD_BLUE_NOSHAREDEXP; + } + + return SHIELD_BLUE_NOSHAREDEXP_BLINK; + } + return SHIELD_BLUE; } @@ -3621,6 +3985,10 @@ PartyShields_t Player::getPartyShield(const Player* player) const return SHIELD_WHITEYELLOW; } + if (player->party) { + return SHIELD_GRAY; + } + return SHIELD_NONE; } @@ -3634,7 +4002,7 @@ bool Player::isInviting(const Player* player) const bool Player::isPartner(const Player* player) const { - if (!player || !party) { + if (!player || !party || player == this) { return false; } return party == player->party; @@ -3678,6 +4046,379 @@ void Player::clearPartyInvitations() invitePartyList.clear(); } +GuildEmblems_t Player::getGuildEmblem(const Player* player) const +{ + if (!player) { + return GUILDEMBLEM_NONE; + } + + const Guild* playerGuild = player->getGuild(); + if (!playerGuild) { + return GUILDEMBLEM_NONE; + } + + if (player->getGuildWarVector().empty()) { + if (guild == playerGuild) { + return GUILDEMBLEM_MEMBER; + } else { + return GUILDEMBLEM_OTHER; + } + } else if (guild == playerGuild) { + return GUILDEMBLEM_ALLY; + } else if (isInWar(player)) { + return GUILDEMBLEM_ENEMY; + } + + return GUILDEMBLEM_NEUTRAL; +} + +uint8_t Player::getCurrentMount() const +{ + int32_t value; + if (getStorageValue(PSTRG_MOUNTS_CURRENTMOUNT, value)) { + return value; + } + return 0; +} + +void Player::setCurrentMount(uint8_t mountId) +{ + addStorageValue(PSTRG_MOUNTS_CURRENTMOUNT, mountId); +} + +bool Player::toggleMount(bool mount) +{ + if ((OTSYS_TIME() - lastToggleMount) < 3000 && !wasMounted) { + sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED); + return false; + } + + if (mount) { + if (isMounted()) { + return false; + } + + if (!group->access && tile->hasFlag(TILESTATE_PROTECTIONZONE)) { + sendCancelMessage(RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE); + return false; + } + + const Outfit* playerOutfit = Outfits::getInstance().getOutfitByLookType(getSex(), defaultOutfit.lookType); + if (!playerOutfit) { + return false; + } + + uint8_t currentMountId = getCurrentMount(); + if (currentMountId == 0) { + sendOutfitWindow(); + return false; + } + + Mount* currentMount = g_game.mounts.getMountByID(currentMountId); + if (!currentMount) { + return false; + } + + if (!hasMount(currentMount)) { + setCurrentMount(0); + sendOutfitWindow(); + return false; + } + + if (currentMount->premium && !isPremium()) { + sendCancelMessage(RETURNVALUE_YOUNEEDPREMIUMACCOUNT); + return false; + } + + if (hasCondition(CONDITION_OUTFIT)) { + sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return false; + } + + defaultOutfit.lookMount = currentMount->clientId; + + if (currentMount->speed != 0) { + g_game.changeSpeed(this, currentMount->speed); + } + } else { + if (!isMounted()) { + return false; + } + + dismount(); + } + + g_game.internalCreatureChangeOutfit(this, defaultOutfit); + lastToggleMount = OTSYS_TIME(); + return true; +} + +bool Player::tameMount(uint8_t mountId) +{ + if (!g_game.mounts.getMountByID(mountId)) { + return false; + } + + const uint8_t tmpMountId = mountId - 1; + const uint32_t key = PSTRG_MOUNTS_RANGE_START + (tmpMountId / 31); + + int32_t value; + if (getStorageValue(key, value)) { + value |= (1 << (tmpMountId % 31)); + } else { + value = (1 << (tmpMountId % 31)); + } + + addStorageValue(key, value); + return true; +} + +bool Player::untameMount(uint8_t mountId) +{ + if (!g_game.mounts.getMountByID(mountId)) { + return false; + } + + const uint8_t tmpMountId = mountId - 1; + const uint32_t key = PSTRG_MOUNTS_RANGE_START + (tmpMountId / 31); + + int32_t value; + if (!getStorageValue(key, value)) { + return true; + } + + value &= ~(1 << (tmpMountId % 31)); + addStorageValue(key, value); + + if (getCurrentMount() == mountId) { + if (isMounted()) { + dismount(); + g_game.internalCreatureChangeOutfit(this, defaultOutfit); + } + + setCurrentMount(0); + } + + return true; +} + +bool Player::hasMount(const Mount* mount) const +{ + if (isAccessPlayer()) { + return true; + } + + if (mount->premium && !isPremium()) { + return false; + } + + const uint8_t tmpMountId = mount->id - 1; + + int32_t value; + if (!getStorageValue(PSTRG_MOUNTS_RANGE_START + (tmpMountId / 31), value)) { + return false; + } + + return ((1 << (tmpMountId % 31)) & value) != 0; +} + +void Player::dismount() +{ + Mount* mount = g_game.mounts.getMountByID(getCurrentMount()); + if (mount && mount->speed > 0) { + g_game.changeSpeed(this, -mount->speed); + } + + defaultOutfit.lookMount = 0; +} + +bool Player::addOfflineTrainingTries(skills_t skill, uint64_t tries) +{ + if (tries == 0 || skill == SKILL_LEVEL) { + return false; + } + + bool sendUpdate = false; + uint32_t oldSkillValue, newSkillValue; + long double oldPercentToNextLevel, newPercentToNextLevel; + + if (skill == SKILL_MAGLEVEL) { + uint64_t currReqMana = vocation->getReqMana(magLevel); + uint64_t nextReqMana = vocation->getReqMana(magLevel + 1); + + if (currReqMana >= nextReqMana) { + return false; + } + + oldSkillValue = magLevel; + oldPercentToNextLevel = static_cast(manaSpent * 100) / nextReqMana; + + g_events->eventPlayerOnGainSkillTries(this, SKILL_MAGLEVEL, tries); + uint32_t currMagLevel = magLevel; + + while ((manaSpent + tries) >= nextReqMana) { + tries -= nextReqMana - manaSpent; + + magLevel++; + manaSpent = 0; + + g_creatureEvents->playerAdvance(this, SKILL_MAGLEVEL, magLevel - 1, magLevel); + + sendUpdate = true; + currReqMana = nextReqMana; + nextReqMana = vocation->getReqMana(magLevel + 1); + + if (currReqMana >= nextReqMana) { + tries = 0; + break; + } + } + + manaSpent += tries; + + if (magLevel != currMagLevel) { + std::ostringstream ss; + ss << "You advanced to magic level " << magLevel << '.'; + sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + } + + uint8_t newPercent; + if (nextReqMana > currReqMana) { + newPercent = Player::getPercentLevel(manaSpent, nextReqMana); + newPercentToNextLevel = static_cast(manaSpent * 100) / nextReqMana; + } else { + newPercent = 0; + newPercentToNextLevel = 0; + } + + if (newPercent != magLevelPercent) { + magLevelPercent = newPercent; + sendUpdate = true; + } + + newSkillValue = magLevel; + } else { + uint64_t currReqTries = vocation->getReqSkillTries(skill, skills[skill].level); + uint64_t nextReqTries = vocation->getReqSkillTries(skill, skills[skill].level + 1); + if (currReqTries >= nextReqTries) { + return false; + } + + oldSkillValue = skills[skill].level; + oldPercentToNextLevel = static_cast(skills[skill].tries * 100) / nextReqTries; + + g_events->eventPlayerOnGainSkillTries(this, skill, tries); + uint32_t currSkillLevel = skills[skill].level; + + while ((skills[skill].tries + tries) >= nextReqTries) { + tries -= nextReqTries - skills[skill].tries; + + skills[skill].level++; + skills[skill].tries = 0; + skills[skill].percent = 0; + + g_creatureEvents->playerAdvance(this, skill, (skills[skill].level - 1), skills[skill].level); + + sendUpdate = true; + currReqTries = nextReqTries; + nextReqTries = vocation->getReqSkillTries(skill, skills[skill].level + 1); + + if (currReqTries >= nextReqTries) { + tries = 0; + break; + } + } + + skills[skill].tries += tries; + + if (currSkillLevel != skills[skill].level) { + std::ostringstream ss; + ss << "You advanced to " << getSkillName(skill) << " level " << skills[skill].level << '.'; + sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + } + + uint8_t newPercent; + if (nextReqTries > currReqTries) { + newPercent = Player::getPercentLevel(skills[skill].tries, nextReqTries); + newPercentToNextLevel = static_cast(skills[skill].tries * 100) / nextReqTries; + } else { + newPercent = 0; + newPercentToNextLevel = 0; + } + + if (skills[skill].percent != newPercent) { + skills[skill].percent = newPercent; + sendUpdate = true; + } + + newSkillValue = skills[skill].level; + } + + if (sendUpdate) { + sendSkills(); + } + + std::ostringstream ss; + ss << std::fixed << std::setprecision(2) << "Your " << ucwords(getSkillName(skill)) << " skill changed from level " << oldSkillValue << " (with " << oldPercentToNextLevel << "% progress towards level " << (oldSkillValue + 1) << ") to level " << newSkillValue << " (with " << newPercentToNextLevel << "% progress towards level " << (newSkillValue + 1) << ')'; + sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); + return sendUpdate; +} + +bool Player::hasModalWindowOpen(uint32_t modalWindowId) const +{ + return find(modalWindows.begin(), modalWindows.end(), modalWindowId) != modalWindows.end(); +} + +void Player::onModalWindowHandled(uint32_t modalWindowId) +{ + modalWindows.remove(modalWindowId); +} + +void Player::sendModalWindow(const ModalWindow& modalWindow) +{ + if (!client) { + return; + } + + modalWindows.push_front(modalWindow.id); + client->sendModalWindow(modalWindow); +} + +void Player::clearModalWindows() +{ + modalWindows.clear(); +} + +uint16_t Player::getHelpers() const +{ + uint16_t helpers; + + if (guild && party) { + std::unordered_set helperSet; + + const auto& guildMembers = guild->getMembersOnline(); + helperSet.insert(guildMembers.begin(), guildMembers.end()); + + const auto& partyMembers = party->getMembers(); + helperSet.insert(partyMembers.begin(), partyMembers.end()); + + const auto& partyInvitees = party->getInvitees(); + helperSet.insert(partyInvitees.begin(), partyInvitees.end()); + + helperSet.insert(party->getLeader()); + + helpers = helperSet.size(); + } else if (guild) { + helpers = guild->getMembersOnline().size(); + } else if (party) { + helpers = party->getMemberCount() + party->getInvitationCount() + 1; + } else { + helpers = 0; + } + + return helpers; +} + void Player::sendClosePrivate(uint16_t channelId) { if (channelId == CHANNEL_GUILD || channelId == CHANNEL_PARTY) { @@ -3740,7 +4481,6 @@ size_t Player::getMaxDepotItems() const } else if (isPremium()) { return 2000; } - return 1000; } diff --git a/src/player.h b/src/player.h index f088f13..db0dc4e 100644 --- a/src/player.h +++ b/src/player.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,12 +29,14 @@ #include "protocolgame.h" #include "ioguild.h" #include "party.h" +#include "inbox.h" +#include "depotchest.h" #include "depotlocker.h" #include "guild.h" #include "groups.h" #include "town.h" +#include "mounts.h" -class BehaviourDatabase; class House; class NetworkMessage; class Weapon; @@ -51,9 +53,17 @@ enum skillsid_t { SKILLVALUE_PERCENT = 2, }; -enum chaseMode_t : uint8_t { - CHASEMODE_STANDSTILL = 0, - CHASEMODE_FOLLOW = 1, +enum fightMode_t : uint8_t { + FIGHTMODE_ATTACK = 1, + FIGHTMODE_BALANCED = 2, + FIGHTMODE_DEFENSE = 3, +}; + +enum pvpMode_t : uint8_t { + PVP_MODE_DOVE = 0, + PVP_MODE_WHITE_HAND = 1, + PVP_MODE_YELLOW_HAND = 2, + PVP_MODE_RED_FIST = 3, }; enum tradestate_t : uint8_t { @@ -65,11 +75,14 @@ enum tradestate_t : uint8_t { }; struct VIPEntry { - VIPEntry(uint32_t guid, std::string name) : - guid(guid), name(std::move(name)) {} + VIPEntry(uint32_t guid, std::string name, std::string description, uint32_t icon, bool notify) : + guid(guid), name(std::move(name)), description(std::move(description)), icon(icon), notify(notify) {} uint32_t guid; std::string name; + std::string description; + uint32_t icon; + bool notify; }; struct OpenContainer { @@ -90,10 +103,10 @@ struct Skill { uint8_t percent = 0; }; -typedef std::map MuteCountMap; +using MuteCountMap = std::map; static constexpr int32_t PLAYER_MAX_SPEED = 1500; -static constexpr int32_t PLAYER_MIN_SPEED = 0; +static constexpr int32_t PLAYER_MIN_SPEED = 10; class Player final : public Creature, public Cylinder { @@ -105,14 +118,14 @@ class Player final : public Creature, public Cylinder Player(const Player&) = delete; Player& operator=(const Player&) = delete; - Player* getPlayer() final { + Player* getPlayer() override { return this; } - const Player* getPlayer() const final { + const Player* getPlayer() const override { return this; } - void setID() final { + void setID() override { if (id == 0) { id = playerAutoID++; } @@ -120,16 +133,37 @@ class Player final : public Creature, public Cylinder static MuteCountMap muteCountMap; - const std::string& getName() const final { + const std::string& getName() const override { return name; } void setName(std::string name) { this->name = std::move(name); } - const std::string& getNameDescription() const final { + const std::string& getNameDescription() const override { return name; } - std::string getDescription(int32_t lookDistance) const final; + std::string getDescription(int32_t lookDistance) const override; + + CreatureType_t getType() const override { + return CREATURETYPE_PLAYER; + } + + uint8_t getCurrentMount() const; + void setCurrentMount(uint8_t mountId); + bool isMounted() const { + return defaultOutfit.lookMount != 0; + } + bool toggleMount(bool mount); + bool tameMount(uint8_t mountId); + bool untameMount(uint8_t mountId); + bool hasMount(const Mount* mount) const; + void dismount(); + + void sendFYIBox(const std::string& message) { + if (client) { + client->sendFYIBox(message); + } + } void setGUID(uint32_t guid) { this->guid = guid; @@ -137,12 +171,12 @@ class Player final : public Creature, public Cylinder uint32_t getGUID() const { return guid; } - bool canSeeInvisibility() const final { + bool canSeeInvisibility() const override { return hasFlag(PlayerFlag_CanSenseInvisibility) || group->access; } - void removeList() final; - void addList() final; + void removeList() override; + void addList() override; void kickPlayer(bool displayEffect); static uint64_t getExpForLevel(int32_t lv) { @@ -154,6 +188,25 @@ class Player final : public Creature, public Cylinder return staminaMinutes; } + bool addOfflineTrainingTries(skills_t skill, uint64_t tries); + + void addOfflineTrainingTime(int32_t addTime) { + offlineTrainingTime = std::min(12 * 3600 * 1000, offlineTrainingTime + addTime); + } + void removeOfflineTrainingTime(int32_t removeTime) { + offlineTrainingTime = std::max(0, offlineTrainingTime - removeTime); + } + int32_t getOfflineTrainingTime() const { + return offlineTrainingTime; + } + + int32_t getOfflineTrainingSkill() const { + return offlineTrainingSkill; + } + void setOfflineTrainingSkill(int32_t skill) { + offlineTrainingSkill = skill; + } + uint64_t getBankBalance() const { return bankBalance; } @@ -183,12 +236,23 @@ class Player final : public Creature, public Cylinder } bool isInWar(const Player* player) const; - bool isInWarList(uint32_t guild_id) const; + bool isInWarList(uint32_t guildId) const; + + void setLastWalkthroughAttempt(int64_t walkthroughAttempt) { + lastWalkthroughAttempt = walkthroughAttempt; + } + void setLastWalkthroughPosition(Position walkthroughPosition) { + lastWalkthroughPosition = walkthroughPosition; + } + + Inbox* getInbox() const { + return inbox; + } uint16_t getClientIcons() const; - const GuildWarList& getGuildWarList() const { - return guildWarList; + const GuildWarVector& getGuildWarVector() const { + return guildWarVector; } Vocation* getVocation() const { @@ -228,6 +292,8 @@ class Player final : public Creature, public Cylinder void removePartyInvitation(Party* party); void clearPartyInvitations(); + GuildEmblems_t getGuildEmblem(const Player* player) const; + uint64_t getSpentMana() const { return manaSpent; } @@ -273,7 +339,7 @@ class Player final : public Creature, public Cylinder bool canOpenCorpse(uint32_t ownerId) const; - void addStorageValue(const uint32_t key, const int32_t value); + void addStorageValue(const uint32_t key, const int32_t value, const bool isLogin = false); bool getStorageValue(const uint32_t key, int32_t& value) const; void genReservedStorageRange(); @@ -284,24 +350,25 @@ class Player final : public Creature, public Cylinder return group; } + void setInMarket(bool value) { + inMarket = value; + } + bool isInMarket() const { + return inMarket; + } + + void setLastDepotId(int16_t newId) { + lastDepotId = newId; + } + int16_t getLastDepotId() const { + return lastDepotId; + } + void resetIdleTime() { idleTime = 0; - resetLastWalkingTime(); } - int32_t getIdleTime() const { - return idleTime; - } - - void resetLastWalkingTime() { - lastWalkingTime = 0; - } - - int32_t getLastWalkingTime() const { - return lastWalkingTime; - } - - bool isInGhostMode() const { + bool isInGhostMode() const override { return ghostMode; } void switchGhostMode() { @@ -338,13 +405,12 @@ class Player final : public Creature, public Cylinder bool isPremium() const; void setPremiumDays(int32_t v); + uint16_t getHelpers() const; + bool setVocation(uint16_t vocId); uint16_t getVocationId() const { return vocation->getId(); } - uint16_t getVocationFlagId() const { - return vocation->getFlagId(); - } PlayerSex_t getSex() const { return sex; @@ -375,7 +441,11 @@ class Player final : public Creature, public Cylinder this->town = town; } - bool isPushable() const final; + void clearModalWindows(); + bool hasModalWindowOpen(uint32_t modalWindowId) const; + void onModalWindowHandled(uint32_t modalWindowId); + + bool isPushable() const override; uint32_t isMuted() const; void addMessageBuffer(); void removeMessageBuffer(); @@ -401,14 +471,12 @@ class Player final : public Creature, public Cylinder } } - int32_t getMaxHealth() const final { + int32_t getMaxHealth() const override { return std::max(1, healthMax + varStats[STAT_MAXHITPOINTS]); } - uint32_t getMana() const { return mana; } - uint32_t getMaxMana() const { return std::max(0, manaMax + varStats[STAT_MAXMANAPOINTS]); } @@ -426,20 +494,28 @@ class Player final : public Creature, public Cylinder varSkills[skill] += modifier; } + void setVarSpecialSkill(SpecialSkills_t skill, int32_t modifier) { + varSpecialSkills[skill] += modifier; + } + void setVarStats(stats_t stat, int32_t modifier); int32_t getDefaultStats(stats_t stat) const; void addConditionSuppressions(uint32_t conditions); void removeConditionSuppressions(uint32_t conditions); - DepotLocker* getDepotLocker(uint32_t depotId, bool autoCreate); - void onReceiveMail(uint32_t townId) const; - bool isNearDepotBox(uint32_t townId) const; + DepotChest* getDepotChest(uint32_t depotId, bool autoCreate); + DepotLocker* getDepotLocker(uint32_t depotId); + void onReceiveMail() const; + bool isNearDepotBox() const; - bool canSee(const Position& pos) const final; - bool canSeeCreature(const Creature* creature) const final; + bool canSee(const Position& pos) const override; + bool canSeeCreature(const Creature* creature) const override; - RaceType_t getRace() const final { + bool canWalkthrough(const Creature* creature) const; + bool canWalkthroughEx(const Creature* creature) const; + + RaceType_t getRace() const override { return RACE_BLOOD; } @@ -456,27 +532,51 @@ class Player final : public Creature, public Cylinder return tradeItem; } + //shop functions + void setShopOwner(Npc* owner, int32_t onBuy, int32_t onSell) { + shopOwner = owner; + purchaseCallback = onBuy; + saleCallback = onSell; + } + + Npc* getShopOwner(int32_t& onBuy, int32_t& onSell) { + onBuy = purchaseCallback; + onSell = saleCallback; + return shopOwner; + } + + const Npc* getShopOwner(int32_t& onBuy, int32_t& onSell) const { + onBuy = purchaseCallback; + onSell = saleCallback; + return shopOwner; + } + //V.I.P. functions - void notifyStatusChange(Player* player, VipStatus_t status); + void notifyStatusChange(Player* loginPlayer, VipStatus_t status); bool removeVIP(uint32_t vipGuid); bool addVIP(uint32_t vipGuid, const std::string& vipName, VipStatus_t status); bool addVIPInternal(uint32_t vipGuid); + bool editVIP(uint32_t vipGuid, const std::string& description, uint32_t icon, bool notify); //follow functions - bool setFollowCreature(Creature* creature) final; - void goToFollowCreature() final; + bool setFollowCreature(Creature* creature) override; + void goToFollowCreature() override; //follow events - void onFollowCreature(const Creature* creature) final; + void onFollowCreature(const Creature* creature) override; //walk events - void onWalk(Direction& dir) final; - void onWalkAborted() final; - void onWalkComplete() final; + void onWalk(Direction& dir) override; + void onWalkAborted() override; + void onWalkComplete() override; void stopWalk(); + void openShopWindow(Npc* npc, const std::list& shop); + bool closeShopWindow(bool sendCloseShopWindow = true); + bool updateSaleShopList(const Item* item); + bool hasShopItemForSale(uint32_t itemId, uint8_t subType) const; - void setChaseMode(chaseMode_t mode); + void setChaseMode(bool mode); void setFightMode(fightMode_t mode) { fightMode = mode; } @@ -485,14 +585,14 @@ class Player final : public Creature, public Cylinder } //combat functions - bool setAttackedCreature(Creature* creature) final; - bool isImmune(CombatType_t type) const final; - bool isImmune(ConditionType_t type) const final; + bool setAttackedCreature(Creature* creature) override; + bool isImmune(CombatType_t type) const override; + bool isImmune(ConditionType_t type) const override; bool hasShield() const; - bool isAttackable() const final; + bool isAttackable() const override; static bool lastHitIsPlayer(Creature* lastHitCreature); - void changeHealth(int32_t healthChange, bool sendHealthChange = true) final; + void changeHealth(int32_t healthChange, bool sendHealthChange = true) override; void changeMana(int32_t manaChange); void changeSoul(int32_t soulChange); @@ -500,12 +600,15 @@ class Player final : public Creature, public Cylinder return pzLocked; } BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, - bool checkDefense = false, bool checkArmor = false, bool field = false) final; - void doAttacking(uint32_t interval) final; - bool hasExtraSwing() final { + bool checkDefense = false, bool checkArmor = false, bool field = false) override; + void doAttacking(uint32_t interval) override; + bool hasExtraSwing() override { return lastAttack > 0 && ((OTSYS_TIME() - lastAttack) >= getAttackSpeed()); } + uint16_t getSpecialSkill(uint8_t skill) const { + return std::max(0, varSpecialSkills[skill]); + } uint16_t getSkillLevel(uint8_t skill) const { return std::max(0, skills[skill].level + varSkills[skill]); } @@ -523,48 +626,51 @@ class Player final : public Creature, public Cylinder return lastAttackBlockType; } - Item* getWeapon() const; - Item* getAmmunition() const; + Item* getWeapon(slots_t slot, bool ignoreAmmo) const; + Item* getWeapon(bool ignoreAmmo = false) const; + WeaponType_t getWeaponType() const; + int32_t getWeaponSkill(const Item* item) const; void getShieldAndWeapon(const Item*& shield, const Item*& weapon) const; - void drainHealth(Creature* attacker, int32_t damage) final; + void drainHealth(Creature* attacker, int32_t damage) override; void drainMana(Creature* attacker, int32_t manaLoss); void addManaSpent(uint64_t amount); void addSkillAdvance(skills_t skill, uint64_t count); - int32_t getArmor() const final; - int32_t getDefense() final; - fightMode_t getFightMode() const; + int32_t getArmor() const override; + int32_t getDefense() const override; + float getAttackFactor() const override; + float getDefenseFactor() const override; void addInFightTicks(bool pzlock = false); - uint64_t getGainedExperience(Creature* attacker) const final; + uint64_t getGainedExperience(Creature* attacker) const override; //combat event functions - void onAddCondition(ConditionType_t type) final; - void onAddCombatCondition(ConditionType_t type) final; - void onEndCondition(ConditionType_t type) final; - void onCombatRemoveCondition(Condition* condition) final; - void onAttackedCreature(Creature* target) final; - void onAttacked() final; - void onAttackedCreatureDrainHealth(Creature* target, int32_t points) final; - void onTargetCreatureGainHealth(Creature* target, int32_t points) final; - bool onKilledCreature(Creature* target, bool lastHit = true) final; - void onGainExperience(uint64_t gainExp, Creature* target) final; + void onAddCondition(ConditionType_t type) override; + void onAddCombatCondition(ConditionType_t type) override; + void onEndCondition(ConditionType_t type) override; + void onCombatRemoveCondition(Condition* condition) override; + void onAttackedCreature(Creature* target) override; + void onAttacked() override; + void onAttackedCreatureDrainHealth(Creature* target, int32_t points) override; + void onTargetCreatureGainHealth(Creature* target, int32_t points) override; + bool onKilledCreature(Creature* target, bool lastHit = true) override; + void onGainExperience(uint64_t gainExp, Creature* target) override; void onGainSharedExperience(uint64_t gainExp, Creature* source); - void onAttackedCreatureBlockHit(BlockType_t blockType) final; - void onBlockHit() final; - void onChangeZone(ZoneType_t zone) final; - void onAttackedCreatureChangeZone(ZoneType_t zone) final; - void onIdleStatus() final; - void onPlacedCreature() final; + void onAttackedCreatureBlockHit(BlockType_t blockType) override; + void onBlockHit() override; + void onChangeZone(ZoneType_t zone) override; + void onAttackedCreatureChangeZone(ZoneType_t zone) override; + void onIdleStatus() override; + void onPlacedCreature() override; - void getCreatureLight(LightInfo& light) const final; + LightInfo getCreatureLight() const override; - Skulls_t getSkull() const final; - Skulls_t getSkullClient(const Creature* creature) const final; - time_t getPlayerKillerEnd() const { return playerKillerEnd; } - void setPlayerKillerEnd(time_t ticks) { playerKillerEnd = ticks; } + Skulls_t getSkull() const override; + Skulls_t getSkullClient(const Creature* creature) const override; + int64_t getSkullTicks() const { return skullTicks; } + void setSkullTicks(int64_t ticks) { skullTicks = ticks; } bool hasAttacked(const Player* attacked) const; void addAttacked(const Player* attacked); @@ -576,7 +682,7 @@ class Player final : public Creature, public Cylinder client->sendCreatureSkull(creature); } } - void checkSkullTicks(); + void checkSkullTicks(int64_t ticks); bool canWear(uint32_t lookType, uint8_t addons) const; void addOutfit(uint16_t lookType, uint8_t addons); @@ -584,7 +690,6 @@ class Player final : public Creature, public Cylinder bool removeOutfitAddon(uint16_t lookType, uint8_t addons); bool getOutfitAddons(const Outfit& outfit, uint8_t& addons) const; - bool canLogout(); size_t getMaxVIPEntries() const; @@ -596,7 +701,7 @@ class Player final : public Creature, public Cylinder if (client) { int32_t stackpos = tile->getStackposOfItem(this, item); if (stackpos != -1) { - client->sendAddTileItem(pos, item, stackpos); + client->sendAddTileItem(pos, stackpos, item); } } } @@ -619,6 +724,16 @@ class Player final : public Creature, public Cylinder } } + void sendChannelMessage(const std::string& author, const std::string& text, SpeakClasses type, uint16_t channel) { + if (client) { + client->sendChannelMessage(author, text, type, channel); + } + } + void sendChannelEvent(uint16_t channelId, const std::string& playerName, ChannelEvent_t channelEvent) { + if (client) { + client->sendChannelEvent(channelId, playerName, channelEvent); + } + } void sendCreatureAppear(const Creature* creature, const Position& pos, bool isLogin) { if (client) { client->sendAddCreature(creature, pos, creature->getTile()->getStackposOfCreature(this, creature), isLogin); @@ -689,11 +804,37 @@ class Player final : public Creature, public Cylinder client->sendCreatureLight(creature); } } + void sendCreatureWalkthrough(const Creature* creature, bool walkthrough) { + if (client) { + client->sendCreatureWalkthrough(creature, walkthrough); + } + } void sendCreatureShield(const Creature* creature) { if (client) { client->sendCreatureShield(creature); } } + void sendCreatureType(uint32_t creatureId, uint8_t creatureType) { + if (client) { + client->sendCreatureType(creatureId, creatureType); + } + } + void sendCreatureHelpers(uint32_t creatureId, uint16_t helpers) { + if (client) { + client->sendCreatureHelpers(creatureId, helpers); + } + } + void sendSpellCooldown(uint8_t spellId, uint32_t time) { + if (client) { + client->sendSpellCooldown(spellId, time); + } + } + void sendSpellGroupCooldown(SpellGroup_t groupId, uint32_t time) { + if (client) { + client->sendSpellGroupCooldown(groupId, time); + } + } + void sendModalWindow(const ModalWindow& modalWindow); //container void sendAddContainerItem(const Container* container, const Item* item); @@ -711,20 +852,25 @@ class Player final : public Creature, public Cylinder client->sendInventoryItem(slot, item); } } + void sendItems() { + if (client) { + client->sendItems(); + } + } //event methods void onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, - const ItemType& oldType, const Item* newItem, const ItemType& newType) final; + const ItemType& oldType, const Item* newItem, const ItemType& newType) override; void onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, - const Item* item) final; + const Item* item) override; - void onCreatureAppear(Creature* creature, bool isLogin) final; - void onRemoveCreature(Creature* creature, bool isLogout) final; + void onCreatureAppear(Creature* creature, bool isLogin) override; + void onRemoveCreature(Creature* creature, bool isLogout) override; void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, - const Tile* oldTile, const Position& oldPos, bool teleport) final; + const Tile* oldTile, const Position& oldPos, bool teleport) override; - void onAttackedCreatureDisappear(bool isLogout) final; - void onFollowCreatureDisappear(bool isLogout) final; + void onAttackedCreatureDisappear(bool isLogout) override; + void onFollowCreatureDisappear(bool isLogout) override; //container void onAddContainerItem(const Item* item); @@ -794,16 +940,16 @@ class Player final : public Creature, public Cylinder } } void sendStats(); + void sendBasicData() const { + if (client) { + client->sendBasicData(); + } + } void sendSkills() const { if (client) { client->sendSkills(); } } - void sendAnimatedText(const Position& pos, uint8_t color, const std::string& text) const { - if (client) { - client->sendAnimatedText(pos, color, text); - } - } void sendTextMessage(MessageClasses mclass, const std::string& message) const { if (client) { client->sendTextMessage(TextMessage(mclass, message)); @@ -814,6 +960,11 @@ class Player final : public Creature, public Cylinder client->sendTextMessage(message); } } + void sendReLoginWindow(uint8_t unfairFightReduction) const { + if (client) { + client->sendReLoginWindow(unfairFightReduction); + } + } void sendTextWindow(Item* item, uint16_t maxlen, bool canWrite) const { if (client) { client->sendTextWindow(windowTextId, item, maxlen, canWrite); @@ -829,6 +980,62 @@ class Player final : public Creature, public Cylinder client->sendToChannel(creature, type, text, channelId); } } + void sendShop(Npc* npc) const { + if (client) { + client->sendShop(npc, shopItemList); + } + } + void sendSaleItemList() const { + if (client) { + client->sendSaleItemList(shopItemList); + } + } + void sendCloseShop() const { + if (client) { + client->sendCloseShop(); + } + } + void sendMarketEnter(uint32_t depotId) const { + if (client) { + client->sendMarketEnter(depotId); + } + } + void sendMarketLeave() { + inMarket = false; + if (client) { + client->sendMarketLeave(); + } + } + void sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& buyOffers, const MarketOfferList& sellOffers) const { + if (client) { + client->sendMarketBrowseItem(itemId, buyOffers, sellOffers); + } + } + void sendMarketBrowseOwnOffers(const MarketOfferList& buyOffers, const MarketOfferList& sellOffers) const { + if (client) { + client->sendMarketBrowseOwnOffers(buyOffers, sellOffers); + } + } + void sendMarketBrowseOwnHistory(const HistoryMarketOfferList& buyOffers, const HistoryMarketOfferList& sellOffers) const { + if (client) { + client->sendMarketBrowseOwnHistory(buyOffers, sellOffers); + } + } + void sendMarketDetail(uint16_t itemId) const { + if (client) { + client->sendMarketDetail(itemId); + } + } + void sendMarketAcceptOffer(const MarketOfferEx& offer) const { + if (client) { + client->sendMarketAcceptOffer(offer); + } + } + void sendMarketCancelOffer(const MarketOfferEx& offer) const { + if (client) { + client->sendMarketCancelOffer(offer); + } + } void sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack) const { if (client) { client->sendTradeItemRequest(traderName, item, ack); @@ -839,7 +1046,7 @@ class Player final : public Creature, public Cylinder client->sendCloseTrade(); } } - void sendWorldLight(const LightInfo& lightInfo) { + void sendWorldLight(LightInfo lightInfo) { if (client) { client->sendWorldLight(lightInfo); } @@ -864,30 +1071,35 @@ class Player final : public Creature, public Cylinder client->sendCloseContainer(cid); } } - void sendRemoveRuleViolationReport(const std::string& name) const { - if (client) { - client->sendRemoveRuleViolationReport(name); - } - } - void sendRuleViolationCancel(const std::string& name) const { - if (client) { - client->sendRuleViolationCancel(name); - } - } - void sendLockRuleViolationReport() const { - if (client) { - client->sendLockRuleViolation(); - } - } - void sendRuleViolationsChannel(uint16_t channelId) const { - if (client) { - client->sendRuleViolationsChannel(channelId); - } - } - void sendChannel(uint16_t channelId, const std::string& channelName) { + void sendChannel(uint16_t channelId, const std::string& channelName, const UsersMap* channelUsers, const InvitedMap* invitedUsers) { if (client) { - client->sendChannel(channelId, channelName); + client->sendChannel(channelId, channelName, channelUsers, invitedUsers); + } + } + void sendTutorial(uint8_t tutorialId) { + if (client) { + client->sendTutorial(tutorialId); + } + } + void sendAddMarker(const Position& pos, uint8_t markType, const std::string& desc) { + if (client) { + client->sendAddMarker(pos, markType, desc); + } + } + void sendQuestLog() { + if (client) { + client->sendQuestLog(); + } + } + void sendQuestLine(const Quest* quest) { + if (client) { + client->sendQuestLine(quest); + } + } + void sendEnterWorld() { + if (client) { + client->sendEnterWorld(); } } void sendFightModes() { @@ -905,10 +1117,10 @@ class Player final : public Creature, public Cylinder lastPong = OTSYS_TIME(); } - void onThink(uint32_t interval) final; + void onThink(uint32_t interval) override; - 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; + 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 setNextAction(int64_t time) { if (time > nextAction) { @@ -930,7 +1142,7 @@ class Player final : public Creature, public Cylinder void forgetInstantSpell(const std::string& spellName); bool hasLearnedInstantSpell(const std::string& spellName) const; - protected: + private: std::forward_list getMuteConditions() const; void checkTradeState(const Item* item); @@ -938,7 +1150,7 @@ class Player final : public Creature, public Cylinder void gainExperience(uint64_t gainExp, Creature* source); void addExperience(Creature* source, uint64_t exp, bool sendText = false); - void removeExperience(uint64_t exp); + void removeExperience(uint64_t exp, bool sendText = false); void updateInventoryWeight(); @@ -946,56 +1158,55 @@ class Player final : public Creature, public Cylinder void setNextWalkTask(SchedulerTask* task); void setNextActionTask(SchedulerTask* task); - void dropLoot(Container* corpse, Creature*) final; - void death(Creature* lastHitCreature) final; - bool dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified) final; - Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) final; + void death(Creature* lastHitCreature) override; + bool dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified) override; + Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) override; //cylinder implementations ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const final; + 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; + uint32_t flags) const override; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const override; Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, - uint32_t& flags) final; + uint32_t& flags) override; - void addThing(Thing*) final {} - void addThing(int32_t index, Thing* thing) final; + void addThing(Thing*) override {} + void addThing(int32_t index, Thing* thing) override; - void updateThing(Thing* thing, uint16_t itemId, uint32_t count) final; - void replaceThing(uint32_t index, Thing* thing) final; + void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override; + void replaceThing(uint32_t index, Thing* thing) override; - void removeThing(Thing* thing, uint32_t count) final; + void removeThing(Thing* thing, uint32_t count) override; - 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& getAllItemTypeCount(std::map& countMap) const final; - Thing* getThing(size_t index) const final; + int32_t getThingIndex(const Thing* thing) const override; + size_t getFirstIndex() const override; + size_t getLastIndex() const override; + uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const override; + std::map& getAllItemTypeCount(std::map& countMap) const override; + Thing* getThing(size_t index) const override; - void internalAddThing(Thing* thing) final; - void internalAddThing(uint32_t index, Thing* thing) final; - - uint32_t checkPlayerKilling(); + void internalAddThing(Thing* thing) override; + void internalAddThing(uint32_t index, Thing* thing) override; std::unordered_set attackedSet; std::unordered_set VIPList; std::map openContainers; std::map depotLockerMap; + std::map depotChests; std::map storageMap; std::vector outfits; - GuildWarList guildWarList; + GuildWarVector guildWarVector; + + std::list shopItemList; std::forward_list invitePartyList; + std::forward_list modalWindows; std::forward_list learnedInstantSpellList; std::forward_list storedConditionList; // TODO: This variable is only temporarily used when logging in, get rid of it somehow - std::list murderTimeStamps; - std::string name; std::string guildNick; @@ -1006,26 +1217,30 @@ class Player final : public Creature, public Cylinder time_t lastLoginSaved = 0; time_t lastLogout = 0; - time_t playerKillerEnd = 0; uint64_t experience = 0; uint64_t manaSpent = 0; + uint64_t lastAttack = 0; uint64_t bankBalance = 0; - int64_t lastAttack = 0; + uint64_t lastQuestlogUpdate = 0; int64_t lastFailedFollow = 0; + int64_t skullTicks = 0; + int64_t lastWalkthroughAttempt = 0; + int64_t lastToggleMount = 0; int64_t lastPing; int64_t lastPong; int64_t nextAction = 0; - int64_t earliestAttackTime = 0; - + BedItem* bedItem = nullptr; Guild* guild = nullptr; const GuildRank* guildRank = nullptr; Group* group = nullptr; + Inbox* inbox; Item* tradeItem = nullptr; Item* inventory[CONST_SLOT_LAST + 1] = {}; Item* writeItem = nullptr; House* editHouse = nullptr; + Npc* shopOwner = nullptr; Party* party = nullptr; Player* tradePartner = nullptr; ProtocolGame_ptr client; @@ -1052,16 +1267,22 @@ class Player final : public Creature, public Cylinder uint32_t mana = 0; uint32_t manaMax = 0; int32_t varSkills[SKILL_LAST + 1] = {}; + int32_t varSpecialSkills[SPECIALSKILL_LAST + 1] = {}; int32_t varStats[STAT_LAST + 1] = {}; + int32_t purchaseCallback = -1; + int32_t saleCallback = -1; int32_t MessageBufferCount = 0; int32_t premiumDays = 0; int32_t bloodHitCount = 0; int32_t shieldBlockCount = 0; + int32_t offlineTrainingSkill = -1; + int32_t offlineTrainingTime = 0; int32_t idleTime = 0; - int32_t lastWalkingTime = 0; - uint16_t staminaMinutes = 3360; + uint16_t lastStatsTrainingTime = 0; + uint16_t staminaMinutes = 2520; uint16_t maxWriteLen = 0; + int16_t lastDepotId = -1; uint8_t soul = 0; uint8_t blessings = 0; @@ -1072,11 +1293,13 @@ class Player final : public Creature, public Cylinder OperatingSystem_t operatingSystem = CLIENTOS_NONE; BlockType_t lastAttackBlockType = BLOCK_NONE; tradestate_t tradeState = TRADE_NONE; - chaseMode_t chaseMode = CHASEMODE_STANDSTILL; fightMode_t fightMode = FIGHTMODE_ATTACK; AccountType_t accountType = ACCOUNT_TYPE_NORMAL; + bool chaseMode = false; bool secureMode = false; + bool inMarket = false; + bool wasMounted = false; bool ghostMode = false; bool pzLocked = false; bool isConnecting = false; @@ -1086,12 +1309,12 @@ class Player final : public Creature, public Cylinder static uint32_t playerAutoID; void updateItemsLight(bool internal = false); - int32_t getStepSpeed() const final { + int32_t getStepSpeed() const override { return std::max(PLAYER_MIN_SPEED, std::min(PLAYER_MAX_SPEED, getSpeed())); } void updateBaseSpeed() { if (!hasFlag(PlayerFlag_SetMaxSpeed)) { - baseSpeed = vocation->getBaseSpeed() + (level - 1); + baseSpeed = vocation->getBaseSpeed() + (2 * (level - 1)); } else { baseSpeed = PLAYER_MAX_SPEED; } @@ -1104,22 +1327,21 @@ class Player final : public Creature, public Cylinder } static uint8_t getPercentLevel(uint64_t count, uint64_t nextLevelCount); - static uint16_t getDropLootPercent(); double getLostPercent() const; - uint64_t getLostExperience() const final { + uint64_t getLostExperience() const override { return skillLoss ? static_cast(experience * getLostPercent()) : 0; } - uint32_t getDamageImmunities() const final { + uint32_t getDamageImmunities() const override { return damageImmunities; } - uint32_t getConditionImmunities() const final { + uint32_t getConditionImmunities() const override { return conditionImmunities; } - uint32_t getConditionSuppressions() const final { + uint32_t getConditionSuppressions() const override { return conditionSuppressions; } - uint16_t getLookCorpse() const final; - void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const final; + uint16_t getLookCorpse() const override; + void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const override; friend class Game; friend class Npc; @@ -1128,8 +1350,6 @@ class Player final : public Creature, public Cylinder friend class Actions; friend class IOLoginData; friend class ProtocolGame; - friend class BehaviourDatabase; - friend class ConjureSpell; }; #endif diff --git a/src/position.cpp b/src/position.cpp index a972899..247aace 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/position.h b/src/position.h index abd83ad..1d48840 100644 --- a/src/position.h +++ b/src/position.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,32 +42,32 @@ struct Position constexpr Position(uint16_t x, uint16_t y, uint8_t z) : x(x), y(y), z(z) {} template - inline static bool areInRange(const Position& p1, const Position& p2) { + static bool areInRange(const Position& p1, const Position& p2) { return Position::getDistanceX(p1, p2) <= deltax && Position::getDistanceY(p1, p2) <= deltay; } template - inline static bool areInRange(const Position& p1, const Position& p2) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + static int16_t getDistanceZ(const Position& p1, const Position& p2) { return std::abs(Position::getOffsetZ(p1, p2)); } @@ -123,9 +123,9 @@ struct Position 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; } + int_fast32_t getX() const { return x; } + int_fast32_t getY() const { return y; } + int_fast16_t getZ() const { return z; } }; std::ostream& operator<<(std::ostream&, const Position&); diff --git a/src/protocol.cpp b/src/protocol.cpp index 2686f1a..6980580 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,6 +22,7 @@ #include "protocol.h" #include "outputmessage.h" #include "rsa.h" +#include "xtea.h" extern RSA g_RSA; @@ -32,7 +33,7 @@ void Protocol::onSendMessage(const OutputMessage_ptr& msg) const if (encryptionEnabled) { XTEA_encrypt(*msg); - msg->addCryptoHeader(); + msg->addCryptoHeader(checksumEnabled); } } } @@ -51,8 +52,7 @@ OutputMessage_ptr Protocol::getOutputBuffer(int32_t size) //dispatcher thread if (!outputBuffer) { outputBuffer = OutputMessagePool::getOutputMessage(); - } - else if ((outputBuffer->getLength() + size) > NetworkMessage::MAX_PROTOCOL_BODY_LENGTH) { + } else if ((outputBuffer->getLength() + size) > NetworkMessage::MAX_PROTOCOL_BODY_LENGTH) { send(outputBuffer); outputBuffer = OutputMessagePool::getOutputMessage(); } @@ -61,73 +61,27 @@ OutputMessage_ptr Protocol::getOutputBuffer(int32_t size) 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; + size_t paddingBytes = msg.getLength() % 8u; 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; - } + xtea::encrypt(buffer, msg.getLength(), key); } bool Protocol::XTEA_decrypt(NetworkMessage& msg) const { - if (((msg.getLength() - 2) & 7) != 0) { + if (((msg.getLength() - 6) & 7) != 0) { return false; } - const uint32_t delta = 0x61C88647; - uint8_t* buffer = msg.getBuffer() + msg.getBufferPosition(); - const size_t messageLength = (msg.getLength() - 2); - 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); + xtea::decrypt(buffer, msg.getLength() - 6, key); - 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(); - if (innerLength > msg.getLength() - 4) { + uint16_t innerLength = msg.get(); + if (innerLength + 8 > msg.getLength()) { return false; } @@ -137,7 +91,7 @@ bool Protocol::XTEA_decrypt(NetworkMessage& msg) const bool Protocol::RSA_decrypt(NetworkMessage& msg) { - if ((msg.getLength() - msg.getBufferPosition()) != 128) { + if ((msg.getLength() - msg.getBufferPosition()) < 128) { return false; } diff --git a/src/protocol.h b/src/protocol.h index 43ad54d..3cd757d 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -1,6 +1,6 @@ /** * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2016 Mark Samman + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,77 +21,85 @@ #define FS_PROTOCOL_H_D71405071ACF4137A4B1203899DE80E1 #include "connection.h" +#include "xtea.h" class Protocol : public std::enable_shared_from_this { -public: - explicit Protocol(Connection_ptr connection) : connection(connection), key(), encryptionEnabled(false), rawMessages(false) {} - virtual ~Protocol() = default; + public: + explicit Protocol(Connection_ptr connection) : connection(connection) {} + virtual ~Protocol() = default; - // non-copyable - Protocol(const Protocol&) = delete; - Protocol& operator=(const Protocol&) = delete; + // non-copyable + Protocol(const Protocol&) = delete; + Protocol& operator=(const Protocol&) = delete; - virtual void parsePacket(NetworkMessage&) {} + 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() {} + 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); + bool isConnectionExpired() const { + return connection.expired(); } - } -protected: - void disconnect() const { - if (auto connection = getConnection()) { - connection->close(); + Connection_ptr getConnection() const { + return connection.lock(); } - } - 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); + uint32_t getIP() const; - void setRawMessages(bool value) { - rawMessages = value; - } + //Use this function for autosend messages only + OutputMessage_ptr getOutputBuffer(int32_t size); - virtual void release() {} - friend class Connection; + OutputMessage_ptr& getCurrentBuffer() { + return outputBuffer; + } - OutputMessage_ptr outputBuffer; -private: - const ConnectionWeak_ptr connection; - uint32_t key[4]; - bool encryptionEnabled; - bool rawMessages; + 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(xtea::key key) { + this->key = std::move(key); + } + void disableChecksum() { + checksumEnabled = false; + } + + static bool RSA_decrypt(NetworkMessage& msg); + + void setRawMessages(bool value) { + rawMessages = value; + } + + virtual void release() {} + + private: + void XTEA_encrypt(OutputMessage& msg) const; + bool XTEA_decrypt(NetworkMessage& msg) const; + + friend class Connection; + + OutputMessage_ptr outputBuffer; + + const ConnectionWeak_ptr connection; + xtea::key key; + bool encryptionEnabled = false; + bool checksumEnabled = true; + bool rawMessages = false; }; #endif diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp index faa9e5b..b979d99 100644 --- a/src/protocolgame.cpp +++ b/src/protocolgame.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,6 +31,7 @@ #include "actions.h" #include "game.h" #include "iologindata.h" +#include "iomarket.h" #include "waitlist.h" #include "ban.h" #include "scheduler.h" @@ -107,8 +108,9 @@ void ProtocolGame::login(const std::string& name, uint32_t accountId, OperatingS } } - if (!WaitingList::getInstance()->clientLogin(player)) { - uint32_t currentSlot = WaitingList::getInstance()->getClientSlot(player); + WaitingList& waitingList = WaitingList::getInstance(); + if (!waitingList.clientLogin(player)) { + uint32_t currentSlot = waitingList.getClientSlot(player); uint32_t retryTime = WaitingList::getTime(currentSlot); std::ostringstream ss; @@ -124,7 +126,7 @@ void ProtocolGame::login(const std::string& name, uint32_t accountId, OperatingS return; } - if (!IOLoginData::loadPlayerByName(player, name)) { + if (!IOLoginData::loadPlayerById(player, player->getGUID())) { disconnectClient("Your character could not be loaded."); return; } @@ -184,6 +186,7 @@ void ProtocolGame::connect(uint32_t playerId, OperatingSystem_t operatingSystem) player->incrementReferenceCounter(); g_chat->removeUserFromAllChannels(*player); + player->clearModalWindows(); player->setOperatingSystem(operatingSystem); player->isConnecting = false; @@ -209,7 +212,7 @@ void ProtocolGame::logout(bool displayEffect, bool forced) return; } - if (player->hasCondition(CONDITION_INFIGHT)) { + if (!player->getTile()->hasFlag(TILESTATE_PROTECTIONZONE) && player->hasCondition(CONDITION_INFIGHT)) { player->sendCancelMessage(RETURNVALUE_YOUMAYNOTLOGOUTDURINGAFIGHT); return; } @@ -242,18 +245,20 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg) OperatingSystem_t operatingSystem = static_cast(msg.get()); version = msg.get(); + msg.skipBytes(7); // U32 client version, U8 client type, U16 dat revision + if (!Protocol::RSA_decrypt(msg)) { disconnect(); return; } - uint32_t key[4]; + xtea::key key; key[0] = msg.get(); key[1] = msg.get(); key[2] = msg.get(); key[3] = msg.get(); enableXTEAEncryption(); - setXTEAKey(key); + setXTEAKey(std::move(key)); if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) { NetworkMessage opcodeMessage; @@ -265,15 +270,48 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg) msg.skipBytes(1); // gamemaster flag - uint32_t accountNumber = msg.get(); - std::string characterName = msg.getString(); - std::string password = msg.getString(); + std::string sessionKey = msg.getString(); - /*if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) { - //sendUpdateRequest(); - disconnectClient("Use Tibia 7.72 to login!"); + auto sessionArgs = explodeString(sessionKey, "\n", 4); + if (sessionArgs.size() != 4) { + disconnect(); return; - }*/ + } + + std::string& accountName = sessionArgs[0]; + std::string& password = sessionArgs[1]; + std::string& token = sessionArgs[2]; + uint32_t tokenTime = 0; + try { + tokenTime = std::stoul(sessionArgs[3]); + } catch (const std::invalid_argument&) { + disconnectClient("Malformed token packet."); + return; + } catch (const std::out_of_range&) { + disconnectClient("Token time is too long."); + return; + } + + if (accountName.empty()) { + disconnectClient("You must enter your account name."); + return; + } + + std::string characterName = msg.getString(); + + uint32_t timeStamp = msg.get(); + uint8_t randNumber = msg.getByte(); + if (challengeTimestamp != timeStamp || challengeRandom != randNumber) { + disconnect(); + return; + } + + if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) { + std::ostringstream ss; + ss << "Only clients with protocol " << CLIENT_VERSION_STR << " allowed!"; + disconnectClient(ss.str()); + return; + } if (g_game.getGameState() == GAME_STATE_STARTUP) { disconnectClient("Gameworld is starting up. Please wait."); @@ -297,29 +335,41 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg) return; } - uint32_t accountId = IOLoginData::gameworldAuthentication(accountNumber, password, characterName); + uint32_t accountId = IOLoginData::gameworldAuthentication(accountName, password, characterName, token, tokenTime); if (accountId == 0) { - disconnectClient("Account number or password is not correct."); + disconnectClient("Account name or password is not correct."); return; } - Account account; - if (!IOLoginData::loginserverAuthentication(accountNumber, password, account)) { - disconnectClient("Account number or password is not correct."); - return; - } - - //Update premium days - Game::updatePremium(account); - g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::login, getThis(), characterName, accountId, operatingSystem))); - } void ProtocolGame::onConnect() { + auto output = OutputMessagePool::getOutputMessage(); + static std::random_device rd; + static std::ranlux24 generator(rd()); + static std::uniform_int_distribution randNumber(0x00, 0xFF); + // Skip checksum + output->skipBytes(sizeof(uint32_t)); + // Packet length & type + output->add(0x0006); + output->addByte(0x1F); + + // Add timestamp & random number + challengeTimestamp = static_cast(time(nullptr)); + output->add(challengeTimestamp); + + challengeRandom = randNumber(generator); + output->addByte(challengeRandom); + + // Go back and write checksum + output->skipBytes(-12); + output->add(adlerChecksum(output->getOutputBuffer() + sizeof(uint32_t), 8)); + + send(output); } void ProtocolGame::disconnectClient(const std::string& message) const @@ -384,7 +434,12 @@ void ProtocolGame::parsePacket(NetworkMessage& msg) case 0x70: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_EAST); break; case 0x71: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_SOUTH); break; case 0x72: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_WEST); break; + case 0x77: parseEquipObject(msg); break; case 0x78: parseThrow(msg); break; + case 0x79: parseLookInShop(msg); break; + case 0x7A: parsePlayerPurchase(msg); break; + case 0x7B: parsePlayerSale(msg); break; + case 0x7C: addGameTask(&Game::playerCloseShop, player->getID()); break; case 0x7D: parseRequestTrade(msg); break; case 0x7E: parseLookInTrade(msg); break; case 0x7F: addGameTask(&Game::playerAcceptTrade, player->getID()); break; @@ -399,14 +454,13 @@ void ProtocolGame::parsePacket(NetworkMessage& msg) case 0x8A: parseHouseWindow(msg); break; case 0x8C: parseLookAt(msg); break; case 0x8D: parseLookInBattleList(msg); break; + case 0x8E: /* join aggression */ break; case 0x96: parseSay(msg); break; case 0x97: addGameTask(&Game::playerRequestChannels, player->getID()); break; case 0x98: parseOpenChannel(msg); break; case 0x99: parseCloseChannel(msg); break; case 0x9A: parseOpenPrivateChannel(msg); break; - case 0x9B: parseProcessRuleViolationReport(msg); break; - case 0x9C: parseCloseRuleViolationReport(msg); break; - case 0x9D: addGameTask(&Game::playerCancelRuleViolationReport, player->getID()); break; + case 0x9E: addGameTask(&Game::playerCloseNpcChannel, player->getID()); break; case 0xA0: parseFightModes(msg); break; case 0xA1: parseAttack(msg); break; case 0xA2: parseFollow(msg); break; @@ -415,22 +469,37 @@ void ProtocolGame::parsePacket(NetworkMessage& msg) case 0xA5: parseRevokePartyInvite(msg); break; case 0xA6: parsePassPartyLeadership(msg); break; case 0xA7: addGameTask(&Game::playerLeaveParty, player->getID()); break; + case 0xA8: parseEnableSharedPartyExperience(msg); break; case 0xAA: addGameTask(&Game::playerCreatePrivateChannel, player->getID()); break; case 0xAB: parseChannelInvite(msg); break; case 0xAC: parseChannelExclude(msg); break; case 0xBE: addGameTask(&Game::playerCancelAttackAndFollow, player->getID()); break; case 0xC9: /* update tile */ break; case 0xCA: parseUpdateContainer(msg); break; + case 0xCB: parseBrowseField(msg); break; case 0xCC: parseSeekInContainer(msg); break; case 0xD2: addGameTask(&Game::playerRequestOutfit, player->getID()); break; case 0xD3: parseSetOutfit(msg); break; + case 0xD4: parseToggleMount(msg); break; case 0xDC: parseAddVip(msg); break; case 0xDD: parseRemoveVip(msg); break; + case 0xDE: parseEditVip(msg); break; case 0xE6: parseBugReport(msg); break; - case 0xE7: /* violation window */ break; + case 0xE7: /* thank you */ break; case 0xE8: parseDebugAssert(msg); break; + case 0xF0: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerShowQuestLog, player->getID()); break; + case 0xF1: parseQuestLine(msg); break; + case 0xF2: parseRuleViolationReport(msg); break; + case 0xF3: /* get object info */ break; + case 0xF4: parseMarketLeave(); break; + case 0xF5: parseMarketBrowse(msg); break; + case 0xF6: parseMarketCreateOffer(msg); break; + case 0xF7: parseMarketCancelOffer(msg); break; + case 0xF8: parseMarketAcceptOffer(msg); break; + case 0xF9: parseModalWindowAnswer(msg); break; + default: - std::cout << "Player: " << player->getName() << " sent an unknown packet header: 0x" << std::hex << static_cast(recvbyte) << std::dec << "!" << std::endl; + // std::cout << "Player: " << player->getName() << " sent an unknown packet header: 0x" << std::hex << static_cast(recvbyte) << std::dec << "!" << std::endl; break; } @@ -441,13 +510,14 @@ void ProtocolGame::parsePacket(NetworkMessage& msg) void ProtocolGame::GetTileDescription(const Tile* tile, NetworkMessage& msg) { + msg.add(0x00); //environmental effects + int32_t count; Item* ground = tile->getGround(); if (ground) { msg.addItem(ground); count = 1; - } - else { + } else { count = 0; } @@ -459,8 +529,7 @@ void ProtocolGame::GetTileDescription(const Tile* tile, NetworkMessage& msg) count++; if (count == 9 && tile->getPosition() == player->getPosition()) { break; - } - else if (count == 10) { + } else if (count == 10) { return; } } @@ -563,7 +632,7 @@ void ProtocolGame::checkCreatureAsKnown(uint32_t id, bool& known, uint32_t& remo known = false; - if (knownCreatureSet.size() > 150) { + if (knownCreatureSet.size() > 1300) { // Look for a creature to remove for (auto it = knownCreatureSet.begin(), end = knownCreatureSet.end(); it != end; ++it) { Creature* creature = g_game.getCreatureByID(*it); @@ -669,7 +738,7 @@ void ProtocolGame::parseOpenPrivateChannel(NetworkMessage& msg) void ProtocolGame::parseAutoWalk(NetworkMessage& msg) { uint8_t numdirs = msg.getByte(); - if (numdirs == 0 || (msg.getBufferPosition() + numdirs) != (msg.getLength() + 4)) { + if (numdirs == 0 || (msg.getBufferPosition() + numdirs) != (msg.getLength() + 8)) { return; } @@ -707,9 +776,16 @@ void ProtocolGame::parseSetOutfit(NetworkMessage& msg) newOutfit.lookLegs = msg.getByte(); newOutfit.lookFeet = msg.getByte(); newOutfit.lookAddons = msg.getByte(); + newOutfit.lookMount = msg.get(); addGameTask(&Game::playerChangeOutfit, player->getID(), newOutfit); } +void ProtocolGame::parseToggleMount(NetworkMessage& msg) +{ + bool mount = msg.getByte() != 0; + addGameTask(&Game::playerToggleMount, player->getID(), mount); +} + void ProtocolGame::parseUseItem(NetworkMessage& msg) { Position pos = msg.getPosition(); @@ -791,16 +867,14 @@ void ProtocolGame::parseSay(NetworkMessage& msg) SpeakClasses type = static_cast(msg.getByte()); switch (type) { - case TALKTYPE_PRIVATE: - case TALKTYPE_PRIVATE_RED: - case TALKTYPE_RVR_ANSWER: + case TALKTYPE_PRIVATE_TO: + case TALKTYPE_PRIVATE_RED_TO: receiver = msg.getString(); channelId = 0; break; case TALKTYPE_CHANNEL_Y: case TALKTYPE_CHANNEL_R1: - case TALKTYPE_CHANNEL_R2: channelId = msg.get(); break; @@ -810,7 +884,7 @@ void ProtocolGame::parseSay(NetworkMessage& msg) } const std::string text = msg.getString(); - if (text.length() > 255 || text.find('\n') != std::string::npos) { + if (text.length() > 255) { return; } @@ -824,13 +898,6 @@ void ProtocolGame::parseFightModes(NetworkMessage& msg) uint8_t rawSecureMode = msg.getByte(); // 0 - can't attack unmarked, 1 - can attack unmarked // uint8_t rawPvpMode = msg.getByte(); // pvp mode introduced in 10.0 - chaseMode_t chaseMode; - if (rawChaseMode == 1) { - chaseMode = CHASEMODE_FOLLOW; - } else { - chaseMode = CHASEMODE_STANDSTILL; - } - fightMode_t fightMode; if (rawFightMode == 1) { fightMode = FIGHTMODE_ATTACK; @@ -840,31 +907,29 @@ void ProtocolGame::parseFightModes(NetworkMessage& msg) fightMode = FIGHTMODE_DEFENSE; } - addGameTask(&Game::playerSetFightModes, player->getID(), fightMode, chaseMode, rawSecureMode != 0); + addGameTask(&Game::playerSetFightModes, player->getID(), fightMode, rawChaseMode != 0, rawSecureMode != 0); } void ProtocolGame::parseAttack(NetworkMessage& msg) { uint32_t creatureId = msg.get(); + // msg.get(); creatureId (same as above) addGameTask(&Game::playerSetAttackedCreature, player->getID(), creatureId); } void ProtocolGame::parseFollow(NetworkMessage& msg) { uint32_t creatureId = msg.get(); + // msg.get(); creatureId (same as above) addGameTask(&Game::playerFollowCreature, player->getID(), creatureId); } -void ProtocolGame::parseProcessRuleViolationReport(NetworkMessage& msg) +void ProtocolGame::parseEquipObject(NetworkMessage& msg) { - const std::string reporter = msg.getString(); - addGameTask(&Game::playerProcessRuleViolationReport, player->getID(), reporter); -} + uint16_t spriteId = msg.get(); + // msg.get(); -void ProtocolGame::parseCloseRuleViolationReport(NetworkMessage& msg) -{ - const std::string reporter = msg.getString(); - addGameTask(&Game::playerCloseRuleViolationReport, player->getID(), reporter); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerEquipItem, player->getID(), spriteId); } void ProtocolGame::parseTextWindow(NetworkMessage& msg) @@ -882,6 +947,32 @@ void ProtocolGame::parseHouseWindow(NetworkMessage& msg) addGameTask(&Game::playerUpdateHouseWindow, player->getID(), doorId, id, text); } +void ProtocolGame::parseLookInShop(NetworkMessage& msg) +{ + uint16_t id = msg.get(); + uint8_t count = msg.getByte(); + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInShop, player->getID(), id, count); +} + +void ProtocolGame::parsePlayerPurchase(NetworkMessage& msg) +{ + uint16_t id = msg.get(); + uint8_t count = msg.getByte(); + uint8_t amount = msg.getByte(); + bool ignoreCap = msg.getByte() != 0; + bool inBackpacks = msg.getByte() != 0; + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerPurchaseItem, player->getID(), id, count, amount, ignoreCap, inBackpacks); +} + +void ProtocolGame::parsePlayerSale(NetworkMessage& msg) +{ + uint16_t id = msg.get(); + uint8_t count = msg.getByte(); + uint8_t amount = msg.getByte(); + bool ignoreEquipped = msg.getByte() != 0; + addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerSellItem, player->getID(), id, count, amount, ignoreEquipped); +} + void ProtocolGame::parseRequestTrade(NetworkMessage& msg) { Position pos = msg.getPosition(); @@ -910,6 +1001,15 @@ void ProtocolGame::parseRemoveVip(NetworkMessage& msg) addGameTask(&Game::playerRequestRemoveVip, player->getID(), guid); } +void ProtocolGame::parseEditVip(NetworkMessage& msg) +{ + uint32_t guid = msg.get(); + const std::string description = msg.getString(); + uint32_t icon = std::min(10, msg.get()); // 10 is max icon in 9.63 + bool notify = msg.getByte() != 0; + addGameTask(&Game::playerRequestEditVip, player->getID(), guid, description, icon, notify); +} + void ProtocolGame::parseRotateItem(NetworkMessage& msg) { Position pos = msg.getPosition(); @@ -918,10 +1018,34 @@ void ProtocolGame::parseRotateItem(NetworkMessage& msg) addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerRotateItem, player->getID(), pos, stackpos, spriteId); } +void ProtocolGame::parseRuleViolationReport(NetworkMessage& msg) +{ + uint8_t reportType = msg.getByte(); + uint8_t reportReason = msg.getByte(); + const std::string& targetName = msg.getString(); + const std::string& comment = msg.getString(); + std::string translation; + if (reportType == REPORT_TYPE_NAME) { + translation = msg.getString(); + } else if (reportType == REPORT_TYPE_STATEMENT) { + translation = msg.getString(); + msg.get(); // statement id, used to get whatever player have said, we don't log that. + } + + addGameTask(&Game::playerReportRuleViolation, player->getID(), targetName, reportType, reportReason, comment, translation); +} + void ProtocolGame::parseBugReport(NetworkMessage& msg) { - std::string bug = msg.getString(); - addGameTask(&Game::playerReportBug, player->getID(), bug); + uint8_t category = msg.getByte(); + std::string message = msg.getString(); + + Position position; + if (category == BUG_CATEGORY_MAP) { + position = msg.getPosition(); + } + + addGameTask(&Game::playerReportBug, player->getID(), message, position, category); } void ProtocolGame::parseDebugAssert(NetworkMessage& msg) @@ -963,6 +1087,75 @@ void ProtocolGame::parsePassPartyLeadership(NetworkMessage& msg) addGameTask(&Game::playerPassPartyLeadership, player->getID(), targetId); } +void ProtocolGame::parseEnableSharedPartyExperience(NetworkMessage& msg) +{ + bool sharedExpActive = msg.getByte() == 1; + addGameTask(&Game::playerEnableSharedPartyExperience, player->getID(), sharedExpActive); +} + +void ProtocolGame::parseQuestLine(NetworkMessage& msg) +{ + uint16_t questId = msg.get(); + addGameTask(&Game::playerShowQuestLine, player->getID(), questId); +} + +void ProtocolGame::parseMarketLeave() +{ + addGameTask(&Game::playerLeaveMarket, player->getID()); +} + +void ProtocolGame::parseMarketBrowse(NetworkMessage& msg) +{ + uint16_t browseId = msg.get(); + + if (browseId == MARKETREQUEST_OWN_OFFERS) { + addGameTask(&Game::playerBrowseMarketOwnOffers, player->getID()); + } else if (browseId == MARKETREQUEST_OWN_HISTORY) { + addGameTask(&Game::playerBrowseMarketOwnHistory, player->getID()); + } else { + addGameTask(&Game::playerBrowseMarket, player->getID(), browseId); + } +} + +void ProtocolGame::parseMarketCreateOffer(NetworkMessage& msg) +{ + uint8_t type = msg.getByte(); + uint16_t spriteId = msg.get(); + uint16_t amount = msg.get(); + uint32_t price = msg.get(); + bool anonymous = (msg.getByte() != 0); + addGameTask(&Game::playerCreateMarketOffer, player->getID(), type, spriteId, amount, price, anonymous); +} + +void ProtocolGame::parseMarketCancelOffer(NetworkMessage& msg) +{ + uint32_t timestamp = msg.get(); + uint16_t counter = msg.get(); + addGameTask(&Game::playerCancelMarketOffer, player->getID(), timestamp, counter); +} + +void ProtocolGame::parseMarketAcceptOffer(NetworkMessage& msg) +{ + uint32_t timestamp = msg.get(); + uint16_t counter = msg.get(); + uint16_t amount = msg.get(); + addGameTask(&Game::playerAcceptMarketOffer, player->getID(), timestamp, counter, amount); +} + +void ProtocolGame::parseModalWindowAnswer(NetworkMessage& msg) +{ + uint32_t id = msg.get(); + uint8_t button = msg.getByte(); + uint8_t choice = msg.getByte(); + addGameTask(&Game::playerAnswerModalWindow, player->getID(), id, button, choice); +} + +void ProtocolGame::parseBrowseField(NetworkMessage& msg) +{ + const Position& pos = msg.getPosition(); + addGameTask(&Game::playerBrowseField, player->getID(), pos); +} + void ProtocolGame::parseSeekInContainer(NetworkMessage& msg) { uint8_t containerId = msg.getByte(); @@ -979,6 +1172,16 @@ void ProtocolGame::sendOpenPrivateChannel(const std::string& receiver) writeToOutputBuffer(msg); } +void ProtocolGame::sendChannelEvent(uint16_t channelId, const std::string& playerName, ChannelEvent_t channelEvent) +{ + NetworkMessage msg; + msg.addByte(0xF3); + msg.add(channelId); + msg.addString(playerName); + msg.addByte(channelEvent); + writeToOutputBuffer(msg); +} + void ProtocolGame::sendCreatureOutfit(const Creature* creature, const Outfit_t& outfit) { if (!canSee(creature)) { @@ -1003,13 +1206,26 @@ void ProtocolGame::sendCreatureLight(const Creature* creature) writeToOutputBuffer(msg); } -void ProtocolGame::sendWorldLight(const LightInfo& lightInfo) +void ProtocolGame::sendWorldLight(LightInfo lightInfo) { NetworkMessage msg; AddWorldLight(msg, lightInfo); writeToOutputBuffer(msg); } +void ProtocolGame::sendCreatureWalkthrough(const Creature* creature, bool walkthrough) +{ + if (!canSee(creature)) { + return; + } + + NetworkMessage msg; + msg.addByte(0x92); + msg.add(creature->getID()); + msg.addByte(walkthrough ? 0x00 : 0x01); + writeToOutputBuffer(msg); +} + void ProtocolGame::sendCreatureShield(const Creature* creature) { if (!canSee(creature)) { @@ -1040,6 +1256,24 @@ void ProtocolGame::sendCreatureSkull(const Creature* creature) writeToOutputBuffer(msg); } +void ProtocolGame::sendCreatureType(uint32_t creatureId, uint8_t creatureType) +{ + NetworkMessage msg; + msg.addByte(0x95); + msg.add(creatureId); + msg.addByte(creatureType); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCreatureHelpers(uint32_t creatureId, uint16_t helpers) +{ + NetworkMessage msg; + msg.addByte(0x94); + msg.add(creatureId); + msg.add(helpers); + writeToOutputBuffer(msg); +} + void ProtocolGame::sendCreatureSquare(const Creature* creature, SquareColor_t color) { if (!canSee(creature)) { @@ -1047,55 +1281,37 @@ void ProtocolGame::sendCreatureSquare(const Creature* creature, SquareColor_t co } NetworkMessage msg; - msg.addByte(0x86); + msg.addByte(0x93); msg.add(creature->getID()); + msg.addByte(0x01); msg.addByte(color); writeToOutputBuffer(msg); } -void ProtocolGame::sendRemoveRuleViolationReport(const std::string& name) +void ProtocolGame::sendTutorial(uint8_t tutorialId) { NetworkMessage msg; - msg.addByte(0xAF); - msg.addString(name); + msg.addByte(0xDC); + msg.addByte(tutorialId); writeToOutputBuffer(msg); } -void ProtocolGame::sendLockRuleViolation() +void ProtocolGame::sendAddMarker(const Position& pos, uint8_t markType, const std::string& desc) { NetworkMessage msg; - msg.addByte(0xB1); + msg.addByte(0xDD); + msg.addPosition(pos); + msg.addByte(markType); + msg.addString(desc); writeToOutputBuffer(msg); } -void ProtocolGame::sendRuleViolationCancel(const std::string& name) +void ProtocolGame::sendReLoginWindow(uint8_t unfairFightReduction) { NetworkMessage msg; - msg.addByte(0xB0); - msg.addString(name); - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendRuleViolationsChannel(uint16_t channelId) -{ - NetworkMessage msg; - msg.addByte(0xAE); - msg.add(channelId); - auto it = g_game.getRuleViolationReports().begin(); - for (; it != g_game.getRuleViolationReports().end(); ++it) { - const RuleViolation& rvr = it->second; - if (rvr.pending) { - Player* reporter = g_game.getPlayerByID(rvr.reporterId); - if (reporter) { - msg.addByte(0xAA); - msg.add(0); - msg.addString(reporter->getName()); - msg.addByte(TALKTYPE_RVR_CHANNEL); - msg.add(0); - msg.addString(rvr.text); - } - } - } + msg.addByte(0x28); + msg.addByte(0x00); + msg.addByte(unfairFightReduction); writeToOutputBuffer(msg); } @@ -1106,25 +1322,63 @@ void ProtocolGame::sendStats() writeToOutputBuffer(msg); } +void ProtocolGame::sendBasicData() +{ + NetworkMessage msg; + msg.addByte(0x9F); + if (player->isPremium()) { + msg.addByte(1); + msg.add(time(nullptr) + (player->premiumDays * 86400)); + } else { + msg.addByte(0); + msg.add(0); + } + msg.addByte(player->getVocation()->getClientId()); + msg.add(0xFF); // number of known spells + for (uint8_t spellId = 0x00; spellId < 0xFF; spellId++) { + msg.addByte(spellId); + } + writeToOutputBuffer(msg); +} + void ProtocolGame::sendTextMessage(const TextMessage& message) { NetworkMessage msg; msg.addByte(0xB4); msg.addByte(message.type); + switch (message.type) { + case MESSAGE_DAMAGE_DEALT: + case MESSAGE_DAMAGE_RECEIVED: + case MESSAGE_DAMAGE_OTHERS: { + msg.addPosition(message.position); + msg.add(message.primary.value); + msg.addByte(message.primary.color); + msg.add(message.secondary.value); + msg.addByte(message.secondary.color); + break; + } + case MESSAGE_HEALED: + case MESSAGE_HEALED_OTHERS: + case MESSAGE_EXPERIENCE: + case MESSAGE_EXPERIENCE_OTHERS: { + msg.addPosition(message.position); + msg.add(message.primary.value); + msg.addByte(message.primary.color); + break; + } + case MESSAGE_GUILD: + case MESSAGE_PARTY_MANAGEMENT: + case MESSAGE_PARTY: + msg.add(message.channelId); + break; + default: { + break; + } + } msg.addString(message.text); writeToOutputBuffer(msg); } -void ProtocolGame::sendAnimatedText(const Position& pos, uint8_t color, const std::string& text) -{ - NetworkMessage msg; - msg.addByte(0x84); - msg.addPosition(pos); - msg.addByte(color); - msg.addString(text); - writeToOutputBuffer(msg); -} - void ProtocolGame::sendClosePrivate(uint16_t channelId) { NetworkMessage msg; @@ -1139,6 +1393,9 @@ void ProtocolGame::sendCreatePrivateChannel(uint16_t channelId, const std::strin msg.addByte(0xB2); msg.add(channelId); msg.addString(channelName); + msg.add(0x01); + msg.addString(player->getName()); + msg.add(0x00); writeToOutputBuffer(msg); } @@ -1157,7 +1414,7 @@ void ProtocolGame::sendChannelsDialog() writeToOutputBuffer(msg); } -void ProtocolGame::sendChannel(uint16_t channelId, const std::string& channelName) +void ProtocolGame::sendChannel(uint16_t channelId, const std::string& channelName, const UsersMap* channelUsers, const InvitedMap* invitedUsers) { NetworkMessage msg; msg.addByte(0xAC); @@ -1165,9 +1422,38 @@ void ProtocolGame::sendChannel(uint16_t channelId, const std::string& channelNam msg.add(channelId); msg.addString(channelName); + if (channelUsers) { + msg.add(channelUsers->size()); + for (const auto& it : *channelUsers) { + msg.addString(it.second->getName()); + } + } else { + msg.add(0x00); + } + + if (invitedUsers) { + msg.add(invitedUsers->size()); + for (const auto& it : *invitedUsers) { + msg.addString(it.second->getName()); + } + } else { + msg.add(0x00); + } writeToOutputBuffer(msg); } +void ProtocolGame::sendChannelMessage(const std::string& author, const std::string& text, SpeakClasses type, uint16_t channel) +{ + NetworkMessage msg; + msg.addByte(0xAA); + msg.add(0x00); + msg.addString(author); + msg.add(0x00); + msg.addByte(type); + msg.add(channel); + msg.addString(text); + writeToOutputBuffer(msg); +} void ProtocolGame::sendIcons(uint16_t icons) { @@ -1184,14 +1470,24 @@ void ProtocolGame::sendContainer(uint8_t cid, const Container* container, bool h msg.addByte(cid); - msg.addItem(container); - msg.addString(container->getName()); + if (container->getID() == ITEM_BROWSEFIELD) { + msg.addItem(1987, 1); + msg.addString("Browse Field"); + } else { + msg.addItem(container); + msg.addString(container->getName()); + } msg.addByte(container->capacity()); msg.addByte(hasParent ? 0x01 : 0x00); + msg.addByte(container->isUnlocked() ? 0x01 : 0x00); // Drag and drop + msg.addByte(container->hasPagination() ? 0x01 : 0x00); // Pagination + uint32_t containerSize = container->size(); + msg.add(containerSize); + msg.add(firstIndex); if (firstIndex < containerSize) { uint8_t itemsToSend = std::min(std::min(container->capacity(), containerSize - firstIndex), std::numeric_limits::max()); @@ -1205,7 +1501,562 @@ void ProtocolGame::sendContainer(uint8_t cid, const Container* container, bool h writeToOutputBuffer(msg); } +void ProtocolGame::sendShop(Npc* npc, const ShopInfoList& itemList) +{ + NetworkMessage msg; + msg.addByte(0x7A); + msg.addString(npc->getName()); + uint16_t itemsToSend = std::min(itemList.size(), std::numeric_limits::max()); + msg.add(itemsToSend); + + uint16_t i = 0; + for (auto it = itemList.begin(); i < itemsToSend; ++it, ++i) { + AddShopItem(msg, *it); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendCloseShop() +{ + NetworkMessage msg; + msg.addByte(0x7C); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendSaleItemList(const std::list& shop) +{ + NetworkMessage msg; + msg.addByte(0x7B); + msg.add(player->getMoney() + player->getBankBalance()); + + std::map saleMap; + + if (shop.size() <= 5) { + // For very small shops it's not worth it to create the complete map + for (const ShopInfo& shopInfo : shop) { + if (shopInfo.sellPrice == 0) { + continue; + } + + int8_t subtype = -1; + + const ItemType& itemType = Item::items[shopInfo.itemId]; + if (itemType.hasSubType() && !itemType.stackable) { + subtype = (shopInfo.subType == 0 ? -1 : shopInfo.subType); + } + + uint32_t count = player->getItemTypeCount(shopInfo.itemId, subtype); + if (count > 0) { + saleMap[shopInfo.itemId] = count; + } + } + } else { + // Large shop, it's better to get a cached map of all item counts and use it + // We need a temporary map since the finished map should only contain items + // available in the shop + std::map tempSaleMap; + player->getAllItemTypeCount(tempSaleMap); + + // We must still check manually for the special items that require subtype matches + // (That is, fluids such as potions etc., actually these items are very few since + // health potions now use their own ID) + for (const ShopInfo& shopInfo : shop) { + if (shopInfo.sellPrice == 0) { + continue; + } + + int8_t subtype = -1; + + const ItemType& itemType = Item::items[shopInfo.itemId]; + if (itemType.hasSubType() && !itemType.stackable) { + subtype = (shopInfo.subType == 0 ? -1 : shopInfo.subType); + } + + if (subtype != -1) { + uint32_t count; + if (!itemType.isFluidContainer() && !itemType.isSplash()) { + count = player->getItemTypeCount(shopInfo.itemId, subtype); // This shop item requires extra checks + } else { + count = subtype; + } + + if (count > 0) { + saleMap[shopInfo.itemId] = count; + } + } else { + std::map::const_iterator findIt = tempSaleMap.find(shopInfo.itemId); + if (findIt != tempSaleMap.end() && findIt->second > 0) { + saleMap[shopInfo.itemId] = findIt->second; + } + } + } + } + + uint8_t itemsToSend = std::min(saleMap.size(), std::numeric_limits::max()); + msg.addByte(itemsToSend); + + uint8_t i = 0; + for (std::map::const_iterator it = saleMap.begin(); i < itemsToSend; ++it, ++i) { + msg.addItemId(it->first); + msg.addByte(std::min(it->second, std::numeric_limits::max())); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendMarketEnter(uint32_t depotId) +{ + NetworkMessage msg; + msg.addByte(0xF6); + + msg.add(player->getBankBalance()); + msg.addByte(std::min(IOMarket::getPlayerOfferCount(player->getGUID()), std::numeric_limits::max())); + + DepotChest* depotChest = player->getDepotChest(depotId, false); + if (!depotChest) { + msg.add(0x00); + writeToOutputBuffer(msg); + return; + } + + player->setInMarket(true); + + std::map depotItems; + std::forward_list containerList { depotChest, player->getInbox() }; + + do { + Container* container = containerList.front(); + containerList.pop_front(); + + for (Item* item : container->getItemList()) { + Container* c = item->getContainer(); + if (c && !c->empty()) { + containerList.push_front(c); + continue; + } + + const ItemType& itemType = Item::items[item->getID()]; + if (itemType.wareId == 0) { + continue; + } + + if (c && (!itemType.isContainer() || c->capacity() != itemType.maxItems)) { + continue; + } + + if (!item->hasMarketAttributes()) { + continue; + } + + depotItems[itemType.wareId] += Item::countByType(item, -1); + } + } while (!containerList.empty()); + + uint16_t itemsToSend = std::min(depotItems.size(), std::numeric_limits::max()); + msg.add(itemsToSend); + + uint16_t i = 0; + for (std::map::const_iterator it = depotItems.begin(); i < itemsToSend; ++it, ++i) { + msg.add(it->first); + msg.add(std::min(0xFFFF, it->second)); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendMarketLeave() +{ + NetworkMessage msg; + msg.addByte(0xF7); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& buyOffers, const MarketOfferList& sellOffers) +{ + NetworkMessage msg; + + msg.addByte(0xF9); + msg.addItemId(itemId); + + msg.add(buyOffers.size()); + for (const MarketOffer& offer : buyOffers) { + msg.add(offer.timestamp); + msg.add(offer.counter); + msg.add(offer.amount); + msg.add(offer.price); + msg.addString(offer.playerName); + } + + msg.add(sellOffers.size()); + for (const MarketOffer& offer : sellOffers) { + msg.add(offer.timestamp); + msg.add(offer.counter); + msg.add(offer.amount); + msg.add(offer.price); + msg.addString(offer.playerName); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendMarketAcceptOffer(const MarketOfferEx& offer) +{ + NetworkMessage msg; + msg.addByte(0xF9); + msg.addItemId(offer.itemId); + + if (offer.type == MARKETACTION_BUY) { + msg.add(0x01); + msg.add(offer.timestamp); + msg.add(offer.counter); + msg.add(offer.amount); + msg.add(offer.price); + msg.addString(offer.playerName); + msg.add(0x00); + } else { + msg.add(0x00); + msg.add(0x01); + msg.add(offer.timestamp); + msg.add(offer.counter); + msg.add(offer.amount); + msg.add(offer.price); + msg.addString(offer.playerName); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendMarketBrowseOwnOffers(const MarketOfferList& buyOffers, const MarketOfferList& sellOffers) +{ + NetworkMessage msg; + msg.addByte(0xF9); + msg.add(MARKETREQUEST_OWN_OFFERS); + + msg.add(buyOffers.size()); + for (const MarketOffer& offer : buyOffers) { + msg.add(offer.timestamp); + msg.add(offer.counter); + msg.addItemId(offer.itemId); + msg.add(offer.amount); + msg.add(offer.price); + } + + msg.add(sellOffers.size()); + for (const MarketOffer& offer : sellOffers) { + msg.add(offer.timestamp); + msg.add(offer.counter); + msg.addItemId(offer.itemId); + msg.add(offer.amount); + msg.add(offer.price); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendMarketCancelOffer(const MarketOfferEx& offer) +{ + NetworkMessage msg; + msg.addByte(0xF9); + msg.add(MARKETREQUEST_OWN_OFFERS); + + if (offer.type == MARKETACTION_BUY) { + msg.add(0x01); + msg.add(offer.timestamp); + msg.add(offer.counter); + msg.addItemId(offer.itemId); + msg.add(offer.amount); + msg.add(offer.price); + msg.add(0x00); + } else { + msg.add(0x00); + msg.add(0x01); + msg.add(offer.timestamp); + msg.add(offer.counter); + msg.addItemId(offer.itemId); + msg.add(offer.amount); + msg.add(offer.price); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendMarketBrowseOwnHistory(const HistoryMarketOfferList& buyOffers, const HistoryMarketOfferList& sellOffers) +{ + uint32_t i = 0; + std::map counterMap; + uint32_t buyOffersToSend = std::min(buyOffers.size(), 810 + std::max(0, 810 - sellOffers.size())); + uint32_t sellOffersToSend = std::min(sellOffers.size(), 810 + std::max(0, 810 - buyOffers.size())); + + NetworkMessage msg; + msg.addByte(0xF9); + msg.add(MARKETREQUEST_OWN_HISTORY); + + msg.add(buyOffersToSend); + for (auto it = buyOffers.begin(); i < buyOffersToSend; ++it, ++i) { + msg.add(it->timestamp); + msg.add(counterMap[it->timestamp]++); + msg.addItemId(it->itemId); + msg.add(it->amount); + msg.add(it->price); + msg.addByte(it->state); + } + + counterMap.clear(); + i = 0; + + msg.add(sellOffersToSend); + for (auto it = sellOffers.begin(); i < sellOffersToSend; ++it, ++i) { + msg.add(it->timestamp); + msg.add(counterMap[it->timestamp]++); + msg.addItemId(it->itemId); + msg.add(it->amount); + msg.add(it->price); + msg.addByte(it->state); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendMarketDetail(uint16_t itemId) +{ + NetworkMessage msg; + msg.addByte(0xF8); + msg.addItemId(itemId); + + const ItemType& it = Item::items[itemId]; + if (it.armor != 0) { + msg.addString(std::to_string(it.armor)); + } else { + msg.add(0x00); + } + + if (it.attack != 0) { + // TODO: chance to hit, range + // example: + // "attack +x, chance to hit +y%, z fields" + if (it.abilities && it.abilities->elementType != COMBAT_NONE && it.abilities->elementDamage != 0) { + std::ostringstream ss; + ss << it.attack << " physical +" << it.abilities->elementDamage << ' ' << getCombatName(it.abilities->elementType); + msg.addString(ss.str()); + } else { + msg.addString(std::to_string(it.attack)); + } + } else { + msg.add(0x00); + } + + if (it.isContainer()) { + msg.addString(std::to_string(it.maxItems)); + } else { + msg.add(0x00); + } + + if (it.defense != 0) { + if (it.extraDefense != 0) { + std::ostringstream ss; + ss << it.defense << ' ' << std::showpos << it.extraDefense << std::noshowpos; + msg.addString(ss.str()); + } else { + msg.addString(std::to_string(it.defense)); + } + } else { + msg.add(0x00); + } + + if (!it.description.empty()) { + const std::string& descr = it.description; + if (descr.back() == '.') { + msg.addString(std::string(descr, 0, descr.length() - 1)); + } else { + msg.addString(descr); + } + } else { + msg.add(0x00); + } + + if (it.decayTime != 0) { + std::ostringstream ss; + ss << it.decayTime << " seconds"; + msg.addString(ss.str()); + } else { + msg.add(0x00); + } + + if (it.abilities) { + std::ostringstream ss; + bool separator = false; + + for (size_t i = 0; i < COMBAT_COUNT; ++i) { + if (it.abilities->absorbPercent[i] == 0) { + continue; + } + + if (separator) { + ss << ", "; + } else { + separator = true; + } + + ss << getCombatName(indexToCombatType(i)) << ' ' << std::showpos << it.abilities->absorbPercent[i] << std::noshowpos << '%'; + } + + msg.addString(ss.str()); + } else { + msg.add(0x00); + } + + if (it.minReqLevel != 0) { + msg.addString(std::to_string(it.minReqLevel)); + } else { + msg.add(0x00); + } + + if (it.minReqMagicLevel != 0) { + msg.addString(std::to_string(it.minReqMagicLevel)); + } else { + msg.add(0x00); + } + + msg.addString(it.vocationString); + + msg.addString(it.runeSpellName); + + if (it.abilities) { + std::ostringstream ss; + bool separator = false; + + for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; i++) { + if (!it.abilities->skills[i]) { + continue; + } + + if (separator) { + ss << ", "; + } else { + separator = true; + } + + ss << getSkillName(i) << ' ' << std::showpos << it.abilities->skills[i] << std::noshowpos; + } + + if (it.abilities->stats[STAT_MAGICPOINTS] != 0) { + if (separator) { + ss << ", "; + } else { + separator = true; + } + + ss << "magic level " << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos; + } + + if (it.abilities->speed != 0) { + if (separator) { + ss << ", "; + } + + ss << "speed " << std::showpos << (it.abilities->speed >> 1) << std::noshowpos; + } + + msg.addString(ss.str()); + } else { + msg.add(0x00); + } + + if (it.charges != 0) { + msg.addString(std::to_string(it.charges)); + } else { + msg.add(0x00); + } + + std::string weaponName = getWeaponName(it.weaponType); + + if (it.slotPosition & SLOTP_TWO_HAND) { + if (!weaponName.empty()) { + weaponName += ", two-handed"; + } else { + weaponName = "two-handed"; + } + } + + msg.addString(weaponName); + + if (it.weight != 0) { + std::ostringstream ss; + if (it.weight < 10) { + ss << "0.0" << it.weight; + } else if (it.weight < 100) { + ss << "0." << it.weight; + } else { + std::string weightString = std::to_string(it.weight); + weightString.insert(weightString.end() - 2, '.'); + ss << weightString; + } + ss << " oz"; + msg.addString(ss.str()); + } else { + msg.add(0x00); + } + + MarketStatistics* statistics = IOMarket::getInstance().getPurchaseStatistics(itemId); + if (statistics) { + msg.addByte(0x01); + msg.add(statistics->numTransactions); + msg.add(std::min(std::numeric_limits::max(), statistics->totalPrice)); + msg.add(statistics->highestPrice); + msg.add(statistics->lowestPrice); + } else { + msg.addByte(0x00); + } + + statistics = IOMarket::getInstance().getSaleStatistics(itemId); + if (statistics) { + msg.addByte(0x01); + msg.add(statistics->numTransactions); + msg.add(std::min(std::numeric_limits::max(), statistics->totalPrice)); + msg.add(statistics->highestPrice); + msg.add(statistics->lowestPrice); + } else { + msg.addByte(0x00); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendQuestLog() +{ + NetworkMessage msg; + msg.addByte(0xF0); + msg.add(g_game.quests.getQuestsCount(player)); + + for (const Quest& quest : g_game.quests.getQuests()) { + if (quest.isStarted(player)) { + msg.add(quest.getID()); + msg.addString(quest.getName()); + msg.addByte(quest.isCompleted(player)); + } + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendQuestLine(const Quest* quest) +{ + NetworkMessage msg; + msg.addByte(0xF1); + msg.add(quest->getID()); + msg.addByte(quest->getMissionsCount(player)); + + for (const Mission& mission : quest->getMissions()) { + if (mission.isStarted(player)) { + msg.addString(mission.getName(player)); + msg.addString(mission.getDescription(player)); + } + } + + writeToOutputBuffer(msg); +} void ProtocolGame::sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack) { @@ -1274,6 +2125,7 @@ void ProtocolGame::sendCreatureTurn(const Creature* creature, uint32_t stackPos) msg.add(0x63); msg.add(creature->getID()); msg.addByte(creature->getDirection()); + msg.addByte(player->canWalkthroughEx(creature) ? 0x00 : 0x01); writeToOutputBuffer(msg); } @@ -1290,16 +2142,14 @@ void ProtocolGame::sendCreatureSay(const Creature* creature, SpeakClasses type, //Add level only for players if (const Player* speaker = creature->getPlayer()) { msg.add(speaker->getLevel()); - } - else { + } else { msg.add(0x00); } msg.addByte(type); if (pos) { msg.addPosition(*pos); - } - else { + } else { msg.addPosition(creature->getPosition()); } @@ -1316,18 +2166,15 @@ void ProtocolGame::sendToChannel(const Creature* creature, SpeakClasses type, co msg.add(++statementId); if (!creature) { msg.add(0x00); - } - else if (type == TALKTYPE_CHANNEL_R2) { + } else if (type == TALKTYPE_CHANNEL_R2) { msg.add(0x00); type = TALKTYPE_CHANNEL_R1; - } - else { + } else { msg.addString(creature->getName()); //Add level only for players if (const Player* speaker = creature->getPlayer()) { msg.add(speaker->getLevel()); - } - else { + } else { msg.add(0x00); } } @@ -1347,8 +2194,7 @@ void ProtocolGame::sendPrivateMessage(const Player* speaker, SpeakClasses type, if (speaker) { msg.addString(speaker->getName()); msg.add(speaker->getLevel()); - } - else { + } else { msg.add(0x00); } msg.addByte(type); @@ -1360,6 +2206,7 @@ void ProtocolGame::sendCancelTarget() { NetworkMessage msg; msg.addByte(0xA3); + msg.add(0x00); writeToOutputBuffer(msg); } @@ -1368,7 +2215,8 @@ void ProtocolGame::sendChangeSpeed(const Creature* creature, uint32_t speed) NetworkMessage msg; msg.addByte(0x8F); msg.add(creature->getID()); - msg.add(speed); + msg.add(creature->getBaseSpeed() / 2); + msg.add(speed / 2); writeToOutputBuffer(msg); } @@ -1390,12 +2238,7 @@ void ProtocolGame::sendSkills() void ProtocolGame::sendPing() { NetworkMessage msg; - if (player->getOperatingSystem() >= CLIENTOS_OTCLIENT_LINUX) { - msg.addByte(0x1D); - } else { - // classic clients ping - msg.addByte(0x1E); - } + msg.addByte(0x1D); writeToOutputBuffer(msg); } @@ -1437,11 +2280,17 @@ void ProtocolGame::sendCreatureHealth(const Creature* creature) if (creature->isHealthHidden()) { msg.addByte(0x00); - } - else { + } else { msg.addByte(std::ceil((static_cast(creature->getHealth()) / std::max(creature->getMaxHealth(), 1)) * 100)); } + writeToOutputBuffer(msg); +} +void ProtocolGame::sendFYIBox(const std::string& message) +{ + NetworkMessage msg; + msg.addByte(0x15); + msg.addString(message); writeToOutputBuffer(msg); } @@ -1455,7 +2304,7 @@ void ProtocolGame::sendMapDescription(const Position& pos) writeToOutputBuffer(msg); } -void ProtocolGame::sendAddTileItem(const Position& pos, const Item* item, uint32_t stackpos) +void ProtocolGame::sendAddTileItem(const Position& pos, uint32_t stackpos, const Item* item) { if (!canSee(pos)) { return; @@ -1464,9 +2313,7 @@ void ProtocolGame::sendAddTileItem(const Position& pos, const Item* item, uint32 NetworkMessage msg; msg.addByte(0x6A); msg.addPosition(pos); - if (player->getOperatingSystem() >= CLIENTOS_OTCLIENT_LINUX) { - msg.addByte(stackpos); - } + msg.addByte(stackpos); msg.addItem(item); writeToOutputBuffer(msg); } @@ -1518,6 +2365,20 @@ void ProtocolGame::sendUpdateTile(const Tile* tile, const Position& pos) writeToOutputBuffer(msg); } +void ProtocolGame::sendPendingStateEntered() +{ + NetworkMessage msg; + msg.addByte(0x0A); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendEnterWorld() +{ + NetworkMessage msg; + msg.addByte(0x0F); + writeToOutputBuffer(msg); +} + void ProtocolGame::sendFightModes() { NetworkMessage msg; @@ -1525,6 +2386,7 @@ void ProtocolGame::sendFightModes() msg.addByte(player->fightMode); msg.addByte(player->chaseMode); msg.addByte(player->secureMode); + msg.addByte(PVP_MODE_DOVE); writeToOutputBuffer(msg); } @@ -1539,10 +2401,7 @@ void ProtocolGame::sendAddCreature(const Creature* creature, const Position& pos NetworkMessage msg; msg.addByte(0x6A); msg.addPosition(pos); - - if (player->getOperatingSystem() >= CLIENTOS_OTCLIENT_LINUX) { - msg.addByte(stackpos); - } + msg.addByte(stackpos); bool known; uint32_t removedKnown; @@ -1558,11 +2417,14 @@ void ProtocolGame::sendAddCreature(const Creature* creature, const Position& pos } NetworkMessage msg; - msg.addByte(0x0A); + msg.addByte(0x17); msg.add(player->getID()); - msg.addByte(0x32); // beat duration (50) - msg.addByte(0x00); + msg.add(0x32); // beat duration (50) + + msg.addDouble(Creature::speedA, 3); + msg.addDouble(Creature::speedB, 3); + msg.addDouble(Creature::speedC, 3); // can report bugs? if (player->getAccountType() >= ACCOUNT_TYPE_TUTOR) { @@ -1571,15 +2433,16 @@ void ProtocolGame::sendAddCreature(const Creature* creature, const Position& pos msg.addByte(0x00); } - if (player->getAccountType() >= ACCOUNT_TYPE_GAMEMASTER) { - msg.addByte(0x0B); - for (uint8_t i = 0; i < 32; i++) { - msg.addByte(0xFF); - } - } + msg.addByte(0x00); // can change pvp framing option + msg.addByte(0x00); // expert mode button enabled + + msg.add(0x00); // URL (string) to ingame store images + msg.add(25); // premium coin package size writeToOutputBuffer(msg); + sendPendingStateEntered(); + sendEnterWorld(); sendMapDescription(pos); if (isLogin) { @@ -1594,16 +2457,14 @@ void ProtocolGame::sendAddCreature(const Creature* creature, const Position& pos sendSkills(); //gameworld light-settings - LightInfo lightInfo; - g_game.getWorldLightInfo(lightInfo); - sendWorldLight(lightInfo); + sendWorldLight(g_game.getWorldLightInfo()); //player light level sendCreatureLight(creature); - //player vip sendVIPEntries(); + sendBasicData(); player->sendIcons(); } @@ -1684,11 +2545,34 @@ void ProtocolGame::sendInventoryItem(slots_t slot, const Item* item) writeToOutputBuffer(msg); } -void ProtocolGame::sendAddContainerItem(uint8_t cid, const Item* item) +void ProtocolGame::sendItems() +{ + NetworkMessage msg; + msg.addByte(0xF5); + + const std::vector& inventory = Item::items.getInventory(); + msg.add(inventory.size() + 11); + for (uint16_t i = 1; i <= 11; i++) { + msg.add(i); + msg.addByte(0); //always 0 + msg.add(1); // always 1 + } + + for (auto clientId : inventory) { + msg.add(clientId); + msg.addByte(0); //always 0 + msg.add(1); + } + + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendAddContainerItem(uint8_t cid, uint16_t slot, const Item* item) { NetworkMessage msg; msg.addByte(0x70); msg.addByte(cid); + msg.add(slot); msg.addItem(item); writeToOutputBuffer(msg); } @@ -1698,17 +2582,22 @@ void ProtocolGame::sendUpdateContainerItem(uint8_t cid, uint16_t slot, const Ite NetworkMessage msg; msg.addByte(0x71); msg.addByte(cid); - msg.addByte(static_cast(slot)); + msg.add(slot); msg.addItem(item); writeToOutputBuffer(msg); } -void ProtocolGame::sendRemoveContainerItem(uint8_t cid, uint16_t slot) +void ProtocolGame::sendRemoveContainerItem(uint8_t cid, uint16_t slot, const Item* lastItem) { NetworkMessage msg; msg.addByte(0x72); msg.addByte(cid); - msg.addByte(static_cast(slot)); + msg.add(slot); + if (lastItem) { + msg.addItem(lastItem); + } else { + msg.add(0x00); + } writeToOutputBuffer(msg); } @@ -1735,13 +2624,11 @@ void ProtocolGame::sendTextWindow(uint32_t windowTextId, Item* item, uint16_t ma msg.add(0x00); } - if (player->getOperatingSystem() >= CLIENTOS_OTCLIENT_LINUX) { - time_t writtenDate = item->getDate(); - if (writtenDate != 0) { - msg.addString(formatDateShort(writtenDate)); - } else { - msg.add(0x00); - } + time_t writtenDate = item->getDate(); + if (writtenDate != 0) { + msg.addString(formatDateShort(writtenDate)); + } else { + msg.add(0x00); } writeToOutputBuffer(msg); @@ -1756,6 +2643,7 @@ void ProtocolGame::sendTextWindow(uint32_t windowTextId, uint32_t itemId, const msg.add(text.size()); msg.addString(text); msg.add(0x00); + msg.add(0x00); writeToOutputBuffer(msg); } @@ -1775,6 +2663,11 @@ void ProtocolGame::sendOutfitWindow() msg.addByte(0xC8); Outfit_t currentOutfit = player->getDefaultOutfit(); + Mount* currentMount = g_game.mounts.getMountByID(player->getCurrentMount()); + if (currentMount) { + currentOutfit.lookMount = currentMount->clientId; + } + AddOutfit(msg, currentOutfit); std::vector protocolOutfits; @@ -1792,7 +2685,7 @@ void ProtocolGame::sendOutfitWindow() } protocolOutfits.emplace_back(outfit.name, outfit.lookType, addons); - if (protocolOutfits.size() == 15) { // Game client doesn't allow more than 15 outfits + if (protocolOutfits.size() == 100) { // Game client doesn't allow more than 100 outfits break; } } @@ -1800,26 +2693,44 @@ void ProtocolGame::sendOutfitWindow() msg.addByte(protocolOutfits.size()); for (const ProtocolOutfit& outfit : protocolOutfits) { msg.add(outfit.lookType); + msg.addString(outfit.name); msg.addByte(outfit.addons); } + std::vector mounts; + for (const Mount& mount : g_game.mounts.getMounts()) { + if (player->hasMount(&mount)) { + mounts.push_back(&mount); + } + } + + msg.addByte(mounts.size()); + for (const Mount* mount : mounts) { + msg.add(mount->clientId); + msg.addString(mount->name); + } + writeToOutputBuffer(msg); } void ProtocolGame::sendUpdatedVIPStatus(uint32_t guid, VipStatus_t newStatus) { NetworkMessage msg; - msg.addByte(newStatus == VIPSTATUS_ONLINE ? 0xD3 : 0xD4); + msg.addByte(0xD3); msg.add(guid); + msg.addByte(newStatus); writeToOutputBuffer(msg); } -void ProtocolGame::sendVIP(uint32_t guid, const std::string& name, VipStatus_t status) +void ProtocolGame::sendVIP(uint32_t guid, const std::string& name, const std::string& description, uint32_t icon, bool notify, VipStatus_t status) { NetworkMessage msg; msg.addByte(0xD2); msg.add(guid); msg.addString(name); + msg.addString(description); + msg.add(std::min(10, icon)); + msg.addByte(notify ? 0x01 : 0x00); msg.addByte(status); writeToOutputBuffer(msg); } @@ -1832,17 +2743,66 @@ void ProtocolGame::sendVIPEntries() VipStatus_t vipStatus = VIPSTATUS_ONLINE; Player* vipPlayer = g_game.getPlayerByGUID(entry.guid); - if (!vipPlayer || (vipPlayer->isInGhostMode() && !player->isAccessPlayer())) { + + if (!vipPlayer || vipPlayer->isInGhostMode() || player->isAccessPlayer()) { vipStatus = VIPSTATUS_OFFLINE; } - sendVIP(entry.guid, entry.name, vipStatus); + sendVIP(entry.guid, entry.name, entry.description, entry.icon, entry.notify, vipStatus); } } +void ProtocolGame::sendSpellCooldown(uint8_t spellId, uint32_t time) +{ + NetworkMessage msg; + msg.addByte(0xA4); + msg.addByte(spellId); + msg.add(time); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendSpellGroupCooldown(SpellGroup_t groupId, uint32_t time) +{ + NetworkMessage msg; + msg.addByte(0xA5); + msg.addByte(groupId); + msg.add(time); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendModalWindow(const ModalWindow& modalWindow) +{ + NetworkMessage msg; + msg.addByte(0xFA); + + msg.add(modalWindow.id); + msg.addString(modalWindow.title); + msg.addString(modalWindow.message); + + msg.addByte(modalWindow.buttons.size()); + for (const auto& it : modalWindow.buttons) { + msg.addString(it.first); + msg.addByte(it.second); + } + + msg.addByte(modalWindow.choices.size()); + for (const auto& it : modalWindow.choices) { + msg.addString(it.first); + msg.addByte(it.second); + } + + msg.addByte(modalWindow.defaultEscapeButton); + msg.addByte(modalWindow.defaultEnterButton); + msg.addByte(modalWindow.priority ? 0x01 : 0x00); + + writeToOutputBuffer(msg); +} + ////////////// Add common messages void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bool known, uint32_t remove) { + CreatureType_t creatureType = creature->getType(); + const Player* otherPlayer = creature->getPlayer(); if (known) { @@ -1852,16 +2812,16 @@ void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bo msg.add(0x61); msg.add(remove); msg.add(creature->getID()); + msg.addByte(creatureType); msg.addString(creature->getName()); } if (creature->isHealthHidden()) { msg.addByte(0x00); - } - else { + } else { msg.addByte(std::ceil((static_cast(creature->getHealth()) / std::max(creature->getMaxHealth(), 1)) * 100)); } - + msg.addByte(creature->getDirection()); if (!creature->isInGhostMode() && !creature->isInvisible()) { @@ -1871,15 +2831,44 @@ void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bo AddOutfit(msg, outfit); } - LightInfo lightInfo; - creature->getCreatureLight(lightInfo); + LightInfo lightInfo = creature->getCreatureLight(); msg.addByte(player->isAccessPlayer() ? 0xFF : lightInfo.level); msg.addByte(lightInfo.color); - msg.add(creature->getStepSpeed()); + msg.add(creature->getStepSpeed() / 2); msg.addByte(player->getSkullClient(creature)); msg.addByte(player->getPartyShield(otherPlayer)); + + if (!known) { + msg.addByte(player->getGuildEmblem(otherPlayer)); + } + + if (creatureType == CREATURETYPE_MONSTER) { + const Creature* master = creature->getMaster(); + if (master) { + const Player* masterPlayer = master->getPlayer(); + if (masterPlayer) { + if (masterPlayer == player) { + creatureType = CREATURETYPE_SUMMON_OWN; + } else { + creatureType = CREATURETYPE_SUMMON_OTHERS; + } + } + } + } + + msg.addByte(creatureType); // Type (for summons) + msg.addByte(creature->getSpeechBubble()); + msg.addByte(0xFF); // MARK_UNMARKED + + if (otherPlayer) { + msg.add(otherPlayer->getHelpers()); + } else { + msg.add(0x00); + } + + msg.addByte(player->canWalkthroughEx(creature) ? 0x00 : 0x01); } void ProtocolGame::AddPlayerStats(NetworkMessage& msg) @@ -1888,23 +2877,41 @@ void ProtocolGame::AddPlayerStats(NetworkMessage& msg) msg.add(std::min(player->getHealth(), std::numeric_limits::max())); msg.add(std::min(player->getMaxHealth(), std::numeric_limits::max())); - - msg.add(player->getFreeCapacity() / 100); - msg.add(std::min(player->getExperience(), 0x7FFFFFFF)); + msg.add(player->getFreeCapacity()); + msg.add(player->getCapacity()); + + msg.add(player->getExperience()); msg.add(player->getLevel()); msg.addByte(player->getLevelPercent()); + msg.add(100); // base xp gain rate + msg.add(0); // xp voucher + msg.add(0); // low level bonus + msg.add(0); // xp boost + msg.add(100); // stamina multiplier (100 = x1.0) + msg.add(std::min(player->getMana(), std::numeric_limits::max())); msg.add(std::min(player->getMaxMana(), std::numeric_limits::max())); msg.addByte(std::min(player->getMagicLevel(), std::numeric_limits::max())); + msg.addByte(std::min(player->getBaseMagicLevel(), std::numeric_limits::max())); msg.addByte(player->getMagicLevelPercent()); msg.addByte(player->getSoul()); msg.add(player->getStaminaMinutes()); + + msg.add(player->getBaseSpeed() / 2); + + Condition* condition = player->getCondition(CONDITION_REGENERATION); + msg.add(condition ? condition->getTicks() / 1000 : 0x00); + + msg.add(player->getOfflineTrainingTime() / 60 / 1000); + + msg.add(0); // xp boost time (seconds) + msg.addByte(0); // enables exp boost in the store } void ProtocolGame::AddPlayerSkills(NetworkMessage& msg) @@ -1912,9 +2919,15 @@ void ProtocolGame::AddPlayerSkills(NetworkMessage& msg) msg.addByte(0xA1); for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { - msg.addByte(std::min(player->getSkillLevel(i), std::numeric_limits::max())); + msg.add(std::min(player->getSkillLevel(i), std::numeric_limits::max())); + msg.add(player->getBaseSkill(i)); msg.addByte(player->getSkillPercent(i)); } + + for (uint8_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; ++i) { + msg.add(std::min(100, player->varSpecialSkills[i])); + msg.add(0); + } } void ProtocolGame::AddOutfit(NetworkMessage& msg, const Outfit_t& outfit) @@ -1930,9 +2943,11 @@ void ProtocolGame::AddOutfit(NetworkMessage& msg, const Outfit_t& outfit) } else { msg.addItemId(outfit.lookTypeEx); } + + msg.add(outfit.lookMount); } -void ProtocolGame::AddWorldLight(NetworkMessage& msg, const LightInfo& lightInfo) +void ProtocolGame::AddWorldLight(NetworkMessage& msg, LightInfo lightInfo) { msg.addByte(0x82); msg.addByte((player->isAccessPlayer() ? 0xFF : lightInfo.level)); @@ -1941,8 +2956,7 @@ void ProtocolGame::AddWorldLight(NetworkMessage& msg, const LightInfo& lightInfo void ProtocolGame::AddCreatureLight(NetworkMessage& msg, const Creature* creature) { - LightInfo lightInfo; - creature->getCreatureLight(lightInfo); + LightInfo lightInfo = creature->getCreatureLight(); msg.addByte(0x8D); msg.add(creature->getID()); @@ -2050,6 +3064,23 @@ void ProtocolGame::MoveDownCreature(NetworkMessage& msg, const Creature* creatur GetMapDescription(oldPos.x - 8, oldPos.y + 7, newPos.z, 18, 1, msg); } +void ProtocolGame::AddShopItem(NetworkMessage& msg, const ShopInfo& item) +{ + const ItemType& it = Item::items[item.itemId]; + msg.add(it.clientId); + + if (it.isSplash() || it.isFluidContainer()) { + msg.addByte(serverFluidToClient(item.subType)); + } else { + msg.addByte(0x00); + } + + msg.addString(item.realName); + msg.add(it.weight); + msg.add(item.buyPrice); + msg.add(item.sellPrice); +} + void ProtocolGame::parseExtendedOpcode(NetworkMessage& msg) { uint8_t opcode = msg.getByte(); diff --git a/src/protocolgame.h b/src/protocolgame.h index 9d0cbe3..e8eb135 100644 --- a/src/protocolgame.h +++ b/src/protocolgame.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -34,14 +34,21 @@ class Tile; class Connection; class Quest; class ProtocolGame; -typedef std::shared_ptr ProtocolGame_ptr; +using ProtocolGame_ptr = std::shared_ptr; extern Game g_game; struct TextMessage { - MessageClasses type; + MessageClasses type = MESSAGE_STATUS_DEFAULT; std::string text; + Position position; + uint16_t channelId; + struct { + int32_t value = 0; + TextColor_t color; + } primary, secondary; + TextMessage() = default; TextMessage(MessageClasses type, std::string text) : type(type), text(std::move(text)) {} }; @@ -50,16 +57,16 @@ class ProtocolGame final : public Protocol { public: // static protocol information - enum { server_sends_first = true }; - enum { protocol_identifier = 0 }; // Not required as we send first - + enum {server_sends_first = true}; + enum {protocol_identifier = 0}; // Not required as we send first + enum {use_checksum = true}; static const char* protocol_name() { return "gameworld protocol"; } explicit ProtocolGame(Connection_ptr connection) : Protocol(connection) {} - void login(const std::string& name, uint32_t accnumber, OperatingSystem_t operatingSystem); + void login(const std::string& name, uint32_t accountId, OperatingSystem_t operatingSystem); void logout(bool displayEffect, bool forced); uint16_t getVersion() const { @@ -74,7 +81,7 @@ class ProtocolGame final : public Protocol void disconnectClient(const std::string& message) const; void writeToOutputBuffer(const NetworkMessage& msg); - void release() final; + void release() override; void checkCreatureAsKnown(uint32_t id, bool& known, uint32_t& removedKnown); @@ -83,8 +90,8 @@ class ProtocolGame final : public Protocol bool canSee(const Position& pos) const; // we have all the parse methods - void parsePacket(NetworkMessage& msg) final; - void onRecvFirstMessage(NetworkMessage& msg) final; + void parsePacket(NetworkMessage& msg) override; + void onRecvFirstMessage(NetworkMessage& msg) override; void onConnect() override; //Parse methods @@ -96,12 +103,11 @@ class ProtocolGame final : public Protocol void parseFightModes(NetworkMessage& msg); void parseAttack(NetworkMessage& msg); void parseFollow(NetworkMessage& msg); - - void parseProcessRuleViolationReport(NetworkMessage& msg); - void parseCloseRuleViolationReport(NetworkMessage& msg); + void parseEquipObject(NetworkMessage& msg); void parseBugReport(NetworkMessage& msg); void parseDebugAssert(NetworkMessage& msg); + void parseRuleViolationReport(NetworkMessage& msg); void parseThrow(NetworkMessage& msg); void parseUseItemEx(NetworkMessage& msg); @@ -113,20 +119,40 @@ class ProtocolGame final : public Protocol void parseTextWindow(NetworkMessage& msg); void parseHouseWindow(NetworkMessage& msg); + void parseLookInShop(NetworkMessage& msg); + void parsePlayerPurchase(NetworkMessage& msg); + void parsePlayerSale(NetworkMessage& msg); + + void parseQuestLine(NetworkMessage& msg); + void parseInviteToParty(NetworkMessage& msg); void parseJoinParty(NetworkMessage& msg); void parseRevokePartyInvite(NetworkMessage& msg); void parsePassPartyLeadership(NetworkMessage& msg); + void parseEnableSharedPartyExperience(NetworkMessage& msg); + void parseToggleMount(NetworkMessage& msg); + + void parseModalWindowAnswer(NetworkMessage& msg); + + void parseBrowseField(NetworkMessage& msg); void parseSeekInContainer(NetworkMessage& msg); //trade methods void parseRequestTrade(NetworkMessage& msg); void parseLookInTrade(NetworkMessage& msg); + //market methods + void parseMarketLeave(); + void parseMarketBrowse(NetworkMessage& msg); + void parseMarketCreateOffer(NetworkMessage& msg); + void parseMarketCancelOffer(NetworkMessage& msg); + void parseMarketAcceptOffer(NetworkMessage& msg); + //VIP methods void parseAddVip(NetworkMessage& msg); void parseRemoveVip(NetworkMessage& msg); + void parseEditVip(NetworkMessage& msg); void parseRotateItem(NetworkMessage& msg); @@ -138,14 +164,17 @@ class ProtocolGame final : public Protocol void parseCloseChannel(NetworkMessage& msg); //Send functions + void sendChannelMessage(const std::string& author, const std::string& text, SpeakClasses type, uint16_t channel); + void sendChannelEvent(uint16_t channelId, const std::string& playerName, ChannelEvent_t channelEvent); void sendClosePrivate(uint16_t channelId); void sendCreatePrivateChannel(uint16_t channelId, const std::string& channelName); void sendChannelsDialog(); - void sendChannel(uint16_t channelId, const std::string& channelName); + void sendChannel(uint16_t channelId, const std::string& channelName, const UsersMap* channelUsers, const InvitedMap* invitedUsers); void sendOpenPrivateChannel(const std::string& receiver); void sendToChannel(const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId); void sendPrivateMessage(const Player* speaker, SpeakClasses type, const std::string& text); void sendIcons(uint16_t icons); + void sendFYIBox(const std::string& message); void sendDistanceShoot(const Position& from, const Position& to, uint8_t type); void sendMagicEffect(const Position& pos, uint8_t type); @@ -153,20 +182,41 @@ class ProtocolGame final : public Protocol void sendSkills(); void sendPing(); void sendPingBack(); - void sendCreatureTurn(const Creature* creature, uint32_t stackpos); + void sendCreatureTurn(const Creature* creature, uint32_t stackPos); void sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, const Position* pos = nullptr); + void sendQuestLog(); + void sendQuestLine(const Quest* quest); + void sendCancelWalk(); void sendChangeSpeed(const Creature* creature, uint32_t speed); void sendCancelTarget(); void sendCreatureOutfit(const Creature* creature, const Outfit_t& outfit); void sendStats(); + void sendBasicData(); void sendTextMessage(const TextMessage& message); - void sendAnimatedText(const Position& pos, uint8_t color, const std::string& text); + void sendReLoginWindow(uint8_t unfairFightReduction); + void sendTutorial(uint8_t tutorialId); + void sendAddMarker(const Position& pos, uint8_t markType, const std::string& desc); + + void sendCreatureWalkthrough(const Creature* creature, bool walkthrough); void sendCreatureShield(const Creature* creature); void sendCreatureSkull(const Creature* creature); + void sendCreatureType(uint32_t creatureId, uint8_t creatureType); + void sendCreatureHelpers(uint32_t creatureId, uint16_t helpers); + void sendShop(Npc* npc, const ShopInfoList& itemList); + void sendCloseShop(); + void sendSaleItemList(const std::list& shop); + void sendMarketEnter(uint32_t depotId); + void sendMarketLeave(); + void sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& buyOffers, const MarketOfferList& sellOffers); + void sendMarketAcceptOffer(const MarketOfferEx& offer); + void sendMarketBrowseOwnOffers(const MarketOfferList& buyOffers, const MarketOfferList& sellOffers); + void sendMarketCancelOffer(const MarketOfferEx& offer); + void sendMarketBrowseOwnHistory(const HistoryMarketOfferList& buyOffers, const HistoryMarketOfferList& sellOffers); + void sendMarketDetail(uint16_t itemId); void sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack); void sendCloseTrade(); @@ -176,26 +226,26 @@ class ProtocolGame final : public Protocol void sendOutfitWindow(); void sendUpdatedVIPStatus(uint32_t guid, VipStatus_t newStatus); - void sendVIP(uint32_t guid, const std::string& name, VipStatus_t status); + void sendVIP(uint32_t guid, const std::string& name, const std::string& description, uint32_t icon, bool notify, VipStatus_t status); void sendVIPEntries(); + void sendPendingStateEntered(); + void sendEnterWorld(); + void sendFightModes(); void sendCreatureLight(const Creature* creature); - void sendWorldLight(const LightInfo& lightInfo); + void sendWorldLight(LightInfo lightInfo); void sendCreatureSquare(const Creature* creature, SquareColor_t color); - //rule violations - void sendRemoveRuleViolationReport(const std::string& name); - void sendLockRuleViolation(); - void sendRuleViolationCancel(const std::string& name); - void sendRuleViolationsChannel(uint16_t channelId); + void sendSpellCooldown(uint8_t spellId, uint32_t time); + void sendSpellGroupCooldown(SpellGroup_t groupId, uint32_t time); //tiles void sendMapDescription(const Position& pos); - void sendAddTileItem(const Position& pos, const Item* item, uint32_t stackpos); + void sendAddTileItem(const Position& pos, uint32_t stackpos, const Item* item); void sendUpdateTileItem(const Position& pos, uint32_t stackpos, const Item* item); void sendRemoveTileThing(const Position& pos, uint32_t stackpos); void sendUpdateTile(const Tile* tile, const Position& pos); @@ -205,15 +255,19 @@ class ProtocolGame final : public Protocol const Position& oldPos, int32_t oldStackPos, bool teleport); //containers - void sendAddContainerItem(uint8_t cid, const Item* item); + void sendAddContainerItem(uint8_t cid, uint16_t slot, const Item* item); void sendUpdateContainerItem(uint8_t cid, uint16_t slot, const Item* item); - void sendRemoveContainerItem(uint8_t cid, uint16_t slot); + void sendRemoveContainerItem(uint8_t cid, uint16_t slot, const Item* lastItem); void sendContainer(uint8_t cid, const Container* container, bool hasParent, uint16_t firstIndex); void sendCloseContainer(uint8_t cid); //inventory void sendInventoryItem(slots_t slot, const Item* item); + void sendItems(); + + //messages + void sendModalWindow(const ModalWindow& modalWindow); //Help functions @@ -232,7 +286,7 @@ class ProtocolGame final : public Protocol void AddPlayerStats(NetworkMessage& msg); void AddOutfit(NetworkMessage& msg, const Outfit_t& outfit); void AddPlayerSkills(NetworkMessage& msg); - void AddWorldLight(NetworkMessage& msg, const LightInfo& lightInfo); + void AddWorldLight(NetworkMessage& msg, LightInfo lightInfo); void AddCreatureLight(NetworkMessage& msg, const Creature* creature); //tiles @@ -241,6 +295,9 @@ class ProtocolGame final : public Protocol void MoveUpCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, const Position& oldPos); void MoveDownCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, const Position& oldPos); + //shop + void AddShopItem(NetworkMessage& msg, const ShopInfo& item); + //otclient void parseExtendedOpcode(NetworkMessage& msg); diff --git a/src/protocollogin.cpp b/src/protocollogin.cpp index 27e2ea3..91da0ab 100644 --- a/src/protocollogin.cpp +++ b/src/protocollogin.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -43,15 +43,28 @@ void ProtocolLogin::disconnectClient(const std::string& message, uint16_t versio disconnect(); } -void ProtocolLogin::getCharacterList(uint32_t accountNumber, const std::string& password, uint16_t version) +void ProtocolLogin::getCharacterList(const std::string& accountName, const std::string& password, const std::string& token, uint16_t version) { Account account; - if (!IOLoginData::loginserverAuthentication(accountNumber, password, account)) { - disconnectClient("Accountnumber or password is not correct.", version); + if (!IOLoginData::loginserverAuthentication(accountName, password, account)) { + disconnectClient("Account name or password is not correct.", version); return; } + uint32_t ticks = time(nullptr) / AUTHENTICATOR_PERIOD; + auto output = OutputMessagePool::getOutputMessage(); + if (!account.key.empty()) { + if (token.empty() || !(token == generateToken(account.key, ticks) || token == generateToken(account.key, ticks - 1) || token == generateToken(account.key, ticks + 1))) { + output->addByte(0x0D); + output->addByte(0); + send(output); + disconnect(); + return; + } + output->addByte(0x0C); + output->addByte(0); + } //Update premium days Game::updatePremium(account); @@ -66,24 +79,53 @@ void ProtocolLogin::getCharacterList(uint32_t accountNumber, const std::string& output->addString(ss.str()); } + //Add session key + output->addByte(0x28); + output->addString(accountName + "\n" + password + "\n" + token + "\n" + std::to_string(ticks)); + //Add char list output->addByte(0x64); uint8_t size = std::min(std::numeric_limits::max(), account.characters.size()); + + if (g_config.getBoolean(ConfigManager::ONLINE_OFFLINE_CHARLIST)) { + output->addByte(2); // number of worlds + + for (uint8_t i = 0; i < 2; i++) { + output->addByte(i); // world id + output->addString(i == 0 ? "Offline" : "Online"); + output->addString(g_config.getString(ConfigManager::IP)); + output->add(g_config.getNumber(ConfigManager::GAME_PORT)); + output->addByte(0); + } + } else { + output->addByte(1); // number of worlds + output->addByte(0); // world id + output->addString(g_config.getString(ConfigManager::SERVER_NAME)); + output->addString(g_config.getString(ConfigManager::IP)); + output->add(g_config.getNumber(ConfigManager::GAME_PORT)); + output->addByte(0); + } + output->addByte(size); for (uint8_t i = 0; i < size; i++) { - output->addString(account.characters[i]); - output->addString(g_config.getString(ConfigManager::SERVER_NAME)); - output->add(inet_addr(g_config.getString(ConfigManager::IP).c_str())); - output->add(g_config.getNumber(ConfigManager::GAME_PORT)); + const std::string& character = account.characters[i]; + if (g_config.getBoolean(ConfigManager::ONLINE_OFFLINE_CHARLIST)) { + output->addByte(g_game.getPlayerByName(character) ? 1 : 0); + } else { + output->addByte(0); + } + output->addString(character); } //Add premium days + output->addByte(0); if (g_config.getBoolean(ConfigManager::FREE_PREMIUM)) { - output->add(0xFFFF); - } - else { - output->add(account.premiumDays); + output->addByte(1); + output->add(0); + } else { + output->addByte(account.premiumDays > 0 ? 1 : 0); + output->add(time(nullptr) + (account.premiumDays * 86400)); } send(output); @@ -91,7 +133,6 @@ void ProtocolLogin::getCharacterList(uint32_t accountNumber, const std::string& disconnect(); } - void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) { if (g_game.getGameState() == GAME_STATE_SHUTDOWN) { @@ -104,11 +145,9 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) uint16_t version = msg.get(); if (version >= 971) { msg.skipBytes(17); - } - else { + } else { msg.skipBytes(12); } - /* * Skipped bytes: * 4 bytes: protocolVersion @@ -128,13 +167,13 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) return; } - uint32_t key[4]; + xtea::key key; key[0] = msg.get(); key[1] = msg.get(); key[2] = msg.get(); key[3] = msg.get(); enableXTEAEncryption(); - setXTEAKey(key); + setXTEAKey(std::move(key)); if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) { std::ostringstream ss; @@ -170,9 +209,9 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) return; } - uint32_t accountNumber = msg.get(); - if (!accountNumber) { - disconnectClient("Invalid account number.", version); + std::string accountName = msg.getString(); + if (accountName.empty()) { + disconnectClient("Invalid account name.", version); return; } @@ -182,6 +221,15 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) return; } + // read authenticator token and stay logged in flag from last 128 bytes + msg.skipBytes((msg.getLength() - 128) - msg.getBufferPosition()); + if (!Protocol::RSA_decrypt(msg)) { + disconnectClient("Invalid authentification token.", version); + return; + } + + std::string authToken = msg.getString(); + auto thisPtr = std::static_pointer_cast(shared_from_this()); - g_dispatcher.addTask(createTask(std::bind(&ProtocolLogin::getCharacterList, thisPtr, accountNumber, password, version))); + g_dispatcher.addTask(createTask(std::bind(&ProtocolLogin::getCharacterList, thisPtr, accountName, password, authToken, version))); } diff --git a/src/protocollogin.h b/src/protocollogin.h index a45898f..9781446 100644 --- a/src/protocollogin.h +++ b/src/protocollogin.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,6 +31,7 @@ class ProtocolLogin : public Protocol // static protocol information enum {server_sends_first = false}; enum {protocol_identifier = 0x01}; + enum {use_checksum = true}; static const char* protocol_name() { return "login protocol"; } @@ -42,7 +43,7 @@ class ProtocolLogin : public Protocol private: void disconnectClient(const std::string& message, uint16_t version); - void getCharacterList(uint32_t accountNumber, const std::string& password, uint16_t version); + void getCharacterList(const std::string& accountName, const std::string& password, const std::string& token, uint16_t version); }; #endif diff --git a/src/protocolold.cpp b/src/protocolold.cpp new file mode 100644 index 0000000..e981d56 --- /dev/null +++ b/src/protocolold.cpp @@ -0,0 +1,77 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "protocolold.h" +#include "outputmessage.h" + +#include "game.h" + +extern Game g_game; + +void ProtocolOld::disconnectClient(const std::string& message) +{ + auto output = OutputMessagePool::getOutputMessage(); + output->addByte(0x0A); + output->addString(message); + send(output); + + disconnect(); +} + +void ProtocolOld::onRecvFirstMessage(NetworkMessage& msg) +{ + if (g_game.getGameState() == GAME_STATE_SHUTDOWN) { + disconnect(); + return; + } + + /*uint16_t clientOS =*/ msg.get(); + uint16_t version = msg.get(); + msg.skipBytes(12); + + if (version <= 760) { + std::ostringstream ss; + ss << "Only clients with protocol " << CLIENT_VERSION_STR << " allowed!"; + disconnectClient(ss.str()); + return; + } + + if (!Protocol::RSA_decrypt(msg)) { + disconnect(); + return; + } + + xtea::key key; + key[0] = msg.get(); + key[1] = msg.get(); + key[2] = msg.get(); + key[3] = msg.get(); + enableXTEAEncryption(); + setXTEAKey(std::move(key)); + + if (version <= 822) { + disableChecksum(); + } + + std::ostringstream ss; + ss << "Only clients with protocol " << CLIENT_VERSION_STR << " allowed!"; + disconnectClient(ss.str()); +} diff --git a/src/protocolold.h b/src/protocolold.h new file mode 100644 index 0000000..708e763 --- /dev/null +++ b/src/protocolold.h @@ -0,0 +1,46 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_PROTOCOLOLD_H_5487B862FE144AE0904D098A3238E161 +#define FS_PROTOCOLOLD_H_5487B862FE144AE0904D098A3238E161 + +#include "protocol.h" + +class NetworkMessage; + +class ProtocolOld final : public Protocol +{ + public: + // static protocol information + enum {server_sends_first = false}; + enum {protocol_identifier = 0x01}; + enum {use_checksum = false}; + static const char* protocol_name() { + return "old login protocol"; + } + + explicit ProtocolOld(Connection_ptr connection) : Protocol(connection) {} + + void onRecvFirstMessage(NetworkMessage& msg) override; + + private: + void disconnectClient(const std::string& message); +}; + +#endif diff --git a/src/protocolstatus.cpp b/src/protocolstatus.cpp index d29b19e..aba1bb2 100644 --- a/src/protocolstatus.cpp +++ b/src/protocolstatus.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -117,26 +117,7 @@ void ProtocolStatus::sendStatusString() owner.append_attribute("email") = g_config.getString(ConfigManager::OWNER_EMAIL).c_str(); pugi::xml_node players = tsqp.append_child("players"); - uint32_t real = 0; - - std::map listIP; - - for (const auto& it : g_game.getPlayers()) { - if (it.second->getIP() != 0) { - auto ip = listIP.find(it.second->getIP()); - if (ip != listIP.end()) { - listIP[it.second->getIP()]++; - if (listIP[it.second->getIP()] < 5) { - real++; - } - } else { - listIP[it.second->getIP()] = 1; - real++; - } - } - } - players.append_attribute("online") = std::to_string(real).c_str(); - + players.append_attribute("online") = std::to_string(g_game.getPlayersOnline()).c_str(); players.append_attribute("max") = std::to_string(g_config.getNumber(ConfigManager::MAX_PLAYERS)).c_str(); players.append_attribute("peak") = std::to_string(g_game.getPlayersRecord()).c_str(); diff --git a/src/protocolstatus.h b/src/protocolstatus.h index 2e6cae2..5df99e4 100644 --- a/src/protocolstatus.h +++ b/src/protocolstatus.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,20 +29,21 @@ class ProtocolStatus final : public Protocol // static protocol information enum {server_sends_first = false}; enum {protocol_identifier = 0xFF}; + enum {use_checksum = false}; static const char* protocol_name() { return "status protocol"; } explicit ProtocolStatus(Connection_ptr connection) : Protocol(connection) {} - void onRecvFirstMessage(NetworkMessage& msg) final; + void onRecvFirstMessage(NetworkMessage& msg) override; void sendStatusString(); void sendInfo(uint16_t requestedInfo, const std::string& characterName); static const uint64_t start; - protected: + private: static std::map ipConnectMap; }; diff --git a/src/pugicast.h b/src/pugicast.h index 456a2c0..9b188ed 100644 --- a/src/pugicast.h +++ b/src/pugicast.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/quests.cpp b/src/quests.cpp new file mode 100644 index 0000000..2e4f552 --- /dev/null +++ b/src/quests.cpp @@ -0,0 +1,228 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "quests.h" + +#include "pugicast.h" + +std::string Mission::getDescription(Player* player) const +{ + int32_t value; + player->getStorageValue(storageID, value); + + if (!mainDescription.empty()) { + std::string desc = mainDescription; + replaceString(desc, "|STATE|", std::to_string(value)); + replaceString(desc, "\\n", "\n"); + return desc; + } + + if (ignoreEndValue) { + for (int32_t current = endValue; current >= startValue; current--) { + if (value >= current) { + auto sit = descriptions.find(current); + if (sit != descriptions.end()) { + return sit->second; + } + } + } + } else { + for (int32_t current = endValue; current >= startValue; current--) { + if (value == current) { + auto sit = descriptions.find(current); + if (sit != descriptions.end()) { + return sit->second; + } + } + } + } + return "An error has occurred, please contact a gamemaster."; +} + +bool Mission::isStarted(Player* player) const +{ + if (!player) { + return false; + } + + int32_t value; + if (!player->getStorageValue(storageID, value)) { + return false; + } + + if (value < startValue) { + return false; + } + + if (!ignoreEndValue && value > endValue) { + return false; + } + + return true; +} + +bool Mission::isCompleted(Player* player) const +{ + if (!player) { + return false; + } + + int32_t value; + if (!player->getStorageValue(storageID, value)) { + return false; + } + + if (ignoreEndValue) { + return value >= endValue; + } + + return value == endValue; +} + +std::string Mission::getName(Player* player) const +{ + if (isCompleted(player)) { + return name + " (completed)"; + } + return name; +} + +uint16_t Quest::getMissionsCount(Player* player) const +{ + uint16_t count = 0; + for (const Mission& mission : missions) { + if (mission.isStarted(player)) { + count++; + } + } + return count; +} + +bool Quest::isCompleted(Player* player) const +{ + for (const Mission& mission : missions) { + if (!mission.isCompleted(player)) { + return false; + } + } + return true; +} + +bool Quest::isStarted(Player* player) const +{ + if (!player) { + return false; + } + + int32_t value; + if (!player->getStorageValue(startStorageID, value) || value < startStorageValue) { + return false; + } + + return true; +} + +bool Quests::reload() +{ + quests.clear(); + return loadFromXml(); +} + +bool Quests::loadFromXml() +{ + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_file("data/XML/quests.xml"); + if (!result) { + printXMLError("Error - Quests::loadFromXml", "data/XML/quests.xml", result); + return false; + } + + uint16_t id = 0; + for (auto questNode : doc.child("quests").children()) { + quests.emplace_back( + questNode.attribute("name").as_string(), + ++id, + pugi::cast(questNode.attribute("startstorageid").value()), + pugi::cast(questNode.attribute("startstoragevalue").value()) + ); + Quest& quest = quests.back(); + + for (auto missionNode : questNode.children()) { + std::string mainDescription = missionNode.attribute("description").as_string(); + + quest.missions.emplace_back( + missionNode.attribute("name").as_string(), + pugi::cast(missionNode.attribute("storageid").value()), + pugi::cast(missionNode.attribute("startvalue").value()), + pugi::cast(missionNode.attribute("endvalue").value()), + missionNode.attribute("ignoreendvalue").as_bool() + ); + Mission& mission = quest.missions.back(); + + if (mainDescription.empty()) { + for (auto missionStateNode : missionNode.children()) { + int32_t missionId = pugi::cast(missionStateNode.attribute("id").value()); + mission.descriptions.emplace(missionId, missionStateNode.attribute("description").as_string()); + } + } else { + mission.mainDescription = mainDescription; + } + } + } + return true; +} + +Quest* Quests::getQuestByID(uint16_t id) +{ + for (Quest& quest : quests) { + if (quest.id == id) { + return ? + } + } + return nullptr; +} + +uint16_t Quests::getQuestsCount(Player* player) const +{ + uint16_t count = 0; + for (const Quest& quest : quests) { + if (quest.isStarted(player)) { + count++; + } + } + return count; +} + +bool Quests::isQuestStorage(const uint32_t key, const int32_t value, const int32_t oldValue) const +{ + for (const Quest& quest : quests) { + if (quest.getStartStorageId() == key && quest.getStartStorageValue() == value) { + return true; + } + + for (const Mission& mission : quest.getMissions()) { + if (mission.getStorageId() == key && value >= mission.getStartStorageValue() && value <= mission.getEndStorageValue()) { + return mission.mainDescription.empty() || oldValue < mission.getStartStorageValue() || oldValue > mission.getEndStorageValue(); + } + } + } + return false; +} diff --git a/src/quests.h b/src/quests.h new file mode 100644 index 0000000..3ccd180 --- /dev/null +++ b/src/quests.h @@ -0,0 +1,119 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_QUESTS_H_16E44051F23547BE8097F8EA9FCAACA0 +#define FS_QUESTS_H_16E44051F23547BE8097F8EA9FCAACA0 + +#include "player.h" +#include "networkmessage.h" + +class Mission; +class Quest; + +using MissionsList = std::list; +using QuestsList = std::list; + +class Mission +{ + public: + Mission(std::string name, int32_t storageID, int32_t startValue, int32_t endValue, bool ignoreEndValue) : + name(std::move(name)), storageID(storageID), startValue(startValue), endValue(endValue), ignoreEndValue(ignoreEndValue) {} + + bool isCompleted(Player* player) const; + bool isStarted(Player* player) const; + std::string getName(Player* player) const; + std::string getDescription(Player* player) const; + + uint32_t getStorageId() const { + return storageID; + } + int32_t getStartStorageValue() const { + return startValue; + } + int32_t getEndStorageValue() const { + return endValue; + } + + std::map descriptions; + std::string mainDescription; + + private: + std::string name; + uint32_t storageID; + int32_t startValue, endValue; + bool ignoreEndValue; +}; + +class Quest +{ + public: + Quest(std::string name, uint16_t id, int32_t startStorageID, int32_t startStorageValue) : + name(std::move(name)), startStorageID(startStorageID), startStorageValue(startStorageValue), id(id) {} + + bool isCompleted(Player* player) const; + bool isStarted(Player* player) const; + uint16_t getID() const { + return id; + } + std::string getName() const { + return name; + } + uint16_t getMissionsCount(Player* player) const; + + uint32_t getStartStorageId() const { + return startStorageID; + } + int32_t getStartStorageValue() const { + return startStorageValue; + } + + const MissionsList& getMissions() const { + return missions; + } + + private: + std::string name; + + uint32_t startStorageID; + int32_t startStorageValue; + uint16_t id; + + MissionsList missions; + + friend class Quests; +}; + +class Quests +{ + public: + const QuestsList& getQuests() const { + return quests; + } + + bool loadFromXml(); + Quest* getQuestByID(uint16_t id); + bool isQuestStorage(const uint32_t key, const int32_t value, const int32_t oldValue) const; + uint16_t getQuestsCount(Player* player) const; + bool reload(); + + private: + QuestsList quests; +}; + +#endif diff --git a/src/raids.cpp b/src/raids.cpp index 6fd4f1f..d831bdc 100644 --- a/src/raids.cpp +++ b/src/raids.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -228,7 +228,9 @@ bool Raid::loadFromXml(const std::string& filename) } //sort by delay time - std::sort(raidEvents.begin(), raidEvents.end(), RaidEvent::compareEvents); + std::sort(raidEvents.begin(), raidEvents.end(), [](const RaidEvent* lhs, const RaidEvent* rhs) { + return lhs->getDelay() < rhs->getDelay(); + }); loaded = true; return true; @@ -479,10 +481,6 @@ bool AreaSpawnEvent::configureRaidEvent(const pugi::xml_node& eventNode) } } - if ((attr = eventNode.attribute("lifetime"))) { - lifetime = pugi::cast(attr.value()); - } - for (auto monsterNode : eventNode.children()) { const char* name; @@ -545,10 +543,6 @@ bool AreaSpawnEvent::executeEvent() if (!success) { delete monster; } - - if (lifetime > 0) { - monster->lifetime = OTSYS_TIME() + lifetime; - } } } return true; diff --git a/src/raids.h b/src/raids.h index 3c97d1d..bb6d75d 100644 --- a/src/raids.h +++ b/src/raids.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -167,10 +167,6 @@ class RaidEvent return delay; } - static bool compareEvents(const RaidEvent* lhs, const RaidEvent* rhs) { - return lhs->getDelay() < rhs->getDelay(); - } - private: uint32_t delay; }; @@ -180,9 +176,9 @@ class AnnounceEvent final : public RaidEvent public: AnnounceEvent() = default; - bool configureRaidEvent(const pugi::xml_node& eventNode) final; + bool configureRaidEvent(const pugi::xml_node& eventNode) override; - bool executeEvent() final; + bool executeEvent() override; private: std::string message; @@ -192,9 +188,9 @@ class AnnounceEvent final : public RaidEvent class SingleSpawnEvent final : public RaidEvent { public: - bool configureRaidEvent(const pugi::xml_node& eventNode) final; + bool configureRaidEvent(const pugi::xml_node& eventNode) override; - bool executeEvent() final; + bool executeEvent() override; private: std::string monsterName; @@ -204,14 +200,13 @@ class SingleSpawnEvent final : public RaidEvent class AreaSpawnEvent final : public RaidEvent { public: - bool configureRaidEvent(const pugi::xml_node& eventNode) final; + bool configureRaidEvent(const pugi::xml_node& eventNode) override; - bool executeEvent() final; + bool executeEvent() override; private: std::list spawnList; Position fromPos, toPos; - uint32_t lifetime = 0; }; class ScriptEvent final : public RaidEvent, public Event @@ -219,15 +214,15 @@ class ScriptEvent final : public RaidEvent, public Event public: explicit ScriptEvent(LuaScriptInterface* interface) : Event(interface) {} - bool configureRaidEvent(const pugi::xml_node& eventNode) final; - bool configureEvent(const pugi::xml_node&) final { + bool configureRaidEvent(const pugi::xml_node& eventNode) override; + bool configureEvent(const pugi::xml_node&) override { return false; } - bool executeEvent() final; + bool executeEvent() override; - protected: - std::string getScriptEventName() const final; + private: + std::string getScriptEventName() const override; }; #endif diff --git a/src/rsa.cpp b/src/rsa.cpp index ba557ac..0c95e4d 100644 --- a/src/rsa.cpp +++ b/src/rsa.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,7 +31,7 @@ static CryptoPP::AutoSeededRandomPool prng; void RSA::decrypt(char* msg) const { - CryptoPP::Integer m{ reinterpret_cast(msg), 128 }; + CryptoPP::Integer m{reinterpret_cast(msg), 128}; auto c = pk.CalculateInverse(prng, m); c.Encode(reinterpret_cast(msg), 128); } @@ -41,11 +41,11 @@ static const std::string footer = "-----END RSA PRIVATE KEY-----"; void RSA::loadPEM(const std::string& filename) { - std::ifstream file{ filename }; + std::ifstream file{filename}; if (!file.is_open()) { throw std::runtime_error("Missing file " + filename + "."); - } + } std::ostringstream oss; for (std::string line; std::getline(file, line); oss << line); @@ -73,8 +73,7 @@ void RSA::loadPEM(const std::string& filename) if (!pk.Validate(prng, 3)) { throw std::runtime_error("RSA private key is not valid."); } - } - catch (const CryptoPP::Exception& e) { + } catch (const CryptoPP::Exception& e) { std::cout << e.what() << '\n'; } } diff --git a/src/rsa.h b/src/rsa.h index 0783e7c..888e3b6 100644 --- a/src/rsa.h +++ b/src/rsa.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,18 +26,18 @@ class RSA { -public: - RSA() = default; + public: + RSA() = default; - // non-copyable - RSA(const RSA&) = delete; - RSA& operator=(const RSA&) = delete; + // non-copyable + RSA(const RSA&) = delete; + RSA& operator=(const RSA&) = delete; - void loadPEM(const std::string& filename); - void decrypt(char* msg) const; + void loadPEM(const std::string& filename); + void decrypt(char* msg) const; -private: - CryptoPP::RSA::PrivateKey pk; + private: + CryptoPP::RSA::PrivateKey pk; }; -#endif \ No newline at end of file +#endif diff --git a/src/scheduler.cpp b/src/scheduler.cpp index 28fe462..f489f61 100644 --- a/src/scheduler.cpp +++ b/src/scheduler.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,7 +35,7 @@ void Scheduler::threadMain() } // the mutex is locked again now... - if (ret == std::cv_status::timeout) { + if (ret == std::cv_status::timeout && !eventList.empty()) { // ok we had a timeout, so there has to be an event we have to execute... SchedulerTask* task = eventList.top(); eventList.pop(); @@ -60,54 +60,54 @@ void Scheduler::threadMain() uint32_t Scheduler::addEvent(SchedulerTask* task) { - bool do_signal = false; eventLock.lock(); - if (getState() == THREAD_STATE_RUNNING) { - // check if the event has a valid id - if (task->getEventId() == 0) { - // if not generate one - if (++lastEventId == 0) { - lastEventId = 1; - } - - task->setEventId(lastEventId); - } - - // insert the event id in the list of active events - eventIds.insert(task->getEventId()); - - // add the event to the queue - eventList.push(task); - - // if the list was empty or this event is the top in the list - // we have to signal it - do_signal = (task == eventList.top()); - } else { + if (getState() != THREAD_STATE_RUNNING) { eventLock.unlock(); delete task; return 0; } + // check if the event has a valid id + if (task->getEventId() == 0) { + // if not generate one + if (++lastEventId == 0) { + lastEventId = 1; + } + + task->setEventId(lastEventId); + } + + // insert the event id in the list of active events + uint32_t eventId = task->getEventId(); + eventIds.insert(eventId); + + // add the event to the queue + eventList.push(task); + + // if the list was empty or this event is the top in the list + // we have to signal it + bool do_signal = (task == eventList.top()); + eventLock.unlock(); if (do_signal) { eventSignal.notify_one(); } - return task->getEventId(); + return eventId; } -bool Scheduler::stopEvent(uint32_t eventid) +bool Scheduler::stopEvent(uint32_t eventId) { - if (eventid == 0) { + if (eventId == 0) { return false; } std::lock_guard lockClass(eventLock); // search the event id.. - auto it = eventIds.find(eventid); + auto it = eventIds.find(eventId); if (it == eventIds.end()) { return false; } @@ -132,3 +132,7 @@ void Scheduler::shutdown() eventSignal.notify_one(); } +SchedulerTask* createSchedulerTask(uint32_t delay, std::function f) +{ + return new SchedulerTask(delay, std::move(f)); +} diff --git a/src/scheduler.h b/src/scheduler.h index cc8a839..e42f59f 100644 --- a/src/scheduler.h +++ b/src/scheduler.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,18 +42,15 @@ class SchedulerTask : public Task return expiration; } - protected: - SchedulerTask(uint32_t delay, const std::function& f) : Task(delay, f) {} + private: + SchedulerTask(uint32_t delay, std::function&& f) : Task(delay, std::move(f)) {} uint32_t eventId = 0; - friend SchedulerTask* createSchedulerTask(uint32_t, const std::function&); + friend SchedulerTask* createSchedulerTask(uint32_t, std::function); }; -inline SchedulerTask* createSchedulerTask(uint32_t delay, const std::function& f) -{ - return new SchedulerTask(delay, f); -} +SchedulerTask* createSchedulerTask(uint32_t delay, std::function f); struct TaskComparator { bool operator()(const SchedulerTask* lhs, const SchedulerTask* rhs) const { @@ -70,7 +67,8 @@ class Scheduler : public ThreadHolder void shutdown(); void threadMain(); - protected: + + private: std::thread thread; std::mutex eventLock; std::condition_variable eventSignal; diff --git a/src/script.cpp b/src/script.cpp index 40e955e..144d9d2 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -1,432 +1,99 @@ /** -* Tibia GIMUD Server - a free and open-source MMORPG server emulator -* Copyright (C) 2019 Sabrehaven and Mark Samman -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License along -* with this program; if not, write to the Free Software Foundation, Inc., -* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ #include "otpch.h" #include "script.h" +#include +#include "configmanager.h" -void ScriptReader::nextToken() +extern LuaEnvironment g_luaEnvironment; +extern ConfigManager g_config; + +Scripts::Scripts() : + scriptInterface("Scripts Interface") { - signed int v1; // esi@3 - int v4; // eax@5 - int v5; // eax@8 - int v6; // edi@8 - int v7; // eax@24 - int v9; // eax@32 - int v10; // eax@36 - int v11; // edi@36 - int v12; // eax@45 - int v13; // edi@45 - int v14; // eax@52 - int v15; // edi@52 - int v16; // eax@55 - int v17; // eax@62 - int v18; // eax@70 - int v19; // eax@78 - int v20; // edi@78 - int v21; // eax@86 - int v22; // eax@90 - int v23; // edi@90 - int v24; // eax@94 - int v25; // eax@98 - int v26; // edi@98 - int v27; // eax@102 - int v28; // eax@121 - int v29; // eax@127 - int v30; // eax@131 - int v31; // eax@136 - int v32; // eax@139 - std::string FileName; // [sp+1Ch] [bp-1Ch]@2 - int Sign; // [sp+20h] [bp-18h]@5 - FILE *f; // [sp+24h] [bp-14h]@5 - int pos; // [sp+28h] [bp-10h]@3 + scriptInterface.initState(); +} - if (this->RecursionDepth == -1) - { - error("ScriptReader::nextToken: Kein Skript zum Lesen geffnet.\n"); - LABEL_30: - this->Token = ENDOFFILE; - return; +Scripts::~Scripts() +{ + scriptInterface.reInitState(); +} + +bool Scripts::loadScripts(std::string folderName, bool isLib, bool reload) +{ + namespace fs = boost::filesystem; + + const auto dir = fs::current_path() / "data" / folderName; + if(!fs::exists(dir) || !fs::is_directory(dir)) { + std::cout << "[Warning - Scripts::loadScripts] Can not load folder '" << folderName << "'." << std::endl; + return false; } - FileName = String; -LABEL_3: - pos = 0; - v1 = 0; - this->String = ""; - v4 = this->RecursionDepth; - this->Number = 0; - Sign = 1; - f = this->File[v4]; - while (2) - { - if (pos == 3999) - error("string too long"); - switch (v1) - { - case 0: - v5 = getc(f); - v6 = v5; - if (v5 == -1) - goto LABEL_24; - if (v5 == 10) - { - ++this->Line[this->RecursionDepth]; - } else if (!isspace(v5)) - { - v1 = 1; - if (v6 != 35) - { - v1 = 30; - if (v6 != 64) - { - if (isalpha(v6)) - { - v1 = 2; - this->String += v6; - pos++; - } else if (IsDigit(v6)) - { - this->Number = v6 - 48; - v1 = 3; - } else - { - v1 = 6; - if (v6 != 34) - { - v1 = 11; - if (v6 != 91) - { - v1 = 22; - if (v6 != 60) - { - v1 = 25; - if (v6 != 62) - { - v1 = 27; - if (v6 != 45) - { - v1 = 10; - this->Special = v6; - } - } - } - } - } - } - } - } - } + + fs::recursive_directory_iterator endit; + std::vector v; + std::string disable = ("#"); + for(fs::recursive_directory_iterator it(dir); it != endit; ++it) { + auto fn = it->path().parent_path().filename(); + if ((fn == "lib" && !isLib) || fn == "events") { continue; - case 1: - v9 = getc(f); - if (v9 != -1) - { - if (v9 == 10) - { - ++this->Line[this->RecursionDepth]; - LABEL_35: - v1 = 0; + } + if(fs::is_regular_file(*it) && it->path().extension() == ".lua") { + size_t found = it->path().filename().string().find(disable); + if (found != std::string::npos) { + if (g_config.getBoolean(ConfigManager::SCRIPTS_CONSOLE_LOGS)) { + std::cout << "> " << it->path().filename().string() << " [disabled]" << std::endl; } continue; } - LABEL_24: - v7 = this->RecursionDepth; - if (v7 <= 0) - goto LABEL_30; - if (v7 == -1) - { - printf("ScriptReader::close: Keine Datei offen.\n"); - } else - { - if (fclose(this->File[v7])) - { - printf("ScriptReader::close: Fehler %d beim Schlieen der Datei.\n", RecursionDepth); - } - --this->RecursionDepth; - } - goto LABEL_3; - case 2: - v10 = getc(f); - v11 = v10; - if (pos == 30) - error("identifier too long"); - if (v10 == -1) - goto LABEL_43; - if (isalpha(v10) || IsDigit(v11) || v11 == 95) - goto LABEL_39; - ungetc(v11, f); - LABEL_43: - this->Token = IDENTIFIER; - return; - case 3: - v12 = getc(f); - v13 = v12; - if (v12 == -1) - goto LABEL_50; - if (IsDigit(v12)) - goto LABEL_51; - if (v13 == 45) - goto LABEL_48; - ungetc(v13, f); - LABEL_50: - this->Token = NUMBER; - return; - case 4: - v14 = getc(f); - v15 = v14; - if (v14 == -1) - error("unexpected end of file"); - if (!IsDigit(v14)) - error("syntax error"); - v1 = 5; - this->Number = v15 - 48; - continue; - case 5: - v16 = getc(f); - v13 = v16; - if (v16 == -1) - goto LABEL_59; - if (IsDigit(v16)) - goto LABEL_51; - if (v13 != 45) - { - ungetc(v13, f); - LABEL_59: - this->Bytes[pos] = this->Number; - this->Token = BYTES; - return; - } - LABEL_48: - this->Bytes[pos++] = this->Number; - v1 = 4; - continue; - case 6: - v17 = getc(f); - v11 = v17; - if (v17 == -1) - error("unexpected end of file"); - if (v17 == 34) - { - v1 = 8; - } else if (v17 == 92) - { - v1 = 7; - } else - { - if (v17 == 10) - ++this->Line[this->RecursionDepth]; - LABEL_39: - this->String += v11; - pos++; - } - continue; - case 7: - v18 = getc(f); - if (v18 == -1) - error("unexpected end of file"); - if (v18 == 110) { - this->String += 10; - pos++; - } else { - this->String += v18; - pos++; - } - v1 = 6; - continue; - case 8: - this->Token = STRING; - return; - case 10: - goto LABEL_77; - case 11: - v19 = getc(f); - this->Special = 91; - v20 = v19; - if (v19 == -1) - goto LABEL_77; - if (IsDigit(v19)) - { - Sign = 1; - this->Number = v20 - 48; - LABEL_82: - v1 = 12; - continue; - } - if (v20 == 45) - { - Sign = -1; - this->Number = 0; - goto LABEL_82; - } - LABEL_83: - ungetc(v20, f); - LABEL_77: - this->Token = SPECIAL; - return; - case 12: - v21 = getc(f); - v13 = v21; - if (v21 == -1) - error("unexpected end of file"); - if (IsDigit(v21)) - goto LABEL_51; - if (v13 != 44) - error("syntax error"); - v1 = 13; - this->CoordX = this->Number * Sign; - continue; - case 13: - v22 = getc(f); - v23 = v22; - if (v22 == -1) - error("unexpected end of file"); - if (IsDigit(v22)) - { - Sign = 1; - this->Number = v23 - 48; - } else - { - if (v23 != 45) - error("syntax error"); - Sign = -1; - this->Number = 0; - } - v1 = 14; - continue; - case 14: - v24 = getc(f); - v13 = v24; - if (v24 == -1) - error("unexpected end of file"); - if (IsDigit(v24)) - goto LABEL_51; - if (v13 != 44) - error("syntax error"); - v1 = 15; - this->CoordY = this->Number * Sign; - continue; - case 15: - v25 = getc(f); - v26 = v25; - if (v25 == -1) - error("unexpected end of file"); - if (IsDigit(v25)) - { - Sign = 1; - this->Number = v26 - 48; - } else - { - if (v26 != 45) - error("syntax error"); - Sign = -1; - this->Number = 0; - } - v1 = 16; - continue; - case 16: - v27 = getc(f); - v13 = v27; - if (v27 == -1) - error("unexpected end of file"); - if (IsDigit(v27)) - { - LABEL_51: - this->Number = v13 + 10 * this->Number - 48; - } else - { - if (v13 != 93) - error("syntax error"); - v1 = 17; - this->CoordZ = this->Number * Sign; - } - continue; - case 17: - this->Token = COORDINATE; - return; - case 22: - v28 = getc(f); - this->Special = 60; - if (v28 == -1) - goto LABEL_77; - v1 = 23; - if (v28 == 61) - continue; - v1 = 24; - if (v28 == 62) - continue; - ungetc(v28, f); - goto LABEL_77; - case 23: - this->Special = 76; - goto LABEL_77; - case 24: - this->Special = 78; - goto LABEL_77; - case 25: - v29 = getc(f); - this->Special = 62; - v20 = v29; - if (v29 == -1) - goto LABEL_77; - v1 = 26; - if (v29 != 61) - goto LABEL_83; - continue; - case 26: - this->Special = 71; - goto LABEL_77; - case 27: - v30 = getc(f); - this->Special = 45; - if (v30 == -1) - goto LABEL_77; - v1 = 28; - if (v30 == 62) - continue; - ungetc(v30, f); - goto LABEL_77; - case 28: - this->Special = 73; - goto LABEL_77; - default: - error("ScriptReader::nextToken: Ungnltiger Zustand.\n"); - goto LABEL_35; - case 30: - v31 = getc(f); - if (v31 == -1) - error("unexpected end of file"); - if (v31 != 34) - error("syntax error"); - v1 = 31; - continue; - case 31: - v32 = getc(f); - v11 = v32; - if (v32 == -1) - error("unexpected end of file"); - if (v32 != 34) - goto LABEL_39; - v1 = 32; - continue; - case 32: - open(CurrentDirectory + "/" + String); - goto LABEL_3; + v.push_back(it->path()); } } + sort(v.begin(), v.end()); + std::string redir; + for (auto it = v.begin(); it != v.end(); ++it) { + const std::string scriptFile = it->string(); + if (!isLib) { + if (redir.empty() || redir != it->parent_path().string()) { + auto p = fs::path(it->relative_path()); + if (g_config.getBoolean(ConfigManager::SCRIPTS_CONSOLE_LOGS)) { + std::cout << ">> [" << p.parent_path().filename() << "]" << std::endl; + } + redir = it->parent_path().string(); + } + } + + if(scriptInterface.loadFile(scriptFile) == -1) { + std::cout << "> " << it->filename().string() << " [error]" << std::endl; + std::cout << "^ " << scriptInterface.getLastLuaError() << std::endl; + continue; + } + + if (g_config.getBoolean(ConfigManager::SCRIPTS_CONSOLE_LOGS)) { + if (!reload) { + std::cout << "> " << it->filename().string() << " [loaded]" << std::endl; + } else { + std::cout << "> " << it->filename().string() << " [reloaded]" << std::endl; + } + } + } + + return true; } diff --git a/src/script.h b/src/script.h index 60d646a..1a23cb3 100644 --- a/src/script.h +++ b/src/script.h @@ -1,277 +1,40 @@ /** -* Tibia GIMUD Server - a free and open-source MMORPG server emulator -* Copyright (C) 2019 Sabrehaven and Mark Samman -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License along -* with this program; if not, write to the Free Software Foundation, Inc., -* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ -#ifndef FS_SCRIPT_H_2905B3D5EAB34B4BA8830167262D2DC1 -#define FS_SCRIPT_H_2905B3D5EAB34B4BA8830167262D2DC1 +#ifndef FS_SCRIPTS_H +#define FS_SCRIPTS_H -#include "tools.h" +#include "luascript.h" +#include "enums.h" -enum TOKEN +class Scripts { - ENDOFFILE = 0, - IDENTIFIER, - NUMBER, - STRING, - BYTES, - COORDINATE, - SPECIAL -}; + public: + Scripts(); + ~Scripts(); -class ScriptReader -{ -public: - ScriptReader() - { - Token = ENDOFFILE; - RecursionDepth = -1; - } - - ~ScriptReader() - { - if (RecursionDepth != -1) - { - std::cout << "ScriptReader::~ScriptReader: File is still open.\n"; - for (int i = RecursionDepth; i != -1; i = RecursionDepth) - { - if (fclose(File[i])) - { - std::cout << "ScriptReader::close: Error when closing the file.\n"; - } - --RecursionDepth; - } + bool loadScripts(std::string folderName, bool isLib, bool reload); + LuaScriptInterface& getScriptInterface() { + return scriptInterface; } - } - - TOKEN Token; - FILE* File[3]; - int RecursionDepth; - char Filename[3][4096]; - std::string CurrentDirectory; - std::string String; - unsigned char Bytes[1000]; - int Line[3]; - int Number; - uint16_t CoordX; - uint16_t CoordY; - uint8_t CoordZ; - char Special; - - bool open(const std::string& FileName) - { - RecursionDepth++; - if (RecursionDepth == 3) - { - error("ScriptReader::open: too big recursion.\n"); - error("Recursion depth too high.\n"); - return false; - } - - if (RecursionDepth > -1) - { - CurrentDirectory = FileName; - if (FileName.find('/') != std::string::npos) { - int32_t end = FileName.find_last_of('/'); - CurrentDirectory = FileName.substr(0, end); - strcpy(Filename[RecursionDepth], FileName.substr(end + 1, FileName.length() - end).c_str()); - } else { - strcpy(Filename[RecursionDepth], FileName.c_str()); - } - - File[RecursionDepth] = fopen(FileName.c_str(), "rb"); - if (!File[RecursionDepth]) - { - printf("ScriptReader::open: Can not open file %s.\n", FileName.c_str()); - RecursionDepth--; - printf("Cannot open script-file\n"); - return false; - } - } - - Line[RecursionDepth] = 1; - return true; - } - - void close() - { - int depth; // eax@1 - - depth = RecursionDepth; - if (depth == -1) - { - std::cout << "ScriptReader::close: Invalid recursion depth.\n"; - } else - { - if (fclose(this->File[depth])) - { - std::cout << "ScriptReader::close: Error when closing file.\n"; - } - --RecursionDepth; - } - } - - void error(const std::string& text) - { - int depth = this->RecursionDepth; - if (depth != -1) - { - printf("error in script-file \"%s\", line %d: %s\n", Filename[this->RecursionDepth], this->Line[this->RecursionDepth], text.c_str()); - do - { - if (fclose(this->File[depth])) - { - std::cout << "ScriptReader::close: Error when closing file.\n"; - } - --this->RecursionDepth; - depth = this->RecursionDepth; - } while (this->RecursionDepth != -1); - } - } - - void nextToken(); - - std::string readIdentifier() - { - nextToken(); - if (this->Token != IDENTIFIER) - error("identifier expected"); - if (this->Token != IDENTIFIER) - error("identifier expected"); - String = asLowerCaseString(String); - return std::string(this->String); - } - - int readNumber() - { - TOKEN v1; // edx@1 - int v2; // esi@1 - - nextToken(); - v1 = this->Token; - v2 = 1; - if (this->Token == SPECIAL && this->Special == 45) - { - v2 = -1; - nextToken(); - v1 = this->Token; - } - if (v1 != NUMBER) - error("number expected"); - if (this->Token != NUMBER) - error("number expected"); - return this->Number * v2; - } - - std::string readString() - { - nextToken(); - if (this->Token != STRING) - error("string expected"); - if (this->Token != STRING) - error("string expected"); - return this->String; - } - - uint8_t* readBytesequence() - { - nextToken(); - if (this->Token != 4) - error("byte-sequence expected"); - if (this->Token != 4) - error("byte-sequence expected"); - return this->Bytes; - } - - void readCoordinate(uint16_t& x, uint16_t& y, uint8_t& z) - { - nextToken(); - if (this->Token != COORDINATE) - error("coordinates expected"); - if (this->Token != COORDINATE) - error("coordinates expected"); - x = this->CoordX; - y = this->CoordY; - z = this->CoordZ; - } - - int readSpecial() - { - nextToken(); - if (this->Token != SPECIAL) - error("special-char expected"); - if (this->Token != SPECIAL) - error("special-char expected"); - return this->Special; - } - - void readSymbol(char Symbol) - { - nextToken(); - if (this->Token != SPECIAL) - error("special-char expected"); - if (Symbol != this->Special) - error("special-char expected"); - } - - std::string getIdentifier() - { - if (this->Token != IDENTIFIER) - error("identifier expected"); - String = asLowerCaseString(String); - return this->String; - } - - int getNumber() - { - if (this->Token != NUMBER) - error("number expected"); - return this->Number; - } - - std::string getString() - { - if (this->Token != STRING) - error("string expected"); - return this->String; - } - - uint8_t* getBytesequence() - { - if (this->Token != BYTES) - error("byte-sequence expected"); - return this->Bytes; - } - - void getcoordinate(int32_t& x, int32_t& y, int32_t& z) - { - if (this->Token != COORDINATE) - error("coordinates expected"); - x = this->CoordX; - y = this->CoordY; - z = this->CoordZ; - } - - int getSpecial() - { - if (this->Token != SPECIAL) - error("special-char expected"); - return this->Special; - } + private: + LuaScriptInterface scriptInterface; }; #endif diff --git a/src/scriptmanager.cpp b/src/scriptmanager.cpp index f372962..b166da7 100644 --- a/src/scriptmanager.cpp +++ b/src/scriptmanager.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,8 +26,10 @@ #include "talkaction.h" #include "spells.h" #include "movement.h" +#include "weapons.h" #include "globalevent.h" #include "events.h" +#include "script.h" Actions* g_actions = nullptr; CreatureEvents* g_creatureEvents = nullptr; @@ -37,12 +39,15 @@ GlobalEvents* g_globalEvents = nullptr; Spells* g_spells = nullptr; TalkActions* g_talkActions = nullptr; MoveEvents* g_moveEvents = nullptr; +Weapons* g_weapons = nullptr; +Scripts* g_scripts = nullptr; extern LuaEnvironment g_luaEnvironment; ScriptingManager::~ScriptingManager() { delete g_events; + delete g_weapons; delete g_spells; delete g_actions; delete g_talkActions; @@ -50,6 +55,7 @@ ScriptingManager::~ScriptingManager() delete g_chat; delete g_creatureEvents; delete g_globalEvents; + delete g_scripts; } bool ScriptingManager::loadScriptSystems() @@ -58,8 +64,23 @@ bool ScriptingManager::loadScriptSystems() std::cout << "[Warning - ScriptingManager::loadScriptSystems] Can not load data/global.lua" << std::endl; } + g_scripts = new Scripts(); + std::cout << ">> Loading lua libs" << std::endl; + if (!g_scripts->loadScripts("scripts/lib", true, false)) { + std::cout << "> ERROR: Unable to load lua libs!" << std::endl; + return false; + } + g_chat = new Chat(); + g_weapons = new Weapons(); + if (!g_weapons->loadFromXml()) { + std::cout << "> ERROR: Unable to load weapons!" << std::endl; + return false; + } + + g_weapons->loadDefaults(); + g_spells = new Spells(); if (!g_spells->loadFromXml()) { std::cout << "> ERROR: Unable to load spells!" << std::endl; @@ -102,6 +123,5 @@ bool ScriptingManager::loadScriptSystems() return false; } - return true; } diff --git a/src/scriptmanager.h b/src/scriptmanager.h index fc5da53..d3b52e4 100644 --- a/src/scriptmanager.h +++ b/src/scriptmanager.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,9 +30,9 @@ class ScriptingManager ScriptingManager(const ScriptingManager&) = delete; ScriptingManager& operator=(const ScriptingManager&) = delete; - static ScriptingManager* getInstance() { + static ScriptingManager& getInstance() { static ScriptingManager instance; - return &instance; + return instance; } bool loadScriptSystems(); diff --git a/src/server.cpp b/src/server.cpp index d681456..7d58986 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -56,8 +56,7 @@ void ServiceManager::stop() for (auto& servicePortIt : acceptors) { try { io_service.post(std::bind(&ServicePort::onStopServer, servicePortIt.second)); - } - catch (boost::system::system_error& e) { + } catch (boost::system::system_error& e) { std::cout << "[ServiceManager::stop] Network Error: " << e.what() << std::endl; } } @@ -115,28 +114,25 @@ void ServicePort::onAccept(Connection_ptr connection, const boost::system::error Service_ptr service = services.front(); if (service->is_single_socket()) { connection->accept(service->make_protocol(connection)); - } - else { + } else { connection->accept(); } - } - else { + } else { connection->close(Connection::FORCE_CLOSE); } accept(); - } - else if (error != boost::asio::error::operation_aborted) { + } else if (error != boost::asio::error::operation_aborted) { if (!pendingStart) { close(); pendingStart = true; g_scheduler.addEvent(createSchedulerTask(15000, - std::bind(&ServicePort::openAcceptor, std::weak_ptr(shared_from_this()), serverPort))); + std::bind(&ServicePort::openAcceptor, std::weak_ptr(shared_from_this()), serverPort))); } } } -Protocol_ptr ServicePort::make_protocol(NetworkMessage& msg, const Connection_ptr& connection) const +Protocol_ptr ServicePort::make_protocol(bool checksummed, NetworkMessage& msg, const Connection_ptr& connection) const { uint8_t protocolID = msg.getByte(); for (auto& service : services) { @@ -144,7 +140,9 @@ Protocol_ptr ServicePort::make_protocol(NetworkMessage& msg, const Connection_pt continue; } - return service->make_protocol(connection); + if ((checksummed && service->is_checksummed()) || !service->is_checksummed()) { + return service->make_protocol(connection); + } } return nullptr; } @@ -171,23 +169,21 @@ void ServicePort::open(uint16_t port) try { if (g_config.getBoolean(ConfigManager::BIND_ONLY_GLOBAL_ADDRESS)) { acceptor.reset(new boost::asio::ip::tcp::acceptor(io_service, boost::asio::ip::tcp::endpoint( - boost::asio::ip::address(boost::asio::ip::address_v4::from_string(g_config.getString(ConfigManager::IP))), serverPort))); - } - else { + boost::asio::ip::address(boost::asio::ip::address_v4::from_string(g_config.getString(ConfigManager::IP))), serverPort))); + } else { acceptor.reset(new boost::asio::ip::tcp::acceptor(io_service, boost::asio::ip::tcp::endpoint( - boost::asio::ip::address(boost::asio::ip::address_v4(INADDR_ANY)), serverPort))); + boost::asio::ip::address(boost::asio::ip::address_v4(INADDR_ANY)), serverPort))); } acceptor->set_option(boost::asio::ip::tcp::no_delay(true)); accept(); - } - catch (boost::system::system_error& e) { + } catch (boost::system::system_error& e) { std::cout << "[ServicePort::open] Error: " << e.what() << std::endl; pendingStart = true; g_scheduler.addEvent(createSchedulerTask(15000, - std::bind(&ServicePort::openAcceptor, std::weak_ptr(shared_from_this()), port))); + std::bind(&ServicePort::openAcceptor, std::weak_ptr(shared_from_this()), port))); } } @@ -201,7 +197,7 @@ void ServicePort::close() bool ServicePort::add_service(const Service_ptr& new_svc) { - if (std::any_of(services.begin(), services.end(), [](const Service_ptr& svc) {return svc->is_single_socket(); })) { + if (std::any_of(services.begin(), services.end(), [](const Service_ptr& svc) {return svc->is_single_socket();})) { return false; } diff --git a/src/server.h b/src/server.h index e1d9aa2..02cb373 100644 --- a/src/server.h +++ b/src/server.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,100 +21,106 @@ #define FS_SERVER_H_984DA68ABF744127850F90CC710F281B #include "connection.h" +#include "signals.h" #include class Protocol; class ServiceBase { -public: - virtual bool is_single_socket() const = 0; - virtual uint8_t get_protocol_identifier() const = 0; - virtual const char* get_protocol_name() const = 0; + public: + virtual bool is_single_socket() const = 0; + virtual bool is_checksummed() const = 0; + virtual uint8_t get_protocol_identifier() const = 0; + virtual const char* get_protocol_name() const = 0; - virtual Protocol_ptr make_protocol(const Connection_ptr& c) const = 0; + virtual Protocol_ptr make_protocol(const Connection_ptr& c) const = 0; }; template class Service final : public ServiceBase { -public: - bool is_single_socket() const final { - return ProtocolType::server_sends_first; - } - uint8_t get_protocol_identifier() const final { - return ProtocolType::protocol_identifier; - } - const char* get_protocol_name() const final { - return ProtocolType::protocol_name(); - } + public: + bool is_single_socket() const override { + return ProtocolType::server_sends_first; + } + bool is_checksummed() const override { + return ProtocolType::use_checksum; + } + uint8_t get_protocol_identifier() const override { + return ProtocolType::protocol_identifier; + } + const char* get_protocol_name() const override { + return ProtocolType::protocol_name(); + } - Protocol_ptr make_protocol(const Connection_ptr& c) const final { - return std::make_shared(c); - } + Protocol_ptr make_protocol(const Connection_ptr& c) const override { + return std::make_shared(c); + } }; class ServicePort : public std::enable_shared_from_this { -public: - explicit ServicePort(boost::asio::io_service& io_service) : io_service(io_service) {} - ~ServicePort(); + public: + explicit ServicePort(boost::asio::io_service& io_service) : io_service(io_service) {} + ~ServicePort(); - // non-copyable - ServicePort(const ServicePort&) = delete; - ServicePort& operator=(const ServicePort&) = delete; + // non-copyable + ServicePort(const ServicePort&) = delete; + ServicePort& operator=(const ServicePort&) = delete; - static void openAcceptor(std::weak_ptr weak_service, uint16_t port); - void open(uint16_t port); - void close(); - bool is_single_socket() const; - std::string get_protocol_names() const; + static void openAcceptor(std::weak_ptr weak_service, uint16_t port); + void open(uint16_t port); + void close(); + bool is_single_socket() const; + std::string get_protocol_names() const; - bool add_service(const Service_ptr& new_svc); - Protocol_ptr make_protocol(NetworkMessage& msg, const Connection_ptr& connection) const; + bool add_service(const Service_ptr& new_svc); + Protocol_ptr make_protocol(bool checksummed, NetworkMessage& msg, const Connection_ptr& connection) const; - void onStopServer(); - void onAccept(Connection_ptr connection, const boost::system::error_code& error); + void onStopServer(); + void onAccept(Connection_ptr connection, const boost::system::error_code& error); -protected: - void accept(); + private: + void accept(); - boost::asio::io_service& io_service; - std::unique_ptr acceptor; - std::vector services; + boost::asio::io_service& io_service; + std::unique_ptr acceptor; + std::vector services; - uint16_t serverPort = 0; - bool pendingStart = false; + uint16_t serverPort = 0; + bool pendingStart = false; }; class ServiceManager { -public: - ServiceManager() = default; - ~ServiceManager(); + public: + ServiceManager() = default; + ~ServiceManager(); - // non-copyable - ServiceManager(const ServiceManager&) = delete; - ServiceManager& operator=(const ServiceManager&) = delete; + // non-copyable + ServiceManager(const ServiceManager&) = delete; + ServiceManager& operator=(const ServiceManager&) = delete; - void run(); - void stop(); + void run(); + void stop(); - template - bool add(uint16_t port); + template + bool add(uint16_t port); - bool is_running() const { - return acceptors.empty() == false; - } + bool is_running() const { + return acceptors.empty() == false; + } -protected: - void die(); + private: + void die(); - std::unordered_map acceptors; + std::unordered_map acceptors; - boost::asio::io_service io_service; - boost::asio::deadline_timer death_timer{ io_service }; - bool running = false; + boost::asio::io_service io_service; + Signals signals{io_service}; + boost::asio::deadline_timer death_timer { io_service }; + bool running = false; }; template @@ -133,14 +139,13 @@ bool ServiceManager::add(uint16_t port) service_port = std::make_shared(io_service); service_port->open(port); acceptors[port] = service_port; - } - else { + } else { service_port = foundServicePort->second; if (service_port->is_single_socket() || ProtocolType::server_sends_first) { std::cout << "ERROR: " << ProtocolType::protocol_name() << - " and " << service_port->get_protocol_names() << - " cannot use the same port " << port << '.' << std::endl; + " and " << service_port->get_protocol_names() << + " cannot use the same port " << port << '.' << std::endl; return false; } } diff --git a/src/signals.cpp b/src/signals.cpp new file mode 100644 index 0000000..9ed5277 --- /dev/null +++ b/src/signals.cpp @@ -0,0 +1,212 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" +#include + +#include "signals.h" +#include "tasks.h" +#include "game.h" +#include "actions.h" +#include "configmanager.h" +#include "spells.h" +#include "talkaction.h" +#include "movement.h" +#include "weapons.h" +#include "raids.h" +#include "quests.h" +#include "mounts.h" +#include "globalevent.h" +#include "monster.h" +#include "events.h" +#include "scheduler.h" +#include "databasetasks.h" + + +extern Scheduler g_scheduler; +extern DatabaseTasks g_databaseTasks; +extern Dispatcher g_dispatcher; + +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 Weapons* g_weapons; +extern Game g_game; +extern CreatureEvents* g_creatureEvents; +extern GlobalEvents* g_globalEvents; +extern Events* g_events; +extern Chat* g_chat; +extern LuaEnvironment g_luaEnvironment; + +using ErrorCode = boost::system::error_code; + +Signals::Signals(boost::asio::io_service& service) : + set(service) +{ + set.add(SIGINT); + set.add(SIGTERM); +#ifndef _WIN32 + set.add(SIGUSR1); + set.add(SIGHUP); +#else + // This must be a blocking call as Windows calls it in a new thread and terminates + // the process when the handler returns (or after 5 seconds, whichever is earlier). + // On Windows it is called in a new thread. + signal(SIGBREAK, dispatchSignalHandler); +#endif + + asyncWait(); +} + +void Signals::asyncWait() +{ + set.async_wait([this] (ErrorCode err, int signal) { + if (err) { + std::cerr << "Signal handling error: " << err.message() << std::endl; + return; + } + dispatchSignalHandler(signal); + asyncWait(); + }); +} + +// On Windows this function does not need to be signal-safe, +// as it is called in a new thread. +// https://github.com/otland/forgottenserver/pull/2473 +void Signals::dispatchSignalHandler(int signal) +{ + switch(signal) { + case SIGINT: //Shuts the server down + g_dispatcher.addTask(createTask(sigintHandler)); + break; + case SIGTERM: //Shuts the server down + g_dispatcher.addTask(createTask(sigtermHandler)); + break; +#ifndef _WIN32 + case SIGHUP: //Reload config/data + g_dispatcher.addTask(createTask(sighupHandler)); + break; + case SIGUSR1: //Saves game state + g_dispatcher.addTask(createTask(sigusr1Handler)); + break; +#else + case SIGBREAK: //Shuts the server down + g_dispatcher.addTask(createTask(sigbreakHandler)); + // hold the thread until other threads end + g_scheduler.join(); + g_databaseTasks.join(); + g_dispatcher.join(); + break; +#endif + default: + break; + } +} + +void Signals::sigbreakHandler() +{ + //Dispatcher thread + std::cout << "SIGBREAK received, shutting game server down..." << std::endl; + g_game.setGameState(GAME_STATE_SHUTDOWN); +} + +void Signals::sigtermHandler() +{ + //Dispatcher thread + std::cout << "SIGTERM received, shutting game server down..." << std::endl; + g_game.setGameState(GAME_STATE_SHUTDOWN); +} + +void Signals::sigusr1Handler() +{ + //Dispatcher thread + std::cout << "SIGUSR1 received, saving the game state..." << std::endl; + g_game.saveGameState(); +} + +void Signals::sighupHandler() +{ + //Dispatcher thread + std::cout << "SIGHUP received, reloading config files..." << std::endl; + + g_actions->reload(); + std::cout << "Reloaded actions." << std::endl; + + g_config.reload(); + std::cout << "Reloaded config." << std::endl; + + g_creatureEvents->reload(); + std::cout << "Reloaded creature scripts." << std::endl; + + g_moveEvents->reload(); + std::cout << "Reloaded movements." << std::endl; + + Npcs::reload(); + std::cout << "Reloaded npcs." << std::endl; + + g_game.raids.reload(); + g_game.raids.startup(); + std::cout << "Reloaded raids." << std::endl; + + g_spells->reload(); + std::cout << "Reloaded monsters." << std::endl; + + g_monsters.reload(); + std::cout << "Reloaded spells." << std::endl; + + g_talkActions->reload(); + std::cout << "Reloaded talk actions." << std::endl; + + Item::items.reload(); + std::cout << "Reloaded items." << std::endl; + + g_weapons->reload(); + g_weapons->loadDefaults(); + std::cout << "Reloaded weapons." << std::endl; + + g_game.quests.reload(); + std::cout << "Reloaded quests." << std::endl; + + g_game.mounts.reload(); + std::cout << "Reloaded mounts." << std::endl; + + g_globalEvents->reload(); + std::cout << "Reloaded globalevents." << std::endl; + + g_events->load(); + std::cout << "Reloaded events." << std::endl; + + g_chat->load(); + std::cout << "Reloaded chatchannels." << std::endl; + + g_luaEnvironment.loadFile("data/global.lua"); + std::cout << "Reloaded global.lua." << std::endl; + + lua_gc(g_luaEnvironment.getLuaState(), LUA_GCCOLLECT, 0); +} + +void Signals::sigintHandler() +{ + //Dispatcher thread + std::cout << "SIGINT received, shutting game server down..." << std::endl; + g_game.setGameState(GAME_STATE_SHUTDOWN); +} diff --git a/src/signals.h b/src/signals.h new file mode 100644 index 0000000..5306852 --- /dev/null +++ b/src/signals.h @@ -0,0 +1,42 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_SIGNALHANDLINGTHREAD_H_01C6BF08B0EFE9E200175D108CF0B35F +#define FS_SIGNALHANDLINGTHREAD_H_01C6BF08B0EFE9E200175D108CF0B35F + +#include + +class Signals +{ + boost::asio::signal_set set; + public: + explicit Signals(boost::asio::io_service& service); + + private: + void asyncWait(); + static void dispatchSignalHandler(int signal); + + static void sigbreakHandler(); + static void sigintHandler(); + static void sighupHandler(); + static void sigtermHandler(); + static void sigusr1Handler(); +}; + +#endif diff --git a/src/spawn.cpp b/src/spawn.cpp index 8b08492..446d4e0 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -64,6 +64,9 @@ bool Spawns::loadFromXml(const std::string& filename) radius = -1; } + spawnList.emplace_front(centerPos, radius); + Spawn& spawn = spawnList.front(); + for (auto childNode : spawnNode.children()) { if (strcasecmp(childNode.name(), "monster") == 0) { pugi::xml_attribute nameAttribute = childNode.attribute("name"); @@ -85,18 +88,9 @@ bool Spawns::loadFromXml(const std::string& filename) centerPos.y + pugi::cast(childNode.attribute("y").value()), centerPos.z ); - - spawnList.emplace_front(pos, radius); - Spawn& spawn = spawnList.front(); - uint32_t interval = pugi::cast(childNode.attribute("spawntime").value()) * 1000; if (interval > MINSPAWN_INTERVAL) { - uint32_t exInterval = g_config.getNumber(ConfigManager::RATE_SPAWN); - if (exInterval) { - spawn.addMonster(nameAttribute.as_string(), pos, dir, exInterval * 1000); - } else { - spawn.addMonster(nameAttribute.as_string(), pos, dir, interval); - } + spawn.addMonster(nameAttribute.as_string(), pos, dir, interval); } else { std::cout << "[Warning - Spawns::loadFromXml] " << nameAttribute.as_string() << ' ' << pos << " spawntime can not be less than " << MINSPAWN_INTERVAL / 1000 << " seconds." << std::endl; } @@ -186,9 +180,9 @@ Spawn::~Spawn() bool Spawn::findPlayer(const Position& pos) { - SpectatorVec list; - g_game.map.getSpectators(list, pos, false, true); - for (Creature* spectator : list) { + SpectatorVec spectators; + g_game.map.getSpectators(spectators, pos, false, true); + for (Creature* spectator : spectators) { if (!spectator->getPlayer()->hasFlag(PlayerFlag_IgnoredByMonsters)) { return true; } @@ -226,26 +220,6 @@ bool Spawn::spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& p return true; } -uint32_t Spawn::getInterval() const -{ - uint32_t newInterval = interval; - - if (newInterval > 500000) { - size_t playersOnline = g_game.getPlayersOnline(); - if (playersOnline <= 800) { - if (playersOnline > 200) { - newInterval = 200 * interval / (playersOnline / 2 + 100); - } - } else { - newInterval = 2 * interval / 5; - } - - return normal_random(newInterval / 2, newInterval); - } - - return newInterval; -} - void Spawn::startup() { for (const auto& it : spawnMap) { @@ -261,6 +235,8 @@ void Spawn::checkSpawn() cleanup(); + uint32_t spawnCount = 0; + for (auto& it : spawnMap) { uint32_t spawnId = it.first; if (spawnedMap.find(spawnId) != spawnedMap.end()) { @@ -275,6 +251,9 @@ void Spawn::checkSpawn() } spawnMonster(spawnId, sb.mType, sb.pos, sb.direction); + if (++spawnCount >= static_cast(g_config.getNumber(ConfigManager::RATE_SPAWN))) { + break; + } } } @@ -296,6 +275,9 @@ void Spawn::cleanup() monster->decrementReferenceCounter(); it = spawnedMap.erase(it); + } else if (!isInSpawnZone(monster->getPosition()) && spawnId != 0) { + spawnedMap.insert(spawned_pair(0, monster)); + it = spawnedMap.erase(it); } else { ++it; } diff --git a/src/spawn.h b/src/spawn.h index aece74e..15657e6 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -48,7 +48,9 @@ class Spawn bool addMonster(const std::string& name, const Position& pos, Direction dir, uint32_t interval); void removeMonster(Monster* monster); - uint32_t getInterval() const; + uint32_t getInterval() const { + return interval; + } void startup(); void startSpawnCheck(); @@ -59,8 +61,8 @@ class Spawn private: //map of the spawned creatures - typedef std::multimap SpawnedMap; - typedef SpawnedMap::value_type spawned_pair; + using SpawnedMap = std::multimap; + using spawned_pair = SpawnedMap::value_type; SpawnedMap spawnedMap; //map of creatures in the spawn diff --git a/src/spectators.h b/src/spectators.h new file mode 100644 index 0000000..990d957 --- /dev/null +++ b/src/spectators.h @@ -0,0 +1,83 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_SPECTATORS_H_D78A7CCB7080406E8CAA6B1D31D3DA71 +#define FS_SPECTATORS_H_D78A7CCB7080406E8CAA6B1D31D3DA71 + +#include + +class Creature; + +class SpectatorVec +{ + using Vec = std::vector; + using Iterator = Vec::iterator; + using ConstIterator = Vec::const_iterator; +public: + SpectatorVec() { + vec.reserve(32); + } + + void addSpectators(const SpectatorVec& spectators) { + const size_t size = vec.size(); + for (Creature* spectator : spectators.vec) { + bool duplicate = false; + for (size_t i = 0; i < size; ++i) { + if (vec[i] == spectator) { + duplicate = true; + break; + } + } + + if (!duplicate) { + vec.emplace_back(spectator); + } + } + } + + void erase(Creature* spectator) { + for (size_t i = 0, len = vec.size(); i < len; i++) { + if (vec[i] == spectator) { + Creature* tmp = vec[len - 1]; + vec[len - 1] = vec[i]; + vec[i] = tmp; + vec.pop_back(); + break; + } + } + } + + inline size_t size() const { return vec.size(); } + inline bool empty() const { return vec.empty(); } + inline Iterator begin() { return vec.begin(); } + inline ConstIterator begin() const { return vec.begin(); } + inline ConstIterator cbegin() const { return vec.cbegin(); } + inline Iterator end() { return vec.end(); } + inline ConstIterator end() const { return vec.end(); } + inline ConstIterator cend() const { return vec.cend(); } + inline void emplace_back(Creature* c) { return vec.emplace_back(c); } + + template + inline void insert(Iterator pos, InputIterator first, InputIterator last) { vec.insert(pos, first, last); } + +private: + Vec vec; +}; + +#endif diff --git a/src/spells.cpp b/src/spells.cpp index 03500da..36b79e0 100644 --- a/src/spells.cpp +++ b/src/spells.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,7 +40,7 @@ Spells::Spells() Spells::~Spells() { - clear(); + clear(false); } TalkActionResult_t Spells::playerSaySpell(Player* player, std::string& words) @@ -50,15 +50,6 @@ TalkActionResult_t Spells::playerSaySpell(Player* player, std::string& words) //strip trailing spaces trimString(str_words); - std::ostringstream str_instantSpell; - for (size_t i = 0; i < str_words.length(); i++) { - if (!isspace(str_words[i]) || (i < str_words.length() - 1 && !isspace(str_words[i + 1]))) { - str_instantSpell << str_words[i]; - } - } - - str_words = str_instantSpell.str(); - InstantSpell* instantSpell = getInstantSpell(str_words); if (!instantSpell) { return TALKACTION_CONTINUE; @@ -94,25 +85,42 @@ TalkActionResult_t Spells::playerSaySpell(Player* player, std::string& words) } if (instantSpell->playerCastInstant(player, param)) { + words = instantSpell->getWords(); + + if (instantSpell->getHasParam() && !param.empty()) { + words += " \"" + param + "\""; + } + return TALKACTION_BREAK; } return TALKACTION_FAILED; } -void Spells::clear() +void Spells::clearMaps(bool fromLua) { - for (const auto& it : runes) { - delete it.second; + for (auto instant = instants.begin(); instant != instants.end(); ) { + if (fromLua == instant->second.fromLua) { + instant = instants.erase(instant); + } else { + ++instant; + } } - runes.clear(); - for (const auto& it : instants) { - delete it.second; + for (auto rune = runes.begin(); rune != runes.end(); ) { + if (fromLua == rune->second.fromLua) { + rune = runes.erase(rune); + } else { + ++rune; + } } - instants.clear(); +} - scriptInterface.reInitState(); +void Spells::clear(bool fromLua) +{ + clearMaps(fromLua); + + reInitState(fromLua); } LuaScriptInterface& Spells::getScriptInterface() @@ -125,32 +133,30 @@ std::string Spells::getScriptBaseName() const return "spells"; } -Event* Spells::getEvent(const std::string& nodeName) +Event_ptr Spells::getEvent(const std::string& nodeName) { if (strcasecmp(nodeName.c_str(), "rune") == 0) { - return new RuneSpell(&scriptInterface); + return Event_ptr(new RuneSpell(&scriptInterface)); } else if (strcasecmp(nodeName.c_str(), "instant") == 0) { - return new InstantSpell(&scriptInterface); - } else if (strcasecmp(nodeName.c_str(), "conjure") == 0) { - return new ConjureSpell(&scriptInterface); + return Event_ptr(new InstantSpell(&scriptInterface)); } return nullptr; } -bool Spells::registerEvent(Event* event, const pugi::xml_node&) +bool Spells::registerEvent(Event_ptr event, const pugi::xml_node&) { - InstantSpell* instant = dynamic_cast(event); + InstantSpell* instant = dynamic_cast(event.get()); if (instant) { - auto result = instants.emplace(instant->getWords(), instant); + auto result = instants.emplace(instant->getWords(), std::move(*instant)); if (!result.second) { std::cout << "[Warning - Spells::registerEvent] Duplicate registered instant spell with words: " << instant->getWords() << std::endl; } return result.second; } - RuneSpell* rune = dynamic_cast(event); + RuneSpell* rune = dynamic_cast(event.get()); if (rune) { - auto result = runes.emplace(rune->getRuneItemId(), rune); + auto result = runes.emplace(rune->getRuneItemId(), std::move(*rune)); if (!result.second) { std::cout << "[Warning - Spells::registerEvent] Duplicate registered rune with id: " << rune->getRuneItemId() << std::endl; } @@ -160,6 +166,36 @@ bool Spells::registerEvent(Event* event, const pugi::xml_node&) return false; } +bool Spells::registerInstantLuaEvent(InstantSpell* event) +{ + InstantSpell_ptr instant { event }; + if (instant) { + std::string words = instant->getWords(); + auto result = instants.emplace(instant->getWords(), std::move(*instant)); + if (!result.second) { + std::cout << "[Warning - Spells::registerInstantLuaEvent] Duplicate registered instant spell with words: " << words << std::endl; + } + return result.second; + } + + return false; +} + +bool Spells::registerRuneLuaEvent(RuneSpell* event) +{ + RuneSpell_ptr rune { event }; + if (rune) { + uint16_t id = rune->getRuneItemId(); + auto result = runes.emplace(rune->getRuneItemId(), std::move(*rune)); + if (!result.second) { + std::cout << "[Warning - Spells::registerRuneLuaEvent] Duplicate registered rune with id: " << id << std::endl; + } + return result.second; + } + + return false; +} + Spell* Spells::getSpellByName(const std::string& name) { Spell* spell = getRuneSpellByName(name); @@ -173,16 +209,21 @@ RuneSpell* Spells::getRuneSpell(uint32_t id) { auto it = runes.find(id); if (it == runes.end()) { + for (auto& rune : runes) { + if (rune.second.getId() == id) { + return &rune.second; + } + } return nullptr; } - return it->second; + return &it->second; } RuneSpell* Spells::getRuneSpellByName(const std::string& name) { - for (const auto& it : runes) { - if (strcasecmp(it.second->getName().c_str(), name.c_str()) == 0) { - return it.second; + for (auto& it : runes) { + if (strcasecmp(it.second.getName().c_str(), name.c_str()) == 0) { + return &it.second; } } return nullptr; @@ -192,14 +233,12 @@ InstantSpell* Spells::getInstantSpell(const std::string& words) { InstantSpell* result = nullptr; - for (const auto& it : instants) { - InstantSpell* instantSpell = it.second; - - const std::string& instantSpellWords = instantSpell->getWords(); + for (auto& it : instants) { + const std::string& instantSpellWords = it.second.getWords(); size_t spellLen = instantSpellWords.length(); if (strncasecmp(instantSpellWords.c_str(), words.c_str(), spellLen) == 0) { if (!result || spellLen > result->getWords().length()) { - result = instantSpell; + result = &it.second; if (words.length() == spellLen) { break; } @@ -210,41 +249,26 @@ InstantSpell* Spells::getInstantSpell(const std::string& words) if (result) { const std::string& resultWords = result->getWords(); if (words.length() > resultWords.length()) { + if (!result->getHasParam()) { + return nullptr; + } + size_t spellLen = resultWords.length(); size_t paramLen = words.length() - spellLen; if (paramLen < 2 || words[spellLen] != ' ') { return nullptr; } } - return result; } - return nullptr; } -uint32_t Spells::getInstantSpellCount(const Player* player) const +InstantSpell* Spells::getInstantSpellById(uint32_t spellId) { - uint32_t count = 0; - for (const auto& it : instants) { - InstantSpell* instantSpell = it.second; - if (instantSpell->canCast(player)) { - ++count; - } - } - return count; -} - -InstantSpell* Spells::getInstantSpellByIndex(const Player* player, uint32_t index) -{ - uint32_t count = 0; - for (const auto& it : instants) { - InstantSpell* instantSpell = it.second; - if (instantSpell->canCast(player)) { - if (count == index) { - return instantSpell; - } - ++count; + for (auto& it : instants) { + if (it.second.getId() == spellId) { + return &it.second; } } return nullptr; @@ -252,9 +276,9 @@ InstantSpell* Spells::getInstantSpellByIndex(const Player* player, uint32_t inde InstantSpell* Spells::getInstantSpellByName(const std::string& name) { - for (const auto& it : instants) { - if (strcasecmp(it.second->getName().c_str(), name.c_str()) == 0) { - return it.second; + for (auto& it : instants) { + if (strcasecmp(it.second.getName().c_str(), name.c_str()) == 0) { + return &it.second; } } return nullptr; @@ -378,7 +402,7 @@ bool Spell::configureSpell(const pugi::xml_node& node) name = nameAttribute.as_string(); - /*static const char* reservedList[] = { + static const char* reservedList[] = { "melee", "physical", "poison", @@ -399,6 +423,9 @@ bool Spell::configureSpell(const pugi::xml_node& node) "poisoncondition", "energycondition", "drowncondition", + "freezecondition", + "cursecondition", + "dazzlecondition" }; //static size_t size = sizeof(reservedList) / sizeof(const char*); @@ -408,18 +435,60 @@ bool Spell::configureSpell(const pugi::xml_node& node) std::cout << "[Error - Spell::configureSpell] Spell is using a reserved name: " << reserved << std::endl; return false; } - }*/ + } pugi::xml_attribute attr; if ((attr = node.attribute("spellid"))) { spellId = pugi::cast(attr.value()); } - if ((attr = node.attribute("lvl"))) { + if ((attr = node.attribute("group"))) { + std::string tmpStr = asLowerCaseString(attr.as_string()); + if (tmpStr == "none" || tmpStr == "0") { + group = SPELLGROUP_NONE; + } else if (tmpStr == "attack" || tmpStr == "1") { + group = SPELLGROUP_ATTACK; + } else if (tmpStr == "healing" || tmpStr == "2") { + group = SPELLGROUP_HEALING; + } else if (tmpStr == "support" || tmpStr == "3") { + group = SPELLGROUP_SUPPORT; + } else if (tmpStr == "special" || tmpStr == "4") { + group = SPELLGROUP_SPECIAL; + } else { + std::cout << "[Warning - Spell::configureSpell] Unknown group: " << attr.as_string() << std::endl; + } + } + + if ((attr = node.attribute("groupcooldown"))) { + groupCooldown = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("secondarygroup"))) { + std::string tmpStr = asLowerCaseString(attr.as_string()); + if (tmpStr == "none" || tmpStr == "0") { + secondaryGroup = SPELLGROUP_NONE; + } else if (tmpStr == "attack" || tmpStr == "1") { + secondaryGroup = SPELLGROUP_ATTACK; + } else if (tmpStr == "healing" || tmpStr == "2") { + secondaryGroup = SPELLGROUP_HEALING; + } else if (tmpStr == "support" || tmpStr == "3") { + secondaryGroup = SPELLGROUP_SUPPORT; + } else if (tmpStr == "special" || tmpStr == "4") { + secondaryGroup = SPELLGROUP_SPECIAL; + } else { + std::cout << "[Warning - Spell::configureSpell] Unknown secondarygroup: " << attr.as_string() << std::endl; + } + } + + if ((attr = node.attribute("secondarygroupcooldown"))) { + secondaryGroupCooldown = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("level")) || (attr = node.attribute("lvl"))) { level = pugi::cast(attr.value()); } - if ((attr = node.attribute("maglv"))) { + if ((attr = node.attribute("magiclevel")) || (attr = node.attribute("maglv"))) { magLevel = pugi::cast(attr.value()); } @@ -439,11 +508,11 @@ bool Spell::configureSpell(const pugi::xml_node& node) range = pugi::cast(attr.value()); } - if ((attr = node.attribute("exhaustion")) || (attr = node.attribute("cooldown"))) { + if ((attr = node.attribute("cooldown")) || (attr = node.attribute("exhaustion"))) { cooldown = pugi::cast(attr.value()); } - if ((attr = node.attribute("prem"))) { + if ((attr = node.attribute("premium")) || (attr = node.attribute("prem"))) { premium = attr.as_bool(); } @@ -490,6 +559,10 @@ bool Spell::configureSpell(const pugi::xml_node& node) aggressive = booleanString(attr.as_string()); } + if (group == SPELLGROUP_NONE) { + group = (aggressive ? SPELLGROUP_ATTACK : SPELLGROUP_HEALING); + } + for (auto vocationNode : node.children()) { if (!(attr = vocationNode.attribute("name"))) { continue; @@ -520,12 +593,23 @@ bool Spell::playerSpellCheck(Player* player) const return false; } - if (aggressive && !player->hasFlag(PlayerFlag_IgnoreProtectionZone) && player->getZone() == ZONE_PROTECTION) { + if (aggressive && (range < 1 || (range > 0 && !player->getAttackedCreature())) && player->getSkull() == SKULL_BLACK) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return false; + } + + if (aggressive && player->hasCondition(CONDITION_PACIFIED)) { + player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (aggressive && !player->hasFlag(PlayerFlag_IgnoreProtectionZone) && player->getZone() == ZONE_PROTECTION) { player->sendCancelMessage(RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE); return false; } - if (player->hasCondition(CONDITION_EXHAUST)) { + if (player->hasCondition(CONDITION_SPELLGROUPCOOLDOWN, group) || player->hasCondition(CONDITION_SPELLCOOLDOWN, spellId) || (secondaryGroup != SPELLGROUP_NONE && player->hasCondition(CONDITION_SPELLGROUPCOOLDOWN, secondaryGroup))) { player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED); if (isInstant()) { @@ -572,14 +656,7 @@ bool Spell::playerSpellCheck(Player* player) const } if (needWeapon) { - Item* weapon = player->getWeapon(); - if (!weapon) { - player->sendCancelMessage(RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - return false; - } - - switch (weapon->getWeaponType()) { + switch (player->getWeaponType()) { case WEAPON_SWORD: case WEAPON_CLUB: case WEAPON_AXE: @@ -688,33 +765,31 @@ bool Spell::playerRuneSpellCheck(Player* player, const Position& toPos) return false; } - const Creature* visibleCreature = tile->getTopCreature(); - if (blockingCreature && visibleCreature) { + const Creature* topVisibleCreature = tile->getBottomVisibleCreature(player); + if (blockingCreature && topVisibleCreature) { player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; - } - else if (blockingSolid && tile->hasFlag(TILESTATE_BLOCKSOLID)) { + } else if (blockingSolid && tile->hasFlag(TILESTATE_BLOCKSOLID)) { player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } - if (needTarget && !visibleCreature) { + if (needTarget && !topVisibleCreature) { player->sendCancelMessage(RETURNVALUE_CANONLYUSETHISRUNEONCREATURES); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } - if (aggressive && needTarget && visibleCreature && player->hasSecureMode()) { - const Player* targetPlayer = visibleCreature->getPlayer(); + if (aggressive && needTarget && topVisibleCreature && player->hasSecureMode()) { + const Player* targetPlayer = topVisibleCreature->getPlayer(); if (targetPlayer && targetPlayer != player && player->getSkullClient(targetPlayer) == SKULL_NONE && !Combat::isInPvpZone(player, targetPlayer)) { player->sendCancelMessage(RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } } - return true; } @@ -722,22 +797,19 @@ void Spell::postCastSpell(Player* player, bool finishedCast /*= true*/, bool pay { if (finishedCast) { if (!player->hasFlag(PlayerFlag_HasNoExhaustion)) { - if (aggressive) { - if (cooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST, cooldown); - player->addCondition(condition); - } else { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST, 2000); - player->addCondition(condition); - } - } else { - if (cooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST, cooldown); - player->addCondition(condition); - } else { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST, 1000); - player->addCondition(condition); - } + if (cooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, cooldown, 0, false, spellId); + player->addCondition(condition); + } + + if (groupCooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, groupCooldown, 0, false, group); + player->addCondition(condition); + } + + if (secondaryGroupCooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, secondaryGroupCooldown, 0, false, secondaryGroup); + player->addCondition(condition); } } @@ -772,50 +844,14 @@ uint32_t Spell::getManaCost(const Player* player) const } if (manaPercent != 0) { - return player->getLevel() * manaPercent; + uint32_t maxMana = player->getMaxMana(); + uint32_t manaCost = (maxMana * manaPercent) / 100; + return manaCost; } return 0; } -ReturnValue Spell::CreateIllusion(Creature* creature, const Outfit_t& outfit, int32_t time) -{ - ConditionOutfit* outfitCondition = new ConditionOutfit(CONDITIONID_COMBAT, CONDITION_OUTFIT, time); - outfitCondition->setOutfit(outfit); - creature->addCondition(outfitCondition); - return RETURNVALUE_NOERROR; -} - -ReturnValue Spell::CreateIllusion(Creature* creature, const std::string& name, int32_t time) -{ - const auto mType = g_monsters.getMonsterType(name); - if (mType == nullptr) { - return RETURNVALUE_CREATUREDOESNOTEXIST; - } - - Player* player = creature->getPlayer(); - if (player && !player->hasFlag(PlayerFlag_CanIllusionAll)) { - if (!mType->info.isIllusionable) { - return RETURNVALUE_NOTPOSSIBLE; - } - } - - return CreateIllusion(creature, mType->info.outfit, time); -} - -ReturnValue Spell::CreateIllusion(Creature* creature, uint32_t itemId, int32_t time) -{ - const ItemType& it = Item::items[itemId]; - if (it.id == 0) { - return RETURNVALUE_NOTPOSSIBLE; - } - - Outfit_t outfit; - outfit.lookTypeEx = itemId; - - return CreateIllusion(creature, outfit, time); -} - std::string InstantSpell::getScriptEventName() const { return "onCastSpell"; @@ -831,6 +867,8 @@ bool InstantSpell::configureEvent(const pugi::xml_node& node) return false; } + spellType = SPELL_INSTANT; + pugi::xml_attribute attr; if ((attr = node.attribute("params"))) { hasParam = attr.as_bool(); @@ -852,34 +890,6 @@ bool InstantSpell::configureEvent(const pugi::xml_node& node) return true; } -bool InstantSpell::loadFunction(const pugi::xml_attribute& attr) -{ - const char* functionName = attr.as_string(); - if (strcasecmp(functionName, "edithouseguest") == 0) { - function = HouseGuestList; - } else if (strcasecmp(functionName, "edithousesubowner") == 0) { - function = HouseSubOwnerList; - } else if (strcasecmp(functionName, "edithousedoor") == 0) { - function = HouseDoorList; - } else if (strcasecmp(functionName, "housekick") == 0) { - function = HouseKick; - } else if (strcasecmp(functionName, "searchplayer") == 0) { - function = SearchPlayer; - } else if (strcasecmp(functionName, "levitate") == 0) { - function = Levitate; - } else if (strcasecmp(functionName, "illusion") == 0) { - function = Illusion; - } else if (strcasecmp(functionName, "summonmonster") == 0) { - function = SummonMonster; - } else { - std::cout << "[Warning - InstantSpell::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; - return false; - } - - scripted = false; - return true; -} - bool InstantSpell::playerCastInstant(Player* player, std::string& param) { if (!playerSpellCheck(player)) { @@ -906,6 +916,21 @@ bool InstantSpell::playerCastInstant(Player* player, std::string& param) target = playerTarget; if (!target || target->getHealth() <= 0) { if (!casterTargetOrDirection) { + if (cooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, cooldown, 0, false, spellId); + player->addCondition(condition); + } + + if (groupCooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, groupCooldown, 0, false, group); + player->addCondition(condition); + } + + if (secondaryGroupCooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, secondaryGroupCooldown, 0, false, secondaryGroup); + player->addCondition(condition); + } + player->sendCancelMessage(ret); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; @@ -955,6 +980,21 @@ bool InstantSpell::playerCastInstant(Player* player, std::string& param) ReturnValue ret = g_game.getPlayerByNameWildcard(param, playerTarget); if (ret != RETURNVALUE_NOERROR) { + if (cooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, cooldown, 0, false, spellId); + player->addCondition(condition); + } + + if (groupCooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, groupCooldown, 0, false, group); + player->addCondition(condition); + } + + if (secondaryGroupCooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, secondaryGroupCooldown, 0, false, secondaryGroup); + player->addCondition(condition); + } + player->sendCancelMessage(ret); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; @@ -1042,13 +1082,7 @@ bool InstantSpell::castSpell(Creature* creature, Creature* target) bool InstantSpell::internalCastSpell(Creature* creature, const LuaVariant& var) { - if (scripted) { - return executeCastSpell(creature, var); - } else if (function) { - return function(this, creature, var.text); - } - - return false; + return executeCastSpell(creature, var); } bool InstantSpell::executeCastSpell(Creature* creature, const LuaVariant& var) @@ -1074,409 +1108,6 @@ bool InstantSpell::executeCastSpell(Creature* creature, const LuaVariant& var) return scriptInterface->callFunction(2); } -House* InstantSpell::getHouseFromPos(Creature* creature) -{ - if (!creature) { - return nullptr; - } - - Player* player = creature->getPlayer(); - if (!player) { - return nullptr; - } - - HouseTile* houseTile = dynamic_cast(player->getTile()); - if (!houseTile) { - return nullptr; - } - - House* house = houseTile->getHouse(); - if (!house) { - return nullptr; - } - - return house; -} - -bool InstantSpell::HouseGuestList(const InstantSpell*, Creature* creature, const std::string&) -{ - House* house = getHouseFromPos(creature); - if (!house) { - return false; - } - - Player* player = creature->getPlayer(); - if (house->canEditAccessList(GUEST_LIST, player)) { - player->setEditHouse(house, GUEST_LIST); - player->sendHouseWindow(house, GUEST_LIST); - } else { - player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - } - return true; -} - -bool InstantSpell::HouseSubOwnerList(const InstantSpell*, Creature* creature, const std::string&) -{ - House* house = getHouseFromPos(creature); - if (!house) { - return false; - } - - Player* player = creature->getPlayer(); - if (house->canEditAccessList(SUBOWNER_LIST, player)) { - player->setEditHouse(house, SUBOWNER_LIST); - player->sendHouseWindow(house, SUBOWNER_LIST); - } else { - player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - } - return true; -} - -bool InstantSpell::HouseDoorList(const InstantSpell*, Creature* creature, const std::string&) -{ - House* house = getHouseFromPos(creature); - if (!house) { - return false; - } - - Player* player = creature->getPlayer(); - Position pos = Spells::getCasterPosition(player, player->getDirection()); - Door* door = house->getDoorByPosition(pos); - if (door && house->canEditAccessList(door->getDoorId(), player)) { - player->setEditHouse(house, door->getDoorId()); - player->sendHouseWindow(house, door->getDoorId()); - } else { - player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - } - return true; -} - -bool InstantSpell::HouseKick(const InstantSpell*, Creature* creature, const std::string& param) -{ - Player* player = creature->getPlayer(); - - Player* targetPlayer = g_game.getPlayerByName(param); - if (!targetPlayer) { - targetPlayer = player; - } - - House* house = getHouseFromPos(targetPlayer); - if (!house) { - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); - return false; - } - - if (!house->kickPlayer(player, targetPlayer)) { - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); - return false; - } - return true; -} - -bool InstantSpell::SearchPlayer(const InstantSpell*, Creature* creature, const std::string& param) -{ - //a. From 1 to 4 sq's [Person] is standing next to you. - //b. From 5 to 100 sq's [Person] is to the south, north, east, west. - //c. From 101 to 274 sq's [Person] is far to the south, north, east, west. - //d. From 275 to infinite sq's [Person] is very far to the south, north, east, west. - //e. South-west, s-e, n-w, n-e (corner coordinates): this phrase appears if the player you're looking for has moved five squares in any direction from the south, north, east or west. - //f. Lower level to the (direction): this phrase applies if the person you're looking for is from 1-25 squares up/down the actual floor you're in. - //g. Higher level to the (direction): this phrase applies if the person you're looking for is from 1-25 squares up/down the actual floor you're in. - - Player* player = creature->getPlayer(); - if (!player) { - return false; - } - - enum distance_t { - DISTANCE_BESIDE, - DISTANCE_CLOSE, - DISTANCE_FAR, - DISTANCE_VERYFAR, - }; - - enum direction_t { - DIR_N, DIR_S, DIR_E, DIR_W, - DIR_NE, DIR_NW, DIR_SE, DIR_SW, - }; - - enum level_t { - LEVEL_HIGHER, - LEVEL_LOWER, - LEVEL_SAME, - }; - - Player* playerExiva = g_game.getPlayerByName(param); - if (!playerExiva) { - return false; - } - - if (playerExiva->isAccessPlayer() && !player->isAccessPlayer()) { - player->sendCancelMessage(RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - return false; - } - - const Position& lookPos = player->getPosition(); - const Position& searchPos = playerExiva->getPosition(); - - int32_t dx = Position::getOffsetX(lookPos, searchPos); - int32_t dy = Position::getOffsetY(lookPos, searchPos); - int32_t dz = Position::getOffsetZ(lookPos, searchPos); - - distance_t distance; - - direction_t direction; - - level_t level; - - //getting floor - if (dz > 0) { - level = LEVEL_HIGHER; - } else if (dz < 0) { - level = LEVEL_LOWER; - } else { - level = LEVEL_SAME; - } - - //getting distance - if (std::abs(dx) < 4 && std::abs(dy) < 4) { - distance = DISTANCE_BESIDE; - } else { - int32_t distance2 = dx * dx + dy * dy; - if (distance2 < 10000) { - distance = DISTANCE_CLOSE; - } else if (distance2 < 75076) { - distance = DISTANCE_FAR; - } else { - distance = DISTANCE_VERYFAR; - } - } - - //getting direction - float tan; - if (dx != 0) { - tan = static_cast(dy) / dx; - } else { - tan = 10.; - } - - if (std::abs(tan) < 0.4142) { - if (dx > 0) { - direction = DIR_W; - } else { - direction = DIR_E; - } - } else if (std::abs(tan) < 2.4142) { - if (tan > 0) { - if (dy > 0) { - direction = DIR_NW; - } else { - direction = DIR_SE; - } - } else { - if (dx > 0) { - direction = DIR_SW; - } else { - direction = DIR_NE; - } - } - } else { - if (dy > 0) { - direction = DIR_N; - } else { - direction = DIR_S; - } - } - - std::ostringstream ss; - ss << playerExiva->getName(); - - if (distance == DISTANCE_BESIDE) { - if (level == LEVEL_SAME) { - ss << " is standing next to you."; - } else if (level == LEVEL_HIGHER) { - ss << " is above you."; - } else if (level == LEVEL_LOWER) { - ss << " is below you."; - } - } else { - switch (distance) { - case DISTANCE_CLOSE: - if (level == LEVEL_SAME) { - ss << " is to the "; - } else if (level == LEVEL_HIGHER) { - ss << " is on a higher level to the "; - } else if (level == LEVEL_LOWER) { - ss << " is on a lower level to the "; - } - break; - case DISTANCE_FAR: - ss << " is far to the "; - break; - case DISTANCE_VERYFAR: - ss << " is very far to the "; - break; - default: - break; - } - - switch (direction) { - case DIR_N: - ss << "north."; - break; - case DIR_S: - ss << "south."; - break; - case DIR_E: - ss << "east."; - break; - case DIR_W: - ss << "west."; - break; - case DIR_NE: - ss << "north-east."; - break; - case DIR_NW: - ss << "north-west."; - break; - case DIR_SE: - ss << "south-east."; - break; - case DIR_SW: - ss << "south-west."; - break; - } - } - player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); - g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_BLUE); - return true; -} - -bool InstantSpell::SummonMonster(const InstantSpell* spell, Creature* creature, const std::string& param) -{ - Player* player = creature->getPlayer(); - if (!player) { - return false; - } - - MonsterType* mType = g_monsters.getMonsterType(param); - if (!mType) { - player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - return false; - } - - if (!player->hasFlag(PlayerFlag_CanSummonAll)) { - if (!mType->info.isSummonable) { - player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - return false; - } - - if (player->getMana() < mType->info.manaCost) { - player->sendCancelMessage(RETURNVALUE_NOTENOUGHMANA); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - return false; - } - - if (player->getSummonCount() >= 2) { - player->sendCancelMessage("You cannot summon more creatures."); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - return false; - } - } - - Monster* monster = Monster::createMonster(param); - if (!monster) { - player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - return false; - } - - // Place the monster - creature->addSummon(monster); - - if (!g_game.placeCreature(monster, creature->getPosition(), true)) { - creature->removeSummon(monster); - player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - return false; - } - - Spell::postCastSpell(player, mType->info.manaCost, spell->getSoulCost()); - g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_BLUE); - g_game.addMagicEffect(monster->getPosition(), CONST_ME_TELEPORT); - return true; -} - -bool InstantSpell::Levitate(const InstantSpell*, Creature* creature, const std::string& param) -{ - Player* player = creature->getPlayer(); - if (!player) { - return false; - } - - const Position& currentPos = creature->getPosition(); - const Position& destPos = Spells::getCasterPosition(creature, creature->getDirection()); - - ReturnValue ret = RETURNVALUE_NOTPOSSIBLE; - - if (strcasecmp(param.c_str(), "up") == 0) { - if (currentPos.z != 8) { - Tile* tmpTile = g_game.map.getTile(currentPos.x, currentPos.y, currentPos.getZ() - 1); - if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID))) { - tmpTile = g_game.map.getTile(destPos.x, destPos.y, destPos.getZ() - 1); - if (tmpTile && tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID)) { - ret = g_game.internalMoveCreature(*player, *tmpTile, FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE); - } - } - } - } else if (strcasecmp(param.c_str(), "down") == 0) { - if (currentPos.z != 7) { - Tile* tmpTile = g_game.map.getTile(destPos); - if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) { - tmpTile = g_game.map.getTile(destPos.x, destPos.y, destPos.z + 1); - if (tmpTile && tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID)) { - ret = g_game.internalMoveCreature(*player, *tmpTile, FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE); - } - } - } - } - - if (ret != RETURNVALUE_NOERROR) { - player->sendCancelMessage(ret); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - return false; - } - - g_game.addMagicEffect(player->getPosition(), CONST_ME_TELEPORT); - return true; -} - -bool InstantSpell::Illusion(const InstantSpell*, Creature* creature, const std::string& param) -{ - Player* player = creature->getPlayer(); - if (!player) { - return false; - } - - ReturnValue ret = CreateIllusion(creature, param, 180000); - if (ret != RETURNVALUE_NOERROR) { - player->sendCancelMessage(ret); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - return false; - } - - g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED); - return true; -} - bool InstantSpell::canCast(const Player* player) const { if (player->hasFlag(PlayerFlag_CannotUseSpells)) { @@ -1500,152 +1131,6 @@ bool InstantSpell::canCast(const Player* player) const return false; } -std::string ConjureSpell::getScriptEventName() const -{ - return "onCastSpell"; -} - -bool ConjureSpell::configureEvent(const pugi::xml_node& node) -{ - if (!InstantSpell::configureEvent(node)) { - return false; - } - - pugi::xml_attribute attr; - if ((attr = node.attribute("conjureId"))) { - conjureId = pugi::cast(attr.value()); - } - - if ((attr = node.attribute("conjureCount"))) { - conjureCount = pugi::cast(attr.value()); - } else if (conjureId != 0) { - // load default charges from items.xml - const ItemType& it = Item::items[conjureId]; - if (it.charges != 0) { - conjureCount = it.charges; - } - } - - if ((attr = node.attribute("reagentId"))) { - reagentId = pugi::cast(attr.value()); - } - - ItemType& iType = Item::items.getItemType(conjureId); - if (iType.isRune()) { - iType.runeSpellName = words; - } - - return true; -} - -bool ConjureSpell::loadFunction(const pugi::xml_attribute&) -{ - scripted = false; - return true; -} - -bool ConjureSpell::conjureItem(Creature* creature) const -{ - Player* player = creature->getPlayer(); - if (!player) { - return false; - } - - const uint32_t conjureCost = getManaCost(player); - const uint32_t soulCost = getSoulCost(); - - if (reagentId != 0) { - bool foundReagent = false; - - Item* item = player->getInventoryItem(CONST_SLOT_LEFT); - if (item && item->getID() == reagentId) { - foundReagent = true; - - // left arm conjure - int32_t index = player->getThingIndex(item); - g_game.internalRemoveItem(item); - - Item* newItem = Item::CreateItem(conjureId, conjureCount); - if (!newItem) { - return false; - } - - ReturnValue ret = g_game.internalAddItem(player, newItem, index); - if (ret != RETURNVALUE_NOERROR) { - delete newItem; - return false; - } - - g_game.startDecay(newItem); - - Spell::postCastSpell(player, conjureCost, soulCost); - } - - item = player->getInventoryItem(CONST_SLOT_RIGHT); - if (item && item->getID() == reagentId && player->getMana() >= conjureCost) { - foundReagent = true; - - // right arm conjure - int32_t index = player->getThingIndex(item); - g_game.internalRemoveItem(item); - - Item* newItem = Item::CreateItem(conjureId, conjureCount); - if (!newItem) { - return false; - } - - ReturnValue ret = g_game.internalAddItem(player, newItem, index); - if (ret != RETURNVALUE_NOERROR) { - delete newItem; - return false; - } - - g_game.startDecay(newItem); - - Spell::postCastSpell(player, conjureCost, soulCost); - } - - if (!foundReagent) { - player->sendCancelMessage(RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - return false; - } - } else { - Item* newItem = Item::CreateItem(conjureId, conjureCount); - if (!newItem) { - return false; - } - - ReturnValue ret = g_game.internalPlayerAddItem(player, newItem); - if (ret != RETURNVALUE_NOERROR) { - delete newItem; - return false; - } - - g_game.startDecay(newItem); - Spell::postCastSpell(player, conjureCost, soulCost); - } - - postCastSpell(player, true, false); - g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED); - return true; -} - -bool ConjureSpell::playerCastInstant(Player* player, std::string& param) -{ - if (!playerSpellCheck(player)) { - return false; - } - - if (scripted) { - LuaVariant var; - var.type = VARIANT_STRING; - var.text = param; - return executeCastSpell(player, var); - } - return conjureItem(player); -} - std::string RuneSpell::getScriptEventName() const { return "onCastSpell"; @@ -1661,6 +1146,8 @@ bool RuneSpell::configureEvent(const pugi::xml_node& node) return false; } + spellType = SPELL_RUNE; + pugi::xml_attribute attr; if (!(attr = node.attribute("id"))) { std::cout << "[Error - RuneSpell::configureSpell] Rune spell without id." << std::endl; @@ -1668,7 +1155,6 @@ bool RuneSpell::configureEvent(const pugi::xml_node& node) } runeId = pugi::cast(attr.value()); - uint32_t charges; if ((attr = node.attribute("charges"))) { charges = pugi::cast(attr.value()); } else { @@ -1676,107 +1162,14 @@ bool RuneSpell::configureEvent(const pugi::xml_node& node) } hasCharges = (charges > 0); - - //Change information in the ItemType to get accurate description - ItemType& iType = Item::items.getItemType(runeId); - iType.runeMagLevel = magLevel; - iType.runeLevel = level; - iType.charges = charges; - - return true; -} - -bool RuneSpell::loadFunction(const pugi::xml_attribute& attr) -{ - const char* functionName = attr.as_string(); - if (strcasecmp(functionName, "chameleon") == 0) { - runeFunction = Illusion; - } else if (strcasecmp(functionName, "convince") == 0) { - runeFunction = Convince; - } else { - std::cout << "[Warning - RuneSpell::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; - return false; + if (magLevel != 0 || level != 0) { + //Change information in the ItemType to get accurate description + ItemType& iType = Item::items.getItemType(runeId); + iType.runeMagLevel = magLevel; + iType.runeLevel = level; + iType.charges = charges; } - scripted = false; - return true; -} - -bool RuneSpell::Illusion(const RuneSpell*, Player* player, const Position& posTo) -{ - Thing* thing = g_game.internalGetThing(player, posTo, 0, 0, STACKPOS_MOVE); - if (!thing) { - player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - return false; - } - - Item* illusionItem = thing->getItem(); - if (!illusionItem || !illusionItem->isMoveable()) { - player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - return false; - } - - uint32_t itemId = illusionItem->getID(); - if (illusionItem->isDisguised()) { - itemId = illusionItem->getDisguiseId(); - } - - ReturnValue ret = CreateIllusion(player, itemId, 200000); - if (ret != RETURNVALUE_NOERROR) { - player->sendCancelMessage(ret); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - return false; - } - - g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED); - return true; -} - -bool RuneSpell::Convince(const RuneSpell* spell, Player* player, const Position& posTo) -{ - if (!player->hasFlag(PlayerFlag_CanConvinceAll)) { - if (player->getSummonCount() >= 2) { - player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - return false; - } - } - - Thing* thing = g_game.internalGetThing(player, posTo, 0, 0, STACKPOS_LOOK); - if (!thing) { - player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - return false; - } - - Creature* convinceCreature = thing->getCreature(); - if (!convinceCreature) { - player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - return false; - } - - uint32_t manaCost = 0; - if (convinceCreature->getMonster()) { - manaCost = convinceCreature->getMonster()->getManaCost(); - } - - if (!player->hasFlag(PlayerFlag_HasInfiniteMana) && player->getMana() < manaCost) { - player->sendCancelMessage(RETURNVALUE_NOTENOUGHMANA); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - return false; - } - - if (!convinceCreature->convinceCreature(player)) { - player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - return false; - } - - Spell::postCastSpell(player, manaCost, spell->getSoulCost()); - g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED); return true; } @@ -1825,12 +1218,10 @@ bool RuneSpell::executeUse(Player* player, Item* item, const Position&, Thing* t var.number = visibleCreature->getID(); } } - } - else { + } else { var.number = target->getCreature()->getID(); } - } - else { + } else { var.type = VARIANT_POSITION; var.pos = toPosition; } @@ -1868,8 +1259,7 @@ bool RuneSpell::internalCastSpell(Creature* creature, const LuaVariant& var, boo bool result; if (scripted) { result = executeCastSpell(creature, var, isHotkey); - } - else { + } else { result = false; } return result; diff --git a/src/spells.h b/src/spells.h index d0907fe..d59dd99 100644 --- a/src/spells.h +++ b/src/spells.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,11 +27,12 @@ #include "baseevents.h" class InstantSpell; -class ConjureSpell; class RuneSpell; class Spell; -typedef std::map VocSpellMap; +using VocSpellMap = std::map; +using InstantSpell_ptr = std::unique_ptr; +using RuneSpell_ptr = std::unique_ptr; class Spells final : public BaseEvents { @@ -50,29 +51,35 @@ class Spells final : public BaseEvents InstantSpell* getInstantSpell(const std::string& words); InstantSpell* getInstantSpellByName(const std::string& name); - uint32_t getInstantSpellCount(const Player* player) const; - InstantSpell* getInstantSpellByIndex(const Player* player, uint32_t index); + InstantSpell* getInstantSpellById(uint32_t spellId); TalkActionResult_t playerSaySpell(Player* player, std::string& words); static Position getCasterPosition(Creature* creature, Direction dir); std::string getScriptBaseName() const override; - protected: - void clear() final; - LuaScriptInterface& getScriptInterface() final; - Event* getEvent(const std::string& nodeName) final; - bool registerEvent(Event* event, const pugi::xml_node& node) final; + const std::map& getInstantSpells() const { + return instants; + }; - std::map runes; - std::map instants; + void clearMaps(bool fromLua); + void clear(bool fromLua) override final; + bool registerInstantLuaEvent(InstantSpell* event); + bool registerRuneLuaEvent(RuneSpell* event); + + private: + LuaScriptInterface& getScriptInterface() override; + Event_ptr getEvent(const std::string& nodeName) override; + bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; + + std::map runes; + std::map instants; friend class CombatSpell; LuaScriptInterface scriptInterface { "Spell Interface" }; }; -typedef bool (InstantSpellFunction)(const InstantSpell* spell, Creature* creature, const std::string& param); -typedef bool (RuneSpellFunction)(const RuneSpell* spell, Player* player, const Position& posTo); +using RuneSpellFunction = std::function; class BaseSpell { @@ -108,7 +115,7 @@ class CombatSpell final : public Event, public BaseSpell return combat; } - protected: + private: std::string getScriptEventName() const override { return "onCastSpell"; } @@ -128,58 +135,191 @@ class Spell : public BaseSpell const std::string& getName() const { return name; } + void setName(std::string n) { + name = n; + } + uint8_t getId() const { + return spellId; + } + void setId(uint8_t id) { + spellId = id; + } - void postCastSpell(Player* player, bool finishedSpell = true, bool payCost = true) const; + void postCastSpell(Player* player, bool finishedCast = true, bool payCost = true) const; static void postCastSpell(Player* player, uint32_t manaCost, uint32_t soulCost); uint32_t getManaCost(const Player* player) const; uint32_t getSoulCost() const { return soul; } + void setSoulCost(uint32_t s) { + soul = s; + } uint32_t getLevel() const { return level; } + void setLevel(uint32_t lvl) { + level = lvl; + } uint32_t getMagicLevel() const { return magLevel; } + void setMagicLevel(uint32_t lvl) { + magLevel = lvl; + } + uint32_t getMana() const { + return mana; + } + void setMana(uint32_t m) { + mana = m; + } uint32_t getManaPercent() const { return manaPercent; } + void setManaPercent(uint32_t m) { + manaPercent = m; + } bool isPremium() const { return premium; } + void setPremium(bool p) { + premium = p; + } + bool isEnabled() const { + return enabled; + } + void setEnabled(bool e) { + enabled = e; + } virtual bool isInstant() const = 0; bool isLearnable() const { return learnable; } - - static ReturnValue CreateIllusion(Creature* creature, const Outfit_t& outfit, int32_t time); - static ReturnValue CreateIllusion(Creature* creature, const std::string& name, int32_t time); - static ReturnValue CreateIllusion(Creature* creature, uint32_t itemId, int32_t time); + void setLearnable(bool l) { + learnable = l; + } const VocSpellMap& getVocMap() const { return vocSpellMap; } + void addVocMap(uint16_t n, bool b) { + vocSpellMap[n] = b; + } + + const SpellGroup_t getGroup() const { + return group; + } + void setGroup(SpellGroup_t g) { + group = g; + } + const SpellGroup_t getSecondaryGroup() const { + return secondaryGroup; + } + void setSecondaryGroup(SpellGroup_t g) { + secondaryGroup = g; + } + + uint32_t getCooldown() const { + return cooldown; + } + void setCooldown(uint32_t cd) { + cooldown = cd; + } + uint32_t getSecondaryCooldown() const { + return secondaryGroupCooldown; + } + void setSecondaryCooldown(uint32_t cd) { + secondaryGroupCooldown = cd; + } + uint32_t getGroupCooldown() const { + return groupCooldown; + } + void setGroupCooldown(uint32_t cd) { + groupCooldown = cd; + } + + int32_t getRange() const { + return range; + } + void setRange(int32_t r) { + range = r; + } + + bool getNeedTarget() const { + return needTarget; + } + void setNeedTarget(bool n) { + needTarget = n; + } + bool getNeedWeapon() const { + return needWeapon; + } + void setNeedWeapon(bool n) { + needWeapon = n; + } + bool getNeedLearn() const { + return learnable; + } + void setNeedLearn(bool n) { + learnable = n; + } + bool getSelfTarget() const { + return selfTarget; + } + void setSelfTarget(bool s) { + selfTarget = s; + } + bool getBlockingSolid() const { + return blockingSolid; + } + void setBlockingSolid(bool b) { + blockingSolid = b; + } + bool getBlockingCreature() const { + return blockingCreature; + } + void setBlockingCreature(bool b) { + blockingCreature = b; + } + bool getAggressive() const { + return aggressive; + } + void setAggressive(bool a) { + aggressive = a; + } + + SpellType_t spellType = SPELL_UNDEFINED; protected: bool playerSpellCheck(Player* player) const; bool playerInstantSpellCheck(Player* player, const Position& toPos); bool playerRuneSpellCheck(Player* player, const Position& toPos); - uint8_t spellId = 0; + VocSpellMap vocSpellMap; - uint32_t mana = 0; - uint32_t manaPercent = 0; - uint32_t soul = 0; - uint32_t cooldown = 0; + SpellGroup_t group = SPELLGROUP_NONE; + SpellGroup_t secondaryGroup = SPELLGROUP_NONE; + + uint32_t cooldown = 1000; + uint32_t groupCooldown = 1000; + uint32_t secondaryGroupCooldown = 0; uint32_t level = 0; uint32_t magLevel = 0; int32_t range = -1; - bool needTarget = false; - bool needWeapon = false; + uint8_t spellId = 0; + bool selfTarget = false; + bool needTarget = false; + + private: + + uint32_t mana = 0; + uint32_t manaPercent = 0; + uint32_t soul = 0; + + bool needWeapon = false; bool blockingSolid = false; bool blockingCreature = false; bool aggressive = true; @@ -187,19 +327,17 @@ class Spell : public BaseSpell bool enabled = true; bool premium = false; - VocSpellMap vocSpellMap; private: std::string name; }; -class InstantSpell : public TalkAction, public Spell +class InstantSpell final : public TalkAction, public Spell { public: explicit InstantSpell(LuaScriptInterface* interface) : TalkAction(interface) {} bool configureEvent(const pugi::xml_node& node) override; - bool loadFunction(const pugi::xml_attribute& attr) override; virtual bool playerCastInstant(Player* player, std::string& param); @@ -215,30 +353,41 @@ class InstantSpell : public TalkAction, public Spell bool getHasParam() const { return hasParam; } + void setHasParam(bool p) { + hasParam = p; + } bool getHasPlayerNameParam() const { return hasPlayerNameParam; } + void setHasPlayerNameParam(bool p) { + hasPlayerNameParam = p; + } + bool getNeedDirection() const { + return needDirection; + } + void setNeedDirection(bool n) { + needDirection = n; + } + bool getNeedCasterTargetOrDirection() const { + return casterTargetOrDirection; + } + void setNeedCasterTargetOrDirection(bool d) { + casterTargetOrDirection = d; + } + bool getBlockWalls() const { + return checkLineOfSight; + } + void setBlockWalls(bool w) { + checkLineOfSight = w; + } bool canCast(const Player* player) const; bool canThrowSpell(const Creature* creature, const Creature* target) const; - protected: + private: std::string getScriptEventName() const override; - static InstantSpellFunction HouseGuestList; - static InstantSpellFunction HouseSubOwnerList; - static InstantSpellFunction HouseDoorList; - static InstantSpellFunction HouseKick; - static InstantSpellFunction SearchPlayer; - static InstantSpellFunction SummonMonster; - static InstantSpellFunction Levitate; - static InstantSpellFunction Illusion; - - static House* getHouseFromPos(Creature* creature); - bool internalCastSpell(Creature* creature, const LuaVariant& var); - InstantSpellFunction* function = nullptr; - bool needDirection = false; bool hasParam = false; bool hasPlayerNameParam = false; @@ -246,77 +395,56 @@ class InstantSpell : public TalkAction, public Spell bool casterTargetOrDirection = false; }; -class ConjureSpell final : public InstantSpell -{ - public: - explicit ConjureSpell(LuaScriptInterface* interface) : InstantSpell(interface) { - aggressive = false; - } - - bool configureEvent(const pugi::xml_node& node) final; - bool loadFunction(const pugi::xml_attribute& attr) final; - - bool playerCastInstant(Player* player, std::string& param) final; - - bool castSpell(Creature*) final { - return false; - } - bool castSpell(Creature*, Creature*) final { - return false; - } - - protected: - std::string getScriptEventName() const final; - - bool conjureItem(Creature* creature) const; - - uint32_t conjureId = 0; - uint32_t conjureCount = 1; - uint32_t reagentId = 0; -}; - class RuneSpell final : public Action, public Spell { public: explicit RuneSpell(LuaScriptInterface* interface) : Action(interface) {} - bool configureEvent(const pugi::xml_node& node) final; - bool loadFunction(const pugi::xml_attribute& attr) final; + bool configureEvent(const pugi::xml_node& node) override; - ReturnValue canExecuteAction(const Player* player, const Position& toPos) final; - bool hasOwnErrorHandler() final { + ReturnValue canExecuteAction(const Player* player, const Position& toPos) override; + bool hasOwnErrorHandler() override { return true; } - Thing* getTarget(Player*, Creature* targetCreature, const Position&, uint8_t) const final { + Thing* getTarget(Player*, Creature* targetCreature, const Position&, uint8_t) const override { return targetCreature; } bool executeUse(Player* player, Item* item, const Position& fromPosition, Thing* target, const Position& toPosition, bool isHotkey) override; - bool castSpell(Creature* creature) final; - bool castSpell(Creature* creature, Creature* target) final; + bool castSpell(Creature* creature) override; + bool castSpell(Creature* creature, Creature* target) override; //scripting bool executeCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey); - bool isInstant() const final { + bool isInstant() const override { return false; } uint16_t getRuneItemId() const { return runeId; } + void setRuneItemId(uint16_t i) { + runeId = i; + } + uint32_t getCharges() const { + return charges; + } + void setCharges(uint32_t c) { + if (c > 0) { + hasCharges = true; + } + charges = c; + } - protected: - std::string getScriptEventName() const final; - - static RuneSpellFunction Illusion; - static RuneSpellFunction Convince; + private: + std::string getScriptEventName() const override; bool internalCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey); - RuneSpellFunction* runeFunction = nullptr; uint16_t runeId = 0; - bool hasCharges = true; + uint32_t charges = 0; + bool hasCharges = false; }; #endif diff --git a/src/talkaction.cpp b/src/talkaction.cpp index 8e2a0b7..2a30410 100644 --- a/src/talkaction.cpp +++ b/src/talkaction.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,14 +31,20 @@ TalkActions::TalkActions() TalkActions::~TalkActions() { - clear(); + clear(false); } -void TalkActions::clear() +void TalkActions::clear(bool fromLua) { - talkActions.clear(); + for (auto it = talkActions.begin(); it != talkActions.end(); ) { + if (fromLua == it->second.fromLua) { + it = talkActions.erase(it); + } else { + ++it; + } + } - scriptInterface.reInitState(); + reInitState(fromLua); } LuaScriptInterface& TalkActions::getScriptInterface() @@ -51,29 +57,36 @@ std::string TalkActions::getScriptBaseName() const return "talkactions"; } -Event* TalkActions::getEvent(const std::string& nodeName) +Event_ptr TalkActions::getEvent(const std::string& nodeName) { if (strcasecmp(nodeName.c_str(), "talkaction") != 0) { return nullptr; } - return new TalkAction(&scriptInterface); + return Event_ptr(new TalkAction(&scriptInterface)); } -bool TalkActions::registerEvent(Event* event, const pugi::xml_node&) +bool TalkActions::registerEvent(Event_ptr event, const pugi::xml_node&) { - auto talkAction = std::unique_ptr(static_cast(event)); // event is guaranteed to be a TalkAction - talkActions.push_front(std::move(*talkAction)); + TalkAction_ptr talkAction{static_cast(event.release())}; // event is guaranteed to be a TalkAction + talkActions.emplace(talkAction->getWords(), std::move(*talkAction)); + return true; +} +bool TalkActions::registerLuaEvent(TalkAction* event) +{ + TalkAction_ptr talkAction{ event }; + talkActions.emplace(talkAction->getWords(), std::move(*talkAction)); return true; } TalkActionResult_t TalkActions::playerSaySpell(Player* player, SpeakClasses type, const std::string& words) const { size_t wordsLength = words.length(); - for (const TalkAction& talkAction : talkActions) { - const std::string& talkactionWords = talkAction.getWords(); + for (auto it = talkActions.begin(); it != talkActions.end(); ) { + const std::string& talkactionWords = it->first; size_t talkactionLength = talkactionWords.length(); if (wordsLength < talkactionLength || strncasecmp(words.c_str(), talkactionWords.c_str(), talkactionLength) != 0) { + ++it; continue; } @@ -81,14 +94,16 @@ TalkActionResult_t TalkActions::playerSaySpell(Player* player, SpeakClasses type if (wordsLength != talkactionLength) { param = words.substr(talkactionLength); if (param.front() != ' ') { + ++it; continue; } trim_left(param, ' '); - char separator = talkAction.getSeparator(); - if (separator != ' ') { + std::string separator = it->second.getSeparator(); + if (separator != " ") { if (!param.empty()) { - if (param.front() != separator) { + if (param != separator) { + ++it; continue; } else { param.erase(param.begin()); @@ -97,7 +112,7 @@ TalkActionResult_t TalkActions::playerSaySpell(Player* player, SpeakClasses type } } - if (talkAction.executeSay(player, param, type)) { + if (it->second.executeSay(player, param, type)) { return TALKACTION_CONTINUE; } else { return TALKACTION_BREAK; diff --git a/src/talkaction.h b/src/talkaction.h index 765f336..686c38b 100644 --- a/src/talkaction.h +++ b/src/talkaction.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,38 +24,15 @@ #include "baseevents.h" #include "const.h" +class TalkAction; +using TalkAction_ptr = std::unique_ptr; + enum TalkActionResult_t { TALKACTION_CONTINUE, TALKACTION_BREAK, TALKACTION_FAILED, }; -class TalkAction; - -class TalkActions : public BaseEvents -{ - public: - TalkActions(); - ~TalkActions(); - - // non-copyable - TalkActions(const TalkActions&) = delete; - TalkActions& operator=(const TalkActions&) = delete; - - TalkActionResult_t playerSaySpell(Player* player, SpeakClasses type, const std::string& words) const; - - 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; - - std::forward_list talkActions; - - LuaScriptInterface scriptInterface; -}; - class TalkAction : public Event { public: @@ -66,19 +43,51 @@ class TalkAction : public Event const std::string& getWords() const { return words; } - char getSeparator() const { + void setWords(std::string word) { + words = word; + } + std::string getSeparator() const { return separator; } + void setSeparator(std::string sep) { + separator = sep; + } //scripting bool executeSay(Player* player, const std::string& param, SpeakClasses type) const; // - protected: + private: std::string getScriptEventName() const override; std::string words; - char separator = '"'; + std::string separator = "\""; +}; + +class TalkActions final : public BaseEvents +{ + public: + TalkActions(); + ~TalkActions(); + + // non-copyable + TalkActions(const TalkActions&) = delete; + TalkActions& operator=(const TalkActions&) = delete; + + TalkActionResult_t playerSaySpell(Player* player, SpeakClasses type, const std::string& words) const; + + bool registerLuaEvent(TalkAction* event); + void clear(bool fromLua) override final; + + private: + LuaScriptInterface& getScriptInterface() override; + std::string getScriptBaseName() const override; + Event_ptr getEvent(const std::string& nodeName) override; + bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; + + std::map talkActions; + + LuaScriptInterface scriptInterface; }; #endif diff --git a/src/tasks.cpp b/src/tasks.cpp index 1ed727f..58a82a0 100644 --- a/src/tasks.cpp +++ b/src/tasks.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,6 +24,16 @@ extern Game g_game; +Task* createTask(std::function f) +{ + return new Task(std::move(f)); +} + +Task* createTask(uint32_t expiration, std::function f) +{ + return new Task(expiration, std::move(f)); +} + void Dispatcher::threadMain() { // NOTE: second argument defer_lock is to prevent from immediate locking @@ -48,8 +58,6 @@ void Dispatcher::threadMain() ++dispatcherCycle; // execute it (*task)(); - - g_game.map.clearSpectatorCache(); } delete task; } else { diff --git a/src/tasks.h b/src/tasks.h index 4ea459c..6f6af57 100644 --- a/src/tasks.h +++ b/src/tasks.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,8 +31,8 @@ class Task { public: // DO NOT allocate this class on the stack - explicit Task(std::function f) : func(std::move(f)) {} - Task(uint32_t ms, std::function f) : + explicit Task(std::function&& f) : func(std::move(f)) {} + Task(uint32_t ms, std::function&& f) : expiration(std::chrono::system_clock::now() + std::chrono::milliseconds(ms)), func(std::move(f)) {} virtual ~Task() = default; @@ -52,22 +52,17 @@ class Task } protected: + std::chrono::system_clock::time_point expiration = SYSTEM_TIME_ZERO; + + private: // Expiration has another meaning for scheduler tasks, // then it is the time the task should be added to the // dispatcher - std::chrono::system_clock::time_point expiration = SYSTEM_TIME_ZERO; std::function func; }; -inline Task* createTask(const std::function& f) -{ - return new Task(f); -} - -inline Task* createTask(uint32_t expiration, const std::function& f) -{ - return new Task(expiration, f); -} +Task* createTask(std::function f); +Task* createTask(uint32_t expiration, std::function f); class Dispatcher : public ThreadHolder { public: @@ -81,7 +76,7 @@ class Dispatcher : public ThreadHolder { void threadMain(); - protected: + private: std::thread thread; std::mutex taskLock; std::condition_variable taskSignal; diff --git a/src/teleport.cpp b/src/teleport.cpp index 562fd0b..e0dddfd 100644 --- a/src/teleport.cpp +++ b/src/teleport.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/teleport.h b/src/teleport.h index 410b2ab..f79579c 100644 --- a/src/teleport.h +++ b/src/teleport.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,16 +27,16 @@ class Teleport final : public Item, public Cylinder public: explicit Teleport(uint16_t type) : Item(type) {}; - Teleport* getTeleport() final { + Teleport* getTeleport() override { return this; } - const Teleport* getTeleport() const final { + const Teleport* getTeleport() const override { return this; } //serialization - Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) final; - void serializeAttr(PropWriteStream& propWriteStream) const final; + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; + void serializeAttr(PropWriteStream& propWriteStream) const override; const Position& getDestPos() const { return destPos; @@ -47,23 +47,23 @@ class Teleport final : public Item, public Cylinder //cylinder implementations ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const final; + 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; + uint32_t& maxQueryCount, uint32_t flags) const override; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const override; Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, - uint32_t& flags) final; + uint32_t& flags) override; - void addThing(Thing* thing) final; - void addThing(int32_t index, Thing* thing) final; + void addThing(Thing* thing) override; + void addThing(int32_t index, Thing* thing) override; - void updateThing(Thing* thing, uint16_t itemId, uint32_t count) final; - void replaceThing(uint32_t index, Thing* thing) final; + void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override; + void replaceThing(uint32_t index, Thing* thing) override; - void removeThing(Thing* thing, uint32_t count) final; + void removeThing(Thing* thing, uint32_t count) override; - 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; + 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; private: Position destPos; diff --git a/src/thing.cpp b/src/thing.cpp index 21e72f6..abda31f 100644 --- a/src/thing.cpp +++ b/src/thing.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/thing.h b/src/thing.h index b23be2b..3fee9d2 100644 --- a/src/thing.h +++ b/src/thing.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,11 +30,10 @@ class Container; class Thing { - protected: - constexpr Thing() = default; - ~Thing() = default; - public: + constexpr Thing() = default; + virtual ~Thing() = default; + // non-copyable Thing(const Thing&) = delete; Thing& operator=(const Thing&) = delete; diff --git a/src/thread_holder_base.h b/src/thread_holder_base.h index 91280db..1ab206b 100644 --- a/src/thread_holder_base.h +++ b/src/thread_holder_base.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/tile.cpp b/src/tile.cpp index 6240678..d5347dc 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,6 +30,7 @@ #include "monster.h" #include "movement.h" #include "teleport.h" +#include "trashholder.h" extern Game g_game; extern MoveEvents* g_moveEvents; @@ -100,25 +101,6 @@ bool Tile::hasHeight(uint32_t n) const return false; } -int32_t Tile::getHeight() { - int32_t height = 0; - if (ground) { - if (ground->hasProperty(CONST_PROP_HASHEIGHT)) { - ++height; - } - } - - if (const TileItemVector* items = getItemList()) { - for (ItemVector::const_iterator it = items->begin(); it != items->end(); ++it) { - if ((*it)->hasProperty(CONST_PROP_HASHEIGHT)) { - ++height; - } - } - } - - return std::min(height, 4); -} - size_t Tile::getCreatureCount() const { if (const CreatureVector* creatures = getCreatures()) { @@ -192,6 +174,26 @@ MagicField* Tile::getFieldItem() const return nullptr; } +TrashHolder* Tile::getTrashHolder() const +{ + if (!hasFlag(TILESTATE_TRASHHOLDER)) { + return nullptr; + } + + if (ground && ground->getTrashHolder()) { + return ground->getTrashHolder(); + } + + if (const TileItemVector* items = getItemList()) { + for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) { + if ((*it)->getTrashHolder()) { + return (*it)->getTrashHolder(); + } + } + } + return nullptr; +} + Mailbox* Tile::getMailbox() const { if (!hasFlag(TILESTATE_MAILBOX)) { @@ -212,27 +214,6 @@ Mailbox* Tile::getMailbox() const return nullptr; } -DepotLocker* Tile::getDepotLocker() const -{ - if (!hasFlag(TILESTATE_DEPOT)) { - return nullptr; - } - - if (ground && ground->getDepotLocker()) { - return ground->getDepotLocker(); - } - - if (const TileItemVector* items = getItemList()) { - for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) { - if ((*it)->getDepotLocker()) { - return (*it)->getDepotLocker(); - } - } - } - return nullptr; -} - - BedItem* Tile::getBedItem() const { if (!hasFlag(TILESTATE_BED)) { @@ -372,11 +353,17 @@ Thing* Tile::getTopVisibleThing(const Creature* creature) TileItemVector* items = getItemList(); if (items) { for (ItemVector::const_iterator it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; ++it) { - return (*it); + const ItemType& iit = Item::items[(*it)->getID()]; + if (!iit.lookThrough) { + return (*it); + } } for (auto it = ItemVector::const_reverse_iterator(items->getEndTopItem()), end = ItemVector::const_reverse_iterator(items->getBeginTopItem()); it != end; ++it) { - return (*it); + const ItemType& iit = Item::items[(*it)->getID()]; + if (!iit.lookThrough) { + return (*it); + } } } @@ -385,48 +372,81 @@ Thing* Tile::getTopVisibleThing(const Creature* creature) void Tile::onAddTileItem(Item* item) { + if (item->hasProperty(CONST_PROP_MOVEABLE) || item->getContainer()) { + auto it = g_game.browseFields.find(this); + if (it != g_game.browseFields.end()) { + it->second->addItemBack(item); + item->setParent(this); + } + } + setTileFlags(item); const Position& cylinderMapPos = getPosition(); - SpectatorVec list; - g_game.map.getSpectators(list, cylinderMapPos, true); + SpectatorVec spectators; + g_game.map.getSpectators(spectators, cylinderMapPos, true); //send to client - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendAddTileItem(this, cylinderMapPos, item); } } //event methods - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { spectator->onAddTileItem(this, cylinderMapPos); } } void Tile::onUpdateTileItem(Item* oldItem, const ItemType& oldType, Item* newItem, const ItemType& newType) { + if (newItem->hasProperty(CONST_PROP_MOVEABLE) || newItem->getContainer()) { + auto it = g_game.browseFields.find(this); + if (it != g_game.browseFields.end()) { + int32_t index = it->second->getThingIndex(oldItem); + if (index != -1) { + it->second->replaceThing(index, newItem); + newItem->setParent(this); + } + } + } else if (oldItem->hasProperty(CONST_PROP_MOVEABLE) || oldItem->getContainer()) { + auto it = g_game.browseFields.find(this); + if (it != g_game.browseFields.end()) { + Cylinder* oldParent = oldItem->getParent(); + it->second->removeThing(oldItem, oldItem->getItemCount()); + oldItem->setParent(oldParent); + } + } + const Position& cylinderMapPos = getPosition(); - SpectatorVec list; - g_game.map.getSpectators(list, cylinderMapPos, true); + SpectatorVec spectators; + g_game.map.getSpectators(spectators, cylinderMapPos, true); //send to client - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendUpdateTileItem(this, cylinderMapPos, newItem); } } //event methods - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { spectator->onUpdateTileItem(this, cylinderMapPos, oldItem, oldType, newItem, newType); } } -void Tile::onRemoveTileItem(const SpectatorVec& list, const std::vector& oldStackPosVector, Item* item) +void Tile::onRemoveTileItem(const SpectatorVec& spectators, const std::vector& oldStackPosVector, Item* item) { + if (item->hasProperty(CONST_PROP_MOVEABLE) || item->getContainer()) { + auto it = g_game.browseFields.find(this); + if (it != g_game.browseFields.end()) { + it->second->removeThing(item, item->getItemCount()); + } + } + resetTileFlags(item); const Position& cylinderMapPos = getPosition(); @@ -434,24 +454,24 @@ void Tile::onRemoveTileItem(const SpectatorVec& list, const std::vector //send to client size_t i = 0; - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendRemoveTileThing(cylinderMapPos, oldStackPosVector[i++]); } } //event methods - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { spectator->onRemoveTileItem(this, cylinderMapPos, iType, item); } } -void Tile::onUpdateTile(const SpectatorVec& list) +void Tile::onUpdateTile(const SpectatorVec& spectators) { const Position& cylinderMapPos = getPosition(); //send to clients - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { spectator->getPlayer()->sendUpdateTile(this, cylinderMapPos); } } @@ -463,7 +483,7 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags return RETURNVALUE_NOERROR; } - if (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_IMMOVABLENOFIELDBLOCKPATH)) { + if (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_FLOORCHANGE | TILESTATE_TELEPORT)) { return RETURNVALUE_NOTPOSSIBLE; } @@ -472,15 +492,7 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags } if (const Monster* monster = creature->getMonster()) { - if (hasFlag(TILESTATE_PROTECTIONZONE | TILESTATE_TELEPORT)) { - return RETURNVALUE_NOTPOSSIBLE; - } - - if (hasFlag(TILESTATE_IMMOVABLEBLOCKPATH | TILESTATE_IMMOVABLENOFIELDBLOCKPATH)) { - return RETURNVALUE_NOTPOSSIBLE; - } - - if (hasBitSet(FLAG_PLACECHECK, flags) && hasFlag(TILESTATE_BLOCKSOLID)) { + if (hasFlag(TILESTATE_PROTECTIONZONE | TILESTATE_FLOORCHANGE | TILESTATE_TELEPORT)) { return RETURNVALUE_NOTPOSSIBLE; } @@ -494,7 +506,7 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags const Monster* creatureMonster = tileCreature->getMonster(); if (!creatureMonster || !tileCreature->isPushable() || - (creatureMonster->isSummon() && creatureMonster->getMaster()->getPlayer())) { + (creatureMonster->isSummon() && creatureMonster->getMaster()->getPlayer())) { return RETURNVALUE_NOTPOSSIBLE; } } @@ -521,17 +533,23 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags } } - if (!monster->hasCondition(CONDITION_AGGRESSIVE) && - !hasBitSet(FLAG_IGNOREFIELDDAMAGE, flags)) { - if (hasFlag(TILESTATE_FIREDAMAGE) && !monster->isImmune(COMBAT_FIREDAMAGE)) { - return RETURNVALUE_NOTPOSSIBLE; - } + MagicField* field = getFieldItem(); + if (!field || field->isBlocking() || field->getDamage() == 0) { + return RETURNVALUE_NOERROR; + } - if (hasFlag(TILESTATE_POISONDAMAGE) && !monster->isImmune(COMBAT_EARTHDAMAGE)) { - return RETURNVALUE_NOTPOSSIBLE; - } + CombatType_t combatType = field->getCombatType(); - if (hasFlag(TILESTATE_ENERGYDAMAGE) && !monster->isImmune(COMBAT_ENERGYDAMAGE)) { + //There is 3 options for a monster to enter a magic field + //1) Monster is immune + if (!monster->isImmune(combatType)) { + //1) Monster is able to walk over field type + //2) Being attacked while random stepping will make it ignore field damages + if (hasBitSet(FLAG_IGNOREFIELDDAMAGE, flags)) { + if (!(monster->canWalkOnFieldType(combatType) || monster->isIgnoringFieldDamage())) { + return RETURNVALUE_NOTPOSSIBLE; + } + } else { return RETURNVALUE_NOTPOSSIBLE; } } @@ -542,11 +560,11 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags const CreatureVector* creatures = getCreatures(); if (const Player* player = creature->getPlayer()) { if (creatures && !creatures->empty() && !hasBitSet(FLAG_IGNOREBLOCKCREATURE, flags) && !player->isAccessPlayer()) { - return RETURNVALUE_NOTPOSSIBLE; - } - - if (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_BLOCKPATH)) { - return RETURNVALUE_NOTPOSSIBLE; + for (const Creature* tileCreature : *creatures) { + if (!player->canWalkthrough(tileCreature)) { + return RETURNVALUE_NOTPOSSIBLE; + } + } } if (player->getParent() == nullptr && hasFlag(TILESTATE_NOLOGOUT)) { @@ -589,7 +607,7 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags //FLAG_IGNOREBLOCKITEM is set if (ground) { const ItemType& iiType = Item::items[ground->getID()]; - if (iiType.blockSolid) { + if (iiType.blockSolid && (!iiType.moveable || ground->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID))) { return RETURNVALUE_NOTPOSSIBLE; } } @@ -597,7 +615,7 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags if (const auto items = getItemList()) { for (const Item* item : *items) { const ItemType& iiType = Item::items[item->getID()]; - if (iiType.blockSolid && !iiType.moveable) { + if (iiType.blockSolid && (!iiType.moveable || item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID))) { return RETURNVALUE_NOTPOSSIBLE; } } @@ -627,10 +645,6 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags } } - if (item->isMagicField() && hasProperty(CONST_PROP_IMMOVABLENOFIELDBLOCKPATH)) { - return RETURNVALUE_NOTENOUGHROOM; - } - if (itemIsHangable && hasFlag(TILESTATE_SUPPORTS_HANGABLE)) { if (items) { for (const Item* tileItem : *items) { @@ -709,14 +723,101 @@ ReturnValue Tile::queryRemove(const Thing& thing, uint32_t count, uint32_t flags return RETURNVALUE_NOERROR; } -Tile* Tile::queryDestination(int32_t&, const Thing&, Item** destItem, uint32_t&) +Tile* Tile::queryDestination(int32_t&, const Thing&, Item** destItem, uint32_t& flags) { - Thing* destThing = getTopDownItem(); - if (destThing) { - *destItem = destThing->getItem(); + Tile* destTile = nullptr; + *destItem = nullptr; + + if (hasFlag(TILESTATE_FLOORCHANGE_DOWN)) { + uint16_t dx = tilePos.x; + uint16_t dy = tilePos.y; + uint8_t dz = tilePos.z + 1; + + Tile* southDownTile = g_game.map.getTile(dx, dy - 1, dz); + if (southDownTile && southDownTile->hasFlag(TILESTATE_FLOORCHANGE_SOUTH_ALT)) { + dy -= 2; + destTile = g_game.map.getTile(dx, dy, dz); + } else { + Tile* eastDownTile = g_game.map.getTile(dx - 1, dy, dz); + if (eastDownTile && eastDownTile->hasFlag(TILESTATE_FLOORCHANGE_EAST_ALT)) { + dx -= 2; + destTile = g_game.map.getTile(dx, dy, dz); + } else { + Tile* downTile = g_game.map.getTile(dx, dy, dz); + if (downTile) { + if (downTile->hasFlag(TILESTATE_FLOORCHANGE_NORTH)) { + ++dy; + } + + if (downTile->hasFlag(TILESTATE_FLOORCHANGE_SOUTH)) { + --dy; + } + + if (downTile->hasFlag(TILESTATE_FLOORCHANGE_SOUTH_ALT)) { + dy -= 2; + } + + if (downTile->hasFlag(TILESTATE_FLOORCHANGE_EAST)) { + --dx; + } + + if (downTile->hasFlag(TILESTATE_FLOORCHANGE_EAST_ALT)) { + dx -= 2; + } + + if (downTile->hasFlag(TILESTATE_FLOORCHANGE_WEST)) { + ++dx; + } + + destTile = g_game.map.getTile(dx, dy, dz); + } + } + } + } else if (hasFlag(TILESTATE_FLOORCHANGE)) { + uint16_t dx = tilePos.x; + uint16_t dy = tilePos.y; + uint8_t dz = tilePos.z - 1; + + if (hasFlag(TILESTATE_FLOORCHANGE_NORTH)) { + --dy; + } + + if (hasFlag(TILESTATE_FLOORCHANGE_SOUTH)) { + ++dy; + } + + if (hasFlag(TILESTATE_FLOORCHANGE_EAST)) { + ++dx; + } + + if (hasFlag(TILESTATE_FLOORCHANGE_WEST)) { + --dx; + } + + if (hasFlag(TILESTATE_FLOORCHANGE_SOUTH_ALT)) { + dy += 2; + } + + if (hasFlag(TILESTATE_FLOORCHANGE_EAST_ALT)) { + dx += 2; + } + + destTile = g_game.map.getTile(dx, dy, dz); } - return this; + if (destTile == nullptr) { + destTile = this; + } else { + flags |= FLAG_NOLIMIT; //Will ignore that there is blocking items/creatures + } + + if (destTile) { + Thing* destThing = destTile->getTopDownItem(); + if (destThing) { + *destItem = destThing->getItem(); + } + } + return destTile; } void Tile::addThing(Thing* thing) @@ -729,9 +830,13 @@ void Tile::addThing(int32_t, Thing* thing) Creature* creature = thing->getCreature(); if (creature) { g_game.map.clearSpectatorCache(); + if (creature->getPlayer()) { + g_game.map.clearPlayersSpectatorCache(); + } + creature->setParent(this); CreatureVector* creatures = makeCreatures(); - creatures->insert(creatures->end(), creature); + creatures->insert(creatures->begin(), creature); } else { Item* item = thing->getItem(); if (item == nullptr) { @@ -934,6 +1039,10 @@ void Tile::removeThing(Thing* thing, uint32_t count) auto it = std::find(creatures->begin(), creatures->end(), thing); if (it != creatures->end()) { g_game.map.clearSpectatorCache(); + if (creature->getPlayer()) { + g_game.map.clearPlayersSpectatorCache(); + } + creatures->erase(it); } } @@ -954,9 +1063,9 @@ void Tile::removeThing(Thing* thing, uint32_t count) ground->setParent(nullptr); ground = nullptr; - SpectatorVec list; - g_game.map.getSpectators(list, getPosition(), true); - onRemoveTileItem(list, std::vector(list.size(), 0), item); + SpectatorVec spectators; + g_game.map.getSpectators(spectators, getPosition(), true); + onRemoveTileItem(spectators, std::vector(spectators.size(), 0), item); return; } @@ -974,9 +1083,9 @@ void Tile::removeThing(Thing* thing, uint32_t count) std::vector oldStackPosVector; - SpectatorVec list; - g_game.map.getSpectators(list, getPosition(), true); - for (Creature* spectator : list) { + SpectatorVec spectators; + g_game.map.getSpectators(spectators, getPosition(), true); + for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { oldStackPosVector.push_back(getStackposOfItem(tmpPlayer, item)); } @@ -984,7 +1093,7 @@ void Tile::removeThing(Thing* thing, uint32_t count) item->setParent(nullptr); items->erase(it); - onRemoveTileItem(list, oldStackPosVector, item); + onRemoveTileItem(spectators, oldStackPosVector, item); } else { auto it = std::find(items->getBeginDownItem(), items->getEndDownItem(), item); if (it == items->getEndDownItem()) { @@ -998,9 +1107,9 @@ void Tile::removeThing(Thing* thing, uint32_t count) } else { std::vector oldStackPosVector; - SpectatorVec list; - g_game.map.getSpectators(list, getPosition(), true); - for (Creature* spectator : list) { + SpectatorVec spectators; + g_game.map.getSpectators(spectators, getPosition(), true); + for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { oldStackPosVector.push_back(getStackposOfItem(tmpPlayer, item)); } @@ -1009,7 +1118,7 @@ void Tile::removeThing(Thing* thing, uint32_t count) item->setParent(nullptr); items->erase(it); items->addDownItemCount(-1); - onRemoveTileItem(list, oldStackPosVector, item); + onRemoveTileItem(spectators, oldStackPosVector, item); } } } @@ -1241,9 +1350,9 @@ Thing* Tile::getThing(size_t index) const void Tile::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) { - SpectatorVec list; - g_game.map.getSpectators(list, getPosition(), true, true); - for (Creature* spectator : list) { + SpectatorVec spectators; + g_game.map.getSpectators(spectators, getPosition(), true, true); + for (Creature* spectator : spectators) { spectator->getPlayer()->postAddNotification(thing, oldParent, index, LINK_NEAR); } @@ -1266,6 +1375,11 @@ void Tile::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t if (teleport) { teleport->addThing(thing); } + } else if (hasFlag(TILESTATE_TRASHHOLDER)) { + TrashHolder* trashholder = getTrashHolder(); + if (trashholder) { + trashholder->addThing(thing); + } } else if (hasFlag(TILESTATE_MAILBOX)) { Mailbox* mailbox = getMailbox(); if (mailbox) { @@ -1291,14 +1405,14 @@ void Tile::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t void Tile::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) { - SpectatorVec list; - g_game.map.getSpectators(list, getPosition(), true, true); + SpectatorVec spectators; + g_game.map.getSpectators(spectators, getPosition(), true, true); if (getThingCount() > 8) { - onUpdateTile(list); + onUpdateTile(spectators); } - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { spectator->getPlayer()->postRemoveNotification(thing, newParent, index, LINK_NEAR); } @@ -1326,8 +1440,12 @@ void Tile::internalAddThing(uint32_t, Thing* thing) Creature* creature = thing->getCreature(); if (creature) { g_game.map.clearSpectatorCache(); + if (creature->getPlayer()) { + g_game.map.clearPlayersSpectatorCache(); + } + CreatureVector* creatures = makeCreatures(); - creatures->insert(creatures->end(), creature); + creatures->insert(creatures->begin(), creature); } else { Item* item = thing->getItem(); if (item == nullptr) { @@ -1372,6 +1490,13 @@ void Tile::internalAddThing(uint32_t, Thing* thing) void Tile::setTileFlags(const Item* item) { + if (!hasFlag(TILESTATE_FLOORCHANGE)) { + const ItemType& it = Item::items[item->getID()]; + if (it.floorChange != 0) { + setFlag(it.floorChange); + } + } + if (item->hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID)) { setFlag(TILESTATE_IMMOVABLEBLOCKSOLID); } @@ -1400,6 +1525,10 @@ void Tile::setTileFlags(const Item* item) setFlag(TILESTATE_MAILBOX); } + if (item->getTrashHolder()) { + setFlag(TILESTATE_TRASHHOLDER); + } + if (item->hasProperty(CONST_PROP_BLOCKSOLID)) { setFlag(TILESTATE_BLOCKSOLID); } @@ -1408,18 +1537,6 @@ void Tile::setTileFlags(const Item* item) setFlag(TILESTATE_BED); } - if (item->getCombatType() == COMBAT_FIREDAMAGE) { - setFlag(TILESTATE_FIREDAMAGE); - } - - if (item->getCombatType() == COMBAT_ENERGYDAMAGE) { - setFlag(TILESTATE_ENERGYDAMAGE); - } - - if (item->getCombatType() == COMBAT_EARTHDAMAGE) { - setFlag(TILESTATE_POISONDAMAGE); - } - const Container* container = item->getContainer(); if (container && container->getDepotLocker()) { setFlag(TILESTATE_DEPOT); @@ -1432,6 +1549,11 @@ void Tile::setTileFlags(const Item* item) void Tile::resetTileFlags(const Item* item) { + const ItemType& it = Item::items[item->getID()]; + if (it.floorChange != 0) { + resetFlag(TILESTATE_FLOORCHANGE); + } + if (item->hasProperty(CONST_PROP_BLOCKSOLID) && !hasProperty(item, CONST_PROP_BLOCKSOLID)) { resetFlag(TILESTATE_BLOCKSOLID); } @@ -1468,22 +1590,14 @@ void Tile::resetTileFlags(const Item* item) resetFlag(TILESTATE_MAILBOX); } + if (item->getTrashHolder()) { + resetFlag(TILESTATE_TRASHHOLDER); + } + if (item->getBed()) { resetFlag(TILESTATE_BED); } - if (item->getCombatType() == COMBAT_FIREDAMAGE) { - resetFlag(TILESTATE_FIREDAMAGE); - } - - if (item->getCombatType() == COMBAT_ENERGYDAMAGE) { - resetFlag(TILESTATE_ENERGYDAMAGE); - } - - if (item->getCombatType() == COMBAT_EARTHDAMAGE) { - resetFlag(TILESTATE_POISONDAMAGE); - } - const Container* container = item->getContainer(); if (container && container->getDepotLocker()) { resetFlag(TILESTATE_DEPOT); diff --git a/src/tile.h b/src/tile.h index 0b68759..d04ecc7 100644 --- a/src/tile.h +++ b/src/tile.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,47 +20,51 @@ #ifndef FS_TILE_H_96C7EE7CF8CD48E59D5D554A181F0C56 #define FS_TILE_H_96C7EE7CF8CD48E59D5D554A181F0C56 -#include - #include "cylinder.h" #include "item.h" #include "tools.h" +#include "spectators.h" class Creature; class Teleport; +class TrashHolder; class Mailbox; -class DepotLocker; class MagicField; class QTreeLeafNode; class BedItem; -typedef std::vector CreatureVector; -typedef std::vector ItemVector; -typedef std::unordered_set SpectatorVec; +using CreatureVector = std::vector; +using ItemVector = std::vector; enum tileflags_t : uint32_t { TILESTATE_NONE = 0, - TILESTATE_PROTECTIONZONE = 1 << 0, - TILESTATE_NOPVPZONE = 1 << 1, - TILESTATE_NOLOGOUT = 1 << 2, - TILESTATE_PVPZONE = 1 << 3, - TILESTATE_REFRESH = 1 << 4, - TILESTATE_TELEPORT = 1 << 5, - TILESTATE_MAGICFIELD = 1 << 6, - TILESTATE_MAILBOX = 1 << 7, - TILESTATE_BED = 1 << 8, - TILESTATE_DEPOT = 1 << 9, - TILESTATE_BLOCKSOLID = 1 << 10, - TILESTATE_BLOCKPATH = 1 << 11, - TILESTATE_IMMOVABLEBLOCKSOLID = 1 << 12, - TILESTATE_IMMOVABLEBLOCKPATH = 1 << 13, - TILESTATE_IMMOVABLENOFIELDBLOCKPATH = 1 << 14, - TILESTATE_NOFIELDBLOCKPATH = 1 << 15, - TILESTATE_SUPPORTS_HANGABLE = 1 << 16, - TILESTATE_FIREDAMAGE = 1 << 17, - TILESTATE_POISONDAMAGE = 1 << 18, - TILESTATE_ENERGYDAMAGE = 1 << 19, + TILESTATE_FLOORCHANGE_DOWN = 1 << 0, + TILESTATE_FLOORCHANGE_NORTH = 1 << 1, + TILESTATE_FLOORCHANGE_SOUTH = 1 << 2, + TILESTATE_FLOORCHANGE_EAST = 1 << 3, + TILESTATE_FLOORCHANGE_WEST = 1 << 4, + TILESTATE_FLOORCHANGE_SOUTH_ALT = 1 << 5, + TILESTATE_FLOORCHANGE_EAST_ALT = 1 << 6, + TILESTATE_PROTECTIONZONE = 1 << 7, + TILESTATE_NOPVPZONE = 1 << 8, + TILESTATE_NOLOGOUT = 1 << 9, + TILESTATE_PVPZONE = 1 << 10, + TILESTATE_TELEPORT = 1 << 11, + TILESTATE_MAGICFIELD = 1 << 12, + TILESTATE_MAILBOX = 1 << 13, + TILESTATE_TRASHHOLDER = 1 << 14, + TILESTATE_BED = 1 << 15, + TILESTATE_DEPOT = 1 << 16, + TILESTATE_BLOCKSOLID = 1 << 17, + TILESTATE_BLOCKPATH = 1 << 18, + TILESTATE_IMMOVABLEBLOCKSOLID = 1 << 19, + TILESTATE_IMMOVABLEBLOCKPATH = 1 << 20, + TILESTATE_IMMOVABLENOFIELDBLOCKPATH = 1 << 21, + TILESTATE_NOFIELDBLOCKPATH = 1 << 22, + TILESTATE_SUPPORTS_HANGABLE = 1 << 23, + + TILESTATE_FLOORCHANGE = TILESTATE_FLOORCHANGE_DOWN | TILESTATE_FLOORCHANGE_NORTH | TILESTATE_FLOORCHANGE_SOUTH | TILESTATE_FLOORCHANGE_EAST | TILESTATE_FLOORCHANGE_WEST | TILESTATE_FLOORCHANGE_SOUTH_ALT | TILESTATE_FLOORCHANGE_EAST_ALT, }; enum ZoneType_t { @@ -146,7 +150,9 @@ class Tile : public Cylinder public: static Tile& nullptr_tile; Tile(uint16_t x, uint16_t y, uint8_t z) : tilePos(x, y, z) {} - virtual ~Tile(); + virtual ~Tile() { + delete ground; + }; // non-copyable Tile(const Tile&) = delete; @@ -160,17 +166,17 @@ class Tile : public Cylinder virtual const CreatureVector* getCreatures() const = 0; virtual CreatureVector* makeCreatures() = 0; - int32_t getThrowRange() const final { + int32_t getThrowRange() const override final { return 0; } - bool isPushable() const final { + bool isPushable() const override final { return false; } MagicField* getFieldItem() const; Teleport* getTeleportItem() const; + TrashHolder* getTrashHolder() const; Mailbox* getMailbox() const; - DepotLocker* getDepotLocker() const; BedItem* getBedItem() const; Creature* getTopCreature() const; @@ -199,13 +205,13 @@ class Tile : public Cylinder bool hasProperty(ITEMPROPERTY prop) const; bool hasProperty(const Item* exclude, ITEMPROPERTY prop) const; - inline bool hasFlag(uint32_t flag) const { + bool hasFlag(uint32_t flag) const { return hasBitSet(flag, this->flags); } - inline void setFlag(uint32_t flag) { + void setFlag(uint32_t flag) { this->flags |= flag; } - inline void resetFlag(uint32_t flag) { + void resetFlag(uint32_t flag) { this->flags &= ~flag; } @@ -222,9 +228,8 @@ class Tile : public Cylinder } bool hasHeight(uint32_t n) const; - int32_t getHeight(); - std::string getDescription(int32_t lookDistance) const final; + std::string getDescription(int32_t lookDistance) const override final; int32_t getClientIndexOfCreature(const Player* player, const Creature* creature) const; int32_t getStackposOfCreature(const Player* player, const Creature* creature) const; @@ -234,37 +239,37 @@ class Tile : public Cylinder 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; + uint32_t& maxQueryCount, uint32_t flags) const override final; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const override final; Tile* queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) override; - void addThing(Thing* thing) final; + void addThing(Thing* thing) override final; void addThing(int32_t index, Thing* thing) override; - void updateThing(Thing* thing, uint16_t itemId, uint32_t count) final; - void replaceThing(uint32_t index, Thing* thing) final; + void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override final; + void replaceThing(uint32_t index, Thing* thing) override final; - void removeThing(Thing* thing, uint32_t count) final; + void removeThing(Thing* thing, uint32_t count) override final; void removeCreature(Creature* creature); - 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; - Thing* getThing(size_t index) const final; + int32_t getThingIndex(const Thing* thing) const override final; + size_t getFirstIndex() const override final; + size_t getLastIndex() const override final; + uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const override final; + Thing* getThing(size_t index) const override 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; + void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) override final; + void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) override final; - void internalAddThing(Thing* thing) final; + void internalAddThing(Thing* thing) override final; void internalAddThing(uint32_t index, Thing* thing) override; - const Position& getPosition() const final { + const Position& getPosition() const override final { return tilePos; } - bool isRemoved() const final { + bool isRemoved() const override final { return false; } @@ -280,13 +285,12 @@ class Tile : public Cylinder private: void onAddTileItem(Item* item); void onUpdateTileItem(Item* oldItem, const ItemType& oldType, Item* newItem, const ItemType& newType); - void onRemoveTileItem(const SpectatorVec& list, const std::vector& oldStackPosVector, Item* item); - void onUpdateTile(const SpectatorVec& list); + void onRemoveTileItem(const SpectatorVec& spectators, const std::vector& oldStackPosVector, Item* item); + void onUpdateTile(const SpectatorVec& spectators); void setTileFlags(const Item* item); void resetTileFlags(const Item* item); - protected: Item* ground = nullptr; Position tilePos; uint32_t flags = 0; @@ -302,29 +306,33 @@ class DynamicTile : public Tile public: DynamicTile(uint16_t x, uint16_t y, uint8_t z) : Tile(x, y, z) {} - ~DynamicTile(); + ~DynamicTile() { + for (Item* item : items) { + item->decrementReferenceCounter(); + } + } // non-copyable DynamicTile(const DynamicTile&) = delete; DynamicTile& operator=(const DynamicTile&) = delete; - TileItemVector* getItemList() final { + TileItemVector* getItemList() override { return &items; } - const TileItemVector* getItemList() const final { + const TileItemVector* getItemList() const override { return &items; } - TileItemVector* makeItemList() final { + TileItemVector* makeItemList() override { return &items; } - CreatureVector* getCreatures() final { + CreatureVector* getCreatures() override { return &creatures; } - const CreatureVector* getCreatures() const final { + const CreatureVector* getCreatures() const override { return &creatures; } - CreatureVector* makeCreatures() final { + CreatureVector* makeCreatures() override { return &creatures; } }; @@ -338,32 +346,38 @@ class StaticTile final : public Tile public: StaticTile(uint16_t x, uint16_t y, uint8_t z) : Tile(x, y, z) {} - ~StaticTile(); + ~StaticTile() { + if (items) { + for (Item* item : *items) { + item->decrementReferenceCounter(); + } + } + } // non-copyable StaticTile(const StaticTile&) = delete; StaticTile& operator=(const StaticTile&) = delete; - TileItemVector* getItemList() final { + TileItemVector* getItemList() override { return items.get(); } - const TileItemVector* getItemList() const final { + const TileItemVector* getItemList() const override { return items.get(); } - TileItemVector* makeItemList() final { + TileItemVector* makeItemList() override { if (!items) { items.reset(new TileItemVector); } return items.get(); } - CreatureVector* getCreatures() final { + CreatureVector* getCreatures() override { return creatures.get(); } - const CreatureVector* getCreatures() const final { + const CreatureVector* getCreatures() const override { return creatures.get(); } - CreatureVector* makeCreatures() final { + CreatureVector* makeCreatures() override { if (!creatures) { creatures.reset(new CreatureVector); } @@ -371,25 +385,4 @@ class StaticTile final : public Tile } }; -inline Tile::~Tile() -{ - delete ground; -} - -inline StaticTile::~StaticTile() -{ - if (items) { - for (Item* item : *items) { - item->decrementReferenceCounter(); - } - } -} - -inline DynamicTile::~DynamicTile() -{ - for (Item* item : items) { - item->decrementReferenceCounter(); - } -} - #endif diff --git a/src/tools.cpp b/src/tools.cpp index 506f512..81a9834 100644 --- a/src/tools.cpp +++ b/src/tools.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -73,7 +73,7 @@ void printXMLError(const std::string& where, const std::string& fileName, const std::cout << '^' << std::endl; } -inline static uint32_t circularShift(int bits, uint32_t value) +static uint32_t circularShift(int bits, uint32_t value) { return (value << bits) | (value >> (32 - bits)); } @@ -186,95 +186,6 @@ std::string transformToSHA1(const std::string& input) return std::string(hexstring, 40); } -uint8_t getLiquidColor(uint8_t type) -{ - uint8_t result = FLUID_COLOR_NONE; - switch (type) - { - case FLUID_WATER: - result = FLUID_COLOR_BLUE; - break; - case FLUID_NONE: - result = FLUID_COLOR_NONE; - break; - case FLUID_SLIME: - result = FLUID_COLOR_GREEN; - break; - case FLUID_BEER: - case FLUID_MUD: - case FLUID_OIL: - case FLUID_RUM: - result = FLUID_COLOR_BROWN; - break; - case FLUID_MILK: - case FLUID_COCONUTMILK: - result = FLUID_COLOR_WHITE; - break; - case FLUID_WINE: - case FLUID_MANAFLUID: - result = FLUID_COLOR_PURPLE; - break; - case FLUID_BLOOD: - case FLUID_LIFEFLUID: - result = FLUID_COLOR_RED; - break; - case FLUID_URINE: - case FLUID_LEMONADE: - case FLUID_FRUITJUICE: - result = FLUID_COLOR_YELLOW; - break; - default: - result = FLUID_COLOR_NONE; - break; - } - return result; -} - -void extractArticleAndName(std::string& data, std::string& article, std::string& name) -{ - std::string xarticle = data.substr(0, 3); - if (xarticle == "an ") - { - name = data.substr(3, data.size()); - article = "an"; - } else { - xarticle = data.substr(0, 2); - if (xarticle == "a ") - { - name = data.substr(2, data.size()); - article = "a"; - } else { - name = data; - article = ""; - } - } -} - -std::string pluralizeString(std::string str) -{ - if (str == "meat") return "meat"; - - int n = str.length(); - char ch = str[n - 1]; - char ch2 = str[n - 2]; - - std::string str2; - if (ch == 'y') - str2 = str.substr(0, n - 1) + "ies"; - else if (ch == 'o' || ch == 's' || ch == 'x') - str2 = str + "es"; - else if (ch == 'h'&& ch2 == 'c') - str2 = str + "es"; - else if (ch == 'f') - str2 = str.substr(0, n - 1) + "ves"; - else if (ch == 'e'&&ch2 == 'f') - str2 = str.substr(0, n - 2) + "ves"; - else - str2 = str + "s"; - - return str2; -} - std::string generateToken(const std::string& key, uint32_t ticks) { // generate message from ticks @@ -360,9 +271,9 @@ std::string asUpperCaseString(std::string source) return source; } -StringVec explodeString(const std::string& inString, const std::string& separator, int32_t limit/* = -1*/) +StringVector explodeString(const std::string& inString, const std::string& separator, int32_t limit/* = -1*/) { - StringVec returnVector; + StringVector returnVector; std::string::size_type start = 0, end = 0; while (--limit != -1 && (end = inString.find(separator, start)) != std::string::npos) { @@ -374,9 +285,9 @@ StringVec explodeString(const std::string& inString, const std::string& separato return returnVector; } -IntegerVec vectorAtoi(const StringVec& stringVector) +IntegerVector vectorAtoi(const StringVector& stringVector) { - IntegerVec returnVector; + IntegerVector returnVector; for (const auto& string : stringVector) { returnVector.push_back(std::stoi(string)); } @@ -586,42 +497,14 @@ Direction getDirectionTo(const Position& from, const Position& to) return dir; } -struct MagicEffectNames { - const char* name; - MagicEffectClasses effect; -}; +using MagicEffectNames = std::unordered_map; +using ShootTypeNames = std::unordered_map; +using CombatTypeNames = std::unordered_map>; +using AmmoTypeNames = std::unordered_map; +using WeaponActionNames = std::unordered_map; +using SkullNames = std::unordered_map; -struct ShootTypeNames { - const char* name; - ShootType_t shoot; -}; - -struct CombatTypeNames { - const char* name; - CombatType_t combat; -}; - -struct AmmoTypeNames { - const char* name; - Ammo_t ammoType; -}; - -struct WeaponActionNames { - const char* name; - WeaponAction_t weaponAction; -}; - -struct SkullNames { - const char* name; - Skulls_t skull; -}; - -struct FluidNames { - const char* name; - FluidTypes_t fluidType; -}; - -MagicEffectNames magicEffectNames[] = { +MagicEffectNames magicEffectNames = { {"redspark", CONST_ME_DRAWBLOOD}, {"bluebubble", CONST_ME_LOSEENERGY}, {"poff", CONST_ME_POFF}, @@ -649,9 +532,63 @@ MagicEffectNames magicEffectNames[] = { {"whitenote", CONST_ME_SOUND_WHITE}, {"bubbles", CONST_ME_BUBBLES}, {"dice", CONST_ME_CRAPS}, + {"giftwraps", CONST_ME_GIFT_WRAPS}, + {"yellowfirework", CONST_ME_FIREWORK_YELLOW}, + {"redfirework", CONST_ME_FIREWORK_RED}, + {"bluefirework", CONST_ME_FIREWORK_BLUE}, + {"stun", CONST_ME_STUN}, + {"sleep", CONST_ME_SLEEP}, + {"watercreature", CONST_ME_WATERCREATURE}, + {"groundshaker", CONST_ME_GROUNDSHAKER}, + {"hearts", CONST_ME_HEARTS}, + {"fireattack", CONST_ME_FIREATTACK}, + {"energyarea", CONST_ME_ENERGYAREA}, + {"smallclouds", CONST_ME_SMALLCLOUDS}, + {"holydamage", CONST_ME_HOLYDAMAGE}, + {"bigclouds", CONST_ME_BIGCLOUDS}, + {"icearea", CONST_ME_ICEAREA}, + {"icetornado", CONST_ME_ICETORNADO}, + {"iceattack", CONST_ME_ICEATTACK}, + {"stones", CONST_ME_STONES}, + {"smallplants", CONST_ME_SMALLPLANTS}, + {"carniphila", CONST_ME_CARNIPHILA}, + {"purpleenergy", CONST_ME_PURPLEENERGY}, + {"yellowenergy", CONST_ME_YELLOWENERGY}, + {"holyarea", CONST_ME_HOLYAREA}, + {"bigplants", CONST_ME_BIGPLANTS}, + {"cake", CONST_ME_CAKE}, + {"giantice", CONST_ME_GIANTICE}, + {"watersplash", CONST_ME_WATERSPLASH}, + {"plantattack", CONST_ME_PLANTATTACK}, + {"tutorialarrow", CONST_ME_TUTORIALARROW}, + {"tutorialsquare", CONST_ME_TUTORIALSQUARE}, + {"mirrorhorizontal", CONST_ME_MIRRORHORIZONTAL}, + {"mirrorvertical", CONST_ME_MIRRORVERTICAL}, + {"skullhorizontal", CONST_ME_SKULLHORIZONTAL}, + {"skullvertical", CONST_ME_SKULLVERTICAL}, + {"assassin", CONST_ME_ASSASSIN}, + {"stepshorizontal", CONST_ME_STEPSHORIZONTAL}, + {"bloodysteps", CONST_ME_BLOODYSTEPS}, + {"stepsvertical", CONST_ME_STEPSVERTICAL}, + {"yalaharighost", CONST_ME_YALAHARIGHOST}, + {"bats", CONST_ME_BATS}, + {"smoke", CONST_ME_SMOKE}, + {"insects", CONST_ME_INSECTS}, + {"dragonhead", CONST_ME_DRAGONHEAD}, + {"orcshaman", CONST_ME_ORCSHAMAN}, + {"orcshamanfire", CONST_ME_ORCSHAMAN_FIRE}, + {"thunder", CONST_ME_THUNDER}, + {"ferumbras", CONST_ME_FERUMBRAS}, + {"confettihorizontal", CONST_ME_CONFETTI_HORIZONTAL}, + {"confettivertical", CONST_ME_CONFETTI_VERTICAL}, + {"blacksmoke", CONST_ME_BLACKSMOKE}, + {"redsmoke", CONST_ME_REDSMOKE}, + {"yellowsmoke", CONST_ME_YELLOWSMOKE}, + {"greensmoke", CONST_ME_GREENSMOKE}, + {"purplesmoke", CONST_ME_PURPLESMOKE}, }; -ShootTypeNames shootTypeNames[] = { +ShootTypeNames shootTypeNames = { {"spear", CONST_ANI_SPEAR}, {"bolt", CONST_ANI_BOLT}, {"arrow", CONST_ANI_ARROW}, @@ -667,22 +604,59 @@ ShootTypeNames shootTypeNames[] = { {"snowball", CONST_ANI_SNOWBALL}, {"powerbolt", CONST_ANI_POWERBOLT}, {"poison", CONST_ANI_POISON}, + {"infernalbolt", CONST_ANI_INFERNALBOLT}, + {"huntingspear", CONST_ANI_HUNTINGSPEAR}, + {"enchantedspear", CONST_ANI_ENCHANTEDSPEAR}, + {"redstar", CONST_ANI_REDSTAR}, + {"greenstar", CONST_ANI_GREENSTAR}, + {"royalspear", CONST_ANI_ROYALSPEAR}, + {"sniperarrow", CONST_ANI_SNIPERARROW}, + {"onyxarrow", CONST_ANI_ONYXARROW}, + {"piercingbolt", CONST_ANI_PIERCINGBOLT}, + {"whirlwindsword", CONST_ANI_WHIRLWINDSWORD}, + {"whirlwindaxe", CONST_ANI_WHIRLWINDAXE}, + {"whirlwindclub", CONST_ANI_WHIRLWINDCLUB}, + {"etherealspear", CONST_ANI_ETHEREALSPEAR}, + {"ice", CONST_ANI_ICE}, + {"earth", CONST_ANI_EARTH}, + {"holy", CONST_ANI_HOLY}, + {"suddendeath", CONST_ANI_SUDDENDEATH}, + {"flasharrow", CONST_ANI_FLASHARROW}, + {"flammingarrow", CONST_ANI_FLAMMINGARROW}, + {"shiverarrow", CONST_ANI_SHIVERARROW}, + {"energyball", CONST_ANI_ENERGYBALL}, + {"smallice", CONST_ANI_SMALLICE}, + {"smallholy", CONST_ANI_SMALLHOLY}, + {"smallearth", CONST_ANI_SMALLEARTH}, + {"eartharrow", CONST_ANI_EARTHARROW}, + {"explosion", CONST_ANI_EXPLOSION}, + {"cake", CONST_ANI_CAKE}, + {"tarsalarrow", CONST_ANI_TARSALARROW}, + {"vortexbolt", CONST_ANI_VORTEXBOLT}, + {"prismaticbolt", CONST_ANI_PRISMATICBOLT}, + {"crystallinearrow", CONST_ANI_CRYSTALLINEARROW}, + {"drillbolt", CONST_ANI_DRILLBOLT}, + {"envenomedarrow", CONST_ANI_ENVENOMEDARROW}, + {"gloothspear", CONST_ANI_GLOOTHSPEAR}, + {"simplearrow", CONST_ANI_SIMPLEARROW}, }; -CombatTypeNames combatTypeNames[] = { - {"physical", COMBAT_PHYSICALDAMAGE}, - {"energy", COMBAT_ENERGYDAMAGE}, - {"drown", COMBAT_DROWNDAMAGE}, - {"earth", COMBAT_EARTHDAMAGE}, - {"poison", COMBAT_EARTHDAMAGE}, - {"fire", COMBAT_FIREDAMAGE}, - {"undefined", COMBAT_UNDEFINEDDAMAGE}, - {"lifedrain", COMBAT_LIFEDRAIN}, - {"manadrain", COMBAT_MANADRAIN}, - {"healing", COMBAT_HEALING}, +CombatTypeNames combatTypeNames = { + {COMBAT_PHYSICALDAMAGE, "physical"}, + {COMBAT_ENERGYDAMAGE, "energy"}, + {COMBAT_EARTHDAMAGE, "earth"}, + {COMBAT_FIREDAMAGE, "fire"}, + {COMBAT_UNDEFINEDDAMAGE, "undefined"}, + {COMBAT_LIFEDRAIN, "lifedrain"}, + {COMBAT_MANADRAIN, "manadrain"}, + {COMBAT_HEALING, "healing"}, + {COMBAT_DROWNDAMAGE, "drown"}, + {COMBAT_ICEDAMAGE, "ice"}, + {COMBAT_HOLYDAMAGE, "holy"}, + {COMBAT_DEATHDAMAGE, "death"}, }; -AmmoTypeNames ammoTypeNames[] = { +AmmoTypeNames ammoTypeNames = { {"spear", AMMO_SPEAR}, {"bolt", AMMO_BOLT}, {"arrow", AMMO_ARROW}, @@ -694,119 +668,114 @@ AmmoTypeNames ammoTypeNames[] = { {"largerock", AMMO_STONE}, {"snowball", AMMO_SNOWBALL}, {"powerbolt", AMMO_BOLT}, + {"infernalbolt", AMMO_BOLT}, + {"huntingspear", AMMO_SPEAR}, + {"enchantedspear", AMMO_SPEAR}, + {"royalspear", AMMO_SPEAR}, + {"sniperarrow", AMMO_ARROW}, + {"onyxarrow", AMMO_ARROW}, + {"piercingbolt", AMMO_BOLT}, + {"etherealspear", AMMO_SPEAR}, + {"flasharrow", AMMO_ARROW}, + {"flammingarrow", AMMO_ARROW}, + {"shiverarrow", AMMO_ARROW}, + {"eartharrow", AMMO_ARROW}, }; -WeaponActionNames weaponActionNames[] = { +WeaponActionNames weaponActionNames = { {"move", WEAPONACTION_MOVE}, {"removecharge", WEAPONACTION_REMOVECHARGE}, {"removecount", WEAPONACTION_REMOVECOUNT}, }; -SkullNames skullNames[] = { +SkullNames skullNames = { {"none", SKULL_NONE}, {"yellow", SKULL_YELLOW}, {"green", SKULL_GREEN}, {"white", SKULL_WHITE}, {"red", SKULL_RED}, -}; - -FluidNames fluidNames[] = { - {"none", FLUID_NONE}, - {"water", FLUID_WATER}, - {"wine", FLUID_WINE}, - {"beer", FLUID_BEER}, - {"mud", FLUID_MUD}, - {"blood", FLUID_BLOOD}, - {"slime", FLUID_SLIME}, - {"oil", FLUID_OIL}, - {"urine", FLUID_URINE}, - {"milk", FLUID_MILK}, - {"manafluid", FLUID_MANAFLUID}, - {"lifefluid", FLUID_LIFEFLUID}, - {"lemonade", FLUID_LEMONADE}, - {"rum", FLUID_RUM}, - {"coconutmilk", FLUID_COCONUTMILK}, - {"fruitjuice", FLUID_FRUITJUICE} + {"black", SKULL_BLACK}, + {"orange", SKULL_ORANGE}, }; MagicEffectClasses getMagicEffect(const std::string& strValue) { - for (auto& magicEffectName : magicEffectNames) { - if (strcasecmp(strValue.c_str(), magicEffectName.name) == 0) { - return magicEffectName.effect; - } + auto magicEffect = magicEffectNames.find(strValue); + if (magicEffect != magicEffectNames.end()) { + return magicEffect->second; } return CONST_ME_NONE; } ShootType_t getShootType(const std::string& strValue) { - for (size_t i = 0, size = sizeof(shootTypeNames) / sizeof(ShootTypeNames); i < size; ++i) { - if (strcasecmp(strValue.c_str(), shootTypeNames[i].name) == 0) { - return shootTypeNames[i].shoot; - } + auto shootType = shootTypeNames.find(strValue); + if (shootType != shootTypeNames.end()) { + return shootType->second; } return CONST_ANI_NONE; } -CombatType_t getCombatType(const std::string& strValue) -{ - for (size_t i = 0, size = sizeof(combatTypeNames) / sizeof(CombatTypeNames); i < size; ++i) { - if (strcasecmp(strValue.c_str(), combatTypeNames[i].name) == 0) { - return combatTypeNames[i].combat; - } - } - return COMBAT_NONE; -} - std::string getCombatName(CombatType_t combatType) { - for (size_t i = 0, size = sizeof(combatTypeNames) / sizeof(CombatTypeNames); i < size; ++i) { - if (combatTypeNames[i].combat == combatType) { - return combatTypeNames[i].name; - } + auto combatName = combatTypeNames.find(combatType); + if (combatName != combatTypeNames.end()) { + return combatName->second; } return "unknown"; } Ammo_t getAmmoType(const std::string& strValue) { - for (size_t i = 0, size = sizeof(ammoTypeNames) / sizeof(AmmoTypeNames); i < size; ++i) { - if (strcasecmp(strValue.c_str(), ammoTypeNames[i].name) == 0) { - return ammoTypeNames[i].ammoType; - } + auto ammoType = ammoTypeNames.find(strValue); + if (ammoType != ammoTypeNames.end()) { + return ammoType->second; } return AMMO_NONE; } WeaponAction_t getWeaponAction(const std::string& strValue) { - for (size_t i = 0, size = sizeof(weaponActionNames) / sizeof(WeaponActionNames); i < size; ++i) { - if (strcasecmp(strValue.c_str(), weaponActionNames[i].name) == 0) { - return weaponActionNames[i].weaponAction; - } + auto weaponAction = weaponActionNames.find(strValue); + if (weaponAction != weaponActionNames.end()) { + return weaponAction->second; } return WEAPONACTION_NONE; } Skulls_t getSkullType(const std::string& strValue) { - for (size_t i = 0, size = sizeof(skullNames) / sizeof(SkullNames); i < size; ++i) { - if (strcasecmp(strValue.c_str(), skullNames[i].name) == 0) { - return skullNames[i].skull; - } + auto skullType = skullNames.find(strValue); + if (skullType != skullNames.end()) { + return skullType->second; } return SKULL_NONE; } -FluidTypes_t getFluidType(const std::string& strValue) +std::string getSpecialSkillName(uint8_t skillid) { - for (size_t i = 0, size = sizeof(fluidNames) / sizeof(FluidNames); i < size; ++i) { - if (strcasecmp(strValue.c_str(), fluidNames[i].name) == 0) { - return fluidNames[i].fluidType; - } + switch (skillid) { + case SPECIALSKILL_CRITICALHITCHANCE: + return "critical hit chance"; + + case SPECIALSKILL_CRITICALHITAMOUNT: + return "critical extra damage"; + + case SPECIALSKILL_LIFELEECHCHANCE: + return "hitpoints leech chance"; + + case SPECIALSKILL_LIFELEECHAMOUNT: + return "hitpoints leech amount"; + + case SPECIALSKILL_MANALEECHCHANCE: + return "manapoints leech chance"; + + case SPECIALSKILL_MANALEECHAMOUNT: + return "mana points leech amount"; + + default: + return "unknown"; } - return FLUID_NONE; } std::string getSkillName(uint8_t skillid) @@ -844,6 +813,31 @@ std::string getSkillName(uint8_t skillid) } } +uint32_t adlerChecksum(const uint8_t* data, size_t length) +{ + if (length > NETWORKMESSAGE_MAXSIZE) { + return 0; + } + + const uint16_t adler = 65521; + + uint32_t a = 1, b = 0; + + while (length > 0) { + size_t tmp = length > 5552 ? 5552 : length; + length -= tmp; + + do { + a += *data++; + b += a; + } while (--tmp); + + a %= adler; + b %= adler; + } + + return (b << 16) | a; +} std::string ucfirst(std::string str) { @@ -917,6 +911,12 @@ size_t combatTypeToIndex(CombatType_t combatType) return 7; case COMBAT_DROWNDAMAGE: return 8; + case COMBAT_ICEDAMAGE: + return 9; + case COMBAT_HOLYDAMAGE: + return 10; + case COMBAT_DEATHDAMAGE: + return 11; default: return 0; } @@ -927,12 +927,32 @@ CombatType_t indexToCombatType(size_t v) return static_cast(1 << v); } +uint8_t serverFluidToClient(uint8_t serverFluid) +{ + uint8_t size = sizeof(clientToServerFluidMap) / sizeof(uint8_t); + for (uint8_t i = 0; i < size; ++i) { + if (clientToServerFluidMap[i] == serverFluid) { + return i; + } + } + return 0; +} + +uint8_t clientFluidToServer(uint8_t clientFluid) +{ + uint8_t size = sizeof(clientToServerFluidMap) / sizeof(uint8_t); + if (clientFluid >= size) { + return 0; + } + return clientToServerFluidMap[clientFluid]; +} + itemAttrTypes stringToItemAttribute(const std::string& str) { if (str == "aid") { return ITEM_ATTRIBUTE_ACTIONID; - } else if (str == "mid") { - return ITEM_ATTRIBUTE_MOVEMENTID; + } else if (str == "uid") { + return ITEM_ATTRIBUTE_UNIQUEID; } else if (str == "description") { return ITEM_ATTRIBUTE_DESCRIPTION; } else if (str == "text") { @@ -953,8 +973,12 @@ itemAttrTypes stringToItemAttribute(const std::string& str) return ITEM_ATTRIBUTE_ATTACK; } else if (str == "defense") { return ITEM_ATTRIBUTE_DEFENSE; + } else if (str == "extradefense") { + return ITEM_ATTRIBUTE_EXTRADEFENSE; } else if (str == "armor") { return ITEM_ATTRIBUTE_ARMOR; + } else if (str == "hitchance") { + return ITEM_ATTRIBUTE_HITCHANCE; } else if (str == "shootrange") { return ITEM_ATTRIBUTE_SHOOTRANGE; } else if (str == "owner") { @@ -1157,8 +1181,8 @@ const char* getReturnMessage(ReturnValue value) case RETURNVALUE_YOUNEEDTOSPLITYOURSPEARS: return "You need to split your spears first."; - case RETURNVALUE_NAMEISTOOAMBIGIOUS: - return "Name is too ambigious."; + case RETURNVALUE_NAMEISTOOAMBIGUOUS: + return "Player name is ambiguous."; case RETURNVALUE_CANONLYUSEONESHIELD: return "You may use only one shield."; @@ -1169,6 +1193,12 @@ const char* getReturnMessage(ReturnValue value) case RETURNVALUE_YOUARENOTTHEOWNER: return "You are not the owner."; + case RETURNVALUE_NOSUCHRAIDEXISTS: + return "No such raid exists."; + + case RETURNVALUE_ANOTHERRAIDISALREADYEXECUTING: + return "Another raid is already executing."; + case RETURNVALUE_TRADEPLAYERFARAWAY: return "Trade player is too far away."; @@ -1189,23 +1219,23 @@ const char* getReturnMessage(ReturnValue value) } } -void getFilesInDirectory(const boost::filesystem::path& root, const std::string& ext, std::vector& ret) +int64_t OTSYS_TIME() { - if (!boost::filesystem::exists(root)) { - return; + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); +} + +SpellGroup_t stringToSpellGroup(std::string value) +{ + std::string tmpStr = asLowerCaseString(value); + if (tmpStr == "attack" || tmpStr == "1") { + return SPELLGROUP_ATTACK; + } else if (tmpStr == "healing" || tmpStr == "2") { + return SPELLGROUP_HEALING; + } else if (tmpStr == "support" || tmpStr == "3") { + return SPELLGROUP_SUPPORT; + } else if (tmpStr == "special" || tmpStr == "4") { + return SPELLGROUP_SPECIAL; } - if (boost::filesystem::is_directory(root)) - { - boost::filesystem::recursive_directory_iterator it(root); - boost::filesystem::recursive_directory_iterator endit; - while (it != endit) - { - if (boost::filesystem::is_regular_file(*it) && it->path().extension() == ext) - { - ret.push_back(it->path().filename()); - } - ++it; - } - } + return SPELLGROUP_NONE; } diff --git a/src/tools.h b/src/tools.h index 8f05a93..f59004e 100644 --- a/src/tools.h +++ b/src/tools.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,7 +21,6 @@ #define FS_TOOLS_H_5F9A9742DA194628830AA1C64909AE43 #include -#include #include "position.h" #include "const.h" @@ -31,10 +30,7 @@ void printXMLError(const std::string& where, const std::string& fileName, const std::string transformToSHA1(const std::string& input); std::string generateToken(const std::string& key, uint32_t ticks); -uint8_t getLiquidColor(uint8_t type); -void extractArticleAndName(std::string& data, std::string& article, std::string& name); -std::string pluralizeString(std::string str); void replaceString(std::string& str, const std::string& sought, const std::string& replacement); void trim_right(std::string& source, char t); void trim_left(std::string& source, char t); @@ -42,20 +38,15 @@ void toLowerCaseString(std::string& source); std::string asLowerCaseString(std::string source); std::string asUpperCaseString(std::string source); -typedef std::vector StringVec; -typedef std::vector IntegerVec; +using StringVector = std::vector; +using IntegerVector = std::vector; -StringVec explodeString(const std::string& inString, const std::string& separator, int32_t limit = -1); -IntegerVec vectorAtoi(const StringVec& stringVector); -inline bool hasBitSet(uint32_t flag, uint32_t flags) { +StringVector explodeString(const std::string& inString, const std::string& separator, int32_t limit = -1); +IntegerVector vectorAtoi(const StringVector& stringVector); +constexpr bool hasBitSet(uint32_t flag, uint32_t flags) { return (flags & flag) != 0; } -inline bool IsDigit(char c) -{ - return ('0' <= c && c <= '9'); -} - std::mt19937& getRandomGenerator(); int32_t uniform_random(int32_t minNumber, int32_t maxNumber); int32_t normal_random(int32_t minNumber, int32_t maxNumber); @@ -77,13 +68,14 @@ MagicEffectClasses getMagicEffect(const std::string& strValue); ShootType_t getShootType(const std::string& strValue); Ammo_t getAmmoType(const std::string& strValue); WeaponAction_t getWeaponAction(const std::string& strValue); -CombatType_t getCombatType(const std::string& strValue); Skulls_t getSkullType(const std::string& strValue); -FluidTypes_t getFluidType(const std::string& strValue); std::string getCombatName(CombatType_t combatType); +std::string getSpecialSkillName(uint8_t skillid); std::string getSkillName(uint8_t skillid); +uint32_t adlerChecksum(const uint8_t* data, size_t length); + std::string ucfirst(std::string str); std::string ucwords(std::string str); bool booleanString(const std::string& str); @@ -93,15 +85,15 @@ std::string getWeaponName(WeaponType_t weaponType); size_t combatTypeToIndex(CombatType_t combatType); CombatType_t indexToCombatType(size_t v); +uint8_t serverFluidToClient(uint8_t serverFluid); +uint8_t clientFluidToServer(uint8_t clientFluid); + itemAttrTypes stringToItemAttribute(const std::string& str); const char* getReturnMessage(ReturnValue value); -void getFilesInDirectory(const boost::filesystem::path& root, const std::string& ext, std::vector& ret); +int64_t OTSYS_TIME(); -inline int64_t OTSYS_TIME() -{ - return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); -} +SpellGroup_t stringToSpellGroup(std::string value); #endif diff --git a/src/town.h b/src/town.h index 9e50720..be3c9e9 100644 --- a/src/town.h +++ b/src/town.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -50,7 +50,7 @@ class Town Position templePosition; }; -typedef std::map TownMap; +using TownMap = std::map; class Towns { diff --git a/src/trashholder.cpp b/src/trashholder.cpp new file mode 100644 index 0000000..5d90758 --- /dev/null +++ b/src/trashholder.cpp @@ -0,0 +1,102 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "trashholder.h" +#include "game.h" + +extern Game g_game; + +ReturnValue TrashHolder::queryAdd(int32_t, const Thing&, uint32_t, uint32_t, Creature*) const +{ + return RETURNVALUE_NOERROR; +} + +ReturnValue TrashHolder::queryMaxCount(int32_t, const Thing&, uint32_t count, uint32_t& maxQueryCount, uint32_t) const +{ + maxQueryCount = std::max(1, count); + return RETURNVALUE_NOERROR; +} + +ReturnValue TrashHolder::queryRemove(const Thing&, uint32_t, uint32_t) const +{ + return RETURNVALUE_NOTPOSSIBLE; +} + +Cylinder* TrashHolder::queryDestination(int32_t&, const Thing&, Item**, uint32_t&) +{ + return this; +} + +void TrashHolder::addThing(Thing* thing) +{ + return addThing(0, thing); +} + +void TrashHolder::addThing(int32_t, Thing* thing) +{ + Item* item = thing->getItem(); + if (!item) { + return; + } + + if (item == this || !item->hasProperty(CONST_PROP_MOVEABLE)) { + return; + } + + const ItemType& it = Item::items[id]; + if (item->isHangable() && it.isGroundTile()) { + Tile* tile = dynamic_cast(getParent()); + if (tile && tile->hasFlag(TILESTATE_SUPPORTS_HANGABLE)) { + return; + } + } + + g_game.internalRemoveItem(item); + + if (it.magicEffect != CONST_ME_NONE) { + g_game.addMagicEffect(getPosition(), it.magicEffect); + } +} + +void TrashHolder::updateThing(Thing*, uint16_t, uint32_t) +{ + // +} + +void TrashHolder::replaceThing(uint32_t, Thing*) +{ + // +} + +void TrashHolder::removeThing(Thing*, uint32_t) +{ + // +} + +void TrashHolder::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t) +{ + getParent()->postAddNotification(thing, oldParent, index, LINK_PARENT); +} + +void TrashHolder::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) +{ + getParent()->postRemoveNotification(thing, newParent, index, LINK_PARENT); +} diff --git a/src/trashholder.h b/src/trashholder.h new file mode 100644 index 0000000..a8d1f46 --- /dev/null +++ b/src/trashholder.h @@ -0,0 +1,57 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_TRASHHOLDER_H_BA162024D67B4D388147F5EE06F33098 +#define FS_TRASHHOLDER_H_BA162024D67B4D388147F5EE06F33098 + +#include "item.h" +#include "cylinder.h" +#include "const.h" + +class TrashHolder final : public Item, public Cylinder +{ + public: + explicit TrashHolder(uint16_t itemId) : Item(itemId) {} + + TrashHolder* getTrashHolder() override { + return this; + } + const TrashHolder* getTrashHolder() const override { + return this; + } + + //cylinder implementations + 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 override; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const override; + Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) override; + + void addThing(Thing* thing) override; + void addThing(int32_t index, Thing* thing) override; + + void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override; + void replaceThing(uint32_t index, Thing* thing) override; + + void removeThing(Thing* thing, uint32_t count) override; + + 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; +}; + +#endif diff --git a/src/vocation.cpp b/src/vocation.cpp index 98a9973..015fdac 100644 --- a/src/vocation.cpp +++ b/src/vocation.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -46,17 +46,14 @@ bool Vocations::loadFromXml() std::forward_as_tuple(id), std::forward_as_tuple(id)); Vocation& voc = res.first->second; - if (!(attr = vocationNode.attribute("flagid"))) { - std::cout << "[Warning - Vocations::loadFromXml] Missing vocation flag id" << std::endl; - continue; - } - - voc.flagid = pugi::cast(attr.value()); - if ((attr = vocationNode.attribute("name"))) { voc.name = attr.as_string(); } + if ((attr = vocationNode.attribute("clientid"))) { + voc.clientId = pugi::cast(attr.value()); + } + if ((attr = vocationNode.attribute("description"))) { voc.description = attr.as_string(); } @@ -126,6 +123,26 @@ bool Vocations::loadFromXml() } else { std::cout << "[Notice - Vocations::loadFromXml] Missing skill id for vocation: " << voc.id << std::endl; } + } else if (strcasecmp(childNode.name(), "formula") == 0) { + pugi::xml_attribute meleeDamageAttribute = childNode.attribute("meleeDamage"); + if (meleeDamageAttribute) { + voc.meleeDamageMultiplier = pugi::cast(meleeDamageAttribute.value()); + } + + pugi::xml_attribute distDamageAttribute = childNode.attribute("distDamage"); + if (distDamageAttribute) { + voc.distDamageMultiplier = pugi::cast(distDamageAttribute.value()); + } + + pugi::xml_attribute defenseAttribute = childNode.attribute("defense"); + if (defenseAttribute) { + voc.defenseMultiplier = pugi::cast(defenseAttribute.value()); + } + + pugi::xml_attribute armorAttribute = childNode.attribute("armor"); + if (armorAttribute) { + voc.armorMultiplier = pugi::cast(armorAttribute.value()); + } } } } @@ -187,7 +204,7 @@ uint64_t Vocation::getReqMana(uint32_t magLevel) return it->second; } - uint64_t reqMana = static_cast(400 * std::pow(manaMultiplier, static_cast(magLevel) - 1)); + uint64_t reqMana = static_cast(1600 * std::pow(manaMultiplier, static_cast(magLevel) - 1)); uint32_t modResult = reqMana % 20; if (modResult < 10) { reqMana -= modResult; diff --git a/src/vocation.h b/src/vocation.h index b6bc48e..757d45a 100644 --- a/src/vocation.h +++ b/src/vocation.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,8 +40,9 @@ class Vocation uint16_t getId() const { return id; } - uint16_t getFlagId() const { - return flagid; + + uint8_t getClientId() const { + return clientId; } uint32_t getHPGain() const { @@ -85,7 +86,12 @@ class Vocation return fromVocation; } - protected: + float meleeDamageMultiplier = 1.0f; + float distDamageMultiplier = 1.0f; + float defenseMultiplier = 1.0f; + float armorMultiplier = 1.0f; + + private: friend class Vocations; std::map cacheMana; @@ -106,13 +112,13 @@ class Vocation uint32_t gainHP = 5; uint32_t fromVocation = VOCATION_NONE; uint32_t attackSpeed = 1500; - uint32_t baseSpeed = 70; + uint32_t baseSpeed = 220; uint16_t id; - uint16_t flagid; uint16_t gainSoulTicks = 120; uint8_t soulMax = 100; + uint8_t clientId = 0; static uint32_t skillBase[SKILL_LAST + 1]; }; diff --git a/src/waitlist.cpp b/src/waitlist.cpp index 2743629..355e88b 100644 --- a/src/waitlist.cpp +++ b/src/waitlist.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,26 +26,71 @@ extern ConfigManager g_config; extern Game g_game; -WaitListIterator WaitingList::findClient(const Player* player, uint32_t& slot) -{ - slot = 1; - for (auto it = priorityWaitList.begin(), end = priorityWaitList.end(); it != end; ++it) { - if (it->playerGUID == player->getGUID()) { - return it; - } - ++slot; - } - for (auto it = waitList.begin(), end = waitList.end(); it != end; ++it) { - if (it->playerGUID == player->getGUID()) { - return it; +namespace { + +struct Wait +{ + constexpr Wait(std::size_t timeout, uint32_t playerGUID) : + timeout(timeout), playerGUID(playerGUID) {} + + std::size_t timeout; + uint32_t playerGUID; +}; + +using WaitList = std::list; + +void cleanupList(WaitList& list) +{ + int64_t time = OTSYS_TIME(); + + auto it = list.begin(), end = list.end(); + while (it != end) { + if ((it->timeout - time) <= 0) { + it = list.erase(it); + } else { + ++it; } - ++slot; } - return waitList.end(); } -uint32_t WaitingList::getTime(uint32_t slot) +std::size_t getTimeout(std::size_t slot) +{ + //timeout is set to 15 seconds longer than expected retry attempt + return WaitingList::getTime(slot) + 15; +} + +} // namespace + +struct WaitListInfo +{ + WaitList priorityWaitList; + WaitList waitList; + + std::pair findClient(const Player *player) { + std::size_t slot = 1; + for (auto it = priorityWaitList.begin(), end = priorityWaitList.end(); it != end; ++it, ++slot) { + if (it->playerGUID == player->getGUID()) { + return {it, slot}; + } + } + + for (auto it = waitList.begin(), end = waitList.end(); it != end; ++it, ++slot) { + if (it->playerGUID == player->getGUID()) { + return {it, slot}; + } + } + return {waitList.end(), slot}; + } +}; + +WaitingList& WaitingList::getInstance() +{ + static WaitingList waitingList; + return waitingList; +} + +std::size_t WaitingList::getTime(std::size_t slot) { if (slot < 5) { return 5; @@ -60,12 +105,6 @@ uint32_t WaitingList::getTime(uint32_t slot) } } -uint32_t WaitingList::getTimeout(uint32_t slot) -{ - //timeout is set to 15 seconds longer than expected retry attempt - return getTime(slot) + 15; -} - bool WaitingList::clientLogin(const Player* player) { if (player->hasFlag(PlayerFlag_CanAlwaysLogin) || player->getAccountType() >= ACCOUNT_TYPE_GAMEMASTER) { @@ -73,20 +112,20 @@ bool WaitingList::clientLogin(const Player* player) } uint32_t maxPlayers = static_cast(g_config.getNumber(ConfigManager::MAX_PLAYERS)); - if (maxPlayers == 0 || (priorityWaitList.empty() && waitList.empty() && g_game.getPlayersOnline() < maxPlayers)) { + if (maxPlayers == 0 || (info->priorityWaitList.empty() && info->waitList.empty() && g_game.getPlayersOnline() < maxPlayers)) { return true; } - WaitingList::cleanupList(priorityWaitList); - WaitingList::cleanupList(waitList); + cleanupList(info->priorityWaitList); + cleanupList(info->waitList); - uint32_t slot; - - auto it = findClient(player, slot); - if (it != waitList.end()) { + WaitList::iterator it; + WaitList::size_type slot; + std::tie(it, slot) = info->findClient(player); + if (it != info->waitList.end()) { if ((g_game.getPlayersOnline() + slot) <= maxPlayers) { //should be able to login now - waitList.erase(it); + info->waitList.erase(it); return true; } @@ -95,36 +134,25 @@ bool WaitingList::clientLogin(const Player* player) return false; } - slot = priorityWaitList.size(); + slot = info->priorityWaitList.size(); if (player->isPremium()) { - priorityWaitList.emplace_back(OTSYS_TIME() + (getTimeout(slot + 1) * 1000), player->getGUID()); + info->priorityWaitList.emplace_back(OTSYS_TIME() + (getTimeout(slot + 1) * 1000), player->getGUID()); } else { - slot += waitList.size(); - waitList.emplace_back(OTSYS_TIME() + (getTimeout(slot + 1) * 1000), player->getGUID()); + slot += info->waitList.size(); + info->waitList.emplace_back(OTSYS_TIME() + (getTimeout(slot + 1) * 1000), player->getGUID()); } return false; } -uint32_t WaitingList::getClientSlot(const Player* player) +std::size_t WaitingList::getClientSlot(const Player* player) { - uint32_t slot; - auto it = findClient(player, slot); - if (it == waitList.end()) { + WaitList::iterator it; + WaitList::size_type slot; + std::tie(it, slot) = info->findClient(player); + if (it == info->waitList.end()) { return 0; } return slot; } -void WaitingList::cleanupList(WaitList& list) -{ - int64_t time = OTSYS_TIME(); - - auto it = list.begin(), end = list.end(); - while (it != end) { - if ((it->timeout - time) <= 0) { - it = list.erase(it); - } else { - ++it; - } - } -} +WaitingList::WaitingList() : info(new WaitListInfo) {} diff --git a/src/waitlist.h b/src/waitlist.h index 77dd2c5..661e7b9 100644 --- a/src/waitlist.h +++ b/src/waitlist.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,36 +22,21 @@ #include "player.h" -struct Wait { - constexpr Wait(int64_t timeout, uint32_t playerGUID) : - timeout(timeout), playerGUID(playerGUID) {} - - int64_t timeout; - uint32_t playerGUID; -}; - -typedef std::list WaitList; -typedef WaitList::iterator WaitListIterator; +struct WaitListInfo; class WaitingList { public: - static WaitingList* getInstance() { - static WaitingList waitingList; - return &waitingList; - } + static WaitingList& getInstance(); bool clientLogin(const Player* player); - uint32_t getClientSlot(const Player* player); - static uint32_t getTime(uint32_t slot); + std::size_t getClientSlot(const Player* player); + static std::size_t getTime(std::size_t slot); - protected: - WaitList priorityWaitList; - WaitList waitList; + private: + WaitingList(); - static uint32_t getTimeout(uint32_t slot); - WaitListIterator findClient(const Player* player, uint32_t& slot); - static void cleanupList(WaitList& list); + std::unique_ptr info; }; #endif diff --git a/src/weapons.cpp b/src/weapons.cpp new file mode 100644 index 0000000..2452f8a --- /dev/null +++ b/src/weapons.cpp @@ -0,0 +1,944 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "otpch.h" + +#include "combat.h" +#include "configmanager.h" +#include "game.h" +#include "pugicast.h" +#include "weapons.h" + +extern Game g_game; +extern Vocations g_vocations; +extern ConfigManager g_config; +extern Weapons* g_weapons; + +Weapons::Weapons() +{ + scriptInterface.initState(); +} + +Weapons::~Weapons() +{ + clear(false); +} + +const Weapon* Weapons::getWeapon(const Item* item) const +{ + if (!item) { + return nullptr; + } + + auto it = weapons.find(item->getID()); + if (it == weapons.end()) { + return nullptr; + } + return it->second; +} + +void Weapons::clear(bool fromLua) +{ + for (auto it = weapons.begin(); it != weapons.end(); ) { + if (fromLua == it->second->fromLua) { + it = weapons.erase(it); + } else { + ++it; + } + } + + reInitState(fromLua); +} + +LuaScriptInterface& Weapons::getScriptInterface() +{ + return scriptInterface; +} + +std::string Weapons::getScriptBaseName() const +{ + return "weapons"; +} + +void Weapons::loadDefaults() +{ + for (size_t i = 100, size = Item::items.size(); i < size; ++i) { + const ItemType& it = Item::items.getItemType(i); + if (it.id == 0 || weapons.find(i) != weapons.end()) { + continue; + } + + switch (it.weaponType) { + case WEAPON_AXE: + case WEAPON_SWORD: + case WEAPON_CLUB: { + WeaponMelee* weapon = new WeaponMelee(&scriptInterface); + weapon->configureWeapon(it); + weapons[i] = weapon; + break; + } + + case WEAPON_AMMO: + case WEAPON_DISTANCE: { + if (it.weaponType == WEAPON_DISTANCE && it.ammoType != AMMO_NONE) { + continue; + } + + WeaponDistance* weapon = new WeaponDistance(&scriptInterface); + weapon->configureWeapon(it); + weapons[i] = weapon; + break; + } + + default: + break; + } + } +} + +Event_ptr Weapons::getEvent(const std::string& nodeName) +{ + if (strcasecmp(nodeName.c_str(), "melee") == 0) { + return Event_ptr(new WeaponMelee(&scriptInterface)); + } else if (strcasecmp(nodeName.c_str(), "distance") == 0) { + return Event_ptr(new WeaponDistance(&scriptInterface)); + } else if (strcasecmp(nodeName.c_str(), "wand") == 0) { + return Event_ptr(new WeaponWand(&scriptInterface)); + } + return nullptr; +} + +bool Weapons::registerEvent(Event_ptr event, const pugi::xml_node&) +{ + Weapon* weapon = static_cast(event.release()); //event is guaranteed to be a Weapon + + auto result = weapons.emplace(weapon->getID(), weapon); + if (!result.second) { + std::cout << "[Warning - Weapons::registerEvent] Duplicate registered item with id: " << weapon->getID() << std::endl; + } + return result.second; +} + +bool Weapons::registerLuaEvent(Weapon* event) +{ + Weapon_ptr weapon{ event }; + weapons[weapon->getID()] = weapon.release(); + + return true; +} + +//monsters +int32_t Weapons::getMaxMeleeDamage(int32_t attackSkill, int32_t attackValue) +{ + return static_cast(std::ceil((attackSkill * (attackValue * 0.05)) + (attackValue * 0.5))); +} + +//players +int32_t Weapons::getMaxWeaponDamage(uint32_t level, int32_t attackSkill, int32_t attackValue, float attackFactor) +{ + return static_cast(std::round((level / 5) + (((((attackSkill / 4.) + 1) * (attackValue / 3.)) * 1.03) / attackFactor))); +} + +bool Weapon::configureEvent(const pugi::xml_node& node) +{ + pugi::xml_attribute attr; + if (!(attr = node.attribute("id"))) { + std::cout << "[Error - Weapon::configureEvent] Weapon without id." << std::endl; + return false; + } + id = pugi::cast(attr.value()); + + if ((attr = node.attribute("level"))) { + level = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("maglv")) || (attr = node.attribute("maglevel"))) { + magLevel = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("mana"))) { + mana = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("manapercent"))) { + manaPercent = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("soul"))) { + soul = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("prem"))) { + premium = attr.as_bool(); + } + + if ((attr = node.attribute("breakchance"))) { + breakChance = std::min(100, pugi::cast(attr.value())); + } + + if ((attr = node.attribute("action"))) { + action = getWeaponAction(asLowerCaseString(attr.as_string())); + if (action == WEAPONACTION_NONE) { + std::cout << "[Warning - Weapon::configureEvent] Unknown action " << attr.as_string() << std::endl; + } + } + + if ((attr = node.attribute("enabled"))) { + enabled = attr.as_bool(); + } + + if ((attr = node.attribute("unproperly"))) { + wieldUnproperly = attr.as_bool(); + } + + std::list vocStringList; + for (auto vocationNode : node.children()) { + if (!(attr = vocationNode.attribute("name"))) { + continue; + } + + int32_t vocationId = g_vocations.getVocationId(attr.as_string()); + if (vocationId != -1) { + vocWeaponMap[vocationId] = true; + int32_t promotedVocation = g_vocations.getPromotedVocation(vocationId); + if (promotedVocation != VOCATION_NONE) { + vocWeaponMap[promotedVocation] = true; + } + + if (vocationNode.attribute("showInDescription").as_bool(true)) { + vocStringList.push_back(asLowerCaseString(attr.as_string())); + } + } + } + + std::string vocationString; + 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'); + } + + uint32_t wieldInfo = 0; + if (getReqLevel() > 0) { + wieldInfo |= WIELDINFO_LEVEL; + } + + if (getReqMagLv() > 0) { + wieldInfo |= WIELDINFO_MAGLV; + } + + if (!vocationString.empty()) { + wieldInfo |= WIELDINFO_VOCREQ; + } + + if (isPremium()) { + wieldInfo |= WIELDINFO_PREMIUM; + } + + if (wieldInfo != 0) { + ItemType& it = Item::items.getItemType(id); + it.wieldInfo = wieldInfo; + it.vocationString = vocationString; + it.minReqLevel = getReqLevel(); + it.minReqMagicLevel = getReqMagLv(); + } + + configureWeapon(Item::items[id]); + return true; +} + +void Weapon::configureWeapon(const ItemType& it) +{ + id = it.id; +} + +std::string Weapon::getScriptEventName() const +{ + return "onUseWeapon"; +} + +int32_t Weapon::playerWeaponCheck(Player* player, Creature* target, uint8_t shootRange) const +{ + const Position& playerPos = player->getPosition(); + const Position& targetPos = target->getPosition(); + if (playerPos.z != targetPos.z) { + return 0; + } + + if (std::max(Position::getDistanceX(playerPos, targetPos), Position::getDistanceY(playerPos, targetPos)) > shootRange) { + return 0; + } + + if (!player->hasFlag(PlayerFlag_IgnoreWeaponCheck)) { + if (!enabled) { + return 0; + } + + if (player->getMana() < getManaCost(player)) { + return 0; + } + + if (player->getHealth() < getHealthCost(player)) { + return 0; + } + + if (player->getSoul() < soul) { + return 0; + } + + if (isPremium() && !player->isPremium()) { + return 0; + } + + if (!vocWeaponMap.empty()) { + if (vocWeaponMap.find(player->getVocationId()) == vocWeaponMap.end()) { + return 0; + } + } + + int32_t damageModifier = 100; + if (player->getLevel() < getReqLevel()) { + damageModifier = (isWieldedUnproperly() ? damageModifier / 2 : 0); + } + + if (player->getMagicLevel() < getReqMagLv()) { + damageModifier = (isWieldedUnproperly() ? damageModifier / 2 : 0); + } + return damageModifier; + } + + return 100; +} + +bool Weapon::useWeapon(Player* player, Item* item, Creature* target) const +{ + int32_t damageModifier = playerWeaponCheck(player, target, item->getShootRange()); + if (damageModifier == 0) { + return false; + } + + internalUseWeapon(player, item, target, damageModifier); + return true; +} + +bool Weapon::useFist(Player* player, Creature* target) +{ + if (!Position::areInRange<1, 1>(player->getPosition(), target->getPosition())) { + return false; + } + + float attackFactor = player->getAttackFactor(); + int32_t attackSkill = player->getSkillLevel(SKILL_FIST); + int32_t attackValue = 7; + + int32_t maxDamage = Weapons::getMaxWeaponDamage(player->getLevel(), attackSkill, attackValue, attackFactor); + + CombatParams params; + params.combatType = COMBAT_PHYSICALDAMAGE; + params.blockedByArmor = true; + params.blockedByShield = true; + + CombatDamage damage; + damage.origin = ORIGIN_MELEE; + damage.primary.type = params.combatType; + damage.primary.value = -normal_random(0, maxDamage); + + Combat::doCombatHealth(player, target, damage, params); + if (!player->hasFlag(PlayerFlag_NotGainSkill) && player->getAddAttackSkill()) { + player->addSkillAdvance(SKILL_FIST, 1); + } + + return true; +} + +void Weapon::internalUseWeapon(Player* player, Item* item, Creature* target, int32_t damageModifier) const +{ + if (scripted) { + LuaVariant var; + var.type = VARIANT_NUMBER; + var.number = target->getID(); + executeUseWeapon(player, var); + } else { + CombatDamage damage; + WeaponType_t weaponType = item->getWeaponType(); + if (weaponType == WEAPON_AMMO || weaponType == WEAPON_DISTANCE) { + damage.origin = ORIGIN_RANGED; + } else { + damage.origin = ORIGIN_MELEE; + } + damage.primary.type = params.combatType; + damage.primary.value = (getWeaponDamage(player, target, item) * damageModifier) / 100; + damage.secondary.type = getElementType(); + damage.secondary.value = getElementDamage(player, target, item); + Combat::doCombatHealth(player, target, damage, params); + } + + onUsedWeapon(player, item, target->getTile()); +} + +void Weapon::internalUseWeapon(Player* player, Item* item, Tile* tile) const +{ + if (scripted) { + LuaVariant var; + var.type = VARIANT_TARGETPOSITION; + var.pos = tile->getPosition(); + executeUseWeapon(player, var); + } else { + Combat::postCombatEffects(player, tile->getPosition(), params); + g_game.addMagicEffect(tile->getPosition(), CONST_ME_POFF); + } + + onUsedWeapon(player, item, tile); +} + +void Weapon::onUsedWeapon(Player* player, Item* item, Tile* destTile) const +{ + if (!player->hasFlag(PlayerFlag_NotGainSkill)) { + skills_t skillType; + uint32_t skillPoint; + if (getSkillType(player, item, skillType, skillPoint)) { + player->addSkillAdvance(skillType, skillPoint); + } + } + + uint32_t manaCost = getManaCost(player); + if (manaCost != 0) { + player->addManaSpent(manaCost); + player->changeMana(-static_cast(manaCost)); + } + + uint32_t healthCost = getHealthCost(player); + if (healthCost != 0) { + player->changeHealth(-static_cast(healthCost)); + } + + if (!player->hasFlag(PlayerFlag_HasInfiniteSoul) && soul > 0) { + player->changeSoul(-static_cast(soul)); + } + + if (breakChance != 0 && uniform_random(1, 100) <= breakChance) { + Weapon::decrementItemCount(item); + return; + } + + switch (action) { + case WEAPONACTION_REMOVECOUNT: + Weapon::decrementItemCount(item); + break; + + case WEAPONACTION_REMOVECHARGE: { + uint16_t charges = item->getCharges(); + if (charges != 0) { + g_game.transformItem(item, item->getID(), charges - 1); + } + break; + } + + case WEAPONACTION_MOVE: + g_game.internalMoveItem(item->getParent(), destTile, INDEX_WHEREEVER, item, 1, nullptr, FLAG_NOLIMIT); + break; + + default: + break; + } +} + +uint32_t Weapon::getManaCost(const Player* player) const +{ + if (mana != 0) { + return mana; + } + + if (manaPercent == 0) { + return 0; + } + + return (player->getMaxMana() * manaPercent) / 100; +} + +int32_t Weapon::getHealthCost(const Player* player) const +{ + if (health != 0) { + return health; + } + + if (healthPercent == 0) { + return 0; + } + + return (player->getMaxHealth() * healthPercent) / 100; +} + +bool Weapon::executeUseWeapon(Player* player, const LuaVariant& var) const +{ + //onUseWeapon(player, var) + if (!scriptInterface->reserveScriptEnv()) { + std::cout << "[Error - Weapon::executeUseWeapon] 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"); + scriptInterface->pushVariant(L, var); + + return scriptInterface->callFunction(2); +} + +void Weapon::decrementItemCount(Item* item) +{ + uint16_t count = item->getItemCount(); + if (count > 1) { + g_game.transformItem(item, item->getID(), count - 1); + } else { + g_game.internalRemoveItem(item); + } +} + +WeaponMelee::WeaponMelee(LuaScriptInterface* interface) : + Weapon(interface) +{ + params.blockedByArmor = true; + params.blockedByShield = true; + params.combatType = COMBAT_PHYSICALDAMAGE; +} + +void WeaponMelee::configureWeapon(const ItemType& it) +{ + if (it.abilities) { + elementType = it.abilities->elementType; + elementDamage = it.abilities->elementDamage; + params.aggressive = true; + params.useCharges = true; + } else { + elementType = COMBAT_NONE; + elementDamage = 0; + } + Weapon::configureWeapon(it); +} + +bool WeaponMelee::useWeapon(Player* player, Item* item, Creature* target) const +{ + int32_t damageModifier = playerWeaponCheck(player, target, item->getShootRange()); + if (damageModifier == 0) { + return false; + } + + internalUseWeapon(player, item, target, damageModifier); + return true; +} + +bool WeaponMelee::getSkillType(const Player* player, const Item* item, + skills_t& skill, uint32_t& skillpoint) const +{ + if (player->getAddAttackSkill() && player->getLastAttackBlockType() != BLOCK_IMMUNITY) { + skillpoint = 1; + } else { + skillpoint = 0; + } + + WeaponType_t weaponType = item->getWeaponType(); + switch (weaponType) { + case WEAPON_SWORD: { + skill = SKILL_SWORD; + return true; + } + + case WEAPON_CLUB: { + skill = SKILL_CLUB; + return true; + } + + case WEAPON_AXE: { + skill = SKILL_AXE; + return true; + } + + default: + break; + } + return false; +} + +int32_t WeaponMelee::getElementDamage(const Player* player, const Creature*, const Item* item) const +{ + if (elementType == COMBAT_NONE) { + return 0; + } + + int32_t attackSkill = player->getWeaponSkill(item); + int32_t attackValue = elementDamage; + float attackFactor = player->getAttackFactor(); + + int32_t maxValue = Weapons::getMaxWeaponDamage(player->getLevel(), attackSkill, attackValue, attackFactor); + return -normal_random(0, static_cast(maxValue * player->getVocation()->meleeDamageMultiplier)); +} + +int32_t WeaponMelee::getWeaponDamage(const Player* player, const Creature*, const Item* item, bool maxDamage /*= false*/) const +{ + int32_t attackSkill = player->getWeaponSkill(item); + int32_t attackValue = std::max(0, item->getAttack()); + float attackFactor = player->getAttackFactor(); + + int32_t maxValue = static_cast(Weapons::getMaxWeaponDamage(player->getLevel(), attackSkill, attackValue, attackFactor) * player->getVocation()->meleeDamageMultiplier); + if (maxDamage) { + return -maxValue; + } + + return -normal_random(0, maxValue); +} + +WeaponDistance::WeaponDistance(LuaScriptInterface* interface) : + Weapon(interface) +{ + params.blockedByArmor = true; + params.combatType = COMBAT_PHYSICALDAMAGE; +} + +void WeaponDistance::configureWeapon(const ItemType& it) +{ + params.distanceEffect = it.shootType; + + if (it.abilities) { + elementType = it.abilities->elementType; + elementDamage = it.abilities->elementDamage; + params.aggressive = true; + params.useCharges = true; + } else { + elementType = COMBAT_NONE; + elementDamage = 0; + } + + Weapon::configureWeapon(it); +} + +bool WeaponDistance::useWeapon(Player* player, Item* item, Creature* target) const +{ + int32_t damageModifier; + const ItemType& it = Item::items[id]; + if (it.weaponType == WEAPON_AMMO) { + Item* mainWeaponItem = player->getWeapon(true); + const Weapon* mainWeapon = g_weapons->getWeapon(mainWeaponItem); + if (mainWeapon) { + damageModifier = mainWeapon->playerWeaponCheck(player, target, mainWeaponItem->getShootRange()); + } else { + damageModifier = playerWeaponCheck(player, target, mainWeaponItem->getShootRange()); + } + } else { + damageModifier = playerWeaponCheck(player, target, item->getShootRange()); + } + + if (damageModifier == 0) { + return false; + } + + int32_t chance; + if (it.hitChance == 0) { + //hit chance is based on distance to target and distance skill + uint32_t skill = player->getSkillLevel(SKILL_DISTANCE); + const Position& playerPos = player->getPosition(); + const Position& targetPos = target->getPosition(); + uint32_t distance = std::max(Position::getDistanceX(playerPos, targetPos), Position::getDistanceY(playerPos, targetPos)); + + uint32_t maxHitChance; + if (it.maxHitChance != -1) { + maxHitChance = it.maxHitChance; + } else if (it.ammoType != AMMO_NONE) { + //hit chance on two-handed weapons is limited to 90% + maxHitChance = 90; + } else { + //one-handed is set to 75% + maxHitChance = 75; + } + + if (maxHitChance == 75) { + //chance for one-handed weapons + switch (distance) { + case 1: + case 5: + chance = std::min(skill, 74) + 1; + break; + case 2: + chance = static_cast(std::min(skill, 28) * 2.40f) + 8; + break; + case 3: + chance = static_cast(std::min(skill, 45) * 1.55f) + 6; + break; + case 4: + chance = static_cast(std::min(skill, 58) * 1.25f) + 3; + break; + case 6: + chance = static_cast(std::min(skill, 90) * 0.80f) + 3; + break; + case 7: + chance = static_cast(std::min(skill, 104) * 0.70f) + 2; + break; + default: + chance = it.hitChance; + break; + } + } else if (maxHitChance == 90) { + //formula for two-handed weapons + switch (distance) { + case 1: + case 5: + chance = static_cast(std::min(skill, 74) * 1.20f) + 1; + break; + case 2: + chance = static_cast(std::min(skill, 28) * 3.20f); + break; + case 3: + chance = std::min(skill, 45) * 2; + break; + case 4: + chance = static_cast(std::min(skill, 58) * 1.55f); + break; + case 6: + case 7: + chance = std::min(skill, 90); + break; + default: + chance = it.hitChance; + break; + } + } else if (maxHitChance == 100) { + switch (distance) { + case 1: + case 5: + chance = static_cast(std::min(skill, 73) * 1.35f) + 1; + break; + case 2: + chance = static_cast(std::min(skill, 30) * 3.20f) + 4; + break; + case 3: + chance = static_cast(std::min(skill, 48) * 2.05f) + 2; + break; + case 4: + chance = static_cast(std::min(skill, 65) * 1.50f) + 2; + break; + case 6: + chance = static_cast(std::min(skill, 87) * 1.20f) - 4; + break; + case 7: + chance = static_cast(std::min(skill, 90) * 1.10f) + 1; + break; + default: + chance = it.hitChance; + break; + } + } else { + chance = maxHitChance; + } + } else { + chance = it.hitChance; + } + + if (item->getWeaponType() == WEAPON_AMMO) { + Item* bow = player->getWeapon(true); + if (bow && bow->getHitChance() != 0) { + chance += bow->getHitChance(); + } + } + + if (chance >= uniform_random(1, 100)) { + Weapon::internalUseWeapon(player, item, target, damageModifier); + } else { + //miss target + Tile* destTile = target->getTile(); + + if (!Position::areInRange<1, 1, 0>(player->getPosition(), target->getPosition())) { + static std::vector> destList { + {-1, -1}, {0, -1}, {1, -1}, + {-1, 0}, {0, 0}, {1, 0}, + {-1, 1}, {0, 1}, {1, 1} + }; + std::shuffle(destList.begin(), destList.end(), getRandomGenerator()); + + Position destPos = target->getPosition(); + + for (const auto& dir : destList) { + // Blocking tiles or tiles without ground ain't valid targets for spears + Tile* tmpTile = g_game.map.getTile(destPos.x + dir.first, destPos.y + dir.second, destPos.z); + if (tmpTile && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID) && tmpTile->getGround() != nullptr) { + destTile = tmpTile; + break; + } + } + } + + Weapon::internalUseWeapon(player, item, destTile); + } + return true; +} + +int32_t WeaponDistance::getElementDamage(const Player* player, const Creature* target, const Item* item) const +{ + if (elementType == COMBAT_NONE) { + return 0; + } + + int32_t attackValue = elementDamage; + if (item->getWeaponType() == WEAPON_AMMO) { + Item* weapon = player->getWeapon(true); + if (weapon) { + attackValue += weapon->getAttack(); + } + } + + int32_t attackSkill = player->getSkillLevel(SKILL_DISTANCE); + float attackFactor = player->getAttackFactor(); + + int32_t minValue = 0; + int32_t maxValue = Weapons::getMaxWeaponDamage(player->getLevel(), attackSkill, attackValue, attackFactor); + if (target) { + if (target->getPlayer()) { + minValue = static_cast(std::ceil(player->getLevel() * 0.1)); + } else { + minValue = static_cast(std::ceil(player->getLevel() * 0.2)); + } + } + + return -normal_random(minValue, static_cast(maxValue * player->getVocation()->distDamageMultiplier)); +} + +int32_t WeaponDistance::getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage /*= false*/) const +{ + int32_t attackValue = item->getAttack(); + + if (item->getWeaponType() == WEAPON_AMMO) { + Item* weapon = player->getWeapon(true); + if (weapon) { + attackValue += weapon->getAttack(); + } + } + + int32_t attackSkill = player->getSkillLevel(SKILL_DISTANCE); + float attackFactor = player->getAttackFactor(); + + int32_t maxValue = static_cast(Weapons::getMaxWeaponDamage(player->getLevel(), attackSkill, attackValue, attackFactor) * player->getVocation()->distDamageMultiplier); + if (maxDamage) { + return -maxValue; + } + + int32_t minValue; + if (target) { + if (target->getPlayer()) { + minValue = static_cast(std::ceil(player->getLevel() * 0.1)); + } else { + minValue = static_cast(std::ceil(player->getLevel() * 0.2)); + } + } else { + minValue = 0; + } + return -normal_random(minValue, maxValue); +} + +bool WeaponDistance::getSkillType(const Player* player, const Item*, skills_t& skill, uint32_t& skillpoint) const +{ + skill = SKILL_DISTANCE; + + if (player->getAddAttackSkill()) { + switch (player->getLastAttackBlockType()) { + case BLOCK_NONE: { + skillpoint = 2; + break; + } + + case BLOCK_DEFENSE: + case BLOCK_ARMOR: { + skillpoint = 1; + break; + } + + default: + skillpoint = 0; + break; + } + } else { + skillpoint = 0; + } + return true; +} + +bool WeaponWand::configureEvent(const pugi::xml_node& node) +{ + if (!Weapon::configureEvent(node)) { + return false; + } + + pugi::xml_attribute attr; + if ((attr = node.attribute("min"))) { + minChange = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("max"))) { + maxChange = pugi::cast(attr.value()); + } + + attr = node.attribute("type"); + if (!attr) { + return true; + } + + std::string tmpStrValue = asLowerCaseString(attr.as_string()); + if (tmpStrValue == "earth") { + params.combatType = COMBAT_EARTHDAMAGE; + } else if (tmpStrValue == "ice") { + params.combatType = COMBAT_ICEDAMAGE; + } else if (tmpStrValue == "energy") { + params.combatType = COMBAT_ENERGYDAMAGE; + } else if (tmpStrValue == "fire") { + params.combatType = COMBAT_FIREDAMAGE; + } else if (tmpStrValue == "death") { + params.combatType = COMBAT_DEATHDAMAGE; + } else if (tmpStrValue == "holy") { + params.combatType = COMBAT_HOLYDAMAGE; + } else { + std::cout << "[Warning - WeaponWand::configureEvent] Type \"" << attr.as_string() << "\" does not exist." << std::endl; + } + return true; +} + +void WeaponWand::configureWeapon(const ItemType& it) +{ + params.distanceEffect = it.shootType; + + Weapon::configureWeapon(it); +} + +int32_t WeaponWand::getWeaponDamage(const Player*, const Creature*, const Item*, bool maxDamage /*= false*/) const +{ + if (maxDamage) { + return -maxChange; + } + return -normal_random(minChange, maxChange); +} diff --git a/src/weapons.h b/src/weapons.h new file mode 100644 index 0000000..e889784 --- /dev/null +++ b/src/weapons.h @@ -0,0 +1,311 @@ +/** + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef FS_WEAPONS_H_69D1993478AA42948E24C0B90B8F5BF5 +#define FS_WEAPONS_H_69D1993478AA42948E24C0B90B8F5BF5 + +#include "luascript.h" +#include "player.h" +#include "baseevents.h" +#include "combat.h" +#include "const.h" +#include "vocation.h" + +extern Vocations g_vocations; + +class Weapon; +class WeaponMelee; +class WeaponDistance; +class WeaponWand; + +using Weapon_ptr = std::unique_ptr; + +class Weapons final : public BaseEvents +{ + public: + Weapons(); + ~Weapons(); + + // non-copyable + Weapons(const Weapons&) = delete; + Weapons& operator=(const Weapons&) = delete; + + void loadDefaults(); + const Weapon* getWeapon(const Item* item) const; + + static int32_t getMaxMeleeDamage(int32_t attackSkill, int32_t attackValue); + static int32_t getMaxWeaponDamage(uint32_t level, int32_t attackSkill, int32_t attackValue, float attackFactor); + + bool registerLuaEvent(Weapon* event); + void clear(bool fromLua) override final; + + private: + LuaScriptInterface& getScriptInterface() override; + std::string getScriptBaseName() const override; + Event_ptr getEvent(const std::string& nodeName) override; + bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; + + std::map weapons; + + LuaScriptInterface scriptInterface { "Weapon Interface" }; +}; + +class Weapon : public Event +{ + public: + explicit Weapon(LuaScriptInterface* interface) : Event(interface) {} + + bool configureEvent(const pugi::xml_node& node) override; + bool loadFunction(const pugi::xml_attribute&, bool) final { + return true; + } + virtual void configureWeapon(const ItemType& it); + virtual bool interruptSwing() const { + return false; + } + + int32_t playerWeaponCheck(Player* player, Creature* target, uint8_t shootRange) const; + static bool useFist(Player* player, Creature* target); + virtual bool useWeapon(Player* player, Item* item, Creature* target) const; + + virtual int32_t getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage = false) const = 0; + virtual int32_t getElementDamage(const Player* player, const Creature* target, const Item* item) const = 0; + virtual CombatType_t getElementType() const = 0; + + uint16_t getID() const { + return id; + } + void setID(uint16_t newId) { + id = newId; + } + + uint32_t getReqLevel() const { + return level; + } + void setRequiredLevel(uint32_t reqlvl) { + level = reqlvl; + } + + uint32_t getReqMagLv() const { + return magLevel; + } + void setRequiredMagLevel(uint32_t reqlvl) { + magLevel = reqlvl; + } + + bool isPremium() const { + return premium; + } + void setNeedPremium(bool prem) { + premium = prem; + } + + bool isWieldedUnproperly() const { + return wieldUnproperly; + } + void setWieldUnproperly(bool unproperly) { + wieldUnproperly = unproperly; + } + + uint32_t getMana() const { + return mana; + } + void setMana(uint32_t m) { + mana = m; + } + + uint32_t getManaPercent() const { + return manaPercent; + } + void setManaPercent(uint32_t m) { + manaPercent = m; + } + + int32_t getHealth() const { + return health; + } + void setHealth(int32_t h) { + health = h; + } + + uint32_t getHealthPercent() const { + return healthPercent; + } + void setHealthPercent(uint32_t m) { + healthPercent = m; + } + + uint32_t getSoul() const { + return soul; + } + void setSoul(uint32_t s) { + soul = s; + } + + uint8_t getBreakChance() const { + return breakChance; + } + void setBreakChance(uint8_t b) { + breakChance = b; + } + + bool isEnabled() const { + return enabled; + } + void setIsEnabled(bool e) { + enabled = e; + } + + uint32_t getWieldInfo() const { + return wieldInfo; + } + void setWieldInfo(uint32_t info) { + wieldInfo |= info; + } + + void addVocWeaponMap(std::string vocName) { + int32_t vocationId = g_vocations.getVocationId(vocName); + if (vocationId != -1) { + vocWeaponMap[vocationId] = true; + } + } + + const std::string& getVocationString() const { + return vocationString; + } + void setVocationString(const std::string& str) { + vocationString = str; + } + + WeaponAction_t action = WEAPONACTION_NONE; + CombatParams params; + WeaponType_t weaponType; + std::map vocWeaponMap; + + protected: + void internalUseWeapon(Player* player, Item* item, Creature* target, int32_t damageModifier) const; + void internalUseWeapon(Player* player, Item* item, Tile* tile) const; + + uint16_t id = 0; + + private: + virtual bool getSkillType(const Player*, const Item*, skills_t&, uint32_t&) const { + return false; + } + + uint32_t getManaCost(const Player* player) const; + int32_t getHealthCost(const Player* player) const; + + uint32_t level = 0; + uint32_t magLevel = 0; + uint32_t mana = 0; + uint32_t manaPercent = 0; + uint32_t health = 0; + uint32_t healthPercent = 0; + uint32_t soul = 0; + uint32_t wieldInfo = WIELDINFO_NONE; + uint8_t breakChance = 0; + bool enabled = true; + bool premium = false; + bool wieldUnproperly = false; + std::string vocationString = ""; + + std::string getScriptEventName() const override final; + + bool executeUseWeapon(Player* player, const LuaVariant& var) const; + void onUsedWeapon(Player* player, Item* item, Tile* destTile) const; + + static void decrementItemCount(Item* item); + + friend class Combat; +}; + +class WeaponMelee final : public Weapon +{ + public: + explicit WeaponMelee(LuaScriptInterface* interface); + + void configureWeapon(const ItemType& it) override; + + bool useWeapon(Player* player, Item* item, Creature* target) const override; + + int32_t getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage = false) const override; + int32_t getElementDamage(const Player* player, const Creature* target, const Item* item) const override; + CombatType_t getElementType() const override { return elementType; } + + private: + bool getSkillType(const Player* player, const Item* item, skills_t& skill, uint32_t& skillpoint) const override; + + CombatType_t elementType = COMBAT_NONE; + uint16_t elementDamage = 0; +}; + +class WeaponDistance final : public Weapon +{ + public: + explicit WeaponDistance(LuaScriptInterface* interface); + + void configureWeapon(const ItemType& it) override; + bool interruptSwing() const override { + return true; + } + + bool useWeapon(Player* player, Item* item, Creature* target) const override; + + int32_t getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage = false) const override; + int32_t getElementDamage(const Player* player, const Creature* target, const Item* item) const override; + CombatType_t getElementType() const override { return elementType; } + + private: + bool getSkillType(const Player* player, const Item* item, skills_t& skill, uint32_t& skillpoint) const override; + + CombatType_t elementType = COMBAT_NONE; + uint16_t elementDamage = 0; +}; + +class WeaponWand final : public Weapon +{ + public: + explicit WeaponWand(LuaScriptInterface* interface) : Weapon(interface) {} + + bool configureEvent(const pugi::xml_node& node) override; + void configureWeapon(const ItemType& it) override; + + int32_t getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage = false) const override; + int32_t getElementDamage(const Player*, const Creature*, const Item*) const override { return 0; } + CombatType_t getElementType() const override { return COMBAT_NONE; } + + void setMinChange(int32_t change) { + minChange = change; + } + + void setMaxChange(int32_t change) { + maxChange = change; + } + + private: + bool getSkillType(const Player*, const Item*, skills_t&, uint32_t&) const override { + return false; + } + + int32_t minChange = 0; + int32_t maxChange = 0; +}; + +#endif diff --git a/src/wildcardtree.cpp b/src/wildcardtree.cpp index fa5eef8..ec182b6 100644 --- a/src/wildcardtree.cpp +++ b/src/wildcardtree.cpp @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -119,7 +119,7 @@ ReturnValue WildcardTreeNode::findOne(const std::string& query, std::string& res if (size == 0) { return RETURNVALUE_NOERROR; } else if (size > 1 || cur->breakpoint) { - return RETURNVALUE_NAMEISTOOAMBIGIOUS; + return RETURNVALUE_NAMEISTOOAMBIGUOUS; } auto it = cur->children.begin(); diff --git a/src/wildcardtree.h b/src/wildcardtree.h index 2d16981..38aa7ed 100644 --- a/src/wildcardtree.h +++ b/src/wildcardtree.h @@ -1,6 +1,6 @@ /** - * Tibia GIMUD Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Sabrehaven and Mark Samman + * The Forgotten Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From 447b858551b9d0b4e73f4c29f179d52e826cd137 Mon Sep 17 00:00:00 2001 From: ErikasKontenis Date: Thu, 2 Jan 2020 19:40:35 +0200 Subject: [PATCH 2/3] Revert "commit newest tfs branch only for compare" This reverts commit 1f7dcd73477eae559e0f5915aece1388a54d0b76. --- src/CMakeLists.txt | 14 +- src/account.h | 6 +- src/actions.cpp | 273 +- src/actions.h | 24 +- src/ban.cpp | 30 +- src/ban.h | 12 +- src/baseevents.cpp | 47 +- src/baseevents.h | 27 +- src/bed.cpp | 14 +- src/bed.h | 19 +- src/chat.cpp | 92 +- src/chat.h | 25 +- src/combat.cpp | 903 ++++-- src/combat.h | 102 +- src/condition.cpp | 836 ++--- src/condition.h | 278 +- src/configmanager.cpp | 147 +- src/configmanager.h | 45 +- src/connection.cpp | 41 +- src/connection.h | 34 +- src/const.h | 414 +-- src/container.cpp | 126 +- src/container.h | 85 +- src/creature.cpp | 283 +- src/creature.h | 93 +- src/creatureevent.cpp | 173 +- src/creatureevent.h | 121 +- src/cylinder.cpp | 4 +- src/cylinder.h | 5 +- src/database.cpp | 8 +- src/database.h | 25 +- src/databasemanager.cpp | 99 +- src/databasemanager.h | 7 +- src/databasetasks.cpp | 16 +- src/databasetasks.h | 8 +- src/definitions.h | 17 +- src/depotchest.cpp | 82 - src/depotchest.h | 57 - src/depotlocker.cpp | 53 +- src/depotlocker.h | 21 +- src/enums.h | 356 +-- src/events.cpp | 432 ++- src/events.h | 124 +- src/fileloader.cpp | 425 ++- src/fileloader.h | 168 +- src/game.cpp | 2305 ++++---------- src/game.h | 118 +- src/globalevent.cpp | 106 +- src/globalevent.h | 40 +- src/groups.cpp | 61 +- src/groups.h | 4 +- src/guild.cpp | 21 +- src/guild.h | 20 +- src/house.cpp | 92 +- src/house.h | 36 +- src/housetile.cpp | 4 +- src/housetile.h | 12 +- src/inbox.cpp | 72 - src/inbox.h | 49 - src/ioguild.cpp | 42 +- src/ioguild.h | 10 +- src/iologindata.cpp | 379 +-- src/iologindata.h | 19 +- src/iomap.cpp | 548 ++-- src/iomap.h | 23 +- src/iomapserialize.cpp | 40 +- src/iomapserialize.h | 6 +- src/iomarket.cpp | 347 --- src/iomarket.h | 63 - src/item.cpp | 953 ++---- src/item.h | 527 +--- src/itemloader.h | 198 -- src/items.cpp | 1742 +++-------- src/items.h | 218 +- src/lockfree.h | 47 +- src/luascript.cpp | 6289 ++++++-------------------------------- src/luascript.h | 452 +-- src/mailbox.cpp | 67 +- src/mailbox.h | 32 +- src/map.cpp | 83 +- src/map.h | 25 +- src/monster.cpp | 683 +++-- src/monster.h | 146 +- src/monsters.cpp | 830 ++--- src/monsters.h | 112 +- src/mounts.cpp | 82 - src/mounts.h | 52 - src/movement.cpp | 381 +-- src/movement.h | 160 +- src/networkmessage.cpp | 33 +- src/networkmessage.h | 37 +- src/npc.cpp | 1150 +------ src/npc.h | 171 +- src/otpch.cpp | 4 +- src/otpch.h | 4 +- src/otserv.cpp | 99 +- src/outputmessage.cpp | 16 +- src/outputmessage.h | 116 +- src/party.cpp | 62 +- src/party.h | 15 +- src/player.cpp | 1694 +++------- src/player.h | 582 ++-- src/position.cpp | 4 +- src/position.h | 26 +- src/protocol.cpp | 72 +- src/protocol.h | 124 +- src/protocolgame.cpp | 1385 ++------- src/protocolgame.h | 113 +- src/protocollogin.cpp | 94 +- src/protocollogin.h | 7 +- src/protocolold.cpp | 77 - src/protocolold.h | 46 - src/protocolstatus.cpp | 25 +- src/protocolstatus.h | 9 +- src/pugicast.h | 4 +- src/quests.cpp | 228 -- src/quests.h | 119 - src/raids.cpp | 16 +- src/raids.h | 31 +- src/rsa.cpp | 13 +- src/rsa.h | 24 +- src/scheduler.cpp | 62 +- src/scheduler.h | 18 +- src/script.cpp | 491 ++- src/script.h | 297 +- src/scriptmanager.cpp | 26 +- src/scriptmanager.h | 8 +- src/server.cpp | 38 +- src/server.h | 135 +- src/signals.cpp | 212 -- src/signals.h | 42 - src/spawn.cpp | 52 +- src/spawn.h | 12 +- src/spectators.h | 83 - src/spells.cpp | 1058 +++++-- src/spells.h | 304 +- src/talkaction.cpp | 49 +- src/talkaction.h | 71 +- src/tasks.cpp | 16 +- src/tasks.h | 25 +- src/teleport.cpp | 4 +- src/teleport.h | 34 +- src/thing.cpp | 4 +- src/thing.h | 9 +- src/thread_holder_base.h | 4 +- src/tile.cpp | 388 +-- src/tile.h | 175 +- src/tools.cpp | 480 ++- src/tools.h | 38 +- src/town.h | 6 +- src/trashholder.cpp | 102 - src/trashholder.h | 57 - src/vocation.cpp | 37 +- src/vocation.h | 20 +- src/waitlist.cpp | 126 +- src/waitlist.h | 33 +- src/weapons.cpp | 944 ------ src/weapons.h | 311 -- src/wildcardtree.cpp | 6 +- src/wildcardtree.h | 4 +- 160 files changed, 10972 insertions(+), 24876 deletions(-) delete mode 100644 src/depotchest.cpp delete mode 100644 src/depotchest.h delete mode 100644 src/inbox.cpp delete mode 100644 src/inbox.h delete mode 100644 src/iomarket.cpp delete mode 100644 src/iomarket.h delete mode 100644 src/itemloader.h delete mode 100644 src/mounts.cpp delete mode 100644 src/mounts.h delete mode 100644 src/protocolold.cpp delete mode 100644 src/protocolold.h delete mode 100644 src/quests.cpp delete mode 100644 src/quests.h delete mode 100644 src/signals.cpp delete mode 100644 src/signals.h delete mode 100644 src/spectators.h delete mode 100644 src/trashholder.cpp delete mode 100644 src/trashholder.h delete mode 100644 src/weapons.cpp delete mode 100644 src/weapons.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b375b16..5beaa34 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,6 +4,7 @@ set(tfs_SRC ${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}/condition.cpp @@ -16,7 +17,6 @@ set(tfs_SRC ${CMAKE_CURRENT_LIST_DIR}/database.cpp ${CMAKE_CURRENT_LIST_DIR}/databasemanager.cpp ${CMAKE_CURRENT_LIST_DIR}/databasetasks.cpp - ${CMAKE_CURRENT_LIST_DIR}/depotchest.cpp ${CMAKE_CURRENT_LIST_DIR}/depotlocker.cpp ${CMAKE_CURRENT_LIST_DIR}/events.cpp ${CMAKE_CURRENT_LIST_DIR}/fileloader.cpp @@ -26,12 +26,10 @@ set(tfs_SRC ${CMAKE_CURRENT_LIST_DIR}/groups.cpp ${CMAKE_CURRENT_LIST_DIR}/house.cpp ${CMAKE_CURRENT_LIST_DIR}/housetile.cpp - ${CMAKE_CURRENT_LIST_DIR}/inbox.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}/iomarket.cpp ${CMAKE_CURRENT_LIST_DIR}/item.cpp ${CMAKE_CURRENT_LIST_DIR}/items.cpp ${CMAKE_CURRENT_LIST_DIR}/luascript.cpp @@ -39,7 +37,6 @@ set(tfs_SRC ${CMAKE_CURRENT_LIST_DIR}/map.cpp ${CMAKE_CURRENT_LIST_DIR}/monster.cpp ${CMAKE_CURRENT_LIST_DIR}/monsters.cpp - ${CMAKE_CURRENT_LIST_DIR}/mounts.cpp ${CMAKE_CURRENT_LIST_DIR}/movement.cpp ${CMAKE_CURRENT_LIST_DIR}/networkmessage.cpp ${CMAKE_CURRENT_LIST_DIR}/npc.cpp @@ -52,29 +49,24 @@ set(tfs_SRC ${CMAKE_CURRENT_LIST_DIR}/protocol.cpp ${CMAKE_CURRENT_LIST_DIR}/protocolgame.cpp ${CMAKE_CURRENT_LIST_DIR}/protocollogin.cpp - ${CMAKE_CURRENT_LIST_DIR}/protocolold.cpp ${CMAKE_CURRENT_LIST_DIR}/protocolstatus.cpp - ${CMAKE_CURRENT_LIST_DIR}/quests.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}/script.cpp ${CMAKE_CURRENT_LIST_DIR}/server.cpp - ${CMAKE_CURRENT_LIST_DIR}/signals.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}/trashholder.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 ${CMAKE_CURRENT_LIST_DIR}/xtea.cpp - PARENT_SCOPE) +) diff --git a/src/account.h b/src/account.h index 7e14962..5bdc53a 100644 --- a/src/account.h +++ b/src/account.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,8 +24,6 @@ struct Account { std::vector characters; - std::string name; - std::string key; time_t lastDay = 0; uint32_t id = 0; uint16_t premiumDays = 0; diff --git a/src/actions.cpp b/src/actions.cpp index 9a02048..c23df0d 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,27 +40,29 @@ Actions::Actions() : Actions::~Actions() { - clear(false); + clear(); } -void Actions::clearMap(ActionUseMap& map, bool fromLua) +inline void Actions::clearMap(ActionUseMap& map) { - for (auto it = map.begin(); it != map.end(); ) { - if (fromLua == it->second.fromLua) { - it = map.erase(it); - } else { - ++it; - } + // Filter out duplicates to avoid double-free + std::unordered_set set; + for (const auto& it : map) { + set.insert(it.second); + } + map.clear(); + + for (Action* action : set) { + delete action; } } -void Actions::clear(bool fromLua) +void Actions::clear() { - clearMap(useItemMap, fromLua); - clearMap(uniqueItemMap, fromLua); - clearMap(actionItemMap, fromLua); + clearMap(useItemMap); + clearMap(actionItemMap); - reInitState(fromLua); + scriptInterface.reInitState(); } LuaScriptInterface& Actions::getScriptInterface() @@ -73,23 +75,23 @@ std::string Actions::getScriptBaseName() const return "actions"; } -Event_ptr Actions::getEvent(const std::string& nodeName) +Event* Actions::getEvent(const std::string& nodeName) { if (strcasecmp(nodeName.c_str(), "action") != 0) { return nullptr; } - return Event_ptr(new Action(&scriptInterface)); + return new Action(&scriptInterface); } -bool Actions::registerEvent(Event_ptr event, const pugi::xml_node& node) +bool Actions::registerEvent(Event* event, const pugi::xml_node& node) { - Action_ptr action{static_cast(event.release())}; //event is guaranteed to be an Action + Action* action = static_cast(event); //event is guaranteed to be an Action pugi::xml_attribute attr; if ((attr = node.attribute("itemid"))) { uint16_t id = pugi::cast(attr.value()); - auto result = useItemMap.emplace(id, std::move(*action)); + auto result = useItemMap.emplace(id, action); if (!result.second) { std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with id: " << id << std::endl; } @@ -105,14 +107,14 @@ bool Actions::registerEvent(Event_ptr event, const pugi::xml_node& node) uint16_t iterId = fromId; uint16_t toId = pugi::cast(toIdAttribute.value()); - auto result = useItemMap.emplace(iterId, *action); + 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); + 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; @@ -120,44 +122,10 @@ bool Actions::registerEvent(Event_ptr event, const pugi::xml_node& node) success = true; } return success; - } else if ((attr = node.attribute("uniqueid"))) { - uint16_t uid = pugi::cast(attr.value()); - - auto result = uniqueItemMap.emplace(uid, std::move(*action)); - if (!result.second) { - std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with uniqueid: " << uid << std::endl; - } - return result.second; - } else if ((attr = node.attribute("fromuid"))) { - pugi::xml_attribute toUidAttribute = node.attribute("touid"); - if (!toUidAttribute) { - std::cout << "[Warning - Actions::registerEvent] Missing touid in fromuid: " << attr.as_string() << std::endl; - return false; - } - - uint16_t fromUid = pugi::cast(attr.value()); - uint16_t iterUid = fromUid; - uint16_t toUid = pugi::cast(toUidAttribute.value()); - - auto result = uniqueItemMap.emplace(iterUid, *action); - if (!result.second) { - std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with unique id: " << iterUid << " in fromuid: " << fromUid << ", touid: " << toUid << std::endl; - } - - bool success = result.second; - while (++iterUid <= toUid) { - result = uniqueItemMap.emplace(iterUid, *action); - if (!result.second) { - std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with unique id: " << iterUid << " in fromuid: " << fromUid << ", touid: " << toUid << std::endl; - continue; - } - success = true; - } - return success; } else if ((attr = node.attribute("actionid"))) { uint16_t aid = pugi::cast(attr.value()); - auto result = actionItemMap.emplace(aid, std::move(*action)); + auto result = actionItemMap.emplace(aid, action); if (!result.second) { std::cout << "[Warning - Actions::registerEvent] Duplicate registered item with actionid: " << aid << std::endl; } @@ -173,14 +141,14 @@ bool Actions::registerEvent(Event_ptr event, const pugi::xml_node& node) uint16_t iterAid = fromAid; uint16_t toAid = pugi::cast(toAidAttribute.value()); - auto result = actionItemMap.emplace(iterAid, *action); + 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); + 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; @@ -192,69 +160,6 @@ bool Actions::registerEvent(Event_ptr event, const pugi::xml_node& node) return false; } -bool Actions::registerLuaEvent(Action* event) -{ - Action_ptr action{ event }; - if (action->getItemIdRange().size() > 0) { - if (action->getItemIdRange().size() == 1) { - auto result = useItemMap.emplace(action->getItemIdRange().at(0), std::move(*action)); - if (!result.second) { - std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with id: " << action->getItemIdRange().at(0) << std::endl; - } - return result.second; - } else { - auto v = action->getItemIdRange(); - for (auto i = v.begin(); i != v.end(); i++) { - auto result = useItemMap.emplace(*i, std::move(*action)); - if (!result.second) { - std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with id: " << *i << " in range from id: " << v.at(0) << ", to id: " << v.at(v.size() - 1) << std::endl; - continue; - } - } - return true; - } - } else if (action->getUniqueIdRange().size() > 0) { - if (action->getUniqueIdRange().size() == 1) { - auto result = uniqueItemMap.emplace(action->getUniqueIdRange().at(0), std::move(*action)); - if (!result.second) { - std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with uid: " << action->getUniqueIdRange().at(0) << std::endl; - } - return result.second; - } else { - auto v = action->getUniqueIdRange(); - for (auto i = v.begin(); i != v.end(); i++) { - auto result = uniqueItemMap.emplace(*i, std::move(*action)); - if (!result.second) { - std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with uid: " << *i << " in range from uid: " << v.at(0) << ", to uid: " << v.at(v.size() - 1) << std::endl; - continue; - } - } - return true; - } - } else if (action->getActionIdRange().size() > 0) { - if (action->getActionIdRange().size() == 1) { - auto result = actionItemMap.emplace(action->getActionIdRange().at(0), std::move(*action)); - if (!result.second) { - std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with aid: " << action->getActionIdRange().at(0) << std::endl; - } - return result.second; - } else { - auto v = action->getActionIdRange(); - for (auto i = v.begin(); i != v.end(); i++) { - auto result = actionItemMap.emplace(*i, std::move(*action)); - if (!result.second) { - std::cout << "[Warning - Actions::registerLuaEvent] Duplicate registered item with aid: " << *i << " in range from aid: " << v.at(0) << ", to aid: " << v.at(v.size() - 1) << std::endl; - continue; - } - } - return true; - } - } else { - std::cout << "[Warning - Actions::registerLuaEvent] There is no id / aid / uid set for this event" << std::endl; - return false; - } -} - ReturnValue Actions::canUse(const Player* player, const Position& pos) { if (pos.x != 0xFFFF) { @@ -303,23 +208,16 @@ ReturnValue Actions::canUseFar(const Creature* creature, const Position& toPos, Action* Actions::getAction(const Item* item) { - if (item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { - auto it = uniqueItemMap.find(item->getUniqueId()); - if (it != uniqueItemMap.end()) { - return &it->second; - } - } - if (item->hasAttribute(ITEM_ATTRIBUTE_ACTIONID)) { auto it = actionItemMap.find(item->getActionId()); if (it != actionItemMap.end()) { - return &it->second; + return it->second; } } auto it = useItemMap.find(item->getID()); if (it != useItemMap.end()) { - return &it->second; + return it->second; } //rune items @@ -358,41 +256,44 @@ ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_ if (bed->trySleep(player)) { player->setBedItem(bed); - g_game.sendOfflineTrainingDialog(player); + if (!bed->sleep(player)) { + return RETURNVALUE_CANNOTUSETHISOBJECT; + } } return RETURNVALUE_NOERROR; } if (Container* container = item->getContainer()) { - Container* openContainer; + if (!item->isChestQuest()) { + Container* openContainer; - //depot container - if (DepotLocker* depot = container->getDepotLocker()) { - DepotLocker* myDepotLocker = player->getDepotLocker(depot->getDepotId()); - myDepotLocker->setParent(depot->getParent()->getTile()); - openContainer = myDepotLocker; - player->setLastDepotId(depot->getDepotId()); - } else { - openContainer = container; + //depot container + if (DepotLocker* depot = container->getDepotLocker()) { + DepotLocker* myDepotLocker = player->getDepotLocker(depot->getDepotId(), true); + myDepotLocker->setParent(depot->getParent()->getTile()); + openContainer = myDepotLocker; + } else { + openContainer = container; + } + + uint32_t corpseOwner = container->getCorpseOwner(); + if (corpseOwner != 0 && !player->canOpenCorpse(corpseOwner)) { + return RETURNVALUE_YOUARENOTTHEOWNER; + } + + //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; } - - uint32_t corpseOwner = container->getCorpseOwner(); - if (corpseOwner != 0 && !player->canOpenCorpse(corpseOwner)) { - return RETURNVALUE_YOUARENOTTHEOWNER; - } - - //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()]; @@ -406,6 +307,12 @@ ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint8_ } 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; @@ -417,7 +324,7 @@ bool Actions::useItem(Player* player, const Position& pos, uint8_t index, Item* player->stopWalk(); if (isHotkey) { - showUseHotkeyMessage(player, item, player->getItemTypeCount(item->getID(), item->getSubType())); + showUseHotkeyMessage(player, item, player->getItemTypeCount(item->getID(), -1)); } ReturnValue ret = internalUseItem(player, pos, index, item, isHotkey); @@ -447,7 +354,7 @@ bool Actions::useItemEx(Player* player, const Position& fromPos, const Position& } if (isHotkey) { - showUseHotkeyMessage(player, item, player->getItemTypeCount(item->getID(), item->getSubType())); + showUseHotkeyMessage(player, item, player->getItemTypeCount(item->getID(), -1)); } if (!action->executeUse(player, item, fromPos, action->getTarget(player, creature, toPos, toStackPos), toPos, isHotkey)) { @@ -466,9 +373,11 @@ void Actions::showUseHotkeyMessage(Player* player, const Item* item, uint32_t co const ItemType& it = Item::items[item->getID()]; if (!it.showCount) { ss << "Using one of " << item->getName() << "..."; - } else if (count == 1) { + } + else if (count == 1) { ss << "Using the last " << item->getName() << "..."; - } else { + } + else { ss << "Using one of " << count << ' ' << item->getPluralName() << "..."; } player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); @@ -477,6 +386,9 @@ void Actions::showUseHotkeyMessage(Player* player, const Item* item, uint32_t co Action::Action(LuaScriptInterface* interface) : Event(interface), function(nullptr), 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"); @@ -497,38 +409,6 @@ bool Action::configureEvent(const pugi::xml_node& node) return true; } -namespace { - -bool enterMarket(Player* player, Item*, const Position&, Thing*, const Position&, bool) -{ - if (player->getLastDepotId() == -1) { - return false; - } - - player->sendMarketEnter(player->getLastDepotId()); - return true; -} - -} - -bool Action::loadFunction(const pugi::xml_attribute& attr, bool isScripted) -{ - const char* functionName = attr.as_string(); - if (strcasecmp(functionName, "market") == 0) { - function = enterMarket; - } else { - if (!isScripted) { - std::cout << "[Warning - Action::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; - return false; - } - } - - if (!isScripted) { - scripted = false; - } - return true; -} - std::string Action::getScriptEventName() const { return "onUse"; @@ -538,7 +418,8 @@ ReturnValue Action::canExecuteAction(const Player* player, const Position& toPos { if (!allowFarUse) { return g_actions->canUse(player, toPos); - } else { + } + else { return g_actions->canUseFar(player, toPos, checkLineOfSight, checkFloor); } } @@ -577,4 +458,4 @@ bool Action::executeUse(Player* player, Item* item, const Position& fromPosition LuaScriptInterface::pushBoolean(L, isHotkey); return scriptInterface->callFunction(6); -} +} \ No newline at end of file diff --git a/src/actions.h b/src/actions.h index 69ca1a9..6f03677 100644 --- a/src/actions.h +++ b/src/actions.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,14 +31,15 @@ using ActionFunction = std::function; + typedef std::map ActionUseMap; ActionUseMap useItemMap; - ActionUseMap uniqueItemMap; ActionUseMap actionItemMap; Action* getAction(const Item* item); - void clearMap(ActionUseMap& map, bool fromLua); + void clearMap(ActionUseMap& map); LuaScriptInterface scriptInterface; }; diff --git a/src/ban.cpp b/src/ban.cpp index 0b75ceb..547ad29 100644 --- a/src/ban.cpp +++ b/src/ban.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,15 +24,15 @@ #include "databasetasks.h" #include "tools.h" -bool Ban::acceptConnection(uint32_t clientIP) +bool Ban::acceptConnection(uint32_t clientip) { std::lock_guard lockClass(lock); uint64_t currentTime = OTSYS_TIME(); - auto it = ipConnectMap.find(clientIP); + auto it = ipConnectMap.find(clientip); if (it == ipConnectMap.end()) { - ipConnectMap.emplace(clientIP, ConnectBlock(currentTime, 0, 1)); + ipConnectMap.emplace(clientip, ConnectBlock(currentTime, 0, 1)); return true; } @@ -60,12 +60,12 @@ bool Ban::acceptConnection(uint32_t clientIP) bool IOBan::isAccountBanned(uint32_t accountId, BanInfo& banInfo) { - Database& db = Database::getInstance(); + 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()); + DBResult_ptr result = db->storeQuery(query.str()); if (!result) { return false; } @@ -74,7 +74,7 @@ bool IOBan::isAccountBanned(uint32_t accountId, BanInfo& banInfo) 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("banned_at") << ',' << expiresAt << ',' << result->getNumber("banned_by") << ')'; + query << "INSERT INTO `account_ban_history` (`account_id`, `reason`, `banned_at`, `expired_at`, `banned_by`) VALUES (" << accountId << ',' << db->escapeString(result->getString("reason")) << ',' << result->getNumber("banned_at") << ',' << expiresAt << ',' << result->getNumber("banned_by") << ')'; g_databaseTasks.addTask(query.str()); query.str(std::string()); @@ -89,18 +89,18 @@ bool IOBan::isAccountBanned(uint32_t accountId, BanInfo& banInfo) return true; } -bool IOBan::isIpBanned(uint32_t clientIP, BanInfo& banInfo) +bool IOBan::isIpBanned(uint32_t clientip, BanInfo& banInfo) { - if (clientIP == 0) { + if (clientip == 0) { return false; } - Database& db = Database::getInstance(); + 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; + 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()); + DBResult_ptr result = db->storeQuery(query.str()); if (!result) { return false; } @@ -108,7 +108,7 @@ bool IOBan::isIpBanned(uint32_t clientIP, BanInfo& banInfo) int64_t expiresAt = result->getNumber("expires_at"); if (expiresAt != 0 && time(nullptr) > expiresAt) { query.str(std::string()); - query << "DELETE FROM `ip_bans` WHERE `ip` = " << clientIP; + query << "DELETE FROM `ip_bans` WHERE `ip` = " << clientip; g_databaseTasks.addTask(query.str()); return false; } @@ -123,5 +123,5 @@ 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; + return Database::getInstance()->storeQuery(query.str()).get() != nullptr; } diff --git a/src/ban.h b/src/ban.h index 850d7cc..86535a6 100644 --- a/src/ban.h +++ b/src/ban.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,14 +35,14 @@ struct ConnectBlock { uint32_t count; }; -using IpConnectMap = std::map; +typedef std::map IpConnectMap; class Ban { public: - bool acceptConnection(uint32_t clientIP); + bool acceptConnection(uint32_t clientip); - private: + protected: IpConnectMap ipConnectMap; std::recursive_mutex lock; }; @@ -51,7 +51,7 @@ class IOBan { public: static bool isAccountBanned(uint32_t accountId, BanInfo& banInfo); - static bool isIpBanned(uint32_t clientIP, BanInfo& banInfo); + static bool isIpBanned(uint32_t ip, BanInfo& banInfo); static bool isPlayerNamelocked(uint32_t playerId); }; diff --git a/src/baseevents.cpp b/src/baseevents.cpp index 4dc648c..c99243e 100644 --- a/src/baseevents.cpp +++ b/src/baseevents.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -51,13 +51,14 @@ bool BaseEvents::loadFromXml() loaded = true; for (auto node : doc.child(scriptsName.c_str()).children()) { - Event_ptr event = getEvent(node.name()); + 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; } @@ -67,15 +68,12 @@ bool BaseEvents::loadFromXml() if (scriptAttribute) { std::string scriptFile = "scripts/" + std::string(scriptAttribute.as_string()); success = event->checkScript(basePath, scriptsName, scriptFile) && event->loadScript(basePath + scriptFile); - if (node.attribute("function")) { - event->loadFunction(node.attribute("function"), true); - } } else { - success = event->loadFunction(node.attribute("function"), false); + success = event->loadFunction(node.attribute("function")); } - if (success) { - registerEvent(std::move(event), node); + if (!success || !registerEvent(event, node)) { + delete event; } } return true; @@ -84,19 +82,15 @@ bool BaseEvents::loadFromXml() bool BaseEvents::reload() { loaded = false; - clear(false); + clear(); return loadFromXml(); } -void BaseEvents::reInitState(bool fromLua) -{ - if (!fromLua) { - getScriptInterface().reInitState(); - } -} - 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(); @@ -149,24 +143,6 @@ bool Event::loadScript(const std::string& scriptFile) return true; } -bool Event::loadCallback() -{ - if (!scriptInterface || scriptId != 0) { - std::cout << "Failure: [Event::loadCallback] scriptInterface == nullptr. scriptid = " << scriptId << std::endl; - return false; - } - - int32_t id = scriptInterface->getEvent(); - if (id == -1) { - std::cout << "[Warning - Event::loadCallback] Event " << getScriptEventName() << " not found. " << std::endl; - return false; - } - - scripted = true; - scriptId = id; - return true; -} - bool CallBack::loadCallBack(LuaScriptInterface* interface, const std::string& name) { if (!interface) { @@ -182,6 +158,7 @@ bool CallBack::loadCallBack(LuaScriptInterface* interface, const std::string& na return false; } + callbackName = name; scriptId = id; loaded = true; return true; diff --git a/src/baseevents.h b/src/baseevents.h index bf74c97..c5b528f 100644 --- a/src/baseevents.h +++ b/src/baseevents.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,21 +22,18 @@ #include "luascript.h" -class Event; -using Event_ptr = std::unique_ptr; - 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); - bool loadCallback(); - virtual bool loadFunction(const pugi::xml_attribute&, bool) { + virtual bool loadFunction(const pugi::xml_attribute&) { return false; } @@ -44,12 +41,10 @@ class Event return scripted; } - bool scripted = false; - bool fromLua = false; - protected: virtual std::string getScriptEventName() const = 0; + bool scripted = false; int32_t scriptId = 0; LuaScriptInterface* scriptInterface = nullptr; }; @@ -65,14 +60,13 @@ class BaseEvents bool isLoaded() const { return loaded; } - void reInitState(bool fromLua); - private: + protected: virtual LuaScriptInterface& getScriptInterface() = 0; virtual std::string getScriptBaseName() const = 0; - virtual Event_ptr getEvent(const std::string& nodeName) = 0; - virtual bool registerEvent(Event_ptr event, const pugi::xml_node& node) = 0; - virtual void clear(bool) = 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; }; @@ -88,8 +82,9 @@ class CallBack int32_t scriptId = 0; LuaScriptInterface* scriptInterface = nullptr; - private: bool loaded = false; + + std::string callbackName; }; #endif diff --git a/src/bed.cpp b/src/bed.cpp index a66a573..90629a9 100644 --- a/src/bed.cpp +++ b/src/bed.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -159,8 +159,8 @@ bool BedItem::sleep(Player* player) // make the player walk onto the bed g_game.map.moveCreature(*player, *getTile()); - // display 'Zzzz'/sleep effect - g_game.addMagicEffect(player->getPosition(), CONST_ME_SLEEP); + // 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(); @@ -246,10 +246,10 @@ void BedItem::updateAppearance(const Player* player) { const ItemType& it = Item::items[id]; if (it.type == ITEM_TYPE_BED) { - if (player && it.transformToOnUse[player->getSex()] != 0) { - const ItemType& newType = Item::items[it.transformToOnUse[player->getSex()]]; + if (player && it.transformToOnUse != 0) { + const ItemType& newType = Item::items[it.transformToOnUse]; if (newType.type == ITEM_TYPE_BED) { - g_game.transformItem(this, it.transformToOnUse[player->getSex()]); + g_game.transformItem(this, it.transformToOnUse); } } else if (it.transformToFree != 0) { const ItemType& newType = Item::items[it.transformToFree]; diff --git a/src/bed.h b/src/bed.h index ccc37cc..f1f836d 100644 --- a/src/bed.h +++ b/src/bed.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,17 +30,17 @@ class BedItem final : public Item public: explicit BedItem(uint16_t id); - BedItem* getBed() override { + BedItem* getBed() final { return this; } - const BedItem* getBed() const override { + const BedItem* getBed() const final { return this; } - Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; - void serializeAttr(PropWriteStream& propWriteStream) const override; + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) final; + void serializeAttr(PropWriteStream& propWriteStream) const final; - bool canRemove() const override { + bool canRemove() const final { return house == nullptr; } @@ -48,6 +48,9 @@ class BedItem final : public Item return sleeperGUID; } + House* getHouse() const { + return house; + } void setHouse(House* h) { house = h; } @@ -60,7 +63,7 @@ class BedItem final : public Item BedItem* getNextBedItem() const; - private: + protected: void updateAppearance(const Player* player); void regeneratePlayer(Player* player) const; void internalSetSleeper(const Player* player); diff --git a/src/chat.cpp b/src/chat.cpp index 48f5338..59973c8 100644 --- a/src/chat.cpp +++ b/src/chat.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -54,10 +54,6 @@ void PrivateChatChannel::invitePlayer(const Player& player, Player& invitePlayer ss.str(std::string()); ss << invitePlayer.getName() << " has been invited."; player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); - - for (const auto& it : users) { - it.second->sendChannelEvent(id, invitePlayer.getName(), CHANNELEVENT_INVITE); - } } void PrivateChatChannel::excludePlayer(const Player& player, Player& excludePlayer) @@ -73,10 +69,6 @@ void PrivateChatChannel::excludePlayer(const Player& player, Player& excludePlay player.sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); excludePlayer.sendClosePrivate(id); - - for (const auto& it : users) { - it.second->sendChannelEvent(id, excludePlayer.getName(), CHANNELEVENT_EXCLUDE); - } } void PrivateChatChannel::closeChannel() const @@ -96,20 +88,6 @@ bool ChatChannel::addUser(Player& player) return false; } - // TODO: Move to script when guild channels can be scripted - if (id == CHANNEL_GUILD) { - Guild* guild = player.getGuild(); - if (guild && !guild->getMotd().empty()) { - g_scheduler.addEvent(createSchedulerTask(150, std::bind(&Game::sendGuildMotd, &g_game, player.getID()))); - } - } - - if (!publicChannel) { - for (const auto& it : users) { - it.second->sendChannelEvent(id, player.getName(), CHANNELEVENT_JOIN); - } - } - users[player.getID()] = &player; return true; } @@ -123,27 +101,10 @@ bool ChatChannel::removeUser(const Player& player) users.erase(iter); - if (!publicChannel) { - for (const auto& it : users) { - it.second->sendChannelEvent(id, player.getName(), CHANNELEVENT_LEAVE); - } - } - executeOnLeaveEvent(player); return true; } -bool ChatChannel::hasUser(const Player& player) { - return users.find(player.getID()) != users.end(); -} - -void ChatChannel::sendToAll(const std::string& message, SpeakClasses type) const -{ - for (const auto& it : users) { - it.second->sendChannelMessage("", message, type, id); - } -} - bool ChatChannel::talk(const Player& fromPlayer, SpeakClasses type, const std::string& text) { if (users.find(fromPlayer.getID()) == users.end()) { @@ -294,39 +255,21 @@ bool Chat::load() return false; } + std::forward_list 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()) { - uint16_t channelId = pugi::cast(channelNode.attribute("id").value()); - std::string channelName = channelNode.attribute("name").as_string(); - bool isPublic = channelNode.attribute("public").as_bool(); + ChatChannel channel(pugi::cast(channelNode.attribute("id").value()), channelNode.attribute("name").as_string()); + channel.publicChannel = channelNode.attribute("public").as_bool(); + pugi::xml_attribute scriptAttribute = channelNode.attribute("script"); - - auto it = normalChannels.find(channelId); - if (it != normalChannels.end()) { - ChatChannel& channel = it->second; - channel.publicChannel = isPublic; - channel.name = channelName; - - 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; - } - } - - UsersMap tempUserMap = std::move(channel.users); - for (const auto& pair : tempUserMap) { - channel.addUser(*pair.second); - } - continue; - } - - ChatChannel channel(channelId, channelName); - channel.publicChannel = isPublic; - if (scriptAttribute) { if (scriptInterface.loadFile("data/chatchannels/scripts/" + std::string(scriptAttribute.as_string())) == 0) { channel.onSpeakEvent = scriptInterface.getEvent("onSpeak"); @@ -338,8 +281,13 @@ bool Chat::load() } } + removedChannels.remove(channel.id); normalChannels[channel.id] = channel; } + + for (uint16_t channelId : removedChannels) { + normalChannels.erase(channelId); + } return true; } diff --git a/src/chat.h b/src/chat.h index 2f4c603..e181142 100644 --- a/src/chat.h +++ b/src/chat.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,24 +26,23 @@ class Party; class Player; -using UsersMap = std::map; -using InvitedMap = std::map; +typedef std::map UsersMap; +typedef std::map InvitedMap; class ChatChannel { public: ChatChannel() = default; ChatChannel(uint16_t channelId, std::string channelName): - id{channelId}, name{std::move(channelName)} {} + name(std::move(channelName)), + id(channelId) {} virtual ~ChatChannel() = default; bool addUser(Player& player); bool removeUser(const Player& player); - bool hasUser(const Player& player); bool talk(const Player& fromPlayer, SpeakClasses type, const std::string& text); - void sendToAll(const std::string& message, SpeakClasses type) const; const std::string& getName() const { return name; @@ -72,9 +71,6 @@ class ChatChannel protected: UsersMap users; - uint16_t id; - - private: std::string name; int32_t canJoinEvent = -1; @@ -82,6 +78,7 @@ class ChatChannel int32_t onLeaveEvent = -1; int32_t onSpeakEvent = -1; + uint16_t id; bool publicChannel = false; friend class Chat; @@ -92,7 +89,7 @@ class PrivateChatChannel final : public ChatChannel public: PrivateChatChannel(uint16_t channelId, std::string channelName) : ChatChannel(channelId, channelName) {} - uint32_t getOwner() const override { + uint32_t getOwner() const final { return owner; } void setOwner(uint32_t owner) { @@ -108,16 +105,16 @@ class PrivateChatChannel final : public ChatChannel void closeChannel() const; - const InvitedMap* getInvitedUsers() const override { + const InvitedMap* getInvitedUsers() const final { return &invites; } - private: + protected: InvitedMap invites; uint32_t owner = 0; }; -using ChannelList = std::list; +typedef std::list ChannelList; class Chat { diff --git a/src/combat.cpp b/src/combat.cpp index f033057..e63bc0f 100644 --- a/src/combat.cpp +++ b/src/combat.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,61 +22,30 @@ #include "combat.h" #include "game.h" -#include "weapons.h" #include "configmanager.h" +#include "monster.h" #include "events.h" extern Game g_game; -extern Weapons* g_weapons; extern ConfigManager g_config; extern Events* g_events; -CombatDamage Combat::getCombatDamage(Creature* creature, Creature* target) const +CombatDamage Combat::getCombatDamage(Creature* creature) const { CombatDamage damage; damage.origin = params.origin; - damage.primary.type = params.combatType; + damage.type = params.combatType; if (formulaType == COMBAT_FORMULA_DAMAGE) { - damage.primary.value = normal_random( - static_cast(mina), - static_cast(maxa) - ); + damage.min = static_cast(mina); + damage.max = static_cast(maxa); } else if (creature) { int32_t min, max; if (creature->getCombatValues(min, max)) { - damage.primary.value = normal_random(min, max); + damage.min = min; + damage.max = max; } else if (Player* player = creature->getPlayer()) { if (params.valueCallback) { params.valueCallback->getMinMaxValues(player, damage, params.useCharges); - } else if (formulaType == COMBAT_FORMULA_LEVELMAGIC) { - int32_t levelFormula = player->getLevel() * 2 + player->getMagicLevel() * 3; - damage.primary.value = normal_random( - static_cast(levelFormula * mina + minb), - static_cast(levelFormula * maxa + maxb) - ); - } else if (formulaType == COMBAT_FORMULA_SKILL) { - Item* tool = player->getWeapon(); - const Weapon* weapon = g_weapons->getWeapon(tool); - if (weapon) { - damage.primary.value = normal_random( - static_cast(minb), - static_cast(weapon->getWeaponDamage(player, target, tool, true) * maxa + maxb) - ); - - damage.secondary.type = weapon->getElementType(); - damage.secondary.value = weapon->getElementDamage(player, target, tool); - if (params.useCharges) { - uint16_t charges = tool->getCharges(); - if (charges != 0) { - g_game.transformItem(tool, tool->getID(), charges - 1); - } - } - } else { - damage.primary.value = normal_random( - static_cast(minb), - static_cast(maxb) - ); - } } } } @@ -110,23 +79,11 @@ CombatType_t Combat::ConditionToDamageType(ConditionType_t type) case CONDITION_ENERGY: return COMBAT_ENERGYDAMAGE; - case CONDITION_BLEEDING: - return COMBAT_PHYSICALDAMAGE; - - case CONDITION_DROWN: - return COMBAT_DROWNDAMAGE; - case CONDITION_POISON: return COMBAT_EARTHDAMAGE; - case CONDITION_FREEZING: - return COMBAT_ICEDAMAGE; - - case CONDITION_DAZZLED: - return COMBAT_HOLYDAMAGE; - - case CONDITION_CURSED: - return COMBAT_DEATHDAMAGE; + case CONDITION_DROWN: + return COMBAT_DROWNDAMAGE; default: break; @@ -144,23 +101,11 @@ ConditionType_t Combat::DamageToConditionType(CombatType_t type) case COMBAT_ENERGYDAMAGE: return CONDITION_ENERGY; - case COMBAT_DROWNDAMAGE: - return CONDITION_DROWN; - case COMBAT_EARTHDAMAGE: return CONDITION_POISON; - case COMBAT_ICEDAMAGE: - return CONDITION_FREEZING; - - case COMBAT_HOLYDAMAGE: - return CONDITION_DAZZLED; - - case COMBAT_DEATHDAMAGE: - return CONDITION_CURSED; - - case COMBAT_PHYSICALDAMAGE: - return CONDITION_BLEEDING; + case COMBAT_DROWNDAMAGE: + return CONDITION_DROWN; default: return CONDITION_NONE; @@ -180,15 +125,15 @@ bool Combat::isPlayerCombat(const Creature* target) return false; } -ReturnValue Combat::canTargetCreature(Player* attacker, Creature* target) +ReturnValue Combat::canTargetCreature(Player* player, Creature* target) { - if (attacker == target) { + if (player == target) { return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; } - if (!attacker->hasFlag(PlayerFlag_IgnoreProtectionZone)) { + if (!player->hasFlag(PlayerFlag_IgnoreProtectionZone)) { //pz-zone - if (attacker->getZone() == ZONE_PROTECTION) { + if (player->getZone() == ZONE_PROTECTION) { return RETURNVALUE_YOUMAYNOTATTACKAPERSONWHILEINPROTECTIONZONE; } @@ -198,7 +143,7 @@ ReturnValue Combat::canTargetCreature(Player* attacker, Creature* target) //nopvp-zone if (isPlayerCombat(target)) { - if (attacker->getZone() == ZONE_NOPVP) { + if (player->getZone() == ZONE_NOPVP) { return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; } @@ -208,7 +153,7 @@ ReturnValue Combat::canTargetCreature(Player* attacker, Creature* target) } } - if (attacker->hasFlag(PlayerFlag_CannotUseCombat) || !target->isAttackable()) { + if (player->hasFlag(PlayerFlag_CannotUseCombat) || !target->isAttackable()) { if (target->getPlayer()) { return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; } else { @@ -217,16 +162,16 @@ ReturnValue Combat::canTargetCreature(Player* attacker, Creature* target) } if (target->getPlayer()) { - if (isProtected(attacker, target->getPlayer())) { + if (isProtected(player, target->getPlayer())) { return RETURNVALUE_YOUMAYNOTATTACKTHISPLAYER; } - if (attacker->hasSecureMode() && !Combat::isInPvpZone(attacker, target) && attacker->getSkullClient(target->getPlayer()) == SKULL_NONE) { + if (player->hasSecureMode() && !Combat::isInPvpZone(player, target) && player->getSkullClient(target->getPlayer()) == SKULL_NONE) { return RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS; } } - return Combat::canDoCombat(attacker, target); + return Combat::canDoCombat(player, target); } ReturnValue Combat::canDoCombat(Creature* caster, Tile* tile, bool aggressive) @@ -235,7 +180,11 @@ ReturnValue Combat::canDoCombat(Creature* caster, Tile* tile, bool aggressive) return RETURNVALUE_NOTENOUGHROOM; } - if (tile->hasFlag(TILESTATE_FLOORCHANGE)) { + if (tile->hasProperty(CONST_PROP_BLOCKPROJECTILE) && tile->hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID)) { + return RETURNVALUE_NOTENOUGHROOM; + } + + if (tile->hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID) && tile->hasProperty(CONST_PROP_UNLAY)) { return RETURNVALUE_NOTENOUGHROOM; } @@ -248,7 +197,8 @@ ReturnValue Combat::canDoCombat(Creature* caster, Tile* tile, bool aggressive) const Position& tilePosition = tile->getPosition(); if (casterPosition.z < tilePosition.z) { return RETURNVALUE_FIRSTGODOWNSTAIRS; - } else if (casterPosition.z > tilePosition.z) { + } + else if (casterPosition.z > tilePosition.z) { return RETURNVALUE_FIRSTGOUPSTAIRS; } @@ -283,10 +233,6 @@ bool Combat::isProtected(const Player* attacker, const Player* target) return true; } - if (attacker->getSkull() == SKULL_BLACK && attacker->getSkullClient(target) == SKULL_NONE) { - return true; - } - return false; } @@ -314,7 +260,8 @@ ReturnValue Combat::canDoCombat(Creature* attacker, Creature* target) const Tile* targetPlayerTile = targetPlayer->getTile(); if (targetPlayerTile->hasFlag(TILESTATE_NOPVPZONE)) { return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; - } else if (attackerPlayer->getTile()->hasFlag(TILESTATE_NOPVPZONE) && !targetPlayerTile->hasFlag(TILESTATE_NOPVPZONE | TILESTATE_PROTECTIONZONE)) { + } + else if (attackerPlayer->getTile()->hasFlag(TILESTATE_NOPVPZONE) && !targetPlayerTile->hasFlag(TILESTATE_NOPVPZONE | TILESTATE_PROTECTIONZONE)) { return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; } } @@ -334,7 +281,8 @@ ReturnValue Combat::canDoCombat(Creature* attacker, Creature* target) } } } - } else if (target->getMonster()) { + } + else if (target->getMonster()) { if (const Player* attackerPlayer = attacker->getPlayer()) { if (attackerPlayer->hasFlag(PlayerFlag_CannotAttackMonster)) { return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE; @@ -343,16 +291,6 @@ ReturnValue Combat::canDoCombat(Creature* attacker, Creature* target) if (target->isSummon() && target->getMaster()->getPlayer() && target->getZone() == ZONE_NOPVP) { return RETURNVALUE_ACTIONNOTPERMITTEDINANOPVPZONE; } - } else if (attacker->getMonster()) { - const Creature* targetMaster = target->getMaster(); - - if (!targetMaster || !targetMaster->getPlayer()) { - const Creature* attackerMaster = attacker->getMaster(); - - if (!attackerMaster || !attackerMaster->getPlayer()) { - return RETURNVALUE_YOUMAYNOTATTACKTHISCREATURE; - } - } } } @@ -435,6 +373,16 @@ bool Combat::setParam(CombatParam_t param, uint32_t value) params.useCharges = (value != 0); return true; } + + case COMBAT_PARAM_DECREASEDAMAGE: { + params.decreaseDamage = static_cast(value); + return true; + } + + case COMBAT_PARAM_MAXIMUMDECREASEDDAMAGE: { + params.maximumDecreasedDamage = static_cast(value); + return true; + } } return false; } @@ -488,35 +436,44 @@ void Combat::CombatHealthFunc(Creature* caster, Creature* target, const CombatPa { assert(data); CombatDamage damage = *data; - if (g_game.combatBlockHit(damage, caster, target, params.blockedByShield, params.blockedByArmor, params.itemId != 0)) { - return; + + if (damage.value == 0) { + damage.value = normal_random(damage.min, damage.max); } - if ((damage.primary.value < 0 || damage.secondary.value < 0) && caster) { + if (damage.value < 0 && caster) { Player* targetPlayer = target->getPlayer(); - if (targetPlayer && caster->getPlayer() && targetPlayer->getSkull() != SKULL_BLACK) { - damage.primary.value /= 2; - damage.secondary.value /= 2; + if (targetPlayer && caster->getPlayer()) { + damage.value /= 2; } } + if (g_game.combatBlockHit(damage, caster, target, params.blockedByShield, params.blockedByArmor, params.itemId != 0)) { + return; + } + if (g_game.combatChangeHealth(caster, target, damage)) { CombatConditionFunc(caster, target, params, &damage); CombatDispelFunc(caster, target, params, nullptr); } } -void Combat::CombatManaFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* damage) +void Combat::CombatManaFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data) { - assert(damage); - CombatDamage damageCopy = *damage; - if (damageCopy.primary.value < 0) { + assert(data); + CombatDamage damage = *data; + + if (damage.value == 0) { + damage.value = normal_random(damage.min, damage.max); + } + + if (damage.value < 0) { if (caster && caster->getPlayer() && target->getPlayer()) { - damageCopy.primary.value /= 2; + damage.value /= 2; } } - if (g_game.combatChangeMana(caster, target, damageCopy)) { + if (g_game.combatChangeMana(caster, target, damage)) { CombatConditionFunc(caster, target, params, nullptr); CombatDispelFunc(caster, target, params, nullptr); } @@ -524,7 +481,7 @@ void Combat::CombatManaFunc(Creature* caster, Creature* target, const CombatPara void Combat::CombatConditionFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data) { - if (params.origin == ORIGIN_MELEE && data && data->primary.value == 0 && data->secondary.value == 0) { + if (params.origin == ORIGIN_MELEE && data && data->value == 0) { return; } @@ -552,7 +509,7 @@ void Combat::CombatNullFunc(Creature* caster, Creature* target, const CombatPara CombatDispelFunc(caster, target, params, nullptr); } -void Combat::combatTileEffects(const SpectatorVec& spectators, Creature* caster, Tile* tile, const CombatParams& params) +void Combat::combatTileEffects(const SpectatorVec& list, Creature* caster, Tile* tile, const CombatParams& params) { if (params.itemId != 0) { uint16_t itemId = params.itemId; @@ -630,51 +587,25 @@ void Combat::combatTileEffects(const SpectatorVec& spectators, Creature* caster, } if (params.impactEffect != CONST_ME_NONE) { - Game::addMagicEffect(spectators, tile->getPosition(), params.impactEffect); + Game::addMagicEffect(list, tile->getPosition(), params.impactEffect); } } void Combat::postCombatEffects(Creature* caster, const Position& pos, const CombatParams& params) { if (caster && params.distanceEffect != CONST_ANI_NONE) { - addDistanceEffect(caster, caster->getPosition(), pos, params.distanceEffect); + addDistanceEffect(caster->getPosition(), pos, params.distanceEffect); } } -void Combat::addDistanceEffect(Creature* caster, const Position& fromPos, const Position& toPos, uint8_t effect) +void Combat::addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect) { - if (effect == CONST_ANI_WEAPONTYPE) { - if (!caster) { - return; - } - - Player* player = caster->getPlayer(); - if (!player) { - return; - } - - switch (player->getWeaponType()) { - case WEAPON_AXE: - effect = CONST_ANI_WHIRLWINDAXE; - break; - case WEAPON_SWORD: - effect = CONST_ANI_WHIRLWINDSWORD; - break; - case WEAPON_CLUB: - effect = CONST_ANI_WHIRLWINDCLUB; - break; - default: - effect = CONST_ANI_NONE; - break; - } - } - if (effect != CONST_ANI_NONE) { g_game.addDistanceEffect(fromPos, toPos, effect); } } -void Combat::CombatFunc(Creature* caster, const Position& pos, const AreaCombat* area, const CombatParams& params, CombatFunction func, CombatDamage* data) +void Combat::CombatFunc(Creature* caster, const Position& pos, const AreaCombat* area, const CombatParams& params, COMBATFUNC func, CombatDamage* data) { std::forward_list tileList; @@ -684,6 +615,7 @@ void Combat::CombatFunc(Creature* caster, const Position& pos, const AreaCombat* getCombatArea(pos, pos, area, tileList); } + SpectatorVec list; uint32_t maxX = 0; uint32_t maxY = 0; @@ -704,18 +636,100 @@ void Combat::CombatFunc(Creature* caster, const Position& pos, const AreaCombat* const int32_t rangeX = maxX + Map::maxViewportX; const int32_t rangeY = maxY + Map::maxViewportY; - - SpectatorVec spectators; - g_game.map.getSpectators(spectators, pos, true, true, rangeX, rangeX, rangeY, rangeY); + g_game.map.getSpectators(list, pos, true, true, rangeX, rangeX, rangeY, rangeY); postCombatEffects(caster, pos, params); + uint16_t decreasedDamage = 0; + const uint16_t maximumDecreasedDamage = params.maximumDecreasedDamage; + + bool firstCreature = true; + + if (params.decreaseDamage && data) { + for (Tile* tile : tileList) { + if (canDoCombat(caster, tile, params.aggressive) != RETURNVALUE_NOERROR) { + continue; + } + + if (CreatureVector* creatures = tile->getCreatures()) { + const Creature* topCreature = tile->getTopCreature(); + for (Creature* creature : *creatures) { + if (params.targetCasterOrTopMost) { + if (caster && caster->getTile() == tile) { + if (creature != caster) { + continue; + } + } else if (creature != topCreature) { + continue; + } + } + + if (!params.aggressive || (caster != creature && Combat::canDoCombat(caster, creature) == RETURNVALUE_NOERROR)) { + if (firstCreature) { + firstCreature = false; + continue; + } + + // only apply to players + if (creature->getPlayer()) { + if (maximumDecreasedDamage && decreasedDamage >= maximumDecreasedDamage) { + break; + } + + decreasedDamage += params.decreaseDamage; + } + } + } + } + } + + // actually decrease total damage output + if (data->value == 0) { + int32_t decreasedMinDamage = std::abs(data->min) * decreasedDamage / 100; + int32_t decreasedMaxDamage = std::abs(data->max) * decreasedDamage / 100; + + if (data->min < 0) { + // damaging spell, get as close as zero as we can get + // do not allow healing values + data->min += decreasedMinDamage; + data->max += decreasedMaxDamage; + + data->min = std::min(0, data->min); + data->max = std::min(0, data->max); + } else { + // healing spell, get as close as zero as we can get + // do not allow damaging values + data->min -= decreasedMinDamage; + data->max -= decreasedMaxDamage; + + data->min = std::max(0, data->min); + data->max = std::max(0, data->max); + } + } else { + int32_t decreasedValue = (std::abs(data->value) * decreasedDamage) / 100; + + if (data->value < 0) { + // damaging spell, get as close as zero as we can get + // do not allow healing values + data->value += decreasedValue; + + data->value = std::min(0, data->value); + } else { + // healing spell, get as close as zero as we can get + // do not allow damaging values + data->value -= decreasedValue; + + data->value = std::max(0, data->value); + } + } + } + for (Tile* tile : tileList) { if (canDoCombat(caster, tile, params.aggressive) != RETURNVALUE_NOERROR) { continue; } - combatTileEffects(spectators, caster, tile, params); + combatTileEffects(list, caster, tile, params); if (CreatureVector* creatures = tile->getCreatures()) { const Creature* topCreature = tile->getTopCreature(); @@ -749,8 +763,8 @@ void Combat::doCombat(Creature* caster, Creature* target) const { //target combat callback function if (params.combatType != COMBAT_NONE) { - CombatDamage damage = getCombatDamage(caster, target); - if (damage.primary.type != COMBAT_MANADRAIN) { + CombatDamage damage = getCombatDamage(caster); + if (damage.type != COMBAT_MANADRAIN) { doCombatHealth(caster, target, damage, params); } else { doCombatMana(caster, target, damage, params); @@ -764,8 +778,8 @@ void Combat::doCombat(Creature* caster, const Position& position) const { //area combat callback function if (params.combatType != COMBAT_NONE) { - CombatDamage damage = getCombatDamage(caster, nullptr); - if (damage.primary.type != COMBAT_MANADRAIN) { + CombatDamage damage = getCombatDamage(caster); + if (damage.type != COMBAT_MANADRAIN) { doCombatHealth(caster, position, area.get(), damage, params); } else { doCombatMana(caster, position, area.get(), damage, params); @@ -775,7 +789,501 @@ void Combat::doCombat(Creature* caster, const Position& position) const } } -void Combat::doCombatHealth(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params) +int32_t Combat::computeDamage(Creature* creature, int32_t strength, int32_t variation) +{ + int32_t damage = strength; + if (variation) { + damage = normal_random(-variation, variation) + strength; + } + + if (creature) { + if (Player* player = creature->getPlayer()) { + int32_t formula = 3 * player->getMagicLevel() + 2 * player->getLevel(); + damage = formula * damage / 100; + } + } + + return damage; +} + +int32_t Combat::getTotalDamage(int32_t attackSkill, int32_t attackValue, fightMode_t fightMode) +{ + int32_t damage = attackValue; + + switch (fightMode) { + case FIGHTMODE_ATTACK: + damage += 2 * damage / 10; + break; + case FIGHTMODE_DEFENSE: + damage -= 4 * damage / 10; + break; + default: break; + } + + int32_t formula = (5 * (attackSkill) + 50) * damage; + int32_t randresult = rand() % 100; + int32_t totalDamage = -(ceil(formula * ((rand() % 100 + randresult) / 2) / 10000.)); + return totalDamage; +} + +bool Combat::attack(Creature* attacker, Creature* target) +{ + if (Player* player = attacker->getPlayer()) { + Item* weapon = player->getWeapon(); + if (weapon) { + if (weapon->getWeaponType() == WEAPON_DISTANCE || weapon->getWeaponType() == WEAPON_WAND) { + return rangeAttack(attacker, target, player->getFightMode()); + } + } + + return closeAttack(attacker, target, player->getFightMode()); + } + + return false; +} + +bool Combat::closeAttack(Creature* attacker, Creature* target, fightMode_t fightMode) +{ + const Position& attackerPos = attacker->getPosition(); + const Position& targetPos = target->getPosition(); + if (attackerPos.z != targetPos.z) { + return false; + } + + if (std::max(Position::getDistanceX(attackerPos, targetPos), Position::getDistanceY(attackerPos, targetPos)) > 1) { + return false; + } + + Item* weapon = nullptr; + + if (Player* player = attacker->getPlayer()) { + weapon = player->getWeapon(); + if (weapon && !Combat::canUseWeapon(player, weapon)) { + return false; + } + } + + uint32_t attackValue = 0; + uint32_t skillValue = 0; + uint8_t skill = SKILL_FIST; + + Combat::getAttackValue(attacker, attackValue, skillValue, skill); + + int32_t defense = target->getDefense(); + + if (OTSYS_TIME() < target->earliestDefendTime) { + defense = 0; + } + + CombatParams combatParams; + combatParams.blockedByArmor = true; + combatParams.blockedByShield = true; + combatParams.combatType = COMBAT_PHYSICALDAMAGE; + + CombatDamage combatDamage; + combatDamage.type = combatParams.combatType; + int32_t totalDamage = Combat::getTotalDamage(skillValue, attackValue, fightMode); + combatDamage.value = totalDamage; + combatDamage.origin = ORIGIN_MELEE; + + bool hit = Combat::doCombatHealth(attacker, target, combatDamage, combatParams); + + if (Monster* monster = attacker->getMonster()) { + int32_t poison = monster->mType->info.poison; + if (poison) { + int32_t randTest = rand(); + + if (hit || ((-totalDamage > defense) && (randTest == 5 * (randTest / 5)))) { + poison = normal_random(poison / 2, poison); + if (poison) { + ConditionDamage* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, CONDITION_POISON, 0, 0)); + condition->setParam(CONDITION_PARAM_OWNER, attacker->getID()); + condition->setParam(CONDITION_PARAM_CYCLE, poison); + condition->setParam(CONDITION_PARAM_COUNT, 3); + condition->setParam(CONDITION_PARAM_MAX_COUNT, 3); + target->addCombatCondition(condition); + } + } + } + } + + if (Player* player = attacker->getPlayer()) { + // skills advancing + if (!player->hasFlag(PlayerFlag_NotGainSkill)) { + if (player->getAddAttackSkill() && player->getLastAttackBlockType() != BLOCK_IMMUNITY) { + player->addSkillAdvance(static_cast(skill), 1); + } + } + + // weapon + if (weapon) { + if (weapon->getCharges() > 0) { + int32_t charges = weapon->getCharges() - 1; + if (charges <= 0) { + g_game.internalRemoveItem(weapon); + } else { + g_game.transformItem(weapon, weapon->getID(), charges); + } + } + } + } + + if (Player* player = attacker->getPlayer()) { + Combat::postWeaponEffects(player, weapon); + } + + return true; +} + +bool Combat::rangeAttack(Creature* attacker, Creature* target, fightMode_t fightMode) +{ + const Position& attackerPos = attacker->getPosition(); + const Position& targetPos = target->getPosition(); + if (attackerPos.z != targetPos.z) { + return false; + } + + uint8_t range = 0; + uint8_t hitChance = 0; + uint8_t distanceEffect = 0; + uint8_t specialEffect = 0; + int32_t attackStrength = 0; + int32_t attackVariation = 0; + + Item* weapon = nullptr; + Item* ammunition = nullptr; + + bool moveWeapon = true; + + if (Player* player = attacker->getPlayer()) { + weapon = player->getWeapon(); + if (!weapon) { + return false; + } + + if (!Combat::canUseWeapon(player, weapon)) { + return false; + } + + range = weapon->getShootRange(); + distanceEffect = weapon->getMissileType(); + + if (weapon->getWeaponType() == WEAPON_DISTANCE) { + ammunition = player->getAmmunition(); + if (weapon->getAmmoType() != AMMO_NONE) { + if (!ammunition || weapon->getAmmoType() != ammunition->getAmmoType()) { + // redirect to fist fighting + return closeAttack(attacker, target, fightMode); + } + + distanceEffect = ammunition->getMissileType(); + } + } + } + + if (std::max(Position::getDistanceX(attackerPos, targetPos), Position::getDistanceY(attackerPos, targetPos)) > range) { + return false; + } + + if (weapon->getWeaponType() == WEAPON_DISTANCE) { + uint32_t attackValue = 0; + uint32_t skillValue = 0; + uint8_t skill = SKILL_FIST; + + Combat::getAttackValue(attacker, attackValue, skillValue, skill); + + CombatParams combatParams; + combatParams.blockedByArmor = true; + combatParams.blockedByShield = false; + combatParams.combatType = COMBAT_PHYSICALDAMAGE; + + CombatDamage combatDamage; + combatDamage.type = combatParams.combatType; + combatDamage.value = Combat::getTotalDamage(skillValue, attackValue, fightMode); + combatDamage.origin = ORIGIN_RANGED; + + if (weapon) { + hitChance = 75; // throwables and such + specialEffect = weapon->getWeaponSpecialEffect(); + attackStrength = weapon->getAttackStrength(); + attackVariation = weapon->getAttackVariation(); + if (weapon->getFragility()) { + if (normal_random(0, 99) <= weapon->getFragility()) { + uint16_t count = weapon->getItemCount(); + if (count > 1) { + g_game.transformItem(weapon, weapon->getID(), count - 1); + } else { + g_game.internalRemoveItem(weapon); + } + + moveWeapon = false; + } + } + } + + if (ammunition && weapon->getAmmoType() != AMMO_NONE && weapon->getAmmoType() == ammunition->getAmmoType()) { + hitChance = 90; // bows and crossbows + specialEffect = ammunition->getWeaponSpecialEffect(); + attackStrength = ammunition->getAttackStrength(); + attackVariation = ammunition->getAttackVariation(); + if (normal_random(0, 100) <= ammunition->getFragility()) { + uint16_t count = ammunition->getItemCount(); + if (count > 1) { + g_game.transformItem(ammunition, ammunition->getID(), count - 1); + } else { + g_game.internalRemoveItem(ammunition); + } + } + + moveWeapon = false; + } + + int32_t distance = std::max(Position::getDistanceX(attackerPos, targetPos), Position::getDistanceY(attackerPos, targetPos)); + if (distance <= 1) { + distance = 5; + } + + distance *= 15; + + bool hit = false; + + int32_t random = rand(); + if (random % distance <= static_cast(skillValue)) { + hit = random % 100 <= hitChance; + } + + + if (Player* player = attacker->getPlayer()) { + if (player->getAddAttackSkill()) { + switch (player->getLastAttackBlockType()) { + case BLOCK_NONE: { + player->addSkillAdvance(SKILL_DISTANCE, 2); + break; + } + + case BLOCK_DEFENSE: + case BLOCK_ARMOR: { + player->addSkillAdvance(SKILL_DISTANCE, 1); + break; + } + + default: break; + } + } + } + + if (specialEffect == 1) { + if (hit) { + const int32_t rounds = ammunition ? ammunition->getAttackStrength() : weapon->getAttackStrength(); + + ConditionDamage* poisonDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_POISON); + poisonDamage->setParam(CONDITION_PARAM_OWNER, attacker->getID()); + poisonDamage->setParam(CONDITION_PARAM_CYCLE, rounds); + poisonDamage->setParam(CONDITION_PARAM_COUNT, 3); + poisonDamage->setParam(CONDITION_PARAM_MAX_COUNT, 3); + + target->addCombatCondition(poisonDamage); + } + } else if (specialEffect == 2) { + DamageImpact impact; + impact.actor = attacker; + impact.damage.type = COMBAT_PHYSICALDAMAGE; + impact.damage.value = -Combat::computeDamage(attacker, attackStrength, attackVariation); + impact.params.blockedByArmor = true; + impact.params.blockedByShield = false; + circleShapeSpell(attacker, target->getPosition(), 0xFF, 0, 3, &impact, 7); + } + + if (!hit) { + Tile* destTile = target->getTile(); + + if (!Position::areInRange<1, 1, 0>(attacker->getPosition(), target->getPosition())) { + static std::vector> destList{ + { -1, -1 },{ 0, -1 },{ 1, -1 }, + { -1, 0 },{ 0, 0 },{ 1, 0 }, + { -1, 1 },{ 0, 1 },{ 1, 1 } + }; + std::shuffle(destList.begin(), destList.end(), getRandomGenerator()); + + Position destPos = target->getPosition(); + + for (const auto& dir : destList) { + // Blocking tiles or tiles without ground ain't valid targets for spears + Tile* tmpTile = g_game.map.getTile(destPos.x + dir.first, destPos.y + dir.second, destPos.z); + if (tmpTile && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID) && tmpTile->getGround() != nullptr) { + destTile = tmpTile; + break; + } + } + } + + g_game.addMagicEffect(destTile->getPosition(), CONST_ME_POFF); + g_game.addDistanceEffect(attackerPos, destTile->getPosition(), distanceEffect); + + if (moveWeapon) { + g_game.internalMoveItem(weapon->getParent(), destTile, INDEX_WHEREEVER, weapon, 1, nullptr, FLAG_NOLIMIT); + } + + return true; + } + + g_game.addDistanceEffect(attackerPos, targetPos, distanceEffect); + Combat::doCombatHealth(attacker, target, combatDamage, combatParams); + + if (moveWeapon) { + g_game.internalMoveItem(weapon->getParent(), target->getTile(), INDEX_WHEREEVER, weapon, 1, nullptr, FLAG_NOLIMIT); + } + } else if (weapon->getWeaponType() == WEAPON_WAND) { + int32_t variation = normal_random(-weapon->getAttackVariation(), weapon->getAttackVariation()); + + CombatParams combatParams; + combatParams.combatType = weapon->getDamageType(); + + CombatDamage combatDamage; + combatDamage.type = combatParams.combatType; + combatDamage.value = -(variation + weapon->getAttackStrength()); + combatDamage.origin = ORIGIN_RANGED; + + g_game.addDistanceEffect(attackerPos, targetPos, distanceEffect); + Combat::doCombatHealth(attacker, target, combatDamage, combatParams); + } + + if (Player* player = attacker->getPlayer()) { + Combat::postWeaponEffects(player, weapon); + } + + return true; +} + +void Combat::circleShapeSpell(Creature* attacker, const Position& toPos, int32_t range, int32_t animation, int32_t radius, DamageImpact* impact, int32_t effect) +{ + const Position& fromPos = attacker->getPosition(); + if (fromPos.z != toPos.z) { + return; + } + + int32_t distance = std::max(Position::getDistanceX(fromPos, toPos), Position::getDistanceY(fromPos, toPos)); + if (distance > range) { + return; + } + + if (animation && fromPos != toPos) { + g_game.addDistanceEffect(fromPos, toPos, animation); + } + + std::forward_list tiles; + + AreaCombat areaCombat; + areaCombat.setupArea(radius); + + areaCombat.getList(toPos, toPos, tiles); + + for (Tile* tile : tiles) { + if (tile->hasFlag(TILESTATE_PROTECTIONZONE)) { + continue; + } + + if (CreatureVector* creatures = tile->getCreatures()) { + for (Creature* creature : *creatures) { + impact->handleCreature(creature); + } + } + + if (effect) { + g_game.addMagicEffect(tile->getPosition(), effect); + } + } +} + +void Combat::getAttackValue(Creature* creature, uint32_t& attackValue, uint32_t& skillValue, uint8_t& skill) +{ + skill = SKILL_FIST; + + if (Player* player = creature->getPlayer()) { + Item* weapon = player->getWeapon(); + if (weapon) { + switch (weapon->getWeaponType()) { + case WEAPON_AXE: { + skill = SKILL_AXE; + attackValue = weapon->getAttack(); + break; + } + case WEAPON_SWORD: { + skill = SKILL_SWORD; + attackValue = weapon->getAttack(); + break; + } + case WEAPON_CLUB: { + skill = SKILL_CLUB; + attackValue = weapon->getAttack(); + break; + } + case WEAPON_DISTANCE: { + skill = SKILL_DISTANCE; + attackValue = weapon->getAttack(); + + if (weapon->getAmmoType() != AMMO_NONE) { + Item* ammunition = player->getAmmunition(); + if (ammunition && ammunition->getAmmoType() == weapon->getAmmoType()) { + attackValue += ammunition->getAttack(); + } + } + break; + } + default: + attackValue = 7; + break; + } + + skillValue = player->getSkillLevel(skill); + } else { + attackValue = 7; + skillValue = player->getSkillLevel(skill); + } + } else if (Monster* monster = creature->getMonster()) { + attackValue = monster->mType->info.attack; + skillValue = monster->mType->info.skill; + } +} + +bool Combat::canUseWeapon(Player* player, Item* weapon) +{ + if (player->hasFlag(PlayerFlag_IgnoreWeaponCheck)) { + return true; + } + + if (player->getLevel() < weapon->getMinimumLevel()) { + return false; + } + + if (!player->hasFlag(PlayerFlag_HasInfiniteMana) && static_cast(player->getMana()) < weapon->getManaConsumption()) { + return false; + } + + const ItemType& itemType = Item::items[weapon->getID()]; + if (hasBitSet(WIELDINFO_VOCREQ, itemType.wieldInfo)) { + if (!hasBitSet(player->getVocationFlagId(), itemType.vocations)) { + return false; + } + } + + return true; +} + +void Combat::postWeaponEffects(Player* player, Item* weapon) +{ + if (!weapon || player->hasFlag(PlayerFlag_IgnoreWeaponCheck)) { + return; + } + + int32_t manaConsumption = weapon->getManaConsumption(); + if (manaConsumption) { + player->addManaSpent(manaConsumption); + player->changeMana(-manaConsumption); + } +} + +bool Combat::doCombatHealth(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params) { bool canCombat = !params.aggressive || (caster != target && Combat::canDoCombat(caster, target) == RETURNVALUE_NOERROR); if ((caster == target || canCombat) && params.impactEffect != CONST_ME_NONE) { @@ -784,7 +1292,7 @@ void Combat::doCombatHealth(Creature* caster, Creature* target, CombatDamage& da if (canCombat) { if (caster && params.distanceEffect != CONST_ANI_NONE) { - addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect); + addDistanceEffect(caster->getPosition(), target->getPosition(), params.distanceEffect); } CombatHealthFunc(caster, target, params, &damage); @@ -792,6 +1300,8 @@ void Combat::doCombatHealth(Creature* caster, Creature* target, CombatDamage& da params.targetCallback->onTargetCombat(caster, target); } } + + return canCombat; } void Combat::doCombatHealth(Creature* caster, const Position& position, const AreaCombat* area, CombatDamage& damage, const CombatParams& params) @@ -808,7 +1318,7 @@ void Combat::doCombatMana(Creature* caster, Creature* target, CombatDamage& dama if (canCombat) { if (caster && params.distanceEffect != CONST_ANI_NONE) { - addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect); + addDistanceEffect(caster->getPosition(), target->getPosition(), params.distanceEffect); } CombatManaFunc(caster, target, params, &damage); @@ -837,7 +1347,7 @@ void Combat::doCombatCondition(Creature* caster, Creature* target, const CombatP if (canCombat) { if (caster && params.distanceEffect != CONST_ANI_NONE) { - addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect); + addDistanceEffect(caster->getPosition(), target->getPosition(), params.distanceEffect); } CombatConditionFunc(caster, target, params, nullptr); @@ -866,7 +1376,7 @@ void Combat::doCombatDispel(Creature* caster, Creature* target, const CombatPara } if (caster && params.distanceEffect != CONST_ANI_NONE) { - addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect); + addDistanceEffect(caster->getPosition(), target->getPosition(), params.distanceEffect); } } } @@ -874,11 +1384,11 @@ void Combat::doCombatDispel(Creature* caster, Creature* target, const CombatPara void Combat::doCombatDefault(Creature* caster, Creature* target, const CombatParams& params) { if (!params.aggressive || (caster != target && Combat::canDoCombat(caster, target) == RETURNVALUE_NOERROR)) { - SpectatorVec spectators; - g_game.map.getSpectators(spectators, target->getPosition(), true, true); + SpectatorVec list; + g_game.map.getSpectators(list, target->getPosition(), true, true); CombatNullFunc(caster, target, params, nullptr); - combatTileEffects(spectators, caster, target->getTile(), params); + combatTileEffects(list, caster, target->getTile(), params); if (params.targetCallback) { params.targetCallback->onTargetCombat(caster, target); @@ -891,7 +1401,7 @@ void Combat::doCombatDefault(Creature* caster, Creature* target, const CombatPar */ if (caster && params.distanceEffect != CONST_ANI_NONE) { - addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect); + addDistanceEffect(caster->getPosition(), target->getPosition(), params.distanceEffect); } } } @@ -930,33 +1440,29 @@ void ValueCallback::getMinMaxValues(Player* player, CombatDamage& damage, bool u } case COMBAT_FORMULA_SKILL: { - //onGetPlayerMinMaxValues(player, attackSkill, attackValue, attackFactor) - Item* tool = player->getWeapon(); - const Weapon* weapon = g_weapons->getWeapon(tool); + //onGetPlayerMinMaxValues(player, attackSkill, attackValue, fightMode) + uint32_t attackValue = 7; + uint32_t attackSkill = 0; + uint8_t skill = 0; - int32_t attackValue = 7; - if (weapon) { - attackValue = tool->getAttack(); - if (tool->getWeaponType() == WEAPON_AMMO) { - Item* item = player->getWeapon(true); - if (item) { - attackValue += item->getAttack(); - } - } + Combat::getAttackValue(player, attackValue, attackSkill, skill); - damage.secondary.type = weapon->getElementType(); - damage.secondary.value = weapon->getElementDamage(player, nullptr, tool); - if (useCharges) { - uint16_t charges = tool->getCharges(); - if (charges != 0) { - g_game.transformItem(tool, tool->getID(), charges - 1); + Item* weapon = player->getWeapon(); + if (useCharges && weapon) { + const ItemType& itemType = Item::items.getItemType(weapon->getID()); + if (itemType.charges) { + int32_t newCount = std::max(0, weapon->getCharges() - 1); + if (newCount <= 0) { + g_game.internalRemoveItem(weapon); + } else { + g_game.transformItem(weapon, weapon->getID(), newCount); } } } - lua_pushnumber(L, player->getWeaponSkill(tool)); + lua_pushnumber(L, attackSkill); lua_pushnumber(L, attackValue); - lua_pushnumber(L, player->getAttackFactor()); + lua_pushnumber(L, player->getFightMode()); parameters += 3; break; } @@ -972,10 +1478,8 @@ void ValueCallback::getMinMaxValues(Player* player, CombatDamage& damage, bool u if (lua_pcall(L, parameters, 2, 0) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); } else { - damage.primary.value = normal_random( - LuaScriptInterface::getNumber(L, -2), - LuaScriptInterface::getNumber(L, -1) - ); + damage.min = LuaScriptInterface::getNumber(L, -2); + damage.max = LuaScriptInterface::getNumber(L, -1); lua_pop(L, 2); } @@ -1112,7 +1616,7 @@ void AreaCombat::getList(const Position& centerPos, const Position& targetPos, s } } -void AreaCombat::copyArea(const MatrixArea* input, MatrixArea* output, MatrixOperation_t op) +void AreaCombat::copyArea(const MatrixArea* input, MatrixArea* output, MatrixOperation_t op) const { uint32_t centerY, centerX; input->getCenter(centerY, centerX); @@ -1238,17 +1742,17 @@ void AreaCombat::setupArea(const std::list& list, uint32_t rows) //SOUTH MatrixArea* southArea = new MatrixArea(maxOutput, maxOutput); - AreaCombat::copyArea(area, southArea, MATRIXOPERATION_ROTATE180); + copyArea(area, southArea, MATRIXOPERATION_ROTATE180); areas[DIRECTION_SOUTH] = southArea; //EAST MatrixArea* eastArea = new MatrixArea(maxOutput, maxOutput); - AreaCombat::copyArea(area, eastArea, MATRIXOPERATION_ROTATE90); + copyArea(area, eastArea, MATRIXOPERATION_ROTATE90); areas[DIRECTION_EAST] = eastArea; //WEST MatrixArea* westArea = new MatrixArea(maxOutput, maxOutput); - AreaCombat::copyArea(area, westArea, MATRIXOPERATION_ROTATE270); + copyArea(area, westArea, MATRIXOPERATION_ROTATE270); areas[DIRECTION_WEST] = westArea; } @@ -1338,17 +1842,17 @@ void AreaCombat::setupExtArea(const std::list& list, uint32_t rows) //NORTH-EAST MatrixArea* neArea = new MatrixArea(maxOutput, maxOutput); - AreaCombat::copyArea(area, neArea, MATRIXOPERATION_MIRROR); + copyArea(area, neArea, MATRIXOPERATION_MIRROR); areas[DIRECTION_NORTHEAST] = neArea; //SOUTH-WEST MatrixArea* swArea = new MatrixArea(maxOutput, maxOutput); - AreaCombat::copyArea(area, swArea, MATRIXOPERATION_FLIP); + copyArea(area, swArea, MATRIXOPERATION_FLIP); areas[DIRECTION_SOUTHWEST] = swArea; //SOUTH-EAST MatrixArea* seArea = new MatrixArea(maxOutput, maxOutput); - AreaCombat::copyArea(swArea, seArea, MATRIXOPERATION_MIRROR); + copyArea(swArea, seArea, MATRIXOPERATION_MIRROR); areas[DIRECTION_SOUTHEAST] = seArea; } @@ -1357,7 +1861,7 @@ void AreaCombat::setupExtArea(const std::list& list, uint32_t rows) void MagicField::onStepInField(Creature* creature) { //remove magic walls/wild growth - if (id == ITEM_MAGICWALL || id == ITEM_WILDGROWTH || id == ITEM_MAGICWALL_SAFE || id == ITEM_WILDGROWTH_SAFE || isBlocking()) { + if (id == ITEM_MAGICWALL || id == ITEM_WILDGROWTH || isBlocking()) { if (!creature->isInGhostMode()) { g_game.internalRemoveItem(this, 1); } @@ -1399,3 +1903,26 @@ void MagicField::onStepInField(Creature* creature) creature->addCondition(conditionCopy); } } + +void DamageImpact::handleCreature(Creature* target) +{ + Combat::doCombatHealth(actor, target, damage, params); +} + +void SpeedImpact::handleCreature(Creature* target) +{ + ConditionType_t conditionType = CONDITION_PARALYZE; + if (percent > 0) { + conditionType = CONDITION_HASTE; + } + + ConditionSpeed* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, duration)); + condition->setSpeedDelta(percent); + target->addCondition(condition); +} + +void DunkenImpact::handleCreature(Creature* target) +{ + Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_DRUNK, duration); + target->addCondition(condition); +} diff --git a/src/combat.h b/src/combat.h index c6b4dff..a6b6b70 100644 --- a/src/combat.h +++ b/src/combat.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -38,7 +38,7 @@ class ValueCallback final : public CallBack explicit ValueCallback(formulaType_t type): type(type) {} void getMinMaxValues(Player* player, CombatDamage& damage, bool useCharges) const; - private: + protected: formulaType_t type; }; @@ -46,12 +46,18 @@ 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 { @@ -62,6 +68,8 @@ struct CombatParams { std::unique_ptr targetCallback; uint16_t itemId = 0; + uint16_t decreaseDamage = 0; + uint16_t maximumDecreasedDamage = 0; ConditionType_t dispelType = CONDITION_NONE; CombatType_t combatType = COMBAT_NONE; @@ -77,7 +85,39 @@ struct CombatParams { bool useCharges = false; }; -using CombatFunction = std::function; +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 void(*COMBATFUNC)(Creature*, Creature*, const CombatParams&, CombatDamage*); class MatrixArea { @@ -145,14 +185,14 @@ class MatrixArea return cols; } - const bool* operator[](uint32_t i) const { + inline const bool* operator[](uint32_t i) const { return data_[i]; } - bool* operator[](uint32_t i) { + inline bool* operator[](uint32_t i) { return data_[i]; } - private: + protected: uint32_t centerX; uint32_t centerY; @@ -182,7 +222,7 @@ class AreaCombat void setupExtArea(const std::list& list, uint32_t rows); void clear(); - private: + protected: enum MatrixOperation_t { MATRIXOPERATION_COPY, MATRIXOPERATION_MIRROR, @@ -193,7 +233,7 @@ class AreaCombat }; MatrixArea* createArea(const std::list& list, uint32_t rows); - static void copyArea(const MatrixArea* input, MatrixArea* output, MatrixOperation_t op); + 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); @@ -242,7 +282,18 @@ class Combat Combat(const Combat&) = delete; Combat& operator=(const Combat&) = delete; - static void doCombatHealth(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params); + 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); @@ -266,10 +317,10 @@ class Combat static ReturnValue canDoCombat(Creature* attacker, Creature* target); static void postCombatEffects(Creature* caster, const Position& pos, const CombatParams& params); - static void addDistanceEffect(Creature* caster, const Position& fromPos, const Position& toPos, uint8_t effect); + 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& position) const; + void doCombat(Creature* caster, const Position& pos) const; bool setCallback(CallBackParam_t key); CallBack* getCallback(CallBackParam_t key); @@ -281,12 +332,9 @@ class Combat bool hasArea() const { return area != nullptr; } - void addCondition(const Condition* condition) { + void setCondition(const Condition* condition) { params.conditionList.emplace_front(condition); } - void clearConditions() { - params.conditionList.clear(); - } 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); @@ -296,10 +344,13 @@ class Combat params.origin = origin; } - private: + 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, CombatFunction func, CombatDamage* data); + static void CombatFunc(Creature* caster, const Position& pos, const AreaCombat* area, const CombatParams& params, COMBATFUNC func, CombatDamage* data); static void CombatHealthFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data); static void CombatManaFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* damage); @@ -307,8 +358,8 @@ class Combat static void CombatDispelFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data); static void CombatNullFunc(Creature* caster, Creature* target, const CombatParams& params, CombatDamage* data); - static void combatTileEffects(const SpectatorVec& spectators, Creature* caster, Tile* tile, const CombatParams& params); - CombatDamage getCombatDamage(Creature* creature, Creature* target) const; + static void combatTileEffects(const SpectatorVec& list, Creature* caster, Tile* tile, const CombatParams& params); + CombatDamage getCombatDamage(Creature* creature) const; //configureable CombatParams params; @@ -328,10 +379,10 @@ class MagicField final : public Item public: explicit MagicField(uint16_t type) : Item(type), createTime(OTSYS_TIME()) {} - MagicField* getMagicField() override { + MagicField* getMagicField() final { return this; } - const MagicField* getMagicField() const override { + const MagicField* getMagicField() const final { return this; } @@ -342,13 +393,6 @@ class MagicField final : public Item const ItemType& it = items[getID()]; return it.combatType; } - int32_t getDamage() const { - const ItemType& it = items[getID()]; - if (it.conditionDamage) { - return it.conditionDamage->getTotalDamage(); - } - return 0; - } void onStepInField(Creature* creature); private: diff --git a/src/condition.cpp b/src/condition.cpp index 8862e96..7487e6a 100644 --- a/src/condition.cpp +++ b/src/condition.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,11 +32,6 @@ bool Condition::setParam(ConditionParam_t param, int32_t value) return true; } - case CONDITION_PARAM_BUFF_SPELL: { - isBuff = (value != 0); - return true; - } - case CONDITION_PARAM_SUBID: { subId = value; return true; @@ -86,16 +81,6 @@ bool Condition::unserializeProp(ConditionAttr_t attr, PropStream& propStream) return propStream.read(ticks); } - case CONDITIONATTR_ISBUFF: { - uint8_t value; - if (!propStream.read(value)) { - return false; - } - - isBuff = (value != 0); - return true; - } - case CONDITIONATTR_SUBID: { return propStream.read(subId); } @@ -119,9 +104,6 @@ void Condition::serialize(PropWriteStream& propWriteStream) propWriteStream.write(CONDITIONATTR_TICKS); propWriteStream.write(ticks); - propWriteStream.write(CONDITIONATTR_ISBUFF); - propWriteStream.write(isBuff); - propWriteStream.write(CONDITIONATTR_SUBID); propWriteStream.write(subId); } @@ -143,58 +125,47 @@ bool Condition::executeCondition(Creature*, int32_t interval) return getEndTime() >= OTSYS_TIME(); } -Condition* Condition::createCondition(ConditionId_t id, ConditionType_t type, int32_t ticks, int32_t param/* = 0*/, bool buff/* = false*/, uint32_t subId/* = 0*/) +Condition* Condition::createCondition(ConditionId_t id, ConditionType_t type, int32_t ticks, int32_t param/* = 0*/, uint32_t subId/* = 0*/) { switch (type) { case CONDITION_POISON: case CONDITION_FIRE: case CONDITION_ENERGY: case CONDITION_DROWN: - case CONDITION_FREEZING: - case CONDITION_DAZZLED: - case CONDITION_CURSED: - case CONDITION_BLEEDING: - return new ConditionDamage(id, type, buff, subId); + return new ConditionDamage(id, type, subId); case CONDITION_HASTE: case CONDITION_PARALYZE: - return new ConditionSpeed(id, type, ticks, buff, subId, param); + return new ConditionSpeed(id, type, ticks, subId, param); case CONDITION_INVISIBLE: - return new ConditionInvisible(id, type, ticks, buff, subId); + return new ConditionInvisible(id, type, ticks, subId); case CONDITION_OUTFIT: - return new ConditionOutfit(id, type, ticks, buff, subId); + return new ConditionOutfit(id, type, ticks, subId); case CONDITION_LIGHT: - return new ConditionLight(id, type, ticks, buff, subId, param & 0xFF, (param & 0xFF00) >> 8); + return new ConditionLight(id, type, ticks, subId, param & 0xFF, (param & 0xFF00) >> 8); case CONDITION_REGENERATION: - return new ConditionRegeneration(id, type, ticks, buff, subId); + return new ConditionRegeneration(id, type, ticks, subId); case CONDITION_SOUL: - return new ConditionSoul(id, type, ticks, buff, subId); + return new ConditionSoul(id, type, ticks, subId); case CONDITION_ATTRIBUTES: - return new ConditionAttributes(id, type, ticks, buff, subId); - - case CONDITION_SPELLCOOLDOWN: - return new ConditionSpellCooldown(id, type, ticks, buff, subId); - - case CONDITION_SPELLGROUPCOOLDOWN: - return new ConditionSpellGroupCooldown(id, type, ticks, buff, subId); + return new ConditionAttributes(id, type, ticks, subId); case CONDITION_INFIGHT: case CONDITION_DRUNK: - case CONDITION_EXHAUST_WEAPON: - case CONDITION_EXHAUST_COMBAT: - case CONDITION_EXHAUST_HEAL: + case CONDITION_EXHAUST: case CONDITION_MUTED: case CONDITION_CHANNELMUTEDTICKS: case CONDITION_YELLTICKS: case CONDITION_PACIFIED: case CONDITION_MANASHIELD: - return new ConditionGeneric(id, type, ticks, buff, subId); + case CONDITION_AGGRESSIVE: + return new ConditionGeneric(id, type, ticks, subId); default: return nullptr; @@ -231,15 +202,6 @@ Condition* Condition::createCondition(PropStream& propStream) return nullptr; } - if (!propStream.read(attr) || attr != CONDITIONATTR_ISBUFF) { - return nullptr; - } - - uint8_t buff; - if (!propStream.read(buff)) { - return nullptr; - } - if (!propStream.read(attr) || attr != CONDITIONATTR_SUBID) { return nullptr; } @@ -249,7 +211,7 @@ Condition* Condition::createCondition(PropStream& propStream) return nullptr; } - return createCondition(static_cast(id), static_cast(type), ticks, 0, buff != 0, subId); + return createCondition(static_cast(id), static_cast(type), ticks, 0, subId); } bool Condition::startCondition(Creature*) @@ -275,7 +237,7 @@ bool Condition::isPersistent() const uint32_t Condition::getIcons() const { - return isBuff ? ICON_PARTY_BUFF : 0; + return 0; } bool Condition::updateCondition(const Condition* addCondition) @@ -310,10 +272,10 @@ void ConditionGeneric::endCondition(Creature*) // } -void ConditionGeneric::addCondition(Creature*, const Condition* condition) +void ConditionGeneric::addCondition(Creature*, const Condition* addCondition) { - if (updateCondition(condition)) { - setTicks(condition->getTicks()); + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); } } @@ -341,22 +303,20 @@ uint32_t ConditionGeneric::getIcons() const return icons; } -void ConditionAttributes::addCondition(Creature* creature, const Condition* condition) +void ConditionAttributes::addCondition(Creature* creature, const Condition* addCondition) { - if (updateCondition(condition)) { - setTicks(condition->getTicks()); + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); - const ConditionAttributes& conditionAttrs = static_cast(*condition); + const ConditionAttributes& conditionAttrs = static_cast(*addCondition); //Remove the old condition endCondition(creature); //Apply the new one memcpy(skills, conditionAttrs.skills, sizeof(skills)); - memcpy(specialSkills, conditionAttrs.specialSkills, sizeof(specialSkills)); memcpy(skillsPercent, conditionAttrs.skillsPercent, sizeof(skillsPercent)); memcpy(stats, conditionAttrs.stats, sizeof(stats)); memcpy(statsPercent, conditionAttrs.statsPercent, sizeof(statsPercent)); - disableDefense = conditionAttrs.disableDefense; if (Player* player = creature->getPlayer()) { updatePercentSkills(player); @@ -398,8 +358,6 @@ bool ConditionAttributes::startCondition(Creature* creature) return false; } - creature->setUseDefense(!disableDefense); - if (Player* player = creature->getPlayer()) { updatePercentSkills(player); updateSkills(player); @@ -427,7 +385,7 @@ void ConditionAttributes::updatePercentStats(Player* player) break; case STAT_MAGICPOINTS: - stats[i] = static_cast(player->getBaseMagicLevel() * ((statsPercent[i] - 100) / 100.f)); + stats[i] = static_cast(player->getMagicLevel() * ((statsPercent[i] - 100) / 100.f)); break; } } @@ -472,13 +430,6 @@ void ConditionAttributes::updateSkills(Player* player) } } - for (int32_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; ++i) { - if (specialSkills[i]) { - needUpdateSkills = true; - player->setVarSpecialSkill(static_cast(i), specialSkills[i]); - } - } - if (needUpdateSkills) { player->sendSkills(); } @@ -502,13 +453,6 @@ void ConditionAttributes::endCondition(Creature* creature) } } - for (int32_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; ++i) { - if (specialSkills[i]) { - needUpdateSkills = true; - player->setVarSpecialSkill(static_cast(i), -specialSkills[i]); - } - } - if (needUpdateSkills) { player->sendSkills(); } @@ -526,10 +470,6 @@ void ConditionAttributes::endCondition(Creature* creature) player->sendStats(); } } - - if (disableDefense) { - creature->setUseDefense(true); - } } bool ConditionAttributes::setParam(ConditionParam_t param, int32_t value) @@ -651,52 +591,17 @@ bool ConditionAttributes::setParam(ConditionParam_t param, int32_t value) return true; } - case CONDITION_PARAM_DISABLE_DEFENSE: { - disableDefense = (value != 0); - return true; - } - - case CONDITION_PARAM_SPECIALSKILL_CRITICALHITCHANCE: { - specialSkills[SPECIALSKILL_CRITICALHITCHANCE] = value; - return true; - } - - case CONDITION_PARAM_SPECIALSKILL_CRITICALHITAMOUNT: { - specialSkills[SPECIALSKILL_CRITICALHITAMOUNT] = value; - return true; - } - - case CONDITION_PARAM_SPECIALSKILL_LIFELEECHCHANCE: { - specialSkills[SPECIALSKILL_LIFELEECHCHANCE] = value; - return true; - } - - case CONDITION_PARAM_SPECIALSKILL_LIFELEECHAMOUNT: { - specialSkills[SPECIALSKILL_LIFELEECHAMOUNT] = value; - return true; - } - - case CONDITION_PARAM_SPECIALSKILL_MANALEECHCHANCE: { - specialSkills[SPECIALSKILL_MANALEECHCHANCE] = value; - return true; - } - - case CONDITION_PARAM_SPECIALSKILL_MANALEECHAMOUNT: { - specialSkills[SPECIALSKILL_MANALEECHAMOUNT] = value; - return true; - } - default: return ret; } } -void ConditionRegeneration::addCondition(Creature*, const Condition* condition) +void ConditionRegeneration::addCondition(Creature*, const Condition* addCondition) { - if (updateCondition(condition)) { - setTicks(condition->getTicks()); + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); - const ConditionRegeneration& conditionRegen = static_cast(*condition); + const ConditionRegeneration& conditionRegen = static_cast(*addCondition); healthTicks = conditionRegen.healthTicks; manaTicks = conditionRegen.manaTicks; @@ -742,69 +647,17 @@ bool ConditionRegeneration::executeCondition(Creature* creature, int32_t interva internalHealthTicks += interval; internalManaTicks += interval; - if (creature->getZone() == ZONE_PROTECTION) { - return ConditionGeneric::executeCondition(creature, interval); - } + if (creature->getZone() != ZONE_PROTECTION) { + if (internalHealthTicks >= healthTicks) { + internalHealthTicks = 0; - if (internalHealthTicks >= healthTicks) { - internalHealthTicks = 0; - - int32_t realHealthGain = creature->getHealth(); - creature->changeHealth(healthGain); - realHealthGain = creature->getHealth() - realHealthGain; - - if (isBuff && realHealthGain > 0) { - Player* player = creature->getPlayer(); - if (player) { - std::string healString = std::to_string(realHealthGain) + (realHealthGain != 1 ? " hitpoints." : " hitpoint."); - - TextMessage message(MESSAGE_HEALED, "You were healed for " + healString); - message.position = player->getPosition(); - message.primary.value = realHealthGain; - message.primary.color = TEXTCOLOR_MAYABLUE; - player->sendTextMessage(message); - - SpectatorVec spectators; - g_game.map.getSpectators(spectators, player->getPosition(), false, true); - spectators.erase(player); - if (!spectators.empty()) { - message.type = MESSAGE_HEALED_OTHERS; - message.text = player->getName() + " was healed for " + healString; - for (Creature* spectator : spectators) { - spectator->getPlayer()->sendTextMessage(message); - } - } - } + creature->changeHealth(healthGain); } - } - if (internalManaTicks >= manaTicks) { - internalManaTicks = 0; - - if (Player* player = creature->getPlayer()) { - int32_t realManaGain = player->getMana(); - player->changeMana(manaGain); - realManaGain = player->getMana() - realManaGain; - - if (isBuff && realManaGain > 0) { - std::string manaGainString = std::to_string(realManaGain); - - TextMessage message(MESSAGE_HEALED, "You gained " + manaGainString + " mana."); - message.position = player->getPosition(); - message.primary.value = realManaGain; - message.primary.color = TEXTCOLOR_MAYABLUE; - player->sendTextMessage(message); - - SpectatorVec spectators; - g_game.map.getSpectators(spectators, player->getPosition(), false, true); - spectators.erase(player); - if (!spectators.empty()) { - message.type = MESSAGE_HEALED_OTHERS; - message.text = player->getName() + " gained " + manaGainString + " mana."; - for (Creature* spectator : spectators) { - spectator->getPlayer()->sendTextMessage(message); - } - } + if (internalManaTicks >= manaTicks) { + internalManaTicks = 0; + if (Player* player = creature->getPlayer()) { + player->changeMana(manaGain); } } } @@ -838,12 +691,12 @@ bool ConditionRegeneration::setParam(ConditionParam_t param, int32_t value) } } -void ConditionSoul::addCondition(Creature*, const Condition* condition) +void ConditionSoul::addCondition(Creature*, const Condition* addCondition) { - if (updateCondition(condition)) { - setTicks(condition->getTicks()); + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); - const ConditionSoul& conditionSoul = static_cast(*condition); + const ConditionSoul& conditionSoul = static_cast(*addCondition); soulTicks = conditionSoul.soulTicks; soulGain = conditionSoul.soulGain; @@ -906,78 +759,62 @@ bool ConditionSoul::setParam(ConditionParam_t param, int32_t value) bool ConditionDamage::setParam(ConditionParam_t param, int32_t value) { - bool ret = Condition::setParam(param, value); - switch (param) { case CONDITION_PARAM_OWNER: owner = value; return true; - case CONDITION_PARAM_FORCEUPDATE: - forceUpdate = (value != 0); + case CONDITION_PARAM_CYCLE: + cycle = value; return true; - case CONDITION_PARAM_DELAYED: - delayed = (value != 0); + case CONDITION_PARAM_COUNT: + count = value; return true; - case CONDITION_PARAM_MAXVALUE: - maxDamage = std::abs(value); - break; + case CONDITION_PARAM_MAX_COUNT: + max_count = value; + return true; - case CONDITION_PARAM_MINVALUE: - minDamage = std::abs(value); - break; - - case CONDITION_PARAM_STARTVALUE: - startDamage = std::abs(value); - break; - - case CONDITION_PARAM_TICKINTERVAL: - tickInterval = std::abs(value); - break; - - case CONDITION_PARAM_PERIODICDAMAGE: - periodDamage = value; - break; - - case CONDITION_PARAM_FIELD: - field = (value != 0); - break; + case CONDITION_PARAM_HIT_DAMAGE: + hit_damage = value; + return true; default: return false; } - - return ret; } bool ConditionDamage::unserializeProp(ConditionAttr_t attr, PropStream& propStream) { - if (attr == CONDITIONATTR_DELAYED) { - uint8_t value; - if (!propStream.read(value)) { - return false; - } - - delayed = (value != 0); - return true; - } else if (attr == CONDITIONATTR_PERIODDAMAGE) { - return propStream.read(periodDamage); - } else if (attr == CONDITIONATTR_OWNER) { + if (attr == CONDITIONATTR_OWNER) { return propStream.skip(4); - } else if (attr == CONDITIONATTR_INTERVALDATA) { - IntervalInfo damageInfo; - if (!propStream.read(damageInfo)) { + } else if (attr == CONDITIONATTR_CYCLE) { + if (!propStream.read(cycle)) { return false; } - damageList.push_back(damageInfo); - if (ticks != -1) { - setTicks(ticks + damageInfo.interval); + return true; + } else if (attr == CONDITIONATTR_COUNT) { + if (!propStream.read(count)) { + return false; } + + return true; + } else if (attr == CONDITIONATTR_MAX_COUNT) { + if (!propStream.read(max_count)) { + return false; + } + + return true; + } else if (attr == CONDITIONATTR_FACTOR_PERCENT) { + if (!propStream.read(factor_percent)) { + return false; + } + return true; } + return Condition::unserializeProp(attr, propStream); } @@ -985,91 +822,23 @@ void ConditionDamage::serialize(PropWriteStream& propWriteStream) { Condition::serialize(propWriteStream); - propWriteStream.write(CONDITIONATTR_DELAYED); - propWriteStream.write(delayed); + propWriteStream.write(CONDITIONATTR_CYCLE); + propWriteStream.write(cycle); - propWriteStream.write(CONDITIONATTR_PERIODDAMAGE); - propWriteStream.write(periodDamage); + propWriteStream.write(CONDITIONATTR_COUNT); + propWriteStream.write(count); - for (const IntervalInfo& intervalInfo : damageList) { - propWriteStream.write(CONDITIONATTR_INTERVALDATA); - propWriteStream.write(intervalInfo); - } + propWriteStream.write(CONDITIONATTR_MAX_COUNT); + propWriteStream.write(max_count); + + propWriteStream.write(CONDITIONATTR_FACTOR_PERCENT); + propWriteStream.write(factor_percent); } bool ConditionDamage::updateCondition(const Condition* addCondition) { const ConditionDamage& conditionDamage = static_cast(*addCondition); - if (conditionDamage.doForceUpdate()) { - return true; - } - - if (ticks == -1 && conditionDamage.ticks > 0) { - return false; - } - - return conditionDamage.getTotalDamage() > getTotalDamage(); -} - -bool ConditionDamage::addDamage(int32_t rounds, int32_t time, int32_t value) -{ - time = std::max(time, EVENT_CREATURE_THINK_INTERVAL); - if (rounds == -1) { - //periodic damage - periodDamage = value; - setParam(CONDITION_PARAM_TICKINTERVAL, time); - setParam(CONDITION_PARAM_TICKS, -1); - return true; - } - - if (periodDamage > 0) { - return false; - } - - //rounds, time, damage - for (int32_t i = 0; i < rounds; ++i) { - IntervalInfo damageInfo; - damageInfo.interval = time; - damageInfo.timeLeft = time; - damageInfo.value = value; - - damageList.push_back(damageInfo); - - if (ticks != -1) { - setTicks(ticks + damageInfo.interval); - } - } - - return true; -} - -bool ConditionDamage::init() -{ - if (periodDamage != 0) { - return true; - } - - if (!damageList.empty()) { - return true; - } - - setTicks(0); - - int32_t amount = uniform_random(minDamage, maxDamage); - if (amount != 0) { - if (startDamage > maxDamage) { - startDamage = maxDamage; - } else if (startDamage == 0) { - startDamage = std::max(1, std::ceil(amount / 20.0)); - } - - std::list list; - ConditionDamage::generateDamageList(amount, startDamage, list); - for (int32_t value : list) { - addDamage(1, tickInterval, -value); - } - } - return !damageList.empty(); + return conditionDamage.getTotalDamage() >= getTotalDamage(); } bool ConditionDamage::startCondition(Creature* creature) @@ -1078,73 +847,112 @@ bool ConditionDamage::startCondition(Creature* creature) return false; } - if (!init()) { - return false; + creature->onAttacked(); + + setParam(CONDITION_PARAM_TICKINTERVAL, 1000); + + if (factor_percent == -1) { + factor_percent = 50; } - if (!delayed) { - int32_t damage; - if (getNextDamage(damage)) { - return doDamage(creature, damage); - } + if (factor_percent <= 9) { + factor_percent = 10; } + + if (factor_percent >= 1001) { + factor_percent = 1000; + } + + if (hit_damage) { + doDamage(creature, -hit_damage); + } + return true; } -bool ConditionDamage::executeCondition(Creature* creature, int32_t interval) +bool ConditionDamage::executeCondition(Creature* creature, int32_t) { - if (periodDamage != 0) { - periodDamageTick += interval; - - if (periodDamageTick >= tickInterval) { - periodDamageTick = 0; - doDamage(creature, periodDamage); + if (conditionType == CONDITION_FIRE) { + if (creature->isImmune(CONDITION_FIRE)) { + return false; } - } else if (!damageList.empty()) { - IntervalInfo& damageInfo = damageList.front(); - bool bRemove = (ticks != -1); - creature->onTickCondition(getType(), bRemove); - damageInfo.timeLeft -= interval; - - if (damageInfo.timeLeft <= 0) { - int32_t damage = damageInfo.value; - - if (bRemove) { - damageList.pop_front(); + const int32_t r_cycle = cycle; + if (r_cycle) { + if (count <= 0) { + count = max_count; + cycle = r_cycle + 2 * (r_cycle <= 0) - 1; + doDamage(creature, -10); } else { - damageInfo.timeLeft = damageInfo.interval; + --count; } - - doDamage(creature, damage); + } else { + return false; + } + } else if (conditionType == CONDITION_POISON) { + if (creature->isImmune(CONDITION_POISON)) { + return false; } - if (!bRemove) { - if (ticks > 0) { - endTime += interval; - } + const int32_t r_cycle = cycle; + if (r_cycle) { + if (count <= 0) { + count = max_count; + int32_t f = factor_percent * r_cycle / 1000; + if (!f) { + f = 2 * (r_cycle > 0) - 1; + } - interval = 0; + cycle = r_cycle - f; + doDamage(creature, -f); + } else { + --count; + } + } else { + return false; + } + } else if (conditionType == CONDITION_ENERGY) { + if (creature->isImmune(CONDITION_ENERGY)) { + return false; + } + + const int32_t r_cycle = cycle; + if (r_cycle) { + if (count <= 0) { + count = max_count; + cycle = r_cycle + 2 * (r_cycle <= 0) - 1; + doDamage(creature, -25); + } else { + --count; + } + } else { + return false; + } + } else if (conditionType == CONDITION_DROWN) { + if (isFirstCycle && count - max_count == -2) { + doDamage(creature, -20); + isFirstCycle = false; + count = max_count; + return true; + } + + const int32_t r_cycle = cycle; + if (r_cycle) { + if (count <= 0) { + count = max_count; + cycle = r_cycle + 2 * (r_cycle <= 0) - 1; + doDamage(creature, -20); + } + else { + --count; + } + } + else { + return false; } } - return Condition::executeCondition(creature, interval); -} - -bool ConditionDamage::getNextDamage(int32_t& damage) -{ - if (periodDamage != 0) { - damage = periodDamage; - return true; - } else if (!damageList.empty()) { - IntervalInfo& damageInfo = damageList.front(); - damage = damageInfo.value; - if (ticks != -1) { - damageList.pop_front(); - } - return true; - } - return false; + return true; } bool ConditionDamage::doDamage(Creature* creature, int32_t healthChange) @@ -1155,13 +963,10 @@ bool ConditionDamage::doDamage(Creature* creature, int32_t healthChange) CombatDamage damage; damage.origin = ORIGIN_CONDITION; - damage.primary.value = healthChange; - damage.primary.type = Combat::ConditionToDamageType(conditionType); + damage.value = healthChange; + damage.type = Combat::ConditionToDamageType(conditionType); Creature* attacker = g_game.getCreatureByID(owner); - if (field && creature->getPlayer() && attacker && attacker->getPlayer()) { - damage.primary.value = static_cast(std::round(damage.primary.value / 2.)); - } if (!creature->isAttackable() || Combat::canDoCombat(attacker, creature) != RETURNVALUE_NOERROR) { if (!creature->isInGhostMode()) { @@ -1170,7 +975,7 @@ bool ConditionDamage::doDamage(Creature* creature, int32_t healthChange) return false; } - if (g_game.combatBlockHit(damage, attacker, creature, false, false, field)) { + if (g_game.combatBlockHit(damage, attacker, creature, false, false, true)) { return false; } return g_game.combatChangeHealth(attacker, creature, damage); @@ -1181,64 +986,31 @@ void ConditionDamage::endCondition(Creature*) // } -void ConditionDamage::addCondition(Creature* creature, const Condition* condition) +void ConditionDamage::addCondition(Creature* creature, const Condition* addCondition) { - if (condition->getType() != conditionType) { + if (addCondition->getType() != conditionType) { return; } - if (!updateCondition(condition)) { + const ConditionDamage& conditionDamage = static_cast(*addCondition); + + if (hit_damage) { + doDamage(creature, -conditionDamage.hit_damage); + } + + if (!updateCondition(addCondition)) { return; } - const ConditionDamage& conditionDamage = static_cast(*condition); - - setTicks(condition->getTicks()); owner = conditionDamage.owner; - maxDamage = conditionDamage.maxDamage; - minDamage = conditionDamage.minDamage; - startDamage = conditionDamage.startDamage; - tickInterval = conditionDamage.tickInterval; - periodDamage = conditionDamage.periodDamage; - int32_t nextTimeLeft = tickInterval; - - if (!damageList.empty()) { - //save previous timeLeft - IntervalInfo& damageInfo = damageList.front(); - nextTimeLeft = damageInfo.timeLeft; - damageList.clear(); - } - - damageList = conditionDamage.damageList; - - if (init()) { - if (!damageList.empty()) { - //restore last timeLeft - IntervalInfo& damageInfo = damageList.front(); - damageInfo.timeLeft = nextTimeLeft; - } - - if (!delayed) { - int32_t damage; - if (getNextDamage(damage)) { - doDamage(creature, damage); - } - } - } + cycle = conditionDamage.cycle; + count = conditionDamage.count; + max_count = conditionDamage.max_count; } int32_t ConditionDamage::getTotalDamage() const { - int32_t result; - if (!damageList.empty()) { - result = 0; - for (const IntervalInfo& intervalInfo : damageList) { - result += intervalInfo.value; - } - } else { - result = minDamage + (maxDamage - minDamage) / 2; - } - return std::abs(result); + return cycle; } uint32_t ConditionDamage::getIcons() const @@ -1253,28 +1025,12 @@ uint32_t ConditionDamage::getIcons() const icons |= ICON_ENERGY; break; - case CONDITION_DROWN: - icons |= ICON_DROWNING; - break; - case CONDITION_POISON: icons |= ICON_POISON; break; - case CONDITION_FREEZING: - icons |= ICON_FREEZING; - break; - - case CONDITION_DAZZLED: - icons |= ICON_DAZZLED; - break; - - case CONDITION_CURSED: - icons |= ICON_CURSED; - break; - - case CONDITION_BLEEDING: - icons |= ICON_BLEEDING; + case CONDITION_DROWN: + icons |= ICON_DROWNING; break; default: @@ -1283,69 +1039,12 @@ uint32_t ConditionDamage::getIcons() const return icons; } -void ConditionDamage::generateDamageList(int32_t amount, int32_t start, std::list& list) -{ - amount = std::abs(amount); - int32_t sum = 0; - double x1, x2; - - for (int32_t i = start; i > 0; --i) { - int32_t n = start + 1 - i; - int32_t med = (n * amount) / start; - - do { - sum += i; - list.push_back(i); - - x1 = std::fabs(1.0 - ((static_cast(sum)) + i) / med); - x2 = std::fabs(1.0 - (static_cast(sum) / med)); - } while (x1 < x2); - } -} - -void ConditionSpeed::setFormulaVars(float mina, float minb, float maxa, float maxb) -{ - this->mina = mina; - this->minb = minb; - this->maxa = maxa; - this->maxb = maxb; -} - -void ConditionSpeed::getFormulaValues(int32_t var, int32_t& min, int32_t& max) const -{ - min = (var * mina) + minb; - max = (var * maxa) + maxb; -} - -bool ConditionSpeed::setParam(ConditionParam_t param, int32_t value) -{ - Condition::setParam(param, value); - if (param != CONDITION_PARAM_SPEED) { - return false; - } - - speedDelta = value; - - if (value > 0) { - conditionType = CONDITION_HASTE; - } else { - conditionType = CONDITION_PARALYZE; - } - return true; -} - bool ConditionSpeed::unserializeProp(ConditionAttr_t attr, PropStream& propStream) { if (attr == CONDITIONATTR_SPEEDDELTA) { return propStream.read(speedDelta); - } else if (attr == CONDITIONATTR_FORMULA_MINA) { - return propStream.read(mina); - } else if (attr == CONDITIONATTR_FORMULA_MINB) { - return propStream.read(minb); - } else if (attr == CONDITIONATTR_FORMULA_MAXA) { - return propStream.read(maxa); - } else if (attr == CONDITIONATTR_FORMULA_MAXB) { - return propStream.read(maxb); + } else if (attr == CONDITIONATTR_APPLIEDSPEEDDELTA) { + return propStream.read(appliedSpeedDelta); } return Condition::unserializeProp(attr, propStream); } @@ -1357,17 +1056,8 @@ void ConditionSpeed::serialize(PropWriteStream& propWriteStream) propWriteStream.write(CONDITIONATTR_SPEEDDELTA); propWriteStream.write(speedDelta); - propWriteStream.write(CONDITIONATTR_FORMULA_MINA); - propWriteStream.write(mina); - - propWriteStream.write(CONDITIONATTR_FORMULA_MINB); - propWriteStream.write(minb); - - propWriteStream.write(CONDITIONATTR_FORMULA_MAXA); - propWriteStream.write(maxa); - - propWriteStream.write(CONDITIONATTR_FORMULA_MAXB); - propWriteStream.write(maxb); + propWriteStream.write(CONDITIONATTR_APPLIEDSPEEDDELTA); + propWriteStream.write(appliedSpeedDelta); } bool ConditionSpeed::startCondition(Creature* creature) @@ -1376,10 +1066,18 @@ bool ConditionSpeed::startCondition(Creature* creature) return false; } - if (speedDelta == 0) { - int32_t min, max; - getFormulaValues(creature->getBaseSpeed(), min, max); - speedDelta = uniform_random(min, max); + if (appliedSpeedDelta == 0) { + speedDelta = normal_random(-variation, variation) + speedDelta; + + if (speedDelta >= -100) { + speedDelta = static_cast(creature->getBaseSpeed()) * speedDelta / 100; + } else { + speedDelta = -20 - creature->getBaseSpeed(); + } + + appliedSpeedDelta = speedDelta; + } else { + speedDelta = appliedSpeedDelta; } g_game.changeSpeed(creature, speedDelta); @@ -1393,40 +1091,41 @@ bool ConditionSpeed::executeCondition(Creature* creature, int32_t interval) void ConditionSpeed::endCondition(Creature* creature) { - g_game.changeSpeed(creature, -speedDelta); + g_game.changeSpeed(creature, -appliedSpeedDelta); } -void ConditionSpeed::addCondition(Creature* creature, const Condition* condition) +void ConditionSpeed::addCondition(Creature* creature, const Condition* addCondition) { - if (conditionType != condition->getType()) { + if (conditionType != addCondition->getType()) { return; } - if (ticks == -1 && condition->getTicks() > 0) { + if (ticks == -1 && addCondition->getTicks() > 0) { return; } - setTicks(condition->getTicks()); + const ConditionSpeed& conditionSpeed = static_cast(*addCondition); - const ConditionSpeed& conditionSpeed = static_cast(*condition); - int32_t oldSpeedDelta = speedDelta; - speedDelta = conditionSpeed.speedDelta; - mina = conditionSpeed.mina; - maxa = conditionSpeed.maxa; - minb = conditionSpeed.minb; - maxb = conditionSpeed.maxb; + int32_t newVariation = conditionSpeed.variation; + int32_t newSpeedDelta = conditionSpeed.speedDelta; - if (speedDelta == 0) { - int32_t min; - int32_t max; - getFormulaValues(creature->getBaseSpeed(), min, max); - speedDelta = uniform_random(min, max); + newSpeedDelta = normal_random(-newVariation, newVariation) + newSpeedDelta; + + // update ticks + setTicks(addCondition->getTicks()); + + if (newSpeedDelta >= -100) { + newSpeedDelta = static_cast(creature->getBaseSpeed()) * newSpeedDelta / 100; + } else { + newSpeedDelta = -20 - creature->getBaseSpeed(); } - int32_t newSpeedChange = (speedDelta - oldSpeedDelta); - if (newSpeedChange != 0) { - g_game.changeSpeed(creature, newSpeedChange); - } + creature->setSpeed(-appliedSpeedDelta); + + appliedSpeedDelta = newSpeedDelta; + speedDelta = newSpeedDelta; + + g_game.changeSpeed(creature, newSpeedDelta); } uint32_t ConditionSpeed::getIcons() const @@ -1505,12 +1204,12 @@ void ConditionOutfit::endCondition(Creature* creature) g_game.internalCreatureChangeOutfit(creature, creature->getDefaultOutfit()); } -void ConditionOutfit::addCondition(Creature* creature, const Condition* condition) +void ConditionOutfit::addCondition(Creature* creature, const Condition* addCondition) { - if (updateCondition(condition)) { - setTicks(condition->getTicks()); + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); - const ConditionOutfit& conditionOutfit = static_cast(*condition); + const ConditionOutfit& conditionOutfit = static_cast(*addCondition); outfit = conditionOutfit.outfit; g_game.internalCreatureChangeOutfit(creature, outfit); @@ -1536,11 +1235,12 @@ bool ConditionLight::executeCondition(Creature* creature, int32_t interval) if (internalLightTicks >= lightChangeInterval) { internalLightTicks = 0; - LightInfo lightInfo = creature->getCreatureLight(); + LightInfo creatureLight; + creature->getCreatureLight(creatureLight); - if (lightInfo.level > 0) { - --lightInfo.level; - creature->setCreatureLight(lightInfo); + if (creatureLight.level > 0) { + --creatureLight.level; + creature->setCreatureLight(creatureLight); g_game.changeLight(creature); } } @@ -1554,12 +1254,12 @@ void ConditionLight::endCondition(Creature* creature) g_game.changeLight(creature); } -void ConditionLight::addCondition(Creature* creature, const Condition* condition) +void ConditionLight::addCondition(Creature* creature, const Condition* addCondition) { - if (updateCondition(condition)) { - setTicks(condition->getTicks()); + if (updateCondition(addCondition)) { + setTicks(addCondition->getTicks()); - const ConditionLight& conditionLight = static_cast(*condition); + const ConditionLight& conditionLight = static_cast(*addCondition); lightInfo.level = conditionLight.lightInfo.level; lightInfo.color = conditionLight.lightInfo.color; lightChangeInterval = ticks / lightInfo.level; @@ -1635,61 +1335,3 @@ void ConditionLight::serialize(PropWriteStream& propWriteStream) propWriteStream.write(CONDITIONATTR_LIGHTINTERVAL); propWriteStream.write(lightChangeInterval); } - -void ConditionSpellCooldown::addCondition(Creature* creature, const Condition* condition) -{ - if (updateCondition(condition)) { - setTicks(condition->getTicks()); - - if (subId != 0 && ticks > 0) { - Player* player = creature->getPlayer(); - if (player) { - player->sendSpellCooldown(subId, ticks); - } - } - } -} - -bool ConditionSpellCooldown::startCondition(Creature* creature) -{ - if (!Condition::startCondition(creature)) { - return false; - } - - if (subId != 0 && ticks > 0) { - Player* player = creature->getPlayer(); - if (player) { - player->sendSpellCooldown(subId, ticks); - } - } - return true; -} - -void ConditionSpellGroupCooldown::addCondition(Creature* creature, const Condition* condition) -{ - if (updateCondition(condition)) { - setTicks(condition->getTicks()); - - if (subId != 0 && ticks > 0) { - Player* player = creature->getPlayer(); - if (player) { - player->sendSpellGroupCooldown(static_cast(subId), ticks); - } - } - } -} - -bool ConditionSpellGroupCooldown::startCondition(Creature* creature) -{ - if (!Condition::startCondition(creature)) { - return false; - } - - if (subId != 0 && ticks > 0) { - Player* player = creature->getPlayer(); - if (player) { - player->sendSpellGroupCooldown(static_cast(subId), ticks); - } - } - return true; -} diff --git a/src/condition.h b/src/condition.h index cc99c5e..ed33877 100644 --- a/src/condition.h +++ b/src/condition.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,10 +35,13 @@ enum ConditionAttr_t { CONDITIONATTR_HEALTHGAIN, CONDITIONATTR_MANATICKS, CONDITIONATTR_MANAGAIN, - CONDITIONATTR_DELAYED, CONDITIONATTR_OWNER, - CONDITIONATTR_INTERVALDATA, + CONDITIONATTR_CYCLE, + CONDITIONATTR_COUNT, + CONDITIONATTR_MAX_COUNT, + CONDITIONATTR_FACTOR_PERCENT, CONDITIONATTR_SPEEDDELTA, + CONDITIONATTR_APPLIEDSPEEDDELTA, CONDITIONATTR_FORMULA_MINA, CONDITIONATTR_FORMULA_MINB, CONDITIONATTR_FORMULA_MAXA, @@ -52,8 +55,6 @@ enum ConditionAttr_t { CONDITIONATTR_SKILLS, CONDITIONATTR_STATS, CONDITIONATTR_OUTFIT, - CONDITIONATTR_PERIODDAMAGE, - CONDITIONATTR_ISBUFF, CONDITIONATTR_SUBID, //reserved for serialization @@ -70,9 +71,9 @@ class Condition { public: Condition() = default; - Condition(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0) : + Condition(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0) : endTime(ticks == -1 ? std::numeric_limits::max() : 0), - subId(subId), ticks(ticks), conditionType(type), isBuff(buff), id(id) {} + subId(subId), ticks(ticks), conditionType(type), id(id) {} virtual ~Condition() = default; virtual bool startCondition(Creature* creature); @@ -100,7 +101,7 @@ class Condition } void setTicks(int32_t newTicks); - static Condition* createCondition(ConditionId_t id, ConditionType_t type, int32_t ticks, int32_t param = 0, bool buff = false, uint32_t subId = 0); + 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); @@ -113,23 +114,20 @@ class Condition bool isPersistent() const; protected: - virtual bool updateCondition(const Condition* addCondition); - int64_t endTime; uint32_t subId; int32_t ticks; ConditionType_t conditionType; - bool isBuff; - - private: ConditionId_t id; + + virtual bool updateCondition(const Condition* addCondition); }; class ConditionGeneric : public Condition { public: - ConditionGeneric(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0): - Condition(id, type, ticks, buff, subId) {} + 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; @@ -145,35 +143,32 @@ class ConditionGeneric : public Condition class ConditionAttributes final : public ConditionGeneric { public: - ConditionAttributes(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0) : - ConditionGeneric(id, type, ticks, buff, subId) {} + ConditionAttributes(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0) : + ConditionGeneric(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; + 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) override; + bool setParam(ConditionParam_t param, int32_t value) final; - ConditionAttributes* clone() const override { + ConditionAttributes* clone() const final { return new ConditionAttributes(*this); } //serialization - void serialize(PropWriteStream& propWriteStream) override; - bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; + void serialize(PropWriteStream& propWriteStream) final; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; - private: + protected: int32_t skills[SKILL_LAST + 1] = {}; int32_t skillsPercent[SKILL_LAST + 1] = {}; - int32_t specialSkills[SPECIALSKILL_LAST + 1] = {}; int32_t stats[STAT_LAST + 1] = {}; int32_t statsPercent[STAT_LAST + 1] = {}; int32_t currentSkill = 0; int32_t currentStat = 0; - bool disableDefense = false; - void updatePercentStats(Player* player); void updateStats(Player* player); void updatePercentSkills(Player* player); @@ -183,23 +178,23 @@ class ConditionAttributes final : public ConditionGeneric class ConditionRegeneration final : public ConditionGeneric { public: - ConditionRegeneration(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0): - ConditionGeneric(id, type, ticks, buff, subId) {} + 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* condition) override; - bool executeCondition(Creature* creature, int32_t interval) override; + void addCondition(Creature* creature, const Condition* addCondition) final; + bool executeCondition(Creature* creature, int32_t interval) final; - bool setParam(ConditionParam_t param, int32_t value) override; + bool setParam(ConditionParam_t param, int32_t value) final; - ConditionRegeneration* clone() const override { + ConditionRegeneration* clone() const final { return new ConditionRegeneration(*this); } //serialization - void serialize(PropWriteStream& propWriteStream) override; - bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; + void serialize(PropWriteStream& propWriteStream) final; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; - private: + protected: uint32_t internalHealthTicks = 0; uint32_t internalManaTicks = 0; @@ -212,23 +207,23 @@ class ConditionRegeneration final : public ConditionGeneric class ConditionSoul final : public ConditionGeneric { public: - ConditionSoul(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0) : - ConditionGeneric(id, type, ticks, buff, subId) {} + 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* condition) override; - bool executeCondition(Creature* creature, int32_t interval) override; + void addCondition(Creature* creature, const Condition* addCondition) final; + bool executeCondition(Creature* creature, int32_t interval) final; - bool setParam(ConditionParam_t param, int32_t value) override; + bool setParam(ConditionParam_t param, int32_t value) final; - ConditionSoul* clone() const override { + ConditionSoul* clone() const final { return new ConditionSoul(*this); } //serialization - void serialize(PropWriteStream& propWriteStream) override; - bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; + void serialize(PropWriteStream& propWriteStream) final; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; - private: + protected: uint32_t internalSoulTicks = 0; uint32_t soulTicks = 0; uint32_t soulGain = 0; @@ -237,13 +232,13 @@ class ConditionSoul final : public ConditionGeneric class ConditionInvisible final : public ConditionGeneric { public: - ConditionInvisible(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0) : - ConditionGeneric(id, type, ticks, buff, subId) {} + ConditionInvisible(ConditionId_t id, ConditionType_t type, int32_t ticks, uint32_t subId = 0) : + ConditionGeneric(id, type, ticks, subId) {} - bool startCondition(Creature* creature) override; - void endCondition(Creature* creature) override; + bool startCondition(Creature* creature) final; + void endCondition(Creature* creature) final; - ConditionInvisible* clone() const override { + ConditionInvisible* clone() const final { return new ConditionInvisible(*this); } }; @@ -252,170 +247,133 @@ class ConditionDamage final : public Condition { public: ConditionDamage() = default; - ConditionDamage(ConditionId_t id, ConditionType_t type, bool buff = false, uint32_t subId = 0) : - Condition(id, type, 0, buff, subId) {} + 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; + } else if (type == CONDITION_DROWN) { + count = max_count = 3; + } + } - static void generateDamageList(int32_t amount, int32_t start, std::list& list); + 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; - 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; - - ConditionDamage* clone() const override { + ConditionDamage* clone() const final { return new ConditionDamage(*this); } - bool setParam(ConditionParam_t param, int32_t value) override; + bool setParam(ConditionParam_t param, int32_t value) final; - bool addDamage(int32_t rounds, int32_t time, int32_t value); - bool doForceUpdate() const { - return forceUpdate; - } int32_t getTotalDamage() const; //serialization - void serialize(PropWriteStream& propWriteStream) override; - bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; - - private: - int32_t maxDamage = 0; - int32_t minDamage = 0; - int32_t startDamage = 0; - int32_t periodDamage = 0; - int32_t periodDamageTick = 0; - int32_t tickInterval = 2000; - - bool forceUpdate = false; - bool delayed = false; - bool field = false; + 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; + bool isFirstCycle = true; uint32_t owner = 0; - bool init(); - - std::list damageList; - - bool getNextDamage(int32_t& damage); bool doDamage(Creature* creature, int32_t healthChange); - bool updateCondition(const Condition* addCondition) override; + bool updateCondition(const Condition* addCondition) final; }; class ConditionSpeed final : public Condition { public: - ConditionSpeed(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff, uint32_t subId, int32_t changeSpeed) : - Condition(id, type, ticks, buff, subId), speedDelta(changeSpeed) {} + 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) 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; + 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 override { + ConditionSpeed* clone() const final { return new ConditionSpeed(*this); } - bool setParam(ConditionParam_t param, int32_t value) override; - - void setFormulaVars(float mina, float minb, float maxa, float maxb); + void setVariation(int32_t newVariation) { + variation = newVariation; + } + void setSpeedDelta(int32_t newSpeedDelta) { + speedDelta = newSpeedDelta; + } //serialization - void serialize(PropWriteStream& propWriteStream) override; - bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; + void serialize(PropWriteStream& propWriteStream) final; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; - private: - void getFormulaValues(int32_t var, int32_t& min, int32_t& max) const; - - int32_t speedDelta; - - //formula variables - float mina = 0.0f; - float minb = 0.0f; - float maxa = 0.0f; - float maxb = 0.0f; + 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, bool buff = false, uint32_t subId = 0) : - Condition(id, type, ticks, buff, subId) {} + ConditionOutfit(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; + 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 override { + ConditionOutfit* clone() const final { return new ConditionOutfit(*this); } void setOutfit(const Outfit_t& outfit); //serialization - void serialize(PropWriteStream& propWriteStream) override; - bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; + void serialize(PropWriteStream& propWriteStream) final; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; - private: + protected: Outfit_t outfit; }; class ConditionLight final : public Condition { public: - ConditionLight(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff, uint32_t subId, uint8_t lightlevel, uint8_t lightcolor) : - Condition(id, type, ticks, buff, subId), lightInfo(lightlevel, lightcolor) {} + 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) override; - bool executeCondition(Creature* creature, int32_t interval) override; - void endCondition(Creature* creature) override; - void addCondition(Creature* creature, const Condition* condition) override; + 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 override { + ConditionLight* clone() const final { return new ConditionLight(*this); } - bool setParam(ConditionParam_t param, int32_t value) override; + bool setParam(ConditionParam_t param, int32_t value) final; //serialization - void serialize(PropWriteStream& propWriteStream) override; - bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) override; + void serialize(PropWriteStream& propWriteStream) final; + bool unserializeProp(ConditionAttr_t attr, PropStream& propStream) final; - private: + protected: LightInfo lightInfo; uint32_t internalLightTicks = 0; uint32_t lightChangeInterval = 0; }; -class ConditionSpellCooldown final : public ConditionGeneric -{ - public: - ConditionSpellCooldown(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0) : - ConditionGeneric(id, type, ticks, buff, subId) {} - - bool startCondition(Creature* creature) override; - void addCondition(Creature* creature, const Condition* condition) override; - - ConditionSpellCooldown* clone() const override { - return new ConditionSpellCooldown(*this); - } -}; - -class ConditionSpellGroupCooldown final : public ConditionGeneric -{ - public: - ConditionSpellGroupCooldown(ConditionId_t id, ConditionType_t type, int32_t ticks, bool buff = false, uint32_t subId = 0) : - ConditionGeneric(id, type, ticks, buff, subId) {} - - bool startCondition(Creature* creature) override; - void addCondition(Creature* creature, const Condition* condition) override; - - ConditionSpellGroupCooldown* clone() const override { - return new ConditionSpellGroupCooldown(*this); - } -}; - #endif diff --git a/src/configmanager.cpp b/src/configmanager.cpp index 3b41920..6610b74 100644 --- a/src/configmanager.cpp +++ b/src/configmanager.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,12 +19,6 @@ #include "otpch.h" -#if __has_include("luajit/lua.hpp") -#include -#else -#include -#endif - #include "configmanager.h" #include "game.h" @@ -35,57 +29,6 @@ extern Game g_game; -namespace { - -std::string getGlobalString(lua_State* L, const char* identifier, const char* defaultValue) -{ - lua_getglobal(L, identifier); - if (!lua_isstring(L, -1)) { - lua_pop(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 getGlobalNumber(lua_State* L, const char* identifier, const int32_t defaultValue = 0) -{ - lua_getglobal(L, identifier); - if (!lua_isnumber(L, -1)) { - lua_pop(L, 1); - return defaultValue; - } - - int32_t val = lua_tonumber(L, -1); - lua_pop(L, 1); - return val; -} - -bool getGlobalBoolean(lua_State* L, const char* identifier, const bool defaultValue) -{ - lua_getglobal(L, identifier); - if (!lua_isboolean(L, -1)) { - if (!lua_isstring(L, -1)) { - lua_pop(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; -} - -} - bool ConfigManager::load() { lua_State* L = luaL_newstate(); @@ -120,10 +63,9 @@ bool ConfigManager::load() integer[GAME_PORT] = getGlobalNumber(L, "gameProtocolPort", 7172); integer[LOGIN_PORT] = getGlobalNumber(L, "loginProtocolPort", 7171); integer[STATUS_PORT] = getGlobalNumber(L, "statusProtocolPort", 7171); - - integer[MARKET_OFFER_DURATION] = getGlobalNumber(L, "marketOfferDuration", 30 * 24 * 60 * 60); } + 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[AIMBOT_HOTKEY_ENABLED] = getGlobalBoolean(L, "hotkeyAimbotEnabled", true); @@ -132,19 +74,14 @@ bool ConfigManager::load() boolean[FREE_PREMIUM] = getGlobalBoolean(L, "freePremium", false); boolean[REPLACE_KICK_ON_LOGIN] = getGlobalBoolean(L, "replaceKickOnLogin", true); boolean[ALLOW_CLONES] = getGlobalBoolean(L, "allowClones", false); - boolean[MARKET_PREMIUM] = getGlobalBoolean(L, "premiumToCreateMarketOffer", true); - boolean[EMOTE_SPELLS] = getGlobalBoolean(L, "emoteSpells", false); boolean[STAMINA_SYSTEM] = getGlobalBoolean(L, "staminaSystem", true); boolean[WARN_UNSAFE_SCRIPTS] = getGlobalBoolean(L, "warnUnsafeScripts", true); boolean[CONVERT_UNSAFE_SCRIPTS] = getGlobalBoolean(L, "convertUnsafeScripts", true); - boolean[CLASSIC_EQUIPMENT_SLOTS] = getGlobalBoolean(L, "classicEquipmentSlots", false); - boolean[CLASSIC_ATTACK_SPEED] = getGlobalBoolean(L, "classicAttackSpeed", false); - boolean[SCRIPTS_CONSOLE_LOGS] = getGlobalBoolean(L, "showScriptsLogInConsole", true); - boolean[SERVER_SAVE_NOTIFY_MESSAGE] = getGlobalBoolean(L, "serverSaveNotifyMessage", true); - boolean[SERVER_SAVE_CLEAN_MAP] = getGlobalBoolean(L, "serverSaveCleanMap", false); - boolean[SERVER_SAVE_CLOSE] = getGlobalBoolean(L, "serverSaveClose", false); - boolean[SERVER_SAVE_SHUTDOWN] = getGlobalBoolean(L, "serverSaveShutdown", true); - boolean[ONLINE_OFFLINE_CHARLIST] = getGlobalBoolean(L, "showOnlineStatusInCharlist", false); + boolean[TELEPORT_NEWBIES] = getGlobalBoolean(L, "teleportNewbies", true); + boolean[STACK_CUMULATIVES] = getGlobalBoolean(L, "autoStackCumulatives", false); + boolean[BLOCK_HEIGHT] = getGlobalBoolean(L, "blockHeight", false); + boolean[DROP_ITEMS] = getGlobalBoolean(L, "dropItems", false); + string[DEFAULT_PRIORITY] = getGlobalString(L, "defaultPriority", "high"); string[SERVER_NAME] = getGlobalString(L, "serverName", ""); @@ -164,9 +101,7 @@ bool ConfigManager::load() integer[RATE_LOOT] = getGlobalNumber(L, "rateLoot", 2); integer[RATE_MAGIC] = getGlobalNumber(L, "rateMagic", 3); integer[RATE_SPAWN] = getGlobalNumber(L, "rateSpawn", 1); - integer[HOUSE_PRICE] = getGlobalNumber(L, "housePriceEachSQM", 1000); - integer[KILLS_TO_RED] = getGlobalNumber(L, "killsToRedSkull", 3); - integer[KILLS_TO_BLACK] = getGlobalNumber(L, "killsToBlackSkull", 6); + 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); @@ -174,14 +109,20 @@ bool ConfigManager::load() integer[PROTECTION_LEVEL] = getGlobalNumber(L, "protectionLevel", 1); integer[DEATH_LOSE_PERCENT] = getGlobalNumber(L, "deathLosePercent", -1); integer[STATUSQUERY_TIMEOUT] = getGlobalNumber(L, "statusTimeout", 5000); - integer[FRAG_TIME] = getGlobalNumber(L, "timeToDecreaseFrags", 24 * 60 * 60 * 1000); - integer[WHITE_SKULL_TIME] = getGlobalNumber(L, "whiteSkullTime", 15 * 60 * 1000); + 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[CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES] = getGlobalNumber(L, "checkExpiredMarketOffersEachMinutes", 60); - integer[MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER] = getGlobalNumber(L, "maxMarketOffersAtATimePerPlayer", 100); integer[MAX_PACKETS_PER_SECOND] = getGlobalNumber(L, "maxPacketsPerSecond", 25); - integer[SERVER_SAVE_NOTIFY_DURATION] = getGlobalNumber(L, "serverSaveNotifyDuration", 5); + 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); @@ -197,13 +138,11 @@ bool ConfigManager::reload() return result; } -static std::string dummyStr; - 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 dummyStr; + return string[DUMMY_STR]; } return string[what]; } @@ -225,3 +164,47 @@ bool ConfigManager::getBoolean(boolean_config_t what) const } 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; +} diff --git a/src/configmanager.h b/src/configmanager.h index 0b47566..2d7b0db 100644 --- a/src/configmanager.h +++ b/src/configmanager.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,10 +20,13 @@ #ifndef FS_CONFIGMANAGER_H_6BDD23BD0B8344F4B7C40E8BE6AF6F39 #define FS_CONFIGMANAGER_H_6BDD23BD0B8344F4B7C40E8BE6AF6F39 +#include + class ConfigManager { public: enum boolean_config_t { + SHOW_MONSTER_LOOT, ALLOW_CHANGEOUTFIT, ONE_PLAYER_ON_ACCOUNT, AIMBOT_HOTKEY_ENABLED, @@ -34,24 +37,19 @@ class ConfigManager ALLOW_CLONES, BIND_ONLY_GLOBAL_ADDRESS, OPTIMIZE_DATABASE, - MARKET_PREMIUM, - EMOTE_SPELLS, STAMINA_SYSTEM, WARN_UNSAFE_SCRIPTS, CONVERT_UNSAFE_SCRIPTS, - CLASSIC_EQUIPMENT_SLOTS, - CLASSIC_ATTACK_SPEED, - SCRIPTS_CONSOLE_LOGS, - SERVER_SAVE_NOTIFY_MESSAGE, - SERVER_SAVE_CLEAN_MAP, - SERVER_SAVE_CLOSE, - SERVER_SAVE_SHUTDOWN, - ONLINE_OFFLINE_CHARLIST, + TELEPORT_NEWBIES, + STACK_CUMULATIVES, + BLOCK_HEIGHT, + DROP_ITEMS, LAST_BOOLEAN_CONFIG /* this must be the last one */ }; enum string_config_t { + DUMMY_STR, MAP_NAME, HOUSE_RENT_PERIOD, SERVER_NAME, @@ -84,9 +82,7 @@ class ConfigManager RATE_LOOT, RATE_MAGIC, RATE_SPAWN, - HOUSE_PRICE, - KILLS_TO_RED, - KILLS_TO_BLACK, + BAN_LENGTH, MAX_MESSAGEBUFFER, ACTIONS_DELAY_INTERVAL, EX_ACTIONS_DELAY_INTERVAL, @@ -94,18 +90,23 @@ class ConfigManager PROTECTION_LEVEL, DEATH_LOSE_PERCENT, STATUSQUERY_TIMEOUT, - FRAG_TIME, 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, - MARKET_OFFER_DURATION, - CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES, - MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER, EXP_FROM_PLAYERS_LEVEL_RANGE, MAX_PACKETS_PER_SECOND, - SERVER_SAVE_NOTIFY_DURATION, + NEWBIE_TOWN, + NEWBIE_LEVEL_THRESHOLD, + MONEY_RATE, LAST_INTEGER_CONFIG /* this must be the last one */ }; @@ -118,6 +119,10 @@ class ConfigManager 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] = {}; diff --git a/src/connection.cpp b/src/connection.cpp index 2224742..dce33eb 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -182,60 +182,51 @@ void Connection::parsePacket(const boost::system::error_code& error) if (error) { close(FORCE_CLOSE); return; - } else if (connectionState != CONNECTION_STATE_OPEN) { + } + else if (connectionState != CONNECTION_STATE_OPEN) { return; } - //Check packet checksum - uint32_t checksum; - int32_t len = msg.getLength() - msg.getBufferPosition() - NetworkMessage::CHECKSUM_LENGTH; - if (len > 0) { - checksum = adlerChecksum(msg.getBuffer() + msg.getBufferPosition() + NetworkMessage::CHECKSUM_LENGTH, len); - } else { - checksum = 0; - } - - uint32_t recvChecksum = msg.get(); - if (recvChecksum != checksum) { - // it might not have been the checksum, step back - msg.skipBytes(-NetworkMessage::CHECKSUM_LENGTH); - } - if (!receivedFirst) { // First message received receivedFirst = true; if (!protocol) { // Game protocol has already been created at this point - protocol = service_port->make_protocol(recvChecksum == checksum, msg, shared_from_this()); + protocol = service_port->make_protocol(msg, shared_from_this()); if (!protocol) { close(FORCE_CLOSE); return; } - } else { + } + else { msg.skipBytes(1); // Skip protocol ID } protocol->onRecvFirstMessage(msg); - } else { + } + 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(shared_from_this()), - std::placeholders::_1)); + 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) { + 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 lockClass(connectionLock); diff --git a/src/connection.h b/src/connection.h index 36dc24d..b885749 100644 --- a/src/connection.h +++ b/src/connection.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,17 +28,17 @@ static constexpr int32_t CONNECTION_WRITE_TIMEOUT = 30; static constexpr int32_t CONNECTION_READ_TIMEOUT = 30; class Protocol; -using Protocol_ptr = std::shared_ptr; +typedef std::shared_ptr Protocol_ptr; class OutputMessage; -using OutputMessage_ptr = std::shared_ptr; +typedef std::shared_ptr OutputMessage_ptr; class Connection; -using Connection_ptr = std::shared_ptr ; -using ConnectionWeak_ptr = std::weak_ptr; +typedef std::shared_ptr Connection_ptr; +typedef std::weak_ptr ConnectionWeak_ptr; class ServiceBase; -using Service_ptr = std::shared_ptr; +typedef std::shared_ptr Service_ptr; class ServicePort; -using ServicePort_ptr = std::shared_ptr; -using ConstServicePort_ptr = std::shared_ptr; +typedef std::shared_ptr ServicePort_ptr; +typedef std::shared_ptr ConstServicePort_ptr; class ConnectionManager { @@ -52,7 +52,7 @@ class ConnectionManager void releaseConnection(const Connection_ptr& connection); void closeAll(); - private: + protected: ConnectionManager() = default; std::unordered_set connections; @@ -78,8 +78,12 @@ class Connection : public std::enable_shared_from_this readTimer(io_service), writeTimer(io_service), service_port(std::move(service_port)), - socket(io_service), - timeConnected(time(nullptr)) {} + socket(io_service) { + connectionState = CONNECTION_STATE_OPEN; + receivedFirst = false; + packetsSent = 0; + timeConnected = time(nullptr); + } ~Connection(); friend class ConnectionManager; @@ -124,10 +128,10 @@ class Connection : public std::enable_shared_from_this boost::asio::ip::tcp::socket socket; time_t timeConnected; - uint32_t packetsSent = 0; + uint32_t packetsSent; - bool connectionState = CONNECTION_STATE_OPEN; - bool receivedFirst = false; + bool connectionState; + bool receivedFirst; }; #endif diff --git a/src/const.h b/src/const.h index 412669a..c4dffb2 100644 --- a/src/const.h +++ b/src/const.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -52,67 +52,6 @@ enum MagicEffectClasses : uint8_t { CONST_ME_SOUND_WHITE = 25, CONST_ME_BUBBLES = 26, CONST_ME_CRAPS = 27, - CONST_ME_GIFT_WRAPS = 28, - CONST_ME_FIREWORK_YELLOW = 29, - CONST_ME_FIREWORK_RED = 30, - CONST_ME_FIREWORK_BLUE = 31, - CONST_ME_STUN = 32, - CONST_ME_SLEEP = 33, - CONST_ME_WATERCREATURE = 34, - CONST_ME_GROUNDSHAKER = 35, - CONST_ME_HEARTS = 36, - CONST_ME_FIREATTACK = 37, - CONST_ME_ENERGYAREA = 38, - CONST_ME_SMALLCLOUDS = 39, - CONST_ME_HOLYDAMAGE = 40, - CONST_ME_BIGCLOUDS = 41, - CONST_ME_ICEAREA = 42, - CONST_ME_ICETORNADO = 43, - CONST_ME_ICEATTACK = 44, - CONST_ME_STONES = 45, - CONST_ME_SMALLPLANTS = 46, - CONST_ME_CARNIPHILA = 47, - CONST_ME_PURPLEENERGY = 48, - CONST_ME_YELLOWENERGY = 49, - CONST_ME_HOLYAREA = 50, - CONST_ME_BIGPLANTS = 51, - CONST_ME_CAKE = 52, - CONST_ME_GIANTICE = 53, - CONST_ME_WATERSPLASH = 54, - CONST_ME_PLANTATTACK = 55, - CONST_ME_TUTORIALARROW = 56, - CONST_ME_TUTORIALSQUARE = 57, - CONST_ME_MIRRORHORIZONTAL = 58, - CONST_ME_MIRRORVERTICAL = 59, - CONST_ME_SKULLHORIZONTAL = 60, - CONST_ME_SKULLVERTICAL = 61, - CONST_ME_ASSASSIN = 62, - CONST_ME_STEPSHORIZONTAL = 63, - CONST_ME_BLOODYSTEPS = 64, - CONST_ME_STEPSVERTICAL = 65, - CONST_ME_YALAHARIGHOST = 66, - CONST_ME_BATS = 67, - CONST_ME_SMOKE = 68, - CONST_ME_INSECTS = 69, - CONST_ME_DRAGONHEAD = 70, - CONST_ME_ORCSHAMAN = 71, - CONST_ME_ORCSHAMAN_FIRE = 72, - CONST_ME_THUNDER = 73, - CONST_ME_FERUMBRAS = 74, - CONST_ME_CONFETTI_HORIZONTAL = 75, - CONST_ME_CONFETTI_VERTICAL = 76, - // 77-157 are empty - CONST_ME_BLACKSMOKE = 158, - // 159-166 are empty - CONST_ME_REDSMOKE = 167, - CONST_ME_YELLOWSMOKE = 168, - CONST_ME_GREENSMOKE = 169, - CONST_ME_PURPLESMOKE = 170, - CONST_ME_EARLY_THUNDER = 171, - CONST_ME_RAGIAZ_BONECAPSULE = 172, - CONST_ME_CRITICAL_DAMAGE = 173, - // 174 is empty - CONST_ME_PLUNGING_FISH = 175, }; enum ShootType_t : uint8_t { @@ -133,192 +72,75 @@ enum ShootType_t : uint8_t { CONST_ANI_SNOWBALL = 13, CONST_ANI_POWERBOLT = 14, CONST_ANI_POISON = 15, - CONST_ANI_INFERNALBOLT = 16, - CONST_ANI_HUNTINGSPEAR = 17, - CONST_ANI_ENCHANTEDSPEAR = 18, - CONST_ANI_REDSTAR = 19, - CONST_ANI_GREENSTAR = 20, - CONST_ANI_ROYALSPEAR = 21, - CONST_ANI_SNIPERARROW = 22, - CONST_ANI_ONYXARROW = 23, - CONST_ANI_PIERCINGBOLT = 24, - CONST_ANI_WHIRLWINDSWORD = 25, - CONST_ANI_WHIRLWINDAXE = 26, - CONST_ANI_WHIRLWINDCLUB = 27, - CONST_ANI_ETHEREALSPEAR = 28, - CONST_ANI_ICE = 29, - CONST_ANI_EARTH = 30, - CONST_ANI_HOLY = 31, - CONST_ANI_SUDDENDEATH = 32, - CONST_ANI_FLASHARROW = 33, - CONST_ANI_FLAMMINGARROW = 34, - CONST_ANI_SHIVERARROW = 35, - CONST_ANI_ENERGYBALL = 36, - CONST_ANI_SMALLICE = 37, - CONST_ANI_SMALLHOLY = 38, - CONST_ANI_SMALLEARTH = 39, - CONST_ANI_EARTHARROW = 40, - CONST_ANI_EXPLOSION = 41, - CONST_ANI_CAKE = 42, - - CONST_ANI_TARSALARROW = 44, - CONST_ANI_VORTEXBOLT = 45, - - CONST_ANI_PRISMATICBOLT = 48, - CONST_ANI_CRYSTALLINEARROW = 49, - CONST_ANI_DRILLBOLT = 50, - CONST_ANI_ENVENOMEDARROW = 51, - - CONST_ANI_GLOOTHSPEAR = 53, - CONST_ANI_SIMPLEARROW = 54, - - // for internal use, don't send to client - CONST_ANI_WEAPONTYPE = 0xFE, // 254 }; enum SpeakClasses : uint8_t { TALKTYPE_SAY = 1, TALKTYPE_WHISPER = 2, TALKTYPE_YELL = 3, - TALKTYPE_PRIVATE_FROM = 4, - TALKTYPE_PRIVATE_TO = 5, - TALKTYPE_CHANNEL_Y = 7, - TALKTYPE_CHANNEL_O = 8, - TALKTYPE_PRIVATE_NP = 10, - TALKTYPE_PRIVATE_PN = 12, - TALKTYPE_BROADCAST = 13, - TALKTYPE_CHANNEL_R1 = 14, //red - #c text - TALKTYPE_PRIVATE_RED_FROM = 15, //@name@text - TALKTYPE_PRIVATE_RED_TO = 16, //@name@text - TALKTYPE_MONSTER_SAY = 36, - TALKTYPE_MONSTER_YELL = 37, - - TALKTYPE_CHANNEL_R2 = 0xFF, //#d + 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_BLUE = 4, /*FIXME Blue message in the console*/ + 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_STATUS_CONSOLE_RED = 13, /*Red message in the console*/ - - MESSAGE_STATUS_DEFAULT = 17, /*White message at the bottom of the game window and in the console*/ - MESSAGE_STATUS_WARNING = 18, /*Red message in game window and in the console*/ - MESSAGE_EVENT_ADVANCE = 19, /*White message in game window and in the console*/ - - MESSAGE_STATUS_SMALL = 21, /*White message at the bottom of the game window"*/ - MESSAGE_INFO_DESCR = 22, /*Green message in game window and in the console*/ - MESSAGE_DAMAGE_DEALT = 23, - MESSAGE_DAMAGE_RECEIVED = 24, - MESSAGE_HEALED = 25, - MESSAGE_EXPERIENCE = 26, - MESSAGE_DAMAGE_OTHERS = 27, - MESSAGE_HEALED_OTHERS = 28, - MESSAGE_EXPERIENCE_OTHERS = 29, - MESSAGE_EVENT_DEFAULT = 30, /*White message at the bottom of the game window and in the console*/ - MESSAGE_LOOT = 31, - - MESSAGE_GUILD = 33, /*White message in channel (+ channelId)*/ - MESSAGE_PARTY_MANAGEMENT = 34, /*White message in channel (+ channelId)*/ - MESSAGE_PARTY = 35, /*White message in channel (+ channelId)*/ - MESSAGE_EVENT_ORANGE = 36, /*Orange message in the console*/ - MESSAGE_STATUS_CONSOLE_ORANGE = 37, /*Orange message in the console*/ + MESSAGE_CLASS_FIRST = MESSAGE_STATUS_CONSOLE_YELLOW, + MESSAGE_CLASS_LAST = MESSAGE_STATUS_CONSOLE_RED, }; -enum FluidColors_t : uint8_t { - FLUID_EMPTY, - FLUID_BLUE, - FLUID_RED, - FLUID_BROWN, - FLUID_GREEN, - FLUID_YELLOW, - FLUID_WHITE, - FLUID_PURPLE, -}; - -enum FluidTypes_t : uint8_t { - FLUID_NONE = FLUID_EMPTY, - FLUID_WATER = FLUID_BLUE, - FLUID_BLOOD = FLUID_RED, - FLUID_BEER = FLUID_BROWN, - FLUID_SLIME = FLUID_GREEN, - FLUID_LEMONADE = FLUID_YELLOW, - FLUID_MILK = FLUID_WHITE, - FLUID_MANA = FLUID_PURPLE, - - FLUID_LIFE = FLUID_RED + 8, - FLUID_OIL = FLUID_BROWN + 8, - FLUID_URINE = FLUID_YELLOW + 8, - FLUID_COCONUTMILK = FLUID_WHITE + 8, - FLUID_WINE = FLUID_PURPLE + 8, - - FLUID_MUD = FLUID_BROWN + 16, - FLUID_FRUITJUICE = FLUID_YELLOW + 16, - - FLUID_LAVA = FLUID_RED + 24, - FLUID_RUM = FLUID_BROWN + 24, - FLUID_SWAMP = FLUID_GREEN + 24, - - FLUID_TEA = FLUID_BROWN + 32, - - FLUID_MEAD = FLUID_BROWN + 40, -}; - -const uint8_t reverseFluidMap[] = { - FLUID_EMPTY, +enum FluidTypes_t : uint8_t +{ + FLUID_NONE = 0, FLUID_WATER, - FLUID_MANA, - FLUID_BEER, - FLUID_EMPTY, - FLUID_BLOOD, - FLUID_SLIME, - FLUID_EMPTY, - FLUID_LEMONADE, - FLUID_MILK, -}; - -const uint8_t clientToServerFluidMap[] = { - FLUID_EMPTY, - FLUID_WATER, - FLUID_MANA, + FLUID_WINE, FLUID_BEER, FLUID_MUD, FLUID_BLOOD, FLUID_SLIME, - FLUID_RUM, - FLUID_LEMONADE, - FLUID_MILK, - FLUID_WINE, - FLUID_LIFE, - FLUID_URINE, FLUID_OIL, - FLUID_FRUITJUICE, + FLUID_URINE, + FLUID_MILK, + FLUID_MANAFLUID, + FLUID_LIFEFLUID, + FLUID_LEMONADE, + FLUID_RUM, FLUID_COCONUTMILK, - FLUID_TEA, - FLUID_MEAD, + FLUID_FRUITJUICE, }; -enum ClientFluidTypes_t : uint8_t { - CLIENTFLUID_EMPTY = 0, - CLIENTFLUID_BLUE = 1, - CLIENTFLUID_PURPLE = 2, - CLIENTFLUID_BROWN_1 = 3, - CLIENTFLUID_BROWN_2 = 4, - CLIENTFLUID_RED = 5, - CLIENTFLUID_GREEN = 6, - CLIENTFLUID_BROWN = 7, - CLIENTFLUID_YELLOW = 8, - CLIENTFLUID_WHITE = 9, -}; - -const uint8_t fluidMap[] = { - CLIENTFLUID_EMPTY, - CLIENTFLUID_BLUE, - CLIENTFLUID_RED, - CLIENTFLUID_BROWN_1, - CLIENTFLUID_GREEN, - CLIENTFLUID_YELLOW, - CLIENTFLUID_WHITE, - CLIENTFLUID_PURPLE, +enum FluidColor_t : uint8_t +{ + FLUID_COLOR_NONE = 0, + FLUID_COLOR_BLUE = 1, + FLUID_COLOR_PURPLE = 2, + FLUID_COLOR_BROWN = 3, + FLUID_COLOR_BROWN1 = 4, + FLUID_COLOR_RED = 5, + FLUID_COLOR_GREEN = 6, + FLUID_COLOR_BROWN2 = 7, + FLUID_COLOR_YELLOW = 8, + FLUID_COLOR_WHITE = 9, }; enum SquareColor_t : uint8_t { @@ -333,10 +155,8 @@ enum TextColor_t : uint8_t { TEXTCOLOR_DARKRED = 108, TEXTCOLOR_LIGHTGREY = 129, TEXTCOLOR_SKYBLUE = 143, - TEXTCOLOR_PURPLE = 154, - TEXTCOLOR_ELECTRICPURPLE = 155, + TEXTCOLOR_PURPLE = 155, TEXTCOLOR_RED = 180, - TEXTCOLOR_PASTELRED = 194, TEXTCOLOR_ORANGE = 198, TEXTCOLOR_YELLOW = 210, TEXTCOLOR_WHITE_EXP = 215, @@ -353,13 +173,6 @@ enum Icons_t { ICON_HASTE = 1 << 6, ICON_SWORDS = 1 << 7, ICON_DROWNING = 1 << 8, - ICON_FREEZING = 1 << 9, - ICON_DAZZLED = 1 << 10, - ICON_CURSED = 1 << 11, - ICON_PARTY_BUFF = 1 << 12, - ICON_REDSWORDS = 1 << 13, - ICON_PIGEON = 1 << 14, - ICON_BLEEDING = 1 << 15, }; enum WeaponType_t : uint8_t { @@ -392,7 +205,6 @@ enum WeaponAction_t : uint8_t { }; enum WieldInfo_t { - WIELDINFO_NONE = 0 << 0, WIELDINFO_LEVEL = 1 << 0, WIELDINFO_MAGLV = 1 << 1, WIELDINFO_VOCREQ = 1 << 2, @@ -405,8 +217,6 @@ enum Skulls_t : uint8_t { SKULL_GREEN = 2, SKULL_WHITE = 3, SKULL_RED = 4, - SKULL_BLACK = 5, - SKULL_ORANGE = 6, }; enum PartyShields_t : uint8_t { @@ -414,97 +224,54 @@ enum PartyShields_t : uint8_t { SHIELD_WHITEYELLOW = 1, SHIELD_WHITEBLUE = 2, SHIELD_BLUE = 3, - SHIELD_YELLOW = 4, - SHIELD_BLUE_SHAREDEXP = 5, - SHIELD_YELLOW_SHAREDEXP = 6, - SHIELD_BLUE_NOSHAREDEXP_BLINK = 7, - SHIELD_YELLOW_NOSHAREDEXP_BLINK = 8, - SHIELD_BLUE_NOSHAREDEXP = 9, - SHIELD_YELLOW_NOSHAREDEXP = 10, - SHIELD_GRAY = 11, -}; - -enum GuildEmblems_t : uint8_t { - GUILDEMBLEM_NONE = 0, - GUILDEMBLEM_ALLY = 1, - GUILDEMBLEM_ENEMY = 2, - GUILDEMBLEM_NEUTRAL = 3, - GUILDEMBLEM_MEMBER = 4, - GUILDEMBLEM_OTHER = 5, + SHIELD_YELLOW = 4 }; enum item_t : uint16_t { - ITEM_BROWSEFIELD = 460, // for internal use + 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_FIREFIELD_PVP_FULL = 1487, - ITEM_FIREFIELD_PVP_MEDIUM = 1488, - ITEM_FIREFIELD_PVP_SMALL = 1489, - ITEM_FIREFIELD_PERSISTENT_FULL = 1492, - ITEM_FIREFIELD_PERSISTENT_MEDIUM = 1493, - ITEM_FIREFIELD_PERSISTENT_SMALL = 1494, - ITEM_FIREFIELD_NOPVP = 1500, + ITEM_POISONFIELD_PVP = 2121, + ITEM_POISONFIELD_PERSISTENT = 2127, + ITEM_POISONFIELD_NOPVP = 2134, - ITEM_POISONFIELD_PVP = 1490, - ITEM_POISONFIELD_PERSISTENT = 1496, - ITEM_POISONFIELD_NOPVP = 1503, + ITEM_ENERGYFIELD_PVP = 2122, + ITEM_ENERGYFIELD_PERSISTENT = 2126, + ITEM_ENERGYFIELD_NOPVP = 2135, - ITEM_ENERGYFIELD_PVP = 1491, - ITEM_ENERGYFIELD_PERSISTENT = 1495, - ITEM_ENERGYFIELD_NOPVP = 1504, + ITEM_MAGICWALL = 2128, + ITEM_MAGICWALL_PERSISTENT = 2128, - ITEM_MAGICWALL = 1497, - ITEM_MAGICWALL_PERSISTENT = 1498, - ITEM_MAGICWALL_SAFE = 11098, + ITEM_WILDGROWTH = 2130, + ITEM_WILDGROWTH_PERSISTENT = 2130, - ITEM_WILDGROWTH = 1499, - ITEM_WILDGROWTH_PERSISTENT = 2721, - ITEM_WILDGROWTH_SAFE = 11099, + ITEM_GOLD_COIN = 3031, + ITEM_PLATINUM_COIN = 3035, + ITEM_CRYSTAL_COIN = 3043, - ITEM_BAG = 1987, - ITEM_SHOPPING_BAG = 23782, + ITEM_DEPOT = 3502, + ITEM_LOCKER1 = 3497, - ITEM_GOLD_COIN = 2148, - ITEM_PLATINUM_COIN = 2152, - ITEM_CRYSTAL_COIN = 2160, - ITEM_STORE_COIN = 24774, // in-game store currency + ITEM_MALE_CORPSE = 4240, + ITEM_FEMALE_CORPSE = 4247, - ITEM_DEPOT = 2594, - ITEM_LOCKER1 = 2589, - ITEM_INBOX = 14404, - ITEM_MARKET = 14405, - ITEM_STORE_INBOX = 26052, - ITEM_DEPOT_BOX_I = 25453, - ITEM_DEPOT_BOX_II = 25454, - ITEM_DEPOT_BOX_III = 25455, - ITEM_DEPOT_BOX_IV = 25456, - ITEM_DEPOT_BOX_V = 25457, - ITEM_DEPOT_BOX_VI = 25458, - ITEM_DEPOT_BOX_VII = 25459, - ITEM_DEPOT_BOX_VIII = 25460, - ITEM_DEPOT_BOX_IX = 25461, - ITEM_DEPOT_BOX_X = 25462, - ITEM_DEPOT_BOX_XI = 25463, - ITEM_DEPOT_BOX_XII = 25464, - ITEM_DEPOT_BOX_XIII = 25465, - ITEM_DEPOT_BOX_XIV = 25466, - ITEM_DEPOT_BOX_XV = 25467, - ITEM_DEPOT_BOX_XVI = 25468, - ITEM_DEPOT_BOX_XVII = 25469, + ITEM_FULLSPLASH = 2886, + ITEM_SMALLSPLASH = 2889, - ITEM_MALE_CORPSE = 3058, - ITEM_FEMALE_CORPSE = 3065, + ITEM_PARCEL = 3503, + ITEM_PARCEL_STAMPED = 3504, + ITEM_LETTER = 3505, + ITEM_LETTER_STAMPED = 3506, + ITEM_LABEL = 3507, - ITEM_FULLSPLASH = 2016, - ITEM_SMALLSPLASH = 2019, + ITEM_AMULETOFLOSS = 3057, - ITEM_PARCEL = 2595, - ITEM_LETTER = 2597, - ITEM_LETTER_STAMPED = 2598, - ITEM_LABEL = 2599, - - ITEM_AMULETOFLOSS = 2173, - - ITEM_DOCUMENT_RO = 1968, //read-only + ITEM_DOCUMENT_RO = 2819, //read-only }; enum PlayerFlags : uint64_t { @@ -546,12 +313,14 @@ enum PlayerFlags : uint64_t { PlayerFlag_IgnoreWeaponCheck = static_cast(1) << 35, PlayerFlag_CannotBeMuted = static_cast(1) << 36, PlayerFlag_IsAlwaysPremium = static_cast(1) << 37, + PlayerFlag_SpecialMoveUse = static_cast(1) << 38, }; -enum ReloadTypes_t : uint8_t { +enum ReloadTypes_t : uint8_t { RELOAD_TYPE_ALL, RELOAD_TYPE_ACTIONS, RELOAD_TYPE_CHAT, + RELOAD_TYPE_COMMANDS, RELOAD_TYPE_CONFIG, RELOAD_TYPE_CREATURESCRIPTS, RELOAD_TYPE_EVENTS, @@ -564,7 +333,6 @@ enum ReloadTypes_t : uint8_t { RELOAD_TYPE_NPCS, RELOAD_TYPE_QUESTS, RELOAD_TYPE_RAIDS, - RELOAD_TYPE_SCRIPTS, RELOAD_TYPE_SPELLS, RELOAD_TYPE_TALKACTIONS, RELOAD_TYPE_WEAPONS, @@ -572,6 +340,7 @@ enum ReloadTypes_t : uint8_t { 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; //Reserved player storage key ranges; @@ -581,11 +350,6 @@ static constexpr int32_t PSTRG_RESERVED_RANGE_SIZE = 10000000; //[1000 - 1500]; static constexpr int32_t PSTRG_OUTFITS_RANGE_START = (PSTRG_RESERVED_RANGE_START + 1000); static constexpr int32_t PSTRG_OUTFITS_RANGE_SIZE = 500; -//[2001 - 2011]; -static constexpr int32_t PSTRG_MOUNTS_RANGE_START = (PSTRG_RESERVED_RANGE_START + 2001); -static constexpr int32_t PSTRG_MOUNTS_RANGE_SIZE = 10; -static constexpr int32_t PSTRG_MOUNTS_CURRENTMOUNT = (PSTRG_MOUNTS_RANGE_START + 10); - #define IS_IN_KEYRANGE(key, range) (key >= PSTRG_##range##_START && ((key - PSTRG_##range##_START) <= PSTRG_##range##_SIZE)) diff --git a/src/container.cpp b/src/container.cpp index dfcecad..c694871 100644 --- a/src/container.cpp +++ b/src/container.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,41 +28,16 @@ extern Game g_game; Container::Container(uint16_t type) : Container(type, items[type].maxItems) {} -Container::Container(uint16_t type, uint16_t size, bool unlocked /*= true*/, bool pagination /*= false*/) : +Container::Container(uint16_t type, uint16_t size) : Item(type), - maxSize(size), - unlocked(unlocked), - pagination(pagination) + maxSize(size) {} -Container::Container(Tile* tile) : Container(ITEM_BROWSEFIELD, 30, false, true) -{ - TileItemVector* itemVector = tile->getItemList(); - if (itemVector) { - for (Item* item : *itemVector) { - if ((item->getContainer() || item->hasProperty(CONST_PROP_MOVEABLE)) && !item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { - itemlist.push_front(item); - item->setParent(this); - } - } - } - - setParent(tile); -} - Container::~Container() { - if (getID() == ITEM_BROWSEFIELD) { - g_game.browseFields.erase(getTile()); - - for (Item* item : itemlist) { - item->setParent(parent); - } - } else { - for (Item* item : itemlist) { - item->setParent(nullptr); - item->decrementReferenceCounter(); - } + for (Item* item : itemlist) { + item->setParent(nullptr); + item->decrementReferenceCounter(); } } @@ -87,7 +62,7 @@ Container* Container::getParentContainer() bool Container::hasParent() const { - return getID() != ITEM_BROWSEFIELD && dynamic_cast(getParent()) == nullptr; + return dynamic_cast(getParent()) != nullptr; } void Container::addItem(Item* item) @@ -107,22 +82,24 @@ Attr_ReadValue Container::readAttr(AttrTypes_t attr, PropStream& propStream) return Item::readAttr(attr, propStream); } -bool Container::unserializeItemNode(OTB::Loader& loader, const OTB::Node& node, PropStream& propStream) +bool Container::unserializeItemNode(FileLoader& f, NODE node, PropStream& propStream) { - bool ret = Item::unserializeItemNode(loader, node, propStream); + bool ret = Item::unserializeItemNode(f, node, propStream); if (!ret) { return false; } - for (auto& itemNode : node.children) { + uint32_t type; + NODE nodeItem = f.getChildNode(node, type); + while (nodeItem) { //load container items - if (itemNode.type != OTBM_ITEM) { + if (type != OTBM_ITEM) { // unknown type return false; } PropStream itemPropStream; - if (!loader.getProps(itemNode, itemPropStream)) { + if (!f.getProps(nodeItem, itemPropStream)) { return false; } @@ -131,12 +108,14 @@ bool Container::unserializeItemNode(OTB::Loader& loader, const OTB::Node& node, return false; } - if (!item->unserializeItemNode(loader, itemNode, itemPropStream)) { + if (!item->unserializeItemNode(f, nodeItem, itemPropStream)) { return false; } addItem(item); updateItemWeight(item->getWeight()); + + nodeItem = f.getNextNode(nodeItem, type); } return true; } @@ -215,48 +194,48 @@ bool Container::isHoldingItem(const Item* item) const void Container::onAddContainerItem(Item* item) { - SpectatorVec spectators; - g_game.map.getSpectators(spectators, getPosition(), false, true, 2, 2, 2, 2); + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), false, true, 2, 2, 2, 2); //send to client - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { spectator->getPlayer()->sendAddContainerItem(this, item); } //event methods - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { spectator->getPlayer()->onAddContainerItem(item); } } void Container::onUpdateContainerItem(uint32_t index, Item* oldItem, Item* newItem) { - SpectatorVec spectators; - g_game.map.getSpectators(spectators, getPosition(), false, true, 2, 2, 2, 2); + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), false, true, 2, 2, 2, 2); //send to client - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { spectator->getPlayer()->sendUpdateContainerItem(this, index, newItem); } //event methods - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { spectator->getPlayer()->onUpdateContainerItem(this, oldItem, newItem); } } void Container::onRemoveContainerItem(uint32_t index, Item* item) { - SpectatorVec spectators; - g_game.map.getSpectators(spectators, getPosition(), false, true, 2, 2, 2, 2); + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), false, true, 2, 2, 2, 2); //send change to client - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { spectator->getPlayer()->sendRemoveContainerItem(this, index); } //event methods - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { spectator->getPlayer()->onRemoveContainerItem(this, item); } } @@ -271,10 +250,6 @@ ReturnValue Container::queryAdd(int32_t index, const Thing& thing, uint32_t coun return RETURNVALUE_NOERROR; } - if (!unlocked) { - return RETURNVALUE_NOTPOSSIBLE; - } - const Item* item = thing.getItem(); if (item == nullptr) { return RETURNVALUE_NOTPOSSIBLE; @@ -295,10 +270,6 @@ ReturnValue Container::queryAdd(int32_t index, const Thing& thing, uint32_t coun return RETURNVALUE_THISISIMPOSSIBLE; } - if (dynamic_cast(cylinder)) { - return RETURNVALUE_CONTAINERNOTENOUGHROOM; - } - cylinder = cylinder->getParent(); } @@ -355,7 +326,7 @@ ReturnValue Container::queryMaxCount(int32_t index, const Thing& thing, uint32_t } } else { const Item* destItem = getItemByIndex(index); - if (item->equals(destItem) && destItem->getItemCount() < 100) { + if (item->equals(destItem) && !destItem->isRune() && destItem->getItemCount() < 100) { uint32_t remainder = 100 - destItem->getItemCount(); if (queryAdd(index, *item, remainder, flags) == RETURNVALUE_NOERROR) { n = remainder; @@ -398,14 +369,9 @@ ReturnValue Container::queryRemove(const Thing& thing, uint32_t count, uint32_t return RETURNVALUE_NOERROR; } -Cylinder* Container::queryDestination(int32_t& index, const Thing& thing, Item** destItem, - uint32_t& flags) +Cylinder* Container::queryDestination(int32_t& index, const Thing &thing, Item** destItem, + uint32_t& flags) { - if (!unlocked) { - *destItem = nullptr; - return this; - } - if (index == 254 /*move up*/) { index = INDEX_WHEREEVER; *destItem = nullptr; @@ -420,7 +386,8 @@ Cylinder* Container::queryDestination(int32_t& index, const Thing& thing, Item** if (index == 255 /*add wherever*/) { index = INDEX_WHEREEVER; *destItem = nullptr; - } else if (index >= static_cast(capacity())) { + } + else if (index >= static_cast(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 @@ -451,19 +418,22 @@ Cylinder* Container::queryDestination(int32_t& index, const Thing& thing, Item** } } - 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; + 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; } - ++n; } } + return this; } diff --git a/src/container.h b/src/container.h index c4a6838..2330c26 100644 --- a/src/container.h +++ b/src/container.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,7 +26,6 @@ #include "item.h" class Container; -class DepotChest; class DepotLocker; class ContainerIterator @@ -39,7 +38,7 @@ class ContainerIterator void advance(); Item* operator*(); - private: + protected: std::list over; ItemDeque::const_iterator cur; @@ -50,32 +49,31 @@ class Container : public Item, public Cylinder { public: explicit Container(uint16_t type); - Container(uint16_t type, uint16_t size, bool unlocked = true, bool pagination = false); - explicit Container(Tile* tile); + Container(uint16_t type, uint16_t size); ~Container(); // non-copyable Container(const Container&) = delete; Container& operator=(const Container&) = delete; - Item* clone() const override final; + Item* clone() const final; - Container* getContainer() override final { + Container* getContainer() final { return this; } - const Container* getContainer() const override final { + const Container* getContainer() const final { return this; } - virtual DepotLocker* getDepotLocker() { + virtual DepotLocker* getDepotLocker() override { return nullptr; } - virtual const DepotLocker* getDepotLocker() const { + virtual const DepotLocker* getDepotLocker() const override { return nullptr; } Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; - bool unserializeItemNode(OTB::Loader& loader, const OTB::Node& node, PropStream& propStream) override; + bool unserializeItemNode(FileLoader& f, NODE node, PropStream& propStream) override; std::string getContentDescription() const; size_t size() const { @@ -107,60 +105,41 @@ class Container : public Item, public Cylinder bool isHoldingItem(const Item* item) const; uint32_t getItemHoldingCount() const; - uint32_t getWeight() const override final; - - bool isUnlocked() const { - return unlocked; - } - bool hasPagination() const { - return pagination; - } + 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 override final; - ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const override final; + 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) override final; + uint32_t& flags) final; - void addThing(Thing* thing) override final; - void addThing(int32_t index, Thing* thing) override 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) override final; - void replaceThing(uint32_t index, Thing* thing) override 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) override final; + void removeThing(Thing* thing, uint32_t count) final; - int32_t getThingIndex(const Thing* thing) const override final; - size_t getFirstIndex() const override final; - size_t getLastIndex() const override final; - uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const override final; - std::map& getAllItemTypeCount(std::map& countMap) const override final; - Thing* getThing(size_t index) const override 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& getAllItemTypeCount(std::map& 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) override final; - void internalAddThing(uint32_t index, Thing* thing) override final; - void startDecaying() override final; - - protected: - ItemDeque itemlist; + void internalAddThing(Thing* thing) final; + void internalAddThing(uint32_t index, Thing* thing) final; + void startDecaying() final; private: - std::ostringstream& getContentDescription(std::ostringstream& os) const; - - uint32_t maxSize; - uint32_t totalWeight = 0; - uint32_t serializationCount = 0; - - bool unlocked; - bool pagination; - void onAddContainerItem(Item* item); void onUpdateContainerItem(uint32_t index, Item* oldItem, Item* newItem); void onRemoveContainerItem(uint32_t index, Item* item); @@ -168,6 +147,14 @@ class Container : public Item, public Cylinder 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; }; diff --git a/src/creature.cpp b/src/creature.cpp index 81dd0ff..da8bf6d 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,10 +25,6 @@ #include "configmanager.h" #include "scheduler.h" -double Creature::speedA = 857.36; -double Creature::speedB = 261.29; -double Creature::speedC = -4795.01; - extern Game g_game; extern ConfigManager g_config; extern CreatureEvents* g_creatureEvents; @@ -42,7 +38,8 @@ Creature::~Creature() { for (Creature* summon : summons) { summon->setAttackedCreature(nullptr); - summon->removeMaster(); + summon->setMaster(nullptr); + summon->decrementReferenceCounter(); } for (Condition* condition : conditions) { @@ -230,7 +227,7 @@ void Creature::onWalk(Direction& dir) if (r < DIRECTION_DIAGONAL_MASK) { dir = static_cast(r); } - g_game.internalCreatureSay(this, TALKTYPE_MONSTER_SAY, "Hicks!", false); + g_game.internalCreatureSay(this, TALKTYPE_SAY, "Hicks!", false); } } } @@ -310,7 +307,7 @@ void Creature::updateMapCache() void Creature::updateTileCache(const Tile* tile, int32_t dx, int32_t dy) { if (std::abs(dx) <= maxWalkCacheWidth && std::abs(dy) <= maxWalkCacheHeight) { - localMapCache[maxWalkCacheHeight + dy][maxWalkCacheWidth + dx] = tile && tile->queryAdd(0, *this, 1, FLAG_PATHFINDING | FLAG_IGNOREFIELDDAMAGE) == RETURNVALUE_NOERROR; + localMapCache[maxWalkCacheHeight + dy][maxWalkCacheWidth + dx] = tile && tile->queryAdd(0, *this, 1, FLAG_PATHFINDING) == RETURNVALUE_NOERROR; } } @@ -412,7 +409,7 @@ void Creature::onRemoveCreature(Creature* creature, bool) onCreatureDisappear(creature, true); if (creature == this) { if (master && !master->isRemoved()) { - setMaster(nullptr); + master->removeSummon(this); } } else if (isMapLoaded) { if (creature->getPosition().z == getPosition().z) { @@ -585,6 +582,11 @@ void Creature::onCreatureMove(Creature* creature, const Tile* newTile, const Pos if (creature == followCreature || (creature == this && followCreature)) { if (hasFollowPath) { isUpdatingPath = true; + // this updates following walking + if (lastWalkUpdate == 0 || OTSYS_TIME() - lastWalkUpdate >= 250) { + g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, &g_game, getID()))); + lastWalkUpdate = OTSYS_TIME(); + } } if (newPos.z != oldPos.z || !canSee(followCreature->getPosition())) { @@ -617,7 +619,8 @@ void Creature::onDeath() if (lastHitCreature) { lastHitUnjustified = lastHitCreature->onKilledCreature(this); lastHitCreatureMaster = lastHitCreature->getMaster(); - } else { + } + else { lastHitCreatureMaster = nullptr; } @@ -639,7 +642,6 @@ void Creature::onDeath() uint64_t gainExp = getGainedExperience(attacker); if (Player* attackerPlayer = attacker->getPlayer()) { attackerPlayer->removeAttacked(getPlayer()); - Party* party = attackerPlayer->getParty(); if (party && party->getLeader() && party->isSharedExperienceActive() && party->isSharedExperienceEnabled()) { attacker = party->getLeader(); @@ -649,7 +651,8 @@ void Creature::onDeath() auto tmpIt = experienceMap.find(attacker); if (tmpIt == experienceMap.end()) { experienceMap[attacker] = gainExp; - } else { + } + else { tmpIt->second += gainExp; } } @@ -673,7 +676,7 @@ void Creature::onDeath() death(lastHitCreature); if (master) { - setMaster(nullptr); + master->removeSummon(this); } if (droppedCorpse) { @@ -683,53 +686,41 @@ void Creature::onDeath() bool Creature::dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified) { - if (!lootDrop && getMonster()) { - if (master) { - //scripting event - onDeath - const CreatureEventList& deathEvents = getCreatureEvents(CREATURE_EVENT_DEATH); - for (CreatureEvent* deathEvent : deathEvents) { - deathEvent->executeOnDeath(this, nullptr, lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified); - } - } + Item* splash; + switch (getRace()) { + case RACE_VENOM: + splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_SLIME); + break; - g_game.addMagicEffect(getPosition(), CONST_ME_POFF); - } else { - Item* splash; - switch (getRace()) { - case RACE_VENOM: - splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_SLIME); - break; + case RACE_BLOOD: + splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_BLOOD); + break; - case RACE_BLOOD: - splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_BLOOD); - break; + default: + splash = nullptr; + break; + } - default: - splash = nullptr; - break; - } + Tile* tile = getTile(); - Tile* tile = getTile(); + if (splash) { + g_game.internalAddItem(tile, splash, INDEX_WHEREEVER, FLAG_NOLIMIT); + g_game.startDecay(splash); + } - if (splash) { - g_game.internalAddItem(tile, splash, INDEX_WHEREEVER, FLAG_NOLIMIT); - g_game.startDecay(splash); - } + Item* corpse = getCorpse(lastHitCreature, mostDamageCreature); + if (corpse) { + g_game.internalAddItem(tile, corpse, INDEX_WHEREEVER, FLAG_NOLIMIT); + g_game.startDecay(corpse); + } - Item* corpse = getCorpse(lastHitCreature, mostDamageCreature); - if (corpse) { - g_game.internalAddItem(tile, corpse, INDEX_WHEREEVER, FLAG_NOLIMIT); - g_game.startDecay(corpse); - } + //scripting event - onDeath + for (CreatureEvent* deathEvent : getCreatureEvents(CREATURE_EVENT_DEATH)) { + deathEvent->executeOnDeath(this, corpse, lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified); + } - //scripting event - onDeath - for (CreatureEvent* deathEvent : getCreatureEvents(CREATURE_EVENT_DEATH)) { - deathEvent->executeOnDeath(this, corpse, lastHitCreature, mostDamageCreature, lastHitUnjustified, mostDamageUnjustified); - } - - if (corpse) { - dropLoot(corpse->getContainer(), lastHitCreature); - } + if (corpse) { + dropLoot(corpse->getContainer(), lastHitCreature); } return true; @@ -790,6 +781,28 @@ BlockType_t Creature::blockHit(Creature* attacker, CombatType_t combatType, int3 damage = 0; blockType = BLOCK_IMMUNITY; } else if (checkDefense || checkArmor) { + if (checkDefense && OTSYS_TIME() >= earliestDefendTime) { + damage -= getDefense(); + + earliestDefendTime = lastDefendTime + 2000; + lastDefendTime = OTSYS_TIME(); + + if (damage <= 0) { + damage = 0; + blockType = BLOCK_DEFENSE; + } + } + + if (checkArmor) { + if (damage > 0 && combatType == COMBAT_PHYSICALDAMAGE) { + damage -= getArmor(); + if (damage <= 0) { + damage = 0; + blockType = BLOCK_ARMOR; + } + } + } + bool hasDefense = false; if (blockCount > 0) { @@ -797,30 +810,6 @@ BlockType_t Creature::blockHit(Creature* attacker, CombatType_t combatType, int3 hasDefense = true; } - if (checkDefense && hasDefense && canUseDefense) { - int32_t defense = getDefense(); - damage -= uniform_random(defense / 2, defense); - if (damage <= 0) { - damage = 0; - blockType = BLOCK_DEFENSE; - checkArmor = false; - } - } - - if (checkArmor) { - int32_t armor = getArmor(); - if (armor > 3) { - damage -= uniform_random(armor / 2, armor - (armor % 2 + 1)); - } else if (armor > 0) { - --damage; - } - - if (damage <= 0) { - damage = 0; - blockType = BLOCK_ARMOR; - } - } - if (hasDefense && blockType != BLOCK_NONE) { onBlockHit(); } @@ -844,6 +833,16 @@ bool Creature::setAttackedCreature(Creature* creature) return false; } + if (isSummon() && master) { + if (Monster* monster = master->getMonster()) { + if (monster->mType->info.targetDistance <= 1) { + if (!monster->hasFollowPath && !monster->followCreature) { + return false; + } + } + } + } + attackedCreature = creature; onAttackedCreature(attackedCreature); attackedCreature->onAttacked(); @@ -1027,21 +1026,9 @@ void Creature::onTickCondition(ConditionType_t type, bool& bRemove) case CONDITION_POISON: bRemove = (field->getCombatType() != COMBAT_EARTHDAMAGE); break; - case CONDITION_FREEZING: - bRemove = (field->getCombatType() != COMBAT_ICEDAMAGE); - break; - case CONDITION_DAZZLED: - bRemove = (field->getCombatType() != COMBAT_HOLYDAMAGE); - break; - case CONDITION_CURSED: - bRemove = (field->getCombatType() != COMBAT_DEATHDAMAGE); - break; case CONDITION_DROWN: bRemove = (field->getCombatType() != COMBAT_DROWNDAMAGE); break; - case CONDITION_BLEEDING: - bRemove = (field->getCombatType() != COMBAT_PHYSICALDAMAGE); - break; default: break; } @@ -1064,8 +1051,15 @@ void Creature::onAttackedCreatureDrainHealth(Creature* target, int32_t points) bool Creature::onKilledCreature(Creature* target, bool) { + if (latestKillEvent == target->getID()) { + return false; + } + + latestKillEvent = target->getID(); + if (master) { master->onKilledCreature(target); + return false; } //scripting event - onKill @@ -1085,43 +1079,28 @@ void Creature::onGainExperience(uint64_t gainExp, Creature* target) gainExp /= 2; master->onGainExperience(gainExp, target); - SpectatorVec spectators; - g_game.map.getSpectators(spectators, position, false, true); - if (spectators.empty()) { - return; - } - - TextMessage message(MESSAGE_EXPERIENCE_OTHERS, ucfirst(getNameDescription()) + " gained " + std::to_string(gainExp) + (gainExp != 1 ? " experience points." : " experience point.")); - message.position = position; - message.primary.color = TEXTCOLOR_WHITE_EXP; - message.primary.value = gainExp; - - for (Creature* spectator : spectators) { - spectator->getPlayer()->sendTextMessage(message); - } + g_game.addAnimatedText(position, TEXTCOLOR_WHITE_EXP, std::to_string(gainExp)); } -bool Creature::setMaster(Creature* newMaster) { - if (!newMaster && !master) { - return false; - } +void Creature::addSummon(Creature* creature) +{ + creature->setDropLoot(false); + creature->setLossSkill(false); + creature->setMaster(this); + creature->incrementReferenceCounter(); + summons.push_back(creature); +} - if (newMaster) { - incrementReferenceCounter(); - newMaster->summons.push_back(this); +void Creature::removeSummon(Creature* creature) +{ + auto cit = std::find(summons.begin(), summons.end(), creature); + if (cit != summons.end()) { + creature->setDropLoot(true); + creature->setLossSkill(true); + creature->setMaster(nullptr); + creature->decrementReferenceCounter(); + summons.erase(cit); } - - Creature* oldMaster = master; - master = newMaster; - - if (oldMaster) { - auto summon = std::find(oldMaster->summons.begin(), oldMaster->summons.end(), this); - if (summon != oldMaster->summons.end()) { - oldMaster->summons.erase(summon); - decrementReferenceCounter(); - } - } - return true; } bool Creature::addCondition(Condition* condition, bool force/* = false*/) @@ -1280,21 +1259,20 @@ Condition* Creature::getCondition(ConditionType_t type, ConditionId_t conditionI void Creature::executeConditions(uint32_t interval) { - ConditionList tempConditions{ conditions }; - for (Condition* condition : tempConditions) { - auto it = std::find(conditions.begin(), conditions.end(), condition); - if (it == conditions.end()) { - continue; - } - + auto it = conditions.begin(), end = conditions.end(); + while (it != end) { + Condition* condition = *it; if (!condition->executeCondition(this, interval)) { - it = std::find(conditions.begin(), conditions.end(), condition); - if (it != conditions.end()) { - conditions.erase(it); - condition->endCondition(this); - onEndCondition(condition->getType()); - delete condition; - } + ConditionType_t type = condition->getType(); + + it = conditions.erase(it); + + condition->endCondition(this); + delete condition; + + onEndCondition(type); + } else { + ++it; } } } @@ -1311,7 +1289,7 @@ bool Creature::hasCondition(ConditionType_t type, uint32_t subId/* = 0*/) const continue; } - if (condition->getEndTime() >= timeNow || condition->getTicks() == -1) { + if (condition->getEndTime() >= timeNow) { return true; } } @@ -1348,18 +1326,8 @@ int64_t Creature::getStepDuration() const return 0; } - uint32_t calculatedStepSpeed; uint32_t groundSpeed; - int32_t stepSpeed = getStepSpeed(); - if (stepSpeed > -Creature::speedB) { - calculatedStepSpeed = floor((Creature::speedA * log((stepSpeed / 2) + Creature::speedB) + Creature::speedC) + 0.5); - if (calculatedStepSpeed == 0) { - calculatedStepSpeed = 1; - } - } else { - calculatedStepSpeed = 1; - } Item* ground = tile->getGround(); if (ground) { @@ -1371,12 +1339,12 @@ int64_t Creature::getStepDuration() const groundSpeed = 150; } - double duration = std::floor(1000 * groundSpeed / calculatedStepSpeed); + double duration = std::floor(1000 * groundSpeed) / stepSpeed; int64_t stepDuration = std::ceil(duration / 50) * 50; const Monster* monster = getMonster(); if (monster && monster->isTargetNearby() && !monster->isFleeing() && !monster->getMaster()) { - stepDuration *= 2; + stepDuration *= 3; } return stepDuration; @@ -1396,18 +1364,15 @@ int64_t Creature::getEventStepTicks(bool onlyDelay) const return ret; } -LightInfo Creature::getCreatureLight() const +void Creature::getCreatureLight(LightInfo& light) const { - return internalLight; -} - -void Creature::setCreatureLight(LightInfo lightInfo) { - internalLight = std::move(lightInfo); + light = internalLight; } void Creature::setNormalCreatureLight() { - internalLight = {}; + internalLight.level = 0; + internalLight.color = 0; } bool Creature::registerCreatureEvent(const std::string& name) @@ -1475,10 +1440,6 @@ CreatureEventList Creature::getCreatureEvents(CreatureEventType_t type) } for (CreatureEvent* creatureEvent : eventsList) { - if (!creatureEvent->isLoaded()) { - continue; - } - if (creatureEvent->getEventType() == type) { tmpEventList.push_back(creatureEvent); } diff --git a/src/creature.h b/src/creature.h index 1d65ac2..26f5627 100644 --- a/src/creature.h +++ b/src/creature.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,8 +28,8 @@ #include "enums.h" #include "creatureevent.h" -using ConditionList = std::list; -using CreatureEventList = std::list; +typedef std::list ConditionList; +typedef std::list CreatureEventList; enum slots_t : uint8_t { CONST_SLOT_WHEREEVER = 0, @@ -66,6 +66,7 @@ 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; @@ -82,7 +83,7 @@ class FrozenPathingConditionCall bool isInRange(const Position& startPos, const Position& testPos, const FindPathParams& fpp) const; - private: + protected: Position targetPos; }; @@ -96,18 +97,16 @@ class Creature : virtual public Thing Creature(); public: - static double speedA, speedB, speedC; - virtual ~Creature(); // non-copyable Creature(const Creature&) = delete; Creature& operator=(const Creature&) = delete; - Creature* getCreature() override final { + Creature* getCreature() final { return this; } - const Creature* getCreature() const override final { + const Creature* getCreature() const final { return this; } virtual Player* getPlayer() { @@ -132,8 +131,6 @@ class Creature : virtual public Thing virtual const std::string& getName() const = 0; virtual const std::string& getNameDescription() const = 0; - virtual CreatureType_t getType() const = 0; - virtual void setID() = 0; void setRemoved() { isInternalRemoved = true; @@ -172,13 +169,13 @@ class Creature : virtual public Thing hiddenHealth = b; } - int32_t getThrowRange() const override final { + int32_t getThrowRange() const final { return 1; } bool isPushable() const override { return getWalkDelay() <= 0; } - bool isRemoved() const override final { + bool isRemoved() const final { return isInternalRemoved; } virtual bool canSeeInvisibility() const { @@ -199,11 +196,15 @@ class Creature : virtual public Thing return getSpeed(); } int32_t getSpeed() const { - return baseSpeed + varSpeed; + if (baseSpeed == 0) { + return 0; + } + + return (2 * (varSpeed + baseSpeed)) + 80; } void setSpeed(int32_t varSpeedDelta) { int32_t oldSpeed = getSpeed(); - varSpeed = varSpeedDelta; + varSpeed += varSpeedDelta; if (getSpeed() <= 0) { stopEventWalk(); @@ -270,15 +271,9 @@ class Creature : virtual public Thing virtual BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, bool checkDefense = false, bool checkArmor = false, bool field = false); - bool setMaster(Creature* newMaster); - - void removeMaster() { - if (master) { - master = nullptr; - decrementReferenceCounter(); - } + void setMaster(Creature* creature) { + master = creature; } - bool isSummon() const { return master != nullptr; } @@ -286,6 +281,8 @@ class Creature : virtual public Thing return master; } + void addSummon(Creature* creature); + void removeSummon(Creature* creature); const std::list& getSummons() const { return summons; } @@ -293,19 +290,9 @@ class Creature : virtual public Thing virtual int32_t getArmor() const { return 0; } - virtual int32_t getDefense() const { + virtual int32_t getDefense() { return 0; } - virtual float getAttackFactor() const { - return 1.0f; - } - virtual float getDefenseFactor() const { - return 1.0f; - } - - virtual uint8_t getSpeechBubble() const { - return SPEECHBUBBLE_NONE; - } bool addCondition(Condition* condition, bool force = false); bool addCombatCondition(Condition* condition); @@ -335,12 +322,15 @@ class Creature : virtual public Thing virtual void changeHealth(int32_t healthChange, bool sendHealthChange = true); - void gainHealth(Creature* healer, int32_t healthGain); + void gainHealth(Creature* attacker, int32_t healthGain); virtual void drainHealth(Creature* attacker, int32_t damage); virtual bool challengeCreature(Creature*) { return false; } + virtual bool convinceCreature(Creature*) { + return false; + } void onDeath(); virtual uint64_t getGainedExperience(Creature* attacker) const; @@ -365,9 +355,11 @@ class Creature : virtual public Thing virtual void onAttackedCreatureChangeZone(ZoneType_t zone); virtual void onIdleStatus(); - virtual LightInfo getCreatureLight() const; + virtual void getCreatureLight(LightInfo& light) const; virtual void setNormalCreatureLight(); - void setCreatureLight(LightInfo lightInfo); + void setCreatureLight(LightInfo light) { + internalLight = light; + } virtual void onThink(uint32_t interval); void onAttacking(uint32_t interval); @@ -390,6 +382,7 @@ class Creature : virtual public Thing 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&) { @@ -402,33 +395,30 @@ class Creature : virtual public Thing void setDropLoot(bool lootDrop) { this->lootDrop = lootDrop; } - void setSkillLoss(bool skillLoss) { + void setLossSkill(bool skillLoss) { this->skillLoss = skillLoss; } - void setUseDefense(bool useDefense) { - canUseDefense = useDefense; - } //creature script events bool registerCreatureEvent(const std::string& name); bool unregisterCreatureEvent(const std::string& name); - Cylinder* getParent() const override final { + Cylinder* getParent() const final { return tile; } - void setParent(Cylinder* cylinder) override final { + void setParent(Cylinder* cylinder) final { tile = static_cast(cylinder); position = tile->getPosition(); } - const Position& getPosition() const override final { + inline const Position& getPosition() const final { return position; } - Tile* getTile() override final { + Tile* getTile() final { return tile; } - const Tile* getTile() const override final { + const Tile* getTile() const final { return tile; } @@ -474,7 +464,7 @@ class Creature : virtual public Thing Position position; - using CountMap = std::map; + typedef std::map CountMap; CountMap damageMap; std::list summons; @@ -488,6 +478,10 @@ class Creature : virtual public Thing 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; @@ -498,7 +492,8 @@ class Creature : virtual public Thing uint32_t blockCount = 0; uint32_t blockTicks = 0; uint32_t lastStepCost = 1; - uint32_t baseSpeed = 220; + uint32_t baseSpeed = 70; + uint32_t latestKillEvent = 0; int32_t varSpeed = 0; int32_t health = 1000; int32_t healthMax = 1000; @@ -524,7 +519,6 @@ class Creature : virtual public Thing bool hasFollowPath = false; bool forceUpdateFollowPath = false; bool hiddenHealth = false; - bool canUseDefense = true; //creature script events bool hasEventRegistered(CreatureEventType_t event) const { @@ -556,6 +550,7 @@ class Creature : virtual public Thing friend class Game; friend class Map; friend class LuaScriptInterface; + friend class Combat; }; #endif diff --git a/src/creatureevent.cpp b/src/creatureevent.cpp index 3e4e7b6..eb72b28 100644 --- a/src/creatureevent.cpp +++ b/src/creatureevent.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,15 +29,22 @@ CreatureEvents::CreatureEvents() : scriptInterface.initState(); } -void CreatureEvents::clear(bool fromLua) +CreatureEvents::~CreatureEvents() { - for (auto it = creatureEvents.begin(); it != creatureEvents.end(); ++it) { - if (fromLua == it->second.fromLua) { - it->second.clearEvent(); - } + for (const auto& it : creatureEvents) { + delete it.second; + } +} + +void CreatureEvents::clear() +{ + //clear creature events + for (const auto& it : creatureEvents) { + it.second->clearEvent(); } - reInitState(fromLua); + //clear lua state + scriptInterface.reInitState(); } LuaScriptInterface& CreatureEvents::getScriptInterface() @@ -50,17 +57,17 @@ std::string CreatureEvents::getScriptBaseName() const return "creaturescripts"; } -Event_ptr CreatureEvents::getEvent(const std::string& nodeName) +Event* CreatureEvents::getEvent(const std::string& nodeName) { if (strcasecmp(nodeName.c_str(), "event") != 0) { return nullptr; } - return Event_ptr(new CreatureEvent(&scriptInterface)); + return new CreatureEvent(&scriptInterface); } -bool CreatureEvents::registerEvent(Event_ptr event, const pugi::xml_node&) +bool CreatureEvents::registerEvent(Event* event, const pugi::xml_node&) { - CreatureEvent_ptr creatureEvent{static_cast(event.release())}; //event is guaranteed to be a CreatureEvent + CreatureEvent* creatureEvent = static_cast(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; @@ -71,37 +78,13 @@ bool CreatureEvents::registerEvent(Event_ptr event, const pugi::xml_node&) //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.get()); + oldEvent->copyEvent(creatureEvent); } return false; } else { //if not, register it normally - creatureEvents.emplace(creatureEvent->getName(), std::move(*creatureEvent)); - return true; - } -} - -bool CreatureEvents::registerLuaEvent(CreatureEvent* event) -{ - CreatureEvent_ptr creatureEvent{ event }; - if (creatureEvent->getEventType() == CREATURE_EVENT_NONE) { - std::cout << "Error: [CreatureEvents::registerLuaEvent] 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.get()); - } - - return false; - } else { - //if not, register it normally - creatureEvents.emplace(creatureEvent->getName(), std::move(*creatureEvent)); + creatureEvents[creatureEvent->getName()] = creatureEvent; return true; } } @@ -110,8 +93,8 @@ CreatureEvent* CreatureEvents::getEventByName(const std::string& name, bool forc { auto it = creatureEvents.find(name); if (it != creatureEvents.end()) { - if (!forceLoaded || it->second.isLoaded()) { - return &it->second; + if (!forceLoaded || it->second->isLoaded()) { + return it->second; } } return nullptr; @@ -121,8 +104,8 @@ 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)) { + if (it.second->getEventType() == CREATURE_EVENT_LOGIN) { + if (!it.second->executeOnLogin(player)) { return false; } } @@ -134,8 +117,8 @@ 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)) { + if (it.second->getEventType() == CREATURE_EVENT_LOGOUT) { + if (!it.second->executeOnLogout(player)) { return false; } } @@ -146,9 +129,9 @@ bool CreatureEvents::playerLogout(Player* player) const bool CreatureEvents::playerAdvance(Player* player, skills_t skill, uint32_t oldLevel, uint32_t newLevel) { - for (auto& it : creatureEvents) { - if (it.second.getEventType() == CREATURE_EVENT_ADVANCE) { - if (!it.second.executeAdvance(player, skill, oldLevel, newLevel)) { + for (const auto& it : creatureEvents) { + if (it.second->getEventType() == CREATURE_EVENT_ADVANCE) { + if (!it.second->executeAdvance(player, skill, oldLevel, newLevel)) { return false; } } @@ -194,10 +177,6 @@ bool CreatureEvent::configureEvent(const pugi::xml_node& node) type = CREATURE_EVENT_KILL; } else if (tmpStr == "advance") { type = CREATURE_EVENT_ADVANCE; - } else if (tmpStr == "modalwindow") { - type = CREATURE_EVENT_MODALWINDOW; - } else if (tmpStr == "textedit") { - type = CREATURE_EVENT_TEXTEDIT; } else if (tmpStr == "healthchange") { type = CREATURE_EVENT_HEALTHCHANGE; } else if (tmpStr == "manachange") { @@ -238,12 +217,6 @@ std::string CreatureEvent::getScriptEventName() const case CREATURE_EVENT_ADVANCE: return "onAdvance"; - case CREATURE_EVENT_MODALWINDOW: - return "onModalWindow"; - - case CREATURE_EVENT_TEXTEDIT: - return "onTextEdit"; - case CREATURE_EVENT_HEALTHCHANGE: return "onHealthChange"; @@ -275,7 +248,7 @@ void CreatureEvent::clearEvent() loaded = false; } -bool CreatureEvent::executeOnLogin(Player* player) const +bool CreatureEvent::executeOnLogin(Player* player) { //onLogin(player) if (!scriptInterface->reserveScriptEnv()) { @@ -294,7 +267,7 @@ bool CreatureEvent::executeOnLogin(Player* player) const return scriptInterface->callFunction(1); } -bool CreatureEvent::executeOnLogout(Player* player) const +bool CreatureEvent::executeOnLogout(Player* player) { //onLogout(player) if (!scriptInterface->reserveScriptEnv()) { @@ -446,56 +419,9 @@ void CreatureEvent::executeOnKill(Creature* creature, Creature* target) scriptInterface->callVoidFunction(2); } -void CreatureEvent::executeModalWindow(Player* player, uint32_t modalWindowId, uint8_t buttonId, uint8_t choiceId) -{ - //onModalWindow(player, modalWindowId, buttonId, choiceId) - if (!scriptInterface->reserveScriptEnv()) { - std::cout << "[Error - CreatureEvent::executeModalWindow] Call stack overflow" << std::endl; - return; - } - - 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, modalWindowId); - lua_pushnumber(L, buttonId); - lua_pushnumber(L, choiceId); - - scriptInterface->callVoidFunction(4); -} - -bool CreatureEvent::executeTextEdit(Player* player, Item* item, const std::string& text) -{ - //onTextEdit(player, item, text) - if (!scriptInterface->reserveScriptEnv()) { - std::cout << "[Error - CreatureEvent::executeTextEdit] 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"); - - LuaScriptInterface::pushThing(L, item); - LuaScriptInterface::pushString(L, text); - - return scriptInterface->callFunction(3); -} - void CreatureEvent::executeHealthChange(Creature* creature, Creature* attacker, CombatDamage& damage) { - //onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin) + //onHealthChange(creature, attacker, value, type, min, max, origin) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeHealthChange] Call stack overflow" << std::endl; return; @@ -512,7 +438,8 @@ void CreatureEvent::executeHealthChange(Creature* creature, Creature* attacker, if (attacker) { LuaScriptInterface::pushUserdata(L, attacker); LuaScriptInterface::setCreatureMetatable(L, -1, attacker); - } else { + } + else { lua_pushnil(L); } @@ -520,16 +447,16 @@ void CreatureEvent::executeHealthChange(Creature* creature, Creature* attacker, if (scriptInterface->protectedCall(L, 7, 4) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); - } else { - damage.primary.value = std::abs(LuaScriptInterface::getNumber(L, -4)); - damage.primary.type = LuaScriptInterface::getNumber(L, -3); - damage.secondary.value = std::abs(LuaScriptInterface::getNumber(L, -2)); - damage.secondary.type = LuaScriptInterface::getNumber(L, -1); + } + else { + damage.value = std::abs(LuaScriptInterface::getNumber(L, -4)); + damage.type = LuaScriptInterface::getNumber(L, -3); + damage.min = std::abs(LuaScriptInterface::getNumber(L, -2)); + damage.max = LuaScriptInterface::getNumber(L, -1); lua_pop(L, 4); - if (damage.primary.type != COMBAT_HEALING) { - damage.primary.value = -damage.primary.value; - damage.secondary.value = -damage.secondary.value; + if (damage.type != COMBAT_HEALING) { + damage.value = -damage.value; } } @@ -537,7 +464,7 @@ void CreatureEvent::executeHealthChange(Creature* creature, Creature* attacker, } void CreatureEvent::executeManaChange(Creature* creature, Creature* attacker, CombatDamage& damage) { - //onManaChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin) + //onManaChange(creature, attacker, value, type, min, max, origin) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeManaChange] Call stack overflow" << std::endl; return; @@ -554,7 +481,8 @@ void CreatureEvent::executeManaChange(Creature* creature, Creature* attacker, Co if (attacker) { LuaScriptInterface::pushUserdata(L, attacker); LuaScriptInterface::setCreatureMetatable(L, -1, attacker); - } else { + } + else { lua_pushnil(L); } @@ -562,12 +490,9 @@ void CreatureEvent::executeManaChange(Creature* creature, Creature* attacker, Co if (scriptInterface->protectedCall(L, 7, 4) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); - } else { - damage.primary.value = LuaScriptInterface::getNumber(L, -4); - damage.primary.type = LuaScriptInterface::getNumber(L, -3); - damage.secondary.value = LuaScriptInterface::getNumber(L, -2); - damage.secondary.type = LuaScriptInterface::getNumber(L, -1); - lua_pop(L, 4); + } + else { + damage = LuaScriptInterface::getCombatDamage(L); } scriptInterface->resetScriptEnv(); diff --git a/src/creatureevent.h b/src/creatureevent.h index 643ac85..6dbdeac 100644 --- a/src/creatureevent.h +++ b/src/creatureevent.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,9 +24,6 @@ #include "baseevents.h" #include "enums.h" -class CreatureEvent; -using CreatureEvent_ptr = std::unique_ptr; - enum CreatureEventType_t { CREATURE_EVENT_NONE, CREATURE_EVENT_LOGIN, @@ -36,69 +33,18 @@ enum CreatureEventType_t { CREATURE_EVENT_DEATH, CREATURE_EVENT_KILL, CREATURE_EVENT_ADVANCE, - CREATURE_EVENT_MODALWINDOW, - CREATURE_EVENT_TEXTEDIT, CREATURE_EVENT_HEALTHCHANGE, CREATURE_EVENT_MANACHANGE, CREATURE_EVENT_EXTENDED_OPCODE, // otclient additional network opcodes }; -class CreatureEvent final : public Event -{ - public: - explicit CreatureEvent(LuaScriptInterface* interface); - - bool configureEvent(const pugi::xml_node& node) override; - - CreatureEventType_t getEventType() const { - return type; - } - void setEventType(CreatureEventType_t eventType) { - type = eventType; - } - const std::string& getName() const { - return eventName; - } - void setName(const std::string& name) { - eventName = name; - } - bool isLoaded() const { - return loaded; - } - void setLoaded(bool b) { - loaded = b; - } - - void clearEvent(); - void copyEvent(CreatureEvent* creatureEvent); - - //scripting - bool executeOnLogin(Player* player) const; - bool executeOnLogout(Player* player) const; - 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 executeModalWindow(Player* player, uint32_t modalWindowId, uint8_t buttonId, uint8_t choiceId); - bool executeTextEdit(Player* player, Item* item, const std::string& text); - void executeHealthChange(Creature* creature, Creature* attacker, CombatDamage& damage); - void executeManaChange(Creature* creature, Creature* attacker, CombatDamage& damage); - void executeExtendedOpcode(Player* player, uint8_t opcode, const std::string& buffer); - // - - private: - std::string getScriptEventName() const override; - - std::string eventName; - CreatureEventType_t type; - bool loaded; -}; +class CreatureEvent; class CreatureEvents final : public BaseEvents { public: CreatureEvents(); + ~CreatureEvents(); // non-copyable CreatureEvents(const CreatureEvents&) = delete; @@ -111,20 +57,59 @@ class CreatureEvents final : public BaseEvents CreatureEvent* getEventByName(const std::string& name, bool forceLoaded = true); - bool registerLuaEvent(CreatureEvent* event); - void clear(bool fromLua) override final; - - private: - LuaScriptInterface& getScriptInterface() override; - std::string getScriptBaseName() const override; - Event_ptr getEvent(const std::string& nodeName) override; - bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; + 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 - using CreatureEventMap = std::map; - CreatureEventMap creatureEvents; + typedef std::map 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 executeHealthChange(Creature* creature, Creature* attacker, CombatDamage& damage); + void executeManaChange(Creature* creature, Creature* attacker, CombatDamage& damage); + 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 diff --git a/src/cylinder.cpp b/src/cylinder.cpp index 4032343..406ca9a 100644 --- a/src/cylinder.cpp +++ b/src/cylinder.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/cylinder.h b/src/cylinder.h index 63a794e..872609c 100644 --- a/src/cylinder.h +++ b/src/cylinder.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -37,6 +37,7 @@ enum cylinderflags_t { 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 { diff --git a/src/database.cpp b/src/database.cpp index 8e74801..0dfc590 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -255,7 +255,7 @@ 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()) { + if (length > Database::getInstance()->getMaxPacketSize() && !execute()) { return false; } @@ -288,7 +288,7 @@ bool DBInsert::execute() } // executes buffer - bool res = Database::getInstance().executeQuery(query + values); + bool res = Database::getInstance()->executeQuery(query + values); values.clear(); length = query.length(); return res; diff --git a/src/database.h b/src/database.h index 51c3d78..e0168ba 100644 --- a/src/database.h +++ b/src/database.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,7 +25,7 @@ #include class DBResult; -using DBResult_ptr = std::shared_ptr; +typedef std::shared_ptr DBResult_ptr; class Database { @@ -42,10 +42,10 @@ class Database * * @return database connection handler singleton */ - static Database& getInstance() + static Database* getInstance() { static Database instance; - return instance; + return &instance; } /** @@ -117,7 +117,7 @@ class Database return maxPacketSize; } - private: + protected: /** * Transaction related methods. * @@ -129,6 +129,7 @@ class Database bool rollback(); bool commit(); + private: MYSQL* handle = nullptr; std::recursive_mutex databaseLock; uint64_t maxPacketSize = 1048576; @@ -194,7 +195,7 @@ class DBInsert bool addRow(std::ostringstream& row); bool execute(); - private: + protected: std::string query; std::string values; size_t length; @@ -207,7 +208,7 @@ class DBTransaction ~DBTransaction() { if (state == STATE_START) { - Database::getInstance().rollback(); + Database::getInstance()->rollback(); } } @@ -217,7 +218,7 @@ class DBTransaction bool begin() { state = STATE_START; - return Database::getInstance().beginTransaction(); + return Database::getInstance()->beginTransaction(); } bool commit() { @@ -225,15 +226,15 @@ class DBTransaction return false; } - state = STATE_COMMIT; - return Database::getInstance().commit(); + state = STEATE_COMMIT; + return Database::getInstance()->commit(); } private: enum TransactionStates_t { STATE_NO_START, STATE_START, - STATE_COMMIT, + STEATE_COMMIT, }; TransactionStates_t state = STATE_NO_START; diff --git a/src/databasemanager.cpp b/src/databasemanager.cpp index 6a57652..dc38b37 100644 --- a/src/databasemanager.cpp +++ b/src/databasemanager.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,11 +27,11 @@ extern ConfigManager g_config; bool DatabaseManager::optimizeTables() { - Database& db = Database::getInstance(); + 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()); + 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; } @@ -43,7 +43,7 @@ bool DatabaseManager::optimizeTables() query.str(std::string()); query << "OPTIMIZE TABLE `" << tableName << '`'; - if (db.executeQuery(query.str())) { + if (db->executeQuery(query.str())) { std::cout << " [success]" << std::endl; } else { std::cout << " [failed]" << std::endl; @@ -54,27 +54,27 @@ bool DatabaseManager::optimizeTables() bool DatabaseManager::tableExists(const std::string& tableName) { - Database& db = Database::getInstance(); + 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; + 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(); + 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; + 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)"); + 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; } @@ -85,68 +85,13 @@ int32_t DatabaseManager::getDatabaseVersion() return -1; } -void DatabaseManager::updateDatabase() -{ - lua_State* L = luaL_newstate(); - if (!L) { - return; - } - - luaL_openlibs(L); - -#ifndef LUAJIT_VERSION - //bit operations for Lua, based on bitlib project release 24 - //bit.bnot, bit.band, bit.bor, bit.bxor, bit.lshift, bit.rshift - luaL_register(L, "bit", LuaScriptInterface::luaBitReg); -#endif - - //db table - luaL_register(L, "db", LuaScriptInterface::luaDatabaseTable); - - //result table - luaL_register(L, "result", LuaScriptInterface::luaResultTable); - - int32_t version = getDatabaseVersion(); - do { - std::ostringstream ss; - ss << "data/migrations/" << version << ".lua"; - if (luaL_dofile(L, ss.str().c_str()) != 0) { - std::cout << "[Error - DatabaseManager::updateDatabase - Version: " << version << "] " << lua_tostring(L, -1) << std::endl; - break; - } - - if (!LuaScriptInterface::reserveScriptEnv()) { - break; - } - - lua_getglobal(L, "onUpdateDatabase"); - if (lua_pcall(L, 0, 1, 0) != 0) { - LuaScriptInterface::resetScriptEnv(); - std::cout << "[Error - DatabaseManager::updateDatabase - Version: " << version << "] " << lua_tostring(L, -1) << std::endl; - break; - } - - if (!LuaScriptInterface::getBoolean(L, -1, false)) { - LuaScriptInterface::resetScriptEnv(); - break; - } - - version++; - std::cout << "> Database has been updated to version " << version << '.' << std::endl; - registerDatabaseConfig("db_version", version); - - LuaScriptInterface::resetScriptEnv(); - } while (true); - lua_close(L); -} - bool DatabaseManager::getDatabaseConfig(const std::string& config, int32_t& value) { - Database& db = Database::getInstance(); + Database* db = Database::getInstance(); std::ostringstream query; - query << "SELECT `value` FROM `server_config` WHERE `config` = " << db.escapeString(config); + query << "SELECT `value` FROM `server_config` WHERE `config` = " << db->escapeString(config); - DBResult_ptr result = db.storeQuery(query.str()); + DBResult_ptr result = db->storeQuery(query.str()); if (!result) { return false; } @@ -157,16 +102,16 @@ bool DatabaseManager::getDatabaseConfig(const std::string& config, int32_t& valu void DatabaseManager::registerDatabaseConfig(const std::string& config, int32_t value) { - Database& db = Database::getInstance(); + Database* db = Database::getInstance(); std::ostringstream query; int32_t tmp; if (!getDatabaseConfig(config, tmp)) { - query << "INSERT INTO `server_config` VALUES (" << db.escapeString(config) << ", '" << value << "')"; + query << "INSERT INTO `server_config` VALUES (" << db->escapeString(config) << ", '" << value << "')"; } else { - query << "UPDATE `server_config` SET `value` = '" << value << "' WHERE `config` = " << db.escapeString(config); + query << "UPDATE `server_config` SET `value` = '" << value << "' WHERE `config` = " << db->escapeString(config); } - db.executeQuery(query.str()); + db->executeQuery(query.str()); } diff --git a/src/databasemanager.h b/src/databasemanager.h index 2e60c44..d15f1ec 100644 --- a/src/databasemanager.h +++ b/src/databasemanager.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,13 +24,12 @@ class DatabaseManager { public: - static bool tableExists(const std::string& tableName); + static bool tableExists(const std::string& table); static int32_t getDatabaseVersion(); static bool isDatabaseSetup(); static bool optimizeTables(); - static void updateDatabase(); static bool getDatabaseConfig(const std::string& config, int32_t& value); static void registerDatabaseConfig(const std::string& config, int32_t value); diff --git a/src/databasetasks.cpp b/src/databasetasks.cpp index 60969b1..37f1176 100644 --- a/src/databasetasks.cpp +++ b/src/databasetasks.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -51,13 +51,13 @@ void DatabaseTasks::threadMain() } } -void DatabaseTasks::addTask(std::string query, std::function callback/* = nullptr*/, bool store/* = false*/) +void DatabaseTasks::addTask(const std::string& query, const std::function& callback/* = nullptr*/, bool store/* = false*/) { bool signal = false; taskLock.lock(); if (getState() == THREAD_STATE_RUNNING) { signal = tasks.empty(); - tasks.emplace_back(std::move(query), std::move(callback), store); + tasks.emplace_back(query, callback, store); } taskLock.unlock(); @@ -85,13 +85,9 @@ void DatabaseTasks::runTask(const DatabaseTask& task) void DatabaseTasks::flush() { - std::unique_lock guard{ taskLock }; while (!tasks.empty()) { - auto task = std::move(tasks.front()); + runTask(tasks.front()); tasks.pop_front(); - guard.unlock(); - runTask(task); - guard.lock(); } } @@ -99,7 +95,7 @@ void DatabaseTasks::shutdown() { taskLock.lock(); setState(THREAD_STATE_TERMINATED); - taskLock.unlock(); flush(); + taskLock.unlock(); taskSignal.notify_one(); } diff --git a/src/databasetasks.h b/src/databasetasks.h index 2d19442..42b36e2 100644 --- a/src/databasetasks.h +++ b/src/databasetasks.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,7 +26,7 @@ #include "enums.h" struct DatabaseTask { - DatabaseTask(std::string&& query, std::function&& callback, bool store) : + DatabaseTask(std::string query, std::function callback, bool store) : query(std::move(query)), callback(std::move(callback)), store(store) {} std::string query; @@ -42,7 +42,7 @@ class DatabaseTasks : public ThreadHolder void flush(); void shutdown(); - void addTask(std::string query, std::function callback = nullptr, bool store = false); + void addTask(const std::string& query, const std::function& callback = nullptr, bool store = false); void threadMain(); private: diff --git a/src/definitions.h b/src/definitions.h index b779f71..63c9e08 100644 --- a/src/definitions.h +++ b/src/definitions.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,13 +20,13 @@ #ifndef FS_DEFINITIONS_H_877452FEC245450C9F96B8FD268D8963 #define FS_DEFINITIONS_H_877452FEC245450C9F96B8FD268D8963 -static constexpr auto STATUS_SERVER_NAME = "The Forgotten Server"; -static constexpr auto STATUS_SERVER_VERSION = "1.3"; -static constexpr auto STATUS_SERVER_DEVELOPERS = "Mark Samman"; +static constexpr auto STATUS_SERVER_NAME = "Sabrehaven"; +static constexpr auto STATUS_SERVER_VERSION = "1.0"; +static constexpr auto STATUS_SERVER_DEVELOPERS = "OTLand community & Sabrehaven Developers Team"; -static constexpr auto CLIENT_VERSION_MIN = 1097; -static constexpr auto CLIENT_VERSION_MAX = 1098; -static constexpr auto CLIENT_VERSION_STR = "10.98"; +static constexpr auto CLIENT_VERSION_MIN = 781; +static constexpr auto CLIENT_VERSION_MAX = 781; +static constexpr auto CLIENT_VERSION_STR = "7.81"; static constexpr auto AUTHENTICATOR_DIGITS = 6U; static constexpr auto AUTHENTICATOR_PERIOD = 30U; @@ -60,6 +60,7 @@ static constexpr auto AUTHENTICATOR_PERIOD = 30U; #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 +#pragma warning(disable:4996) // inetpton warning #endif #define strcasecmp _stricmp diff --git a/src/depotchest.cpp b/src/depotchest.cpp deleted file mode 100644 index 8d28e22..0000000 --- a/src/depotchest.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "otpch.h" - -#include "depotchest.h" -#include "tools.h" - -DepotChest::DepotChest(uint16_t type) : - Container(type), maxDepotItems(1500) {} - -ReturnValue DepotChest::queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor/* = nullptr*/) 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 (getItemHoldingCount() + addCount > maxDepotItems) { - return RETURNVALUE_DEPOTISFULL; - } - } - - return Container::queryAdd(index, thing, count, flags, actor); -} - -void DepotChest::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t) -{ - Cylinder* parent = getParent(); - if (parent != nullptr) { - parent->postAddNotification(thing, oldParent, index, LINK_PARENT); - } -} - -void DepotChest::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) -{ - Cylinder* parent = getParent(); - if (parent != nullptr) { - parent->postRemoveNotification(thing, newParent, index, LINK_PARENT); - } -} - -Cylinder* DepotChest::getParent() const -{ - if (parent) { - return parent->getParent(); - } - return nullptr; -} diff --git a/src/depotchest.h b/src/depotchest.h deleted file mode 100644 index b1db4ea..0000000 --- a/src/depotchest.h +++ /dev/null @@ -1,57 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_DEPOTCHEST_H_6538526014684E3DBC92CC12815B6766 -#define FS_DEPOTCHEST_H_6538526014684E3DBC92CC12815B6766 - -#include "container.h" - -class DepotChest final : public Container -{ - public: - explicit DepotChest(uint16_t type); - - //serialization - void setMaxDepotItems(uint32_t maxitems) { - maxDepotItems = maxitems; - } - - //cylinder implementations - ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const override; - - 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; - - //overrides - bool canRemove() const override { - return false; - } - - Cylinder* getParent() const override; - Cylinder* getRealParent() const override { - return parent; - } - - private: - uint32_t maxDepotItems; -}; - -#endif - diff --git a/src/depotlocker.cpp b/src/depotlocker.cpp index 6cce856..09189a1 100644 --- a/src/depotlocker.cpp +++ b/src/depotlocker.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,9 +20,12 @@ #include "otpch.h" #include "depotlocker.h" +#include "creature.h" +#include "player.h" +#include "tools.h" DepotLocker::DepotLocker(uint16_t type) : - Container(type, 3), depotId(0) {} + Container(type, 30), depotId(0) {} Attr_ReadValue DepotLocker::readAttr(AttrTypes_t attr, PropStream& propStream) { @@ -35,9 +38,40 @@ Attr_ReadValue DepotLocker::readAttr(AttrTypes_t attr, PropStream& propStream) return Item::readAttr(attr, propStream); } -ReturnValue DepotLocker::queryAdd(int32_t, const Thing&, uint32_t, uint32_t, Creature*) const +ReturnValue DepotLocker::queryAdd(int32_t index, const Thing& thing, uint32_t count, uint32_t flags, Creature* actor) const { - return RETURNVALUE_NOTENOUGHROOM; + 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) @@ -53,12 +87,3 @@ void DepotLocker::postRemoveNotification(Thing* thing, const Cylinder* newParent parent->postRemoveNotification(thing, newParent, index, LINK_PARENT); } } - -void DepotLocker::removeInbox(Inbox* inbox) -{ - auto cit = std::find(itemlist.begin(), itemlist.end(), inbox); - if (cit == itemlist.end()) { - return; - } - itemlist.erase(cit); -} diff --git a/src/depotlocker.h b/src/depotlocker.h index 750ec42..094defc 100644 --- a/src/depotlocker.h +++ b/src/depotlocker.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,24 +21,21 @@ #define FS_DEPOTLOCKER_H_53AD8E0606A34070B87F792611F4F3F8 #include "container.h" -#include "inbox.h" class DepotLocker final : public Container { public: explicit DepotLocker(uint16_t type); - DepotLocker* getDepotLocker() override { + DepotLocker* getDepotLocker() final { return this; } - const DepotLocker* getDepotLocker() const override { + const DepotLocker* getDepotLocker() const final { return this; } - void removeInbox(Inbox* inbox); - //serialization - Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) final; uint16_t getDepotId() const { return depotId; @@ -49,12 +46,12 @@ class DepotLocker final : public Container //cylinder implementations ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const override; + uint32_t flags, Creature* actor = nullptr) 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 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 override { + bool canRemove() const final { return false; } diff --git a/src/enums.h b/src/enums.h index 579a3c9..d6bf53a 100644 --- a/src/enums.h +++ b/src/enums.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,43 +20,6 @@ #ifndef FS_ENUMS_H_003445999FEE4A67BCECBE918B0124CE #define FS_ENUMS_H_003445999FEE4A67BCECBE918B0124CE -enum RuleViolationType_t : uint8_t { - REPORT_TYPE_NAME = 0, - REPORT_TYPE_STATEMENT = 1, - REPORT_TYPE_BOT = 2 -}; - -enum RuleViolationReasons_t : uint8_t { - REPORT_REASON_NAMEINAPPROPRIATE = 0, - REPORT_REASON_NAMEPOORFORMATTED = 1, - REPORT_REASON_NAMEADVERTISING = 2, - REPORT_REASON_NAMEUNFITTING = 3, - REPORT_REASON_NAMERULEVIOLATION = 4, - REPORT_REASON_INSULTINGSTATEMENT = 5, - REPORT_REASON_SPAMMING = 6, - REPORT_REASON_ADVERTISINGSTATEMENT = 7, - REPORT_REASON_UNFITTINGSTATEMENT = 8, - REPORT_REASON_LANGUAGESTATEMENT = 9, - REPORT_REASON_DISCLOSURE = 10, - REPORT_REASON_RULEVIOLATION = 11, - REPORT_REASON_STATEMENT_BUGABUSE = 12, - REPORT_REASON_UNOFFICIALSOFTWARE = 13, - REPORT_REASON_PRETENDING = 14, - REPORT_REASON_HARASSINGOWNERS = 15, - REPORT_REASON_FALSEINFO = 16, - REPORT_REASON_ACCOUNTSHARING = 17, - REPORT_REASON_STEALINGDATA = 18, - REPORT_REASON_SERVICEATTACKING = 19, - REPORT_REASON_SERVICEAGREEMENT = 20 -}; - -enum BugReportType_t : uint8_t { - BUG_CATEGORY_MAP = 0, - BUG_CATEGORY_TYPO = 1, - BUG_CATEGORY_TECHNICAL = 2, - BUG_CATEGORY_OTHER = 3 -}; - enum ThreadState { THREAD_STATE_RUNNING, THREAD_STATE_CLOSING, @@ -67,7 +30,7 @@ enum itemAttrTypes : uint32_t { ITEM_ATTRIBUTE_NONE, ITEM_ATTRIBUTE_ACTIONID = 1 << 0, - ITEM_ATTRIBUTE_UNIQUEID = 1 << 1, + ITEM_ATTRIBUTE_MOVEMENTID = 1 << 1, ITEM_ATTRIBUTE_DESCRIPTION = 1 << 2, ITEM_ATTRIBUTE_TEXT = 1 << 3, ITEM_ATTRIBUTE_DATE = 1 << 4, @@ -78,60 +41,26 @@ enum itemAttrTypes : uint32_t { ITEM_ATTRIBUTE_WEIGHT = 1 << 9, ITEM_ATTRIBUTE_ATTACK = 1 << 10, ITEM_ATTRIBUTE_DEFENSE = 1 << 11, - ITEM_ATTRIBUTE_EXTRADEFENSE = 1 << 12, - ITEM_ATTRIBUTE_ARMOR = 1 << 13, - ITEM_ATTRIBUTE_HITCHANCE = 1 << 14, - ITEM_ATTRIBUTE_SHOOTRANGE = 1 << 15, - ITEM_ATTRIBUTE_OWNER = 1 << 16, - ITEM_ATTRIBUTE_DURATION = 1 << 17, - ITEM_ATTRIBUTE_DECAYSTATE = 1 << 18, - ITEM_ATTRIBUTE_CORPSEOWNER = 1 << 19, - ITEM_ATTRIBUTE_CHARGES = 1 << 20, - ITEM_ATTRIBUTE_FLUIDTYPE = 1 << 21, - ITEM_ATTRIBUTE_DOORID = 1 << 22, - ITEM_ATTRIBUTE_DECAYTO = 1 << 23, - - ITEM_ATTRIBUTE_CUSTOM = 1U << 31 + 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, - VIPSTATUS_PENDING = 2 -}; - -enum MarketAction_t { - MARKETACTION_BUY = 0, - MARKETACTION_SELL = 1, -}; - -enum MarketRequest_t { - MARKETREQUEST_OWN_OFFERS = 0xFFFE, - MARKETREQUEST_OWN_HISTORY = 0xFFFF, -}; - -enum MarketOfferState_t { - OFFERSTATE_ACTIVE = 0, - OFFERSTATE_CANCELLED = 1, - OFFERSTATE_EXPIRED = 2, - OFFERSTATE_ACCEPTED = 3, - - OFFERSTATE_ACCEPTEDEX = 255, -}; - -enum ChannelEvent_t : uint8_t { - CHANNELEVENT_JOIN = 0, - CHANNELEVENT_LEAVE = 1, - CHANNELEVENT_INVITE = 2, - CHANNELEVENT_EXCLUDE = 3, -}; - -enum CreatureType_t : uint8_t { - CREATURETYPE_PLAYER = 0, - CREATURETYPE_MONSTER = 1, - CREATURETYPE_NPC = 2, - CREATURETYPE_SUMMON_OWN = 3, - CREATURETYPE_SUMMON_OTHERS = 4, }; enum OperatingSystem_t : uint8_t { @@ -146,20 +75,6 @@ enum OperatingSystem_t : uint8_t { CLIENTOS_OTCLIENT_MAC = 12, }; -enum SpellGroup_t : uint8_t { - SPELLGROUP_NONE = 0, - SPELLGROUP_ATTACK = 1, - SPELLGROUP_HEALING = 2, - SPELLGROUP_SUPPORT = 3, - SPELLGROUP_SPECIAL = 4, -}; - -enum SpellType_t : uint8_t { - SPELL_UNDEFINED = 0, - SPELL_INSTANT = 1, - SPELL_RUNE = 2, -}; - enum AccountType_t : uint8_t { ACCOUNT_TYPE_NORMAL = 1, ACCOUNT_TYPE_TUTOR = 2, @@ -174,7 +89,6 @@ enum RaceType_t : uint8_t { RACE_BLOOD, RACE_UNDEAD, RACE_FIRE, - RACE_ENERGY, }; enum CombatType_t : uint16_t { @@ -189,11 +103,8 @@ enum CombatType_t : uint16_t { COMBAT_MANADRAIN = 1 << 6, COMBAT_HEALING = 1 << 7, COMBAT_DROWNDAMAGE = 1 << 8, - COMBAT_ICEDAMAGE = 1 << 9, - COMBAT_HOLYDAMAGE = 1 << 10, - COMBAT_DEATHDAMAGE = 1 << 11, - COMBAT_COUNT = 12 + COMBAT_COUNT = 10 }; enum CombatParam_t { @@ -207,6 +118,14 @@ enum CombatParam_t { 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 { @@ -234,7 +153,6 @@ enum ConditionParam_t { CONDITION_PARAM_MAXVALUE = 15, CONDITION_PARAM_STARTVALUE = 16, CONDITION_PARAM_TICKINTERVAL = 17, - CONDITION_PARAM_FORCEUPDATE = 18, CONDITION_PARAM_SKILL_MELEE = 19, CONDITION_PARAM_SKILL_FIST = 20, CONDITION_PARAM_SKILL_CLUB = 21, @@ -260,16 +178,13 @@ enum ConditionParam_t { CONDITION_PARAM_SKILL_DISTANCEPERCENT = 41, CONDITION_PARAM_SKILL_SHIELDPERCENT = 42, CONDITION_PARAM_SKILL_FISHINGPERCENT = 43, - CONDITION_PARAM_BUFF_SPELL = 44, + // CONDITION_PARAM_BUFF_SPELL = 44, CONDITION_PARAM_SUBID = 45, CONDITION_PARAM_FIELD = 46, - CONDITION_PARAM_DISABLE_DEFENSE = 47, - CONDITION_PARAM_SPECIALSKILL_CRITICALHITCHANCE = 48, - CONDITION_PARAM_SPECIALSKILL_CRITICALHITAMOUNT = 49, - CONDITION_PARAM_SPECIALSKILL_LIFELEECHCHANCE = 50, - CONDITION_PARAM_SPECIALSKILL_LIFELEECHAMOUNT = 51, - CONDITION_PARAM_SPECIALSKILL_MANALEECHCHANCE = 52, - CONDITION_PARAM_SPECIALSKILL_MANALEECHAMOUNT = 53, + CONDITION_PARAM_CYCLE = 47, + CONDITION_PARAM_HIT_DAMAGE = 48, + CONDITION_PARAM_COUNT = 49, + CONDITION_PARAM_MAX_COUNT = 50, }; enum BlockType_t : uint8_t { @@ -305,18 +220,6 @@ enum stats_t { STAT_LAST = STAT_MAGICPOINTS }; -enum SpecialSkills_t { - SPECIALSKILL_CRITICALHITCHANCE, - SPECIALSKILL_CRITICALHITAMOUNT, - SPECIALSKILL_LIFELEECHCHANCE, - SPECIALSKILL_LIFELEECHAMOUNT, - SPECIALSKILL_MANALEECHCHANCE, - SPECIALSKILL_MANALEECHAMOUNT, - - SPECIALSKILL_FIRST = SPECIALSKILL_CRITICALHITCHANCE, - SPECIALSKILL_LAST = SPECIALSKILL_MANALEECHAMOUNT -}; - enum formulaType_t { COMBAT_FORMULA_UNDEFINED, COMBAT_FORMULA_LEVELMAGIC, @@ -330,31 +233,24 @@ enum ConditionType_t { CONDITION_POISON = 1 << 0, CONDITION_FIRE = 1 << 1, CONDITION_ENERGY = 1 << 2, - CONDITION_BLEEDING = 1 << 3, - CONDITION_HASTE = 1 << 4, - CONDITION_PARALYZE = 1 << 5, - CONDITION_OUTFIT = 1 << 6, - CONDITION_INVISIBLE = 1 << 7, - CONDITION_LIGHT = 1 << 8, - CONDITION_MANASHIELD = 1 << 9, - CONDITION_INFIGHT = 1 << 10, - CONDITION_DRUNK = 1 << 11, - CONDITION_EXHAUST_WEAPON = 1 << 12, // unused - CONDITION_REGENERATION = 1 << 13, - CONDITION_SOUL = 1 << 14, - CONDITION_DROWN = 1 << 15, - CONDITION_MUTED = 1 << 16, - CONDITION_CHANNELMUTEDTICKS = 1 << 17, - CONDITION_YELLTICKS = 1 << 18, - CONDITION_ATTRIBUTES = 1 << 19, - CONDITION_FREEZING = 1 << 20, - CONDITION_DAZZLED = 1 << 21, - CONDITION_CURSED = 1 << 22, - CONDITION_EXHAUST_COMBAT = 1 << 23, // unused - CONDITION_EXHAUST_HEAL = 1 << 24, // unused - CONDITION_PACIFIED = 1 << 25, - CONDITION_SPELLCOOLDOWN = 1 << 26, - CONDITION_SPELLGROUPCOOLDOWN = 1 << 27, + 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, + CONDITION_DROWN = 1 << 20, }; enum ConditionId_t : int8_t { @@ -380,7 +276,11 @@ enum PlayerSex_t : uint8_t { }; enum Vocation_t : uint16_t { - VOCATION_NONE = 0 + VOCATION_NONE, + VOCATION_SORCERER = 1 << 0, + VOCATION_DRUID = 1 << 1, + VOCATION_PALADIN = 1 << 2, + VOCATION_KNIGHT = 1 << 3, }; enum ReturnValue { @@ -443,12 +343,10 @@ enum ReturnValue { RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL, RETURNVALUE_CANNOTCONJUREITEMHERE, RETURNVALUE_YOUNEEDTOSPLITYOURSPEARS, - RETURNVALUE_NAMEISTOOAMBIGUOUS, + RETURNVALUE_NAMEISTOOAMBIGIOUS, RETURNVALUE_CANONLYUSEONESHIELD, RETURNVALUE_NOPARTYMEMBERSINRANGE, RETURNVALUE_YOUARENOTTHEOWNER, - RETURNVALUE_NOSUCHRAIDEXISTS, - RETURNVALUE_ANOTHERRAIDISALREADYEXECUTING, RETURNVALUE_TRADEPLAYERFARAWAY, RETURNVALUE_YOUDONTOWNTHISHOUSE, RETURNVALUE_TRADEPLAYERALREADYOWNSAHOUSE, @@ -456,43 +354,9 @@ enum ReturnValue { RETURNVALUE_YOUCANNOTTRADETHISHOUSE, }; -enum SpeechBubble_t -{ - SPEECHBUBBLE_NONE = 0, - SPEECHBUBBLE_NORMAL = 1, - SPEECHBUBBLE_TRADE = 2, - SPEECHBUBBLE_QUEST = 3, - SPEECHBUBBLE_QUESTTRADER = 4, -}; - -enum MapMark_t -{ - MAPMARK_TICK = 0, - MAPMARK_QUESTION = 1, - MAPMARK_EXCLAMATION = 2, - MAPMARK_STAR = 3, - MAPMARK_CROSS = 4, - MAPMARK_TEMPLE = 5, - MAPMARK_KISS = 6, - MAPMARK_SHOVEL = 7, - MAPMARK_SWORD = 8, - MAPMARK_FLAG = 9, - MAPMARK_LOCK = 10, - MAPMARK_BAG = 11, - MAPMARK_SKULL = 12, - MAPMARK_DOLLAR = 13, - MAPMARK_REDNORTH = 14, - MAPMARK_REDSOUTH = 15, - MAPMARK_REDEAST = 16, - MAPMARK_REDWEST = 17, - MAPMARK_GREENNORTH = 18, - MAPMARK_GREENSOUTH = 19, -}; - struct Outfit_t { uint16_t lookType = 0; uint16_t lookTypeEx = 0; - uint16_t lookMount = 0; uint8_t lookHead = 0; uint8_t lookBody = 0; uint8_t lookLegs = 0; @@ -507,85 +371,6 @@ struct LightInfo { constexpr LightInfo(uint8_t level, uint8_t color) : level(level), color(color) {} }; -struct ShopInfo { - uint16_t itemId; - int32_t subType; - uint32_t buyPrice; - uint32_t sellPrice; - std::string realName; - - ShopInfo() { - itemId = 0; - subType = 1; - buyPrice = 0; - sellPrice = 0; - } - - ShopInfo(uint16_t itemId, int32_t subType = 0, uint32_t buyPrice = 0, uint32_t sellPrice = 0, std::string realName = "") - : itemId(itemId), subType(subType), buyPrice(buyPrice), sellPrice(sellPrice), realName(std::move(realName)) {} -}; - -struct MarketOffer { - uint32_t price; - uint32_t timestamp; - uint16_t amount; - uint16_t counter; - uint16_t itemId; - std::string playerName; -}; - -struct MarketOfferEx { - MarketOfferEx() = default; - MarketOfferEx(MarketOfferEx&& other) : - id(other.id), playerId(other.playerId), timestamp(other.timestamp), price(other.price), - amount(other.amount), counter(other.counter), itemId(other.itemId), type(other.type), - playerName(std::move(other.playerName)) {} - - uint32_t id; - uint32_t playerId; - uint32_t timestamp; - uint32_t price; - uint16_t amount; - uint16_t counter; - uint16_t itemId; - MarketAction_t type; - std::string playerName; -}; - -struct HistoryMarketOffer { - uint32_t timestamp; - uint32_t price; - uint16_t itemId; - uint16_t amount; - MarketOfferState_t state; -}; - -struct MarketStatistics { - MarketStatistics() { - numTransactions = 0; - highestPrice = 0; - totalPrice = 0; - lowestPrice = 0; - } - - uint32_t numTransactions; - uint32_t highestPrice; - uint64_t totalPrice; - uint32_t lowestPrice; -}; - -struct ModalWindow -{ - std::list> buttons, choices; - std::string title, message; - uint32_t id; - uint8_t defaultEnterButton, defaultEscapeButton; - bool priority; - - ModalWindow(uint32_t id, std::string title, std::string message) - : title(std::move(title)), message(std::move(message)), id(id), defaultEnterButton(0xFF), defaultEscapeButton(0xFF), priority(false) {} -}; - enum CombatOrigin { ORIGIN_NONE, @@ -597,31 +382,20 @@ enum CombatOrigin struct CombatDamage { - struct { - CombatType_t type; - int32_t value; - } primary, secondary; - + CombatType_t type; + int32_t value; + int32_t min; + int32_t max; CombatOrigin origin; + CombatDamage() { origin = ORIGIN_NONE; - primary.type = secondary.type = COMBAT_NONE; - primary.value = secondary.value = 0; + type = COMBAT_NONE; + value = 0; + min = 0; + max = 0; } }; -using MarketOfferList = std::list; -using HistoryMarketOfferList = std::list; -using ShopInfoList = std::list; - -enum MonstersEvent_t : uint8_t { - MONSTERS_EVENT_NONE = 0, - MONSTERS_EVENT_THINK = 1, - MONSTERS_EVENT_APPEAR = 2, - MONSTERS_EVENT_DISAPPEAR = 3, - MONSTERS_EVENT_MOVE = 4, - MONSTERS_EVENT_SAY = 5, -}; - #endif diff --git a/src/events.cpp b/src/events.cpp index 9b9c910..868901e 100644 --- a/src/events.cpp +++ b/src/events.cpp @@ -1,6 +1,6 @@ /** * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Copyright (C) 2016 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,9 +29,39 @@ Events::Events() : scriptInterface("Event Interface") { + clear(); scriptInterface.initState(); } +void Events::clear() +{ + // Creature + creatureOnChangeOutfit = -1; + creatureOnAreaCombat = -1; + creatureOnTargetCombat = -1; + + // Party + partyOnJoin = -1; + partyOnLeave = -1; + partyOnDisband = -1; + + // Player + playerOnLook = -1; + playerOnLookInBattleList = -1; + playerOnLookInTrade = -1; + playerOnMoveItem = -1; + playerOnItemMoved = -1; + playerOnMoveCreature = -1; + playerOnTurn = -1; + playerOnTradeRequest = -1; + playerOnTradeAccept = -1; + playerOnGainExperience = -1; + playerOnLoseExperience = -1; + playerOnGainSkillTries = -1; + playerOnReportBug = -1; + +} + bool Events::load() { pugi::xml_document doc; @@ -41,8 +71,6 @@ bool Events::load() return false; } - info = {}; - std::set classes; for (auto eventNode : doc.child("events").children()) { if (!eventNode.attribute("enabled").as_bool()) { @@ -63,69 +91,80 @@ bool Events::load() const int32_t event = scriptInterface.getMetaEvent(className, methodName); if (className == "Creature") { if (methodName == "onChangeOutfit") { - info.creatureOnChangeOutfit = event; - } else if (methodName == "onAreaCombat") { - info.creatureOnAreaCombat = event; - } else if (methodName == "onTargetCombat") { - info.creatureOnTargetCombat = event; - } else { + creatureOnChangeOutfit = event; + } + else if (methodName == "onAreaCombat") { + creatureOnAreaCombat = event; + } + else if (methodName == "onTargetCombat") { + creatureOnTargetCombat = event; + } + else { std::cout << "[Warning - Events::load] Unknown creature method: " << methodName << std::endl; } - } else if (className == "Party") { + } + else if (className == "Party") { if (methodName == "onJoin") { - info.partyOnJoin = event; - } else if (methodName == "onLeave") { - info.partyOnLeave = event; - } else if (methodName == "onDisband") { - info.partyOnDisband = event; - } else if (methodName == "onShareExperience") { - info.partyOnShareExperience = event; - } else { + partyOnJoin = event; + } + else if (methodName == "onLeave") { + partyOnLeave = event; + } + else if (methodName == "onDisband") { + partyOnDisband = event; + } + else if (methodName == "onShareExperience") { + partyOnShareExperience = event; + } + else { std::cout << "[Warning - Events::load] Unknown party method: " << methodName << std::endl; } - } else if (className == "Player") { - if (methodName == "onBrowseField") { - info.playerOnBrowseField = event; - } else if (methodName == "onLook") { - info.playerOnLook = event; - } else if (methodName == "onLookInBattleList") { - info.playerOnLookInBattleList = event; - } else if (methodName == "onLookInTrade") { - info.playerOnLookInTrade = event; - } else if (methodName == "onLookInShop") { - info.playerOnLookInShop = event; - } else if (methodName == "onTradeRequest") { - info.playerOnTradeRequest = event; - } else if (methodName == "onTradeAccept") { - info.playerOnTradeAccept = event; - } else if (methodName == "onMoveItem") { - info.playerOnMoveItem = event; - } else if (methodName == "onItemMoved") { - info.playerOnItemMoved = event; - } else if (methodName == "onMoveCreature") { - info.playerOnMoveCreature = event; - } else if (methodName == "onReportRuleViolation") { - info.playerOnReportRuleViolation = event; - } else if (methodName == "onReportBug") { - info.playerOnReportBug = event; - } else if (methodName == "onTurn") { - info.playerOnTurn = event; - } else if (methodName == "onGainExperience") { - info.playerOnGainExperience = event; - } else if (methodName == "onLoseExperience") { - info.playerOnLoseExperience = event; - } else if (methodName == "onGainSkillTries") { - info.playerOnGainSkillTries = event; - } else { + } + else if (className == "Player") { + if (methodName == "onLook") { + playerOnLook = event; + } + else if (methodName == "onLookInBattleList") { + playerOnLookInBattleList = event; + } + else if (methodName == "onLookInTrade") { + playerOnLookInTrade = event; + } + else if (methodName == "onTradeRequest") { + playerOnTradeRequest = event; + } + else if (methodName == "onTradeAccept") { + playerOnTradeAccept = event; + } + else if (methodName == "onMoveItem") { + playerOnMoveItem = event; + } + else if (methodName == "onItemMoved") { + playerOnItemMoved = event; + } + else if (methodName == "onMoveCreature") { + playerOnMoveCreature = event; + } + else if (methodName == "onReportBug") { + playerOnReportBug = event; + } + else if (methodName == "onTurn") { + playerOnTurn = event; + } + else if (methodName == "onGainExperience") { + playerOnGainExperience = event; + } + else if (methodName == "onLoseExperience") { + playerOnLoseExperience = event; + } + else if (methodName == "onGainSkillTries") { + playerOnGainSkillTries = event; + } + else { std::cout << "[Warning - Events::load] Unknown player method: " << methodName << std::endl; } - } else if (className == "Monster") { - if (methodName == "onDropLoot") { - info.monsterOnDropLoot = event; - } else { - std::cout << "[Warning - Events::load] Unknown monster method: " << methodName << std::endl; - } - } else { + } + else { std::cout << "[Warning - Events::load] Unknown class: " << className << std::endl; } } @@ -136,7 +175,7 @@ bool Events::load() bool Events::eventCreatureOnChangeOutfit(Creature* creature, const Outfit_t& outfit) { // Creature:onChangeOutfit(outfit) or Creature.onChangeOutfit(self, outfit) - if (info.creatureOnChangeOutfit == -1) { + if (creatureOnChangeOutfit == -1) { return true; } @@ -146,10 +185,10 @@ bool Events::eventCreatureOnChangeOutfit(Creature* creature, const Outfit_t& out } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.creatureOnChangeOutfit, &scriptInterface); + env->setScriptId(creatureOnChangeOutfit, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.creatureOnChangeOutfit); + scriptInterface.pushFunction(creatureOnChangeOutfit); LuaScriptInterface::pushUserdata(L, creature); LuaScriptInterface::setCreatureMetatable(L, -1, creature); @@ -162,7 +201,7 @@ bool Events::eventCreatureOnChangeOutfit(Creature* creature, const Outfit_t& out ReturnValue Events::eventCreatureOnAreaCombat(Creature* creature, Tile* tile, bool aggressive) { // Creature:onAreaCombat(tile, aggressive) or Creature.onAreaCombat(self, tile, aggressive) - if (info.creatureOnAreaCombat == -1) { + if (creatureOnAreaCombat == -1) { return RETURNVALUE_NOERROR; } @@ -172,15 +211,16 @@ ReturnValue Events::eventCreatureOnAreaCombat(Creature* creature, Tile* tile, bo } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.creatureOnAreaCombat, &scriptInterface); + env->setScriptId(creatureOnAreaCombat, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.creatureOnAreaCombat); + scriptInterface.pushFunction(creatureOnAreaCombat); if (creature) { LuaScriptInterface::pushUserdata(L, creature); LuaScriptInterface::setCreatureMetatable(L, -1, creature); - } else { + } + else { lua_pushnil(L); } @@ -193,7 +233,8 @@ ReturnValue Events::eventCreatureOnAreaCombat(Creature* creature, Tile* tile, bo if (scriptInterface.protectedCall(L, 3, 1) != 0) { returnValue = RETURNVALUE_NOTPOSSIBLE; LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); - } else { + } + else { returnValue = LuaScriptInterface::getNumber(L, -1); lua_pop(L, 1); } @@ -205,7 +246,7 @@ ReturnValue Events::eventCreatureOnAreaCombat(Creature* creature, Tile* tile, bo ReturnValue Events::eventCreatureOnTargetCombat(Creature* creature, Creature* target) { // Creature:onTargetCombat(target) or Creature.onTargetCombat(self, target) - if (info.creatureOnTargetCombat == -1) { + if (creatureOnTargetCombat == -1) { return RETURNVALUE_NOERROR; } @@ -215,15 +256,16 @@ ReturnValue Events::eventCreatureOnTargetCombat(Creature* creature, Creature* ta } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.creatureOnTargetCombat, &scriptInterface); + env->setScriptId(creatureOnTargetCombat, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.creatureOnTargetCombat); + scriptInterface.pushFunction(creatureOnTargetCombat); if (creature) { LuaScriptInterface::pushUserdata(L, creature); LuaScriptInterface::setCreatureMetatable(L, -1, creature); - } else { + } + else { lua_pushnil(L); } @@ -234,7 +276,8 @@ ReturnValue Events::eventCreatureOnTargetCombat(Creature* creature, Creature* ta if (scriptInterface.protectedCall(L, 2, 1) != 0) { returnValue = RETURNVALUE_NOTPOSSIBLE; LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); - } else { + } + else { returnValue = LuaScriptInterface::getNumber(L, -1); lua_pop(L, 1); } @@ -247,7 +290,7 @@ ReturnValue Events::eventCreatureOnTargetCombat(Creature* creature, Creature* ta bool Events::eventPartyOnJoin(Party* party, Player* player) { // Party:onJoin(player) or Party.onJoin(self, player) - if (info.partyOnJoin == -1) { + if (partyOnJoin == -1) { return true; } @@ -257,10 +300,10 @@ bool Events::eventPartyOnJoin(Party* party, Player* player) } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.partyOnJoin, &scriptInterface); + env->setScriptId(partyOnJoin, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.partyOnJoin); + scriptInterface.pushFunction(partyOnJoin); LuaScriptInterface::pushUserdata(L, party); LuaScriptInterface::setMetatable(L, -1, "Party"); @@ -274,7 +317,7 @@ bool Events::eventPartyOnJoin(Party* party, Player* player) bool Events::eventPartyOnLeave(Party* party, Player* player) { // Party:onLeave(player) or Party.onLeave(self, player) - if (info.partyOnLeave == -1) { + if (partyOnLeave == -1) { return true; } @@ -284,10 +327,10 @@ bool Events::eventPartyOnLeave(Party* party, Player* player) } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.partyOnLeave, &scriptInterface); + env->setScriptId(partyOnLeave, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.partyOnLeave); + scriptInterface.pushFunction(partyOnLeave); LuaScriptInterface::pushUserdata(L, party); LuaScriptInterface::setMetatable(L, -1, "Party"); @@ -301,7 +344,7 @@ bool Events::eventPartyOnLeave(Party* party, Player* player) bool Events::eventPartyOnDisband(Party* party) { // Party:onDisband() or Party.onDisband(self) - if (info.partyOnDisband == -1) { + if (partyOnDisband == -1) { return true; } @@ -311,10 +354,10 @@ bool Events::eventPartyOnDisband(Party* party) } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.partyOnDisband, &scriptInterface); + env->setScriptId(partyOnDisband, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.partyOnDisband); + scriptInterface.pushFunction(partyOnDisband); LuaScriptInterface::pushUserdata(L, party); LuaScriptInterface::setMetatable(L, -1, "Party"); @@ -325,7 +368,7 @@ bool Events::eventPartyOnDisband(Party* party) void Events::eventPartyOnShareExperience(Party* party, uint64_t& exp) { // Party:onShareExperience(exp) or Party.onShareExperience(self, exp) - if (info.partyOnShareExperience == -1) { + if (partyOnShareExperience == -1) { return; } @@ -335,10 +378,10 @@ void Events::eventPartyOnShareExperience(Party* party, uint64_t& exp) } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.partyOnShareExperience, &scriptInterface); + env->setScriptId(partyOnShareExperience, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.partyOnShareExperience); + scriptInterface.pushFunction(partyOnShareExperience); LuaScriptInterface::pushUserdata(L, party); LuaScriptInterface::setMetatable(L, -1, "Party"); @@ -347,7 +390,8 @@ void Events::eventPartyOnShareExperience(Party* party, uint64_t& exp) if (scriptInterface.protectedCall(L, 2, 1) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); - } else { + } + else { exp = LuaScriptInterface::getNumber(L, -1); lua_pop(L, 1); } @@ -355,37 +399,10 @@ void Events::eventPartyOnShareExperience(Party* party, uint64_t& exp) scriptInterface.resetScriptEnv(); } -// Player -bool Events::eventPlayerOnBrowseField(Player* player, const Position& position) -{ - // Player:onBrowseField(position) or Player.onBrowseField(self, position) - if (info.playerOnBrowseField == -1) { - return true; - } - - if (!scriptInterface.reserveScriptEnv()) { - std::cout << "[Error - Events::eventPlayerOnBrowseField] Call stack overflow" << std::endl; - return false; - } - - ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.playerOnBrowseField, &scriptInterface); - - lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.playerOnBrowseField); - - LuaScriptInterface::pushUserdata(L, player); - LuaScriptInterface::setMetatable(L, -1, "Player"); - - LuaScriptInterface::pushPosition(L, position); - - return scriptInterface.callFunction(2); -} - void Events::eventPlayerOnLook(Player* player, const Position& position, Thing* thing, uint8_t stackpos, int32_t lookDistance) { // Player:onLook(thing, position, distance) or Player.onLook(self, thing, position, distance) - if (info.playerOnLook == -1) { + if (playerOnLook == -1) { return; } @@ -395,10 +412,10 @@ void Events::eventPlayerOnLook(Player* player, const Position& position, Thing* } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.playerOnLook, &scriptInterface); + env->setScriptId(playerOnLook, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.playerOnLook); + scriptInterface.pushFunction(playerOnLook); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -406,10 +423,12 @@ void Events::eventPlayerOnLook(Player* player, const Position& position, Thing* if (Creature* creature = thing->getCreature()) { LuaScriptInterface::pushUserdata(L, creature); LuaScriptInterface::setCreatureMetatable(L, -1, creature); - } else if (Item* item = thing->getItem()) { + } + else if (Item* item = thing->getItem()) { LuaScriptInterface::pushUserdata(L, item); LuaScriptInterface::setItemMetatable(L, -1, item); - } else { + } + else { lua_pushnil(L); } @@ -422,7 +441,7 @@ void Events::eventPlayerOnLook(Player* player, const Position& position, Thing* void Events::eventPlayerOnLookInBattleList(Player* player, Creature* creature, int32_t lookDistance) { // Player:onLookInBattleList(creature, position, distance) or Player.onLookInBattleList(self, creature, position, distance) - if (info.playerOnLookInBattleList == -1) { + if (playerOnLookInBattleList == -1) { return; } @@ -432,10 +451,10 @@ void Events::eventPlayerOnLookInBattleList(Player* player, Creature* creature, i } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.playerOnLookInBattleList, &scriptInterface); + env->setScriptId(playerOnLookInBattleList, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.playerOnLookInBattleList); + scriptInterface.pushFunction(playerOnLookInBattleList); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -451,7 +470,7 @@ void Events::eventPlayerOnLookInBattleList(Player* player, Creature* creature, i void Events::eventPlayerOnLookInTrade(Player* player, Player* partner, Item* item, int32_t lookDistance) { // Player:onLookInTrade(partner, item, distance) or Player.onLookInTrade(self, partner, item, distance) - if (info.playerOnLookInTrade == -1) { + if (playerOnLookInTrade == -1) { return; } @@ -461,10 +480,10 @@ void Events::eventPlayerOnLookInTrade(Player* player, Player* partner, Item* ite } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.playerOnLookInTrade, &scriptInterface); + env->setScriptId(playerOnLookInTrade, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.playerOnLookInTrade); + scriptInterface.pushFunction(playerOnLookInTrade); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -480,39 +499,10 @@ void Events::eventPlayerOnLookInTrade(Player* player, Player* partner, Item* ite scriptInterface.callVoidFunction(4); } -bool Events::eventPlayerOnLookInShop(Player* player, const ItemType* itemType, uint8_t count) -{ - // Player:onLookInShop(itemType, count) or Player.onLookInShop(self, itemType, count) - if (info.playerOnLookInShop == -1) { - return true; - } - - if (!scriptInterface.reserveScriptEnv()) { - std::cout << "[Error - Events::eventPlayerOnLookInShop] Call stack overflow" << std::endl; - return false; - } - - ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.playerOnLookInShop, &scriptInterface); - - lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.playerOnLookInShop); - - LuaScriptInterface::pushUserdata(L, player); - LuaScriptInterface::setMetatable(L, -1, "Player"); - - LuaScriptInterface::pushUserdata(L, itemType); - LuaScriptInterface::setMetatable(L, -1, "ItemType"); - - lua_pushnumber(L, count); - - return scriptInterface.callFunction(3); -} - bool Events::eventPlayerOnMoveItem(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder) { // Player:onMoveItem(item, count, fromPosition, toPosition) or Player.onMoveItem(self, item, count, fromPosition, toPosition, fromCylinder, toCylinder) - if (info.playerOnMoveItem == -1) { + if (playerOnMoveItem == -1) { return true; } @@ -522,10 +512,10 @@ bool Events::eventPlayerOnMoveItem(Player* player, Item* item, uint16_t count, c } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.playerOnMoveItem, &scriptInterface); + env->setScriptId(playerOnMoveItem, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.playerOnMoveItem); + scriptInterface.pushFunction(playerOnMoveItem); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -546,7 +536,7 @@ bool Events::eventPlayerOnMoveItem(Player* player, Item* item, uint16_t count, c void Events::eventPlayerOnItemMoved(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder) { // Player:onItemMoved(item, count, fromPosition, toPosition) or Player.onItemMoved(self, item, count, fromPosition, toPosition, fromCylinder, toCylinder) - if (info.playerOnItemMoved == -1) { + if (playerOnItemMoved == -1) { return; } @@ -556,10 +546,10 @@ void Events::eventPlayerOnItemMoved(Player* player, Item* item, uint16_t count, } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.playerOnItemMoved, &scriptInterface); + env->setScriptId(playerOnItemMoved, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.playerOnItemMoved); + scriptInterface.pushFunction(playerOnItemMoved); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -580,7 +570,7 @@ void Events::eventPlayerOnItemMoved(Player* player, Item* item, uint16_t count, bool Events::eventPlayerOnMoveCreature(Player* player, Creature* creature, const Position& fromPosition, const Position& toPosition) { // Player:onMoveCreature(creature, fromPosition, toPosition) or Player.onMoveCreature(self, creature, fromPosition, toPosition) - if (info.playerOnMoveCreature == -1) { + if (playerOnMoveCreature == -1) { return true; } @@ -590,10 +580,10 @@ bool Events::eventPlayerOnMoveCreature(Player* player, Creature* creature, const } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.playerOnMoveCreature, &scriptInterface); + env->setScriptId(playerOnMoveCreature, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.playerOnMoveCreature); + scriptInterface.pushFunction(playerOnMoveCreature); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -607,42 +597,10 @@ bool Events::eventPlayerOnMoveCreature(Player* player, Creature* creature, const return scriptInterface.callFunction(4); } -void Events::eventPlayerOnReportRuleViolation(Player* player, const std::string& targetName, uint8_t reportType, uint8_t reportReason, const std::string& comment, const std::string& translation) -{ - // Player:onReportRuleViolation(targetName, reportType, reportReason, comment, translation) - if (info.playerOnReportRuleViolation == -1) { - return; - } - - if (!scriptInterface.reserveScriptEnv()) { - std::cout << "[Error - Events::eventPlayerOnReportRuleViolation] Call stack overflow" << std::endl; - return; - } - - ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.playerOnReportRuleViolation, &scriptInterface); - - lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.playerOnReportRuleViolation); - - LuaScriptInterface::pushUserdata(L, player); - LuaScriptInterface::setMetatable(L, -1, "Player"); - - LuaScriptInterface::pushString(L, targetName); - - lua_pushnumber(L, reportType); - lua_pushnumber(L, reportReason); - - LuaScriptInterface::pushString(L, comment); - LuaScriptInterface::pushString(L, translation); - - scriptInterface.callVoidFunction(6); -} - -bool Events::eventPlayerOnReportBug(Player* player, const std::string& message, const Position& position, uint8_t category) +bool Events::eventPlayerOnReportBug(Player* player, const std::string& message, const Position& position) { // Player:onReportBug(message, position, category) - if (info.playerOnReportBug == -1) { + if (playerOnReportBug == -1) { return true; } @@ -652,25 +610,24 @@ bool Events::eventPlayerOnReportBug(Player* player, const std::string& message, } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.playerOnReportBug, &scriptInterface); + env->setScriptId(playerOnReportBug, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.playerOnReportBug); + scriptInterface.pushFunction(playerOnReportBug); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); LuaScriptInterface::pushString(L, message); LuaScriptInterface::pushPosition(L, position); - lua_pushnumber(L, category); - return scriptInterface.callFunction(4); + return scriptInterface.callFunction(3); } bool Events::eventPlayerOnTurn(Player* player, Direction direction) { // Player:onTurn(direction) or Player.onTurn(self, direction) - if (info.playerOnTurn == -1) { + if (playerOnTurn == -1) { return true; } @@ -680,10 +637,10 @@ bool Events::eventPlayerOnTurn(Player* player, Direction direction) } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.playerOnTurn, &scriptInterface); + env->setScriptId(playerOnTurn, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.playerOnTurn); + scriptInterface.pushFunction(playerOnTurn); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -696,7 +653,7 @@ bool Events::eventPlayerOnTurn(Player* player, Direction direction) bool Events::eventPlayerOnTradeRequest(Player* player, Player* target, Item* item) { // Player:onTradeRequest(target, item) - if (info.playerOnTradeRequest == -1) { + if (playerOnTradeRequest == -1) { return true; } @@ -706,10 +663,10 @@ bool Events::eventPlayerOnTradeRequest(Player* player, Player* target, Item* ite } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.playerOnTradeRequest, &scriptInterface); + env->setScriptId(playerOnTradeRequest, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.playerOnTradeRequest); + scriptInterface.pushFunction(playerOnTradeRequest); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -726,7 +683,7 @@ bool Events::eventPlayerOnTradeRequest(Player* player, Player* target, Item* ite bool Events::eventPlayerOnTradeAccept(Player* player, Player* target, Item* item, Item* targetItem) { // Player:onTradeAccept(target, item, targetItem) - if (info.playerOnTradeAccept == -1) { + if (playerOnTradeAccept == -1) { return true; } @@ -736,10 +693,10 @@ bool Events::eventPlayerOnTradeAccept(Player* player, Player* target, Item* item } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.playerOnTradeAccept, &scriptInterface); + env->setScriptId(playerOnTradeAccept, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.playerOnTradeAccept); + scriptInterface.pushFunction(playerOnTradeAccept); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -760,7 +717,7 @@ void Events::eventPlayerOnGainExperience(Player* player, Creature* source, uint6 { // Player:onGainExperience(source, exp, rawExp) // rawExp gives the original exp which is not multiplied - if (info.playerOnGainExperience == -1) { + if (playerOnGainExperience == -1) { return; } @@ -770,10 +727,10 @@ void Events::eventPlayerOnGainExperience(Player* player, Creature* source, uint6 } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.playerOnGainExperience, &scriptInterface); + env->setScriptId(playerOnGainExperience, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.playerOnGainExperience); + scriptInterface.pushFunction(playerOnGainExperience); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -781,7 +738,8 @@ void Events::eventPlayerOnGainExperience(Player* player, Creature* source, uint6 if (source) { LuaScriptInterface::pushUserdata(L, source); LuaScriptInterface::setCreatureMetatable(L, -1, source); - } else { + } + else { lua_pushnil(L); } @@ -790,7 +748,8 @@ void Events::eventPlayerOnGainExperience(Player* player, Creature* source, uint6 if (scriptInterface.protectedCall(L, 4, 1) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); - } else { + } + else { exp = LuaScriptInterface::getNumber(L, -1); lua_pop(L, 1); } @@ -801,7 +760,7 @@ void Events::eventPlayerOnGainExperience(Player* player, Creature* source, uint6 void Events::eventPlayerOnLoseExperience(Player* player, uint64_t& exp) { // Player:onLoseExperience(exp) - if (info.playerOnLoseExperience == -1) { + if (playerOnLoseExperience == -1) { return; } @@ -811,10 +770,10 @@ void Events::eventPlayerOnLoseExperience(Player* player, uint64_t& exp) } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.playerOnLoseExperience, &scriptInterface); + env->setScriptId(playerOnLoseExperience, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.playerOnLoseExperience); + scriptInterface.pushFunction(playerOnLoseExperience); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -823,7 +782,8 @@ void Events::eventPlayerOnLoseExperience(Player* player, uint64_t& exp) if (scriptInterface.protectedCall(L, 2, 1) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); - } else { + } + else { exp = LuaScriptInterface::getNumber(L, -1); lua_pop(L, 1); } @@ -834,7 +794,7 @@ void Events::eventPlayerOnLoseExperience(Player* player, uint64_t& exp) void Events::eventPlayerOnGainSkillTries(Player* player, skills_t skill, uint64_t& tries) { // Player:onGainSkillTries(skill, tries) - if (info.playerOnGainSkillTries == -1) { + if (playerOnGainSkillTries == -1) { return; } @@ -844,10 +804,10 @@ void Events::eventPlayerOnGainSkillTries(Player* player, skills_t skill, uint64_ } ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.playerOnGainSkillTries, &scriptInterface); + env->setScriptId(playerOnGainSkillTries, &scriptInterface); lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.playerOnGainSkillTries); + scriptInterface.pushFunction(playerOnGainSkillTries); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); @@ -857,37 +817,11 @@ void Events::eventPlayerOnGainSkillTries(Player* player, skills_t skill, uint64_ if (scriptInterface.protectedCall(L, 3, 1) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); - } else { + } + else { tries = LuaScriptInterface::getNumber(L, -1); lua_pop(L, 1); } scriptInterface.resetScriptEnv(); -} - -void Events::eventMonsterOnDropLoot(Monster* monster, Container* corpse) -{ - // Monster:onDropLoot(corpse) - if (info.monsterOnDropLoot == -1) { - return; - } - - if (!scriptInterface.reserveScriptEnv()) { - std::cout << "[Error - Events::eventMonsterOnDropLoot] Call stack overflow" << std::endl; - return; - } - - ScriptEnvironment* env = scriptInterface.getScriptEnv(); - env->setScriptId(info.monsterOnDropLoot, &scriptInterface); - - lua_State* L = scriptInterface.getLuaState(); - scriptInterface.pushFunction(info.monsterOnDropLoot); - - LuaScriptInterface::pushUserdata(L, monster); - LuaScriptInterface::setMetatable(L, -1, "Monster"); - - LuaScriptInterface::pushUserdata(L, corpse); - LuaScriptInterface::setMetatable(L, -1, "Container"); - - return scriptInterface.callVoidFunction(2); -} +} \ No newline at end of file diff --git a/src/events.h b/src/events.h index 2f81b6e..7582c29 100644 --- a/src/events.h +++ b/src/events.h @@ -1,6 +1,6 @@ /** * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Copyright (C) 2016 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,80 +28,68 @@ class Tile; class Events { - struct EventsInfo { - // Creature - int32_t creatureOnChangeOutfit = -1; - int32_t creatureOnAreaCombat = -1; - int32_t creatureOnTargetCombat = -1; +public: + Events(); - // Party - int32_t partyOnJoin = -1; - int32_t partyOnLeave = -1; - int32_t partyOnDisband = -1; - int32_t partyOnShareExperience = -1; + void clear(); + bool load(); - // Player - int32_t playerOnBrowseField = -1; - int32_t playerOnLook = -1; - int32_t playerOnLookInBattleList = -1; - int32_t playerOnLookInTrade = -1; - int32_t playerOnLookInShop = -1; - int32_t playerOnMoveItem = -1; - int32_t playerOnItemMoved = -1; - int32_t playerOnMoveCreature = -1; - int32_t playerOnReportRuleViolation = -1; - int32_t playerOnReportBug = -1; - int32_t playerOnTurn = -1; - int32_t playerOnTradeRequest = -1; - int32_t playerOnTradeAccept = -1; - int32_t playerOnGainExperience = -1; - int32_t playerOnLoseExperience = -1; - int32_t playerOnGainSkillTries = -1; + // Creature + bool eventCreatureOnChangeOutfit(Creature* creature, const Outfit_t& outfit); + ReturnValue eventCreatureOnAreaCombat(Creature* creature, Tile* tile, bool aggressive); + ReturnValue eventCreatureOnTargetCombat(Creature* creature, Creature* target); - // Monster - int32_t monsterOnDropLoot = -1; - }; + // Party + bool eventPartyOnJoin(Party* party, Player* player); + bool eventPartyOnLeave(Party* party, Player* player); + bool eventPartyOnDisband(Party* party); + void eventPartyOnShareExperience(Party* party, uint64_t& exp); - public: - Events(); + // Player + void eventPlayerOnLook(Player* player, const Position& position, Thing* thing, uint8_t stackpos, int32_t lookDistance); + void eventPlayerOnLookInBattleList(Player* player, Creature* creature, int32_t lookDistance); + void eventPlayerOnLookInTrade(Player* player, Player* partner, Item* item, int32_t lookDistance); + bool eventPlayerOnMoveItem(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder); + void eventPlayerOnItemMoved(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder); + bool eventPlayerOnMoveCreature(Player* player, Creature* creature, const Position& fromPosition, const Position& toPosition); + void eventPlayerOnReportRuleViolation(Player* player, const std::string& targetName, uint8_t reportType, uint8_t reportReason, const std::string& comment, const std::string& translation); + bool eventPlayerOnReportBug(Player* player, const std::string& message, const Position& position); + bool eventPlayerOnTurn(Player* player, Direction direction); + bool eventPlayerOnTradeRequest(Player* player, Player* target, Item* item); + bool eventPlayerOnTradeAccept(Player* player, Player* target, Item* item, Item* targetItem); + void eventPlayerOnGainExperience(Player* player, Creature* source, uint64_t& exp, uint64_t rawExp); + void eventPlayerOnLoseExperience(Player* player, uint64_t& exp); + void eventPlayerOnGainSkillTries(Player* player, skills_t skill, uint64_t& tries); - bool load(); +private: + LuaScriptInterface scriptInterface; - // Creature - bool eventCreatureOnChangeOutfit(Creature* creature, const Outfit_t& outfit); - ReturnValue eventCreatureOnAreaCombat(Creature* creature, Tile* tile, bool aggressive); - ReturnValue eventCreatureOnTargetCombat(Creature* creature, Creature* target); + // Creature + int32_t creatureOnChangeOutfit; + int32_t creatureOnAreaCombat; + int32_t creatureOnTargetCombat; - // Party - bool eventPartyOnJoin(Party* party, Player* player); - bool eventPartyOnLeave(Party* party, Player* player); - bool eventPartyOnDisband(Party* party); - void eventPartyOnShareExperience(Party* party, uint64_t& exp); + // Party + int32_t partyOnJoin; + int32_t partyOnLeave; + int32_t partyOnDisband; + int32_t partyOnShareExperience; - // Player - bool eventPlayerOnBrowseField(Player* player, const Position& position); - void eventPlayerOnLook(Player* player, const Position& position, Thing* thing, uint8_t stackpos, int32_t lookDistance); - void eventPlayerOnLookInBattleList(Player* player, Creature* creature, int32_t lookDistance); - void eventPlayerOnLookInTrade(Player* player, Player* partner, Item* item, int32_t lookDistance); - bool eventPlayerOnLookInShop(Player* player, const ItemType* itemType, uint8_t count); - bool eventPlayerOnMoveItem(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder); - void eventPlayerOnItemMoved(Player* player, Item* item, uint16_t count, const Position& fromPosition, const Position& toPosition, Cylinder* fromCylinder, Cylinder* toCylinder); - bool eventPlayerOnMoveCreature(Player* player, Creature* creature, const Position& fromPosition, const Position& toPosition); - void eventPlayerOnReportRuleViolation(Player* player, const std::string& targetName, uint8_t reportType, uint8_t reportReason, const std::string& comment, const std::string& translation); - bool eventPlayerOnReportBug(Player* player, const std::string& message, const Position& position, uint8_t category); - bool eventPlayerOnTurn(Player* player, Direction direction); - bool eventPlayerOnTradeRequest(Player* player, Player* target, Item* item); - bool eventPlayerOnTradeAccept(Player* player, Player* target, Item* item, Item* targetItem); - void eventPlayerOnGainExperience(Player* player, Creature* source, uint64_t& exp, uint64_t rawExp); - void eventPlayerOnLoseExperience(Player* player, uint64_t& exp); - void eventPlayerOnGainSkillTries(Player* player, skills_t skill, uint64_t& tries); - - // Monster - void eventMonsterOnDropLoot(Monster* monster, Container* corpse); - - private: - LuaScriptInterface scriptInterface; - EventsInfo info; + // Player + int32_t playerOnLook; + int32_t playerOnLookInBattleList; + int32_t playerOnLookInTrade; + int32_t playerOnMoveItem; + int32_t playerOnItemMoved; + int32_t playerOnMoveCreature; + int32_t playerOnReportRuleViolation; + int32_t playerOnReportBug; + int32_t playerOnTurn; + int32_t playerOnTradeRequest; + int32_t playerOnTradeAccept; + int32_t playerOnGainExperience; + int32_t playerOnLoseExperience; + int32_t playerOnGainSkillTries; }; -#endif +#endif \ No newline at end of file diff --git a/src/fileloader.cpp b/src/fileloader.cpp index 5c754fd..be25ecf 100644 --- a/src/fileloader.cpp +++ b/src/fileloader.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,106 +19,387 @@ #include "otpch.h" -#include #include "fileloader.h" - -namespace OTB { - -constexpr Identifier wildcard = {{'\0', '\0', '\0', '\0'}}; - -Loader::Loader(const std::string& fileName, const Identifier& acceptedIdentifier): - fileContents(fileName) +FileLoader::~FileLoader() { - constexpr auto minimalSize = sizeof(Identifier) + sizeof(Node::START) + sizeof(Node::type) + sizeof(Node::END); - if (fileContents.size() <= minimalSize) { - throw InvalidOTBFormat{}; + if (file) { + fclose(file); + file = nullptr; } - Identifier fileIdentifier; - std::copy(fileContents.begin(), fileContents.begin() + fileIdentifier.size(), fileIdentifier.begin()); - if (fileIdentifier != acceptedIdentifier && fileIdentifier != wildcard) { - throw InvalidOTBFormat{}; + NodeStruct::clearNet(root); + delete[] buffer; + + for (auto& i : cached_data) { + delete[] i.data; } } -using NodeStack = std::stack>; -static Node& getCurrentNode(const NodeStack& nodeStack) { - if (nodeStack.empty()) { - throw InvalidOTBFormat{}; +bool FileLoader::openFile(const char* filename, const char* accept_identifier) +{ + file = fopen(filename, "rb"); + if (!file) { + lastError = ERROR_CAN_NOT_OPEN; + return false; } - return *nodeStack.top(); + + 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(32768, std::max(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; } -const Node& Loader::parseTree() +bool FileLoader::parseNode(NODE node) { - auto it = fileContents.begin() + sizeof(Identifier); - if (static_cast(*it) != Node::START) { - throw InvalidOTBFormat{}; - } - root.type = *(++it); - root.propsBegin = ++it; - NodeStack parseStack; - parseStack.push(&root); + int32_t byte, pos; + NODE currentNode = node; - for (; it != fileContents.end(); ++it) { - switch(static_cast(*it)) { - case Node::START: { - auto& currentNode = getCurrentNode(parseStack); - if (currentNode.children.empty()) { - currentNode.propsEnd = it; - } - currentNode.children.emplace_back(); - auto& child = currentNode.children.back(); - if (++it == fileContents.end()) { - throw InvalidOTBFormat{}; - } - child.type = *it; - child.propsBegin = it + sizeof(Node::type); - parseStack.push(&child); - break; + while (readByte(byte)) { + currentNode->type = byte; + bool setPropsSize = false; + + while (true) { + if (!readByte(byte)) { + return false; } - case Node::END: { - auto& currentNode = getCurrentNode(parseStack); - if (currentNode.children.empty()) { - currentNode.propsEnd = it; + + 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; } - parseStack.pop(); - break; - } - case Node::ESCAPE: { - if (++it == fileContents.end()) { - throw InvalidOTBFormat{}; + + 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; } - break; + + case ESCAPE_CHAR: { + if (!readByte(byte)) { + return false; + } + + break; + } + + default: + break; } - default: { + + if (skipNode) { break; } } } - if (!parseStack.empty()) { - throw InvalidOTBFormat{}; + 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(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; } -bool Loader::getProps(const Node& node, PropStream& props) +NODE FileLoader::getNextNode(const NODE prev, uint32_t& type) { - auto size = std::distance(node.propsBegin, node.propsEnd); - if (size == 0) { + 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; } - propBuffer.resize(size); - bool lastEscaped = false; - auto escapedPropEnd = std::copy_if(node.propsBegin, node.propsEnd, propBuffer.begin(), [&lastEscaped](const char& byte) { - lastEscaped = byte == static_cast(Node::ESCAPE) && !lastEscaped; - return !lastEscaped; - }); - props.init(&propBuffer[0], std::distance(propBuffer.begin(), escapedPropEnd)); + 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; } -} //namespace OTB +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(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(cached_data[i].base) - base_pos) > static_cast(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; +} diff --git a/src/fileloader.h b/src/fileloader.h index c55f624..74872b0 100644 --- a/src/fileloader.h +++ b/src/fileloader.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,53 +22,133 @@ #include #include -#include + +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; -namespace OTB { -using MappedFile = boost::iostreams::mapped_file_source; -using ContentIt = MappedFile::iterator; -using Identifier = std::array; - -struct Node +class FileLoader { - using ChildrenVector = std::vector; + public: + FileLoader() = default; + ~FileLoader(); - ChildrenVector children; - ContentIt propsBegin; - ContentIt propsEnd; - uint8_t type; - enum NodeChar: uint8_t - { - ESCAPE = 0xFD, - START = 0xFE, - END = 0xFF, - }; + // 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::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); }; -struct LoadError : std::exception { - const char* what() const noexcept override = 0; -}; - -struct InvalidOTBFormat final : LoadError { - const char* what() const noexcept override { - return "Invalid OTBM file format"; - } -}; - -class Loader { - MappedFile fileContents; - Node root; - std::vector propBuffer; -public: - Loader(const std::string& fileName, const Identifier& acceptedIdentifier); - bool getProps(const Node& node, PropStream& props); - const Node& parseTree(); -}; - -} //namespace OTB - class PropStream { public: @@ -120,7 +200,7 @@ class PropStream return true; } - private: + protected: const char* p = nullptr; const char* end = nullptr; }; @@ -160,7 +240,7 @@ class PropWriteStream std::copy(str.begin(), str.end(), std::back_inserter(buffer)); } - private: + protected: std::vector buffer; }; diff --git a/src/game.cpp b/src/game.cpp index be8bf46..114b961 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,26 +21,22 @@ #include "pugicast.h" -#include "actions.h" -#include "bed.h" -#include "configmanager.h" +#include "items.h" #include "creature.h" -#include "creatureevent.h" -#include "databasetasks.h" +#include "monster.h" #include "events.h" #include "game.h" -#include "globalevent.h" +#include "actions.h" #include "iologindata.h" -#include "iomarket.h" -#include "items.h" -#include "monster.h" -#include "movement.h" -#include "scheduler.h" -#include "server.h" -#include "spells.h" #include "talkaction.h" -#include "weapons.h" -#include "script.h" +#include "spells.h" +#include "configmanager.h" +#include "server.h" +#include "globalevent.h" +#include "bed.h" +#include "scheduler.h" +#include "databasetasks.h" +#include "movement.h" extern ConfigManager g_config; extern Actions* g_actions; @@ -49,26 +45,10 @@ extern TalkActions* g_talkActions; extern Spells* g_spells; extern Vocations g_vocations; extern GlobalEvents* g_globalEvents; -extern CreatureEvents* g_creatureEvents; extern Events* g_events; +extern CreatureEvents* g_creatureEvents; extern Monsters g_monsters; extern MoveEvents* g_moveEvents; -extern Weapons* g_weapons; -extern Scripts* g_scripts; - -Game::Game() -{ - offlineTrainingWindow.choices.emplace_back("Sword Fighting and Shielding", SKILL_SWORD); - offlineTrainingWindow.choices.emplace_back("Axe Fighting and Shielding", SKILL_AXE); - offlineTrainingWindow.choices.emplace_back("Club Fighting and Shielding", SKILL_CLUB); - offlineTrainingWindow.choices.emplace_back("Distance Fighting and Shielding", SKILL_DISTANCE); - offlineTrainingWindow.choices.emplace_back("Magic Level and Shielding", SKILL_MAGLEVEL); - offlineTrainingWindow.buttons.emplace_back("Okay", 1); - offlineTrainingWindow.buttons.emplace_back("Cancel", 0); - offlineTrainingWindow.defaultEnterButton = 1; - offlineTrainingWindow.defaultEscapeButton = 0; - offlineTrainingWindow.priority = true; -} Game::~Game() { @@ -119,9 +99,6 @@ void Game::setGameState(GameState_t newState) raids.loadFromXml(); raids.startup(); - quests.loadFromXml(); - mounts.loadFromXml(); - loadMotdNum(); loadPlayersRecord(); @@ -187,8 +164,6 @@ void Game::saveGameState() Map::save(); - g_databaseTasks.flush(); - if (gameState == GAME_STATE_MAINTAIN) { setGameState(GAME_STATE_NORMAL); } @@ -257,7 +232,7 @@ Thing* Game::internalGetThing(Player* player, const Position& pos, int32_t index } case STACKPOS_USETARGET: { - thing = tile->getTopVisibleCreature(player); + thing = tile->getTopCreature(); if (!thing) { thing = tile->getUseItem(index); } @@ -296,32 +271,17 @@ Thing* Game::internalGetThing(Player* player, const Position& pos, int32_t index return nullptr; } - if (parentContainer->getID() == ITEM_BROWSEFIELD) { - Tile* tile = parentContainer->getTile(); - if (tile && tile->hasFlag(TILESTATE_SUPPORTS_HANGABLE)) { - if (tile->hasProperty(CONST_PROP_ISVERTICAL)) { - if (player->getPosition().x + 1 == tile->getPosition().x) { - return nullptr; - } - } else { // horizontal - if (player->getPosition().y + 1 == tile->getPosition().y) { - return nullptr; - } - } - } - } - uint8_t slot = pos.z; return parentContainer->getItemByIndex(player->getContainerIndex(fromCid) + slot); } else if (pos.y == 0 && pos.z == 0) { - const ItemType& it = Item::items.getItemIdByClientId(spriteId); + const ItemType& it = Item::items.getItemType(spriteId); if (it.id == 0) { return nullptr; } int32_t subType; - if (it.isFluidContainer() && index < static_cast(sizeof(reverseFluidMap) / sizeof(uint8_t))) { - subType = reverseFluidMap[index]; + if (it.isFluidContainer()) { + subType = static_cast(index); } else { subType = -1; } @@ -541,15 +501,15 @@ bool Game::placeCreature(Creature* creature, const Position& pos, bool extendedP return false; } - SpectatorVec spectators; - map.getSpectators(spectators, creature->getPosition(), true); - for (Creature* spectator : spectators) { + SpectatorVec list; + map.getSpectators(list, creature->getPosition(), true); + for (Creature* spectator : list) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendCreatureAppear(creature, creature->getPosition(), true); } } - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { spectator->onCreatureAppear(creature, true); } @@ -570,9 +530,9 @@ bool Game::removeCreature(Creature* creature, bool isLogout/* = true*/) std::vector oldStackPosVector; - SpectatorVec spectators; - map.getSpectators(spectators, tile->getPosition(), true); - for (Creature* spectator : spectators) { + SpectatorVec list; + map.getSpectators(list, tile->getPosition(), true); + for (Creature* spectator : list) { if (Player* player = spectator->getPlayer()) { oldStackPosVector.push_back(player->canSeeCreature(creature) ? tile->getStackposOfCreature(player, creature) : -1); } @@ -584,14 +544,14 @@ bool Game::removeCreature(Creature* creature, bool isLogout/* = true*/) //send to client size_t i = 0; - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { if (Player* player = spectator->getPlayer()) { player->sendRemoveTileThing(tilePosition, oldStackPosVector[i++]); } } //event method - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { spectator->onRemoveCreature(creature, isLogout); } @@ -604,7 +564,7 @@ bool Game::removeCreature(Creature* creature, bool isLogout/* = true*/) removeCreatureCheck(creature); for (Creature* summon : creature->summons) { - summon->setSkillLoss(false); + summon->setLossSkill(false); removeCreature(summon); } return true; @@ -694,6 +654,13 @@ void Game::playerMoveCreature(Player* player, Creature* movingCreature, const Po player->setNextActionTask(nullptr); + if (g_config.getBoolean(ConfigManager::BLOCK_HEIGHT)) { + if (toTile->getHeight() > 1) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return; + } + } + if (!Position::areInRange<1, 1, 0>(movingCreatureOrigPos, player->getPosition())) { //need to walk to the creature first before moving it std::forward_list listDir; @@ -763,45 +730,64 @@ ReturnValue Game::internalMoveCreature(Creature* creature, Direction direction, creature->setLastPosition(creature->getPosition()); const Position& currentPos = creature->getPosition(); Position destPos = getNextPosition(direction, currentPos); - Player* player = creature->getPlayer(); bool diagonalMovement = (direction & DIRECTION_DIAGONAL_MASK) != 0; - if (player && !diagonalMovement) { + if (creature->getPlayer() && !diagonalMovement) { //try go up if (currentPos.z != 8 && creature->getTile()->hasHeight(3)) { Tile* tmpTile = map.getTile(currentPos.x, currentPos.y, currentPos.getZ() - 1); if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) { tmpTile = map.getTile(destPos.x, destPos.y, destPos.getZ() - 1); if (tmpTile && tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID)) { - flags |= FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE; - - if (!tmpTile->hasFlag(TILESTATE_FLOORCHANGE)) { - player->setDirection(direction); - destPos.z--; - } + destPos.z--; + internalCreatureTurn(creature, DIRECTION_NORTH); } } } - - //try go down - if (currentPos.z != 7 && currentPos.z == destPos.z) { + else { + //try go down Tile* tmpTile = map.getTile(destPos.x, destPos.y, destPos.z); - if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) { + if (currentPos.z != 7 && (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID)))) { tmpTile = map.getTile(destPos.x, destPos.y, destPos.z + 1); if (tmpTile && tmpTile->hasHeight(3)) { - flags |= FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE; - player->setDirection(direction); destPos.z++; + internalCreatureTurn(creature, DIRECTION_SOUTH); } } } } + ReturnValue ret = RETURNVALUE_NOTPOSSIBLE; Tile* toTile = map.getTile(destPos); - if (!toTile) { - return RETURNVALUE_NOTPOSSIBLE; + + Tile* toPos = map.getTile(destPos.x, destPos.y, destPos.z); + Tile* fromPos = map.getTile(currentPos.x, currentPos.y, currentPos.z); + + if (g_config.getBoolean(ConfigManager::BLOCK_HEIGHT)) { + if (toTile) { + if (currentPos.z > destPos.z && toPos->getHeight() > 1); + // not possible + else if ((((toPos->getHeight() - fromPos->getHeight()) < 2)) || + (fromPos->hasHeight(3) && (currentPos.z == destPos.z)) || + ((currentPos.z < destPos.z) && (toPos->hasHeight(3) && (fromPos->getHeight() < 2)))) + ret = internalMoveCreature(*creature, *toTile, flags); + } + + if (ret != RETURNVALUE_NOERROR) { + if (Player* player = creature->getPlayer()) { + player->sendCancelMessage(ret); + player->sendCancelWalk(); + } + } + + return ret; + } + else { + if (!toTile) { + return RETURNVALUE_NOTPOSSIBLE; + } + return internalMoveCreature(*creature, *toTile, flags); } - return internalMoveCreature(*creature, *toTile, flags); } ReturnValue Game::internalMoveCreature(Creature& creature, Tile& toTile, uint32_t flags /*= 0*/) @@ -900,7 +886,7 @@ void Game::playerMoveItem(Player* player, const Position& fromPos, item = thing->getItem(); } - if (item->getClientID() != spriteId) { + if ((item->isDisguised() && item->getDisguiseId() != spriteId) || (!item->isDisguised() && item->getID() != spriteId)) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } @@ -919,7 +905,7 @@ void Game::playerMoveItem(Player* player, const Position& fromPos, } } - if (!item->isPushable() || item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { + if (!item->isPushable()) { player->sendCancelMessage(RETURNVALUE_NOTMOVEABLE); return; } @@ -1032,25 +1018,17 @@ void Game::playerMoveItem(Player* player, const Position& fromPos, } } - ReturnValue ret = internalMoveItem(fromCylinder, toCylinder, toIndex, item, count, nullptr, 0, player); + ReturnValue ret = internalMoveItem(fromCylinder, toCylinder, toIndex, item, item->isRune() ? item->getItemCount() : count, nullptr, 0, player); if (ret != RETURNVALUE_NOERROR) { player->sendCancelMessage(ret); } else { - g_events->eventPlayerOnItemMoved(player, item, count, fromPos, toPos, fromCylinder, toCylinder); + g_events->eventPlayerOnItemMoved(player, item, item->isRune() ? item->getItemCount() : count, fromPos, toPos, fromCylinder, toCylinder); } } ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, int32_t index, Item* item, uint32_t count, Item** _moveItem, uint32_t flags /*= 0*/, Creature* actor/* = nullptr*/, Item* tradeItem/* = nullptr*/) { - Tile* fromTile = fromCylinder->getTile(); - if (fromTile) { - auto it = browseFields.find(fromTile); - if (it != browseFields.end() && it->second == fromCylinder) { - fromCylinder = fromTile; - } - } - Item* toItem = nullptr; Cylinder* subCylinder; @@ -1118,8 +1096,14 @@ ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, uint32_t m; if (item->isStackable()) { - m = std::min(count, maxQueryCount); - } else { + if (item->isRune()) { + m = std::min(item->getItemCount(), maxQueryCount); + } + else { + m = std::min(count, maxQueryCount); + } + } + else { m = maxQueryCount; } @@ -1155,11 +1139,12 @@ ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, if (item->isStackable()) { uint32_t n; - if (item->equals(toItem)) { + if (!item->isRune() && item->equals(toItem)) { n = std::min(100 - toItem->getItemCount(), m); toCylinder->updateThing(toItem, toItem->getID(), toItem->getItemCount() + n); updateItem = toItem; - } else { + } + else { n = 0; } @@ -1212,14 +1197,6 @@ ReturnValue Game::internalMoveItem(Cylinder* fromCylinder, Cylinder* toCylinder, return retMaxCount; } - if (moveItem && moveItem->getDuration() > 0) { - if (moveItem->getDecaying() != DECAYING_TRUE) { - moveItem->incrementReferenceCounter(); - moveItem->setDecaying(DECAYING_TRUE); - toDecayItems.push_front(moveItem); - } - } - return ret; } @@ -1262,7 +1239,7 @@ ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t inde return RETURNVALUE_NOERROR; } - if (item->isStackable() && item->equals(toItem)) { + if (item->isStackable() && !item->isRune() && item->equals(toItem)) { uint32_t m = std::min(item->getItemCount(), maxQueryCount); uint32_t n = std::min(100 - toItem->getItemCount(), m); @@ -1277,7 +1254,8 @@ ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t inde ReleaseItem(remainderItem); remainderCount = count; } - } else { + } + else { toCylinder->addThing(index, item); int32_t itemIndex = toCylinder->getThingIndex(item); @@ -1285,7 +1263,8 @@ ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t inde toCylinder->postAddNotification(item, nullptr, itemIndex); } } - } else { + } + else { //fully merged with toItem, item will be destroyed item->onRemoved(); ReleaseItem(item); @@ -1295,7 +1274,8 @@ ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t inde toCylinder->postAddNotification(toItem, nullptr, itemIndex); } } - } else { + } + else { toCylinder->addThing(index, item); int32_t itemIndex = toCylinder->getThingIndex(item); @@ -1304,12 +1284,6 @@ ReturnValue Game::internalAddItem(Cylinder* toCylinder, Item* item, int32_t inde } } - if (item->getDuration() > 0) { - item->incrementReferenceCounter(); - item->setDecaying(DECAYING_TRUE); - toDecayItems.push_front(item); - } - return RETURNVALUE_NOERROR; } @@ -1320,14 +1294,6 @@ ReturnValue Game::internalRemoveItem(Item* item, int32_t count /*= -1*/, bool te return RETURNVALUE_NOTPOSSIBLE; } - Tile* fromTile = cylinder->getTile(); - if (fromTile) { - auto it = browseFields.find(fromTile); - if (it != browseFields.end() && it->second == cylinder) { - cylinder = fromTile; - } - } - if (count == -1) { count = item->getItemCount(); } @@ -1349,16 +1315,13 @@ ReturnValue Game::internalRemoveItem(Item* item, int32_t count /*= -1*/, bool te cylinder->removeThing(item, count); if (item->isRemoved()) { - item->onRemoved(); - if (item->canDecay()) { - decayItems->remove(item); - } ReleaseItem(item); } cylinder->postRemoveNotification(item, nullptr, index); } + item->onRemoved(); return RETURNVALUE_NOERROR; } @@ -1562,14 +1525,6 @@ Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/) return nullptr; } - Tile* fromTile = cylinder->getTile(); - if (fromTile) { - auto it = browseFields.find(fromTile); - if (it != browseFields.end() && it->second == cylinder) { - cylinder = fromTile; - } - } - int32_t itemIndex = cylinder->getThingIndex(item); if (itemIndex == -1) { return item; @@ -1616,7 +1571,7 @@ Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/) } else { int32_t newItemId = newId; if (curType.id == newType.id) { - newItemId = item->getDecayTo(); + newItemId = curType.decayTo; } if (newItemId < 0) { @@ -1682,14 +1637,6 @@ Item* Game::transformItem(Item* item, uint16_t newId, int32_t newCount /*= -1*/) cylinder->postRemoveNotification(item, cylinder, itemIndex); ReleaseItem(item); - if (newItem->getDuration() > 0) { - if (newItem->getDecaying() != DECAYING_TRUE) { - newItem->incrementReferenceCounter(); - newItem->setDecaying(DECAYING_TRUE); - toDecayItems.push_front(newItem); - } - } - return newItem; } @@ -1712,6 +1659,19 @@ ReturnValue Game::internalTeleport(Thing* thing, const Position& newPos, bool pu return ret; } + Position fromPos = creature->getPosition(); + if (Position::getOffsetX(fromPos, newPos) <= 0) { + if (Position::getOffsetX(fromPos, newPos) < 0) { + internalCreatureTurn(creature, DIRECTION_EAST); + } else if (Position::getOffsetY(fromPos, newPos) < 0) { + internalCreatureTurn(creature, DIRECTION_SOUTH); + } else if (Position::getOffsetY(fromPos, newPos) > 0) { + internalCreatureTurn(creature, DIRECTION_NORTH); + } + } else { + internalCreatureTurn(creature, DIRECTION_WEST); + } + map.moveCreature(*creature, *toTile, !pushMove); return RETURNVALUE_NOERROR; } else if (Item* item = thing->getItem()) { @@ -1720,75 +1680,7 @@ ReturnValue Game::internalTeleport(Thing* thing, const Position& newPos, bool pu return RETURNVALUE_NOTPOSSIBLE; } -Item* searchForItem(Container* container, uint16_t itemId) -{ - for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { - if ((*it)->getID() == itemId) { - return *it; - } - } - - return nullptr; -} - -slots_t getSlotType(const ItemType& it) -{ - slots_t slot = CONST_SLOT_RIGHT; - if (it.weaponType != WeaponType_t::WEAPON_SHIELD) { - int32_t slotPosition = it.slotPosition; - - if (slotPosition & SLOTP_HEAD) { - slot = CONST_SLOT_HEAD; - } else if (slotPosition & SLOTP_NECKLACE) { - slot = CONST_SLOT_NECKLACE; - } else if (slotPosition & SLOTP_ARMOR) { - slot = CONST_SLOT_ARMOR; - } else if (slotPosition & SLOTP_LEGS) { - slot = CONST_SLOT_LEGS; - } else if (slotPosition & SLOTP_FEET) { - slot = CONST_SLOT_FEET ; - } else if (slotPosition & SLOTP_RING) { - slot = CONST_SLOT_RING; - } else if (slotPosition & SLOTP_AMMO) { - slot = CONST_SLOT_AMMO; - } else if (slotPosition & SLOTP_TWO_HAND || slotPosition & SLOTP_LEFT) { - slot = CONST_SLOT_LEFT; - } - } - - return slot; -} - //Implementation of player invoked events -void Game::playerEquipItem(uint32_t playerId, uint16_t spriteId) -{ - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - Item* item = player->getInventoryItem(CONST_SLOT_BACKPACK); - if (!item) { - return; - } - - Container* backpack = item->getContainer(); - if (!backpack) { - return; - } - - const ItemType& it = Item::items.getItemIdByClientId(spriteId); - slots_t slot = getSlotType(it); - - Item* slotItem = player->getInventoryItem(slot); - Item* equipItem = searchForItem(backpack, it.id); - if (slotItem && slotItem->getID() == it.id && (!it.stackable || slotItem->getItemCount() == 100 || !equipItem)) { - internalMoveItem(slotItem->getParent(), player, CONST_SLOT_WHEREEVER, slotItem, slotItem->getItemCount(), nullptr); - } else if (equipItem) { - internalMoveItem(equipItem->getParent(), player, slot, equipItem, equipItem->getItemCount(), nullptr); - } -} - void Game::playerMove(uint32_t playerId, Direction direction) { Player* player = getPlayerByID(playerId); @@ -1902,15 +1794,11 @@ void Game::playerOpenChannel(uint32_t playerId, uint16_t channelId) return; } - const InvitedMap* invitedUsers = channel->getInvitedUsers(); - const UsersMap* users; - if (!channel->isPublicChannel()) { - users = &channel->getUsers(); + if (channel->getId() == CHANNEL_RULE_REP) { + player->sendRuleViolationsChannel(channel->getId()); } else { - users = nullptr; + player->sendChannel(channel->getId(), channel->getName()); } - - player->sendChannel(channel->getId(), channel->getName(), users, invitedUsers); } void Game::playerCloseChannel(uint32_t playerId, uint16_t channelId) @@ -1935,30 +1823,9 @@ void Game::playerOpenPrivateChannel(uint32_t playerId, std::string& receiver) return; } - if (player->getName() == receiver) { - player->sendCancelMessage("You cannot set up a private message channel with yourself."); - return; - } - player->sendOpenPrivateChannel(receiver); } -void Game::playerCloseNpcChannel(uint32_t playerId) -{ - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - SpectatorVec spectators; - map.getSpectators(spectators, player->getPosition()); - for (Creature* spectator : spectators) { - if (Npc* npc = spectator->getNpc()) { - npc->onPlayerCloseChannel(player); - } - } -} - void Game::playerReceivePing(uint32_t playerId) { Player* player = getPlayerByID(playerId); @@ -2021,7 +1888,7 @@ void Game::playerUseItemEx(uint32_t playerId, const Position& fromPos, uint8_t f } Item* item = thing->getItem(); - if (!item || !item->isUseable() || item->getClientID() != fromSpriteId) { + if (!item || (item->isDisguised() && item->getDisguiseId() != fromSpriteId) || (!item->isDisguised() && item->getID() != fromSpriteId)) { player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); return; } @@ -2105,7 +1972,7 @@ void Game::playerUseItem(uint32_t playerId, const Position& pos, uint8_t stackPo } Item* item = thing->getItem(); - if (!item || item->isUseable() || item->getClientID() != spriteId) { + if (!item || (item->isDisguised() && item->getDisguiseId() != spriteId) || (!item->isDisguised() && item->getID() != spriteId)) { player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); return; } @@ -2176,7 +2043,7 @@ void Game::playerUseWithCreature(uint32_t playerId, const Position& fromPos, uin } Item* item = thing->getItem(); - if (!item || !item->isUseable() || item->getClientID() != spriteId) { + if (!item || (item->isDisguised() && item->getDisguiseId() != spriteId) || (!item->isDisguised() && item->getID() != spriteId)) { player->sendCancelMessage(RETURNVALUE_CANNOTUSETHISOBJECT); return; } @@ -2265,20 +2132,7 @@ void Game::playerMoveUpContainer(uint32_t playerId, uint8_t cid) Container* parentContainer = dynamic_cast(container->getRealParent()); if (!parentContainer) { - Tile* tile = container->getTile(); - if (!tile) { - return; - } - - auto it = browseFields.find(tile); - if (it == browseFields.end()) { - parentContainer = new Container(tile); - parentContainer->incrementReferenceCounter(); - browseFields[tile] = parentContainer; - g_scheduler.addEvent(createSchedulerTask(30000, std::bind(&Game::decreaseBrowseFieldRef, this, tile->getPosition()))); - } else { - parentContainer = it->second; - } + return; } player->addContainer(cid, parentContainer); @@ -2313,7 +2167,7 @@ void Game::playerRotateItem(uint32_t playerId, const Position& pos, uint8_t stac } Item* item = thing->getItem(); - if (!item || item->getClientID() != spriteId || !item->isRotatable() || item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { + if (!item || (item->isDisguised() && item->getDisguiseId() != spriteId) || !item->isRotatable() || (!item->isDisguised() && item->getID() != spriteId)) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } @@ -2372,13 +2226,6 @@ void Game::playerWriteItem(uint32_t playerId, uint32_t windowTextId, const std:: return; } - for (auto creatureEvent : player->getCreatureEvents(CREATURE_EVENT_TEXTEDIT)) { - if (!creatureEvent->executeTextEdit(player, writeItem, text)) { - player->setWriteItem(nullptr); - return; - } - } - if (!text.empty()) { if (writeItem->getText() != text) { writeItem->setText(text); @@ -2399,66 +2246,6 @@ void Game::playerWriteItem(uint32_t playerId, uint32_t windowTextId, const std:: player->setWriteItem(nullptr); } -void Game::playerBrowseField(uint32_t playerId, const Position& pos) -{ - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - const Position& playerPos = player->getPosition(); - if (playerPos.z != pos.z) { - player->sendCancelMessage(playerPos.z > pos.z ? RETURNVALUE_FIRSTGOUPSTAIRS : RETURNVALUE_FIRSTGODOWNSTAIRS); - return; - } - - if (!Position::areInRange<1, 1>(playerPos, pos)) { - std::forward_list listDir; - if (player->getPathTo(pos, listDir, 0, 1, true, true)) { - g_dispatcher.addTask(createTask(std::bind(&Game::playerAutoWalk, - this, player->getID(), listDir))); - SchedulerTask* task = createSchedulerTask(400, std::bind( - &Game::playerBrowseField, this, playerId, pos - )); - player->setNextWalkActionTask(task); - } else { - player->sendCancelMessage(RETURNVALUE_THEREISNOWAY); - } - return; - } - - Tile* tile = map.getTile(pos); - if (!tile) { - return; - } - - if (!g_events->eventPlayerOnBrowseField(player, pos)) { - return; - } - - Container* container; - - auto it = browseFields.find(tile); - if (it == browseFields.end()) { - container = new Container(tile); - container->incrementReferenceCounter(); - browseFields[tile] = container; - g_scheduler.addEvent(createSchedulerTask(30000, std::bind(&Game::decreaseBrowseFieldRef, this, tile->getPosition()))); - } else { - container = it->second; - } - - uint8_t dummyContainerId = 0xF - ((pos.x % 3) * 3 + (pos.y % 3)); - Container* openContainer = player->getContainerByID(dummyContainerId); - if (openContainer) { - player->onCloseContainer(openContainer); - player->closeContainer(dummyContainerId); - } else { - player->addContainer(dummyContainerId, container); - player->sendContainer(dummyContainerId, container, false, 0); - } -} - void Game::playerSeekInContainer(uint32_t playerId, uint8_t containerId, uint16_t index) { Player* player = getPlayerByID(playerId); @@ -2467,7 +2254,7 @@ void Game::playerSeekInContainer(uint32_t playerId, uint8_t containerId, uint16_ } Container* container = player->getContainerByID(containerId); - if (!container || !container->hasPagination()) { + if (!container) { return; } @@ -2530,7 +2317,7 @@ void Game::playerRequestTrade(uint32_t playerId, const Position& pos, uint8_t st } Item* tradeItem = tradeThing->getItem(); - if (tradeItem->getClientID() != spriteId || !tradeItem->isPickupable() || tradeItem->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { + if (!tradeItem->isPickupable() || (tradeItem->isDisguised() && tradeItem->getDisguiseId() != spriteId) || (!tradeItem->isDisguised() && tradeItem->getID() != spriteId)) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return; } @@ -2663,10 +2450,10 @@ void Game::playerAcceptTrade(uint32_t playerId) player->setTradeState(TRADE_ACCEPT); if (tradePartner->getTradeState() == TRADE_ACCEPT) { - Item* playerTradeItem = player->tradeItem; - Item* partnerTradeItem = tradePartner->tradeItem; + Item* tradeItem1 = player->tradeItem; + Item* tradeItem2 = tradePartner->tradeItem; - if (!g_events->eventPlayerOnTradeAccept(player, tradePartner, playerTradeItem, partnerTradeItem)) { + if (!g_events->eventPlayerOnTradeAccept(player, tradePartner, tradeItem1, tradeItem2)) { internalCloseTrade(player); return; } @@ -2674,13 +2461,13 @@ void Game::playerAcceptTrade(uint32_t playerId) player->setTradeState(TRADE_TRANSFER); tradePartner->setTradeState(TRADE_TRANSFER); - auto it = tradeItems.find(playerTradeItem); + auto it = tradeItems.find(tradeItem1); if (it != tradeItems.end()) { ReleaseItem(it->first); tradeItems.erase(it); } - it = tradeItems.find(partnerTradeItem); + it = tradeItems.find(tradeItem2); if (it != tradeItems.end()) { ReleaseItem(it->first); tradeItems.erase(it); @@ -2688,17 +2475,25 @@ void Game::playerAcceptTrade(uint32_t playerId) bool isSuccess = false; - ReturnValue tradePartnerRet = internalAddItem(tradePartner, playerTradeItem, INDEX_WHEREEVER, 0, true); - ReturnValue playerRet = internalAddItem(player, partnerTradeItem, INDEX_WHEREEVER, 0, true); - if (tradePartnerRet == RETURNVALUE_NOERROR && playerRet == RETURNVALUE_NOERROR) { - playerRet = internalRemoveItem(playerTradeItem, playerTradeItem->getItemCount(), true); - tradePartnerRet = internalRemoveItem(partnerTradeItem, partnerTradeItem->getItemCount(), true); - if (tradePartnerRet == RETURNVALUE_NOERROR && playerRet == RETURNVALUE_NOERROR) { - tradePartnerRet = internalMoveItem(playerTradeItem->getParent(), tradePartner, INDEX_WHEREEVER, playerTradeItem, playerTradeItem->getItemCount(), nullptr, FLAG_IGNOREAUTOSTACK, nullptr, partnerTradeItem); - if (tradePartnerRet == RETURNVALUE_NOERROR) { - internalMoveItem(partnerTradeItem->getParent(), player, INDEX_WHEREEVER, partnerTradeItem, partnerTradeItem->getItemCount(), nullptr, FLAG_IGNOREAUTOSTACK); - playerTradeItem->onTradeEvent(ON_TRADE_TRANSFER, tradePartner); - partnerTradeItem->onTradeEvent(ON_TRADE_TRANSFER, player); + ReturnValue ret1 = internalAddItem(tradePartner, tradeItem1, INDEX_WHEREEVER, 0, true); + ReturnValue ret2 = internalAddItem(player, tradeItem2, INDEX_WHEREEVER, 0, true); + if (ret1 == RETURNVALUE_NOERROR && ret2 == RETURNVALUE_NOERROR) { + ret1 = internalRemoveItem(tradeItem1, tradeItem1->getItemCount(), true); + ret2 = internalRemoveItem(tradeItem2, tradeItem2->getItemCount(), true); + if (ret1 == RETURNVALUE_NOERROR && ret2 == RETURNVALUE_NOERROR) { + Cylinder* cylinder1 = tradeItem1->getParent(); + Cylinder* cylinder2 = tradeItem2->getParent(); + + uint32_t count1 = tradeItem1->getItemCount(); + uint32_t count2 = tradeItem2->getItemCount(); + + ret1 = internalMoveItem(cylinder1, tradePartner, INDEX_WHEREEVER, tradeItem1, count1, nullptr, FLAG_IGNOREAUTOSTACK, nullptr, tradeItem2); + if (ret1 == RETURNVALUE_NOERROR) { + internalMoveItem(cylinder2, player, INDEX_WHEREEVER, tradeItem2, count2, nullptr, FLAG_IGNOREAUTOSTACK); + + tradeItem1->onTradeEvent(ON_TRADE_TRANSFER, tradePartner); + tradeItem2->onTradeEvent(ON_TRADE_TRANSFER, player); + isSuccess = true; } } @@ -2708,13 +2503,13 @@ void Game::playerAcceptTrade(uint32_t playerId) std::string errorDescription; if (tradePartner->tradeItem) { - errorDescription = getTradeErrorDescription(tradePartnerRet, playerTradeItem); + errorDescription = getTradeErrorDescription(ret1, tradeItem1); tradePartner->sendTextMessage(MESSAGE_EVENT_ADVANCE, errorDescription); tradePartner->tradeItem->onTradeEvent(ON_TRADE_CANCEL, tradePartner); } if (player->tradeItem) { - errorDescription = getTradeErrorDescription(playerRet, partnerTradeItem); + errorDescription = getTradeErrorDescription(ret2, tradeItem2); player->sendTextMessage(MESSAGE_EVENT_ADVANCE, errorDescription); player->tradeItem->onTradeEvent(ON_TRADE_CANCEL, player); } @@ -2745,7 +2540,7 @@ std::string Game::getTradeErrorDescription(ReturnValue ret, Item* item) ss << " this object."; } - ss << "\n " << item->getWeightDescription(); + ss << std::endl << ' ' << item->getWeightDescription(); return ss.str(); } else if (ret == RETURNVALUE_NOTENOUGHROOM || ret == RETURNVALUE_CONTAINERNOTENOUGHROOM) { std::ostringstream ss; @@ -2791,6 +2586,7 @@ void Game::playerLookInTrade(uint32_t playerId, bool lookAtCounterOffer, uint8_t int32_t lookDistance = std::max(Position::getDistanceX(playerPosition, tradeItemPosition), Position::getDistanceY(playerPosition, tradeItemPosition)); + if (index == 0) { g_events->eventPlayerOnLookInTrade(player, tradePartner, tradeItem, lookDistance); return; @@ -2873,126 +2669,6 @@ void Game::internalCloseTrade(Player* player) } } -void Game::playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount, - bool ignoreCap/* = false*/, bool inBackpacks/* = false*/) -{ - if (amount == 0 || amount > 100) { - return; - } - - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - int32_t onBuy, onSell; - - Npc* merchant = player->getShopOwner(onBuy, onSell); - if (!merchant) { - return; - } - - const ItemType& it = Item::items.getItemIdByClientId(spriteId); - if (it.id == 0) { - return; - } - - uint8_t subType; - if (it.isSplash() || it.isFluidContainer()) { - subType = clientFluidToServer(count); - } else { - subType = count; - } - - if (!player->hasShopItemForSale(it.id, subType)) { - return; - } - - merchant->onPlayerTrade(player, onBuy, it.id, subType, amount, ignoreCap, inBackpacks); -} - -void Game::playerSellItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount, bool ignoreEquipped) -{ - if (amount == 0 || amount > 100) { - return; - } - - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - int32_t onBuy, onSell; - - Npc* merchant = player->getShopOwner(onBuy, onSell); - if (!merchant) { - return; - } - - const ItemType& it = Item::items.getItemIdByClientId(spriteId); - if (it.id == 0) { - return; - } - - uint8_t subType; - if (it.isSplash() || it.isFluidContainer()) { - subType = clientFluidToServer(count); - } else { - subType = count; - } - - merchant->onPlayerTrade(player, onSell, it.id, subType, amount, ignoreEquipped); -} - -void Game::playerCloseShop(uint32_t playerId) -{ - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - player->closeShopWindow(); -} - -void Game::playerLookInShop(uint32_t playerId, uint16_t spriteId, uint8_t count) -{ - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - int32_t onBuy, onSell; - - Npc* merchant = player->getShopOwner(onBuy, onSell); - if (!merchant) { - return; - } - - const ItemType& it = Item::items.getItemIdByClientId(spriteId); - if (it.id == 0) { - return; - } - - int32_t subType; - if (it.isFluidContainer() || it.isSplash()) { - subType = clientFluidToServer(count); - } else { - subType = count; - } - - if (!player->hasShopItemForSale(it.id, subType)) { - return; - } - - if (!g_events->eventPlayerOnLookInShop(player, &it, subType)) { - return; - } - - std::ostringstream ss; - ss << "You see " << Item::getDescription(it, 1, nullptr, subType); - player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); -} - void Game::playerLookAt(uint32_t playerId, const Position& pos, uint8_t stackPos) { Player* player = getPlayerByID(playerId); @@ -3118,7 +2794,7 @@ void Game::playerFollowCreature(uint32_t playerId, uint32_t creatureId) player->setFollowCreature(getCreatureByID(creatureId)); } -void Game::playerSetFightModes(uint32_t playerId, fightMode_t fightMode, bool chaseMode, bool secureMode) +void Game::playerSetFightModes(uint32_t playerId, fightMode_t fightMode, chaseMode_t chaseMode, bool secureMode) { Player* player = getPlayerByID(playerId); if (!player) { @@ -3181,16 +2857,6 @@ void Game::playerRequestRemoveVip(uint32_t playerId, uint32_t guid) player->removeVIP(guid); } -void Game::playerRequestEditVip(uint32_t playerId, uint32_t guid, const std::string& description, uint32_t icon, bool notify) -{ - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - player->editVIP(guid, description, icon, notify); -} - void Game::playerTurn(uint32_t playerId, Direction dir) { Player* player = getPlayerByID(playerId); @@ -3220,16 +2886,6 @@ void Game::playerRequestOutfit(uint32_t playerId) player->sendOutfitWindow(); } -void Game::playerToggleMount(uint32_t playerId, bool mount) -{ - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - player->toggleMount(mount); -} - void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit) { if (!g_config.getBoolean(ConfigManager::ALLOW_CHANGEOUTFIT)) { @@ -3241,36 +2897,6 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit) return; } - const Outfit* playerOutfit = Outfits::getInstance().getOutfitByLookType(player->getSex(), outfit.lookType); - if (!playerOutfit) { - outfit.lookMount = 0; - } - - if (outfit.lookMount != 0) { - Mount* mount = mounts.getMountByClientID(outfit.lookMount); - if (!mount) { - return; - } - - if (!player->hasMount(mount)) { - return; - } - - if (player->isMounted()) { - Mount* prevMount = mounts.getMountByID(player->getCurrentMount()); - if (prevMount) { - changeSpeed(player, mount->speed - prevMount->speed); - } - - player->setCurrentMount(mount->id); - } else { - player->setCurrentMount(mount->id); - outfit.lookMount = 0; - } - } else if (player->isMounted()) { - player->dismount(); - } - if (player->canWear(outfit.lookType, outfit.lookAddons)) { player->defaultOutfit = outfit; @@ -3282,31 +2908,6 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit) } } -void Game::playerShowQuestLog(uint32_t playerId) -{ - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - player->sendQuestLog(); -} - -void Game::playerShowQuestLine(uint32_t playerId, uint16_t questId) -{ - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - Quest* quest = quests.getQuestByID(questId); - if (!quest) { - return; - } - - player->sendQuestLine(quest); -} - void Game::playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, const std::string& receiver, const std::string& text) { @@ -3333,9 +2934,7 @@ void Game::playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, return; } - if (type != TALKTYPE_PRIVATE_PN) { - player->removeMessageBuffer(); - } + player->removeMessageBuffer(); switch (type) { case TALKTYPE_SAY: @@ -3350,25 +2949,35 @@ void Game::playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type, playerYell(player, text); break; - case TALKTYPE_PRIVATE_TO: - case TALKTYPE_PRIVATE_RED_TO: + case TALKTYPE_PRIVATE: + case TALKTYPE_PRIVATE_RED: + case TALKTYPE_RVR_ANSWER: playerSpeakTo(player, type, receiver, text); break; case TALKTYPE_CHANNEL_O: case TALKTYPE_CHANNEL_Y: case TALKTYPE_CHANNEL_R1: - g_chat->talkToChannel(*player, type, text, channelId); - break; - - case TALKTYPE_PRIVATE_PN: - playerSpeakToNpc(player, text); + case TALKTYPE_CHANNEL_R2: + if (channelId == CHANNEL_RULE_REP) { + playerSay(playerId, 0, TALKTYPE_SAY, receiver, text); + } else { + g_chat->talkToChannel(*player, type, text, channelId); + } break; case TALKTYPE_BROADCAST: playerBroadcastMessage(player, text); break; + case TALKTYPE_RVR_CHANNEL: + playerReportRuleViolationReport(player, text); + break; + + case TALKTYPE_RVR_CONTINUE: + playerContinueRuleViolationReport(player, text); + break; + default: break; } @@ -3385,12 +2994,7 @@ bool Game::playerSaySpell(Player* player, SpeakClasses type, const std::string& result = g_spells->playerSaySpell(player, words); if (result == TALKACTION_BREAK) { - if (!g_config.getBoolean(ConfigManager::EMOTE_SPELLS)) { - return internalCreatureSay(player, TALKTYPE_SAY, words, false); - } else { - return internalCreatureSay(player, TALKTYPE_MONSTER_SAY, words, false); - } - + return internalCreatureSay(player, TALKTYPE_SAY, text, false); } else if (result == TALKACTION_FAILED) { return true; } @@ -3400,13 +3004,13 @@ bool Game::playerSaySpell(Player* player, SpeakClasses type, const std::string& void Game::playerWhisper(Player* player, const std::string& text) { - SpectatorVec spectators; - map.getSpectators(spectators, player->getPosition(), false, false, + SpectatorVec list; + map.getSpectators(list, player->getPosition(), false, false, Map::maxClientViewportX, Map::maxClientViewportX, Map::maxClientViewportY, Map::maxClientViewportY); //send to client - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { if (Player* spectatorPlayer = spectator->getPlayer()) { if (!Position::areInRange<1, 1>(player->getPosition(), spectatorPlayer->getPosition())) { spectatorPlayer->sendCreatureSay(player, TALKTYPE_WHISPER, "pspsps"); @@ -3417,7 +3021,7 @@ void Game::playerWhisper(Player* player, const std::string& text) } //event method - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { spectator->onCreatureSay(player, TALKTYPE_WHISPER, text); } } @@ -3452,10 +3056,8 @@ bool Game::playerSpeakTo(Player* player, SpeakClasses type, const std::string& r return false; } - if (type == TALKTYPE_PRIVATE_RED_TO && (player->hasFlag(PlayerFlag_CanTalkRedPrivate) || player->getAccountType() >= ACCOUNT_TYPE_GAMEMASTER)) { - type = TALKTYPE_PRIVATE_RED_FROM; - } else { - type = TALKTYPE_PRIVATE_FROM; + if (type == TALKTYPE_PRIVATE_RED && (!player->hasFlag(PlayerFlag_CanTalkRedPrivate) || player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER)) { + type = TALKTYPE_PRIVATE; } toPlayer->sendPrivateMessage(player, type, text); @@ -3471,17 +3073,6 @@ bool Game::playerSpeakTo(Player* player, SpeakClasses type, const std::string& r return true; } -void Game::playerSpeakToNpc(Player* player, const std::string& text) -{ - SpectatorVec spectators; - map.getSpectators(spectators, player->getPosition()); - for (Creature* spectator : spectators) { - if (spectator->getNpc()) { - spectator->onCreatureSay(player, TALKTYPE_PRIVATE_PN, text); - } - } -} - //-- bool Game::canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight /*= true*/, int32_t rangex /*= Map::maxClientViewportX*/, int32_t rangey /*= Map::maxClientViewportY*/) const @@ -3503,16 +3094,16 @@ bool Game::internalCreatureTurn(Creature* creature, Direction dir) creature->setDirection(dir); //send to client - SpectatorVec spectators; - map.getSpectators(spectators, creature->getPosition(), true, true); - for (Creature* spectator : spectators) { + SpectatorVec list; + map.getSpectators(list, creature->getPosition(), true, true); + for (Creature* spectator : list) { spectator->getPlayer()->sendCreatureTurn(creature); } return true; } bool Game::internalCreatureSay(Creature* creature, SpeakClasses type, const std::string& text, - bool ghostMode, SpectatorVec* spectatorsPtr/* = nullptr*/, const Position* pos/* = nullptr*/) + bool ghostMode, SpectatorVec* listPtr/* = nullptr*/, const Position* pos/* = nullptr*/) { if (text.empty()) { return false; @@ -3522,26 +3113,26 @@ bool Game::internalCreatureSay(Creature* creature, SpeakClasses type, const std: pos = &creature->getPosition(); } - SpectatorVec spectators; + SpectatorVec list; - if (!spectatorsPtr || spectatorsPtr->empty()) { + if (!listPtr || listPtr->empty()) { // This somewhat complex construct ensures that the cached SpectatorVec // is used if available and if it can be used, else a local vector is // used (hopefully the compiler will optimize away the construction of // the temporary when it's not used). if (type != TALKTYPE_YELL && type != TALKTYPE_MONSTER_YELL) { - map.getSpectators(spectators, *pos, false, false, + map.getSpectators(list, *pos, false, false, Map::maxClientViewportX, Map::maxClientViewportX, Map::maxClientViewportY, Map::maxClientViewportY); } else { - map.getSpectators(spectators, *pos, true, false, 18, 18, 14, 14); + map.getSpectators(list, *pos, true, false, 30, 30, 30, 30); } } else { - spectators = (*spectatorsPtr); + list = (*listPtr); } //send to client - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { if (Player* tmpPlayer = spectator->getPlayer()) { if (!ghostMode || tmpPlayer->canSeeCreature(creature)) { tmpPlayer->sendCreatureSay(creature, type, text, pos); @@ -3550,7 +3141,7 @@ bool Game::internalCreatureSay(Creature* creature, SpeakClasses type, const std: } //event method - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { spectator->onCreatureSay(creature, type, text); } return true; @@ -3631,15 +3222,12 @@ void Game::checkCreatures(size_t index) void Game::changeSpeed(Creature* creature, int32_t varSpeedDelta) { - int32_t varSpeed = creature->getSpeed() - creature->getBaseSpeed(); - varSpeed += varSpeedDelta; - - creature->setSpeed(varSpeed); + creature->setSpeed(varSpeedDelta); //send to clients - SpectatorVec spectators; - map.getSpectators(spectators, creature->getPosition(), false, true); - for (Creature* spectator : spectators) { + SpectatorVec list; + map.getSpectators(list, creature->getPosition(), false, true); + for (Creature* spectator : list) { spectator->getPlayer()->sendChangeSpeed(creature, creature->getStepSpeed()); } } @@ -3657,9 +3245,9 @@ void Game::internalCreatureChangeOutfit(Creature* creature, const Outfit_t& outf } //send to clients - SpectatorVec spectators; - map.getSpectators(spectators, creature->getPosition(), true, true); - for (Creature* spectator : spectators) { + SpectatorVec list; + map.getSpectators(list, creature->getPosition(), true, true); + for (Creature* spectator : list) { spectator->getPlayer()->sendCreatureChangeOutfit(creature, outfit); } } @@ -3667,9 +3255,9 @@ void Game::internalCreatureChangeOutfit(Creature* creature, const Outfit_t& outf void Game::internalCreatureChangeVisible(Creature* creature, bool visible) { //send to clients - SpectatorVec spectators; - map.getSpectators(spectators, creature->getPosition(), true, true); - for (Creature* spectator : spectators) { + SpectatorVec list; + map.getSpectators(list, creature->getPosition(), true, true); + for (Creature* spectator : list) { spectator->getPlayer()->sendCreatureChangeVisible(creature, visible); } } @@ -3677,16 +3265,16 @@ void Game::internalCreatureChangeVisible(Creature* creature, bool visible) void Game::changeLight(const Creature* creature) { //send to clients - SpectatorVec spectators; - map.getSpectators(spectators, creature->getPosition(), true, true); - for (Creature* spectator : spectators) { + SpectatorVec list; + map.getSpectators(list, creature->getPosition(), true, true); + for (Creature* spectator : list) { spectator->getPlayer()->sendCreatureLight(creature); } } bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* target, bool checkDefense, bool checkArmor, bool field) { - if (damage.primary.type == COMBAT_NONE && damage.secondary.type == COMBAT_NONE) { + if (damage.type == COMBAT_NONE) { return true; } @@ -3694,7 +3282,7 @@ bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* ta return true; } - if (damage.primary.value > 0) { + if (damage.value > 0) { return false; } @@ -3711,9 +3299,7 @@ bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* ta } case COMBAT_ENERGYDAMAGE: case COMBAT_FIREDAMAGE: - case COMBAT_PHYSICALDAMAGE: - case COMBAT_ICEDAMAGE: - case COMBAT_DEATHDAMAGE: { + case COMBAT_PHYSICALDAMAGE: { hitEffect = CONST_ME_BLOCKHIT; break; } @@ -3721,10 +3307,6 @@ bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* ta hitEffect = CONST_ME_GREEN_RINGS; break; } - case COMBAT_HOLYDAMAGE: { - hitEffect = CONST_ME_HOLYDAMAGE; - break; - } default: { hitEffect = CONST_ME_POFF; break; @@ -3734,27 +3316,18 @@ bool Game::combatBlockHit(CombatDamage& damage, Creature* attacker, Creature* ta } }; - BlockType_t primaryBlockType, secondaryBlockType; - if (damage.primary.type != COMBAT_NONE) { - damage.primary.value = -damage.primary.value; - primaryBlockType = target->blockHit(attacker, damage.primary.type, damage.primary.value, checkDefense, checkArmor, field); + BlockType_t primaryBlockType; + if (damage.type != COMBAT_NONE) { + damage.value = -damage.value; + primaryBlockType = target->blockHit(attacker, damage.type, damage.value, checkDefense, checkArmor, field); - damage.primary.value = -damage.primary.value; - sendBlockEffect(primaryBlockType, damage.primary.type, target->getPosition()); + damage.value = -damage.value; + sendBlockEffect(primaryBlockType, damage.type, target->getPosition()); } else { primaryBlockType = BLOCK_NONE; } - if (damage.secondary.type != COMBAT_NONE) { - damage.secondary.value = -damage.secondary.value; - secondaryBlockType = target->blockHit(attacker, damage.secondary.type, damage.secondary.value, false, false, field); - - damage.secondary.value = -damage.secondary.value; - sendBlockEffect(secondaryBlockType, damage.secondary.type, target->getPosition()); - } else { - secondaryBlockType = BLOCK_NONE; - } - return (primaryBlockType != BLOCK_NONE) && (secondaryBlockType != BLOCK_NONE); + return (primaryBlockType != BLOCK_NONE); } void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColor_t& color, uint8_t& effect) @@ -3771,11 +3344,7 @@ void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColo case RACE_BLOOD: color = TEXTCOLOR_RED; effect = CONST_ME_DRAWBLOOD; - if (const Tile* tile = target->getTile()) { - if (!tile->hasFlag(TILESTATE_PROTECTIONZONE)) { - splash = Item::CreateItem(ITEM_SMALLSPLASH, FLUID_BLOOD); - } - } + splash = Item::CreateItem(ITEM_SMALLSPLASH, FLUID_BLOOD); break; case RACE_UNDEAD: color = TEXTCOLOR_LIGHTGREY; @@ -3785,10 +3354,6 @@ void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColo color = TEXTCOLOR_ORANGE; effect = CONST_ME_DRAWBLOOD; break; - case RACE_ENERGY: - color = TEXTCOLOR_ELECTRICPURPLE; - effect = CONST_ME_ENERGYHIT; - break; default: color = TEXTCOLOR_NONE; effect = CONST_ME_NONE; @@ -3804,7 +3369,7 @@ void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColo } case COMBAT_ENERGYDAMAGE: { - color = TEXTCOLOR_ELECTRICPURPLE; + color = TEXTCOLOR_LIGHTBLUE; effect = CONST_ME_ENERGYHIT; break; } @@ -3815,36 +3380,23 @@ void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColo break; } - case COMBAT_DROWNDAMAGE: { - color = TEXTCOLOR_LIGHTBLUE; - effect = CONST_ME_LOSEENERGY; - break; - } case COMBAT_FIREDAMAGE: { color = TEXTCOLOR_ORANGE; effect = CONST_ME_HITBYFIRE; break; } - case COMBAT_ICEDAMAGE: { - color = TEXTCOLOR_SKYBLUE; - effect = CONST_ME_ICEATTACK; - break; - } - case COMBAT_HOLYDAMAGE: { - color = TEXTCOLOR_YELLOW; - effect = CONST_ME_HOLYDAMAGE; - break; - } - case COMBAT_DEATHDAMAGE: { - color = TEXTCOLOR_DARKRED; - effect = CONST_ME_SMALLCLOUDS; - break; - } + case COMBAT_LIFEDRAIN: { color = TEXTCOLOR_RED; effect = CONST_ME_MAGIC_RED; break; } + + case COMBAT_DROWNDAMAGE: { + color = TEXTCOLOR_LIGHTBLUE; + effect = CONST_ME_LOSEENERGY; + break; + } default: { color = TEXTCOLOR_NONE; effect = CONST_ME_NONE; @@ -3856,23 +3408,11 @@ void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColo bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage& damage) { const Position& targetPos = target->getPosition(); - if (damage.primary.value > 0) { + if (damage.value > 0) { if (target->getHealth() <= 0) { return false; } - Player* attackerPlayer; - if (attacker) { - attackerPlayer = attacker->getPlayer(); - } else { - attackerPlayer = nullptr; - } - - Player* targetPlayer = target->getPlayer(); - if (attackerPlayer && targetPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { - return false; - } - if (damage.origin != ORIGIN_NONE) { const auto& events = target->getCreatureEvents(CREATURE_EVENT_HEALTHCHANGE); if (!events.empty()) { @@ -3885,66 +3425,24 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } int32_t realHealthChange = target->getHealth(); - target->gainHealth(attacker, damage.primary.value); + target->gainHealth(attacker, damage.value); realHealthChange = target->getHealth() - realHealthChange; if (realHealthChange > 0 && !target->isInGhostMode()) { - std::stringstream ss; - - ss << realHealthChange << (realHealthChange != 1 ? " hitpoints." : " hitpoint."); - std::string damageString = ss.str(); - - std::string spectatorMessage; - - TextMessage message; - message.position = targetPos; - message.primary.value = realHealthChange; - message.primary.color = TEXTCOLOR_PASTELRED; - - SpectatorVec spectators; - map.getSpectators(spectators, targetPos, false, true); - for (Creature* spectator : spectators) { - Player* tmpPlayer = spectator->getPlayer(); - if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) { - ss.str({}); - ss << "You heal " << target->getNameDescription() << " for " << damageString; - message.type = MESSAGE_HEALED; - message.text = ss.str(); - } else if (tmpPlayer == targetPlayer) { - ss.str({}); - if (!attacker) { - ss << "You were healed"; - } else if (targetPlayer == attackerPlayer) { - ss << "You healed yourself"; - } else { - ss << "You were healed by " << attacker->getNameDescription(); - } - ss << " for " << damageString; - message.type = MESSAGE_HEALED; - message.text = ss.str(); - } else { - if (spectatorMessage.empty()) { - ss.str({}); - if (!attacker) { - ss << ucfirst(target->getNameDescription()) << " was healed"; - } else { - ss << ucfirst(attacker->getNameDescription()) << " healed "; - if (attacker == target) { - ss << (targetPlayer ? (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "herself" : "himself") : "itself"); - } else { - ss << target->getNameDescription(); - } - } - ss << " for " << damageString; - spectatorMessage = ss.str(); - } - message.type = MESSAGE_HEALED_OTHERS; - message.text = spectatorMessage; - } - tmpPlayer->sendTextMessage(message); + addMagicEffect(targetPos, CONST_ME_MAGIC_BLUE); + } + } + else { + if (Monster* monster = target->getMonster()) { + // makes monsters aggressive when damaged + // basically stands for UNDERATTACK stance under CipSoft servers + // the attacker must be valid everytime (avoid field ticks damage to trigger condition) + if (!monster->hasCondition(CONDITION_AGGRESSIVE) && attacker) { + Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_AGGRESSIVE, 3000); + monster->addCondition(condition, true); } } - } else { + if (!target->isAttackable()) { if (!target->isInGhostMode()) { addMagicEffect(targetPos, CONST_ME_POFF); @@ -3955,50 +3453,19 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage Player* attackerPlayer; if (attacker) { attackerPlayer = attacker->getPlayer(); - } else { + } + else { attackerPlayer = nullptr; } + damage.value = std::abs(damage.value); - Player* targetPlayer = target->getPlayer(); - if (attackerPlayer && targetPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { - return false; - } - - damage.primary.value = std::abs(damage.primary.value); - damage.secondary.value = std::abs(damage.secondary.value); - - int32_t healthChange = damage.primary.value + damage.secondary.value; + int32_t healthChange = damage.value; if (healthChange == 0) { return true; } - - if (attackerPlayer) { - uint16_t chance = attackerPlayer->getSpecialSkill(SPECIALSKILL_LIFELEECHCHANCE); - if (chance != 0 && uniform_random(1, 100) <= chance) { - CombatDamage lifeLeech; - lifeLeech.primary.value = std::round(healthChange * (attackerPlayer->getSpecialSkill(SPECIALSKILL_LIFELEECHAMOUNT) / 100.)); - combatChangeHealth(nullptr, attackerPlayer, lifeLeech); - } - - chance = attackerPlayer->getSpecialSkill(SPECIALSKILL_MANALEECHCHANCE); - if (chance != 0 && uniform_random(1, 100) <= chance) { - CombatDamage manaLeech; - manaLeech.primary.value = std::round(healthChange * (attackerPlayer->getSpecialSkill(SPECIALSKILL_MANALEECHAMOUNT) / 100.)); - combatChangeMana(nullptr, attackerPlayer, manaLeech); - } - - chance = attackerPlayer->getSpecialSkill(SPECIALSKILL_CRITICALHITCHANCE); - if (chance != 0 && uniform_random(1, 100) <= chance) { - healthChange += std::round(healthChange * (attackerPlayer->getSpecialSkill(SPECIALSKILL_CRITICALHITAMOUNT) / 100.)); - addMagicEffect(target->getPosition(), CONST_ME_CRITICAL_DAMAGE); - } - } - - TextMessage message; - message.position = targetPos; - - SpectatorVec spectators; - if (targetPlayer && target->hasCondition(CONDITION_MANASHIELD) && damage.primary.type != COMBAT_UNDEFINEDDAMAGE) { + Player* targetPlayer = target->getPlayer(); + SpectatorVec list; + if (target->hasCondition(CONDITION_MANASHIELD) && damage.type != COMBAT_UNDEFINEDDAMAGE) { int32_t manaDamage = std::min(targetPlayer->getMana(), healthChange); if (manaDamage != 0) { if (damage.origin != ORIGIN_NONE) { @@ -4007,186 +3474,93 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage for (CreatureEvent* creatureEvent : events) { creatureEvent->executeManaChange(target, attacker, damage); } - healthChange = damage.primary.value + damage.secondary.value; + healthChange = damage.value; if (healthChange == 0) { return true; } manaDamage = std::min(targetPlayer->getMana(), healthChange); } } - targetPlayer->drainMana(attacker, manaDamage); - map.getSpectators(spectators, targetPos, true, true); - addMagicEffect(spectators, targetPos, CONST_ME_LOSEENERGY); - - std::stringstream ss; + map.getSpectators(list, targetPos, true, true); + addMagicEffect(list, targetPos, CONST_ME_LOSEENERGY); std::string damageString = std::to_string(manaDamage); - std::string spectatorMessage; - - message.primary.value = manaDamage; - message.primary.color = TEXTCOLOR_BLUE; - - for (Creature* spectator : spectators) { - Player* tmpPlayer = spectator->getPlayer(); - if (tmpPlayer->getPosition().z != targetPos.z) { - continue; + if (targetPlayer) { + std::stringstream ss; + if (!attacker) { + ss << "You lose " << damageString << " mana."; } - - if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) { - ss.str({}); - ss << ucfirst(target->getNameDescription()) << " loses " << damageString + " mana due to your attack."; - message.type = MESSAGE_DAMAGE_DEALT; - message.text = ss.str(); - } else if (tmpPlayer == targetPlayer) { - ss.str({}); - ss << "You lose " << damageString << " mana"; - if (!attacker) { - ss << '.'; - } else if (targetPlayer == attackerPlayer) { - ss << " due to your own attack."; - } else { - ss << " due to an attack by " << attacker->getNameDescription() << '.'; - } - message.type = MESSAGE_DAMAGE_RECEIVED; - message.text = ss.str(); - } else { - if (spectatorMessage.empty()) { - ss.str({}); - ss << ucfirst(target->getNameDescription()) << " loses " << damageString + " mana"; - if (attacker) { - ss << " due to "; - if (attacker == target) { - ss << (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her own attack" : "his own attack"); - } else { - ss << "an attack by " << attacker->getNameDescription(); - } - } - ss << '.'; - spectatorMessage = ss.str(); - } - message.type = MESSAGE_DAMAGE_OTHERS; - message.text = spectatorMessage; + else if (targetPlayer == attackerPlayer) { + ss << "You lose " << damageString << " mana due to your own attack."; } - tmpPlayer->sendTextMessage(message); + else { + ss << "You lose " << damageString << " mana due to an attack by " << attacker->getNameDescription() << '.'; + } + targetPlayer->sendTextMessage(MESSAGE_EVENT_DEFAULT, ss.str()); } - damage.primary.value -= manaDamage; - if (damage.primary.value < 0) { - damage.secondary.value = std::max(0, damage.secondary.value + damage.primary.value); - damage.primary.value = 0; + for (Creature* spectator : list) { + Player* tmpPlayer = spectator->getPlayer(); + tmpPlayer->sendAnimatedText(targetPos, TEXTCOLOR_BLUE, damageString); + } + + damage.value -= manaDamage; + if (damage.value < 0) { + damage.value = 0; } } } - int32_t realDamage = damage.primary.value + damage.secondary.value; + int32_t realDamage = damage.value; if (realDamage == 0) { return true; } - if (damage.origin != ORIGIN_NONE) { - const auto& events = target->getCreatureEvents(CREATURE_EVENT_HEALTHCHANGE); - if (!events.empty()) { - for (CreatureEvent* creatureEvent : events) { - creatureEvent->executeHealthChange(target, attacker, damage); - } - damage.origin = ORIGIN_NONE; - return combatChangeHealth(attacker, target, damage); - } - } - int32_t targetHealth = target->getHealth(); - if (damage.primary.value >= targetHealth) { - damage.primary.value = targetHealth; - damage.secondary.value = 0; - } else if (damage.secondary.value) { - damage.secondary.value = std::min(damage.secondary.value, targetHealth - damage.primary.value); + if (damage.value >= targetHealth) { + damage.value = targetHealth; } - realDamage = damage.primary.value + damage.secondary.value; + realDamage = damage.value; if (realDamage == 0) { return true; } - if (spectators.empty()) { - map.getSpectators(spectators, targetPos, true, true); + if (list.empty()) { + map.getSpectators(list, targetPos, true, true); } - message.primary.value = damage.primary.value; - message.secondary.value = damage.secondary.value; - + TextColor_t color = TEXTCOLOR_NONE; uint8_t hitEffect; - if (message.primary.value) { - combatGetTypeInfo(damage.primary.type, target, message.primary.color, hitEffect); + if (damage.value) { + combatGetTypeInfo(damage.type, target, color, hitEffect); if (hitEffect != CONST_ME_NONE) { - addMagicEffect(spectators, targetPos, hitEffect); + addMagicEffect(list, targetPos, hitEffect); } } - if (message.secondary.value) { - combatGetTypeInfo(damage.secondary.type, target, message.secondary.color, hitEffect); - if (hitEffect != CONST_ME_NONE) { - addMagicEffect(spectators, targetPos, hitEffect); + if (color != TEXTCOLOR_NONE) { + std::string damageString = std::to_string(realDamage) + (realDamage != 1 ? " hitpoints" : " hitpoint"); + if (targetPlayer) { + std::stringstream ss; + if (!attacker) { + ss << "You lose " << damageString << "."; + } + else if (targetPlayer == attackerPlayer) { + ss << "You lose " << damageString << " due to your own attack."; + } + else { + ss << "You lose " << damageString << " due to an attack by " << attacker->getNameDescription() << '.'; + } + targetPlayer->sendTextMessage(MESSAGE_EVENT_DEFAULT, ss.str()); } - } - if (message.primary.color != TEXTCOLOR_NONE || message.secondary.color != TEXTCOLOR_NONE) { - std::stringstream ss; - - ss << realDamage << (realDamage != 1 ? " hitpoints" : " hitpoint"); - std::string damageString = ss.str(); - - std::string spectatorMessage; - - for (Creature* spectator : spectators) { + std::string realDamageStr = std::to_string(realDamage); + for (Creature* spectator : list) { Player* tmpPlayer = spectator->getPlayer(); - if (tmpPlayer->getPosition().z != targetPos.z) { - continue; - } - - if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) { - ss.str({}); - ss << ucfirst(target->getNameDescription()) << " loses " << damageString << " due to your attack."; - message.type = MESSAGE_DAMAGE_DEALT; - message.text = ss.str(); - } else if (tmpPlayer == targetPlayer) { - ss.str({}); - ss << "You lose " << damageString; - if (!attacker) { - ss << '.'; - } else if (targetPlayer == attackerPlayer) { - ss << " due to your own attack."; - } else { - ss << " due to an attack by " << attacker->getNameDescription() << '.'; - } - message.type = MESSAGE_DAMAGE_RECEIVED; - message.text = ss.str(); - } else { - message.type = MESSAGE_DAMAGE_OTHERS; - - if (spectatorMessage.empty()) { - ss.str({}); - ss << ucfirst(target->getNameDescription()) << " loses " << damageString; - if (attacker) { - ss << " due to "; - if (attacker == target) { - if (targetPlayer) { - ss << (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her own attack" : "his own attack"); - } else { - ss << "its own attack"; - } - } else { - ss << "an attack by " << attacker->getNameDescription(); - } - } - ss << '.'; - spectatorMessage = ss.str(); - } - - message.text = spectatorMessage; - } - tmpPlayer->sendTextMessage(message); + tmpPlayer->sendAnimatedText(targetPos, color, realDamageStr); } } @@ -4199,7 +3573,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } target->drainHealth(attacker, realDamage); - addCreatureHealth(spectators, target); + addCreatureHealth(list, target); } return true; @@ -4211,16 +3585,8 @@ bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& if (!targetPlayer) { return true; } - - int32_t manaChange = damage.primary.value + damage.secondary.value; + int32_t manaChange = damage.value; if (manaChange > 0) { - if (attacker) { - const Player* attackerPlayer = attacker->getPlayer(); - if (attackerPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(target) == SKULL_NONE) { - return false; - } - } - if (damage.origin != ORIGIN_NONE) { const auto& events = target->getCreatureEvents(CREATURE_EVENT_MANACHANGE); if (!events.empty()) { @@ -4231,19 +3597,9 @@ bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& return combatChangeMana(attacker, target, damage); } } - - int32_t realManaChange = targetPlayer->getMana(); targetPlayer->changeMana(manaChange); - realManaChange = targetPlayer->getMana() - realManaChange; - - if (realManaChange > 0 && !targetPlayer->isInGhostMode()) { - TextMessage message(MESSAGE_HEALED, "You gained " + std::to_string(realManaChange) + " mana."); - message.position = target->getPosition(); - message.primary.value = realManaChange; - message.primary.color = TEXTCOLOR_MAYABLUE; - targetPlayer->sendTextMessage(message); - } - } else { + } + else { const Position& targetPos = target->getPosition(); if (!target->isAttackable()) { if (!target->isInGhostMode()) { @@ -4255,12 +3611,9 @@ bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& Player* attackerPlayer; if (attacker) { attackerPlayer = attacker->getPlayer(); - } else { - attackerPlayer = nullptr; } - - if (attackerPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { - return false; + else { + attackerPlayer = nullptr; } int32_t manaLoss = std::min(targetPlayer->getMana(), -manaChange); @@ -4287,57 +3640,27 @@ bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& targetPlayer->drainMana(attacker, manaLoss); - std::stringstream ss; - std::string damageString = std::to_string(manaLoss); - std::string spectatorMessage; - - TextMessage message; - message.position = targetPos; - message.primary.value = manaLoss; - message.primary.color = TEXTCOLOR_BLUE; - - SpectatorVec spectators; - map.getSpectators(spectators, targetPos, false, true); - for (Creature* spectator : spectators) { - Player* tmpPlayer = spectator->getPlayer(); - if (tmpPlayer == attackerPlayer && attackerPlayer != targetPlayer) { - ss.str({}); - ss << ucfirst(target->getNameDescription()) << " loses " << damageString << " mana due to your attack."; - message.type = MESSAGE_DAMAGE_DEALT; - message.text = ss.str(); - } else if (tmpPlayer == targetPlayer) { - ss.str({}); - ss << "You lose " << damageString << " mana"; - if (!attacker) { - ss << '.'; - } else if (targetPlayer == attackerPlayer) { - ss << " due to your own attack."; - } else { - ss << " mana due to an attack by " << attacker->getNameDescription() << '.'; - } - message.type = MESSAGE_DAMAGE_RECEIVED; - message.text = ss.str(); - } else { - if (spectatorMessage.empty()) { - ss.str({}); - ss << ucfirst(target->getNameDescription()) << " loses " << damageString << " mana"; - if (attacker) { - ss << " due to "; - if (attacker == target) { - ss << (targetPlayer->getSex() == PLAYERSEX_FEMALE ? "her own attack" : "his own attack"); - } else { - ss << "an attack by " << attacker->getNameDescription(); - } - } - ss << '.'; - spectatorMessage = ss.str(); - } - message.type = MESSAGE_DAMAGE_OTHERS; - message.text = spectatorMessage; + SpectatorVec list; + map.getSpectators(list, targetPos, false, true); + if (targetPlayer) { + std::stringstream ss; + if (!attacker) { + ss << "You lose " << damageString << " mana."; } - tmpPlayer->sendTextMessage(message); + else if (targetPlayer == attackerPlayer) { + ss << "You lose " << damageString << " mana due to your own attack."; + } + else { + ss << "You lose " << damageString << " mana due to an attack by " << attacker->getNameDescription() << '.'; + } + targetPlayer->sendTextMessage(MESSAGE_EVENT_DEFAULT, ss.str()); + } + + for (Creature* spectator : list) { + Player* tmpPlayer = spectator->getPlayer(); + tmpPlayer->sendAnimatedText(targetPos, TEXTCOLOR_BLUE, damageString); } } @@ -4346,14 +3669,14 @@ bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& void Game::addCreatureHealth(const Creature* target) { - SpectatorVec spectators; - map.getSpectators(spectators, target->getPosition(), true, true); - addCreatureHealth(spectators, target); + SpectatorVec list; + map.getSpectators(list, target->getPosition(), true, true); + addCreatureHealth(list, target); } -void Game::addCreatureHealth(const SpectatorVec& spectators, const Creature* target) +void Game::addCreatureHealth(const SpectatorVec& list, const Creature* target) { - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendCreatureHealth(target); } @@ -4362,14 +3685,14 @@ void Game::addCreatureHealth(const SpectatorVec& spectators, const Creature* tar void Game::addMagicEffect(const Position& pos, uint8_t effect) { - SpectatorVec spectators; - map.getSpectators(spectators, pos, true, true); - addMagicEffect(spectators, pos, effect); + SpectatorVec list; + map.getSpectators(list, pos, true, true); + addMagicEffect(list, pos, effect); } -void Game::addMagicEffect(const SpectatorVec& spectators, const Position& pos, uint8_t effect) +void Game::addMagicEffect(const SpectatorVec& list, const Position& pos, uint8_t effect) { - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendMagicEffect(pos, effect); } @@ -4378,23 +3701,49 @@ void Game::addMagicEffect(const SpectatorVec& spectators, const Position& pos, u void Game::addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect) { - SpectatorVec spectators, toPosSpectators; - map.getSpectators(spectators, fromPos, false, true); - map.getSpectators(toPosSpectators, toPos, false, true); - spectators.addSpectators(toPosSpectators); - - addDistanceEffect(spectators, fromPos, toPos, effect); + SpectatorVec list; + map.getSpectators(list, fromPos, false, true); + map.getSpectators(list, toPos, false, true); + addDistanceEffect(list, fromPos, toPos, effect); } -void Game::addDistanceEffect(const SpectatorVec& spectators, const Position& fromPos, const Position& toPos, uint8_t effect) +void Game::addDistanceEffect(const SpectatorVec& list, const Position& fromPos, const Position& toPos, uint8_t effect) { - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendDistanceShoot(fromPos, toPos, effect); } } } +void Game::addAnimatedText(const Position& pos, uint8_t color, const std::string& text) +{ + SpectatorVec list; + map.getSpectators(list, pos, false, true); + addAnimatedText(list, pos, color, text); +} + +void Game::addAnimatedText(const SpectatorVec& list, const Position& pos, uint8_t color, const std::string& text) +{ + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + tmpPlayer->sendAnimatedText(pos, color, text); + } + } +} + +void Game::addMonsterSayText(const Position& pos, const std::string& text) +{ + SpectatorVec list; + map.getSpectators(list, pos, false, true); + + for (Creature* spectator : list) { + if (Player* tmpPlayer = spectator->getPlayer()) { + tmpPlayer->sendCreatureSay(tmpPlayer, TALKTYPE_MONSTER_SAY, text, &pos); + } + } +} + void Game::startDecay(Item* item) { if (!item || !item->canDecay()) { @@ -4419,7 +3768,7 @@ void Game::internalDecayItem(Item* item) { const ItemType& it = Item::items[item->getID()]; if (it.decayTo != 0) { - Item* newItem = transformItem(item, item->getDecayTo()); + Item* newItem = transformItem(item, it.decayTo); startDecay(newItem); } else { ReturnValue ret = internalRemoveItem(item); @@ -4518,7 +3867,8 @@ void Game::checkLight() } if (lightChange) { - LightInfo lightInfo = getWorldLightInfo(); + LightInfo lightInfo; + getWorldLightInfo(lightInfo); for (const auto& it : players) { it.second->sendWorldLight(lightInfo); @@ -4526,13 +3876,15 @@ void Game::checkLight() } } -LightInfo Game::getWorldLightInfo() const +void Game::getWorldLightInfo(LightInfo& lightInfo) const { - return {lightLevel, 0xD7}; + lightInfo.level = lightLevel; + lightInfo.color = 0xD7; } void Game::shutdown() { + saveGameState(); std::cout << "Shutting down..." << std::flush; g_scheduler.shutdown(); @@ -4594,87 +3946,28 @@ void Game::broadcastMessage(const std::string& text, MessageClasses type) const } } -void Game::updateCreatureWalkthrough(const Creature* creature) -{ - //send to clients - SpectatorVec spectators; - map.getSpectators(spectators, creature->getPosition(), true, true); - for (Creature* spectator : spectators) { - Player* tmpPlayer = spectator->getPlayer(); - tmpPlayer->sendCreatureWalkthrough(creature, tmpPlayer->canWalkthroughEx(creature)); - } -} - void Game::updateCreatureSkull(const Creature* creature) { if (getWorldType() != WORLD_TYPE_PVP) { return; } - SpectatorVec spectators; - map.getSpectators(spectators, creature->getPosition(), true, true); - for (Creature* spectator : spectators) { + SpectatorVec list; + map.getSpectators(list, creature->getPosition(), true, true); + for (Creature* spectator : list) { spectator->getPlayer()->sendCreatureSkull(creature); } } void Game::updatePlayerShield(Player* player) { - SpectatorVec spectators; - map.getSpectators(spectators, player->getPosition(), true, true); - for (Creature* spectator : spectators) { + SpectatorVec list; + map.getSpectators(list, player->getPosition(), true, true); + for (Creature* spectator : list) { spectator->getPlayer()->sendCreatureShield(player); } } -void Game::updatePlayerHelpers(const Player& player) -{ - uint32_t creatureId = player.getID(); - uint16_t helpers = player.getHelpers(); - - SpectatorVec spectators; - map.getSpectators(spectators, player.getPosition(), true, true); - for (Creature* spectator : spectators) { - spectator->getPlayer()->sendCreatureHelpers(creatureId, helpers); - } -} - -void Game::updateCreatureType(Creature* creature) -{ - const Player* masterPlayer = nullptr; - - uint32_t creatureId = creature->getID(); - CreatureType_t creatureType = creature->getType(); - if (creatureType == CREATURETYPE_MONSTER) { - const Creature* master = creature->getMaster(); - if (master) { - masterPlayer = master->getPlayer(); - if (masterPlayer) { - creatureType = CREATURETYPE_SUMMON_OTHERS; - } - } - } - - //send to clients - SpectatorVec spectators; - map.getSpectators(spectators, creature->getPosition(), true, true); - - if (creatureType == CREATURETYPE_SUMMON_OTHERS) { - for (Creature* spectator : spectators) { - Player* player = spectator->getPlayer(); - if (masterPlayer == player) { - player->sendCreatureType(creatureId, CREATURETYPE_SUMMON_OWN); - } else { - player->sendCreatureType(creatureId, creatureType); - } - } - } else { - for (Creature* spectator : spectators) { - spectator->getPlayer()->sendCreatureType(creatureId, creatureType); - } - } -} - void Game::updatePremium(Account& account) { bool save = false; @@ -4705,43 +3998,43 @@ void Game::updatePremium(Account& account) } if (save && !IOLoginData::saveAccount(account)) { - std::cout << "> ERROR: Failed to save account: " << account.name << "!" << std::endl; + std::cout << "> ERROR: Failed to save account: " << account.id << "!" << std::endl; } } void Game::loadMotdNum() { - Database& db = Database::getInstance(); + Database* db = Database::getInstance(); - DBResult_ptr result = db.storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'motd_num'"); + DBResult_ptr result = db->storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'motd_num'"); if (result) { motdNum = result->getNumber("value"); } else { - db.executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('motd_num', '0')"); + db->executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('motd_num', '0')"); } - result = db.storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'motd_hash'"); + result = db->storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'motd_hash'"); if (result) { motdHash = result->getString("value"); if (motdHash != transformToSHA1(g_config.getString(ConfigManager::MOTD))) { ++motdNum; } } else { - db.executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('motd_hash', '')"); + db->executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('motd_hash', '')"); } } void Game::saveMotdNum() const { - Database& db = Database::getInstance(); + Database* db = Database::getInstance(); std::ostringstream query; query << "UPDATE `server_config` SET `value` = '" << motdNum << "' WHERE `config` = 'motd_num'"; - db.executeQuery(query.str()); + db->executeQuery(query.str()); query.str(std::string()); query << "UPDATE `server_config` SET `value` = '" << transformToSHA1(g_config.getString(ConfigManager::MOTD)) << "' WHERE `config` = 'motd_hash'"; - db.executeQuery(query.str()); + db->executeQuery(query.str()); } void Game::checkPlayersRecord() @@ -4751,8 +4044,8 @@ void Game::checkPlayersRecord() uint32_t previousRecord = playersRecord; playersRecord = playersOnline; - for (auto& it : g_globalEvents->getEventMap(GLOBALEVENT_RECORD)) { - it.second.executeRecord(playersRecord, previousRecord); + for (const auto& it : g_globalEvents->getEventMap(GLOBALEVENT_RECORD)) { + it.second->executeRecord(playersRecord, previousRecord); } updatePlayersRecord(); } @@ -4760,22 +4053,22 @@ void Game::checkPlayersRecord() void Game::updatePlayersRecord() const { - Database& db = Database::getInstance(); + Database* db = Database::getInstance(); std::ostringstream query; query << "UPDATE `server_config` SET `value` = '" << playersRecord << "' WHERE `config` = 'players_record'"; - db.executeQuery(query.str()); + db->executeQuery(query.str()); } void Game::loadPlayersRecord() { - Database& db = Database::getInstance(); + Database* db = Database::getInstance(); - DBResult_ptr result = db.storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'players_record'"); + DBResult_ptr result = db->storeQuery("SELECT `value` FROM `server_config` WHERE `config` = 'players_record'"); if (result) { playersRecord = result->getNumber("value"); } else { - db.executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('players_record', '0')"); + db->executeQuery("INSERT INTO `server_config` (`config`, `value`) VALUES ('players_record', '0')"); } } @@ -4844,10 +4137,6 @@ bool Game::loadExperienceStages() void Game::playerInviteToParty(uint32_t playerId, uint32_t invitedId) { - if (playerId == invitedId) { - return; - } - Player* player = getPlayerByID(playerId); if (!player) { return; @@ -4963,24 +4252,113 @@ void Game::playerEnableSharedPartyExperience(uint32_t playerId, bool sharedExpAc } Party* party = player->getParty(); - if (!party || (player->hasCondition(CONDITION_INFIGHT) && player->getZone() != ZONE_PROTECTION)) { + if (!party || player->hasCondition(CONDITION_INFIGHT)) { return; } party->setSharedExperience(player, sharedExpActive); } -void Game::sendGuildMotd(uint32_t playerId) +void Game::playerProcessRuleViolationReport(uint32_t playerId, const std::string& name) { Player* player = getPlayerByID(playerId); if (!player) { return; } - Guild* guild = player->getGuild(); - if (guild) { - player->sendChannelMessage("Message of the Day", guild->getMotd(), TALKTYPE_CHANNEL_R1, CHANNEL_GUILD); + if (player->getAccountType() < ACCOUNT_TYPE_GAMEMASTER) { + return; } + + Player* reporter = getPlayerByName(name); + if (!reporter) { + return; + } + + auto it = ruleViolations.find(reporter->getID()); + if (it == ruleViolations.end()) { + return; + } + + RuleViolation& ruleViolation = it->second; + if (!ruleViolation.pending) { + return; + } + + ruleViolation.gamemasterId = player->getID(); + ruleViolation.pending = false; + + ChatChannel* channel = g_chat->getChannelById(CHANNEL_RULE_REP); + if (channel) { + for (auto userPtr : channel->getUsers()) { + if (userPtr.second) { + userPtr.second->sendRemoveRuleViolationReport(reporter->getName()); + } + } + } +} + +void Game::playerCloseRuleViolationReport(uint32_t playerId, const std::string& name) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + Player* reporter = getPlayerByName(name); + if (!reporter) { + return; + } + + closeRuleViolationReport(reporter); +} + +void Game::playerCancelRuleViolationReport(uint32_t playerId) +{ + Player* player = getPlayerByID(playerId); + if (!player) { + return; + } + + cancelRuleViolationReport(player); +} + +void Game::playerReportRuleViolationReport(Player* player, const std::string& text) +{ + auto it = ruleViolations.find(player->getID()); + if (it != ruleViolations.end()) { + player->sendCancelMessage("You already have a pending rule violation report. Close it before starting a new one."); + return; + } + + RuleViolation ruleViolation = RuleViolation(player->getID(), text); + ruleViolations[player->getID()] = ruleViolation; + + ChatChannel* channel = g_chat->getChannelById(CHANNEL_RULE_REP); + if (channel) { + for (auto userPtr : channel->getUsers()) { + if (userPtr.second) { + userPtr.second->sendToChannel(player, TALKTYPE_RVR_CHANNEL, text, CHANNEL_RULE_REP); + } + } + } +} + +void Game::playerContinueRuleViolationReport(Player* player, const std::string& text) +{ + auto it = ruleViolations.find(player->getID()); + if (it == ruleViolations.end()) { + return; + } + + RuleViolation& rvr = it->second; + Player* toPlayer = getPlayerByID(rvr.gamemasterId); + if (!toPlayer) { + return; + } + + toPlayer->sendCreatureSay(player, TALKTYPE_RVR_CONTINUE, text, 0); + player->sendTextMessage(MESSAGE_STATUS_SMALL, "Message sent to Counsellor."); } void Game::kickPlayer(uint32_t playerId, bool displayEffect) @@ -4993,24 +4371,15 @@ void Game::kickPlayer(uint32_t playerId, bool displayEffect) player->kickPlayer(displayEffect); } -void Game::playerReportRuleViolation(uint32_t playerId, const std::string& targetName, uint8_t reportType, uint8_t reportReason, const std::string& comment, const std::string& translation) +void Game::playerReportBug(uint32_t playerId, const std::string& message) { Player* player = getPlayerByID(playerId); if (!player) { return; } - g_events->eventPlayerOnReportRuleViolation(player, targetName, reportType, reportReason, comment, translation); -} - -void Game::playerReportBug(uint32_t playerId, const std::string& message, const Position& position, uint8_t category) -{ - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - g_events->eventPlayerOnReportBug(player, message, position, category); + const Position& position = player->getPosition(); + g_events->eventPlayerOnReportBug(player, message, position); } void Game::playerDebugAssert(uint32_t playerId, const std::string& assertLine, const std::string& date, const std::string& description, const std::string& comment) @@ -5029,411 +4398,6 @@ void Game::playerDebugAssert(uint32_t playerId, const std::string& assertLine, c } } -void Game::playerLeaveMarket(uint32_t playerId) -{ - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - player->setInMarket(false); -} - -void Game::playerBrowseMarket(uint32_t playerId, uint16_t spriteId) -{ - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - if (!player->isInMarket()) { - return; - } - - const ItemType& it = Item::items.getItemIdByClientId(spriteId); - if (it.id == 0) { - return; - } - - if (it.wareId == 0) { - return; - } - - const MarketOfferList& buyOffers = IOMarket::getActiveOffers(MARKETACTION_BUY, it.id); - const MarketOfferList& sellOffers = IOMarket::getActiveOffers(MARKETACTION_SELL, it.id); - player->sendMarketBrowseItem(it.id, buyOffers, sellOffers); - player->sendMarketDetail(it.id); -} - -void Game::playerBrowseMarketOwnOffers(uint32_t playerId) -{ - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - if (!player->isInMarket()) { - return; - } - - const MarketOfferList& buyOffers = IOMarket::getOwnOffers(MARKETACTION_BUY, player->getGUID()); - const MarketOfferList& sellOffers = IOMarket::getOwnOffers(MARKETACTION_SELL, player->getGUID()); - player->sendMarketBrowseOwnOffers(buyOffers, sellOffers); -} - -void Game::playerBrowseMarketOwnHistory(uint32_t playerId) -{ - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - if (!player->isInMarket()) { - return; - } - - const HistoryMarketOfferList& buyOffers = IOMarket::getOwnHistory(MARKETACTION_BUY, player->getGUID()); - const HistoryMarketOfferList& sellOffers = IOMarket::getOwnHistory(MARKETACTION_SELL, player->getGUID()); - player->sendMarketBrowseOwnHistory(buyOffers, sellOffers); -} - -void Game::playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t spriteId, uint16_t amount, uint32_t price, bool anonymous) -{ - if (amount == 0 || amount > 64000) { - return; - } - - if (price == 0 || price > 999999999) { - return; - } - - if (type != MARKETACTION_BUY && type != MARKETACTION_SELL) { - return; - } - - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - if (!player->isInMarket()) { - return; - } - - if (g_config.getBoolean(ConfigManager::MARKET_PREMIUM) && !player->isPremium()) { - player->sendMarketLeave(); - return; - } - - const ItemType& itt = Item::items.getItemIdByClientId(spriteId); - if (itt.id == 0 || itt.wareId == 0) { - return; - } - - const ItemType& it = Item::items.getItemIdByClientId(itt.wareId); - if (it.id == 0 || it.wareId == 0) { - return; - } - - if (!it.stackable && amount > 2000) { - return; - } - - const uint32_t maxOfferCount = g_config.getNumber(ConfigManager::MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER); - if (maxOfferCount != 0 && IOMarket::getPlayerOfferCount(player->getGUID()) >= maxOfferCount) { - return; - } - - uint64_t fee = (price / 100.) * amount; - if (fee < 20) { - fee = 20; - } else if (fee > 1000) { - fee = 1000; - } - - if (type == MARKETACTION_SELL) { - if (fee > player->bankBalance) { - return; - } - - DepotChest* depotChest = player->getDepotChest(player->getLastDepotId(), false); - if (!depotChest) { - return; - } - - std::forward_list itemList = getMarketItemList(it.wareId, amount, depotChest, player->getInbox()); - if (itemList.empty()) { - return; - } - - if (it.stackable) { - uint16_t tmpAmount = amount; - for (Item* item : itemList) { - uint16_t removeCount = std::min(tmpAmount, item->getItemCount()); - tmpAmount -= removeCount; - internalRemoveItem(item, removeCount); - - if (tmpAmount == 0) { - break; - } - } - } else { - for (Item* item : itemList) { - internalRemoveItem(item); - } - } - - player->bankBalance -= fee; - } else { - uint64_t totalPrice = static_cast(price) * amount; - totalPrice += fee; - if (totalPrice > player->bankBalance) { - return; - } - - player->bankBalance -= totalPrice; - } - - IOMarket::createOffer(player->getGUID(), static_cast(type), it.id, amount, price, anonymous); - - player->sendMarketEnter(player->getLastDepotId()); - const MarketOfferList& buyOffers = IOMarket::getActiveOffers(MARKETACTION_BUY, it.id); - const MarketOfferList& sellOffers = IOMarket::getActiveOffers(MARKETACTION_SELL, it.id); - player->sendMarketBrowseItem(it.id, buyOffers, sellOffers); -} - -void Game::playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter) -{ - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - if (!player->isInMarket()) { - return; - } - - MarketOfferEx offer = IOMarket::getOfferByCounter(timestamp, counter); - if (offer.id == 0 || offer.playerId != player->getGUID()) { - return; - } - - if (offer.type == MARKETACTION_BUY) { - player->bankBalance += static_cast(offer.price) * offer.amount; - player->sendMarketEnter(player->getLastDepotId()); - } else { - const ItemType& it = Item::items[offer.itemId]; - if (it.id == 0) { - return; - } - - if (it.stackable) { - uint16_t tmpAmount = offer.amount; - while (tmpAmount > 0) { - int32_t stackCount = std::min(100, tmpAmount); - Item* item = Item::CreateItem(it.id, stackCount); - if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { - delete item; - break; - } - - tmpAmount -= stackCount; - } - } else { - int32_t subType; - if (it.charges != 0) { - subType = it.charges; - } else { - subType = -1; - } - - for (uint16_t i = 0; i < offer.amount; ++i) { - Item* item = Item::CreateItem(it.id, subType); - if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { - delete item; - break; - } - } - } - } - - IOMarket::moveOfferToHistory(offer.id, OFFERSTATE_CANCELLED); - offer.amount = 0; - offer.timestamp += g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); - player->sendMarketCancelOffer(offer); - player->sendMarketEnter(player->getLastDepotId()); -} - -void Game::playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter, uint16_t amount) -{ - if (amount == 0 || amount > 64000) { - return; - } - - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - if (!player->isInMarket()) { - return; - } - - MarketOfferEx offer = IOMarket::getOfferByCounter(timestamp, counter); - if (offer.id == 0) { - return; - } - - if (amount > offer.amount) { - return; - } - - const ItemType& it = Item::items[offer.itemId]; - if (it.id == 0) { - return; - } - - uint64_t totalPrice = static_cast(offer.price) * amount; - - if (offer.type == MARKETACTION_BUY) { - DepotChest* depotChest = player->getDepotChest(player->getLastDepotId(), false); - if (!depotChest) { - return; - } - - std::forward_list itemList = getMarketItemList(it.wareId, amount, depotChest, player->getInbox()); - if (itemList.empty()) { - return; - } - - Player* buyerPlayer = getPlayerByGUID(offer.playerId); - if (!buyerPlayer) { - buyerPlayer = new Player(nullptr); - if (!IOLoginData::loadPlayerById(buyerPlayer, offer.playerId)) { - delete buyerPlayer; - return; - } - } - - if (it.stackable) { - uint16_t tmpAmount = amount; - for (Item* item : itemList) { - uint16_t removeCount = std::min(tmpAmount, item->getItemCount()); - tmpAmount -= removeCount; - internalRemoveItem(item, removeCount); - - if (tmpAmount == 0) { - break; - } - } - } else { - for (Item* item : itemList) { - internalRemoveItem(item); - } - } - - player->bankBalance += totalPrice; - - if (it.stackable) { - uint16_t tmpAmount = amount; - while (tmpAmount > 0) { - uint16_t stackCount = std::min(100, tmpAmount); - Item* item = Item::CreateItem(it.id, stackCount); - if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { - delete item; - break; - } - - tmpAmount -= stackCount; - } - } else { - int32_t subType; - if (it.charges != 0) { - subType = it.charges; - } else { - subType = -1; - } - - for (uint16_t i = 0; i < amount; ++i) { - Item* item = Item::CreateItem(it.id, subType); - if (internalAddItem(buyerPlayer->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { - delete item; - break; - } - } - } - - if (buyerPlayer->isOffline()) { - IOLoginData::savePlayer(buyerPlayer); - delete buyerPlayer; - } else { - buyerPlayer->onReceiveMail(); - } - } else { - if (totalPrice > player->bankBalance) { - return; - } - - player->bankBalance -= totalPrice; - - if (it.stackable) { - uint16_t tmpAmount = amount; - while (tmpAmount > 0) { - uint16_t stackCount = std::min(100, tmpAmount); - Item* item = Item::CreateItem(it.id, stackCount); - if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { - delete item; - break; - } - - tmpAmount -= stackCount; - } - } else { - int32_t subType; - if (it.charges != 0) { - subType = it.charges; - } else { - subType = -1; - } - - for (uint16_t i = 0; i < amount; ++i) { - Item* item = Item::CreateItem(it.id, subType); - if (internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { - delete item; - break; - } - } - } - - Player* sellerPlayer = getPlayerByGUID(offer.playerId); - if (sellerPlayer) { - sellerPlayer->bankBalance += totalPrice; - } else { - IOLoginData::increaseBankBalance(offer.playerId, totalPrice); - } - - player->onReceiveMail(); - } - - const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); - - IOMarket::appendHistory(player->getGUID(), (offer.type == MARKETACTION_BUY ? MARKETACTION_SELL : MARKETACTION_BUY), offer.itemId, amount, offer.price, offer.timestamp + marketOfferDuration, OFFERSTATE_ACCEPTEDEX); - - IOMarket::appendHistory(offer.playerId, offer.type, offer.itemId, amount, offer.price, offer.timestamp + marketOfferDuration, OFFERSTATE_ACCEPTED); - - offer.amount -= amount; - - if (offer.amount == 0) { - IOMarket::deleteOffer(offer.id); - } else { - IOMarket::acceptOffer(offer.id, amount); - } - - player->sendMarketEnter(player->getLastDepotId()); - offer.timestamp += marketOfferDuration; - player->sendMarketAcceptOffer(offer); -} - void Game::parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const std::string& buffer) { Player* player = getPlayerByID(playerId); @@ -5446,45 +4410,52 @@ void Game::parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const st } } -std::forward_list Game::getMarketItemList(uint16_t wareId, uint16_t sufficientCount, DepotChest* depotChest, Inbox* inbox) +void Game::closeRuleViolationReport(Player* player) { - std::forward_list itemList; - uint16_t count = 0; + const auto it = ruleViolations.find(player->getID()); + if (it == ruleViolations.end()) { + return; + } - std::list containers { depotChest, inbox }; - do { - Container* container = containers.front(); - containers.pop_front(); + ruleViolations.erase(it); + player->sendLockRuleViolationReport(); - for (Item* item : container->getItemList()) { - Container* c = item->getContainer(); - if (c && !c->empty()) { - containers.push_back(c); - continue; - } - - const ItemType& itemType = Item::items[item->getID()]; - if (itemType.wareId != wareId) { - continue; - } - - if (c && (!itemType.isContainer() || c->capacity() != itemType.maxItems)) { - continue; - } - - if (!item->hasMarketAttributes()) { - continue; - } - - itemList.push_front(item); - - count += Item::countByType(item, -1); - if (count >= sufficientCount) { - return itemList; + ChatChannel* channel = g_chat->getChannelById(CHANNEL_RULE_REP); + if (channel) { + for (UsersMap::const_iterator ut = channel->getUsers().begin(); ut != channel->getUsers().end(); ++ut) { + if (ut->second) { + ut->second->sendRemoveRuleViolationReport(player->getName()); } } - } while (!containers.empty()); - return std::forward_list(); + } +} + +void Game::cancelRuleViolationReport(Player* player) +{ + const auto it = ruleViolations.find(player->getID()); + if (it == ruleViolations.end()) { + return; + } + + RuleViolation& ruleViolation = it->second; + Player* gamemaster = getPlayerByID(ruleViolation.gamemasterId); + if (!ruleViolation.pending && gamemaster) { + // Send to the responder + gamemaster->sendRuleViolationCancel(player->getName()); + } + + // Send to channel + ChatChannel* channel = g_chat->getChannelById(CHANNEL_RULE_REP); + if (channel) { + for (UsersMap::const_iterator ut = channel->getUsers().begin(); ut != channel->getUsers().end(); ++ut) { + if (ut->second) { + ut->second->sendRemoveRuleViolationReport(player->getName()); + } + } + } + + // Erase it + ruleViolations.erase(it); } void Game::forceAddCondition(uint32_t creatureId, Condition* condition) @@ -5508,52 +4479,6 @@ void Game::forceRemoveCondition(uint32_t creatureId, ConditionType_t type) creature->removeCondition(type, true); } -void Game::sendOfflineTrainingDialog(Player* player) -{ - if (!player) { - return; - } - - if (!player->hasModalWindowOpen(offlineTrainingWindow.id)) { - player->sendModalWindow(offlineTrainingWindow); - } -} - -void Game::playerAnswerModalWindow(uint32_t playerId, uint32_t modalWindowId, uint8_t button, uint8_t choice) -{ - Player* player = getPlayerByID(playerId); - if (!player) { - return; - } - - if (!player->hasModalWindowOpen(modalWindowId)) { - return; - } - - player->onModalWindowHandled(modalWindowId); - - // offline training, hardcoded - if (modalWindowId == std::numeric_limits::max()) { - if (button == 1) { - if (choice == SKILL_SWORD || choice == SKILL_AXE || choice == SKILL_CLUB || choice == SKILL_DISTANCE || choice == SKILL_MAGLEVEL) { - BedItem* bedItem = player->getBedItem(); - if (bedItem && bedItem->sleep(player)) { - player->setOfflineTrainingSkill(choice); - return; - } - } - } else { - player->sendTextMessage(MESSAGE_EVENT_ADVANCE, "Offline training aborted."); - } - - player->setBedItem(nullptr); - } else { - for (auto creatureEvent : player->getCreatureEvents(CREATURE_EVENT_MODALWINDOW)) { - creatureEvent->executeModalWindow(player, modalWindowId, button, choice); - } - } -} - void Game::addPlayer(Player* player) { const std::string& lowercase_name = asLowerCaseString(player->getName()); @@ -5609,19 +4534,6 @@ void Game::removeGuild(uint32_t guildId) guilds.erase(guildId); } -void Game::decreaseBrowseFieldRef(const Position& pos) -{ - Tile* tile = map.getTile(pos.x, pos.y, pos.z); - if (!tile) { - return; - } - - auto it = browseFields.find(tile); - if (it != browseFields.end()) { - it->second->decrementReferenceCounter(); - } -} - void Game::internalRemoveItems(std::vector itemList, uint32_t amount, bool stackable) { if (stackable) { @@ -5663,132 +4575,63 @@ void Game::removeBedSleeper(uint32_t guid) } } -Item* Game::getUniqueItem(uint16_t uniqueId) -{ - auto it = uniqueItems.find(uniqueId); - if (it == uniqueItems.end()) { - return nullptr; - } - return it->second; -} - -bool Game::addUniqueItem(uint16_t uniqueId, Item* item) -{ - auto result = uniqueItems.emplace(uniqueId, item); - if (!result.second) { - std::cout << "Duplicate unique id: " << uniqueId << std::endl; - } - return result.second; -} - -void Game::removeUniqueItem(uint16_t uniqueId) -{ - auto it = uniqueItems.find(uniqueId); - if (it != uniqueItems.end()) { - uniqueItems.erase(it); - } -} - bool Game::reload(ReloadTypes_t reloadType) { switch (reloadType) { - case RELOAD_TYPE_ACTIONS: return g_actions->reload(); - case RELOAD_TYPE_CHAT: return g_chat->load(); - case RELOAD_TYPE_CONFIG: return g_config.reload(); - case RELOAD_TYPE_CREATURESCRIPTS: return g_creatureEvents->reload(); - case RELOAD_TYPE_EVENTS: return g_events->load(); - case RELOAD_TYPE_GLOBALEVENTS: return g_globalEvents->reload(); - case RELOAD_TYPE_ITEMS: return Item::items.reload(); - case RELOAD_TYPE_MONSTERS: return g_monsters.reload(); - case RELOAD_TYPE_MOUNTS: return mounts.reload(); - case RELOAD_TYPE_MOVEMENTS: return g_moveEvents->reload(); - case RELOAD_TYPE_NPCS: { - Npcs::reload(); - return true; - } - - case RELOAD_TYPE_QUESTS: return quests.reload(); - case RELOAD_TYPE_RAIDS: return raids.reload() && raids.startup(); - - case RELOAD_TYPE_SPELLS: { - if (!g_spells->reload()) { - std::cout << "[Error - Game::reload] Failed to reload spells." << std::endl; - std::terminate(); - } else if (!g_monsters.reload()) { - std::cout << "[Error - Game::reload] Failed to reload monsters." << std::endl; - std::terminate(); - } - return true; - } - - case RELOAD_TYPE_TALKACTIONS: return g_talkActions->reload(); - - case RELOAD_TYPE_WEAPONS: { - bool results = g_weapons->reload(); - g_weapons->loadDefaults(); - return results; - } - - case RELOAD_TYPE_SCRIPTS: { - // commented out stuff is TODO, once we approach further in revscriptsys - g_actions->clear(true); - g_creatureEvents->clear(true); - g_moveEvents->clear(true); - g_talkActions->clear(true); - g_globalEvents->clear(true); - g_weapons->clear(true); - g_weapons->loadDefaults(); - g_spells->clear(true); - g_scripts->loadScripts("scripts", false, true); - /* - Npcs::reload(); - raids.reload() && raids.startup(); - Item::items.reload(); - quests.reload(); - mounts.reload(); - g_config.reload(); - g_events->load(); - g_chat->load(); - */ - return true; - } - - default: { - if (!g_spells->reload()) { - std::cout << "[Error - Game::reload] Failed to reload spells." << std::endl; - std::terminate(); - } else if (!g_monsters.reload()) { - std::cout << "[Error - Game::reload] Failed to reload monsters." << std::endl; - std::terminate(); - } - - g_actions->reload(); - g_config.reload(); - g_creatureEvents->reload(); - g_monsters.reload(); - g_moveEvents->reload(); - Npcs::reload(); - raids.reload() && raids.startup(); - g_talkActions->reload(); - Item::items.reload(); - g_weapons->reload(); - g_weapons->clear(true); - g_weapons->loadDefaults(); - quests.reload(); - mounts.reload(); - g_globalEvents->reload(); - g_events->load(); - g_chat->load(); - g_actions->clear(true); - g_creatureEvents->clear(true); - g_moveEvents->clear(true); - g_talkActions->clear(true); - g_globalEvents->clear(true); - g_spells->clear(true); - g_scripts->loadScripts("scripts", false, true); - return true; - } + case RELOAD_TYPE_ACTIONS: return g_actions->reload(); + case RELOAD_TYPE_CHAT: return g_chat->load(); + case RELOAD_TYPE_CONFIG: return g_config.reload(); + case RELOAD_TYPE_CREATURESCRIPTS: return g_creatureEvents->reload(); + case RELOAD_TYPE_EVENTS: return g_events->load(); + case RELOAD_TYPE_GLOBALEVENTS: return g_globalEvents->reload(); + case RELOAD_TYPE_ITEMS: return Item::items.reload(); + case RELOAD_TYPE_MONSTERS: return g_monsters.reload(); + case RELOAD_TYPE_MOVEMENTS: return g_moveEvents->reload(); + case RELOAD_TYPE_NPCS: { + Npcs::reload(); + return true; } - return true; -} + case RELOAD_TYPE_RAIDS: return raids.reload() && raids.startup(); + case RELOAD_TYPE_SPELLS: { + if (!g_spells->reload()) { + std::cout << "[Error - Game::reload] Failed to reload spells." << std::endl; + std::terminate(); + } + else if (!g_monsters.reload()) { + std::cout << "[Error - Game::reload] Failed to reload monsters." << std::endl; + std::terminate(); + } + return true; + } + + case RELOAD_TYPE_TALKACTIONS: return g_talkActions->reload(); + + default: { + if (!g_spells->reload()) { + std::cout << "[Error - Game::reload] Failed to reload spells." << std::endl; + std::terminate(); + return false; + } + else if (!g_monsters.reload()) { + std::cout << "[Error - Game::reload] Failed to reload monsters." << std::endl; + std::terminate(); + return false; + } + + g_actions->reload(); + g_config.reload(); + g_creatureEvents->reload(); + g_monsters.reload(); + g_moveEvents->reload(); + Npcs::reload(); + raids.reload() && raids.startup(); + g_talkActions->reload(); + Item::items.reload(); + g_globalEvents->reload(); + g_events->load(); + g_chat->load(); + return true; + } + } +} diff --git a/src/game.h b/src/game.h index 83d5e55..633b6a7 100644 --- a/src/game.h +++ b/src/game.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,7 +31,6 @@ #include "raids.h" #include "npc.h" #include "wildcardtree.h" -#include "quests.h" class ServiceManager; class Creature; @@ -70,6 +69,22 @@ enum LightState_t { 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; @@ -82,7 +97,7 @@ static constexpr int32_t EVENT_DECAY_BUCKETS = 4; class Game { public: - Game(); + Game() = default; ~Game(); // non-copyable @@ -204,7 +219,7 @@ class Game * \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 forced = false); + bool placeCreature(Creature* creature, const Position& pos, bool extendedPos = false, bool force = false); /** * Remove Creature from the map. @@ -229,7 +244,7 @@ class Game return playersRecord; } - LightInfo getWorldLightInfo() const; + 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); @@ -307,19 +322,16 @@ class Game * \param text The text to say */ bool internalCreatureSay(Creature* creature, SpeakClasses type, const std::string& text, - bool ghostMode, SpectatorVec* spectatorsPtr = nullptr, const Position* pos = nullptr); + bool ghostMode, SpectatorVec* listPtr = nullptr, const Position* pos = nullptr); void loadPlayersRecord(); void checkPlayersRecord(); - void sendGuildMotd(uint32_t playerId); void kickPlayer(uint32_t playerId, bool displayEffect); - void playerReportBug(uint32_t playerId, const std::string& message, const Position& position, uint8_t category); + 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); - void playerAnswerModalWindow(uint32_t playerId, uint32_t modalWindowId, uint8_t button, uint8_t choice); - void playerReportRuleViolation(uint32_t playerId, const std::string& targetName, uint8_t reportType, uint8_t reportReason, const std::string& comment, const std::string& translation); - bool internalStartTrade(Player* player, Player* tradePartner, Item* tradeItem); + 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; @@ -328,11 +340,10 @@ class Game 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* player, Creature* movingCreature, const Position& movingCreatureOrigPos, Tile* toTile); + 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 playerEquipItem(uint32_t playerId, uint16_t spriteId); void playerMove(uint32_t playerId, Direction direction); void playerCreatePrivateChannel(uint32_t playerId); void playerChannelInvite(uint32_t playerId, const std::string& name); @@ -341,7 +352,6 @@ class Game 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 playerCloseNpcChannel(uint32_t playerId); void playerReceivePing(uint32_t playerId); void playerReceivePingBack(uint32_t playerId); void playerAutoWalk(uint32_t playerId, const std::forward_list& listDir); @@ -355,33 +365,23 @@ class Game 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 playerBrowseField(uint32_t playerId, const Position& pos); 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 playerPurchaseItem(uint32_t playerId, uint16_t spriteId, uint8_t count, uint8_t amount, - bool ignoreCap = false, bool inBackpacks = false); - void playerSellItem(uint32_t playerId, uint16_t spriteId, uint8_t count, - uint8_t amount, bool ignoreEquipped = false); - void playerCloseShop(uint32_t playerId); - void playerLookInShop(uint32_t playerId, uint16_t spriteId, uint8_t count); 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, bool chaseMode, bool secureMode); + 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 playerRequestEditVip(uint32_t playerId, uint32_t guid, const std::string& description, uint32_t icon, bool notify); void playerTurn(uint32_t playerId, Direction dir); void playerRequestOutfit(uint32_t playerId); - void playerShowQuestLog(uint32_t playerId); - void playerShowQuestLine(uint32_t playerId, uint16_t questId); 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); @@ -391,18 +391,15 @@ class Game void playerPassPartyLeadership(uint32_t playerId, uint32_t newLeaderId); void playerLeaveParty(uint32_t playerId); void playerEnableSharedPartyExperience(uint32_t playerId, bool sharedExpActive); - void playerToggleMount(uint32_t playerId, bool mount); - void playerLeaveMarket(uint32_t playerId); - void playerBrowseMarket(uint32_t playerId, uint16_t spriteId); - void playerBrowseMarketOwnOffers(uint32_t playerId); - void playerBrowseMarketOwnHistory(uint32_t playerId); - void playerCreateMarketOffer(uint32_t playerId, uint8_t type, uint16_t spriteId, uint16_t amount, uint32_t price, bool anonymous); - void playerCancelMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter); - void playerAcceptMarketOffer(uint32_t playerId, uint32_t timestamp, uint16_t counter, uint16_t amount); - + 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); - std::forward_list getMarketItemList(uint16_t wareId, uint16_t sufficientCount, DepotChest* depotChest, Inbox* inbox); + void closeRuleViolationReport(Player* player); + void cancelRuleViolationReport(Player* player); static void updatePremium(Account& account); @@ -413,17 +410,14 @@ class Game 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 floorCheck) 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& outfit); + void internalCreatureChangeOutfit(Creature* creature, const Outfit_t& oufit); void internalCreatureChangeVisible(Creature* creature, bool visible); void changeLight(const Creature* creature); - void updateCreatureSkull(const Creature* creature); + void updateCreatureSkull(const Creature* player); void updatePlayerShield(Player* player); - void updatePlayerHelpers(const Player& player); - void updateCreatureType(Creature* creature); - void updateCreatureWalkthrough(const Creature* creature); GameState_t getGameState() const; void setGameState(GameState_t newState); @@ -445,11 +439,14 @@ class Game //animation help functions void addCreatureHealth(const Creature* target); - static void addCreatureHealth(const SpectatorVec& spectators, 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& spectators, 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& spectators, 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 startDecay(Item* item); int32_t getLightHour() const { @@ -465,8 +462,7 @@ class Game uint32_t getMotdNum() const { return motdNum; } void incrementMotdNum() { motdNum++; } - void sendOfflineTrainingDialog(Player* player); - + const std::unordered_map& getRuleViolationReports() const { return ruleViolations; } const std::unordered_map& getPlayers() const { return players; } const std::map& getNpcs() const { return npcs; } @@ -476,55 +472,45 @@ class Game void addNpc(Npc* npc); void removeNpc(Npc* npc); - void addMonster(Monster* monster); - void removeMonster(Monster* monster); + void addMonster(Monster* npc); + void removeMonster(Monster* npc); Guild* getGuild(uint32_t id) const; void addGuild(Guild* guild); void removeGuild(uint32_t guildId); - void decreaseBrowseFieldRef(const Position& pos); - - std::unordered_map browseFields; void internalRemoveItems(std::vector itemList, uint32_t amount, bool stackable); BedItem* getBedBySleeper(uint32_t guid) const; void setBedSleeper(BedItem* bed, uint32_t guid); void removeBedSleeper(uint32_t guid); - - Item* getUniqueItem(uint16_t uniqueId); - bool addUniqueItem(uint16_t uniqueId, Item* item); - void removeUniqueItem(uint16_t uniqueId); - bool reload(ReloadTypes_t reloadType); - Groups groups; Map map; - Mounts mounts; Raids raids; - Quests quests; - std::forward_list toDecayItems; - - private: + protected: 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 playerSpeakToNpc(Player* player, const std::string& text); void checkDecay(); void internalDecayItem(Item* item); + //list of reported rule violations, for correct channel listing + std::unordered_map ruleViolations; + std::unordered_map players; std::unordered_map mappedPlayerNames; std::unordered_map guilds; - std::unordered_map uniqueItems; std::map stages; std::list decayItems[EVENT_DECAY_BUCKETS]; std::list checkCreatureLists[EVENT_CREATURECOUNT]; + std::forward_list toDecayItems; + std::vector ToReleaseCreatures; std::vector ToReleaseItems; @@ -540,8 +526,6 @@ class Game std::map bedSleepersMap; - ModalWindow offlineTrainingWindow { std::numeric_limits::max(), "Choose a Skill", "Please choose a skill:" }; - static constexpr int32_t LIGHT_LEVEL_DAY = 250; static constexpr int32_t LIGHT_LEVEL_NIGHT = 40; static constexpr int32_t SUNSET = 1305; diff --git a/src/globalevent.cpp b/src/globalevent.cpp index daa233e..96c9d71 100644 --- a/src/globalevent.cpp +++ b/src/globalevent.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,47 +35,44 @@ GlobalEvents::GlobalEvents() : GlobalEvents::~GlobalEvents() { - clear(false); + clear(); } -void GlobalEvents::clearMap(GlobalEventMap& map, bool fromLua) +void GlobalEvents::clearMap(GlobalEventMap& map) { - for (auto it = map.begin(); it != map.end(); ) { - if (fromLua == it->second.fromLua) { - it = map.erase(it); - } else { - ++it; - } + for (const auto& it : map) { + delete it.second; } + map.clear(); } -void GlobalEvents::clear(bool fromLua) +void GlobalEvents::clear() { g_scheduler.stopEvent(thinkEventId); thinkEventId = 0; g_scheduler.stopEvent(timerEventId); timerEventId = 0; - clearMap(thinkMap, fromLua); - clearMap(serverMap, fromLua); - clearMap(timerMap, fromLua); + clearMap(thinkMap); + clearMap(serverMap); + clearMap(timerMap); - reInitState(fromLua); + scriptInterface.reInitState(); } -Event_ptr GlobalEvents::getEvent(const std::string& nodeName) +Event* GlobalEvents::getEvent(const std::string& nodeName) { if (strcasecmp(nodeName.c_str(), "globalevent") != 0) { return nullptr; } - return Event_ptr(new GlobalEvent(&scriptInterface)); + return new GlobalEvent(&scriptInterface); } -bool GlobalEvents::registerEvent(Event_ptr event, const pugi::xml_node&) +bool GlobalEvents::registerEvent(Event* event, const pugi::xml_node&) { - GlobalEvent_ptr globalEvent{static_cast(event.release())}; //event is guaranteed to be a GlobalEvent + GlobalEvent* globalEvent = static_cast(event); //event is guaranteed to be a GlobalEvent if (globalEvent->getEventType() == GLOBALEVENT_TIMER) { - auto result = timerMap.emplace(globalEvent->getName(), std::move(*globalEvent)); + 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))); @@ -83,42 +80,12 @@ bool GlobalEvents::registerEvent(Event_ptr event, const pugi::xml_node&) return true; } } else if (globalEvent->getEventType() != GLOBALEVENT_NONE) { - auto result = serverMap.emplace(globalEvent->getName(), std::move(*globalEvent)); + auto result = serverMap.emplace(globalEvent->getName(), globalEvent); if (result.second) { return true; } } else { // think event - auto result = thinkMap.emplace(globalEvent->getName(), std::move(*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; -} - -bool GlobalEvents::registerLuaEvent(GlobalEvent* event) -{ - GlobalEvent_ptr globalEvent{ event }; - if (globalEvent->getEventType() == GLOBALEVENT_TIMER) { - auto result = timerMap.emplace(globalEvent->getName(), std::move(*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(), std::move(*globalEvent)); - if (result.second) { - return true; - } - } else { // think event - auto result = thinkMap.emplace(globalEvent->getName(), std::move(*globalEvent)); + 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))); @@ -144,9 +111,9 @@ void GlobalEvents::timer() auto it = timerMap.begin(); while (it != timerMap.end()) { - GlobalEvent& globalEvent = it->second; + GlobalEvent* globalEvent = it->second; - int64_t nextExecutionTime = globalEvent.getNextExecution() - now; + int64_t nextExecutionTime = globalEvent->getNextExecution() - now; if (nextExecutionTime > 0) { if (nextExecutionTime < nextScheduledTime) { nextScheduledTime = nextExecutionTime; @@ -156,7 +123,7 @@ void GlobalEvents::timer() continue; } - if (!globalEvent.executeEvent()) { + if (!globalEvent->executeEvent()) { it = timerMap.erase(it); continue; } @@ -166,7 +133,7 @@ void GlobalEvents::timer() nextScheduledTime = nextExecutionTime; } - globalEvent.setNextExecution(globalEvent.getNextExecution() + nextExecutionTime); + globalEvent->setNextExecution(globalEvent->getNextExecution() + nextExecutionTime); ++it; } @@ -182,10 +149,10 @@ void GlobalEvents::think() int64_t now = OTSYS_TIME(); int64_t nextScheduledTime = std::numeric_limits::max(); - for (auto& it : thinkMap) { - GlobalEvent& globalEvent = it.second; + for (const auto& it : thinkMap) { + GlobalEvent* globalEvent = it.second; - int64_t nextExecutionTime = globalEvent.getNextExecution() - now; + int64_t nextExecutionTime = globalEvent->getNextExecution() - now; if (nextExecutionTime > 0) { if (nextExecutionTime < nextScheduledTime) { nextScheduledTime = nextExecutionTime; @@ -193,16 +160,16 @@ void GlobalEvents::think() continue; } - if (!globalEvent.executeEvent()) { - std::cout << "[Error - GlobalEvents::think] Failed to execute event: " << globalEvent.getName() << std::endl; + if (!globalEvent->executeEvent()) { + std::cout << "[Error - GlobalEvents::think] Failed to execute event: " << globalEvent->getName() << std::endl; } - nextExecutionTime = globalEvent.getInterval(); + nextExecutionTime = globalEvent->getInterval(); if (nextExecutionTime < nextScheduledTime) { nextScheduledTime = nextExecutionTime; } - globalEvent.setNextExecution(globalEvent.getNextExecution() + nextExecutionTime); + globalEvent->setNextExecution(globalEvent->getNextExecution() + nextExecutionTime); } if (nextScheduledTime != std::numeric_limits::max()) { @@ -213,16 +180,15 @@ void GlobalEvents::think() void GlobalEvents::execute(GlobalEvent_t type) const { for (const auto& it : serverMap) { - const GlobalEvent& globalEvent = it.second; - if (globalEvent.getEventType() == type) { - globalEvent.executeEvent(); + GlobalEvent* globalEvent = it.second; + if (globalEvent->getEventType() == type) { + globalEvent->executeEvent(); } } } GlobalEventMap GlobalEvents::getEventMap(GlobalEvent_t type) { - // TODO: This should be better implemented. Maybe have a map for every type. switch (type) { case GLOBALEVENT_NONE: return thinkMap; case GLOBALEVENT_TIMER: return timerMap; @@ -231,8 +197,8 @@ GlobalEventMap GlobalEvents::getEventMap(GlobalEvent_t type) case GLOBALEVENT_RECORD: { GlobalEventMap retMap; for (const auto& it : serverMap) { - if (it.second.getEventType() == type) { - retMap.emplace(it.first, it.second); + if (it.second->getEventType() == type) { + retMap[it.first] = it.second; } } return retMap; @@ -349,7 +315,7 @@ bool GlobalEvent::executeRecord(uint32_t current, uint32_t old) return scriptInterface->callFunction(2); } -bool GlobalEvent::executeEvent() const +bool GlobalEvent::executeEvent() { if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - GlobalEvent::executeEvent] Call stack overflow" << std::endl; diff --git a/src/globalevent.h b/src/globalevent.h index 6c65482..b4ba1cb 100644 --- a/src/globalevent.h +++ b/src/globalevent.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,8 +33,7 @@ enum GlobalEvent_t { }; class GlobalEvent; -using GlobalEvent_ptr = std::unique_ptr; -using GlobalEventMap = std::map; +typedef std::map GlobalEventMap; class GlobalEvents final : public BaseEvents { @@ -53,20 +52,18 @@ class GlobalEvents final : public BaseEvents void execute(GlobalEvent_t type) const; GlobalEventMap getEventMap(GlobalEvent_t type); - static void clearMap(GlobalEventMap& map, bool fromLua); + static void clearMap(GlobalEventMap& map); - bool registerLuaEvent(GlobalEvent* event); - void clear(bool fromLua) override final; - - private: - std::string getScriptBaseName() const override { + protected: + std::string getScriptBaseName() const final { return "globalevents"; } + void clear() final; - Event_ptr getEvent(const std::string& nodeName) override; - bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; + Event* getEvent(const std::string& nodeName) final; + bool registerEvent(Event* event, const pugi::xml_node& node) final; - LuaScriptInterface& getScriptInterface() override { + LuaScriptInterface& getScriptInterface() final { return scriptInterface; } LuaScriptInterface scriptInterface; @@ -80,31 +77,22 @@ class GlobalEvent final : public Event public: explicit GlobalEvent(LuaScriptInterface* interface); - bool configureEvent(const pugi::xml_node& node) override; + bool configureEvent(const pugi::xml_node& node) final; bool executeRecord(uint32_t current, uint32_t old); - bool executeEvent() const; + bool executeEvent(); GlobalEvent_t getEventType() const { return eventType; } - void setEventType(GlobalEvent_t type) { - eventType = type; - } const std::string& getName() const { return name; } - void setName(std::string eventName) { - name = eventName; - } uint32_t getInterval() const { return interval; } - void setInterval(uint32_t eventInterval) { - interval |= eventInterval; - } int64_t getNextExecution() const { return nextExecution; @@ -113,10 +101,10 @@ class GlobalEvent final : public Event nextExecution = time; } - private: + protected: GlobalEvent_t eventType = GLOBALEVENT_NONE; - std::string getScriptEventName() const override; + std::string getScriptEventName() const final; std::string name; int64_t nextExecution = 0; diff --git a/src/groups.cpp b/src/groups.cpp index 2908bd0..8b4460d 100644 --- a/src/groups.cpp +++ b/src/groups.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,47 +24,6 @@ #include "pugicast.h" #include "tools.h" -const std::unordered_map ParsePlayerFlagMap = { - {"cannotusecombat", PlayerFlag_CannotUseCombat}, - {"cannotattackplayer", PlayerFlag_CannotAttackPlayer}, - {"cannotattackmonster", PlayerFlag_CannotAttackMonster}, - {"cannotbeattacked", PlayerFlag_CannotBeAttacked}, - {"canconvinceall", PlayerFlag_CanConvinceAll}, - {"cansummonall", PlayerFlag_CanSummonAll}, - {"canillusionall", PlayerFlag_CanIllusionAll}, - {"cansenseinvisibility", PlayerFlag_CanSenseInvisibility}, - {"ignoredbymonsters", PlayerFlag_IgnoredByMonsters}, - {"notgaininfight", PlayerFlag_NotGainInFight}, - {"hasinfinitemana", PlayerFlag_HasInfiniteMana}, - {"hasinfinitesoul", PlayerFlag_HasInfiniteSoul}, - {"hasnoexhaustion", PlayerFlag_HasNoExhaustion}, - {"cannotusespells", PlayerFlag_CannotUseSpells}, - {"cannotpickupitem", PlayerFlag_CannotPickupItem}, - {"canalwayslogin", PlayerFlag_CanAlwaysLogin}, - {"canbroadcast", PlayerFlag_CanBroadcast}, - {"canedithouses", PlayerFlag_CanEditHouses}, - {"cannotbebanned", PlayerFlag_CannotBeBanned}, - {"cannotbepushed", PlayerFlag_CannotBePushed}, - {"hasinfinitecapacity", PlayerFlag_HasInfiniteCapacity}, - {"cannotpushallcreatures", PlayerFlag_CanPushAllCreatures}, - {"cantalkredprivate", PlayerFlag_CanTalkRedPrivate}, - {"cantalkredchannel", PlayerFlag_CanTalkRedChannel}, - {"talkorangehelpchannel", PlayerFlag_TalkOrangeHelpChannel}, - {"notgainexperience", PlayerFlag_NotGainExperience}, - {"notgainmana", PlayerFlag_NotGainMana}, - {"notgainhealth", PlayerFlag_NotGainHealth}, - {"notgainskill", PlayerFlag_NotGainSkill}, - {"setmaxspeed", PlayerFlag_SetMaxSpeed}, - {"specialvip", PlayerFlag_SpecialVIP}, - {"notgenerateloot", PlayerFlag_NotGenerateLoot}, - {"cantalkredchannelanonymous", PlayerFlag_CanTalkRedChannelAnonymous}, - {"ignoreprotectionzone", PlayerFlag_IgnoreProtectionZone}, - {"ignorespellcheck", PlayerFlag_IgnoreSpellCheck}, - {"ignoreweaponcheck", PlayerFlag_IgnoreWeaponCheck}, - {"cannotbemuted", PlayerFlag_CannotBeMuted}, - {"isalwayspremium", PlayerFlag_IsAlwaysPremium} -}; - bool Groups::load() { pugi::xml_document doc; @@ -78,24 +37,10 @@ bool Groups::load() Group group; group.id = pugi::cast(groupNode.attribute("id").value()); group.name = groupNode.attribute("name").as_string(); + group.flags = pugi::cast(groupNode.attribute("flags").value()); group.access = groupNode.attribute("access").as_bool(); group.maxDepotItems = pugi::cast(groupNode.attribute("maxdepotitems").value()); group.maxVipEntries = pugi::cast(groupNode.attribute("maxvipentries").value()); - group.flags = pugi::cast(groupNode.attribute("flags").value()); - if (pugi::xml_node node = groupNode.child("flags")) { - for (auto flagNode : node.children()) { - pugi::xml_attribute attr = flagNode.first_attribute(); - if (!attr || (attr && !attr.as_bool())) { - continue; - } - - auto parseFlag = ParsePlayerFlagMap.find(attr.name()); - if (parseFlag != ParsePlayerFlagMap.end()) { - group.flags |= parseFlag->second; - } - } - } - groups.push_back(group); } return true; diff --git a/src/groups.h b/src/groups.h index 3297ed5..e16adfb 100644 --- a/src/groups.h +++ b/src/groups.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/guild.cpp b/src/guild.cpp index 6e27c57..e6bed21 100644 --- a/src/guild.cpp +++ b/src/guild.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,18 +28,11 @@ extern Game g_game; void Guild::addMember(Player* player) { membersOnline.push_back(player); - for (Player* member : membersOnline) { - g_game.updatePlayerHelpers(*member); - } } void Guild::removeMember(Player* player) { membersOnline.remove(player); - for (Player* member : membersOnline) { - g_game.updatePlayerHelpers(*member); - } - g_game.updatePlayerHelpers(*player); if (membersOnline.empty()) { g_game.removeGuild(id); @@ -57,16 +50,6 @@ GuildRank* Guild::getRankById(uint32_t rankId) return nullptr; } -const GuildRank* Guild::getRankByName(const std::string& name) const -{ - for (const auto& rank : ranks) { - if (rank.name == name) { - return &rank; - } - } - return nullptr; -} - const GuildRank* Guild::getRankByLevel(uint8_t level) const { for (const auto& rank : ranks) { diff --git a/src/guild.h b/src/guild.h index e0a6f12..3085ec9 100644 --- a/src/guild.h +++ b/src/guild.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -55,26 +55,14 @@ class Guild memberCount = count; } - const std::vector& getRanks() const { - return ranks; - } - GuildRank* getRankById(uint32_t rankId); - const GuildRank* getRankByName(const std::string& name) const; + GuildRank* getRankById(uint32_t id); const GuildRank* getRankByLevel(uint8_t level) const; - void addRank(uint32_t rankId, const std::string& rankName, uint8_t level); - - const std::string& getMotd() const { - return motd; - } - void setMotd(const std::string& motd) { - this->motd = motd; - } + void addRank(uint32_t id, const std::string& name, uint8_t level); private: std::list membersOnline; std::vector ranks; std::string name; - std::string motd; uint32_t id; uint32_t memberCount = 0; }; diff --git a/src/house.cpp b/src/house.cpp index e37befb..29f375c 100644 --- a/src/house.cpp +++ b/src/house.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -41,11 +41,11 @@ void House::addTile(HouseTile* tile) void House::setOwner(uint32_t guid, bool updateDatabase/* = true*/, Player* player/* = nullptr*/) { if (updateDatabase && owner != guid) { - Database& db = Database::getInstance(); + 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()); + db->executeQuery(query.str()); } if (isLoaded && owner == guid) { @@ -85,26 +85,12 @@ void House::setOwner(uint32_t guid, bool updateDatabase/* = true*/, Player* play for (Door* door : doorSet) { door->setAccessList(""); } - } else { - std::string strRentPeriod = asLowerCaseString(g_config.getString(ConfigManager::HOUSE_RENT_PERIOD)); - time_t currentTime = time(nullptr); - if (strRentPeriod == "yearly") { - currentTime += 24 * 60 * 60 * 365; - } else if (strRentPeriod == "monthly") { - currentTime += 24 * 60 * 60 * 30; - } else if (strRentPeriod == "weekly") { - currentTime += 24 * 60 * 60 * 7; - } else if (strRentPeriod == "daily") { - currentTime += 24 * 60 * 60; - } else { - currentTime = 0; - } - paidUntil = currentTime; + //reset paid date + paidUntil = 0; + rentWarnings = 0; } - rentWarnings = 0; - if (guid != 0) { std::string name = IOLoginData::getNameByGuid(guid); if (!name.empty()) { @@ -124,9 +110,9 @@ void House::updateDoorDescription() const } else { ss << "It belongs to house '" << houseName << "'. Nobody owns this house."; - const int32_t housePrice = g_config.getNumber(ConfigManager::HOUSE_PRICE); + const int32_t housePrice = getRent(); if (housePrice != -1) { - ss << " It costs " << (houseTiles.size() * housePrice) << " gold coins."; + ss << " It costs " << housePrice * 5 << " gold coins."; } } @@ -230,6 +216,7 @@ bool House::transferToDepot() const transferToDepot(&tmpPlayer); IOLoginData::savePlayer(&tmpPlayer); } + return true; } @@ -258,8 +245,9 @@ bool House::transferToDepot(Player* player) const } for (Item* item : moveItemList) { - g_game.internalMoveItem(item->getParent(), player->getInbox(), INDEX_WHEREEVER, item, item->getItemCount(), nullptr, FLAG_NOLIMIT); + g_game.internalMoveItem(item->getParent(), player->getDepotLocker(getTownId(), true), INDEX_WHEREEVER, item, item->getItemCount(), nullptr, FLAG_NOLIMIT); } + return true; } @@ -408,7 +396,7 @@ bool House::executeTransfer(HouseTransferItem* item, Player* newOwner) void AccessList::parseList(const std::string& list) { playerList.clear(); - guildRankList.clear(); + guildList.clear(); expressionList.clear(); regExList.clear(); this->list = list; @@ -433,11 +421,7 @@ void AccessList::parseList(const std::string& list) std::string::size_type at_pos = line.find("@"); if (at_pos != std::string::npos) { - if (at_pos == 0) { - addGuild(line.substr(1)); - } else { - addGuildRank(line.substr(0, at_pos - 1), line.substr(at_pos + 1)); - } + 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 { @@ -459,43 +443,11 @@ void AccessList::addPlayer(const std::string& name) } } -namespace { - -const Guild* getGuildByName(const std::string& name) -{ - uint32_t guildId = IOGuild::getGuildIdByName(name); - if (guildId == 0) { - return nullptr; - } - - const Guild* guild = g_game.getGuild(guildId); - if (guild) { - return guild; - } - - return IOGuild::loadGuild(guildId); -} - -} - void AccessList::addGuild(const std::string& name) { - const Guild* guild = getGuildByName(name); - if (guild) { - for (const auto& rank : guild->getRanks()) { - guildRankList.insert(rank.id); - } - } -} - -void AccessList::addGuildRank(const std::string& name, const std::string& rankName) -{ - const Guild* guild = getGuildByName(name); - if (guild) { - const GuildRank* rank = guild->getRankByName(rankName); - if (rank) { - guildRankList.insert(rank->id); - } + uint32_t guildId = IOGuild::getGuildIdByName(name); + if (guildId != 0) { + guildList.insert(guildId); } } @@ -550,8 +502,8 @@ bool AccessList::isInList(const Player* player) return true; } - const GuildRank* rank = player->getGuildRank(); - return rank && guildRankList.find(rank->id) != guildRankList.end(); + const Guild* guild = player->getGuild(); + return guild && guildList.find(guild->getId()) != guildList.end(); } void AccessList::getList(std::string& list) const @@ -715,9 +667,7 @@ void Houses::payHouses(RentPeriod_t rentPeriod) const continue; } - if (player.getBankBalance() >= rent) { - player.setBankBalance(player.getBankBalance() - rent); - + if (g_game.removeMoney(player.getDepotLocker(house->getTownId(), true), house->getRent(), FLAG_NOLIMIT)) { time_t paidUntil = currentTime; switch (rentPeriod) { case RENTPERIOD_DAILY: @@ -768,7 +718,7 @@ void Houses::payHouses(RentPeriod_t rentPeriod) const 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.getInbox(), letter, INDEX_WHEREEVER, FLAG_NOLIMIT); + g_game.internalAddItem(player.getDepotLocker(house->getTownId(), true), letter, INDEX_WHEREEVER, FLAG_NOLIMIT); house->setPayRentWarnings(house->getPayRentWarnings() + 1); } else { house->setOwner(0, true, &player); diff --git a/src/house.h b/src/house.h index 386ef1f..3d9b543 100644 --- a/src/house.h +++ b/src/house.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,7 +22,6 @@ #include #include -#include #include "container.h" #include "housetile.h" @@ -38,7 +37,6 @@ class AccessList void parseList(const std::string& list); void addPlayer(const std::string& name); void addGuild(const std::string& name); - void addGuildRank(const std::string& name, const std::string& rankName); void addExpression(const std::string& expression); bool isInList(const Player* player); @@ -48,7 +46,7 @@ class AccessList private: std::string list; std::unordered_set playerList; - std::unordered_set guildRankList; + std::unordered_set guildList; // TODO: include ranks std::list expressionList; std::list> regExList; }; @@ -62,10 +60,10 @@ class Door final : public Item Door(const Door&) = delete; Door& operator=(const Door&) = delete; - Door* getDoor() override { + Door* getDoor() final { return this; } - const Door* getDoor() const override { + const Door* getDoor() const final { return this; } @@ -74,8 +72,8 @@ class Door final : public Item } //serialization - Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; - void serializeAttr(PropWriteStream&) const override {} + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) final; + void serializeAttr(PropWriteStream&) const final {} void setDoorId(uint32_t doorId) { setIntAttr(ITEM_ATTRIBUTE_DOORID, doorId); @@ -89,11 +87,12 @@ class Door final : public Item void setAccessList(const std::string& textlist); bool getAccessList(std::string& list) const; - void onRemoved() override; + void onRemoved() final; - private: + protected: void setHouse(House* house); + private: House* house = nullptr; std::unique_ptr accessList; friend class House; @@ -111,8 +110,8 @@ enum AccessHouseLevel_t { HOUSE_OWNER = 3, }; -using HouseTileList = std::list; -using HouseBedItemList = std::list; +typedef std::list HouseTileList; +typedef std::list HouseBedItemList; class HouseTransferItem final : public Item { @@ -121,12 +120,12 @@ class HouseTransferItem final : public Item explicit HouseTransferItem(House* house) : Item(0), house(house) {} - void onTradeEvent(TradeEvents_t event, Player* owner) override; - bool canTransform() const override { + void onTradeEvent(TradeEvents_t event, Player* owner) final; + bool canTransform() const final { return false; } - private: + protected: House* house; }; @@ -208,7 +207,7 @@ class House HouseTransferItem* getTransferItem(); void resetTransferItem(); - bool executeTransfer(HouseTransferItem* item, Player* newOwner); + bool executeTransfer(HouseTransferItem* item, Player* player); const HouseTileList& getTiles() const { return houseTiles; @@ -218,6 +217,7 @@ class House return doorSet; } + void addBed(BedItem* bed); const HouseBedItemList& getBeds() const { return bedsList; @@ -257,7 +257,7 @@ class House bool isLoaded = false; }; -using HouseMap = std::map; +typedef std::map HouseMap; enum RentPeriod_t { RENTPERIOD_DAILY, diff --git a/src/housetile.cpp b/src/housetile.cpp index 3cf8993..f3fccf5 100644 --- a/src/housetile.cpp +++ b/src/housetile.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/housetile.h b/src/housetile.h index fcb6d22..17ef634 100644 --- a/src/housetile.h +++ b/src/housetile.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,13 +31,13 @@ class HouseTile final : public DynamicTile //cylinder implementations ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const override; + uint32_t flags, Creature* actor = nullptr) const final; Tile* queryDestination(int32_t& index, const Thing& thing, Item** destItem, - uint32_t& flags) override; + uint32_t& flags) final; - void addThing(int32_t index, Thing* thing) override; - void internalAddThing(uint32_t index, Thing* thing) override; + void addThing(int32_t index, Thing* thing) final; + void internalAddThing(uint32_t index, Thing* thing) final; House* getHouse() { return house; diff --git a/src/inbox.cpp b/src/inbox.cpp deleted file mode 100644 index 42dff7d..0000000 --- a/src/inbox.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "otpch.h" - -#include "inbox.h" -#include "tools.h" - -Inbox::Inbox(uint16_t type) : Container(type, 30, false, true) {} - -ReturnValue Inbox::queryAdd(int32_t, const Thing& thing, uint32_t, - uint32_t flags, Creature*) const -{ - if (!hasBitSet(FLAG_NOLIMIT, flags)) { - return RETURNVALUE_CONTAINERNOTENOUGHROOM; - } - - const Item* item = thing.getItem(); - if (!item) { - return RETURNVALUE_NOTPOSSIBLE; - } - - if (item == this) { - return RETURNVALUE_THISISIMPOSSIBLE; - } - - if (!item->isPickupable()) { - return RETURNVALUE_CANNOTPICKUP; - } - - return RETURNVALUE_NOERROR; -} - -void Inbox::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t) -{ - Cylinder* parent = getParent(); - if (parent != nullptr) { - parent->postAddNotification(thing, oldParent, index, LINK_PARENT); - } -} - -void Inbox::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) -{ - Cylinder* parent = getParent(); - if (parent != nullptr) { - parent->postRemoveNotification(thing, newParent, index, LINK_PARENT); - } -} - -Cylinder* Inbox::getParent() const -{ - if (parent) { - return parent->getParent(); - } - return nullptr; -} diff --git a/src/inbox.h b/src/inbox.h deleted file mode 100644 index 8ad746a..0000000 --- a/src/inbox.h +++ /dev/null @@ -1,49 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_INBOX_H_C3EF10190329447883B9C3479234EE5C -#define FS_INBOX_H_C3EF10190329447883B9C3479234EE5C - -#include "container.h" - -class Inbox final : public Container -{ - public: - explicit Inbox(uint16_t type); - - //cylinder implementations - ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const override; - - 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; - - //overrides - bool canRemove() const override { - return false; - } - - Cylinder* getParent() const override; - Cylinder* getRealParent() const override { - return parent; - } -}; - -#endif - diff --git a/src/ioguild.cpp b/src/ioguild.cpp index ed48b14..b4ef9b5 100644 --- a/src/ioguild.cpp +++ b/src/ioguild.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,51 +19,29 @@ #include "otpch.h" -#include "database.h" -#include "guild.h" #include "ioguild.h" - -Guild* IOGuild::loadGuild(uint32_t guildId) -{ - Database& db = Database::getInstance(); - std::ostringstream query; - query << "SELECT `name` FROM `guilds` WHERE `id` = " << guildId; - if (DBResult_ptr result = db.storeQuery(query.str())) { - Guild* guild = new Guild(guildId, result->getString("name")); - - 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("id"), result->getString("name"), result->getNumber("level")); - } while (result->next()); - } - return guild; - } - return nullptr; -} +#include "database.h" uint32_t IOGuild::getGuildIdByName(const std::string& name) { - Database& db = Database::getInstance(); + Database* db = Database::getInstance(); std::ostringstream query; - query << "SELECT `id` FROM `guilds` WHERE `name` = " << db.escapeString(name); + query << "SELECT `id` FROM `guilds` WHERE `name` = " << db->escapeString(name); - DBResult_ptr result = db.storeQuery(query.str()); + DBResult_ptr result = db->storeQuery(query.str()); if (!result) { return 0; } return result->getNumber("id"); } -void IOGuild::getWarList(uint32_t guildId, GuildWarVector& guildWarVector) +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()); + DBResult_ptr result = Database::getInstance()->storeQuery(query.str()); if (!result) { return; } @@ -71,9 +49,9 @@ void IOGuild::getWarList(uint32_t guildId, GuildWarVector& guildWarVector) do { uint32_t guild1 = result->getNumber("guild1"); if (guildId != guild1) { - guildWarVector.push_back(guild1); + guildWarList.push_back(guild1); } else { - guildWarVector.push_back(result->getNumber("guild2")); + guildWarList.push_back(result->getNumber("guild2")); } } while (result->next()); } diff --git a/src/ioguild.h b/src/ioguild.h index 66e4339..e5f058c 100644 --- a/src/ioguild.h +++ b/src/ioguild.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,15 +20,13 @@ #ifndef FS_IOGUILD_H_EF9ACEBA0B844C388B70FF52E69F1AFF #define FS_IOGUILD_H_EF9ACEBA0B844C388B70FF52E69F1AFF -class Guild; -using GuildWarVector = std::vector; +typedef std::vector GuildWarList; class IOGuild { public: - static Guild* loadGuild(uint32_t guildId); static uint32_t getGuildIdByName(const std::string& name); - static void getWarList(uint32_t guildId, GuildWarVector& guildWarVector); + static void getWarList(uint32_t guildId, GuildWarList& guildWarList); }; #endif diff --git a/src/iologindata.cpp b/src/iologindata.cpp index 040688c..74c6f88 100644 --- a/src/iologindata.cpp +++ b/src/iologindata.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,14 +31,13 @@ Account IOLoginData::loadAccount(uint32_t accno) Account account; std::ostringstream query; - query << "SELECT `id`, `name`, `password`, `type`, `premdays`, `lastday` FROM `accounts` WHERE `id` = " << accno; - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); + 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("id"); - account.name = result->getString("name"); account.accountType = static_cast(result->getNumber("type")); account.premiumDays = result->getNumber("premdays"); account.lastDay = result->getNumber("lastday"); @@ -49,45 +48,16 @@ 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()); + return Database::getInstance()->executeQuery(query.str()); } -std::string decodeSecret(const std::string& secret) +bool IOLoginData::loginserverAuthentication(uint32_t accountNumber, const std::string& password, Account& account) { - // simple base32 decoding - std::string key; - key.reserve(10); - - uint32_t buffer = 0, left = 0; - for (const auto& ch : secret) { - buffer <<= 5; - if (ch >= 'A' && ch <= 'Z') { - buffer |= (ch & 0x1F) - 1; - } else if (ch >= '2' && ch <= '7') { - buffer |= ch - 24; - } else { - // if a key is broken, return empty and the comparison - // will always be false since the token must not be empty - return {}; - } - - left += 5; - if (left >= 8) { - left -= 8; - key.push_back(static_cast(buffer >> left)); - } - } - - return key; -} - -bool IOLoginData::loginserverAuthentication(const std::string& name, const std::string& password, Account& account) -{ - Database& db = Database::getInstance(); + Database* db = Database::getInstance(); std::ostringstream query; - query << "SELECT `id`, `name`, `password`, `secret`, `type`, `premdays`, `lastday` FROM `accounts` WHERE `name` = " << db.escapeString(name); - DBResult_ptr result = db.storeQuery(query.str()); + query << "SELECT `id`, `password`, `type`, `premdays`, `lastday` FROM `accounts` WHERE `id` = " << accountNumber; + DBResult_ptr result = db->storeQuery(query.str()); if (!result) { return false; } @@ -97,15 +67,13 @@ bool IOLoginData::loginserverAuthentication(const std::string& name, const std:: } account.id = result->getNumber("id"); - account.name = result->getString("name"); - account.key = decodeSecret(result->getString("secret")); account.accountType = static_cast(result->getNumber("type")); account.premiumDays = result->getNumber("premdays"); account.lastDay = result->getNumber("lastday"); query.str(std::string()); query << "SELECT `name`, `deletion` FROM `players` WHERE `account_id` = " << account.id; - result = db.storeQuery(query.str()); + result = db->storeQuery(query.str()); if (result) { do { if (result->getNumber("deletion") == 0) { @@ -117,29 +85,17 @@ bool IOLoginData::loginserverAuthentication(const std::string& name, const std:: return true; } -uint32_t IOLoginData::gameworldAuthentication(const std::string& accountName, const std::string& password, std::string& characterName, std::string& token, uint32_t tokenTime) +uint32_t IOLoginData::gameworldAuthentication(uint32_t accountNumber, const std::string& password, std::string& characterName) { - Database& db = Database::getInstance(); + Database* db = Database::getInstance(); std::ostringstream query; - query << "SELECT `id`, `password`, `secret` FROM `accounts` WHERE `name` = " << db.escapeString(accountName); - DBResult_ptr result = db.storeQuery(query.str()); + query << "SELECT `id`, `password` FROM `accounts` WHERE `id` = " << accountNumber; + DBResult_ptr result = db->storeQuery(query.str()); if (!result) { return 0; } - std::string secret = decodeSecret(result->getString("secret")); - if (!secret.empty()) { - if (token.empty()) { - return 0; - } - - bool tokenValid = token == generateToken(secret, tokenTime) || token == generateToken(secret, tokenTime - 1) || token == generateToken(secret, tokenTime + 1); - if (!tokenValid) { - return 0; - } - } - if (transformToSHA1(password) != result->getString("password")) { return 0; } @@ -147,8 +103,8 @@ uint32_t IOLoginData::gameworldAuthentication(const std::string& accountName, co uint32_t accountId = result->getNumber("id"); query.str(std::string()); - query << "SELECT `account_id`, `name`, `deletion` FROM `players` WHERE `name` = " << db.escapeString(characterName); - result = db.storeQuery(query.str()); + query << "SELECT `account_id`, `name`, `deletion` FROM `players` WHERE `name` = " << db->escapeString(characterName); + result = db->storeQuery(query.str()); if (!result) { return 0; } @@ -164,7 +120,7 @@ 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()); + DBResult_ptr result = Database::getInstance()->storeQuery(query.str()); if (!result) { return ACCOUNT_TYPE_NORMAL; } @@ -175,7 +131,7 @@ void IOLoginData::setAccountType(uint32_t accountId, AccountType_t accountType) { std::ostringstream query; query << "UPDATE `accounts` SET `type` = " << static_cast(accountType) << " WHERE `id` = " << accountId; - Database::getInstance().executeQuery(query.str()); + Database::getInstance()->executeQuery(query.str()); } void IOLoginData::updateOnlineStatus(uint32_t guid, bool login) @@ -190,20 +146,20 @@ void IOLoginData::updateOnlineStatus(uint32_t guid, bool login) } else { query << "DELETE FROM `players_online` WHERE `player_id` = " << guid; } - Database::getInstance().executeQuery(query.str()); + Database::getInstance()->executeQuery(query.str()); } bool IOLoginData::preloadPlayer(Player* player, const std::string& name) { - Database& db = Database::getInstance(); + 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()); + query << " FROM `players` WHERE `name` = " << db->escapeString(name); + DBResult_ptr result = db->storeQuery(query.str()); if (!result) { return false; } @@ -231,18 +187,17 @@ bool IOLoginData::preloadPlayer(Player* player, const std::string& name) bool IOLoginData::loadPlayerById(Player* player, uint32_t id) { - 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`, `lookaddons`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `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`, `direction` FROM `players` WHERE `id` = " << id; - return loadPlayer(player, db.storeQuery(query.str())); + 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`, `lookaddons`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `stamina`, `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(); + 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`, `lookaddons`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `offlinetraining_time`, `offlinetraining_skill`, `stamina`, `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`, `direction` FROM `players` WHERE `name` = " << db.escapeString(name); - return loadPlayer(player, db.storeQuery(query.str())); + 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`, `lookaddons`, `posx`, `posy`, `posz`, `cap`, `lastlogin`, `lastlogout`, `lastip`, `conditions`, `skulltime`, `skull`, `town_id`, `balance`, `stamina`, `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) @@ -251,7 +206,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) return false; } - Database& db = Database::getInstance(); + Database* db = Database::getInstance(); uint32_t accno = result->getNumber("account_id"); Account acc = loadAccount(accno); @@ -297,7 +252,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) } player->soul = result->getNumber("soul"); - player->capacity = result->getNumber("cap") * 100; + player->capacity = std::max(400, result->getNumber("cap")) * 100; player->blessings = result->getNumber("blessings"); unsigned long conditionsSize; @@ -343,20 +298,17 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) player->defaultOutfit.lookFeet = result->getNumber("lookfeet"); player->defaultOutfit.lookAddons = result->getNumber("lookaddons"); player->currentOutfit = player->defaultOutfit; - player->direction = static_cast (result->getNumber("direction")); if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { - const time_t skullSeconds = result->getNumber("skulltime") - time(nullptr); - if (skullSeconds > 0) { - //ensure that we round up the number of ticks - player->skullTicks = (skullSeconds + 2); + player->playerKillerEnd = result->getNumber("skulltime"); - uint16_t skull = result->getNumber("skull"); - if (skull == SKULL_RED) { - player->skull = SKULL_RED; - } else if (skull == SKULL_BLACK) { - player->skull = SKULL_BLACK; - } + uint16_t skull = result->getNumber("skull"); + if (skull == SKULL_RED) { + player->skull = SKULL_RED; + } + + if (player->playerKillerEnd == 0) { + player->skull = SKULL_NONE; } } @@ -367,9 +319,6 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) player->lastLoginSaved = result->getNumber("lastlogin"); player->lastLogout = result->getNumber("lastlogout"); - player->offlineTrainingTime = result->getNumber("offlinetraining_time") * 1000; - player->offlineTrainingSkill = result->getNumber("offlinetraining_skill"); - Town* town = g_game.map.towns.getTown(result->getNumber("town_id")); if (!town) { std::cout << "[Error - IOLoginData::loadPlayer] " << player->name << " has Town ID " << result->getNumber("town_id") << " which doesn't exist" << std::endl; @@ -402,16 +351,38 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) } 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("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()))) { + if ((result = db->storeQuery(query.str()))) { uint32_t guildId = result->getNumber("guild_id"); uint32_t playerRankId = result->getNumber("rank_id"); player->guildNick = result->getString("nick"); Guild* guild = g_game.getGuild(guildId); if (!guild) { - guild = IOGuild::loadGuild(guildId); - g_game.addGuild(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("id"), result->getString("name"), result->getNumber("level")); + } while (result->next()); + } + } } if (guild) { @@ -421,7 +392,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) query.str(std::string()); query << "SELECT `id`, `name`, `level` FROM `guild_ranks` WHERE `id` = " << playerRankId; - if ((result = db.storeQuery(query.str()))) { + if ((result = db->storeQuery(query.str()))) { guild->addRank(result->getNumber("id"), result->getString("name"), result->getNumber("level")); } @@ -433,11 +404,11 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) player->guildRank = rank; - IOGuild::getWarList(guildId, player->guildWarVector); + 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()))) { + if ((result = db->storeQuery(query.str()))) { guild->setMemberCount(result->getNumber("members")); } } @@ -445,7 +416,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) query.str(std::string()); query << "SELECT `player_id`, `name` FROM `player_spells` WHERE `player_id` = " << player->getGUID(); - if ((result = db.storeQuery(query.str()))) { + if ((result = db->storeQuery(query.str()))) { do { player->learnedInstantSpellList.emplace_front(result->getString("name")); } while (result->next()); @@ -456,7 +427,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) 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()))) { + if ((result = db->storeQuery(query.str()))) { loadItems(itemMap, result); for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { @@ -484,7 +455,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) 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()))) { + if ((result = db->storeQuery(query.str()))) { loadItems(itemMap, result); for (ItemMap::const_reverse_iterator it = itemMap.rbegin(), end = itemMap.rend(); it != end; ++it) { @@ -493,9 +464,15 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) int32_t pid = pair.second; if (pid >= 0 && pid < 100) { - DepotChest* depotChest = player->getDepotChest(pid, true); - if (depotChest) { - depotChest->internalAddThing(item); + 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); @@ -511,49 +488,19 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result) } } - //load inbox items - itemMap.clear(); - - query.str(std::string()); - query << "SELECT `pid`, `sid`, `itemtype`, `count`, `attributes` FROM `player_inboxitems` 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& pair = it->second; - Item* item = pair.first; - int32_t pid = pair.second; - - if (pid >= 0 && pid < 100) { - player->getInbox()->internalAddThing(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 storage map query.str(std::string()); query << "SELECT `key`, `value` FROM `player_storage` WHERE `player_id` = " << player->getGUID(); - if ((result = db.storeQuery(query.str()))) { + if ((result = db->storeQuery(query.str()))) { do { - player->addStorageValue(result->getNumber("key"), result->getNumber("value"), true); + player->addStorageValue(result->getNumber("key"), result->getNumber("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()))) { + if ((result = db->storeQuery(query.str()))) { do { player->addVIPInternal(result->getNumber("player_id")); } while (result->next()); @@ -569,12 +516,12 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, { std::ostringstream ss; - using ContainerBlock = std::pair; - std::list queue; + typedef std::pair containerBlock; + std::list queue; int32_t runningId = 100; - Database& db = Database::getInstance(); + Database* db = Database::getInstance(); for (const auto& it : itemList) { int32_t pid = it.first; Item* item = it.second; @@ -586,7 +533,7 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, size_t attributesSize; const char* attributes = propWriteStream.getStream(attributesSize); - ss << player->getGUID() << ',' << pid << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db.escapeBlob(attributes, attributesSize); + ss << player->getGUID() << ',' << pid << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db->escapeBlob(attributes, attributesSize); if (!query_insert.addRow(ss)) { return false; } @@ -597,7 +544,7 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, } while (!queue.empty()) { - const ContainerBlock& cb = queue.front(); + const containerBlock& cb = queue.front(); Container* container = cb.first; int32_t parentId = cb.second; queue.pop_front(); @@ -616,7 +563,7 @@ bool IOLoginData::saveItems(const Player* player, const ItemBlockList& itemList, size_t attributesSize; const char* attributes = propWriteStream.getStream(attributesSize); - ss << player->getGUID() << ',' << parentId << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db.escapeBlob(attributes, attributesSize); + ss << player->getGUID() << ',' << parentId << ',' << runningId << ',' << item->getID() << ',' << item->getSubType() << ',' << db->escapeBlob(attributes, attributesSize); if (!query_insert.addRow(ss)) { return false; } @@ -631,11 +578,11 @@ bool IOLoginData::savePlayer(Player* player) player->changeHealth(1); } - Database& db = Database::getInstance(); + Database* db = Database::getInstance(); std::ostringstream query; query << "SELECT `save` FROM `players` WHERE `id` = " << player->getGUID(); - DBResult_ptr result = db.storeQuery(query.str()); + DBResult_ptr result = db->storeQuery(query.str()); if (!result) { return false; } @@ -643,7 +590,7 @@ bool IOLoginData::savePlayer(Player* player) if (result->getNumber("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()); + return db->executeQuery(query.str()); } //serialize conditions @@ -686,7 +633,7 @@ bool IOLoginData::savePlayer(Player* player) query << "`posz` = " << loginPosition.getZ() << ','; query << "`cap` = " << (player->capacity / 100) << ','; - query << "`sex` = " << static_cast(player->sex) << ','; + query << "`sex` = " << player->sex << ','; if (player->lastLoginSaved != 0) { query << "`lastlogin` = " << player->lastLoginSaved << ','; @@ -696,29 +643,21 @@ bool IOLoginData::savePlayer(Player* player) query << "`lastip` = " << player->lastIP << ','; } - query << "`conditions` = " << db.escapeBlob(conditions, conditionsSize) << ','; + query << "`conditions` = " << db->escapeBlob(conditions, conditionsSize) << ','; if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { - int64_t skullTime = 0; - - if (player->skullTicks > 0) { - skullTime = time(nullptr) + player->skullTicks; - } - query << "`skulltime` = " << skullTime << ','; + query << "`skulltime` = " << player->getPlayerKillerEnd() << ','; Skulls_t skull = SKULL_NONE; if (player->skull == SKULL_RED) { skull = SKULL_RED; - } else if (player->skull == SKULL_BLACK) { - skull = SKULL_BLACK; } - query << "`skull` = " << static_cast(skull) << ','; + + query << "`skull` = " << static_cast(skull) << ','; } query << "`lastlogout` = " << player->getLastLogout() << ','; query << "`balance` = " << player->bankBalance << ','; - query << "`offlinetraining_time` = " << player->getOfflineTrainingTime() / 1000 << ','; - query << "`offlinetraining_skill` = " << player->getOfflineTrainingSkill() << ','; query << "`stamina` = " << player->getStaminaMinutes() << ','; query << "`skill_fist` = " << player->skills[SKILL_FIST].level << ','; @@ -735,7 +674,6 @@ bool IOLoginData::savePlayer(Player* player) 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 << ','; - query << "`direction` = " << static_cast (player->getDirection()) << ','; if (!player->isOffline()) { query << "`onlinetime` = `onlinetime` + " << (time(nullptr) - player->lastLoginSaved) << ','; @@ -748,14 +686,14 @@ bool IOLoginData::savePlayer(Player* player) return false; } - if (!db.executeQuery(query.str())) { + 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())) { + if (!db->executeQuery(query.str())) { return false; } @@ -763,7 +701,7 @@ bool IOLoginData::savePlayer(Player* player) DBInsert spellsQuery("INSERT INTO `player_spells` (`player_id`, `name` ) VALUES "); for (const std::string& spellName : player->learnedInstantSpellList) { - query << player->getGUID() << ',' << db.escapeString(spellName); + query << player->getGUID() << ',' << db->escapeString(spellName); if (!spellsQuery.addRow(query)) { return false; } @@ -773,9 +711,31 @@ bool IOLoginData::savePlayer(Player* player) 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())) { + if (!db->executeQuery(query.str())) { return false; } @@ -793,51 +753,28 @@ bool IOLoginData::savePlayer(Player* player) return false; } - if (player->lastDepotId != -1) { - //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->depotChests) { - DepotChest* depotChest = it.second; - for (Item* item : depotChest->getItemList()) { - itemList.emplace_back(it.first, item); - } - } - - if (!saveItems(player, itemList, depotQuery, propWriteStream)) { - return false; - } - } - - //save inbox items + //save depot items query.str(std::string()); - query << "DELETE FROM `player_inboxitems` WHERE `player_id` = " << player->getGUID(); - if (!db.executeQuery(query.str())) { + query << "DELETE FROM `player_depotitems` WHERE `player_id` = " << player->getGUID(); + + if (!db->executeQuery(query.str())) { return false; } - DBInsert inboxQuery("INSERT INTO `player_inboxitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); + DBInsert depotQuery("INSERT INTO `player_depotitems` (`player_id`, `pid`, `sid`, `itemtype`, `count`, `attributes`) VALUES "); itemList.clear(); - for (Item* item : player->getInbox()->getItemList()) { - itemList.emplace_back(0, item); + for (const auto& it : player->depotLockerMap) { + itemList.emplace_back(it.first, it.second); } - if (!saveItems(player, itemList, inboxQuery, propWriteStream)) { + 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())) { + if (!db->executeQuery(query.str())) { return false; } @@ -865,7 +802,7 @@ 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()); + DBResult_ptr result = Database::getInstance()->storeQuery(query.str()); if (!result) { return std::string(); } @@ -874,11 +811,11 @@ std::string IOLoginData::getNameByGuid(uint32_t guid) uint32_t IOLoginData::getGuidByName(const std::string& name) { - Database& db = Database::getInstance(); + Database* db = Database::getInstance(); std::ostringstream query; - query << "SELECT `id` FROM `players` WHERE `name` = " << db.escapeString(name); - DBResult_ptr result = db.storeQuery(query.str()); + query << "SELECT `id` FROM `players` WHERE `name` = " << db->escapeString(name); + DBResult_ptr result = db->storeQuery(query.str()); if (!result) { return 0; } @@ -887,11 +824,11 @@ uint32_t IOLoginData::getGuidByName(const std::string& name) bool IOLoginData::getGuidByNameEx(uint32_t& guid, bool& specialVip, std::string& name) { - Database& db = Database::getInstance(); + 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()); + 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; } @@ -913,12 +850,12 @@ bool IOLoginData::getGuidByNameEx(uint32_t& guid, bool& specialVip, std::string& bool IOLoginData::formatPlayerName(std::string& name) { - Database& db = Database::getInstance(); + Database* db = Database::getInstance(); std::ostringstream query; - query << "SELECT `name` FROM `players` WHERE `name` = " << db.escapeString(name); + query << "SELECT `name` FROM `players` WHERE `name` = " << db->escapeString(name); - DBResult_ptr result = db.storeQuery(query.str()); + DBResult_ptr result = db->storeQuery(query.str()); if (!result) { return false; } @@ -957,16 +894,16 @@ 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()); + Database::getInstance()->executeQuery(query.str()); } bool IOLoginData::hasBiddedOnHouse(uint32_t guid) { - Database& db = Database::getInstance(); + 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; + return db->storeQuery(query.str()).get() != nullptr; } std::forward_list IOLoginData::getVIPEntries(uint32_t accountId) @@ -974,58 +911,46 @@ std::forward_list IOLoginData::getVIPEntries(uint32_t accountId) std::forward_list entries; std::ostringstream query; - query << "SELECT `player_id`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `name`, `description`, `icon`, `notify` FROM `account_viplist` WHERE `account_id` = " << accountId; + 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()); + DBResult_ptr result = Database::getInstance()->storeQuery(query.str()); if (result) { do { entries.emplace_front( result->getNumber("player_id"), - result->getString("name"), - result->getString("description"), - result->getNumber("icon"), - result->getNumber("notify") != 0 + result->getString("name") ); } while (result->next()); } return entries; } -void IOLoginData::addVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, bool notify) +void IOLoginData::addVIPEntry(uint32_t accountId, uint32_t guid) { - Database& db = Database::getInstance(); + Database* db = Database::getInstance(); std::ostringstream query; - query << "INSERT INTO `account_viplist` (`account_id`, `player_id`, `description`, `icon`, `notify`) VALUES (" << accountId << ',' << guid << ',' << db.escapeString(description) << ',' << icon << ',' << notify << ')'; - db.executeQuery(query.str()); -} - -void IOLoginData::editVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, bool notify) -{ - Database& db = Database::getInstance(); - - std::ostringstream query; - query << "UPDATE `account_viplist` SET `description` = " << db.escapeString(description) << ", `icon` = " << icon << ", `notify` = " << notify << " WHERE `account_id` = " << accountId << " AND `player_id` = " << guid; - db.executeQuery(query.str()); + 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()); + 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()); + 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()); + Database::getInstance()->executeQuery(query.str()); } diff --git a/src/iologindata.h b/src/iologindata.h index 446e361..d207507 100644 --- a/src/iologindata.h +++ b/src/iologindata.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,7 +24,7 @@ #include "player.h" #include "database.h" -using ItemBlockList = std::list>; +typedef std::list> ItemBlockList; class IOLoginData { @@ -32,8 +32,8 @@ class IOLoginData static Account loadAccount(uint32_t accno); static bool saveAccount(const Account& acc); - static bool loginserverAuthentication(const std::string& name, const std::string& password, Account& account); - static uint32_t gameworldAuthentication(const std::string& accountName, const std::string& password, std::string& characterName, std::string& token, uint32_t tokenTime); + 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); @@ -52,18 +52,17 @@ class IOLoginData static bool hasBiddedOnHouse(uint32_t guid); static std::forward_list getVIPEntries(uint32_t accountId); - static void addVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, bool notify); - static void editVIPEntry(uint32_t accountId, uint32_t guid, const std::string& description, uint32_t icon, bool notify); + 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); - private: - using ItemMap = std::map>; + protected: + typedef std::map> ItemMap; static void loadItems(ItemMap& itemMap, DBResult_ptr result); - static bool saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& propWriteStream); + static bool saveItems(const Player* player, const ItemBlockList& itemList, DBInsert& query_insert, PropWriteStream& stream); }; #endif diff --git a/src/iomap.cpp b/src/iomap.cpp index 9c40761..197bf97 100644 --- a/src/iomap.cpp +++ b/src/iomap.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -66,14 +66,23 @@ Tile* IOMap::createTile(Item*& ground, Item* item, uint16_t x, uint16_t y, uint8 return tile; } -bool IOMap::loadMap(Map* map, const std::string& fileName) +bool IOMap::loadMap(Map* map, const std::string& identifier) { int64_t start = OTSYS_TIME(); - OTB::Loader loader{fileName, OTB::Identifier{{'O', 'T', 'B', 'M'}}}; - auto& root = loader.parseTree(); + 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; - if (!loader.getProps(root, propStream)) { + + NODE root = f.getChildNode(nullptr, type); + if (!f.getProps(root, propStream)) { setLastErrorString("Could not read root property."); return false; } @@ -85,7 +94,7 @@ bool IOMap::loadMap(Map* map, const std::string& fileName) } uint32_t headerVersion = root_header.version; - if (headerVersion == 0) { + 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. @@ -98,66 +107,17 @@ bool IOMap::loadMap(Map* map, const std::string& fileName) return false; } - if (root_header.majorVersionItems < 3) { - setLastErrorString("This map need to be upgraded by using the latest map editor version to be able to load correctly."); - return false; - } - - if (root_header.majorVersionItems > Item::items.majorVersion) { - setLastErrorString("The map was saved with a different items.otb version, an upgraded items.otb is required."); - return false; - } - - if (root_header.minorVersionItems < CLIENT_VERSION_810) { - setLastErrorString("This map needs to be updated."); - return false; - } - - if (root_header.minorVersionItems > Item::items.minorVersion) { - std::cout << "[Warning - IOMap::loadMap] This map needs an updated items.otb." << std::endl; - } - std::cout << "> Map size: " << root_header.width << "x" << root_header.height << '.' << std::endl; map->width = root_header.width; map->height = root_header.height; - if (root.children.size() != 1 || root.children[0].type != OTBM_MAP_DATA) { + NODE nodeMap = f.getChildNode(root, type); + if (type != OTBM_MAP_DATA) { setLastErrorString("Could not read data node."); return false; } - auto& mapNode = root.children[0]; - if (!parseMapDataAttributes(loader, mapNode, *map, fileName)) { - return false; - } - - for (auto& mapDataNode : mapNode.children) { - if (mapDataNode.type == OTBM_TILE_AREA) { - if (!parseTileArea(loader, mapDataNode, *map)) { - return false; - } - } else if (mapDataNode.type == OTBM_TOWNS) { - if (!parseTowns(loader, mapDataNode, *map)) { - return false; - } - } else if (mapDataNode.type == OTBM_WAYPOINTS && headerVersion > 1) { - if (!parseWaypoints(loader, mapDataNode, *map)) { - return false; - } - } else { - setLastErrorString("Unknown map node."); - return false; - } - } - - std::cout << "> Map loading time: " << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl; - return true; -} - -bool IOMap::parseMapDataAttributes(OTB::Loader& loader, const OTB::Node& mapNode, Map& map, const std::string& fileName) -{ - PropStream propStream; - if (!loader.getProps(mapNode, propStream)) { + if (!f.getProps(nodeMap, propStream)) { setLastErrorString("Could not read map data attributes."); return false; } @@ -181,8 +141,8 @@ bool IOMap::parseMapDataAttributes(OTB::Loader& loader, const OTB::Node& mapNode return false; } - map.spawnfile = fileName.substr(0, fileName.rfind('/') + 1); - map.spawnfile += tmp; + map->spawnfile = identifier.substr(0, identifier.rfind('/') + 1); + map->spawnfile += tmp; break; case OTBM_ATTR_EXT_HOUSE_FILE: @@ -191,8 +151,8 @@ bool IOMap::parseMapDataAttributes(OTB::Loader& loader, const OTB::Node& mapNode return false; } - map.housefile = fileName.substr(0, fileName.rfind('/') + 1); - map.housefile += tmp; + map->housefile = identifier.substr(0, identifier.rfind('/') + 1); + map->housefile += tmp; break; default: @@ -200,104 +160,172 @@ bool IOMap::parseMapDataAttributes(OTB::Loader& loader, const OTB::Node& mapNode return false; } } - return true; -} -bool IOMap::parseTileArea(OTB::Loader& loader, const OTB::Node& tileAreaNode, Map& map) -{ - PropStream propStream; - if (!loader.getProps(tileAreaNode, 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; - - for (auto& tileNode : tileAreaNode.children) { - if (tileNode.type != OTBM_TILE && tileNode.type != OTBM_HOUSETILE) { - setLastErrorString("Unknown tile node."); + NODE nodeMapData = f.getChildNode(nodeMap, type); + while (nodeMapData != NO_NODE) { + if (f.getError() != ERROR_NONE) { + setLastErrorString("Invalid map node."); return false; } - if (!loader.getProps(tileNode, 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 (tileNode.type == OTBM_HOUSETILE) { - uint32_t houseId; - if (!propStream.read(houseId)) { - std::ostringstream ss; - ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Could not read house id."; - setLastErrorString(ss.str()); + if (type == OTBM_TILE_AREA) { + if (!f.getProps(nodeMapData, propStream)) { + setLastErrorString("Invalid map node."); 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()); + OTBM_Destination_coords area_coord; + if (!propStream.read(area_coord)) { + setLastErrorString("Invalid map node."); return false; } - tile = new HouseTile(x, y, z, house); - house->addTile(static_cast(tile)); - isHouseTile = true; - } + uint16_t base_x = area_coord.x; + uint16_t base_y = area_coord.y; + uint16_t z = area_coord.z; - uint8_t attribute; - //read tile attributes - while (propStream.read(attribute)) { - switch (attribute) { - case OTBM_ATTR_TILE_FLAGS: { - uint32_t flags; - if (!propStream.read(flags)) { + 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(houseId)) { std::ostringstream ss; - ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Failed to read tile flags."; + ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Could not read house id."; 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; + 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; } - if ((flags & OTBM_TILEFLAG_NOLOGOUT) != 0) { - tileflags |= TILESTATE_NOLOGOUT; - } - break; + tile = new HouseTile(x, y, z, house); + house->addTile(static_cast(tile)); + isHouseTile = true; } - case OTBM_ATTR_ITEM: { - Item* item = Item::CreateItem(propStream); + //read tile attributes + while (propStream.read(attribute)) { + switch (attribute) { + case OTBM_ATTR_TILE_FLAGS: { + uint32_t flags; + if (!propStream.read(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."; @@ -305,11 +333,19 @@ bool IOMap::parseTileArea(OTB::Loader& loader, const OTB::Node& tileAreaNode, Ma 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; + //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) { + if (item->getItemCount() <= 0) { item->setItemCount(1); } @@ -327,156 +363,100 @@ bool IOMap::parseTileArea(OTB::Loader& loader, const OTB::Node& tileAreaNode, Ma item->setLoadedFromMap(true); } } - break; + + nodeItem = f.getNextNode(nodeItem, type); } - default: - std::ostringstream ss; - ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Unknown tile attribute."; - setLastErrorString(ss.str()); + if (!tile) { + tile = createTile(ground_item, nullptr, x, y, z); + } + + tile->setFlag(static_cast(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; - } - } - - for (auto& itemNode : tileNode.children) { - if (itemNode.type != OTBM_ITEM) { - std::ostringstream ss; - ss << "[x:" << x << ", y:" << y << ", z:" << z << "] Unknown node type."; - setLastErrorString(ss.str()); - return false; - } - - PropStream stream; - if (!loader.getProps(itemNode, 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(loader, itemNode, 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); + if (!f.getProps(nodeTown, propStream)) { + setLastErrorString("Could not read town data."); + return false; } + + uint32_t townId; + if (!propStream.read(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; } - if (!tile) { - tile = createTile(ground_item, nullptr, x, y, z); - } - - tile->setFlag(static_cast(tileflags)); - - map.setTile(x, y, z, tile); + nodeMapData = f.getNextNode(nodeMapData, type); } + + std::cout << "> Map loading time: " << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl; return true; } - -bool IOMap::parseTowns(OTB::Loader& loader, const OTB::Node& townsNode, Map& map) -{ - for (auto& townNode : townsNode.children) { - PropStream propStream; - if (townNode.type != OTBM_TOWN) { - setLastErrorString("Unknown town node."); - return false; - } - - if (!loader.getProps(townNode, propStream)) { - setLastErrorString("Could not read town data."); - return false; - } - - uint32_t townId; - if (!propStream.read(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)); - } - return true; -} - - -bool IOMap::parseWaypoints(OTB::Loader& loader, const OTB::Node& waypointsNode, Map& map) -{ - PropStream propStream; - for (auto& node : waypointsNode.children) { - if (node.type != OTBM_WAYPOINT) { - setLastErrorString("Unknown waypoint node."); - return false; - } - - if (!loader.getProps(node, 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); - } - return true; -} - diff --git a/src/iomap.h b/src/iomap.h index a00d8a4..5062e98 100644 --- a/src/iomap.h +++ b/src/iomap.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,7 +33,7 @@ enum OTBM_AttrTypes_t { OTBM_ATTR_EXT_FILE = 2, OTBM_ATTR_TILE_FLAGS = 3, OTBM_ATTR_ACTION_ID = 4, - OTBM_ATTR_UNIQUE_ID = 5, + OTBM_ATTR_MOVEMENT_ID = 5, OTBM_ATTR_TEXT = 6, OTBM_ATTR_DESC = 7, OTBM_ATTR_TELE_DEST = 8, @@ -51,6 +51,12 @@ enum OTBM_AttrTypes_t { 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 { @@ -76,7 +82,8 @@ 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_PVPZONE = 1 << 4, + OTBM_TILEFLAG_REFRESH = 1 << 5, }; #pragma pack(1) @@ -107,7 +114,7 @@ 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& fileName); + bool loadMap(Map* map, const std::string& identifier); /* Load the spawns * \param map pointer to the Map class @@ -147,11 +154,7 @@ class IOMap errorString = error; } - private: - bool parseMapDataAttributes(OTB::Loader& loader, const OTB::Node& mapNode, Map& map, const std::string& fileName); - bool parseWaypoints(OTB::Loader& loader, const OTB::Node& waypointsNode, Map& map); - bool parseTowns(OTB::Loader& loader, const OTB::Node& townsNode, Map& map); - bool parseTileArea(OTB::Loader& loader, const OTB::Node& tileAreaNode, Map& map); + protected: std::string errorString; }; diff --git a/src/iomapserialize.cpp b/src/iomapserialize.cpp index af606c2..a6a5b9d 100644 --- a/src/iomapserialize.cpp +++ b/src/iomapserialize.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,7 +29,7 @@ void IOMapSerialize::loadHouseItems(Map* map) { int64_t start = OTSYS_TIME(); - DBResult_ptr result = Database::getInstance().storeQuery("SELECT `data` FROM `tile_store`"); + DBResult_ptr result = Database::getInstance()->storeQuery("SELECT `data` FROM `tile_store`"); if (!result) { return; } @@ -67,7 +67,7 @@ void IOMapSerialize::loadHouseItems(Map* map) bool IOMapSerialize::saveHouseItems() { int64_t start = OTSYS_TIME(); - Database& db = Database::getInstance(); + Database* db = Database::getInstance(); std::ostringstream query; //Start the transaction @@ -77,7 +77,7 @@ bool IOMapSerialize::saveHouseItems() } //clear old tile data - if (!db.executeQuery("DELETE FROM `tile_store`")) { + if (!db->executeQuery("DELETE FROM `tile_store`")) { return false; } @@ -93,7 +93,7 @@ bool IOMapSerialize::saveHouseItems() size_t attributesSize; const char* attributes = stream.getStream(attributesSize); if (attributesSize > 0) { - query << house->getId() << ',' << db.escapeBlob(attributes, attributesSize); + query << house->getId() << ',' << db->escapeBlob(attributes, attributesSize); if (!stmt.addRow(query)) { return false; } @@ -144,7 +144,7 @@ bool IOMapSerialize::loadItem(PropStream& propStream, Cylinder* parent) } const ItemType& iType = Item::items[id]; - if (iType.moveable || iType.forceSerialize || !tile) { + if (iType.moveable || !tile) { //create a new item Item* item = Item::CreateItem(id); if (item) { @@ -247,7 +247,7 @@ void IOMapSerialize::saveTile(PropWriteStream& stream, const Tile* tile) const ItemType& it = Item::items[item->getID()]; // Note that these are NEGATED, ie. these are the items that will be saved. - if (!(it.moveable || it.forceSerialize || item->getDoor() || (item->getContainer() && !item->getContainer()->empty()) || it.canWriteText || item->getBed())) { + if (!(it.moveable || item->getDoor() || (item->getContainer() && !item->getContainer()->empty()) || it.canWriteText || item->getBed())) { continue; } @@ -270,9 +270,9 @@ void IOMapSerialize::saveTile(PropWriteStream& stream, const Tile* tile) bool IOMapSerialize::loadHouseInfo() { - Database& db = Database::getInstance(); + Database* db = Database::getInstance(); - DBResult_ptr result = db.storeQuery("SELECT `id`, `owner`, `paid`, `warnings` FROM `houses`"); + DBResult_ptr result = db->storeQuery("SELECT `id`, `owner`, `paid`, `warnings` FROM `houses`"); if (!result) { return false; } @@ -286,7 +286,7 @@ bool IOMapSerialize::loadHouseInfo() } } while (result->next()); - result = db.storeQuery("SELECT `house_id`, `listid`, `list` FROM `house_lists`"); + result = db->storeQuery("SELECT `house_id`, `listid`, `list` FROM `house_lists`"); if (result) { do { House* house = g_game.map.houses.getHouse(result->getNumber("house_id")); @@ -300,14 +300,14 @@ bool IOMapSerialize::loadHouseInfo() bool IOMapSerialize::saveHouseInfo() { - Database& db = Database::getInstance(); + Database* db = Database::getInstance(); DBTransaction transaction; if (!transaction.begin()) { return false; } - if (!db.executeQuery("DELETE FROM `house_lists`")) { + if (!db->executeQuery("DELETE FROM `house_lists`")) { return false; } @@ -315,16 +315,16 @@ bool IOMapSerialize::saveHouseInfo() 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()); + 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(); + 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() << ')'; + 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()); + db->executeQuery(query.str()); query.str(std::string()); } @@ -335,7 +335,7 @@ bool IOMapSerialize::saveHouseInfo() std::string listText; if (house->getAccessList(GUEST_LIST, listText) && !listText.empty()) { - query << house->getId() << ',' << GUEST_LIST << ',' << db.escapeString(listText); + query << house->getId() << ',' << GUEST_LIST << ',' << db->escapeString(listText); if (!stmt.addRow(query)) { return false; } @@ -344,7 +344,7 @@ bool IOMapSerialize::saveHouseInfo() } if (house->getAccessList(SUBOWNER_LIST, listText) && !listText.empty()) { - query << house->getId() << ',' << SUBOWNER_LIST << ',' << db.escapeString(listText); + query << house->getId() << ',' << SUBOWNER_LIST << ',' << db->escapeString(listText); if (!stmt.addRow(query)) { return false; } @@ -354,7 +354,7 @@ bool IOMapSerialize::saveHouseInfo() for (Door* door : house->getDoors()) { if (door->getAccessList(listText) && !listText.empty()) { - query << house->getId() << ',' << door->getDoorId() << ',' << db.escapeString(listText); + query << house->getId() << ',' << door->getDoorId() << ',' << db->escapeString(listText); if (!stmt.addRow(query)) { return false; } diff --git a/src/iomapserialize.h b/src/iomapserialize.h index 9840f24..1a87c28 100644 --- a/src/iomapserialize.h +++ b/src/iomapserialize.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,7 +31,7 @@ class IOMapSerialize static bool loadHouseInfo(); static bool saveHouseInfo(); - private: + protected: static void saveItem(PropWriteStream& stream, const Item* item); static void saveTile(PropWriteStream& stream, const Tile* tile); diff --git a/src/iomarket.cpp b/src/iomarket.cpp deleted file mode 100644 index 7a651df..0000000 --- a/src/iomarket.cpp +++ /dev/null @@ -1,347 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "otpch.h" - -#include "iomarket.h" - -#include "configmanager.h" -#include "databasetasks.h" -#include "iologindata.h" -#include "game.h" -#include "scheduler.h" - -extern ConfigManager g_config; -extern Game g_game; - -MarketOfferList IOMarket::getActiveOffers(MarketAction_t action, uint16_t itemId) -{ - MarketOfferList offerList; - - std::ostringstream query; - query << "SELECT `id`, `amount`, `price`, `created`, `anonymous`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `player_name` FROM `market_offers` WHERE `sale` = " << action << " AND `itemtype` = " << itemId; - - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); - if (!result) { - return offerList; - } - - const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); - - do { - MarketOffer offer; - offer.amount = result->getNumber("amount"); - offer.price = result->getNumber("price"); - offer.timestamp = result->getNumber("created") + marketOfferDuration; - offer.counter = result->getNumber("id") & 0xFFFF; - if (result->getNumber("anonymous") == 0) { - offer.playerName = result->getString("player_name"); - } else { - offer.playerName = "Anonymous"; - } - offerList.push_back(offer); - } while (result->next()); - return offerList; -} - -MarketOfferList IOMarket::getOwnOffers(MarketAction_t action, uint32_t playerId) -{ - MarketOfferList offerList; - - const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); - - std::ostringstream query; - query << "SELECT `id`, `amount`, `price`, `created`, `itemtype` FROM `market_offers` WHERE `player_id` = " << playerId << " AND `sale` = " << action; - - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); - if (!result) { - return offerList; - } - - do { - MarketOffer offer; - offer.amount = result->getNumber("amount"); - offer.price = result->getNumber("price"); - offer.timestamp = result->getNumber("created") + marketOfferDuration; - offer.counter = result->getNumber("id") & 0xFFFF; - offer.itemId = result->getNumber("itemtype"); - offerList.push_back(offer); - } while (result->next()); - return offerList; -} - -HistoryMarketOfferList IOMarket::getOwnHistory(MarketAction_t action, uint32_t playerId) -{ - HistoryMarketOfferList offerList; - - std::ostringstream query; - query << "SELECT `itemtype`, `amount`, `price`, `expires_at`, `state` FROM `market_history` WHERE `player_id` = " << playerId << " AND `sale` = " << action; - - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); - if (!result) { - return offerList; - } - - do { - HistoryMarketOffer offer; - offer.itemId = result->getNumber("itemtype"); - offer.amount = result->getNumber("amount"); - offer.price = result->getNumber("price"); - offer.timestamp = result->getNumber("expires_at"); - - MarketOfferState_t offerState = static_cast(result->getNumber("state")); - if (offerState == OFFERSTATE_ACCEPTEDEX) { - offerState = OFFERSTATE_ACCEPTED; - } - - offer.state = offerState; - - offerList.push_back(offer); - } while (result->next()); - return offerList; -} - -void IOMarket::processExpiredOffers(DBResult_ptr result, bool) -{ - if (!result) { - return; - } - - do { - if (!IOMarket::moveOfferToHistory(result->getNumber("id"), OFFERSTATE_EXPIRED)) { - continue; - } - - const uint32_t playerId = result->getNumber("player_id"); - const uint16_t amount = result->getNumber("amount"); - if (result->getNumber("sale") == 1) { - const ItemType& itemType = Item::items[result->getNumber("itemtype")]; - if (itemType.id == 0) { - continue; - } - - Player* player = g_game.getPlayerByGUID(playerId); - if (!player) { - player = new Player(nullptr); - if (!IOLoginData::loadPlayerById(player, playerId)) { - delete player; - continue; - } - } - - if (itemType.stackable) { - uint16_t tmpAmount = amount; - while (tmpAmount > 0) { - uint16_t stackCount = std::min(100, tmpAmount); - Item* item = Item::CreateItem(itemType.id, stackCount); - if (g_game.internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { - delete item; - break; - } - - tmpAmount -= stackCount; - } - } else { - int32_t subType; - if (itemType.charges != 0) { - subType = itemType.charges; - } else { - subType = -1; - } - - for (uint16_t i = 0; i < amount; ++i) { - Item* item = Item::CreateItem(itemType.id, subType); - if (g_game.internalAddItem(player->getInbox(), item, INDEX_WHEREEVER, FLAG_NOLIMIT) != RETURNVALUE_NOERROR) { - delete item; - break; - } - } - } - - if (player->isOffline()) { - IOLoginData::savePlayer(player); - delete player; - } - } else { - uint64_t totalPrice = result->getNumber("price") * amount; - - Player* player = g_game.getPlayerByGUID(playerId); - if (player) { - player->setBankBalance(player->getBankBalance() + totalPrice); - } else { - IOLoginData::increaseBankBalance(playerId, totalPrice); - } - } - } while (result->next()); -} - -void IOMarket::checkExpiredOffers() -{ - const time_t lastExpireDate = time(nullptr) - g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); - - std::ostringstream query; - query << "SELECT `id`, `amount`, `price`, `itemtype`, `player_id`, `sale` FROM `market_offers` WHERE `created` <= " << lastExpireDate; - g_databaseTasks.addTask(query.str(), IOMarket::processExpiredOffers, true); - - int32_t checkExpiredMarketOffersEachMinutes = g_config.getNumber(ConfigManager::CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES); - if (checkExpiredMarketOffersEachMinutes <= 0) { - return; - } - - g_scheduler.addEvent(createSchedulerTask(checkExpiredMarketOffersEachMinutes * 60 * 1000, IOMarket::checkExpiredOffers)); -} - -uint32_t IOMarket::getPlayerOfferCount(uint32_t playerId) -{ - std::ostringstream query; - query << "SELECT COUNT(*) AS `count` FROM `market_offers` WHERE `player_id` = " << playerId; - - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); - if (!result) { - return 0; - } - return result->getNumber("count"); -} - -MarketOfferEx IOMarket::getOfferByCounter(uint32_t timestamp, uint16_t counter) -{ - MarketOfferEx offer; - - const int32_t created = timestamp - g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); - - std::ostringstream query; - query << "SELECT `id`, `sale`, `itemtype`, `amount`, `created`, `price`, `player_id`, `anonymous`, (SELECT `name` FROM `players` WHERE `id` = `player_id`) AS `player_name` FROM `market_offers` WHERE `created` = " << created << " AND (`id` & 65535) = " << counter << " LIMIT 1"; - - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); - if (!result) { - offer.id = 0; - return offer; - } - - offer.id = result->getNumber("id"); - offer.type = static_cast(result->getNumber("sale")); - offer.amount = result->getNumber("amount"); - offer.counter = result->getNumber("id") & 0xFFFF; - offer.timestamp = result->getNumber("created"); - offer.price = result->getNumber("price"); - offer.itemId = result->getNumber("itemtype"); - offer.playerId = result->getNumber("player_id"); - if (result->getNumber("anonymous") == 0) { - offer.playerName = result->getString("player_name"); - } else { - offer.playerName = "Anonymous"; - } - return offer; -} - -void IOMarket::createOffer(uint32_t playerId, MarketAction_t action, uint32_t itemId, uint16_t amount, uint32_t price, bool anonymous) -{ - std::ostringstream query; - query << "INSERT INTO `market_offers` (`player_id`, `sale`, `itemtype`, `amount`, `price`, `created`, `anonymous`) VALUES (" << playerId << ',' << action << ',' << itemId << ',' << amount << ',' << price << ',' << time(nullptr) << ',' << anonymous << ')'; - Database::getInstance().executeQuery(query.str()); -} - -void IOMarket::acceptOffer(uint32_t offerId, uint16_t amount) -{ - std::ostringstream query; - query << "UPDATE `market_offers` SET `amount` = `amount` - " << amount << " WHERE `id` = " << offerId; - Database::getInstance().executeQuery(query.str()); -} - -void IOMarket::deleteOffer(uint32_t offerId) -{ - std::ostringstream query; - query << "DELETE FROM `market_offers` WHERE `id` = " << offerId; - Database::getInstance().executeQuery(query.str()); -} - -void IOMarket::appendHistory(uint32_t playerId, MarketAction_t type, uint16_t itemId, uint16_t amount, uint32_t price, time_t timestamp, MarketOfferState_t state) -{ - std::ostringstream query; - query << "INSERT INTO `market_history` (`player_id`, `sale`, `itemtype`, `amount`, `price`, `expires_at`, `inserted`, `state`) VALUES (" - << playerId << ',' << type << ',' << itemId << ',' << amount << ',' << price << ',' - << timestamp << ',' << time(nullptr) << ',' << state << ')'; - g_databaseTasks.addTask(query.str()); -} - -bool IOMarket::moveOfferToHistory(uint32_t offerId, MarketOfferState_t state) -{ - const int32_t marketOfferDuration = g_config.getNumber(ConfigManager::MARKET_OFFER_DURATION); - - Database& db = Database::getInstance(); - - std::ostringstream query; - query << "SELECT `player_id`, `sale`, `itemtype`, `amount`, `price`, `created` FROM `market_offers` WHERE `id` = " << offerId; - - DBResult_ptr result = db.storeQuery(query.str()); - if (!result) { - return false; - } - - query.str(std::string()); - query << "DELETE FROM `market_offers` WHERE `id` = " << offerId; - if (!db.executeQuery(query.str())) { - return false; - } - - appendHistory(result->getNumber("player_id"), static_cast(result->getNumber("sale")), result->getNumber("itemtype"), result->getNumber("amount"), result->getNumber("price"), result->getNumber("created") + marketOfferDuration, state); - return true; -} - -void IOMarket::updateStatistics() -{ - std::ostringstream query; - query << "SELECT `sale` AS `sale`, `itemtype` AS `itemtype`, COUNT(`price`) AS `num`, MIN(`price`) AS `min`, MAX(`price`) AS `max`, SUM(`price`) AS `sum` FROM `market_history` WHERE `state` = " << OFFERSTATE_ACCEPTED << " GROUP BY `itemtype`, `sale`"; - DBResult_ptr result = Database::getInstance().storeQuery(query.str()); - if (!result) { - return; - } - - do { - MarketStatistics* statistics; - if (result->getNumber("sale") == MARKETACTION_BUY) { - statistics = &purchaseStatistics[result->getNumber("itemtype")]; - } else { - statistics = &saleStatistics[result->getNumber("itemtype")]; - } - - statistics->numTransactions = result->getNumber("num"); - statistics->lowestPrice = result->getNumber("min"); - statistics->totalPrice = result->getNumber("sum"); - statistics->highestPrice = result->getNumber("max"); - } while (result->next()); -} - -MarketStatistics* IOMarket::getPurchaseStatistics(uint16_t itemId) -{ - auto it = purchaseStatistics.find(itemId); - if (it == purchaseStatistics.end()) { - return nullptr; - } - return &it->second; -} - -MarketStatistics* IOMarket::getSaleStatistics(uint16_t itemId) -{ - auto it = saleStatistics.find(itemId); - if (it == saleStatistics.end()) { - return nullptr; - } - return &it->second; -} diff --git a/src/iomarket.h b/src/iomarket.h deleted file mode 100644 index 3491e6f..0000000 --- a/src/iomarket.h +++ /dev/null @@ -1,63 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_IOMARKET_H_B981E52C218C42D3B9EF726EBF0E92C9 -#define FS_IOMARKET_H_B981E52C218C42D3B9EF726EBF0E92C9 - -#include "enums.h" -#include "database.h" - -class IOMarket -{ - public: - static IOMarket& getInstance() { - static IOMarket instance; - return instance; - } - - static MarketOfferList getActiveOffers(MarketAction_t action, uint16_t itemId); - static MarketOfferList getOwnOffers(MarketAction_t action, uint32_t playerId); - static HistoryMarketOfferList getOwnHistory(MarketAction_t action, uint32_t playerId); - - static void processExpiredOffers(DBResult_ptr result, bool); - static void checkExpiredOffers(); - - static uint32_t getPlayerOfferCount(uint32_t playerId); - static MarketOfferEx getOfferByCounter(uint32_t timestamp, uint16_t counter); - - static void createOffer(uint32_t playerId, MarketAction_t action, uint32_t itemId, uint16_t amount, uint32_t price, bool anonymous); - static void acceptOffer(uint32_t offerId, uint16_t amount); - static void deleteOffer(uint32_t offerId); - - static void appendHistory(uint32_t playerId, MarketAction_t type, uint16_t itemId, uint16_t amount, uint32_t price, time_t timestamp, MarketOfferState_t state); - static bool moveOfferToHistory(uint32_t offerId, MarketOfferState_t state); - - void updateStatistics(); - - MarketStatistics* getPurchaseStatistics(uint16_t itemId); - MarketStatistics* getSaleStatistics(uint16_t itemId); - - private: - IOMarket() = default; - - std::map purchaseStatistics; - std::map saleStatistics; -}; - -#endif diff --git a/src/item.cpp b/src/item.cpp index 69fa85c..acf2f1b 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,7 +22,6 @@ #include "item.h" #include "container.h" #include "teleport.h" -#include "trashholder.h" #include "mailbox.h" #include "house.h" #include "game.h" @@ -53,7 +52,7 @@ Item* Item::CreateItem(const uint16_t type, uint16_t count /*= 0*/) if (it.id != 0) { if (it.isDepot()) { newItem = new DepotLocker(type); - } else if (it.isContainer()) { + } else if (it.isContainer() || it.isChest()) { newItem = new Container(type); } else if (it.isTeleport()) { newItem = new Teleport(type); @@ -61,24 +60,10 @@ Item* Item::CreateItem(const uint16_t type, uint16_t count /*= 0*/) newItem = new MagicField(type); } else if (it.isDoor()) { newItem = new Door(type); - } else if (it.isTrashHolder()) { - newItem = new TrashHolder(type); } else if (it.isMailbox()) { newItem = new Mailbox(type); } else if (it.isBed()) { newItem = new BedItem(type); - } else if (it.id >= 2210 && it.id <= 2212) { - newItem = new Item(type - 3, count); - } else if (it.id == 2215 || it.id == 2216) { - newItem = new Item(type - 2, count); - } else if (it.id >= 2202 && it.id <= 2206) { - newItem = new Item(type - 37, count); - } else if (it.id == 2640) { - newItem = new Item(6132, count); - } else if (it.id == 6301) { - newItem = new Item(6300, count); - } else if (it.id == 18528) { - newItem = new Item(18408, count); } else { newItem = new Item(type, count); } @@ -163,6 +148,8 @@ Item::Item(const uint16_t type, uint16_t count /*= 0*/) : } else { setCharges(it.charges); } + } else if (it.isKey()) { + setIntAttr(ITEM_ATTRIBUTE_KEYNUMBER, count); } setDefaultDuration(); @@ -181,11 +168,6 @@ Item* Item::clone() const Item* item = Item::CreateItem(id, count); if (attributes) { item->attributes.reset(new ItemAttributes(*attributes)); - if (item->getDuration() > 0) { - item->incrementReferenceCounter(); - item->setDecaying(DECAYING_TRUE); - g_game.toDecayItems.push_front(item); - } } return item; } @@ -243,10 +225,6 @@ void Item::setDefaultSubtype() void Item::onRemoved() { ScriptEnvironment::removeTempItem(this); - - if (hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { - g_game.removeUniqueItem(getUniqueId()); - } } void Item::setID(uint16_t newid) @@ -392,13 +370,13 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) break; } - case ATTR_UNIQUE_ID: { - uint16_t uniqueId; - if (!propStream.read(uniqueId)) { + case ATTR_MOVEMENT_ID: { + uint16_t movementId; + if (!propStream.read(movementId)) { return ATTR_READ_ERROR; } - setUniqueId(uniqueId); + setMovementID(movementId); break; } @@ -534,16 +512,6 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) break; } - case ATTR_EXTRADEFENSE: { - int32_t extraDefense; - if (!propStream.read(extraDefense)) { - return ATTR_READ_ERROR; - } - - setIntAttr(ITEM_ATTRIBUTE_EXTRADEFENSE, extraDefense); - break; - } - case ATTR_ARMOR: { int32_t armor; if (!propStream.read(armor)) { @@ -554,16 +522,6 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) break; } - case ATTR_HITCHANCE: { - int8_t hitChance; - if (!propStream.read(hitChance)) { - return ATTR_READ_ERROR; - } - - setIntAttr(ITEM_ATTRIBUTE_HITCHANCE, hitChance); - break; - } - case ATTR_SHOOTRANGE: { uint8_t shootRange; if (!propStream.read(shootRange)) { @@ -574,13 +532,68 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) break; } - case ATTR_DECAYTO: { - int32_t decayTo; - if (!propStream.read(decayTo)) { + case ATTR_KEYNUMBER: { + uint16_t keyNumber; + if (!propStream.read(keyNumber)) { return ATTR_READ_ERROR; } - setIntAttr(ITEM_ATTRIBUTE_DECAYTO, decayTo); + setIntAttr(ITEM_ATTRIBUTE_KEYNUMBER, keyNumber); + break; + } + + case ATTR_KEYHOLENUMBER: + { + uint16_t keyHoleNumber; + if (!propStream.read(keyHoleNumber)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER, keyHoleNumber); + break; + } + + case ATTR_DOORLEVEL: + { + uint16_t doorLevel; + if (!propStream.read(doorLevel)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_DOORLEVEL, doorLevel); + break; + } + + case ATTR_DOORQUESTNUMBER: + { + uint16_t doorQuestNumber; + if (!propStream.read(doorQuestNumber)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER, doorQuestNumber); + break; + } + + case ATTR_DOORQUESTVALUE: + { + uint16_t doorQuestValue; + if (!propStream.read(doorQuestValue)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE, doorQuestValue); + break; + } + + case ATTR_CHESTQUESTNUMBER: + { + uint16_t chestQuestNumber; + if (!propStream.read(chestQuestNumber)) { + return ATTR_READ_ERROR; + } + + setIntAttr(ITEM_ATTRIBUTE_CHESTQUESTNUMBER, chestQuestNumber); break; } @@ -632,30 +645,6 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) return ATTR_READ_ERROR; } - case ATTR_CUSTOM_ATTRIBUTES: { - uint64_t size; - if (!propStream.read(size)) { - return ATTR_READ_ERROR; - } - - for (uint64_t i = 0; i < size; i++) { - // Unserialize key type and value - std::string key; - if (!propStream.readString(key)) { - return ATTR_READ_ERROR; - }; - - // Unserialize value type and value - ItemAttributes::CustomAttribute val; - if (!val.unserialize(propStream)) { - return ATTR_READ_ERROR; - } - - setCustomAttribute(key, val); - } - break; - } - default: return ATTR_READ_ERROR; } @@ -677,7 +666,7 @@ bool Item::unserializeAttr(PropStream& propStream) return true; } -bool Item::unserializeItemNode(OTB::Loader&, const OTB::Node&, PropStream& propStream) +bool Item::unserializeItemNode(FileLoader&, NODE, PropStream& propStream) { return unserializeAttr(propStream); } @@ -769,42 +758,44 @@ void Item::serializeAttr(PropWriteStream& propWriteStream) const propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DEFENSE)); } - if (hasAttribute(ITEM_ATTRIBUTE_EXTRADEFENSE)) { - propWriteStream.write(ATTR_EXTRADEFENSE); - propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_EXTRADEFENSE)); - } - if (hasAttribute(ITEM_ATTRIBUTE_ARMOR)) { propWriteStream.write(ATTR_ARMOR); propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_ARMOR)); } - if (hasAttribute(ITEM_ATTRIBUTE_HITCHANCE)) { - propWriteStream.write(ATTR_HITCHANCE); - propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_HITCHANCE)); - } - if (hasAttribute(ITEM_ATTRIBUTE_SHOOTRANGE)) { propWriteStream.write(ATTR_SHOOTRANGE); propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_SHOOTRANGE)); } - if (hasAttribute(ITEM_ATTRIBUTE_DECAYTO)) { - propWriteStream.write(ATTR_DECAYTO); - propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DECAYTO)); + if (hasAttribute(ITEM_ATTRIBUTE_KEYNUMBER)) { + propWriteStream.write(ATTR_KEYNUMBER); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_KEYNUMBER)); } - if (hasAttribute(ITEM_ATTRIBUTE_CUSTOM)) { - const ItemAttributes::CustomAttributeMap* customAttrMap = attributes->getCustomAttributeMap(); - propWriteStream.write(ATTR_CUSTOM_ATTRIBUTES); - propWriteStream.write(static_cast(customAttrMap->size())); - for (const auto &entry : *customAttrMap) { - // Serializing key type and value - propWriteStream.writeString(entry.first); + if (hasAttribute(ITEM_ATTRIBUTE_KEYHOLENUMBER)) { + propWriteStream.write(ATTR_KEYHOLENUMBER); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER)); + } - // Serializing value type and value - entry.second.serialize(propWriteStream); - } + if (hasAttribute(ITEM_ATTRIBUTE_DOORLEVEL)) { + propWriteStream.write(ATTR_DOORLEVEL); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DOORLEVEL)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_DOORQUESTNUMBER)) { + propWriteStream.write(ATTR_DOORQUESTNUMBER); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_DOORQUESTVALUE)) { + propWriteStream.write(ATTR_DOORQUESTVALUE); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE)); + } + + if (hasAttribute(ITEM_ATTRIBUTE_CHESTQUESTNUMBER)) { + propWriteStream.write(ATTR_CHESTQUESTNUMBER); + propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_CHESTQUESTNUMBER)); } } @@ -813,17 +804,18 @@ bool Item::hasProperty(ITEMPROPERTY prop) const const ItemType& it = items[id]; switch (prop) { case CONST_PROP_BLOCKSOLID: return it.blockSolid; - case CONST_PROP_MOVEABLE: return it.moveable && !hasAttribute(ITEM_ATTRIBUTE_UNIQUEID); + case CONST_PROP_MOVEABLE: return it.moveable; case CONST_PROP_HASHEIGHT: return it.hasHeight; case CONST_PROP_BLOCKPROJECTILE: return it.blockProjectile; case CONST_PROP_BLOCKPATH: return it.blockPathFind; case CONST_PROP_ISVERTICAL: return it.isVertical; case CONST_PROP_ISHORIZONTAL: return it.isHorizontal; - case CONST_PROP_IMMOVABLEBLOCKSOLID: return it.blockSolid && (!it.moveable || hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)); - case CONST_PROP_IMMOVABLEBLOCKPATH: return it.blockPathFind && (!it.moveable || hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)); - case CONST_PROP_IMMOVABLENOFIELDBLOCKPATH: return !it.isMagicField() && it.blockPathFind && (!it.moveable || hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)); + case CONST_PROP_IMMOVABLEBLOCKSOLID: return it.blockSolid && !it.moveable; + case CONST_PROP_IMMOVABLEBLOCKPATH: return it.blockPathFind && !it.moveable; + case CONST_PROP_IMMOVABLENOFIELDBLOCKPATH: return !it.isMagicField() && it.blockPathFind && !it.moveable; case CONST_PROP_NOFIELDBLOCKPATH: return !it.isMagicField() && it.blockPathFind; case CONST_PROP_SUPPORTHANGABLE: return it.isHorizontal || it.isVertical; + case CONST_PROP_UNLAY: return !it.allowPickupable; default: return false; } } @@ -838,10 +830,8 @@ uint32_t Item::getWeight() const } std::string Item::getDescription(const ItemType& it, int32_t lookDistance, - const Item* item /*= nullptr*/, int32_t subType /*= -1*/, bool addArticle /*= true*/) + const Item* item /*= nullptr*/, int32_t subType /*= -1*/, bool addArticle /*= true*/) { - const std::string* text = nullptr; - std::ostringstream s; s << getNameDescription(it, item, subType, addArticle); @@ -850,567 +840,141 @@ std::string Item::getDescription(const ItemType& it, int32_t lookDistance, } if (it.isRune()) { - if (it.runeLevel > 0 || it.runeMagLevel > 0) { - if (RuneSpell* rune = g_spells->getRuneSpell(it.id)) { - int32_t tmpSubType = subType; - if (item) { - tmpSubType = item->getSubType(); - } - s << ". " << (it.stackable && tmpSubType > 1 ? "They" : "It") << " can only be used by "; + uint32_t charges = std::max(static_cast(1), static_cast(item == nullptr ? it.charges : item->getCharges())); - const VocSpellMap& vocMap = rune->getVocMap(); - std::vector showVocMap; - - // vocations are usually listed with the unpromoted and promoted version, the latter being - // hidden from description, so `total / 2` is most likely the amount of vocations to be shown. - showVocMap.reserve(vocMap.size() / 2); - for (const auto& voc : vocMap) { - if (voc.second) { - showVocMap.push_back(g_vocations.getVocation(voc.first)); - } - } - - if (!showVocMap.empty()) { - auto vocIt = showVocMap.begin(), vocLast = (showVocMap.end() - 1); - while (vocIt != vocLast) { - s << asLowerCaseString((*vocIt)->getVocName()) << "s"; - if (++vocIt == vocLast) { - s << " and "; - } else { - s << ", "; - } - } - s << asLowerCaseString((*vocLast)->getVocName()) << "s"; - } else { - s << "players"; - } - - s << " with"; - - if (it.runeLevel > 0) { - s << " level " << it.runeLevel; - } - - if (it.runeMagLevel > 0) { - if (it.runeLevel > 0) { - s << " and"; - } - - s << " magic level " << it.runeMagLevel; - } - - s << " or higher"; - } + if (it.runeLevel > 0) { + s << " for level " << it.runeLevel; } + + if (it.runeLevel > 0) { + s << " and"; + } + + s << " for magic level " << it.runeMagLevel; + s << ". It's an \"" << it.runeSpellName << "\"-spell (" << charges << "x). "; + } else if (it.isDoor() && item) { + if (item->hasAttribute(ITEM_ATTRIBUTE_DOORLEVEL)) { + s << " for level " << item->getIntAttr(ITEM_ATTRIBUTE_DOORLEVEL); + } + s << "."; } else if (it.weaponType != WEAPON_NONE) { - bool begin = true; if (it.weaponType == WEAPON_DISTANCE && it.ammoType != AMMO_NONE) { - s << " (Range:" << static_cast(item ? item->getShootRange() : it.shootRange); + if (it.attack != 0) { + s << ", Atk" << std::showpos << it.attack << std::noshowpos; + } + } else if (it.weaponType != WEAPON_AMMO && it.weaponType != WEAPON_WAND && (it.attack != 0 || it.defense != 0)) { + s << " ("; + if (it.attack != 0) { + s << "Atk:" << static_cast(it.attack); + } - int32_t attack; - int8_t hitChance; - if (item) { - attack = item->getAttack(); - hitChance = item->getHitChance(); + if (it.defense != 0) { + if (it.attack != 0) + s << " "; + + s << "Def:" << static_cast(it.defense); + } + + s << ")"; + } + s << "."; + } else if (it.armor != 0) { + if (it.charges > 0) { + if (subType > 1) { + s << " that has " << static_cast(subType) << " charges left"; } else { - attack = it.attack; - hitChance = it.hitChance; - } - - if (attack != 0) { - s << ", Atk" << std::showpos << attack << std::noshowpos; - } - - if (hitChance != 0) { - s << ", Hit%" << std::showpos << static_cast(hitChance) << std::noshowpos; - } - - begin = false; - } else if (it.weaponType != WEAPON_AMMO) { - - int32_t attack, defense, extraDefense; - if (item) { - attack = item->getAttack(); - defense = item->getDefense(); - extraDefense = item->getExtraDefense(); - } else { - attack = it.attack; - defense = it.defense; - extraDefense = it.extraDefense; - } - - if (attack != 0) { - begin = false; - s << " (Atk:" << attack; - - if (it.abilities && it.abilities->elementType != COMBAT_NONE && it.abilities->elementDamage != 0) { - s << " physical + " << it.abilities->elementDamage << ' ' << getCombatName(it.abilities->elementType); - } - } - - if (defense != 0 || extraDefense != 0) { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "Def:" << defense; - if (extraDefense != 0) { - s << ' ' << std::showpos << extraDefense << std::noshowpos; - } + s << " that has " << it.charges << " charge left"; } } - if (it.abilities) { - for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; i++) { - if (!it.abilities->skills[i]) { - continue; - } + s << " (Arm:" << it.armor << ")."; + } else if (it.isFluidContainer()) { + if (item && item->getFluidType() != 0) { + s << " of " << items[item->getFluidType()].name << "."; + } else { + s << ". It is empty."; + } + } else if (it.isSplash()) { + s << " of "; + if (item && item->getFluidType() != 0) { + s << items[item->getFluidType()].name; + } else { + s << items[1].name; + } + s << "."; + } else if (it.isContainer() && !it.isChest()) { + s << " (Vol:" << static_cast(it.maxItems) << ")."; + } else if (it.isKey()) { + if (item) { + s << " (Key:" << static_cast(item->getIntAttr(ITEM_ATTRIBUTE_KEYNUMBER)) << ")."; + } else { + s << " (Key:0)."; + } + } else if (it.allowDistRead) { + s << "."; + s << std::endl; - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << getSkillName(i) << ' ' << std::showpos << it.abilities->skills[i] << std::noshowpos; - } - - for (uint8_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; i++) { - if (!it.abilities->specialSkills[i]) { - continue; - } - - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << getSpecialSkillName(i) << ' ' << std::showpos << it.abilities->specialSkills[i] << '%' << std::noshowpos; - } - - if (it.abilities->stats[STAT_MAGICPOINTS]) { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "magic level " << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos; - } - - int16_t show = it.abilities->absorbPercent[0]; - if (show != 0) { - for (size_t i = 1; i < COMBAT_COUNT; ++i) { - if (it.abilities->absorbPercent[i] != show) { - show = 0; - break; + if (item && item->getText() != "") { + if (lookDistance <= 4) { + const std::string& writer = item->getWriter(); + if (!writer.empty()) { + s << writer << " wrote"; + time_t date = item->getDate(); + if (date != 0) { + s << " on " << formatDateShort(date); } + s << ": "; + } else { + s << "You read: "; } - } - - if (show == 0) { - bool tmp = true; - - for (size_t i = 0; i < COMBAT_COUNT; ++i) { - if (it.abilities->absorbPercent[i] == 0) { - continue; - } - - if (tmp) { - tmp = false; - - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "protection "; - } else { - s << ", "; - } - - s << getCombatName(indexToCombatType(i)) << ' ' << std::showpos << it.abilities->absorbPercent[i] << std::noshowpos << '%'; - } + s << item->getText(); } else { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "protection all " << std::showpos << show << std::noshowpos << '%'; - } - - show = it.abilities->fieldAbsorbPercent[0]; - if (show != 0) { - for (size_t i = 1; i < COMBAT_COUNT; ++i) { - if (it.abilities->absorbPercent[i] != show) { - show = 0; - break; - } - } - } - - if (show == 0) { - bool tmp = true; - - for (size_t i = 0; i < COMBAT_COUNT; ++i) { - if (it.abilities->fieldAbsorbPercent[i] == 0) { - continue; - } - - if (tmp) { - tmp = false; - - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "protection "; - } else { - s << ", "; - } - - s << getCombatName(indexToCombatType(i)) << " field " << std::showpos << it.abilities->fieldAbsorbPercent[i] << std::noshowpos << '%'; - } - } else { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "protection all fields " << std::showpos << show << std::noshowpos << '%'; - } - - if (it.abilities->speed) { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "speed " << std::showpos << (it.abilities->speed >> 1) << std::noshowpos; - } - } - - if (!begin) { - s << ')'; - } - } else if (it.armor != 0 || (item && item->getArmor() != 0) || it.showAttributes) { - bool begin = true; - - int32_t armor = (item ? item->getArmor() : it.armor); - if (armor != 0) { - s << " (Arm:" << armor; - begin = false; - } - - if (it.abilities) { - for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; i++) { - if (!it.abilities->skills[i]) { - continue; - } - - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << getSkillName(i) << ' ' << std::showpos << it.abilities->skills[i] << std::noshowpos; - } - - if (it.abilities->stats[STAT_MAGICPOINTS]) { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "magic level " << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos; - } - - int16_t show = it.abilities->absorbPercent[0]; - if (show != 0) { - for (size_t i = 1; i < COMBAT_COUNT; ++i) { - if (it.abilities->absorbPercent[i] != show) { - show = 0; - break; - } - } - } - - if (!show) { - bool protectionBegin = true; - for (size_t i = 0; i < COMBAT_COUNT; ++i) { - if (it.abilities->absorbPercent[i] == 0) { - continue; - } - - if (protectionBegin) { - protectionBegin = false; - - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "protection "; - } else { - s << ", "; - } - - s << getCombatName(indexToCombatType(i)) << ' ' << std::showpos << it.abilities->absorbPercent[i] << std::noshowpos << '%'; - } - } else { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "protection all " << std::showpos << show << std::noshowpos << '%'; - } - - show = it.abilities->fieldAbsorbPercent[0]; - if (show != 0) { - for (size_t i = 1; i < COMBAT_COUNT; ++i) { - if (it.abilities->absorbPercent[i] != show) { - show = 0; - break; - } - } - } - - if (!show) { - bool tmp = true; - - for (size_t i = 0; i < COMBAT_COUNT; ++i) { - if (it.abilities->fieldAbsorbPercent[i] == 0) { - continue; - } - - if (tmp) { - tmp = false; - - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "protection "; - } else { - s << ", "; - } - - s << getCombatName(indexToCombatType(i)) << " field " << std::showpos << it.abilities->fieldAbsorbPercent[i] << std::noshowpos << '%'; - } - } else { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "protection all fields " << std::showpos << show << std::noshowpos << '%'; - } - - if (it.abilities->speed) { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "speed " << std::showpos << (it.abilities->speed >> 1) << std::noshowpos; - } - } - - if (!begin) { - s << ')'; - } - } else if (it.isContainer() || (item && item->getContainer())) { - uint32_t volume = 0; - if (!item || !item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { - if (it.isContainer()) { - volume = it.maxItems; - } else { - volume = item->getContainer()->capacity(); - } - } - - if (volume != 0) { - s << " (Vol:" << volume << ')'; - } - } else { - bool found = true; - - if (it.abilities) { - if (it.abilities->speed > 0) { - s << " (speed " << std::showpos << (it.abilities->speed / 2) << std::noshowpos << ')'; - } else if (hasBitSet(CONDITION_DRUNK, it.abilities->conditionSuppressions)) { - s << " (hard drinking)"; - } else if (it.abilities->invisible) { - s << " (invisibility)"; - } else if (it.abilities->regeneration) { - s << " (faster regeneration)"; - } else if (it.abilities->manaShield) { - s << " (mana shield)"; - } else { - found = false; + s << "You are too far away to read it."; } } else { - found = false; + s << "Nothing is written on it."; } - - if (!found) { - if (it.isKey()) { - s << " (Key:" << std::setfill('0') << std::setw(4) << (item ? item->getActionId() : 0) << ')'; - } else if (it.isFluidContainer()) { - if (subType > 0) { - const std::string& itemName = items[subType].name; - s << " of " << (!itemName.empty() ? itemName : "unknown"); - } else { - s << ". It is empty"; - } - } else if (it.isSplash()) { - s << " of "; - - if (subType > 0 && !items[subType].name.empty()) { - s << items[subType].name; - } else { - s << "unknown"; - } - } else if (it.allowDistRead && (it.id < 7369 || it.id > 7371)) { - s << ".\n"; - - if (lookDistance <= 4) { - if (item) { - text = &item->getText(); - if (!text->empty()) { - const std::string& writer = item->getWriter(); - if (!writer.empty()) { - s << writer << " wrote"; - time_t date = item->getDate(); - if (date != 0) { - s << " on " << formatDateShort(date); - } - s << ": "; - } else { - s << "You read: "; - } - s << *text; - } else { - s << "Nothing is written on it"; - } - } else { - s << "Nothing is written on it"; - } - } else { - s << "You are too far away to read it"; - } - } else if (it.levelDoor != 0 && item) { - uint16_t actionId = item->getActionId(); - if (actionId >= it.levelDoor) { - s << " for level " << (actionId - it.levelDoor); - } - } + } else if (it.charges > 0) { + uint32_t charges = (item == nullptr ? it.charges : item->getCharges()); + if (charges > 1) { + s << " that has " << static_cast(charges) << " charges left."; + } else { + s << " that has 1 charge left."; } - } - - if (it.showCharges) { - s << " that has " << subType << " charge" << (subType != 1 ? "s" : "") << " left"; - } - - if (it.showDuration) { + } else if (it.showDuration) { if (item && item->hasAttribute(ITEM_ATTRIBUTE_DURATION)) { - uint32_t duration = item->getDuration() / 1000; - s << " that will expire in "; + int32_t duration = item->getDuration() / 1000; + s << " that has energy for "; - if (duration >= 86400) { - uint16_t days = duration / 86400; - uint16_t hours = (duration % 86400) / 3600; - s << days << " day" << (days != 1 ? "s" : ""); - - if (hours > 0) { - s << " and " << hours << " hour" << (hours != 1 ? "s" : ""); - } - } else if (duration >= 3600) { - uint16_t hours = duration / 3600; - uint16_t minutes = (duration % 3600) / 60; - s << hours << " hour" << (hours != 1 ? "s" : ""); - - if (minutes > 0) { - s << " and " << minutes << " minute" << (minutes != 1 ? "s" : ""); - } - } else if (duration >= 60) { - uint16_t minutes = duration / 60; - s << minutes << " minute" << (minutes != 1 ? "s" : ""); - uint16_t seconds = duration % 60; - - if (seconds > 0) { - s << " and " << seconds << " second" << (seconds != 1 ? "s" : ""); - } + if (duration >= 120) { + s << duration / 60 << " minutes left."; + } else if (duration > 60) { + s << "1 minute left."; } else { - s << duration << " second" << (duration != 1 ? "s" : ""); + s << "less than a minute left."; } } else { - s << " that is brand-new"; + s << " that is brand-new."; } - } - - if (!it.allowDistRead || (it.id >= 7369 && it.id <= 7371)) { - s << '.'; } else { - if (!text && item) { - text = &item->getText(); - } - - if (!text || text->empty()) { - s << '.'; - } + s << "."; } if (it.wieldInfo != 0) { - s << "\nIt can only be wielded properly by "; + s << std::endl << "It can only be wielded properly by "; if (it.wieldInfo & WIELDINFO_PREMIUM) { s << "premium "; } - if (!it.vocationString.empty()) { + if (it.wieldInfo & WIELDINFO_VOCREQ) { s << it.vocationString; } else { s << "players"; } if (it.wieldInfo & WIELDINFO_LEVEL) { - s << " of level " << it.minReqLevel << " or higher"; + s << " of level " << static_cast(it.minReqLevel) << " or higher"; } if (it.wieldInfo & WIELDINFO_MAGLV) { @@ -1420,43 +984,25 @@ std::string Item::getDescription(const ItemType& it, int32_t lookDistance, s << " of"; } - s << " magic level " << it.minReqMagicLevel << " or higher"; + s << " magic level " << static_cast(it.minReqMagicLevel) << " or higher"; } - s << '.'; + s << "."; } - if (lookDistance <= 1) { - if (item) { - const uint32_t weight = item->getWeight(); - if (weight != 0 && it.pickupable) { - s << '\n' << getWeightDescription(it, weight, item->getItemCount()); - } - } else if (it.weight != 0 && it.pickupable) { - s << '\n' << getWeightDescription(it, it.weight); + if (lookDistance <= 1 && !it.isChest() && it.pickupable) { + double weight = (item == nullptr ? it.weight : item->getWeight()); + if (weight > 0) { + s << std::endl << getWeightDescription(it, weight); } } - if (item) { - const std::string& specialDescription = item->getSpecialDescription(); - if (!specialDescription.empty()) { - s << '\n' << specialDescription; - } else if (lookDistance <= 1 && !it.description.empty()) { - s << '\n' << it.description; - } - } else if (lookDistance <= 1 && !it.description.empty()) { - s << '\n' << it.description; + if (item && item->getSpecialDescription() != "") { + s << std::endl << item->getSpecialDescription().c_str(); + } else if (it.description.length() && lookDistance <= 1) { + s << std::endl << it.description << "."; } - if (it.allowDistRead && it.id >= 7369 && it.id <= 7371) { - if (!text && item) { - text = &item->getText(); - } - - if (text && !text->empty()) { - s << '\n' << *text; - } - } return s.str(); } @@ -1542,17 +1088,6 @@ std::string Item::getWeightDescription() const return getWeightDescription(weight); } -void Item::setUniqueId(uint16_t n) -{ - if (hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { - return; - } - - if (g_game.addUniqueItem(n, this)) { - getAttributes()->setUniqueId(n); - } -} - bool Item::canDecay() const { if (isRemoved()) { @@ -1560,11 +1095,7 @@ bool Item::canDecay() const } const ItemType& it = Item::items[id]; - if (getDecayTo() < 0 || it.decayTime == 0) { - return false; - } - - if (hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { + if (it.decayTo < 0 || it.decayTime == 0) { return false; } @@ -1588,16 +1119,14 @@ uint32_t Item::getWorth() const } } -LightInfo Item::getLightInfo() const +void Item::getLight(LightInfo& lightInfo) const { const ItemType& it = items[id]; - return {it.lightLevel, it.lightColor}; + lightInfo.color = it.lightColor; + lightInfo.level = it.lightLevel; } std::string ItemAttributes::emptyString; -int64_t ItemAttributes::emptyInt; -double ItemAttributes::emptyDouble; -bool ItemAttributes::emptyBool; const std::string& ItemAttributes::getStrAttr(itemAttrTypes type) const { @@ -1711,63 +1240,3 @@ void Item::startDecaying() { g_game.startDecay(this); } - -bool Item::hasMarketAttributes() const -{ - if (attributes == nullptr) { - return true; - } - - for (const auto& attr : attributes->getList()) { - if (attr.type == ITEM_ATTRIBUTE_CHARGES) { - uint16_t charges = static_cast(attr.value.integer); - if (charges != items[id].charges) { - return false; - } - } else if (attr.type == ITEM_ATTRIBUTE_DURATION) { - uint32_t duration = static_cast(attr.value.integer); - if (duration != getDefaultDuration()) { - return false; - } - } else { - return false; - } - } - return true; -} - -template<> -const std::string& ItemAttributes::CustomAttribute::get() { - if (value.type() == typeid(std::string)) { - return boost::get(value); - } - - return emptyString; -} - -template<> -const int64_t& ItemAttributes::CustomAttribute::get() { - if (value.type() == typeid(int64_t)) { - return boost::get(value); - } - - return emptyInt; -} - -template<> -const double& ItemAttributes::CustomAttribute::get() { - if (value.type() == typeid(double)) { - return boost::get(value); - } - - return emptyDouble; -} - -template<> -const bool& ItemAttributes::CustomAttribute::get() { - if (value.type() == typeid(bool)) { - return boost::get(value); - } - - return emptyBool; -} diff --git a/src/item.h b/src/item.h index edbda91..87b2a04 100644 --- a/src/item.h +++ b/src/item.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,12 +23,7 @@ #include "cylinder.h" #include "thing.h" #include "items.h" -#include "luascript.h" -#include "tools.h" -#include -#include -#include #include class Creature; @@ -36,8 +31,8 @@ class Player; class Container; class Depot; class Teleport; -class TrashHolder; class Mailbox; +class DepotLocker; class Door; class MagicField; class BedItem; @@ -55,6 +50,7 @@ enum ITEMPROPERTY { CONST_PROP_IMMOVABLENOFIELDBLOCKPATH, CONST_PROP_NOFIELDBLOCKPATH, CONST_PROP_SUPPORTHANGABLE, + CONST_PROP_UNLAY, }; enum TradeEvents_t { @@ -73,7 +69,7 @@ enum AttrTypes_t { //ATTR_EXT_FILE = 2, ATTR_TILE_FLAGS = 3, ATTR_ACTION_ID = 4, - ATTR_UNIQUE_ID = 5, + ATTR_MOVEMENT_ID = 5, ATTR_TEXT = 6, ATTR_DESC = 7, ATTR_TELE_DEST = 8, @@ -91,19 +87,22 @@ enum AttrTypes_t { ATTR_SLEEPERGUID = 20, ATTR_SLEEPSTART = 21, ATTR_CHARGES = 22, - ATTR_CONTAINER_ITEMS = 23, - ATTR_NAME = 24, - ATTR_ARTICLE = 25, - ATTR_PLURALNAME = 26, - ATTR_WEIGHT = 27, - ATTR_ATTACK = 28, - ATTR_DEFENSE = 29, - ATTR_EXTRADEFENSE = 30, - ATTR_ARMOR = 31, - ATTR_HITCHANCE = 32, - ATTR_SHOOTRANGE = 33, - ATTR_CUSTOM_ATTRIBUTES = 34, - ATTR_DECAYTO = 35 + 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 { @@ -161,11 +160,53 @@ class ItemAttributes return static_cast(getIntAttr(ITEM_ATTRIBUTE_ACTIONID)); } - void setUniqueId(uint16_t n) { - setIntAttr(ITEM_ATTRIBUTE_UNIQUEID, n); + void setMovementID(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_MOVEMENTID, n); } - uint16_t getUniqueId() const { - return static_cast(getIntAttr(ITEM_ATTRIBUTE_UNIQUEID)); + uint16_t getMovementId() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_MOVEMENTID)); + } + + void setKeyNumber(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_KEYNUMBER, n); + } + uint16_t getKeyNumber() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_KEYNUMBER)); + } + + void setKeyHoleNumber(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER, n); + } + uint16_t getKeyHoleNumber() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER)); + } + + void setDoorQuestNumber(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER, n); + } + uint16_t getDoorQuestNumber() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER)); + } + + void setDoorQuestValue(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE, n); + } + uint16_t getDoorQuestValue() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE)); + } + + void setDoorLevel(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_DOORLEVEL, n); + } + uint16_t getDoorLevel() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_DOORLEVEL)); + } + + void setChestQuestNumber(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_CHESTQUESTNUMBER, n); + } + uint16_t getChestQuestNumber() const { + return static_cast(getIntAttr(ITEM_ATTRIBUTE_CHESTQUESTNUMBER)); } void setCharges(uint16_t n) { @@ -189,13 +230,6 @@ class ItemAttributes return getIntAttr(ITEM_ATTRIBUTE_OWNER); } - void setCorpseOwner(uint32_t corpseOwner) { - setIntAttr(ITEM_ATTRIBUTE_CORPSEOWNER, corpseOwner); - } - uint32_t getCorpseOwner() const { - return getIntAttr(ITEM_ATTRIBUTE_CORPSEOWNER); - } - void setDuration(int32_t time) { setIntAttr(ITEM_ATTRIBUTE_DURATION, time); } @@ -213,149 +247,19 @@ class ItemAttributes return static_cast(getIntAttr(ITEM_ATTRIBUTE_DECAYSTATE)); } - struct CustomAttribute - { - typedef boost::variant VariantAttribute; - VariantAttribute value; - - CustomAttribute() : value(boost::blank()) {} - - template - explicit CustomAttribute(const T& v) : value(v) {} - - template - void set(const T& v) { - value = v; - } - - template - const T& get(); - - struct PushLuaVisitor : public boost::static_visitor<> { - lua_State* L; - - explicit PushLuaVisitor(lua_State* L) : boost::static_visitor<>(), L(L) {} - - void operator()(const boost::blank&) const { - lua_pushnil(L); - } - - void operator()(const std::string& v) const { - LuaScriptInterface::pushString(L, v); - } - - void operator()(bool v) const { - LuaScriptInterface::pushBoolean(L, v); - } - - void operator()(const int64_t& v) const { - lua_pushnumber(L, v); - } - - void operator()(const double& v) const { - lua_pushnumber(L, v); - } - }; - - void pushToLua(lua_State* L) const { - boost::apply_visitor(PushLuaVisitor(L), value); - } - - struct SerializeVisitor : public boost::static_visitor<> { - PropWriteStream& propWriteStream; - - explicit SerializeVisitor(PropWriteStream& propWriteStream) : boost::static_visitor<>(), propWriteStream(propWriteStream) {} - - void operator()(const boost::blank&) const { - } - - void operator()(const std::string& v) const { - propWriteStream.writeString(v); - } - - template - void operator()(const T& v) const { - propWriteStream.write(v); - } - }; - - void serialize(PropWriteStream& propWriteStream) const { - propWriteStream.write(static_cast(value.which())); - boost::apply_visitor(SerializeVisitor(propWriteStream), value); - } - - bool unserialize(PropStream& propStream) { - // This is hard coded so it's not general, depends on the position of the variants. - uint8_t pos; - if (!propStream.read(pos)) { - return false; - } - - switch (pos) { - case 1: { // std::string - std::string tmp; - if (!propStream.readString(tmp)) { - return false; - } - value = tmp; - break; - } - - case 2: { // int64_t - int64_t tmp; - if (!propStream.read(tmp)) { - return false; - } - value = tmp; - break; - } - - case 3: { // double - double tmp; - if (!propStream.read(tmp)) { - return false; - } - value = tmp; - break; - } - - case 4: { // bool - bool tmp; - if (!propStream.read(tmp)) { - return false; - } - value = tmp; - break; - } - - default: { - value = boost::blank(); - return false; - } - } - return true; - } - }; - - private: - bool hasAttribute(itemAttrTypes type) const { + protected: + inline bool hasAttribute(itemAttrTypes type) const { return (type & attributeBits) != 0; } void removeAttribute(itemAttrTypes type); static std::string emptyString; - static int64_t emptyInt; - static double emptyDouble; - static bool emptyBool; - - typedef std::unordered_map CustomAttributeMap; struct Attribute { union { int64_t integer; std::string* string; - CustomAttributeMap* custom; } value; itemAttrTypes type; @@ -368,8 +272,6 @@ class ItemAttributes value.integer = i.value.integer; } else if (ItemAttributes::isStrAttrType(type)) { value.string = new std::string(*i.value.string); - } else if (ItemAttributes::isCustomAttrType(type)) { - value.custom = new CustomAttributeMap(*i.value.custom); } else { memset(&value, 0, sizeof(value)); } @@ -381,8 +283,6 @@ class ItemAttributes ~Attribute() { if (ItemAttributes::isStrAttrType(type)) { delete value.string; - } else if (ItemAttributes::isCustomAttrType(type)) { - delete value.custom; } } Attribute& operator=(Attribute other) { @@ -393,8 +293,6 @@ class ItemAttributes if (this != &other) { if (ItemAttributes::isStrAttrType(type)) { delete value.string; - } else if (ItemAttributes::isCustomAttrType(type)) { - delete value.custom; } value = other.value; @@ -425,94 +323,12 @@ class ItemAttributes const Attribute* getExistingAttr(itemAttrTypes type) const; Attribute& getAttr(itemAttrTypes type); - CustomAttributeMap* getCustomAttributeMap() { - if (!hasAttribute(ITEM_ATTRIBUTE_CUSTOM)) { - return nullptr; - } - - return getAttr(ITEM_ATTRIBUTE_CUSTOM).value.custom; - } - - template - void setCustomAttribute(int64_t key, R value) { - std::string tmp = boost::lexical_cast(key); - setCustomAttribute(tmp, value); - } - - void setCustomAttribute(int64_t key, CustomAttribute& value) { - std::string tmp = boost::lexical_cast(key); - setCustomAttribute(tmp, value); - } - - template - void setCustomAttribute(std::string& key, R value) { - toLowerCaseString(key); - if (hasAttribute(ITEM_ATTRIBUTE_CUSTOM)) { - removeCustomAttribute(key); - } else { - getAttr(ITEM_ATTRIBUTE_CUSTOM).value.custom = new CustomAttributeMap(); - } - getAttr(ITEM_ATTRIBUTE_CUSTOM).value.custom->emplace(key, value); - } - - void setCustomAttribute(std::string& key, CustomAttribute& value) { - toLowerCaseString(key); - if (hasAttribute(ITEM_ATTRIBUTE_CUSTOM)) { - removeCustomAttribute(key); - } else { - getAttr(ITEM_ATTRIBUTE_CUSTOM).value.custom = new CustomAttributeMap(); - } - getAttr(ITEM_ATTRIBUTE_CUSTOM).value.custom->insert(std::make_pair(std::move(key), std::move(value))); - } - - const CustomAttribute* getCustomAttribute(int64_t key) { - std::string tmp = boost::lexical_cast(key); - return getCustomAttribute(tmp); - } - - const CustomAttribute* getCustomAttribute(const std::string& key) { - if (const CustomAttributeMap* customAttrMap = getCustomAttributeMap()) { - auto it = customAttrMap->find(asLowerCaseString(key)); - if (it != customAttrMap->end()) { - return &(it->second); - } - } - return nullptr; - } - - bool removeCustomAttribute(int64_t key) { - std::string tmp = boost::lexical_cast(key); - return removeCustomAttribute(tmp); - } - - bool removeCustomAttribute(const std::string& key) { - if (CustomAttributeMap* customAttrMap = getCustomAttributeMap()) { - auto it = customAttrMap->find(asLowerCaseString(key)); - if (it != customAttrMap->end()) { - customAttrMap->erase(it); - return true; - } - } - return false; - } - - const static uint32_t intAttributeTypes = ITEM_ATTRIBUTE_ACTIONID | ITEM_ATTRIBUTE_UNIQUEID | ITEM_ATTRIBUTE_DATE - | ITEM_ATTRIBUTE_WEIGHT | ITEM_ATTRIBUTE_ATTACK | ITEM_ATTRIBUTE_DEFENSE | ITEM_ATTRIBUTE_EXTRADEFENSE - | ITEM_ATTRIBUTE_ARMOR | ITEM_ATTRIBUTE_HITCHANCE | ITEM_ATTRIBUTE_SHOOTRANGE | ITEM_ATTRIBUTE_OWNER - | ITEM_ATTRIBUTE_DURATION | ITEM_ATTRIBUTE_DECAYSTATE | ITEM_ATTRIBUTE_CORPSEOWNER | ITEM_ATTRIBUTE_CHARGES - | ITEM_ATTRIBUTE_FLUIDTYPE | ITEM_ATTRIBUTE_DOORID | ITEM_ATTRIBUTE_DECAYTO; - const static uint32_t stringAttributeTypes = ITEM_ATTRIBUTE_DESCRIPTION | ITEM_ATTRIBUTE_TEXT | ITEM_ATTRIBUTE_WRITER - | ITEM_ATTRIBUTE_NAME | ITEM_ATTRIBUTE_ARTICLE | ITEM_ATTRIBUTE_PLURALNAME; - public: - static bool isIntAttrType(itemAttrTypes type) { - return (type & intAttributeTypes) == type; + inline static bool isIntAttrType(itemAttrTypes type) { + return (type & 0xFFFFE13) != 0; } - static bool isStrAttrType(itemAttrTypes type) { - return (type & stringAttributeTypes) == type; - } - inline static bool isCustomAttrType(itemAttrTypes type) { - return (type & ITEM_ATTRIBUTE_CUSTOM) == type; + inline static bool isStrAttrType(itemAttrTypes type) { + return (type & 0x1EC) != 0; } const std::forward_list& getList() const { @@ -543,10 +359,10 @@ class Item : virtual public Thing bool equals(const Item* otherItem) const; - Item* getItem() override final { + Item* getItem() final { return this; } - const Item* getItem() const override final { + const Item* getItem() const final { return this; } virtual Teleport* getTeleport() { @@ -555,18 +371,18 @@ class Item : virtual public Thing virtual const Teleport* getTeleport() const { return nullptr; } - virtual TrashHolder* getTrashHolder() { - return nullptr; - } - virtual const TrashHolder* getTrashHolder() const { - return nullptr; - } virtual Mailbox* getMailbox() { return nullptr; } virtual const Mailbox* getMailbox() const { return nullptr; } + virtual DepotLocker* getDepotLocker() { + return nullptr; + } + virtual const DepotLocker* getDepotLocker() const { + return nullptr; + } virtual Door* getDoor() { return nullptr; } @@ -621,31 +437,6 @@ class Item : virtual public Thing return attributes->hasAttribute(type); } - template - void setCustomAttribute(std::string& key, R value) { - getAttributes()->setCustomAttribute(key, value); - } - - void setCustomAttribute(std::string& key, ItemAttributes::CustomAttribute& value) { - getAttributes()->setCustomAttribute(key, value); - } - - const ItemAttributes::CustomAttribute* getCustomAttribute(int64_t key) { - return getAttributes()->getCustomAttribute(key); - } - - const ItemAttributes::CustomAttribute* getCustomAttribute(const std::string& key) { - return getAttributes()->getCustomAttribute(key); - } - - bool removeCustomAttribute(int64_t key) { - return getAttributes()->removeCustomAttribute(key); - } - - bool removeCustomAttribute(const std::string& key) { - return getAttributes()->removeCustomAttribute(key); - } - void setSpecialDescription(const std::string& desc) { setStrAttr(ITEM_ATTRIBUTE_DESCRIPTION, desc); } @@ -684,10 +475,6 @@ class Item : virtual public Thing } void setActionId(uint16_t n) { - if (n < 100) { - n = 100; - } - setIntAttr(ITEM_ATTRIBUTE_ACTIONID, n); } uint16_t getActionId() const { @@ -697,11 +484,14 @@ class Item : virtual public Thing return static_cast(getIntAttr(ITEM_ATTRIBUTE_ACTIONID)); } - uint16_t getUniqueId() const { + void setMovementID(uint16_t n) { + setIntAttr(ITEM_ATTRIBUTE_MOVEMENTID, n); + } + uint16_t getMovementId() const { if (!attributes) { return 0; } - return static_cast(getIntAttr(ITEM_ATTRIBUTE_UNIQUEID)); + return static_cast(getIntAttr(ITEM_ATTRIBUTE_MOVEMENTID)); } void setCharges(uint16_t n) { @@ -767,49 +557,42 @@ class Item : virtual public Thing return static_cast(getIntAttr(ITEM_ATTRIBUTE_DECAYSTATE)); } - void setDecayTo(int32_t decayTo) { - setIntAttr(ITEM_ATTRIBUTE_DECAYTO, decayTo); - } - int32_t getDecayTo() const { - if (hasAttribute(ITEM_ATTRIBUTE_DECAYTO)) { - return getIntAttr(ITEM_ATTRIBUTE_DECAYTO); - } - return items[id].decayTo; - } - 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 override final; + 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(OTB::Loader&, const OTB::Node&, PropStream& propStream); + virtual bool unserializeItemNode(FileLoader& f, NODE node, PropStream& propStream); virtual void serializeAttr(PropWriteStream& propWriteStream) const; - bool isPushable() const override final { + bool isPushable() const final { return isMoveable(); } - int32_t getThrowRange() const override final { + int32_t getThrowRange() const final { return (isPickupable() ? 15 : 2); } uint16_t getID() const { return id; } - uint16_t getClientID() const { - return items[id].clientId; - } 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; } @@ -822,7 +605,28 @@ class Item : virtual public Thing } 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)) { @@ -848,24 +652,15 @@ class Item : virtual public Thing } return items[id].defense; } - int32_t getExtraDefense() const { - if (hasAttribute(ITEM_ATTRIBUTE_EXTRADEFENSE)) { - return getIntAttr(ITEM_ATTRIBUTE_EXTRADEFENSE); - } - return items[id].extraDefense; - } int32_t getSlotPosition() const { return items[id].slotPosition; } - int8_t getHitChance() const { - if (hasAttribute(ITEM_ATTRIBUTE_HITCHANCE)) { - return getIntAttr(ITEM_ATTRIBUTE_HITCHANCE); - } - return items[id].hitChance; + uint16_t getDisguiseId() const { + return items[id].disguiseId; } uint32_t getWorth() const; - LightInfo getLightInfo() const; + void getLight(LightInfo& lightInfo) const; bool hasProperty(ITEMPROPERTY prop) const; bool isBlocking() const { @@ -883,15 +678,15 @@ class Item : virtual public Thing 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 isUseable() const { - return items[id].useable; - } bool isHangable() const { return items[id].isHangable; } @@ -899,10 +694,33 @@ class Item : virtual public Thing const ItemType& it = items[id]; return it.rotatable && it.rotateTo; } - bool hasWalkStack() const { - return items[id].walkStack; + 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; + } + bool isRune() const { + return items[id].isRune(); } - const std::string& getName() const { if (hasAttribute(ITEM_ATTRIBUTE_NAME)) { return getStrAttr(ITEM_ATTRIBUTE_NAME); @@ -930,20 +748,12 @@ class Item : virtual public Thing count = n; } - static uint32_t countByType(const Item* i, int32_t subType) { - if (subType == -1 || subType == i->getSubType()) { - return i->getItemCount(); - } - - return 0; - } + static uint32_t countByType(const Item* i, int32_t subType); void setDefaultSubtype(); uint16_t getSubType() const; void setSubType(uint16_t n); - void setUniqueId(uint16_t n); - void setDefaultDuration() { uint32_t duration = getDefaultDuration(); if (duration != 0) { @@ -966,18 +776,13 @@ class Item : virtual public Thing virtual void startDecaying(); - bool isLoadedFromMap() const { - return loadedFromMap; - } void setLoadedFromMap(bool value) { loadedFromMap = value; } bool isCleanable() const { - return !loadedFromMap && canRemove() && isPickupable() && !hasAttribute(ITEM_ATTRIBUTE_UNIQUEID) && !hasAttribute(ITEM_ATTRIBUTE_ACTIONID); + return !loadedFromMap && canRemove() && isPickupable() && !hasAttribute(ITEM_ATTRIBUTE_ACTIONID); } - bool hasMarketAttributes() const; - std::unique_ptr& getAttributes() { if (!attributes) { attributes.reset(new ItemAttributes()); @@ -994,32 +799,29 @@ class Item : virtual public Thing } } - Cylinder* getParent() const override { + Cylinder* getParent() const { return parent; } - void setParent(Cylinder* cylinder) override { + void setParent(Cylinder* cylinder) { parent = cylinder; } Cylinder* getTopParent(); const Cylinder* getTopParent() const; - Tile* getTile() override; - const Tile* getTile() const override; - bool isRemoved() const override { + Tile* getTile(); + const Tile* getTile() const; + bool isRemoved() const { return !parent || parent->isRemoved(); } protected: - Cylinder* parent = nullptr; - - uint16_t id; // the same id as in ItemType - - private: std::string getWeightDescription(uint32_t weight) const; + Cylinder* parent = nullptr; std::unique_ptr 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; @@ -1027,7 +829,16 @@ class Item : virtual public Thing //Don't add variables here, use the ItemAttribute class. }; -using ItemList = std::list; -using ItemDeque = std::deque; +typedef std::list ItemList; +typedef std::deque ItemDeque; + +inline uint32_t Item::countByType(const Item* i, int32_t subType) +{ + if (subType == -1 || subType == i->getSubType()) { + return i->getItemCount(); + } + + return 0; +} #endif diff --git a/src/itemloader.h b/src/itemloader.h deleted file mode 100644 index 159ee2a..0000000 --- a/src/itemloader.h +++ /dev/null @@ -1,198 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_ITEMLOADER_H_107F1D3EECC94CD0A0F528843010D5D4 -#define FS_ITEMLOADER_H_107F1D3EECC94CD0A0F528843010D5D4 - -#include "fileloader.h" - -enum itemgroup_t { - ITEM_GROUP_NONE, - - ITEM_GROUP_GROUND, - ITEM_GROUP_CONTAINER, - ITEM_GROUP_WEAPON, //deprecated - ITEM_GROUP_AMMUNITION, //deprecated - ITEM_GROUP_ARMOR, //deprecated - ITEM_GROUP_CHARGES, - ITEM_GROUP_TELEPORT, //deprecated - ITEM_GROUP_MAGICFIELD, //deprecated - ITEM_GROUP_WRITEABLE, //deprecated - ITEM_GROUP_KEY, //deprecated - ITEM_GROUP_SPLASH, - ITEM_GROUP_FLUID, - ITEM_GROUP_DOOR, //deprecated - ITEM_GROUP_DEPRECATED, - - ITEM_GROUP_LAST -}; - -/////////OTB specific////////////// -enum clientVersion_t { - CLIENT_VERSION_750 = 1, - CLIENT_VERSION_755 = 2, - CLIENT_VERSION_760 = 3, - CLIENT_VERSION_770 = 3, - CLIENT_VERSION_780 = 4, - CLIENT_VERSION_790 = 5, - CLIENT_VERSION_792 = 6, - CLIENT_VERSION_800 = 7, - CLIENT_VERSION_810 = 8, - CLIENT_VERSION_811 = 9, - CLIENT_VERSION_820 = 10, - CLIENT_VERSION_830 = 11, - CLIENT_VERSION_840 = 12, - CLIENT_VERSION_841 = 13, - CLIENT_VERSION_842 = 14, - CLIENT_VERSION_850 = 15, - CLIENT_VERSION_854_BAD = 16, - CLIENT_VERSION_854 = 17, - CLIENT_VERSION_855 = 18, - CLIENT_VERSION_860_OLD = 19, - CLIENT_VERSION_860 = 20, - CLIENT_VERSION_861 = 21, - CLIENT_VERSION_862 = 22, - CLIENT_VERSION_870 = 23, - CLIENT_VERSION_871 = 24, - CLIENT_VERSION_872 = 25, - CLIENT_VERSION_873 = 26, - CLIENT_VERSION_900 = 27, - CLIENT_VERSION_910 = 28, - CLIENT_VERSION_920 = 29, - CLIENT_VERSION_940 = 30, - CLIENT_VERSION_944_V1 = 31, - CLIENT_VERSION_944_V2 = 32, - CLIENT_VERSION_944_V3 = 33, - CLIENT_VERSION_944_V4 = 34, - CLIENT_VERSION_946 = 35, - CLIENT_VERSION_950 = 36, - CLIENT_VERSION_952 = 37, - CLIENT_VERSION_953 = 38, - CLIENT_VERSION_954 = 39, - CLIENT_VERSION_960 = 40, - CLIENT_VERSION_961 = 41, - CLIENT_VERSION_963 = 42, - CLIENT_VERSION_970 = 43, - CLIENT_VERSION_980 = 44, - CLIENT_VERSION_981 = 45, - CLIENT_VERSION_982 = 46, - CLIENT_VERSION_983 = 47, - CLIENT_VERSION_985 = 48, - CLIENT_VERSION_986 = 49, - CLIENT_VERSION_1010 = 50, - CLIENT_VERSION_1020 = 51, - CLIENT_VERSION_1021 = 52, - CLIENT_VERSION_1030 = 53, - CLIENT_VERSION_1031 = 54, - CLIENT_VERSION_1035 = 55, - CLIENT_VERSION_1076 = 56, - CLIENT_VERSION_1098 = 57, -}; - -enum rootattrib_ { - ROOT_ATTR_VERSION = 0x01, -}; - -enum itemattrib_t { - ITEM_ATTR_FIRST = 0x10, - ITEM_ATTR_SERVERID = ITEM_ATTR_FIRST, - ITEM_ATTR_CLIENTID, - ITEM_ATTR_NAME, - ITEM_ATTR_DESCR, - ITEM_ATTR_SPEED, - ITEM_ATTR_SLOT, - ITEM_ATTR_MAXITEMS, - ITEM_ATTR_WEIGHT, - ITEM_ATTR_WEAPON, - ITEM_ATTR_AMU, - ITEM_ATTR_ARMOR, - ITEM_ATTR_MAGLEVEL, - ITEM_ATTR_MAGFIELDTYPE, - ITEM_ATTR_WRITEABLE, - ITEM_ATTR_ROTATETO, - ITEM_ATTR_DECAY, - ITEM_ATTR_SPRITEHASH, - ITEM_ATTR_MINIMAPCOLOR, - ITEM_ATTR_07, - ITEM_ATTR_08, - ITEM_ATTR_LIGHT, - - //1-byte aligned - ITEM_ATTR_DECAY2, //deprecated - ITEM_ATTR_WEAPON2, //deprecated - ITEM_ATTR_AMU2, //deprecated - ITEM_ATTR_ARMOR2, //deprecated - ITEM_ATTR_WRITEABLE2, //deprecated - ITEM_ATTR_LIGHT2, - ITEM_ATTR_TOPORDER, - ITEM_ATTR_WRITEABLE3, //deprecated - - ITEM_ATTR_WAREID, - - ITEM_ATTR_LAST -}; - -enum itemflags_t { - FLAG_BLOCK_SOLID = 1 << 0, - FLAG_BLOCK_PROJECTILE = 1 << 1, - FLAG_BLOCK_PATHFIND = 1 << 2, - FLAG_HAS_HEIGHT = 1 << 3, - FLAG_USEABLE = 1 << 4, - FLAG_PICKUPABLE = 1 << 5, - FLAG_MOVEABLE = 1 << 6, - FLAG_STACKABLE = 1 << 7, - FLAG_FLOORCHANGEDOWN = 1 << 8, // unused - FLAG_FLOORCHANGENORTH = 1 << 9, // unused - FLAG_FLOORCHANGEEAST = 1 << 10, // unused - FLAG_FLOORCHANGESOUTH = 1 << 11, // unused - FLAG_FLOORCHANGEWEST = 1 << 12, // unused - FLAG_ALWAYSONTOP = 1 << 13, - FLAG_READABLE = 1 << 14, - FLAG_ROTATABLE = 1 << 15, - FLAG_HANGABLE = 1 << 16, - FLAG_VERTICAL = 1 << 17, - FLAG_HORIZONTAL = 1 << 18, - FLAG_CANNOTDECAY = 1 << 19, // unused - FLAG_ALLOWDISTREAD = 1 << 20, - FLAG_UNUSED = 1 << 21, // unused - FLAG_CLIENTCHARGES = 1 << 22, /* deprecated */ - FLAG_LOOKTHROUGH = 1 << 23, - FLAG_ANIMATION = 1 << 24, - FLAG_FULLTILE = 1 << 25, // unused - FLAG_FORCEUSE = 1 << 26, -}; - -//1-byte aligned structs -#pragma pack(1) - -struct VERSIONINFO { - uint32_t dwMajorVersion; - uint32_t dwMinorVersion; - uint32_t dwBuildNumber; - uint8_t CSDVersion[128]; -}; - -struct lightBlock2 { - uint16_t lightLevel; - uint16_t lightColor; -}; - -#pragma pack() -/////////OTB specific////////////// -#endif diff --git a/src/items.cpp b/src/items.cpp index fb5cc3c..8259ff0 100644 --- a/src/items.cpp +++ b/src/items.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,1323 +22,562 @@ #include "items.h" #include "spells.h" #include "movement.h" -#include "weapons.h" +#include "script.h" #include "pugicast.h" extern MoveEvents* g_moveEvents; -extern Weapons* g_weapons; - -const std::unordered_map ItemParseAttributesMap = { - {"type", ITEM_PARSE_TYPE}, - {"description", ITEM_PARSE_DESCRIPTION}, - {"runespellname", ITEM_PARSE_RUNESPELLNAME}, - {"weight", ITEM_PARSE_WEIGHT}, - {"showcount", ITEM_PARSE_SHOWCOUNT}, - {"armor", ITEM_PARSE_ARMOR}, - {"defense", ITEM_PARSE_DEFENSE}, - {"extradef", ITEM_PARSE_EXTRADEF}, - {"attack", ITEM_PARSE_ATTACK}, - {"rotateto", ITEM_PARSE_ROTATETO}, - {"moveable", ITEM_PARSE_MOVEABLE}, - {"movable", ITEM_PARSE_MOVEABLE}, - {"blockprojectile", ITEM_PARSE_BLOCKPROJECTILE}, - {"allowpickupable", ITEM_PARSE_PICKUPABLE}, - {"pickupable", ITEM_PARSE_PICKUPABLE}, - {"forceserialize", ITEM_PARSE_FORCESERIALIZE}, - {"forcesave", ITEM_PARSE_FORCESERIALIZE}, - {"floorchange", ITEM_PARSE_FLOORCHANGE}, - {"corpsetype", ITEM_PARSE_CORPSETYPE}, - {"containersize", ITEM_PARSE_CONTAINERSIZE}, - {"fluidsource", ITEM_PARSE_FLUIDSOURCE}, - {"readable", ITEM_PARSE_READABLE}, - {"writeable", ITEM_PARSE_WRITEABLE}, - {"maxtextlen", ITEM_PARSE_MAXTEXTLEN}, - {"writeonceitemid", ITEM_PARSE_WRITEONCEITEMID}, - {"weapontype", ITEM_PARSE_WEAPONTYPE}, - {"slottype", ITEM_PARSE_SLOTTYPE}, - {"ammotype", ITEM_PARSE_AMMOTYPE}, - {"shoottype", ITEM_PARSE_SHOOTTYPE}, - {"effect", ITEM_PARSE_EFFECT}, - {"range", ITEM_PARSE_RANGE}, - {"stopduration", ITEM_PARSE_STOPDURATION}, - {"decayto", ITEM_PARSE_DECAYTO}, - {"transformequipto", ITEM_PARSE_TRANSFORMEQUIPTO}, - {"transformdeequipto", ITEM_PARSE_TRANSFORMDEEQUIPTO}, - {"duration", ITEM_PARSE_DURATION}, - {"showduration", ITEM_PARSE_SHOWDURATION}, - {"charges", ITEM_PARSE_CHARGES}, - {"showcharges", ITEM_PARSE_SHOWCHARGES}, - {"showattributes", ITEM_PARSE_SHOWATTRIBUTES}, - {"hitchance", ITEM_PARSE_HITCHANCE}, - {"maxhitchance", ITEM_PARSE_MAXHITCHANCE}, - {"invisible", ITEM_PARSE_INVISIBLE}, - {"speed", ITEM_PARSE_SPEED}, - {"healthgain", ITEM_PARSE_HEALTHGAIN}, - {"healthticks", ITEM_PARSE_HEALTHTICKS}, - {"managain", ITEM_PARSE_MANAGAIN}, - {"manaticks", ITEM_PARSE_MANATICKS}, - {"manashield", ITEM_PARSE_MANASHIELD}, - {"skillsword", ITEM_PARSE_SKILLSWORD}, - {"skillaxe", ITEM_PARSE_SKILLAXE}, - {"skillclub", ITEM_PARSE_SKILLCLUB}, - {"skilldist", ITEM_PARSE_SKILLDIST}, - {"skillfish", ITEM_PARSE_SKILLFISH}, - {"skillshield", ITEM_PARSE_SKILLSHIELD}, - {"skillfist", ITEM_PARSE_SKILLFIST}, - {"maxhitpoints", ITEM_PARSE_MAXHITPOINTS}, - {"maxhitpointspercent", ITEM_PARSE_MAXHITPOINTSPERCENT}, - {"maxmanapoints", ITEM_PARSE_MAXMANAPOINTS}, - {"maxmanapointspercent", ITEM_PARSE_MAXMANAPOINTSPERCENT}, - {"magicpoints", ITEM_PARSE_MAGICPOINTS}, - {"magiclevelpoints", ITEM_PARSE_MAGICPOINTS}, - {"magicpointspercent", ITEM_PARSE_MAGICPOINTSPERCENT}, - {"criticalhitchance", ITEM_PARSE_CRITICALHITCHANCE}, - {"criticalhitamount", ITEM_PARSE_CRITICALHITAMOUNT}, - {"lifeleechchance", ITEM_PARSE_LIFELEECHCHANCE}, - {"lifeleechamount", ITEM_PARSE_LIFELEECHAMOUNT}, - {"manaleechchance", ITEM_PARSE_MANALEECHCHANCE}, - {"manaleechamount", ITEM_PARSE_MANALEECHAMOUNT}, - {"fieldabsorbpercentenergy", ITEM_PARSE_FIELDABSORBPERCENTENERGY}, - {"fieldabsorbpercentfire", ITEM_PARSE_FIELDABSORBPERCENTFIRE}, - {"fieldabsorbpercentpoison", ITEM_PARSE_FIELDABSORBPERCENTPOISON}, - {"fieldabsorbpercentearth", ITEM_PARSE_FIELDABSORBPERCENTPOISON}, - {"absorbpercentall", ITEM_PARSE_ABSORBPERCENTALL}, - {"absorbpercentallelements", ITEM_PARSE_ABSORBPERCENTALL}, - {"absorbpercentelements", ITEM_PARSE_ABSORBPERCENTELEMENTS}, - {"absorbpercentmagic", ITEM_PARSE_ABSORBPERCENTMAGIC}, - {"absorbpercentenergy", ITEM_PARSE_ABSORBPERCENTENERGY}, - {"absorbpercentfire", ITEM_PARSE_ABSORBPERCENTFIRE}, - {"absorbpercentpoison", ITEM_PARSE_ABSORBPERCENTPOISON}, - {"absorbpercentearth", ITEM_PARSE_ABSORBPERCENTPOISON}, - {"absorbpercentice", ITEM_PARSE_ABSORBPERCENTICE}, - {"absorbpercentholy", ITEM_PARSE_ABSORBPERCENTHOLY}, - {"absorbpercentdeath", ITEM_PARSE_ABSORBPERCENTDEATH}, - {"absorbpercentlifedrain", ITEM_PARSE_ABSORBPERCENTLIFEDRAIN}, - {"absorbpercentmanadrain", ITEM_PARSE_ABSORBPERCENTMANADRAIN}, - {"absorbpercentdrown", ITEM_PARSE_ABSORBPERCENTDROWN}, - {"absorbpercentphysical", ITEM_PARSE_ABSORBPERCENTPHYSICAL}, - {"absorbpercenthealing", ITEM_PARSE_ABSORBPERCENTHEALING}, - {"absorbpercentundefined", ITEM_PARSE_ABSORBPERCENTUNDEFINED}, - {"suppressdrunk", ITEM_PARSE_SUPPRESSDRUNK}, - {"suppressenergy", ITEM_PARSE_SUPPRESSENERGY}, - {"suppressfire", ITEM_PARSE_SUPPRESSFIRE}, - {"suppresspoison", ITEM_PARSE_SUPPRESSPOISON}, - {"suppressdrown", ITEM_PARSE_SUPPRESSDROWN}, - {"suppressphysical", ITEM_PARSE_SUPPRESSPHYSICAL}, - {"suppressfreeze", ITEM_PARSE_SUPPRESSFREEZE}, - {"suppressdazzle", ITEM_PARSE_SUPPRESSDAZZLE}, - {"suppresscurse", ITEM_PARSE_SUPPRESSCURSE}, - {"field", ITEM_PARSE_FIELD}, - {"replaceable", ITEM_PARSE_REPLACEABLE}, - {"partnerdirection", ITEM_PARSE_PARTNERDIRECTION}, - {"leveldoor", ITEM_PARSE_LEVELDOOR}, - {"maletransformto", ITEM_PARSE_MALETRANSFORMTO}, - {"malesleeper", ITEM_PARSE_MALETRANSFORMTO}, - {"femaletransformto", ITEM_PARSE_FEMALETRANSFORMTO}, - {"femalesleeper", ITEM_PARSE_FEMALETRANSFORMTO}, - {"transformto", ITEM_PARSE_TRANSFORMTO}, - {"destroyto", ITEM_PARSE_DESTROYTO}, - {"elementice", ITEM_PARSE_ELEMENTICE}, - {"elementearth", ITEM_PARSE_ELEMENTEARTH}, - {"elementfire", ITEM_PARSE_ELEMENTFIRE}, - {"elementenergy", ITEM_PARSE_ELEMENTENERGY}, - {"walkstack", ITEM_PARSE_WALKSTACK}, - {"blocking", ITEM_PARSE_BLOCKING}, - {"allowdistread", ITEM_PARSE_ALLOWDISTREAD}, -}; - -const std::unordered_map ItemTypesMap = { - {"key", ITEM_TYPE_KEY}, - {"magicfield", ITEM_TYPE_MAGICFIELD}, - {"container", ITEM_TYPE_CONTAINER}, - {"depot", ITEM_TYPE_DEPOT}, - {"mailbox", ITEM_TYPE_MAILBOX}, - {"trashholder", ITEM_TYPE_TRASHHOLDER}, - {"teleport", ITEM_TYPE_TELEPORT}, - {"door", ITEM_TYPE_DOOR}, - {"bed", ITEM_TYPE_BED}, - {"rune", ITEM_TYPE_RUNE}, -}; - -const std::unordered_map TileStatesMap = { - {"down", TILESTATE_FLOORCHANGE_DOWN}, - {"north", TILESTATE_FLOORCHANGE_NORTH}, - {"south", TILESTATE_FLOORCHANGE_SOUTH}, - {"southalt", TILESTATE_FLOORCHANGE_SOUTH_ALT}, - {"west", TILESTATE_FLOORCHANGE_WEST}, - {"east", TILESTATE_FLOORCHANGE_EAST}, - {"eastalt", TILESTATE_FLOORCHANGE_EAST_ALT}, -}; - -const std::unordered_map RaceTypesMap = { - {"venom", RACE_VENOM}, - {"blood", RACE_BLOOD}, - {"undead", RACE_UNDEAD}, - {"fire", RACE_FIRE}, - {"energy", RACE_ENERGY}, -}; - -const std::unordered_map WeaponTypesMap = { - {"sword", WEAPON_SWORD}, - {"club", WEAPON_CLUB}, - {"axe", WEAPON_AXE}, - {"shield", WEAPON_SHIELD}, - {"distance", WEAPON_DISTANCE}, - {"wand", WEAPON_WAND}, - {"ammunition", WEAPON_AMMO}, -}; - -const std::unordered_map FluidTypesMap = { - {"water", FLUID_WATER}, - {"blood", FLUID_BLOOD}, - {"beer", FLUID_BEER}, - {"slime", FLUID_SLIME}, - {"lemonade", FLUID_LEMONADE}, - {"milk", FLUID_MILK}, - {"mana", FLUID_MANA}, - {"life", FLUID_LIFE}, - {"oil", FLUID_OIL}, - {"urine", FLUID_URINE}, - {"coconut", FLUID_COCONUTMILK}, - {"wine", FLUID_WINE}, - {"mud", FLUID_MUD}, - {"fruitjuice", FLUID_FRUITJUICE}, - {"lava", FLUID_LAVA}, - {"rum", FLUID_RUM}, - {"swamp", FLUID_SWAMP}, - {"tea", FLUID_TEA}, - {"mead", FLUID_MEAD}, -}; - Items::Items() { - items.reserve(30000); - nameToItems.reserve(30000); + items.reserve(6000); + nameToItems.reserve(6000); } void Items::clear() { items.clear(); - reverseItemMap.clear(); nameToItems.clear(); } bool Items::reload() { clear(); - loadFromOtb("data/items/items.otb"); - - if (!loadFromXml()) { + if (!loadItems()) { return false; } g_moveEvents->reload(); - g_weapons->reload(); - g_weapons->loadDefaults(); return true; } -constexpr auto OTBI = OTB::Identifier{{'O','T', 'B', 'I'}}; - -bool Items::loadFromOtb(const std::string& file) +bool Items::loadItems() { - OTB::Loader loader{file, OTBI}; - - auto& root = loader.parseTree(); - - PropStream props; - if (loader.getProps(root, props)) { - //4 byte flags - //attributes - //0x01 = version data - uint32_t flags; - if (!props.read(flags)) { - return false; - } - - uint8_t attr; - if (!props.read(attr)) { - return false; - } - - if (attr == ROOT_ATTR_VERSION) { - uint16_t datalen; - if (!props.read(datalen)) { - return false; - } - - if (datalen != sizeof(VERSIONINFO)) { - return false; - } - - VERSIONINFO vi; - if (!props.read(vi)) { - return false; - } - - majorVersion = vi.dwMajorVersion; //items otb format file version - minorVersion = vi.dwMinorVersion; //client version - buildNumber = vi.dwBuildNumber; //revision - } - } - - if (majorVersion == 0xFFFFFFFF) { - std::cout << "[Warning - Items::loadFromOtb] items.otb using generic client version." << std::endl; - } else if (majorVersion != 3) { - std::cout << "Old version detected, a newer version of items.otb is required." << std::endl; - return false; - } else if (minorVersion < CLIENT_VERSION_1098) { - std::cout << "A newer version of items.otb is required." << std::endl; + ScriptReader script; + if (!script.open("data/items/items.srv")) { return false; } - for (auto& itemNode : root.children) { - PropStream stream; - if (!loader.getProps(itemNode, stream)) { + 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; } - uint32_t flags; - if (!stream.read(flags)) { - return false; - } + identifier = script.getIdentifier(); + script.readSymbol('='); - uint16_t serverId = 0; - uint16_t clientId = 0; - uint16_t speed = 0; - uint16_t wareId = 0; - uint8_t lightLevel = 0; - uint8_t lightColor = 0; - uint8_t alwaysOnTopOrder = 0; + if (identifier == "typeid") { + id = script.readNumber(); + if (id >= items.size()) { + items.resize(id + 1); + } - uint8_t attrib; - while (stream.read(attrib)) { - uint16_t datalen; - if (!stream.read(datalen)) { + if (items[id].id) { + script.error("item type already defined"); return false; } - switch (attrib) { - case ITEM_ATTR_SERVERID: { - if (datalen != sizeof(uint16_t)) { - 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; } - if (!stream.read(serverId)) { + 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 (serverId > 30000 && serverId < 30100) { - serverId -= 30000; - } + if (script.getSpecial() == '}') { break; } - case ITEM_ATTR_CLIENTID: { - if (datalen != sizeof(uint16_t)) { - return false; - } - - if (!stream.read(clientId)) { - return false; - } - break; - } - - case ITEM_ATTR_SPEED: { - if (datalen != sizeof(uint16_t)) { - return false; - } - - if (!stream.read(speed)) { - return false; - } - break; - } - - case ITEM_ATTR_LIGHT2: { - if (datalen != sizeof(lightBlock2)) { - return false; - } - - lightBlock2 lb2; - if (!stream.read(lb2)) { - return false; - } - - lightLevel = static_cast(lb2.lightLevel); - lightColor = static_cast(lb2.lightColor); - break; - } - - case ITEM_ATTR_TOPORDER: { - if (datalen != sizeof(uint8_t)) { - return false; - } - - if (!stream.read(alwaysOnTopOrder)) { - return false; - } - break; - } - - case ITEM_ATTR_WAREID: { - if (datalen != sizeof(uint16_t)) { - return false; - } - - if (!stream.read(wareId)) { - return false; - } - break; - } - - default: { - //skip unknown attributes - if (!stream.skip(datalen)) { - return false; - } - 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; + } - reverseItemMap.emplace(clientId, serverId); + identifier = script.getIdentifier(); + script.readSymbol('='); - // store the found item - if (serverId >= items.size()) { - items.resize(serverId + 1); - } - ItemType& iType = items[serverId]; + 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; - iType.group = static_cast(itemNode.type); - switch (itemNode.type) { - case ITEM_GROUP_CONTAINER: - iType.type = ITEM_TYPE_CONTAINER; - break; - case ITEM_GROUP_DOOR: - //not used - iType.type = ITEM_TYPE_DOOR; - break; - case ITEM_GROUP_MAGICFIELD: - //not used - iType.type = ITEM_TYPE_MAGICFIELD; - break; - case ITEM_GROUP_TELEPORT: - //not used - iType.type = ITEM_TYPE_TELEPORT; - break; - case ITEM_GROUP_NONE: - case ITEM_GROUP_GROUND: - case ITEM_GROUP_SPLASH: - case ITEM_GROUP_FLUID: - case ITEM_GROUP_CHARGES: - case ITEM_GROUP_DEPRECATED: - break; - default: - return false; - } + std::list vocationStringList; - iType.blockSolid = hasBitSet(FLAG_BLOCK_SOLID, flags); - iType.blockProjectile = hasBitSet(FLAG_BLOCK_PROJECTILE, flags); - iType.blockPathFind = hasBitSet(FLAG_BLOCK_PATHFIND, flags); - iType.hasHeight = hasBitSet(FLAG_HAS_HEIGHT, flags); - iType.useable = hasBitSet(FLAG_USEABLE, flags); - iType.pickupable = hasBitSet(FLAG_PICKUPABLE, flags); - iType.moveable = hasBitSet(FLAG_MOVEABLE, flags); - iType.stackable = hasBitSet(FLAG_STACKABLE, flags); - - iType.alwaysOnTop = hasBitSet(FLAG_ALWAYSONTOP, flags); - iType.isVertical = hasBitSet(FLAG_VERTICAL, flags); - iType.isHorizontal = hasBitSet(FLAG_HORIZONTAL, flags); - iType.isHangable = hasBitSet(FLAG_HANGABLE, flags); - iType.allowDistRead = hasBitSet(FLAG_ALLOWDISTREAD, flags); - iType.rotatable = hasBitSet(FLAG_ROTATABLE, flags); - iType.canReadText = hasBitSet(FLAG_READABLE, flags); - iType.lookThrough = hasBitSet(FLAG_LOOKTHROUGH, flags); - iType.isAnimation = hasBitSet(FLAG_ANIMATION, flags); - // iType.walkStack = !hasBitSet(FLAG_FULLTILE, flags); - iType.forceUse = hasBitSet(FLAG_FORCEUSE, flags); - - iType.id = serverId; - iType.clientId = clientId; - iType.speed = speed; - iType.lightLevel = lightLevel; - iType.lightColor = lightColor; - iType.wareId = wareId; - iType.alwaysOnTopOrder = alwaysOnTopOrder; - } - - items.shrink_to_fit(); - return true; -} - -bool Items::loadFromXml() -{ - pugi::xml_document doc; - pugi::xml_parse_result result = doc.load_file("data/items/items.xml"); - if (!result) { - printXMLError("Error - Items::loadFromXml", "data/items/items.xml", result); - return false; - } - - for (auto itemNode : doc.child("items").children()) { - pugi::xml_attribute idAttribute = itemNode.attribute("id"); - if (idAttribute) { - parseItemNode(itemNode, pugi::cast(idAttribute.value())); - continue; - } - - pugi::xml_attribute fromIdAttribute = itemNode.attribute("fromid"); - if (!fromIdAttribute) { - std::cout << "[Warning - Items::loadFromXml] No item id found" << std::endl; - continue; - } - - pugi::xml_attribute toIdAttribute = itemNode.attribute("toid"); - if (!toIdAttribute) { - std::cout << "[Warning - Items::loadFromXml] fromid (" << fromIdAttribute.value() << ") without toid" << std::endl; - continue; - } - - uint16_t id = pugi::cast(fromIdAttribute.value()); - uint16_t toId = pugi::cast(toIdAttribute.value()); - while (id <= toId) { - parseItemNode(itemNode, id++); - } - } - - buildInventoryList(); - return true; -} - -void Items::buildInventoryList() -{ - inventory.reserve(items.size()); - for (const auto& type: items) { - if (type.weaponType != WEAPON_NONE || type.ammoType != AMMO_NONE || - type.attack != 0 || type.defense != 0 || - type.extraDefense != 0 || type.armor != 0 || - type.slotPosition & SLOTP_NECKLACE || - type.slotPosition & SLOTP_RING || - type.slotPosition & SLOTP_AMMO || - type.slotPosition & SLOTP_FEET || - type.slotPosition & SLOTP_HEAD || - type.slotPosition & SLOTP_ARMOR || - type.slotPosition & SLOTP_LEGS) - { - inventory.push_back(type.clientId); - } - } - inventory.shrink_to_fit(); - std::sort(inventory.begin(), inventory.end()); -} - -void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) -{ - if (id > 30000 && id < 30100) { - id -= 30000; - - if (id >= items.size()) { - items.resize(id + 1); - } - ItemType& iType = items[id]; - iType.id = id; - } - - ItemType& it = getItemType(id); - if (it.id == 0) { - return; - } - - it.name = itemNode.attribute("name").as_string(); - - nameToItems.insert({ asLowerCaseString(it.name), id }); - - pugi::xml_attribute articleAttribute = itemNode.attribute("article"); - if (articleAttribute) { - it.article = articleAttribute.as_string(); - } - - pugi::xml_attribute pluralAttribute = itemNode.attribute("plural"); - if (pluralAttribute) { - it.pluralName = pluralAttribute.as_string(); - } - - Abilities& abilities = it.getAbilities(); - - for (auto attributeNode : itemNode.children()) { - pugi::xml_attribute keyAttribute = attributeNode.attribute("key"); - if (!keyAttribute) { - continue; - } - - pugi::xml_attribute valueAttribute = attributeNode.attribute("value"); - if (!valueAttribute) { - continue; - } - - std::string tmpStrValue = asLowerCaseString(keyAttribute.as_string()); - auto parseAttribute = ItemParseAttributesMap.find(tmpStrValue); - if (parseAttribute != ItemParseAttributesMap.end()) { - ItemParseAttributes_t parseType = parseAttribute->second; - switch (parseType) { - case ITEM_PARSE_TYPE: { - tmpStrValue = asLowerCaseString(valueAttribute.as_string()); - auto it2 = ItemTypesMap.find(tmpStrValue); - if (it2 != ItemTypesMap.end()) { - it.type = it2->second; - if (it.type == ITEM_TYPE_CONTAINER) { - it.group = ITEM_GROUP_CONTAINER; + if (hasBitSet(VOCATION_SORCERER, vocations)) { + vocationStringList.push_back("sorcerer"); } - } else { - std::cout << "[Warning - Items::parseItemNode] Unknown type: " << valueAttribute.as_string() << std::endl; - } - break; - } - case ITEM_PARSE_DESCRIPTION: { - it.description = valueAttribute.as_string(); - break; - } + if (hasBitSet(VOCATION_DRUID, vocations)) { + vocationStringList.push_back("druid"); + } - case ITEM_PARSE_RUNESPELLNAME: { - it.runeSpellName = valueAttribute.as_string(); - break; - } + if (hasBitSet(VOCATION_PALADIN, vocations)) { + vocationStringList.push_back("paladin"); + } - case ITEM_PARSE_WEIGHT: { - it.weight = pugi::cast(valueAttribute.value()); - break; - } + if (hasBitSet(VOCATION_KNIGHT, vocations)) { + vocationStringList.push_back("knight"); + } - case ITEM_PARSE_SHOWCOUNT: { - it.showCount = valueAttribute.as_bool(); - break; - } - - case ITEM_PARSE_ARMOR: { - it.armor = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_DEFENSE: { - it.defense = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_EXTRADEF: { - it.extraDefense = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ATTACK: { - it.attack = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ROTATETO: { - it.rotateTo = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_MOVEABLE: { - it.moveable = valueAttribute.as_bool(); - break; - } - - case ITEM_PARSE_BLOCKPROJECTILE: { - it.blockProjectile = valueAttribute.as_bool(); - break; - } - - case ITEM_PARSE_PICKUPABLE: { - it.allowPickupable = valueAttribute.as_bool(); - break; - } - - case ITEM_PARSE_FORCESERIALIZE: { - it.forceSerialize = valueAttribute.as_bool(); - break; - } - - case ITEM_PARSE_FLOORCHANGE: { - tmpStrValue = asLowerCaseString(valueAttribute.as_string()); - auto it2 = TileStatesMap.find(tmpStrValue); - if (it2 != TileStatesMap.end()) { - it.floorChange |= it2->second; - } else { - std::cout << "[Warning - Items::parseItemNode] Unknown floorChange: " << valueAttribute.as_string() << std::endl; - } - break; - } - - case ITEM_PARSE_CORPSETYPE: { - tmpStrValue = asLowerCaseString(valueAttribute.as_string()); - auto it2 = RaceTypesMap.find(tmpStrValue); - if (it2 != RaceTypesMap.end()) { - it.corpseType = it2->second; - } else { - std::cout << "[Warning - Items::parseItemNode] Unknown corpseType: " << valueAttribute.as_string() << std::endl; - } - break; - } - - case ITEM_PARSE_CONTAINERSIZE: { - it.maxItems = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_FLUIDSOURCE: { - tmpStrValue = asLowerCaseString(valueAttribute.as_string()); - auto it2 = FluidTypesMap.find(tmpStrValue); - if (it2 != FluidTypesMap.end()) { - it.fluidSource = it2->second; - } else { - std::cout << "[Warning - Items::parseItemNode] Unknown fluidSource: " << valueAttribute.as_string() << std::endl; - } - break; - } - - case ITEM_PARSE_READABLE: { - it.canReadText = valueAttribute.as_bool(); - break; - } - - case ITEM_PARSE_WRITEABLE: { - it.canWriteText = valueAttribute.as_bool(); - it.canReadText = it.canWriteText; - break; - } - - case ITEM_PARSE_MAXTEXTLEN: { - it.maxTextLen = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_WRITEONCEITEMID: { - it.writeOnceItemId = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_WEAPONTYPE: { - tmpStrValue = asLowerCaseString(valueAttribute.as_string()); - auto it2 = WeaponTypesMap.find(tmpStrValue); - if (it2 != WeaponTypesMap.end()) { - it.weaponType = it2->second; - } else { - std::cout << "[Warning - Items::parseItemNode] Unknown weaponType: " << valueAttribute.as_string() << std::endl; - } - break; - } - - case ITEM_PARSE_SLOTTYPE: { - tmpStrValue = asLowerCaseString(valueAttribute.as_string()); - if (tmpStrValue == "head") { - it.slotPosition |= SLOTP_HEAD; - } else if (tmpStrValue == "body") { - it.slotPosition |= SLOTP_ARMOR; - } else if (tmpStrValue == "legs") { - it.slotPosition |= SLOTP_LEGS; - } else if (tmpStrValue == "feet") { - it.slotPosition |= SLOTP_FEET; - } else if (tmpStrValue == "backpack") { - it.slotPosition |= SLOTP_BACKPACK; - } else if (tmpStrValue == "two-handed") { - it.slotPosition |= SLOTP_TWO_HAND; - } else if (tmpStrValue == "right-hand") { - it.slotPosition &= ~SLOTP_LEFT; - } else if (tmpStrValue == "left-hand") { - it.slotPosition &= ~SLOTP_RIGHT; - } else if (tmpStrValue == "necklace") { - it.slotPosition |= SLOTP_NECKLACE; - } else if (tmpStrValue == "ring") { - it.slotPosition |= SLOTP_RING; - } else if (tmpStrValue == "ammo") { - it.slotPosition |= SLOTP_AMMO; - } else if (tmpStrValue == "hand") { - it.slotPosition |= SLOTP_HAND; - } else { - std::cout << "[Warning - Items::parseItemNode] Unknown slotType: " << valueAttribute.as_string() << std::endl; - } - break; - } - - case ITEM_PARSE_AMMOTYPE: { - it.ammoType = getAmmoType(asLowerCaseString(valueAttribute.as_string())); - if (it.ammoType == AMMO_NONE) { - std::cout << "[Warning - Items::parseItemNode] Unknown ammoType: " << valueAttribute.as_string() << std::endl; - } - break; - } - - case ITEM_PARSE_SHOOTTYPE: { - ShootType_t shoot = getShootType(asLowerCaseString(valueAttribute.as_string())); - if (shoot != CONST_ANI_NONE) { - it.shootType = shoot; - } else { - std::cout << "[Warning - Items::parseItemNode] Unknown shootType: " << valueAttribute.as_string() << std::endl; - } - break; - } - - case ITEM_PARSE_EFFECT: { - MagicEffectClasses effect = getMagicEffect(asLowerCaseString(valueAttribute.as_string())); - if (effect != CONST_ME_NONE) { - it.magicEffect = effect; - } else { - std::cout << "[Warning - Items::parseItemNode] Unknown effect: " << valueAttribute.as_string() << std::endl; - } - break; - } - - case ITEM_PARSE_RANGE: { - it.shootRange = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_STOPDURATION: { - it.stopTime = valueAttribute.as_bool(); - break; - } - - case ITEM_PARSE_DECAYTO: { - it.decayTo = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_TRANSFORMEQUIPTO: { - it.transformEquipTo = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_TRANSFORMDEEQUIPTO: { - it.transformDeEquipTo = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_DURATION: { - it.decayTime = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_SHOWDURATION: { - it.showDuration = valueAttribute.as_bool(); - break; - } - - case ITEM_PARSE_CHARGES: { - it.charges = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_SHOWCHARGES: { - it.showCharges = valueAttribute.as_bool(); - break; - } - - case ITEM_PARSE_SHOWATTRIBUTES: { - it.showAttributes = valueAttribute.as_bool(); - break; - } - - case ITEM_PARSE_HITCHANCE: { - it.hitChance = std::min(100, std::max(-100, pugi::cast(valueAttribute.value()))); - break; - } - - case ITEM_PARSE_MAXHITCHANCE: { - it.maxHitChance = std::min(100, pugi::cast(valueAttribute.value())); - break; - } - - case ITEM_PARSE_INVISIBLE: { - abilities.invisible = valueAttribute.as_bool(); - break; - } - - case ITEM_PARSE_SPEED: { - abilities.speed = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_HEALTHGAIN: { - abilities.regeneration = true; - abilities.healthGain = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_HEALTHTICKS: { - abilities.regeneration = true; - abilities.healthTicks = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_MANAGAIN: { - abilities.regeneration = true; - abilities.manaGain = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_MANATICKS: { - abilities.regeneration = true; - abilities.manaTicks = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_MANASHIELD: { - abilities.manaShield = valueAttribute.as_bool(); - break; - } - - case ITEM_PARSE_SKILLSWORD: { - abilities.skills[SKILL_SWORD] = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_SKILLAXE: { - abilities.skills[SKILL_AXE] = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_SKILLCLUB: { - abilities.skills[SKILL_CLUB] = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_SKILLDIST: { - abilities.skills[SKILL_DISTANCE] = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_SKILLFISH: { - abilities.skills[SKILL_FISHING] = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_SKILLSHIELD: { - abilities.skills[SKILL_SHIELD] = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_SKILLFIST: { - abilities.skills[SKILL_FIST] = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_CRITICALHITAMOUNT: { - abilities.specialSkills[SPECIALSKILL_CRITICALHITAMOUNT] = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_CRITICALHITCHANCE: { - abilities.specialSkills[SPECIALSKILL_CRITICALHITCHANCE] = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_MANALEECHAMOUNT: { - abilities.specialSkills[SPECIALSKILL_MANALEECHAMOUNT] = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_MANALEECHCHANCE: { - abilities.specialSkills[SPECIALSKILL_MANALEECHCHANCE] = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_LIFELEECHAMOUNT: { - abilities.specialSkills[SPECIALSKILL_LIFELEECHAMOUNT] = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_LIFELEECHCHANCE: { - abilities.specialSkills[SPECIALSKILL_LIFELEECHCHANCE] = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_MAXHITPOINTS: { - abilities.stats[STAT_MAXHITPOINTS] = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_MAXHITPOINTSPERCENT: { - abilities.statsPercent[STAT_MAXHITPOINTS] = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_MAXMANAPOINTS: { - abilities.stats[STAT_MAXMANAPOINTS] = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_MAXMANAPOINTSPERCENT: { - abilities.statsPercent[STAT_MAXMANAPOINTS] = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_MAGICPOINTS: { - abilities.stats[STAT_MAGICPOINTS] = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_MAGICPOINTSPERCENT: { - abilities.statsPercent[STAT_MAGICPOINTS] = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_FIELDABSORBPERCENTENERGY: { - abilities.fieldAbsorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_FIELDABSORBPERCENTFIRE: { - abilities.fieldAbsorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_FIELDABSORBPERCENTPOISON: { - abilities.fieldAbsorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTALL: { - int16_t value = pugi::cast(valueAttribute.value()); - for (auto& i : abilities.absorbPercent) { - i += value; - } - break; - } - - case ITEM_PARSE_ABSORBPERCENTELEMENTS: { - int16_t value = pugi::cast(valueAttribute.value()); - abilities.absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += value; - break; - } - - case ITEM_PARSE_ABSORBPERCENTMAGIC: { - int16_t value = pugi::cast(valueAttribute.value()); - abilities.absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_HOLYDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_DEATHDAMAGE)] += value; - break; - } - - case ITEM_PARSE_ABSORBPERCENTENERGY: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTFIRE: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTPOISON: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTICE: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTHOLY: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_HOLYDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTDEATH: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_DEATHDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTLIFEDRAIN: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_LIFEDRAIN)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTMANADRAIN: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_MANADRAIN)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTDROWN: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_DROWNDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTPHYSICAL: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTHEALING: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_HEALING)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTUNDEFINED: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_UNDEFINEDDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_SUPPRESSDRUNK: { - if (valueAttribute.as_bool()) { - abilities.conditionSuppressions |= CONDITION_DRUNK; - } - break; - } - - case ITEM_PARSE_SUPPRESSENERGY: { - if (valueAttribute.as_bool()) { - abilities.conditionSuppressions |= CONDITION_ENERGY; - } - break; - } - - case ITEM_PARSE_SUPPRESSFIRE: { - if (valueAttribute.as_bool()) { - abilities.conditionSuppressions |= CONDITION_FIRE; - } - break; - } - - case ITEM_PARSE_SUPPRESSPOISON: { - if (valueAttribute.as_bool()) { - abilities.conditionSuppressions |= CONDITION_POISON; - } - break; - } - - case ITEM_PARSE_SUPPRESSDROWN: { - if (valueAttribute.as_bool()) { - abilities.conditionSuppressions |= CONDITION_DROWN; - } - break; - } - - case ITEM_PARSE_SUPPRESSPHYSICAL: { - if (valueAttribute.as_bool()) { - abilities.conditionSuppressions |= CONDITION_BLEEDING; - } - break; - } - - case ITEM_PARSE_SUPPRESSFREEZE: { - if (valueAttribute.as_bool()) { - abilities.conditionSuppressions |= CONDITION_FREEZING; - } - break; - } - - case ITEM_PARSE_SUPPRESSDAZZLE: { - if (valueAttribute.as_bool()) { - abilities.conditionSuppressions |= CONDITION_DAZZLED; - } - break; - } - - case ITEM_PARSE_SUPPRESSCURSE: { - if (valueAttribute.as_bool()) { - abilities.conditionSuppressions |= CONDITION_CURSED; - } - break; - } - - case ITEM_PARSE_FIELD: { - it.group = ITEM_GROUP_MAGICFIELD; - it.type = ITEM_TYPE_MAGICFIELD; - - CombatType_t combatType = COMBAT_NONE; - ConditionDamage* conditionDamage = nullptr; - - tmpStrValue = asLowerCaseString(valueAttribute.as_string()); - if (tmpStrValue == "fire") { - conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_FIRE); - combatType = COMBAT_FIREDAMAGE; - } else if (tmpStrValue == "energy") { - conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_ENERGY); - combatType = COMBAT_ENERGYDAMAGE; - } else if (tmpStrValue == "poison") { - conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_POISON); - combatType = COMBAT_EARTHDAMAGE; - } else if (tmpStrValue == "drown") { - conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_DROWN); - combatType = COMBAT_DROWNDAMAGE; - } else if (tmpStrValue == "physical") { - conditionDamage = new ConditionDamage(CONDITIONID_COMBAT, CONDITION_BLEEDING); - combatType = COMBAT_PHYSICALDAMAGE; - } else { - std::cout << "[Warning - Items::parseItemNode] Unknown field value: " << valueAttribute.as_string() << std::endl; - } - - if (combatType != COMBAT_NONE) { - it.combatType = combatType; - it.conditionDamage.reset(conditionDamage); - - uint32_t ticks = 0; - int32_t start = 0; - int32_t count = 1; - for (auto subAttributeNode : attributeNode.children()) { - pugi::xml_attribute subKeyAttribute = subAttributeNode.attribute("key"); - if (!subKeyAttribute) { - continue; - } - - pugi::xml_attribute subValueAttribute = subAttributeNode.attribute("value"); - if (!subValueAttribute) { - continue; - } - - tmpStrValue = asLowerCaseString(subKeyAttribute.as_string()); - if (tmpStrValue == "ticks") { - ticks = pugi::cast(subValueAttribute.value()); - } else if (tmpStrValue == "count") { - count = std::max(1, pugi::cast(subValueAttribute.value())); - } else if (tmpStrValue == "start") { - start = std::max(0, pugi::cast(subValueAttribute.value())); - } else if (tmpStrValue == "damage") { - int32_t damage = -pugi::cast(subValueAttribute.value()); - if (start > 0) { - std::list damageList; - ConditionDamage::generateDamageList(damage, start, damageList); - for (int32_t damageValue : damageList) { - conditionDamage->addDamage(1, ticks, -damageValue); - } - - start = 0; + std::string vocationString; + for (const std::string& str : vocationStringList) { + if (!vocationString.empty()) { + if (str != vocationStringList.back()) { + vocationString.push_back(','); + vocationString.push_back(' '); } else { - conditionDamage->addDamage(count, ticks, damage); + vocationString += " and "; } } + + vocationString += str; + vocationString.push_back('s'); } - conditionDamage->setParam(CONDITION_PARAM_FIELD, 1); - - if (conditionDamage->getTotalDamage() > 0) { - conditionDamage->setParam(CONDITION_PARAM_FORCEUPDATE, 1); + 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 == "absorbdrown") { + items[id].getAbilities().absorbPercent[combatTypeToIndex(COMBAT_DROWNDAMAGE)] += 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(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(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(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; } - case ITEM_PARSE_REPLACEABLE: { - it.replaceable = valueAttribute.as_bool(); - break; - } - - case ITEM_PARSE_PARTNERDIRECTION: { - it.bedPartnerDir = getDirection(valueAttribute.as_string()); - break; - } - - case ITEM_PARSE_LEVELDOOR: { - it.levelDoor = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_MALETRANSFORMTO: { - uint16_t value = pugi::cast(valueAttribute.value()); - it.transformToOnUse[PLAYERSEX_MALE] = value; - ItemType& other = getItemType(value); - if (other.transformToFree == 0) { - other.transformToFree = it.id; - } - - if (it.transformToOnUse[PLAYERSEX_FEMALE] == 0) { - it.transformToOnUse[PLAYERSEX_FEMALE] = value; - } - break; - } - - case ITEM_PARSE_FEMALETRANSFORMTO: { - uint16_t value = pugi::cast(valueAttribute.value()); - it.transformToOnUse[PLAYERSEX_FEMALE] = value; - - ItemType& other = getItemType(value); - if (other.transformToFree == 0) { - other.transformToFree = it.id; - } - - if (it.transformToOnUse[PLAYERSEX_MALE] == 0) { - it.transformToOnUse[PLAYERSEX_MALE] = value; - } - break; - } - - case ITEM_PARSE_TRANSFORMTO: { - it.transformToFree = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_DESTROYTO: { - it.destroyTo = pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ELEMENTICE: { - abilities.elementDamage = pugi::cast(valueAttribute.value()); - abilities.elementType = COMBAT_ICEDAMAGE; - break; - } - - case ITEM_PARSE_ELEMENTEARTH: { - abilities.elementDamage = pugi::cast(valueAttribute.value()); - abilities.elementType = COMBAT_EARTHDAMAGE; - break; - } - - case ITEM_PARSE_ELEMENTFIRE: { - abilities.elementDamage = pugi::cast(valueAttribute.value()); - abilities.elementType = COMBAT_FIREDAMAGE; - break; - } - - case ITEM_PARSE_ELEMENTENERGY: { - abilities.elementDamage = pugi::cast(valueAttribute.value()); - abilities.elementType = COMBAT_ENERGYDAMAGE; - break; - } - - case ITEM_PARSE_WALKSTACK: { - it.walkStack = valueAttribute.as_bool(); - break; - } - - case ITEM_PARSE_BLOCKING: { - it.blockSolid = valueAttribute.as_bool(); - break; - } - - case ITEM_PARSE_ALLOWDISTREAD: { - it.allowDistRead = booleanString(valueAttribute.as_string()); - break; - } - - default: { - // It should not ever get to here, only if you add a new key to the map and don't configure a case for it. - std::cout << "[Warning - Items::parseItemNode] Not configured key value: " << keyAttribute.as_string() << std::endl; - break; + if (script.Token != SPECIAL || script.getSpecial() != ',') { + continue; } } - } else { - std::cout << "[Warning - Items::parseItemNode] Unknown key value: " << keyAttribute.as_string() << std::endl; + } 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); + 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); } } - //check bed items - if ((it.transformToFree != 0 || it.transformToOnUse[PLAYERSEX_FEMALE] != 0 || it.transformToOnUse[PLAYERSEX_MALE] != 0) && it.type != ITEM_TYPE_BED) { - std::cout << "[Warning - Items::parseItemNode] Item " << it.id << " is not set as a bed-type" << std::endl; + 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) @@ -1357,15 +596,6 @@ const ItemType& Items::getItemType(size_t id) const return items.front(); } -const ItemType& Items::getItemIdByClientId(uint16_t spriteId) const -{ - auto it = reverseItemMap.find(spriteId); - if (it != reverseItemMap.end()) { - return getItemType(it->second); - } - return items.front(); -} - uint16_t Items::getItemIdByName(const std::string& name) { auto result = nameToItems.find(asLowerCaseString(name)); diff --git a/src/items.h b/src/items.h index 0bc3d13..6411e99 100644 --- a/src/items.h +++ b/src/items.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,8 +22,8 @@ #include "const.h" #include "enums.h" -#include "itemloader.h" #include "position.h" +#include "fileloader.h" enum SlotPositionBits : uint32_t { SLOTP_WHEREEVER = 0xFFFFFFFF, @@ -46,7 +46,6 @@ enum ItemTypes_t { ITEM_TYPE_NONE, ITEM_TYPE_DEPOT, ITEM_TYPE_MAILBOX, - ITEM_TYPE_TRASHHOLDER, ITEM_TYPE_CONTAINER, ITEM_TYPE_DOOR, ITEM_TYPE_MAGICFIELD, @@ -54,117 +53,28 @@ enum ItemTypes_t { ITEM_TYPE_BED, ITEM_TYPE_KEY, ITEM_TYPE_RUNE, + ITEM_TYPE_CHEST, ITEM_TYPE_LAST }; -enum ItemParseAttributes_t { - ITEM_PARSE_TYPE, - ITEM_PARSE_DESCRIPTION, - ITEM_PARSE_RUNESPELLNAME, - ITEM_PARSE_WEIGHT, - ITEM_PARSE_SHOWCOUNT, - ITEM_PARSE_ARMOR, - ITEM_PARSE_DEFENSE, - ITEM_PARSE_EXTRADEF, - ITEM_PARSE_ATTACK, - ITEM_PARSE_ROTATETO, - ITEM_PARSE_MOVEABLE, - ITEM_PARSE_BLOCKPROJECTILE, - ITEM_PARSE_PICKUPABLE, - ITEM_PARSE_FORCESERIALIZE, - ITEM_PARSE_FLOORCHANGE, - ITEM_PARSE_CORPSETYPE, - ITEM_PARSE_CONTAINERSIZE, - ITEM_PARSE_FLUIDSOURCE, - ITEM_PARSE_READABLE, - ITEM_PARSE_WRITEABLE, - ITEM_PARSE_MAXTEXTLEN, - ITEM_PARSE_WRITEONCEITEMID, - ITEM_PARSE_WEAPONTYPE, - ITEM_PARSE_SLOTTYPE, - ITEM_PARSE_AMMOTYPE, - ITEM_PARSE_SHOOTTYPE, - ITEM_PARSE_EFFECT, - ITEM_PARSE_RANGE, - ITEM_PARSE_STOPDURATION, - ITEM_PARSE_DECAYTO, - ITEM_PARSE_TRANSFORMEQUIPTO, - ITEM_PARSE_TRANSFORMDEEQUIPTO, - ITEM_PARSE_DURATION, - ITEM_PARSE_SHOWDURATION, - ITEM_PARSE_CHARGES, - ITEM_PARSE_SHOWCHARGES, - ITEM_PARSE_SHOWATTRIBUTES, - ITEM_PARSE_HITCHANCE, - ITEM_PARSE_MAXHITCHANCE, - ITEM_PARSE_INVISIBLE, - ITEM_PARSE_SPEED, - ITEM_PARSE_HEALTHGAIN, - ITEM_PARSE_HEALTHTICKS, - ITEM_PARSE_MANAGAIN, - ITEM_PARSE_MANATICKS, - ITEM_PARSE_MANASHIELD, - ITEM_PARSE_SKILLSWORD, - ITEM_PARSE_SKILLAXE, - ITEM_PARSE_SKILLCLUB, - ITEM_PARSE_SKILLDIST, - ITEM_PARSE_SKILLFISH, - ITEM_PARSE_SKILLSHIELD, - ITEM_PARSE_SKILLFIST, - ITEM_PARSE_MAXHITPOINTS, - ITEM_PARSE_MAXHITPOINTSPERCENT, - ITEM_PARSE_MAXMANAPOINTS, - ITEM_PARSE_MAXMANAPOINTSPERCENT, - ITEM_PARSE_MAGICPOINTS, - ITEM_PARSE_MAGICPOINTSPERCENT, - ITEM_PARSE_CRITICALHITCHANCE, - ITEM_PARSE_CRITICALHITAMOUNT, - ITEM_PARSE_LIFELEECHCHANCE, - ITEM_PARSE_LIFELEECHAMOUNT, - ITEM_PARSE_MANALEECHCHANCE, - ITEM_PARSE_MANALEECHAMOUNT, - ITEM_PARSE_FIELDABSORBPERCENTENERGY, - ITEM_PARSE_FIELDABSORBPERCENTFIRE, - ITEM_PARSE_FIELDABSORBPERCENTPOISON, - ITEM_PARSE_ABSORBPERCENTALL, - ITEM_PARSE_ABSORBPERCENTELEMENTS, - ITEM_PARSE_ABSORBPERCENTMAGIC, - ITEM_PARSE_ABSORBPERCENTENERGY, - ITEM_PARSE_ABSORBPERCENTFIRE, - ITEM_PARSE_ABSORBPERCENTPOISON, - ITEM_PARSE_ABSORBPERCENTICE, - ITEM_PARSE_ABSORBPERCENTHOLY, - ITEM_PARSE_ABSORBPERCENTDEATH, - ITEM_PARSE_ABSORBPERCENTLIFEDRAIN, - ITEM_PARSE_ABSORBPERCENTMANADRAIN, - ITEM_PARSE_ABSORBPERCENTDROWN, - ITEM_PARSE_ABSORBPERCENTPHYSICAL, - ITEM_PARSE_ABSORBPERCENTHEALING, - ITEM_PARSE_ABSORBPERCENTUNDEFINED, - ITEM_PARSE_SUPPRESSDRUNK, - ITEM_PARSE_SUPPRESSENERGY, - ITEM_PARSE_SUPPRESSFIRE, - ITEM_PARSE_SUPPRESSPOISON, - ITEM_PARSE_SUPPRESSDROWN, - ITEM_PARSE_SUPPRESSPHYSICAL, - ITEM_PARSE_SUPPRESSFREEZE, - ITEM_PARSE_SUPPRESSDAZZLE, - ITEM_PARSE_SUPPRESSCURSE, - ITEM_PARSE_FIELD, - ITEM_PARSE_REPLACEABLE, - ITEM_PARSE_PARTNERDIRECTION, - ITEM_PARSE_LEVELDOOR, - ITEM_PARSE_MALETRANSFORMTO, - ITEM_PARSE_FEMALETRANSFORMTO, - ITEM_PARSE_TRANSFORMTO, - ITEM_PARSE_DESTROYTO, - ITEM_PARSE_ELEMENTICE, - ITEM_PARSE_ELEMENTEARTH, - ITEM_PARSE_ELEMENTFIRE, - ITEM_PARSE_ELEMENTENERGY, - ITEM_PARSE_WALKSTACK, - ITEM_PARSE_BLOCKING, - ITEM_PARSE_ALLOWDISTREAD, +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 { @@ -182,7 +92,6 @@ struct Abilities { //extra skill modifiers int32_t skills[SKILL_LAST + 1] = { 0 }; - int32_t specialSkills[SPECIALSKILL_LAST + 1] = { 0 }; int32_t speed = 0; @@ -192,10 +101,6 @@ struct Abilities { //damage abilities modifiers int16_t absorbPercent[COMBAT_COUNT] = { 0 }; - //elemental damage - uint16_t elementDamage = 0; - CombatType_t elementType = COMBAT_NONE; - bool manaShield = false; bool invisible = false; bool regeneration = false; @@ -219,7 +124,10 @@ class ItemType return group == ITEM_GROUP_GROUND; } bool isContainer() const { - return group == ITEM_GROUP_CONTAINER; + return type == ITEM_TYPE_CONTAINER; + } + bool isChest() const { + return type == ITEM_TYPE_CHEST; } bool isSplash() const { return group == ITEM_GROUP_SPLASH; @@ -246,20 +154,11 @@ class ItemType bool isMailbox() const { return (type == ITEM_TYPE_MAILBOX); } - bool isTrashHolder() const { - return (type == ITEM_TYPE_TRASHHOLDER); - } bool isBed() const { return (type == ITEM_TYPE_BED); } bool isRune() const { - return (type == ITEM_TYPE_RUNE); - } - bool isPickupable() const { - return (allowPickupable || pickupable); - } - bool isUseable() const { - return (useable); + return type == ITEM_TYPE_RUNE; } bool hasSubType() const { return (isFluidContainer() || isSplash() || stackable || charges != 0); @@ -291,9 +190,7 @@ class ItemType itemgroup_t group = ITEM_GROUP_NONE; ItemTypes_t type = ITEM_TYPE_NONE; uint16_t id = 0; - uint16_t clientId = 0; bool stackable = false; - bool isAnimation = false; std::string name; std::string article; @@ -306,35 +203,40 @@ class ItemType std::unique_ptr conditionDamage; uint32_t weight = 0; - uint32_t levelDoor = 0; uint32_t decayTime = 0; uint32_t wieldInfo = 0; uint32_t minReqLevel = 0; uint32_t minReqMagicLevel = 0; uint32_t charges = 0; - int32_t maxHitChance = -1; + 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; - uint16_t rotateTo = 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[2] = {0, 0}; + 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_HAND; + uint16_t slotPosition = SLOTP_RIGHT | SLOTP_LEFT | SLOTP_AMMO; uint16_t speed = 0; - uint16_t wareId = 0; MagicEffectClasses magicEffect = CONST_ME_NONE; Direction bedPartnerDir = DIRECTION_NONE; @@ -344,22 +246,30 @@ class ItemType RaceType_t corpseType = RACE_NONE; FluidTypes_t fluidSource = FLUID_NONE; - uint8_t floorChange = 0; + uint8_t fragility = 0; uint8_t alwaysOnTopOrder = 0; uint8_t lightLevel = 0; uint8_t lightColor = 0; uint8_t shootRange = 1; - int8_t hitChance = 0; + 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 forceSerialize = 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 = false; + bool allowPickupable = true; bool showDuration = false; bool showCharges = false; bool showAttributes = false; @@ -367,7 +277,7 @@ class ItemType bool pickupable = false; bool rotatable = false; bool useable = false; - bool moveable = false; + bool moveable = true; bool alwaysOnTop = false; bool canReadText = false; bool canWriteText = false; @@ -383,8 +293,7 @@ class ItemType class Items { public: - using NameMap = std::unordered_multimap; - using InventoryVector = std::vector; + using nameMap = std::unordered_multimap; Items(); @@ -395,38 +304,23 @@ class Items bool reload(); void clear(); - bool loadFromOtb(const std::string& file); - const ItemType& operator[](size_t id) const { return getItemType(id); } const ItemType& getItemType(size_t id) const; ItemType& getItemType(size_t id); - const ItemType& getItemIdByClientId(uint16_t spriteId) const; uint16_t getItemIdByName(const std::string& name); - uint32_t majorVersion = 0; - uint32_t minorVersion = 0; - uint32_t buildNumber = 0; + bool loadItems(); - bool loadFromXml(); - void parseItemNode(const pugi::xml_node& itemNode, uint16_t id); - - void buildInventoryList(); - const InventoryVector& getInventory() const { - return inventory; - } - - size_t size() const { + inline size_t size() const { return items.size(); } - NameMap nameToItems; + nameMap nameToItems; - private: - std::map reverseItemMap; + protected: std::vector items; - InventoryVector inventory; }; #endif diff --git a/src/lockfree.h b/src/lockfree.h index b555139..7c8cc7b 100644 --- a/src/lockfree.h +++ b/src/lockfree.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,52 +26,37 @@ #include -/* - * we use this to avoid instantiating multiple free lists for objects of the - * same size and it can be replaced by a variable template in C++14 - * - * template - * boost::lockfree::stack lockfreeFreeList; - */ -template -struct LockfreeFreeList -{ - using FreeList = boost::lockfree::stack>; - static FreeList& get() - { - static FreeList freeList; - return freeList; - } -}; - template class LockfreePoolingAllocator : public std::allocator { public: - LockfreePoolingAllocator() = default; - - template ::value>::type> + template explicit constexpr LockfreePoolingAllocator(const U&) {} - using value_type = T; + typedef T value_type; T* allocate(size_t) const { - auto& inst = LockfreeFreeList::get(); - void* p; // NOTE: p doesn't have to be initialized - if (!inst.pop(p)) { + T* p; // NOTE: p doesn't have to be initialized + if (!getFreeList().pop(p)) { //Acquire memory without calling the constructor of T - p = operator new (sizeof(T)); + p = static_cast(operator new (sizeof(T))); } - return static_cast(p); + return p; } void deallocate(T* p, size_t) const { - auto& inst = LockfreeFreeList::get(); - if (!inst.bounded_push(p)) { + 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> FreeList; + static FreeList& getFreeList() { + static FreeList freeList; + return freeList; + } }; #endif diff --git a/src/luascript.cpp b/src/luascript.cpp index c1f05b5..c46e5d7 100644 --- a/src/luascript.cpp +++ b/src/luascript.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,10 +35,6 @@ #include "monster.h" #include "scheduler.h" #include "databasetasks.h" -#include "movement.h" -#include "globalevent.h" -#include "script.h" -#include "weapons.h" extern Chat* g_chat; extern Game g_game; @@ -46,13 +42,6 @@ extern Monsters g_monsters; extern ConfigManager g_config; extern Vocations g_vocations; extern Spells* g_spells; -extern Actions* g_actions; -extern TalkActions* g_talkActions; -extern CreatureEvents* g_creatureEvents; -extern MoveEvents* g_moveEvents; -extern GlobalEvents* g_globalEvents; -extern Scripts* g_scripts; -extern Weapons* g_weapons; ScriptEnvironment::DBResultMap ScriptEnvironment::tempResults; uint32_t ScriptEnvironment::lastResultId = 0; @@ -126,10 +115,6 @@ uint32_t ScriptEnvironment::addThing(Thing* thing) } Item* item = thing->getItem(); - if (item && item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { - return item->getUniqueId(); - } - for (const auto& it : localMap) { if (it.second == item) { return it.first; @@ -154,14 +139,6 @@ Thing* ScriptEnvironment::getThingByUID(uint32_t uid) return g_game.getCreatureByID(uid); } - if (uid <= std::numeric_limits::max()) { - Item* item = g_game.getUniqueItem(uid); - if (item && !item->isRemoved()) { - return item; - } - return nullptr; - } - auto it = localMap.find(uid); if (it != localMap.end()) { Item* item = it->second; @@ -192,11 +169,6 @@ Container* ScriptEnvironment::getContainerByUID(uint32_t uid) void ScriptEnvironment::removeItemByUID(uint32_t uid) { - if (uid <= std::numeric_limits::max()) { - g_game.removeUniqueItem(uid); - return; - } - auto it = localMap.find(uid); if (it != localMap.end()) { localMap.erase(it); @@ -365,29 +337,6 @@ int32_t LuaScriptInterface::getEvent(const std::string& eventName) return runningEventId++; } -int32_t LuaScriptInterface::getEvent() -{ - //check if function is on the stack - if (!isFunction(luaState, -1)) { - return -1; - } - - //get our events table - lua_rawgeti(luaState, LUA_REGISTRYINDEX, eventTableRef); - if (!isTable(luaState, -1)) { - lua_pop(luaState, 1); - return -1; - } - - //save in our events table - lua_pushvalue(luaState, -2); - lua_rawseti(luaState, -2, runningEventId); - lua_pop(luaState, 2); - - cacheFiles[runningEventId] = loadingFile + ":callback"; - return runningEventId++; -} - int32_t LuaScriptInterface::getMetaEvent(const std::string& globalName, const std::string& eventName) { //get our events table @@ -727,6 +676,18 @@ void LuaScriptInterface::setCreatureMetatable(lua_State* L, int32_t index, const } // Get +CombatDamage LuaScriptInterface::getCombatDamage(lua_State* L) +{ + CombatDamage damage; + damage.value = getNumber(L, -4); + damage.type = getNumber(L, -3); + damage.min = getNumber(L, -2); + damage.max = getNumber(L, -1); + + lua_pop(L, 4); + return damage; +} + std::string LuaScriptInterface::getString(lua_State* L, int32_t arg) { size_t len; @@ -769,7 +730,6 @@ Position LuaScriptInterface::getPosition(lua_State* L, int32_t arg) Outfit_t LuaScriptInterface::getOutfit(lua_State* L, int32_t arg) { Outfit_t outfit; - outfit.lookMount = getField(L, arg, "lookMount"); outfit.lookAddons = getField(L, arg, "lookAddons"); outfit.lookFeet = getField(L, arg, "lookFeet"); @@ -777,10 +737,10 @@ Outfit_t LuaScriptInterface::getOutfit(lua_State* L, int32_t arg) outfit.lookBody = getField(L, arg, "lookBody"); outfit.lookHead = getField(L, arg, "lookHead"); - outfit.lookTypeEx = getField(L, arg, "lookTypeEx"); - outfit.lookType = getField(L, arg, "lookType"); + outfit.lookTypeEx = getField(L, arg, "lookTypeEx"); + outfit.lookType = getField(L, arg, "lookType"); - lua_pop(L, 8); + lua_pop(L, 6); return outfit; } @@ -817,13 +777,6 @@ LuaVariant LuaScriptInterface::getVariant(lua_State* L, int32_t arg) return var; } -InstantSpell* LuaScriptInterface::getInstantSpell(lua_State* L, int32_t arg) -{ - InstantSpell* spell = g_spells->getInstantSpellByName(getFieldString(L, arg, "name")); - lua_pop(L, 1); - return spell; -} - Thing* LuaScriptInterface::getThing(lua_State* L, int32_t arg) { Thing* thing; @@ -902,26 +855,13 @@ void LuaScriptInterface::pushBoolean(lua_State* L, bool value) void LuaScriptInterface::pushCombatDamage(lua_State* L, const CombatDamage& damage) { - lua_pushnumber(L, damage.primary.value); - lua_pushnumber(L, damage.primary.type); - lua_pushnumber(L, damage.secondary.value); - lua_pushnumber(L, damage.secondary.type); + lua_pushnumber(L, damage.value); + lua_pushnumber(L, damage.type); + lua_pushnumber(L, damage.min); + lua_pushnumber(L, damage.max); lua_pushnumber(L, damage.origin); } -void LuaScriptInterface::pushInstantSpell(lua_State* L, const InstantSpell& spell) -{ - lua_createtable(L, 0, 6); - - setField(L, "name", spell.getName()); - setField(L, "words", spell.getWords()); - setField(L, "level", spell.getLevel()); - setField(L, "mlevel", spell.getMagicLevel()); - setField(L, "mana", spell.getMana()); - setField(L, "manapercent", spell.getManaPercent()); - - setMetatable(L, -1, "Spell"); -} void LuaScriptInterface::pushPosition(lua_State* L, const Position& position, int32_t stackpos/* = 0*/) { @@ -937,7 +877,7 @@ void LuaScriptInterface::pushPosition(lua_State* L, const Position& position, in void LuaScriptInterface::pushOutfit(lua_State* L, const Outfit_t& outfit) { - lua_createtable(L, 0, 8); + lua_createtable(L, 0, 6); setField(L, "lookType", outfit.lookType); setField(L, "lookTypeEx", outfit.lookTypeEx); setField(L, "lookHead", outfit.lookHead); @@ -945,29 +885,6 @@ void LuaScriptInterface::pushOutfit(lua_State* L, const Outfit_t& outfit) setField(L, "lookLegs", outfit.lookLegs); setField(L, "lookFeet", outfit.lookFeet); setField(L, "lookAddons", outfit.lookAddons); - setField(L, "lookMount", outfit.lookMount); -} - -void LuaScriptInterface::pushLoot(lua_State* L, const std::vector& lootList) -{ - lua_createtable(L, lootList.size(), 0); - - int index = 0; - for (const auto& lootBlock : lootList) { - lua_createtable(L, 0, 7); - - setField(L, "itemId", lootBlock.id); - setField(L, "chance", lootBlock.chance); - setField(L, "subType", lootBlock.subType); - setField(L, "maxCount", lootBlock.countmax); - setField(L, "actionId", lootBlock.actionId); - setField(L, "text", lootBlock.text); - - pushLoot(L, lootBlock.childLoot); - lua_setfield(L, -2, "childLoot"); - - lua_rawseti(L, -2, ++index); - } } #define registerEnum(value) { std::string enumName = #value; registerGlobalVariable(enumName.substr(enumName.find_last_of(':') + 1), value); } @@ -975,14 +892,39 @@ void LuaScriptInterface::pushLoot(lua_State* L, const std::vector& lo void LuaScriptInterface::registerFunctions() { + //getPlayerFlagValue(cid, flag) + lua_register(luaState, "getPlayerFlagValue", LuaScriptInterface::luaGetPlayerFlagValue); + + //getPlayerInstantSpellCount(cid) + lua_register(luaState, "getPlayerInstantSpellCount", LuaScriptInterface::luaGetPlayerInstantSpellCount); + + //getPlayerInstantSpellInfo(cid, index) + lua_register(luaState, "getPlayerInstantSpellInfo", LuaScriptInterface::luaGetPlayerInstantSpellInfo); + //doPlayerAddItem(uid, itemid, count/subtype) //doPlayerAddItem(cid, itemid, count, canDropOnMap, subtype) //Returns uid of the created item lua_register(luaState, "doPlayerAddItem", LuaScriptInterface::luaDoPlayerAddItem); + //doCreateItem(itemid, type/count, pos) + //Returns uid of the created item, only works on tiles. + lua_register(luaState, "doCreateItem", LuaScriptInterface::luaDoCreateItem); + + //doCreateItemEx(itemid, count/subtype) + lua_register(luaState, "doCreateItemEx", LuaScriptInterface::luaDoCreateItemEx); + + //doTileAddItemEx(pos, uid) + lua_register(luaState, "doTileAddItemEx", LuaScriptInterface::luaDoTileAddItemEx); + + //doMoveCreature(cid, direction) + lua_register(luaState, "doMoveCreature", LuaScriptInterface::luaDoMoveCreature); + //doSetCreatureLight(cid, lightLevel, lightColor, time) lua_register(luaState, "doSetCreatureLight", LuaScriptInterface::luaDoSetCreatureLight); + //getCreatureCondition(cid, condition[, subId]) + lua_register(luaState, "getCreatureCondition", LuaScriptInterface::luaGetCreatureCondition); + //isValidUID(uid) lua_register(luaState, "isValidUID", LuaScriptInterface::luaIsValidUID); @@ -1037,6 +979,18 @@ void LuaScriptInterface::registerFunctions() //doChallengeCreature(cid, target) lua_register(luaState, "doChallengeCreature", LuaScriptInterface::luaDoChallengeCreature); + //doSetMonsterOutfit(cid, name, time) + lua_register(luaState, "doSetMonsterOutfit", LuaScriptInterface::luaSetMonsterOutfit); + + //doSetItemOutfit(cid, item, time) + lua_register(luaState, "doSetItemOutfit", LuaScriptInterface::luaSetItemOutfit); + + //doSetCreatureOutfit(cid, outfit, time) + lua_register(luaState, "doSetCreatureOutfit", LuaScriptInterface::luaSetCreatureOutfit); + + //isInArray(array, value) + lua_register(luaState, "isInArray", LuaScriptInterface::luaIsInArray); + //addEvent(callback, delay, ...) lua_register(luaState, "addEvent", LuaScriptInterface::luaAddEvent); @@ -1058,12 +1012,6 @@ void LuaScriptInterface::registerFunctions() //getWaypointPosition(name) lua_register(luaState, "getWaypointPositionByName", LuaScriptInterface::luaGetWaypointPositionByName); - //sendChannelMessage(channelId, type, message) - lua_register(luaState, "sendChannelMessage", LuaScriptInterface::luaSendChannelMessage); - - //sendGuildChannelMessage(guildId, type, message) - lua_register(luaState, "sendGuildChannelMessage", LuaScriptInterface::luaSendGuildChannelMessage); - #ifndef LUAJIT_VERSION //bit operations for Lua, based on bitlib project release 24 //bit.bnot, bit.band, bit.bor, bit.bxor, bit.lshift, bit.rshift @@ -1097,11 +1045,6 @@ void LuaScriptInterface::registerFunctions() registerEnum(ACCOUNT_TYPE_GAMEMASTER) registerEnum(ACCOUNT_TYPE_GOD) - registerEnum(BUG_CATEGORY_MAP) - registerEnum(BUG_CATEGORY_TYPO) - registerEnum(BUG_CATEGORY_TECHNICAL) - registerEnum(BUG_CATEGORY_OTHER) - registerEnum(CALLBACK_PARAM_LEVELMAGICVALUE) registerEnum(CALLBACK_PARAM_SKILLVALUE) registerEnum(CALLBACK_PARAM_TARGETTILE) @@ -1126,14 +1069,11 @@ void LuaScriptInterface::registerFunctions() registerEnum(COMBAT_ENERGYDAMAGE) registerEnum(COMBAT_EARTHDAMAGE) registerEnum(COMBAT_FIREDAMAGE) + registerEnum(COMBAT_DROWNDAMAGE) registerEnum(COMBAT_UNDEFINEDDAMAGE) registerEnum(COMBAT_LIFEDRAIN) registerEnum(COMBAT_MANADRAIN) registerEnum(COMBAT_HEALING) - registerEnum(COMBAT_DROWNDAMAGE) - registerEnum(COMBAT_ICEDAMAGE) - registerEnum(COMBAT_HOLYDAMAGE) - registerEnum(COMBAT_DEATHDAMAGE) registerEnum(COMBAT_PARAM_TYPE) registerEnum(COMBAT_PARAM_EFFECT) @@ -1145,12 +1085,14 @@ void LuaScriptInterface::registerFunctions() registerEnum(COMBAT_PARAM_AGGRESSIVE) registerEnum(COMBAT_PARAM_DISPEL) registerEnum(COMBAT_PARAM_USECHARGES) + registerEnum(COMBAT_PARAM_DECREASEDAMAGE) + registerEnum(COMBAT_PARAM_MAXIMUMDECREASEDDAMAGE) registerEnum(CONDITION_NONE) registerEnum(CONDITION_POISON) registerEnum(CONDITION_FIRE) registerEnum(CONDITION_ENERGY) - registerEnum(CONDITION_BLEEDING) + registerEnum(CONDITION_DROWN) registerEnum(CONDITION_HASTE) registerEnum(CONDITION_PARALYZE) registerEnum(CONDITION_OUTFIT) @@ -1159,22 +1101,14 @@ void LuaScriptInterface::registerFunctions() registerEnum(CONDITION_MANASHIELD) registerEnum(CONDITION_INFIGHT) registerEnum(CONDITION_DRUNK) - registerEnum(CONDITION_EXHAUST_WEAPON) registerEnum(CONDITION_REGENERATION) registerEnum(CONDITION_SOUL) - registerEnum(CONDITION_DROWN) registerEnum(CONDITION_MUTED) registerEnum(CONDITION_CHANNELMUTEDTICKS) registerEnum(CONDITION_YELLTICKS) registerEnum(CONDITION_ATTRIBUTES) - registerEnum(CONDITION_FREEZING) - registerEnum(CONDITION_DAZZLED) - registerEnum(CONDITION_CURSED) - registerEnum(CONDITION_EXHAUST_COMBAT) - registerEnum(CONDITION_EXHAUST_HEAL) + registerEnum(CONDITION_EXHAUST) registerEnum(CONDITION_PACIFIED) - registerEnum(CONDITION_SPELLCOOLDOWN) - registerEnum(CONDITION_SPELLGROUPCOOLDOWN) registerEnum(CONDITIONID_DEFAULT) registerEnum(CONDITIONID_COMBAT) @@ -1205,7 +1139,6 @@ void LuaScriptInterface::registerFunctions() registerEnum(CONDITION_PARAM_MAXVALUE) registerEnum(CONDITION_PARAM_STARTVALUE) registerEnum(CONDITION_PARAM_TICKINTERVAL) - registerEnum(CONDITION_PARAM_FORCEUPDATE) registerEnum(CONDITION_PARAM_SKILL_MELEE) registerEnum(CONDITION_PARAM_SKILL_FIST) registerEnum(CONDITION_PARAM_SKILL_CLUB) @@ -1221,6 +1154,7 @@ void LuaScriptInterface::registerFunctions() registerEnum(CONDITION_PARAM_STAT_MAXMANAPOINTSPERCENT) registerEnum(CONDITION_PARAM_STAT_MAGICPOINTSPERCENT) registerEnum(CONDITION_PARAM_PERIODICDAMAGE) + registerEnum(CONDITION_PARAM_HIT_DAMAGE) registerEnum(CONDITION_PARAM_SKILL_MELEEPERCENT) registerEnum(CONDITION_PARAM_SKILL_FISTPERCENT) registerEnum(CONDITION_PARAM_SKILL_CLUBPERCENT) @@ -1229,16 +1163,8 @@ void LuaScriptInterface::registerFunctions() registerEnum(CONDITION_PARAM_SKILL_DISTANCEPERCENT) registerEnum(CONDITION_PARAM_SKILL_SHIELDPERCENT) registerEnum(CONDITION_PARAM_SKILL_FISHINGPERCENT) - registerEnum(CONDITION_PARAM_BUFF_SPELL) registerEnum(CONDITION_PARAM_SUBID) registerEnum(CONDITION_PARAM_FIELD) - registerEnum(CONDITION_PARAM_DISABLE_DEFENSE) - registerEnum(CONDITION_PARAM_SPECIALSKILL_CRITICALHITCHANCE) - registerEnum(CONDITION_PARAM_SPECIALSKILL_CRITICALHITAMOUNT) - registerEnum(CONDITION_PARAM_SPECIALSKILL_LIFELEECHCHANCE) - registerEnum(CONDITION_PARAM_SPECIALSKILL_LIFELEECHAMOUNT) - registerEnum(CONDITION_PARAM_SPECIALSKILL_MANALEECHCHANCE) - registerEnum(CONDITION_PARAM_SPECIALSKILL_MANALEECHAMOUNT) registerEnum(CONST_ME_NONE) registerEnum(CONST_ME_DRAWBLOOD) @@ -1268,65 +1194,6 @@ void LuaScriptInterface::registerFunctions() registerEnum(CONST_ME_SOUND_WHITE) registerEnum(CONST_ME_BUBBLES) registerEnum(CONST_ME_CRAPS) - registerEnum(CONST_ME_GIFT_WRAPS) - registerEnum(CONST_ME_FIREWORK_YELLOW) - registerEnum(CONST_ME_FIREWORK_RED) - registerEnum(CONST_ME_FIREWORK_BLUE) - registerEnum(CONST_ME_STUN) - registerEnum(CONST_ME_SLEEP) - registerEnum(CONST_ME_WATERCREATURE) - registerEnum(CONST_ME_GROUNDSHAKER) - registerEnum(CONST_ME_HEARTS) - registerEnum(CONST_ME_FIREATTACK) - registerEnum(CONST_ME_ENERGYAREA) - registerEnum(CONST_ME_SMALLCLOUDS) - registerEnum(CONST_ME_HOLYDAMAGE) - registerEnum(CONST_ME_BIGCLOUDS) - registerEnum(CONST_ME_ICEAREA) - registerEnum(CONST_ME_ICETORNADO) - registerEnum(CONST_ME_ICEATTACK) - registerEnum(CONST_ME_STONES) - registerEnum(CONST_ME_SMALLPLANTS) - registerEnum(CONST_ME_CARNIPHILA) - registerEnum(CONST_ME_PURPLEENERGY) - registerEnum(CONST_ME_YELLOWENERGY) - registerEnum(CONST_ME_HOLYAREA) - registerEnum(CONST_ME_BIGPLANTS) - registerEnum(CONST_ME_CAKE) - registerEnum(CONST_ME_GIANTICE) - registerEnum(CONST_ME_WATERSPLASH) - registerEnum(CONST_ME_PLANTATTACK) - registerEnum(CONST_ME_TUTORIALARROW) - registerEnum(CONST_ME_TUTORIALSQUARE) - registerEnum(CONST_ME_MIRRORHORIZONTAL) - registerEnum(CONST_ME_MIRRORVERTICAL) - registerEnum(CONST_ME_SKULLHORIZONTAL) - registerEnum(CONST_ME_SKULLVERTICAL) - registerEnum(CONST_ME_ASSASSIN) - registerEnum(CONST_ME_STEPSHORIZONTAL) - registerEnum(CONST_ME_BLOODYSTEPS) - registerEnum(CONST_ME_STEPSVERTICAL) - registerEnum(CONST_ME_YALAHARIGHOST) - registerEnum(CONST_ME_BATS) - registerEnum(CONST_ME_SMOKE) - registerEnum(CONST_ME_INSECTS) - registerEnum(CONST_ME_DRAGONHEAD) - registerEnum(CONST_ME_ORCSHAMAN) - registerEnum(CONST_ME_ORCSHAMAN_FIRE) - registerEnum(CONST_ME_THUNDER) - registerEnum(CONST_ME_FERUMBRAS) - registerEnum(CONST_ME_CONFETTI_HORIZONTAL) - registerEnum(CONST_ME_CONFETTI_VERTICAL) - registerEnum(CONST_ME_BLACKSMOKE) - registerEnum(CONST_ME_REDSMOKE) - registerEnum(CONST_ME_YELLOWSMOKE) - registerEnum(CONST_ME_GREENSMOKE) - registerEnum(CONST_ME_PURPLESMOKE) - registerEnum(CONST_ME_EARLY_THUNDER) - registerEnum(CONST_ME_RAGIAZ_BONECAPSULE) - registerEnum(CONST_ME_CRITICAL_DAMAGE) - registerEnum(CONST_ME_PLUNGING_FISH) - registerEnum(CONST_ANI_NONE) registerEnum(CONST_ANI_SPEAR) registerEnum(CONST_ANI_BOLT) @@ -1343,42 +1210,6 @@ void LuaScriptInterface::registerFunctions() registerEnum(CONST_ANI_SNOWBALL) registerEnum(CONST_ANI_POWERBOLT) registerEnum(CONST_ANI_POISON) - registerEnum(CONST_ANI_INFERNALBOLT) - registerEnum(CONST_ANI_HUNTINGSPEAR) - registerEnum(CONST_ANI_ENCHANTEDSPEAR) - registerEnum(CONST_ANI_REDSTAR) - registerEnum(CONST_ANI_GREENSTAR) - registerEnum(CONST_ANI_ROYALSPEAR) - registerEnum(CONST_ANI_SNIPERARROW) - registerEnum(CONST_ANI_ONYXARROW) - registerEnum(CONST_ANI_PIERCINGBOLT) - registerEnum(CONST_ANI_WHIRLWINDSWORD) - registerEnum(CONST_ANI_WHIRLWINDAXE) - registerEnum(CONST_ANI_WHIRLWINDCLUB) - registerEnum(CONST_ANI_ETHEREALSPEAR) - registerEnum(CONST_ANI_ICE) - registerEnum(CONST_ANI_EARTH) - registerEnum(CONST_ANI_HOLY) - registerEnum(CONST_ANI_SUDDENDEATH) - registerEnum(CONST_ANI_FLASHARROW) - registerEnum(CONST_ANI_FLAMMINGARROW) - registerEnum(CONST_ANI_SHIVERARROW) - registerEnum(CONST_ANI_ENERGYBALL) - registerEnum(CONST_ANI_SMALLICE) - registerEnum(CONST_ANI_SMALLHOLY) - registerEnum(CONST_ANI_SMALLEARTH) - registerEnum(CONST_ANI_EARTHARROW) - registerEnum(CONST_ANI_EXPLOSION) - registerEnum(CONST_ANI_CAKE) - registerEnum(CONST_ANI_TARSALARROW) - registerEnum(CONST_ANI_VORTEXBOLT) - registerEnum(CONST_ANI_PRISMATICBOLT) - registerEnum(CONST_ANI_CRYSTALLINEARROW) - registerEnum(CONST_ANI_DRILLBOLT) - registerEnum(CONST_ANI_ENVENOMEDARROW) - registerEnum(CONST_ANI_GLOOTHSPEAR) - registerEnum(CONST_ANI_SIMPLEARROW) - registerEnum(CONST_ANI_WEAPONTYPE) registerEnum(CONST_PROP_BLOCKSOLID) registerEnum(CONST_PROP_HASHEIGHT) @@ -1412,10 +1243,6 @@ void LuaScriptInterface::registerFunctions() registerEnum(CREATURE_EVENT_DEATH) registerEnum(CREATURE_EVENT_KILL) registerEnum(CREATURE_EVENT_ADVANCE) - registerEnum(CREATURE_EVENT_MODALWINDOW) - registerEnum(CREATURE_EVENT_TEXTEDIT) - registerEnum(CREATURE_EVENT_HEALTHCHANGE) - registerEnum(CREATURE_EVENT_MANACHANGE) registerEnum(CREATURE_EVENT_EXTENDED_OPCODE) registerEnum(GAME_STATE_STARTUP) @@ -1433,26 +1260,8 @@ void LuaScriptInterface::registerFunctions() registerEnum(MESSAGE_EVENT_ADVANCE) registerEnum(MESSAGE_STATUS_SMALL) registerEnum(MESSAGE_INFO_DESCR) - registerEnum(MESSAGE_DAMAGE_DEALT) - registerEnum(MESSAGE_DAMAGE_RECEIVED) - registerEnum(MESSAGE_HEALED) - registerEnum(MESSAGE_EXPERIENCE) - registerEnum(MESSAGE_DAMAGE_OTHERS) - registerEnum(MESSAGE_HEALED_OTHERS) - registerEnum(MESSAGE_EXPERIENCE_OTHERS) registerEnum(MESSAGE_EVENT_DEFAULT) - registerEnum(MESSAGE_GUILD) - registerEnum(MESSAGE_PARTY_MANAGEMENT) - registerEnum(MESSAGE_PARTY) - registerEnum(MESSAGE_EVENT_ORANGE) registerEnum(MESSAGE_STATUS_CONSOLE_ORANGE) - registerEnum(MESSAGE_LOOT) - - registerEnum(CREATURETYPE_PLAYER) - registerEnum(CREATURETYPE_MONSTER) - registerEnum(CREATURETYPE_NPC) - registerEnum(CREATURETYPE_SUMMON_OWN) - registerEnum(CREATURETYPE_SUMMON_OTHERS) registerEnum(CLIENTOS_LINUX) registerEnum(CLIENTOS_WINDOWS) @@ -1461,13 +1270,9 @@ void LuaScriptInterface::registerFunctions() registerEnum(CLIENTOS_OTCLIENT_WINDOWS) registerEnum(CLIENTOS_OTCLIENT_MAC) - registerEnum(FIGHTMODE_ATTACK) - registerEnum(FIGHTMODE_BALANCED) - registerEnum(FIGHTMODE_DEFENSE) - registerEnum(ITEM_ATTRIBUTE_NONE) registerEnum(ITEM_ATTRIBUTE_ACTIONID) - registerEnum(ITEM_ATTRIBUTE_UNIQUEID) + registerEnum(ITEM_ATTRIBUTE_MOVEMENTID) registerEnum(ITEM_ATTRIBUTE_DESCRIPTION) registerEnum(ITEM_ATTRIBUTE_TEXT) registerEnum(ITEM_ATTRIBUTE_DATE) @@ -1478,9 +1283,7 @@ void LuaScriptInterface::registerFunctions() registerEnum(ITEM_ATTRIBUTE_WEIGHT) registerEnum(ITEM_ATTRIBUTE_ATTACK) registerEnum(ITEM_ATTRIBUTE_DEFENSE) - registerEnum(ITEM_ATTRIBUTE_EXTRADEFENSE) registerEnum(ITEM_ATTRIBUTE_ARMOR) - registerEnum(ITEM_ATTRIBUTE_HITCHANCE) registerEnum(ITEM_ATTRIBUTE_SHOOTRANGE) registerEnum(ITEM_ATTRIBUTE_OWNER) registerEnum(ITEM_ATTRIBUTE_DURATION) @@ -1489,10 +1292,15 @@ void LuaScriptInterface::registerFunctions() registerEnum(ITEM_ATTRIBUTE_CHARGES) registerEnum(ITEM_ATTRIBUTE_FLUIDTYPE) registerEnum(ITEM_ATTRIBUTE_DOORID) + registerEnum(ITEM_ATTRIBUTE_KEYNUMBER) + registerEnum(ITEM_ATTRIBUTE_KEYHOLENUMBER) + registerEnum(ITEM_ATTRIBUTE_DOORQUESTNUMBER) + registerEnum(ITEM_ATTRIBUTE_DOORQUESTVALUE) + registerEnum(ITEM_ATTRIBUTE_DOORLEVEL) + registerEnum(ITEM_ATTRIBUTE_CHESTQUESTNUMBER) registerEnum(ITEM_TYPE_DEPOT) registerEnum(ITEM_TYPE_MAILBOX) - registerEnum(ITEM_TYPE_TRASHHOLDER) registerEnum(ITEM_TYPE_CONTAINER) registerEnum(ITEM_TYPE_DOOR) registerEnum(ITEM_TYPE_MAGICFIELD) @@ -1500,9 +1308,8 @@ void LuaScriptInterface::registerFunctions() registerEnum(ITEM_TYPE_BED) registerEnum(ITEM_TYPE_KEY) registerEnum(ITEM_TYPE_RUNE) + registerEnum(ITEM_TYPE_CHEST) - registerEnum(ITEM_BAG) - registerEnum(ITEM_SHOPPING_BAG) registerEnum(ITEM_GOLD_COIN) registerEnum(ITEM_PLATINUM_COIN) registerEnum(ITEM_CRYSTAL_COIN) @@ -1524,10 +1331,8 @@ void LuaScriptInterface::registerFunctions() registerEnum(ITEM_ENERGYFIELD_NOPVP) registerEnum(ITEM_MAGICWALL) registerEnum(ITEM_MAGICWALL_PERSISTENT) - registerEnum(ITEM_MAGICWALL_SAFE) registerEnum(ITEM_WILDGROWTH) registerEnum(ITEM_WILDGROWTH_PERSISTENT) - registerEnum(ITEM_WILDGROWTH_SAFE) registerEnum(PlayerFlag_CannotUseCombat) registerEnum(PlayerFlag_CannotAttackPlayer) @@ -1567,38 +1372,17 @@ void LuaScriptInterface::registerFunctions() registerEnum(PlayerFlag_IgnoreWeaponCheck) registerEnum(PlayerFlag_CannotBeMuted) registerEnum(PlayerFlag_IsAlwaysPremium) + registerEnum(PlayerFlag_SpecialMoveUse) registerEnum(PLAYERSEX_FEMALE) registerEnum(PLAYERSEX_MALE) - registerEnum(REPORT_REASON_NAMEINAPPROPRIATE) - registerEnum(REPORT_REASON_NAMEPOORFORMATTED) - registerEnum(REPORT_REASON_NAMEADVERTISING) - registerEnum(REPORT_REASON_NAMEUNFITTING) - registerEnum(REPORT_REASON_NAMERULEVIOLATION) - registerEnum(REPORT_REASON_INSULTINGSTATEMENT) - registerEnum(REPORT_REASON_SPAMMING) - registerEnum(REPORT_REASON_ADVERTISINGSTATEMENT) - registerEnum(REPORT_REASON_UNFITTINGSTATEMENT) - registerEnum(REPORT_REASON_LANGUAGESTATEMENT) - registerEnum(REPORT_REASON_DISCLOSURE) - registerEnum(REPORT_REASON_RULEVIOLATION) - registerEnum(REPORT_REASON_STATEMENT_BUGABUSE) - registerEnum(REPORT_REASON_UNOFFICIALSOFTWARE) - registerEnum(REPORT_REASON_PRETENDING) - registerEnum(REPORT_REASON_HARASSINGOWNERS) - registerEnum(REPORT_REASON_FALSEINFO) - registerEnum(REPORT_REASON_ACCOUNTSHARING) - registerEnum(REPORT_REASON_STEALINGDATA) - registerEnum(REPORT_REASON_SERVICEATTACKING) - registerEnum(REPORT_REASON_SERVICEAGREEMENT) - - registerEnum(REPORT_TYPE_NAME) - registerEnum(REPORT_TYPE_STATEMENT) - registerEnum(REPORT_TYPE_BOT) - registerEnum(VOCATION_NONE) + registerEnum(FIGHTMODE_ATTACK) + registerEnum(FIGHTMODE_BALANCED) + registerEnum(FIGHTMODE_DEFENSE) + registerEnum(SKILL_FIST) registerEnum(SKILL_CLUB) registerEnum(SKILL_SWORD) @@ -1609,34 +1393,36 @@ void LuaScriptInterface::registerFunctions() registerEnum(SKILL_MAGLEVEL) registerEnum(SKILL_LEVEL) - registerEnum(SPECIALSKILL_CRITICALHITCHANCE) - registerEnum(SPECIALSKILL_CRITICALHITAMOUNT) - registerEnum(SPECIALSKILL_LIFELEECHCHANCE) - registerEnum(SPECIALSKILL_LIFELEECHAMOUNT) - registerEnum(SPECIALSKILL_MANALEECHCHANCE) - registerEnum(SPECIALSKILL_MANALEECHAMOUNT) - registerEnum(SKULL_NONE) registerEnum(SKULL_YELLOW) registerEnum(SKULL_GREEN) registerEnum(SKULL_WHITE) registerEnum(SKULL_RED) - registerEnum(SKULL_BLACK) - registerEnum(SKULL_ORANGE) + + registerEnum(FLUID_NONE) + registerEnum(FLUID_WATER) + registerEnum(FLUID_WINE) + registerEnum(FLUID_BEER) + registerEnum(FLUID_MUD) + registerEnum(FLUID_BLOOD) + registerEnum(FLUID_SLIME) + registerEnum(FLUID_OIL) + registerEnum(FLUID_URINE) + registerEnum(FLUID_MILK) + registerEnum(FLUID_MANAFLUID) + registerEnum(FLUID_LIFEFLUID) + registerEnum(FLUID_LEMONADE) + registerEnum(FLUID_RUM) + registerEnum(FLUID_COCONUTMILK) + registerEnum(FLUID_FRUITJUICE) registerEnum(TALKTYPE_SAY) registerEnum(TALKTYPE_WHISPER) registerEnum(TALKTYPE_YELL) - registerEnum(TALKTYPE_PRIVATE_FROM) - registerEnum(TALKTYPE_PRIVATE_TO) registerEnum(TALKTYPE_CHANNEL_Y) registerEnum(TALKTYPE_CHANNEL_O) - registerEnum(TALKTYPE_PRIVATE_NP) - registerEnum(TALKTYPE_PRIVATE_PN) registerEnum(TALKTYPE_BROADCAST) registerEnum(TALKTYPE_CHANNEL_R1) - registerEnum(TALKTYPE_PRIVATE_RED_FROM) - registerEnum(TALKTYPE_PRIVATE_RED_TO) registerEnum(TALKTYPE_MONSTER_SAY) registerEnum(TALKTYPE_MONSTER_YELL) registerEnum(TALKTYPE_CHANNEL_R2) @@ -1649,9 +1435,7 @@ void LuaScriptInterface::registerFunctions() registerEnum(TEXTCOLOR_LIGHTGREY) registerEnum(TEXTCOLOR_SKYBLUE) registerEnum(TEXTCOLOR_PURPLE) - registerEnum(TEXTCOLOR_ELECTRICPURPLE) registerEnum(TEXTCOLOR_RED) - registerEnum(TEXTCOLOR_PASTELRED) registerEnum(TEXTCOLOR_ORANGE) registerEnum(TEXTCOLOR_YELLOW) registerEnum(TEXTCOLOR_WHITE_EXP) @@ -1662,16 +1446,10 @@ void LuaScriptInterface::registerFunctions() registerEnum(TILESTATE_NOPVPZONE) registerEnum(TILESTATE_NOLOGOUT) registerEnum(TILESTATE_PVPZONE) - registerEnum(TILESTATE_FLOORCHANGE) - registerEnum(TILESTATE_FLOORCHANGE_DOWN) - registerEnum(TILESTATE_FLOORCHANGE_NORTH) - registerEnum(TILESTATE_FLOORCHANGE_SOUTH) - registerEnum(TILESTATE_FLOORCHANGE_EAST) - registerEnum(TILESTATE_FLOORCHANGE_WEST) + registerEnum(TILESTATE_REFRESH) registerEnum(TILESTATE_TELEPORT) registerEnum(TILESTATE_MAGICFIELD) registerEnum(TILESTATE_MAILBOX) - registerEnum(TILESTATE_TRASHHOLDER) registerEnum(TILESTATE_BED) registerEnum(TILESTATE_DEPOT) registerEnum(TILESTATE_BLOCKSOLID) @@ -1680,8 +1458,6 @@ void LuaScriptInterface::registerFunctions() registerEnum(TILESTATE_IMMOVABLEBLOCKPATH) registerEnum(TILESTATE_IMMOVABLENOFIELDBLOCKPATH) registerEnum(TILESTATE_NOFIELDBLOCKPATH) - registerEnum(TILESTATE_FLOORCHANGE_SOUTH_ALT) - registerEnum(TILESTATE_FLOORCHANGE_EAST_ALT) registerEnum(TILESTATE_SUPPORTS_HANGABLE) registerEnum(WEAPON_NONE) @@ -1733,35 +1509,6 @@ void LuaScriptInterface::registerFunctions() registerEnum(GUEST_LIST) registerEnum(SUBOWNER_LIST) - // Use with npc:setSpeechBubble - registerEnum(SPEECHBUBBLE_NONE) - registerEnum(SPEECHBUBBLE_NORMAL) - registerEnum(SPEECHBUBBLE_TRADE) - registerEnum(SPEECHBUBBLE_QUEST) - registerEnum(SPEECHBUBBLE_QUESTTRADER) - - // Use with player:addMapMark - registerEnum(MAPMARK_TICK) - registerEnum(MAPMARK_QUESTION) - registerEnum(MAPMARK_EXCLAMATION) - registerEnum(MAPMARK_STAR) - registerEnum(MAPMARK_CROSS) - registerEnum(MAPMARK_TEMPLE) - registerEnum(MAPMARK_KISS) - registerEnum(MAPMARK_SHOVEL) - registerEnum(MAPMARK_SWORD) - registerEnum(MAPMARK_FLAG) - registerEnum(MAPMARK_LOCK) - registerEnum(MAPMARK_BAG) - registerEnum(MAPMARK_SKULL) - registerEnum(MAPMARK_DOLLAR) - registerEnum(MAPMARK_REDNORTH) - registerEnum(MAPMARK_REDSOUTH) - registerEnum(MAPMARK_REDEAST) - registerEnum(MAPMARK_REDWEST) - registerEnum(MAPMARK_GREENNORTH) - registerEnum(MAPMARK_GREENSOUTH) - // Use with Game.getReturnMessage registerEnum(RETURNVALUE_NOERROR) registerEnum(RETURNVALUE_NOTPOSSIBLE) @@ -1822,7 +1569,7 @@ void LuaScriptInterface::registerFunctions() registerEnum(RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL) registerEnum(RETURNVALUE_CANNOTCONJUREITEMHERE) registerEnum(RETURNVALUE_YOUNEEDTOSPLITYOURSPEARS) - registerEnum(RETURNVALUE_NAMEISTOOAMBIGUOUS) + registerEnum(RETURNVALUE_NAMEISTOOAMBIGIOUS) registerEnum(RETURNVALUE_CANONLYUSEONESHIELD) registerEnum(RETURNVALUE_NOPARTYMEMBERSINRANGE) registerEnum(RETURNVALUE_YOUARENOTTHEOWNER) @@ -1831,10 +1578,11 @@ void LuaScriptInterface::registerFunctions() registerEnum(RETURNVALUE_TRADEPLAYERALREADYOWNSAHOUSE) registerEnum(RETURNVALUE_TRADEPLAYERHIGHESTBIDDER) registerEnum(RETURNVALUE_YOUCANNOTTRADETHISHOUSE) - + registerEnum(RELOAD_TYPE_ALL) registerEnum(RELOAD_TYPE_ACTIONS) registerEnum(RELOAD_TYPE_CHAT) + registerEnum(RELOAD_TYPE_COMMANDS) registerEnum(RELOAD_TYPE_CONFIG) registerEnum(RELOAD_TYPE_CREATURESCRIPTS) registerEnum(RELOAD_TYPE_EVENTS) @@ -1847,28 +1595,10 @@ void LuaScriptInterface::registerFunctions() registerEnum(RELOAD_TYPE_NPCS) registerEnum(RELOAD_TYPE_QUESTS) registerEnum(RELOAD_TYPE_RAIDS) - registerEnum(RELOAD_TYPE_SCRIPTS) registerEnum(RELOAD_TYPE_SPELLS) registerEnum(RELOAD_TYPE_TALKACTIONS) registerEnum(RELOAD_TYPE_WEAPONS) - registerEnum(ZONE_PROTECTION) - registerEnum(ZONE_NOPVP) - registerEnum(ZONE_PVP) - registerEnum(ZONE_NOLOGOUT) - registerEnum(ZONE_NORMAL) - - registerEnum(MAX_LOOTCHANCE) - - registerEnum(SPELL_INSTANT) - registerEnum(SPELL_RUNE) - - registerEnum(MONSTERS_EVENT_THINK) - registerEnum(MONSTERS_EVENT_APPEAR) - registerEnum(MONSTERS_EVENT_DISAPPEAR) - registerEnum(MONSTERS_EVENT_MOVE) - registerEnum(MONSTERS_EVENT_SAY) - // _G registerGlobalVariable("INDEX_WHEREEVER", INDEX_WHEREEVER); registerGlobalBoolean("VIRTUAL_PARENT", true); @@ -1881,7 +1611,6 @@ void LuaScriptInterface::registerFunctions() registerEnumIn("configKeys", ConfigManager::ALLOW_CHANGEOUTFIT) registerEnumIn("configKeys", ConfigManager::ONE_PLAYER_ON_ACCOUNT) - registerEnumIn("configKeys", ConfigManager::AIMBOT_HOTKEY_ENABLED) registerEnumIn("configKeys", ConfigManager::REMOVE_RUNE_CHARGES) registerEnumIn("configKeys", ConfigManager::EXPERIENCE_FROM_PLAYERS) registerEnumIn("configKeys", ConfigManager::FREE_PREMIUM) @@ -1889,19 +1618,10 @@ void LuaScriptInterface::registerFunctions() registerEnumIn("configKeys", ConfigManager::ALLOW_CLONES) registerEnumIn("configKeys", ConfigManager::BIND_ONLY_GLOBAL_ADDRESS) registerEnumIn("configKeys", ConfigManager::OPTIMIZE_DATABASE) - registerEnumIn("configKeys", ConfigManager::MARKET_PREMIUM) - registerEnumIn("configKeys", ConfigManager::EMOTE_SPELLS) registerEnumIn("configKeys", ConfigManager::STAMINA_SYSTEM) registerEnumIn("configKeys", ConfigManager::WARN_UNSAFE_SCRIPTS) registerEnumIn("configKeys", ConfigManager::CONVERT_UNSAFE_SCRIPTS) - registerEnumIn("configKeys", ConfigManager::CLASSIC_EQUIPMENT_SLOTS) - registerEnumIn("configKeys", ConfigManager::CLASSIC_ATTACK_SPEED) - registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_NOTIFY_MESSAGE) - registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_NOTIFY_DURATION) - registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_CLEAN_MAP) - registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_CLOSE) - registerEnumIn("configKeys", ConfigManager::SERVER_SAVE_SHUTDOWN) - registerEnumIn("configKeys", ConfigManager::ONLINE_OFFLINE_CHARLIST) + registerEnumIn("configKeys", ConfigManager::TELEPORT_NEWBIES) registerEnumIn("configKeys", ConfigManager::MAP_NAME) registerEnumIn("configKeys", ConfigManager::HOUSE_RENT_PERIOD) @@ -1931,9 +1651,6 @@ void LuaScriptInterface::registerFunctions() registerEnumIn("configKeys", ConfigManager::RATE_LOOT) registerEnumIn("configKeys", ConfigManager::RATE_MAGIC) registerEnumIn("configKeys", ConfigManager::RATE_SPAWN) - registerEnumIn("configKeys", ConfigManager::HOUSE_PRICE) - registerEnumIn("configKeys", ConfigManager::KILLS_TO_RED) - registerEnumIn("configKeys", ConfigManager::KILLS_TO_BLACK) registerEnumIn("configKeys", ConfigManager::MAX_MESSAGEBUFFER) registerEnumIn("configKeys", ConfigManager::ACTIONS_DELAY_INTERVAL) registerEnumIn("configKeys", ConfigManager::EX_ACTIONS_DELAY_INTERVAL) @@ -1941,17 +1658,24 @@ void LuaScriptInterface::registerFunctions() registerEnumIn("configKeys", ConfigManager::PROTECTION_LEVEL) registerEnumIn("configKeys", ConfigManager::DEATH_LOSE_PERCENT) registerEnumIn("configKeys", ConfigManager::STATUSQUERY_TIMEOUT) - registerEnumIn("configKeys", ConfigManager::FRAG_TIME) registerEnumIn("configKeys", ConfigManager::WHITE_SKULL_TIME) + registerEnumIn("configKeys", ConfigManager::RED_SKULL_TIME) + registerEnumIn("configKeys", ConfigManager::KILLS_DAY_RED_SKULL) + registerEnumIn("configKeys", ConfigManager::KILLS_WEEK_RED_SKULL) + registerEnumIn("configKeys", ConfigManager::KILLS_MONTH_RED_SKULL) + registerEnumIn("configKeys", ConfigManager::KILLS_DAY_BANISHMENT) + registerEnumIn("configKeys", ConfigManager::KILLS_WEEK_BANISHMENT) + registerEnumIn("configKeys", ConfigManager::KILLS_MONTH_BANISHMENT) registerEnumIn("configKeys", ConfigManager::GAME_PORT) registerEnumIn("configKeys", ConfigManager::LOGIN_PORT) registerEnumIn("configKeys", ConfigManager::STATUS_PORT) registerEnumIn("configKeys", ConfigManager::STAIRHOP_DELAY) - registerEnumIn("configKeys", ConfigManager::MARKET_OFFER_DURATION) - registerEnumIn("configKeys", ConfigManager::CHECK_EXPIRED_MARKET_OFFERS_EACH_MINUTES) - registerEnumIn("configKeys", ConfigManager::MAX_MARKET_OFFERS_AT_A_TIME_PER_PLAYER) registerEnumIn("configKeys", ConfigManager::EXP_FROM_PLAYERS_LEVEL_RANGE) registerEnumIn("configKeys", ConfigManager::MAX_PACKETS_PER_SECOND) + registerEnumIn("configKeys", ConfigManager::NEWBIE_TOWN) + registerEnumIn("configKeys", ConfigManager::NEWBIE_LEVEL_THRESHOLD) + registerEnumIn("configKeys", ConfigManager::BLOCK_HEIGHT) + registerEnumIn("configKeys", ConfigManager::DROP_ITEMS) // os registerMethod("os", "mtime", LuaScriptInterface::luaSystemTime); @@ -1987,12 +1711,9 @@ void LuaScriptInterface::registerFunctions() registerMethod("Game", "createMonster", LuaScriptInterface::luaGameCreateMonster); registerMethod("Game", "createNpc", LuaScriptInterface::luaGameCreateNpc); registerMethod("Game", "createTile", LuaScriptInterface::luaGameCreateTile); - registerMethod("Game", "createMonsterType", LuaScriptInterface::luaGameCreateMonsterType); registerMethod("Game", "startRaid", LuaScriptInterface::luaGameStartRaid); - registerMethod("Game", "getClientVersion", LuaScriptInterface::luaGameGetClientVersion); - registerMethod("Game", "reload", LuaScriptInterface::luaGameReload); // Variant @@ -2013,6 +1734,7 @@ void LuaScriptInterface::registerFunctions() registerMethod("Position", "sendMagicEffect", LuaScriptInterface::luaPositionSendMagicEffect); registerMethod("Position", "sendDistanceEffect", LuaScriptInterface::luaPositionSendDistanceEffect); + registerMethod("Position", "sendMonsterSay", LuaScriptInterface::luaPositionSendMonsterSay); // Tile registerClass("Tile", "", LuaScriptInterface::luaTileCreate); @@ -2052,8 +1774,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("Tile", "hasFlag", LuaScriptInterface::luaTileHasFlag); registerMethod("Tile", "queryAdd", LuaScriptInterface::luaTileQueryAdd); - registerMethod("Tile", "addItem", LuaScriptInterface::luaTileAddItem); - registerMethod("Tile", "addItemEx", LuaScriptInterface::luaTileAddItemEx); registerMethod("Tile", "getHouse", LuaScriptInterface::luaTileGetHouse); @@ -2084,36 +1804,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("NetworkMessage", "skipBytes", LuaScriptInterface::luaNetworkMessageSkipBytes); registerMethod("NetworkMessage", "sendToPlayer", LuaScriptInterface::luaNetworkMessageSendToPlayer); - // ModalWindow - registerClass("ModalWindow", "", LuaScriptInterface::luaModalWindowCreate); - registerMetaMethod("ModalWindow", "__eq", LuaScriptInterface::luaUserdataCompare); - registerMetaMethod("ModalWindow", "__gc", LuaScriptInterface::luaModalWindowDelete); - registerMethod("ModalWindow", "delete", LuaScriptInterface::luaModalWindowDelete); - - registerMethod("ModalWindow", "getId", LuaScriptInterface::luaModalWindowGetId); - registerMethod("ModalWindow", "getTitle", LuaScriptInterface::luaModalWindowGetTitle); - registerMethod("ModalWindow", "getMessage", LuaScriptInterface::luaModalWindowGetMessage); - - registerMethod("ModalWindow", "setTitle", LuaScriptInterface::luaModalWindowSetTitle); - registerMethod("ModalWindow", "setMessage", LuaScriptInterface::luaModalWindowSetMessage); - - registerMethod("ModalWindow", "getButtonCount", LuaScriptInterface::luaModalWindowGetButtonCount); - registerMethod("ModalWindow", "getChoiceCount", LuaScriptInterface::luaModalWindowGetChoiceCount); - - registerMethod("ModalWindow", "addButton", LuaScriptInterface::luaModalWindowAddButton); - registerMethod("ModalWindow", "addChoice", LuaScriptInterface::luaModalWindowAddChoice); - - registerMethod("ModalWindow", "getDefaultEnterButton", LuaScriptInterface::luaModalWindowGetDefaultEnterButton); - registerMethod("ModalWindow", "setDefaultEnterButton", LuaScriptInterface::luaModalWindowSetDefaultEnterButton); - - registerMethod("ModalWindow", "getDefaultEscapeButton", LuaScriptInterface::luaModalWindowGetDefaultEscapeButton); - registerMethod("ModalWindow", "setDefaultEscapeButton", LuaScriptInterface::luaModalWindowSetDefaultEscapeButton); - - registerMethod("ModalWindow", "hasPriority", LuaScriptInterface::luaModalWindowHasPriority); - registerMethod("ModalWindow", "setPriority", LuaScriptInterface::luaModalWindowSetPriority); - - registerMethod("ModalWindow", "sendToPlayer", LuaScriptInterface::luaModalWindowSendToPlayer); - // Item registerClass("Item", "", LuaScriptInterface::luaItemCreate); registerMetaMethod("Item", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -2129,9 +1819,11 @@ void LuaScriptInterface::registerFunctions() registerMethod("Item", "split", LuaScriptInterface::luaItemSplit); registerMethod("Item", "remove", LuaScriptInterface::luaItemRemove); - registerMethod("Item", "getUniqueId", LuaScriptInterface::luaItemGetUniqueId); + registerMethod("Item", "getMovementId", LuaScriptInterface::luaItemGetMovementId); + registerMethod("Item", "setMovementId", LuaScriptInterface::luaItemSetMovementId); registerMethod("Item", "getActionId", LuaScriptInterface::luaItemGetActionId); registerMethod("Item", "setActionId", LuaScriptInterface::luaItemSetActionId); + registerMethod("Item", "getUniqueId", LuaScriptInterface::luaItemGetUniqueId); registerMethod("Item", "getCount", LuaScriptInterface::luaItemGetCount); registerMethod("Item", "getCharges", LuaScriptInterface::luaItemGetCharges); @@ -2151,9 +1843,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("Item", "getAttribute", LuaScriptInterface::luaItemGetAttribute); registerMethod("Item", "setAttribute", LuaScriptInterface::luaItemSetAttribute); registerMethod("Item", "removeAttribute", LuaScriptInterface::luaItemRemoveAttribute); - registerMethod("Item", "getCustomAttribute", LuaScriptInterface::luaItemGetCustomAttribute); - registerMethod("Item", "setCustomAttribute", LuaScriptInterface::luaItemSetCustomAttribute); - registerMethod("Item", "removeCustomAttribute", LuaScriptInterface::luaItemRemoveCustomAttribute); registerMethod("Item", "moveTo", LuaScriptInterface::luaItemMoveTo); registerMethod("Item", "transform", LuaScriptInterface::luaItemTransform); @@ -2162,7 +1851,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("Item", "getDescription", LuaScriptInterface::luaItemGetDescription); registerMethod("Item", "hasProperty", LuaScriptInterface::luaItemHasProperty); - registerMethod("Item", "isLoadedFromMap", LuaScriptInterface::luaItemIsLoadedFromMap); // Container registerClass("Container", "Item", LuaScriptInterface::luaContainerCreate); @@ -2171,7 +1859,7 @@ void LuaScriptInterface::registerFunctions() registerMethod("Container", "getSize", LuaScriptInterface::luaContainerGetSize); registerMethod("Container", "getCapacity", LuaScriptInterface::luaContainerGetCapacity); registerMethod("Container", "getEmptySlots", LuaScriptInterface::luaContainerGetEmptySlots); - registerMethod("Container", "getContentDescription", LuaScriptInterface::luaContainerGetContentDescription); + registerMethod("Container", "getItemHoldingCount", LuaScriptInterface::luaContainerGetItemHoldingCount); registerMethod("Container", "getItemCountById", LuaScriptInterface::luaContainerGetItemCountById); @@ -2179,7 +1867,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("Container", "hasItem", LuaScriptInterface::luaContainerHasItem); registerMethod("Container", "addItem", LuaScriptInterface::luaContainerAddItem); registerMethod("Container", "addItemEx", LuaScriptInterface::luaContainerAddItemEx); - registerMethod("Container", "getCorpseOwner", LuaScriptInterface::luaContainerGetCorpseOwner); // Teleport registerClass("Teleport", "Item", LuaScriptInterface::luaTeleportCreate); @@ -2199,8 +1886,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("Creature", "isRemoved", LuaScriptInterface::luaCreatureIsRemoved); registerMethod("Creature", "isCreature", LuaScriptInterface::luaCreatureIsCreature); registerMethod("Creature", "isInGhostMode", LuaScriptInterface::luaCreatureIsInGhostMode); - registerMethod("Creature", "isHealthHidden", LuaScriptInterface::luaCreatureIsHealthHidden); - registerMethod("Creature", "isImmune", LuaScriptInterface::luaCreatureIsImmune); registerMethod("Creature", "canSee", LuaScriptInterface::luaCreatureCanSee); registerMethod("Creature", "canSeeCreature", LuaScriptInterface::luaCreatureCanSeeCreature); @@ -2227,7 +1912,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("Creature", "changeSpeed", LuaScriptInterface::luaCreatureChangeSpeed); registerMethod("Creature", "setDropLoot", LuaScriptInterface::luaCreatureSetDropLoot); - registerMethod("Creature", "setSkillLoss", LuaScriptInterface::luaCreatureSetSkillLoss); registerMethod("Creature", "getPosition", LuaScriptInterface::luaCreatureGetPosition); registerMethod("Creature", "getTile", LuaScriptInterface::luaCreatureGetTile); @@ -2235,7 +1919,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("Creature", "setDirection", LuaScriptInterface::luaCreatureSetDirection); registerMethod("Creature", "getHealth", LuaScriptInterface::luaCreatureGetHealth); - registerMethod("Creature", "setHealth", LuaScriptInterface::luaCreatureSetHealth); registerMethod("Creature", "addHealth", LuaScriptInterface::luaCreatureAddHealth); registerMethod("Creature", "getMaxHealth", LuaScriptInterface::luaCreatureGetMaxHealth); registerMethod("Creature", "setMaxHealth", LuaScriptInterface::luaCreatureSetMaxHealth); @@ -2250,7 +1933,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("Creature", "getCondition", LuaScriptInterface::luaCreatureGetCondition); registerMethod("Creature", "addCondition", LuaScriptInterface::luaCreatureAddCondition); registerMethod("Creature", "removeCondition", LuaScriptInterface::luaCreatureRemoveCondition); - registerMethod("Creature", "hasCondition", LuaScriptInterface::luaCreatureHasCondition); registerMethod("Creature", "remove", LuaScriptInterface::luaCreatureRemove); registerMethod("Creature", "teleportTo", LuaScriptInterface::luaCreatureTeleportTo); @@ -2263,9 +1945,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("Creature", "getDescription", LuaScriptInterface::luaCreatureGetDescription); registerMethod("Creature", "getPathTo", LuaScriptInterface::luaCreatureGetPathTo); - registerMethod("Creature", "move", LuaScriptInterface::luaCreatureMove); - - registerMethod("Creature", "getZone", LuaScriptInterface::luaCreatureGetZone); // Player registerClass("Player", "Creature", LuaScriptInterface::luaPlayerCreate); @@ -2278,6 +1957,7 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "getAccountId", LuaScriptInterface::luaPlayerGetAccountId); registerMethod("Player", "getLastLoginSaved", LuaScriptInterface::luaPlayerGetLastLoginSaved); registerMethod("Player", "getLastLogout", LuaScriptInterface::luaPlayerGetLastLogout); + registerMethod("Player", "hasFlag", LuaScriptInterface::luaPlayerHasFlag); registerMethod("Player", "getAccountType", LuaScriptInterface::luaPlayerGetAccountType); registerMethod("Player", "setAccountType", LuaScriptInterface::luaPlayerSetAccountType); @@ -2288,10 +1968,10 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "getFreeCapacity", LuaScriptInterface::luaPlayerGetFreeCapacity); registerMethod("Player", "getDepotChest", LuaScriptInterface::luaPlayerGetDepotChest); - registerMethod("Player", "getInbox", LuaScriptInterface::luaPlayerGetInbox); - registerMethod("Player", "getSkullTime", LuaScriptInterface::luaPlayerGetSkullTime); - registerMethod("Player", "setSkullTime", LuaScriptInterface::luaPlayerSetSkullTime); + registerMethod("Player", "getMurderTimestamps", LuaScriptInterface::luaPlayerGetMurderTimestamps); + registerMethod("Player", "getPlayerKillerEnd", LuaScriptInterface::luaPlayerGetPlayerKillerEnd); + registerMethod("Player", "setPlayerKillerEnd", LuaScriptInterface::luaPlayerSetPlayerKillerEnd); registerMethod("Player", "getDeathPenalty", LuaScriptInterface::luaPlayerGetDeathPenalty); registerMethod("Player", "getExperience", LuaScriptInterface::luaPlayerGetExperience); @@ -2316,17 +1996,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "getSkillPercent", LuaScriptInterface::luaPlayerGetSkillPercent); registerMethod("Player", "getSkillTries", LuaScriptInterface::luaPlayerGetSkillTries); registerMethod("Player", "addSkillTries", LuaScriptInterface::luaPlayerAddSkillTries); - registerMethod("Player", "getSpecialSkill", LuaScriptInterface::luaPlayerGetSpecialSkill); - registerMethod("Player", "addSpecialSkill", LuaScriptInterface::luaPlayerAddSpecialSkill); - - registerMethod("Player", "addOfflineTrainingTime", LuaScriptInterface::luaPlayerAddOfflineTrainingTime); - registerMethod("Player", "getOfflineTrainingTime", LuaScriptInterface::luaPlayerGetOfflineTrainingTime); - registerMethod("Player", "removeOfflineTrainingTime", LuaScriptInterface::luaPlayerRemoveOfflineTrainingTime); - - registerMethod("Player", "addOfflineTrainingTries", LuaScriptInterface::luaPlayerAddOfflineTrainingTries); - - registerMethod("Player", "getOfflineTrainingSkill", LuaScriptInterface::luaPlayerGetOfflineTrainingSkill); - registerMethod("Player", "setOfflineTrainingSkill", LuaScriptInterface::luaPlayerSetOfflineTrainingSkill); registerMethod("Player", "getItemCount", LuaScriptInterface::luaPlayerGetItemCount); registerMethod("Player", "getItemById", LuaScriptInterface::luaPlayerGetItemById); @@ -2376,7 +2045,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "showTextDialog", LuaScriptInterface::luaPlayerShowTextDialog); registerMethod("Player", "sendTextMessage", LuaScriptInterface::luaPlayerSendTextMessage); - registerMethod("Player", "sendChannelMessage", LuaScriptInterface::luaPlayerSendChannelMessage); registerMethod("Player", "sendPrivateMessage", LuaScriptInterface::luaPlayerSendPrivateMessage); registerMethod("Player", "channelSay", LuaScriptInterface::luaPlayerChannelSay); registerMethod("Player", "openChannel", LuaScriptInterface::luaPlayerOpenChannel); @@ -2392,10 +2060,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "hasOutfit", LuaScriptInterface::luaPlayerHasOutfit); registerMethod("Player", "sendOutfitWindow", LuaScriptInterface::luaPlayerSendOutfitWindow); - registerMethod("Player", "addMount", LuaScriptInterface::luaPlayerAddMount); - registerMethod("Player", "removeMount", LuaScriptInterface::luaPlayerRemoveMount); - registerMethod("Player", "hasMount", LuaScriptInterface::luaPlayerHasMount); - registerMethod("Player", "getPremiumDays", LuaScriptInterface::luaPlayerGetPremiumDays); registerMethod("Player", "addPremiumDays", LuaScriptInterface::luaPlayerAddPremiumDays); registerMethod("Player", "removePremiumDays", LuaScriptInterface::luaPlayerRemovePremiumDays); @@ -2409,19 +2073,12 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "forgetSpell", LuaScriptInterface::luaPlayerForgetSpell); registerMethod("Player", "hasLearnedSpell", LuaScriptInterface::luaPlayerHasLearnedSpell); - registerMethod("Player", "sendTutorial", LuaScriptInterface::luaPlayerSendTutorial); - registerMethod("Player", "addMapMark", LuaScriptInterface::luaPlayerAddMapMark); - registerMethod("Player", "save", LuaScriptInterface::luaPlayerSave); - registerMethod("Player", "popupFYI", LuaScriptInterface::luaPlayerPopupFYI); registerMethod("Player", "isPzLocked", LuaScriptInterface::luaPlayerIsPzLocked); registerMethod("Player", "getClient", LuaScriptInterface::luaPlayerGetClient); - registerMethod("Player", "getHouse", LuaScriptInterface::luaPlayerGetHouse); - registerMethod("Player", "sendHouseWindow", LuaScriptInterface::luaPlayerSendHouseWindow); - registerMethod("Player", "setEditHouse", LuaScriptInterface::luaPlayerSetEditHouse); registerMethod("Player", "setGhostMode", LuaScriptInterface::luaPlayerSetGhostMode); @@ -2429,12 +2086,7 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "getContainerById", LuaScriptInterface::luaPlayerGetContainerById); registerMethod("Player", "getContainerIndex", LuaScriptInterface::luaPlayerGetContainerIndex); - registerMethod("Player", "getInstantSpells", LuaScriptInterface::luaPlayerGetInstantSpells); - registerMethod("Player", "canCast", LuaScriptInterface::luaPlayerCanCast); - - registerMethod("Player", "hasChaseMode", LuaScriptInterface::luaPlayerHasChaseMode); - registerMethod("Player", "hasSecureMode", LuaScriptInterface::luaPlayerHasSecureMode); - registerMethod("Player", "getFightMode", LuaScriptInterface::luaPlayerGetFightMode); + registerMethod("Player", "getTotalDamage", LuaScriptInterface::luaPlayerGetTotalDamage); // Monster registerClass("Monster", "Creature", LuaScriptInterface::luaMonsterCreate); @@ -2475,9 +2127,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("Npc", "setMasterPos", LuaScriptInterface::luaNpcSetMasterPos); - registerMethod("Npc", "getSpeechBubble", LuaScriptInterface::luaNpcGetSpeechBubble); - registerMethod("Npc", "setSpeechBubble", LuaScriptInterface::luaNpcSetSpeechBubble); - // Guild registerClass("Guild", "", LuaScriptInterface::luaGuildCreate); registerMetaMethod("Guild", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -2490,9 +2139,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("Guild", "getRankById", LuaScriptInterface::luaGuildGetRankById); registerMethod("Guild", "getRankByLevel", LuaScriptInterface::luaGuildGetRankByLevel); - registerMethod("Guild", "getMotd", LuaScriptInterface::luaGuildGetMotd); - registerMethod("Guild", "setMotd", LuaScriptInterface::luaGuildSetMotd); - // Group registerClass("Group", "", LuaScriptInterface::luaGroupCreate); registerMetaMethod("Group", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -2503,14 +2149,12 @@ void LuaScriptInterface::registerFunctions() registerMethod("Group", "getAccess", LuaScriptInterface::luaGroupGetAccess); registerMethod("Group", "getMaxDepotItems", LuaScriptInterface::luaGroupGetMaxDepotItems); registerMethod("Group", "getMaxVipEntries", LuaScriptInterface::luaGroupGetMaxVipEntries); - registerMethod("Group", "hasFlag", LuaScriptInterface::luaGroupHasFlag); // Vocation registerClass("Vocation", "", LuaScriptInterface::luaVocationCreate); registerMetaMethod("Vocation", "__eq", LuaScriptInterface::luaUserdataCompare); registerMethod("Vocation", "getId", LuaScriptInterface::luaVocationGetId); - registerMethod("Vocation", "getClientId", LuaScriptInterface::luaVocationGetClientId); registerMethod("Vocation", "getName", LuaScriptInterface::luaVocationGetName); registerMethod("Vocation", "getDescription", LuaScriptInterface::luaVocationGetDescription); @@ -2563,18 +2207,13 @@ void LuaScriptInterface::registerFunctions() registerMethod("House", "getDoors", LuaScriptInterface::luaHouseGetDoors); registerMethod("House", "getDoorCount", LuaScriptInterface::luaHouseGetDoorCount); - registerMethod("House", "getDoorIdByPosition", LuaScriptInterface::luaHouseGetDoorIdByPosition); registerMethod("House", "getTiles", LuaScriptInterface::luaHouseGetTiles); - registerMethod("House", "getItems", LuaScriptInterface::luaHouseGetItems); registerMethod("House", "getTileCount", LuaScriptInterface::luaHouseGetTileCount); - registerMethod("House", "canEditAccessList", LuaScriptInterface::luaHouseCanEditAccessList); registerMethod("House", "getAccessList", LuaScriptInterface::luaHouseGetAccessList); registerMethod("House", "setAccessList", LuaScriptInterface::luaHouseSetAccessList); - registerMethod("House", "kickPlayer", LuaScriptInterface::luaHouseKickPlayer); - // ItemType registerClass("ItemType", "", LuaScriptInterface::luaItemTypeCreate); registerMetaMethod("ItemType", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -2582,51 +2221,47 @@ void LuaScriptInterface::registerFunctions() registerMethod("ItemType", "isCorpse", LuaScriptInterface::luaItemTypeIsCorpse); registerMethod("ItemType", "isDoor", LuaScriptInterface::luaItemTypeIsDoor); registerMethod("ItemType", "isContainer", LuaScriptInterface::luaItemTypeIsContainer); + registerMethod("ItemType", "isChest", LuaScriptInterface::luaItemTypeIsChest); registerMethod("ItemType", "isFluidContainer", LuaScriptInterface::luaItemTypeIsFluidContainer); registerMethod("ItemType", "isMovable", LuaScriptInterface::luaItemTypeIsMovable); registerMethod("ItemType", "isRune", LuaScriptInterface::luaItemTypeIsRune); registerMethod("ItemType", "isStackable", LuaScriptInterface::luaItemTypeIsStackable); registerMethod("ItemType", "isReadable", LuaScriptInterface::luaItemTypeIsReadable); registerMethod("ItemType", "isWritable", LuaScriptInterface::luaItemTypeIsWritable); - registerMethod("ItemType", "isBlocking", LuaScriptInterface::luaItemTypeIsBlocking); - registerMethod("ItemType", "isGroundTile", LuaScriptInterface::luaItemTypeIsGroundTile); registerMethod("ItemType", "isMagicField", LuaScriptInterface::luaItemTypeIsMagicField); - registerMethod("ItemType", "isUseable", LuaScriptInterface::luaItemTypeIsUseable); - registerMethod("ItemType", "isPickupable", LuaScriptInterface::luaItemTypeIsPickupable); + registerMethod("ItemType", "isSplash", LuaScriptInterface::luaItemTypeIsSplash); + registerMethod("ItemType", "isKey", LuaScriptInterface::luaItemTypeIsKey); + registerMethod("ItemType", "isDisguised", LuaScriptInterface::luaItemTypeIsDisguised); + registerMethod("ItemType", "isDestroyable", LuaScriptInterface::luaItemTypeIsDestroyable); + registerMethod("ItemType", "isGroundTile", LuaScriptInterface::luaItemTypeIsGroundTile); registerMethod("ItemType", "getType", LuaScriptInterface::luaItemTypeGetType); registerMethod("ItemType", "getId", LuaScriptInterface::luaItemTypeGetId); - registerMethod("ItemType", "getClientId", LuaScriptInterface::luaItemTypeGetClientId); + registerMethod("ItemType", "getDisguiseId", LuaScriptInterface::luaItemTypeGetDisguiseId); registerMethod("ItemType", "getName", LuaScriptInterface::luaItemTypeGetName); registerMethod("ItemType", "getPluralName", LuaScriptInterface::luaItemTypeGetPluralName); registerMethod("ItemType", "getArticle", LuaScriptInterface::luaItemTypeGetArticle); registerMethod("ItemType", "getDescription", LuaScriptInterface::luaItemTypeGetDescription); registerMethod("ItemType", "getSlotPosition", LuaScriptInterface::luaItemTypeGetSlotPosition); + registerMethod("ItemType", "getDestroyTarget", LuaScriptInterface::luaItemTypeGetDestroyTarget); registerMethod("ItemType", "getCharges", LuaScriptInterface::luaItemTypeGetCharges); registerMethod("ItemType", "getFluidSource", LuaScriptInterface::luaItemTypeGetFluidSource); registerMethod("ItemType", "getCapacity", LuaScriptInterface::luaItemTypeGetCapacity); registerMethod("ItemType", "getWeight", LuaScriptInterface::luaItemTypeGetWeight); - registerMethod("ItemType", "getHitChance", LuaScriptInterface::luaItemTypeGetHitChance); registerMethod("ItemType", "getShootRange", LuaScriptInterface::luaItemTypeGetShootRange); registerMethod("ItemType", "getAttack", LuaScriptInterface::luaItemTypeGetAttack); registerMethod("ItemType", "getDefense", LuaScriptInterface::luaItemTypeGetDefense); - registerMethod("ItemType", "getExtraDefense", LuaScriptInterface::luaItemTypeGetExtraDefense); registerMethod("ItemType", "getArmor", LuaScriptInterface::luaItemTypeGetArmor); registerMethod("ItemType", "getWeaponType", LuaScriptInterface::luaItemTypeGetWeaponType); - registerMethod("ItemType", "getElementType", LuaScriptInterface::luaItemTypeGetElementType); - registerMethod("ItemType", "getElementDamage", LuaScriptInterface::luaItemTypeGetElementDamage); - registerMethod("ItemType", "getTransformEquipId", LuaScriptInterface::luaItemTypeGetTransformEquipId); registerMethod("ItemType", "getTransformDeEquipId", LuaScriptInterface::luaItemTypeGetTransformDeEquipId); - registerMethod("ItemType", "getDestroyId", LuaScriptInterface::luaItemTypeGetDestroyId); registerMethod("ItemType", "getDecayId", LuaScriptInterface::luaItemTypeGetDecayId); + registerMethod("ItemType", "getNutrition", LuaScriptInterface::luaItemTypeGetNutrition); registerMethod("ItemType", "getRequiredLevel", LuaScriptInterface::luaItemTypeGetRequiredLevel); - registerMethod("ItemType", "getAmmoType", LuaScriptInterface::luaItemTypeGetAmmoType); - registerMethod("ItemType", "getCorpseType", LuaScriptInterface::luaItemTypeGetCorpseType); registerMethod("ItemType", "hasSubType", LuaScriptInterface::luaItemTypeHasSubType); @@ -2638,8 +2273,7 @@ void LuaScriptInterface::registerFunctions() registerMethod("Combat", "setFormula", LuaScriptInterface::luaCombatSetFormula); registerMethod("Combat", "setArea", LuaScriptInterface::luaCombatSetArea); - registerMethod("Combat", "addCondition", LuaScriptInterface::luaCombatAddCondition); - registerMethod("Combat", "clearConditions", LuaScriptInterface::luaCombatClearConditions); + registerMethod("Combat", "setCondition", LuaScriptInterface::luaCombatSetCondition); registerMethod("Combat", "setCallback", LuaScriptInterface::luaCombatSetCallback); registerMethod("Combat", "setOrigin", LuaScriptInterface::luaCombatSetOrigin); @@ -2663,10 +2297,10 @@ void LuaScriptInterface::registerFunctions() registerMethod("Condition", "setTicks", LuaScriptInterface::luaConditionSetTicks); registerMethod("Condition", "setParameter", LuaScriptInterface::luaConditionSetParameter); - registerMethod("Condition", "setFormula", LuaScriptInterface::luaConditionSetFormula); + registerMethod("Condition", "setSpeedDelta", LuaScriptInterface::luaConditionSetSpeedDelta); registerMethod("Condition", "setOutfit", LuaScriptInterface::luaConditionSetOutfit); - registerMethod("Condition", "addDamage", LuaScriptInterface::luaConditionAddDamage); + registerMethod("Condition", "setTiming", LuaScriptInterface::luaConditionSetTiming); // MonsterType registerClass("MonsterType", "", LuaScriptInterface::luaMonsterTypeCreate); @@ -2678,109 +2312,48 @@ void LuaScriptInterface::registerFunctions() registerMethod("MonsterType", "isIllusionable", LuaScriptInterface::luaMonsterTypeIsIllusionable); registerMethod("MonsterType", "isHostile", LuaScriptInterface::luaMonsterTypeIsHostile); registerMethod("MonsterType", "isPushable", LuaScriptInterface::luaMonsterTypeIsPushable); - registerMethod("MonsterType", "isHealthHidden", LuaScriptInterface::luaMonsterTypeIsHealthHidden); + registerMethod("MonsterType", "isHealthShown", LuaScriptInterface::luaMonsterTypeIsHealthShown); registerMethod("MonsterType", "canPushItems", LuaScriptInterface::luaMonsterTypeCanPushItems); registerMethod("MonsterType", "canPushCreatures", LuaScriptInterface::luaMonsterTypeCanPushCreatures); - registerMethod("MonsterType", "name", LuaScriptInterface::luaMonsterTypeName); + registerMethod("MonsterType", "getName", LuaScriptInterface::luaMonsterTypeGetName); + registerMethod("MonsterType", "getNameDescription", LuaScriptInterface::luaMonsterTypeGetNameDescription); - registerMethod("MonsterType", "nameDescription", LuaScriptInterface::luaMonsterTypeNameDescription); + registerMethod("MonsterType", "getHealth", LuaScriptInterface::luaMonsterTypeGetHealth); + registerMethod("MonsterType", "getMaxHealth", LuaScriptInterface::luaMonsterTypeGetMaxHealth); + registerMethod("MonsterType", "getRunHealth", LuaScriptInterface::luaMonsterTypeGetRunHealth); + registerMethod("MonsterType", "getExperience", LuaScriptInterface::luaMonsterTypeGetExperience); - registerMethod("MonsterType", "health", LuaScriptInterface::luaMonsterTypeHealth); - registerMethod("MonsterType", "maxHealth", LuaScriptInterface::luaMonsterTypeMaxHealth); - registerMethod("MonsterType", "runHealth", LuaScriptInterface::luaMonsterTypeRunHealth); - registerMethod("MonsterType", "experience", LuaScriptInterface::luaMonsterTypeExperience); - - registerMethod("MonsterType", "combatImmunities", LuaScriptInterface::luaMonsterTypeCombatImmunities); - registerMethod("MonsterType", "conditionImmunities", LuaScriptInterface::luaMonsterTypeConditionImmunities); + registerMethod("MonsterType", "getCombatImmunities", LuaScriptInterface::luaMonsterTypeGetCombatImmunities); + registerMethod("MonsterType", "getConditionImmunities", LuaScriptInterface::luaMonsterTypeGetConditionImmunities); registerMethod("MonsterType", "getAttackList", LuaScriptInterface::luaMonsterTypeGetAttackList); - registerMethod("MonsterType", "addAttack", LuaScriptInterface::luaMonsterTypeAddAttack); - registerMethod("MonsterType", "getDefenseList", LuaScriptInterface::luaMonsterTypeGetDefenseList); - registerMethod("MonsterType", "addDefense", LuaScriptInterface::luaMonsterTypeAddDefense); - registerMethod("MonsterType", "getElementList", LuaScriptInterface::luaMonsterTypeGetElementList); - registerMethod("MonsterType", "addElement", LuaScriptInterface::luaMonsterTypeAddElement); registerMethod("MonsterType", "getVoices", LuaScriptInterface::luaMonsterTypeGetVoices); - registerMethod("MonsterType", "addVoice", LuaScriptInterface::luaMonsterTypeAddVoice); - registerMethod("MonsterType", "getLoot", LuaScriptInterface::luaMonsterTypeGetLoot); - registerMethod("MonsterType", "addLoot", LuaScriptInterface::luaMonsterTypeAddLoot); - registerMethod("MonsterType", "getCreatureEvents", LuaScriptInterface::luaMonsterTypeGetCreatureEvents); - registerMethod("MonsterType", "registerEvent", LuaScriptInterface::luaMonsterTypeRegisterEvent); - - registerMethod("MonsterType", "eventType", LuaScriptInterface::luaMonsterTypeEventType); - registerMethod("MonsterType", "onThink", LuaScriptInterface::luaMonsterTypeEventOnCallback); - registerMethod("MonsterType", "onAppear", LuaScriptInterface::luaMonsterTypeEventOnCallback); - registerMethod("MonsterType", "onDisappear", LuaScriptInterface::luaMonsterTypeEventOnCallback); - registerMethod("MonsterType", "onMove", LuaScriptInterface::luaMonsterTypeEventOnCallback); - registerMethod("MonsterType", "onSay", LuaScriptInterface::luaMonsterTypeEventOnCallback); registerMethod("MonsterType", "getSummonList", LuaScriptInterface::luaMonsterTypeGetSummonList); - registerMethod("MonsterType", "addSummon", LuaScriptInterface::luaMonsterTypeAddSummon); + registerMethod("MonsterType", "getMaxSummons", LuaScriptInterface::luaMonsterTypeGetMaxSummons); - registerMethod("MonsterType", "maxSummons", LuaScriptInterface::luaMonsterTypeMaxSummons); + registerMethod("MonsterType", "getArmor", LuaScriptInterface::luaMonsterTypeGetArmor); + registerMethod("MonsterType", "getDefense", LuaScriptInterface::luaMonsterTypeGetDefense); + registerMethod("MonsterType", "getOutfit", LuaScriptInterface::luaMonsterTypeGetOutfit); + registerMethod("MonsterType", "getRace", LuaScriptInterface::luaMonsterTypeGetRace); + registerMethod("MonsterType", "getCorpseId", LuaScriptInterface::luaMonsterTypeGetCorpseId); + registerMethod("MonsterType", "getManaCost", LuaScriptInterface::luaMonsterTypeGetManaCost); + registerMethod("MonsterType", "getBaseSpeed", LuaScriptInterface::luaMonsterTypeGetBaseSpeed); + registerMethod("MonsterType", "getLight", LuaScriptInterface::luaMonsterTypeGetLight); - registerMethod("MonsterType", "armor", LuaScriptInterface::luaMonsterTypeArmor); - registerMethod("MonsterType", "defense", LuaScriptInterface::luaMonsterTypeDefense); - registerMethod("MonsterType", "outfit", LuaScriptInterface::luaMonsterTypeOutfit); - registerMethod("MonsterType", "race", LuaScriptInterface::luaMonsterTypeRace); - registerMethod("MonsterType", "corpseId", LuaScriptInterface::luaMonsterTypeCorpseId); - registerMethod("MonsterType", "manaCost", LuaScriptInterface::luaMonsterTypeManaCost); - registerMethod("MonsterType", "baseSpeed", LuaScriptInterface::luaMonsterTypeBaseSpeed); - registerMethod("MonsterType", "light", LuaScriptInterface::luaMonsterTypeLight); - - registerMethod("MonsterType", "staticAttackChance", LuaScriptInterface::luaMonsterTypeStaticAttackChance); - registerMethod("MonsterType", "targetDistance", LuaScriptInterface::luaMonsterTypeTargetDistance); - registerMethod("MonsterType", "yellChance", LuaScriptInterface::luaMonsterTypeYellChance); - registerMethod("MonsterType", "yellSpeedTicks", LuaScriptInterface::luaMonsterTypeYellSpeedTicks); - registerMethod("MonsterType", "changeTargetChance", LuaScriptInterface::luaMonsterTypeChangeTargetChance); - registerMethod("MonsterType", "changeTargetSpeed", LuaScriptInterface::luaMonsterTypeChangeTargetSpeed); - - // Loot - registerClass("Loot", "", LuaScriptInterface::luaCreateLoot); - registerMetaMethod("Loot", "__gc", LuaScriptInterface::luaDeleteLoot); - registerMethod("Loot", "delete", LuaScriptInterface::luaDeleteLoot); - - registerMethod("Loot", "setId", LuaScriptInterface::luaLootSetId); - registerMethod("Loot", "setMaxCount", LuaScriptInterface::luaLootSetMaxCount); - registerMethod("Loot", "setSubType", LuaScriptInterface::luaLootSetSubType); - registerMethod("Loot", "setChance", LuaScriptInterface::luaLootSetChance); - registerMethod("Loot", "setActionId", LuaScriptInterface::luaLootSetActionId); - registerMethod("Loot", "setDescription", LuaScriptInterface::luaLootSetDescription); - registerMethod("Loot", "addChildLoot", LuaScriptInterface::luaLootAddChildLoot); - - // MonsterSpell - registerClass("MonsterSpell", "", LuaScriptInterface::luaCreateMonsterSpell); - registerMetaMethod("MonsterSpell", "__gc", LuaScriptInterface::luaDeleteMonsterSpell); - registerMethod("MonsterSpell", "delete", LuaScriptInterface::luaDeleteMonsterSpell); - - registerMethod("MonsterSpell", "setType", LuaScriptInterface::luaMonsterSpellSetType); - registerMethod("MonsterSpell", "setScriptName", LuaScriptInterface::luaMonsterSpellSetScriptName); - registerMethod("MonsterSpell", "setChance", LuaScriptInterface::luaMonsterSpellSetChance); - registerMethod("MonsterSpell", "setInterval", LuaScriptInterface::luaMonsterSpellSetInterval); - registerMethod("MonsterSpell", "setRange", LuaScriptInterface::luaMonsterSpellSetRange); - registerMethod("MonsterSpell", "setCombatValue", LuaScriptInterface::luaMonsterSpellSetCombatValue); - registerMethod("MonsterSpell", "setCombatType", LuaScriptInterface::luaMonsterSpellSetCombatType); - registerMethod("MonsterSpell", "setAttackValue", LuaScriptInterface::luaMonsterSpellSetAttackValue); - registerMethod("MonsterSpell", "setNeedTarget", LuaScriptInterface::luaMonsterSpellSetNeedTarget); - registerMethod("MonsterSpell", "setCombatLength", LuaScriptInterface::luaMonsterSpellSetCombatLength); - registerMethod("MonsterSpell", "setCombatSpread", LuaScriptInterface::luaMonsterSpellSetCombatSpread); - registerMethod("MonsterSpell", "setCombatRadius", LuaScriptInterface::luaMonsterSpellSetCombatRadius); - registerMethod("MonsterSpell", "setConditionType", LuaScriptInterface::luaMonsterSpellSetConditionType); - registerMethod("MonsterSpell", "setConditionDamage", LuaScriptInterface::luaMonsterSpellSetConditionDamage); - registerMethod("MonsterSpell", "setConditionSpeedChange", LuaScriptInterface::luaMonsterSpellSetConditionSpeedChange); - registerMethod("MonsterSpell", "setConditionDuration", LuaScriptInterface::luaMonsterSpellSetConditionDuration); - registerMethod("MonsterSpell", "setConditionTickInterval", LuaScriptInterface::luaMonsterSpellSetConditionTickInterval); - registerMethod("MonsterSpell", "setCombatShootEffect", LuaScriptInterface::luaMonsterSpellSetCombatShootEffect); - registerMethod("MonsterSpell", "setCombatEffect", LuaScriptInterface::luaMonsterSpellSetCombatEffect); + registerMethod("MonsterType", "getTargetDistance", LuaScriptInterface::luaMonsterTypeGetTargetDistance); + registerMethod("MonsterType", "getChangeTargetChance", LuaScriptInterface::luaMonsterTypeGetChangeTargetChance); + registerMethod("MonsterType", "getChangeTargetSpeed", LuaScriptInterface::luaMonsterTypeGetChangeTargetSpeed); // Party - registerClass("Party", "", LuaScriptInterface::luaPartyCreate); + registerClass("Party", "", nullptr); registerMetaMethod("Party", "__eq", LuaScriptInterface::luaUserdataCompare); registerMethod("Party", "disband", LuaScriptInterface::luaPartyDisband); @@ -2804,154 +2377,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("Party", "isSharedExperienceEnabled", LuaScriptInterface::luaPartyIsSharedExperienceEnabled); registerMethod("Party", "shareExperience", LuaScriptInterface::luaPartyShareExperience); registerMethod("Party", "setSharedExperience", LuaScriptInterface::luaPartySetSharedExperience); - - // Spells - registerClass("Spell", "", LuaScriptInterface::luaSpellCreate); - registerMetaMethod("Spell", "__eq", LuaScriptInterface::luaUserdataCompare); - - registerMethod("Spell", "onCastSpell", LuaScriptInterface::luaSpellOnCastSpell); - registerMethod("Spell", "register", LuaScriptInterface::luaSpellRegister); - registerMethod("Spell", "name", LuaScriptInterface::luaSpellName); - registerMethod("Spell", "id", LuaScriptInterface::luaSpellId); - registerMethod("Spell", "group", LuaScriptInterface::luaSpellGroup); - registerMethod("Spell", "cooldown", LuaScriptInterface::luaSpellCooldown); - registerMethod("Spell", "groupCooldown", LuaScriptInterface::luaSpellGroupCooldown); - registerMethod("Spell", "level", LuaScriptInterface::luaSpellLevel); - registerMethod("Spell", "magicLevel", LuaScriptInterface::luaSpellMagicLevel); - registerMethod("Spell", "mana", LuaScriptInterface::luaSpellMana); - registerMethod("Spell", "manaPercent", LuaScriptInterface::luaSpellManaPercent); - registerMethod("Spell", "soul", LuaScriptInterface::luaSpellSoul); - registerMethod("Spell", "range", LuaScriptInterface::luaSpellRange); - registerMethod("Spell", "isPremium", LuaScriptInterface::luaSpellPremium); - registerMethod("Spell", "isEnabled", LuaScriptInterface::luaSpellEnabled); - registerMethod("Spell", "needTarget", LuaScriptInterface::luaSpellNeedTarget); - registerMethod("Spell", "needWeapon", LuaScriptInterface::luaSpellNeedWeapon); - registerMethod("Spell", "needLearn", LuaScriptInterface::luaSpellNeedLearn); - registerMethod("Spell", "isSelfTarget", LuaScriptInterface::luaSpellSelfTarget); - registerMethod("Spell", "isBlocking", LuaScriptInterface::luaSpellBlocking); - registerMethod("Spell", "isAggressive", LuaScriptInterface::luaSpellAggressive); - registerMethod("Spell", "vocation", LuaScriptInterface::luaSpellVocation); - - // only for InstantSpell - registerMethod("Spell", "words", LuaScriptInterface::luaSpellWords); - registerMethod("Spell", "needDirection", LuaScriptInterface::luaSpellNeedDirection); - registerMethod("Spell", "hasParams", LuaScriptInterface::luaSpellHasParams); - registerMethod("Spell", "hasPlayerNameParam", LuaScriptInterface::luaSpellHasPlayerNameParam); - registerMethod("Spell", "needCasterTargetOrDirection", LuaScriptInterface::luaSpellNeedCasterTargetOrDirection); - registerMethod("Spell", "isBlockingWalls", LuaScriptInterface::luaSpellIsBlockingWalls); - - // only for RuneSpells - registerMethod("Spell", "runeId", LuaScriptInterface::luaSpellRuneId); - registerMethod("Spell", "charges", LuaScriptInterface::luaSpellCharges); - registerMethod("Spell", "allowFarUse", LuaScriptInterface::luaSpellAllowFarUse); - registerMethod("Spell", "blockWalls", LuaScriptInterface::luaSpellBlockWalls); - registerMethod("Spell", "checkFloor", LuaScriptInterface::luaSpellCheckFloor); - - // Action - registerClass("Action", "", LuaScriptInterface::luaCreateAction); - registerMethod("Action", "onUse", LuaScriptInterface::luaActionOnUse); - registerMethod("Action", "register", LuaScriptInterface::luaActionRegister); - registerMethod("Action", "id", LuaScriptInterface::luaActionItemId); - registerMethod("Action", "aid", LuaScriptInterface::luaActionActionId); - registerMethod("Action", "uid", LuaScriptInterface::luaActionUniqueId); - registerMethod("Action", "allowFarUse", LuaScriptInterface::luaActionAllowFarUse); - registerMethod("Action", "blockWalls", LuaScriptInterface::luaActionBlockWalls); - registerMethod("Action", "checkFloor", LuaScriptInterface::luaActionCheckFloor); - - // TalkAction - registerClass("TalkAction", "", LuaScriptInterface::luaCreateTalkaction); - registerMethod("TalkAction", "onSay", LuaScriptInterface::luaTalkactionOnSay); - registerMethod("TalkAction", "register", LuaScriptInterface::luaTalkactionRegister); - registerMethod("TalkAction", "separator", LuaScriptInterface::luaTalkactionSeparator); - - // CreatureEvent - registerClass("CreatureEvent", "", LuaScriptInterface::luaCreateCreatureEvent); - registerMethod("CreatureEvent", "type", LuaScriptInterface::luaCreatureEventType); - registerMethod("CreatureEvent", "register", LuaScriptInterface::luaCreatureEventRegister); - registerMethod("CreatureEvent", "onLogin", LuaScriptInterface::luaCreatureEventOnCallback); - registerMethod("CreatureEvent", "onLogout", LuaScriptInterface::luaCreatureEventOnCallback); - registerMethod("CreatureEvent", "onThink", LuaScriptInterface::luaCreatureEventOnCallback); - registerMethod("CreatureEvent", "onPrepareDeath", LuaScriptInterface::luaCreatureEventOnCallback); - registerMethod("CreatureEvent", "onDeath", LuaScriptInterface::luaCreatureEventOnCallback); - registerMethod("CreatureEvent", "onKill", LuaScriptInterface::luaCreatureEventOnCallback); - registerMethod("CreatureEvent", "onAdvance", LuaScriptInterface::luaCreatureEventOnCallback); - registerMethod("CreatureEvent", "onModalWindow", LuaScriptInterface::luaCreatureEventOnCallback); - registerMethod("CreatureEvent", "onTextEdit", LuaScriptInterface::luaCreatureEventOnCallback); - registerMethod("CreatureEvent", "onHealthChange", LuaScriptInterface::luaCreatureEventOnCallback); - registerMethod("CreatureEvent", "onManaChange", LuaScriptInterface::luaCreatureEventOnCallback); - registerMethod("CreatureEvent", "onExtendedOpcode", LuaScriptInterface::luaCreatureEventOnCallback); - - // MoveEvent - registerClass("MoveEvent", "", LuaScriptInterface::luaCreateMoveEvent); - registerMethod("MoveEvent", "type", LuaScriptInterface::luaMoveEventType); - registerMethod("MoveEvent", "register", LuaScriptInterface::luaMoveEventRegister); - registerMethod("MoveEvent", "level", LuaScriptInterface::luaMoveEventLevel); - registerMethod("MoveEvent", "magicLevel", LuaScriptInterface::luaMoveEventMagLevel); - registerMethod("MoveEvent", "slot", LuaScriptInterface::luaMoveEventSlot); - registerMethod("MoveEvent", "id", LuaScriptInterface::luaMoveEventItemId); - registerMethod("MoveEvent", "aid", LuaScriptInterface::luaMoveEventActionId); - registerMethod("MoveEvent", "uid", LuaScriptInterface::luaMoveEventUniqueId); - registerMethod("MoveEvent", "position", LuaScriptInterface::luaMoveEventPosition); - registerMethod("MoveEvent", "premium", LuaScriptInterface::luaMoveEventPremium); - registerMethod("MoveEvent", "vocation", LuaScriptInterface::luaMoveEventVocation); - registerMethod("MoveEvent", "onEquip", LuaScriptInterface::luaMoveEventOnCallback); - registerMethod("MoveEvent", "onDeEquip", LuaScriptInterface::luaMoveEventOnCallback); - registerMethod("MoveEvent", "onStepIn", LuaScriptInterface::luaMoveEventOnCallback); - registerMethod("MoveEvent", "onStepOut", LuaScriptInterface::luaMoveEventOnCallback); - registerMethod("MoveEvent", "onAddItem", LuaScriptInterface::luaMoveEventOnCallback); - registerMethod("MoveEvent", "onRemoveItem", LuaScriptInterface::luaMoveEventOnCallback); - - // GlobalEvent - registerClass("GlobalEvent", "", LuaScriptInterface::luaCreateGlobalEvent); - registerMethod("GlobalEvent", "type", LuaScriptInterface::luaGlobalEventType); - registerMethod("GlobalEvent", "register", LuaScriptInterface::luaGlobalEventRegister); - registerMethod("GlobalEvent", "time", LuaScriptInterface::luaGlobalEventTime); - registerMethod("GlobalEvent", "interval", LuaScriptInterface::luaGlobalEventInterval); - registerMethod("GlobalEvent", "onThink", LuaScriptInterface::luaGlobalEventOnCallback); - registerMethod("GlobalEvent", "onTime", LuaScriptInterface::luaGlobalEventOnCallback); - registerMethod("GlobalEvent", "onStartup", LuaScriptInterface::luaGlobalEventOnCallback); - registerMethod("GlobalEvent", "onShutdown", LuaScriptInterface::luaGlobalEventOnCallback); - registerMethod("GlobalEvent", "onRecord", LuaScriptInterface::luaGlobalEventOnCallback); - - // Weapon - registerClass("Weapon", "", LuaScriptInterface::luaCreateWeapon); - registerMethod("Weapon", "action", LuaScriptInterface::luaWeaponAction); - registerMethod("Weapon", "register", LuaScriptInterface::luaWeaponRegister); - registerMethod("Weapon", "id", LuaScriptInterface::luaWeaponId); - registerMethod("Weapon", "level", LuaScriptInterface::luaWeaponLevel); - registerMethod("Weapon", "magicLevel", LuaScriptInterface::luaWeaponMagicLevel); - registerMethod("Weapon", "mana", LuaScriptInterface::luaWeaponMana); - registerMethod("Weapon", "manaPercent", LuaScriptInterface::luaWeaponManaPercent); - registerMethod("Weapon", "health", LuaScriptInterface::luaWeaponHealth); - registerMethod("Weapon", "healthPercent", LuaScriptInterface::luaWeaponHealthPercent); - registerMethod("Weapon", "soul", LuaScriptInterface::luaWeaponSoul); - registerMethod("Weapon", "breakChance", LuaScriptInterface::luaWeaponBreakChance); - registerMethod("Weapon", "premium", LuaScriptInterface::luaWeaponPremium); - registerMethod("Weapon", "wieldUnproperly", LuaScriptInterface::luaWeaponUnproperly); - registerMethod("Weapon", "vocation", LuaScriptInterface::luaWeaponVocation); - registerMethod("Weapon", "onUseWeapon", LuaScriptInterface::luaWeaponOnUseWeapon); - registerMethod("Weapon", "element", LuaScriptInterface::luaWeaponElement); - registerMethod("Weapon", "attack", LuaScriptInterface::luaWeaponAttack); - registerMethod("Weapon", "defense", LuaScriptInterface::luaWeaponDefense); - registerMethod("Weapon", "range", LuaScriptInterface::luaWeaponRange); - registerMethod("Weapon", "charges", LuaScriptInterface::luaWeaponCharges); - registerMethod("Weapon", "duration", LuaScriptInterface::luaWeaponDuration); - registerMethod("Weapon", "decayTo", LuaScriptInterface::luaWeaponDecayTo); - registerMethod("Weapon", "transformEquipTo", LuaScriptInterface::luaWeaponTransformEquipTo); - registerMethod("Weapon", "transformDeEquipTo", LuaScriptInterface::luaWeaponTransformDeEquipTo); - registerMethod("Weapon", "slotType", LuaScriptInterface::luaWeaponSlotType); - registerMethod("Weapon", "hitChance", LuaScriptInterface::luaWeaponHitChance); - registerMethod("Weapon", "extraElement", LuaScriptInterface::luaWeaponExtraElement); - - // exclusively for distance weapons - registerMethod("Weapon", "ammoType", LuaScriptInterface::luaWeaponAmmoType); - registerMethod("Weapon", "maxHitChance", LuaScriptInterface::luaWeaponMaxHitChance); - - // exclusively for wands - registerMethod("Weapon", "damage", LuaScriptInterface::luaWeaponWandDamage); - - // exclusively for wands & distance weapons - registerMethod("Weapon", "shootType", LuaScriptInterface::luaWeaponShootType); } #undef registerEnum @@ -3091,6 +2516,61 @@ void LuaScriptInterface::registerGlobalBoolean(const std::string& name, bool val lua_setglobal(luaState, name.c_str()); } +int LuaScriptInterface::luaGetPlayerFlagValue(lua_State* L) +{ + //getPlayerFlagValue(cid, flag) + Player* player = getPlayer(L, 1); + if (player) { + PlayerFlags flag = getNumber(L, 2); + pushBoolean(L, player->hasFlag(flag)); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaGetPlayerInstantSpellCount(lua_State* L) +{ + //getPlayerInstantSpellCount(cid) + Player* player = getPlayer(L, 1); + if (player) { + lua_pushnumber(L, g_spells->getInstantSpellCount(player)); + } else { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + } + return 1; +} + +int LuaScriptInterface::luaGetPlayerInstantSpellInfo(lua_State* L) +{ + //getPlayerInstantSpellInfo(cid, index) + Player* player = getPlayer(L, 1); + if (!player) { + reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + uint32_t index = getNumber(L, 2); + InstantSpell* spell = g_spells->getInstantSpellByIndex(player, index); + if (!spell) { + reportErrorFunc(getErrorDesc(LUA_ERROR_SPELL_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + lua_createtable(L, 0, 6); + setField(L, "name", spell->getName()); + setField(L, "words", spell->getWords()); + setField(L, "level", spell->getLevel()); + setField(L, "mlevel", spell->getMagicLevel()); + setField(L, "mana", spell->getManaCost(player)); + setField(L, "manapercent", spell->getManaPercent()); + return 1; +} + int LuaScriptInterface::luaDoPlayerAddItem(lua_State* L) { //doPlayerAddItem(cid, itemid, count/subtype, canDropOnMap) @@ -3166,6 +2646,138 @@ int LuaScriptInterface::luaDoPlayerAddItem(lua_State* L) return 1; } +int LuaScriptInterface::luaDoTileAddItemEx(lua_State* L) +{ + //doTileAddItemEx(pos, uid) + const Position& pos = getPosition(L, 1); + + Tile* tile = g_game.map.getTile(pos); + if (!tile) { + std::ostringstream ss; + ss << pos << ' ' << getErrorDesc(LUA_ERROR_TILE_NOT_FOUND); + reportErrorFunc(ss.str()); + pushBoolean(L, false); + return 1; + } + + uint32_t uid = getNumber(L, 2); + Item* item = getScriptEnv()->getItemByUID(uid); + if (!item) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + if (item->getParent() != VirtualCylinder::virtualCylinder) { + reportErrorFunc("Item already has a parent"); + pushBoolean(L, false); + return 1; + } + + lua_pushnumber(L, g_game.internalAddItem(tile, item)); + return 1; +} + +int LuaScriptInterface::luaDoCreateItem(lua_State* L) +{ + //doCreateItem(itemid, type/count, pos) + //Returns uid of the created item, only works on tiles. + const Position& pos = getPosition(L, 3); + Tile* tile = g_game.map.getTile(pos); + if (!tile) { + std::ostringstream ss; + ss << pos << ' ' << getErrorDesc(LUA_ERROR_TILE_NOT_FOUND); + reportErrorFunc(ss.str()); + pushBoolean(L, false); + return 1; + } + + ScriptEnvironment* env = getScriptEnv(); + + int32_t itemCount = 1; + int32_t subType = 1; + + uint16_t itemId = getNumber(L, 1); + uint32_t count = getNumber(L, 2, 1); + + const ItemType& it = Item::items[itemId]; + if (it.hasSubType()) { + if (it.stackable) { + itemCount = static_cast(std::ceil(static_cast(count) / 100)); + } + + subType = count; + } else { + itemCount = std::max(1, count); + } + + while (itemCount > 0) { + int32_t stackCount = std::min(100, subType); + Item* newItem = Item::CreateItem(itemId, stackCount); + if (!newItem) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + if (it.stackable) { + subType -= stackCount; + } + + ReturnValue ret = g_game.internalAddItem(tile, newItem, INDEX_WHEREEVER, FLAG_NOLIMIT); + if (ret != RETURNVALUE_NOERROR) { + delete newItem; + pushBoolean(L, false); + return 1; + } + + if (--itemCount == 0) { + if (newItem->getParent()) { + uint32_t uid = env->addThing(newItem); + lua_pushnumber(L, uid); + return 1; + } else { + //stackable item stacked with existing object, newItem will be released + pushBoolean(L, false); + return 1; + } + } + } + + pushBoolean(L, false); + return 1; +} + +int LuaScriptInterface::luaDoCreateItemEx(lua_State* L) +{ + //doCreateItemEx(itemid, count/subtype) + //Returns uid of the created item + uint16_t itemId = getNumber(L, 1); + uint32_t count = getNumber(L, 2, 1); + + const ItemType& it = Item::items[itemId]; + if (it.stackable && count > 100) { + reportErrorFunc("Stack count cannot be higher than 100."); + count = 100; + } + + Item* newItem = Item::CreateItem(itemId, count); + if (!newItem) { + reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + newItem->setParent(VirtualCylinder::virtualCylinder); + + ScriptEnvironment* env = getScriptEnv(); + env->addTempItem(newItem); + + uint32_t uid = env->addThing(newItem); + lua_pushnumber(L, uid); + return 1; +} + int LuaScriptInterface::luaDebugPrint(lua_State* L) { //debugPrint(text) @@ -3184,7 +2796,8 @@ int LuaScriptInterface::luaGetWorldTime(lua_State* L) int LuaScriptInterface::luaGetWorldLight(lua_State* L) { //getWorldLight() - LightInfo lightInfo = g_game.getWorldLightInfo(); + LightInfo lightInfo; + g_game.getWorldLightInfo(lightInfo); lua_pushnumber(L, lightInfo.level); lua_pushnumber(L, lightInfo.color); return 2; @@ -3281,8 +2894,8 @@ int LuaScriptInterface::luaDoAreaCombatHealth(lua_State* L) CombatDamage damage; damage.origin = getNumber(L, 8, ORIGIN_SPELL); - damage.primary.type = combatType; - damage.primary.value = normal_random(getNumber(L, 6), getNumber(L, 5)); + damage.type = combatType; + damage.value = normal_random(getNumber(L, 6), getNumber(L, 5)); Combat::doCombatHealth(creature, getPosition(L, 3), area, damage, params); pushBoolean(L, true); @@ -3318,8 +2931,8 @@ int LuaScriptInterface::luaDoTargetCombatHealth(lua_State* L) CombatDamage damage; damage.origin = getNumber(L, 7, ORIGIN_SPELL); - damage.primary.type = combatType; - damage.primary.value = normal_random(getNumber(L, 4), getNumber(L, 5)); + damage.type = combatType; + damage.value = normal_random(getNumber(L, 4), getNumber(L, 5)); Combat::doCombatHealth(creature, target, damage, params); pushBoolean(L, true); @@ -3344,8 +2957,8 @@ int LuaScriptInterface::luaDoAreaCombatMana(lua_State* L) CombatDamage damage; damage.origin = getNumber(L, 7, ORIGIN_SPELL); - damage.primary.type = COMBAT_MANADRAIN; - damage.primary.value = normal_random(getNumber(L, 4), getNumber(L, 5)); + damage.type = COMBAT_MANADRAIN; + damage.value = normal_random(getNumber(L, 4), getNumber(L, 5)); Position pos = getPosition(L, 2); Combat::doCombatMana(creature, pos, area, damage, params); @@ -3379,8 +2992,8 @@ int LuaScriptInterface::luaDoTargetCombatMana(lua_State* L) CombatDamage damage; damage.origin = getNumber(L, 6, ORIGIN_SPELL); - damage.primary.type = COMBAT_MANADRAIN; - damage.primary.value = normal_random(getNumber(L, 3), getNumber(L, 4)); + damage.type = COMBAT_MANADRAIN; + damage.value = normal_random(getNumber(L, 3), getNumber(L, 4)); Combat::doCombatMana(creature, target, damage, params); pushBoolean(L, true); @@ -3409,7 +3022,7 @@ int LuaScriptInterface::luaDoAreaCombatCondition(lua_State* L) if (area || areaId == 0) { CombatParams params; params.impactEffect = getNumber(L, 5); - params.conditionList.emplace_front(condition->clone()); + params.conditionList.emplace_front(condition); Combat::doCombatCondition(creature, getPosition(L, 2), area, params); pushBoolean(L, true); } else { @@ -3445,7 +3058,7 @@ int LuaScriptInterface::luaDoTargetCombatCondition(lua_State* L) CombatParams params; params.impactEffect = getNumber(L, 4); - params.conditionList.emplace_front(condition->clone()); + params.conditionList.emplace_front(condition); Combat::doCombatCondition(creature, target, params); pushBoolean(L, true); return 1; @@ -3524,6 +3137,76 @@ int LuaScriptInterface::luaDoChallengeCreature(lua_State* L) return 1; } +int LuaScriptInterface::luaSetCreatureOutfit(lua_State* L) +{ + //doSetCreatureOutfit(cid, outfit, time) + Creature* creature = getCreature(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Outfit_t outfit = getOutfit(L, 2); + int32_t time = getNumber(L, 3); + pushBoolean(L, Spell::CreateIllusion(creature, outfit, time) == RETURNVALUE_NOERROR); + return 1; +} + +int LuaScriptInterface::luaSetMonsterOutfit(lua_State* L) +{ + //doSetMonsterOutfit(cid, name, time) + Creature* creature = getCreature(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + std::string name = getString(L, 2); + int32_t time = getNumber(L, 3); + pushBoolean(L, Spell::CreateIllusion(creature, name, time) == RETURNVALUE_NOERROR); + return 1; +} + +int LuaScriptInterface::luaSetItemOutfit(lua_State* L) +{ + //doSetItemOutfit(cid, item, time) + Creature* creature = getCreature(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + uint32_t item = getNumber(L, 2); + int32_t time = getNumber(L, 3); + pushBoolean(L, Spell::CreateIllusion(creature, item, time) == RETURNVALUE_NOERROR); + return 1; +} + +int LuaScriptInterface::luaDoMoveCreature(lua_State* L) +{ + //doMoveCreature(cid, direction) + Creature* creature = getCreature(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + Direction direction = getNumber(L, 2); + if (direction > DIRECTION_LAST) { + reportErrorFunc("No valid direction"); + pushBoolean(L, false); + return 1; + } + + ReturnValue ret = g_game.internalMoveCreature(creature, direction, FLAG_NOLIMIT); + lua_pushnumber(L, ret); + return 1; +} + int LuaScriptInterface::luaIsValidUID(lua_State* L) { //isValidUID(uid) @@ -3636,6 +3319,27 @@ int LuaScriptInterface::luaGetDepotId(lua_State* L) return 1; } +int LuaScriptInterface::luaIsInArray(lua_State* L) +{ + //isInArray(array, value) + if (!isTable(L, 1)) { + pushBoolean(L, false); + return 1; + } + + lua_pushnil(L); + while (lua_next(L, 1)) { + if (lua_equal(L, 2, -1) != 0) { + pushBoolean(L, true); + return 1; + } + lua_pop(L, 1); + } + + pushBoolean(L, false); + return 1; +} + int LuaScriptInterface::luaDoSetCreatureLight(lua_State* L) { //doSetCreatureLight(cid, lightLevel, lightColor, time) @@ -3726,7 +3430,7 @@ int LuaScriptInterface::luaAddEvent(lua_State* L) case LuaData_Container: case LuaData_Teleport: { lua_getglobal(globalState, "Item"); - lua_getfield(globalState, -1, "getUniqueId"); + lua_getfield(globalState, -1, "getMovementId"); break; } case LuaData_Player: @@ -3802,6 +3506,21 @@ int LuaScriptInterface::luaStopEvent(lua_State* L) return 1; } +int LuaScriptInterface::luaGetCreatureCondition(lua_State* L) +{ + Creature* creature = getCreature(L, 1); + if (!creature) { + reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); + pushBoolean(L, false); + return 1; + } + + ConditionType_t condition = getNumber(L, 2); + uint32_t subId = getNumber(L, 3, 0); + pushBoolean(L, creature->hasCondition(condition, subId)); + return 1; +} + int LuaScriptInterface::luaSaveServer(lua_State* L) { g_game.saveGameState(); @@ -3850,40 +3569,6 @@ int LuaScriptInterface::luaGetWaypointPositionByName(lua_State* L) return 1; } -int LuaScriptInterface::luaSendChannelMessage(lua_State* L) -{ - //sendChannelMessage(channelId, type, message) - uint32_t channelId = getNumber(L, 1); - ChatChannel* channel = g_chat->getChannelById(channelId); - if (!channel) { - pushBoolean(L, false); - return 1; - } - - SpeakClasses type = getNumber(L, 2); - std::string message = getString(L, 3); - channel->sendToAll(message, type); - pushBoolean(L, true); - return 1; -} - -int LuaScriptInterface::luaSendGuildChannelMessage(lua_State* L) -{ - //sendGuildChannelMessage(guildId, type, message) - uint32_t guildId = getNumber(L, 1); - ChatChannel* channel = g_chat->getGuildChannelById(guildId); - if (!channel) { - pushBoolean(L, false); - return 1; - } - - SpeakClasses type = getNumber(L, 2); - std::string message = getString(L, 3); - channel->sendToAll(message, type); - pushBoolean(L, true); - return 1; -} - std::string LuaScriptInterface::escapeString(const std::string& string) { std::string s = string; @@ -3983,7 +3668,7 @@ const luaL_Reg LuaScriptInterface::luaDatabaseTable[] = { int LuaScriptInterface::luaDatabaseExecute(lua_State* L) { - pushBoolean(L, Database::getInstance().executeQuery(getString(L, -1))); + pushBoolean(L, Database::getInstance()->executeQuery(getString(L, -1))); return 1; } @@ -4019,7 +3704,7 @@ int LuaScriptInterface::luaDatabaseAsyncExecute(lua_State* L) int LuaScriptInterface::luaDatabaseStoreQuery(lua_State* L) { - if (DBResult_ptr res = Database::getInstance().storeQuery(getString(L, -1))) { + if (DBResult_ptr res = Database::getInstance()->storeQuery(getString(L, -1))) { lua_pushnumber(L, ScriptEnvironment::addResult(res)); } else { pushBoolean(L, false); @@ -4063,20 +3748,20 @@ int LuaScriptInterface::luaDatabaseAsyncStoreQuery(lua_State* L) int LuaScriptInterface::luaDatabaseEscapeString(lua_State* L) { - pushString(L, Database::getInstance().escapeString(getString(L, -1))); + pushString(L, Database::getInstance()->escapeString(getString(L, -1))); return 1; } int LuaScriptInterface::luaDatabaseEscapeBlob(lua_State* L) { uint32_t length = getNumber(L, 2); - pushString(L, Database::getInstance().escapeBlob(getString(L, 1).c_str(), length)); + pushString(L, Database::getInstance()->escapeBlob(getString(L, 1).c_str(), length)); return 1; } int LuaScriptInterface::luaDatabaseLastInsertId(lua_State* L) { - lua_pushnumber(L, Database::getInstance().getLastInsertId()); + lua_pushnumber(L, Database::getInstance()->getLastInsertId()); return 1; } @@ -4256,15 +3941,7 @@ int LuaScriptInterface::luaGameLoadMap(lua_State* L) { // Game.loadMap(path) const std::string& path = getString(L, 1); - g_dispatcher.addTask(createTask([path]() { - try { - g_game.loadMap(path); - } catch (const std::exception& e) { - // FIXME: Should only catch some exceptions - std::cout << "[Error - LuaScriptInterface::luaGameLoadMap] Failed to load map: " - << e.what() << std::endl; - } - })); + g_dispatcher.addTask(createTask(std::bind(&Game::loadMap, &g_game, path))); return 0; } @@ -4530,37 +4207,6 @@ int LuaScriptInterface::luaGameCreateTile(lua_State* L) return 1; } -int LuaScriptInterface::luaGameCreateMonsterType(lua_State* L) -{ - // Game.createMonsterType(name) - if (getScriptEnv()->getScriptInterface() != &g_scripts->getScriptInterface()) { - reportErrorFunc("MonsterTypes can only be registered in the Scripts interface."); - lua_pushnil(L); - return 1; - } - - MonsterType* monsterType = g_monsters.getMonsterType(getString(L, 1)); - if (monsterType) { - monsterType->info.lootItems.clear(); - monsterType->info.attackSpells.clear(); - monsterType->info.defenseSpells.clear(); - pushUserdata(L, monsterType); - setMetatable(L, -1, "MonsterType"); - } else if (isString(L, 1)) { - monsterType = new MonsterType(); - std::string name = getString(L, 1); - g_monsters.addMonsterType(name, monsterType); - monsterType = g_monsters.getMonsterType(getString(L, 1)); - monsterType->name = name; - monsterType->nameDescription = "a " + name; - pushUserdata(L, monsterType); - setMetatable(L, -1, "MonsterType"); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaGameStartRaid(lua_State* L) { // Game.startRaid(raidName) @@ -4568,12 +4214,12 @@ int LuaScriptInterface::luaGameStartRaid(lua_State* L) Raid* raid = g_game.raids.getRaidByName(raidName); if (!raid || !raid->isLoaded()) { - lua_pushnumber(L, RETURNVALUE_NOSUCHRAIDEXISTS); + lua_pushnil(L); return 1; } if (g_game.raids.getRunning()) { - lua_pushnumber(L, RETURNVALUE_ANOTHERRAIDISALREADYEXECUTING); + lua_pushnil(L); return 1; } @@ -4583,24 +4229,19 @@ int LuaScriptInterface::luaGameStartRaid(lua_State* L) return 1; } -int LuaScriptInterface::luaGameGetClientVersion(lua_State* L) -{ - // Game.getClientVersion() - lua_createtable(L, 0, 3); - setField(L, "min", CLIENT_VERSION_MIN); - setField(L, "max", CLIENT_VERSION_MAX); - setField(L, "string", CLIENT_VERSION_STR); - return 1; -} - int LuaScriptInterface::luaGameReload(lua_State* L) { // Game.reload(reloadType) ReloadTypes_t reloadType = getNumber(L, 1); + if (!reloadType) { + lua_pushnil(L); + return 1; + } + if (reloadType == RELOAD_TYPE_GLOBAL) { pushBoolean(L, g_luaEnvironment.loadFile("data/global.lua") == 0); - pushBoolean(L, g_scripts->loadScripts("scripts/lib", true, true)); - } else { + } + else { pushBoolean(L, g_game.reload(reloadType)); } lua_gc(g_luaEnvironment.getLuaState(), LUA_GCCOLLECT, 0); @@ -4763,18 +4404,18 @@ int LuaScriptInterface::luaPositionIsSightClear(lua_State* L) int LuaScriptInterface::luaPositionSendMagicEffect(lua_State* L) { // position:sendMagicEffect(magicEffect[, player = nullptr]) - SpectatorVec spectators; + SpectatorVec list; if (lua_gettop(L) >= 3) { Player* player = getPlayer(L, 3); if (player) { - spectators.emplace_back(player); + list.insert(player); } } MagicEffectClasses magicEffect = getNumber(L, 2); const Position& position = getPosition(L, 1); - if (!spectators.empty()) { - Game::addMagicEffect(spectators, position, magicEffect); + if (!list.empty()) { + Game::addMagicEffect(list, position, magicEffect); } else { g_game.addMagicEffect(position, magicEffect); } @@ -4786,19 +4427,19 @@ int LuaScriptInterface::luaPositionSendMagicEffect(lua_State* L) int LuaScriptInterface::luaPositionSendDistanceEffect(lua_State* L) { // position:sendDistanceEffect(positionEx, distanceEffect[, player = nullptr]) - SpectatorVec spectators; + SpectatorVec list; if (lua_gettop(L) >= 4) { Player* player = getPlayer(L, 4); if (player) { - spectators.emplace_back(player); + list.insert(player); } } ShootType_t distanceEffect = getNumber(L, 3); const Position& positionEx = getPosition(L, 2); const Position& position = getPosition(L, 1); - if (!spectators.empty()) { - Game::addDistanceEffect(spectators, position, positionEx, distanceEffect); + if (!list.empty()) { + Game::addDistanceEffect(list, position, positionEx, distanceEffect); } else { g_game.addDistanceEffect(position, positionEx, distanceEffect); } @@ -4807,6 +4448,16 @@ int LuaScriptInterface::luaPositionSendDistanceEffect(lua_State* L) return 1; } +int LuaScriptInterface::luaPositionSendMonsterSay(lua_State * L) +{ + // position:sendMonsterSay(text) + const std::string& text = getString(L, 2); + const Position& position = getPosition(L, 1); + g_game.addMonsterSayText(position, text); + pushBoolean(L, true); + return 1; +} + // Tile int LuaScriptInterface::luaTileCreate(lua_State* L) { @@ -5034,9 +4685,6 @@ int LuaScriptInterface::luaTileGetItemByType(lua_State* L) case ITEM_TYPE_MAILBOX: found = tile->hasFlag(TILESTATE_MAILBOX); break; - case ITEM_TYPE_TRASHHOLDER: - found = tile->hasFlag(TILESTATE_TRASHHOLDER); - break; case ITEM_TYPE_BED: found = tile->hasFlag(TILESTATE_BED); break; @@ -5393,77 +5041,6 @@ int LuaScriptInterface::luaTileQueryAdd(lua_State* L) return 1; } -int LuaScriptInterface::luaTileAddItem(lua_State* L) -{ - // tile:addItem(itemId[, count/subType = 1[, flags = 0]]) - Tile* tile = getUserdata(L, 1); - if (!tile) { - lua_pushnil(L); - return 1; - } - - uint16_t itemId; - if (isNumber(L, 2)) { - itemId = getNumber(L, 2); - } else { - itemId = Item::items.getItemIdByName(getString(L, 2)); - if (itemId == 0) { - lua_pushnil(L); - return 1; - } - } - - uint32_t subType = getNumber(L, 3, 1); - - Item* item = Item::CreateItem(itemId, std::min(subType, 100)); - if (!item) { - lua_pushnil(L); - return 1; - } - - uint32_t flags = getNumber(L, 4, 0); - - ReturnValue ret = g_game.internalAddItem(tile, item, INDEX_WHEREEVER, flags); - if (ret == RETURNVALUE_NOERROR) { - pushUserdata(L, item); - setItemMetatable(L, -1, item); - } else { - delete item; - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaTileAddItemEx(lua_State* L) -{ - // tile:addItemEx(item[, flags = 0]) - Item* item = getUserdata(L, 2); - if (!item) { - lua_pushnil(L); - return 1; - } - - Tile* tile = getUserdata(L, 1); - if (!tile) { - lua_pushnil(L); - return 1; - } - - if (item->getParent() != VirtualCylinder::virtualCylinder) { - reportErrorFunc("Item already has a parent"); - lua_pushnil(L); - return 1; - } - - uint32_t flags = getNumber(L, 3, 0); - ReturnValue ret = g_game.internalAddItem(tile, item, INDEX_WHEREEVER, flags); - if (ret == RETURNVALUE_NOERROR) { - ScriptEnvironment::removeTempItem(item); - } - lua_pushnumber(L, ret); - return 1; -} - int LuaScriptInterface::luaTileGetHouse(lua_State* L) { // tile:getHouse() @@ -5763,243 +5340,6 @@ int LuaScriptInterface::luaNetworkMessageSendToPlayer(lua_State* L) return 1; } -// ModalWindow -int LuaScriptInterface::luaModalWindowCreate(lua_State* L) -{ - // ModalWindow(id, title, message) - const std::string& message = getString(L, 4); - const std::string& title = getString(L, 3); - uint32_t id = getNumber(L, 2); - - pushUserdata(L, new ModalWindow(id, title, message)); - setMetatable(L, -1, "ModalWindow"); - return 1; -} - -int LuaScriptInterface::luaModalWindowDelete(lua_State* L) -{ - ModalWindow** windowPtr = getRawUserdata(L, 1); - if (windowPtr && *windowPtr) { - delete *windowPtr; - *windowPtr = nullptr; - } - return 0; -} - -int LuaScriptInterface::luaModalWindowGetId(lua_State* L) -{ - // modalWindow:getId() - ModalWindow* window = getUserdata(L, 1); - if (window) { - lua_pushnumber(L, window->id); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaModalWindowGetTitle(lua_State* L) -{ - // modalWindow:getTitle() - ModalWindow* window = getUserdata(L, 1); - if (window) { - pushString(L, window->title); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaModalWindowGetMessage(lua_State* L) -{ - // modalWindow:getMessage() - ModalWindow* window = getUserdata(L, 1); - if (window) { - pushString(L, window->message); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaModalWindowSetTitle(lua_State* L) -{ - // modalWindow:setTitle(text) - const std::string& text = getString(L, 2); - ModalWindow* window = getUserdata(L, 1); - if (window) { - window->title = text; - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaModalWindowSetMessage(lua_State* L) -{ - // modalWindow:setMessage(text) - const std::string& text = getString(L, 2); - ModalWindow* window = getUserdata(L, 1); - if (window) { - window->message = text; - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaModalWindowGetButtonCount(lua_State* L) -{ - // modalWindow:getButtonCount() - ModalWindow* window = getUserdata(L, 1); - if (window) { - lua_pushnumber(L, window->buttons.size()); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaModalWindowGetChoiceCount(lua_State* L) -{ - // modalWindow:getChoiceCount() - ModalWindow* window = getUserdata(L, 1); - if (window) { - lua_pushnumber(L, window->choices.size()); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaModalWindowAddButton(lua_State* L) -{ - // modalWindow:addButton(id, text) - const std::string& text = getString(L, 3); - uint8_t id = getNumber(L, 2); - ModalWindow* window = getUserdata(L, 1); - if (window) { - window->buttons.emplace_back(text, id); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaModalWindowAddChoice(lua_State* L) -{ - // modalWindow:addChoice(id, text) - const std::string& text = getString(L, 3); - uint8_t id = getNumber(L, 2); - ModalWindow* window = getUserdata(L, 1); - if (window) { - window->choices.emplace_back(text, id); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaModalWindowGetDefaultEnterButton(lua_State* L) -{ - // modalWindow:getDefaultEnterButton() - ModalWindow* window = getUserdata(L, 1); - if (window) { - lua_pushnumber(L, window->defaultEnterButton); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaModalWindowSetDefaultEnterButton(lua_State* L) -{ - // modalWindow:setDefaultEnterButton(buttonId) - ModalWindow* window = getUserdata(L, 1); - if (window) { - window->defaultEnterButton = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaModalWindowGetDefaultEscapeButton(lua_State* L) -{ - // modalWindow:getDefaultEscapeButton() - ModalWindow* window = getUserdata(L, 1); - if (window) { - lua_pushnumber(L, window->defaultEscapeButton); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaModalWindowSetDefaultEscapeButton(lua_State* L) -{ - // modalWindow:setDefaultEscapeButton(buttonId) - ModalWindow* window = getUserdata(L, 1); - if (window) { - window->defaultEscapeButton = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaModalWindowHasPriority(lua_State* L) -{ - // modalWindow:hasPriority() - ModalWindow* window = getUserdata(L, 1); - if (window) { - pushBoolean(L, window->priority); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaModalWindowSetPriority(lua_State* L) -{ - // modalWindow:setPriority(priority) - ModalWindow* window = getUserdata(L, 1); - if (window) { - window->priority = getBoolean(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaModalWindowSendToPlayer(lua_State* L) -{ - // modalWindow:sendToPlayer(player) - Player* player = getPlayer(L, 2); - if (!player) { - lua_pushnil(L); - return 1; - } - - ModalWindow* window = getUserdata(L, 1); - if (window) { - if (!player->hasModalWindowOpen(window->id)) { - player->sendModalWindow(*window); - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - // Item int LuaScriptInterface::luaItemCreate(lua_State* L) { @@ -6120,8 +5460,6 @@ int LuaScriptInterface::luaItemSplit(lua_State* L) return 1; } - splitItem->setItemCount(count); - ScriptEnvironment* env = getScriptEnv(); uint32_t uid = env->addThing(item); @@ -6157,16 +5495,12 @@ int LuaScriptInterface::luaItemRemove(lua_State* L) return 1; } -int LuaScriptInterface::luaItemGetUniqueId(lua_State* L) +int LuaScriptInterface::luaItemGetMovementId(lua_State* L) { - // item:getUniqueId() + // item:getMovementId() Item* item = getUserdata(L, 1); if (item) { - uint32_t uniqueId = item->getUniqueId(); - if (uniqueId == 0) { - uniqueId = getScriptEnv()->addThing(item); - } - lua_pushnumber(L, uniqueId); + lua_pushnumber(L, item->getMovementId()); } else { lua_pushnil(L); } @@ -6199,6 +5533,32 @@ int LuaScriptInterface::luaItemSetActionId(lua_State* L) return 1; } +int LuaScriptInterface::luaItemSetMovementId(lua_State* L) +{ + // item:setMovementId(movementId) + uint16_t movementId = getNumber(L, 2); + Item* item = getUserdata(L, 1); + if (item) { + item->setMovementID(movementId); + pushBoolean(L, true); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetUniqueId(lua_State * L) +{ + // item:getUniqueId() + Item* item = getUserdata(L, 1); + if (item) { + lua_pushnumber(L, getScriptEnv()->addThing(item)); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaItemGetCount(lua_State* L) { // item:getCount() @@ -6395,12 +5755,6 @@ int LuaScriptInterface::luaItemSetAttribute(lua_State* L) } if (ItemAttributes::isIntAttrType(attribute)) { - if (attribute == ITEM_ATTRIBUTE_UNIQUEID) { - reportErrorFunc("Attempt to set protected key \"uid\""); - pushBoolean(L, false); - return 1; - } - item->setIntAttr(attribute, getNumber(L, 3)); pushBoolean(L, true); } else if (ItemAttributes::isStrAttrType(attribute)) { @@ -6430,103 +5784,14 @@ int LuaScriptInterface::luaItemRemoveAttribute(lua_State* L) attribute = ITEM_ATTRIBUTE_NONE; } - bool ret = attribute != ITEM_ATTRIBUTE_UNIQUEID; - if (ret) { - item->removeAttribute(attribute); - } else { - reportErrorFunc("Attempt to erase protected key \"uid\""); - } - pushBoolean(L, ret); - return 1; -} - -int LuaScriptInterface::luaItemGetCustomAttribute(lua_State* L) { - // item:getCustomAttribute(key) - Item* item = getUserdata(L, 1); - if (!item) { - lua_pushnil(L); - return 1; - } - - const ItemAttributes::CustomAttribute* attr; - if (isNumber(L, 2)) { - attr = item->getCustomAttribute(getNumber(L, 2)); - } else if (isString(L, 2)) { - attr = item->getCustomAttribute(getString(L, 2)); - } else { - lua_pushnil(L); - return 1; - } - - if (attr) { - attr->pushToLua(L); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaItemSetCustomAttribute(lua_State* L) { - // item:setCustomAttribute(key, value) - Item* item = getUserdata(L, 1); - if (!item) { - lua_pushnil(L); - return 1; - } - - std::string key; - if (isNumber(L, 2)) { - key = boost::lexical_cast(getNumber(L, 2)); - } else if (isString(L, 2)) { - key = getString(L, 2); - } else { - lua_pushnil(L); - return 1; - } - - ItemAttributes::CustomAttribute val; - if (isNumber(L, 3)) { - double tmp = getNumber(L, 3); - if (std::floor(tmp) < tmp) { - val.set(tmp); - } else { - val.set(tmp); - } - } else if (isString(L, 3)) { - val.set(getString(L, 3)); - } else if (isBoolean(L, 3)) { - val.set(getBoolean(L, 3)); - } else { - lua_pushnil(L); - return 1; - } - - item->setCustomAttribute(key, val); + item->removeAttribute(attribute); pushBoolean(L, true); return 1; } -int LuaScriptInterface::luaItemRemoveCustomAttribute(lua_State* L) { - // item:removeCustomAttribute(key) - Item* item = getUserdata(L, 1); - if (!item) { - lua_pushnil(L); - return 1; - } - - if (isNumber(L, 2)) { - pushBoolean(L, item->removeCustomAttribute(getNumber(L, 2))); - } else if (isString(L, 2)) { - pushBoolean(L, item->removeCustomAttribute(getString(L, 2))); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaItemMoveTo(lua_State* L) { - // item:moveTo(position or cylinder[, flags]) + // item:moveTo(position or cylinder) Item** itemPtr = getRawUserdata(L, 1); if (!itemPtr) { lua_pushnil(L); @@ -6570,13 +5835,11 @@ int LuaScriptInterface::luaItemMoveTo(lua_State* L) return 1; } - uint32_t flags = getNumber(L, 3, FLAG_NOLIMIT | FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE | FLAG_IGNORENOTMOVEABLE); - if (item->getParent() == VirtualCylinder::virtualCylinder) { - pushBoolean(L, g_game.internalAddItem(toCylinder, item, INDEX_WHEREEVER, flags) == RETURNVALUE_NOERROR); + pushBoolean(L, g_game.internalAddItem(toCylinder, item) == RETURNVALUE_NOERROR); } else { Item* moveItem = nullptr; - ReturnValue ret = g_game.internalMoveItem(item->getParent(), toCylinder, INDEX_WHEREEVER, item, item->getItemCount(), &moveItem, flags); + ReturnValue ret = g_game.internalMoveItem(item->getParent(), toCylinder, INDEX_WHEREEVER, item, item->getItemCount(), &moveItem, FLAG_NOLIMIT | FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE | FLAG_IGNORENOTMOVEABLE); if (moveItem) { *itemPtr = moveItem; } @@ -6641,13 +5904,9 @@ int LuaScriptInterface::luaItemTransform(lua_State* L) int LuaScriptInterface::luaItemDecay(lua_State* L) { - // item:decay(decayId) + // item:decay() Item* item = getUserdata(L, 1); if (item) { - if (isNumber(L, 2)) { - item->setDecayTo(getNumber(L, 2)); - } - g_game.startDecay(item); pushBoolean(L, true); } else { @@ -6682,18 +5941,6 @@ int LuaScriptInterface::luaItemHasProperty(lua_State* L) return 1; } -int LuaScriptInterface::luaItemIsLoadedFromMap(lua_State* L) -{ - // item:isLoadedFromMap() - Item* item = getUserdata(L, 1); - if (item) { - pushBoolean(L, item->isLoadedFromMap()); - } else { - lua_pushnil(L); - } - return 1; -} - // Container int LuaScriptInterface::luaContainerCreate(lua_State* L) { @@ -6821,13 +6068,9 @@ int LuaScriptInterface::luaContainerAddItem(lua_State* L) } } - uint32_t count = getNumber(L, 3, 1); - const ItemType& it = Item::items[itemId]; - if (it.stackable) { - count = std::min(count, 100); - } + uint32_t subType = getNumber(L, 3, 1); - Item* item = Item::CreateItem(itemId, count); + Item* item = Item::CreateItem(itemId, std::min(subType, 100)); if (!item) { lua_pushnil(L); return 1; @@ -6878,18 +6121,6 @@ int LuaScriptInterface::luaContainerAddItemEx(lua_State* L) return 1; } -int LuaScriptInterface::luaContainerGetCorpseOwner(lua_State* L) -{ - // container:getCorpseOwner() - Container* container = getUserdata(L, 1); - if (container) { - lua_pushnumber(L, container->getCorpseOwner()); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaContainerGetItemCountById(lua_State* L) { // container:getItemCountById(itemId[, subType = -1]) @@ -6915,18 +6146,6 @@ int LuaScriptInterface::luaContainerGetItemCountById(lua_State* L) return 1; } -int LuaScriptInterface::luaContainerGetContentDescription(lua_State* L) -{ - // container:getContentDescription() - Container* container = getUserdata(L, 1); - if (container) { - pushString(L, container->getContentDescription()); - } else { - lua_pushnil(L); - } - return 1; -} - // Teleport int LuaScriptInterface::luaTeleportCreate(lua_State* L) { @@ -7075,18 +6294,6 @@ int LuaScriptInterface::luaCreatureIsInGhostMode(lua_State* L) return 1; } -int LuaScriptInterface::luaCreatureIsHealthHidden(lua_State* L) -{ - // creature:isHealthHidden() - const Creature* creature = getUserdata(L, 1); - if (creature) { - pushBoolean(L, creature->isHealthHidden()); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaCreatureCanSee(lua_State* L) { // creature:canSee(position) @@ -7249,8 +6456,19 @@ int LuaScriptInterface::luaCreatureSetMaster(lua_State* L) return 1; } - pushBoolean(L, creature->setMaster(getCreature(L, 2))); - g_game.updateCreatureType(creature); + Creature* master = getCreature(L, 2); + if (master) { + pushBoolean(L, creature->convinceCreature(master)); + } else { + master = creature->getMaster(); + if (master) { + master->removeSummon(creature); + creature->incrementReferenceCounter(); + creature->setDropLoot(true); + } + pushBoolean(L, true); + } + return 1; } @@ -7263,9 +6481,10 @@ int LuaScriptInterface::luaCreatureGetLight(lua_State* L) return 1; } - LightInfo lightInfo = creature->getCreatureLight(); - lua_pushnumber(L, lightInfo.level); - lua_pushnumber(L, lightInfo.color); + LightInfo light; + creature->getCreatureLight(light); + lua_pushnumber(L, light.level); + lua_pushnumber(L, light.color); return 2; } @@ -7340,19 +6559,6 @@ int LuaScriptInterface::luaCreatureSetDropLoot(lua_State* L) return 1; } -int LuaScriptInterface::luaCreatureSetSkillLoss(lua_State* L) -{ - // creature:setSkillLoss(skillLoss) - Creature* creature = getUserdata(L, 1); - if (creature) { - creature->setSkillLoss(getBoolean(L, 2)); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaCreatureGetPosition(lua_State* L) { // creature:getPosition() @@ -7420,26 +6626,6 @@ int LuaScriptInterface::luaCreatureGetHealth(lua_State* L) return 1; } -int LuaScriptInterface::luaCreatureSetHealth(lua_State* L) -{ - // creature:setHealth(health) - Creature* creature = getUserdata(L, 1); - if (!creature) { - lua_pushnil(L); - return 1; - } - - creature->health = std::min(getNumber(L, 2), creature->healthMax); - g_game.addCreatureHealth(creature); - - Player* player = creature->getPlayer(); - if (player) { - player->sendStats(); - } - pushBoolean(L, true); - return 1; -} - int LuaScriptInterface::luaCreatureAddHealth(lua_State* L) { // creature:addHealth(healthChange) @@ -7450,11 +6636,11 @@ int LuaScriptInterface::luaCreatureAddHealth(lua_State* L) } CombatDamage damage; - damage.primary.value = getNumber(L, 2); - if (damage.primary.value >= 0) { - damage.primary.type = COMBAT_HEALING; + damage.value = getNumber(L, 2); + if (damage.value >= 0) { + damage.type = COMBAT_HEALING; } else { - damage.primary.type = COMBAT_UNDEFINEDDAMAGE; + damage.type = COMBAT_UNDEFINEDDAMAGE; } pushBoolean(L, g_game.combatChangeHealth(nullptr, creature, damage)); return 1; @@ -7501,7 +6687,8 @@ int LuaScriptInterface::luaCreatureSetHiddenHealth(lua_State* L) creature->setHiddenHealth(getBoolean(L, 2)); g_game.addCreatureHealth(creature); pushBoolean(L, true); - } else { + } + else { lua_pushnil(L); } return 1; @@ -7609,7 +6796,7 @@ int LuaScriptInterface::luaCreatureRemoveCondition(lua_State* L) uint32_t subId = getNumber(L, 4, 0); Condition* condition = creature->getCondition(conditionType, conditionId, subId); if (condition) { - bool force = getBoolean(L, 5, false); + bool force = getBoolean(L, 5, true); creature->removeCondition(condition, force); pushBoolean(L, true); } else { @@ -7618,40 +6805,6 @@ int LuaScriptInterface::luaCreatureRemoveCondition(lua_State* L) return 1; } -int LuaScriptInterface::luaCreatureHasCondition(lua_State* L) -{ - // creature:hasCondition(conditionType[, subId = 0]) - Creature* creature = getUserdata(L, 1); - if (!creature) { - lua_pushnil(L); - return 1; - } - - ConditionType_t conditionType = getNumber(L, 2); - uint32_t subId = getNumber(L, 3, 0); - pushBoolean(L, creature->hasCondition(conditionType, subId)); - return 1; -} - -int LuaScriptInterface::luaCreatureIsImmune(lua_State* L) -{ - // creature:isImmune(condition or conditionType) - Creature* creature = getUserdata(L, 1); - if (!creature) { - lua_pushnil(L); - return 1; - } - - if (isNumber(L, 2)) { - pushBoolean(L, creature->isImmune(getNumber(L, 2))); - } else if (Condition* condition = getUserdata(L, 2)) { - pushBoolean(L, creature->isImmune(condition->getType())); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaCreatureRemove(lua_State* L) { // creature:remove() @@ -7697,7 +6850,7 @@ int LuaScriptInterface::luaCreatureTeleportTo(lua_State* L) return 1; } - if (pushMovement) { + if (!pushMovement) { if (oldPosition.x == position.x) { if (oldPosition.y < position.y) { g_game.internalCreatureTurn(creature, DIRECTION_SOUTH); @@ -7716,7 +6869,7 @@ int LuaScriptInterface::luaCreatureTeleportTo(lua_State* L) int LuaScriptInterface::luaCreatureSay(lua_State* L) { - // creature:say(text[, type = TALKTYPE_MONSTER_SAY[, ghost = false[, target = nullptr[, position]]]]) + // creature:say(text, type[, ghost = false[, target = nullptr[, position]]]) int parameters = lua_gettop(L); Position position; @@ -7736,7 +6889,7 @@ int LuaScriptInterface::luaCreatureSay(lua_State* L) bool ghost = getBoolean(L, 4, false); - SpeakClasses type = getNumber(L, 3, TALKTYPE_MONSTER_SAY); + SpeakClasses type = getNumber(L, 3); const std::string& text = getString(L, 2); Creature* creature = getUserdata(L, 1); if (!creature) { @@ -7744,15 +6897,15 @@ int LuaScriptInterface::luaCreatureSay(lua_State* L) return 1; } - SpectatorVec spectators; + SpectatorVec list; if (target) { - spectators.emplace_back(target); + list.insert(target); } if (position.x != 0) { - pushBoolean(L, g_game.internalCreatureSay(creature, type, text, ghost, &spectators, &position)); + pushBoolean(L, g_game.internalCreatureSay(creature, type, text, ghost, &list, &position)); } else { - pushBoolean(L, g_game.internalCreatureSay(creature, type, text, ghost, &spectators)); + pushBoolean(L, g_game.internalCreatureSay(creature, type, text, ghost, &list)); } return 1; } @@ -7842,58 +6995,13 @@ int LuaScriptInterface::luaCreatureGetPathTo(lua_State* L) return 1; } -int LuaScriptInterface::luaCreatureMove(lua_State* L) -{ - // creature:move(direction) - // creature:move(tile[, flags = 0]) - Creature* creature = getUserdata(L, 1); - if (!creature) { - lua_pushnil(L); - return 1; - } - - if (isNumber(L, 2)) { - Direction direction = getNumber(L, 2); - if (direction > DIRECTION_LAST) { - lua_pushnil(L); - return 1; - } - lua_pushnumber(L, g_game.internalMoveCreature(creature, direction, FLAG_NOLIMIT)); - } else { - Tile* tile = getUserdata(L, 2); - if (!tile) { - lua_pushnil(L); - return 1; - } - lua_pushnumber(L, g_game.internalMoveCreature(*creature, *tile, getNumber(L, 3))); - } - return 1; -} - -int LuaScriptInterface::luaCreatureGetZone(lua_State* L) -{ - // creature:getZone() - Creature* creature = getUserdata(L, 1); - if (creature) { - lua_pushnumber(L, creature->getZone()); - } else { - lua_pushnil(L); - } - return 1; -} - // Player int LuaScriptInterface::luaPlayerCreate(lua_State* L) { - // Player(id or guid or name or userdata) + // Player(id or name or userdata) Player* player; if (isNumber(L, 2)) { - uint32_t id = getNumber(L, 2); - if (id >= 0x10000000 && id <= Player::playerAutoID) { - player = g_game.getPlayerByID(id); - } else { - player = g_game.getPlayerByGUID(id); - } + player = g_game.getPlayerByID(getNumber(L, 2)); } else if (isString(L, 2)) { ReturnValue ret = g_game.getPlayerByNameWildcard(getString(L, 2), player); if (ret != RETURNVALUE_NOERROR) { @@ -7987,6 +7095,19 @@ int LuaScriptInterface::luaPlayerGetLastLogout(lua_State* L) return 1; } +int LuaScriptInterface::luaPlayerHasFlag(lua_State * L) +{ + // player:hasFlag(flag) + Player* player = getUserdata(L, 1); + if (player) { + PlayerFlags flag = getNumber(L, 2); + pushBoolean(L, player->hasFlag(flag)); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaPlayerGetAccountType(lua_State* L) { // player:getAccountType() @@ -8062,54 +7183,56 @@ int LuaScriptInterface::luaPlayerGetDepotChest(lua_State* L) uint32_t depotId = getNumber(L, 2); bool autoCreate = getBoolean(L, 3, false); - DepotChest* depotChest = player->getDepotChest(depotId, autoCreate); - if (depotChest) { - player->setLastDepotId(depotId); // FIXME: workaround for #2251 - pushUserdata(L, depotChest); - setItemMetatable(L, -1, depotChest); + DepotLocker* depotLocker = player->getDepotLocker(depotId, autoCreate); + if (depotLocker) { + if (!depotLocker->getParent() && player->getTile()) { + depotLocker->setParent(player->getTile()); + } + + pushUserdata(L, depotLocker); + setItemMetatable(L, -1, depotLocker); } else { pushBoolean(L, false); } return 1; } -int LuaScriptInterface::luaPlayerGetInbox(lua_State* L) +int LuaScriptInterface::luaPlayerGetMurderTimestamps(lua_State * L) { - // player:getInbox() - Player* player = getUserdata(L, 1); - if (!player) { - lua_pushnil(L); - return 1; - } - - Inbox* inbox = player->getInbox(); - if (inbox) { - pushUserdata(L, inbox); - setItemMetatable(L, -1, inbox); - } else { - pushBoolean(L, false); - } - return 1; -} - -int LuaScriptInterface::luaPlayerGetSkullTime(lua_State* L) -{ - // player:getSkullTime() + // player:getMurderTimestamps() Player* player = getUserdata(L, 1); if (player) { - lua_pushnumber(L, player->getSkullTicks()); + lua_createtable(L, player->murderTimeStamps.size(), 0); + + uint32_t i = 1; + for (time_t currentMurderTimestamp : player->murderTimeStamps) { + lua_pushnumber(L, static_cast(currentMurderTimestamp)); + lua_rawseti(L, -2, ++i); + } } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaPlayerSetSkullTime(lua_State* L) +int LuaScriptInterface::luaPlayerGetPlayerKillerEnd(lua_State* L) { - // player:setSkullTime(skullTime) + // player:getPlayerKillerEnd() Player* player = getUserdata(L, 1); if (player) { - player->setSkullTicks(getNumber(L, 2)); + lua_pushnumber(L, player->getPlayerKillerEnd()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerSetPlayerKillerEnd(lua_State* L) +{ + // player:setPlayerKillerEnd(skullTime) + Player* player = getUserdata(L, 1); + if (player) { + player->setPlayerKillerEnd(getNumber(L, 2)); pushBoolean(L, true); } else { lua_pushnil(L); @@ -8150,7 +7273,8 @@ int LuaScriptInterface::luaPlayerAddExperience(lua_State* L) bool sendText = getBoolean(L, 3, false); player->addExperience(nullptr, experience, sendText); pushBoolean(L, true); - } else { + } + else { lua_pushnil(L); } return 1; @@ -8158,12 +7282,11 @@ int LuaScriptInterface::luaPlayerAddExperience(lua_State* L) int LuaScriptInterface::luaPlayerRemoveExperience(lua_State* L) { - // player:removeExperience(experience[, sendText = false]) + // player:removeExperience(experience) Player* player = getUserdata(L, 1); if (player) { int64_t experience = getNumber(L, 2); - bool sendText = getBoolean(L, 3, false); - player->removeExperience(experience, sendText); + player->removeExperience(experience); pushBoolean(L, true); } else { lua_pushnil(L); @@ -8213,7 +7336,8 @@ int LuaScriptInterface::luaPlayerGetMana(lua_State* L) const Player* player = getUserdata(L, 1); if (player) { lua_pushnumber(L, player->getMana()); - } else { + } + else { lua_pushnil(L); } return 1; @@ -8232,9 +7356,10 @@ int LuaScriptInterface::luaPlayerAddMana(lua_State* L) bool animationOnLoss = getBoolean(L, 3, false); if (!animationOnLoss && manaChange < 0) { player->changeMana(manaChange); - } else { + } + else { CombatDamage damage; - damage.primary.value = manaChange; + damage.value = manaChange; damage.origin = ORIGIN_NONE; g_game.combatChangeMana(nullptr, player, damage); } @@ -8248,7 +7373,8 @@ int LuaScriptInterface::luaPlayerGetMaxMana(lua_State* L) const Player* player = getUserdata(L, 1); if (player) { lua_pushnumber(L, player->getMaxMana()); - } else { + } + else { lua_pushnil(L); } return 1; @@ -8385,123 +7511,6 @@ int LuaScriptInterface::luaPlayerAddSkillTries(lua_State* L) return 1; } -int LuaScriptInterface::luaPlayerGetSpecialSkill(lua_State* L) -{ - // player:getSpecialSkill(specialSkillType) - SpecialSkills_t specialSkillType = getNumber(L, 2); - Player* player = getUserdata(L, 1); - if (player && specialSkillType <= SPECIALSKILL_LAST) { - lua_pushnumber(L, player->getSpecialSkill(specialSkillType)); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaPlayerAddSpecialSkill(lua_State* L) -{ - // player:addSpecialSkill(specialSkillType, value) - Player* player = getUserdata(L, 1); - if (!player) { - lua_pushnil(L); - return 1; - } - - SpecialSkills_t specialSkillType = getNumber(L, 2); - if (specialSkillType > SPECIALSKILL_LAST) { - lua_pushnil(L); - return 1; - } - - player->setVarSpecialSkill(specialSkillType, getNumber(L, 3)); - player->sendSkills(); - pushBoolean(L, true); - return 1; -} - -int LuaScriptInterface::luaPlayerAddOfflineTrainingTime(lua_State* L) -{ - // player:addOfflineTrainingTime(time) - Player* player = getUserdata(L, 1); - if (player) { - int32_t time = getNumber(L, 2); - player->addOfflineTrainingTime(time); - player->sendStats(); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - - -int LuaScriptInterface::luaPlayerGetOfflineTrainingTime(lua_State* L) -{ - // player:getOfflineTrainingTime() - Player* player = getUserdata(L, 1); - if (player) { - lua_pushnumber(L, player->getOfflineTrainingTime()); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaPlayerRemoveOfflineTrainingTime(lua_State* L) -{ - // player:removeOfflineTrainingTime(time) - Player* player = getUserdata(L, 1); - if (player) { - int32_t time = getNumber(L, 2); - player->removeOfflineTrainingTime(time); - player->sendStats(); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaPlayerAddOfflineTrainingTries(lua_State* L) -{ - // player:addOfflineTrainingTries(skillType, tries) - Player* player = getUserdata(L, 1); - if (player) { - skills_t skillType = getNumber(L, 2); - uint64_t tries = getNumber(L, 3); - pushBoolean(L, player->addOfflineTrainingTries(skillType, tries)); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaPlayerGetOfflineTrainingSkill(lua_State* L) -{ - // player:getOfflineTrainingSkill() - Player* player = getUserdata(L, 1); - if (player) { - lua_pushnumber(L, player->getOfflineTrainingSkill()); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaPlayerSetOfflineTrainingSkill(lua_State* L) -{ - // player:setOfflineTrainingSkill(skillId) - Player* player = getUserdata(L, 1); - if (player) { - uint32_t skillId = getNumber(L, 2); - player->setOfflineTrainingSkill(skillId); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaPlayerGetItemCount(lua_State* L) { // player:getItemCount(itemId[, subType = -1]) @@ -8791,7 +7800,8 @@ int LuaScriptInterface::luaPlayerGetStamina(lua_State* L) Player* player = getUserdata(L, 1); if (player) { lua_pushnumber(L, player->getStaminaMinutes()); - } else { + } + else { lua_pushnil(L); } return 1; @@ -8803,10 +7813,11 @@ int LuaScriptInterface::luaPlayerSetStamina(lua_State* L) uint16_t stamina = getNumber(L, 2); Player* player = getUserdata(L, 1); if (player) { - player->staminaMinutes = std::min(2520, stamina); + player->staminaMinutes = std::min(3360, stamina); player->sendStats(); pushBoolean(L, true); - } else { + } + else { lua_pushnil(L); } return 1; @@ -8897,7 +7908,7 @@ int LuaScriptInterface::luaPlayerGetStorageValue(lua_State* L) if (player->getStorageValue(key, value)) { lua_pushnumber(L, value); } else { - lua_pushnumber(L, -1); + lua_pushnumber(L, 0); } return 1; } @@ -8908,14 +7919,6 @@ int LuaScriptInterface::luaPlayerSetStorageValue(lua_State* L) int32_t value = getNumber(L, 3); uint32_t key = getNumber(L, 2); Player* player = getUserdata(L, 1); - if (IS_IN_KEYRANGE(key, RESERVED_RANGE)) { - std::ostringstream ss; - ss << "Accessing reserved range: " << key; - reportErrorFunc(ss.str()); - pushBoolean(L, false); - return 1; - } - if (player) { player->addStorageValue(key, value); pushBoolean(L, true); @@ -9120,7 +8123,7 @@ int LuaScriptInterface::luaPlayerRemoveMoney(lua_State* L) int LuaScriptInterface::luaPlayerShowTextDialog(lua_State* L) { - // player:showTextDialog(id or name or userdata[, text[, canWrite[, length]]]) + // player:showTextDialog(itemId[, text[, canWrite[, length]]]) Player* player = getUserdata(L, 1); if (!player) { lua_pushnil(L); @@ -9136,22 +8139,18 @@ int LuaScriptInterface::luaPlayerShowTextDialog(lua_State* L) text = getString(L, 3); } - Item* item; + uint16_t itemId; if (isNumber(L, 2)) { - item = Item::CreateItem(getNumber(L, 2)); - } else if (isString(L, 2)) { - item = Item::CreateItem(Item::items.getItemIdByName(getString(L, 2))); - } else if (isUserdata(L, 2)) { - if (getUserdataType(L, 2) != LuaData_Item) { - pushBoolean(L, false); + itemId = getNumber(L, 2); + } else { + itemId = Item::items.getItemIdByName(getString(L, 2)); + if (itemId == 0) { + lua_pushnil(L); return 1; } - - item = getUserdata(L, 2); - } else { - item = nullptr; } + Item* item = Item::CreateItem(itemId); if (!item) { reportErrorFunc(getErrorDesc(LUA_ERROR_ITEM_NOT_FOUND)); pushBoolean(L, false); @@ -9176,55 +8175,11 @@ int LuaScriptInterface::luaPlayerShowTextDialog(lua_State* L) int LuaScriptInterface::luaPlayerSendTextMessage(lua_State* L) { - // player:sendTextMessage(type, text[, position, primaryValue = 0, primaryColor = TEXTCOLOR_NONE[, secondaryValue = 0, secondaryColor = TEXTCOLOR_NONE]]) - // player:sendTextMessage(type, text, channelId) - - Player* player = getUserdata(L, 1); - if (!player) { - lua_pushnil(L); - return 1; - } - - int parameters = lua_gettop(L); - + // player:sendTextMessage(type, text) TextMessage message(getNumber(L, 2), getString(L, 3)); - if (parameters == 4) { - uint16_t channelId = getNumber(L, 4); - ChatChannel* channel = g_chat->getChannel(*player, channelId); - if (!channel || !channel->hasUser(*player)) { - pushBoolean(L, false); - return 1; - } - message.channelId = channelId; - } else { - if (parameters >= 6) { - message.position = getPosition(L, 4); - message.primary.value = getNumber(L, 5); - message.primary.color = getNumber(L, 6); - } - - if (parameters >= 8) { - message.secondary.value = getNumber(L, 7); - message.secondary.color = getNumber(L, 8); - } - } - - player->sendTextMessage(message); - pushBoolean(L, true); - - return 1; -} - -int LuaScriptInterface::luaPlayerSendChannelMessage(lua_State* L) -{ - // player:sendChannelMessage(author, text, type, channelId) - uint16_t channelId = getNumber(L, 5); - SpeakClasses type = getNumber(L, 4); - const std::string& text = getString(L, 3); - const std::string& author = getString(L, 2); Player* player = getUserdata(L, 1); if (player) { - player->sendChannelMessage(author, text, type, channelId); + player->sendTextMessage(message); pushBoolean(L, true); } else { lua_pushnil(L); @@ -9243,7 +8198,7 @@ int LuaScriptInterface::luaPlayerSendPrivateMessage(lua_State* L) const Player* speaker = getUserdata(L, 2); const std::string& text = getString(L, 3); - SpeakClasses type = getNumber(L, 4, TALKTYPE_PRIVATE_FROM); + SpeakClasses type = getNumber(L, 4, TALKTYPE_PRIVATE); player->sendPrivateMessage(speaker, type, text); pushBoolean(L, true); return 1; @@ -9333,7 +8288,8 @@ int LuaScriptInterface::luaPlayerAddOutfit(lua_State* L) if (player) { player->addOutfit(getNumber(L, 2), 0); pushBoolean(L, true); - } else { + } + else { lua_pushnil(L); } return 1; @@ -9348,7 +8304,8 @@ int LuaScriptInterface::luaPlayerAddOutfitAddon(lua_State* L) uint8_t addon = getNumber(L, 3); player->addOutfit(lookType, addon); pushBoolean(L, true); - } else { + } + else { lua_pushnil(L); } return 1; @@ -9361,7 +8318,8 @@ int LuaScriptInterface::luaPlayerRemoveOutfit(lua_State* L) if (player) { uint16_t lookType = getNumber(L, 2); pushBoolean(L, player->removeOutfit(lookType)); - } else { + } + else { lua_pushnil(L); } return 1; @@ -9375,7 +8333,8 @@ int LuaScriptInterface::luaPlayerRemoveOutfitAddon(lua_State* L) uint16_t lookType = getNumber(L, 2); uint8_t addon = getNumber(L, 3); pushBoolean(L, player->removeOutfitAddon(lookType, addon)); - } else { + } + else { lua_pushnil(L); } return 1; @@ -9389,7 +8348,8 @@ int LuaScriptInterface::luaPlayerHasOutfit(lua_State* L) uint16_t lookType = getNumber(L, 2); uint8_t addon = getNumber(L, 3, 0); pushBoolean(L, player->canWear(lookType, addon)); - } else { + } + else { lua_pushnil(L); } return 1; @@ -9408,75 +8368,6 @@ int LuaScriptInterface::luaPlayerSendOutfitWindow(lua_State* L) return 1; } -int LuaScriptInterface::luaPlayerAddMount(lua_State* L) { - // player:addMount(mountId or mountName) - Player* player = getUserdata(L, 1); - if (!player) { - lua_pushnil(L); - return 1; - } - - uint8_t mountId; - if (isNumber(L, 2)) { - mountId = getNumber(L, 2); - } else { - Mount* mount = g_game.mounts.getMountByName(getString(L, 2)); - if (!mount) { - lua_pushnil(L); - return 1; - } - mountId = mount->id; - } - pushBoolean(L, player->tameMount(mountId)); - return 1; -} - -int LuaScriptInterface::luaPlayerRemoveMount(lua_State* L) { - // player:removeMount(mountId or mountName) - Player* player = getUserdata(L, 1); - if (!player) { - lua_pushnil(L); - return 1; - } - - uint8_t mountId; - if (isNumber(L, 2)) { - mountId = getNumber(L, 2); - } else { - Mount* mount = g_game.mounts.getMountByName(getString(L, 2)); - if (!mount) { - lua_pushnil(L); - return 1; - } - mountId = mount->id; - } - pushBoolean(L, player->untameMount(mountId)); - return 1; -} - -int LuaScriptInterface::luaPlayerHasMount(lua_State* L) { - // player:hasMount(mountId or mountName) - const Player* player = getUserdata(L, 1); - if (!player) { - lua_pushnil(L); - return 1; - } - - Mount* mount = nullptr; - if (isNumber(L, 2)) { - mount = g_game.mounts.getMountByID(getNumber(L, 2)); - } else { - mount = g_game.mounts.getMountByName(getString(L, 2)); - } - - if (mount) { - pushBoolean(L, player->hasMount(mount)); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaPlayerGetPremiumDays(lua_State* L) { // player:getPremiumDays() @@ -9660,36 +8551,6 @@ int LuaScriptInterface::luaPlayerHasLearnedSpell(lua_State* L) return 1; } -int LuaScriptInterface::luaPlayerSendTutorial(lua_State* L) -{ - // player:sendTutorial(tutorialId) - Player* player = getUserdata(L, 1); - if (player) { - uint8_t tutorialId = getNumber(L, 2); - player->sendTutorial(tutorialId); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaPlayerAddMapMark(lua_State* L) -{ - // player:addMapMark(position, type, description) - Player* player = getUserdata(L, 1); - if (player) { - const Position& position = getPosition(L, 2); - uint8_t type = getNumber(L, 3); - const std::string& description = getString(L, 4); - player->sendAddMarker(position, type, description); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaPlayerSave(lua_State* L) { // player:save() @@ -9703,20 +8564,6 @@ int LuaScriptInterface::luaPlayerSave(lua_State* L) return 1; } -int LuaScriptInterface::luaPlayerPopupFYI(lua_State* L) -{ - // player:popupFYI(message) - Player* player = getUserdata(L, 1); - if (player) { - const std::string& message = getString(L, 2); - player->sendFYIBox(message); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaPlayerIsPzLocked(lua_State* L) { // player:isPzLocked() @@ -9762,51 +8609,9 @@ int LuaScriptInterface::luaPlayerGetHouse(lua_State* L) return 1; } -int LuaScriptInterface::luaPlayerSendHouseWindow(lua_State* L) -{ - // player:sendHouseWindow(house, listId) - Player* player = getUserdata(L, 1); - if (!player) { - lua_pushnil(L); - return 1; - } - - House* house = getUserdata(L, 2); - if (!house) { - lua_pushnil(L); - return 1; - } - - uint32_t listId = getNumber(L, 3); - player->sendHouseWindow(house, listId); - pushBoolean(L, true); - return 1; -} - -int LuaScriptInterface::luaPlayerSetEditHouse(lua_State* L) -{ - // player:setEditHouse(house, listId) - Player* player = getUserdata(L, 1); - if (!player) { - lua_pushnil(L); - return 1; - } - - House* house = getUserdata(L, 2); - if (!house) { - lua_pushnil(L); - return 1; - } - - uint32_t listId = getNumber(L, 3); - player->setEditHouse(house, listId); - pushBoolean(L, true); - return 1; -} - int LuaScriptInterface::luaPlayerSetGhostMode(lua_State* L) { - // player:setGhostMode(enabled[, showEffect=true]) + // player:setGhostMode(enabled) Player* player = getUserdata(L, 1); if (!player) { lua_pushnil(L); @@ -9819,22 +8624,20 @@ int LuaScriptInterface::luaPlayerSetGhostMode(lua_State* L) return 1; } - bool showEffect = getBoolean(L, 3, true); - player->switchGhostMode(); Tile* tile = player->getTile(); const Position& position = player->getPosition(); - SpectatorVec spectators; - g_game.map.getSpectators(spectators, position, true, true); - for (Creature* spectator : spectators) { + SpectatorVec list; + g_game.map.getSpectators(list, position, true, true); + for (Creature* spectator : list) { Player* tmpPlayer = spectator->getPlayer(); if (tmpPlayer != player && !tmpPlayer->isAccessPlayer()) { if (enabled) { tmpPlayer->sendRemoveTileThing(position, tile->getStackposOfCreature(tmpPlayer, player)); } else { - tmpPlayer->sendCreatureAppear(player, position, showEffect); + tmpPlayer->sendCreatureAppear(player, position, true); } } else { tmpPlayer->sendCreatureChangeVisible(player, !enabled); @@ -9909,75 +8712,16 @@ int LuaScriptInterface::luaPlayerGetContainerIndex(lua_State* L) return 1; } -int LuaScriptInterface::luaPlayerGetInstantSpells(lua_State* L) +int LuaScriptInterface::luaPlayerGetTotalDamage(lua_State * L) { - // player:getInstantSpells() - Player* player = getUserdata(L, 1); - if (!player) { - lua_pushnil(L); - return 1; - } - - std::vector spells; - for (auto& spell : g_spells->getInstantSpells()) { - if (spell.second.canCast(player)) { - spells.push_back(&spell.second); - } - } - - lua_createtable(L, spells.size(), 0); - - int index = 0; - for (auto spell : spells) { - pushInstantSpell(L, *spell); - lua_rawseti(L, -2, ++index); - } - return 1; -} - -int LuaScriptInterface::luaPlayerCanCast(lua_State* L) -{ - // player:canCast(spell) - Player* player = getUserdata(L, 1); - InstantSpell* spell = getUserdata(L, 2); - if (player && spell) { - pushBoolean(L, spell->canCast(player)); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaPlayerHasChaseMode(lua_State* L) -{ - // player:hasChaseMode() + // player:getTotalDamage(attackSkill, attackValue, fightMode) Player* player = getUserdata(L, 1); if (player) { - pushBoolean(L, player->chaseMode); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaPlayerHasSecureMode(lua_State* L) -{ - // player:hasSecureMode() - Player* player = getUserdata(L, 1); - if (player) { - pushBoolean(L, player->secureMode); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaPlayerGetFightMode(lua_State* L) -{ - // player:getFightMode() - Player* player = getUserdata(L, 1); - if (player) { - lua_pushnumber(L, player->fightMode); + uint32_t attackSkill = getNumber(L, 2); + uint32_t attackValue = getNumber(L, 3); + fightMode_t fightMode = static_cast(getNumber(L, 4, FIGHTMODE_BALANCED)); + + lua_pushnumber(L, Combat::getTotalDamage(attackSkill, attackValue, fightMode)); } else { lua_pushnil(L); } @@ -10258,10 +9002,10 @@ int LuaScriptInterface::luaMonsterSelectTarget(lua_State* L) int LuaScriptInterface::luaMonsterSearchTarget(lua_State* L) { - // monster:searchTarget([searchType = TARGETSEARCH_DEFAULT]) + // monster:searchTarget([searchType = TARGETSEARCH_ANY]) Monster* monster = getUserdata(L, 1); if (monster) { - TargetSearchType_t searchType = getNumber(L, 2, TARGETSEARCH_DEFAULT); + TargetSearchType_t searchType = getNumber(L, 2, TARGETSEARCH_ANY); pushBoolean(L, monster->searchTarget(searchType)); } else { lua_pushnil(L); @@ -10324,28 +9068,6 @@ int LuaScriptInterface::luaNpcSetMasterPos(lua_State* L) return 1; } -int LuaScriptInterface::luaNpcGetSpeechBubble(lua_State* L) -{ - // npc:getSpeechBubble() - Npc* npc = getUserdata(L, 1); - if (npc) { - lua_pushnumber(L, npc->getSpeechBubble()); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaNpcSetSpeechBubble(lua_State* L) -{ - // npc:setSpeechBubble(speechBubble) - Npc* npc = getUserdata(L, 1); - if (npc) { - npc->setSpeechBubble(getNumber(L, 2)); - } - return 0; -} - // Guild int LuaScriptInterface::luaGuildCreate(lua_State* L) { @@ -10467,32 +9189,6 @@ int LuaScriptInterface::luaGuildGetRankByLevel(lua_State* L) return 1; } -int LuaScriptInterface::luaGuildGetMotd(lua_State* L) -{ - // guild:getMotd() - Guild* guild = getUserdata(L, 1); - if (guild) { - pushString(L, guild->getMotd()); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaGuildSetMotd(lua_State* L) -{ - // guild:setMotd(motd) - const std::string& motd = getString(L, 2); - Guild* guild = getUserdata(L, 1); - if (guild) { - guild->setMotd(motd); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - // Group int LuaScriptInterface::luaGroupCreate(lua_State* L) { @@ -10581,19 +9277,6 @@ int LuaScriptInterface::luaGroupGetMaxVipEntries(lua_State* L) return 1; } -int LuaScriptInterface::luaGroupHasFlag(lua_State* L) -{ - // group:hasFlag(flag) - Group* group = getUserdata(L, 1); - if (group) { - PlayerFlags flag = getNumber(L, 2); - pushBoolean(L, (group->flags & flag) != 0); - } else { - lua_pushnil(L); - } - return 1; -} - // Vocation int LuaScriptInterface::luaVocationCreate(lua_State* L) { @@ -10627,18 +9310,6 @@ int LuaScriptInterface::luaVocationGetId(lua_State* L) return 1; } -int LuaScriptInterface::luaVocationGetClientId(lua_State* L) -{ - // vocation:getClientId() - Vocation* vocation = getUserdata(L, 1); - if (vocation) { - lua_pushnumber(L, vocation->getClientId()); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaVocationGetName(lua_State* L) { // vocation:getName() @@ -11151,24 +9822,6 @@ int LuaScriptInterface::luaHouseGetDoorCount(lua_State* L) return 1; } -int LuaScriptInterface::luaHouseGetDoorIdByPosition(lua_State* L) -{ - // house:getDoorIdByPosition(position) - House* house = getUserdata(L, 1); - if (!house) { - lua_pushnil(L); - return 1; - } - - Door* door = house->getDoorByPosition(getPosition(L, 2)); - if (door) { - lua_pushnumber(L, door->getDoorId()); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaHouseGetTiles(lua_State* L) { // house:getTiles() @@ -11190,32 +9843,6 @@ int LuaScriptInterface::luaHouseGetTiles(lua_State* L) return 1; } -int LuaScriptInterface::luaHouseGetItems(lua_State* L) -{ - // house:getItems() - House* house = getUserdata(L, 1); - if (!house) { - lua_pushnil(L); - return 1; - } - - const auto& tiles = house->getTiles(); - lua_newtable(L); - - int index = 0; - for (Tile* tile : tiles) { - TileItemVector* itemVector = tile->getItemList(); - if(itemVector) { - for(Item* item : *itemVector) { - pushUserdata(L, item); - setItemMetatable(L, -1, item); - lua_rawseti(L, -2, ++index); - } - } - } - return 1; -} - int LuaScriptInterface::luaHouseGetTileCount(lua_State* L) { // house:getTileCount() @@ -11228,22 +9855,6 @@ int LuaScriptInterface::luaHouseGetTileCount(lua_State* L) return 1; } -int LuaScriptInterface::luaHouseCanEditAccessList(lua_State* L) -{ - // house:canEditAccessList(listId, player) - House* house = getUserdata(L, 1); - if (!house) { - lua_pushnil(L); - return 1; - } - - uint32_t listId = getNumber(L, 2); - Player* player = getPlayer(L, 3); - - pushBoolean(L, house->canEditAccessList(listId, player)); - return 1; -} - int LuaScriptInterface::luaHouseGetAccessList(lua_State* L) { // house:getAccessList(listId) @@ -11279,19 +9890,6 @@ int LuaScriptInterface::luaHouseSetAccessList(lua_State* L) return 1; } -int LuaScriptInterface::luaHouseKickPlayer(lua_State* L) -{ - // house:kickPlayer(player, targetPlayer) - House* house = getUserdata(L, 1); - if (!house) { - lua_pushnil(L); - return 1; - } - - pushBoolean(L, house->kickPlayer(getPlayer(L, 2), getPlayer(L, 3))); - return 1; -} - // ItemType int LuaScriptInterface::luaItemTypeCreate(lua_State* L) { @@ -11314,7 +9912,7 @@ int LuaScriptInterface::luaItemTypeIsCorpse(lua_State* L) // itemType:isCorpse() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - pushBoolean(L, itemType->corpseType != RACE_NONE); + pushBoolean(L, itemType->corpse); } else { lua_pushnil(L); } @@ -11345,6 +9943,18 @@ int LuaScriptInterface::luaItemTypeIsContainer(lua_State* L) return 1; } +int LuaScriptInterface::luaItemTypeIsChest(lua_State * L) +{ + // itemType:isChest() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->isChest()); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaItemTypeIsFluidContainer(lua_State* L) { // itemType:isFluidContainer() @@ -11417,31 +10027,7 @@ int LuaScriptInterface::luaItemTypeIsWritable(lua_State* L) return 1; } -int LuaScriptInterface::luaItemTypeIsBlocking(lua_State* L) -{ - // itemType:isBlocking() - const ItemType* itemType = getUserdata(L, 1); - if (itemType) { - pushBoolean(L, itemType->blockProjectile || itemType->blockSolid); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaItemTypeIsGroundTile(lua_State* L) -{ - // itemType:isGroundTile() - const ItemType* itemType = getUserdata(L, 1); - if (itemType) { - pushBoolean(L, itemType->isGroundTile()); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaItemTypeIsMagicField(lua_State* L) +int LuaScriptInterface::luaItemTypeIsMagicField(lua_State* L) { // itemType:isMagicField() const ItemType* itemType = getUserdata(L, 1); @@ -11453,24 +10039,61 @@ int LuaScriptInterface::luaItemTypeIsMagicField(lua_State* L) return 1; } -int LuaScriptInterface::luaItemTypeIsUseable(lua_State* L) +int LuaScriptInterface::luaItemTypeIsSplash(lua_State* L) { - // itemType:isUseable() + // itemType:isSplash() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - pushBoolean(L, itemType->isUseable()); + pushBoolean(L, itemType->isSplash()); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaItemTypeIsPickupable(lua_State* L) +int LuaScriptInterface::luaItemTypeIsKey(lua_State* L) { - // itemType:isPickupable() + // itemType:isKey() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - pushBoolean(L, itemType->isPickupable()); + pushBoolean(L, itemType->isKey()); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsDisguised(lua_State* L) +{ + // itemType:isDisguised() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->disguise); + } + else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsDestroyable(lua_State * L) +{ + // itemType:isDestroyable() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->destroy); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemTypeIsGroundTile(lua_State * L) +{ + // itemType:isGroundTile() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + pushBoolean(L, itemType->group == ITEM_GROUP_GROUND); } else { lua_pushnil(L); } @@ -11501,13 +10124,14 @@ int LuaScriptInterface::luaItemTypeGetId(lua_State* L) return 1; } -int LuaScriptInterface::luaItemTypeGetClientId(lua_State* L) +int LuaScriptInterface::luaItemTypeGetDisguiseId(lua_State * L) { - // itemType:getClientId() + // itemType:getDisguiseId() const ItemType* itemType = getUserdata(L, 1); if (itemType) { - lua_pushnumber(L, itemType->clientId); - } else { + lua_pushnumber(L, itemType->disguiseId); + } + else { lua_pushnil(L); } return 1; @@ -11573,6 +10197,18 @@ int LuaScriptInterface::luaItemTypeGetSlotPosition(lua_State *L) return 1; } +int LuaScriptInterface::luaItemTypeGetDestroyTarget(lua_State * L) +{ + // itemType:getDestroyTarget() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->destroyTarget); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaItemTypeGetCharges(lua_State* L) { // itemType:getCharges() @@ -11625,18 +10261,6 @@ int LuaScriptInterface::luaItemTypeGetWeight(lua_State* L) return 1; } -int LuaScriptInterface::luaItemTypeGetHitChance(lua_State* L) -{ - // itemType:getHitChance() - const ItemType* itemType = getUserdata(L, 1); - if (itemType) { - lua_pushnumber(L, itemType->hitChance); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaItemTypeGetShootRange(lua_State* L) { // itemType:getShootRange() @@ -11673,18 +10297,6 @@ int LuaScriptInterface::luaItemTypeGetDefense(lua_State* L) return 1; } -int LuaScriptInterface::luaItemTypeGetExtraDefense(lua_State* L) -{ - // itemType:getExtraDefense() - const ItemType* itemType = getUserdata(L, 1); - if (itemType) { - lua_pushnumber(L, itemType->extraDefense); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaItemTypeGetArmor(lua_State* L) { // itemType:getArmor() @@ -11709,66 +10321,6 @@ int LuaScriptInterface::luaItemTypeGetWeaponType(lua_State* L) return 1; } -int LuaScriptInterface::luaItemTypeGetAmmoType(lua_State* L) -{ - // itemType:getAmmoType() - const ItemType* itemType = getUserdata(L, 1); - if (itemType) { - lua_pushnumber(L, itemType->ammoType); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaItemTypeGetCorpseType(lua_State* L) -{ - // itemType:getCorpseType() - const ItemType* itemType = getUserdata(L, 1); - if (itemType) { - lua_pushnumber(L, itemType->corpseType); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaItemTypeGetElementType(lua_State* L) -{ - // itemType:getElementType() - const ItemType* itemType = getUserdata(L, 1); - if (!itemType) { - lua_pushnil(L); - return 1; - } - - auto& abilities = itemType->abilities; - if (abilities) { - lua_pushnumber(L, abilities->elementType); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaItemTypeGetElementDamage(lua_State* L) -{ - // itemType:getElementDamage() - const ItemType* itemType = getUserdata(L, 1); - if (!itemType) { - lua_pushnil(L); - return 1; - } - - auto& abilities = itemType->abilities; - if (abilities) { - lua_pushnumber(L, abilities->elementDamage); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaItemTypeGetTransformEquipId(lua_State* L) { // itemType:getTransformEquipId() @@ -11793,18 +10345,6 @@ int LuaScriptInterface::luaItemTypeGetTransformDeEquipId(lua_State* L) return 1; } -int LuaScriptInterface::luaItemTypeGetDestroyId(lua_State* L) -{ - // itemType:getDestroyId() - const ItemType* itemType = getUserdata(L, 1); - if (itemType) { - lua_pushnumber(L, itemType->destroyTo); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaItemTypeGetDecayId(lua_State* L) { // itemType:getDecayId() @@ -11817,6 +10357,18 @@ int LuaScriptInterface::luaItemTypeGetDecayId(lua_State* L) return 1; } +int LuaScriptInterface::luaItemTypeGetNutrition(lua_State* L) +{ + // itemType:getNutrition() + const ItemType* itemType = getUserdata(L, 1); + if (itemType) { + lua_pushnumber(L, itemType->nutrition); + } else { + lua_pushnil(L); + } + return 1; +} + int LuaScriptInterface::luaItemTypeGetRequiredLevel(lua_State* L) { // itemType:getRequiredLevel() @@ -11916,26 +10468,13 @@ int LuaScriptInterface::luaCombatSetArea(lua_State* L) return 1; } -int LuaScriptInterface::luaCombatAddCondition(lua_State* L) +int LuaScriptInterface::luaCombatSetCondition(lua_State* L) { - // combat:addCondition(condition) + // combat:setCondition(condition) Condition* condition = getUserdata(L, 2); Combat* combat = getUserdata(L, 1); if (combat && condition) { - combat->addCondition(condition->clone()); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaCombatClearConditions(lua_State* L) -{ - // combat:clearConditions() - Combat* combat = getUserdata(L, 1); - if (combat) { - combat->clearConditions(); + combat->setCondition(condition->clone()); pushBoolean(L, true); } else { lua_pushnil(L); @@ -11976,7 +10515,8 @@ int LuaScriptInterface::luaCombatSetOrigin(lua_State* L) if (combat) { combat->setOrigin(getNumber(L, 2)); pushBoolean(L, true); - } else { + } + else { lua_pushnil(L); } return 1; @@ -11991,14 +10531,6 @@ int LuaScriptInterface::luaCombatExecute(lua_State* L) return 1; } - if (isUserdata(L, 2)) { - LuaDataType type = getUserdataType(L, 2); - if (type != LuaData_Player && type != LuaData_Monster && type != LuaData_Npc) { - pushBoolean(L, false); - return 1; - } - } - Creature* creature = getCreature(L, 2); const LuaVariant& variant = getVariant(L, 3); @@ -12207,16 +10739,13 @@ int LuaScriptInterface::luaConditionSetParameter(lua_State* L) return 1; } -int LuaScriptInterface::luaConditionSetFormula(lua_State* L) +int LuaScriptInterface::luaConditionSetSpeedDelta(lua_State* L) { - // condition:setFormula(mina, minb, maxa, maxb) - double maxb = getNumber(L, 5); - double maxa = getNumber(L, 4); - double minb = getNumber(L, 3); - double mina = getNumber(L, 2); + // condition:setSpeedDelta(speedDelta) + int32_t speedDelta = getNumber(L, 2); ConditionSpeed* condition = dynamic_cast(getUserdata(L, 1)); if (condition) { - condition->setFormulaVars(mina, minb, maxa, maxb); + condition->setSpeedDelta(speedDelta); pushBoolean(L, true); } else { lua_pushnil(L); @@ -12227,19 +10756,18 @@ int LuaScriptInterface::luaConditionSetFormula(lua_State* L) int LuaScriptInterface::luaConditionSetOutfit(lua_State* L) { // condition:setOutfit(outfit) - // condition:setOutfit(lookTypeEx, lookType, lookHead, lookBody, lookLegs, lookFeet[, lookAddons[, lookMount]]) + // condition:setOutfit(lookTypeEx, lookType, lookHead, lookBody, lookLegs, lookFeet[, lookAddons]) Outfit_t outfit; if (isTable(L, 2)) { outfit = getOutfit(L, 2); } else { - outfit.lookMount = getNumber(L, 9, outfit.lookMount); outfit.lookAddons = getNumber(L, 8, outfit.lookAddons); outfit.lookFeet = getNumber(L, 7); outfit.lookLegs = getNumber(L, 6); outfit.lookBody = getNumber(L, 5); outfit.lookHead = getNumber(L, 4); - outfit.lookType = getNumber(L, 3); - outfit.lookTypeEx = getNumber(L, 2); + outfit.lookType = getNumber(L, 3); + outfit.lookTypeEx = getNumber(L, 2); } ConditionOutfit* condition = dynamic_cast(getUserdata(L, 1)); @@ -12252,21 +10780,37 @@ int LuaScriptInterface::luaConditionSetOutfit(lua_State* L) return 1; } -int LuaScriptInterface::luaConditionAddDamage(lua_State* L) +int LuaScriptInterface::luaConditionSetTiming(lua_State* L) { - // condition:addDamage(rounds, time, value) - int32_t value = getNumber(L, 4); - int32_t time = getNumber(L, 3); - int32_t rounds = getNumber(L, 2); + // condition:setTiming(count) + int32_t count = getNumber(L, 2); ConditionDamage* condition = dynamic_cast(getUserdata(L, 1)); if (condition) { - pushBoolean(L, condition->addDamage(rounds, time, value)); + if (condition->getType() == CONDITION_POISON) { + condition->setParam(CONDITION_PARAM_COUNT, 3); + condition->setParam(CONDITION_PARAM_MAX_COUNT, 3); + } else if (condition->getType() == CONDITION_FIRE) { + condition->setParam(CONDITION_PARAM_COUNT, 8); + condition->setParam(CONDITION_PARAM_MAX_COUNT, 8); + + count /= 10; + } else if (condition->getType() == CONDITION_ENERGY) { + condition->setParam(CONDITION_PARAM_COUNT, 10); + condition->setParam(CONDITION_PARAM_MAX_COUNT, 10); + + count /= 20; + } + + condition->setParam(CONDITION_PARAM_CYCLE, count); + + pushBoolean(L, true); } else { lua_pushnil(L); } return 1; } + // MonsterType int LuaScriptInterface::luaMonsterTypeCreate(lua_State* L) { @@ -12283,15 +10827,10 @@ int LuaScriptInterface::luaMonsterTypeCreate(lua_State* L) int LuaScriptInterface::luaMonsterTypeIsAttackable(lua_State* L) { - // get: monsterType:isAttackable() set: monsterType:isAttackable(bool) + // monsterType:isAttackable() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - pushBoolean(L, monsterType->info.isAttackable); - } else { - monsterType->info.isAttackable = getBoolean(L, 2); - pushBoolean(L, true); - } + pushBoolean(L, monsterType->info.isAttackable); } else { lua_pushnil(L); } @@ -12300,15 +10839,10 @@ int LuaScriptInterface::luaMonsterTypeIsAttackable(lua_State* L) int LuaScriptInterface::luaMonsterTypeIsConvinceable(lua_State* L) { - // get: monsterType:isConvinceable() set: monsterType:isConvinceable(bool) + // monsterType:isConvinceable() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - pushBoolean(L, monsterType->info.isConvinceable); - } else { - monsterType->info.isConvinceable = getBoolean(L, 2); - pushBoolean(L, true); - } + pushBoolean(L, monsterType->info.isConvinceable); } else { lua_pushnil(L); } @@ -12317,15 +10851,10 @@ int LuaScriptInterface::luaMonsterTypeIsConvinceable(lua_State* L) int LuaScriptInterface::luaMonsterTypeIsSummonable(lua_State* L) { - // get: monsterType:isSummonable() set: monsterType:isSummonable(bool) + // monsterType:isSummonable() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - pushBoolean(L, monsterType->info.isSummonable); - } else { - monsterType->info.isSummonable = getBoolean(L, 2); - pushBoolean(L, true); - } + pushBoolean(L, monsterType->info.isSummonable); } else { lua_pushnil(L); } @@ -12334,15 +10863,10 @@ int LuaScriptInterface::luaMonsterTypeIsSummonable(lua_State* L) int LuaScriptInterface::luaMonsterTypeIsIllusionable(lua_State* L) { - // get: monsterType:isIllusionable() set: monsterType:isIllusionable(bool) + // monsterType:isIllusionable() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - pushBoolean(L, monsterType->info.isIllusionable); - } else { - monsterType->info.isIllusionable = getBoolean(L, 2); - pushBoolean(L, true); - } + pushBoolean(L, monsterType->info.isIllusionable); } else { lua_pushnil(L); } @@ -12351,15 +10875,10 @@ int LuaScriptInterface::luaMonsterTypeIsIllusionable(lua_State* L) int LuaScriptInterface::luaMonsterTypeIsHostile(lua_State* L) { - // get: monsterType:isHostile() set: monsterType:isHostile(bool) + // monsterType:isHostile() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - pushBoolean(L, monsterType->info.isHostile); - } else { - monsterType->info.isHostile = getBoolean(L, 2); - pushBoolean(L, true); - } + pushBoolean(L, monsterType->info.isHostile); } else { lua_pushnil(L); } @@ -12368,33 +10887,24 @@ int LuaScriptInterface::luaMonsterTypeIsHostile(lua_State* L) int LuaScriptInterface::luaMonsterTypeIsPushable(lua_State* L) { - // get: monsterType:isPushable() set: monsterType:isPushable(bool) + // monsterType:isPushable() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - pushBoolean(L, monsterType->info.pushable); - } else { - monsterType->info.pushable = getBoolean(L, 2); - pushBoolean(L, true); - } + pushBoolean(L, monsterType->info.pushable); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeIsHealthHidden(lua_State* L) +int LuaScriptInterface::luaMonsterTypeIsHealthShown(lua_State* L) { - // get: monsterType:isHealthHidden() set: monsterType:isHealthHidden(bool) + // monsterType:isHealthShown() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - pushBoolean(L, monsterType->info.hiddenHealth); - } else { - monsterType->info.hiddenHealth = getBoolean(L, 2); - pushBoolean(L, true); - } - } else { + pushBoolean(L, !monsterType->info.hiddenHealth); + } + else { lua_pushnil(L); } return 1; @@ -12402,15 +10912,10 @@ int LuaScriptInterface::luaMonsterTypeIsHealthHidden(lua_State* L) int LuaScriptInterface::luaMonsterTypeCanPushItems(lua_State* L) { - // get: monsterType:canPushItems() set: monsterType:canPushItems(bool) + // monsterType:canPushItems() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - pushBoolean(L, monsterType->info.canPushItems); - } else { - monsterType->info.canPushItems = getBoolean(L, 2); - pushBoolean(L, true); - } + pushBoolean(L, monsterType->info.canPushItems); } else { lua_pushnil(L); } @@ -12419,226 +10924,106 @@ int LuaScriptInterface::luaMonsterTypeCanPushItems(lua_State* L) int LuaScriptInterface::luaMonsterTypeCanPushCreatures(lua_State* L) { - // get: monsterType:canPushCreatures() set: monsterType:canPushCreatures(bool) + // monsterType:canPushCreatures() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - pushBoolean(L, monsterType->info.canPushCreatures); - } else { - monsterType->info.canPushCreatures = getBoolean(L, 2); - pushBoolean(L, true); - } + pushBoolean(L, monsterType->info.canPushCreatures); } else { lua_pushnil(L); } return 1; } -int32_t LuaScriptInterface::luaMonsterTypeName(lua_State* L) +int LuaScriptInterface::luaMonsterTypeGetName(lua_State* L) { - // get: monsterType:name() set: monsterType:name(name) + // monsterType:getName() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - pushString(L, monsterType->name); - } else { - monsterType->name = getString(L, 2); - pushBoolean(L, true); - } + pushString(L, monsterType->name); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeNameDescription(lua_State* L) +int LuaScriptInterface::luaMonsterTypeGetNameDescription(lua_State* L) { - // get: monsterType:nameDescription() set: monsterType:nameDescription(desc) + // monsterType:getNameDescription() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - pushString(L, monsterType->nameDescription); - } else { - monsterType->nameDescription = getString(L, 2); - pushBoolean(L, true); - } + pushString(L, monsterType->nameDescription); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeHealth(lua_State* L) +int LuaScriptInterface::luaMonsterTypeGetHealth(lua_State* L) { - // get: monsterType:health() set: monsterType:health(health) + // monsterType:getHealth() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.health); - } else { - monsterType->info.health = getNumber(L, 2); - pushBoolean(L, true); - } + lua_pushnumber(L, monsterType->info.health); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeMaxHealth(lua_State* L) +int LuaScriptInterface::luaMonsterTypeGetMaxHealth(lua_State* L) { - // get: monsterType:maxHealth() set: monsterType:maxHealth(health) + // monsterType:getMaxHealth() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.healthMax); - } else { - monsterType->info.healthMax = getNumber(L, 2); - pushBoolean(L, true); - } + lua_pushnumber(L, monsterType->info.healthMax); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeRunHealth(lua_State* L) +int LuaScriptInterface::luaMonsterTypeGetRunHealth(lua_State* L) { - // get: monsterType:runHealth() set: monsterType:runHealth(health) + // monsterType:getRunHealth() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.runAwayHealth); - } else { - monsterType->info.runAwayHealth = getNumber(L, 2); - pushBoolean(L, true); - } + lua_pushnumber(L, monsterType->info.runAwayHealth); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeExperience(lua_State* L) +int LuaScriptInterface::luaMonsterTypeGetExperience(lua_State* L) { - // get: monsterType:experience() set: monsterType:experience(exp) + // monsterType:getExperience() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.experience); - } else { - monsterType->info.experience = getNumber(L, 2); - pushBoolean(L, true); - } + lua_pushnumber(L, monsterType->info.experience); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeCombatImmunities(lua_State* L) +int LuaScriptInterface::luaMonsterTypeGetCombatImmunities(lua_State* L) { - // get: monsterType:combatImmunities() set: monsterType:combatImmunities(immunity) + // monsterType:getCombatImmunities() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.damageImmunities); - } else { - std::string immunity = getString(L, 2); - if (immunity == "physical") { - monsterType->info.damageImmunities |= COMBAT_PHYSICALDAMAGE; - pushBoolean(L, true); - } else if (immunity == "energy") { - monsterType->info.damageImmunities |= COMBAT_ENERGYDAMAGE; - pushBoolean(L, true); - } else if (immunity == "fire") { - monsterType->info.damageImmunities |= COMBAT_FIREDAMAGE; - pushBoolean(L, true); - } else if (immunity == "poison" || immunity == "earth") { - monsterType->info.damageImmunities |= COMBAT_EARTHDAMAGE; - pushBoolean(L, true); - } else if (immunity == "drown") { - monsterType->info.damageImmunities |= COMBAT_DROWNDAMAGE; - pushBoolean(L, true); - } else if (immunity == "ice") { - monsterType->info.damageImmunities |= COMBAT_ICEDAMAGE; - pushBoolean(L, true); - } else if (immunity == "holy") { - monsterType->info.damageImmunities |= COMBAT_HOLYDAMAGE; - pushBoolean(L, true); - } else if (immunity == "death") { - monsterType->info.damageImmunities |= COMBAT_DEATHDAMAGE; - pushBoolean(L, true); - } else if (immunity == "lifedrain") { - monsterType->info.damageImmunities |= COMBAT_LIFEDRAIN; - pushBoolean(L, true); - } else if (immunity == "manadrain") { - monsterType->info.damageImmunities |= COMBAT_MANADRAIN; - pushBoolean(L, true); - } else { - std::cout << "[Warning - Monsters::loadMonster] Unknown immunity name " << immunity << " for monster: " << monsterType->name << std::endl; - lua_pushnil(L); - } - } + lua_pushnumber(L, monsterType->info.damageImmunities); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeConditionImmunities(lua_State* L) +int LuaScriptInterface::luaMonsterTypeGetConditionImmunities(lua_State* L) { - // get: monsterType:conditionImmunities() set: monsterType:conditionImmunities(immunity) + // monsterType:getConditionImmunities() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.conditionImmunities); - } else { - std::string immunity = getString(L, 2); - if (immunity == "physical") { - monsterType->info.conditionImmunities |= CONDITION_BLEEDING; - pushBoolean(L, true); - } else if (immunity == "energy") { - monsterType->info.conditionImmunities |= CONDITION_ENERGY; - pushBoolean(L, true); - } else if (immunity == "fire") { - monsterType->info.conditionImmunities |= CONDITION_FIRE; - pushBoolean(L, true); - } else if (immunity == "poison" || immunity == "earth") { - monsterType->info.conditionImmunities |= CONDITION_POISON; - pushBoolean(L, true); - } else if (immunity == "drown") { - monsterType->info.conditionImmunities |= CONDITION_DROWN; - pushBoolean(L, true); - } else if (immunity == "ice") { - monsterType->info.conditionImmunities |= CONDITION_FREEZING; - pushBoolean(L, true); - } else if (immunity == "holy") { - monsterType->info.conditionImmunities |= CONDITION_DAZZLED; - pushBoolean(L, true); - } else if (immunity == "death") { - monsterType->info.conditionImmunities |= CONDITION_CURSED; - pushBoolean(L, true); - } else if (immunity == "paralyze") { - monsterType->info.conditionImmunities |= CONDITION_PARALYZE; - pushBoolean(L, true); - } else if (immunity == "outfit") { - monsterType->info.conditionImmunities |= CONDITION_OUTFIT; - pushBoolean(L, true); - } else if (immunity == "drunk") { - monsterType->info.conditionImmunities |= CONDITION_DRUNK; - pushBoolean(L, true); - } else if (immunity == "invisible" || immunity == "invisibility") { - monsterType->info.conditionImmunities |= CONDITION_INVISIBLE; - pushBoolean(L, true); - } else if (immunity == "bleed") { - monsterType->info.conditionImmunities |= CONDITION_BLEEDING; - pushBoolean(L, true); - } else { - std::cout << "[Warning - Monsters::loadMonster] Unknown immunity name " << immunity << " for monster: " << monsterType->name << std::endl; - lua_pushnil(L); - } - } + lua_pushnumber(L, monsterType->info.conditionImmunities); } else { lua_pushnil(L); } @@ -12662,11 +11047,9 @@ int LuaScriptInterface::luaMonsterTypeGetAttackList(lua_State* L) setField(L, "chance", spellBlock.chance); setField(L, "isCombatSpell", spellBlock.combatSpell ? 1 : 0); - setField(L, "isMelee", spellBlock.isMelee ? 1 : 0); setField(L, "minCombatValue", spellBlock.minCombatValue); setField(L, "maxCombatValue", spellBlock.maxCombatValue); setField(L, "range", spellBlock.range); - setField(L, "speed", spellBlock.speed); pushUserdata(L, static_cast(spellBlock.spell)); lua_setfield(L, -2, "spell"); @@ -12675,29 +11058,6 @@ int LuaScriptInterface::luaMonsterTypeGetAttackList(lua_State* L) return 1; } -int LuaScriptInterface::luaMonsterTypeAddAttack(lua_State* L) -{ - // monsterType:addAttack(monsterspell) - MonsterType* monsterType = getUserdata(L, 1); - if (monsterType) { - MonsterSpell* spell = getUserdata(L, 2); - if (spell) { - spellBlock_t sb; - if (g_monsters.deserializeSpell(spell, sb, monsterType->name)) { - monsterType->info.attackSpells.push_back(std::move(sb)); - } else { - std::cout << monsterType->name << std::endl; - std::cout << "[Warning - Monsters::loadMonster] Cant load spell. " << spell->name << std::endl; - } - } else { - lua_pushnil(L); - } - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaMonsterTypeGetDefenseList(lua_State* L) { // monsterType:getDefenseList() @@ -12716,11 +11076,9 @@ int LuaScriptInterface::luaMonsterTypeGetDefenseList(lua_State* L) setField(L, "chance", spellBlock.chance); setField(L, "isCombatSpell", spellBlock.combatSpell ? 1 : 0); - setField(L, "isMelee", spellBlock.isMelee ? 1 : 0); setField(L, "minCombatValue", spellBlock.minCombatValue); setField(L, "maxCombatValue", spellBlock.maxCombatValue); setField(L, "range", spellBlock.range); - setField(L, "speed", spellBlock.speed); pushUserdata(L, static_cast(spellBlock.spell)); lua_setfield(L, -2, "spell"); @@ -12729,29 +11087,6 @@ int LuaScriptInterface::luaMonsterTypeGetDefenseList(lua_State* L) return 1; } -int LuaScriptInterface::luaMonsterTypeAddDefense(lua_State* L) -{ - // monsterType:addDefense(monsterspell) - MonsterType* monsterType = getUserdata(L, 1); - if (monsterType) { - MonsterSpell* spell = getUserdata(L, 2); - if (spell) { - spellBlock_t sb; - if (g_monsters.deserializeSpell(spell, sb, monsterType->name)) { - monsterType->info.defenseSpells.push_back(std::move(sb)); - } else { - std::cout << monsterType->name << std::endl; - std::cout << "[Warning - Monsters::loadMonster] Cant load spell. " << spell->name << std::endl; - } - } else { - lua_pushnil(L); - } - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaMonsterTypeGetElementList(lua_State* L) { // monsterType:getElementList() @@ -12769,20 +11104,6 @@ int LuaScriptInterface::luaMonsterTypeGetElementList(lua_State* L) return 1; } -int LuaScriptInterface::luaMonsterTypeAddElement(lua_State* L) -{ - // monsterType:addElement(type, percent) - MonsterType* monsterType = getUserdata(L, 1); - if (monsterType) { - CombatType_t element = getNumber(L, 2); - monsterType->info.elementMap[element] = getNumber(L, 3); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaMonsterTypeGetVoices(lua_State* L) { // monsterType:getVoices() @@ -12803,24 +11124,6 @@ int LuaScriptInterface::luaMonsterTypeGetVoices(lua_State* L) return 1; } -int LuaScriptInterface::luaMonsterTypeAddVoice(lua_State* L) -{ - // monsterType:addVoice(sentence, interval, chance, yell) - MonsterType* monsterType = getUserdata(L, 1); - if (monsterType) { - voiceBlock_t voice; - voice.text = getString(L, 2); - monsterType->info.yellSpeedTicks = getNumber(L, 3); - monsterType->info.yellChance = getNumber(L, 4); - voice.yellText = getBoolean(L, 5); - monsterType->info.voiceVector.push_back(voice); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaMonsterTypeGetLoot(lua_State* L) { // monsterType:getLoot() @@ -12830,25 +11133,27 @@ int LuaScriptInterface::luaMonsterTypeGetLoot(lua_State* L) return 1; } - pushLoot(L, monsterType->info.lootItems); - return 1; -} + static const std::function&)> parseLoot = [&](const std::vector& lootList) { + lua_createtable(L, lootList.size(), 0); -int LuaScriptInterface::luaMonsterTypeAddLoot(lua_State* L) -{ - // monsterType:addLoot(loot) - MonsterType* monsterType = getUserdata(L, 1); - if (monsterType) { - Loot* loot = getUserdata(L, 2); - if (loot) { - monsterType->loadLoot(monsterType, loot->lootBlock); - pushBoolean(L, true); - } else { - lua_pushnil(L); + int index = 0; + for (const auto& lootBlock : lootList) { + lua_createtable(L, 0, 7); + + setField(L, "itemId", lootBlock.id); + setField(L, "chance", lootBlock.chance); + setField(L, "subType", lootBlock.subType); + setField(L, "maxCount", lootBlock.countmax); + setField(L, "actionId", lootBlock.actionId); + setField(L, "text", lootBlock.text); + + parseLoot(lootBlock.childLoot); + lua_setfield(L, -2, "childLoot"); + + lua_rawseti(L, -2, ++index); } - } else { - lua_pushnil(L); - } + }; + parseLoot(monsterType->info.lootItems); return 1; } @@ -12870,52 +11175,6 @@ int LuaScriptInterface::luaMonsterTypeGetCreatureEvents(lua_State* L) return 1; } -int LuaScriptInterface::luaMonsterTypeRegisterEvent(lua_State* L) -{ - // monsterType:registerEvent(name) - MonsterType* monsterType = getUserdata(L, 1); - if (monsterType) { - monsterType->info.scripts.push_back(getString(L, 2)); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterTypeEventOnCallback(lua_State* L) -{ - // monsterType:onThink(callback) - // monsterType:onAppear(callback) - // monsterType:onDisappear(callback) - // monsterType:onMove(callback) - // monsterType:onSay(callback) - MonsterType* monsterType = getUserdata(L, 1); - if (monsterType) { - if (monsterType->loadCallback(&g_scripts->getScriptInterface())) { - pushBoolean(L, true); - return 1; - } - pushBoolean(L, false); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterTypeEventType(lua_State* L) -{ - // monstertype:eventType(event) - MonsterType* monsterType = getUserdata(L, 1); - if (monsterType) { - monsterType->info.eventType = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaMonsterTypeGetSummonList(lua_State* L) { // monsterType:getSummonList() @@ -12930,694 +11189,152 @@ int LuaScriptInterface::luaMonsterTypeGetSummonList(lua_State* L) for (const auto& summonBlock : monsterType->info.summons) { lua_createtable(L, 0, 3); setField(L, "name", summonBlock.name); - setField(L, "speed", summonBlock.speed); setField(L, "chance", summonBlock.chance); lua_rawseti(L, -2, ++index); } return 1; } -int LuaScriptInterface::luaMonsterTypeAddSummon(lua_State* L) +int LuaScriptInterface::luaMonsterTypeGetMaxSummons(lua_State* L) { - // monsterType:addSummon(name, interval, chance) + // monsterType:getMaxSummons() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - summonBlock_t summon; - summon.name = getString(L, 2); - summon.chance = getNumber(L, 3); - summon.speed = getNumber(L, 4); - monsterType->info.summons.push_back(summon); - pushBoolean(L, true); + lua_pushnumber(L, monsterType->info.maxSummons); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeMaxSummons(lua_State* L) +int LuaScriptInterface::luaMonsterTypeGetArmor(lua_State* L) { - // get: monsterType:maxSummons() set: monsterType:maxSummons(ammount) + // monsterType:getArmor() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.maxSummons); - } else { - monsterType->info.maxSummons = getNumber(L, 2); - pushBoolean(L, true); - } + lua_pushnumber(L, monsterType->info.armor); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeArmor(lua_State* L) +int LuaScriptInterface::luaMonsterTypeGetDefense(lua_State* L) { - // get: monsterType:armor() set: monsterType:armor(armor) + // monsterType:getDefense() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.armor); - } else { - monsterType->info.armor = getNumber(L, 2); - pushBoolean(L, true); - } + lua_pushnumber(L, monsterType->info.defense); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeDefense(lua_State* L) +int LuaScriptInterface::luaMonsterTypeGetOutfit(lua_State* L) { - // get: monsterType:defense() set: monsterType:defense(defense) + // monsterType:getOutfit() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.defense); - } else { - monsterType->info.defense = getNumber(L, 2); - pushBoolean(L, true); - } + pushOutfit(L, monsterType->info.outfit); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeOutfit(lua_State* L) +int LuaScriptInterface::luaMonsterTypeGetRace(lua_State* L) { - // get: monsterType:outfit() set: monsterType:outfit(outfit) + // monsterType:getRace() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - pushOutfit(L, monsterType->info.outfit); - } else { - monsterType->info.outfit = getOutfit(L, 2); - pushBoolean(L, true); - } + lua_pushnumber(L, monsterType->info.race); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeRace(lua_State* L) +int LuaScriptInterface::luaMonsterTypeGetCorpseId(lua_State* L) { - // get: monsterType:race() set: monsterType:race(race) + // monsterType:getCorpseId() MonsterType* monsterType = getUserdata(L, 1); - std::string race = getString(L, 2); if (monsterType) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.race); - } else { - if (race == "venom") { - monsterType->info.race = RACE_VENOM; - } else if (race == "blood") { - monsterType->info.race = RACE_BLOOD; - } else if (race == "undead") { - monsterType->info.race = RACE_UNDEAD; - } else if (race == "fire") { - monsterType->info.race = RACE_FIRE; - } else if (race == "energy") { - monsterType->info.race = RACE_ENERGY; - } else { - std::cout << "[Warning - Monsters::loadMonster] Unknown race type " << race << "." << std::endl; - lua_pushnil(L); - return 1; - } - pushBoolean(L, true); - } + lua_pushnumber(L, monsterType->info.lookcorpse); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeCorpseId(lua_State* L) +int LuaScriptInterface::luaMonsterTypeGetManaCost(lua_State* L) { - // get: monsterType:corpseId() set: monsterType:corpseId(id) + // monsterType:getManaCost() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.lookcorpse); - } else { - monsterType->info.lookcorpse = getNumber(L, 2); - lua_pushboolean(L, true); - } + lua_pushnumber(L, monsterType->info.manaCost); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeManaCost(lua_State* L) +int LuaScriptInterface::luaMonsterTypeGetBaseSpeed(lua_State* L) { - // get: monsterType:manaCost() set: monsterType:manaCost(mana) + // monsterType:getBaseSpeed() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.manaCost); - } else { - monsterType->info.manaCost = getNumber(L, 2); - pushBoolean(L, true); - } + lua_pushnumber(L, monsterType->info.baseSpeed); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeBaseSpeed(lua_State* L) +int LuaScriptInterface::luaMonsterTypeGetLight(lua_State* L) { - // get: monsterType:baseSpeed() set: monsterType:baseSpeed(speed) - MonsterType* monsterType = getUserdata(L, 1); - if (monsterType) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.baseSpeed); - } else { - monsterType->info.baseSpeed = getNumber(L, 2); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterTypeLight(lua_State* L) -{ - // get: monsterType:light() set: monsterType:light(color, level) + // monsterType:getLight() MonsterType* monsterType = getUserdata(L, 1); if (!monsterType) { lua_pushnil(L); return 1; } - if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.light.level); - lua_pushnumber(L, monsterType->info.light.color); - return 2; - } else { - monsterType->info.light.color = getNumber(L, 2); - monsterType->info.light.level = getNumber(L, 3); - pushBoolean(L, true); - } - return 1; + + lua_pushnumber(L, monsterType->info.light.level); + lua_pushnumber(L, monsterType->info.light.color); + return 2; } -int LuaScriptInterface::luaMonsterTypeStaticAttackChance(lua_State* L) +int LuaScriptInterface::luaMonsterTypeGetTargetDistance(lua_State* L) { - // get: monsterType:staticAttackChance() set: monsterType:staticAttackChance(chance) + // monsterType:getTargetDistance() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.staticAttackChance); - } else { - monsterType->info.staticAttackChance = getNumber(L, 2); - pushBoolean(L, true); - } + lua_pushnumber(L, monsterType->info.targetDistance); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeTargetDistance(lua_State* L) +int LuaScriptInterface::luaMonsterTypeGetChangeTargetChance(lua_State* L) { - // get: monsterType:targetDistance() set: monsterType:targetDistance(distance) + // monsterType:getChangeTargetChance() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.targetDistance); - } else { - monsterType->info.targetDistance = getNumber(L, 2); - pushBoolean(L, true); - } + lua_pushnumber(L, monsterType->info.changeTargetChance); } else { lua_pushnil(L); } return 1; } -int LuaScriptInterface::luaMonsterTypeYellChance(lua_State* L) +int LuaScriptInterface::luaMonsterTypeGetChangeTargetSpeed(lua_State* L) { - // get: monsterType:yellChance() set: monsterType:yellChance(chance) + // monsterType:getChangeTargetSpeed() MonsterType* monsterType = getUserdata(L, 1); if (monsterType) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.yellChance); - } else { - monsterType->info.yellChance = getNumber(L, 2); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterTypeYellSpeedTicks(lua_State* L) -{ - // get: monsterType:yellSpeedTicks() set: monsterType:yellSpeedTicks(rate) - MonsterType* monsterType = getUserdata(L, 1); - if (monsterType) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.yellSpeedTicks); - } else { - monsterType->info.yellSpeedTicks = getNumber(L, 2); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterTypeChangeTargetChance(lua_State* L) -{ - // get: monsterType:changeTargetChance() set: monsterType:changeTargetChance(chance) - MonsterType* monsterType = getUserdata(L, 1); - if (monsterType) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.changeTargetChance); - } else { - monsterType->info.changeTargetChance = getNumber(L, 2); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterTypeChangeTargetSpeed(lua_State* L) -{ - // get: monsterType:changeTargetSpeed() set: monsterType:changeTargetSpeed(speed) - MonsterType* monsterType = getUserdata(L, 1); - if (monsterType) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, monsterType->info.changeTargetSpeed); - } else { - monsterType->info.changeTargetSpeed = getNumber(L, 2); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -// Loot -int LuaScriptInterface::luaCreateLoot(lua_State* L) -{ - // Loot() will create a new loot item - Loot* loot = new Loot(); - if (loot) { - pushUserdata(L, loot); - setMetatable(L, -1, "Loot"); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaDeleteLoot(lua_State* L) -{ - // loot:delete() loot:__gc() - Loot** lootPtr = getRawUserdata(L, 1); - if (lootPtr && *lootPtr) { - delete *lootPtr; - *lootPtr = nullptr; - } - return 0; -} - -int LuaScriptInterface::luaLootSetId(lua_State* L) -{ - // loot:setId(id or name) - Loot* loot = getUserdata(L, 1); - uint16_t item; - if (loot) { - if (isNumber(L, 2)) { - loot->lootBlock.id = getNumber(L, 2); - } else { - item = Item::items.getItemIdByName(getString(L, 2)); - loot->lootBlock.id = item; - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaLootSetSubType(lua_State* L) -{ - // loot:setSubType(type) - Loot* loot = getUserdata(L, 1); - if (loot) { - loot->lootBlock.subType = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaLootSetChance(lua_State* L) -{ - // loot:setChance(chance) - Loot* loot = getUserdata(L, 1); - if (loot) { - loot->lootBlock.chance = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaLootSetMaxCount(lua_State* L) -{ - // loot:setMaxCount(max) - Loot* loot = getUserdata(L, 1); - if (loot) { - loot->lootBlock.countmax = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaLootSetActionId(lua_State* L) -{ - // loot:setActionId(actionid) - Loot* loot = getUserdata(L, 1); - if (loot) { - loot->lootBlock.actionId = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaLootSetDescription(lua_State* L) -{ - // loot:setDescription(desc) - Loot* loot = getUserdata(L, 1); - if (loot) { - loot->lootBlock.text = getString(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaLootAddChildLoot(lua_State* L) -{ - // loot:addChildLoot(loot) - Loot* loot = getUserdata(L, 1); - if (loot) { - loot->lootBlock.childLoot.push_back(getUserdata(L, 2)->lootBlock); - } else { - lua_pushnil(L); - } - return 1; -} - -// MonsterSpell -int LuaScriptInterface::luaCreateMonsterSpell(lua_State* L) -{ - // MonsterSpell() will create a new Monster Spell - MonsterSpell* spell = new MonsterSpell(); - if (spell) { - pushUserdata(L, spell); - setMetatable(L, -1, "MonsterSpell"); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaDeleteMonsterSpell(lua_State* L) -{ - // monsterSpell:delete() monsterSpell:__gc() - MonsterSpell** monsterSpellPtr = getRawUserdata(L, 1); - if (monsterSpellPtr && *monsterSpellPtr) { - delete *monsterSpellPtr; - *monsterSpellPtr = nullptr; - } - return 0; -} - -int LuaScriptInterface::luaMonsterSpellSetType(lua_State* L) -{ - // monsterSpell:setType(type) - MonsterSpell* spell = getUserdata(L, 1); - if (spell) { - spell->name = getString(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterSpellSetScriptName(lua_State* L) -{ - // monsterSpell:setScriptName(name) - MonsterSpell* spell = getUserdata(L, 1); - if (spell) { - spell->scriptName = getString(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterSpellSetChance(lua_State* L) -{ - // monsterSpell:setChance(chance) - MonsterSpell* spell = getUserdata(L, 1); - if (spell) { - spell->chance = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterSpellSetInterval(lua_State* L) -{ - // monsterSpell:setInterval(interval) - MonsterSpell* spell = getUserdata(L, 1); - if (spell) { - spell->interval = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterSpellSetRange(lua_State* L) -{ - // monsterSpell:setRange(range) - MonsterSpell* spell = getUserdata(L, 1); - if (spell) { - spell->range = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterSpellSetCombatValue(lua_State* L) -{ - // monsterSpell:setCombatValue(min, max) - MonsterSpell* spell = getUserdata(L, 1); - if (spell) { - spell->minCombatValue = getNumber(L, 2); - spell->maxCombatValue = getNumber(L, 3); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterSpellSetCombatType(lua_State* L) -{ - // monsterSpell:setCombatType(combatType_t) - MonsterSpell* spell = getUserdata(L, 1); - if (spell) { - spell->combatType = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterSpellSetAttackValue(lua_State* L) -{ - // monsterSpell:setAttackValue(attack, skill) - MonsterSpell* spell = getUserdata(L, 1); - if (spell) { - spell->attack = getNumber(L, 2); - spell->skill = getNumber(L, 3); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterSpellSetNeedTarget(lua_State* L) -{ - // monsterSpell:setNeedTarget(bool) - MonsterSpell* spell = getUserdata(L, 1); - if (spell) { - spell->needTarget = getBoolean(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterSpellSetCombatLength(lua_State* L) -{ - // monsterSpell:setCombatLength(length) - MonsterSpell* spell = getUserdata(L, 1); - if (spell) { - spell->length = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterSpellSetCombatSpread(lua_State* L) -{ - // monsterSpell:setCombatSpread(spread) - MonsterSpell* spell = getUserdata(L, 1); - if (spell) { - spell->spread = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterSpellSetCombatRadius(lua_State* L) -{ - // monsterSpell:setCombatRadius(radius) - MonsterSpell* spell = getUserdata(L, 1); - if (spell) { - spell->radius = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterSpellSetConditionType(lua_State* L) -{ - // monsterSpell:setConditionType(type) - MonsterSpell* spell = getUserdata(L, 1); - if (spell) { - spell->conditionType = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterSpellSetConditionDamage(lua_State* L) -{ - // monsterSpell:setConditionDamage(min, max, start) - MonsterSpell* spell = getUserdata(L, 1); - if (spell) { - spell->conditionMinDamage = getNumber(L, 2); - spell->conditionMaxDamage = getNumber(L, 3); - spell->conditionStartDamage = getNumber(L, 4); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterSpellSetConditionSpeedChange(lua_State* L) -{ - // monsterSpell:setConditionSpeedChange(speed) - MonsterSpell* spell = getUserdata(L, 1); - if (spell) { - spell->speedChange = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterSpellSetConditionDuration(lua_State* L) -{ - // monsterSpell:setConditionDuration(duration) - MonsterSpell* spell = getUserdata(L, 1); - if (spell) { - spell->duration = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterSpellSetConditionTickInterval(lua_State* L) -{ - // monsterSpell:setConditionTickInterval(interval) - MonsterSpell* spell = getUserdata(L, 1); - if (spell) { - spell->tickInterval = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterSpellSetCombatShootEffect(lua_State* L) -{ - // monsterSpell:setCombatShootEffect(effect) - MonsterSpell* spell = getUserdata(L, 1); - if (spell) { - spell->shoot = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMonsterSpellSetCombatEffect(lua_State* L) -{ - // monsterSpell:setCombatEffect(effect) - MonsterSpell* spell = getUserdata(L, 1); - if (spell) { - spell->effect = getNumber(L, 2); - pushBoolean(L, true); + lua_pushnumber(L, monsterType->info.changeTargetSpeed); } else { lua_pushnil(L); } @@ -13625,28 +11342,6 @@ int LuaScriptInterface::luaMonsterSpellSetCombatEffect(lua_State* L) } // Party -int32_t LuaScriptInterface::luaPartyCreate(lua_State* L) -{ - // Party(userdata) - Player* player = getUserdata(L, 2); - if (!player) { - lua_pushnil(L); - return 1; - } - - Party* party = player->getParty(); - if (!party) { - party = new Party(player); - g_game.updatePlayerShield(player); - player->sendCreatureSkull(player); - pushUserdata(L, party); - setMetatable(L, -1, "Party"); - } else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaPartyDisband(lua_State* L) { // party:disband() @@ -13838,7 +11533,7 @@ int LuaScriptInterface::luaPartyShareExperience(lua_State* L) uint64_t experience = getNumber(L, 2); Party* party = getUserdata(L, 1); if (party) { - party->shareExperience(experience); + party->shareExperience(experience, nullptr); pushBoolean(L, true); } else { lua_pushnil(L); @@ -13859,2222 +11554,6 @@ int LuaScriptInterface::luaPartySetSharedExperience(lua_State* L) return 1; } -// Spells -int LuaScriptInterface::luaSpellCreate(lua_State* L) -{ - // Spell(words, name or id) to get an existing spell - // Spell(type) ex: Spell(SPELL_INSTANT) or Spell(SPELL_RUNE) to create a new spell - if (lua_gettop(L) == 1) { - std::cout << "[Error - Spell::luaSpellCreate] There is no parameter set!" << std::endl; - lua_pushnil(L); - return 1; - } - - SpellType_t type = getNumber(L, 2); - - if (isString(L, 2)) { - std::string tmp = asLowerCaseString(getString(L, 2)); - if (tmp == "instant") { - type = SPELL_INSTANT; - } else if (tmp == "rune") { - type = SPELL_RUNE; - } - } - - if (type == SPELL_INSTANT) { - InstantSpell* spell = new InstantSpell(getScriptEnv()->getScriptInterface()); - spell->fromLua = true; - pushUserdata(L, spell); - setMetatable(L, -1, "Spell"); - spell->spellType = SPELL_INSTANT; - return 1; - } else if (type == SPELL_RUNE) { - RuneSpell* spell = new RuneSpell(getScriptEnv()->getScriptInterface()); - spell->fromLua = true; - pushUserdata(L, spell); - setMetatable(L, -1, "Spell"); - spell->spellType = SPELL_RUNE; - return 1; - } - - // isNumber(L, 2) doesn't work here for some reason, maybe a bug? - if (getNumber(L, 2)) { - InstantSpell* instant = g_spells->getInstantSpellById(getNumber(L, 2)); - if (instant) { - pushUserdata(L, instant); - setMetatable(L, -1, "Spell"); - return 1; - } - RuneSpell* rune = g_spells->getRuneSpell(getNumber(L, 2)); - if (rune) { - pushUserdata(L, rune); - setMetatable(L, -1, "Spell"); - return 1; - } - } else if (isString(L, 2)) { - std::string arg = getString(L, 2); - InstantSpell* instant = g_spells->getInstantSpellByName(arg); - if (instant) { - pushUserdata(L, instant); - setMetatable(L, -1, "Spell"); - return 1; - } - instant = g_spells->getInstantSpell(arg); - if (instant) { - pushUserdata(L, instant); - setMetatable(L, -1, "Spell"); - return 1; - } - RuneSpell* rune = g_spells->getRuneSpellByName(arg); - if (rune) { - pushUserdata(L, rune); - setMetatable(L, -1, "Spell"); - return 1; - } - } - lua_pushnil(L); - return 1; -} - -int LuaScriptInterface::luaSpellOnCastSpell(lua_State* L) -{ - // spell:onCastSpell(callback) - Spell* spell = getUserdata(L, 1); - if (spell) { - if (spell->spellType == SPELL_INSTANT) { - InstantSpell* instant = dynamic_cast(getUserdata(L, 1)); - if (!instant->loadCallback()) { - pushBoolean(L, false); - return 1; - } - instant->scripted = true; - pushBoolean(L, true); - } else if (spell->spellType == SPELL_RUNE) { - RuneSpell* rune = dynamic_cast(getUserdata(L, 1)); - if (!rune->loadCallback()) { - pushBoolean(L, false); - return 1; - } - rune->scripted = true; - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaSpellRegister(lua_State* L) -{ - // spell:register() - Spell* spell = getUserdata(L, 1); - if (spell) { - if (spell->spellType == SPELL_INSTANT) { - InstantSpell* instant = dynamic_cast(getUserdata(L, 1)); - if (!instant->isScripted()) { - pushBoolean(L, false); - return 1; - } - pushBoolean(L, g_spells->registerInstantLuaEvent(instant)); - } else if (spell->spellType == SPELL_RUNE) { - RuneSpell* rune = dynamic_cast(getUserdata(L, 1)); - if (rune->getMagicLevel() != 0 || rune->getLevel() != 0) { - //Change information in the ItemType to get accurate description - ItemType& iType = Item::items.getItemType(rune->getRuneItemId()); - iType.name = rune->getName(); - iType.runeMagLevel = rune->getMagicLevel(); - iType.runeLevel = rune->getLevel(); - iType.charges = rune->getCharges(); - } - if (!rune->isScripted()) { - pushBoolean(L, false); - return 1; - } - pushBoolean(L, g_spells->registerRuneLuaEvent(rune)); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaSpellName(lua_State* L) -{ - // spell:name(name) - Spell* spell = getUserdata(L, 1); - if (spell) { - if (lua_gettop(L) == 1) { - pushString(L, spell->getName()); - } else { - spell->setName(getString(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaSpellId(lua_State* L) -{ - // spell:id(id) - Spell* spell = getUserdata(L, 1); - if (spell) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getId()); - } else { - spell->setId(getNumber(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaSpellGroup(lua_State* L) -{ - // spell:group(primaryGroup[, secondaryGroup]) - Spell* spell = getUserdata(L, 1); - if (spell) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getGroup()); - lua_pushnumber(L, spell->getSecondaryGroup()); - return 2; - } else if (lua_gettop(L) == 2) { - SpellGroup_t group = getNumber(L, 2); - if (group) { - spell->setGroup(group); - pushBoolean(L, true); - } else if (isString(L, 2)) { - group = stringToSpellGroup(getString(L, 2)); - if (group != SPELLGROUP_NONE) { - spell->setGroup(group); - } else { - std::cout << "[Warning - Spell::group] Unknown group: " << getString(L, 2) << std::endl; - pushBoolean(L, false); - return 1; - } - pushBoolean(L, true); - } else { - std::cout << "[Warning - Spell::group] Unknown group: " << getString(L, 2) << std::endl; - pushBoolean(L, false); - return 1; - } - } else { - SpellGroup_t primaryGroup = getNumber(L, 2); - SpellGroup_t secondaryGroup = getNumber(L, 2); - if (primaryGroup && secondaryGroup) { - spell->setGroup(primaryGroup); - spell->setSecondaryGroup(secondaryGroup); - pushBoolean(L, true); - } else if (isString(L, 2) && isString(L, 3)) { - primaryGroup = stringToSpellGroup(getString(L, 2)); - if (primaryGroup != SPELLGROUP_NONE) { - spell->setGroup(primaryGroup); - } else { - std::cout << "[Warning - Spell::group] Unknown primaryGroup: " << getString(L, 2) << std::endl; - pushBoolean(L, false); - return 1; - } - secondaryGroup = stringToSpellGroup(getString(L, 3)); - if (secondaryGroup != SPELLGROUP_NONE) { - spell->setSecondaryGroup(secondaryGroup); - } else { - std::cout << "[Warning - Spell::group] Unknown secondaryGroup: " << getString(L, 3) << std::endl; - pushBoolean(L, false); - return 1; - } - pushBoolean(L, true); - } else { - std::cout << "[Warning - Spell::group] Unknown primaryGroup: " << getString(L, 2) << " or secondaryGroup: " << getString(L, 3) << std::endl; - pushBoolean(L, false); - return 1; - } - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaSpellCooldown(lua_State* L) -{ - // spell:cooldown(cooldown) - Spell* spell = getUserdata(L, 1); - if (spell) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getCooldown()); - } else { - spell->setCooldown(getNumber(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaSpellGroupCooldown(lua_State* L) -{ - // spell:groupCooldown(primaryGroupCd[, secondaryGroupCd]) - Spell* spell = getUserdata(L, 1); - if (spell) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getGroupCooldown()); - lua_pushnumber(L, spell->getSecondaryCooldown()); - return 2; - } else if (lua_gettop(L) == 2) { - spell->setGroupCooldown(getNumber(L, 2)); - pushBoolean(L, true); - } else { - spell->setGroupCooldown(getNumber(L, 2)); - spell->setSecondaryCooldown(getNumber(L, 3)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaSpellLevel(lua_State* L) -{ - // spell:level(lvl) - Spell* spell = getUserdata(L, 1); - if (spell) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getLevel()); - } else { - spell->setLevel(getNumber(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaSpellMagicLevel(lua_State* L) -{ - // spell:magicLevel(lvl) - Spell* spell = getUserdata(L, 1); - if (spell) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getMagicLevel()); - } else { - spell->setMagicLevel(getNumber(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaSpellMana(lua_State* L) -{ - // spell:mana(mana) - Spell* spell = getUserdata(L, 1); - if (spell) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getMana()); - } else { - spell->setMana(getNumber(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaSpellManaPercent(lua_State* L) -{ - // spell:manaPercent(percent) - Spell* spell = getUserdata(L, 1); - if (spell) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getManaPercent()); - } else { - spell->setManaPercent(getNumber(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaSpellSoul(lua_State* L) -{ - // spell:soul(soul) - Spell* spell = getUserdata(L, 1); - if (spell) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getSoulCost()); - } else { - spell->setSoulCost(getNumber(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaSpellRange(lua_State* L) -{ - // spell:range(range) - Spell* spell = getUserdata(L, 1); - if (spell) { - if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getRange()); - } else { - spell->setRange(getNumber(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaSpellPremium(lua_State* L) -{ - // spell:isPremium(bool) - Spell* spell = getUserdata(L, 1); - if (spell) { - if (lua_gettop(L) == 1) { - pushBoolean(L, spell->isPremium()); - } else { - spell->setPremium(getBoolean(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaSpellEnabled(lua_State* L) -{ - // spell:isEnabled(bool) - Spell* spell = getUserdata(L, 1); - if (spell) { - if (lua_gettop(L) == 1) { - pushBoolean(L, spell->isEnabled()); - } else { - spell->setEnabled(getBoolean(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaSpellNeedTarget(lua_State* L) -{ - // spell:needTarget(bool) - Spell* spell = getUserdata(L, 1); - if (spell) { - if (lua_gettop(L) == 1) { - pushBoolean(L, spell->getNeedTarget()); - } else { - spell->setNeedTarget(getBoolean(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaSpellNeedWeapon(lua_State* L) -{ - // spell:needWeapon(bool) - Spell* spell = getUserdata(L, 1); - if (spell) { - if (lua_gettop(L) == 1) { - pushBoolean(L, spell->getNeedWeapon()); - } else { - spell->setNeedWeapon(getBoolean(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaSpellNeedLearn(lua_State* L) -{ - // spell:needLearn(bool) - Spell* spell = getUserdata(L, 1); - if (spell) { - if (lua_gettop(L) == 1) { - pushBoolean(L, spell->getNeedLearn()); - } else { - spell->setNeedLearn(getBoolean(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaSpellSelfTarget(lua_State* L) -{ - // spell:isSelfTarget(bool) - Spell* spell = getUserdata(L, 1); - if (spell) { - if (lua_gettop(L) == 1) { - pushBoolean(L, spell->getSelfTarget()); - } else { - spell->setSelfTarget(getBoolean(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaSpellBlocking(lua_State* L) -{ - // spell:isBlocking(blockingSolid, blockingCreature) - Spell* spell = getUserdata(L, 1); - if (spell) { - if (lua_gettop(L) == 1) { - pushBoolean(L, spell->getBlockingSolid()); - pushBoolean(L, spell->getBlockingCreature()); - return 2; - } else { - spell->setBlockingSolid(getBoolean(L, 2)); - spell->setBlockingCreature(getBoolean(L, 3)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaSpellAggressive(lua_State* L) -{ - // spell:isAggressive(bool) - Spell* spell = getUserdata(L, 1); - if (spell) { - if (lua_gettop(L) == 1) { - pushBoolean(L, spell->getAggressive()); - } else { - spell->setAggressive(getBoolean(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaSpellVocation(lua_State* L) -{ - // spell:vocation(vocation) - Spell* spell = getUserdata(L, 1); - if (spell) { - if (lua_gettop(L) == 1) { - lua_createtable(L, 0, 0); - auto it = 0; - for (auto voc : spell->getVocMap()) { - ++it; - std::string s = std::to_string(it); - char const *pchar = s.c_str(); - std::string name = g_vocations.getVocation(voc.first)->getVocName(); - setField(L, pchar, name); - } - setMetatable(L, -1, "Spell"); - } else { - int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc - for (int i = 0; i < parameters; ++i) { - if (getString(L, 2 + i).find(";") != std::string::npos) { - std::vector vocList = explodeString(getString(L, 2 + i), ";"); - int32_t vocationId = g_vocations.getVocationId(vocList[0]); - if (vocList.size() > 0) { - if (vocList[1] == "true") { - spell->addVocMap(vocationId, true); - } else { - spell->addVocMap(vocationId, false); - } - } - } else { - int32_t vocationId = g_vocations.getVocationId(getString(L, 2 + i)); - spell->addVocMap(vocationId, false); - } - } - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -// only for InstantSpells -int LuaScriptInterface::luaSpellWords(lua_State* L) -{ - // spell:words(words[, separator = ""]) - InstantSpell* spell = dynamic_cast(getUserdata(L, 1)); - if (spell) { - // if spell != SPELL_INSTANT, it means that this actually is no InstantSpell, so we return nil - if (spell->spellType != SPELL_INSTANT) { - lua_pushnil(L); - return 1; - } - - if (lua_gettop(L) == 1) { - pushString(L, spell->getWords()); - pushString(L, spell->getSeparator()); - return 2; - } else { - std::string sep = ""; - if (lua_gettop(L) == 3) { - sep = getString(L, 3); - } - spell->setWords(getString(L, 2)); - spell->setSeparator(sep); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -// only for InstantSpells -int LuaScriptInterface::luaSpellNeedDirection(lua_State* L) -{ - // spell:needDirection(bool) - InstantSpell* spell = dynamic_cast(getUserdata(L, 1)); - if (spell) { - // if spell != SPELL_INSTANT, it means that this actually is no InstantSpell, so we return nil - if (spell->spellType != SPELL_INSTANT) { - lua_pushnil(L); - return 1; - } - - if (lua_gettop(L) == 1) { - pushBoolean(L, spell->getNeedDirection()); - } else { - spell->setNeedDirection(getBoolean(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -// only for InstantSpells -int LuaScriptInterface::luaSpellHasParams(lua_State* L) -{ - // spell:hasParams(bool) - InstantSpell* spell = dynamic_cast(getUserdata(L, 1)); - if (spell) { - // if spell != SPELL_INSTANT, it means that this actually is no InstantSpell, so we return nil - if (spell->spellType != SPELL_INSTANT) { - lua_pushnil(L); - return 1; - } - - if (lua_gettop(L) == 1) { - pushBoolean(L, spell->getHasParam()); - } else { - spell->setHasParam(getBoolean(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -// only for InstantSpells -int LuaScriptInterface::luaSpellHasPlayerNameParam(lua_State* L) -{ - // spell:hasPlayerNameParam(bool) - InstantSpell* spell = dynamic_cast(getUserdata(L, 1)); - if (spell) { - // if spell != SPELL_INSTANT, it means that this actually is no InstantSpell, so we return nil - if (spell->spellType != SPELL_INSTANT) { - lua_pushnil(L); - return 1; - } - - if (lua_gettop(L) == 1) { - pushBoolean(L, spell->getHasPlayerNameParam()); - } else { - spell->setHasPlayerNameParam(getBoolean(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -// only for InstantSpells -int LuaScriptInterface::luaSpellNeedCasterTargetOrDirection(lua_State* L) -{ - // spell:needCasterTargetOrDirection(bool) - InstantSpell* spell = dynamic_cast(getUserdata(L, 1)); - if (spell) { - // if spell != SPELL_INSTANT, it means that this actually is no InstantSpell, so we return nil - if (spell->spellType != SPELL_INSTANT) { - lua_pushnil(L); - return 1; - } - - if (lua_gettop(L) == 1) { - pushBoolean(L, spell->getNeedCasterTargetOrDirection()); - } else { - spell->setNeedCasterTargetOrDirection(getBoolean(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -// only for InstantSpells -int LuaScriptInterface::luaSpellIsBlockingWalls(lua_State* L) -{ - // spell:blockWalls(bool) - InstantSpell* spell = dynamic_cast(getUserdata(L, 1)); - if (spell) { - // if spell != SPELL_INSTANT, it means that this actually is no InstantSpell, so we return nil - if (spell->spellType != SPELL_INSTANT) { - lua_pushnil(L); - return 1; - } - - if (lua_gettop(L) == 1) { - pushBoolean(L, spell->getBlockWalls()); - } else { - spell->setBlockWalls(getBoolean(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -// only for RuneSpells -int LuaScriptInterface::luaSpellRuneId(lua_State* L) -{ - // spell:runeId(id) - RuneSpell* spell = dynamic_cast(getUserdata(L, 1)); - if (spell) { - // if spell != SPELL_RUNE, it means that this actually is no RuneSpell, so we return nil - if (spell->spellType != SPELL_RUNE) { - lua_pushnil(L); - return 1; - } - - if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getRuneItemId()); - } else { - spell->setRuneItemId(getNumber(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -// only for RuneSpells -int LuaScriptInterface::luaSpellCharges(lua_State* L) -{ - // spell:charges(charges) - RuneSpell* spell = dynamic_cast(getUserdata(L, 1)); - if (spell) { - // if spell != SPELL_RUNE, it means that this actually is no RuneSpell, so we return nil - if (spell->spellType != SPELL_RUNE) { - lua_pushnil(L); - return 1; - } - - if (lua_gettop(L) == 1) { - lua_pushnumber(L, spell->getCharges()); - } else { - spell->setCharges(getNumber(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -// only for RuneSpells -int LuaScriptInterface::luaSpellAllowFarUse(lua_State* L) -{ - // spell:allowFarUse(bool) - RuneSpell* spell = dynamic_cast(getUserdata(L, 1)); - if (spell) { - // if spell != SPELL_RUNE, it means that this actually is no RuneSpell, so we return nil - if (spell->spellType != SPELL_RUNE) { - lua_pushnil(L); - return 1; - } - - if (lua_gettop(L) == 1) { - pushBoolean(L, spell->getAllowFarUse()); - } else { - spell->setAllowFarUse(getBoolean(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -// only for RuneSpells -int LuaScriptInterface::luaSpellBlockWalls(lua_State* L) -{ - // spell:blockWalls(bool) - RuneSpell* spell = dynamic_cast(getUserdata(L, 1)); - if (spell) { - // if spell != SPELL_RUNE, it means that this actually is no RuneSpell, so we return nil - if (spell->spellType != SPELL_RUNE) { - lua_pushnil(L); - return 1; - } - - if (lua_gettop(L) == 1) { - pushBoolean(L, spell->getCheckLineOfSight()); - } else { - spell->setCheckLineOfSight(getBoolean(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -// only for RuneSpells -int LuaScriptInterface::luaSpellCheckFloor(lua_State* L) -{ - // spell:checkFloor(bool) - RuneSpell* spell = dynamic_cast(getUserdata(L, 1)); - if (spell) { - // if spell != SPELL_RUNE, it means that this actually is no RuneSpell, so we return nil - if (spell->spellType != SPELL_RUNE) { - lua_pushnil(L); - return 1; - } - - if (lua_gettop(L) == 1) { - pushBoolean(L, spell->getCheckFloor()); - } else { - spell->setCheckFloor(getBoolean(L, 2)); - pushBoolean(L, true); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaCreateAction(lua_State* L) -{ - // Action() - if (getScriptEnv()->getScriptInterface() != &g_scripts->getScriptInterface()) { - reportErrorFunc("Actions can only be registered in the Scripts interface."); - lua_pushnil(L); - return 1; - } - - Action* action = new Action(getScriptEnv()->getScriptInterface()); - if (action) { - action->fromLua = true; - pushUserdata(L, action); - setMetatable(L, -1, "Action"); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaActionOnUse(lua_State* L) -{ - // action:onUse(callback) - Action* action = getUserdata(L, 1); - if (action) { - if (!action->loadCallback()) { - pushBoolean(L, false); - return 1; - } - action->scripted = true; - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaActionRegister(lua_State* L) -{ - // action:register() - Action* action = getUserdata(L, 1); - if (action) { - if (!action->isScripted()) { - pushBoolean(L, false); - return 1; - } - pushBoolean(L, g_actions->registerLuaEvent(action)); - action->getActionIdRange().clear(); - action->getItemIdRange().clear(); - action->getUniqueIdRange().clear(); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaActionItemId(lua_State* L) -{ - // action:id(ids) - Action* action = getUserdata(L, 1); - if (action) { - int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc - if (parameters > 1) { - for (int i = 0; i < parameters; ++i) { - action->addItemId(getNumber(L, 2 + i)); - } - } else { - action->addItemId(getNumber(L, 2)); - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaActionActionId(lua_State* L) -{ - // action:aid(aids) - Action* action = getUserdata(L, 1); - if (action) { - int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc - if (parameters > 1) { - for (int i = 0; i < parameters; ++i) { - action->addActionId(getNumber(L, 2 + i)); - } - } else { - action->addActionId(getNumber(L, 2)); - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaActionUniqueId(lua_State* L) -{ - // action:uid(uids) - Action* action = getUserdata(L, 1); - if (action) { - int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc - if (parameters > 1) { - for (int i = 0; i < parameters; ++i) { - action->addUniqueId(getNumber(L, 2 + i)); - } - } else { - action->addUniqueId(getNumber(L, 2)); - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaActionAllowFarUse(lua_State* L) -{ - // action:allowFarUse(bool) - Action* action = getUserdata(L, 1); - if (action) { - action->setAllowFarUse(getBoolean(L, 2)); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaActionBlockWalls(lua_State* L) -{ - // action:blockWalls(bool) - Action* action = getUserdata(L, 1); - if (action) { - action->setCheckLineOfSight(getBoolean(L, 2)); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaActionCheckFloor(lua_State* L) -{ - // action:checkFloor(bool) - Action* action = getUserdata(L, 1); - if (action) { - action->setCheckFloor(getBoolean(L, 2)); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaCreateTalkaction(lua_State* L) -{ - // TalkAction(words) - if (getScriptEnv()->getScriptInterface() != &g_scripts->getScriptInterface()) { - reportErrorFunc("TalkActions can only be registered in the Scripts interface."); - lua_pushnil(L); - return 1; - } - - TalkAction* talk = new TalkAction(getScriptEnv()->getScriptInterface()); - if (talk) { - talk->setWords(getString(L, 2)); - talk->fromLua = true; - pushUserdata(L, talk); - setMetatable(L, -1, "TalkAction"); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaTalkactionOnSay(lua_State* L) -{ - // talkAction:onSay(callback) - TalkAction* talk = getUserdata(L, 1); - if (talk) { - if (!talk->loadCallback()) { - pushBoolean(L, false); - return 1; - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaTalkactionRegister(lua_State* L) -{ - // talkAction:register() - TalkAction* talk = getUserdata(L, 1); - if (talk) { - if (!talk->isScripted()) { - pushBoolean(L, false); - return 1; - } - pushBoolean(L, g_talkActions->registerLuaEvent(talk)); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaTalkactionSeparator(lua_State* L) -{ - // talkAction:separator(sep) - TalkAction* talk = getUserdata(L, 1); - if (talk) { - talk->setSeparator(getString(L, 2).c_str()); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaCreateCreatureEvent(lua_State* L) -{ - // CreatureEvent(eventName) - if (getScriptEnv()->getScriptInterface() != &g_scripts->getScriptInterface()) { - reportErrorFunc("CreatureEvents can only be registered in the Scripts interface."); - lua_pushnil(L); - return 1; - } - - CreatureEvent* creature = new CreatureEvent(getScriptEnv()->getScriptInterface()); - if (creature) { - creature->setName(getString(L, 2)); - creature->fromLua = true; - pushUserdata(L, creature); - setMetatable(L, -1, "CreatureEvent"); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaCreatureEventType(lua_State* L) -{ - // creatureevent:type(callback) - CreatureEvent* creature = getUserdata(L, 1); - if (creature) { - std::string typeName = getString(L, 2); - std::string tmpStr = asLowerCaseString(typeName); - if (tmpStr == "login") { - creature->setEventType(CREATURE_EVENT_LOGIN); - } else if (tmpStr == "logout") { - creature->setEventType(CREATURE_EVENT_LOGOUT); - } else if (tmpStr == "think") { - creature->setEventType(CREATURE_EVENT_THINK); - } else if (tmpStr == "preparedeath") { - creature->setEventType(CREATURE_EVENT_PREPAREDEATH); - } else if (tmpStr == "death") { - creature->setEventType(CREATURE_EVENT_DEATH); - } else if (tmpStr == "kill") { - creature->setEventType(CREATURE_EVENT_KILL); - } else if (tmpStr == "advance") { - creature->setEventType(CREATURE_EVENT_ADVANCE); - } else if (tmpStr == "modalwindow") { - creature->setEventType(CREATURE_EVENT_MODALWINDOW); - } else if (tmpStr == "textedit") { - creature->setEventType(CREATURE_EVENT_TEXTEDIT); - } else if (tmpStr == "healthchange") { - creature->setEventType(CREATURE_EVENT_HEALTHCHANGE); - } else if (tmpStr == "manachange") { - creature->setEventType(CREATURE_EVENT_MANACHANGE); - } else if (tmpStr == "extendedopcode") { - creature->setEventType(CREATURE_EVENT_EXTENDED_OPCODE); - } else { - std::cout << "[Error - CreatureEvent::configureLuaEvent] Invalid type for creature event: " << typeName << std::endl; - pushBoolean(L, false); - } - creature->setLoaded(true); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaCreatureEventRegister(lua_State* L) -{ - // creatureevent:register() - CreatureEvent* creature = getUserdata(L, 1); - if (creature) { - if (!creature->isScripted()) { - pushBoolean(L, false); - return 1; - } - pushBoolean(L, g_creatureEvents->registerLuaEvent(creature)); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaCreatureEventOnCallback(lua_State* L) -{ - // creatureevent:onLogin / logout / etc. (callback) - CreatureEvent* creature = getUserdata(L, 1); - if (creature) { - if (!creature->loadCallback()) { - pushBoolean(L, false); - return 1; - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaCreateMoveEvent(lua_State* L) -{ - // MoveEvent() - if (getScriptEnv()->getScriptInterface() != &g_scripts->getScriptInterface()) { - reportErrorFunc("MoveEvents can only be registered in the Scripts interface."); - lua_pushnil(L); - return 1; - } - - MoveEvent* moveevent = new MoveEvent(getScriptEnv()->getScriptInterface()); - if (moveevent) { - moveevent->fromLua = true; - pushUserdata(L, moveevent); - setMetatable(L, -1, "MoveEvent"); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMoveEventType(lua_State* L) -{ - // moveevent:type(callback) - MoveEvent* moveevent = getUserdata(L, 1); - if (moveevent) { - std::string typeName = getString(L, 2); - std::string tmpStr = asLowerCaseString(typeName); - if (tmpStr == "stepin") { - moveevent->setEventType(MOVE_EVENT_STEP_IN); - moveevent->stepFunction = moveevent->StepInField; - } else if (tmpStr == "stepout") { - moveevent->setEventType(MOVE_EVENT_STEP_OUT); - moveevent->stepFunction = moveevent->StepOutField; - } else if (tmpStr == "equip") { - moveevent->setEventType(MOVE_EVENT_EQUIP); - moveevent->equipFunction = moveevent->EquipItem; - } else if (tmpStr == "deequip") { - moveevent->setEventType(MOVE_EVENT_DEEQUIP); - moveevent->equipFunction = moveevent->DeEquipItem; - } else if (tmpStr == "additem") { - moveevent->setEventType(MOVE_EVENT_ADD_ITEM_ITEMTILE); - moveevent->moveFunction = moveevent->AddItemField; - } else if (tmpStr == "removeitem") { - moveevent->setEventType(MOVE_EVENT_REMOVE_ITEM_ITEMTILE); - moveevent->moveFunction = moveevent->RemoveItemField; - } else { - std::cout << "Error: [MoveEvent::configureMoveEvent] No valid event name " << typeName << std::endl; - pushBoolean(L, false); - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMoveEventRegister(lua_State* L) -{ - // moveevent:register() - MoveEvent* moveevent = getUserdata(L, 1); - if (moveevent) { - if (!moveevent->isScripted()) { - pushBoolean(L, g_moveEvents->registerLuaFunction(moveevent)); - return 1; - } - pushBoolean(L, g_moveEvents->registerLuaEvent(moveevent)); - moveevent->getItemIdRange().clear(); - moveevent->getActionIdRange().clear(); - moveevent->getUniqueIdRange().clear(); - moveevent->getPosList().clear(); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMoveEventOnCallback(lua_State* L) -{ - // moveevent:onEquip / deEquip / etc. (callback) - MoveEvent* moveevent = getUserdata(L, 1); - if (moveevent) { - if (!moveevent->loadCallback()) { - pushBoolean(L, false); - return 1; - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMoveEventSlot(lua_State* L) -{ - // moveevent:slot(slot) - MoveEvent* moveevent = getUserdata(L, 1); - if (moveevent) { - if (moveevent->getEventType() == MOVE_EVENT_EQUIP || moveevent->getEventType() == MOVE_EVENT_DEEQUIP) { - if (!moveevent->getSlotName().empty()) { - std::string slotName = getString(L, 2); - std::string tmpStr = asLowerCaseString(slotName); - tmpStr = asLowerCaseString(moveevent->getSlotName()); - if (tmpStr == "head") { - moveevent->setSlot(SLOTP_HEAD); - } else if (tmpStr == "necklace") { - moveevent->setSlot(SLOTP_NECKLACE); - } else if (tmpStr == "backpack") { - moveevent->setSlot(SLOTP_BACKPACK); - } else if (tmpStr == "armor" || tmpStr == "body") { - moveevent->setSlot(SLOTP_ARMOR); - } else if (tmpStr == "right-hand") { - moveevent->setSlot(SLOTP_RIGHT); - } else if (tmpStr == "left-hand") { - moveevent->setSlot(SLOTP_LEFT); - } else if (tmpStr == "hand" || tmpStr == "shield") { - moveevent->setSlot(SLOTP_RIGHT | SLOTP_LEFT); - } else if (tmpStr == "legs") { - moveevent->setSlot(SLOTP_LEGS); - } else if (tmpStr == "feet") { - moveevent->setSlot(SLOTP_FEET); - } else if (tmpStr == "ring") { - moveevent->setSlot(SLOTP_RING); - } else if (tmpStr == "ammo") { - moveevent->setSlot(SLOTP_AMMO); - } else { - std::cout << "[Warning - MoveEvent::configureMoveEvent] Unknown slot type: " << moveevent->getSlotName() << std::endl; - } - } - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMoveEventLevel(lua_State* L) -{ - // moveevent:level(lvl) - MoveEvent* moveevent = getUserdata(L, 1); - if (moveevent) { - moveevent->setRequiredLevel(getNumber(L, 2)); - moveevent->setWieldInfo(WIELDINFO_LEVEL); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMoveEventMagLevel(lua_State* L) -{ - // moveevent:magicLevel(lvl) - MoveEvent* moveevent = getUserdata(L, 1); - if (moveevent) { - moveevent->setRequiredMagLevel(getNumber(L, 2)); - moveevent->setWieldInfo(WIELDINFO_MAGLV); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMoveEventPremium(lua_State* L) -{ - // moveevent:premium(bool) - MoveEvent* moveevent = getUserdata(L, 1); - if (moveevent) { - moveevent->setNeedPremium(getBoolean(L, 2)); - moveevent->setWieldInfo(WIELDINFO_PREMIUM); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMoveEventVocation(lua_State* L) -{ - // moveevent:vocation(vocName[, showInDescription = false, lastVoc = false]) - MoveEvent* moveevent = getUserdata(L, 1); - if (moveevent) { - moveevent->addVocEquipMap(getString(L, 2)); - moveevent->setWieldInfo(WIELDINFO_VOCREQ); - std::string tmp; - bool showInDescription = false; - bool lastVoc = false; - if (getBoolean(L, 3)) { - showInDescription = getBoolean(L, 3); - } - if (getBoolean(L, 4)) { - lastVoc = getBoolean(L, 4); - } - if (showInDescription) { - if (moveevent->getVocationString().empty()) { - tmp = asLowerCaseString(getString(L, 2)); - tmp += "s"; - moveevent->setVocationString(tmp); - } else { - tmp = moveevent->getVocationString(); - if (lastVoc) { - tmp += " and "; - } else { - tmp += ", "; - } - tmp += asLowerCaseString(getString(L, 2)); - tmp += "s"; - moveevent->setVocationString(tmp); - } - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMoveEventItemId(lua_State* L) -{ - // moveevent:id(ids) - MoveEvent* moveevent = getUserdata(L, 1); - if (moveevent) { - int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc - if (parameters > 1) { - for (int i = 0; i < parameters; ++i) { - moveevent->addItemId(getNumber(L, 2 + i)); - } - } else { - moveevent->addItemId(getNumber(L, 2)); - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMoveEventActionId(lua_State* L) -{ - // moveevent:aid(ids) - MoveEvent* moveevent = getUserdata(L, 1); - if (moveevent) { - int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc - if (parameters > 1) { - for (int i = 0; i < parameters; ++i) { - moveevent->addActionId(getNumber(L, 2 + i)); - } - } else { - moveevent->addActionId(getNumber(L, 2)); - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMoveEventUniqueId(lua_State* L) -{ - // moveevent:uid(ids) - MoveEvent* moveevent = getUserdata(L, 1); - if (moveevent) { - int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc - if (parameters > 1) { - for (int i = 0; i < parameters; ++i) { - moveevent->addUniqueId(getNumber(L, 2 + i)); - } - } else { - moveevent->addUniqueId(getNumber(L, 2)); - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaMoveEventPosition(lua_State* L) -{ - // moveevent:position(positions) - MoveEvent* moveevent = getUserdata(L, 1); - if (moveevent) { - int parameters = lua_gettop(L) - 1; // - 1 because self is a parameter aswell, which we want to skip ofc - if (parameters > 1) { - for (int i = 0; i < parameters; ++i) { - moveevent->addPosList(getPosition(L, 2 + i)); - } - } else { - moveevent->addPosList(getPosition(L, 2)); - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaCreateGlobalEvent(lua_State* L) -{ - // GlobalEvent(eventName) - if (getScriptEnv()->getScriptInterface() != &g_scripts->getScriptInterface()) { - reportErrorFunc("GlobalEvents can only be registered in the Scripts interface."); - lua_pushnil(L); - return 1; - } - - GlobalEvent* global = new GlobalEvent(getScriptEnv()->getScriptInterface()); - if (global) { - global->setName(getString(L, 2)); - global->setEventType(GLOBALEVENT_NONE); - global->fromLua = true; - pushUserdata(L, global); - setMetatable(L, -1, "GlobalEvent"); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaGlobalEventType(lua_State* L) -{ - // globalevent:type(callback) - GlobalEvent* global = getUserdata(L, 1); - if (global) { - std::string typeName = getString(L, 2); - std::string tmpStr = asLowerCaseString(typeName); - if (tmpStr == "startup") { - global->setEventType(GLOBALEVENT_STARTUP); - } else if (tmpStr == "shutdown") { - global->setEventType(GLOBALEVENT_SHUTDOWN); - } else if (tmpStr == "record") { - global->setEventType(GLOBALEVENT_RECORD); - } else { - std::cout << "[Error - CreatureEvent::configureLuaEvent] Invalid type for global event: " << typeName << std::endl; - pushBoolean(L, false); - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaGlobalEventRegister(lua_State* L) -{ - // globalevent:register() - GlobalEvent* globalevent = getUserdata(L, 1); - if (globalevent) { - if (!globalevent->isScripted()) { - pushBoolean(L, false); - return 1; - } - pushBoolean(L, g_globalEvents->registerLuaEvent(globalevent)); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaGlobalEventOnCallback(lua_State* L) -{ - // globalevent:onThink / record / etc. (callback) - GlobalEvent* globalevent = getUserdata(L, 1); - if (globalevent) { - if (!globalevent->loadCallback()) { - pushBoolean(L, false); - return 1; - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaGlobalEventTime(lua_State* L) -{ - // globalevent:time(time) - GlobalEvent* globalevent = getUserdata(L, 1); - if (globalevent) { - std::string timer = getString(L, 2); - std::vector params = vectorAtoi(explodeString(timer, ":")); - - int32_t hour = params.front(); - if (hour < 0 || hour > 23) { - std::cout << "[Error - GlobalEvent::configureEvent] Invalid hour \"" << timer << "\" for globalevent with name: " << globalevent->getName() << std::endl; - pushBoolean(L, false); - return 1; - } - - globalevent->setInterval(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 \"" << timer << "\" for globalevent with name: " << globalevent->getName() << std::endl; - pushBoolean(L, false); - return 1; - } - - if (params.size() > 2) { - sec = params[2]; - if (sec < 0 || sec > 59) { - std::cout << "[Error - GlobalEvent::configureEvent] Invalid second \"" << timer << "\" for globalevent with name: " << globalevent->getName() << std::endl; - pushBoolean(L, false); - return 1; - } - } - } - - time_t current_time = time(nullptr); - tm* timeinfo = localtime(¤t_time); - timeinfo->tm_hour = hour; - timeinfo->tm_min = min; - timeinfo->tm_sec = sec; - - time_t difference = static_cast(difftime(mktime(timeinfo), current_time)); - if (difference < 0) { - difference += 86400; - } - - globalevent->setNextExecution(current_time + difference); - globalevent->setEventType(GLOBALEVENT_TIMER); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaGlobalEventInterval(lua_State* L) -{ - // globalevent:interval(interval) - GlobalEvent* globalevent = getUserdata(L, 1); - if (globalevent) { - globalevent->setInterval(getNumber(L, 2)); - globalevent->setNextExecution(OTSYS_TIME() + getNumber(L, 2)); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -// Weapon -int LuaScriptInterface::luaCreateWeapon(lua_State* L) -{ - // Weapon(type) - if (getScriptEnv()->getScriptInterface() != &g_scripts->getScriptInterface()) { - reportErrorFunc("Weapons can only be registered in the Scripts interface."); - lua_pushnil(L); - return 1; - } - - WeaponType_t type = getNumber(L, 2); - switch (type) { - case WEAPON_SWORD: - case WEAPON_AXE: - case WEAPON_CLUB: { - WeaponMelee* weapon = new WeaponMelee(getScriptEnv()->getScriptInterface()); - if (weapon) { - pushUserdata(L, weapon); - setMetatable(L, -1, "Weapon"); - weapon->weaponType = type; - } else { - lua_pushnil(L); - } - break; - } - case WEAPON_DISTANCE: - case WEAPON_AMMO: { - WeaponDistance* weapon = new WeaponDistance(getScriptEnv()->getScriptInterface()); - if (weapon) { - pushUserdata(L, weapon); - setMetatable(L, -1, "Weapon"); - weapon->weaponType = type; - } else { - lua_pushnil(L); - } - break; - } - case WEAPON_WAND: { - WeaponWand* weapon = new WeaponWand(getScriptEnv()->getScriptInterface()); - if (weapon) { - pushUserdata(L, weapon); - setMetatable(L, -1, "Weapon"); - weapon->weaponType = type; - } else { - lua_pushnil(L); - } - break; - } - default: { - lua_pushnil(L); - break; - } - } - return 1; -} - -int LuaScriptInterface::luaWeaponAction(lua_State* L) -{ - // weapon:action(callback) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - std::string typeName = getString(L, 2); - std::string tmpStr = asLowerCaseString(typeName); - if (tmpStr == "removecount") { - weapon->action = WEAPONACTION_REMOVECOUNT; - } else if (tmpStr == "removecharge") { - weapon->action = WEAPONACTION_REMOVECHARGE; - } else if (tmpStr == "move") { - weapon->action = WEAPONACTION_MOVE; - } else { - std::cout << "Error: [Weapon::action] No valid action " << typeName << std::endl; - pushBoolean(L, false); - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponRegister(lua_State* L) -{ - // weapon:register() - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - if (weapon->weaponType == WEAPON_DISTANCE || weapon->weaponType == WEAPON_AMMO) { - weapon = getUserdata(L, 1); - } else if (weapon->weaponType == WEAPON_WAND) { - weapon = getUserdata(L, 1); - } else { - weapon = getUserdata(L, 1); - } - - uint16_t id = weapon->getID(); - ItemType& it = Item::items.getItemType(id); - it.weaponType = weapon->weaponType; - - if (weapon->getWieldInfo() != 0) { - it.wieldInfo = weapon->getWieldInfo(); - it.vocationString = weapon->getVocationString(); - it.minReqLevel = weapon->getReqLevel(); - it.minReqMagicLevel = weapon->getReqMagLv(); - } - - weapon->configureWeapon(it); - pushBoolean(L, g_weapons->registerLuaEvent(weapon)); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponOnUseWeapon(lua_State* L) -{ - // weapon:onUseWeapon(callback) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - if (!weapon->loadCallback()) { - pushBoolean(L, false); - return 1; - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponUnproperly(lua_State* L) -{ - // weapon:wieldedUnproperly(bool) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - weapon->setWieldUnproperly(getBoolean(L, 2)); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponLevel(lua_State* L) -{ - // weapon:level(lvl) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - weapon->setRequiredLevel(getNumber(L, 2)); - weapon->setWieldInfo(WIELDINFO_LEVEL); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponMagicLevel(lua_State* L) -{ - // weapon:magicLevel(lvl) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - weapon->setRequiredMagLevel(getNumber(L, 2)); - weapon->setWieldInfo(WIELDINFO_MAGLV); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponMana(lua_State* L) -{ - // weapon:mana(mana) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - weapon->setMana(getNumber(L, 2)); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponManaPercent(lua_State* L) -{ - // weapon:manaPercent(percent) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - weapon->setManaPercent(getNumber(L, 2)); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponHealth(lua_State* L) -{ - // weapon:health(health) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - weapon->setHealth(getNumber(L, 2)); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponHealthPercent(lua_State* L) -{ - // weapon:healthPercent(percent) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - weapon->setHealthPercent(getNumber(L, 2)); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponSoul(lua_State* L) -{ - // weapon:soul(soul) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - weapon->setSoul(getNumber(L, 2)); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponBreakChance(lua_State* L) -{ - // weapon:breakChance(percent) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - weapon->setBreakChance(getNumber(L, 2)); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponWandDamage(lua_State* L) -{ - // weapon:damage(damage[min, max]) only use this if the weapon is a wand! - WeaponWand* weapon = getUserdata(L, 1); - if (weapon) { - weapon->setMinChange(getNumber(L, 2)); - if (lua_gettop(L) > 2) { - weapon->setMaxChange(getNumber(L, 3)); - } else { - weapon->setMaxChange(getNumber(L, 2)); - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponElement(lua_State* L) -{ - // weapon:element(combatType) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - if (!getNumber(L, 2)) { - std::string element = getString(L, 2); - std::string tmpStrValue = asLowerCaseString(element); - if (tmpStrValue == "earth") { - weapon->params.combatType = COMBAT_EARTHDAMAGE; - } else if (tmpStrValue == "ice") { - weapon->params.combatType = COMBAT_ICEDAMAGE; - } else if (tmpStrValue == "energy") { - weapon->params.combatType = COMBAT_ENERGYDAMAGE; - } else if (tmpStrValue == "fire") { - weapon->params.combatType = COMBAT_FIREDAMAGE; - } else if (tmpStrValue == "death") { - weapon->params.combatType = COMBAT_DEATHDAMAGE; - } else if (tmpStrValue == "holy") { - weapon->params.combatType = COMBAT_HOLYDAMAGE; - } else { - std::cout << "[Warning - weapon:element] Type \"" << element << "\" does not exist." << std::endl; - } - } else { - weapon->params.combatType = getNumber(L, 2); - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponPremium(lua_State* L) -{ - // weapon:premium(bool) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - weapon->setNeedPremium(getBoolean(L, 2)); - weapon->setWieldInfo(WIELDINFO_PREMIUM); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponVocation(lua_State* L) -{ - // weapon:vocation(vocName[, showInDescription = false, lastVoc = false]) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - weapon->addVocWeaponMap(getString(L, 2)); - weapon->setWieldInfo(WIELDINFO_VOCREQ); - std::string tmp; - bool showInDescription = getBoolean(L, 3, false); - bool lastVoc = getBoolean(L, 4, false); - - if (showInDescription) { - if (weapon->getVocationString().empty()) { - tmp = asLowerCaseString(getString(L, 2)); - tmp += "s"; - weapon->setVocationString(tmp); - } else { - tmp = weapon->getVocationString(); - if (lastVoc) { - tmp += " and "; - } else { - tmp += ", "; - } - tmp += asLowerCaseString(getString(L, 2)); - tmp += "s"; - weapon->setVocationString(tmp); - } - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponId(lua_State* L) -{ - // weapon:id(id) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - weapon->setID(getNumber(L, 2)); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponAttack(lua_State* L) -{ - // weapon:attack(atk) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - uint16_t id = weapon->getID(); - ItemType& it = Item::items.getItemType(id); - it.attack = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponDefense(lua_State* L) -{ - // weapon:defense(defense[, extraDefense]) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - uint16_t id = weapon->getID(); - ItemType& it = Item::items.getItemType(id); - it.defense = getNumber(L, 2); - if (lua_gettop(L) > 2) { - it.extraDefense = getNumber(L, 3); - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponRange(lua_State* L) -{ - // weapon:range(range) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - uint16_t id = weapon->getID(); - ItemType& it = Item::items.getItemType(id); - it.shootRange = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponCharges(lua_State* L) -{ - // weapon:charges(charges[, showCharges = true]) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - bool showCharges = getBoolean(L, 3, true); - uint16_t id = weapon->getID(); - ItemType& it = Item::items.getItemType(id); - - it.charges = getNumber(L, 2); - it.showCharges = showCharges; - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponDuration(lua_State* L) -{ - // weapon:duration(duration[, showDuration = true]) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - bool showDuration = getBoolean(L, 3, true); - uint16_t id = weapon->getID(); - ItemType& it = Item::items.getItemType(id); - - it.decayTime = getNumber(L, 2); - it.showDuration = showDuration; - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponDecayTo(lua_State* L) -{ - // weapon:decayTo([itemid = 0] - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - uint16_t itemid = getNumber(L, 2, 0); - uint16_t id = weapon->getID(); - ItemType& it = Item::items.getItemType(id); - - it.decayTo = itemid; - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponTransformEquipTo(lua_State* L) -{ - // weapon:transformEquipTo(itemid) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - uint16_t id = weapon->getID(); - ItemType& it = Item::items.getItemType(id); - it.transformEquipTo = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponTransformDeEquipTo(lua_State* L) -{ - // weapon:transformDeEquipTo(itemid) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - uint16_t id = weapon->getID(); - ItemType& it = Item::items.getItemType(id); - it.transformDeEquipTo = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponShootType(lua_State* L) -{ - // weapon:shootType(type) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - uint16_t id = weapon->getID(); - ItemType& it = Item::items.getItemType(id); - it.shootType = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponSlotType(lua_State* L) -{ - // weapon:slotType(slot) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - uint16_t id = weapon->getID(); - ItemType& it = Item::items.getItemType(id); - std::string slot = getString(L, 2); - - if (slot == "two-handed") { - it.slotPosition |= SLOTP_TWO_HAND; - } else { - it.slotPosition |= SLOTP_HAND; - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponAmmoType(lua_State* L) -{ - // weapon:ammoType(type) - WeaponDistance* weapon = getUserdata(L, 1); - if (weapon) { - uint16_t id = weapon->getID(); - ItemType& it = Item::items.getItemType(id); - std::string type = getString(L, 2); - - if (type == "arrow") { - it.ammoType = AMMO_ARROW; - } else if (type == "bolt"){ - it.ammoType = AMMO_BOLT; - } else { - std::cout << "[Warning - weapon:ammoType] Type \"" << type << "\" does not exist." << std::endl; - lua_pushnil(L); - return 1; - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponHitChance(lua_State* L) -{ - // weapon:hitChance(chance) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - uint16_t id = weapon->getID(); - ItemType& it = Item::items.getItemType(id); - it.hitChance = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponMaxHitChance(lua_State* L) -{ - // weapon:maxHitChance(max) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - uint16_t id = weapon->getID(); - ItemType& it = Item::items.getItemType(id); - it.maxHitChance = getNumber(L, 2); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaWeaponExtraElement(lua_State* L) -{ - // weapon:extraElement(atk, combatType) - Weapon* weapon = getUserdata(L, 1); - if (weapon) { - uint16_t id = weapon->getID(); - ItemType& it = Item::items.getItemType(id); - it.abilities.get()->elementDamage = getNumber(L, 2); - - if (!getNumber(L, 3)) { - std::string element = getString(L, 3); - std::string tmpStrValue = asLowerCaseString(element); - if (tmpStrValue == "earth") { - it.abilities.get()->elementType = COMBAT_EARTHDAMAGE; - } else if (tmpStrValue == "ice") { - it.abilities.get()->elementType = COMBAT_ICEDAMAGE; - } else if (tmpStrValue == "energy") { - it.abilities.get()->elementType = COMBAT_ENERGYDAMAGE; - } else if (tmpStrValue == "fire") { - it.abilities.get()->elementType = COMBAT_FIREDAMAGE; - } else if (tmpStrValue == "death") { - it.abilities.get()->elementType = COMBAT_DEATHDAMAGE; - } else if (tmpStrValue == "holy") { - it.abilities.get()->elementType = COMBAT_HOLYDAMAGE; - } else { - std::cout << "[Warning - weapon:extraElement] Type \"" << element << "\" does not exist." << std::endl; - } - } else { - it.abilities.get()->elementType = getNumber(L, 3); - } - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - // LuaEnvironment::LuaEnvironment() : LuaScriptInterface("Main Interface") {} diff --git a/src/luascript.h b/src/luascript.h index b2644cb..8fd8a1e 100644 --- a/src/luascript.h +++ b/src/luascript.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,11 +20,7 @@ #ifndef FS_LUASCRIPT_H_5344B2BC907E46E3943EA78574A212D8 #define FS_LUASCRIPT_H_5344B2BC907E46E3943EA78574A212D8 -#if __has_include("luajit/lua.hpp") #include -#else -#include -#endif #if LUA_VERSION_NUM >= 502 #ifndef LUA_COMPAT_ALL @@ -39,7 +35,6 @@ #include "database.h" #include "enums.h" #include "position.h" -#include class Thing; class Creature; @@ -51,7 +46,6 @@ class Combat; class Condition; class Npc; class Monster; -class InstantSpell; enum { EVENT_ID_LOADING = 1, @@ -99,8 +93,7 @@ struct LuaTimerEventDesc { class LuaScriptInterface; class Cylinder; class Game; - -struct LootBlock; +class Npc; class ScriptEnvironment { @@ -155,9 +148,9 @@ class ScriptEnvironment void removeItemByUID(uint32_t uid); private: - using VariantVector = std::vector; - using StorageMap = std::map; - using DBResultMap = std::map; + typedef std::vector VariantVector; + typedef std::map StorageMap; + typedef std::map DBResultMap; LuaScriptInterface* interface; @@ -216,7 +209,6 @@ class LuaScriptInterface const std::string& getFileById(int32_t scriptId); int32_t getEvent(const std::string& eventName); - int32_t getEvent(); int32_t getMetaEvent(const std::string& globalName, const std::string& eventName); static ScriptEnvironment* getScriptEnv() { @@ -279,13 +271,13 @@ class LuaScriptInterface // Get template - static typename std::enable_if::value, T>::type + inline static typename std::enable_if::value, T>::type getNumber(lua_State* L, int32_t arg) { return static_cast(static_cast(lua_tonumber(L, arg))); } template - static typename std::enable_if::value || std::is_floating_point::value, T>::type + inline static typename std::enable_if::value || std::is_floating_point::value, T>::type getNumber(lua_State* L, int32_t arg) { return static_cast(lua_tonumber(L, arg)); @@ -309,16 +301,16 @@ class LuaScriptInterface return *userdata; } template - static T** getRawUserdata(lua_State* L, int32_t arg) + inline static T** getRawUserdata(lua_State* L, int32_t arg) { return static_cast(lua_touserdata(L, arg)); } - static bool getBoolean(lua_State* L, int32_t arg) + inline static bool getBoolean(lua_State* L, int32_t arg) { return lua_toboolean(L, arg) != 0; } - static bool getBoolean(lua_State* L, int32_t arg, bool defaultValue) + inline static bool getBoolean(lua_State* L, int32_t arg, bool defaultValue) { const auto parameters = lua_gettop(L); if (parameters == 0 || arg > parameters) { @@ -328,11 +320,11 @@ class LuaScriptInterface } static std::string getString(lua_State* L, int32_t arg); + static CombatDamage getCombatDamage(lua_State* L); static Position getPosition(lua_State* L, int32_t arg, int32_t& stackpos); static Position getPosition(lua_State* L, int32_t arg); static Outfit_t getOutfit(lua_State* L, int32_t arg); static LuaVariant getVariant(lua_State* L, int32_t arg); - static InstantSpell* getInstantSpell(lua_State* L, int32_t arg); static Thing* getThing(lua_State* L, int32_t arg); static Creature* getCreature(lua_State* L, int32_t arg); @@ -350,27 +342,27 @@ class LuaScriptInterface static LuaDataType getUserdataType(lua_State* L, int32_t arg); // Is - static bool isNumber(lua_State* L, int32_t arg) + inline static bool isNumber(lua_State* L, int32_t arg) { return lua_type(L, arg) == LUA_TNUMBER; } - static bool isString(lua_State* L, int32_t arg) + inline static bool isString(lua_State* L, int32_t arg) { return lua_isstring(L, arg) != 0; } - static bool isBoolean(lua_State* L, int32_t arg) + inline static bool isBoolean(lua_State* L, int32_t arg) { return lua_isboolean(L, arg); } - static bool isTable(lua_State* L, int32_t arg) + inline static bool isTable(lua_State* L, int32_t arg) { return lua_istable(L, arg); } - static bool isFunction(lua_State* L, int32_t arg) + inline static bool isFunction(lua_State* L, int32_t arg) { return lua_isfunction(L, arg); } - static bool isUserdata(lua_State* L, int32_t arg) + inline static bool isUserdata(lua_State* L, int32_t arg) { return lua_isuserdata(L, arg) != 0; } @@ -378,19 +370,17 @@ class LuaScriptInterface // Push static void pushBoolean(lua_State* L, bool value); static void pushCombatDamage(lua_State* L, const CombatDamage& damage); - static void pushInstantSpell(lua_State* L, const InstantSpell& spell); static void pushPosition(lua_State* L, const Position& position, int32_t stackpos = 0); static void pushOutfit(lua_State* L, const Outfit_t& outfit); - static void pushLoot(lua_State* L, const std::vector& lootList); // - static void setField(lua_State* L, const char* index, lua_Number value) + inline static void setField(lua_State* L, const char* index, lua_Number value) { lua_pushnumber(L, value); lua_setfield(L, -2, index); } - static void setField(lua_State* L, const char* index, const std::string& value) + inline static void setField(lua_State* L, const char* index, const std::string& value) { pushString(L, value); lua_setfield(L, -2, index); @@ -412,21 +402,9 @@ class LuaScriptInterface void registerFunctions(); - void registerMethod(const std::string& globalName, const std::string& methodName, lua_CFunction func); - - static std::string getErrorDesc(ErrorCode_t code); - - lua_State* luaState = nullptr; - - int32_t eventTableRef = -1; - int32_t runningEventId = EVENT_ID_USER; - - //script file cache - std::map cacheFiles; - - private: void registerClass(const std::string& className, const std::string& baseClass, lua_CFunction newFunction = nullptr); void registerTable(const std::string& tableName); + void registerMethod(const std::string& className, const std::string& methodName, lua_CFunction func); void registerMetaMethod(const std::string& className, const std::string& methodName, lua_CFunction func); void registerGlobalMethod(const std::string& functionName, lua_CFunction func); void registerVariable(const std::string& tableName, const std::string& name, lua_Number value); @@ -435,16 +413,28 @@ class LuaScriptInterface std::string getStackTrace(const std::string& error_desc); + static std::string getErrorDesc(ErrorCode_t code); static bool getArea(lua_State* L, std::list& list, uint32_t& rows); //lua functions + static int luaDoCreateItem(lua_State* L); + static int luaDoCreateItemEx(lua_State* L); + static int luaDoMoveCreature(lua_State* L); + static int luaDoPlayerAddItem(lua_State* L); + static int luaDoTileAddItemEx(lua_State* L); static int luaDoSetCreatureLight(lua_State* L); //get item info static int luaGetDepotId(lua_State* L); - //get world info + //get creature info functions + static int luaGetPlayerFlagValue(lua_State* L); + static int luaGetCreatureCondition(lua_State* L); + + static int luaGetPlayerInstantSpellInfo(lua_State* L); + static int luaGetPlayerInstantSpellCount(lua_State* L); + static int luaGetWorldTime(lua_State* L); static int luaGetWorldLight(lua_State* L); static int luaGetWorldUpTime(lua_State* L); @@ -475,7 +465,12 @@ class LuaScriptInterface static int luaDoChallengeCreature(lua_State* L); + static int luaSetCreatureOutfit(lua_State* L); + static int luaSetMonsterOutfit(lua_State* L); + static int luaSetItemOutfit(lua_State* L); + static int luaDebugPrint(lua_State* L); + static int luaIsInArray(lua_State* L); static int luaAddEvent(lua_State* L); static int luaStopEvent(lua_State* L); @@ -486,9 +481,6 @@ class LuaScriptInterface static int luaGetWaypointPositionByName(lua_State* L); - static int luaSendChannelMessage(lua_State* L); - static int luaSendGuildChannelMessage(lua_State* L); - #ifndef LUAJIT_VERSION static int luaBitNot(lua_State* L); static int luaBitAnd(lua_State* L); @@ -556,12 +548,9 @@ class LuaScriptInterface static int luaGameCreateMonster(lua_State* L); static int luaGameCreateNpc(lua_State* L); static int luaGameCreateTile(lua_State* L); - static int luaGameCreateMonsterType(lua_State* L); static int luaGameStartRaid(lua_State* L); - static int luaGameGetClientVersion(lua_State* L); - static int luaGameReload(lua_State* L); // Variant @@ -582,6 +571,7 @@ class LuaScriptInterface static int luaPositionSendMagicEffect(lua_State* L); static int luaPositionSendDistanceEffect(lua_State* L); + static int luaPositionSendMonsterSay(lua_State* L); // Tile static int luaTileCreate(lua_State* L); @@ -620,8 +610,6 @@ class LuaScriptInterface static int luaTileGetThingIndex(lua_State* L); static int luaTileQueryAdd(lua_State* L); - static int luaTileAddItem(lua_State* L); - static int luaTileAddItemEx(lua_State* L); static int luaTileGetHouse(lua_State* L); @@ -650,34 +638,6 @@ class LuaScriptInterface static int luaNetworkMessageSkipBytes(lua_State* L); static int luaNetworkMessageSendToPlayer(lua_State* L); - // ModalWindow - static int luaModalWindowCreate(lua_State* L); - static int luaModalWindowDelete(lua_State* L); - - static int luaModalWindowGetId(lua_State* L); - static int luaModalWindowGetTitle(lua_State* L); - static int luaModalWindowGetMessage(lua_State* L); - - static int luaModalWindowSetTitle(lua_State* L); - static int luaModalWindowSetMessage(lua_State* L); - - static int luaModalWindowGetButtonCount(lua_State* L); - static int luaModalWindowGetChoiceCount(lua_State* L); - - static int luaModalWindowAddButton(lua_State* L); - static int luaModalWindowAddChoice(lua_State* L); - - static int luaModalWindowGetDefaultEnterButton(lua_State* L); - static int luaModalWindowSetDefaultEnterButton(lua_State* L); - - static int luaModalWindowGetDefaultEscapeButton(lua_State* L); - static int luaModalWindowSetDefaultEscapeButton(lua_State* L); - - static int luaModalWindowHasPriority(lua_State* L); - static int luaModalWindowSetPriority(lua_State* L); - - static int luaModalWindowSendToPlayer(lua_State* L); - // Item static int luaItemCreate(lua_State* L); @@ -692,9 +652,11 @@ class LuaScriptInterface static int luaItemSplit(lua_State* L); static int luaItemRemove(lua_State* L); - static int luaItemGetUniqueId(lua_State* L); + static int luaItemGetMovementId(lua_State* L); + static int luaItemSetMovementId(lua_State* L); static int luaItemGetActionId(lua_State* L); static int luaItemSetActionId(lua_State* L); + static int luaItemGetUniqueId(lua_State* L); static int luaItemGetCount(lua_State* L); static int luaItemGetCharges(lua_State* L); @@ -714,9 +676,6 @@ class LuaScriptInterface static int luaItemGetAttribute(lua_State* L); static int luaItemSetAttribute(lua_State* L); static int luaItemRemoveAttribute(lua_State* L); - static int luaItemGetCustomAttribute(lua_State* L); - static int luaItemSetCustomAttribute(lua_State* L); - static int luaItemRemoveCustomAttribute(lua_State* L); static int luaItemMoveTo(lua_State* L); static int luaItemTransform(lua_State* L); @@ -725,7 +684,6 @@ class LuaScriptInterface static int luaItemGetDescription(lua_State* L); static int luaItemHasProperty(lua_State* L); - static int luaItemIsLoadedFromMap(lua_State* L); // Container static int luaContainerCreate(lua_State* L); @@ -733,7 +691,7 @@ class LuaScriptInterface static int luaContainerGetSize(lua_State* L); static int luaContainerGetCapacity(lua_State* L); static int luaContainerGetEmptySlots(lua_State* L); - static int luaContainerGetContentDescription(lua_State* L); + static int luaContainerGetItemHoldingCount(lua_State* L); static int luaContainerGetItemCountById(lua_State* L); @@ -741,8 +699,7 @@ class LuaScriptInterface static int luaContainerHasItem(lua_State* L); static int luaContainerAddItem(lua_State* L); static int luaContainerAddItemEx(lua_State* L); - static int luaContainerGetCorpseOwner(lua_State* L); - + // Teleport static int luaTeleportCreate(lua_State* L); @@ -759,8 +716,6 @@ class LuaScriptInterface static int luaCreatureIsRemoved(lua_State* L); static int luaCreatureIsCreature(lua_State* L); static int luaCreatureIsInGhostMode(lua_State* L); - static int luaCreatureIsHealthHidden(lua_State* L); - static int luaCreatureIsImmune(lua_State* L); static int luaCreatureCanSee(lua_State* L); static int luaCreatureCanSeeCreature(lua_State* L); @@ -787,7 +742,6 @@ class LuaScriptInterface static int luaCreatureChangeSpeed(lua_State* L); static int luaCreatureSetDropLoot(lua_State* L); - static int luaCreatureSetSkillLoss(lua_State* L); static int luaCreatureGetPosition(lua_State* L); static int luaCreatureGetTile(lua_State* L); @@ -795,7 +749,6 @@ class LuaScriptInterface static int luaCreatureSetDirection(lua_State* L); static int luaCreatureGetHealth(lua_State* L); - static int luaCreatureSetHealth(lua_State* L); static int luaCreatureAddHealth(lua_State* L); static int luaCreatureGetMaxHealth(lua_State* L); static int luaCreatureSetMaxHealth(lua_State* L); @@ -810,7 +763,6 @@ class LuaScriptInterface static int luaCreatureGetCondition(lua_State* L); static int luaCreatureAddCondition(lua_State* L); static int luaCreatureRemoveCondition(lua_State* L); - static int luaCreatureHasCondition(lua_State* L); static int luaCreatureRemove(lua_State* L); static int luaCreatureTeleportTo(lua_State* L); @@ -823,9 +775,6 @@ class LuaScriptInterface static int luaCreatureGetDescription(lua_State* L); static int luaCreatureGetPathTo(lua_State* L); - static int luaCreatureMove(lua_State* L); - - static int luaCreatureGetZone(lua_State* L); // Player static int luaPlayerCreate(lua_State* L); @@ -837,6 +786,7 @@ class LuaScriptInterface static int luaPlayerGetAccountId(lua_State* L); static int luaPlayerGetLastLoginSaved(lua_State* L); static int luaPlayerGetLastLogout(lua_State* L); + static int luaPlayerHasFlag(lua_State* L); static int luaPlayerGetAccountType(lua_State* L); static int luaPlayerSetAccountType(lua_State* L); @@ -847,10 +797,10 @@ class LuaScriptInterface static int luaPlayerGetFreeCapacity(lua_State* L); static int luaPlayerGetDepotChest(lua_State* L); - static int luaPlayerGetInbox(lua_State* L); - static int luaPlayerGetSkullTime(lua_State* L); - static int luaPlayerSetSkullTime(lua_State* L); + static int luaPlayerGetMurderTimestamps(lua_State* L); + static int luaPlayerGetPlayerKillerEnd(lua_State* L); + static int luaPlayerSetPlayerKillerEnd(lua_State* L); static int luaPlayerGetDeathPenalty(lua_State* L); static int luaPlayerGetExperience(lua_State* L); @@ -875,17 +825,6 @@ class LuaScriptInterface static int luaPlayerGetSkillPercent(lua_State* L); static int luaPlayerGetSkillTries(lua_State* L); static int luaPlayerAddSkillTries(lua_State* L); - static int luaPlayerGetSpecialSkill(lua_State* L); - static int luaPlayerAddSpecialSkill(lua_State* L); - - static int luaPlayerAddOfflineTrainingTime(lua_State* L); - static int luaPlayerGetOfflineTrainingTime(lua_State* L); - static int luaPlayerRemoveOfflineTrainingTime(lua_State* L); - - static int luaPlayerAddOfflineTrainingTries(lua_State* L); - - static int luaPlayerGetOfflineTrainingSkill(lua_State* L); - static int luaPlayerSetOfflineTrainingSkill(lua_State* L); static int luaPlayerGetItemCount(lua_State* L); static int luaPlayerGetItemById(lua_State* L); @@ -935,7 +874,6 @@ class LuaScriptInterface static int luaPlayerShowTextDialog(lua_State* L); static int luaPlayerSendTextMessage(lua_State* L); - static int luaPlayerSendChannelMessage(lua_State* L); static int luaPlayerSendPrivateMessage(lua_State* L); static int luaPlayerChannelSay(lua_State* L); @@ -952,10 +890,6 @@ class LuaScriptInterface static int luaPlayerHasOutfit(lua_State* L); static int luaPlayerSendOutfitWindow(lua_State* L); - static int luaPlayerAddMount(lua_State* L); - static int luaPlayerRemoveMount(lua_State* L); - static int luaPlayerHasMount(lua_State* L); - static int luaPlayerGetPremiumDays(lua_State* L); static int luaPlayerAddPremiumDays(lua_State* L); static int luaPlayerRemovePremiumDays(lua_State* L); @@ -969,19 +903,12 @@ class LuaScriptInterface static int luaPlayerForgetSpell(lua_State* L); static int luaPlayerHasLearnedSpell(lua_State* L); - static int luaPlayerSendTutorial(lua_State* L); - static int luaPlayerAddMapMark(lua_State* L); - static int luaPlayerSave(lua_State* L); - static int luaPlayerPopupFYI(lua_State* L); static int luaPlayerIsPzLocked(lua_State* L); static int luaPlayerGetClient(lua_State* L); - static int luaPlayerGetHouse(lua_State* L); - static int luaPlayerSendHouseWindow(lua_State* L); - static int luaPlayerSetEditHouse(lua_State* L); static int luaPlayerSetGhostMode(lua_State* L); @@ -989,12 +916,7 @@ class LuaScriptInterface static int luaPlayerGetContainerById(lua_State* L); static int luaPlayerGetContainerIndex(lua_State* L); - static int luaPlayerGetInstantSpells(lua_State* L); - static int luaPlayerCanCast(lua_State* L); - - static int luaPlayerHasChaseMode(lua_State* L); - static int luaPlayerHasSecureMode(lua_State* L); - static int luaPlayerGetFightMode(lua_State* L); + static int luaPlayerGetTotalDamage(lua_State* L); // Monster static int luaMonsterCreate(lua_State* L); @@ -1033,9 +955,6 @@ class LuaScriptInterface static int luaNpcSetMasterPos(lua_State* L); - static int luaNpcGetSpeechBubble(lua_State* L); - static int luaNpcSetSpeechBubble(lua_State* L); - // Guild static int luaGuildCreate(lua_State* L); @@ -1047,9 +966,6 @@ class LuaScriptInterface static int luaGuildGetRankById(lua_State* L); static int luaGuildGetRankByLevel(lua_State* L); - static int luaGuildGetMotd(lua_State* L); - static int luaGuildSetMotd(lua_State* L); - // Group static int luaGroupCreate(lua_State* L); @@ -1059,13 +975,11 @@ class LuaScriptInterface static int luaGroupGetAccess(lua_State* L); static int luaGroupGetMaxDepotItems(lua_State* L); static int luaGroupGetMaxVipEntries(lua_State* L); - static int luaGroupHasFlag(lua_State* L); // Vocation static int luaVocationCreate(lua_State* L); static int luaVocationGetId(lua_State* L); - static int luaVocationGetClientId(lua_State* L); static int luaVocationGetName(lua_State* L); static int luaVocationGetDescription(lua_State* L); @@ -1116,68 +1030,59 @@ class LuaScriptInterface static int luaHouseGetDoors(lua_State* L); static int luaHouseGetDoorCount(lua_State* L); - static int luaHouseGetDoorIdByPosition(lua_State* L); static int luaHouseGetTiles(lua_State* L); - static int luaHouseGetItems(lua_State* L); static int luaHouseGetTileCount(lua_State* L); - static int luaHouseCanEditAccessList(lua_State* L); static int luaHouseGetAccessList(lua_State* L); static int luaHouseSetAccessList(lua_State* L); - static int luaHouseKickPlayer(lua_State* L); - // ItemType static int luaItemTypeCreate(lua_State* L); static int luaItemTypeIsCorpse(lua_State* L); static int luaItemTypeIsDoor(lua_State* L); static int luaItemTypeIsContainer(lua_State* L); + static int luaItemTypeIsChest(lua_State* L); static int luaItemTypeIsFluidContainer(lua_State* L); static int luaItemTypeIsMovable(lua_State* L); static int luaItemTypeIsRune(lua_State* L); static int luaItemTypeIsStackable(lua_State* L); static int luaItemTypeIsReadable(lua_State* L); static int luaItemTypeIsWritable(lua_State* L); - static int luaItemTypeIsBlocking(lua_State* L); - static int luaItemTypeIsGroundTile(lua_State* L); static int luaItemTypeIsMagicField(lua_State* L); - static int luaItemTypeIsUseable(lua_State* L); - static int luaItemTypeIsPickupable(lua_State* L); + static int luaItemTypeIsSplash(lua_State* L); + static int luaItemTypeIsKey(lua_State* L); + static int luaItemTypeIsDisguised(lua_State* L); + static int luaItemTypeIsDestroyable(lua_State* L); + static int luaItemTypeIsGroundTile(lua_State* L); static int luaItemTypeGetType(lua_State* L); static int luaItemTypeGetId(lua_State* L); - static int luaItemTypeGetClientId(lua_State* L); + static int luaItemTypeGetDisguiseId(lua_State* L); static int luaItemTypeGetName(lua_State* L); static int luaItemTypeGetPluralName(lua_State* L); static int luaItemTypeGetArticle(lua_State* L); static int luaItemTypeGetDescription(lua_State* L); static int luaItemTypeGetSlotPosition(lua_State *L); + static int luaItemTypeGetDestroyTarget(lua_State* L); static int luaItemTypeGetCharges(lua_State* L); static int luaItemTypeGetFluidSource(lua_State* L); static int luaItemTypeGetCapacity(lua_State* L); static int luaItemTypeGetWeight(lua_State* L); - static int luaItemTypeGetHitChance(lua_State* L); static int luaItemTypeGetShootRange(lua_State* L); static int luaItemTypeGetAttack(lua_State* L); static int luaItemTypeGetDefense(lua_State* L); - static int luaItemTypeGetExtraDefense(lua_State* L); static int luaItemTypeGetArmor(lua_State* L); static int luaItemTypeGetWeaponType(lua_State* L); - static int luaItemTypeGetElementType(lua_State* L); - static int luaItemTypeGetElementDamage(lua_State* L); - static int luaItemTypeGetTransformEquipId(lua_State* L); static int luaItemTypeGetTransformDeEquipId(lua_State* L); - static int luaItemTypeGetDestroyId(lua_State* L); static int luaItemTypeGetDecayId(lua_State* L); + static int luaItemTypeGetNutrition(lua_State* L); static int luaItemTypeGetRequiredLevel(lua_State* L); - static int luaItemTypeGetAmmoType(lua_State* L); - static int luaItemTypeGetCorpseType(lua_State* L); static int luaItemTypeHasSubType(lua_State* L); @@ -1188,8 +1093,7 @@ class LuaScriptInterface static int luaCombatSetFormula(lua_State* L); static int luaCombatSetArea(lua_State* L); - static int luaCombatAddCondition(lua_State* L); - static int luaCombatClearConditions(lua_State* L); + static int luaCombatSetCondition(lua_State* L); static int luaCombatSetCallback(lua_State* L); static int luaCombatSetOrigin(lua_State* L); @@ -1211,10 +1115,10 @@ class LuaScriptInterface static int luaConditionSetTicks(lua_State* L); static int luaConditionSetParameter(lua_State* L); - static int luaConditionSetFormula(lua_State* L); + static int luaConditionSetSpeedDelta(lua_State* L); static int luaConditionSetOutfit(lua_State* L); - static int luaConditionAddDamage(lua_State* L); + static int luaConditionSetTiming(lua_State* L); // MonsterType static int luaMonsterTypeCreate(lua_State* L); @@ -1225,100 +1129,47 @@ class LuaScriptInterface static int luaMonsterTypeIsIllusionable(lua_State* L); static int luaMonsterTypeIsHostile(lua_State* L); static int luaMonsterTypeIsPushable(lua_State* L); - static int luaMonsterTypeIsHealthHidden(lua_State* L); + static int luaMonsterTypeIsHealthShown(lua_State* L); static int luaMonsterTypeCanPushItems(lua_State* L); static int luaMonsterTypeCanPushCreatures(lua_State* L); - static int luaMonsterTypeName(lua_State* L); - static int luaMonsterTypeNameDescription(lua_State* L); + static int luaMonsterTypeGetName(lua_State* L); + static int luaMonsterTypeGetNameDescription(lua_State* L); - static int luaMonsterTypeHealth(lua_State* L); - static int luaMonsterTypeMaxHealth(lua_State* L); - static int luaMonsterTypeRunHealth(lua_State* L); - static int luaMonsterTypeExperience(lua_State* L); + static int luaMonsterTypeGetHealth(lua_State* L); + static int luaMonsterTypeGetMaxHealth(lua_State* L); + static int luaMonsterTypeGetRunHealth(lua_State* L); + static int luaMonsterTypeGetExperience(lua_State* L); - static int luaMonsterTypeCombatImmunities(lua_State* L); - static int luaMonsterTypeConditionImmunities(lua_State* L); + static int luaMonsterTypeGetCombatImmunities(lua_State* L); + static int luaMonsterTypeGetConditionImmunities(lua_State* L); static int luaMonsterTypeGetAttackList(lua_State* L); - static int luaMonsterTypeAddAttack(lua_State* L); - static int luaMonsterTypeGetDefenseList(lua_State* L); - static int luaMonsterTypeAddDefense(lua_State* L); - static int luaMonsterTypeGetElementList(lua_State* L); - static int luaMonsterTypeAddElement(lua_State* L); static int luaMonsterTypeGetVoices(lua_State* L); - static int luaMonsterTypeAddVoice(lua_State* L); - static int luaMonsterTypeGetLoot(lua_State* L); - static int luaMonsterTypeAddLoot(lua_State* L); - static int luaMonsterTypeGetCreatureEvents(lua_State* L); - static int luaMonsterTypeRegisterEvent(lua_State* L); - - static int luaMonsterTypeEventOnCallback(lua_State* L); - static int luaMonsterTypeEventType(lua_State* L); static int luaMonsterTypeGetSummonList(lua_State* L); - static int luaMonsterTypeAddSummon(lua_State* L); + static int luaMonsterTypeGetMaxSummons(lua_State* L); - static int luaMonsterTypeMaxSummons(lua_State* L); + static int luaMonsterTypeGetArmor(lua_State* L); + static int luaMonsterTypeGetDefense(lua_State* L); + static int luaMonsterTypeGetOutfit(lua_State* L); + static int luaMonsterTypeGetRace(lua_State* L); + static int luaMonsterTypeGetCorpseId(lua_State* L); + static int luaMonsterTypeGetManaCost(lua_State* L); + static int luaMonsterTypeGetBaseSpeed(lua_State* L); + static int luaMonsterTypeGetLight(lua_State* L); - static int luaMonsterTypeArmor(lua_State* L); - static int luaMonsterTypeDefense(lua_State* L); - static int luaMonsterTypeOutfit(lua_State* L); - static int luaMonsterTypeRace(lua_State* L); - static int luaMonsterTypeCorpseId(lua_State* L); - static int luaMonsterTypeManaCost(lua_State* L); - static int luaMonsterTypeBaseSpeed(lua_State* L); - static int luaMonsterTypeLight(lua_State* L); - - static int luaMonsterTypeStaticAttackChance(lua_State* L); - static int luaMonsterTypeTargetDistance(lua_State* L); - static int luaMonsterTypeYellChance(lua_State* L); - static int luaMonsterTypeYellSpeedTicks(lua_State* L); - static int luaMonsterTypeChangeTargetChance(lua_State* L); - static int luaMonsterTypeChangeTargetSpeed(lua_State* L); - - // Loot - static int luaCreateLoot(lua_State* L); - static int luaDeleteLoot(lua_State* L); - static int luaLootSetId(lua_State* L); - static int luaLootSetMaxCount(lua_State* L); - static int luaLootSetSubType(lua_State* L); - static int luaLootSetChance(lua_State* L); - static int luaLootSetActionId(lua_State* L); - static int luaLootSetDescription(lua_State* L); - static int luaLootAddChildLoot(lua_State* L); - - // MonsterSpell - static int luaCreateMonsterSpell(lua_State* L); - static int luaDeleteMonsterSpell(lua_State* L); - static int luaMonsterSpellSetType(lua_State* L); - static int luaMonsterSpellSetScriptName(lua_State* L); - static int luaMonsterSpellSetChance(lua_State* L); - static int luaMonsterSpellSetInterval(lua_State* L); - static int luaMonsterSpellSetRange(lua_State* L); - static int luaMonsterSpellSetCombatValue(lua_State* L); - static int luaMonsterSpellSetCombatType(lua_State* L); - static int luaMonsterSpellSetAttackValue(lua_State* L); - static int luaMonsterSpellSetNeedTarget(lua_State* L); - static int luaMonsterSpellSetCombatLength(lua_State* L); - static int luaMonsterSpellSetCombatSpread(lua_State* L); - static int luaMonsterSpellSetCombatRadius(lua_State* L); - static int luaMonsterSpellSetConditionType(lua_State* L); - static int luaMonsterSpellSetConditionDamage(lua_State* L); - static int luaMonsterSpellSetConditionSpeedChange(lua_State* L); - static int luaMonsterSpellSetConditionDuration(lua_State* L); - static int luaMonsterSpellSetConditionTickInterval(lua_State* L); - static int luaMonsterSpellSetCombatShootEffect(lua_State* L); - static int luaMonsterSpellSetCombatEffect(lua_State* L); + static int luaMonsterTypeGetTargetDistance(lua_State* L); + static int luaMonsterTypeGetChangeTargetChance(lua_State* L); + static int luaMonsterTypeGetChangeTargetSpeed(lua_State* L); // Party - static int luaPartyCreate(lua_State* L); static int luaPartyDisband(lua_State* L); static int luaPartyGetLeader(lua_State* L); @@ -1341,142 +1192,21 @@ class LuaScriptInterface static int luaPartyShareExperience(lua_State* L); static int luaPartySetSharedExperience(lua_State* L); - // Spells - static int luaSpellCreate(lua_State* L); - - static int luaSpellOnCastSpell(lua_State* L); - static int luaSpellRegister(lua_State* L); - static int luaSpellName(lua_State* L); - static int luaSpellId(lua_State* L); - static int luaSpellGroup(lua_State* L); - static int luaSpellCooldown(lua_State* L); - static int luaSpellGroupCooldown(lua_State* L); - static int luaSpellLevel(lua_State* L); - static int luaSpellMagicLevel(lua_State* L); - static int luaSpellMana(lua_State* L); - static int luaSpellManaPercent(lua_State* L); - static int luaSpellSoul(lua_State* L); - static int luaSpellRange(lua_State* L); - static int luaSpellPremium(lua_State* L); - static int luaSpellEnabled(lua_State* L); - static int luaSpellNeedTarget(lua_State* L); - static int luaSpellNeedWeapon(lua_State* L); - static int luaSpellNeedLearn(lua_State* L); - static int luaSpellSelfTarget(lua_State* L); - static int luaSpellBlocking(lua_State* L); - static int luaSpellAggressive(lua_State* L); - static int luaSpellVocation(lua_State* L); - - // only for InstantSpells - static int luaSpellWords(lua_State* L); - static int luaSpellNeedDirection(lua_State* L); - static int luaSpellHasParams(lua_State* L); - static int luaSpellHasPlayerNameParam(lua_State* L); - static int luaSpellNeedCasterTargetOrDirection(lua_State* L); - static int luaSpellIsBlockingWalls(lua_State* L); - - // only for RuneSpells - static int luaSpellRuneId(lua_State* L); - static int luaSpellCharges(lua_State* L); - static int luaSpellAllowFarUse(lua_State* L); - static int luaSpellBlockWalls(lua_State* L); - static int luaSpellCheckFloor(lua_State* L); - - // Actions - static int luaCreateAction(lua_State* L); - static int luaActionOnUse(lua_State* L); - static int luaActionRegister(lua_State* L); - static int luaActionItemId(lua_State* L); - static int luaActionActionId(lua_State* L); - static int luaActionUniqueId(lua_State* L); - static int luaActionAllowFarUse(lua_State* L); - static int luaActionBlockWalls(lua_State* L); - static int luaActionCheckFloor(lua_State* L); - - // Talkactions - static int luaCreateTalkaction(lua_State* L); - static int luaTalkactionOnSay(lua_State* L); - static int luaTalkactionRegister(lua_State* L); - static int luaTalkactionSeparator(lua_State* L); - - // CreatureEvents - static int luaCreateCreatureEvent(lua_State* L); - static int luaCreatureEventType(lua_State* L); - static int luaCreatureEventRegister(lua_State* L); - static int luaCreatureEventOnCallback(lua_State* L); - - // MoveEvents - static int luaCreateMoveEvent(lua_State* L); - static int luaMoveEventType(lua_State* L); - static int luaMoveEventRegister(lua_State* L); - static int luaMoveEventOnCallback(lua_State* L); - static int luaMoveEventLevel(lua_State* L); - static int luaMoveEventSlot(lua_State* L); - static int luaMoveEventMagLevel(lua_State* L); - static int luaMoveEventPremium(lua_State* L); - static int luaMoveEventVocation(lua_State* L); - static int luaMoveEventItemId(lua_State* L); - static int luaMoveEventActionId(lua_State* L); - static int luaMoveEventUniqueId(lua_State* L); - static int luaMoveEventPosition(lua_State* L); - - // GlobalEvents - static int luaCreateGlobalEvent(lua_State* L); - static int luaGlobalEventType(lua_State* L); - static int luaGlobalEventRegister(lua_State* L); - static int luaGlobalEventOnCallback(lua_State* L); - static int luaGlobalEventTime(lua_State* L); - static int luaGlobalEventInterval(lua_State* L); - - // Weapon - static int luaCreateWeapon(lua_State* L); - static int luaWeaponId(lua_State* L); - static int luaWeaponLevel(lua_State* L); - static int luaWeaponMagicLevel(lua_State* L); - static int luaWeaponMana(lua_State* L); - static int luaWeaponManaPercent(lua_State* L); - static int luaWeaponHealth(lua_State* L); - static int luaWeaponHealthPercent(lua_State* L); - static int luaWeaponSoul(lua_State* L); - static int luaWeaponPremium(lua_State* L); - static int luaWeaponBreakChance(lua_State* L); - static int luaWeaponAction(lua_State* L); - static int luaWeaponUnproperly(lua_State* L); - static int luaWeaponVocation(lua_State* L); - static int luaWeaponOnUseWeapon(lua_State* L); - static int luaWeaponRegister(lua_State* L); - static int luaWeaponElement(lua_State* L); - static int luaWeaponAttack(lua_State* L); - static int luaWeaponDefense(lua_State* L); - static int luaWeaponRange(lua_State* L); - static int luaWeaponCharges(lua_State* L); - static int luaWeaponDuration(lua_State* L); - static int luaWeaponDecayTo(lua_State* L); - static int luaWeaponTransformEquipTo(lua_State* L); - static int luaWeaponTransformDeEquipTo(lua_State* L); - static int luaWeaponSlotType(lua_State* L); - static int luaWeaponHitChance(lua_State* L); - static int luaWeaponExtraElement(lua_State* L); - - // exclusively for distance weapons - static int luaWeaponMaxHitChance(lua_State* L); - static int luaWeaponAmmoType(lua_State* L); - - // exclusively for wands - static int luaWeaponWandDamage(lua_State* L); - - // exclusively for wands & distance weapons - static int luaWeaponShootType(lua_State* L); - // + lua_State* luaState = nullptr; std::string lastLuaError; std::string interfaceName; + int32_t eventTableRef = -1; static ScriptEnvironment scriptEnv[16]; static int32_t scriptEnvIndex; + int32_t runningEventId = EVENT_ID_USER; std::string loadingFile; + + //script file cache + std::map cacheFiles; }; class LuaEnvironment : public LuaScriptInterface @@ -1489,9 +1219,9 @@ class LuaEnvironment : public LuaScriptInterface LuaEnvironment(const LuaEnvironment&) = delete; LuaEnvironment& operator=(const LuaEnvironment&) = delete; - bool initState() override; + bool initState(); bool reInitState(); - bool closeState() override; + bool closeState(); LuaScriptInterface* getTestInterface(); diff --git a/src/mailbox.cpp b/src/mailbox.cpp index 98423da..31cd67f 100644 --- a/src/mailbox.cpp +++ b/src/mailbox.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,7 +21,9 @@ #include "mailbox.h" #include "game.h" +#include "player.h" #include "iologindata.h" +#include "town.h" extern Game g_game; @@ -91,22 +93,30 @@ void Mailbox::postRemoveNotification(Thing* thing, const Cylinder* newParent, in bool Mailbox::sendItem(Item* item) const { std::string receiver; - if (!getReceiver(item, receiver)) { + std::string townName; + if (!getDestination(item, receiver, townName)) { return false; } - /**No need to continue if its still empty**/ - if (receiver.empty()) { + 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) { - if (g_game.internalMoveItem(item->getParent(), player->getInbox(), INDEX_WHEREEVER, - item, item->getItemCount(), nullptr, FLAG_NOLIMIT) == RETURNVALUE_NOERROR) { - g_game.transformItem(item, item->getID() + 1); - player->onReceiveMail(); - return true; + 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(town->getID()); + return true; + } } } else { Player tmpPlayer(nullptr); @@ -114,25 +124,30 @@ bool Mailbox::sendItem(Item* item) const return false; } - if (g_game.internalMoveItem(item->getParent(), tmpPlayer.getInbox(), INDEX_WHEREEVER, - item, item->getItemCount(), nullptr, FLAG_NOLIMIT) == RETURNVALUE_NOERROR) { - g_game.transformItem(item, item->getID() + 1); - IOLoginData::savePlayer(&tmpPlayer); - return true; + 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::getReceiver(Item* item, std::string& name) const +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 && getReceiver(containerItem, name)) { + if (containerItem->getID() == ITEM_LABEL && getDestination(containerItem, name, town)) { return true; } } + return false; } @@ -141,8 +156,24 @@ bool Mailbox::getReceiver(Item* item, std::string& name) const return false; } - name = getFirstLine(text); + 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; } diff --git a/src/mailbox.h b/src/mailbox.h index 400b6c6..084dc26 100644 --- a/src/mailbox.h +++ b/src/mailbox.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,35 +29,35 @@ class Mailbox final : public Item, public Cylinder public: explicit Mailbox(uint16_t itemId) : Item(itemId) {} - Mailbox* getMailbox() override { + Mailbox* getMailbox() final { return this; } - const Mailbox* getMailbox() const override { + 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 override; + 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 override; - ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const override; + 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) override; + uint32_t& flags) final; - void addThing(Thing* thing) override; - void addThing(int32_t index, Thing* thing) override; + void addThing(Thing* thing) final; + void addThing(int32_t index, Thing* thing) final; - void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override; - void replaceThing(uint32_t index, Thing* thing) override; + 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) override; + void removeThing(Thing* thing, uint32_t count) 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 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 getReceiver(Item* item, std::string& name) const; + bool getDestination(Item* item, std::string& name, std::string& town) const; bool sendItem(Item* item) const; static bool canSend(const Item* item); diff --git a/src/map.cpp b/src/map.cpp index 539e937..94bdeef 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ #include "iomapserialize.h" #include "combat.h" #include "creature.h" +#include "monster.h" #include "game.h" extern Game g_game; @@ -35,6 +36,7 @@ bool Map::loadMap(const std::string& identifier, bool loadHouses) return false; } + Npcs::loadNpcs(); if (!IOMap::loadSpawns(this)) { std::cout << "[Warning - Map::loadMap] Failed to load spawn data." << std::endl; } @@ -161,7 +163,14 @@ bool Map::placeCreature(const Position& centerPos, Creature* creature, bool exte Tile* tile = getTile(centerPos.x, centerPos.y, centerPos.z); if (tile) { placeInPZ = tile->hasFlag(TILESTATE_PROTECTIONZONE); - ReturnValue ret = tile->queryAdd(0, *creature, 1, FLAG_IGNOREBLOCKITEM); + + ReturnValue ret; + if (creature->getPlayer()) { + ret = tile->queryAdd(0, *creature, 1, 0); + } else { + ret = tile->queryAdd(0, *creature, 1, (creature->getMonster() ? FLAG_PLACECHECK : FLAG_IGNOREBLOCKITEM)); + } + foundTile = forceLogin || ret == RETURNVALUE_NOERROR || ret == RETURNVALUE_PLAYERISNOTINVITED; } else { placeInPZ = false; @@ -200,7 +209,7 @@ bool Map::placeCreature(const Position& centerPos, Creature* creature, bool exte continue; } - if (tile->queryAdd(0, *creature, 1, 0) == RETURNVALUE_NOERROR) { + if (tile->queryAdd(0, *creature, 1, (creature->getMonster() ? FLAG_PLACECHECK : 0)) == RETURNVALUE_NOERROR) { if (!extendedPos || isSightClear(centerPos, tryPos, false)) { foundTile = true; break; @@ -234,13 +243,12 @@ void Map::moveCreature(Creature& creature, Tile& newTile, bool forceTeleport/* = bool teleport = forceTeleport || !newTile.getGround() || !Position::areInRange<1, 1, 0>(oldPos, newPos); - SpectatorVec spectators, newPosSpectators; - getSpectators(spectators, oldPos, true); - getSpectators(newPosSpectators, newPos, true); - spectators.addSpectators(newPosSpectators); + SpectatorVec list; + getSpectators(list, oldPos, true); + getSpectators(list, newPos, true); std::vector oldStackPosVector; - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { if (Player* tmpPlayer = spectator->getPlayer()) { if (tmpPlayer->canSeeCreature(&creature)) { oldStackPosVector.push_back(oldTile.getClientIndexOfCreature(tmpPlayer, &creature)); @@ -281,7 +289,7 @@ void Map::moveCreature(Creature& creature, Tile& newTile, bool forceTeleport/* = //send to client size_t i = 0; - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { if (Player* tmpPlayer = spectator->getPlayer()) { //Use the correct stackpos int32_t stackpos = oldStackPosVector[i++]; @@ -292,7 +300,7 @@ void Map::moveCreature(Creature& creature, Tile& newTile, bool forceTeleport/* = } //event method - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { spectator->onCreatureMove(&creature, &newTile, newPos, &oldTile, oldPos, teleport); } @@ -300,7 +308,7 @@ void Map::moveCreature(Creature& creature, Tile& newTile, bool forceTeleport/* = newTile.postAddNotification(&creature, &oldTile, 0); } -void Map::getSpectatorsInternal(SpectatorVec& spectators, const Position& centerPos, int32_t minRangeX, int32_t maxRangeX, int32_t minRangeY, int32_t maxRangeY, int32_t minRangeZ, int32_t maxRangeZ, bool onlyPlayers) const +void Map::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 { int_fast16_t min_y = centerPos.y + minRangeY; int_fast16_t min_x = centerPos.x + minRangeX; @@ -340,7 +348,7 @@ void Map::getSpectatorsInternal(SpectatorVec& spectators, const Position& center continue; } - spectators.emplace_back(creature); + list.insert(creature); } leafE = leafE->leafE; } else { @@ -356,7 +364,7 @@ void Map::getSpectatorsInternal(SpectatorVec& spectators, const Position& center } } -void Map::getSpectators(SpectatorVec& spectators, 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 Map::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*/) { if (centerPos.z >= MAP_MAX_LAYERS) { return; @@ -374,11 +382,11 @@ void Map::getSpectators(SpectatorVec& spectators, const Position& centerPos, boo if (onlyPlayers) { auto it = playersSpectatorCache.find(centerPos); if (it != playersSpectatorCache.end()) { - if (!spectators.empty()) { - const SpectatorVec& cachedSpectators = it->second; - spectators.insert(spectators.end(), cachedSpectators.begin(), cachedSpectators.end()); + if (!list.empty()) { + const SpectatorVec& cachedList = it->second; + list.insert(cachedList.begin(), cachedList.end()); } else { - spectators = it->second; + list = it->second; } foundCache = true; @@ -389,17 +397,17 @@ void Map::getSpectators(SpectatorVec& spectators, const Position& centerPos, boo auto it = spectatorCache.find(centerPos); if (it != spectatorCache.end()) { if (!onlyPlayers) { - if (!spectators.empty()) { - const SpectatorVec& cachedSpectators = it->second; - spectators.insert(spectators.end(), cachedSpectators.begin(), cachedSpectators.end()); + if (!list.empty()) { + const SpectatorVec& cachedList = it->second; + list.insert(cachedList.begin(), cachedList.end()); } else { - spectators = it->second; + list = it->second; } } else { - const SpectatorVec& cachedSpectators = it->second; - for (Creature* spectator : cachedSpectators) { + const SpectatorVec& cachedList = it->second; + for (Creature* spectator : cachedList) { if (spectator->getPlayer()) { - spectators.emplace_back(spectator); + list.insert(spectator); } } } @@ -437,13 +445,13 @@ void Map::getSpectators(SpectatorVec& spectators, const Position& centerPos, boo maxRangeZ = centerPos.z; } - getSpectatorsInternal(spectators, centerPos, minRangeX, maxRangeX, minRangeY, maxRangeY, minRangeZ, maxRangeZ, onlyPlayers); + getSpectatorsInternal(list, centerPos, minRangeX, maxRangeX, minRangeY, maxRangeY, minRangeZ, maxRangeZ, onlyPlayers); if (cacheResult) { if (onlyPlayers) { - playersSpectatorCache[centerPos] = spectators; + playersSpectatorCache[centerPos] = list; } else { - spectatorCache[centerPos] = spectators; + spectatorCache[centerPos] = list; } } } @@ -452,10 +460,6 @@ void Map::getSpectators(SpectatorVec& spectators, const Position& centerPos, boo void Map::clearSpectatorCache() { spectatorCache.clear(); -} - -void Map::clearPlayersSpectatorCache() -{ playersSpectatorCache.clear(); } @@ -559,7 +563,7 @@ const Tile* Map::canWalkTo(const Creature& creature, const Position& pos) const //used for non-cached tiles Tile* tile = getTile(pos.x, pos.y, pos.z); if (creature.getTile() != tile) { - if (!tile || tile->queryAdd(0, creature, 1, FLAG_PATHFINDING | FLAG_IGNOREFIELDDAMAGE) != RETURNVALUE_NOERROR) { + if (!tile || tile->queryAdd(0, creature, 1, FLAG_PATHFINDING) != RETURNVALUE_NOERROR) { return nullptr; } } @@ -842,16 +846,25 @@ int_fast32_t AStarNodes::getTileWalkCost(const Creature& creature, const Tile* t { int_fast32_t cost = 0; if (tile->getTopVisibleCreature(&creature) != nullptr) { + if (const Monster* monster = creature.getMonster()) { + if (monster->canPushCreatures()) { + return cost; + } + } + //destroy creature cost cost += MAP_NORMALWALKCOST * 3; } if (const MagicField* field = tile->getFieldItem()) { CombatType_t combatType = field->getCombatType(); - if (!creature.isImmune(combatType) && !creature.hasCondition(Combat::DamageToConditionType(combatType))) { - cost += MAP_NORMALWALKCOST * 18; + if (combatType != COMBAT_NONE) { + if (!creature.isImmune(combatType) && !creature.hasCondition(Combat::DamageToConditionType(combatType))) { + cost += MAP_NORMALWALKCOST * 18; + } } } + return cost; } diff --git a/src/map.h b/src/map.h index 390eb6d..3e8df8d 100644 --- a/src/map.h +++ b/src/map.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -73,7 +73,7 @@ class AStarNodes int_fast32_t closedNodes; }; -using SpectatorCache = std::map; +typedef std::map SpectatorCache; static constexpr int32_t FLOOR_BITS = 3; static constexpr int32_t FLOOR_SIZE = (1 << FLOOR_BITS); @@ -110,7 +110,7 @@ class QTreeNode QTreeLeafNode* getLeaf(uint32_t x, uint32_t y); template - static Leaf getLeafStatic(Node node, uint32_t x, uint32_t y) + inline static Leaf getLeafStatic(Node node, uint32_t x, uint32_t y) { do { node = node->child[((x & 0x8000) >> 15) | ((y & 0x8000) >> 14)]; @@ -127,11 +127,10 @@ class QTreeNode QTreeLeafNode* createLeaf(uint32_t x, uint32_t y, uint32_t level); protected: - bool leaf = false; - - private: QTreeNode* child[4] = {}; + bool leaf = false; + friend class Map; }; @@ -153,7 +152,7 @@ class QTreeLeafNode final : public QTreeNode void addCreature(Creature* c); void removeCreature(Creature* c); - private: + protected: static bool newLeaf; QTreeLeafNode* leafS = nullptr; QTreeLeafNode* leafE = nullptr; @@ -197,7 +196,7 @@ class Map * \returns A pointer to that tile. */ Tile* getTile(uint16_t x, uint16_t y, uint8_t z) const; - Tile* getTile(const Position& pos) const { + inline Tile* getTile(const Position& pos) const { return getTile(pos.x, pos.y, pos.z); } @@ -220,12 +219,11 @@ class Map void moveCreature(Creature& creature, Tile& newTile, bool forceTeleport = false); - void getSpectators(SpectatorVec& spectators, const Position& centerPos, bool multifloor = false, bool onlyPlayers = 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(); - void clearPlayersSpectatorCache(); /** * Checks if you can throw an object to that position @@ -264,8 +262,7 @@ class Map Spawns spawns; Towns towns; Houses houses; - - private: + protected: SpectatorCache spectatorCache; SpectatorCache playersSpectatorCache; @@ -278,7 +275,7 @@ class Map uint32_t height = 0; // Actually scans the map for spectators - void getSpectatorsInternal(SpectatorVec& spectators, const Position& centerPos, + 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; diff --git a/src/monster.cpp b/src/monster.cpp index 58883a7..69718e4 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,11 +22,11 @@ #include "monster.h" #include "game.h" #include "spells.h" -#include "events.h" +#include "configmanager.h" +extern ConfigManager g_config; extern Game g_game; extern Monsters g_monsters; -extern Events* g_events; int32_t Monster::despawnRange; int32_t Monster::despawnRadius; @@ -42,10 +42,10 @@ Monster* Monster::createMonster(const std::string& name) return new Monster(mType); } -Monster::Monster(MonsterType* mType) : +Monster::Monster(MonsterType* mtype) : Creature(), - strDescription(mType->nameDescription), - mType(mType) + strDescription(asLowerCaseString(mtype->nameDescription)), + mType(mtype) { defaultOutfit = mType->info.outfit; currentOutfit = mType->info.outfit; @@ -80,31 +80,45 @@ void Monster::removeList() g_game.removeMonster(this); } +int32_t Monster::getDefense() +{ + int32_t totalDefense = mType->info.defense + 1; + int32_t defenseSkill = mType->info.skill; + + fightMode_t attackMode = FIGHTMODE_BALANCED; + + if ((followCreature || !attackedCreature) && earliestAttackTime <= OTSYS_TIME()) { + attackMode = FIGHTMODE_DEFENSE; + } + + if (attackMode == FIGHTMODE_ATTACK) { + totalDefense -= 4 * totalDefense / 10; + } else if (attackMode == FIGHTMODE_DEFENSE) { + totalDefense += 8 * totalDefense / 10; + } + + if (totalDefense) { + int32_t formula = (5 * (defenseSkill) + 50) * totalDefense; + int32_t randresult = rand() % 100; + + totalDefense = formula * ((rand() % 100 + randresult) / 2) / 10000.; + } + + return totalDefense; +} + bool Monster::canSee(const Position& pos) const { return Creature::canSee(getPosition(), pos, 9, 9); } -bool Monster::canWalkOnFieldType(CombatType_t combatType) const +void Monster::onAttackedCreature(Creature* creature) { - switch (combatType) { - case COMBAT_ENERGYDAMAGE: - return mType->info.canWalkOnEnergy; - case COMBAT_FIREDAMAGE: - return mType->info.canWalkOnFire; - case COMBAT_EARTHDAMAGE: - return mType->info.canWalkOnPoison; - default: - return true; + if (isSummon() && getMaster()) { + master->onAttackedCreature(creature); } } -void Monster::onAttackedCreatureDisappear(bool) -{ - attackTicks = 0; - extraMeleeAttack = true; -} - void Monster::onCreatureAppear(Creature* creature, bool isLogin) { Creature::onCreatureAppear(creature, isLogin); @@ -238,7 +252,7 @@ void Monster::onCreatureMove(Creature* creature, const Tile* newTile, const Posi } if (canSeeNewPos && isSummon() && getMaster() == creature) { - isMasterInRange = true; //Follow master again + isMasterInRange = true; //Follow master again } updateIdleStatus(); @@ -250,7 +264,7 @@ void Monster::onCreatureMove(Creature* creature, const Tile* newTile, const Posi int32_t offset_x = Position::getDistanceX(followPosition, position); int32_t offset_y = Position::getDistanceY(followPosition, position); - if ((offset_x > 1 || offset_y > 1) && mType->info.changeTargetChance > 0) { + if ((offset_x > 1 || offset_y > 1) && mType->info.changeTargetChance > 0 && targetChangeCooldown <= 0) { Direction dir = getDirectionTo(position, followPosition); const Position& checkPosition = getNextPosition(dir, position); @@ -365,10 +379,10 @@ void Monster::updateTargetList() } } - SpectatorVec spectators; - g_game.map.getSpectators(spectators, position, true); - spectators.erase(this); - for (Creature* spectator : spectators) { + SpectatorVec list; + g_game.map.getSpectators(list, position, true); + list.erase(this); + for (Creature* spectator : list) { if (canSee(spectator->getPosition())) { onCreatureFound(spectator); } @@ -481,14 +495,14 @@ void Monster::onCreatureLeave(Creature* creature) } } -bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAULT*/) +bool Monster::searchTarget(TargetSearchType_t searchType) { std::list resultList; const Position& myPos = getPosition(); for (Creature* creature : targetList) { if (followCreature != creature && isTarget(creature)) { - if (searchType == TARGETSEARCH_RANDOM || canUseAttack(myPos, creature)) { + if (searchType == TARGETSEARCH_ANY || canUseAttack(myPos, creature)) { resultList.push_back(creature); } } @@ -497,32 +511,31 @@ bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAUL switch (searchType) { case TARGETSEARCH_NEAREST: { Creature* target = nullptr; + + int32_t minRange = 0; + if (attackedCreature) { + const Position& targetPosition = attackedCreature->getPosition(); + minRange = Position::getDistanceX(myPos, targetPosition) + Position::getDistanceY(myPos, targetPosition); + } + if (!resultList.empty()) { - auto it = resultList.begin(); - target = *it; + for (Creature* creature : resultList) { + const Position& targetPosition = creature->getPosition(); - if (++it != resultList.end()) { - const Position& targetPosition = target->getPosition(); - int32_t minRange = Position::getDistanceX(myPos, targetPosition) + Position::getDistanceY(myPos, targetPosition); - do { - const Position& pos = (*it)->getPosition(); - - int32_t distance = Position::getDistanceX(myPos, pos) + Position::getDistanceY(myPos, pos); - if (distance < minRange) { - target = *it; - minRange = distance; - } - } while (++it != resultList.end()); + int32_t distance = Position::getDistanceX(myPos, targetPosition) + Position::getDistanceY(myPos, targetPosition); + if (distance < minRange) { + target = creature; + minRange = distance; + } } } else { - int32_t minRange = std::numeric_limits::max(); for (Creature* creature : targetList) { if (!isTarget(creature)) { continue; } - const Position& pos = creature->getPosition(); - int32_t distance = Position::getDistanceX(myPos, pos) + Position::getDistanceY(myPos, pos); + const Position& targetPosition = creature->getPosition(); + int32_t distance = Position::getDistanceX(myPos, targetPosition) + Position::getDistanceY(myPos, targetPosition); if (distance < minRange) { target = creature; minRange = distance; @@ -535,10 +548,79 @@ bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAUL } break; } + case TARGETSEARCH_WEAKEST: { + Creature* target = nullptr; - case TARGETSEARCH_DEFAULT: - case TARGETSEARCH_ATTACKRANGE: - case TARGETSEARCH_RANDOM: + int32_t health = 0; + if (attackedCreature) { + health = attackedCreature->getMaxHealth(); + } + + if (!resultList.empty()) { + for (Creature* creature : resultList) { + if (creature->getMaxHealth() < health) { + target = creature; + health = creature->getMaxHealth(); + } + } + } else { + for (Creature* creature : targetList) { + if (creature->getMaxHealth() < health) { + target = creature; + health = creature->getMaxHealth(); + } + } + } + + if (target && selectTarget(target)) { + return true; + } + break; + } + case TARGETSEARCH_MOSTDAMAGE: { + Creature* target = nullptr; + + int32_t maxDamage = 0; + + if (!resultList.empty()) { + for (Creature* creature : resultList) { + auto it = damageMap.find(creature->getID()); + if (it == damageMap.end()) { + continue; + } + + int32_t damage = it->second.total; + + if (OTSYS_TIME() - it->second.ticks <= g_config.getNumber(ConfigManager::PZ_LOCKED)) { + if (damage > maxDamage) { + target = creature; + maxDamage = damage; + } + } + } + } else { + for (Creature* creature : targetList) { + auto it = damageMap.find(creature->getID()); + if (it == damageMap.end()) { + continue; + } + + int32_t damage = it->second.total; + + if (OTSYS_TIME() - it->second.ticks <= g_config.getNumber(ConfigManager::PZ_LOCKED)) { + if (damage > maxDamage) { + target = creature; + maxDamage = damage; + } + } + } + } + + if (target && selectTarget(target)) { + return true; + } + break; + } default: { if (!resultList.empty()) { auto it = resultList.begin(); @@ -546,20 +628,19 @@ bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAUL return selectTarget(*it); } - if (searchType == TARGETSEARCH_ATTACKRANGE) { - return false; - } - break; } } - //lets just pick the first target in the list - for (Creature* target : targetList) { - if (followCreature != target && selectTarget(target)) { - return true; + //lets just pick the first target in the list if we do not have a target + if (!attackedCreature) { + for (Creature* target : targetList) { + if (followCreature != target && selectTarget(target)) { + return true; + } } } + return false; } @@ -637,6 +718,10 @@ bool Monster::selectTarget(Creature* creature) g_dispatcher.addTask(createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID()))); } } + + // without this task, monster would randomly start dancing until next game round + g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, &g_game, getID()))); + targetChangeCooldown += 3000; return setFollowCreature(creature); } @@ -673,7 +758,7 @@ void Monster::updateIdleStatus() void Monster::onAddCondition(ConditionType_t type) { - if (type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON) { + if (type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON || type == CONDITION_AGGRESSIVE) { updateMapCache(); } @@ -682,8 +767,7 @@ void Monster::onAddCondition(ConditionType_t type) void Monster::onEndCondition(ConditionType_t type) { - if (type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON) { - ignoreFieldDamage = false; + if (type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON || type == CONDITION_AGGRESSIVE) { updateMapCache(); } @@ -692,6 +776,10 @@ void Monster::onEndCondition(ConditionType_t type) void Monster::onThink(uint32_t interval) { + if (OTSYS_TIME() < earliestWakeUpTime) { + return; + } + Creature::onThink(interval); if (mType->info.thinkEvent != -1) { @@ -718,9 +806,10 @@ void Monster::onThink(uint32_t interval) } } - if (!isInSpawnRange(position)) { - g_game.internalTeleport(this, masterPos); - setIdle(true); + if (!isInSpawnRange(position) || (lifetime > 0 && (OTSYS_TIME() >= lifetime))) { + // Despawn creatures if they are out of their spawn zone + g_game.removeCreature(this); + g_game.addMagicEffect(getPosition(), CONST_ME_POFF); } else { updateIdleStatus(); @@ -730,7 +819,6 @@ void Monster::onThink(uint32_t interval) if (isSummon()) { if (!attackedCreature) { if (getMaster() && getMaster()->getAttackedCreature()) { - //This happens if the monster is summoned during combat selectTarget(getMaster()->getAttackedCreature()); } else if (getMaster() != followCreature) { //Our master has not ordered us to attack anything, lets follow him around instead. @@ -739,15 +827,23 @@ void Monster::onThink(uint32_t interval) } else if (attackedCreature == this) { setFollowCreature(nullptr); } else if (followCreature != attackedCreature) { - //This happens just after a master orders an attack, so lets follow it aswell. setFollowCreature(attackedCreature); } + + if (master) { + if (Monster* monster = master->getMonster()) { + if (monster->mType->info.targetDistance <= 1 && !monster->hasFollowPath) { + setFollowCreature(master); + setAttackedCreature(nullptr); + } + } + } } else if (!targetList.empty()) { if (!followCreature || !hasFollowPath) { - searchTarget(); + searchTarget(TARGETSEARCH_ANY); } else if (isFleeing()) { if (attackedCreature && !canUseAttack(getPosition(), attackedCreature)) { - searchTarget(TARGETSEARCH_ATTACKRANGE); + searchTarget(TARGETSEARCH_NEAREST); } } } @@ -759,56 +855,43 @@ void Monster::onThink(uint32_t interval) } } -void Monster::doAttacking(uint32_t interval) +void Monster::doAttacking(uint32_t) { if (!attackedCreature || (isSummon() && attackedCreature == this)) { return; } - bool updateLook = true; - bool resetTicks = interval != 0; - attackTicks += interval; - const Position& myPos = getPosition(); const Position& targetPos = attackedCreature->getPosition(); - for (const spellBlock_t& spellBlock : mType->info.attackSpells) { - bool inRange = false; - - if (attackedCreature == nullptr) { - break; + bool updateLook = false; + + if (OTSYS_TIME() >= earliestAttackTime && !isFleeing()) { + updateLook = true; + if (Combat::closeAttack(this, attackedCreature, FIGHTMODE_BALANCED)) { + egibleToDance = true; + earliestAttackTime = OTSYS_TIME() + 2000; + removeCondition(CONDITION_AGGRESSIVE, true); } - - if (canUseSpell(myPos, targetPos, spellBlock, interval, inRange, resetTicks)) { - if (spellBlock.chance >= static_cast(uniform_random(1, 100))) { - if (updateLook) { - updateLookDirection(); - updateLook = false; - } + } + + for (spellBlock_t& spellBlock : mType->info.attackSpells) { + if (spellBlock.range != 0 && std::max(Position::getDistanceX(myPos, targetPos), Position::getDistanceY(myPos, targetPos)) <= spellBlock.range) { + if (uniform_random(0, spellBlock.chance) == 0 && (master || health > mType->info.runAwayHealth || uniform_random(1, 3) == 1)) { + updateLookDirection(); minCombatValue = spellBlock.minCombatValue; maxCombatValue = spellBlock.maxCombatValue; + spellBlock.spell->castSpell(this, attackedCreature); - - if (spellBlock.isMelee) { - extraMeleeAttack = false; - } + egibleToDance = true; } } - - if (!inRange && spellBlock.isMelee) { - //melee swing out of reach - extraMeleeAttack = true; - } } if (updateLook) { updateLookDirection(); } - - if (resetTicks) { - attackTicks = 0; - } } bool Monster::canUseAttack(const Position& pos, const Creature* target) const @@ -826,40 +909,6 @@ bool Monster::canUseAttack(const Position& pos, const Creature* target) const return true; } -bool Monster::canUseSpell(const Position& pos, const Position& targetPos, - const spellBlock_t& sb, uint32_t interval, bool& inRange, bool& resetTicks) -{ - inRange = true; - - if (sb.isMelee && isFleeing()) { - return false; - } - - if (extraMeleeAttack) { - lastMeleeAttack = OTSYS_TIME(); - } else if (sb.isMelee && (OTSYS_TIME() - lastMeleeAttack) < 1500) { - return false; - } - - if (!sb.isMelee || !extraMeleeAttack) { - if (sb.speed > attackTicks) { - resetTicks = false; - return false; - } - - if (attackTicks % sb.speed >= interval) { - //already used this spell for this round - return false; - } - } - - if (sb.range != 0 && std::max(Position::getDistanceX(pos, targetPos), Position::getDistanceY(pos, targetPos)) > sb.range) { - inRange = false; - return false; - } - return true; -} - void Monster::onThinkTarget(uint32_t interval) { if (!isSummon()) { @@ -882,13 +931,42 @@ void Monster::onThinkTarget(uint32_t interval) if (targetChangeTicks >= mType->info.changeTargetSpeed) { targetChangeTicks = 0; - targetChangeCooldown = mType->info.changeTargetSpeed; - if (mType->info.changeTargetChance >= uniform_random(1, 100)) { - if (mType->info.targetDistance <= 1) { - searchTarget(TARGETSEARCH_RANDOM); - } else { - searchTarget(TARGETSEARCH_NEAREST); + if (mType->info.changeTargetChance > uniform_random(0, 99)) { + // search target strategies, if no strategy succeeds, target is not switched + int32_t random = uniform_random(0, 99); + int32_t current_strategy = 0; + + TargetSearchType_t searchType = TARGETSEARCH_ANY; + + do + { + int32_t strategy = 0; + + if (current_strategy == 0) { + strategy = mType->info.strategyNearestEnemy; + searchType = TARGETSEARCH_NEAREST; + } else if (current_strategy == 1) { + strategy = mType->info.strategyWeakestEnemy; + searchType = TARGETSEARCH_WEAKEST; + } else if (current_strategy == 2) { + strategy = mType->info.strategyMostDamageEnemy; + searchType = TARGETSEARCH_MOSTDAMAGE; + } else if (current_strategy == 3) { + strategy = mType->info.strategyRandomEnemy; + searchType = TARGETSEARCH_RANDOM; + } + + if (random < strategy) { + break; + } + + current_strategy++; + random -= strategy; + } while (current_strategy <= 3); + + if (searchType != TARGETSEARCH_ANY) { + searchTarget(searchType); } } } @@ -897,23 +975,10 @@ void Monster::onThinkTarget(uint32_t interval) } } -void Monster::onThinkDefense(uint32_t interval) +void Monster::onThinkDefense(uint32_t) { - bool resetTicks = true; - defenseTicks += interval; - for (const spellBlock_t& spellBlock : mType->info.defenseSpells) { - if (spellBlock.speed > defenseTicks) { - resetTicks = false; - continue; - } - - if (defenseTicks % spellBlock.speed >= interval) { - //already used this spell for this round - continue; - } - - if ((spellBlock.chance >= static_cast(uniform_random(1, 100)))) { + if (uniform_random(0, spellBlock.chance) == 0 && (master || health > mType->info.runAwayHealth || uniform_random(1, 3) == 1)) { minCombatValue = spellBlock.minCombatValue; maxCombatValue = spellBlock.maxCombatValue; spellBlock.spell->castSpell(this, this); @@ -922,20 +987,10 @@ void Monster::onThinkDefense(uint32_t interval) if (!isSummon() && summons.size() < mType->info.maxSummons && hasFollowPath) { for (const summonBlock_t& summonBlock : mType->info.summons) { - if (summonBlock.speed > defenseTicks) { - resetTicks = false; - continue; - } - if (summons.size() >= mType->info.maxSummons) { continue; } - if (defenseTicks % summonBlock.speed >= interval) { - //already used this spell for this round - continue; - } - uint32_t summonCount = 0; for (Creature* summon : summons) { if (summon->getName() == summonBlock.name) { @@ -947,49 +1002,41 @@ void Monster::onThinkDefense(uint32_t interval) continue; } - if (summonBlock.chance < static_cast(uniform_random(1, 100))) { - continue; - } + if (normal_random(0, summonBlock.chance) == 0 && (health > mType->info.runAwayHealth || normal_random(1, 3) == 1)) { + Monster* summon = Monster::createMonster(summonBlock.name); + if (summon) { + const Position& summonPos = getPosition(); - Monster* summon = Monster::createMonster(summonBlock.name); - if (summon) { - if (g_game.placeCreature(summon, getPosition(), false, summonBlock.force)) { - summon->setDropLoot(false); - summon->setSkillLoss(false); - summon->setMaster(this); - g_game.addMagicEffect(getPosition(), CONST_ME_MAGIC_BLUE); - g_game.addMagicEffect(summon->getPosition(), CONST_ME_TELEPORT); - } else { - delete summon; + addSummon(summon); + + if (!g_game.placeCreature(summon, summonPos, false, summonBlock.force)) { + removeSummon(summon); + } else { + g_game.addMagicEffect(getPosition(), CONST_ME_MAGIC_BLUE); + g_game.addMagicEffect(summon->getPosition(), CONST_ME_TELEPORT); + } } } } } - - if (resetTicks) { - defenseTicks = 0; - } } -void Monster::onThinkYell(uint32_t interval) +void Monster::onThinkYell(uint32_t) { - if (mType->info.yellSpeedTicks == 0) { + if (mType->info.voiceVector.empty()) { return; } - yellTicks += interval; - if (yellTicks >= mType->info.yellSpeedTicks) { - yellTicks = 0; + int32_t randomResult = rand(); + if (rand() == 50 * (randomResult / 50)) { + int32_t totalVoices = mType->info.voiceVector.size(); + const voiceBlock_t& voice = mType->info.voiceVector[rand() % totalVoices + 1]; - if (!mType->info.voiceVector.empty() && (mType->info.yellChance >= static_cast(uniform_random(1, 100)))) { - uint32_t index = uniform_random(0, mType->info.voiceVector.size() - 1); - const voiceBlock_t& vb = mType->info.voiceVector[index]; - - if (vb.yellText) { - g_game.internalCreatureSay(this, TALKTYPE_MONSTER_YELL, vb.text, false); - } else { - g_game.internalCreatureSay(this, TALKTYPE_MONSTER_SAY, vb.text, false); - } + if (voice.yellText) { + g_game.internalCreatureSay(this, TALKTYPE_MONSTER_YELL, voice.text, false); + } + else { + g_game.internalCreatureSay(this, TALKTYPE_MONSTER_SAY, voice.text, false); } } } @@ -1112,27 +1159,117 @@ bool Monster::getNextStep(Direction& direction, uint32_t& flags) bool result = false; if ((!followCreature || !hasFollowPath) && (!isSummon() || !isMasterInRange)) { - if (getTimeSinceLastMove() >= 1000) { - randomStepping = true; + if (OTSYS_TIME() >= nextDanceStepRound) { + updateLookDirection(); + nextDanceStepRound = OTSYS_TIME() + getStepDuration(); + //choose a random direction result = getRandomStep(getPosition(), direction); } } else if ((isSummon() && isMasterInRange) || followCreature) { - randomStepping = false; result = Creature::getNextStep(direction, flags); if (result) { flags |= FLAG_PATHFINDING; } else { - if (ignoreFieldDamage) { - ignoreFieldDamage = false; - updateMapCache(); - } //target dancing if (attackedCreature && attackedCreature == followCreature) { if (isFleeing()) { result = getDanceStep(getPosition(), direction, false, false); - } else if (mType->info.staticAttackChance < static_cast(uniform_random(1, 100))) { - result = getDanceStep(getPosition(), direction); + } else if (egibleToDance && OTSYS_TIME() >= earliestDanceTime) { + if (mType->info.targetDistance >= 4) { + const Position& myPos = getPosition(); + const Position targetPos = attackedCreature->getPosition(); + + if (Position::getDistanceX(myPos, targetPos) == 4 || Position::getDistanceY(myPos, targetPos) == 4) { + int32_t currentX = myPos.x; + int32_t currentY = myPos.y; + int32_t danceRandom = rand(); + int32_t danceRandomResult = danceRandom % 5; + + if (danceRandom % 5 == 1) { + direction = DIRECTION_EAST; + currentX++; + } else if (danceRandomResult <= 1) { + if (danceRandom == 5 * (danceRandom / 5)) { + direction = DIRECTION_WEST; + currentX--; + } + } else if (danceRandomResult == 2) { + direction = DIRECTION_NORTH; + currentY--; + } else if (danceRandomResult == 3) { + direction = DIRECTION_SOUTH; + currentY++; + } + + if (danceRandomResult <= 3 && canWalkTo(myPos, direction)) { + int32_t xTest = targetPos.x - currentX; + if (currentX - targetPos.x > -1) { + xTest = currentX - targetPos.x; + } + + int32_t yTest = targetPos.y - currentY; + if (currentY - targetPos.y > -1) { + yTest = currentY - targetPos.y; + } + + int32_t realTest = yTest; + + if (xTest >= yTest) { + realTest = xTest; + } + + if (realTest == 4) { + result = true; + egibleToDance = false; + earliestWakeUpTime = OTSYS_TIME() + 1000; + earliestDanceTime = OTSYS_TIME() + 1000 + getStepDuration(); + earliestAttackTime += 200; + } + } + } + } else { + const Position& myPos = getPosition(); + const Position targetPos = attackedCreature->getPosition(); + + if (Position::areInRange<1, 1>(myPos, targetPos)) { + int32_t danceRandom = rand(); + int32_t danceRandomResult = danceRandom % 5; + + int32_t currentX = myPos.x; + int32_t currentY = myPos.y; + + if (danceRandom % 5 == 1) { + direction = DIRECTION_EAST; + currentX++; + } else if (danceRandomResult <= 1) { + if (danceRandom == 5 * (danceRandom / 5)) { + direction = DIRECTION_WEST; + currentX--; + } + } else if (danceRandomResult == 2) { + direction = DIRECTION_NORTH; + currentY--; + } else if (danceRandomResult == 3) { + direction = DIRECTION_SOUTH; + currentY++; + } + + Position position = myPos; + position.x = currentX; + position.y = currentY; + + if (danceRandomResult <= 3 && + canWalkTo(myPos, direction) && + Position::areInRange<1, 1>(position, targetPos)) { + result = true; + egibleToDance = false; + earliestWakeUpTime = OTSYS_TIME() + 1000; + earliestDanceTime = OTSYS_TIME() + 1000 + getStepDuration(); + earliestAttackTime += 200; + } + } + } } } } @@ -1343,7 +1480,7 @@ bool Monster::getDistanceStep(const Position& targetPos, Direction& direction, b //escape to NW , W or N [and some extra] bool w = canWalkTo(creaturePos, DIRECTION_WEST); bool n = canWalkTo(creaturePos, DIRECTION_NORTH); - + if (w && n) { direction = boolean_random() ? DIRECTION_WEST : DIRECTION_NORTH; return true; @@ -1776,7 +1913,8 @@ void Monster::death(Creature*) for (Creature* summon : summons) { summon->changeHealth(-summon->getHealth()); - summon->removeMaster(); + summon->setMaster(nullptr); + summon->decrementReferenceCounter(); } summons.clear(); @@ -1855,61 +1993,86 @@ void Monster::updateLookDirection() //look EAST/WEST if (offsetx < 0) { newDir = DIRECTION_WEST; - } else { + } + else { newDir = DIRECTION_EAST; } - } else if (dx < dy) { + } + else if (dx < dy) { //look NORTH/SOUTH if (offsety < 0) { newDir = DIRECTION_NORTH; - } else { + } + else { newDir = DIRECTION_SOUTH; } - } else { + } + else { Direction dir = getDirection(); if (offsetx < 0 && offsety < 0) { + if (offsetx == -1 && offsety == -1) { + if (dir == DIRECTION_NORTH) { + newDir = DIRECTION_WEST; + } + } if (dir == DIRECTION_SOUTH) { newDir = DIRECTION_WEST; - } else if (dir == DIRECTION_NORTH) { - newDir = DIRECTION_WEST; - } else if (dir == DIRECTION_EAST) { + } + else if (dir == DIRECTION_EAST) { newDir = DIRECTION_NORTH; } - } else if (offsetx < 0 && offsety > 0) { + } + else if (offsetx < 0 && offsety > 0) { + if (offsetx == -1 && offsety == 1) { + if (dir == DIRECTION_SOUTH) { + newDir = DIRECTION_WEST; + } + } if (dir == DIRECTION_NORTH) { newDir = DIRECTION_WEST; - } else if (dir == DIRECTION_SOUTH) { - newDir = DIRECTION_WEST; - } else if (dir == DIRECTION_EAST) { + } + else if (dir == DIRECTION_EAST) { newDir = DIRECTION_SOUTH; } - } else if (offsetx > 0 && offsety < 0) { + } + else if (offsetx > 0 && offsety < 0) { + if (offsetx == 1 && offsety == -1) { + if (dir == DIRECTION_NORTH) { + newDir = DIRECTION_EAST; + } + } if (dir == DIRECTION_SOUTH) { newDir = DIRECTION_EAST; - } else if (dir == DIRECTION_NORTH) { - newDir = DIRECTION_EAST; - } else if (dir == DIRECTION_WEST) { + } + else if (dir == DIRECTION_WEST) { newDir = DIRECTION_NORTH; } - } else { + } + else { + if (offsetx == 1 && offsety == 1) { + if (dir == DIRECTION_SOUTH) { + newDir = DIRECTION_EAST; + } + } if (dir == DIRECTION_NORTH) { newDir = DIRECTION_EAST; - } else if (dir == DIRECTION_SOUTH) { - newDir = DIRECTION_EAST; - } else if (dir == DIRECTION_WEST) { + } + else if (dir == DIRECTION_WEST) { newDir = DIRECTION_SOUTH; } } } } - g_game.internalCreatureTurn(this, newDir); + if (direction != newDir) { + g_game.internalCreatureTurn(this, newDir); + } } void Monster::dropLoot(Container* corpse, Creature*) { if (corpse && lootDrop) { - g_events->eventMonsterOnDropLoot(this, corpse); + mType->createLoot(corpse); } } @@ -1921,12 +2084,6 @@ void Monster::setNormalCreatureLight() void Monster::drainHealth(Creature* attacker, int32_t damage) { Creature::drainHealth(attacker, damage); - - if (damage > 0 && randomStepping) { - ignoreFieldDamage = true; - updateMapCache(); - } - if (isInvisible()) { removeCondition(CONDITION_INVISIBLE); } @@ -1947,12 +2104,70 @@ bool Monster::challengeCreature(Creature* creature) bool result = selectTarget(creature); if (result) { - targetChangeCooldown = 8000; + targetChangeCooldown = 1000; targetChangeTicks = 0; } return result; } +bool Monster::convinceCreature(Creature* creature) +{ + Player* player = creature->getPlayer(); + if (player && !player->hasFlag(PlayerFlag_CanConvinceAll)) { + if (!mType->info.isConvinceable) { + return false; + } + } + + if (isSummon()) { + if (getMaster() == creature) { + return false; + } + + Creature* oldMaster = getMaster(); + oldMaster->removeSummon(this); + } + + creature->addSummon(this); + + setFollowCreature(nullptr); + setAttackedCreature(nullptr); + + //destroy summons + for (Creature* summon : summons) { + summon->changeHealth(-summon->getHealth()); + summon->setMaster(nullptr); + summon->decrementReferenceCounter(); + } + summons.clear(); + + isMasterInRange = true; + updateTargetList(); + updateIdleStatus(); + + //Notify surrounding about the change + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), true); + g_game.map.getSpectators(list, creature->getPosition(), true); + for (Creature* spectator : list) { + spectator->onCreatureConvinced(creature, this); + } + + if (spawn) { + spawn->removeMonster(this); + spawn = nullptr; + } + return true; +} + +void Monster::onCreatureConvinced(const Creature* convincer, const Creature* creature) +{ + if (convincer != this && (isFriend(creature) || isOpponent(creature))) { + updateTargetList(); + updateIdleStatus(); + } +} + void Monster::getPathSearchParams(const Creature* creature, FindPathParams& fpp) const { Creature::getPathSearchParams(creature, fpp); diff --git a/src/monster.h b/src/monster.h index 6d9f94f..8447df7 100644 --- a/src/monster.h +++ b/src/monster.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,15 +26,17 @@ class Creature; class Game; class Spawn; +class Combat; -using CreatureHashSet = std::unordered_set; -using CreatureList = std::list; +typedef std::unordered_set CreatureHashSet; +typedef std::list CreatureList; enum TargetSearchType_t { - TARGETSEARCH_DEFAULT, + TARGETSEARCH_ANY, TARGETSEARCH_RANDOM, - TARGETSEARCH_ATTACKRANGE, TARGETSEARCH_NEAREST, + TARGETSEARCH_WEAKEST, + TARGETSEARCH_MOSTDAMAGE, }; class Monster final : public Creature @@ -44,43 +46,39 @@ class Monster final : public Creature static int32_t despawnRange; static int32_t despawnRadius; - explicit Monster(MonsterType* mType); + explicit Monster(MonsterType* mtype); ~Monster(); // non-copyable Monster(const Monster&) = delete; Monster& operator=(const Monster&) = delete; - Monster* getMonster() override { + Monster* getMonster() final { return this; } - const Monster* getMonster() const override { + const Monster* getMonster() const final { return this; } - void setID() override { + void setID() final { if (id == 0) { id = monsterAutoID++; } } - void removeList() override; - void addList() override; + void removeList() final; + void addList() final; - const std::string& getName() const override { + const std::string& getName() const final { return mType->name; } - const std::string& getNameDescription() const override { + const std::string& getNameDescription() const final { return mType->nameDescription; } - std::string getDescription(int32_t) const override { + std::string getDescription(int32_t) const final { return strDescription + '.'; } - CreatureType_t getType() const override { - return CREATURETYPE_MONSTER; - } - const Position& getMasterPos() const { return masterPos; } @@ -88,19 +86,22 @@ class Monster final : public Creature masterPos = pos; } - RaceType_t getRace() const override { + RaceType_t getRace() const final { return mType->info.race; } - int32_t getArmor() const override { - return mType->info.armor; + 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() const override { - return mType->info.defense; - } - bool isPushable() const override { + int32_t getDefense() final; + bool isPushable() const final { return mType->info.pushable && baseSpeed != 0; } - bool isAttackable() const override { + bool isAttackable() const final { return mType->info.isAttackable; } @@ -113,8 +114,8 @@ class Monster final : public Creature bool isHostile() const { return mType->info.isHostile; } - bool canSee(const Position& pos) const override; - bool canSeeInvisibility() const override { + bool canSee(const Position& pos) const final; + bool canSeeInvisibility() const final { return isImmune(CONDITION_INVISIBLE); } uint32_t getManaCost() const { @@ -123,35 +124,31 @@ class Monster final : public Creature void setSpawn(Spawn* spawn) { this->spawn = spawn; } - bool canWalkOnFieldType(CombatType_t combatType) const; + void onAttackedCreature(Creature* creature) final; - void onAttackedCreatureDisappear(bool isLogout) override; + 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 onCreatureAppear(Creature* creature, bool isLogin) override; - void onRemoveCreature(Creature* creature, bool isLogout) override; - void onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, const Tile* oldTile, const Position& oldPos, bool teleport) override; - void onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) override; + 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 drainHealth(Creature* attacker, int32_t damage) override; - void changeHealth(int32_t healthChange, bool sendHealthChange = true) override; - void onWalk() override; - bool getNextStep(Direction& direction, uint32_t& flags) override; - void onFollowCreatureComplete(const Creature* creature) override; + void onThink(uint32_t interval) final; - void onThink(uint32_t interval) override; + bool challengeCreature(Creature* creature) final; + bool convinceCreature(Creature* creature) final; - bool challengeCreature(Creature* creature) override; + void setNormalCreatureLight() final; + bool getCombatValues(int32_t& min, int32_t& max) final; - void setNormalCreatureLight() override; - bool getCombatValues(int32_t& min, int32_t& max) override; + void doAttacking(uint32_t interval) final; - void doAttacking(uint32_t interval) override; - bool hasExtraSwing() override { - return extraMeleeAttack; - } - - bool searchTarget(TargetSearchType_t searchType = TARGETSEARCH_DEFAULT); + bool searchTarget(TargetSearchType_t searchType); bool selectTarget(Creature* creature); const CreatureList& getTargetList() const { @@ -170,12 +167,9 @@ class Monster final : public Creature bool isTargetNearby() const { return stepDuration >= 1; } - bool isIgnoringFieldDamage() const { - return ignoreFieldDamage; - } BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, - bool checkDefense = false, bool checkArmor = false, bool field = false) override; + bool checkDefense = false, bool checkArmor = false, bool field = false); static uint32_t monsterAutoID; @@ -188,13 +182,13 @@ class Monster final : public Creature MonsterType* mType; Spawn* spawn = nullptr; - int64_t lastMeleeAttack = 0; + int64_t lifetime = 0; + int64_t nextDanceStepRound = 0; + int64_t earliestAttackTime = 0; + int64_t earliestWakeUpTime = 0; + int64_t earliestDanceTime = 0; - uint32_t attackTicks = 0; - uint32_t targetTicks = 0; uint32_t targetChangeTicks = 0; - uint32_t defenseTicks = 0; - uint32_t yellTicks = 0; int32_t minCombatValue = 0; int32_t maxCombatValue = 0; int32_t targetChangeCooldown = 0; @@ -203,10 +197,8 @@ class Monster final : public Creature Position masterPos; bool isIdle = true; - bool extraMeleeAttack = false; bool isMasterInRange = false; - bool randomStepping = false; - bool ignoreFieldDamage = false; + bool egibleToDance = true; void onCreatureEnter(Creature* creature); void onCreatureLeave(Creature* creature); @@ -223,8 +215,8 @@ class Monster final : public Creature void clearTargetList(); void clearFriendList(); - void death(Creature* lastHitCreature) override; - Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) override; + void death(Creature* lastHitCreature) final; + Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) final; void setIdle(bool idle); void updateIdleStatus(); @@ -232,12 +224,11 @@ class Monster final : public Creature return isIdle; } - void onAddCondition(ConditionType_t type) override; - void onEndCondition(ConditionType_t type) override; + 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 canUseSpell(const Position& pos, const Position& targetPos, - const spellBlock_t& sb, uint32_t interval, bool& inRange, bool& resetTicks); bool getRandomStep(const Position& creaturePos, Direction& direction) const; bool getDanceStep(const Position& creaturePos, Direction& direction, bool keepAttack = true, bool keepDistance = true); @@ -256,25 +247,28 @@ class Monster final : public Creature bool isFriend(const Creature* creature) const; bool isOpponent(const Creature* creature) const; - uint64_t getLostExperience() const override { + uint64_t getLostExperience() const final { return skillLoss ? mType->info.experience : 0; } - uint16_t getLookCorpse() const override { + uint16_t getLookCorpse() const final { return mType->info.lookcorpse; } - void dropLoot(Container* corpse, Creature* lastHitCreature) override; - uint32_t getDamageImmunities() const override { + void dropLoot(Container* corpse, Creature* lastHitCreature) final; + uint32_t getDamageImmunities() const final { return mType->info.damageImmunities; } - uint32_t getConditionImmunities() const override { + uint32_t getConditionImmunities() const final { return mType->info.conditionImmunities; } - void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const override; - bool useCacheMap() const override { - return !randomStepping; + 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 diff --git a/src/monsters.cpp b/src/monsters.cpp index c135872..ca294e2 100644 --- a/src/monsters.cpp +++ b/src/monsters.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,7 +23,6 @@ #include "monster.h" #include "spells.h" #include "combat.h" -#include "weapons.h" #include "configmanager.h" #include "game.h" @@ -41,24 +40,158 @@ spellBlock_t::~spellBlock_t() } } -void MonsterType::loadLoot(MonsterType* monsterType, LootBlock lootBlock) +uint32_t Monsters::getLootRandom() { - if (lootBlock.childLoot.empty()) { - bool isContainer = Item::items[lootBlock.id].isContainer(); - if (isContainer) { - for (LootBlock child : lootBlock.childLoot) { - lootBlock.childLoot.push_back(child); + return uniform_random(0, MAX_LOOTCHANCE); +} + +void MonsterType::createLoot(Container* corpse) +{ + if (g_config.getNumber(ConfigManager::RATE_LOOT) == 0) { + corpse->startDecaying(); + return; + } + + Item* bagItem = Item::CreateItem(2853, 1); + if (!bagItem) { + return; + } + + Container* bagContainer = bagItem->getContainer(); + if (!bagContainer) { + return; + } + + if (g_game.internalAddItem(corpse, bagItem) != RETURNVALUE_NOERROR) { + corpse->internalAddThing(bagItem); + } + + bool includeBagLoot = false; + for (auto it = info.lootItems.rbegin(), end = info.lootItems.rend(); it != end; ++it) { + std::vector itemList = createLootItem(*it); + if (itemList.empty()) { + continue; + } + + for (Item* item : itemList) { + //check containers + if (Container* container = item->getContainer()) { + if (!createLootContainer(container, *it)) { + delete container; + continue; + } + } + + const ItemType& itemType = Item::items[item->getID()]; + if (itemType.weaponType != WEAPON_NONE || + itemType.stopTime || + itemType.decayTime) { + includeBagLoot = true; + if (g_game.internalAddItem(bagContainer, item) != RETURNVALUE_NOERROR) { + corpse->internalAddThing(item); + } + } else { + if (g_game.internalAddItem(corpse, item) != RETURNVALUE_NOERROR) { + corpse->internalAddThing(item); + } } } - monsterType->info.lootItems.push_back(lootBlock); - } else { - monsterType->info.lootItems.push_back(lootBlock); } + + if (!includeBagLoot) { + g_game.internalRemoveItem(bagItem); + } + + if (g_config.getBoolean(ConfigManager::SHOW_MONSTER_LOOT)) { + Player* owner = g_game.getPlayerByID(corpse->getCorpseOwner()); + if (owner) { + std::ostringstream ss; + ss << "Loot of " << nameDescription << ": " << corpse->getContentDescription(); + + if (owner->getParty()) { + owner->getParty()->broadcastPartyLoot(ss.str()); + } else { + owner->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + } + } + } + + corpse->startDecaying(); +} + +std::vector MonsterType::createLootItem(const LootBlock& lootBlock) +{ + int32_t itemCount = 0; + + uint32_t randvalue = Monsters::getLootRandom(); + uint32_t extraMoney = g_config.getNumber(ConfigManager::MONEY_RATE); + uint32_t countMax = lootBlock.countmax + 1; + + if (randvalue < g_config.getNumber(ConfigManager::RATE_LOOT) * lootBlock.chance) { + if (Item::items[lootBlock.id].stackable) { + if (lootBlock.id == 3031) { + countMax *= extraMoney; + } + + itemCount = randvalue % countMax; + } else { + itemCount = 1; + } + } + + std::vector itemList; + while (itemCount > 0) { + uint16_t n = static_cast(std::min(itemCount, 100)); + Item* tmpItem = Item::CreateItem(lootBlock.id, n); + if (!tmpItem) { + break; + } + + itemCount -= n; + + if (lootBlock.subType != -1) { + tmpItem->setSubType(lootBlock.subType); + } + + if (lootBlock.actionId != -1) { + tmpItem->setActionId(lootBlock.actionId); + } + + if (!lootBlock.text.empty()) { + tmpItem->setText(lootBlock.text); + } + + itemList.push_back(tmpItem); + } + return itemList; +} + +bool MonsterType::createLootContainer(Container* parent, const LootBlock& lootblock) +{ + auto it = lootblock.childLoot.begin(), end = lootblock.childLoot.end(); + if (it == end) { + return true; + } + + for (; it != end && parent->size() < parent->capacity(); ++it) { + auto itemList = createLootItem(*it); + for (Item* tmpItem : itemList) { + if (Container* container = tmpItem->getContainer()) { + if (!createLootContainer(container, *it)) { + delete container; + } else { + parent->internalAddThing(container); + } + } else { + parent->internalAddThing(tmpItem); + } + } + } + return !parent->empty(); } bool Monsters::loadFromXml(bool reloading /*= false*/) { - unloadedMonsters = {}; pugi::xml_document doc; pugi::xml_parse_result result = doc.load_file("data/monster/monsters.xml"); if (!result) { @@ -68,13 +201,30 @@ bool Monsters::loadFromXml(bool reloading /*= false*/) loaded = true; + std::list> monsterScriptList; for (auto monsterNode : doc.child("monsters").children()) { - std::string name = asLowerCaseString(monsterNode.attribute("name").as_string()); - std::string file = "data/monster/" + std::string(monsterNode.attribute("file").as_string()); - if (reloading && monsters.find(name) != monsters.end()) { - loadMonster(file, name, true); - } else { - unloadedMonsters.emplace(name, file); + loadMonster("data/monster/" + std::string(monsterNode.attribute("file").as_string()), monsterNode.attribute("name").as_string(), monsterScriptList, reloading); + } + + if (!monsterScriptList.empty()) { + if (!scriptInterface) { + scriptInterface.reset(new LuaScriptInterface("Monster Interface")); + scriptInterface->initState(); + } + + for (const auto& scriptEntry : monsterScriptList) { + MonsterType* mType = scriptEntry.first; + if (scriptInterface->loadFile("data/monster/scripts/" + scriptEntry.second) == 0) { + mType->info.scriptInterface = scriptInterface.get(); + mType->info.creatureAppearEvent = scriptInterface->getEvent("onCreatureAppear"); + mType->info.creatureDisappearEvent = scriptInterface->getEvent("onCreatureDisappear"); + mType->info.creatureMoveEvent = scriptInterface->getEvent("onCreatureMove"); + mType->info.creatureSayEvent = scriptInterface->getEvent("onCreatureSay"); + mType->info.thinkEvent = scriptInterface->getEvent("onThink"); + } else { + std::cout << "[Warning - Monsters::loadMonster] Can not load script: " << scriptEntry.second << std::endl; + std::cout << scriptInterface->getLastLuaError() << std::endl; + } } } return true; @@ -89,15 +239,13 @@ bool Monsters::reload() return loadFromXml(true); } -ConditionDamage* Monsters::getDamageCondition(ConditionType_t conditionType, - int32_t maxDamage, int32_t minDamage, int32_t startDamage, uint32_t tickInterval) +ConditionDamage* Monsters::getDamageCondition(ConditionType_t conditionType, int32_t cycle, int32_t count, int32_t max_count) { ConditionDamage* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, 0, 0)); - condition->setParam(CONDITION_PARAM_TICKINTERVAL, tickInterval); - condition->setParam(CONDITION_PARAM_MINVALUE, minDamage); - condition->setParam(CONDITION_PARAM_MAXVALUE, maxDamage); - condition->setParam(CONDITION_PARAM_STARTVALUE, startDamage); - condition->setParam(CONDITION_PARAM_DELAYED, 1); + + condition->setParam(CONDITION_PARAM_CYCLE, cycle); + condition->setParam(CONDITION_PARAM_COUNT, count); + condition->setParam(CONDITION_PARAM_MAX_COUNT, max_count); return condition; } @@ -118,15 +266,16 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co return false; } - if ((attr = node.attribute("speed")) || (attr = node.attribute("interval"))) { - sb.speed = std::max(1, pugi::cast(attr.value())); - } - if ((attr = node.attribute("chance"))) { uint32_t chance = pugi::cast(attr.value()); if (chance > 100) { chance = 100; } + + if (chance == 0) { + std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - Spell chance is zero: " << name << std::endl; + } + sb.chance = chance; } @@ -136,6 +285,8 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co range = Map::maxViewportX * 2; } sb.range = range; + } else { + sb.range = Map::maxClientViewportX; } if ((attr = node.attribute("min"))) { @@ -217,88 +368,22 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co std::string tmpName = asLowerCaseString(name); - if (tmpName == "melee") { - sb.isMelee = true; - - pugi::xml_attribute attackAttribute, skillAttribute; - if ((attackAttribute = node.attribute("attack")) && (skillAttribute = node.attribute("skill"))) { - sb.minCombatValue = 0; - sb.maxCombatValue = -Weapons::getMaxMeleeDamage(pugi::cast(skillAttribute.value()), pugi::cast(attackAttribute.value())); - } - - ConditionType_t conditionType = CONDITION_NONE; - int32_t minDamage = 0; - int32_t maxDamage = 0; - uint32_t tickInterval = 2000; - - if ((attr = node.attribute("fire"))) { - conditionType = CONDITION_FIRE; - - minDamage = pugi::cast(attr.value()); - maxDamage = minDamage; - tickInterval = 9000; - } else if ((attr = node.attribute("poison"))) { - conditionType = CONDITION_POISON; - - minDamage = pugi::cast(attr.value()); - maxDamage = minDamage; - tickInterval = 4000; - } else if ((attr = node.attribute("energy"))) { - conditionType = CONDITION_ENERGY; - - minDamage = pugi::cast(attr.value()); - maxDamage = minDamage; - tickInterval = 10000; - } else if ((attr = node.attribute("drown"))) { - conditionType = CONDITION_DROWN; - - minDamage = pugi::cast(attr.value()); - maxDamage = minDamage; - tickInterval = 5000; - } else if ((attr = node.attribute("freeze"))) { - conditionType = CONDITION_FREEZING; - - minDamage = pugi::cast(attr.value()); - maxDamage = minDamage; - tickInterval = 8000; - } else if ((attr = node.attribute("dazzle"))) { - conditionType = CONDITION_DAZZLED; - - minDamage = pugi::cast(attr.value()); - maxDamage = minDamage; - tickInterval = 10000; - } else if ((attr = node.attribute("curse"))) { - conditionType = CONDITION_CURSED; - - minDamage = pugi::cast(attr.value()); - maxDamage = minDamage; - tickInterval = 4000; - } else if ((attr = node.attribute("bleed")) || (attr = node.attribute("physical"))) { - conditionType = CONDITION_BLEEDING; - tickInterval = 4000; - } - - if ((attr = node.attribute("tick"))) { - int32_t value = pugi::cast(attr.value()); - if (value > 0) { - tickInterval = value; - } - } - - if (conditionType != CONDITION_NONE) { - Condition* condition = getDamageCondition(conditionType, maxDamage, minDamage, 0, tickInterval); - combat->addCondition(condition); - } - - sb.range = 1; + if (tmpName == "physical") { combat->setParam(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE); combat->setParam(COMBAT_PARAM_BLOCKARMOR, 1); combat->setParam(COMBAT_PARAM_BLOCKSHIELD, 1); - combat->setOrigin(ORIGIN_MELEE); - } else if (tmpName == "physical") { - combat->setParam(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE); - combat->setParam(COMBAT_PARAM_BLOCKARMOR, 1); - combat->setOrigin(ORIGIN_RANGED); + uint32_t tD = this->getMonsterType(description)->info.targetDistance; + if (tD == 1) { + if (sb.range > 1) { + combat->setOrigin(ORIGIN_RANGED); + } + else { + combat->setOrigin(ORIGIN_MELEE); + } + } + else if (tD > 1 && sb.range > 1) { + combat->setOrigin(ORIGIN_RANGED); + } } else if (tmpName == "bleed") { combat->setParam(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE); } else if (tmpName == "poison" || tmpName == "earth") { @@ -309,12 +394,6 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co combat->setParam(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE); } else if (tmpName == "drown") { combat->setParam(COMBAT_PARAM_TYPE, COMBAT_DROWNDAMAGE); - } else if (tmpName == "ice") { - combat->setParam(COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE); - } else if (tmpName == "holy") { - combat->setParam(COMBAT_PARAM_TYPE, COMBAT_HOLYDAMAGE); - } else if (tmpName == "death") { - combat->setParam(COMBAT_PARAM_TYPE, COMBAT_DEATHDAMAGE); } else if (tmpName == "lifedrain") { combat->setParam(COMBAT_PARAM_TYPE, COMBAT_LIFEDRAIN); } else if (tmpName == "manadrain") { @@ -324,6 +403,7 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); } else if (tmpName == "speed") { int32_t speedChange = 0; + int32_t variation = 0; int32_t duration = 10000; if ((attr = node.attribute("duration"))) { @@ -332,10 +412,10 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co if ((attr = node.attribute("speedchange"))) { speedChange = pugi::cast(attr.value()); - if (speedChange < -1000) { - //cant be slower than 100% - speedChange = -1000; - } + } + + if ((attr = node.attribute("variation"))) { + variation = pugi::cast(attr.value()); } ConditionType_t conditionType; @@ -347,8 +427,9 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co } ConditionSpeed* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, duration, 0)); - condition->setFormulaVars(speedChange / 1000.0, 0, speedChange / 1000.0, 0); - combat->addCondition(condition); + condition->setVariation(variation); + condition->setSpeedDelta(speedChange); + combat->setCondition(condition); } else if (tmpName == "outfit") { int32_t duration = 10000; @@ -362,7 +443,7 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co ConditionOutfit* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0)); condition->setOutfit(mType->info.outfit); combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); - combat->addCondition(condition); + combat->setCondition(condition); } } else if ((attr = node.attribute("item"))) { Outfit_t outfit; @@ -371,7 +452,7 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co ConditionOutfit* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0)); condition->setOutfit(outfit); combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); - combat->addCondition(condition); + combat->setCondition(condition); } } else if (tmpName == "invisible") { int32_t duration = 10000; @@ -382,7 +463,7 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_INVISIBLE, duration, 0); combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); - combat->addCondition(condition); + combat->setCondition(condition); } else if (tmpName == "drunk") { int32_t duration = 10000; @@ -391,7 +472,7 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co } Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_DRUNK, duration, 0); - combat->addCondition(condition); + combat->setCondition(condition); } else if (tmpName == "firefield") { combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_FIREFIELD_PVP_FULL); } else if (tmpName == "poisonfield") { @@ -401,59 +482,42 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co } else if (tmpName == "firecondition" || tmpName == "energycondition" || tmpName == "earthcondition" || tmpName == "poisoncondition" || tmpName == "icecondition" || tmpName == "freezecondition" || - tmpName == "deathcondition" || tmpName == "cursecondition" || - tmpName == "holycondition" || tmpName == "dazzlecondition" || - tmpName == "drowncondition" || tmpName == "bleedcondition" || - tmpName == "physicalcondition") { + tmpName == "physicalcondition" || tmpName == "drowncondition") { ConditionType_t conditionType = CONDITION_NONE; - uint32_t tickInterval = 2000; if (tmpName == "firecondition") { conditionType = CONDITION_FIRE; - tickInterval = 10000; } else if (tmpName == "poisoncondition" || tmpName == "earthcondition") { conditionType = CONDITION_POISON; - tickInterval = 4000; } else if (tmpName == "energycondition") { conditionType = CONDITION_ENERGY; - tickInterval = 10000; } else if (tmpName == "drowncondition") { conditionType = CONDITION_DROWN; - tickInterval = 5000; - } else if (tmpName == "freezecondition" || tmpName == "icecondition") { - conditionType = CONDITION_FREEZING; - tickInterval = 10000; - } else if (tmpName == "cursecondition" || tmpName == "deathcondition") { - conditionType = CONDITION_CURSED; - tickInterval = 4000; - } else if (tmpName == "dazzlecondition" || tmpName == "holycondition") { - conditionType = CONDITION_DAZZLED; - tickInterval = 10000; - } else if (tmpName == "physicalcondition" || tmpName == "bleedcondition") { - conditionType = CONDITION_BLEEDING; - tickInterval = 4000; } - if ((attr = node.attribute("tick"))) { - int32_t value = pugi::cast(attr.value()); - if (value > 0) { - tickInterval = value; - } + int32_t cycle = 0; + if ((attr = node.attribute("count"))) { + cycle = std::abs(pugi::cast(attr.value())); + } else { + std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - missing count attribute" << std::endl; + delete combat; + return false; } - int32_t minDamage = std::abs(sb.minCombatValue); - int32_t maxDamage = std::abs(sb.maxCombatValue); - int32_t startDamage = 0; + int32_t count = 0; - if ((attr = node.attribute("start"))) { - int32_t value = std::abs(pugi::cast(attr.value())); - if (value <= minDamage) { - startDamage = value; - } + if (conditionType == CONDITION_POISON) { + count = 3; + } else if (conditionType == CONDITION_FIRE) { + count = 8; + cycle /= 10; + } else if (conditionType == CONDITION_ENERGY) { + count = 10; + cycle /= 20; } - Condition* condition = getDamageCondition(conditionType, maxDamage, minDamage, startDamage, tickInterval); - combat->addCondition(condition); + Condition* condition = getDamageCondition(conditionType, cycle, count, count); + combat->setCondition(condition); } else if (tmpName == "strength") { // } else if (tmpName == "effect") { @@ -465,6 +529,7 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co } combat->setPlayerCombatValues(COMBAT_FORMULA_DAMAGE, sb.minCombatValue, 0, sb.maxCombatValue, 0); + combatSpell = new CombatSpell(combat, needTarget, needDirection); for (auto attributeNode : node.children()) { @@ -472,7 +537,7 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co const char* value = attr.value(); if (strcasecmp(value, "shooteffect") == 0) { if ((attr = attributeNode.attribute("value"))) { - ShootType_t shoot = getShootType(asLowerCaseString(attr.as_string())); + ShootType_t shoot = getShootType(attr.as_string()); if (shoot != CONST_ANI_NONE) { combat->setParam(COMBAT_PARAM_DISTANCEEFFECT, shoot); } else { @@ -481,7 +546,7 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co } } else if (strcasecmp(value, "areaeffect") == 0) { if ((attr = attributeNode.attribute("value"))) { - MagicEffectClasses effect = getMagicEffect(asLowerCaseString(attr.as_string())); + MagicEffectClasses effect = getMagicEffect(attr.as_string()); if (effect != CONST_ME_NONE) { combat->setParam(COMBAT_PARAM_EFFECT, effect); } else { @@ -502,269 +567,39 @@ bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, co return true; } -bool Monsters::deserializeSpell(MonsterSpell* spell, spellBlock_t& sb, const std::string& description) -{ - if (!spell->scriptName.empty()) { - spell->isScripted = true; - } else if (!spell->name.empty()) { - spell->isScripted = false; - } else { - return false; - } - - sb.speed = spell->interval; - - if (spell->chance > 100) { - sb.chance = 100; - } else { - sb.chance = spell->chance; - } - - if (spell->range > (Map::maxViewportX * 2)) { - spell->range = Map::maxViewportX * 2; - } - sb.range = spell->range; - - sb.minCombatValue = spell->minCombatValue; - sb.maxCombatValue = spell->maxCombatValue; - if (std::abs(sb.minCombatValue) > std::abs(sb.maxCombatValue)) { - int32_t value = sb.maxCombatValue; - sb.maxCombatValue = sb.minCombatValue; - sb.minCombatValue = value; - } - - sb.spell = g_spells->getSpellByName(spell->name); - if (sb.spell) { - return true; - } - - CombatSpell* combatSpell = nullptr; - - if (spell->isScripted) { - std::unique_ptr combatSpellPtr(new CombatSpell(nullptr, spell->needTarget, spell->needDirection)); - if (!combatSpellPtr->loadScript("data/" + g_spells->getScriptBaseName() + "/scripts/" + spell->scriptName)) { - std::cout << "cannot find file" << std::endl; - return false; - } - - if (!combatSpellPtr->loadScriptCombat()) { - return false; - } - - combatSpell = combatSpellPtr.release(); - combatSpell->getCombat()->setPlayerCombatValues(COMBAT_FORMULA_DAMAGE, sb.minCombatValue, 0, sb.maxCombatValue, 0); - } else { - std::unique_ptr combat{ new Combat }; - sb.combatSpell = true; - - if (spell->length > 0) { - spell->spread = std::max(0, spell->spread); - - AreaCombat* area = new AreaCombat(); - area->setupArea(spell->length, spell->spread); - combat->setArea(area); - - spell->needDirection = true; - } - - if (spell->radius > 0) { - AreaCombat* area = new AreaCombat(); - area->setupArea(spell->radius); - combat->setArea(area); - } - - std::string tmpName = asLowerCaseString(spell->name); - - if (tmpName == "melee") { - sb.isMelee = true; - - if (spell->attack > 0 && spell->skill > 0) { - sb.minCombatValue = 0; - sb.maxCombatValue = -Weapons::getMaxMeleeDamage(spell->skill, spell->attack); - } - - ConditionType_t conditionType = CONDITION_NONE; - int32_t minDamage = 0; - int32_t maxDamage = 0; - uint32_t tickInterval = 2000; - - if (spell->conditionType != CONDITION_NONE) { - conditionType = spell->conditionType; - - minDamage = spell->conditionMinDamage; - maxDamage = minDamage; - if (spell->tickInterval != 0) { - tickInterval = spell->tickInterval; - } - - Condition* condition = getDamageCondition(conditionType, maxDamage, minDamage, spell->conditionStartDamage, tickInterval); - combat->addCondition(condition); - } - - sb.range = 1; - combat->setParam(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE); - combat->setParam(COMBAT_PARAM_BLOCKARMOR, 1); - combat->setParam(COMBAT_PARAM_BLOCKSHIELD, 1); - combat->setOrigin(ORIGIN_MELEE); - } else if (tmpName == "combat") { - if (spell->combatType == COMBAT_PHYSICALDAMAGE) { - combat->setParam(COMBAT_PARAM_BLOCKARMOR, 1); - combat->setOrigin(ORIGIN_RANGED); - } else if (spell->combatType == COMBAT_HEALING) { - combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); - } - combat->setParam(COMBAT_PARAM_TYPE, spell->combatType); - } else if (tmpName == "speed") { - int32_t speedChange = 0; - int32_t duration = 10000; - - if (spell->duration != 0) { - duration = spell->duration; - } - - if (spell->speedChange != 0) { - speedChange = spell->speedChange; - if (speedChange < -1000) { - //cant be slower than 100% - speedChange = -1000; - } - } - - ConditionType_t conditionType; - if (speedChange > 0) { - conditionType = CONDITION_HASTE; - combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); - } else { - conditionType = CONDITION_PARALYZE; - } - - ConditionSpeed* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, duration, 0)); - condition->setFormulaVars(speedChange / 1000.0, 0, speedChange / 1000.0, 0); - combat->addCondition(condition); - } else if (tmpName == "outfit") { - int32_t duration = 10000; - - if (spell->duration != 0) { - duration = spell->duration; - } - - ConditionOutfit* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0)); - condition->setOutfit(spell->outfit); - combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); - combat->addCondition(condition); - } else if (tmpName == "invisible") { - int32_t duration = 10000; - - if (spell->duration != 0) { - duration = spell->duration; - } - - Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_INVISIBLE, duration, 0); - combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); - combat->addCondition(condition); - } else if (tmpName == "drunk") { - int32_t duration = 10000; - - if (spell->duration != 0) { - duration = spell->duration; - } - - Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_DRUNK, duration, 0); - combat->addCondition(condition); - } else if (tmpName == "firefield") { - combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_FIREFIELD_PVP_FULL); - } else if (tmpName == "poisonfield") { - combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_POISONFIELD_PVP); - } else if (tmpName == "energyfield") { - combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_ENERGYFIELD_PVP); - } else if (tmpName == "condition") { - uint32_t tickInterval = 2000; - - if (spell->conditionType == CONDITION_NONE) { - std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - Condition is not set for: " << spell->name << std::endl; - } - - if (spell->tickInterval != 0) { - int32_t value = spell->tickInterval; - if (value > 0) { - tickInterval = value; - } - } - - int32_t minDamage = std::abs(spell->conditionMinDamage); - int32_t maxDamage = std::abs(spell->conditionMaxDamage); - int32_t startDamage = 0; - - if (spell->conditionStartDamage != 0) { - int32_t value = std::abs(spell->conditionStartDamage); - if (value <= minDamage) { - startDamage = value; - } - } - - Condition* condition = getDamageCondition(spell->conditionType, maxDamage, minDamage, startDamage, tickInterval); - combat->addCondition(condition); - } else if (tmpName == "strength") { - // - } else if (tmpName == "effect") { - // - } else { - std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - Unknown spell name: " << spell->name << std::endl; - } - - if (spell->needTarget) { - if (spell->shoot != CONST_ANI_NONE) { - combat->setParam(COMBAT_PARAM_DISTANCEEFFECT, spell->shoot); - } - } - - if (spell->effect != CONST_ME_NONE) { - combat->setParam(COMBAT_PARAM_EFFECT, spell->effect); - } - - combat->setPlayerCombatValues(COMBAT_FORMULA_DAMAGE, sb.minCombatValue, 0, sb.maxCombatValue, 0); - combatSpell = new CombatSpell(combat.release(), spell->needTarget, spell->needDirection); - } - - sb.spell = combatSpell; - if (combatSpell) { - sb.combatSpell = true; - } - return true; -} - -MonsterType* Monsters::loadMonster(const std::string& file, const std::string& monsterName, bool reloading /*= false*/) +bool Monsters::loadMonster(const std::string& file, const std::string& monsterName, std::list>& monsterScriptList, bool reloading /*= false*/) { MonsterType* mType = nullptr; + bool new_mType = true; pugi::xml_document doc; pugi::xml_parse_result result = doc.load_file(file.c_str()); if (!result) { printXMLError("Error - Monsters::loadMonster", file, result); - return nullptr; + return false; } pugi::xml_node monsterNode = doc.child("monster"); if (!monsterNode) { std::cout << "[Error - Monsters::loadMonster] Missing monster node in: " << file << std::endl; - return nullptr; + return false; } pugi::xml_attribute attr; if (!(attr = monsterNode.attribute("name"))) { std::cout << "[Error - Monsters::loadMonster] Missing name in: " << file << std::endl; - return nullptr; + return false; } if (reloading) { - auto it = monsters.find(asLowerCaseString(monsterName)); - if (it != monsters.end()) { - mType = &it->second; + mType = getMonsterType(monsterName); + if (mType != nullptr) { + new_mType = false; mType->info = {}; } } - if (!mType) { + if (new_mType) { mType = &monsters[asLowerCaseString(monsterName)]; } @@ -787,8 +622,6 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m mType->info.race = RACE_UNDEAD; } else if (tmpStrValue == "fire" || tmpInt == 4) { mType->info.race = RACE_FIRE; - } else if (tmpStrValue == "energy" || tmpInt == 5) { - mType->info.race = RACE_ENERGY; } else { std::cout << "[Warning - Monsters::loadMonster] Unknown race type " << attr.as_string() << ". " << file << std::endl; } @@ -807,27 +640,11 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m } if ((attr = monsterNode.attribute("skull"))) { - mType->info.skull = getSkullType(asLowerCaseString(attr.as_string())); + mType->info.skull = getSkullType(attr.as_string()); } if ((attr = monsterNode.attribute("script"))) { - if (!scriptInterface) { - scriptInterface.reset(new LuaScriptInterface("Monster Interface")); - scriptInterface->initState(); - } - - std::string script = attr.as_string(); - if (scriptInterface->loadFile("data/monster/scripts/" + script) == 0) { - mType->info.scriptInterface = scriptInterface.get(); - mType->info.creatureAppearEvent = scriptInterface->getEvent("onCreatureAppear"); - mType->info.creatureDisappearEvent = scriptInterface->getEvent("onCreatureDisappear"); - mType->info.creatureMoveEvent = scriptInterface->getEvent("onCreatureMove"); - mType->info.creatureSayEvent = scriptInterface->getEvent("onCreatureSay"); - mType->info.thinkEvent = scriptInterface->getEvent("onThink"); - } else { - std::cout << "[Warning - Monsters::loadMonster] Can not load script: " << script << std::endl; - std::cout << scriptInterface->getLastLuaError() << std::endl; - } + monsterScriptList.emplace_back(mType, attr.as_string()); } pugi::xml_node node; @@ -865,14 +682,6 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m mType->info.canPushItems = attr.as_bool(); } else if (strcasecmp(attrName, "canpushcreatures") == 0) { mType->info.canPushCreatures = attr.as_bool(); - } else if (strcasecmp(attrName, "staticattack") == 0) { - uint32_t staticAttack = pugi::cast(attr.value()); - if (staticAttack > 100) { - std::cout << "[Warning - Monsters::loadMonster] staticattack greater than 100. " << file << std::endl; - staticAttack = 100; - } - - mType->info.staticAttackChance = staticAttack; } else if (strcasecmp(attrName, "lightlevel") == 0) { mType->info.light.level = pugi::cast(attr.value()); } else if (strcasecmp(attrName, "lightcolor") == 0) { @@ -883,12 +692,6 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m mType->info.runAwayHealth = pugi::cast(attr.value()); } else if (strcasecmp(attrName, "hidehealth") == 0) { mType->info.hiddenHealth = attr.as_bool(); - } else if (strcasecmp(attrName, "canwalkonenergy") == 0) { - mType->info.canWalkOnEnergy = attr.as_bool(); - } else if (strcasecmp(attrName, "canwalkonfire") == 0) { - mType->info.canWalkOnFire = attr.as_bool(); - } else if (strcasecmp(attrName, "canwalkonpoison") == 0) { - mType->info.canWalkOnPoison = attr.as_bool(); } else { std::cout << "[Warning - Monsters::loadMonster] Unknown flag attribute: " << attrName << ". " << file << std::endl; } @@ -915,6 +718,34 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m } } + if ((node = monsterNode.child("targetstrategy"))) { + if ((attr = node.attribute("nearest"))) { + mType->info.strategyNearestEnemy = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing nearest enemy chance. " << file << std::endl; + } + + if ((attr = node.attribute("weakest"))) { + mType->info.strategyWeakestEnemy = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing weakest enemy chance. " << file << std::endl; + } + + if ((attr = node.attribute("mostdamage"))) { + mType->info.strategyMostDamageEnemy = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing most damage enemy chance. " << file << std::endl; + } + + if ((attr = node.attribute("random"))) { + mType->info.strategyRandomEnemy = pugi::cast(attr.value()); + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing random enemy chance. " << file << std::endl; + } + } else { + std::cout << "[Warning - Monsters::loadMonster] Missing target change strategies. " << file << std::endl; + } + if ((node = monsterNode.child("look"))) { if ((attr = node.attribute("type"))) { mType->info.outfit.lookType = pugi::cast(attr.value()); @@ -944,16 +775,24 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m std::cout << "[Warning - Monsters::loadMonster] Missing look type/typeex. " << file << std::endl; } - if ((attr = node.attribute("mount"))) { - mType->info.outfit.lookMount = pugi::cast(attr.value()); - } - if ((attr = node.attribute("corpse"))) { mType->info.lookcorpse = pugi::cast(attr.value()); } } if ((node = monsterNode.child("attacks"))) { + if ((attr = node.attribute("attack"))) { + mType->info.attack = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("skill"))) { + mType->info.skill = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("poison"))) { + mType->info.poison = pugi::cast(attr.value()); + } + for (auto attackNode : node.children()) { spellBlock_t sb; if (deserializeSpell(attackNode, sb, monsterName)) { @@ -989,29 +828,19 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m std::string tmpStrValue = asLowerCaseString(attr.as_string()); if (tmpStrValue == "physical") { mType->info.damageImmunities |= COMBAT_PHYSICALDAMAGE; - mType->info.conditionImmunities |= CONDITION_BLEEDING; } else if (tmpStrValue == "energy") { mType->info.damageImmunities |= COMBAT_ENERGYDAMAGE; mType->info.conditionImmunities |= CONDITION_ENERGY; } else if (tmpStrValue == "fire") { mType->info.damageImmunities |= COMBAT_FIREDAMAGE; mType->info.conditionImmunities |= CONDITION_FIRE; + } else if (tmpStrValue == "drown") { + mType->info.damageImmunities |= COMBAT_DROWNDAMAGE; + mType->info.conditionImmunities |= CONDITION_DROWN; } else if (tmpStrValue == "poison" || tmpStrValue == "earth") { mType->info.damageImmunities |= COMBAT_EARTHDAMAGE; mType->info.conditionImmunities |= CONDITION_POISON; - } else if (tmpStrValue == "drown") { - mType->info.damageImmunities |= COMBAT_DROWNDAMAGE; - mType->info.conditionImmunities |= CONDITION_DROWN; - } else if (tmpStrValue == "ice") { - mType->info.damageImmunities |= COMBAT_ICEDAMAGE; - mType->info.conditionImmunities |= CONDITION_FREEZING; - } else if (tmpStrValue == "holy") { - mType->info.damageImmunities |= COMBAT_HOLYDAMAGE; - mType->info.conditionImmunities |= CONDITION_DAZZLED; - } else if (tmpStrValue == "death") { - mType->info.damageImmunities |= COMBAT_DEATHDAMAGE; - mType->info.conditionImmunities |= CONDITION_CURSED; } else if (tmpStrValue == "lifedrain") { mType->info.damageImmunities |= COMBAT_LIFEDRAIN; } else if (tmpStrValue == "manadrain") { @@ -1024,15 +853,12 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m mType->info.conditionImmunities |= CONDITION_DRUNK; } else if (tmpStrValue == "invisible" || tmpStrValue == "invisibility") { mType->info.conditionImmunities |= CONDITION_INVISIBLE; - } else if (tmpStrValue == "bleed") { - mType->info.conditionImmunities |= CONDITION_BLEEDING; } else { std::cout << "[Warning - Monsters::loadMonster] Unknown immunity name " << attr.as_string() << ". " << file << std::endl; } } else if ((attr = immunityNode.attribute("physical"))) { if (attr.as_bool()) { mType->info.damageImmunities |= COMBAT_PHYSICALDAMAGE; - mType->info.conditionImmunities |= CONDITION_BLEEDING; } } else if ((attr = immunityNode.attribute("energy"))) { if (attr.as_bool()) { @@ -1044,30 +870,15 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m mType->info.damageImmunities |= COMBAT_FIREDAMAGE; mType->info.conditionImmunities |= CONDITION_FIRE; } - } else if ((attr = immunityNode.attribute("poison")) || (attr = immunityNode.attribute("earth"))) { - if (attr.as_bool()) { - mType->info.damageImmunities |= COMBAT_EARTHDAMAGE; - mType->info.conditionImmunities |= CONDITION_POISON; - } } else if ((attr = immunityNode.attribute("drown"))) { if (attr.as_bool()) { mType->info.damageImmunities |= COMBAT_DROWNDAMAGE; mType->info.conditionImmunities |= CONDITION_DROWN; } - } else if ((attr = immunityNode.attribute("ice"))) { + } else if ((attr = immunityNode.attribute("poison")) || (attr = immunityNode.attribute("earth"))) { if (attr.as_bool()) { - mType->info.damageImmunities |= COMBAT_ICEDAMAGE; - mType->info.conditionImmunities |= CONDITION_FREEZING; - } - } else if ((attr = immunityNode.attribute("holy"))) { - if (attr.as_bool()) { - mType->info.damageImmunities |= COMBAT_HOLYDAMAGE; - mType->info.conditionImmunities |= CONDITION_DAZZLED; - } - } else if ((attr = immunityNode.attribute("death"))) { - if (attr.as_bool()) { - mType->info.damageImmunities |= COMBAT_DEATHDAMAGE; - mType->info.conditionImmunities |= CONDITION_CURSED; + mType->info.damageImmunities |= COMBAT_EARTHDAMAGE; + mType->info.conditionImmunities |= CONDITION_POISON; } } else if ((attr = immunityNode.attribute("lifedrain"))) { if (attr.as_bool()) { @@ -1085,10 +896,6 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m if (attr.as_bool()) { mType->info.conditionImmunities |= CONDITION_OUTFIT; } - } else if ((attr = immunityNode.attribute("bleed"))) { - if (attr.as_bool()) { - mType->info.conditionImmunities |= CONDITION_BLEEDING; - } } else if ((attr = immunityNode.attribute("drunk"))) { if (attr.as_bool()) { mType->info.conditionImmunities |= CONDITION_DRUNK; @@ -1104,18 +911,6 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m } if ((node = monsterNode.child("voices"))) { - if ((attr = node.attribute("speed")) || (attr = node.attribute("interval"))) { - mType->info.yellSpeedTicks = pugi::cast(attr.value()); - } else { - std::cout << "[Warning - Monsters::loadMonster] Missing voices speed. " << file << std::endl; - } - - if ((attr = node.attribute("chance"))) { - mType->info.yellChance = pugi::cast(attr.value()); - } else { - std::cout << "[Warning - Monsters::loadMonster] Missing voices chance. " << file << std::endl; - } - for (auto voiceNode : node.children()) { voiceBlock_t vb; if ((attr = voiceNode.attribute("sentence"))) { @@ -1148,20 +943,12 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m for (auto elementNode : node.children()) { if ((attr = elementNode.attribute("physicalPercent"))) { mType->info.elementMap[COMBAT_PHYSICALDAMAGE] = pugi::cast(attr.value()); - } else if ((attr = elementNode.attribute("icePercent"))) { - mType->info.elementMap[COMBAT_ICEDAMAGE] = pugi::cast(attr.value()); } else if ((attr = elementNode.attribute("poisonPercent")) || (attr = elementNode.attribute("earthPercent"))) { mType->info.elementMap[COMBAT_EARTHDAMAGE] = pugi::cast(attr.value()); } else if ((attr = elementNode.attribute("firePercent"))) { mType->info.elementMap[COMBAT_FIREDAMAGE] = pugi::cast(attr.value()); } else if ((attr = elementNode.attribute("energyPercent"))) { - mType->info.elementMap[COMBAT_ENERGYDAMAGE] = pugi::cast(attr.value()); - } else if ((attr = elementNode.attribute("holyPercent"))) { - mType->info.elementMap[COMBAT_HOLYDAMAGE] = pugi::cast(attr.value()); - } else if ((attr = elementNode.attribute("deathPercent"))) { - mType->info.elementMap[COMBAT_DEATHDAMAGE] = pugi::cast(attr.value()); - } else if ((attr = elementNode.attribute("drownPercent"))) { - mType->info.elementMap[COMBAT_DROWNDAMAGE] = pugi::cast(attr.value()); + mType->info.elementMap[COMBAT_ENERGYDAMAGE] = pugi::cast(attr.value()); } else if ((attr = elementNode.attribute("lifedrainPercent"))) { mType->info.elementMap[COMBAT_LIFEDRAIN] = pugi::cast(attr.value()); } else if ((attr = elementNode.attribute("manadrainPercent"))) { @@ -1181,14 +968,9 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m for (auto summonNode : node.children()) { int32_t chance = 100; - int32_t speed = 1000; int32_t max = mType->info.maxSummons; bool force = false; - if ((attr = summonNode.attribute("speed")) || (attr = summonNode.attribute("interval"))) { - speed = std::max(1, pugi::cast(attr.value())); - } - if ((attr = summonNode.attribute("chance"))) { chance = pugi::cast(attr.value()); } @@ -1204,7 +986,6 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m if ((attr = summonNode.attribute("name"))) { summonBlock_t sb; sb.name = attr.as_string(); - sb.speed = speed; sb.chance = chance; sb.max = max; sb.force = force; @@ -1231,29 +1012,6 @@ MonsterType* Monsters::loadMonster(const std::string& file, const std::string& m mType->info.defenseSpells.shrink_to_fit(); mType->info.voiceVector.shrink_to_fit(); mType->info.scripts.shrink_to_fit(); - return mType; -} - -bool MonsterType::loadCallback(LuaScriptInterface* scriptInterface) -{ - int32_t id = scriptInterface->getEvent(); - if (id == -1) { - std::cout << "[Warning - MonsterType::loadCallback] Event not found. " << std::endl; - return false; - } - - info.scriptInterface = scriptInterface; - if (info.eventType == MONSTERS_EVENT_THINK) { - info.thinkEvent = id; - } else if (info.eventType == MONSTERS_EVENT_APPEAR) { - info.creatureAppearEvent = id; - } else if (info.eventType == MONSTERS_EVENT_DISAPPEAR) { - info.creatureDisappearEvent = id; - } else if (info.eventType == MONSTERS_EVENT_MOVE) { - info.creatureMoveEvent = id; - } else if (info.eventType == MONSTERS_EVENT_SAY) { - info.creatureSayEvent = id; - } return true; } @@ -1333,22 +1091,10 @@ void Monsters::loadLootContainer(const pugi::xml_node& node, LootBlock& lBlock) MonsterType* Monsters::getMonsterType(const std::string& name) { - std::string lowerCaseName = asLowerCaseString(name); + auto it = monsters.find(asLowerCaseString(name)); - auto it = monsters.find(lowerCaseName); if (it == monsters.end()) { - auto it2 = unloadedMonsters.find(lowerCaseName); - if (it2 == unloadedMonsters.end()) { - return nullptr; - } - - return loadMonster(it2->second, name); + return nullptr; } return &it->second; } - -void Monsters::addMonsterType(const std::string& name, MonsterType* mType) -{ - mType = &monsters[asLowerCaseString(name)]; -} - diff --git a/src/monsters.h b/src/monsters.h index 992373e..23906ab 100644 --- a/src/monsters.h +++ b/src/monsters.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,9 +22,8 @@ #include "creature.h" - -const uint32_t MAX_LOOTCHANCE = 100000; -const uint32_t MAX_STATICWALK = 100; +#define MAX_LOOTCHANCE 1000 +#define MAX_STATICWALK 100 struct LootBlock { uint16_t id; @@ -39,7 +38,7 @@ struct LootBlock { std::vector childLoot; LootBlock() { id = 0; - countmax = 1; + countmax = 0; chance = 0; subType = -1; @@ -47,21 +46,9 @@ struct LootBlock { } }; -class Loot { - public: - Loot() = default; - - // non-copyable - Loot(const Loot&) = delete; - Loot& operator=(const Loot&) = delete; - - LootBlock lootBlock; -}; - struct summonBlock_t { std::string name; uint32_t chance; - uint32_t speed; uint32_t max; bool force = false; }; @@ -75,23 +62,23 @@ struct spellBlock_t { spellBlock_t(spellBlock_t&& other) : spell(other.spell), chance(other.chance), - speed(other.speed), range(other.range), minCombatValue(other.minCombatValue), maxCombatValue(other.maxCombatValue), - combatSpell(other.combatSpell), - isMelee(other.isMelee) { + attack(other.attack), + skill(other.skill), + combatSpell(other.combatSpell) { other.spell = nullptr; } BaseSpell* spell = nullptr; uint32_t chance = 100; - uint32_t speed = 2000; uint32_t range = 0; int32_t minCombatValue = 0; int32_t maxCombatValue = 0; + int32_t attack = 0; + int32_t skill = 0; bool combatSpell = false; - bool isMelee = false; }; struct voiceBlock_t { @@ -124,14 +111,11 @@ class MonsterType uint64_t experience = 0; uint32_t manaCost = 0; - uint32_t yellChance = 0; - uint32_t yellSpeedTicks = 0; - uint32_t staticAttackChance = 95; uint32_t maxSummons = 0; uint32_t changeTargetSpeed = 0; uint32_t conditionImmunities = 0; uint32_t damageImmunities = 0; - uint32_t baseSpeed = 200; + uint32_t baseSpeed = 70; int32_t creatureAppearEvent = -1; int32_t creatureDisappearEvent = -1; @@ -143,8 +127,15 @@ class MonsterType int32_t health = 100; int32_t healthMax = 100; int32_t changeTargetChance = 0; - int32_t defense = 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; @@ -155,11 +146,6 @@ class MonsterType bool isAttackable = true; bool isHostile = true; bool hiddenHealth = false; - bool canWalkOnEnergy = true; - bool canWalkOnFire = true; - bool canWalkOnPoison = true; - - MonstersEvent_t eventType = MONSTERS_EVENT_NONE; }; public: @@ -169,57 +155,14 @@ class MonsterType MonsterType(const MonsterType&) = delete; MonsterType& operator=(const MonsterType&) = delete; - bool loadCallback(LuaScriptInterface* scriptInterface); - std::string name; std::string nameDescription; MonsterInfo info; - void loadLoot(MonsterType* monsterType, LootBlock lootblock); -}; - -class MonsterSpell -{ - public: - MonsterSpell() = default; - - MonsterSpell(const MonsterSpell&) = delete; - MonsterSpell& operator=(const MonsterSpell&) = delete; - - std::string name = ""; - std::string scriptName = ""; - - uint8_t chance = 100; - uint8_t range = 0; - - uint16_t interval = 2000; - - int32_t minCombatValue = 0; - int32_t maxCombatValue = 0; - int32_t attack = 0; - int32_t skill = 0; - int32_t length = 0; - int32_t spread = 0; - int32_t radius = 0; - int32_t conditionMinDamage = 0; - int32_t conditionMaxDamage = 0; - int32_t conditionStartDamage = 0; - int32_t tickInterval = 0; - int32_t speedChange = 0; - int32_t duration = 0; - - bool isScripted = false; - bool needTarget = false; - bool needDirection = false; - bool combatSpell = false; - bool isMelee = false; - - Outfit_t outfit = {}; - ShootType_t shoot = CONST_ANI_NONE; - MagicEffectClasses effect = CONST_ME_NONE; - ConditionType_t conditionType = CONDITION_NONE; - CombatType_t combatType = COMBAT_UNDEFINEDDAMAGE; + void createLoot(Container* corpse); + bool createLootContainer(Container* parent, const LootBlock& lootblock); + std::vector createLootItem(const LootBlock& lootBlock); }; class Monsters @@ -237,23 +180,20 @@ class Monsters bool reload(); MonsterType* getMonsterType(const std::string& name); - void addMonsterType(const std::string& name, MonsterType* mType); - bool deserializeSpell(MonsterSpell* spell, spellBlock_t& sb, const std::string& description = ""); - std::unique_ptr scriptInterface; + static uint32_t getLootRandom(); private: - ConditionDamage* getDamageCondition(ConditionType_t conditionType, - int32_t maxDamage, int32_t minDamage, int32_t startDamage, uint32_t tickInterval); + 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 = ""); - MonsterType* loadMonster(const std::string& file, const std::string& monsterName, bool reloading = false); + bool loadMonster(const std::string& file, const std::string& monsterName, std::list>& monsterScriptList, bool reloading = false); void loadLootContainer(const pugi::xml_node& node, LootBlock&); bool loadLootItem(const pugi::xml_node& node, LootBlock&); std::map monsters; - std::map unloadedMonsters; + std::unique_ptr scriptInterface; bool loaded = false; }; diff --git a/src/mounts.cpp b/src/mounts.cpp deleted file mode 100644 index ce10d97..0000000 --- a/src/mounts.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "otpch.h" - -#include "mounts.h" - -#include "pugicast.h" -#include "tools.h" - -bool Mounts::reload() -{ - mounts.clear(); - return loadFromXml(); -} - -bool Mounts::loadFromXml() -{ - pugi::xml_document doc; - pugi::xml_parse_result result = doc.load_file("data/XML/mounts.xml"); - if (!result) { - printXMLError("Error - Mounts::loadFromXml", "data/XML/mounts.xml", result); - return false; - } - - for (auto mountNode : doc.child("mounts").children()) { - mounts.emplace_back( - static_cast(pugi::cast(mountNode.attribute("id").value())), - pugi::cast(mountNode.attribute("clientid").value()), - mountNode.attribute("name").as_string(), - pugi::cast(mountNode.attribute("speed").value()), - mountNode.attribute("premium").as_bool() - ); - } - mounts.shrink_to_fit(); - return true; -} - -Mount* Mounts::getMountByID(uint8_t id) -{ - auto it = std::find_if(mounts.begin(), mounts.end(), [id](const Mount& mount) { - return mount.id == id; - }); - - return it != mounts.end() ? &*it : nullptr; -} - -Mount* Mounts::getMountByName(const std::string& name) { - auto mountName = name.c_str(); - for (auto& it : mounts) { - if (strcasecmp(mountName, it.name.c_str()) == 0) { - return ⁢ - } - } - - return nullptr; -} - -Mount* Mounts::getMountByClientID(uint16_t clientId) -{ - auto it = std::find_if(mounts.begin(), mounts.end(), [clientId](const Mount& mount) { - return mount.clientId == clientId; - }); - - return it != mounts.end() ? &*it : nullptr; -} diff --git a/src/mounts.h b/src/mounts.h deleted file mode 100644 index c63d425..0000000 --- a/src/mounts.h +++ /dev/null @@ -1,52 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_MOUNTS_H_73716D11906A4C5C9F4A7B68D34C9BA6 -#define FS_MOUNTS_H_73716D11906A4C5C9F4A7B68D34C9BA6 - -struct Mount -{ - Mount(uint8_t id, uint16_t clientId, std::string name, int32_t speed, bool premium) : - name(std::move(name)), speed(speed), clientId(clientId), id(id), premium(premium) {} - - std::string name; - int32_t speed; - uint16_t clientId; - uint8_t id; - bool premium; -}; - -class Mounts -{ - public: - bool reload(); - bool loadFromXml(); - Mount* getMountByID(uint8_t id); - Mount* getMountByName(const std::string& name); - Mount* getMountByClientID(uint16_t clientId); - - const std::vector& getMounts() const { - return mounts; - } - - private: - std::vector mounts; -}; - -#endif diff --git a/src/movement.cpp b/src/movement.cpp index c59c772..0dc15bf 100644 --- a/src/movement.cpp +++ b/src/movement.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,49 +36,43 @@ MoveEvents::MoveEvents() : MoveEvents::~MoveEvents() { - clear(false); + clear(); } -void MoveEvents::clearMap(MoveListMap& map, bool fromLua) +void MoveEvents::clearMap(MoveListMap& map) { - for (auto it = map.begin(); it != map.end(); ++it) { - for (int eventType = MOVE_EVENT_STEP_IN; eventType < MOVE_EVENT_LAST; ++eventType) { - auto& moveEvents = it->second.moveEvent[eventType]; - for (auto find = moveEvents.begin(); find != moveEvents.end(); ) { - if (fromLua == find->fromLua) { - find = moveEvents.erase(find); - } else { - ++find; - } + std::unordered_set 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(); -void MoveEvents::clearPosMap(MovePosListMap& map, bool fromLua) -{ - for (auto it = map.begin(); it != map.end(); ++it) { - for (int eventType = MOVE_EVENT_STEP_IN; eventType < MOVE_EVENT_LAST; ++eventType) { - auto& moveEvents = it->second.moveEvent[eventType]; - for (auto find = moveEvents.begin(); find != moveEvents.end(); ) { - if (fromLua == find->fromLua) { - find = moveEvents.erase(find); - } else { - ++find; - } - } - } + for (MoveEvent* moveEvent : set) { + delete moveEvent; } } -void MoveEvents::clear(bool fromLua) +void MoveEvents::clear() { - clearMap(itemIdMap, fromLua); - clearMap(actionIdMap, fromLua); - clearMap(uniqueIdMap, fromLua); - clearPosMap(positionMap, fromLua); + clearMap(itemIdMap); + clearMap(movementIdMap); - reInitState(fromLua); + 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() @@ -91,17 +85,17 @@ std::string MoveEvents::getScriptBaseName() const return "movements"; } -Event_ptr MoveEvents::getEvent(const std::string& nodeName) +Event* MoveEvents::getEvent(const std::string& nodeName) { if (strcasecmp(nodeName.c_str(), "movevent") != 0) { return nullptr; } - return Event_ptr(new MoveEvent(&scriptInterface)); + return new MoveEvent(&scriptInterface); } -bool MoveEvents::registerEvent(Event_ptr event, const pugi::xml_node& node) +bool MoveEvents::registerEvent(Event* event, const pugi::xml_node& node) { - MoveEvent_ptr moveEvent{static_cast(event.release())}; //event is guaranteed to be a MoveEvent + MoveEvent* moveEvent = static_cast(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) { @@ -123,6 +117,7 @@ bool MoveEvents::registerEvent(Event_ptr event, const pugi::xml_node& node) pugi::xml_attribute attr; if ((attr = node.attribute("itemid"))) { int32_t id = pugi::cast(attr.value()); + addEvent(moveEvent, id, itemIdMap); if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) { ItemType& it = Item::items.getItemType(id); it.wieldInfo = moveEvent->getWieldInfo(); @@ -130,12 +125,11 @@ bool MoveEvents::registerEvent(Event_ptr event, const pugi::xml_node& node) it.minReqMagicLevel = moveEvent->getReqMagLv(); it.vocationString = moveEvent->getVocationString(); } - addEvent(std::move(*moveEvent), id, itemIdMap); } else if ((attr = node.attribute("fromid"))) { uint32_t id = pugi::cast(attr.value()); uint32_t endId = pugi::cast(node.attribute("toid").value()); - addEvent(*moveEvent, id, itemIdMap); + addEvent(moveEvent, id, itemIdMap); if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) { ItemType& it = Item::items.getItemType(id); @@ -145,7 +139,7 @@ bool MoveEvents::registerEvent(Event_ptr event, const pugi::xml_node& node) it.vocationString = moveEvent->getVocationString(); while (++id <= endId) { - addEvent(*moveEvent, id, itemIdMap); + addEvent(moveEvent, id, itemIdMap); ItemType& tit = Item::items.getItemType(id); tit.wieldInfo = moveEvent->getWieldInfo(); @@ -155,26 +149,17 @@ bool MoveEvents::registerEvent(Event_ptr event, const pugi::xml_node& node) } } else { while (++id <= endId) { - addEvent(*moveEvent, id, itemIdMap); + addEvent(moveEvent, id, itemIdMap); } } - } else if ((attr = node.attribute("uniqueid"))) { - addEvent(std::move(*moveEvent), pugi::cast(attr.value()), uniqueIdMap); - } else if ((attr = node.attribute("fromuid"))) { + } else if ((attr = node.attribute("movementid"))) { + addEvent(moveEvent, pugi::cast(attr.value()), movementIdMap); + } else if ((attr = node.attribute("frommovementid"))) { uint32_t id = pugi::cast(attr.value()); - uint32_t endId = pugi::cast(node.attribute("touid").value()); - addEvent(*moveEvent, id, uniqueIdMap); + uint32_t endId = pugi::cast(node.attribute("tomovementid").value()); + addEvent(moveEvent, id, movementIdMap); while (++id <= endId) { - addEvent(*moveEvent, id, uniqueIdMap); - } - } else if ((attr = node.attribute("actionid"))) { - addEvent(std::move(*moveEvent), pugi::cast(attr.value()), actionIdMap); - } else if ((attr = node.attribute("fromaid"))) { - uint32_t id = pugi::cast(attr.value()); - uint32_t endId = pugi::cast(node.attribute("toaid").value()); - addEvent(*moveEvent, id, actionIdMap); - while (++id <= endId) { - addEvent(*moveEvent, id, actionIdMap); + addEvent(moveEvent, id, movementIdMap); } } else if ((attr = node.attribute("pos"))) { std::vector posList = vectorAtoi(explodeString(attr.as_string(), ";")); @@ -183,125 +168,28 @@ bool MoveEvents::registerEvent(Event_ptr event, const pugi::xml_node& node) } Position pos(posList[0], posList[1], posList[2]); - addEvent(std::move(*moveEvent), pos, positionMap); + addEvent(moveEvent, pos, positionMap); } else { return false; } return true; } -bool MoveEvents::registerLuaFunction(MoveEvent* event) -{ - MoveEvent_ptr moveEvent{ event }; - if (moveEvent->getItemIdRange().size() > 0) { - if (moveEvent->getItemIdRange().size() == 1) { - uint32_t id = moveEvent->getItemIdRange().at(0); - 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 { - uint32_t iterId = 0; - while (++iterId < moveEvent->getItemIdRange().size()) { - if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) { - ItemType& it = Item::items.getItemType(moveEvent->getItemIdRange().at(iterId)); - it.wieldInfo = moveEvent->getWieldInfo(); - it.minReqLevel = moveEvent->getReqLevel(); - it.minReqMagicLevel = moveEvent->getReqMagLv(); - it.vocationString = moveEvent->getVocationString(); - } - addEvent(*moveEvent, moveEvent->getItemIdRange().at(iterId), itemIdMap); - } - } - } else { - return false; - } - return true; -} - -bool MoveEvents::registerLuaEvent(MoveEvent* event) -{ - MoveEvent_ptr moveEvent{ event }; - if (moveEvent->getItemIdRange().size() > 0) { - if (moveEvent->getItemIdRange().size() == 1) { - uint32_t id = moveEvent->getItemIdRange().at(0); - 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 { - auto v = moveEvent->getItemIdRange(); - for (auto i = v.begin(); i != v.end(); i++) { - if (moveEvent->getEventType() == MOVE_EVENT_EQUIP) { - ItemType& it = Item::items.getItemType(*i); - it.wieldInfo = moveEvent->getWieldInfo(); - it.minReqLevel = moveEvent->getReqLevel(); - it.minReqMagicLevel = moveEvent->getReqMagLv(); - it.vocationString = moveEvent->getVocationString(); - } - addEvent(*moveEvent, *i, itemIdMap); - } - } - } else if (moveEvent->getActionIdRange().size() > 0) { - if (moveEvent->getActionIdRange().size() == 1) { - int32_t id = moveEvent->getActionIdRange().at(0); - addEvent(*moveEvent, id, actionIdMap); - } else { - auto v = moveEvent->getActionIdRange(); - for (auto i = v.begin(); i != v.end(); i++) { - addEvent(*moveEvent, *i, actionIdMap); - } - } - } else if (moveEvent->getUniqueIdRange().size() > 0) { - if (moveEvent->getUniqueIdRange().size() == 1) { - int32_t id = moveEvent->getUniqueIdRange().at(0); - addEvent(*moveEvent, id, uniqueIdMap); - } else { - auto v = moveEvent->getUniqueIdRange(); - for (auto i = v.begin(); i != v.end(); i++) { - addEvent(*moveEvent, *i, uniqueIdMap); - } - } - } else if (moveEvent->getPosList().size() > 0) { - if (moveEvent->getPosList().size() == 1) { - Position pos = moveEvent->getPosList().at(0); - addEvent(*moveEvent, pos, positionMap); - } else { - auto v = moveEvent->getPosList(); - for (auto i = v.begin(); i != v.end(); i++) { - addEvent(*moveEvent, *i, positionMap); - } - } - } else { - return false; - } - - return true; -} - -void MoveEvents::addEvent(MoveEvent moveEvent, int32_t id, MoveListMap& map) +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(std::move(moveEvent)); + moveEventList.moveEvent[moveEvent->getEventType()].push_back(moveEvent); map[id] = moveEventList; } else { - std::list& moveEventList = it->second.moveEvent[moveEvent.getEventType()]; - for (MoveEvent& existingMoveEvent : moveEventList) { - if (existingMoveEvent.getSlot() == moveEvent.getSlot()) { + std::list& 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(std::move(moveEvent)); + moveEventList.push_back(moveEvent); } } @@ -324,64 +212,59 @@ MoveEvent* MoveEvents::getEvent(Item* item, MoveEvent_t eventType, slots_t slot) auto it = itemIdMap.find(item->getID()); if (it != itemIdMap.end()) { - std::list& moveEventList = it->second.moveEvent[eventType]; - for (MoveEvent& moveEvent : moveEventList) { - if ((moveEvent.getSlot() & slotp) != 0) { - return &moveEvent; + std::list& 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) +MoveEvent* MoveEvents::getEvent(Item* item, MoveEvent_t eventType) { MoveListMap::iterator it; - if (item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) { - it = uniqueIdMap.find(item->getUniqueId()); - if (it != uniqueIdMap.end()) { - std::list& moveEventList = it->second.moveEvent[eventType]; + if (item->hasAttribute(ITEM_ATTRIBUTE_MOVEMENTID)) { + it = movementIdMap.find(item->getMovementId()); + if (it != movementIdMap.end()) { + std::list& moveEventList = it->second.moveEvent[eventType]; if (!moveEventList.empty()) { - return &(*moveEventList.begin()); + return *moveEventList.begin(); } } } - if (item->hasAttribute(ITEM_ATTRIBUTE_ACTIONID)) { - it = actionIdMap.find(item->getActionId()); - if (it != actionIdMap.end()) { - std::list& 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& moveEventList = it->second.moveEvent[eventType]; + std::list& moveEventList = it->second.moveEvent[eventType]; if (!moveEventList.empty()) { - return &(*moveEventList.begin()); + return *moveEventList.begin(); } } + return nullptr; } -void MoveEvents::addEvent(MoveEvent moveEvent, const Position& pos, MovePosListMap& map) +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(std::move(moveEvent)); + moveEventList.moveEvent[moveEvent->getEventType()].push_back(moveEvent); map[pos] = moveEventList; } else { - std::list& moveEventList = it->second.moveEvent[moveEvent.getEventType()]; + std::list& moveEventList = it->second.moveEvent[moveEvent->getEventType()]; if (!moveEventList.empty()) { std::cout << "[Warning - MoveEvents::addEvent] Duplicate move event found: " << pos << std::endl; } - moveEventList.push_back(std::move(moveEvent)); + moveEventList.push_back(moveEvent); } } @@ -389,9 +272,9 @@ MoveEvent* MoveEvents::getEvent(const Tile* tile, MoveEvent_t eventType) { auto it = positionMap.find(tile->getPosition()); if (it != positionMap.end()) { - std::list& moveEventList = it->second.moveEvent[eventType]; + std::list& moveEventList = it->second.moveEvent[eventType]; if (!moveEventList.empty()) { - return &(*moveEventList.begin()); + return *moveEventList.begin(); } } return nullptr; @@ -442,7 +325,7 @@ uint32_t MoveEvents::onPlayerDeEquip(Player* player, Item* item, slots_t slot) if (!moveEvent) { return 1; } - return moveEvent->fireEquip(player, item, slot, false); + return moveEvent->fireEquip(player, item, slot, true); } uint32_t MoveEvents::onItemMove(Item* item, Tile* tile, bool isAdd) @@ -488,6 +371,20 @@ uint32_t MoveEvents::onItemMove(Item* item, Tile* tile, bool isAdd) 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) { @@ -624,6 +521,40 @@ bool MoveEvent::configureEvent(const pugi::xml_node& node) 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(); @@ -715,6 +646,10 @@ uint32_t MoveEvent::EquipItem(MoveEvent* moveEvent, Player* player, Item* item, player->sendIcons(); } + if (it.abilities->absorbPercent[combatTypeToIndex(COMBAT_DROWNDAMAGE)] == 100) { + player->removeCondition(CONDITION_DROWN); + } + if (it.abilities->regeneration) { Condition* condition = Condition::createCondition(static_cast(slot), CONDITION_REGENERATION, -1, 0); @@ -747,13 +682,6 @@ uint32_t MoveEvent::EquipItem(MoveEvent* moveEvent, Player* player, Item* item, } } - for (int32_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; ++i) { - if (it.abilities->specialSkills[i]) { - needUpdateSkills = true; - player->setVarSpecialSkill(static_cast(i), it.abilities->specialSkills[i]); - } - } - if (needUpdateSkills) { player->sendSkills(); } @@ -829,13 +757,6 @@ uint32_t MoveEvent::DeEquipItem(MoveEvent*, Player* player, Item* item, slots_t } } - for (int32_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; ++i) { - if (it.abilities->specialSkills[i] != 0) { - needUpdateSkills = true; - player->setVarSpecialSkill(static_cast(i), -it.abilities->specialSkills[i]); - } - } - if (needUpdateSkills) { player->sendSkills(); } @@ -862,44 +783,6 @@ uint32_t MoveEvent::DeEquipItem(MoveEvent*, Player* player, Item* item, slots_t return 1; } -bool MoveEvent::loadFunction(const pugi::xml_attribute& attr, bool isScripted) -{ - 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 { - if (!isScripted) { - std::cout << "[Warning - MoveEvent::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; - return false; - } - } - - if (!isScripted) { - scripted = false; - } - return true; -} - -MoveEvent_t MoveEvent::getEventType() const -{ - return eventType; -} - -void MoveEvent::setEventType(MoveEvent_t type) -{ - eventType = type; -} - uint32_t MoveEvent::fireStepEvent(Creature* creature, Item* item, const Position& pos) { if (scripted) { @@ -933,24 +816,19 @@ bool MoveEvent::executeStep(Creature* creature, Item* item, const Position& pos) return scriptInterface->callFunction(4); } -uint32_t MoveEvent::fireEquip(Player* player, Item* item, slots_t slot, bool isCheck) +uint32_t MoveEvent::fireEquip(Player* player, Item* item, slots_t slot, bool boolean) { if (scripted) { - if (!equipFunction || equipFunction(this, player, item, slot, isCheck) == 1) { - if (executeEquip(player, item, slot, isCheck)) { - return 1; - } - } - return 0; + return executeEquip(player, item, slot); } else { - return equipFunction(this, player, item, slot, isCheck); + return equipFunction(this, player, item, slot, boolean); } } -bool MoveEvent::executeEquip(Player* player, Item* item, slots_t slot, bool isCheck) +bool MoveEvent::executeEquip(Player* player, Item* item, slots_t slot) { - //onEquip(player, item, slot, isCheck) - //onDeEquip(player, item, slot, isCheck) + //onEquip(player, item, slot) + //onDeEquip(player, item, slot) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - MoveEvent::executeEquip] Call stack overflow" << std::endl; return false; @@ -966,9 +844,8 @@ bool MoveEvent::executeEquip(Player* player, Item* item, slots_t slot, bool isCh LuaScriptInterface::setMetatable(L, -1, "Player"); LuaScriptInterface::pushThing(L, item); lua_pushnumber(L, slot); - LuaScriptInterface::pushBoolean(L, isCheck); - return scriptInterface->callFunction(4); + return scriptInterface->callFunction(3); } uint32_t MoveEvent::fireAddRemItem(Item* item, Item* tileItem, const Position& pos) diff --git a/src/movement.h b/src/movement.h index 27ed82f..d8a67e4 100644 --- a/src/movement.h +++ b/src/movement.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,9 +23,6 @@ #include "baseevents.h" #include "item.h" #include "luascript.h" -#include "vocation.h" - -extern Vocations g_vocations; enum MoveEvent_t { MOVE_EVENT_STEP_IN, @@ -42,13 +39,12 @@ enum MoveEvent_t { }; class MoveEvent; -using MoveEvent_ptr = std::unique_ptr; struct MoveEventList { - std::list moveEvent[MOVE_EVENT_LAST]; + std::list moveEvent[MOVE_EVENT_LAST]; }; -using VocEquipMap = std::map; +typedef std::map VocEquipMap; class MoveEvents final : public BaseEvents { @@ -67,54 +63,51 @@ class MoveEvents final : public BaseEvents MoveEvent* getEvent(Item* item, MoveEvent_t eventType); - bool registerLuaEvent(MoveEvent* event); - bool registerLuaFunction(MoveEvent* event); - void clear(bool fromLua) override final; + protected: + typedef std::map MoveListMap; + void clearMap(MoveListMap& map); - private: - using MoveListMap = std::map; - using MovePosListMap = std::map; - void clearMap(MoveListMap& map, bool fromLua); - void clearPosMap(MovePosListMap& map, bool fromLua); + typedef std::map 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; - LuaScriptInterface& getScriptInterface() override; - std::string getScriptBaseName() const override; - Event_ptr getEvent(const std::string& nodeName) override; - bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; + void addEvent(MoveEvent* moveEvent, int32_t id, MoveListMap& map); - void addEvent(MoveEvent moveEvent, int32_t id, MoveListMap& map); - - void addEvent(MoveEvent moveEvent, const Position& pos, MovePosListMap& 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 uniqueIdMap; - MoveListMap actionIdMap; + + MoveListMap movementIdMap; MoveListMap itemIdMap; MovePosListMap positionMap; LuaScriptInterface scriptInterface; }; -using StepFunction = std::function; -using MoveFunction = std::function; -using EquipFunction = std::function; +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) override; - bool loadFunction(const pugi::xml_attribute& attr, bool isScripted) override; + 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 isCheck); + uint32_t fireEquip(Player* player, Item* item, slots_t slot, bool boolean); uint32_t getSlot() const { return slot; @@ -122,7 +115,7 @@ class MoveEvent final : public Event //scripting bool executeStep(Creature* creature, Item* item, const Position& pos); - bool executeEquip(Player* player, Item* item, slots_t slot, bool isCheck); + bool executeEquip(Player* player, Item* item, slots_t slot); bool executeAddRemItem(Item* item, Item* tileItem, const Position& pos); // @@ -139,104 +132,29 @@ class MoveEvent final : public Event const std::string& getVocationString() const { return vocationString; } - void setVocationString(const std::string& str) { - vocationString = str; - } uint32_t getWieldInfo() const { return wieldInfo; } const VocEquipMap& getVocEquipMap() const { return vocEquipMap; } - void addVocEquipMap(std::string vocName) { - int32_t vocationId = g_vocations.getVocationId(vocName); - if (vocationId != -1) { - vocEquipMap[vocationId] = true; - } - } - bool getTileItem() const { - return tileItem; - } - void setTileItem(bool b) { - tileItem = b; - } - std::vector getItemIdRange() { - return itemIdRange; - } - void addItemId(uint32_t id) { - itemIdRange.emplace_back(id); - } - std::vector getActionIdRange() { - return actionIdRange; - } - void addActionId(uint32_t id) { - actionIdRange.emplace_back(id); - } - std::vector getUniqueIdRange() { - return uniqueIdRange; - } - void addUniqueId(uint32_t id) { - uniqueIdRange.emplace_back(id); - } - std::vector getPosList() { - return posList; - } - void addPosList(Position pos) { - posList.emplace_back(pos); - } - std::string getSlotName() { - return slotName; - } - void setSlotName(std::string name) { - slotName = name; - } - void setSlot(uint32_t s) { - slot = s; - } - uint32_t getRequiredLevel() { - return reqLevel; - } - void setRequiredLevel(uint32_t level) { - reqLevel = level; - } - uint32_t getRequiredMagLevel() { - return reqMagLevel; - } - void setRequiredMagLevel(uint32_t level) { - reqMagLevel = level; - } - bool needPremium() { - return premium; - } - void setNeedPremium(bool b) { - premium = b; - } - uint32_t getWieldInfo() { - return wieldInfo; - } - void setWieldInfo(WieldInfo_t info) { - wieldInfo |= info; - } - static uint32_t StepInField(Creature* creature, Item* item, const Position& pos); - static uint32_t StepOutField(Creature* creature, Item* item, const Position& pos); + protected: + std::string getScriptEventName() const final; - static uint32_t AddItemField(Item* item, Item* tileItem, const Position& pos); - static uint32_t RemoveItemField(Item* item, Item* tileItem, const Position& pos); + static StepFunction StepInField; + static StepFunction StepOutField; - static uint32_t EquipItem(MoveEvent* moveEvent, Player* player, Item* item, slots_t slot, bool boolean); - static uint32_t DeEquipItem(MoveEvent* moveEvent, Player* player, Item* item, slots_t slot, bool boolean); + static MoveFunction AddItemField; + static MoveFunction RemoveItemField; + static EquipFunction EquipItem; + static EquipFunction DeEquipItem; MoveEvent_t eventType = MOVE_EVENT_NONE; - StepFunction stepFunction; - MoveFunction moveFunction; - EquipFunction equipFunction; - - private: - std::string getScriptEventName() const override; - + StepFunction* stepFunction = nullptr; + MoveFunction* moveFunction = nullptr; + EquipFunction* equipFunction = nullptr; uint32_t slot = SLOTP_WHEREEVER; - std::string slotName; //onEquip information uint32_t reqLevel = 0; @@ -245,12 +163,6 @@ class MoveEvent final : public Event std::string vocationString; uint32_t wieldInfo = 0; VocEquipMap vocEquipMap; - bool tileItem = false; - - std::vector itemIdRange; - std::vector actionIdRange; - std::vector uniqueIdRange; - std::vector posList; }; #endif diff --git a/src/networkmessage.cpp b/src/networkmessage.cpp index b9be9f0..7f02803 100644 --- a/src/networkmessage.cpp +++ b/src/networkmessage.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -99,18 +99,16 @@ void NetworkMessage::addItem(uint16_t id, uint8_t count) { const ItemType& it = Item::items[id]; - add(it.clientId); - - addByte(0xFF); // MARK_UNMARKED + if (it.disguise) { + add(it.disguiseId); + } else { + add(it.id); + } if (it.stackable) { addByte(count); } else if (it.isSplash() || it.isFluidContainer()) { - addByte(fluidMap[count & 7]); - } - - if (it.isAnimation) { - addByte(0xFE); // random phase (0xFF for async) + addByte(getLiquidColor(count)); } } @@ -118,21 +116,20 @@ void NetworkMessage::addItem(const Item* item) { const ItemType& it = Item::items[item->getID()]; - add(it.clientId); - addByte(0xFF); // MARK_UNMARKED + if (it.disguise) { + add(it.disguiseId); + } else { + add(it.id); + } if (it.stackable) { addByte(std::min(0xFF, item->getItemCount())); } else if (it.isSplash() || it.isFluidContainer()) { - addByte(fluidMap[item->getFluidType() & 7]); - } - - if (it.isAnimation) { - addByte(0xFE); // random phase (0xFF for async) + addByte(getLiquidColor(item->getFluidType())); } } void NetworkMessage::addItemId(uint16_t itemId) { - add(Item::items[itemId].clientId); + add(itemId); } diff --git a/src/networkmessage.h b/src/networkmessage.h index 7118f44..7d11153 100644 --- a/src/networkmessage.h +++ b/src/networkmessage.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,16 +31,14 @@ class RSA; class NetworkMessage { public: - using MsgSize_t = uint16_t; + typedef uint16_t MsgSize_t; // Headers: // 2 bytes for unencrypted message size - // 4 bytes for checksum // 2 bytes for encrypted message size - static constexpr MsgSize_t INITIAL_BUFFER_POSITION = 8; + static constexpr MsgSize_t INITIAL_BUFFER_POSITION = 4; enum { HEADER_LENGTH = 2 }; - enum { CHECKSUM_LENGTH = 4 }; enum { XTEA_MULTIPLE = 8 }; - enum { MAX_BODY_LENGTH = NETWORKMESSAGE_MAXSIZE - HEADER_LENGTH - CHECKSUM_LENGTH - XTEA_MULTIPLE }; + enum { MAX_BODY_LENGTH = NETWORKMESSAGE_MAXSIZE - HEADER_LENGTH - XTEA_MULTIPLE }; enum { MAX_PROTOCOL_BODY_LENGTH = MAX_BODY_LENGTH - 10 }; NetworkMessage() = default; @@ -150,6 +148,18 @@ class NetworkMessage } 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; @@ -158,19 +168,6 @@ class NetworkMessage NetworkMessageInfo info; uint8_t buffer[NETWORKMESSAGE_MAXSIZE]; - - private: - bool canAdd(size_t size) const { - return (size + info.position) < MAX_BODY_LENGTH; - } - - bool canRead(int32_t size) { - if ((info.position + size) > (info.length + 8) || size >= (NETWORKMESSAGE_MAXSIZE - info.position)) { - info.overrun = true; - return false; - } - return true; - } }; #endif // #ifndef __NETWORK_MESSAGE_H__ diff --git a/src/npc.cpp b/src/npc.cpp index f090ff9..4d811e7 100644 --- a/src/npc.cpp +++ b/src/npc.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,23 +21,43 @@ #include "npc.h" #include "game.h" -#include "pugicast.h" +#include "tools.h" +#include "position.h" +#include "player.h" +#include "spawn.h" +#include "script.h" +#include "behaviourdatabase.h" extern Game g_game; -extern LuaEnvironment g_luaEnvironment; uint32_t Npc::npcAutoID = 0x80000000; -NpcScriptInterface* Npc::scriptInterface = nullptr; + +void Npcs::loadNpcs() +{ + std::cout << ">> Loading npcs..." << std::endl; + + std::vector 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& npcs = g_game.getNpcs(); - for (const auto& it : npcs) { - it.second->closeAllShopWindows(); - } - - delete Npc::scriptInterface; - Npc::scriptInterface = nullptr; for (const auto& it : npcs) { it.second->reload(); @@ -47,19 +67,23 @@ void Npcs::reload() Npc* Npc::createNpc(const std::string& name) { std::unique_ptr 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 + ".xml"), - npcEventHandler(nullptr), - masterRadius(-1), - loaded(false) + filename("data/npc/" + name + ".npc"), + masterRadius(0), + staticMovementTime(0), + loaded(false), + behaviourDatabase(nullptr) { + baseSpeed = 5; reset(); } @@ -86,147 +110,89 @@ bool Npc::load() reset(); - if (!scriptInterface) { - scriptInterface = new NpcScriptInterface(); - scriptInterface->loadNpcLib("data/npc/lib/npc.lua"); + ScriptReader script; + if (!script.open(filename)) { + return false; } - loaded = loadFromXml(); - return loaded; + 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]; + currentOutfit.lookAddons = c[4]; + } 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; - walkTicks = 1500; - pushable = true; - floorChange = false; - attackable = false; - ignoreHeight = true; focusCreature = 0; - speechBubble = SPEECHBUBBLE_NONE; + conversationEndTime = 0; - delete npcEventHandler; - npcEventHandler = nullptr; - - parameters.clear(); - shopPlayerSet.clear(); + if (behaviourDatabase) { + delete behaviourDatabase; + behaviourDatabase = nullptr; + } } void Npc::reload() { + loaded = false; + reset(); load(); - // Simulate that the creature is placed on the map again. - if (npcEventHandler) { - npcEventHandler->onCreatureAppear(this); - } - - if (walkTicks > 0) { + if (baseSpeed > 0) { addEventWalk(); } } -bool Npc::loadFromXml() -{ - pugi::xml_document doc; - pugi::xml_parse_result result = doc.load_file(filename.c_str()); - if (!result) { - printXMLError("Error - Npc::loadFromXml", filename, result); - return false; - } - - pugi::xml_node npcNode = doc.child("npc"); - if (!npcNode) { - std::cout << "[Error - Npc::loadFromXml] Missing npc tag in " << filename << std::endl; - return false; - } - - name = npcNode.attribute("name").as_string(); - attackable = npcNode.attribute("attackable").as_bool(); - floorChange = npcNode.attribute("floorchange").as_bool(); - - pugi::xml_attribute attr; - if ((attr = npcNode.attribute("speed"))) { - baseSpeed = pugi::cast(attr.value()); - } else { - baseSpeed = 100; - } - - if ((attr = npcNode.attribute("pushable"))) { - pushable = attr.as_bool(); - } - - if ((attr = npcNode.attribute("walkinterval"))) { - walkTicks = pugi::cast(attr.value()); - } - - if ((attr = npcNode.attribute("walkradius"))) { - masterRadius = pugi::cast(attr.value()); - } - - if ((attr = npcNode.attribute("ignoreheight"))) { - ignoreHeight = attr.as_bool(); - } - - if ((attr = npcNode.attribute("speechbubble"))) { - speechBubble = pugi::cast(attr.value()); - } - - if ((attr = npcNode.attribute("skull"))) { - setSkull(getSkullType(asLowerCaseString(attr.as_string()))); - } - - pugi::xml_node healthNode = npcNode.child("health"); - if (healthNode) { - if ((attr = healthNode.attribute("now"))) { - health = pugi::cast(attr.value()); - } else { - health = 100; - } - - if ((attr = healthNode.attribute("max"))) { - healthMax = pugi::cast(attr.value()); - } else { - healthMax = 100; - } - } - - pugi::xml_node lookNode = npcNode.child("look"); - if (lookNode) { - pugi::xml_attribute lookTypeAttribute = lookNode.attribute("type"); - if (lookTypeAttribute) { - defaultOutfit.lookType = pugi::cast(lookTypeAttribute.value()); - defaultOutfit.lookHead = pugi::cast(lookNode.attribute("head").value()); - defaultOutfit.lookBody = pugi::cast(lookNode.attribute("body").value()); - defaultOutfit.lookLegs = pugi::cast(lookNode.attribute("legs").value()); - defaultOutfit.lookFeet = pugi::cast(lookNode.attribute("feet").value()); - defaultOutfit.lookAddons = pugi::cast(lookNode.attribute("addons").value()); - } else if ((attr = lookNode.attribute("typeex"))) { - defaultOutfit.lookTypeEx = pugi::cast(attr.value()); - } - defaultOutfit.lookMount = pugi::cast(lookNode.attribute("mount").value()); - - currentOutfit = defaultOutfit; - } - - for (auto parameterNode : npcNode.child("parameters").children()) { - parameters[parameterNode.attribute("key").as_string()] = parameterNode.attribute("value").as_string(); - } - - pugi::xml_attribute scriptFile = npcNode.attribute("script"); - if (scriptFile) { - npcEventHandler = new NpcEventsHandler(scriptFile.as_string(), this); - if (!npcEventHandler->isLoaded()) { - delete npcEventHandler; - npcEventHandler = nullptr; - return false; - } - } - return true; -} - bool Npc::canSee(const Position& pos) const { if (pos.z != getPosition().z) { @@ -249,18 +215,10 @@ void Npc::onCreatureAppear(Creature* creature, bool isLogin) Creature::onCreatureAppear(creature, isLogin); if (creature == this) { - if (walkTicks > 0) { + if (baseSpeed > 0) { addEventWalk(); } - - if (npcEventHandler) { - npcEventHandler->onCreatureAppear(creature); - } } else if (Player* player = creature->getPlayer()) { - if (npcEventHandler) { - npcEventHandler->onCreatureAppear(creature); - } - spectators.insert(player); updateIdleStatus(); } @@ -270,14 +228,14 @@ void Npc::onRemoveCreature(Creature* creature, bool isLogout) { Creature::onRemoveCreature(creature, isLogout); - if (creature == this) { - closeAllShopWindows(); - if (npcEventHandler) { - npcEventHandler->onCreatureDisappear(creature); - } - } else if (Player* player = creature->getPlayer()) { - if (npcEventHandler) { - npcEventHandler->onCreatureDisappear(creature); + if (!behaviourDatabase) { + return; + } + + Player* player = creature->getPlayer(); + if (player) { + if (player->getID() == focusCreature) { + behaviourDatabase->react(SITUATION_VANISH, player, ""); } spectators.erase(player); @@ -290,15 +248,19 @@ void Npc::onCreatureMove(Creature* creature, const Tile* newTile, const Position { Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport); - if (creature == this || creature->getPlayer()) { - if (npcEventHandler) { - npcEventHandler->onCreatureMove(creature, oldPos, newPos); + 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) { - Player* player = creature->getPlayer(); - - // if player is now in range, add to spectators list, otherwise erase + if (creature != this) { + if (player) { if (player->canSee(position)) { spectators.insert(player); } else { @@ -312,23 +274,25 @@ void Npc::onCreatureMove(Creature* creature, const Tile* newTile, const Position void Npc::onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) { - if (creature->getID() == id) { + if (creature->getID() == id || type != TALKTYPE_SAY || !behaviourDatabase) { return; } - //only players for script events Player* player = creature->getPlayer(); if (player) { - if (npcEventHandler) { - npcEventHandler->onCreatureSay(player, type, text); + if (!Position::areInRange<3, 3>(creature->getPosition(), getPosition())) { + return; } - } -} -void Npc::onPlayerCloseChannel(Player* player) -{ - if (npcEventHandler) { - npcEventHandler->onPlayerCloseChannel(player); + 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); + } } } @@ -336,63 +300,44 @@ void Npc::onThink(uint32_t interval) { Creature::onThink(interval); - if (npcEventHandler) { - npcEventHandler->onThink(); + if (!isIdle && focusCreature == 0 && baseSpeed > 0 && getTimeSinceLastMove() >= 100 + getStepDuration()) { + addEventWalk(); } - if (!isIdle && getTimeSinceLastMove() >= walkTicks) { - 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); } -void Npc::doSayToPlayer(Player* player, const std::string& text) -{ - if (player) { - player->sendCreatureSay(this, TALKTYPE_PRIVATE_NP, text); - player->onCreatureSay(this, TALKTYPE_PRIVATE_NP, text); - } -} - -void Npc::onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count, - uint8_t amount, bool ignore/* = false*/, bool inBackpacks/* = false*/) -{ - if (npcEventHandler) { - npcEventHandler->onPlayerTrade(player, callback, itemId, count, amount, ignore, inBackpacks); - } - player->sendSaleItemList(); -} - -void Npc::onPlayerEndTrade(Player* player, int32_t buyCallback, int32_t sellCallback) -{ - lua_State* L = getScriptInterface()->getLuaState(); - - if (buyCallback != -1) { - luaL_unref(L, LUA_REGISTRYINDEX, buyCallback); - } - - if (sellCallback != -1) { - luaL_unref(L, LUA_REGISTRYINDEX, sellCallback); - } - - removeShopPlayer(player); - - if (npcEventHandler) { - npcEventHandler->onPlayerEndTrade(player); - } -} - bool Npc::getNextStep(Direction& dir, uint32_t& flags) { if (Creature::getNextStep(dir, flags)) { return true; } - if (walkTicks == 0) { + if (baseSpeed <= 0) { return false; } @@ -400,7 +345,11 @@ bool Npc::getNextStep(Direction& dir, uint32_t& flags) return false; } - if (getTimeSinceLastMove() < walkTicks) { + if (OTSYS_TIME() < staticMovementTime) { + return false; + } + + if (getTimeSinceLastMove() < 100 + getStepDuration() + getStepSpeed()) { return false; } @@ -444,11 +393,11 @@ bool Npc::canWalkTo(const Position& fromPos, Direction dir) const return false; } - if (!floorChange && (tile->hasFlag(TILESTATE_FLOORCHANGE) || tile->getTeleportItem())) { + if (tile->hasFlag(TILESTATE_BLOCKPATH)) { return false; } - if (!ignoreHeight && tile->hasHeight(1)) { + if (tile->hasHeight(1)) { return false; } @@ -484,10 +433,10 @@ bool Npc::getRandomStep(Direction& dir) const return true; } -void Npc::doMoveTo(const Position& pos) +void Npc::doMoveTo(const Position& target) { std::forward_list listDir; - if (getPathTo(pos, listDir, 1, 1, true, true)) { + if (getPathTo(target, listDir, 1, 1, true, true)) { startAutoWalk(listDir); } } @@ -531,771 +480,4 @@ void Npc::setCreatureFocus(Creature* creature) } else { focusCreature = 0; } -} - -void Npc::addShopPlayer(Player* player) -{ - shopPlayerSet.insert(player); -} - -void Npc::removeShopPlayer(Player* player) -{ - shopPlayerSet.erase(player); -} - -void Npc::closeAllShopWindows() -{ - while (!shopPlayerSet.empty()) { - Player* player = *shopPlayerSet.begin(); - if (!player->closeShopWindow()) { - removeShopPlayer(player); - } - } -} - -NpcScriptInterface* Npc::getScriptInterface() -{ - return scriptInterface; -} - -NpcScriptInterface::NpcScriptInterface() : - LuaScriptInterface("Npc interface") -{ - libLoaded = false; - initState(); -} - -bool NpcScriptInterface::initState() -{ - luaState = g_luaEnvironment.getLuaState(); - if (!luaState) { - return false; - } - - registerFunctions(); - - lua_newtable(luaState); - eventTableRef = luaL_ref(luaState, LUA_REGISTRYINDEX); - runningEventId = EVENT_ID_USER; - return true; -} - -bool NpcScriptInterface::closeState() -{ - libLoaded = false; - LuaScriptInterface::closeState(); - return true; -} - -bool NpcScriptInterface::loadNpcLib(const std::string& file) -{ - if (libLoaded) { - return true; - } - - if (loadFile(file) == -1) { - std::cout << "[Warning - NpcScriptInterface::loadNpcLib] Can not load " << file << std::endl; - return false; - } - - libLoaded = true; - return true; -} - -void NpcScriptInterface::registerFunctions() -{ - //npc exclusive functions - lua_register(luaState, "selfSay", NpcScriptInterface::luaActionSay); - lua_register(luaState, "selfMove", NpcScriptInterface::luaActionMove); - lua_register(luaState, "selfMoveTo", NpcScriptInterface::luaActionMoveTo); - lua_register(luaState, "selfTurn", NpcScriptInterface::luaActionTurn); - lua_register(luaState, "selfFollow", NpcScriptInterface::luaActionFollow); - lua_register(luaState, "getDistanceTo", NpcScriptInterface::luagetDistanceTo); - lua_register(luaState, "doNpcSetCreatureFocus", NpcScriptInterface::luaSetNpcFocus); - lua_register(luaState, "getNpcCid", NpcScriptInterface::luaGetNpcCid); - lua_register(luaState, "getNpcParameter", NpcScriptInterface::luaGetNpcParameter); - lua_register(luaState, "openShopWindow", NpcScriptInterface::luaOpenShopWindow); - lua_register(luaState, "closeShopWindow", NpcScriptInterface::luaCloseShopWindow); - lua_register(luaState, "doSellItem", NpcScriptInterface::luaDoSellItem); - - // metatable - registerMethod("Npc", "getParameter", NpcScriptInterface::luaNpcGetParameter); - registerMethod("Npc", "setFocus", NpcScriptInterface::luaNpcSetFocus); - - registerMethod("Npc", "openShopWindow", NpcScriptInterface::luaNpcOpenShopWindow); - registerMethod("Npc", "closeShopWindow", NpcScriptInterface::luaNpcCloseShopWindow); -} - -int NpcScriptInterface::luaActionSay(lua_State* L) -{ - //selfSay(words[, target]) - Npc* npc = getScriptEnv()->getNpc(); - if (!npc) { - return 0; - } - - const std::string& text = getString(L, 1); - if (lua_gettop(L) >= 2) { - Player* target = getPlayer(L, 2); - if (target) { - npc->doSayToPlayer(target, text); - return 0; - } - } - - npc->doSay(text); - return 0; -} - -int NpcScriptInterface::luaActionMove(lua_State* L) -{ - //selfMove(direction) - Npc* npc = getScriptEnv()->getNpc(); - if (npc) { - g_game.internalMoveCreature(npc, getNumber(L, 1)); - } - return 0; -} - -int NpcScriptInterface::luaActionMoveTo(lua_State* L) -{ - //selfMoveTo(x,y,z) - Npc* npc = getScriptEnv()->getNpc(); - if (!npc) { - return 0; - } - - npc->doMoveTo(Position( - getNumber(L, 1), - getNumber(L, 2), - getNumber(L, 3) - )); - return 0; -} - -int NpcScriptInterface::luaActionTurn(lua_State* L) -{ - //selfTurn(direction) - Npc* npc = getScriptEnv()->getNpc(); - if (npc) { - g_game.internalCreatureTurn(npc, getNumber(L, 1)); - } - return 0; -} - -int NpcScriptInterface::luaActionFollow(lua_State* L) -{ - //selfFollow(player) - Npc* npc = getScriptEnv()->getNpc(); - if (!npc) { - pushBoolean(L, false); - return 1; - } - - pushBoolean(L, npc->setFollowCreature(getPlayer(L, 1))); - return 1; -} - -int NpcScriptInterface::luagetDistanceTo(lua_State* L) -{ - //getDistanceTo(uid) - ScriptEnvironment* env = getScriptEnv(); - - Npc* npc = env->getNpc(); - if (!npc) { - reportErrorFunc(getErrorDesc(LUA_ERROR_THING_NOT_FOUND)); - lua_pushnil(L); - return 1; - } - - uint32_t uid = getNumber(L, -1); - - Thing* thing = env->getThingByUID(uid); - if (!thing) { - reportErrorFunc(getErrorDesc(LUA_ERROR_THING_NOT_FOUND)); - lua_pushnil(L); - return 1; - } - - const Position& thingPos = thing->getPosition(); - const Position& npcPos = npc->getPosition(); - if (npcPos.z != thingPos.z) { - lua_pushnumber(L, -1); - } else { - int32_t dist = std::max(Position::getDistanceX(npcPos, thingPos), Position::getDistanceY(npcPos, thingPos)); - lua_pushnumber(L, dist); - } - return 1; -} - -int NpcScriptInterface::luaSetNpcFocus(lua_State* L) -{ - //doNpcSetCreatureFocus(cid) - Npc* npc = getScriptEnv()->getNpc(); - if (npc) { - npc->setCreatureFocus(getCreature(L, -1)); - } - return 0; -} - -int NpcScriptInterface::luaGetNpcCid(lua_State* L) -{ - //getNpcCid() - Npc* npc = getScriptEnv()->getNpc(); - if (npc) { - lua_pushnumber(L, npc->getID()); - } else { - lua_pushnil(L); - } - return 1; -} - -int NpcScriptInterface::luaGetNpcParameter(lua_State* L) -{ - //getNpcParameter(paramKey) - Npc* npc = getScriptEnv()->getNpc(); - if (!npc) { - lua_pushnil(L); - return 1; - } - - std::string paramKey = getString(L, -1); - - auto it = npc->parameters.find(paramKey); - if (it != npc->parameters.end()) { - LuaScriptInterface::pushString(L, it->second); - } else { - lua_pushnil(L); - } - return 1; -} - -int NpcScriptInterface::luaOpenShopWindow(lua_State* L) -{ - //openShopWindow(cid, items, onBuy callback, onSell callback) - int32_t sellCallback; - if (lua_isfunction(L, -1) == 0) { - sellCallback = -1; - lua_pop(L, 1); // skip it - use default value - } else { - sellCallback = popCallback(L); - } - - int32_t buyCallback; - if (lua_isfunction(L, -1) == 0) { - buyCallback = -1; - lua_pop(L, 1); // skip it - use default value - } else { - buyCallback = popCallback(L); - } - - if (lua_istable(L, -1) == 0) { - reportError(__FUNCTION__, "item list is not a table."); - pushBoolean(L, false); - return 1; - } - - std::list items; - lua_pushnil(L); - while (lua_next(L, -2) != 0) { - const auto tableIndex = lua_gettop(L); - ShopInfo item; - - item.itemId = getField(L, tableIndex, "id"); - item.subType = getField(L, tableIndex, "subType"); - if (item.subType == 0) { - item.subType = getField(L, tableIndex, "subtype"); - lua_pop(L, 1); - } - - item.buyPrice = getField(L, tableIndex, "buy"); - item.sellPrice = getField(L, tableIndex, "sell"); - item.realName = getFieldString(L, tableIndex, "name"); - - items.push_back(item); - lua_pop(L, 6); - } - lua_pop(L, 1); - - Player* player = getPlayer(L, -1); - if (!player) { - reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); - pushBoolean(L, false); - return 1; - } - - //Close any eventual other shop window currently open. - player->closeShopWindow(false); - - Npc* npc = getScriptEnv()->getNpc(); - if (!npc) { - reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); - pushBoolean(L, false); - return 1; - } - - npc->addShopPlayer(player); - player->setShopOwner(npc, buyCallback, sellCallback); - player->openShopWindow(npc, items); - - pushBoolean(L, true); - return 1; -} - -int NpcScriptInterface::luaCloseShopWindow(lua_State* L) -{ - //closeShopWindow(cid) - Npc* npc = getScriptEnv()->getNpc(); - if (!npc) { - reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); - pushBoolean(L, false); - return 1; - } - - Player* player = getPlayer(L, 1); - if (!player) { - reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); - pushBoolean(L, false); - return 1; - } - - int32_t buyCallback; - int32_t sellCallback; - - Npc* merchant = player->getShopOwner(buyCallback, sellCallback); - - //Check if we actually have a shop window with this player. - if (merchant == npc) { - player->sendCloseShop(); - - if (buyCallback != -1) { - luaL_unref(L, LUA_REGISTRYINDEX, buyCallback); - } - - if (sellCallback != -1) { - luaL_unref(L, LUA_REGISTRYINDEX, sellCallback); - } - - player->setShopOwner(nullptr, -1, -1); - npc->removeShopPlayer(player); - } - - pushBoolean(L, true); - return 1; -} - -int NpcScriptInterface::luaDoSellItem(lua_State* L) -{ - //doSellItem(cid, itemid, amount, subtype, actionid, canDropOnMap) - Player* player = getPlayer(L, 1); - if (!player) { - reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); - pushBoolean(L, false); - return 1; - } - - uint32_t sellCount = 0; - - uint32_t itemId = getNumber(L, 2); - uint32_t amount = getNumber(L, 3); - uint32_t subType; - - int32_t n = getNumber(L, 4, -1); - if (n != -1) { - subType = n; - } else { - subType = 1; - } - - uint32_t actionId = getNumber(L, 5, 0); - bool canDropOnMap = getBoolean(L, 6, true); - - const ItemType& it = Item::items[itemId]; - if (it.stackable) { - while (amount > 0) { - int32_t stackCount = std::min(100, amount); - Item* item = Item::CreateItem(it.id, stackCount); - if (item && actionId != 0) { - item->setActionId(actionId); - } - - if (g_game.internalPlayerAddItem(player, item, canDropOnMap) != RETURNVALUE_NOERROR) { - delete item; - lua_pushnumber(L, sellCount); - return 1; - } - - amount -= stackCount; - sellCount += stackCount; - } - } else { - for (uint32_t i = 0; i < amount; ++i) { - Item* item = Item::CreateItem(it.id, subType); - if (item && actionId != 0) { - item->setActionId(actionId); - } - - if (g_game.internalPlayerAddItem(player, item, canDropOnMap) != RETURNVALUE_NOERROR) { - delete item; - lua_pushnumber(L, sellCount); - return 1; - } - - ++sellCount; - } - } - - lua_pushnumber(L, sellCount); - return 1; -} - -int NpcScriptInterface::luaNpcGetParameter(lua_State* L) -{ - // npc:getParameter(key) - const std::string& key = getString(L, 2); - Npc* npc = getUserdata(L, 1); - if (npc) { - auto it = npc->parameters.find(key); - if (it != npc->parameters.end()) { - pushString(L, it->second); - } else { - lua_pushnil(L); - } - } else { - lua_pushnil(L); - } - return 1; -} - -int NpcScriptInterface::luaNpcSetFocus(lua_State* L) -{ - // npc:setFocus(creature) - Creature* creature = getCreature(L, 2); - Npc* npc = getUserdata(L, 1); - if (npc) { - npc->setCreatureFocus(creature); - pushBoolean(L, true); - } else { - lua_pushnil(L); - } - return 1; -} - -int NpcScriptInterface::luaNpcOpenShopWindow(lua_State* L) -{ - // npc:openShopWindow(cid, items, buyCallback, sellCallback) - if (!isTable(L, 3)) { - reportErrorFunc("item list is not a table."); - pushBoolean(L, false); - return 1; - } - - Player* player = getPlayer(L, 2); - if (!player) { - reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); - pushBoolean(L, false); - return 1; - } - - Npc* npc = getUserdata(L, 1); - if (!npc) { - reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); - pushBoolean(L, false); - return 1; - } - - int32_t sellCallback = -1; - if (LuaScriptInterface::isFunction(L, 5)) { - sellCallback = luaL_ref(L, LUA_REGISTRYINDEX); - } - - int32_t buyCallback = -1; - if (LuaScriptInterface::isFunction(L, 4)) { - buyCallback = luaL_ref(L, LUA_REGISTRYINDEX); - } - - std::list items; - - lua_pushnil(L); - while (lua_next(L, 3) != 0) { - const auto tableIndex = lua_gettop(L); - ShopInfo item; - - item.itemId = getField(L, tableIndex, "id"); - item.subType = getField(L, tableIndex, "subType"); - if (item.subType == 0) { - item.subType = getField(L, tableIndex, "subtype"); - lua_pop(L, 1); - } - - item.buyPrice = getField(L, tableIndex, "buy"); - item.sellPrice = getField(L, tableIndex, "sell"); - item.realName = getFieldString(L, tableIndex, "name"); - - items.push_back(item); - lua_pop(L, 6); - } - lua_pop(L, 1); - - player->closeShopWindow(false); - npc->addShopPlayer(player); - - player->setShopOwner(npc, buyCallback, sellCallback); - player->openShopWindow(npc, items); - - pushBoolean(L, true); - return 1; -} - -int NpcScriptInterface::luaNpcCloseShopWindow(lua_State* L) -{ - // npc:closeShopWindow(player) - Player* player = getPlayer(L, 2); - if (!player) { - reportErrorFunc(getErrorDesc(LUA_ERROR_PLAYER_NOT_FOUND)); - pushBoolean(L, false); - return 1; - } - - Npc* npc = getUserdata(L, 1); - if (!npc) { - reportErrorFunc(getErrorDesc(LUA_ERROR_CREATURE_NOT_FOUND)); - pushBoolean(L, false); - return 1; - } - - int32_t buyCallback; - int32_t sellCallback; - - Npc* merchant = player->getShopOwner(buyCallback, sellCallback); - if (merchant == npc) { - player->sendCloseShop(); - if (buyCallback != -1) { - luaL_unref(L, LUA_REGISTRYINDEX, buyCallback); - } - - if (sellCallback != -1) { - luaL_unref(L, LUA_REGISTRYINDEX, sellCallback); - } - - player->setShopOwner(nullptr, -1, -1); - npc->removeShopPlayer(player); - } - - pushBoolean(L, true); - return 1; -} - -NpcEventsHandler::NpcEventsHandler(const std::string& file, Npc* npc) : - npc(npc), scriptInterface(npc->getScriptInterface()) -{ - loaded = scriptInterface->loadFile("data/npc/scripts/" + file, npc) == 0; - if (!loaded) { - std::cout << "[Warning - NpcScript::NpcScript] Can not load script: " << file << std::endl; - std::cout << scriptInterface->getLastLuaError() << std::endl; - } else { - creatureSayEvent = scriptInterface->getEvent("onCreatureSay"); - creatureDisappearEvent = scriptInterface->getEvent("onCreatureDisappear"); - creatureAppearEvent = scriptInterface->getEvent("onCreatureAppear"); - creatureMoveEvent = scriptInterface->getEvent("onCreatureMove"); - playerCloseChannelEvent = scriptInterface->getEvent("onPlayerCloseChannel"); - playerEndTradeEvent = scriptInterface->getEvent("onPlayerEndTrade"); - thinkEvent = scriptInterface->getEvent("onThink"); - } -} - -bool NpcEventsHandler::isLoaded() const -{ - return loaded; -} - -void NpcEventsHandler::onCreatureAppear(Creature* creature) -{ - if (creatureAppearEvent == -1) { - return; - } - - //onCreatureAppear(creature) - if (!scriptInterface->reserveScriptEnv()) { - std::cout << "[Error - NpcScript::onCreatureAppear] Call stack overflow" << std::endl; - return; - } - - ScriptEnvironment* env = scriptInterface->getScriptEnv(); - env->setScriptId(creatureAppearEvent, scriptInterface); - env->setNpc(npc); - - lua_State* L = scriptInterface->getLuaState(); - scriptInterface->pushFunction(creatureAppearEvent); - LuaScriptInterface::pushUserdata(L, creature); - LuaScriptInterface::setCreatureMetatable(L, -1, creature); - scriptInterface->callFunction(1); -} - -void NpcEventsHandler::onCreatureDisappear(Creature* creature) -{ - if (creatureDisappearEvent == -1) { - return; - } - - //onCreatureDisappear(creature) - if (!scriptInterface->reserveScriptEnv()) { - std::cout << "[Error - NpcScript::onCreatureDisappear] Call stack overflow" << std::endl; - return; - } - - ScriptEnvironment* env = scriptInterface->getScriptEnv(); - env->setScriptId(creatureDisappearEvent, scriptInterface); - env->setNpc(npc); - - lua_State* L = scriptInterface->getLuaState(); - scriptInterface->pushFunction(creatureDisappearEvent); - LuaScriptInterface::pushUserdata(L, creature); - LuaScriptInterface::setCreatureMetatable(L, -1, creature); - scriptInterface->callFunction(1); -} - -void NpcEventsHandler::onCreatureMove(Creature* creature, const Position& oldPos, const Position& newPos) -{ - if (creatureMoveEvent == -1) { - return; - } - - //onCreatureMove(creature, oldPos, newPos) - if (!scriptInterface->reserveScriptEnv()) { - std::cout << "[Error - NpcScript::onCreatureMove] Call stack overflow" << std::endl; - return; - } - - ScriptEnvironment* env = scriptInterface->getScriptEnv(); - env->setScriptId(creatureMoveEvent, scriptInterface); - env->setNpc(npc); - - lua_State* L = scriptInterface->getLuaState(); - scriptInterface->pushFunction(creatureMoveEvent); - LuaScriptInterface::pushUserdata(L, creature); - LuaScriptInterface::setCreatureMetatable(L, -1, creature); - LuaScriptInterface::pushPosition(L, oldPos); - LuaScriptInterface::pushPosition(L, newPos); - scriptInterface->callFunction(3); -} - -void NpcEventsHandler::onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) -{ - if (creatureSayEvent == -1) { - return; - } - - //onCreatureSay(creature, type, msg) - if (!scriptInterface->reserveScriptEnv()) { - std::cout << "[Error - NpcScript::onCreatureSay] Call stack overflow" << std::endl; - return; - } - - ScriptEnvironment* env = scriptInterface->getScriptEnv(); - env->setScriptId(creatureSayEvent, scriptInterface); - env->setNpc(npc); - - lua_State* L = scriptInterface->getLuaState(); - scriptInterface->pushFunction(creatureSayEvent); - LuaScriptInterface::pushUserdata(L, creature); - LuaScriptInterface::setCreatureMetatable(L, -1, creature); - lua_pushnumber(L, type); - LuaScriptInterface::pushString(L, text); - scriptInterface->callFunction(3); -} - -void NpcEventsHandler::onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, - uint8_t count, uint8_t amount, bool ignore, bool inBackpacks) -{ - if (callback == -1) { - return; - } - - //onBuy(player, itemid, count, amount, ignore, inbackpacks) - if (!scriptInterface->reserveScriptEnv()) { - std::cout << "[Error - NpcScript::onPlayerTrade] Call stack overflow" << std::endl; - return; - } - - ScriptEnvironment* env = scriptInterface->getScriptEnv(); - env->setScriptId(-1, scriptInterface); - env->setNpc(npc); - - lua_State* L = scriptInterface->getLuaState(); - LuaScriptInterface::pushCallback(L, callback); - LuaScriptInterface::pushUserdata(L, player); - LuaScriptInterface::setMetatable(L, -1, "Player"); - lua_pushnumber(L, itemId); - lua_pushnumber(L, count); - lua_pushnumber(L, amount); - LuaScriptInterface::pushBoolean(L, ignore); - LuaScriptInterface::pushBoolean(L, inBackpacks); - scriptInterface->callFunction(6); -} - -void NpcEventsHandler::onPlayerCloseChannel(Player* player) -{ - if (playerCloseChannelEvent == -1) { - return; - } - - //onPlayerCloseChannel(player) - if (!scriptInterface->reserveScriptEnv()) { - std::cout << "[Error - NpcScript::onPlayerCloseChannel] Call stack overflow" << std::endl; - return; - } - - ScriptEnvironment* env = scriptInterface->getScriptEnv(); - env->setScriptId(playerCloseChannelEvent, scriptInterface); - env->setNpc(npc); - - lua_State* L = scriptInterface->getLuaState(); - scriptInterface->pushFunction(playerCloseChannelEvent); - LuaScriptInterface::pushUserdata(L, player); - LuaScriptInterface::setMetatable(L, -1, "Player"); - scriptInterface->callFunction(1); -} - -void NpcEventsHandler::onPlayerEndTrade(Player* player) -{ - if (playerEndTradeEvent == -1) { - return; - } - - //onPlayerEndTrade(player) - if (!scriptInterface->reserveScriptEnv()) { - std::cout << "[Error - NpcScript::onPlayerEndTrade] Call stack overflow" << std::endl; - return; - } - - ScriptEnvironment* env = scriptInterface->getScriptEnv(); - env->setScriptId(playerEndTradeEvent, scriptInterface); - env->setNpc(npc); - - lua_State* L = scriptInterface->getLuaState(); - scriptInterface->pushFunction(playerEndTradeEvent); - LuaScriptInterface::pushUserdata(L, player); - LuaScriptInterface::setMetatable(L, -1, "Player"); - scriptInterface->callFunction(1); -} - -void NpcEventsHandler::onThink() -{ - if (thinkEvent == -1) { - return; - } - - //onThink() - if (!scriptInterface->reserveScriptEnv()) { - std::cout << "[Error - NpcScript::onThink] Call stack overflow" << std::endl; - return; - } - - ScriptEnvironment* env = scriptInterface->getScriptEnv(); - env->setScriptId(thinkEvent, scriptInterface); - env->setNpc(npc); - - scriptInterface->pushFunction(thinkEvent); - scriptInterface->callFunction(0); -} +} \ No newline at end of file diff --git a/src/npc.h b/src/npc.h index cd463bb..acf95ec 100644 --- a/src/npc.h +++ b/src/npc.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,86 +21,20 @@ #define FS_NPC_H_B090D0CB549D4435AFA03647195D156F #include "creature.h" -#include "luascript.h" #include class Npc; class Player; +class BehaviourDatabase; class Npcs { public: + static void loadNpcs(); static void reload(); }; -class NpcScriptInterface final : public LuaScriptInterface -{ - public: - NpcScriptInterface(); - - bool loadNpcLib(const std::string& file); - - private: - void registerFunctions(); - - static int luaActionSay(lua_State* L); - static int luaActionMove(lua_State* L); - static int luaActionMoveTo(lua_State* L); - static int luaActionTurn(lua_State* L); - static int luaActionFollow(lua_State* L); - static int luagetDistanceTo(lua_State* L); - static int luaSetNpcFocus(lua_State* L); - static int luaGetNpcCid(lua_State* L); - static int luaGetNpcParameter(lua_State* L); - static int luaOpenShopWindow(lua_State* L); - static int luaCloseShopWindow(lua_State* L); - static int luaDoSellItem(lua_State* L); - - // metatable - static int luaNpcGetParameter(lua_State* L); - static int luaNpcSetFocus(lua_State* L); - - static int luaNpcOpenShopWindow(lua_State* L); - static int luaNpcCloseShopWindow(lua_State* L); - - private: - bool initState() override; - bool closeState() override; - - bool libLoaded; -}; - -class NpcEventsHandler -{ - public: - NpcEventsHandler(const std::string& file, Npc* npc); - - void onCreatureAppear(Creature* creature); - void onCreatureDisappear(Creature* creature); - void onCreatureMove(Creature* creature, const Position& oldPos, const Position& newPos); - void onCreatureSay(Creature* creature, SpeakClasses, const std::string& text); - void onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count, uint8_t amount, bool ignore = false, bool inBackpacks = false); - void onPlayerCloseChannel(Player* player); - void onPlayerEndTrade(Player* player); - void onThink(); - - bool isLoaded() const; - - private: - Npc* npc; - NpcScriptInterface* scriptInterface; - - int32_t creatureAppearEvent = -1; - int32_t creatureDisappearEvent = -1; - int32_t creatureMoveEvent = -1; - int32_t creatureSayEvent = -1; - int32_t playerCloseChannelEvent = -1; - int32_t playerEndTradeEvent = -1; - int32_t thinkEvent = -1; - bool loaded = false; -}; - class Npc final : public Creature { public: @@ -110,53 +44,41 @@ class Npc final : public Creature Npc(const Npc&) = delete; Npc& operator=(const Npc&) = delete; - Npc* getNpc() override { + Npc* getNpc() final { return this; } - const Npc* getNpc() const override { + const Npc* getNpc() const final { return this; } - bool isPushable() const override { - return pushable && walkTicks != 0; + bool isPushable() const final { + return baseSpeed > 0; } - void setID() override { + void setID() final { if (id == 0) { id = npcAutoID++; } } - void removeList() override; - void addList() override; + void removeList() final; + void addList() final; static Npc* createNpc(const std::string& name); - bool canSee(const Position& pos) const override; + bool canSee(const Position& pos) const final; bool load(); void reload(); - const std::string& getName() const override { + const std::string& getName() const final { return name; } - const std::string& getNameDescription() const override { + const std::string& getNameDescription() const final { return name; } - CreatureType_t getType() const override { - return CREATURETYPE_NPC; - } - - uint8_t getSpeechBubble() const override { - return speechBubble; - } - void setSpeechBubble(const uint8_t bubble) { - speechBubble = bubble; - } - void doSay(const std::string& text); - void doSayToPlayer(Player* player, const std::string& text); void doMoveTo(const Position& pos); @@ -168,45 +90,38 @@ class Npc final : public Creature } void setMasterPos(Position pos, int32_t radius = 1) { masterPos = pos; - if (masterRadius == -1) { + if (masterRadius == 0) { masterRadius = radius; } } - void onPlayerCloseChannel(Player* player); - void onPlayerTrade(Player* player, int32_t callback, uint16_t itemId, uint8_t count, - uint8_t amount, bool ignore = false, bool inBackpacks = false); - void onPlayerEndTrade(Player* player, int32_t buyCallback, int32_t sellCallback); - void turnToCreature(Creature* creature); void setCreatureFocus(Creature* creature); - NpcScriptInterface* getScriptInterface(); - static uint32_t npcAutoID; - private: + protected: explicit Npc(const std::string& name); - void onCreatureAppear(Creature* creature, bool isLogin) override; - void onRemoveCreature(Creature* creature, bool isLogout) override; + 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) override; + const Tile* oldTile, const Position& oldPos, bool teleport) final; - void onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) override; - void onThink(uint32_t interval) override; - std::string getDescription(int32_t lookDistance) const override; + 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 override { - return !attackable; + bool isImmune(CombatType_t) const final { + return true; } - bool isImmune(ConditionType_t) const override { - return !attackable; + bool isImmune(ConditionType_t) const final { + return true; } - bool isAttackable() const override { - return attackable; + bool isAttackable() const final { + return false; } - bool getNextStep(Direction& dir, uint32_t& flags) override; + bool getNextStep(Direction& dir, uint32_t& flags) final; void setIdle(bool idle); void updateIdleStatus(); @@ -215,41 +130,29 @@ class Npc final : public Creature bool getRandomStep(Direction& dir) const; void reset(); - bool loadFromXml(); - void addShopPlayer(Player* player); - void removeShopPlayer(Player* player); - void closeAllShopWindows(); - - std::map parameters; - - std::set shopPlayerSet; std::set spectators; std::string name; std::string filename; - NpcEventsHandler* npcEventHandler; - Position masterPos; - uint32_t walkTicks; - int32_t focusCreature; - int32_t masterRadius; + uint32_t lastTalkCreature; + uint32_t focusCreature; + uint32_t masterRadius; - uint8_t speechBubble; + int64_t conversationStartTime; + int64_t conversationEndTime; + int64_t staticMovementTime; - bool floorChange; - bool attackable; - bool ignoreHeight; bool loaded; bool isIdle; - bool pushable; - static NpcScriptInterface* scriptInterface; + BehaviourDatabase* behaviourDatabase; friend class Npcs; - friend class NpcScriptInterface; + friend class BehaviourDatabase; }; #endif diff --git a/src/otpch.cpp b/src/otpch.cpp index c60a95d..5db3123 100644 --- a/src/otpch.cpp +++ b/src/otpch.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/otpch.h b/src/otpch.h index e154370..a2ff7c1 100644 --- a/src/otpch.h +++ b/src/otpch.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/otserv.cpp b/src/otserv.cpp index c47501c..ce6e996 100644 --- a/src/otserv.cpp +++ b/src/otserv.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,19 +23,18 @@ #include "game.h" -#include "iomarket.h" +#ifndef _WIN32 +#include // for sigemptyset() +#endif #include "configmanager.h" #include "scriptmanager.h" #include "rsa.h" -#include "protocolold.h" #include "protocollogin.h" #include "protocolstatus.h" #include "databasemanager.h" #include "scheduler.h" #include "databasetasks.h" -#include "script.h" -#include DatabaseTasks g_databaseTasks; Dispatcher g_dispatcher; @@ -45,7 +44,6 @@ Game g_game; ConfigManager g_config; Monsters g_monsters; Vocations g_vocations; -extern Scripts* g_scripts; RSA g_RSA; std::mutex g_loaderLock; @@ -58,9 +56,9 @@ void startupErrorMessage(const std::string& errorStr) g_loaderSignal.notify_all(); } -void mainLoader(int argc, char* argv[], ServiceManager* services); +void mainLoader(int argc, char* argv[], ServiceManager* servicer); -[[noreturn]] void badAllocationHandler() +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"); @@ -73,6 +71,15 @@ 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(); @@ -84,6 +91,19 @@ int main(int argc, char* argv[]) 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; @@ -126,21 +146,6 @@ void mainLoader(int, char*[], ServiceManager* services) std::cout << "Visit our forum for updates, support, and resources: http://otland.net/." << std::endl; std::cout << std::endl; - // check if config.lua or config.lua.dist exist - std::ifstream c_test("./config.lua"); - if (!c_test.is_open()) { - std::ifstream config_lua_dist("./config.lua.dist"); - if (config_lua_dist.is_open()) { - std::cout << ">> copying config.lua.dist to config.lua" << std::endl; - std::ofstream config_lua("config.lua"); - config_lua << config_lua_dist.rdbuf(); - config_lua.close(); - config_lua_dist.close(); - } - } else { - c_test.close(); - } - // read global config std::cout << ">> Loading config" << std::endl; if (!g_config.load()) { @@ -160,14 +165,16 @@ void mainLoader(int, char*[], ServiceManager* services) //set RSA key try { g_RSA.loadPEM("key.pem"); - } catch(const std::exception& e) { + } + catch (const std::exception& e) { startupErrorMessage(e.what()); return; } std::cout << ">> Establishing database connection..." << std::flush; - if (!Database::getInstance().connect()) { + Database* db = Database::getInstance(); + if (!db->connect()) { startupErrorMessage("Failed to connect to database."); return; } @@ -183,8 +190,6 @@ void mainLoader(int, char*[], ServiceManager* services) } g_databaseTasks.start(); - DatabaseManager::updateDatabase(); - if (g_config.getBoolean(ConfigManager::OPTIMIZE_DATABASE) && !DatabaseManager::optimizeTables()) { std::cout << "> No tables were optimized." << std::endl; } @@ -198,42 +203,26 @@ void mainLoader(int, char*[], ServiceManager* services) // load item data std::cout << ">> Loading items" << std::endl; - if (!Item::items.loadFromOtb("data/items/items.otb")) { - startupErrorMessage("Unable to load items (OTB)!"); - return; - } - - if (!Item::items.loadFromXml()) { - startupErrorMessage("Unable to load items (XML)!"); + if (!Item::items.loadItems()) { + startupErrorMessage("Unable to load items (SRV)!"); return; } std::cout << ">> Loading script systems" << std::endl; - if (!ScriptingManager::getInstance().loadScriptSystems()) { + if (!ScriptingManager::getInstance()->loadScriptSystems()) { startupErrorMessage("Failed to load script systems"); return; } - std::cout << ">> Loading lua scripts" << std::endl; - if (!g_scripts->loadScripts("scripts", false, false)) { - startupErrorMessage("Failed to load lua scripts"); - return; - } - std::cout << ">> Loading monsters" << std::endl; if (!g_monsters.loadFromXml()) { startupErrorMessage("Unable to load monsters!"); return; } - std::cout << ">> Loading lua monsters" << std::endl; - if (!g_scripts->loadScripts("monster", false, false)) { - startupErrorMessage("Failed to load lua monsters"); - return; - } - std::cout << ">> Loading outfits" << std::endl; - if (!Outfits::getInstance().loadFromXml()) { + auto& outfits = Outfits::getInstance(); + if (!outfits.loadFromXml()) { startupErrorMessage("Unable to load outfits!"); return; } @@ -266,14 +255,11 @@ void mainLoader(int, char*[], ServiceManager* services) g_game.setGameState(GAME_STATE_INIT); // Game client protocols - services->add(static_cast(g_config.getNumber(ConfigManager::GAME_PORT))); - services->add(static_cast(g_config.getNumber(ConfigManager::LOGIN_PORT))); + services->add(g_config.getNumber(ConfigManager::GAME_PORT)); + services->add(g_config.getNumber(ConfigManager::LOGIN_PORT)); // OT protocols - services->add(static_cast(g_config.getNumber(ConfigManager::STATUS_PORT))); - - // Legacy login protocol - services->add(static_cast(g_config.getNumber(ConfigManager::LOGIN_PORT))); + services->add(g_config.getNumber(ConfigManager::STATUS_PORT)); RentPeriod_t rentPeriod; std::string strRentPeriod = asLowerCaseString(g_config.getString(ConfigManager::HOUSE_RENT_PERIOD)); @@ -292,9 +278,6 @@ void mainLoader(int, char*[], ServiceManager* services) g_game.map.houses.payHouses(rentPeriod); - IOMarket::checkExpiredOffers(); - IOMarket::getInstance().updateStatistics(); - std::cout << ">> Loaded all modules, server starting up..." << std::endl; #ifndef _WIN32 diff --git a/src/outputmessage.cpp b/src/outputmessage.cpp index b4c0f77..0d3d597 100644 --- a/src/outputmessage.cpp +++ b/src/outputmessage.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,6 +29,14 @@ 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 + struct rebind {typedef LockfreePoolingAllocator other;}; +}; + void OutputMessagePool::scheduleSendAll() { auto functor = std::bind(&OutputMessagePool::sendAll, this); @@ -71,7 +79,5 @@ void OutputMessagePool::removeProtocolFromAutosend(const Protocol_ptr& protocol) OutputMessage_ptr OutputMessagePool::getOutputMessage() { - // LockfreePoolingAllocator will leave (void* allocate) ill-formed because - // of sizeof(T), so this guaranatees that only one list will be initialized - return std::allocate_shared(LockfreePoolingAllocator()); + return std::allocate_shared(OutputMessageAllocator()); } diff --git a/src/outputmessage.h b/src/outputmessage.h index 1a78599..3dde508 100644 --- a/src/outputmessage.h +++ b/src/outputmessage.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,80 +28,76 @@ class Protocol; class OutputMessage : public NetworkMessage { - public: - OutputMessage() = default; +public: + OutputMessage() = default; - // non-copyable - OutputMessage(const OutputMessage&) = delete; - OutputMessage& operator=(const OutputMessage&) = delete; + // non-copyable + OutputMessage(const OutputMessage&) = delete; + OutputMessage& operator=(const OutputMessage&) = delete; - uint8_t* getOutputBuffer() { - return buffer + outputBufferStart; - } + uint8_t* getOutputBuffer() { + return buffer + outputBufferStart; + } - void writeMessageLength() { - add_header(info.length); - } + void writeMessageLength() { + add_header(info.length); + } - void addCryptoHeader(bool addChecksum) { - if (addChecksum) { - add_header(adlerChecksum(buffer + outputBufferStart, info.length)); - } + void addCryptoHeader() { + writeMessageLength(); + } - 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; + } - void append(const NetworkMessage& msg) { - auto msgLen = msg.getLength(); - memcpy(buffer + info.position, msg.getBuffer() + 8, 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; + } - void append(const OutputMessage_ptr& msg) { - auto msgLen = msg->getLength(); - memcpy(buffer + info.position, msg->getBuffer() + 8, msgLen); - info.length += msgLen; - info.position += msgLen; - } +protected: + template + 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); + } - private: - template - 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; + MsgSize_t outputBufferStart = INITIAL_BUFFER_POSITION; }; class OutputMessagePool { - public: - // non-copyable - OutputMessagePool(const OutputMessagePool&) = delete; - OutputMessagePool& operator=(const OutputMessagePool&) = delete; +public: + // non-copyable + OutputMessagePool(const OutputMessagePool&) = delete; + OutputMessagePool& operator=(const OutputMessagePool&) = delete; - static OutputMessagePool& getInstance() { - static OutputMessagePool instance; - return instance; - } + static OutputMessagePool& getInstance() { + static OutputMessagePool instance; + return instance; + } - void sendAll(); - void scheduleSendAll(); + void sendAll(); + void scheduleSendAll(); - static OutputMessage_ptr getOutputMessage(); + 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 bufferedProtocols; + 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 bufferedProtocols; }; diff --git a/src/party.cpp b/src/party.cpp index 1f1cd50..e0d80a4 100644 --- a/src/party.cpp +++ b/src/party.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -39,13 +39,13 @@ void Party::disband() return; } + Player* currentLeader = leader; leader = nullptr; currentLeader->setParty(nullptr); currentLeader->sendClosePrivate(CHANNEL_PARTY); g_game.updatePlayerShield(currentLeader); - g_game.updatePlayerHelpers(*currentLeader); currentLeader->sendCreatureSkull(currentLeader); currentLeader->sendTextMessage(MESSAGE_INFO_DESCR, "Your party has been disbanded."); @@ -70,8 +70,8 @@ void Party::disband() member->sendCreatureSkull(currentLeader); currentLeader->sendCreatureSkull(member); - g_game.updatePlayerHelpers(*member); } + memberList.clear(); delete this; } @@ -112,12 +112,10 @@ bool Party::leaveParty(Player* player) player->setParty(nullptr); player->sendClosePrivate(CHANNEL_PARTY); g_game.updatePlayerShield(player); - g_game.updatePlayerHelpers(*player); for (Player* member : memberList) { member->sendCreatureSkull(player); player->sendPlayerPartyIcons(member); - g_game.updatePlayerHelpers(*member); } leader->sendCreatureSkull(player); @@ -127,6 +125,7 @@ bool Party::leaveParty(Player* player) player->sendTextMessage(MESSAGE_INFO_DESCR, "You have left the party."); updateSharedExperience(); + updateVocationsList(); clearPlayerPoints(player); @@ -213,10 +212,9 @@ bool Party::joinParty(Player& player) memberList.push_back(&player); - g_game.updatePlayerHelpers(player); - player.removePartyInvitation(this); updateSharedExperience(); + updateVocationsList(); const std::string& leaderName = leader->getName(); ss.str(std::string()); @@ -244,12 +242,6 @@ bool Party::removeInvite(Player& player, bool removeFromPlayer/* = true*/) if (empty()) { disband(); - } else { - for (Player* member : memberList) { - g_game.updatePlayerHelpers(*member); - } - - g_game.updatePlayerHelpers(*leader); } return true; @@ -277,7 +269,7 @@ bool Party::invitePlayer(Player& player) std::ostringstream ss; ss << player.getName() << " has been invited."; - if (empty()) { + if (memberList.empty() && inviteList.empty()) { ss << " Open the party channel to communicate with your members."; g_game.updatePlayerShield(leader); leader->sendCreatureSkull(leader); @@ -287,11 +279,6 @@ bool Party::invitePlayer(Player& player) inviteList.push_back(&player); - for (Player* member : memberList) { - g_game.updatePlayerHelpers(*member); - } - g_game.updatePlayerHelpers(*leader); - leader->sendCreatureShield(&player); player.sendCreatureShield(leader); @@ -336,6 +323,15 @@ void Party::broadcastPartyMessage(MessageClasses msgClass, const std::string& ms } } +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) { @@ -347,6 +343,30 @@ void Party::updateSharedExperience() } } +void Party::updateVocationsList() +{ + std::set 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(size * (10 + (size - 1) * 5)) / 100.f; + } else { + extraExpRate = 0.20f; + } +} + bool Party::setSharedExperience(Player* player, bool sharedExpActive) { if (!player || leader != player) { @@ -399,7 +419,7 @@ bool Party::canUseSharedExperience(const Player* player) const } } - uint32_t minLevel = static_cast(std::ceil((static_cast(highestLevel) * 2) / 3)); + uint32_t minLevel = static_cast(std::ceil((static_cast(highestLevel) * 2) / 3)); if (player->getLevel() < minLevel) { return false; } diff --git a/src/party.h b/src/party.h index 50c10c0..96fce94 100644 --- a/src/party.h +++ b/src/party.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,7 +26,7 @@ class Player; class Party; -using PlayerVector = std::vector; +typedef std::vector PlayerVector; class Party { @@ -61,12 +61,13 @@ class Party 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(); } bool canOpenCorpse(uint32_t ownerId) const; - void shareExperience(uint64_t experience, Creature* source = nullptr); + void shareExperience(uint64_t experience, Creature* source/* = nullptr*/); bool setSharedExperience(Player* player, bool sharedExpActive); bool isSharedExperienceActive() const { return sharedExpActive; @@ -77,10 +78,12 @@ class Party bool canUseSharedExperience(const Player* player) const; void updateSharedExperience(); + void updateVocationsList(); + void updatePlayerTicks(Player* player, uint32_t points); void clearPlayerPoints(Player* player); - private: + protected: bool canEnableSharedExperience(); std::map ticksMap; @@ -90,6 +93,8 @@ class Party Player* leader; + float extraExpRate = 0.20f; + bool sharedExpActive = false; bool sharedExpEnabled = false; }; diff --git a/src/player.cpp b/src/player.cpp index 41a72ff..a4936c0 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,14 +32,12 @@ #include "monster.h" #include "movement.h" #include "scheduler.h" -#include "weapons.h" extern ConfigManager g_config; extern Game g_game; extern Chat* g_chat; extern Vocations g_vocations; extern MoveEvents* g_moveEvents; -extern Weapons* g_weapons; extern CreatureEvents* g_creatureEvents; extern Events* g_events; @@ -48,9 +46,8 @@ MuteCountMap Player::muteCountMap; uint32_t Player::playerAutoID = 0x10000000; Player::Player(ProtocolGame_ptr p) : - Creature(), lastPing(OTSYS_TIME()), lastPong(lastPing), inbox(new Inbox(ITEM_INBOX)), client(std::move(p)) + Creature(), lastPing(OTSYS_TIME()), lastPong(lastPing), client(std::move(p)) { - inbox->incrementReferenceCounter(); } Player::~Player() @@ -63,12 +60,9 @@ Player::~Player() } for (const auto& it : depotLockerMap) { - it.second->removeInbox(inbox); it.second->decrementReferenceCounter(); } - inbox->decrementReferenceCounter(); - setWriteItem(nullptr); setEditHouse(nullptr); } @@ -135,53 +129,23 @@ std::string Player::getDescription(int32_t lookDistance) const } } - if (party) { + if (guild && guildRank) { if (lookDistance == -1) { - s << " Your party has "; + s << " You are "; } else if (sex == PLAYERSEX_FEMALE) { - s << " She is in a party with "; + s << " She is "; } else { - s << " He is in a party with "; + s << " He is "; } - size_t memberCount = party->getMemberCount() + 1; - if (memberCount == 1) { - s << "1 member and "; - } else { - s << memberCount << " members and "; + s << guildRank->name << " of the " << guild->getName(); + if (!guildNick.empty()) { + s << " (" << guildNick << ')'; } - size_t invitationCount = party->getInvitationCount(); - if (invitationCount == 1) { - s << "1 pending invitation."; - } else { - s << invitationCount << " pending invitations."; - } + s << "."; } - if (!guild || !guildRank) { - return s.str(); - } - - if (lookDistance == -1) { - s << " You are "; - } else if (sex == PLAYERSEX_FEMALE) { - s << " She is "; - } else { - s << " He is "; - } - - s << guildRank->name << " of the " << guild->getName(); - if (!guildNick.empty()) { - s << " (" << guildNick << ')'; - } - - size_t memberCount = guild->getMemberCount(); - if (memberCount == 1) { - s << ", which has 1 member, " << guild->getMembersOnline().size() << " of them online."; - } else { - s << ", which has " << memberCount << " members, " << guild->getMembersOnline().size() << " of them online."; - } return s.str(); } @@ -203,104 +167,43 @@ void Player::removeConditionSuppressions(uint32_t conditions) conditionSuppressions &= ~conditions; } -Item* Player::getWeapon(slots_t slot, bool ignoreAmmo) const +Item* Player::getWeapon() const { - Item* item = inventory[slot]; - if (!item) { - return nullptr; - } - - WeaponType_t weaponType = item->getWeaponType(); - if (weaponType == WEAPON_NONE || weaponType == WEAPON_SHIELD || weaponType == WEAPON_AMMO) { - return nullptr; - } - - if (!ignoreAmmo && weaponType == WEAPON_DISTANCE) { - const ItemType& it = Item::items[item->getID()]; - if (it.ammoType != AMMO_NONE) { - Item* ammoItem = inventory[CONST_SLOT_AMMO]; - if (!ammoItem || ammoItem->getAmmoType() != it.ammoType) { - return nullptr; - } - item = ammoItem; - } - } - return item; -} - -Item* Player::getWeapon(bool ignoreAmmo/* = false*/) const -{ - Item* item = getWeapon(CONST_SLOT_LEFT, ignoreAmmo); - if (item) { + Item* item = inventory[CONST_SLOT_LEFT]; + if (item && item->getWeaponType() != WEAPON_NONE && item->getWeaponType() != WEAPON_SHIELD && item->getWeaponType() != WEAPON_AMMO) { return item; } - item = getWeapon(CONST_SLOT_RIGHT, ignoreAmmo); - if (item) { + item = inventory[CONST_SLOT_RIGHT]; + if (item && item->getWeaponType() != WEAPON_NONE && item->getWeaponType() != WEAPON_SHIELD && item->getWeaponType() != WEAPON_AMMO) { return item; } + return nullptr; } -WeaponType_t Player::getWeaponType() const +Item* Player::getAmmunition() const { - Item* item = getWeapon(); - if (!item) { - return WEAPON_NONE; - } - return item->getWeaponType(); -} - -int32_t Player::getWeaponSkill(const Item* item) const -{ - if (!item) { - return getSkillLevel(SKILL_FIST); - } - - int32_t attackSkill; - - WeaponType_t weaponType = item->getWeaponType(); - switch (weaponType) { - case WEAPON_SWORD: { - attackSkill = getSkillLevel(SKILL_SWORD); - break; - } - - case WEAPON_CLUB: { - attackSkill = getSkillLevel(SKILL_CLUB); - break; - } - - case WEAPON_AXE: { - attackSkill = getSkillLevel(SKILL_AXE); - break; - } - - case WEAPON_DISTANCE: { - attackSkill = getSkillLevel(SKILL_DISTANCE); - break; - } - - default: { - attackSkill = 0; - break; - } - } - return attackSkill; + return inventory[CONST_SLOT_AMMO]; } int32_t Player::getArmor() const { - int32_t armor = 0; + int32_t armor = 0; // base armor - static const slots_t armorSlots[] = {CONST_SLOT_HEAD, CONST_SLOT_NECKLACE, CONST_SLOT_ARMOR, CONST_SLOT_LEGS, CONST_SLOT_FEET, CONST_SLOT_RING}; + static const slots_t armorSlots[] = { CONST_SLOT_HEAD, CONST_SLOT_NECKLACE, CONST_SLOT_ARMOR, CONST_SLOT_LEGS, CONST_SLOT_FEET, CONST_SLOT_RING }; for (slots_t slot : armorSlots) { Item* inventoryItem = inventory[slot]; if (inventoryItem) { armor += inventoryItem->getArmor(); } } - return static_cast(armor * vocation->armorMultiplier); + + if (armor > 1) { + armor = rand() % (armor >> 1) + (armor >> 1); + } + + return armor; } void Player::getShieldAndWeapon(const Item*& shield, const Item*& weapon) const @@ -316,7 +219,7 @@ void Player::getShieldAndWeapon(const Item*& shield, const Item*& weapon) const switch (item->getWeaponType()) { case WEAPON_NONE: - break; + break; case WEAPON_SHIELD: { if (!shield || item->getDefense() > shield->getDefense()) { @@ -333,56 +236,67 @@ void Player::getShieldAndWeapon(const Item*& shield, const Item*& weapon) const } } -int32_t Player::getDefense() const +int32_t Player::getDefense() { + int32_t totalDefense = 5; int32_t defenseSkill = getSkillLevel(SKILL_FIST); - int32_t defenseValue = 7; + const Item* weapon; const Item* shield; getShieldAndWeapon(shield, weapon); if (weapon) { - defenseValue = weapon->getDefense() + weapon->getExtraDefense(); - defenseSkill = getWeaponSkill(weapon); - } + totalDefense = weapon->getDefense() + 1; - if (shield) { - defenseValue = weapon != nullptr ? shield->getDefense() + weapon->getExtraDefense() : shield->getDefense(); - defenseSkill = getSkillLevel(SKILL_SHIELD); - } - - if (defenseSkill == 0) { - switch (fightMode) { - case FIGHTMODE_ATTACK: - case FIGHTMODE_BALANCED: - return 1; - - case FIGHTMODE_DEFENSE: - return 2; + switch (weapon->getWeaponType()) { + case WEAPON_AXE: + defenseSkill = SKILL_AXE; + break; + case WEAPON_SWORD: + defenseSkill = SKILL_SWORD; + break; + case WEAPON_CLUB: + defenseSkill = SKILL_CLUB; + break; + case WEAPON_DISTANCE: + case WEAPON_AMMO: + defenseSkill = SKILL_DISTANCE; + break; + default: + break; } } - return (defenseSkill / 4. + 2.23) * defenseValue * 0.15 * getDefenseFactor() * vocation->defenseMultiplier; + if (shield) { + totalDefense = shield->getDefense() + 1; + defenseSkill = getSkillLevel(SKILL_SHIELD); + } + + fightMode_t attackMode = getFightMode(); + + if ((followCreature || !attackedCreature) && earliestAttackTime <= OTSYS_TIME()) { + attackMode = FIGHTMODE_DEFENSE; + } + + if (attackMode == FIGHTMODE_ATTACK) { + totalDefense -= 4 * totalDefense / 10; + } else if (attackMode == FIGHTMODE_DEFENSE) { + totalDefense += 8 * totalDefense / 10; + } + + if (totalDefense) { + int32_t formula = (5 * (defenseSkill) + 50) * totalDefense; + int32_t randresult = rand() % 100; + + totalDefense = formula * ((rand() % 100 + randresult) / 2) / 10000.; + } + + return totalDefense; } -float Player::getAttackFactor() const +fightMode_t Player::getFightMode() const { - switch (fightMode) { - case FIGHTMODE_ATTACK: return 1.0f; - case FIGHTMODE_BALANCED: return 1.2f; - case FIGHTMODE_DEFENSE: return 2.0f; - default: return 1.0f; - } -} - -float Player::getDefenseFactor() const -{ - switch (fightMode) { - case FIGHTMODE_ATTACK: return (OTSYS_TIME() - lastAttack) < getAttackSpeed() ? 0.5f : 1.0f; - case FIGHTMODE_BALANCED: return (OTSYS_TIME() - lastAttack) < getAttackSpeed() ? 0.75f : 1.0f; - case FIGHTMODE_DEFENSE: return 1.0f; - default: return 1.0f; - } + return fightMode; } uint16_t Player::getClientIcons() const @@ -394,19 +308,6 @@ uint16_t Player::getClientIcons() const } } - if (pzLocked) { - icons |= ICON_REDSWORDS; - } - - if (tile->hasFlag(TILESTATE_PROTECTIONZONE)) { - icons |= ICON_PIGEON; - - // Don't show ICON_SWORDS if player is in protection zone. - if (hasBitSet(ICON_SWORDS, icons)) { - icons &= ~ICON_SWORDS; - } - } - // Game client debugs with 10 or more icons // so let's prevent that from happening. std::bitset<20> icon_bitset(static_cast(icons)); @@ -532,18 +433,9 @@ void Player::addContainer(uint8_t cid, Container* container) return; } - if (container->getID() == ITEM_BROWSEFIELD) { - container->incrementReferenceCounter(); - } - auto it = openContainers.find(cid); if (it != openContainers.end()) { OpenContainer& openContainer = it->second; - Container* oldContainer = openContainer.container; - if (oldContainer->getID() == ITEM_BROWSEFIELD) { - oldContainer->decrementReferenceCounter(); - } - openContainer.container = container; openContainer.index = 0; } else { @@ -561,13 +453,7 @@ void Player::closeContainer(uint8_t cid) return; } - OpenContainer openContainer = it->second; - Container* container = openContainer.container; openContainers.erase(it); - - if (container && container->getID() == ITEM_BROWSEFIELD) { - container->decrementReferenceCounter(); - } } void Player::setContainerIndex(uint8_t cid, uint16_t index) @@ -621,7 +507,7 @@ uint16_t Player::getLookCorpse() const } } -void Player::addStorageValue(const uint32_t key, const int32_t value, const bool isLogin/* = false*/) +void Player::addStorageValue(const uint32_t key, const int32_t value) { if (IS_IN_KEYRANGE(key, RESERVED_RANGE)) { if (IS_IN_KEYRANGE(key, OUTFITS_RANGE)) { @@ -630,27 +516,15 @@ void Player::addStorageValue(const uint32_t key, const int32_t value, const bool value & 0xFF ); return; - } else if (IS_IN_KEYRANGE(key, MOUNTS_RANGE)) { - // do nothing - } else { + } + else { std::cout << "Warning: unknown reserved key: " << key << " player: " << getName() << std::endl; return; } } if (value != -1) { - int32_t oldValue; - getStorageValue(key, oldValue); - storageMap[key] = value; - - if (!isLogin) { - auto currentFrameTime = g_dispatcher.getDispatcherCycle(); - if (lastQuestlogUpdate != currentFrameTime && g_game.quests.isQuestStorage(key, value, oldValue)) { - lastQuestlogUpdate = currentFrameTime; - sendTextMessage(MESSAGE_EVENT_ADVANCE, "Your questlog has been updated."); - } - } } else { storageMap.erase(key); } @@ -660,7 +534,7 @@ bool Player::getStorageValue(const uint32_t key, int32_t& value) const { auto it = storageMap.find(key); if (it == storageMap.end()) { - value = -1; + value = 0; return false; } @@ -689,68 +563,18 @@ bool Player::canSeeCreature(const Creature* creature) const if (!creature->getPlayer() && !canSeeInvisibility() && creature->isInvisible()) { return false; } + return true; } -bool Player::canWalkthrough(const Creature* creature) const +void Player::onReceiveMail(uint32_t townId) const { - if (group->access || creature->isInGhostMode()) { - return true; - } - - const Player* player = creature->getPlayer(); - if (!player) { - return false; - } - - const Tile* playerTile = player->getTile(); - if (!playerTile || (!playerTile->hasFlag(TILESTATE_PROTECTIONZONE) && player->getLevel() > static_cast(g_config.getNumber(ConfigManager::PROTECTION_LEVEL)))) { - return false; - } - - const Item* playerTileGround = playerTile->getGround(); - if (!playerTileGround || !playerTileGround->hasWalkStack()) { - return false; - } - - Player* thisPlayer = const_cast(this); - if ((OTSYS_TIME() - lastWalkthroughAttempt) > 2000) { - thisPlayer->setLastWalkthroughAttempt(OTSYS_TIME()); - return false; - } - - if (creature->getPosition() != lastWalkthroughPosition) { - thisPlayer->setLastWalkthroughPosition(creature->getPosition()); - return false; - } - - thisPlayer->setLastWalkthroughPosition(creature->getPosition()); - return true; -} - -bool Player::canWalkthroughEx(const Creature* creature) const -{ - if (group->access) { - return true; - } - - const Player* player = creature->getPlayer(); - if (!player) { - return false; - } - - const Tile* playerTile = player->getTile(); - return playerTile && (playerTile->hasFlag(TILESTATE_PROTECTIONZONE) || player->getLevel() <= static_cast(g_config.getNumber(ConfigManager::PROTECTION_LEVEL))); -} - -void Player::onReceiveMail() const -{ - if (isNearDepotBox()) { + if (isNearDepotBox(townId)) { sendTextMessage(MESSAGE_EVENT_ADVANCE, "New mail has arrived."); } } -bool Player::isNearDepotBox() const +bool Player::isNearDepotBox(uint32_t townId) const { const Position& pos = getPosition(); for (int32_t cx = -1; cx <= 1; ++cx) { @@ -760,47 +584,35 @@ bool Player::isNearDepotBox() const continue; } - if (tile->hasFlag(TILESTATE_DEPOT)) { - return true; + if (DepotLocker* depotLocker = tile->getDepotLocker()) { + if (depotLocker->getDepotId() == townId) { + return true; + } } } } return false; } -DepotChest* Player::getDepotChest(uint32_t depotId, bool autoCreate) -{ - auto it = depotChests.find(depotId); - if (it != depotChests.end()) { - return it->second; - } - - if (!autoCreate) { - return nullptr; - } - - DepotChest* depotChest = new DepotChest(ITEM_DEPOT); - depotChest->incrementReferenceCounter(); - depotChest->setMaxDepotItems(getMaxDepotItems()); - depotChests[depotId] = depotChest; - return depotChest; -} - -DepotLocker* Player::getDepotLocker(uint32_t depotId) +DepotLocker* Player::getDepotLocker(uint32_t depotId, bool autoCreate) { auto it = depotLockerMap.find(depotId); if (it != depotLockerMap.end()) { - inbox->setParent(it->second); return it->second; } - DepotLocker* depotLocker = new DepotLocker(ITEM_LOCKER1); - depotLocker->setDepotId(depotId); - depotLocker->internalAddThing(Item::CreateItem(ITEM_MARKET)); - depotLocker->internalAddThing(inbox); - depotLocker->internalAddThing(getDepotChest(depotId, true)); - depotLockerMap[depotId] = depotLocker; - return depotLocker; + if (autoCreate) { + DepotLocker* depotLocker = new DepotLocker(ITEM_LOCKER1); + depotLocker->setDepotId(depotId); + Item* depotItem = Item::CreateItem(ITEM_DEPOT); + if (depotItem) { + depotLocker->internalAddThing(depotItem); + } + depotLockerMap[depotId] = depotLocker; + return depotLocker; + } + + return nullptr; } void Player::sendCancelMessage(ReturnValue message) const @@ -812,7 +624,6 @@ void Player::sendStats() { if (client) { client->sendStats(); - lastStatsTrainingTime = getOfflineTrainingTime() / 60 / 1000; } } @@ -910,20 +721,10 @@ void Player::sendAddContainerItem(const Container* container, const Item* item) continue; } - uint16_t slot = openContainer.index; - if (container->getID() == ITEM_BROWSEFIELD) { - uint16_t containerSize = container->size() - 1; - uint16_t pageEnd = openContainer.index + container->capacity() - 1; - if (containerSize > pageEnd) { - slot = pageEnd; - item = container->getItemByIndex(pageEnd); - } else { - slot = containerSize; - } - } else if (openContainer.index >= container->capacity()) { + if (openContainer.index >= container->capacity()) { item = container->getItemByIndex(openContainer.index - 1); } - client->sendAddContainerItem(it.first, slot, item); + client->sendAddContainerItem(it.first, item); } } @@ -970,12 +771,12 @@ void Player::sendRemoveContainerItem(const Container* container, uint16_t slot) sendContainer(it.first, container, false, firstIndex); } - client->sendRemoveContainerItem(it.first, std::max(slot, firstIndex), container->getItemByIndex(container->capacity() + firstIndex)); + client->sendRemoveContainerItem(it.first, std::max(slot, firstIndex)); } } void Player::onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, - const ItemType& oldType, const Item* newItem, const ItemType& newType) + const ItemType& oldType, const Item* newItem, const ItemType& newType) { Creature::onUpdateTileItem(tile, pos, oldItem, oldType, newItem, newType); @@ -991,7 +792,7 @@ void Player::onUpdateTileItem(const Tile* tile, const Position& pos, const Item* } void Player::onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, - const Item* item) + const Item* item) { Creature::onRemoveTileItem(tile, pos, iType, item); @@ -1012,8 +813,6 @@ void Player::onCreatureAppear(Creature* creature, bool isLogin) Creature::onCreatureAppear(creature, isLogin); if (isLogin && creature == this) { - sendItems(); - for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { Item* item = inventory[slot]; if (item) { @@ -1031,9 +830,6 @@ void Player::onCreatureAppear(Creature* creature, bool isLogin) if (bed) { bed->wakeUp(this); } - - Account account = IOLoginData::loadAccount(accountNumber); - Game::updatePremium(account); std::cout << name << " has logged in." << std::endl; @@ -1086,20 +882,8 @@ void Player::onChangeZone(ZoneType_t zone) setAttackedCreature(nullptr); onAttackedCreatureDisappear(false); } - - if (!group->access && isMounted()) { - dismount(); - g_game.internalCreatureChangeOutfit(this, defaultOutfit); - wasMounted = true; - } - } else { - if (wasMounted) { - toggleMount(true); - wasMounted = false; - } } - g_game.updateCreatureWalkthrough(this); sendIcons(); } @@ -1147,8 +931,6 @@ void Player::onRemoveCreature(Creature* creature, bool isLogout) g_game.internalCloseTrade(this); } - closeShopWindow(); - clearPartyInvitations(); if (party) { @@ -1179,36 +961,6 @@ void Player::onRemoveCreature(Creature* creature, bool isLogout) } } -void Player::openShopWindow(Npc* npc, const std::list& shop) -{ - shopItemList = shop; - sendShop(npc); - sendSaleItemList(); -} - -bool Player::closeShopWindow(bool sendCloseShopWindow /*= true*/) -{ - //unreference callbacks - int32_t onBuy; - int32_t onSell; - - Npc* npc = getShopOwner(onBuy, onSell); - if (!npc) { - shopItemList.clear(); - return false; - } - - setShopOwner(nullptr, -1, -1); - npc->onPlayerEndTrade(this, onBuy, onSell); - - if (sendCloseShopWindow) { - sendCloseShop(); - } - - shopItemList.clear(); - return true; -} - void Player::onWalk(Direction& dir) { Creature::onWalk(dir); @@ -1217,7 +969,7 @@ void Player::onWalk(Direction& dir) } void Player::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, - const Tile* oldTile, const Position& oldPos, bool teleport) + const Tile* oldTile, const Position& oldPos, bool teleport) { Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport); @@ -1241,23 +993,6 @@ void Player::onCreatureMove(Creature* creature, const Tile* newTile, const Posit } } - // close modal windows - if (!modalWindows.empty()) { - // TODO: This shouldn't be hardcoded - for (uint32_t modalWindowId : modalWindows) { - if (modalWindowId == std::numeric_limits::max()) { - sendTextMessage(MESSAGE_EVENT_ADVANCE, "Offline training aborted."); - break; - } - } - modalWindows.clear(); - } - - // leave market - if (inMarket) { - inMarket = false; - } - if (party) { party->updateSharedExperience(); } @@ -1431,10 +1166,11 @@ void Player::onThink(uint32_t interval) addMessageBuffer(); } + lastWalkingTime += interval; if (!getTile()->hasFlag(TILESTATE_NOLOGOUT) && !isAccessPlayer()) { idleTime += interval; const int32_t kickAfterMinutes = g_config.getNumber(ConfigManager::KICK_AFTER_MINUTES); - if (idleTime > (kickAfterMinutes * 60000) + 60000) { + if ((!pzLocked && OTSYS_TIME() - lastPong >= 60000) || idleTime > (kickAfterMinutes * 60000) + 60000) { kickPlayer(true); } else if (client && idleTime == 60000 * kickAfterMinutes) { std::ostringstream ss; @@ -1444,12 +1180,7 @@ void Player::onThink(uint32_t interval) } if (g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { - checkSkullTicks(interval / 1000); - } - - addOfflineTrainingTime(interval); - if (lastStatsTrainingTime != getOfflineTrainingTime() / 60 / 1000) { - sendStats(); + checkSkullTicks(); } } @@ -1597,24 +1328,7 @@ void Player::addExperience(Creature* source, uint64_t exp, bool sendText/* = fal experience += exp; if (sendText) { - std::string expString = std::to_string(exp) + (exp != 1 ? " experience points." : " experience point."); - - TextMessage message(MESSAGE_EXPERIENCE, "You gained " + expString); - message.position = position; - message.primary.value = exp; - message.primary.color = TEXTCOLOR_WHITE_EXP; - sendTextMessage(message); - - SpectatorVec spectators; - g_game.map.getSpectators(spectators, position, false, true); - spectators.erase(this); - if (!spectators.empty()) { - message.type = MESSAGE_EXPERIENCE_OTHERS; - message.text = getName() + " gained " + expString; - for (Creature* spectator : spectators) { - spectator->getPlayer()->sendTextMessage(message); - } - } + g_game.addAnimatedText(position, TEXTCOLOR_WHITE_EXP, std::to_string(exp)); } uint32_t prevLevel = level; @@ -1635,9 +1349,6 @@ void Player::addExperience(Creature* source, uint64_t exp, bool sendText/* = fal } if (prevLevel != level) { - health = healthMax; - mana = manaMax; - updateBaseSpeed(); setBaseSpeed(getBaseSpeed()); @@ -1663,51 +1374,22 @@ void Player::addExperience(Creature* source, uint64_t exp, bool sendText/* = fal sendStats(); } -void Player::removeExperience(uint64_t exp, bool sendText/* = false*/) +void Player::removeExperience(uint64_t exp) { if (experience == 0 || exp == 0) { return; } - g_events->eventPlayerOnLoseExperience(this, exp); - if (exp == 0) { - return; - } - - uint64_t lostExp = experience; experience = std::max(0, experience - exp); - if (sendText) { - lostExp -= experience; - - std::string expString = std::to_string(lostExp) + (lostExp != 1 ? " experience points." : " experience point."); - - TextMessage message(MESSAGE_EXPERIENCE, "You lost " + expString); - message.position = position; - message.primary.value = lostExp; - message.primary.color = TEXTCOLOR_RED; - sendTextMessage(message); - - SpectatorVec spectators; - g_game.map.getSpectators(spectators, position, false, true); - spectators.erase(this); - if (!spectators.empty()) { - message.type = MESSAGE_EXPERIENCE_OTHERS; - message.text = getName() + " lost " + expString; - for (Creature* spectator : spectators) { - spectator->getPlayer()->sendTextMessage(message); - } - } - } - uint32_t oldLevel = level; uint64_t currLevelExp = Player::getExpForLevel(level); while (level > 1 && experience < currLevelExp) { --level; - healthMax = std::max(0, healthMax - vocation->getHPGain()); + healthMax = std::max(150, std::max(0, healthMax - vocation->getHPGain())); manaMax = std::max(0, manaMax - vocation->getManaGain()); - capacity = std::max(0, capacity - vocation->getCapGain()); + capacity = std::max(400, std::max(0, capacity - vocation->getCapGain())); currLevelExp = Player::getExpForLevel(level); } @@ -1752,6 +1434,11 @@ uint8_t Player::getPercentLevel(uint64_t count, uint64_t nextLevelCount) return result; } +uint16_t Player::getDropLootPercent() +{ + return 10; +} + void Player::onBlockHit() { if (shieldBlockCount > 0) { @@ -1809,7 +1496,7 @@ bool Player::hasShield() const } BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, - bool checkDefense /* = false*/, bool checkArmor /* = false*/, bool field /* = false*/) + bool checkDefense /* = false*/, bool checkArmor /* = false*/, bool field /* = false*/) { BlockType_t blockType = Creature::blockHit(attacker, combatType, damage, checkDefense, checkArmor, field); @@ -1821,58 +1508,55 @@ BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_ return blockType; } - if (damage <= 0) { - damage = 0; - return BLOCK_ARMOR; - } - - for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { - if (!isItemAbilityEnabled(static_cast(slot))) { - continue; - } - - Item* item = inventory[slot]; - if (!item) { - continue; - } - - const ItemType& it = Item::items[item->getID()]; - if (!it.abilities) { - if (damage <= 0) { - damage = 0; - return BLOCK_ARMOR; + if (damage > 0) { + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { + if (!isItemAbilityEnabled(static_cast(slot))) { + continue; } - continue; - } - - const int16_t& absorbPercent = it.abilities->absorbPercent[combatTypeToIndex(combatType)]; - if (absorbPercent != 0) { - damage -= std::round(damage * (absorbPercent / 100.)); - - uint16_t charges = item->getCharges(); - if (charges != 0) { - g_game.transformItem(item, item->getID(), charges - 1); + Item* item = inventory[slot]; + if (!item) { + continue; } - } - if (field) { - const int16_t& fieldAbsorbPercent = it.abilities->fieldAbsorbPercent[combatTypeToIndex(combatType)]; - if (fieldAbsorbPercent != 0) { - damage -= std::round(damage * (fieldAbsorbPercent / 100.)); + const ItemType& it = Item::items[item->getID()]; + if (it.abilities) { + const int16_t& absorbPercent = it.abilities->absorbPercent[combatTypeToIndex(combatType)]; + if (absorbPercent != 0) { + damage -= std::round(damage * (absorbPercent / 100.)); - uint16_t charges = item->getCharges(); - if (charges != 0) { - g_game.transformItem(item, item->getID(), charges - 1); + uint16_t charges = item->getCharges() - 1; + if (charges != 0) { + g_game.transformItem(item, item->getID(), charges); + } else { + g_game.internalRemoveItem(item); + } + } + + if (field) { + const int16_t& fieldAbsorbPercent = it.abilities->fieldAbsorbPercent[combatTypeToIndex(combatType)]; + if (fieldAbsorbPercent != 0) { + damage -= std::round(damage * (fieldAbsorbPercent / 100.)); + + uint16_t charges = item->getCharges(); + if (charges != 0) { + if (charges - 1 == 0) { + g_game.internalRemoveItem(item); + } else { + g_game.transformItem(item, item->getID(), charges - 1); + } + } + } } } } + + if (damage <= 0) { + damage = 0; + blockType = BLOCK_ARMOR; + } } - if (damage <= 0) { - damage = 0; - blockType = BLOCK_ARMOR; - } return blockType; } @@ -1885,33 +1569,31 @@ uint32_t Player::getIP() const return 0; } +void Player::dropLoot(Container* corpse, Creature*) +{ + if (corpse && lootDrop) { + Skulls_t playerSkull = getSkull(); + if (inventory[CONST_SLOT_NECKLACE] && inventory[CONST_SLOT_NECKLACE]->getID() == ITEM_AMULETOFLOSS && playerSkull != SKULL_RED) { + g_game.internalRemoveItem(inventory[CONST_SLOT_NECKLACE], 1); + } else { + for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { + Item* item = inventory[i]; + if (item) { + if (playerSkull == SKULL_RED || item->getContainer() || uniform_random(1, 100) <= getDropLootPercent()) { + g_game.internalMoveItem(this, corpse, INDEX_WHEREEVER, item, item->getItemCount(), 0); + sendInventoryItem(static_cast(i), nullptr); + } + } + } + } + } +} + void Player::death(Creature* lastHitCreature) { loginPosition = town->getTemplePosition(); if (skillLoss) { - uint8_t unfairFightReduction = 100; - bool lastHitPlayer = Player::lastHitIsPlayer(lastHitCreature); - - if (lastHitPlayer) { - uint32_t sumLevels = 0; - uint32_t inFightTicks = g_config.getNumber(ConfigManager::PZ_LOCKED); - for (const auto& it : damageMap) { - CountBlock_t cb = it.second; - if ((OTSYS_TIME() - cb.ticks) <= inFightTicks) { - Player* damageDealer = g_game.getPlayerByID(it.first); - if (damageDealer) { - sumLevels += damageDealer->getLevel(); - } - } - } - - if (sumLevels > level) { - double reduce = level / static_cast(sumLevels); - unfairFightReduction = std::max(20, std::floor((reduce * 100) + 0.5)); - } - } - //Magic level loss uint64_t sumMana = 0; uint64_t lostMana = 0; @@ -1923,7 +1605,7 @@ void Player::death(Creature* lastHitCreature) sumMana += manaSpent; - double deathLossPercent = getLostPercent() * (unfairFightReduction / 100.); + double deathLossPercent = getLostPercent(); lostMana = static_cast(sumMana * deathLossPercent); @@ -1977,9 +1659,7 @@ void Player::death(Creature* lastHitCreature) if (expLoss != 0) { uint32_t oldLevel = level; - if (vocation->getId() == VOCATION_NONE || level > 7) { - experience -= expLoss; - } + experience -= expLoss; while (level > 1 && experience < Player::getExpForLevel(level)) { --level; @@ -2005,7 +1685,7 @@ void Player::death(Creature* lastHitCreature) std::bitset<6> bitset(blessings); if (bitset[5]) { - if (lastHitPlayer) { + if (Player::lastHitIsPlayer(lastHitCreature)) { bitset.reset(5); blessings = bitset.to_ulong(); } else { @@ -2017,15 +1697,9 @@ void Player::death(Creature* lastHitCreature) sendStats(); sendSkills(); - sendReLoginWindow(unfairFightReduction); - if (getSkull() == SKULL_BLACK) { - health = 40; - mana = 0; - } else { - health = healthMax; - mana = manaMax; - } + health = healthMax; + mana = manaMax; auto it = conditions.begin(), end = conditions.end(); while (it != end) { @@ -2040,8 +1714,61 @@ void Player::death(Creature* lastHitCreature) ++it; } } + + // Teleport newbies to newbie island + if (g_config.getBoolean(ConfigManager::TELEPORT_NEWBIES)) { + if (getVocationId() != VOCATION_NONE && level <= static_cast(g_config.getNumber(ConfigManager::NEWBIE_LEVEL_THRESHOLD))) { + Town* newbieTown = g_game.map.towns.getTown(g_config.getNumber(ConfigManager::NEWBIE_TOWN)); + if (newbieTown) { + // Restart stats + level = 1; + experience = 0; + levelPercent = 0; + capacity = 400; + health = 150; + healthMax = 150; + mana = 0; + manaMax = 0; + magLevel = 0; + magLevelPercent = 0; + manaSpent = 0; + staminaMinutes = 3360; + setVocation(0); + + // Restart skills + for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { //for each skill + skills[i].level = 10; + skills[i].tries = 0; + skills[i].percent = 0; + } + + // Restart town + setTown(newbieTown); + loginPosition = getTemplePosition(); + + // Restart first items + lastLoginSaved = 0; + lastLogout = 0; + + // Restart storages + storageMap.clear(); + outfits.clear(); + + // Restart items + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; slot++) + { + Item* item = inventory[slot]; + if (item) { + g_game.internalRemoveItem(item, item->getItemCount()); + } + } + } else { + std::cout << "[Warning - Player:death] Newbie teletransportation is enabled, newbie town does not exist." << std::endl; + } + } + } } else { - setSkillLoss(true); + setLossSkill(true); auto it = conditions.begin(), end = conditions.end(); while (it != end) { @@ -2052,7 +1779,8 @@ void Player::death(Creature* lastHitCreature) condition->endCondition(this); onEndCondition(condition->getType()); delete condition; - } else { + } + else { ++it; } } @@ -2166,7 +1894,12 @@ bool Player::removeVIP(uint32_t vipGuid) bool Player::addVIP(uint32_t vipGuid, const std::string& vipName, VipStatus_t status) { - if (VIPList.size() >= getMaxVIPEntries() || VIPList.size() == 200) { // max number of buddies is 200 in 9.53 + if (guid == vipGuid) { + sendTextMessage(MESSAGE_STATUS_SMALL, "You cannot add yourself."); + return false; + } + + if (VIPList.size() >= getMaxVIPEntries() || VIPList.size() == 100) { sendTextMessage(MESSAGE_STATUS_SMALL, "You cannot add more buddies."); return false; } @@ -2177,33 +1910,26 @@ bool Player::addVIP(uint32_t vipGuid, const std::string& vipName, VipStatus_t st return false; } - IOLoginData::addVIPEntry(accountNumber, vipGuid, "", 0, false); + IOLoginData::addVIPEntry(accountNumber, vipGuid); if (client) { - client->sendVIP(vipGuid, vipName, "", 0, false, status); + client->sendVIP(vipGuid, vipName, status); } return true; } bool Player::addVIPInternal(uint32_t vipGuid) { - if (VIPList.size() >= getMaxVIPEntries() || VIPList.size() == 200) { // max number of buddies is 200 in 9.53 + if (guid == vipGuid) { + return false; + } + + if (VIPList.size() >= getMaxVIPEntries() || VIPList.size() == 100) { return false; } return VIPList.insert(vipGuid).second; } -bool Player::editVIP(uint32_t vipGuid, const std::string& description, uint32_t icon, bool notify) -{ - auto it = VIPList.find(vipGuid); - if (it == VIPList.end()) { - return false; // player is not in VIP - } - - IOLoginData::editVIPEntry(accountNumber, vipGuid, description, icon, notify); - return true; -} - //close container and its child containers void Player::autoCloseContainers(const Container* container) { @@ -2270,18 +1996,14 @@ ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, const int32_t& slotPosition = item->getSlotPosition(); if ((slotPosition & SLOTP_HEAD) || (slotPosition & SLOTP_NECKLACE) || - (slotPosition & SLOTP_BACKPACK) || (slotPosition & SLOTP_ARMOR) || - (slotPosition & SLOTP_LEGS) || (slotPosition & SLOTP_FEET) || - (slotPosition & SLOTP_RING)) { + (slotPosition & SLOTP_BACKPACK) || (slotPosition & SLOTP_ARMOR) || + (slotPosition & SLOTP_LEGS) || (slotPosition & SLOTP_FEET) || + (slotPosition & SLOTP_RING)) { ret = RETURNVALUE_CANNOTBEDRESSED; } else if (slotPosition & SLOTP_TWO_HAND) { ret = RETURNVALUE_PUTTHISOBJECTINBOTHHANDS; } else if ((slotPosition & SLOTP_RIGHT) || (slotPosition & SLOTP_LEFT)) { - if (!g_config.getBoolean(ConfigManager::CLASSIC_EQUIPMENT_SLOTS)) { - ret = RETURNVALUE_CANNOTBEDRESSED; - } else { - ret = RETURNVALUE_PUTTHISOBJECTINYOURHAND; - } + ret = RETURNVALUE_PUTTHISOBJECTINYOURHAND; } switch (index) { @@ -2315,22 +2037,7 @@ ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, case CONST_SLOT_RIGHT: { if (slotPosition & SLOTP_RIGHT) { - if (!g_config.getBoolean(ConfigManager::CLASSIC_EQUIPMENT_SLOTS)) { - if (item->getWeaponType() != WEAPON_SHIELD) { - ret = RETURNVALUE_CANNOTBEDRESSED; - } else { - const Item* leftItem = inventory[CONST_SLOT_LEFT]; - if (leftItem) { - if ((leftItem->getSlotPosition() | slotPosition) & SLOTP_TWO_HAND) { - ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE; - } else { - ret = RETURNVALUE_NOERROR; - } - } else { - ret = RETURNVALUE_NOERROR; - } - } - } else if (slotPosition & SLOTP_TWO_HAND) { + if (slotPosition & SLOTP_TWO_HAND) { if (inventory[CONST_SLOT_LEFT] && inventory[CONST_SLOT_LEFT] != item) { ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE; } else { @@ -2347,8 +2054,8 @@ ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, } else if (leftType == WEAPON_SHIELD && type == WEAPON_SHIELD) { ret = RETURNVALUE_CANONLYUSEONESHIELD; } else if (leftType == WEAPON_NONE || type == WEAPON_NONE || - leftType == WEAPON_SHIELD || leftType == WEAPON_AMMO - || type == WEAPON_SHIELD || type == WEAPON_AMMO) { + leftType == WEAPON_SHIELD || leftType == WEAPON_AMMO + || type == WEAPON_SHIELD || type == WEAPON_AMMO) { ret = RETURNVALUE_NOERROR; } else { ret = RETURNVALUE_CANONLYUSEONEWEAPON; @@ -2362,16 +2069,7 @@ ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, case CONST_SLOT_LEFT: { if (slotPosition & SLOTP_LEFT) { - if (!g_config.getBoolean(ConfigManager::CLASSIC_EQUIPMENT_SLOTS)) { - WeaponType_t type = item->getWeaponType(); - if (type == WEAPON_NONE || type == WEAPON_SHIELD) { - ret = RETURNVALUE_CANNOTBEDRESSED; - } else if (inventory[CONST_SLOT_RIGHT] && (slotPosition & SLOTP_TWO_HAND)) { - ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE; - } else { - ret = RETURNVALUE_NOERROR; - } - } else if (slotPosition & SLOTP_TWO_HAND) { + if (slotPosition & SLOTP_TWO_HAND) { if (inventory[CONST_SLOT_RIGHT] && inventory[CONST_SLOT_RIGHT] != item) { ret = RETURNVALUE_BOTHHANDSNEEDTOBEFREE; } else { @@ -2388,8 +2086,8 @@ ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, } else if (rightType == WEAPON_SHIELD && type == WEAPON_SHIELD) { ret = RETURNVALUE_CANONLYUSEONESHIELD; } else if (rightType == WEAPON_NONE || type == WEAPON_NONE || - rightType == WEAPON_SHIELD || rightType == WEAPON_AMMO - || type == WEAPON_SHIELD || type == WEAPON_AMMO) { + rightType == WEAPON_SHIELD || rightType == WEAPON_AMMO + || type == WEAPON_SHIELD || type == WEAPON_AMMO) { ret = RETURNVALUE_NOERROR; } else { ret = RETURNVALUE_CANONLYUSEONEWEAPON; @@ -2423,46 +2121,41 @@ ReturnValue Player::queryAdd(int32_t index, const Thing& thing, uint32_t count, } case CONST_SLOT_AMMO: { - if ((slotPosition & SLOTP_AMMO) || g_config.getBoolean(ConfigManager::CLASSIC_EQUIPMENT_SLOTS)) { - ret = RETURNVALUE_NOERROR; - } + ret = RETURNVALUE_NOERROR; break; } case CONST_SLOT_WHEREEVER: case -1: - ret = RETURNVALUE_NOTENOUGHROOM; - break; + ret = RETURNVALUE_NOTENOUGHROOM; + break; default: - ret = RETURNVALUE_NOTPOSSIBLE; - break; + ret = RETURNVALUE_NOTPOSSIBLE; + break; } + if (ret == RETURNVALUE_NOERROR || ret == RETURNVALUE_NOTENOUGHROOM) { + //need an exchange with source? + const Item* inventoryItem = getInventoryItem(static_cast(index)); + if (inventoryItem && (!inventoryItem->isStackable() || inventoryItem->isRune() || inventoryItem->getID() != item->getID())) { + return RETURNVALUE_NEEDEXCHANGE; + } - if (ret != RETURNVALUE_NOERROR && ret != RETURNVALUE_NOTENOUGHROOM) { - return ret; - } + //check if enough capacity + if (!hasCapacity(item, count)) { + return RETURNVALUE_NOTENOUGHCAPACITY; + } - //need an exchange with source? - const Item* inventoryItem = getInventoryItem(static_cast(index)); - if (inventoryItem && (!inventoryItem->isStackable() || inventoryItem->getID() != item->getID())) { - return RETURNVALUE_NEEDEXCHANGE; - } - - //check if enough capacity - if (!hasCapacity(item, count)) { - return RETURNVALUE_NOTENOUGHCAPACITY; - } - - if (!g_moveEvents->onPlayerEquip(const_cast(this), const_cast(item), static_cast(index), true)) { - return RETURNVALUE_CANNOTBEDRESSED; + if (!g_moveEvents->onPlayerEquip(const_cast(this), const_cast(item), static_cast(index), true)) { + return RETURNVALUE_CANNOTBEDRESSED; + } } return ret; } ReturnValue Player::queryMaxCount(int32_t index, const Thing& thing, uint32_t count, uint32_t& maxQueryCount, - uint32_t flags) const + uint32_t flags) const { const Item* item = thing.getItem(); if (item == nullptr) { @@ -2514,15 +2207,18 @@ ReturnValue Player::queryMaxCount(int32_t index, const Thing& thing, uint32_t co } if (destItem) { - if (destItem->isStackable() && item->equals(destItem) && destItem->getItemCount() < 100) { + if (!destItem->isRune() && destItem->isStackable() && item->equals(destItem) && destItem->getItemCount() < 100) { maxQueryCount = 100 - destItem->getItemCount(); - } else { + } + else { maxQueryCount = 0; } - } else if (queryAdd(index, *item, count, flags) == RETURNVALUE_NOERROR) { //empty slot + } + else if (queryAdd(index, *item, count, flags) == RETURNVALUE_NOERROR) { //empty slot if (item->isStackable()) { maxQueryCount = 100; - } else { + } + else { maxQueryCount = 1; } @@ -2561,7 +2257,7 @@ ReturnValue Player::queryRemove(const Thing& thing, uint32_t count, uint32_t fla } Cylinder* Player::queryDestination(int32_t& index, const Thing& thing, Item** destItem, - uint32_t& flags) + uint32_t& flags) { if (index == 0 /*drop to capacity window*/ || index == INDEX_WHEREEVER) { *destItem = nullptr; @@ -2626,9 +2322,11 @@ Cylinder* Player::queryDestination(int32_t& index, const Thing& thing, Item** de n--; } - for (Item* tmpContainerItem : tmpContainer->getItemList()) { - if (Container* subContainer = tmpContainerItem->getContainer()) { - containers.push_back(subContainer); + if (!g_config.getBoolean(ConfigManager::DROP_ITEMS)) { + for (Item* tmpContainerItem : tmpContainer->getItemList()) { + if (Container* subContainer = tmpContainerItem->getContainer()) { + containers.push_back(subContainer); + } } } @@ -2653,8 +2351,10 @@ Cylinder* Player::queryDestination(int32_t& index, const Thing& thing, Item** de return tmpContainer; } - if (Container* subContainer = tmpItem->getContainer()) { - containers.push_back(subContainer); + if (!g_config.getBoolean(ConfigManager::DROP_ITEMS)) { + if (Container* subContainer = tmpItem->getContainer()) { + containers.push_back(subContainer); + } } n++; @@ -2868,7 +2568,23 @@ bool Player::removeItemOfType(uint16_t itemId, uint32_t amount, int32_t subType, g_game.internalRemoveItems(std::move(itemList), amount, Item::items[itemId].stackable); return true; } - } else if (Container* container = item->getContainer()) { + } + else if (Container* container = item->getContainer()) { + if (container->getID() == itemId) { + uint32_t itemCount = Item::countByType(item, subType); + if (itemCount == 0) { + continue; + } + + itemList.push_back(item); + + count += itemCount; + if (count >= amount) { + g_game.internalRemoveItems(std::move(itemList), amount, Item::items[itemId].stackable); + return true; + } + } + for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { Item* containerItem = *it; if (containerItem->getID() == itemId) { @@ -2918,28 +2634,14 @@ Thing* Player::getThing(size_t index) const return nullptr; } -void Player::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +void Player::postAddNotification(Thing* thing, const Cylinder*, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) { if (link == LINK_OWNER) { //calling movement scripts g_moveEvents->onPlayerEquip(this, thing->getItem(), static_cast(index), false); } - bool requireListUpdate = false; - if (link == LINK_OWNER || link == LINK_TOPPARENT) { - const Item* i = (oldParent ? oldParent->getItem() : nullptr); - - // Check if we owned the old container too, so we don't need to do anything, - // as the list was updated in postRemoveNotification - assert(i ? i->getContainer() != nullptr : true); - - if (i) { - requireListUpdate = i->getContainer()->getHoldingPlayer() != this; - } else { - requireListUpdate = oldParent != this; - } - updateInventoryWeight(); updateItemsLight(); sendStats(); @@ -2949,10 +2651,6 @@ void Player::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_ if (const Container* container = item->getContainer()) { onSendContainer(container); } - - if (shopOwner && requireListUpdate) { - updateSaleShopList(item); - } } else if (const Creature* creature = thing->getCreature()) { if (creature == this) { //check containers @@ -2972,28 +2670,14 @@ void Player::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_ } } -void Player::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) +void Player::postRemoveNotification(Thing* thing, const Cylinder*, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) { if (link == LINK_OWNER) { //calling movement scripts g_moveEvents->onPlayerDeEquip(this, thing->getItem(), static_cast(index)); } - bool requireListUpdate = false; - if (link == LINK_OWNER || link == LINK_TOPPARENT) { - const Item* i = (newParent ? newParent->getItem() : nullptr); - - // Check if we owned the old container too, so we don't need to do anything, - // as the list was updated in postRemoveNotification - assert(i ? i->getContainer() != nullptr : true); - - if (i) { - requireListUpdate = i->getContainer()->getHoldingPlayer() != this; - } else { - requireListUpdate = newParent != this; - } - updateInventoryWeight(); updateItemsLight(); sendStats(); @@ -3006,11 +2690,11 @@ void Player::postRemoveNotification(Thing* thing, const Cylinder* newParent, int } else if (container->getTopParent() == this) { onSendContainer(container); } else if (const Container* topContainer = dynamic_cast(container->getTopParent())) { - if (const DepotChest* depotChest = dynamic_cast(topContainer)) { + if (const DepotLocker* depotLocker = dynamic_cast(topContainer)) { bool isOwner = false; - for (const auto& it : depotChests) { - if (it.second == depotChest) { + for (const auto& it : depotLockerMap) { + if (it.second == depotLocker) { isOwner = true; onSendContainer(container); } @@ -3026,45 +2710,9 @@ void Player::postRemoveNotification(Thing* thing, const Cylinder* newParent, int autoCloseContainers(container); } } - - if (shopOwner && requireListUpdate) { - updateSaleShopList(item); - } } } -bool Player::updateSaleShopList(const Item* item) -{ - uint16_t itemId = item->getID(); - if (itemId != ITEM_GOLD_COIN && itemId != ITEM_PLATINUM_COIN && itemId != ITEM_CRYSTAL_COIN) { - auto it = std::find_if(shopItemList.begin(), shopItemList.end(), [itemId](const ShopInfo& shopInfo) { return shopInfo.itemId == itemId && shopInfo.sellPrice != 0; }); - if (it == shopItemList.end()) { - const Container* container = item->getContainer(); - if (!container) { - return false; - } - - const auto& items = container->getItemList(); - return std::any_of(items.begin(), items.end(), [this](const Item* containerItem) { - return updateSaleShopList(containerItem); - }); - } - } - - if (client) { - client->sendSaleItemList(shopItemList); - } - return true; -} - -bool Player::hasShopItemForSale(uint32_t itemId, uint8_t subType) const -{ - const ItemType& itemType = Item::items[itemId]; - return std::any_of(shopItemList.begin(), shopItemList.end(), [&](const ShopInfo& shopInfo) { - return shopInfo.itemId == itemId && shopInfo.buyPrice != 0 && (!itemType.isFluidContainer() || shopInfo.subType == subType); - }); -} - void Player::internalAddThing(Thing* thing) { internalAddThing(0, thing); @@ -3088,6 +2736,51 @@ void Player::internalAddThing(uint32_t index, Thing* thing) } } +uint32_t Player::checkPlayerKilling() +{ + time_t today = std::time(nullptr); + int32_t lastDay = 0; + int32_t lastWeek = 0; + int32_t lastMonth = 0; + int64_t egibleMurders = 0; + + time_t dayTimestamp = today - (24 * 60 * 60); + time_t weekTimestamp = today - (7 * 24 * 60 * 60); + time_t monthTimestamp = today - (30 * 24 * 60 * 60); + + for (time_t currentMurderTimestamp : murderTimeStamps) { + if (currentMurderTimestamp > dayTimestamp) { + lastDay++; + } + + if (currentMurderTimestamp > weekTimestamp) { + lastWeek++; + } + + egibleMurders = lastMonth + 1; + + if (currentMurderTimestamp <= monthTimestamp) { + egibleMurders = lastMonth; + } + + lastMonth = egibleMurders; + } + + if (lastDay >= g_config.getNumber(ConfigManager::KILLS_DAY_BANISHMENT) || + lastWeek >= g_config.getNumber(ConfigManager::KILLS_WEEK_BANISHMENT) || + lastMonth >= g_config.getNumber(ConfigManager::KILLS_MONTH_BANISHMENT)) { + return 2; // banishment! + } + + if (lastDay >= g_config.getNumber(ConfigManager::KILLS_DAY_RED_SKULL) || + lastWeek >= g_config.getNumber(ConfigManager::KILLS_WEEK_RED_SKULL) || + lastMonth >= g_config.getNumber(ConfigManager::KILLS_MONTH_RED_SKULL)) { + return 1; // red skull! + } + + return 0; +} + bool Player::setFollowCreature(Creature* creature) { if (!Creature::setFollowCreature(creature)) { @@ -3109,7 +2802,7 @@ bool Player::setAttackedCreature(Creature* creature) return false; } - if (chaseMode && creature) { + if (chaseMode == CHASEMODE_FOLLOW && creature) { if (followCreature != creature) { //chase opponent setFollowCreature(creature); @@ -3156,33 +2849,8 @@ void Player::doAttacking(uint32_t) } if ((OTSYS_TIME() - lastAttack) >= getAttackSpeed()) { - bool result = false; - - Item* tool = getWeapon(); - const Weapon* weapon = g_weapons->getWeapon(tool); - uint32_t delay = getAttackSpeed(); - bool classicSpeed = g_config.getBoolean(ConfigManager::CLASSIC_ATTACK_SPEED); - - if (weapon) { - if (!weapon->interruptSwing()) { - result = weapon->useWeapon(this, tool, attackedCreature); - } else if (!classicSpeed && !canDoAction()) { - delay = getNextActionTime(); - } else { - result = weapon->useWeapon(this, tool, attackedCreature); - } - } else { - result = Weapon::useFist(this, attackedCreature); - } - - SchedulerTask* task = createSchedulerTask(std::max(SCHEDULER_MINTICKS, delay), std::bind(&Game::checkCreatureAttack, &g_game, getID())); - if (!classicSpeed) { - setNextActionTask(task); - } else { - g_scheduler.addEvent(task); - } - - if (result) { + if (Combat::attack(this, attackedCreature)) { + earliestAttackTime = OTSYS_TIME() + 2000; lastAttack = OTSYS_TIME(); } } @@ -3206,13 +2874,13 @@ void Player::onFollowCreature(const Creature* creature) } } -void Player::setChaseMode(bool mode) +void Player::setChaseMode(chaseMode_t mode) { - bool prevChaseMode = chaseMode; + chaseMode_t prevChaseMode = chaseMode; chaseMode = mode; if (prevChaseMode != chaseMode) { - if (chaseMode) { + if (chaseMode == CHASEMODE_FOLLOW) { if (!followCreature && attackedCreature) { //chase opponent setFollowCreature(attackedCreature); @@ -3243,25 +2911,27 @@ void Player::stopWalk() cancelNextWalk = true; } -LightInfo Player::getCreatureLight() const +void Player::getCreatureLight(LightInfo& light) const { if (internalLight.level > itemsLight.level) { - return internalLight; + light = internalLight; + } else { + light = itemsLight; } - return itemsLight; } void Player::updateItemsLight(bool internal /*=false*/) { LightInfo maxLight; + LightInfo curLight; for (int32_t i = CONST_SLOT_FIRST; i <= CONST_SLOT_LAST; ++i) { Item* item = inventory[i]; if (item) { - LightInfo curLight = item->getLightInfo(); + item->getLight(curLight); if (curLight.level > maxLight.level) { - maxLight = std::move(curLight); + maxLight = curLight; } } } @@ -3278,11 +2948,6 @@ void Player::updateItemsLight(bool internal /*=false*/) void Player::onAddCondition(ConditionType_t type) { Creature::onAddCondition(type); - - if (type == CONDITION_OUTFIT && isMounted()) { - dismount(); - } - sendIcons(); } @@ -3290,39 +2955,23 @@ void Player::onAddCombatCondition(ConditionType_t type) { switch (type) { case CONDITION_POISON: - sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are poisoned."); - break; + sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are poisoned."); + break; case CONDITION_DROWN: - sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are drowning."); - break; + sendTextMessage(MESSAGE_STATUS_SMALL, "You are drowning."); + break; case CONDITION_PARALYZE: - sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are paralyzed."); - break; + sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are paralyzed."); + break; case CONDITION_DRUNK: - sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are drunk."); - break; - - case CONDITION_CURSED: - sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are cursed."); - break; - - case CONDITION_FREEZING: - sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are freezing."); - break; - - case CONDITION_DAZZLED: - sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are dazzled."); - break; - - case CONDITION_BLEEDING: - sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are bleeding."); - break; + sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are drunk."); + break; default: - break; + break; } } @@ -3335,7 +2984,7 @@ void Player::onEndCondition(ConditionType_t type) pzLocked = false; clearAttacked(); - if (getSkull() != SKULL_RED && getSkull() != SKULL_BLACK) { + if (getSkull() != SKULL_RED) { setSkull(SKULL_NONE); } } @@ -3390,30 +3039,28 @@ void Player::onAttackedCreature(Creature* target) } Player* targetPlayer = target->getPlayer(); - if (targetPlayer && !isPartner(targetPlayer) && !isGuildMate(targetPlayer)) { - if (!pzLocked && g_game.getWorldType() == WORLD_TYPE_PVP_ENFORCED) { + if (targetPlayer) { + if (!pzLocked && g_game.getWorldType() != WORLD_TYPE_PVP_ENFORCED) { pzLocked = true; sendIcons(); } - if (getSkull() == SKULL_NONE && getSkullClient(targetPlayer) == SKULL_YELLOW) { - addAttacked(targetPlayer); - targetPlayer->sendCreatureSkull(this); - } else if (!targetPlayer->hasAttacked(this)) { - if (!pzLocked) { - pzLocked = true; - sendIcons(); - } - - if (!Combat::isInPvpZone(this, targetPlayer) && !isInWar(targetPlayer)) { + if (!isPartner(targetPlayer)) { + if (getSkull() == SKULL_NONE && getSkullClient(targetPlayer) == SKULL_YELLOW) { addAttacked(targetPlayer); - - if (targetPlayer->getSkull() == SKULL_NONE && getSkull() == SKULL_NONE) { - setSkull(SKULL_WHITE); - } - - if (getSkull() == SKULL_NONE) { - targetPlayer->sendCreatureSkull(this); + targetPlayer->sendCreatureSkull(this); + } else { + if (!targetPlayer->hasAttacked(this)) { + if (!Combat::isInPvpZone(this, targetPlayer) && !isInWar(targetPlayer)) { + addAttacked(targetPlayer); + if (targetPlayer->getSkull() == SKULL_NONE && getSkull() == SKULL_NONE) { + setSkull(SKULL_WHITE); + } + } + + if (getSkull() == SKULL_NONE) { + targetPlayer->sendCreatureSkull(this); + } } } } @@ -3490,26 +3137,23 @@ bool Player::onKilledCreature(Creature* target, bool lastHit/* = true*/) Creature::onKilledCreature(target, lastHit); - Player* targetPlayer = target->getPlayer(); - if (!targetPlayer) { - return false; - } - - if (targetPlayer->getZone() == ZONE_PVP) { - targetPlayer->setDropLoot(false); - targetPlayer->setSkillLoss(false); - } else if (!hasFlag(PlayerFlag_NotGainInFight) && !isPartner(targetPlayer)) { - if (!Combat::isInPvpZone(this, targetPlayer) && hasAttacked(targetPlayer) && !targetPlayer->hasAttacked(this) && !isGuildMate(targetPlayer) && targetPlayer != this) { - if (targetPlayer->getSkull() == SKULL_NONE && !isInWar(targetPlayer)) { - unjustified = true; - addUnjustifiedDead(targetPlayer); + if (Player* targetPlayer = target->getPlayer()) { + if (targetPlayer && targetPlayer->getZone() == ZONE_PVP) { + targetPlayer->setDropLoot(false); + targetPlayer->setLossSkill(false); + } else if (!hasFlag(PlayerFlag_NotGainInFight) && !isPartner(targetPlayer)) { + if (!Combat::isInPvpZone(this, targetPlayer) && hasAttacked(targetPlayer) && !targetPlayer->hasAttacked(this) && targetPlayer != this) { + if (targetPlayer->getSkull() == SKULL_NONE && !isInWar(targetPlayer)) { + unjustified = true; + addUnjustifiedDead(targetPlayer); + } } + } - if (lastHit && hasCondition(CONDITION_INFIGHT)) { - pzLocked = true; - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_INFIGHT, g_config.getNumber(ConfigManager::WHITE_SKULL_TIME), 0); - addCondition(condition); - } + if (lastHit && hasCondition(CONDITION_INFIGHT)) { + pzLocked = true; + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_INFIGHT, g_config.getNumber(ConfigManager::WHITE_SKULL_TIME) * 1000, 0); + addCondition(condition); } } @@ -3592,7 +3236,8 @@ void Player::changeMana(int32_t manaChange) if (!hasFlag(PlayerFlag_HasInfiniteMana)) { if (manaChange > 0) { mana += std::min(manaChange, getMaxMana() - mana); - } else { + } + else { mana = std::max(0, mana + manaChange); } } @@ -3747,24 +3392,22 @@ Skulls_t Player::getSkullClient(const Creature* creature) const } const Player* player = creature->getPlayer(); - if (!player || player->getSkull() != SKULL_NONE) { - return Creature::getSkullClient(creature); - } + if (player && player->getSkull() == SKULL_NONE) { + if (isInWar(player)) { + return SKULL_GREEN; + } - if (isInWar(player)) { - return SKULL_GREEN; - } + if (!player->getGuildWarList().empty() && guild == player->getGuild()) { + return SKULL_GREEN; + } - if (!player->getGuildWarVector().empty() && guild == player->getGuild()) { - return SKULL_GREEN; - } + if (player->hasAttacked(this)) { + return SKULL_YELLOW; + } - if (player->hasAttacked(this)) { - return SKULL_YELLOW; - } - - if (isPartner(player)) { - return SKULL_GREEN; + if (isPartner(player)) { + return SKULL_GREEN; + } } return Creature::getSkullClient(creature); } @@ -3775,7 +3418,7 @@ bool Player::hasAttacked(const Player* attacked) const return false; } - return attackedSet.find(attacked->guid) != attackedSet.end(); + return attackedSet.find(attacked->id) != attackedSet.end(); } void Player::addAttacked(const Player* attacked) @@ -3784,7 +3427,7 @@ void Player::addAttacked(const Player* attacked) return; } - attackedSet.insert(attacked->guid); + attackedSet.insert(attacked->id); } void Player::removeAttacked(const Player* attacked) @@ -3810,29 +3453,48 @@ void Player::addUnjustifiedDead(const Player* attacked) return; } - sendTextMessage(MESSAGE_EVENT_ADVANCE, "Warning! The murder of " + attacked->getName() + " was not justified."); + // current unjustified kill! + murderTimeStamps.push_back(std::time(nullptr)); - skullTicks += g_config.getNumber(ConfigManager::FRAG_TIME); + sendTextMessage(MESSAGE_STATUS_WARNING, "Warning! The murder of " + attacked->getName() + " was not justified."); - if (getSkull() != SKULL_BLACK) { - if (g_config.getNumber(ConfigManager::KILLS_TO_BLACK) != 0 && skullTicks > (g_config.getNumber(ConfigManager::KILLS_TO_BLACK) - 1) * static_cast(g_config.getNumber(ConfigManager::FRAG_TIME))) { - setSkull(SKULL_BLACK); - } else if (getSkull() != SKULL_RED && g_config.getNumber(ConfigManager::KILLS_TO_RED) != 0 && skullTicks > (g_config.getNumber(ConfigManager::KILLS_TO_RED) - 1) * static_cast(g_config.getNumber(ConfigManager::FRAG_TIME))) { - setSkull(SKULL_RED); + if (playerKillerEnd == 0) { + // white skull time, it only sets on first kill! + playerKillerEnd = std::time(nullptr) + g_config.getNumber(ConfigManager::WHITE_SKULL_TIME); + } + + uint32_t murderResult = checkPlayerKilling(); + if (murderResult >= 1) { + // red skull player + playerKillerEnd = std::time(nullptr) + g_config.getNumber(ConfigManager::RED_SKULL_TIME); + setSkull(SKULL_RED); + + if (murderResult == 2) { + // banishment for too many unjustified kills + Database* db = Database::getInstance(); + + std::ostringstream ss; + ss << "INSERT INTO `account_bans` (`account_id`, `reason`, `banned_at`, `expires_at`, `banned_by`) VALUES ("; + ss << getAccount() << ", "; + ss << db->escapeString("Too many unjustified kills") << ", "; + ss << std::time(nullptr) << ", "; + ss << std::time(nullptr) + g_config.getNumber(ConfigManager::BAN_LENGTH) << ", "; + ss << "1);"; + + db->executeQuery(ss.str()); + + g_game.addMagicEffect(getPosition(), CONST_ME_GREEN_RINGS); + g_game.removeCreature(this); + disconnect(); } } } -void Player::checkSkullTicks(int64_t ticks) +void Player::checkSkullTicks() { - int64_t newTicks = skullTicks - ticks; - if (newTicks < 0) { - skullTicks = 0; - } else { - skullTicks = newTicks; - } + time_t today = std::time(nullptr); - if ((skull == SKULL_RED || skull == SKULL_BLACK) && skullTicks < 1 && !hasCondition(CONDITION_INFIGHT)) { + if (!hasCondition(CONDITION_INFIGHT) && ((skull == SKULL_RED && today >= playerKillerEnd) || (skull == SKULL_WHITE))) { setSkull(SKULL_NONE); } } @@ -3865,12 +3527,11 @@ double Player::getLostPercent() const lossPercent = 10; } - double percentReduction = 0; if (isPromoted()) { - percentReduction += 30; + lossPercent *= 0.7; } - percentReduction += blessingCount * 8; - return lossPercent * (1 - (percentReduction / 100.)) / 100.; + + return lossPercent * pow(0.92, blessingCount) / 100; } void Player::learnInstantSpell(const std::string& spellName) @@ -3919,7 +3580,7 @@ bool Player::isInWar(const Player* player) const bool Player::isInWarList(uint32_t guildId) const { - return std::find(guildWarVector.begin(), guildWarVector.end(), guildId) != guildWarVector.end(); + return std::find(guildWarList.begin(), guildWarList.end(), guildId) != guildWarList.end(); } bool Player::isPremium() const @@ -3934,7 +3595,6 @@ bool Player::isPremium() const void Player::setPremiumDays(int32_t v) { premiumDays = v; - sendBasicData(); } PartyShields_t Player::getPartyShield(const Player* player) const @@ -3945,34 +3605,10 @@ PartyShields_t Player::getPartyShield(const Player* player) const if (party) { if (party->getLeader() == player) { - if (party->isSharedExperienceActive()) { - if (party->isSharedExperienceEnabled()) { - return SHIELD_YELLOW_SHAREDEXP; - } - - if (party->canUseSharedExperience(player)) { - return SHIELD_YELLOW_NOSHAREDEXP; - } - - return SHIELD_YELLOW_NOSHAREDEXP_BLINK; - } - return SHIELD_YELLOW; } if (player->party == party) { - if (party->isSharedExperienceActive()) { - if (party->isSharedExperienceEnabled()) { - return SHIELD_BLUE_SHAREDEXP; - } - - if (party->canUseSharedExperience(player)) { - return SHIELD_BLUE_NOSHAREDEXP; - } - - return SHIELD_BLUE_NOSHAREDEXP_BLINK; - } - return SHIELD_BLUE; } @@ -3985,10 +3621,6 @@ PartyShields_t Player::getPartyShield(const Player* player) const return SHIELD_WHITEYELLOW; } - if (player->party) { - return SHIELD_GRAY; - } - return SHIELD_NONE; } @@ -4002,7 +3634,7 @@ bool Player::isInviting(const Player* player) const bool Player::isPartner(const Player* player) const { - if (!player || !party || player == this) { + if (!player || !party) { return false; } return party == player->party; @@ -4046,379 +3678,6 @@ void Player::clearPartyInvitations() invitePartyList.clear(); } -GuildEmblems_t Player::getGuildEmblem(const Player* player) const -{ - if (!player) { - return GUILDEMBLEM_NONE; - } - - const Guild* playerGuild = player->getGuild(); - if (!playerGuild) { - return GUILDEMBLEM_NONE; - } - - if (player->getGuildWarVector().empty()) { - if (guild == playerGuild) { - return GUILDEMBLEM_MEMBER; - } else { - return GUILDEMBLEM_OTHER; - } - } else if (guild == playerGuild) { - return GUILDEMBLEM_ALLY; - } else if (isInWar(player)) { - return GUILDEMBLEM_ENEMY; - } - - return GUILDEMBLEM_NEUTRAL; -} - -uint8_t Player::getCurrentMount() const -{ - int32_t value; - if (getStorageValue(PSTRG_MOUNTS_CURRENTMOUNT, value)) { - return value; - } - return 0; -} - -void Player::setCurrentMount(uint8_t mountId) -{ - addStorageValue(PSTRG_MOUNTS_CURRENTMOUNT, mountId); -} - -bool Player::toggleMount(bool mount) -{ - if ((OTSYS_TIME() - lastToggleMount) < 3000 && !wasMounted) { - sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED); - return false; - } - - if (mount) { - if (isMounted()) { - return false; - } - - if (!group->access && tile->hasFlag(TILESTATE_PROTECTIONZONE)) { - sendCancelMessage(RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE); - return false; - } - - const Outfit* playerOutfit = Outfits::getInstance().getOutfitByLookType(getSex(), defaultOutfit.lookType); - if (!playerOutfit) { - return false; - } - - uint8_t currentMountId = getCurrentMount(); - if (currentMountId == 0) { - sendOutfitWindow(); - return false; - } - - Mount* currentMount = g_game.mounts.getMountByID(currentMountId); - if (!currentMount) { - return false; - } - - if (!hasMount(currentMount)) { - setCurrentMount(0); - sendOutfitWindow(); - return false; - } - - if (currentMount->premium && !isPremium()) { - sendCancelMessage(RETURNVALUE_YOUNEEDPREMIUMACCOUNT); - return false; - } - - if (hasCondition(CONDITION_OUTFIT)) { - sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); - return false; - } - - defaultOutfit.lookMount = currentMount->clientId; - - if (currentMount->speed != 0) { - g_game.changeSpeed(this, currentMount->speed); - } - } else { - if (!isMounted()) { - return false; - } - - dismount(); - } - - g_game.internalCreatureChangeOutfit(this, defaultOutfit); - lastToggleMount = OTSYS_TIME(); - return true; -} - -bool Player::tameMount(uint8_t mountId) -{ - if (!g_game.mounts.getMountByID(mountId)) { - return false; - } - - const uint8_t tmpMountId = mountId - 1; - const uint32_t key = PSTRG_MOUNTS_RANGE_START + (tmpMountId / 31); - - int32_t value; - if (getStorageValue(key, value)) { - value |= (1 << (tmpMountId % 31)); - } else { - value = (1 << (tmpMountId % 31)); - } - - addStorageValue(key, value); - return true; -} - -bool Player::untameMount(uint8_t mountId) -{ - if (!g_game.mounts.getMountByID(mountId)) { - return false; - } - - const uint8_t tmpMountId = mountId - 1; - const uint32_t key = PSTRG_MOUNTS_RANGE_START + (tmpMountId / 31); - - int32_t value; - if (!getStorageValue(key, value)) { - return true; - } - - value &= ~(1 << (tmpMountId % 31)); - addStorageValue(key, value); - - if (getCurrentMount() == mountId) { - if (isMounted()) { - dismount(); - g_game.internalCreatureChangeOutfit(this, defaultOutfit); - } - - setCurrentMount(0); - } - - return true; -} - -bool Player::hasMount(const Mount* mount) const -{ - if (isAccessPlayer()) { - return true; - } - - if (mount->premium && !isPremium()) { - return false; - } - - const uint8_t tmpMountId = mount->id - 1; - - int32_t value; - if (!getStorageValue(PSTRG_MOUNTS_RANGE_START + (tmpMountId / 31), value)) { - return false; - } - - return ((1 << (tmpMountId % 31)) & value) != 0; -} - -void Player::dismount() -{ - Mount* mount = g_game.mounts.getMountByID(getCurrentMount()); - if (mount && mount->speed > 0) { - g_game.changeSpeed(this, -mount->speed); - } - - defaultOutfit.lookMount = 0; -} - -bool Player::addOfflineTrainingTries(skills_t skill, uint64_t tries) -{ - if (tries == 0 || skill == SKILL_LEVEL) { - return false; - } - - bool sendUpdate = false; - uint32_t oldSkillValue, newSkillValue; - long double oldPercentToNextLevel, newPercentToNextLevel; - - if (skill == SKILL_MAGLEVEL) { - uint64_t currReqMana = vocation->getReqMana(magLevel); - uint64_t nextReqMana = vocation->getReqMana(magLevel + 1); - - if (currReqMana >= nextReqMana) { - return false; - } - - oldSkillValue = magLevel; - oldPercentToNextLevel = static_cast(manaSpent * 100) / nextReqMana; - - g_events->eventPlayerOnGainSkillTries(this, SKILL_MAGLEVEL, tries); - uint32_t currMagLevel = magLevel; - - while ((manaSpent + tries) >= nextReqMana) { - tries -= nextReqMana - manaSpent; - - magLevel++; - manaSpent = 0; - - g_creatureEvents->playerAdvance(this, SKILL_MAGLEVEL, magLevel - 1, magLevel); - - sendUpdate = true; - currReqMana = nextReqMana; - nextReqMana = vocation->getReqMana(magLevel + 1); - - if (currReqMana >= nextReqMana) { - tries = 0; - break; - } - } - - manaSpent += tries; - - if (magLevel != currMagLevel) { - std::ostringstream ss; - ss << "You advanced to magic level " << magLevel << '.'; - sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); - } - - uint8_t newPercent; - if (nextReqMana > currReqMana) { - newPercent = Player::getPercentLevel(manaSpent, nextReqMana); - newPercentToNextLevel = static_cast(manaSpent * 100) / nextReqMana; - } else { - newPercent = 0; - newPercentToNextLevel = 0; - } - - if (newPercent != magLevelPercent) { - magLevelPercent = newPercent; - sendUpdate = true; - } - - newSkillValue = magLevel; - } else { - uint64_t currReqTries = vocation->getReqSkillTries(skill, skills[skill].level); - uint64_t nextReqTries = vocation->getReqSkillTries(skill, skills[skill].level + 1); - if (currReqTries >= nextReqTries) { - return false; - } - - oldSkillValue = skills[skill].level; - oldPercentToNextLevel = static_cast(skills[skill].tries * 100) / nextReqTries; - - g_events->eventPlayerOnGainSkillTries(this, skill, tries); - uint32_t currSkillLevel = skills[skill].level; - - while ((skills[skill].tries + tries) >= nextReqTries) { - tries -= nextReqTries - skills[skill].tries; - - skills[skill].level++; - skills[skill].tries = 0; - skills[skill].percent = 0; - - g_creatureEvents->playerAdvance(this, skill, (skills[skill].level - 1), skills[skill].level); - - sendUpdate = true; - currReqTries = nextReqTries; - nextReqTries = vocation->getReqSkillTries(skill, skills[skill].level + 1); - - if (currReqTries >= nextReqTries) { - tries = 0; - break; - } - } - - skills[skill].tries += tries; - - if (currSkillLevel != skills[skill].level) { - std::ostringstream ss; - ss << "You advanced to " << getSkillName(skill) << " level " << skills[skill].level << '.'; - sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); - } - - uint8_t newPercent; - if (nextReqTries > currReqTries) { - newPercent = Player::getPercentLevel(skills[skill].tries, nextReqTries); - newPercentToNextLevel = static_cast(skills[skill].tries * 100) / nextReqTries; - } else { - newPercent = 0; - newPercentToNextLevel = 0; - } - - if (skills[skill].percent != newPercent) { - skills[skill].percent = newPercent; - sendUpdate = true; - } - - newSkillValue = skills[skill].level; - } - - if (sendUpdate) { - sendSkills(); - } - - std::ostringstream ss; - ss << std::fixed << std::setprecision(2) << "Your " << ucwords(getSkillName(skill)) << " skill changed from level " << oldSkillValue << " (with " << oldPercentToNextLevel << "% progress towards level " << (oldSkillValue + 1) << ") to level " << newSkillValue << " (with " << newPercentToNextLevel << "% progress towards level " << (newSkillValue + 1) << ')'; - sendTextMessage(MESSAGE_EVENT_ADVANCE, ss.str()); - return sendUpdate; -} - -bool Player::hasModalWindowOpen(uint32_t modalWindowId) const -{ - return find(modalWindows.begin(), modalWindows.end(), modalWindowId) != modalWindows.end(); -} - -void Player::onModalWindowHandled(uint32_t modalWindowId) -{ - modalWindows.remove(modalWindowId); -} - -void Player::sendModalWindow(const ModalWindow& modalWindow) -{ - if (!client) { - return; - } - - modalWindows.push_front(modalWindow.id); - client->sendModalWindow(modalWindow); -} - -void Player::clearModalWindows() -{ - modalWindows.clear(); -} - -uint16_t Player::getHelpers() const -{ - uint16_t helpers; - - if (guild && party) { - std::unordered_set helperSet; - - const auto& guildMembers = guild->getMembersOnline(); - helperSet.insert(guildMembers.begin(), guildMembers.end()); - - const auto& partyMembers = party->getMembers(); - helperSet.insert(partyMembers.begin(), partyMembers.end()); - - const auto& partyInvitees = party->getInvitees(); - helperSet.insert(partyInvitees.begin(), partyInvitees.end()); - - helperSet.insert(party->getLeader()); - - helpers = helperSet.size(); - } else if (guild) { - helpers = guild->getMembersOnline().size(); - } else if (party) { - helpers = party->getMemberCount() + party->getInvitationCount() + 1; - } else { - helpers = 0; - } - - return helpers; -} - void Player::sendClosePrivate(uint16_t channelId) { if (channelId == CHANNEL_GUILD || channelId == CHANNEL_PARTY) { @@ -4481,6 +3740,7 @@ size_t Player::getMaxDepotItems() const } else if (isPremium()) { return 2000; } + return 1000; } diff --git a/src/player.h b/src/player.h index db0dc4e..f088f13 100644 --- a/src/player.h +++ b/src/player.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,14 +29,12 @@ #include "protocolgame.h" #include "ioguild.h" #include "party.h" -#include "inbox.h" -#include "depotchest.h" #include "depotlocker.h" #include "guild.h" #include "groups.h" #include "town.h" -#include "mounts.h" +class BehaviourDatabase; class House; class NetworkMessage; class Weapon; @@ -53,17 +51,9 @@ enum skillsid_t { SKILLVALUE_PERCENT = 2, }; -enum fightMode_t : uint8_t { - FIGHTMODE_ATTACK = 1, - FIGHTMODE_BALANCED = 2, - FIGHTMODE_DEFENSE = 3, -}; - -enum pvpMode_t : uint8_t { - PVP_MODE_DOVE = 0, - PVP_MODE_WHITE_HAND = 1, - PVP_MODE_YELLOW_HAND = 2, - PVP_MODE_RED_FIST = 3, +enum chaseMode_t : uint8_t { + CHASEMODE_STANDSTILL = 0, + CHASEMODE_FOLLOW = 1, }; enum tradestate_t : uint8_t { @@ -75,14 +65,11 @@ enum tradestate_t : uint8_t { }; struct VIPEntry { - VIPEntry(uint32_t guid, std::string name, std::string description, uint32_t icon, bool notify) : - guid(guid), name(std::move(name)), description(std::move(description)), icon(icon), notify(notify) {} + VIPEntry(uint32_t guid, std::string name) : + guid(guid), name(std::move(name)) {} uint32_t guid; std::string name; - std::string description; - uint32_t icon; - bool notify; }; struct OpenContainer { @@ -103,10 +90,10 @@ struct Skill { uint8_t percent = 0; }; -using MuteCountMap = std::map; +typedef std::map MuteCountMap; static constexpr int32_t PLAYER_MAX_SPEED = 1500; -static constexpr int32_t PLAYER_MIN_SPEED = 10; +static constexpr int32_t PLAYER_MIN_SPEED = 0; class Player final : public Creature, public Cylinder { @@ -118,14 +105,14 @@ class Player final : public Creature, public Cylinder Player(const Player&) = delete; Player& operator=(const Player&) = delete; - Player* getPlayer() override { + Player* getPlayer() final { return this; } - const Player* getPlayer() const override { + const Player* getPlayer() const final { return this; } - void setID() override { + void setID() final { if (id == 0) { id = playerAutoID++; } @@ -133,37 +120,16 @@ class Player final : public Creature, public Cylinder static MuteCountMap muteCountMap; - const std::string& getName() const override { + const std::string& getName() const final { return name; } void setName(std::string name) { this->name = std::move(name); } - const std::string& getNameDescription() const override { + const std::string& getNameDescription() const final { return name; } - std::string getDescription(int32_t lookDistance) const override; - - CreatureType_t getType() const override { - return CREATURETYPE_PLAYER; - } - - uint8_t getCurrentMount() const; - void setCurrentMount(uint8_t mountId); - bool isMounted() const { - return defaultOutfit.lookMount != 0; - } - bool toggleMount(bool mount); - bool tameMount(uint8_t mountId); - bool untameMount(uint8_t mountId); - bool hasMount(const Mount* mount) const; - void dismount(); - - void sendFYIBox(const std::string& message) { - if (client) { - client->sendFYIBox(message); - } - } + std::string getDescription(int32_t lookDistance) const final; void setGUID(uint32_t guid) { this->guid = guid; @@ -171,12 +137,12 @@ class Player final : public Creature, public Cylinder uint32_t getGUID() const { return guid; } - bool canSeeInvisibility() const override { + bool canSeeInvisibility() const final { return hasFlag(PlayerFlag_CanSenseInvisibility) || group->access; } - void removeList() override; - void addList() override; + void removeList() final; + void addList() final; void kickPlayer(bool displayEffect); static uint64_t getExpForLevel(int32_t lv) { @@ -188,25 +154,6 @@ class Player final : public Creature, public Cylinder return staminaMinutes; } - bool addOfflineTrainingTries(skills_t skill, uint64_t tries); - - void addOfflineTrainingTime(int32_t addTime) { - offlineTrainingTime = std::min(12 * 3600 * 1000, offlineTrainingTime + addTime); - } - void removeOfflineTrainingTime(int32_t removeTime) { - offlineTrainingTime = std::max(0, offlineTrainingTime - removeTime); - } - int32_t getOfflineTrainingTime() const { - return offlineTrainingTime; - } - - int32_t getOfflineTrainingSkill() const { - return offlineTrainingSkill; - } - void setOfflineTrainingSkill(int32_t skill) { - offlineTrainingSkill = skill; - } - uint64_t getBankBalance() const { return bankBalance; } @@ -236,23 +183,12 @@ class Player final : public Creature, public Cylinder } bool isInWar(const Player* player) const; - bool isInWarList(uint32_t guildId) const; - - void setLastWalkthroughAttempt(int64_t walkthroughAttempt) { - lastWalkthroughAttempt = walkthroughAttempt; - } - void setLastWalkthroughPosition(Position walkthroughPosition) { - lastWalkthroughPosition = walkthroughPosition; - } - - Inbox* getInbox() const { - return inbox; - } + bool isInWarList(uint32_t guild_id) const; uint16_t getClientIcons() const; - const GuildWarVector& getGuildWarVector() const { - return guildWarVector; + const GuildWarList& getGuildWarList() const { + return guildWarList; } Vocation* getVocation() const { @@ -292,8 +228,6 @@ class Player final : public Creature, public Cylinder void removePartyInvitation(Party* party); void clearPartyInvitations(); - GuildEmblems_t getGuildEmblem(const Player* player) const; - uint64_t getSpentMana() const { return manaSpent; } @@ -339,7 +273,7 @@ class Player final : public Creature, public Cylinder bool canOpenCorpse(uint32_t ownerId) const; - void addStorageValue(const uint32_t key, const int32_t value, const bool isLogin = false); + void addStorageValue(const uint32_t key, const int32_t value); bool getStorageValue(const uint32_t key, int32_t& value) const; void genReservedStorageRange(); @@ -350,25 +284,24 @@ class Player final : public Creature, public Cylinder return group; } - void setInMarket(bool value) { - inMarket = value; - } - bool isInMarket() const { - return inMarket; - } - - void setLastDepotId(int16_t newId) { - lastDepotId = newId; - } - int16_t getLastDepotId() const { - return lastDepotId; - } - void resetIdleTime() { idleTime = 0; + resetLastWalkingTime(); } - bool isInGhostMode() const override { + int32_t getIdleTime() const { + return idleTime; + } + + void resetLastWalkingTime() { + lastWalkingTime = 0; + } + + int32_t getLastWalkingTime() const { + return lastWalkingTime; + } + + bool isInGhostMode() const { return ghostMode; } void switchGhostMode() { @@ -405,12 +338,13 @@ class Player final : public Creature, public Cylinder bool isPremium() const; void setPremiumDays(int32_t v); - uint16_t getHelpers() const; - bool setVocation(uint16_t vocId); uint16_t getVocationId() const { return vocation->getId(); } + uint16_t getVocationFlagId() const { + return vocation->getFlagId(); + } PlayerSex_t getSex() const { return sex; @@ -441,11 +375,7 @@ class Player final : public Creature, public Cylinder this->town = town; } - void clearModalWindows(); - bool hasModalWindowOpen(uint32_t modalWindowId) const; - void onModalWindowHandled(uint32_t modalWindowId); - - bool isPushable() const override; + bool isPushable() const final; uint32_t isMuted() const; void addMessageBuffer(); void removeMessageBuffer(); @@ -471,12 +401,14 @@ class Player final : public Creature, public Cylinder } } - int32_t getMaxHealth() const override { + int32_t getMaxHealth() const final { return std::max(1, healthMax + varStats[STAT_MAXHITPOINTS]); } + uint32_t getMana() const { return mana; } + uint32_t getMaxMana() const { return std::max(0, manaMax + varStats[STAT_MAXMANAPOINTS]); } @@ -494,28 +426,20 @@ class Player final : public Creature, public Cylinder varSkills[skill] += modifier; } - void setVarSpecialSkill(SpecialSkills_t skill, int32_t modifier) { - varSpecialSkills[skill] += modifier; - } - void setVarStats(stats_t stat, int32_t modifier); int32_t getDefaultStats(stats_t stat) const; void addConditionSuppressions(uint32_t conditions); void removeConditionSuppressions(uint32_t conditions); - DepotChest* getDepotChest(uint32_t depotId, bool autoCreate); - DepotLocker* getDepotLocker(uint32_t depotId); - void onReceiveMail() const; - bool isNearDepotBox() const; + DepotLocker* getDepotLocker(uint32_t depotId, bool autoCreate); + void onReceiveMail(uint32_t townId) const; + bool isNearDepotBox(uint32_t townId) const; - bool canSee(const Position& pos) const override; - bool canSeeCreature(const Creature* creature) const override; + bool canSee(const Position& pos) const final; + bool canSeeCreature(const Creature* creature) const final; - bool canWalkthrough(const Creature* creature) const; - bool canWalkthroughEx(const Creature* creature) const; - - RaceType_t getRace() const override { + RaceType_t getRace() const final { return RACE_BLOOD; } @@ -532,51 +456,27 @@ class Player final : public Creature, public Cylinder return tradeItem; } - //shop functions - void setShopOwner(Npc* owner, int32_t onBuy, int32_t onSell) { - shopOwner = owner; - purchaseCallback = onBuy; - saleCallback = onSell; - } - - Npc* getShopOwner(int32_t& onBuy, int32_t& onSell) { - onBuy = purchaseCallback; - onSell = saleCallback; - return shopOwner; - } - - const Npc* getShopOwner(int32_t& onBuy, int32_t& onSell) const { - onBuy = purchaseCallback; - onSell = saleCallback; - return shopOwner; - } - //V.I.P. functions - void notifyStatusChange(Player* loginPlayer, VipStatus_t status); + void notifyStatusChange(Player* player, VipStatus_t status); bool removeVIP(uint32_t vipGuid); bool addVIP(uint32_t vipGuid, const std::string& vipName, VipStatus_t status); bool addVIPInternal(uint32_t vipGuid); - bool editVIP(uint32_t vipGuid, const std::string& description, uint32_t icon, bool notify); //follow functions - bool setFollowCreature(Creature* creature) override; - void goToFollowCreature() override; + bool setFollowCreature(Creature* creature) final; + void goToFollowCreature() final; //follow events - void onFollowCreature(const Creature* creature) override; + void onFollowCreature(const Creature* creature) final; //walk events - void onWalk(Direction& dir) override; - void onWalkAborted() override; - void onWalkComplete() override; + void onWalk(Direction& dir) final; + void onWalkAborted() final; + void onWalkComplete() final; void stopWalk(); - void openShopWindow(Npc* npc, const std::list& shop); - bool closeShopWindow(bool sendCloseShopWindow = true); - bool updateSaleShopList(const Item* item); - bool hasShopItemForSale(uint32_t itemId, uint8_t subType) const; - void setChaseMode(bool mode); + void setChaseMode(chaseMode_t mode); void setFightMode(fightMode_t mode) { fightMode = mode; } @@ -585,14 +485,14 @@ class Player final : public Creature, public Cylinder } //combat functions - bool setAttackedCreature(Creature* creature) override; - bool isImmune(CombatType_t type) const override; - bool isImmune(ConditionType_t type) const override; + bool setAttackedCreature(Creature* creature) final; + bool isImmune(CombatType_t type) const final; + bool isImmune(ConditionType_t type) const final; bool hasShield() const; - bool isAttackable() const override; + bool isAttackable() const final; static bool lastHitIsPlayer(Creature* lastHitCreature); - void changeHealth(int32_t healthChange, bool sendHealthChange = true) override; + void changeHealth(int32_t healthChange, bool sendHealthChange = true) final; void changeMana(int32_t manaChange); void changeSoul(int32_t soulChange); @@ -600,15 +500,12 @@ class Player final : public Creature, public Cylinder return pzLocked; } BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, - bool checkDefense = false, bool checkArmor = false, bool field = false) override; - void doAttacking(uint32_t interval) override; - bool hasExtraSwing() override { + bool checkDefense = false, bool checkArmor = false, bool field = false) final; + void doAttacking(uint32_t interval) final; + bool hasExtraSwing() final { return lastAttack > 0 && ((OTSYS_TIME() - lastAttack) >= getAttackSpeed()); } - uint16_t getSpecialSkill(uint8_t skill) const { - return std::max(0, varSpecialSkills[skill]); - } uint16_t getSkillLevel(uint8_t skill) const { return std::max(0, skills[skill].level + varSkills[skill]); } @@ -626,51 +523,48 @@ class Player final : public Creature, public Cylinder return lastAttackBlockType; } - Item* getWeapon(slots_t slot, bool ignoreAmmo) const; - Item* getWeapon(bool ignoreAmmo = false) const; - WeaponType_t getWeaponType() const; - int32_t getWeaponSkill(const Item* item) const; + Item* getWeapon() const; + Item* getAmmunition() const; void getShieldAndWeapon(const Item*& shield, const Item*& weapon) const; - void drainHealth(Creature* attacker, int32_t damage) override; + void drainHealth(Creature* attacker, int32_t damage) final; void drainMana(Creature* attacker, int32_t manaLoss); void addManaSpent(uint64_t amount); void addSkillAdvance(skills_t skill, uint64_t count); - int32_t getArmor() const override; - int32_t getDefense() const override; - float getAttackFactor() const override; - float getDefenseFactor() const override; + int32_t getArmor() const final; + int32_t getDefense() final; + fightMode_t getFightMode() const; void addInFightTicks(bool pzlock = false); - uint64_t getGainedExperience(Creature* attacker) const override; + uint64_t getGainedExperience(Creature* attacker) const final; //combat event functions - void onAddCondition(ConditionType_t type) override; - void onAddCombatCondition(ConditionType_t type) override; - void onEndCondition(ConditionType_t type) override; - void onCombatRemoveCondition(Condition* condition) override; - void onAttackedCreature(Creature* target) override; - void onAttacked() override; - void onAttackedCreatureDrainHealth(Creature* target, int32_t points) override; - void onTargetCreatureGainHealth(Creature* target, int32_t points) override; - bool onKilledCreature(Creature* target, bool lastHit = true) override; - void onGainExperience(uint64_t gainExp, Creature* target) override; + void onAddCondition(ConditionType_t type) final; + void onAddCombatCondition(ConditionType_t type) final; + void onEndCondition(ConditionType_t type) final; + void onCombatRemoveCondition(Condition* condition) final; + void onAttackedCreature(Creature* target) final; + void onAttacked() final; + void onAttackedCreatureDrainHealth(Creature* target, int32_t points) final; + void onTargetCreatureGainHealth(Creature* target, int32_t points) final; + bool onKilledCreature(Creature* target, bool lastHit = true) final; + void onGainExperience(uint64_t gainExp, Creature* target) final; void onGainSharedExperience(uint64_t gainExp, Creature* source); - void onAttackedCreatureBlockHit(BlockType_t blockType) override; - void onBlockHit() override; - void onChangeZone(ZoneType_t zone) override; - void onAttackedCreatureChangeZone(ZoneType_t zone) override; - void onIdleStatus() override; - void onPlacedCreature() override; + void onAttackedCreatureBlockHit(BlockType_t blockType) final; + void onBlockHit() final; + void onChangeZone(ZoneType_t zone) final; + void onAttackedCreatureChangeZone(ZoneType_t zone) final; + void onIdleStatus() final; + void onPlacedCreature() final; - LightInfo getCreatureLight() const override; + void getCreatureLight(LightInfo& light) const final; - Skulls_t getSkull() const override; - Skulls_t getSkullClient(const Creature* creature) const override; - int64_t getSkullTicks() const { return skullTicks; } - void setSkullTicks(int64_t ticks) { skullTicks = ticks; } + Skulls_t getSkull() const final; + Skulls_t getSkullClient(const Creature* creature) const final; + time_t getPlayerKillerEnd() const { return playerKillerEnd; } + void setPlayerKillerEnd(time_t ticks) { playerKillerEnd = ticks; } bool hasAttacked(const Player* attacked) const; void addAttacked(const Player* attacked); @@ -682,7 +576,7 @@ class Player final : public Creature, public Cylinder client->sendCreatureSkull(creature); } } - void checkSkullTicks(int64_t ticks); + void checkSkullTicks(); bool canWear(uint32_t lookType, uint8_t addons) const; void addOutfit(uint16_t lookType, uint8_t addons); @@ -690,6 +584,7 @@ class Player final : public Creature, public Cylinder bool removeOutfitAddon(uint16_t lookType, uint8_t addons); bool getOutfitAddons(const Outfit& outfit, uint8_t& addons) const; + bool canLogout(); size_t getMaxVIPEntries() const; @@ -701,7 +596,7 @@ class Player final : public Creature, public Cylinder if (client) { int32_t stackpos = tile->getStackposOfItem(this, item); if (stackpos != -1) { - client->sendAddTileItem(pos, stackpos, item); + client->sendAddTileItem(pos, item, stackpos); } } } @@ -724,16 +619,6 @@ class Player final : public Creature, public Cylinder } } - void sendChannelMessage(const std::string& author, const std::string& text, SpeakClasses type, uint16_t channel) { - if (client) { - client->sendChannelMessage(author, text, type, channel); - } - } - void sendChannelEvent(uint16_t channelId, const std::string& playerName, ChannelEvent_t channelEvent) { - if (client) { - client->sendChannelEvent(channelId, playerName, channelEvent); - } - } void sendCreatureAppear(const Creature* creature, const Position& pos, bool isLogin) { if (client) { client->sendAddCreature(creature, pos, creature->getTile()->getStackposOfCreature(this, creature), isLogin); @@ -804,37 +689,11 @@ class Player final : public Creature, public Cylinder client->sendCreatureLight(creature); } } - void sendCreatureWalkthrough(const Creature* creature, bool walkthrough) { - if (client) { - client->sendCreatureWalkthrough(creature, walkthrough); - } - } void sendCreatureShield(const Creature* creature) { if (client) { client->sendCreatureShield(creature); } } - void sendCreatureType(uint32_t creatureId, uint8_t creatureType) { - if (client) { - client->sendCreatureType(creatureId, creatureType); - } - } - void sendCreatureHelpers(uint32_t creatureId, uint16_t helpers) { - if (client) { - client->sendCreatureHelpers(creatureId, helpers); - } - } - void sendSpellCooldown(uint8_t spellId, uint32_t time) { - if (client) { - client->sendSpellCooldown(spellId, time); - } - } - void sendSpellGroupCooldown(SpellGroup_t groupId, uint32_t time) { - if (client) { - client->sendSpellGroupCooldown(groupId, time); - } - } - void sendModalWindow(const ModalWindow& modalWindow); //container void sendAddContainerItem(const Container* container, const Item* item); @@ -852,25 +711,20 @@ class Player final : public Creature, public Cylinder client->sendInventoryItem(slot, item); } } - void sendItems() { - if (client) { - client->sendItems(); - } - } //event methods void onUpdateTileItem(const Tile* tile, const Position& pos, const Item* oldItem, - const ItemType& oldType, const Item* newItem, const ItemType& newType) override; + const ItemType& oldType, const Item* newItem, const ItemType& newType) final; void onRemoveTileItem(const Tile* tile, const Position& pos, const ItemType& iType, - const Item* item) override; + const Item* item) final; - void onCreatureAppear(Creature* creature, bool isLogin) override; - void onRemoveCreature(Creature* creature, bool isLogout) override; + 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) override; + const Tile* oldTile, const Position& oldPos, bool teleport) final; - void onAttackedCreatureDisappear(bool isLogout) override; - void onFollowCreatureDisappear(bool isLogout) override; + void onAttackedCreatureDisappear(bool isLogout) final; + void onFollowCreatureDisappear(bool isLogout) final; //container void onAddContainerItem(const Item* item); @@ -940,16 +794,16 @@ class Player final : public Creature, public Cylinder } } void sendStats(); - void sendBasicData() const { - if (client) { - client->sendBasicData(); - } - } void sendSkills() const { if (client) { client->sendSkills(); } } + void sendAnimatedText(const Position& pos, uint8_t color, const std::string& text) const { + if (client) { + client->sendAnimatedText(pos, color, text); + } + } void sendTextMessage(MessageClasses mclass, const std::string& message) const { if (client) { client->sendTextMessage(TextMessage(mclass, message)); @@ -960,11 +814,6 @@ class Player final : public Creature, public Cylinder client->sendTextMessage(message); } } - void sendReLoginWindow(uint8_t unfairFightReduction) const { - if (client) { - client->sendReLoginWindow(unfairFightReduction); - } - } void sendTextWindow(Item* item, uint16_t maxlen, bool canWrite) const { if (client) { client->sendTextWindow(windowTextId, item, maxlen, canWrite); @@ -980,62 +829,6 @@ class Player final : public Creature, public Cylinder client->sendToChannel(creature, type, text, channelId); } } - void sendShop(Npc* npc) const { - if (client) { - client->sendShop(npc, shopItemList); - } - } - void sendSaleItemList() const { - if (client) { - client->sendSaleItemList(shopItemList); - } - } - void sendCloseShop() const { - if (client) { - client->sendCloseShop(); - } - } - void sendMarketEnter(uint32_t depotId) const { - if (client) { - client->sendMarketEnter(depotId); - } - } - void sendMarketLeave() { - inMarket = false; - if (client) { - client->sendMarketLeave(); - } - } - void sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& buyOffers, const MarketOfferList& sellOffers) const { - if (client) { - client->sendMarketBrowseItem(itemId, buyOffers, sellOffers); - } - } - void sendMarketBrowseOwnOffers(const MarketOfferList& buyOffers, const MarketOfferList& sellOffers) const { - if (client) { - client->sendMarketBrowseOwnOffers(buyOffers, sellOffers); - } - } - void sendMarketBrowseOwnHistory(const HistoryMarketOfferList& buyOffers, const HistoryMarketOfferList& sellOffers) const { - if (client) { - client->sendMarketBrowseOwnHistory(buyOffers, sellOffers); - } - } - void sendMarketDetail(uint16_t itemId) const { - if (client) { - client->sendMarketDetail(itemId); - } - } - void sendMarketAcceptOffer(const MarketOfferEx& offer) const { - if (client) { - client->sendMarketAcceptOffer(offer); - } - } - void sendMarketCancelOffer(const MarketOfferEx& offer) const { - if (client) { - client->sendMarketCancelOffer(offer); - } - } void sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack) const { if (client) { client->sendTradeItemRequest(traderName, item, ack); @@ -1046,7 +839,7 @@ class Player final : public Creature, public Cylinder client->sendCloseTrade(); } } - void sendWorldLight(LightInfo lightInfo) { + void sendWorldLight(const LightInfo& lightInfo) { if (client) { client->sendWorldLight(lightInfo); } @@ -1071,35 +864,30 @@ class Player final : public Creature, public Cylinder client->sendCloseContainer(cid); } } + void sendRemoveRuleViolationReport(const std::string& name) const { + if (client) { + client->sendRemoveRuleViolationReport(name); + } + } + void sendRuleViolationCancel(const std::string& name) const { + if (client) { + client->sendRuleViolationCancel(name); + } + } + void sendLockRuleViolationReport() const { + if (client) { + client->sendLockRuleViolation(); + } + } + void sendRuleViolationsChannel(uint16_t channelId) const { + if (client) { + client->sendRuleViolationsChannel(channelId); + } + } - void sendChannel(uint16_t channelId, const std::string& channelName, const UsersMap* channelUsers, const InvitedMap* invitedUsers) { + void sendChannel(uint16_t channelId, const std::string& channelName) { if (client) { - client->sendChannel(channelId, channelName, channelUsers, invitedUsers); - } - } - void sendTutorial(uint8_t tutorialId) { - if (client) { - client->sendTutorial(tutorialId); - } - } - void sendAddMarker(const Position& pos, uint8_t markType, const std::string& desc) { - if (client) { - client->sendAddMarker(pos, markType, desc); - } - } - void sendQuestLog() { - if (client) { - client->sendQuestLog(); - } - } - void sendQuestLine(const Quest* quest) { - if (client) { - client->sendQuestLine(quest); - } - } - void sendEnterWorld() { - if (client) { - client->sendEnterWorld(); + client->sendChannel(channelId, channelName); } } void sendFightModes() { @@ -1117,10 +905,10 @@ class Player final : public Creature, public Cylinder lastPong = OTSYS_TIME(); } - void onThink(uint32_t interval) override; + void onThink(uint32_t interval) 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 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; void setNextAction(int64_t time) { if (time > nextAction) { @@ -1142,7 +930,7 @@ class Player final : public Creature, public Cylinder void forgetInstantSpell(const std::string& spellName); bool hasLearnedInstantSpell(const std::string& spellName) const; - private: + protected: std::forward_list getMuteConditions() const; void checkTradeState(const Item* item); @@ -1150,7 +938,7 @@ class Player final : public Creature, public Cylinder void gainExperience(uint64_t gainExp, Creature* source); void addExperience(Creature* source, uint64_t exp, bool sendText = false); - void removeExperience(uint64_t exp, bool sendText = false); + void removeExperience(uint64_t exp); void updateInventoryWeight(); @@ -1158,55 +946,56 @@ class Player final : public Creature, public Cylinder void setNextWalkTask(SchedulerTask* task); void setNextActionTask(SchedulerTask* task); - void death(Creature* lastHitCreature) override; - bool dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified) override; - Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) override; + void dropLoot(Container* corpse, Creature*) final; + void death(Creature* lastHitCreature) final; + bool dropCorpse(Creature* lastHitCreature, Creature* mostDamageCreature, bool lastHitUnjustified, bool mostDamageUnjustified) final; + Item* getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) final; //cylinder implementations ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const override; + 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 override; - ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const override; + 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) override; + uint32_t& flags) final; - void addThing(Thing*) override {} - void addThing(int32_t index, Thing* thing) override; + void addThing(Thing*) final {} + void addThing(int32_t index, Thing* thing) final; - void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override; - void replaceThing(uint32_t index, Thing* thing) override; + 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) override; + void removeThing(Thing* thing, uint32_t count) final; - int32_t getThingIndex(const Thing* thing) const override; - size_t getFirstIndex() const override; - size_t getLastIndex() const override; - uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const override; - std::map& getAllItemTypeCount(std::map& countMap) const override; - Thing* getThing(size_t index) const override; + 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& getAllItemTypeCount(std::map& countMap) const final; + Thing* getThing(size_t index) const final; - void internalAddThing(Thing* thing) override; - void internalAddThing(uint32_t index, Thing* thing) override; + void internalAddThing(Thing* thing) final; + void internalAddThing(uint32_t index, Thing* thing) final; + + uint32_t checkPlayerKilling(); std::unordered_set attackedSet; std::unordered_set VIPList; std::map openContainers; std::map depotLockerMap; - std::map depotChests; std::map storageMap; std::vector outfits; - GuildWarVector guildWarVector; - - std::list shopItemList; + GuildWarList guildWarList; std::forward_list invitePartyList; - std::forward_list modalWindows; std::forward_list learnedInstantSpellList; std::forward_list storedConditionList; // TODO: This variable is only temporarily used when logging in, get rid of it somehow + std::list murderTimeStamps; + std::string name; std::string guildNick; @@ -1217,30 +1006,26 @@ class Player final : public Creature, public Cylinder time_t lastLoginSaved = 0; time_t lastLogout = 0; + time_t playerKillerEnd = 0; uint64_t experience = 0; uint64_t manaSpent = 0; - uint64_t lastAttack = 0; uint64_t bankBalance = 0; - uint64_t lastQuestlogUpdate = 0; + int64_t lastAttack = 0; int64_t lastFailedFollow = 0; - int64_t skullTicks = 0; - int64_t lastWalkthroughAttempt = 0; - int64_t lastToggleMount = 0; int64_t lastPing; int64_t lastPong; int64_t nextAction = 0; - + int64_t earliestAttackTime = 0; + BedItem* bedItem = nullptr; Guild* guild = nullptr; const GuildRank* guildRank = nullptr; Group* group = nullptr; - Inbox* inbox; Item* tradeItem = nullptr; Item* inventory[CONST_SLOT_LAST + 1] = {}; Item* writeItem = nullptr; House* editHouse = nullptr; - Npc* shopOwner = nullptr; Party* party = nullptr; Player* tradePartner = nullptr; ProtocolGame_ptr client; @@ -1267,22 +1052,16 @@ class Player final : public Creature, public Cylinder uint32_t mana = 0; uint32_t manaMax = 0; int32_t varSkills[SKILL_LAST + 1] = {}; - int32_t varSpecialSkills[SPECIALSKILL_LAST + 1] = {}; int32_t varStats[STAT_LAST + 1] = {}; - int32_t purchaseCallback = -1; - int32_t saleCallback = -1; int32_t MessageBufferCount = 0; int32_t premiumDays = 0; int32_t bloodHitCount = 0; int32_t shieldBlockCount = 0; - int32_t offlineTrainingSkill = -1; - int32_t offlineTrainingTime = 0; int32_t idleTime = 0; + int32_t lastWalkingTime = 0; - uint16_t lastStatsTrainingTime = 0; - uint16_t staminaMinutes = 2520; + uint16_t staminaMinutes = 3360; uint16_t maxWriteLen = 0; - int16_t lastDepotId = -1; uint8_t soul = 0; uint8_t blessings = 0; @@ -1293,13 +1072,11 @@ class Player final : public Creature, public Cylinder OperatingSystem_t operatingSystem = CLIENTOS_NONE; BlockType_t lastAttackBlockType = BLOCK_NONE; tradestate_t tradeState = TRADE_NONE; + chaseMode_t chaseMode = CHASEMODE_STANDSTILL; fightMode_t fightMode = FIGHTMODE_ATTACK; AccountType_t accountType = ACCOUNT_TYPE_NORMAL; - bool chaseMode = false; bool secureMode = false; - bool inMarket = false; - bool wasMounted = false; bool ghostMode = false; bool pzLocked = false; bool isConnecting = false; @@ -1309,12 +1086,12 @@ class Player final : public Creature, public Cylinder static uint32_t playerAutoID; void updateItemsLight(bool internal = false); - int32_t getStepSpeed() const override { + int32_t getStepSpeed() const final { return std::max(PLAYER_MIN_SPEED, std::min(PLAYER_MAX_SPEED, getSpeed())); } void updateBaseSpeed() { if (!hasFlag(PlayerFlag_SetMaxSpeed)) { - baseSpeed = vocation->getBaseSpeed() + (2 * (level - 1)); + baseSpeed = vocation->getBaseSpeed() + (level - 1); } else { baseSpeed = PLAYER_MAX_SPEED; } @@ -1327,21 +1104,22 @@ class Player final : public Creature, public Cylinder } static uint8_t getPercentLevel(uint64_t count, uint64_t nextLevelCount); + static uint16_t getDropLootPercent(); double getLostPercent() const; - uint64_t getLostExperience() const override { + uint64_t getLostExperience() const final { return skillLoss ? static_cast(experience * getLostPercent()) : 0; } - uint32_t getDamageImmunities() const override { + uint32_t getDamageImmunities() const final { return damageImmunities; } - uint32_t getConditionImmunities() const override { + uint32_t getConditionImmunities() const final { return conditionImmunities; } - uint32_t getConditionSuppressions() const override { + uint32_t getConditionSuppressions() const final { return conditionSuppressions; } - uint16_t getLookCorpse() const override; - void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const override; + uint16_t getLookCorpse() const final; + void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const final; friend class Game; friend class Npc; @@ -1350,6 +1128,8 @@ class Player final : public Creature, public Cylinder friend class Actions; friend class IOLoginData; friend class ProtocolGame; + friend class BehaviourDatabase; + friend class ConjureSpell; }; #endif diff --git a/src/position.cpp b/src/position.cpp index 247aace..a972899 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/position.h b/src/position.h index 1d48840..abd83ad 100644 --- a/src/position.h +++ b/src/position.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,32 +42,32 @@ struct Position constexpr Position(uint16_t x, uint16_t y, uint8_t z) : x(x), y(y), z(z) {} template - static bool areInRange(const Position& p1, const Position& p2) { + inline static bool areInRange(const Position& p1, const Position& p2) { return Position::getDistanceX(p1, p2) <= deltax && Position::getDistanceY(p1, p2) <= deltay; } template - static bool areInRange(const Position& p1, const Position& p2) { + 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; } - static int_fast32_t getOffsetX(const Position& p1, const Position& p2) { + inline static int_fast32_t getOffsetX(const Position& p1, const Position& p2) { return p1.getX() - p2.getX(); } - static int_fast32_t getOffsetY(const Position& p1, const Position& p2) { + inline static int_fast32_t getOffsetY(const Position& p1, const Position& p2) { return p1.getY() - p2.getY(); } - static int_fast16_t getOffsetZ(const Position& p1, const Position& p2) { + inline static int_fast16_t getOffsetZ(const Position& p1, const Position& p2) { return p1.getZ() - p2.getZ(); } - static int32_t getDistanceX(const Position& p1, const Position& p2) { + inline static int32_t getDistanceX(const Position& p1, const Position& p2) { return std::abs(Position::getOffsetX(p1, p2)); } - static int32_t getDistanceY(const Position& p1, const Position& p2) { + inline static int32_t getDistanceY(const Position& p1, const Position& p2) { return std::abs(Position::getOffsetY(p1, p2)); } - static int16_t getDistanceZ(const Position& p1, const Position& p2) { + inline static int16_t getDistanceZ(const Position& p1, const Position& p2) { return std::abs(Position::getOffsetZ(p1, p2)); } @@ -123,9 +123,9 @@ struct Position return Position(x - p1.x, y - p1.y, z - p1.z); } - int_fast32_t getX() const { return x; } - int_fast32_t getY() const { return y; } - int_fast16_t getZ() const { return 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&); diff --git a/src/protocol.cpp b/src/protocol.cpp index 6980580..2686f1a 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,7 +22,6 @@ #include "protocol.h" #include "outputmessage.h" #include "rsa.h" -#include "xtea.h" extern RSA g_RSA; @@ -33,7 +32,7 @@ void Protocol::onSendMessage(const OutputMessage_ptr& msg) const if (encryptionEnabled) { XTEA_encrypt(*msg); - msg->addCryptoHeader(checksumEnabled); + msg->addCryptoHeader(); } } } @@ -52,7 +51,8 @@ OutputMessage_ptr Protocol::getOutputBuffer(int32_t size) //dispatcher thread if (!outputBuffer) { outputBuffer = OutputMessagePool::getOutputMessage(); - } else if ((outputBuffer->getLength() + size) > NetworkMessage::MAX_PROTOCOL_BODY_LENGTH) { + } + else if ((outputBuffer->getLength() + size) > NetworkMessage::MAX_PROTOCOL_BODY_LENGTH) { send(outputBuffer); outputBuffer = OutputMessagePool::getOutputMessage(); } @@ -61,27 +61,73 @@ OutputMessage_ptr Protocol::getOutputBuffer(int32_t size) 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() % 8u; + size_t paddingBytes = msg.getLength() % 8; if (paddingBytes != 0) { msg.addPaddingBytes(8 - paddingBytes); } uint8_t* buffer = msg.getOutputBuffer(); - xtea::encrypt(buffer, msg.getLength(), key); + 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() - 6) & 7) != 0) { + if (((msg.getLength() - 2) & 7) != 0) { return false; } - uint8_t* buffer = msg.getBuffer() + msg.getBufferPosition(); - xtea::decrypt(buffer, msg.getLength() - 6, key); + const uint32_t delta = 0x61C88647; - uint16_t innerLength = msg.get(); - if (innerLength + 8 > msg.getLength()) { + uint8_t* buffer = msg.getBuffer() + msg.getBufferPosition(); + const size_t messageLength = (msg.getLength() - 2); + 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(); + if (innerLength > msg.getLength() - 4) { return false; } @@ -91,7 +137,7 @@ bool Protocol::XTEA_decrypt(NetworkMessage& msg) const bool Protocol::RSA_decrypt(NetworkMessage& msg) { - if ((msg.getLength() - msg.getBufferPosition()) < 128) { + if ((msg.getLength() - msg.getBufferPosition()) != 128) { return false; } diff --git a/src/protocol.h b/src/protocol.h index 3cd757d..43ad54d 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -1,6 +1,6 @@ /** * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Copyright (C) 2016 Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,85 +21,77 @@ #define FS_PROTOCOL_H_D71405071ACF4137A4B1203899DE80E1 #include "connection.h" -#include "xtea.h" class Protocol : public std::enable_shared_from_this { - public: - explicit Protocol(Connection_ptr connection) : connection(connection) {} - virtual ~Protocol() = default; +public: + explicit Protocol(Connection_ptr connection) : connection(connection), key(), encryptionEnabled(false), rawMessages(false) {} + virtual ~Protocol() = default; - // non-copyable - Protocol(const Protocol&) = delete; - Protocol& operator=(const Protocol&) = delete; + // non-copyable + Protocol(const Protocol&) = delete; + Protocol& operator=(const Protocol&) = delete; - virtual void parsePacket(NetworkMessage&) {} + 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() {} + 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(); + 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); } + } - Connection_ptr getConnection() const { - return connection.lock(); +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); + } - uint32_t getIP() const; + void XTEA_encrypt(OutputMessage& msg) const; + bool XTEA_decrypt(NetworkMessage& msg) const; + static bool RSA_decrypt(NetworkMessage& msg); - //Use this function for autosend messages only - OutputMessage_ptr getOutputBuffer(int32_t size); + void setRawMessages(bool value) { + rawMessages = value; + } - OutputMessage_ptr& getCurrentBuffer() { - return outputBuffer; - } + virtual void release() {} + friend class Connection; - 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(xtea::key key) { - this->key = std::move(key); - } - void disableChecksum() { - checksumEnabled = false; - } - - static bool RSA_decrypt(NetworkMessage& msg); - - void setRawMessages(bool value) { - rawMessages = value; - } - - virtual void release() {} - - private: - void XTEA_encrypt(OutputMessage& msg) const; - bool XTEA_decrypt(NetworkMessage& msg) const; - - friend class Connection; - - OutputMessage_ptr outputBuffer; - - const ConnectionWeak_ptr connection; - xtea::key key; - bool encryptionEnabled = false; - bool checksumEnabled = true; - bool rawMessages = false; + OutputMessage_ptr outputBuffer; +private: + const ConnectionWeak_ptr connection; + uint32_t key[4]; + bool encryptionEnabled; + bool rawMessages; }; #endif diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp index b979d99..faa9e5b 100644 --- a/src/protocolgame.cpp +++ b/src/protocolgame.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,7 +31,6 @@ #include "actions.h" #include "game.h" #include "iologindata.h" -#include "iomarket.h" #include "waitlist.h" #include "ban.h" #include "scheduler.h" @@ -108,9 +107,8 @@ void ProtocolGame::login(const std::string& name, uint32_t accountId, OperatingS } } - WaitingList& waitingList = WaitingList::getInstance(); - if (!waitingList.clientLogin(player)) { - uint32_t currentSlot = waitingList.getClientSlot(player); + if (!WaitingList::getInstance()->clientLogin(player)) { + uint32_t currentSlot = WaitingList::getInstance()->getClientSlot(player); uint32_t retryTime = WaitingList::getTime(currentSlot); std::ostringstream ss; @@ -126,7 +124,7 @@ void ProtocolGame::login(const std::string& name, uint32_t accountId, OperatingS return; } - if (!IOLoginData::loadPlayerById(player, player->getGUID())) { + if (!IOLoginData::loadPlayerByName(player, name)) { disconnectClient("Your character could not be loaded."); return; } @@ -186,7 +184,6 @@ void ProtocolGame::connect(uint32_t playerId, OperatingSystem_t operatingSystem) player->incrementReferenceCounter(); g_chat->removeUserFromAllChannels(*player); - player->clearModalWindows(); player->setOperatingSystem(operatingSystem); player->isConnecting = false; @@ -212,7 +209,7 @@ void ProtocolGame::logout(bool displayEffect, bool forced) return; } - if (!player->getTile()->hasFlag(TILESTATE_PROTECTIONZONE) && player->hasCondition(CONDITION_INFIGHT)) { + if (player->hasCondition(CONDITION_INFIGHT)) { player->sendCancelMessage(RETURNVALUE_YOUMAYNOTLOGOUTDURINGAFIGHT); return; } @@ -245,20 +242,18 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg) OperatingSystem_t operatingSystem = static_cast(msg.get()); version = msg.get(); - msg.skipBytes(7); // U32 client version, U8 client type, U16 dat revision - if (!Protocol::RSA_decrypt(msg)) { disconnect(); return; } - xtea::key key; + uint32_t key[4]; key[0] = msg.get(); key[1] = msg.get(); key[2] = msg.get(); key[3] = msg.get(); enableXTEAEncryption(); - setXTEAKey(std::move(key)); + setXTEAKey(key); if (operatingSystem >= CLIENTOS_OTCLIENT_LINUX) { NetworkMessage opcodeMessage; @@ -270,48 +265,15 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg) msg.skipBytes(1); // gamemaster flag - std::string sessionKey = msg.getString(); - - auto sessionArgs = explodeString(sessionKey, "\n", 4); - if (sessionArgs.size() != 4) { - disconnect(); - return; - } - - std::string& accountName = sessionArgs[0]; - std::string& password = sessionArgs[1]; - std::string& token = sessionArgs[2]; - uint32_t tokenTime = 0; - try { - tokenTime = std::stoul(sessionArgs[3]); - } catch (const std::invalid_argument&) { - disconnectClient("Malformed token packet."); - return; - } catch (const std::out_of_range&) { - disconnectClient("Token time is too long."); - return; - } - - if (accountName.empty()) { - disconnectClient("You must enter your account name."); - return; - } - + uint32_t accountNumber = msg.get(); std::string characterName = msg.getString(); + std::string password = msg.getString(); - uint32_t timeStamp = msg.get(); - uint8_t randNumber = msg.getByte(); - if (challengeTimestamp != timeStamp || challengeRandom != randNumber) { - disconnect(); + /*if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) { + //sendUpdateRequest(); + disconnectClient("Use Tibia 7.72 to login!"); return; - } - - if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) { - std::ostringstream ss; - ss << "Only clients with protocol " << CLIENT_VERSION_STR << " allowed!"; - disconnectClient(ss.str()); - return; - } + }*/ if (g_game.getGameState() == GAME_STATE_STARTUP) { disconnectClient("Gameworld is starting up. Please wait."); @@ -335,41 +297,29 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg) return; } - uint32_t accountId = IOLoginData::gameworldAuthentication(accountName, password, characterName, token, tokenTime); + uint32_t accountId = IOLoginData::gameworldAuthentication(accountNumber, password, characterName); if (accountId == 0) { - disconnectClient("Account name or password is not correct."); + disconnectClient("Account number or password is not correct."); return; } + Account account; + if (!IOLoginData::loginserverAuthentication(accountNumber, password, account)) { + disconnectClient("Account number or password is not correct."); + return; + } + + //Update premium days + Game::updatePremium(account); + g_dispatcher.addTask(createTask(std::bind(&ProtocolGame::login, getThis(), characterName, accountId, operatingSystem))); + } void ProtocolGame::onConnect() { - auto output = OutputMessagePool::getOutputMessage(); - static std::random_device rd; - static std::ranlux24 generator(rd()); - static std::uniform_int_distribution randNumber(0x00, 0xFF); - // Skip checksum - output->skipBytes(sizeof(uint32_t)); - // Packet length & type - output->add(0x0006); - output->addByte(0x1F); - - // Add timestamp & random number - challengeTimestamp = static_cast(time(nullptr)); - output->add(challengeTimestamp); - - challengeRandom = randNumber(generator); - output->addByte(challengeRandom); - - // Go back and write checksum - output->skipBytes(-12); - output->add(adlerChecksum(output->getOutputBuffer() + sizeof(uint32_t), 8)); - - send(output); } void ProtocolGame::disconnectClient(const std::string& message) const @@ -434,12 +384,7 @@ void ProtocolGame::parsePacket(NetworkMessage& msg) case 0x70: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_EAST); break; case 0x71: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_SOUTH); break; case 0x72: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerTurn, player->getID(), DIRECTION_WEST); break; - case 0x77: parseEquipObject(msg); break; case 0x78: parseThrow(msg); break; - case 0x79: parseLookInShop(msg); break; - case 0x7A: parsePlayerPurchase(msg); break; - case 0x7B: parsePlayerSale(msg); break; - case 0x7C: addGameTask(&Game::playerCloseShop, player->getID()); break; case 0x7D: parseRequestTrade(msg); break; case 0x7E: parseLookInTrade(msg); break; case 0x7F: addGameTask(&Game::playerAcceptTrade, player->getID()); break; @@ -454,13 +399,14 @@ void ProtocolGame::parsePacket(NetworkMessage& msg) case 0x8A: parseHouseWindow(msg); break; case 0x8C: parseLookAt(msg); break; case 0x8D: parseLookInBattleList(msg); break; - case 0x8E: /* join aggression */ break; case 0x96: parseSay(msg); break; case 0x97: addGameTask(&Game::playerRequestChannels, player->getID()); break; case 0x98: parseOpenChannel(msg); break; case 0x99: parseCloseChannel(msg); break; case 0x9A: parseOpenPrivateChannel(msg); break; - case 0x9E: addGameTask(&Game::playerCloseNpcChannel, player->getID()); break; + case 0x9B: parseProcessRuleViolationReport(msg); break; + case 0x9C: parseCloseRuleViolationReport(msg); break; + case 0x9D: addGameTask(&Game::playerCancelRuleViolationReport, player->getID()); break; case 0xA0: parseFightModes(msg); break; case 0xA1: parseAttack(msg); break; case 0xA2: parseFollow(msg); break; @@ -469,37 +415,22 @@ void ProtocolGame::parsePacket(NetworkMessage& msg) case 0xA5: parseRevokePartyInvite(msg); break; case 0xA6: parsePassPartyLeadership(msg); break; case 0xA7: addGameTask(&Game::playerLeaveParty, player->getID()); break; - case 0xA8: parseEnableSharedPartyExperience(msg); break; case 0xAA: addGameTask(&Game::playerCreatePrivateChannel, player->getID()); break; case 0xAB: parseChannelInvite(msg); break; case 0xAC: parseChannelExclude(msg); break; case 0xBE: addGameTask(&Game::playerCancelAttackAndFollow, player->getID()); break; case 0xC9: /* update tile */ break; case 0xCA: parseUpdateContainer(msg); break; - case 0xCB: parseBrowseField(msg); break; case 0xCC: parseSeekInContainer(msg); break; case 0xD2: addGameTask(&Game::playerRequestOutfit, player->getID()); break; case 0xD3: parseSetOutfit(msg); break; - case 0xD4: parseToggleMount(msg); break; case 0xDC: parseAddVip(msg); break; case 0xDD: parseRemoveVip(msg); break; - case 0xDE: parseEditVip(msg); break; case 0xE6: parseBugReport(msg); break; - case 0xE7: /* thank you */ break; + case 0xE7: /* violation window */ break; case 0xE8: parseDebugAssert(msg); break; - case 0xF0: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerShowQuestLog, player->getID()); break; - case 0xF1: parseQuestLine(msg); break; - case 0xF2: parseRuleViolationReport(msg); break; - case 0xF3: /* get object info */ break; - case 0xF4: parseMarketLeave(); break; - case 0xF5: parseMarketBrowse(msg); break; - case 0xF6: parseMarketCreateOffer(msg); break; - case 0xF7: parseMarketCancelOffer(msg); break; - case 0xF8: parseMarketAcceptOffer(msg); break; - case 0xF9: parseModalWindowAnswer(msg); break; - default: - // std::cout << "Player: " << player->getName() << " sent an unknown packet header: 0x" << std::hex << static_cast(recvbyte) << std::dec << "!" << std::endl; + std::cout << "Player: " << player->getName() << " sent an unknown packet header: 0x" << std::hex << static_cast(recvbyte) << std::dec << "!" << std::endl; break; } @@ -510,14 +441,13 @@ void ProtocolGame::parsePacket(NetworkMessage& msg) void ProtocolGame::GetTileDescription(const Tile* tile, NetworkMessage& msg) { - msg.add(0x00); //environmental effects - int32_t count; Item* ground = tile->getGround(); if (ground) { msg.addItem(ground); count = 1; - } else { + } + else { count = 0; } @@ -529,7 +459,8 @@ void ProtocolGame::GetTileDescription(const Tile* tile, NetworkMessage& msg) count++; if (count == 9 && tile->getPosition() == player->getPosition()) { break; - } else if (count == 10) { + } + else if (count == 10) { return; } } @@ -632,7 +563,7 @@ void ProtocolGame::checkCreatureAsKnown(uint32_t id, bool& known, uint32_t& remo known = false; - if (knownCreatureSet.size() > 1300) { + if (knownCreatureSet.size() > 150) { // Look for a creature to remove for (auto it = knownCreatureSet.begin(), end = knownCreatureSet.end(); it != end; ++it) { Creature* creature = g_game.getCreatureByID(*it); @@ -738,7 +669,7 @@ void ProtocolGame::parseOpenPrivateChannel(NetworkMessage& msg) void ProtocolGame::parseAutoWalk(NetworkMessage& msg) { uint8_t numdirs = msg.getByte(); - if (numdirs == 0 || (msg.getBufferPosition() + numdirs) != (msg.getLength() + 8)) { + if (numdirs == 0 || (msg.getBufferPosition() + numdirs) != (msg.getLength() + 4)) { return; } @@ -776,16 +707,9 @@ void ProtocolGame::parseSetOutfit(NetworkMessage& msg) newOutfit.lookLegs = msg.getByte(); newOutfit.lookFeet = msg.getByte(); newOutfit.lookAddons = msg.getByte(); - newOutfit.lookMount = msg.get(); addGameTask(&Game::playerChangeOutfit, player->getID(), newOutfit); } -void ProtocolGame::parseToggleMount(NetworkMessage& msg) -{ - bool mount = msg.getByte() != 0; - addGameTask(&Game::playerToggleMount, player->getID(), mount); -} - void ProtocolGame::parseUseItem(NetworkMessage& msg) { Position pos = msg.getPosition(); @@ -867,14 +791,16 @@ void ProtocolGame::parseSay(NetworkMessage& msg) SpeakClasses type = static_cast(msg.getByte()); switch (type) { - case TALKTYPE_PRIVATE_TO: - case TALKTYPE_PRIVATE_RED_TO: + case TALKTYPE_PRIVATE: + case TALKTYPE_PRIVATE_RED: + case TALKTYPE_RVR_ANSWER: receiver = msg.getString(); channelId = 0; break; case TALKTYPE_CHANNEL_Y: case TALKTYPE_CHANNEL_R1: + case TALKTYPE_CHANNEL_R2: channelId = msg.get(); break; @@ -884,7 +810,7 @@ void ProtocolGame::parseSay(NetworkMessage& msg) } const std::string text = msg.getString(); - if (text.length() > 255) { + if (text.length() > 255 || text.find('\n') != std::string::npos) { return; } @@ -898,6 +824,13 @@ void ProtocolGame::parseFightModes(NetworkMessage& msg) uint8_t rawSecureMode = msg.getByte(); // 0 - can't attack unmarked, 1 - can attack unmarked // uint8_t rawPvpMode = msg.getByte(); // pvp mode introduced in 10.0 + chaseMode_t chaseMode; + if (rawChaseMode == 1) { + chaseMode = CHASEMODE_FOLLOW; + } else { + chaseMode = CHASEMODE_STANDSTILL; + } + fightMode_t fightMode; if (rawFightMode == 1) { fightMode = FIGHTMODE_ATTACK; @@ -907,29 +840,31 @@ void ProtocolGame::parseFightModes(NetworkMessage& msg) fightMode = FIGHTMODE_DEFENSE; } - addGameTask(&Game::playerSetFightModes, player->getID(), fightMode, rawChaseMode != 0, rawSecureMode != 0); + addGameTask(&Game::playerSetFightModes, player->getID(), fightMode, chaseMode, rawSecureMode != 0); } void ProtocolGame::parseAttack(NetworkMessage& msg) { uint32_t creatureId = msg.get(); - // msg.get(); creatureId (same as above) addGameTask(&Game::playerSetAttackedCreature, player->getID(), creatureId); } void ProtocolGame::parseFollow(NetworkMessage& msg) { uint32_t creatureId = msg.get(); - // msg.get(); creatureId (same as above) addGameTask(&Game::playerFollowCreature, player->getID(), creatureId); } -void ProtocolGame::parseEquipObject(NetworkMessage& msg) +void ProtocolGame::parseProcessRuleViolationReport(NetworkMessage& msg) { - uint16_t spriteId = msg.get(); - // msg.get(); + const std::string reporter = msg.getString(); + addGameTask(&Game::playerProcessRuleViolationReport, player->getID(), reporter); +} - addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerEquipItem, player->getID(), spriteId); +void ProtocolGame::parseCloseRuleViolationReport(NetworkMessage& msg) +{ + const std::string reporter = msg.getString(); + addGameTask(&Game::playerCloseRuleViolationReport, player->getID(), reporter); } void ProtocolGame::parseTextWindow(NetworkMessage& msg) @@ -947,32 +882,6 @@ void ProtocolGame::parseHouseWindow(NetworkMessage& msg) addGameTask(&Game::playerUpdateHouseWindow, player->getID(), doorId, id, text); } -void ProtocolGame::parseLookInShop(NetworkMessage& msg) -{ - uint16_t id = msg.get(); - uint8_t count = msg.getByte(); - addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerLookInShop, player->getID(), id, count); -} - -void ProtocolGame::parsePlayerPurchase(NetworkMessage& msg) -{ - uint16_t id = msg.get(); - uint8_t count = msg.getByte(); - uint8_t amount = msg.getByte(); - bool ignoreCap = msg.getByte() != 0; - bool inBackpacks = msg.getByte() != 0; - addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerPurchaseItem, player->getID(), id, count, amount, ignoreCap, inBackpacks); -} - -void ProtocolGame::parsePlayerSale(NetworkMessage& msg) -{ - uint16_t id = msg.get(); - uint8_t count = msg.getByte(); - uint8_t amount = msg.getByte(); - bool ignoreEquipped = msg.getByte() != 0; - addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerSellItem, player->getID(), id, count, amount, ignoreEquipped); -} - void ProtocolGame::parseRequestTrade(NetworkMessage& msg) { Position pos = msg.getPosition(); @@ -1001,15 +910,6 @@ void ProtocolGame::parseRemoveVip(NetworkMessage& msg) addGameTask(&Game::playerRequestRemoveVip, player->getID(), guid); } -void ProtocolGame::parseEditVip(NetworkMessage& msg) -{ - uint32_t guid = msg.get(); - const std::string description = msg.getString(); - uint32_t icon = std::min(10, msg.get()); // 10 is max icon in 9.63 - bool notify = msg.getByte() != 0; - addGameTask(&Game::playerRequestEditVip, player->getID(), guid, description, icon, notify); -} - void ProtocolGame::parseRotateItem(NetworkMessage& msg) { Position pos = msg.getPosition(); @@ -1018,34 +918,10 @@ void ProtocolGame::parseRotateItem(NetworkMessage& msg) addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerRotateItem, player->getID(), pos, stackpos, spriteId); } -void ProtocolGame::parseRuleViolationReport(NetworkMessage& msg) -{ - uint8_t reportType = msg.getByte(); - uint8_t reportReason = msg.getByte(); - const std::string& targetName = msg.getString(); - const std::string& comment = msg.getString(); - std::string translation; - if (reportType == REPORT_TYPE_NAME) { - translation = msg.getString(); - } else if (reportType == REPORT_TYPE_STATEMENT) { - translation = msg.getString(); - msg.get(); // statement id, used to get whatever player have said, we don't log that. - } - - addGameTask(&Game::playerReportRuleViolation, player->getID(), targetName, reportType, reportReason, comment, translation); -} - void ProtocolGame::parseBugReport(NetworkMessage& msg) { - uint8_t category = msg.getByte(); - std::string message = msg.getString(); - - Position position; - if (category == BUG_CATEGORY_MAP) { - position = msg.getPosition(); - } - - addGameTask(&Game::playerReportBug, player->getID(), message, position, category); + std::string bug = msg.getString(); + addGameTask(&Game::playerReportBug, player->getID(), bug); } void ProtocolGame::parseDebugAssert(NetworkMessage& msg) @@ -1087,75 +963,6 @@ void ProtocolGame::parsePassPartyLeadership(NetworkMessage& msg) addGameTask(&Game::playerPassPartyLeadership, player->getID(), targetId); } -void ProtocolGame::parseEnableSharedPartyExperience(NetworkMessage& msg) -{ - bool sharedExpActive = msg.getByte() == 1; - addGameTask(&Game::playerEnableSharedPartyExperience, player->getID(), sharedExpActive); -} - -void ProtocolGame::parseQuestLine(NetworkMessage& msg) -{ - uint16_t questId = msg.get(); - addGameTask(&Game::playerShowQuestLine, player->getID(), questId); -} - -void ProtocolGame::parseMarketLeave() -{ - addGameTask(&Game::playerLeaveMarket, player->getID()); -} - -void ProtocolGame::parseMarketBrowse(NetworkMessage& msg) -{ - uint16_t browseId = msg.get(); - - if (browseId == MARKETREQUEST_OWN_OFFERS) { - addGameTask(&Game::playerBrowseMarketOwnOffers, player->getID()); - } else if (browseId == MARKETREQUEST_OWN_HISTORY) { - addGameTask(&Game::playerBrowseMarketOwnHistory, player->getID()); - } else { - addGameTask(&Game::playerBrowseMarket, player->getID(), browseId); - } -} - -void ProtocolGame::parseMarketCreateOffer(NetworkMessage& msg) -{ - uint8_t type = msg.getByte(); - uint16_t spriteId = msg.get(); - uint16_t amount = msg.get(); - uint32_t price = msg.get(); - bool anonymous = (msg.getByte() != 0); - addGameTask(&Game::playerCreateMarketOffer, player->getID(), type, spriteId, amount, price, anonymous); -} - -void ProtocolGame::parseMarketCancelOffer(NetworkMessage& msg) -{ - uint32_t timestamp = msg.get(); - uint16_t counter = msg.get(); - addGameTask(&Game::playerCancelMarketOffer, player->getID(), timestamp, counter); -} - -void ProtocolGame::parseMarketAcceptOffer(NetworkMessage& msg) -{ - uint32_t timestamp = msg.get(); - uint16_t counter = msg.get(); - uint16_t amount = msg.get(); - addGameTask(&Game::playerAcceptMarketOffer, player->getID(), timestamp, counter, amount); -} - -void ProtocolGame::parseModalWindowAnswer(NetworkMessage& msg) -{ - uint32_t id = msg.get(); - uint8_t button = msg.getByte(); - uint8_t choice = msg.getByte(); - addGameTask(&Game::playerAnswerModalWindow, player->getID(), id, button, choice); -} - -void ProtocolGame::parseBrowseField(NetworkMessage& msg) -{ - const Position& pos = msg.getPosition(); - addGameTask(&Game::playerBrowseField, player->getID(), pos); -} - void ProtocolGame::parseSeekInContainer(NetworkMessage& msg) { uint8_t containerId = msg.getByte(); @@ -1172,16 +979,6 @@ void ProtocolGame::sendOpenPrivateChannel(const std::string& receiver) writeToOutputBuffer(msg); } -void ProtocolGame::sendChannelEvent(uint16_t channelId, const std::string& playerName, ChannelEvent_t channelEvent) -{ - NetworkMessage msg; - msg.addByte(0xF3); - msg.add(channelId); - msg.addString(playerName); - msg.addByte(channelEvent); - writeToOutputBuffer(msg); -} - void ProtocolGame::sendCreatureOutfit(const Creature* creature, const Outfit_t& outfit) { if (!canSee(creature)) { @@ -1206,26 +1003,13 @@ void ProtocolGame::sendCreatureLight(const Creature* creature) writeToOutputBuffer(msg); } -void ProtocolGame::sendWorldLight(LightInfo lightInfo) +void ProtocolGame::sendWorldLight(const LightInfo& lightInfo) { NetworkMessage msg; AddWorldLight(msg, lightInfo); writeToOutputBuffer(msg); } -void ProtocolGame::sendCreatureWalkthrough(const Creature* creature, bool walkthrough) -{ - if (!canSee(creature)) { - return; - } - - NetworkMessage msg; - msg.addByte(0x92); - msg.add(creature->getID()); - msg.addByte(walkthrough ? 0x00 : 0x01); - writeToOutputBuffer(msg); -} - void ProtocolGame::sendCreatureShield(const Creature* creature) { if (!canSee(creature)) { @@ -1256,24 +1040,6 @@ void ProtocolGame::sendCreatureSkull(const Creature* creature) writeToOutputBuffer(msg); } -void ProtocolGame::sendCreatureType(uint32_t creatureId, uint8_t creatureType) -{ - NetworkMessage msg; - msg.addByte(0x95); - msg.add(creatureId); - msg.addByte(creatureType); - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendCreatureHelpers(uint32_t creatureId, uint16_t helpers) -{ - NetworkMessage msg; - msg.addByte(0x94); - msg.add(creatureId); - msg.add(helpers); - writeToOutputBuffer(msg); -} - void ProtocolGame::sendCreatureSquare(const Creature* creature, SquareColor_t color) { if (!canSee(creature)) { @@ -1281,37 +1047,55 @@ void ProtocolGame::sendCreatureSquare(const Creature* creature, SquareColor_t co } NetworkMessage msg; - msg.addByte(0x93); + msg.addByte(0x86); msg.add(creature->getID()); - msg.addByte(0x01); msg.addByte(color); writeToOutputBuffer(msg); } -void ProtocolGame::sendTutorial(uint8_t tutorialId) +void ProtocolGame::sendRemoveRuleViolationReport(const std::string& name) { NetworkMessage msg; - msg.addByte(0xDC); - msg.addByte(tutorialId); + msg.addByte(0xAF); + msg.addString(name); writeToOutputBuffer(msg); } -void ProtocolGame::sendAddMarker(const Position& pos, uint8_t markType, const std::string& desc) +void ProtocolGame::sendLockRuleViolation() { NetworkMessage msg; - msg.addByte(0xDD); - msg.addPosition(pos); - msg.addByte(markType); - msg.addString(desc); + msg.addByte(0xB1); writeToOutputBuffer(msg); } -void ProtocolGame::sendReLoginWindow(uint8_t unfairFightReduction) +void ProtocolGame::sendRuleViolationCancel(const std::string& name) { NetworkMessage msg; - msg.addByte(0x28); - msg.addByte(0x00); - msg.addByte(unfairFightReduction); + msg.addByte(0xB0); + msg.addString(name); + writeToOutputBuffer(msg); +} + +void ProtocolGame::sendRuleViolationsChannel(uint16_t channelId) +{ + NetworkMessage msg; + msg.addByte(0xAE); + msg.add(channelId); + auto it = g_game.getRuleViolationReports().begin(); + for (; it != g_game.getRuleViolationReports().end(); ++it) { + const RuleViolation& rvr = it->second; + if (rvr.pending) { + Player* reporter = g_game.getPlayerByID(rvr.reporterId); + if (reporter) { + msg.addByte(0xAA); + msg.add(0); + msg.addString(reporter->getName()); + msg.addByte(TALKTYPE_RVR_CHANNEL); + msg.add(0); + msg.addString(rvr.text); + } + } + } writeToOutputBuffer(msg); } @@ -1322,63 +1106,25 @@ void ProtocolGame::sendStats() writeToOutputBuffer(msg); } -void ProtocolGame::sendBasicData() -{ - NetworkMessage msg; - msg.addByte(0x9F); - if (player->isPremium()) { - msg.addByte(1); - msg.add(time(nullptr) + (player->premiumDays * 86400)); - } else { - msg.addByte(0); - msg.add(0); - } - msg.addByte(player->getVocation()->getClientId()); - msg.add(0xFF); // number of known spells - for (uint8_t spellId = 0x00; spellId < 0xFF; spellId++) { - msg.addByte(spellId); - } - writeToOutputBuffer(msg); -} - void ProtocolGame::sendTextMessage(const TextMessage& message) { NetworkMessage msg; msg.addByte(0xB4); msg.addByte(message.type); - switch (message.type) { - case MESSAGE_DAMAGE_DEALT: - case MESSAGE_DAMAGE_RECEIVED: - case MESSAGE_DAMAGE_OTHERS: { - msg.addPosition(message.position); - msg.add(message.primary.value); - msg.addByte(message.primary.color); - msg.add(message.secondary.value); - msg.addByte(message.secondary.color); - break; - } - case MESSAGE_HEALED: - case MESSAGE_HEALED_OTHERS: - case MESSAGE_EXPERIENCE: - case MESSAGE_EXPERIENCE_OTHERS: { - msg.addPosition(message.position); - msg.add(message.primary.value); - msg.addByte(message.primary.color); - break; - } - case MESSAGE_GUILD: - case MESSAGE_PARTY_MANAGEMENT: - case MESSAGE_PARTY: - msg.add(message.channelId); - break; - default: { - break; - } - } msg.addString(message.text); writeToOutputBuffer(msg); } +void ProtocolGame::sendAnimatedText(const Position& pos, uint8_t color, const std::string& text) +{ + NetworkMessage msg; + msg.addByte(0x84); + msg.addPosition(pos); + msg.addByte(color); + msg.addString(text); + writeToOutputBuffer(msg); +} + void ProtocolGame::sendClosePrivate(uint16_t channelId) { NetworkMessage msg; @@ -1393,9 +1139,6 @@ void ProtocolGame::sendCreatePrivateChannel(uint16_t channelId, const std::strin msg.addByte(0xB2); msg.add(channelId); msg.addString(channelName); - msg.add(0x01); - msg.addString(player->getName()); - msg.add(0x00); writeToOutputBuffer(msg); } @@ -1414,7 +1157,7 @@ void ProtocolGame::sendChannelsDialog() writeToOutputBuffer(msg); } -void ProtocolGame::sendChannel(uint16_t channelId, const std::string& channelName, const UsersMap* channelUsers, const InvitedMap* invitedUsers) +void ProtocolGame::sendChannel(uint16_t channelId, const std::string& channelName) { NetworkMessage msg; msg.addByte(0xAC); @@ -1422,38 +1165,9 @@ void ProtocolGame::sendChannel(uint16_t channelId, const std::string& channelNam msg.add(channelId); msg.addString(channelName); - if (channelUsers) { - msg.add(channelUsers->size()); - for (const auto& it : *channelUsers) { - msg.addString(it.second->getName()); - } - } else { - msg.add(0x00); - } - - if (invitedUsers) { - msg.add(invitedUsers->size()); - for (const auto& it : *invitedUsers) { - msg.addString(it.second->getName()); - } - } else { - msg.add(0x00); - } writeToOutputBuffer(msg); } -void ProtocolGame::sendChannelMessage(const std::string& author, const std::string& text, SpeakClasses type, uint16_t channel) -{ - NetworkMessage msg; - msg.addByte(0xAA); - msg.add(0x00); - msg.addString(author); - msg.add(0x00); - msg.addByte(type); - msg.add(channel); - msg.addString(text); - writeToOutputBuffer(msg); -} void ProtocolGame::sendIcons(uint16_t icons) { @@ -1470,24 +1184,14 @@ void ProtocolGame::sendContainer(uint8_t cid, const Container* container, bool h msg.addByte(cid); - if (container->getID() == ITEM_BROWSEFIELD) { - msg.addItem(1987, 1); - msg.addString("Browse Field"); - } else { - msg.addItem(container); - msg.addString(container->getName()); - } + msg.addItem(container); + msg.addString(container->getName()); msg.addByte(container->capacity()); msg.addByte(hasParent ? 0x01 : 0x00); - msg.addByte(container->isUnlocked() ? 0x01 : 0x00); // Drag and drop - msg.addByte(container->hasPagination() ? 0x01 : 0x00); // Pagination - uint32_t containerSize = container->size(); - msg.add(containerSize); - msg.add(firstIndex); if (firstIndex < containerSize) { uint8_t itemsToSend = std::min(std::min(container->capacity(), containerSize - firstIndex), std::numeric_limits::max()); @@ -1501,562 +1205,7 @@ void ProtocolGame::sendContainer(uint8_t cid, const Container* container, bool h writeToOutputBuffer(msg); } -void ProtocolGame::sendShop(Npc* npc, const ShopInfoList& itemList) -{ - NetworkMessage msg; - msg.addByte(0x7A); - msg.addString(npc->getName()); - uint16_t itemsToSend = std::min(itemList.size(), std::numeric_limits::max()); - msg.add(itemsToSend); - - uint16_t i = 0; - for (auto it = itemList.begin(); i < itemsToSend; ++it, ++i) { - AddShopItem(msg, *it); - } - - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendCloseShop() -{ - NetworkMessage msg; - msg.addByte(0x7C); - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendSaleItemList(const std::list& shop) -{ - NetworkMessage msg; - msg.addByte(0x7B); - msg.add(player->getMoney() + player->getBankBalance()); - - std::map saleMap; - - if (shop.size() <= 5) { - // For very small shops it's not worth it to create the complete map - for (const ShopInfo& shopInfo : shop) { - if (shopInfo.sellPrice == 0) { - continue; - } - - int8_t subtype = -1; - - const ItemType& itemType = Item::items[shopInfo.itemId]; - if (itemType.hasSubType() && !itemType.stackable) { - subtype = (shopInfo.subType == 0 ? -1 : shopInfo.subType); - } - - uint32_t count = player->getItemTypeCount(shopInfo.itemId, subtype); - if (count > 0) { - saleMap[shopInfo.itemId] = count; - } - } - } else { - // Large shop, it's better to get a cached map of all item counts and use it - // We need a temporary map since the finished map should only contain items - // available in the shop - std::map tempSaleMap; - player->getAllItemTypeCount(tempSaleMap); - - // We must still check manually for the special items that require subtype matches - // (That is, fluids such as potions etc., actually these items are very few since - // health potions now use their own ID) - for (const ShopInfo& shopInfo : shop) { - if (shopInfo.sellPrice == 0) { - continue; - } - - int8_t subtype = -1; - - const ItemType& itemType = Item::items[shopInfo.itemId]; - if (itemType.hasSubType() && !itemType.stackable) { - subtype = (shopInfo.subType == 0 ? -1 : shopInfo.subType); - } - - if (subtype != -1) { - uint32_t count; - if (!itemType.isFluidContainer() && !itemType.isSplash()) { - count = player->getItemTypeCount(shopInfo.itemId, subtype); // This shop item requires extra checks - } else { - count = subtype; - } - - if (count > 0) { - saleMap[shopInfo.itemId] = count; - } - } else { - std::map::const_iterator findIt = tempSaleMap.find(shopInfo.itemId); - if (findIt != tempSaleMap.end() && findIt->second > 0) { - saleMap[shopInfo.itemId] = findIt->second; - } - } - } - } - - uint8_t itemsToSend = std::min(saleMap.size(), std::numeric_limits::max()); - msg.addByte(itemsToSend); - - uint8_t i = 0; - for (std::map::const_iterator it = saleMap.begin(); i < itemsToSend; ++it, ++i) { - msg.addItemId(it->first); - msg.addByte(std::min(it->second, std::numeric_limits::max())); - } - - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendMarketEnter(uint32_t depotId) -{ - NetworkMessage msg; - msg.addByte(0xF6); - - msg.add(player->getBankBalance()); - msg.addByte(std::min(IOMarket::getPlayerOfferCount(player->getGUID()), std::numeric_limits::max())); - - DepotChest* depotChest = player->getDepotChest(depotId, false); - if (!depotChest) { - msg.add(0x00); - writeToOutputBuffer(msg); - return; - } - - player->setInMarket(true); - - std::map depotItems; - std::forward_list containerList { depotChest, player->getInbox() }; - - do { - Container* container = containerList.front(); - containerList.pop_front(); - - for (Item* item : container->getItemList()) { - Container* c = item->getContainer(); - if (c && !c->empty()) { - containerList.push_front(c); - continue; - } - - const ItemType& itemType = Item::items[item->getID()]; - if (itemType.wareId == 0) { - continue; - } - - if (c && (!itemType.isContainer() || c->capacity() != itemType.maxItems)) { - continue; - } - - if (!item->hasMarketAttributes()) { - continue; - } - - depotItems[itemType.wareId] += Item::countByType(item, -1); - } - } while (!containerList.empty()); - - uint16_t itemsToSend = std::min(depotItems.size(), std::numeric_limits::max()); - msg.add(itemsToSend); - - uint16_t i = 0; - for (std::map::const_iterator it = depotItems.begin(); i < itemsToSend; ++it, ++i) { - msg.add(it->first); - msg.add(std::min(0xFFFF, it->second)); - } - - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendMarketLeave() -{ - NetworkMessage msg; - msg.addByte(0xF7); - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& buyOffers, const MarketOfferList& sellOffers) -{ - NetworkMessage msg; - - msg.addByte(0xF9); - msg.addItemId(itemId); - - msg.add(buyOffers.size()); - for (const MarketOffer& offer : buyOffers) { - msg.add(offer.timestamp); - msg.add(offer.counter); - msg.add(offer.amount); - msg.add(offer.price); - msg.addString(offer.playerName); - } - - msg.add(sellOffers.size()); - for (const MarketOffer& offer : sellOffers) { - msg.add(offer.timestamp); - msg.add(offer.counter); - msg.add(offer.amount); - msg.add(offer.price); - msg.addString(offer.playerName); - } - - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendMarketAcceptOffer(const MarketOfferEx& offer) -{ - NetworkMessage msg; - msg.addByte(0xF9); - msg.addItemId(offer.itemId); - - if (offer.type == MARKETACTION_BUY) { - msg.add(0x01); - msg.add(offer.timestamp); - msg.add(offer.counter); - msg.add(offer.amount); - msg.add(offer.price); - msg.addString(offer.playerName); - msg.add(0x00); - } else { - msg.add(0x00); - msg.add(0x01); - msg.add(offer.timestamp); - msg.add(offer.counter); - msg.add(offer.amount); - msg.add(offer.price); - msg.addString(offer.playerName); - } - - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendMarketBrowseOwnOffers(const MarketOfferList& buyOffers, const MarketOfferList& sellOffers) -{ - NetworkMessage msg; - msg.addByte(0xF9); - msg.add(MARKETREQUEST_OWN_OFFERS); - - msg.add(buyOffers.size()); - for (const MarketOffer& offer : buyOffers) { - msg.add(offer.timestamp); - msg.add(offer.counter); - msg.addItemId(offer.itemId); - msg.add(offer.amount); - msg.add(offer.price); - } - - msg.add(sellOffers.size()); - for (const MarketOffer& offer : sellOffers) { - msg.add(offer.timestamp); - msg.add(offer.counter); - msg.addItemId(offer.itemId); - msg.add(offer.amount); - msg.add(offer.price); - } - - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendMarketCancelOffer(const MarketOfferEx& offer) -{ - NetworkMessage msg; - msg.addByte(0xF9); - msg.add(MARKETREQUEST_OWN_OFFERS); - - if (offer.type == MARKETACTION_BUY) { - msg.add(0x01); - msg.add(offer.timestamp); - msg.add(offer.counter); - msg.addItemId(offer.itemId); - msg.add(offer.amount); - msg.add(offer.price); - msg.add(0x00); - } else { - msg.add(0x00); - msg.add(0x01); - msg.add(offer.timestamp); - msg.add(offer.counter); - msg.addItemId(offer.itemId); - msg.add(offer.amount); - msg.add(offer.price); - } - - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendMarketBrowseOwnHistory(const HistoryMarketOfferList& buyOffers, const HistoryMarketOfferList& sellOffers) -{ - uint32_t i = 0; - std::map counterMap; - uint32_t buyOffersToSend = std::min(buyOffers.size(), 810 + std::max(0, 810 - sellOffers.size())); - uint32_t sellOffersToSend = std::min(sellOffers.size(), 810 + std::max(0, 810 - buyOffers.size())); - - NetworkMessage msg; - msg.addByte(0xF9); - msg.add(MARKETREQUEST_OWN_HISTORY); - - msg.add(buyOffersToSend); - for (auto it = buyOffers.begin(); i < buyOffersToSend; ++it, ++i) { - msg.add(it->timestamp); - msg.add(counterMap[it->timestamp]++); - msg.addItemId(it->itemId); - msg.add(it->amount); - msg.add(it->price); - msg.addByte(it->state); - } - - counterMap.clear(); - i = 0; - - msg.add(sellOffersToSend); - for (auto it = sellOffers.begin(); i < sellOffersToSend; ++it, ++i) { - msg.add(it->timestamp); - msg.add(counterMap[it->timestamp]++); - msg.addItemId(it->itemId); - msg.add(it->amount); - msg.add(it->price); - msg.addByte(it->state); - } - - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendMarketDetail(uint16_t itemId) -{ - NetworkMessage msg; - msg.addByte(0xF8); - msg.addItemId(itemId); - - const ItemType& it = Item::items[itemId]; - if (it.armor != 0) { - msg.addString(std::to_string(it.armor)); - } else { - msg.add(0x00); - } - - if (it.attack != 0) { - // TODO: chance to hit, range - // example: - // "attack +x, chance to hit +y%, z fields" - if (it.abilities && it.abilities->elementType != COMBAT_NONE && it.abilities->elementDamage != 0) { - std::ostringstream ss; - ss << it.attack << " physical +" << it.abilities->elementDamage << ' ' << getCombatName(it.abilities->elementType); - msg.addString(ss.str()); - } else { - msg.addString(std::to_string(it.attack)); - } - } else { - msg.add(0x00); - } - - if (it.isContainer()) { - msg.addString(std::to_string(it.maxItems)); - } else { - msg.add(0x00); - } - - if (it.defense != 0) { - if (it.extraDefense != 0) { - std::ostringstream ss; - ss << it.defense << ' ' << std::showpos << it.extraDefense << std::noshowpos; - msg.addString(ss.str()); - } else { - msg.addString(std::to_string(it.defense)); - } - } else { - msg.add(0x00); - } - - if (!it.description.empty()) { - const std::string& descr = it.description; - if (descr.back() == '.') { - msg.addString(std::string(descr, 0, descr.length() - 1)); - } else { - msg.addString(descr); - } - } else { - msg.add(0x00); - } - - if (it.decayTime != 0) { - std::ostringstream ss; - ss << it.decayTime << " seconds"; - msg.addString(ss.str()); - } else { - msg.add(0x00); - } - - if (it.abilities) { - std::ostringstream ss; - bool separator = false; - - for (size_t i = 0; i < COMBAT_COUNT; ++i) { - if (it.abilities->absorbPercent[i] == 0) { - continue; - } - - if (separator) { - ss << ", "; - } else { - separator = true; - } - - ss << getCombatName(indexToCombatType(i)) << ' ' << std::showpos << it.abilities->absorbPercent[i] << std::noshowpos << '%'; - } - - msg.addString(ss.str()); - } else { - msg.add(0x00); - } - - if (it.minReqLevel != 0) { - msg.addString(std::to_string(it.minReqLevel)); - } else { - msg.add(0x00); - } - - if (it.minReqMagicLevel != 0) { - msg.addString(std::to_string(it.minReqMagicLevel)); - } else { - msg.add(0x00); - } - - msg.addString(it.vocationString); - - msg.addString(it.runeSpellName); - - if (it.abilities) { - std::ostringstream ss; - bool separator = false; - - for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; i++) { - if (!it.abilities->skills[i]) { - continue; - } - - if (separator) { - ss << ", "; - } else { - separator = true; - } - - ss << getSkillName(i) << ' ' << std::showpos << it.abilities->skills[i] << std::noshowpos; - } - - if (it.abilities->stats[STAT_MAGICPOINTS] != 0) { - if (separator) { - ss << ", "; - } else { - separator = true; - } - - ss << "magic level " << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos; - } - - if (it.abilities->speed != 0) { - if (separator) { - ss << ", "; - } - - ss << "speed " << std::showpos << (it.abilities->speed >> 1) << std::noshowpos; - } - - msg.addString(ss.str()); - } else { - msg.add(0x00); - } - - if (it.charges != 0) { - msg.addString(std::to_string(it.charges)); - } else { - msg.add(0x00); - } - - std::string weaponName = getWeaponName(it.weaponType); - - if (it.slotPosition & SLOTP_TWO_HAND) { - if (!weaponName.empty()) { - weaponName += ", two-handed"; - } else { - weaponName = "two-handed"; - } - } - - msg.addString(weaponName); - - if (it.weight != 0) { - std::ostringstream ss; - if (it.weight < 10) { - ss << "0.0" << it.weight; - } else if (it.weight < 100) { - ss << "0." << it.weight; - } else { - std::string weightString = std::to_string(it.weight); - weightString.insert(weightString.end() - 2, '.'); - ss << weightString; - } - ss << " oz"; - msg.addString(ss.str()); - } else { - msg.add(0x00); - } - - MarketStatistics* statistics = IOMarket::getInstance().getPurchaseStatistics(itemId); - if (statistics) { - msg.addByte(0x01); - msg.add(statistics->numTransactions); - msg.add(std::min(std::numeric_limits::max(), statistics->totalPrice)); - msg.add(statistics->highestPrice); - msg.add(statistics->lowestPrice); - } else { - msg.addByte(0x00); - } - - statistics = IOMarket::getInstance().getSaleStatistics(itemId); - if (statistics) { - msg.addByte(0x01); - msg.add(statistics->numTransactions); - msg.add(std::min(std::numeric_limits::max(), statistics->totalPrice)); - msg.add(statistics->highestPrice); - msg.add(statistics->lowestPrice); - } else { - msg.addByte(0x00); - } - - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendQuestLog() -{ - NetworkMessage msg; - msg.addByte(0xF0); - msg.add(g_game.quests.getQuestsCount(player)); - - for (const Quest& quest : g_game.quests.getQuests()) { - if (quest.isStarted(player)) { - msg.add(quest.getID()); - msg.addString(quest.getName()); - msg.addByte(quest.isCompleted(player)); - } - } - - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendQuestLine(const Quest* quest) -{ - NetworkMessage msg; - msg.addByte(0xF1); - msg.add(quest->getID()); - msg.addByte(quest->getMissionsCount(player)); - - for (const Mission& mission : quest->getMissions()) { - if (mission.isStarted(player)) { - msg.addString(mission.getName(player)); - msg.addString(mission.getDescription(player)); - } - } - - writeToOutputBuffer(msg); -} void ProtocolGame::sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack) { @@ -2125,7 +1274,6 @@ void ProtocolGame::sendCreatureTurn(const Creature* creature, uint32_t stackPos) msg.add(0x63); msg.add(creature->getID()); msg.addByte(creature->getDirection()); - msg.addByte(player->canWalkthroughEx(creature) ? 0x00 : 0x01); writeToOutputBuffer(msg); } @@ -2142,14 +1290,16 @@ void ProtocolGame::sendCreatureSay(const Creature* creature, SpeakClasses type, //Add level only for players if (const Player* speaker = creature->getPlayer()) { msg.add(speaker->getLevel()); - } else { + } + else { msg.add(0x00); } msg.addByte(type); if (pos) { msg.addPosition(*pos); - } else { + } + else { msg.addPosition(creature->getPosition()); } @@ -2166,15 +1316,18 @@ void ProtocolGame::sendToChannel(const Creature* creature, SpeakClasses type, co msg.add(++statementId); if (!creature) { msg.add(0x00); - } else if (type == TALKTYPE_CHANNEL_R2) { + } + else if (type == TALKTYPE_CHANNEL_R2) { msg.add(0x00); type = TALKTYPE_CHANNEL_R1; - } else { + } + else { msg.addString(creature->getName()); //Add level only for players if (const Player* speaker = creature->getPlayer()) { msg.add(speaker->getLevel()); - } else { + } + else { msg.add(0x00); } } @@ -2194,7 +1347,8 @@ void ProtocolGame::sendPrivateMessage(const Player* speaker, SpeakClasses type, if (speaker) { msg.addString(speaker->getName()); msg.add(speaker->getLevel()); - } else { + } + else { msg.add(0x00); } msg.addByte(type); @@ -2206,7 +1360,6 @@ void ProtocolGame::sendCancelTarget() { NetworkMessage msg; msg.addByte(0xA3); - msg.add(0x00); writeToOutputBuffer(msg); } @@ -2215,8 +1368,7 @@ void ProtocolGame::sendChangeSpeed(const Creature* creature, uint32_t speed) NetworkMessage msg; msg.addByte(0x8F); msg.add(creature->getID()); - msg.add(creature->getBaseSpeed() / 2); - msg.add(speed / 2); + msg.add(speed); writeToOutputBuffer(msg); } @@ -2238,7 +1390,12 @@ void ProtocolGame::sendSkills() void ProtocolGame::sendPing() { NetworkMessage msg; - msg.addByte(0x1D); + if (player->getOperatingSystem() >= CLIENTOS_OTCLIENT_LINUX) { + msg.addByte(0x1D); + } else { + // classic clients ping + msg.addByte(0x1E); + } writeToOutputBuffer(msg); } @@ -2280,17 +1437,11 @@ void ProtocolGame::sendCreatureHealth(const Creature* creature) if (creature->isHealthHidden()) { msg.addByte(0x00); - } else { + } + else { msg.addByte(std::ceil((static_cast(creature->getHealth()) / std::max(creature->getMaxHealth(), 1)) * 100)); } - writeToOutputBuffer(msg); -} -void ProtocolGame::sendFYIBox(const std::string& message) -{ - NetworkMessage msg; - msg.addByte(0x15); - msg.addString(message); writeToOutputBuffer(msg); } @@ -2304,7 +1455,7 @@ void ProtocolGame::sendMapDescription(const Position& pos) writeToOutputBuffer(msg); } -void ProtocolGame::sendAddTileItem(const Position& pos, uint32_t stackpos, const Item* item) +void ProtocolGame::sendAddTileItem(const Position& pos, const Item* item, uint32_t stackpos) { if (!canSee(pos)) { return; @@ -2313,7 +1464,9 @@ void ProtocolGame::sendAddTileItem(const Position& pos, uint32_t stackpos, const NetworkMessage msg; msg.addByte(0x6A); msg.addPosition(pos); - msg.addByte(stackpos); + if (player->getOperatingSystem() >= CLIENTOS_OTCLIENT_LINUX) { + msg.addByte(stackpos); + } msg.addItem(item); writeToOutputBuffer(msg); } @@ -2365,20 +1518,6 @@ void ProtocolGame::sendUpdateTile(const Tile* tile, const Position& pos) writeToOutputBuffer(msg); } -void ProtocolGame::sendPendingStateEntered() -{ - NetworkMessage msg; - msg.addByte(0x0A); - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendEnterWorld() -{ - NetworkMessage msg; - msg.addByte(0x0F); - writeToOutputBuffer(msg); -} - void ProtocolGame::sendFightModes() { NetworkMessage msg; @@ -2386,7 +1525,6 @@ void ProtocolGame::sendFightModes() msg.addByte(player->fightMode); msg.addByte(player->chaseMode); msg.addByte(player->secureMode); - msg.addByte(PVP_MODE_DOVE); writeToOutputBuffer(msg); } @@ -2401,7 +1539,10 @@ void ProtocolGame::sendAddCreature(const Creature* creature, const Position& pos NetworkMessage msg; msg.addByte(0x6A); msg.addPosition(pos); - msg.addByte(stackpos); + + if (player->getOperatingSystem() >= CLIENTOS_OTCLIENT_LINUX) { + msg.addByte(stackpos); + } bool known; uint32_t removedKnown; @@ -2417,14 +1558,11 @@ void ProtocolGame::sendAddCreature(const Creature* creature, const Position& pos } NetworkMessage msg; - msg.addByte(0x17); + msg.addByte(0x0A); msg.add(player->getID()); - msg.add(0x32); // beat duration (50) - - msg.addDouble(Creature::speedA, 3); - msg.addDouble(Creature::speedB, 3); - msg.addDouble(Creature::speedC, 3); + msg.addByte(0x32); // beat duration (50) + msg.addByte(0x00); // can report bugs? if (player->getAccountType() >= ACCOUNT_TYPE_TUTOR) { @@ -2433,16 +1571,15 @@ void ProtocolGame::sendAddCreature(const Creature* creature, const Position& pos msg.addByte(0x00); } - msg.addByte(0x00); // can change pvp framing option - msg.addByte(0x00); // expert mode button enabled - - msg.add(0x00); // URL (string) to ingame store images - msg.add(25); // premium coin package size + if (player->getAccountType() >= ACCOUNT_TYPE_GAMEMASTER) { + msg.addByte(0x0B); + for (uint8_t i = 0; i < 32; i++) { + msg.addByte(0xFF); + } + } writeToOutputBuffer(msg); - sendPendingStateEntered(); - sendEnterWorld(); sendMapDescription(pos); if (isLogin) { @@ -2457,14 +1594,16 @@ void ProtocolGame::sendAddCreature(const Creature* creature, const Position& pos sendSkills(); //gameworld light-settings - sendWorldLight(g_game.getWorldLightInfo()); + LightInfo lightInfo; + g_game.getWorldLightInfo(lightInfo); + sendWorldLight(lightInfo); //player light level sendCreatureLight(creature); + //player vip sendVIPEntries(); - sendBasicData(); player->sendIcons(); } @@ -2545,34 +1684,11 @@ void ProtocolGame::sendInventoryItem(slots_t slot, const Item* item) writeToOutputBuffer(msg); } -void ProtocolGame::sendItems() -{ - NetworkMessage msg; - msg.addByte(0xF5); - - const std::vector& inventory = Item::items.getInventory(); - msg.add(inventory.size() + 11); - for (uint16_t i = 1; i <= 11; i++) { - msg.add(i); - msg.addByte(0); //always 0 - msg.add(1); // always 1 - } - - for (auto clientId : inventory) { - msg.add(clientId); - msg.addByte(0); //always 0 - msg.add(1); - } - - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendAddContainerItem(uint8_t cid, uint16_t slot, const Item* item) +void ProtocolGame::sendAddContainerItem(uint8_t cid, const Item* item) { NetworkMessage msg; msg.addByte(0x70); msg.addByte(cid); - msg.add(slot); msg.addItem(item); writeToOutputBuffer(msg); } @@ -2582,22 +1698,17 @@ void ProtocolGame::sendUpdateContainerItem(uint8_t cid, uint16_t slot, const Ite NetworkMessage msg; msg.addByte(0x71); msg.addByte(cid); - msg.add(slot); + msg.addByte(static_cast(slot)); msg.addItem(item); writeToOutputBuffer(msg); } -void ProtocolGame::sendRemoveContainerItem(uint8_t cid, uint16_t slot, const Item* lastItem) +void ProtocolGame::sendRemoveContainerItem(uint8_t cid, uint16_t slot) { NetworkMessage msg; msg.addByte(0x72); msg.addByte(cid); - msg.add(slot); - if (lastItem) { - msg.addItem(lastItem); - } else { - msg.add(0x00); - } + msg.addByte(static_cast(slot)); writeToOutputBuffer(msg); } @@ -2624,11 +1735,13 @@ void ProtocolGame::sendTextWindow(uint32_t windowTextId, Item* item, uint16_t ma msg.add(0x00); } - time_t writtenDate = item->getDate(); - if (writtenDate != 0) { - msg.addString(formatDateShort(writtenDate)); - } else { - msg.add(0x00); + if (player->getOperatingSystem() >= CLIENTOS_OTCLIENT_LINUX) { + time_t writtenDate = item->getDate(); + if (writtenDate != 0) { + msg.addString(formatDateShort(writtenDate)); + } else { + msg.add(0x00); + } } writeToOutputBuffer(msg); @@ -2643,7 +1756,6 @@ void ProtocolGame::sendTextWindow(uint32_t windowTextId, uint32_t itemId, const msg.add(text.size()); msg.addString(text); msg.add(0x00); - msg.add(0x00); writeToOutputBuffer(msg); } @@ -2663,11 +1775,6 @@ void ProtocolGame::sendOutfitWindow() msg.addByte(0xC8); Outfit_t currentOutfit = player->getDefaultOutfit(); - Mount* currentMount = g_game.mounts.getMountByID(player->getCurrentMount()); - if (currentMount) { - currentOutfit.lookMount = currentMount->clientId; - } - AddOutfit(msg, currentOutfit); std::vector protocolOutfits; @@ -2685,7 +1792,7 @@ void ProtocolGame::sendOutfitWindow() } protocolOutfits.emplace_back(outfit.name, outfit.lookType, addons); - if (protocolOutfits.size() == 100) { // Game client doesn't allow more than 100 outfits + if (protocolOutfits.size() == 15) { // Game client doesn't allow more than 15 outfits break; } } @@ -2693,44 +1800,26 @@ void ProtocolGame::sendOutfitWindow() msg.addByte(protocolOutfits.size()); for (const ProtocolOutfit& outfit : protocolOutfits) { msg.add(outfit.lookType); - msg.addString(outfit.name); msg.addByte(outfit.addons); } - std::vector mounts; - for (const Mount& mount : g_game.mounts.getMounts()) { - if (player->hasMount(&mount)) { - mounts.push_back(&mount); - } - } - - msg.addByte(mounts.size()); - for (const Mount* mount : mounts) { - msg.add(mount->clientId); - msg.addString(mount->name); - } - writeToOutputBuffer(msg); } void ProtocolGame::sendUpdatedVIPStatus(uint32_t guid, VipStatus_t newStatus) { NetworkMessage msg; - msg.addByte(0xD3); + msg.addByte(newStatus == VIPSTATUS_ONLINE ? 0xD3 : 0xD4); msg.add(guid); - msg.addByte(newStatus); writeToOutputBuffer(msg); } -void ProtocolGame::sendVIP(uint32_t guid, const std::string& name, const std::string& description, uint32_t icon, bool notify, VipStatus_t status) +void ProtocolGame::sendVIP(uint32_t guid, const std::string& name, VipStatus_t status) { NetworkMessage msg; msg.addByte(0xD2); msg.add(guid); msg.addString(name); - msg.addString(description); - msg.add(std::min(10, icon)); - msg.addByte(notify ? 0x01 : 0x00); msg.addByte(status); writeToOutputBuffer(msg); } @@ -2743,66 +1832,17 @@ void ProtocolGame::sendVIPEntries() VipStatus_t vipStatus = VIPSTATUS_ONLINE; Player* vipPlayer = g_game.getPlayerByGUID(entry.guid); - - if (!vipPlayer || vipPlayer->isInGhostMode() || player->isAccessPlayer()) { + if (!vipPlayer || (vipPlayer->isInGhostMode() && !player->isAccessPlayer())) { vipStatus = VIPSTATUS_OFFLINE; } - sendVIP(entry.guid, entry.name, entry.description, entry.icon, entry.notify, vipStatus); + sendVIP(entry.guid, entry.name, vipStatus); } } -void ProtocolGame::sendSpellCooldown(uint8_t spellId, uint32_t time) -{ - NetworkMessage msg; - msg.addByte(0xA4); - msg.addByte(spellId); - msg.add(time); - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendSpellGroupCooldown(SpellGroup_t groupId, uint32_t time) -{ - NetworkMessage msg; - msg.addByte(0xA5); - msg.addByte(groupId); - msg.add(time); - writeToOutputBuffer(msg); -} - -void ProtocolGame::sendModalWindow(const ModalWindow& modalWindow) -{ - NetworkMessage msg; - msg.addByte(0xFA); - - msg.add(modalWindow.id); - msg.addString(modalWindow.title); - msg.addString(modalWindow.message); - - msg.addByte(modalWindow.buttons.size()); - for (const auto& it : modalWindow.buttons) { - msg.addString(it.first); - msg.addByte(it.second); - } - - msg.addByte(modalWindow.choices.size()); - for (const auto& it : modalWindow.choices) { - msg.addString(it.first); - msg.addByte(it.second); - } - - msg.addByte(modalWindow.defaultEscapeButton); - msg.addByte(modalWindow.defaultEnterButton); - msg.addByte(modalWindow.priority ? 0x01 : 0x00); - - writeToOutputBuffer(msg); -} - ////////////// Add common messages void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bool known, uint32_t remove) { - CreatureType_t creatureType = creature->getType(); - const Player* otherPlayer = creature->getPlayer(); if (known) { @@ -2812,16 +1852,16 @@ void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bo msg.add(0x61); msg.add(remove); msg.add(creature->getID()); - msg.addByte(creatureType); msg.addString(creature->getName()); } if (creature->isHealthHidden()) { msg.addByte(0x00); - } else { + } + else { msg.addByte(std::ceil((static_cast(creature->getHealth()) / std::max(creature->getMaxHealth(), 1)) * 100)); } - + msg.addByte(creature->getDirection()); if (!creature->isInGhostMode() && !creature->isInvisible()) { @@ -2831,44 +1871,15 @@ void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bo AddOutfit(msg, outfit); } - LightInfo lightInfo = creature->getCreatureLight(); + LightInfo lightInfo; + creature->getCreatureLight(lightInfo); msg.addByte(player->isAccessPlayer() ? 0xFF : lightInfo.level); msg.addByte(lightInfo.color); - msg.add(creature->getStepSpeed() / 2); + msg.add(creature->getStepSpeed()); msg.addByte(player->getSkullClient(creature)); msg.addByte(player->getPartyShield(otherPlayer)); - - if (!known) { - msg.addByte(player->getGuildEmblem(otherPlayer)); - } - - if (creatureType == CREATURETYPE_MONSTER) { - const Creature* master = creature->getMaster(); - if (master) { - const Player* masterPlayer = master->getPlayer(); - if (masterPlayer) { - if (masterPlayer == player) { - creatureType = CREATURETYPE_SUMMON_OWN; - } else { - creatureType = CREATURETYPE_SUMMON_OTHERS; - } - } - } - } - - msg.addByte(creatureType); // Type (for summons) - msg.addByte(creature->getSpeechBubble()); - msg.addByte(0xFF); // MARK_UNMARKED - - if (otherPlayer) { - msg.add(otherPlayer->getHelpers()); - } else { - msg.add(0x00); - } - - msg.addByte(player->canWalkthroughEx(creature) ? 0x00 : 0x01); } void ProtocolGame::AddPlayerStats(NetworkMessage& msg) @@ -2877,41 +1888,23 @@ void ProtocolGame::AddPlayerStats(NetworkMessage& msg) msg.add(std::min(player->getHealth(), std::numeric_limits::max())); msg.add(std::min(player->getMaxHealth(), std::numeric_limits::max())); + + msg.add(player->getFreeCapacity() / 100); - msg.add(player->getFreeCapacity()); - msg.add(player->getCapacity()); - - msg.add(player->getExperience()); + msg.add(std::min(player->getExperience(), 0x7FFFFFFF)); msg.add(player->getLevel()); msg.addByte(player->getLevelPercent()); - msg.add(100); // base xp gain rate - msg.add(0); // xp voucher - msg.add(0); // low level bonus - msg.add(0); // xp boost - msg.add(100); // stamina multiplier (100 = x1.0) - msg.add(std::min(player->getMana(), std::numeric_limits::max())); msg.add(std::min(player->getMaxMana(), std::numeric_limits::max())); msg.addByte(std::min(player->getMagicLevel(), std::numeric_limits::max())); - msg.addByte(std::min(player->getBaseMagicLevel(), std::numeric_limits::max())); msg.addByte(player->getMagicLevelPercent()); msg.addByte(player->getSoul()); msg.add(player->getStaminaMinutes()); - - msg.add(player->getBaseSpeed() / 2); - - Condition* condition = player->getCondition(CONDITION_REGENERATION); - msg.add(condition ? condition->getTicks() / 1000 : 0x00); - - msg.add(player->getOfflineTrainingTime() / 60 / 1000); - - msg.add(0); // xp boost time (seconds) - msg.addByte(0); // enables exp boost in the store } void ProtocolGame::AddPlayerSkills(NetworkMessage& msg) @@ -2919,15 +1912,9 @@ void ProtocolGame::AddPlayerSkills(NetworkMessage& msg) msg.addByte(0xA1); for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { - msg.add(std::min(player->getSkillLevel(i), std::numeric_limits::max())); - msg.add(player->getBaseSkill(i)); + msg.addByte(std::min(player->getSkillLevel(i), std::numeric_limits::max())); msg.addByte(player->getSkillPercent(i)); } - - for (uint8_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; ++i) { - msg.add(std::min(100, player->varSpecialSkills[i])); - msg.add(0); - } } void ProtocolGame::AddOutfit(NetworkMessage& msg, const Outfit_t& outfit) @@ -2943,11 +1930,9 @@ void ProtocolGame::AddOutfit(NetworkMessage& msg, const Outfit_t& outfit) } else { msg.addItemId(outfit.lookTypeEx); } - - msg.add(outfit.lookMount); } -void ProtocolGame::AddWorldLight(NetworkMessage& msg, LightInfo lightInfo) +void ProtocolGame::AddWorldLight(NetworkMessage& msg, const LightInfo& lightInfo) { msg.addByte(0x82); msg.addByte((player->isAccessPlayer() ? 0xFF : lightInfo.level)); @@ -2956,7 +1941,8 @@ void ProtocolGame::AddWorldLight(NetworkMessage& msg, LightInfo lightInfo) void ProtocolGame::AddCreatureLight(NetworkMessage& msg, const Creature* creature) { - LightInfo lightInfo = creature->getCreatureLight(); + LightInfo lightInfo; + creature->getCreatureLight(lightInfo); msg.addByte(0x8D); msg.add(creature->getID()); @@ -3064,23 +2050,6 @@ void ProtocolGame::MoveDownCreature(NetworkMessage& msg, const Creature* creatur GetMapDescription(oldPos.x - 8, oldPos.y + 7, newPos.z, 18, 1, msg); } -void ProtocolGame::AddShopItem(NetworkMessage& msg, const ShopInfo& item) -{ - const ItemType& it = Item::items[item.itemId]; - msg.add(it.clientId); - - if (it.isSplash() || it.isFluidContainer()) { - msg.addByte(serverFluidToClient(item.subType)); - } else { - msg.addByte(0x00); - } - - msg.addString(item.realName); - msg.add(it.weight); - msg.add(item.buyPrice); - msg.add(item.sellPrice); -} - void ProtocolGame::parseExtendedOpcode(NetworkMessage& msg) { uint8_t opcode = msg.getByte(); diff --git a/src/protocolgame.h b/src/protocolgame.h index e8eb135..9d0cbe3 100644 --- a/src/protocolgame.h +++ b/src/protocolgame.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -34,21 +34,14 @@ class Tile; class Connection; class Quest; class ProtocolGame; -using ProtocolGame_ptr = std::shared_ptr; +typedef std::shared_ptr ProtocolGame_ptr; extern Game g_game; struct TextMessage { - MessageClasses type = MESSAGE_STATUS_DEFAULT; + MessageClasses type; std::string text; - Position position; - uint16_t channelId; - struct { - int32_t value = 0; - TextColor_t color; - } primary, secondary; - TextMessage() = default; TextMessage(MessageClasses type, std::string text) : type(type), text(std::move(text)) {} }; @@ -57,16 +50,16 @@ class ProtocolGame final : public Protocol { public: // static protocol information - enum {server_sends_first = true}; - enum {protocol_identifier = 0}; // Not required as we send first - enum {use_checksum = true}; + enum { server_sends_first = true }; + enum { protocol_identifier = 0 }; // Not required as we send first + static const char* protocol_name() { return "gameworld protocol"; } explicit ProtocolGame(Connection_ptr connection) : Protocol(connection) {} - void login(const std::string& name, uint32_t accountId, OperatingSystem_t operatingSystem); + void login(const std::string& name, uint32_t accnumber, OperatingSystem_t operatingSystem); void logout(bool displayEffect, bool forced); uint16_t getVersion() const { @@ -81,7 +74,7 @@ class ProtocolGame final : public Protocol void disconnectClient(const std::string& message) const; void writeToOutputBuffer(const NetworkMessage& msg); - void release() override; + void release() final; void checkCreatureAsKnown(uint32_t id, bool& known, uint32_t& removedKnown); @@ -90,8 +83,8 @@ class ProtocolGame final : public Protocol bool canSee(const Position& pos) const; // we have all the parse methods - void parsePacket(NetworkMessage& msg) override; - void onRecvFirstMessage(NetworkMessage& msg) override; + void parsePacket(NetworkMessage& msg) final; + void onRecvFirstMessage(NetworkMessage& msg) final; void onConnect() override; //Parse methods @@ -103,11 +96,12 @@ class ProtocolGame final : public Protocol void parseFightModes(NetworkMessage& msg); void parseAttack(NetworkMessage& msg); void parseFollow(NetworkMessage& msg); - void parseEquipObject(NetworkMessage& msg); + + void parseProcessRuleViolationReport(NetworkMessage& msg); + void parseCloseRuleViolationReport(NetworkMessage& msg); void parseBugReport(NetworkMessage& msg); void parseDebugAssert(NetworkMessage& msg); - void parseRuleViolationReport(NetworkMessage& msg); void parseThrow(NetworkMessage& msg); void parseUseItemEx(NetworkMessage& msg); @@ -119,40 +113,20 @@ class ProtocolGame final : public Protocol void parseTextWindow(NetworkMessage& msg); void parseHouseWindow(NetworkMessage& msg); - void parseLookInShop(NetworkMessage& msg); - void parsePlayerPurchase(NetworkMessage& msg); - void parsePlayerSale(NetworkMessage& msg); - - void parseQuestLine(NetworkMessage& msg); - void parseInviteToParty(NetworkMessage& msg); void parseJoinParty(NetworkMessage& msg); void parseRevokePartyInvite(NetworkMessage& msg); void parsePassPartyLeadership(NetworkMessage& msg); - void parseEnableSharedPartyExperience(NetworkMessage& msg); - void parseToggleMount(NetworkMessage& msg); - - void parseModalWindowAnswer(NetworkMessage& msg); - - void parseBrowseField(NetworkMessage& msg); void parseSeekInContainer(NetworkMessage& msg); //trade methods void parseRequestTrade(NetworkMessage& msg); void parseLookInTrade(NetworkMessage& msg); - //market methods - void parseMarketLeave(); - void parseMarketBrowse(NetworkMessage& msg); - void parseMarketCreateOffer(NetworkMessage& msg); - void parseMarketCancelOffer(NetworkMessage& msg); - void parseMarketAcceptOffer(NetworkMessage& msg); - //VIP methods void parseAddVip(NetworkMessage& msg); void parseRemoveVip(NetworkMessage& msg); - void parseEditVip(NetworkMessage& msg); void parseRotateItem(NetworkMessage& msg); @@ -164,17 +138,14 @@ class ProtocolGame final : public Protocol void parseCloseChannel(NetworkMessage& msg); //Send functions - void sendChannelMessage(const std::string& author, const std::string& text, SpeakClasses type, uint16_t channel); - void sendChannelEvent(uint16_t channelId, const std::string& playerName, ChannelEvent_t channelEvent); void sendClosePrivate(uint16_t channelId); void sendCreatePrivateChannel(uint16_t channelId, const std::string& channelName); void sendChannelsDialog(); - void sendChannel(uint16_t channelId, const std::string& channelName, const UsersMap* channelUsers, const InvitedMap* invitedUsers); + void sendChannel(uint16_t channelId, const std::string& channelName); void sendOpenPrivateChannel(const std::string& receiver); void sendToChannel(const Creature* creature, SpeakClasses type, const std::string& text, uint16_t channelId); void sendPrivateMessage(const Player* speaker, SpeakClasses type, const std::string& text); void sendIcons(uint16_t icons); - void sendFYIBox(const std::string& message); void sendDistanceShoot(const Position& from, const Position& to, uint8_t type); void sendMagicEffect(const Position& pos, uint8_t type); @@ -182,41 +153,20 @@ class ProtocolGame final : public Protocol void sendSkills(); void sendPing(); void sendPingBack(); - void sendCreatureTurn(const Creature* creature, uint32_t stackPos); + void sendCreatureTurn(const Creature* creature, uint32_t stackpos); void sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, const Position* pos = nullptr); - void sendQuestLog(); - void sendQuestLine(const Quest* quest); - void sendCancelWalk(); void sendChangeSpeed(const Creature* creature, uint32_t speed); void sendCancelTarget(); void sendCreatureOutfit(const Creature* creature, const Outfit_t& outfit); void sendStats(); - void sendBasicData(); void sendTextMessage(const TextMessage& message); - void sendReLoginWindow(uint8_t unfairFightReduction); + void sendAnimatedText(const Position& pos, uint8_t color, const std::string& text); - void sendTutorial(uint8_t tutorialId); - void sendAddMarker(const Position& pos, uint8_t markType, const std::string& desc); - - void sendCreatureWalkthrough(const Creature* creature, bool walkthrough); void sendCreatureShield(const Creature* creature); void sendCreatureSkull(const Creature* creature); - void sendCreatureType(uint32_t creatureId, uint8_t creatureType); - void sendCreatureHelpers(uint32_t creatureId, uint16_t helpers); - void sendShop(Npc* npc, const ShopInfoList& itemList); - void sendCloseShop(); - void sendSaleItemList(const std::list& shop); - void sendMarketEnter(uint32_t depotId); - void sendMarketLeave(); - void sendMarketBrowseItem(uint16_t itemId, const MarketOfferList& buyOffers, const MarketOfferList& sellOffers); - void sendMarketAcceptOffer(const MarketOfferEx& offer); - void sendMarketBrowseOwnOffers(const MarketOfferList& buyOffers, const MarketOfferList& sellOffers); - void sendMarketCancelOffer(const MarketOfferEx& offer); - void sendMarketBrowseOwnHistory(const HistoryMarketOfferList& buyOffers, const HistoryMarketOfferList& sellOffers); - void sendMarketDetail(uint16_t itemId); void sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack); void sendCloseTrade(); @@ -226,26 +176,26 @@ class ProtocolGame final : public Protocol void sendOutfitWindow(); void sendUpdatedVIPStatus(uint32_t guid, VipStatus_t newStatus); - void sendVIP(uint32_t guid, const std::string& name, const std::string& description, uint32_t icon, bool notify, VipStatus_t status); + void sendVIP(uint32_t guid, const std::string& name, VipStatus_t status); void sendVIPEntries(); - void sendPendingStateEntered(); - void sendEnterWorld(); - void sendFightModes(); void sendCreatureLight(const Creature* creature); - void sendWorldLight(LightInfo lightInfo); + void sendWorldLight(const LightInfo& lightInfo); void sendCreatureSquare(const Creature* creature, SquareColor_t color); - void sendSpellCooldown(uint8_t spellId, uint32_t time); - void sendSpellGroupCooldown(SpellGroup_t groupId, uint32_t time); + //rule violations + void sendRemoveRuleViolationReport(const std::string& name); + void sendLockRuleViolation(); + void sendRuleViolationCancel(const std::string& name); + void sendRuleViolationsChannel(uint16_t channelId); //tiles void sendMapDescription(const Position& pos); - void sendAddTileItem(const Position& pos, uint32_t stackpos, const Item* item); + void sendAddTileItem(const Position& pos, const Item* item, uint32_t stackpos); void sendUpdateTileItem(const Position& pos, uint32_t stackpos, const Item* item); void sendRemoveTileThing(const Position& pos, uint32_t stackpos); void sendUpdateTile(const Tile* tile, const Position& pos); @@ -255,19 +205,15 @@ class ProtocolGame final : public Protocol const Position& oldPos, int32_t oldStackPos, bool teleport); //containers - void sendAddContainerItem(uint8_t cid, uint16_t slot, const Item* item); + void sendAddContainerItem(uint8_t cid, const Item* item); void sendUpdateContainerItem(uint8_t cid, uint16_t slot, const Item* item); - void sendRemoveContainerItem(uint8_t cid, uint16_t slot, const Item* lastItem); + void sendRemoveContainerItem(uint8_t cid, uint16_t slot); void sendContainer(uint8_t cid, const Container* container, bool hasParent, uint16_t firstIndex); void sendCloseContainer(uint8_t cid); //inventory void sendInventoryItem(slots_t slot, const Item* item); - void sendItems(); - - //messages - void sendModalWindow(const ModalWindow& modalWindow); //Help functions @@ -286,7 +232,7 @@ class ProtocolGame final : public Protocol void AddPlayerStats(NetworkMessage& msg); void AddOutfit(NetworkMessage& msg, const Outfit_t& outfit); void AddPlayerSkills(NetworkMessage& msg); - void AddWorldLight(NetworkMessage& msg, LightInfo lightInfo); + void AddWorldLight(NetworkMessage& msg, const LightInfo& lightInfo); void AddCreatureLight(NetworkMessage& msg, const Creature* creature); //tiles @@ -295,9 +241,6 @@ class ProtocolGame final : public Protocol void MoveUpCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, const Position& oldPos); void MoveDownCreature(NetworkMessage& msg, const Creature* creature, const Position& newPos, const Position& oldPos); - //shop - void AddShopItem(NetworkMessage& msg, const ShopInfo& item); - //otclient void parseExtendedOpcode(NetworkMessage& msg); diff --git a/src/protocollogin.cpp b/src/protocollogin.cpp index 91da0ab..27e2ea3 100644 --- a/src/protocollogin.cpp +++ b/src/protocollogin.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -43,28 +43,15 @@ void ProtocolLogin::disconnectClient(const std::string& message, uint16_t versio disconnect(); } -void ProtocolLogin::getCharacterList(const std::string& accountName, const std::string& password, const std::string& token, uint16_t version) +void ProtocolLogin::getCharacterList(uint32_t accountNumber, const std::string& password, uint16_t version) { Account account; - if (!IOLoginData::loginserverAuthentication(accountName, password, account)) { - disconnectClient("Account name or password is not correct.", version); + if (!IOLoginData::loginserverAuthentication(accountNumber, password, account)) { + disconnectClient("Accountnumber or password is not correct.", version); return; } - uint32_t ticks = time(nullptr) / AUTHENTICATOR_PERIOD; - auto output = OutputMessagePool::getOutputMessage(); - if (!account.key.empty()) { - if (token.empty() || !(token == generateToken(account.key, ticks) || token == generateToken(account.key, ticks - 1) || token == generateToken(account.key, ticks + 1))) { - output->addByte(0x0D); - output->addByte(0); - send(output); - disconnect(); - return; - } - output->addByte(0x0C); - output->addByte(0); - } //Update premium days Game::updatePremium(account); @@ -79,53 +66,24 @@ void ProtocolLogin::getCharacterList(const std::string& accountName, const std:: output->addString(ss.str()); } - //Add session key - output->addByte(0x28); - output->addString(accountName + "\n" + password + "\n" + token + "\n" + std::to_string(ticks)); - //Add char list output->addByte(0x64); uint8_t size = std::min(std::numeric_limits::max(), account.characters.size()); - - if (g_config.getBoolean(ConfigManager::ONLINE_OFFLINE_CHARLIST)) { - output->addByte(2); // number of worlds - - for (uint8_t i = 0; i < 2; i++) { - output->addByte(i); // world id - output->addString(i == 0 ? "Offline" : "Online"); - output->addString(g_config.getString(ConfigManager::IP)); - output->add(g_config.getNumber(ConfigManager::GAME_PORT)); - output->addByte(0); - } - } else { - output->addByte(1); // number of worlds - output->addByte(0); // world id - output->addString(g_config.getString(ConfigManager::SERVER_NAME)); - output->addString(g_config.getString(ConfigManager::IP)); - output->add(g_config.getNumber(ConfigManager::GAME_PORT)); - output->addByte(0); - } - output->addByte(size); for (uint8_t i = 0; i < size; i++) { - const std::string& character = account.characters[i]; - if (g_config.getBoolean(ConfigManager::ONLINE_OFFLINE_CHARLIST)) { - output->addByte(g_game.getPlayerByName(character) ? 1 : 0); - } else { - output->addByte(0); - } - output->addString(character); + output->addString(account.characters[i]); + output->addString(g_config.getString(ConfigManager::SERVER_NAME)); + output->add(inet_addr(g_config.getString(ConfigManager::IP).c_str())); + output->add(g_config.getNumber(ConfigManager::GAME_PORT)); } //Add premium days - output->addByte(0); if (g_config.getBoolean(ConfigManager::FREE_PREMIUM)) { - output->addByte(1); - output->add(0); - } else { - output->addByte(account.premiumDays > 0 ? 1 : 0); - output->add(time(nullptr) + (account.premiumDays * 86400)); + output->add(0xFFFF); + } + else { + output->add(account.premiumDays); } send(output); @@ -133,6 +91,7 @@ void ProtocolLogin::getCharacterList(const std::string& accountName, const std:: disconnect(); } + void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) { if (g_game.getGameState() == GAME_STATE_SHUTDOWN) { @@ -145,9 +104,11 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) uint16_t version = msg.get(); if (version >= 971) { msg.skipBytes(17); - } else { + } + else { msg.skipBytes(12); } + /* * Skipped bytes: * 4 bytes: protocolVersion @@ -167,13 +128,13 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) return; } - xtea::key key; + uint32_t key[4]; key[0] = msg.get(); key[1] = msg.get(); key[2] = msg.get(); key[3] = msg.get(); enableXTEAEncryption(); - setXTEAKey(std::move(key)); + setXTEAKey(key); if (version < CLIENT_VERSION_MIN || version > CLIENT_VERSION_MAX) { std::ostringstream ss; @@ -209,9 +170,9 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) return; } - std::string accountName = msg.getString(); - if (accountName.empty()) { - disconnectClient("Invalid account name.", version); + uint32_t accountNumber = msg.get(); + if (!accountNumber) { + disconnectClient("Invalid account number.", version); return; } @@ -221,15 +182,6 @@ void ProtocolLogin::onRecvFirstMessage(NetworkMessage& msg) return; } - // read authenticator token and stay logged in flag from last 128 bytes - msg.skipBytes((msg.getLength() - 128) - msg.getBufferPosition()); - if (!Protocol::RSA_decrypt(msg)) { - disconnectClient("Invalid authentification token.", version); - return; - } - - std::string authToken = msg.getString(); - auto thisPtr = std::static_pointer_cast(shared_from_this()); - g_dispatcher.addTask(createTask(std::bind(&ProtocolLogin::getCharacterList, thisPtr, accountName, password, authToken, version))); + g_dispatcher.addTask(createTask(std::bind(&ProtocolLogin::getCharacterList, thisPtr, accountNumber, password, version))); } diff --git a/src/protocollogin.h b/src/protocollogin.h index 9781446..a45898f 100644 --- a/src/protocollogin.h +++ b/src/protocollogin.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,7 +31,6 @@ class ProtocolLogin : public Protocol // static protocol information enum {server_sends_first = false}; enum {protocol_identifier = 0x01}; - enum {use_checksum = true}; static const char* protocol_name() { return "login protocol"; } @@ -43,7 +42,7 @@ class ProtocolLogin : public Protocol private: void disconnectClient(const std::string& message, uint16_t version); - void getCharacterList(const std::string& accountName, const std::string& password, const std::string& token, uint16_t version); + void getCharacterList(uint32_t accountNumber, const std::string& password, uint16_t version); }; #endif diff --git a/src/protocolold.cpp b/src/protocolold.cpp deleted file mode 100644 index e981d56..0000000 --- a/src/protocolold.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "otpch.h" - -#include "protocolold.h" -#include "outputmessage.h" - -#include "game.h" - -extern Game g_game; - -void ProtocolOld::disconnectClient(const std::string& message) -{ - auto output = OutputMessagePool::getOutputMessage(); - output->addByte(0x0A); - output->addString(message); - send(output); - - disconnect(); -} - -void ProtocolOld::onRecvFirstMessage(NetworkMessage& msg) -{ - if (g_game.getGameState() == GAME_STATE_SHUTDOWN) { - disconnect(); - return; - } - - /*uint16_t clientOS =*/ msg.get(); - uint16_t version = msg.get(); - msg.skipBytes(12); - - if (version <= 760) { - std::ostringstream ss; - ss << "Only clients with protocol " << CLIENT_VERSION_STR << " allowed!"; - disconnectClient(ss.str()); - return; - } - - if (!Protocol::RSA_decrypt(msg)) { - disconnect(); - return; - } - - xtea::key key; - key[0] = msg.get(); - key[1] = msg.get(); - key[2] = msg.get(); - key[3] = msg.get(); - enableXTEAEncryption(); - setXTEAKey(std::move(key)); - - if (version <= 822) { - disableChecksum(); - } - - std::ostringstream ss; - ss << "Only clients with protocol " << CLIENT_VERSION_STR << " allowed!"; - disconnectClient(ss.str()); -} diff --git a/src/protocolold.h b/src/protocolold.h deleted file mode 100644 index 708e763..0000000 --- a/src/protocolold.h +++ /dev/null @@ -1,46 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_PROTOCOLOLD_H_5487B862FE144AE0904D098A3238E161 -#define FS_PROTOCOLOLD_H_5487B862FE144AE0904D098A3238E161 - -#include "protocol.h" - -class NetworkMessage; - -class ProtocolOld final : public Protocol -{ - public: - // static protocol information - enum {server_sends_first = false}; - enum {protocol_identifier = 0x01}; - enum {use_checksum = false}; - static const char* protocol_name() { - return "old login protocol"; - } - - explicit ProtocolOld(Connection_ptr connection) : Protocol(connection) {} - - void onRecvFirstMessage(NetworkMessage& msg) override; - - private: - void disconnectClient(const std::string& message); -}; - -#endif diff --git a/src/protocolstatus.cpp b/src/protocolstatus.cpp index aba1bb2..d29b19e 100644 --- a/src/protocolstatus.cpp +++ b/src/protocolstatus.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -117,7 +117,26 @@ void ProtocolStatus::sendStatusString() owner.append_attribute("email") = g_config.getString(ConfigManager::OWNER_EMAIL).c_str(); pugi::xml_node players = tsqp.append_child("players"); - players.append_attribute("online") = std::to_string(g_game.getPlayersOnline()).c_str(); + uint32_t real = 0; + + std::map listIP; + + for (const auto& it : g_game.getPlayers()) { + if (it.second->getIP() != 0) { + auto ip = listIP.find(it.second->getIP()); + if (ip != listIP.end()) { + listIP[it.second->getIP()]++; + if (listIP[it.second->getIP()] < 5) { + real++; + } + } else { + listIP[it.second->getIP()] = 1; + real++; + } + } + } + players.append_attribute("online") = std::to_string(real).c_str(); + players.append_attribute("max") = std::to_string(g_config.getNumber(ConfigManager::MAX_PLAYERS)).c_str(); players.append_attribute("peak") = std::to_string(g_game.getPlayersRecord()).c_str(); diff --git a/src/protocolstatus.h b/src/protocolstatus.h index 5df99e4..2e6cae2 100644 --- a/src/protocolstatus.h +++ b/src/protocolstatus.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,21 +29,20 @@ class ProtocolStatus final : public Protocol // static protocol information enum {server_sends_first = false}; enum {protocol_identifier = 0xFF}; - enum {use_checksum = false}; static const char* protocol_name() { return "status protocol"; } explicit ProtocolStatus(Connection_ptr connection) : Protocol(connection) {} - void onRecvFirstMessage(NetworkMessage& msg) override; + void onRecvFirstMessage(NetworkMessage& msg) final; void sendStatusString(); void sendInfo(uint16_t requestedInfo, const std::string& characterName); static const uint64_t start; - private: + protected: static std::map ipConnectMap; }; diff --git a/src/pugicast.h b/src/pugicast.h index 9b188ed..456a2c0 100644 --- a/src/pugicast.h +++ b/src/pugicast.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/quests.cpp b/src/quests.cpp deleted file mode 100644 index 2e4f552..0000000 --- a/src/quests.cpp +++ /dev/null @@ -1,228 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "otpch.h" - -#include "quests.h" - -#include "pugicast.h" - -std::string Mission::getDescription(Player* player) const -{ - int32_t value; - player->getStorageValue(storageID, value); - - if (!mainDescription.empty()) { - std::string desc = mainDescription; - replaceString(desc, "|STATE|", std::to_string(value)); - replaceString(desc, "\\n", "\n"); - return desc; - } - - if (ignoreEndValue) { - for (int32_t current = endValue; current >= startValue; current--) { - if (value >= current) { - auto sit = descriptions.find(current); - if (sit != descriptions.end()) { - return sit->second; - } - } - } - } else { - for (int32_t current = endValue; current >= startValue; current--) { - if (value == current) { - auto sit = descriptions.find(current); - if (sit != descriptions.end()) { - return sit->second; - } - } - } - } - return "An error has occurred, please contact a gamemaster."; -} - -bool Mission::isStarted(Player* player) const -{ - if (!player) { - return false; - } - - int32_t value; - if (!player->getStorageValue(storageID, value)) { - return false; - } - - if (value < startValue) { - return false; - } - - if (!ignoreEndValue && value > endValue) { - return false; - } - - return true; -} - -bool Mission::isCompleted(Player* player) const -{ - if (!player) { - return false; - } - - int32_t value; - if (!player->getStorageValue(storageID, value)) { - return false; - } - - if (ignoreEndValue) { - return value >= endValue; - } - - return value == endValue; -} - -std::string Mission::getName(Player* player) const -{ - if (isCompleted(player)) { - return name + " (completed)"; - } - return name; -} - -uint16_t Quest::getMissionsCount(Player* player) const -{ - uint16_t count = 0; - for (const Mission& mission : missions) { - if (mission.isStarted(player)) { - count++; - } - } - return count; -} - -bool Quest::isCompleted(Player* player) const -{ - for (const Mission& mission : missions) { - if (!mission.isCompleted(player)) { - return false; - } - } - return true; -} - -bool Quest::isStarted(Player* player) const -{ - if (!player) { - return false; - } - - int32_t value; - if (!player->getStorageValue(startStorageID, value) || value < startStorageValue) { - return false; - } - - return true; -} - -bool Quests::reload() -{ - quests.clear(); - return loadFromXml(); -} - -bool Quests::loadFromXml() -{ - pugi::xml_document doc; - pugi::xml_parse_result result = doc.load_file("data/XML/quests.xml"); - if (!result) { - printXMLError("Error - Quests::loadFromXml", "data/XML/quests.xml", result); - return false; - } - - uint16_t id = 0; - for (auto questNode : doc.child("quests").children()) { - quests.emplace_back( - questNode.attribute("name").as_string(), - ++id, - pugi::cast(questNode.attribute("startstorageid").value()), - pugi::cast(questNode.attribute("startstoragevalue").value()) - ); - Quest& quest = quests.back(); - - for (auto missionNode : questNode.children()) { - std::string mainDescription = missionNode.attribute("description").as_string(); - - quest.missions.emplace_back( - missionNode.attribute("name").as_string(), - pugi::cast(missionNode.attribute("storageid").value()), - pugi::cast(missionNode.attribute("startvalue").value()), - pugi::cast(missionNode.attribute("endvalue").value()), - missionNode.attribute("ignoreendvalue").as_bool() - ); - Mission& mission = quest.missions.back(); - - if (mainDescription.empty()) { - for (auto missionStateNode : missionNode.children()) { - int32_t missionId = pugi::cast(missionStateNode.attribute("id").value()); - mission.descriptions.emplace(missionId, missionStateNode.attribute("description").as_string()); - } - } else { - mission.mainDescription = mainDescription; - } - } - } - return true; -} - -Quest* Quests::getQuestByID(uint16_t id) -{ - for (Quest& quest : quests) { - if (quest.id == id) { - return ? - } - } - return nullptr; -} - -uint16_t Quests::getQuestsCount(Player* player) const -{ - uint16_t count = 0; - for (const Quest& quest : quests) { - if (quest.isStarted(player)) { - count++; - } - } - return count; -} - -bool Quests::isQuestStorage(const uint32_t key, const int32_t value, const int32_t oldValue) const -{ - for (const Quest& quest : quests) { - if (quest.getStartStorageId() == key && quest.getStartStorageValue() == value) { - return true; - } - - for (const Mission& mission : quest.getMissions()) { - if (mission.getStorageId() == key && value >= mission.getStartStorageValue() && value <= mission.getEndStorageValue()) { - return mission.mainDescription.empty() || oldValue < mission.getStartStorageValue() || oldValue > mission.getEndStorageValue(); - } - } - } - return false; -} diff --git a/src/quests.h b/src/quests.h deleted file mode 100644 index 3ccd180..0000000 --- a/src/quests.h +++ /dev/null @@ -1,119 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_QUESTS_H_16E44051F23547BE8097F8EA9FCAACA0 -#define FS_QUESTS_H_16E44051F23547BE8097F8EA9FCAACA0 - -#include "player.h" -#include "networkmessage.h" - -class Mission; -class Quest; - -using MissionsList = std::list; -using QuestsList = std::list; - -class Mission -{ - public: - Mission(std::string name, int32_t storageID, int32_t startValue, int32_t endValue, bool ignoreEndValue) : - name(std::move(name)), storageID(storageID), startValue(startValue), endValue(endValue), ignoreEndValue(ignoreEndValue) {} - - bool isCompleted(Player* player) const; - bool isStarted(Player* player) const; - std::string getName(Player* player) const; - std::string getDescription(Player* player) const; - - uint32_t getStorageId() const { - return storageID; - } - int32_t getStartStorageValue() const { - return startValue; - } - int32_t getEndStorageValue() const { - return endValue; - } - - std::map descriptions; - std::string mainDescription; - - private: - std::string name; - uint32_t storageID; - int32_t startValue, endValue; - bool ignoreEndValue; -}; - -class Quest -{ - public: - Quest(std::string name, uint16_t id, int32_t startStorageID, int32_t startStorageValue) : - name(std::move(name)), startStorageID(startStorageID), startStorageValue(startStorageValue), id(id) {} - - bool isCompleted(Player* player) const; - bool isStarted(Player* player) const; - uint16_t getID() const { - return id; - } - std::string getName() const { - return name; - } - uint16_t getMissionsCount(Player* player) const; - - uint32_t getStartStorageId() const { - return startStorageID; - } - int32_t getStartStorageValue() const { - return startStorageValue; - } - - const MissionsList& getMissions() const { - return missions; - } - - private: - std::string name; - - uint32_t startStorageID; - int32_t startStorageValue; - uint16_t id; - - MissionsList missions; - - friend class Quests; -}; - -class Quests -{ - public: - const QuestsList& getQuests() const { - return quests; - } - - bool loadFromXml(); - Quest* getQuestByID(uint16_t id); - bool isQuestStorage(const uint32_t key, const int32_t value, const int32_t oldValue) const; - uint16_t getQuestsCount(Player* player) const; - bool reload(); - - private: - QuestsList quests; -}; - -#endif diff --git a/src/raids.cpp b/src/raids.cpp index d831bdc..6fd4f1f 100644 --- a/src/raids.cpp +++ b/src/raids.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -228,9 +228,7 @@ bool Raid::loadFromXml(const std::string& filename) } //sort by delay time - std::sort(raidEvents.begin(), raidEvents.end(), [](const RaidEvent* lhs, const RaidEvent* rhs) { - return lhs->getDelay() < rhs->getDelay(); - }); + std::sort(raidEvents.begin(), raidEvents.end(), RaidEvent::compareEvents); loaded = true; return true; @@ -481,6 +479,10 @@ bool AreaSpawnEvent::configureRaidEvent(const pugi::xml_node& eventNode) } } + if ((attr = eventNode.attribute("lifetime"))) { + lifetime = pugi::cast(attr.value()); + } + for (auto monsterNode : eventNode.children()) { const char* name; @@ -543,6 +545,10 @@ bool AreaSpawnEvent::executeEvent() if (!success) { delete monster; } + + if (lifetime > 0) { + monster->lifetime = OTSYS_TIME() + lifetime; + } } } return true; diff --git a/src/raids.h b/src/raids.h index bb6d75d..3c97d1d 100644 --- a/src/raids.h +++ b/src/raids.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -167,6 +167,10 @@ class RaidEvent return delay; } + static bool compareEvents(const RaidEvent* lhs, const RaidEvent* rhs) { + return lhs->getDelay() < rhs->getDelay(); + } + private: uint32_t delay; }; @@ -176,9 +180,9 @@ class AnnounceEvent final : public RaidEvent public: AnnounceEvent() = default; - bool configureRaidEvent(const pugi::xml_node& eventNode) override; + bool configureRaidEvent(const pugi::xml_node& eventNode) final; - bool executeEvent() override; + bool executeEvent() final; private: std::string message; @@ -188,9 +192,9 @@ class AnnounceEvent final : public RaidEvent class SingleSpawnEvent final : public RaidEvent { public: - bool configureRaidEvent(const pugi::xml_node& eventNode) override; + bool configureRaidEvent(const pugi::xml_node& eventNode) final; - bool executeEvent() override; + bool executeEvent() final; private: std::string monsterName; @@ -200,13 +204,14 @@ class SingleSpawnEvent final : public RaidEvent class AreaSpawnEvent final : public RaidEvent { public: - bool configureRaidEvent(const pugi::xml_node& eventNode) override; + bool configureRaidEvent(const pugi::xml_node& eventNode) final; - bool executeEvent() override; + bool executeEvent() final; private: std::list spawnList; Position fromPos, toPos; + uint32_t lifetime = 0; }; class ScriptEvent final : public RaidEvent, public Event @@ -214,15 +219,15 @@ class ScriptEvent final : public RaidEvent, public Event public: explicit ScriptEvent(LuaScriptInterface* interface) : Event(interface) {} - bool configureRaidEvent(const pugi::xml_node& eventNode) override; - bool configureEvent(const pugi::xml_node&) override { + bool configureRaidEvent(const pugi::xml_node& eventNode) final; + bool configureEvent(const pugi::xml_node&) final { return false; } - bool executeEvent() override; + bool executeEvent() final; - private: - std::string getScriptEventName() const override; + protected: + std::string getScriptEventName() const final; }; #endif diff --git a/src/rsa.cpp b/src/rsa.cpp index 0c95e4d..ba557ac 100644 --- a/src/rsa.cpp +++ b/src/rsa.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,7 +31,7 @@ static CryptoPP::AutoSeededRandomPool prng; void RSA::decrypt(char* msg) const { - CryptoPP::Integer m{reinterpret_cast(msg), 128}; + CryptoPP::Integer m{ reinterpret_cast(msg), 128 }; auto c = pk.CalculateInverse(prng, m); c.Encode(reinterpret_cast(msg), 128); } @@ -41,11 +41,11 @@ static const std::string footer = "-----END RSA PRIVATE KEY-----"; void RSA::loadPEM(const std::string& filename) { - std::ifstream file{filename}; + std::ifstream file{ filename }; if (!file.is_open()) { throw std::runtime_error("Missing file " + filename + "."); - } + } std::ostringstream oss; for (std::string line; std::getline(file, line); oss << line); @@ -73,7 +73,8 @@ void RSA::loadPEM(const std::string& filename) if (!pk.Validate(prng, 3)) { throw std::runtime_error("RSA private key is not valid."); } - } catch (const CryptoPP::Exception& e) { + } + catch (const CryptoPP::Exception& e) { std::cout << e.what() << '\n'; } } diff --git a/src/rsa.h b/src/rsa.h index 888e3b6..0783e7c 100644 --- a/src/rsa.h +++ b/src/rsa.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,18 +26,18 @@ class RSA { - public: - RSA() = default; +public: + RSA() = default; - // non-copyable - RSA(const RSA&) = delete; - RSA& operator=(const RSA&) = delete; + // non-copyable + RSA(const RSA&) = delete; + RSA& operator=(const RSA&) = delete; - void loadPEM(const std::string& filename); - void decrypt(char* msg) const; + void loadPEM(const std::string& filename); + void decrypt(char* msg) const; - private: - CryptoPP::RSA::PrivateKey pk; +private: + CryptoPP::RSA::PrivateKey pk; }; -#endif +#endif \ No newline at end of file diff --git a/src/scheduler.cpp b/src/scheduler.cpp index f489f61..28fe462 100644 --- a/src/scheduler.cpp +++ b/src/scheduler.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,7 +35,7 @@ void Scheduler::threadMain() } // the mutex is locked again now... - if (ret == std::cv_status::timeout && !eventList.empty()) { + if (ret == std::cv_status::timeout) { // ok we had a timeout, so there has to be an event we have to execute... SchedulerTask* task = eventList.top(); eventList.pop(); @@ -60,54 +60,54 @@ void Scheduler::threadMain() uint32_t Scheduler::addEvent(SchedulerTask* task) { + bool do_signal = false; eventLock.lock(); - if (getState() != THREAD_STATE_RUNNING) { + if (getState() == THREAD_STATE_RUNNING) { + // check if the event has a valid id + if (task->getEventId() == 0) { + // if not generate one + if (++lastEventId == 0) { + lastEventId = 1; + } + + task->setEventId(lastEventId); + } + + // insert the event id in the list of active events + eventIds.insert(task->getEventId()); + + // add the event to the queue + eventList.push(task); + + // if the list was empty or this event is the top in the list + // we have to signal it + do_signal = (task == eventList.top()); + } else { eventLock.unlock(); delete task; return 0; } - // check if the event has a valid id - if (task->getEventId() == 0) { - // if not generate one - if (++lastEventId == 0) { - lastEventId = 1; - } - - task->setEventId(lastEventId); - } - - // insert the event id in the list of active events - uint32_t eventId = task->getEventId(); - eventIds.insert(eventId); - - // add the event to the queue - eventList.push(task); - - // if the list was empty or this event is the top in the list - // we have to signal it - bool do_signal = (task == eventList.top()); - eventLock.unlock(); if (do_signal) { eventSignal.notify_one(); } - return eventId; + return task->getEventId(); } -bool Scheduler::stopEvent(uint32_t eventId) +bool Scheduler::stopEvent(uint32_t eventid) { - if (eventId == 0) { + if (eventid == 0) { return false; } std::lock_guard lockClass(eventLock); // search the event id.. - auto it = eventIds.find(eventId); + auto it = eventIds.find(eventid); if (it == eventIds.end()) { return false; } @@ -132,7 +132,3 @@ void Scheduler::shutdown() eventSignal.notify_one(); } -SchedulerTask* createSchedulerTask(uint32_t delay, std::function f) -{ - return new SchedulerTask(delay, std::move(f)); -} diff --git a/src/scheduler.h b/src/scheduler.h index e42f59f..cc8a839 100644 --- a/src/scheduler.h +++ b/src/scheduler.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,15 +42,18 @@ class SchedulerTask : public Task return expiration; } - private: - SchedulerTask(uint32_t delay, std::function&& f) : Task(delay, std::move(f)) {} + protected: + SchedulerTask(uint32_t delay, const std::function& f) : Task(delay, f) {} uint32_t eventId = 0; - friend SchedulerTask* createSchedulerTask(uint32_t, std::function); + friend SchedulerTask* createSchedulerTask(uint32_t, const std::function&); }; -SchedulerTask* createSchedulerTask(uint32_t delay, std::function f); +inline SchedulerTask* createSchedulerTask(uint32_t delay, const std::function& f) +{ + return new SchedulerTask(delay, f); +} struct TaskComparator { bool operator()(const SchedulerTask* lhs, const SchedulerTask* rhs) const { @@ -67,8 +70,7 @@ class Scheduler : public ThreadHolder void shutdown(); void threadMain(); - - private: + protected: std::thread thread; std::mutex eventLock; std::condition_variable eventSignal; diff --git a/src/script.cpp b/src/script.cpp index 144d9d2..40e955e 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -1,99 +1,432 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +* Tibia GIMUD Server - a free and open-source MMORPG server emulator +* Copyright (C) 2019 Sabrehaven and Mark Samman +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License along +* with this program; if not, write to the Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ #include "otpch.h" #include "script.h" -#include -#include "configmanager.h" -extern LuaEnvironment g_luaEnvironment; -extern ConfigManager g_config; - -Scripts::Scripts() : - scriptInterface("Scripts Interface") +void ScriptReader::nextToken() { - scriptInterface.initState(); -} + signed int v1; // esi@3 + int v4; // eax@5 + int v5; // eax@8 + int v6; // edi@8 + int v7; // eax@24 + int v9; // eax@32 + int v10; // eax@36 + int v11; // edi@36 + int v12; // eax@45 + int v13; // edi@45 + int v14; // eax@52 + int v15; // edi@52 + int v16; // eax@55 + int v17; // eax@62 + int v18; // eax@70 + int v19; // eax@78 + int v20; // edi@78 + int v21; // eax@86 + int v22; // eax@90 + int v23; // edi@90 + int v24; // eax@94 + int v25; // eax@98 + int v26; // edi@98 + int v27; // eax@102 + int v28; // eax@121 + int v29; // eax@127 + int v30; // eax@131 + int v31; // eax@136 + int v32; // eax@139 + std::string FileName; // [sp+1Ch] [bp-1Ch]@2 + int Sign; // [sp+20h] [bp-18h]@5 + FILE *f; // [sp+24h] [bp-14h]@5 + int pos; // [sp+28h] [bp-10h]@3 -Scripts::~Scripts() -{ - scriptInterface.reInitState(); -} - -bool Scripts::loadScripts(std::string folderName, bool isLib, bool reload) -{ - namespace fs = boost::filesystem; - - const auto dir = fs::current_path() / "data" / folderName; - if(!fs::exists(dir) || !fs::is_directory(dir)) { - std::cout << "[Warning - Scripts::loadScripts] Can not load folder '" << folderName << "'." << std::endl; - return false; + if (this->RecursionDepth == -1) + { + error("ScriptReader::nextToken: Kein Skript zum Lesen geffnet.\n"); + LABEL_30: + this->Token = ENDOFFILE; + return; } - - fs::recursive_directory_iterator endit; - std::vector v; - std::string disable = ("#"); - for(fs::recursive_directory_iterator it(dir); it != endit; ++it) { - auto fn = it->path().parent_path().filename(); - if ((fn == "lib" && !isLib) || fn == "events") { + FileName = String; +LABEL_3: + pos = 0; + v1 = 0; + this->String = ""; + v4 = this->RecursionDepth; + this->Number = 0; + Sign = 1; + f = this->File[v4]; + while (2) + { + if (pos == 3999) + error("string too long"); + switch (v1) + { + case 0: + v5 = getc(f); + v6 = v5; + if (v5 == -1) + goto LABEL_24; + if (v5 == 10) + { + ++this->Line[this->RecursionDepth]; + } else if (!isspace(v5)) + { + v1 = 1; + if (v6 != 35) + { + v1 = 30; + if (v6 != 64) + { + if (isalpha(v6)) + { + v1 = 2; + this->String += v6; + pos++; + } else if (IsDigit(v6)) + { + this->Number = v6 - 48; + v1 = 3; + } else + { + v1 = 6; + if (v6 != 34) + { + v1 = 11; + if (v6 != 91) + { + v1 = 22; + if (v6 != 60) + { + v1 = 25; + if (v6 != 62) + { + v1 = 27; + if (v6 != 45) + { + v1 = 10; + this->Special = v6; + } + } + } + } + } + } + } + } + } continue; - } - if(fs::is_regular_file(*it) && it->path().extension() == ".lua") { - size_t found = it->path().filename().string().find(disable); - if (found != std::string::npos) { - if (g_config.getBoolean(ConfigManager::SCRIPTS_CONSOLE_LOGS)) { - std::cout << "> " << it->path().filename().string() << " [disabled]" << std::endl; + case 1: + v9 = getc(f); + if (v9 != -1) + { + if (v9 == 10) + { + ++this->Line[this->RecursionDepth]; + LABEL_35: + v1 = 0; } continue; } - v.push_back(it->path()); - } - } - sort(v.begin(), v.end()); - std::string redir; - for (auto it = v.begin(); it != v.end(); ++it) { - const std::string scriptFile = it->string(); - if (!isLib) { - if (redir.empty() || redir != it->parent_path().string()) { - auto p = fs::path(it->relative_path()); - if (g_config.getBoolean(ConfigManager::SCRIPTS_CONSOLE_LOGS)) { - std::cout << ">> [" << p.parent_path().filename() << "]" << std::endl; + LABEL_24: + v7 = this->RecursionDepth; + if (v7 <= 0) + goto LABEL_30; + if (v7 == -1) + { + printf("ScriptReader::close: Keine Datei offen.\n"); + } else + { + if (fclose(this->File[v7])) + { + printf("ScriptReader::close: Fehler %d beim Schlieen der Datei.\n", RecursionDepth); } - redir = it->parent_path().string(); + --this->RecursionDepth; } - } - - if(scriptInterface.loadFile(scriptFile) == -1) { - std::cout << "> " << it->filename().string() << " [error]" << std::endl; - std::cout << "^ " << scriptInterface.getLastLuaError() << std::endl; + goto LABEL_3; + case 2: + v10 = getc(f); + v11 = v10; + if (pos == 30) + error("identifier too long"); + if (v10 == -1) + goto LABEL_43; + if (isalpha(v10) || IsDigit(v11) || v11 == 95) + goto LABEL_39; + ungetc(v11, f); + LABEL_43: + this->Token = IDENTIFIER; + return; + case 3: + v12 = getc(f); + v13 = v12; + if (v12 == -1) + goto LABEL_50; + if (IsDigit(v12)) + goto LABEL_51; + if (v13 == 45) + goto LABEL_48; + ungetc(v13, f); + LABEL_50: + this->Token = NUMBER; + return; + case 4: + v14 = getc(f); + v15 = v14; + if (v14 == -1) + error("unexpected end of file"); + if (!IsDigit(v14)) + error("syntax error"); + v1 = 5; + this->Number = v15 - 48; continue; - } - - if (g_config.getBoolean(ConfigManager::SCRIPTS_CONSOLE_LOGS)) { - if (!reload) { - std::cout << "> " << it->filename().string() << " [loaded]" << std::endl; - } else { - std::cout << "> " << it->filename().string() << " [reloaded]" << std::endl; + case 5: + v16 = getc(f); + v13 = v16; + if (v16 == -1) + goto LABEL_59; + if (IsDigit(v16)) + goto LABEL_51; + if (v13 != 45) + { + ungetc(v13, f); + LABEL_59: + this->Bytes[pos] = this->Number; + this->Token = BYTES; + return; } + LABEL_48: + this->Bytes[pos++] = this->Number; + v1 = 4; + continue; + case 6: + v17 = getc(f); + v11 = v17; + if (v17 == -1) + error("unexpected end of file"); + if (v17 == 34) + { + v1 = 8; + } else if (v17 == 92) + { + v1 = 7; + } else + { + if (v17 == 10) + ++this->Line[this->RecursionDepth]; + LABEL_39: + this->String += v11; + pos++; + } + continue; + case 7: + v18 = getc(f); + if (v18 == -1) + error("unexpected end of file"); + if (v18 == 110) { + this->String += 10; + pos++; + } else { + this->String += v18; + pos++; + } + v1 = 6; + continue; + case 8: + this->Token = STRING; + return; + case 10: + goto LABEL_77; + case 11: + v19 = getc(f); + this->Special = 91; + v20 = v19; + if (v19 == -1) + goto LABEL_77; + if (IsDigit(v19)) + { + Sign = 1; + this->Number = v20 - 48; + LABEL_82: + v1 = 12; + continue; + } + if (v20 == 45) + { + Sign = -1; + this->Number = 0; + goto LABEL_82; + } + LABEL_83: + ungetc(v20, f); + LABEL_77: + this->Token = SPECIAL; + return; + case 12: + v21 = getc(f); + v13 = v21; + if (v21 == -1) + error("unexpected end of file"); + if (IsDigit(v21)) + goto LABEL_51; + if (v13 != 44) + error("syntax error"); + v1 = 13; + this->CoordX = this->Number * Sign; + continue; + case 13: + v22 = getc(f); + v23 = v22; + if (v22 == -1) + error("unexpected end of file"); + if (IsDigit(v22)) + { + Sign = 1; + this->Number = v23 - 48; + } else + { + if (v23 != 45) + error("syntax error"); + Sign = -1; + this->Number = 0; + } + v1 = 14; + continue; + case 14: + v24 = getc(f); + v13 = v24; + if (v24 == -1) + error("unexpected end of file"); + if (IsDigit(v24)) + goto LABEL_51; + if (v13 != 44) + error("syntax error"); + v1 = 15; + this->CoordY = this->Number * Sign; + continue; + case 15: + v25 = getc(f); + v26 = v25; + if (v25 == -1) + error("unexpected end of file"); + if (IsDigit(v25)) + { + Sign = 1; + this->Number = v26 - 48; + } else + { + if (v26 != 45) + error("syntax error"); + Sign = -1; + this->Number = 0; + } + v1 = 16; + continue; + case 16: + v27 = getc(f); + v13 = v27; + if (v27 == -1) + error("unexpected end of file"); + if (IsDigit(v27)) + { + LABEL_51: + this->Number = v13 + 10 * this->Number - 48; + } else + { + if (v13 != 93) + error("syntax error"); + v1 = 17; + this->CoordZ = this->Number * Sign; + } + continue; + case 17: + this->Token = COORDINATE; + return; + case 22: + v28 = getc(f); + this->Special = 60; + if (v28 == -1) + goto LABEL_77; + v1 = 23; + if (v28 == 61) + continue; + v1 = 24; + if (v28 == 62) + continue; + ungetc(v28, f); + goto LABEL_77; + case 23: + this->Special = 76; + goto LABEL_77; + case 24: + this->Special = 78; + goto LABEL_77; + case 25: + v29 = getc(f); + this->Special = 62; + v20 = v29; + if (v29 == -1) + goto LABEL_77; + v1 = 26; + if (v29 != 61) + goto LABEL_83; + continue; + case 26: + this->Special = 71; + goto LABEL_77; + case 27: + v30 = getc(f); + this->Special = 45; + if (v30 == -1) + goto LABEL_77; + v1 = 28; + if (v30 == 62) + continue; + ungetc(v30, f); + goto LABEL_77; + case 28: + this->Special = 73; + goto LABEL_77; + default: + error("ScriptReader::nextToken: Ungnltiger Zustand.\n"); + goto LABEL_35; + case 30: + v31 = getc(f); + if (v31 == -1) + error("unexpected end of file"); + if (v31 != 34) + error("syntax error"); + v1 = 31; + continue; + case 31: + v32 = getc(f); + v11 = v32; + if (v32 == -1) + error("unexpected end of file"); + if (v32 != 34) + goto LABEL_39; + v1 = 32; + continue; + case 32: + open(CurrentDirectory + "/" + String); + goto LABEL_3; } } - - return true; } diff --git a/src/script.h b/src/script.h index 1a23cb3..60d646a 100644 --- a/src/script.h +++ b/src/script.h @@ -1,40 +1,277 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ +* Tibia GIMUD Server - a free and open-source MMORPG server emulator +* Copyright (C) 2019 Sabrehaven and Mark Samman +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License along +* with this program; if not, write to the Free Software Foundation, Inc., +* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ -#ifndef FS_SCRIPTS_H -#define FS_SCRIPTS_H +#ifndef FS_SCRIPT_H_2905B3D5EAB34B4BA8830167262D2DC1 +#define FS_SCRIPT_H_2905B3D5EAB34B4BA8830167262D2DC1 -#include "luascript.h" -#include "enums.h" +#include "tools.h" -class Scripts +enum TOKEN { - public: - Scripts(); - ~Scripts(); + ENDOFFILE = 0, + IDENTIFIER, + NUMBER, + STRING, + BYTES, + COORDINATE, + SPECIAL +}; - bool loadScripts(std::string folderName, bool isLib, bool reload); - LuaScriptInterface& getScriptInterface() { - return scriptInterface; +class ScriptReader +{ +public: + ScriptReader() + { + Token = ENDOFFILE; + RecursionDepth = -1; + } + + ~ScriptReader() + { + if (RecursionDepth != -1) + { + std::cout << "ScriptReader::~ScriptReader: File is still open.\n"; + for (int i = RecursionDepth; i != -1; i = RecursionDepth) + { + if (fclose(File[i])) + { + std::cout << "ScriptReader::close: Error when closing the file.\n"; + } + --RecursionDepth; + } } - private: - LuaScriptInterface scriptInterface; + } + + TOKEN Token; + FILE* File[3]; + int RecursionDepth; + char Filename[3][4096]; + std::string CurrentDirectory; + std::string String; + unsigned char Bytes[1000]; + int Line[3]; + int Number; + uint16_t CoordX; + uint16_t CoordY; + uint8_t CoordZ; + char Special; + + bool open(const std::string& FileName) + { + RecursionDepth++; + if (RecursionDepth == 3) + { + error("ScriptReader::open: too big recursion.\n"); + error("Recursion depth too high.\n"); + return false; + } + + if (RecursionDepth > -1) + { + CurrentDirectory = FileName; + if (FileName.find('/') != std::string::npos) { + int32_t end = FileName.find_last_of('/'); + CurrentDirectory = FileName.substr(0, end); + strcpy(Filename[RecursionDepth], FileName.substr(end + 1, FileName.length() - end).c_str()); + } else { + strcpy(Filename[RecursionDepth], FileName.c_str()); + } + + File[RecursionDepth] = fopen(FileName.c_str(), "rb"); + if (!File[RecursionDepth]) + { + printf("ScriptReader::open: Can not open file %s.\n", FileName.c_str()); + RecursionDepth--; + printf("Cannot open script-file\n"); + return false; + } + } + + Line[RecursionDepth] = 1; + return true; + } + + void close() + { + int depth; // eax@1 + + depth = RecursionDepth; + if (depth == -1) + { + std::cout << "ScriptReader::close: Invalid recursion depth.\n"; + } else + { + if (fclose(this->File[depth])) + { + std::cout << "ScriptReader::close: Error when closing file.\n"; + } + --RecursionDepth; + } + } + + void error(const std::string& text) + { + int depth = this->RecursionDepth; + if (depth != -1) + { + printf("error in script-file \"%s\", line %d: %s\n", Filename[this->RecursionDepth], this->Line[this->RecursionDepth], text.c_str()); + do + { + if (fclose(this->File[depth])) + { + std::cout << "ScriptReader::close: Error when closing file.\n"; + } + --this->RecursionDepth; + depth = this->RecursionDepth; + } while (this->RecursionDepth != -1); + } + } + + void nextToken(); + + std::string readIdentifier() + { + nextToken(); + if (this->Token != IDENTIFIER) + error("identifier expected"); + if (this->Token != IDENTIFIER) + error("identifier expected"); + String = asLowerCaseString(String); + return std::string(this->String); + } + + int readNumber() + { + TOKEN v1; // edx@1 + int v2; // esi@1 + + nextToken(); + v1 = this->Token; + v2 = 1; + if (this->Token == SPECIAL && this->Special == 45) + { + v2 = -1; + nextToken(); + v1 = this->Token; + } + if (v1 != NUMBER) + error("number expected"); + if (this->Token != NUMBER) + error("number expected"); + return this->Number * v2; + } + + std::string readString() + { + nextToken(); + if (this->Token != STRING) + error("string expected"); + if (this->Token != STRING) + error("string expected"); + return this->String; + } + + uint8_t* readBytesequence() + { + nextToken(); + if (this->Token != 4) + error("byte-sequence expected"); + if (this->Token != 4) + error("byte-sequence expected"); + return this->Bytes; + } + + void readCoordinate(uint16_t& x, uint16_t& y, uint8_t& z) + { + nextToken(); + if (this->Token != COORDINATE) + error("coordinates expected"); + if (this->Token != COORDINATE) + error("coordinates expected"); + x = this->CoordX; + y = this->CoordY; + z = this->CoordZ; + } + + int readSpecial() + { + nextToken(); + if (this->Token != SPECIAL) + error("special-char expected"); + if (this->Token != SPECIAL) + error("special-char expected"); + return this->Special; + } + + void readSymbol(char Symbol) + { + nextToken(); + if (this->Token != SPECIAL) + error("special-char expected"); + if (Symbol != this->Special) + error("special-char expected"); + } + + std::string getIdentifier() + { + if (this->Token != IDENTIFIER) + error("identifier expected"); + String = asLowerCaseString(String); + return this->String; + } + + int getNumber() + { + if (this->Token != NUMBER) + error("number expected"); + return this->Number; + } + + std::string getString() + { + if (this->Token != STRING) + error("string expected"); + return this->String; + } + + uint8_t* getBytesequence() + { + if (this->Token != BYTES) + error("byte-sequence expected"); + return this->Bytes; + } + + void getcoordinate(int32_t& x, int32_t& y, int32_t& z) + { + if (this->Token != COORDINATE) + error("coordinates expected"); + x = this->CoordX; + y = this->CoordY; + z = this->CoordZ; + } + + int getSpecial() + { + if (this->Token != SPECIAL) + error("special-char expected"); + return this->Special; + } }; #endif diff --git a/src/scriptmanager.cpp b/src/scriptmanager.cpp index b166da7..f372962 100644 --- a/src/scriptmanager.cpp +++ b/src/scriptmanager.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,10 +26,8 @@ #include "talkaction.h" #include "spells.h" #include "movement.h" -#include "weapons.h" #include "globalevent.h" #include "events.h" -#include "script.h" Actions* g_actions = nullptr; CreatureEvents* g_creatureEvents = nullptr; @@ -39,15 +37,12 @@ GlobalEvents* g_globalEvents = nullptr; Spells* g_spells = nullptr; TalkActions* g_talkActions = nullptr; MoveEvents* g_moveEvents = nullptr; -Weapons* g_weapons = nullptr; -Scripts* g_scripts = nullptr; extern LuaEnvironment g_luaEnvironment; ScriptingManager::~ScriptingManager() { delete g_events; - delete g_weapons; delete g_spells; delete g_actions; delete g_talkActions; @@ -55,7 +50,6 @@ ScriptingManager::~ScriptingManager() delete g_chat; delete g_creatureEvents; delete g_globalEvents; - delete g_scripts; } bool ScriptingManager::loadScriptSystems() @@ -64,23 +58,8 @@ bool ScriptingManager::loadScriptSystems() std::cout << "[Warning - ScriptingManager::loadScriptSystems] Can not load data/global.lua" << std::endl; } - g_scripts = new Scripts(); - std::cout << ">> Loading lua libs" << std::endl; - if (!g_scripts->loadScripts("scripts/lib", true, false)) { - std::cout << "> ERROR: Unable to load lua libs!" << std::endl; - return false; - } - g_chat = new Chat(); - g_weapons = new Weapons(); - if (!g_weapons->loadFromXml()) { - std::cout << "> ERROR: Unable to load weapons!" << std::endl; - return false; - } - - g_weapons->loadDefaults(); - g_spells = new Spells(); if (!g_spells->loadFromXml()) { std::cout << "> ERROR: Unable to load spells!" << std::endl; @@ -123,5 +102,6 @@ bool ScriptingManager::loadScriptSystems() return false; } + return true; } diff --git a/src/scriptmanager.h b/src/scriptmanager.h index d3b52e4..fc5da53 100644 --- a/src/scriptmanager.h +++ b/src/scriptmanager.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,9 +30,9 @@ class ScriptingManager ScriptingManager(const ScriptingManager&) = delete; ScriptingManager& operator=(const ScriptingManager&) = delete; - static ScriptingManager& getInstance() { + static ScriptingManager* getInstance() { static ScriptingManager instance; - return instance; + return &instance; } bool loadScriptSystems(); diff --git a/src/server.cpp b/src/server.cpp index 7d58986..d681456 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -56,7 +56,8 @@ void ServiceManager::stop() for (auto& servicePortIt : acceptors) { try { io_service.post(std::bind(&ServicePort::onStopServer, servicePortIt.second)); - } catch (boost::system::system_error& e) { + } + catch (boost::system::system_error& e) { std::cout << "[ServiceManager::stop] Network Error: " << e.what() << std::endl; } } @@ -114,25 +115,28 @@ void ServicePort::onAccept(Connection_ptr connection, const boost::system::error Service_ptr service = services.front(); if (service->is_single_socket()) { connection->accept(service->make_protocol(connection)); - } else { + } + else { connection->accept(); } - } else { + } + else { connection->close(Connection::FORCE_CLOSE); } accept(); - } else if (error != boost::asio::error::operation_aborted) { + } + else if (error != boost::asio::error::operation_aborted) { if (!pendingStart) { close(); pendingStart = true; g_scheduler.addEvent(createSchedulerTask(15000, - std::bind(&ServicePort::openAcceptor, std::weak_ptr(shared_from_this()), serverPort))); + std::bind(&ServicePort::openAcceptor, std::weak_ptr(shared_from_this()), serverPort))); } } } -Protocol_ptr ServicePort::make_protocol(bool checksummed, NetworkMessage& msg, const Connection_ptr& connection) const +Protocol_ptr ServicePort::make_protocol(NetworkMessage& msg, const Connection_ptr& connection) const { uint8_t protocolID = msg.getByte(); for (auto& service : services) { @@ -140,9 +144,7 @@ Protocol_ptr ServicePort::make_protocol(bool checksummed, NetworkMessage& msg, c continue; } - if ((checksummed && service->is_checksummed()) || !service->is_checksummed()) { - return service->make_protocol(connection); - } + return service->make_protocol(connection); } return nullptr; } @@ -169,21 +171,23 @@ void ServicePort::open(uint16_t port) try { if (g_config.getBoolean(ConfigManager::BIND_ONLY_GLOBAL_ADDRESS)) { acceptor.reset(new boost::asio::ip::tcp::acceptor(io_service, boost::asio::ip::tcp::endpoint( - boost::asio::ip::address(boost::asio::ip::address_v4::from_string(g_config.getString(ConfigManager::IP))), serverPort))); - } else { + boost::asio::ip::address(boost::asio::ip::address_v4::from_string(g_config.getString(ConfigManager::IP))), serverPort))); + } + else { acceptor.reset(new boost::asio::ip::tcp::acceptor(io_service, boost::asio::ip::tcp::endpoint( - boost::asio::ip::address(boost::asio::ip::address_v4(INADDR_ANY)), serverPort))); + boost::asio::ip::address(boost::asio::ip::address_v4(INADDR_ANY)), serverPort))); } acceptor->set_option(boost::asio::ip::tcp::no_delay(true)); accept(); - } catch (boost::system::system_error& e) { + } + catch (boost::system::system_error& e) { std::cout << "[ServicePort::open] Error: " << e.what() << std::endl; pendingStart = true; g_scheduler.addEvent(createSchedulerTask(15000, - std::bind(&ServicePort::openAcceptor, std::weak_ptr(shared_from_this()), port))); + std::bind(&ServicePort::openAcceptor, std::weak_ptr(shared_from_this()), port))); } } @@ -197,7 +201,7 @@ void ServicePort::close() bool ServicePort::add_service(const Service_ptr& new_svc) { - if (std::any_of(services.begin(), services.end(), [](const Service_ptr& svc) {return svc->is_single_socket();})) { + if (std::any_of(services.begin(), services.end(), [](const Service_ptr& svc) {return svc->is_single_socket(); })) { return false; } diff --git a/src/server.h b/src/server.h index 02cb373..e1d9aa2 100644 --- a/src/server.h +++ b/src/server.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,106 +21,100 @@ #define FS_SERVER_H_984DA68ABF744127850F90CC710F281B #include "connection.h" -#include "signals.h" #include class Protocol; class ServiceBase { - public: - virtual bool is_single_socket() const = 0; - virtual bool is_checksummed() const = 0; - virtual uint8_t get_protocol_identifier() const = 0; - virtual const char* get_protocol_name() const = 0; +public: + virtual bool is_single_socket() const = 0; + virtual uint8_t get_protocol_identifier() const = 0; + virtual const char* get_protocol_name() const = 0; - virtual Protocol_ptr make_protocol(const Connection_ptr& c) const = 0; + virtual Protocol_ptr make_protocol(const Connection_ptr& c) const = 0; }; template class Service final : public ServiceBase { - public: - bool is_single_socket() const override { - return ProtocolType::server_sends_first; - } - bool is_checksummed() const override { - return ProtocolType::use_checksum; - } - uint8_t get_protocol_identifier() const override { - return ProtocolType::protocol_identifier; - } - const char* get_protocol_name() const override { - return ProtocolType::protocol_name(); - } +public: + bool is_single_socket() const final { + return ProtocolType::server_sends_first; + } + uint8_t get_protocol_identifier() const final { + return ProtocolType::protocol_identifier; + } + const char* get_protocol_name() const final { + return ProtocolType::protocol_name(); + } - Protocol_ptr make_protocol(const Connection_ptr& c) const override { - return std::make_shared(c); - } + Protocol_ptr make_protocol(const Connection_ptr& c) const final { + return std::make_shared(c); + } }; class ServicePort : public std::enable_shared_from_this { - public: - explicit ServicePort(boost::asio::io_service& io_service) : io_service(io_service) {} - ~ServicePort(); +public: + explicit ServicePort(boost::asio::io_service& io_service) : io_service(io_service) {} + ~ServicePort(); - // non-copyable - ServicePort(const ServicePort&) = delete; - ServicePort& operator=(const ServicePort&) = delete; + // non-copyable + ServicePort(const ServicePort&) = delete; + ServicePort& operator=(const ServicePort&) = delete; - static void openAcceptor(std::weak_ptr weak_service, uint16_t port); - void open(uint16_t port); - void close(); - bool is_single_socket() const; - std::string get_protocol_names() const; + static void openAcceptor(std::weak_ptr weak_service, uint16_t port); + void open(uint16_t port); + void close(); + bool is_single_socket() const; + std::string get_protocol_names() const; - bool add_service(const Service_ptr& new_svc); - Protocol_ptr make_protocol(bool checksummed, NetworkMessage& msg, const Connection_ptr& connection) const; + bool add_service(const Service_ptr& new_svc); + Protocol_ptr make_protocol(NetworkMessage& msg, const Connection_ptr& connection) const; - void onStopServer(); - void onAccept(Connection_ptr connection, const boost::system::error_code& error); + void onStopServer(); + void onAccept(Connection_ptr connection, const boost::system::error_code& error); - private: - void accept(); +protected: + void accept(); - boost::asio::io_service& io_service; - std::unique_ptr acceptor; - std::vector services; + boost::asio::io_service& io_service; + std::unique_ptr acceptor; + std::vector services; - uint16_t serverPort = 0; - bool pendingStart = false; + uint16_t serverPort = 0; + bool pendingStart = false; }; class ServiceManager { - public: - ServiceManager() = default; - ~ServiceManager(); +public: + ServiceManager() = default; + ~ServiceManager(); - // non-copyable - ServiceManager(const ServiceManager&) = delete; - ServiceManager& operator=(const ServiceManager&) = delete; + // non-copyable + ServiceManager(const ServiceManager&) = delete; + ServiceManager& operator=(const ServiceManager&) = delete; - void run(); - void stop(); + void run(); + void stop(); - template - bool add(uint16_t port); + template + bool add(uint16_t port); - bool is_running() const { - return acceptors.empty() == false; - } + bool is_running() const { + return acceptors.empty() == false; + } - private: - void die(); +protected: + void die(); - std::unordered_map acceptors; + std::unordered_map acceptors; - boost::asio::io_service io_service; - Signals signals{io_service}; - boost::asio::deadline_timer death_timer { io_service }; - bool running = false; + boost::asio::io_service io_service; + boost::asio::deadline_timer death_timer{ io_service }; + bool running = false; }; template @@ -139,13 +133,14 @@ bool ServiceManager::add(uint16_t port) service_port = std::make_shared(io_service); service_port->open(port); acceptors[port] = service_port; - } else { + } + else { service_port = foundServicePort->second; if (service_port->is_single_socket() || ProtocolType::server_sends_first) { std::cout << "ERROR: " << ProtocolType::protocol_name() << - " and " << service_port->get_protocol_names() << - " cannot use the same port " << port << '.' << std::endl; + " and " << service_port->get_protocol_names() << + " cannot use the same port " << port << '.' << std::endl; return false; } } diff --git a/src/signals.cpp b/src/signals.cpp deleted file mode 100644 index 9ed5277..0000000 --- a/src/signals.cpp +++ /dev/null @@ -1,212 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "otpch.h" -#include - -#include "signals.h" -#include "tasks.h" -#include "game.h" -#include "actions.h" -#include "configmanager.h" -#include "spells.h" -#include "talkaction.h" -#include "movement.h" -#include "weapons.h" -#include "raids.h" -#include "quests.h" -#include "mounts.h" -#include "globalevent.h" -#include "monster.h" -#include "events.h" -#include "scheduler.h" -#include "databasetasks.h" - - -extern Scheduler g_scheduler; -extern DatabaseTasks g_databaseTasks; -extern Dispatcher g_dispatcher; - -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 Weapons* g_weapons; -extern Game g_game; -extern CreatureEvents* g_creatureEvents; -extern GlobalEvents* g_globalEvents; -extern Events* g_events; -extern Chat* g_chat; -extern LuaEnvironment g_luaEnvironment; - -using ErrorCode = boost::system::error_code; - -Signals::Signals(boost::asio::io_service& service) : - set(service) -{ - set.add(SIGINT); - set.add(SIGTERM); -#ifndef _WIN32 - set.add(SIGUSR1); - set.add(SIGHUP); -#else - // This must be a blocking call as Windows calls it in a new thread and terminates - // the process when the handler returns (or after 5 seconds, whichever is earlier). - // On Windows it is called in a new thread. - signal(SIGBREAK, dispatchSignalHandler); -#endif - - asyncWait(); -} - -void Signals::asyncWait() -{ - set.async_wait([this] (ErrorCode err, int signal) { - if (err) { - std::cerr << "Signal handling error: " << err.message() << std::endl; - return; - } - dispatchSignalHandler(signal); - asyncWait(); - }); -} - -// On Windows this function does not need to be signal-safe, -// as it is called in a new thread. -// https://github.com/otland/forgottenserver/pull/2473 -void Signals::dispatchSignalHandler(int signal) -{ - switch(signal) { - case SIGINT: //Shuts the server down - g_dispatcher.addTask(createTask(sigintHandler)); - break; - case SIGTERM: //Shuts the server down - g_dispatcher.addTask(createTask(sigtermHandler)); - break; -#ifndef _WIN32 - case SIGHUP: //Reload config/data - g_dispatcher.addTask(createTask(sighupHandler)); - break; - case SIGUSR1: //Saves game state - g_dispatcher.addTask(createTask(sigusr1Handler)); - break; -#else - case SIGBREAK: //Shuts the server down - g_dispatcher.addTask(createTask(sigbreakHandler)); - // hold the thread until other threads end - g_scheduler.join(); - g_databaseTasks.join(); - g_dispatcher.join(); - break; -#endif - default: - break; - } -} - -void Signals::sigbreakHandler() -{ - //Dispatcher thread - std::cout << "SIGBREAK received, shutting game server down..." << std::endl; - g_game.setGameState(GAME_STATE_SHUTDOWN); -} - -void Signals::sigtermHandler() -{ - //Dispatcher thread - std::cout << "SIGTERM received, shutting game server down..." << std::endl; - g_game.setGameState(GAME_STATE_SHUTDOWN); -} - -void Signals::sigusr1Handler() -{ - //Dispatcher thread - std::cout << "SIGUSR1 received, saving the game state..." << std::endl; - g_game.saveGameState(); -} - -void Signals::sighupHandler() -{ - //Dispatcher thread - std::cout << "SIGHUP received, reloading config files..." << std::endl; - - g_actions->reload(); - std::cout << "Reloaded actions." << std::endl; - - g_config.reload(); - std::cout << "Reloaded config." << std::endl; - - g_creatureEvents->reload(); - std::cout << "Reloaded creature scripts." << std::endl; - - g_moveEvents->reload(); - std::cout << "Reloaded movements." << std::endl; - - Npcs::reload(); - std::cout << "Reloaded npcs." << std::endl; - - g_game.raids.reload(); - g_game.raids.startup(); - std::cout << "Reloaded raids." << std::endl; - - g_spells->reload(); - std::cout << "Reloaded monsters." << std::endl; - - g_monsters.reload(); - std::cout << "Reloaded spells." << std::endl; - - g_talkActions->reload(); - std::cout << "Reloaded talk actions." << std::endl; - - Item::items.reload(); - std::cout << "Reloaded items." << std::endl; - - g_weapons->reload(); - g_weapons->loadDefaults(); - std::cout << "Reloaded weapons." << std::endl; - - g_game.quests.reload(); - std::cout << "Reloaded quests." << std::endl; - - g_game.mounts.reload(); - std::cout << "Reloaded mounts." << std::endl; - - g_globalEvents->reload(); - std::cout << "Reloaded globalevents." << std::endl; - - g_events->load(); - std::cout << "Reloaded events." << std::endl; - - g_chat->load(); - std::cout << "Reloaded chatchannels." << std::endl; - - g_luaEnvironment.loadFile("data/global.lua"); - std::cout << "Reloaded global.lua." << std::endl; - - lua_gc(g_luaEnvironment.getLuaState(), LUA_GCCOLLECT, 0); -} - -void Signals::sigintHandler() -{ - //Dispatcher thread - std::cout << "SIGINT received, shutting game server down..." << std::endl; - g_game.setGameState(GAME_STATE_SHUTDOWN); -} diff --git a/src/signals.h b/src/signals.h deleted file mode 100644 index 5306852..0000000 --- a/src/signals.h +++ /dev/null @@ -1,42 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_SIGNALHANDLINGTHREAD_H_01C6BF08B0EFE9E200175D108CF0B35F -#define FS_SIGNALHANDLINGTHREAD_H_01C6BF08B0EFE9E200175D108CF0B35F - -#include - -class Signals -{ - boost::asio::signal_set set; - public: - explicit Signals(boost::asio::io_service& service); - - private: - void asyncWait(); - static void dispatchSignalHandler(int signal); - - static void sigbreakHandler(); - static void sigintHandler(); - static void sighupHandler(); - static void sigtermHandler(); - static void sigusr1Handler(); -}; - -#endif diff --git a/src/spawn.cpp b/src/spawn.cpp index 446d4e0..8b08492 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -64,9 +64,6 @@ bool Spawns::loadFromXml(const std::string& filename) radius = -1; } - spawnList.emplace_front(centerPos, radius); - Spawn& spawn = spawnList.front(); - for (auto childNode : spawnNode.children()) { if (strcasecmp(childNode.name(), "monster") == 0) { pugi::xml_attribute nameAttribute = childNode.attribute("name"); @@ -88,9 +85,18 @@ bool Spawns::loadFromXml(const std::string& filename) centerPos.y + pugi::cast(childNode.attribute("y").value()), centerPos.z ); + + spawnList.emplace_front(pos, radius); + Spawn& spawn = spawnList.front(); + uint32_t interval = pugi::cast(childNode.attribute("spawntime").value()) * 1000; if (interval > MINSPAWN_INTERVAL) { - spawn.addMonster(nameAttribute.as_string(), pos, dir, interval); + uint32_t exInterval = g_config.getNumber(ConfigManager::RATE_SPAWN); + if (exInterval) { + spawn.addMonster(nameAttribute.as_string(), pos, dir, exInterval * 1000); + } else { + spawn.addMonster(nameAttribute.as_string(), pos, dir, interval); + } } else { std::cout << "[Warning - Spawns::loadFromXml] " << nameAttribute.as_string() << ' ' << pos << " spawntime can not be less than " << MINSPAWN_INTERVAL / 1000 << " seconds." << std::endl; } @@ -180,9 +186,9 @@ Spawn::~Spawn() bool Spawn::findPlayer(const Position& pos) { - SpectatorVec spectators; - g_game.map.getSpectators(spectators, pos, false, true); - for (Creature* spectator : spectators) { + SpectatorVec list; + g_game.map.getSpectators(list, pos, false, true); + for (Creature* spectator : list) { if (!spectator->getPlayer()->hasFlag(PlayerFlag_IgnoredByMonsters)) { return true; } @@ -220,6 +226,26 @@ bool Spawn::spawnMonster(uint32_t spawnId, MonsterType* mType, const Position& p return true; } +uint32_t Spawn::getInterval() const +{ + uint32_t newInterval = interval; + + if (newInterval > 500000) { + size_t playersOnline = g_game.getPlayersOnline(); + if (playersOnline <= 800) { + if (playersOnline > 200) { + newInterval = 200 * interval / (playersOnline / 2 + 100); + } + } else { + newInterval = 2 * interval / 5; + } + + return normal_random(newInterval / 2, newInterval); + } + + return newInterval; +} + void Spawn::startup() { for (const auto& it : spawnMap) { @@ -235,8 +261,6 @@ void Spawn::checkSpawn() cleanup(); - uint32_t spawnCount = 0; - for (auto& it : spawnMap) { uint32_t spawnId = it.first; if (spawnedMap.find(spawnId) != spawnedMap.end()) { @@ -251,9 +275,6 @@ void Spawn::checkSpawn() } spawnMonster(spawnId, sb.mType, sb.pos, sb.direction); - if (++spawnCount >= static_cast(g_config.getNumber(ConfigManager::RATE_SPAWN))) { - break; - } } } @@ -275,9 +296,6 @@ void Spawn::cleanup() monster->decrementReferenceCounter(); it = spawnedMap.erase(it); - } else if (!isInSpawnZone(monster->getPosition()) && spawnId != 0) { - spawnedMap.insert(spawned_pair(0, monster)); - it = spawnedMap.erase(it); } else { ++it; } diff --git a/src/spawn.h b/src/spawn.h index 15657e6..aece74e 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -48,9 +48,7 @@ class Spawn bool addMonster(const std::string& name, const Position& pos, Direction dir, uint32_t interval); void removeMonster(Monster* monster); - uint32_t getInterval() const { - return interval; - } + uint32_t getInterval() const; void startup(); void startSpawnCheck(); @@ -61,8 +59,8 @@ class Spawn private: //map of the spawned creatures - using SpawnedMap = std::multimap; - using spawned_pair = SpawnedMap::value_type; + typedef std::multimap SpawnedMap; + typedef SpawnedMap::value_type spawned_pair; SpawnedMap spawnedMap; //map of creatures in the spawn diff --git a/src/spectators.h b/src/spectators.h deleted file mode 100644 index 990d957..0000000 --- a/src/spectators.h +++ /dev/null @@ -1,83 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_SPECTATORS_H_D78A7CCB7080406E8CAA6B1D31D3DA71 -#define FS_SPECTATORS_H_D78A7CCB7080406E8CAA6B1D31D3DA71 - -#include - -class Creature; - -class SpectatorVec -{ - using Vec = std::vector; - using Iterator = Vec::iterator; - using ConstIterator = Vec::const_iterator; -public: - SpectatorVec() { - vec.reserve(32); - } - - void addSpectators(const SpectatorVec& spectators) { - const size_t size = vec.size(); - for (Creature* spectator : spectators.vec) { - bool duplicate = false; - for (size_t i = 0; i < size; ++i) { - if (vec[i] == spectator) { - duplicate = true; - break; - } - } - - if (!duplicate) { - vec.emplace_back(spectator); - } - } - } - - void erase(Creature* spectator) { - for (size_t i = 0, len = vec.size(); i < len; i++) { - if (vec[i] == spectator) { - Creature* tmp = vec[len - 1]; - vec[len - 1] = vec[i]; - vec[i] = tmp; - vec.pop_back(); - break; - } - } - } - - inline size_t size() const { return vec.size(); } - inline bool empty() const { return vec.empty(); } - inline Iterator begin() { return vec.begin(); } - inline ConstIterator begin() const { return vec.begin(); } - inline ConstIterator cbegin() const { return vec.cbegin(); } - inline Iterator end() { return vec.end(); } - inline ConstIterator end() const { return vec.end(); } - inline ConstIterator cend() const { return vec.cend(); } - inline void emplace_back(Creature* c) { return vec.emplace_back(c); } - - template - inline void insert(Iterator pos, InputIterator first, InputIterator last) { vec.insert(pos, first, last); } - -private: - Vec vec; -}; - -#endif diff --git a/src/spells.cpp b/src/spells.cpp index 36b79e0..03500da 100644 --- a/src/spells.cpp +++ b/src/spells.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,7 +40,7 @@ Spells::Spells() Spells::~Spells() { - clear(false); + clear(); } TalkActionResult_t Spells::playerSaySpell(Player* player, std::string& words) @@ -50,6 +50,15 @@ TalkActionResult_t Spells::playerSaySpell(Player* player, std::string& words) //strip trailing spaces trimString(str_words); + std::ostringstream str_instantSpell; + for (size_t i = 0; i < str_words.length(); i++) { + if (!isspace(str_words[i]) || (i < str_words.length() - 1 && !isspace(str_words[i + 1]))) { + str_instantSpell << str_words[i]; + } + } + + str_words = str_instantSpell.str(); + InstantSpell* instantSpell = getInstantSpell(str_words); if (!instantSpell) { return TALKACTION_CONTINUE; @@ -85,42 +94,25 @@ TalkActionResult_t Spells::playerSaySpell(Player* player, std::string& words) } if (instantSpell->playerCastInstant(player, param)) { - words = instantSpell->getWords(); - - if (instantSpell->getHasParam() && !param.empty()) { - words += " \"" + param + "\""; - } - return TALKACTION_BREAK; } return TALKACTION_FAILED; } -void Spells::clearMaps(bool fromLua) +void Spells::clear() { - for (auto instant = instants.begin(); instant != instants.end(); ) { - if (fromLua == instant->second.fromLua) { - instant = instants.erase(instant); - } else { - ++instant; - } + for (const auto& it : runes) { + delete it.second; } + runes.clear(); - for (auto rune = runes.begin(); rune != runes.end(); ) { - if (fromLua == rune->second.fromLua) { - rune = runes.erase(rune); - } else { - ++rune; - } + for (const auto& it : instants) { + delete it.second; } -} + instants.clear(); -void Spells::clear(bool fromLua) -{ - clearMaps(fromLua); - - reInitState(fromLua); + scriptInterface.reInitState(); } LuaScriptInterface& Spells::getScriptInterface() @@ -133,30 +125,32 @@ std::string Spells::getScriptBaseName() const return "spells"; } -Event_ptr Spells::getEvent(const std::string& nodeName) +Event* Spells::getEvent(const std::string& nodeName) { if (strcasecmp(nodeName.c_str(), "rune") == 0) { - return Event_ptr(new RuneSpell(&scriptInterface)); + return new RuneSpell(&scriptInterface); } else if (strcasecmp(nodeName.c_str(), "instant") == 0) { - return Event_ptr(new InstantSpell(&scriptInterface)); + return new InstantSpell(&scriptInterface); + } else if (strcasecmp(nodeName.c_str(), "conjure") == 0) { + return new ConjureSpell(&scriptInterface); } return nullptr; } -bool Spells::registerEvent(Event_ptr event, const pugi::xml_node&) +bool Spells::registerEvent(Event* event, const pugi::xml_node&) { - InstantSpell* instant = dynamic_cast(event.get()); + InstantSpell* instant = dynamic_cast(event); if (instant) { - auto result = instants.emplace(instant->getWords(), std::move(*instant)); + auto result = instants.emplace(instant->getWords(), instant); if (!result.second) { std::cout << "[Warning - Spells::registerEvent] Duplicate registered instant spell with words: " << instant->getWords() << std::endl; } return result.second; } - RuneSpell* rune = dynamic_cast(event.get()); + RuneSpell* rune = dynamic_cast(event); if (rune) { - auto result = runes.emplace(rune->getRuneItemId(), std::move(*rune)); + auto result = runes.emplace(rune->getRuneItemId(), rune); if (!result.second) { std::cout << "[Warning - Spells::registerEvent] Duplicate registered rune with id: " << rune->getRuneItemId() << std::endl; } @@ -166,36 +160,6 @@ bool Spells::registerEvent(Event_ptr event, const pugi::xml_node&) return false; } -bool Spells::registerInstantLuaEvent(InstantSpell* event) -{ - InstantSpell_ptr instant { event }; - if (instant) { - std::string words = instant->getWords(); - auto result = instants.emplace(instant->getWords(), std::move(*instant)); - if (!result.second) { - std::cout << "[Warning - Spells::registerInstantLuaEvent] Duplicate registered instant spell with words: " << words << std::endl; - } - return result.second; - } - - return false; -} - -bool Spells::registerRuneLuaEvent(RuneSpell* event) -{ - RuneSpell_ptr rune { event }; - if (rune) { - uint16_t id = rune->getRuneItemId(); - auto result = runes.emplace(rune->getRuneItemId(), std::move(*rune)); - if (!result.second) { - std::cout << "[Warning - Spells::registerRuneLuaEvent] Duplicate registered rune with id: " << id << std::endl; - } - return result.second; - } - - return false; -} - Spell* Spells::getSpellByName(const std::string& name) { Spell* spell = getRuneSpellByName(name); @@ -209,21 +173,16 @@ RuneSpell* Spells::getRuneSpell(uint32_t id) { auto it = runes.find(id); if (it == runes.end()) { - for (auto& rune : runes) { - if (rune.second.getId() == id) { - return &rune.second; - } - } return nullptr; } - return &it->second; + return it->second; } RuneSpell* Spells::getRuneSpellByName(const std::string& name) { - for (auto& it : runes) { - if (strcasecmp(it.second.getName().c_str(), name.c_str()) == 0) { - return &it.second; + for (const auto& it : runes) { + if (strcasecmp(it.second->getName().c_str(), name.c_str()) == 0) { + return it.second; } } return nullptr; @@ -233,12 +192,14 @@ InstantSpell* Spells::getInstantSpell(const std::string& words) { InstantSpell* result = nullptr; - for (auto& it : instants) { - const std::string& instantSpellWords = it.second.getWords(); + for (const auto& it : instants) { + InstantSpell* instantSpell = it.second; + + const std::string& instantSpellWords = instantSpell->getWords(); size_t spellLen = instantSpellWords.length(); if (strncasecmp(instantSpellWords.c_str(), words.c_str(), spellLen) == 0) { if (!result || spellLen > result->getWords().length()) { - result = &it.second; + result = instantSpell; if (words.length() == spellLen) { break; } @@ -249,26 +210,41 @@ InstantSpell* Spells::getInstantSpell(const std::string& words) if (result) { const std::string& resultWords = result->getWords(); if (words.length() > resultWords.length()) { - if (!result->getHasParam()) { - return nullptr; - } - size_t spellLen = resultWords.length(); size_t paramLen = words.length() - spellLen; if (paramLen < 2 || words[spellLen] != ' ') { return nullptr; } } + return result; } + return nullptr; } -InstantSpell* Spells::getInstantSpellById(uint32_t spellId) +uint32_t Spells::getInstantSpellCount(const Player* player) const { - for (auto& it : instants) { - if (it.second.getId() == spellId) { - return &it.second; + uint32_t count = 0; + for (const auto& it : instants) { + InstantSpell* instantSpell = it.second; + if (instantSpell->canCast(player)) { + ++count; + } + } + return count; +} + +InstantSpell* Spells::getInstantSpellByIndex(const Player* player, uint32_t index) +{ + uint32_t count = 0; + for (const auto& it : instants) { + InstantSpell* instantSpell = it.second; + if (instantSpell->canCast(player)) { + if (count == index) { + return instantSpell; + } + ++count; } } return nullptr; @@ -276,9 +252,9 @@ InstantSpell* Spells::getInstantSpellById(uint32_t spellId) InstantSpell* Spells::getInstantSpellByName(const std::string& name) { - for (auto& it : instants) { - if (strcasecmp(it.second.getName().c_str(), name.c_str()) == 0) { - return &it.second; + for (const auto& it : instants) { + if (strcasecmp(it.second->getName().c_str(), name.c_str()) == 0) { + return it.second; } } return nullptr; @@ -402,7 +378,7 @@ bool Spell::configureSpell(const pugi::xml_node& node) name = nameAttribute.as_string(); - static const char* reservedList[] = { + /*static const char* reservedList[] = { "melee", "physical", "poison", @@ -423,9 +399,6 @@ bool Spell::configureSpell(const pugi::xml_node& node) "poisoncondition", "energycondition", "drowncondition", - "freezecondition", - "cursecondition", - "dazzlecondition" }; //static size_t size = sizeof(reservedList) / sizeof(const char*); @@ -435,60 +408,18 @@ bool Spell::configureSpell(const pugi::xml_node& node) std::cout << "[Error - Spell::configureSpell] Spell is using a reserved name: " << reserved << std::endl; return false; } - } + }*/ pugi::xml_attribute attr; if ((attr = node.attribute("spellid"))) { spellId = pugi::cast(attr.value()); } - if ((attr = node.attribute("group"))) { - std::string tmpStr = asLowerCaseString(attr.as_string()); - if (tmpStr == "none" || tmpStr == "0") { - group = SPELLGROUP_NONE; - } else if (tmpStr == "attack" || tmpStr == "1") { - group = SPELLGROUP_ATTACK; - } else if (tmpStr == "healing" || tmpStr == "2") { - group = SPELLGROUP_HEALING; - } else if (tmpStr == "support" || tmpStr == "3") { - group = SPELLGROUP_SUPPORT; - } else if (tmpStr == "special" || tmpStr == "4") { - group = SPELLGROUP_SPECIAL; - } else { - std::cout << "[Warning - Spell::configureSpell] Unknown group: " << attr.as_string() << std::endl; - } - } - - if ((attr = node.attribute("groupcooldown"))) { - groupCooldown = pugi::cast(attr.value()); - } - - if ((attr = node.attribute("secondarygroup"))) { - std::string tmpStr = asLowerCaseString(attr.as_string()); - if (tmpStr == "none" || tmpStr == "0") { - secondaryGroup = SPELLGROUP_NONE; - } else if (tmpStr == "attack" || tmpStr == "1") { - secondaryGroup = SPELLGROUP_ATTACK; - } else if (tmpStr == "healing" || tmpStr == "2") { - secondaryGroup = SPELLGROUP_HEALING; - } else if (tmpStr == "support" || tmpStr == "3") { - secondaryGroup = SPELLGROUP_SUPPORT; - } else if (tmpStr == "special" || tmpStr == "4") { - secondaryGroup = SPELLGROUP_SPECIAL; - } else { - std::cout << "[Warning - Spell::configureSpell] Unknown secondarygroup: " << attr.as_string() << std::endl; - } - } - - if ((attr = node.attribute("secondarygroupcooldown"))) { - secondaryGroupCooldown = pugi::cast(attr.value()); - } - - if ((attr = node.attribute("level")) || (attr = node.attribute("lvl"))) { + if ((attr = node.attribute("lvl"))) { level = pugi::cast(attr.value()); } - if ((attr = node.attribute("magiclevel")) || (attr = node.attribute("maglv"))) { + if ((attr = node.attribute("maglv"))) { magLevel = pugi::cast(attr.value()); } @@ -508,11 +439,11 @@ bool Spell::configureSpell(const pugi::xml_node& node) range = pugi::cast(attr.value()); } - if ((attr = node.attribute("cooldown")) || (attr = node.attribute("exhaustion"))) { + if ((attr = node.attribute("exhaustion")) || (attr = node.attribute("cooldown"))) { cooldown = pugi::cast(attr.value()); } - if ((attr = node.attribute("premium")) || (attr = node.attribute("prem"))) { + if ((attr = node.attribute("prem"))) { premium = attr.as_bool(); } @@ -559,10 +490,6 @@ bool Spell::configureSpell(const pugi::xml_node& node) aggressive = booleanString(attr.as_string()); } - if (group == SPELLGROUP_NONE) { - group = (aggressive ? SPELLGROUP_ATTACK : SPELLGROUP_HEALING); - } - for (auto vocationNode : node.children()) { if (!(attr = vocationNode.attribute("name"))) { continue; @@ -593,23 +520,12 @@ bool Spell::playerSpellCheck(Player* player) const return false; } - if (aggressive && (range < 1 || (range > 0 && !player->getAttackedCreature())) && player->getSkull() == SKULL_BLACK) { - player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); - return false; - } - - if (aggressive && player->hasCondition(CONDITION_PACIFIED)) { - player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED); - g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); - return false; - } - - if (aggressive && !player->hasFlag(PlayerFlag_IgnoreProtectionZone) && player->getZone() == ZONE_PROTECTION) { + if (aggressive && !player->hasFlag(PlayerFlag_IgnoreProtectionZone) && player->getZone() == ZONE_PROTECTION) { player->sendCancelMessage(RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE); return false; } - if (player->hasCondition(CONDITION_SPELLGROUPCOOLDOWN, group) || player->hasCondition(CONDITION_SPELLCOOLDOWN, spellId) || (secondaryGroup != SPELLGROUP_NONE && player->hasCondition(CONDITION_SPELLGROUPCOOLDOWN, secondaryGroup))) { + if (player->hasCondition(CONDITION_EXHAUST)) { player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED); if (isInstant()) { @@ -656,7 +572,14 @@ bool Spell::playerSpellCheck(Player* player) const } if (needWeapon) { - switch (player->getWeaponType()) { + Item* weapon = player->getWeapon(); + if (!weapon) { + player->sendCancelMessage(RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + switch (weapon->getWeaponType()) { case WEAPON_SWORD: case WEAPON_CLUB: case WEAPON_AXE: @@ -765,31 +688,33 @@ bool Spell::playerRuneSpellCheck(Player* player, const Position& toPos) return false; } - const Creature* topVisibleCreature = tile->getBottomVisibleCreature(player); - if (blockingCreature && topVisibleCreature) { + const Creature* visibleCreature = tile->getTopCreature(); + if (blockingCreature && visibleCreature) { player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; - } else if (blockingSolid && tile->hasFlag(TILESTATE_BLOCKSOLID)) { + } + else if (blockingSolid && tile->hasFlag(TILESTATE_BLOCKSOLID)) { player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } - if (needTarget && !topVisibleCreature) { + if (needTarget && !visibleCreature) { player->sendCancelMessage(RETURNVALUE_CANONLYUSETHISRUNEONCREATURES); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } - if (aggressive && needTarget && topVisibleCreature && player->hasSecureMode()) { - const Player* targetPlayer = topVisibleCreature->getPlayer(); + if (aggressive && needTarget && visibleCreature && player->hasSecureMode()) { + const Player* targetPlayer = visibleCreature->getPlayer(); if (targetPlayer && targetPlayer != player && player->getSkullClient(targetPlayer) == SKULL_NONE && !Combat::isInPvpZone(player, targetPlayer)) { player->sendCancelMessage(RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } } + return true; } @@ -797,19 +722,22 @@ void Spell::postCastSpell(Player* player, bool finishedCast /*= true*/, bool pay { if (finishedCast) { if (!player->hasFlag(PlayerFlag_HasNoExhaustion)) { - if (cooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, cooldown, 0, false, spellId); - player->addCondition(condition); - } - - if (groupCooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, groupCooldown, 0, false, group); - player->addCondition(condition); - } - - if (secondaryGroupCooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, secondaryGroupCooldown, 0, false, secondaryGroup); - player->addCondition(condition); + if (aggressive) { + if (cooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST, cooldown); + player->addCondition(condition); + } else { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST, 2000); + player->addCondition(condition); + } + } else { + if (cooldown > 0) { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST, cooldown); + player->addCondition(condition); + } else { + Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_EXHAUST, 1000); + player->addCondition(condition); + } } } @@ -844,14 +772,50 @@ uint32_t Spell::getManaCost(const Player* player) const } if (manaPercent != 0) { - uint32_t maxMana = player->getMaxMana(); - uint32_t manaCost = (maxMana * manaPercent) / 100; - return manaCost; + return player->getLevel() * manaPercent; } return 0; } +ReturnValue Spell::CreateIllusion(Creature* creature, const Outfit_t& outfit, int32_t time) +{ + ConditionOutfit* outfitCondition = new ConditionOutfit(CONDITIONID_COMBAT, CONDITION_OUTFIT, time); + outfitCondition->setOutfit(outfit); + creature->addCondition(outfitCondition); + return RETURNVALUE_NOERROR; +} + +ReturnValue Spell::CreateIllusion(Creature* creature, const std::string& name, int32_t time) +{ + const auto mType = g_monsters.getMonsterType(name); + if (mType == nullptr) { + return RETURNVALUE_CREATUREDOESNOTEXIST; + } + + Player* player = creature->getPlayer(); + if (player && !player->hasFlag(PlayerFlag_CanIllusionAll)) { + if (!mType->info.isIllusionable) { + return RETURNVALUE_NOTPOSSIBLE; + } + } + + return CreateIllusion(creature, mType->info.outfit, time); +} + +ReturnValue Spell::CreateIllusion(Creature* creature, uint32_t itemId, int32_t time) +{ + const ItemType& it = Item::items[itemId]; + if (it.id == 0) { + return RETURNVALUE_NOTPOSSIBLE; + } + + Outfit_t outfit; + outfit.lookTypeEx = itemId; + + return CreateIllusion(creature, outfit, time); +} + std::string InstantSpell::getScriptEventName() const { return "onCastSpell"; @@ -867,8 +831,6 @@ bool InstantSpell::configureEvent(const pugi::xml_node& node) return false; } - spellType = SPELL_INSTANT; - pugi::xml_attribute attr; if ((attr = node.attribute("params"))) { hasParam = attr.as_bool(); @@ -890,6 +852,34 @@ bool InstantSpell::configureEvent(const pugi::xml_node& node) return true; } +bool InstantSpell::loadFunction(const pugi::xml_attribute& attr) +{ + const char* functionName = attr.as_string(); + if (strcasecmp(functionName, "edithouseguest") == 0) { + function = HouseGuestList; + } else if (strcasecmp(functionName, "edithousesubowner") == 0) { + function = HouseSubOwnerList; + } else if (strcasecmp(functionName, "edithousedoor") == 0) { + function = HouseDoorList; + } else if (strcasecmp(functionName, "housekick") == 0) { + function = HouseKick; + } else if (strcasecmp(functionName, "searchplayer") == 0) { + function = SearchPlayer; + } else if (strcasecmp(functionName, "levitate") == 0) { + function = Levitate; + } else if (strcasecmp(functionName, "illusion") == 0) { + function = Illusion; + } else if (strcasecmp(functionName, "summonmonster") == 0) { + function = SummonMonster; + } else { + std::cout << "[Warning - InstantSpell::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; + return false; + } + + scripted = false; + return true; +} + bool InstantSpell::playerCastInstant(Player* player, std::string& param) { if (!playerSpellCheck(player)) { @@ -916,21 +906,6 @@ bool InstantSpell::playerCastInstant(Player* player, std::string& param) target = playerTarget; if (!target || target->getHealth() <= 0) { if (!casterTargetOrDirection) { - if (cooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, cooldown, 0, false, spellId); - player->addCondition(condition); - } - - if (groupCooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, groupCooldown, 0, false, group); - player->addCondition(condition); - } - - if (secondaryGroupCooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, secondaryGroupCooldown, 0, false, secondaryGroup); - player->addCondition(condition); - } - player->sendCancelMessage(ret); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; @@ -980,21 +955,6 @@ bool InstantSpell::playerCastInstant(Player* player, std::string& param) ReturnValue ret = g_game.getPlayerByNameWildcard(param, playerTarget); if (ret != RETURNVALUE_NOERROR) { - if (cooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, cooldown, 0, false, spellId); - player->addCondition(condition); - } - - if (groupCooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, groupCooldown, 0, false, group); - player->addCondition(condition); - } - - if (secondaryGroupCooldown > 0) { - Condition* condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLGROUPCOOLDOWN, secondaryGroupCooldown, 0, false, secondaryGroup); - player->addCondition(condition); - } - player->sendCancelMessage(ret); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; @@ -1082,7 +1042,13 @@ bool InstantSpell::castSpell(Creature* creature, Creature* target) bool InstantSpell::internalCastSpell(Creature* creature, const LuaVariant& var) { - return executeCastSpell(creature, var); + if (scripted) { + return executeCastSpell(creature, var); + } else if (function) { + return function(this, creature, var.text); + } + + return false; } bool InstantSpell::executeCastSpell(Creature* creature, const LuaVariant& var) @@ -1108,6 +1074,409 @@ bool InstantSpell::executeCastSpell(Creature* creature, const LuaVariant& var) return scriptInterface->callFunction(2); } +House* InstantSpell::getHouseFromPos(Creature* creature) +{ + if (!creature) { + return nullptr; + } + + Player* player = creature->getPlayer(); + if (!player) { + return nullptr; + } + + HouseTile* houseTile = dynamic_cast(player->getTile()); + if (!houseTile) { + return nullptr; + } + + House* house = houseTile->getHouse(); + if (!house) { + return nullptr; + } + + return house; +} + +bool InstantSpell::HouseGuestList(const InstantSpell*, Creature* creature, const std::string&) +{ + House* house = getHouseFromPos(creature); + if (!house) { + return false; + } + + Player* player = creature->getPlayer(); + if (house->canEditAccessList(GUEST_LIST, player)) { + player->setEditHouse(house, GUEST_LIST); + player->sendHouseWindow(house, GUEST_LIST); + } else { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + } + return true; +} + +bool InstantSpell::HouseSubOwnerList(const InstantSpell*, Creature* creature, const std::string&) +{ + House* house = getHouseFromPos(creature); + if (!house) { + return false; + } + + Player* player = creature->getPlayer(); + if (house->canEditAccessList(SUBOWNER_LIST, player)) { + player->setEditHouse(house, SUBOWNER_LIST); + player->sendHouseWindow(house, SUBOWNER_LIST); + } else { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + } + return true; +} + +bool InstantSpell::HouseDoorList(const InstantSpell*, Creature* creature, const std::string&) +{ + House* house = getHouseFromPos(creature); + if (!house) { + return false; + } + + Player* player = creature->getPlayer(); + Position pos = Spells::getCasterPosition(player, player->getDirection()); + Door* door = house->getDoorByPosition(pos); + if (door && house->canEditAccessList(door->getDoorId(), player)) { + player->setEditHouse(house, door->getDoorId()); + player->sendHouseWindow(house, door->getDoorId()); + } else { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + } + return true; +} + +bool InstantSpell::HouseKick(const InstantSpell*, Creature* creature, const std::string& param) +{ + Player* player = creature->getPlayer(); + + Player* targetPlayer = g_game.getPlayerByName(param); + if (!targetPlayer) { + targetPlayer = player; + } + + House* house = getHouseFromPos(targetPlayer); + if (!house) { + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return false; + } + + if (!house->kickPlayer(player, targetPlayer)) { + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + return false; + } + return true; +} + +bool InstantSpell::SearchPlayer(const InstantSpell*, Creature* creature, const std::string& param) +{ + //a. From 1 to 4 sq's [Person] is standing next to you. + //b. From 5 to 100 sq's [Person] is to the south, north, east, west. + //c. From 101 to 274 sq's [Person] is far to the south, north, east, west. + //d. From 275 to infinite sq's [Person] is very far to the south, north, east, west. + //e. South-west, s-e, n-w, n-e (corner coordinates): this phrase appears if the player you're looking for has moved five squares in any direction from the south, north, east or west. + //f. Lower level to the (direction): this phrase applies if the person you're looking for is from 1-25 squares up/down the actual floor you're in. + //g. Higher level to the (direction): this phrase applies if the person you're looking for is from 1-25 squares up/down the actual floor you're in. + + Player* player = creature->getPlayer(); + if (!player) { + return false; + } + + enum distance_t { + DISTANCE_BESIDE, + DISTANCE_CLOSE, + DISTANCE_FAR, + DISTANCE_VERYFAR, + }; + + enum direction_t { + DIR_N, DIR_S, DIR_E, DIR_W, + DIR_NE, DIR_NW, DIR_SE, DIR_SW, + }; + + enum level_t { + LEVEL_HIGHER, + LEVEL_LOWER, + LEVEL_SAME, + }; + + Player* playerExiva = g_game.getPlayerByName(param); + if (!playerExiva) { + return false; + } + + if (playerExiva->isAccessPlayer() && !player->isAccessPlayer()) { + player->sendCancelMessage(RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + const Position& lookPos = player->getPosition(); + const Position& searchPos = playerExiva->getPosition(); + + int32_t dx = Position::getOffsetX(lookPos, searchPos); + int32_t dy = Position::getOffsetY(lookPos, searchPos); + int32_t dz = Position::getOffsetZ(lookPos, searchPos); + + distance_t distance; + + direction_t direction; + + level_t level; + + //getting floor + if (dz > 0) { + level = LEVEL_HIGHER; + } else if (dz < 0) { + level = LEVEL_LOWER; + } else { + level = LEVEL_SAME; + } + + //getting distance + if (std::abs(dx) < 4 && std::abs(dy) < 4) { + distance = DISTANCE_BESIDE; + } else { + int32_t distance2 = dx * dx + dy * dy; + if (distance2 < 10000) { + distance = DISTANCE_CLOSE; + } else if (distance2 < 75076) { + distance = DISTANCE_FAR; + } else { + distance = DISTANCE_VERYFAR; + } + } + + //getting direction + float tan; + if (dx != 0) { + tan = static_cast(dy) / dx; + } else { + tan = 10.; + } + + if (std::abs(tan) < 0.4142) { + if (dx > 0) { + direction = DIR_W; + } else { + direction = DIR_E; + } + } else if (std::abs(tan) < 2.4142) { + if (tan > 0) { + if (dy > 0) { + direction = DIR_NW; + } else { + direction = DIR_SE; + } + } else { + if (dx > 0) { + direction = DIR_SW; + } else { + direction = DIR_NE; + } + } + } else { + if (dy > 0) { + direction = DIR_N; + } else { + direction = DIR_S; + } + } + + std::ostringstream ss; + ss << playerExiva->getName(); + + if (distance == DISTANCE_BESIDE) { + if (level == LEVEL_SAME) { + ss << " is standing next to you."; + } else if (level == LEVEL_HIGHER) { + ss << " is above you."; + } else if (level == LEVEL_LOWER) { + ss << " is below you."; + } + } else { + switch (distance) { + case DISTANCE_CLOSE: + if (level == LEVEL_SAME) { + ss << " is to the "; + } else if (level == LEVEL_HIGHER) { + ss << " is on a higher level to the "; + } else if (level == LEVEL_LOWER) { + ss << " is on a lower level to the "; + } + break; + case DISTANCE_FAR: + ss << " is far to the "; + break; + case DISTANCE_VERYFAR: + ss << " is very far to the "; + break; + default: + break; + } + + switch (direction) { + case DIR_N: + ss << "north."; + break; + case DIR_S: + ss << "south."; + break; + case DIR_E: + ss << "east."; + break; + case DIR_W: + ss << "west."; + break; + case DIR_NE: + ss << "north-east."; + break; + case DIR_NW: + ss << "north-west."; + break; + case DIR_SE: + ss << "south-east."; + break; + case DIR_SW: + ss << "south-west."; + break; + } + } + player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); + g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_BLUE); + return true; +} + +bool InstantSpell::SummonMonster(const InstantSpell* spell, Creature* creature, const std::string& param) +{ + Player* player = creature->getPlayer(); + if (!player) { + return false; + } + + MonsterType* mType = g_monsters.getMonsterType(param); + if (!mType) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (!player->hasFlag(PlayerFlag_CanSummonAll)) { + if (!mType->info.isSummonable) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (player->getMana() < mType->info.manaCost) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHMANA); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (player->getSummonCount() >= 2) { + player->sendCancelMessage("You cannot summon more creatures."); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + } + + Monster* monster = Monster::createMonster(param); + if (!monster) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + // Place the monster + creature->addSummon(monster); + + if (!g_game.placeCreature(monster, creature->getPosition(), true)) { + creature->removeSummon(monster); + player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + Spell::postCastSpell(player, mType->info.manaCost, spell->getSoulCost()); + g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_BLUE); + g_game.addMagicEffect(monster->getPosition(), CONST_ME_TELEPORT); + return true; +} + +bool InstantSpell::Levitate(const InstantSpell*, Creature* creature, const std::string& param) +{ + Player* player = creature->getPlayer(); + if (!player) { + return false; + } + + const Position& currentPos = creature->getPosition(); + const Position& destPos = Spells::getCasterPosition(creature, creature->getDirection()); + + ReturnValue ret = RETURNVALUE_NOTPOSSIBLE; + + if (strcasecmp(param.c_str(), "up") == 0) { + if (currentPos.z != 8) { + Tile* tmpTile = g_game.map.getTile(currentPos.x, currentPos.y, currentPos.getZ() - 1); + if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID))) { + tmpTile = g_game.map.getTile(destPos.x, destPos.y, destPos.getZ() - 1); + if (tmpTile && tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID)) { + ret = g_game.internalMoveCreature(*player, *tmpTile, FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE); + } + } + } + } else if (strcasecmp(param.c_str(), "down") == 0) { + if (currentPos.z != 7) { + Tile* tmpTile = g_game.map.getTile(destPos); + if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) { + tmpTile = g_game.map.getTile(destPos.x, destPos.y, destPos.z + 1); + if (tmpTile && tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID)) { + ret = g_game.internalMoveCreature(*player, *tmpTile, FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE); + } + } + } + } + + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + g_game.addMagicEffect(player->getPosition(), CONST_ME_TELEPORT); + return true; +} + +bool InstantSpell::Illusion(const InstantSpell*, Creature* creature, const std::string& param) +{ + Player* player = creature->getPlayer(); + if (!player) { + return false; + } + + ReturnValue ret = CreateIllusion(creature, param, 180000); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED); + return true; +} + bool InstantSpell::canCast(const Player* player) const { if (player->hasFlag(PlayerFlag_CannotUseSpells)) { @@ -1131,6 +1500,152 @@ bool InstantSpell::canCast(const Player* player) const return false; } +std::string ConjureSpell::getScriptEventName() const +{ + return "onCastSpell"; +} + +bool ConjureSpell::configureEvent(const pugi::xml_node& node) +{ + if (!InstantSpell::configureEvent(node)) { + return false; + } + + pugi::xml_attribute attr; + if ((attr = node.attribute("conjureId"))) { + conjureId = pugi::cast(attr.value()); + } + + if ((attr = node.attribute("conjureCount"))) { + conjureCount = pugi::cast(attr.value()); + } else if (conjureId != 0) { + // load default charges from items.xml + const ItemType& it = Item::items[conjureId]; + if (it.charges != 0) { + conjureCount = it.charges; + } + } + + if ((attr = node.attribute("reagentId"))) { + reagentId = pugi::cast(attr.value()); + } + + ItemType& iType = Item::items.getItemType(conjureId); + if (iType.isRune()) { + iType.runeSpellName = words; + } + + return true; +} + +bool ConjureSpell::loadFunction(const pugi::xml_attribute&) +{ + scripted = false; + return true; +} + +bool ConjureSpell::conjureItem(Creature* creature) const +{ + Player* player = creature->getPlayer(); + if (!player) { + return false; + } + + const uint32_t conjureCost = getManaCost(player); + const uint32_t soulCost = getSoulCost(); + + if (reagentId != 0) { + bool foundReagent = false; + + Item* item = player->getInventoryItem(CONST_SLOT_LEFT); + if (item && item->getID() == reagentId) { + foundReagent = true; + + // left arm conjure + int32_t index = player->getThingIndex(item); + g_game.internalRemoveItem(item); + + Item* newItem = Item::CreateItem(conjureId, conjureCount); + if (!newItem) { + return false; + } + + ReturnValue ret = g_game.internalAddItem(player, newItem, index); + if (ret != RETURNVALUE_NOERROR) { + delete newItem; + return false; + } + + g_game.startDecay(newItem); + + Spell::postCastSpell(player, conjureCost, soulCost); + } + + item = player->getInventoryItem(CONST_SLOT_RIGHT); + if (item && item->getID() == reagentId && player->getMana() >= conjureCost) { + foundReagent = true; + + // right arm conjure + int32_t index = player->getThingIndex(item); + g_game.internalRemoveItem(item); + + Item* newItem = Item::CreateItem(conjureId, conjureCount); + if (!newItem) { + return false; + } + + ReturnValue ret = g_game.internalAddItem(player, newItem, index); + if (ret != RETURNVALUE_NOERROR) { + delete newItem; + return false; + } + + g_game.startDecay(newItem); + + Spell::postCastSpell(player, conjureCost, soulCost); + } + + if (!foundReagent) { + player->sendCancelMessage(RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + } else { + Item* newItem = Item::CreateItem(conjureId, conjureCount); + if (!newItem) { + return false; + } + + ReturnValue ret = g_game.internalPlayerAddItem(player, newItem); + if (ret != RETURNVALUE_NOERROR) { + delete newItem; + return false; + } + + g_game.startDecay(newItem); + Spell::postCastSpell(player, conjureCost, soulCost); + } + + postCastSpell(player, true, false); + g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED); + return true; +} + +bool ConjureSpell::playerCastInstant(Player* player, std::string& param) +{ + if (!playerSpellCheck(player)) { + return false; + } + + if (scripted) { + LuaVariant var; + var.type = VARIANT_STRING; + var.text = param; + return executeCastSpell(player, var); + } + return conjureItem(player); +} + std::string RuneSpell::getScriptEventName() const { return "onCastSpell"; @@ -1146,8 +1661,6 @@ bool RuneSpell::configureEvent(const pugi::xml_node& node) return false; } - spellType = SPELL_RUNE; - pugi::xml_attribute attr; if (!(attr = node.attribute("id"))) { std::cout << "[Error - RuneSpell::configureSpell] Rune spell without id." << std::endl; @@ -1155,6 +1668,7 @@ bool RuneSpell::configureEvent(const pugi::xml_node& node) } runeId = pugi::cast(attr.value()); + uint32_t charges; if ((attr = node.attribute("charges"))) { charges = pugi::cast(attr.value()); } else { @@ -1162,14 +1676,107 @@ bool RuneSpell::configureEvent(const pugi::xml_node& node) } hasCharges = (charges > 0); - if (magLevel != 0 || level != 0) { - //Change information in the ItemType to get accurate description - ItemType& iType = Item::items.getItemType(runeId); - iType.runeMagLevel = magLevel; - iType.runeLevel = level; - iType.charges = charges; + + //Change information in the ItemType to get accurate description + ItemType& iType = Item::items.getItemType(runeId); + iType.runeMagLevel = magLevel; + iType.runeLevel = level; + iType.charges = charges; + + return true; +} + +bool RuneSpell::loadFunction(const pugi::xml_attribute& attr) +{ + const char* functionName = attr.as_string(); + if (strcasecmp(functionName, "chameleon") == 0) { + runeFunction = Illusion; + } else if (strcasecmp(functionName, "convince") == 0) { + runeFunction = Convince; + } else { + std::cout << "[Warning - RuneSpell::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; + return false; } + scripted = false; + return true; +} + +bool RuneSpell::Illusion(const RuneSpell*, Player* player, const Position& posTo) +{ + Thing* thing = g_game.internalGetThing(player, posTo, 0, 0, STACKPOS_MOVE); + if (!thing) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + Item* illusionItem = thing->getItem(); + if (!illusionItem || !illusionItem->isMoveable()) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + uint32_t itemId = illusionItem->getID(); + if (illusionItem->isDisguised()) { + itemId = illusionItem->getDisguiseId(); + } + + ReturnValue ret = CreateIllusion(player, itemId, 200000); + if (ret != RETURNVALUE_NOERROR) { + player->sendCancelMessage(ret); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED); + return true; +} + +bool RuneSpell::Convince(const RuneSpell* spell, Player* player, const Position& posTo) +{ + if (!player->hasFlag(PlayerFlag_CanConvinceAll)) { + if (player->getSummonCount() >= 2) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + } + + Thing* thing = g_game.internalGetThing(player, posTo, 0, 0, STACKPOS_LOOK); + if (!thing) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + Creature* convinceCreature = thing->getCreature(); + if (!convinceCreature) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + uint32_t manaCost = 0; + if (convinceCreature->getMonster()) { + manaCost = convinceCreature->getMonster()->getManaCost(); + } + + if (!player->hasFlag(PlayerFlag_HasInfiniteMana) && player->getMana() < manaCost) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHMANA); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + if (!convinceCreature->convinceCreature(player)) { + player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); + g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); + return false; + } + + Spell::postCastSpell(player, manaCost, spell->getSoulCost()); + g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED); return true; } @@ -1218,10 +1825,12 @@ bool RuneSpell::executeUse(Player* player, Item* item, const Position&, Thing* t var.number = visibleCreature->getID(); } } - } else { + } + else { var.number = target->getCreature()->getID(); } - } else { + } + else { var.type = VARIANT_POSITION; var.pos = toPosition; } @@ -1259,7 +1868,8 @@ bool RuneSpell::internalCastSpell(Creature* creature, const LuaVariant& var, boo bool result; if (scripted) { result = executeCastSpell(creature, var, isHotkey); - } else { + } + else { result = false; } return result; diff --git a/src/spells.h b/src/spells.h index d59dd99..d0907fe 100644 --- a/src/spells.h +++ b/src/spells.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,12 +27,11 @@ #include "baseevents.h" class InstantSpell; +class ConjureSpell; class RuneSpell; class Spell; -using VocSpellMap = std::map; -using InstantSpell_ptr = std::unique_ptr; -using RuneSpell_ptr = std::unique_ptr; +typedef std::map VocSpellMap; class Spells final : public BaseEvents { @@ -51,35 +50,29 @@ class Spells final : public BaseEvents InstantSpell* getInstantSpell(const std::string& words); InstantSpell* getInstantSpellByName(const std::string& name); - InstantSpell* getInstantSpellById(uint32_t spellId); + uint32_t getInstantSpellCount(const Player* player) const; + InstantSpell* getInstantSpellByIndex(const Player* player, uint32_t index); TalkActionResult_t playerSaySpell(Player* player, std::string& words); static Position getCasterPosition(Creature* creature, Direction dir); std::string getScriptBaseName() const override; - const std::map& getInstantSpells() const { - return instants; - }; + protected: + void clear() final; + LuaScriptInterface& getScriptInterface() final; + Event* getEvent(const std::string& nodeName) final; + bool registerEvent(Event* event, const pugi::xml_node& node) final; - void clearMaps(bool fromLua); - void clear(bool fromLua) override final; - bool registerInstantLuaEvent(InstantSpell* event); - bool registerRuneLuaEvent(RuneSpell* event); - - private: - LuaScriptInterface& getScriptInterface() override; - Event_ptr getEvent(const std::string& nodeName) override; - bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; - - std::map runes; - std::map instants; + std::map runes; + std::map instants; friend class CombatSpell; LuaScriptInterface scriptInterface { "Spell Interface" }; }; -using RuneSpellFunction = std::function; +typedef bool (InstantSpellFunction)(const InstantSpell* spell, Creature* creature, const std::string& param); +typedef bool (RuneSpellFunction)(const RuneSpell* spell, Player* player, const Position& posTo); class BaseSpell { @@ -115,7 +108,7 @@ class CombatSpell final : public Event, public BaseSpell return combat; } - private: + protected: std::string getScriptEventName() const override { return "onCastSpell"; } @@ -135,191 +128,58 @@ class Spell : public BaseSpell const std::string& getName() const { return name; } - void setName(std::string n) { - name = n; - } - uint8_t getId() const { - return spellId; - } - void setId(uint8_t id) { - spellId = id; - } - void postCastSpell(Player* player, bool finishedCast = true, bool payCost = true) const; + void postCastSpell(Player* player, bool finishedSpell = true, bool payCost = true) const; static void postCastSpell(Player* player, uint32_t manaCost, uint32_t soulCost); uint32_t getManaCost(const Player* player) const; uint32_t getSoulCost() const { return soul; } - void setSoulCost(uint32_t s) { - soul = s; - } uint32_t getLevel() const { return level; } - void setLevel(uint32_t lvl) { - level = lvl; - } uint32_t getMagicLevel() const { return magLevel; } - void setMagicLevel(uint32_t lvl) { - magLevel = lvl; - } - uint32_t getMana() const { - return mana; - } - void setMana(uint32_t m) { - mana = m; - } uint32_t getManaPercent() const { return manaPercent; } - void setManaPercent(uint32_t m) { - manaPercent = m; - } bool isPremium() const { return premium; } - void setPremium(bool p) { - premium = p; - } - bool isEnabled() const { - return enabled; - } - void setEnabled(bool e) { - enabled = e; - } virtual bool isInstant() const = 0; bool isLearnable() const { return learnable; } - void setLearnable(bool l) { - learnable = l; - } + + static ReturnValue CreateIllusion(Creature* creature, const Outfit_t& outfit, int32_t time); + static ReturnValue CreateIllusion(Creature* creature, const std::string& name, int32_t time); + static ReturnValue CreateIllusion(Creature* creature, uint32_t itemId, int32_t time); const VocSpellMap& getVocMap() const { return vocSpellMap; } - void addVocMap(uint16_t n, bool b) { - vocSpellMap[n] = b; - } - - const SpellGroup_t getGroup() const { - return group; - } - void setGroup(SpellGroup_t g) { - group = g; - } - const SpellGroup_t getSecondaryGroup() const { - return secondaryGroup; - } - void setSecondaryGroup(SpellGroup_t g) { - secondaryGroup = g; - } - - uint32_t getCooldown() const { - return cooldown; - } - void setCooldown(uint32_t cd) { - cooldown = cd; - } - uint32_t getSecondaryCooldown() const { - return secondaryGroupCooldown; - } - void setSecondaryCooldown(uint32_t cd) { - secondaryGroupCooldown = cd; - } - uint32_t getGroupCooldown() const { - return groupCooldown; - } - void setGroupCooldown(uint32_t cd) { - groupCooldown = cd; - } - - int32_t getRange() const { - return range; - } - void setRange(int32_t r) { - range = r; - } - - bool getNeedTarget() const { - return needTarget; - } - void setNeedTarget(bool n) { - needTarget = n; - } - bool getNeedWeapon() const { - return needWeapon; - } - void setNeedWeapon(bool n) { - needWeapon = n; - } - bool getNeedLearn() const { - return learnable; - } - void setNeedLearn(bool n) { - learnable = n; - } - bool getSelfTarget() const { - return selfTarget; - } - void setSelfTarget(bool s) { - selfTarget = s; - } - bool getBlockingSolid() const { - return blockingSolid; - } - void setBlockingSolid(bool b) { - blockingSolid = b; - } - bool getBlockingCreature() const { - return blockingCreature; - } - void setBlockingCreature(bool b) { - blockingCreature = b; - } - bool getAggressive() const { - return aggressive; - } - void setAggressive(bool a) { - aggressive = a; - } - - SpellType_t spellType = SPELL_UNDEFINED; protected: bool playerSpellCheck(Player* player) const; bool playerInstantSpellCheck(Player* player, const Position& toPos); bool playerRuneSpellCheck(Player* player, const Position& toPos); - VocSpellMap vocSpellMap; - - SpellGroup_t group = SPELLGROUP_NONE; - SpellGroup_t secondaryGroup = SPELLGROUP_NONE; - - uint32_t cooldown = 1000; - uint32_t groupCooldown = 1000; - uint32_t secondaryGroupCooldown = 0; - uint32_t level = 0; - uint32_t magLevel = 0; - int32_t range = -1; - uint8_t spellId = 0; - bool selfTarget = false; - bool needTarget = false; - - private: - uint32_t mana = 0; uint32_t manaPercent = 0; uint32_t soul = 0; + uint32_t cooldown = 0; + uint32_t level = 0; + uint32_t magLevel = 0; + int32_t range = -1; + bool needTarget = false; bool needWeapon = false; + bool selfTarget = false; bool blockingSolid = false; bool blockingCreature = false; bool aggressive = true; @@ -327,17 +187,19 @@ class Spell : public BaseSpell bool enabled = true; bool premium = false; + VocSpellMap vocSpellMap; private: std::string name; }; -class InstantSpell final : public TalkAction, public Spell +class InstantSpell : public TalkAction, public Spell { public: explicit InstantSpell(LuaScriptInterface* interface) : TalkAction(interface) {} bool configureEvent(const pugi::xml_node& node) override; + bool loadFunction(const pugi::xml_attribute& attr) override; virtual bool playerCastInstant(Player* player, std::string& param); @@ -353,41 +215,30 @@ class InstantSpell final : public TalkAction, public Spell bool getHasParam() const { return hasParam; } - void setHasParam(bool p) { - hasParam = p; - } bool getHasPlayerNameParam() const { return hasPlayerNameParam; } - void setHasPlayerNameParam(bool p) { - hasPlayerNameParam = p; - } - bool getNeedDirection() const { - return needDirection; - } - void setNeedDirection(bool n) { - needDirection = n; - } - bool getNeedCasterTargetOrDirection() const { - return casterTargetOrDirection; - } - void setNeedCasterTargetOrDirection(bool d) { - casterTargetOrDirection = d; - } - bool getBlockWalls() const { - return checkLineOfSight; - } - void setBlockWalls(bool w) { - checkLineOfSight = w; - } bool canCast(const Player* player) const; bool canThrowSpell(const Creature* creature, const Creature* target) const; - private: + protected: std::string getScriptEventName() const override; + static InstantSpellFunction HouseGuestList; + static InstantSpellFunction HouseSubOwnerList; + static InstantSpellFunction HouseDoorList; + static InstantSpellFunction HouseKick; + static InstantSpellFunction SearchPlayer; + static InstantSpellFunction SummonMonster; + static InstantSpellFunction Levitate; + static InstantSpellFunction Illusion; + + static House* getHouseFromPos(Creature* creature); + bool internalCastSpell(Creature* creature, const LuaVariant& var); + InstantSpellFunction* function = nullptr; + bool needDirection = false; bool hasParam = false; bool hasPlayerNameParam = false; @@ -395,56 +246,77 @@ class InstantSpell final : public TalkAction, public Spell bool casterTargetOrDirection = false; }; +class ConjureSpell final : public InstantSpell +{ + public: + explicit ConjureSpell(LuaScriptInterface* interface) : InstantSpell(interface) { + aggressive = false; + } + + bool configureEvent(const pugi::xml_node& node) final; + bool loadFunction(const pugi::xml_attribute& attr) final; + + bool playerCastInstant(Player* player, std::string& param) final; + + bool castSpell(Creature*) final { + return false; + } + bool castSpell(Creature*, Creature*) final { + return false; + } + + protected: + std::string getScriptEventName() const final; + + bool conjureItem(Creature* creature) const; + + uint32_t conjureId = 0; + uint32_t conjureCount = 1; + uint32_t reagentId = 0; +}; + class RuneSpell final : public Action, public Spell { public: explicit RuneSpell(LuaScriptInterface* interface) : Action(interface) {} - bool configureEvent(const pugi::xml_node& node) override; + bool configureEvent(const pugi::xml_node& node) final; + bool loadFunction(const pugi::xml_attribute& attr) final; - ReturnValue canExecuteAction(const Player* player, const Position& toPos) override; - bool hasOwnErrorHandler() override { + ReturnValue canExecuteAction(const Player* player, const Position& toPos) final; + bool hasOwnErrorHandler() final { return true; } - Thing* getTarget(Player*, Creature* targetCreature, const Position&, uint8_t) const override { + Thing* getTarget(Player*, Creature* targetCreature, const Position&, uint8_t) const final { return targetCreature; } bool executeUse(Player* player, Item* item, const Position& fromPosition, Thing* target, const Position& toPosition, bool isHotkey) override; - bool castSpell(Creature* creature) override; - bool castSpell(Creature* creature, Creature* target) override; + bool castSpell(Creature* creature) final; + bool castSpell(Creature* creature, Creature* target) final; //scripting bool executeCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey); - bool isInstant() const override { + bool isInstant() const final { return false; } uint16_t getRuneItemId() const { return runeId; } - void setRuneItemId(uint16_t i) { - runeId = i; - } - uint32_t getCharges() const { - return charges; - } - void setCharges(uint32_t c) { - if (c > 0) { - hasCharges = true; - } - charges = c; - } - private: - std::string getScriptEventName() const override; + protected: + std::string getScriptEventName() const final; + + static RuneSpellFunction Illusion; + static RuneSpellFunction Convince; bool internalCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey); + RuneSpellFunction* runeFunction = nullptr; uint16_t runeId = 0; - uint32_t charges = 0; - bool hasCharges = false; + bool hasCharges = true; }; #endif diff --git a/src/talkaction.cpp b/src/talkaction.cpp index 2a30410..8e2a0b7 100644 --- a/src/talkaction.cpp +++ b/src/talkaction.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,20 +31,14 @@ TalkActions::TalkActions() TalkActions::~TalkActions() { - clear(false); + clear(); } -void TalkActions::clear(bool fromLua) +void TalkActions::clear() { - for (auto it = talkActions.begin(); it != talkActions.end(); ) { - if (fromLua == it->second.fromLua) { - it = talkActions.erase(it); - } else { - ++it; - } - } + talkActions.clear(); - reInitState(fromLua); + scriptInterface.reInitState(); } LuaScriptInterface& TalkActions::getScriptInterface() @@ -57,36 +51,29 @@ std::string TalkActions::getScriptBaseName() const return "talkactions"; } -Event_ptr TalkActions::getEvent(const std::string& nodeName) +Event* TalkActions::getEvent(const std::string& nodeName) { if (strcasecmp(nodeName.c_str(), "talkaction") != 0) { return nullptr; } - return Event_ptr(new TalkAction(&scriptInterface)); + return new TalkAction(&scriptInterface); } -bool TalkActions::registerEvent(Event_ptr event, const pugi::xml_node&) +bool TalkActions::registerEvent(Event* event, const pugi::xml_node&) { - TalkAction_ptr talkAction{static_cast(event.release())}; // event is guaranteed to be a TalkAction - talkActions.emplace(talkAction->getWords(), std::move(*talkAction)); - return true; -} + auto talkAction = std::unique_ptr(static_cast(event)); // event is guaranteed to be a TalkAction + talkActions.push_front(std::move(*talkAction)); -bool TalkActions::registerLuaEvent(TalkAction* event) -{ - TalkAction_ptr talkAction{ event }; - talkActions.emplace(talkAction->getWords(), std::move(*talkAction)); return true; } TalkActionResult_t TalkActions::playerSaySpell(Player* player, SpeakClasses type, const std::string& words) const { size_t wordsLength = words.length(); - for (auto it = talkActions.begin(); it != talkActions.end(); ) { - const std::string& talkactionWords = it->first; + for (const TalkAction& talkAction : talkActions) { + const std::string& talkactionWords = talkAction.getWords(); size_t talkactionLength = talkactionWords.length(); if (wordsLength < talkactionLength || strncasecmp(words.c_str(), talkactionWords.c_str(), talkactionLength) != 0) { - ++it; continue; } @@ -94,16 +81,14 @@ TalkActionResult_t TalkActions::playerSaySpell(Player* player, SpeakClasses type if (wordsLength != talkactionLength) { param = words.substr(talkactionLength); if (param.front() != ' ') { - ++it; continue; } trim_left(param, ' '); - std::string separator = it->second.getSeparator(); - if (separator != " ") { + char separator = talkAction.getSeparator(); + if (separator != ' ') { if (!param.empty()) { - if (param != separator) { - ++it; + if (param.front() != separator) { continue; } else { param.erase(param.begin()); @@ -112,7 +97,7 @@ TalkActionResult_t TalkActions::playerSaySpell(Player* player, SpeakClasses type } } - if (it->second.executeSay(player, param, type)) { + if (talkAction.executeSay(player, param, type)) { return TALKACTION_CONTINUE; } else { return TALKACTION_BREAK; diff --git a/src/talkaction.h b/src/talkaction.h index 686c38b..765f336 100644 --- a/src/talkaction.h +++ b/src/talkaction.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,15 +24,38 @@ #include "baseevents.h" #include "const.h" -class TalkAction; -using TalkAction_ptr = std::unique_ptr; - enum TalkActionResult_t { TALKACTION_CONTINUE, TALKACTION_BREAK, TALKACTION_FAILED, }; +class TalkAction; + +class TalkActions : public BaseEvents +{ + public: + TalkActions(); + ~TalkActions(); + + // non-copyable + TalkActions(const TalkActions&) = delete; + TalkActions& operator=(const TalkActions&) = delete; + + TalkActionResult_t playerSaySpell(Player* player, SpeakClasses type, const std::string& words) const; + + 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; + + std::forward_list talkActions; + + LuaScriptInterface scriptInterface; +}; + class TalkAction : public Event { public: @@ -43,51 +66,19 @@ class TalkAction : public Event const std::string& getWords() const { return words; } - void setWords(std::string word) { - words = word; - } - std::string getSeparator() const { + char getSeparator() const { return separator; } - void setSeparator(std::string sep) { - separator = sep; - } //scripting bool executeSay(Player* player, const std::string& param, SpeakClasses type) const; // - private: + protected: std::string getScriptEventName() const override; std::string words; - std::string separator = "\""; -}; - -class TalkActions final : public BaseEvents -{ - public: - TalkActions(); - ~TalkActions(); - - // non-copyable - TalkActions(const TalkActions&) = delete; - TalkActions& operator=(const TalkActions&) = delete; - - TalkActionResult_t playerSaySpell(Player* player, SpeakClasses type, const std::string& words) const; - - bool registerLuaEvent(TalkAction* event); - void clear(bool fromLua) override final; - - private: - LuaScriptInterface& getScriptInterface() override; - std::string getScriptBaseName() const override; - Event_ptr getEvent(const std::string& nodeName) override; - bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; - - std::map talkActions; - - LuaScriptInterface scriptInterface; + char separator = '"'; }; #endif diff --git a/src/tasks.cpp b/src/tasks.cpp index 58a82a0..1ed727f 100644 --- a/src/tasks.cpp +++ b/src/tasks.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,16 +24,6 @@ extern Game g_game; -Task* createTask(std::function f) -{ - return new Task(std::move(f)); -} - -Task* createTask(uint32_t expiration, std::function f) -{ - return new Task(expiration, std::move(f)); -} - void Dispatcher::threadMain() { // NOTE: second argument defer_lock is to prevent from immediate locking @@ -58,6 +48,8 @@ void Dispatcher::threadMain() ++dispatcherCycle; // execute it (*task)(); + + g_game.map.clearSpectatorCache(); } delete task; } else { diff --git a/src/tasks.h b/src/tasks.h index 6f6af57..4ea459c 100644 --- a/src/tasks.h +++ b/src/tasks.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,8 +31,8 @@ class Task { public: // DO NOT allocate this class on the stack - explicit Task(std::function&& f) : func(std::move(f)) {} - Task(uint32_t ms, std::function&& f) : + explicit Task(std::function f) : func(std::move(f)) {} + Task(uint32_t ms, std::function f) : expiration(std::chrono::system_clock::now() + std::chrono::milliseconds(ms)), func(std::move(f)) {} virtual ~Task() = default; @@ -52,17 +52,22 @@ class Task } protected: - std::chrono::system_clock::time_point expiration = SYSTEM_TIME_ZERO; - - private: // Expiration has another meaning for scheduler tasks, // then it is the time the task should be added to the // dispatcher + std::chrono::system_clock::time_point expiration = SYSTEM_TIME_ZERO; std::function func; }; -Task* createTask(std::function f); -Task* createTask(uint32_t expiration, std::function f); +inline Task* createTask(const std::function& f) +{ + return new Task(f); +} + +inline Task* createTask(uint32_t expiration, const std::function& f) +{ + return new Task(expiration, f); +} class Dispatcher : public ThreadHolder { public: @@ -76,7 +81,7 @@ class Dispatcher : public ThreadHolder { void threadMain(); - private: + protected: std::thread thread; std::mutex taskLock; std::condition_variable taskSignal; diff --git a/src/teleport.cpp b/src/teleport.cpp index e0dddfd..562fd0b 100644 --- a/src/teleport.cpp +++ b/src/teleport.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/teleport.h b/src/teleport.h index f79579c..410b2ab 100644 --- a/src/teleport.h +++ b/src/teleport.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,16 +27,16 @@ class Teleport final : public Item, public Cylinder public: explicit Teleport(uint16_t type) : Item(type) {}; - Teleport* getTeleport() override { + Teleport* getTeleport() final { return this; } - const Teleport* getTeleport() const override { + const Teleport* getTeleport() const final { return this; } //serialization - Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) override; - void serializeAttr(PropWriteStream& propWriteStream) const override; + Attr_ReadValue readAttr(AttrTypes_t attr, PropStream& propStream) final; + void serializeAttr(PropWriteStream& propWriteStream) const final; const Position& getDestPos() const { return destPos; @@ -47,23 +47,23 @@ class Teleport final : public Item, public Cylinder //cylinder implementations ReturnValue queryAdd(int32_t index, const Thing& thing, uint32_t count, - uint32_t flags, Creature* actor = nullptr) const override; + 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 override; - ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const override; + 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) override; + uint32_t& flags) final; - void addThing(Thing* thing) override; - void addThing(int32_t index, Thing* thing) override; + void addThing(Thing* thing) final; + void addThing(int32_t index, Thing* thing) final; - void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override; - void replaceThing(uint32_t index, Thing* thing) override; + 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) override; + void removeThing(Thing* thing, uint32_t count) 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 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: Position destPos; diff --git a/src/thing.cpp b/src/thing.cpp index abda31f..21e72f6 100644 --- a/src/thing.cpp +++ b/src/thing.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/thing.h b/src/thing.h index 3fee9d2..b23be2b 100644 --- a/src/thing.h +++ b/src/thing.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,10 +30,11 @@ class Container; class Thing { - public: + protected: constexpr Thing() = default; - virtual ~Thing() = default; + ~Thing() = default; + public: // non-copyable Thing(const Thing&) = delete; Thing& operator=(const Thing&) = delete; diff --git a/src/thread_holder_base.h b/src/thread_holder_base.h index 1ab206b..91280db 100644 --- a/src/thread_holder_base.h +++ b/src/thread_holder_base.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/tile.cpp b/src/tile.cpp index d5347dc..6240678 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,7 +30,6 @@ #include "monster.h" #include "movement.h" #include "teleport.h" -#include "trashholder.h" extern Game g_game; extern MoveEvents* g_moveEvents; @@ -101,6 +100,25 @@ bool Tile::hasHeight(uint32_t n) const return false; } +int32_t Tile::getHeight() { + int32_t height = 0; + if (ground) { + if (ground->hasProperty(CONST_PROP_HASHEIGHT)) { + ++height; + } + } + + if (const TileItemVector* items = getItemList()) { + for (ItemVector::const_iterator it = items->begin(); it != items->end(); ++it) { + if ((*it)->hasProperty(CONST_PROP_HASHEIGHT)) { + ++height; + } + } + } + + return std::min(height, 4); +} + size_t Tile::getCreatureCount() const { if (const CreatureVector* creatures = getCreatures()) { @@ -174,26 +192,6 @@ MagicField* Tile::getFieldItem() const return nullptr; } -TrashHolder* Tile::getTrashHolder() const -{ - if (!hasFlag(TILESTATE_TRASHHOLDER)) { - return nullptr; - } - - if (ground && ground->getTrashHolder()) { - return ground->getTrashHolder(); - } - - if (const TileItemVector* items = getItemList()) { - for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) { - if ((*it)->getTrashHolder()) { - return (*it)->getTrashHolder(); - } - } - } - return nullptr; -} - Mailbox* Tile::getMailbox() const { if (!hasFlag(TILESTATE_MAILBOX)) { @@ -214,6 +212,27 @@ Mailbox* Tile::getMailbox() const return nullptr; } +DepotLocker* Tile::getDepotLocker() const +{ + if (!hasFlag(TILESTATE_DEPOT)) { + return nullptr; + } + + if (ground && ground->getDepotLocker()) { + return ground->getDepotLocker(); + } + + if (const TileItemVector* items = getItemList()) { + for (auto it = items->rbegin(), end = items->rend(); it != end; ++it) { + if ((*it)->getDepotLocker()) { + return (*it)->getDepotLocker(); + } + } + } + return nullptr; +} + + BedItem* Tile::getBedItem() const { if (!hasFlag(TILESTATE_BED)) { @@ -353,17 +372,11 @@ Thing* Tile::getTopVisibleThing(const Creature* creature) TileItemVector* items = getItemList(); if (items) { for (ItemVector::const_iterator it = items->getBeginDownItem(), end = items->getEndDownItem(); it != end; ++it) { - const ItemType& iit = Item::items[(*it)->getID()]; - if (!iit.lookThrough) { - return (*it); - } + return (*it); } for (auto it = ItemVector::const_reverse_iterator(items->getEndTopItem()), end = ItemVector::const_reverse_iterator(items->getBeginTopItem()); it != end; ++it) { - const ItemType& iit = Item::items[(*it)->getID()]; - if (!iit.lookThrough) { - return (*it); - } + return (*it); } } @@ -372,81 +385,48 @@ Thing* Tile::getTopVisibleThing(const Creature* creature) void Tile::onAddTileItem(Item* item) { - if (item->hasProperty(CONST_PROP_MOVEABLE) || item->getContainer()) { - auto it = g_game.browseFields.find(this); - if (it != g_game.browseFields.end()) { - it->second->addItemBack(item); - item->setParent(this); - } - } - setTileFlags(item); const Position& cylinderMapPos = getPosition(); - SpectatorVec spectators; - g_game.map.getSpectators(spectators, cylinderMapPos, true); + SpectatorVec list; + g_game.map.getSpectators(list, cylinderMapPos, true); //send to client - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendAddTileItem(this, cylinderMapPos, item); } } //event methods - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { spectator->onAddTileItem(this, cylinderMapPos); } } void Tile::onUpdateTileItem(Item* oldItem, const ItemType& oldType, Item* newItem, const ItemType& newType) { - if (newItem->hasProperty(CONST_PROP_MOVEABLE) || newItem->getContainer()) { - auto it = g_game.browseFields.find(this); - if (it != g_game.browseFields.end()) { - int32_t index = it->second->getThingIndex(oldItem); - if (index != -1) { - it->second->replaceThing(index, newItem); - newItem->setParent(this); - } - } - } else if (oldItem->hasProperty(CONST_PROP_MOVEABLE) || oldItem->getContainer()) { - auto it = g_game.browseFields.find(this); - if (it != g_game.browseFields.end()) { - Cylinder* oldParent = oldItem->getParent(); - it->second->removeThing(oldItem, oldItem->getItemCount()); - oldItem->setParent(oldParent); - } - } - const Position& cylinderMapPos = getPosition(); - SpectatorVec spectators; - g_game.map.getSpectators(spectators, cylinderMapPos, true); + SpectatorVec list; + g_game.map.getSpectators(list, cylinderMapPos, true); //send to client - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendUpdateTileItem(this, cylinderMapPos, newItem); } } //event methods - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { spectator->onUpdateTileItem(this, cylinderMapPos, oldItem, oldType, newItem, newType); } } -void Tile::onRemoveTileItem(const SpectatorVec& spectators, const std::vector& oldStackPosVector, Item* item) +void Tile::onRemoveTileItem(const SpectatorVec& list, const std::vector& oldStackPosVector, Item* item) { - if (item->hasProperty(CONST_PROP_MOVEABLE) || item->getContainer()) { - auto it = g_game.browseFields.find(this); - if (it != g_game.browseFields.end()) { - it->second->removeThing(item, item->getItemCount()); - } - } - resetTileFlags(item); const Position& cylinderMapPos = getPosition(); @@ -454,24 +434,24 @@ void Tile::onRemoveTileItem(const SpectatorVec& spectators, const std::vectorgetPlayer()) { tmpPlayer->sendRemoveTileThing(cylinderMapPos, oldStackPosVector[i++]); } } //event methods - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { spectator->onRemoveTileItem(this, cylinderMapPos, iType, item); } } -void Tile::onUpdateTile(const SpectatorVec& spectators) +void Tile::onUpdateTile(const SpectatorVec& list) { const Position& cylinderMapPos = getPosition(); //send to clients - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { spectator->getPlayer()->sendUpdateTile(this, cylinderMapPos); } } @@ -483,7 +463,7 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags return RETURNVALUE_NOERROR; } - if (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_FLOORCHANGE | TILESTATE_TELEPORT)) { + if (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_IMMOVABLENOFIELDBLOCKPATH)) { return RETURNVALUE_NOTPOSSIBLE; } @@ -492,7 +472,15 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags } if (const Monster* monster = creature->getMonster()) { - if (hasFlag(TILESTATE_PROTECTIONZONE | TILESTATE_FLOORCHANGE | TILESTATE_TELEPORT)) { + if (hasFlag(TILESTATE_PROTECTIONZONE | TILESTATE_TELEPORT)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasFlag(TILESTATE_IMMOVABLEBLOCKPATH | TILESTATE_IMMOVABLENOFIELDBLOCKPATH)) { + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasBitSet(FLAG_PLACECHECK, flags) && hasFlag(TILESTATE_BLOCKSOLID)) { return RETURNVALUE_NOTPOSSIBLE; } @@ -506,7 +494,7 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags const Monster* creatureMonster = tileCreature->getMonster(); if (!creatureMonster || !tileCreature->isPushable() || - (creatureMonster->isSummon() && creatureMonster->getMaster()->getPlayer())) { + (creatureMonster->isSummon() && creatureMonster->getMaster()->getPlayer())) { return RETURNVALUE_NOTPOSSIBLE; } } @@ -533,23 +521,17 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags } } - MagicField* field = getFieldItem(); - if (!field || field->isBlocking() || field->getDamage() == 0) { - return RETURNVALUE_NOERROR; - } + if (!monster->hasCondition(CONDITION_AGGRESSIVE) && + !hasBitSet(FLAG_IGNOREFIELDDAMAGE, flags)) { + if (hasFlag(TILESTATE_FIREDAMAGE) && !monster->isImmune(COMBAT_FIREDAMAGE)) { + return RETURNVALUE_NOTPOSSIBLE; + } - CombatType_t combatType = field->getCombatType(); + if (hasFlag(TILESTATE_POISONDAMAGE) && !monster->isImmune(COMBAT_EARTHDAMAGE)) { + return RETURNVALUE_NOTPOSSIBLE; + } - //There is 3 options for a monster to enter a magic field - //1) Monster is immune - if (!monster->isImmune(combatType)) { - //1) Monster is able to walk over field type - //2) Being attacked while random stepping will make it ignore field damages - if (hasBitSet(FLAG_IGNOREFIELDDAMAGE, flags)) { - if (!(monster->canWalkOnFieldType(combatType) || monster->isIgnoringFieldDamage())) { - return RETURNVALUE_NOTPOSSIBLE; - } - } else { + if (hasFlag(TILESTATE_ENERGYDAMAGE) && !monster->isImmune(COMBAT_ENERGYDAMAGE)) { return RETURNVALUE_NOTPOSSIBLE; } } @@ -560,11 +542,11 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags const CreatureVector* creatures = getCreatures(); if (const Player* player = creature->getPlayer()) { if (creatures && !creatures->empty() && !hasBitSet(FLAG_IGNOREBLOCKCREATURE, flags) && !player->isAccessPlayer()) { - for (const Creature* tileCreature : *creatures) { - if (!player->canWalkthrough(tileCreature)) { - return RETURNVALUE_NOTPOSSIBLE; - } - } + return RETURNVALUE_NOTPOSSIBLE; + } + + if (hasBitSet(FLAG_PATHFINDING, flags) && hasFlag(TILESTATE_BLOCKPATH)) { + return RETURNVALUE_NOTPOSSIBLE; } if (player->getParent() == nullptr && hasFlag(TILESTATE_NOLOGOUT)) { @@ -607,7 +589,7 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags //FLAG_IGNOREBLOCKITEM is set if (ground) { const ItemType& iiType = Item::items[ground->getID()]; - if (iiType.blockSolid && (!iiType.moveable || ground->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID))) { + if (iiType.blockSolid) { return RETURNVALUE_NOTPOSSIBLE; } } @@ -615,7 +597,7 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags if (const auto items = getItemList()) { for (const Item* item : *items) { const ItemType& iiType = Item::items[item->getID()]; - if (iiType.blockSolid && (!iiType.moveable || item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID))) { + if (iiType.blockSolid && !iiType.moveable) { return RETURNVALUE_NOTPOSSIBLE; } } @@ -645,6 +627,10 @@ ReturnValue Tile::queryAdd(int32_t, const Thing& thing, uint32_t, uint32_t flags } } + if (item->isMagicField() && hasProperty(CONST_PROP_IMMOVABLENOFIELDBLOCKPATH)) { + return RETURNVALUE_NOTENOUGHROOM; + } + if (itemIsHangable && hasFlag(TILESTATE_SUPPORTS_HANGABLE)) { if (items) { for (const Item* tileItem : *items) { @@ -723,101 +709,14 @@ ReturnValue Tile::queryRemove(const Thing& thing, uint32_t count, uint32_t flags return RETURNVALUE_NOERROR; } -Tile* Tile::queryDestination(int32_t&, const Thing&, Item** destItem, uint32_t& flags) +Tile* Tile::queryDestination(int32_t&, const Thing&, Item** destItem, uint32_t&) { - Tile* destTile = nullptr; - *destItem = nullptr; - - if (hasFlag(TILESTATE_FLOORCHANGE_DOWN)) { - uint16_t dx = tilePos.x; - uint16_t dy = tilePos.y; - uint8_t dz = tilePos.z + 1; - - Tile* southDownTile = g_game.map.getTile(dx, dy - 1, dz); - if (southDownTile && southDownTile->hasFlag(TILESTATE_FLOORCHANGE_SOUTH_ALT)) { - dy -= 2; - destTile = g_game.map.getTile(dx, dy, dz); - } else { - Tile* eastDownTile = g_game.map.getTile(dx - 1, dy, dz); - if (eastDownTile && eastDownTile->hasFlag(TILESTATE_FLOORCHANGE_EAST_ALT)) { - dx -= 2; - destTile = g_game.map.getTile(dx, dy, dz); - } else { - Tile* downTile = g_game.map.getTile(dx, dy, dz); - if (downTile) { - if (downTile->hasFlag(TILESTATE_FLOORCHANGE_NORTH)) { - ++dy; - } - - if (downTile->hasFlag(TILESTATE_FLOORCHANGE_SOUTH)) { - --dy; - } - - if (downTile->hasFlag(TILESTATE_FLOORCHANGE_SOUTH_ALT)) { - dy -= 2; - } - - if (downTile->hasFlag(TILESTATE_FLOORCHANGE_EAST)) { - --dx; - } - - if (downTile->hasFlag(TILESTATE_FLOORCHANGE_EAST_ALT)) { - dx -= 2; - } - - if (downTile->hasFlag(TILESTATE_FLOORCHANGE_WEST)) { - ++dx; - } - - destTile = g_game.map.getTile(dx, dy, dz); - } - } - } - } else if (hasFlag(TILESTATE_FLOORCHANGE)) { - uint16_t dx = tilePos.x; - uint16_t dy = tilePos.y; - uint8_t dz = tilePos.z - 1; - - if (hasFlag(TILESTATE_FLOORCHANGE_NORTH)) { - --dy; - } - - if (hasFlag(TILESTATE_FLOORCHANGE_SOUTH)) { - ++dy; - } - - if (hasFlag(TILESTATE_FLOORCHANGE_EAST)) { - ++dx; - } - - if (hasFlag(TILESTATE_FLOORCHANGE_WEST)) { - --dx; - } - - if (hasFlag(TILESTATE_FLOORCHANGE_SOUTH_ALT)) { - dy += 2; - } - - if (hasFlag(TILESTATE_FLOORCHANGE_EAST_ALT)) { - dx += 2; - } - - destTile = g_game.map.getTile(dx, dy, dz); + Thing* destThing = getTopDownItem(); + if (destThing) { + *destItem = destThing->getItem(); } - if (destTile == nullptr) { - destTile = this; - } else { - flags |= FLAG_NOLIMIT; //Will ignore that there is blocking items/creatures - } - - if (destTile) { - Thing* destThing = destTile->getTopDownItem(); - if (destThing) { - *destItem = destThing->getItem(); - } - } - return destTile; + return this; } void Tile::addThing(Thing* thing) @@ -830,13 +729,9 @@ void Tile::addThing(int32_t, Thing* thing) Creature* creature = thing->getCreature(); if (creature) { g_game.map.clearSpectatorCache(); - if (creature->getPlayer()) { - g_game.map.clearPlayersSpectatorCache(); - } - creature->setParent(this); CreatureVector* creatures = makeCreatures(); - creatures->insert(creatures->begin(), creature); + creatures->insert(creatures->end(), creature); } else { Item* item = thing->getItem(); if (item == nullptr) { @@ -1039,10 +934,6 @@ void Tile::removeThing(Thing* thing, uint32_t count) auto it = std::find(creatures->begin(), creatures->end(), thing); if (it != creatures->end()) { g_game.map.clearSpectatorCache(); - if (creature->getPlayer()) { - g_game.map.clearPlayersSpectatorCache(); - } - creatures->erase(it); } } @@ -1063,9 +954,9 @@ void Tile::removeThing(Thing* thing, uint32_t count) ground->setParent(nullptr); ground = nullptr; - SpectatorVec spectators; - g_game.map.getSpectators(spectators, getPosition(), true); - onRemoveTileItem(spectators, std::vector(spectators.size(), 0), item); + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), true); + onRemoveTileItem(list, std::vector(list.size(), 0), item); return; } @@ -1083,9 +974,9 @@ void Tile::removeThing(Thing* thing, uint32_t count) std::vector oldStackPosVector; - SpectatorVec spectators; - g_game.map.getSpectators(spectators, getPosition(), true); - for (Creature* spectator : spectators) { + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), true); + for (Creature* spectator : list) { if (Player* tmpPlayer = spectator->getPlayer()) { oldStackPosVector.push_back(getStackposOfItem(tmpPlayer, item)); } @@ -1093,7 +984,7 @@ void Tile::removeThing(Thing* thing, uint32_t count) item->setParent(nullptr); items->erase(it); - onRemoveTileItem(spectators, oldStackPosVector, item); + onRemoveTileItem(list, oldStackPosVector, item); } else { auto it = std::find(items->getBeginDownItem(), items->getEndDownItem(), item); if (it == items->getEndDownItem()) { @@ -1107,9 +998,9 @@ void Tile::removeThing(Thing* thing, uint32_t count) } else { std::vector oldStackPosVector; - SpectatorVec spectators; - g_game.map.getSpectators(spectators, getPosition(), true); - for (Creature* spectator : spectators) { + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), true); + for (Creature* spectator : list) { if (Player* tmpPlayer = spectator->getPlayer()) { oldStackPosVector.push_back(getStackposOfItem(tmpPlayer, item)); } @@ -1118,7 +1009,7 @@ void Tile::removeThing(Thing* thing, uint32_t count) item->setParent(nullptr); items->erase(it); items->addDownItemCount(-1); - onRemoveTileItem(spectators, oldStackPosVector, item); + onRemoveTileItem(list, oldStackPosVector, item); } } } @@ -1350,9 +1241,9 @@ Thing* Tile::getThing(size_t index) const void Tile::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link /*= LINK_OWNER*/) { - SpectatorVec spectators; - g_game.map.getSpectators(spectators, getPosition(), true, true); - for (Creature* spectator : spectators) { + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), true, true); + for (Creature* spectator : list) { spectator->getPlayer()->postAddNotification(thing, oldParent, index, LINK_NEAR); } @@ -1375,11 +1266,6 @@ void Tile::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t if (teleport) { teleport->addThing(thing); } - } else if (hasFlag(TILESTATE_TRASHHOLDER)) { - TrashHolder* trashholder = getTrashHolder(); - if (trashholder) { - trashholder->addThing(thing); - } } else if (hasFlag(TILESTATE_MAILBOX)) { Mailbox* mailbox = getMailbox(); if (mailbox) { @@ -1405,14 +1291,14 @@ void Tile::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t void Tile::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) { - SpectatorVec spectators; - g_game.map.getSpectators(spectators, getPosition(), true, true); + SpectatorVec list; + g_game.map.getSpectators(list, getPosition(), true, true); if (getThingCount() > 8) { - onUpdateTile(spectators); + onUpdateTile(list); } - for (Creature* spectator : spectators) { + for (Creature* spectator : list) { spectator->getPlayer()->postRemoveNotification(thing, newParent, index, LINK_NEAR); } @@ -1440,12 +1326,8 @@ void Tile::internalAddThing(uint32_t, Thing* thing) Creature* creature = thing->getCreature(); if (creature) { g_game.map.clearSpectatorCache(); - if (creature->getPlayer()) { - g_game.map.clearPlayersSpectatorCache(); - } - CreatureVector* creatures = makeCreatures(); - creatures->insert(creatures->begin(), creature); + creatures->insert(creatures->end(), creature); } else { Item* item = thing->getItem(); if (item == nullptr) { @@ -1490,13 +1372,6 @@ void Tile::internalAddThing(uint32_t, Thing* thing) void Tile::setTileFlags(const Item* item) { - if (!hasFlag(TILESTATE_FLOORCHANGE)) { - const ItemType& it = Item::items[item->getID()]; - if (it.floorChange != 0) { - setFlag(it.floorChange); - } - } - if (item->hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID)) { setFlag(TILESTATE_IMMOVABLEBLOCKSOLID); } @@ -1525,10 +1400,6 @@ void Tile::setTileFlags(const Item* item) setFlag(TILESTATE_MAILBOX); } - if (item->getTrashHolder()) { - setFlag(TILESTATE_TRASHHOLDER); - } - if (item->hasProperty(CONST_PROP_BLOCKSOLID)) { setFlag(TILESTATE_BLOCKSOLID); } @@ -1537,6 +1408,18 @@ void Tile::setTileFlags(const Item* item) setFlag(TILESTATE_BED); } + if (item->getCombatType() == COMBAT_FIREDAMAGE) { + setFlag(TILESTATE_FIREDAMAGE); + } + + if (item->getCombatType() == COMBAT_ENERGYDAMAGE) { + setFlag(TILESTATE_ENERGYDAMAGE); + } + + if (item->getCombatType() == COMBAT_EARTHDAMAGE) { + setFlag(TILESTATE_POISONDAMAGE); + } + const Container* container = item->getContainer(); if (container && container->getDepotLocker()) { setFlag(TILESTATE_DEPOT); @@ -1549,11 +1432,6 @@ void Tile::setTileFlags(const Item* item) void Tile::resetTileFlags(const Item* item) { - const ItemType& it = Item::items[item->getID()]; - if (it.floorChange != 0) { - resetFlag(TILESTATE_FLOORCHANGE); - } - if (item->hasProperty(CONST_PROP_BLOCKSOLID) && !hasProperty(item, CONST_PROP_BLOCKSOLID)) { resetFlag(TILESTATE_BLOCKSOLID); } @@ -1590,14 +1468,22 @@ void Tile::resetTileFlags(const Item* item) resetFlag(TILESTATE_MAILBOX); } - if (item->getTrashHolder()) { - resetFlag(TILESTATE_TRASHHOLDER); - } - if (item->getBed()) { resetFlag(TILESTATE_BED); } + if (item->getCombatType() == COMBAT_FIREDAMAGE) { + resetFlag(TILESTATE_FIREDAMAGE); + } + + if (item->getCombatType() == COMBAT_ENERGYDAMAGE) { + resetFlag(TILESTATE_ENERGYDAMAGE); + } + + if (item->getCombatType() == COMBAT_EARTHDAMAGE) { + resetFlag(TILESTATE_POISONDAMAGE); + } + const Container* container = item->getContainer(); if (container && container->getDepotLocker()) { resetFlag(TILESTATE_DEPOT); diff --git a/src/tile.h b/src/tile.h index d04ecc7..0b68759 100644 --- a/src/tile.h +++ b/src/tile.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,51 +20,47 @@ #ifndef FS_TILE_H_96C7EE7CF8CD48E59D5D554A181F0C56 #define FS_TILE_H_96C7EE7CF8CD48E59D5D554A181F0C56 +#include + #include "cylinder.h" #include "item.h" #include "tools.h" -#include "spectators.h" class Creature; class Teleport; -class TrashHolder; class Mailbox; +class DepotLocker; class MagicField; class QTreeLeafNode; class BedItem; -using CreatureVector = std::vector; -using ItemVector = std::vector; +typedef std::vector CreatureVector; +typedef std::vector ItemVector; +typedef std::unordered_set SpectatorVec; enum tileflags_t : uint32_t { TILESTATE_NONE = 0, - TILESTATE_FLOORCHANGE_DOWN = 1 << 0, - TILESTATE_FLOORCHANGE_NORTH = 1 << 1, - TILESTATE_FLOORCHANGE_SOUTH = 1 << 2, - TILESTATE_FLOORCHANGE_EAST = 1 << 3, - TILESTATE_FLOORCHANGE_WEST = 1 << 4, - TILESTATE_FLOORCHANGE_SOUTH_ALT = 1 << 5, - TILESTATE_FLOORCHANGE_EAST_ALT = 1 << 6, - TILESTATE_PROTECTIONZONE = 1 << 7, - TILESTATE_NOPVPZONE = 1 << 8, - TILESTATE_NOLOGOUT = 1 << 9, - TILESTATE_PVPZONE = 1 << 10, - TILESTATE_TELEPORT = 1 << 11, - TILESTATE_MAGICFIELD = 1 << 12, - TILESTATE_MAILBOX = 1 << 13, - TILESTATE_TRASHHOLDER = 1 << 14, - TILESTATE_BED = 1 << 15, - TILESTATE_DEPOT = 1 << 16, - TILESTATE_BLOCKSOLID = 1 << 17, - TILESTATE_BLOCKPATH = 1 << 18, - TILESTATE_IMMOVABLEBLOCKSOLID = 1 << 19, - TILESTATE_IMMOVABLEBLOCKPATH = 1 << 20, - TILESTATE_IMMOVABLENOFIELDBLOCKPATH = 1 << 21, - TILESTATE_NOFIELDBLOCKPATH = 1 << 22, - TILESTATE_SUPPORTS_HANGABLE = 1 << 23, - - TILESTATE_FLOORCHANGE = TILESTATE_FLOORCHANGE_DOWN | TILESTATE_FLOORCHANGE_NORTH | TILESTATE_FLOORCHANGE_SOUTH | TILESTATE_FLOORCHANGE_EAST | TILESTATE_FLOORCHANGE_WEST | TILESTATE_FLOORCHANGE_SOUTH_ALT | TILESTATE_FLOORCHANGE_EAST_ALT, + TILESTATE_PROTECTIONZONE = 1 << 0, + TILESTATE_NOPVPZONE = 1 << 1, + TILESTATE_NOLOGOUT = 1 << 2, + TILESTATE_PVPZONE = 1 << 3, + TILESTATE_REFRESH = 1 << 4, + TILESTATE_TELEPORT = 1 << 5, + TILESTATE_MAGICFIELD = 1 << 6, + TILESTATE_MAILBOX = 1 << 7, + TILESTATE_BED = 1 << 8, + TILESTATE_DEPOT = 1 << 9, + TILESTATE_BLOCKSOLID = 1 << 10, + TILESTATE_BLOCKPATH = 1 << 11, + TILESTATE_IMMOVABLEBLOCKSOLID = 1 << 12, + TILESTATE_IMMOVABLEBLOCKPATH = 1 << 13, + TILESTATE_IMMOVABLENOFIELDBLOCKPATH = 1 << 14, + TILESTATE_NOFIELDBLOCKPATH = 1 << 15, + TILESTATE_SUPPORTS_HANGABLE = 1 << 16, + TILESTATE_FIREDAMAGE = 1 << 17, + TILESTATE_POISONDAMAGE = 1 << 18, + TILESTATE_ENERGYDAMAGE = 1 << 19, }; enum ZoneType_t { @@ -150,9 +146,7 @@ class Tile : public Cylinder public: static Tile& nullptr_tile; Tile(uint16_t x, uint16_t y, uint8_t z) : tilePos(x, y, z) {} - virtual ~Tile() { - delete ground; - }; + virtual ~Tile(); // non-copyable Tile(const Tile&) = delete; @@ -166,17 +160,17 @@ class Tile : public Cylinder virtual const CreatureVector* getCreatures() const = 0; virtual CreatureVector* makeCreatures() = 0; - int32_t getThrowRange() const override final { + int32_t getThrowRange() const final { return 0; } - bool isPushable() const override final { + bool isPushable() const final { return false; } MagicField* getFieldItem() const; Teleport* getTeleportItem() const; - TrashHolder* getTrashHolder() const; Mailbox* getMailbox() const; + DepotLocker* getDepotLocker() const; BedItem* getBedItem() const; Creature* getTopCreature() const; @@ -205,13 +199,13 @@ class Tile : public Cylinder bool hasProperty(ITEMPROPERTY prop) const; bool hasProperty(const Item* exclude, ITEMPROPERTY prop) const; - bool hasFlag(uint32_t flag) const { + inline bool hasFlag(uint32_t flag) const { return hasBitSet(flag, this->flags); } - void setFlag(uint32_t flag) { + inline void setFlag(uint32_t flag) { this->flags |= flag; } - void resetFlag(uint32_t flag) { + inline void resetFlag(uint32_t flag) { this->flags &= ~flag; } @@ -228,8 +222,9 @@ class Tile : public Cylinder } bool hasHeight(uint32_t n) const; + int32_t getHeight(); - std::string getDescription(int32_t lookDistance) const override final; + std::string getDescription(int32_t lookDistance) const final; int32_t getClientIndexOfCreature(const Player* player, const Creature* creature) const; int32_t getStackposOfCreature(const Player* player, const Creature* creature) const; @@ -239,37 +234,37 @@ class Tile : public Cylinder 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 override final; - ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const override final; + uint32_t& maxQueryCount, uint32_t flags) const final; + ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const final; Tile* queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) override; - void addThing(Thing* thing) override final; + void addThing(Thing* thing) final; void addThing(int32_t index, Thing* thing) override; - void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override final; - void replaceThing(uint32_t index, Thing* thing) override 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) override final; + void removeThing(Thing* thing, uint32_t count) final; void removeCreature(Creature* creature); - int32_t getThingIndex(const Thing* thing) const override final; - size_t getFirstIndex() const override final; - size_t getLastIndex() const override final; - uint32_t getItemTypeCount(uint16_t itemId, int32_t subType = -1) const override final; - Thing* getThing(size_t index) const override 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; + Thing* getThing(size_t index) const final; - void postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t link = LINK_OWNER) override final; - void postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t link = LINK_OWNER) override 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; - void internalAddThing(Thing* thing) override final; + void internalAddThing(Thing* thing) final; void internalAddThing(uint32_t index, Thing* thing) override; - const Position& getPosition() const override final { + const Position& getPosition() const final { return tilePos; } - bool isRemoved() const override final { + bool isRemoved() const final { return false; } @@ -285,12 +280,13 @@ class Tile : public Cylinder private: void onAddTileItem(Item* item); void onUpdateTileItem(Item* oldItem, const ItemType& oldType, Item* newItem, const ItemType& newType); - void onRemoveTileItem(const SpectatorVec& spectators, const std::vector& oldStackPosVector, Item* item); - void onUpdateTile(const SpectatorVec& spectators); + void onRemoveTileItem(const SpectatorVec& list, const std::vector& oldStackPosVector, Item* item); + void onUpdateTile(const SpectatorVec& list); void setTileFlags(const Item* item); void resetTileFlags(const Item* item); + protected: Item* ground = nullptr; Position tilePos; uint32_t flags = 0; @@ -306,33 +302,29 @@ class DynamicTile : public Tile public: DynamicTile(uint16_t x, uint16_t y, uint8_t z) : Tile(x, y, z) {} - ~DynamicTile() { - for (Item* item : items) { - item->decrementReferenceCounter(); - } - } + ~DynamicTile(); // non-copyable DynamicTile(const DynamicTile&) = delete; DynamicTile& operator=(const DynamicTile&) = delete; - TileItemVector* getItemList() override { + TileItemVector* getItemList() final { return &items; } - const TileItemVector* getItemList() const override { + const TileItemVector* getItemList() const final { return &items; } - TileItemVector* makeItemList() override { + TileItemVector* makeItemList() final { return &items; } - CreatureVector* getCreatures() override { + CreatureVector* getCreatures() final { return &creatures; } - const CreatureVector* getCreatures() const override { + const CreatureVector* getCreatures() const final { return &creatures; } - CreatureVector* makeCreatures() override { + CreatureVector* makeCreatures() final { return &creatures; } }; @@ -346,38 +338,32 @@ class StaticTile final : public Tile public: StaticTile(uint16_t x, uint16_t y, uint8_t z) : Tile(x, y, z) {} - ~StaticTile() { - if (items) { - for (Item* item : *items) { - item->decrementReferenceCounter(); - } - } - } + ~StaticTile(); // non-copyable StaticTile(const StaticTile&) = delete; StaticTile& operator=(const StaticTile&) = delete; - TileItemVector* getItemList() override { + TileItemVector* getItemList() final { return items.get(); } - const TileItemVector* getItemList() const override { + const TileItemVector* getItemList() const final { return items.get(); } - TileItemVector* makeItemList() override { + TileItemVector* makeItemList() final { if (!items) { items.reset(new TileItemVector); } return items.get(); } - CreatureVector* getCreatures() override { + CreatureVector* getCreatures() final { return creatures.get(); } - const CreatureVector* getCreatures() const override { + const CreatureVector* getCreatures() const final { return creatures.get(); } - CreatureVector* makeCreatures() override { + CreatureVector* makeCreatures() final { if (!creatures) { creatures.reset(new CreatureVector); } @@ -385,4 +371,25 @@ class StaticTile final : public Tile } }; +inline Tile::~Tile() +{ + delete ground; +} + +inline StaticTile::~StaticTile() +{ + if (items) { + for (Item* item : *items) { + item->decrementReferenceCounter(); + } + } +} + +inline DynamicTile::~DynamicTile() +{ + for (Item* item : items) { + item->decrementReferenceCounter(); + } +} + #endif diff --git a/src/tools.cpp b/src/tools.cpp index 81a9834..506f512 100644 --- a/src/tools.cpp +++ b/src/tools.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -73,7 +73,7 @@ void printXMLError(const std::string& where, const std::string& fileName, const std::cout << '^' << std::endl; } -static uint32_t circularShift(int bits, uint32_t value) +inline static uint32_t circularShift(int bits, uint32_t value) { return (value << bits) | (value >> (32 - bits)); } @@ -186,6 +186,95 @@ std::string transformToSHA1(const std::string& input) return std::string(hexstring, 40); } +uint8_t getLiquidColor(uint8_t type) +{ + uint8_t result = FLUID_COLOR_NONE; + switch (type) + { + case FLUID_WATER: + result = FLUID_COLOR_BLUE; + break; + case FLUID_NONE: + result = FLUID_COLOR_NONE; + break; + case FLUID_SLIME: + result = FLUID_COLOR_GREEN; + break; + case FLUID_BEER: + case FLUID_MUD: + case FLUID_OIL: + case FLUID_RUM: + result = FLUID_COLOR_BROWN; + break; + case FLUID_MILK: + case FLUID_COCONUTMILK: + result = FLUID_COLOR_WHITE; + break; + case FLUID_WINE: + case FLUID_MANAFLUID: + result = FLUID_COLOR_PURPLE; + break; + case FLUID_BLOOD: + case FLUID_LIFEFLUID: + result = FLUID_COLOR_RED; + break; + case FLUID_URINE: + case FLUID_LEMONADE: + case FLUID_FRUITJUICE: + result = FLUID_COLOR_YELLOW; + break; + default: + result = FLUID_COLOR_NONE; + break; + } + return result; +} + +void extractArticleAndName(std::string& data, std::string& article, std::string& name) +{ + std::string xarticle = data.substr(0, 3); + if (xarticle == "an ") + { + name = data.substr(3, data.size()); + article = "an"; + } else { + xarticle = data.substr(0, 2); + if (xarticle == "a ") + { + name = data.substr(2, data.size()); + article = "a"; + } else { + name = data; + article = ""; + } + } +} + +std::string pluralizeString(std::string str) +{ + if (str == "meat") return "meat"; + + int n = str.length(); + char ch = str[n - 1]; + char ch2 = str[n - 2]; + + std::string str2; + if (ch == 'y') + str2 = str.substr(0, n - 1) + "ies"; + else if (ch == 'o' || ch == 's' || ch == 'x') + str2 = str + "es"; + else if (ch == 'h'&& ch2 == 'c') + str2 = str + "es"; + else if (ch == 'f') + str2 = str.substr(0, n - 1) + "ves"; + else if (ch == 'e'&&ch2 == 'f') + str2 = str.substr(0, n - 2) + "ves"; + else + str2 = str + "s"; + + return str2; +} + std::string generateToken(const std::string& key, uint32_t ticks) { // generate message from ticks @@ -271,9 +360,9 @@ std::string asUpperCaseString(std::string source) return source; } -StringVector explodeString(const std::string& inString, const std::string& separator, int32_t limit/* = -1*/) +StringVec explodeString(const std::string& inString, const std::string& separator, int32_t limit/* = -1*/) { - StringVector returnVector; + StringVec returnVector; std::string::size_type start = 0, end = 0; while (--limit != -1 && (end = inString.find(separator, start)) != std::string::npos) { @@ -285,9 +374,9 @@ StringVector explodeString(const std::string& inString, const std::string& separ return returnVector; } -IntegerVector vectorAtoi(const StringVector& stringVector) +IntegerVec vectorAtoi(const StringVec& stringVector) { - IntegerVector returnVector; + IntegerVec returnVector; for (const auto& string : stringVector) { returnVector.push_back(std::stoi(string)); } @@ -497,14 +586,42 @@ Direction getDirectionTo(const Position& from, const Position& to) return dir; } -using MagicEffectNames = std::unordered_map; -using ShootTypeNames = std::unordered_map; -using CombatTypeNames = std::unordered_map>; -using AmmoTypeNames = std::unordered_map; -using WeaponActionNames = std::unordered_map; -using SkullNames = std::unordered_map; +struct MagicEffectNames { + const char* name; + MagicEffectClasses effect; +}; -MagicEffectNames magicEffectNames = { +struct ShootTypeNames { + const char* name; + ShootType_t shoot; +}; + +struct CombatTypeNames { + const char* name; + CombatType_t combat; +}; + +struct AmmoTypeNames { + const char* name; + Ammo_t ammoType; +}; + +struct WeaponActionNames { + const char* name; + WeaponAction_t weaponAction; +}; + +struct SkullNames { + const char* name; + Skulls_t skull; +}; + +struct FluidNames { + const char* name; + FluidTypes_t fluidType; +}; + +MagicEffectNames magicEffectNames[] = { {"redspark", CONST_ME_DRAWBLOOD}, {"bluebubble", CONST_ME_LOSEENERGY}, {"poff", CONST_ME_POFF}, @@ -532,63 +649,9 @@ MagicEffectNames magicEffectNames = { {"whitenote", CONST_ME_SOUND_WHITE}, {"bubbles", CONST_ME_BUBBLES}, {"dice", CONST_ME_CRAPS}, - {"giftwraps", CONST_ME_GIFT_WRAPS}, - {"yellowfirework", CONST_ME_FIREWORK_YELLOW}, - {"redfirework", CONST_ME_FIREWORK_RED}, - {"bluefirework", CONST_ME_FIREWORK_BLUE}, - {"stun", CONST_ME_STUN}, - {"sleep", CONST_ME_SLEEP}, - {"watercreature", CONST_ME_WATERCREATURE}, - {"groundshaker", CONST_ME_GROUNDSHAKER}, - {"hearts", CONST_ME_HEARTS}, - {"fireattack", CONST_ME_FIREATTACK}, - {"energyarea", CONST_ME_ENERGYAREA}, - {"smallclouds", CONST_ME_SMALLCLOUDS}, - {"holydamage", CONST_ME_HOLYDAMAGE}, - {"bigclouds", CONST_ME_BIGCLOUDS}, - {"icearea", CONST_ME_ICEAREA}, - {"icetornado", CONST_ME_ICETORNADO}, - {"iceattack", CONST_ME_ICEATTACK}, - {"stones", CONST_ME_STONES}, - {"smallplants", CONST_ME_SMALLPLANTS}, - {"carniphila", CONST_ME_CARNIPHILA}, - {"purpleenergy", CONST_ME_PURPLEENERGY}, - {"yellowenergy", CONST_ME_YELLOWENERGY}, - {"holyarea", CONST_ME_HOLYAREA}, - {"bigplants", CONST_ME_BIGPLANTS}, - {"cake", CONST_ME_CAKE}, - {"giantice", CONST_ME_GIANTICE}, - {"watersplash", CONST_ME_WATERSPLASH}, - {"plantattack", CONST_ME_PLANTATTACK}, - {"tutorialarrow", CONST_ME_TUTORIALARROW}, - {"tutorialsquare", CONST_ME_TUTORIALSQUARE}, - {"mirrorhorizontal", CONST_ME_MIRRORHORIZONTAL}, - {"mirrorvertical", CONST_ME_MIRRORVERTICAL}, - {"skullhorizontal", CONST_ME_SKULLHORIZONTAL}, - {"skullvertical", CONST_ME_SKULLVERTICAL}, - {"assassin", CONST_ME_ASSASSIN}, - {"stepshorizontal", CONST_ME_STEPSHORIZONTAL}, - {"bloodysteps", CONST_ME_BLOODYSTEPS}, - {"stepsvertical", CONST_ME_STEPSVERTICAL}, - {"yalaharighost", CONST_ME_YALAHARIGHOST}, - {"bats", CONST_ME_BATS}, - {"smoke", CONST_ME_SMOKE}, - {"insects", CONST_ME_INSECTS}, - {"dragonhead", CONST_ME_DRAGONHEAD}, - {"orcshaman", CONST_ME_ORCSHAMAN}, - {"orcshamanfire", CONST_ME_ORCSHAMAN_FIRE}, - {"thunder", CONST_ME_THUNDER}, - {"ferumbras", CONST_ME_FERUMBRAS}, - {"confettihorizontal", CONST_ME_CONFETTI_HORIZONTAL}, - {"confettivertical", CONST_ME_CONFETTI_VERTICAL}, - {"blacksmoke", CONST_ME_BLACKSMOKE}, - {"redsmoke", CONST_ME_REDSMOKE}, - {"yellowsmoke", CONST_ME_YELLOWSMOKE}, - {"greensmoke", CONST_ME_GREENSMOKE}, - {"purplesmoke", CONST_ME_PURPLESMOKE}, }; -ShootTypeNames shootTypeNames = { +ShootTypeNames shootTypeNames[] = { {"spear", CONST_ANI_SPEAR}, {"bolt", CONST_ANI_BOLT}, {"arrow", CONST_ANI_ARROW}, @@ -604,59 +667,22 @@ ShootTypeNames shootTypeNames = { {"snowball", CONST_ANI_SNOWBALL}, {"powerbolt", CONST_ANI_POWERBOLT}, {"poison", CONST_ANI_POISON}, - {"infernalbolt", CONST_ANI_INFERNALBOLT}, - {"huntingspear", CONST_ANI_HUNTINGSPEAR}, - {"enchantedspear", CONST_ANI_ENCHANTEDSPEAR}, - {"redstar", CONST_ANI_REDSTAR}, - {"greenstar", CONST_ANI_GREENSTAR}, - {"royalspear", CONST_ANI_ROYALSPEAR}, - {"sniperarrow", CONST_ANI_SNIPERARROW}, - {"onyxarrow", CONST_ANI_ONYXARROW}, - {"piercingbolt", CONST_ANI_PIERCINGBOLT}, - {"whirlwindsword", CONST_ANI_WHIRLWINDSWORD}, - {"whirlwindaxe", CONST_ANI_WHIRLWINDAXE}, - {"whirlwindclub", CONST_ANI_WHIRLWINDCLUB}, - {"etherealspear", CONST_ANI_ETHEREALSPEAR}, - {"ice", CONST_ANI_ICE}, - {"earth", CONST_ANI_EARTH}, - {"holy", CONST_ANI_HOLY}, - {"suddendeath", CONST_ANI_SUDDENDEATH}, - {"flasharrow", CONST_ANI_FLASHARROW}, - {"flammingarrow", CONST_ANI_FLAMMINGARROW}, - {"shiverarrow", CONST_ANI_SHIVERARROW}, - {"energyball", CONST_ANI_ENERGYBALL}, - {"smallice", CONST_ANI_SMALLICE}, - {"smallholy", CONST_ANI_SMALLHOLY}, - {"smallearth", CONST_ANI_SMALLEARTH}, - {"eartharrow", CONST_ANI_EARTHARROW}, - {"explosion", CONST_ANI_EXPLOSION}, - {"cake", CONST_ANI_CAKE}, - {"tarsalarrow", CONST_ANI_TARSALARROW}, - {"vortexbolt", CONST_ANI_VORTEXBOLT}, - {"prismaticbolt", CONST_ANI_PRISMATICBOLT}, - {"crystallinearrow", CONST_ANI_CRYSTALLINEARROW}, - {"drillbolt", CONST_ANI_DRILLBOLT}, - {"envenomedarrow", CONST_ANI_ENVENOMEDARROW}, - {"gloothspear", CONST_ANI_GLOOTHSPEAR}, - {"simplearrow", CONST_ANI_SIMPLEARROW}, }; -CombatTypeNames combatTypeNames = { - {COMBAT_PHYSICALDAMAGE, "physical"}, - {COMBAT_ENERGYDAMAGE, "energy"}, - {COMBAT_EARTHDAMAGE, "earth"}, - {COMBAT_FIREDAMAGE, "fire"}, - {COMBAT_UNDEFINEDDAMAGE, "undefined"}, - {COMBAT_LIFEDRAIN, "lifedrain"}, - {COMBAT_MANADRAIN, "manadrain"}, - {COMBAT_HEALING, "healing"}, - {COMBAT_DROWNDAMAGE, "drown"}, - {COMBAT_ICEDAMAGE, "ice"}, - {COMBAT_HOLYDAMAGE, "holy"}, - {COMBAT_DEATHDAMAGE, "death"}, +CombatTypeNames combatTypeNames[] = { + {"physical", COMBAT_PHYSICALDAMAGE}, + {"energy", COMBAT_ENERGYDAMAGE}, + {"drown", COMBAT_DROWNDAMAGE}, + {"earth", COMBAT_EARTHDAMAGE}, + {"poison", COMBAT_EARTHDAMAGE}, + {"fire", COMBAT_FIREDAMAGE}, + {"undefined", COMBAT_UNDEFINEDDAMAGE}, + {"lifedrain", COMBAT_LIFEDRAIN}, + {"manadrain", COMBAT_MANADRAIN}, + {"healing", COMBAT_HEALING}, }; -AmmoTypeNames ammoTypeNames = { +AmmoTypeNames ammoTypeNames[] = { {"spear", AMMO_SPEAR}, {"bolt", AMMO_BOLT}, {"arrow", AMMO_ARROW}, @@ -668,114 +694,119 @@ AmmoTypeNames ammoTypeNames = { {"largerock", AMMO_STONE}, {"snowball", AMMO_SNOWBALL}, {"powerbolt", AMMO_BOLT}, - {"infernalbolt", AMMO_BOLT}, - {"huntingspear", AMMO_SPEAR}, - {"enchantedspear", AMMO_SPEAR}, - {"royalspear", AMMO_SPEAR}, - {"sniperarrow", AMMO_ARROW}, - {"onyxarrow", AMMO_ARROW}, - {"piercingbolt", AMMO_BOLT}, - {"etherealspear", AMMO_SPEAR}, - {"flasharrow", AMMO_ARROW}, - {"flammingarrow", AMMO_ARROW}, - {"shiverarrow", AMMO_ARROW}, - {"eartharrow", AMMO_ARROW}, }; -WeaponActionNames weaponActionNames = { +WeaponActionNames weaponActionNames[] = { {"move", WEAPONACTION_MOVE}, {"removecharge", WEAPONACTION_REMOVECHARGE}, {"removecount", WEAPONACTION_REMOVECOUNT}, }; -SkullNames skullNames = { +SkullNames skullNames[] = { {"none", SKULL_NONE}, {"yellow", SKULL_YELLOW}, {"green", SKULL_GREEN}, {"white", SKULL_WHITE}, {"red", SKULL_RED}, - {"black", SKULL_BLACK}, - {"orange", SKULL_ORANGE}, +}; + +FluidNames fluidNames[] = { + {"none", FLUID_NONE}, + {"water", FLUID_WATER}, + {"wine", FLUID_WINE}, + {"beer", FLUID_BEER}, + {"mud", FLUID_MUD}, + {"blood", FLUID_BLOOD}, + {"slime", FLUID_SLIME}, + {"oil", FLUID_OIL}, + {"urine", FLUID_URINE}, + {"milk", FLUID_MILK}, + {"manafluid", FLUID_MANAFLUID}, + {"lifefluid", FLUID_LIFEFLUID}, + {"lemonade", FLUID_LEMONADE}, + {"rum", FLUID_RUM}, + {"coconutmilk", FLUID_COCONUTMILK}, + {"fruitjuice", FLUID_FRUITJUICE} }; MagicEffectClasses getMagicEffect(const std::string& strValue) { - auto magicEffect = magicEffectNames.find(strValue); - if (magicEffect != magicEffectNames.end()) { - return magicEffect->second; + for (auto& magicEffectName : magicEffectNames) { + if (strcasecmp(strValue.c_str(), magicEffectName.name) == 0) { + return magicEffectName.effect; + } } return CONST_ME_NONE; } ShootType_t getShootType(const std::string& strValue) { - auto shootType = shootTypeNames.find(strValue); - if (shootType != shootTypeNames.end()) { - return shootType->second; + for (size_t i = 0, size = sizeof(shootTypeNames) / sizeof(ShootTypeNames); i < size; ++i) { + if (strcasecmp(strValue.c_str(), shootTypeNames[i].name) == 0) { + return shootTypeNames[i].shoot; + } } return CONST_ANI_NONE; } +CombatType_t getCombatType(const std::string& strValue) +{ + for (size_t i = 0, size = sizeof(combatTypeNames) / sizeof(CombatTypeNames); i < size; ++i) { + if (strcasecmp(strValue.c_str(), combatTypeNames[i].name) == 0) { + return combatTypeNames[i].combat; + } + } + return COMBAT_NONE; +} + std::string getCombatName(CombatType_t combatType) { - auto combatName = combatTypeNames.find(combatType); - if (combatName != combatTypeNames.end()) { - return combatName->second; + for (size_t i = 0, size = sizeof(combatTypeNames) / sizeof(CombatTypeNames); i < size; ++i) { + if (combatTypeNames[i].combat == combatType) { + return combatTypeNames[i].name; + } } return "unknown"; } Ammo_t getAmmoType(const std::string& strValue) { - auto ammoType = ammoTypeNames.find(strValue); - if (ammoType != ammoTypeNames.end()) { - return ammoType->second; + for (size_t i = 0, size = sizeof(ammoTypeNames) / sizeof(AmmoTypeNames); i < size; ++i) { + if (strcasecmp(strValue.c_str(), ammoTypeNames[i].name) == 0) { + return ammoTypeNames[i].ammoType; + } } return AMMO_NONE; } WeaponAction_t getWeaponAction(const std::string& strValue) { - auto weaponAction = weaponActionNames.find(strValue); - if (weaponAction != weaponActionNames.end()) { - return weaponAction->second; + for (size_t i = 0, size = sizeof(weaponActionNames) / sizeof(WeaponActionNames); i < size; ++i) { + if (strcasecmp(strValue.c_str(), weaponActionNames[i].name) == 0) { + return weaponActionNames[i].weaponAction; + } } return WEAPONACTION_NONE; } Skulls_t getSkullType(const std::string& strValue) { - auto skullType = skullNames.find(strValue); - if (skullType != skullNames.end()) { - return skullType->second; + for (size_t i = 0, size = sizeof(skullNames) / sizeof(SkullNames); i < size; ++i) { + if (strcasecmp(strValue.c_str(), skullNames[i].name) == 0) { + return skullNames[i].skull; + } } return SKULL_NONE; } -std::string getSpecialSkillName(uint8_t skillid) +FluidTypes_t getFluidType(const std::string& strValue) { - switch (skillid) { - case SPECIALSKILL_CRITICALHITCHANCE: - return "critical hit chance"; - - case SPECIALSKILL_CRITICALHITAMOUNT: - return "critical extra damage"; - - case SPECIALSKILL_LIFELEECHCHANCE: - return "hitpoints leech chance"; - - case SPECIALSKILL_LIFELEECHAMOUNT: - return "hitpoints leech amount"; - - case SPECIALSKILL_MANALEECHCHANCE: - return "manapoints leech chance"; - - case SPECIALSKILL_MANALEECHAMOUNT: - return "mana points leech amount"; - - default: - return "unknown"; + for (size_t i = 0, size = sizeof(fluidNames) / sizeof(FluidNames); i < size; ++i) { + if (strcasecmp(strValue.c_str(), fluidNames[i].name) == 0) { + return fluidNames[i].fluidType; + } } + return FLUID_NONE; } std::string getSkillName(uint8_t skillid) @@ -813,31 +844,6 @@ std::string getSkillName(uint8_t skillid) } } -uint32_t adlerChecksum(const uint8_t* data, size_t length) -{ - if (length > NETWORKMESSAGE_MAXSIZE) { - return 0; - } - - const uint16_t adler = 65521; - - uint32_t a = 1, b = 0; - - while (length > 0) { - size_t tmp = length > 5552 ? 5552 : length; - length -= tmp; - - do { - a += *data++; - b += a; - } while (--tmp); - - a %= adler; - b %= adler; - } - - return (b << 16) | a; -} std::string ucfirst(std::string str) { @@ -911,12 +917,6 @@ size_t combatTypeToIndex(CombatType_t combatType) return 7; case COMBAT_DROWNDAMAGE: return 8; - case COMBAT_ICEDAMAGE: - return 9; - case COMBAT_HOLYDAMAGE: - return 10; - case COMBAT_DEATHDAMAGE: - return 11; default: return 0; } @@ -927,32 +927,12 @@ CombatType_t indexToCombatType(size_t v) return static_cast(1 << v); } -uint8_t serverFluidToClient(uint8_t serverFluid) -{ - uint8_t size = sizeof(clientToServerFluidMap) / sizeof(uint8_t); - for (uint8_t i = 0; i < size; ++i) { - if (clientToServerFluidMap[i] == serverFluid) { - return i; - } - } - return 0; -} - -uint8_t clientFluidToServer(uint8_t clientFluid) -{ - uint8_t size = sizeof(clientToServerFluidMap) / sizeof(uint8_t); - if (clientFluid >= size) { - return 0; - } - return clientToServerFluidMap[clientFluid]; -} - itemAttrTypes stringToItemAttribute(const std::string& str) { if (str == "aid") { return ITEM_ATTRIBUTE_ACTIONID; - } else if (str == "uid") { - return ITEM_ATTRIBUTE_UNIQUEID; + } else if (str == "mid") { + return ITEM_ATTRIBUTE_MOVEMENTID; } else if (str == "description") { return ITEM_ATTRIBUTE_DESCRIPTION; } else if (str == "text") { @@ -973,12 +953,8 @@ itemAttrTypes stringToItemAttribute(const std::string& str) return ITEM_ATTRIBUTE_ATTACK; } else if (str == "defense") { return ITEM_ATTRIBUTE_DEFENSE; - } else if (str == "extradefense") { - return ITEM_ATTRIBUTE_EXTRADEFENSE; } else if (str == "armor") { return ITEM_ATTRIBUTE_ARMOR; - } else if (str == "hitchance") { - return ITEM_ATTRIBUTE_HITCHANCE; } else if (str == "shootrange") { return ITEM_ATTRIBUTE_SHOOTRANGE; } else if (str == "owner") { @@ -1181,8 +1157,8 @@ const char* getReturnMessage(ReturnValue value) case RETURNVALUE_YOUNEEDTOSPLITYOURSPEARS: return "You need to split your spears first."; - case RETURNVALUE_NAMEISTOOAMBIGUOUS: - return "Player name is ambiguous."; + case RETURNVALUE_NAMEISTOOAMBIGIOUS: + return "Name is too ambigious."; case RETURNVALUE_CANONLYUSEONESHIELD: return "You may use only one shield."; @@ -1193,12 +1169,6 @@ const char* getReturnMessage(ReturnValue value) case RETURNVALUE_YOUARENOTTHEOWNER: return "You are not the owner."; - case RETURNVALUE_NOSUCHRAIDEXISTS: - return "No such raid exists."; - - case RETURNVALUE_ANOTHERRAIDISALREADYEXECUTING: - return "Another raid is already executing."; - case RETURNVALUE_TRADEPLAYERFARAWAY: return "Trade player is too far away."; @@ -1219,23 +1189,23 @@ const char* getReturnMessage(ReturnValue value) } } -int64_t OTSYS_TIME() +void getFilesInDirectory(const boost::filesystem::path& root, const std::string& ext, std::vector& ret) { - return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); -} - -SpellGroup_t stringToSpellGroup(std::string value) -{ - std::string tmpStr = asLowerCaseString(value); - if (tmpStr == "attack" || tmpStr == "1") { - return SPELLGROUP_ATTACK; - } else if (tmpStr == "healing" || tmpStr == "2") { - return SPELLGROUP_HEALING; - } else if (tmpStr == "support" || tmpStr == "3") { - return SPELLGROUP_SUPPORT; - } else if (tmpStr == "special" || tmpStr == "4") { - return SPELLGROUP_SPECIAL; + if (!boost::filesystem::exists(root)) { + return; } - return SPELLGROUP_NONE; + if (boost::filesystem::is_directory(root)) + { + boost::filesystem::recursive_directory_iterator it(root); + boost::filesystem::recursive_directory_iterator endit; + while (it != endit) + { + if (boost::filesystem::is_regular_file(*it) && it->path().extension() == ext) + { + ret.push_back(it->path().filename()); + } + ++it; + } + } } diff --git a/src/tools.h b/src/tools.h index f59004e..8f05a93 100644 --- a/src/tools.h +++ b/src/tools.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,6 +21,7 @@ #define FS_TOOLS_H_5F9A9742DA194628830AA1C64909AE43 #include +#include #include "position.h" #include "const.h" @@ -30,7 +31,10 @@ void printXMLError(const std::string& where, const std::string& fileName, const std::string transformToSHA1(const std::string& input); std::string generateToken(const std::string& key, uint32_t ticks); +uint8_t getLiquidColor(uint8_t type); +void extractArticleAndName(std::string& data, std::string& article, std::string& name); +std::string pluralizeString(std::string str); void replaceString(std::string& str, const std::string& sought, const std::string& replacement); void trim_right(std::string& source, char t); void trim_left(std::string& source, char t); @@ -38,15 +42,20 @@ void toLowerCaseString(std::string& source); std::string asLowerCaseString(std::string source); std::string asUpperCaseString(std::string source); -using StringVector = std::vector; -using IntegerVector = std::vector; +typedef std::vector StringVec; +typedef std::vector IntegerVec; -StringVector explodeString(const std::string& inString, const std::string& separator, int32_t limit = -1); -IntegerVector vectorAtoi(const StringVector& stringVector); -constexpr bool hasBitSet(uint32_t flag, uint32_t flags) { +StringVec explodeString(const std::string& inString, const std::string& separator, int32_t limit = -1); +IntegerVec vectorAtoi(const StringVec& stringVector); +inline bool hasBitSet(uint32_t flag, uint32_t flags) { return (flags & flag) != 0; } +inline bool IsDigit(char c) +{ + return ('0' <= c && c <= '9'); +} + std::mt19937& getRandomGenerator(); int32_t uniform_random(int32_t minNumber, int32_t maxNumber); int32_t normal_random(int32_t minNumber, int32_t maxNumber); @@ -68,14 +77,13 @@ MagicEffectClasses getMagicEffect(const std::string& strValue); ShootType_t getShootType(const std::string& strValue); Ammo_t getAmmoType(const std::string& strValue); WeaponAction_t getWeaponAction(const std::string& strValue); +CombatType_t getCombatType(const std::string& strValue); Skulls_t getSkullType(const std::string& strValue); +FluidTypes_t getFluidType(const std::string& strValue); std::string getCombatName(CombatType_t combatType); -std::string getSpecialSkillName(uint8_t skillid); std::string getSkillName(uint8_t skillid); -uint32_t adlerChecksum(const uint8_t* data, size_t length); - std::string ucfirst(std::string str); std::string ucwords(std::string str); bool booleanString(const std::string& str); @@ -85,15 +93,15 @@ std::string getWeaponName(WeaponType_t weaponType); size_t combatTypeToIndex(CombatType_t combatType); CombatType_t indexToCombatType(size_t v); -uint8_t serverFluidToClient(uint8_t serverFluid); -uint8_t clientFluidToServer(uint8_t clientFluid); - itemAttrTypes stringToItemAttribute(const std::string& str); const char* getReturnMessage(ReturnValue value); -int64_t OTSYS_TIME(); +void getFilesInDirectory(const boost::filesystem::path& root, const std::string& ext, std::vector& ret); -SpellGroup_t stringToSpellGroup(std::string value); +inline int64_t OTSYS_TIME() +{ + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); +} #endif diff --git a/src/town.h b/src/town.h index be3c9e9..9e50720 100644 --- a/src/town.h +++ b/src/town.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -50,7 +50,7 @@ class Town Position templePosition; }; -using TownMap = std::map; +typedef std::map TownMap; class Towns { diff --git a/src/trashholder.cpp b/src/trashholder.cpp deleted file mode 100644 index 5d90758..0000000 --- a/src/trashholder.cpp +++ /dev/null @@ -1,102 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "otpch.h" - -#include "trashholder.h" -#include "game.h" - -extern Game g_game; - -ReturnValue TrashHolder::queryAdd(int32_t, const Thing&, uint32_t, uint32_t, Creature*) const -{ - return RETURNVALUE_NOERROR; -} - -ReturnValue TrashHolder::queryMaxCount(int32_t, const Thing&, uint32_t count, uint32_t& maxQueryCount, uint32_t) const -{ - maxQueryCount = std::max(1, count); - return RETURNVALUE_NOERROR; -} - -ReturnValue TrashHolder::queryRemove(const Thing&, uint32_t, uint32_t) const -{ - return RETURNVALUE_NOTPOSSIBLE; -} - -Cylinder* TrashHolder::queryDestination(int32_t&, const Thing&, Item**, uint32_t&) -{ - return this; -} - -void TrashHolder::addThing(Thing* thing) -{ - return addThing(0, thing); -} - -void TrashHolder::addThing(int32_t, Thing* thing) -{ - Item* item = thing->getItem(); - if (!item) { - return; - } - - if (item == this || !item->hasProperty(CONST_PROP_MOVEABLE)) { - return; - } - - const ItemType& it = Item::items[id]; - if (item->isHangable() && it.isGroundTile()) { - Tile* tile = dynamic_cast(getParent()); - if (tile && tile->hasFlag(TILESTATE_SUPPORTS_HANGABLE)) { - return; - } - } - - g_game.internalRemoveItem(item); - - if (it.magicEffect != CONST_ME_NONE) { - g_game.addMagicEffect(getPosition(), it.magicEffect); - } -} - -void TrashHolder::updateThing(Thing*, uint16_t, uint32_t) -{ - // -} - -void TrashHolder::replaceThing(uint32_t, Thing*) -{ - // -} - -void TrashHolder::removeThing(Thing*, uint32_t) -{ - // -} - -void TrashHolder::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t) -{ - getParent()->postAddNotification(thing, oldParent, index, LINK_PARENT); -} - -void TrashHolder::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t) -{ - getParent()->postRemoveNotification(thing, newParent, index, LINK_PARENT); -} diff --git a/src/trashholder.h b/src/trashholder.h deleted file mode 100644 index a8d1f46..0000000 --- a/src/trashholder.h +++ /dev/null @@ -1,57 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_TRASHHOLDER_H_BA162024D67B4D388147F5EE06F33098 -#define FS_TRASHHOLDER_H_BA162024D67B4D388147F5EE06F33098 - -#include "item.h" -#include "cylinder.h" -#include "const.h" - -class TrashHolder final : public Item, public Cylinder -{ - public: - explicit TrashHolder(uint16_t itemId) : Item(itemId) {} - - TrashHolder* getTrashHolder() override { - return this; - } - const TrashHolder* getTrashHolder() const override { - return this; - } - - //cylinder implementations - 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 override; - ReturnValue queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const override; - Cylinder* queryDestination(int32_t& index, const Thing& thing, Item** destItem, uint32_t& flags) override; - - void addThing(Thing* thing) override; - void addThing(int32_t index, Thing* thing) override; - - void updateThing(Thing* thing, uint16_t itemId, uint32_t count) override; - void replaceThing(uint32_t index, Thing* thing) override; - - void removeThing(Thing* thing, uint32_t count) override; - - 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; -}; - -#endif diff --git a/src/vocation.cpp b/src/vocation.cpp index 015fdac..98a9973 100644 --- a/src/vocation.cpp +++ b/src/vocation.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -46,12 +46,15 @@ bool Vocations::loadFromXml() std::forward_as_tuple(id), std::forward_as_tuple(id)); Vocation& voc = res.first->second; - if ((attr = vocationNode.attribute("name"))) { - voc.name = attr.as_string(); + if (!(attr = vocationNode.attribute("flagid"))) { + std::cout << "[Warning - Vocations::loadFromXml] Missing vocation flag id" << std::endl; + continue; } - if ((attr = vocationNode.attribute("clientid"))) { - voc.clientId = pugi::cast(attr.value()); + voc.flagid = pugi::cast(attr.value()); + + if ((attr = vocationNode.attribute("name"))) { + voc.name = attr.as_string(); } if ((attr = vocationNode.attribute("description"))) { @@ -123,26 +126,6 @@ bool Vocations::loadFromXml() } else { std::cout << "[Notice - Vocations::loadFromXml] Missing skill id for vocation: " << voc.id << std::endl; } - } else if (strcasecmp(childNode.name(), "formula") == 0) { - pugi::xml_attribute meleeDamageAttribute = childNode.attribute("meleeDamage"); - if (meleeDamageAttribute) { - voc.meleeDamageMultiplier = pugi::cast(meleeDamageAttribute.value()); - } - - pugi::xml_attribute distDamageAttribute = childNode.attribute("distDamage"); - if (distDamageAttribute) { - voc.distDamageMultiplier = pugi::cast(distDamageAttribute.value()); - } - - pugi::xml_attribute defenseAttribute = childNode.attribute("defense"); - if (defenseAttribute) { - voc.defenseMultiplier = pugi::cast(defenseAttribute.value()); - } - - pugi::xml_attribute armorAttribute = childNode.attribute("armor"); - if (armorAttribute) { - voc.armorMultiplier = pugi::cast(armorAttribute.value()); - } } } } @@ -204,7 +187,7 @@ uint64_t Vocation::getReqMana(uint32_t magLevel) return it->second; } - uint64_t reqMana = static_cast(1600 * std::pow(manaMultiplier, static_cast(magLevel) - 1)); + uint64_t reqMana = static_cast(400 * std::pow(manaMultiplier, static_cast(magLevel) - 1)); uint32_t modResult = reqMana % 20; if (modResult < 10) { reqMana -= modResult; diff --git a/src/vocation.h b/src/vocation.h index 757d45a..b6bc48e 100644 --- a/src/vocation.h +++ b/src/vocation.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,9 +40,8 @@ class Vocation uint16_t getId() const { return id; } - - uint8_t getClientId() const { - return clientId; + uint16_t getFlagId() const { + return flagid; } uint32_t getHPGain() const { @@ -86,12 +85,7 @@ class Vocation return fromVocation; } - float meleeDamageMultiplier = 1.0f; - float distDamageMultiplier = 1.0f; - float defenseMultiplier = 1.0f; - float armorMultiplier = 1.0f; - - private: + protected: friend class Vocations; std::map cacheMana; @@ -112,13 +106,13 @@ class Vocation uint32_t gainHP = 5; uint32_t fromVocation = VOCATION_NONE; uint32_t attackSpeed = 1500; - uint32_t baseSpeed = 220; + uint32_t baseSpeed = 70; uint16_t id; + uint16_t flagid; uint16_t gainSoulTicks = 120; uint8_t soulMax = 100; - uint8_t clientId = 0; static uint32_t skillBase[SKILL_LAST + 1]; }; diff --git a/src/waitlist.cpp b/src/waitlist.cpp index 355e88b..2743629 100644 --- a/src/waitlist.cpp +++ b/src/waitlist.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,71 +26,26 @@ extern ConfigManager g_config; extern Game g_game; - -namespace { - -struct Wait +WaitListIterator WaitingList::findClient(const Player* player, uint32_t& slot) { - constexpr Wait(std::size_t timeout, uint32_t playerGUID) : - timeout(timeout), playerGUID(playerGUID) {} - - std::size_t timeout; - uint32_t playerGUID; -}; - -using WaitList = std::list; - -void cleanupList(WaitList& list) -{ - int64_t time = OTSYS_TIME(); - - auto it = list.begin(), end = list.end(); - while (it != end) { - if ((it->timeout - time) <= 0) { - it = list.erase(it); - } else { - ++it; + slot = 1; + for (auto it = priorityWaitList.begin(), end = priorityWaitList.end(); it != end; ++it) { + if (it->playerGUID == player->getGUID()) { + return it; } + ++slot; } -} -std::size_t getTimeout(std::size_t slot) -{ - //timeout is set to 15 seconds longer than expected retry attempt - return WaitingList::getTime(slot) + 15; -} - -} // namespace - -struct WaitListInfo -{ - WaitList priorityWaitList; - WaitList waitList; - - std::pair findClient(const Player *player) { - std::size_t slot = 1; - for (auto it = priorityWaitList.begin(), end = priorityWaitList.end(); it != end; ++it, ++slot) { - if (it->playerGUID == player->getGUID()) { - return {it, slot}; - } + for (auto it = waitList.begin(), end = waitList.end(); it != end; ++it) { + if (it->playerGUID == player->getGUID()) { + return it; } - - for (auto it = waitList.begin(), end = waitList.end(); it != end; ++it, ++slot) { - if (it->playerGUID == player->getGUID()) { - return {it, slot}; - } - } - return {waitList.end(), slot}; + ++slot; } -}; - -WaitingList& WaitingList::getInstance() -{ - static WaitingList waitingList; - return waitingList; + return waitList.end(); } -std::size_t WaitingList::getTime(std::size_t slot) +uint32_t WaitingList::getTime(uint32_t slot) { if (slot < 5) { return 5; @@ -105,6 +60,12 @@ std::size_t WaitingList::getTime(std::size_t slot) } } +uint32_t WaitingList::getTimeout(uint32_t slot) +{ + //timeout is set to 15 seconds longer than expected retry attempt + return getTime(slot) + 15; +} + bool WaitingList::clientLogin(const Player* player) { if (player->hasFlag(PlayerFlag_CanAlwaysLogin) || player->getAccountType() >= ACCOUNT_TYPE_GAMEMASTER) { @@ -112,20 +73,20 @@ bool WaitingList::clientLogin(const Player* player) } uint32_t maxPlayers = static_cast(g_config.getNumber(ConfigManager::MAX_PLAYERS)); - if (maxPlayers == 0 || (info->priorityWaitList.empty() && info->waitList.empty() && g_game.getPlayersOnline() < maxPlayers)) { + if (maxPlayers == 0 || (priorityWaitList.empty() && waitList.empty() && g_game.getPlayersOnline() < maxPlayers)) { return true; } - cleanupList(info->priorityWaitList); - cleanupList(info->waitList); + WaitingList::cleanupList(priorityWaitList); + WaitingList::cleanupList(waitList); - WaitList::iterator it; - WaitList::size_type slot; - std::tie(it, slot) = info->findClient(player); - if (it != info->waitList.end()) { + uint32_t slot; + + auto it = findClient(player, slot); + if (it != waitList.end()) { if ((g_game.getPlayersOnline() + slot) <= maxPlayers) { //should be able to login now - info->waitList.erase(it); + waitList.erase(it); return true; } @@ -134,25 +95,36 @@ bool WaitingList::clientLogin(const Player* player) return false; } - slot = info->priorityWaitList.size(); + slot = priorityWaitList.size(); if (player->isPremium()) { - info->priorityWaitList.emplace_back(OTSYS_TIME() + (getTimeout(slot + 1) * 1000), player->getGUID()); + priorityWaitList.emplace_back(OTSYS_TIME() + (getTimeout(slot + 1) * 1000), player->getGUID()); } else { - slot += info->waitList.size(); - info->waitList.emplace_back(OTSYS_TIME() + (getTimeout(slot + 1) * 1000), player->getGUID()); + slot += waitList.size(); + waitList.emplace_back(OTSYS_TIME() + (getTimeout(slot + 1) * 1000), player->getGUID()); } return false; } -std::size_t WaitingList::getClientSlot(const Player* player) +uint32_t WaitingList::getClientSlot(const Player* player) { - WaitList::iterator it; - WaitList::size_type slot; - std::tie(it, slot) = info->findClient(player); - if (it == info->waitList.end()) { + uint32_t slot; + auto it = findClient(player, slot); + if (it == waitList.end()) { return 0; } return slot; } -WaitingList::WaitingList() : info(new WaitListInfo) {} +void WaitingList::cleanupList(WaitList& list) +{ + int64_t time = OTSYS_TIME(); + + auto it = list.begin(), end = list.end(); + while (it != end) { + if ((it->timeout - time) <= 0) { + it = list.erase(it); + } else { + ++it; + } + } +} diff --git a/src/waitlist.h b/src/waitlist.h index 661e7b9..77dd2c5 100644 --- a/src/waitlist.h +++ b/src/waitlist.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,21 +22,36 @@ #include "player.h" -struct WaitListInfo; +struct Wait { + constexpr Wait(int64_t timeout, uint32_t playerGUID) : + timeout(timeout), playerGUID(playerGUID) {} + + int64_t timeout; + uint32_t playerGUID; +}; + +typedef std::list WaitList; +typedef WaitList::iterator WaitListIterator; class WaitingList { public: - static WaitingList& getInstance(); + static WaitingList* getInstance() { + static WaitingList waitingList; + return &waitingList; + } bool clientLogin(const Player* player); - std::size_t getClientSlot(const Player* player); - static std::size_t getTime(std::size_t slot); + uint32_t getClientSlot(const Player* player); + static uint32_t getTime(uint32_t slot); - private: - WaitingList(); + protected: + WaitList priorityWaitList; + WaitList waitList; - std::unique_ptr info; + static uint32_t getTimeout(uint32_t slot); + WaitListIterator findClient(const Player* player, uint32_t& slot); + static void cleanupList(WaitList& list); }; #endif diff --git a/src/weapons.cpp b/src/weapons.cpp deleted file mode 100644 index 2452f8a..0000000 --- a/src/weapons.cpp +++ /dev/null @@ -1,944 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "otpch.h" - -#include "combat.h" -#include "configmanager.h" -#include "game.h" -#include "pugicast.h" -#include "weapons.h" - -extern Game g_game; -extern Vocations g_vocations; -extern ConfigManager g_config; -extern Weapons* g_weapons; - -Weapons::Weapons() -{ - scriptInterface.initState(); -} - -Weapons::~Weapons() -{ - clear(false); -} - -const Weapon* Weapons::getWeapon(const Item* item) const -{ - if (!item) { - return nullptr; - } - - auto it = weapons.find(item->getID()); - if (it == weapons.end()) { - return nullptr; - } - return it->second; -} - -void Weapons::clear(bool fromLua) -{ - for (auto it = weapons.begin(); it != weapons.end(); ) { - if (fromLua == it->second->fromLua) { - it = weapons.erase(it); - } else { - ++it; - } - } - - reInitState(fromLua); -} - -LuaScriptInterface& Weapons::getScriptInterface() -{ - return scriptInterface; -} - -std::string Weapons::getScriptBaseName() const -{ - return "weapons"; -} - -void Weapons::loadDefaults() -{ - for (size_t i = 100, size = Item::items.size(); i < size; ++i) { - const ItemType& it = Item::items.getItemType(i); - if (it.id == 0 || weapons.find(i) != weapons.end()) { - continue; - } - - switch (it.weaponType) { - case WEAPON_AXE: - case WEAPON_SWORD: - case WEAPON_CLUB: { - WeaponMelee* weapon = new WeaponMelee(&scriptInterface); - weapon->configureWeapon(it); - weapons[i] = weapon; - break; - } - - case WEAPON_AMMO: - case WEAPON_DISTANCE: { - if (it.weaponType == WEAPON_DISTANCE && it.ammoType != AMMO_NONE) { - continue; - } - - WeaponDistance* weapon = new WeaponDistance(&scriptInterface); - weapon->configureWeapon(it); - weapons[i] = weapon; - break; - } - - default: - break; - } - } -} - -Event_ptr Weapons::getEvent(const std::string& nodeName) -{ - if (strcasecmp(nodeName.c_str(), "melee") == 0) { - return Event_ptr(new WeaponMelee(&scriptInterface)); - } else if (strcasecmp(nodeName.c_str(), "distance") == 0) { - return Event_ptr(new WeaponDistance(&scriptInterface)); - } else if (strcasecmp(nodeName.c_str(), "wand") == 0) { - return Event_ptr(new WeaponWand(&scriptInterface)); - } - return nullptr; -} - -bool Weapons::registerEvent(Event_ptr event, const pugi::xml_node&) -{ - Weapon* weapon = static_cast(event.release()); //event is guaranteed to be a Weapon - - auto result = weapons.emplace(weapon->getID(), weapon); - if (!result.second) { - std::cout << "[Warning - Weapons::registerEvent] Duplicate registered item with id: " << weapon->getID() << std::endl; - } - return result.second; -} - -bool Weapons::registerLuaEvent(Weapon* event) -{ - Weapon_ptr weapon{ event }; - weapons[weapon->getID()] = weapon.release(); - - return true; -} - -//monsters -int32_t Weapons::getMaxMeleeDamage(int32_t attackSkill, int32_t attackValue) -{ - return static_cast(std::ceil((attackSkill * (attackValue * 0.05)) + (attackValue * 0.5))); -} - -//players -int32_t Weapons::getMaxWeaponDamage(uint32_t level, int32_t attackSkill, int32_t attackValue, float attackFactor) -{ - return static_cast(std::round((level / 5) + (((((attackSkill / 4.) + 1) * (attackValue / 3.)) * 1.03) / attackFactor))); -} - -bool Weapon::configureEvent(const pugi::xml_node& node) -{ - pugi::xml_attribute attr; - if (!(attr = node.attribute("id"))) { - std::cout << "[Error - Weapon::configureEvent] Weapon without id." << std::endl; - return false; - } - id = pugi::cast(attr.value()); - - if ((attr = node.attribute("level"))) { - level = pugi::cast(attr.value()); - } - - if ((attr = node.attribute("maglv")) || (attr = node.attribute("maglevel"))) { - magLevel = pugi::cast(attr.value()); - } - - if ((attr = node.attribute("mana"))) { - mana = pugi::cast(attr.value()); - } - - if ((attr = node.attribute("manapercent"))) { - manaPercent = pugi::cast(attr.value()); - } - - if ((attr = node.attribute("soul"))) { - soul = pugi::cast(attr.value()); - } - - if ((attr = node.attribute("prem"))) { - premium = attr.as_bool(); - } - - if ((attr = node.attribute("breakchance"))) { - breakChance = std::min(100, pugi::cast(attr.value())); - } - - if ((attr = node.attribute("action"))) { - action = getWeaponAction(asLowerCaseString(attr.as_string())); - if (action == WEAPONACTION_NONE) { - std::cout << "[Warning - Weapon::configureEvent] Unknown action " << attr.as_string() << std::endl; - } - } - - if ((attr = node.attribute("enabled"))) { - enabled = attr.as_bool(); - } - - if ((attr = node.attribute("unproperly"))) { - wieldUnproperly = attr.as_bool(); - } - - std::list vocStringList; - for (auto vocationNode : node.children()) { - if (!(attr = vocationNode.attribute("name"))) { - continue; - } - - int32_t vocationId = g_vocations.getVocationId(attr.as_string()); - if (vocationId != -1) { - vocWeaponMap[vocationId] = true; - int32_t promotedVocation = g_vocations.getPromotedVocation(vocationId); - if (promotedVocation != VOCATION_NONE) { - vocWeaponMap[promotedVocation] = true; - } - - if (vocationNode.attribute("showInDescription").as_bool(true)) { - vocStringList.push_back(asLowerCaseString(attr.as_string())); - } - } - } - - std::string vocationString; - 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'); - } - - uint32_t wieldInfo = 0; - if (getReqLevel() > 0) { - wieldInfo |= WIELDINFO_LEVEL; - } - - if (getReqMagLv() > 0) { - wieldInfo |= WIELDINFO_MAGLV; - } - - if (!vocationString.empty()) { - wieldInfo |= WIELDINFO_VOCREQ; - } - - if (isPremium()) { - wieldInfo |= WIELDINFO_PREMIUM; - } - - if (wieldInfo != 0) { - ItemType& it = Item::items.getItemType(id); - it.wieldInfo = wieldInfo; - it.vocationString = vocationString; - it.minReqLevel = getReqLevel(); - it.minReqMagicLevel = getReqMagLv(); - } - - configureWeapon(Item::items[id]); - return true; -} - -void Weapon::configureWeapon(const ItemType& it) -{ - id = it.id; -} - -std::string Weapon::getScriptEventName() const -{ - return "onUseWeapon"; -} - -int32_t Weapon::playerWeaponCheck(Player* player, Creature* target, uint8_t shootRange) const -{ - const Position& playerPos = player->getPosition(); - const Position& targetPos = target->getPosition(); - if (playerPos.z != targetPos.z) { - return 0; - } - - if (std::max(Position::getDistanceX(playerPos, targetPos), Position::getDistanceY(playerPos, targetPos)) > shootRange) { - return 0; - } - - if (!player->hasFlag(PlayerFlag_IgnoreWeaponCheck)) { - if (!enabled) { - return 0; - } - - if (player->getMana() < getManaCost(player)) { - return 0; - } - - if (player->getHealth() < getHealthCost(player)) { - return 0; - } - - if (player->getSoul() < soul) { - return 0; - } - - if (isPremium() && !player->isPremium()) { - return 0; - } - - if (!vocWeaponMap.empty()) { - if (vocWeaponMap.find(player->getVocationId()) == vocWeaponMap.end()) { - return 0; - } - } - - int32_t damageModifier = 100; - if (player->getLevel() < getReqLevel()) { - damageModifier = (isWieldedUnproperly() ? damageModifier / 2 : 0); - } - - if (player->getMagicLevel() < getReqMagLv()) { - damageModifier = (isWieldedUnproperly() ? damageModifier / 2 : 0); - } - return damageModifier; - } - - return 100; -} - -bool Weapon::useWeapon(Player* player, Item* item, Creature* target) const -{ - int32_t damageModifier = playerWeaponCheck(player, target, item->getShootRange()); - if (damageModifier == 0) { - return false; - } - - internalUseWeapon(player, item, target, damageModifier); - return true; -} - -bool Weapon::useFist(Player* player, Creature* target) -{ - if (!Position::areInRange<1, 1>(player->getPosition(), target->getPosition())) { - return false; - } - - float attackFactor = player->getAttackFactor(); - int32_t attackSkill = player->getSkillLevel(SKILL_FIST); - int32_t attackValue = 7; - - int32_t maxDamage = Weapons::getMaxWeaponDamage(player->getLevel(), attackSkill, attackValue, attackFactor); - - CombatParams params; - params.combatType = COMBAT_PHYSICALDAMAGE; - params.blockedByArmor = true; - params.blockedByShield = true; - - CombatDamage damage; - damage.origin = ORIGIN_MELEE; - damage.primary.type = params.combatType; - damage.primary.value = -normal_random(0, maxDamage); - - Combat::doCombatHealth(player, target, damage, params); - if (!player->hasFlag(PlayerFlag_NotGainSkill) && player->getAddAttackSkill()) { - player->addSkillAdvance(SKILL_FIST, 1); - } - - return true; -} - -void Weapon::internalUseWeapon(Player* player, Item* item, Creature* target, int32_t damageModifier) const -{ - if (scripted) { - LuaVariant var; - var.type = VARIANT_NUMBER; - var.number = target->getID(); - executeUseWeapon(player, var); - } else { - CombatDamage damage; - WeaponType_t weaponType = item->getWeaponType(); - if (weaponType == WEAPON_AMMO || weaponType == WEAPON_DISTANCE) { - damage.origin = ORIGIN_RANGED; - } else { - damage.origin = ORIGIN_MELEE; - } - damage.primary.type = params.combatType; - damage.primary.value = (getWeaponDamage(player, target, item) * damageModifier) / 100; - damage.secondary.type = getElementType(); - damage.secondary.value = getElementDamage(player, target, item); - Combat::doCombatHealth(player, target, damage, params); - } - - onUsedWeapon(player, item, target->getTile()); -} - -void Weapon::internalUseWeapon(Player* player, Item* item, Tile* tile) const -{ - if (scripted) { - LuaVariant var; - var.type = VARIANT_TARGETPOSITION; - var.pos = tile->getPosition(); - executeUseWeapon(player, var); - } else { - Combat::postCombatEffects(player, tile->getPosition(), params); - g_game.addMagicEffect(tile->getPosition(), CONST_ME_POFF); - } - - onUsedWeapon(player, item, tile); -} - -void Weapon::onUsedWeapon(Player* player, Item* item, Tile* destTile) const -{ - if (!player->hasFlag(PlayerFlag_NotGainSkill)) { - skills_t skillType; - uint32_t skillPoint; - if (getSkillType(player, item, skillType, skillPoint)) { - player->addSkillAdvance(skillType, skillPoint); - } - } - - uint32_t manaCost = getManaCost(player); - if (manaCost != 0) { - player->addManaSpent(manaCost); - player->changeMana(-static_cast(manaCost)); - } - - uint32_t healthCost = getHealthCost(player); - if (healthCost != 0) { - player->changeHealth(-static_cast(healthCost)); - } - - if (!player->hasFlag(PlayerFlag_HasInfiniteSoul) && soul > 0) { - player->changeSoul(-static_cast(soul)); - } - - if (breakChance != 0 && uniform_random(1, 100) <= breakChance) { - Weapon::decrementItemCount(item); - return; - } - - switch (action) { - case WEAPONACTION_REMOVECOUNT: - Weapon::decrementItemCount(item); - break; - - case WEAPONACTION_REMOVECHARGE: { - uint16_t charges = item->getCharges(); - if (charges != 0) { - g_game.transformItem(item, item->getID(), charges - 1); - } - break; - } - - case WEAPONACTION_MOVE: - g_game.internalMoveItem(item->getParent(), destTile, INDEX_WHEREEVER, item, 1, nullptr, FLAG_NOLIMIT); - break; - - default: - break; - } -} - -uint32_t Weapon::getManaCost(const Player* player) const -{ - if (mana != 0) { - return mana; - } - - if (manaPercent == 0) { - return 0; - } - - return (player->getMaxMana() * manaPercent) / 100; -} - -int32_t Weapon::getHealthCost(const Player* player) const -{ - if (health != 0) { - return health; - } - - if (healthPercent == 0) { - return 0; - } - - return (player->getMaxHealth() * healthPercent) / 100; -} - -bool Weapon::executeUseWeapon(Player* player, const LuaVariant& var) const -{ - //onUseWeapon(player, var) - if (!scriptInterface->reserveScriptEnv()) { - std::cout << "[Error - Weapon::executeUseWeapon] 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"); - scriptInterface->pushVariant(L, var); - - return scriptInterface->callFunction(2); -} - -void Weapon::decrementItemCount(Item* item) -{ - uint16_t count = item->getItemCount(); - if (count > 1) { - g_game.transformItem(item, item->getID(), count - 1); - } else { - g_game.internalRemoveItem(item); - } -} - -WeaponMelee::WeaponMelee(LuaScriptInterface* interface) : - Weapon(interface) -{ - params.blockedByArmor = true; - params.blockedByShield = true; - params.combatType = COMBAT_PHYSICALDAMAGE; -} - -void WeaponMelee::configureWeapon(const ItemType& it) -{ - if (it.abilities) { - elementType = it.abilities->elementType; - elementDamage = it.abilities->elementDamage; - params.aggressive = true; - params.useCharges = true; - } else { - elementType = COMBAT_NONE; - elementDamage = 0; - } - Weapon::configureWeapon(it); -} - -bool WeaponMelee::useWeapon(Player* player, Item* item, Creature* target) const -{ - int32_t damageModifier = playerWeaponCheck(player, target, item->getShootRange()); - if (damageModifier == 0) { - return false; - } - - internalUseWeapon(player, item, target, damageModifier); - return true; -} - -bool WeaponMelee::getSkillType(const Player* player, const Item* item, - skills_t& skill, uint32_t& skillpoint) const -{ - if (player->getAddAttackSkill() && player->getLastAttackBlockType() != BLOCK_IMMUNITY) { - skillpoint = 1; - } else { - skillpoint = 0; - } - - WeaponType_t weaponType = item->getWeaponType(); - switch (weaponType) { - case WEAPON_SWORD: { - skill = SKILL_SWORD; - return true; - } - - case WEAPON_CLUB: { - skill = SKILL_CLUB; - return true; - } - - case WEAPON_AXE: { - skill = SKILL_AXE; - return true; - } - - default: - break; - } - return false; -} - -int32_t WeaponMelee::getElementDamage(const Player* player, const Creature*, const Item* item) const -{ - if (elementType == COMBAT_NONE) { - return 0; - } - - int32_t attackSkill = player->getWeaponSkill(item); - int32_t attackValue = elementDamage; - float attackFactor = player->getAttackFactor(); - - int32_t maxValue = Weapons::getMaxWeaponDamage(player->getLevel(), attackSkill, attackValue, attackFactor); - return -normal_random(0, static_cast(maxValue * player->getVocation()->meleeDamageMultiplier)); -} - -int32_t WeaponMelee::getWeaponDamage(const Player* player, const Creature*, const Item* item, bool maxDamage /*= false*/) const -{ - int32_t attackSkill = player->getWeaponSkill(item); - int32_t attackValue = std::max(0, item->getAttack()); - float attackFactor = player->getAttackFactor(); - - int32_t maxValue = static_cast(Weapons::getMaxWeaponDamage(player->getLevel(), attackSkill, attackValue, attackFactor) * player->getVocation()->meleeDamageMultiplier); - if (maxDamage) { - return -maxValue; - } - - return -normal_random(0, maxValue); -} - -WeaponDistance::WeaponDistance(LuaScriptInterface* interface) : - Weapon(interface) -{ - params.blockedByArmor = true; - params.combatType = COMBAT_PHYSICALDAMAGE; -} - -void WeaponDistance::configureWeapon(const ItemType& it) -{ - params.distanceEffect = it.shootType; - - if (it.abilities) { - elementType = it.abilities->elementType; - elementDamage = it.abilities->elementDamage; - params.aggressive = true; - params.useCharges = true; - } else { - elementType = COMBAT_NONE; - elementDamage = 0; - } - - Weapon::configureWeapon(it); -} - -bool WeaponDistance::useWeapon(Player* player, Item* item, Creature* target) const -{ - int32_t damageModifier; - const ItemType& it = Item::items[id]; - if (it.weaponType == WEAPON_AMMO) { - Item* mainWeaponItem = player->getWeapon(true); - const Weapon* mainWeapon = g_weapons->getWeapon(mainWeaponItem); - if (mainWeapon) { - damageModifier = mainWeapon->playerWeaponCheck(player, target, mainWeaponItem->getShootRange()); - } else { - damageModifier = playerWeaponCheck(player, target, mainWeaponItem->getShootRange()); - } - } else { - damageModifier = playerWeaponCheck(player, target, item->getShootRange()); - } - - if (damageModifier == 0) { - return false; - } - - int32_t chance; - if (it.hitChance == 0) { - //hit chance is based on distance to target and distance skill - uint32_t skill = player->getSkillLevel(SKILL_DISTANCE); - const Position& playerPos = player->getPosition(); - const Position& targetPos = target->getPosition(); - uint32_t distance = std::max(Position::getDistanceX(playerPos, targetPos), Position::getDistanceY(playerPos, targetPos)); - - uint32_t maxHitChance; - if (it.maxHitChance != -1) { - maxHitChance = it.maxHitChance; - } else if (it.ammoType != AMMO_NONE) { - //hit chance on two-handed weapons is limited to 90% - maxHitChance = 90; - } else { - //one-handed is set to 75% - maxHitChance = 75; - } - - if (maxHitChance == 75) { - //chance for one-handed weapons - switch (distance) { - case 1: - case 5: - chance = std::min(skill, 74) + 1; - break; - case 2: - chance = static_cast(std::min(skill, 28) * 2.40f) + 8; - break; - case 3: - chance = static_cast(std::min(skill, 45) * 1.55f) + 6; - break; - case 4: - chance = static_cast(std::min(skill, 58) * 1.25f) + 3; - break; - case 6: - chance = static_cast(std::min(skill, 90) * 0.80f) + 3; - break; - case 7: - chance = static_cast(std::min(skill, 104) * 0.70f) + 2; - break; - default: - chance = it.hitChance; - break; - } - } else if (maxHitChance == 90) { - //formula for two-handed weapons - switch (distance) { - case 1: - case 5: - chance = static_cast(std::min(skill, 74) * 1.20f) + 1; - break; - case 2: - chance = static_cast(std::min(skill, 28) * 3.20f); - break; - case 3: - chance = std::min(skill, 45) * 2; - break; - case 4: - chance = static_cast(std::min(skill, 58) * 1.55f); - break; - case 6: - case 7: - chance = std::min(skill, 90); - break; - default: - chance = it.hitChance; - break; - } - } else if (maxHitChance == 100) { - switch (distance) { - case 1: - case 5: - chance = static_cast(std::min(skill, 73) * 1.35f) + 1; - break; - case 2: - chance = static_cast(std::min(skill, 30) * 3.20f) + 4; - break; - case 3: - chance = static_cast(std::min(skill, 48) * 2.05f) + 2; - break; - case 4: - chance = static_cast(std::min(skill, 65) * 1.50f) + 2; - break; - case 6: - chance = static_cast(std::min(skill, 87) * 1.20f) - 4; - break; - case 7: - chance = static_cast(std::min(skill, 90) * 1.10f) + 1; - break; - default: - chance = it.hitChance; - break; - } - } else { - chance = maxHitChance; - } - } else { - chance = it.hitChance; - } - - if (item->getWeaponType() == WEAPON_AMMO) { - Item* bow = player->getWeapon(true); - if (bow && bow->getHitChance() != 0) { - chance += bow->getHitChance(); - } - } - - if (chance >= uniform_random(1, 100)) { - Weapon::internalUseWeapon(player, item, target, damageModifier); - } else { - //miss target - Tile* destTile = target->getTile(); - - if (!Position::areInRange<1, 1, 0>(player->getPosition(), target->getPosition())) { - static std::vector> destList { - {-1, -1}, {0, -1}, {1, -1}, - {-1, 0}, {0, 0}, {1, 0}, - {-1, 1}, {0, 1}, {1, 1} - }; - std::shuffle(destList.begin(), destList.end(), getRandomGenerator()); - - Position destPos = target->getPosition(); - - for (const auto& dir : destList) { - // Blocking tiles or tiles without ground ain't valid targets for spears - Tile* tmpTile = g_game.map.getTile(destPos.x + dir.first, destPos.y + dir.second, destPos.z); - if (tmpTile && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID) && tmpTile->getGround() != nullptr) { - destTile = tmpTile; - break; - } - } - } - - Weapon::internalUseWeapon(player, item, destTile); - } - return true; -} - -int32_t WeaponDistance::getElementDamage(const Player* player, const Creature* target, const Item* item) const -{ - if (elementType == COMBAT_NONE) { - return 0; - } - - int32_t attackValue = elementDamage; - if (item->getWeaponType() == WEAPON_AMMO) { - Item* weapon = player->getWeapon(true); - if (weapon) { - attackValue += weapon->getAttack(); - } - } - - int32_t attackSkill = player->getSkillLevel(SKILL_DISTANCE); - float attackFactor = player->getAttackFactor(); - - int32_t minValue = 0; - int32_t maxValue = Weapons::getMaxWeaponDamage(player->getLevel(), attackSkill, attackValue, attackFactor); - if (target) { - if (target->getPlayer()) { - minValue = static_cast(std::ceil(player->getLevel() * 0.1)); - } else { - minValue = static_cast(std::ceil(player->getLevel() * 0.2)); - } - } - - return -normal_random(minValue, static_cast(maxValue * player->getVocation()->distDamageMultiplier)); -} - -int32_t WeaponDistance::getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage /*= false*/) const -{ - int32_t attackValue = item->getAttack(); - - if (item->getWeaponType() == WEAPON_AMMO) { - Item* weapon = player->getWeapon(true); - if (weapon) { - attackValue += weapon->getAttack(); - } - } - - int32_t attackSkill = player->getSkillLevel(SKILL_DISTANCE); - float attackFactor = player->getAttackFactor(); - - int32_t maxValue = static_cast(Weapons::getMaxWeaponDamage(player->getLevel(), attackSkill, attackValue, attackFactor) * player->getVocation()->distDamageMultiplier); - if (maxDamage) { - return -maxValue; - } - - int32_t minValue; - if (target) { - if (target->getPlayer()) { - minValue = static_cast(std::ceil(player->getLevel() * 0.1)); - } else { - minValue = static_cast(std::ceil(player->getLevel() * 0.2)); - } - } else { - minValue = 0; - } - return -normal_random(minValue, maxValue); -} - -bool WeaponDistance::getSkillType(const Player* player, const Item*, skills_t& skill, uint32_t& skillpoint) const -{ - skill = SKILL_DISTANCE; - - if (player->getAddAttackSkill()) { - switch (player->getLastAttackBlockType()) { - case BLOCK_NONE: { - skillpoint = 2; - break; - } - - case BLOCK_DEFENSE: - case BLOCK_ARMOR: { - skillpoint = 1; - break; - } - - default: - skillpoint = 0; - break; - } - } else { - skillpoint = 0; - } - return true; -} - -bool WeaponWand::configureEvent(const pugi::xml_node& node) -{ - if (!Weapon::configureEvent(node)) { - return false; - } - - pugi::xml_attribute attr; - if ((attr = node.attribute("min"))) { - minChange = pugi::cast(attr.value()); - } - - if ((attr = node.attribute("max"))) { - maxChange = pugi::cast(attr.value()); - } - - attr = node.attribute("type"); - if (!attr) { - return true; - } - - std::string tmpStrValue = asLowerCaseString(attr.as_string()); - if (tmpStrValue == "earth") { - params.combatType = COMBAT_EARTHDAMAGE; - } else if (tmpStrValue == "ice") { - params.combatType = COMBAT_ICEDAMAGE; - } else if (tmpStrValue == "energy") { - params.combatType = COMBAT_ENERGYDAMAGE; - } else if (tmpStrValue == "fire") { - params.combatType = COMBAT_FIREDAMAGE; - } else if (tmpStrValue == "death") { - params.combatType = COMBAT_DEATHDAMAGE; - } else if (tmpStrValue == "holy") { - params.combatType = COMBAT_HOLYDAMAGE; - } else { - std::cout << "[Warning - WeaponWand::configureEvent] Type \"" << attr.as_string() << "\" does not exist." << std::endl; - } - return true; -} - -void WeaponWand::configureWeapon(const ItemType& it) -{ - params.distanceEffect = it.shootType; - - Weapon::configureWeapon(it); -} - -int32_t WeaponWand::getWeaponDamage(const Player*, const Creature*, const Item*, bool maxDamage /*= false*/) const -{ - if (maxDamage) { - return -maxChange; - } - return -normal_random(minChange, maxChange); -} diff --git a/src/weapons.h b/src/weapons.h deleted file mode 100644 index e889784..0000000 --- a/src/weapons.h +++ /dev/null @@ -1,311 +0,0 @@ -/** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef FS_WEAPONS_H_69D1993478AA42948E24C0B90B8F5BF5 -#define FS_WEAPONS_H_69D1993478AA42948E24C0B90B8F5BF5 - -#include "luascript.h" -#include "player.h" -#include "baseevents.h" -#include "combat.h" -#include "const.h" -#include "vocation.h" - -extern Vocations g_vocations; - -class Weapon; -class WeaponMelee; -class WeaponDistance; -class WeaponWand; - -using Weapon_ptr = std::unique_ptr; - -class Weapons final : public BaseEvents -{ - public: - Weapons(); - ~Weapons(); - - // non-copyable - Weapons(const Weapons&) = delete; - Weapons& operator=(const Weapons&) = delete; - - void loadDefaults(); - const Weapon* getWeapon(const Item* item) const; - - static int32_t getMaxMeleeDamage(int32_t attackSkill, int32_t attackValue); - static int32_t getMaxWeaponDamage(uint32_t level, int32_t attackSkill, int32_t attackValue, float attackFactor); - - bool registerLuaEvent(Weapon* event); - void clear(bool fromLua) override final; - - private: - LuaScriptInterface& getScriptInterface() override; - std::string getScriptBaseName() const override; - Event_ptr getEvent(const std::string& nodeName) override; - bool registerEvent(Event_ptr event, const pugi::xml_node& node) override; - - std::map weapons; - - LuaScriptInterface scriptInterface { "Weapon Interface" }; -}; - -class Weapon : public Event -{ - public: - explicit Weapon(LuaScriptInterface* interface) : Event(interface) {} - - bool configureEvent(const pugi::xml_node& node) override; - bool loadFunction(const pugi::xml_attribute&, bool) final { - return true; - } - virtual void configureWeapon(const ItemType& it); - virtual bool interruptSwing() const { - return false; - } - - int32_t playerWeaponCheck(Player* player, Creature* target, uint8_t shootRange) const; - static bool useFist(Player* player, Creature* target); - virtual bool useWeapon(Player* player, Item* item, Creature* target) const; - - virtual int32_t getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage = false) const = 0; - virtual int32_t getElementDamage(const Player* player, const Creature* target, const Item* item) const = 0; - virtual CombatType_t getElementType() const = 0; - - uint16_t getID() const { - return id; - } - void setID(uint16_t newId) { - id = newId; - } - - uint32_t getReqLevel() const { - return level; - } - void setRequiredLevel(uint32_t reqlvl) { - level = reqlvl; - } - - uint32_t getReqMagLv() const { - return magLevel; - } - void setRequiredMagLevel(uint32_t reqlvl) { - magLevel = reqlvl; - } - - bool isPremium() const { - return premium; - } - void setNeedPremium(bool prem) { - premium = prem; - } - - bool isWieldedUnproperly() const { - return wieldUnproperly; - } - void setWieldUnproperly(bool unproperly) { - wieldUnproperly = unproperly; - } - - uint32_t getMana() const { - return mana; - } - void setMana(uint32_t m) { - mana = m; - } - - uint32_t getManaPercent() const { - return manaPercent; - } - void setManaPercent(uint32_t m) { - manaPercent = m; - } - - int32_t getHealth() const { - return health; - } - void setHealth(int32_t h) { - health = h; - } - - uint32_t getHealthPercent() const { - return healthPercent; - } - void setHealthPercent(uint32_t m) { - healthPercent = m; - } - - uint32_t getSoul() const { - return soul; - } - void setSoul(uint32_t s) { - soul = s; - } - - uint8_t getBreakChance() const { - return breakChance; - } - void setBreakChance(uint8_t b) { - breakChance = b; - } - - bool isEnabled() const { - return enabled; - } - void setIsEnabled(bool e) { - enabled = e; - } - - uint32_t getWieldInfo() const { - return wieldInfo; - } - void setWieldInfo(uint32_t info) { - wieldInfo |= info; - } - - void addVocWeaponMap(std::string vocName) { - int32_t vocationId = g_vocations.getVocationId(vocName); - if (vocationId != -1) { - vocWeaponMap[vocationId] = true; - } - } - - const std::string& getVocationString() const { - return vocationString; - } - void setVocationString(const std::string& str) { - vocationString = str; - } - - WeaponAction_t action = WEAPONACTION_NONE; - CombatParams params; - WeaponType_t weaponType; - std::map vocWeaponMap; - - protected: - void internalUseWeapon(Player* player, Item* item, Creature* target, int32_t damageModifier) const; - void internalUseWeapon(Player* player, Item* item, Tile* tile) const; - - uint16_t id = 0; - - private: - virtual bool getSkillType(const Player*, const Item*, skills_t&, uint32_t&) const { - return false; - } - - uint32_t getManaCost(const Player* player) const; - int32_t getHealthCost(const Player* player) const; - - uint32_t level = 0; - uint32_t magLevel = 0; - uint32_t mana = 0; - uint32_t manaPercent = 0; - uint32_t health = 0; - uint32_t healthPercent = 0; - uint32_t soul = 0; - uint32_t wieldInfo = WIELDINFO_NONE; - uint8_t breakChance = 0; - bool enabled = true; - bool premium = false; - bool wieldUnproperly = false; - std::string vocationString = ""; - - std::string getScriptEventName() const override final; - - bool executeUseWeapon(Player* player, const LuaVariant& var) const; - void onUsedWeapon(Player* player, Item* item, Tile* destTile) const; - - static void decrementItemCount(Item* item); - - friend class Combat; -}; - -class WeaponMelee final : public Weapon -{ - public: - explicit WeaponMelee(LuaScriptInterface* interface); - - void configureWeapon(const ItemType& it) override; - - bool useWeapon(Player* player, Item* item, Creature* target) const override; - - int32_t getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage = false) const override; - int32_t getElementDamage(const Player* player, const Creature* target, const Item* item) const override; - CombatType_t getElementType() const override { return elementType; } - - private: - bool getSkillType(const Player* player, const Item* item, skills_t& skill, uint32_t& skillpoint) const override; - - CombatType_t elementType = COMBAT_NONE; - uint16_t elementDamage = 0; -}; - -class WeaponDistance final : public Weapon -{ - public: - explicit WeaponDistance(LuaScriptInterface* interface); - - void configureWeapon(const ItemType& it) override; - bool interruptSwing() const override { - return true; - } - - bool useWeapon(Player* player, Item* item, Creature* target) const override; - - int32_t getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage = false) const override; - int32_t getElementDamage(const Player* player, const Creature* target, const Item* item) const override; - CombatType_t getElementType() const override { return elementType; } - - private: - bool getSkillType(const Player* player, const Item* item, skills_t& skill, uint32_t& skillpoint) const override; - - CombatType_t elementType = COMBAT_NONE; - uint16_t elementDamage = 0; -}; - -class WeaponWand final : public Weapon -{ - public: - explicit WeaponWand(LuaScriptInterface* interface) : Weapon(interface) {} - - bool configureEvent(const pugi::xml_node& node) override; - void configureWeapon(const ItemType& it) override; - - int32_t getWeaponDamage(const Player* player, const Creature* target, const Item* item, bool maxDamage = false) const override; - int32_t getElementDamage(const Player*, const Creature*, const Item*) const override { return 0; } - CombatType_t getElementType() const override { return COMBAT_NONE; } - - void setMinChange(int32_t change) { - minChange = change; - } - - void setMaxChange(int32_t change) { - maxChange = change; - } - - private: - bool getSkillType(const Player*, const Item*, skills_t&, uint32_t&) const override { - return false; - } - - int32_t minChange = 0; - int32_t maxChange = 0; -}; - -#endif diff --git a/src/wildcardtree.cpp b/src/wildcardtree.cpp index ec182b6..fa5eef8 100644 --- a/src/wildcardtree.cpp +++ b/src/wildcardtree.cpp @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -119,7 +119,7 @@ ReturnValue WildcardTreeNode::findOne(const std::string& query, std::string& res if (size == 0) { return RETURNVALUE_NOERROR; } else if (size > 1 || cur->breakpoint) { - return RETURNVALUE_NAMEISTOOAMBIGUOUS; + return RETURNVALUE_NAMEISTOOAMBIGIOUS; } auto it = cur->children.begin(); diff --git a/src/wildcardtree.h b/src/wildcardtree.h index 38aa7ed..2d16981 100644 --- a/src/wildcardtree.h +++ b/src/wildcardtree.h @@ -1,6 +1,6 @@ /** - * The Forgotten Server - a free and open-source MMORPG server emulator - * Copyright (C) 2019 Mark Samman + * Tibia GIMUD Server - a free and open-source MMORPG server emulator + * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From 37d8a76f06a563df56c24649e69b7c63d0f616e6 Mon Sep 17 00:00:00 2001 From: ErikasKontenis Date: Thu, 2 Jan 2020 23:20:10 +0200 Subject: [PATCH 3/3] done with most important files comparison --- config.lua | 3 +- sabrehaven.sql | 8 +- src/account.h | 1 + src/condition.cpp | 9 +- src/const.h | 13 +++ src/creature.cpp | 8 +- src/creature.h | 4 +- src/game.cpp | 228 ++++++++++++++++++++++--------------------- src/game.h | 4 +- src/iologindata.cpp | 12 ++- src/iologindata.h | 4 +- src/luascript.cpp | 10 +- src/player.cpp | 42 ++++---- src/player.h | 2 +- src/protocolgame.cpp | 10 +- 15 files changed, 190 insertions(+), 168 deletions(-) diff --git a/config.lua b/config.lua index 7a602ce..28d6887 100644 --- a/config.lua +++ b/config.lua @@ -1,6 +1,7 @@ -- Combat settings -- NOTE: valid values for worldType are: "pvp", "no-pvp" and "pvp-enforced" worldType = "pvp" +hotkeyAimbotEnabled = true protectionLevel = 1 pzLocked = 60000 removeChargesFromRunes = true @@ -65,7 +66,7 @@ mysqlSock = "" -- Misc. allowChangeOutfit = true -freePremium = false +freePremium = true kickIdlePlayerAfterMinutes = 15 maxMessageBuffer = 4 showMonsterLoot = false diff --git a/sabrehaven.sql b/sabrehaven.sql index c89fa9f..6d8d00e 100644 --- a/sabrehaven.sql +++ b/sabrehaven.sql @@ -30,6 +30,7 @@ SET time_zone = "+00:00"; CREATE TABLE `accounts` ( `id` int(11) NOT NULL, + `name` int(11) NOT NULL, `password` char(40) NOT NULL, `type` int(11) NOT NULL DEFAULT '1', `premdays` int(11) NOT NULL DEFAULT '0', @@ -40,8 +41,8 @@ CREATE TABLE `accounts` ( -- Dumping data for table `accounts` -- -INSERT INTO `accounts` (`id`, `password`, `type`, `premdays`, `lastday`) VALUES -(1234567, '41da8bef22aaef9d7c5821fa0f0de7cccc4dda4d', 5, 497, 1547320555); +INSERT INTO `accounts` (`id`, `name`, `password`, `type`, `premdays`, `lastday`) VALUES +(1, 1234567, '41da8bef22aaef9d7c5821fa0f0de7cccc4dda4d', 5, 497, 1547320555); -- -------------------------------------------------------- @@ -7638,7 +7639,8 @@ INSERT INTO `tile_store` (`house_id`, `data`) VALUES -- Indexes for table `accounts` -- ALTER TABLE `accounts` - ADD PRIMARY KEY (`id`); + ADD PRIMARY KEY (`id`) + ADD UNIQUE KEY `name` (`name`); -- -- Indexes for table `account_bans` diff --git a/src/account.h b/src/account.h index 5bdc53a..2090d92 100644 --- a/src/account.h +++ b/src/account.h @@ -24,6 +24,7 @@ struct Account { std::vector characters; + uint32_t name; time_t lastDay = 0; uint32_t id = 0; uint16_t premiumDays = 0; diff --git a/src/condition.cpp b/src/condition.cpp index 7487e6a..bd35633 100644 --- a/src/condition.cpp +++ b/src/condition.cpp @@ -1235,12 +1235,11 @@ bool ConditionLight::executeCondition(Creature* creature, int32_t interval) if (internalLightTicks >= lightChangeInterval) { internalLightTicks = 0; - LightInfo creatureLight; - creature->getCreatureLight(creatureLight); + LightInfo lightInfo = creature->getCreatureLight(); - if (creatureLight.level > 0) { - --creatureLight.level; - creature->setCreatureLight(creatureLight); + if (lightInfo.level > 0) { + --lightInfo.level; + creature->setCreatureLight(lightInfo); g_game.changeLight(creature); } } diff --git a/src/const.h b/src/const.h index c4dffb2..29609fd 100644 --- a/src/const.h +++ b/src/const.h @@ -129,6 +129,19 @@ enum FluidTypes_t : uint8_t FLUID_FRUITJUICE, }; +const uint8_t reverseFluidMap[] = { + FLUID_NONE, + FLUID_WATER, + FLUID_MANAFLUID, + FLUID_BEER, + FLUID_NONE, + FLUID_LIFEFLUID, + FLUID_SLIME, + FLUID_NONE, + FLUID_LEMONADE, + FLUID_MILK, +}; + enum FluidColor_t : uint8_t { FLUID_COLOR_NONE = 0, diff --git a/src/creature.cpp b/src/creature.cpp index da8bf6d..cc4570c 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -1085,7 +1085,7 @@ void Creature::onGainExperience(uint64_t gainExp, Creature* target) void Creature::addSummon(Creature* creature) { creature->setDropLoot(false); - creature->setLossSkill(false); + creature->setSkillLoss(false); creature->setMaster(this); creature->incrementReferenceCounter(); summons.push_back(creature); @@ -1096,7 +1096,7 @@ void Creature::removeSummon(Creature* creature) auto cit = std::find(summons.begin(), summons.end(), creature); if (cit != summons.end()) { creature->setDropLoot(true); - creature->setLossSkill(true); + creature->setSkillLoss(true); creature->setMaster(nullptr); creature->decrementReferenceCounter(); summons.erase(cit); @@ -1364,9 +1364,9 @@ int64_t Creature::getEventStepTicks(bool onlyDelay) const return ret; } -void Creature::getCreatureLight(LightInfo& light) const +LightInfo Creature::getCreatureLight() const { - light = internalLight; + return internalLight; } void Creature::setNormalCreatureLight() diff --git a/src/creature.h b/src/creature.h index 26f5627..f673066 100644 --- a/src/creature.h +++ b/src/creature.h @@ -355,7 +355,7 @@ class Creature : virtual public Thing virtual void onAttackedCreatureChangeZone(ZoneType_t zone); virtual void onIdleStatus(); - virtual void getCreatureLight(LightInfo& light) const; + virtual LightInfo getCreatureLight() const; virtual void setNormalCreatureLight(); void setCreatureLight(LightInfo light) { internalLight = light; @@ -395,7 +395,7 @@ class Creature : virtual public Thing void setDropLoot(bool lootDrop) { this->lootDrop = lootDrop; } - void setLossSkill(bool skillLoss) { + void setSkillLoss(bool skillLoss) { this->skillLoss = skillLoss; } diff --git a/src/game.cpp b/src/game.cpp index 114b961..7b55432 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -232,7 +232,7 @@ Thing* Game::internalGetThing(Player* player, const Position& pos, int32_t index } case STACKPOS_USETARGET: { - thing = tile->getTopCreature(); + thing = tile->getTopVisibleCreature(player); if (!thing) { thing = tile->getUseItem(index); } @@ -280,9 +280,10 @@ Thing* Game::internalGetThing(Player* player, const Position& pos, int32_t index } int32_t subType; - if (it.isFluidContainer()) { - subType = static_cast(index); - } else { + if (it.isFluidContainer() && index < static_cast(sizeof(reverseFluidMap) / sizeof(uint8_t))) { + subType = reverseFluidMap[index]; + } + else { subType = -1; } @@ -501,15 +502,15 @@ bool Game::placeCreature(Creature* creature, const Position& pos, bool extendedP return false; } - SpectatorVec list; - map.getSpectators(list, creature->getPosition(), true); - for (Creature* spectator : list) { + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), true); + for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendCreatureAppear(creature, creature->getPosition(), true); } } - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { spectator->onCreatureAppear(creature, true); } @@ -530,9 +531,9 @@ bool Game::removeCreature(Creature* creature, bool isLogout/* = true*/) std::vector oldStackPosVector; - SpectatorVec list; - map.getSpectators(list, tile->getPosition(), true); - for (Creature* spectator : list) { + SpectatorVec spectators; + map.getSpectators(spectators, tile->getPosition(), true); + for (Creature* spectator : spectators) { if (Player* player = spectator->getPlayer()) { oldStackPosVector.push_back(player->canSeeCreature(creature) ? tile->getStackposOfCreature(player, creature) : -1); } @@ -544,14 +545,14 @@ bool Game::removeCreature(Creature* creature, bool isLogout/* = true*/) //send to client size_t i = 0; - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { if (Player* player = spectator->getPlayer()) { player->sendRemoveTileThing(tilePosition, oldStackPosVector[i++]); } } //event method - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { spectator->onRemoveCreature(creature, isLogout); } @@ -564,7 +565,7 @@ bool Game::removeCreature(Creature* creature, bool isLogout/* = true*/) removeCreatureCheck(creature); for (Creature* summon : creature->summons) { - summon->setLossSkill(false); + summon->setSkillLoss(false); removeCreature(summon); } return true; @@ -1823,6 +1824,11 @@ void Game::playerOpenPrivateChannel(uint32_t playerId, std::string& receiver) return; } + if (player->getName() == receiver) { + player->sendCancelMessage("You cannot set up a private message channel with yourself."); + return; + } + player->sendOpenPrivateChannel(receiver); } @@ -2450,10 +2456,10 @@ void Game::playerAcceptTrade(uint32_t playerId) player->setTradeState(TRADE_ACCEPT); if (tradePartner->getTradeState() == TRADE_ACCEPT) { - Item* tradeItem1 = player->tradeItem; - Item* tradeItem2 = tradePartner->tradeItem; + Item* playerTradeItem = player->tradeItem; + Item* partnerTradeItem = tradePartner->tradeItem; - if (!g_events->eventPlayerOnTradeAccept(player, tradePartner, tradeItem1, tradeItem2)) { + if (!g_events->eventPlayerOnTradeAccept(player, tradePartner, playerTradeItem, partnerTradeItem)) { internalCloseTrade(player); return; } @@ -2461,13 +2467,13 @@ void Game::playerAcceptTrade(uint32_t playerId) player->setTradeState(TRADE_TRANSFER); tradePartner->setTradeState(TRADE_TRANSFER); - auto it = tradeItems.find(tradeItem1); + auto it = tradeItems.find(playerTradeItem); if (it != tradeItems.end()) { ReleaseItem(it->first); tradeItems.erase(it); } - it = tradeItems.find(tradeItem2); + it = tradeItems.find(partnerTradeItem); if (it != tradeItems.end()) { ReleaseItem(it->first); tradeItems.erase(it); @@ -2475,24 +2481,24 @@ void Game::playerAcceptTrade(uint32_t playerId) bool isSuccess = false; - ReturnValue ret1 = internalAddItem(tradePartner, tradeItem1, INDEX_WHEREEVER, 0, true); - ReturnValue ret2 = internalAddItem(player, tradeItem2, INDEX_WHEREEVER, 0, true); - if (ret1 == RETURNVALUE_NOERROR && ret2 == RETURNVALUE_NOERROR) { - ret1 = internalRemoveItem(tradeItem1, tradeItem1->getItemCount(), true); - ret2 = internalRemoveItem(tradeItem2, tradeItem2->getItemCount(), true); - if (ret1 == RETURNVALUE_NOERROR && ret2 == RETURNVALUE_NOERROR) { - Cylinder* cylinder1 = tradeItem1->getParent(); - Cylinder* cylinder2 = tradeItem2->getParent(); + ReturnValue tradePartnerRet = internalAddItem(tradePartner, playerTradeItem, INDEX_WHEREEVER, 0, true); + ReturnValue playerRet = internalAddItem(player, partnerTradeItem, INDEX_WHEREEVER, 0, true); + if (tradePartnerRet == RETURNVALUE_NOERROR && playerRet == RETURNVALUE_NOERROR) { + tradePartnerRet = internalRemoveItem(playerTradeItem, playerTradeItem->getItemCount(), true); + playerRet = internalRemoveItem(partnerTradeItem, partnerTradeItem->getItemCount(), true); + if (tradePartnerRet == RETURNVALUE_NOERROR && playerRet == RETURNVALUE_NOERROR) { + Cylinder* cylinder1 = playerTradeItem->getParent(); + Cylinder* cylinder2 = partnerTradeItem->getParent(); - uint32_t count1 = tradeItem1->getItemCount(); - uint32_t count2 = tradeItem2->getItemCount(); + uint32_t count1 = playerTradeItem->getItemCount(); + uint32_t count2 = partnerTradeItem->getItemCount(); - ret1 = internalMoveItem(cylinder1, tradePartner, INDEX_WHEREEVER, tradeItem1, count1, nullptr, FLAG_IGNOREAUTOSTACK, nullptr, tradeItem2); - if (ret1 == RETURNVALUE_NOERROR) { - internalMoveItem(cylinder2, player, INDEX_WHEREEVER, tradeItem2, count2, nullptr, FLAG_IGNOREAUTOSTACK); + tradePartnerRet = internalMoveItem(cylinder1, tradePartner, INDEX_WHEREEVER, playerTradeItem, count1, nullptr, FLAG_IGNOREAUTOSTACK, nullptr, partnerTradeItem); + if (tradePartnerRet == RETURNVALUE_NOERROR) { + internalMoveItem(cylinder2, player, INDEX_WHEREEVER, partnerTradeItem, count2, nullptr, FLAG_IGNOREAUTOSTACK); - tradeItem1->onTradeEvent(ON_TRADE_TRANSFER, tradePartner); - tradeItem2->onTradeEvent(ON_TRADE_TRANSFER, player); + playerTradeItem->onTradeEvent(ON_TRADE_TRANSFER, tradePartner); + partnerTradeItem->onTradeEvent(ON_TRADE_TRANSFER, player); isSuccess = true; } @@ -2503,13 +2509,13 @@ void Game::playerAcceptTrade(uint32_t playerId) std::string errorDescription; if (tradePartner->tradeItem) { - errorDescription = getTradeErrorDescription(ret1, tradeItem1); + errorDescription = getTradeErrorDescription(tradePartnerRet, playerTradeItem); tradePartner->sendTextMessage(MESSAGE_EVENT_ADVANCE, errorDescription); tradePartner->tradeItem->onTradeEvent(ON_TRADE_CANCEL, tradePartner); } if (player->tradeItem) { - errorDescription = getTradeErrorDescription(ret2, tradeItem2); + errorDescription = getTradeErrorDescription(playerRet, partnerTradeItem); player->sendTextMessage(MESSAGE_EVENT_ADVANCE, errorDescription); player->tradeItem->onTradeEvent(ON_TRADE_CANCEL, player); } @@ -3004,13 +3010,13 @@ bool Game::playerSaySpell(Player* player, SpeakClasses type, const std::string& void Game::playerWhisper(Player* player, const std::string& text) { - SpectatorVec list; - map.getSpectators(list, player->getPosition(), false, false, + SpectatorVec spectators; + map.getSpectators(spectators, player->getPosition(), false, false, Map::maxClientViewportX, Map::maxClientViewportX, Map::maxClientViewportY, Map::maxClientViewportY); //send to client - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { if (Player* spectatorPlayer = spectator->getPlayer()) { if (!Position::areInRange<1, 1>(player->getPosition(), spectatorPlayer->getPosition())) { spectatorPlayer->sendCreatureSay(player, TALKTYPE_WHISPER, "pspsps"); @@ -3021,7 +3027,7 @@ void Game::playerWhisper(Player* player, const std::string& text) } //event method - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { spectator->onCreatureSay(player, TALKTYPE_WHISPER, text); } } @@ -3094,16 +3100,16 @@ bool Game::internalCreatureTurn(Creature* creature, Direction dir) creature->setDirection(dir); //send to client - SpectatorVec list; - map.getSpectators(list, creature->getPosition(), true, true); - for (Creature* spectator : list) { + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), true, true); + for (Creature* spectator : spectators) { spectator->getPlayer()->sendCreatureTurn(creature); } return true; } bool Game::internalCreatureSay(Creature* creature, SpeakClasses type, const std::string& text, - bool ghostMode, SpectatorVec* listPtr/* = nullptr*/, const Position* pos/* = nullptr*/) + bool ghostMode, SpectatorVec* spectatorsPtr/* = nullptr*/, const Position* pos/* = nullptr*/) { if (text.empty()) { return false; @@ -3113,26 +3119,26 @@ bool Game::internalCreatureSay(Creature* creature, SpeakClasses type, const std: pos = &creature->getPosition(); } - SpectatorVec list; + SpectatorVec spectators; - if (!listPtr || listPtr->empty()) { + if (!spectatorsPtr || spectatorsPtr->empty()) { // This somewhat complex construct ensures that the cached SpectatorVec // is used if available and if it can be used, else a local vector is // used (hopefully the compiler will optimize away the construction of // the temporary when it's not used). if (type != TALKTYPE_YELL && type != TALKTYPE_MONSTER_YELL) { - map.getSpectators(list, *pos, false, false, + map.getSpectators(spectators, *pos, false, false, Map::maxClientViewportX, Map::maxClientViewportX, Map::maxClientViewportY, Map::maxClientViewportY); } else { - map.getSpectators(list, *pos, true, false, 30, 30, 30, 30); + map.getSpectators(spectators, *pos, true, false, 30, 30, 30, 30); } } else { - list = (*listPtr); + spectators = (*spectatorsPtr); } //send to client - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { if (!ghostMode || tmpPlayer->canSeeCreature(creature)) { tmpPlayer->sendCreatureSay(creature, type, text, pos); @@ -3141,7 +3147,7 @@ bool Game::internalCreatureSay(Creature* creature, SpeakClasses type, const std: } //event method - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { spectator->onCreatureSay(creature, type, text); } return true; @@ -3225,9 +3231,9 @@ void Game::changeSpeed(Creature* creature, int32_t varSpeedDelta) creature->setSpeed(varSpeedDelta); //send to clients - SpectatorVec list; - map.getSpectators(list, creature->getPosition(), false, true); - for (Creature* spectator : list) { + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), false, true); + for (Creature* spectator : spectators) { spectator->getPlayer()->sendChangeSpeed(creature, creature->getStepSpeed()); } } @@ -3245,9 +3251,9 @@ void Game::internalCreatureChangeOutfit(Creature* creature, const Outfit_t& outf } //send to clients - SpectatorVec list; - map.getSpectators(list, creature->getPosition(), true, true); - for (Creature* spectator : list) { + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), true, true); + for (Creature* spectator : spectators) { spectator->getPlayer()->sendCreatureChangeOutfit(creature, outfit); } } @@ -3255,9 +3261,9 @@ void Game::internalCreatureChangeOutfit(Creature* creature, const Outfit_t& outf void Game::internalCreatureChangeVisible(Creature* creature, bool visible) { //send to clients - SpectatorVec list; - map.getSpectators(list, creature->getPosition(), true, true); - for (Creature* spectator : list) { + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), true, true); + for (Creature* spectator : spectators) { spectator->getPlayer()->sendCreatureChangeVisible(creature, visible); } } @@ -3265,9 +3271,9 @@ void Game::internalCreatureChangeVisible(Creature* creature, bool visible) void Game::changeLight(const Creature* creature) { //send to clients - SpectatorVec list; - map.getSpectators(list, creature->getPosition(), true, true); - for (Creature* spectator : list) { + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), true, true); + for (Creature* spectator : spectators) { spectator->getPlayer()->sendCreatureLight(creature); } } @@ -3464,7 +3470,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage return true; } Player* targetPlayer = target->getPlayer(); - SpectatorVec list; + SpectatorVec spectators; if (target->hasCondition(CONDITION_MANASHIELD) && damage.type != COMBAT_UNDEFINEDDAMAGE) { int32_t manaDamage = std::min(targetPlayer->getMana(), healthChange); if (manaDamage != 0) { @@ -3482,8 +3488,8 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } } targetPlayer->drainMana(attacker, manaDamage); - map.getSpectators(list, targetPos, true, true); - addMagicEffect(list, targetPos, CONST_ME_LOSEENERGY); + map.getSpectators(spectators, targetPos, true, true); + addMagicEffect(spectators, targetPos, CONST_ME_LOSEENERGY); std::string damageString = std::to_string(manaDamage); @@ -3501,7 +3507,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage targetPlayer->sendTextMessage(MESSAGE_EVENT_DEFAULT, ss.str()); } - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { Player* tmpPlayer = spectator->getPlayer(); tmpPlayer->sendAnimatedText(targetPos, TEXTCOLOR_BLUE, damageString); } @@ -3528,8 +3534,8 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage return true; } - if (list.empty()) { - map.getSpectators(list, targetPos, true, true); + if (spectators.empty()) { + map.getSpectators(spectators, targetPos, true, true); } TextColor_t color = TEXTCOLOR_NONE; @@ -3537,7 +3543,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage if (damage.value) { combatGetTypeInfo(damage.type, target, color, hitEffect); if (hitEffect != CONST_ME_NONE) { - addMagicEffect(list, targetPos, hitEffect); + addMagicEffect(spectators, targetPos, hitEffect); } } @@ -3558,7 +3564,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } std::string realDamageStr = std::to_string(realDamage); - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { Player* tmpPlayer = spectator->getPlayer(); tmpPlayer->sendAnimatedText(targetPos, color, realDamageStr); } @@ -3573,7 +3579,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } target->drainHealth(attacker, realDamage); - addCreatureHealth(list, target); + addCreatureHealth(spectators, target); } return true; @@ -3642,8 +3648,8 @@ bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& std::string damageString = std::to_string(manaLoss); - SpectatorVec list; - map.getSpectators(list, targetPos, false, true); + SpectatorVec spectators; + map.getSpectators(spectators, targetPos, false, true); if (targetPlayer) { std::stringstream ss; if (!attacker) { @@ -3658,7 +3664,7 @@ bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& targetPlayer->sendTextMessage(MESSAGE_EVENT_DEFAULT, ss.str()); } - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { Player* tmpPlayer = spectator->getPlayer(); tmpPlayer->sendAnimatedText(targetPos, TEXTCOLOR_BLUE, damageString); } @@ -3669,14 +3675,14 @@ bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& void Game::addCreatureHealth(const Creature* target) { - SpectatorVec list; - map.getSpectators(list, target->getPosition(), true, true); - addCreatureHealth(list, target); + SpectatorVec spectators; + map.getSpectators(spectators, target->getPosition(), true, true); + addCreatureHealth(spectators, target); } -void Game::addCreatureHealth(const SpectatorVec& list, const Creature* target) +void Game::addCreatureHealth(const SpectatorVec& spectators, const Creature* target) { - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendCreatureHealth(target); } @@ -3685,14 +3691,14 @@ void Game::addCreatureHealth(const SpectatorVec& list, const Creature* target) void Game::addMagicEffect(const Position& pos, uint8_t effect) { - SpectatorVec list; - map.getSpectators(list, pos, true, true); - addMagicEffect(list, pos, effect); + SpectatorVec spectators; + map.getSpectators(spectators, pos, true, true); + addMagicEffect(spectators, pos, effect); } -void Game::addMagicEffect(const SpectatorVec& list, const Position& pos, uint8_t effect) +void Game::addMagicEffect(const SpectatorVec& spectators, const Position& pos, uint8_t effect) { - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendMagicEffect(pos, effect); } @@ -3701,15 +3707,15 @@ void Game::addMagicEffect(const SpectatorVec& list, const Position& pos, uint8_t void Game::addDistanceEffect(const Position& fromPos, const Position& toPos, uint8_t effect) { - SpectatorVec list; - map.getSpectators(list, fromPos, false, true); - map.getSpectators(list, toPos, false, true); - addDistanceEffect(list, fromPos, toPos, effect); + SpectatorVec spectators; + map.getSpectators(spectators, fromPos, false, true); + map.getSpectators(spectators, toPos, false, true); + addDistanceEffect(spectators, fromPos, toPos, effect); } -void Game::addDistanceEffect(const SpectatorVec& list, const Position& fromPos, const Position& toPos, uint8_t effect) +void Game::addDistanceEffect(const SpectatorVec& spectators, const Position& fromPos, const Position& toPos, uint8_t effect) { - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendDistanceShoot(fromPos, toPos, effect); } @@ -3718,14 +3724,14 @@ void Game::addDistanceEffect(const SpectatorVec& list, const Position& fromPos, void Game::addAnimatedText(const Position& pos, uint8_t color, const std::string& text) { - SpectatorVec list; - map.getSpectators(list, pos, false, true); - addAnimatedText(list, pos, color, text); + SpectatorVec spectators; + map.getSpectators(spectators, pos, false, true); + addAnimatedText(spectators, pos, color, text); } -void Game::addAnimatedText(const SpectatorVec& list, const Position& pos, uint8_t color, const std::string& text) +void Game::addAnimatedText(const SpectatorVec& spectators, const Position& pos, uint8_t color, const std::string& text) { - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendAnimatedText(pos, color, text); } @@ -3734,10 +3740,10 @@ void Game::addAnimatedText(const SpectatorVec& list, const Position& pos, uint8_ void Game::addMonsterSayText(const Position& pos, const std::string& text) { - SpectatorVec list; - map.getSpectators(list, pos, false, true); + SpectatorVec spectators; + map.getSpectators(spectators, pos, false, true); - for (Creature* spectator : list) { + for (Creature* spectator : spectators) { if (Player* tmpPlayer = spectator->getPlayer()) { tmpPlayer->sendCreatureSay(tmpPlayer, TALKTYPE_MONSTER_SAY, text, &pos); } @@ -3867,8 +3873,7 @@ void Game::checkLight() } if (lightChange) { - LightInfo lightInfo; - getWorldLightInfo(lightInfo); + LightInfo lightInfo = getWorldLightInfo(); for (const auto& it : players) { it.second->sendWorldLight(lightInfo); @@ -3876,10 +3881,9 @@ void Game::checkLight() } } -void Game::getWorldLightInfo(LightInfo& lightInfo) const +LightInfo Game::getWorldLightInfo() const { - lightInfo.level = lightLevel; - lightInfo.color = 0xD7; + return { lightLevel, 0xD7 }; } void Game::shutdown() @@ -3952,18 +3956,18 @@ void Game::updateCreatureSkull(const Creature* creature) return; } - SpectatorVec list; - map.getSpectators(list, creature->getPosition(), true, true); - for (Creature* spectator : list) { + SpectatorVec spectators; + map.getSpectators(spectators, creature->getPosition(), true, true); + for (Creature* spectator : spectators) { spectator->getPlayer()->sendCreatureSkull(creature); } } void Game::updatePlayerShield(Player* player) { - SpectatorVec list; - map.getSpectators(list, player->getPosition(), true, true); - for (Creature* spectator : list) { + SpectatorVec spectators; + map.getSpectators(spectators, player->getPosition(), true, true); + for (Creature* spectator : spectators) { spectator->getPlayer()->sendCreatureShield(player); } } @@ -4137,6 +4141,10 @@ bool Game::loadExperienceStages() void Game::playerInviteToParty(uint32_t playerId, uint32_t invitedId) { + if (playerId == invitedId) { + return; + } + Player* player = getPlayerByID(playerId); if (!player) { return; diff --git a/src/game.h b/src/game.h index 633b6a7..0582a23 100644 --- a/src/game.h +++ b/src/game.h @@ -244,7 +244,7 @@ class Game return playersRecord; } - void getWorldLightInfo(LightInfo& lightInfo) const; + LightInfo getWorldLightInfo() const; ReturnValue internalMoveCreature(Creature* creature, Direction direction, uint32_t flags = 0); ReturnValue internalMoveCreature(Creature& creature, Tile& toTile, uint32_t flags = 0); @@ -322,7 +322,7 @@ class Game * \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); + bool ghostMode, SpectatorVec* spectatorsPtr = nullptr, const Position* pos = nullptr); void loadPlayersRecord(); void checkPlayersRecord(); diff --git a/src/iologindata.cpp b/src/iologindata.cpp index 74c6f88..69044ff 100644 --- a/src/iologindata.cpp +++ b/src/iologindata.cpp @@ -31,13 +31,14 @@ Account IOLoginData::loadAccount(uint32_t accno) Account account; std::ostringstream query; - query << "SELECT `id`, `password`, `type`, `premdays`, `lastday` FROM `accounts` WHERE `id` = " << accno; + query << "SELECT `id`, `name`, `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("id"); + account.name = result->getNumber("name"); account.accountType = static_cast(result->getNumber("type")); account.premiumDays = result->getNumber("premdays"); account.lastDay = result->getNumber("lastday"); @@ -51,12 +52,12 @@ bool IOLoginData::saveAccount(const Account& acc) return Database::getInstance()->executeQuery(query.str()); } -bool IOLoginData::loginserverAuthentication(uint32_t accountNumber, const std::string& password, Account& account) +bool IOLoginData::loginserverAuthentication(uint32_t accountName, 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; + query << "SELECT `id`, `name`, `password`, `type`, `premdays`, `lastday` FROM `accounts` WHERE `name` = " << accountName; DBResult_ptr result = db->storeQuery(query.str()); if (!result) { return false; @@ -67,6 +68,7 @@ bool IOLoginData::loginserverAuthentication(uint32_t accountNumber, const std::s } account.id = result->getNumber("id"); + account.name = result->getNumber("name"); account.accountType = static_cast(result->getNumber("type")); account.premiumDays = result->getNumber("premdays"); account.lastDay = result->getNumber("lastday"); @@ -85,12 +87,12 @@ bool IOLoginData::loginserverAuthentication(uint32_t accountNumber, const std::s return true; } -uint32_t IOLoginData::gameworldAuthentication(uint32_t accountNumber, const std::string& password, std::string& characterName) +uint32_t IOLoginData::gameworldAuthentication(uint32_t accountName, const std::string& password, std::string& characterName) { Database* db = Database::getInstance(); std::ostringstream query; - query << "SELECT `id`, `password` FROM `accounts` WHERE `id` = " << accountNumber; + query << "SELECT `id`, `password` FROM `accounts` WHERE `name` = " << accountName; DBResult_ptr result = db->storeQuery(query.str()); if (!result) { return 0; diff --git a/src/iologindata.h b/src/iologindata.h index d207507..7a1091e 100644 --- a/src/iologindata.h +++ b/src/iologindata.h @@ -32,8 +32,8 @@ class IOLoginData 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 bool loginserverAuthentication(uint32_t accountName, const std::string& password, Account& account); + static uint32_t gameworldAuthentication(uint32_t accountName, const std::string& password, std::string& characterName); static AccountType_t getAccountType(uint32_t accountId); static void setAccountType(uint32_t accountId, AccountType_t accountType); diff --git a/src/luascript.cpp b/src/luascript.cpp index c46e5d7..95a540e 100644 --- a/src/luascript.cpp +++ b/src/luascript.cpp @@ -2796,8 +2796,7 @@ int LuaScriptInterface::luaGetWorldTime(lua_State* L) int LuaScriptInterface::luaGetWorldLight(lua_State* L) { //getWorldLight() - LightInfo lightInfo; - g_game.getWorldLightInfo(lightInfo); + LightInfo lightInfo = g_game.getWorldLightInfo(); lua_pushnumber(L, lightInfo.level); lua_pushnumber(L, lightInfo.color); return 2; @@ -6481,10 +6480,9 @@ int LuaScriptInterface::luaCreatureGetLight(lua_State* L) return 1; } - LightInfo light; - creature->getCreatureLight(light); - lua_pushnumber(L, light.level); - lua_pushnumber(L, light.color); + LightInfo lightInfo = creature->getCreatureLight(); + lua_pushnumber(L, lightInfo.level); + lua_pushnumber(L, lightInfo.color); return 2; } diff --git a/src/player.cpp b/src/player.cpp index a4936c0..e5d3a6a 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -1768,7 +1768,7 @@ void Player::death(Creature* lastHitCreature) } } } else { - setLossSkill(true); + setSkillLoss(true); auto it = conditions.begin(), end = conditions.end(); while (it != end) { @@ -2911,13 +2911,12 @@ void Player::stopWalk() cancelNextWalk = true; } -void Player::getCreatureLight(LightInfo& light) const +LightInfo Player::getCreatureLight() const { if (internalLight.level > itemsLight.level) { - light = internalLight; - } else { - light = itemsLight; + return internalLight; } + return itemsLight; } void Player::updateItemsLight(bool internal /*=false*/) @@ -3140,7 +3139,7 @@ bool Player::onKilledCreature(Creature* target, bool lastHit/* = true*/) if (Player* targetPlayer = target->getPlayer()) { if (targetPlayer && targetPlayer->getZone() == ZONE_PVP) { targetPlayer->setDropLoot(false); - targetPlayer->setLossSkill(false); + targetPlayer->setSkillLoss(false); } else if (!hasFlag(PlayerFlag_NotGainInFight) && !isPartner(targetPlayer)) { if (!Combat::isInPvpZone(this, targetPlayer) && hasAttacked(targetPlayer) && !targetPlayer->hasAttacked(this) && targetPlayer != this) { if (targetPlayer->getSkull() == SKULL_NONE && !isInWar(targetPlayer)) { @@ -3392,26 +3391,29 @@ Skulls_t Player::getSkullClient(const Creature* creature) const } const Player* player = creature->getPlayer(); - if (player && player->getSkull() == SKULL_NONE) { - if (isInWar(player)) { - return SKULL_GREEN; - } + if (!player || player->getSkull() != SKULL_NONE) { + return Creature::getSkullClient(creature); + } - if (!player->getGuildWarList().empty() && guild == player->getGuild()) { - return SKULL_GREEN; - } + if (isInWar(player)) { + return SKULL_GREEN; + } - if (player->hasAttacked(this)) { - return SKULL_YELLOW; - } + if (!player->getGuildWarList().empty() && guild == player->getGuild()) { + return SKULL_GREEN; + } - if (isPartner(player)) { - return SKULL_GREEN; - } + if (player->hasAttacked(this)) { + return SKULL_YELLOW; + } + + if (isPartner(player)) { + return SKULL_GREEN; } return Creature::getSkullClient(creature); } + bool Player::hasAttacked(const Player* attacked) const { if (hasFlag(PlayerFlag_NotGainInFight) || !attacked) { @@ -3634,7 +3636,7 @@ bool Player::isInviting(const Player* player) const bool Player::isPartner(const Player* player) const { - if (!player || !party) { + if (!player || !party || player == this) { return false; } return party == player->party; diff --git a/src/player.h b/src/player.h index f088f13..0ca4979 100644 --- a/src/player.h +++ b/src/player.h @@ -559,7 +559,7 @@ class Player final : public Creature, public Cylinder void onIdleStatus() final; void onPlacedCreature() final; - void getCreatureLight(LightInfo& light) const final; + LightInfo getCreatureLight() const override; Skulls_t getSkull() const final; Skulls_t getSkullClient(const Creature* creature) const final; diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp index faa9e5b..a371bc1 100644 --- a/src/protocolgame.cpp +++ b/src/protocolgame.cpp @@ -1594,9 +1594,7 @@ void ProtocolGame::sendAddCreature(const Creature* creature, const Position& pos sendSkills(); //gameworld light-settings - LightInfo lightInfo; - g_game.getWorldLightInfo(lightInfo); - sendWorldLight(lightInfo); + sendWorldLight(g_game.getWorldLightInfo()); //player light level sendCreatureLight(creature); @@ -1871,8 +1869,7 @@ void ProtocolGame::AddCreature(NetworkMessage& msg, const Creature* creature, bo AddOutfit(msg, outfit); } - LightInfo lightInfo; - creature->getCreatureLight(lightInfo); + LightInfo lightInfo = creature->getCreatureLight(); msg.addByte(player->isAccessPlayer() ? 0xFF : lightInfo.level); msg.addByte(lightInfo.color); @@ -1941,8 +1938,7 @@ void ProtocolGame::AddWorldLight(NetworkMessage& msg, const LightInfo& lightInfo void ProtocolGame::AddCreatureLight(NetworkMessage& msg, const Creature* creature) { - LightInfo lightInfo; - creature->getCreatureLight(lightInfo); + LightInfo lightInfo = creature->getCreatureLight(); msg.addByte(0x8D); msg.add(creature->getID());