Added support for race/size/element/min level/max level quest objectives (#5191)

* Added support for race/size/element/min level/max level quest objectives
* Enabled Butler for the Richards hunting quests (ep16.1) 

Co-authored-by: Aleos <aleos89@users.noreply.github.com>

Thanks to @attackjom, @Balferian !
Took some parts from b67c688e79 (diff-f3653b71c45029581a98314726e4d8f5) thanks to @exneval !
This commit is contained in:
Atemo
2020-07-20 18:59:50 +02:00
committed by GitHub
parent 28632e8328
commit 5ba29be2a3
12 changed files with 419 additions and 89 deletions

View File

@@ -105,26 +105,49 @@ uint64 QuestDatabase::parseBodyNode(const YAML::Node &node) {
return 0;
}
if (!this->nodeExists(targetNode, "Mob"))
continue;
std::string mob_name;
if (!this->asString(targetNode, "Mob", mob_name))
if (!this->nodeExists(targetNode, "Mob") && !this->nodeExists(targetNode, "Id")) {
this->invalidWarning(targetNode, "Missing Target 'Mob' or 'Id', skipping.\n");
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_;
});
std::vector<std::shared_ptr<s_quest_objective>>::iterator it;
uint16 index = 0, mob_id = 0;
if (this->nodeExists(targetNode, "Mob")) {
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());
return 0;
}
mob_id = mob->vd.class_;
it = std::find_if(quest->objectives.begin(), quest->objectives.end(), [&](std::shared_ptr<s_quest_objective> const &v) {
return (*v).mob_id == mob_id;
});
}
else {
if (!this->asUInt16(targetNode, "Id", index)) {
this->invalidWarning(targetNode, "Missing 'Id', skipping.\n");
return 0;
}
if (index == 0) {
this->invalidWarning(targetNode["Id"], "'Id' can't be 0, skipping.\n");
return 0;
}
it = std::find_if(quest->objectives.begin(), quest->objectives.end(), [&](std::shared_ptr<s_quest_objective> const &v) {
return (*v).index == index;
});
}
if (it != quest->objectives.end())
target = (*it);
@@ -136,11 +159,118 @@ uint64 QuestDatabase::parseBodyNode(const YAML::Node &node) {
if (!targetExists) {
if (!this->nodeExists(targetNode, "Count")) {
this->invalidWarning(targetNode["Count"], "Targets has no Count value specified, skipping.\n");
continue;
return 0;
}
if (!this->nodeExists(targetNode, "Mob") && !this->nodeExists(targetNode, "MinLevel") && !this->nodeExists(targetNode, "MaxLevel") &&
!this->nodeExists(targetNode, "Race") && !this->nodeExists(targetNode, "Size") && !this->nodeExists(targetNode, "Element")) {
this->invalidWarning(targetNode, "Targets is missing required field, skipping.\n");
return 0;
}
target = std::make_shared<s_quest_objective>();
target->mob_id = mob->vd.class_;
target->index = index;
target->mob_id = mob_id;
target->min_level = 0;
target->max_level = 0;
target->race = RC_ALL;
target->size = SZ_ALL;
target->element = ELE_ALL;
}
if (!this->nodeExists(targetNode, "Mob")) {
if (this->nodeExists(targetNode, "MinLevel")) {
uint16 level;
if (!this->asUInt16(targetNode, "MinLevel", level))
return 0;
target->min_level = level;
}
if (this->nodeExists(targetNode, "MaxLevel")) {
uint16 level;
if (!this->asUInt16(targetNode, "MaxLevel", level))
return 0;
if (target->min_level > level) {
this->invalidWarning(targetNode["MaxLevel"], "%d's MinLevel is greater than MaxLevel. Defaulting MaxLevel to %d.\n", target->min_level, MAX_LEVEL);
level = MAX_LEVEL;
}
target->max_level = level;
}
if (this->nodeExists(targetNode, "Race")) {
std::string race;
if (!this->asString(targetNode, "Race", race))
return 0;
std::string race_constant = "RC_" + race;
int64 constant;
if (!script_get_constant(race_constant.c_str(), &constant)) {
this->invalidWarning(targetNode["Race"], "Invalid race %s, skipping.\n", race.c_str());
return 0;
}
if (constant < RC_FORMLESS || constant > RC_ALL || constant == RC_PLAYER_HUMAN || constant == RC_PLAYER_DORAM) {
this->invalidWarning(targetNode["Race"], "Unsupported race %s, skipping.\n", race.c_str());
return 0;
}
target->race = static_cast<e_race>(constant);
}
if (this->nodeExists(targetNode, "Size")) {
std::string size_;
if (!this->asString(targetNode, "Size", size_))
return 0;
std::string size_constant = "Size_" + size_;
int64 constant;
if (!script_get_constant(size_constant.c_str(), &constant)) {
this->invalidWarning(targetNode["Size"], "Invalid size type %s, skipping.\n", size_.c_str());
return 0;
}
if (constant < SZ_SMALL || constant > SZ_ALL) {
this->invalidWarning(targetNode["size"], "Unsupported size %s, skipping.\n", size_.c_str());
return 0;
}
target->size = static_cast<e_size>(constant);
}
if (this->nodeExists(targetNode, "Element")) {
std::string element;
if (!this->asString(targetNode, "Element", element))
return 0;
std::string element_constant = "Ele_" + element;
int64 constant;
if (!script_get_constant(element_constant.c_str(), &constant)) {
this->invalidWarning(targetNode["Element"], "Invalid element %s, skipping.\n", element.c_str());
return 0;
}
if (constant < ELE_NEUTRAL || constant > ELE_ALL) {
this->invalidWarning(targetNode["Element"], "Unsupported element %s, skipping.\n", element.c_str());
return 0;
}
target->element = static_cast<e_element>(constant);
}
// if max_level is set, min_level is 1
if (target->min_level == 0 && target->max_level > 0)
target->min_level = 1;
}
if (this->nodeExists(targetNode, "Count")) {
@@ -336,7 +466,7 @@ int quest_pc_login(struct map_session_data *sd)
//@TODO[Haru]: Is this necessary? Does quest_send_mission not take care of this?
for (int i = 0; i < sd->avail_quests; i++)
clif_quest_update_objective(sd, &sd->quest_log[i], 0);
clif_quest_update_objective(sd, &sd->quest_log[i]);
#endif
return 0;
@@ -406,7 +536,7 @@ int quest_add(struct map_session_data *sd, int quest_id)
sd->save_quest = true;
clif_quest_add(sd, &sd->quest_log[n]);
clif_quest_update_objective(sd, &sd->quest_log[n], 0);
clif_quest_update_objective(sd, &sd->quest_log[n]);
if( save_settings&CHARSAVE_QUEST )
chrif_save(sd, CSAVE_NORMAL);
@@ -456,7 +586,7 @@ int quest_change(struct map_session_data *sd, int qid1, int qid2)
clif_quest_delete(sd, qid1);
clif_quest_add(sd, &sd->quest_log[i]);
clif_quest_update_objective(sd, &sd->quest_log[i], 0);
clif_quest_update_objective(sd, &sd->quest_log[i]);
if( save_settings&CHARSAVE_QUEST )
chrif_save(sd, CSAVE_NORMAL);
@@ -509,47 +639,81 @@ int quest_delete(struct map_session_data *sd, int quest_id)
* @param ap : Argument list, expecting:
* int Party ID
* int Mob ID
* int Mob Level
* int Mob Race
* int Mob Size
* int Mob Element
*/
int quest_update_objective_sub(struct block_list *bl, va_list ap)
{
struct map_session_data *sd;
int mob_id, party_id;
nullpo_ret(bl);
nullpo_ret(sd = (struct map_session_data *)bl);
party_id = va_arg(ap,int);
mob_id = va_arg(ap,int);
struct map_session_data *sd;
nullpo_ret(sd = (struct map_session_data *)bl);
if( !sd->avail_quests )
return 0;
int party_id = va_arg(ap,int);
int mob_id = va_arg(ap, int);
int mob_level = va_arg(ap, int);
e_race mob_race = static_cast<e_race>(va_arg(ap, int));
e_size mob_size = static_cast<e_size>(va_arg(ap, int));
e_element mob_element = static_cast<e_element>(va_arg(ap, int));
if( sd->status.party_id != party_id )
return 0;
quest_update_objective(sd, mob_id);
quest_update_objective(sd, mob_id, mob_level, mob_race, mob_size, mob_element);
return 1;
}
/**
* Updates the quest objectives for a character after killing a monster, including the handling of quest-granted drops.
* @param sd : Character's data
* @param mob_id : Monster ID
* @param sd: Character's data
* @param mob_id: Monster ID
* @param mob_level: Monster Level
* @param mob_race: Monster Race
* @param mob_size: Monster Size
* @param mob_element: Monster Element
*/
void quest_update_objective(struct map_session_data *sd, int mob_id)
void quest_update_objective(struct map_session_data *sd, int mob_id, int mob_level, e_race mob_race, e_size mob_size, e_element mob_element)
{
nullpo_retv(sd);
for (int i = 0; i < sd->avail_quests; i++) {
if (sd->quest_log[i].state == Q_COMPLETE) // Skip complete quests
continue;
std::shared_ptr<s_quest_db> qi = quest_search(sd->quest_log[i].quest_id);
if (!qi)
continue;
// 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) {
uint8 objective_check = 0; // Must pass all 5 checks
if (qi->objectives[j]->mob_id == mob_id)
objective_check = 5;
else if (qi->objectives[j]->mob_id == 0) {
if (qi->objectives[j]->min_level == 0 || qi->objectives[j]->min_level <= mob_level)
objective_check++;
if (qi->objectives[j]->max_level == 0 || qi->objectives[j]->max_level >= mob_level)
objective_check++;
if (qi->objectives[j]->race == RC_ALL || qi->objectives[j]->race == mob_race)
objective_check++;
if (qi->objectives[j]->size == SZ_ALL || qi->objectives[j]->size == mob_size)
objective_check++;
if (qi->objectives[j]->element == ELE_ALL || qi->objectives[j]->element == mob_element)
objective_check++;
}
if (objective_check == 5 && 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);
clif_quest_update_objective(sd, &sd->quest_log[i]);
}
}
@@ -562,21 +726,21 @@ void quest_update_objective(struct map_session_data *sd, int mob_id)
if (!itemdb_exists(it->nameid))
continue;
struct item item = {};
struct item entry = {};
item.nameid = it->nameid;
item.identify = itemdb_isidentified(it->nameid);
item.amount = it->count;
entry.nameid = it->nameid;
entry.identify = itemdb_isidentified(it->nameid);
entry.amount = it->count;
//#ifdef BOUND_ITEMS
// item.bound = it.bound;
// entry.bound = it->bound;
//#endif
// if (it.isGUID)
// item.unique_id = pc_generate_unique_id(sd);
char temp;
e_additem_result result;
if ((temp = pc_additem(sd, &item, 1, LOG_TYPE_QUEST)) != ADDITEM_SUCCESS) // Failed to obtain the item
clif_additem(sd, 0, 0, temp);
if ((result = pc_additem(sd, &entry, 1, LOG_TYPE_QUEST)) != ADDITEM_SUCCESS) // Failed to obtain the item
clif_additem(sd, 0, 0, result);
// 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);
}