diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/char/Makefile.in | 8 | ||||
-rw-r--r-- | src/char/int_achievement.c | 252 | ||||
-rw-r--r-- | src/char/int_achievement.h | 53 | ||||
-rw-r--r-- | src/map/Makefile.in | 4 | ||||
-rw-r--r-- | src/map/achievement.c | 1878 | ||||
-rw-r--r-- | src/map/achievement.h | 285 |
6 files changed, 2474 insertions, 6 deletions
diff --git a/src/char/Makefile.in b/src/char/Makefile.in index 49fc8ec92..b79958679 100644 --- a/src/char/Makefile.in +++ b/src/char/Makefile.in @@ -40,12 +40,12 @@ MT19937AR_D = $(THIRDPARTY_D)/mt19937ar MT19937AR_OBJ = $(MT19937AR_D)/mt19937ar.o MT19937AR_H = $(MT19937AR_D)/mt19937ar.h -CHAR_C = char.c HPMchar.c loginif.c mapif.c geoip.c inter.c int_auction.c int_clan.c int_elemental.c int_guild.c \ - int_homun.c int_mail.c int_mercenary.c int_party.c int_pet.c \ +CHAR_C = char.c HPMchar.c loginif.c mapif.c geoip.c inter.c int_achievement.c int_auction.c int_clan.c int_elemental.c \ + int_guild.c int_homun.c int_mail.c int_mercenary.c int_party.c int_pet.c \ int_quest.c int_rodex.c int_storage.c pincode.c CHAR_OBJ = $(addprefix obj_sql/, $(patsubst %.c,%.o,$(CHAR_C))) -CHAR_H = char.h HPMchar.h loginif.h mapif.h geoip.h inter.h int_auction.h int_clan.h int_elemental.h int_guild.h \ - int_homun.h int_mail.h int_mercenary.h int_party.h int_pet.h \ +CHAR_H = char.h HPMchar.h loginif.h mapif.h geoip.h inter.h int_achievement.h int_auction.h int_clan.h int_elemental.h + int_guild.h int_homun.h int_mail.h int_mercenary.h int_party.h int_pet.h \ int_quest.h int_rodex.h int_storage.h pincode.h CHAR_PH = diff --git a/src/char/int_achievement.c b/src/char/int_achievement.c new file mode 100644 index 000000000..14311ecf0 --- /dev/null +++ b/src/char/int_achievement.c @@ -0,0 +1,252 @@ +/** +* This file is part of Hercules. +* http://herc.ws - http://github.com/HerculesWS/Hercules +* +* Copyright (C) 2017 Hercules Dev Team +* Copyright (C) Smokexyz +* +* Hercules is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +#define HERCULES_CORE + +#include "int_achievement.h" + +#include "char/char.h" +#include "char/inter.h" +#include "char/mapif.h" + +#include "common/db.h" +#include "common/memmgr.h" +#include "common/mmo.h" +#include "common/nullpo.h" +#include "common/showmsg.h" +#include "common/socket.h" +#include "common/sql.h" +#include "common/strlib.h" + +#include <stdio.h> +#include <stdlib.h> + +static struct inter_achievement_interface inter_achievement_s; +struct inter_achievement_interface *inter_achievement; + +/** + * Saves changed achievements for a character. + * @param[in] char_id character identifier. + * @param[out] cp pointer to loaded achievements. + * @param[in] p pointer to map-sent character achievements. + * @return number of achievements saved. + */ +static int inter_achievement_tosql(int char_id, struct char_achievements *cp, const struct char_achievements *p) +{ + StringBuf buf; + int i = 0, rows = 0; + + nullpo_ret(cp); + nullpo_ret(p); + Assert_ret(char_id > 0); + + StrBuf->Init(&buf); + StrBuf->Printf(&buf, "REPLACE INTO `%s` (`char_id`, `ach_id`, `completed_at`, `rewarded_at`", char_achievement_db); + for (i = 0; i < MAX_ACHIEVEMENT_OBJECTIVES; i++) + StrBuf->Printf(&buf, ", `obj_%d`", i); + StrBuf->AppendStr(&buf, ") VALUES "); + + for (i = 0; i < VECTOR_LENGTH(*p); i++) { + int j = 0; + bool save = false; + struct achievement *pa = &VECTOR_INDEX(*p, i), *cpa = NULL; + + ARR_FIND(0, VECTOR_LENGTH(*cp), j, ((cpa = &VECTOR_INDEX(*cp, j)) && cpa->id == pa->id)); + + if (j == VECTOR_LENGTH(*cp)) + save = true; + else if (memcmp(cpa, pa, sizeof(struct achievement)) != 0) + save = true; + + if (save) { + StrBuf->Printf(&buf, "%s('%d', '%d', '%"PRId64"', '%"PRId64"'", rows ?", ":"", char_id, pa->id, (int64)pa->completed_at, (int64)pa->rewarded_at); + for (j = 0; j < MAX_ACHIEVEMENT_OBJECTIVES; j++) + StrBuf->Printf(&buf, ", '%d'", pa->objective[j]); + StrBuf->AppendStr(&buf, ")"); + rows++; + } + } + + if (rows > 0 && SQL_ERROR == SQL->QueryStr(inter->sql_handle, StrBuf->Value(&buf))) { + Sql_ShowDebug(inter->sql_handle); + StrBuf->Destroy(&buf); // Destroy the buffer. + return 0; + } + // Destroy the buffer. + StrBuf->Destroy(&buf); + + if (rows) { + ShowInfo("achievements saved for char %d (total: %d, saved: %d)\n", char_id, VECTOR_LENGTH(*p), rows); + + /* Sync with inter-db acheivements. */ + VECTOR_CLEAR(*cp); + VECTOR_ENSURE(*cp, VECTOR_LENGTH(*p), 1); + VECTOR_PUSHARRAY(*cp, VECTOR_DATA(*p), VECTOR_LENGTH(*p)); + } + + return rows; +} + +/** + * Retrieves all achievements of a character. + * @param[in] char_id character identifier. + * @param[out] cp pointer to character achievements structure. + * @return true on success, false on failure. + */ +static bool inter_achievement_fromsql(int char_id, struct char_achievements *cp) +{ + StringBuf buf; + char *data; + int i = 0, num_rows = 0; + + nullpo_ret(cp); + + Assert_ret(char_id > 0); + + // char_achievements (`char_id`, `ach_id`, `completed_at`, `rewarded_at`, `obj_0`, `obj_2`, ...`obj_9`) + StrBuf->Init(&buf); + StrBuf->AppendStr(&buf, "SELECT `ach_id`, `completed_at`, `rewarded_at`"); + for (i = 0; i < MAX_ACHIEVEMENT_OBJECTIVES; i++) + StrBuf->Printf(&buf, ", `obj_%d`", i); + StrBuf->Printf(&buf, " FROM `%s` WHERE `char_id` = '%d' ORDER BY `ach_id`", char_achievement_db, char_id); + + if (SQL_ERROR == SQL->QueryStr(inter->sql_handle, StrBuf->Value(&buf))) { + Sql_ShowDebug(inter->sql_handle); + StrBuf->Destroy(&buf); + return false; + } + + VECTOR_CLEAR(*cp); + + if ((num_rows = (int) SQL->NumRows(inter->sql_handle)) != 0) { + int j = 0; + + VECTOR_ENSURE(*cp, num_rows, 1); + + for (i = 0; i < num_rows && SQL_SUCCESS == SQL->NextRow(inter->sql_handle); i++) { + struct achievement t_ach = { 0 }; + SQL->GetData(inter->sql_handle, 0, &data, NULL); t_ach.id = atoi(data); + SQL->GetData(inter->sql_handle, 1, &data, NULL); t_ach.completed_at = atoi(data); + SQL->GetData(inter->sql_handle, 2, &data, NULL); t_ach.rewarded_at = atoi(data); + /* Objectives */ + for (j = 0; j < MAX_ACHIEVEMENT_OBJECTIVES; j++) { + SQL->GetData(inter->sql_handle, j + 3, &data, NULL); + t_ach.objective[j] = atoi(data); + } + /* Add Entry */ + VECTOR_PUSH(*cp, t_ach); + } + } + + SQL->FreeResult(inter->sql_handle); + + StrBuf->Destroy(&buf); + + if (num_rows > 0) + ShowInfo("achievements loaded for char %d (total: %d)\n", char_id, num_rows); + + return true; +} + +/** + * Handles checking of map server packets and calls appropriate functions. + * @param fd socket descriptor. + * @return 0 on failure, 1 on succes. + */ +static int inter_achievement_parse_frommap(int fd) +{ + RFIFOHEAD(fd); + + switch (RFIFOW(fd,0)) { + case 0x3012: + mapif->pLoadAchievements(fd); + break; + case 0x3013: + mapif->pSaveAchievements(fd); + break; + default: + return 0; + } + + return 1; +} + +/** + * Initialization function + */ +static int inter_achievement_sql_init(void) +{ + // Initialize the loaded db storage. + // used as a comparand against map-server achievement data before saving. + inter_achievement->char_achievements = idb_alloc(DB_OPT_RELEASE_DATA); + return 1; +} + +/** + * This function ensures idb's entry. + */ +static struct DBData inter_achievement_ensure_char_achievements(union DBKey key, va_list args) +{ + struct char_achievements *ca = NULL; + + CREATE(ca, struct char_achievements, 1); + VECTOR_INIT(*ca); + + return DB->ptr2data(ca); +} + +/** + * Cleaning function called through db_destroy() + */ +static int inter_achievement_char_achievements_clear(union DBKey key, struct DBData *data, va_list args) +{ + struct char_achievements *ca = DB->data2ptr(data); + + VECTOR_CLEAR(*ca); + + return 0; +} + +/** + * Finalization function. + */ +static void inter_achievement_sql_final(void) +{ + inter_achievement->char_achievements->destroy(inter_achievement->char_achievements, inter_achievement->char_achievements_clear); +} + +/** + * Inter-achievement interface. + */ +void inter_achievement_defaults(void) +{ + inter_achievement = &inter_achievement_s; + /* */ + inter_achievement->ensure_char_achievements = inter_achievement_ensure_char_achievements; + /* */ + inter_achievement->sql_init = inter_achievement_sql_init; + inter_achievement->sql_final = inter_achievement_sql_final; + /* */ + inter_achievement->tosql = inter_achievement_tosql; + inter_achievement->fromsql = inter_achievement_fromsql; + /* */ + inter_achievement->parse_frommap = inter_achievement_parse_frommap; + inter_achievement->char_achievements_clear = inter_achievement_char_achievements_clear; +} diff --git a/src/char/int_achievement.h b/src/char/int_achievement.h new file mode 100644 index 000000000..4a44a798d --- /dev/null +++ b/src/char/int_achievement.h @@ -0,0 +1,53 @@ +/** +* This file is part of Hercules. +* http://herc.ws - http://github.com/HerculesWS/Hercules +* +* Copyright (C) 2017 Hercules Dev Team +* Copyright (C) Smokexyz +* +* Hercules is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +#ifndef CHAR_INT_ACHIEVEMENT_H +#define CHAR_INT_ACHIEVEMENT_H + +#include "common/hercules.h" +#include "common/db.h" + +struct achievement; +struct char_achievements; + +/** + * inter_achievement Interface + */ +struct inter_achievement_interface { + struct DBMap *char_achievements; + /* */ + int (*sql_init) (void); + void (*sql_final) (void); + /* */ + int (*tosql) (int char_id, struct char_achievements *cp, const struct char_achievements *p); + bool (*fromsql) (int char_id, struct char_achievements *a); + /* */ + struct DBData(*ensure_char_achievements) (union DBKey key, va_list args); + int (*char_achievements_clear) (union DBKey key, struct DBData *data, va_list args); + /* */ + int (*parse_frommap) (int fd); +}; + +#ifdef HERCULES_CORE +void inter_achievement_defaults(void); +#endif // HERCULES_CORE + +HPShared struct inter_achievement_interface *inter_achievement; +#endif /* CHAR_INT_ACHIEVEMENT_H */ diff --git a/src/map/Makefile.in b/src/map/Makefile.in index 7c04a4f37..1bef380e1 100644 --- a/src/map/Makefile.in +++ b/src/map/Makefile.in @@ -40,14 +40,14 @@ MT19937AR_D = $(THIRDPARTY_D)/mt19937ar MT19937AR_OBJ = $(MT19937AR_D)/mt19937ar.o MT19937AR_H = $(MT19937AR_D)/mt19937ar.h -MAP_C = atcommand.c battle.c battleground.c buyingstore.c channel.c chat.c \ +MAP_C = achievement.c atcommand.c battle.c battleground.c buyingstore.c channel.c chat.c \ chrif.c clan.c clif.c date.c duel.c elemental.c guild.c homunculus.c HPMmap.c \ instance.c intif.c irc-bot.c itemdb.c log.c mail.c map.c mapreg_sql.c \ mercenary.c mob.c npc.c npc_chat.c party.c path.c pc.c pc_groups.c \ pet.c quest.c rodex.c script.c searchstore.c skill.c status.c storage.c \ trade.c unit.c vending.c MAP_OBJ = $(addprefix obj_sql/, $(patsubst %c,%o,$(MAP_C))) -MAP_H = atcommand.h battle.h battleground.h buyingstore.h channel.h chat.h \ +MAP_H = achievement.h atcommand.h battle.h battleground.h buyingstore.h channel.h chat.h \ chrif.h clan.h clif.h date.h duel.h elemental.h guild.h homunculus.h HPMmap.h \ instance.h intif.h irc-bot.h itemdb.h log.h mail.h map.h mapreg.h \ mercenary.h messages.h messages_main.h messages_re.h messages_zero.h \ diff --git a/src/map/achievement.c b/src/map/achievement.c new file mode 100644 index 000000000..67807c734 --- /dev/null +++ b/src/map/achievement.c @@ -0,0 +1,1878 @@ +/** +* This file is part of Hercules. +* http://herc.ws - http://github.com/HerculesWS/Hercules +* +* Copyright (C) 2017 Hercules Dev Team +* Copyright (C) Smokexyz +* +* Hercules is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +#define HERCULES_CORE + +#include "map/achievement.h" + +#include "map/itemdb.h" +#include "map/mob.h" +#include "map/party.h" +#include "map/pc.h" +#include "map/script.h" + +#include "common/conf.h" +#include "common/db.h" +#include "common/memmgr.h" +#include "common/nullpo.h" +#include "common/showmsg.h" +#include "common/strlib.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static struct achievement_interface achievement_s; +struct achievement_interface *achievement; + +/** + * Retrieve an achievement via it's ID. + * @param aid as the achievement ID. + * @return NULL or pointer to the achievement data. + */ +static const struct achievement_data *achievement_get(int aid) +{ + return (struct achievement_data *) idb_get(achievement->db, aid); +} + +/** + * Searches the provided achievement data for an achievement, + * optionally creates a new one if no key exists. + * @param[in] sd a pointer to map_session_data. + * @param[in] aid ID of the achievement provided as key. + * @param[in] create new key creation flag. + * @return pointer to the session's achievement data. + */ +static struct achievement *achievement_ensure(struct map_session_data *sd, const struct achievement_data *ad) +{ + struct achievement *s_ad = NULL; + int i = 0; + + nullpo_retr(NULL, sd); + nullpo_retr(NULL, ad); + + /* Lookup for achievement entry */ + ARR_FIND(0, VECTOR_LENGTH(sd->achievement), i, (s_ad = &VECTOR_INDEX(sd->achievement, i)) && s_ad->id == ad->id); + + if (i == VECTOR_LENGTH(sd->achievement)) { + struct achievement ta = { 0 }; + ta.id = ad->id; + + VECTOR_ENSURE(sd->achievement, 1, 1); + VECTOR_PUSH(sd->achievement, ta); + + s_ad = &VECTOR_LAST(sd->achievement); + } + + return s_ad; +} + +/** + * Calculates the achievement's totals via reference. + * @param[in] sd pointer to map_session_data + * @param[out] tota_points pointer to total points var + * @param[out] completed pointer to total var + * @param[out] rank pointer to completed var + * @param[out] curr_rank_points pointer to achievement rank var + */ +static void achievement_calculate_totals(const struct map_session_data *sd, int *total_points, int *completed, int *rank, int *curr_rank_points) +{ + const struct achievement *a = NULL; + const struct achievement_data *ad = NULL; + int tmp_curr_points = 0; + int tmp_total_points = 0; + int tmp_total_completed = 0; + int tmp_rank = 0; + int i = 0; + + nullpo_retv(sd); + + for (i = 0; i < VECTOR_LENGTH(sd->achievement); i++) { + a = &VECTOR_INDEX(sd->achievement, i); + + if ((ad = achievement->get(a->id)) == NULL) + continue; + + if (a->completed_at != 0) { + tmp_total_points += ad->points; + tmp_total_completed++; + } + } + + if (tmp_total_points > 0) { + tmp_curr_points = tmp_total_points; + for (i = 0; i < MAX_ACHIEVEMENT_RANKS + && tmp_curr_points >= VECTOR_INDEX(achievement->rank_exp, i) + && i < VECTOR_LENGTH(achievement->rank_exp); i++) { + tmp_curr_points -= VECTOR_INDEX(achievement->rank_exp, i); + tmp_rank++; + } + } + + if (total_points != NULL) + *total_points = tmp_total_points; + + if (completed != NULL) + *completed = tmp_total_completed; + + if (rank != NULL) + *rank = tmp_rank; + + if (curr_rank_points != NULL) + *curr_rank_points = tmp_curr_points; +} + +/** + * Checks whether all objectives of the achievement are completed. + * @param[in] sd as the map_session_data pointer + * @param[in] ad as the achievement_data pointer + * @return true if complete, false if not. + */ +static bool achievement_check_complete(struct map_session_data *sd, const struct achievement_data *ad) +{ + int i; + struct achievement *ach = NULL; + + nullpo_retr(false, sd); + nullpo_retr(false, ad); + + if ((ach = achievement->ensure(sd, ad)) == NULL) + return false; + for (i = 0; i < VECTOR_LENGTH(ad->objective); i++) + if (ach->objective[i] < VECTOR_INDEX(ad->objective, i).goal) + return false; + + return true; +} + +/** + * Compares the progress of an objective against it's goal. + * Increments the progress of the objective by the specified amount, towards the goal. + * @param sd [in] as a pointer to map_session_data + * @param ad [in] as a pointer to the achievement_data + * @param obj_idx [in] as the index of the objective. + * @param progress [in] as the progress of the objective to be added. + */ +static void achievement_progress_add(struct map_session_data *sd, const struct achievement_data *ad, unsigned int obj_idx, int progress) +{ + struct achievement *ach = NULL; + + nullpo_retv(sd); + nullpo_retv(ad); + + Assert_retv(progress != 0); + Assert_retv(obj_idx < VECTOR_LENGTH(ad->objective)); + + if ((ach = achievement->ensure(sd, ad)) == NULL) + return; + + if (ach->completed_at) + return; // ignore the call if the achievement is completed. + + // Check and increment the objective count. + if (!ach->objective[obj_idx] || ach->objective[obj_idx] < VECTOR_INDEX(ad->objective, obj_idx).goal) { + ach->objective[obj_idx] = min(progress + ach->objective[obj_idx], VECTOR_INDEX(ad->objective, obj_idx).goal); + + // Check if the Achievement is complete. + if (achievement->check_complete(sd, ad)) { + achievement->validate_achieve(sd, ad->id); + ach->completed_at = time(NULL); + } + + // update client. + clif->achievement_send_update(sd->fd, sd, ad); + } +} + +/** + * Compare an absolute progress value against the goal of an objective. + * Does not add/increase progress. + * @param sd [in] pointer to map-server session data. + * @param ad [in] pointer to achievement data. + * @param obj_idx [in] index of the objective in question. + * @param progress progress of the objective in question. + */ +static void achievement_progress_set(struct map_session_data *sd, const struct achievement_data *ad, unsigned int obj_idx, int progress) +{ + struct achievement *ach = NULL; + + nullpo_retv(sd); + nullpo_retv(ad); + + Assert_retv(progress != 0); + Assert_retv(obj_idx < VECTOR_LENGTH(ad->objective)); + + if (progress >= VECTOR_INDEX(ad->objective, obj_idx).goal) { + + if ((ach = achievement->ensure(sd, ad)) == NULL) + return; + + if (ach->completed_at) + return; + + ach->objective[obj_idx] = VECTOR_INDEX(ad->objective, obj_idx).goal; + + if (achievement->check_complete(sd, ad)) { + achievement->validate_achieve(sd, ad->id); + ach->completed_at = time(NULL); + } + + clif->achievement_send_update(sd->fd, sd, ad); + } +} + +/** +* Checks if the given criteria satisfies the achievement's objective. +* @param objective [in] pointer to the achievement's objectives data. +* @param criteria [in] pointer to the current session's criteria as a comparand. +* @return true if all criteria are satisfied, else false. +*/ +static bool achievement_check_criteria(const struct achievement_objective *objective, const struct achievement_objective *criteria) +{ + int i = 0, j = 0; + + nullpo_retr(false, objective); + nullpo_retr(false, criteria); + + /* Item Id */ + if (objective->unique_type == CRITERIA_UNIQUE_ITEM_ID && objective->unique.itemid != criteria->unique.itemid) + return false; + /* Weapon Level */ + else if (objective->unique_type == CRITERIA_UNIQUE_WEAPON_LV && objective->unique.weapon_lv != criteria->unique.weapon_lv) + return false; + /* Status Types */ + else if (objective->unique_type == CRITERIA_UNIQUE_STATUS_TYPE && objective->unique.status_type != criteria->unique.status_type) + return false; + /* Achievement Id */ + else if (objective->unique_type == CRITERIA_UNIQUE_ACHIEVE_ID && objective->unique.achieve_id != criteria->unique.achieve_id) + return false; + + /* Monster Id */ + if (objective->mobid > 0 && objective->mobid != criteria->mobid) + return false; + + /* Item Type */ + if (objective->item_type > 0 && (objective->item_type & (2 << criteria->item_type)) == 0) + return false; + + /* Job Ids */ + for (i = 0; i < VECTOR_LENGTH(objective->jobid); i++) { + ARR_FIND(0, VECTOR_LENGTH(criteria->jobid), j, VECTOR_INDEX(criteria->jobid, j) != VECTOR_INDEX(objective->jobid, i)); + if (j < VECTOR_LENGTH(criteria->jobid)) + return false; + } + + return true; +} + +/** + * Validates an Achievement Objective of similar types. + * @param[in] sd as a pointer to the map session data. + * @param[in] type as the type of the achievement. + * @param[in] criteria as the criteria of the objective (mob id, job id etc.. 0 for no criteria). + * @param[in] progress as the current progress of the objective. + * @return total number of updated achievements on success, 0 on failure. + */ +static int achievement_validate_type(struct map_session_data *sd, enum achievement_types type, const struct achievement_objective *criteria, bool additive) +{ + int i = 0, total = 0; + struct achievement *ach = NULL; + + nullpo_ret(sd); + nullpo_ret(criteria); + + Assert_ret(criteria->goal != 0); + + if (type == ACH_QUEST) { + ShowError("achievement_validate_type: ACH_QUEST is not handled by this function. (use achievement_validate())\n"); + return 0; + } else if (type >= ACH_TYPE_MAX) { + ShowError("achievement_validate_type: Invalid Achievement Type %d! (min: %d, max: %d)\n", (int)type, (int)ACH_QUEST, (int)ACH_TYPE_MAX - 1); + return 0; + } + + /* Loop through all achievements of the type, checking for possible matches. */ + for (i = 0; i < VECTOR_LENGTH(achievement->category[type]); i++) { + int j = 0; + bool updated = false; + const struct achievement_data *ad = NULL; + + if ((ad = achievement->get(VECTOR_INDEX(achievement->category[type], i))) == NULL) + continue; + + for (j = 0; j < VECTOR_LENGTH(ad->objective); j++) { + // Check if objective criteria matches. + if (achievement->check_criteria(&VECTOR_INDEX(ad->objective, j), criteria) == false) + continue; + // Ensure availability of the achievement. + if ((ach = achievement->ensure(sd, ad)) == NULL) + return false; + // Criteria passed, check if not completed and update progress. + if ((ach->completed_at == 0 && ach->objective[j] < VECTOR_INDEX(ad->objective, j).goal)) { + if (additive == true) + achievement->progress_add(sd, ad, j, criteria->goal); + else + achievement->progress_set(sd, ad, j, criteria->goal); + updated = true; + } + } + + if (updated == true) + total++; + } + + return total; +} + +/** + * Validates any achievement's specific objective index. + * @param[in] sd pointer to the session data. + * @param[in] aid ID of the achievement. + * @param[in] index index of the objective. + * @param[in] progress progress to be added towards the goal. + */ +static bool achievement_validate(struct map_session_data *sd, int aid, unsigned int obj_idx, int progress, bool additive) +{ + const struct achievement_data *ad = NULL; + struct achievement *ach = NULL; + + nullpo_retr(false, sd); + Assert_retr(false, progress > 0); + Assert_retr(false, obj_idx < MAX_ACHIEVEMENT_OBJECTIVES); + + if ((ad = achievement->get(aid)) == NULL) { + ShowError("achievement_validate: Invalid Achievement %d provided.", aid); + return false; + } + + // Ensure availability of the achievement. + if ((ach = achievement->ensure(sd, ad)) == NULL) + return false; + + // Check if not completed and update progress. + if ((!ach->completed_at && ach->objective[obj_idx] < VECTOR_INDEX(ad->objective, obj_idx).goal)) { + if (additive == true) + achievement->progress_add(sd, ad, obj_idx, progress); + else + achievement->progress_set(sd, ad, obj_idx, progress); + } + + return true; +} + +/** + * Validates monster kill type objectives. + * @type ACH_KILL_MOB_CLASS + * @param[in] sd pointer to session data. + * @param[in] mob_id (criteria) class of the monster checked for. + * @param[in] progress (goal) progress to be added. + * @see achievement_vaildate_type() + */ +static void achievement_validate_mob_kill(struct map_session_data *sd, int mob_id) +{ + struct achievement_objective criteria = { 0 }; + + nullpo_retv(sd); + Assert_retv(mob_id > 0 && mob->db(mob_id) != NULL); + + if (sd->achievements_received == false) + return; + + criteria.mobid = mob_id; + criteria.goal = 1; + + achievement->validate_type(sd, ACH_KILL_MOB_CLASS, &criteria, true); +} + +/** + * Validate monster damage type objectives. + * @types ACH_DAMAGE_MOB_REC_MAX + * ACH_DAMAGE_MOB_REC_TOTAL + * @param[in] sd pointer to session data. + * @param[in] damage amount of damage received/dealt. + * @param received received/dealt boolean switch. + */ +static void achievement_validate_mob_damage(struct map_session_data *sd, unsigned int damage, bool received) +{ + struct achievement_objective criteria = { 0 }; + + nullpo_retv(sd); + Assert_retv(damage > 0); + + if (sd->achievements_received == false) + return; + + criteria.goal = (int) damage; + + if (received) { + achievement->validate_type(sd, ACH_DAMAGE_MOB_REC_MAX, &criteria, false); + achievement->validate_type(sd, ACH_DAMAGE_MOB_REC_TOTAL, &criteria, true); + } else { + achievement->validate_type(sd, ACH_DAMAGE_MOB_MAX, &criteria, false); + achievement->validate_type(sd, ACH_DAMAGE_MOB_TOTAL, &criteria, true); + } + +} + +/** + * Validate player kill (PVP) achievements. + * @types ACH_KILL_PC_TOTAL + * ACH_KILL_PC_JOB + * ACH_KILL_PC_JOBTYPE + * @param[in] sd pointer to killed player's session data. + * @param[in] dstsd pointer to killer's session data. + */ +static void achievement_validate_pc_kill(struct map_session_data *sd, struct map_session_data *dstsd) +{ + struct achievement_objective criteria = { 0 }; + + nullpo_retv(sd); + nullpo_retv(dstsd); + + if (sd->achievements_received == false) + return; + + criteria.goal = 1; + + /* */ + achievement->validate_type(sd, ACH_KILL_PC_TOTAL, &criteria, true); + + /* */ + VECTOR_INIT(criteria.jobid); + VECTOR_ENSURE(criteria.jobid, 1, 1); + VECTOR_PUSH(criteria.jobid, dstsd->status.class); + + /* Job class */ + achievement->validate_type(sd, ACH_KILL_PC_JOB, &criteria, true); + /* Job Type */ + achievement->validate_type(sd, ACH_KILL_PC_JOBTYPE, &criteria, true); + + VECTOR_CLEAR(criteria.jobid); +} + +/** + * Validate player kill (PVP) achievements. + * @types ACH_DAMAGE_PC_MAX + * ACH_DAMAGE_PC_TOTAL + * ACH_DAMAGE_PC_REC_MAX + * ACH_DAMAGE_PC_REC_TOTAL + * @param[in] sd pointer to source player's session data. + * @param[in] dstsd pointer to target player's session data. + * @param[in] damage amount of damage dealt / received. + */ +static void achievement_validate_pc_damage(struct map_session_data *sd, struct map_session_data *dstsd, unsigned int damage) +{ + struct achievement_objective criteria = { 0 }; + + nullpo_retv(sd); + + if (sd->achievements_received == false) + return; + + if (damage == 0) + return; + + criteria.goal = (int) damage; + + /* */ + achievement->validate_type(sd, ACH_DAMAGE_PC_MAX, &criteria, false); + achievement->validate_type(sd, ACH_DAMAGE_PC_TOTAL, &criteria, true); + + /* */ + achievement->validate_type(dstsd, ACH_DAMAGE_PC_REC_MAX, &criteria, false); + achievement->validate_type(dstsd, ACH_DAMAGE_PC_REC_TOTAL, &criteria, true); +} + +/** + * Validates job change objectives. + * @type ACH_JOB_CHANGE + * @param[in] sd pointer to session data. + * @see achivement_validate_type() + */ +static void achievement_validate_jobchange(struct map_session_data *sd) +{ + struct achievement_objective criteria = { 0 }; + + nullpo_retv(sd); + + if (sd->achievements_received == false) + return; + + VECTOR_INIT(criteria.jobid); + VECTOR_ENSURE(criteria.jobid, 1, 1); + VECTOR_PUSH(criteria.jobid, sd->status.class); + + criteria.goal = 1; + + achievement->validate_type(sd, ACH_JOB_CHANGE, &criteria, false); + + VECTOR_CLEAR(criteria.jobid); +} + +/** + * Validates stat type objectives. + * @types ACH_STATUS + * ACH_STATUS_BY_JOB + * ACH_STATUS_BY_JOBTYPE + * @param[in] sd pointer to session data. + * @param[in] stat_type (criteria) status point type. (see status_point_types) + * @param[in] progress (goal) amount of absolute progress to check. + * @see achievement_validate_type() + */ +static void achievement_validate_stats(struct map_session_data *sd, enum status_point_types stat_type, int progress) +{ + struct achievement_objective criteria = { 0 }; + + nullpo_retv(sd); + Assert_retv(progress > 0); + + if (sd->achievements_received == false) + return; + + if (!achievement_valid_status_types(stat_type)) { + ShowError("achievement_validate_stats: Invalid status type %d given.\n", (int) stat_type); + return; + } + + criteria.unique.status_type = stat_type; + + criteria.goal = progress; + + achievement->validate_type(sd, ACH_STATUS, &criteria, false); + + VECTOR_INIT(criteria.jobid); + VECTOR_ENSURE(criteria.jobid, 1, 1); + VECTOR_PUSH(criteria.jobid, sd->status.class); + + /* Stat and Job class */ + achievement->validate_type(sd, ACH_STATUS_BY_JOB, &criteria, false); + + /* Stat and Job Type */ + achievement->validate_type(sd, ACH_STATUS_BY_JOBTYPE, &criteria, false); + + VECTOR_CLEAR(criteria.jobid); +} + +/** + * Validates chatroom creation type objectives. + * @types ACH_CHATROOM_CREATE + * ACH_CHATROOM_CREATE_DEAD + * @param[in] sd pointer to session data. + * @see achievement_validate_type() + */ +static void achievement_validate_chatroom_create(struct map_session_data *sd) +{ + struct achievement_objective criteria = { 0 }; + + nullpo_retv(sd); + + if (sd->achievements_received == false) + return; + + criteria.goal = 1; + + if (pc_isdead(sd)) { + achievement->validate_type(sd, ACH_CHATROOM_CREATE_DEAD, &criteria, true); + return; + } + + achievement->validate_type(sd, ACH_CHATROOM_CREATE, &criteria, true); +} + +/** + * Validates chatroom member count type objectives. + * @type ACH_CHATROOM_MEMBERS + * @param[in] sd pointer to session data. + * @param[in] progress (goal) amount of progress to be added. + * @see achievement_validate_type() + */ +static void achievement_validate_chatroom_members(struct map_session_data *sd, int progress) +{ + struct achievement_objective criteria = { 0 }; + + nullpo_retv(sd); + + if (sd->achievements_received == false) + return; + + Assert_retv(progress > 0); + + criteria.goal = progress; + + achievement->validate_type(sd, ACH_CHATROOM_MEMBERS, &criteria, false); +} + +/** + * Validates friend add type objectives. + * @type ACH_FRIEND_ADD + * @param[in] sd pointer to session data. + * @see achievement_validate_type() + */ +static void achievement_validate_friend_add(struct map_session_data *sd) +{ + struct achievement_objective criteria = { 0 }; + + nullpo_retv(sd); + + if (sd->achievements_received == false) + return; + + criteria.goal = 1; + + achievement->validate_type(sd, ACH_FRIEND_ADD, &criteria, true); +} + +/** + * Validates party creation type objectives. + * @type ACH_PARTY_CREATE + * @param[in] sd pointer to session data. + * @see achievement_validate_type() + */ +static void achievement_validate_party_create(struct map_session_data *sd) +{ + struct achievement_objective criteria = { 0 }; + + nullpo_retv(sd); + + if (sd->achievements_received == false) + return; + + criteria.goal = 1; + achievement->validate_type(sd, ACH_PARTY_CREATE, &criteria, true); +} + +/** + * Validates marriage type objectives. + * @type ACH_MARRY + * @param[in] sd pointer to session data. + * @see achievement_validate_type() + */ +static void achievement_validate_marry(struct map_session_data *sd) +{ + struct achievement_objective criteria = { 0 }; + + nullpo_retv(sd); + + if (sd->achievements_received == false) + return; + + criteria.goal = 1; + + achievement->validate_type(sd, ACH_MARRY, &criteria, true); +} + +/** + * Validates adoption type objectives. + * @types ACH_ADOPT_PARENT + * ACH_ADOPT_BABY + * @param[in] sd pointer to session data. + * @param[in] parent (type) boolean value to indicate if parent (true) or baby (false). + * @see achievement_validate_type() + */ +static void achievement_validate_adopt(struct map_session_data *sd, bool parent) +{ + struct achievement_objective criteria = { 0 }; + + nullpo_retv(sd); + + if (sd->achievements_received == false) + return; + + criteria.goal = 1; + + if (parent) + achievement->validate_type(sd, ACH_ADOPT_PARENT, &criteria, true); + else + achievement->validate_type(sd, ACH_ADOPT_BABY, &criteria, true); +} + +/** + * Validates zeny type objectives. + * @types ACH_ZENY_HOLD + * ACH_ZENY_GET_ONCE + * ACH_ZENY_GET_TOTAL + * ACH_ZENY_SPEND_ONCE + * ACH_ZENY_SPEND_TOTAL + * @param[in] sd pointer to session data. + * @param[in] amount (goal) amount of zeny earned or spent. + * @see achievement_validate_type() + */ +static void achievement_validate_zeny(struct map_session_data *sd, int amount) +{ + struct achievement_objective criteria = { 0 }; + + nullpo_retv(sd); + + if (sd->achievements_received == false) + return; + + Assert_retv(amount != 0); + + if (amount > 0) { + criteria.goal = sd->status.zeny; + achievement->validate_type(sd, ACH_ZENY_HOLD, &criteria, false); + criteria.goal = amount; + achievement->validate_type(sd, ACH_ZENY_GET_ONCE, &criteria, false); + achievement->validate_type(sd, ACH_ZENY_GET_TOTAL, &criteria, true); + } else { + criteria.goal = amount; + achievement->validate_type(sd, ACH_ZENY_SPEND_ONCE, &criteria, false); + achievement->validate_type(sd, ACH_ZENY_SPEND_TOTAL, &criteria, true); + } +} + +/** + * Validates equipment refinement type objectives. + * @types ACH_EQUIP_REFINE_SUCCESS + * ACH_EQUIP_REFINE_FAILURE + * ACH_EQUIP_REFINE_SUCCESS_TOTAL + * ACH_EQUIP_REFINE_FAILURE_TOTAL + * ACH_EQUIP_REFINE_SUCCESS_WLV + * ACH_EQUIP_REFINE_FAILURE_WLV + * ACH_EQUIP_REFINE_SUCCESS_ID + * ACH_EQUIP_REFINE_FAILURE_ID + * @param[in] sd pointer to session data. + * @param[in] idx Inventory index of the item. + * @param[in] success (type) boolean switch for failure / success. + * @see achievement_validate_type() + */ +static void achievement_validate_refine(struct map_session_data *sd, unsigned int idx, bool success) +{ + struct achievement_objective criteria = { 0 }; + struct item_data *id = NULL; + + nullpo_retv(sd); + Assert_retv(idx < MAX_INVENTORY); + + id = itemdb->exists(sd->status.inventory[idx].nameid); + + if (sd->achievements_received == false) + return; + + Assert_retv(idx < MAX_INVENTORY); + Assert_retv(id != NULL); + + criteria.goal = sd->status.inventory[idx].refine; + + /* Universal */ + achievement->validate_type(sd, + success ? ACH_EQUIP_REFINE_SUCCESS : ACH_EQUIP_REFINE_FAILURE, + &criteria, false); + + /* Total */ + criteria.goal = 1; + achievement->validate_type(sd, + success ? ACH_EQUIP_REFINE_SUCCESS_TOTAL : ACH_EQUIP_REFINE_FAILURE_TOTAL, + &criteria, true); + + /* By Weapon Level */ + if (id->type == IT_WEAPON) { + criteria.item_type = id->type; + criteria.unique.weapon_lv = id->wlv; + criteria.goal = sd->status.inventory[idx].refine; + achievement->validate_type(sd, + success ? ACH_EQUIP_REFINE_SUCCESS_WLV : ACH_EQUIP_REFINE_FAILURE_WLV, + &criteria, false); + criteria.item_type = 0; + criteria.unique.weapon_lv = 0; // cleanup + } + + /* By NameId */ + criteria.unique.itemid = id->nameid; + criteria.goal = sd->status.inventory[idx].refine; + achievement->validate_type(sd, + success ? ACH_EQUIP_REFINE_SUCCESS_ID : ACH_EQUIP_REFINE_FAILURE_ID, + &criteria, false); + criteria.unique.itemid = 0; // cleanup +} + +/** + * Validates item received type objectives. + * @types ACH_ITEM_GET_COUNT + * ACH_ITEM_GET_WORTH + * ACH_ITEM_GET_COUNT_ITEMTYPE + * @param[in] sd pointer to session data. + * @param[in] nameid (criteria) ID of the item. + * @param[in] amount (goal) amount of the item collected. + */ +static void achievement_validate_item_get(struct map_session_data *sd, int nameid, int amount) +{ + struct item_data *it = itemdb->exists(nameid); + struct achievement_objective criteria = { 0 }; + + nullpo_retv(sd); + + if (sd->achievements_received == false) + return; + + Assert_retv(amount > 0); + nullpo_retv(it); + + criteria.unique.itemid = it->nameid; + criteria.goal = amount; + achievement->validate_type(sd, ACH_ITEM_GET_COUNT, &criteria, false); + criteria.unique.itemid = 0; // cleanup + + /* Item Buy Value*/ + criteria.goal = max(it->value_buy, 1); + achievement->validate_type(sd, ACH_ITEM_GET_WORTH, &criteria, false); + + /* Item Type */ + criteria.item_type = it->type; + criteria.goal = 1; + achievement->validate_type(sd, ACH_ITEM_GET_COUNT_ITEMTYPE, &criteria, false); + criteria.item_type = 0; // cleanup +} + +/** + * Validates item sold type objectives. + * @type ACH_ITEM_SELL_WORTH + * @param[in] sd pointer to session data. + * @param[in] nameid Item Id in question. + * @param[in] amount amount of item in question. + */ +static void achievement_validate_item_sell(struct map_session_data *sd, int nameid, int amount) +{ + struct item_data *it = itemdb->exists(nameid); + struct achievement_objective criteria = { 0 }; + + nullpo_retv(sd); + + if (sd->achievements_received == false) + return; + + Assert_retv(amount > 0); + nullpo_retv(it); + + criteria.unique.itemid = it->nameid; + + criteria.goal = max(it->value_sell, 1); + + achievement->validate_type(sd, ACH_ITEM_SELL_WORTH, &criteria, false); +} + +/** + * Validates achievement type objectives. + * @type ACH_ACHIEVE + * @param[in] sd pointer to session data. + * @param[in] achid (criteria) achievement id. + */ +static void achievement_validate_achieve(struct map_session_data *sd, int achid) +{ + const struct achievement_data *ad = achievement->get(achid); + struct achievement_objective criteria = { 0 }; + + nullpo_retv(sd); + nullpo_retv(ad); + + if (sd->achievements_received == false) + return; + + Assert_retv(achid > 0); + + criteria.unique.achieve_id = ad->id; + + criteria.goal = 1; + + achievement->validate_type(sd, ACH_ACHIEVE, &criteria, false); +} + +/** + * Validates taming type objectives. + * @type ACH_PET_CREATE + * @param[in] sd pointer to session data. + * @param[in] class (criteria) class of the monster tamed. + */ +static void achievement_validate_taming(struct map_session_data *sd, int class) +{ + struct achievement_objective criteria = { 0 }; + + nullpo_retv(sd); + + if (sd->achievements_received == false) + return; + + Assert_retv(class > 0); + Assert_retv(mob->db(class) != mob->dummy); + + criteria.mobid = class; + criteria.goal = 1; + + achievement->validate_type(sd, ACH_PET_CREATE, &criteria, true); +} + +/** + * Validated achievement rank type objectives. + * @type ACH_ACHIEVEMENT_RANK + * @param[in] sd pointer to session data. + * @param[in] rank (goal) rank of earned. + */ +static void achievement_validate_achievement_rank(struct map_session_data *sd, int rank) +{ + struct achievement_objective criteria = { 0 }; + + nullpo_retv(sd); + + if (sd->achievements_received == false) + return; + + Assert_retv(rank >= 0 && rank <= VECTOR_LENGTH(achievement->rank_exp)); + + criteria.goal = 1; + + achievement->validate_type(sd, ACH_ACHIEVEMENT_RANK, &criteria, false); +} + +/** + * Verifies if an achievement type requires a criteria field. + * @param[in] type achievement type in question. + * @return true if required, false if not. + */ +static bool achievement_type_requires_criteria(enum achievement_types type) +{ + if (type == ACH_KILL_PC_JOB + || type == ACH_KILL_PC_JOBTYPE + || type == ACH_KILL_MOB_CLASS + || type == ACH_JOB_CHANGE + || type == ACH_STATUS + || type == ACH_STATUS_BY_JOB + || type == ACH_STATUS_BY_JOBTYPE + || type == ACH_EQUIP_REFINE_SUCCESS_WLV + || type == ACH_EQUIP_REFINE_FAILURE_WLV + || type == ACH_EQUIP_REFINE_SUCCESS_ID + || type == ACH_EQUIP_REFINE_FAILURE_ID + || type == ACH_ITEM_GET_COUNT + || type == ACH_PET_CREATE + || type == ACH_ACHIEVE) + return true; + + return false; +} + +/** + * Parses the Achievement Ranks. + * @read db/achievement_rank_db.conf + */ +static void achievement_readdb_ranks(void) +{ + const char *filename = "db/achievement_rank_db.conf"; + struct config_t ar_conf = { 0 }; + struct config_setting_t *ardb = NULL, *conf = NULL; + int entry = 0; + + if (!libconfig->load_file(&ar_conf, filename)) + return; // report error. + + if (!(ardb = libconfig->setting_get_member(ar_conf.root, "achievement_rank_db"))) { + ShowError("achievement_readdb_ranks: Could not process contents of file '%s', skipping...\n", filename); + libconfig->destroy(&ar_conf); + return; + } + + while (entry < libconfig->setting_length(ardb) && entry < MAX_ACHIEVEMENT_RANKS) { + char rank[8]; + + if (!(conf = libconfig->setting_get_elem(ardb, entry))) { + ShowError("achievement_readdb_ranks: Could not read value for entry %d, skipping...\n", entry+1); + continue; + } + + entry++; // Rank counter; + + sprintf(rank, "Rank%d", entry); + + if (strcmp(rank, config_setting_name(conf)) == 0) { + int exp = 0; + + if ((exp = libconfig->setting_get_int(conf)) <= 0) { + ShowError("achievement_readdb_ranks: Invalid value provided for %s in '%s'.\n", rank, filename); + continue; + } + + VECTOR_ENSURE(achievement->rank_exp, 1, 1); + VECTOR_PUSH(achievement->rank_exp, exp); + } else { + ShowWarning("achievement_readdb_ranks: Ranks are not in order! Ignoring all ranks after Rank %d...\n", entry); + break; // break if elements are not in order or rank doesn't exist. + } + } + + if (libconfig->setting_length(ardb) > MAX_ACHIEVEMENT_RANKS) + ShowWarning("achievement_rankdb_ranks: Maximum number of achievement ranks exceeded. Skipping all after entry %d...\n", entry); + + libconfig->destroy(&ar_conf); + + if (!entry) { + ShowError("achievement_readdb_ranks: No ranks provided in '%s'!\n", filename); + return; + } + + ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", entry, filename); +} + +/** + * Validates Mob Id criteria of an objective while parsing an achievement entry. + * @param[in] t pointer to the config setting. + * @param[out] obj pointer to the achievement objective entry being parsed. + * @param[in] type Type of the achievement being parsed. + * @param[in] entry_id Id of the entry being parsed. + * @param[in] obj_idx Index of the objective entry being parsed. + * @return false on failure, true on success + */ +static bool achievement_readdb_validate_criteria_mobid(const struct config_setting_t *t, struct achievement_objective *obj, enum achievement_types type, int entry_id, int obj_idx) +{ + int val = 0; + const char *string = NULL; + + nullpo_retr(false, t); + nullpo_retr(false, obj); + + if (libconfig->setting_lookup_int(t, "MobId", &val)) { + if (mob->db_checkid(val) == 0) { + ShowError("achievement_readdb_validate_criteria_mobid: Non-existant monster with ID %id provided (Achievement: %d, Objective: %d). Skipping...\n", val, entry_id, obj_idx); + return false; + } + + obj->mobid = val; + } else if (libconfig->setting_lookup_string(t, "MobId", &string)) { + if (!script->get_constant(string, &val)) { + ShowError("achievement_readdb_validate_criteria_mobid: Non-existant constant %s provided (Achievement: %d, Objective: %d). Skipping...\n", string, entry_id, obj_idx); + return false; + } + + obj->mobid = val; + } else if (achievement_criteria_mobid(type)) { + ShowError("achievement_readdb_validate_criteria_mobid: Achievement type of ID %d requires MobId as objective criteria, setting not provided. Skipping...\n", entry_id); + return false; + } + + return true; +} + +/** + * Validates Job Id criteria of an objective while parsing an achievement entry. + * @param[in] t pointer to the config setting. + * @param[out] obj pointer to the achievement objective entry being parsed. + * @param[in] type Type of the achievement being parsed. + * @param[in] entry_id Id of the entry being parsed. + * @param[in] obj_idx Index of the objective entry being parsed. + * @return false on failure, true on success. + */ +static bool achievement_readdb_validate_criteria_jobid(const struct config_setting_t *t, struct achievement_objective *obj, enum achievement_types type, int entry_id, int obj_idx) +{ + int job_id = 0; + const char *string = NULL; + struct config_setting_t *tt = NULL; + + nullpo_retr(false, t); + nullpo_retr(false, obj); + + // initialize the buffered objective jobid vector. + VECTOR_INIT(obj->jobid); + + if (libconfig->setting_lookup_int(t, "JobId", &job_id)) { + if (pc->jobid2mapid(job_id) == -1) { + ShowError("achievement_readdb_validate_criteria_jobid: Invalid JobId %d provided (Achievement: %d, Objective: %d). Skipping...\n", job_id, entry_id, obj_idx); + return false; + } + + VECTOR_ENSURE(obj->jobid, 1, 1); + VECTOR_PUSH(obj->jobid, job_id); + } else if (libconfig->setting_lookup_string(t, "JobId", &string)) { + if (script->get_constant(string, &job_id) == false) { + ShowError("achievement_readdb_validate_criteria_jobid: Invalid JobId %d provided (Achievement: %d, Objective: %d). Skipping...\n", job_id, entry_id, obj_idx); + return false; + } + + VECTOR_ENSURE(obj->jobid, 1, 1); + VECTOR_PUSH(obj->jobid, job_id); + } else if ((tt = libconfig->setting_get_member(t, "JobId")) && config_setting_is_array(tt)) { + int j = 0; + + while (j < libconfig->setting_length(tt)) { + if ((job_id = libconfig->setting_get_int_elem(tt, j)) == 0) { + if ((string = libconfig->setting_get_string_elem(tt, j)) != NULL && script->get_constant(string, &job_id) == false) { + ShowError("achievement_readdb_validate_criteria_jobid: Invalid JobId provided at index %d (Achievement: %d, Objective: %d). Skipping...\n", j, entry_id, obj_idx); + continue; + } + } + + /* Ensure size and allocation */ + VECTOR_ENSURE(obj->jobid, 1, 1); + /* push buffer */ + VECTOR_PUSH(obj->jobid, job_id); + j++; + } + } else if (achievement_criteria_jobid(type)) { + ShowError("achievement_readdb_validate_criteria_jobid: Achievement type of ID %d requires a JobId field in the objective criteria, setting not provided. Skipping...\n", entry_id); + return false; + } + + return true; + +} + +/** + * Validates Item Id criteria of an objective while parsing an achievement entry. + * @param[in] t pointer to the config setting. + * @param[out] obj pointer to the achievement objective entry being parsed. + * @param[in] type Type of the achievement being parsed. + * @param[in] entry_id Id of the entry being parsed. + * @param[in] obj_idx Index of the objective entry being parsed. + * @return false on failure, true on success. + */ +static bool achievement_readdb_validate_criteria_itemid(const struct config_setting_t *t, struct achievement_objective *obj, enum achievement_types type, int entry_id, int obj_idx) +{ + int val = 0; + const char *string = NULL; + + nullpo_retr(false, t); + nullpo_retr(false, obj); + + + if (libconfig->setting_lookup_int(t, "ItemId", &val)) { + if (itemdb->exists(val) == NULL) { + ShowError("achievement_readdb_validate_criteria_itemid: Invalid ItemID %d provided (Achievement: %d, Objective: %d). Skipping...\n", val, entry_id, obj_idx); + return false; + } else if (obj->unique_type != CRITERIA_UNIQUE_NONE) { + ShowError("achievement_readdb_validate_criteria_itemid: Unique criteria has already been set to type %d. (Achievement: %d, Objective: %d). Skipping...\n", (int) obj->unique_type, entry_id, obj_idx); + return false; + } + + obj->unique.itemid = val; + obj->unique_type = CRITERIA_UNIQUE_ITEM_ID; + } else if (libconfig->setting_lookup_string(t, "ItemId", &string)) { + if (script->get_constant(string, &val) == false) { + ShowError("achievement_readdb_validate_criteria_itemid: Invalid ItemID %d provided (Achievement: %d, Objective: %d). Skipping...\n", val, entry_id, obj_idx); + return false; + } else if (obj->unique_type != CRITERIA_UNIQUE_NONE) { + ShowError("achievement_readdb_validate_criteria_itemid: Unique criteria has already been set to type %d. (Achievement: %d, Objective: %d). Skipping...\n", (int) obj->unique_type, entry_id, obj_idx); + return false; + } + + obj->unique.itemid = val; + obj->unique_type = CRITERIA_UNIQUE_ITEM_ID; + } else if (achievement_criteria_itemid(type)) { + ShowError("achievement_readdb_validate_criteria_itemid: Criteria requires a ItemId field (Achievement: %d, Objective: %d). Skipping...\n", entry_id, obj_idx); + return false; + } + + return true; +} + +/** + * Validates Status Type criteria of an objective while parsing an achievement entry. + * @param[in] t pointer to the config setting. + * @param[out] obj pointer to the achievement objective entry being parsed. + * @param[in] type Type of the achievement being parsed. + * @param[in] entry_id Id of the entry being parsed. + * @param[in] obj_idx Index of the objective entry being parsed. + * @return false on failure, true on success. + */ +static bool achievement_readdb_validate_criteria_statustype(const struct config_setting_t *t, struct achievement_objective *obj, enum achievement_types type, int entry_id, int obj_idx) +{ + int val = 0; + const char *string = NULL; + + nullpo_retr(false, t); + nullpo_retr(false, obj); + + if (libconfig->setting_lookup_int(t, "StatusType", &val)) { + if (!achievement_valid_status_types(val)) { + ShowError("achievement_readdb_validate_criteria_statustype: Invalid StatusType %d provided (Achievement: %d, Objective: %d). Skipping...\n", val, entry_id, obj_idx); + return false; + } else if (obj->unique_type != CRITERIA_UNIQUE_NONE) { + ShowError("achievement_readdb_validate_criteria_statustype: Unique criteria has already been set to type %d. (Achievement: %d, Objective: %d). Skipping...\n", (int) obj->unique_type, entry_id, obj_idx); + return false; + } + + obj->unique.status_type = (enum status_point_types) val; + obj->unique_type = CRITERIA_UNIQUE_STATUS_TYPE; + } else if (libconfig->setting_lookup_string(t, "StatusType", &string)) { + if (strcmp(string, "SP_STR") == 0) val = SP_STR; + else if (strcmp(string, "SP_AGI") == 0) val = SP_AGI; + else if (strcmp(string, "SP_VIT") == 0) val = SP_VIT; + else if (strcmp(string, "SP_INT") == 0) val = SP_INT; + else if (strcmp(string, "SP_DEX") == 0) val = SP_DEX; + else if (strcmp(string, "SP_LUK") == 0) val = SP_LUK; + else if (strcmp(string, "SP_BASELEVEL") == 0) val = SP_BASELEVEL; + else if (strcmp(string, "SP_JOBLEVEL") == 0) val = SP_JOBLEVEL; + else val = SP_NONE; + + if (!achievement_valid_status_types(val)) { + ShowError("achievement_readdb_validate_criteria_statustype: Invalid StatusType %s provided (Achievement: %d, Objective: %d). Skipping...\n", string, entry_id, obj_idx); + return false; + } else if (obj->unique_type != CRITERIA_UNIQUE_NONE) { + ShowError("achievement_readdb_validate_criteria_statustype: Unique criteria has already been set to type %d. (Achievement: %d, Objective: %d). Skipping...\n", (int) obj->unique_type, entry_id, obj_idx); + return false; + } + + obj->unique.status_type = (enum status_point_types) val; + obj->unique_type = CRITERIA_UNIQUE_STATUS_TYPE; + } else if (achievement_criteria_stattype(type)) { + ShowError("achievement_readdb_validate_criteria_statustype: Criteria requires a StatusType field (Achievement: %d, Objective: %d). Skipping...\n", entry_id, obj_idx); + return false; + } + + return true; +} + +/** + * Validates Item Type criteria of an objective while parsing an achievement entry. + * @param[in] t pointer to the config setting. + * @param[out] obj pointer to the achievement objective entry being parsed. + * @param[in] type Type of the achievement being parsed. + * @param[in] entry_id Id of the entry being parsed. + * @param[in] obj_idx Index of the objective entry being parsed. + * @return false on failure, true on success. + */ +static bool achievement_readdb_validate_criteria_itemtype(const struct config_setting_t *t, struct achievement_objective *obj, enum achievement_types type, int entry_id, int obj_idx) +{ + int val = 0; + const char *string = NULL; + struct config_setting_t *tt = NULL; + + nullpo_retr(false, t); + nullpo_retr(false, obj); + + if (libconfig->setting_lookup_int(t, "ItemType", &val)) { + if (val < IT_HEALING || val > IT_MAX) { + ShowError("achievement_readdb_validate_criteria_itemtype: Invalid ItemType %d provided (Achievement: %d, Objective: %d). Skipping...\n", val, entry_id, obj_idx); + return false; + } + + if (val == IT_MAX) { + obj->item_type |= (2 << val) - 1; + } else { + obj->item_type |= (2 << val); + } + + } else if (libconfig->setting_lookup_string(t, "ItemType", &string)) { + if (!script->get_constant(string, &val) || val < IT_HEALING || val > IT_MAX) { + ShowError("achievement_readdb_validate_criteria_itemtype: Invalid ItemType %d provided (Achievement: %d, Objective: %d). Skipping...\n", val, entry_id, obj_idx); + return false; + } + + if (val == IT_MAX) { + obj->item_type |= (2 << val) - 1; + } else { + obj->item_type |= (2 << val); + } + } else if ((tt = libconfig->setting_get_member(t, "ItemType")) && config_setting_is_array(tt)) { + int j = 0; + uint32 it_type = 0; + + while (j < libconfig->setting_length(tt)) { + if ((val = libconfig->setting_get_int_elem(tt, j))) { + if (val < IT_HEALING || val >= IT_MAX) { + ShowError("achievement_readdb_validate_criteria_itemtype: Invalid ItemType %d provided (Achievement: %d, Objective: %d). Skipping...\n", val, entry_id, obj_idx); + continue; + } + if (val == IT_MAX) { + obj->item_type |= (2 << val) - 1; + } else { + obj->item_type |= (2 << val); + } + } else if ((string = libconfig->setting_get_string_elem(tt, j))) { + if (!script->get_constant(string, &val)) { + ShowError("achievement_readdb_validate_criteria_itemtype: Invalid ItemType %s provided (Achievement: %d, Objective: %d). Skipping...\n", string, entry_id, obj_idx); + continue; + } + + if (val == IT_MAX) { + obj->item_type |= (2 << val) - 1; + } else { + obj->item_type |= (2 << val); + } + } + j++; + } + + obj->item_type = it_type; + } else if (achievement_criteria_itemtype(type)) { + ShowError("achievement_readdb_validate_criteria_itemtype: Criteria requires a ItemType field (Achievement: %d, Objective: %d). Skipping...\n", entry_id, obj_idx); + return false; + } + + return true; +} + +/** + * Validates Weapon Level criteria of an objective while parsing an achievement entry. + * @param[in] t pointer to the config setting. + * @param[out] obj pointer to the achievement objective entry being parsed. + * @param[in] type Type of the achievement being parsed. + * @param[in] entry_id Id of the entry being parsed. + * @param[in] obj_idx Index of the objective entry being parsed. + * @return false on failure, true on success. + */ +static bool achievement_readdb_validate_criteria_weaponlv(const struct config_setting_t *t, struct achievement_objective *obj, enum achievement_types type, int entry_id, int obj_idx) +{ + int val = 0; + + nullpo_retr(false, t); + nullpo_retr(false, obj); + + if (libconfig->setting_lookup_int(t, "WeaponLevel", &val)) { + if (val < 1 || val > 4) { + ShowError("achievement_readdb_validate_criteria_weaponlv: Invalid WeaponLevel %d provided (Achievement: %d, Objective: %d). Skipping...\n", val, entry_id, obj_idx); + return false; + } else if (obj->unique_type != CRITERIA_UNIQUE_NONE) { + ShowError("achievement_readdb_validate_criteria_weaponlv: Unique criteria has already been set to type %d. (Achievement: %d, Objective: %d). Skipping...\n", (int) obj->unique_type, entry_id, obj_idx); + return false; + } + + obj->unique.weapon_lv = val; + obj->unique_type = CRITERIA_UNIQUE_WEAPON_LV; + } else if (achievement_criteria_weaponlv(type)) { + ShowError("achievement_readdb_validate_criteria_weaponlv: Criteria requires a WeaponType field. (Achievement: %d, Objective: %d). Skipping...\n", entry_id, obj_idx); + return false; + } + + return true; +} + +/** + * Validates achievement Id criteria of an objective while parsing an achievement entry. + * @param[in] t pointer to the config setting. + * @param[out] obj pointer to the achievement objective entry being parsed. + * @param[in] type Type of the achievement being parsed. + * @param[in] entry_id Id of the entry being parsed. + * @param[in] obj_idx Index of the objective entry being parsed. + * @return false on failure, true on success. + */ +static bool achievement_readdb_validate_criteria_achievement(const struct config_setting_t *t, struct achievement_objective *obj, enum achievement_types type, int entry_id, int obj_idx) +{ + int val = 0; + + nullpo_retr(false, t); + nullpo_retr(false, obj); + + if (libconfig->setting_lookup_int(t, "Achieve", &val)) { + if (achievement->get(val) == NULL) { + ShowError("achievement_readdb_validate_criteria_achievement: Invalid Achievement %d provided as objective (Achievement %d, Objective %d). Skipping...\n", val, entry_id, obj_idx); + return false; + } else if (obj->unique_type != CRITERIA_UNIQUE_NONE) { + ShowError("achievement_readdb_validate_criteria_achievement: Unique criteria has already been set to type %d. (Achievement: %d, Objective: %d). Skipping...\n", (int) obj->unique_type, entry_id, obj_idx); + return false; + } + + obj->unique.achieve_id = val; + obj->unique_type = CRITERIA_UNIQUE_ACHIEVE_ID; + } else if (type == ACH_ACHIEVE) { + ShowError("achievement_readdb_validate_criteria_achievement: Achievement type of ID %d requires an Achieve field in the objective criteria, setting not provided. Skipping...\n", entry_id); + return false; + } + + return true; +} + +/** + * Read a single objective entry of an achievement. + * @param[in] conf config pointer. + * @param[in] index Index of the objective being in the objective list being parsed. + * @param[out] entry pointer to the achievement db entry being parsed. + * @return false on failure, true on success. + */ +static bool achievement_readdb_objective_sub(const struct config_setting_t *conf, int index, struct achievement_data *entry) +{ + struct config_setting_t *tt = NULL; + char objnum[12]; + + nullpo_retr(false, conf); + nullpo_retr(false, entry); + + sprintf(objnum, "*%d", index); // Search Objective 1..MAX + if ((tt = libconfig->setting_get_member(conf, objnum)) && config_setting_is_group(tt)) { + struct achievement_objective obj = { 0 }; + struct config_setting_t *c = NULL; + + /* Description */ + if (libconfig->setting_lookup_mutable_string(tt, "Description", obj.description, OBJECTIVE_DESCRIPTION_LENGTH) == 0) { + ShowError("achievement_readdb_objective_sub: Objective %d has no description for Achievement %d, skipping...\n", index, entry->id); + return false; + } + + /* Criteria */ + if ((c = libconfig->setting_get_member(tt, "Criteria")) && config_setting_is_group(tt)) { + /* MobId */ + if (achievement->readdb_validate_criteria_mobid(c, &obj, entry->type, entry->id, index) == false) + return false; + + /* ItemId */ + if (achievement->readdb_validate_criteria_itemid(c, &obj, entry->type, entry->id, index) == false) + return false; + + /* StatusType */ + if (achievement->readdb_validate_criteria_statustype(c, &obj, entry->type, entry->id, index) == false) + return false; + + /* ItemType */ + if (achievement->readdb_validate_criteria_itemtype(c, &obj, entry->type, entry->id, index) == false) + return false; + + /* WeaponLevel */ + if (achievement->readdb_validate_criteria_weaponlv(c, &obj, entry->type, entry->id, index) == false) + return false; + + /* Achievement */ + if (achievement->readdb_validate_criteria_achievement(c, &obj, entry->type, entry->id, index) == false) + return false; + + /** + * Vectors are read last to avoid memory leaks if either of the above break, in cases where they are stacked with other criteria. + * Note to future editors - be sure to cleanup previous vectors before breaks. + */ + /* JobId */ + if (achievement->readdb_validate_criteria_jobid(c, &obj, entry->type, entry->id, index) == false) + return false; + } else if (achievement->type_requires_criteria(entry->type)) { + ShowError("achievement_readdb_objective_sub: No criteria field added (Achievement: %d, Objective: %d)! Skipping...\n", entry->id, index); + return false; + } + + /* Goal */ + if (libconfig->setting_lookup_int(tt, "Goal", &obj.goal) <= 0) + obj.goal = 1; // 1 count by default. + + /* Ensure size and allocation */ + VECTOR_ENSURE(entry->objective, 1, 1); + + /* Push buffer */ + VECTOR_PUSH(entry->objective, obj); + } else { // Break if not in order, to comply with the client's objective order. + ShowWarning("achievement_readdb_objective_sub: Objectives for Achievement %d are not in order (starting from *%d). Remaining objectives will be skipped.\n", entry->id, index); + return false; + } + + return true; +} + +/** + * Parses achievement objective entries of the current achievement db entry being parsed. + * @param[in] conf config pointer. + * @param[out] entry pointer to the achievement db entry being parsed. + * @return false on failure, true on success. + */ +static bool achievement_readdb_objectives(const struct config_setting_t *conf, struct achievement_data *entry) +{ + struct config_setting_t *t = NULL; + + nullpo_retr(false, conf); + nullpo_retr(false, entry); + + if ((t = libconfig->setting_get_member(conf, "Objectives")) && config_setting_is_group(t)) { + int i = 0; + // Initialize the buffer objective vector. + VECTOR_INIT(entry->objective); + + for (i = 0; i < libconfig->setting_length(t) && i < MAX_ACHIEVEMENT_OBJECTIVES; i++) + if (achievement_readdb_objective_sub(t, i + 1, entry) == false) + break; + + // Assess total objectives. + if (libconfig->setting_length(t) > MAX_ACHIEVEMENT_OBJECTIVES) + ShowWarning("achievement_readdb_objectives: Exceeded maximum number of objectives (%d) for Achievement %d. Remaining objectives will be skipped.\n", MAX_ACHIEVEMENT_OBJECTIVES, entry->id); + if (i == 0) { + ShowError("achievement_readdb_objectives: No Objectives provided for Achievement %d, skipping...\n", entry->id); + return false; + } + } else { + ShowError("achievement_readdb_objectives: No Objectives provided for Achievement %d, skipping...\n", entry->id); + return false; + } // end of "Objectives" + + return true; +} + +/** + * Validates a single reward item in the reward item list of an achievement. + * @param[in] t pointer to the config setting. + * @param[in] index Index of the item in the reward list. + * @param[out] entry pointer to the achievement entry being parsed. + * @return false on failure, true on success. + */ +static bool achievement_readdb_validate_reward_item_sub(const struct config_setting_t *t, int index, struct achievement_data *entry) +{ + struct config_setting_t *it = NULL; + struct achievement_reward_item item = { 0 }; + const char *name = NULL; + int amount = 0; + int val = 0; + + nullpo_retr(false, t); + nullpo_retr(false, entry); + + if ((it = libconfig->setting_get_elem(t, index)) == NULL) + return false; + + name = config_setting_name(it); + amount = libconfig->setting_get_int(it); + + if (name[0] == 'I' && name[1] == 'D' && itemdb->exists(atoi(name+2))) { + val = atoi(name); + } else if (!script->get_constant(name, &val)) { + ShowWarning("achievement_readdb_validate_reward_item_sub: Non existant Item %s provided as a reward in Achievement %d, skipping...\n", name, entry->id); + return false; + } + + if (amount <= 0) { + ShowWarning("achievement_readdb_validate_reward_item_sub: No amount provided for Item %s as a reward in Achievement %d, skipping...\n", name, entry->id); + return false; + } + + /* Ensure size and allocation */ + VECTOR_ENSURE(entry->rewards.item, 1, 1); + + item.id = val; + item.amount = amount; + + /* push buffer */ + VECTOR_PUSH(entry->rewards.item, item); + + return true; +} + +/** + * Validates the reward items when parsing an achievement db entry. + * @param[in] t pointer to the config setting. + * @param[out] entry pointer to the achievement entry being parsed. + */ +static void achievement_readdb_validate_reward_items(const struct config_setting_t *t, struct achievement_data *entry) +{ + struct config_setting_t *tt = NULL; + int i = 0; + + nullpo_retv(t); + nullpo_retv(entry); + + if ((tt = libconfig->setting_get_member(t, "Items")) && config_setting_is_group(t)) { + for (i = 0; i < libconfig->setting_length(tt) && i < MAX_ACHIEVEMENT_ITEM_REWARDS; i++) + if (achievement->readdb_validate_reward_item_sub(tt, i, entry) == false) + continue; + + if (libconfig->setting_length(tt) > MAX_ACHIEVEMENT_ITEM_REWARDS) + ShowError("achievement_readdb_validate_reward_items: Maximum amount of item rewards (%d) exceeded for Achievement %d. Remaining items will be skipped.\n", MAX_ACHIEVEMENT_ITEM_REWARDS, entry->id); + } +} + +/** + * Validates the reward Bonus script when parsing an achievement db entry. + * @param[in] t pointer to the config setting. + * @param[out] entry pointer to the achievement entry being parsed. + * @param[in] source pointer to the source file name. + */ +static void achievement_readdb_validate_reward_bonus(const struct config_setting_t *t, struct achievement_data *entry, const char *source) +{ + const char *string = NULL; + + nullpo_retv(source); + nullpo_retv(t); + nullpo_retv(entry); + + if (libconfig->setting_lookup_string(t, "Bonus", &string)) + entry->rewards.bonus = string ? script->parse(string, source, 0, SCRIPT_IGNORE_EXTERNAL_BRACKETS, NULL) : NULL; +} + +/** + * Validates the reward Title Id when parsing an achievement entry. + * @param[in] t pointer to the config setting. + * @param[out] entry pointer to the entry. + */ +static void achievement_readdb_validate_reward_titleid(const struct config_setting_t *t, struct achievement_data *entry) +{ + nullpo_retv(t); + nullpo_retv(entry); + + libconfig->setting_lookup_int(t, "TitleId", &entry->rewards.title_id); +} + +/** + * Validates the reward Bonus script when parsing an achievement db entry. + * @param[in] conf pointer to the config setting. + * @param[out] entry pointer to the achievement entry being parsed. + * @param[in] source pointer to the source file name. + */ +static bool achievement_readdb_rewards(const struct config_setting_t *conf, struct achievement_data *entry, const char *source) +{ + struct config_setting_t *t = NULL; + + nullpo_retr(false, conf); + nullpo_retr(false, entry); + nullpo_retr(false, source); + + VECTOR_INIT(entry->rewards.item); + + if ((t = libconfig->setting_get_member(conf, "Rewards")) && config_setting_is_group(t)) { + /* Items */ + achievement->readdb_validate_reward_items(t, entry); + + /* Buff/Bonus Script Code */ + achievement->readdb_validate_reward_bonus(t, entry, source); + + /* Title Id */ + // @TODO Check Title ID against title DB! + achievement->readdb_validate_reward_titleid(t, entry); + + } + + return true; +} + +/** + * Validates the reward Bonus script when parsing an achievement db entry. + * @param[in] conf pointer to the config setting. + * @param[out] entry pointer to the achievement entry being parsed. + * @param[in] source pointer to the source file name. + */ +static void achievement_readdb_additional_fields(const struct config_setting_t *conf, struct achievement_data *entry, const char *source) +{ + // plugins do their own thing. +} + +/** + * Parses the Achievement DB. + * @read achievement_db.conf + */ +static void achievement_readb(void) +{ + const char *filename = "db/"DBPATH"achievement_db.conf"; + struct config_t ach_conf = { 0 }; + struct config_setting_t *achdb = NULL, *conf = NULL; + int entry = 0, count = 0; + VECTOR_DECL(int) duplicate; + + if (!libconfig->load_file(&ach_conf, filename)) + return; // report error. + + if (!(achdb = libconfig->setting_get_member(ach_conf.root, "achievement_db"))) { + ShowError("achievement_readdb: Could not process contents of file '%s', skipping...\n", filename); + libconfig->destroy(&ach_conf); + return; + } + + VECTOR_INIT(duplicate); + + while ((conf = libconfig->setting_get_elem(achdb, entry++))) { + const char *string = NULL; + int val = 0, i = 0; + struct achievement_data t_ad = { 0 }, *p_ad = NULL; + + /* Achievement ID */ + if (libconfig->setting_lookup_int(conf, "Id", &t_ad.id) == 0) { + ShowError("achievement_readdb: Id field for entry %d is not provided! Skipping...\n", entry); + continue; + } else if (t_ad.id <= 0 || t_ad.id > INT32_MAX) { + ShowError("achievement_readdb: Invalid Id %d for entry %d. Skipping...\n", t_ad.id, entry); + continue; + } + + ARR_FIND(0, VECTOR_LENGTH(duplicate), i, VECTOR_INDEX(duplicate, i) == t_ad.id); + if (i != VECTOR_LENGTH(duplicate)) { + ShowError("achievement_readdb: Duplicate Id %d for entry %d. Skipping...\n", t_ad.id, entry); + continue; + } + + /* Achievement Name */ + if (libconfig->setting_lookup_mutable_string(conf, "Name", t_ad.name, ACHIEVEMENT_NAME_LENGTH) == 0) { + ShowError("achievement_readdb: Name field not provided for Achievement %d!\n", t_ad.id); + continue; + } + + /* Achievement Type*/ + if (libconfig->setting_lookup_string(conf, "Type", &string) == 0) { + ShowError("achievement_readdb: Type field not provided for Achievement %d! Skipping...\n", t_ad.id); + continue; + } else if (!script->get_constant(string, &val)) { + ShowError("achievement_readdb: Invalid constant %s provided as type for Achievement %d! Skipping...\n", string, t_ad.id); + continue; + } + + t_ad.type = (enum achievement_types) val; + + /* Objectives */ + achievement->readdb_objectives(conf, &t_ad); + + /* Rewards */ + achievement->readdb_rewards(conf, &t_ad, filename); + + /* Achievement Points */ + libconfig->setting_lookup_int(conf, "Points", &t_ad.points); + + /* Additional Fields */ + achievement->readdb_additional_fields(conf, &t_ad, filename); + + /* Allocate memory for data. */ + CREATE(p_ad, struct achievement_data, 1); + *p_ad = t_ad; + + /* Place in the database. */ + idb_put(achievement->db, p_ad->id, p_ad); + + /* Put achievement key in categories. */ + VECTOR_ENSURE(achievement->category[p_ad->type], 1, 1); + VECTOR_PUSH(achievement->category[p_ad->type], p_ad->id); + + /* Qualify for duplicate Id checks */ + VECTOR_ENSURE(duplicate, 1, 1); + VECTOR_PUSH(duplicate, t_ad.id); + + count++; + } // end of achievement_db parsing. + + /* Destroy the buffer */ + libconfig->destroy(&ach_conf); + + VECTOR_CLEAR(duplicate); + + ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", count, filename); +} + +/** + * On server initiation. + * @param[in] minimal ignores alocating/reading databases. + */ +static void do_init_achievement(bool minimal) +{ + int i = 0; + + if (minimal) + return; + + /* DB Initialization */ + achievement->db = idb_alloc(DB_OPT_RELEASE_DATA); + + for (i = 0; i < ACH_TYPE_MAX; i++) + VECTOR_INIT(achievement->category[i]); + + VECTOR_INIT(achievement->rank_exp); + + /* Read LibConfig Files */ + achievement->readdb(); + achievement->readdb_ranks(); +} + +/** + * Cleaning function called through achievement->db->destroy() + */ +static int achievement_db_finalize(union DBKey key, struct DBData *data, va_list args) +{ + int i = 0; + struct achievement_data *ad = DB->data2ptr(data); + + for(i = 0; i < VECTOR_LENGTH(ad->objective); i++) + VECTOR_CLEAR(VECTOR_INDEX(ad->objective, i).jobid); + + VECTOR_CLEAR(ad->objective); + VECTOR_CLEAR(ad->rewards.item); + + // Free the script + if (ad->rewards.bonus != NULL) { + script->free_code(ad->rewards.bonus); + ad->rewards.bonus = NULL; + } + + return 0; +} + +/** + * On server finalizing. + */ +static void do_final_achievement(void) +{ + int i = 0; + + achievement->db->destroy(achievement->db, achievement->db_finalize); + + for (i = 0; i < ACH_TYPE_MAX; i++) + VECTOR_CLEAR(achievement->category[i]); + + VECTOR_CLEAR(achievement->rank_exp); +} + +/** + * Achievement Interface + */ +void achievement_defaults(void) +{ + achievement = &achievement_s; + /* */ + achievement->init = do_init_achievement; + achievement->final = do_final_achievement; + /* */ + achievement->db_finalize = achievement_db_finalize; + /* */ + achievement->readdb = achievement_readb; + /* */ + achievement->readdb_objectives_sub = achievement_readdb_objective_sub; + achievement->readdb_objectives = achievement_readdb_objectives; + /* */ + achievement->readdb_validate_criteria_mobid = achievement_readdb_validate_criteria_mobid; + achievement->readdb_validate_criteria_jobid = achievement_readdb_validate_criteria_jobid; + achievement->readdb_validate_criteria_itemid = achievement_readdb_validate_criteria_itemid; + achievement->readdb_validate_criteria_statustype = achievement_readdb_validate_criteria_statustype; + achievement->readdb_validate_criteria_itemtype = achievement_readdb_validate_criteria_itemtype; + achievement->readdb_validate_criteria_weaponlv = achievement_readdb_validate_criteria_weaponlv; + achievement->readdb_validate_criteria_achievement = achievement_readdb_validate_criteria_achievement; + /* */ + achievement->readdb_rewards = achievement_readdb_rewards; + achievement->readdb_validate_reward_items = achievement_readdb_validate_reward_items; + achievement->readdb_validate_reward_item_sub = achievement_readdb_validate_reward_item_sub; + achievement->readdb_validate_reward_bonus = achievement_readdb_validate_reward_bonus; + achievement->readdb_validate_reward_titleid = achievement_readdb_validate_reward_titleid; + /* */ + achievement->readdb_additional_fields = achievement_readdb_additional_fields; + /* */ + achievement->readdb_ranks = achievement_readdb_ranks; + /* */ + achievement->get = achievement_get; + achievement->ensure = achievement_ensure; + /* */ + achievement->calculate_totals = achievement_calculate_totals; + achievement->check_complete = achievement_check_complete; + achievement->progress_add = achievement_progress_add; + achievement->progress_set = achievement_progress_set; + achievement->check_criteria = achievement_check_criteria; + /* */ + achievement->validate = achievement_validate; + achievement->validate_type = achievement_validate_type; + /* */ + achievement->validate_mob_kill = achievement_validate_mob_kill; + achievement->validate_mob_damage = achievement_validate_mob_damage; + achievement->validate_pc_kill = achievement_validate_pc_kill; + achievement->validate_pc_damage = achievement_validate_pc_damage; + achievement->validate_jobchange = achievement_validate_jobchange; + achievement->validate_stats = achievement_validate_stats; + achievement->validate_chatroom_create = achievement_validate_chatroom_create; + achievement->validate_chatroom_members = achievement_validate_chatroom_members; + achievement->validate_friend_add = achievement_validate_friend_add; + achievement->validate_party_create = achievement_validate_party_create; + achievement->validate_marry = achievement_validate_marry; + achievement->validate_adopt = achievement_validate_adopt; + achievement->validate_zeny = achievement_validate_zeny; + achievement->validate_refine = achievement_validate_refine; + achievement->validate_item_get = achievement_validate_item_get; + achievement->validate_item_sell = achievement_validate_item_sell; + achievement->validate_achieve = achievement_validate_achieve; + achievement->validate_taming = achievement_validate_taming; + achievement->validate_achievement_rank = achievement_validate_achievement_rank; + /* */ + achievement->type_requires_criteria = achievement_type_requires_criteria; +} diff --git a/src/map/achievement.h b/src/map/achievement.h new file mode 100644 index 000000000..f875f3a62 --- /dev/null +++ b/src/map/achievement.h @@ -0,0 +1,285 @@ +/** +* This file is part of Hercules. +* http://herc.ws - http://github.com/HerculesWS/Hercules +* +* Copyright (C) 2018 Hercules Dev Team +* Copyright (C) Smokexyz +* +* Hercules is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +#ifndef MAP_ACHIEVEMENT_H +#define MAP_ACHIEVEMENT_H + +#include "common/hercules.h" +#include "common/db.h" +#include "map/map.h" // enum status_point_types + +#define ACHIEVEMENT_NAME_LENGTH 50 +#define OBJECTIVE_DESCRIPTION_LENGTH 100 + +struct achievement; +struct map_session_data; +struct char_achievements; + +/** + * Achievement Types + */ +enum achievement_types { + // Quest + ACH_QUEST, // achievement and objective specific + // PC Kills + ACH_KILL_PC_TOTAL, + ACH_KILL_PC_JOB, + ACH_KILL_PC_JOBTYPE, + // Mob Kills + ACH_KILL_MOB_CLASS, + // PC Damage + ACH_DAMAGE_PC_MAX, + ACH_DAMAGE_PC_TOTAL, + ACH_DAMAGE_PC_REC_MAX, + ACH_DAMAGE_PC_REC_TOTAL, + // Mob Damage + ACH_DAMAGE_MOB_MAX, + ACH_DAMAGE_MOB_TOTAL, + ACH_DAMAGE_MOB_REC_MAX, + ACH_DAMAGE_MOB_REC_TOTAL, + // Job + ACH_JOB_CHANGE, + // Status + ACH_STATUS, + ACH_STATUS_BY_JOB, + ACH_STATUS_BY_JOBTYPE, + // Chatroom + ACH_CHATROOM_CREATE_DEAD, + ACH_CHATROOM_CREATE, + ACH_CHATROOM_MEMBERS, + // Friend + ACH_FRIEND_ADD, + // Party + ACH_PARTY_CREATE, + ACH_PARTY_JOIN, + // Marriage + ACH_MARRY, + // Adoption + ACH_ADOPT_BABY, + ACH_ADOPT_PARENT, + // Zeny + ACH_ZENY_HOLD, + ACH_ZENY_GET_ONCE, + ACH_ZENY_GET_TOTAL, + ACH_ZENY_SPEND_ONCE, + ACH_ZENY_SPEND_TOTAL, + // Equipment + ACH_EQUIP_REFINE_SUCCESS, + ACH_EQUIP_REFINE_FAILURE, + ACH_EQUIP_REFINE_SUCCESS_TOTAL, + ACH_EQUIP_REFINE_FAILURE_TOTAL, + ACH_EQUIP_REFINE_SUCCESS_WLV, + ACH_EQUIP_REFINE_FAILURE_WLV, + ACH_EQUIP_REFINE_SUCCESS_ID, + ACH_EQUIP_REFINE_FAILURE_ID, + // Items + ACH_ITEM_GET_COUNT, + ACH_ITEM_GET_COUNT_ITEMTYPE, + ACH_ITEM_GET_WORTH, + ACH_ITEM_SELL_WORTH, + // Monsters + ACH_PET_CREATE, + // Achievement + ACH_ACHIEVE, + ACH_ACHIEVEMENT_RANK, + ACH_TYPE_MAX +}; + +enum unique_criteria_types { + CRITERIA_UNIQUE_NONE, + CRITERIA_UNIQUE_ACHIEVE_ID, + CRITERIA_UNIQUE_ITEM_ID, + CRITERIA_UNIQUE_STATUS_TYPE, + CRITERIA_UNIQUE_WEAPON_LV +}; + +/** + * Achievement Objective Structure + * + * @see achievement_type_requires_criteria() + * @see achievement_criteria_mobid() + * @see achievement_criteria_jobid() + * @see achievement_criteria_itemid() + * @see achievement_criteria_stattype() + * @see achievement_criteria_itemtype() + * @see achievement_criteria_weaponlv() + */ +struct achievement_objective { + int goal; + char description[OBJECTIVE_DESCRIPTION_LENGTH]; + /** + * Those criteria that do not make sense if stacked together. + * union identifier is set in unique_type. (@see unique_criteria_type) + */ + union { + int achieve_id; + int itemid; + enum status_point_types status_type; + int weapon_lv; + } unique; + enum unique_criteria_types unique_type; + /* */ + uint32 item_type; + int mobid; + VECTOR_DECL(int) jobid; +}; + +struct achievement_reward_item { + int id, amount; +}; + +struct achievement_rewards { + int title_id; + struct script_code *bonus; + VECTOR_DECL(struct achievement_reward_item) item; +}; + +/** + * Achievement Data Structure + */ +struct achievement_data { + int id; + char name[ACHIEVEMENT_NAME_LENGTH]; + enum achievement_types type; + int points; + VECTOR_DECL(struct achievement_objective) objective; + struct achievement_rewards rewards; +}; + +// Achievements types that use Mob ID as criteria. +#define achievement_criteria_mobid(t) ( \ + (t) == ACH_KILL_MOB_CLASS \ + || (t) == ACH_PET_CREATE ) + +// Achievements types that use JobID vector as criteria. +#define achievement_criteria_jobid(t) ( \ + (t) == ACH_KILL_PC_JOB \ + || (t) == ACH_KILL_PC_JOBTYPE \ + || (t) == ACH_JOB_CHANGE \ + || (t) == ACH_STATUS_BY_JOB \ + || (t) == ACH_STATUS_BY_JOBTYPE ) + +// Achievements types that use Item ID as criteria. +#define achievement_criteria_itemid(t) ( \ + (t) == ACH_ITEM_GET_COUNT \ + || (t) == ACH_EQUIP_REFINE_SUCCESS_ID \ + || (t) == ACH_EQUIP_REFINE_FAILURE_ID ) + +// Achievements types that use status type parameter as criteria. +#define achievement_criteria_stattype(t) ( \ + (t) == ACH_STATUS \ + || (t) == ACH_STATUS_BY_JOB \ + || (t) == ACH_STATUS_BY_JOBTYPE ) + +// Achievements types that use item type mask as criteria. +#define achievement_criteria_itemtype(t) ( \ + (t) == ACH_ITEM_GET_COUNT_ITEMTYPE ) + +// Achievements types that use weapon level as criteria. +#define achievement_criteria_weaponlv(t) ( \ + (t) == ACH_EQUIP_REFINE_SUCCESS_WLV \ + || (t) == ACH_EQUIP_REFINE_FAILURE_WLV ) + +// Valid status types for objective criteria. +#define achievement_valid_status_types(s) ( \ + (s) == SP_STR \ + || (s) == SP_AGI \ + || (s) == SP_VIT \ + || (s) == SP_INT \ + || (s) == SP_DEX \ + || (s) == SP_LUK \ + || (s) == SP_BASELEVEL || (s) == SP_JOBLEVEL ) + +struct achievement_interface { + struct DBMap *db; // int id -> struct achievement_data * + /* */ + VECTOR_DECL(int) rank_exp; // Achievement Rank Exp Requirements + VECTOR_DECL(int) category[ACH_TYPE_MAX]; /* A collection of Ids per type for faster processing. */ + /* */ + void (*init) (bool minimal); + void (*final) (void); + /* */ + int (*db_finalize) (union DBKey key, struct DBData *data, va_list args); + /* */ + void (*readdb)(void); + /* */ + bool (*readdb_objectives_sub) (const struct config_setting_t *conf, int index, struct achievement_data *entry); + bool (*readdb_objectives) (const struct config_setting_t *conf, struct achievement_data *entry); + /* */ + bool (*readdb_validate_criteria_mobid) (const struct config_setting_t *t, struct achievement_objective *obj, enum achievement_types type, int entry_id, int obj_idx); + bool (*readdb_validate_criteria_jobid) (const struct config_setting_t *t, struct achievement_objective *obj, enum achievement_types type, int entry_id, int obj_idx); + bool (*readdb_validate_criteria_itemid) (const struct config_setting_t *t, struct achievement_objective *obj, enum achievement_types type, int entry_id, int obj_idx); + bool (*readdb_validate_criteria_statustype) (const struct config_setting_t *t, struct achievement_objective *obj, enum achievement_types type, int entry_id, int obj_idx); + bool (*readdb_validate_criteria_itemtype) (const struct config_setting_t *t, struct achievement_objective *obj, enum achievement_types type, int entry_id, int obj_idx); + bool (*readdb_validate_criteria_weaponlv) (const struct config_setting_t *t, struct achievement_objective *obj, enum achievement_types type, int entry_id, int obj_idx); + bool (*readdb_validate_criteria_achievement) (const struct config_setting_t *t, struct achievement_objective *obj, enum achievement_types type, int entry_id, int obj_idx); + /* */ + bool (*readdb_rewards) (const struct config_setting_t *conf, struct achievement_data *entry, const char *source); + void (*readdb_validate_reward_items) (const struct config_setting_t *t, struct achievement_data *entry); + bool (*readdb_validate_reward_item_sub) (const struct config_setting_t *t, int element, struct achievement_data *entry); + void (*readdb_validate_reward_bonus) (const struct config_setting_t *t, struct achievement_data *entry, const char *source); + void (*readdb_validate_reward_titleid) (const struct config_setting_t *t, struct achievement_data *entry); + /* */ + void (*readdb_additional_fields) (const struct config_setting_t *conf, struct achievement_data *entry, const char *source); + /* */ + void (*readdb_ranks) (void); + /* */ + const struct achievement_data *(*get) (int aid); + struct achievement *(*ensure) (struct map_session_data *sd, const struct achievement_data *ad); + /* */ + void (*calculate_totals) (const struct map_session_data *sd, int *points, int *completed, int *rank, int *curr_rank_points); + bool (*check_complete) (struct map_session_data *sd, const struct achievement_data *ad); + void (*progress_add) (struct map_session_data *sd, const struct achievement_data *ad, unsigned int obj_idx, int progress); + void (*progress_set) (struct map_session_data *sd, const struct achievement_data *ad, unsigned int obj_idx, int progress); + bool (*check_criteria) (const struct achievement_objective *objective, const struct achievement_objective *criteria); + /* */ + bool (*validate) (struct map_session_data *sd, int aid, unsigned int obj_idx, int progress, bool additive); + int (*validate_type) (struct map_session_data *sd, enum achievement_types type, const struct achievement_objective *criteria, bool additive); + /* */ + void (*validate_mob_kill) (struct map_session_data *sd, int mob_id); + void (*validate_mob_damage) (struct map_session_data *sd, unsigned int damage, bool received); + void (*validate_pc_kill) (struct map_session_data *sd, struct map_session_data *dstsd); + void (*validate_pc_damage) (struct map_session_data *sd, struct map_session_data *dstsd, unsigned int damage); + void (*validate_jobchange) (struct map_session_data *sd); + void (*validate_stats) (struct map_session_data *sd, enum status_point_types stat_type, int progress); + void (*validate_chatroom_create) (struct map_session_data *sd); + void (*validate_chatroom_members) (struct map_session_data *sd, int progress); + void (*validate_friend_add) (struct map_session_data *sd); + void (*validate_party_create) (struct map_session_data *sd); + void (*validate_marry) (struct map_session_data *sd); + void (*validate_adopt) (struct map_session_data *sd, bool parent); + void (*validate_zeny) (struct map_session_data *sd, int amount); + void (*validate_refine) (struct map_session_data *sd, unsigned int idx, bool success); + void (*validate_item_get) (struct map_session_data *sd, int nameid, int amount); + void (*validate_item_sell) (struct map_session_data *sd, int nameid, int amount); + void (*validate_achieve) (struct map_session_data *sd, int achid); + void (*validate_taming) (struct map_session_data *sd, int class); + void (*validate_achievement_rank) (struct map_session_data *sd, int rank); + /* */ + bool (*type_requires_criteria) (enum achievement_types type); +}; + +#ifdef HERCULES_CORE +void achievement_defaults(void); +#endif // HERCULES_CORE + +HPShared struct achievement_interface *achievement; + +#endif // MAP_ACHIEVEMENT_H |