فهرست منبع

Initial implementation of enchantgrade UI (#6913)

Includes walkscript conversion of kRO scripts

Thanks to @Asheraf, @Balferian, @JohnnyPlayy, @aleos89, @Atemo, @eppc0330 and @Pokye.

Co-authored-by: JohnnyPlayy <lenon32@gmail.com>
Co-authored-by: Asheraf <Asheraf@users.noreply.github.com>
Co-authored-by: Balferian <balfear@yandex.ru>
Co-authored-by: Aleos <aleos89@users.noreply.github.com>
Co-authored-by: Atemo <Atemo@users.noreply.github.com>
Lemongrass3110 2 سال پیش
والد
کامیت
001981cf66

+ 3 - 0
conf/atcommands.yml

@@ -1010,6 +1010,9 @@ Body:
     Aliases:
       - famepoint
       - famepoints
+  - Command: enchantgradeui
+    Help: |
+      Opens the enchantgrade UI.
 
 Footer:
   Imports:

+ 31 - 28
conf/log_athena.conf

@@ -8,37 +8,40 @@
 //--------------------------------------------------------------
 
 // Enable Logs? (Note 3)
-// 0x0000000 - Don't log at all
-// 0x0000001 - (T) Log trades
-// 0x0000002 - (V) Log vending transactions
-// 0x0000004 - (P) Log items drop/picked by players
-// 0x0000008 - (L) Log items drop/looted by monsters
-// 0x0000010 - (S) Log NPC transactions (buy/sell)
-// 0x0000020 - (N) Log Script transactions (items deleted/acquired through quests)
-// 0x0000040 - (D) Log items stolen from mobs (Steal/Gank)
-// 0x0000080 - (C) Log player-used items (consumables/pet&hom&merc food/items used for skills&attacks)
-// 0x0000100 - (O) Log produced/ingredient items
-// 0x0000200 - (U) Log MVP prize items
-// 0x0000400 - (A) Log player created/deleted items (through @/# commands)
-// 0x0000800 - (R) Log items placed/retrieved from storage.
-// 0x0001000 - (G) Log items placed/retrieved from guild storage.
-// 0x0002000 - (E) Log mail system transactions.
-// 0x0004000 - (I) Log auction system transactions.
-// 0x0008000 - (B) Log buying store transactions
-// 0x0010000 - (X) Log all other transactions (rentals expiring/inserting cards/items removed by item_check/
+// 0x00000000 - Don't log at all
+// 0x00000001 - (T) Log trades
+// 0x00000002 - (V) Log vending transactions
+// 0x00000004 - (P) Log items drop/picked by players
+// 0x00000008 - (L) Log items drop/looted by monsters
+// 0x00000010 - (S) Log NPC transactions (buy/sell)
+// 0x00000020 - (N) Log Script transactions (items deleted/acquired through quests)
+// 0x00000040 - (D) Log items stolen from mobs (Steal/Gank)
+// 0x00000080 - (C) Log player-used items (consumables/pet&hom&merc food/items used for skills&attacks)
+// 0x00000100 - (O) Log produced/ingredient items
+// 0x00000200 - (U) Log MVP prize items
+// 0x00000400 - (A) Log player created/deleted items (through @/# commands)
+// 0x00000800 - (R) Log items placed/retrieved from storage.
+// 0x00001000 - (G) Log items placed/retrieved from guild storage.
+// 0x00002000 - (E) Log mail system transactions.
+// 0x00004000 - (I) Log auction system transactions.
+// 0x00008000 - (B) Log buying store transactions
+// 0x00010000 - (X) Log all other transactions (rentals expiring/inserting cards/items removed by item_check/
 //           rings deleted by divorce/pet egg (un)hatching/pet armor (un)equipping/Weapon Refine skill/Remove Trap skill/Stylist)
-// 0x0020000 - ($) Log cash transactions
-// 0x0040000 - (K) Log account bank transactions
-// 0x0080000 - (F) Removed bound items when guild/party is broken
-// 0x0100000 - (Y) Log Roulette Lottery
-// 0x0200000 - (Z) Merged items from item mergers process.
-// 0x0400000 - (Q) Log items given from quest-granted drops.
-// 0x0800000 - (H) Log items consumed by Private Airship system
-// 0x1000000 - (J) Log Barter Shop transactions
-// 0x2000000 - (W) Log Laphine system transactions
+// 0x00020000 - ($) Log cash transactions
+// 0x00040000 - (K) Log account bank transactions
+// 0x00080000 - (F) Removed bound items when guild/party is broken
+// 0x00100000 - (Y) Log Roulette Lottery
+// 0x00200000 - (Z) Merged items from item mergers process.
+// 0x00400000 - (Q) Log items given from quest-granted drops.
+// 0x00800000 - (H) Log items consumed by Private Airship system
+// 0x01000000 - (J) Log Barter Shop transactions
+// 0x02000000 - (W) Log Laphine system transactions
+// 0x04000000 - (0) Enchantgrade UI
+// 0x08000000 - (1) Reform UI
+// 0x10000000 - (2) Enchant UI
 // Example: Log trades+vending+script items+created items: 1+2+32+1024 = 1059
 // Please note that moving items from inventory to cart and back is not logged by design.
-enable_logs: 0xFFFFFFF
+enable_logs: 0xFFFFFFFF
 
 // Use MySQL Logs? (Note 1)
 sql_logs: yes

+ 4 - 1
conf/msg_conf/map_msg.conf

@@ -921,7 +921,10 @@
 // @mobinfo RES/MRES
 827:  RES:%d  MRES:%d
 
-//828-899 free
+// General packet version check messages
+828: This command requires packet version %s or newer.
+
+//829-899 free
 
 //------------------------------------
 // More atcommands message

+ 58 - 0
db/enchantgrade.yml

@@ -0,0 +1,58 @@
+# 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/>.
+#
+###########################################################################
+# Enchantgrade Database
+###########################################################################
+#
+# Enchantgrade Settings
+#
+###########################################################################
+# - Type                                Item type.
+#   Levels:                             Enchantgrade settings per item level.
+#     - Level                           Item level.
+#       Grades:                         Enchantgrade settings per grade level.
+#         - Grade                       Enchantgrade level.
+#           Refine                      Required refine level.
+#           Chance                      Base chance of success out of 0~10000.
+#           Bonus                       Enchantgrade bonus. (Default: 0)
+#           Announce                    Announce if someone tries to increase the enchantgrade. (Default: true)
+#           Catalyst:                   Catalyst item to increase chance of success.
+#             Item                      The item that can be used.
+#             AmountPerStep             Amount of Item needed.
+#                                       Set to 0 to disable the catalyst.
+#             MaximumSteps              Maximum amount of times Item can be used.
+#             ChanceIncrease            Amount at which the chance increases for each Item used.
+#           Options:                    Success chance based on cost type.
+#             - Option                  Index of the client option.
+#               Item                    Required item.
+#               Amount                  Amount of required item. (Default: 1)
+#                                       Set to 0 to remove an option.
+#               Price                   Amount of zeny required. (Default: 0)
+#               BreakingRate            Chance of item breaking out of 0~10000. (Default: 0)
+#               DowngradeAmount         Number of refine levels reduced on failure. (Default: 0)
+###########################################################################
+
+Header:
+  Type: ENCHANTGRADE_DB
+  Version: 1
+
+Footer:
+  Imports:
+  - Path: db/re/enchantgrade.yml
+    Mode: Renewal
+  - Path: db/import/enchantgrade.yml

+ 52 - 0
db/import-tmpl/enchantgrade.yml

@@ -0,0 +1,52 @@
+# 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/>.
+#
+###########################################################################
+# Enchantgrade Database
+###########################################################################
+#
+# Enchantgrade Settings
+#
+###########################################################################
+# - Type                                Item type.
+#   Levels:                             Enchantgrade settings per item level.
+#     - Level                           Item level.
+#       Grades:                         Enchantgrade settings per grade level.
+#         - Grade                       Enchantgrade level.
+#           Refine                      Required refine level.
+#           Chance                      Base chance of success out of 0~10000.
+#           Bonus                       Enchantgrade bonus. (Default: 0)
+#           Announce                    Announce if someone tries to increase the enchantgrade. (Default: true)
+#           Catalyst:                   Catalyst item to increase chance of success.
+#             Item                      The item that can be used.
+#             AmountPerStep             Amount of Item needed.
+#                                       Set to 0 to disable the catalyst.
+#             MaximumSteps              Maximum amount of times Item can be used.
+#             ChanceIncrease            Amount at which the chance increases for each Item used.
+#           Options:                    Success chance based on cost type.
+#             - Option                  Index of the client option.
+#               Item                    Required item.
+#               Amount                  Amount of required item. (Default: 1)
+#                                       Set to 0 to remove an option.
+#               Price                   Amount of zeny required. (Default: 0)
+#               BreakingRate            Chance of item breaking out of 0~10000. (Default: 0)
+#               DowngradeAmount         Number of refine levels reduced on failure. (Default: 0)
+###########################################################################
+
+Header:
+  Type: ENCHANTGRADE_DB
+  Version: 1

+ 214 - 0
db/re/enchantgrade.yml

@@ -0,0 +1,214 @@
+# 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/>.
+#
+###########################################################################
+# Enchantgrade Database
+###########################################################################
+#
+# Enchantgrade Settings
+#
+###########################################################################
+# - Type                                Item type.
+#   Levels:                             Enchantgrade settings per item level.
+#     - Level                           Item level.
+#       Grades:                         Enchantgrade settings per grade level.
+#         - Grade                       Enchantgrade level.
+#           Refine                      Required refine level.
+#           Chance                      Base chance of success out of 0~10000.
+#           Bonus                       Enchantgrade bonus. (Default: 0)
+#           Announce                    Announce if someone tries to increase the enchantgrade. (Default: true)
+#           Catalyst:                   Catalyst item to increase chance of success.
+#             Item                      The item that can be used.
+#             AmountPerStep             Amount of Item needed.
+#                                       Set to 0 to disable the catalyst.
+#             MaximumSteps              Maximum amount of times Item can be used.
+#             ChanceIncrease            Amount at which the chance increases for each Item used.
+#           Options:                    Success chance based on cost type.
+#             - Option                  Index of the client option.
+#               Item                    Required item.
+#               Amount                  Amount of required item. (Default: 1)
+#                                       Set to 0 to remove an option.
+#               Price                   Amount of zeny required. (Default: 0)
+#               BreakingRate            Chance of item breaking out of 0~10000. (Default: 0)
+#               DowngradeAmount         Number of refine levels reduced on failure. (Default: 0)
+###########################################################################
+
+Header:
+  Type: ENCHANTGRADE_DB
+  Version: 1
+
+Body:
+  - Type: Armor
+    Levels:
+      - Level: 2
+        Grades:
+          - Grade: None
+            Refine: 11
+            Chance: 7000
+            Bonus: 10
+            Catalyst:
+                Item: Blessed_Etel_Dust
+                AmountPerStep: 1
+                MaximumSteps: 10
+                ChanceIncrease: 100
+            Options:
+              - Option: 0
+                Item: Etel_Skyblue_Jewel
+                Amount: 1
+                Zeny: 175000
+                BreakingRate: 10000
+              - Option: 1
+                Item: Etel_Skyblue_Jewel
+                Amount: 5
+                Zeny: 875000
+          - Grade: D
+            Refine: 11
+            Chance: 6000
+            Bonus: 30
+            Catalyst:
+                Item: Blessed_Etel_Dust
+                AmountPerStep: 3
+                MaximumSteps: 10
+                ChanceIncrease: 100
+            Options:
+              - Option: 0
+                Item: Etel_Topaz
+                Amount: 1
+                Zeny: 175000
+                BreakingRate: 10000
+              - Option: 1
+                Item: Etel_Topaz
+                Amount: 5
+                Zeny: 875000
+          - Grade: C
+            Refine: 11
+            Chance: 5000
+            Bonus: 50
+            Catalyst:
+                Item: Blessed_Etel_Dust
+                AmountPerStep: 5
+                MaximumSteps: 10
+                ChanceIncrease: 100
+            Options:
+              - Option: 0
+                Item: Etel_Violet_Jewel
+                Amount: 1
+                Zeny: 175000
+                BreakingRate: 10000
+              - Option: 1
+                Item: Etel_Violet_Jewel
+                Amount: 5
+                Zeny: 875000
+          - Grade: B
+            Refine: 11
+            Chance: 4000
+            Bonus: 100
+            Catalyst:
+                Item: Blessed_Etel_Dust
+                AmountPerStep: 7
+                MaximumSteps: 10
+                ChanceIncrease: 100
+            Options:
+              - Option: 0
+                Item: Etel_Amber
+                Amount: 2
+                Zeny: 175000
+                BreakingRate: 10000
+              - Option: 1
+                Item: Etel_Amber
+                Amount: 10
+                Zeny: 875000
+  - Type: Weapon
+    Levels:
+      - Level: 5
+        Grades:
+          - Grade: None
+            Refine: 11
+            Chance: 7000
+            Bonus: 10
+            Catalyst:
+                Item: Blessed_Etel_Dust
+                AmountPerStep: 1
+                MaximumSteps: 10
+                ChanceIncrease: 100
+            Options:
+              - Option: 0
+                Item: Etel_Skyblue_Jewel
+                Amount: 1
+                Zeny: 175000
+                BreakingRate: 10000
+              - Option: 1
+                Item: Etel_Skyblue_Jewel
+                Amount: 5
+                Zeny: 875000
+          - Grade: D
+            Refine: 11
+            Chance: 6000
+            Bonus: 30
+            Catalyst:
+                Item: Blessed_Etel_Dust
+                AmountPerStep: 3
+                MaximumSteps: 10
+                ChanceIncrease: 100
+            Options:
+              - Option: 0
+                Item: Etel_Topaz
+                Amount: 1
+                Zeny: 175000
+                BreakingRate: 10000
+              - Option: 1
+                Item: Etel_Topaz
+                Amount: 5
+                Zeny: 875000
+          - Grade: C
+            Refine: 11
+            Chance: 5000
+            Bonus: 50
+            Catalyst:
+                Item: Blessed_Etel_Dust
+                AmountPerStep: 5
+                MaximumSteps: 10
+                ChanceIncrease: 100
+            Options:
+              - Option: 0
+                Item: Etel_Violet_Jewel
+                Amount: 1
+                Zeny: 175000
+                BreakingRate: 10000
+              - Option: 1
+                Item: Etel_Violet_Jewel
+                Amount: 5
+                Zeny: 875000
+          - Grade: B
+            Refine: 11
+            Chance: 4000
+            Bonus: 100
+            Catalyst:
+                Item: Blessed_Etel_Dust
+                AmountPerStep: 7
+                MaximumSteps: 10
+                ChanceIncrease: 100
+            Options:
+              - Option: 0
+                Item: Etel_Amber
+                Amount: 2
+                Zeny: 175000
+                BreakingRate: 10000
+              - Option: 1
+                Item: Etel_Amber
+                Amount: 10
+                Zeny: 875000

+ 1 - 0
db/re/item_db_etc.yml

@@ -61756,6 +61756,7 @@ Body:
     AegisName: Amber
     Name: Amber
     Type: Etc
+    Buy: 45000
     Weight: 100
   - Id: 1000322
     AegisName: Etel_Dust

+ 6 - 0
doc/atcommands.txt

@@ -1178,6 +1178,12 @@ If args are given, sets camera position.
 
 ---------------------------------------
 
+@enchantgradeui
+
+Opens the enchantgrade UI.
+
+---------------------------------------
+
 ==============================
 | 5. Administrative Commands |
 ==============================

+ 11 - 1
doc/script_commands.txt

@@ -8164,7 +8164,15 @@ This function is intended for use in item scripts.
 
 Opens the Bank UI for the attached player or the given character ID.
 
-This command requires packet version 2015-01-28 or newer.
+This command requires packet version 2015-12-02 or newer.
+
+---------------------------------------
+
+*enchantgradeui {<char id>};
+
+Opens the enchantgrade UI for the attached character or the player given by the char ID parameter.
+
+This command requires packet version 2020-07-24 or newer.
 
 ---------------------------------------
 \\
@@ -9784,6 +9792,8 @@ QMARK_PURPLE - Purple Marker
 Opens the quest UI for the attached player or the given character ID.
 Use 0 as the quest ID to open the main quest UI. If the quest ID is not 0 then the quest UI is opened to the given quest. If the quest data is not populated in the client LUB then a message will be displayed saying the quest doesn't exist.
 
+This command requires packet version 2015-12-02 or newer.
+
 ---------------------------------------
 
 ============================

+ 1 - 0
npc/re/merchants/barters.yml

@@ -52,3 +52,4 @@ Footer:
   - Path: npc/re/merchants/barters/quests_16_2.yml
   - Path: npc/re/merchants/barters/quests_17_1.yml
   - Path: npc/re/merchants/barters/refine.yml
+  - Path: npc/re/merchants/barters/enchantgrade.yml

+ 110 - 0
npc/re/merchants/barters/enchantgrade.yml

@@ -0,0 +1,110 @@
+# 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/>.
+#
+###########################################################################
+# Barter Database
+###########################################################################
+#
+# Barter Settings
+#
+###########################################################################
+#  - Name               NPC name.
+#    Map                Map name. (Default: not on a map)
+#    X                  Map x coordinate. (Default: 0)
+#    Y                  Map y coordinate. (Default: 0)
+#    Direction          Direction the NPC is looking. (Default: North)
+#    Sprite             Sprite name of the NPC. (Default: FakeNpc)
+#    Items:             List of sold items.
+#      - Index          Index of the item inside the shop. (0-...)
+#                       Maximum index depends on client.
+#        Item           Aegis name of the item.
+#        Stock          Amount of item in stock. 0 means unlimited. (Default: 0)
+#        Zeny           Cost of them item in Zeny. (Default: 0)
+#        RequiredItems: List of required items (Optional)
+#          - Index      Index of the required item. (0-4)
+#            Item       Aegis name of required item.
+#            Amount     Amount of required item. (Default: 1)
+#            Refine     Refine level of required item. (Default: 0)
+###########################################################################
+
+Header:
+  Type: BARTER_DB
+  Version: 1
+
+Body:
+###########################################################################
+##= Enchant Grade Exchange
+###########################################################################
+  - Name: EnchantGradeExchange
+    Items:
+      - Index: 0
+        Item: Etel_Stone
+        Zeny: 100000
+        RequiredItems:
+          - Index: 0
+            Item: Etel_Dust
+            Amount: 5
+      - Index: 1
+        Item: Blessed_Etel_Dust
+        Zeny: 100000
+        RequiredItems:
+          - Index: 0
+            Item: Etel_Dust
+            Amount: 5
+          - Index: 1
+            Item: Blacksmith_Blessing
+            Amount: 1
+      - Index: 2
+        Item: Etel_Skyblue_Jewel
+        Zeny: 100000
+        RequiredItems:
+          - Index: 0
+            Item: Etel_Stone
+            Amount: 3
+          - Index: 1
+            Item: Skyblue_Jewel
+            Amount: 1
+      - Index: 3
+        Item: Etel_Topaz
+        Zeny: 200000
+        RequiredItems:
+          - Index: 0
+            Item: Etel_Stone
+            Amount: 6
+          - Index: 1
+            Item: Golden_Jewel
+            Amount: 1
+      - Index: 4
+        Item: Etel_Violet_Jewel
+        Zeny: 300000
+        RequiredItems:
+          - Index: 0
+            Item: Etel_Stone
+            Amount: 10
+          - Index: 1
+            Item: Violet_Jewel
+            Amount: 1
+      - Index: 5
+        Item: Etel_Amber
+        Zeny: 300000
+        RequiredItems:
+          - Index: 0
+            Item: Etel_Stone
+            Amount: 15
+          - Index: 1
+            Item: Amber
+            Amount: 1

+ 131 - 0
npc/re/merchants/enchantgrade.txt

@@ -0,0 +1,131 @@
+//===== rAthena Script ======================================= 
+//= Enchant Grade
+//===== Changelogs: ==========================================
+//= 1.0 First Version. [JohnnyPlayy]
+//= 1.1 Fixed small issues. [Lemongrass]
+//= 1.2 Added translation. [Asheraf]
+//= 1.3 Added paramarket NPC and warps. [Balfear]
+//= 1.4 Translated paramarket NPC. [Lemongrass]
+//============================================================
+
+grademk,34,184,4	script	Sratos#sratos	4_JP_GARM_H,{
+	mes "[Sratos]";
+	mes "Hello, dear customer who walked in, let's hope for a miracle today.";
+	mes "How can I help you?";
+	next;
+	switch( select( "Enhance the equipment's grade.", "Exchange Etel items", "What is Grade Enhancement?" ) ){
+		case 1:
+			mes "[Sratos]";
+			mes "Our customer. You want to enhance the grade of equipment.";
+			mes "It is not easy to move the magical power of jewels.";
+			next;
+			mes "[Sratos]";
+			mes "Magical power can explode on the spot.";
+			mes "Then the weapon that will inherit the magical power also explodes!";
+			next;
+			switch( select( "I'll still do it!", "I'll think about it." ) ){
+				case 1:
+					mes "[Sratos]";
+					mes "I wish good luck to our courageous customers!";
+					close2;
+					enchantgradeui();
+					end;
+				case 2:
+					mes "[Sratos]";
+					mes "Whenever you have the courage to challenge, please come back.";
+					close;
+			}
+		case 2:
+			mes "[Sratos]";
+			mes "These are jewels used to enhance grades.";
+			mes "Etel Dust, the jewels that will be the base, and if you give me a small fee I'll exchange it for Etel jewels.";
+			close2;
+			callshop "EnchantGradeExchange";
+			end;
+
+		case 3:
+			mes "[Sratos]";
+			mes "Occasionally, unstable jewels with a lot of pure magical power are found.";
+			mes "We call these etheric gems, right?";
+			next;
+			mes "[Sratos]";
+			mes "You can transfer the magical power of the etheric gem to other weapons, but of course there is some risk.";
+			mes "It's delicate, so if you aren't careful, pop! It will explode.";
+			next;
+			mes "[Sratos]";
+			mes "Anyway, if you use this etheric gem, you can enhance the weapon you are using.";
+			mes "Performance gets better. We call this grade enhancement.";
+			next;
+			mes "[Sratos]";
+			mes "You too, wouldn't you be happy if the weapons you love grow one step further?";
+			mes "We are the people who assist it. It's a bit risky, though. ahahaha.";
+			close;
+	}
+
+OnInit:
+	setunittitle(getnpcid(0), "<Grade Enhancer>");
+	end;
+}
+
+paramk,34,184,4	script	Suribell#suribell	4_F_FRUIT,{
+	mes "[Suribell]";
+	mes "May good luck always be with you!";
+	mes "Welcome to Paramarket's Grade Enhancement Center~";
+	next;
+	switch( select( "Enhance the equipment's grade.", "Exchange Etel items", "What is Grade Enhancement?" )) {
+		case 1:
+			mes "[Suribell]";
+			mes "Do you want to unlock the potential of your favorite equipment?";
+			mes "Explosions may also occur in the process of dealing with magical powers.";
+			next;
+			mes "[Suribell]";
+			mes "Still, if you're trying to enhance your grade... !";
+			mes "I, Suribell, will do my best!";
+			next;
+			switch( select( "I'll still do it!", "I'll think about it." ) ){
+				case 1:
+					mes "[Suribell]";
+					mes "Let's hold hands and have faith! Try clenching your teeth!";
+					close2;
+					enchantgradeui();
+					end;
+				case 2:
+					mes "[Suribell]";
+					mes "Okay! Let's drink a glass of cold water!";
+					close;
+			}
+		case 2:
+			mes "[Suribell]";
+			mes "You need etheric gems to enhance grades!";
+			mes "If you combine Etel Dust and jewels to make it, it is fine! An Etel jewel for adventurers is complete!";
+			mes "Would you like to combine jewels and Etel Dust?";
+			close2;
+			callshop "EnchantGradeExchange";
+			end;
+		case 3:
+			mes "[Suribell]";
+			mes "Occasionally, unstable jewels with a lot of pure magical power are found.";
+			mes "I decided to call such gems etheric gems.";
+			mes "Who? I am Suribell!";
+			next;
+			mes "[Suribell]";
+			mes "The magical power of etheric gems sometimes awakens the hidden potential of equipment.";
+			mes "We call it grade enhancement because it goes up one tier!";
+			next;
+			mes "[Suribell]";
+			mes "The adventurer may have a potential that is unknown to him.";
+			mes "There is a risk that it can be destroyed if done wrong, but try it with faith!";
+			close;
+	}
+
+OnInit:
+	setunittitle(getnpcid(0), "<Grade Enhancer>");
+	end;
+}
+
+// = Portals
+//============================================================
+prontera,50,293,0	warp	Grademk_Int	1,1,grademk,13,172
+grademk,9,172,0	warp	Grademk_Out	1,1,prontera,50,290
+paramk,8,171,0	warp	grade_in	1,1,paramk,141,64
+paramk,145,64,0	warp	grade_out	1,1,paramk,11,171

