summaryrefslogtreecommitdiff
path: root/src/map/mob.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/map/mob.c')
-rw-r--r--src/map/mob.c800
1 files changed, 482 insertions, 318 deletions
diff --git a/src/map/mob.c b/src/map/mob.c
index 37da81a15..8d38fead7 100644
--- a/src/map/mob.c
+++ b/src/map/mob.c
@@ -46,6 +46,7 @@
#include "map/status.h"
#include "common/HPM.h"
#include "common/cbasetypes.h"
+#include "common/conf.h"
#include "common/db.h"
#include "common/ers.h"
#include "common/memmgr.h"
@@ -743,7 +744,7 @@ int mob_spawn_bg(const char* mapname, short x, short y, const char* mobname, int
data.class_ = class_;
if( (x <= 0 || y <= 0) && !map->search_freecell(NULL, m, &x, &y, -1,-1, 1) ) {
- ShowWarning("mob_spawn_bg: Couldn't locate a spawn cell for guardian class %d (bg_id %d) at map %s\n",class_, bg_id, map->list[m].name);
+ ShowWarning("mob_spawn_bg: Couldn't locate a spawn cell for guardian class %d (bg_id %u) at map %s\n", class_, bg_id, map->list[m].name);
return 0;
}
@@ -841,7 +842,8 @@ int mob_delayspawn(int tid, int64 tick, int id, intptr_t data) {
*------------------------------------------*/
int mob_setdelayspawn(struct mob_data *md)
{
- unsigned int spawntime, mode;
+ unsigned int spawntime;
+ uint32 mode;
struct mob_db *db;
if (!md->spawn) //Doesn't has respawn data!
@@ -988,7 +990,7 @@ int mob_spawn (struct mob_data *md)
/*==========================================
* Determines if the mob can change target. [Skotlex]
*------------------------------------------*/
-int mob_can_changetarget(struct mob_data* md, struct block_list* target, int mode)
+int mob_can_changetarget(const struct mob_data *md, const struct block_list *target, uint32 mode)
{
// if the monster was provoked ignore the above rule [celest]
if(md->state.provoke_flag)
@@ -1005,7 +1007,7 @@ int mob_can_changetarget(struct mob_data* md, struct block_list* target, int mod
return 0;
return (battle_config.mob_ai&0x4 || check_distance_bl(&md->bl, target, 3));
case MSS_RUSH:
- return (mode&MD_CHANGETARGET_CHASE);
+ return (mode&MD_CHANGETARGET_CHASE) ? 1 : 0;
case MSS_FOLLOW:
case MSS_ANGRY:
case MSS_IDLE:
@@ -1044,17 +1046,17 @@ int mob_target(struct mob_data *md,struct block_list *bl,int dist)
/*==========================================
* The ?? routine of an active monster
*------------------------------------------*/
-int mob_ai_sub_hard_activesearch(struct block_list *bl,va_list ap)
+int mob_ai_sub_hard_activesearch(struct block_list *bl, va_list ap)
{
struct mob_data *md;
struct block_list **target;
- int mode;
+ uint32 mode;
int dist;
nullpo_ret(bl);
md=va_arg(ap,struct mob_data *);
target= va_arg(ap,struct block_list**);
- mode= va_arg(ap,int);
+ mode = va_arg(ap, uint32);
//If can't seek yet, not an enemy, or you can't attack it, skip.
if (md->bl.id == bl->id || (*target) == bl || !status->check_skilluse(&md->bl, bl, 0, 0))
@@ -1400,7 +1402,7 @@ int mob_warpchase(struct mob_data *md, struct block_list *target)
*------------------------------------------*/
bool mob_ai_sub_hard(struct mob_data *md, int64 tick) {
struct block_list *tbl = NULL, *abl = NULL;
- int mode;
+ uint32 mode;
int view_range, can_move;
if(md->bl.prev == NULL || md->status.hp <= 0)
@@ -1541,7 +1543,7 @@ bool mob_ai_sub_hard(struct mob_data *md, int64 tick) {
}
if ((!tbl && mode&MD_AGGRESSIVE) || md->state.skillstate == MSS_FOLLOW) {
- map->foreachinrange (mob->ai_sub_hard_activesearch, &md->bl, view_range, DEFAULT_ENEMY_TYPE(md), md, &tbl, mode);
+ map->foreachinrange(mob->ai_sub_hard_activesearch, &md->bl, view_range, DEFAULT_ENEMY_TYPE(md), md, &tbl, mode);
} else if ((mode&MD_CHANGECHASE && (md->state.skillstate == MSS_RUSH || md->state.skillstate == MSS_FOLLOW)) || (md->sc.count && md->sc.data[SC__CHAOS])) {
int search_size;
search_size = view_range<md->status.rhw.range ? view_range:md->status.rhw.range;
@@ -2064,7 +2066,7 @@ void mob_damage(struct mob_data *md, struct block_list *src, int damage) {
if (battle_config.show_mob_info&3)
clif->charnameack (0, &md->bl);
-
+
#if PACKETVER >= 20131223
// Resend ZC_NOTIFY_MOVEENTRY to Update the HP
if (battle_config.show_monster_hp_bar)
@@ -2482,15 +2484,15 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type) {
if(mvp_sd && md->db->mexp > 0 && md->special_state.ai == AI_NONE) {
int log_mvp[2] = {0};
unsigned int mexp;
- double exp;
+ int64 exp;
//mapflag: noexp check [Lorky]
- if (map->list[m].flag.nobaseexp || type&2)
- exp =1;
- else {
+ if (map->list[m].flag.nobaseexp || type&2) {
+ exp = 1;
+ } else {
exp = md->db->mexp;
if (count > 1)
- exp += exp*(battle_config.exp_bonus_attacker*(count-1))/100.; //[Gengar]
+ exp += apply_percentrate64(exp, battle_config.exp_bonus_attacker * (count-1), 100); //[Gengar]
}
mexp = (unsigned int)cap_value(exp, 1, UINT_MAX);
@@ -2986,18 +2988,20 @@ int mob_summonslave(struct mob_data *md2,int *value,int amount,uint16 skill_id)
/*==========================================
* MOBskill lookup (get skillindex through skill_id)
- * Returns -1 if not found.
+ * Returns INDEX_NOT_FOUND if not found.
*------------------------------------------*/
int mob_skill_id2skill_idx(int class_,uint16 skill_id)
{
int i, max = mob->db(class_)->maxskill;
struct mob_skill *ms=mob->db(class_)->skill;
- if(ms==NULL)
- return -1;
+ if (ms == NULL)
+ return INDEX_NOT_FOUND;
- ARR_FIND( 0, max, i, ms[i].skill_id == skill_id );
- return ( i < max ) ? i : -1;
+ ARR_FIND(0, max, i, ms[i].skill_id == skill_id);
+ if (i == max)
+ return INDEX_NOT_FOUND;
+ return i;
}
/*==========================================
@@ -3366,7 +3370,8 @@ int mob_is_clone(int class_)
//If mode is not passed, a default aggressive mode is used.
//If master_id is passed, clone is attached to him.
//Returns: ID of newly crafted copy.
-int mob_clone_spawn(struct map_session_data *sd, int16 m, int16 x, int16 y, const char *event, int master_id, int mode, int flag, unsigned int duration) {
+int mob_clone_spawn(struct map_session_data *sd, int16 m, int16 x, int16 y, const char *event, int master_id, uint32 mode, int flag, unsigned int duration)
+{
int class_;
int i,j,h,inf, fd;
struct mob_data *md;
@@ -3396,7 +3401,7 @@ int mob_clone_spawn(struct map_session_data *sd, int16 m, int16 x, int16 y, cons
mstatus->lhw.atk2= mstatus->dex + mstatus->lhw.atk + mstatus->lhw.atk2; //Max ATK
mstatus->lhw.atk = mstatus->dex; //Min ATK
}
- if (mode) //User provided mode.
+ if (mode != MD_NONE) //User provided mode.
mstatus->mode = mode;
else if (flag&1) //Friendly Character, remove looting.
mstatus->mode &= ~MD_LOOTER;
@@ -3632,15 +3637,21 @@ int mob_makedummymobdb(int class_)
//Adjusts the drop rate of item according to the criteria given. [Skotlex]
unsigned int mob_drop_adjust(int baserate, int rate_adjust, unsigned short rate_min, unsigned short rate_max)
{
- double rate = baserate;
+ int64 rate = baserate;
- if (battle_config.logarithmic_drops && rate_adjust > 0 && rate_adjust != 100 && baserate > 0) //Logarithmic drops equation by Ishizu-Chan
- //Equation: Droprate(x,y) = x * (5 - log(x)) ^ (ln(y) / ln(5))
- //x is the normal Droprate, y is the Modificator.
- rate = rate * pow((5.0 - log10(rate)), (log(rate_adjust/100.) / log(5.0))) + 0.5;
- else
- //Classical linear rate adjustment.
- rate = rate * rate_adjust/100;
+ Assert_ret(baserate >= 0);
+
+ if (rate_adjust != 100 && baserate > 0) {
+ if (battle_config.logarithmic_drops && rate_adjust > 0) {
+ // Logarithmic drops equation by Ishizu-Chan
+ //Equation: Droprate(x,y) = x * (5 - log(x)) ^ (ln(y) / ln(5))
+ //x is the normal Droprate, y is the Modificator.
+ rate = (int64)(baserate * pow((5.0 - log10(baserate)), (log(rate_adjust/100.) / log(5.0))) + 0.5);
+ } else {
+ //Classical linear rate adjustment.
+ rate = apply_percentrate64(baserate, rate_adjust, 100);
+ }
+ }
return (unsigned int)cap_value(rate,rate_min,rate_max);
}
@@ -3678,71 +3689,95 @@ static inline int mob_parse_dbrow_cap_value(int class_, int min, int max, int va
return value;
}
-void mob_read_db_stats_sub(struct mob_db *entry, struct status_data *mstatus, int class_, config_setting_t *t)
+/**
+ * Processes the stats for a mob database entry.
+ *
+ * @param[in,out] entry The destination mob_db entry, already initialized
+ * (mob_id is expected to be already set).
+ * @param[in] t The libconfig entry.
+ */
+void mob_read_db_stats_sub(struct mob_db *entry, struct config_setting_t *t)
{
int i32;
if (mob->lookup_const(t, "Str", &i32) && i32 >= 0) {
- mstatus->str = mob_parse_dbrow_cap_value(class_, UINT16_MIN, UINT16_MAX, i32);
+ entry->status.str = mob_parse_dbrow_cap_value(entry->mob_id, UINT16_MIN, UINT16_MAX, i32);
}
if (mob->lookup_const(t, "Agi", &i32) && i32 >= 0) {
- mstatus->agi = mob_parse_dbrow_cap_value(class_, UINT16_MIN, UINT16_MAX, i32);
+ entry->status.agi = mob_parse_dbrow_cap_value(entry->mob_id, UINT16_MIN, UINT16_MAX, i32);
}
if (mob->lookup_const(t, "Vit", &i32) && i32 >= 0) {
- mstatus->vit = mob_parse_dbrow_cap_value(class_, UINT16_MIN, UINT16_MAX, i32);
+ entry->status.vit = mob_parse_dbrow_cap_value(entry->mob_id, UINT16_MIN, UINT16_MAX, i32);
}
if (mob->lookup_const(t, "Int", &i32) && i32 >= 0) {
- mstatus->int_ = mob_parse_dbrow_cap_value(class_, UINT16_MIN, UINT16_MAX, i32);
+ entry->status.int_ = mob_parse_dbrow_cap_value(entry->mob_id, UINT16_MIN, UINT16_MAX, i32);
}
if (mob->lookup_const(t, "Dex", &i32) && i32 >= 0) {
- mstatus->dex = mob_parse_dbrow_cap_value(class_, UINT16_MIN, UINT16_MAX, i32);
+ entry->status.dex = mob_parse_dbrow_cap_value(entry->mob_id, UINT16_MIN, UINT16_MAX, i32);
}
if (mob->lookup_const(t, "Luk", &i32) && i32 >= 0) {
- mstatus->luk = mob_parse_dbrow_cap_value(class_, UINT16_MIN, UINT16_MAX, i32);
+ entry->status.luk = mob_parse_dbrow_cap_value(entry->mob_id, UINT16_MIN, UINT16_MAX, i32);
}
}
-int mob_read_db_mode_sub(struct mob_db *entry, struct status_data *mstatus, int class_, config_setting_t *t)
+/**
+ * Processes the mode for a mob_db entry.
+ *
+ * @param[in] entry The destination mob_db entry, already initialized.
+ * @param[in] t The libconfig entry.
+ *
+ * @return The parsed mode.
+ */
+uint32 mob_read_db_mode_sub(struct mob_db *entry, struct config_setting_t *t)
{
- int mode = 0;
- config_setting_t *t2;
+ uint32 mode = 0;
+ struct config_setting_t *t2;
if ((t2 = libconfig->setting_get_member(t, "CanMove")))
- mode |= libconfig->setting_get_bool(t2) ? MD_CANMOVE : 0;
+ mode |= libconfig->setting_get_bool(t2) ? MD_CANMOVE : MD_NONE;
if ((t2 = libconfig->setting_get_member(t, "Looter")))
- mode |= libconfig->setting_get_bool(t2) ? MD_LOOTER : 0;
+ mode |= libconfig->setting_get_bool(t2) ? MD_LOOTER : MD_NONE;
if ((t2 = libconfig->setting_get_member(t, "Aggressive")))
- mode |= libconfig->setting_get_bool(t2) ? MD_AGGRESSIVE : 0;
+ mode |= libconfig->setting_get_bool(t2) ? MD_AGGRESSIVE : MD_NONE;
if ((t2 = libconfig->setting_get_member(t, "Assist")))
- mode |= libconfig->setting_get_bool(t2) ? MD_ASSIST : 0;
+ mode |= libconfig->setting_get_bool(t2) ? MD_ASSIST : MD_NONE;
if ((t2 = libconfig->setting_get_member(t, "CastSensorIdle")))
- mode |= libconfig->setting_get_bool(t2) ? MD_CASTSENSOR_IDLE : 0;
+ mode |= libconfig->setting_get_bool(t2) ? MD_CASTSENSOR_IDLE : MD_NONE;
if ((t2 = libconfig->setting_get_member(t, "Boss")))
- mode |= libconfig->setting_get_bool(t2) ? MD_BOSS : 0;
+ mode |= libconfig->setting_get_bool(t2) ? MD_BOSS : MD_NONE;
if ((t2 = libconfig->setting_get_member(t, "Plant")))
- mode |= libconfig->setting_get_bool(t2) ? MD_PLANT : 0;
+ mode |= libconfig->setting_get_bool(t2) ? MD_PLANT : MD_NONE;
if ((t2 = libconfig->setting_get_member(t, "CanAttack")))
- mode |= libconfig->setting_get_bool(t2) ? MD_CANATTACK : 0;
+ mode |= libconfig->setting_get_bool(t2) ? MD_CANATTACK : MD_NONE;
if ((t2 = libconfig->setting_get_member(t, "Detector")))
- mode |= libconfig->setting_get_bool(t2) ? MD_DETECTOR : 0;
+ mode |= libconfig->setting_get_bool(t2) ? MD_DETECTOR : MD_NONE;
if ((t2 = libconfig->setting_get_member(t, "CastSensorChase")))
- mode |= libconfig->setting_get_bool(t2) ? MD_CASTSENSOR_CHASE : 0;
+ mode |= libconfig->setting_get_bool(t2) ? MD_CASTSENSOR_CHASE : MD_NONE;
if ((t2 = libconfig->setting_get_member(t, "ChangeChase")))
- mode |= libconfig->setting_get_bool(t2) ? MD_CHANGECHASE : 0;
+ mode |= libconfig->setting_get_bool(t2) ? MD_CHANGECHASE : MD_NONE;
if ((t2 = libconfig->setting_get_member(t, "Angry")))
- mode |= libconfig->setting_get_bool(t2) ? MD_ANGRY : 0;
+ mode |= libconfig->setting_get_bool(t2) ? MD_ANGRY : MD_NONE;
if ((t2 = libconfig->setting_get_member(t, "ChangeTargetMelee")))
- mode |= libconfig->setting_get_bool(t2) ? MD_CHANGETARGET_MELEE : 0;
+ mode |= libconfig->setting_get_bool(t2) ? MD_CHANGETARGET_MELEE : MD_NONE;
if ((t2 = libconfig->setting_get_member(t, "ChangeTargetChase")))
- mode |= libconfig->setting_get_bool(t2) ? MD_CHANGETARGET_CHASE : 0;
+ mode |= libconfig->setting_get_bool(t2) ? MD_CHANGETARGET_CHASE : MD_NONE;
if ((t2 = libconfig->setting_get_member(t, "TargetWeak")))
- mode |= libconfig->setting_get_bool(t2) ? MD_TARGETWEAK : 0;
+ mode |= libconfig->setting_get_bool(t2) ? MD_TARGETWEAK : MD_NONE;
+ if ((t2 = libconfig->setting_get_member(t, "NoKnockback")))
+ mode |= libconfig->setting_get_bool(t2) ? MD_NOKNOCKBACK : MD_NONE;
- return mode;
+ return mode & MD_MASK;
}
-void mob_read_db_mvpdrops_sub(struct mob_db *entry, struct status_data *mstatus, int class_, config_setting_t *t)
+/**
+ * Processes the MVP drops for a mob_db entry.
+ *
+ * @param[in,out] entry The destination mob_db entry, already initialized
+ * (mob_id is expected to be already set).
+ * @param[in] t The libconfig entry.
+ */
+void mob_read_db_mvpdrops_sub(struct mob_db *entry, struct config_setting_t *t)
{
- config_setting_t *drop;
+ struct config_setting_t *drop;
int i = 0;
int idx = 0;
int i32;
@@ -3752,28 +3787,26 @@ void mob_read_db_mvpdrops_sub(struct mob_db *entry, struct status_data *mstatus,
int rate_adjust = battle_config.item_rate_mvp;
struct item_data* id = itemdb->search_name(name);
int value = 0;
- if (!id)
- {
- ShowWarning("mob_read_db: mvp drop item %s not found in monster %d\n", name, class_);
- i ++;
+ if (!id) {
+ ShowWarning("mob_read_db: mvp drop item %s not found in monster %d\n", name, entry->mob_id);
+ i++;
continue;
}
if (mob->get_const(drop, &i32) && i32 >= 0) {
value = i32;
}
- if (value <= 0)
- {
- ShowWarning("mob_read_db: wrong drop chance %d for mvp drop item %s in monster %d\n", value, name, class_);
- i ++;
+ if (value <= 0) {
+ ShowWarning("mob_read_db: wrong drop chance %d for mvp drop item %s in monster %d\n", value, name, entry->mob_id);
+ i++;
continue;
}
entry->mvpitem[idx].nameid = id->nameid;
if (!entry->mvpitem[idx].nameid) {
entry->mvpitem[idx].p = 0; //No item....
- i ++;
+ i++;
continue;
}
- mob->item_dropratio_adjust(entry->mvpitem[idx].nameid, class_, &rate_adjust);
+ mob->item_dropratio_adjust(entry->mvpitem[idx].nameid, entry->mob_id, &rate_adjust);
entry->mvpitem[idx].p = mob->drop_adjust(value, rate_adjust, battle_config.item_drop_mvp_min, battle_config.item_drop_mvp_max);
//calculate and store Max available drop chance of the MVP item
@@ -3787,13 +3820,20 @@ void mob_read_db_mvpdrops_sub(struct mob_db *entry, struct status_data *mstatus,
idx++;
}
if (idx == MAX_MVP_DROP && libconfig->setting_get_elem(t, i)) {
- ShowWarning("mob_read_db: Too many mvp drops in mob %d\n", class_);
+ ShowWarning("mob_read_db: Too many mvp drops in mob %d\n", entry->mob_id);
}
}
-void mob_read_db_drops_sub(struct mob_db *entry, struct status_data *mstatus, int class_, config_setting_t *t)
+/**
+ * Processes the drops for a mob_db entry.
+ *
+ * @param[in,out] entry The destination mob_db entry, already initialized
+ * (mob_id, status.mode are expected to be already set).
+ * @param[in] t The libconfig entry.
+ */
+void mob_read_db_drops_sub(struct mob_db *entry, struct config_setting_t *t)
{
- config_setting_t *drop;
+ struct config_setting_t *drop;
int i = 0;
int idx = 0;
int i32;
@@ -3805,73 +3845,72 @@ void mob_read_db_drops_sub(struct mob_db *entry, struct status_data *mstatus, in
unsigned short ratemin, ratemax;
struct item_data* id = itemdb->search_name(name);
int value = 0;
- if (!id)
- {
- ShowWarning("mob_read_db: drop item %s not found in monster %d\n", name, class_);
- i ++;
+ if (!id) {
+ ShowWarning("mob_read_db: drop item %s not found in monster %d\n", name, entry->mob_id);
+ i++;
continue;
}
if (mob->get_const(drop, &i32) && i32 >= 0) {
value = i32;
}
- if (value <= 0)
- {
- ShowWarning("mob_read_db: wrong drop chance %d for drop item %s in monster %d\n", value, name, class_);
- i ++;
+ if (value <= 0) {
+ ShowWarning("mob_read_db: wrong drop chance %d for drop item %s in monster %d\n", value, name, entry->mob_id);
+ i++;
continue;
}
entry->dropitem[idx].nameid = id->nameid;
if (!entry->dropitem[idx].nameid) {
entry->dropitem[idx].p = 0; //No drop.
- i ++;
+ i++;
continue;
}
type = id->type;
- if ((class_ >= MOBID_TREASURE_BOX1 && class_ <= MOBID_TREASURE_BOX40) || (class_ >= MOBID_TREASURE_BOX41 && class_ <= MOBID_TREASURE_BOX49)) {
+ if ((entry->mob_id >= MOBID_TREASURE_BOX1 && entry->mob_id <= MOBID_TREASURE_BOX40)
+ || (entry->mob_id >= MOBID_TREASURE_BOX41 && entry->mob_id <= MOBID_TREASURE_BOX49)) {
//Treasure box drop rates [Skotlex]
rate_adjust = battle_config.item_rate_treasure;
ratemin = battle_config.item_drop_treasure_min;
ratemax = battle_config.item_drop_treasure_max;
- }
- else switch (type)
- { // Added support to restrict normal drops of MVP's [Reddozen]
+ } else {
+ switch (type) { // Added support to restrict normal drops of MVP's [Reddozen]
case IT_HEALING:
- rate_adjust = (mstatus->mode&MD_BOSS) ? battle_config.item_rate_heal_boss : battle_config.item_rate_heal;
+ rate_adjust = (entry->status.mode&MD_BOSS) ? battle_config.item_rate_heal_boss : battle_config.item_rate_heal;
ratemin = battle_config.item_drop_heal_min;
ratemax = battle_config.item_drop_heal_max;
break;
case IT_USABLE:
case IT_CASH:
- rate_adjust = (mstatus->mode&MD_BOSS) ? battle_config.item_rate_use_boss : battle_config.item_rate_use;
+ rate_adjust = (entry->status.mode&MD_BOSS) ? battle_config.item_rate_use_boss : battle_config.item_rate_use;
ratemin = battle_config.item_drop_use_min;
ratemax = battle_config.item_drop_use_max;
break;
case IT_WEAPON:
case IT_ARMOR:
case IT_PETARMOR:
- rate_adjust = (mstatus->mode&MD_BOSS) ? battle_config.item_rate_equip_boss : battle_config.item_rate_equip;
+ rate_adjust = (entry->status.mode&MD_BOSS) ? battle_config.item_rate_equip_boss : battle_config.item_rate_equip;
ratemin = battle_config.item_drop_equip_min;
ratemax = battle_config.item_drop_equip_max;
break;
case IT_CARD:
- rate_adjust = (mstatus->mode&MD_BOSS) ? battle_config.item_rate_card_boss : battle_config.item_rate_card;
+ rate_adjust = (entry->status.mode&MD_BOSS) ? battle_config.item_rate_card_boss : battle_config.item_rate_card;
ratemin = battle_config.item_drop_card_min;
ratemax = battle_config.item_drop_card_max;
break;
default:
- rate_adjust = (mstatus->mode&MD_BOSS) ? battle_config.item_rate_common_boss : battle_config.item_rate_common;
+ rate_adjust = (entry->status.mode&MD_BOSS) ? battle_config.item_rate_common_boss : battle_config.item_rate_common;
ratemin = battle_config.item_drop_common_min;
ratemax = battle_config.item_drop_common_max;
break;
+ }
}
- mob->item_dropratio_adjust(id->nameid, class_, &rate_adjust);
+ mob->item_dropratio_adjust(id->nameid, entry->mob_id, &rate_adjust);
entry->dropitem[idx].p = mob->drop_adjust(value, rate_adjust, ratemin, ratemax);
//calculate and store Max available drop chance of the item
if (entry->dropitem[idx].p
- && (class_ < MOBID_TREASURE_BOX1 || class_ > MOBID_TREASURE_BOX40)
- && (class_ < MOBID_TREASURE_BOX41 || class_ > MOBID_TREASURE_BOX49)) {
+ && (entry->mob_id < MOBID_TREASURE_BOX1 || entry->mob_id > MOBID_TREASURE_BOX40)
+ && (entry->mob_id < MOBID_TREASURE_BOX41 || entry->mob_id > MOBID_TREASURE_BOX49)) {
//Skip treasure chests.
if (id->maxchance == -1 || (id->maxchance < entry->dropitem[idx].p) ) {
id->maxchance = entry->dropitem[idx].p; //item has bigger drop chance or sold in shops
@@ -3880,366 +3919,460 @@ void mob_read_db_drops_sub(struct mob_db *entry, struct status_data *mstatus, in
if (id->mob[k].chance <= entry->dropitem[idx].p)
break;
}
- if (k == MAX_SEARCH)
- {
+ if (k == MAX_SEARCH) {
i++;
idx++;
continue;
}
- if (id->mob[k].id != class_ && k != MAX_SEARCH - 1)
+ if (id->mob[k].id != entry->mob_id && k != MAX_SEARCH - 1)
memmove(&id->mob[k+1], &id->mob[k], (MAX_SEARCH-k-1)*sizeof(id->mob[0]));
id->mob[k].chance = entry->dropitem[idx].p;
- id->mob[k].id = class_;
+ id->mob[k].id = entry->mob_id;
}
i++;
idx++;
}
if (idx == MAX_MOB_DROP && libconfig->setting_get_elem(t, i)) {
- ShowWarning("mob_read_db: Too many drops in mob %d\n", class_);
+ ShowWarning("mob_read_db: Too many drops in mob %d\n", entry->mob_id);
}
}
-/*==========================================
- * processes one mobdb entry
- *------------------------------------------*/
-bool mob_read_db_sub(config_setting_t *mobt, int id, const char *source)
+/**
+ * Validates a mob DB entry and inserts it into the database.
+ * This function is called after preparing the mob entry data, and it takes
+ * care of inserting it and cleaning up any remainders of the previous one (in
+ * case it is overwriting an existing entry).
+ *
+ * @param entry Pointer to the new mob_db entry. Ownership is NOT taken, but
+ * the content is modified to reflect the validation.
+ * @param n Ordinal number of the entry, to be displayed in case of
+ * validation errors.
+ * @param source Source of the entry (file name), to be displayed in case of
+ * validation errors.
+ * @return Mob ID of the validated entry, or 0 in case of failure.
+ *
+ * Note: This is safe to call if the new entry is a shallow copy of the old one
+ * (i.e. mob_db2 inheritance), as it will make sure not to free any data still
+ * in use by the new entry.
+ */
+int mob_db_validate_entry(struct mob_db *entry, int n, const char *source)
{
- struct mob_db *entry = NULL, tmpEntry;
- config_setting_t *t = NULL;
- int i32 = 0, value = 0, class_ = 0;
- struct status_data *mstatus;
struct mob_data data;
- const char *str = NULL;
- double maxhp;
- double exp;
- bool inherit = false;
- bool range2Updated = false;
- bool range3Updated = false;
- bool dmotionUpdated = false;
- bool maxhpUpdated = false;
- bool maxspUpdated = false;
- entry = &tmpEntry;
- if (!libconfig->setting_lookup_int(mobt, "Id", &class_)) {
- ShowWarning("mob_read_db_sub: Missing id in \"%s\", entry #%d, skipping.\n", source, class_);
- return false;
+ if (entry->mob_id <= 1000 || entry->mob_id > MAX_MOB_DB) {
+ ShowError("mob_db_validate_entry: Invalid monster ID %d, must be in range %d-%d.\n", entry->mob_id, 1000, MAX_MOB_DB);
+ return 0;
+ }
+ if (pc->db_checkid(entry->mob_id)) {
+ ShowError("mob_read_db_sub: Invalid monster ID %d, reserved for player classes.\n", entry->mob_id);
+ return 0;
+ }
+ if (entry->mob_id >= MOB_CLONE_START && entry->mob_id < MOB_CLONE_END) {
+ ShowError("mob_read_db_sub: Invalid monster ID %d. Range %d-%d is reserved for player clones. Please increase MAX_MOB_DB (%d).\n",
+ entry->mob_id, MOB_CLONE_START, MOB_CLONE_END-1, MAX_MOB_DB);
+ return 0;
}
- if (class_ <= 1000 || class_ > MAX_MOB_DB) {
- ShowError("mob_read_db_sub: Invalid monster ID %d, must be in range %d-%d.\n", class_, 1000, MAX_MOB_DB);
- return false;
+ entry->lv = cap_value(entry->lv, 1, USHRT_MAX);
+
+ if (entry->status.max_sp < 1)
+ entry->status.max_sp = 1;
+ //Since mobs always respawn with full life...
+ entry->status.hp = entry->status.max_hp;
+ entry->status.sp = entry->status.max_sp;
+
+ /*
+ * Disabled for renewal since difference of 0 and 1 still has an impact in the formulas
+ * Just in case there is a mishandled division by zero please let us know. [malufett]
+ */
+#ifndef RENEWAL
+ //All status should be min 1 to prevent divisions by zero from some skills. [Skotlex]
+ if (entry->status.str < 1) entry->status.str = 1;
+ if (entry->status.agi < 1) entry->status.agi = 1;
+ if (entry->status.vit < 1) entry->status.vit = 1;
+ if (entry->status.int_< 1) entry->status.int_= 1;
+ if (entry->status.dex < 1) entry->status.dex = 1;
+ if (entry->status.luk < 1) entry->status.luk = 1;
+#endif
+
+ if (entry->range2 < 1)
+ entry->range2 = 1;
+
+#if 0 // This code was (accidentally) never enabled. It'll stay commented out until it's proven to be needed.
+ //Tests showed that chase range is effectively 2 cells larger than expected [Playtester]
+ if (entry->range3 > 0)
+ entry->range3 += 2;
+#endif // 0
+
+ if (entry->range3 < entry->range2)
+ entry->range3 = entry->range2;
+
+ entry->status.size = cap_value(entry->status.size, 0, 2);
+
+ entry->status.race = cap_value(entry->status.race, 0, RC_MAX - 1);
+
+ if (entry->status.def_ele >= ELE_MAX) {
+ ShowWarning("mob_read_db_sub: Invalid element type %d for monster ID %d (max=%d).\n", entry->status.def_ele, entry->mob_id, ELE_MAX-1);
+ entry->status.def_ele = ELE_NEUTRAL;
+ entry->status.ele_lv = 1;
}
- if (pc->db_checkid(class_)) {
- ShowError("mob_read_db_sub: Invalid monster ID %d, reserved for player classes.\n", class_);
- return false;
+ if (entry->status.ele_lv < 1 || entry->status.ele_lv > 4) {
+ ShowWarning("mob_read_db_sub: Invalid element level %d for monster ID %d, must be in range 1-4.\n", entry->status.ele_lv, entry->mob_id);
+ entry->status.ele_lv = 1;
}
- if (class_ >= MOB_CLONE_START && class_ < MOB_CLONE_END) {
- ShowError("mob_read_db_sub: Invalid monster ID %d. Range %d-%d is reserved for player clones. Please increase MAX_MOB_DB (%d).\n", class_, MOB_CLONE_START, MOB_CLONE_END-1, MAX_MOB_DB);
- return false;
+ // If the attack animation is longer than the delay, the client crops the attack animation!
+ // On aegis there is no real visible effect of having a recharge-time less than amotion anyway.
+ if (entry->status.adelay < entry->status.amotion)
+ entry->status.adelay = entry->status.amotion;
+
+ // Fill in remaining status data by using a dummy monster.
+ data.bl.type = BL_MOB;
+ data.level = entry->lv;
+ memcpy(&data.status, &entry->status, sizeof(struct status_data));
+ status->calc_misc(&data.bl, &entry->status, entry->lv);
+
+ // Finally insert monster's data into the database.
+ if (mob->db_data[entry->mob_id] == NULL) {
+ mob->db_data[entry->mob_id] = (struct mob_db*)aMalloc(sizeof(struct mob_db));
+ } else {
+ //Copy over spawn data
+ memcpy(&entry->spawn, mob->db_data[entry->mob_id]->spawn, sizeof(entry->spawn));
+ }
+ memcpy(mob->db_data[entry->mob_id], entry, sizeof(struct mob_db));
+
+ return entry->mob_id;
+}
+
+/**
+ * Processes one mobdb entry from the libconfig file, loading and inserting it
+ * into the mob database.
+ *
+ * @param mobt Libconfig setting entry. It is expected to be valid and it
+ * won't be freed (it is care of the caller to do so if
+ * necessary).
+ * @param n Ordinal number of the entry, to be displayed in case of
+ * validation errors.
+ * @param source Source of the entry (file name), to be displayed in case of
+ * validation errors.
+ * @return Mob ID of the validated entry, or 0 in case of failure.
+ */
+int mob_read_db_sub(struct config_setting_t *mobt, int n, const char *source)
+{
+ struct mob_db md = { 0 };
+ struct config_setting_t *t = NULL;
+ const char *str = NULL;
+ int i32 = 0;
+ bool inherit = false;
+ bool maxhpUpdated = false;
+
+ nullpo_ret(mobt);
+ /*
+ * // Mandatory fields
+ * Id: ID
+ * SpriteName: "SPRITE_NAME"
+ * Name: "Mob name"
+ * JName: "Mob name"
+ * // Optional fields
+ * Lv: level
+ * Hp: health
+ * Sp: mana
+ * Exp: basic experience
+ * JExp: job experience
+ * AttackRange: attack range
+ * Attack: [attack1, attack2]
+ * Def: defence
+ * Mdef: magic defence
+ * Stats: {
+ * Str: strength
+ * Agi: agility
+ * Vit: vitality
+ * Int: intelligence
+ * Dex: dexterity
+ * Luk: luck
+ * }
+ * ViewRange: view range
+ * ChaseRange: chase range
+ * Size: size
+ * Race: race
+ * Element: (type, level)
+ * Mode: {
+ * CanMove: true/false
+ * Looter: true/false
+ * Aggressive: true/false
+ * Assist: true/false
+ * CastSensorIdle:true/false
+ * Boss: true/false
+ * Plant: true/false
+ * CanAttack: true/false
+ * Detector: true/false
+ * CastSensorChase: true/false
+ * ChangeChase: true/false
+ * Angry: true/false
+ * ChangeTargetMelee: true/false
+ * ChangeTargetChase: true/false
+ * TargetWeak: true/false
+ * NoKnockback: true/false
+ * }
+ * MoveSpeed: move speed
+ * AttackDelay: attack delay
+ * AttackMotion: attack motion
+ * DamageMotion: damage motion
+ * MvpExp: mvp experience
+ * MvpDrops: {
+ * AegisName: chance
+ * ...
+ * }
+ * Drops: {
+ * AegisName: chance
+ * ...
+ * }
+ */
+
+ if (!libconfig->setting_lookup_int(mobt, "Id", &i32)) {
+ ShowWarning("mob_read_db_sub: Missing id in \"%s\", entry #%d, skipping.\n", source, n);
+ return 0;
}
+ md.mob_id = i32;
+ md.vd.class_ = md.mob_id;
if ((t = libconfig->setting_get_member(mobt, "Inherit")) && (inherit = libconfig->setting_get_bool(t))) {
- if (!mob->db_data[class_]) {
- ShowWarning("mob_read_db_sub: Trying to inherit nonexistent mob %d, default values will be used instead.\n", class_);
+ if (!mob->db_data[md.mob_id]) {
+ ShowWarning("mob_read_db_sub: Trying to inherit nonexistent mob %d, default values will be used instead.\n", md.mob_id);
inherit = false;
} else {
// Use old entry as default
- struct mob_db *old_entry = mob->db_data[class_];
- memcpy(entry, old_entry, sizeof(struct mob_db));
- inherit = true;
+ struct mob_db *old_entry = mob->db_data[md.mob_id];
+ memcpy(&md, old_entry, sizeof(md));
}
}
- if (!inherit) {
- memset(&tmpEntry, 0, sizeof(tmpEntry));
- }
-
- mstatus = &entry->status;
-
- entry->vd.class_ = class_;
if (!libconfig->setting_lookup_string(mobt, "SpriteName", &str) || !*str ) {
if (!inherit) {
- ShowWarning("mob_read_db_sub: Missing SpriteName in mob %d of \"%s\", skipping.\n", class_, source);
- return false;
+ ShowWarning("mob_read_db_sub: Missing SpriteName in mob %d of \"%s\", skipping.\n", md.mob_id, source);
+ return 0;
}
} else {
- safestrncpy(entry->sprite, str, sizeof(entry->sprite));
+ safestrncpy(md.sprite, str, sizeof(md.sprite));
}
if (!libconfig->setting_lookup_string(mobt, "Name", &str) || !*str ) {
if (!inherit) {
- ShowWarning("mob_read_db_sub: Missing Name in mob %d of \"%s\", skipping.\n", class_, source);
- return false;
+ ShowWarning("mob_read_db_sub: Missing Name in mob %d of \"%s\", skipping.\n", md.mob_id, source);
+ return 0;
+ }
+ } else {
+ safestrncpy(md.name, str, sizeof(md.name));
+ }
+
+ if (!libconfig->setting_lookup_string(mobt, "JName", &str) || !*str ) {
+ if (!inherit) {
+ safestrncpy(md.jname, md.name, sizeof(md.jname));
}
} else {
- safestrncpy(entry->name, str, sizeof(entry->name));
- safestrncpy(entry->jname, str, sizeof(entry->jname));
+ safestrncpy(md.jname, str, sizeof(md.jname));
}
if (mob->lookup_const(mobt, "Lv", &i32) && i32 >= 0) {
- entry->lv = i32;
- entry->lv = cap_value(entry->lv, 1, USHRT_MAX);
+ md.lv = i32;
} else if (!inherit) {
- entry->lv = 1;
+ md.lv = 1;
}
if (mob->lookup_const(mobt, "Hp", &i32) && i32 >= 0) {
- mstatus->max_hp = i32;
- maxhpUpdated = true;
+ md.status.max_hp = i32;
+ maxhpUpdated = true; // battle_config modifiers to max_hp are applied below
} else if (!inherit) {
- mstatus->max_hp = 1;
- maxhpUpdated = true;
+ md.status.max_hp = 1;
+ maxhpUpdated = true; // battle_config modifiers to max_hp are applied below
}
if (mob->lookup_const(mobt, "Sp", &i32) && i32 >= 0) {
- mstatus->max_sp = i32;
- maxspUpdated = true;
+ md.status.max_sp = i32;
} else if (!inherit) {
- maxspUpdated = true;
+ md.status.max_sp = 1;
}
if (mob->lookup_const(mobt, "Exp", &i32) && i32 >= 0) {
- exp = (double)(i32) * (double)battle_config.base_exp_rate / 100.;
- entry->base_exp = (unsigned int)cap_value(exp, 0, UINT_MAX);
+ int64 exp = apply_percentrate64(i32, battle_config.base_exp_rate, 100);
+ md.base_exp = (unsigned int)cap_value(exp, 0, UINT_MAX);
}
if (mob->lookup_const(mobt, "JExp", &i32) && i32 >= 0) {
- exp = (double)(i32) * (double)battle_config.job_exp_rate / 100.;
- entry->job_exp = (unsigned int)cap_value(exp, 0, UINT_MAX);
+ int64 exp = apply_percentrate64(i32, battle_config.job_exp_rate, 100);
+ md.job_exp = (unsigned int)cap_value(exp, 0, UINT_MAX);
}
if (mob->lookup_const(mobt, "AttackRange", &i32) && i32 >= 0) {
- mstatus->rhw.range = i32;
+ md.status.rhw.range = i32;
} else {
- mstatus->rhw.range = 1;
+ md.status.rhw.range = 1;
}
if ((t = libconfig->setting_get_member(mobt, "Attack"))) {
if (config_setting_is_aggregate(t)) {
if (libconfig->setting_length(t) >= 2)
- mstatus->rhw.atk2 = libconfig->setting_get_int_elem(t, 1);
+ md.status.rhw.atk2 = libconfig->setting_get_int_elem(t, 1);
if (libconfig->setting_length(t) >= 1)
- mstatus->rhw.atk = libconfig->setting_get_int_elem(t, 0);
+ md.status.rhw.atk = libconfig->setting_get_int_elem(t, 0);
} else if (mob->lookup_const(mobt, "Attack", &i32) && i32 >= 0) {
- mstatus->rhw.atk = i32;
- mstatus->rhw.atk2 = i32;
+ md.status.rhw.atk = i32;
+ md.status.rhw.atk2 = i32;
}
}
if (mob->lookup_const(mobt, "Def", &i32) && i32 >= 0) {
- mstatus->def = mob_parse_dbrow_cap_value(class_, DEFTYPE_MIN, DEFTYPE_MAX, i32);
+ md.status.def = mob_parse_dbrow_cap_value(md.mob_id, DEFTYPE_MIN, DEFTYPE_MAX, i32);
}
if (mob->lookup_const(mobt, "Mdef", &i32) && i32 >= 0) {
- mstatus->mdef = mob_parse_dbrow_cap_value(class_, DEFTYPE_MIN, DEFTYPE_MAX, i32);
+ md.status.mdef = mob_parse_dbrow_cap_value(md.mob_id, DEFTYPE_MIN, DEFTYPE_MAX, i32);
}
if ((t = libconfig->setting_get_member(mobt, "Stats"))) {
if (config_setting_is_group(t)) {
- mob->read_db_stats_sub(entry, mstatus, class_, t);
+ mob->read_db_stats_sub(&md, t);
} else if (mob->lookup_const(mobt, "Stats", &i32) && i32 >= 0) {
- mstatus->str = mstatus->agi = mstatus->vit = mstatus->int_ = mstatus->dex = mstatus->luk =
- mob_parse_dbrow_cap_value(class_, UINT16_MIN, UINT16_MAX, i32);
+ md.status.str = md.status.agi = md.status.vit = md.status.int_ = md.status.dex = md.status.luk =
+ mob_parse_dbrow_cap_value(md.mob_id, UINT16_MIN, UINT16_MAX, i32);
}
}
- /*
- * Disabled for renewal since difference of 0 and 1 still has an impact in the formulas
- * Just in case there is a mishandled division by zero please let us know. [malufett]
- */
-#ifndef RENEWAL
- //All status should be min 1 to prevent divisions by zero from some skills. [Skotlex]
- if (mstatus->str < 1) mstatus->str = 1;
- if (mstatus->agi < 1) mstatus->agi = 1;
- if (mstatus->vit < 1) mstatus->vit = 1;
- if (mstatus->int_< 1) mstatus->int_= 1;
- if (mstatus->dex < 1) mstatus->dex = 1;
- if (mstatus->luk < 1) mstatus->luk = 1;
-#endif
-
- //Tests showed that chase range is effectively 2 cells larger than expected [Playtester]
- if (entry->range3 > 0)
- entry->range3 += 2;
-
if (mob->lookup_const(mobt, "ViewRange", &i32) && i32 >= 0) {
- entry->range2 = i32;
- range2Updated = true;
+ if (battle_config.view_range_rate != 100) {
+ md.range2 = i32 * battle_config.view_range_rate / 100;
+ } else {
+ md.range2 = i32;
+ }
} else if (!inherit) {
- entry->range2 = 1;
- range2Updated = true;
+ md.range2 = 1;
}
if (mob->lookup_const(mobt, "ChaseRange", &i32) && i32 >= 0) {
- entry->range3 = i32;
- range3Updated = true;
- } else if (!inherit) {
- entry->range3 = 1;
- range3Updated = true;
- }
- if (range2Updated) {
- if (battle_config.view_range_rate != 100) {
- entry->range2 = entry->range2 * battle_config.view_range_rate / 100;
- if (entry->range2 < 1)
- entry->range2 = 1;
- }
- }
- if (range3Updated) {
if (battle_config.chase_range_rate != 100) {
- entry->range3 = entry->range3 * battle_config.chase_range_rate / 100;
- if (entry->range3 < entry->range2)
- entry->range3 = entry->range2;
+ md.range3 = i32 * battle_config.chase_range_rate / 100;
+ } else {
+ md.range3 = i32;
}
+ } else if (!inherit) {
+ md.range3 = 1;
}
if (mob->lookup_const(mobt, "Size", &i32) && i32 >= 0) {
- mstatus->size = i32;
- mstatus->size = cap_value(mstatus->size, 0, 2);
+ md.status.size = i32;
} else if (!inherit) {
- mstatus->size = 0;
+ md.status.size = 0;
}
if (mob->lookup_const(mobt, "Race", &i32) && i32 >= 0) {
- mstatus->race = i32;
- mstatus->race = cap_value(mstatus->race, 0, RC_MAX - 1);
+ md.status.race = i32;
} else if (!inherit) {
- mstatus->race = 0;
+ md.status.race = 0;
}
if ((t = libconfig->setting_get_member(mobt, "Element")) && config_setting_is_list(t)) {
+ int value = 0;
if (mob->get_const(libconfig->setting_get_elem(t, 0), &i32) && mob->get_const(libconfig->setting_get_elem(t, 1), &value)) {
- mstatus->def_ele = i32;
- mstatus->ele_lv = value;
- }
- } else {
- if (!inherit) {
- ShowError("mob_read_db_sub: Missing element for monster ID %d.\n", class_);
- return false;
- }
- }
-
- if (mstatus->def_ele >= ELE_MAX) {
- if (!inherit) {
- ShowError("mob_read_db_sub: Invalid element type %d for monster ID %d (max=%d).\n", mstatus->def_ele, class_, ELE_MAX-1);
- return false;
- }
- }
- if (mstatus->ele_lv < 1 || mstatus->ele_lv > 4) {
- if (!inherit) {
- ShowError("mob_read_db_sub: Invalid element level %d for monster ID %d, must be in range 1-4.\n", mstatus->ele_lv, class_);
- return false;
+ md.status.def_ele = i32;
+ md.status.ele_lv = value;
+ } else if (!inherit) {
+ ShowWarning("mob_read_db_sub: Missing element for monster ID %d.\n", md.mob_id);
+ md.status.def_ele = ELE_NEUTRAL;
+ md.status.ele_lv = 1;
}
+ } else if (!inherit) {
+ ShowWarning("mob_read_db_sub: Missing element for monster ID %d.\n", md.mob_id);
+ md.status.def_ele = ELE_NEUTRAL;
+ md.status.ele_lv = 1;
}
if ((t = libconfig->setting_get_member(mobt, "Mode"))) {
if (config_setting_is_group(t)) {
- mstatus->mode = mob->read_db_mode_sub(entry, mstatus, class_, t);
+ md.status.mode = mob->read_db_mode_sub(&md, t);
} else if (mob->lookup_const(mobt, "Mode", &i32) && i32 >= 0) {
- mstatus->mode = i32;
+ md.status.mode = (uint32)i32 & MD_MASK;
}
}
-
if (!battle_config.monster_active_enable)
- mstatus->mode &= ~MD_AGGRESSIVE;
+ md.status.mode &= ~MD_AGGRESSIVE;
if (mob->lookup_const(mobt, "MoveSpeed", &i32) && i32 >= 0) {
- mstatus->speed = i32;
+ md.status.speed = i32;
}
- mstatus->aspd_rate = 1000;
+ md.status.aspd_rate = 1000;
if (mob->lookup_const(mobt, "AttackDelay", &i32) && i32 >= 0) {
- mstatus->adelay = cap_value(i32, battle_config.monster_max_aspd*2, 4000);
+ md.status.adelay = cap_value(i32, battle_config.monster_max_aspd*2, 4000);
} else if (!inherit) {
- mstatus->adelay = 4000;
+ md.status.adelay = 4000;
}
if (mob->lookup_const(mobt, "AttackMotion", &i32) && i32 >= 0) {
- mstatus->amotion = cap_value(i32, battle_config.monster_max_aspd, 2000);
+ md.status.amotion = cap_value(i32, battle_config.monster_max_aspd, 2000);
} else if (!inherit) {
- mstatus->amotion = 2000;
+ md.status.amotion = 2000;
}
- //If the attack animation is longer than the delay, the client crops the attack animation!
- //On aegis there is no real visible effect of having a recharge-time less than amotion anyway.
- if (mstatus->adelay < mstatus->amotion)
- mstatus->adelay = mstatus->amotion;
-
if (mob->lookup_const(mobt, "DamageMotion", &i32) && i32 >= 0) {
- mstatus->dmotion = i32;
- dmotionUpdated = true;
- } else if (!inherit) {
- dmotionUpdated = true;
+ if (battle_config.monster_damage_delay_rate != 100)
+ md.status.dmotion = i32 * battle_config.monster_damage_delay_rate / 100;
+ else
+ md.status.dmotion = i32;
}
- if (dmotionUpdated && battle_config.monster_damage_delay_rate != 100)
- mstatus->dmotion = mstatus->dmotion * battle_config.monster_damage_delay_rate / 100;
-
- // Fill in remaining status data by using a dummy monster.
- data.bl.type = BL_MOB;
- data.level = entry->lv;
- memcpy(&data.status, mstatus, sizeof(struct status_data));
- status->calc_misc(&data.bl, mstatus, entry->lv);
-
// MVP EXP Bonus: MEXP
- // Some new MVP's MEXP multiple by high exp-rate cause overflow. [LuzZza]
if (mob->lookup_const(mobt, "MvpExp", &i32) && i32 >= 0) {
- exp = (double)i32 * (double)battle_config.mvp_exp_rate / 100.;
- entry->mexp = (unsigned int)cap_value(exp, 0, UINT_MAX);
- } else if (!inherit) {
- exp = 0;
+ // Some new MVP's MEXP multiple by high exp-rate cause overflow. [LuzZza]
+ int64 exp = apply_percentrate64(i32, battle_config.mvp_exp_rate, 100);
+ md.mexp = (unsigned int)cap_value(exp, 0, UINT_MAX);
}
if (maxhpUpdated) {
- //Now that we know if it is an mvp or not, apply battle_config modifiers [Skotlex]
- maxhp = (double)mstatus->max_hp;
- if (entry->mexp > 0) { //Mvp
- if (battle_config.mvp_hp_rate != 100)
- maxhp = maxhp * (double)battle_config.mvp_hp_rate / 100.;
+ // Now that we know if it is an mvp or not, apply battle_config modifiers [Skotlex]
+ int64 maxhp = md.status.max_hp;
+ if (md.mexp > 0) { //Mvp
+ maxhp = apply_percentrate64(maxhp, battle_config.mvp_hp_rate, 100);
} else { //Normal mob
- if (battle_config.monster_hp_rate != 100)
- maxhp = maxhp * (double)battle_config.monster_hp_rate / 100.;
+ maxhp = apply_percentrate64(maxhp, battle_config.monster_hp_rate, 100);
}
- mstatus->max_hp = (unsigned int)cap_value(maxhp, 1, UINT_MAX);
- }
- if (maxspUpdated) {
- if(mstatus->max_sp < 1) mstatus->max_sp = 1;
+ md.status.max_hp = (unsigned int)cap_value(maxhp, 1, UINT_MAX);
}
- //Since mobs always respawn with full life...
- mstatus->hp = mstatus->max_hp;
- mstatus->sp = mstatus->max_sp;
-
if ((t = libconfig->setting_get_member(mobt, "MvpDrops"))) {
if (config_setting_is_group(t)) {
- mob->read_db_mvpdrops_sub(entry, mstatus, class_, t);
+ mob->read_db_mvpdrops_sub(&md, t);
}
}
if ((t = libconfig->setting_get_member(mobt, "Drops"))) {
if (config_setting_is_group(t)) {
- mob->read_db_drops_sub(entry, mstatus, class_, t);
+ mob->read_db_drops_sub(&md, t);
}
}
- mob->read_db_additional_fields(entry, class_, mobt, id, source);
- // Finally insert monster's data into the database.
- if (mob->db_data[class_] == NULL)
- mob->db_data[class_] = (struct mob_db*)aMalloc(sizeof(struct mob_db));
- else
- //Copy over spawn data
- memcpy(&entry->spawn, mob->db_data[class_]->spawn, sizeof(entry->spawn));
+ mob->read_db_additional_fields(&md, mobt, n, source);
- memcpy(mob->db_data[class_], entry, sizeof(struct mob_db));
- return true;
+ return mob->db_validate_entry(&md, n, source);
}
-void mob_read_db_additional_fields(struct mob_db *entry, int class_, config_setting_t *it, int n, const char *source)
+/**
+ * Processes any (plugin-defined) additional fields for a mob_db entry.
+ *
+ * @param[in,out] entry The destination mob_db entry, already initialized
+ * (mob_id, status.mode are expected to be already set).
+ * @param[in] t The libconfig entry.
+ * @param[in] n Ordinal number of the entry, to be displayed in case
+ * of validation errors.
+ * @param[in] source Source of the entry (file name), to be displayed in
+ * case of validation errors.
+ */
+void mob_read_db_additional_fields(struct mob_db *entry, struct config_setting_t *t, int n, const char *source)
{
// do nothing. plugins can do own work
}
-bool mob_lookup_const(const config_setting_t *it, const char *name, int *value)
+bool mob_lookup_const(const struct config_setting_t *it, const char *name, int *value)
{
if (libconfig->setting_lookup_int(it, name, value))
{
@@ -4257,7 +4390,7 @@ bool mob_lookup_const(const config_setting_t *it, const char *name, int *value)
return false;
}
-bool mob_get_const(const config_setting_t *it, int *value)
+bool mob_get_const(const struct config_setting_t *it, int *value)
{
const char *str = config_setting_get_string(it);
if (str && *str && script->get_constant(str, value))
@@ -4282,13 +4415,23 @@ void mob_readdb(void) {
mob->name_constants();
}
+/**
+ * Reads from a libconfig-formatted mobdb file and inserts the found entries
+ * into the mob database, overwriting duplicate ones (i.e. mob_db2 overriding
+ * mob_db.)
+ *
+ * @param filename File name, relative to the database path.
+ * @param ignore_missing Whether to ignore errors caused by a missing db file.
+ * @return the number of found entries.
+ */
int mob_read_libconfig(const char *filename, bool ignore_missing)
{
- config_t mob_db_conf;
+ bool duplicate[MAX_MOB_DB] = { 0 };
+ struct config_t mob_db_conf;
char filepath[256];
- config_setting_t *mdb;
- config_setting_t *t;
- int i = 0;
+ struct config_setting_t *mdb;
+ struct config_setting_t *t;
+ int i = 0, count = 0;
nullpo_ret(filename);
sprintf(filepath, "%s/%s", map->db_path, filename);
@@ -4296,17 +4439,33 @@ int mob_read_libconfig(const char *filename, bool ignore_missing)
if (ignore_missing && !exists(filepath))
return 0;
- if (libconfig->read_file(&mob_db_conf, filepath) || !(mdb = libconfig->setting_get_member(mob_db_conf.root, "mob_db"))) {
+ if (!libconfig->load_file(&mob_db_conf, filepath))
+ return 0;
+
+ if ((mdb = libconfig->setting_get_member(mob_db_conf.root, "mob_db")) == NULL) {
ShowError("can't read %s\n", filepath);
- return -1;
+ return 0;
}
while ((t = libconfig->setting_get_elem(mdb, i++))) {
- mob->read_db_sub(t, i - 1, filepath);
+ int mob_id = mob->read_db_sub(t, i - 1, filename);
+
+ if (mob_id <= 0 || mob_id >= MAX_MOB_DB)
+ continue;
+
+ count++;
+
+ if (duplicate[mob_id]) {
+ ShowWarning("mob_read_libconfig:%s: duplicate entry of ID #%d (%s/%s)\n",
+ filename, mob_id, mob->db_data[mob_id]->sprite, mob->db_data[mob_id]->jname);
+ } else {
+ duplicate[mob_id] = true;
+ }
}
libconfig->destroy(&mob_db_conf);
- ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", i, filepath);
- return 0;
+ ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", count, filename);
+
+ return count;
}
void mob_name_constants(void) {
@@ -4316,7 +4475,7 @@ void mob_name_constants(void) {
#endif // ENABLE_CASE_CHECK
for (i = 0; i < MAX_MOB_DB; i++) {
if (mob->db_data[i] && !mob->is_clone(i))
- script->set_constant2(mob->db_data[i]->sprite, i, 0);
+ script->set_constant2(mob->db_data[i]->sprite, i, false, false);
}
#ifdef ENABLE_CASE_CHECK
script->parser_current_file = NULL;
@@ -4529,7 +4688,7 @@ void mob_readchatdb(void) {
if( j < 2 || str[2]==NULL)
{
- ShowError("mob_readchatdb: Insufficient number of fields for skill at %s, line %d\n", arc, lines);
+ ShowError("mob_readchatdb: Insufficient number of fields for skill at %s, line %u\n", arc, lines);
continue;
}
@@ -4749,19 +4908,23 @@ bool mob_parse_row_mobskilldb(char** str, int columns, int current)
ms->val[3]=(int)strtol(str[15],NULL,0);
ms->val[4]=(int)strtol(str[16],NULL,0);
- if(ms->skill_id == NPC_EMOTION && mob_id>0 &&
- ms->val[1] == mob->db(mob_id)->status.mode)
- {
- ms->val[1] = 0;
+ if (ms->skill_id == NPC_EMOTION) {
+ ms->val[1] &= MD_MASK;
+ ms->val[2] &= MD_MASK;
+ ms->val[3] &= MD_MASK;
+ }
+ if (ms->skill_id == NPC_EMOTION && mob_id > 0
+ && (uint32)ms->val[1] == mob->db(mob_id)->status.mode) {
+ ms->val[1] = MD_NONE;
ms->val[4] = 1; //request to return mode to normal.
}
- if (ms->skill_id == NPC_EMOTION_ON && mob_id>0 && ms->val[1]) {
+ if (ms->skill_id == NPC_EMOTION_ON && mob_id>0 && ms->val[1] != MD_NONE) {
//Adds a mode to the mob.
//Remove aggressive mode when the new mob type is passive.
if (!(ms->val[1]&MD_AGGRESSIVE))
- ms->val[3]|=MD_AGGRESSIVE;
- ms->val[2]|= ms->val[1]; //Add the new mode.
- ms->val[1] = 0; //Do not "set" it.
+ ms->val[3] |= MD_AGGRESSIVE;
+ ms->val[2] |= (uint32)ms->val[1]; //Add the new mode.
+ ms->val[1] = MD_NONE; //Do not "set" it.
}
if(*str[17])
@@ -4922,7 +5085,7 @@ void mob_reload(void) {
/**
* Clears spawn related information for a script reload.
*/
-void mob_clear_spawninfo()
+void mob_clear_spawninfo(void)
{
int i;
for (i = 0; i < MAX_MOB_DB; i++)
@@ -5127,6 +5290,7 @@ void mob_defaults(void) {
mob->item_dropratio_adjust = item_dropratio_adjust;
mob->lookup_const = mob_lookup_const;
mob->get_const = mob_get_const;
+ mob->db_validate_entry = mob_db_validate_entry;
mob->readdb = mob_readdb;
mob->read_libconfig = mob_read_libconfig;
mob->read_db_additional_fields = mob_read_db_additional_fields;