Bläddra i källkod

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 https://github.com/idathena/trunk/commit/b67c688e791d303a1dfe949bf0bd2b00b7b332ad#diff-f3653b71c45029581a98314726e4d8f5 thanks to @exneval !
Atemo 4 år sedan
förälder
incheckning
5ba29be2a3

+ 12 - 2
db/import-tmpl/quest_db.yml

@@ -30,8 +30,18 @@
 #                   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.
+#                   The target can be a couple of node Mob/Count or of Id/Race/Size/Element/MinLevel/MaxLevel.
+#                   If Mob is supplied, Count is required and the other fields are ignored.
+#                   If Id is supplied, at least one other field of Race/Size/Element/MinLevel/MaxLevel is required.
+#                   If Id is supplied, Count is required for each new entry.
+#     - Mob         Monster to kill (aegis monster name).
+#       Count       Amount of monsters to kill. Set to 0 to skip the target on import.
+#       Id          Unique target index for the quest Id. Requires a positive number.
+#       Race        Monster race target (default All). Valids race are Angel, Brute, DemiHuman, Demon, Dragon, Fish, Formless, Insect, Plant, Undead, All.
+#       Size        Monster size target (default All). Valids size are Small, Medium, Large, All.
+#       Element     Monster element target (default All). Valids elements are Dark, Earth, Fire, Ghost, Holy, Neutral, Poison, Undead, Water, Wind, All.
+#       MinLevel    Minimum monster level target. If not supplied but MaxLevel defined, MinLevel is 1. Set to 0 to ignore MinLevel on import. (Default: 0)
+#       MaxLevel    Maximum monster level target. Set to 0 to ignore MaxLevel on import. (Default: 0)
 #   Drops:          Quest item drop targets. (Default: null)
 #     - Mob         Monster to kill. 0 will apply to all monsters. (Default: 0)
 #       Item        Item to drop.

+ 12 - 2
db/pre-re/quest_db.yml

@@ -30,8 +30,18 @@
 #                   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.
+#                   The target can be a couple of node Mob/Count or of Id/Race/Size/Element/MinLevel/MaxLevel.
+#                   If Mob is supplied, Count is required and the other fields are ignored.
+#                   If Id is supplied, at least one other field of Race/Size/Element/MinLevel/MaxLevel is required.
+#                   If Id is supplied, Count is required for each new entry.
+#     - Mob         Monster to kill (aegis monster name).
+#       Count       Amount of monsters to kill. Set to 0 to skip the target on import.
+#       Id          Unique target index for the quest Id. Requires a positive number.
+#       Race        Monster race target (default All). Valids race are Angel, Brute, DemiHuman, Demon, Dragon, Fish, Formless, Insect, Plant, Undead, All.
+#       Size        Monster size target (default All). Valids size are Small, Medium, Large, All.
+#       Element     Monster element target (default All). Valids elements are Dark, Earth, Fire, Ghost, Holy, Neutral, Poison, Undead, Water, Wind, All.
+#       MinLevel    Minimum monster level target. If not supplied but MaxLevel defined, MinLevel is 1. Set to 0 to ignore MinLevel on import. (Default: 0)
+#       MaxLevel    Maximum monster level target. Set to 0 to ignore MaxLevel on import. (Default: 0)
 #   Drops:          Quest item drop targets. (Default: null)
 #     - Mob         Monster to kill. 0 will apply to all monsters. (Default: 0)
 #       Item        Item to drop.

+ 12 - 2
db/quest_db.yml

@@ -30,8 +30,18 @@
 #                   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.
