diff --git a/src/map/achievement.cpp b/src/map/achievement.cpp index ea53f38891..11760f5fea 100644 --- a/src/map/achievement.cpp +++ b/src/map/achievement.cpp @@ -1027,7 +1027,7 @@ struct achievement_db *achievement_read_db_sub(yamlwrapper *wrapper, int n, cons for (tt = yaml_iterator_first(it); yaml_iterator_has_next(it) && entry->target_count < MAX_ACHIEVEMENT_OBJECTIVES; tt = yaml_iterator_next(it)) { int mobid = 0, count = 0; - if (yaml_node_is_defined(tt, "MobID") && (mobid = yaml_get_int(tt, "MobID")) && !mobdb_exists(mobid)) { // The mob ID field is not required + if (yaml_node_is_defined(tt, "MobID") && (mobid = yaml_get_int(tt, "MobID")) && mob_db(mobid) == NULL) { // The mob ID field is not required ShowError("achievement_read_db_sub: Invalid mob ID %d for achievement %d in \"%s\", skipping.\n", mobid, achievement_id, source); continue; } diff --git a/src/map/itemdb.cpp b/src/map/itemdb.cpp index 92fe29d939..9922778427 100644 --- a/src/map/itemdb.cpp +++ b/src/map/itemdb.cpp @@ -1913,40 +1913,6 @@ static int itemdb_randomopt_free(DBKey key, DBData *data, va_list ap) { return 1; } -/** - * Re-link monster drop data with item data - * Fixes the need of a @reloadmobdb after a @reloaditemdb - * @author Epoque - */ -void itemdb_reload_itemmob_data(void) { - int i; - - for( i = 0; i < MAX_MOB_DB; i++ ) { - struct mob_db *entry = mob_db(i); - int d, k; - - for(d = 0; d < MAX_MOB_DROP_TOTAL; d++) { - struct item_data *id; - if( !entry->dropitem[d].nameid ) - continue; - id = itemdb_search(entry->dropitem[d].nameid); - - for (k = 0; k < MAX_SEARCH; k++) { - if (id->mob[k].chance <= entry->dropitem[d].p) - break; - } - - if (k == MAX_SEARCH) - continue; - - if (id->mob[k].id != i) - memmove(&id->mob[k+1], &id->mob[k], (MAX_SEARCH-k-1)*sizeof(id->mob[0])); - id->mob[k].chance = entry->dropitem[d].p; - id->mob[k].id = i; - } - } -} - /** * Reload Item DB */ @@ -1969,7 +1935,7 @@ void itemdb_reload(void) { if (battle_config.feature_roulette) itemdb_parse_roulette_db(); - itemdb_reload_itemmob_data(); + mob_reload_itemmob_data(); // readjust itemdb pointer cache for each player iter = mapit_geteachpc(); diff --git a/src/map/itemdb.hpp b/src/map/itemdb.hpp index 27e5ba8082..9202fc6051 100644 --- a/src/map/itemdb.hpp +++ b/src/map/itemdb.hpp @@ -954,7 +954,6 @@ bool itemdb_parse_roulette_db(void); struct s_random_opt_data *itemdb_randomopt_exists(short id); struct s_random_opt_group *itemdb_randomopt_group_exists(int id); -void itemdb_reload_itemmob_data(void); void itemdb_reload(void); void do_final_itemdb(void); diff --git a/src/map/mob.cpp b/src/map/mob.cpp index 5052582a48..64b24c5cad 100644 --- a/src/map/mob.cpp +++ b/src/map/mob.cpp @@ -4,6 +4,7 @@ #include "mob.hpp" #include +#include #include #include "../common/cbasetypes.h" @@ -52,7 +53,6 @@ #define MOB_MAX_DELAY (24*3600*1000) #define MAX_MINCHASE 30 //Max minimum chase value to use for mobs. #define RUDE_ATTACKED_COUNT 1 //After how many rude-attacks should the skill be used? -#define MAX_MOB_CHAT 50 //Max Skill's messages // On official servers, monsters will only seek targets that are closer to walk to than their // search range. The search range is affected depending on if the monster is walking or not. @@ -63,19 +63,39 @@ // Disable this to make monsters not do any path search when looking for a target (old behavior). #define ACTIVEPATHSEARCH -//Dynamic mob database, allows saving of memory when there's big gaps in the mob_db [Skotlex] -//!NOTE: Mob ID is used also as index in mob db -struct mob_db *mob_db_data[MAX_MOB_DB+1]; -struct mob_db *mob_dummy = NULL; //Dummy mob to be returned when a non-existant one is requested. +// Limits for the monster database +#define MIN_MOB_DB 1000 +#define MAX_MOB_DB 3999 +#define MIN_MOB_DB2 20020 +#define MAX_MOB_DB2 31999 -struct mob_db *mob_db(int mob_id) { if (mob_id < 0 || mob_id > MAX_MOB_DB || mob_db_data[mob_id] == NULL) return mob_dummy; return mob_db_data[mob_id]; } +// These define the range of available IDs for clones. [Valaris] +#define MOB_CLONE_START MAX_MOB_DB +#define MOB_CLONE_END MIN_MOB_DB2 + +//Dynamic mob database +std::map mob_db_data; + +struct mob_db *mob_db( int mob_id ){ + try{ + return &mob_db_data.at(mob_id); + }catch( std::out_of_range ){ + return NULL; + } +} // holds Monster Spawn informations std::unordered_map> mob_spawn_data; //Dynamic mob chat database -struct mob_chat *mob_chat_db[MAX_MOB_CHAT+1]; -struct mob_chat *mob_chat(short id) { if(id<=0 || id>MAX_MOB_CHAT || mob_chat_db[id]==NULL) return (struct mob_chat*)NULL; return mob_chat_db[id]; } +std::map mob_chat_db; +struct mob_chat *mob_chat(short id) { + try{ + return &mob_chat_db.at(id); + }catch( std::out_of_range ){ + return NULL; + } +} //Dynamic item drop ratio database for per-item drop ratio modifiers overriding global drop ratios. #define MAX_ITEMRATIO_MOBS 10 @@ -94,14 +114,6 @@ struct s_mob_skill { }; static DBMap *mob_skill_db; /// Monster skill temporary db. s_mob_skill -> mobid -struct mob_db *mobdb_exists(uint16 mob_id) { - struct mob_db *db = mob_db(mob_id); - - if (db == mob_dummy) - return NULL; - return db; -} - static struct eri *item_drop_ers; //For loot drops delay structures. static struct eri *item_drop_list_ers; @@ -121,7 +133,6 @@ static DBMap *mob_summon_db; /// Random Summon DB. struct s_randomsummon_group - /*========================================== * Local prototype declaration (only required thing) *------------------------------------------*/ -static int mob_makedummymobdb(int); static int mob_spawn_guardian_sub(int tid, unsigned int tick, int id, intptr_t data); int mob_skill_id2skill_idx(int mob_id,uint16 skill_id); @@ -315,7 +326,8 @@ static bool mobdb_searchname_sub(uint16 mob_id, const char * const str, bool ful */ uint16 mobdb_searchname_(const char * const str, bool full_cmp) { - for(uint16 mob_id = 0; mob_id <= MAX_MOB_DB; mob_id++) { + for( auto const &mobdb_pair : mob_db_data ) { + const uint16 mob_id = mobdb_pair.first; if( mobdb_searchname_sub(mob_id, str, full_cmp) ) return mob_id; } @@ -332,12 +344,15 @@ uint16 mobdb_searchname(const char * const str) int mobdb_searchname_array_(const char *str, uint16 * out, int size, bool full_cmp) { unsigned short count = 0; - for(uint16 mob_id = 0; mob_id <= MAX_MOB_DB && count < size; mob_id++) { + for( auto const &mobdb_pair : mob_db_data ) { + const uint16 mob_id = mobdb_pair.first; if( mobdb_searchname_sub(mob_id, str, full_cmp) ) { - out[count] = mob_id; + if( count < size ) + out[count] = mob_id; count++; } } + return count; } @@ -351,7 +366,7 @@ int mobdb_searchname_array(const char *str, uint16 * out, int size) *------------------------------------------*/ int mobdb_checkid(const int id) { - if (mob_db(id) == mob_dummy) + if (mob_db(id) == NULL) return 0; if (mob_is_clone(id)) //checkid is used mostly for random ID based code, therefore clone mobs are out of the question. return 0; @@ -363,9 +378,13 @@ int mobdb_checkid(const int id) *------------------------------------------*/ struct view_data * mob_get_viewdata(int mob_id) { - if (mob_db(mob_id) == mob_dummy) + struct mob_db* db = mob_db(mob_id); + + if( db == NULL ){ return NULL; - return &mob_db(mob_id)->vd; + } + + return &db->vd; } /** @@ -507,7 +526,7 @@ int mob_get_random_id(int type, int flag, int lv) mob_id = entry->mob_id; mob = mob_db(mob_id); } while ((rand == 0 || // Skip default first - mob == mob_dummy || + mob == NULL || mob_is_clone(mob_id) || (flag&0x01 && (entry->rate < 1000000 && entry->rate <= rnd() % 1000000)) || (flag&0x02 && lv < mob->lv) || @@ -2954,7 +2973,7 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type) } if (sd) { - struct mob_db *mission_mdb = mobdb_exists(sd->mission_mobid); + struct mob_db *mission_mdb = mob_db(sd->mission_mobid); if ((sd->mission_mobid == md->mob_id) || (battle_config.taekwon_mission_mobname == 1 && mission_mdb && status_get_race2(&md->bl) == RC2_GOBLIN && mission_mdb->race2 == RC2_GOBLIN) || @@ -3789,7 +3808,7 @@ int mob_is_clone(int mob_id) { if(mob_id < MOB_CLONE_START || mob_id > MOB_CLONE_END) return 0; - if (mob_db(mob_id) == mob_dummy) + if (mob_db(mob_id) == NULL) return 0; return mob_id; } @@ -3828,11 +3847,17 @@ int mob_clone_spawn(struct map_session_data *sd, int16 m, int16 x, int16 y, cons if(pc_isdead(sd) && master_id && flag&1) return 0; - ARR_FIND( MOB_CLONE_START, MOB_CLONE_END, mob_id, mob_db_data[mob_id] == NULL ); + ARR_FIND( MOB_CLONE_START, MOB_CLONE_END, mob_id, mob_db(mob_id) == NULL ); if(mob_id >= MOB_CLONE_END) return 0; - db = mob_db_data[mob_id]=(struct mob_db*)aCalloc(1, sizeof(struct mob_db)); + try{ + db = &mob_db_data[mob_id]; + }catch( std::bad_alloc ){ + ShowError( "mob_clone_spawn: Memory allocation for clone %hu failed.\n", mob_id ); + return 0; + } + status = &db->status; strcpy(db->sprite,sd->status.name); strcpy(db->name,sd->status.name); @@ -4012,69 +4037,19 @@ int mob_clone_spawn(struct map_session_data *sd, int16 m, int16 x, int16 y, cons return md->bl.id; } -int mob_clone_delete(struct mob_data *md) -{ +int mob_clone_delete(struct mob_data *md){ const int mob_id = md->mob_id; if (mob_id >= MOB_CLONE_START && mob_id < MOB_CLONE_END - && mob_db_data[mob_id]!=NULL) { - aFree(mob_db_data[mob_id]); - mob_db_data[mob_id]=NULL; + && mob_db(mob_id)!=NULL) { + mob_db_data.erase(mob_id); //Clear references to the db - md->db = mob_dummy; + md->db = NULL; md->vd = NULL; return 1; } return 0; } -// -// Initialization -// -/*========================================== - * Since un-setting [ mob ] up was used, it is an initial provisional value setup. - *------------------------------------------*/ -static int mob_makedummymobdb(int mob_id) -{ - if (mob_dummy != NULL) - { - if (mob_db(mob_id) == mob_dummy) - return 1; //Using the mob_dummy data already. [Skotlex] - if (mob_id > 0 && mob_id <= MAX_MOB_DB) - { //Remove the mob data so that it uses the dummy data instead. - aFree(mob_db_data[mob_id]); - mob_db_data[mob_id] = NULL; - } - return 0; - } - //Initialize dummy data. - mob_dummy = (struct mob_db*)aCalloc(1, sizeof(struct mob_db)); //Initializing the dummy mob. - sprintf(mob_dummy->sprite,"DUMMY"); - sprintf(mob_dummy->name,"Dummy"); - sprintf(mob_dummy->jname,"Dummy"); - mob_dummy->lv=1; - mob_dummy->status.max_hp=1000; - mob_dummy->status.max_sp=1; - mob_dummy->status.rhw.range=1; - mob_dummy->status.rhw.atk=7; - mob_dummy->status.rhw.atk2=10; - mob_dummy->status.str=1; - mob_dummy->status.agi=1; - mob_dummy->status.vit=1; - mob_dummy->status.int_=1; - mob_dummy->status.dex=6; - mob_dummy->status.luk=2; - mob_dummy->status.speed=300; - mob_dummy->status.adelay=1000; - mob_dummy->status.amotion=500; - mob_dummy->status.dmotion=500; - mob_dummy->base_exp=2; - mob_dummy->job_exp=1; - mob_dummy->range2=10; - mob_dummy->range3=10; - - return 0; -} - //Adjusts the drop rate of item according to the criteria given. [Skotlex] static unsigned int mob_drop_adjust(int baserate, int rate_adjust, unsigned short rate_min, unsigned short rate_max) { @@ -4126,39 +4101,29 @@ static bool mob_parse_dbrow(char** str) mob_id = atoi(str[0]); - if (mob_id <= 1000 || mob_id > MAX_MOB_DB) { - ShowError("mob_parse_dbrow: Invalid monster ID %d, must be in range %d-%d.\n", mob_id, 1000, MAX_MOB_DB); - return false; - } - if (pcdb_checkid(mob_id)) { - ShowError("mob_parse_dbrow: Invalid monster ID %d, reserved for player classes.\n", mob_id); - return false; - } - - if (mob_id >= MOB_CLONE_START && mob_id < 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", mob_id, MOB_CLONE_START, MOB_CLONE_END-1, MAX_MOB_DB); + if (!((mob_id > MIN_MOB_DB && mob_id < MAX_MOB_DB) || (mob_id > MIN_MOB_DB2 && mob_id < MAX_MOB_DB2))) { + ShowError("mob_parse_dbrow: Invalid monster ID %d, must be in range %d-%d or %d-%d.\n", mob_id, MIN_MOB_DB, MAX_MOB_DB); return false; } memset(&entry, 0, sizeof(entry)); - db = &entry; - status = &db->status; + status = &entry.status; - db->vd.class_ = mob_id; - 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); + entry.vd.class_ = mob_id; + safestrncpy(entry.sprite, str[1], sizeof(entry.sprite)); + safestrncpy(entry.jname, str[2], sizeof(entry.jname)); + safestrncpy(entry.name, str[3], sizeof(entry.name)); + entry.lv = atoi(str[4]); + entry.lv = cap_value(entry.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); + entry.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); + entry.job_exp = (unsigned int)cap_value(exp, 0, UINT_MAX); status->rhw.range = atoi(str[9]); status->rhw.atk = atoi(str[10]); @@ -4179,20 +4144,20 @@ static bool mob_parse_dbrow(char** str) if (status->dex < 1) status->dex = 1; if (status->luk < 1) status->luk = 1; - db->range2 = atoi(str[20]); - db->range3 = atoi(str[21]); + entry.range2 = atoi(str[20]); + entry.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; + entry.range2 = entry.range2 * battle_config.view_range_rate / 100; + if (entry.range2 < 1) + entry.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; + entry.range3 = entry.range3 * battle_config.chase_range_rate / 100; + if (entry.range3 < entry.range2) + entry.range3 = entry.range2; } //Tests showed that chase range is effectively 2 cells larger than expected [Playtester] - db->range3 += 2; + entry.range3 += 2; status->size = atoi(str[22]); status->race = atoi(str[23]); @@ -4234,18 +4199,18 @@ static bool mob_parse_dbrow(char** str) // 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, status, sizeof(struct status_data)); - status_calc_misc(&data.bl, status, db->lv); + status_calc_misc(&data.bl, status, entry.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); + entry.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 (entry.mexp > 0) { //Mvp if (battle_config.mvp_hp_rate != 100) maxhp = maxhp * (double)battle_config.mvp_hp_rate / 100.; } else //Normal mob @@ -4261,46 +4226,54 @@ static bool mob_parse_dbrow(char** str) // MVP Drops: MVP1id,MVP1per,MVP2id,MVP2per,MVP3id,MVP3per for(i = 0; i < MAX_MVP_DROP; i++) { - db->mvpitem[i].nameid = atoi(str[31+i*2]); + entry.mvpitem[i].nameid = atoi(str[31+i*2]); - if( db->mvpitem[i].nameid ){ - if( itemdb_search(db->mvpitem[i].nameid) ){ - db->mvpitem[i].p = atoi(str[32+i*2]); + if( entry.mvpitem[i].nameid ){ + if( itemdb_search(entry.mvpitem[i].nameid) ){ + entry.mvpitem[i].p = atoi(str[32+i*2]); continue; }else{ - ShowWarning( "Monster \"%s\"(id: %d) is dropping an unknown item \"%s\"(MVP-Drop %d)\n", db->name, mob_id, str[31+i*2], ( i / 2 ) + 1 ); + ShowWarning( "Monster \"%s\"(id: %d) is dropping an unknown item \"%s\"(MVP-Drop %d)\n", entry.name, mob_id, str[31+i*2], ( i / 2 ) + 1 ); } } // Delete the item - db->mvpitem[i].nameid = 0; - db->mvpitem[i].p = 0; + entry.mvpitem[i].nameid = 0; + entry.mvpitem[i].p = 0; } for(i = 0; i < MAX_MOB_DROP; i++) { int k = 31 + MAX_MVP_DROP*2 + i*2; - db->dropitem[i].nameid = atoi(str[k]); + entry.dropitem[i].nameid = atoi(str[k]); - if( db->dropitem[i].nameid ){ - if( itemdb_search( db->dropitem[i].nameid ) ){ - db->dropitem[i].p = atoi(str[k+1]); + if( entry.dropitem[i].nameid ){ + if( itemdb_search( entry.dropitem[i].nameid ) ){ + entry.dropitem[i].p = atoi(str[k+1]); continue; }else{ - ShowWarning( "Monster \"%s\"(id: %d) is dropping an unknown item \"%s\"(Drop %d)\n", db->name, mob_id, str[k], ( i / 2 ) + 1 ); + ShowWarning( "Monster \"%s\"(id: %d) is dropping an unknown item \"%s\"(Drop %d)\n", entry.name, mob_id, str[k], ( i / 2 ) + 1 ); } } // Delete the item - db->dropitem[i].nameid = 0; - db->dropitem[i].p = 0; + entry.dropitem[i].nameid = 0; + entry.dropitem[i].p = 0; } - // Finally insert monster's data into the database. - if (mob_db_data[mob_id] == NULL) - mob_db_data[mob_id] = (struct mob_db*)aCalloc(1, sizeof(struct mob_db)); + db = mob_db(mob_id); - memcpy(mob_db_data[mob_id], db, sizeof(struct mob_db)); + // Finally insert monster's data into the database. + if (db == NULL) { + try{ + db = &mob_db_data[mob_id]; + }catch( std::bad_alloc ){ + ShowError( "Memory allocation for monster %hu failed.\n", mob_id ); + return false; + } + } + + memcpy(db, &entry, sizeof(struct mob_db)); return true; } @@ -4371,10 +4344,11 @@ static int mob_read_sqldb(void) static bool mob_readdb_mobavail(char* str[], int columns, int current) { int mob_id, sprite_id; + struct mob_db *db; mob_id = atoi(str[0]); - if(mob_db(mob_id) == mob_dummy) // invalid class (probably undefined in db) + if( (db=mob_db(mob_id)) == NULL) // invalid class (probably undefined in db) { ShowWarning("mob_readdb_mobavail: Unknown mob id %d.\n", mob_id); return false; @@ -4382,31 +4356,31 @@ static bool mob_readdb_mobavail(char* str[], int columns, int current) sprite_id = atoi(str[1]); - memset(&mob_db_data[mob_id]->vd, 0, sizeof(struct view_data)); - mob_db_data[mob_id]->vd.class_ = sprite_id; + memset(&db->vd, 0, sizeof(struct view_data)); + db->vd.class_ = sprite_id; //Player sprites if(pcdb_checkid(sprite_id) && columns==12) { - mob_db_data[mob_id]->vd.sex=atoi(str[2]); - mob_db_data[mob_id]->vd.hair_style=atoi(str[3]); - mob_db_data[mob_id]->vd.hair_color=atoi(str[4]); - mob_db_data[mob_id]->vd.weapon=atoi(str[5]); - mob_db_data[mob_id]->vd.shield=atoi(str[6]); - mob_db_data[mob_id]->vd.head_top=atoi(str[7]); - mob_db_data[mob_id]->vd.head_mid=atoi(str[8]); - mob_db_data[mob_id]->vd.head_bottom=atoi(str[9]); - mob_db_data[mob_id]->option=atoi(str[10])&~(OPTION_HIDE|OPTION_CLOAK|OPTION_INVISIBLE); - mob_db_data[mob_id]->vd.cloth_color=atoi(str[11]); // Monster player dye option - Valaris + db->vd.sex=atoi(str[2]); + db->vd.hair_style=atoi(str[3]); + db->vd.hair_color=atoi(str[4]); + db->vd.weapon=atoi(str[5]); + db->vd.shield=atoi(str[6]); + db->vd.head_top=atoi(str[7]); + db->vd.head_mid=atoi(str[8]); + db->vd.head_bottom=atoi(str[9]); + db->option=atoi(str[10])&~(OPTION_HIDE|OPTION_CLOAK|OPTION_INVISIBLE); + db->vd.cloth_color=atoi(str[11]); // Monster player dye option - Valaris #ifdef NEW_CARTS - if( mob_db_data[mob_id]->option & OPTION_CART ){ + if( db->option & OPTION_CART ){ ShowWarning("mob_readdb_mobavail: You tried to use a cart for mob id %d. This does not work with setting an option anymore.\n", mob_id ); - mob_db_data[mob_id]->option &= ~OPTION_CART; + db->option &= ~OPTION_CART; } #endif } else if(columns==3) - mob_db_data[mob_id]->vd.head_bottom=atoi(str[2]); // mob equipment [Valaris] + db->vd.head_bottom=atoi(str[2]); // mob equipment [Valaris] else if( columns != 2 ) return false; @@ -4431,13 +4405,13 @@ static bool mob_readdb_group(char* str[], int columns, int current){ } mob_id = atoi(str[1]); - if (mob_id != 0 && mob_db(mob_id) == mob_dummy) { + if (mob_id != 0 && mob_db(mob_id) == NULL) { ShowError("mob_readdb_group: Invalid random monster group '%s'\n", str[0]); return false; } else if (mob_id == 0){ mob_id = atoi(str[3]); - if (mob_db(mob_id) == mob_dummy) { + if (mob_db(mob_id) == NULL) { ShowError("mob_readdb_group: Invalid random monster group '%s'\n", str[0]); return false; } @@ -4474,15 +4448,22 @@ static bool mob_parse_row_chatdb(char* fields[], int columns, int current) msg_id = atoi(fields[0]); - if (msg_id <= 0 || msg_id > MAX_MOB_CHAT){ + if (msg_id <= 0){ ShowError("mob_parse_row_chatdb: Invalid chat ID '%d' in line %d\n", msg_id, current); return false; } - if (mob_chat_db[msg_id] == NULL) - mob_chat_db[msg_id] = (struct mob_chat*)aCalloc(1, sizeof (struct mob_chat)); + ms = mob_chat(msg_id); - ms = mob_chat_db[msg_id]; + if( ms == NULL ){ + try{ + ms = &mob_chat_db[msg_id]; + }catch( std::bad_alloc ){ + ShowError( "mob_parse_row_chatdb: Memory allocation for chat ID '%d' failed.\n", msg_id ); + return false; + } + } + //MSG ID ms->msg_id=msg_id; //Color @@ -4595,10 +4576,11 @@ static bool mob_parse_row_mobskilldb(char** str, int columns, int current) struct mob_skill *ms = NULL; int mob_id; int i = 0, j, tmp; + struct mob_db *mob; mob_id = atoi(str[0]); - if (mob_id > 0 && mob_db(mob_id) == mob_dummy) + if (mob_id > 0 && (mob = mob_db(mob_id)) == NULL) { if (mob_id != last_mob_id) { ShowError("mob_parse_row_mobskilldb: Non existant Mob id %d\n", mob_id); @@ -4624,7 +4606,7 @@ static bool mob_parse_row_mobskilldb(char** str, int columns, int current) if( i == MAX_MOBSKILL ) { if (mob_id != last_mob_id) { - ShowError("mob_parse_row_mobskilldb: Too many skills for monster %d[%s]\n", mob_id, mob_db_data[mob_id]->sprite); + ShowError("mob_parse_row_mobskilldb: Too many skills for monster %d[%s]\n", mob_id, mob->sprite); last_mob_id = mob_id; } return false; @@ -4648,7 +4630,7 @@ static bool mob_parse_row_mobskilldb(char** str, int columns, int current) if (mob_id < 0) ShowError("mob_parse_row_mobskilldb: Invalid Skill ID (%d) for all mobs\n", j); else - ShowError("mob_parse_row_mobskilldb: Invalid Skill ID (%d) for mob %d (%s)\n", j, mob_id, mob_db_data[mob_id]->sprite); + ShowError("mob_parse_row_mobskilldb: Invalid Skill ID (%d) for mob %d (%s)\n", j, mob_id, mob->sprite); return false; } ms->skill_id = j; @@ -4693,13 +4675,13 @@ static bool mob_parse_row_mobskilldb(char** str, int columns, int current) { ShowWarning("mob_parse_row_mobskilldb: Wrong mob skill target for ground skill %d (%s) for %s.\n", ms->skill_id, skill_get_name(ms->skill_id), - mob_id < 0 ? "all mobs" : mob_db_data[mob_id]->sprite); + mob_id < 0 ? "all mobs" : mob->sprite); ms->target = MST_TARGET; } } else if (ms->target > MST_MASTER) { ShowWarning("mob_parse_row_mobskilldb: Wrong mob skill target 'around' for non-ground skill %d (%s) for %s.\n", ms->skill_id, skill_get_name(ms->skill_id), - mob_id < 0 ? "all mobs" : mob_db_data[mob_id]->sprite); + mob_id < 0 ? "all mobs" : mob->sprite); ms->target = MST_TARGET; } @@ -4746,7 +4728,7 @@ static bool mob_parse_row_mobskilldb(char** str, int columns, int current) else ms->emotion = -1; - if(str[18] != NULL && mob_chat_db[atoi(str[18])]!=NULL) + if(str[18] != NULL && mob_chat(atoi(str[18]))!=NULL) ms->msg_id = atoi(str[18]); else ms->msg_id = 0; @@ -4845,18 +4827,19 @@ static bool mob_readdb_race2(char* fields[], int columns, int current) for(i = 1; i < columns; i++) { int mob_id = atoi(fields[i]); + struct mob_db* db = mob_db(mob_id); - if (mob_db(mob_id) == mob_dummy) { + if (db == NULL) { ShowWarning("mob_readdb_race2: Unknown mob id %d for race2 %d.\n", mob_id, race); continue; } - mob_db_data[mob_id]->race2 = (enum e_race2)race; + db->race2 = (enum e_race2)race; // Apply Aegis Class if (race == RC2_GUARDIAN) - mob_db_data[mob_id]->status.class_ = CLASS_GUARDIAN; + db->status.class_ = CLASS_GUARDIAN; else if (race == RC2_BATTLEFIELD) - mob_db_data[mob_id]->status.class_ = CLASS_BATTLEFIELD; + db->status.class_ = CLASS_BATTLEFIELD; } return true; } @@ -4885,7 +4868,7 @@ static bool mob_readdb_itemratio(char* str[], int columns, int current) memset(item_ratio->mob_id, 0, sizeof(item_ratio->mob_id)); for (i = 0; i < columns-2; i++) { uint16 mob_id = atoi(str[i+2]); - if (mob_db(mob_id) == mob_dummy) + if (mob_db(mob_id) == NULL) ShowError("mob_readdb_itemratio: Invalid monster with ID %hu (Item:%hu Col:%d).\n", mob_id, nameid, columns); else item_ratio->mob_id[i] = atoi(str[i+2]); @@ -4910,7 +4893,7 @@ static bool mob_readdb_drop(char* str[], int columns, int current) { struct s_mob_drop *drop; mobid = atoi(str[0]); - if ((mob = mob_db(mobid)) == mob_dummy) { + if ((mob = mob_db(mobid)) == NULL) { ShowError("mob_readdb_drop: Invalid monster with ID %s.\n", str[0]); return false; } @@ -4987,23 +4970,19 @@ static int mob_item_drop_ratio_free(DBKey key, DBData *data, va_list ap) { * Adjust drop ratio for each monster **/ static void mob_drop_ratio_adjust(void){ - unsigned short i; - - for( i = 0; i <= MAX_MOB_DB; i++ ){ + for( auto &pair : mob_db_data ){ struct mob_db *mob; struct item_data *id; unsigned short nameid; int j, rate, rate_adjust = 0, mob_id; - mob = mob_db(i); + mob_id = pair.first; + mob = &pair.second; - // Skip dummy mobs. - if( mob == mob_dummy ){ + if( mob_is_clone( mob_id ) ){ continue; } - mob_id = i; - for( j = 0; j < MAX_MVP_DROP_TOTAL; j++ ){ nameid = mob->mvpitem[j].nameid; rate = mob->mvpitem[j].p; @@ -5026,7 +5005,7 @@ static void mob_drop_ratio_adjust(void){ // Item is not known anymore(should never happen) if( !id ){ - ShowWarning( "Monster \"%s\"(id:%d) is dropping an unknown item(id: %d)\n", mob->name, mob_id, nameid ); + ShowWarning( "Monster \"%s\"(id:%hu) is dropping an unknown item(id: %d)\n", mob->name, mob_id, nameid ); mob->mvpitem[j].nameid = 0; mob->mvpitem[j].p = 0; continue; @@ -5056,7 +5035,7 @@ static void mob_drop_ratio_adjust(void){ // Item is not known anymore(should never happen) if( !id ){ - ShowWarning( "Monster \"%s\"(id:%d) is dropping an unknown item(id: %d)\n", mob->name, mob_id, nameid ); + ShowWarning( "Monster \"%s\"(id:%hu) is dropping an unknown item(id: %d)\n", mob->name, mob_id, nameid ); mob->dropitem[j].nameid = 0; mob->dropitem[j].p = 0; continue; @@ -5172,30 +5151,27 @@ static void mob_skill_db_set_single_sub(struct mob_db *mob, struct s_mob_skill * * @param skill **/ static void mob_skill_db_set_single(struct s_mob_skill *skill) { - struct mob_db *mob = NULL; - nullpo_retv(skill); // Specific monster if (skill->mob_id >= 0) { - mob = mob_db(skill->mob_id); - if (mob != mob_dummy) - //memcpy(&mob->skill, skill, sizeof(skill)); + struct mob_db *mob = mob_db(skill->mob_id); + if (mob != NULL) mob_skill_db_set_single_sub(mob, skill); } // Global skill else { - uint16 i, id = skill->mob_id; + uint16 id = skill->mob_id; id *= -1; - for (i = 0; i < MAX_MOB_DB; i++) { - mob = mob_db(i); - if (mob == mob_dummy) + for( auto &pair : mob_db_data ){ + if ( mob_is_clone(pair.first) ){ continue; - if ( (!(id&1) && status_has_mode(&mob->status,MD_STATUS_IMMUNE)) // Bosses - || (!(id&2) && !status_has_mode(&mob->status,MD_STATUS_IMMUNE)) // Normal monsters + } + if ( (!(id&1) && status_has_mode(&pair.second.status,MD_STATUS_IMMUNE)) // Bosses + || (!(id&2) && !status_has_mode(&pair.second.status,MD_STATUS_IMMUNE)) // Normal monsters ) continue; - mob_skill_db_set_single_sub(mob, skill); + mob_skill_db_set_single_sub(&pair.second, skill); } } @@ -5299,7 +5275,7 @@ static void mob_load(void) sv_readdb(dbsubpath1, "mob_avail.txt", ',', 2, 12, -1, &mob_readdb_mobavail,silent); sv_readdb(dbsubpath2, "mob_race2_db.txt", ',', 2, MAX_RACE2_MOBS, -1, &mob_readdb_race2, silent); sv_readdb(dbsubpath1, "mob_item_ratio.txt", ',', 2, 2+MAX_ITEMRATIO_MOBS, -1, &mob_readdb_itemratio, silent); - sv_readdb(dbsubpath1, "mob_chat_db.txt", '#', 3, 3, MAX_MOB_CHAT, &mob_parse_row_chatdb, silent); + sv_readdb(dbsubpath1, "mob_chat_db.txt", '#', 3, 3, -1, &mob_parse_row_chatdb, silent); sv_readdb(dbsubpath2, "mob_random_db.txt", ',', 4, 4, -1, &mob_readdb_group, silent); sv_readdb(dbsubpath2, "mob_branch.txt", ',', 4, 4, -1, &mob_readdb_group, silent); sv_readdb(dbsubpath2, "mob_poring.txt", ',', 4, 4, -1, &mob_readdb_group, silent); @@ -5320,9 +5296,6 @@ static void mob_load(void) * Initialize monster data */ void mob_db_load(bool is_reload){ - memset(mob_db_data,0,sizeof(mob_db_data)); //Clear the array - mob_db_data[0] = (struct mob_db*)aCalloc(1, sizeof (struct mob_db)); //This mob is used for random spawns - mob_makedummymobdb(0); //The first time this is invoked, it creates the dummy mob if( !is_reload ) { // on mobdbreload it's not neccessary to execute this // item ers needs to be allocated only once @@ -5335,6 +5308,41 @@ void mob_db_load(bool is_reload){ mob_load(); } +/** + * Re-link monster drop data with item data + * Fixes the need of a @reloadmobdb after a @reloaditemdb + * @author Epoque + */ +void mob_reload_itemmob_data(void) { + for( auto const &pair : mob_db_data ){ + int d, k; + + if( mob_is_clone( pair.first ) ){ + continue; + } + + for(d = 0; d < MAX_MOB_DROP_TOTAL; d++) { + struct item_data *id; + if( !pair.second.dropitem[d].nameid ) + continue; + id = itemdb_search(pair.second.dropitem[d].nameid); + + for (k = 0; k < MAX_SEARCH; k++) { + if (id->mob[k].chance <= pair.second.dropitem[d].p) + break; + } + + if (k == MAX_SEARCH) + continue; + + if (id->mob[k].id != pair.first) + memmove(&id->mob[k+1], &id->mob[k], (MAX_SEARCH-k-1)*sizeof(id->mob[0])); + id->mob[k].chance = pair.second.dropitem[d].p; + id->mob[k].id = pair.first; + } + } +} + /** * Apply the proper view data on monsters during mob_db reload. * @param md: Mob to adjust @@ -5424,28 +5432,9 @@ void do_init_mob(void){ * Clean memory usage. *------------------------------------------*/ void do_final_mob(bool is_reload){ - int i; - if (mob_dummy) - { - aFree(mob_dummy); - mob_dummy = NULL; - } - for (i = 0; i <= MAX_MOB_DB; i++) - { - if (mob_db_data[i] != NULL) - { - aFree(mob_db_data[i]); - mob_db_data[i] = NULL; - } - } - for (i = 0; i <= MAX_MOB_CHAT; i++) - { - if (mob_chat_db[i] != NULL) - { - aFree(mob_chat_db[i]); - mob_chat_db[i] = NULL; - } - } + mob_db_data.clear(); + mob_chat_db.clear(); + mob_item_drop_ratio->destroy(mob_item_drop_ratio,mob_item_drop_ratio_free); mob_skill_db->destroy(mob_skill_db, mob_skill_db_free); mob_summon_db->destroy(mob_summon_db, mob_summon_db_free); diff --git a/src/map/mob.hpp b/src/map/mob.hpp index f876419baa..b25254eee0 100644 --- a/src/map/mob.hpp +++ b/src/map/mob.hpp @@ -13,11 +13,6 @@ struct guardian_data; -// Change this to increase the table size in your mob_db to accomodate a larger mob database. -// Be sure to note that IDs 4001 to 4048 are reserved for advanced/baby/expanded classes. -// Notice that the last 1000 entries are used for player clones, so always set this to desired value +1000 -#define MAX_MOB_DB 5000 - //The number of drops all mobs have and the max drop-slot that the steal skill will attempt to steal from. #define MAX_MOB_DROP 10 #define MAX_MVP_DROP 3 @@ -39,10 +34,6 @@ struct guardian_data; //Distance that slaves should keep from their master. #define MOB_SLAVEDISTANCE 2 -// These define the range of available IDs for clones. [Valaris] -#define MOB_CLONE_START (MAX_MOB_DB-999) -#define MOB_CLONE_END MAX_MOB_DB - //Used to determine default enemy type of mobs (for use in eachinrange calls) #define DEFAULT_ENEMY_TYPE(md) (md->special_state.ai?BL_CHAR:BL_MOB|BL_PC|BL_HOM|BL_MER) @@ -294,7 +285,6 @@ struct item_drop_list { }; struct mob_db *mob_db(int mob_id); -struct mob_db *mobdb_exists(uint16 mob_id); uint16 mobdb_searchname(const char * const str); int mobdb_searchname_array(const char *str, uint16 * out, int size); int mobdb_checkid(const int id); @@ -359,6 +349,7 @@ int mob_is_clone(int mob_id); int mob_clone_spawn(struct map_session_data *sd, int16 m, int16 x, int16 y, const char *event, int master_id, enum e_mode mode, int flag, unsigned int duration); int mob_clone_delete(struct mob_data *md); +void mob_reload_itemmob_data(void); void mob_reload(void); void mob_add_spawn(uint16 mob_id, const struct spawn_info& new_spawn); diff --git a/src/map/quest.cpp b/src/map/quest.cpp index 87c1293680..79d680514f 100644 --- a/src/map/quest.cpp +++ b/src/map/quest.cpp @@ -514,7 +514,7 @@ void quest_read_txtdb(void) if (!mob_id) continue; - if (mobdb_exists(mob_id) == NULL) { + if (mob_db(mob_id) == NULL) { ShowWarning("quest_read_txtdb: Invalid monster as objective '%d' in line %d.\n", mob_id, ln); continue; } @@ -529,7 +529,7 @@ void quest_read_txtdb(void) if (!nameid) continue; - if (!itemdb_exists(nameid) || (mob_id && mobdb_exists(mob_id) == NULL)) { + if (!itemdb_exists(nameid) || (mob_id && mob_db(mob_id) == NULL)) { ShowWarning("quest_read_txtdb: Invalid item reward '%d' (mob %d, optional) in line %d.\n", nameid, mob_id, ln); continue; } diff --git a/src/map/script.cpp b/src/map/script.cpp index 629bce0073..b89feca168 100644 --- a/src/map/script.cpp +++ b/src/map/script.cpp @@ -17069,7 +17069,7 @@ BUILDIN_FUNC(addmonsterdrop) if(c) { //Fill in the slot with the item and rate mob->dropitem[c].nameid = item_id; mob->dropitem[c].p = (rate > 10000)?10000:rate; - itemdb_reload_itemmob_data(); // Reload the mob search data stored in the item_data + mob_reload_itemmob_data(); // Reload the mob search data stored in the item_data script_pushint(st,1); } else //No place to put the new drop script_pushint(st,0); @@ -17115,7 +17115,7 @@ BUILDIN_FUNC(delmonsterdrop) if(mob->dropitem[i].nameid == item_id) { mob->dropitem[i].nameid = 0; mob->dropitem[i].p = 0; - itemdb_reload_itemmob_data(); // Reload the mob search data stored in the item_data + mob_reload_itemmob_data(); // Reload the mob search data stored in the item_data script_pushint(st,1); return SCRIPT_CMD_SUCCESS; }