|
@@ -87,6 +87,7 @@ static struct eri *item_drop_list_ers;
|
|
|
|
|
|
MobSummonDatabase mob_summon_db;
|
|
|
MobChatDatabase mob_chat_db;
|
|
|
+MapDropDatabase map_drop_db;
|
|
|
|
|
|
/*==========================================
|
|
|
* Local prototype declaration (only required thing)
|
|
@@ -2836,8 +2837,7 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type)
|
|
|
|
|
|
// Ore Discovery [Celest]
|
|
|
if (sd == mvp_sd && pc_checkskill(sd,BS_FINDINGORE)>0 && battle_config.finding_ore_rate/10 >= rnd()%10000) {
|
|
|
- struct s_mob_drop mobdrop;
|
|
|
- memset(&mobdrop, 0, sizeof(struct s_mob_drop));
|
|
|
+ struct s_mob_drop mobdrop = {};
|
|
|
mobdrop.nameid = itemdb_group.get_random_item_id(IG_FINDINGORE,1);
|
|
|
ditem = mob_setdropitem(&mobdrop, 1, md->mob_id);
|
|
|
mob_item_drop(md, dlist, ditem, 0, battle_config.finding_ore_rate/10, homkillonly || merckillonly);
|
|
@@ -2848,7 +2848,6 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type)
|
|
|
t_itemid dropid = 0;
|
|
|
|
|
|
for (const auto &it : sd->add_drop) {
|
|
|
- struct s_mob_drop mobdrop;
|
|
|
if (!&it || (!it.nameid && !it.group))
|
|
|
continue;
|
|
|
if ((it.race < RC_NONE_ && it.race == -md->mob_id) || //Race < RC_NONE_, use mob_id
|
|
@@ -2869,7 +2868,7 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type)
|
|
|
if (rnd()%10000 >= drop_rate)
|
|
|
continue;
|
|
|
dropid = (it.nameid > 0) ? it.nameid : itemdb_group.get_random_item_id(it.group,1);
|
|
|
- memset(&mobdrop, 0, sizeof(struct s_mob_drop));
|
|
|
+ struct s_mob_drop mobdrop = {};
|
|
|
mobdrop.nameid = dropid;
|
|
|
|
|
|
mob_item_drop(md, dlist, mob_setdropitem(&mobdrop,1,md->mob_id), 0, drop_rate, homkillonly || merckillonly);
|
|
@@ -2889,6 +2888,37 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type)
|
|
|
for (i = 0; i < md->lootitem_count; i++)
|
|
|
mob_item_drop(md, dlist, mob_setlootitem(&md->lootitems[i], md->mob_id), 1, 10000, homkillonly || merckillonly);
|
|
|
}
|
|
|
+
|
|
|
+ // Process map specific drops
|
|
|
+ std::shared_ptr<s_map_drops> mapdrops;
|
|
|
+
|
|
|
+ // If it is an instance map, we check for map specific drops of the original map
|
|
|
+ if( map[md->bl.m].instance_id > 0 ){
|
|
|
+ mapdrops = map_drop_db.find( map[md->bl.m].instance_src_map );
|
|
|
+ }else{
|
|
|
+ mapdrops = map_drop_db.find( md->bl.m );
|
|
|
+ }
|
|
|
+
|
|
|
+ if( mapdrops != nullptr ){
|
|
|
+ // Process map wide drops
|
|
|
+ for( const auto& it : mapdrops->globals ){
|
|
|
+ if( rnd_chance( it.second->rate, 10000 ) ){
|
|
|
+ mob_item_drop( md, dlist, mob_setdropitem( it.second.get(), 1, md->mob_id ), 0, it.second->rate, homkillonly || merckillonly );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Process map drops for this specific mob
|
|
|
+ const auto& specific = mapdrops->specific.find( md->mob_id );
|
|
|
+
|
|
|
+ if( specific != mapdrops->specific.end() ){
|
|
|
+ for( const auto& it : specific->second ){
|
|
|
+ if( rnd_chance( it.second->rate, 10000 ) ){
|
|
|
+ mob_item_drop( md, dlist, mob_setdropitem( it.second.get(), 1, md->mob_id ), 0, it.second->rate, homkillonly || merckillonly );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
if (dlist->item) //There are drop items.
|
|
|
add_timer(tick + (!battle_config.delay_battle_damage?500:0), mob_delay_item_drop, 0, (intptr_t)dlist);
|
|
|
else //No drops
|
|
@@ -6320,6 +6350,170 @@ static void mob_drop_ratio_adjust(void){
|
|
|
mob_item_drop_ratio.clear();
|
|
|
}
|
|
|
|
|
|
+const std::string MapDropDatabase::getDefaultLocation(){
|
|
|
+ return std::string( db_path ) + "/map_drops.yml";
|
|
|
+}
|
|
|
+
|
|
|
+uint64 MapDropDatabase::parseBodyNode( const ryml::NodeRef& node ){
|
|
|
+ std::string mapname;
|
|
|
+
|
|
|
+ if( !this->asString( node, "Map", mapname ) ){
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ uint16 mapindex = mapindex_name2idx( mapname.c_str(), nullptr );
|
|
|
+
|
|
|
+ if( mapindex == 0 ){
|
|
|
+ this->invalidWarning( node["Map"], "Unknown map \"%s\".\n", mapname.c_str() );
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ int16 mapid = map_mapindex2mapid( mapindex );
|
|
|
+
|
|
|
+ if( mapid < 0 ){
|
|
|
+ // Silently ignore. Map might be on a different map-server
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ std::shared_ptr<s_map_drops> mapdrops = this->find( mapid );
|
|
|
+ bool exists = mapdrops != nullptr;
|
|
|
+
|
|
|
+ if( !exists ){
|
|
|
+ mapdrops = std::make_shared<s_map_drops>();
|
|
|
+ mapdrops->mapid = mapid;
|
|
|
+ }
|
|
|
+
|
|
|
+ if( this->nodeExists( node, "GlobalDrops" ) ){
|
|
|
+ const ryml::NodeRef& globalNode = node["GlobalDrops"];
|
|
|
+
|
|
|
+ for( const auto& it : globalNode ){
|
|
|
+ if( !this->parseDrop( it, mapdrops->globals ) ){
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if( this->nodeExists( node, "SpecificDrops" ) ){
|
|
|
+ const ryml::NodeRef& specificNode = node["SpecificDrops"];
|
|
|
+
|
|
|
+ for( const auto& monsterNode : specificNode ){
|
|
|
+ if( !this->nodesExist( monsterNode, { "Monster", "Drops" } ) ){
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ std::string mobname;
|
|
|
+
|
|
|
+ if( !this->asString( monsterNode, "Monster", mobname ) ){
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ std::shared_ptr<s_mob_db> mob = mobdb_search_aegisname( mobname.c_str() );
|
|
|
+
|
|
|
+ if( mob == nullptr ){
|
|
|
+ this->invalidWarning( monsterNode["Monster"], "Unknown monster \"%s\".\n", mobname.c_str() );
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ std::unordered_map<uint16, std::shared_ptr<s_mob_drop>>& specificDrops = mapdrops->specific[mob->id];
|
|
|
+
|
|
|
+ for( const auto& it : monsterNode["Drops"] ){
|
|
|
+ if( !this->parseDrop( it, specificDrops ) ){
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if( !exists ){
|
|
|
+ this->put( mapid, mapdrops );
|
|
|
+ }
|
|
|
+
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+
|
|
|
+bool MapDropDatabase::parseDrop( const ryml::NodeRef& node, std::unordered_map<uint16, std::shared_ptr<s_mob_drop>>& drops ){
|
|
|
+ uint16 index;
|
|
|
+
|
|
|
+ if( !this->asUInt16( node, "Index", index ) ){
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ std::shared_ptr<s_mob_drop> drop = util::umap_find( drops, index );
|
|
|
+ bool exists = drop != nullptr;
|
|
|
+
|
|
|
+ if( !exists ){
|
|
|
+ if( !this->nodesExist( node, { "Item", "Rate" } ) ){
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ drop = std::make_shared<s_mob_drop>();
|
|
|
+ drop->steal_protected = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if( this->nodeExists( node, "Item" ) ){
|
|
|
+ std::string itemname;
|
|
|
+
|
|
|
+ if( !this->asString( node, "Item", itemname ) ){
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ std::shared_ptr<item_data> item = item_db.search_aegisname( itemname.c_str() );
|
|
|
+
|
|
|
+ if( item == nullptr ){
|
|
|
+ this->invalidWarning( node["Item"], "Item %s does not exist.\n", itemname.c_str() );
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ drop->nameid = item->nameid;
|
|
|
+ }
|
|
|
+
|
|
|
+ if( this->nodeExists( node, "Rate" ) ){
|
|
|
+ uint16 rate;
|
|
|
+
|
|
|
+ if( !this->asUInt16Rate( node, "Rate", rate ) ){
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if( rate == 0 ){
|
|
|
+ if( exists ){
|
|
|
+ drops.erase( index );
|
|
|
+ return true;
|
|
|
+ }else{
|
|
|
+ this->invalidWarning( node["Rate"], "Rate %" PRIu16 " is below minimum of 1.\n", rate );
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }else if( rate > 10000 ){
|
|
|
+ this->invalidWarning( node["Rate"], "Rate %" PRIu16 " exceeds maximum of 10000.\n", rate );
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ drop->rate = rate;
|
|
|
+ }
|
|
|
+
|
|
|
+ if( this->nodeExists( node, "RandomOptionGroup" ) ){
|
|
|
+ std::string name;
|
|
|
+
|
|
|
+ if( !this->asString( node, "RandomOptionGroup", name ) ){
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if( !random_option_group.option_get_id( name, drop->randomopt_group ) ){
|
|
|
+ this->invalidWarning( node["RandomOptionGroup"], "Unknown random option group \"%s\".\n", name.c_str() );
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ if( !exists ){
|
|
|
+ drop->randomopt_group = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if( !exists ){
|
|
|
+ drops[drop->nameid] = drop;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* Copy skill from DB to monster
|
|
|
* @param mob Monster DB entry
|
|
@@ -6431,6 +6625,7 @@ static void mob_load(void)
|
|
|
mob_item_drop_ratio.load();
|
|
|
mob_avail_db.load();
|
|
|
mob_summon_db.load();
|
|
|
+ map_drop_db.load();
|
|
|
|
|
|
mob_drop_ratio_adjust();
|
|
|
mob_skill_db_set();
|
|
@@ -6593,9 +6788,9 @@ void do_final_mob(bool is_reload){
|
|
|
mob_db.clear();
|
|
|
mob_chat_db.clear();
|
|
|
mob_skill_db.clear();
|
|
|
-
|
|
|
mob_item_drop_ratio.clear();
|
|
|
mob_summon_db.clear();
|
|
|
+ map_drop_db.clear();
|
|
|
if( !is_reload ) {
|
|
|
ers_destroy(item_drop_ers);
|
|
|
ers_destroy(item_drop_list_ers);
|