first implementation of quest log need to review everything

This commit is contained in:
ErikasKontenis 2020-02-11 21:03:54 +02:00
parent abed251a30
commit 871013018e
13 changed files with 1629 additions and 6 deletions

View File

@ -37,7 +37,7 @@ replaceKickOnLogin = true
maxPacketsPerSecond = -1 maxPacketsPerSecond = -1
autoStackCumulatives = false autoStackCumulatives = false
moneyRate = 1 moneyRate = 1
clientVersion = 780 clientVersion = 792
-- Deaths -- Deaths
-- NOTE: Leave deathLosePercent as -1 if you want to use the default -- NOTE: Leave deathLosePercent as -1 if you want to use the default

1167
data/XML/quests.xml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -32,7 +32,10 @@ local reloadTypes = {
["npc"] = { targetType = RELOAD_TYPE_NPCS, name = "npcs" }, ["npc"] = { targetType = RELOAD_TYPE_NPCS, name = "npcs" },
["npcs"] = { 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" }, ["raid"] = { targetType = RELOAD_TYPE_RAIDS, name = "raids" },
["raids"] = { targetType = RELOAD_TYPE_RAIDS, name = "raids" }, ["raids"] = { targetType = RELOAD_TYPE_RAIDS, name = "raids" },

View File

@ -104,6 +104,8 @@ void Game::setGameState(GameState_t newState)
raids.loadFromXml(); raids.loadFromXml();
raids.startup(); raids.startup();
quests.loadFromXml();
loadMotdNum(); loadMotdNum();
loadPlayersRecord(); 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, void Game::playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type,
const std::string& receiver, const std::string& text) const std::string& receiver, const std::string& text)
{ {
@ -4599,6 +4626,8 @@ bool Game::reload(ReloadTypes_t reloadType)
Npcs::reload(); Npcs::reload();
return true; return true;
} }
case RELOAD_TYPE_QUESTS: return quests.reload();
case RELOAD_TYPE_RAIDS: return raids.reload() && raids.startup(); case RELOAD_TYPE_RAIDS: return raids.reload() && raids.startup();
case RELOAD_TYPE_SPELLS: { case RELOAD_TYPE_SPELLS: {
@ -4636,6 +4665,7 @@ bool Game::reload(ReloadTypes_t reloadType)
raids.reload() && raids.startup(); raids.reload() && raids.startup();
g_talkActions->reload(); g_talkActions->reload();
Item::items.reload(); Item::items.reload();
quests.reload();
g_globalEvents->reload(); g_globalEvents->reload();
g_events->load(); g_events->load();
g_chat->load(); g_chat->load();

View File

@ -31,6 +31,7 @@
#include "raids.h" #include "raids.h"
#include "npc.h" #include "npc.h"
#include "wildcardtree.h" #include "wildcardtree.h"
#include "quests.h"
class ServiceManager; class ServiceManager;
class Creature; class Creature;
@ -387,6 +388,8 @@ class Game
void playerRequestRemoveVip(uint32_t playerId, uint32_t guid); void playerRequestRemoveVip(uint32_t playerId, uint32_t guid);
void playerTurn(uint32_t playerId, Direction dir); void playerTurn(uint32_t playerId, Direction dir);
void playerRequestOutfit(uint32_t playerId); 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, void playerSay(uint32_t playerId, uint16_t channelId, SpeakClasses type,
const std::string& receiver, const std::string& text); const std::string& receiver, const std::string& text);
void playerChangeOutfit(uint32_t playerId, Outfit_t outfit); void playerChangeOutfit(uint32_t playerId, Outfit_t outfit);
@ -493,6 +496,7 @@ class Game
Groups groups; Groups groups;
Map map; Map map;
Raids raids; Raids raids;
Quests quests;
protected: protected:
bool playerSaySpell(Player* player, SpeakClasses type, const std::string& text); bool playerSaySpell(Player* player, SpeakClasses type, const std::string& text);

View File

@ -495,7 +495,7 @@ bool IOLoginData::loadPlayer(Player* player, DBResult_ptr result)
query << "SELECT `key`, `value` FROM `player_storage` WHERE `player_id` = " << player->getGUID(); 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 { do {
player->addStorageValue(result->getNumber<uint32_t>("key"), result->getNumber<int32_t>("value")); player->addStorageValue(result->getNumber<uint32_t>("key"), result->getNumber<int32_t>("value"), true);
} while (result->next()); } while (result->next());
} }