+ 1 - 1
npc/re/merchants/shops.txt

@@ -125,7 +125,7 @@ morocc,154,55,6	shop	Jeweler#moc3	99,730:-1,2613:-1
 morocc,171,103,4	shop	Item Collector#moc3	85,911:-1,528:-1,919:-1,925:-1
 morocc,205,247,2	shop	Item Collector#moc4	85,911:-1,528:-1,919:-1,925:-1
 morocc,140,90,6	shop	Trader#moc6	99,513:-1,513:-1,513:-1,513:-1,513:-1,513:-1
-morocc,166,54,2	shop	Jeweler#moc4	102,721:-1,723:-1,726:-1,728:-1,729:-1
+morocc,166,54,2	shop	Jeweler#moc4	102,721:-1,723:-1,726:-1,728:-1,729:-1,1000321:-1
 morocc,34,68,0	shop	Trader#moc7	93,748:-1
 morocc,269,193,4	shop	Trader#moc8	89,2609:-1,1516:-1,1522:-1
 morocc,256,191,5	shop	Trader#moc9	93,2612:-1

+ 1 - 0
npc/re/scripts_athena.conf

@@ -126,6 +126,7 @@ npc: npc/re/merchants/enchan_mal.txt
 npc: npc/re/merchants/enchan_mora.txt
 npc: npc/re/merchants/enchan_rockridge.txt
 npc: npc/re/merchants/enchan_verus.txt