+#                   The target can be a couple of node Mob/Count or of Id/Race/Size/Element/MinLevel/MaxLevel.
+#                   If Mob is supplied, Count is required and the other fields are ignored.
+#                   If Id is supplied, at least one other field of Race/Size/Element/MinLevel/MaxLevel is required.
+#                   If Id is supplied, Count is required for each new entry.
+#     - Mob         Monster to kill (aegis monster name).
+#       Count       Amount of monsters to kill. Set to 0 to skip the target on import.
+#       Id          Unique target index for the quest Id. Requires a positive number.
+#       Race        Monster race target (default All). Valids race are Angel, Brute, DemiHuman, Demon, Dragon, Fish, Formless, Insect, Plant, Undead, All.
+#       Size        Monster size target (default All). Valids size are Small, Medium, Large, All.
+#       Element     Monster element target (default All). Valids elements are Dark, Earth, Fire, Ghost, Holy, Neutral, Poison, Undead, Water, Wind, All.
+#       MinLevel    Minimum monster level target. If not supplied but MaxLevel defined, MinLevel is 1. Set to 0 to ignore MinLevel on import. (Default: 0)
+#       MaxLevel    Maximum monster level target. Set to 0 to ignore MaxLevel on import. (Default: 0)
 #   Drops:          Quest item drop targets. (Default: null)
 #     - Mob         Monster to kill. 0 will apply to all monsters. (Default: 0)
 #       Item        Item to drop.

+ 62 - 2
db/re/quest_db.yml

@@ -30,8 +30,18 @@
 #                   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.
+#                   The target can be a couple of node Mob/Count or of Id/Race/Size/Element/MinLevel/MaxLevel.
+#                   If Mob is supplied, Count is required and the other fields are ignored.
+#                   If Id is supplied, at least one other field of Race/Size/Element/MinLevel/MaxLevel is required.
+#                   If Id is supplied, Count is required for each new entry.
+#     - Mob         Monster to kill (aegis monster name).
+#       Count       Amount of monsters to kill. Set to 0 to skip the target on import.
+#       Id          Unique target index for the quest Id. Requires a positive number.
+#       Race        Monster race target (default All). Valids race are Angel, Brute, DemiHuman, Demon, Dragon, Fish, Formless, Insect, Plant, Undead, All.
+#       Size        Monster size target (default All). Valids size are Small, Medium, Large, All.
+#       Element     Monster element target (default All). Valids elements are Dark, Earth, Fire, Ghost, Holy, Neutral, Poison, Undead, Water, Wind, All.
+#       MinLevel    Minimum monster level target. If not supplied but MaxLevel defined, MinLevel is 1. Set to 0 to ignore MinLevel on import. (Default: 0)
+#       MaxLevel    Maximum monster level target. Set to 0 to ignore MaxLevel on import. (Default: 0)
 #   Drops:          Quest item drop targets. (Default: null)
 #     - Mob         Monster to kill. 0 will apply to all monsters. (Default: 0)
 #       Item        Item to drop.
@@ -2245,24 +2255,74 @@ Body:
     Title: The Royal Richard
   - Id: 5404
     Title: "[Repeat]Warrior Discipline-Human"
+    Targets:
+      - Id: 1
+        Count: 50
+        Race: DemiHuman
+        MinLevel: 140
   - Id: 5405
     Title: "[Repeat]Warrior Discipline-Animal"
+    Targets:
+      - Id: 1
+        Count: 50
+        Race: Brute
+        MinLevel: 140
   - Id: 5406
     Title: "[Repeat]Warrior Discipline-Insect"
+    Targets:
+      - Id: 1
+        Count: 50
+        Race: Insect
+        MinLevel: 140
   - Id: 5407
     Title: "[Repeat]Warrior Discipline-Fish"
+    Targets:
+      - Id: 1
+        Count: 50
+        Race: Fish
+        MinLevel: 140
   - Id: 5408
     Title: "[Repeat]Warrior Discipline-Plant"
+    Targets:
+      - Id: 1
+        Count: 50
+        Race: Plant
+        MinLevel: 140
   - Id: 5409
     Title: "[Repeat]Warrior Discipline-Devil"
+    Targets:
+      - Id: 1
+        Count: 50
+        Race: Demon
+        MinLevel: 140
   - Id: 5410
     Title: "[Repeat]Warrior Discipline-Angel"
