From a420bba4be36b10f305ab64c94f6cab981a96b79 Mon Sep 17 00:00:00 2001 From: Zheng Bian Date: Thu, 11 Sep 2025 02:21:43 +0800 Subject: [PATCH 1/2] fix(ChangeAttributes): Fix wrong bonus value gen by Value Re-roller of two-handed weapons # Changes ## `Maple2.Server.Game\PacketHandlers\ChangeAttributesScrollHandler.cs` - Modified the handling logic for the in-game item "Bonus Value Re-roller" (e.g., ID 31001985) to align with the structure of "Bonus Re-roller" - Implemented calls to the new processing interface `UpdateFixedOption` (as counterpart to `UpdateRandomOption`) ## `Maple2.Server.Game\Util\ItemStatsCalculator.cs` - Created the `UpdateFixedOption` function following a similar structure to `UpdateRandomOption` - The updated Option directly uses the Basic and Special values from the original Option, along with the `multiplyFactor` from the metadata's RandomTable # Reason for Changes Bug Symptom: When using the "Bonus Value Re-roller" item (e.g., ID 31001985), two-handed weapons would receive abnormal values that were lower than the minimum expected range. Root Cause: The original logic failed to transfer the `multiplyFactor` between old and new Options, causing two-handed weapons to use the default `multiplyFactor = 1` and generate new values within **the same random range as one-handed weapons**. Change 1: > ``` > Maple2.Server.Game\PacketHandlers\ChangeAttributesScrollHandler.cs > ``` Standardized the abstraction level by unifying attribute modification logic into the `ItemStatsCalculator.cs` Change 2: > ``` > Maple2.Server.Game\Util\ItemStatsCalculator.cs > ``` Correctly implemented the transfer of `multiplyFactor` between Options --- .../ChangeAttributesScrollHandler.cs | 30 +++++---------- .../Util/ItemStatsCalculator.cs | 38 +++++++++++++++++++ 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/Maple2.Server.Game/PacketHandlers/ChangeAttributesScrollHandler.cs b/Maple2.Server.Game/PacketHandlers/ChangeAttributesScrollHandler.cs index cd3fab69f..05094ff7c 100644 --- a/Maple2.Server.Game/PacketHandlers/ChangeAttributesScrollHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/ChangeAttributesScrollHandler.cs @@ -111,11 +111,6 @@ private void HandleChange(GameSession session, IByteReader packet) { return; } - if (!TableMetadata.ItemOptionRandomTable.Options.TryGetValue(changeItem.Metadata.Option.RandomId, changeItem.Rarity, out ItemOption? itemOptionMetadata)) { - session.Send(ChangeAttributesScrollPacket.Error(ChangeAttributesScrollError.s_itemremake_scroll_error_impossible_item)); - return; - } - if (itemRemakeScrollMetadata.RollAttribute) { // Randomize attributes. if (lockItem != null) { @@ -138,22 +133,17 @@ private void HandleChange(GameSession session, IByteReader packet) { } } } else { - ItemStats.Option changeOption = changeItem.Stats[ItemStats.Type.Random]; - - if (!ItemStatsCalc.RandomizeValues(changeItem, itemOptionMetadata, ref changeOption)) { - session.Send(ChangeAttributesScrollPacket.Error(ChangeAttributesScrollError.s_itemremake_scroll_error_server_fail_remake)); - return; - } - - // Restore locked attribute values. + // Fixed attributes. if (lockItem != null) { - ItemStats.Option option = item.Stats[ItemStats.Type.Random]; - if (isSpecialAttribute) { - var specialAttribute = (SpecialAttribute) attribute; - changeOption.Special[specialAttribute] = option.Special[specialAttribute]; - } else { - var basicAttribute = (BasicAttribute) attribute; - changeOption.Basic[basicAttribute] = option.Basic[basicAttribute]; + // Restore locked attribute values. + if (!ItemStatsCalc.UpdateFixedOption(ref changeItem, new LockOption((SpecialAttribute) attribute, true))) { + session.Send(ChangeAttributesPacket.Error(ChangeAttributesError.s_itemremake_error_server_default)); + return; + } + } else { + if (!ItemStatsCalc.UpdateFixedOption(ref changeItem)) { + session.Send(ChangeAttributesPacket.Error(ChangeAttributesError.s_itemremake_error_server_default)); + return; } } } diff --git a/Maple2.Server.Game/Util/ItemStatsCalculator.cs b/Maple2.Server.Game/Util/ItemStatsCalculator.cs index 263698a4f..ce5d1776f 100644 --- a/Maple2.Server.Game/Util/ItemStatsCalculator.cs +++ b/Maple2.Server.Game/Util/ItemStatsCalculator.cs @@ -184,6 +184,44 @@ public bool UpdateRandomOption(ref Item item, params LockOption[] presets) { return true; } + public bool UpdateFixedOption(ref Item item, params LockOption[] presets){ + if (item.Metadata.Option == null || item.Stats == null) { + return false; + } + + ItemStats.Option option = item.Stats[ItemStats.Type.Random]; + if (option.Count == 0) { + return false; + } + + // Get fixed options (with right MultiplyFactor) + if (!TableMetadata.ItemOptionRandomTable.Options.TryGetValue(item.Metadata.Option.RandomId, item.Rarity, out ItemOption? itemOption)) { + return false; + } + ItemStats.Option fixedOption = new ItemStats.Option(option.Basic, option.Special, multiplyFactor: itemOption.MultiplyFactor); + + if (!RandomizeValues(item, itemOption, ref fixedOption)) { + return false; + } + + // Restore locked values. + foreach (LockOption lockOption in presets) { + if (lockOption.TryGet(out BasicAttribute basic, out bool lockBasicValue)) { + if (lockBasicValue) { + fixedOption.Basic[basic] = option.Basic[basic]; + } + } else if (lockOption.TryGet(out SpecialAttribute special, out bool lockSpecialValue)) { + if (lockSpecialValue) { + fixedOption.Special[special] = option.Special[special]; + } + } + } + + // Update item with result. + item.Stats[ItemStats.Type.Random] = fixedOption; + return true; + } + /// Item /// Item's Random Option Metadata /// From 45f19f5ca02798404f6dc72e6d3c4582c01b910b Mon Sep 17 00:00:00 2001 From: Zheng Bian Date: Thu, 11 Sep 2025 20:33:55 +0800 Subject: [PATCH 2/2] Fix special attribute bug, format and reuse of Dictionary; Add Assert fix bug of Special Attribute fix format to pass format check fix reuses original dictionaries to avoid potential issues add assert of attribute in option --- .../ChangeAttributesScrollHandler.cs | 14 +++++++++++--- Maple2.Server.Game/Util/ItemStatsCalculator.cs | 8 ++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Maple2.Server.Game/PacketHandlers/ChangeAttributesScrollHandler.cs b/Maple2.Server.Game/PacketHandlers/ChangeAttributesScrollHandler.cs index 05094ff7c..fa2b5830c 100644 --- a/Maple2.Server.Game/PacketHandlers/ChangeAttributesScrollHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/ChangeAttributesScrollHandler.cs @@ -136,9 +136,16 @@ private void HandleChange(GameSession session, IByteReader packet) { // Fixed attributes. if (lockItem != null) { // Restore locked attribute values. - if (!ItemStatsCalc.UpdateFixedOption(ref changeItem, new LockOption((SpecialAttribute) attribute, true))) { - session.Send(ChangeAttributesPacket.Error(ChangeAttributesError.s_itemremake_error_server_default)); - return; + if (isSpecialAttribute) { + if (!ItemStatsCalc.UpdateFixedOption(ref changeItem, new LockOption((SpecialAttribute) attribute, true))) { + session.Send(ChangeAttributesPacket.Error(ChangeAttributesError.s_itemremake_error_server_default)); + return; + } + } else { + if (!ItemStatsCalc.UpdateFixedOption(ref changeItem, new LockOption((BasicAttribute) attribute, true))) { + session.Send(ChangeAttributesPacket.Error(ChangeAttributesError.s_itemremake_error_server_default)); + return; + } } } else { if (!ItemStatsCalc.UpdateFixedOption(ref changeItem)) { @@ -146,6 +153,7 @@ private void HandleChange(GameSession session, IByteReader packet) { return; } } + } // Try to consume both items. diff --git a/Maple2.Server.Game/Util/ItemStatsCalculator.cs b/Maple2.Server.Game/Util/ItemStatsCalculator.cs index ce5d1776f..a3830687e 100644 --- a/Maple2.Server.Game/Util/ItemStatsCalculator.cs +++ b/Maple2.Server.Game/Util/ItemStatsCalculator.cs @@ -184,7 +184,7 @@ public bool UpdateRandomOption(ref Item item, params LockOption[] presets) { return true; } - public bool UpdateFixedOption(ref Item item, params LockOption[] presets){ + public bool UpdateFixedOption(ref Item item, params LockOption[] presets) { if (item.Metadata.Option == null || item.Stats == null) { return false; } @@ -198,7 +198,9 @@ public bool UpdateFixedOption(ref Item item, params LockOption[] presets){ if (!TableMetadata.ItemOptionRandomTable.Options.TryGetValue(item.Metadata.Option.RandomId, item.Rarity, out ItemOption? itemOption)) { return false; } - ItemStats.Option fixedOption = new ItemStats.Option(option.Basic, option.Special, multiplyFactor: itemOption.MultiplyFactor); + var statResult = new Dictionary(option.Basic); + var specialResult = new Dictionary(option.Special); + var fixedOption = new ItemStats.Option(statResult, specialResult, multiplyFactor: itemOption.MultiplyFactor); if (!RandomizeValues(item, itemOption, ref fixedOption)) { return false; @@ -208,10 +210,12 @@ public bool UpdateFixedOption(ref Item item, params LockOption[] presets){ foreach (LockOption lockOption in presets) { if (lockOption.TryGet(out BasicAttribute basic, out bool lockBasicValue)) { if (lockBasicValue) { + Debug.Assert(option.Basic.ContainsKey(basic), "Missing basic attribute after using lock."); fixedOption.Basic[basic] = option.Basic[basic]; } } else if (lockOption.TryGet(out SpecialAttribute special, out bool lockSpecialValue)) { if (lockSpecialValue) { + Debug.Assert(option.Special.ContainsKey(special), "Missing special attribute after using lock."); fixedOption.Special[special] = option.Special[special]; } }