diff --git a/Maple2.Server.Game/Util/ItemStatsCalculator.cs b/Maple2.Server.Game/Util/ItemStatsCalculator.cs index 64fab0d10..79e121500 100644 --- a/Maple2.Server.Game/Util/ItemStatsCalculator.cs +++ b/Maple2.Server.Game/Util/ItemStatsCalculator.cs @@ -517,28 +517,39 @@ private static ItemStats.Option RandomItemOption(ItemOption option, in ItemType // Ensures that there are enough options to choose. total = Math.Min(total, option.Entries.Length); + // Create a mutable list of entries + List availableEntries = option.Entries.ToList(); + // Compute locked options first. foreach (LockOption preset in presets) { if (preset.TryGet(out BasicAttribute basic, out bool _)) { ItemOption.Entry entry = option.Entries.FirstOrDefault(e => e.BasicAttribute == basic); // Ignore any invalid presets, they will get populated with valid data below. AddResult(entry, statResult, specialResult); + availableEntries.Remove(entry); } else if (preset.TryGet(out SpecialAttribute special, out bool _)) { ItemOption.Entry entry = option.Entries.FirstOrDefault(e => e.SpecialAttribute == special); // Ignore any invalid presets, they will get populated with valid data below. AddResult(entry, statResult, specialResult); + availableEntries.Remove(entry); } } - while (statResult.Count + specialResult.Count < total) { - ItemOption.Entry entry = option.Entries.Random(); + while (statResult.Count + specialResult.Count < total && availableEntries.Count > 0) { + ItemOption.Entry entry = availableEntries.Random(); if (statsType == ItemStats.Type.Random && !IsValidStat(itemType, total, statResult, specialResult, entry)) { + availableEntries.Remove(entry); continue; } if (!AddResult(entry, statResult, specialResult)) { - Log.Error("Failed to select random item option: {Entry}", entry); // Invalid entry + Log.Logger.Error("Failed to select random item option: {Entry}", entry); // Invalid entry } + availableEntries.Remove(entry); + } + + if (availableEntries.Count == 0) { + Log.Logger.Error("Failed to select random item option, no more entries available. ItemType: {ItemType}, StatsType: {StatsType}, Total: {Total}", itemType, statsType, total); } return new ItemStats.Option(statResult, specialResult, multiplyFactor: option.MultiplyFactor); @@ -588,15 +599,14 @@ private static bool IsValidStat(ItemType itemType, int statLineCount, IDictionar return damageTypeStatCount < 1; } - if (itemType is { IsCombatPet: false, IsWeapon: false, IsAccessory: false }) { + bool isBaseOffense = entry.BasicAttribute != null && offenseBasicAttributes.Contains((BasicAttribute) entry.BasicAttribute); + bool isSpecialOffense = entry.SpecialAttribute != null && offenseSpecialAttributes.Contains((SpecialAttribute) entry.SpecialAttribute); + if (itemType is { IsCombatPet: false, IsWeapon: false, IsAccessory: false } && (isBaseOffense || isSpecialOffense)) { int offenseStatCount = statDict.Keys.Count(stat => offenseBasicAttributes.Contains(stat)); offenseStatCount += specialDict.Keys.Count(stat => offenseSpecialAttributes.Contains(stat)); - if (entry.BasicAttribute != null && offenseBasicAttributes.Contains((BasicAttribute) entry.BasicAttribute)) { - offenseStatCount++; - } else if (entry.SpecialAttribute != null && offenseSpecialAttributes.Contains((SpecialAttribute) entry.SpecialAttribute)) { - offenseStatCount++; - } + // Simulate the addition of the new stat + offenseStatCount++; return (float) offenseStatCount / statLineCount <= OFFENSE_LINE_MAX_THRESHOLD; }