+    Targets:
+      - Id: 1
+        Count: 50
+        Race: Angel
+        MinLevel: 140
   - Id: 5411
     Title: "[Repeat]Warrior Discipline-Immortal"
+    Targets:
+      - Id: 1
+        Count: 50
+        Race: Undead
+        MinLevel: 140
   - Id: 5412
     Title: "[Repeat]Warrior Discipline-Intangible"
+    Targets:
+      - Id: 1
+        Count: 50
+        Race: Formless
+        MinLevel: 140
   - Id: 5413
     Title: "[Repeat]Warrior Discipline-Dragon"
+    Targets:
+      - Id: 1
+        Count: 50
+        Race: Dragon
+        MinLevel: 140
   - Id: 5414
     Title: "[Stand by]Warrior Discipline"
     TimeLimit: 4h

+ 12 - 2
doc/yaml/db/quest_db.yml

@@ -13,8 +13,18 @@
 #                   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.
+#                   The target can be a couple of node Mob/Count or of Id/Race/Size/Element/MinLevel/MaxLevel.
+#                   If Mob is supplied, Count is required and the other fields are ignored.
+#                   If Id is supplied, at least one other field of Race/Size/Element/MinLevel/MaxLevel is required.
+#                   If Id is supplied, Count is required for each new entry.
+#     - Mob         Monster to kill (aegis monster name).
+#       Count       Amount of monsters to kill. Set to 0 to skip the target on import.
+#       Id          Unique target index for the quest Id. Requires a positive number.
+#       Race        Monster race target (default All). Valids race are Angel, Brute, DemiHuman, Demon, Dragon, Fish, Formless, Insect, Plant, Undead, All.
+#       Size        Monster size target (default All). Valids size are Small, Medium, Large, All.
+#       Element     Monster element target (default All). Valids elements are Dark, Earth, Fire, Ghost, Holy, Neutral, Poison, Undead, Water, Wind, All.
+#       MinLevel    Minimum monster level target. If not supplied but MaxLevel defined, MinLevel is 1. Set to 0 to ignore MinLevel on import. (Default: 0)
+#       MaxLevel    Maximum monster level target. Set to 0 to ignore MaxLevel on import. (Default: 0)
 #   Drops:          Quest item drop targets. (Default: null)
 #     - Mob         Monster to kill. 0 will apply to all monsters. (Default: 0)
 #       Item        Item to drop.

+ 0 - 4
npc/re/quests/quests_16_1.txt

