From 3feef93f71b52c90ff0ef46af65962d55ef37fed Mon Sep 17 00:00:00 2001
From: Andrei Karas <akaras@inbox.ru>
Date: Wed, 7 Oct 2015 18:32:33 +0300
Subject: Add support for mob_db in conf format.

---
 src/map/mob.c | 778 ++++++++++++++++++++++++++++++++++++++++------------------
 src/map/mob.h |  12 +-
 2 files changed, 548 insertions(+), 242 deletions(-)

(limited to 'src')

diff --git a/src/map/mob.c b/src/map/mob.c
index a14fa02df..a94abbd97 100644
--- a/src/map/mob.c
+++ b/src/map/mob.c
@@ -3646,77 +3646,382 @@ void item_dropratio_adjust(int nameid, int mob_id, int *rate_adjust)
 			*rate_adjust = item_drop_ratio_db[nameid]->drop_ratio;
 	}
 }
+
 /* (mob_parse_dbrow)_cap_value */
 static inline int mob_parse_dbrow_cap_value(int class_, int min, int max, int value) {
 	if( value > max ) {
-		ShowError("mob_parse_dbrow: for class '%d', field value '%d' is higher than the maximum '%d'! capping...\n", class_, value, max);
+		ShowError("mob_parse_dbrow_cap_value: for class '%d', field value '%d' is higher than the maximum '%d'! capping...\n", class_, value, max);
 		return max;
 	} else if ( value < min ) {
-		ShowError("mob_parse_dbrow: for class '%d', field value '%d' is lower than the minimum '%d'! capping...\n", class_, value, min);
+		ShowError("mob_parse_dbrow_cap_value: for class '%d', field value '%d' is lower than the minimum '%d'! capping...\n", class_, value, min);
 		return min;
 	}
 	return value;
 }