View File

@ -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, RESERVED_RANGE)) {
if (IS_IN_KEYRANGE(key, OUTFITS_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) { if (value != -1) {
int32_t oldValue;
getStorageValue(key, oldValue);
storageMap[key] = value; 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); storageMap.erase(key);
} }
} }

View File

@ -273,7 +273,7 @@ class Player final : public Creature, public Cylinder
bool canOpenCorpse(uint32_t ownerId) const; 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; bool getStorageValue(const uint32_t key, int32_t& value) const;
void genReservedStorageRange(); void genReservedStorageRange();
@ -854,6 +854,16 @@ class Player final : public Creature, public Cylinder
client->sendOpenPrivateChannel(receiver); client->sendOpenPrivateChannel(receiver);
} }
} }
void sendQuestLog() {
if (client) {
client->sendQuestLog();
}
}
void sendQuestLine(const Quest* quest) {
if (client) {
client->sendQuestLine(quest);
}
}
void sendOutfitWindow() { void sendOutfitWindow() {
if (client) { if (client) {
client->sendOutfitWindow(); client->sendOutfitWindow();
@ -1012,6 +1022,7 @@ class Player final : public Creature, public Cylinder
uint64_t experience = 0; uint64_t experience = 0;
uint64_t manaSpent = 0; uint64_t manaSpent = 0;
uint64_t bankBalance = 0; uint64_t bankBalance = 0;
uint64_t lastQuestlogUpdate = 0;
int64_t lastAttack = 0; int64_t lastAttack = 0;
int64_t lastFailedFollow = 0; int64_t lastFailedFollow = 0;
int64_t lastPing; int64_t lastPing;

View File

@ -429,6 +429,8 @@ void ProtocolGame::parsePacket(NetworkMessage& msg)
case 0xE6: parseBugReport(msg); break; case 0xE6: parseBugReport(msg); break;
case 0xE7: /* violation window */ break; case 0xE7: /* violation window */ break;
case 0xE8: parseDebugAssert(msg); break; case 0xE8: parseDebugAssert(msg); break;
case 0xF0: addGameTaskTimed(DISPATCHER_TASK_EXPIRATION, &Game::playerShowQuestLog, player->getID()); break;
case 0xF1: parseQuestLine(msg); break;
default: default:
std::cout << "Player: " << player->getName() << " sent an unknown packet header: 0x" << std::hex << static_cast<uint16_t>(recvbyte) << std::dec << "!" << std::endl; std::cout << "Player: " << player->getName() << " sent an unknown packet header: 0x" << std::hex << static_cast<uint16_t>(recvbyte) << std::dec << "!" << std::endl;
break; break;
@ -963,6 +965,12 @@ void ProtocolGame::parsePassPartyLeadership(NetworkMessage& msg)
addGameTask(&Game::playerPassPartyLeadership, player->getID(), targetId); addGameTask(&Game::playerPassPartyLeadership, player->getID(), targetId);
} }
void ProtocolGame::parseQuestLine(NetworkMessage& msg)
{
uint16_t questId = msg.get<uint16_t>();
addGameTask(&Game::playerShowQuestLine, player->getID(), questId);
}
void ProtocolGame::parseSeekInContainer(NetworkMessage& msg) void ProtocolGame::parseSeekInContainer(NetworkMessage& msg)
{ {
uint8_t containerId = msg.getByte(); uint8_t containerId = msg.getByte();
@ -1205,7 +1213,39 @@ void ProtocolGame::sendContainer(uint8_t cid, const Container* container, bool h
writeToOutputBuffer(msg); writeToOutputBuffer(msg);
} }
void ProtocolGame::sendQuestLog()
{
NetworkMessage msg;
msg.addByte(0xF0);
msg.add<uint16_t>(g_game.quests.getQuestsCount(player));
for (const Quest& quest : g_game.quests.getQuests()) {
if (quest.isStarted(player)) {
msg.add<uint16_t>(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<uint16_t>(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) void ProtocolGame::sendTradeItemRequest(const std::string& traderName, const Item* item, bool ack)
{ {

View File

@ -113,6 +113,8 @@ class ProtocolGame final : public Protocol
void parseTextWindow(NetworkMessage& msg); void parseTextWindow(NetworkMessage& msg);
void parseHouseWindow(NetworkMessage& msg); void parseHouseWindow(NetworkMessage& msg);
void parseQuestLine(NetworkMessage& msg);
void parseInviteToParty(NetworkMessage& msg); void parseInviteToParty(NetworkMessage& msg);
void parseJoinParty(NetworkMessage& msg); void parseJoinParty(NetworkMessage& msg);
void parseRevokePartyInvite(NetworkMessage& msg); void parseRevokePartyInvite(NetworkMessage& msg);
@ -156,6 +158,9 @@ class ProtocolGame final : public Protocol
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 sendCreatureSay(const Creature* creature, SpeakClasses type, const std::string& text, const Position* pos = nullptr);
void sendQuestLog();
void sendQuestLine(const Quest* quest);
void sendCancelWalk(); void sendCancelWalk();
void sendChangeSpeed(const Creature* creature, uint32_t speed); void sendChangeSpeed(const Creature* creature, uint32_t speed);
void sendCancelTarget(); void sendCancelTarget();

230
src/quests.cpp Normal file
View File

@ -0,0 +1,230 @@
/**
* The Forgotten Server - a free and open-source MMORPG server emulator
* Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "otpch.h"
#include "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<int32_t>(questNode.attribute("startstorageid").value()),
pugi::cast<int32_t>(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<int32_t>(missionNode.attribute("storageid").value()),
pugi::cast<int32_t>(missionNode.attribute("startvalue").value()),
pugi::cast<int32_t>(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<int32_t>(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 &quest;
}
}
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;
}

119
src/quests.h Normal file
View File

@ -0,0 +1,119 @@
/**
* The Forgotten Server - a free and open-source MMORPG server emulator
* Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef FS_QUESTS_H_16E44051F23547BE8097F8EA9FCAACA0
#define FS_QUESTS_H_16E44051F23547BE8097F8EA9FCAACA0
#include "player.h"
#include "networkmessage.h"
class Mission;
class Quest;
using MissionsList = std::list<Mission>;
using QuestsList = std::list<Quest>;
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<int32_t, std::string> 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

View File

@ -217,6 +217,7 @@
<ClCompile Include="..\src\waitlist.cpp" /> <ClCompile Include="..\src\waitlist.cpp" />
<ClCompile Include="..\src\wildcardtree.cpp" /> <ClCompile Include="..\src\wildcardtree.cpp" />
<ClCompile Include="..\src\xtea.cpp" /> <ClCompile Include="..\src\xtea.cpp" />
<ClCompile Include="..\src\quests.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="..\src\account.h" /> <ClInclude Include="..\src\account.h" />
@ -295,6 +296,7 @@
<ClInclude Include="..\src\waitlist.h" /> <ClInclude Include="..\src\waitlist.h" />
<ClInclude Include="..\src\wildcardtree.h" /> <ClInclude Include="..\src\wildcardtree.h" />
<ClInclude Include="..\src\xtea.h" /> <ClInclude Include="..\src\xtea.h" />
<ClInclude Include="..\src\quests.h" />
</ItemGroup> </ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets"> <ImportGroup Label="ExtensionTargets">