@@ -8622,10 +8622,6 @@ prt_cas_q,80,80,4	script	Butler for the Richards	1_M_LIBRARYMASTER,{
 			next;
 		}
 		if (checkquest(.@quest_list[.@i],HUNTING) == 2) {
-			// todo src side
-			mes "This quest isn't enabled for now.";
-			close;
-
 			if (isbegin_quest(5403) == 1)
 				completequest 5403;// The Royal Richard
 			erasequest .@quest_list[.@i];

+ 93 - 29
src/map/clif.cpp

@@ -17048,12 +17048,55 @@ static void clif_quest_len(int def_len, int info_len, int avail_quests, int *lim
 	(*len_out) = ((*limit_out) * info_len) + def_len;
 }
 
+std::string clif_mobtype_name(e_race race, e_size size, e_element element) {
+	std::string race_name, size_name, ele_name;
+
+	switch(race) {
+		case RC_FORMLESS:	race_name = "Formless"; break;
+		case RC_UNDEAD:		race_name = "Undead"; break;
+		case RC_BRUTE:		race_name = "Brute"; break;
+		case RC_PLANT:		race_name = "Plant"; break;
+		case RC_INSECT:		race_name = "Insect"; break;
+		case RC_FISH:		race_name = "Fish"; break;
+		case RC_DEMON:		race_name = "Demon"; break;
+		case RC_DEMIHUMAN:	race_name = "Demihuman"; break;
+		case RC_ANGEL:		race_name = "Angel"; break;
+		case RC_DRAGON:		race_name = "Dragon"; break;
+		case RC_ALL:		race_name = ""; break;
+		default:			race_name = "unknown"; break;
+	}
+	switch(size) {
+		case SZ_SMALL:	size_name = "Small"; break;
+		case SZ_MEDIUM:	size_name = "Medium"; break;
+		case SZ_BIG:	size_name = "Large"; break;
+		case SZ_ALL:	size_name = ""; break;
+		default:		size_name = "unknown"; break;
+	}
+	switch(element) {
+		case ELE_NEUTRAL:	ele_name = "Neutral Element"; break;
+		case ELE_WATER:		ele_name = "Water Element"; break;
+		case ELE_EARTH:		ele_name = "Earth Element"; break;
+		case ELE_FIRE:		ele_name = "Fire Element"; break;
+		case ELE_WIND:		ele_name = "Wind Element"; break;
+		case ELE_POISON:	ele_name = "Poison Element"; break;
+		case ELE_HOLY:		ele_name = "Holy Element"; break;
+		case ELE_DARK:		ele_name = "Shadow Element"; break;
+		case ELE_GHOST:		ele_name = "Ghost Element"; break;
+		case ELE_UNDEAD:	ele_name = "Undead Element"; break;
+		case ELE_ALL:		ele_name = ""; break;
+		default:			ele_name = "unknown"; break;
+	}
+	return (race_name + (race_name.size() && size_name.size() ? ", " + size_name : size_name) + ((race_name.size() || size_name.size()) && ele_name.size() ? ", " + ele_name : ele_name));
+}
+
 /// Sends list of all quest states
 /// 02b1 <packet len>.W <num>.L { <quest id>.L <active>.B }*num (ZC_ALL_QUEST_LIST)
 /// 097a <packet len>.W <num>.L { <quest id>.L <active>.B <remaining time>.L <time>.L <count>.W { <mob_id>.L <killed>.W <total>.W <mob name>.24B }*count }*num (ZC_ALL_QUEST_LIST2)
 /// 09f8 <packet len>.W <num>.L { <quest id>.L <active>.B <remaining time>.L <time>.L <count>.W { <hunt identification>.L <mob type>.L <mob_id>.L <min level>.W <max level>.W <killed>.W <total>.W <mob name>.24B }*count }*num  (ZC_ALL_QUEST_LIST3)
 void clif_quest_send_list(struct map_session_data *sd)
 {
+	nullpo_retv(sd);
+
 	int fd = sd->fd;
 	int i;
 	int offset = 8;
@@ -17095,25 +17138,32 @@ void clif_quest_send_list(struct map_session_data *sd)
 			for (int j = 0; j < qi->objectives.size(); j++) {
 				mob = mob_db(qi->objectives[j]->mob_id);
 
+				e_race race = qi->objectives[j]->race;
+				e_size size = qi->objectives[j]->size;
+				e_element element = qi->objectives[j]->element;
+
 #if PACKETVER >= 20150513
 				WFIFOL(fd, offset) = sd->quest_log[i].quest_id * 1000 + j;
 				offset += 4;
-				WFIFOL(fd, offset) = 0; // TODO: Find info - mobType
+				WFIFOL(fd, offset) = (race ? race : (size ? size : (element ? element : 0)));
 				offset += 4;
 #endif
-				WFIFOL(fd, offset) = qi->objectives[j]->mob_id;
+				WFIFOL(fd, offset) = ((mob && qi->objectives[j]->mob_id > 0) ? qi->objectives[j]->mob_id : MOBID_PORING);
 				offset += 4;
 #if PACKETVER >= 20150513
-				WFIFOW(fd, offset) = 0; // TODO: Find info - levelMin
+				WFIFOW(fd, offset) = qi->objectives[j]->min_level;
 				offset += 2;
-				WFIFOW(fd, offset) = 0; // TODO: Find info - levelMax
+				WFIFOW(fd, offset) = qi->objectives[j]->max_level;
 				offset += 2;
 #endif
 				WFIFOW(fd, offset) = sd->quest_log[i].count[j];
 				offset += 2;
 				WFIFOW(fd, offset) = qi->objectives[j]->count;
 				offset += 2;
-				safestrncpy((char*)WFIFOP(fd, offset), mob->jname, NAME_LENGTH);
+				if (mob && qi->objectives[j]->mob_id > 0)
+					safestrncpy((char *)WFIFOP(fd,offset), mob->jname, NAME_LENGTH);
+				else
+					safestrncpy((char *)WFIFOP(fd,offset), clif_mobtype_name(race, size, element).c_str(), NAME_LENGTH);
 				offset += NAME_LENGTH;
 			}
 		}
@@ -17143,10 +17193,11 @@ void clif_quest_send_list(struct map_session_data *sd)
 /// 02b2 <packet len>.W <num>.L { <quest id>.L <start time>.L <expire time>.L <mobs>.W { <mob id>.L <mob count>.W <mob name>.24B }*3 }*num
 void clif_quest_send_mission(struct map_session_data *sd)
 {
+	nullpo_retv(sd);
+
 	int fd = sd->fd;
 	int limit = 0;
 	int len = sd->avail_quests*104+8;
-	struct mob_db *mob;
 
 	clif_quest_len(8, 14 + ((6 + NAME_LENGTH) * MAX_QUEST_OBJECTIVES), sd->avail_quests, &limit, &len);
 	WFIFOHEAD(fd, len);
@@ -17163,10 +17214,14 @@ void clif_quest_send_mission(struct map_session_data *sd)
 		WFIFOW(fd, i*104+20) = static_cast<uint16>(qi->objectives.size());
 
 		for (int j = 0 ; j < qi->objectives.size(); j++) {
-			WFIFOL(fd, i*104+22+j*30) = qi->objectives[j]->mob_id;
+			struct mob_db *mob = mob_db(qi->objectives[j]->mob_id);
+
+			WFIFOL(fd, i*104+22+j*30) = (mob ? qi->objectives[j]->mob_id : MOBID_PORING);
 			WFIFOW(fd, i*104+26+j*30) = sd->quest_log[i].count[j];
-			mob = mob_db(qi->objectives[j]->mob_id);
-			safestrncpy(WFIFOCP(fd, i*104+28+j*30), mob->jname, NAME_LENGTH);
+			if (mob && qi->objectives[j]->mob_id > 0)
+				safestrncpy(WFIFOCP(fd, i*104+28+j*30), mob->jname, NAME_LENGTH);
+			else
+				safestrncpy(WFIFOCP(fd, i*104+28+j*30), clif_mobtype_name(qi->objectives[j]->race, qi->objectives[j]->size, qi->objectives[j]->element).c_str(), NAME_LENGTH);
 		}
 	}
 
@@ -17180,8 +17235,13 @@ void clif_quest_send_mission(struct map_session_data *sd)
 /// 09f9 <quest id>.L <active>.B <start time>.L <expire time>.L <mobs>.W { <hunt identification>.L <mob type>.L <mob id>.L <min level>.W <max level>.W <mob count>.W <mob name>.24B }*3 (ZC_ADD_QUEST_EX)
 void clif_quest_add(struct map_session_data *sd, struct quest *qd)
 {
+	nullpo_retv(sd);
+	nullpo_retv(qd);
+
 	int fd = sd->fd;
 	std::shared_ptr<s_quest_db> qi = quest_search(qd->quest_id);
+	if (!qi)
+		return;
 #if PACKETVER >= 20150513
 	int cmd = 0x9f9;
 #else
@@ -17197,25 +17257,31 @@ void clif_quest_add(struct map_session_data *sd, struct quest *qd)
 	WFIFOW(fd, 15) = static_cast<uint16>(qi->objectives.size());
 
 	for (int i = 0, offset = 17; i < qi->objectives.size(); i++) {
-		struct mob_db *mob;
+		struct mob_db *mob = mob_db(qi->objectives[i]->mob_id);
+		e_race race = qi->objectives[i]->race;
+		e_size size = qi->objectives[i]->size;
+		e_element element = qi->objectives[i]->element;
+
 #if PACKETVER >= 20150513
 		WFIFOL(fd, offset) = qd->quest_id * 1000 + i;
 		offset += 4;
-		WFIFOL(fd, offset) = 0; // TODO: Find info - mobType
+		WFIFOL(fd, offset) = (race ? race : (size ? size : (element ? element : 0)));	// effect ?
 		offset += 4;
 #endif
-		WFIFOL(fd, offset) = qi->objectives[i]->mob_id;
+		WFIFOL(fd, offset) = ((mob && qi->objectives[i]->mob_id > 0) ? qi->objectives[i]->mob_id : MOBID_PORING);	// 0 can't be used as it displays "Novice" job regardless of the clif_mobtype_name
 		offset += 4;
 #if PACKETVER >= 20150513
-		WFIFOW(fd, offset) = 0; // TODO: Find info - levelMin
+		WFIFOW(fd, offset) = qi->objectives[i]->min_level;
 		offset += 2;
-		WFIFOW(fd, offset) = 0; // TODO: Find info - levelMax
+		WFIFOW(fd, offset) = qi->objectives[i]->max_level;
 		offset += 2;
 #endif
 		WFIFOW(fd, offset) = qd->count[i];
 		offset += 2;
-		mob = mob_db(qi->objectives[i]->mob_id);
-		safestrncpy(WFIFOCP(fd, offset), mob->jname, NAME_LENGTH);
+		if (mob && qi->objectives[i]->mob_id > 0)
+			safestrncpy((char *)WFIFOP(fd,offset), mob->jname, NAME_LENGTH);
+		else
+			safestrncpy((char *)WFIFOP(fd,offset), clif_mobtype_name(race, size, element).c_str(), NAME_LENGTH);
 		offset += NAME_LENGTH;
 	}
 
@@ -17257,7 +17323,7 @@ void clif_quest_delete(struct map_session_data *sd, int quest_id)
 /// Notification of an update to the hunting mission counter
 /// 02b5 <packet len>.W <mobs>.W { <quest id>.L <mob id>.L <total count>.W <current count>.W }*3 (ZC_UPDATE_MISSION_HUNT)
 /// 09fa <packet len>.W <mobs>.W { <quest id>.L <hunt identification>.L <total count>.W <current count>.W }*3 (ZC_UPDATE_MISSION_HUNT_EX)
-void clif_quest_update_objective(struct map_session_data *sd, struct quest *qd, int mobid)
+void clif_quest_update_objective(struct map_session_data *sd, struct quest *qd)
 {
 	int fd = sd->fd;
 	int offset = 6;
@@ -17274,21 +17340,19 @@ void clif_quest_update_objective(struct map_session_data *sd, struct quest *qd,
 	WFIFOW(fd, 4) = static_cast<uint16>(qi->objectives.size());
 
 	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;
+		WFIFOL(fd, offset) = qd->quest_id;
+		offset += 4;
 #if PACKETVER >= 20150513
-			WFIFOL(fd, offset) = qd->quest_id * 1000 + i;
-			offset += 4;
+		WFIFOL(fd, offset) = qd->quest_id * 1000 + i;
+		offset += 4;
 #else
-			WFIFOL(fd, offset) = qi->objectives[i]->mob_id;
-			offset += 4;
+		WFIFOL(fd, offset) = qi->objectives[i]->mob_id;
+		offset += 4;
 #endif
-			WFIFOW(fd, offset) = qi->objectives[i]->count;
-			offset += 2;
-			WFIFOW(fd, offset) = qd->count[i];
-			offset += 2;
-		}
+		WFIFOW(fd, offset) = qi->objectives[i]->count;
+		offset += 2;
+		WFIFOW(fd, offset) = qd->count[i];
+		offset += 2;
 	}
 
 	WFIFOW(fd, 2) = offset;

+ 1 - 1
src/map/clif.hpp

@@ -930,7 +930,7 @@ void clif_quest_send_mission(struct map_session_data * sd);
 void clif_quest_add(struct map_session_data * sd, struct quest * qd);
 void clif_quest_delete(struct map_session_data * sd, int quest_id);
 void clif_quest_update_status(struct map_session_data * sd, int quest_id, bool active);
-void clif_quest_update_objective(struct map_session_data * sd, struct quest * qd, int mobid);
+void clif_quest_update_objective(struct map_session_data * sd, struct quest * qd);
 void clif_quest_show_event(struct map_session_data *sd, struct block_list *bl, e_questinfo_types effect, e_questinfo_markcolor color);
 void clif_displayexp(struct map_session_data *sd, unsigned int exp, char type, bool quest, bool lost);
 

+ 2 - 2
src/map/mob.cpp

@@ -3009,9 +3009,9 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type)
 			}
 
 			if (sd->status.party_id)