+npc: npc/re/merchants/enchantgrade.txt
 npc: npc/re/merchants/Extended_Ammunition.txt
 npc: npc/re/merchants/Extended_Stylist.txt
 npc: npc/re/merchants/flute.txt

+ 7 - 2
sql-files/logs.sql

@@ -169,12 +169,15 @@ CREATE TABLE IF NOT EXISTS `npclog` (
 # Private Airs(H)ip
 # Barter Shop (J)
 # Laphine systems (W)
+# Enchantgrade UI (0)
+# Reform UI (1)
+# Enchant UI (2)
 
 CREATE TABLE IF NOT EXISTS `picklog` (
   `id` int(11) NOT NULL auto_increment,
   `time` datetime NOT NULL,
   `char_id` int(11) NOT NULL default '0',
-  `type` enum('M','P','L','T','V','S','N','C','A','R','G','E','B','O','I','X','D','U','$','F','Y','Z','Q','H','J','W') NOT NULL default 'P',
+  `type` enum('M','P','L','T','V','S','N','C','A','R','G','E','B','O','I','X','D','U','$','F','Y','Z','Q','H','J','W','0','1','2') NOT NULL default 'P',
   `nameid` int(10) unsigned NOT NULL default '0',
   `amount` int(11) NOT NULL default '1',
   `refine` tinyint(3) unsigned NOT NULL default '0',
@@ -224,13 +227,15 @@ CREATE TABLE IF NOT EXISTS `picklog` (
 # Ban(K) Transactions
 # Barter Shop (J)
 # (X) Other
+# Enchantgrade UI (0)
+# Enchant UI (2)
 
 CREATE TABLE IF NOT EXISTS `zenylog` (
   `id` int(11) NOT NULL auto_increment,
   `time` datetime NOT NULL,
   `char_id` int(11) NOT NULL default '0',
   `src_id` int(11) NOT NULL default '0',
-  `type` enum('T','V','P','M','S','N','D','C','A','E','I','B','K','J','X') NOT NULL default 'S',
+  `type` enum('T','V','P','M','S','N','D','C','A','E','I','B','K','J','X','0','2') NOT NULL default 'S',
   `amount` int(11) NOT NULL default '0',
   `map` varchar(11) NOT NULL default '',
   PRIMARY KEY  (`id`),

+ 7 - 0
sql-files/upgrades/upgrade_20220607_logs.sql

@@ -0,0 +1,7 @@
+ALTER TABLE `picklog`
+	MODIFY `type` enum('M','P','L','T','V','S','N','C','A','R','G','E','B','O','I','X','D','U','$','F','Y','Z','Q','H','J','W','0','1','2') NOT NULL default 'P'
+;
+
+ALTER TABLE `zenylog`
+	MODIFY `type` enum('T','V','P','M','S','N','D','C','A','E','I','B','K','J','X','0','2') NOT NULL default 'S'
+;

+ 10 - 0
src/common/mmo.hpp

@@ -111,12 +111,22 @@ typedef uint32 t_itemid;
 	#define MAX_BARTER_REQUIREMENTS 5
 #endif
 
+enum e_enchantgrade : uint16{
+	ENCHANTGRADE_NONE = 0,
+	ENCHANTGRADE_D,
+	ENCHANTGRADE_C,
+	ENCHANTGRADE_B,
+	ENCHANTGRADE_A
+};
+
 #ifdef RENEWAL
 	#define MAX_WEAPON_LEVEL 5
 	#define MAX_ARMOR_LEVEL 2
+	#define MAX_ENCHANTGRADE ENCHANTGRADE_A
 #else
 	#define MAX_WEAPON_LEVEL 4
 	#define MAX_ARMOR_LEVEL 1
+	#define MAX_ENCHANTGRADE ENCHANTGRADE_NONE
 #endif
 
 // for produce

+ 19 - 1
src/map/atcommand.cpp

@@ -10662,7 +10662,7 @@ ACMD_FUNC( stylist ){
 		return -1;
 	}
 
-	clif_ui_open( sd, OUT_UI_STYLIST, 0 );
+	clif_ui_open( *sd, OUT_UI_STYLIST, 0 );
 	return 0;
 #endif
 }
@@ -10697,6 +10697,23 @@ ACMD_FUNC(addfame)
 	return 0;
 }
 
+/**
+ * Opens the enchantgrade UI
+ * Usage: @enchantgradeui
+ */
+ACMD_FUNC( enchantgradeui ){
+	nullpo_retr( -1, sd );
+
+#if !( PACKETVER_MAIN_NUM >= 20200916 || PACKETVER_RE_NUM >= 20200724 )
+	sprintf( atcmd_output, msg_txt( sd, 798 ), "2020-07-24" ); // This command requires packet version %s or newer.
+	clif_displaymessage( fd, atcmd_output );
+	return -1;
+#else
+	clif_ui_open( *sd, OUT_UI_ENCHANTGRADE, 0 );
+	return 0;
+#endif
+}
+
 #include "../custom/atcommand.inc"
 
 /**
@@ -11019,6 +11036,7 @@ void atcommand_basecommands(void) {
 		ACMD_DEF(refineui),
 		ACMD_DEFR(stylist, ATCMD_NOCONSOLE|ATCMD_NOAUTOTRADE),
 		ACMD_DEF(addfame),
+		ACMD_DEFR(enchantgradeui, ATCMD_NOCONSOLE|ATCMD_NOAUTOTRADE),
 	};
 	AtCommandInfo* atcommand;
 	int i;

+ 352 - 11
src/map/clif.cpp

@@ -21551,23 +21551,32 @@ void clif_parse_changedress( int fd, struct map_session_data* sd ){
 
 /// Opens an UI window of the given type and initializes it with the given data
 /// 0AE2 <type>.B <data>.L
-void clif_ui_open( struct map_session_data *sd, enum out_ui_type ui_type, int32 data ){
-	nullpo_retv(sd);
-
+void clif_ui_open( struct map_session_data& sd, enum out_ui_type ui_type, int32 data ){
+#if PACKETVER >= 20151202
 	// If the UI requires state tracking
 	switch( ui_type ){
 		case OUT_UI_STYLIST:
-			sd->state.stylist_open = true;
+			sd.state.stylist_open = true;
+			break;
+		case OUT_UI_ENCHANTGRADE:
+#if PACKETVER_MAIN_NUM >= 20200916 || PACKETVER_RE_NUM >= 20200724
+			sd.state.enchantgrade_open = true;
 			break;
+#else
+			return;
+#endif
 	}
 
-	int fd = sd->fd;
+	struct PACKET_ZC_UI_OPEN p = {};
 
-	WFIFOHEAD(fd,packet_len(0xae2));
-	WFIFOW(fd,0) = 0xae2;
-	WFIFOB(fd,2) = ui_type;
-	WFIFOL(fd,3) = data;
-	WFIFOSET(fd,packet_len(0xae2));
+	p.PacketType = HEADER_ZC_UI_OPEN;
+	p.UIType = ui_type;
+#if PACKETVER >= 20171122
+	p.data = data;
+#endif
+
+	clif_send( &p, sizeof( p ), &sd.bl, SELF );
+#endif
 }
 
 /// Request to open an UI window of the given type
@@ -21578,7 +21587,7 @@ void clif_parse_open_ui( int fd, struct map_session_data* sd ){
 			if( !pc_has_permission( sd, PC_PERM_ATTENDANCE ) ){
 				clif_messagecolor( &sd->bl, color_table[COLOR_RED], msg_txt( sd, 791 ), false, SELF ); // You are not allowed to use the attendance system.
 			}else if( pc_attendance_enabled() ){
-				clif_ui_open( sd, OUT_UI_ATTENDANCE, pc_attendance_counter( sd ) );
+				clif_ui_open( *sd, OUT_UI_ATTENDANCE, pc_attendance_counter( sd ) );
 			}else{
 				clif_msg_color( sd, MSG_ATTENDANCE_DISABLED, color_table[COLOR_RED] );
 			}
@@ -23454,6 +23463,338 @@ void clif_parse_laphine_upgrade( int fd, struct map_session_data* sd ){
 #endif
 }
 
+void clif_enchantgrade_add( struct map_session_data& sd, uint16 index = UINT16_MAX, std::shared_ptr<s_enchantgradelevel> gradeLevel = nullptr ){
+#if PACKETVER_MAIN_NUM >= 20200916 || PACKETVER_RE_NUM >= 20200724
+	struct PACKET_ZC_GRADE_ENCHANT_MATERIAL_LIST* p = (struct PACKET_ZC_GRADE_ENCHANT_MATERIAL_LIST*)packet_buffer;
+
+	p->PacketType = HEADER_ZC_GRADE_ENCHANT_MATERIAL_LIST;
+	p->PacketLength = sizeof( struct PACKET_ZC_GRADE_ENCHANT_MATERIAL_LIST );
+
+	if( index < UINT16_MAX ){
+		p->index = client_index( index );
+		if( sd.inventory.u.items_inventory[index].refine >= gradeLevel->refine ){
+			p->success_chance = gradeLevel->chance / 100;
+		}else{
+			p->success_chance = 0;
+		}
+		p->blessing_info.id = client_nameid( gradeLevel->catalyst.item );
+		p->blessing_info.amount = gradeLevel->catalyst.amountPerStep;
+		p->blessing_info.max_blessing = gradeLevel->catalyst.maximumSteps;
+		p->blessing_info.bonus = gradeLevel->catalyst.chanceIncrease / 100;
+		// Not displayed by client
+		p->protect_itemid = 0;
+		p->protect_amount = 0;
+
+		int i = 0;
+		for( const auto& pair : gradeLevel->options ){
+			std::shared_ptr<s_enchantgradeoption> option = pair.second;
+
+			p->material_info[i].nameid = client_nameid( option->item );
+			p->material_info[i].amount = option->amount;
+			p->material_info[i].price = option->zeny;
+			p->material_info[i].downgrade = option->downgrade_amount > 0;
+			p->material_info[i].breakable = option->breaking_rate > 0;
+
+			p->PacketLength += sizeof( struct GRADE_ENCHANT_MATERIAL );
+			i++;
+		}
+	}else{
+		p->index = -1;
+		p->success_chance = 0;
+		p->blessing_info.id = 0;
+		p->blessing_info.amount = 0;
+		p->blessing_info.max_blessing = 0;
+		p->blessing_info.bonus = 0;
+		p->protect_itemid = 0;
+		p->protect_amount = 0;
+	}
+
+	clif_send( p, p->PacketLength, &sd.bl, SELF );
+#endif
+}
+
+void clif_parse_enchantgrade_add( int fd, struct map_session_data* sd ){
+#if PACKETVER_MAIN_NUM >= 20200916 || PACKETVER_RE_NUM >= 20200724
+	nullpo_retv( sd );
+
+	if( !sd->state.enchantgrade_open ){
+		return;
+	}
+
+	struct PACKET_CZ_GRADE_ENCHANT_SELECT_EQUIPMENT* p = (struct PACKET_CZ_GRADE_ENCHANT_SELECT_EQUIPMENT*)RFIFOP( fd, 0 );
+
+	uint16 index = server_index( p->index );
+
+	if( index >= MAX_INVENTORY || sd->inventory_data[index] == nullptr ){
+		return;
+	}
+
+	std::shared_ptr<s_enchantgrade> enchantgrade = enchantgrade_db.find( sd->inventory_data[index]->type );
+
+	// Unsupported item type - no answer, because client should have actually prevented this request
+	if( enchantgrade == nullptr ){
+		return;
+	}
+
+	uint16 level = 0;
+
+	if( sd->inventory_data[index]->type == IT_WEAPON ){
+		level = sd->inventory_data[index]->weapon_level;
+	}else if( sd->inventory_data[index]->type == IT_ARMOR ){
+		level = sd->inventory_data[index]->armor_level;
+	}
+
+	const auto& enchantgradelevels = enchantgrade->levels.find( level );
+
+	// Cannot upgrade this weapon or armor level
+	if( enchantgradelevels == enchantgrade->levels.end() ){
+		clif_enchantgrade_add( *sd );
+		return;
+	}
+
+	std::shared_ptr<s_enchantgradelevel> enchantgradelevel = util::map_find( enchantgradelevels->second, (e_enchantgrade)sd->inventory.u.items_inventory[index].enchantgrade );
+
+	// Cannot increase enchantgrade any further - no answer, because client should have actually prevented this request
+	if( enchantgradelevel == nullptr ){
+		return;
+	}
+
+	clif_enchantgrade_add( *sd, index, enchantgradelevel );
+#endif
+}
+
+/// <summary>
+/// Sends the result for trying to enchant an item
+/// </summary>
+/// <param name="sd">The player session</param>
+/// <param name="index">The target item</param>
+/// <param name="result">
+///  0= The grade has been successfully upgraded.
+///  1= Refinement failed.
+///  2= The refine level has decreased.
+///  3= Equipment destroyed.
+///  4= The equipment is protected.
+/// </param>
+void clif_enchantgrade_result( struct map_session_data& sd, uint16 index, e_enchantgrade_result result ){
+#if PACKETVER_MAIN_NUM >= 20200916 || PACKETVER_RE_NUM >= 20200724
+	struct PACKET_ZC_GRADE_ENCHANT_ACK p = {};
+
+	p.PacketType = HEADER_ZC_GRADE_ENCHANT_ACK;
+	p.index = client_index( index );
+	p.enchantgrade = sd.inventory.u.items_inventory[index].enchantgrade;
+	p.result = result;
+
+	clif_send( &p, sizeof( p ), &sd.bl, SELF );
+#endif
+}
+
+void clif_enchantgrade_announce( struct map_session_data& sd, struct item& item, bool success ){
+#if PACKETVER_MAIN_NUM >= 20200916 || PACKETVER_RE_NUM >= 20200724
+	struct PACKET_ZC_GRADE_ENCHANT_BROADCAST_RESULT p = {};
+
+	p.packetType = HEADER_ZC_GRADE_ENCHANT_BROADCAST_RESULT;
+	safestrncpy( p.name, sd.status.name, sizeof( p.name ) );
+	p.itemId = client_nameid( item.nameid );
+	p.enchantgrade = item.enchantgrade;
+	p.status = success;
+
+	clif_send( &p, sizeof( p ), nullptr, ALL_CLIENT );
+#endif
+}
+
+void clif_parse_enchantgrade_start( int fd, struct map_session_data* sd ){
+#if PACKETVER_MAIN_NUM >= 20200916 || PACKETVER_RE_NUM >= 20200724
+	nullpo_retv( sd );
+
+	if( !sd->state.enchantgrade_open ){
+		return;
+	}
+
+	struct PACKET_CZ_GRADE_ENCHANT_REQUEST* p = (struct PACKET_CZ_GRADE_ENCHANT_REQUEST*)RFIFOP( fd, 0 );
+
+	uint16 index = server_index( p->index );
+
+	if( index >= MAX_INVENTORY || sd->inventory_data[index] == nullptr ){
+		return;
+	}
+
+	std::shared_ptr<s_enchantgrade> enchantgrade = enchantgrade_db.find( sd->inventory_data[index]->type );
+
+	// Unsupported item type - no answer
+	if( enchantgrade == nullptr ){
+		return;
+	}
+
+	uint16 level = 0;
+
+	if( sd->inventory_data[index]->type == IT_WEAPON ){
+		level = sd->inventory_data[index]->weapon_level;
+	}else if( sd->inventory_data[index]->type == IT_ARMOR ){
+		level = sd->inventory_data[index]->armor_level;
+	}
+
+	const auto& enchantgradelevels = enchantgrade->levels.find( level );
+
+	// Cannot upgrade this weapon or armor level - no answer
+	if( enchantgradelevels == enchantgrade->levels.end() ){
+		return;
+	}
+
+	std::shared_ptr<s_enchantgradelevel> enchantgradelevel = util::map_find( enchantgradelevels->second, (e_enchantgrade)sd->inventory.u.items_inventory[index].enchantgrade );
+
+	// Cannot increase enchantgrade any further - no answer
+	if( enchantgradelevel == nullptr ){
+		return;
+	}
+
+	// Not refined enough
+	if( sd->inventory.u.items_inventory[index].refine < enchantgradelevel->refine ){
+		return;
+	}
+
+	std::shared_ptr<s_enchantgradeoption> option = util::map_find( enchantgradelevel->options, (uint16)p->material_index );
+
+	// Unknown option id - no answer
+	if( option == nullptr ){
+		return;
+	}
+
+	// Not enough zeny
+	if( sd->status.zeny < option->zeny ){
+		return;
+	}
+
+	uint16 totalChance = enchantgradelevel->chance;
+	uint16 steps = min( p->blessing_amount, enchantgradelevel->catalyst.maximumSteps );
+	std::unordered_map<uint16, uint16> requiredItems;
+
+	if( p->blessing_flag ){
+		// If the catalysator item is the same as the option item build the sum of amounts
+		if( enchantgradelevel->catalyst.item == option->item ){
+			uint16 amount = enchantgradelevel->catalyst.amountPerStep * steps + option->amount;
+
+			int16 index = pc_search_inventory( sd, enchantgradelevel->catalyst.item );
+
+			if( index < 0 ){
+				return;
+			}
+
+			if( sd->inventory.u.items_inventory[index].amount < amount ){
+				return;
+			}
+
+			requiredItems[index] = amount;
+		}else{
+			uint16 amount = enchantgradelevel->catalyst.amountPerStep * steps;
+
+			// Check catalysator item
+			int16 index = pc_search_inventory( sd, enchantgradelevel->catalyst.item );
+
+			if( index < 0 ){
+				return;
+			}
+
+			if( sd->inventory.u.items_inventory[index].amount < amount ){
+				return;
+			}
+
+			requiredItems[index] = amount;
+
+			// Check option item
+			index = pc_search_inventory( sd, option->item );
+
+			if( index < 0 ){
+				return;
+			}
+
+			if( sd->inventory.u.items_inventory[index].amount < option->amount ){
+				return;
+			}
+
+			requiredItems[index] = option->amount;
+		}
+
+		totalChance += steps * enchantgradelevel->catalyst.chanceIncrease;
+	}else{
+		// Check option item
+		int16 index = pc_search_inventory( sd, option->item );
+
+		if( index < 0 ){
+			return;
+		}
+
+		if( sd->inventory.u.items_inventory[index].amount < option->amount ){
+			return;
+		}
+
+		requiredItems[index] = option->amount;
+	}
+
+	// All items should be there, start deleting
+	for( const auto& pair : requiredItems ){
+		if( pc_delitem( sd, pair.first, pair.second, 0, 0, LOG_TYPE_ENCHANTGRADE ) != 0 ){
+			return;
+		}
+	}
+
+	if( pc_payzeny( sd, option->zeny, LOG_TYPE_ENCHANTGRADE, nullptr ) > 0 ){
+		return;
+	}
+
+	if( rnd()%10000 < totalChance ){
+		// Log removal of item
+		log_pick_pc( sd, LOG_TYPE_ENCHANTGRADE, -1, &sd->inventory.u.items_inventory[index] );
+		// Increase enchantgrade
+		sd->inventory.u.items_inventory[index].enchantgrade = min( sd->inventory.u.items_inventory[index].enchantgrade + 1, MAX_ENCHANTGRADE );
+		// On successful enchantgrade increase the refine is reset
+		sd->inventory.u.items_inventory[index].refine = 0;
+		// Log retrieving the item again -> with the new refine and enchantgrade
+		log_pick_pc( sd, LOG_TYPE_ENCHANTGRADE, 1, &sd->inventory.u.items_inventory[index] );
+		// Show success
+		clif_enchantgrade_result( *sd, index, ENCHANTGRADE_UPGRADE_SUCCESS );
+
+		// Check if it has to be announced
+		if( enchantgradelevel->announce ){
+			clif_enchantgrade_announce( *sd, sd->inventory.u.items_inventory[index], true );
+		}
+	}else{
+		// Check if it has to be announced (has to be done before deleting the item from inventory)
+		if( enchantgradelevel->announce ){
+			clif_enchantgrade_announce( *sd, sd->inventory.u.items_inventory[index], false );
+		}
+
+		// Delete the item if it is breakable
+		if( option->breaking_rate > 0 && ( rnd() % 10000 ) < option->breaking_rate ){
+			// Delete the item
+			pc_delitem( sd, index, 1, 0, 0, LOG_TYPE_ENCHANTGRADE );
+			// Show failure
+			clif_enchantgrade_result( *sd, index, ENCHANTGRADE_UPGRADE_BREAK );
+		// Downgrade the item if necessary
+		}else if( option->downgrade_amount > 0 ){
+			// Log removal of item
+			log_pick_pc( sd, LOG_TYPE_ENCHANTGRADE, -1, &sd->inventory.u.items_inventory[index] );
+			// Decrease refine level
+			sd->inventory.u.items_inventory[index].refine = cap_value( sd->inventory.u.items_inventory[index].refine - option->downgrade_amount, 0, MAX_REFINE );
+			// Log retrieving the item again -> with the new refine
+			log_pick_pc( sd, LOG_TYPE_ENCHANTGRADE, 1, &sd->inventory.u.items_inventory[index] );
+			// Show downgrade
+			clif_enchantgrade_result( *sd, index, ENCHANTGRADE_UPGRADE_DOWNGRADE );
+		// Only show failure, but dont do anything
+		}else{
+			clif_enchantgrade_result( *sd, index, ENCHANTGRADE_UPGRADE_FAILED );
+		}
+	}
+#endif
+}
+
+void clif_parse_enchantgrade_close( int fd, struct map_session_data* sd ){
+#if PACKETVER_MAIN_NUM >= 20200916 || PACKETVER_RE_NUM >= 20200724
+	nullpo_retv( sd );
+
+	sd->state.enchantgrade_open = false;
+#endif
+}
+
 /*==========================================
  * Main client packet processing function
  *------------------------------------------*/

+ 3 - 2
src/map/clif.hpp

@@ -1162,10 +1162,11 @@ enum out_ui_type : int8 {
 	OUT_UI_BANK = 0,
 	OUT_UI_STYLIST,
 	OUT_UI_QUEST = 6,
-	OUT_UI_ATTENDANCE = 7
+	OUT_UI_ATTENDANCE,
+	OUT_UI_ENCHANTGRADE,
 };
 
-void clif_ui_open( struct map_session_data *sd, enum out_ui_type ui_type, int32 data );
+void clif_ui_open( struct map_session_data& sd, enum out_ui_type ui_type, int32 data );
 void clif_attendence_response( struct map_session_data *sd, int32 data );
 
 void clif_weight_limit( struct map_session_data* sd );

+ 3 - 1
src/map/clif_packetdb.hpp

@@ -2375,7 +2375,6 @@
 // 2018-03-07bRagexeRE
 #if PACKETVER >= 20180307
 	parseable_packet(0x0A68,3,clif_parse_open_ui,2);
-	packet(0x0AE2,7);
 	parseable_packet(0x0AEF,2,clif_parse_attendance_request,0);
 	packet(0x0AF0,10);
 #endif
@@ -2447,6 +2446,9 @@
 
 #if PACKETVER_MAIN_NUM >= 20200916 || PACKETVER_RE_NUM >= 20200724
 	parseable_packet( HEADER_CZ_UNCONFIRMED_TSTATUS_UP, sizeof( PACKET_CZ_UNCONFIRMED_TSTATUS_UP ), clif_parse_traitstatus_up, 0 );
+	parseable_packet( HEADER_CZ_GRADE_ENCHANT_SELECT_EQUIPMENT, sizeof( struct PACKET_CZ_GRADE_ENCHANT_SELECT_EQUIPMENT ), clif_parse_enchantgrade_add, 0 );
+	parseable_packet( HEADER_CZ_GRADE_ENCHANT_REQUEST, sizeof( struct PACKET_CZ_GRADE_ENCHANT_REQUEST ), clif_parse_enchantgrade_start, 0 );
+	parseable_packet( HEADER_CZ_GRADE_ENCHANT_CLOSE_UI, sizeof( struct PACKET_CZ_GRADE_ENCHANT_CLOSE_UI ), clif_parse_enchantgrade_close, 0 );
 #endif
 
 #if PACKETVER_RE_NUM >= 20211103 || PACKETVER_ZERO_NUM >= 20210818

+ 4 - 1
src/map/log.cpp

@@ -86,7 +86,10 @@ static char log_picktype2char(e_log_pick_type type)
 		case LOG_TYPE_QUEST:			return 'Q';  // (Q)uest Item
 		case LOG_TYPE_PRIVATE_AIRSHIP:	return 'H';  // Private Airs(H)ip
 		case LOG_TYPE_BARTER:			return 'J';  // Barter Shop
-		case LOG_TYPE_LAPHINE:			return 'W'; // Laphine UI
+		case LOG_TYPE_LAPHINE:			return 'W';  // Laphine UI
+		case LOG_TYPE_ENCHANTGRADE:		return '0';  // Enchantgrade UI
+		case LOG_TYPE_REFORM:			return '1';  // Reform UI
+		case LOG_TYPE_ENCHANT:			return '2';  // Echant UI
 	}
 
 	// should not get here, fallback

+ 31 - 28
src/map/log.hpp

@@ -26,37 +26,40 @@ enum e_log_chat_type : uint8
 
 enum e_log_pick_type : uint32
 {
-	LOG_TYPE_NONE             = 0x0000000,
-	LOG_TYPE_TRADE            = 0x0000001,
-	LOG_TYPE_VENDING          = 0x0000002,
-	LOG_TYPE_PICKDROP_PLAYER  = 0x0000004,
-	LOG_TYPE_PICKDROP_MONSTER = 0x0000008,
-	LOG_TYPE_NPC              = 0x0000010,
-	LOG_TYPE_SCRIPT           = 0x0000020,
-	LOG_TYPE_STEAL            = 0x0000040,
-	LOG_TYPE_CONSUME          = 0x0000080,
-	LOG_TYPE_PRODUCE          = 0x0000100,
-	LOG_TYPE_MVP              = 0x0000200,
-	LOG_TYPE_COMMAND          = 0x0000400,
-	LOG_TYPE_STORAGE          = 0x0000800,
-	LOG_TYPE_GSTORAGE         = 0x0001000,
-	LOG_TYPE_MAIL             = 0x0002000,
-	LOG_TYPE_AUCTION          = 0x0004000,
-	LOG_TYPE_BUYING_STORE     = 0x0008000,
-	LOG_TYPE_OTHER            = 0x0010000,
-	LOG_TYPE_CASH             = 0x0020000,
-	LOG_TYPE_BANK             = 0x0040000,
-	LOG_TYPE_BOUND_REMOVAL    = 0x0080000,
-	LOG_TYPE_ROULETTE         = 0x0100000,
-	LOG_TYPE_MERGE_ITEM       = 0x0200000,
-	LOG_TYPE_QUEST            = 0x0400000,
-	LOG_TYPE_PRIVATE_AIRSHIP  = 0x0800000,
-	LOG_TYPE_BARTER           = 0x1000000,
-	LOG_TYPE_LAPHINE          = 0x2000000,
+	LOG_TYPE_NONE             = 0x00000000,
+	LOG_TYPE_TRADE            = 0x00000001,
+	LOG_TYPE_VENDING          = 0x00000002,
+	LOG_TYPE_PICKDROP_PLAYER  = 0x00000004,
+	LOG_TYPE_PICKDROP_MONSTER = 0x00000008,
+	LOG_TYPE_NPC              = 0x00000010,
+	LOG_TYPE_SCRIPT           = 0x00000020,
+	LOG_TYPE_STEAL            = 0x00000040,
+	LOG_TYPE_CONSUME          = 0x00000080,
+	LOG_TYPE_PRODUCE          = 0x00000100,
+	LOG_TYPE_MVP              = 0x00000200,
+	LOG_TYPE_COMMAND          = 0x00000400,
+	LOG_TYPE_STORAGE          = 0x00000800,
+	LOG_TYPE_GSTORAGE         = 0x00001000,
+	LOG_TYPE_MAIL             = 0x00002000,
+	LOG_TYPE_AUCTION          = 0x00004000,
+	LOG_TYPE_BUYING_STORE     = 0x00008000,
+	LOG_TYPE_OTHER            = 0x00010000,
+	LOG_TYPE_CASH             = 0x00020000,
+	LOG_TYPE_BANK             = 0x00040000,
+	LOG_TYPE_BOUND_REMOVAL    = 0x00080000,
+	LOG_TYPE_ROULETTE         = 0x00100000,
+	LOG_TYPE_MERGE_ITEM       = 0x00200000,
+	LOG_TYPE_QUEST            = 0x00400000,
+	LOG_TYPE_PRIVATE_AIRSHIP  = 0x00800000,
+	LOG_TYPE_BARTER           = 0x01000000,
+	LOG_TYPE_LAPHINE          = 0x02000000,
+	LOG_TYPE_ENCHANTGRADE     = 0x04000000,
+	LOG_TYPE_REFORM           = 0x08000000,
+	LOG_TYPE_ENCHANT          = 0x10000000,
 	// combinations
 	LOG_TYPE_LOOT             = LOG_TYPE_PICKDROP_MONSTER|LOG_TYPE_CONSUME,
 	// all
-	LOG_TYPE_ALL              = 0xFFFFFFF,
+	LOG_TYPE_ALL              = 0xFFFFFFFF,
 };
 
 enum e_log_cash_type : uint8

+ 1 - 0
src/map/map-server.vcxproj

@@ -322,6 +322,7 @@
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\const.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\const.yml')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\create_arrow_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\create_arrow_db.yml')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\elemental_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\elemental_db.yml')" />
+    <Copy SourceFiles="$(SolutionDir)db\import-tmpl\enchantgrade.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\enchantgrade.yml')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\exp_homun.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\exp_homun.yml')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\exp_guild.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\exp_guild.yml')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\guild_skill_tree.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\guild_skill_tree.yml')" />

