diff options
Diffstat (limited to 'src/map/skill.c')
-rw-r--r-- | src/map/skill.c | 4868 |
1 files changed, 3621 insertions, 1247 deletions
diff --git a/src/map/skill.c b/src/map/skill.c index e3fa6b0a1..765e3b6bf 100644 --- a/src/map/skill.c +++ b/src/map/skill.c @@ -176,14 +176,26 @@ static const char *skill_get_desc(int skill_id) // Skill DB -static int skill_get_hit(int skill_id) +/** + * Gets a skill's hit type by its ID and level. (See enum battle_dmg_type.) + * + * @param skill_id The skill's ID. + * @param skill_lv The skill's level. + * @return The skill's hit type corresponding to the passed level. Defaults to BDT_NORMAL (0) in case of error. + * + **/ +static int skill_get_hit(int skill_id, int skill_lv) { - int idx; if (skill_id == 0) - return 0; - idx = skill->get_index(skill_id); - Assert_ret(idx != 0); - return skill->dbs->db[idx].hit; + return BDT_NORMAL; + + Assert_retr(BDT_NORMAL, skill_lv > 0); + + int idx = skill->get_index(skill_id); + + Assert_retr(BDT_NORMAL, idx != 0); + + return skill->dbs->db[idx].hit[skill_get_lvl_idx(skill_lv)]; } static int skill_get_inf(int skill_id) @@ -320,14 +332,26 @@ static int skill_get_sp_rate(int skill_id, int skill_lv) return skill->dbs->db[idx].sp_rate[skill_get_lvl_idx(skill_lv)]; } -static int skill_get_state(int skill_id) +/** + * Gets a skill's required state by its ID and level. + * + * @param skill_id The skill's ID. + * @param skill_lv The skill's level. + * @return The skill's required state corresponding to the passed level. Defaults to ST_NONE (0) in case of error. + * + **/ +static int skill_get_state(int skill_id, int skill_lv) { - int idx; if (skill_id == 0) return ST_NONE; - idx = skill->get_index(skill_id); + + Assert_retr(ST_NONE, skill_lv > 0); + + int idx = skill->get_index(skill_id); + Assert_retr(ST_NONE, idx != 0); - return skill->dbs->db[idx].state; + + return skill->dbs->db[idx].state[skill_get_lvl_idx(skill_lv)]; } static int skill_get_spiritball(int skill_id, int skill_lv) @@ -345,26 +369,140 @@ static int skill_get_spiritball(int skill_id, int skill_lv) return skill->dbs->db[idx].spiritball[skill_get_lvl_idx(skill_lv)]; } +/** + * Gets a skill's required item's ID by the skill's ID and the item's index. + * + * @param skill_id The skill's ID. + * @param item_idx The item's index. + * @return The skill's required item's ID corresponding to the passed index. Defaults to 0 in case of error. + * + **/ static int skill_get_itemid(int skill_id, int item_idx) { - int idx; if (skill_id == 0) return 0; - idx = skill->get_index(skill_id); + + Assert_ret(item_idx >= 0 && item_idx < MAX_SKILL_ITEM_REQUIRE); + + int idx = skill->get_index(skill_id); + Assert_ret(idx != 0); + + return skill->dbs->db[idx].req_items.item[item_idx].id; +} + +/** + * Gets a skill's required item's amount by the skill's ID and level and the item's index. + * + * @param skill_id The skill's ID. + * @param item_idx The item's index. + * @param skill_lv The skill's level. + * @return The skill's required item's amount corresponding to the passed index and level. Defaults to 0 in case of error. + * + **/ +static int skill_get_itemqty(int skill_id, int item_idx, int skill_lv) +{ + if (skill_id == 0) + return 0; + Assert_ret(item_idx >= 0 && item_idx < MAX_SKILL_ITEM_REQUIRE); - return skill->dbs->db[idx].itemid[item_idx]; + Assert_ret(skill_lv > 0); + + int idx = skill->get_index(skill_id); + + Assert_ret(idx != 0); + + return skill->dbs->db[idx].req_items.item[item_idx].amount[skill_get_lvl_idx(skill_lv)]; } -static int skill_get_itemqty(int skill_id, int item_idx) +/** + * Gets a skill's required items any-flag by the skill's ID and level. + * + * @param skill_id The skill's ID. + * @param skill_lv The skill's level. + * @return The skill's required items any-flag corresponding to the passed level. Defaults to false in case of error. + * + **/ +static bool skill_get_item_any_flag(int skill_id, int skill_lv) +{ + if (skill_id == 0) + return false; + + Assert_retr(false, skill_lv > 0); + + int idx = skill->get_index(skill_id); + + Assert_retr(false, idx != 0); + + return skill->dbs->db[idx].req_items.any[skill_get_lvl_idx(skill_lv)]; +} + +/** + * Gets a skill's required equipment's ID by the skill's ID and the equipment item's index. + * + * @param skill_id The skill's ID. + * @param item_idx The equipment item's index. + * @return The skill's required equipment's ID corresponding to the passed index. Defaults to 0 in case of error. + * + **/ +static int skill_get_equip_id(int skill_id, int item_idx) { - int idx; if (skill_id == 0) return 0; - idx = skill->get_index(skill_id); + + Assert_ret(item_idx >= 0 && item_idx < MAX_SKILL_ITEM_REQUIRE); + + int idx = skill->get_index(skill_id); + Assert_ret(idx != 0); + + return skill->dbs->db[idx].req_equip.item[item_idx].id; +} + +/** + * Gets a skill's required equipment's amount by the skill's ID and level and the equipment item's index. + * + * @param skill_id The skill's ID. + * @param item_idx The equipment item's index. + * @param skill_lv The skill's level. + * @return The skill's required equipment item's amount corresponding to the passed index and level. Defaults to 0 in case of error. + * + **/ +static int skill_get_equip_amount(int skill_id, int item_idx, int skill_lv) +{ + if (skill_id == 0) + return 0; + Assert_ret(item_idx >= 0 && item_idx < MAX_SKILL_ITEM_REQUIRE); - return skill->dbs->db[idx].amount[item_idx]; + Assert_ret(skill_lv > 0); + + int idx = skill->get_index(skill_id); + + Assert_ret(idx != 0); + + return skill->dbs->db[idx].req_equip.item[item_idx].amount[skill_get_lvl_idx(skill_lv)]; +} + +/** + * Gets a skill's required equipment any-flag by the skill's ID and level. + * + * @param skill_id The skill's ID. + * @param skill_lv The skill's level. + * @return The skill's required equipment's any-flag corresponding to the passed level. Defaults to false in case of error. + * + **/ +static bool skill_get_equip_any_flag(int skill_id, int skill_lv) +{ + if (skill_id == 0) + return false; + + Assert_retr(false, skill_lv > 0); + + int idx = skill->get_index(skill_id); + + Assert_retr(false, idx != 0); + + return skill->dbs->db[idx].req_equip.any[skill_get_lvl_idx(skill_lv)]; } static int skill_get_zeny(int skill_id, int skill_lv) @@ -472,14 +610,26 @@ static int skill_get_time2(int skill_id, int skill_lv) return skill->dbs->db[idx].upkeep_time2[skill_get_lvl_idx(skill_lv)]; } -static int skill_get_castdef(int skill_id) +/** + * Gets a skill's cast defence rate by its ID and level. + * + * @param skill_id The skill's ID. + * @param skill_lv The skill's level. + * @return The skill's cast defence rate corresponding to the passed level. Defaults to 0 in case of error. + * + **/ +static int skill_get_castdef(int skill_id, int skill_lv) { - int idx; if (skill_id == 0) return 0; - idx = skill->get_index(skill_id); + + Assert_ret(skill_lv > 0); + + int idx = skill->get_index(skill_id); + Assert_ret(idx != 0); - return skill->dbs->db[idx].cast_def_rate; + + return skill->dbs->db[idx].cast_def_rate[skill_get_lvl_idx(skill_lv)]; } static int skill_get_weapontype(int skill_id) @@ -527,14 +677,26 @@ static int skill_get_inf2(int skill_id) return skill->dbs->db[idx].inf2; } -static int skill_get_castcancel(int skill_id) +/** + * Gets a skill's cast interruptibility by its ID and level. + * + * @param skill_id The skill's ID. + * @param skill_lv The skill's level. + * @return The skill's cast interruptibility corresponding to the passed level. Defaults to 0 in case of error. + * + **/ +static int skill_get_castcancel(int skill_id, int skill_lv) { - int idx; if (skill_id == 0) return 0; - idx = skill->get_index(skill_id); + + Assert_ret(skill_lv > 0); + + int idx = skill->get_index(skill_id); + Assert_ret(idx != 0); - return skill->dbs->db[idx].castcancel; + + return skill->dbs->db[idx].castcancel[skill_get_lvl_idx(skill_lv)]; } static int skill_get_maxcount(int skill_id, int skill_lv) @@ -582,6 +744,28 @@ static int skill_get_mhp(int skill_id, int skill_lv) return skill->dbs->db[idx].mhp[skill_get_lvl_idx(skill_lv)]; } +/** + * Gets a skill's maximum SP trigger by its ID and level. + * + * @param skill_id The skill's ID. + * @param skill_lv The skill's level. + * @return The skill's maximum SP trigger corresponding to the passed level. Defaults to 0 in case of error. + * + **/ +static int skill_get_msp(int skill_id, int skill_lv) +{ + if (skill_id == 0) + return 0; + + Assert_ret(skill_lv > 0); + + int idx = skill->get_index(skill_id); + + Assert_ret(idx != 0); + + return skill->dbs->db[idx].msp[skill_get_lvl_idx(skill_lv)]; +} + static int skill_get_castnodex(int skill_id, int skill_lv) { int idx; @@ -612,35 +796,72 @@ static int skill_get_delaynodex(int skill_id, int skill_lv) return skill->dbs->db[idx].delaynodex[skill_get_lvl_idx(skill_lv)]; } -static int skill_get_type(int skill_id) +/** + * Gets a skill's attack type by its ID and level. + * + * @param skill_id The skill's ID. + * @param skill_lv The skill's level. + * @return The skill's attack type corresponding to the passed level. Defaults to BF_NONE (0) in case of error. + * + **/ +static int skill_get_type(int skill_id, int skill_lv) { - int idx; if (skill_id == 0) return BF_NONE; - idx = skill->get_index(skill_id); + + Assert_retr(BF_NONE, skill_lv > 0); + + int idx = skill->get_index(skill_id); + Assert_retr(BF_NONE, idx != 0); - return skill->dbs->db[idx].skill_type; + + return skill->dbs->db[idx].skill_type[skill_get_lvl_idx(skill_lv)]; } -static int skill_get_unit_id(int skill_id, int flag) +/** + * Gets a skill's unit ID by its ID and level. + * + * @param skill_id The skill's ID. + * @param skill_lv The skill's level. + * @param flag + * @return The skill's unit ID corresponding to the passed level. Defaults to 0 in case of error. + * + **/ +static int skill_get_unit_id(int skill_id, int skill_lv, int flag) { - int idx; if (skill_id == 0) return 0; - idx = skill->get_index(skill_id); + + Assert_ret(skill_lv > 0); + Assert_ret(flag >= 0 && flag < ARRAYLENGTH(skill->dbs->db[0].unit_id[0])); + + int idx = skill->get_index(skill_id); + Assert_ret(idx != 0); - Assert_ret(flag >= 0 && flag < ARRAYLENGTH(skill->dbs->db[0].unit_id)); - return skill->dbs->db[idx].unit_id[flag]; + + return skill->dbs->db[idx].unit_id[skill_get_lvl_idx(skill_lv)][flag]; } -static int skill_get_unit_interval(int skill_id) +/** + * Gets a skill's unit interval by its ID and level. + * + * @param skill_id The skill's ID. + * @param skill_lv The skill's level. + * @return The skill's unit interval corresponding to the passed level. Defaults to 0 in case of error. + * + **/ +static int skill_get_unit_interval(int skill_id, int skill_lv) { - int idx; if (skill_id == 0) return 0; - idx = skill->get_index(skill_id); + + Assert_ret(skill_lv > 0); + + int idx = skill->get_index(skill_id); + Assert_ret(idx != 0); - return skill->dbs->db[idx].unit_interval; + + return skill->dbs->db[idx].unit_interval[skill_get_lvl_idx(skill_lv)]; } static int skill_get_unit_range(int skill_id, int skill_lv) @@ -658,24 +879,48 @@ static int skill_get_unit_range(int skill_id, int skill_lv) return skill->dbs->db[idx].unit_range[skill_get_lvl_idx(skill_lv)]; } -static int skill_get_unit_target(int skill_id) +/** + * Gets a skill's unit target by its ID and level. + * + * @param skill_id The skill's ID. + * @param skill_lv The skill's level. + * @return The skill's unit target corresponding to the passed level. Defaults to BCT_NOONE (0) in case of error. + * + **/ +static int skill_get_unit_target(int skill_id, int skill_lv) { - int idx; if (skill_id == 0) return BCT_NOONE; - idx = skill->get_index(skill_id); + + Assert_retr(BCT_NOONE, skill_lv > 0); + + int idx = skill->get_index(skill_id); + Assert_retr(BCT_NOONE, idx != 0); - return skill->dbs->db[idx].unit_target & BCT_ALL; + + return (skill->dbs->db[idx].unit_target[skill_get_lvl_idx(skill_lv)] & BCT_ALL); } -static int skill_get_unit_bl_target(int skill_id) +/** + * Gets a skill's unit target as bl type by its ID and level. + * + * @param skill_id The skill's ID. + * @param skill_lv The skill's level. + * @return The skill's unit target as bl type corresponding to the passed level. Defaults to BL_NUL (0) in case of error. + * + **/ +static int skill_get_unit_bl_target(int skill_id, int skill_lv) { - int idx; if (skill_id == 0) return BL_NUL; - idx = skill->get_index(skill_id); - Assert_retr(BL_NUL, idx != 0); - return skill->dbs->db[idx].unit_target & BL_ALL; + + Assert_retr(BCT_NOONE, skill_lv > 0); + + int idx = skill->get_index(skill_id); + + Assert_retr(BCT_NOONE, idx != 0); + + return (skill->dbs->db[idx].unit_target[skill_get_lvl_idx(skill_lv)] & BL_ALL); } static int skill_get_unit_flag(int skill_id) @@ -1029,14 +1274,14 @@ static int skillnotok(uint16 skill_id, struct map_session_data *sd) if (map->getcell(sd->bl.m, &sd->bl, sd->bl.x, sd->bl.y, CELL_CHKNOSKILL)) return 1; // block usage on 'noskill' cells [Wolfie] - if (skill_id == AL_TELEPORT && sd->autocast.type == AUTOCAST_ITEM && sd->autocast.skill_lv > 2) + if (skill_id == AL_TELEPORT && sd->auto_cast_current.type == AUTOCAST_ITEM && sd->auto_cast_current.skill_lv > 2) return 0; // Teleport level 3 and higher bypasses this check if cast by itemskill() script commands. // Epoque: // This code will compare the player's attack motion value which is influenced by ASPD before // allowing a skill to be cast. This is to prevent no-delay ACT files from spamming skills such as // AC_DOUBLE which do not have a skill delay and are not regarded in terms of attack motion. - if (sd->autocast.type == AUTOCAST_NONE && sd->canskill_tick != 0 && + if (sd->auto_cast_current.type == AUTOCAST_NONE && sd->canskill_tick != 0 && DIFF_TICK(timer->gettick(), sd->canskill_tick) < (sd->battle_status.amotion * (battle_config.skill_amotion_leniency) / 100) ) {// attempted to cast a skill before the attack motion has finished return 1; @@ -1051,7 +1296,7 @@ static int skillnotok(uint16 skill_id, struct map_session_data *sd) * It has been confirmed on a official server (thanks to Yommy) that item-cast skills bypass all the restrictions below * Also, without this check, an exploit where an item casting + healing (or any other kind buff) isn't deleted after used on a restricted map **/ - if (sd->autocast.type == AUTOCAST_ITEM) + if (sd->auto_cast_current.type == AUTOCAST_ITEM) return 0; if( sd->sc.data[SC_ALL_RIDING] ) @@ -1211,14 +1456,16 @@ static void skill_validate_autocast_data(struct map_session_data *sd, int skill_ // Determine if called by clif_parse_UseSkillMap(). bool use_skill_map = (skill_lv == 0 && (skill_id == AL_WARP || skill_id == AL_TELEPORT)); - if (sd->autocast.type == AUTOCAST_NONE) + struct autocast_data *auto_cast = &sd->auto_cast_current; + + if (auto_cast->type == AUTOCAST_NONE) pc->autocast_clear(sd); // No auto-cast type set. Preventively unset all auto-cast related data. - else if (sd->autocast.type == AUTOCAST_TEMP) + else if (auto_cast->type == AUTOCAST_TEMP) pc->autocast_clear(sd); // AUTOCAST_TEMP should have been unset straight after usage. - else if (sd->autocast.skill_id == 0 || skill_id == 0 || sd->autocast.skill_id != skill_id) - pc->autocast_clear(sd); // Implausible skill ID. - else if (sd->autocast.skill_lv == 0 || (!use_skill_map && (skill_lv == 0 || sd->autocast.skill_lv != skill_lv))) - pc->autocast_clear(sd); // Implausible skill level. + else if (auto_cast->skill_id == 0 || skill_id == 0 || auto_cast->skill_id != skill_id) + pc->autocast_remove(sd, auto_cast->type, auto_cast->skill_id, auto_cast->skill_lv); // Implausible skill ID. + else if (auto_cast->skill_lv == 0 || (!use_skill_map && (skill_lv == 0 || auto_cast->skill_lv != skill_lv))) + pc->autocast_remove(sd, auto_cast->type, auto_cast->skill_id, auto_cast->skill_lv); // Implausible skill level. } static struct s_skill_unit_layout *skill_get_unit_layout(uint16 skill_id, uint16 skill_lv, struct block_list *src, int x, int y) @@ -2109,9 +2356,9 @@ static int skill_additional_effect(struct block_list *src, struct block_list *bl temp = (sd->autospell[i].id > 0) ? sd->autospell[i].id : -sd->autospell[i].id; - sd->autocast.type = AUTOCAST_TEMP; + sd->auto_cast_current.type = AUTOCAST_TEMP; notok = skill->not_ok(temp, sd); - sd->autocast.type = AUTOCAST_NONE; + sd->auto_cast_current.type = AUTOCAST_NONE; if ( notok ) continue; @@ -2162,11 +2409,11 @@ static int skill_additional_effect(struct block_list *src, struct block_list *bl else if (temp == PF_SPIDERWEB) //Special case, due to its nature of coding. type = CAST_GROUND; - sd->autocast.type = AUTOCAST_TEMP; + sd->auto_cast_current.type = AUTOCAST_TEMP; skill->consume_requirement(sd,temp,auto_skill_lv,1); - skill->toggle_magicpower(src, temp); + skill->toggle_magicpower(src, temp, auto_skill_lv); skill->castend_type(type, src, tbl, temp, auto_skill_lv, tick, 0); - sd->autocast.type = AUTOCAST_NONE; + sd->auto_cast_current.type = AUTOCAST_NONE; //Set canact delay. [Skotlex] ud = unit->bl2ud(src); @@ -2237,7 +2484,7 @@ static int skill_onskillusage(struct map_session_data *sd, struct block_list *bl return 0; // Preserve auto-cast type if bAutoSpellOnSkill was triggered by a skill which was cast by Abracadabra, Improvised Song or an item. - enum autocast_type ac_type = sd->autocast.type; + enum autocast_type ac_type = sd->auto_cast_current.type; for( i = 0; i < ARRAYLENGTH(sd->autospell3) && sd->autospell3[i].flag; i++ ) { if( sd->autospell3[i].flag != skill_id ) @@ -2248,9 +2495,9 @@ static int skill_onskillusage(struct map_session_data *sd, struct block_list *bl temp = (sd->autospell3[i].id > 0) ? sd->autospell3[i].id : -sd->autospell3[i].id; - sd->autocast.type = AUTOCAST_TEMP; + sd->auto_cast_current.type = AUTOCAST_TEMP; notok = skill->not_ok(temp, sd); - sd->autocast.type = AUTOCAST_NONE; + sd->auto_cast_current.type = AUTOCAST_NONE; if ( notok ) continue; @@ -2297,14 +2544,14 @@ static int skill_onskillusage(struct map_session_data *sd, struct block_list *bl continue; sd->autospell3[i].lock = true; - sd->autocast.type = AUTOCAST_TEMP; + sd->auto_cast_current.type = AUTOCAST_TEMP; skill->consume_requirement(sd,temp,skill_lv,1); skill->castend_type(type, &sd->bl, tbl, temp, skill_lv, tick, 0); - sd->autocast.type = AUTOCAST_NONE; + sd->auto_cast_current.type = AUTOCAST_NONE; sd->autospell3[i].lock = false; } - sd->autocast.type = ac_type; + sd->auto_cast_current.type = ac_type; if (sd->autobonus3[0].rate) { for( i = 0; i < ARRAYLENGTH(sd->autobonus3); i++ ) { @@ -2453,7 +2700,7 @@ static int skill_counter_additional_effect(struct block_list *src, struct block_ int i, auto_skill_id, auto_skill_lv, type, notok; // Preserve auto-cast type if bAutoSpellWhenHit was triggered during cast of a skill which was cast by Abracadabra, Improvised Song or an item. - enum autocast_type ac_type = dstsd->autocast.type; + enum autocast_type ac_type = dstsd->auto_cast_current.type; for (i = 0; i < ARRAYLENGTH(dstsd->autospell2) && dstsd->autospell2[i].id; i++) { @@ -2470,9 +2717,9 @@ static int skill_counter_additional_effect(struct block_list *src, struct block_ if (attack_type&BF_LONG) rate>>=1; - dstsd->autocast.type = AUTOCAST_TEMP; + dstsd->auto_cast_current.type = AUTOCAST_TEMP; notok = skill->not_ok(auto_skill_id, dstsd); - dstsd->autocast.type = AUTOCAST_NONE; + dstsd->auto_cast_current.type = AUTOCAST_NONE; if ( notok ) continue; @@ -2513,10 +2760,10 @@ static int skill_counter_additional_effect(struct block_list *src, struct block_ if( !battle->check_range(src, tbl, skill->get_range2(src, auto_skill_id,auto_skill_lv) + (auto_skill_id == RG_CLOSECONFINE?0:1)) && battle_config.autospell_check_range ) continue; - dstsd->autocast.type = AUTOCAST_TEMP; + dstsd->auto_cast_current.type = AUTOCAST_TEMP; skill->consume_requirement(dstsd,auto_skill_id,auto_skill_lv,1); skill->castend_type(type, bl, tbl, auto_skill_id, auto_skill_lv, tick, 0); - dstsd->autocast.type = AUTOCAST_NONE; + dstsd->auto_cast_current.type = AUTOCAST_NONE; // Set canact delay. [Skotlex] ud = unit->bl2ud(bl); @@ -2530,7 +2777,7 @@ static int skill_counter_additional_effect(struct block_list *src, struct block_ } } - dstsd->autocast.type = ac_type; + dstsd->auto_cast_current.type = ac_type; } //Autobonus when attacked @@ -2991,7 +3238,7 @@ static int skill_attack(int attack_type, struct block_list *src, struct block_li } //Skill hit type - type=(skill_id==0)?BDT_SPLASH:skill->get_hit(skill_id); + type = (skill_id == 0) ? BDT_SPLASH : skill->get_hit(skill_id, skill_lv); if(damage < dmg.div_ //Only skills that knockback even when they miss. [Skotlex] @@ -3771,9 +4018,9 @@ static int skill_check_condition_mercenary(struct block_list *bl, int skill_id, { struct status_data *st; struct map_session_data *sd = NULL; - int i, hp, sp, hp_rate, sp_rate, state, mhp; + int hp, sp, hp_rate, sp_rate, state; int idx; - int itemid[MAX_SKILL_ITEM_REQUIRE], amount[MAX_SKILL_ITEM_REQUIRE], index[MAX_SKILL_ITEM_REQUIRE]; + int itemid[MAX_SKILL_ITEM_REQUIRE], amount[MAX_SKILL_ITEM_REQUIRE]; if( lv < 1 || lv > MAX_SKILL_LEVEL ) return 0; @@ -3789,17 +4036,16 @@ static int skill_check_condition_mercenary(struct block_list *bl, int skill_id, return 0; // Requirements - for (i = 0; i < MAX_SKILL_ITEM_REQUIRE; i++) { - itemid[i] = skill->dbs->db[idx].itemid[i]; - amount[i] = skill->dbs->db[idx].amount[i]; + for (int i = 0; i < MAX_SKILL_ITEM_REQUIRE; i++) { + itemid[i] = skill->get_itemid(skill_id, i); + amount[i] = skill->get_itemqty(skill_id, i, lv); } hp = skill->dbs->db[idx].hp[lv-1]; sp = skill->dbs->db[idx].sp[lv-1]; hp_rate = skill->dbs->db[idx].hp_rate[lv-1]; sp_rate = skill->dbs->db[idx].sp_rate[lv-1]; - state = skill->dbs->db[idx].state; - if( (mhp = skill->dbs->db[idx].mhp[lv-1]) > 0 ) - hp += (st->max_hp * mhp) / 100; + state = skill->dbs->db[idx].state[lv - 1]; + if( hp_rate > 0 ) hp += (st->hp * hp_rate) / 100; else @@ -3832,6 +4078,20 @@ static int skill_check_condition_mercenary(struct block_list *bl, int skill_id, clif->skill_fail(sd, skill_id, USESKILL_FAIL_SP_INSUFFICIENT, 0, 0); return 0; } + + int mhp = skill->get_mhp(skill_id, lv); + + if (mhp > 0 && get_percentage(st->hp, st->max_hp) > mhp) { + clif->skill_fail(sd, skill_id, USESKILL_FAIL_HP_INSUFFICIENT, 0, 0); + return 0; + } + + int msp = skill->get_msp(skill_id, lv); + + if (msp > 0 && get_percentage(st->sp, st->max_sp) > msp) { + clif->skill_fail(sd, skill_id, USESKILL_FAIL_SP_INSUFFICIENT, 0, 0); + return 0; + } } if( !type ) @@ -3846,23 +4106,32 @@ static int skill_check_condition_mercenary(struct block_list *bl, int skill_id, if( !(type&1) ) return 1; - // Check item existences - for (i = 0; i < ARRAYLENGTH(itemid); i++) { - index[i] = INDEX_NOT_FOUND; - if (itemid[i] < 1) continue; // No item - index[i] = pc->search_inventory(sd, itemid[i]); - if (index[i] == INDEX_NOT_FOUND || sd->status.inventory[index[i]].amount < amount[i]) { - clif->skill_fail(sd, skill_id, USESKILL_FAIL_NEED_ITEM, amount[i], itemid[i]); - return 0; - } - } + bool items_required = skill->items_required(sd, skill_id, lv); + + if (items_required && skill->check_condition_required_items(sd, skill_id, lv) != 0) + return 0; + + int any_item_index = INDEX_NOT_FOUND; + + if (items_required) + any_item_index = skill->get_any_item_index(sd, skill_id, lv); + + for (int i = 0; i < MAX_SKILL_ITEM_REQUIRE && items_required; i++) { + if (itemid[i] == 0) + continue; - // Consume items - for (i = 0; i < ARRAYLENGTH(itemid); i++) { - if (index[i] != INDEX_NOT_FOUND) - pc->delitem(sd, index[i], amount[i], 0, DELITEM_SKILLUSE, LOG_TYPE_CONSUME); + if (any_item_index != INDEX_NOT_FOUND && any_item_index != i) + continue; + + int inventory_index = pc->search_inventory(sd, itemid[i]); + + if (inventory_index != INDEX_NOT_FOUND) + pc->delitem(sd, inventory_index, amount[i], 0, DELITEM_SKILLUSE, LOG_TYPE_CONSUME); } + if (skill->check_condition_required_equip(sd, skill_id, lv) != 0) + return 0; + if( type&2 ) return 1; @@ -3950,7 +4219,7 @@ static int skill_timerskill(int tid, int64 tick, int id, intptr_t data) clif->skill_nodamage(src,target,skl->skill_id,skl->skill_lv,1); break; case WZ_WATERBALL: - skill->toggle_magicpower(src, skl->skill_id); // only the first hit will be amplify + skill->toggle_magicpower(src, skl->skill_id, skl->skill_lv); // only the first hit will be amplify if (!status->isdead(target)) skill->attack(BF_MAGIC,src,src,target,skl->skill_id,skl->skill_lv,tick,skl->flag); if (skl->type>1 && !status->isdead(target) && !status->isdead(src)) { @@ -3970,7 +4239,7 @@ static int skill_timerskill(int tid, int64 tick, int id, intptr_t data) **/ case WL_CHAINLIGHTNING_ATK: skill->attack(BF_MAGIC, src, src, target, skl->skill_id, skl->skill_lv, tick, (9-skl->type)); // Hit a Lightning on the current Target - skill->toggle_magicpower(src, skl->skill_id); // only the first hit will be amplify + skill->toggle_magicpower(src, skl->skill_id, skl->skill_lv); // only the first hit will be amplify if (skl->type < (4 + skl->skill_lv - 1) && skl->x < 3) { // Remaining Chains Hit @@ -3990,7 +4259,7 @@ static int skill_timerskill(int tid, int64 tick, int id, intptr_t data) case WL_TETRAVORTEX_GROUND: clif->skill_nodamage(src, target, skl->skill_id, skl->skill_lv, 1); skill->attack(BF_MAGIC, src, src, target, skl->skill_id, skl->skill_lv, tick, skl->flag); - skill->toggle_magicpower(src, skl->skill_id); // only the first hit will be amplify + skill->toggle_magicpower(src, skl->skill_id, skl->skill_lv); // only the first hit will be amplify if( skl->type == 4 ){ const enum sc_type scs[] = { SC_BURNING, SC_BLOODING, SC_FROSTMISTY, SC_STUN }; // status inflicts are depend on what summoned element is used. int rate = skl->y, index = skl->x-1; @@ -3999,7 +4268,7 @@ static int skill_timerskill(int tid, int64 tick, int id, intptr_t data) break; case WM_REVERBERATION_MELEE: case WM_REVERBERATION_MAGIC: - skill->attack(skill->get_type(skl->skill_id),src, src, target, skl->skill_id, skl->skill_lv, 0, SD_LEVEL); + skill->attack(skill->get_type(skl->skill_id, skl->skill_lv), src, src, target, skl->skill_id, skl->skill_lv, 0, SD_LEVEL); break; case SC_FATALMENACE: if( src == target ) // Casters Part @@ -4104,7 +4373,7 @@ static int skill_timerskill(int tid, int64 tick, int id, intptr_t data) case LG_OVERBRAND_BRANDISH: skill->area_temp[1] = 0; map->foreachinpath(skill->attack_area,src->m,src->x,src->y,skl->x,skl->y,4,2,BL_CHAR, - skill->get_type(skl->skill_id),src,src,skl->skill_id,skl->skill_lv,tick,skl->flag,BCT_ENEMY); + skill->get_type(skl->skill_id, skl->skill_lv), src, src, skl->skill_id, skl->skill_lv, tick, skl->flag, BCT_ENEMY); break; default: skill->timerskill_notarget_unknown(tid, tick, src, ud, skl); @@ -4281,12 +4550,12 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl if (status->isdead(bl)) return 1; - if (skill_id && skill->get_type(skill_id) == BF_MAGIC && status->isimmune(bl) == 100) { + if (skill_id != 0 && skill->get_type(skill_id, skill_lv) == BF_MAGIC && status->isimmune(bl) == 100) { //GTB makes all targeted magic display miss with a single bolt. sc_type sct = status->skill2sc(skill_id); if(sct != SC_NONE) status_change_end(bl, sct, INVALID_TIMER); - clif->skill_damage(src, bl, tick, status_get_amotion(src), status_get_dmotion(bl), 0, 1, skill_id, skill_lv, skill->get_hit(skill_id)); + clif->skill_damage(src, bl, tick, status_get_amotion(src), status_get_dmotion(bl), 0, 1, skill_id, skill_lv, skill->get_hit(skill_id, skill_lv)); return 1; } @@ -4494,7 +4763,7 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl skill->area_temp[1] = bl->id; map->foreachinpath(skill->attack_area,src->m,src->x,src->y,bl->x,bl->y, skill->get_splash(skill_id, skill_lv),skill->get_maxcount(skill_id,skill_lv), skill->splash_target(src), - skill->get_type(skill_id),src,src,skill_id,skill_lv,tick,flag,BCT_ENEMY); + skill->get_type(skill_id, skill_lv), src, src, skill_id, skill_lv, tick, flag, BCT_ENEMY); break; case NPC_ACIDBREATH: @@ -4505,7 +4774,7 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl skill->area_temp[1] = bl->id; map->foreachinpath(skill->attack_area,src->m,src->x,src->y,bl->x,bl->y, skill->get_splash(skill_id, skill_lv),skill->get_maxcount(skill_id,skill_lv), skill->splash_target(src), - skill->get_type(skill_id),src,src,skill_id,skill_lv,tick,flag,BCT_ENEMY); + skill->get_type(skill_id, skill_lv), src, src, skill_id, skill_lv, tick, flag, BCT_ENEMY); break; case MO_INVESTIGATE: @@ -4677,7 +4946,7 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl if ( tsc && tsc->data[SC_HOVERING] && ( skill_id == SR_WINDMILL || skill_id == LG_MOONSLASHER ) ) break; - heal = skill->attack(skill->get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, sflag); + heal = skill->attack(skill->get_type(skill_id, skill_lv), src, src, bl, skill_id, skill_lv, tick, sflag); if (skill_id == NPC_VAMPIRE_GIFT && heal > 0) { clif->skill_nodamage(NULL, src, AL_HEAL, heal, 1); status->heal(src, heal, 0, STATUS_HEAL_DEFAULT); @@ -4752,7 +5021,7 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl case MS_MAGNUM: if( flag&1 ) { //Damage depends on distance, so add it to flag if it is > 1 - skill->attack(skill->get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag|SD_ANIMATION|distance_bl(src, bl)); + skill->attack(skill->get_type(skill_id, skill_lv), src, src, bl, skill_id, skill_lv, tick, flag|SD_ANIMATION|distance_bl(src, bl)); } break; @@ -4760,9 +5029,9 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl case ML_BRANDISH: //Coded apart for it needs the flag passed to the damage calculation. if (skill->area_temp[1] != bl->id) - skill->attack(skill->get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag|SD_ANIMATION); + skill->attack(skill->get_type(skill_id, skill_lv), src, src, bl, skill_id, skill_lv, tick, flag|SD_ANIMATION); else - skill->attack(skill->get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); + skill->attack(skill->get_type(skill_id, skill_lv), src, src, bl, skill_id, skill_lv, tick, flag); break; case KN_BOWLINGBASH: @@ -5157,7 +5426,7 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl break; case WL_DRAINLIFE: { - int heal = skill->attack(skill->get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); + int heal = skill->attack(skill->get_type(skill_id, skill_lv), src, src, bl, skill_id, skill_lv, tick, flag); int rate = 70 + 5 * skill_lv; heal = heal * (5 + 5 * skill_lv) / 100; @@ -5203,7 +5472,7 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl if (sd) { int i; clif->skill_nodamage(src, bl, skill_id, skill_lv, 1); - skill->toggle_magicpower(src, skill_id); + skill->toggle_magicpower(src, skill_id, skill_lv); // Priority is to release SpellBook if (sc && sc->data[SC_READING_SB]) { // SpellBook @@ -5447,7 +5716,7 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl { struct status_change *tsc = status->get_sc(bl); if( tsc && tsc->data[SC_POISON] ) { - skill->attack(skill->get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); + skill->attack(skill->get_type(skill_id, skill_lv), src, src, bl, skill_id, skill_lv, tick, flag); status_change_end(bl, SC_POISON, INVALID_TIMER); } else if( sd ) clif->skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0, 0); @@ -5469,7 +5738,7 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl case EL_HURRICANE: case EL_TYPOON_MIS: if( flag&1 ) - skill->attack(skill->get_type(skill_id+1),src,src,bl,skill_id+1,skill_lv,tick,flag); + skill->attack(skill->get_type(skill_id + 1, skill_lv), src, src, bl, skill_id + 1, skill_lv, tick, flag); else { int i = skill->get_splash(skill_id,skill_lv); clif->skill_nodamage(src,battle->get_master(src),skill_id,skill_lv,1); @@ -5477,7 +5746,7 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl if( rnd()%100 < 30 ) map->foreachinrange(skill->area_sub,bl,i,BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill->castend_damage_id); else - skill->attack(skill->get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); + skill->attack(skill->get_type(skill_id, skill_lv), src, src, bl, skill_id, skill_lv, tick, flag); } break; @@ -5492,7 +5761,7 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl case EL_STONE_RAIN: if( flag&1 ) - skill->attack(skill->get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); + skill->attack(skill->get_type(skill_id, skill_lv), src, src, bl, skill_id, skill_lv, tick, flag); else { int i = skill->get_splash(skill_id,skill_lv); clif->skill_nodamage(src,battle->get_master(src),skill_id,skill_lv,1); @@ -5500,7 +5769,7 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl if( rnd()%100 < 30 ) map->foreachinrange(skill->area_sub,bl,i,BL_CHAR,src,skill_id,skill_lv,tick,flag|BCT_ENEMY|1,skill->castend_damage_id); else - skill->attack(skill->get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); + skill->attack(skill->get_type(skill_id, skill_lv), src, src, bl, skill_id, skill_lv, tick, flag); } break; @@ -5510,7 +5779,7 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl case EL_STONE_HAMMER: clif->skill_nodamage(src,battle->get_master(src),skill_id,skill_lv,1); clif->skill_damage(src, bl, tick, status_get_amotion(src), 0, -30000, 1, skill_id, skill_lv, BDT_SKILL); - skill->attack(skill->get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); + skill->attack(skill->get_type(skill_id, skill_lv), src, src, bl, skill_id, skill_lv, tick, flag); break; case EL_TIDAL_WEAPON: @@ -5527,7 +5796,7 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl elemental->clean_single_effect(ele, skill_id); } if( rnd()%100 < 50 ) - skill->attack(skill->get_type(skill_id),src,src,bl,skill_id,skill_lv,tick,flag); + skill->attack(skill->get_type(skill_id, skill_lv), src, src, bl, skill_id, skill_lv, tick, flag); else { sc_start(src, src,type2,100,skill_lv,skill->get_time(skill_id,skill_lv)); sc_start(src, battle->get_master(src),type,100,ele->bl.id,skill->get_time(skill_id,skill_lv)); @@ -5540,7 +5809,7 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl case MH_MAGMA_FLOW: case MH_HEILIGE_STANGE: if(flag & 1) - skill->attack(skill->get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); + skill->attack(skill->get_type(skill_id, skill_lv), src, src, bl, skill_id, skill_lv, tick, flag); else { map->foreachinrange(skill->area_sub, bl, skill->get_splash(skill_id, skill_lv), skill->splash_target(src), src, skill_id, skill_lv, tick, flag | BCT_ENEMY | SD_SPLASH | 1, skill->castend_damage_id); } @@ -5564,7 +5833,7 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl break; case SU_SV_STEMSPEAR: - skill->attack(skill->get_type(skill_id), src, src, bl, skill_id, skill_lv, tick, flag); + skill->attack(skill->get_type(skill_id, skill_lv), src, src, bl, skill_id, skill_lv, tick, flag); if (status->get_lv(src) >= 30 && (rnd() % 100 < (int)(status->get_lv(src) / 30) + 10)) // TODO: Need activation chance. skill->addtimerskill(src, tick + skill->get_delay(skill_id, skill_lv), bl->id, 0, 0, skill_id, skill_lv, (skill_id == SU_SV_STEMSPEAR) ? BF_MAGIC : BF_WEAPON, flag); break; @@ -5624,7 +5893,7 @@ static bool skill_castend_damage_id_unknown(struct block_list *src, struct block ShowWarning("skill_castend_damage_id: Unknown skill used:%d\n", *skill_id); clif->skill_damage(src, bl, *tick, status_get_amotion(src), tstatus->dmotion, 0, abs(skill->get_num(*skill_id, *skill_lv)), - *skill_id, *skill_lv, skill->get_hit(*skill_id)); + *skill_id, *skill_lv, skill->get_hit(*skill_id, *skill_lv)); map->freeblock_unlock(); return true; } @@ -5843,7 +6112,7 @@ static int skill_castend_id(int tid, int64 tick, int id, intptr_t data) if (ud->walktimer != INVALID_TIMER && ud->skill_id != TK_RUN && ud->skill_id != RA_WUGDASH) unit->stop_walking(src, STOPWALKING_FLAG_FIXPOS); - if (sd == NULL || sd->autocast.skill_id != ud->skill_id || skill->get_delay(ud->skill_id,ud->skill_lv) != 0) + if (sd == NULL || sd->auto_cast_current.skill_id != ud->skill_id || skill->get_delay(ud->skill_id, ud->skill_lv) != 0) ud->canact_tick = tick + skill->delay_fix(src, ud->skill_id, ud->skill_lv); // Tests show wings don't overwrite the delay but skill scrolls do. [Inkfish] if (sd) { // Cooldown application int i, cooldown = skill->get_cooldown(ud->skill_id, ud->skill_lv); @@ -5876,7 +6145,7 @@ static int skill_castend_id(int tid, int64 tick, int id, intptr_t data) break; } } - if (skill->get_state(ud->skill_id) != ST_MOVE_ENABLE) + if (skill->get_state(ud->skill_id, ud->skill_lv) != ST_MOVE_ENABLE) unit->set_walkdelay(src, tick, battle_config.default_walk_delay+skill->get_walkdelay(ud->skill_id, ud->skill_lv), 1); if(battle_config.skill_log && battle_config.skill_log&src->type) @@ -5886,7 +6155,7 @@ static int skill_castend_id(int tid, int64 tick, int id, intptr_t data) map->freeblock_lock(); // SC_MAGICPOWER needs to switch states before any damage is actually dealt - skill->toggle_magicpower(src, ud->skill_id); + skill->toggle_magicpower(src, ud->skill_id, ud->skill_lv); #if 0 // On aegis damage skills are also increase by camouflage. Need confirmation on kRO. if( ud->skill_id != RA_CAMOUFLAGE ) // only normal attack and auto cast skills benefit from its bonuses @@ -5911,8 +6180,10 @@ static int skill_castend_id(int tid, int64 tick, int id, intptr_t data) skill->blockpc_start(sd,BD_ADAPTATION,3000); } - if( sd && ud->skill_id != SA_ABRACADABRA && ud->skill_id != WM_RANDOMIZESPELL ) // they just set the data so leave it as it is.[Inkfish] - pc->autocast_clear(sd); + if (sd != NULL && ud->skill_id != SA_ABRACADABRA && ud->skill_id != WM_RANDOMIZESPELL + && ud->skill_id == sd->auto_cast_current.skill_id) { // they just set the data so leave it as it is.[Inkfish] + pc->autocast_remove(sd, sd->auto_cast_current.type, ud->skill_id, ud->skill_lv); + } if (ud->skilltimer == INVALID_TIMER) { if(md) md->skill_idx = -1; @@ -5961,16 +6232,20 @@ static int skill_castend_id(int tid, int64 tick, int id, intptr_t data) } } - if (sd == NULL || sd->autocast.skill_id != ud->skill_id || skill->get_delay(ud->skill_id,ud->skill_lv) != 0) + if (sd == NULL || sd->auto_cast_current.skill_id != ud->skill_id || skill->get_delay(ud->skill_id, ud->skill_lv) != 0) ud->canact_tick = tick; - ud->skill_id = ud->skill_lv = ud->skilltarget = 0; //You can't place a skill failed packet here because it would be //sent in ALL cases, even cases where skill_check_condition fails //which would lead to double 'skill failed' messages u.u [Skotlex] - if(sd) - pc->autocast_clear(sd); + if (sd != NULL && ud->skill_id == sd->auto_cast_current.skill_id) + pc->autocast_remove(sd, sd->auto_cast_current.type, ud->skill_id, ud->skill_lv); else if(md) md->skill_idx = -1; + + ud->skill_id = 0; + ud->skill_lv = 0; + ud->skilltarget = 0; + return 0; } @@ -6152,7 +6427,7 @@ static int skill_castend_nodamage_id(struct block_list *src, struct block_list * if (skill->castend_nodamage_id_undead_unknown(src, bl, &skill_id, &skill_lv, &tick, &flag)) { //Skill is actually ground placed. - if (src == bl && skill->get_unit_id(skill_id,0)) + if (src == bl && skill->get_unit_id(skill_id, skill_lv, 0) != 0) return skill->castend_pos2(src,bl->x,bl->y,skill_id,skill_lv,tick,0); } break; @@ -6350,9 +6625,12 @@ static int skill_castend_nodamage_id(struct block_list *src, struct block_list * if (sd) { // player-casted - sd->autocast.type = AUTOCAST_ABRA; - sd->autocast.skill_id = abra_skill_id; - sd->autocast.skill_lv = abra_skill_lv; + pc->autocast_clear(sd); + sd->auto_cast_current.type = AUTOCAST_ABRA; + sd->auto_cast_current.skill_id = abra_skill_id; + sd->auto_cast_current.skill_lv = abra_skill_lv; + VECTOR_ENSURE(sd->auto_cast, 1, 1); + VECTOR_PUSH(sd->auto_cast, sd->auto_cast_current); clif->item_skill(sd, abra_skill_id, abra_skill_lv); } else { // mob-casted @@ -7481,7 +7759,7 @@ static int skill_castend_nodamage_id(struct block_list *src, struct block_list * map->freeblock_unlock(); return 1; } - if (sd->autocast.type == AUTOCAST_NONE) + if (sd->auto_cast_current.type == AUTOCAST_NONE) status_zap(src, 0, skill->get_sp(skill_id, skill_lv)); // consume sp only if succeeded } break; @@ -7518,7 +7796,7 @@ static int skill_castend_nodamage_id(struct block_list *src, struct block_list * break; } - if (sd->autocast.type == AUTOCAST_TEMP || ((sd->autocast.skill_id == AL_TELEPORT || battle_config.skip_teleport_lv1_menu) && skill_lv == 1) || skill_lv == 3) + if (sd->auto_cast_current.type == AUTOCAST_TEMP || ((sd->auto_cast_current.skill_id == AL_TELEPORT || battle_config.skip_teleport_lv1_menu) && skill_lv == 1) || skill_lv == 3) { if( skill_lv == 1 ) pc->randomwarp(sd,CLR_TELEPORT); @@ -7667,7 +7945,7 @@ static int skill_castend_nodamage_id(struct block_list *src, struct block_list * map->freeblock_unlock(); return 1; } - if (sd->inventory_data[inventory_idx] == NULL || sd->status.inventory[inventory_idx].amount < skill->get_itemqty(skill_id, item_idx)) { + if (sd->inventory_data[inventory_idx] == NULL || sd->status.inventory[inventory_idx].amount < skill->get_itemqty(skill_id, item_idx, skill_lv)) { clif->skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0, 0); map->freeblock_unlock(); return 1; @@ -8295,7 +8573,7 @@ static int skill_castend_nodamage_id(struct block_list *src, struct block_list * if (nameid > 0) { int success; struct item item_tmp = { 0 }; - int amount = skill->get_itemqty(su->group->skill_id, i); + int amount = skill->get_itemqty(su->group->skill_id, i, skill_lv); item_tmp.nameid = nameid; item_tmp.identify = 1; if ((success = pc->additem(sd, &item_tmp, amount, LOG_TYPE_SKILL)) != 0) { @@ -10092,9 +10370,12 @@ static int skill_castend_nodamage_id(struct block_list *src, struct block_list * clif->skill_nodamage (src, bl, skill_id, skill_lv, 1); if (sd != NULL) { - sd->autocast.type = AUTOCAST_IMPROVISE; - sd->autocast.skill_id = improv_skill_id; - sd->autocast.skill_lv = improv_skill_lv; + pc->autocast_clear(sd); + sd->auto_cast_current.type = AUTOCAST_IMPROVISE; + sd->auto_cast_current.skill_id = improv_skill_id; + sd->auto_cast_current.skill_lv = improv_skill_lv; + VECTOR_ENSURE(sd->auto_cast, 1, 1); + VECTOR_PUSH(sd->auto_cast, sd->auto_cast_current); clif->item_skill(sd, improv_skill_id, improv_skill_lv); } else { struct unit_data *ud = unit->bl2ud(src); @@ -10863,7 +11144,7 @@ static int skill_castend_pos(int tid, int64 tick, int id, intptr_t data) if (ud->walktimer != INVALID_TIMER) unit->stop_walking(src, STOPWALKING_FLAG_FIXPOS); - if (sd == NULL || sd->autocast.skill_id != ud->skill_id || skill->get_delay(ud->skill_id,ud->skill_lv) != 0) + if (sd == NULL || sd->auto_cast_current.skill_id != ud->skill_id || skill->get_delay(ud->skill_id, ud->skill_lv) != 0) ud->canact_tick = tick + skill->delay_fix(src, ud->skill_id, ud->skill_lv); if (sd) { //Cooldown application int i, cooldown = skill->get_cooldown(ud->skill_id, ud->skill_lv); @@ -10892,8 +11173,8 @@ static int skill_castend_pos(int tid, int64 tick, int id, intptr_t data) map->freeblock_lock(); skill->castend_pos2(src,ud->skillx,ud->skilly,ud->skill_id,ud->skill_lv,tick,0); - if (sd != NULL && sd->autocast.skill_id != AL_WARP) // Warp-Portal thru items will clear data in skill_castend_map. [Inkfish] - pc->autocast_clear(sd); + if (sd != NULL && ud->skill_id != AL_WARP && ud->skill_id == sd->auto_cast_current.skill_id) // Warp-Portal thru items will clear data in skill_castend_map. [Inkfish] + pc->autocast_remove(sd, sd->auto_cast_current.type, ud->skill_id, ud->skill_lv); unit->set_dir(src, map->calc_dir(src, ud->skillx, ud->skilly)); @@ -10907,13 +11188,17 @@ static int skill_castend_pos(int tid, int64 tick, int id, intptr_t data) return 1; } while(0); - if (sd == NULL || sd->autocast.skill_id != ud->skill_id || skill->get_delay(ud->skill_id,ud->skill_lv) != 0) + if (sd == NULL || sd->auto_cast_current.skill_id != ud->skill_id || skill->get_delay(ud->skill_id, ud->skill_lv) != 0) ud->canact_tick = tick; - ud->skill_id = ud->skill_lv = 0; - if(sd) - pc->autocast_clear(sd); + + if (sd != NULL && ud->skill_id == sd->auto_cast_current.skill_id) + pc->autocast_remove(sd, sd->auto_cast_current.type, ud->skill_id, ud->skill_lv); else if(md) md->skill_idx = -1; + + ud->skill_id = 0; + ud->skill_lv = 0; + return 0; } @@ -11072,7 +11357,7 @@ static int skill_castend_map(struct map_session_data *sd, uint16 skill_id, const } } - lv = (sd->autocast.type > AUTOCAST_TEMP) ? sd->autocast.skill_lv : pc->checkskill(sd, skill_id); + lv = (sd->auto_cast_current.type > AUTOCAST_TEMP) ? sd->auto_cast_current.skill_lv : pc->checkskill(sd, skill_id); wx = sd->menuskill_val>>16; wy = sd->menuskill_val&0xffff; @@ -11095,7 +11380,10 @@ static int skill_castend_map(struct map_session_data *sd, uint16 skill_id, const } skill->consume_requirement(sd,sd->menuskill_id,lv,2); - pc->autocast_clear(sd); // Clear data which was skipped in skill_castend_pos(). + + // Clear data which was skipped in skill_castend_pos(). + pc->autocast_remove(sd, sd->auto_cast_current.type, sd->auto_cast_current.skill_id, + sd->auto_cast_current.skill_lv); if((group=skill->unitsetting(&sd->bl,skill_id,lv,wx,wy,0))==NULL) { skill_failed(sd); @@ -11156,7 +11444,7 @@ static int skill_castend_pos2(struct block_list *src, int x, int y, uint16 skill } // SC_MAGICPOWER needs to switch states before any damage is actually dealt - skill->toggle_magicpower(src, skill_id); + skill->toggle_magicpower(src, skill_id, skill_lv); switch(skill_id) { case PR_BENEDICTIO: @@ -11515,7 +11803,7 @@ static int skill_castend_pos2(struct block_list *src, int x, int y, uint16 skill int bonus; if (inventory_idx == INDEX_NOT_FOUND || item_id <= 0 || sd->inventory_data[inventory_idx] == NULL - || sd->status.inventory[inventory_idx].amount < skill->get_itemqty(skill_id, item_idx) + || sd->status.inventory[inventory_idx].amount < skill->get_itemqty(skill_id, item_idx, skill_lv) ) { clif->skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0, 0); return 1; @@ -11761,7 +12049,7 @@ static int skill_castend_pos2(struct block_list *src, int x, int y, uint16 skill skill->unitsetting(src, skill_id, skill_lv, x, y, 0); // Set bomb on current Position clif->skill_nodamage(src, src, skill_id, skill_lv, 1); if( skill->blown(src, src, 3 * skill_lv, unit->getdir(src), 0) && sc) { - sc_start(src, src, SC__FEINTBOMB_MASTER, 100, 0, skill->get_unit_interval(SC_FEINTBOMB)); + sc_start(src, src, SC__FEINTBOMB_MASTER, 100, 0, skill->get_unit_interval(SC_FEINTBOMB, skill_lv)); } break; @@ -11774,7 +12062,7 @@ static int skill_castend_pos2(struct block_list *src, int x, int y, uint16 skill case LG_OVERBRAND: skill->area_temp[1] = 0; map->foreachinpath(skill->attack_area,src->m,src->x,src->y,x,y,1,5,BL_CHAR, - skill->get_type(skill_id),src,src,skill_id,skill_lv,tick,flag,BCT_ENEMY); + skill->get_type(skill_id, skill_lv), src, src, skill_id, skill_lv, tick, flag, BCT_ENEMY); skill->addtimerskill(src,timer->gettick() + status_get_amotion(src), 0, x, y, LG_OVERBRAND_BRANDISH, skill_lv, 0, flag); break; @@ -11811,7 +12099,7 @@ static int skill_castend_pos2(struct block_list *src, int x, int y, uint16 skill int tmpx = x - area + rnd()%(area * 2 + 1); int tmpy = y - area + rnd()%(area * 2 + 1); - skill->addtimerskill(src,tick+r*250,0,tmpx,tmpy,GN_CRAZYWEED_ATK,skill_lv,-1,0); + skill->addtimerskill(src, tick + (int64)r * 250, 0, tmpx, tmpy, GN_CRAZYWEED_ATK, skill_lv, 0, 0); } } break; @@ -12008,10 +12296,10 @@ static bool skill_dance_switch(struct skill_unit *su, int flag) // replace group->skill_id = skill_id; group->skill_lv = 1; - group->unit_id = skill->get_unit_id(skill_id,0); - group->target_flag = skill->get_unit_target(skill_id); - group->bl_flag = skill->get_unit_bl_target(skill_id); - group->interval = skill->get_unit_interval(skill_id); + group->unit_id = skill->get_unit_id(skill_id, 1, 0); + group->target_flag = skill->get_unit_target(skill_id, 1); + group->bl_flag = skill->get_unit_bl_target(skill_id, 1); + group->interval = skill->get_unit_interval(skill_id, 1); } else { //Restore group->skill_id = backup.skill_id; @@ -12044,8 +12332,8 @@ static struct skill_unit_group *skill_unitsetting(struct block_list *src, uint16 limit = skill->get_time(skill_id,skill_lv); range = skill->get_unit_range(skill_id,skill_lv); - interval = skill->get_unit_interval(skill_id); - target = skill->get_unit_target(skill_id); + interval = skill->get_unit_interval(skill_id, skill_lv); + target = skill->get_unit_target(skill_id, skill_lv); unit_flag = skill->get_unit_flag(skill_id); layout = skill->get_unit_layout(skill_id,skill_lv,src,x,y); @@ -12409,12 +12697,12 @@ static struct skill_unit_group *skill_unitsetting(struct block_list *src, uint16 } nullpo_retr(NULL, layout); - nullpo_retr(NULL, group=skill->init_unitgroup(src,layout->count,skill_id,skill_lv,skill->get_unit_id(skill_id,flag&1)+subunt, limit, interval)); + nullpo_retr(NULL, group = skill->init_unitgroup(src, layout->count, skill_id, skill_lv, skill->get_unit_id(skill_id, skill_lv, flag & 1) + subunt, limit, interval)); group->val1=val1; group->val2=val2; group->val3=val3; group->target_flag=target; - group->bl_flag= skill->get_unit_bl_target(skill_id); + group->bl_flag= skill->get_unit_bl_target(skill_id, skill_lv); group->state.ammo_consume = (sd && sd->state.arrow_atk && skill_id != GS_GROUNDDRIFT); //Store if this skill needs to consume ammo. group->state.song_dance = ((unit_flag&(UF_DANCE|UF_SONG)) ? 1 : 0)|((unit_flag&UF_ENSEMBLE) ? 2 : 0); //Signals if this is a song/dance/duet group->state.guildaura = ( skill_id >= GD_LEADERSHIP && skill_id <= GD_HAWKEYES )?1:0; @@ -12583,7 +12871,7 @@ static int skill_unit_onplace(struct skill_unit *src, struct block_list *bl, int nullpo_ret(sg=src->group); nullpo_ret(ss=map->id2bl(sg->src_id)); - if (skill->get_type(sg->skill_id) == BF_MAGIC && map->getcell(src->bl.m, &src->bl, src->bl.x, src->bl.y, CELL_CHKLANDPROTECTOR) && sg->skill_id != SA_LANDPROTECTOR) + if (skill->get_type(sg->skill_id, sg->skill_lv) == BF_MAGIC && map->getcell(src->bl.m, &src->bl, src->bl.x, src->bl.y, CELL_CHKLANDPROTECTOR) != 0 && sg->skill_id != SA_LANDPROTECTOR) return 0; //AoE skills are ineffective. [Skotlex] sc = status->get_sc(bl); @@ -12782,7 +13070,7 @@ static int skill_unit_onplace(struct skill_unit *src, struct block_list *bl, int if( status_get_mode(bl)&MD_BOSS ) break; // iRO Wiki says that this skill don't affect to Boss monsters. if( map_flag_vs(bl->m) || bl->id == src->bl.id || battle->check_target(&src->bl,bl, BCT_ENEMY) == 1 ) - skill->attack(skill->get_type(sg->skill_id), ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); + skill->attack(skill->get_type(sg->skill_id, sg->skill_lv), ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); break; case UNT_REVERBERATION: @@ -13018,7 +13306,7 @@ static int skill_unit_onplace_timer(struct skill_unit *src, struct block_list *b case WZ_STORMGUST: //SG counter does not reset per stormgust. IE: One hit from a SG and two hits from another will freeze you. if (tsc) tsc->sg_counter++; //SG hit counter. - if (skill->attack(skill->get_type(sg->skill_id),ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0) <= 0 && tsc) + if (skill->attack(skill->get_type(sg->skill_id, sg->skill_lv), ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0) <= 0 && tsc != NULL) tsc->sg_counter=0; //Attack absorbed. break; #endif @@ -13027,7 +13315,7 @@ static int skill_unit_onplace_timer(struct skill_unit *src, struct block_list *b skill->attack(BF_WEAPON,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); break; default: - skill->attack(skill->get_type(sg->skill_id),ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + skill->attack(skill->get_type(sg->skill_id, sg->skill_lv), ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); } break; @@ -13348,7 +13636,7 @@ static int skill_unit_onplace_timer(struct skill_unit *src, struct block_list *b case UNT_PSYCHIC_WAVE: case UNT_MAGMA_ERUPTION: case UNT_MAKIBISHI: - skill->attack(skill->get_type(sg->skill_id),ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + skill->attack(skill->get_type(sg->skill_id, sg->skill_lv), ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); break; case UNT_GROUNDDRIFT_WIND: @@ -13449,7 +13737,7 @@ static int skill_unit_onplace_timer(struct skill_unit *src, struct block_list *b sec = 3000; // Couldn't trap it? sg->limit = DIFF_TICK32(tick, sg->tick) + sec; } else if( tsc->data[SC_THORNS_TRAP] && bl->id == sg->val2 ) - skill->attack(skill->get_type(GN_THORNS_TRAP), ss, ss, bl, sg->skill_id, sg->skill_lv, tick, SD_LEVEL|SD_ANIMATION); + skill->attack(skill->get_type(GN_THORNS_TRAP, sg->skill_lv), ss, ss, bl, sg->skill_id, sg->skill_lv, tick, SD_LEVEL|SD_ANIMATION); } break; @@ -13461,11 +13749,11 @@ static int skill_unit_onplace_timer(struct skill_unit *src, struct block_list *b default: sc_start4(ss, bl, SC_BURNING, 4 + 4 * sg->skill_lv, sg->skill_lv, 0, ss->id, 0, skill->get_time2(sg->skill_id, sg->skill_lv)); - skill->attack(skill->get_type(sg->skill_id), ss, &src->bl, bl, + skill->attack(skill->get_type(sg->skill_id, sg->skill_lv), ss, &src->bl, bl, sg->skill_id, sg->skill_lv + 10 * sg->val2, tick, 0); break; case 3: - skill->attack(skill->get_type(CR_ACIDDEMONSTRATION), ss, &src->bl, bl, + skill->attack(skill->get_type(CR_ACIDDEMONSTRATION, sg->skill_lv), ss, &src->bl, bl, CR_ACIDDEMONSTRATION, sd ? pc->checkskill(sd, CR_ACIDDEMONSTRATION) : sg->skill_lv, tick, 0); break; @@ -13483,7 +13771,7 @@ static int skill_unit_onplace_timer(struct skill_unit *src, struct block_list *b case UNT_HELLS_PLANT: if( battle->check_target(&src->bl,bl,BCT_ENEMY) > 0 ) - skill->attack(skill->get_type(GN_HELLS_PLANT_ATK), ss, &src->bl, bl, GN_HELLS_PLANT_ATK, sg->skill_lv, tick, 0); + skill->attack(skill->get_type(GN_HELLS_PLANT_ATK, sg->skill_lv), ss, &src->bl, bl, GN_HELLS_PLANT_ATK, sg->skill_lv, tick, 0); if( ss != bl) //The caster is the only one who can step on the Plants, without destroying them sg->limit = DIFF_TICK32(tick, sg->tick) + 100; break; @@ -13491,7 +13779,7 @@ static int skill_unit_onplace_timer(struct skill_unit *src, struct block_list *b case UNT_CLOUD_KILL: if(tsc && !tsc->data[type]) status->change_start(ss,bl,type,10000,sg->skill_lv,sg->group_id,0,0,skill->get_time2(sg->skill_id,sg->skill_lv),SCFLAG_FIXEDRATE); - skill->attack(skill->get_type(sg->skill_id),ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + skill->attack(skill->get_type(sg->skill_id, sg->skill_lv), ss, &src->bl, bl, sg->skill_id, sg->skill_lv, tick, 0); break; case UNT_WARMER: @@ -13616,7 +13904,7 @@ static int skill_unit_onplace_timer(struct skill_unit *src, struct block_list *b } sg->limit = DIFF_TICK32(tick, sg->tick) + sec; } else if (tsc->data[type] && bl->id == sg->val2) { - skill->attack(skill->get_type(SU_SV_ROOTTWIST_ATK), ss, &src->bl, bl, SU_SV_ROOTTWIST_ATK, sg->skill_lv, tick, SD_LEVEL|SD_ANIMATION); + skill->attack(skill->get_type(SU_SV_ROOTTWIST_ATK, sg->skill_lv), ss, &src->bl, bl, SU_SV_ROOTTWIST_ATK, sg->skill_lv, tick, SD_LEVEL|SD_ANIMATION); } } break; @@ -14070,16 +14358,16 @@ static int skill_check_condition_mob_master_sub(struct block_list *bl, va_list a * Determines if a given skill should be made to consume ammo * when used by the player. [Skotlex] *------------------------------------------*/ -static int skill_isammotype(struct map_session_data *sd, int skill_id) +static int skill_isammotype(struct map_session_data *sd, int skill_id, int skill_lv) { nullpo_ret(sd); return ( battle_config.arrow_decrement==2 && (sd->weapontype == W_BOW || (sd->weapontype >= W_REVOLVER && sd->weapontype <= W_GRENADE)) && skill_id != HT_PHANTASMIC && - skill->get_type(skill_id) == BF_WEAPON && + skill->get_type(skill_id, skill_lv) == BF_WEAPON && !(skill->get_nk(skill_id)&NK_NO_DAMAGE) && - !skill->get_spiritball(skill_id,1) //Assume spirit spheres are used as ammo instead. + !skill->get_spiritball(skill_id, skill_lv) //Assume spirit spheres are used as ammo instead. ); } @@ -14094,6 +14382,90 @@ static bool skill_is_combo(int skill_id) return false; } +/** + * Checks if a skill's equipment requirements are fulfilled. + * + * @param sd The character who casts the skill. + * @param skill_id The skill's ID. + * @param skill_lv The skill's level. + * @return 0 on success or 1 in case of error. + * + **/ +static int skill_check_condition_required_equip(struct map_session_data *sd, int skill_id, int skill_lv) +{ + nullpo_retr(1, sd); + + struct skill_condition req = skill->get_requirement(sd, skill_id, skill_lv); + bool any_equip_flag = skill->get_equip_any_flag(skill_id, skill_lv); + bool any_equip_found = false; + int fail_id = 0; + int fail_amount = 0; + + for (int i = 0; i < MAX_SKILL_ITEM_REQUIRE; i++) { + if (req.equip_id[i] == 0) + continue; + + int req_id = req.equip_id[i]; + int req_amount = req.equip_amount[i]; + int found_amount = 0; + + for (int j = 0; j < EQI_MAX; j++) { + int inv_idx = sd->equip_index[j]; + + if (inv_idx == INDEX_NOT_FOUND || sd->inventory_data[inv_idx] == NULL) + continue; + + if ((j == EQI_HAND_R && sd->equip_index[EQI_HAND_L] == inv_idx) + || (j == EQI_HEAD_MID && sd->equip_index[EQI_HEAD_LOW] == inv_idx) + || (j == EQI_HEAD_TOP && sd->equip_index[EQI_HEAD_MID] == inv_idx) + || (j == EQI_HEAD_TOP && sd->equip_index[EQI_HEAD_LOW] == inv_idx) + || (j == EQI_COSTUME_MID && sd->equip_index[EQI_COSTUME_LOW] == inv_idx) + || (j == EQI_COSTUME_TOP && sd->equip_index[EQI_COSTUME_MID] == inv_idx) + || (j == EQI_COSTUME_TOP && sd->equip_index[EQI_COSTUME_LOW] == inv_idx)) { + continue; // Equipment uses more than one slot; only process it once! + } + + if (itemdb_type(req_id) != IT_CARD) { + if (sd->inventory_data[inv_idx]->nameid != req_id) + continue; + + if (itemdb_type(req_id) == IT_AMMO) + found_amount += sd->status.inventory[inv_idx].amount; + else + found_amount++; + } else { + if (itemdb_isspecial(sd->status.inventory[inv_idx].card[0])) + continue; + + for (int k = 0; k < sd->inventory_data[inv_idx]->slot; k++) { + if (sd->status.inventory[inv_idx].card[k] == req_id) + found_amount++; + } + } + } + + if (any_equip_flag) { + if (found_amount >= req_amount) { + any_equip_found = true; + break; + } else if (fail_id == 0) { // Save ID/amount of first missing equipment for skill fail message. + fail_id = req_id; + fail_amount = req_amount; + } + } else if (found_amount < req_amount) { + clif->skill_fail(sd, skill_id, USESKILL_FAIL_NEED_EQUIPMENT, req_amount, req_id); + return 1; + } + } + + if (any_equip_flag && !any_equip_found) { + clif->skill_fail(sd, skill_id, USESKILL_FAIL_NEED_EQUIPMENT, fail_amount, fail_id); + return 1; + } + + return 0; +} + static int skill_check_condition_castbegin(struct map_session_data *sd, uint16 skill_id, uint16 skill_lv) { struct status_data *st; @@ -14108,12 +14480,12 @@ static int skill_check_condition_castbegin(struct map_session_data *sd, uint16 s if (sd->chat_id != 0) return 0; - if (((sd->autocast.itemskill_conditions_checked || !sd->autocast.itemskill_check_conditions) - && sd->autocast.type == AUTOCAST_ITEM) || sd->autocast.type == AUTOCAST_IMPROVISE) { + if (((sd->auto_cast_current.itemskill_conditions_checked || !sd->auto_cast_current.itemskill_check_conditions) + && sd->auto_cast_current.type == AUTOCAST_ITEM) || sd->auto_cast_current.type == AUTOCAST_IMPROVISE) { return 1; } - if (pc_has_permission(sd, PC_PERM_SKILL_UNCONDITIONAL) && sd->autocast.type != AUTOCAST_ITEM) { + if (pc_has_permission(sd, PC_PERM_SKILL_UNCONDITIONAL) && sd->auto_cast_current.type != AUTOCAST_ITEM) { // GMs don't override the AUTOCAST_ITEM check, otherwise they can use items without them being consumed! sd->state.arrow_atk = skill->get_ammotype(skill_id)?1:0; //Need to do arrow state check. sd->spiritball_old = sd->spiritball; //Need to do Spiritball check. @@ -14145,7 +14517,7 @@ static int skill_check_condition_castbegin(struct map_session_data *sd, uint16 s if( !sc->count ) sc = NULL; - if (pc_is90overweight(sd) && sd->autocast.type != AUTOCAST_ITEM) { // Skill casting items ignore the overweight restriction. + if (pc_is90overweight(sd) && sd->auto_cast_current.type != AUTOCAST_ITEM) { // Skill casting items ignore the overweight restriction. clif->skill_fail(sd, skill_id, USESKILL_FAIL_WEIGHTOVER, 0, 0); return 0; } @@ -14793,22 +15165,6 @@ static int skill_check_condition_castbegin(struct map_session_data *sd, uint16 s require.sp -= require.sp * 20 * count / 100; // -20% each W/M in the party. } break; - case NC_PILEBUNKER: - if (sd->equip_index[EQI_HAND_R] < 0 - || !itemid_is_pilebunker(sd->status.inventory[sd->equip_index[EQI_HAND_R]].nameid) - ) { - clif->skill_fail(sd, skill_id, USESKILL_FAIL_THIS_WEAPON, 0, 0); - return 0; - } - break; - case NC_HOVERING: - if (( sd->equip_index[EQI_ACC_L] >= 0 && sd->status.inventory[sd->equip_index[EQI_ACC_L]].nameid == ITEMID_HOVERING_BOOSTER ) || - ( sd->equip_index[EQI_ACC_R] >= 0 && sd->status.inventory[sd->equip_index[EQI_ACC_R]].nameid == ITEMID_HOVERING_BOOSTER )); - else { - clif->skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0, 0); - return 0; - } - break; case SO_FIREWALK: case SO_ELECTRICWALK: // Can't be casted until you've walked all cells. if( sc && sc->data[SC_PROPERTYWALK] && @@ -15011,12 +15367,20 @@ static int skill_check_condition_castbegin(struct map_session_data *sd, uint16 s return 0; } + if (require.msp > 0 && get_percentage(st->sp, st->max_sp) > require.msp) { + clif->skill_fail(sd, skill_id, USESKILL_FAIL_SP_INSUFFICIENT, 0, 0); + return 0; + } + if( require.weapon && !pc_check_weapontype(sd,require.weapon) ) { clif->skill_fail(sd, skill_id, USESKILL_FAIL_THIS_WEAPON, 0, 0); return 0; } - if (require.sp > 0 && st->sp < (unsigned int)require.sp && sd->autocast.type == AUTOCAST_NONE) { // Auto-cast skills don't consume SP. + if (skill->check_condition_required_equip(sd, skill_id, skill_lv) != 0) + return 0; + + if (require.sp > 0 && st->sp < (unsigned int)require.sp && sd->auto_cast_current.type == AUTOCAST_NONE) { // Auto-cast skills don't consume SP. clif->skill_fail(sd, skill_id, USESKILL_FAIL_SP_INSUFFICIENT, 0, 0); return 0; } @@ -15062,24 +15426,139 @@ static int skill_check_condition_castbegin_unknown(struct status_change *sc, uin return -1; } +/** + * Checks if a skill's item requirements are fulfilled. + * + * @param sd The character who casts the skill. + * @param skill_id The skill's ID. + * @param skill_lv The skill's level. + * @return 0 on success or 1 in case of error. + * + **/ +static int skill_check_condition_required_items(struct map_session_data *sd, int skill_id, int skill_lv) +{ + nullpo_retr(1, sd); + + struct skill_condition req = skill->get_requirement(sd, skill_id, skill_lv); + + if (skill->get_item_any_flag(skill_id, skill_lv)) { + for (int i = 0; i < MAX_SKILL_ITEM_REQUIRE; i++) { + if (req.itemid[i] == 0) + continue; + + int inv_idx = pc->search_inventory(sd, req.itemid[i]); + + if (inv_idx == INDEX_NOT_FOUND) + continue; + + if ((req.amount[i] > 0 && sd->status.inventory[inv_idx].amount >= req.amount[i]) + || (req.amount[i] == 0 && sd->status.inventory[inv_idx].amount > 0)) { + return 0; + } + } + } + + /** + * Find first missing item and show skill failed message if item any-flag is false + * or item any-flag check didn't find an item with sufficient amount. + * + **/ + for (int i = 0; i < MAX_SKILL_ITEM_REQUIRE; i++) { + if (req.itemid[i] == 0) + continue; + + int inv_idx = pc->search_inventory(sd, req.itemid[i]); + + if (inv_idx == INDEX_NOT_FOUND || sd->status.inventory[inv_idx].amount < req.amount[i]) { + useskill_fail_cause cause = USESKILL_FAIL_NEED_ITEM; + + switch (skill_id) { + case NC_SILVERSNIPER: + case NC_MAGICDECOY: + cause = USESKILL_FAIL_STUFF_INSUFFICIENT; + break; + default: + switch (req.itemid[i]) { + case ITEMID_RED_GEMSTONE: + cause = USESKILL_FAIL_REDJAMSTONE; + break; + case ITEMID_BLUE_GEMSTONE: + cause = USESKILL_FAIL_BLUEJAMSTONE; + break; + case ITEMID_HOLY_WATER: + cause = USESKILL_FAIL_HOLYWATER; + break; + case ITEMID_ANSILA: + cause = USESKILL_FAIL_ANCILLA; + break; + case ITEMID_ACCELERATOR: + case ITEMID_HOVERING_BOOSTER: + case ITEMID_SUICIDAL_DEVICE: + case ITEMID_SHAPE_SHIFTER: + case ITEMID_COOLING_DEVICE: + case ITEMID_MAGNETIC_FIELD_GENERATOR: + case ITEMID_BARRIER_BUILDER: + case ITEMID_CAMOUFLAGE_GENERATOR: + case ITEMID_REPAIR_KIT: + case ITEMID_MONKEY_SPANNER: + cause = USESKILL_FAIL_NEED_EQUIPMENT; + FALLTHROUGH + default: + clif->skill_fail(sd, skill_id, cause, max(1, req.amount[i]), req.itemid[i]); + return 1; + } + + break; + } + + clif->skill_fail(sd, skill_id, cause, 0, 0); + return 1; + } + } + + return 0; +} + +/** + * Checks if a skill has item requirements. + * + * @param sd The character who casts the skill. + * @param skill_id The skill's ID. + * @param skill_lv The skill's level. + * @return True if skill has item requirements, otherwise false. + * + **/ +static bool skill_items_required(struct map_session_data *sd, int skill_id, int skill_lv) +{ + nullpo_retr(false, sd); + + struct skill_condition req = skill->get_requirement(sd, skill_id, skill_lv); + + for (int i = 0; i < MAX_SKILL_ITEM_REQUIRE; i++) { + if (req.itemid[i] != 0) + return true; + } + + return false; +} + static int skill_check_condition_castend(struct map_session_data *sd, uint16 skill_id, uint16 skill_lv) { struct skill_condition require; struct status_data *st; int i; - int index[MAX_SKILL_ITEM_REQUIRE]; nullpo_ret(sd); if (sd->chat_id != 0) return 0; - if (((sd->autocast.itemskill_conditions_checked || !sd->autocast.itemskill_check_conditions) - && sd->autocast.type == AUTOCAST_ITEM) || sd->autocast.type == AUTOCAST_IMPROVISE) { + if (((sd->auto_cast_current.itemskill_conditions_checked || !sd->auto_cast_current.itemskill_check_conditions) + && sd->auto_cast_current.type == AUTOCAST_ITEM) || sd->auto_cast_current.type == AUTOCAST_IMPROVISE) { return 1; } - if (pc_has_permission(sd, PC_PERM_SKILL_UNCONDITIONAL) && sd->autocast.type != AUTOCAST_ITEM) { + if (pc_has_permission(sd, PC_PERM_SKILL_UNCONDITIONAL) && sd->auto_cast_current.type != AUTOCAST_ITEM) { // GMs don't override the AUTOCAST_ITEM check, otherwise they can use items without them being consumed! sd->state.arrow_atk = skill->get_ammotype(skill_id)?1:0; //Need to do arrow state check. sd->spiritball_old = sd->spiritball; //Need to do Spiritball check. @@ -15107,7 +15586,7 @@ static int skill_check_condition_castend(struct map_session_data *sd, uint16 ski break; } - if (pc_is90overweight(sd) && sd->autocast.type != AUTOCAST_ITEM) { // Skill casting items ignore the overweight restriction. + if (pc_is90overweight(sd) && sd->auto_cast_current.type != AUTOCAST_ITEM) { // Skill casting items ignore the overweight restriction. clif->skill_fail(sd, skill_id, USESKILL_FAIL_WEIGHTOVER, 0, 0); return 0; } @@ -15219,48 +15698,10 @@ static int skill_check_condition_castend(struct map_session_data *sd, uint16 ski } } - for( i = 0; i < MAX_SKILL_ITEM_REQUIRE; ++i ) { - if( !require.itemid[i] ) - continue; - index[i] = pc->search_inventory(sd,require.itemid[i]); - if (index[i] == INDEX_NOT_FOUND || sd->status.inventory[index[i]].amount < require.amount[i]) { - useskill_fail_cause cause = USESKILL_FAIL_NEED_ITEM; - switch( skill_id ){ - case NC_SILVERSNIPER: - case NC_MAGICDECOY: - cause = USESKILL_FAIL_STUFF_INSUFFICIENT; - break; - default: - switch(require.itemid[i]){ - case ITEMID_RED_GEMSTONE: - cause = USESKILL_FAIL_REDJAMSTONE; break; - case ITEMID_BLUE_GEMSTONE: - cause = USESKILL_FAIL_BLUEJAMSTONE; break; - case ITEMID_HOLY_WATER: - cause = USESKILL_FAIL_HOLYWATER; break; - case ITEMID_ANSILA: - cause = USESKILL_FAIL_ANCILLA; break; - case ITEMID_ACCELERATOR: - case ITEMID_HOVERING_BOOSTER: - case ITEMID_SUICIDAL_DEVICE: - case ITEMID_SHAPE_SHIFTER: - case ITEMID_COOLING_DEVICE: - case ITEMID_MAGNETIC_FIELD_GENERATOR: - case ITEMID_BARRIER_BUILDER: - case ITEMID_CAMOUFLAGE_GENERATOR: - case ITEMID_REPAIR_KIT: - case ITEMID_MONKEY_SPANNER: - cause = USESKILL_FAIL_NEED_EQUIPMENT; - /* Fall through */ - default: - clif->skill_fail(sd, skill_id, cause, max(1, require.amount[i]), require.itemid[i]); - return 0; - } - } - clif->skill_fail(sd, skill_id, cause, 0, 0); - return 0; - } - } + bool items_required = skill->items_required(sd, skill_id, skill_lv); + + if (items_required && skill->check_condition_required_items(sd, skill_id, skill_lv) != 0) + return 0; return 1; } @@ -15270,6 +15711,43 @@ static bool skill_check_condition_castend_unknown(struct map_session_data *sd, u return false; } +/** + * Gets the array index of the first required item with sufficient amount. + * + * @param sd The character who casts the skill. + * @param skill_id The skill's ID. + * @param skill_lv The skill's level. + * @return A number greater than or equal to 0 on success, otherwise INDEX_NOT_FOUND (-1). + * + **/ +static int skill_get_any_item_index(struct map_session_data *sd, int skill_id, int skill_lv) +{ + nullpo_retr(INDEX_NOT_FOUND, sd); + + int any_item_index = INDEX_NOT_FOUND; + + if (skill->get_item_any_flag(skill_id, skill_lv)) { + struct skill_condition req = skill->get_requirement(sd, skill_id, skill_lv); + + for (int i = 0; i < MAX_SKILL_ITEM_REQUIRE; i++) { + if (req.itemid[i] == 0) + continue; + + int inv_idx = pc->search_inventory(sd, req.itemid[i]); + + if (inv_idx == INDEX_NOT_FOUND) + continue; + + if (req.amount[i] == 0 || sd->status.inventory[inv_idx].amount >= req.amount[i]) { + any_item_index = i; + break; + } + } + } + + return any_item_index; +} + // type&2: consume items (after skill was used) // type&1: consume the others (before skill was used) static int skill_consume_requirement(struct map_session_data *sd, uint16 skill_id, uint16 skill_lv, short type) @@ -15278,8 +15756,8 @@ static int skill_consume_requirement(struct map_session_data *sd, uint16 skill_i nullpo_ret(sd); - if ((!sd->autocast.itemskill_check_conditions && sd->autocast.type == AUTOCAST_ITEM) - || sd->autocast.type == AUTOCAST_IMPROVISE) { + if ((!sd->auto_cast_current.itemskill_check_conditions && sd->auto_cast_current.type == AUTOCAST_ITEM) + || sd->auto_cast_current.type == AUTOCAST_IMPROVISE) { return 1; } @@ -15297,7 +15775,7 @@ static int skill_consume_requirement(struct map_session_data *sd, uint16 skill_i break; default: - if (sd->autocast.type != AUTOCAST_NONE) // Auto-cast skills don't consume SP. + if (sd->auto_cast_current.type != AUTOCAST_NONE) // Auto-cast skills don't consume SP. req.sp = 0; break; @@ -15322,16 +15800,24 @@ static int skill_consume_requirement(struct map_session_data *sd, uint16 skill_i if( type&2 ) { struct status_change *sc = &sd->sc; - int n,i; + int n; if( !sc->count ) sc = NULL; - for( i = 0; i < MAX_SKILL_ITEM_REQUIRE; ++i ) - { + bool items_required = skill->items_required(sd, skill_id, skill_lv); + int any_item_index = INDEX_NOT_FOUND; + + if (items_required) + any_item_index = skill->get_any_item_index(sd, skill_id, skill_lv); + + for (int i = 0; i < MAX_SKILL_ITEM_REQUIRE && items_required; i++) { if( !req.itemid[i] ) continue; + if (any_item_index != INDEX_NOT_FOUND && any_item_index != i) + continue; + if( itemid_isgemstone(req.itemid[i]) && skill_id != HW_GANBANTEIN && sc && sc->data[SC_SOULLINK] && sc->data[SC_SOULLINK]->val2 == SL_WIZARD ) continue; //Gemstones are checked, but not subtracted from inventory. @@ -15469,61 +15955,36 @@ static struct skill_condition skill_get_requirement(struct map_session_data *sd, req.spiritball = skill->dbs->db[idx].spiritball[skill_lv-1]; - req.state = skill->dbs->db[idx].state; + req.state = skill->dbs->db[idx].state[skill_lv - 1]; req.mhp = skill->dbs->db[idx].mhp[skill_lv-1]; + req.msp = skill->get_msp(skill_id, skill_lv); + req.weapon = skill->dbs->db[idx].weapon; req.ammo_qty = skill->dbs->db[idx].ammo_qty[skill_lv-1]; if (req.ammo_qty) req.ammo = skill->dbs->db[idx].ammo; - if (!req.ammo && skill_id && skill->isammotype(sd, skill_id)) { + if (req.ammo == 0 && skill_id != 0 && skill->isammotype(sd, skill_id, skill_lv)) { //Assume this skill is using the weapon, therefore it requires arrows. req.ammo = 0xFFFFFFFF; //Enable use on all ammo types. req.ammo_qty = 1; } for( i = 0; i < MAX_SKILL_ITEM_REQUIRE; i++ ) { - int item_idx = (skill_lv - 1) % MAX_SKILL_ITEM_REQUIRE; - if ((skill_id == AM_POTIONPITCHER || skill_id == CR_SLIMPITCHER || skill_id == CR_CULTIVATION) && i != item_idx) - continue; - switch( skill_id ) { case AM_CALLHOMUN: if (sd->status.hom_id) //Don't delete items when hom is already out. continue; break; - case NC_SHAPESHIFT: - if( i < 4 ) - continue; - break; - case WZ_FIREPILLAR: // celest - if (skill_lv <= 5) // no gems required at level 1-5 - continue; - break; case AB_ADORAMUS: - if( itemid_isgemstone(skill->dbs->db[idx].itemid[i]) && skill->check_pc_partner(sd,skill_id,&skill_lv, 1, 2) ) + if (itemid_isgemstone(skill->get_itemid(skill_id, i)) && skill->check_pc_partner(sd, skill_id, &skill_lv, 1, 2) != 0) continue; break; case WL_COMET: - if( itemid_isgemstone(skill->dbs->db[idx].itemid[i]) && skill->check_pc_partner(sd,skill_id,&skill_lv, 1, 0) ) - continue; - break; - case GN_FIRE_EXPANSION: - if( i < 5 ) - continue; - break; - case SO_SUMMON_AGNI: - case SO_SUMMON_AQUA: - case SO_SUMMON_VENTUS: - case SO_SUMMON_TERA: - case SO_WATER_INSIGNIA: - case SO_FIRE_INSIGNIA: - case SO_WIND_INSIGNIA: - case SO_EARTH_INSIGNIA: - if( i < 3 ) + if (itemid_isgemstone(skill->get_itemid(skill_id, i)) && skill->check_pc_partner(sd, skill_id, &skill_lv, 1, 0) != 0) continue; break; default: @@ -15534,8 +15995,17 @@ static struct skill_condition skill_get_requirement(struct map_session_data *sd, } } - req.itemid[i] = skill->dbs->db[idx].itemid[i]; - req.amount[i] = skill->dbs->db[idx].amount[i]; + int amount; + + if ((amount = skill->get_itemqty(skill_id, i, skill_lv)) >= 0) { + req.itemid[i] = skill->get_itemid(skill_id, i); + req.amount[i] = amount; + } + + if ((amount = skill->get_equip_amount(skill_id, i, skill_lv)) > 0) { + req.equip_id[i] = skill->get_equip_id(skill_id, i); + req.equip_amount[i] = amount; + } if (itemid_isgemstone(req.itemid[i]) && skill_id != HW_GANBANTEIN) { if (sd->special_state.no_gemstone) { @@ -15567,39 +16037,6 @@ static struct skill_condition skill_get_requirement(struct map_session_data *sd, } } - /* requirements are level-dependent */ - switch( skill_id ) { - case NC_SHAPESHIFT: - case GN_FIRE_EXPANSION: - case SO_SUMMON_AGNI: - case SO_SUMMON_AQUA: - case SO_SUMMON_VENTUS: - case SO_SUMMON_TERA: - case SO_WATER_INSIGNIA: - case SO_FIRE_INSIGNIA: - case SO_WIND_INSIGNIA: - case SO_EARTH_INSIGNIA: - req.itemid[skill_lv-1] = skill->dbs->db[idx].itemid[skill_lv-1]; - req.amount[skill_lv-1] = skill->dbs->db[idx].amount[skill_lv-1]; - break; - } - if (skill_id == NC_REPAIR) { - switch(skill_lv) { - case 1: - case 2: - req.itemid[1] = ITEMID_REPAIRA; - break; - case 3: - case 4: - req.itemid[1] = ITEMID_REPAIRB; - break; - case 5: - req.itemid[1] = ITEMID_REPAIRC; - break; - } - req.amount[1] = 1; - } - // Check for cost reductions due to skills & SCs switch(skill_id) { case MC_MAMMONITE: @@ -17003,7 +17440,7 @@ static int skill_trap_splash(struct block_list *bl, va_list ap) } /* Fall through */ default: - skill->attack(skill->get_type(sg->skill_id), ss, src, bl, sg->skill_id, sg->skill_lv, tick, enemy_count); + skill->attack(skill->get_type(sg->skill_id, sg->skill_lv), ss, src, bl, sg->skill_id, sg->skill_lv, tick, enemy_count); break; } return 1; @@ -17687,7 +18124,7 @@ static int skill_unit_timer_sub(union DBKey key, struct DBData *data, va_list ap case UNT_WARP_ACTIVE: // warp portal opens (morph to a UNT_WARP_WAITING cell) - group->unit_id = skill->get_unit_id(group->skill_id, 1); // UNT_WARP_WAITING + group->unit_id = skill->get_unit_id(group->skill_id, group->skill_lv, 1); // UNT_WARP_WAITING clif->changelook(&su->bl, LOOK_BASE, group->unit_id); // restart timers group->limit = skill->get_time(group->skill_id,group->skill_lv); @@ -18814,12 +19251,12 @@ static int skill_poisoningweapon(struct map_session_data *sd, int nameid) return 0; } -static void skill_toggle_magicpower(struct block_list *bl, uint16 skill_id) +static void skill_toggle_magicpower(struct block_list *bl, uint16 skill_id, int skill_lv) { struct status_change *sc = status->get_sc(bl); // non-offensive and non-magic skills do not affect the status - if (skill->get_nk(skill_id)&NK_NO_DAMAGE || !(skill->get_type(skill_id)&BF_MAGIC)) + if ((skill->get_nk(skill_id) & NK_NO_DAMAGE) != 0 || (skill->get_type(skill_id, skill_lv) & BF_MAGIC) == 0) return; if (sc && sc->count && sc->data[SC_MAGICPOWER]) { @@ -18953,14 +19390,20 @@ static int skill_select_menu(struct map_session_data *sd, uint16 skill_id) idx = skill->get_index(skill_id); - if( skill_id >= GS_GLITTERING || skill->get_type(skill_id) != BF_MAGIC || - (id = sd->status.skill[idx].id) == 0 || sd->status.skill[idx].flag != SKILL_FLAG_PLAGIARIZED ) { + if (skill_id >= GS_GLITTERING || (id = sd->status.skill[idx].id) == 0 + || sd->status.skill[idx].flag != SKILL_FLAG_PLAGIARIZED) { clif->skill_fail(sd, SC_AUTOSHADOWSPELL, 0, 0, 0); return 0; } lv = (aslvl + 1) / 2; // The level the skill will be autocasted - lv = min(lv,sd->status.skill[idx].lv); + lv = min(lv, sd->status.skill[idx].lv); + + if (skill->get_type(skill_id, lv) != BF_MAGIC) { + clif->skill_fail(sd, SC_AUTOSHADOWSPELL, 0, 0, 0); + return 0; + } + prob = (aslvl == 10) ? 15 : (32 - 2 * aslvl); // Probability at level 10 was increased to 15. sc_start4(&sd->bl,&sd->bl,SC__AUTOSHADOWSPELL,100,id,lv,prob,0,skill->get_time(SC_AUTOSHADOWSPELL,aslvl)); return 0; @@ -20101,9 +20544,6 @@ static bool skill_parse_row_changematerialdb(char *split[], int columns, int cur return true; } -#define skilldb_duplicate_warning(name, setting, skill) (ShowError("skill_read_skilldb: Duplicate entry '%s' in setting '%s' for Skill Id %d in '%s', skipping...\n", name, setting, skill, "db/"DBPATH"skill_db.conf")) -#define skilldb_invalid_error(name, setting, skill) (ShowError("skill_read_skilldb: Invalid entry '%s' in setting '%s' for Skill Id %d in '%s', skipping...\n", name, setting, skill, "db/"DBPATH"skill_db.conf")) - /** * Sets Level based configuration for skill groups from skill_db.conf [ Smokexyz/Hercules ] * @param *conf pointer to config setting. @@ -20148,295 +20588,549 @@ static void skill_level_set_value(int *arr, int value) } } -static void skill_validate_hittype(struct config_setting_t *conf, struct s_skill_db *sk) +/** + * Validates a skill's ID when reading the skill DB. + * If validating fails, the ID is set to 0. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the ID should be set it. + * @param conf_index The 1-based index of the currently processed libconfig settings block. + * + **/ +static void skill_validate_id(struct config_setting_t *conf, struct s_skill_db *sk, int conf_index) { - const char *type = NULL; + nullpo_retv(conf); + nullpo_retv(sk); + + sk->nameid = 0; + + int id; + + if (libconfig->setting_lookup_int(conf, "Id", &id) == CONFIG_FALSE) + ShowError("%s: No skill ID specified in entry %d in %s! Skipping skill...\n", + __func__, conf_index, conf->file); + else if (id <= 0) + ShowError("%s: Invalid skill ID %d specified in entry %d in %s! Skipping skill...\n", + __func__, id, conf_index, conf->file); + else if(skill->get_index(id) == 0) + ShowError("%s: Skill ID %d in entry %d in %s is out of range, or within a reserved range (for guild, homunculus, mercenary or elemental skills)! Skipping skill...\n", + __func__, id, conf_index, conf->file); + else if (*skill->get_name(id) != '\0') + ShowError("%s: Duplicate skill ID %d in entry %d in %s! Skipping skill...\n", + __func__, id, conf_index, conf->file); + else + sk->nameid = id; +} + +/** + * Validates if a skill's name contains invalid characters when reading the skill DB. + * + * @param name The name to validate. + * @return True if the passed name is a NULL pointer or contains at least one invalid character, otherwise false. + * + **/ +static bool skill_name_contains_invalid_character(const char *name) +{ + nullpo_retr(true, name); + + for (int i = 0; i < MAX_SKILL_NAME_LENGTH && name[i] != '\0'; i++) { + if (ISALNUM(name[i]) == 0 && name[i] != '_') + return true; + } + + return false; +} +/** + * Validates a skill's name when reading the skill DB. + * If validating fails, the name is set to an enpty string. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the name should be set it. + * + **/ +static void skill_validate_name(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); nullpo_retv(sk); - if (libconfig->setting_lookup_string(conf, "Hit", &type)) { - if (strcmpi(type, "BDT_SKILL") == 0) { - sk->hit = BDT_SKILL; - } else if (strcmpi(type, "BDT_MULTIHIT") == 0) { - sk->hit = BDT_MULTIHIT; - } else if (strcmpi(type, "BDT_NORMAL") == 0) { - sk->hit = BDT_NORMAL; - } else { - skilldb_invalid_error(type, "Hit", sk->nameid); + + *sk->name = '\0'; + + const char *name; + + if (libconfig->setting_lookup_string(conf, "Name", &name) == CONFIG_FALSE || *name == '\0') + ShowError("%s: No name specified for skill ID %d in %s! Skipping skill...\n", + __func__, sk->nameid, conf->file); + else if (strlen(name) >= sizeof(sk->name)) + ShowError("%s: Specified name %s for skill ID %d in %s is too long: %lu! Maximum is %lu. Skipping skill...\n", + __func__, name, sk->nameid, conf->file, strlen(name), sizeof(sk->name) - 1); + else if (skill->name_contains_invalid_character(name)) + ShowError("%s: Specified name %s for skill ID %d in %s contains invalid characters! Allowed characters are letters, numbers and underscores. Skipping skill...\n", + __func__, name, sk->nameid, conf->file); + else if (skill->name2id(name) != 0) + ShowError("%s: Duplicate name %s for skill ID %d in %s! Skipping skill...\n", + __func__, name, sk->nameid, conf->file); + else + safestrncpy(sk->name, name, sizeof(sk->name)); +} + +/** + * Validates a skill's maximum level when reading the skill DB. + * If validating fails, the maximum level is set to 0. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the maximum level should be set it. + * + **/ +static void skill_validate_max_level(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + sk->max = 0; + + int max_level; + + if (libconfig->setting_lookup_int(conf, "MaxLevel", &max_level) == CONFIG_FALSE) + ShowError("%s: No maximum level specified for skill ID %d in %s! Skipping skill...\n", + __func__, sk->nameid, conf->file); + else if (max_level < 1 || max_level > MAX_SKILL_LEVEL) + ShowError("%s: Invalid maximum level %d specified for skill ID %d in %s! Minimum is 1, maximum is %d. Skipping skill...\n", + __func__, max_level, sk->nameid, conf->file, MAX_SKILL_LEVEL); + else + sk->max = max_level; +} + +/** + * Validates a skill's description when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the description should be set it. + * + **/ +static void skill_validate_description(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + *sk->desc = '\0'; + + const char *description; + + if (libconfig->setting_lookup_string(conf, "Description", &description) == CONFIG_TRUE && *description != '\0') { + if (strlen(description) >= sizeof(sk->desc)) + ShowWarning("%s: Specified description '%s' for skill ID %d in %s is too long: %lu! Maximum is %lu. Trimming...\n", + __func__, description, sk->nameid, conf->file, strlen(description), sizeof(sk->desc) - 1); + + safestrncpy(sk->desc, description, sizeof(sk->desc)); + } +} + +/** + * Validates a skill's range when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the range should be set it. + * + **/ +static void skill_validate_range(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->range, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "Range"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int range; + + if (libconfig->setting_lookup_int(t, lv, &range) == CONFIG_TRUE) { + if (range >= SHRT_MIN && range <= SHRT_MAX) + sk->range[i] = range; + else + ShowWarning("%s: Invalid range %d specified in level %d for skill ID %d in %s! Minimum is %d, maximum is %d. Defaulting to 0...\n", + __func__, range, i + 1, sk->nameid, conf->file, SHRT_MIN, SHRT_MAX); + } + } + + return; + } + + int range; + + if (libconfig->setting_lookup_int(conf, "Range", &range) == CONFIG_TRUE) { + if (range >= SHRT_MIN && range <= SHRT_MAX) + skill->level_set_value(sk->range, range); + else + ShowWarning("%s: Invalid range %d specified for skill ID %d in %s! Minimum is %d, maximum is %d. Defaulting to 0...\n", + __func__, range, sk->nameid, conf->file, SHRT_MIN, SHRT_MAX); + } +} + +/** + * Validates a skill's hit type when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the hit type should be set it. + * + **/ +static void skill_validate_hittype(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->hit, BDT_NORMAL); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "Hit"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + const char *hit_type; + + if (libconfig->setting_lookup_string(t, lv, &hit_type) == CONFIG_TRUE) { + if (strcmpi(hit_type, "BDT_SKILL") == 0) + sk->hit[i] = BDT_SKILL; + else if (strcmpi(hit_type, "BDT_MULTIHIT") == 0) + sk->hit[i] = BDT_MULTIHIT; + else if (strcmpi(hit_type, "BDT_NORMAL") != 0) + ShowWarning("%s: Invalid hit type %s specified in level %d for skill ID %d in %s! Defaulting to BDT_NORMAL...\n", + __func__, hit_type, i + 1, sk->nameid, conf->file); + } + } + + return; + } + + const char *hit_type; + + if (libconfig->setting_lookup_string(conf, "Hit", &hit_type) == CONFIG_TRUE) { + int hit = BDT_NORMAL; + + if (strcmpi(hit_type, "BDT_SKILL") == 0) { + hit = BDT_SKILL; + } else if (strcmpi(hit_type, "BDT_MULTIHIT") == 0) { + hit = BDT_MULTIHIT; + } else if (strcmpi(hit_type, "BDT_NORMAL") != 0) { + ShowWarning("%s: Invalid hit type %s specified for skill ID %d in %s! Defaulting to BDT_NORMAL...\n", + __func__, hit_type, sk->nameid, conf->file); return; } + + skill->level_set_value(sk->hit, hit); } } /** - * Validates "SkillType" when reading skill_db.conf - * @param conf struct, pointer to skill configuration - * @param sk struct, pointer to s_skill_db - * @return (void) - */ + * Validates a skill's types when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the types should be set it. + * + **/ static void skill_validate_skilltype(struct config_setting_t *conf, struct s_skill_db *sk) { - struct config_setting_t *t = NULL, *tt = NULL; - + nullpo_retv(conf); nullpo_retv(sk); - if((t=libconfig->setting_get_member(conf, "SkillType")) && config_setting_is_group(t)) { - int j=0; - while ((tt = libconfig->setting_get_elem(t, j++))) { - const char *type = config_setting_name(tt); + + sk->inf = INF_NONE; + + struct config_setting_t *t = libconfig->setting_get_member(conf, "SkillType"); + + if (t != NULL && config_setting_is_group(t)) { + struct config_setting_t *tt; + int i = 0; + + while ((tt = libconfig->setting_get_elem(t, i++)) != NULL) { + const char *skill_type = config_setting_name(tt); bool on = libconfig->setting_get_bool_real(tt); - if (strcmpi(type, "Enemy") == 0) { - if (on) { + if (strcmpi(skill_type, "Enemy") == 0) { + if (on) sk->inf |= INF_ATTACK_SKILL; - } else { + else sk->inf &= ~INF_ATTACK_SKILL; - } - } else if (strcmpi(type, "Place") == 0) { - if (on) { + } else if (strcmpi(skill_type, "Place") == 0) { + if (on) sk->inf |= INF_GROUND_SKILL; - } else { + else sk->inf &= ~INF_GROUND_SKILL; - } - } else if (strcmpi(type, "Self") == 0) { - if (on) { + } else if (strcmpi(skill_type, "Self") == 0) { + if (on) sk->inf |= INF_SELF_SKILL; - } else { + else sk->inf &= ~INF_SELF_SKILL; - } - } else if (strcmpi(type, "Friend") == 0) { - if (on) { + } else if (strcmpi(skill_type, "Friend") == 0) { + if (on) sk->inf |= INF_SUPPORT_SKILL; - } else { + else sk->inf &= ~INF_SUPPORT_SKILL; - } - } else if (strcmpi(type, "Trap") == 0) { - if (on) { + } else if (strcmpi(skill_type, "Trap") == 0) { + if (on) sk->inf |= INF_TARGET_TRAP; - } else { + else sk->inf &= ~INF_TARGET_TRAP; - } - } else if (strcmpi(type, "Passive") != 0) { - skilldb_invalid_error(type, config_setting_name(t), sk->nameid); + } else if (strcmpi(skill_type, "Passive") != 0) { + ShowWarning("%s: Invalid skill type %s specified for skill ID %d in %s! Skipping type...\n", + __func__, skill_type, sk->nameid, conf->file); } } } } /** - * Validates "SkillInfo" when reading skill_db.conf - * @param conf struct, pointer to skill configuration - * @param sk struct, pointer to s_skill_db - * @return (void) - */ + * Validates a skill's sub-types when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the sub-types should be set it. + * + **/ static void skill_validate_skillinfo(struct config_setting_t *conf, struct s_skill_db *sk) { - struct config_setting_t *t = NULL, *tt = NULL; - + nullpo_retv(conf); nullpo_retv(sk); - if ((t=libconfig->setting_get_member(conf, "SkillInfo")) && config_setting_is_group(t)) { - int j=0; - while ((tt = libconfig->setting_get_elem(t, j++))) { - const char *type = config_setting_name(tt); + + sk->inf2 = INF2_NONE; + + struct config_setting_t *t = libconfig->setting_get_member(conf, "SkillInfo"); + + if (t != NULL && config_setting_is_group(t)) { + struct config_setting_t *tt; + int i = 0; + + while ((tt = libconfig->setting_get_elem(t, i++)) != NULL) { + const char *skill_info = config_setting_name(tt); bool on = libconfig->setting_get_bool_real(tt); - if (strcmpi(type, "Quest") == 0) { - if (on) { + if (strcmpi(skill_info, "Quest") == 0) { + if (on) sk->inf2 |= INF2_QUEST_SKILL; - } else { + else sk->inf2 &= ~INF2_QUEST_SKILL; - } - } else if (strcmpi(type, "NPC") == 0) { - if (on) { + } else if (strcmpi(skill_info, "NPC") == 0) { + if (on) sk->inf2 |= INF2_NPC_SKILL; - } else { + else sk->inf2 &= ~INF2_NPC_SKILL; - } - } else if (strcmpi(type, "Wedding") == 0) { - if (on) { + } else if (strcmpi(skill_info, "Wedding") == 0) { + if (on) sk->inf2 |= INF2_WEDDING_SKILL; - } else { + else sk->inf2 &= ~INF2_WEDDING_SKILL; - } - } else if (strcmpi(type, "Spirit") == 0) { - if (on) { + } else if (strcmpi(skill_info, "Spirit") == 0) { + if (on) sk->inf2 |= INF2_SPIRIT_SKILL; - } else { + else sk->inf2 &= ~INF2_SPIRIT_SKILL; - } - } else if (strcmpi(type, "Guild") == 0) { - if (on) { + } else if (strcmpi(skill_info, "Guild") == 0) { + if (on) sk->inf2 |= INF2_GUILD_SKILL; - } else { + else sk->inf2 &= ~INF2_GUILD_SKILL; - } - } else if (strcmpi(type, "Song") == 0) { - if (on) { + } else if (strcmpi(skill_info, "Song") == 0) { + if (on) sk->inf2 |= INF2_SONG_DANCE; - } else { + else sk->inf2 &= ~INF2_SONG_DANCE; - } - } else if (strcmpi(type, "Ensemble") == 0) { - if (on) { + } else if (strcmpi(skill_info, "Ensemble") == 0) { + if (on) sk->inf2 |= INF2_ENSEMBLE_SKILL; - } else { + else sk->inf2 &= ~INF2_ENSEMBLE_SKILL; - } - } else if (strcmpi(type, "Trap") == 0) { - if (on) { + } else if (strcmpi(skill_info, "Trap") == 0) { + if (on) sk->inf2 |= INF2_TRAP; - } else { + else sk->inf2 &= ~INF2_TRAP; - } - } else if (strcmpi(type, "TargetSelf") == 0) { - if (on) { + } else if (strcmpi(skill_info, "TargetSelf") == 0) { + if (on) sk->inf2 |= INF2_TARGET_SELF; - } else { + else sk->inf2 &= ~INF2_TARGET_SELF; - } - } else if (strcmpi(type, "NoCastSelf") == 0) { - if (on) { + } else if (strcmpi(skill_info, "NoCastSelf") == 0) { + if (on) sk->inf2 |= INF2_NO_TARGET_SELF; - } else { + else sk->inf2 &= ~INF2_NO_TARGET_SELF; - } - } else if (strcmpi(type, "PartyOnly") == 0) { - if (on) { + } else if (strcmpi(skill_info, "PartyOnly") == 0) { + if (on) sk->inf2 |= INF2_PARTY_ONLY; - } else { + else sk->inf2 &= ~INF2_PARTY_ONLY; - } - } else if (strcmpi(type, "GuildOnly") == 0) { - if (on) { + } else if (strcmpi(skill_info, "GuildOnly") == 0) { + if (on) sk->inf2 |= INF2_GUILD_ONLY; - } else { + else sk->inf2 &= ~INF2_GUILD_ONLY; - } - } else if (strcmpi(type, "NoEnemy") == 0) { - if (on) { + } else if (strcmpi(skill_info, "NoEnemy") == 0) { + if (on) sk->inf2 |= INF2_NO_ENEMY; - } else { + else sk->inf2 &= ~INF2_NO_ENEMY; - } - } else if (strcmpi(type, "IgnoreLandProtector") == 0) { - if (on) { + } else if (strcmpi(skill_info, "IgnoreLandProtector") == 0) { + if (on) sk->inf2 |= INF2_NOLP; - } else { + else sk->inf2 &= ~INF2_NOLP; - } - } else if (strcmpi(type, "Chorus") == 0) { - if (on) { + } else if (strcmpi(skill_info, "Chorus") == 0) { + if (on) sk->inf2 |= INF2_CHORUS_SKILL; - } else { + else sk->inf2 &= ~INF2_CHORUS_SKILL; - } - } else if (strcmpi(type, "FreeCastNormal") == 0) { - if (on) { + } else if (strcmpi(skill_info, "FreeCastNormal") == 0) { + if (on) sk->inf2 |= INF2_FREE_CAST_NORMAL; - } else { + else sk->inf2 &= ~INF2_FREE_CAST_NORMAL; - } - } else if (strcmpi(type, "FreeCastReduced") == 0) { - if (on) { + } else if (strcmpi(skill_info, "FreeCastReduced") == 0) { + if (on) sk->inf2 |= INF2_FREE_CAST_REDUCED; - } else { + else sk->inf2 &= ~INF2_FREE_CAST_REDUCED; - } - } else if (strcmpi(type, "ShowSkillScale") == 0) { - if (on) { + } else if (strcmpi(skill_info, "ShowSkillScale") == 0) { + if (on) sk->inf2 |= INF2_SHOW_SKILL_SCALE; - } else { + else sk->inf2 &= ~INF2_SHOW_SKILL_SCALE; - } - } else if (strcmpi(type, "AllowReproduce") == 0) { - if (on) { + } else if (strcmpi(skill_info, "AllowReproduce") == 0) { + if (on) sk->inf2 |= INF2_ALLOW_REPRODUCE; - } else { + else sk->inf2 &= ~INF2_ALLOW_REPRODUCE; - } - } else if (strcmpi(type, "HiddenTrap") == 0) { - if (on) { + } else if (strcmpi(skill_info, "HiddenTrap") == 0) { + if (on) sk->inf2 |= INF2_HIDDEN_TRAP; - } else { + else sk->inf2 &= ~INF2_HIDDEN_TRAP; - } - } else if (strcmpi(type, "IsCombo") == 0) { - if (on) { + } else if (strcmpi(skill_info, "IsCombo") == 0) { + if (on) sk->inf2 |= INF2_IS_COMBO_SKILL; - } else { + else sk->inf2 &= ~INF2_IS_COMBO_SKILL; - } - } else if (strcmpi(type, "None") != 0) { - skilldb_invalid_error(type, config_setting_name(t), sk->nameid); + } else if (strcmpi(skill_info, "None") != 0) { + ShowWarning("%s: Invalid sub-type %s specified for skill ID %d in %s! Skipping sub-type...\n", + __func__, skill_info, sk->nameid, conf->file); } } } } /** - * Validates "AttackType" when reading skill_db.conf - * @param conf struct, pointer to skill configuration - * @param sk struct, pointer to s_skill_db - * @return (void) - */ + * Validates a skill's attack type when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the attack type should be set it. + * + **/ static void skill_validate_attacktype(struct config_setting_t *conf, struct s_skill_db *sk) { - const char *type = NULL; - + nullpo_retv(conf); nullpo_retv(sk); - if (libconfig->setting_lookup_string(conf, "AttackType", &type)) { - if (!strcmpi(type, "Weapon")) { - sk->skill_type = BF_WEAPON; - } else if (!strcmpi(type, "Magic")) { - sk->skill_type = BF_MAGIC; - } else if (!strcmpi(type, "Misc")) { - sk->skill_type = BF_MISC; - } else { - skilldb_invalid_error(type, "AttackType", sk->nameid); + + skill->level_set_value(sk->skill_type, BF_NONE); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "AttackType"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + const char *attack_type; + + if (libconfig->setting_lookup_string(t, lv, &attack_type) == CONFIG_TRUE) { + if (strcmpi(attack_type, "Weapon") == 0) + sk->skill_type[i] = BF_WEAPON; + else if (strcmpi(attack_type, "Magic") == 0) + sk->skill_type[i] = BF_MAGIC; + else if (strcmpi(attack_type, "Misc") == 0) + sk->skill_type[i] = BF_MISC; + else if (strcmpi(attack_type, "None") != 0) + ShowWarning("%s: Invalid attack type %s specified in level %d for skill ID %d in %s! Defaulting to None...\n", + __func__, attack_type, i + 1, sk->nameid, conf->file); + } + } + + return; + } + + const char *attack_type; + + if (libconfig->setting_lookup_string(conf, "AttackType", &attack_type) == CONFIG_TRUE) { + int attack = BF_NONE; + + if (strcmpi(attack_type, "Weapon") == 0) { + attack = BF_WEAPON; + } else if (strcmpi(attack_type, "Magic") == 0) { + attack = BF_MAGIC; + } else if (strcmpi(attack_type, "Misc") == 0) { + attack = BF_MISC; + } else if (strcmpi(attack_type, "None") != 0) { + ShowWarning("%s: Invalid attack type %s specified for skill ID %d in %s! Defaulting to None...\n", + __func__, attack_type, sk->nameid, conf->file); return; } + + skill->level_set_value(sk->skill_type, attack); } } /** - * Validates "Element" when reading skill_db.conf - * @param ele_t struct, pointer to skill configuration - * @param sk struct, pointer to s_skill_db - * @return (void) - */ + * Validates a skill's element when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the element should be set it. + * + **/ static void skill_validate_element(struct config_setting_t *conf, struct s_skill_db *sk) { - const char *type = NULL; - struct config_setting_t *t = NULL; - + nullpo_retv(conf); nullpo_retv(sk); - if ((t=libconfig->setting_get_member(conf, "Element")) && config_setting_is_group(t)) { - int j = 0; - char lv[6]; // enough to contain "Lv100" in case of custom MAX_SKILL_LEVEL - for (j=0; j < MAX_SKILL_LEVEL; j++) { - sprintf(lv, "Lv%d",j+1); - if (libconfig->setting_lookup_string(t, lv, &type)) { - if (strcmpi(type,"Ele_Weapon") == 0) - sk->element[j] = -1; - else if (strcmpi(type,"Ele_Endowed") == 0) - sk->element[j] = -2; - else if (strcmpi(type,"Ele_Random") == 0) - sk->element[j] = -3; - else if (!script->get_constant(type,&sk->element[j])) - skilldb_invalid_error(type, config_setting_name(conf), sk->nameid); + skill->level_set_value(sk->element, ELE_NEUTRAL); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "Element"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + const char *element; + + if (libconfig->setting_lookup_string(t, lv, &element) == CONFIG_TRUE) { + if (strcmpi(element, "Ele_Weapon") == 0) + sk->element[i] = -1; + else if (strcmpi(element, "Ele_Endowed") == 0) + sk->element[i] = -2; + else if (strcmpi(element, "Ele_Random") == 0) + sk->element[i] = -3; + else if (!script->get_constant(element, &sk->element[i])) + ShowWarning("%s: Invalid element %s specified in level %d for skill ID %d in %s! Defaulting to Ele_Neutral...\n", + __func__, element, i + 1, sk->nameid, conf->file); } } - } else if (libconfig->setting_lookup_string(conf, "Element", &type)) { - int ele = 0; + return; + } + + const char *element; + + if (libconfig->setting_lookup_string(conf, "Element", &element) == CONFIG_TRUE) { + int ele = ELE_NEUTRAL; - if (strcmpi(type,"Ele_Weapon") == 0) + if (strcmpi(element, "Ele_Weapon") == 0) { ele = -1; - else if (strcmpi(type,"Ele_Endowed") == 0) + } else if (strcmpi(element, "Ele_Endowed") == 0) { ele = -2; - else if (strcmpi(type,"Ele_Random") == 0) + } else if (strcmpi(element, "Ele_Random") == 0) { ele = -3; - else if (!script->get_constant(type, &ele)) { - skilldb_invalid_error(type, config_setting_name(conf), sk->nameid); + } else if (!script->get_constant(element, &ele)) { + ShowWarning("%s: Invalid element %s specified for skill ID %d in %s! Defaulting to Ele_Neutral...\n", + __func__, element, sk->nameid, conf->file); return; } @@ -20445,696 +21139,2516 @@ static void skill_validate_element(struct config_setting_t *conf, struct s_skill } /** - * Validates "DamageType" when reading skill_db.conf - * @param conf struct, pointer to skill configuration - * @param sk struct, pointer to s_skill_db - * @return (void) - */ + * Validates a skill's damage types when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the damage types should be set it. + * + **/ static void skill_validate_damagetype(struct config_setting_t *conf, struct s_skill_db *sk) { - struct config_setting_t *t = NULL, *tt = NULL; - + nullpo_retv(conf); nullpo_retv(sk); - if ((t=libconfig->setting_get_member(conf, "DamageType")) && config_setting_is_group(t)) { - int j=0; - while ((tt = libconfig->setting_get_elem(t, j++))) { - const char *type = config_setting_name(tt); + + sk->nk = NK_NONE; + + struct config_setting_t *t = libconfig->setting_get_member(conf, "DamageType"); + + if (t != NULL && config_setting_is_group(t)) { + struct config_setting_t *tt; + int i = 0; + + while ((tt = libconfig->setting_get_elem(t, i++)) != NULL) { + const char *damage_type = config_setting_name(tt); bool on = libconfig->setting_get_bool_real(tt); - if (strcmpi(type, "NoDamage") == 0) { - if (on) { + if (strcmpi(damage_type, "NoDamage") == 0) { + if (on) sk->nk |= NK_NO_DAMAGE; - } else { + else sk->nk &= ~NK_NO_DAMAGE; - } - } else if (strcmpi(type, "SplashArea") == 0) { - if (on) { + } else if (strcmpi(damage_type, "SplashArea") == 0) { + if (on) sk->nk |= NK_SPLASH_ONLY; - } else { + else sk->nk &= ~NK_SPLASH_ONLY; - } - } else if (strcmpi(type, "SplitDamage") == 0) { - if (on) { + } else if (strcmpi(damage_type, "SplitDamage") == 0) { + if (on) sk->nk |= NK_SPLASHSPLIT; - } else { + else sk->nk &= ~NK_SPLASHSPLIT; - } - } else if (strcmpi(type, "IgnoreCards") == 0) { - if (on) { + } else if (strcmpi(damage_type, "IgnoreCards") == 0) { + if (on) sk->nk |= NK_NO_CARDFIX_ATK; - } else { + else sk->nk &= ~NK_NO_CARDFIX_ATK; - } - } else if (strcmpi(type, "IgnoreElement") == 0) { - if (on) { + } else if (strcmpi(damage_type, "IgnoreElement") == 0) { + if (on) sk->nk |= NK_NO_ELEFIX; - } else { + else sk->nk &= ~NK_NO_ELEFIX; - } - } else if (strcmpi(type, "IgnoreDefense") == 0) { - if (on) { + } else if (strcmpi(damage_type, "IgnoreDefense") == 0) { + if (on) sk->nk |= NK_IGNORE_DEF; - } else { + else sk->nk &= ~NK_IGNORE_DEF; - } - } else if (strcmpi(type, "IgnoreFlee") == 0) { - if (on) { + } else if (strcmpi(damage_type, "IgnoreFlee") == 0) { + if (on) sk->nk |= NK_IGNORE_FLEE; - } else { + else sk->nk &= ~NK_IGNORE_FLEE; - } - } else if (strcmpi(type, "IgnoreDefCards") == 0) { - if (on) { + } else if (strcmpi(damage_type, "IgnoreDefCards") == 0) { + if (on) sk->nk |= NK_NO_CARDFIX_DEF; - } else { + else sk->nk &= ~NK_NO_CARDFIX_DEF; - } } else { - skilldb_invalid_error(type, config_setting_name(t), sk->nameid); + ShowWarning("%s: Invalid damage type %s specified for skill ID %d in %s! Skipping damage type...\n", + __func__, damage_type, sk->nameid, conf->file); } } } + + if (sk->nk == NK_NONE) + sk->nk = NK_NO_DAMAGE; } /** - * Validates "SkillCast/DelayOptions" when reading skill_db.conf - * @param conf struct, pointer to skill configuration - * @param sk struct, pointer to s_skill_db - * @param delay boolean, switch for cast/delay setting - * @return (void) - */ -static void skill_validate_castnodex(struct config_setting_t *conf, struct s_skill_db *sk, bool delay) + * Validates a skill's splash range when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the splash range should be set it. + * + **/ +static void skill_validate_splash_range(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->splash, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "SplashRange"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int splash_range; + + if (libconfig->setting_lookup_int(t, lv, &splash_range) == CONFIG_TRUE) { + if (splash_range >= SHRT_MIN && splash_range <= SHRT_MAX) + sk->splash[i] = splash_range; + else + ShowWarning("%s: Invalid splash range %d specified in level %d for skill ID %d in %s! Minimum is %d, maximum is %d. Defaulting to 0...\n", + __func__, splash_range, i + 1, sk->nameid, conf->file, SHRT_MIN, SHRT_MAX); + } + } + + return; + } + + int splash_range; + + if (libconfig->setting_lookup_int(conf, "SplashRange", &splash_range) == CONFIG_TRUE) { + if (splash_range >= SHRT_MIN && splash_range <= SHRT_MAX) + skill->level_set_value(sk->splash, splash_range); + else + ShowWarning("%s: Invalid splash range %d specified for skill ID %d in %s! Minimum is %d, maximum is %d. Defaulting to 0...\n", + __func__, splash_range, sk->nameid, conf->file, SHRT_MIN, SHRT_MAX); + } +} + +/** + * Validates a skill's number of hits when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the number of hits should be set it. + * + **/ +static void skill_validate_number_of_hits(struct config_setting_t *conf, struct s_skill_db *sk) { - struct config_setting_t *t = NULL, *tt = NULL; + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->num, 1); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "NumberOfHits"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int number_of_hits; + + if (libconfig->setting_lookup_int(t, lv, &number_of_hits) == CONFIG_TRUE) { + if (number_of_hits >= SHRT_MIN && number_of_hits <= SHRT_MAX) + sk->num[i] = number_of_hits; + else + ShowWarning("%s: Invalid number of hits %d specified in level %d for skill ID %d in %s! Minimum is %d, maximum is %d. Defaulting to 1...\n", + __func__, number_of_hits, i + 1, sk->nameid, conf->file, SHRT_MIN, SHRT_MAX); + } + } + + return; + } + + int number_of_hits; + + if (libconfig->setting_lookup_int(conf, "NumberOfHits", &number_of_hits) == CONFIG_TRUE) { + if (number_of_hits >= SHRT_MIN && number_of_hits <= SHRT_MAX) + skill->level_set_value(sk->num, number_of_hits); + else + ShowWarning("%s: Invalid number of hits %d specified for skill ID %d in %s! Minimum is %d, maximum is %d. Defaulting to 1...\n", + __func__, number_of_hits, sk->nameid, conf->file, SHRT_MIN, SHRT_MAX); + } +} + +/** + * Validates a skill's cast interruptibility when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the cast interruptibility should be set it. + * + **/ +static void skill_validate_interrupt_cast(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->castcancel, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "InterruptCast"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int interrupt_cast; + + if (libconfig->setting_lookup_bool(t, lv, &interrupt_cast) == CONFIG_TRUE) + sk->castcancel[i] = (interrupt_cast != 0) ? 1 : 0; + } + + return; + } + + int interrupt_cast; + + if (libconfig->setting_lookup_bool(conf, "InterruptCast", &interrupt_cast) == CONFIG_TRUE) { + if (interrupt_cast != 0) + skill->level_set_value(sk->castcancel, 1); + } +} + +/** + * Validates a skill's cast defence rate when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the cast defence rate should be set it. + * + **/ +static void skill_validate_cast_def_rate(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->cast_def_rate, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "CastDefRate"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int cast_def_rate; + + if (libconfig->setting_lookup_int(t, lv, &cast_def_rate) == CONFIG_TRUE) { + if (cast_def_rate >= SHRT_MIN && cast_def_rate <= SHRT_MAX) + sk->cast_def_rate[i] = cast_def_rate; + else + ShowWarning("%s: Invalid cast defence rate %d specified in level %d for skill ID %d in %s! Minimum is %d, maximum is %d. Defaulting to 0...\n", + __func__, cast_def_rate, i + 1, sk->nameid, conf->file, SHRT_MIN, SHRT_MAX); + } + } + + return; + } + + int cast_def_rate; + + if (libconfig->setting_lookup_int(conf, "CastDefRate", &cast_def_rate) == CONFIG_TRUE) { + if (cast_def_rate >= SHRT_MIN && cast_def_rate <= SHRT_MAX) + skill->level_set_value(sk->cast_def_rate, cast_def_rate); + else + ShowWarning("%s: Invalid cast defence rate %d specified for skill ID %d in %s! Minimum is %d, maximum is %d. Defaulting to 0...\n", + __func__, cast_def_rate, sk->nameid, conf->file, SHRT_MIN, SHRT_MAX); + } +} + +/** + * Validates a skill's number of instances when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the number of instances should be set it. + * + **/ +static void skill_validate_number_of_instances(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->maxcount, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "SkillInstances"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int number_of_instances; + + if (libconfig->setting_lookup_int(t, lv, &number_of_instances) == CONFIG_TRUE) { + if (number_of_instances >= 0 && number_of_instances <= MAX_SKILLUNITGROUP) + sk->maxcount[i] = number_of_instances; + else + ShowWarning("%s: Invalid number of instances %d specified in level %d for skill ID %d in %s! Minimum is 0, maximum is %d. Defaulting to 0...\n", + __func__, number_of_instances, i + 1, sk->nameid, conf->file, MAX_SKILLUNITGROUP); + } + } + + return; + } + + int number_of_instances; + + if (libconfig->setting_lookup_int(conf, "SkillInstances", &number_of_instances) == CONFIG_TRUE) { + if (number_of_instances >= 0 && number_of_instances <= MAX_SKILLUNITGROUP) + skill->level_set_value(sk->maxcount, number_of_instances); + else + ShowWarning("%s: Invalid number of instances %d specified for skill ID %d in %s! Minimum is 0, maximum is %d. Defaulting to 0...\n", + __func__, number_of_instances, sk->nameid, conf->file, MAX_SKILLUNITGROUP); + } +} + +/** + * Validates a skill's number of knock back tiles when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the number of knock back tiles should be set it. + * + **/ +static void skill_validate_knock_back_tiles(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->blewcount, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "KnockBackTiles"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int knock_back_tiles; + + if (libconfig->setting_lookup_int(t, lv, &knock_back_tiles) == CONFIG_TRUE) { + if (knock_back_tiles >= 0) + sk->blewcount[i] = knock_back_tiles; + else + ShowWarning("%s: Invalid number of knock back tiles %d specified in level %d for skill ID %d in %s! Must be greater than or equal to 0. Defaulting to 0...\n", + __func__, knock_back_tiles, i + 1, sk->nameid, conf->file); + } + } + + return; + } + + int knock_back_tiles; + + if (libconfig->setting_lookup_int(conf, "KnockBackTiles", &knock_back_tiles) == CONFIG_TRUE) { + if (knock_back_tiles >= 0) + skill->level_set_value(sk->blewcount, knock_back_tiles); + else + ShowWarning("%s: Invalid number of knock back tiles %d specified for skill ID %d in %s! Must be greater than or equal to 0. Defaulting to 0...\n", + __func__, knock_back_tiles, sk->nameid, conf->file); + } +} + +/** + * Validates a skill's cast time when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the cast time should be set it. + * + **/ +static void skill_validate_cast_time(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->cast, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "CastTime"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int cast_time; + + if (libconfig->setting_lookup_int(t, lv, &cast_time) == CONFIG_TRUE) { + if (cast_time >= 0) + sk->cast[i] = cast_time; + else + ShowWarning("%s: Invalid cast time %d specified in level %d for skill ID %d in %s! Must be greater than or equal to 0. Defaulting to 0...\n", + __func__, cast_time, i + 1, sk->nameid, conf->file); + } + } + + return; + } + + int cast_time; + + if (libconfig->setting_lookup_int(conf, "CastTime", &cast_time) == CONFIG_TRUE) { + if (cast_time >= 0) + skill->level_set_value(sk->cast, cast_time); + else + ShowWarning("%s: Invalid cast time %d specified for skill ID %d in %s! Must be greater than or equal to 0. Defaulting to 0...\n", + __func__, cast_time, sk->nameid, conf->file); + } +} + +/** + * Validates a skill's after cast act delay when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the after cast act delay should be set it. + * + **/ +static void skill_validate_act_delay(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->delay, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "AfterCastActDelay"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int act_delay; + + if (libconfig->setting_lookup_int(t, lv, &act_delay) == CONFIG_TRUE) { + if (act_delay >= 0) + sk->delay[i] = act_delay; + else + ShowWarning("%s: Invalid after cast act delay %d specified in level %d for skill ID %d in %s! Must be greater than or equal to 0. Defaulting to 0...\n", + __func__, act_delay, i + 1, sk->nameid, conf->file); + } + } + + return; + } + + int act_delay; + + if (libconfig->setting_lookup_int(conf, "AfterCastActDelay", &act_delay) == CONFIG_TRUE) { + if (act_delay >= 0) + skill->level_set_value(sk->delay, act_delay); + else + ShowWarning("%s: Invalid after cast act delay %d specified for skill ID %d in %s! Must be greater than or equal to 0. Defaulting to 0...\n", + __func__, act_delay, sk->nameid, conf->file); + } +} + +/** + * Validates a skill's after cast walk delay when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the after cast walk delay should be set it. + * + **/ +static void skill_validate_walk_delay(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->walkdelay, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "AfterCastWalkDelay"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int walk_delay; + + if (libconfig->setting_lookup_int(t, lv, &walk_delay) == CONFIG_TRUE) { + if (walk_delay >= 0) + sk->walkdelay[i] = walk_delay; + else + ShowWarning("%s: Invalid after cast walk delay %d specified in level %d for skill ID %d in %s! Must be greater than or equal to 0. Defaulting to 0...\n", + __func__, walk_delay, i + 1, sk->nameid, conf->file); + } + } + + return; + } + + int walk_delay; + + if (libconfig->setting_lookup_int(conf, "AfterCastWalkDelay", &walk_delay) == CONFIG_TRUE) { + if (walk_delay >= 0) + skill->level_set_value(sk->walkdelay, walk_delay); + else + ShowWarning("%s: Invalid after cast walk delay %d specified for skill ID %d in %s! Must be greater than or equal to 0. Defaulting to 0...\n", + __func__, walk_delay, sk->nameid, conf->file); + } +} + +/** + * Validates a skill's stay duration when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the stay duration should be set it. + * + **/ +static void skill_validate_skill_data1(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->upkeep_time, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "SkillData1"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int skill_data1; + + if (libconfig->setting_lookup_int(t, lv, &skill_data1) == CONFIG_TRUE) { + if (skill_data1 >= INFINITE_DURATION) + sk->upkeep_time[i] = skill_data1; + else + ShowWarning("%s: Invalid stay duration %d specified in level %d for skill ID %d in %s! Must be greater than or equal to %d. Defaulting to 0...\n", + __func__, skill_data1, i + 1, sk->nameid, conf->file, INFINITE_DURATION); + } + } + + return; + } + + int skill_data1; + + if (libconfig->setting_lookup_int(conf, "SkillData1", &skill_data1) == CONFIG_TRUE) { + if (skill_data1 >= INFINITE_DURATION) + skill->level_set_value(sk->upkeep_time, skill_data1); + else + ShowWarning("%s: Invalid stay duration %d specified for skill ID %d in %s! Must be greater than or equal to %d. Defaulting to 0...\n", + __func__, skill_data1, sk->nameid, conf->file, INFINITE_DURATION); + } +} + +/** + * Validates a skill's effect duration when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the effect duration should be set it. + * + **/ +static void skill_validate_skill_data2(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->upkeep_time2, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "SkillData2"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int skill_data2; + + if (libconfig->setting_lookup_int(t, lv, &skill_data2) == CONFIG_TRUE) { + if (skill_data2 >= INFINITE_DURATION) + sk->upkeep_time2[i] = skill_data2; + else + ShowWarning("%s: Invalid effect duration %d specified in level %d for skill ID %d in %s! Must be greater than or equal to %d. Defaulting to 0...\n", + __func__, skill_data2, i + 1, sk->nameid, conf->file, INFINITE_DURATION); + } + } + + return; + } + + int skill_data2; + + if (libconfig->setting_lookup_int(conf, "SkillData2", &skill_data2) == CONFIG_TRUE) { + if (skill_data2 >= INFINITE_DURATION) + skill->level_set_value(sk->upkeep_time2, skill_data2); + else + ShowWarning("%s: Invalid effect duration %d specified for skill ID %d in %s! Must be greater than or equal to %d. Defaulting to 0...\n", + __func__, skill_data2, sk->nameid, conf->file, INFINITE_DURATION); + } +} + +/** + * Validates a skill's cooldown when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the cooldown should be set it. + * + **/ +static void skill_validate_cooldown(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->cooldown, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "CoolDown"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int cooldown; + + if (libconfig->setting_lookup_int(t, lv, &cooldown) == CONFIG_TRUE) { + if (cooldown >= 0) + sk->cooldown[i] = cooldown; + else + ShowWarning("%s: Invalid cooldown %d specified in level %d for skill ID %d in %s! Must be greater than or equal to 0. Defaulting to 0...\n", + __func__, cooldown, i + 1, sk->nameid, conf->file); + } + } + + return; + } + + int cooldown; + + if (libconfig->setting_lookup_int(conf, "CoolDown", &cooldown) == CONFIG_TRUE) { + if (cooldown >= 0) + skill->level_set_value(sk->cooldown, cooldown); + else + ShowWarning("%s: Invalid cooldown %d specified for skill ID %d in %s! Must be greater than or equal to 0. Defaulting to 0...\n", + __func__, cooldown, sk->nameid, conf->file); + } +} + +/** + * Validates a skill's fixed cast time when reading the skill DB. + * If RENEWAL_CAST is not defined, nothing is done. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the fixed cast time should be set it. + * + **/ +static void skill_validate_fixed_cast_time(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + +#ifdef RENEWAL_CAST + skill->level_set_value(sk->fixed_cast, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "FixedCastTime"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int fixed_cast_time; + + if (libconfig->setting_lookup_int(t, lv, &fixed_cast_time) == CONFIG_TRUE) { + if (fixed_cast_time >= INFINITE_DURATION) + sk->fixed_cast[i] = fixed_cast_time; + else + ShowWarning("%s: Invalid fixed cast time %d specified in level %d for skill ID %d in %s! Must be greater than or equal to %d. Defaulting to 0...\n", + __func__, fixed_cast_time, i + 1, sk->nameid, conf->file, INFINITE_DURATION); + } + } + + return; + } + + int fixed_cast_time; + + if (libconfig->setting_lookup_int(conf, "FixedCastTime", &fixed_cast_time) == CONFIG_TRUE) { + if (fixed_cast_time >= INFINITE_DURATION) + skill->level_set_value(sk->fixed_cast, fixed_cast_time); + else + ShowWarning("%s: Invalid fixed cast time %d specified for skill ID %d in %s! Must be greater than or equal to %d. Defaulting to 0...\n", + __func__, fixed_cast_time, sk->nameid, conf->file, INFINITE_DURATION); + } +#else +#ifndef RENEWAL /** Check pre-RE skill DB for FixedCastTime. **/ + if (libconfig->setting_get_member(conf, "FixedCastTime") != NULL) + ShowWarning("%s: Fixed cast time was specified for skill ID %d in %s without RENEWAL_CAST being defined! Skipping...\n", __func__, sk->nameid, conf->file); +#endif /** RENEWAL **/ +#endif /** RENEWAL_CAST **/ +} +/** + * Validates a skill's cast time or delay options when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the cast time or delay options should be set it. + * @param delay If true, the skill's delay options are validated, otherwise its cast time options. + * + **/ +static void skill_validate_castnodex(struct config_setting_t *conf, struct s_skill_db *sk, bool delay) +{ + nullpo_retv(conf); nullpo_retv(sk); - if ((t=libconfig->setting_get_member(conf, delay?"SkillDelayOptions":"CastTimeOptions")) && config_setting_is_group(t)) { - int j = 0, tmpopt = 0; - while ((tt = libconfig->setting_get_elem(t, j++)) && j < 4) { - const char *type = config_setting_name(tt); + + skill->level_set_value(delay ? sk->delaynodex : sk->castnodex, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, delay ? "SkillDelayOptions" : "CastTimeOptions"); + + if (t != NULL && config_setting_is_group(t)) { + struct config_setting_t *tt; + int i = 0; + int options = 0; + + while ((tt = libconfig->setting_get_elem(t, i++)) != NULL) { + const char *value = config_setting_name(tt); bool on = libconfig->setting_get_bool_real(tt); - if (strcmpi(type, "IgnoreDex") == 0) { - if (on) { - tmpopt |= 1<<0; - } else { - tmpopt &= ~(1<<0); - } - } else if (strcmpi(type, "IgnoreStatusEffect") == 0) { - if (on) { - tmpopt |= 1<<1; - } else { - tmpopt &= ~(1<<1); - } - } else if (strcmpi(type, "IgnoreItemBonus") == 0) { - if (on) { - tmpopt |= 1<<2; - } else { - tmpopt &= ~(1<<2); - } + if (strcmpi(value, "IgnoreDex") == 0) { + if (on) + options |= 1; + else + options &= ~1; + } else if (strcmpi(value, "IgnoreStatusEffect") == 0) { + if (on) + options |= 2; + else + options &= ~2; + } else if (strcmpi(value, "IgnoreItemBonus") == 0) { + if (on) + options |= 4; + else + options &= ~4; } else { - skilldb_invalid_error(type, config_setting_name(t), sk->nameid); - return; + const char *option_string = delay ? "skill delay" : "cast time"; + ShowWarning("%s: Invalid %s option %s specified for skill ID %d in %s! Skipping option...\n", + __func__, option_string, value, sk->nameid, conf->file); } + } + skill->level_set_value(delay ? sk->delaynodex : sk->castnodex, options); + } +} + +/** + * Validates a skill's HP cost when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the HP cost should be set it. + * + **/ +static void skill_validate_hp_cost(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->hp, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "HPCost"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int hp_cost; + + if (libconfig->setting_lookup_int(t, lv, &hp_cost) == CONFIG_TRUE) { + if (hp_cost >= 0 && hp_cost <= battle_config.max_hp) + sk->hp[i] = hp_cost; + else + ShowWarning("%s: Invalid HP cost %d specified in level %d for skill ID %d in %s! Minimum is 0, maximum is %d. Defaulting to 0...\n", + __func__, hp_cost, i + 1, sk->nameid, conf->file, battle_config.max_hp); + } } - skill->level_set_value(delay?sk->delaynodex:sk->castnodex, tmpopt); + + return; + } + + int hp_cost; + + if (libconfig->setting_lookup_int(conf, "HPCost", &hp_cost) == CONFIG_TRUE) { + if (hp_cost >= 0 && hp_cost <= battle_config.max_hp) + skill->level_set_value(sk->hp, hp_cost); + else + ShowWarning("%s: Invalid HP cost %d specified for skill ID %d in %s! Minimum is 0, maximum is %d. Defaulting to 0...\n", + __func__, hp_cost, sk->nameid, conf->file, battle_config.max_hp); } } /** - * Validates the "WeaponTypes" flag - * when parsing skill_db.conf - * @param *type const char, weapon type flag - * @param on boolean, switch for the flag - * @param *sk struct, pointer to s_skill_db - * @return void - */ + * Validates a skill's SP cost when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the SP cost should be set it. + * + **/ +static void skill_validate_sp_cost(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->sp, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "SPCost"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int sp_cost; + + if (libconfig->setting_lookup_int(t, lv, &sp_cost) == CONFIG_TRUE) { + if (sp_cost >= 0 && sp_cost <= battle_config.max_sp) + sk->sp[i] = sp_cost; + else + ShowWarning("%s: Invalid SP cost %d specified in level %d for skill ID %d in %s! Minimum is 0, maximum is %d. Defaulting to 0...\n", + __func__, sp_cost, i + 1, sk->nameid, conf->file, battle_config.max_sp); + } + } + + return; + } + + int sp_cost; + + if (libconfig->setting_lookup_int(conf, "SPCost", &sp_cost) == CONFIG_TRUE) { + if (sp_cost >= 0 && sp_cost <= battle_config.max_sp) + skill->level_set_value(sk->sp, sp_cost); + else + ShowWarning("%s: Invalid SP cost %d specified for skill ID %d in %s! Minimum is 0, maximum is %d. Defaulting to 0...\n", + __func__, sp_cost, sk->nameid, conf->file, battle_config.max_sp); + } +} + +/** + * Validates a skill's HP rate cost when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the HP rate cost should be set it. + * + **/ +static void skill_validate_hp_rate_cost(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->hp_rate, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "HPRateCost"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int hp_rate_cost; + + if (libconfig->setting_lookup_int(t, lv, &hp_rate_cost) == CONFIG_TRUE) { + if (hp_rate_cost >= -100 && hp_rate_cost <= 100) + sk->hp_rate[i] = hp_rate_cost; + else + ShowWarning("%s: Invalid HP rate cost %d specified in level %d for skill ID %d in %s! Minimum is -100, maximum is 100. Defaulting to 0...\n", + __func__, hp_rate_cost, i + 1, sk->nameid, conf->file); + } + } + + return; + } + + int hp_rate_cost; + + if (libconfig->setting_lookup_int(conf, "HPRateCost", &hp_rate_cost) == CONFIG_TRUE) { + if (hp_rate_cost >= -100 && hp_rate_cost <= 100) + skill->level_set_value(sk->hp_rate, hp_rate_cost); + else + ShowWarning("%s: Invalid HP rate cost %d specified for skill ID %d in %s! Minimum is -100, maximum is 100. Defaulting to 0...\n", + __func__, hp_rate_cost, sk->nameid, conf->file); + } +} + +/** + * Validates a skill's SP rate cost when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the SP rate cost should be set it. + * + **/ +static void skill_validate_sp_rate_cost(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->sp_rate, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "SPRateCost"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int sp_rate_cost; + + if (libconfig->setting_lookup_int(t, lv, &sp_rate_cost) == CONFIG_TRUE) { + if (sp_rate_cost >= -100 && sp_rate_cost <= 100) + sk->sp_rate[i] = sp_rate_cost; + else + ShowWarning("%s: Invalid SP rate cost %d specified in level %d for skill ID %d in %s! Minimum is -100, maximum is 100. Defaulting to 0...\n", + __func__, sp_rate_cost, i + 1, sk->nameid, conf->file); + } + } + + return; + } + + int sp_rate_cost; + + if (libconfig->setting_lookup_int(conf, "SPRateCost", &sp_rate_cost) == CONFIG_TRUE) { + if (sp_rate_cost >= -100 && sp_rate_cost <= 100) + skill->level_set_value(sk->sp_rate, sp_rate_cost); + else + ShowWarning("%s: Invalid SP rate cost %d specified for skill ID %d in %s! Minimum is -100, maximum is 100. Defaulting to 0...\n", + __func__, sp_rate_cost, sk->nameid, conf->file); + } +} + +/** + * Validates a skill's maximum HP trigger when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the maximum HP trigger should be set it. + * + **/ +static void skill_validate_max_hp_trigger(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->mhp, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "MaxHPTrigger"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int max_hp_trigger; + + if (libconfig->setting_lookup_int(t, lv, &max_hp_trigger) == CONFIG_TRUE) { + if (max_hp_trigger >= 0 && max_hp_trigger <= 100) + sk->mhp[i] = max_hp_trigger; + else + ShowWarning("%s: Invalid maximum HP trigger %d specified in level %d for skill ID %d in %s! Minimum is 0, maximum is 100. Defaulting to 0...\n", + __func__, max_hp_trigger, i + 1, sk->nameid, conf->file); + } + } + + return; + } + + int max_hp_trigger; + + if (libconfig->setting_lookup_int(conf, "MaxHPTrigger", &max_hp_trigger) == CONFIG_TRUE) { + if (max_hp_trigger >= 0 && max_hp_trigger <= 100) + skill->level_set_value(sk->mhp, max_hp_trigger); + else + ShowWarning("%s: Invalid maximum HP trigger %d specified for skill ID %d in %s! Minimum is 0, maximum is 100. Defaulting to 0...\n", + __func__, max_hp_trigger, sk->nameid, conf->file); + } +} + +/** + * Validates a skill's maximum SP trigger when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the maximum SP trigger should be set it. + * + **/ +static void skill_validate_max_sp_trigger(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->msp, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "MaxSPTrigger"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int max_sp_trigger; + + if (libconfig->setting_lookup_int(t, lv, &max_sp_trigger) == CONFIG_TRUE) { + if (max_sp_trigger >= 0 && max_sp_trigger <= 100) + sk->msp[i] = max_sp_trigger; + else + ShowWarning("%s: Invalid maximum SP trigger %d specified in level %d for skill ID %d in %s! Minimum is 0, maximum is 100. Defaulting to 0...\n", + __func__, max_sp_trigger, i + 1, sk->nameid, conf->file); + } + } + + return; + } + + int max_sp_trigger; + + if (libconfig->setting_lookup_int(conf, "MaxSPTrigger", &max_sp_trigger) == CONFIG_TRUE) { + if (max_sp_trigger >= 0 && max_sp_trigger <= 100) + skill->level_set_value(sk->msp, max_sp_trigger); + else + ShowWarning("%s: Invalid maximum SP trigger %d specified for skill ID %d in %s! Minimum is 0, maximum is 100. Defaulting to 0...\n", + __func__, max_sp_trigger, sk->nameid, conf->file); + } +} + +/** + * Validates a skill's Zeny cost when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the Zeny cost should be set it. + * + **/ +static void skill_validate_zeny_cost(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->zeny, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "ZenyCost"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int zeny_cost; + + if (libconfig->setting_lookup_int(t, lv, &zeny_cost) == CONFIG_TRUE) { + if (zeny_cost >= 0 && zeny_cost <= MAX_ZENY) + sk->zeny[i] = zeny_cost; + else + ShowWarning("%s: Invalid Zeny cost %d specified in level %d for skill ID %d in %s! Minimum is 0, maximum is %d. Defaulting to 0...\n", + __func__, zeny_cost, i + 1, sk->nameid, conf->file, MAX_ZENY); + } + } + + return; + } + + int zeny_cost; + + if (libconfig->setting_lookup_int(conf, "ZenyCost", &zeny_cost) == CONFIG_TRUE) { + if (zeny_cost >= 0 && zeny_cost <= MAX_ZENY) + skill->level_set_value(sk->zeny, zeny_cost); + else + ShowWarning("%s: Invalid Zeny cost %d specified for skill ID %d in %s! Minimum is 0, maximum is %d. Defaulting to 0...\n", + __func__, zeny_cost, sk->nameid, conf->file, MAX_ZENY); + } +} + +/** + * Validates a single weapon type when reading the skill DB. + * + * @param type The weapon type to validate. + * @param on Whether the weapon type is required for the skill. + * @param sk The s_skill_db struct where the weapon type should be set it. + * @return 0 if the passed weapon type is valid, otherwise 1. + * + **/ static int skill_validate_weapontype_sub(const char *type, bool on, struct s_skill_db *sk) { - nullpo_ret(sk); + nullpo_retr(1, type); + nullpo_retr(1, sk); + if (strcmpi(type, "NoWeapon") == 0) { - if (on) { - sk->weapon |= 1<<W_FIST; - } else { - sk->weapon &= ~(1<<W_FIST); - } + if (on) + sk->weapon |= (1 << W_FIST); + else + sk->weapon &= ~(1 << W_FIST); } else if (strcmpi(type, "Daggers") == 0) { - if (on) { - sk->weapon |= 1<<W_DAGGER; - } else { - sk->weapon &= ~(1<<W_DAGGER); - } + if (on) + sk->weapon |= (1 << W_DAGGER); + else + sk->weapon &= ~(1 << W_DAGGER); } else if (strcmpi(type, "1HSwords") == 0) { - if (on) { - sk->weapon |= 1<<W_1HSWORD; - } else { - sk->weapon &= ~(1<<W_1HSWORD); - } + if (on) + sk->weapon |= (1 << W_1HSWORD); + else + sk->weapon &= ~(1 << W_1HSWORD); } else if (strcmpi(type, "2HSwords") == 0) { - if (on) { - sk->weapon |= 1<<W_2HSWORD; - } else { - sk->weapon &= ~(1<<W_2HSWORD); - } + if (on) + sk->weapon |= (1 << W_2HSWORD); + else + sk->weapon &= ~(1 << W_2HSWORD); } else if (strcmpi(type, "1HSpears") == 0) { - if (on) { - sk->weapon |= 1<<W_1HSPEAR; - } else { - sk->weapon &= ~(1<<W_1HSPEAR); - } + if (on) + sk->weapon |= (1 << W_1HSPEAR); + else + sk->weapon &= ~(1 << W_1HSPEAR); } else if (strcmpi(type, "2HSpears") == 0) { - if (on) { - sk->weapon |= 1<<W_2HSPEAR; - } else { - sk->weapon &= ~(1<<W_2HSPEAR); - } + if (on) + sk->weapon |= (1 << W_2HSPEAR); + else + sk->weapon &= ~(1 << W_2HSPEAR); } else if (strcmpi(type, "1HAxes") == 0) { - if (on) { - sk->weapon |= 1<<W_1HAXE; - } else { - sk->weapon &= ~(1<<W_1HAXE); - } + if (on) + sk->weapon |= (1 << W_1HAXE); + else + sk->weapon &= ~(1 << W_1HAXE); } else if (strcmpi(type, "2HAxes") == 0) { - if (on) { - sk->weapon |= 1<<W_2HAXE; - } else { - sk->weapon &= ~(1<<W_2HAXE); - } + if (on) + sk->weapon |= (1 << W_2HAXE); + else + sk->weapon &= ~(1 << W_2HAXE); } else if (strcmpi(type, "Maces") == 0) { - if (on) { - sk->weapon |= 1<<W_MACE; - } else { - sk->weapon &= ~(1<<W_MACE); - } + if (on) + sk->weapon |= (1 << W_MACE); + else + sk->weapon &= ~(1 << W_MACE); } else if (strcmpi(type, "2HMaces") == 0) { - if (on) { - sk->weapon |= 1<<W_2HMACE; - } else { - sk->weapon &= ~(1<<W_2HMACE); - } + if (on) + sk->weapon |= (1 << W_2HMACE); + else + sk->weapon &= ~(1 << W_2HMACE); } else if (strcmpi(type, "Staves") == 0) { - if (on) { - sk->weapon |= 1<<W_STAFF; - } else { - sk->weapon &= ~(1<<W_STAFF); - } + if (on) + sk->weapon |= (1 << W_STAFF); + else + sk->weapon &= ~(1 << W_STAFF); } else if (strcmpi(type, "Bows") == 0) { - if (on) { - sk->weapon |= 1<<W_BOW; - } else { - sk->weapon &= ~(1<<W_BOW); - } + if (on) + sk->weapon |= (1 << W_BOW); + else + sk->weapon &= ~(1 << W_BOW); } else if (strcmpi(type, "Knuckles") == 0) { - if (on) { - sk->weapon |= 1<<W_KNUCKLE; - } else { - sk->weapon &= ~(1<<W_KNUCKLE); - } + if (on) + sk->weapon |= (1 << W_KNUCKLE); + else + sk->weapon &= ~(1 << W_KNUCKLE); } else if (strcmpi(type, "Instruments") == 0) { - if (on) { - sk->weapon |= 1<<W_MUSICAL; - } else { - sk->weapon &= ~(1<<W_MUSICAL); - } + if (on) + sk->weapon |= (1 << W_MUSICAL); + else + sk->weapon &= ~(1 << W_MUSICAL); } else if (strcmpi(type, "Whips") == 0) { - if (on) { - sk->weapon |= 1<<W_WHIP; - } else { - sk->weapon &= ~(1<<W_WHIP); - } + if (on) + sk->weapon |= (1 << W_WHIP); + else + sk->weapon &= ~(1 << W_WHIP); } else if (strcmpi(type, "Books") == 0) { - if (on) { - sk->weapon |= 1<<W_BOOK; - } else { - sk->weapon &= ~(1<<W_BOOK); - } + if (on) + sk->weapon |= (1 << W_BOOK); + else + sk->weapon &= ~(1 << W_BOOK); } else if (strcmpi(type, "Katars") == 0) { - if (on) { - sk->weapon |= 1<<W_KATAR; - } else { - sk->weapon &= ~(1<<W_KATAR); - } + if (on) + sk->weapon |= (1 << W_KATAR); + else + sk->weapon &= ~(1 << W_KATAR); } else if (strcmpi(type, "Revolvers") == 0) { - if (on) { - sk->weapon |= 1<<W_REVOLVER; - } else { - sk->weapon &= ~(1<<W_REVOLVER); - } + if (on) + sk->weapon |= (1 << W_REVOLVER); + else + sk->weapon &= ~(1 << W_REVOLVER); } else if (strcmpi(type, "Rifles") == 0) { - if (on) { - sk->weapon |= 1<<W_RIFLE; - } else { - sk->weapon &= ~(1<<W_RIFLE); - } + if (on) + sk->weapon |= (1 << W_RIFLE); + else + sk->weapon &= ~(1 << W_RIFLE); } else if (strcmpi(type, "GatlingGuns") == 0) { - if (on) { - sk->weapon |= 1<<W_GATLING; - } else { - sk->weapon &= ~(1<<W_GATLING); - } + if (on) + sk->weapon |= (1 << W_GATLING); + else + sk->weapon &= ~(1 << W_GATLING); } else if (strcmpi(type, "Shotguns") == 0) { - if (on) { - sk->weapon |= 1<<W_SHOTGUN; - } else { - sk->weapon &= ~(1<<W_SHOTGUN); - } + if (on) + sk->weapon |= (1 << W_SHOTGUN); + else + sk->weapon &= ~(1 << W_SHOTGUN); } else if (strcmpi(type, "GrenadeLaunchers") == 0) { - if (on) { - sk->weapon |= 1<<W_GRENADE; - } else { - sk->weapon &= ~(1<<W_GRENADE); - } + if (on) + sk->weapon |= (1 << W_GRENADE); + else + sk->weapon &= ~(1 << W_GRENADE); } else if (strcmpi(type, "FuumaShurikens") == 0) { - if (on) { - sk->weapon |= 1<<W_HUUMA; - } else { - sk->weapon &= ~(1<<W_HUUMA); - } + if (on) + sk->weapon |= (1 << W_HUUMA); + else + sk->weapon &= ~(1 << W_HUUMA); } else if (strcmpi(type, "2HStaves") == 0) { - if (on) { - sk->weapon |= 1<<W_2HSTAFF; - } else { - sk->weapon &= ~(1<<W_2HSTAFF); - } - } - /* MAX_SINGLE_WEAPON_TYPE excluded */ - else if (strcmpi(type, "DWDaggers") == 0) { - if (on) { - sk->weapon |= 1<<W_DOUBLE_DD; - } else { - sk->weapon &= ~(1<<W_DOUBLE_DD); - } + if (on) + sk->weapon |= (1 << W_2HSTAFF); + else + sk->weapon &= ~(1 << W_2HSTAFF); + } else if (strcmpi(type, "DWDaggers") == 0) { + if (on) + sk->weapon |= (1 << W_DOUBLE_DD); + else + sk->weapon &= ~(1 << W_DOUBLE_DD); } else if (strcmpi(type, "DWSwords") == 0) { - if (on) { - sk->weapon |= 1<<W_DOUBLE_SS; - } else { - sk->weapon &= ~(1<<W_DOUBLE_SS); - } + if (on) + sk->weapon |= (1 << W_DOUBLE_SS); + else + sk->weapon &= ~(1 << W_DOUBLE_SS); } else if (strcmpi(type, "DWAxes") == 0) { - if (on) { - sk->weapon |= 1<<W_DOUBLE_AA; - } else { - sk->weapon &= ~(1<<W_DOUBLE_AA); - } + if (on) + sk->weapon |= (1 << W_DOUBLE_AA); + else + sk->weapon &= ~(1 << W_DOUBLE_AA); } else if (strcmpi(type, "DWDaggerSword") == 0) { - if (on) { - sk->weapon |= 1<<W_DOUBLE_DS; - } else { - sk->weapon &= ~(1<<W_DOUBLE_DS); - } + if (on) + sk->weapon |= (1 << W_DOUBLE_DS); + else + sk->weapon &= ~(1 << W_DOUBLE_DS); } else if (strcmpi(type, "DWDaggerAxe") == 0) { - if (on) { - sk->weapon |= 1<<W_DOUBLE_DA; - } else { - sk->weapon &= ~(1<<W_DOUBLE_DA); - } + if (on) + sk->weapon |= (1 << W_DOUBLE_DA); + else + sk->weapon &= ~(1 << W_DOUBLE_DA); } else if (strcmpi(type, "DWSwordAxe") == 0) { - if (on) { - sk->weapon |= 1<<W_DOUBLE_SA; - } else { - sk->weapon &= ~(1<<W_DOUBLE_SA); - } + if (on) + sk->weapon |= (1 << W_DOUBLE_SA); + else + sk->weapon &= ~(1 << W_DOUBLE_SA); } else if (strcmpi(type, "All") == 0) { sk->weapon = 0; } else { - ShowError("Item %d. Unknown weapon type %s\n", sk->nameid, type); - return 1; // invalid type + return 1; } return 0; } /** - * Validates "WeaponTypes" - * when parsing skill_db.conf - * @param conf struct, pointer to the skill configuration - * @param sk struct, struct, pointer to s_skill_db - * @return (void) - */ + * Validates a skill's required weapon types when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the required weapon types should be set it. + * + **/ static void skill_validate_weapontype(struct config_setting_t *conf, struct s_skill_db *sk) { - struct config_setting_t *tt = NULL; - const char *type = NULL; - + nullpo_retv(conf); nullpo_retv(sk); - if ((tt = libconfig->setting_get_member(conf, "WeaponTypes")) && config_setting_is_group(tt)) { - int j = 0; - struct config_setting_t *wpt = NULL; - while ((wpt = libconfig->setting_get_elem(tt, j++)) != NULL) { - if (skill->validate_weapontype_sub(config_setting_name(wpt), libconfig->setting_get_bool_real(wpt), sk)) - skilldb_invalid_error(config_setting_name(wpt), config_setting_name(tt), sk->nameid); + + sk->weapon = 0; + + struct config_setting_t *t = libconfig->setting_get_member(conf, "WeaponTypes"); + + if (t != NULL && config_setting_is_group(t)) { + struct config_setting_t *tt; + int i = 0; + + while ((tt = libconfig->setting_get_elem(t, i++)) != NULL) { + bool on = libconfig->setting_get_bool_real(tt); + + if (skill->validate_weapontype_sub(config_setting_name(tt), on, sk) != 0) + ShowWarning("%s: Invalid required weapon type %s specified for skill ID %d in %s! Skipping type...\n", + __func__, config_setting_name(tt), sk->nameid, conf->file); } - } else if (libconfig->setting_lookup_string(conf, "WeaponTypes", &type)) { - if (skill->validate_weapontype_sub(type, true, sk)) - skilldb_invalid_error(type, "WeaponTypes", sk->nameid); + + return; + } + + const char *weapon_type; + + if (libconfig->setting_lookup_string(conf, "WeaponTypes", &weapon_type) == CONFIG_TRUE) { + if (skill->validate_weapontype_sub(weapon_type, true, sk) != 0) + ShowWarning("%s: Invalid required weapon type %s specified for skill ID %d in %s! Defaulting to All...\n", + __func__, weapon_type, sk->nameid, conf->file); } } /** - * Validates the "AmmoTypes" flag - * when parsing skill_db.conf - * @param type string, ammo type flag - * @param on boolean, switch for the flag - * @param sk struct, pointer to s_skill_db - * @return void - */ + * Validates a single ammunition type when reading the skill DB. + * + * @param type The ammunition type to validate. + * @param on Whether the ammunition type is required for the skill. + * @param sk The s_skill_db struct where the ammunition type should be set it. + * @return 0 if the passed ammunition type is valid, otherwise 1. + * + **/ static int skill_validate_ammotype_sub(const char *type, bool on, struct s_skill_db *sk) { - nullpo_ret(sk); + nullpo_retr(1, type); + nullpo_retr(1, sk); + if (strcmpi(type, "A_ARROW") == 0) { - if (on) { - sk->ammo |= 1<<A_ARROW; - } else { - sk->ammo &= ~(1<<A_ARROW); - } + if (on) + sk->ammo |= (1 << A_ARROW); + else + sk->ammo &= ~(1 << A_ARROW); } else if (strcmpi(type, "A_DAGGER") == 0) { - if (on) { - sk->ammo |= 1<<A_DAGGER; - } else { - sk->ammo &= ~(1<<A_DAGGER); - } + if (on) + sk->ammo |= (1 << A_DAGGER); + else + sk->ammo &= ~(1 << A_DAGGER); } else if (strcmpi(type, "A_BULLET") == 0) { - if (on) { - sk->ammo |= 1<<A_BULLET; - } else { - sk->ammo &= ~(1<<A_BULLET); - } + if (on) + sk->ammo |= (1 << A_BULLET); + else + sk->ammo &= ~(1 << A_BULLET); } else if (strcmpi(type, "A_SHELL") == 0) { - if (on) { - sk->ammo |= 1<<A_SHELL; - } else { - sk->ammo &= ~(1<<A_SHELL); - } + if (on) + sk->ammo |= (1 << A_SHELL); + else + sk->ammo &= ~(1 << A_SHELL); } else if (strcmpi(type, "A_GRENADE") == 0) { - if (on) { - sk->ammo |= 1<<A_GRENADE; - } else { - sk->ammo &= ~(1<<A_GRENADE); - } + if (on) + sk->ammo |= (1 << A_GRENADE); + else + sk->ammo &= ~(1 << A_GRENADE); } else if (strcmpi(type, "A_SHURIKEN") == 0) { - if (on) { - sk->ammo |= 1<<A_SHURIKEN; - } else { - sk->ammo &= ~(1<<A_SHURIKEN); - } + if (on) + sk->ammo |= (1 << A_SHURIKEN); + else + sk->ammo &= ~(1 << A_SHURIKEN); } else if (strcmpi(type, "A_KUNAI") == 0) { - if (on) { - sk->ammo |= 1<<A_KUNAI; - } else { - sk->ammo &= ~(1<<A_KUNAI); - } + if (on) + sk->ammo |= (1 << A_KUNAI); + else + sk->ammo &= ~(1 << A_KUNAI); } else if (strcmpi(type, "A_CANNONBALL") == 0) { - if (on) { - sk->ammo |= 1<<A_CANNONBALL; - } else { - sk->ammo &= ~(1<<A_CANNONBALL); - } + if (on) + sk->ammo |= (1 << A_CANNONBALL); + else + sk->ammo &= ~(1 << A_CANNONBALL); } else if (strcmpi(type, "A_THROWWEAPON") == 0) { - if (on) { - sk->ammo |= 1<<A_THROWWEAPON; - } else { - sk->ammo &= ~(1<<A_THROWWEAPON); - } + if (on) + sk->ammo |= (1 << A_THROWWEAPON); + else + sk->ammo &= ~(1 << A_THROWWEAPON); } else if (strcmpi(type, "All") == 0) { - if (on) { + if (on) sk->ammo = 0xFFFFFFFF; - } else { + else sk->ammo = 0; - } } else { - return 1; // Invalid Entry + return 1; } return 0; } /** - * Validates the "AmmoTypes" flag - * when parsing skill_db.conf - * @param conf pointer to the skill configuration - * @param sk struct, pointer to s_skill_db - * @return void - */ + * Validates a skill's required ammunition types when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the required ammunition types should be set it. + * + **/ static void skill_validate_ammotype(struct config_setting_t *conf, struct s_skill_db *sk) { - struct config_setting_t *tt = NULL; - const char *tstr = NULL; + nullpo_retv(conf); + nullpo_retv(sk); + + sk->ammo = 0; + struct config_setting_t *t = libconfig->setting_get_member(conf, "AmmoTypes"); + + if (t != NULL && config_setting_is_group(t)) { + struct config_setting_t *tt; + int i = 0; + + while ((tt = libconfig->setting_get_elem(t, i++)) != NULL) { + bool on = libconfig->setting_get_bool_real(tt); + + if (skill->validate_ammotype_sub(config_setting_name(tt), on, sk) != 0) + ShowWarning("%s: Invalid required ammunition type %s specified for skill ID %d in %s! Skipping type...\n", + __func__, config_setting_name(tt), sk->nameid, conf->file); + } + } + + const char *ammo_type; + + if (libconfig->setting_lookup_string(conf, "AmmoTypes", &ammo_type) == CONFIG_TRUE) { + if (skill->validate_ammotype_sub(ammo_type, true, sk) != 0) + ShowWarning("%s: Invalid required ammunition type %s specified for skill ID %d in %s! Defaulting to None...\n", + __func__, ammo_type, sk->nameid, conf->file); + } +} + +/** + * Validates a skill's required ammunition amount when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the required ammunition amount should be set it. + * + **/ +static void skill_validate_ammo_amount(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); nullpo_retv(sk); - if ((tt = libconfig->setting_get_member(conf, "AmmoTypes")) && config_setting_is_group(tt)) { - int j = 0; - struct config_setting_t *amt = { 0 }; - while ((amt = libconfig->setting_get_elem(tt, j++))) { - if (skill->validate_ammotype_sub(config_setting_name(amt), libconfig->setting_get_bool_real(amt), sk)) - skilldb_invalid_error(config_setting_name(amt), config_setting_name(tt), sk->nameid); + + skill->level_set_value(sk->ammo_qty, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "AmmoAmount"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int ammo_amount; + + if (libconfig->setting_lookup_int(t, lv, &ammo_amount) == CONFIG_TRUE) { + if (ammo_amount >= 0 && ammo_amount <= MAX_AMOUNT) + sk->ammo_qty[i] = ammo_amount; + else + ShowWarning("%s: Invalid required ammunition amount %d specified in level %d for skill ID %d in %s! Minimum is 0, maximum is %d. Defaulting to 0...\n", + __func__, ammo_amount, i + 1, sk->nameid, conf->file, MAX_AMOUNT); + } } - } else if( libconfig->setting_lookup_string(conf, "AmmoTypes", &tstr)) { - if (skill->validate_ammotype_sub(tstr, true, sk)) - skilldb_invalid_error(tstr, "AmmoTypes", sk->nameid); + + return; + } + + int ammo_amount; + + if (libconfig->setting_lookup_int(conf, "AmmoAmount", &ammo_amount) == CONFIG_TRUE) { + if (ammo_amount >= 0 && ammo_amount <= MAX_AMOUNT) + skill->level_set_value(sk->ammo_qty, ammo_amount); + else + ShowWarning("%s: Invalid required ammunition amount %d specified for skill ID %d in %s! Minimum is 0, maximum is %d. Defaulting to 0...\n", + __func__, ammo_amount, sk->nameid, conf->file, MAX_AMOUNT); } } /** - * Validates the "State" flag - * when parsing skill_db.conf - * @param conf struct, pointer to the skill configuration - * @param sk struct, pointer to s_skill_db - * @return void - */ + * Validates a single required state when reading the skill DB. + * + * @param state The required state to validate. + * @return A number greater than or equal to 0 if the passed required state is valid, otherwise -1. + * + **/ +static int skill_validate_state_sub(const char *state) +{ + nullpo_retr(-1, state); + + int ret_val = ST_NONE; + + if (strcmpi(state, "Hiding") == 0) + ret_val = ST_HIDING; + else if (strcmpi(state, "Cloaking") == 0) + ret_val = ST_CLOAKING; + else if (strcmpi(state, "Hidden") == 0) + ret_val = ST_HIDDEN; + else if (strcmpi(state, "Riding") == 0) + ret_val = ST_RIDING; + else if (strcmpi(state, "Falcon") == 0) + ret_val = ST_FALCON; + else if (strcmpi(state, "Cart") == 0) + ret_val = ST_CART; + else if (strcmpi(state, "Shield") == 0) + ret_val = ST_SHIELD; + else if (strcmpi(state, "Sight") == 0) + ret_val = ST_SIGHT; + else if (strcmpi(state, "ExplosionSpirits") == 0) + ret_val = ST_EXPLOSIONSPIRITS; + else if (strcmpi(state, "CartBoost") == 0) + ret_val = ST_CARTBOOST; + else if (strcmpi(state, "NotOverWeight") == 0) + ret_val = ST_RECOV_WEIGHT_RATE; + else if (strcmpi(state, "Moveable") == 0) + ret_val = ST_MOVE_ENABLE; + else if (strcmpi(state, "InWater") == 0) + ret_val = ST_WATER; + else if (strcmpi(state, "Dragon") == 0) + ret_val = ST_RIDINGDRAGON; + else if (strcmpi(state, "Warg") == 0) + ret_val = ST_WUG; + else if (strcmpi(state, "RidingWarg") == 0) + ret_val = ST_RIDINGWUG; + else if (strcmpi(state, "MadoGear") == 0) + ret_val = ST_MADO; + else if (strcmpi(state, "ElementalSpirit") == 0) + ret_val = ST_ELEMENTALSPIRIT; + else if (strcmpi(state, "PoisonWeapon") == 0) + ret_val = ST_POISONINGWEAPON; + else if (strcmpi(state, "RollingCutter") == 0) + ret_val = ST_ROLLINGCUTTER; + else if (strcmpi(state, "MH_Fighting") == 0) + ret_val = ST_MH_FIGHTING; + else if (strcmpi(state, "MH_Grappling") == 0) + ret_val = ST_MH_GRAPPLING; + else if (strcmpi(state, "Peco") == 0) + ret_val = ST_PECO; + else if (strcmpi(state, "None") != 0) + ret_val = -1; + + return ret_val; +} + +/** + * Validates a skill's required states when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the required states should be set it. + * + **/ static void skill_validate_state(struct config_setting_t *conf, struct s_skill_db *sk) { - const char *type = NULL; + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->state, ST_NONE); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "State"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + const char *state; + if (libconfig->setting_lookup_string(t, lv, &state) == CONFIG_TRUE) { + int sta = skill->validate_state_sub(state); + + if (sta > ST_NONE) + sk->state[i] = sta; + else if (sta == -1) + ShowWarning("%s: Invalid required state %s specified in level %d for skill ID %d in %s! Defaulting to None...\n", + __func__, state, i + 1, sk->nameid, conf->file); + } + } + + return; + } + + const char *state; + + if (libconfig->setting_lookup_string(conf, "State", &state) == CONFIG_TRUE) { + int sta = skill->validate_state_sub(state); + + if (sta > ST_NONE) + skill->level_set_value(sk->state, sta); + else if (sta == -1) + ShowWarning("%s: Invalid required state %s specified for skill ID %d in %s! Defaulting to None...\n", + __func__, state, sk->nameid, conf->file); + } +} + +/** + * Validates a skill's Spirit Sphere cost when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the Spirit Sphere cost should be set it. + * + **/ +static void skill_validate_spirit_sphere_cost(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); nullpo_retv(sk); - if (libconfig->setting_lookup_string(conf, "State", &type) && strcmpi(type,"None") != ST_NONE) { - if ( strcmpi(type,"Hiding") == 0 ) sk->state = ST_HIDING; - else if (strcmpi(type,"Cloaking") == 0 ) sk->state = ST_CLOAKING; - else if (strcmpi(type,"Hidden") == 0 ) sk->state = ST_HIDDEN; - else if (strcmpi(type,"Riding") == 0 ) sk->state = ST_RIDING; - else if (strcmpi(type,"Falcon") == 0 ) sk->state = ST_FALCON; - else if (strcmpi(type,"Cart") == 0 ) sk->state = ST_CART; - else if (strcmpi(type,"Shield") == 0 ) sk->state = ST_SHIELD; - else if (strcmpi(type,"Sight") == 0 ) sk->state = ST_SIGHT; - else if (strcmpi(type,"ExplosionSpirits") == 0 ) sk->state = ST_EXPLOSIONSPIRITS; - else if (strcmpi(type,"CartBoost") == 0 ) sk->state = ST_CARTBOOST; - else if (strcmpi(type,"NotOverWeight") == 0 ) sk->state = ST_RECOV_WEIGHT_RATE; - else if (strcmpi(type,"Moveable") == 0 ) sk->state = ST_MOVE_ENABLE; - else if (strcmpi(type,"InWater") == 0 ) sk->state = ST_WATER; - else if (strcmpi(type,"Dragon") == 0 ) sk->state = ST_RIDINGDRAGON; - else if (strcmpi(type,"Warg") == 0 ) sk->state = ST_WUG; - else if (strcmpi(type,"RidingWarg") == 0 ) sk->state = ST_RIDINGWUG; - else if (strcmpi(type,"MadoGear") == 0 ) sk->state = ST_MADO; - else if (strcmpi(type,"ElementalSpirit") == 0 ) sk->state = ST_ELEMENTALSPIRIT; - else if (strcmpi(type,"PoisonWeapon") == 0 ) sk->state = ST_POISONINGWEAPON; - else if (strcmpi(type,"RollingCutter") == 0 ) sk->state = ST_ROLLINGCUTTER; - else if (strcmpi(type,"MH_Fighting") == 0 ) sk->state = ST_MH_FIGHTING; - else if (strcmpi(type,"MH_Grappling") == 0 ) sk->state = ST_MH_GRAPPLING; - else if (strcmpi(type,"Peco") == 0 ) sk->state = ST_PECO; + + skill->level_set_value(sk->spiritball, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "SpiritSphereCost"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int spirit_sphere_cost; + + if (libconfig->setting_lookup_int(t, lv, &spirit_sphere_cost) == CONFIG_TRUE) { + if (spirit_sphere_cost >= 0 && spirit_sphere_cost <= MAX_SPIRITBALL) + sk->spiritball[i] = spirit_sphere_cost; + else + ShowWarning("%s: Invalid Spirit Sphere cost %d specified in level %d for skill ID %d in %s! Minimum is 0, maximum is %d. Defaulting to 0...\n", + __func__, spirit_sphere_cost, i + 1, sk->nameid, conf->file, MAX_SPIRITBALL); + } + } + + return; + } + + int spirit_sphere_cost; + + if (libconfig->setting_lookup_int(conf, "SpiritSphereCost", &spirit_sphere_cost) == CONFIG_TRUE) { + if (spirit_sphere_cost >= 0 && spirit_sphere_cost <= MAX_SPIRITBALL) + skill->level_set_value(sk->spiritball, spirit_sphere_cost); else - skilldb_invalid_error(type, "State", sk->nameid); + ShowWarning("%s: Invalid Spirit Sphere cost %d specified for skill ID %d in %s! Minimum is 0, maximum is %d. Defaulting to 0...\n", + __func__, spirit_sphere_cost, sk->nameid, conf->file, MAX_SPIRITBALL); } } /** - * Validates the "Items" flag - * when parsing skill_db.conf - * @param conf struct, pointer to the skill configuration - * @param sk struct, pointer to s_skill_db - * @return void - */ + * Validates a skill's required items amounts when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the required items amounts should be set it. + * + **/ +static void skill_validate_item_requirements_sub_item_amount(struct config_setting_t *conf, struct s_skill_db *sk, int item_index) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + for (int i = 0; i < MAX_SKILL_LEVEL; i++) + sk->req_items.item[item_index].amount[i] = 0; + + if (config_setting_is_group(conf)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int amount; + + if (libconfig->setting_lookup_int(conf, lv, &amount) == CONFIG_TRUE) { + if (amount >= 0 && amount <= MAX_AMOUNT) + sk->req_items.item[item_index].amount[i] = amount; + else + ShowWarning("%s: Invalid required item amount %d specified in level %d for skill ID %d in %s! Minimum is 0, maximum is %d. Defaulting to 0...\n", + __func__, amount, i + 1, sk->nameid, conf->file, MAX_AMOUNT); + } else { + // Items is not required for this skill level. (Not even in inventory!) + sk->req_items.item[item_index].amount[i] = -1; + } + } + + return; + } + + int amount = libconfig->setting_get_int(conf); + + if (amount >= 0 && amount <= MAX_AMOUNT) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) + sk->req_items.item[item_index].amount[i] = amount; + } else { + ShowWarning("%s: Invalid required item amount %d specified for skill ID %d in %s! Minimum is 0, maximum is %d. Defaulting to 0...\n", + __func__, amount, sk->nameid, conf->file, MAX_AMOUNT); + } +} + +/** + * Validates a skill's required items when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the required items should be set it. + * + **/ +static void skill_validate_item_requirements_sub_items(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + for (int i = 0; i < MAX_SKILL_ITEM_REQUIRE; i++) { + sk->req_items.item[i].id = 0; + + for (int j = 0; j < MAX_SKILL_LEVEL; j++) + sk->req_items.item[i].amount[j] = 0; + } + + int item_index = 0; + int count = libconfig->setting_length(conf); + + for (int i = 0; i < count; i++) { + struct config_setting_t *t = libconfig->setting_get_elem(conf, i); + + if (t != NULL && strcasecmp(config_setting_name(t), "Any") != 0) { + if (item_index >= MAX_SKILL_ITEM_REQUIRE) { + ShowWarning("%s: Too many required items specified for skill ID %d in %s! Skipping item %s...\n", + __func__, sk->nameid, conf->file, config_setting_name(t)); + continue; + } + + int item_id = skill->validate_requirements_item_name(config_setting_name(t)); + + if (item_id == 0) { + ShowWarning("%s: Invalid required item %s specified for skill ID %d in %s! Skipping item...\n", + __func__, config_setting_name(t), sk->nameid, conf->file); + continue; + } + + int j; + + ARR_FIND(0, MAX_SKILL_ITEM_REQUIRE, j, sk->req_items.item[j].id == item_id); + + if (j < MAX_SKILL_ITEM_REQUIRE) { + ShowWarning("%s: Duplicate required item %s specified for skill ID %d in %s! Skipping item...\n", + __func__, config_setting_name(t), sk->nameid, conf->file); + continue; + } + + sk->req_items.item[item_index].id = item_id; + skill->validate_item_requirements_sub_item_amount(t, sk, item_index); + item_index++; + } + } +} + +/** + * Validates a skill's required items any-flag when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the required items any-flag should be set it. + * + **/ +static void skill_validate_item_requirements_sub_any_flag(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + for (int i = 0; i < MAX_SKILL_LEVEL; i++) + sk->req_items.any[i] = false; + + struct config_setting_t *t = libconfig->setting_get_member(conf, "Any"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int any_flag; + + if (libconfig->setting_lookup_bool(t, lv, &any_flag) == CONFIG_TRUE) + sk->req_items.any[i] = (any_flag != 0); + } + + return; + } + + int any_flag; + + if (libconfig->setting_lookup_bool(conf, "Any", &any_flag) == CONFIG_TRUE && any_flag != 0) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) + sk->req_items.any[i] = true; + } +} + +/** + * Validates a skill's required items when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the required items should be set it. + * + **/ static void skill_validate_item_requirements(struct config_setting_t *conf, struct s_skill_db *sk) { - struct config_setting_t *tt = NULL; + nullpo_retv(conf); + nullpo_retv(sk); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "Items"); + if (t != NULL && config_setting_is_group(t)) { + skill->validate_item_requirements_sub_any_flag(t, sk); + skill->validate_item_requirements_sub_items(t, sk); + } +} + +/** + * Validates a skill's required equipment amounts when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the required equipment amounts should be set it. + * + **/ +static void skill_validate_equip_requirements_sub_item_amount(struct config_setting_t *conf, struct s_skill_db *sk, int item_index) +{ + nullpo_retv(conf); nullpo_retv(sk); - if ((tt=libconfig->setting_get_member(conf, "Items")) && config_setting_is_group(conf)) { - int itx=-1; - struct config_setting_t *it; - while((it=libconfig->setting_get_elem(tt, ++itx)) && itx < MAX_SKILL_ITEM_REQUIRE) { - const char *type = config_setting_name(it); + for (int i = 0; i < MAX_SKILL_LEVEL; i++) + sk->req_equip.item[item_index].amount[i] = 0; - if( type[0] == 'I' && type[1] == 'D' && itemdb->exists(atoi(type+2)) ) - sk->itemid[itx] = atoi(type+2); - else if(!script->get_constant(type, &sk->itemid[itx])) { - ShowWarning("skill_read_skilldb: Invalid required Item '%s' given for skill Id %d in '%s', skipping...\n",type, sk->nameid, DBPATH"skill_db.conf"); + if (config_setting_is_group(conf)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int amount; + + if (libconfig->setting_lookup_int(conf, lv, &amount) == CONFIG_TRUE) { + if (amount > 0) { + sk->req_equip.item[item_index].amount[i] = amount; + } else { + ShowWarning("%s: Invalid required equipment amount %d specified in level %d for skill ID %d in %s! Must be greater than 0. Defaulting to 1...\n", + __func__, amount, i + 1, sk->nameid, conf->file); + sk->req_equip.item[item_index].amount[i] = 1; + } + } + } + + return; + } + + int amount = libconfig->setting_get_int(conf); + + if (amount <= 0) { + ShowWarning("%s: Invalid required equipment amount %d specified for skill ID %d in %s! Must be greater than 0. Defaulting to 1...\n", + __func__, amount, sk->nameid, conf->file); + amount = 1; + } + + for (int i = 0; i < MAX_SKILL_LEVEL; i++) + sk->req_equip.item[item_index].amount[i] = amount; +} + +/** + * Validates a skill's required equipment when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the required equipment should be set it. + * + **/ +static void skill_validate_equip_requirements_sub_items(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + for (int i = 0; i < MAX_SKILL_ITEM_REQUIRE; i++) { + sk->req_equip.item[i].id = 0; + + for (int j = 0; j < MAX_SKILL_LEVEL; j++) + sk->req_equip.item[i].amount[j] = 0; + } + + int item_index = 0; + int count = libconfig->setting_length(conf); + + for (int i = 0; i < count; i++) { + struct config_setting_t *t = libconfig->setting_get_elem(conf, i); + + if (t != NULL && strcasecmp(config_setting_name(t), "Any") != 0) { + if (item_index >= MAX_SKILL_ITEM_REQUIRE) { + ShowWarning("%s: Too many required equipment items specified for skill ID %d in %s! Skipping item %s...\n", + __func__, sk->nameid, conf->file, config_setting_name(t)); continue; } - if (config_setting_is_group(it)) { - // TODO: Per-level item requirements are not implemented yet! - // We just take the first level for the time being (old txt behavior) - sk->amount[itx] = libconfig->setting_get_int_elem(it, 0); - } else { - sk->amount[itx] = libconfig->setting_get_int(it); + int item_id = skill->validate_requirements_item_name(config_setting_name(t)); + struct item_data *it = itemdb->exists(item_id); + + if (item_id == 0 || it == NULL) { + ShowWarning("%s: Invalid required equipment item %s specified for skill ID %d in %s! Skipping item...\n", + __func__, config_setting_name(t), sk->nameid, conf->file); + continue; + } + + if (it->type != IT_WEAPON && it->type != IT_AMMO && it->type != IT_ARMOR && it->type != IT_CARD) { + ShowWarning("%s: Non-equipment item %s specified for skill ID %d in %s! Skipping item...\n", + __func__, config_setting_name(t), sk->nameid, conf->file); + continue; + } + + int j; + + ARR_FIND(0, MAX_SKILL_ITEM_REQUIRE, j, sk->req_equip.item[j].id == item_id); + + if (j < MAX_SKILL_ITEM_REQUIRE) { + ShowWarning("%s: Duplicate required equipment item %s specified for skill ID %d in %s! Skipping item...\n", + __func__, config_setting_name(t), sk->nameid, conf->file); + continue; } + + sk->req_equip.item[item_index].id = item_id; + skill->validate_equip_requirements_sub_item_amount(t, sk, item_index); + item_index++; } } } /** - * Validates the "Unit > Target" flag - * when parsing skill_db.conf - * @param conf struct, pointer to the skill configuration - * @param sk struct, pointer to s_skill_db - * @return void - */ -static void skill_validate_unit_target(struct config_setting_t *conf, struct s_skill_db *sk) + * Validates a skill's required equipment any-flag when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the required equipment any-flag should be set it. + * + **/ +static void skill_validate_equip_requirements_sub_any_flag(struct config_setting_t *conf, struct s_skill_db *sk) { - const char *type = NULL; + nullpo_retv(conf); + nullpo_retv(sk); + + for (int i = 0; i < MAX_SKILL_LEVEL; i++) + sk->req_equip.any[i] = false; + + struct config_setting_t *t = libconfig->setting_get_member(conf, "Any"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int any_flag; + + if (libconfig->setting_lookup_bool(t, lv, &any_flag) == CONFIG_TRUE) + sk->req_equip.any[i] = (any_flag != 0); + } + + return; + } + + int any_flag; + + if (libconfig->setting_lookup_bool(conf, "Any", &any_flag) == CONFIG_TRUE && any_flag != 0) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) + sk->req_equip.any[i] = true; + } +} +/** + * Validates a skill's required equipment when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the required equipment should be set it. + * + **/ +static void skill_validate_equip_requirements(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); nullpo_retv(sk); - if(libconfig->setting_lookup_string(conf, "Target", &type)) { - if(!strcmpi(type,"NotEnemy")) sk->unit_target = BCT_NOENEMY; - else if(!strcmpi(type,"NotParty")) sk->unit_target = BCT_NOPARTY; - else if (!strcmpi(type,"NotGuild")) sk->unit_target = BCT_NOGUILD; - else if(!strcmpi(type,"Friend")) sk->unit_target = BCT_NOENEMY; - else if(!strcmpi(type,"Party")) sk->unit_target = BCT_PARTY; - else if(!strcmpi(type,"Ally")) sk->unit_target = BCT_PARTY|BCT_GUILD; - else if(!strcmpi(type,"Guild")) sk->unit_target = BCT_GUILD; - else if(!strcmpi(type,"All")) sk->unit_target = BCT_ALL; - else if(!strcmpi(type,"Enemy")) sk->unit_target = BCT_ENEMY; - else if(!strcmpi(type,"Self")) sk->unit_target = BCT_SELF; - else if(!strcmpi(type,"SameGuild")) sk->unit_target = BCT_GUILD|BCT_SAMEGUILD; + struct config_setting_t *t = libconfig->setting_get_member(conf, "Equip"); + + if (t != NULL && config_setting_is_group(t)) { + skill->validate_equip_requirements_sub_any_flag(t, sk); + skill->validate_equip_requirements_sub_items(t, sk); + } +} + +/** + * Validates a required item's config setting name when reading the skill DB. + * + * @param name The config setting name to validate. + * @return The corresponding item ID if the passed config setting name is valid, otherwise 0. + * + **/ +static int skill_validate_requirements_item_name(const char *name) +{ + nullpo_ret(name); + + int item_id = 0; + + if (strlen(name) > 2 && name[0] == 'I' && name[1] == 'D') { + if ((item_id = atoi(name + 2)) == 0) + return 0; + + struct item_data *it = itemdb->exists(item_id); + + if (it == NULL) + return 0; + + return it->nameid; } - if (sk->unit_flag & UF_DEFNOTENEMY && battle_config.defnotenemy) - sk->unit_target = BCT_NOENEMY; + if (!script->get_constant(name, &item_id)) + return 0; + + return item_id; +} - //By default, target just characters. - sk->unit_target |= BL_CHAR; +/** + * Validates a skill's requirements when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the requirements should be set it. + * + **/ +static void skill_validate_requirements(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); - if (sk->unit_flag & UF_NOPC) - sk->unit_target &= ~BL_PC; - if (sk->unit_flag & UF_NOMOB) - sk->unit_target &= ~BL_MOB; - if (sk->unit_flag & UF_SKILL) - sk->unit_target |= BL_SKILL; + struct config_setting_t *t = libconfig->setting_get_member(conf, "Requirements"); + + if (t != NULL && config_setting_is_group(t)) { + skill->validate_hp_cost(t, sk); + skill->validate_sp_cost(t, sk); + skill->validate_hp_rate_cost(t, sk); + skill->validate_sp_rate_cost(t, sk); + skill->validate_max_hp_trigger(t, sk); + skill->validate_max_sp_trigger(t, sk); + skill->validate_zeny_cost(t, sk); + skill->validate_weapontype(t, sk); + skill->validate_ammotype(t, sk); + skill->validate_ammo_amount(t, sk); + skill->validate_state(t, sk); + skill->validate_spirit_sphere_cost(t, sk); + skill->validate_item_requirements(t, sk); + skill->validate_equip_requirements(t, sk); + } } /** - * Validates the "Unit > Flag" setting - * when parsing skill_db.conf - * @param type const char, name of the flag being parsed. - * @param on boolean, switch for flag setting - * @param sk struct, pointer to s_skill_db. - * @return (void) - */ + * Validates a single unit ID when reading the skill DB. + * + * @param unit_id The unit ID to validate. + * @return A number greater than or equal to 0 if the passed unit ID is valid, otherwise -1. + * + **/ +static int skill_validate_unit_id_sub(int unit_id) +{ + if (unit_id == 0 || (unit_id >= UNT_SAFETYWALL && unit_id <= UNT_SV_ROOTTWIST)) + return unit_id; + + return -1; +} + +/** + * Validates a skill's unit IDs if specified as single value when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's unit ID data. + * @param sk The s_skill_db struct where the unit IDs should be set it. + * @param index The array index to use. (-1 for whole array.) + * @param unit_id The unit ID to validate. + * + **/ +static void skill_validate_unit_id_value(struct config_setting_t *conf, struct s_skill_db *sk, int index, int unit_id) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + if (skill->validate_unit_id_sub(unit_id) == -1) { + char level_string[14]; // Big enough to contain "in level 999 " in case of custom MAX_SKILL_LEVEL. + + if (index == -1) + *level_string = '\0'; + else + safesnprintf(level_string, sizeof(level_string), "in level %d ", index + 1); + + ShowWarning("%s: Invalid unit ID %d specified %sfor skill ID %d in %s! Must be greater than or equal to 0. Defaulting to 0...\n", + __func__, unit_id, level_string, sk->nameid, conf->file); + + return; + } + + if (index == -1) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) + sk->unit_id[i][0] = unit_id; + } else { + sk->unit_id[index][0] = unit_id; + } +} + +/** + * Validates a skill's unit IDs if specified as array when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's unit ID data. + * @param sk The s_skill_db struct where the unit IDs should be set it. + * @param index The array index to use. (-1 for whole array.) + * + **/ +static void skill_validate_unit_id_array(struct config_setting_t *conf, struct s_skill_db *sk, int index) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + char level_string[14]; // Big enough to contain "in level 999 " in case of custom MAX_SKILL_LEVEL. + + if (index == -1) + *level_string = '\0'; + else + safesnprintf(level_string, sizeof(level_string), "in level %d ", index + 1); + + if (libconfig->setting_length(conf) == 0) { + ShowWarning("%s: No unit ID(s) specified %sfor skill ID %d in %s! Defaulting to 0...\n", + __func__, level_string, sk->nameid, conf->file); + return; + } + + if (libconfig->setting_length(conf) > 2) + ShowWarning("%s: Specified more than two unit IDs %sfor skill ID %d in %s! Reading only the first two...\n", + __func__, level_string, sk->nameid, conf->file); + + int unit_id1 = libconfig->setting_get_int_elem(conf, 0); + + if (skill->validate_unit_id_sub(unit_id1) == -1) { + ShowWarning("%s: Invalid unit ID %d specified %sfor skill ID %d in %s! Must be greater than or equal to 0. Defaulting to 0...\n", + __func__, unit_id1, level_string, sk->nameid, conf->file); + unit_id1 = 0; + } + + int unit_id2 = 0; + + if (libconfig->setting_length(conf) > 1) { + unit_id2 = libconfig->setting_get_int_elem(conf, 1); + + if (skill->validate_unit_id_sub(unit_id2) == -1) { + ShowWarning("%s: Invalid unit ID %d specified %sfor skill ID %d in %s! Must be greater than or equal to 0. Defaulting to 0...\n", + __func__, unit_id2, level_string, sk->nameid, conf->file); + unit_id2 = 0; + } + } + + if (unit_id1 == 0 && unit_id2 == 0) + return; + + if (index == -1) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + sk->unit_id[i][0] = unit_id1; + sk->unit_id[i][1] = unit_id2; + } + } else { + sk->unit_id[index][0] = unit_id1; + sk->unit_id[index][1] = unit_id2; + } +} + +/** + * Validates a skill's unit IDs if specified as group when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's unit ID data. + * @param sk The s_skill_db struct where the unit IDs should be set it. + * + **/ +static void skill_validate_unit_id_group(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + struct config_setting_t *t; + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + + if ((t = libconfig->setting_get_member(conf, lv)) != NULL && config_setting_is_array(t)) { + skill_validate_unit_id_array(t, sk, i); + continue; + } + + int unit_id; + + if (libconfig->setting_lookup_int(conf, lv, &unit_id) == CONFIG_TRUE) + skill_validate_unit_id_value(conf, sk, i, unit_id); + } +} + +/** + * Validates a skill's unit IDs when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the unit IDs should be set it. + * + **/ +static void skill_validate_unit_id(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + sk->unit_id[i][0] = 0; + sk->unit_id[i][1] = 0; + } + + struct config_setting_t *t = libconfig->setting_get_member(conf, "Id"); + + if (t != NULL && config_setting_is_group(t)) { + skill_validate_unit_id_group(t, sk); + return; + } + + if (t != NULL && config_setting_is_array(t)) { + skill_validate_unit_id_array(t, sk, -1); + return; + } + + int unit_id; + + if (libconfig->setting_lookup_int(conf, "Id", &unit_id) == CONFIG_TRUE) + skill_validate_unit_id_value(conf, sk, -1, unit_id); +} + +/** + * Validates a skill's unit layout when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the unit layout should be set it. + * + **/ +static void skill_validate_unit_layout(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->unit_layout_type, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "Layout"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int unit_layout; + + if (libconfig->setting_lookup_int(t, lv, &unit_layout) == CONFIG_TRUE) { + if (unit_layout >= -1 && unit_layout <= MAX_SKILL_UNIT_LAYOUT) + sk->unit_layout_type[i] = unit_layout; + else + ShowWarning("%s: Invalid unit layout %d specified in level %d for skill ID %d in %s! Minimum is -1, maximum is %d. Defaulting to 0...\n", + __func__, unit_layout, i + 1, sk->nameid, conf->file, MAX_SKILL_UNIT_LAYOUT); + } + } + + return; + } + + int unit_layout; + + if (libconfig->setting_lookup_int(conf, "Layout", &unit_layout) == CONFIG_TRUE) { + if (unit_layout >= -1 && unit_layout <= MAX_SKILL_UNIT_LAYOUT) + skill->level_set_value(sk->unit_layout_type, unit_layout); + else + ShowWarning("%s: Invalid unit layout %d specified for skill ID %d in %s! Minimum is -1, maximum is %d. Defaulting to 0...\n", + __func__, unit_layout, sk->nameid, conf->file, MAX_SKILL_UNIT_LAYOUT); + } +} + +/** + * Validates a skill's unit range when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the unit range should be set it. + * + **/ +static void skill_validate_unit_range(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->unit_range, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "Range"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int unit_range; + + if (libconfig->setting_lookup_int(t, lv, &unit_range) == CONFIG_TRUE) { + if (unit_range >= -1 && unit_range <= UCHAR_MAX) + sk->unit_range[i] = unit_range; + else + ShowWarning("%s: Invalid unit range %d specified in level %d for skill ID %d in %s! Minimum is -1, maximum is %d. Defaulting to 0...\n", + __func__, unit_range, i + 1, sk->nameid, conf->file, UCHAR_MAX); + } + } + + return; + } + + int unit_range; + + if (libconfig->setting_lookup_int(conf, "Range", &unit_range) == CONFIG_TRUE) { + if (unit_range >= -1 && unit_range <= UCHAR_MAX) + skill->level_set_value(sk->unit_range, unit_range); + else + ShowWarning("%s: Invalid unit range %d specified for skill ID %d in %s! Minimum is -1, maximum is %d. Defaulting to 0...\n", + __func__, unit_range, sk->nameid, conf->file, UCHAR_MAX); + } +} + +/** + * Validates a skill's unit interval when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the unit interval should be set it. + * + **/ +static void skill_validate_unit_interval(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + skill->level_set_value(sk->unit_interval, 0); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "Interval"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + int unit_interval; + + if (libconfig->setting_lookup_int(t, lv, &unit_interval) == CONFIG_TRUE) { + if (unit_interval >= INFINITE_DURATION) + sk->unit_interval[i] = unit_interval; + else + ShowWarning("%s: Invalid unit interval %d specified in level %d for skill ID %d in %s! Must be greater than or equal to %d. Defaulting to 0...\n", + __func__, unit_interval, i + 1, sk->nameid, conf->file, INFINITE_DURATION); + } + } + + return; + } + + int unit_interval; + + if (libconfig->setting_lookup_int(conf, "Interval", &unit_interval) == CONFIG_TRUE) { + if (unit_interval >= INFINITE_DURATION) + skill->level_set_value(sk->unit_interval, unit_interval); + else + ShowWarning("%s: Invalid unit interval %d specified for skill ID %d in %s! Must be greater than or equal to %d. Defaulting to 0...\n", + __func__, unit_interval, sk->nameid, conf->file, INFINITE_DURATION); + } +} + +/** + * Validates a single unit flag when reading the skill DB. + * + * @param type The unit flag to validate. + * @param on Whether the unit flag is set for the skill. + * @param sk The s_skill_db struct where the unit flag should be set it. + * @return 0 if the passed unit flag is valid, otherwise 1. + * + **/ static int skill_validate_unit_flag_sub(const char *type, bool on, struct s_skill_db *sk) { - nullpo_ret(type); - nullpo_ret(sk); + nullpo_retr(1, type); + nullpo_retr(1, sk); + if (strcmpi(type, "UF_DEFNOTENEMY") == 0) { - if (on) { + if (on) sk->unit_flag |= UF_DEFNOTENEMY; - } else { + else sk->unit_flag &= ~UF_DEFNOTENEMY; - } } else if (strcmpi(type, "UF_NOREITERATION") == 0) { - if (on) { + if (on) sk->unit_flag |= UF_NOREITERATION; - } else { + else sk->unit_flag &= ~UF_NOREITERATION; - } } else if (strcmpi(type, "UF_NOFOOTSET") == 0) { - if (on) { + if (on) sk->unit_flag |= UF_NOFOOTSET; - } else { + else sk->unit_flag &= ~UF_NOFOOTSET; - } } else if (strcmpi(type, "UF_NOOVERLAP") == 0) { - if (on) { + if (on) sk->unit_flag |= UF_NOOVERLAP; - } else { + else sk->unit_flag &= ~UF_NOOVERLAP; - } } else if (strcmpi(type, "UF_PATHCHECK") == 0) { - if (on) { + if (on) sk->unit_flag |= UF_PATHCHECK; - } else { + else sk->unit_flag &= ~UF_PATHCHECK; - } } else if (strcmpi(type, "UF_NOPC") == 0) { - if (on) { + if (on) sk->unit_flag |= UF_NOPC; - } else { + else sk->unit_flag &= ~UF_NOPC; - } } else if (strcmpi(type, "UF_NOMOB") == 0) { - if (on) { + if (on) sk->unit_flag |= UF_NOMOB; - } else { + else sk->unit_flag &= ~UF_NOMOB; - } } else if (strcmpi(type, "UF_SKILL") == 0) { - if (on) { + if (on) sk->unit_flag |= UF_SKILL; - } else { + else sk->unit_flag &= ~UF_SKILL; - } } else if (strcmpi(type, "UF_DANCE") == 0) { - if (on) { + if (on) sk->unit_flag |= UF_DANCE; - } else { + else sk->unit_flag &= ~UF_DANCE; - } } else if (strcmpi(type, "UF_ENSEMBLE") == 0) { - if (on) { + if (on) sk->unit_flag |= UF_ENSEMBLE; - } else { + else sk->unit_flag &= ~UF_ENSEMBLE; - } } else if (strcmpi(type, "UF_SONG") == 0) { - if (on) { + if (on) sk->unit_flag |= UF_SONG; - } else { + else sk->unit_flag &= ~UF_SONG; - } } else if (strcmpi(type, "UF_DUALMODE") == 0) { - if (on) { + if (on) sk->unit_flag |= UF_DUALMODE; - } else { + else sk->unit_flag &= ~UF_DUALMODE; - } } else if (strcmpi(type, "UF_RANGEDSINGLEUNIT") == 0) { - if (on) { + if (on) sk->unit_flag |= UF_RANGEDSINGLEUNIT; - } else { + else sk->unit_flag &= ~UF_RANGEDSINGLEUNIT; - } } else { - return 1; // Invalid Type + return 1; } return 0; } /** - * Validate "Unit > Flag" setting - * when parsing skill_db.conf - * @param conf struct, pointer to the skill configuration - * @param sk struct, struct, pointer to s_skill_db - * @return (void) - */ + * Validates a skill's unit flags when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the unit flags should be set it. + * + **/ static void skill_validate_unit_flag(struct config_setting_t *conf, struct s_skill_db *sk) { - struct config_setting_t *t = NULL; + nullpo_retv(conf); + nullpo_retv(sk); + + sk->unit_flag = 0; + + struct config_setting_t *t = libconfig->setting_get_member(conf, "Flag"); + + if (t != NULL && config_setting_is_group(t)) { + struct config_setting_t *tt; + int i = 0; + + while ((tt = libconfig->setting_get_elem(t, i++)) != NULL) { + bool on = libconfig->setting_get_bool_real(tt); + + if (skill->validate_unit_flag_sub(config_setting_name(tt), on, sk)) + ShowWarning("%s: Invalid unit flag %s specified for skill ID %d in %s! Skipping flag...\n", + __func__, config_setting_name(tt), sk->nameid, conf->file); + } + } +} + +/** + * Validates a single unit target when reading the skill DB. + * + * @param target The unit target to validate. + * @return A number greater than or equal to 0 if the passed unit target is valid, otherwise -1. + * + **/ +static int skill_validate_unit_target_sub(const char *target) +{ + nullpo_retr(-1, target); + + int ret_val = BCT_NOONE; + + if (strcmpi(target, "NotEnemy") == 0) + ret_val = BCT_NOENEMY; + else if (strcmpi(target, "NotParty") == 0) + ret_val = BCT_NOPARTY; + else if (strcmpi(target, "NotGuild") == 0) + ret_val = BCT_NOGUILD; + else if (strcmpi(target, "Friend") == 0) + ret_val = BCT_NOENEMY; + else if (strcmpi(target, "Party") == 0) + ret_val = BCT_PARTY; + else if (strcmpi(target, "Ally") == 0) + ret_val = BCT_PARTY|BCT_GUILD; + else if (strcmpi(target, "Guild") == 0) + ret_val = BCT_GUILD; + else if (strcmpi(target, "All") == 0) + ret_val = BCT_ALL; + else if (strcmpi(target, "Enemy") == 0) + ret_val = BCT_ENEMY; + else if (strcmpi(target, "Self") == 0) + ret_val = BCT_SELF; + else if (strcmpi(target, "SameGuild") == 0) + ret_val = BCT_SAMEGUILD; + else if (strcmpi(target, "GuildAlly") == 0) + ret_val = BCT_GUILDALLY; + else if (strcmpi(target, "Neutral") == 0) + ret_val = BCT_NEUTRAL; + else if (strcmpi(target, "None") != 0) + ret_val = -1; + + return ret_val; +} +/** + * Validates a skill's unit targets when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the unit targets should be set it. + * + **/ +static void skill_validate_unit_target(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); nullpo_retv(sk); - if ((t=libconfig->setting_get_member(conf, "Flag")) && config_setting_is_group(t)) { - int j=0; - struct config_setting_t *tt = NULL; - while ((tt = libconfig->setting_get_elem(t, j++))) { - const char *name = config_setting_name(tt); - if (skill->validate_unit_flag_sub(name, libconfig->setting_get_bool_real(tt), sk)) - skilldb_invalid_error(name, config_setting_name(t), sk->nameid); + skill->level_set_value(sk->unit_target, BCT_NOONE); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "Target"); + + if (t != NULL && config_setting_is_group(t)) { + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + char lv[6]; // Big enough to contain "Lv999" in case of custom MAX_SKILL_LEVEL. + safesnprintf(lv, sizeof(lv), "Lv%d", i + 1); + const char *unit_target; + + if (libconfig->setting_lookup_string(t, lv, &unit_target) == CONFIG_TRUE) { + int target = skill->validate_unit_target_sub(unit_target); + + if (target > BCT_NOONE) + sk->unit_target[i] = target; + else if (target == -1) + ShowWarning("%s: Invalid unit target %s specified in level %d for skill ID %d in %s! Defaulting to None...\n", + __func__, unit_target, i + 1, sk->nameid, conf->file); + } } + } else { + const char *unit_target; + + if (libconfig->setting_lookup_string(conf, "Target", &unit_target) == CONFIG_TRUE) { + int target = skill->validate_unit_target_sub(unit_target); + + if (target > BCT_NOONE) + skill->level_set_value(sk->unit_target, target); + else if (target == -1) + ShowWarning("%s: Invalid unit target %s specified for skill ID %d in %s! Defaulting to None...\n", + __func__, unit_target, sk->nameid, conf->file); + } + } + + for (int i = 0; i < MAX_SKILL_LEVEL; i++) { + if ((sk->unit_flag & UF_DEFNOTENEMY) != 0 && battle_config.defnotenemy != 0) + sk->unit_target[i] = BCT_NOENEMY; + + // By default target just characters. + sk->unit_target[i] |= BL_CHAR; + + if ((sk->unit_flag & UF_NOPC) != 0) + sk->unit_target[i] &= ~BL_PC; + + if ((sk->unit_flag & UF_NOMOB) != 0) + sk->unit_target[i] &= ~BL_MOB; + + if ((sk->unit_flag & UF_SKILL) != 0) + sk->unit_target[i] |= BL_SKILL; } } + +/** + * Validates a skill's unit data when reading the skill DB. + * + * @param conf The libconfig settings block which contains the skill's data. + * @param sk The s_skill_db struct where the unit data should be set it. + * + **/ +static void skill_validate_unit(struct config_setting_t *conf, struct s_skill_db *sk) +{ + nullpo_retv(conf); + nullpo_retv(sk); + + struct config_setting_t *t = libconfig->setting_get_member(conf, "Unit"); + + if (t != NULL && config_setting_is_group(t)) { + skill->validate_unit_id(t, sk); + skill->validate_unit_layout(t, sk); + skill->validate_unit_range(t, sk); + skill->validate_unit_interval(t, sk); + skill->validate_unit_flag(t, sk); + skill->validate_unit_target(t, sk); + } +} + /** * Validate additional field settings via plugins * when parsing skill_db.conf @@ -21148,293 +23662,100 @@ static void skill_validate_additional_fields(struct config_setting_t *conf, stru } /** - * Validates a skill entry and adds it to the database. [ Smokexyz/Hercules ] - * @param sk contains skill data to be checked. - * @param *source filepath constant. - * @return boolean true on success. - */ -static bool skill_validate_skilldb(struct s_skill_db *sk, const char *source) + * Reads a skill DB file from relative path. + * + * @param filename The skill DB's file name including the DB path. + * @return True on success, otherwise false. + * + **/ +static bool skill_read_skilldb(const char *filename) { - int idx; + nullpo_retr(false, filename); - nullpo_retr(false, sk); - idx = skill->get_index(sk->nameid); - if (idx == 0) { - ShowWarning("skill_validate_skilldb: Invalid skill Id %d provided in '%s'! ... skipping\n", sk->nameid, source); - ShowInfo("It is possible that the skill Id is 0 or unavailable (interferes with guild/homun/mercenary skill mapping).\n"); - return false; - } else if (sk->max <= 0) { - ShowError("skill_validate_skilldb: Invalid Max Level %d specified for skill Id %d in '%s', skipping...\n", sk->max, sk->nameid, source); - return false; - } + char filepath[256]; - /* Direct assignment of temporary skill storage to skill db */ - skill->dbs->db[idx] = *sk; - /* Put skill name in name2id DB */ - strdb_iput(skill->name2id_db, skill->dbs->db[idx].name, skill->dbs->db[idx].nameid); - /* Set Name to Id script constants */ - script->set_constant2(skill->dbs->db[idx].name, (int)skill->dbs->db[idx].nameid, false, false); + libconfig->format_db_path(filename, filepath, sizeof(filepath)); - return true; -} + if (!exists(filepath)) { + ShowError("%s: Can't find file %s! Abort reading skills...\n", __func__, filepath); + return false; + } -/** - * Reads skill_db.conf from relative filepath and processes [ Smokexyz/Hercules ] - * entries into the skill database. - * @param filename contains the file path and name. - * @return boolean true on success - */ -static bool skill_read_skilldb(const char *filename) -{ struct config_t skilldb; - struct config_setting_t *sk, *conf; - char filepath[256]; - int count=0, index=0; - bool duplicate[MAX_SKILL_DB] = {0}; - - nullpo_retr(false, filename); - libconfig->format_db_path(filename, filepath, sizeof(filepath)); - - if (!libconfig->load_file(&skilldb, filepath)) { + if (libconfig->load_file(&skilldb, filepath) == 0) return false; // Libconfig error report. - } - // Possible Syntax error. - if ((sk=libconfig->setting_get_member(skilldb.root, "skill_db")) == NULL) { - ShowError("skill_read_skilldb: Skill DB could not be loaded, please check '%s'.\n", filepath); + struct config_setting_t *sk = libconfig->setting_get_member(skilldb.root, "skill_db"); + + if (sk == NULL) { + ShowError("%s: Skill DB could not be loaded! Please check %s.\n", __func__, filepath); libconfig->destroy(&skilldb); return false; } - while ((conf = libconfig->setting_get_elem(sk,index++))) { - int idx=0, skill_id=0, temp=0; - struct config_setting_t *t = NULL, *tt = NULL; - struct s_skill_db tmp_db = { 0 }; - - /* Skill ID */ - if (!libconfig->setting_lookup_int(conf, "Id", &skill_id)) { - ShowError("skill_read_skilldb: Skill Id not specified for entry %d in '%s', skipping...\n", index, filepath ); - continue; - } - - tmp_db.nameid = skill_id; + struct config_setting_t *conf; + int index = 0; + int count = 0; - if((idx = skill->get_index(skill_id)) == 0) { - ShowError("skill_read_skilldb: Skill Id %d is out of range, or within a reserved range (for guild, homunculus, mercenary or elemental skills). skipping...\n", idx); - continue; - } + while ((conf = libconfig->setting_get_elem(sk, index++)) != NULL) { + struct s_skill_db tmp_db = {0}; - if (duplicate[idx]) { - ShowWarning("skill_read_skilldb: Duplicate Skill Id %d in entry %d in '%s', skipping...\n", skill_id, index, filepath); + /** Validate mandatory fields. **/ + skill->validate_id(conf, &tmp_db, index); + if (tmp_db.nameid == 0) continue; - } - /* Skill Name Constant */ - if (!libconfig->setting_lookup_mutable_string(conf, "Name", tmp_db.name, sizeof(tmp_db.name))) { - ShowError("skill_read_skilldb: Name not specified for skill Id %d in '%s', skipping...\n", skill_id, filepath); + skill->validate_name(conf, &tmp_db); + if (*tmp_db.name == '\0') continue; - } - - /* Skill Description */ - libconfig->setting_lookup_mutable_string(conf, "Description", tmp_db.desc, sizeof(tmp_db.desc)); - /* Max Level */ - if (!libconfig->setting_lookup_int(conf, "MaxLevel", &temp)) { - ShowError("skill_read_skilldb: MaxLevel not specified for skill Id %d in '%s', skipping...\n", skill_id, filepath); + skill->validate_max_level(conf, &tmp_db); + if (tmp_db.max == 0) continue; - } else { - tmp_db.max = temp; - } - /* Range */ - if ((t=libconfig->setting_get_member(conf, "Range"))) - skill->config_set_level(t, tmp_db.range); - - /* Hit Type */ + /** Validate optional fields. **/ + skill->validate_description(conf, &tmp_db); + skill->validate_range(conf, &tmp_db); skill->validate_hittype(conf, &tmp_db); - - /* Skill Type */ skill->validate_skilltype(conf, &tmp_db); - - /* Skill Info */ skill->validate_skillinfo(conf, &tmp_db); - - /* Skill Attack Type */ skill->validate_attacktype(conf, &tmp_db); - - /* Skill Element */ skill->validate_element(conf, &tmp_db); - - /* Damage Type */ skill->validate_damagetype(conf, &tmp_db); - - /* Splash Range */ - if ((t = libconfig->setting_get_member(conf, "SplashRange"))) - skill->config_set_level(t, tmp_db.splash); - - /* Number of Hits */ - if ((t = libconfig->setting_get_member(conf, "NumberOfHits")) && config_setting_is_group(t)) - skill->config_set_level(t, tmp_db.num); - else if ((libconfig->setting_lookup_int(conf, "NumberOfHits", &temp))) - skill->level_set_value(tmp_db.num, temp); - else - skill->level_set_value(tmp_db.num, 1); // Default 1 - - /* Interrupt Cast */ - if (libconfig->setting_lookup_bool(conf, "InterruptCast", &tmp_db.castcancel) == CONFIG_FALSE) - tmp_db.castcancel = 0; - - /* Cast Defense Rate */ - libconfig->setting_lookup_int(conf, "CastDefRate", &tmp_db.cast_def_rate); - - /* Skill Instances */ - if ((t = libconfig->setting_get_member(conf, "SkillInstances"))) - skill->config_set_level(t, tmp_db.maxcount); - - /* Knock-Back Tiles */ - if ((t = libconfig->setting_get_member(conf, "KnockBackTiles"))) - skill->config_set_level(t, tmp_db.blewcount); - /** - * Skill Cast / Delay data handling - */ - /* Cast Time */ - if ((t=libconfig->setting_get_member(conf, "CastTime"))) - skill->config_set_level(t, tmp_db.cast); - - /* After Cast Act Delay */ - if ((t=libconfig->setting_get_member(conf, "AfterCastActDelay"))) - skill->config_set_level(t, tmp_db.delay); - - /* After Cast Walk Delay */ - if ((t=libconfig->setting_get_member(conf, "AfterCastWalkDelay"))) - skill->config_set_level(t, tmp_db.walkdelay); - - /* Skill Data/Duration */ - if ((t=libconfig->setting_get_member(conf, "SkillData1"))) - skill->config_set_level(t, tmp_db.upkeep_time); - - /* Skill Data/Duration 2 */ - if ((t=libconfig->setting_get_member(conf, "SkillData2"))) - skill->config_set_level(t, tmp_db.upkeep_time2); - - /* Skill Cool Down */ - if ((t=libconfig->setting_get_member(conf, "CoolDown"))) - skill->config_set_level(t, tmp_db.cooldown); - -#ifdef RENEWAL_CAST - /* Fixed Casting Time */ - if ((t=libconfig->setting_get_member(conf, "FixedCastTime"))) - skill->config_set_level(t, tmp_db.fixed_cast); -#endif - /* Cast Time Options */ + skill->validate_splash_range(conf, &tmp_db); + skill->validate_number_of_hits(conf, &tmp_db); + skill->validate_interrupt_cast(conf, &tmp_db); + skill->validate_cast_def_rate(conf, &tmp_db); + skill->validate_number_of_instances(conf, &tmp_db); + skill->validate_knock_back_tiles(conf, &tmp_db); + skill->validate_cast_time(conf, &tmp_db); + skill->validate_act_delay(conf, &tmp_db); + skill->validate_walk_delay(conf, &tmp_db); + skill->validate_skill_data1(conf, &tmp_db); + skill->validate_skill_data2(conf, &tmp_db); + skill->validate_cooldown(conf, &tmp_db); + skill->validate_fixed_cast_time(conf, &tmp_db); skill->validate_castnodex(conf, &tmp_db, false); skill->validate_castnodex(conf, &tmp_db, true); + skill->validate_requirements(conf, &tmp_db); + skill->validate_unit(conf, &tmp_db); - /** - * Skill Requirements data handling - */ - if ((t=libconfig->setting_get_member(conf, "Requirements")) && config_setting_is_group(t)) { - - /* HP Costs */ - if ((tt = libconfig->setting_get_member(t, "HPCost"))) - skill->config_set_level(tt, tmp_db.hp); - - /* Max HP Trigger */ - if ((tt = libconfig->setting_get_member(t, "MaxHPTrigger"))) - skill->config_set_level(tt, tmp_db.mhp); - - /* SP Cost */ - if ((tt = libconfig->setting_get_member(t, "SPCost"))) - skill->config_set_level(tt, tmp_db.sp); - - /* HP Rate */ - if ((tt = libconfig->setting_get_member(t, "HPRateCost"))) - skill->config_set_level(tt, tmp_db.hp_rate); - - /* SP Rate */ - if ((tt = libconfig->setting_get_member(t, "SPRateCost"))) - skill->config_set_level(tt, tmp_db.sp_rate); - - /* Zeny Cost */ - if ((tt = libconfig->setting_get_member(t, "ZenyCost"))) - skill->config_set_level(tt, tmp_db.zeny); - - /* Spirit Sphere Cost */ - if ((tt = libconfig->setting_get_member(t, "SpiritSphereCost"))) - skill->config_set_level(tt, tmp_db.spiritball); - - /* Weapon Types */ - skill->validate_weapontype(t, &tmp_db); - - /* Ammunition Types */ - skill->validate_ammotype(t, &tmp_db); - - /* Ammunition Amount */ - if ((tt = libconfig->setting_get_member(t, "AmmoAmount"))) - skill->config_set_level(tt, tmp_db.ammo_qty); - - /* State */ - skill->validate_state(t, &tmp_db); - - /* Spirit Sphere Cost */ - if ((tt = libconfig->setting_get_member(t, "SpiritSphereCost"))) - skill->config_set_level(tt, tmp_db.spiritball); - - /* Item Requirements and Amounts */ - skill->validate_item_requirements(t, &tmp_db); - } - - /** - * Skill Unit data handling - */ - if ((t=libconfig->setting_get_member(conf, "Unit")) && config_setting_is_group(t)) { - - /* Unit IDs [1,2] */ - if ((tt=libconfig->setting_get_member(t, "Id")) && config_setting_is_array(tt)) { - tmp_db.unit_id[0] = libconfig->setting_get_int_elem(tt, 0); - tmp_db.unit_id[1] = libconfig->setting_get_int_elem(tt, 1); - } else { - libconfig->setting_lookup_int(t, "Id", &tmp_db.unit_id[0]); - } - - /* Layout */ - if((tt=libconfig->setting_get_member(t, "Layout"))) - skill->config_set_level(tt, tmp_db.unit_layout_type); - - /* Range */ - if((tt=libconfig->setting_get_member(t, "Range"))) - skill->config_set_level(tt, tmp_db.unit_range); - - /* Interval */ - if(libconfig->setting_lookup_int(t, "Interval", &temp)) - tmp_db.unit_interval = temp; - - /* Flag */ - skill->validate_unit_flag(t, &tmp_db); - - /* Target */ - skill->validate_unit_target(t, &tmp_db); - } - - /* Additional Fields for Plugins */ + /** Validate additional fields for plugins. **/ skill->validate_additional_fields(conf, &tmp_db); - // Validate the skill entry, add it to the duplicate array and increment count on success. - if ((duplicate[idx] = skill->validate_skilldb(&tmp_db, filepath))) - count++; + /** Add the skill. **/ + skill->dbs->db[skill->get_index(tmp_db.nameid)] = tmp_db; + strdb_iput(skill->name2id_db, tmp_db.name, tmp_db.nameid); + script->set_constant2(tmp_db.name, tmp_db.nameid, false, false); + count++; } libconfig->destroy(&skilldb); - ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", count, filepath); - return true; } -#undef skilldb_duplicate_warning -#undef skilldb_invalid_error - /*=============================== * DB reading. * produce_db.txt @@ -21613,6 +23934,7 @@ void skill_defaults(void) skill->get_splash = skill_get_splash; skill->get_hp = skill_get_hp; skill->get_mhp = skill_get_mhp; + skill->get_msp = skill_get_msp; skill->get_sp = skill_get_sp; skill->get_hp_rate = skill_get_hp_rate; skill->get_sp_rate = skill_get_sp_rate; @@ -21620,6 +23942,10 @@ void skill_defaults(void) skill->get_spiritball = skill_get_spiritball; skill->get_itemid = skill_get_itemid; skill->get_itemqty = skill_get_itemqty; + skill->get_item_any_flag = skill_get_item_any_flag; + skill->get_equip_id = skill_get_equip_id; + skill->get_equip_amount = skill_get_equip_amount; + skill->get_equip_any_flag = skill_get_equip_any_flag; skill->get_zeny = skill_get_zeny; skill->get_num = skill_get_num; skill->get_cast = skill_get_cast; @@ -21678,8 +24004,12 @@ void skill_defaults(void) skill->cast_fix_sc = skill_castfix_sc; skill->vf_cast_fix = skill_vfcastfix; skill->delay_fix = skill_delay_fix; + skill->check_condition_required_equip = skill_check_condition_required_equip; skill->check_condition_castbegin = skill_check_condition_castbegin; + skill->check_condition_required_items = skill_check_condition_required_items; + skill->items_required = skill_items_required; skill->check_condition_castend = skill_check_condition_castend; + skill->get_any_item_index = skill_get_any_item_index; skill->consume_requirement = skill_consume_requirement; skill->get_requirement = skill_get_requirement; skill->check_pc_partner = skill_check_pc_partner; @@ -21764,24 +24094,68 @@ void skill_defaults(void) skill->init_unit_layout = skill_init_unit_layout; skill->init_unit_layout_unknown = skill_init_unit_layout_unknown; /* Skill DB Libconfig */ + skill->validate_id = skill_validate_id; + skill->name_contains_invalid_character = skill_name_contains_invalid_character; + skill->validate_name = skill_validate_name; + skill->validate_max_level = skill_validate_max_level; + skill->validate_description = skill_validate_description; + skill->validate_range = skill_validate_range; skill->validate_hittype = skill_validate_hittype; - skill->validate_attacktype = skill_validate_attacktype; - skill->validate_element = skill_validate_element; skill->validate_skilltype = skill_validate_skilltype; skill->validate_skillinfo = skill_validate_skillinfo; + skill->validate_attacktype = skill_validate_attacktype; + skill->validate_element = skill_validate_element; skill->validate_damagetype = skill_validate_damagetype; + skill->validate_splash_range = skill_validate_splash_range; + skill->validate_number_of_hits = skill_validate_number_of_hits; + skill->validate_interrupt_cast = skill_validate_interrupt_cast; + skill->validate_cast_def_rate = skill_validate_cast_def_rate; + skill->validate_number_of_instances = skill_validate_number_of_instances; + skill->validate_knock_back_tiles = skill_validate_knock_back_tiles; + skill->validate_cast_time = skill_validate_cast_time; + skill->validate_act_delay = skill_validate_act_delay; + skill->validate_walk_delay = skill_validate_walk_delay; + skill->validate_skill_data1 = skill_validate_skill_data1; + skill->validate_skill_data2 = skill_validate_skill_data2; + skill->validate_cooldown = skill_validate_cooldown; + skill->validate_fixed_cast_time = skill_validate_fixed_cast_time; skill->validate_castnodex = skill_validate_castnodex; + skill->validate_hp_cost = skill_validate_hp_cost; + skill->validate_sp_cost = skill_validate_sp_cost; + skill->validate_hp_rate_cost = skill_validate_hp_rate_cost; + skill->validate_sp_rate_cost = skill_validate_sp_rate_cost; + skill->validate_max_hp_trigger = skill_validate_max_hp_trigger; + skill->validate_max_sp_trigger = skill_validate_max_sp_trigger; + skill->validate_zeny_cost = skill_validate_zeny_cost; + skill->validate_weapontype_sub = skill_validate_weapontype_sub; skill->validate_weapontype = skill_validate_weapontype; + skill->validate_ammotype_sub = skill_validate_ammotype_sub; skill->validate_ammotype = skill_validate_ammotype; + skill->validate_ammo_amount = skill_validate_ammo_amount; + skill->validate_state_sub = skill_validate_state_sub; skill->validate_state = skill_validate_state; + skill->validate_spirit_sphere_cost = skill_validate_spirit_sphere_cost; + skill->validate_item_requirements_sub_item_amount = skill_validate_item_requirements_sub_item_amount; + skill->validate_item_requirements_sub_items = skill_validate_item_requirements_sub_items; + skill->validate_item_requirements_sub_any_flag = skill_validate_item_requirements_sub_any_flag; skill->validate_item_requirements = skill_validate_item_requirements; - skill->validate_unit_target = skill_validate_unit_target; + skill->validate_equip_requirements_sub_item_amount = skill_validate_equip_requirements_sub_item_amount; + skill->validate_equip_requirements_sub_items = skill_validate_equip_requirements_sub_items; + skill->validate_equip_requirements_sub_any_flag = skill_validate_equip_requirements_sub_any_flag; + skill->validate_equip_requirements = skill_validate_equip_requirements; + skill->validate_requirements_item_name = skill_validate_requirements_item_name; + skill->validate_requirements = skill_validate_requirements; + skill->validate_unit_id_sub = skill_validate_unit_id_sub; + skill->validate_unit_id = skill_validate_unit_id; + skill->validate_unit_layout = skill_validate_unit_layout; + skill->validate_unit_range = skill_validate_unit_range; + skill->validate_unit_interval = skill_validate_unit_interval; + skill->validate_unit_flag_sub = skill_validate_unit_flag_sub; skill->validate_unit_flag = skill_validate_unit_flag; + skill->validate_unit_target_sub = skill_validate_unit_target_sub; + skill->validate_unit_target = skill_validate_unit_target; + skill->validate_unit = skill_validate_unit; skill->validate_additional_fields = skill_validate_additional_fields; - skill->validate_skilldb = skill_validate_skilldb; - skill->validate_weapontype_sub = skill_validate_weapontype_sub; - skill->validate_ammotype_sub = skill_validate_ammotype_sub; - skill->validate_unit_flag_sub = skill_validate_unit_flag_sub; skill->read_skilldb = skill_read_skilldb; skill->config_set_level = skill_config_set_level; skill->level_set_value = skill_level_set_value; |