|
@@ -3,10 +3,12 @@
|
|
|
|
|
|
#include "achievement.hpp"
|
|
#include "achievement.hpp"
|
|
|
|
|
|
|
|
+#include <array>
|
|
#include <stdio.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <string.h>
|
|
#include <setjmp.h>
|
|
#include <setjmp.h>
|
|
|
|
+#include <yaml-cpp/yaml.h>
|
|
|
|
|
|
#include "../common/cbasetypes.h"
|
|
#include "../common/cbasetypes.h"
|
|
#include "../common/malloc.h"
|
|
#include "../common/malloc.h"
|
|
@@ -14,7 +16,6 @@
|
|
#include "../common/showmsg.h"
|
|
#include "../common/showmsg.h"
|
|
#include "../common/strlib.h"
|
|
#include "../common/strlib.h"
|
|
#include "../common/utils.h"
|
|
#include "../common/utils.h"
|
|
-#include "../common/yamlwrapper.h"
|
|
|
|
|
|
|
|
#include "battle.hpp"
|
|
#include "battle.hpp"
|
|
#include "chrif.hpp"
|
|
#include "chrif.hpp"
|
|
@@ -32,23 +33,27 @@ static char* av_error_msg;
|
|
static const char* av_error_pos;
|
|
static const char* av_error_pos;
|
|
static int av_error_report;
|
|
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
|
|
* Searches an achievement by ID
|
|
* @param achievement_id: ID to lookup
|
|
* @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)
|
|
if (!battle_config.feature_achievement)
|
|
return false;
|
|
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 *achievement_add(struct map_session_data *sd, int achievement_id)
|
|
{
|
|
{
|
|
- struct achievement_db *adb = &achievement_dummy;
|
|
|
|
int i, index;
|
|
int i, index;
|
|
|
|
|
|
nullpo_retr(NULL, sd);
|
|
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);
|
|
ShowError("achievement_add: Achievement %d not found in DB.\n", achievement_id);
|
|
return NULL;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ auto &adb = achievements[achievement_id];
|
|
|
|
+
|
|
ARR_FIND(0, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == 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) {
|
|
if (i < sd->achievement_data.count) {
|
|
ShowError("achievement_add: Character %d already has achievement %d.\n", sd->status.char_id, achievement_id);
|
|
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);
|
|
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);
|
|
ShowError("achievement_delete: Achievement %d not found in DB.\n", achievement_id);
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
@@ -155,6 +164,22 @@ bool achievement_remove(struct map_session_data *sd, int achievement_id)
|
|
return true;
|
|
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
|
|
* Checks to see if an achievement has a dependent, and if so, checks if that dependent is complete
|
|
* @param sd: Player data
|
|
* @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)
|
|
bool achievement_check_dependent(struct map_session_data *sd, int achievement_id)
|
|
{
|
|
{
|
|
- struct achievement_db *adb = &achievement_dummy;
|
|
|
|
-
|
|
|
|
nullpo_retr(false, sd);
|
|
nullpo_retr(false, sd);
|
|
|
|
|
|
- adb = achievement_search(achievement_id);
|
|
|
|
-
|
|
|
|
- if (adb == &achievement_dummy)
|
|
|
|
|
|
+ if (!achievement_exists(achievement_id))
|
|
return false;
|
|
return false;
|
|
|
|
|
|
|
|
+ auto &adb = achievements[achievement_id];
|
|
|
|
+
|
|
// Check if the achievement has a dependent
|
|
// Check if the achievement has a dependent
|
|
// If so, then do a check on all dependents to see if they're complete
|
|
// 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;
|
|
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
|
|
* 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
|
|
* @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;
|
|
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;
|
|
return 0;
|
|
|
|
|
|
if (ad->group != AG_BATTLE && ad->group != AG_TAMING && ad->group != AG_ADVENTURE)
|
|
if (ad->group != AG_BATTLE && ad->group != AG_TAMING && ad->group != AG_ADVENTURE)
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
- if (ad->dependent_count == 0 || ad->condition)
|
|
|
|
|
|
+ if (ad->dependent_ids.size() == 0 || ad->condition)
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
ARR_FIND(0, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == ad->achievement_id);
|
|
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)
|
|
bool achievement_update_achievement(struct map_session_data *sd, int achievement_id, bool complete)
|
|
{
|
|
{
|
|
- struct achievement_db *adb = &achievement_dummy;
|
|
|
|
int i;
|
|
int i;
|
|
|
|
|
|
nullpo_retr(false, sd);
|
|
nullpo_retr(false, sd);
|
|
|
|
|
|
- adb = achievement_search(achievement_id);
|
|
|
|
-
|
|
|
|
- if (adb == &achievement_dummy)
|
|
|
|
|
|
+ if (!achievement_exists(achievement_id))
|
|
return false;
|
|
return false;
|
|
|
|
|
|
|
|
+ auto &adb = achievements[achievement_id];
|
|
|
|
+
|
|
ARR_FIND(0, sd->achievement_data.incompleteCount, i, sd->achievement_data.achievements[i].achievement_id == 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)
|
|
if (i == sd->achievement_data.incompleteCount)
|
|
return false;
|
|
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
|
|
// Finally we send the updated achievement to the client
|
|
if (complete) {
|
|
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;
|
|
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;
|
|
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]);
|
|
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
|
|
achievement_level(sd, true); // Re-calculate achievement level
|
|
// Check dependents
|
|
// 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
|
|
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)
|
|
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;
|
|
int i;
|
|
|
|
|
|
nullpo_retv(sd);
|
|
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;
|
|
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;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
ARR_FIND(0, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == 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) {
|
|
|
|
|
|
+ if (i == sd->achievement_data.count)
|
|
return;
|
|
return;
|
|
- }
|
|
|
|
|
|
|
|
// Only update in the cache, db was updated already
|
|
// Only update in the cache, db was updated already
|
|
sd->achievement_data.achievements[i].rewarded = rewarded;
|
|
sd->achievement_data.achievements[i].rewarded = rewarded;
|
|
|
|
|
|
run_script(adb->rewards.script, 0, sd->bl.id, fake_nd->bl.id);
|
|
run_script(adb->rewards.script, 0, sd->bl.id, fake_nd->bl.id);
|
|
if (adb->rewards.title_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;
|
|
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)
|
|
void achievement_check_reward(struct map_session_data *sd, int achievement_id)
|
|
{
|
|
{
|
|
int i;
|
|
int i;
|
|
- struct achievement_db *adb = achievement_search(achievement_id);
|
|
|
|
|
|
|
|
nullpo_retv(sd);
|
|
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);
|
|
ShowError("achievement_reward: Trying to reward achievement %d not found in DB.\n", achievement_id);
|
|
clif_achievement_reward_ack(sd->fd, 0, achievement_id);
|
|
clif_achievement_reward_ack(sd->fd, 0, achievement_id);
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ auto &adb = achievements[achievement_id];
|
|
|
|
+
|
|
ARR_FIND(0, sd->achievement_data.count, i, sd->achievement_data.achievements[i].achievement_id == 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) {
|
|
if (i == sd->achievement_data.count) {
|
|
clif_achievement_reward_ack(sd->fd, 0, achievement_id);
|
|
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;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
- if( !intif_achievement_reward(sd,adb) ){
|
|
|
|
|
|
+ if (!intif_achievement_reward(sd, adb.get())) {
|
|
clif_achievement_reward_ack(sd->fd, 0, achievement_id);
|
|
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);
|
|
struct map_session_data *sd = map_charid2sd(char_id);
|
|
|
|
|
|
if (sd) {
|
|
if (sd) {
|
|
- sd->titles = NULL;
|
|
|
|
- sd->titleCount = 0;
|
|
|
|
|
|
+ sd->titles.clear();
|
|
|
|
|
|
if (sd->achievement_data.count) {
|
|
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
|
|
* @param sd: Player's session
|
|
*/
|
|
*/
|
|
void achievement_free(struct map_session_data *sd)
|
|
void achievement_free(struct map_session_data *sd)
|
|
{
|
|
{
|
|
nullpo_retv(sd);
|
|
nullpo_retv(sd);
|
|
|
|
|
|
- if (sd->titleCount) {
|
|
|
|
- aFree(sd->titles);
|
|
|
|
- sd->titles = NULL;
|
|
|
|
- sd->titleCount = 0;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
if (sd->achievement_data.count) {
|
|
if (sd->achievement_data.count) {
|
|
aFree(sd->achievement_data.achievements);
|
|
aFree(sd->achievement_data.achievements);
|
|
sd->achievement_data.achievements = NULL;
|
|
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
|
|
* Calculate a player's achievement level
|
|
* @param sd: Player to check achievement level
|
|
* @param sd: Player to check achievement level
|
|
* @param flag: If the call should attempt to give the AG_GOAL_ACHIEVE achievement
|
|
* @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)
|
|
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.
|
|
* 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;
|
|
struct achievement *entry = NULL;
|
|
bool isNew = false, changed = false, complete = false;
|
|
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)
|
|
if (ad == NULL || sd == NULL)
|
|
return 0;
|
|
return 0;
|
|
@@ -535,7 +531,7 @@ static int achievement_update_objectives(DBKey key, DBData *data, va_list ap)
|
|
if (group != ad->group)
|
|
if (group != ad->group)
|
|
return 0;
|
|
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);
|
|
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
|
|
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
|
|
if (entry->completed > 0) // Player has completed the achievement
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
- memcpy(objective_count, entry->count, sizeof(objective_count));
|
|
|
|
|
|
+ memcpy(objective_count.data(), entry->count, sizeof(objective_count));
|
|
}
|
|
}
|
|
|
|
|
|
switch (group) {
|
|
switch (group) {
|
|
@@ -572,7 +568,7 @@ static int achievement_update_objectives(DBKey key, DBData *data, va_list ap)
|
|
changed = true;
|
|
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;
|
|
changed = true;
|
|
complete = true;
|
|
complete = true;
|
|
}
|
|
}
|
|
@@ -586,24 +582,24 @@ static int achievement_update_objectives(DBKey key, DBData *data, va_list ap)
|
|
}
|
|
}
|
|
break;
|
|
break;
|
|
case AG_CHAT:
|
|
case AG_CHAT:
|
|
- if (!ad->target_count)
|
|
|
|
|
|
+ if (!ad->targets.size())
|
|
break;
|
|
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;
|
|
break;
|
|
|
|
|
|
if (ad->mapindex > -1 && sd->bl.m != ad->mapindex)
|
|
if (ad->mapindex > -1 && sd->bl.m != ad->mapindex)
|
|
break;
|
|
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)
|
|
if (objective_count[i] < ad->targets[i].count)
|
|
objective_count[i] += update_count[0];
|
|
objective_count[i] += update_count[0];
|
|
}
|
|
}
|
|
|
|
|
|
changed = true;
|
|
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;
|
|
complete = true;
|
|
|
|
|
|
if (isNew) {
|
|
if (isNew) {
|
|
@@ -613,19 +609,22 @@ static int achievement_update_objectives(DBKey key, DBData *data, va_list ap)
|
|
break;
|
|
break;
|
|
case AG_BATTLE:
|
|
case AG_BATTLE:
|
|
case AG_TAMING:
|
|
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
|
|
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) {
|
|
if (ad->targets[k].mob == update_count[0] && objective_count[k] < ad->targets[k].count) {
|
|
objective_count[k]++;
|
|
objective_count[k]++;
|
|
changed = true;
|
|
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;
|
|
complete = true;
|
|
|
|
|
|
if (isNew) {
|
|
if (isNew) {
|
|
@@ -636,7 +635,7 @@ static int achievement_update_objectives(DBKey key, DBData *data, va_list ap)
|
|
}
|
|
}
|
|
|
|
|
|
if (changed) {
|
|
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);
|
|
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) {
|
|
if (sd) {
|
|
va_list ap;
|
|
va_list ap;
|
|
- int i, count[MAX_ACHIEVEMENT_OBJECTIVES];
|
|
|
|
|
|
+ int i;
|
|
|
|
+ std::array<int,MAX_ACHIEVEMENT_OBJECTIVES> count;
|
|
|
|
|
|
if (!battle_config.feature_achievement)
|
|
if (!battle_config.feature_achievement)
|
|
return;
|
|
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);
|
|
va_start(ap, arg_count);
|
|
for (i = 0; i < arg_count; i++)
|
|
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.
|
|
// These have no objective use right now.
|
|
break;
|
|
break;
|
|
default:
|
|
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;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -697,7 +698,7 @@ static void disp_error_message2(const char *mes,const char *pos,int report)
|
|
* @param count: Script arguments
|
|
* @param count: Script arguments
|
|
* @return The result of the condition.
|
|
* @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 left = 0;
|
|
long long right = 0;
|
|
long long right = 0;
|
|
@@ -788,6 +789,11 @@ long long achievement_check_condition(struct av_condition *condition, struct map
|
|
return false;
|
|
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)
|
|
static const char *skip_word(const char *p)
|
|
{
|
|
{
|
|
while (ISALNUM(*p) || *p == '_')
|
|
while (ISALNUM(*p) || *p == '_')
|
|
@@ -799,7 +805,13 @@ static const char *skip_word(const char *p)
|
|
return 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;
|
|
long long i;
|
|
|
|
|
|
@@ -834,7 +846,6 @@ const char *av_parse_simpleexpr(const char *p, struct av_condition *parent)
|
|
p = np;
|
|
p = np;
|
|
} else {
|
|
} else {
|
|
int v, len;
|
|
int v, len;
|
|
- char * word;
|
|
|
|
|
|
|
|
if (skip_word(p) == p)
|
|
if (skip_word(p) == p)
|
|
disp_error_message("av_parse_simpleexpr: unexpected character.", 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)
|
|
if (len == 0)
|
|
disp_error_message("av_parse_simpleexpr: invalid word. A word consists of undercores and/or alphanumeric characters.", p);
|
|
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;
|
|
word[len] = 0;
|
|
|
|
|
|
- if (script_get_parameter(word, &v))
|
|
|
|
|
|
+ if (script_get_parameter((const char*)&word[0], &v))
|
|
parent->op = C_PARAM;
|
|
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?)
|
|
if (word[0] == 'b' && ISUPPER(word[1])) // Consider b* variables as parameters (because they... are?)
|
|
parent->op = C_PARAM;
|
|
parent->op = C_PARAM;
|
|
else
|
|
else
|
|
@@ -858,14 +869,12 @@ const char *av_parse_simpleexpr(const char *p, struct av_condition *parent)
|
|
} else {
|
|
} else {
|
|
if (word[0] == 'A' && word[1] == 'R' && word[2] == 'G' && ISDIGIT(word[3])) { // Special constants used to set temporary variables
|
|
if (word[0] == 'A' && word[1] == 'R' && word[2] == 'G' && ISDIGIT(word[3])) { // Special constants used to set temporary variables
|
|
parent->op = C_ARG;
|
|
parent->op = C_ARG;
|
|
- v = atoi(word + 3);
|
|
|
|
|
|
+ v = atoi(&word[0] + 3);
|
|
} else {
|
|
} else {
|
|
- aFree(word);
|
|
|
|
disp_error_message("av_parse_simpleexpr: invalid constant.", p);
|
|
disp_error_message("av_parse_simpleexpr: invalid constant.", p);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- aFree(word);
|
|
|
|
parent->value = v;
|
|
parent->value = v;
|
|
p = skip_word(p);
|
|
p = skip_word(p);
|
|
}
|
|
}
|
|
@@ -873,13 +882,19 @@ const char *av_parse_simpleexpr(const char *p, struct av_condition *parent)
|
|
return p;
|
|
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;
|
|
int op, opl, len;
|
|
|
|
|
|
p = skip_space(p);
|
|
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
|
|
if ((op = C_NEG, *p == '-') || (op = C_LNOT, *p == '!') || (op = C_NOT, *p == '~')) { // Unary - ! ~ operators
|
|
p = av_parse_subexpr(p + 1, 11, parent->left);
|
|
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;
|
|
p += len;
|
|
|
|
|
|
if (parent->right) { // Chain conditions
|
|
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->op = parent->op;
|
|
condition->left = parent->left;
|
|
condition->left = parent->left;
|
|
condition->right = parent->right;
|
|
condition->right = parent->right;
|
|
parent->left = condition;
|
|
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);
|
|
p = av_parse_subexpr(p, opl, parent->right);
|
|
parent->op = op;
|
|
parent->op = op;
|
|
p = skip_space(p);
|
|
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->right = parent->left->right;
|
|
parent->op = parent->left->op;
|
|
parent->op = parent->left->op;
|
|
parent->value = parent->left->value;
|
|
parent->value = parent->left->value;
|
|
parent->left = parent->left->left;
|
|
parent->left = parent->left->left;
|
|
-
|
|
|
|
- aFree(temp);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
return p;
|
|
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.
|
|
* @param line: The current achievement line number.
|
|
* @return The parsed achievement condition.
|
|
* @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 (setjmp(av_error_jump) != 0) {
|
|
if (av_error_report)
|
|
if (av_error_report)
|
|
script_error(p,file,line,av_error_msg,av_error_pos);
|
|
script_error(p,file,line,av_error_msg,av_error_pos);
|
|
aFree(av_error_msg);
|
|
aFree(av_error_msg);
|
|
- if (condition)
|
|
|
|
- achievement_script_free(condition);
|
|
|
|
|
|
+ condition.reset();
|
|
return NULL;
|
|
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);
|
|
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);
|
|
av_parse_subexpr(p, -1, condition);
|
|
|
|
|
|
return 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.
|
|
* 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 n: The sequential index of the current entry.
|
|
* @param source: The source YAML file.
|
|
* @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;
|
|
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) {
|
|
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;
|
|
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;
|
|
entry->group = group;
|
|
- safestrncpy(entry->name, name, sizeof(entry->name));
|
|
|
|
- aFree(name);
|
|
|
|
|
|
+ entry->name = name;
|
|
entry->mapindex = -1;
|
|
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)
|
|
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 {
|
|
else {
|
|
- entry->mapindex = map_mapname2mapid(mapname);
|
|
|
|
|
|
+ entry->mapindex = map_mapname2mapid(mapname.c_str());
|
|
|
|
|
|
if (entry->mapindex == -1)
|
|
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)
|
|
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;
|
|
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
|
|
* @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)
|
|
void achievement_db_reload(void)
|
|
{
|
|
{
|
|
if (!battle_config.feature_achievement)
|
|
if (!battle_config.feature_achievement)
|
|
return;
|
|
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)
|
|
void do_init_achievement(void)
|
|
{
|
|
{
|
|
if (!battle_config.feature_achievement)
|
|
if (!battle_config.feature_achievement)
|
|
return;
|
|
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();
|
|
achievement_read_db();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/**
|
|
|
|
+ * Finalizes the achievement database
|
|
|
|
+ */
|
|
void do_final_achievement(void)
|
|
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);
|
|
}
|
|
}
|