+
+void mob_read_db_stats_sub(struct mob_db *entry, struct status_data *mstatus, int class_, 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);
+	}
+	if (mob->lookup_const(t, "Agi", &i32) && i32 >= 0) {
+		mstatus->agi = mob_parse_dbrow_cap_value(class_, 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);
+	}
+	if (mob->lookup_const(t, "Int", &i32) && i32 >= 0) {
+		mstatus->int_ = mob_parse_dbrow_cap_value(class_, 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);
+	}
+	if (mob->lookup_const(t, "Luk", &i32) && i32 >= 0) {
+		mstatus->luk = mob_parse_dbrow_cap_value(class_, 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)
+{
+	int mode = 0;
+	config_setting_t *t2;
+
+	if ((t2 = libconfig->setting_get_member(t, "CanMove")))
+		mode |= libconfig->setting_get_bool(t2) ? MD_CANMOVE : 0;
+	if ((t2 = libconfig->setting_get_member(t, "Looter")))
+		mode |= libconfig->setting_get_bool(t2) ? MD_LOOTER : 0;
+	if ((t2 = libconfig->setting_get_member(t, "Aggressive")))
+		mode |= libconfig->setting_get_bool(t2) ? MD_AGGRESSIVE : 0;
+	if ((t2 = libconfig->setting_get_member(t, "Assist")))
+		mode |= libconfig->setting_get_bool(t2) ? MD_ASSIST : 0;
+	if ((t2 = libconfig->setting_get_member(t, "CastSensorIdle")))
+		mode |= libconfig->setting_get_bool(t2) ? MD_CASTSENSOR_IDLE : 0;
+	if ((t2 = libconfig->setting_get_member(t, "Boss")))
+		mode |= libconfig->setting_get_bool(t2) ? MD_BOSS : 0;
+	if ((t2 = libconfig->setting_get_member(t, "Plant")))
+		mode |= libconfig->setting_get_bool(t2) ? MD_PLANT : 0;
+	if ((t2 = libconfig->setting_get_member(t, "CanAttack")))
+		mode |= libconfig->setting_get_bool(t2) ? MD_CANATTACK : 0;
+	if ((t2 = libconfig->setting_get_member(t, "Detector")))
+		mode |= libconfig->setting_get_bool(t2) ? MD_DETECTOR : 0;
+	if ((t2 = libconfig->setting_get_member(t, "CastSensorChase")))
+		mode |= libconfig->setting_get_bool(t2) ? MD_CASTSENSOR_CHASE : 0;
+	if ((t2 = libconfig->setting_get_member(t, "ChangeChase")))
+		mode |= libconfig->setting_get_bool(t2) ? MD_CHANGECHASE : 0;
+	if ((t2 = libconfig->setting_get_member(t, "Angry")))
+		mode |= libconfig->setting_get_bool(t2) ? MD_ANGRY : 0;
+	if ((t2 = libconfig->setting_get_member(t, "ChangeTargetMelee")))
+		mode |= libconfig->setting_get_bool(t2) ? MD_CHANGETARGET_MELEE : 0;
+	if ((t2 = libconfig->setting_get_member(t, "ChangeTargetChase")))
+		mode |= libconfig->setting_get_bool(t2) ? MD_CHANGETARGET_CHASE : 0;
+	if ((t2 = libconfig->setting_get_member(t, "TargetWeak")))
+		mode |= libconfig->setting_get_bool(t2) ? MD_TARGETWEAK : 0;
+
+	return mode;
+}
+
+void mob_read_db_mvpdrops_sub(struct mob_db *entry, struct status_data *mstatus, int class_, config_setting_t *t)
+{
+	config_setting_t *drop;
+	int i = 0;
+	int idx = 0;
+	int i32;
+
+	while (idx < MAX_MVP_DROP && (drop = libconfig->setting_get_elem(t, i))) {
+		const char *name = config_setting_name(drop);
+		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 ++;
+			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 ++;
+			continue;
+		}
+		entry->mvpitem[idx].nameid = id->nameid;
+		if (!entry->mvpitem[idx].nameid) {
+			entry->mvpitem[idx].p = 0; //No item....
+			i ++;
+			continue;
+		}
+		mob->item_dropratio_adjust(entry->mvpitem[idx].nameid, class_, &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
+		if (entry->mvpitem[idx].p) {
+			if (id->maxchance == -1 || (id->maxchance < entry->mvpitem[idx].p/10 + 1) ) {
+				//item has bigger drop chance or sold in shops
+				id->maxchance = entry->mvpitem[idx].p/10 + 1; //reduce MVP drop info to not spoil common drop rate
+			}
+		}
+		i++;
+		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_);
+	}
+}
+
+void mob_read_db_drops_sub(struct mob_db *entry, struct status_data *mstatus, int class_, config_setting_t *t)
+{
+	config_setting_t *drop;
+	int i = 0;
+	int idx = 0;
+	int i32;
+	int k;
+
+	while (idx < MAX_MOB_DROP && (drop = libconfig->setting_get_elem(t, i))) {
+		const char *name = config_setting_name(drop);
+		int rate_adjust, type;
+		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 ++;
+			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 ++;
+			continue;
+		}
+
+		entry->dropitem[idx].nameid = id->nameid;
+		if (!entry->dropitem[idx].nameid) {
+			entry->dropitem[idx].p = 0; //No drop.
+			i ++;
+			continue;
+		}
+		type = id->type;
+		if ((class_ >= 1324 && class_ <= 1363) || (class_ >= 1938 && class_ <= 1946)) {
+			//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]
+			case IT_HEALING:
+				rate_adjust = (mstatus->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;
+				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;
+				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;
+				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;
+				ratemin = battle_config.item_drop_common_min;
+				ratemax = battle_config.item_drop_common_max;
+				break;
+		}
+		mob->item_dropratio_adjust(id->nameid, class_, &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_ < 1324 || class_ > 1363) && (class_ < 1938 || class_ > 1946))
+		{ //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
+			}
+			for (k = 0; k< MAX_SEARCH; k++) {
+				if (id->mob[k].chance <= entry->dropitem[idx].p)
+					break;
+			}
+			if (k == MAX_SEARCH)
+			{
+				i++;
+				idx++;
+				continue;
+			}
+
+			if (id->mob[k].id != class_ && 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_;
+		}
+		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_);
+	}
+}
+
 /*==========================================
  * processes one mobdb entry
  *------------------------------------------*/
