Browse Source

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 years ago
parent
commit
001981cf66

+ 3 - 0
conf/atcommands.yml

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

+ 31 - 28
conf/log_athena.conf

@@ -8,37 +8,40 @@
 //--------------------------------------------------------------
 //--------------------------------------------------------------
 
 
 // Enable Logs? (Note 3)
 // 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)
 //           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
 // 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.
 // 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)
 // Use MySQL Logs? (Note 1)
 sql_logs: yes
 sql_logs: yes

+ 4 - 1
conf/msg_conf/map_msg.conf

@@ -921,7 +921,10 @@
 // @mobinfo RES/MRES
 // @mobinfo RES/MRES
 827:  RES:%d  MRES:%d
 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
 // 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
     AegisName: Amber
     Name: Amber
     Name: Amber
     Type: Etc
     Type: Etc
+    Buy: 45000
     Weight: 100
     Weight: 100
   - Id: 1000322
   - Id: 1000322
     AegisName: Etel_Dust
     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 |
 | 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.
 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.
 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.
 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_16_2.yml
   - Path: npc/re/merchants/barters/quests_17_1.yml
   - Path: npc/re/merchants/barters/quests_17_1.yml
   - Path: npc/re/merchants/barters/refine.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,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,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,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,34,68,0	shop	Trader#moc7	93,748:-1
 morocc,269,193,4	shop	Trader#moc8	89,2609:-1,1516:-1,1522:-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
 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_mora.txt
 npc: npc/re/merchants/enchan_rockridge.txt
 npc: npc/re/merchants/enchan_rockridge.txt
 npc: npc/re/merchants/enchan_verus.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_Ammunition.txt
 npc: npc/re/merchants/Extended_Stylist.txt
 npc: npc/re/merchants/Extended_Stylist.txt
 npc: npc/re/merchants/flute.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
 # Private Airs(H)ip
 # Barter Shop (J)
 # Barter Shop (J)
 # Laphine systems (W)
 # Laphine systems (W)
+# Enchantgrade UI (0)
+# Reform UI (1)
+# Enchant UI (2)
 
 
 CREATE TABLE IF NOT EXISTS `picklog` (
 CREATE TABLE IF NOT EXISTS `picklog` (
   `id` int(11) NOT NULL auto_increment,
   `id` int(11) NOT NULL auto_increment,
   `time` datetime NOT NULL,
   `time` datetime NOT NULL,
   `char_id` int(11) NOT NULL default '0',
   `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',
   `nameid` int(10) unsigned NOT NULL default '0',
   `amount` int(11) NOT NULL default '1',
   `amount` int(11) NOT NULL default '1',
   `refine` tinyint(3) unsigned NOT NULL default '0',
   `refine` tinyint(3) unsigned NOT NULL default '0',
@@ -224,13 +227,15 @@ CREATE TABLE IF NOT EXISTS `picklog` (
 # Ban(K) Transactions
 # Ban(K) Transactions
 # Barter Shop (J)
 # Barter Shop (J)
 # (X) Other
 # (X) Other
+# Enchantgrade UI (0)
+# Enchant UI (2)
 
 
 CREATE TABLE IF NOT EXISTS `zenylog` (
 CREATE TABLE IF NOT EXISTS `zenylog` (
   `id` int(11) NOT NULL auto_increment,
   `id` int(11) NOT NULL auto_increment,
   `time` datetime NOT NULL,
   `time` datetime NOT NULL,
   `char_id` int(11) NOT NULL default '0',
   `char_id` int(11) NOT NULL default '0',
   `src_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',
   `amount` int(11) NOT NULL default '0',
   `map` varchar(11) NOT NULL default '',
   `map` varchar(11) NOT NULL default '',
   PRIMARY KEY  (`id`),
   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
 	#define MAX_BARTER_REQUIREMENTS 5
 #endif
 #endif
 
 
+enum e_enchantgrade : uint16{
+	ENCHANTGRADE_NONE = 0,
+	ENCHANTGRADE_D,
+	ENCHANTGRADE_C,
+	ENCHANTGRADE_B,
+	ENCHANTGRADE_A
+};
+
 #ifdef RENEWAL
 #ifdef RENEWAL
 	#define MAX_WEAPON_LEVEL 5
 	#define MAX_WEAPON_LEVEL 5
 	#define MAX_ARMOR_LEVEL 2
 	#define MAX_ARMOR_LEVEL 2
+	#define MAX_ENCHANTGRADE ENCHANTGRADE_A
 #else
 #else
 	#define MAX_WEAPON_LEVEL 4
 	#define MAX_WEAPON_LEVEL 4
 	#define MAX_ARMOR_LEVEL 1
 	#define MAX_ARMOR_LEVEL 1
+	#define MAX_ENCHANTGRADE ENCHANTGRADE_NONE
 #endif
 #endif
 
 
 // for produce
 // for produce

+ 19 - 1
src/map/atcommand.cpp

@@ -10662,7 +10662,7 @@ ACMD_FUNC( stylist ){
 		return -1;
 		return -1;
 	}
 	}
 
 
-	clif_ui_open( sd, OUT_UI_STYLIST, 0 );
+	clif_ui_open( *sd, OUT_UI_STYLIST, 0 );
 	return 0;
 	return 0;
 #endif
 #endif
 }
 }
