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:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user