-bool mob_parse_dbrow(char** str) {
-	struct mob_db *db, entry;
+bool mob_read_db_sub(config_setting_t *mobt, int id, 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;
-	int class_, i;
-	double exp, maxhp;
 	struct mob_data data;
-
-	class_ = atoi(str[0]);
+	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 (class_ <= 1000 || class_ > MAX_MOB_DB) {
-		ShowError("mob_parse_dbrow: Invalid monster ID %d, must be in range %d-%d.\n", class_, 1000, 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;
 	}
 	if (pc->db_checkid(class_)) {
-		ShowError("mob_parse_dbrow: Invalid monster ID %d, reserved for player classes.\n", class_);
+		ShowError("mob_read_db_sub: Invalid monster ID %d, reserved for player classes.\n", class_);
 		return false;
 	}
 
 	if (class_ >= MOB_CLONE_START && class_ < MOB_CLONE_END) {
-		ShowError("mob_parse_dbrow: 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);
+		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;
 	}
 
-	memset(&entry, 0, sizeof(entry));
+	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_);
+			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;
+		}
+	}
+	if (!inherit) {
+		memset(&tmpEntry, 0, sizeof(tmpEntry));
+	}
 
-	db = &entry;
-	mstatus = &db->status;
+	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;
+		}
+	} else {
+		safestrncpy(entry->sprite, str, sizeof(entry->sprite));
+	}
 
-	db->vd.class_ = class_;
-	safestrncpy(db->sprite, str[1], sizeof(db->sprite));
-	safestrncpy(db->jname, str[2], sizeof(db->jname));
-	safestrncpy(db->name, str[3], sizeof(db->name));
-	db->lv = atoi(str[4]);
-	db->lv = cap_value(db->lv, 1, USHRT_MAX);
-	mstatus->max_hp = atoi(str[5]);
-	mstatus->max_sp = atoi(str[6]);
+	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;
+		}
+	} else {
+		safestrncpy(entry->name, str, sizeof(entry->name));
+		safestrncpy(entry->jname, str, sizeof(entry->jname));
+	}
 
-	exp = (double)atoi(str[7]) * (double)battle_config.base_exp_rate / 100.;
-	db->base_exp = (unsigned int)cap_value(exp, 0, UINT_MAX);
+	if (mob->lookup_const(mobt, "Lv", &i32) && i32 >= 0) {
+		entry->lv = i32;
+		entry->lv = cap_value(entry->lv, 1, USHRT_MAX);
+	} else if (!inherit) {
+		entry->lv = 1;
+	}
 
-	exp = (double)atoi(str[8]) * (double)battle_config.job_exp_rate / 100.;
-	db->job_exp = (unsigned int)cap_value(exp, 0, UINT_MAX);
+	if (mob->lookup_const(mobt, "Hp", &i32) && i32 >= 0) {
+		mstatus->max_hp = i32;
+		maxhpUpdated = true;
+	} else if (!inherit) {
+		mstatus->max_hp = 1;
+		maxhpUpdated = true;
+	}
 
-	mstatus->rhw.range = atoi(str[9]);
+	if (mob->lookup_const(mobt, "Sp", &i32) && i32 >= 0) {
+		mstatus->max_sp = i32;
+		maxspUpdated = true;
+	} else if (!inherit) {
+		maxspUpdated = true;
+	}
 
-	mstatus->rhw.atk = mob_parse_dbrow_cap_value(class_,UINT16_MIN,UINT16_MAX,atoi(str[10]));
-	mstatus->rhw.atk2 = mob_parse_dbrow_cap_value(class_,UINT16_MIN,UINT16_MAX,atoi(str[11]));
+	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);
+	}
 
