Implemented map specific drops (#7156)

Thanks to @Atemo and @aleos89.
This commit is contained in:
Lemongrass3110 2022-08-09 19:15:13 +02:00 committed by GitHub
parent c5031982d2
commit c06492def6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 365 additions and 5 deletions

View File

@ -0,0 +1,44 @@
# This file is a part of rAthena.
# Copyright(C) 2022 rAthena Development Team
# https://rathena.org - https://github.com/rathena
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###########################################################################
# Map Drop Database
###########################################################################
#
# Map Drop Settings
#
###########################################################################
# - Map Name of the map.
# GlobalDrops Drops for all monsters on this map. (Default: empty)
# These drops are unaffected by server drop rate and cannot be stolen.
# - Index Unique index of the drop.
# Item Item name.
# Rate Drop rate of item.
# RandomOptionGroup Random Option Group applied to item on drop. (Default: None)
# SpecificDrops Drops for specific monsters on this map. (Default: empty)
# - Monster Monster name.
# Drops Drops for this specific monster. (Default: empty)
# These drops are unaffected by server drop rate and cannot be stolen.
# - Index Unique index of the drop.
# Item Item name.
# Rate Drop rate of item.
# RandomOptionGroup Random Option Group applied to item on drop. (Default: None)
###########################################################################
Header:
Type: MAP_DROP_DB
Version: 1

50
db/map_drops.yml Normal file
View File

@ -0,0 +1,50 @@
# This file is a part of rAthena.
# Copyright(C) 2022 rAthena Development Team
# https://rathena.org - https://github.com/rathena
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###########################################################################
# Map Drop Database
###########################################################################
#
# Map Drop Settings
#
###########################################################################
# - Map Name of the map.
# GlobalDrops Drops for all monsters on this map. (Default: empty)
# These drops are unaffected by server drop rate and cannot be stolen.
# - Index Unique index of the drop.
# Item Item name.
# Rate Drop rate of item.
# RandomOptionGroup Random Option Group applied to item on drop. (Default: None)
# SpecificDrops Drops for specific monsters on this map. (Default: empty)
# - Monster Monster name.
# Drops Drops for this specific monster. (Default: empty)
# These drops are unaffected by server drop rate and cannot be stolen.
# - Index Unique index of the drop.
# Item Item name.
# Rate Drop rate of item.
# RandomOptionGroup Random Option Group applied to item on drop. (Default: None)
###########################################################################
Header:
Type: MAP_DROP_DB
Version: 1
Footer:
Imports:
- Path: db/re/map_drops.yml
Mode: Renewal
- Path: db/import/map_drops.yml

44
db/re/map_drops.yml Normal file
View File

@ -0,0 +1,44 @@
# This file is a part of rAthena.
# Copyright(C) 2022 rAthena Development Team
# https://rathena.org - https://github.com/rathena
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
###########################################################################
# Map Drop Database
###########################################################################
#
# Map Drop Settings
#
###########################################################################
# - Map Name of the map.
# GlobalDrops Drops for all monsters on this map. (Default: empty)
# These drops are unaffected by server drop rate and cannot be stolen.
# - Index Unique index of the drop.
# Item Item name.
# Rate Drop rate of item.
# RandomOptionGroup Random Option Group applied to item on drop. (Default: None)
# SpecificDrops Drops for specific monsters on this map. (Default: empty)
# - Monster Monster name.
# Drops Drops for this specific monster. (Default: empty)
# These drops are unaffected by server drop rate and cannot be stolen.
# - Index Unique index of the drop.
# Item Item name.
# Rate Drop rate of item.
# RandomOptionGroup Random Option Group applied to item on drop. (Default: None)
###########################################################################
Header:
Type: MAP_DROP_DB
Version: 1

View File

@ -343,6 +343,7 @@
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\level_penalty.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\level_penalty.yml')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\magicmushroom_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\magicmushroom_db.yml')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\map_cache.dat" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\map_cache.dat')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\map_drops.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\map_drops.yml')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\map_index.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\map_index.txt')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\mercenary_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\mercenary_db.yml')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\mob_avail.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\mob_avail.yml')" />

View File

@ -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);

View File

@ -286,6 +286,32 @@ public:
extern MobDatabase mob_db;
struct s_map_mob_drop{
uint16 mob_id;
std::shared_ptr<s_mob_drop> drop;
};
struct s_map_drops{
uint16 mapid;
std::unordered_map<uint16, std::shared_ptr<s_mob_drop>> globals;
std::unordered_map<uint16, std::unordered_map<uint16, std::shared_ptr<s_mob_drop>>> specific;
};
class MapDropDatabase : public TypesafeYamlDatabase<uint16, s_map_drops>{
public:
MapDropDatabase() : TypesafeYamlDatabase( "MAP_DROP_DB", 1 ){
}
const std::string getDefaultLocation() override;
uint64 parseBodyNode( const ryml::NodeRef& node ) override;
private:
bool parseDrop( const ryml::NodeRef& node, std::unordered_map<uint16, std::shared_ptr<s_mob_drop>>& drops );
};
extern MapDropDatabase map_drop_db;
struct mob_data {
struct block_list bl;
struct unit_data ud;