From 1c66035761d256474bde7df703eb406255514d8c Mon Sep 17 00:00:00 2001 From: Aleos Date: Mon, 20 Nov 2017 21:34:16 -0500 Subject: [PATCH] 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! --- db/pre-re/achievement_db.yml | 28 +- db/re/achievement_db.yml | 28 +- doc/achievements.txt | 2 +- src/common/mmo.h | 4 +- src/map/achievement.cpp | 724 ++++++++++++++++++----------------- src/map/achievement.hpp | 55 +-- src/map/clif.cpp | 5 +- src/map/intif.cpp | 9 +- src/map/intif.hpp | 4 +- src/map/pc.hpp | 5 +- src/map/script.cpp | 12 +- 11 files changed, 442 insertions(+), 434 deletions(-) diff --git a/db/pre-re/achievement_db.yml b/db/pre-re/achievement_db.yml index 24e7173607..d194364633 100644 --- a/db/pre-re/achievement_db.yml +++ b/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" diff --git a/db/re/achievement_db.yml b/db/re/achievement_db.yml index ff756f9524..4b9637ca61 100644 --- a/db/re/achievement_db.yml +++ b/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" diff --git a/doc/achievements.txt b/doc/achievements.txt index 70c9ad2a46..e08b34f395 100644 --- a/doc/achievements.txt +++ b/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 --------------------------------------- diff --git a/src/common/mmo.h b/src/common/mmo.h index 20c9bcdfe3..afe1ef5e77 100644 --- a/src/common/mmo.h +++ b/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 { diff --git a/src/map/achievement.cpp b/src/map/achievement.cpp index 11760f5fea..8b20165dc9 100644 --- a/src/map/achievement.cpp +++ b/src/map/achievement.cpp @@ -3,10 +3,12 @@ #include "achievement.hpp" +#include #include #include #include #include +#include #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> achievements; +std::vector 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 &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); - return; - } - - if (adb == &achievement_dummy) { + 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; } - ARR_FIND(0, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == achievement_id); + auto &adb = achievements[achievement_id]; - if (i == sd->achievement_data.count) { + 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) + 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 ad, enum e_achievement_group group, const std::array &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 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 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 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 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 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 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 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 parse_condition(const char *p, const char *file, int line) { - struct av_condition *condition = NULL; + std::shared_ptr 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(); + } 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(); + 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(); + } 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(); + } 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 (target["MobID"] && (mobid = target["MobID"].as()) && 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()) || 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); + + entry->targets.push_back({ mobid, count }); } - 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; - - CREATE(entrymob, struct achievement_mob, 1); - idb_put(achievementmobs_db, mobid, entrymob); - } - - 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(); + } 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(); + } 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; + if (node["Dependent"]) { + try { + const YAML::Node dependent_list = node["Dependent"]; - 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); + 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()); + } 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 (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())) { + 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 (reward_list["Amount"] && (amount = reward_list["Amount"].as()) && amount > 0 && nameid > 0) + entry->rewards.amount = amount; } - yaml_iterator_destroy(it); - } else - ShowWarning("achievement_read_db_sub: Invalid dependent format for achievement %d in \"%s\".\n", achievement_id, source); + if (reward_list["Script"]) + entry->rewards.script = parse_script(reward_list["Script"].as().c_str(), source.c_str(), achievement_id, SCRIPT_IGNORE_EXTERNAL_BRACKETS); + if (reward_list["TitleID"] && (titleid = reward_list["TitleID"].as()) && 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(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 (yaml_node_is_defined(t, "Amount") && (amount = yaml_get_int(t, "Amount")) && amount > 0 && nameid) - entry->rewards.amount = amount; + if (node["Score"]) { + try { + entry->score = node["Score"].as(); + } 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; } - 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 (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; - - 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 - }; + std::vector directories = { std::string(db_path) + "/" + std::string(DBPATH), std::string(db_path) + "/" + std::string(DBIMPORT) + "/" }; + static const std::string file_name("achievement_db.yml"); - for (i = 0; i < ARRAYLENGTH(dbsubpath); i++) { - char filepath[256]; + for (auto &directory : directories) { + std::string current_file = directory + file_name; + YAML::Node config; int count = 0; - 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"); - - 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); - - 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); - } - yaml_destroy_wrapper(id); - idb_put(achievement_db, entry->achievement_id, entry); + 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; + + 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(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) +void achievement_script_free(std::shared_ptr 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); + condition->left.reset(); + condition->right.reset(); } /** - * Clear achievement single entry - * @param achievement: Achievement to clear - * @param free: Will free achievement from memory + * Reloads the achievement database */ -void achievement_db_free_sub(struct achievement_db *achievement, bool free) -{ - 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); -} - -/** - * Clears the achievement database for shutdown or reload. - */ -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); } diff --git a/src/map/achievement.hpp b/src/map/achievement.hpp index 8fa6fde88e..5abecdf568 100644 --- a/src/map/achievement.hpp +++ b/src/map/achievement.hpp @@ -4,9 +4,18 @@ #ifndef _ACHIEVEMENT_HPP_ #define _ACHIEVEMENT_HPP_ +#include +#include +#include +#include +#include + #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 left; + std::shared_ptr 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 targets; + std::vector dependent_ids; + std::shared_ptr 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 + + s_achievement_db(); }; -struct map_session_data; -struct block_list; - -extern struct achievement_db achievement_dummy; ///< Dummy entry for invalid achievement lookups - -struct achievement_db *achievement_search(int achievement_id); +bool achievement_exists(int achievement_id); +std::shared_ptr& 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 parent); +const char *av_parse_simpleexpr(const char *p, std::shared_ptr parent); +long long achievement_check_condition(std::shared_ptr condition, struct map_session_data *sd, const int *count); +void achievement_script_free(std::shared_ptr condition); #endif /* _ACHIEVEMENT_HPP_ */ diff --git a/src/map/clif.cpp b/src/map/clif.cpp index ecb632786b..6efe626d6a 100644 --- a/src/map/clif.cpp +++ b/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; } diff --git a/src/map/intif.cpp b/src/map/intif.cpp index eae7c1d939..ec90ba10f8 100644 --- a/src/map/intif.cpp +++ b/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; diff --git a/src/map/intif.hpp b/src/map/intif.hpp index 3db22d0eec..ab1e4c8c41 100644 --- a/src/map/intif.hpp +++ b/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); diff --git a/src/map/pc.hpp b/src/map/pc.hpp index 2b50c57c44..c08a4a1a70 100644 --- a/src/map/pc.hpp +++ b/src/map/pc.hpp @@ -4,6 +4,8 @@ #ifndef _PC_HPP_ #define _PC_HPP_ +#include + #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 titles; /* ShowEvent Data Cache flags from map */ bool *qi_display; diff --git a/src/map/script.cpp b/src/map/script.cpp index b89feca168..36eb9bef27 100644 --- a/src/map/script.cpp +++ b/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;