Converted Quest Database to YAML (#4064)

* Changes TimeLimit format to be human readable.
* Converts title to string.
* Converts objectives and drop items to vectors.
* General clean ups and optimizations.
* Got rid of dummy_quest.
* Added TXT to YAML converter.
Thanks to @Atemo and @Normynator!
Co-authored-by: atemo <capucrath@gmail.com>
This commit is contained in:
Aleos 2020-03-30 13:17:19 -04:00 committed by GitHub
parent e07d2b9ec2
commit 2f326bf04a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 17412 additions and 7600 deletions

View File

@ -1,7 +0,0 @@
// Quest Database
//
// Structure of Database:
// Quest ID,Time Limit,Target1,Val1,Target2,Val2,Target3,Val3,MobID1,NameID1,Rate1,MobID2,NameID2,Rate2,MobID3,NameID3,Rate3,Quest Title
//
// The MobID*, NameID*, and Rate* reflect special values for quests that can drop an item at given rate from given mob.
// If no MobID* is given, then any mob has a chance to drop the given ItemID*.

View File

@ -0,0 +1,44 @@
# This file is a part of rAthena.
# Copyright(C) 2019 rAthena Development Team
# https://rathena.org - https://github.com/rathena
#
# This 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 3 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, see <http://www.gnu.org/licenses/>.
#
###########################################################################
# Quest Database
###########################################################################
#
# Quest Settings
#
###########################################################################
# - Id Quest ID.
# Title Quest title.
# TimeLimit Amount of time before the quest expires. (Default: 0)
# Use a number following by "d" for day(s), "h" for hour(s), "mn" for minute(s), and "s" for second(s).
# Specify with "+" for how long until the quest expires.
# Specify without "+" for the exact time the quest expires using "d" (optionnal), [0-23]"h" (required), [0-59]"mn" (optionnal), [0-59]"s" (optionnal) format.
# Please note the number before "d" only shift the exact timer to the given day(s).
# Targets: Quest objective target. (Default: null)
# - Mob Monster to kill.
# Count Amount of monsters to kill.
# Drops: Quest item drop targets. (Default: null)
# - Mob Monster to kill. 0 will apply to all monsters. (Default: 0)
# Item Item to drop.
# Count Amount of items that will drop. Non-stackable items default to 1. (Default: 1)
# Rate Item drop rate. (10000 = 100%)
###########################################################################
Header:
Type: QUEST_DB
Version: 1

File diff suppressed because it is too large Load Diff

7098
db/pre-re/quest_db.yml Normal file

File diff suppressed because it is too large Load Diff

52
db/quest_db.yml Normal file
View File

@ -0,0 +1,52 @@
# This file is a part of rAthena.
# Copyright(C) 2019 rAthena Development Team
# https://rathena.org - https://github.com/rathena
#
# This 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 3 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, see <http://www.gnu.org/licenses/>.
#
###########################################################################
# Quest Database
###########################################################################
#
# Quest Settings
#
###########################################################################
# - Id Quest ID.
# Title Quest title.
# TimeLimit Amount of time before the quest expires. (Default: 0)
# Use a number following by "d" for day(s), "h" for hour(s), "mn" for minute(s), and "s" for second(s).
# Specify with "+" for how long until the quest expires.
# Specify without "+" for the exact time the quest expires using "d" (optionnal), [0-23]"h" (required), [0-59]"mn" (optionnal), [0-59]"s" (optionnal) format.
# Please note the number before "d" only shift the exact timer to the given day(s).
# Targets: Quest objective target. (Default: null)
# - Mob Monster to kill.
# Count Amount of monsters to kill.
# Drops: Quest item drop targets. (Default: null)
# - Mob Monster to kill. 0 will apply to all monsters. (Default: 0)
# Item Item to drop.
# Count Amount of items that will drop. Non-stackable items default to 1. (Default: 1)
# Rate Item drop rate. (10000 = 100%)
###########################################################################
Header:
Type: QUEST_DB
Version: 1
Footer:
Imports:
- Path: db/pre-re/quest_db.yml
Mode: Prerenewal
- Path: db/re/quest_db.yml
Mode: Renewal
- Path: db/import/quest_db.yml

File diff suppressed because it is too large Load Diff

9573
db/re/quest_db.yml Normal file

File diff suppressed because it is too large Load Diff

23
doc/yaml/db/quest_db.yml Normal file
View File

@ -0,0 +1,23 @@
###########################################################################
# Quest Database
###########################################################################
#
# Quest Settings
#
###########################################################################
# - Id Quest ID.
# Title Quest title.
# TimeLimit Amount of time before the quest expires. (Default: 0)
# Use a number following by "d" for day(s), "h" for hour(s), "mn" for minute(s), and "s" for second(s).
# Specify with "+" for how long until the quest expires.
# Specify without "+" for the exact time the quest expires using "d" (optionnal), [0-23]"h" (required), [0-59]"mn" (optionnal), [0-59]"s" (optionnal) format.
# Please note the number before "d" only shift the exact timer to the given day(s).
# Targets: Quest objective target. (Default: null)
# - Mob Monster to kill.
# Count Amount of monsters to kill.
# Drops: Quest item drop targets. (Default: null)
# - Mob Monster to kill. 0 will apply to all monsters. (Default: 0)
# Item Item to drop.
# Count Amount of items that will drop. Non-stackable items default to 1. (Default: 1)
# Rate Item drop rate. (10000 = 100%)
###########################################################################

View File

@ -102,7 +102,7 @@ bool mapif_quest_delete(uint32 char_id, int quest_id) {
* @return false in case of errors, true otherwise
*/
bool mapif_quest_add(uint32 char_id, struct quest qd) {
if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s`(`quest_id`, `char_id`, `state`, `time`, `count1`, `count2`, `count3`) VALUES ('%d', '%d', '%d','%d', '%d', '%d', '%d')", schema_config.quest_db, qd.quest_id, char_id, qd.state, qd.time, qd.count[0], qd.count[1], qd.count[2]) )
if( SQL_ERROR == Sql_Query(sql_handle, "INSERT INTO `%s`(`quest_id`, `char_id`, `state`, `time`, `count1`, `count2`, `count3`) VALUES ('%d', '%d', '%d', '%u', '%d', '%d', '%d')", schema_config.quest_db, qd.quest_id, char_id, qd.state, qd.time, qd.count[0], qd.count[1], qd.count[2]) )
{
Sql_ShowDebug(sql_handle);
return false;

View File

@ -69,7 +69,6 @@
#define MAX_GUILDLEVEL 50 ///Max Guild level
#define MAX_GUARDIANS 8 ///Local max per castle. If this value is increased, need to add more fields on MySQL `guild_castle` table [Skotlex]
#define MAX_QUEST_OBJECTIVES 3 ///Max quest objectives for a quest
#define MAX_QUEST_DROPS 3 ///Max quest drops for a quest
#define MAX_PC_BONUS_SCRIPT 50 ///Max bonus script can be fetched from `bonus_script` table on player load [Cydh]
#define MAX_ITEM_RDM_OPT 5 /// Max item random option [Napster]
#define DB_NAME_LEN 256 //max len of dbs
@ -212,7 +211,7 @@ enum e_mode {
#define CL_MASK 0xF000000
// Questlog states
enum quest_state {
enum e_quest_state : uint8 {
Q_INACTIVE, ///< Inactive quest (the user can toggle between active and inactive quests)
Q_ACTIVE, ///< Active quest
Q_COMPLETE, ///< Completed quest
@ -221,9 +220,9 @@ enum quest_state {
/// Questlog entry
struct quest {
int quest_id; ///< Quest ID
unsigned int time; ///< Expiration time
uint32 time; ///< Expiration time
int count[MAX_QUEST_OBJECTIVES]; ///< Kill counters of each quest objective
enum quest_state state; ///< Current quest state
e_quest_state state; ///< Current quest state
};
struct s_item_randomoption {

View File

@ -4027,8 +4027,8 @@ ACMD_FUNC(reload) {
map_msg_reload();
clif_displaymessage(fd, msg_txt(sd,463)); // Message configuration has been reloaded.
} else if (strstr(command, "questdb") || strncmp(message, "questdb", 3) == 0) {
do_reload_quest();
clif_displaymessage(fd, msg_txt(sd,1377)); // Quest database has been reloaded.
if (quest_db.reload())
clif_displaymessage(fd, msg_txt(sd,1377)); // Quest database has been reloaded.
} else if (strstr(command, "instancedb") || strncmp(message, "instancedb", 4) == 0) {
instance_reload();
clif_displaymessage(fd, msg_txt(sd,516)); // Instance database has been reloaded.

View File

@ -16929,25 +16929,24 @@ void clif_quest_send_list(struct map_session_data *sd)
WFIFOL(fd, 4) = limit;
for (i = 0; i < limit; i++) {
struct quest_db *qi = quest_search(sd->quest_log[i].quest_id);
std::shared_ptr<s_quest_db> qi = quest_search(sd->quest_log[i].quest_id);
WFIFOL(fd, offset) = sd->quest_log[i].quest_id;
offset += 4;
WFIFOB(fd, offset) = sd->quest_log[i].state;
offset++;
WFIFOL(fd, offset) = sd->quest_log[i].time - qi->time;
WFIFOL(fd, offset) = static_cast<uint32>(sd->quest_log[i].time - qi->time);
offset += 4;
WFIFOL(fd, offset) = sd->quest_log[i].time;
WFIFOL(fd, offset) = static_cast<uint32>(sd->quest_log[i].time);
offset += 4;
WFIFOW(fd, offset) = qi->objectives_count;
WFIFOW(fd, offset) = static_cast<uint16>(qi->objectives.size());
offset += 2;
if( qi->objectives_count > 0 ){
int j;
if (!qi->objectives.empty()) {
struct mob_db *mob;
for( j = 0; j < qi->objectives_count; j++ ){
mob = mob_db(qi->objectives[j].mob);
for (int j = 0; j < qi->objectives.size(); j++) {
mob = mob_db(qi->objectives[j]->mob_id);
#if PACKETVER >= 20150513
WFIFOL(fd, offset) = sd->quest_log[i].quest_id * 1000 + j;
@ -16955,7 +16954,7 @@ void clif_quest_send_list(struct map_session_data *sd)
WFIFOL(fd, offset) = 0; // TODO: Find info - mobType
offset += 4;
#endif
WFIFOL(fd, offset) = qi->objectives[j].mob;
WFIFOL(fd, offset) = qi->objectives[j]->mob_id;
offset += 4;
#if PACKETVER >= 20150513
WFIFOW(fd, offset) = 0; // TODO: Find info - levelMin
@ -16965,7 +16964,7 @@ void clif_quest_send_list(struct map_session_data *sd)
#endif
WFIFOW(fd, offset) = sd->quest_log[i].count[j];
offset += 2;
WFIFOW(fd, offset) = qi->objectives[j].count;
WFIFOW(fd, offset) = qi->objectives[j]->count;
offset += 2;
safestrncpy((char*)WFIFOP(fd, offset), mob->jname, NAME_LENGTH);
offset += NAME_LENGTH;
@ -16998,7 +16997,7 @@ void clif_quest_send_list(struct map_session_data *sd)
void clif_quest_send_mission(struct map_session_data *sd)
{
int fd = sd->fd;
int i, j, limit = 0;
int limit = 0;
int len = sd->avail_quests*104+8;
struct mob_db *mob;
@ -17008,18 +17007,18 @@ void clif_quest_send_mission(struct map_session_data *sd)
WFIFOW(fd, 2) = len;
WFIFOL(fd, 4) = limit;
for (i = 0; i < limit; i++) {
struct quest_db *qi = quest_search(sd->quest_log[i].quest_id);
for (int i = 0; i < limit; i++) {
std::shared_ptr<s_quest_db> qi = quest_search(sd->quest_log[i].quest_id);
WFIFOL(fd, i*104+8) = sd->quest_log[i].quest_id;
WFIFOL(fd, i*104+12) = sd->quest_log[i].time - qi->time;
WFIFOL(fd, i*104+16) = sd->quest_log[i].time;
WFIFOW(fd, i*104+20) = qi->objectives_count;
WFIFOL(fd, i*104+12) = static_cast<uint32>(sd->quest_log[i].time - qi->time);
WFIFOL(fd, i*104+16) = static_cast<uint32>(sd->quest_log[i].time);
WFIFOW(fd, i*104+20) = static_cast<uint16>(qi->objectives.size());
for (j = 0 ; j < qi->objectives_count; j++) {
WFIFOL(fd, i*104+22+j*30) = qi->objectives[j].mob;
for (int j = 0 ; j < qi->objectives.size(); j++) {
WFIFOL(fd, i*104+22+j*30) = qi->objectives[j]->mob_id;
WFIFOW(fd, i*104+26+j*30) = sd->quest_log[i].count[j];
mob = mob_db(qi->objectives[j].mob);
mob = mob_db(qi->objectives[j]->mob_id);
safestrncpy(WFIFOCP(fd, i*104+28+j*30), mob->jname, NAME_LENGTH);
}
}
@ -17035,8 +17034,7 @@ void clif_quest_send_mission(struct map_session_data *sd)
void clif_quest_add(struct map_session_data *sd, struct quest *qd)
{
int fd = sd->fd;
int i, offset;
struct quest_db *qi = quest_search(qd->quest_id);
std::shared_ptr<s_quest_db> qi = quest_search(qd->quest_id);
#if PACKETVER >= 20150513
int cmd = 0x9f9;
#else
@ -17047,11 +17045,11 @@ void clif_quest_add(struct map_session_data *sd, struct quest *qd)
WFIFOW(fd, 0) = cmd;
WFIFOL(fd, 2) = qd->quest_id;
WFIFOB(fd, 6) = qd->state;
WFIFOB(fd, 7) = qd->time - qi->time;
WFIFOL(fd, 11) = qd->time;
WFIFOW(fd, 15) = qi->objectives_count;
WFIFOB(fd, 7) = static_cast<uint8>(qd->time - qi->time);
WFIFOL(fd, 11) = static_cast<uint32>(qd->time);
WFIFOW(fd, 15) = static_cast<uint16>(qi->objectives.size());
for (i = 0, offset = 17; i < qi->objectives_count; i++) {
for (int i = 0, offset = 17; i < qi->objectives.size(); i++) {
struct mob_db *mob;
#if PACKETVER >= 20150513
WFIFOL(fd, offset) = qd->quest_id * 1000 + i;
@ -17059,7 +17057,7 @@ void clif_quest_add(struct map_session_data *sd, struct quest *qd)
WFIFOL(fd, offset) = 0; // TODO: Find info - mobType
offset += 4;
#endif
WFIFOL(fd, offset) = qi->objectives[i].mob;
WFIFOL(fd, offset) = qi->objectives[i]->mob_id;
offset += 4;
#if PACKETVER >= 20150513
WFIFOW(fd, offset) = 0; // TODO: Find info - levelMin
@ -17069,7 +17067,7 @@ void clif_quest_add(struct map_session_data *sd, struct quest *qd)
#endif
WFIFOW(fd, offset) = qd->count[i];
offset += 2;
mob = mob_db(qi->objectives[i].mob);
mob = mob_db(qi->objectives[i]->mob_id);
safestrncpy(WFIFOCP(fd, offset), mob->jname, NAME_LENGTH);
offset += NAME_LENGTH;
}
@ -17077,16 +17075,16 @@ void clif_quest_add(struct map_session_data *sd, struct quest *qd)
WFIFOSET(fd, packet_len(cmd));
#if PACKETVER >= 20150513
int len = 4 + qi->objectives_count * 12;
int len = 4 + qi->objectives.size() * 12;
WFIFOHEAD(fd, len);
WFIFOW(fd, 0) = 0x8fe;
WFIFOW(fd, 2) = len;
for( i = 0, offset = 4; i < qi->objectives_count; i++, offset += 12 ){
for (int i = 0, offset = 4; i < qi->objectives.size(); i++, offset += 12) {
WFIFOL(fd, offset) = qd->quest_id * 1000 + i;
WFIFOL(fd, offset+4) = qi->objectives[i].mob;
WFIFOW(fd, offset + 10) = qi->objectives[i].count;
WFIFOL(fd, offset+4) = qi->objectives[i]->mob_id;
WFIFOW(fd, offset + 10) = qi->objectives[i]->count;
WFIFOW(fd, offset + 12) = qd->count[i];
}
@ -17115,9 +17113,9 @@ void clif_quest_delete(struct map_session_data *sd, int quest_id)
void clif_quest_update_objective(struct map_session_data *sd, struct quest *qd, int mobid)
{
int fd = sd->fd;
int i, offset;
struct quest_db *qi = quest_search(qd->quest_id);
int len = qi->objectives_count * 12 + 6;
int offset = 6;
std::shared_ptr<s_quest_db> qi = quest_search(qd->quest_id);
int len = qi->objectives.size() * 12 + 6;
#if PACKETVER >= 20150513
int cmd = 0x9fa;
#else
@ -17126,20 +17124,20 @@ void clif_quest_update_objective(struct map_session_data *sd, struct quest *qd,
WFIFOHEAD(fd, len);
WFIFOW(fd, 0) = cmd;
WFIFOW(fd, 4) = qi->objectives_count;
WFIFOW(fd, 4) = static_cast<uint16>(qi->objectives.size());
for (i = 0, offset = 6; i < qi->objectives_count; i++) {
if (mobid == 0 || mobid == qi->objectives[i].mob) {
for (int i = 0; i < qi->objectives.size(); i++) {
if (mobid == 0 || mobid == qi->objectives[i]->mob_id) {
WFIFOL(fd, offset) = qd->quest_id;
offset += 4;
#if PACKETVER >= 20150513
WFIFOL(fd, offset) = qd->quest_id * 1000 + i;
offset += 4;
#else
WFIFOL(fd, offset) = qi->objectives[i].mob;
WFIFOL(fd, offset) = qi->objectives[i].mob_id;
offset += 4;
#endif
WFIFOW(fd, offset) = qi->objectives[i].count;
WFIFOW(fd, offset) = qi->objectives[i]->count;
offset += 2;
WFIFOW(fd, offset) = qd->count[i];
offset += 2;

View File

@ -2041,15 +2041,15 @@ void intif_parse_questlog(int fd)
}
} else {
struct quest *received = (struct quest *)RFIFOP(fd,8);
int i, k = num_received;
int k = num_received;
if(sd->quest_log)
RECREATE(sd->quest_log, struct quest, num_received);
else
CREATE(sd->quest_log, struct quest, num_received);
for(i = 0; i < num_received; i++) {
if(quest_search(received[i].quest_id) == &quest_dummy) {
for(int i = 0; i < num_received; i++) {
if(!quest_search(received[i].quest_id)) {
ShowError("intif_parse_QuestLog: quest %d not found in DB.\n", received[i].quest_id);
continue;
}

View File

@ -357,7 +357,7 @@
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\mob_skill_db.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\mob_skill_db.txt')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\pet_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\pet_db.yml')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\produce_db.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\produce_db.txt')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\quest_db.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\quest_db.txt')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\quest_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\quest_db.yml')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\refine_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\refine_db.yml')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\size_fix.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\size_fix.yml')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\skill_changematerial_db.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\skill_changematerial_db.txt')" />

View File

@ -12,6 +12,8 @@
#include "../common/showmsg.hpp"
#include "../common/socket.hpp"
#include "../common/strlib.hpp"
#include "../common/utilities.hpp"
#include "../common/utils.hpp"
#include "battle.hpp"
#include "chrif.hpp"
@ -24,20 +26,296 @@
#include "party.hpp"
#include "pc.hpp"
static DBMap *questdb;
static void questdb_free_sub(struct quest_db *quest, bool free);
struct quest_db quest_dummy;
static int split_exact_quest_time(char* modif_p, int* day, int* hour, int* minute, int *second);
const std::string QuestDatabase::getDefaultLocation() {
return std::string(db_path) + "/quest_db.yml";
}
/**
* Reads and parses an entry from the quest_db.
* @param node: YAML node containing the entry.
* @return count of successfully parsed rows
*/
uint64 QuestDatabase::parseBodyNode(const YAML::Node &node) {
uint32 quest_id;
if (!this->asUInt32(node, "Id", quest_id))
return 0;
std::shared_ptr<s_quest_db> quest = this->find(quest_id);
bool exists = quest != nullptr;
if (!exists) {
if (!this->nodesExist(node, { "Title" }))
return 0;
quest = std::make_shared<s_quest_db>();
quest->id = quest_id;
}
if (this->nodeExists(node, "Title")) {
std::string name;
if (!this->asString(node, "Title", name))
return 0;
quest->name = name;
}
if (this->nodeExists(node, "TimeLimit")) {
std::string time;
if (!this->asString(node, "TimeLimit", time))
return 0;
if (time.find("+") != std::string::npos) {
double timediff = solve_time(const_cast<char *>(time.c_str()));
if (timediff <= 0) {
this->invalidWarning(node["TimeLimit"], "Incorrect TimeLimit format %s given, skipping.\n", time.c_str());
return 0;
}
quest->time = static_cast<time_t>(timediff);
}
else {// '+' not found, set to specific time
int32 day, hour, minute, second;
if (split_exact_quest_time(const_cast<char *>(time.c_str()), &day, &hour, &minute, &second) == 0) {
this->invalidWarning(node["TimeLimit"], "Incorrect TimeLimit format %s given, skipping.\n", time.c_str());
return 0;
}
quest->time = day * 86400 + hour * 3600 + minute * 60 + second;
quest->time_at = true;
}
} else {
if (!exists) {
quest->time = 0;
quest->time_at = false;
}
}
if (this->nodeExists(node, "Targets")) {
const YAML::Node &targets = node["Targets"];
for (const YAML::Node &targetNode : targets) {
if (quest->objectives.size() >= MAX_QUEST_OBJECTIVES) {
this->invalidWarning(targetNode, "Targets list exceeds the maximum of %d, skipping.\n", MAX_QUEST_OBJECTIVES);
return 0;
}
if (!this->nodeExists(targetNode, "Mob"))
continue;
std::string mob_name;
if (!this->asString(targetNode, "Mob", mob_name))
return 0;
struct mob_db *mob = mobdb_search_aegisname(mob_name.c_str());
if (!mob) {
this->invalidWarning(targetNode["Mob"], "Mob %s does not exist, skipping.\n", mob_name.c_str());
continue;
}
//std::shared_ptr<s_quest_objective> target = util::vector_find(quest->objectives, mob->vd.class_);
std::shared_ptr<s_quest_objective> target;
std::vector<std::shared_ptr<s_quest_objective>>::iterator it = std::find_if(quest->objectives.begin(), quest->objectives.end(), [&](std::shared_ptr<s_quest_objective> const &v) {
return (*v).mob_id == mob->vd.class_;
});
if (it != quest->objectives.end())
target = (*it);
else
target = nullptr;
bool targetExists = target != nullptr;
if (!targetExists) {
if (!this->nodeExists(targetNode, "Count")) {
this->invalidWarning(targetNode["Count"], "Targets has no Count value specified, skipping.\n");
continue;
}
target = std::make_shared<s_quest_objective>();
target->mob_id = mob->vd.class_;
}
if (this->nodeExists(targetNode, "Count")) {
uint16 count;
if (!this->asUInt16(targetNode, "Count", count))
return 0;
target->count = count;
}
quest->objectives.push_back(target);
}
}
if (this->nodeExists(node, "Drops")) {
const YAML::Node &drops = node["Drops"];
for (const YAML::Node &dropNode : drops) {
if (quest->objectives.size() >= MAX_QUEST_OBJECTIVES) {
this->invalidWarning(dropNode, "Drops list exceeds the maximum of %d, skipping.\n", MAX_QUEST_OBJECTIVES);
return 0;
}
uint32 mob_id = 0; // Can be 0 which means all monsters
if (this->nodeExists(dropNode, "Mob")) {
std::string mob_name;
if (!this->asString(dropNode, "Mob", mob_name))
return 0;
struct mob_db *mob = mobdb_search_aegisname(mob_name.c_str());
if (!mob) {
this->invalidWarning(dropNode["Mob"], "Mob %s does not exist, skipping.\n", mob_name.c_str());
continue;
}
mob_id = mob->vd.class_;
}
//std::shared_ptr<s_quest_dropitem> target = util::vector_find(quest->dropitem, mob_id);
std::shared_ptr<s_quest_dropitem> target;
std::vector<std::shared_ptr<s_quest_dropitem>>::iterator it = std::find_if(quest->dropitem.begin(), quest->dropitem.end(), [&](std::shared_ptr<s_quest_dropitem> const &v) {
return (*v).mob_id == mob_id;
});
if (it != quest->dropitem.end())
target = (*it);
else
target = nullptr;
bool targetExists = target != nullptr;
if (!targetExists) {
if (!this->nodeExists(dropNode, "Item")) {
this->invalidWarning(dropNode["Item"], "Drops has no Item value specified, skipping.\n");
continue;
}
if (!this->nodeExists(dropNode, "Rate")) {
this->invalidWarning(dropNode["Item"], "Drops has no Rate value specified, skipping.\n");
continue;
}
target = std::make_shared<s_quest_dropitem>();
target->mob_id = mob_id;
}
if (this->nodeExists(dropNode, "Item")) {
std::string item_name;
if (!this->asString(dropNode, "Item", item_name))
return 0;
struct item_data *item = itemdb_search_aegisname(item_name.c_str());
if (!item) {
this->invalidWarning(dropNode["Item"], "Item %s does not exist, skipping.\n", item_name.c_str());
continue;
}
target->nameid = item->nameid;
}
if (this->nodeExists(dropNode, "Count")) {
uint16 count;
if (!this->asUInt16(dropNode, "Count", count))
return 0;
if (!itemdb_isstackable(target->nameid)) {
this->invalidWarning(dropNode["Count"], "Item %s is not stackable, capping to 1.\n", itemdb_name(target->nameid));
count = 1;
}
target->count = count;
} else {
if (!targetExists)
target->count = 1;
}
if (this->nodeExists(dropNode, "Rate")) {
uint16 rate;
if (!this->asUInt16(dropNode, "Rate", rate))
return 0;
target->rate = rate;
}
quest->dropitem.push_back(target);
}
}
if (!exists)
this->put(quest_id, quest);
return 1;
}
static int split_exact_quest_time(char* modif_p, int* day, int* hour, int* minute, int *second) {
int d = -1, h = -1, mn = -1, s = -1;
nullpo_retr(0, modif_p);
while (modif_p[0] != '\0') {
int value = atoi(modif_p);
if (modif_p[0] == '-' || modif_p[0] == '+')
modif_p++;
while (modif_p[0] >= '0' && modif_p[0] <= '9')
modif_p++;
if (modif_p[0] == 's') {
s = value;
modif_p++;
} else if (modif_p[0] == 'm' && modif_p[1] == 'n') {
mn = value;
modif_p = modif_p + 2;
} else if (modif_p[0] == 'h') {
h = value;
modif_p++;
} else if (modif_p[0] == 'd' || modif_p[0] == 'j') {
d = value;
modif_p++;
} else if (modif_p[0] != '\0') {
modif_p++;
}
}
if (h < 0 || h > 23 || mn > 59 || s > 59) // hour is required
return 0;
*day = max(0,d);
*hour = h;
*minute = max(0,mn);
*second = max(0,s);
return 1;
}
/**
* Searches a quest by ID.
* @param quest_id : ID to lookup
* @return Quest entry (equals to &quest_dummy if the ID is invalid)
* @return Quest entry or nullptr on failure
*/
struct quest_db *quest_search(int quest_id)
std::shared_ptr<s_quest_db> quest_search(int quest_id)
{
struct quest_db *quest = (struct quest_db *)idb_get(questdb, quest_id);
auto quest = quest_db.find(quest_id);
if (!quest)
return &quest_dummy;
return nullptr;
return quest;
}
@ -46,13 +324,9 @@ struct quest_db *quest_search(int quest_id)
* @param sd : Player's data
* @return 0 in case of success, nonzero otherwise (i.e. the player has no quests)
*/
int quest_pc_login(TBL_PC *sd)
int quest_pc_login(struct map_session_data *sd)
{
#if PACKETVER < 20141022
int i;
#endif
if( sd->avail_quests == 0 )
if (!sd->avail_quests)
return 1;
clif_quest_send_list(sd);
@ -61,13 +335,39 @@ int quest_pc_login(TBL_PC *sd)
clif_quest_send_mission(sd);
//@TODO[Haru]: Is this necessary? Does quest_send_mission not take care of this?
for( i = 0; i < sd->avail_quests; i++ )
for (int i = 0; i < sd->avail_quests; i++)
clif_quest_update_objective(sd, &sd->quest_log[i], 0);
#endif
return 0;
}
/**
* Determine a quest's time limit.
* @param qi: Quest data
* @return Time limit value
*/
static time_t quest_time(std::shared_ptr<s_quest_db> qi)
{
if (!qi || qi->time < 0)
return 0;
if (!qi->time_at && qi->time > 0)
return time(nullptr) + qi->time;
else if (qi->time_at) {
time_t t = time(nullptr);
struct tm *lt = localtime(&t);
uint32 time_today = lt->tm_hour * 3600 + lt->tm_min * 60 + lt->tm_sec;
if (time_today < (qi->time % 86400))
return static_cast<time_t>(t + qi->time - time_today);
else // Carry over to the next day
return static_cast<time_t>(t + 86400 + qi->time - time_today);
}
return 0;
}
/**
* Adds a quest to the player's list.
* New quest will be added as Q_ACTIVE.
@ -75,53 +375,34 @@ int quest_pc_login(TBL_PC *sd)
* @param quest_id : ID of the quest to add.
* @return 0 in case of success, nonzero otherwise
*/
int quest_add(TBL_PC *sd, int quest_id)
int quest_add(struct map_session_data *sd, int quest_id)
{
int n;
struct quest_db *qi = quest_search(quest_id);
std::shared_ptr<s_quest_db> qi = quest_search(quest_id);
if( qi == &quest_dummy ) {
if (!qi) {
ShowError("quest_add: quest %d not found in DB.\n", quest_id);
return -1;
}
if( quest_check(sd, quest_id, HAVEQUEST) >= 0 ) {
if (quest_check(sd, quest_id, HAVEQUEST) >= 0) {
ShowError("quest_add: Character %d already has quest %d.\n", sd->status.char_id, quest_id);
return -1;
}
n = sd->avail_quests; //Insertion point
int n = sd->avail_quests; //Insertion point
sd->num_quests++;
sd->avail_quests++;
RECREATE(sd->quest_log, struct quest, sd->num_quests);
//The character has some completed quests, make room before them so that they will stay at the end of the array
if( sd->avail_quests != sd->num_quests )
if (sd->avail_quests != sd->num_quests)
memmove(&sd->quest_log[n + 1], &sd->quest_log[n], sizeof(struct quest) * (sd->num_quests-sd->avail_quests));
memset(&sd->quest_log[n], 0, sizeof(struct quest));
sd->quest_log[n] = {};
sd->quest_log[n].quest_id = qi->id;
if (qi->time) {
if (qi->time_type == 0)
sd->quest_log[n].time = (unsigned int)(time(NULL) + qi->time);
else { // quest time limit at HH:MM
int time_today;
time_t t;
struct tm * lt;
t = time(NULL);
lt = localtime(&t);
time_today = (lt->tm_hour) * 3600 + (lt->tm_min) * 60 + (lt->tm_sec);
if (time_today < qi->time)
sd->quest_log[n].time = (unsigned int)(time(NULL) + qi->time - time_today);
else // next day
sd->quest_log[n].time = (unsigned int)(time(NULL) + 86400 + qi->time - time_today);
}
}
sd->quest_log[n].time = (uint32)quest_time(qi);
sd->quest_log[n].state = Q_ACTIVE;
sd->save_quest = true;
clif_quest_add(sd, &sd->quest_log[n]);
@ -140,55 +421,37 @@ int quest_add(TBL_PC *sd, int quest_id)
* @param qid2 : New quest to add
* @return 0 in case of success, nonzero otherwise
*/
int quest_change(TBL_PC *sd, int qid1, int qid2)
int quest_change(struct map_session_data *sd, int qid1, int qid2)
{
int i;
struct quest_db *qi = quest_search(qid2);
std::shared_ptr<s_quest_db> qi = quest_search(qid2);
if( qi == &quest_dummy ) {
if (!qi) {
ShowError("quest_change: quest %d not found in DB.\n", qid2);
return -1;
}
if( quest_check(sd, qid2, HAVEQUEST) >= 0 ) {
if (quest_check(sd, qid2, HAVEQUEST) >= 0) {
ShowError("quest_change: Character %d already has quest %d.\n", sd->status.char_id, qid2);
return -1;
}
if( quest_check(sd, qid1, HAVEQUEST) < 0 ) {
if (quest_check(sd, qid1, HAVEQUEST) < 0) {
ShowError("quest_change: Character %d doesn't have quest %d.\n", sd->status.char_id, qid1);
return -1;
}
int i;
ARR_FIND(0, sd->avail_quests, i, sd->quest_log[i].quest_id == qid1);
if( i == sd->avail_quests ) {
if (i == sd->avail_quests) {
ShowError("quest_change: Character %d has completed quest %d.\n", sd->status.char_id, qid1);
return -1;
}
memset(&sd->quest_log[i], 0, sizeof(struct quest));
sd->quest_log[i] = {};
sd->quest_log[i].quest_id = qi->id;
if (qi->time) {
if (qi->time_type == 0)
sd->quest_log[i].time = (unsigned int)(time(NULL) + qi->time);
else { // quest time limit at HH:MM
int time_today;
time_t t;
struct tm * lt;
t = time(NULL);
lt = localtime(&t);
time_today = (lt->tm_hour) * 3600 + (lt->tm_min) * 60 + (lt->tm_sec);
if (time_today < qi->time)
sd->quest_log[i].time = (unsigned int)(time(NULL) + qi->time - time_today);
else // next day
sd->quest_log[i].time = (unsigned int)(time(NULL) + 86400 + qi->time - time_today);
}
}
sd->quest_log[i].time = (uint32)quest_time(qi);
sd->quest_log[i].state = Q_ACTIVE;
sd->save_quest = true;
clif_quest_delete(sd, qid1);
@ -207,24 +470,24 @@ int quest_change(TBL_PC *sd, int qid1, int qid2)
* @param quest_id : ID of the quest to remove
* @return 0 in case of success, nonzero otherwise
*/
int quest_delete(TBL_PC *sd, int quest_id)
int quest_delete(struct map_session_data *sd, int quest_id)
{
int i;
//Search for quest
ARR_FIND(0, sd->num_quests, i, sd->quest_log[i].quest_id == quest_id);
if( i == sd->num_quests ) {
if (i == sd->num_quests) {
ShowError("quest_delete: Character %d doesn't have quest %d.\n", sd->status.char_id, quest_id);
return -1;
}
if( sd->quest_log[i].state != Q_COMPLETE )
if (sd->quest_log[i].state != Q_COMPLETE)
sd->avail_quests--;
if( i < --sd->num_quests ) //Compact the array
if (i < --sd->num_quests) //Compact the array
memmove(&sd->quest_log[i], &sd->quest_log[i + 1], sizeof(struct quest) * (sd->num_quests - i));
if( sd->num_quests == 0 ) {
if (sd->num_quests == 0) {
aFree(sd->quest_log);
sd->quest_log = NULL;
} else
@ -273,53 +536,49 @@ int quest_update_objective_sub(struct block_list *bl, va_list ap)
* @param sd : Character's data
* @param mob_id : Monster ID
*/
void quest_update_objective(TBL_PC *sd, int mob_id)
void quest_update_objective(struct map_session_data *sd, int mob_id)
{
int i, j;
for( i = 0; i < sd->avail_quests; i++ ) {
struct quest_db *qi = NULL;
if( sd->quest_log[i].state == Q_COMPLETE ) // Skip complete quests
for (int i = 0; i < sd->avail_quests; i++) {
if (sd->quest_log[i].state == Q_COMPLETE) // Skip complete quests
continue;
qi = quest_search(sd->quest_log[i].quest_id);
std::shared_ptr<s_quest_db> qi = quest_search(sd->quest_log[i].quest_id);
for( j = 0; j < qi->objectives_count; j++ ) {
if( qi->objectives[j].mob == mob_id && sd->quest_log[i].count[j] < qi->objectives[j].count ) {
// Process quest objectives
for (int j = 0; j < qi->objectives.size(); j++) {
if (qi->objectives[j]->mob_id == mob_id && sd->quest_log[i].count[j] < qi->objectives[j]->count) {
sd->quest_log[i].count[j]++;
sd->save_quest = true;
clif_quest_update_objective(sd, &sd->quest_log[i], mob_id);
}
}
// process quest-granted extra drop bonuses
for (j = 0; j < qi->dropitem_count; j++) {
struct quest_dropitem *dropitem = &qi->dropitem[j];
struct item item;
int temp;
if (dropitem->mob_id != 0 && dropitem->mob_id != mob_id)
// Process quest-granted extra drop bonuses
for (const auto &it : qi->dropitem) {
if (it->mob_id != 0 && it->mob_id != mob_id)
continue;
// TODO: Should this be affected by server rates?
if (dropitem->rate < 10000 && rnd()%10000 >= dropitem->rate)
continue;
if (!itemdb_exists(dropitem->nameid))
if (it->rate < 10000 && rnd()%10000 >= it->rate)
continue; // TODO: Should this be affected by server rates?
if (!itemdb_exists(it->nameid))
continue;
memset(&item,0,sizeof(item));
item.nameid = dropitem->nameid;
item.identify = itemdb_isidentified(dropitem->nameid);
item.amount = dropitem->count;
struct item item = {};
item.nameid = it->nameid;
item.identify = itemdb_isidentified(it->nameid);
item.amount = it->count;
//#ifdef BOUND_ITEMS
// item.bound = dropitem->bound;
// item.bound = it.bound;
//#endif
// if (dropitem->isGUID)
// if (it.isGUID)
// item.unique_id = pc_generate_unique_id(sd);
if ((temp = pc_additem(sd, &item, 1, LOG_TYPE_QUEST)) != 0) // Failed to obtain the item
char temp;
if ((temp = pc_additem(sd, &item, 1, LOG_TYPE_QUEST)) != ADDITEM_SUCCESS) // Failed to obtain the item
clif_additem(sd, 0, 0, temp);
// else if (dropitem->isAnnounced || itemdb_exists(dropitem->nameid)->flag.broadcast)
// intif_broadcast_obtain_special_item(sd, dropitem->nameid, dropitem->mob_id, ITEMOBTAIN_TYPE_MONSTER_ITEM);
// else if (it.isAnnounced || itemdb_exists(it.nameid)->flag.broadcast)
// intif_broadcast_obtain_special_item(sd, it.nameid, it.mob_id, ITEMOBTAIN_TYPE_MONSTER_ITEM);
}
}
pc_show_questinfo(sd);
@ -334,12 +593,12 @@ void quest_update_objective(TBL_PC *sd, int mob_id)
* @return 0 in case of success, nonzero otherwise
* @author [Inkfish]
*/
int quest_update_status(TBL_PC *sd, int quest_id, enum quest_state status)
int quest_update_status(struct map_session_data *sd, int quest_id, e_quest_state status)
{
int i;
ARR_FIND(0, sd->avail_quests, i, sd->quest_log[i].quest_id == quest_id);
if( i == sd->avail_quests ) {
if (i == sd->avail_quests) {
ShowError("quest_update_status: Character %d doesn't have quest %d.\n", sd->status.char_id, quest_id);
return -1;
}
@ -347,13 +606,13 @@ int quest_update_status(TBL_PC *sd, int quest_id, enum quest_state status)
sd->quest_log[i].state = status;
sd->save_quest = true;
if( status < Q_COMPLETE ) {
if (status < Q_COMPLETE) {
clif_quest_update_status(sd, quest_id, status == Q_ACTIVE ? true : false);
return 0;
}
// The quest is complete, so it needs to be moved to the completed quests block at the end of the array.
if( i < (--sd->avail_quests) ) {
if (i < (--sd->avail_quests)) {
struct quest tmp_quest;
memcpy(&tmp_quest, &sd->quest_log[i], sizeof(struct quest));
@ -363,7 +622,7 @@ int quest_update_status(TBL_PC *sd, int quest_id, enum quest_state status)
clif_quest_delete(sd, quest_id);
if( save_settings&CHARSAVE_QUEST )
if (save_settings&CHARSAVE_QUEST)
chrif_save(sd, CSAVE_NORMAL);
return 0;
@ -383,30 +642,30 @@ int quest_update_status(TBL_PC *sd, int quest_id, enum quest_state status)
* 1 if the quest's timeout has expired
* 0 otherwise
*/
int quest_check(TBL_PC *sd, int quest_id, enum quest_check_type type)
int quest_check(struct map_session_data *sd, int quest_id, e_quest_check_type type)
{
int i;
ARR_FIND(0, sd->num_quests, i, sd->quest_log[i].quest_id == quest_id);
if( i == sd->num_quests )
if (i == sd->num_quests)
return -1;
switch( type ) {
switch (type) {
case HAVEQUEST:
if (sd->quest_log[i].state == Q_INACTIVE) // Player has the quest but it's in the inactive state; send it as Q_ACTIVE.
return 1;
return sd->quest_log[i].state;
case PLAYTIME:
return (sd->quest_log[i].time < (unsigned int)time(NULL) ? 2 : sd->quest_log[i].state == Q_COMPLETE ? 1 : 0);
return (sd->quest_log[i].time < (unsigned int)time(nullptr) ? 2 : sd->quest_log[i].state == Q_COMPLETE ? 1 : 0);
case HUNTING:
if( sd->quest_log[i].state == Q_INACTIVE || sd->quest_log[i].state == Q_ACTIVE ) {
if (sd->quest_log[i].state == Q_INACTIVE || sd->quest_log[i].state == Q_ACTIVE) {
int j;
struct quest_db *qi = quest_search(sd->quest_log[i].quest_id);
std::shared_ptr<s_quest_db> qi = quest_search(sd->quest_log[i].quest_id);
ARR_FIND(0, qi->objectives_count, j, sd->quest_log[i].count[j] < qi->objectives[j].count);
if( j == qi->objectives_count )
ARR_FIND(0, qi->objectives.size(), j, sd->quest_log[i].count[j] < qi->objectives[j]->count);
if (j == qi->objectives.size())
return 2;
if( sd->quest_log[i].time < (unsigned int)time(NULL) )
if (sd->quest_log[i].time < (unsigned int)time(nullptr))
return 1;
}
return 0;
@ -418,153 +677,6 @@ int quest_check(TBL_PC *sd, int quest_id, enum quest_check_type type)
return -1;
}
/**
* Loads quests from the quest db.txt
* @return Number of loaded quests, or -1 if the file couldn't be read.
*/
void quest_read_txtdb(void)
{
const char* dbsubpath[] = {
DBPATH,
DBIMPORT"/",
};
uint8 f;
for (f = 0; f < ARRAYLENGTH(dbsubpath); f++) {
FILE *fp;
char line[1024];
uint32 ln = 0, count = 0;
char filename[256];
sprintf(filename, "%s/%s%s", db_path, dbsubpath[f], "quest_db.txt");
if ((fp = fopen(filename, "r")) == NULL) {
if (f == 0)
ShowError("Can't read %s\n", filename);
return;
}
while(fgets(line, sizeof(line), fp)) {
struct quest_db *quest = NULL;
char *str[19], *p;
int quest_id = 0;
uint8 i;
++ln;
if (line[0] == '\0' || (line[0] == '/' && line[1] == '/'))
continue;
p = trim(line);
if (*p == '\0')
continue; // empty line
memset(str, 0, sizeof(str));
for(i = 0, p = line; i < 18 && p; i++) {
str[i] = p;
p = strchr(p,',');
if (p)
*p++ = 0;
}
if (str[0] == NULL)
continue;
if (i < 18) {
ShowError("quest_read_txtdb: Insufficient columns in '%s' line %d (%d of %d)\n", filename, ln, i, 18);
continue;
}
quest_id = atoi(str[0]);
if (quest_id < 0 || quest_id >= INT_MAX) {
ShowError("quest_read_txtdb: Invalid quest ID '%d' in '%s' line '%d' (min: 0, max: %d.)\n", quest_id, filename, ln, INT_MAX);
continue;
}
if (!(quest = (struct quest_db *)idb_get(questdb, quest_id)))
CREATE(quest, struct quest_db, 1);
else {
if (quest->objectives) {
aFree(quest->objectives);
quest->objectives = NULL;
quest->objectives_count = 0;
}
if (quest->dropitem) {
aFree(quest->dropitem);
quest->dropitem = NULL;
quest->dropitem_count = 0;
}
}
if (strchr(str[1],':') == NULL) {
quest->time = atoi(str[1]);
quest->time_type = 0;
}
else {
unsigned char hour, min;
hour = atoi(str[1]);
str[1] = strchr(str[1],':');
*str[1] ++= 0;
min = atoi(str[1]);
quest->time = hour * 3600 + min * 60;
quest->time_type = 1;
}
for(i = 0; i < MAX_QUEST_OBJECTIVES; i++) {
uint16 mob_id = (uint16)atoi(str[2 * i + 2]);
if (!mob_id)
continue;
if (mob_db(mob_id) == NULL) {
ShowWarning("quest_read_txtdb: Invalid monster as objective '%d' in line %d.\n", mob_id, ln);
continue;
}
RECREATE(quest->objectives, struct quest_objective, quest->objectives_count+1);
quest->objectives[quest->objectives_count].mob = mob_id;
quest->objectives[quest->objectives_count].count = (uint16)atoi(str[2 * i + 3]);
quest->objectives_count++;
}
for(i = 0; i < MAX_QUEST_DROPS; i++) {
uint16 mob_id = (uint16)atoi(str[3 * i + (2 * MAX_QUEST_OBJECTIVES + 2)]), nameid = (uint16)atoi(str[3 * i + (2 * MAX_QUEST_OBJECTIVES + 3)]);
if (!nameid)
continue;
if (!itemdb_exists(nameid) || (mob_id && mob_db(mob_id) == NULL)) {
ShowWarning("quest_read_txtdb: Invalid item reward '%d' (mob %d, optional) in line %d.\n", nameid, mob_id, ln);
continue;
}
RECREATE(quest->dropitem, struct quest_dropitem, quest->dropitem_count+1);
quest->dropitem[quest->dropitem_count].mob_id = mob_id;
quest->dropitem[quest->dropitem_count].nameid = nameid;
quest->dropitem[quest->dropitem_count].count = 1;
quest->dropitem[quest->dropitem_count].rate = atoi(str[3 * i + (2 * MAX_QUEST_OBJECTIVES + 4)]);
quest->dropitem_count++;
}
//StringBuf_Init(&entry.name);
//StringBuf_Printf(&entry.name, "%s", str[17]);
if (!quest->id) {
quest->id = quest_id;
idb_put(questdb, quest->id, quest);
}
count++;
}
fclose(fp);
ShowStatus("Done reading '" CL_WHITE "%d" CL_RESET "' entries in '" CL_WHITE "%s" CL_RESET "'.\n", count, filename);
}
}
/**
* Loads Quest DB
*/
static void quest_read_db(void)
{
quest_read_txtdb();
}
/**
* Map iterator to ensures a player has no invalid quest log entries.
* Any entries that are no longer in the db are removed.
@ -572,23 +684,22 @@ static void quest_read_db(void)
* @param sd : Character's data
* @param ap : Ignored
*/
int quest_reload_check_sub(struct map_session_data *sd, va_list ap)
static int quest_reload_check_sub(struct map_session_data *sd, va_list ap)
{
int i, j;
nullpo_ret(sd);
j = 0;
for( i = 0; i < sd->num_quests; i++ ) {
struct quest_db *qi = quest_search(sd->quest_log[i].quest_id);
int i, j = 0;
if( qi == &quest_dummy ) { //Remove no longer existing entries
if( sd->quest_log[i].state != Q_COMPLETE ) //And inform the client if necessary
for (i = 0; i < sd->num_quests; i++) {
std::shared_ptr<s_quest_db> qi = quest_search(sd->quest_log[i].quest_id);
if (!qi) { //Remove no longer existing entries
if (sd->quest_log[i].state != Q_COMPLETE) //And inform the client if necessary
clif_quest_delete(sd, sd->quest_log[i].quest_id);
continue;
}
if( i != j ) {
if (i != j) {
//Move entries if there's a gap to fill
memcpy(&sd->quest_log[j], &sd->quest_log[i], sizeof(struct quest));
}
@ -603,50 +714,23 @@ int quest_reload_check_sub(struct map_session_data *sd, va_list ap)
return 1;
}
/**
* Clear quest single entry
* @param quest
* @param free Will free quest from memory
**/
static void questdb_free_sub(struct quest_db *quest, bool free)
{
if (quest->objectives) {
aFree(quest->objectives);
quest->objectives = NULL;
quest->objectives_count = 0;
}
if (quest->dropitem) {
aFree(quest->dropitem);
quest->dropitem = NULL;
quest->dropitem_count = 0;
}
if (&quest->name)
StringBuf_Destroy(&quest->name);
if (free)
aFree(quest);
bool QuestDatabase::reload() {
if (!TypesafeYamlDatabase::reload())
return false;
// Update quest data for players, to ensure no entries about removed quests are left over.
map_foreachpc(&quest_reload_check_sub);
return true;
}
/**
* Clears the quest database for shutdown or reload.
*/
static int questdb_free(DBKey key, DBData *data, va_list ap)
{
struct quest_db *quest = (struct quest_db *)db_data2ptr(data);
if (!quest)
return 0;
questdb_free_sub(quest, true);
return 1;
}
QuestDatabase quest_db;
/**
* Initializes the quest interface.
*/
void do_init_quest(void)
{
questdb = idb_alloc(DB_OPT_BASE);
quest_read_db();
quest_db.load();
}
/**
@ -654,20 +738,4 @@ void do_init_quest(void)
*/
void do_final_quest(void)
{
memset(&quest_dummy, 0, sizeof(quest_dummy));
questdb->destroy(questdb, questdb_free);
}
/**
* Reloads the quest database.
*/
void do_reload_quest(void)
{
memset(&quest_dummy, 0, sizeof(quest_dummy));
questdb->clear(questdb, questdb_free);
quest_read_db();
//Update quest data for players, to ensure no entries about removed quests are left over.
map_foreachpc(&quest_reload_check_sub);
}

View File

@ -4,14 +4,17 @@
#ifndef QUEST_HPP
#define QUEST_HPP
#include <string>
#include "../common/cbasetypes.hpp"
#include "../common/database.hpp"
#include "../common/strlib.hpp"
#include "map.hpp"
struct map_session_data;
struct quest_dropitem {
struct s_quest_dropitem {
uint16 nameid;
uint16 count;
uint16 rate;
@ -21,46 +24,53 @@ struct quest_dropitem {
//bool isGUID;
};
struct quest_objective {
uint16 mob;
struct s_quest_objective {
uint16 mob_id;
uint16 count;
};
struct quest_db {
// TODO: find out if signed or unsigned in client
int id;
unsigned int time;
bool time_type;
uint8 objectives_count;
struct quest_objective *objectives;
uint8 dropitem_count;
struct quest_dropitem *dropitem;
StringBuf name;
struct s_quest_db {
int32 id;
time_t time;
bool time_at;
std::vector<std::shared_ptr<s_quest_objective>> objectives;
std::vector<std::shared_ptr<s_quest_dropitem>> dropitem;
std::string name;
};
extern struct quest_db quest_dummy; ///< Dummy entry for invalid quest lookups
// Questlog check types
enum quest_check_type {
enum e_quest_check_type : uint8 {
HAVEQUEST, ///< Query the state of the given quest
PLAYTIME, ///< Check if the given quest has been completed or has yet to expire
HUNTING, ///< Check if the given hunting quest's requirements have been met
};
int quest_pc_login(TBL_PC *sd);
class QuestDatabase : public TypesafeYamlDatabase<uint32, s_quest_db> {
public:
QuestDatabase() : TypesafeYamlDatabase("QUEST_DB", 1) {
int quest_add(TBL_PC * sd, int quest_id);
int quest_delete(TBL_PC * sd, int quest_id);
int quest_change(TBL_PC * sd, int qid1, int qid2);
}
const std::string getDefaultLocation();
uint64 parseBodyNode(const YAML::Node& node);
bool reload();
};
extern QuestDatabase quest_db;
int quest_pc_login(struct map_session_data *sd);
int quest_add(struct map_session_data *sd, int quest_id);
int quest_delete(struct map_session_data *sd, int quest_id);
int quest_change(struct map_session_data *sd, int qid1, int qid2);
int quest_update_objective_sub(struct block_list *bl, va_list ap);
void quest_update_objective(TBL_PC * sd, int mob_id);
int quest_update_status(TBL_PC * sd, int quest_id, enum quest_state status);
int quest_check(TBL_PC * sd, int quest_id, enum quest_check_type type);
void quest_update_objective(struct map_session_data *sd, int mob_id);
int quest_update_status(struct map_session_data *sd, int quest_id, e_quest_state status);
int quest_check(struct map_session_data *sd, int quest_id, e_quest_check_type type);
struct quest_db *quest_search(int quest_id);
std::shared_ptr<s_quest_db> quest_search(int quest_id);
void do_init_quest(void);
void do_final_quest(void);
void do_reload_quest(void);
#endif /* QUEST_HPP */

View File

@ -19587,10 +19587,10 @@ BUILDIN_FUNC(changequest)
BUILDIN_FUNC(checkquest)
{
struct map_session_data *sd;
enum quest_check_type type = HAVEQUEST;
e_quest_check_type type = HAVEQUEST;
if( script_hasdata(st, 3) )
type = (enum quest_check_type)script_getnum(st, 3);
type = (e_quest_check_type)script_getnum(st, 3);
if (!script_charid2sd(4,sd))
return SCRIPT_CMD_FAILURE;
@ -19606,12 +19606,11 @@ BUILDIN_FUNC(checkquest)
BUILDIN_FUNC(isbegin_quest)
{
struct map_session_data *sd;
int i;
if (!script_charid2sd(3,sd))
return SCRIPT_CMD_FAILURE;
i = quest_check(sd, script_getnum(st, 2), (enum quest_check_type) HAVEQUEST);
int i = quest_check(sd, script_getnum(st, 2), HAVEQUEST);
script_pushint(st, i + (i < 1));
return SCRIPT_CMD_SUCCESS;

View File

@ -70,6 +70,7 @@ int getch( void ){
#define MAX_SKILL_ITEM_REQUIRE 10
#define MAX_SKILL_STATUS_REQUIRE 3
#define MAX_SKILL_EQUIP_REQUIRE 10
#define MAX_QUEST_DROPS 3
struct s_skill_unit_csv : s_skill_db {
std::string target_str;
@ -98,6 +99,7 @@ static bool skill_parse_row_unitdb(char* split[], int columns, int current);
static bool skill_parse_row_copyabledb(char* split[], int columns, int current);
static bool skill_parse_row_nonearnpcrangedb(char* split[], int columns, int current);
static bool skill_parse_row_skilldb(char* split[], int columns, int current);
static bool quest_read_db(char *split[], int columns, int current);
// Constants for conversion
std::unordered_map<uint16, std::string> aegis_itemnames;
@ -352,6 +354,12 @@ int do_init( int argc, char** argv ){
return 0;
}
if (!process("QUEST_DB", 1, root_paths, "quest_db", [](const std::string &path, const std::string &name_ext) -> bool {
return sv_readdb(path.c_str(), name_ext.c_str(), ',', 3 + MAX_QUEST_OBJECTIVES * 2 + MAX_QUEST_DROPS * 3, 100, -1, &quest_read_db, false);
})) {
return 0;
}
// TODO: add implementations ;-)
return 0;
@ -2390,3 +2398,138 @@ static bool skill_parse_row_skilldb(char* split[], int columns, int current) {
return true;
}
// Copied and adjusted from quest.cpp
static bool quest_read_db(char *split[], int columns, int current) {
int quest_id = atoi(split[0]);
if (quest_id < 0 || quest_id >= INT_MAX) {
ShowError("quest_read_db: Invalid quest ID '%d'.\n", quest_id);
return false;
}
body << YAML::BeginMap;
body << YAML::Key << "Id" << YAML::Value << quest_id;
std::string title = split[17];
if (columns > 18) { // If the title has a comma in it, concatenate
int col = 18;
while (col < columns) {
title += ',' + std::string(split[col]);
col++;
}
}
title.erase(std::remove(title.begin(), title.end(), '"'), title.end()); // Strip double quotes out
body << YAML::Key << "Title" << YAML::Value << title;
if (strchr(split[1], ':') == NULL) {
uint32 time = atoi(split[1]);
if (time > 0) {
int day = time / 86400;
time %= (24 * 3600);
int hour = time / 3600;
time %= 3600;
int minute = time / 60;
time %= 60;
int second = time;
std::string output = "+";
if (day > 0)
output += std::to_string(day) + "d";
if (hour > 0)
output += std::to_string(hour) + "h";
if (minute > 0)
output += std::to_string(minute) + "mn";
if (second > 0)
output += std::to_string(second) + "s";
body << YAML::Key << "TimeLimit" << YAML::Value << output;
}
} else {
if (*split[1]) {
std::string time_str(split[1]), hour = time_str.substr(0, time_str.find(':')), output = {};
time_str.erase(0, 3); // Remove "HH:"
if (std::stoi(hour) > 0)
output = std::to_string(std::stoi(hour)) + "h";
if (std::stoi(time_str) > 0)
output += std::to_string(std::stoi(time_str)) + "mn";
body << YAML::Key << "TimeLimit" << YAML::Value << output; // No quests in TXT format had days, default to 0
}
}
if (atoi(split[2]) > 0) {
body << YAML::Key << "Targets";
body << YAML::BeginSeq;
for (size_t i = 0; i < MAX_QUEST_OBJECTIVES; i++) {
int32 mob_id = (int32)atoi(split[i * 2 + 2]), count = atoi(split[i * 2 + 3]);
if (!mob_id || !count)
continue;
std::string *mob_name = util::umap_find(aegis_mobnames, static_cast<uint16>(mob_id));
if (!mob_name) {
ShowError("quest_read_db: Invalid mob-class %hu, target not read.\n", mob_id);
continue;
}
body << YAML::BeginMap;
body << YAML::Key << "Mob" << YAML::Value << *mob_name;
body << YAML::Key << "Count" << YAML::Value << count;
body << YAML::EndMap;
}
body << YAML::EndSeq;
}
if (atoi(split[2 * MAX_QUEST_OBJECTIVES + 2]) > 0) {
body << YAML::Key << "Drops";
body << YAML::BeginSeq;
for (size_t i = 0; i < MAX_QUEST_DROPS; i++) {
int32 mob_id = (int32)atoi(split[3 * i + (2 * MAX_QUEST_OBJECTIVES + 2)]), nameid = (uint16)atoi(split[3 * i + (2 * MAX_QUEST_OBJECTIVES + 3)]);
if (!mob_id || !nameid)
continue;
std::string *mob_name = util::umap_find(aegis_mobnames, static_cast<uint16>(mob_id));
if (!mob_name) {
ShowError("quest_read_db: Invalid mob-class %hu, drop not read.\n", mob_id);
continue;
}
std::string *item_name = util::umap_find(aegis_itemnames, static_cast<uint16>(nameid));
if (!item_name) {
ShowError("quest_read_db: Invalid item name %hu, drop not read.\n", nameid);
return false;
}
body << YAML::BeginMap;
body << YAML::Key << "Mob" << YAML::Value << *mob_name;
body << YAML::Key << "Item" << YAML::Value << *item_name;
//body << YAML::Key << "Count" << YAML::Value << 1; // Default is 1
body << YAML::Key << "Rate" << YAML::Value << atoi(split[3 * i + (2 * MAX_QUEST_OBJECTIVES + 4)]);
body << YAML::EndMap;
}
body << YAML::EndSeq;
}
body << YAML::EndMap;
return true;
}