Переглянути джерело

Ore Discovery, Equip-Granted Item Group Drops (#8846)

- Ore Discovery and equip-granted item group drops now use the official algorithm
  * First a random item from the group is picked (equal chance for all) 
  * Then this item is attempted to be dropped according to its drop rate
  * This happens every kill on all items that grant item group drops (100% chance)
- Ore Discovery now uses the same item group as Gaia Sword (same chances as well)
- Replaced finding_ore_rate with item_group_rate config (including min and max) 
  * Applies to Ore Discovery, Gaia Sword, Jewel Sword, Blazzer Card, Tengu Card and Dokkaebi Horn 
  * Note: Wild Poring Rider is currently using the wrong bonus and needs to fixed separately
- Fixed chance of Cracked Diamond to drop from Gaia Sword (0.27% -> 0.02%)
- Fixed Tengu Card dropping Fire Resist Potion instead of Earth Resist Potion
- Added helper function rnd_chance_official to calculate chances the official way 
  * It has a small official inaccuracy which results in a ~0.005% higher success chance 
  * It is already used by the new item group drop algorithm, but not by anything else
- Autoloot now properly considers the drop rate of the item within an item group
- Items from Ore Discovery now always drop last
- Ore Discovery no longer requires you to be the killer, but you still need loot priority
- Random item givers (e.g. Old Blue Box) are now more efficient and properly consider the rate
- Fixes #8842
Playtester 5 місяців тому
батько
коміт
97a124ca0e

+ 7 - 4
conf/battle/drops.conf

@@ -79,11 +79,17 @@ item_drop_mvp_min: 1
 item_drop_mvp_max: 10000
 item_drop_mvp_mode: 0
 
-// The rate adjustment for card-granted item drops.
+// The rate adjustment for equip-granted item drops.
 item_rate_adddrop: 100
 item_drop_add_min: 1
 item_drop_add_max: 10000
 
+// The rate adjustment for items inside of equip-granted item group drops.
+// This is used by Ore Discovery and items such as Gaia Sword, Jewel Sword, Blazzer Card, Tengu Card and Dokkaebi Horn.
+item_group_rate: 100
+item_group_drop_min: 1
+item_group_drop_max: 10000
+
 // Rate adjustment for Treasure Box drops (these override all other modifiers)
 item_rate_treasure: 100
 item_drop_treasure_min: 1
@@ -130,9 +136,6 @@ drops_by_luk: 0
 // (So at 100 luk, everything will have double chance of dropping).
 drops_by_luk2: 0
 
-// The rate of monsters dropping ores by the skill Ore Discovery (Default is 100)
-finding_ore_rate: 100
-
 // Whether or not Marine Spheres and Floras summoned by Alchemist drop items?
 // This setting has three available values:
 // 0: Nothing drops.

+ 2 - 2
db/pre-re/item_db_equip.yml

@@ -1183,7 +1183,7 @@ Body:
     EquipLevelMin: 68
     Refineable: true
     Script: |
-      bonus2 bAddMonsterDropItemGroup,IG_Jewel,100;
+      bonus2 bAddMonsterDropItemGroup,IG_Jewel,10000;
   - Id: 1143
     AegisName: Gaia_Sword
     Name: Gaia Sword
@@ -1209,7 +1209,7 @@ Body:
     EquipLevelMin: 74
     Refineable: true
     Script: |
-      bonus2 bAddMonsterDropItemGroup,IG_Ore,30;
+      bonus2 bAddMonsterDropItemGroup,IG_Ore,10000;
   - Id: 1144
     AegisName: Sasimi
     Name: Sashimi

+ 2 - 2
db/pre-re/item_db_etc.yml

@@ -5320,7 +5320,7 @@ Body:
     Flags:
       BuyingStore: true
     Script: |
-      bonus2 bAddMonsterDropItemGroup,IG_Food,600;
+      bonus2 bAddMonsterDropItemGroup,IG_Food,10000;
   - Id: 4216
     AegisName: Sasquatch_Card
     Name: Sasquatch Card
@@ -6230,7 +6230,7 @@ Body:
     Flags:
       BuyingStore: true
     Script: |
-      bonus2 bAddMonsterDropItemGroup,IG_Recovery,600;
+      bonus2 bAddMonsterDropItemGroup,IG_Recovery,10000;
   - Id: 4283
     AegisName: Greatest_General_Card
     Name: Greatest General Card

+ 2 - 66
db/pre-re/item_group_db.yml

@@ -4644,70 +4644,6 @@ Body:
           - Index: 3
             Item: Lime_Green_Pts
             Rate: 1
-  - Group: FINDINGORE
-    SubGroups:
-      - SubGroup: 1
-        List:
-          - Index: 0
-            Item: Emperium
-            Rate: 3
-          - Index: 1
-            Item: Oridecon_Stone
-            Rate: 20
-          - Index: 2
-            Item: Elunium_Stone
-            Rate: 20
-          - Index: 3
-            Item: Gold
-            Rate: 2
-          - Index: 4
-            Item: Oridecon
-            Rate: 10
-          - Index: 5
-            Item: Elunium
-            Rate: 10
-          - Index: 6
-            Item: Boody_Red
-            Rate: 30
-          - Index: 7
-            Item: Crystal_Blue
-            Rate: 30
-          - Index: 8
-            Item: Wind_Of_Verdure
-            Rate: 30
-          - Index: 9
-            Item: Yellow_Live
-            Rate: 30
-          - Index: 10
-            Item: Flame_Heart
-            Rate: 15
-          - Index: 11
-            Item: Mistic_Frozen
-            Rate: 15
-          - Index: 12
-            Item: Rough_Wind
-            Rate: 15
-          - Index: 13
-            Item: Great_Nature
-            Rate: 15
-          - Index: 14
-            Item: Iron
-            Rate: 80
-          - Index: 15
-            Item: Steel
-            Rate: 50
-          - Index: 16
-            Item: Iron_Ore
-            Rate: 100
-          - Index: 17
-            Item: Coal
-            Rate: 60
-          - Index: 18
-            Item: Phracon
-            Rate: 95
-          - Index: 19
-            Item: Emveretarcon
-            Rate: 55
   - Group: FIRSTAID
     SubGroups:
       - SubGroup: 1
@@ -7018,7 +6954,7 @@ Body:
             Rate: 3
           - Index: 18
             Item: Crystal_Jewel___
-            Rate: 27
+            Rate: 2
           - Index: 19
             Item: Glass_Bead
             Rate: 50
@@ -7688,7 +7624,7 @@ Body:
             Item: Red_Slim_Potion
             Rate: 10
           - Index: 4
-            Item: Resist_Fire
+            Item: Resist_Earth
             Rate: 10
           - Index: 5
             Item: Yellow_Herb

+ 3 - 3
db/re/item_db_equip.yml

@@ -1228,7 +1228,7 @@ Body:
     EquipLevelMin: 68
     Refineable: true
     Script: |
-      bonus2 bAddMonsterDropItemGroup,IG_Jewel,100;
+      bonus2 bAddMonsterDropItemGroup,IG_Jewel,10000;
   - Id: 1143
     AegisName: Gaia_Sword
     Name: Gaia Sword
@@ -1254,7 +1254,7 @@ Body:
     EquipLevelMin: 74
     Refineable: true
     Script: |
-      bonus2 bAddMonsterDropItemGroup,IG_Ore,30;
+      bonus2 bAddMonsterDropItemGroup,IG_Ore,10000;
   - Id: 1144
     AegisName: Sasimi
     Name: Sashimi
@@ -50312,7 +50312,7 @@ Body:
     Trade:
       NoDrop: true
     Script: |
-      bonus2 bAddMonsterDropItemGroup,IG_Jewel,100;
+      bonus2 bAddMonsterDropItemGroup,IG_Jewel,10000;
       bonus3 bAutoSpell,"MC_MAMMONITE",5,70;
   - Id: 5574
     AegisName: Pencil_In_Mouth

+ 2 - 2
db/re/item_db_etc.yml

@@ -5662,7 +5662,7 @@ Body:
       BuyingStore: true
       DropEffect: CLIENT
     Script: |
-      bonus2 bAddMonsterDropItemGroup,IG_Food,600;
+      bonus2 bAddMonsterDropItemGroup,IG_Food,10000;
   - Id: 4216
     AegisName: Sasquatch_Card
     Name: Sasquatch Card
@@ -6639,7 +6639,7 @@ Body:
       BuyingStore: true
       DropEffect: CLIENT
     Script: |
-      bonus2 bAddMonsterDropItemGroup,IG_Recovery,600;
+      bonus2 bAddMonsterDropItemGroup,IG_Recovery,10000;
   - Id: 4283
     AegisName: Greatest_General_Card
     Name: Greatest General Card

+ 2 - 66
db/re/item_group_db.yml

@@ -14208,70 +14208,6 @@ Body:
           - Index: 1
             Item: RWC_Scroll_2012
             UniqueId: true
-  - Group: FINDINGORE
-    SubGroups:
-      - SubGroup: 1
-        List:
-          - Index: 0
-            Item: Emperium
-            Rate: 3
-          - Index: 1
-            Item: Oridecon_Stone
-            Rate: 20
-          - Index: 2
-            Item: Elunium_Stone
-            Rate: 20
-          - Index: 3
-            Item: Gold
-            Rate: 2
-          - Index: 4
-            Item: Oridecon
-            Rate: 10
-          - Index: 5
-            Item: Elunium
-            Rate: 10
-          - Index: 6
-            Item: Boody_Red
-            Rate: 30
-          - Index: 7
-            Item: Crystal_Blue
-            Rate: 30
-          - Index: 8
-            Item: Wind_Of_Verdure
-            Rate: 30
-          - Index: 9
-            Item: Yellow_Live
-            Rate: 30
-          - Index: 10
-            Item: Flame_Heart
-            Rate: 15
-          - Index: 11
-            Item: Mistic_Frozen
-            Rate: 15
-          - Index: 12
-            Item: Rough_Wind
-            Rate: 15
-          - Index: 13
-            Item: Great_Nature
-            Rate: 15
-          - Index: 14
-            Item: Iron
-            Rate: 80
-          - Index: 15
-            Item: Steel
-            Rate: 50
-          - Index: 16
-            Item: Iron_Ore
-            Rate: 100
-          - Index: 17
-            Item: Coal
-            Rate: 60
-          - Index: 18
-            Item: Phracon
-            Rate: 95
-          - Index: 19
-            Item: Emveretarcon
-            Rate: 55
   - Group: FIRE_BRAND_BOX
     SubGroups:
       - SubGroup: 0
@@ -19045,7 +18981,7 @@ Body:
             Rate: 3
           - Index: 18
             Item: Crystal_Jewel___
-            Rate: 27
+            Rate: 2
           - Index: 19
             Item: Glass_Bead
             Rate: 50
@@ -24474,7 +24410,7 @@ Body:
             Item: Red_Slim_Potion
             Rate: 10
           - Index: 4
-            Item: Resist_Fire
+            Item: Resist_Earth
             Rate: 10
           - Index: 5
             Item: Yellow_Herb

+ 12 - 0
src/common/random.hpp

@@ -36,4 +36,16 @@ typename std::enable_if<std::is_integral<T>::value, bool>::type rnd_chance(T cha
 	return rnd_value<T>(1, base) <= chance;
 }
 
+/*
+ * Simulates a chance based on a given probability
+ * This considers the official inaccuracy where a random value between 0 and 20000 is generated first
+ * and the taken modulo to the base. That means there's always an increased chance that the result is 0.
+ * For example if base is 10000, there is a 3/20001 chance that the value is 0 (0, 10000 and 20000).
+ * @return true if succeeded / false if it didn't
+ */
+template <typename T>
+typename std::enable_if<std::is_integral<T>::value, bool>::type rnd_chance_official(T chance, T base) {
+	return rnd_value<T>(0, 20000)%base < chance;
+}
+
 #endif /* RANDOM_HPP */

+ 3 - 1
src/map/battle.cpp

@@ -11267,6 +11267,8 @@ static const struct _battle_data {
 	{ "item_drop_use_max",                  &battle_config.item_drop_use_max,               10000,  1,      10000,          },
 	{ "item_drop_add_min",                  &battle_config.item_drop_adddrop_min,           1,      0,      10000,          },
 	{ "item_drop_add_max",                  &battle_config.item_drop_adddrop_max,           10000,  1,      10000,          },
+	{ "item_group_drop_min",                &battle_config.item_group_drop_min,             1,      0,      10000,          },
+	{ "item_group_drop_max",                &battle_config.item_group_drop_max,             10000,  1,      10000,          },
 	{ "item_drop_treasure_min",             &battle_config.item_drop_treasure_min,          1,      0,      10000,          },
 	{ "item_drop_treasure_max",             &battle_config.item_drop_treasure_max,          10000,  1,      10000,          },
 	{ "item_rate_mvp",                      &battle_config.item_rate_mvp,                   100,    0,      1000000,        },
@@ -11286,6 +11288,7 @@ static const struct _battle_data {
 	{ "item_rate_use_boss",                 &battle_config.item_rate_use_boss,              100,    0,      1000000,        },
 	{ "item_rate_use_mvp",                  &battle_config.item_rate_use_mvp,               100,    0,      1000000,        },
 	{ "item_rate_adddrop",                  &battle_config.item_rate_adddrop,               100,    0,      1000000,        },
+	{ "item_group_rate",                    &battle_config.item_group_rate,                 100,    0,      1000000,        },
 	{ "item_rate_treasure",                 &battle_config.item_rate_treasure,              100,    0,      1000000,        },
 	{ "prevent_logout",                     &battle_config.prevent_logout,                  10000,  0,      60000,          },
 	{ "prevent_logout_trigger",             &battle_config.prevent_logout_trigger,          0xE,    0,      0xF,            },
@@ -11326,7 +11329,6 @@ static const struct _battle_data {
 	{ "skill_steal_max_tries",              &battle_config.skill_steal_max_tries,           0,      0,      UCHAR_MAX,      },
 	{ "skill_steal_random_options",         &battle_config.skill_steal_random_options,      0,      0,      1,              },
 	{ "motd_type",                          &battle_config.motd_type,                       0,      0,      1,              },
-	{ "finding_ore_rate",                   &battle_config.finding_ore_rate,                100,    0,      INT_MAX,        },
 	{ "exp_calc_type",                      &battle_config.exp_calc_type,                   0,      0,      2,              },
 	{ "exp_bonus_attacker",                 &battle_config.exp_bonus_attacker,              25,     0,      INT_MAX,        },
 	{ "exp_bonus_max_attacker",             &battle_config.exp_bonus_max_attacker,          12,     2,      INT_MAX,        },

+ 2 - 2
src/map/battle.hpp

@@ -352,7 +352,7 @@ struct Battle_Config
 	int32 attack_attr_none;
 	int32 item_rate_mvp, item_rate_common, item_rate_common_boss, item_rate_card, item_rate_card_boss,
 		item_rate_equip, item_rate_equip_boss, item_rate_heal, item_rate_heal_boss, item_rate_use,
-		item_rate_use_boss, item_rate_treasure, item_rate_adddrop;
+		item_rate_use_boss, item_rate_treasure, item_rate_adddrop, item_group_rate;
 	int32 item_rate_common_mvp, item_rate_heal_mvp, item_rate_use_mvp, item_rate_equip_mvp, item_rate_card_mvp;
 
 	int32 logarithmic_drops;
@@ -365,6 +365,7 @@ struct Battle_Config
 	int32 item_drop_use_min,item_drop_use_max;	//End
 	int32 item_drop_treasure_min,item_drop_treasure_max; //by [Skotlex]
 	int32 item_drop_adddrop_min,item_drop_adddrop_max; //[Skotlex]
+	int32 item_group_drop_min,item_group_drop_max;
 
 	int32 prevent_logout;	// Added by RoVeRT
 	int32 prevent_logout_trigger;
@@ -429,7 +430,6 @@ struct Battle_Config
 	int32 skill_steal_max_tries; //max steal skill tries on a mob. if 0, then w/o limit [Lupus]
 	int32 skill_steal_random_options;
 	int32 motd_type; // [celest]
-	int32 finding_ore_rate; // orn
 	int32 exp_calc_type;
 	int32 exp_bonus_attacker;
 	int32 exp_bonus_max_attacker;

+ 35 - 20
src/map/itemdb.cpp

@@ -2888,27 +2888,47 @@ uint16 itemdb_searchname_array(std::map<t_itemid, std::shared_ptr<item_data>> &d
 	return static_cast<uint16>(data.size());
 }
 
-std::shared_ptr<s_item_group_entry> get_random_itemsubgroup(std::shared_ptr<s_item_group_random> random) {
+std::shared_ptr<s_item_group_entry> ItemGroupDatabase::get_random_itemsubgroup(std::shared_ptr<s_item_group_random> random, e_group_search_type search_type) {
 	if (random == nullptr)
 		return nullptr;
 
-	for (size_t j = 0, max = random->data.size() * 3; j < max; j++) {
+	if (search_type == GROUP_SEARCH_DROP) {
+		// We pick a random item from the group and then do a drop check based on the rate
 		std::shared_ptr<s_item_group_entry> entry = util::umap_random(random->data);
-
-		if (entry->rate == 0 || rnd_chance<uint32>(entry->rate, random->total_rate))	// always return entry for rate 0 ('must' item)
+		if (rnd_chance_official<uint16>(entry->rate, 10000))
 			return entry;
 	}
-
-	return util::umap_random(random->data);
+	else {
+		// Each item has x positions whereas x is the rate defined for the item in the umap
+		// We pick a random position and find the item that is at this position
+		uint32 pos = rnd_value<uint32>(1, random->total_rate);
+		uint32 current_pos = 1;
+		// Iterate through each item in the umap
+		for (const auto& [index, entry] : random->data) {
+			if (entry == nullptr)
+				return nullptr;
+			// If rate is 0 it means that this is SubGroup 0 which should just return any random item
+			if (entry->rate == 0)
+				return util::umap_random(random->data);
+			// We move "rate" positions
+			current_pos += entry->rate;
+			// If we passed the target position, entry is the item we are looking for
+			if (current_pos > pos)
+				return entry;
+		}
+	}
+	// Return nullptr on fail
+	return nullptr;
 }
 
 /**
 * Return a random group entry from Item Group
 * @param group_id
 * @param sub_group: 0 is 'must' item group, random groups start from 1
+* @param search_type: see e_group_search_type
 * @return Item group entry or nullptr on fail
 */
-std::shared_ptr<s_item_group_entry> ItemGroupDatabase::get_random_entry(uint16 group_id, uint8 sub_group) {
+std::shared_ptr<s_item_group_entry> ItemGroupDatabase::get_random_entry(uint16 group_id, uint8 sub_group, e_group_search_type search_type) {
 	std::shared_ptr<s_item_group_db> group = this->find(group_id);
 
 	if (group == nullptr) {
@@ -2924,18 +2944,7 @@ std::shared_ptr<s_item_group_entry> ItemGroupDatabase::get_random_entry(uint16 g
 		return nullptr;
 	}
 
-	return get_random_itemsubgroup(group->random[sub_group]);
-}
-
-/**
-* Return a random Item ID from Item Group
-* @param group_id
-* @param sub_group: 0 is 'must' item group, random groups start from 1
-* @return Item ID or UNKNOWN_ITEM_ID on fail
-*/
-t_itemid ItemGroupDatabase::get_random_item_id(uint16 group_id, uint8 sub_group) {
-	std::shared_ptr<s_item_group_entry> entry = this->get_random_entry(group_id, sub_group);
-	return entry != nullptr ? entry->nameid : UNKNOWN_ITEM_ID;
+	return this->get_random_itemsubgroup(group->random[sub_group], search_type);
 }
 
 /** [Cydh]
@@ -3034,7 +3043,7 @@ uint8 ItemGroupDatabase::pc_get_itemgroup( uint16 group_id, bool identify, map_s
 			continue;
 		}
 
-		this->pc_get_itemgroup_sub( sd, identify, get_random_itemsubgroup( random.second ) );
+		this->pc_get_itemgroup_sub( sd, identify, this->get_random_itemsubgroup( random.second ) );
 	}
 
 	return 0;
@@ -3400,6 +3409,12 @@ uint64 ItemGroupDatabase::parseBodyNode(const ryml::NodeRef& node) {
 					continue;
 				}
 
+				// Rate adjustment
+				if (battle_config.item_group_rate != 100) {
+					entry->rate = (entry->rate * battle_config.item_group_rate) / 100;
+					entry->rate = cap_value(entry->rate, battle_config.item_group_drop_min, battle_config.item_group_drop_max);
+				}
+
 				if (this->nodeExists(listit, "Amount")) {
 					uint16 amount;
 

+ 8 - 5
src/map/itemdb.hpp

@@ -240,7 +240,6 @@ enum e_random_item_group {
 	IG_CARDALBUM,
 	IG_GIFTBOX,
 	IG_SCROLLBOX,
-	IG_FINDINGORE,
 	IG_COOKIEBAG,
 	IG_FIRSTAID,
 	IG_HERB,
@@ -3009,6 +3008,12 @@ enum e_delay_consume : uint8 {
 	DELAYCONSUME_NOCONSUME = 0x2, // Items that are not removed upon double-click
 };
 
+/// Enum for different ways to search an item group
+enum e_group_search_type : uint8 {
+	GROUP_SEARCH_BOX = 0, // Always return an item from the group, rate determines which item is more likely to be returned
+	GROUP_SEARCH_DROP = 1, // Pick one item from the group and check use rate as drop rate, on fail, do not return any item
+};
+
 /// Item combo struct
 struct s_item_combo {
 	std::vector<t_itemid> nameid;
@@ -3135,8 +3140,6 @@ struct s_item_group_random
 {
 	uint32 total_rate;
 	std::unordered_map<uint32, std::shared_ptr<s_item_group_entry>> data; /// index, s_item_group_entry
-
-	std::shared_ptr<s_item_group_entry> get_random_itemsubgroup();
 };
 
 /// Struct of item group that will be used for db
@@ -3304,11 +3307,11 @@ public:
 	// Additional
 	bool item_exists(uint16 group_id, t_itemid nameid);
 	int16 item_exists_pc(map_session_data *sd, uint16 group_id);
-	t_itemid get_random_item_id(uint16 group_id, uint8 sub_group);
-	std::shared_ptr<s_item_group_entry> get_random_entry(uint16 group_id, uint8 sub_group);
+	std::shared_ptr<s_item_group_entry> get_random_entry(uint16 group_id, uint8 sub_group, e_group_search_type search_type = GROUP_SEARCH_BOX);
 	uint8 pc_get_itemgroup( uint16 group_id, bool identify, map_session_data& sd );
 
 private:
+	std::shared_ptr<s_item_group_entry> get_random_itemsubgroup(std::shared_ptr<s_item_group_random> random, e_group_search_type search_type = GROUP_SEARCH_BOX);
 	void pc_get_itemgroup_sub( map_session_data& sd, bool identify, std::shared_ptr<s_item_group_entry> data );
 };
 

+ 26 - 17
src/map/mob.cpp

@@ -2931,21 +2931,9 @@ int32 mob_dead(struct mob_data *md, struct block_list *src, int32 type)
 		dlist->second_charid = (second_sd ? second_sd->status.char_id : 0);
 		dlist->third_charid = (third_sd ? third_sd->status.char_id : 0);
 
-		// Ore Discovery [Celest]
-		if (sd == mvp_sd && pc_checkskill(sd,BS_FINDINGORE)>0 && battle_config.finding_ore_rate/10 >= rnd()%10000) {
-			s_mob_drop mobdrop = {};
-
-			mobdrop.nameid = itemdb_group.get_random_item_id(IG_FINDINGORE,1);
-
-			std::shared_ptr<s_item_drop> ditem = mob_setdropitem( mobdrop, 1, md->mob_id );
-
-			mob_item_drop(md, dlist, ditem, 0, battle_config.finding_ore_rate/10, homkillonly || merckillonly);
-		}
-
+		// These trigger for the killer of the monster
 		if(sd) {
 			// process script-granted extra drop bonuses
-			t_itemid dropid = 0;
-
 			for (const auto &it : sd->add_drop) {
 				if (!&it || (!it.nameid && !it.group))
 					continue;
@@ -2966,15 +2954,22 @@ int32 mob_dead(struct mob_data *md, struct block_list *src, int32 type)
 
 					if (rnd()%10000 >= drop_rate)
 						continue;
-					dropid = (it.nameid > 0) ? it.nameid : itemdb_group.get_random_item_id(it.group,1);
 
 					s_mob_drop mobdrop = {};
-
-					mobdrop.nameid = dropid;
+					if (it.nameid > 0) {
+						mobdrop.nameid = it.nameid;
+						mobdrop.rate = drop_rate;
+					}
+					else {
+						std::shared_ptr<s_item_group_entry> entry = itemdb_group.get_random_entry(it.group, 1, GROUP_SEARCH_DROP);
+						if (entry == nullptr) continue;
+						mobdrop.nameid = entry->nameid;
+						mobdrop.rate = entry->rate * drop_rate / 10000;
+					}
 
 					std::shared_ptr<s_item_drop> ditem = mob_setdropitem(mobdrop, 1, md->mob_id);
 
-					mob_item_drop( md, dlist, ditem, 0, drop_rate, homkillonly || merckillonly );
+					mob_item_drop(md, dlist, ditem, 0, mobdrop.rate, homkillonly || merckillonly);
 				}
 			}
 
@@ -3021,6 +3016,20 @@ int32 mob_dead(struct mob_data *md, struct block_list *src, int32 type)
 			mob_item_drop(md, dlist, ditem, 0, battle_config.autoloot_adjust ? drop_rate : md->db->dropitem[i].rate, homkillonly || merckillonly);
 		}
 
+		// Ore Discovery (triggers if owner has loot priority, does not require to be the killer)
+		if (mvp_sd && pc_checkskill(mvp_sd, BS_FINDINGORE) > 0) {
+			std::shared_ptr<s_item_group_entry> entry = itemdb_group.get_random_entry(IG_ORE, 1, GROUP_SEARCH_DROP);
+			if (entry != nullptr) {
+				s_mob_drop mobdrop = {};
+				mobdrop.nameid = entry->nameid;
+				mobdrop.rate = entry->rate;
+
+				std::shared_ptr<s_item_drop> ditem = mob_setdropitem(mobdrop, 1, md->mob_id);
+
+				mob_item_drop(md, dlist, ditem, 0, mobdrop.rate, homkillonly || merckillonly);
+			}
+		}
+
 		// Process map specific drops
 		std::shared_ptr<s_map_drops> mapdrops;
 

+ 0 - 1
src/map/script_constants.hpp

@@ -4293,7 +4293,6 @@
 	export_constant(IG_CARDALBUM);
 	export_constant(IG_GIFTBOX);
 	export_constant(IG_SCROLLBOX);
-	export_constant(IG_FINDINGORE);
 	export_constant(IG_COOKIEBAG);
 	export_constant(IG_FIRSTAID);
 	export_constant(IG_HERB);