From 1a8a18fa87f29cd42a5963937a2f60e61133794e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Sun, 29 Mar 2026 14:35:30 -0300 Subject: [PATCH 1/2] Fix enchant scroll applying single-level stats instead of cumulative EnchantScrollHandler.HandleEnchant only called GetEnchant for the target level, giving e.g. only level 11's bonus instead of the sum of levels 1 through 11. HandlePreview also computed deltas from current enchant level instead of absolute cumulative values, showing wrong stats when using a scroll on an already-enchanted item. Both now use GetCumulativeEnchant which sums per-level rates from 1 through the target level, matching manual Ophelia/Peachy enchanting. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../PacketHandlers/EnchantScrollHandler.cs | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/Maple2.Server.Game/PacketHandlers/EnchantScrollHandler.cs b/Maple2.Server.Game/PacketHandlers/EnchantScrollHandler.cs index 5ceb682f8..fb673ef46 100644 --- a/Maple2.Server.Game/PacketHandlers/EnchantScrollHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/EnchantScrollHandler.cs @@ -62,31 +62,16 @@ private void HandlePreview(GameSession session, IByteReader packet) { return; } - int minEnchant = Math.Min(item.Enchant?.Enchants ?? 0, metadata.Enchants.Min()); - int maxEnchant = metadata.Enchants.Max(); - Dictionary minOptions = []; - Dictionary maxOptions = []; - for (int i = minEnchant; i < maxEnchant; i++) { - // add to dictionaries - int targetEnchant = i - 1; - ItemEnchant result = ItemEnchantManager.GetEnchant(session, item, i + 1); - foreach ((BasicAttribute attribute, BasicOption option) in result.BasicOptions) { - if (minOptions.TryGetValue(attribute, out BasicOption currentOption)) { - minOptions[attribute] = currentOption + option; - } else { - minOptions[attribute] = option; - } - } - - result = ItemEnchantManager.GetEnchant(session, item, i - 1); - foreach ((BasicAttribute attribute, BasicOption option) in result.BasicOptions) { - if (maxOptions.TryGetValue(attribute, out BasicOption currentOption)) { - maxOptions[attribute] = currentOption + option; - } else { - maxOptions[attribute] = option; - } - } + Dictionary minOptions; + Dictionary maxOptions; + if (metadata.Type == EnchantScrollType.Random) { + minOptions = GetCumulativeEnchant(session, item, metadata.Enchants.Min()).BasicOptions; + maxOptions = GetCumulativeEnchant(session, item, metadata.Enchants.Max()).BasicOptions; + } else { + minOptions = GetCumulativeEnchant(session, item, metadata.Enchants.Max()).BasicOptions; + maxOptions = minOptions; } + session.Send(EnchantScrollPacket.Preview(item, metadata.Type, minOptions, maxOptions)); } @@ -122,8 +107,7 @@ private void HandleEnchant(GameSession session, IByteReader packet) { // Ensure that you cannot randomize an enchant lower than current item. item.Enchant ??= new ItemEnchant(); if (enchantLevel > item.Enchant.Enchants) { - item.Enchant = ItemEnchantManager.GetEnchant(session, item, enchantLevel); - item.Enchant.Enchants = enchantLevel; + item.Enchant = GetCumulativeEnchant(session, item, enchantLevel); } session.Send(EnchantScrollPacket.Enchant(item)); @@ -151,6 +135,22 @@ private static EnchantScrollError IsCompatibleScroll(Item item, EnchantScrollMet return s_enchantscroll_ok; } + private static ItemEnchant GetCumulativeEnchant(GameSession session, Item item, int targetLevel) { + var cumulative = new ItemEnchant(); + for (int i = 1; i <= targetLevel; i++) { + ItemEnchant levelEnchant = ItemEnchantManager.GetEnchant(session, item, i); + foreach ((BasicAttribute attribute, BasicOption option) in levelEnchant.BasicOptions) { + if (cumulative.BasicOptions.TryGetValue(attribute, out BasicOption existing)) { + cumulative.BasicOptions[attribute] = existing + option; + } else { + cumulative.BasicOptions[attribute] = option; + } + } + } + cumulative.Enchants = targetLevel; + return cumulative; + } + private bool TryGetMetadata(GameSession session, long scrollUid, [NotNullWhen(true)] out EnchantScrollMetadata? metadata) { Item? scroll = session.Item.Inventory.Get(scrollUid); if (scroll == null) { From 6a9fb6e16bdb2eae565961bcb76a8e844cf94b36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Sun, 29 Mar 2026 15:00:29 -0300 Subject: [PATCH 2/2] fix: enchant scroll preview floor and in-place enchant update Clamp preview minimum to item's current enchant level (Math.Max) since enchant scrolls never decrease enchant level. Update enchant properties in-place instead of replacing the ItemEnchant object to preserve persisted fields (EnchantExp, EnchantCharges, Charges, Tradeable). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../PacketHandlers/EnchantScrollHandler.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Maple2.Server.Game/PacketHandlers/EnchantScrollHandler.cs b/Maple2.Server.Game/PacketHandlers/EnchantScrollHandler.cs index fb673ef46..a8d723a58 100644 --- a/Maple2.Server.Game/PacketHandlers/EnchantScrollHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/EnchantScrollHandler.cs @@ -65,7 +65,8 @@ private void HandlePreview(GameSession session, IByteReader packet) { Dictionary minOptions; Dictionary maxOptions; if (metadata.Type == EnchantScrollType.Random) { - minOptions = GetCumulativeEnchant(session, item, metadata.Enchants.Min()).BasicOptions; + int minRoll = Math.Max(metadata.Enchants.Min(), item.Enchant?.Enchants ?? 0); + minOptions = GetCumulativeEnchant(session, item, minRoll).BasicOptions; maxOptions = GetCumulativeEnchant(session, item, metadata.Enchants.Max()).BasicOptions; } else { minOptions = GetCumulativeEnchant(session, item, metadata.Enchants.Max()).BasicOptions; @@ -107,7 +108,12 @@ private void HandleEnchant(GameSession session, IByteReader packet) { // Ensure that you cannot randomize an enchant lower than current item. item.Enchant ??= new ItemEnchant(); if (enchantLevel > item.Enchant.Enchants) { - item.Enchant = GetCumulativeEnchant(session, item, enchantLevel); + ItemEnchant computed = GetCumulativeEnchant(session, item, enchantLevel); + item.Enchant.Enchants = computed.Enchants; + item.Enchant.BasicOptions.Clear(); + foreach ((BasicAttribute attribute, BasicOption option) in computed.BasicOptions) { + item.Enchant.BasicOptions[attribute] = option; + } } session.Send(EnchantScrollPacket.Enchant(item));