/** * This file is part of Hercules. * http://herc.ws - http://github.com/HerculesWS/Hercules * * Copyright (C) 2017 Hercules Dev Team * Copyright (C) Smokexyz * Copyright (C) Dastgir * * 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 . */ #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 #include #include 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 (battle_config.feature_enable_achievement == 0) return 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 (battle_config.feature_enable_achievement == 0) return false; 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 < sd->status.inventorySize); id = itemdb->exists(sd->status.inventory[idx].nameid); if (sd->achievements_received == false) return; Assert_retv(id != NULL); criteria.goal = sd->status.inventory[idx].refine; // achievement should not trigger if refine is 0 if (criteria.goal == 0) return; /* 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; } /** * Stores all the title ID that has been earned by player * @param[in] sd pointer to session data. */ static void achievement_init_titles(struct map_session_data *sd) { int i; nullpo_retv(sd); VECTOR_INIT(sd->title_ids); /* Browse through the session's achievement list and gather their values. */ for (i = 0; i < VECTOR_LENGTH(sd->achievement); i++) { struct achievement *a = &VECTOR_INDEX(sd->achievement, i); const struct achievement_data *ad = NULL; /* Sanity check for nonull pointers. */ if (a == NULL || (ad = achievement->get(a->id)) == NULL) continue; if (a->completed_at > 0 && a->rewarded_at > 0 && ad->rewards.title_id > 0) { VECTOR_ENSURE(sd->title_ids, 1, 1); VECTOR_PUSH(sd->title_ids, ad->rewards.title_id); } } } /** * Validates whether player has earned the title. * @param[in] sd pointer to session data. * @param[in] title_id Title ID * @return true, if title has been earned, else false */ static bool achievement_check_title(struct map_session_data *sd, int title_id) { int i; nullpo_retr(false, sd); if (title_id == 0) return true; for (i = 0; i < VECTOR_LENGTH(sd->title_ids); i++) { if (VECTOR_INDEX(sd->title_ids, i) == title_id) { return true; } } return false; } static void achievement_get_rewards_buffs(struct map_session_data *sd, const struct achievement_data *ad) { nullpo_retv(sd); nullpo_retv(ad); if (ad->rewards.bonus != NULL) script->run(ad->rewards.bonus, 0, sd->bl.id, 0); } // TODO: kro send items by rodex static void achievement_get_rewards_items(struct map_session_data *sd, const struct achievement_data *ad) { nullpo_retv(sd); nullpo_retv(ad); struct item it = { 0 }; it.identify = 1; for (int i = 0; i < VECTOR_LENGTH(ad->rewards.item); i++) { it.nameid = VECTOR_INDEX(ad->rewards.item, i).id; int total = VECTOR_INDEX(ad->rewards.item, i).amount; //Check if it's stackable. if (!itemdb->isstackable(it.nameid)) { it.amount = 1; for (int j = 0; j < total; ++j) pc->additem(sd, &it, 1, LOG_TYPE_SCRIPT); } else { it.amount = total; pc->additem(sd, &it, total, LOG_TYPE_SCRIPT); } } } /** * Achievement rewards are given to player * @param sd session data * @param ad achievement data */ static bool achievement_get_rewards(struct map_session_data *sd, const struct achievement_data *ad) { nullpo_retr(false, sd); nullpo_retr(false, ad); struct achievement *ach = achievement->ensure(sd, ad); if (ach == NULL) return false; /* Buff */ achievement->get_rewards_buffs(sd, ad); ach->rewarded_at = time(NULL); if (ad->rewards.title_id > 0) { // Add Title VECTOR_ENSURE(sd->title_ids, 1, 1); VECTOR_PUSH(sd->title_ids, ad->rewards.title_id); clif->achievement_send_list(sd->fd, sd); } else { clif->achievement_send_update(sd->fd, sd, ad); // send update. clif->achievement_reward_ack(sd->fd, sd, ad); } /* Give Items */ achievement->get_rewards_items(sd, ad); return true; } /** * Parses the Achievement Ranks. * @read db/achievement_rank_db.conf */ static void achievement_readdb_ranks(void) { char filename[256]; libconfig->format_db_path("achievement_rank_db.conf", filename, sizeof(filename)); 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[16]; 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; 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++; } } 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) { char filename[256]; libconfig->format_db_path(DBPATH"achievement_db.conf", filename, sizeof(filename)); 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) { 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; /* */ achievement->init_titles = achievement_init_titles; achievement->check_title = achievement_check_title; achievement->get_rewards = achievement_get_rewards; achievement->get_rewards_buffs = achievement_get_rewards_buffs; achievement->get_rewards_items = achievement_get_rewards_items; }