diff --git a/Maple2.File.Ingest/Maple2.File.Ingest.csproj b/Maple2.File.Ingest/Maple2.File.Ingest.csproj index 37c1075d4..7286a848f 100644 --- a/Maple2.File.Ingest/Maple2.File.Ingest.csproj +++ b/Maple2.File.Ingest/Maple2.File.Ingest.csproj @@ -19,7 +19,7 @@ - + diff --git a/Maple2.File.Ingest/Mapper/ItemMapper.cs b/Maple2.File.Ingest/Mapper/ItemMapper.cs index 43b860000..519421871 100644 --- a/Maple2.File.Ingest/Mapper/ItemMapper.cs +++ b/Maple2.File.Ingest/Mapper/ItemMapper.cs @@ -123,7 +123,7 @@ protected override IEnumerable Map() { StaticId: data.option.@static, StaticType: data.option.staticMakeType, RandomId: data.option.random, - RandomType: data.option.randomMakeType, + RandomType: (RandomMakeType) data.option.randomMakeType, ConstantId: data.option.constant, ConstantType: data.option.constantMakeType, LevelFactor: levelFactor, diff --git a/Maple2.File.Ingest/Mapper/TableMapper.cs b/Maple2.File.Ingest/Mapper/TableMapper.cs index 166c97ad2..ee6d54efb 100644 --- a/Maple2.File.Ingest/Mapper/TableMapper.cs +++ b/Maple2.File.Ingest/Mapper/TableMapper.cs @@ -460,10 +460,10 @@ private ItemOptionPickTable ParseItemOptionPick() { } private ItemVariationTable ParseItemVariation() { - var values = new Dictionary>(); - var rates = new Dictionary>(); - var specialValues = new Dictionary>(); - var specialRates = new Dictionary>(); + var values = new Dictionary>>(); + var rates = new Dictionary>>(); + var specialValues = new Dictionary>>(); + var specialRates = new Dictionary>>(); foreach (ItemOptionVariation.Option option in optionParser.ParseVariation()) { string name = option.OptionName; if (name.StartsWith("sid")) continue; // Don't know what stat this maps to. @@ -472,11 +472,19 @@ private ItemVariationTable ParseItemVariation() { var variation = new ItemVariationTable.Range( Min: option.OptionValueMin, Max: option.OptionValueMax, - Interval: option.OptionValueVariation); + Variation: option.OptionValueVariation); try { - values[name.ToBasicAttribute()] = variation; + if (values.ContainsKey(name.ToBasicAttribute())) { + values[name.ToBasicAttribute()].Add(variation); + } else { + values.Add(name.ToBasicAttribute(), [variation]); + } } catch (ArgumentOutOfRangeException) { - specialValues[name.ToSpecialAttribute()] = variation; + if (specialValues.ContainsKey(name.ToSpecialAttribute())) { + specialValues[name.ToSpecialAttribute()].Add(variation); + } else { + specialValues.Add(name.ToSpecialAttribute(), [variation]); + } } } else if (option.OptionRateVariation != 0) { if (name.EndsWith("_rate")) { @@ -486,30 +494,45 @@ private ItemVariationTable ParseItemVariation() { var variation = new ItemVariationTable.Range( Min: option.OptionRateMin, Max: option.OptionRateMax, - Interval: option.OptionRateVariation); + Variation: option.OptionRateVariation); try { - rates[name.ToBasicAttribute()] = variation; + if (rates.ContainsKey(name.ToBasicAttribute())) { + rates[name.ToBasicAttribute()].Add(variation); + } else { + rates.Add(name.ToBasicAttribute(), [variation]); + } } catch (ArgumentOutOfRangeException) { - specialRates[name.ToSpecialAttribute()] = variation; + if (specialRates.ContainsKey(name.ToSpecialAttribute())) { + specialRates[name.ToSpecialAttribute()].Add(variation); + } else { + specialRates.Add(name.ToSpecialAttribute(), [variation]); + } } } } - return new ItemVariationTable(values, rates, specialValues, specialRates); + Dictionary[]> valuesArray = values.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ToArray()); + Dictionary[]> ratesArray = rates.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ToArray()); + Dictionary[]> specialValuesArray = specialValues.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ToArray()); + Dictionary[]> specialRatesArray = specialRates.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ToArray()); + + return new ItemVariationTable(valuesArray, ratesArray, specialValuesArray, specialRatesArray); } private IEnumerable<(string Type, ItemEquipVariationTable Table)> ParseItemEquipVariation() { foreach ((string type, List options) in optionParser.ParseVariationEquip()) { - var values = new Dictionary(); - var rates = new Dictionary(); - var specialValues = new Dictionary(); - var specialRates = new Dictionary(); + var values = new Dictionary[]>(); + var rates = new Dictionary[]>(); + var specialValues = new Dictionary[]>(); + var specialRates = new Dictionary[]>(); foreach (ItemOptionVariationEquip.Option option in options) { string name = option.name.ToLower(); if (name.EndsWith("value")) { - int[] entries = new int[18]; + var entries = new ItemEquipVariationTable.Set[18]; for (int i = 0; i < 18; i++) { - entries[i] = (int) option[i]; + entries[i] = new ItemEquipVariationTable.Set( + Value: (int) option[i], + Weight: 1); // TODO: Weight } name = name[..^"value".Length]; // Remove suffix @@ -520,9 +543,11 @@ private ItemVariationTable ParseItemVariation() { } } else if (name.EndsWith("rate")) { - float[] entries = new float[18]; + var entries = new ItemEquipVariationTable.Set[18]; for (int i = 0; i < 18; i++) { - entries[i] = option[i]; + entries[i] = new ItemEquipVariationTable.Set( + Value: option[i], + Weight: 1); // TODO: Weight } name = name[..^"rate".Length]; // Remove suffix diff --git a/Maple2.Model/Enum/ItemOptionMakeType.cs b/Maple2.Model/Enum/ItemOptionMakeType.cs new file mode 100644 index 000000000..78670b10d --- /dev/null +++ b/Maple2.Model/Enum/ItemOptionMakeType.cs @@ -0,0 +1,6 @@ +namespace Maple2.Model.Enum; + +public enum RandomMakeType { + Base = 0, // uses itemoptionvariation table + Range = 1, // uses itemoptionvariation_* tables +} diff --git a/Maple2.Model/Metadata/ItemMetadata.cs b/Maple2.Model/Metadata/ItemMetadata.cs index aea1f43ff..a8dd1b21c 100644 --- a/Maple2.Model/Metadata/ItemMetadata.cs +++ b/Maple2.Model/Metadata/ItemMetadata.cs @@ -91,7 +91,7 @@ public record ItemMetadataOption( int StaticId, int StaticType, int RandomId, - int RandomType, + RandomMakeType RandomType, int ConstantId, int ConstantType, int LevelFactor, diff --git a/Maple2.Model/Metadata/Table/ItemOptionTable.cs b/Maple2.Model/Metadata/Table/ItemOptionTable.cs index e7dd6257a..694ea3296 100644 --- a/Maple2.Model/Metadata/Table/ItemOptionTable.cs +++ b/Maple2.Model/Metadata/Table/ItemOptionTable.cs @@ -49,17 +49,19 @@ IReadOnlyDictionary RandomRate } public record ItemVariationTable( - IReadOnlyDictionary> Values, - IReadOnlyDictionary> Rates, - IReadOnlyDictionary> SpecialValues, - IReadOnlyDictionary> SpecialRates + IReadOnlyDictionary[]> Values, + IReadOnlyDictionary[]> Rates, + IReadOnlyDictionary[]> SpecialValues, + IReadOnlyDictionary[]> SpecialRates ) : Table { - public readonly record struct Range(T Min, T Max, T Interval) where T : INumber; + public readonly record struct Range(T Min, T Max, T Variation) where T : INumber; } public record ItemEquipVariationTable( - IReadOnlyDictionary Values, - IReadOnlyDictionary Rates, - IReadOnlyDictionary SpecialValues, - IReadOnlyDictionary SpecialRates -) : Table; + IReadOnlyDictionary[]> Values, + IReadOnlyDictionary[]> Rates, + IReadOnlyDictionary[]> SpecialValues, + IReadOnlyDictionary[]> SpecialRates +) : Table { + public record Set(T Value, int Weight) where T : INumber; +} diff --git a/Maple2.Server.Game/PacketHandlers/ChangeAttributesScrollHandler.cs b/Maple2.Server.Game/PacketHandlers/ChangeAttributesScrollHandler.cs index a1f235449..e4d3b2c3f 100644 --- a/Maple2.Server.Game/PacketHandlers/ChangeAttributesScrollHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/ChangeAttributesScrollHandler.cs @@ -9,7 +9,7 @@ using Maple2.Server.Game.Packets; using Maple2.Server.Game.Session; using Maple2.Server.Game.Util; -using static Maple2.Model.Error.ChangeAttributesScrollError; +using Maple2.Tools.Extensions; namespace Maple2.Server.Game.PacketHandlers; @@ -57,21 +57,22 @@ private void HandleChange(GameSession session, IByteReader packet) { lock (session.Item) { Item? scroll = session.Item.Inventory.Get(scrollUid, InventoryType.Misc); if (scroll == null || scroll.Metadata.Function?.Type != ItemFunction.ItemRemakeScroll) { - session.Send(ChangeAttributesScrollPacket.Error(s_itemremake_scroll_error_invalid_scroll)); + session.Send(ChangeAttributesScrollPacket.Error(ChangeAttributesScrollError.s_itemremake_scroll_error_invalid_scroll)); return; } Item? item = session.Item.Inventory.Get(itemUid, InventoryType.Gear); if (item == null) { - session.Send(ChangeAttributesScrollPacket.Error(s_itemremake_scroll_error_invalid_target_data)); + session.Send(ChangeAttributesScrollPacket.Error(ChangeAttributesScrollError.s_itemremake_scroll_error_invalid_target_data)); return; } if (item.Stats == null) { - session.Send(ChangeAttributesScrollPacket.Error(s_itemremake_scroll_error_invalid_target_stat)); + session.Send(ChangeAttributesScrollPacket.Error(ChangeAttributesScrollError.s_itemremake_scroll_error_invalid_target_stat)); return; } + ChangeAttributesScrollError error = IsCompatibleScroll(item, scroll); - if (error != none) { + if (error != ChangeAttributesScrollError.none) { session.Send(ChangeAttributesScrollPacket.Error(error)); return; } @@ -81,19 +82,19 @@ private void HandleChange(GameSession session, IByteReader packet) { ItemStats.Option itemOption = item.Stats[ItemStats.Type.Random]; if (isSpecialAttribute) { if (!itemOption.Special.ContainsKey((SpecialAttribute) attribute)) { - session.Send(ChangeAttributesScrollPacket.Error(s_itemremake_scroll_error_impossible_property)); + session.Send(ChangeAttributesScrollPacket.Error(ChangeAttributesScrollError.s_itemremake_scroll_error_impossible_property)); return; } } else { if (!itemOption.Basic.ContainsKey((BasicAttribute) attribute)) { - session.Send(ChangeAttributesScrollPacket.Error(s_itemremake_scroll_error_impossible_property)); + session.Send(ChangeAttributesScrollPacket.Error(ChangeAttributesScrollError.s_itemremake_scroll_error_impossible_property)); return; } } lockItem = ChangeAttributesHandler.GetLockConsumeItem(session, item); if (lockItem == null) { - session.Send(ChangeAttributesScrollPacket.Error(s_itemremake_error_server_fail_lack_lock_consume_item)); + session.Send(ChangeAttributesScrollPacket.Error(ChangeAttributesScrollError.s_itemremake_error_server_fail_lack_lock_consume_item)); return; } } @@ -101,13 +102,23 @@ private void HandleChange(GameSession session, IByteReader packet) { // Clone the item so we can preview changes without modifying existing item. Item changeItem = item.Clone(); if (changeItem.Stats == null) { // This should be impossible, but check again to make linter happy. - session.Send(ChangeAttributesScrollPacket.Error(s_itemremake_scroll_error_invalid_target_stat)); + session.Send(ChangeAttributesScrollPacket.Error(ChangeAttributesScrollError.s_itemremake_scroll_error_invalid_target_stat)); + return; + } + + if (changeItem.Metadata.Option == null) { // This should be impossible, but check again to make linter happy. + session.Send(ChangeAttributesScrollPacket.Error(ChangeAttributesScrollError.s_itemremake_scroll_error_impossible_item)); + 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; } ItemStats.Option changeOption = changeItem.Stats[ItemStats.Type.Random]; - if (!ItemStatsCalc.RandomizeValues(changeItem.Type, ref changeOption)) { - session.Send(ChangeAttributesScrollPacket.Error(s_itemremake_scroll_error_server_fail_remake)); + if (!ItemStatsCalc.RandomizeValues(changeItem, itemOptionMetadata, ref changeOption)) { + session.Send(ChangeAttributesScrollPacket.Error(ChangeAttributesScrollError.s_itemremake_scroll_error_server_fail_remake)); return; } @@ -124,11 +135,11 @@ private void HandleChange(GameSession session, IByteReader packet) { } if (!session.Item.Inventory.Consume(scroll.Uid, 1)) { - session.Send(ChangeAttributesScrollPacket.Error(s_itemremake_scroll_error_server_fail_consume_scroll)); + session.Send(ChangeAttributesScrollPacket.Error(ChangeAttributesScrollError.s_itemremake_scroll_error_server_fail_consume_scroll)); return; } if (lockItem != null && !session.Item.Inventory.Consume(lockItem.Uid, 1)) { - session.Send(ChangeAttributesScrollPacket.Error(s_itemremake_error_server_fail_lack_lock_consume_item)); + session.Send(ChangeAttributesScrollPacket.Error(ChangeAttributesScrollError.s_itemremake_error_server_fail_lack_lock_consume_item)); return; } @@ -140,19 +151,19 @@ private void HandleChange(GameSession session, IByteReader packet) { private static void HandleSelect(GameSession session, IByteReader packet) { long itemUid = packet.ReadLong(); if (session.ChangeAttributesItem == null || session.ChangeAttributesItem.Uid != itemUid) { - session.Send(ChangeAttributesScrollPacket.Error(s_itemremake_scroll_error_server_fail_apply_before_option)); + session.Send(ChangeAttributesScrollPacket.Error(ChangeAttributesScrollError.s_itemremake_scroll_error_server_fail_apply_before_option)); return; } lock (session.Item) { Item? item = session.Item.Inventory.Get(itemUid, InventoryType.Gear); if (item == null) { - session.Send(ChangeAttributesScrollPacket.Error(s_itemremake_scroll_error_invalid_target_data)); + session.Send(ChangeAttributesScrollPacket.Error(ChangeAttributesScrollError.s_itemremake_scroll_error_invalid_target_data)); return; } if (item.Stats == null || session.ChangeAttributesItem.Stats == null) { - session.Send(ChangeAttributesScrollPacket.Error(s_itemremake_scroll_error_server_fail_apply_before_option)); + session.Send(ChangeAttributesScrollPacket.Error(ChangeAttributesScrollError.s_itemremake_scroll_error_server_fail_apply_before_option)); return; } @@ -164,30 +175,30 @@ private static void HandleSelect(GameSession session, IByteReader packet) { private ChangeAttributesScrollError IsCompatibleScroll(Item item, Item scroll) { if (item.Rarity is < Constant.ChangeAttributesMinRarity or > Constant.ChangeAttributesMaxRarity) { - return s_itemremake_scroll_error_impossible_rank; + return ChangeAttributesScrollError.s_itemremake_scroll_error_impossible_rank; } if (!item.Type.IsWeapon && !item.Type.IsArmor && !item.Type.IsAccessory) { - return s_itemremake_scroll_error_impossible_slot; + return ChangeAttributesScrollError.s_itemremake_scroll_error_impossible_slot; } if (item.Metadata.Limit.Level < Constant.ChangeAttributesMinLevel) { - return s_itemremake_scroll_error_impossible_level; + return ChangeAttributesScrollError.s_itemremake_scroll_error_impossible_level; } // Validate scroll conditions if (!int.TryParse(scroll.Metadata.Function?.Parameters, out int remakeId)) { - return s_itemremake_scroll_error_invalid_scroll_data; + return ChangeAttributesScrollError.s_itemremake_scroll_error_invalid_scroll_data; } if (!TableMetadata.ItemRemakeScrollTable.Entries.TryGetValue(remakeId, out ItemRemakeScrollMetadata? metadata)) { - return s_itemremake_scroll_error_invalid_scroll_data; + return ChangeAttributesScrollError.s_itemremake_scroll_error_invalid_scroll_data; } if (item.Metadata.Limit.Level < metadata.MinLevel || item.Metadata.Limit.Level > metadata.MaxLevel) { - return s_itemremake_scroll_error_impossible_item; + return ChangeAttributesScrollError.s_itemremake_scroll_error_impossible_item; } if (!metadata.Rarities.Contains(item.Rarity) || !metadata.ItemTypes.Contains(item.Type.Type)) { - return s_itemremake_scroll_error_impossible_item; + return ChangeAttributesScrollError.s_itemremake_scroll_error_impossible_item; } - return none; + return ChangeAttributesScrollError.none; } } diff --git a/Maple2.Server.Game/Util/ItemStatsCalculator.cs b/Maple2.Server.Game/Util/ItemStatsCalculator.cs index 5eed7a701..059285a2d 100644 --- a/Maple2.Server.Game/Util/ItemStatsCalculator.cs +++ b/Maple2.Server.Game/Util/ItemStatsCalculator.cs @@ -6,6 +6,7 @@ using Maple2.Model.Metadata; using Maple2.Tools.Extensions; using Maple2.Server.Core.Formulas; +using Maple2.Tools; using Serilog; namespace Maple2.Server.Game.Util; @@ -35,8 +36,10 @@ public sealed class ItemStatsCalculator { stats[ItemStats.Type.Static] = staticOption; } - if (GetRandomOption(item, out ItemStats.Option? option)) { - RandomizeValues(item.Type, ref option, rollMax); + + if (TableMetadata.ItemOptionRandomTable.Options.TryGetValue(item.Metadata.Option.RandomId, item.Rarity, out ItemOption? itemOption)) { + ItemStats.Option option = GetRandomOption(itemOption); + RandomizeValues(item, itemOption, ref option, rollMax); stats[ItemStats.Type.Random] = option; } @@ -77,11 +80,12 @@ public bool UpdateRandomOption(ref Item item, params LockOption[] presets) { } // Get some random options - if (!GetRandomOption(item, out ItemStats.Option? randomOption, option.Count, presets)) { + if (!TableMetadata.ItemOptionRandomTable.Options.TryGetValue(item.Metadata.Option.RandomId, item.Rarity, out ItemOption? itemOption)) { return false; } + ItemStats.Option randomOption = GetRandomOption(itemOption, option.Count, presets); - if (!RandomizeValues(item.Type, ref randomOption)) { + if (!RandomizeValues(item, itemOption, ref randomOption)) { return false; } @@ -105,35 +109,176 @@ public bool UpdateRandomOption(ref Item item, params LockOption[] presets) { return true; } - // TODO: These should technically be weighted towards the lower end. - public bool RandomizeValues(in ItemType type, ref ItemStats.Option option, bool rollMax = false) { - ItemEquipVariationTable? table = GetVariationTable(type); + /// Item + /// Item's Random Option Metadata + /// + /// Select the highest possible roll and circumvents the randomness + public bool RandomizeValues(Item item, ItemOption itemOptionMetadata, ref ItemStats.Option option, bool rollMax = false) { + if (item.Metadata.Option == null) { + return false; + } + ItemEquipVariationTable? table = GetVariationTable(item.Type); if (table == null) { return false; } foreach (BasicAttribute attribute in option.Basic.Keys) { - int index = rollMax ? 17 : Random.Shared.Next(2, 18); - if (table.Values.TryGetValue(attribute, out int[]? values)) { - int value = values.ElementAtOrDefault(index); + if (table.Values.TryGetValue(attribute, out ItemEquipVariationTable.Set[]? values)) { + int value = GetValue(item, itemOptionMetadata, attribute, tableValues: values, rollMax: rollMax); option.Basic[attribute] = new BasicOption((int) (value * option.MultiplyFactor)); - } else if (table.Rates.TryGetValue(attribute, out float[]? rates)) { - float rate = rates.ElementAtOrDefault(index); - option.Basic[attribute] = new BasicOption(rate * option.MultiplyFactor); + } else if (table.Rates.TryGetValue(attribute, out ItemEquipVariationTable.Set[]? rates)) { + int rateInt = GetValue(item, itemOptionMetadata, attribute, tableRates: rates, rollMax: rollMax); + option.Basic[attribute] = new BasicOption((rateInt / 1000f) * option.MultiplyFactor); } } foreach (SpecialAttribute attribute in option.Special.Keys) { - int index = rollMax ? 17 : Random.Shared.Next(2, 18); - if (table.SpecialValues.TryGetValue(attribute, out int[]? values)) { - int value = values.ElementAtOrDefault(index); + if (table.SpecialValues.TryGetValue(attribute, out ItemEquipVariationTable.Set[]? values)) { + int value = GetValue(item, itemOptionMetadata, specialAttribute: attribute, tableValues: values, rollMax: rollMax); option.Special[attribute] = new SpecialOption(0f, value * option.MultiplyFactor); - } else if (table.SpecialRates.TryGetValue(attribute, out float[]? rates)) { - float rate = rates.ElementAtOrDefault(index); - option.Special[attribute] = new SpecialOption(rate * option.MultiplyFactor); + } else if (table.SpecialRates.TryGetValue(attribute, out ItemEquipVariationTable.Set[]? rates)) { + int rateInt = GetValue(item, itemOptionMetadata, specialAttribute: attribute, tableRates: rates, rollMax: rollMax); + option.Special[attribute] = new SpecialOption((rateInt / 1000f) * option.MultiplyFactor); } } return true; + + int GetValue(Item item, ItemOption itemOptionMetadata, BasicAttribute? attribute = null, SpecialAttribute? specialAttribute = null, ItemEquipVariationTable.Set[]? tableValues = null, ItemEquipVariationTable.Set[]? tableRates = null, bool rollMax = false) { + var weightedValues = new WeightedSet(); + var weightedRates = new WeightedSet(); + if (item.Type.IsPet) { + switch (item.Rarity) { + case < 3: + if (rollMax) { + if (tableValues != null) { + return tableValues[4].Value; + } + if (tableRates != null) { + return (int) (tableRates[4].Value * 1000); + } + } + // only gets values from idx 0 to 4 + for (int i = 0; i < 5; i++) { + if (tableValues != null) { + weightedValues.Add(tableValues[i].Value, tableValues[i].Weight); + } else if (tableRates != null) { + weightedRates.Add(tableRates[i].Value, tableRates[i].Weight); + } + } + break; + default: + if (rollMax) { + if (tableValues != null) { + return tableValues[17].Value; + } + if (tableRates != null) { + return (int) (tableRates[17].Value * 1000); + } + } + // only gets values from idx 0 to 17 (entire array) + for (int i = 0; i < 18; i++) { + if (tableValues != null) { + weightedValues.Add(tableValues[i].Value, tableValues[i].Weight); + } else if (tableRates != null) { + weightedRates.Add(tableRates[i].Value, tableRates[i].Weight); + } + } + break; + } + if (tableValues != null) { + return weightedValues.Get(); + } + if (tableRates != null) { + return (int) (weightedRates.Get() * 1000); + } + } else { + switch (item.Metadata.Option!.RandomType) { + case RandomMakeType.Range: + switch (item.Metadata.Option.LevelFactor) { + case < 50: + if (rollMax) { + if (tableValues != null) { + return tableValues[2].Value; + } + if (tableRates != null) { + return (int) (tableRates[2].Value * 1000); + } + } + // only gets values from idx 0 to 2 + for (int i = 0; i < 3; i++) { + if (tableValues != null) { + weightedValues.Add(tableValues[i].Value, tableValues[i].Weight); + } else if (tableRates != null) { + weightedRates.Add(tableRates[i].Value, tableRates[i].Weight); + } + } + break; + case < 70: + if (rollMax) { + if (tableValues != null) { + return tableValues[9].Value; + } + if (tableRates != null) { + return (int) (tableRates[9].Value * 1000); + } + } + // only gets values from idx 2 to 9 + for (int i = 2; i < 10; i++) { + if (tableValues != null) { + weightedValues.Add(tableValues[i].Value, tableValues[i].Weight); + } else if (tableRates != null) { + weightedRates.Add(tableRates[i].Value, tableRates[i].Weight); + } + } + break; + case >= 70: + if (rollMax) { + if (tableValues != null) { + return tableValues[17].Value; + } + if (tableRates != null) { + return (int) (tableRates[17].Value * 1000); + } + } + // only gets values from idx 10 to 17 + for (int i = 10; i < 18; i++) { + if (tableValues != null) { + weightedValues.Add(tableValues[i].Value, tableValues[i].Weight); + } else if (tableRates != null) { + weightedRates.Add(tableRates[i].Value, tableRates[i].Weight); + } + } + break; + } + if (tableValues != null) { + return weightedValues.Get(); + } + if (tableRates != null) { + return (int) (weightedRates.Get() * 1000); + } + break; + case RandomMakeType.Base: + default: + float minRate = 0; + int minValue = 0; + if (attribute != null) { + ItemOption.Entry entry = itemOptionMetadata.Entries.FirstOrDefault(optionEntry => optionEntry.BasicAttribute == attribute); + minRate = entry.Rates?.Min ?? 0; + minValue = entry.Values?.Min ?? 0; + return GetItemVariationValue(basicAttribute: attribute, value: minValue, rate: minRate); + } + if (specialAttribute != null) { + ItemOption.Entry entry = itemOptionMetadata.Entries.FirstOrDefault(optionEntry => optionEntry.SpecialAttribute == specialAttribute); + minRate = entry.Rates?.Min ?? 0; + minValue = entry.Values?.Min ?? 0; + return GetItemVariationValue(specialAttribute: specialAttribute, value: minValue, rate: minRate); + } + break; + } + } + + return 0; + } } // Used to calculate the default constant attributes for a given item. @@ -194,19 +339,8 @@ private bool GetStaticOption(Item item, int job, ItemOptionPickTable.Option? pic } // Used to calculate the default random attributes for a given item. - private bool GetRandomOption(Item item, [NotNullWhen(true)] out ItemStats.Option? option, int count = -1, params LockOption[] presets) { - if (item.Metadata.Option == null) { - option = null; - return false; - } - - if (TableMetadata.ItemOptionRandomTable.Options.TryGetValue(item.Metadata.Option.RandomId, item.Rarity, out ItemOption? itemOption)) { - option = RandomItemOption(itemOption, count, presets); - return true; - } - - option = null; - return false; + private ItemStats.Option GetRandomOption(ItemOption itemOption, int count = -1, params LockOption[] presets) { + return RandomItemOption(itemOption, count, presets); } private ItemEquipVariationTable? GetVariationTable(in ItemType type) { @@ -226,6 +360,44 @@ private bool GetRandomOption(Item item, [NotNullWhen(true)] out ItemStats.Option return null; } + /// If the expected value is a rate, it will be multiplied by 1000. Conversion needs to be done afterward. + private int GetItemVariationValue(BasicAttribute? basicAttribute = null, SpecialAttribute? specialAttribute = null, int value = 0, float rate = 0) { + if (basicAttribute != null) { + if (TableMetadata.ItemVariationTable.Values.TryGetValue(basicAttribute.Value, out ItemVariationTable.Range[]? values)) { + foreach (ItemVariationTable.Range range in values) { + if (value >= range.Min && value <= range.Max) { + return Random.Shared.Next(value, value + range.Variation + 1); + } + } + } else if (TableMetadata.ItemVariationTable.Rates.TryGetValue(basicAttribute.Value, out ItemVariationTable.Range[]? rates)) { + foreach (ItemVariationTable.Range range in rates) { + if (rate >= range.Min && rate <= range.Max) { + int convertedVariation = (int) range.Variation * 1000; + int convertedRate = (int) rate * 1000; + return Random.Shared.Next(convertedRate, convertedRate + convertedVariation + 1); + } + } + } + } else if (specialAttribute != null) { + if (TableMetadata.ItemVariationTable.SpecialValues.TryGetValue(specialAttribute.Value, out ItemVariationTable.Range[]? values)) { + foreach (ItemVariationTable.Range range in values) { + if (value >= range.Min && value <= range.Max) { + return Random.Shared.Next(value, value + range.Variation + 1); + } + } + } else if (TableMetadata.ItemVariationTable.SpecialRates.TryGetValue(specialAttribute.Value, out ItemVariationTable.Range[]? rates)) { + foreach (ItemVariationTable.Range range in rates) { + if (rate >= range.Min && rate <= range.Max) { + int convertedVariation = (int) range.Variation * 1000; + int convertedRate = (int) rate * 1000; + return Random.Shared.Next(convertedRate, convertedRate + convertedVariation + 1); + } + } + } + } + return 0; + } + private static ItemStats.Option ConstantItemOption(ItemOptionConstant option) { var statResult = new Dictionary(); var specialResult = new Dictionary();