-	mstatus->def  = mob_parse_dbrow_cap_value(class_,DEFTYPE_MIN,DEFTYPE_MAX,atoi(str[12]));
-	mstatus->mdef = mob_parse_dbrow_cap_value(class_,DEFTYPE_MIN,DEFTYPE_MAX,atoi(str[13]));
+	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);
+	}
 
-	mstatus->str  = mob_parse_dbrow_cap_value(class_,UINT16_MIN,UINT16_MAX,atoi(str[14]));
-	mstatus->agi  = mob_parse_dbrow_cap_value(class_,UINT16_MIN,UINT16_MAX,atoi(str[15]));
-	mstatus->vit  = mob_parse_dbrow_cap_value(class_,UINT16_MIN,UINT16_MAX,atoi(str[16]));
-	mstatus->int_ = mob_parse_dbrow_cap_value(class_,UINT16_MIN,UINT16_MAX,atoi(str[17]));
-	mstatus->dex  = mob_parse_dbrow_cap_value(class_,UINT16_MIN,UINT16_MAX,atoi(str[18]));
-	mstatus->luk  = mob_parse_dbrow_cap_value(class_,UINT16_MIN,UINT16_MAX,atoi(str[19]));
+	if (mob->lookup_const(mobt, "AttackRange", &i32) && i32 >= 0) {
+		mstatus->rhw.range = i32;
+	} else {
+		mstatus->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);
+			if (libconfig->setting_length(t) >= 1)
+				mstatus->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;
+		}
+	}
+
+	if (mob->lookup_const(mobt, "Def", &i32) && i32 >= 0) {
+		mstatus->def = mob_parse_dbrow_cap_value(class_, 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);
+	}
+
+	if ((t = libconfig->setting_get_member(mobt, "Stats"))) {
+		if (config_setting_is_group(t)) {
+			mob->read_db_stats_sub(entry, mstatus, class_, 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);
+		}
+	}
 
 	/*
 	* Disabled for renewal since difference of 0 and 1 still has an impact in the formulas
@@ -3733,265 +4038,257 @@ bool mob_parse_dbrow(char** str) {
 #endif
 
 	//Tests showed that chase range is effectively 2 cells larger than expected [Playtester]
-	if (db->range3 > 0)
-		db->range3 += 2;
+	if (entry->range3 > 0)
+		entry->range3 += 2;
+
+	if (mob->lookup_const(mobt, "ViewRange", &i32) && i32 >= 0) {
+		entry->range2 = i32;
+		range2Updated = true;
+	} else if (!inherit) {
+		entry->range2 = 1;
+		range2Updated = true;
+	}
+
+	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;
+		}
+	}
 
-	db->range2 = atoi(str[20]);
-	db->range3 = atoi(str[21]);
-	if (battle_config.view_range_rate != 100) {
-		db->range2 = db->range2 * battle_config.view_range_rate / 100;
-		if (db->range2 < 1)
-			db->range2 = 1;
+	if (mob->lookup_const(mobt, "Size", &i32) && i32 >= 0) {
+		mstatus->size = i32;
+		mstatus->size = cap_value(mstatus->size, 0, 2);
+	} else if (!inherit) {
+		mstatus->size = 0;
 	}
-	if (battle_config.chase_range_rate != 100) {
-		db->range3 = db->range3 * battle_config.chase_range_rate / 100;
-		if (db->range3 < db->range2)
-			db->range3 = db->range2;
+
+	if (mob->lookup_const(mobt, "Race", &i32) && i32 >= 0) {
+		mstatus->race = i32;
+		mstatus->race = cap_value(mstatus->race, 0, RC_MAX - 1);
+	} else if (!inherit) {
+		mstatus->race = 0;
 	}
 
-	mstatus->size = atoi(str[22]);
-	mstatus->race = atoi(str[23]);
+	if ((t = libconfig->setting_get_member(mobt, "Element")) && config_setting_is_list(t)) {
+		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;
+		}
+	}
 
-	i = atoi(str[24]); //Element
-	mstatus->def_ele = i%10;
-	mstatus->ele_lv = i/20;
 	if (mstatus->def_ele >= ELE_MAX) {
-		ShowError("mob_parse_dbrow: Invalid element type %d for monster ID %d (max=%d).\n", mstatus->def_ele, class_, ELE_MAX-1);
-		return false;
+		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) {
-		ShowError("mob_parse_dbrow: Invalid element level %d for monster ID %d, must be in range 1-4.\n", mstatus->ele_lv, class_);
-		return false;
+		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;
+		}
+	}
+
+	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);
+		} else if (mob->lookup_const(mobt, "Mode", &i32) && i32 >= 0) {
+			mstatus->mode = i32;
+		}
 	}
 
-	mstatus->mode = (int)strtol(str[25], NULL, 0);
 	if (!battle_config.monster_active_enable)
 		mstatus->mode &= ~MD_AGGRESSIVE;
 
-	mstatus->speed = atoi(str[26]);
+	if (mob->lookup_const(mobt, "MoveSpeed", &i32) && i32 >= 0) {
+		mstatus->speed = i32;
+	}
+
 	mstatus->aspd_rate = 1000;
-	i = atoi(str[27]);
-	mstatus->adelay = cap_value(i, battle_config.monster_max_aspd*2, 4000);
-	i = atoi(str[28]);
-	mstatus->amotion = cap_value(i, battle_config.monster_max_aspd, 2000);
+
+	if (mob->lookup_const(mobt, "AttackDelay", &i32) && i32 >= 0) {
+		mstatus->adelay = cap_value(i32, battle_config.monster_max_aspd*2, 4000);
+	} else if (!inherit) {
+		mstatus->adelay = 4000;
+	}
+
+	if (mob->lookup_const(mobt, "AttackMotion", &i32) && i32 >= 0) {
+		mstatus->amotion = cap_value(i32, battle_config.monster_max_aspd, 2000);
+	} else if (!inherit) {
+		mstatus->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;
-	mstatus->dmotion = atoi(str[29]);
-	if(battle_config.monster_damage_delay_rate != 100)
+
+	if (mob->lookup_const(mobt, "DamageMotion", &i32) && i32 >= 0) {
+		mstatus->dmotion = i32;
+		dmotionUpdated = true;
+	} else if (!inherit) {
+		dmotionUpdated = true;
+	}
+
+	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 = db->lv;
+	data.level = entry->lv;
 	memcpy(&data.status, mstatus, sizeof(struct status_data));
-	status->calc_misc(&data.bl, mstatus, db->lv);
+	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]
-	exp = (double)atoi(str[30]) * (double)battle_config.mvp_exp_rate / 100.;
-	db->mexp = (unsigned int)cap_value(exp, 0, UINT_MAX);
-
-	//Now that we know if it is an mvp or not, apply battle_config modifiers [Skotlex]
-	maxhp = (double)mstatus->max_hp;
-	if (db->mexp > 0) { //Mvp
-		if (battle_config.mvp_hp_rate != 100)
-			maxhp = maxhp * (double)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.;
-
-	mstatus->max_hp = (unsigned int)cap_value(maxhp, 1, UINT_MAX);
-	if(mstatus->max_sp < 1) mstatus->max_sp = 1;
+	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;
+	}
+
+	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.;
+		} else { //Normal mob
+			if (battle_config.monster_hp_rate != 100)
+				maxhp = maxhp * (double)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;
+	}
 
 	//Since mobs always respawn with full life...
 	mstatus->hp = mstatus->max_hp;
 	mstatus->sp = mstatus->max_sp;
 
-	// MVP Drops: MVP1id,MVP1per,MVP2id,MVP2per,MVP3id,MVP3per
-	for(i = 0; i < MAX_MVP_DROP; i++) {
-		int rate_adjust = battle_config.item_rate_mvp;;
-		db->mvpitem[i].nameid = atoi(str[31+i*2]);
-		if (!db->mvpitem[i].nameid) {
-			db->mvpitem[i].p = 0; //No item....
-			continue;
-		}
-		mob->item_dropratio_adjust(db->mvpitem[i].nameid, class_, &rate_adjust);
-		db->mvpitem[i].p = mob->drop_adjust(atoi(str[32+i*2]), 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
-		if (db->mvpitem[i].p) {
-			struct item_data *id;
-			id = itemdb->search(db->mvpitem[i].nameid);
-			if (id->maxchance == -1 || (id->maxchance < db->mvpitem[i].p/10 + 1) ) {
-				//item has bigger drop chance or sold in shops
-				id->maxchance = db->mvpitem[i].p/10 + 1; //reduce MVP drop info to not spoil common drop rate
-			}
+	if ((t = libconfig->setting_get_member(mobt, "MvpDrops"))) {
+		if (config_setting_is_group(t)) {
+			mob->read_db_mvpdrops_sub(entry, mstatus, class_, t);
 		}
 	}
 
-	for(i = 0; i < MAX_MOB_DROP; i++) {
-		int rate = 0, rate_adjust, type;
-		unsigned short ratemin, ratemax;
-		struct item_data *id;
-		int k = 31 + MAX_MVP_DROP*2 + i*2;
-		db->dropitem[i].nameid = atoi(str[k]);
-		if (!db->dropitem[i].nameid) {
-			db->dropitem[i].p = 0; //No drop.
-			continue;
-		}
-		id = itemdb->search(db->dropitem[i].nameid);
-		type = id->type;
-		rate = atoi(str[k+1]);
-		if( (class_ >= 1324 && class_ <= 1363) || (class_ >= 1938 && class_ <= 1946) ) {
-			//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]
-		case IT_HEALING:
-			rate_adjust = (mstatus->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;
-			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;
-			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;
-			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;
-			ratemin = battle_config.item_drop_common_min;
-			ratemax = battle_config.item_drop_common_max;
-			break;
-		}
-		mob->item_dropratio_adjust(id->nameid, class_, &rate_adjust);
-		db->dropitem[i].p = mob->drop_adjust(rate, rate_adjust, ratemin, ratemax);
-
-		//calculate and store Max available drop chance of the item
-		if( db->dropitem[i].p && (class_ < 1324 || class_ > 1363) && (class_ < 1938 || class_ > 1946) )
-		{ //Skip treasure chests.
-			if (id->maxchance == -1 || (id->maxchance < db->dropitem[i].p) ) {
-				id->maxchance = db->dropitem[i].p; //item has bigger drop chance or sold in shops
-			}
-			for (k = 0; k< MAX_SEARCH; k++) {
-				if (id->mob[k].chance <= db->dropitem[i].p)
-					break;
-			}
-			if (k == MAX_SEARCH)
-				continue;
-
-			if (id->mob[k].id != class_ && k != MAX_SEARCH - 1)
-				memmove(&id->mob[k+1], &id->mob[k], (MAX_SEARCH-k-1)*sizeof(id->mob[0]));
-			id->mob[k].chance = db->dropitem[i].p;
-			id->mob[k].id = class_;
+	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_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(&db->spawn, mob->db_data[class_]->spawn, sizeof(db->spawn));
+		memcpy(&entry->spawn, mob->db_data[class_]->spawn, sizeof(entry->spawn));
 
-	memcpy(mob->db_data[class_], db, sizeof(struct mob_db));
+	memcpy(mob->db_data[class_], entry, sizeof(struct mob_db));
+	return true;
+}
+
+void mob_read_db_additional_fields(struct mob_db *entry, int class_, config_setting_t *it, 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)
+{
+	if (libconfig->setting_lookup_int(it, name, value))
+	{
+		return true;
+	}
+	else
+	{
+		const char *str = NULL;
+		if (libconfig->setting_lookup_string(it, name, &str))
+		{
+			if (*str && script->get_constant(str, value))
+				return true;
+		}
+	}
+	return false;
+}
+
+bool mob_get_const(const config_setting_t *it, int *value)
+{
+	const char *str = config_setting_get_string(it);
+	if (str && *str && script->get_constant(str, value))
+		return true;
+
+	*value = libconfig->setting_get_int(it);
 	return true;
 }
 
 /*==========================================
  * mob_db.txt reading
  *------------------------------------------*/
