diff --git a/config.lua b/config.lua
index c692d20..2cf2d9c 100644
--- a/config.lua
+++ b/config.lua
@@ -37,7 +37,7 @@ replaceKickOnLogin = true
maxPacketsPerSecond = -1
autoStackCumulatives = false
moneyRate = 1
-clientVersion = 780
+clientVersion = 792
-- Deaths
-- NOTE: Leave deathLosePercent as -1 if you want to use the default
diff --git a/data/XML/quests.xml b/data/XML/quests.xml
new file mode 100644
index 0000000..fed047e
--- /dev/null
+++ b/data/XML/quests.xml
@@ -0,0 +1,1167 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/data/talkactions/scripts/reload.lua b/data/talkactions/scripts/reload.lua
index 1dd9c14..57e8f90 100644
--- a/data/talkactions/scripts/reload.lua
+++ b/data/talkactions/scripts/reload.lua
@@ -32,7 +32,10 @@ local reloadTypes = {
["npc"] = { targetType = RELOAD_TYPE_NPCS, name = "npcs" },
["npcs"] = { targetType = RELOAD_TYPE_NPCS, name = "npcs" },
-
+
+ ["quest"] = { targetType = RELOAD_TYPE_QUESTS, name = "quests" },
+ ["quests"] = { targetType = RELOAD_TYPE_QUESTS, name = "quests" },
+
["raid"] = { targetType = RELOAD_TYPE_RAIDS, name = "raids" },
["raids"] = { targetType = RELOAD_TYPE_RAIDS, name = "raids" },
diff --git a/src/game.cpp b/src/game.cpp
index c35f83a..028e1a4 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -104,6 +104,8 @@ void Game::setGameState(GameState_t newState)
raids.loadFromXml();
raids.startup();
+ quests.loadFromXml();
+
loadMotdNum();
loadPlayersRecord();
@@ -2914,6 +2916,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)
{
@@ -4599,6 +4626,8 @@ bool Game::reload(ReloadTypes_t reloadType)
Npcs::reload();
return true;
}
+
+ case RELOAD_TYPE_QUESTS: return quests.reload();
case RELOAD_TYPE_RAIDS: return raids.reload() && raids.startup();
case RELOAD_TYPE_SPELLS: {
@@ -4636,6 +4665,7 @@ bool Game::reload(ReloadTypes_t reloadType)
raids.reload() && raids.startup();
g_talkActions->reload();
Item::items.reload();
+ quests.reload();
g_globalEvents->reload();
g_events->load();
g_chat->load();
diff --git a/src/game.h b/src/game.h
index e7132c4..2182dca 100644
--- a/src/game.h
+++ b/src/game.h
@@ -31,6 +31,7 @@
#include "raids.h"
#include "npc.h"
#include "wildcardtree.h"
+#include "quests.h"
class ServiceManager;
class Creature;
@@ -387,6 +388,8 @@ class Game
void playerRequestRemoveVip(uint32_t playerId, uint32_t guid);
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);
@@ -493,6 +496,7 @@ class Game
Groups groups;
Map map;
Raids raids;
+ Quests quests;
protected:
bool playerSaySpell(Player* player, SpeakClasses type, const std::string& text);
diff --git a/src/iologindata.cpp b/src/iologindata.cpp
index 61e49ce..8501bd8 100644
--- a/src/iologindata.cpp
+++ b/src/iologindata.cpp
@@ -495,7 +495,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
query << "SELECT `key`, `value` FROM `player_storage` WHERE `player_id` = " << player->getGUID();
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());
}
diff --git a/src/player.cpp b/src/player.cpp
index 7335037..90a303b 100644
--- a/src/player.cpp
+++ b/src/player.cpp
@@ -507,7 +507,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)) {
@@ -524,8 +524,20 @@ void Player::addStorageValue(const uint32_t key, const int32_t value)
}
if (value != -1) {
+ int32_t oldValue;
+ getStorageValue(key, oldValue);
+
storageMap[key] = value;
- } else {
+
+ if (!isLogin && g_game.getClientVersion() >= CLIENT_VERSION_790) {
+ 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);
}
}
diff --git a/src/player.h b/src/player.h
index 7a47cf3..15703a4 100644
--- a/src/player.h
+++ b/src/player.h
@@ -273,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);
+ 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();
@@ -854,6 +854,16 @@ class Player final : public Creature, public Cylinder
client->sendOpenPrivateChannel(receiver);
}
}
+ void sendQuestLog() {
+ if (client) {
+ client->sendQuestLog();
+ }
+ }
+ void sendQuestLine(const Quest* quest) {
+ if (client) {
+ client->sendQuestLine(quest);
+ }
+ }
void sendOutfitWindow() {
if (client) {
client->sendOutfitWindow();
@@ -1012,6 +1022,7 @@ class Player final : public Creature, public Cylinder
uint64_t experience = 0;
uint64_t manaSpent = 0;
uint64_t bankBalance = 0;
+ uint64_t lastQuestlogUpdate = 0;
int64_t lastAttack = 0;
int64_t lastFailedFollow = 0;
int64_t lastPing;
diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp
index b93956e..7d1d50e 100644
--- a/src/protocolgame.cpp
+++ b/src/protocolgame.cpp
@@ -429,6 +429,8 @@ void ProtocolGame::parsePacket(NetworkMessage& msg)
case 0xE6: parseBugReport(msg); 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;
default:
std::cout << "Player: " << player->getName() << " sent an unknown packet header: 0x" << std::hex << static_cast(recvbyte) << std::dec << "!" << std::endl;
break;
@@ -963,6 +965,12 @@ void ProtocolGame::parsePassPartyLeadership(NetworkMessage& msg)
addGameTask(&Game::playerPassPartyLeadership, player->getID(), targetId);
}
+void ProtocolGame::parseQuestLine(NetworkMessage& msg)
+{
+ uint16_t questId = msg.get();
+ addGameTask(&Game::playerShowQuestLine, player->getID(), questId);
+}
+
void ProtocolGame::parseSeekInContainer(NetworkMessage& msg)
{
uint8_t containerId = msg.getByte();
@@ -1205,7 +1213,39 @@ void ProtocolGame::sendContainer(uint8_t cid, const Container* container, bool h
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)
{
diff --git a/src/protocolgame.h b/src/protocolgame.h
index 773c633..c581a5e 100644
--- a/src/protocolgame.h
+++ b/src/protocolgame.h
@@ -113,6 +113,8 @@ class ProtocolGame final : public Protocol
void parseTextWindow(NetworkMessage& msg);
void parseHouseWindow(NetworkMessage& msg);
+ void parseQuestLine(NetworkMessage& msg);
+
void parseInviteToParty(NetworkMessage& msg);
void parseJoinParty(NetworkMessage& msg);
void parseRevokePartyInvite(NetworkMessage& msg);
@@ -156,6 +158,9 @@ class ProtocolGame final : public Protocol
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();
diff --git a/src/quests.cpp b/src/quests.cpp
new file mode 100644
index 0000000..d28d9e7
--- /dev/null
+++ b/src/quests.cpp
@@ -0,0 +1,230 @@
+/**
+ * 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..45cb002
--- /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/vc14/theforgottenserver.vcxproj b/vc14/theforgottenserver.vcxproj
index 54a98a8..b1c4e61 100644
--- a/vc14/theforgottenserver.vcxproj
+++ b/vc14/theforgottenserver.vcxproj
@@ -217,6 +217,7 @@
+
@@ -295,6 +296,7 @@
+