Kaynağa Gözat

Refactored achievements to utilize C++ features (#2607)

* Refactored achievements to utilize C++ features
* Cleaned up the YAML parser.
* Moved achievements from DBMap to an unordered_map.
* Moved achievement targets from DBMap to a vector.
* Changed all struct arrays into vectors.
* Changed all char arrays to strings.
* Changed all int arrays to std::arrays.
* Removed achievement_dummy as it's no longer needed.
* Achievements now use smart pointer to ensure proper construction and deconstruction of objects.
Thanks to @lighta!
Aleos 7 yıl önce
ebeveyn
işleme
1c66035761

+ 14 - 14
db/pre-re/achievement_db.yml

@@ -967,63 +967,63 @@ Achievements:
     Name: "Prontera Contribution"
     Map: "prontera"
     Target:
-      Count: 100000
+      - Count: 100000
     Score: 10
   - ID: 127002
     Group: "AG_CHATTING"
     Name: "Geffen Contribution"
     Map: "geffen"
     Target:
-      Count: 100000
+      - Count: 100000
     Score: 10
   - ID: 127003
     Group: "AG_CHATTING"
     Name: "Morocc Contribution"
     Map: "morocc"
     Target:
-      Count: 100000
+      - Count: 100000
     Score: 10
   - ID: 127004
     Group: "AG_CHATTING"
     Name: "Payon Contribution"
     Map: "payon"
     Target:
-      Count: 100000
+      - Count: 100000
     Score: 10
   - ID: 127005
     Group: "AG_CHATTING"
     Name: "Yuno Contribution"
     Map: "yuno"
     Target:
-      Count: 100000
+      - Count: 100000
     Score: 10
   - ID: 127006
     Group: "AG_CHATTING"
     Name: "Lighthalzen Contribution"
     Map: "lighthalzen"
     Target:
-      Count: 100000
+      - Count: 100000
     Score: 10
   - ID: 127007
     Group: "AG_CHATTING"
     Name: "Einbroch Contribution"
     Map: "einbroch"
     Target:
-      Count: 100000
+      - Count: 100000
     Score: 10
   - ID: 127008
     Group: "AG_CHATTING"
     Name: "Rachel Contribution"
     Map: "rachel"
     Target:
-      Count: 100000
+      - Count: 100000
     Score: 10
   - ID: 127009
     Group: "AG_CHATTING"
     Name: "Veins Contribution"
     Map: "veins"
     Target:
-      Count: 100000
+      - Count: 100000
     Score: 10
   - ID: 128000
     Group: "AG_BATTLE"
@@ -2046,35 +2046,35 @@ Achievements:
     Name: "Activating the market economy (1)"
     Condition: " ARG0 >= 10000 "
     Target:
-      Count: 10000
+      - Count: 10000
     Score: 10
   - ID: 220010
     Group: "AG_SPEND_ZENY"
     Name: "Activating the market economy (2)"
     Condition: " ARG0 >= 100000 "
     Target:
-      Count: 100000
+      - Count: 100000
     Score: 15
   - ID: 220011
     Group: "AG_SPEND_ZENY"
     Name: "Activating the market economy (3)"
     Condition: " ARG0 >= 500000 "
     Target:
-      Count: 500000
+      - Count: 500000
     Score: 20
   - ID: 220012
     Group: "AG_SPEND_ZENY"
     Name: "Activating the market economy (4)"
     Condition: " ARG0 >= 1000000 "
     Target:
-      Count: 1000000
+      - Count: 1000000
     Score: 30
   - ID: 220013
     Group: "AG_SPEND_ZENY"
     Name: "Activating the market economy (5)"
     Condition: " ARG0 >= 5000000 "
     Target:
-      Count: 5000000
+      - Count: 5000000
     Score: 50
   - ID: 220014
     Group: "AG_ENCHANT_SUCCESS"

+ 14 - 14
db/re/achievement_db.yml

@@ -967,63 +967,63 @@ Achievements:
     Name: "Prontera Contribution"
     Map: "prontera"
     Target:
-      Count: 100000
+      - Count: 100000
     Score: 10
   - ID: 127002
     Group: "AG_CHATTING"
     Name: "Geffen Contribution"
     Map: "geffen"
     Target:
-      Count: 100000
+      - Count: 100000
     Score: 10
   - ID: 127003
     Group: "AG_CHATTING"
     Name: "Morocc Contribution"
     Map: "morocc"
     Target:
-      Count: 100000
+      - Count: 100000
     Score: 10
   - ID: 127004
     Group: "AG_CHATTING"
     Name: "Payon Contribution"
     Map: "payon"
     Target:
-      Count: 100000
+      - Count: 100000
     Score: 10
   - ID: 127005
     Group: "AG_CHATTING"
     Name: "Yuno Contribution"
     Map: "yuno"
     Target:
-      Count: 100000
+      - Count: 100000
     Score: 10
   - ID: 127006
     Group: "AG_CHATTING"
     Name: "Lighthalzen Contribution"
     Map: "lighthalzen"
     Target:
-      Count: 100000
+      - Count: 100000
     Score: 10
   - ID: 127007
     Group: "AG_CHATTING"
     Name: "Einbroch Contribution"
     Map: "einbroch"
     Target:
-      Count: 100000
+      - Count: 100000
     Score: 10
   - ID: 127008
     Group: "AG_CHATTING"
     Name: "Rachel Contribution"
     Map: "rachel"
     Target:
-      Count: 100000
+      - Count: 100000
     Score: 10
   - ID: 127009
     Group: "AG_CHATTING"
     Name: "Veins Contribution"
     Map: "veins"
     Target:
-      Count: 100000
+      - Count: 100000
     Score: 10
   - ID: 128000
     Group: "AG_BATTLE"
@@ -2046,35 +2046,35 @@ Achievements:
     Name: "Activating the market economy (1)"
     Condition: " ARG0 >= 10000 "
     Target:
-      Count: 10000
+      - Count: 10000
     Score: 10
   - ID: 220010
     Group: "AG_SPEND_ZENY"
     Name: "Activating the market economy (2)"
     Condition: " ARG0 >= 100000 "
     Target:
-      Count: 100000
+      - Count: 100000
     Score: 15
   - ID: 220011
     Group: "AG_SPEND_ZENY"
     Name: "Activating the market economy (3)"
     Condition: " ARG0 >= 500000 "
     Target:
-      Count: 500000
+      - Count: 500000
     Score: 20
   - ID: 220012
     Group: "AG_SPEND_ZENY"
     Name: "Activating the market economy (4)"
     Condition: " ARG0 >= 1000000 "
     Target:
-      Count: 1000000
+      - Count: 1000000
     Score: 30
   - ID: 220013
     Group: "AG_SPEND_ZENY"
     Name: "Activating the market economy (5)"
     Condition: " ARG0 >= 5000000 "
     Target:
-      Count: 5000000
+      - Count: 5000000
     Score: 50
   - ID: 220014
     Group: "AG_ENCHANT_SUCCESS"

+ 1 - 1
doc/achievements.txt

@@ -64,7 +64,7 @@ Example 2:
   // IE: In the achievement_list.lub file, UI_Type 0 is displayed as non-incremental while 1 shows a progress bar of completion for the achievement.
   Condition: " ARG0 >= 100 "
   Target:
-    Count: 100
+    - Count: 100
 
 ---------------------------------------
 

+ 2 - 2
src/common/mmo.h

@@ -143,8 +143,8 @@
 
 //Achievement System
 #define MAX_ACHIEVEMENT_RANK 20 /// Maximum achievement level
-#define MAX_ACHIEVEMENT_OBJECTIVES 10 /// Maximum different objectives in achievement_db.conf
-#define MAX_ACHIEVEMENT_DEPENDENTS 20 /// Maximum different dependents in achievement_db.conf
+#define MAX_ACHIEVEMENT_OBJECTIVES 10 /// Maximum different objectives in achievement_db.yml
+#define MAX_ACHIEVEMENT_DEPENDENTS 20 /// Maximum different dependents in achievement_db.yml
 #define ACHIEVEMENT_NAME_LENGTH 50 /// Max Achievement Name length
 
 enum item_types {

+ 363 - 357
src/map/achievement.cpp

@@ -3,10 +3,12 @@
 
 #include "achievement.hpp"
 
+#include <array>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <setjmp.h>
+#include <yaml-cpp/yaml.h>
 
 #include "../common/cbasetypes.h"
 #include "../common/malloc.h"
@@ -14,7 +16,6 @@
 #include "../common/showmsg.h"
 #include "../common/strlib.h"
 #include "../common/utils.h"
-#include "../common/yamlwrapper.h"
 
 #include "battle.hpp"
 #include "chrif.hpp"
@@ -32,23 +33,27 @@ static char*       av_error_msg;
 static const char* av_error_pos;
 static int         av_error_report;
 
-static DBMap *achievement_db = NULL; // int achievement_id -> struct achievement_db *
-static DBMap *achievementmobs_db = NULL; // Avoids checking achievements on every mob killed
-static void achievement_db_free_sub(struct achievement_db *achievement, bool free);
-struct achievement_db achievement_dummy;
+std::unordered_map<int, std::shared_ptr<s_achievement_db>> achievements;
+std::vector<int> achievement_mobs; // Avoids checking achievements on every mob killed
 
 /**
  * Searches an achievement by ID
  * @param achievement_id: ID to lookup
- * @return Achievement entry (equals to &achievement_dummy if the ID is invalid)
+ * @return True if achievement exists or false if it doesn't
  */
-struct achievement_db *achievement_search(int achievement_id)
+bool achievement_exists(int achievement_id)
 {
-	struct achievement_db *achievement = (struct achievement_db *)idb_get(achievement_db, achievement_id);
+	return achievements.find(achievement_id) != achievements.end();
+}
 
-	if (!achievement)
-		return &achievement_dummy;
-	return achievement;
+/**
+ * Return an achievement by ID
+ * @param achievement_id: ID to lookup
+ * @return shared_ptr of achievement
+ */
+std::shared_ptr<s_achievement_db> &achievement_get(int achievement_id) 
+{
+	return achievements[achievement_id];
 }
 
 /**
@@ -60,7 +65,10 @@ bool achievement_mobexists(int mob_id)
 {
 	if (!battle_config.feature_achievement)
 		return false;
-	return idb_exists(achievementmobs_db, mob_id);
+
+	auto it = std::find(achievement_mobs.begin(), achievement_mobs.end(), mob_id);
+
+	return (it != achievement_mobs.end()) ? true : false;
 }
 
 /**
@@ -71,16 +79,17 @@ bool achievement_mobexists(int mob_id)
  */
 struct achievement *achievement_add(struct map_session_data *sd, int achievement_id)
 {
-	struct achievement_db *adb = &achievement_dummy;
 	int i, index;
 
 	nullpo_retr(NULL, sd);
 
-	if ((adb = achievement_search(achievement_id)) == &achievement_dummy) {
+	if (!achievement_exists(achievement_id)) {
 		ShowError("achievement_add: Achievement %d not found in DB.\n", achievement_id);
 		return NULL;
 	}
 
+	auto &adb = achievements[achievement_id];
+
 	ARR_FIND(0, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == achievement_id);
 	if (i < sd->achievement_data.count) {
 		ShowError("achievement_add: Character %d already has achievement %d.\n", sd->status.char_id, achievement_id);
@@ -121,7 +130,7 @@ bool achievement_remove(struct map_session_data *sd, int achievement_id)
 
 	nullpo_retr(false, sd);
 
-	if (achievement_search(achievement_id) == &achievement_dummy) {
+	if (!achievement_exists(achievement_id)) {
 		ShowError("achievement_delete: Achievement %d not found in DB.\n", achievement_id);
 		return false;
 	}
@@ -155,6 +164,22 @@ bool achievement_remove(struct map_session_data *sd, int achievement_id)
 	return true;
 }
 
+/**
+ * Lambda function that checks for completed achievements
+ * @param sd: Player data
+ * @param achievement_id: Achievement to check if it's complete
+ * @return True on completed, false if not
+ */
+static bool achievement_done(struct map_session_data *sd, int achievement_id) {
+	auto &adb = achievements[achievement_id];
+	struct achievement *ach_data = sd->achievement_data.achievements;
+
+	return (std::find_if(adb->dependent_ids.begin(), adb->dependent_ids.end(), [&achievement_id, &ach_data]
+	(const int &d) {
+		return (ach_data[d].achievement_id == achievement_id && ach_data[d].completed > 0);
+	}) != adb->dependent_ids.end());
+}
+
 /**
  * Checks to see if an achievement has a dependent, and if so, checks if that dependent is complete
  * @param sd: Player data
@@ -163,31 +188,18 @@ bool achievement_remove(struct map_session_data *sd, int achievement_id)
  */
 bool achievement_check_dependent(struct map_session_data *sd, int achievement_id)
 {
-	struct achievement_db *adb = &achievement_dummy;
-
 	nullpo_retr(false, sd);
 
-	adb = achievement_search(achievement_id);
-
-	if (adb == &achievement_dummy)
+	if (!achievement_exists(achievement_id))
 		return false;
 
+	auto &adb = achievements[achievement_id];
+
 	// Check if the achievement has a dependent
 	// If so, then do a check on all dependents to see if they're complete
-	if (adb->dependent_count) {
-		int i;
-
-		for (i = 0; i < adb->dependent_count; i++) {
-			struct achievement_db *adb_dep = achievement_search(adb->dependents[i].achievement_id);
-			int j;
-
-			if (adb_dep == &achievement_dummy)
-				return false;
-
-			ARR_FIND(0, sd->achievement_data.count, j, sd->achievement_data.achievements[j].achievement_id == adb->dependents[i].achievement_id && sd->achievement_data.achievements[j].completed > 0);
-			if (j == sd->achievement_data.count)
-				return false; // One of the dependent is not complete!
-		}
+	for (int i = 0; i < adb->dependent_ids.size(); i++) {
+		if (!achievement_done(sd, adb->dependent_ids[i]))
+			return false; // One of the dependent is not complete!
 	}
 
 	return true;
@@ -195,24 +207,21 @@ bool achievement_check_dependent(struct map_session_data *sd, int achievement_id
 
 /**
  * Check achievements that only have dependents and no other requirements
+ * @param sd: Player to update
+ * @param sd: Achievement to compare for completed dependents
  * @return True if successful, false if not
  */
-static int achievement_check_groups(DBKey key, DBData *data, va_list ap)
+static int achievement_check_groups(struct map_session_data *sd, struct s_achievement_db *ad)
 {
-	struct achievement_db *ad;
-	struct map_session_data *sd;
 	int i;
 
-	ad = (struct achievement_db *)db_data2ptr(data);
-	sd = va_arg(ap, struct map_session_data *);
-
-	if (ad == &achievement_dummy || sd == NULL)
+	if (ad == NULL || sd == NULL)
 		return 0;
 
 	if (ad->group != AG_BATTLE && ad->group != AG_TAMING && ad->group != AG_ADVENTURE)
 		return 0;
 
-	if (ad->dependent_count == 0 || ad->condition)
+	if (ad->dependent_ids.size() == 0 || ad->condition)
 		return 0;
 
 	ARR_FIND(0, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == ad->achievement_id);
@@ -235,16 +244,15 @@ static int achievement_check_groups(DBKey key, DBData *data, va_list ap)
  */
 bool achievement_update_achievement(struct map_session_data *sd, int achievement_id, bool complete)
 {
-	struct achievement_db *adb = &achievement_dummy;
 	int i;
 
 	nullpo_retr(false, sd);
 
-	adb = achievement_search(achievement_id);
-
-	if (adb == &achievement_dummy)
+	if (!achievement_exists(achievement_id))
 		return false;
 
+	auto &adb = achievements[achievement_id];
+
 	ARR_FIND(0, sd->achievement_data.incompleteCount, i, sd->achievement_data.achievements[i].achievement_id == achievement_id);
 	if (i == sd->achievement_data.incompleteCount)
 		return false;
@@ -254,13 +262,13 @@ bool achievement_update_achievement(struct map_session_data *sd, int achievement
 
 	// Finally we send the updated achievement to the client
 	if (complete) {
-		if (adb->target_count) { // Make sure all the objective targets are at their respective total requirement
+		if (adb->targets.size()) { // Make sure all the objective targets are at their respective total requirement
 			int k;
 
-			for (k = 0; k < adb->target_count; k++)
+			for (k = 0; k < adb->targets.size(); k++)
 				sd->achievement_data.achievements[i].count[k] = adb->targets[k].count;
 
-			for (k = 1; k < adb->dependent_count; k++) {
+			for (k = 1; k < adb->dependent_ids.size(); k++) {
 				sd->achievement_data.achievements[i].count[k] = max(1, sd->achievement_data.achievements[i].count[k]);
 			}
 		}
@@ -277,7 +285,8 @@ bool achievement_update_achievement(struct map_session_data *sd, int achievement
 
 		achievement_level(sd, true); // Re-calculate achievement level
 		// Check dependents
-		achievement_db->foreach(achievement_db, achievement_check_groups, sd);
+		for (auto &ach : achievements)
+			achievement_check_groups(sd, ach.second.get());
 		ARR_FIND(sd->achievement_data.incompleteCount, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == achievement_id); // Look for the index again, the position most likely changed
 	}
 
@@ -299,35 +308,32 @@ bool achievement_update_achievement(struct map_session_data *sd, int achievement
  */
 void achievement_get_reward(struct map_session_data *sd, int achievement_id, time_t rewarded)
 {
-	struct achievement_db *adb = achievement_search(achievement_id);
 	int i;
 
 	nullpo_retv(sd);
 
-	if( rewarded == 0 ){
-		clif_achievement_reward_ack(sd->fd, 0, achievement_id);
+	if (!achievement_exists(achievement_id)) {
+		ShowError("achievement_reward: Inter server sent a reward claim for achievement %d not found in DB.\n", achievement_id);
 		return;
 	}
 
-	if (adb == &achievement_dummy) {
-		ShowError("achievement_reward: Inter server sent a reward claim for achievement %d not found in DB.\n", achievement_id);
+	auto &adb = achievements[achievement_id];
+
+	if (rewarded == 0) {
+		clif_achievement_reward_ack(sd->fd, 0, achievement_id);
 		return;
 	}
 
 	ARR_FIND(0, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == achievement_id);
-
-	if (i == sd->achievement_data.count) {
+	if (i == sd->achievement_data.count)
 		return;
-	}
 
 	// Only update in the cache, db was updated already
 	sd->achievement_data.achievements[i].rewarded = rewarded;
 
 	run_script(adb->rewards.script, 0, sd->bl.id, fake_nd->bl.id);
 	if (adb->rewards.title_id) {
-		RECREATE(sd->titles, int, sd->titleCount + 1);
-		sd->titles[sd->titleCount] = adb->rewards.title_id;
-		sd->titleCount++;
+		sd->titles.push_back(adb->rewards.title_id);
 		sd->achievement_data.sendlist = true;
 	}
 
@@ -343,16 +349,17 @@ void achievement_get_reward(struct map_session_data *sd, int achievement_id, tim
 void achievement_check_reward(struct map_session_data *sd, int achievement_id)
 {
 	int i;
-	struct achievement_db *adb = achievement_search(achievement_id);
 
 	nullpo_retv(sd);
 
-	if (adb == &achievement_dummy) {
+	if (!achievement_exists(achievement_id)) {
 		ShowError("achievement_reward: Trying to reward achievement %d not found in DB.\n", achievement_id);
 		clif_achievement_reward_ack(sd->fd, 0, achievement_id);
 		return;
 	}
 
+	auto &adb = achievements[achievement_id];
+
 	ARR_FIND(0, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == achievement_id);
 	if (i == sd->achievement_data.count) {
 		clif_achievement_reward_ack(sd->fd, 0, achievement_id);
@@ -364,7 +371,7 @@ void achievement_check_reward(struct map_session_data *sd, int achievement_id)
 		return;
 	}
 
-	if( !intif_achievement_reward(sd,adb) ){
+	if (!intif_achievement_reward(sd, adb.get())) {
 		clif_achievement_reward_ack(sd->fd, 0, achievement_id);
 	}
 }
@@ -378,39 +385,30 @@ void achievement_get_titles(uint32 char_id)
 	struct map_session_data *sd = map_charid2sd(char_id);
 
 	if (sd) {
-		sd->titles = NULL;
-		sd->titleCount = 0;
+		sd->titles.clear();
 
 		if (sd->achievement_data.count) {
-			int i;
+			for (int i = 0; i < sd->achievement_data.count; i++) {
+				if (!achievement_exists(sd->achievement_data.achievements[i].achievement_id))
+					continue;
 
-			for (i = 0; i < sd->achievement_data.count; i++) {
-				struct achievement_db *adb = achievement_search(sd->achievement_data.achievements[i].achievement_id);
+				auto &adb = achievements[sd->achievement_data.achievements[i].achievement_id];
 
-				if (adb && adb->rewards.title_id && sd->achievement_data.achievements[i].completed > 0) { // If the achievement has a title and is complete, give it to the player
-					RECREATE(sd->titles, int, sd->titleCount + 1);
-					sd->titles[sd->titleCount] = adb->rewards.title_id;
-					sd->titleCount++;
-				}
+				if (adb && adb->rewards.title_id && sd->achievement_data.achievements[i].completed > 0) // If the achievement has a title and is complete, give it to the player
+					sd->titles.push_back(adb->rewards.title_id);
 			}
 		}
 	}
 }
 
 /**
- * Frees the player's data for achievements and titles
+ * Frees the player's data for achievements
  * @param sd: Player's session
  */
 void achievement_free(struct map_session_data *sd)
 {
 	nullpo_retv(sd);
 
-	if (sd->titleCount) {
-		aFree(sd->titles);
-		sd->titles = NULL;
-		sd->titleCount = 0;
-	}
-
 	if (sd->achievement_data.count) {
 		aFree(sd->achievement_data.achievements);
 		sd->achievement_data.achievements = NULL;
@@ -456,6 +454,7 @@ int achievement_check_progress(struct map_session_data *sd, int achievement_id,
  * Calculate a player's achievement level
  * @param sd: Player to check achievement level
  * @param flag: If the call should attempt to give the AG_GOAL_ACHIEVE achievement
+ * @return Player's achievement level or 0 on failure
  */
 int *achievement_level(struct map_session_data *sd, bool flag)
 {
@@ -510,21 +509,18 @@ int *achievement_level(struct map_session_data *sd, bool flag)
 
 /**
  * Update achievement objectives.
- * @see DBApply
+ * @param sd: Player to update
+ * @param ad: Achievement data to compare for completion
+ * @param group: Achievement group to update
+ * @param update_count: Objective values player has
+ * @return 1 on success and false on failure
  */
-static int achievement_update_objectives(DBKey key, DBData *data, va_list ap)
+static int achievement_update_objectives(struct map_session_data *sd, std::shared_ptr<struct s_achievement_db> ad, enum e_achievement_group group, const std::array<int,MAX_ACHIEVEMENT_OBJECTIVES> &update_count)
 {
-	struct achievement_db *ad;
-	struct map_session_data *sd;
-	enum e_achievement_group group;
 	struct achievement *entry = NULL;
 	bool isNew = false, changed = false, complete = false;
-	int i, k = 0, objective_count[MAX_ACHIEVEMENT_OBJECTIVES], update_count[MAX_ACHIEVEMENT_OBJECTIVES];
-
-	ad = (struct achievement_db *)db_data2ptr(data);
-	sd = va_arg(ap, struct map_session_data *);
-	group = (enum e_achievement_group)va_arg(ap, int);
-	memcpy(update_count, (int *)va_arg(ap, int *), sizeof(update_count));
+	int i, k = 0;
+	std::array<int,MAX_ACHIEVEMENT_OBJECTIVES> objective_count;
 
 	if (ad == NULL || sd == NULL)
 		return 0;
@@ -535,7 +531,7 @@ static int achievement_update_objectives(DBKey key, DBData *data, va_list ap)
 	if (group != ad->group)
 		return 0;
 
-	memset(objective_count, 0, sizeof(objective_count)); // Current objectives count
+	objective_count.fill(0); // Current objectives count
 
 	ARR_FIND(0, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == ad->achievement_id);
 	if (i == sd->achievement_data.count) { // Achievement isn't in player's log
@@ -548,7 +544,7 @@ static int achievement_update_objectives(DBKey key, DBData *data, va_list ap)
 		if (entry->completed > 0) // Player has completed the achievement
 			return 0;
 
-		memcpy(objective_count, entry->count, sizeof(objective_count));
+		memcpy(objective_count.data(), entry->count, sizeof(objective_count));
 	}
 
 	switch (group) {
@@ -572,7 +568,7 @@ static int achievement_update_objectives(DBKey key, DBData *data, va_list ap)
 				changed = true;
 			}
 
-			if (!ad->condition || achievement_check_condition(ad->condition, sd, update_count)) {
+			if (!ad->condition || achievement_check_condition(ad->condition, sd, update_count.data())) {
 				changed = true;
 				complete = true;
 			}
@@ -586,24 +582,24 @@ static int achievement_update_objectives(DBKey key, DBData *data, va_list ap)
 			}
 			break;
 		case AG_CHAT:
-			if (!ad->target_count)
+			if (!ad->targets.size())
 				break;
 
-			if (ad->condition && !achievement_check_condition(ad->condition, sd, update_count)) // Parameters weren't met
+			if (ad->condition && !achievement_check_condition(ad->condition, sd, update_count.data())) // Parameters weren't met
 				break;
 
 			if (ad->mapindex > -1 && sd->bl.m != ad->mapindex)
 				break;
 
-			for (i = 0; i < ad->target_count; i++) {
+			for (i = 0; i < ad->targets.size(); i++) {
 				if (objective_count[i] < ad->targets[i].count)
 					objective_count[i] += update_count[0];
 			}
 
 			changed = true;
 
-			ARR_FIND(0, ad->target_count, k, objective_count[k] < ad->targets[k].count);
-			if (k == ad->target_count)
+			ARR_FIND(0, ad->targets.size(), k, objective_count[k] < ad->targets[k].count);
+			if (k == ad->targets.size())
 				complete = true;
 
 			if (isNew) {
@@ -613,19 +609,22 @@ static int achievement_update_objectives(DBKey key, DBData *data, va_list ap)
 			break;
 		case AG_BATTLE:
 		case AG_TAMING:
-			ARR_FIND(0, ad->target_count, k, ad->targets[k].mob == update_count[0]);
-			if (k == ad->target_count)
+			auto it = std::find_if(ad->targets.begin(), ad->targets.end(), [&update_count]
+			(const achievement_target &curTarget) {
+				return curTarget.mob == update_count[0];
+			});
+			if (it == ad->targets.end())
 				break; // Mob wasn't found
 
-			for (k = 0; k < ad->target_count; k++) {
+			for (k = 0; k < ad->targets.size(); k++) {
 				if (ad->targets[k].mob == update_count[0] && objective_count[k] < ad->targets[k].count) {
 					objective_count[k]++;
 					changed = true;
 				}
 			}
 
-			ARR_FIND(0, ad->target_count, k, objective_count[k] < ad->targets[k].count);
-			if (k == ad->target_count)
+			ARR_FIND(0, ad->targets.size(), k, objective_count[k] < ad->targets[k].count);
+			if (k == ad->targets.size())
 				complete = true;
 
 			if (isNew) {
@@ -636,7 +635,7 @@ static int achievement_update_objectives(DBKey key, DBData *data, va_list ap)
 	}
 
 	if (changed) {
-		memcpy(entry->count, objective_count, sizeof(objective_count));
+		memcpy(entry->count, objective_count.data(), sizeof(objective_count));
 		achievement_update_achievement(sd, ad->achievement_id, complete);
 	}
 
@@ -654,12 +653,13 @@ void achievement_update_objective(struct map_session_data *sd, enum e_achievemen
 {
 	if (sd) {
 		va_list ap;
-		int i, count[MAX_ACHIEVEMENT_OBJECTIVES];
+		int i;
+		std::array<int,MAX_ACHIEVEMENT_OBJECTIVES> count;
 
 		if (!battle_config.feature_achievement)
 			return;
 
-		memset(count, 0, sizeof(count)); // Clear out array before setting values
+		count.fill(0); // Clear out array before setting values
 
 		va_start(ap, arg_count);
 		for (i = 0; i < arg_count; i++)
@@ -672,7 +672,8 @@ void achievement_update_objective(struct map_session_data *sd, enum e_achievemen
 				// These have no objective use right now.
 				break;
 			default:
-				achievement_db->foreach(achievement_db, achievement_update_objectives, sd, (int)group, count);
+				for (auto &ach : achievements)
+					achievement_update_objectives(sd, ach.second, group, count);
 				break;
 		}
 	}
@@ -697,7 +698,7 @@ static void disp_error_message2(const char *mes,const char *pos,int report)
  * @param count: Script arguments
  * @return The result of the condition.
  */
-long long achievement_check_condition(struct av_condition *condition, struct map_session_data *sd, int *count)
+long long achievement_check_condition(std::shared_ptr<struct av_condition> condition, struct map_session_data *sd, const int *count)
 {
 	long long left = 0;
 	long long right = 0;
@@ -788,6 +789,11 @@ long long achievement_check_condition(struct av_condition *condition, struct map
 	return false;
 }
 
+/**
+ * Skips a word. A word consists of undercores and/or alphanumeric characters, and valid variable prefixes/postfixes.
+ * @param p: Word
+ * @return Next word
+ */
 static const char *skip_word(const char *p)
 {
 	while (ISALNUM(*p) || *p == '_')
@@ -799,7 +805,13 @@ static const char *skip_word(const char *p)
 	return p;
 }
 
-const char *av_parse_simpleexpr(const char *p, struct av_condition *parent)
+/**
+ * Analyze an achievement's condition script
+ * @param p: Word
+ * @param parent: Parent node
+ * @return Word
+ */
+const char *av_parse_simpleexpr(const char *p, std::shared_ptr<struct av_condition> parent)
 {
 	long long i;
 
@@ -834,7 +846,6 @@ const char *av_parse_simpleexpr(const char *p, struct av_condition *parent)
 		p = np;
 	} else {
 		int v, len;
-		char * word;
 
 		if (skip_word(p) == p)
 			disp_error_message("av_parse_simpleexpr: unexpected character.", p);
@@ -844,13 +855,13 @@ const char *av_parse_simpleexpr(const char *p, struct av_condition *parent)
 		if (len == 0)
 			disp_error_message("av_parse_simpleexpr: invalid word. A word consists of undercores and/or alphanumeric characters.", p);
 
-		word = (char*)aMalloc(len + 1);
-		memcpy(word, p, len);
+		std::unique_ptr<char[]> word(new char[len + 1]);
+		memcpy(word.get(), p, len);
 		word[len] = 0;
 
-		if (script_get_parameter(word, &v))
+		if (script_get_parameter((const char*)&word[0], &v))
 			parent->op = C_PARAM;
-		else if (script_get_constant(word, &v)) {
+		else if (script_get_constant(&word[0], &v)) {
 			if (word[0] == 'b' && ISUPPER(word[1])) // Consider b* variables as parameters (because they... are?)
 				parent->op = C_PARAM;
 			else
@@ -858,14 +869,12 @@ const char *av_parse_simpleexpr(const char *p, struct av_condition *parent)
 		} else {
 			if (word[0] == 'A' && word[1] == 'R' && word[2] == 'G' && ISDIGIT(word[3])) { // Special constants used to set temporary variables
 				parent->op = C_ARG;
-				v = atoi(word + 3);
+				v = atoi(&word[0] + 3);
 			} else {
-				aFree(word);
 				disp_error_message("av_parse_simpleexpr: invalid constant.", p);
 			}
 		}
 
-		aFree(word);
 		parent->value = v;
 		p = skip_word(p);
 	}
@@ -873,13 +882,19 @@ const char *av_parse_simpleexpr(const char *p, struct av_condition *parent)
 	return p;
 }
 
-const char* av_parse_subexpr(const char* p, int limit, struct av_condition *parent)
+/**
+ * Analysis of an achievement's expression
+ * @param p: Word
+ * @param parent: Parent node
+ * @return Word
+ */
+const char *av_parse_subexpr(const char* p, int limit, std::shared_ptr<struct av_condition> parent)
 {
 	int op, opl, len;
 
 	p = skip_space(p);
 
-	CREATE(parent->left, struct av_condition, 1);
+	parent->left.reset(new av_condition());
 
 	if ((op = C_NEG, *p == '-') || (op = C_LNOT, *p == '!') || (op = C_NOT, *p == '~')) { // Unary - ! ~ operators
 		p = av_parse_subexpr(p + 1, 11, parent->left);
@@ -911,30 +926,26 @@ const char* av_parse_subexpr(const char* p, int limit, struct av_condition *pare
 		p += len;
 
 		if (parent->right) { // Chain conditions
-			struct av_condition *condition = NULL;
-			CREATE(condition, struct av_condition, 1);
+			std::shared_ptr<struct av_condition> condition(new struct av_condition());
+
 			condition->op = parent->op;
 			condition->left = parent->left;
 			condition->right = parent->right;
 			parent->left = condition;
-			parent->right = NULL;
+			parent->right.reset();
 		}
 
-		CREATE(parent->right, struct av_condition, 1);
+		parent->right.reset(new av_condition());
 		p = av_parse_subexpr(p, opl, parent->right);
 		parent->op = op;
 		p = skip_space(p);
 	}
 
-	if (parent->op == C_NOP && parent->right == NULL) { // Move the node up
-		struct av_condition *temp = parent->left;
-
+	if (parent->op == C_NOP && !parent->right) { // Move the node up
 		parent->right = parent->left->right;
 		parent->op = parent->left->op;
 		parent->value = parent->left->value;
 		parent->left = parent->left->left;
-
-		aFree(temp);
 	}
 
 	return p;
@@ -947,16 +958,15 @@ const char* av_parse_subexpr(const char* p, int limit, struct av_condition *pare
  * @param line: The current achievement line number.
  * @return The parsed achievement condition.
  */
-struct av_condition *parse_condition(const char *p, const char *file, int line)
+std::shared_ptr<struct av_condition> parse_condition(const char *p, const char *file, int line)
 {
-	struct av_condition *condition = NULL;
+	std::shared_ptr<struct av_condition> condition;
 
 	if (setjmp(av_error_jump) != 0) {
 		if (av_error_report)
 			script_error(p,file,line,av_error_msg,av_error_pos);
 		aFree(av_error_msg);
-		if (condition)
-			achievement_script_free(condition);
+		condition.reset();
 		return NULL;
 	}
 
@@ -965,152 +975,198 @@ struct av_condition *parse_condition(const char *p, const char *file, int line)
 			disp_error_message("parse_condition: unexpected character.", p);
 	}
 
-	condition = (struct av_condition *) aCalloc(1, sizeof(struct av_condition));
+	condition.reset(new av_condition());
 	av_parse_subexpr(p, -1, condition);
 
 	return condition;
 }
 
+static void yaml_invalid_warning(const char* fmt, const YAML::Node &node, const std::string &file) {
+	YAML::Emitter out;
+	out << node;
+	ShowWarning(fmt, file.c_str());
+	ShowMessage("%s\n", out.c_str());
+}
+
 /**
  * Reads and parses an entry from the achievement_db.
- * @param wrapper: The YAML wrapper containing the entry.
+ * @param node: YAML node containing the entry.
  * @param n: The sequential index of the current entry.
  * @param source: The source YAML file.
- * @return The parsed achievement entry or NULL in case of error.
+ * @return True on successful parse or false otherwise
  */
-struct achievement_db *achievement_read_db_sub(yamlwrapper *wrapper, int n, const char *source)
+bool achievement_read_db_sub(const YAML::Node &node, int n, const std::string &source)
 {
-	struct achievement_db *entry = NULL;
-	yamlwrapper *t = NULL;
-	yamliterator *it;
 	enum e_achievement_group group = AG_NONE;
-	int score = 0, achievement_id = 0;
-	char *group_char = NULL, *name = NULL, *condition = NULL, *mapname = NULL;
+	int achievement_id = 0;
+	std::string group_char, name, condition, mapname;
+	bool existing = false;
 
-	if (!yaml_node_is_defined(wrapper, "ID")) {
-		ShowWarning("achievement_read_db_sub: Missing ID in \"%s\", entry #%d, skipping.\n", source, n);
-		return NULL;
-	} else
-		achievement_id = yaml_get_int(wrapper, "ID");
+	if (!node["ID"]) {
+		yaml_invalid_warning("achievement_read_db_sub: Missing ID field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source);
+		return false;
+	}
+	try {
+		achievement_id = node["ID"].as<unsigned int>();
+	} catch (...) {
+		yaml_invalid_warning("achievement_read_db_sub: Achievement definition with invalid ID field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source);
+		return false;
+	}
 	if (achievement_id < 1 || achievement_id > INT_MAX) {
-		ShowWarning("achievement_read_db_sub: Invalid achievement ID %d in \"%s\", entry #%d (min: 1, max: %d), skipping.\n", achievement_id, source, n, INT_MAX);
-		return NULL;
+		ShowWarning("achievement_read_db_sub: Invalid achievement ID %d in \"%s\", entry #%d (min: 1, max: %d), skipping.\n", achievement_id, source.c_str(), n, INT_MAX);
+		return false;
 	}
 
-	if (!yaml_node_is_defined(wrapper, "Group")) {
-		ShowWarning("achievement_read_db_sub: Missing group for achievement %d in \"%s\", skipping.\n", achievement_id, source);
-		return NULL;
-	} else
-		group_char = yaml_get_c_string(wrapper, "Group");
-	if (!script_get_constant(group_char, (int *)&group)) {
-		ShowWarning("achievement_read_db_sub: Invalid group %s for achievement %d in \"%s\", skipping.\n", group_char, achievement_id, source);
-		return NULL;
+	if (achievement_exists(achievement_id)) {
+		if (source.find("import") != std::string::npos) // Import file read-in, free previous value and store new value
+			existing = true;
+		else { // Normal file read-in
+			ShowWarning("achievement_read_db: Duplicate achievement %d.\n", achievement_id);
+			return false;
+		}
 	}
-	aFree(group_char);
-
-	if (!yaml_node_is_defined(wrapper, "Name")) {
-		ShowWarning("achievement_read_db_sub: Missing achievement name for achievement %d in \"%s\", skipping.\n", name, achievement_id, source);
-		return NULL;
-	} else
-		name = yaml_get_c_string(wrapper, "Name");
 
-	CREATE(entry, struct achievement_db, 1);
+	if(!existing)
+		achievements[achievement_id] = std::make_shared<s_achievement_db>();
+	auto &entry = achievements[achievement_id];
 	entry->achievement_id = achievement_id;
+
+	if (!node["Group"]) {
+		yaml_invalid_warning("achievement_read_db_sub: Missing group field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source);
+		return false;
+	}
+	try {
+		group_char = node["Group"].as<std::string>();
+	} catch (...) {
+		yaml_invalid_warning("achievement_read_db_sub: Achievement definition with invalid group field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source);
+		return false;
+	}
+	if (!script_get_constant(group_char.c_str(), (int *)&group)) {
+		ShowWarning("achievement_read_db_sub: Invalid group %s for achievement %d in \"%s\", skipping.\n", group_char.c_str(), achievement_id, source.c_str());
+		return false;
+	}
+
+	if (!node["Name"]) {
+		ShowWarning("achievement_read_db_sub: Missing achievement name for achievement %d in \"%s\", skipping.\n", achievement_id, source.c_str());
+		return false;
+	}
+	try {
+		name = node["Name"].as<std::string>();
+	} catch (...) {
+		yaml_invalid_warning("achievement_read_db_sub: Achievement definition with invalid name field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source);
+		return false;
+	}
+	
 	entry->group = group;
-	safestrncpy(entry->name, name, sizeof(entry->name));
-	aFree(name);
+	entry->name = name;
 	entry->mapindex = -1;
 
-	if (yaml_node_is_defined(wrapper, "Target") && (t = yaml_get_subnode(wrapper, "Target")) && (it = yaml_get_iterator(t)) && yaml_iterator_is_valid(it)) {
-		yamlwrapper *tt = NULL;
+	if (node["Target"]) {
+		try {
+			const YAML::Node &target_list = node["Target"];
 
-		for (tt = yaml_iterator_first(it); yaml_iterator_has_next(it) && entry->target_count < MAX_ACHIEVEMENT_OBJECTIVES; tt = yaml_iterator_next(it)) {
-			int mobid = 0, count = 0;
+			for (auto targetit = target_list.begin(); targetit != target_list.end() && target_list.size() < MAX_ACHIEVEMENT_OBJECTIVES; ++targetit) {
+				const YAML::Node &target = *targetit;
+				int mobid = 0, count = 0;
 
-			if (yaml_node_is_defined(tt, "MobID") && (mobid = yaml_get_int(tt, "MobID")) && mob_db(mobid) == NULL) { // The mob ID field is not required
-				ShowError("achievement_read_db_sub: Invalid mob ID %d for achievement %d in \"%s\", skipping.\n", mobid, achievement_id, source);
-				continue;
-			}
-			if (yaml_node_is_defined(tt, "Count") && (!(count = yaml_get_int(tt, "Count")) || count <= 0)) {
-				ShowError("achievement_read_db_sub: Invalid count %d for achievement %d in \"%s\", skipping.\n", count, achievement_id, source);
-				continue;
-			}
-			if (mobid && group == AG_BATTLE && !idb_exists(achievementmobs_db, mobid)) {
-				struct achievement_mob *entrymob = NULL;
+				if (target["MobID"] && (mobid = target["MobID"].as<int>()) && mob_db(mobid) == NULL) { // The mob ID field is not required
+					ShowError("achievement_read_db_sub: Invalid mob ID %d for achievement %d in \"%s\", skipping.\n", mobid, achievement_id, source.c_str());
+					continue;
+				}
+				if (target["Count"] && (!(count = target["Count"].as<int>()) || count <= 0)) {
+					ShowError("achievement_read_db_sub: Invalid count %d for achievement %d in \"%s\", skipping.\n", count, achievement_id, source.c_str());
+					continue;
+				}
+				if (mobid && group == AG_BATTLE && !achievement_mobexists(mobid))
+					achievement_mobs.push_back(mobid);
 
-				CREATE(entrymob, struct achievement_mob, 1);
-				idb_put(achievementmobs_db, mobid, entrymob);
+				entry->targets.push_back({ mobid, count });
 			}
-
-			RECREATE(entry->targets, struct achievement_target, entry->target_count + 1);
-			entry->targets[entry->target_count].mob = mobid;
-			entry->targets[entry->target_count].count = count;
-			entry->target_count++;
-			yaml_destroy_wrapper(tt);
+		} catch (...) {
+			yaml_invalid_warning("achievement_read_db_sub: Achievement definition with invalid target field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source);
+			return false;
 		}
-		yaml_iterator_destroy(it);
 	}
 
-	if (yaml_node_is_defined(wrapper, "Condition") && (condition = yaml_get_c_string(wrapper, "Condition"))){
-		entry->condition = parse_condition(condition, source, n);
-		aFree(condition);
+	if (node["Condition"]) {
+		try {
+			condition = node["Condition"].as<std::string>();
+		} catch (...) {
+			yaml_invalid_warning("achievement_read_db_sub: Achievement definition with invalid condition field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source);
+			return false;
+		}
+		entry->condition = parse_condition(condition.c_str(), source.c_str(), n);
 	}
 
-	if (yaml_node_is_defined(wrapper, "Map") && (mapname = yaml_get_c_string(wrapper, "Map"))) {
+	if (node["Map"]) {
+		try {
+			mapname = node["Map"].as<std::string>();
+		} catch (...) {
+			yaml_invalid_warning("achievement_read_db_sub: Achievement definition with invalid map field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source);
+			return false;
+		}
 		if (group != AG_CHAT)
-			ShowWarning("achievement_read_db_sub: The map argument can only be used with the group AG_CHATTING (achievement %d in \"%s\"), skipping.\n", achievement_id, source);
+			ShowWarning("achievement_read_db_sub: The map argument can only be used with the group AG_CHATTING (achievement %d in \"%s\"), skipping.\n", achievement_id, source.c_str());
 		else {
-			entry->mapindex = map_mapname2mapid(mapname);
+			entry->mapindex = map_mapname2mapid(mapname.c_str());
 
 			if (entry->mapindex == -1)
-				ShowWarning("achievement_read_db_sub: Invalid map name %s for achievement %d in \"%s\".\n", mapname, achievement_id, source);
+				ShowWarning("achievement_read_db_sub: Invalid map name %s for achievement %d in \"%s\".\n", mapname.c_str(), achievement_id, source.c_str());
 		}
-		aFree(mapname);
 	}
 
-	if (yaml_node_is_defined(wrapper, "Dependent") && (t = yaml_get_subnode(wrapper, "Dependent")) && (it = yaml_get_iterator(t))) {
-		if (yaml_iterator_is_valid(it)) {
-			yamlwrapper *tt = NULL;
-
-			for (tt = yaml_iterator_first(it); yaml_iterator_has_next(it) && entry->dependent_count < MAX_ACHIEVEMENT_DEPENDENTS; tt = yaml_iterator_next(it)) {
-				RECREATE(entry->dependents, struct achievement_dependent, entry->dependent_count + 1);
-				entry->dependents[entry->dependent_count].achievement_id = yaml_as_int(tt);
-				entry->dependent_count++;
-				yaml_destroy_wrapper(tt);
-			}
-			yaml_iterator_destroy(it);
-		} else
-			ShowWarning("achievement_read_db_sub: Invalid dependent format for achievement %d in \"%s\".\n", achievement_id, source);
+	if (node["Dependent"]) {
+		try {
+			const YAML::Node dependent_list = node["Dependent"];
+
+			if (dependent_list.IsSequence()) {
+				for (uint8 i = 0; i < dependent_list.size() && dependent_list.size() < MAX_ACHIEVEMENT_DEPENDENTS; i++)
+					entry->dependent_ids.push_back(dependent_list[i].as<int>());
+			} else
+				ShowWarning("achievement_read_db_sub: Invalid dependent format for achievement %d in \"%s\".\n", achievement_id, source.c_str());
+		} catch (...) {
+			yaml_invalid_warning("achievement_read_db_sub: Achievement definition with invalid dependent field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source);
+			return false;
+		}
 	}
 
-	if (yaml_node_is_defined(wrapper, "Reward") && (t = yaml_get_subnode(wrapper, "Reward"))) {
-		char *script_char = NULL;
-		int nameid = 0, amount = 0, titleid = 0;
-
-		if (yaml_node_is_defined(t, "ItemID") && (nameid = yaml_get_int(t, "ItemID"))) {
-			if (itemdb_exists(nameid)) {
-				entry->rewards.nameid = nameid;
-				entry->rewards.amount = 1; // Default the amount to 1
-			} else if (nameid && !itemdb_exists(nameid)) {
-				ShowWarning("achievement_read_db_sub: Invalid reward item ID %hu for achievement %d in \"%s\". Setting to 0.\n", nameid, achievement_id, source);
-				entry->rewards.nameid = nameid = 0;
-			}
+	if (node["Reward"]) {
+		try {
+			const YAML::Node reward_list = node["Reward"];
+			int nameid = 0, amount = 0, titleid = 0;
+
+			if (reward_list["ItemID"] && (nameid = reward_list["ItemID"].as<unsigned short>())) {
+				if (itemdb_exists(nameid)) {
+					entry->rewards.nameid = nameid;
+					entry->rewards.amount = 1; // Default the amount to 1
+				} else if (nameid && !itemdb_exists(nameid)) {
+					ShowWarning("achievement_read_db_sub: Invalid reward item ID %hu for achievement %d in \"%s\". Setting to 0.\n", nameid, achievement_id, source.c_str());
+					entry->rewards.nameid = nameid = 0;
+				}
 
-			if (yaml_node_is_defined(t, "Amount") && (amount = yaml_get_int(t, "Amount")) && amount > 0 && nameid)
-				entry->rewards.amount = amount;
-		}
-		if (yaml_node_is_defined(t, "Script") && (script_char = yaml_get_c_string(t, "Script"))){
-			entry->rewards.script = parse_script(script_char, source, achievement_id, SCRIPT_IGNORE_EXTERNAL_BRACKETS);
-			aFree(script_char);
+				if (reward_list["Amount"] && (amount = reward_list["Amount"].as<unsigned short>()) && amount > 0 && nameid > 0)
+					entry->rewards.amount = amount;
+			}
+			if (reward_list["Script"])
+				entry->rewards.script = parse_script(reward_list["Script"].as<std::string>().c_str(), source.c_str(), achievement_id, SCRIPT_IGNORE_EXTERNAL_BRACKETS);
+			if (reward_list["TitleID"] && (titleid = reward_list["TitleID"].as<int>()) && titleid > 0)
+				entry->rewards.title_id = titleid;
+		} catch (...) {
+			yaml_invalid_warning("achievement_read_db_sub: Achievement definition with invalid target field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source);
+			return false;
 		}
-		if (yaml_node_is_defined(t, "TitleID") && (titleid = yaml_get_int(t, "TitleID")) && titleid > 0)
-			entry->rewards.title_id = titleid;
 	}
 
-	if ((score = yaml_get_int(wrapper, "Score")) && score > 0)
-		entry->score = score;
+	if (node["Score"]) {
+		try {
+			entry->score = node["Score"].as<int>();
+		} catch (...) {
+			yaml_invalid_warning("achievement_read_db_sub: Achievement definition with invalid score field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source);
+			return false;
+		}
+	}
 
-	return entry;
+	return true;
 }
 
 /**
@@ -1118,162 +1174,112 @@ struct achievement_db *achievement_read_db_sub(yamlwrapper *wrapper, int n, cons
  */
 void achievement_read_db(void)
 {
-	yamlwrapper *adb = NULL, *adb_sub = NULL;
-	yamliterator *it;
-	int i = 0;
-	const char *dbsubpath[] = {
-		"",
-		"/" DBIMPORT "/",
-		//add other path here
-	};
-
-	for (i = 0; i < ARRAYLENGTH(dbsubpath); i++) {
-		char filepath[256];
-		int count = 0;
+	std::vector<std::string> directories = { std::string(db_path) + "/" + std::string(DBPATH),  std::string(db_path) + "/" + std::string(DBIMPORT) + "/" };
+	static const std::string file_name("achievement_db.yml");
 
-		if (!i)
-			sprintf(filepath, "%s/%s%s%s", db_path, DBPATH, dbsubpath[i], "achievement_db.yml");
-		else
-			sprintf(filepath, "%s%s%s", db_path, dbsubpath[i], "achievement_db.yml");
+	for (auto &directory : directories) {
+		std::string current_file = directory + file_name;
+		YAML::Node config;
+		int count = 0;
 
-		if ((adb = yaml_load_file(filepath)) == NULL) {
-			ShowError("Failed to read '%s'.\n", filepath);
-			continue;
+		try {
+			config = YAML::LoadFile(current_file);
+		} catch (...) {
+			ShowError("Cannot read '" CL_WHITE "%s" CL_RESET "'.\n", current_file.c_str());
+			return;
 		}
 
-		if (!yaml_node_is_defined(adb, "Achievements"))
-			continue; // Skip if base structure isn't defined
-		adb_sub = yaml_get_subnode(adb, "Achievements");
-		it = yaml_get_iterator(adb_sub);
-		if (yaml_iterator_is_valid(it)) {
-			yamlwrapper *id = NULL;
-
-			for (id = yaml_iterator_first(it); yaml_iterator_has_next(it); id = yaml_iterator_next(it)) {
-				struct achievement_db *duplicate = &achievement_dummy, *entry = achievement_read_db_sub(id, count, filepath);
+		for (const auto &node : config["Achievements"]) {
+			if (node.IsDefined() && achievement_read_db_sub(node, count, current_file))
+				count++;
+		}
+		for (auto &achit : achievements) {
+			const auto ach = achit.second;
 
-				if (!entry) {
-					ShowWarning("achievement_read_db: Failed to parse achievement entry %d.\n", count);
-					continue;
-				}
-				if ((duplicate = achievement_search(entry->achievement_id)) != &achievement_dummy) {
-					if (!i) { // Normal file read-in
-						ShowWarning("achievement_read_db: Duplicate achievement %d.\n", entry->achievement_id);
-						achievement_db_free_sub(entry, false);
-						continue;
-					}
-					else // Import file read-in, free previous value and store new value
-						achievement_db_free_sub(duplicate, false);
+			for (int i = 0; i < ach->dependent_ids.size(); i++) {
+				if (!achievement_exists(ach->dependent_ids[i])) {
+					ShowWarning("achievement_read_db: An invalid Dependent ID %d was given for Achievement %d. Removing from list.\n", ach->dependent_ids[i], ach->achievement_id);
+					ach->dependent_ids.erase(ach->dependent_ids.begin() + i);
 				}
-				yaml_destroy_wrapper(id);
-				idb_put(achievement_db, entry->achievement_id, entry);
-				count++;
 			}
 		}
-		yaml_destroy_wrapper(adb_sub);
-		yaml_iterator_destroy(it);
-
-		ShowStatus("Done reading '" CL_WHITE "%d" CL_RESET "' entries in '" CL_WHITE "%s" CL_RESET "'.\n", count, filepath);
+		ShowStatus("Done reading '" CL_WHITE "%d" CL_RESET "' entries in '" CL_WHITE "%s" CL_RESET "'\n", count, current_file.c_str());
 	}
 
 	return;
 }
 
 /**
- * Recursive method to free an achievement condition
+ * Recursive method to free an achievement condition (probably not needed anymore, but just in case)
  * @param condition: Condition to clear
  */
-void achievement_script_free(struct av_condition *condition) 
-{
-	if (condition->left) {
-		achievement_script_free(condition->left);
-		condition->left = NULL;
-	}
-
-	if (condition->right) {
-		achievement_script_free(condition->right);
-		condition->right = NULL;
-	}
-
-	aFree(condition);
-}
-
-/**
- * Clear achievement single entry
- * @param achievement: Achievement to clear
- * @param free: Will free achievement from memory
- */
-void achievement_db_free_sub(struct achievement_db *achievement, bool free)
+void achievement_script_free(std::shared_ptr<struct av_condition> condition) 
 {
-	if (achievement->targets) {
-		aFree(achievement->targets);
-		achievement->targets = NULL;
-		achievement->target_count = 0;
-	}
-	if (achievement->condition) {
-		achievement_script_free(achievement->condition);
-		achievement->condition = NULL;
-	}
-	if (achievement->dependents) {
-		aFree(achievement->dependents);
-		achievement->dependents = NULL;
-		achievement->dependent_count = 0;
-	}
-	if (achievement->rewards.script) {
-		script_free_code(achievement->rewards.script);
-		achievement->rewards.script = NULL;
-	}
-	if (free)
-		aFree(achievement);
+	condition->left.reset();
+	condition->right.reset();
 }
 
 /**
- * Clears the achievement database for shutdown or reload.
+ * Reloads the achievement database
  */
-static int achievement_db_free(DBKey key, DBData *data, va_list ap)
-{
-	struct achievement_db *achievement = (struct achievement_db *)db_data2ptr(data);
-
-	if (!achievement)
-		return 0;
-
-	achievement_db_free_sub(achievement, true);
-	return 1;
-}
-
-static int achievementmobs_db_free(DBKey key, DBData *data, va_list ap)
-{
-	struct achievementmobs_db *achievement = (struct achievementmobs_db *)db_data2ptr(data);
-
-	if (!achievement)
-		return 0;
-
-	aFree(achievement);
-	return 1;
-}
-
 void achievement_db_reload(void)
 {
 	if (!battle_config.feature_achievement)
 		return;
-	achievementmobs_db->clear(achievementmobs_db, achievementmobs_db_free);
-	achievement_db->clear(achievement_db, achievement_db_free);
-	achievement_read_db();
+	do_final_achievement();
+	do_init_achievement();
 }
 
+/**
+ * Initializes the achievement database
+ */
 void do_init_achievement(void)
 {
 	if (!battle_config.feature_achievement)
 		return;
-	memset(&achievement_dummy, 0, sizeof(achievement_dummy));
-	achievement_db = idb_alloc(DB_OPT_BASE);
-	achievementmobs_db = idb_alloc(DB_OPT_BASE);
 	achievement_read_db();
 }
 
+/**
+ * Finalizes the achievement database
+ */
 void do_final_achievement(void)
 {
-	if (!battle_config.feature_achievement)
-		return;
-	achievementmobs_db->destroy(achievementmobs_db, achievementmobs_db_free);
-	achievement_db->destroy(achievement_db, achievement_db_free);
+	achievement_mobs.clear();
+	achievements.clear();
+}
+
+/**
+ * Achievement constructor
+ */
+s_achievement_db::s_achievement_db()
+	: achievement_id(0)
+	, name("")
+	, group()
+	, targets()
+	, dependent_ids()
+	, condition(nullptr)
+	, mapindex(0)
+	, rewards()
+	, score(0)
+	, has_dependent(0)
+{}
+
+/**
+ * Achievement reward constructor
+ */
+s_achievement_db::ach_reward::ach_reward()
+	: nameid(0)
+	, amount(0)
+	, script(nullptr)
+	, title_id(0)
+{}
+
+/**
+ * Achievement reward deconstructor
+ */
+s_achievement_db::ach_reward::~ach_reward()
+{
+	if (script)
+		script_free_code(script);
 }

+ 28 - 27
src/map/achievement.hpp

@@ -4,9 +4,18 @@
 #ifndef _ACHIEVEMENT_HPP_
 #define _ACHIEVEMENT_HPP_
 
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
 #include "../common/mmo.h"
 #include "../common/db.h"
 
+struct map_session_data;
+struct block_list;
+
 enum e_achievement_group {
 	AG_NONE = 0,
 	AG_ADD_FRIEND,
@@ -54,51 +63,43 @@ enum e_achievement_info {
 	ACHIEVEINFO_MAX,
 };
 
-struct achievement_mob {
-	int mod_id;
-};
-
 struct achievement_target {
 	int mob;
 	int count;
 };
 
-struct achievement_dependent {
-	int achievement_id;
-};
-
 struct av_condition {
 	int op;
-	struct av_condition *left;
-	struct av_condition *right;
+	std::shared_ptr<struct av_condition> left;
+	std::shared_ptr<struct av_condition> right;
 	long long value;
+
+	av_condition() : op(0), left(nullptr), right(nullptr), value(0) {}
 };
 
-struct achievement_db {
+struct s_achievement_db {
 	int achievement_id;
-	char name[ACHIEVEMENT_NAME_LENGTH];
+	std::string name;
 	enum e_achievement_group group;
-	uint8 target_count;
-	struct achievement_target *targets;
-	uint8 dependent_count;
-	struct achievement_dependent *dependents;
-	struct av_condition *condition;
+	std::vector <achievement_target> targets;
+	std::vector <int> dependent_ids;
+	std::shared_ptr<struct av_condition> condition;
 	int16 mapindex;
 	struct ach_reward {
 		unsigned short nameid, amount;
 		struct script_code *script;
 		int title_id;
+		ach_reward();
+		~ach_reward();
 	} rewards;
 	int score;
 	int has_dependent; // Used for quick updating of achievements that depend on others - this is their ID
-};
 
-struct map_session_data;
-struct block_list;
-
-extern struct achievement_db achievement_dummy;	///< Dummy entry for invalid achievement lookups
+	s_achievement_db();
+};
 
-struct achievement_db *achievement_search(int achievement_id);
+bool achievement_exists(int achievement_id);
+std::shared_ptr<s_achievement_db>& achievement_get(int achievement_id);
 bool achievement_mobexists(int mob_id);
 void achievement_get_reward(struct map_session_data *sd, int achievement_id, time_t rewarded);
 struct achievement *achievement_add(struct map_session_data *sd, int achievement_id);
@@ -117,9 +118,9 @@ void do_init_achievement(void);
 void do_final_achievement(void);
 
 // Parser
-const char *av_parse_subexpr(const char *p,int limit, struct av_condition *parent);
-const char *av_parse_simpleexpr(const char *p, struct av_condition *parent);
-long long achievement_check_condition(struct av_condition *condition, struct map_session_data *sd, int *count);
-void achievement_script_free(struct av_condition *condition);
+const char *av_parse_subexpr(const char *p,int limit, std::shared_ptr<struct av_condition> parent);
+const char *av_parse_simpleexpr(const char *p, std::shared_ptr<struct av_condition> parent);
+long long achievement_check_condition(std::shared_ptr<struct av_condition> condition, struct map_session_data *sd, const int *count);
+void achievement_script_free(std::shared_ptr<struct av_condition> condition);
 
 #endif /* _ACHIEVEMENT_HPP_ */

+ 2 - 3
src/map/clif.cpp

@@ -19020,7 +19020,7 @@ void clif_change_title_ack(struct map_session_data *sd, unsigned char result, un
  */
 void clif_parse_change_title(int fd, struct map_session_data *sd)
 {
-	int title_id, i;
+	int title_id;
 
 	nullpo_retv(sd);
 
@@ -19032,8 +19032,7 @@ void clif_parse_change_title(int fd, struct map_session_data *sd)
 	}else if( title_id <= 0 ){
 		sd->status.title_id = 0;
 	}else{
-		ARR_FIND(0, sd->titleCount, i, sd->titles[i] == title_id);
-		if( i == sd->titleCount ){
+		if (std::find(sd->titles.begin(), sd->titles.end(), title_id) != sd->titles.end()) {
 			clif_change_title_ack(sd, 1, title_id);
 			return;
 		}

+ 5 - 4
src/map/intif.cpp

@@ -2130,13 +2130,14 @@ void intif_parse_achievements(int fd)
 			CREATE(sd->achievement_data.achievements, struct achievement, num_received);
 
 		for (i = 0; i < num_received; i++) {
-			struct achievement_db *adb = achievement_search(received[i].achievement_id);
 
-			if (!adb) {
+			if (!achievement_exists(received[i].achievement_id)) {
 				ShowError("intif_parse_achievementlog: Achievement %d not found in DB.\n", received[i].achievement_id);
 				continue;
 			}
 
+			auto &adb = achievement_get(received[i].achievement_id);
+
 			received[i].score = adb->score;
 
 			if (received[i].completed == 0) // Insert at the beginning
@@ -2220,7 +2221,7 @@ void intif_parse_achievementreward(int fd){
 /**
  * Request the achievement rewards from the inter server.
  */
-int intif_achievement_reward(struct map_session_data *sd, struct achievement_db *adb){
+int intif_achievement_reward(struct map_session_data *sd, struct s_achievement_db *adb){
 	if( CheckForCharServer() ){
 		return 0;
 	}
@@ -2232,7 +2233,7 @@ int intif_achievement_reward(struct map_session_data *sd, struct achievement_db
 	WFIFOW(inter_fd, 10) = adb->rewards.nameid;
 	WFIFOL(inter_fd, 12) = adb->rewards.amount;
 	safestrncpy(WFIFOCP(inter_fd, 16), sd->status.name, NAME_LENGTH);
-	safestrncpy(WFIFOCP(inter_fd, 16+NAME_LENGTH), adb->name, ACHIEVEMENT_NAME_LENGTH);
+	safestrncpy(WFIFOCP(inter_fd, 16+NAME_LENGTH), adb->name.c_str(), ACHIEVEMENT_NAME_LENGTH);
 	WFIFOSET(inter_fd, 16+NAME_LENGTH+ACHIEVEMENT_NAME_LENGTH);
 
 	return 1;

+ 2 - 2
src/map/intif.hpp

@@ -16,7 +16,7 @@ struct s_mercenary;
 struct s_elemental;
 struct mail_message;
 struct auction_data;
-struct achievement_db;
+struct s_achievement_db;
 struct map_session_data;
 
 int intif_parse(int fd);
@@ -119,7 +119,7 @@ int intif_clan_member_left( int clan_id );
 // ACHIEVEMENT SYSTEM
 void intif_request_achievements(uint32 char_id);
 int intif_achievement_save(struct map_session_data *sd);
-int intif_achievement_reward(struct map_session_data *sd, struct achievement_db *adb);
+int intif_achievement_reward(struct map_session_data *sd, struct s_achievement_db *adb);
 
 int intif_request_accinfo(int u_fd, int aid, int group_lv, char* query, char type);
 

+ 3 - 2
src/map/pc.hpp

@@ -4,6 +4,8 @@
 #ifndef _PC_HPP_
 #define _PC_HPP_
 
+#include <vector>
+
 #include "../common/mmo.h" // JOB_*, MAX_FAME_LIST, struct fame_list, struct mmo_charstatus
 #include "../common/strlib.h"// StringBuf
 
@@ -612,8 +614,7 @@ struct map_session_data {
 	} achievement_data;
 
 	// Title system
-	int *titles;
-	uint8 titleCount;
+	std::vector<int> titles;
 
 	/* ShowEvent Data Cache flags from map */
 	bool *qi_display;

+ 6 - 6
src/map/script.cpp

@@ -23374,7 +23374,7 @@ BUILDIN_FUNC(achievementadd) {
 		return SCRIPT_CMD_FAILURE;
 	}
 
-	if (achievement_search(achievement_id) == &achievement_dummy) {
+	if (achievement_exists(achievement_id) == false) {
 		ShowWarning("buildin_achievementadd: Achievement '%d' doesn't exist.\n", achievement_id);
 		script_pushint(st, false);
 		return SCRIPT_CMD_FAILURE;
@@ -23411,7 +23411,7 @@ BUILDIN_FUNC(achievementremove) {
 		return SCRIPT_CMD_FAILURE;
 	}
 
-	if (achievement_search(achievement_id) == &achievement_dummy) {
+	if (achievement_exists(achievement_id) == false) {
 		ShowWarning("buildin_achievementremove: Achievement '%d' doesn't exist.\n", achievement_id);
 		script_pushint(st, false);
 		return SCRIPT_CMD_SUCCESS;
@@ -23447,7 +23447,7 @@ BUILDIN_FUNC(achievementinfo) {
 		return SCRIPT_CMD_FAILURE;
 	}
 
-	if (achievement_search(achievement_id) == &achievement_dummy) {
+	if (achievement_exists(achievement_id) == false) {
 		ShowWarning("buildin_achievementinfo: Achievement '%d' doesn't exist.\n", achievement_id);
 		script_pushint(st, false);
 		return SCRIPT_CMD_FAILURE;
@@ -23481,7 +23481,7 @@ BUILDIN_FUNC(achievementcomplete) {
 		return SCRIPT_CMD_FAILURE;
 	}
 
-	if (achievement_search(achievement_id) == &achievement_dummy) {
+	if (achievement_exists(achievement_id) == false) {
 		ShowWarning("buildin_achievementcomplete: Achievement '%d' doesn't exist.\n", achievement_id);
 		script_pushint(st, false);
 		return SCRIPT_CMD_FAILURE;
@@ -23518,7 +23518,7 @@ BUILDIN_FUNC(achievementexists) {
 		return SCRIPT_CMD_FAILURE;
 	}
 
-	if (achievement_search(achievement_id) == &achievement_dummy) {
+	if (achievement_exists(achievement_id) == false) {
 		ShowWarning("buildin_achievementexists: Achievement '%d' doesn't exist.\n", achievement_id);
 		script_pushint(st, false);
 		return SCRIPT_CMD_SUCCESS;
@@ -23557,7 +23557,7 @@ BUILDIN_FUNC(achievementupdate) {
 		return SCRIPT_CMD_FAILURE;
 	}
 
-	if (achievement_search(achievement_id) == &achievement_dummy) {
+	if (achievement_exists(achievement_id) == false) {
 		ShowWarning("buildin_achievementupdate: Achievement '%d' doesn't exist.\n", achievement_id);
 		script_pushint(st, false);
 		return SCRIPT_CMD_FAILURE;