diff options
author | Andrei Karas <akaras@inbox.ru> | 2016-02-05 00:19:58 +0300 |
---|---|---|
committer | Andrei Karas <akaras@inbox.ru> | 2016-02-05 00:19:58 +0300 |
commit | 88a6bbe81cdac8392af63d9732b582fa02b04261 (patch) | |
tree | b3ff5d684c24990a1540bee398b59656c066089c /src | |
parent | a59d2cf735c961d86050ae51d345a5a81cfeb77c (diff) | |
parent | dcc9ee43a4b33ace1501864415f5a856b18f8201 (diff) | |
download | hercules-88a6bbe81cdac8392af63d9732b582fa02b04261.tar.gz hercules-88a6bbe81cdac8392af63d9732b582fa02b04261.tar.bz2 hercules-88a6bbe81cdac8392af63d9732b582fa02b04261.tar.xz hercules-88a6bbe81cdac8392af63d9732b582fa02b04261.zip |
Merge pull request #993 from HerculesWS/mobdb2sql
Mob DB support for the db2sql plugin
Diffstat (limited to 'src')
-rw-r--r-- | src/common/utils.c | 43 | ||||
-rw-r--r-- | src/common/utils.h | 3 | ||||
-rw-r--r-- | src/map/itemdb.c | 36 | ||||
-rw-r--r-- | src/map/mob.c | 659 | ||||
-rw-r--r-- | src/map/mob.h | 14 | ||||
-rw-r--r-- | src/map/npc.c | 30 | ||||
-rw-r--r-- | src/map/pc.c | 101 | ||||
-rw-r--r-- | src/map/script.c | 30 | ||||
-rw-r--r-- | src/map/vending.c | 12 | ||||
-rw-r--r-- | src/plugins/db2sql.c | 557 |
10 files changed, 1024 insertions, 461 deletions
diff --git a/src/common/utils.c b/src/common/utils.c index dcf0a749a..73df3aae1 100644 --- a/src/common/utils.c +++ b/src/common/utils.c @@ -25,6 +25,7 @@ #include "common/cbasetypes.h" #include "common/core.h" #include "common/mmo.h" +#include "common/nullpo.h" #include "common/showmsg.h" #include "common/socket.h" #include "common/strlib.h" @@ -352,6 +353,48 @@ unsigned int get_percentage(const unsigned int A, const unsigned int B) return (unsigned int)floor(result); } +/** + * Applies a percentual rate modifier. + * + * @param value The base value. + * @param rate The rate modifier to apply. + * @param stdrate The rate modifier's divider (rate == stdrate => 100%). + * @return The modified value. + */ +int64 apply_percentrate64(int64 value, int rate, int stdrate) +{ + Assert_ret(stdrate > 0); + Assert_ret(rate >= 0); + if (rate == stdrate) + return value; + if (rate == 0) + return 0; + if (INT64_MAX / rate < value) { + // Give up some precision to prevent overflows + return value / stdrate * rate; + } + return value * rate / stdrate; +} + +/** + * Applies a percentual rate modifier. + * + * @param value The base value. + * @param rate The rate modifier to apply. Must be <= maxrate. + * @param maxrate The rate modifier's divider (maxrate = 100%). + * @return The modified value. + */ +int apply_percentrate(int value, int rate, int maxrate) +{ + Assert_ret(maxrate > 0); + Assert_ret(rate >= 0); + if (rate == maxrate) + return value; + if (rate == 0) + return 0; + return (int)(value * (int64)rate / maxrate); +} + //----------------------------------------------------- // custom timestamp formatting (from eApp) //----------------------------------------------------- diff --git a/src/common/utils.h b/src/common/utils.h index da2a29317..3f181ef12 100644 --- a/src/common/utils.h +++ b/src/common/utils.h @@ -42,6 +42,9 @@ bool exists(const char* filename); /// calculates the value of A / B, in percent (rounded down) unsigned int get_percentage(const unsigned int A, const unsigned int B); +int64 apply_percentrate64(int64 value, int rate, int maxrate); +int apply_percentrate(int value, int rate, int maxrate); + const char* timestamp2string(char* str, size_t size, time_t timestamp, const char* format); ////////////////////////////////////////////////////////////////////////// diff --git a/src/map/itemdb.c b/src/map/itemdb.c index 6428bade5..bd552dd16 100644 --- a/src/map/itemdb.c +++ b/src/map/itemdb.c @@ -1385,17 +1385,17 @@ int itemdb_gendercheck(struct item_data *id) * This function is called after preparing the item entry data, and it takes * care of inserting it and cleaning up any remainders of the previous one. * - * @param *entry Pointer to the new item_data entry. Ownership is NOT taken, - * but the content is modified to reflect the validation. - * @param n Ordinal number of the entry, to be displayed in case of - * validation errors. - * @param *source Source of the entry (table or file name), to be displayed in - * case of validation errors. + * @param entry Pointer to the new item_data entry. Ownership is NOT taken, + * but the content is modified to reflect the validation. + * @param n Ordinal number of the entry, to be displayed in case of + * validation errors. + * @param source Source of the entry (file name), to be displayed in case of + * validation errors. * @return Nameid of the validated entry, or 0 in case of failure. * - * Note: This is safe to call if the new entry is a copy of the old one (i.e. - * item_db2 inheritance), as it will make sure not to free any scripts still in - * use in the new entry. + * Note: This is safe to call if the new entry is a shallow copy of the old one + * (i.e. item_db2 inheritance), as it will make sure not to free any scripts + * still in use by the new entry. */ int itemdb_validate_entry(struct item_data *entry, int n, const char *source) { struct item_data *item; @@ -1544,13 +1544,13 @@ void itemdb_readdb_additional_fields(int itemid, config_setting_t *it, int n, co * Processes one itemdb entry from the libconfig backend, loading and inserting * it into the item database. * - * @param *it Libconfig setting entry. It is expected to be valid and it - * won't be freed (it is care of the caller to do so if - * necessary) - * @param n Ordinal number of the entry, to be displayed in case of - * validation errors. - * @param *source Source of the entry (file name), to be displayed in case of - * validation errors. + * @param it Libconfig setting entry. It is expected to be valid and it + * won't be freed (it is care of the caller to do so if + * necessary) + * @param n Ordinal number of the entry, to be displayed in case of + * validation errors. + * @param source Source of the entry (file name), to be displayed in case of + * validation errors. * @return Nameid of the validated entry, or 0 in case of failure. */ int itemdb_readdb_libconfig_sub(config_setting_t *it, int n, const char *source) { @@ -1627,7 +1627,7 @@ int itemdb_readdb_libconfig_sub(config_setting_t *it, int n, const char *source) } else { // Use old entry as default struct item_data *old_entry = itemdb->load(id.nameid); - memcpy(&id, old_entry, sizeof(struct item_data)); + memcpy(&id, old_entry, sizeof(id)); } } @@ -1874,7 +1874,7 @@ bool itemdb_lookup_const(const config_setting_t *it, const char *name, int *valu * Reads from a libconfig-formatted itemdb file and inserts the found entries into the * item database, overwriting duplicate ones (i.e. item_db2 overriding item_db.) * - * @param *filename File name, relative to the database path. + * @param filename File name, relative to the database path. * @return The number of found entries. */ int itemdb_readdb_libconfig(const char *filename) { diff --git a/src/map/mob.c b/src/map/mob.c index f93a7fef3..bc78c6098 100644 --- a/src/map/mob.c +++ b/src/map/mob.c @@ -2482,15 +2482,15 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type) { if(mvp_sd && md->db->mexp > 0 && md->special_state.ai == AI_NONE) { int log_mvp[2] = {0}; unsigned int mexp; - double exp; + int64 exp; //mapflag: noexp check [Lorky] - if (map->list[m].flag.nobaseexp || type&2) - exp =1; - else { + if (map->list[m].flag.nobaseexp || type&2) { + exp = 1; + } else { exp = md->db->mexp; if (count > 1) - exp += exp*(battle_config.exp_bonus_attacker*(count-1))/100.; //[Gengar] + exp += apply_percentrate64(exp, battle_config.exp_bonus_attacker * (count-1), 100); //[Gengar] } mexp = (unsigned int)cap_value(exp, 1, UINT_MAX); @@ -3632,7 +3632,7 @@ int mob_makedummymobdb(int class_) //Adjusts the drop rate of item according to the criteria given. [Skotlex] unsigned int mob_drop_adjust(int baserate, int rate_adjust, unsigned short rate_min, unsigned short rate_max) { - double rate = baserate; + int64 rate = baserate; if (battle_config.logarithmic_drops && rate_adjust > 0 && rate_adjust != 100 && baserate > 0) //Logarithmic drops equation by Ishizu-Chan //Equation: Droprate(x,y) = x * (5 - log(x)) ^ (ln(y) / ln(5)) @@ -3640,7 +3640,7 @@ unsigned int mob_drop_adjust(int baserate, int rate_adjust, unsigned short rate_ rate = rate * pow((5.0 - log10(rate)), (log(rate_adjust/100.) / log(5.0))) + 0.5; else //Classical linear rate adjustment. - rate = rate * rate_adjust/100; + rate = apply_percentrate64(rate, rate_adjust, 100); return (unsigned int)cap_value(rate,rate_min,rate_max); } @@ -3678,30 +3678,45 @@ static inline int mob_parse_dbrow_cap_value(int class_, int min, int max, int va return value; } -void mob_read_db_stats_sub(struct mob_db *entry, struct status_data *mstatus, int class_, config_setting_t *t) +/** + * Processes the stats for a mob database entry. + * + * @param[in,out] entry The destination mob_db entry, already initialized + * (mob_id is expected to be already set). + * @param[in] t The libconfig entry. + */ +void mob_read_db_stats_sub(struct mob_db *entry, config_setting_t *t) { int i32; if (mob->lookup_const(t, "Str", &i32) && i32 >= 0) { - mstatus->str = mob_parse_dbrow_cap_value(class_, UINT16_MIN, UINT16_MAX, i32); + entry->status.str = mob_parse_dbrow_cap_value(entry->mob_id, UINT16_MIN, UINT16_MAX, i32); } if (mob->lookup_const(t, "Agi", &i32) && i32 >= 0) { - mstatus->agi = mob_parse_dbrow_cap_value(class_, UINT16_MIN, UINT16_MAX, i32); + entry->status.agi = mob_parse_dbrow_cap_value(entry->mob_id, UINT16_MIN, UINT16_MAX, i32); } if (mob->lookup_const(t, "Vit", &i32) && i32 >= 0) { - mstatus->vit = mob_parse_dbrow_cap_value(class_, UINT16_MIN, UINT16_MAX, i32); + entry->status.vit = mob_parse_dbrow_cap_value(entry->mob_id, UINT16_MIN, UINT16_MAX, i32); } if (mob->lookup_const(t, "Int", &i32) && i32 >= 0) { - mstatus->int_ = mob_parse_dbrow_cap_value(class_, UINT16_MIN, UINT16_MAX, i32); + entry->status.int_ = mob_parse_dbrow_cap_value(entry->mob_id, UINT16_MIN, UINT16_MAX, i32); } if (mob->lookup_const(t, "Dex", &i32) && i32 >= 0) { - mstatus->dex = mob_parse_dbrow_cap_value(class_, UINT16_MIN, UINT16_MAX, i32); + entry->status.dex = mob_parse_dbrow_cap_value(entry->mob_id, UINT16_MIN, UINT16_MAX, i32); } if (mob->lookup_const(t, "Luk", &i32) && i32 >= 0) { - mstatus->luk = mob_parse_dbrow_cap_value(class_, UINT16_MIN, UINT16_MAX, i32); + entry->status.luk = mob_parse_dbrow_cap_value(entry->mob_id, UINT16_MIN, UINT16_MAX, i32); } } -int mob_read_db_mode_sub(struct mob_db *entry, struct status_data *mstatus, int class_, config_setting_t *t) +/** + * Processes the mode for a mob_db entry. + * + * @param[in] entry The destination mob_db entry, already initialized. + * @param[in] t The libconfig entry. + * + * @return The parsed mode. + */ +int mob_read_db_mode_sub(struct mob_db *entry, config_setting_t *t) { int mode = 0; config_setting_t *t2; @@ -3740,7 +3755,14 @@ int mob_read_db_mode_sub(struct mob_db *entry, struct status_data *mstatus, int return mode; } -void mob_read_db_mvpdrops_sub(struct mob_db *entry, struct status_data *mstatus, int class_, config_setting_t *t) +/** + * Processes the MVP drops for a mob_db entry. + * + * @param[in,out] entry The destination mob_db entry, already initialized + * (mob_id is expected to be already set). + * @param[in] t The libconfig entry. + */ +void mob_read_db_mvpdrops_sub(struct mob_db *entry, config_setting_t *t) { config_setting_t *drop; int i = 0; @@ -3752,28 +3774,26 @@ void mob_read_db_mvpdrops_sub(struct mob_db *entry, struct status_data *mstatus, int rate_adjust = battle_config.item_rate_mvp; struct item_data* id = itemdb->search_name(name); int value = 0; - if (!id) - { - ShowWarning("mob_read_db: mvp drop item %s not found in monster %d\n", name, class_); - i ++; + if (!id) { + ShowWarning("mob_read_db: mvp drop item %s not found in monster %d\n", name, entry->mob_id); + i++; continue; } if (mob->get_const(drop, &i32) && i32 >= 0) { value = i32; } - if (value <= 0) - { - ShowWarning("mob_read_db: wrong drop chance %d for mvp drop item %s in monster %d\n", value, name, class_); - i ++; + if (value <= 0) { + ShowWarning("mob_read_db: wrong drop chance %d for mvp drop item %s in monster %d\n", value, name, entry->mob_id); + i++; continue; } entry->mvpitem[idx].nameid = id->nameid; if (!entry->mvpitem[idx].nameid) { entry->mvpitem[idx].p = 0; //No item.... - i ++; + i++; continue; } - mob->item_dropratio_adjust(entry->mvpitem[idx].nameid, class_, &rate_adjust); + mob->item_dropratio_adjust(entry->mvpitem[idx].nameid, entry->mob_id, &rate_adjust); entry->mvpitem[idx].p = mob->drop_adjust(value, rate_adjust, battle_config.item_drop_mvp_min, battle_config.item_drop_mvp_max); //calculate and store Max available drop chance of the MVP item @@ -3787,11 +3807,18 @@ void mob_read_db_mvpdrops_sub(struct mob_db *entry, struct status_data *mstatus, idx++; } if (idx == MAX_MVP_DROP && libconfig->setting_get_elem(t, i)) { - ShowWarning("mob_read_db: Too many mvp drops in mob %d\n", class_); + ShowWarning("mob_read_db: Too many mvp drops in mob %d\n", entry->mob_id); } } -void mob_read_db_drops_sub(struct mob_db *entry, struct status_data *mstatus, int class_, config_setting_t *t) +/** + * Processes the drops for a mob_db entry. + * + * @param[in,out] entry The destination mob_db entry, already initialized + * (mob_id, status.mode are expected to be already set). + * @param[in] t The libconfig entry. + */ +void mob_read_db_drops_sub(struct mob_db *entry, config_setting_t *t) { config_setting_t *drop; int i = 0; @@ -3805,73 +3832,72 @@ void mob_read_db_drops_sub(struct mob_db *entry, struct status_data *mstatus, in unsigned short ratemin, ratemax; struct item_data* id = itemdb->search_name(name); int value = 0; - if (!id) - { - ShowWarning("mob_read_db: drop item %s not found in monster %d\n", name, class_); - i ++; + if (!id) { + ShowWarning("mob_read_db: drop item %s not found in monster %d\n", name, entry->mob_id); + i++; continue; } if (mob->get_const(drop, &i32) && i32 >= 0) { value = i32; } - if (value <= 0) - { - ShowWarning("mob_read_db: wrong drop chance %d for drop item %s in monster %d\n", value, name, class_); - i ++; + if (value <= 0) { + ShowWarning("mob_read_db: wrong drop chance %d for drop item %s in monster %d\n", value, name, entry->mob_id); + i++; continue; } entry->dropitem[idx].nameid = id->nameid; if (!entry->dropitem[idx].nameid) { entry->dropitem[idx].p = 0; //No drop. - i ++; + i++; continue; } type = id->type; - if ((class_ >= MOBID_TREASURE_BOX1 && class_ <= MOBID_TREASURE_BOX40) || (class_ >= MOBID_TREASURE_BOX41 && class_ <= MOBID_TREASURE_BOX49)) { + if ((entry->mob_id >= MOBID_TREASURE_BOX1 && entry->mob_id <= MOBID_TREASURE_BOX40) + || (entry->mob_id >= MOBID_TREASURE_BOX41 && entry->mob_id <= MOBID_TREASURE_BOX49)) { //Treasure box drop rates [Skotlex] rate_adjust = battle_config.item_rate_treasure; ratemin = battle_config.item_drop_treasure_min; ratemax = battle_config.item_drop_treasure_max; - } - else switch (type) - { // Added support to restrict normal drops of MVP's [Reddozen] + } else { + switch (type) { // Added support to restrict normal drops of MVP's [Reddozen] case IT_HEALING: - rate_adjust = (mstatus->mode&MD_BOSS) ? battle_config.item_rate_heal_boss : battle_config.item_rate_heal; + rate_adjust = (entry->status.mode&MD_BOSS) ? battle_config.item_rate_heal_boss : battle_config.item_rate_heal; ratemin = battle_config.item_drop_heal_min; ratemax = battle_config.item_drop_heal_max; break; case IT_USABLE: case IT_CASH: - rate_adjust = (mstatus->mode&MD_BOSS) ? battle_config.item_rate_use_boss : battle_config.item_rate_use; + rate_adjust = (entry->status.mode&MD_BOSS) ? battle_config.item_rate_use_boss : battle_config.item_rate_use; ratemin = battle_config.item_drop_use_min; ratemax = battle_config.item_drop_use_max; break; case IT_WEAPON: case IT_ARMOR: case IT_PETARMOR: - rate_adjust = (mstatus->mode&MD_BOSS) ? battle_config.item_rate_equip_boss : battle_config.item_rate_equip; + rate_adjust = (entry->status.mode&MD_BOSS) ? battle_config.item_rate_equip_boss : battle_config.item_rate_equip; ratemin = battle_config.item_drop_equip_min; ratemax = battle_config.item_drop_equip_max; break; case IT_CARD: - rate_adjust = (mstatus->mode&MD_BOSS) ? battle_config.item_rate_card_boss : battle_config.item_rate_card; + rate_adjust = (entry->status.mode&MD_BOSS) ? battle_config.item_rate_card_boss : battle_config.item_rate_card; ratemin = battle_config.item_drop_card_min; ratemax = battle_config.item_drop_card_max; break; default: - rate_adjust = (mstatus->mode&MD_BOSS) ? battle_config.item_rate_common_boss : battle_config.item_rate_common; + rate_adjust = (entry->status.mode&MD_BOSS) ? battle_config.item_rate_common_boss : battle_config.item_rate_common; ratemin = battle_config.item_drop_common_min; ratemax = battle_config.item_drop_common_max; break; + } } - mob->item_dropratio_adjust(id->nameid, class_, &rate_adjust); + mob->item_dropratio_adjust(id->nameid, entry->mob_id, &rate_adjust); entry->dropitem[idx].p = mob->drop_adjust(value, rate_adjust, ratemin, ratemax); //calculate and store Max available drop chance of the item if (entry->dropitem[idx].p - && (class_ < MOBID_TREASURE_BOX1 || class_ > MOBID_TREASURE_BOX40) - && (class_ < MOBID_TREASURE_BOX41 || class_ > MOBID_TREASURE_BOX49)) { + && (entry->mob_id < MOBID_TREASURE_BOX1 || entry->mob_id > MOBID_TREASURE_BOX40) + && (entry->mob_id < MOBID_TREASURE_BOX41 || entry->mob_id > MOBID_TREASURE_BOX49)) { //Skip treasure chests. if (id->maxchance == -1 || (id->maxchance < entry->dropitem[idx].p) ) { id->maxchance = entry->dropitem[idx].p; //item has bigger drop chance or sold in shops @@ -3880,361 +3906,454 @@ void mob_read_db_drops_sub(struct mob_db *entry, struct status_data *mstatus, in if (id->mob[k].chance <= entry->dropitem[idx].p) break; } - if (k == MAX_SEARCH) - { + if (k == MAX_SEARCH) { i++; idx++; continue; } - if (id->mob[k].id != class_ && k != MAX_SEARCH - 1) + if (id->mob[k].id != entry->mob_id && k != MAX_SEARCH - 1) memmove(&id->mob[k+1], &id->mob[k], (MAX_SEARCH-k-1)*sizeof(id->mob[0])); id->mob[k].chance = entry->dropitem[idx].p; - id->mob[k].id = class_; + id->mob[k].id = entry->mob_id; } i++; idx++; } if (idx == MAX_MOB_DROP && libconfig->setting_get_elem(t, i)) { - ShowWarning("mob_read_db: Too many drops in mob %d\n", class_); + ShowWarning("mob_read_db: Too many drops in mob %d\n", entry->mob_id); } } -/*========================================== - * processes one mobdb entry - *------------------------------------------*/ -bool mob_read_db_sub(config_setting_t *mobt, int id, const char *source) +/** + * Validates a mob DB entry and inserts it into the database. + * This function is called after preparing the mob entry data, and it takes + * care of inserting it and cleaning up any remainders of the previous one (in + * case it is overwriting an existing entry). + * + * @param entry Pointer to the new mob_db entry. Ownership is NOT taken, but + * the content is modified to reflect the validation. + * @param n Ordinal number of the entry, to be displayed in case of + * validation errors. + * @param source Source of the entry (file name), to be displayed in case of + * validation errors. + * @return Mob ID of the validated entry, or 0 in case of failure. + * + * Note: This is safe to call if the new entry is a shallow copy of the old one + * (i.e. mob_db2 inheritance), as it will make sure not to free any data still + * in use by the new entry. + */ +int mob_db_validate_entry(struct mob_db *entry, int n, const char *source) { - struct mob_db *entry = NULL, tmpEntry; - config_setting_t *t = NULL; - int i32 = 0, value = 0, class_ = 0; - struct status_data *mstatus; struct mob_data data; - const char *str = NULL; - double maxhp; - double exp; - bool inherit = false; - bool range2Updated = false; - bool range3Updated = false; - bool dmotionUpdated = false; - bool maxhpUpdated = false; - bool maxspUpdated = false; - entry = &tmpEntry; - if (!libconfig->setting_lookup_int(mobt, "Id", &class_)) { - ShowWarning("mob_read_db_sub: Missing id in \"%s\", entry #%d, skipping.\n", source, class_); - return false; + if (entry->mob_id <= 1000 || entry->mob_id > MAX_MOB_DB) { + ShowError("mob_db_validate_entry: Invalid monster ID %d, must be in range %d-%d.\n", entry->mob_id, 1000, MAX_MOB_DB); + return 0; + } + if (pc->db_checkid(entry->mob_id)) { + ShowError("mob_read_db_sub: Invalid monster ID %d, reserved for player classes.\n", entry->mob_id); + return 0; + } + if (entry->mob_id >= MOB_CLONE_START && entry->mob_id < MOB_CLONE_END) { + ShowError("mob_read_db_sub: Invalid monster ID %d. Range %d-%d is reserved for player clones. Please increase MAX_MOB_DB (%d).\n", + entry->mob_id, MOB_CLONE_START, MOB_CLONE_END-1, MAX_MOB_DB); + return 0; } - if (class_ <= 1000 || class_ > MAX_MOB_DB) { - ShowError("mob_read_db_sub: Invalid monster ID %d, must be in range %d-%d.\n", class_, 1000, MAX_MOB_DB); - return false; + entry->lv = cap_value(entry->lv, 1, USHRT_MAX); + + if (entry->status.max_sp < 1) + entry->status.max_sp = 1; + //Since mobs always respawn with full life... + entry->status.hp = entry->status.max_hp; + entry->status.sp = entry->status.max_sp; + + /* + * Disabled for renewal since difference of 0 and 1 still has an impact in the formulas + * Just in case there is a mishandled division by zero please let us know. [malufett] + */ +#ifndef RENEWAL + //All status should be min 1 to prevent divisions by zero from some skills. [Skotlex] + if (entry->status.str < 1) entry->status.str = 1; + if (entry->status.agi < 1) entry->status.agi = 1; + if (entry->status.vit < 1) entry->status.vit = 1; + if (entry->status.int_< 1) entry->status.int_= 1; + if (entry->status.dex < 1) entry->status.dex = 1; + if (entry->status.luk < 1) entry->status.luk = 1; +#endif + + if (entry->range2 < 1) + entry->range2 = 1; + +#if 0 // This code was (accidentally) never enabled. It'll stay commented out until it's proven to be needed. + //Tests showed that chase range is effectively 2 cells larger than expected [Playtester] + if (entry->range3 > 0) + entry->range3 += 2; +#endif // 0 + + if (entry->range3 < entry->range2) + entry->range3 = entry->range2; + + entry->status.size = cap_value(entry->status.size, 0, 2); + + entry->status.race = cap_value(entry->status.race, 0, RC_MAX - 1); + + if (entry->status.def_ele >= ELE_MAX) { + ShowWarning("mob_read_db_sub: Invalid element type %d for monster ID %d (max=%d).\n", entry->status.def_ele, entry->mob_id, ELE_MAX-1); + entry->status.def_ele = ELE_NEUTRAL; + entry->status.ele_lv = 1; } - if (pc->db_checkid(class_)) { - ShowError("mob_read_db_sub: Invalid monster ID %d, reserved for player classes.\n", class_); - return false; + if (entry->status.ele_lv < 1 || entry->status.ele_lv > 4) { + ShowWarning("mob_read_db_sub: Invalid element level %d for monster ID %d, must be in range 1-4.\n", entry->status.ele_lv, entry->mob_id); + entry->status.ele_lv = 1; } - if (class_ >= MOB_CLONE_START && class_ < MOB_CLONE_END) { - ShowError("mob_read_db_sub: Invalid monster ID %d. Range %d-%d is reserved for player clones. Please increase MAX_MOB_DB (%d).\n", class_, MOB_CLONE_START, MOB_CLONE_END-1, MAX_MOB_DB); - return false; + // If the attack animation is longer than the delay, the client crops the attack animation! + // On aegis there is no real visible effect of having a recharge-time less than amotion anyway. + if (entry->status.adelay < entry->status.amotion) + entry->status.adelay = entry->status.amotion; + + // Fill in remaining status data by using a dummy monster. + data.bl.type = BL_MOB; + data.level = entry->lv; + memcpy(&data.status, &entry->status, sizeof(struct status_data)); + status->calc_misc(&data.bl, &entry->status, entry->lv); + + // Finally insert monster's data into the database. + if (mob->db_data[entry->mob_id] == NULL) { + mob->db_data[entry->mob_id] = (struct mob_db*)aMalloc(sizeof(struct mob_db)); + } else { + //Copy over spawn data + memcpy(&entry->spawn, mob->db_data[entry->mob_id]->spawn, sizeof(entry->spawn)); } + memcpy(mob->db_data[entry->mob_id], entry, sizeof(struct mob_db)); + + return entry->mob_id; +} + +/** + * Processes one mobdb entry from the libconfig file, loading and inserting it + * into the mob database. + * + * @param mobt Libconfig setting entry. It is expected to be valid and it + * won't be freed (it is care of the caller to do so if + * necessary). + * @param n Ordinal number of the entry, to be displayed in case of + * validation errors. + * @param source Source of the entry (file name), to be displayed in case of + * validation errors. + * @return Mob ID of the validated entry, or 0 in case of failure. + */ +int mob_read_db_sub(config_setting_t *mobt, int n, const char *source) +{ + struct mob_db md = { 0 }; + config_setting_t *t = NULL; + const char *str = NULL; + int i32 = 0; + bool inherit = false; + bool maxhpUpdated = false; + + nullpo_ret(mobt); + /* + * // Mandatory fields + * Id: ID + * SpriteName: "SPRITE_NAME" + * Name: "Mob name" + * JName: "Mob name" + * // Optional fields + * Lv: level + * Hp: health + * Sp: mana + * Exp: basic experience + * JExp: job experience + * AttackRange: attack range + * Attack: [attack1, attack2] + * Def: defence + * Mdef: magic defence + * Stats: { + * Str: strength + * Agi: agility + * Vit: vitality + * Int: intelligence + * Dex: dexterity + * Luk: luck + * } + * ViewRange: view range + * ChaseRange: chase range + * Size: size + * Race: race + * Element: (type, level) + * Mode: { + * CanMove: true/false + * Looter: true/false + * Aggressive: true/false + * Assist: true/false + * CastSensorIdle:true/false + * Boss: true/false + * Plant: true/false + * CanAttack: true/false + * Detector: true/false + * CastSensorChase: true/false + * ChangeChase: true/false + * Angry: true/false + * ChangeTargetMelee: true/false + * ChangeTargetChase: true/false + * TargetWeak: true/false + * } + * MoveSpeed: move speed + * AttackDelay: attack delay + * AttackMotion: attack motion + * DamageMotion: damage motion + * MvpExp: mvp experience + * MvpDrops: { + * AegisName: chance + * ... + * } + * Drops: { + * AegisName: chance + * ... + * } + */ + + if (!libconfig->setting_lookup_int(mobt, "Id", &i32)) { + ShowWarning("mob_read_db_sub: Missing id in \"%s\", entry #%d, skipping.\n", source, n); + return 0; + } + md.mob_id = i32; + md.vd.class_ = md.mob_id; if ((t = libconfig->setting_get_member(mobt, "Inherit")) && (inherit = libconfig->setting_get_bool(t))) { - if (!mob->db_data[class_]) { - ShowWarning("mob_read_db_sub: Trying to inherit nonexistent mob %d, default values will be used instead.\n", class_); + if (!mob->db_data[md.mob_id]) { + ShowWarning("mob_read_db_sub: Trying to inherit nonexistent mob %d, default values will be used instead.\n", md.mob_id); inherit = false; } else { // Use old entry as default - struct mob_db *old_entry = mob->db_data[class_]; - memcpy(entry, old_entry, sizeof(struct mob_db)); - inherit = true; + struct mob_db *old_entry = mob->db_data[md.mob_id]; + memcpy(&md, old_entry, sizeof(md)); } } - if (!inherit) { - memset(&tmpEntry, 0, sizeof(tmpEntry)); - } - - mstatus = &entry->status; - - entry->vd.class_ = class_; if (!libconfig->setting_lookup_string(mobt, "SpriteName", &str) || !*str ) { if (!inherit) { - ShowWarning("mob_read_db_sub: Missing SpriteName in mob %d of \"%s\", skipping.\n", class_, source); - return false; + ShowWarning("mob_read_db_sub: Missing SpriteName in mob %d of \"%s\", skipping.\n", md.mob_id, source); + return 0; } } else { - safestrncpy(entry->sprite, str, sizeof(entry->sprite)); + safestrncpy(md.sprite, str, sizeof(md.sprite)); } if (!libconfig->setting_lookup_string(mobt, "Name", &str) || !*str ) { if (!inherit) { - ShowWarning("mob_read_db_sub: Missing Name in mob %d of \"%s\", skipping.\n", class_, source); - return false; + ShowWarning("mob_read_db_sub: Missing Name in mob %d of \"%s\", skipping.\n", md.mob_id, source); + return 0; + } + } else { + safestrncpy(md.name, str, sizeof(md.name)); + } + + if (!libconfig->setting_lookup_string(mobt, "JName", &str) || !*str ) { + if (!inherit) { + safestrncpy(md.jname, md.name, sizeof(md.jname)); } } else { - safestrncpy(entry->name, str, sizeof(entry->name)); - safestrncpy(entry->jname, str, sizeof(entry->jname)); + safestrncpy(md.jname, str, sizeof(md.jname)); } if (mob->lookup_const(mobt, "Lv", &i32) && i32 >= 0) { - entry->lv = i32; - entry->lv = cap_value(entry->lv, 1, USHRT_MAX); + md.lv = i32; } else if (!inherit) { - entry->lv = 1; + md.lv = 1; } if (mob->lookup_const(mobt, "Hp", &i32) && i32 >= 0) { - mstatus->max_hp = i32; - maxhpUpdated = true; + md.status.max_hp = i32; + maxhpUpdated = true; // battle_config modifiers to max_hp are applied below } else if (!inherit) { - mstatus->max_hp = 1; - maxhpUpdated = true; + md.status.max_hp = 1; + maxhpUpdated = true; // battle_config modifiers to max_hp are applied below } if (mob->lookup_const(mobt, "Sp", &i32) && i32 >= 0) { - mstatus->max_sp = i32; - maxspUpdated = true; + md.status.max_sp = i32; } else if (!inherit) { - maxspUpdated = true; + md.status.max_sp = 1; } if (mob->lookup_const(mobt, "Exp", &i32) && i32 >= 0) { - exp = (double)(i32) * (double)battle_config.base_exp_rate / 100.; - entry->base_exp = (unsigned int)cap_value(exp, 0, UINT_MAX); + int64 exp = apply_percentrate64(i32, battle_config.base_exp_rate, 100); + md.base_exp = (unsigned int)cap_value(exp, 0, UINT_MAX); } if (mob->lookup_const(mobt, "JExp", &i32) && i32 >= 0) { - exp = (double)(i32) * (double)battle_config.job_exp_rate / 100.; - entry->job_exp = (unsigned int)cap_value(exp, 0, UINT_MAX); + int64 exp = apply_percentrate64(i32, battle_config.job_exp_rate, 100); + md.job_exp = (unsigned int)cap_value(exp, 0, UINT_MAX); } if (mob->lookup_const(mobt, "AttackRange", &i32) && i32 >= 0) { - mstatus->rhw.range = i32; + md.status.rhw.range = i32; } else { - mstatus->rhw.range = 1; + md.status.rhw.range = 1; } if ((t = libconfig->setting_get_member(mobt, "Attack"))) { if (config_setting_is_aggregate(t)) { if (libconfig->setting_length(t) >= 2) - mstatus->rhw.atk2 = libconfig->setting_get_int_elem(t, 1); + md.status.rhw.atk2 = libconfig->setting_get_int_elem(t, 1); if (libconfig->setting_length(t) >= 1) - mstatus->rhw.atk = libconfig->setting_get_int_elem(t, 0); + md.status.rhw.atk = libconfig->setting_get_int_elem(t, 0); } else if (mob->lookup_const(mobt, "Attack", &i32) && i32 >= 0) { - mstatus->rhw.atk = i32; - mstatus->rhw.atk2 = i32; + md.status.rhw.atk = i32; + md.status.rhw.atk2 = i32; } } if (mob->lookup_const(mobt, "Def", &i32) && i32 >= 0) { - mstatus->def = mob_parse_dbrow_cap_value(class_, DEFTYPE_MIN, DEFTYPE_MAX, i32); + md.status.def = mob_parse_dbrow_cap_value(md.mob_id, DEFTYPE_MIN, DEFTYPE_MAX, i32); } if (mob->lookup_const(mobt, "Mdef", &i32) && i32 >= 0) { - mstatus->mdef = mob_parse_dbrow_cap_value(class_, DEFTYPE_MIN, DEFTYPE_MAX, i32); + md.status.mdef = mob_parse_dbrow_cap_value(md.mob_id, DEFTYPE_MIN, DEFTYPE_MAX, i32); } if ((t = libconfig->setting_get_member(mobt, "Stats"))) { if (config_setting_is_group(t)) { - mob->read_db_stats_sub(entry, mstatus, class_, t); + mob->read_db_stats_sub(&md, t); } else if (mob->lookup_const(mobt, "Stats", &i32) && i32 >= 0) { - mstatus->str = mstatus->agi = mstatus->vit = mstatus->int_ = mstatus->dex = mstatus->luk = - mob_parse_dbrow_cap_value(class_, UINT16_MIN, UINT16_MAX, i32); + md.status.str = md.status.agi = md.status.vit = md.status.int_ = md.status.dex = md.status.luk = + mob_parse_dbrow_cap_value(md.mob_id, UINT16_MIN, UINT16_MAX, i32); } } - /* - * Disabled for renewal since difference of 0 and 1 still has an impact in the formulas - * Just in case there is a mishandled division by zero please let us know. [malufett] - */ -#ifndef RENEWAL - //All status should be min 1 to prevent divisions by zero from some skills. [Skotlex] - if (mstatus->str < 1) mstatus->str = 1; - if (mstatus->agi < 1) mstatus->agi = 1; - if (mstatus->vit < 1) mstatus->vit = 1; - if (mstatus->int_< 1) mstatus->int_= 1; - if (mstatus->dex < 1) mstatus->dex = 1; - if (mstatus->luk < 1) mstatus->luk = 1; -#endif - - //Tests showed that chase range is effectively 2 cells larger than expected [Playtester] - if (entry->range3 > 0) - entry->range3 += 2; - if (mob->lookup_const(mobt, "ViewRange", &i32) && i32 >= 0) { - entry->range2 = i32; - range2Updated = true; + if (battle_config.view_range_rate != 100) { + md.range2 = i32 * battle_config.view_range_rate / 100; + } else { + md.range2 = i32; + } } else if (!inherit) { - entry->range2 = 1; - range2Updated = true; + md.range2 = 1; } if (mob->lookup_const(mobt, "ChaseRange", &i32) && i32 >= 0) { - entry->range3 = i32; - range3Updated = true; - } else if (!inherit) { - entry->range3 = 1; - range3Updated = true; - } - if (range2Updated) { - if (battle_config.view_range_rate != 100) { - entry->range2 = entry->range2 * battle_config.view_range_rate / 100; - if (entry->range2 < 1) - entry->range2 = 1; - } - } - if (range3Updated) { if (battle_config.chase_range_rate != 100) { - entry->range3 = entry->range3 * battle_config.chase_range_rate / 100; - if (entry->range3 < entry->range2) - entry->range3 = entry->range2; + md.range3 = i32 * battle_config.chase_range_rate / 100; + } else { + md.range3 = i32; } + } else if (!inherit) { + md.range3 = 1; } if (mob->lookup_const(mobt, "Size", &i32) && i32 >= 0) { - mstatus->size = i32; - mstatus->size = cap_value(mstatus->size, 0, 2); + md.status.size = i32; } else if (!inherit) { - mstatus->size = 0; + md.status.size = 0; } if (mob->lookup_const(mobt, "Race", &i32) && i32 >= 0) { - mstatus->race = i32; - mstatus->race = cap_value(mstatus->race, 0, RC_MAX - 1); + md.status.race = i32; } else if (!inherit) { - mstatus->race = 0; + md.status.race = 0; } if ((t = libconfig->setting_get_member(mobt, "Element")) && config_setting_is_list(t)) { + int value = 0; if (mob->get_const(libconfig->setting_get_elem(t, 0), &i32) && mob->get_const(libconfig->setting_get_elem(t, 1), &value)) { - mstatus->def_ele = i32; - mstatus->ele_lv = value; - } - } else { - if (!inherit) { - ShowError("mob_read_db_sub: Missing element for monster ID %d.\n", class_); - return false; - } - } - - if (mstatus->def_ele >= ELE_MAX) { - if (!inherit) { - ShowError("mob_read_db_sub: Invalid element type %d for monster ID %d (max=%d).\n", mstatus->def_ele, class_, ELE_MAX-1); - return false; - } - } - if (mstatus->ele_lv < 1 || mstatus->ele_lv > 4) { - if (!inherit) { - ShowError("mob_read_db_sub: Invalid element level %d for monster ID %d, must be in range 1-4.\n", mstatus->ele_lv, class_); - return false; + md.status.def_ele = i32; + md.status.ele_lv = value; + } else if (!inherit) { + ShowWarning("mob_read_db_sub: Missing element for monster ID %d.\n", md.mob_id); + md.status.def_ele = ELE_NEUTRAL; + md.status.ele_lv = 1; } + } else if (!inherit) { + ShowWarning("mob_read_db_sub: Missing element for monster ID %d.\n", md.mob_id); + md.status.def_ele = ELE_NEUTRAL; + md.status.ele_lv = 1; } if ((t = libconfig->setting_get_member(mobt, "Mode"))) { if (config_setting_is_group(t)) { - mstatus->mode = mob->read_db_mode_sub(entry, mstatus, class_, t); + md.status.mode = mob->read_db_mode_sub(&md, t); } else if (mob->lookup_const(mobt, "Mode", &i32) && i32 >= 0) { - mstatus->mode = i32; + md.status.mode = i32; } } - if (!battle_config.monster_active_enable) - mstatus->mode &= ~MD_AGGRESSIVE; + md.status.mode &= ~MD_AGGRESSIVE; if (mob->lookup_const(mobt, "MoveSpeed", &i32) && i32 >= 0) { - mstatus->speed = i32; + md.status.speed = i32; } - mstatus->aspd_rate = 1000; + md.status.aspd_rate = 1000; if (mob->lookup_const(mobt, "AttackDelay", &i32) && i32 >= 0) { - mstatus->adelay = cap_value(i32, battle_config.monster_max_aspd*2, 4000); + md.status.adelay = cap_value(i32, battle_config.monster_max_aspd*2, 4000); } else if (!inherit) { - mstatus->adelay = 4000; + md.status.adelay = 4000; } if (mob->lookup_const(mobt, "AttackMotion", &i32) && i32 >= 0) { - mstatus->amotion = cap_value(i32, battle_config.monster_max_aspd, 2000); + md.status.amotion = cap_value(i32, battle_config.monster_max_aspd, 2000); } else if (!inherit) { - mstatus->amotion = 2000; + md.status.amotion = 2000; } - //If the attack animation is longer than the delay, the client crops the attack animation! - //On aegis there is no real visible effect of having a recharge-time less than amotion anyway. - if (mstatus->adelay < mstatus->amotion) - mstatus->adelay = mstatus->amotion; - if (mob->lookup_const(mobt, "DamageMotion", &i32) && i32 >= 0) { - mstatus->dmotion = i32; - dmotionUpdated = true; - } else if (!inherit) { - dmotionUpdated = true; + if (battle_config.monster_damage_delay_rate != 100) + md.status.dmotion = i32 * battle_config.monster_damage_delay_rate / 100; + else + md.status.dmotion = i32; } - if (dmotionUpdated && battle_config.monster_damage_delay_rate != 100) - mstatus->dmotion = mstatus->dmotion * battle_config.monster_damage_delay_rate / 100; - - // Fill in remaining status data by using a dummy monster. - data.bl.type = BL_MOB; - data.level = entry->lv; - memcpy(&data.status, mstatus, sizeof(struct status_data)); - status->calc_misc(&data.bl, mstatus, entry->lv); - // MVP EXP Bonus: MEXP - // Some new MVP's MEXP multiple by high exp-rate cause overflow. [LuzZza] if (mob->lookup_const(mobt, "MvpExp", &i32) && i32 >= 0) { - exp = (double)i32 * (double)battle_config.mvp_exp_rate / 100.; - entry->mexp = (unsigned int)cap_value(exp, 0, UINT_MAX); - } else if (!inherit) { - exp = 0; + // Some new MVP's MEXP multiple by high exp-rate cause overflow. [LuzZza] + int64 exp = apply_percentrate64(i32, battle_config.mvp_exp_rate, 100); + md.mexp = (unsigned int)cap_value(exp, 0, UINT_MAX); } if (maxhpUpdated) { - //Now that we know if it is an mvp or not, apply battle_config modifiers [Skotlex] - maxhp = (double)mstatus->max_hp; - if (entry->mexp > 0) { //Mvp - if (battle_config.mvp_hp_rate != 100) - maxhp = maxhp * (double)battle_config.mvp_hp_rate / 100.; + // Now that we know if it is an mvp or not, apply battle_config modifiers [Skotlex] + int64 maxhp = md.status.max_hp; + if (md.mexp > 0) { //Mvp + maxhp = apply_percentrate64(maxhp, battle_config.mvp_hp_rate, 100); } else { //Normal mob - if (battle_config.monster_hp_rate != 100) - maxhp = maxhp * (double)battle_config.monster_hp_rate / 100.; + maxhp = apply_percentrate64(maxhp, battle_config.monster_hp_rate, 100); } - mstatus->max_hp = (unsigned int)cap_value(maxhp, 1, UINT_MAX); + md.status.max_hp = (unsigned int)cap_value(maxhp, 1, UINT_MAX); } - if (maxspUpdated) { - if(mstatus->max_sp < 1) mstatus->max_sp = 1; - } - - //Since mobs always respawn with full life... - mstatus->hp = mstatus->max_hp; - mstatus->sp = mstatus->max_sp; if ((t = libconfig->setting_get_member(mobt, "MvpDrops"))) { if (config_setting_is_group(t)) { - mob->read_db_mvpdrops_sub(entry, mstatus, class_, t); + mob->read_db_mvpdrops_sub(&md, t); } } if ((t = libconfig->setting_get_member(mobt, "Drops"))) { if (config_setting_is_group(t)) { - mob->read_db_drops_sub(entry, mstatus, class_, t); + mob->read_db_drops_sub(&md, t); } } - mob->read_db_additional_fields(entry, class_, mobt, id, source); - // Finally insert monster's data into the database. - if (mob->db_data[class_] == NULL) - mob->db_data[class_] = (struct mob_db*)aMalloc(sizeof(struct mob_db)); - else - //Copy over spawn data - memcpy(&entry->spawn, mob->db_data[class_]->spawn, sizeof(entry->spawn)); + mob->read_db_additional_fields(&md, mobt, n, source); - memcpy(mob->db_data[class_], entry, sizeof(struct mob_db)); - return true; + return mob->db_validate_entry(&md, n, source); } -void mob_read_db_additional_fields(struct mob_db *entry, int class_, config_setting_t *it, int n, const char *source) +/** + * Processes any (plugin-defined) additional fields for a mob_db entry. + * + * @param[in,out] entry The destination mob_db entry, already initialized + * (mob_id, status.mode are expected to be already set). + * @param[in] t The libconfig entry. + * @param[in] n Ordinal number of the entry, to be displayed in case + * of validation errors. + * @param[in] source Source of the entry (file name), to be displayed in + * case of validation errors. + */ +void mob_read_db_additional_fields(struct mob_db *entry, config_setting_t *t, int n, const char *source) { // do nothing. plugins can do own work } @@ -4282,13 +4401,23 @@ void mob_readdb(void) { mob->name_constants(); } +/** + * Reads from a libconfig-formatted mobdb file and inserts the found entries + * into the mob database, overwriting duplicate ones (i.e. mob_db2 overriding + * mob_db.) + * + * @param filename File name, relative to the database path. + * @param ignore_missing Whether to ignore errors caused by a missing db file. + * @return the number of found entries. + */ int mob_read_libconfig(const char *filename, bool ignore_missing) { + bool duplicate[MAX_MOB_DB] = { 0 }; config_t mob_db_conf; char filepath[256]; config_setting_t *mdb; config_setting_t *t; - int i = 0; + int i = 0, count = 0; nullpo_ret(filename); sprintf(filepath, "%s/%s", map->db_path, filename); @@ -4298,15 +4427,28 @@ int mob_read_libconfig(const char *filename, bool ignore_missing) if (libconfig->read_file(&mob_db_conf, filepath) || !(mdb = libconfig->setting_get_member(mob_db_conf.root, "mob_db"))) { ShowError("can't read %s\n", filepath); - return -1; + return 0; } while ((t = libconfig->setting_get_elem(mdb, i++))) { - mob->read_db_sub(t, i - 1, filepath); + int mob_id = mob->read_db_sub(t, i - 1, filename); + + if (mob_id <= 0 || mob_id >= MAX_MOB_DB) + continue; + + count++; + + if (duplicate[mob_id]) { + ShowWarning("mob_read_libconfig:%s: duplicate entry of ID #%d (%s/%s)\n", + filename, mob_id, mob->db_data[mob_id]->sprite, mob->db_data[mob_id]->jname); + } else { + duplicate[mob_id] = true; + } } libconfig->destroy(&mob_db_conf); - ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", i, filepath); - return 0; + ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", count, filename); + + return count; } void mob_name_constants(void) { @@ -5127,6 +5269,7 @@ void mob_defaults(void) { mob->item_dropratio_adjust = item_dropratio_adjust; mob->lookup_const = mob_lookup_const; mob->get_const = mob_get_const; + mob->db_validate_entry = mob_db_validate_entry; mob->readdb = mob_readdb; mob->read_libconfig = mob_read_libconfig; mob->read_db_additional_fields = mob_read_db_additional_fields; diff --git a/src/map/mob.h b/src/map/mob.h index 77218bf4a..9a5239b11 100644 --- a/src/map/mob.h +++ b/src/map/mob.h @@ -141,6 +141,7 @@ struct spawn_info { }; struct mob_db { + int mob_id; char sprite[NAME_LENGTH],name[NAME_LENGTH],jname[NAME_LENGTH]; unsigned int base_exp,job_exp; unsigned int mexp; @@ -511,13 +512,14 @@ struct mob_interface { void (*readdb) (void); bool (*lookup_const) (const config_setting_t *it, const char *name, int *value); bool (*get_const) (const config_setting_t *it, int *value); + int (*db_validate_entry) (struct mob_db *entry, int n, const char *source); int (*read_libconfig) (const char *filename, bool ignore_missing); - void (*read_db_additional_fields) (struct mob_db *entry, int class_, config_setting_t *it, int n, const char *source); - bool (*read_db_sub) (config_setting_t *mobt, int id, const char *source); - void (*read_db_drops_sub) (struct mob_db *entry, struct status_data *mstatus, int class_, config_setting_t *t); - void (*read_db_mvpdrops_sub) (struct mob_db *entry, struct status_data *mstatus, int class_, config_setting_t *t); - int (*read_db_mode_sub) (struct mob_db *entry, struct status_data *mstatus, int class_, config_setting_t *t); - void (*read_db_stats_sub) (struct mob_db *entry, struct status_data *mstatus, int class_, config_setting_t *t); + void (*read_db_additional_fields) (struct mob_db *entry, config_setting_t *it, int n, const char *source); + int (*read_db_sub) (config_setting_t *mobt, int id, const char *source); + void (*read_db_drops_sub) (struct mob_db *entry, config_setting_t *t); + void (*read_db_mvpdrops_sub) (struct mob_db *entry, config_setting_t *t); + int (*read_db_mode_sub) (struct mob_db *entry, config_setting_t *t); + void (*read_db_stats_sub) (struct mob_db *entry, config_setting_t *t); void (*name_constants) (void); bool (*readdb_mobavail) (char *str[], int columns, int current); int (*read_randommonster) (void); diff --git a/src/map/npc.c b/src/map/npc.c index ae3cf56d0..23b0b9555 100644 --- a/src/map/npc.c +++ b/src/map/npc.c @@ -1769,7 +1769,7 @@ int npc_cashshop_buy(struct map_session_data *sd, int nameid, int amount, int po if( w + sd->weight > sd->max_weight ) return ERROR_TYPE_INVENTORY_WEIGHT; - if( (double)shop[i].value * amount > INT_MAX ) { + if ((int64)shop[i].value * amount > INT_MAX) { ShowWarning("npc_cashshop_buy: Item '%s' (%d) price overflow attempt!\n", item->name, nameid); ShowDebug("(NPC:'%s' (%s,%d,%d), player:'%s' (%d/%d), value:%d, amount:%d)\n", nd->exname, map->list[nd->bl.m].name, nd->bl.x, nd->bl.y, @@ -1812,7 +1812,7 @@ int npc_cashshop_buy(struct map_session_data *sd, int nameid, int amount, int po int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list) { struct npc_data* nd; struct npc_item_list *shop = NULL; - double z; + int64 z; int i,j,w,skill_t,new_, idx = skill->get_index(MC_DISCOUNT); unsigned short shop_size = 0; @@ -1883,21 +1883,21 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list) { value = pc->modifybuyvalue(sd,value); - z += (double)value * amount; + z += (int64)value * amount; w += itemdb_weight(nameid) * amount; } if( nd->master_nd != NULL ) //Script-based shops. return npc->buylist_sub(sd,n,item_list,nd->master_nd); - if( z > (double)sd->status.zeny ) + if (z > sd->status.zeny) return 1; // Not enough Zeny if( w + sd->weight > sd->max_weight ) return 2; // Too heavy if( pc->inventoryblank(sd) < new_ ) return 3; // Not enough space to store items - pc->payzeny(sd,(int)z,LOG_TYPE_NPC, NULL); + pc->payzeny(sd, (int)z, LOG_TYPE_NPC, NULL); for( i = 0; i < n; ++i ) { int nameid = item_list[i*2+1]; @@ -1921,10 +1921,10 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list) { skill_t = sd->status.skill[idx].flag - SKILL_FLAG_REPLACED_LV_0; if( skill_t > 0 ) { - z = z * (double)skill_t * (double)battle_config.shop_exp/10000.; - if( z < 1 ) + z = apply_percentrate64(z, skill_t * battle_config.shop_exp, 10000); + if (z < 1) z = 1; - pc->gainexp(sd,NULL,0,(int)z, false); + pc->gainexp(sd, NULL, 0, (int)z, false); } } @@ -1937,7 +1937,7 @@ int npc_buylist(struct map_session_data* sd, int n, unsigned short* item_list) { int npc_market_buylist(struct map_session_data* sd, unsigned short list_size, struct packet_npc_market_purchase *p) { struct npc_data* nd; struct npc_item_list *shop = NULL; - double z; + int64 z; int i,j,w,new_; unsigned short shop_size = 0; @@ -1997,11 +1997,11 @@ int npc_market_buylist(struct map_session_data* sd, unsigned short list_size, st return 1; } - z += (double)value * amount; + z += (int64)value * amount; w += itemdb_weight(nameid) * amount; } - if( z > (double)sd->status.zeny ) /* TODO find official response for this */ + if (z > sd->status.zeny) /* TODO find official response for this */ return 1; // Not enough Zeny if( w + sd->weight > sd->max_weight ) /* TODO find official response for this */ @@ -2098,7 +2098,7 @@ int npc_selllist_sub(struct map_session_data* sd, int n, unsigned short* item_li /// @param item_list 'n' pairs <index,amount> /// @return result code for clif->parse_NpcSellListSend int npc_selllist(struct map_session_data* sd, int n, unsigned short* item_list) { - double z; + int64 z; int i,skill_t, skill_idx = skill->get_index(MC_OVERCHARGE); struct npc_data *nd; bool duplicates[MAX_INVENTORY] = { 0 }; @@ -2147,7 +2147,7 @@ int npc_selllist(struct map_session_data* sd, int n, unsigned short* item_list) value = pc->modifysellvalue(sd, sd->inventory_data[idx]->value_sell); - z += (double)value*amount; + z += (int64)value * amount; } if( nd->master_nd ) { // Script-controlled shops @@ -2181,8 +2181,8 @@ int npc_selllist(struct map_session_data* sd, int n, unsigned short* item_list) skill_t = sd->status.skill[skill_idx].flag - SKILL_FLAG_REPLACED_LV_0; if( skill_t > 0 ) { - z = z * (double)skill_t * (double)battle_config.shop_exp/10000.; - if( z < 1 ) + z = apply_percentrate64(z, skill_t * battle_config.shop_exp, 10000); + if (z < 1) z = 1; pc->gainexp(sd, NULL, 0, (int)z, false); } diff --git a/src/map/pc.c b/src/map/pc.c index 491584385..8d1df71a9 100644 --- a/src/map/pc.c +++ b/src/map/pc.c @@ -4156,34 +4156,39 @@ int pc_insert_card(struct map_session_data* sd, int idx_card, int idx_equip) /*========================================== * Update buying value by skills *------------------------------------------*/ -int pc_modifybuyvalue(struct map_session_data *sd,int orig_value) { - int skill_lv,val = orig_value,rate1 = 0,rate2 = 0; - if((skill_lv=pc->checkskill(sd,MC_DISCOUNT))>0) // merchant discount +int pc_modifybuyvalue(struct map_session_data *sd, int orig_value) +{ + int skill_lv, rate1 = 0, rate2 = 0; + if (orig_value <= 0) + return 0; + if ((skill_lv=pc->checkskill(sd,MC_DISCOUNT)) > 0) // merchant discount rate1 = 5+skill_lv*2-((skill_lv==10)? 1:0); - if((skill_lv=pc->checkskill(sd,RG_COMPULSION))>0) // rogue discount + if ((skill_lv=pc->checkskill(sd,RG_COMPULSION)) > 0) // rogue discount rate2 = 5+skill_lv*4; - if(rate1 < rate2) rate1 = rate2; - if(rate1) - val = (int)((double)orig_value*(double)(100-rate1)/100.); - if(val < 0) val = 0; - if(orig_value > 0 && val < 1) val = 1; - - return val; + if (rate1 < rate2) + rate1 = rate2; + if (rate1 != 0) + orig_value = apply_percentrate(orig_value, 100-rate1, 100); + if (orig_value < 1) + orig_value = 1; + return orig_value; } /*========================================== * Update selling value by skills *------------------------------------------*/ -int pc_modifysellvalue(struct map_session_data *sd,int orig_value) { - int skill_lv,val = orig_value,rate = 0; - if((skill_lv=pc->checkskill(sd,MC_OVERCHARGE))>0) //OverCharge +int pc_modifysellvalue(struct map_session_data *sd, int orig_value) +{ + int skill_lv, rate = 0; + if (orig_value <= 0) + return 0; + if ((skill_lv=pc->checkskill(sd,MC_OVERCHARGE)) > 0) //OverCharge rate = 5+skill_lv*2-((skill_lv==10)? 1:0); - if(rate) - val = (int)((double)orig_value*(double)(100+rate)/100.); - if(val < 0) val = 0; - if(orig_value > 0 && val < 1) val = 1; - - return val; + if (rate != 0) + orig_value = apply_percentrate(orig_value, 100+rate, 100); + if (orig_value < 1) + orig_value = 1; + return orig_value; } /*========================================== @@ -5259,7 +5264,7 @@ int pc_show_steal(struct block_list *bl,va_list ap) int pc_steal_item(struct map_session_data *sd,struct block_list *bl, uint16 skill_lv) { int i,itemid,flag; - double rate; + int rate; struct status_data *sd_status, *md_status; struct mob_data *md = BL_CAST(BL_MOB, bl); struct item tmp_item; @@ -5284,18 +5289,22 @@ int pc_steal_item(struct map_session_data *sd,struct block_list *bl, uint16 skil } // base skill success chance (percentual) - rate = (sd_status->dex - md_status->dex)/2 + skill_lv*6 + 4; - rate += sd->bonus.add_steal_rate; + rate = (sd_status->dex - md_status->dex)/2 + skill_lv*6 + 4 + sd->bonus.add_steal_rate; if( rate < 1 ) return 0; // Try dropping one item, in the order from first to last possible slot. // Droprate is affected by the skill success rate. - for( i = 0; i < MAX_STEAL_DROP; i++ ) - if (md->db->dropitem[i].nameid > 0 && (data = itemdb->exists(md->db->dropitem[i].nameid)) != NULL && rnd() % 10000 < md->db->dropitem[i].p * rate/100.) + for (i = 0; i < MAX_STEAL_DROP; i++) { + if (md->db->dropitem[i].nameid == 0) + continue; + if ((data = itemdb->exists(md->db->dropitem[i].nameid)) == NULL) + continue; + if (rnd() % 10000 < apply_percentrate(md->db->dropitem[i].p, rate, 100)) break; - if( i == MAX_STEAL_DROP ) + } + if (i == MAX_STEAL_DROP) return 0; itemid = md->db->dropitem[i].nameid; @@ -6612,16 +6621,16 @@ void pc_calcexp(struct map_session_data *sd, unsigned int *base_exp, unsigned in if (sd->sc.data[SC_OVERLAPEXPUP]) bonus += sd->sc.data[SC_OVERLAPEXPUP]->val1; - *base_exp = (unsigned int) cap_value(*base_exp + (double)*base_exp * bonus/100., 1, UINT_MAX); + *base_exp = (unsigned int) cap_value(*base_exp + apply_percentrate64(*base_exp, bonus, 100), 1, UINT_MAX); if (sd->sc.data[SC_CASH_PLUSONLYJOBEXP]) bonus += sd->sc.data[SC_CASH_PLUSONLYJOBEXP]->val1; - *job_exp = (unsigned int) cap_value(*job_exp + (double)*job_exp * bonus/100., 1, UINT_MAX); + *job_exp = (unsigned int) cap_value(*job_exp + apply_percentrate64(*job_exp, bonus, 100), 1, UINT_MAX); - if( sd->status.mod_exp != 100 ) { - *base_exp = (unsigned int) cap_value((double)*base_exp * sd->status.mod_exp/100., 1, UINT_MAX); - *job_exp = (unsigned int) cap_value((double)*job_exp * sd->status.mod_exp/100., 1, UINT_MAX); + if (sd->status.mod_exp != 100) { + *base_exp = (unsigned int) cap_value(apply_percentrate64(*base_exp, sd->status.mod_exp, 100), 1, UINT_MAX); + *job_exp = (unsigned int) cap_value(apply_percentrate64(*job_exp, sd->status.mod_exp, 100), 1, UINT_MAX); } } @@ -7713,18 +7722,18 @@ int pc_dead(struct map_session_data *sd,struct block_list *src) { && !map->list[sd->bl.m].flag.noexppenalty && !map_flag_gvg2(sd->bl.m) && !sd->sc.data[SC_BABY] && !sd->sc.data[SC_CASH_DEATHPENALTY] ) { - unsigned int base_penalty = 0; if (battle_config.death_penalty_base > 0) { + unsigned int base_penalty = 0; switch (battle_config.death_penalty_type) { case 1: - base_penalty = (unsigned int) ((double)pc->nextbaseexp(sd) * (double)battle_config.death_penalty_base/10000); + base_penalty = (unsigned int) apply_percentrate64(pc->nextbaseexp(sd), battle_config.death_penalty_base, 10000); break; case 2: - base_penalty = (unsigned int) ((double)sd->status.base_exp * (double)battle_config.death_penalty_base/10000); + base_penalty = (unsigned int) apply_percentrate64(sd->status.base_exp, battle_config.death_penalty_base, 10000); break; } - if(base_penalty) { + if (base_penalty != 0) { if (battle_config.pk_mode && src && src->type==BL_PC) base_penalty*=2; if( sd->status.mod_death != 100 ) @@ -7735,31 +7744,31 @@ int pc_dead(struct map_session_data *sd,struct block_list *src) { } if(battle_config.death_penalty_job > 0) { - base_penalty = 0; + unsigned int job_penalty = 0; switch (battle_config.death_penalty_type) { case 1: - base_penalty = (unsigned int) ((double)pc->nextjobexp(sd) * (double)battle_config.death_penalty_job/10000); + job_penalty = (unsigned int) apply_percentrate64(pc->nextjobexp(sd), battle_config.death_penalty_job, 10000); break; case 2: - base_penalty = (unsigned int) ((double)sd->status.job_exp * (double)battle_config.death_penalty_job/10000); + job_penalty = (unsigned int) apply_percentrate64(sd->status.job_exp, battle_config.death_penalty_job, 10000); break; } - if(base_penalty) { + if (job_penalty != 0) { if (battle_config.pk_mode && src && src->type==BL_PC) - base_penalty*=2; + job_penalty*=2; if( sd->status.mod_death != 100 ) - base_penalty = base_penalty * sd->status.mod_death / 100; - sd->status.job_exp -= min(sd->status.job_exp, base_penalty); + job_penalty = job_penalty * sd->status.mod_death / 100; + sd->status.job_exp -= min(sd->status.job_exp, job_penalty); clif->updatestatus(sd,SP_JOBEXP); } } - if(battle_config.zeny_penalty > 0 && !map->list[sd->bl.m].flag.nozenypenalty) { - base_penalty = (unsigned int)((double)sd->status.zeny * (double)battle_config.zeny_penalty / 10000.); - if(base_penalty) - pc->payzeny(sd, base_penalty, LOG_TYPE_PICKDROP_PLAYER, NULL); + if (battle_config.zeny_penalty > 0 && !map->list[sd->bl.m].flag.nozenypenalty) { + int zeny_penalty = apply_percentrate(sd->status.zeny, battle_config.zeny_penalty, 10000); + if (zeny_penalty != 0) + pc->payzeny(sd, zeny_penalty, LOG_TYPE_PICKDROP_PLAYER, NULL); } } diff --git a/src/map/script.c b/src/map/script.c index 64bdba592..f3c839555 100644 --- a/src/map/script.c +++ b/src/map/script.c @@ -3845,7 +3845,7 @@ void op_2str(struct script_state* st, int op, const char* s1, const char* s2) void op_2num(struct script_state* st, int op, int i1, int i2) { int ret; - double ret_double; + int64 ret64; switch( op ) { case C_AND: ret = i1 & i2; break; @@ -3877,25 +3877,21 @@ void op_2num(struct script_state* st, int op, int i1, int i2) ret = i1 % i2; break; default: - switch( op ) - {// operators that can overflow/underflow - case C_ADD: ret = i1 + i2; ret_double = (double)i1 + (double)i2; break; - case C_SUB: ret = i1 - i2; ret_double = (double)i1 - (double)i2; break; - case C_MUL: ret = i1 * i2; ret_double = (double)i1 * (double)i2; break; + switch (op) { // operators that can overflow/underflow + case C_ADD: ret = i1 + i2; ret64 = (int64)i1 + i2; break; + case C_SUB: ret = i1 - i2; ret64 = (int64)i1 - i2; break; + case C_MUL: ret = i1 * i2; ret64 = (int64)i1 * i2; break; default: ShowError("script:op_2num: unexpected number operator %s i1=%d i2=%d\n", script->op2name(op), i1, i2); script->reportsrc(st); script_pushnil(st); return; } - if( ret_double < (double)INT_MIN ) - { + if (ret64 < INT_MIN) { ShowWarning("script:op_2num: underflow detected op=%s i1=%d i2=%d\n", script->op2name(op), i1, i2); script->reportsrc(st); ret = INT_MIN; - } - else if( ret_double > (double)INT_MAX ) - { + } else if (ret64 > INT_MAX) { ShowWarning("script:op_2num: overflow detected op=%s i1=%d i2=%d\n", script->op2name(op), i1, i2); script->reportsrc(st); ret = INT_MAX; @@ -9702,20 +9698,18 @@ BUILDIN(makepet) BUILDIN(getexp) { int base=0,job=0; - double bonus; struct map_session_data *sd = script->rid2sd(st); if (sd == NULL) return true; - base=script_getnum(st,2); - job =script_getnum(st,3); - if(base<0 || job<0) + base = script_getnum(st,2); + job = script_getnum(st,3); + if (base < 0 || job < 0) return true; // bonus for npc-given exp - bonus = battle_config.quest_exp_rate / 100.; - base = (int) cap_value(base * bonus, 0, INT_MAX); - job = (int) cap_value(job * bonus, 0, INT_MAX); + base = cap_value(apply_percentrate(base, battle_config.quest_exp_rate, 100), 0, INT_MAX); + job = cap_value(apply_percentrate(job, battle_config.quest_exp_rate, 100), 0, INT_MAX); pc->gainexp(sd, &sd->bl, base, job, true); diff --git a/src/map/vending.c b/src/map/vending.c index 810e6b07a..6e74e6c3e 100644 --- a/src/map/vending.c +++ b/src/map/vending.c @@ -89,7 +89,7 @@ void vending_vendinglistreq(struct map_session_data* sd, unsigned int id) { *------------------------------------------*/ void vending_purchasereq(struct map_session_data* sd, int aid, unsigned int uid, const uint8* data, int count) { int i, j, cursor, w, new_ = 0, blank, vend_list[MAX_VENDING]; - double z; + int64 z; struct s_vending vend[MAX_VENDING]; // against duplicate packets struct map_session_data* vsd = map->id2sd(aid); @@ -116,7 +116,7 @@ void vending_purchasereq(struct map_session_data* sd, int aid, unsigned int uid, memcpy(&vend, &vsd->vending, sizeof(vsd->vending)); // copy vending list // some checks - z = 0.; // zeny counter + z = 0; // zeny counter w = 0; // weight counter for( i = 0; i < count; i++ ) { short amount = *(uint16*)(data + 4*i + 0); @@ -136,12 +136,12 @@ void vending_purchasereq(struct map_session_data* sd, int aid, unsigned int uid, else vend_list[i] = j; - z += ((double)vsd->vending[j].value * (double)amount); - if( z > (double)sd->status.zeny || z < 0. || z > (double)MAX_ZENY ) { + z += (int64)vsd->vending[j].value * amount; + if (z > sd->status.zeny || z < 0 || z > MAX_ZENY) { clif->buyvending(sd, idx, amount, 1); // you don't have enough zeny return; } - if( z + (double)vsd->status.zeny > (double)MAX_ZENY && !battle_config.vending_over_max ) { + if (z > MAX_ZENY - vsd->status.zeny && !battle_config.vending_over_max) { clif->buyvending(sd, idx, vsd->vending[j].amount, 4); // too much zeny = overflow return; @@ -181,7 +181,7 @@ void vending_purchasereq(struct map_session_data* sd, int aid, unsigned int uid, pc->payzeny(sd, (int)z, LOG_TYPE_VENDING, vsd); if( battle_config.vending_tax ) - z -= z * (battle_config.vending_tax/10000.); + z -= apply_percentrate64(z, battle_config.vending_tax, 10000); pc->getzeny(vsd, (int)z, LOG_TYPE_VENDING, sd); for( i = 0; i < count; i++ ) { diff --git a/src/plugins/db2sql.c b/src/plugins/db2sql.c index 5b5d79bac..44e2bac3c 100644 --- a/src/plugins/db2sql.c +++ b/src/plugins/db2sql.c @@ -24,12 +24,12 @@ #include "common/conf.h" #include "common/memmgr.h" #include "common/mmo.h" +#include "common/nullpo.h" #include "common/strlib.h" -#include "common/timer.h" -#include "map/clif.h" +#include "map/battle.h" #include "map/itemdb.h" +#include "map/mob.h" #include "map/map.h" -#include "map/pc.h" #include "common/HPMDataCheck.h" @@ -43,38 +43,102 @@ HPExport struct hplugin_info pinfo = { HPM_VERSION, // HPM Version (don't change, macro is automatically updated) }; +#ifdef RENEWAL +#define DBSUFFIX "_re" +#else // not RENEWAL +#define DBSUFFIX "" +#endif + +/// Conversion state tracking. struct { - FILE *fp; + FILE *fp; ///< Currently open file pointer struct { - char *p; - size_t len; - } buf[4]; - char *db_name; + char *p; ///< Buffer pointer + size_t len; ///< Buffer length + } buf[4]; ///< Output buffer + const char *db_name; ///< Database table name } tosql; -bool torun = false; +/// Whether the item_db converter will automatically run. +bool itemdb2sql_torun = false; +/// Whether the mob_db converter will automatically run. +bool mobdb2sql_torun = false; + +/// Backup of the original item_db parser function pointer. int (*itemdb_readdb_libconfig_sub) (config_setting_t *it, int n, const char *source); +/// Backup of the original mob_db parser function pointer. +int (*mob_read_db_sub) (config_setting_t *it, int n, const char *source); -void hstr(const char *str) { - if( strlen(str) > tosql.buf[3].len ) { +/** + * Normalizes and appends a string to the output buffer. + * + * @param str The string to append. + */ +void hstr(const char *str) +{ + if (strlen(str) > tosql.buf[3].len) { tosql.buf[3].len = tosql.buf[3].len + strlen(str) + 1000; RECREATE(tosql.buf[3].p,char,tosql.buf[3].len); } safestrncpy(tosql.buf[3].p,str,strlen(str)); normalize_name(tosql.buf[3].p,"\t\n "); } -int db2sql(config_setting_t *entry, int n, const char *source) { + +/** + * Prints a SQL file header for the current item_db file. + */ +void db2sql_fileheader(void) +{ + time_t t = time(NULL); + struct tm *lt = localtime(&t); + int year = lt->tm_year+1900; + + fprintf(tosql.fp, + "-- This file is part of Hercules.\n" + "-- http://herc.ws - http://github.com/HerculesWS/Hercules\n" + "--\n" + "-- Copyright (C) 2013-%d Hercules Dev Team\n" + "--\n" + "-- Hercules is free software: you can redistribute it and/or modify\n" + "-- it under the terms of the GNU General Public License as published by\n" + "-- the Free Software Foundation, either version 3 of the License, or\n" + "-- (at your option) any later version.\n" + "--\n" + "-- This program is distributed in the hope that it will be useful,\n" + "-- but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "-- GNU General Public License for more details.\n" + "--\n" + "-- You should have received a copy of the GNU General Public License\n" + "-- along with this program. If not, see <http://www.gnu.org/licenses/>.\n\n" + + "-- NOTE: This file was auto-generated and should never be manually edited,\n" + "-- as it will get overwritten. If you need to modify this file,\n" + "-- please consider modifying the corresponding .conf file inside\n" + "-- the db folder, and then re-run the db2sql plugin.\n" + "\n", year); +} + +/** + * Converts an Item DB entry to SQL. + * + * @see itemdb_readdb_libconfig_sub. + */ +int itemdb2sql_sub(config_setting_t *entry, int n, const char *source) +{ struct item_data *it = NULL; - if( (it = itemdb->exists(itemdb_readdb_libconfig_sub(entry,n,source))) ) { + if ((it = itemdb->exists(itemdb_readdb_libconfig_sub(entry,n,source)))) { char e_name[ITEM_NAME_LENGTH*2+1]; const char *bonus = NULL; char *str; int i32; - unsigned int ui32; + uint32 ui32; config_setting_t *t = NULL; StringBuf buf; + nullpo_ret(entry); + StrBuf->Init(&buf); // id @@ -92,10 +156,10 @@ int db2sql(config_setting_t *entry, int n, const char *source) { StrBuf->Printf(&buf, "'%u',", it->flag.delay_consume?IT_DELAYCONSUME:it->type); // price_buy - StrBuf->Printf(&buf, "'%u',", it->value_buy); + StrBuf->Printf(&buf, "'%d',", it->value_buy); // price_sell - StrBuf->Printf(&buf, "'%u',", it->value_sell); + StrBuf->Printf(&buf, "'%d',", it->value_sell); // weight StrBuf->Printf(&buf, "'%u',", it->weight); @@ -116,15 +180,15 @@ int db2sql(config_setting_t *entry, int n, const char *source) { StrBuf->Printf(&buf, "'%u',", it->slot); // equip_jobs - if( libconfig->setting_lookup_int(entry, "Job", &i32) ) // This is an unsigned value, do not check for >= 0 - ui32 = (unsigned int)i32; + if (libconfig->setting_lookup_int(entry, "Job", &i32)) // This is an unsigned value, do not check for >= 0 + ui32 = (uint32)i32; else ui32 = UINT_MAX; StrBuf->Printf(&buf, "'%u',", ui32); // equip_upper - if( libconfig->setting_lookup_int(entry, "Upper", &i32) && i32 >= 0 ) - ui32 = (unsigned int)i32; + if (libconfig->setting_lookup_int(entry, "Upper", &i32) && i32 >= 0) + ui32 = (uint32)i32; else ui32 = ITEMUPPER_ALL; StrBuf->Printf(&buf, "'%u',", ui32); @@ -142,7 +206,7 @@ int db2sql(config_setting_t *entry, int n, const char *source) { StrBuf->Printf(&buf, "'%u',", it->elv); // equip_level_max - if( (t = libconfig->setting_get_member(entry, "EquipLv")) && config_setting_is_aggregate(t) && libconfig->setting_length(t) >= 2 ) + if ((t = libconfig->setting_get_member(entry, "EquipLv")) && config_setting_is_aggregate(t) && libconfig->setting_length(t) >= 2) StrBuf->Printf(&buf, "'%u',", it->elvmax); else StrBuf->AppendStr(&buf, "NULL,"); @@ -253,36 +317,15 @@ int db2sql(config_setting_t *entry, int n, const char *source) { return it?it->nameid:0; } -void totable(void) { - time_t t; - struct tm *lt; - t = time(NULL); - lt = localtime(&t); - int year = lt->tm_year+1900; - fprintf(tosql.fp, - "-- This file is part of Hercules.\n" - "-- http://herc.ws - http://github.com/HerculesWS/Hercules\n" - "--\n" - "-- Copyright (C) 2013-%d Hercules Dev Team\n" - "--\n" - "-- Hercules is free software: you can redistribute it and/or modify\n" - "-- it under the terms of the GNU General Public License as published by\n" - "-- the Free Software Foundation, either version 3 of the License, or\n" - "-- (at your option) any later version.\n" - "--\n" - "-- This program is distributed in the hope that it will be useful,\n" - "-- but WITHOUT ANY WARRANTY; without even the implied warranty of\n" - "-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" - "-- GNU General Public License for more details.\n" - "--\n" - "-- You should have received a copy of the GNU General Public License\n" - "-- along with this program. If not, see <http://www.gnu.org/licenses/>.\n\n" - "-- NOTE: This file was auto-generated and should never be manually edited,\n" - "-- as it will get overwritten. If you need to modify this file,\n" - "-- please consider modifying the corresponding .conf file inside\n" - "-- the db folder, and then re-run the db2sql plugin.\n" - "\n" +/** + * Prints a SQL table header for the current item_db table. + */ +void itemdb2sql_tableheader(void) +{ + db2sql_fileheader(); + + fprintf(tosql.fp, "--\n" "-- Table structure for table `%s`\n" "--\n" @@ -326,79 +369,405 @@ void totable(void) { " `unequip_script` text,\n" " PRIMARY KEY (`id`)\n" ") ENGINE=MyISAM;\n" - "\n", year, tosql.db_name,tosql.db_name,tosql.db_name); + "\n", tosql.db_name,tosql.db_name,tosql.db_name); } -void do_db2sql(void) { + +/** + * Item DB Conversion. + * + * Converts Item DB and Item DB2 to SQL scripts. + */ +void do_itemdb2sql(void) +{ + int i; + struct convert_db_files { + const char *name; + const char *source; + const char *destination; + } files[] = { + {"item_db", DBPATH"item_db.conf", "sql-files/item_db" DBSUFFIX ".sql"}, + {"item_db2", "item_db2.conf", "sql-files/item_db2.sql"}, + }; + /* link */ itemdb_readdb_libconfig_sub = itemdb->readdb_libconfig_sub; - itemdb->readdb_libconfig_sub = db2sql; - /* */ + itemdb->readdb_libconfig_sub = itemdb2sql_sub; + + memset(&tosql.buf, 0, sizeof(tosql.buf)); + itemdb->clear(false); + + for (i = 0; i < ARRAYLENGTH(files); i++) { + if ((tosql.fp = fopen(files[i].destination, "wt+")) == NULL) { + ShowError("itemdb_tosql: File not found \"%s\".\n", files[i].destination); + return; + } + + tosql.db_name = files[i].name; + itemdb2sql_tableheader(); + + itemdb->readdb_libconfig(files[i].source); - if ((tosql.fp = fopen("sql-files/item_db_re.sql", "wt+")) == NULL) { - ShowError("itemdb_tosql: File not found \"%s\".\n", "sql-files/item_db_re.sql"); - return; + fclose(tosql.fp); } - tosql.db_name = "item_db"; - totable(); + /* unlink */ + itemdb->readdb_libconfig_sub = itemdb_readdb_libconfig_sub; - memset(&tosql.buf, 0, sizeof(tosql.buf) ); + for (i = 0; i < ARRAYLENGTH(tosql.buf); i++) { + if (tosql.buf[i].p) + aFree(tosql.buf[i].p); + } +} - itemdb->clear(false); - itemdb->readdb_libconfig("re/item_db.conf"); +/** + * Converts a Mob DB entry to SQL. + * + * @see mobdb_readdb_libconfig_sub. + */ +int mobdb2sql_sub(config_setting_t *mobt, int n, const char *source) +{ + struct mob_db *md = NULL; + nullpo_ret(mobt); + + if ((md = mob->db(mob_read_db_sub(mobt, n, source))) != mob->dummy) { + char e_name[NAME_LENGTH*2+1]; + StringBuf buf; + int card_idx = 9, i; + + StrBuf->Init(&buf); + + // id + StrBuf->Printf(&buf, "%u,", md->mob_id); - fclose(tosql.fp); + // Sprite + SQL->EscapeString(NULL, e_name, md->sprite); + StrBuf->Printf(&buf, "'%s',", e_name); + + // kName + SQL->EscapeString(NULL, e_name, md->name); + StrBuf->Printf(&buf, "'%s',", e_name); - if ((tosql.fp = fopen("sql-files/item_db.sql", "wt+")) == NULL) { - ShowError("itemdb_tosql: File not found \"%s\".\n", "sql-files/item_db.sql"); - return; + // iName + SQL->EscapeString(NULL, e_name, md->jname); + StrBuf->Printf(&buf, "'%s',", e_name); + + // LV + StrBuf->Printf(&buf, "%u,", md->lv); + + // HP + StrBuf->Printf(&buf, "%u,", md->status.max_hp); + + // SP + StrBuf->Printf(&buf, "%u,", md->status.max_sp); + + // EXP + StrBuf->Printf(&buf, "%u,", md->base_exp); + + // JEXP + StrBuf->Printf(&buf, "%u,", md->job_exp); + + // Range1 + StrBuf->Printf(&buf, "%u,", md->status.rhw.range); + + // ATK1 + StrBuf->Printf(&buf, "%u,", md->status.rhw.atk); + + // ATK2 + StrBuf->Printf(&buf, "%u,", md->status.rhw.atk2); + + // DEF + StrBuf->Printf(&buf, "%u,", md->status.def); + + // MDEF + StrBuf->Printf(&buf, "%u,", md->status.mdef); + + // STR + StrBuf->Printf(&buf, "%u,", md->status.str); + + // AGI + StrBuf->Printf(&buf, "%u,", md->status.agi); + + // VIT + StrBuf->Printf(&buf, "%u,", md->status.vit); + + // INT + StrBuf->Printf(&buf, "%u,", md->status.int_); + + // DEX + StrBuf->Printf(&buf, "%u,", md->status.dex); + + // LUK + StrBuf->Printf(&buf, "%u,", md->status.luk); + + // Range2 + StrBuf->Printf(&buf, "%u,", md->range2); + + // Range3 + StrBuf->Printf(&buf, "%u,", md->range3); + + // Scale + StrBuf->Printf(&buf, "%u,", md->status.size); + + // Race + StrBuf->Printf(&buf, "%u,", md->status.race); + + // Element + StrBuf->Printf(&buf, "%u,", md->status.def_ele + 20 * md->status.ele_lv); + + // Mode + StrBuf->Printf(&buf, "0x%X,", md->status.mode); + + // Speed + StrBuf->Printf(&buf, "%u,", md->status.speed); + + // aDelay + StrBuf->Printf(&buf, "%u,", md->status.adelay); + + // aMotion + StrBuf->Printf(&buf, "%u,", md->status.amotion); + + // dMotion + StrBuf->Printf(&buf, "%u,", md->status.dmotion); + + // MEXP + StrBuf->Printf(&buf, "%u,", md->mexp); + + for (i = 0; i < 3; i++) { + // MVP{i}id + StrBuf->Printf(&buf, "%u,", md->mvpitem[i].nameid); + // MVP{i}per + StrBuf->Printf(&buf, "%u,", md->mvpitem[i].p); + } + + // Scan for cards + for (i = 0; i < 10; i++) { + struct item_data *it = NULL; + if (md->dropitem[i].nameid != 0 && (it = itemdb->exists(md->dropitem[i].nameid)) != NULL && it->type == IT_CARD) + card_idx = i; + } + + for (i = 0; i < 10; i++) { + if (card_idx == i) + continue; + // Drop{i}id + StrBuf->Printf(&buf, "%u,", md->dropitem[i].nameid); + // Drop{i}per + StrBuf->Printf(&buf, "%u,", md->dropitem[i].p); + } + + // DropCardid + StrBuf->Printf(&buf, "%u,", md->dropitem[card_idx].nameid); + // DropCardper + StrBuf->Printf(&buf, "%u", md->dropitem[card_idx].p); + + fprintf(tosql.fp, "REPLACE INTO `%s` VALUES (%s);\n", tosql.db_name, StrBuf->Value(&buf)); + + StrBuf->Destroy(&buf); } - tosql.db_name = "item_db"; - totable(); + return md ? md->mob_id : 0; +} - itemdb->clear(false); - itemdb->readdb_libconfig("pre-re/item_db.conf"); +/** + * Prints a SQL table header for the current mob_db table. + */ +void mobdb2sql_tableheader(void) +{ + db2sql_fileheader(); + + fprintf(tosql.fp, + "--\n" + "-- Table structure for table `%s`\n" + "--\n" + "\n" + "DROP TABLE IF EXISTS `%s`;\n" + "CREATE TABLE `%s` (\n" + " `ID` MEDIUMINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Sprite` TEXT NOT NULL,\n" + " `kName` TEXT NOT NULL,\n" + " `iName` TEXT NOT NULL,\n" + " `LV` TINYINT(6) UNSIGNED NOT NULL DEFAULT '0',\n" + " `HP` INT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `SP` MEDIUMINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `EXP` MEDIUMINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `JEXP` MEDIUMINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Range1` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0',\n" + " `ATK1` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',\n" + " `ATK2` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',\n" + " `DEF` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',\n" + " `MDEF` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',\n" + " `STR` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',\n" + " `AGI` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',\n" + " `VIT` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',\n" + " `INT` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',\n" + " `DEX` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',\n" + " `LUK` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Range2` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Range3` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Scale` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Race` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Element` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Mode` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Speed` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',\n" + " `aDelay` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',\n" + " `aMotion` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',\n" + " `dMotion` SMALLINT(6) UNSIGNED NOT NULL DEFAULT '0',\n" + " `MEXP` MEDIUMINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `MVP1id` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `MVP1per` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `MVP2id` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `MVP2per` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `MVP3id` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `MVP3per` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Drop1id` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Drop1per` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Drop2id` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Drop2per` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Drop3id` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Drop3per` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Drop4id` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Drop4per` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Drop5id` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Drop5per` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Drop6id` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Drop6per` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Drop7id` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Drop7per` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Drop8id` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Drop8per` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Drop9id` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `Drop9per` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `DropCardid` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " `DropCardper` SMALLINT(9) UNSIGNED NOT NULL DEFAULT '0',\n" + " PRIMARY KEY (`ID`)\n" + ") ENGINE=MyISAM;\n" + "\n", tosql.db_name, tosql.db_name, tosql.db_name); +} - fclose(tosql.fp); +/** + * Mob DB Conversion. + * + * Converts Mob DB and Mob DB2 to SQL scripts. + */ +void do_mobdb2sql(void) +{ + int i; + struct convert_db_files { + const char *name; + const char *source; + const char *destination; + } files[] = { + {"mob_db", DBPATH"mob_db.conf", "sql-files/mob_db" DBSUFFIX ".sql"}, + {"mob_db2", "mob_db2.conf", "sql-files/mob_db2.sql"}, + }; + + /* link */ + mob_read_db_sub = mob->read_db_sub; + mob->read_db_sub = mobdb2sql_sub; - if ((tosql.fp = fopen("sql-files/item_db2.sql", "wt+")) == NULL) { - ShowError("itemdb_tosql: File not found \"%s\".\n", "sql-files/item_db2.sql"); - return; + if (map->minimal) { + // Set up modifiers + battle->config_set_defaults(); } - tosql.db_name = "item_db2"; - totable(); + memset(&tosql.buf, 0, sizeof(tosql.buf)); + for (i = 0; i < ARRAYLENGTH(files); i++) { + if ((tosql.fp = fopen(files[i].destination, "wt+")) == NULL) { + ShowError("mobdb_tosql: File not found \"%s\".\n", files[i].destination); + return; + } - itemdb->clear(false); - itemdb->readdb_libconfig("item_db2.conf"); + tosql.db_name = files[i].name; + mobdb2sql_tableheader(); - fclose(tosql.fp); + mob->read_libconfig(files[i].source, false); + + fclose(tosql.fp); + } /* unlink */ - itemdb->readdb_libconfig_sub = itemdb_readdb_libconfig_sub; + mob->read_db_sub = mob_read_db_sub; + + for (i = 0; i < ARRAYLENGTH(tosql.buf); i++) { + if (tosql.buf[i].p) + aFree(tosql.buf[i].p); + } +} + +/** + * Console command db2sql. + */ +CPCMD(db2sql) +{ + do_itemdb2sql(); + do_mobdb2sql(); +} - if( tosql.buf[0].p ) aFree(tosql.buf[0].p); - if( tosql.buf[1].p ) aFree(tosql.buf[1].p); - if( tosql.buf[2].p ) aFree(tosql.buf[2].p); - if( tosql.buf[3].p ) aFree(tosql.buf[3].p); +/** + * Console command itemdb2sql. + */ +CPCMD(itemdb2sql) +{ + do_itemdb2sql(); } -CPCMD(db2sql) { - do_db2sql(); + +/** + * Console command mobdb2sql. + */ +CPCMD(mobdb2sql) +{ + do_mobdb2sql(); } + +/** + * Command line argument handler for --db2sql + */ CMDLINEARG(db2sql) { - map->minimal = torun = true; + map->minimal = true; + itemdb2sql_torun = true; + mobdb2sql_torun = true; return true; } -HPExport void server_preinit(void) { - addArg("--db2sql",false,db2sql,NULL); +/** + * Command line argument handler for --itemdb2sql + */ +CMDLINEARG(itemdb2sql) +{ + map->minimal = true; + itemdb2sql_torun = true; + return true; } -HPExport void plugin_init(void) { - addCPCommand("server:tools:db2sql",db2sql); + +/** + * Command line argument handler for --mobdb2sql + */ +CMDLINEARG(mobdb2sql) +{ + map->minimal = true; + mobdb2sql_torun = true; + return true; +} + +HPExport void server_preinit(void) +{ + addArg("--db2sql", false, db2sql, NULL); + addArg("--itemdb2sql", false, itemdb2sql, NULL); + addArg("--mobdb2sql", false, mobdb2sql, NULL); +} + +HPExport void plugin_init(void) +{ + addCPCommand("server:tools:db2sql", db2sql); + addCPCommand("server:tools:itemdb2sql", itemdb2sql); + addCPCommand("server:tools:mobdb2sql", mobdb2sql); } -HPExport void server_online(void) { - if( torun ) - do_db2sql(); + +HPExport void server_online(void) +{ + if (itemdb2sql_torun) + do_itemdb2sql(); + if (mobdb2sql_torun) + do_mobdb2sql(); } |