+ 2 - 2
src/map/packets_struct.hpp

@@ -5340,7 +5340,7 @@ DEFINE_PACKET_HEADER(CZ_GRADE_ENCHANT_CLOSE_UI, 0x0b5c);
 struct PACKET_ZC_GRADE_ENCHANT_ACK {
 	int16 PacketType;
 	int16 index;
-	int16 grade;
+	int16 enchantgrade;
 	int result;
 } __attribute__((packed));
 DEFINE_PACKET_HEADER(ZC_GRADE_ENCHANT_ACK, 0x0b5d);
@@ -5351,7 +5351,7 @@ struct PACKET_ZC_GRADE_ENCHANT_BROADCAST_RESULT {
 	int16 packetType;
 	char name[NAME_LENGTH];
 	uint32 itemId;
-	int16 grade;
+	int16 enchantgrade;
 	int8 status;
 } __attribute__((packed));
 DEFINE_PACKET_HEADER(ZC_GRADE_ENCHANT_BROADCAST_RESULT, 0x0b5e);

+ 1 - 1
src/map/pc.cpp

@@ -13995,7 +13995,7 @@ void pc_scdata_received(struct map_session_data *sd) {
 	clif_weight_limit( sd );
 
 	if( pc_has_permission( sd, PC_PERM_ATTENDANCE ) && pc_attendance_enabled() && !pc_attendance_rewarded_today( sd ) ){
-		clif_ui_open( sd, OUT_UI_ATTENDANCE, pc_attendance_counter( sd ) );
+		clif_ui_open( *sd, OUT_UI_ATTENDANCE, pc_attendance_counter( sd ) );
 	}
 
 	sd->state.pc_loaded = true;

+ 2 - 1
src/map/pc.hpp

@@ -387,6 +387,7 @@ struct map_session_data {
 		bool stylist_open;
 		bool barter_open;
 		bool barter_extended_open;
+		bool enchantgrade_open; // Whether the enchantgrade window is open or not
 		unsigned int block_action : 10;
 		bool refineui_open;
 		t_itemid inventory_expansion_confirmation;
@@ -1067,7 +1068,7 @@ static bool pc_cant_act2( struct map_session_data* sd ){
 		|| sd->state.stylist_open || sd->state.inventory_expansion_confirmation || sd->npc_shopid
 		|| sd->state.barter_open || sd->state.barter_extended_open
 		|| sd->state.laphine_synthesis || sd->state.laphine_upgrade
-		|| sd->state.roulette_open;
+		|| sd->state.roulette_open || sd->state.enchantgrade_open;
 }
 // equals pc_cant_act2 and additionally checks for chat rooms and npcs
 static bool pc_cant_act( struct map_session_data* sd ){

+ 29 - 5
src/map/script.cpp

@@ -25739,7 +25739,7 @@ BUILDIN_FUNC( openstylist ){
 		return SCRIPT_CMD_FAILURE;
 	}
 
-	clif_ui_open( sd, OUT_UI_STYLIST, 0 );
+	clif_ui_open( *sd, OUT_UI_STYLIST, 0 );
 
 	return SCRIPT_CMD_SUCCESS;
 #else
@@ -25860,6 +25860,10 @@ BUILDIN_FUNC(randomoptgroup)
 }
 
 BUILDIN_FUNC( open_quest_ui ){
+#if PACKETVER < 20151202
+	ShowError( "buildin_open_quest_ui: This command requires PACKETVER 20151202 or newer.\n" );
+	return SCRIPT_CMD_FAILURE;
+#else
 	struct map_session_data* sd;
 
 	if (!script_charid2sd(3, sd))
@@ -25874,14 +25878,15 @@ BUILDIN_FUNC( open_quest_ui ){
 			ShowWarning("buildin_open_quest_ui: Character %d doesn't have quest %d.\n", sd->status.char_id, quest_id);
 	}
 
-	clif_ui_open( sd, OUT_UI_QUEST, quest_id );
+	clif_ui_open( *sd, OUT_UI_QUEST, quest_id );
 
 	return SCRIPT_CMD_SUCCESS;
+#endif
 }
 
 BUILDIN_FUNC(openbank){
-#if PACKETVER < 20150128
-	ShowError( "buildin_openbank: This command requires PACKETVER 20150128 or newer.\n" );
+#if PACKETVER < 20151202
+	ShowError( "buildin_openbank: This command requires PACKETVER 20151202 or newer.\n" );
 	return SCRIPT_CMD_FAILURE;
 #else
 	struct map_session_data* sd = nullptr;
@@ -25895,7 +25900,7 @@ BUILDIN_FUNC(openbank){
 		return SCRIPT_CMD_FAILURE;
 	}
 
-	clif_ui_open( sd, OUT_UI_BANK, 0 );
+	clif_ui_open( *sd, OUT_UI_BANK, 0 );
 	return SCRIPT_CMD_SUCCESS;
 #endif
 }
@@ -25994,6 +25999,23 @@ BUILDIN_FUNC(getjobexp_ratio){
 	return SCRIPT_CMD_SUCCESS;
 }
 
+BUILDIN_FUNC( enchantgradeui ){
+#if PACKETVER_MAIN_NUM >= 20200916 || PACKETVER_RE_NUM >= 20200724
+	struct map_session_data* sd;
+
+	if( !script_charid2sd( 2, sd ) ){
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	clif_ui_open( *sd, OUT_UI_ENCHANTGRADE, 0 );
+
+	return SCRIPT_CMD_SUCCESS;
+#else
+	ShowError( "buildin_enchantgradeui: This command requires PACKETVER 2020-07-24 or newer.\n" );
+	return SCRIPT_CMD_FAILURE;
+#endif
+}
+
 #include "../custom/script.inc"
 
 // declarations that were supposed to be exported from npc_chat.cpp
@@ -26713,6 +26735,8 @@ struct script_function buildin_func[] = {
 	BUILDIN_DEF(openbank,"?"),
 	BUILDIN_DEF(getbaseexp_ratio, "i??"),
 	BUILDIN_DEF(getjobexp_ratio, "i??"),
+	BUILDIN_DEF(enchantgradeui, "?" ),
+
 #include "../custom/script_def.inc"
 
 	{NULL,NULL,NULL},

+ 8 - 0
src/map/script_constants.hpp

@@ -8996,6 +8996,14 @@
 	export_constant(SCF_REMOVEONUNEQUIPWEAPON);
 	export_constant(SCF_REMOVEONUNEQUIPARMOR);
 
+	/* enchantgrades */
+	export_constant(ENCHANTGRADE_NONE);
+	export_constant(ENCHANTGRADE_D);
+	export_constant(ENCHANTGRADE_C);
+	export_constant(ENCHANTGRADE_B);
+	export_constant(ENCHANTGRADE_A);
+	export_constant(MAX_ENCHANTGRADE);
+
 	#undef export_constant
 	#undef export_constant2
 	#undef export_parameter

+ 409 - 2
src/map/status.cpp

@@ -544,6 +544,399 @@ uint64 SizeFixDatabase::parseBodyNode(const ryml::NodeRef& node) {
 
 SizeFixDatabase size_fix_db;
 
+const std::string EnchantgradeDatabase::getDefaultLocation(){
+	return std::string(db_path) + "/enchantgrade.yml";
+}
+
+uint64 EnchantgradeDatabase::parseBodyNode( const ryml::NodeRef& node ){
+	if( !this->nodesExist( node, { "Type", "Levels" } ) ){
+		return 0;
+	}
+
+	std::string itemtype_constant;
+
+	if( !this->asString( node, "Type", itemtype_constant ) ){
+		return 0;
+	}
+
+	int64 constant_value;
+
+	if( !script_get_constant( ( "IT_" + itemtype_constant ).c_str(), &constant_value ) ){
+		this->invalidWarning( node["Type"], "Unknown item type \"%s\".\n", itemtype_constant.c_str() );
+		return 0;
+	}
+
+	uint16 itemtype = static_cast<uint16>( constant_value );
+	uint16 itemtype_maxlevel;
+
+	if( itemtype == IT_WEAPON ){
+		itemtype_maxlevel = MAX_WEAPON_LEVEL;
+	}else if( itemtype == IT_ARMOR ){
+		itemtype_maxlevel = MAX_ARMOR_LEVEL;
+	}else{
+		this->invalidWarning( node["Type"], "Item type \"%s\" is not supported.\n", itemtype_constant.c_str() );
+		return 0;
+	}
+
+	std::shared_ptr<s_enchantgrade> enchantgrade = this->find( itemtype );
+	bool exists = enchantgrade != nullptr;
+
+	if( !exists ){
+		enchantgrade = std::make_shared<s_enchantgrade>();
+		enchantgrade->itemtype = itemtype;
+	}
+
+	for( const ryml::NodeRef& levelNode : node["Levels"] ){
+		if( !this->nodesExist( levelNode, { "Level", "Grades" } ) ){
+			return 0;
+		}
+
+		uint16 level;
+
+		if( !this->asUInt16( levelNode, "Level", level ) ){
+			return 0;
+		}
+
+		if( level == 0 || level > itemtype_maxlevel ){
+			this->invalidWarning( levelNode["Level"], "Level %hu is invalid for item type %s[1~%hu].\n", level, itemtype_constant.c_str(), itemtype_maxlevel );
+			return 0;
+		}
+
+		std::map<e_enchantgrade, std::shared_ptr<s_enchantgradelevel>>& grades = enchantgrade->levels[level];
+
+		for( const ryml::NodeRef& gradeNode : levelNode["Grades"] ){
+			std::string gradeConstant;
+
+			if( !this->asString( gradeNode, "Grade", gradeConstant ) ){
+				return 0;
+			}
+
+			if( !script_get_constant( ( "ENCHANTGRADE_" + gradeConstant ).c_str(), &constant_value ) ){
+				this->invalidWarning( node["Grade"], "Unknown grade \"%s\".\n", gradeConstant.c_str() );
+				return 0;
+			}
+
+			if( constant_value >= MAX_ENCHANTGRADE ){
+				this->invalidWarning( gradeNode["Grade"], "Grade %" PRId64 " is too high. Maximum: %hu.\n", constant_value, MAX_ENCHANTGRADE - 1 );
+				return 0;
+			}
+
+			e_enchantgrade gradeLevel = (e_enchantgrade)constant_value;
+
+			std::shared_ptr<s_enchantgradelevel> grade = util::map_find( grades, gradeLevel );
+			bool gradeExists = grade != nullptr;
+
+			if( !gradeExists ){
+				grade = std::make_shared<s_enchantgradelevel>();
+				grade->grade = gradeLevel;
+
+				if( !this->nodesExist( gradeNode, { "Refine", "Chance", "Options" } ) ){
+					return 0;
+				}
+			}
+
+			if( this->nodeExists( gradeNode, "Refine" ) ){
+				uint16 refine;
+
+				if( !this->asUInt16( gradeNode, "Refine", refine ) ){
+					return 0;
+				}
+
+				if( refine > MAX_REFINE ){
+					this->invalidWarning( gradeNode["Refine"], "Refine %hu is too high, capping to %hu...\n", refine, MAX_REFINE );
+					refine = MAX_REFINE;
+				}
+
+				grade->refine = refine;
+			}
+
+			if( this->nodeExists( gradeNode, "Chance" ) ){
+				uint16 chance;
+
+				if( !this->asUInt16Rate( gradeNode, "Chance", chance ) ){
+					return 0;
+				}
+
+				grade->chance = chance;
+			}
+
+			if( this->nodeExists( gradeNode, "Bonus" ) ){
+				uint16 bonus;
+
+				if( !this->asUInt16( gradeNode, "Bonus", bonus ) ){
+					return 0;
+				}
+
+				grade->bonus = bonus;
+			}else{
+				if( !gradeExists ){
+					grade->bonus = 0;
+				}
+			}
+
+			if( this->nodeExists( gradeNode, "Announce" ) ){
+				bool announce;
+
+				if( !this->asBool( gradeNode, "Announce", announce ) ){
+					return 0;
+				}
+
+				grade->announce = announce;
+			}else{
+				if( !gradeExists ){
+					grade->announce = true;
+				}
+			}
+
+			if( this->nodeExists( gradeNode, "Catalyst") ){
+				const ryml::NodeRef& catalystNode = gradeNode["Catalyst"];
+
+				if( this->nodeExists( catalystNode, "Item" ) ){
+					std::string itemName;
+
+					if( !this->asString( catalystNode, "Item", itemName ) ){
+						return 0;
+					}
+
+					std::shared_ptr<item_data> id = item_db.search_aegisname( itemName.c_str() );
+
+					if( id == nullptr ){
+						this->invalidWarning( catalystNode["Item"], "Unknown item \"%s\".\n", itemName.c_str() );
+						return 0;
+					}
+
+					grade->catalyst.item = id->nameid;
+				}else{
+					if( !gradeExists ){
+						grade->catalyst.item = 0;
+					}
+				}
+
+				if( this->nodeExists( catalystNode, "AmountPerStep" ) ){
+					uint16 amountPerStep;
+
+					if( !this->asUInt16( catalystNode, "AmountPerStep", amountPerStep ) ){
+						return 0;
+					}
+
+					grade->catalyst.amountPerStep = amountPerStep;
+				}else{
+					if( !gradeExists ){
+						grade->catalyst.amountPerStep = 0;
+					}
+				}
+
+				if( this->nodeExists( catalystNode, "MaximumSteps" ) ){
+					uint16 maximumSteps;
+
+					if( !this->asUInt16( catalystNode, "MaximumSteps", maximumSteps ) ){
+						return 0;
+					}
+
+					grade->catalyst.maximumSteps = maximumSteps;
+				}else{
+					if( !gradeExists ){
+						grade->catalyst.maximumSteps = 0;
+					}
+				}
+
+				if( this->nodeExists( catalystNode, "ChanceIncrease" ) ){
+					uint16 chanceIncrease;
+
+					if( !this->asUInt16Rate( catalystNode, "ChanceIncrease", chanceIncrease ) ){
+						return 0;
+					}
+
+					grade->catalyst.chanceIncrease = chanceIncrease;
+				}else{
+					if( !gradeExists ){
+						grade->catalyst.chanceIncrease = 0;
+					}
+				}
+			}else{
+				if( !gradeExists ){
+					grade->catalyst.item = 0;
+					grade->catalyst.amountPerStep = 0;
+					grade->catalyst.maximumSteps = 0;
+					grade->catalyst.chanceIncrease = 0;
+				}
+			}
+
+			if( this->nodeExists( gradeNode, "Options" ) ){
+				for( const ryml::NodeRef& optionNode : gradeNode["Options"] ){
+					uint16 optionIndex;
+
+					if( !this->asUInt16( optionNode, "Option", optionIndex ) ){
+						return 0;
+					}
+
+					std::shared_ptr<s_enchantgradeoption> option = util::map_find( grade->options, optionIndex );
+					bool optionExists = option != nullptr;
+
+					if( !optionExists ){
+						option = std::make_shared<s_enchantgradeoption>();
+						option->id = optionIndex;
+					}
+
+					if( this->nodeExists( optionNode, "Amount" ) ){
+						uint16 amount;
+
+						if( !this->asUInt16( optionNode, "Amount", amount ) ){
+							return 0;
+						}
+
+						if( amount > MAX_AMOUNT ){
+							this->invalidWarning( optionNode["Amount"], "Amount %hu is too high, capping to %hu...\n", amount, MAX_AMOUNT );
+							amount = MAX_AMOUNT;
+						}
+
+						if( amount == 0 ){
+							if( grade->options.erase( optionIndex ) > 0 ){
+								continue;
+							}else{
+								this->invalidWarning( optionNode["Amount"], "Trying to remove invalid option %hu...\n", optionIndex );
+								return 0;
+							}
+						}
+
+						option->amount = amount;
+					}else{
+						if( !optionExists ){
+							option->amount = 1;
+						}
+					}
+
+					if( this->nodeExists( optionNode, "Item" ) ){
+						std::string itemName;
+
+						if( !this->asString( optionNode, "Item", itemName ) ){
+							return 0;
+						}
+
+						std::shared_ptr<item_data> id = item_db.search_aegisname( itemName.c_str() );
+
+						if( id == nullptr ){
+							this->invalidWarning( optionNode["Item"], "Unknown item \"%s\".\n", itemName.c_str() );
+							return 0;
+						}
+
+						option->item = id->nameid;
+					}else{
+						if( !optionExists ){
+							option->item = 0;
+						}
+					}
+
+					if( this->nodeExists( optionNode, "Zeny" ) ){
+						uint32 zeny;
+
+						if( !this->asUInt32( optionNode, "Zeny", zeny ) ){
+							return 0;
+						}
+
+						option->zeny = zeny;
+					}else{
+						if( !optionExists ){
+							option->zeny = 0;
+						}
+					}
+
+					if( this->nodeExists( optionNode, "BreakingRate" ) ){
+						uint16 breaking_rate;
+
+						if( !this->asUInt16Rate( optionNode, "BreakingRate", breaking_rate ) ){
+							return 0;
+						}
+
+						option->breaking_rate = breaking_rate;
+					}else{
+						if( !optionExists ){
+							option->breaking_rate = 0;
+						}
+					}
+
+					if( this->nodeExists( optionNode, "DowngradeAmount" ) ){
+						uint16 downgrade_amount;
+
+						if( !this->asUInt16( optionNode, "DowngradeAmount", downgrade_amount ) ){
+							return 0;
+						}
+
+						if( downgrade_amount > MAX_REFINE ){
+							this->invalidWarning( optionNode["DowngradeAmount"], "Downgrade amount %hu is invalid, skipping.\n", downgrade_amount );
+							return 0;
+						}
+
+						option->downgrade_amount = downgrade_amount;
+					}else{
+						if( !optionExists ){
+							option->downgrade_amount = 0;
+						}
+					}
+
+					if( !optionExists ){
+						grade->options[optionIndex] = option;
+					}
+				}
+			}
+
+			if( !gradeExists ){
+				grades[gradeLevel] = grade;
+			}
+		}
+	}
+
+	if( !exists ){
+		this->put( itemtype, enchantgrade );
+	}
+
+	return 1;
+}
+
+std::shared_ptr<s_enchantgradelevel> EnchantgradeDatabase::findCurrentLevelInfo( const struct item_data& data, struct item& item ){
+	std::shared_ptr<s_enchantgrade> enchantgrade = enchantgrade_db.find( data.type );
+
+	// Unsupported item type - no answer
+	if( enchantgrade == nullptr ){
+		return nullptr;
+	}
+
+	uint16 level = 0;
+
+	if( data.type == IT_WEAPON ){
+		level = data.weapon_level;
+	}else if( data.type == IT_ARMOR ){
+		level = data.armor_level;
+	}
+
+	const auto& enchantgradelevels = enchantgrade->levels.find( level );
+
+	// Cannot upgrade this weapon or armor level - no answer
+	if( enchantgradelevels == enchantgrade->levels.end() ){
+		return nullptr;
+	}
+
+	return util::map_find( enchantgradelevels->second, (e_enchantgrade)( item.enchantgrade - 1 ) );
+}
+
+void EnchantgradeDatabase::loadingFinished(){
+	for( const auto& it_itemTypes : *this ){
+		for( const auto& it_itemLevels : it_itemTypes.second->levels ){
+			for( const auto& it_enchantgrades : it_itemLevels.second ){
+				std::shared_ptr<s_enchantgradelevel> enchantgradelevel = it_enchantgrades.second;
+
+				if( enchantgradelevel->catalyst.amountPerStep == 0 ){
+					enchantgradelevel->catalyst.item = 0;
+					enchantgradelevel->catalyst.chanceIncrease = 0;
+					enchantgradelevel->catalyst.maximumSteps = 0;
+				}
+			}
+		}
+	}
+}
+
+EnchantgradeDatabase enchantgrade_db;
+
 /**
  * Get icon ID of SC
  * @param type: SC type
@@ -3256,6 +3649,13 @@ int status_calc_pc_sub(struct map_session_data* sd, uint8 opt)
 			sd->inventory.u.items_inventory[index].refine = MAX_REFINE;
 
 		std::shared_ptr<s_refine_level_info> info = refine_db.findCurrentLevelInfo( *sd->inventory_data[index], sd->inventory.u.items_inventory[index] );
+#ifdef RENEWAL
+		std::shared_ptr<s_enchantgradelevel> enchantgrade_info = nullptr;
+
+		if( sd->inventory.u.items_inventory[index].enchantgrade > 0 ){
+			enchantgrade_info = enchantgrade_db.findCurrentLevelInfo( *sd->inventory_data[index], sd->inventory.u.items_inventory[index] );
+		}
+#endif
 
 		if (sd->inventory_data[index]->type == IT_WEAPON) {
 			int wlv = sd->inventory_data[index]->weapon_level;
@@ -3277,7 +3677,9 @@ int status_calc_pc_sub(struct map_session_data* sd, uint8 opt)
 				wa->atk2 += info->bonus / 100;
 
 #ifdef RENEWAL
-				// TODO: additional grade bonus
+				if( enchantgrade_info != nullptr ){
+					wa->atk2 += ( ( ( info->bonus / 100 ) * enchantgrade_info->bonus ) / 100 );
+				}
 
 				if( wlv == 5 ){
 					base_status->patk += sd->inventory.u.items_inventory[index].refine * 2;
@@ -3294,7 +3696,9 @@ int status_calc_pc_sub(struct map_session_data* sd, uint8 opt)
 			if( info != nullptr && sd->weapontype1 != W_BOW ){
 				wa->matk += info->bonus / 100;
 
-				// TODO: additional grade bonus
+				if( enchantgrade_info != nullptr ){
+					wa->matk += ( ( ( info->bonus / 100 ) * enchantgrade_info->bonus ) / 100 );
+				}
 			}
 #endif
 			// Overrefine bonus.
@@ -15184,10 +15588,12 @@ void status_readdb( bool reload ){
 		size_fix_db.reload();
 		refine_db.reload();
 		status_db.reload();
+		enchantgrade_db.reload();
 	}else{
 		size_fix_db.load();
 		refine_db.load();
 		status_db.load();
+		enchantgrade_db.load();
 	}
 	elemental_attribute_db.load();
 }
@@ -15211,6 +15617,7 @@ void do_init_status(void) {
 /** Destroy status data */
 void do_final_status(void) {
 	ers_destroy(sc_data_ers);
+	enchantgrade_db.clear();
 	size_fix_db.clear();
 	refine_db.clear();
 	status_db.clear();

+ 53 - 0
src/map/status.hpp

@@ -144,6 +144,59 @@ public:
 
 extern AttributeDatabase elemental_attribute_db;
 
+enum e_enchantgrade_result{
+	ENCHANTGRADE_UPGRADE_SUCCESS,
+	ENCHANTGRADE_UPGRADE_FAILED,
+	ENCHANTGRADE_UPGRADE_DOWNGRADE,
+	ENCHANTGRADE_UPGRADE_BREAK,
+	ENCHANTGRADE_UPGRADE_PROTECTED,
+};
+
+struct s_enchantgradeoption{
+	uint16 id;
+	t_itemid item;
+	uint16 amount;
+	uint32 zeny;
+	uint16 breaking_rate;
+	uint16 downgrade_amount;
+};
+
+struct s_enchantgradelevel{
+	e_enchantgrade grade;
+	uint16 refine;
+	uint16 chance;
+	uint16 bonus;
+	bool announce;
+	struct{
+		t_itemid item;
+		uint16 amountPerStep;
+		uint16 maximumSteps;
+		uint16 chanceIncrease;
+	}catalyst;
+	std::map<uint16,std::shared_ptr<s_enchantgradeoption>> options;
+};
+
+struct s_enchantgrade{
+	uint16 itemtype;
+	std::map<uint16,std::map<e_enchantgrade,std::shared_ptr<s_enchantgradelevel>>> levels;
+};
+
+class EnchantgradeDatabase : public TypesafeYamlDatabase<uint16, s_enchantgrade>{
+public:
+	EnchantgradeDatabase() : TypesafeYamlDatabase( "ENCHANTGRADE_DB", 1 ){
+
+	}
+
+	const std::string getDefaultLocation() override;
+	uint64 parseBodyNode( const ryml::NodeRef& node ) override;
+	void loadingFinished() override;
+
+	// Additional
+	std::shared_ptr<s_enchantgradelevel> findCurrentLevelInfo( const struct item_data& data, struct item& item );
+};
+
+extern EnchantgradeDatabase enchantgrade_db;
+
 /// Status changes listing. These code are for use by the server.
 enum sc_type : int16 {
 	SC_NONE = -1,