Просмотр исходного кода

Algorithm for opening group items in renewal (#8849)

- Introduced a new item_group_db attribute "Algorithm" with three possible values: All, Random and SharedPool
  * The attribute now affects behavior of how an item is selected within the group instead of SubGroup
  * This affects the commands "getgroupitem", "getrandgroupitem" and "groupranditem"
  * Be aware that various commands use or default to SubGroup=1
- Implemented the "SharedPool" algorithm where rate is the amount of items in the group
  * When an item is retrieved from the group, it is removed from the group
  * Only when the group is completely empty or the server restarts, the group refills
  * This algorithm is used by many renewal item groups
- The algorithm "All" replaces the previous SubGroup=0 check
  * When using "getgroupitem" on a group with "All" as algorithm, you will receive all items from the group
  * When using "getrandgroupitem" on a group with "All" as algorithm, you will receive a random item from the group
  * When using "groupranditem" on a group with "All" as algorithm, it will return a random item from the group
- The algorithm "Random" is the previous algorithm that returns a random item using rate as chance
- Battle config "item_group_rate" now only applies to GROUP_ALGORITHM_DROP
  * This is accomplished by storing the adjusted rate in a separate variable
- Updated documentation accordingly
- Fixes #8848 

---------

Co-authored-by: Atemo
Playtester 5 месяцев назад
Родитель
Сommit
f41a14cbd1

+ 3 - 2
db/import-tmpl/item_group_db.yml

@@ -1,5 +1,5 @@
 # This file is a part of rAthena.
-#   Copyright(C) 2021 rAthena Development Team
+#   Copyright(C) 2024 rAthena Development Team
 #   https://rathena.org - https://github.com/rathena
 #
 # This program is free software: you can redistribute it and/or modify
@@ -25,6 +25,7 @@
 # - Group                      Group name of the item.
 #   SubGroups:
 #     - SubGroup:              SubGroup number associated with the list of item. See doc/item_group.txt for more information.
+#       Algorithm:             Type of algorithm associated with SubGroup. (Default: SharedPool)
 #       List:                  List of item(s) for the SubGroup.
 #        - Index               Unique number that can be used to add the same Item with different data in the list.
 #          Item                AegisName of item that will be obtained from this item group.
@@ -45,4 +46,4 @@
 
 Header:
   Type: ITEM_GROUP_DB
-  Version: 3
+  Version: 4

+ 9 - 8
db/item_group_db.yml

@@ -1,5 +1,5 @@
 # This file is a part of rAthena.
-#   Copyright(C) 2021 rAthena Development Team
+#   Copyright(C) 2024 rAthena Development Team
 #   https://rathena.org - https://github.com/rathena
 #
 # This program is free software: you can redistribute it and/or modify
@@ -25,6 +25,7 @@
 # - Group                      Group name of the item.
 #   SubGroups:
 #     - SubGroup:              SubGroup number associated with the list of item. See doc/item_group.txt for more information.
+#       Algorithm:             Type of algorithm associated with SubGroup. (Default: SharedPool)
 #       List:                  List of item(s) for the SubGroup.
 #        - Index               Unique number that can be used to add the same Item with different data in the list.
 #          Item                AegisName of item that will be obtained from this item group.
@@ -32,7 +33,7 @@
 #          Amount              Amount of item that will be obtained. (Default: 1)
 #          Duration            Makes the item a rental item which will expire within the given amount in minutes. Not intended for use with stackable items. (Default: 0)
 #          Announced           If player obtains this item it will be broadcasted to the server. (Default: false)
-#          UniqueId            Makes the given item(s) have a Unique ID. (Default: false)
+#          UniqueId            Makes the given item(s) have a Unique ID. (Default: UniqueId from item_db)
 #          Stacked             Whether stackable items are given stacked or not. (Default: true)
 #          Named               Inscribes the item with the obtainer's name. (Default: false)
 #          Bound               Binds the obtained item. (Default: None)
@@ -45,12 +46,12 @@
 
 Header:
   Type: ITEM_GROUP_DB
-  Version: 3
+  Version: 4
 
 Footer:
   Imports:
-  - Path: db/pre-re/item_group_db.yml
-    Mode: Prerenewal
-  - Path: db/re/item_group_db.yml
-    Mode: Renewal
-  - Path: db/import/item_group_db.yml
+    - Path: db/pre-re/item_group_db.yml
+      Mode: Prerenewal
+    - Path: db/re/item_group_db.yml
+      Mode: Renewal
+    - Path: db/import/item_group_db.yml

+ 59 - 59
db/pre-re/item_db_usable.yml

@@ -1061,7 +1061,7 @@ Body:
       BuyingStore: true
       Container: true
     Script: |
-      getrandgroupitem(IG_BlueBox,1);
+      getgroupitem(IG_BLUEBOX);
   - Id: 604
     AegisName: Branch_Of_Dead_Tree
     Name: Dead Branch
@@ -1199,7 +1199,7 @@ Body:
       BuyingStore: true
       Container: true
     Script: |
-      getrandgroupitem(IG_CardAlbum,1);
+      getgroupitem(IG_CARDALBUM);
   - Id: 617
     AegisName: Old_Violet_Box
     Name: Old Purple Box
@@ -1210,7 +1210,7 @@ Body:
       BuyingStore: true
       Container: true
     Script: |
-      getrandgroupitem(IG_VioletBox,1);
+      getgroupitem(IG_VIOLETBOX);
   - Id: 618
     AegisName: Worn_Out_Scroll
     Name: Worn Out Scroll
@@ -1528,7 +1528,7 @@ Body:
       BuyingStore: true
       Container: true
     Script: |
-      getrandgroupitem(IG_GiftBox,1);
+      getgroupitem(IG_GIFTBOX);
   - Id: 645
     AegisName: Center_Potion
     Name: Concentration Potion
@@ -1674,7 +1674,7 @@ Body:
       BuyingStore: true
       Container: true
     Script: |
-      getrandgroupitem(IG_GiftBox_1,1);
+      getgroupitem(IG_GIFTBOX_1);
   - Id: 665
     AegisName: Gift_Box_2
     Name: Gift Box
@@ -1685,7 +1685,7 @@ Body:
       BuyingStore: true
       Container: true
     Script: |
-      getrandgroupitem(IG_GiftBox_2,1);
+      getgroupitem(IG_GIFTBOX_2);
   - Id: 666
     AegisName: Gift_Box_3
     Name: Gift Box
@@ -1696,7 +1696,7 @@ Body:
       BuyingStore: true
       Container: true
     Script: |
-      getrandgroupitem(IG_GiftBox_3,1);
+      getgroupitem(IG_GIFTBOX_3);
   - Id: 667
     AegisName: Gift_Box_4
     Name: Gift Box
@@ -1707,7 +1707,7 @@ Body:
       BuyingStore: true
       Container: true
     Script: |
-      getrandgroupitem(IG_GiftBox_4,1);
+      getgroupitem(IG_GIFTBOX_4);
   - Id: 668
     AegisName: Handsei
     Name: Red Envelope
@@ -2663,7 +2663,7 @@ Body:
     Flags:
       Container: true
     Script: |
-      getrandgroupitem(IG_GiftBoxChina,1);
+      getgroupitem(IG_GIFTBOXCHINA);
   - Id: 12024
     AegisName: Red_Pouch_Of_Surprise
     Name: Red Pouch
@@ -2683,7 +2683,7 @@ Body:
     Flags:
       Container: true
     Script: |
-      getrandgroupitem(IG_EggBoy,1);
+      getgroupitem(IG_EGGBOY);
   - Id: 12026
     AegisName: Egg_Girl
     Name: Dano Festival Egg
@@ -2693,7 +2693,7 @@ Body:
     Flags:
       Container: true
     Script: |
-      getrandgroupitem(IG_EggGirl,1);
+      getgroupitem(IG_EGGGIRL);
   - Id: 12027
     AegisName: Giggling_Box
     Name: Giggling Box
@@ -2821,7 +2821,7 @@ Body:
     Flags:
       Container: true
     Script: |
-      getrandgroupitem(IG_LottoBox,1);
+      getgroupitem(IG_LOTTOBOX);
   - Id: 12039
     AegisName: Lotto_Box05
     Name: Lotto Box 05
@@ -3536,7 +3536,7 @@ Body:
       BuyingStore: true
       Container: true
     Script: |
-      getrandgroupitem(IG_Quiver,1);
+      getgroupitem(IG_QUIVER);
   - Id: 12105
     AegisName: Set_Of_Taiming_Item
     Name: Taming Gift Set
@@ -3547,9 +3547,9 @@ Body:
       BuyingStore: true
       Container: true
     Script: |
-      getrandgroupitem(IG_Taming,1);
-      getrandgroupitem(IG_Taming,1);
-      getrandgroupitem(IG_Taming,1);
+      getgroupitem(IG_TAMING);
+      getgroupitem(IG_TAMING);
+      getgroupitem(IG_TAMING);
   - Id: 12106
     AegisName: Accessory_Box
     Name: Jewelry Box
@@ -3560,7 +3560,7 @@ Body:
       BuyingStore: true
       Container: true
     Script: |
-      getrandgroupitem(IG_Accesory,1);
+      getgroupitem(IG_ACCESORY);
   - Id: 12107
     AegisName: Wrapped_Mask
     Name: Wrapped Mask
@@ -3571,7 +3571,7 @@ Body:
       BuyingStore: true
       Container: true
     Script: |
-      getrandgroupitem(IG_Mask,1);
+      getgroupitem(IG_MASK);
   - Id: 12108
     AegisName: Bundle_Of_Magic_Scroll
     Name: Scroll Package
@@ -3582,11 +3582,11 @@ Body:
       BuyingStore: true
       Container: true
     Script: |
-      getrandgroupitem(IG_Scroll,1);
-      getrandgroupitem(IG_Scroll,1);
-      getrandgroupitem(IG_Scroll,1);
-      getrandgroupitem(IG_Scroll,1);
-      getrandgroupitem(IG_Scroll,1);
+      getgroupitem(IG_SCROLL);
+      getgroupitem(IG_SCROLL);
+      getgroupitem(IG_SCROLL);
+      getgroupitem(IG_SCROLL);
+      getgroupitem(IG_SCROLL);
   - Id: 12109
     AegisName: Poring_Box
     Name: Poring Box
@@ -3608,11 +3608,11 @@ Body:
       BuyingStore: true
       Container: true
     Script: |
-      getrandgroupitem(IG_FirstAid,1);
-      getrandgroupitem(IG_FirstAid,1);
-      getrandgroupitem(IG_FirstAid,1);
-      getrandgroupitem(IG_FirstAid,1);
-      getrandgroupitem(IG_FirstAid,1);
+      getgroupitem(IG_FIRSTAID);
+      getgroupitem(IG_FIRSTAID);
+      getgroupitem(IG_FIRSTAID);
+      getgroupitem(IG_FIRSTAID);
+      getgroupitem(IG_FIRSTAID);
   - Id: 12111
     AegisName: Food_Package
     Name: Bundle of Food
@@ -3623,9 +3623,9 @@ Body:
       BuyingStore: true
       Container: true
     Script: |
-      getrandgroupitem(IG_FoodBag,1);
-      getrandgroupitem(IG_FoodBag,1);
-      getrandgroupitem(IG_FoodBag,1);
+      getgroupitem(IG_FOODBAG);
+      getgroupitem(IG_FOODBAG);
+      getgroupitem(IG_FOODBAG);
   - Id: 12112
     AegisName: Tropical_Sograt
     Name: Tropical Sograt
@@ -3817,9 +3817,9 @@ Body:
       BuyingStore: true
       Container: true
     Script: |
-      getrandgroupitem(IG_CookieBag,1);
-      getrandgroupitem(IG_CookieBag,1);
-      getrandgroupitem(IG_CookieBag,1);
+      getgroupitem(IG_COOKIEBAG);
+      getgroupitem(IG_COOKIEBAG);
+      getgroupitem(IG_COOKIEBAG);
   - Id: 12131
     AegisName: Lucky_Potion
     Name: Lucky Potion
@@ -4514,7 +4514,7 @@ Body:
     Flags:
       Container: true
     Script: |
-      getrandgroupitem(IG_RedBox,1);
+      getgroupitem(IG_REDBOX);
   - Id: 12187
     AegisName: Green_Box
     Name: Old Green Box
@@ -4524,7 +4524,7 @@ Body:
     Flags:
       Container: true
     Script: |
-      getrandgroupitem(IG_GreenBox,1);
+      getgroupitem(IG_GREENBOX);
   - Id: 12188
     AegisName: Magical_Moon_Cake
     Name: Grace Moon Cake
@@ -4542,7 +4542,7 @@ Body:
     Flags:
       Container: true
     Script: |
-      getrandgroupitem(IG_RedBox_2,1);
+      getgroupitem(IG_REDBOX_2);
   - Id: 12190
     AegisName: Moon_Cake
     Name: Moon Cake
@@ -4579,9 +4579,9 @@ Body:
       BuyingStore: true
       Container: true
     Script: |
-      getrandgroupitem(IG_HometownGift,1);
-      getrandgroupitem(IG_HometownGift,1);
-      getrandgroupitem(IG_HometownGift,1);
+      getgroupitem(IG_HOMETOWNGIFT);
+      getgroupitem(IG_HOMETOWNGIFT);
+      getgroupitem(IG_HOMETOWNGIFT);
   - Id: 12195
     AegisName: Plain_Rice_Cake
     Name: Plain Rice Cake
@@ -5185,7 +5185,7 @@ Body:
     Flags:
       Container: true
     Script: |
-      getrandgroupitem(IG_YellowBox,1);
+      getgroupitem(IG_YELLOWBOX);
   - Id: 12241
     AegisName: M_Center_Potion
     Name: Mercenary Concentration Potion
@@ -5225,7 +5225,7 @@ Body:
     Flags:
       Container: true
     Script: |
-      getrandgroupitem(IG_OldGiftBox,1);
+      getgroupitem(IG_OLDGIFTBOX);
   - Id: 12245
     AegisName: Green_Ale_US
     Name: Green Ale
@@ -5244,7 +5244,7 @@ Body:
       BuyingStore: true
       Container: true
     Script: |
-      getrandgroupitem(IG_MagicCardAlbum,1);
+      getgroupitem(IG_MAGICCARDALBUM);
   - Id: 12247
     AegisName: Halohalo
     Name: Halo-Halo
@@ -5263,7 +5263,7 @@ Body:
     Flags:
       Container: true
     Script: |
-      getrandgroupitem(IG_Masquerade,1);
+      getgroupitem(IG_MASQUERADE);
   - Id: 12249
     AegisName: Payroll_Of_Kafra_
     Name: Payment Statement for Kafra Employee
@@ -5678,7 +5678,7 @@ Body:
     Flags:
       Container: true
     Script: |
-      getrandgroupitem(IG_Tresure_Box_WoE,1);
+      getgroupitem(IG_TRESURE_BOX_WOE);
   - Id: 12282
     AegisName: Internet_Cafe1
     Name: Internet Cafe1
@@ -5728,7 +5728,7 @@ Body:
     Flags:
       Container: true
     Script: |
-      getrandgroupitem(IG_Masquerade_2,1);
+      getgroupitem(IG_MASQUERADE_2);
   - Id: 12287
     AegisName: Love_Angel
     Name: Love Angel Magic Powder
@@ -6341,7 +6341,7 @@ Body:
     Flags:
       Container: true
     Script: |
-      getrandgroupitem(IG_Cherish_Box,1);
+      getgroupitem(IG_CHERISH_BOX);
   - Id: 12335
     AegisName: Yummy_Skewered_Dish
     Name: Grilled Delicious Skewer
@@ -6384,7 +6384,7 @@ Body:
     Flags:
       Container: true
     Script: |
-      getrandgroupitem(IG_Cherish_Box_Ori,1);
+      getgroupitem(IG_CHERISH_BOX_ORI);
   - Id: 12340
     AegisName: Mysterious_Rice_Powder
     Name: Chewy Rice Powder
@@ -6623,7 +6623,7 @@ Body:
       BuyingStore: true
       Container: true
     Script: |
-      getrandgroupitem(IG_Xmas_Gift,1);
+      getgroupitem(IG_XMAS_GIFT);
   - Id: 12356
     AegisName: Louise_Costume_Box
     Name: Louise Costume Box
@@ -6634,7 +6634,7 @@ Body:
       BuyingStore: true
       Container: true
     Script: |
-      getrandgroupitem(IG_Louise_Costume_Box,1);
+      getgroupitem(IG_LOUISE_COSTUME_BOX);
   - Id: 12357
     AegisName: Shiny_Wing_Gown
     Name: Shiny Wing Gown
@@ -8678,8 +8678,8 @@ Body:
     Flags:
       Container: true
     Script: |
-      getrandgroupitem(IG_BleuBox,1);
-      getrandgroupitem(IG_BleuBox,1);
+      getgroupitem(IG_BLEUBOX);
+      getgroupitem(IG_BLEUBOX);
   - Id: 12703
     AegisName: Holy_Egg_2
     Name: Holy Egg
@@ -8796,7 +8796,7 @@ Body:
       NoMail: true
       NoAuction: true
     Script: |
-      getrandgroupitem(IG_Easter_Scroll,1);
+      getgroupitem(IG_EASTER_SCROLL);
   - Id: 12715
     AegisName: Black_Treasure_Box
     Name: Black Treasure Box
@@ -21622,12 +21622,12 @@ Body:
     Flags:
       Container: true
     Script: |
-      getrandgroupitem(IG_Pierre_Treasurebox,1);
-      getrandgroupitem(IG_Pierre_Treasurebox,1);
-      getrandgroupitem(IG_Pierre_Treasurebox,1);
-      getrandgroupitem(IG_Pierre_Treasurebox,1);
-      getrandgroupitem(IG_Pierre_Treasurebox,1);
-      getrandgroupitem(IG_Pierre_Treasurebox,1);
+      getgroupitem(IG_PIERRE_TREASUREBOX);
+      getgroupitem(IG_PIERRE_TREASUREBOX);
+      getgroupitem(IG_PIERRE_TREASUREBOX);
+      getgroupitem(IG_PIERRE_TREASUREBOX);
+      getgroupitem(IG_PIERRE_TREASUREBOX);
+      getgroupitem(IG_PIERRE_TREASUREBOX);
   - Id: 14597
     AegisName: PhreeoniS
     Name: Phreeoni Scroll

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

@@ -1,5 +1,5 @@
 # This file is a part of rAthena.
-#   Copyright(C) 2023 rAthena Development Team
+#   Copyright(C) 2024 rAthena Development Team
 #   https://rathena.org - https://github.com/rathena
 #
 # This program is free software: you can redistribute it and/or modify
@@ -25,6 +25,7 @@
 # - Group                      Group name of the item.
 #   SubGroups:
 #     - SubGroup:              SubGroup number associated with the list of item. See doc/item_group.txt for more information.
+#       Algorithm:             Type of algorithm associated with SubGroup. (Default: SharedPool)
 #       List:                  List of item(s) for the SubGroup.
 #        - Index               Unique number that can be used to add the same Item with different data in the list.
 #          Item                AegisName of item that will be obtained from this item group.
@@ -45,12 +46,13 @@
 
 Header:
   Type: ITEM_GROUP_DB
-  Version: 3
+  Version: 4
 
 Body:
   - Group: ACCESORY
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Ring
@@ -181,6 +183,7 @@ Body:
   - Group: BLEUBOX
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Milk
@@ -218,6 +221,7 @@ Body:
   - Group: BLUEBOX
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Red_Potion
@@ -3288,6 +3292,7 @@ Body:
   - Group: BOMB
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Apple_Bomb
@@ -3316,6 +3321,7 @@ Body:
   - Group: BOX
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Old_Blue_Box
@@ -3329,6 +3335,7 @@ Body:
   - Group: CANDY
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Candy
@@ -3339,6 +3346,7 @@ Body:
   - Group: CARDALBUM
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Poring_Card
@@ -4429,6 +4437,7 @@ Body:
   - Group: CASH_FOOD
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Str_Dish10_
@@ -4451,6 +4460,7 @@ Body:
   - Group: CHERISH_BOX
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Dog_Cap_
@@ -4464,6 +4474,7 @@ Body:
   - Group: CHERISH_BOX_ORI
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Ragnarok_Limited_Ed
@@ -4483,6 +4494,7 @@ Body:
   - Group: COOKIEBAG
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Candy
@@ -4535,6 +4547,7 @@ Body:
   - Group: EASTER_SCROLL
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: No_Recipient
@@ -4581,6 +4594,7 @@ Body:
   - Group: EGGBOY
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Grape
@@ -4606,6 +4620,7 @@ Body:
   - Group: EGGGIRL
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Grape
@@ -4631,6 +4646,7 @@ Body:
   - Group: ELEMENT
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Scarlet_Pts
@@ -4647,6 +4663,7 @@ Body:
   - Group: FIRSTAID
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Red_Potion
@@ -4705,6 +4722,7 @@ Body:
   - Group: FISH
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Fish_Slice
@@ -4715,6 +4733,7 @@ Body:
   - Group: FOOD
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Royal_Jelly
@@ -4785,6 +4804,7 @@ Body:
   - Group: FOODBAG
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Apple
@@ -4921,6 +4941,7 @@ Body:
   - Group: FRUIT
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Apple
@@ -4940,6 +4961,7 @@ Body:
   - Group: GEMSTONE
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Yellow_Gemstone
@@ -4953,6 +4975,7 @@ Body:
   - Group: GIFTBOX
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Red_Potion
@@ -5167,6 +5190,7 @@ Body:
   - Group: GIFTBOXCHINA
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Red_Potion
@@ -5963,6 +5987,7 @@ Body:
   - Group: GIFTBOX_1
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Red_Potion
@@ -6177,6 +6202,7 @@ Body:
   - Group: GIFTBOX_2
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Red_Potion
@@ -6391,6 +6417,7 @@ Body:
   - Group: GIFTBOX_3
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Red_Potion
@@ -6605,6 +6632,7 @@ Body:
   - Group: GIFTBOX_4
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Red_Potion
@@ -6819,6 +6847,7 @@ Body:
   - Group: GREENBOX
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Chocolate_Drink
@@ -6856,6 +6885,7 @@ Body:
   - Group: HERB
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Red_Herb
@@ -6869,6 +6899,7 @@ Body:
   - Group: HOMETOWNGIFT
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Korea_Rice_Cake
@@ -6897,6 +6928,7 @@ Body:
   - Group: JEWEL
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Yellow_Gemstone
@@ -6961,6 +6993,7 @@ Body:
   - Group: JUICE
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Apple_Juice
@@ -6974,6 +7007,7 @@ Body:
   - Group: LOTTOBOX
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Lotto31
@@ -7008,6 +7042,7 @@ Body:
   - Group: LOUISE_COSTUME_BOX
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Red_Bag
@@ -7018,6 +7053,7 @@ Body:
   - Group: MAGICCARDALBUM
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Ghostring_Card
@@ -7115,6 +7151,7 @@ Body:
   - Group: MASK
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Mr_Smile
@@ -7158,6 +7195,7 @@ Body:
   - Group: MASQUERADE
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Drooping_Nine_Tail_
@@ -7174,6 +7212,7 @@ Body:
   - Group: MASQUERADE_2
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Dress_Hat
@@ -7190,6 +7229,7 @@ Body:
   - Group: MEAT
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Meat
@@ -7200,6 +7240,7 @@ Body:
   - Group: MERCENARY
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Bow_Mercenary_Scroll1
@@ -7294,6 +7335,7 @@ Body:
   - Group: MINERAL
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Emperium
@@ -7358,6 +7400,7 @@ Body:
   - Group: OLDGIFTBOX
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Fallen_Leaves_
@@ -7371,6 +7414,7 @@ Body:
   - Group: ORE
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Iron_Ore
@@ -7435,6 +7479,7 @@ Body:
   - Group: PIERRE_TREASUREBOX
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Old_Blue_Box
@@ -7523,6 +7568,7 @@ Body:
   - Group: POISON
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Poison_Paralysis
@@ -7551,6 +7597,7 @@ Body:
   - Group: POTION
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Red_Potion
@@ -7567,6 +7614,7 @@ Body:
   - Group: QUIVER
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Arrow_Container
@@ -7610,6 +7658,7 @@ Body:
   - Group: RECOVERY
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Fruit_Of_Mastela
@@ -7656,6 +7705,7 @@ Body:
   - Group: REDBOX
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Cat_Hat
@@ -7702,6 +7752,7 @@ Body:
   - Group: REDBOX_2
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Alice_Doll
@@ -7718,6 +7769,7 @@ Body:
   - Group: RESIST
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Resist_Fire
@@ -7734,6 +7786,7 @@ Body:
   - Group: SCROLL
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Earth_Scroll_1_3
@@ -7792,6 +7845,7 @@ Body:
   - Group: TAMING
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Unripe_Apple
@@ -7946,6 +8000,7 @@ Body:
   - Group: THROWABLE
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Mysterious_Powder
@@ -8019,6 +8074,7 @@ Body:
   - Group: TOKEN_OF_SIEGFRIED
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: F_Token_Of_Siegfried
@@ -8032,6 +8088,7 @@ Body:
   - Group: TRESURE_BOX_WOE
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Old_Blue_Box
@@ -8096,6 +8153,7 @@ Body:
   - Group: VIOLETBOX
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Red_Potion
@@ -10869,6 +10927,7 @@ Body:
   - Group: XMAS_GIFT
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: HandMade_Chocolate
@@ -10930,6 +10989,7 @@ Body:
   - Group: YELLOWBOX
     SubGroups:
       - SubGroup: 1
+        Algorithm: Random
         List:
           - Index: 0
             Item: Elephant_Hat_
@@ -10946,6 +11006,7 @@ Body:
   - Group: MF_NOTELEPORT
     SubGroups:
       - SubGroup: 0
+        Algorithm: All
         List:
           - Index: 0
             Item: Wing_Of_Fly
@@ -10960,6 +11021,7 @@ Body:
   - Group: MF_NORETURN
     SubGroups:
       - SubGroup: 0
+        Algorithm: All
         List:
           - Index: 0
             Item: Wing_Of_Butterfly
@@ -11008,6 +11070,7 @@ Body:
   - Group: GIANT_FLY_WING
     SubGroups:
       - SubGroup: 0
+        Algorithm: All
         List:
           - Index: 0
             Item: Giant_Fly_Wing

Разница между файлами не показана из-за своего большого размера
+ 130 - 8
db/re/item_group_db.yml


+ 76 - 2
doc/item_group.txt

@@ -75,19 +75,39 @@ Amount: Amount of item that will be obtained.
 
 ---------------------------------------
 
-SubGroup: Setting this to '0' makes the item always obtainable ("must" item).
-		  SubGroup value will allocate where the item will be stored at random group.
+SubGroup: Unique number to create a list of item.
 
+---------------------------------------
+
+Algorithm: Type of algorithm associated with SubGroup.
+
+Random            - A random item is picked from the sub group using rate as chance for an item being picked.
+                    The chance remains the same every time.
+All               - All items in this sub group shall be picked.
+                    If you use a command that is supposed to return only one item with such a sub group, then a random item is returned instead, with each
+                    item having the same chance to be picked.
+                    When using this algorithm, the rate must remain unspecified (0).
+SharedPool        - Rate is the amount of items of this item ID in the sub group. A random item is picked from all the items in the group and then removed
+                    from the sub group. That means each time an item is returned from the sub group, it will have a lower chance to be returned again and if
+                    no more items of this item ID remain in the sub group, it cannot be returned at all anymore. This also means that if the server requests
+                    an item from this sub group as often as there are total items in this sub group, it will get exactly the amounts specified under "rate".
+                    Only when the group is completely empty or the server restarts, the group refills.
+
+Default: SharedPool
+
+Example:
 	Item Group:
       - Group: MyItemGroup
         SubGroups:
           - SubGroup: 0
+            Algorithm: All
             List:
               - Index: 0
                 Item: Knife       # "must" item(s)
               - Index: 1
                 Item: Dagger      # "must" item(s)
           - SubGroup: 1
+            Algorithm: Random
             List:
               - Index: 0
                 Item: Stiletto    # random at SubGroup 1
@@ -96,6 +116,7 @@ SubGroup: Setting this to '0' makes the item always obtainable ("must" item).
                 Item: Stiletto_   # random at SubGroup 1
                 Rate: 2
           - SubGroup: 2
+            Algorithm: Random
             List:
               - Index: 0
                 Item: Stiletto    # random at SubGroup 2
@@ -151,6 +172,59 @@ SubGroup: Setting this to '0' makes the item always obtainable ("must" item).
 		- Returns Item ID of Dagger by chance 2/7 from 'must' SubGroup
 		- 'SubGroup 1' and 'SubGroup 2' are ignored
 
+Example #2:
+
+	Item Group:
+	  - Group: MyItemGroup2
+	    SubGroups:
+	      - SubGroup: 1
+	        Algorithm: SharedPool
+	        List:
+	          - Index: 0
+	            Item: Milk
+	            Rate: 10
+	            Amount: 3
+	          - Index: 1
+	            Item: Well_Baked_Cookie
+	            Rate: 5
+	            Amount: 2
+	          - Index: 2
+	            Item: Gift_Box
+	            Rate: 1
+
+(Note: Specifying the "SharedPool" algorithm is optional, as it defaults to SharedPool if not specified.)
+
+You can interpret this as within MyItemGroup2 there are 10 packs of 3x Milk, 5 packs of 2x Well_Baked_Cookie and 1 Gift_Box; 16 packs in total.
+
+Usages:
+'getgroupitem(IG_MyItemGroup2);'
+'getrandgroupitem(IG_MyItemGroup2);'
+
+The first time one of the two commands above are called:
+	- Player has chance to get 3x Milk by chance 10/16
+	- Player has chance to get 2x Well_Baked_Cookie by chance 5/16
+	- Player has chance to get 1x Gift_Box by chance 1/16
+
+Let's say a pack of Well_Baked_Cookie was received from the group. That means only 4 packs of Well_Baked_Cookie remain in the group.
+
+The second time one of the two commands above are called:
+	- Player has chance to get 3x Milk by chance 10/15
+	- Player has chance to get 2x Well_Baked_Cookie by chance 4/15
+	- Player has chance to get 1x Gift_Box by chance 1/15
+
+Now a Gift_Box is received from the group. That means no more Gift_Box are remaining in the group.
+
+The third time one of the two commands above are called:
+	- Player has chance to get 3x Milk by chance 10/14
+	- Player has chance to get 2x Well_Baked_Cookie by chance 4/14
+
+After the two commands were called 16 times, the server will always have given out exactly:
+	- 30 Milk (10 packs of 3x Milk)
+	- 10 Well_Baked_Cookie (5 packs of 2x Well_Baked_Cookie)
+	- 1 Gift_Box
+
+Now the group is refilled and the next time the command is called, it will behave similar to the first time.
+
 ---------------------------------------
 
 Announced: If player obtained this item, it will be broadcast to the server.

+ 10 - 4
doc/script_commands.txt

@@ -5389,7 +5389,11 @@ example, for a random pet lure:
 getitem groupranditem(IG_Taming),1;
 
 'sub_group' is used to get the available random items of item group from specified random
-group. 0 for 'must' item group, and random item group is 1 until 5 (MAX_ITEMGROUP_RANDGROUP+1).
+group. If 'sub_group' is not defined the value will be 1. Make sure the group has defined a
+sub group with the given value.
+The algorithm specified in the sub group determines how the item is picked.
+If the sub group algorithm is "All", then a random item in the group will be returned with
+each item having the same chance of being picked.
 
 More info, see doc/item_group.txt.
 
@@ -5397,14 +5401,13 @@ More info, see doc/item_group.txt.
 
 *getrandgroupitem <group_id>{,<quantity>{,<sub_group>{,<identify>{,<char_id>}}}};
 
-Similar to the above example, this command allows players to obtain the specified
+Similar to "groupranditem", this command allows players to obtain the specified
 quantity of a random item from the group "<group id>". The different groups and
 their group number are specified in db/(pre-)re/item_group_db.yml
 
 If 'quantity' is not defined or 0, it will uses defined amount from Item Group list.
 
-If 'sub_group' is not defined the value will be 1 (since random group is 1 ~ 5, and 0 is
-'must' item group).
+Sub groups and their algorithm work the same way as explained for "groupranditem".
 
 For item with type IT_WEAPON, IT_ARMOR, IT_PETARMOR, and IT_SHADOWGEAR will be given
 as unidentified item (as defined by itemdb_isidentified in src/map/itemdb.cpp) except
@@ -5424,6 +5427,9 @@ For item with type IT_WEAPON, IT_ARMOR, IT_PETARMOR, and IT_SHADOWGEAR will be g
 as unidentified item (as defined by itemdb_isidentified in src/map/itemdb.cpp) except
 if 'identify' is defined with value 1.
 
+For each sub group defined for the item group, items will be given out according to
+their corresponding algorithm.
+
 More info, see doc/item_group.txt.
 
 ---------------------------------------

+ 1 - 0
doc/yaml/db/item_group_db.yml

@@ -8,6 +8,7 @@
 # - Group                      Group name of the item.
 #   SubGroups:
 #     - SubGroup:              SubGroup number associated with the list of item. See doc/item_group.txt for more information.
+#       Algorithm:             Type of algorithm associated with SubGroup. (Default: SharedPool)
 #       List:                  List of item(s) for the SubGroup.
 #        - Index               Unique number that can be used to add the same Item with different data in the list.
 #          Item                AegisName of item that will be obtained from this item group.

+ 113 - 58
src/map/itemdb.cpp

@@ -2888,33 +2888,72 @@ 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> ItemGroupDatabase::get_random_itemsubgroup(std::shared_ptr<s_item_group_random> random, e_group_search_type search_type) {
+std::shared_ptr<s_item_group_entry> ItemGroupDatabase::get_random_itemsubgroup(std::shared_ptr<s_item_group_random> random, e_group_algorithm_type algorithm) {
 	if (random == nullptr)
 		return nullptr;
 
-	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 (rnd_chance_official<uint16>(entry->rate, 10000))
-			return entry;
-	}
-	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)
+	// Use algorithm defined for the sub group
+	if (algorithm == GROUP_ALGORITHM_USEDB)
+		algorithm = random->algorithm;
+
+	switch( algorithm ) {
+		case GROUP_ALGORITHM_DROP: {
+			// We pick a random item from the group and then do a drop check based on the rate. On fail, do not return any item
+			std::shared_ptr<s_item_group_entry> entry = util::umap_random(random->data);
+			if (rnd_chance_official<uint16>(entry->adj_rate, 10000))
 				return entry;
+			break;
+		}
+		case GROUP_ALGORITHM_ALL:
+			// This group algorithm is usually used to return all items in the group
+			// The code here is only reached when using this algorithm in a command that expects to return only one item
+			// In this case, we return a random item in the group
+			return util::umap_random(random->data);
+		case GROUP_ALGORITHM_RANDOM: {
+			// 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;
+				// 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;
+			}
+			break;
+		}
+		case GROUP_ALGORITHM_SHAREDPOOL: {
+			// By default, each item has x positions whereas x is the rate defined for the item in the umap
+			// Each time an item is picked, it has one of its positions removed until no positions remain in the group
+			// We pick a random position from all remaining positions and find the item that is at this position
+			uint32 pos = rnd_value<uint32>(1, random->total_rate - random->total_given);
+			uint32 current_pos = 1;
+			// Iterate through each item in the umap
+			for (const auto& [index, entry] : random->data) {
+				if (entry == nullptr)
+					return nullptr;
+				// We move as many positions as this item has left
+				current_pos += (entry->rate - entry->given);
+				// If we passed the target position, entry is the item we are looking for
+				if (current_pos > pos) {
+					// Increase amount item has been given out
+					entry->given++;
+					random->total_given++;
+					// All items have been given out, reset all entries in the group
+					if (random->total_given >= random->total_rate) {
+						random->total_given = 0;
+						for (const auto& [reset_index, reset_entry] : random->data) {
+							reset_entry->given = 0;
+						}
+					}
+					return entry;
+				}
+			}
+			break;
 		}
 	}
 	// Return nullptr on fail
