static bool mob_parse_dbrow(char** str) { struct mob_db *db, entry; struct status_data *status; int class_, i, k; double exp, maxhp; struct mob_data data; class_ = atoi(str[0]); 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); return false; } if (pcdb_checkid(class_)) { ShowError("mob_parse_dbrow: 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); return false; } memset(&entry, 0, sizeof(entry)); db = &entry; status = &db->status; 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); status->max_hp = atoi(str[5]); status->max_sp = atoi(str[6]); exp = (double)atoi(str[7]) * (double)battle_config.base_exp_rate / 100.; db->base_exp = (unsigned int)cap_value(exp, 0, UINT_MAX); exp = (double)atoi(str[8]) * (double)battle_config.job_exp_rate / 100.; db->job_exp = (unsigned int)cap_value(exp, 0, UINT_MAX); status->rhw.range = atoi(str[9]); status->rhw.atk = atoi(str[10]); status->rhw.atk2 = atoi(str[11]); status->def = atoi(str[12]); status->mdef = atoi(str[13]); status->str = atoi(str[14]); status->agi = atoi(str[15]); status->vit = atoi(str[16]); status->int_ = atoi(str[17]); status->dex = atoi(str[18]); status->luk = atoi(str[19]); //All status should be min 1 to prevent divisions by zero from some skills. [Skotlex] if (status->str < 1) status->str = 1; if (status->agi < 1) status->agi = 1; if (status->vit < 1) status->vit = 1; if (status->int_< 1) status->int_= 1; if (status->dex < 1) status->dex = 1; if (status->luk < 1) status->luk = 1; 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 (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; } status->size = atoi(str[22]); status->race = atoi(str[23]); i = atoi(str[24]); //Element status->def_ele = i%10; status->ele_lv = i/20; if (status->def_ele >= ELE_MAX) { ShowError("mob_parse_dbrow: Invalid element type %d for monster ID %d (max=%d).\n", status->def_ele, class_, ELE_MAX-1); return false; } if (status->ele_lv < 1 || status->ele_lv > 4) { ShowError("mob_parse_dbrow: Invalid element level %d for monster ID %d, must be in range 1-4.\n", status->ele_lv, class_); return false; } status->mode = (int)strtol(str[25], NULL, 0); if (!battle_config.monster_active_enable) status->mode &= ~MD_AGGRESSIVE; status->speed = atoi(str[26]); status->aspd_rate = 1000; i = atoi(str[27]); status->adelay = cap_value(i, battle_config.monster_max_aspd*2, 4000); i = atoi(str[28]); status->amotion = cap_value(i, battle_config.monster_max_aspd, 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 (status->adelay < status->amotion) status->adelay = status->amotion; status->dmotion = atoi(str[29]); if(battle_config.monster_damage_delay_rate != 100) status->dmotion = status->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; memcpy(&data.status, status, sizeof(struct status_data)); iStatus->calc_misc(&data.bl, status, db->lv); // MVP EXP Bonus: MEXP // Some new MVP's MEXP multipled 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)status->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.; status->max_hp = (unsigned int)cap_value(maxhp, 1, UINT_MAX); if(status->max_sp < 1) status->max_sp = 1; //Since mobs always respawn with full life... status->hp = status->max_hp; status->sp = status->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; } 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 } } } for(i = 0; i < MAX_MOB_DROP; i++) { int rate = 0, rate_adjust, type; unsigned short ratemin, ratemax; struct item_data *id; 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 suport to restrict normal drops of MVP's [Reddozen] case IT_HEALING: rate_adjust = (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 = (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 = (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 = (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 = (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; } 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_) 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_; } } // 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(mob_db_data[class_], db, sizeof(struct mob_db)); return true; }