-bool mob_readdb_sub(char* fields[], int columns, int current) {
-	return mob->parse_dbrow(fields);
-}
-
 void mob_readdb(void) {
 	const char* filename[] = {
-		DBPATH"mob_db.txt",
-		"mob_db2.txt" };
-	int fi;
+		DBPATH"mob_db.conf",
+		"mob_db2.conf" };
+	int i;
 
-	for( fi = 0; fi < ARRAYLENGTH(filename); ++fi ) {
-		if(fi > 0) {
-			char filepath[256];
-			sprintf(filepath, "%s/%s", map->db_path, filename[fi]);
-			if(!exists(filepath)) {
+	for (i = 0; i < ARRAYLENGTH(filename); ++i) {
+		if (i > 0) {
+			if (!exists(filename[i])) {
 				continue;
 			}
 		}
 
-		sv->readdb(map->db_path, filename[fi], ',', 31+2*MAX_MVP_DROP+2*MAX_MOB_DROP, 31+2*MAX_MVP_DROP+2*MAX_MOB_DROP, -1, mob->readdb_sub);
+		mob->read_libconfig(filename[i]);
 	}
 	mob->name_constants();
 }
 
-/*==========================================
- * mob_db table reading
- *------------------------------------------*/
-int mob_read_sqldb(void) {
-	const char* mob_db_name[] = {
-		map->mob_db_db,
-		map->mob_db2_db
-	};
-	int fi;
-
-	for( fi = 0; fi < ARRAYLENGTH(mob_db_name); ++fi ) {
-		uint32 lines = 0, count = 0;
-
-		// retrieve all rows from the mob database
-		if( SQL_ERROR == SQL->Query(map->mysql_handle, "SELECT * FROM `%s`", mob_db_name[fi]) ) {
-			Sql_ShowDebug(map->mysql_handle);
-			continue;
-		}
-
-		// process rows one by one
-		while( SQL_SUCCESS == SQL->NextRow(map->mysql_handle) ) {
-			// wrap the result into a TXT-compatible format
-			char line[1024];
-			char* str[31+2*MAX_MVP_DROP+2*MAX_MOB_DROP];
-			char* p;
-			int i;
 
-			lines++;
-			for(i = 0, p = line; i < 31+2*MAX_MVP_DROP+2*MAX_MOB_DROP; i++)
-			{
-				char* data;
-				size_t len;
-				SQL->GetData(map->mysql_handle, i, &data, &len);
-
-				strcpy(p, data);
-				str[i] = p;
-				p+= len + 1;
-			}
-
-			if (!mob->parse_dbrow(str))
-				continue;
+int mob_read_libconfig(const char *filename)
+{
+	config_t mob_db_conf;
+	char filepath[256];
+	config_setting_t *mdb;
+	config_setting_t *t;
+	int i = 0;
 
-			count++;
-		}
+	nullpo_ret(filename);
+	sprintf(filepath, "%s/%s", map->db_path, filename);
 
-		// free the query result
-		SQL->FreeResult(map->mysql_handle);
+	if (libconfig->read_file(&mob_db_conf, filepath) || !(mdb = libconfig->setting_get_member(mob_db_conf.root, "mob_db"))) {
+		ShowError("can't read %s\n", filepath);
+		return -1;
+	}
 
-		ShowStatus("Done reading '"CL_WHITE"%"PRIu32""CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", count, mob_db_name[fi]);
+	while ((t = libconfig->setting_get_elem(mdb, i++))) {
+		mob->read_db_sub(t, i - 1, filepath);
 	}
-	mob->name_constants();
+	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;
 }
 
@@ -4630,13 +4927,10 @@ void mob_load(bool minimal) {
 	}
 	sv->readdb(map->db_path, "mob_item_ratio.txt", ',', 2, 2+MAX_ITEMRATIO_MOBS, -1, mob->readdb_itemratio); // must be read before mobdb
 	mob->readchatdb();
-	if (map->db_use_sql_mob_db) {
-		mob->read_sqldb();
-	}
+	mob->readdb();
 	if (map->db_use_sql_mob_skill_db) {
 		mob->read_sqlskilldb();
 	} else {
-		mob->readdb();
 		mob->readskilldb();
 	}
 	sv->readdb(map->db_path, "mob_avail.txt", ',', 2, 12, -1, mob->readdb_mobavail);
@@ -4847,10 +5141,16 @@ void mob_defaults(void) {
 	mob->clone_delete = mob_clone_delete;
 	mob->drop_adjust = mob_drop_adjust;
 	mob->item_dropratio_adjust = item_dropratio_adjust;
-	mob->parse_dbrow = mob_parse_dbrow;
-	mob->readdb_sub = mob_readdb_sub;
+	mob->lookup_const = mob_lookup_const;
+	mob->get_const = mob_get_const;
 	mob->readdb = mob_readdb;
-	mob->read_sqldb = mob_read_sqldb;
+	mob->read_libconfig = mob_read_libconfig;
+	mob->read_db_additional_fields = mob_read_db_additional_fields;
+	mob->read_db_sub = mob_read_db_sub;
+	mob->read_db_drops_sub = mob_read_db_drops_sub;
+	mob->read_db_mvpdrops_sub = mob_read_db_mvpdrops_sub;
+	mob->read_db_mode_sub = mob_read_db_mode_sub;
+	mob->read_db_stats_sub = mob_read_db_stats_sub;
 	mob->name_constants = mob_name_constants;
 	mob->readdb_mobavail = mob_readdb_mobavail;
 	mob->read_randommonster = mob_read_randommonster;
diff --git a/src/map/mob.h b/src/map/mob.h
index bb26258c6..c20453cde 100644
--- a/src/map/mob.h
+++ b/src/map/mob.h
@@ -363,10 +363,16 @@ struct mob_interface {
 	int (*clone_delete) (struct mob_data *md);
 	unsigned int (*drop_adjust) (int baserate, int rate_adjust, unsigned short rate_min, unsigned short rate_max);
 	void (*item_dropratio_adjust) (int nameid, int mob_id, int *rate_adjust);
-	bool (*parse_dbrow) (char **str);
-	bool (*readdb_sub) (char *fields[], int columns, int current);
 	void (*readdb) (void);
-	int (*read_sqldb) (void);
+	bool (*lookup_const) (const config_setting_t *it, const char *name, int *value);
+	bool (*get_const) (const config_setting_t *it, int *value);
+	int (*read_libconfig) (const char *filename);
+	void (*read_db_additional_fields) (struct mob_db *entry, int class_, config_setting_t *it, int n, const char *source);
+	bool (*read_db_sub) (config_setting_t *mobt, int id, const char *source);
+	void (*read_db_drops_sub) (struct mob_db *entry, struct status_data *mstatus, int class_, config_setting_t *t);
+	void (*read_db_mvpdrops_sub) (struct mob_db *entry, struct status_data *mstatus, int class_, config_setting_t *t);
+	int (*read_db_mode_sub) (struct mob_db *entry, struct status_data *mstatus, int class_, config_setting_t *t);
+	void (*read_db_stats_sub) (struct mob_db *entry, struct status_data *mstatus, int class_, config_setting_t *t);
 	void (*name_constants) (void);
 	bool (*readdb_mobavail) (char *str[], int columns, int current);
 	int (*read_randommonster) (void);
-- 
cgit v1.2.3-70-g09d2