@@ -10697,6 +10697,23 @@ ACMD_FUNC(addfame)
 	return 0;
 	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"
 #include "../custom/atcommand.inc"
 
 
 /**
 /**
@@ -11019,6 +11036,7 @@ void atcommand_basecommands(void) {
 		ACMD_DEF(refineui),
 		ACMD_DEF(refineui),
 		ACMD_DEFR(stylist, ATCMD_NOCONSOLE|ATCMD_NOAUTOTRADE),
 		ACMD_DEFR(stylist, ATCMD_NOCONSOLE|ATCMD_NOAUTOTRADE),
 		ACMD_DEF(addfame),
 		ACMD_DEF(addfame),
+		ACMD_DEFR(enchantgradeui, ATCMD_NOCONSOLE|ATCMD_NOAUTOTRADE),
 	};
 	};
 	AtCommandInfo* atcommand;
 	AtCommandInfo* atcommand;
 	int i;
 	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
 /// Opens an UI window of the given type and initializes it with the given data
 /// 0AE2 <type>.B <data>.L
 /// 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
 	// If the UI requires state tracking
 	switch( ui_type ){
 	switch( ui_type ){
 		case OUT_UI_STYLIST:
 		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;
 			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
 /// 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 ) ){
 			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.
 				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() ){
 			}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{
 			}else{
 				clif_msg_color( sd, MSG_ATTENDANCE_DISABLED, color_table[COLOR_RED] );
 				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
 #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
  * 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_BANK = 0,
 	OUT_UI_STYLIST,
 	OUT_UI_STYLIST,
 	OUT_UI_QUEST = 6,
 	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_attendence_response( struct map_session_data *sd, int32 data );
 
 
 void clif_weight_limit( struct map_session_data* sd );
 void clif_weight_limit( struct map_session_data* sd );

+ 3 - 1
src/map/clif_packetdb.hpp

@@ -2375,7 +2375,6 @@
 // 2018-03-07bRagexeRE
 // 2018-03-07bRagexeRE
 #if PACKETVER >= 20180307
 #if PACKETVER >= 20180307
 	parseable_packet(0x0A68,3,clif_parse_open_ui,2);
 	parseable_packet(0x0A68,3,clif_parse_open_ui,2);
-	packet(0x0AE2,7);
 	parseable_packet(0x0AEF,2,clif_parse_attendance_request,0);
 	parseable_packet(0x0AEF,2,clif_parse_attendance_request,0);
 	packet(0x0AF0,10);
 	packet(0x0AF0,10);
 #endif
 #endif
@@ -2447,6 +2446,9 @@
 
 
 #if PACKETVER_MAIN_NUM >= 20200916 || PACKETVER_RE_NUM >= 20200724
 #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_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
 #endif
 
 
 #if PACKETVER_RE_NUM >= 20211103 || PACKETVER_ZERO_NUM >= 20210818
 #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_QUEST:			return 'Q';  // (Q)uest Item
 		case LOG_TYPE_PRIVATE_AIRSHIP:	return 'H';  // Private Airs(H)ip
 		case LOG_TYPE_PRIVATE_AIRSHIP:	return 'H';  // Private Airs(H)ip
 		case LOG_TYPE_BARTER:			return 'J';  // Barter Shop
 		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
 	// 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
 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
 	// combinations
 	LOG_TYPE_LOOT             = LOG_TYPE_PICKDROP_MONSTER|LOG_TYPE_CONSUME,
 	LOG_TYPE_LOOT             = LOG_TYPE_PICKDROP_MONSTER|LOG_TYPE_CONSUME,
 	// all
 	// all
-	LOG_TYPE_ALL              = 0xFFFFFFF,
+	LOG_TYPE_ALL              = 0xFFFFFFFF,
 };
 };
 
 
 enum e_log_cash_type : uint8
 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\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\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\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_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\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')" />
     <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 {
 struct PACKET_ZC_GRADE_ENCHANT_ACK {
 	int16 PacketType;
 	int16 PacketType;
 	int16 index;
 	int16 index;
-	int16 grade;
+	int16 enchantgrade;
 	int result;
 	int result;
 } __attribute__((packed));
 } __attribute__((packed));
 DEFINE_PACKET_HEADER(ZC_GRADE_ENCHANT_ACK, 0x0b5d);
 DEFINE_PACKET_HEADER(ZC_GRADE_ENCHANT_ACK, 0x0b5d);
@@ -5351,7 +5351,7 @@ struct PACKET_ZC_GRADE_ENCHANT_BROADCAST_RESULT {
 	int16 packetType;
 	int16 packetType;
 	char name[NAME_LENGTH];
 	char name[NAME_LENGTH];
 	uint32 itemId;
 	uint32 itemId;
-	int16 grade;
+	int16 enchantgrade;
 	int8 status;
 	int8 status;
 } __attribute__((packed));
 } __attribute__((packed));
 DEFINE_PACKET_HEADER(ZC_GRADE_ENCHANT_BROADCAST_RESULT, 0x0b5e);
 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 );
 	clif_weight_limit( sd );
 
 
 	if( pc_has_permission( sd, PC_PERM_ATTENDANCE ) && pc_attendance_enabled() && !pc_attendance_rewarded_today( 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;
 	sd->state.pc_loaded = true;

+ 2 - 1
src/map/pc.hpp

@@ -387,6 +387,7 @@ struct map_session_data {
 		bool stylist_open;
 		bool stylist_open;
 		bool barter_open;
 		bool barter_open;
 		bool barter_extended_open;
 		bool barter_extended_open;
+		bool enchantgrade_open; // Whether the enchantgrade window is open or not
 		unsigned int block_action : 10;
 		unsigned int block_action : 10;
 		bool refineui_open;
 		bool refineui_open;
 		t_itemid inventory_expansion_confirmation;
 		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.stylist_open || sd->state.inventory_expansion_confirmation || sd->npc_shopid
 		|| sd->state.barter_open || sd->state.barter_extended_open
 		|| sd->state.barter_open || sd->state.barter_extended_open
 		|| sd->state.laphine_synthesis || sd->state.laphine_upgrade
 		|| 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
 // equals pc_cant_act2 and additionally checks for chat rooms and npcs
 static bool pc_cant_act( struct map_session_data* sd ){
 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;
 		return SCRIPT_CMD_FAILURE;
 	}
 	}
 
 
-	clif_ui_open( sd, OUT_UI_STYLIST, 0 );
+	clif_ui_open( *sd, OUT_UI_STYLIST, 0 );
 
 
 	return SCRIPT_CMD_SUCCESS;
 	return SCRIPT_CMD_SUCCESS;
 #else
 #else
@@ -25860,6 +25860,10 @@ BUILDIN_FUNC(randomoptgroup)
 }
 }
 
 
 BUILDIN_FUNC( open_quest_ui ){
 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;
 	struct map_session_data* sd;
 
 
 	if (!script_charid2sd(3, 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);
 			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;
 	return SCRIPT_CMD_SUCCESS;
+#endif
 }
 }
 
 
 BUILDIN_FUNC(openbank){
 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;
 	return SCRIPT_CMD_FAILURE;
 #else
 #else
 	struct map_session_data* sd = nullptr;
 	struct map_session_data* sd = nullptr;
@@ -25895,7 +25900,7 @@ BUILDIN_FUNC(openbank){
 		return SCRIPT_CMD_FAILURE;
 		return SCRIPT_CMD_FAILURE;
 	}
 	}
 
 
-	clif_ui_open( sd, OUT_UI_BANK, 0 );
+	clif_ui_open( *sd, OUT_UI_BANK, 0 );
 	return SCRIPT_CMD_SUCCESS;
 	return SCRIPT_CMD_SUCCESS;
 #endif
 #endif
 }
 }
@@ -25994,6 +25999,23 @@ BUILDIN_FUNC(getjobexp_ratio){
 	return SCRIPT_CMD_SUCCESS;
 	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"
 #include "../custom/script.inc"
 
 
 // declarations that were supposed to be exported from npc_chat.cpp
 // 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(openbank,"?"),
 	BUILDIN_DEF(getbaseexp_ratio, "i??"),
 	BUILDIN_DEF(getbaseexp_ratio, "i??"),
 	BUILDIN_DEF(getjobexp_ratio, "i??"),
 	BUILDIN_DEF(getjobexp_ratio, "i??"),
+	BUILDIN_DEF(enchantgradeui, "?" ),
+
 #include "../custom/script_def.inc"
 #include "../custom/script_def.inc"
 
 
 	{NULL,NULL,NULL},
 	{NULL,NULL,NULL},

+ 8 - 0
src/map/script_constants.hpp

@@ -8996,6 +8996,14 @@
 	export_constant(SCF_REMOVEONUNEQUIPWEAPON);
 	export_constant(SCF_REMOVEONUNEQUIPWEAPON);
 	export_constant(SCF_REMOVEONUNEQUIPARMOR);
 	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_constant
 	#undef export_constant2
 	#undef export_constant2
 	#undef export_parameter
 	#undef export_parameter

+ 409 - 2
src/map/status.cpp

@@ -544,6 +544,399 @@ uint64 SizeFixDatabase::parseBodyNode(const ryml::NodeRef& node) {
 
 
 SizeFixDatabase size_fix_db;
 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
  * Get icon ID of SC
  * @param type: SC type
  * @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;
 			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] );
 		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) {
 		if (sd->inventory_data[index]->type == IT_WEAPON) {
 			int wlv = sd->inventory_data[index]->weapon_level;
 			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;
 				wa->atk2 += info->bonus / 100;
 
 
 #ifdef RENEWAL
 #ifdef RENEWAL
-				// TODO: additional grade bonus
+				if( enchantgrade_info != nullptr ){
+					wa->atk2 += ( ( ( info->bonus / 100 ) * enchantgrade_info->bonus ) / 100 );
+				}
 
 
 				if( wlv == 5 ){
 				if( wlv == 5 ){
 					base_status->patk += sd->inventory.u.items_inventory[index].refine * 2;
 					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 ){
 			if( info != nullptr && sd->weapontype1 != W_BOW ){
 				wa->matk += info->bonus / 100;
 				wa->matk += info->bonus / 100;
 
 
-				// TODO: additional grade bonus
+				if( enchantgrade_info != nullptr ){
+					wa->matk += ( ( ( info->bonus / 100 ) * enchantgrade_info->bonus ) / 100 );
+				}
 			}
 			}
 #endif
 #endif
 			// Overrefine bonus.
 			// Overrefine bonus.
@@ -15184,10 +15588,12 @@ void status_readdb( bool reload ){
 		size_fix_db.reload();
 		size_fix_db.reload();
 		refine_db.reload();
 		refine_db.reload();
 		status_db.reload();
 		status_db.reload();
+		enchantgrade_db.reload();
 	}else{
 	}else{
 		size_fix_db.load();
 		size_fix_db.load();
 		refine_db.load();
 		refine_db.load();
 		status_db.load();
 		status_db.load();
+		enchantgrade_db.load();
 	}
 	}
 	elemental_attribute_db.load();
 	elemental_attribute_db.load();
 }
 }
@@ -15211,6 +15617,7 @@ void do_init_status(void) {
 /** Destroy status data */
 /** Destroy status data */
 void do_final_status(void) {
 void do_final_status(void) {
 	ers_destroy(sc_data_ers);
 	ers_destroy(sc_data_ers);
+	enchantgrade_db.clear();
 	size_fix_db.clear();
 	size_fix_db.clear();
 	refine_db.clear();
 	refine_db.clear();
 	status_db.clear();
 	status_db.clear();

+ 53 - 0
src/map/status.hpp

@@ -144,6 +144,59 @@ public:
 
 
 extern AttributeDatabase elemental_attribute_db;
 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.
 /// Status changes listing. These code are for use by the server.
 enum sc_type : int16 {
 enum sc_type : int16 {
 	SC_NONE = -1,
 	SC_NONE = -1,