@@ -2924,11 +2963,11 @@ std::shared_ptr<s_item_group_entry> ItemGroupDatabase::get_random_itemsubgroup(s
 /**
 * 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
+* @param sub_group
+* @param search_type: see e_group_algorithm_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, e_group_search_type search_type) {
+std::shared_ptr<s_item_group_entry> ItemGroupDatabase::get_random_entry(uint16 group_id, uint8 sub_group, e_group_algorithm_type algorithm) {
 	std::shared_ptr<s_item_group_db> group = this->find(group_id);
 
 	if (group == nullptr) {
@@ -2944,14 +2983,14 @@ std::shared_ptr<s_item_group_entry> ItemGroupDatabase::get_random_entry(uint16 g
 		return nullptr;
 	}
 
-	return this->get_random_itemsubgroup(group->random[sub_group], search_type);
+	return this->get_random_itemsubgroup(group->random[sub_group], algorithm);
 }
 
 /** [Cydh]
 * Gives item(s) to the player based on item group
 * @param sd: Player that obtains item from item group
-* @param group_id: The group ID of item that obtained by player
-* @param *group: struct s_item_group from itemgroup_db[group_id].random[idx] or itemgroup_db[group_id].must[sub_group][idx]
+* @param identify
+* @param data: item data selected in a subgroup
 */
 void ItemGroupDatabase::pc_get_itemgroup_sub( map_session_data& sd, bool identify, std::shared_ptr<s_item_group_entry> data ){
 	if (data == nullptr)
@@ -3029,21 +3068,17 @@ uint8 ItemGroupDatabase::pc_get_itemgroup( uint16 group_id, bool identify, map_s
 	if (group->random.empty())
 		return 0;
 
-	// Get all the 'must' item(s) (subgroup 0)
-	std::shared_ptr<s_item_group_random> must = util::umap_find(group->random, static_cast<uint16>(0));
-	if( must != nullptr ){
-		for (const auto &it : must->data)
-			this->pc_get_itemgroup_sub( sd, identify, it.second );
-	}
-
-	// Get 1 'random' item from each subgroup
 	for (const auto &random : group->random) {
-		// Skip the 'must' group
-		if( random.first == 0 ){
-			continue;
+		switch( random.second->algorithm ) {
+			case GROUP_ALGORITHM_RANDOM:
+			case GROUP_ALGORITHM_SHAREDPOOL:
+				this->pc_get_itemgroup_sub( sd, identify, this->get_random_itemsubgroup( random.second ) );
+				break;
+			case GROUP_ALGORITHM_ALL:
+				for (const auto &it : random.second->data)
+					this->pc_get_itemgroup_sub( sd, identify, it.second );
+				break;
 		}
-
-		this->pc_get_itemgroup_sub( sd, identify, this->get_random_itemsubgroup( random.second ) );
 	}
 
 	return 0;
@@ -3315,20 +3350,38 @@ uint64 ItemGroupDatabase::parseBodyNode(const ryml::NodeRef& node) {
 
 			uint16 subgroup;
 
-			if (this->nodeExists(subit, "SubGroup")) {
-				if (!this->asUInt16(subit, "SubGroup", subgroup))
-					continue;
-			} else {
-				subgroup = 1;
-			}
+			if (!this->asUInt16(subit, "SubGroup", subgroup))
+				continue;
 
 			std::shared_ptr<s_item_group_random> random = util::umap_find(group->random, subgroup);
+			bool random_exists = random != nullptr;
 
-			if (random == nullptr) {
+			if (!random_exists) {
 				random = std::make_shared<s_item_group_random>();
 				group->random[subgroup] = random;
 			}
 
+			if (this->nodeExists(subit, "Algorithm")) {
+				std::string sub_str;
+
+				if (!this->asString(subit, "Algorithm", sub_str))
+					return 0;
+
+				std::string sub_constant_str = "GROUP_ALGORITHM_" + sub_str;
+				int64 constant_str;
+
+				if (!script_get_constant(sub_constant_str.c_str(), &constant_str)) {
+					this->invalidWarning(subit["Algorithm"], "Invalid algorithm %s.\n", sub_str.c_str());
+					continue;
+				}
+
+				random->algorithm = static_cast<e_group_algorithm_type>(constant_str);
+			} else {
+				if (!random_exists) {
+					random->algorithm = GROUP_ALGORITHM_SHAREDPOOL;
+				}
+			}
+
 			const auto& listNode = subit["List"];
 
 			for (const auto& listit : listNode) {
@@ -3400,20 +3453,21 @@ uint64 ItemGroupDatabase::parseBodyNode(const ryml::NodeRef& node) {
 						entry->rate = 0;
 				}
 
-				if (subgroup == 0 && entry->rate > 0) {
-					this->invalidWarning(listit["Item"], "SubGroup 0 is reserved for item without Rate ('must' item). Defaulting Rate to 0.\n");
+				if (random->algorithm == GROUP_ALGORITHM_ALL && entry->rate > 0) {
+					this->invalidWarning(listit["Item"], "Item cannot have a rate with \"All\" algorithm. Defaulting Rate to 0.\n");
 					entry->rate = 0;
 				}
-				if (subgroup != 0 && entry->rate == 0) {
-					this->invalidWarning(listit["Item"], "Entry must have a Rate for group above 0, skipping.\n");
+				if (random->algorithm != GROUP_ALGORITHM_ALL && entry->rate == 0) {
+					this->invalidWarning(listit["Item"], "Missing rate, item skipped.\n");
 					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);
-				}
+				// Adjusted rate
+				entry->adj_rate = (entry->rate * battle_config.item_group_rate) / 100;
+				entry->adj_rate = cap_value(entry->adj_rate, battle_config.item_group_drop_min, battle_config.item_group_drop_max);
+
+				// Reset amount given
+				entry->given = 0;
 
 				if (this->nodeExists(listit, "Amount")) {
 					uint16 amount;
@@ -3592,6 +3646,7 @@ void ItemGroupDatabase::loadingFinished() {
 	for (const auto &group : *this) {
 		for (const auto &random : group.second->random) {
 			random.second->total_rate = 0;
+			random.second->total_given = 0;
 			for (const auto &it : random.second->data) {
 				random.second->total_rate += it.second->rate;
 			}

+ 13 - 6
src/map/itemdb.hpp

@@ -3009,9 +3009,12 @@ enum e_delay_consume : uint8 {
 };
 
 /// 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
+enum e_group_algorithm_type : uint8 {
+	GROUP_ALGORITHM_USEDB, // Use algorithm defined in the database for the sub group (All, Random or SharedPool).
+	GROUP_ALGORITHM_DROP, // Pick one item from the group and use rate as drop rate, on fail, do not return any item.
+	GROUP_ALGORITHM_ALL, // All items have an equal chance to be returned. Using getgroupitem will return all items in the group.
+	GROUP_ALGORITHM_RANDOM, // Always return an item from the group, rate determines which item is more likely to be returned.
+	GROUP_ALGORITHM_SHAREDPOOL, // Rate is the amount of items in the group, return a random item and remove it from the group.
 };
 
 /// Item combo struct
@@ -3123,6 +3126,8 @@ struct s_item_group_entry
 {
 	t_itemid nameid; /// Item ID
 	uint16 rate;
+	uint16 adj_rate; /// Rate adjusted by the battle_config.item_group_rate
+	uint16 given; /// Amount of times this item has already been given out
 	uint16 duration, /// Duration if item as rental item (in minutes)
 		amount; /// Amount of item will be obtained
 	bool isAnnounced, /// Broadcast if player get this item
@@ -3139,6 +3144,8 @@ struct s_item_group_entry
 struct s_item_group_random
 {
 	uint32 total_rate;
+	uint32 total_given; /// Amount of times an item from this group has been given out
+	e_group_algorithm_type algorithm;
 	std::unordered_map<uint32, std::shared_ptr<s_item_group_entry>> data; /// index, s_item_group_entry
 };
 
@@ -3296,7 +3303,7 @@ extern ItemDatabase item_db;
 
 class ItemGroupDatabase : public TypesafeCachedYamlDatabase<uint16, s_item_group_db> {
 public:
-	ItemGroupDatabase() : TypesafeCachedYamlDatabase("ITEM_GROUP_DB", 3, 1) {
+	ItemGroupDatabase() : TypesafeCachedYamlDatabase("ITEM_GROUP_DB", 4, 1) {
 
 	}
 
@@ -3307,11 +3314,11 @@ public:
 	// Additional
 	bool item_exists(uint16 group_id, t_itemid nameid);
 	int16 item_exists_pc(map_session_data *sd, uint16 group_id);
-	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);
+	std::shared_ptr<s_item_group_entry> get_random_entry(uint16 group_id, uint8 sub_group, e_group_algorithm_type algorithm = GROUP_ALGORITHM_USEDB);
 	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);
+	std::shared_ptr<s_item_group_entry> get_random_itemsubgroup(std::shared_ptr<s_item_group_random> random, e_group_algorithm_type algorithm = GROUP_ALGORITHM_USEDB);
 	void pc_get_itemgroup_sub( map_session_data& sd, bool identify, std::shared_ptr<s_item_group_entry> data );
 };
 

+ 4 - 4
src/map/mob.cpp

@@ -2961,10 +2961,10 @@ int32 mob_dead(struct mob_data *md, struct block_list *src, int32 type)
 						mobdrop.rate = drop_rate;
 					}
 					else {
-						std::shared_ptr<s_item_group_entry> entry = itemdb_group.get_random_entry(it.group, 1, GROUP_SEARCH_DROP);
+						std::shared_ptr<s_item_group_entry> entry = itemdb_group.get_random_entry(it.group, 1, GROUP_ALGORITHM_DROP);
 						if (entry == nullptr) continue;
 						mobdrop.nameid = entry->nameid;
-						mobdrop.rate = entry->rate * drop_rate / 10000;
+						mobdrop.rate = entry->adj_rate * drop_rate / 10000;
 					}
 
 					std::shared_ptr<s_item_drop> ditem = mob_setdropitem(mobdrop, 1, md->mob_id);
@@ -3018,11 +3018,11 @@ int32 mob_dead(struct mob_data *md, struct block_list *src, int32 type)
 
 		// 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);
+			std::shared_ptr<s_item_group_entry> entry = itemdb_group.get_random_entry(IG_ORE, 1, GROUP_ALGORITHM_DROP);
 			if (entry != nullptr) {
 				s_mob_drop mobdrop = {};
 				mobdrop.nameid = entry->nameid;
-				mobdrop.rate = entry->rate;
+				mobdrop.rate = entry->adj_rate;
 
 				std::shared_ptr<s_item_drop> ditem = mob_setdropitem(mobdrop, 1, md->mob_id);
 

+ 4 - 0
src/map/script_constants.hpp

@@ -10560,6 +10560,10 @@
 	export_constant(SEARCHSTORE_EFFECT_NORMAL);
 	export_constant(SEARCHSTORE_EFFECT_REMOTE);
 
+	export_constant(GROUP_ALGORITHM_RANDOM);
+	export_constant(GROUP_ALGORITHM_SHAREDPOOL);
+	export_constant(GROUP_ALGORITHM_ALL);
+
 	#undef export_constant
 	#undef export_constant2
 	#undef export_parameter

+ 6 - 2
src/tool/csv2yaml.cpp

@@ -440,14 +440,14 @@ bool Csv2YamlTool::initialize( int32 argc, char* argv[] ){
 	}
 
 	item_group_txt_data(path_db_mode, path_db);
-	if (!process("ITEM_GROUP_DB", 2, { path_db_mode }, "item_group_db", [](const std::string &path, const std::string &name_ext) -> bool {
+	if (!process("ITEM_GROUP_DB", 4, { path_db_mode }, "item_group_db", [](const std::string &path, const std::string &name_ext) -> bool {
 		return itemdb_read_group_yaml();
 	})) {
 		return false;
 	}
 
 	item_group_txt_data(path_db_import, path_db_import);
-	if (!process("ITEM_GROUP_DB", 2, { path_db_import }, "item_group_db", [](const std::string &path, const std::string &name_ext) -> bool {
+	if (!process("ITEM_GROUP_DB", 4, { path_db_import }, "item_group_db", [](const std::string &path, const std::string &name_ext) -> bool {
 		return itemdb_read_group_yaml();
 	})) {
 		return false;
@@ -4084,6 +4084,10 @@ static bool itemdb_read_group_yaml(void) {
 		for (const auto &item : it.second.item) {	// subgroup
 			body << YAML::BeginMap;
 			body << YAML::Key << "SubGroup" << YAML::Value << item.first;
+			if (item.first == 0)
+				body << YAML::Key << "Algorithm" << YAML::Value << "All";
+			else if (item.first == 6)
+				body << YAML::Key << "Algorithm" << YAML::Value << "Random";
 			body << YAML::Key << "List";
 			body << YAML::BeginSeq;
 			for (const auto &listit : item.second) {

+ 13 - 5
src/tool/yamlupgrade.cpp

@@ -145,7 +145,7 @@ bool YamlUpgradeTool::initialize( int32 argc, char* argv[] ){
 		} ) ){
 		return false;
 	}
-	if( !process( "ITEM_GROUP_DB", 3, root_paths, "item_group_db", []( const std::string& path, const std::string& name_ext, uint32 source_version ) -> bool {
+	if( !process( "ITEM_GROUP_DB", 4, root_paths, "item_group_db", []( const std::string& path, const std::string& name_ext, uint32 source_version ) -> bool {
 		return upgrade_item_group_db( path + name_ext, source_version );
 		} ) ){
 		return false;
@@ -436,8 +436,7 @@ static bool upgrade_item_group_db( std::string file, const uint32 source_version
 	size_t entries = 0;
 
 	for( const auto input : inNode["Body"] ){
-		// If under version 3
-		if( source_version < 3 ){
+		if( source_version < 4 ){
 			body << YAML::BeginMap;
 			body << YAML::Key << "Group" << YAML::Value << input["Group"];
 
@@ -447,9 +446,18 @@ static bool upgrade_item_group_db( std::string file, const uint32 source_version
 
 				for (const auto &it : input["SubGroups"]) {
 					body << YAML::BeginMap;
-					if( it["SubGroup"].IsDefined() ){
-						body << YAML::Key << "SubGroup" << YAML::Value << it["SubGroup"];
+					if( !it["SubGroup"].IsDefined() ){
+						ShowError( "Cannot upgrade automatically, SubGroup is missing." );
+						return false;
 					}
+					body << YAML::Key << "SubGroup" << YAML::Value << it["SubGroup"];
+
+					if (it["SubGroup"].as<uint16>() == 0)
+						body << YAML::Key << "Algorithm" << YAML::Value << "All";
+					else if (it["SubGroup"].as<uint16>() == 6)
+						body << YAML::Key << "Algorithm" << YAML::Value << "Random";
+					// else
+						// body << YAML::Key << "Algorithm" << YAML::Value << "SharedPool";
 
 					if( it["List"].IsDefined() )
 						body << YAML::Key << "List";{

Некоторые файлы не были показаны из-за большого количества измененных файлов