-				map_foreachinallrange(quest_update_objective_sub, &md->bl, AREA_SIZE, BL_PC, sd->status.party_id, md->mob_id);
+				map_foreachinallrange(quest_update_objective_sub, &md->bl, AREA_SIZE, BL_PC, sd->status.party_id, md->mob_id, md->level, status->race, status->size, status->def_ele);
 			else if (sd->avail_quests)
-				quest_update_objective(sd, md->mob_id);
+				quest_update_objective(sd, md->mob_id, md->level, static_cast<e_race>(status->race), static_cast<e_size>(status->size), static_cast<e_element>(status->def_ele));
 
 			if (achievement_db.mobexists(md->mob_id)) {
 				if (battle_config.achievement_mob_share > 0 && sd->status.party_id > 0)

+ 1 - 1
src/map/mob.hpp

@@ -94,7 +94,7 @@ enum MobDamageLogFlag
 	MDLF_SELF
 };
 
-enum size {
+enum e_size : uint8 {
 	SZ_SMALL = 0,
 	SZ_MEDIUM,
 	SZ_BIG,

+ 202 - 38
src/map/quest.cpp

@@ -105,26 +105,49 @@ uint64 QuestDatabase::parseBodyNode(const YAML::Node &node) {
 				return 0;
 			}
 
-			if (!this->nodeExists(targetNode, "Mob"))
-				continue;
+			if (!this->nodeExists(targetNode, "Mob") && !this->nodeExists(targetNode, "Id")) {
+				this->invalidWarning(targetNode, "Missing Target 'Mob' or 'Id', skipping.\n");
+				return 0;
+			}
 
-			std::string mob_name;
+			std::shared_ptr<s_quest_objective> target;
+			std::vector<std::shared_ptr<s_quest_objective>>::iterator it;
+			uint16 index = 0, mob_id = 0;
 
-			if (!this->asString(targetNode, "Mob", mob_name))
-				return 0;
+			if (this->nodeExists(targetNode, "Mob")) {
 
-			struct mob_db *mob = mobdb_search_aegisname(mob_name.c_str());
+				std::string mob_name;
 
-			if (!mob) {
-				this->invalidWarning(targetNode["Mob"], "Mob %s does not exist, skipping.\n", mob_name.c_str());
-				continue;
+				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;
+				}
 
-			//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_;
-			});
+				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)
 {
+	nullpo_ret(bl);
+
 	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);
-
 	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);
 		}

+ 7 - 1
src/map/quest.hpp

@@ -13,6 +13,7 @@
 #include "map.hpp"
 
 struct map_session_data;
+enum e_size : uint8;
 
 struct s_quest_dropitem {
 	uint16 nameid;
@@ -25,8 +26,13 @@ struct s_quest_dropitem {
 };
 
 struct s_quest_objective {
+	uint16 index;
 	uint16 mob_id;
 	uint16 count;
+	uint16 min_level, max_level;
+	e_race race;
+	e_size size;
+	e_element element;
 };
 
 struct s_quest_db {
@@ -64,7 +70,7 @@ 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(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);
 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);