diff --git a/EpicLoot/Crafting/AugmentChoiceDialog.cs b/EpicLoot/Crafting/AugmentChoiceDialog.cs index 0e04e73c2..8e023d494 100644 --- a/EpicLoot/Crafting/AugmentChoiceDialog.cs +++ b/EpicLoot/Crafting/AugmentChoiceDialog.cs @@ -137,7 +137,7 @@ public void Show(ItemDrop.ItemData fromItem, int effectIndex, Action(); - text.text = Localization.instance.Localize((index == 0 ? "($mod_epicloot_augment_keep) " : "") + MagicItem.GetEffectText(effect, rarity, true)); + text.text = Localization.instance.Localize((index == 0 ? "($mod_epicloot_augment_keep) " : "") + MagicItem.GetEffectText(effect, rarity, magicItem.Quality, fromItem.m_shared.m_name, true, magicItem.LegendaryID)); text.color = rarityColor; if (EpicLoot.HasAuga) diff --git a/EpicLoot/Crafting/AugmentHelper.cs b/EpicLoot/Crafting/AugmentHelper.cs index a044e00d8..a26fb402a 100644 --- a/EpicLoot/Crafting/AugmentHelper.cs +++ b/EpicLoot/Crafting/AugmentHelper.cs @@ -169,7 +169,7 @@ public static List GetAvailableAugments(AugmentRecipe if (recipe.EffectIndex >= 0 && recipe.EffectIndex < magicItem.Effects.Count) { var currentEffectDef = MagicItemEffectDefinitions.Get(magicItem.Effects[recipe.EffectIndex].EffectType); - valuelessEffect = currentEffectDef.GetValuesForRarity(rarity) == null; + valuelessEffect = currentEffectDef.GetValuesForRarity(rarity, item.m_shared.m_name, magicItem.Quality) == null; } return MagicItemEffectDefinitions.GetAvailableEffects(item.Extended(), item.GetMagicItem(), valuelessEffect ? -1 : recipe.EffectIndex); @@ -179,7 +179,7 @@ public static string GetAugmentSelectorText(MagicItem magicItem, int i, IReadOnl { var pip = EpicLoot.GetMagicEffectPip(magicItem.IsEffectAugmented(i)); bool free = EnchantCostsHelper.EffectIsDeprecated(augmentableEffects[i].EffectType); - return $"{pip} {Localization.instance.Localize(MagicItem.GetEffectText(augmentableEffects[i], rarity, true))}{(free ? " [*FREE]" : "")}"; + return $"{pip} {Localization.instance.Localize(MagicItem.GetEffectText(augmentableEffects[i], rarity, magicItem.Quality, magicItem.ItemName, true, magicItem.LegendaryID))}{(free ? " [*FREE]" : "")}"; } public static List> GetAugmentCosts(ItemDrop.ItemData item, int recipeEffectIndex) diff --git a/EpicLoot/Crafting/AugmentsAvailableDialog.cs b/EpicLoot/Crafting/AugmentsAvailableDialog.cs index f3c8a6552..050431269 100644 --- a/EpicLoot/Crafting/AugmentsAvailableDialog.cs +++ b/EpicLoot/Crafting/AugmentsAvailableDialog.cs @@ -72,7 +72,7 @@ public void Show(AugmentHelper.AugmentRecipe recipe) foreach (var effectDef in availableEffects) { - var values = effectDef.GetValuesForRarity(item.GetRarity()); + var values = effectDef.GetValuesForRarity(item.GetRarity(), item.m_shared.m_name, magicItem.Quality); var valueDisplay = values != null ? Mathf.Approximately(values.MinValue, values.MaxValue) ? $"{values.MinValue}" : $"({values.MinValue}-{values.MaxValue})" : ""; t.AppendLine($"‣ {string.Format(Localization.instance.Localize(effectDef.DisplayText), valueDisplay)}"); } diff --git a/EpicLoot/CraftingV2/EnchantingUIController.cs b/EpicLoot/CraftingV2/EnchantingUIController.cs index e5e57b18c..7d9d66e36 100644 --- a/EpicLoot/CraftingV2/EnchantingUIController.cs +++ b/EpicLoot/CraftingV2/EnchantingUIController.cs @@ -388,7 +388,7 @@ private static string GetEnchantInfo(ItemDrop.ItemData item, MagicRarityUnity _r foreach (var effectDef in availableEffects) { - var values = effectDef.GetValuesForRarity(rarity); + var values = effectDef.GetValuesForRarity(rarity, item.m_shared.m_name, ItemQuality.Normal); var valueDisplay = values != null ? Mathf.Approximately(values.MinValue, values.MaxValue) ? $"{values.MinValue}" : $"({values.MinValue}-{values.MaxValue})" : ""; sb.AppendLine($"‣ {string.Format(Localization.instance.Localize(effectDef.DisplayText), valueDisplay)}"); } @@ -418,7 +418,7 @@ private static GameObject EnchantItemAndReturnSuccessDialog(ItemDrop.ItemData it previousDurabilityPercent = item.m_durability / item.GetMaxDurability(); var luckFactor = player.GetTotalActiveMagicEffectValue(MagicEffectType.Luck, 0.01f); - var magicItem = LootRoller.RollMagicItem((ItemRarity)rarity, item, luckFactor); + var magicItem = LootRoller.RollMagicItem((ItemRarity)rarity, ItemQuality.Normal, item, luckFactor); var magicItemComponent = item.Data().GetOrCreate(); magicItemComponent.SetMagicItem(magicItem); @@ -520,7 +520,7 @@ private static string GetAvailableAugmentEffects(ItemDrop.ItemData item, int aug if (augmentindex >= 0 && augmentindex < magicItem.Effects.Count) { var currentEffectDef = MagicItemEffectDefinitions.Get(magicItem.Effects[augmentindex].EffectType); - valuelessEffect = currentEffectDef.GetValuesForRarity(rarity) == null; + valuelessEffect = currentEffectDef.GetValuesForRarity(rarity, item.m_shared.m_name, magicItem.Quality) == null; } var availableEffects = MagicItemEffectDefinitions.GetAvailableEffects(item.Extended(), item.GetMagicItem(), valuelessEffect ? -1 : augmentindex); @@ -529,7 +529,7 @@ private static string GetAvailableAugmentEffects(ItemDrop.ItemData item, int aug sb.Append($""); foreach (var effectDef in availableEffects) { - var values = effectDef.GetValuesForRarity(item.GetRarity()); + var values = effectDef.GetValuesForRarity(item.GetRarity(), item.m_shared.m_name, magicItem.Quality); var valueDisplay = values != null ? Mathf.Approximately(values.MinValue, values.MaxValue) ? $"{values.MinValue}" : $"({values.MinValue}-{values.MaxValue})" : ""; sb.AppendLine($"‣ {string.Format(Localization.instance.Localize(effectDef.DisplayText), valueDisplay)}"); } diff --git a/EpicLoot/EpicLoot.cs b/EpicLoot/EpicLoot.cs index 884bdd427..2b530c34e 100644 --- a/EpicLoot/EpicLoot.cs +++ b/EpicLoot/EpicLoot.cs @@ -52,6 +52,12 @@ public enum GatedBountyMode BossKillUnlocksNextBiomeBounties } + public enum EffectValueRollDistributionTypes + { + Linear, + TendsToLowAverage + } + public class Assets { public AssetBundle AssetBundle; @@ -112,6 +118,8 @@ public class EpicLoot : BaseUnityPlugin //private static ConfigEntry _mythicMaterialIconColor; public static ConfigEntry UseScrollingCraftDescription; public static ConfigEntry TransferMagicItemToCrafts; + public static ConfigEntry EffectValueRollDistribution; + public static ConfigEntry UseLootFilters; public static ConfigEntry CraftingTabStyle; private static ConfigEntry _loggingEnabled; private static ConfigEntry _logLevel; @@ -235,6 +243,8 @@ private void Awake() GlobalDropRateModifier = SyncedConfig("Balance", "Global Drop Rate Modifier", 1.0f, "A global percentage that modifies how likely items are to drop. 1 = Exactly what is in the loot tables will drop. 0 = Nothing will drop. 2 = The number of items in the drop table are twice as likely to drop (note, this doesn't double the number of items dropped, just doubles the relative chance for them to drop). Min = 0, Max = 4"); ItemsToMaterialsDropRatio = SyncedConfig("Balance", "Items To Materials Drop Ratio", 0.0f, "Sets the chance that item drops are instead dropped as magic crafting materials. 0 = all items, no materials. 1 = all materials, no items. Values between 0 and 1 change the ratio of items to materials that drop. At 0.5, half of everything that drops would be items and the other half would be materials. Min = 0, Max = 1"); TransferMagicItemToCrafts = SyncedConfig("Balance", "Transfer Enchants to Crafted Items", false, "When enchanted items are used as ingredients in recipes, transfer the highest enchant to the newly crafted item. Default: False."); + EffectValueRollDistribution = SyncedConfig("Balance", "Effect Value Roll Distribution", EffectValueRollDistributionTypes.Linear, "Function to be used for rolling effect values on an item. Linear: any number in the possible effect value range can be rolled with equal chance. TendsToLowAverage: values close to min and especially max of the range are very unlikely to be rolled. Default: Linear."); + UseLootFilters = SyncedConfig("Balance", "Use Loot Filters", false, "If set to true, all item drop will be affected by loot filters (defined in lootfilters.json). Loot filters allow to prevent dropping of the items you don't need anymore or auto sacrifice the unneeded items into materials on drop."); AlwaysShowWelcomeMessage = Config.Bind("Debug", "AlwaysShowWelcomeMessage", false, "Just a debug flag for testing the welcome message, do not use."); OutputPatchedConfigFiles = Config.Bind("Debug", "OutputPatchedConfigFiles", false, "Just a debug flag for testing the patching system, do not use."); @@ -488,6 +498,7 @@ public static void InitializeConfig() LoadJsonFile("abilities.json", AbilityDefinitions.Initialize, ConfigType.Synced); LoadJsonFile("materialconversions.json", MaterialConversions.Initialize, ConfigType.Synced); LoadJsonFile("enchantingupgrades.json", EnchantingTableUpgrades.InitializeConfig, ConfigType.Synced); + LoadJsonFile("lootfilters.json", LootFilterDefinitions.Initialize, ConfigType.Synced); WatchNewPatchConfig(); } @@ -1483,7 +1494,7 @@ private static void WriteLootTableDrops(StringBuilder t, LootTable lootTable) for (var i = 0; i < limit; i++) { var level = i + 1; - var dropTable = LootRoller.GetDropsForLevel(lootTable, level, false); + var dropTable = LootRoller.GetDropsForLevelOrDistance(lootTable, level, 0, false); if (dropTable == null || dropTable.Count == 0) { continue; @@ -1513,7 +1524,7 @@ private static void WriteLootTableItems(StringBuilder t, LootTable lootTable) for (var i = 0; i < limit; i++) { var level = i + 1; - var lootList = LootRoller.GetLootForLevel(lootTable, level, false); + var lootList = LootRoller.GetLootForLevelOrDistance(lootTable, level, 0, false); if (ArrayUtils.IsNullOrEmpty(lootList)) { continue; diff --git a/EpicLoot/EpicLoot.csproj b/EpicLoot/EpicLoot.csproj index 08a668af7..c30f3574a 100644 --- a/EpicLoot/EpicLoot.csproj +++ b/EpicLoot/EpicLoot.csproj @@ -131,6 +131,7 @@ + @@ -287,6 +288,7 @@ + @@ -338,6 +340,7 @@ xcopy "$(ProjectDir)legendaries.json" "G:\Steam\steamapps\common\Valheim-Dev\BepInEx\plugins\$(ProjectName)\" /q /y /i xcopy "$(ProjectDir)abilities.json" "G:\Steam\steamapps\common\Valheim-Dev\BepInEx\plugins\$(ProjectName)\" /q /y /i xcopy "$(ProjectDir)materialconversions.json" "G:\Steam\steamapps\common\Valheim-Dev\BepInEx\plugins\$(ProjectName)\" /q /y /i + xcopy "$(ProjectDir)lootfilter.json" "G:\Steam\steamapps\common\Valheim-Dev\BepInEx\plugins\$(ProjectName)\" /q /y /i diff --git a/EpicLoot/GatedItemType/GatedItemTypeHelper.cs b/EpicLoot/GatedItemType/GatedItemTypeHelper.cs index 573094d2c..bcc1945a2 100644 --- a/EpicLoot/GatedItemType/GatedItemTypeHelper.cs +++ b/EpicLoot/GatedItemType/GatedItemTypeHelper.cs @@ -111,24 +111,9 @@ public static string GetGatedItemID(string itemID, GatedItemTypeMode mode, List< return null; } - while (CheckIfItemNeedsGate(mode, itemID, itemName)) + if (ItemNotAllowedYet(mode, itemID, itemName)) { - //EpicLoot.Log("Yes..."); - - var index = info.Items.IndexOf(itemID); - if (index < 0) - { - // Items list is empty, no need to gate any items from of this type - return itemID; - } - if (index == 0) - { - return string.IsNullOrEmpty(info.Fallback) ? itemID : GetGatedFallbackItem(info.Fallback, mode, itemID, usedTypes); - } - - itemID = info.Items[index - 1]; - itemName = GetItemName(itemID); - //EpicLoot.Log($"Next lower tier item is ({itemID} - {itemName})"); + return null; } return itemID; @@ -154,24 +139,33 @@ private static string GetItemName(string itemID) return item.m_shared.m_name; } - private static bool CheckIfItemNeedsGate(GatedItemTypeMode mode, string itemID, string itemName) + private static bool ItemNotAllowedYet(GatedItemTypeMode mode, string itemID, string itemName) { - if (!BossPerItem.ContainsKey(itemID)) + if (mode == GatedItemTypeMode.PlayerMustKnowRecipe) { - EpicLoot.LogWarning($"Item ({itemID}) was not registered in iteminfo.json with any particular boss"); - return false; + return Player.m_localPlayer != null && !Player.m_localPlayer.IsRecipeKnown(itemName); } - - var bossKeyForItem = BossPerItem[itemID]; - var prevBossKey = Bosses.GetPrevBossKey(bossKeyForItem); - //EpicLoot.Log($"Checking if item ({itemID}) needs gating (boss: {bossKeyForItem}, prev boss: {prevBossKey}"); - switch (mode) + else if (mode == GatedItemTypeMode.PlayerMustHaveCraftedItem) { - case GatedItemTypeMode.BossKillUnlocksCurrentBiomeItems: return !ZoneSystem.instance.GetGlobalKey(bossKeyForItem); - case GatedItemTypeMode.BossKillUnlocksNextBiomeItems: return !(string.IsNullOrEmpty(prevBossKey) || ZoneSystem.instance.GetGlobalKey(prevBossKey)); - case GatedItemTypeMode.PlayerMustKnowRecipe: return Player.m_localPlayer != null && !Player.m_localPlayer.IsRecipeKnown(itemName); - case GatedItemTypeMode.PlayerMustHaveCraftedItem: return Player.m_localPlayer != null && !Player.m_localPlayer.m_knownMaterial.Contains(itemName); - default: return false; + return Player.m_localPlayer != null && !Player.m_localPlayer.m_knownMaterial.Contains(itemName); + } + else + { + if (!BossPerItem.ContainsKey(itemID)) + { + EpicLoot.LogWarning($"Item ({itemID}) was not registered in iteminfo.json with any particular boss"); + return false; + } + + var bossKeyForItem = BossPerItem[itemID]; + var prevBossKey = Bosses.GetPrevBossKey(bossKeyForItem); + //EpicLoot.Log($"Checking if item ({itemID}) needs gating (boss: {bossKeyForItem}, prev boss: {prevBossKey}"); + switch (mode) + { + case GatedItemTypeMode.BossKillUnlocksCurrentBiomeItems: return !ZoneSystem.instance.GetGlobalKey(bossKeyForItem); + case GatedItemTypeMode.BossKillUnlocksNextBiomeItems: return !(string.IsNullOrEmpty(prevBossKey) || ZoneSystem.instance.GetGlobalKey(prevBossKey)); + default: return false; + } } } diff --git a/EpicLoot/LegendarySystem/LegendaryItemConfig.cs b/EpicLoot/LegendarySystem/LegendaryItemConfig.cs index c0ab8bb49..91ae6f04f 100644 --- a/EpicLoot/LegendarySystem/LegendaryItemConfig.cs +++ b/EpicLoot/LegendarySystem/LegendaryItemConfig.cs @@ -28,6 +28,8 @@ public class LegendaryInfo public string Description; public MagicItemEffectRequirements Requirements; public List GuaranteedMagicEffects = new List(); + public List GuaranteedMagicEffectsExceptional = new List(); + public List GuaranteedMagicEffectsElite = new List(); public int GuaranteedEffectCount = -1; public float SelectionWeight = 1; public string EquipFx; diff --git a/EpicLoot/LegendarySystem/UniqueLegendaryHelper.cs b/EpicLoot/LegendarySystem/UniqueLegendaryHelper.cs index 134a65441..de81ec652 100644 --- a/EpicLoot/LegendarySystem/UniqueLegendaryHelper.cs +++ b/EpicLoot/LegendarySystem/UniqueLegendaryHelper.cs @@ -69,10 +69,25 @@ public static IList GetAvailableLegendaries(ItemDrop.ItemData bas return availableLegendaries; } - public static MagicItemEffectDefinition.ValueDef GetLegendaryEffectValues(string legendaryID, string effectType) + public static MagicItemEffectDefinition.ValueDef GetLegendaryEffectValues(string legendaryID, string effectType, ItemQuality quality) { if (LegendaryInfo.TryGetValue(legendaryID, out var legendaryInfo)) { + if (quality == ItemQuality.Elite) + { + if (legendaryInfo.GuaranteedMagicEffectsElite.Count() > 0 && legendaryInfo.GuaranteedMagicEffectsElite.TryFind(x => x.Type == effectType, out var guaranteedMagicEffectElite)) + { + return guaranteedMagicEffectElite.Values; + } + } + else if (quality == ItemQuality.Exceptional) + { + if (legendaryInfo.GuaranteedMagicEffectsExceptional.Count() > 0 && legendaryInfo.GuaranteedMagicEffectsExceptional.TryFind(x => x.Type == effectType, out var guaranteedMagicEffectExceptional)) + { + return guaranteedMagicEffectExceptional.Values; + } + } + if (legendaryInfo.GuaranteedMagicEffects.TryFind(x => x.Type == effectType, out var guaranteedMagicEffect)) { return guaranteedMagicEffect.Values; diff --git a/EpicLoot/LootConfig.cs b/EpicLoot/LootConfig.cs index 9dd956e9c..998de78c1 100644 --- a/EpicLoot/LootConfig.cs +++ b/EpicLoot/LootConfig.cs @@ -9,6 +9,7 @@ public class LootDrop public string Item; public float Weight = 1; public float[] Rarity; + public float[] Quality; } [Serializable] @@ -19,6 +20,14 @@ public class LeveledLootDef public LootDrop[] Loot; } + [Serializable] + public class DistanceLootDef + { + public int Distance; + public float[][] Drops; + public LootDrop[] Loot; + } + [Serializable] public class LootTable { @@ -31,6 +40,7 @@ public class LootTable public LootDrop[] Loot2; public LootDrop[] Loot3; public List LeveledLoot = new List(); + public List DistanceLoot = new List(); } [Serializable] diff --git a/EpicLoot/LootFilter.cs b/EpicLoot/LootFilter.cs new file mode 100644 index 000000000..574036470 --- /dev/null +++ b/EpicLoot/LootFilter.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EpicLoot +{ + [Serializable] + public class LootFilter + { + public string Name; + public bool Whitelist; + public bool DropMaterials; + public List Items = new List(); + public List Rarities = new List(); + public List Quality = new List(); + public int Distance; + // public int Day; to be implemented for full accordance with CreatureLevelControl world level settings, that is, distance or day based + } + + [Serializable] + public class LootFiltersConfig + { + public List LootFilters = new List(); + } + + public class LootFilterDefinitions { + public static readonly List BlackLists = new List(); + public static readonly List WhiteLists = new List(); + + public static void Initialize(LootFiltersConfig _config) + { + foreach(var item in _config?.LootFilters) + { + if(item.Whitelist) + { + WhiteLists.Add(item); + } + else + { + BlackLists.Add(item); + } + } + } + + public static bool FilteredOut(string itemName, ItemRarity rarity, ItemQuality quality, int distance, out bool dropMaterials) + { + bool ItemMatches(LootFilter item, out bool dropMats) + { + dropMats = item.DropMaterials; + return item.Items.Contains(itemName) && item.Rarities.Contains(rarity) && item.Quality.Contains(quality) && distance >= item.Distance; + } + + foreach (var item in WhiteLists) + { + if(ItemMatches(item, out dropMaterials)) + { + return false; + } + } + + foreach (var item in BlackLists) + { + if (ItemMatches(item, out dropMaterials)) + { + return true; + } + } + + dropMaterials = false; + return false; + } + } +} diff --git a/EpicLoot/LootRoller.cs b/EpicLoot/LootRoller.cs index a36f64a53..e422a70cd 100644 --- a/EpicLoot/LootRoller.cs +++ b/EpicLoot/LootRoller.cs @@ -22,11 +22,13 @@ public static class LootRoller public static readonly Dictionary ItemSets = new Dictionary(); public static readonly Dictionary> LootTables = new Dictionary>(); + private static System.Random _random; private static WeightedRandomCollection> _weightedDropCountTable; private static WeightedRandomCollection _weightedLootTable; private static WeightedRandomCollection _weightedEffectTable; private static WeightedRandomCollection> _weightedEffectCountTable; private static WeightedRandomCollection> _weightedRarityTable; + private static WeightedRandomCollection> _weightedQualityTable; private static WeightedRandomCollection _weightedLegendaryTable; public static bool CheatRollingItem = false; public static int CheatEffectCount; @@ -39,13 +41,14 @@ public static void Initialize(LootConfig lootConfig) { Config = lootConfig; - var random = new System.Random(); - _weightedDropCountTable = new WeightedRandomCollection>(random); - _weightedLootTable = new WeightedRandomCollection(random); - _weightedEffectTable = new WeightedRandomCollection(random); - _weightedEffectCountTable = new WeightedRandomCollection>(random); - _weightedRarityTable = new WeightedRandomCollection>(random); - _weightedLegendaryTable = new WeightedRandomCollection(random); + _random = new System.Random(); + _weightedDropCountTable = new WeightedRandomCollection>(_random); + _weightedLootTable = new WeightedRandomCollection(_random); + _weightedEffectTable = new WeightedRandomCollection(_random); + _weightedEffectCountTable = new WeightedRandomCollection>(_random); + _weightedRarityTable = new WeightedRandomCollection>(_random); + _weightedQualityTable = new WeightedRandomCollection>(_random); + _weightedLegendaryTable = new WeightedRandomCollection(_random); ItemSets.Clear(); LootTables.Clear(); @@ -199,7 +202,9 @@ private static List RollLootTableInternal(LootTable lootTable, int l var luckFactor = GetLuckFactor(dropPoint); - var drops = GetDropsForLevel(lootTable, level); + int distanceFromWorldCenter = (int)new Vector3(dropPoint.x, 0, dropPoint.z).magnitude; + + var drops = GetDropsForLevelOrDistance(lootTable, level, distanceFromWorldCenter); if (drops.Count == 0) { return results; @@ -233,7 +238,7 @@ private static List RollLootTableInternal(LootTable lootTable, int l return results; } - var loot = GetLootForLevel(lootTable, level); + var loot = GetLootForLevelOrDistance(lootTable, level, distanceFromWorldCenter); if (loot == null) loot = new LootDrop[] { }; @@ -270,60 +275,96 @@ private static List RollLootTableInternal(LootTable lootTable, int l var itemName = !string.IsNullOrEmpty(lootDrop?.Item) ? lootDrop.Item : "Invalid Item Name"; var rarityLength = lootDrop?.Rarity?.Length != null ? lootDrop.Rarity.Length : -1; EpicLoot.Log($"Item: {itemName} - Rarity Count: {rarityLength} - Weight: {lootDrop.Weight}"); - - if (!cheatsActive && EpicLoot.ItemsToMaterialsDropRatio.Value > 0) + + var itemID = (CheatDisableGating) ? lootDrop.Item : GatedItemTypeHelper.GetGatedItemID(lootDrop.Item); + + var rarity = RollItemRarity(lootDrop, luckFactor); + var cheatLegendary = !string.IsNullOrEmpty(CheatForceLegendary); + if (cheatLegendary) + { + rarity = ItemRarity.Legendary; + } + var quality = RollItemQuality(lootDrop); + + var lootFilterForcedSacrifice = false; + if(LootFilterDefinitions.FilteredOut(itemName, rarity, quality, distanceFromWorldCenter, out lootFilterForcedSacrifice)) { - var clampedConvertRate = Mathf.Clamp(EpicLoot.ItemsToMaterialsDropRatio.Value, 0.0f, 1.0f); - var replaceWithMats = Random.Range(0.0f, 1.0f) < clampedConvertRate; - if (replaceWithMats) + if (!lootFilterForcedSacrifice) { - GameObject prefab = null; + EpicLoot.Log($"Item filtered out due to loot filters"); + continue; + } + } - try - { - prefab = ObjectDB.instance.GetItemPrefab(lootDrop.Item); - } - catch (Exception e) - { - EpicLoot.LogWarning($"Unable to get Prefab for [{lootDrop.Item}]. Continuing."); - EpicLoot.LogWarning($"Error: {e.Message}"); - } + bool ReplaceWithMats() + { + if (itemID == null) + { + EpicLoot.Log($"Item replaced with materials due to its id being null"); + return true; + } + + if (lootFilterForcedSacrifice) + { + EpicLoot.Log($"Item replaced with materials due to loot filters"); + return true; + } + + if (!cheatsActive && EpicLoot.ItemsToMaterialsDropRatio.Value > 0) + { + var clampedConvertRate = Mathf.Clamp(EpicLoot.ItemsToMaterialsDropRatio.Value, 0.0f, 1.0f); + return Random.Range(0.0f, 1.0f) < clampedConvertRate; + } + + return false; + }; + + if (ReplaceWithMats()) + { + GameObject prefab = null; + + try + { + prefab = ObjectDB.instance.GetItemPrefab(lootDrop.Item); + } + catch (Exception e) + { + EpicLoot.LogWarning($"Unable to get Prefab for [{lootDrop.Item}]. Continuing."); + EpicLoot.LogWarning($"Error: {e.Message}"); + } - if (prefab != null) + if (prefab != null) + { + var itemType = prefab.GetComponent().m_itemData.m_shared.m_itemType; + var disenchantProducts = EnchantCostsHelper.GetSacrificeProducts(true, itemType, rarity); + if (disenchantProducts != null) { - var rarity = RollItemRarity(lootDrop, luckFactor); - var itemType = prefab.GetComponent().m_itemData.m_shared.m_itemType; - var disenchantProducts = EnchantCostsHelper.GetSacrificeProducts(true, itemType, rarity); - if (disenchantProducts != null) + foreach (var itemAmountConfig in disenchantProducts) { - foreach (var itemAmountConfig in disenchantProducts) + GameObject materialPrefab = null; + try + { + materialPrefab = ObjectDB.instance.GetItemPrefab(itemAmountConfig.Item); + } + catch (Exception e) { - GameObject materialPrefab = null; - try - { - materialPrefab = ObjectDB.instance.GetItemPrefab(itemAmountConfig.Item); - } - catch (Exception e) - { - EpicLoot.LogWarning($"Unable to get Disenchant Product Prefab for [{itemAmountConfig?.Item ?? "Invalid Item"}]. Continuing."); - EpicLoot.LogWarning($"Error: {e.Message}"); - } - - if (materialPrefab == null) continue; - var materialItem = SpawnLootForDrop(materialPrefab, dropPoint, true); - var materialItemDrop = materialItem.GetComponent(); - materialItemDrop.m_itemData.m_stack = itemAmountConfig.Amount; - if (materialItemDrop.m_itemData.IsMagicCraftingMaterial()) - materialItemDrop.m_itemData.m_variant = EpicLoot.GetRarityIconIndex(rarity); - results.Add(materialItem); + EpicLoot.LogWarning($"Unable to get Disenchant Product Prefab for [{itemAmountConfig?.Item ?? "Invalid Item"}]. Continuing."); + EpicLoot.LogWarning($"Error: {e.Message}"); } + + if (materialPrefab == null) continue; + var materialItem = SpawnLootForDrop(materialPrefab, dropPoint, true); + var materialItemDrop = materialItem.GetComponent(); + materialItemDrop.m_itemData.m_stack = itemAmountConfig.Amount; + if (materialItemDrop.m_itemData.IsMagicCraftingMaterial()) + materialItemDrop.m_itemData.m_variant = EpicLoot.GetRarityIconIndex(rarity); + results.Add(materialItem); } } - - continue; } + + continue; } - var itemID = (CheatDisableGating) ? lootDrop.Item : GatedItemTypeHelper.GetGatedItemID(lootDrop.Item); GameObject itemPrefab = null; @@ -348,16 +389,21 @@ private static List RollLootTableInternal(LootTable lootTable, int l { var itemData = itemDrop.m_itemData; var magicItemComponent = itemData.Data().GetOrCreate(); - var magicItem = RollMagicItem(lootDrop, itemData, luckFactor); + var magicItem = RollMagicItem(rarity, quality, itemData, luckFactor); if (CheatForceMagicEffect) { - AddDebugMagicEffects(magicItem); + AddDebugMagicEffects(magicItem, itemData.m_shared.m_name); } magicItemComponent.SetMagicItem(magicItem); itemDrop.m_itemData = itemData; itemDrop.Save(); InitializeMagicItem(itemData); } + else + { + EpicLoot.LogWarning($"Warning: can't make item magic due to a) item {itemName} can't be magic: {!EpicLoot.CanBeMagicItem(itemDrop.m_itemData)} b) loot drop rarity is null or empty: {ArrayUtils.IsNullOrEmpty(lootDrop.Rarity)}"); + } + results.Add(item); } @@ -375,7 +421,7 @@ public static GameObject SpawnLootForDrop(GameObject itemPrefab, Vector3 dropPoi private static LootDrop ResolveLootDrop(LootDrop lootDrop) { - var result = new LootDrop { Item = lootDrop.Item, Rarity = ArrayUtils.Copy(lootDrop.Rarity), Weight = lootDrop.Weight }; + var result = new LootDrop { Item = lootDrop.Item, Rarity = ArrayUtils.Copy(lootDrop.Rarity), Quality = ArrayUtils.Copy(lootDrop.Quality), Weight = lootDrop.Weight }; var needsResolve = true; while (needsResolve) { @@ -393,6 +439,10 @@ private static LootDrop ResolveLootDrop(LootDrop lootDrop) { result.Rarity = ArrayUtils.Copy(itemSetResult.Rarity); } + if (ArrayUtils.IsNullOrEmpty(result.Quality)) + { + result.Quality = ArrayUtils.Copy(itemSetResult.Quality); + } } else if (IsLootTableRefence(result.Item, out var lootList)) { @@ -408,6 +458,10 @@ private static LootDrop ResolveLootDrop(LootDrop lootDrop) { result.Rarity = ArrayUtils.Copy(referenceResult.Rarity); } + if (ArrayUtils.IsNullOrEmpty(result.Quality)) + { + result.Quality = ArrayUtils.Copy(referenceResult.Quality); + } } else { @@ -440,7 +494,7 @@ private static bool IsLootTableRefence(string lootDropItem, out LootDrop[] lootL var lootTable = LootTables[objectName].FirstOrDefault(); if (lootTable != null) { - lootList = GetLootForLevel(lootTable, level); + lootList = GetLootForLevelOrDistance(lootTable, level, 0); return true; } @@ -450,13 +504,7 @@ private static bool IsLootTableRefence(string lootDropItem, out LootDrop[] lootL return false; } - public static MagicItem RollMagicItem(LootDrop lootDrop, ItemDrop.ItemData baseItem, float luckFactor) - { - var rarity = RollItemRarity(lootDrop, luckFactor); - return RollMagicItem(rarity, baseItem, luckFactor); - } - - public static MagicItem RollMagicItem(ItemRarity rarity, ItemDrop.ItemData baseItem, float luckFactor) + public static MagicItem RollMagicItem(ItemRarity rarity, ItemQuality quality, ItemDrop.ItemData baseItem, float luckFactor) { var cheatLegendary = !string.IsNullOrEmpty(CheatForceLegendary); if (cheatLegendary) @@ -464,7 +512,7 @@ public static MagicItem RollMagicItem(ItemRarity rarity, ItemDrop.ItemData baseI rarity = ItemRarity.Legendary; } - var magicItem = new MagicItem { Rarity = rarity }; + var magicItem = new MagicItem { Rarity = rarity, Quality = quality, ItemName = baseItem.m_shared.m_name }; var effectCount = CheatEffectCount >= 1 ? CheatEffectCount : RollEffectCountPerRarity(magicItem.Rarity); @@ -503,7 +551,21 @@ public static MagicItem RollMagicItem(ItemRarity rarity, ItemDrop.ItemData baseI effectCount = legendary.GuaranteedEffectCount; } - foreach (var guaranteedMagicEffect in legendary.GuaranteedMagicEffects) + List guaranteedMagicEffects; + if (quality == ItemQuality.Elite && legendary.GuaranteedMagicEffectsElite.Count() > 0) + { + guaranteedMagicEffects = legendary.GuaranteedMagicEffectsElite; + } + else if (quality == ItemQuality.Exceptional && legendary.GuaranteedMagicEffectsExceptional.Count() > 0) + { + guaranteedMagicEffects = legendary.GuaranteedMagicEffectsExceptional; + } + else + { + guaranteedMagicEffects = legendary.GuaranteedMagicEffects; + } + + foreach (var guaranteedMagicEffect in guaranteedMagicEffects) { var effectDef = MagicItemEffectDefinitions.Get(guaranteedMagicEffect.Type); if (effectDef == null) @@ -512,7 +574,7 @@ public static MagicItem RollMagicItem(ItemRarity rarity, ItemDrop.ItemData baseI continue; } - var effect = RollEffect(effectDef, ItemRarity.Legendary, guaranteedMagicEffect.Values); + var effect = RollEffect(effectDef, ItemRarity.Legendary, magicItem.Quality, baseItem.m_shared.m_name, guaranteedMagicEffect.Values); magicItem.Effects.Add(effect); effectCount--; } @@ -532,7 +594,7 @@ public static MagicItem RollMagicItem(ItemRarity rarity, ItemDrop.ItemData baseI _weightedEffectTable.Setup(availableEffects, x => x.SelectionWeight); var effectDef = _weightedEffectTable.Roll(); - var effect = RollEffect(effectDef, magicItem.Rarity); + var effect = RollEffect(effectDef, magicItem.Rarity, magicItem.Quality, baseItem.m_shared.m_name); magicItem.Effects.Add(effect); } @@ -612,10 +674,10 @@ public static List> GetEffectCountsPerRarity(ItemRarity return result; } - public static MagicItemEffect RollEffect(MagicItemEffectDefinition effectDef, ItemRarity itemRarity, MagicItemEffectDefinition.ValueDef valuesOverride = null) + public static MagicItemEffect RollEffect(MagicItemEffectDefinition effectDef, ItemRarity itemRarity, ItemQuality quality, string itemName, MagicItemEffectDefinition.ValueDef valuesOverride = null) { float value = MagicItemEffect.DefaultValue; - var valuesDef = valuesOverride ?? effectDef.GetValuesForRarity(itemRarity); + var valuesDef = valuesOverride ?? effectDef.GetValuesForRarity(itemRarity, itemName, quality); if (valuesDef != null) { value = valuesDef.MinValue; @@ -623,14 +685,26 @@ public static MagicItemEffect RollEffect(MagicItemEffectDefinition effectDef, It { EpicLoot.Log($"RollEffect: {effectDef.Type} {itemRarity} value={value} (min={valuesDef.MinValue} max={valuesDef.MaxValue})"); var incrementCount = (int)((valuesDef.MaxValue - valuesDef.MinValue) / valuesDef.Increment); - value = valuesDef.MinValue + (Random.Range(0, incrementCount + 1) * valuesDef.Increment); + + double v; + if (EpicLoot.EffectValueRollDistribution.Value == EffectValueRollDistributionTypes.TendsToLowAverage) + { + v = Math.Pow(_random.NextDouble() * _random.NextDouble(), 0.7); + } + else + { + v = _random.NextDouble(); + } + + value = valuesDef.MinValue + (int)(v * (incrementCount + 1)) * valuesDef.Increment; } } return new MagicItemEffect(effectDef.Type, value); } - public static List RollEffects(List availableEffects, ItemRarity itemRarity, int count, bool removeOnSelect = true) + + public static List RollEffects(List availableEffects, ItemRarity itemRarity, ItemQuality quality, string itemName, int count, bool removeOnSelect = true) { var results = new List(); @@ -644,7 +718,7 @@ public static List RollEffects(List EpicLoot.LogError($"EffectDef was null! RollEffects({itemRarity}, {count})"); continue; } - results.Add(RollEffect(effectDef, itemRarity)); + results.Add(RollEffect(effectDef, itemRarity, quality, itemName)); } return results; @@ -662,6 +736,19 @@ public static ItemRarity RollItemRarity(LootDrop lootDrop, float luckFactor) _weightedRarityTable.Setup(rarityWeights, x => x.Value); return _weightedRarityTable.Roll().Key; } + public static ItemQuality RollItemQuality(LootDrop lootDrop) + { + if (lootDrop.Quality == null || lootDrop.Quality.Length == 0) + { + return ItemQuality.Normal; + } + + var qualityWeights = GetQualityWeights(lootDrop.Quality); + + _weightedQualityTable.Setup(qualityWeights, x => x.Value); + var quality = _weightedQualityTable.Roll().Key; + return quality; + } public static Dictionary GetRarityWeights(float[] rarity, float luckFactor) { @@ -673,7 +760,28 @@ public static Dictionary GetRarityWeights(float[] rarity, flo { ItemRarity.Legendary, rarity.Length >= 4 ? rarity[3] : 0 } }; - return ModifyRarityByLuck(rarityWeights, luckFactor); + for (var r = ItemRarity.Legendary; r >= ItemRarity.Magic; r--) + { + if (rarityWeights[r] > 0) + { + rarityWeights[r] = rarityWeights[r] * (1 + luckFactor); + break; + } + } + + return rarityWeights; + } + + public static Dictionary GetQualityWeights(float[] quality) + { + var qualityWeights = new Dictionary() + { + { ItemQuality.Normal, quality.Length >= 1 ? quality[0] : 0 }, + { ItemQuality.Exceptional, quality.Length >= 2 ? quality[1] : 0 }, + { ItemQuality.Elite, quality.Length >= 3 ? quality[2] : 0 } + }; + + return qualityWeights; } public static List GetLootTable(string objectName) @@ -689,8 +797,25 @@ public static List GetLootTable(string objectName) return results; } - public static List> GetDropsForLevel([NotNull] LootTable lootTable, int level, bool useNextHighestIfNotPresent = true) + public static List> GetDropsForLevelOrDistance([NotNull] LootTable lootTable, int level, int distance, bool useNextHighestIfNotPresent = true) { + if (lootTable.DistanceLoot.Count > 0) + { + var drops = lootTable.Drops; + + int maxDistanceUsed = -1; + foreach (var distanceLoot in lootTable.DistanceLoot) + { + if (distance >= distanceLoot.Distance && distanceLoot.Distance > maxDistanceUsed) + { + drops = distanceLoot.Drops; + maxDistanceUsed = distanceLoot.Distance; + } + } + + return ToDropList(drops); + } + if (level == 3 && !ArrayUtils.IsNullOrEmpty(lootTable.Drops3)) { if (lootTable.LeveledLoot.Any(x => x.Level == level)) @@ -741,8 +866,25 @@ private static List> ToDropList(float[][] drops) return drops.Select(x => new KeyValuePair((int) x[0], x[1])).ToList(); } - public static LootDrop[] GetLootForLevel([NotNull] LootTable lootTable, int level, bool useNextHighestIfNotPresent = true) + public static LootDrop[] GetLootForLevelOrDistance([NotNull] LootTable lootTable, int level, int distance, bool useNextHighestIfNotPresent = true) { + if (lootTable.DistanceLoot.Count > 0) + { + var loot = lootTable.Loot; + + int maxDistanceUsed = -1; + foreach (var distanceLoot in lootTable.DistanceLoot) + { + if (distance >= distanceLoot.Distance && distanceLoot.Distance > maxDistanceUsed) + { + loot = distanceLoot.Loot; + maxDistanceUsed = distanceLoot.Distance; + } + } + + return loot.ToArray(); + } + if (level == 3 && !ArrayUtils.IsNullOrEmpty(lootTable.Loot3)) { if (lootTable.LeveledLoot.Any(x => x.Level == level)) @@ -818,7 +960,7 @@ public static List RollAugmentEffects(ItemDrop.ItemData item, M for (var i = 0; i < augmentChoices && i < availableEffects.Count; i++) { - var newEffect = RollEffects(availableEffects, rarity, 1, false).FirstOrDefault(); + var newEffect = RollEffects(availableEffects, rarity, magicItem.Quality, item.m_shared.m_name, 1, false).FirstOrDefault(); if (newEffect == null) { EpicLoot.LogError($"Rolled a null effect: item:{item.m_shared.m_name}, index:{effectIndex}"); @@ -837,12 +979,12 @@ public static List RollAugmentEffects(ItemDrop.ItemData item, M return results; } - public static void AddDebugMagicEffects(MagicItem item) + public static void AddDebugMagicEffects(MagicItem item, string itemName) { if (!string.IsNullOrEmpty(ForcedMagicEffect) && !item.HasEffect(ForcedMagicEffect)) { EpicLoot.Log($"AddDebugMagicEffect {ForcedMagicEffect}"); - item.Effects.Add(RollEffect(MagicItemEffectDefinitions.Get(ForcedMagicEffect), item.Rarity)); + item.Effects.Add(RollEffect(MagicItemEffectDefinitions.Get(ForcedMagicEffect), item.Rarity, item.Quality, itemName)); } } @@ -878,41 +1020,10 @@ public static void DebugLuckFactor() } } - public static Dictionary ModifyRarityByLuck(IReadOnlyDictionary rarityWeights, float luckFactor = 0) - { - var results = new Dictionary(); - for (var rarity = ItemRarity.Magic; rarity <= ItemRarity.Legendary; rarity++) - { - var skewFactor = GetSkewFactor(rarity); - results.Add(rarity, rarityWeights[rarity] * GetSkewedLuckFactor(luckFactor, skewFactor)); - } - - return results; - } - - public static float GetSkewFactor(ItemRarity rarity) - { - switch (rarity) - { - case ItemRarity.Magic: return -0.2f; - case ItemRarity.Rare: return 0.0f; - case ItemRarity.Epic: return 0.2f; - case ItemRarity.Legendary: return 1; - case ItemRarity.Mythic: return 1.1f; - default: - throw new ArgumentOutOfRangeException(nameof(rarity), rarity, null); - } - } - - public static float GetSkewedLuckFactor(float luckFactor, float skewFactor) - { - return Mathf.Max(0, 1 + luckFactor * skewFactor); - } - public static void PrintLuckTest(string lootTableName, float luckFactor) { var lootTable = GetLootTable(lootTableName)[0]; - var lootDrop = GetLootForLevel(lootTable, 1)[0]; + var lootDrop = GetLootForLevelOrDistance(lootTable, 1, 0)[0]; lootDrop = ResolveLootDrop(lootDrop); var rarityBase = GetRarityWeights(lootDrop.Rarity, 0); var rarityLuck = GetRarityWeights(lootDrop.Rarity, luckFactor); @@ -945,12 +1056,12 @@ public static void PrintLuckTest(string lootTableName, float luckFactor) Debug.LogWarning(sb.ToString()); } - public static void PrintLootResolutionTest(string lootTableName, int level, int itemIndex) + public static void PrintLootResolutionTest(string lootTableName, int level, int distance, int itemIndex) { - Debug.LogWarning($"{lootTableName}:{level}:{itemIndex}"); + Debug.LogWarning($"{lootTableName}:{level}:{distance}:{itemIndex}"); var lootTable = GetLootTable(lootTableName)[0]; - var lootDrop = GetLootForLevel(lootTable, level)[itemIndex]; + var lootDrop = GetLootForLevelOrDistance(lootTable, level, distance)[itemIndex]; lootDrop = ResolveLootDrop(lootDrop); var rarity = lootDrop.Rarity; diff --git a/EpicLoot/MagicItem.cs b/EpicLoot/MagicItem.cs index 0a8743a28..61befd400 100644 --- a/EpicLoot/MagicItem.cs +++ b/EpicLoot/MagicItem.cs @@ -16,6 +16,13 @@ public enum ItemRarity Mythic } + public enum ItemQuality + { + Normal, + Exceptional, + Elite + } + [Serializable] public class MagicItemEffect { @@ -39,7 +46,7 @@ public MagicItemEffect(string type, float value = DefaultValue) [Serializable] public class MagicItem { - public int Version = 2; + public int Version = 3; public ItemRarity Rarity; public List Effects = new List(); public string TypeNameOverride; @@ -48,6 +55,8 @@ public class MagicItem public string DisplayName; public string LegendaryID; public string SetID; + public string ItemName; + public ItemQuality Quality; public string GetItemTypeName(ItemDrop.ItemData baseItem) { @@ -71,7 +80,7 @@ public string GetTooltip() { var effect = Effects[index]; var pip = EpicLoot.GetMagicEffectPip(IsEffectAugmented(index)); - tooltip.AppendLine($"{pip} {GetEffectText(effect, Rarity, showRange)}"); + tooltip.AppendLine($"{pip} {GetEffectText(effect, Rarity, Quality, ItemName, showRange, LegendaryID)}"); } tooltip.Append($""); @@ -127,11 +136,26 @@ public static string GetEffectText(MagicItemEffectDefinition effectDef, float va return result; } - public static string GetEffectText(MagicItemEffect effect, ItemRarity rarity, bool showRange, string legendaryID, MagicItemEffectDefinition.ValueDef valuesOverride) + public static string GetEffectText(MagicItemEffect effect, ItemRarity rarity, ItemQuality quality, string itemName, bool showRange, string legendaryID, MagicItemEffectDefinition.ValueDef valuesOverride) { var effectDef = MagicItemEffectDefinitions.Get(effect.EffectType); var result = GetEffectText(effectDef, effect.EffectValue); - var values = valuesOverride ?? (string.IsNullOrEmpty(legendaryID) ? effectDef.GetValuesForRarity(rarity) : UniqueLegendaryHelper.GetLegendaryEffectValues(legendaryID, effect.EffectType)); + MagicItemEffectDefinition.ValueDef values = null; + if (valuesOverride != null) + { + values = valuesOverride; + } + else + { + if (!string.IsNullOrEmpty(legendaryID)) + { + values = UniqueLegendaryHelper.GetLegendaryEffectValues(legendaryID, effect.EffectType, quality); + } + if (values == null) + { + values = effectDef.GetValuesForRarity(rarity, itemName, quality); + } + } if (showRange && values != null) { if (!Mathf.Approximately(values.MinValue, values.MaxValue)) @@ -142,14 +166,9 @@ public static string GetEffectText(MagicItemEffect effect, ItemRarity rarity, bo return result; } - public static string GetEffectText(MagicItemEffect effect, ItemRarity rarity, bool showRange, string legendaryID = null) - { - return GetEffectText(effect, rarity, showRange, legendaryID, null); - } - - public static string GetEffectText(MagicItemEffect effect, MagicItemEffectDefinition.ValueDef valuesOverride) + public static string GetEffectText(MagicItemEffect effect, ItemRarity rarity, ItemQuality quality, string itemName, bool showRange, string legendaryID = null) { - return GetEffectText(effect, ItemRarity.Legendary, false, null, valuesOverride); + return GetEffectText(effect, rarity, quality, itemName, showRange, legendaryID, null); } public void ReplaceEffect(int index, MagicItemEffect newEffect) diff --git a/EpicLoot/MagicItemComponent.cs b/EpicLoot/MagicItemComponent.cs index e1c5a1af6..552a7f647 100644 --- a/EpicLoot/MagicItemComponent.cs +++ b/EpicLoot/MagicItemComponent.cs @@ -264,13 +264,33 @@ public static string GetDisplayName(this ItemDrop.ItemData itemData) public static string GetDecoratedName(this ItemDrop.ItemData itemData, string colorOverride = null) { var color = "white"; + + var magicItem = itemData.GetMagicItem(); + var qualityStr = ""; + if (magicItem != null) + { + var quality = magicItem.Quality; + if (quality == ItemQuality.Elite) + { + qualityStr = Localization.instance.Localize("$mod_epicloot_elite"); + } + else if (quality == ItemQuality.Exceptional) + { + qualityStr = Localization.instance.Localize("$mod_epicloot_exceptional"); + } + if (qualityStr != "") + { + qualityStr = qualityStr + " "; + } + } + var name = GetDisplayName(itemData); if (!string.IsNullOrEmpty(colorOverride)) { color = colorOverride; } - else if (itemData.IsMagic(out var magicItem)) + else if (magicItem != null) { color = magicItem.GetColorString(); } @@ -279,7 +299,7 @@ public static string GetDecoratedName(this ItemDrop.ItemData itemData, string co color = itemData.GetCraftingMaterialRarityColor(); } - return $"{name}"; + return $"{qualityStr}{name}"; } public static string GetDescription(this ItemDrop.ItemData itemData) diff --git a/EpicLoot/MagicItemEffectDefinition.cs b/EpicLoot/MagicItemEffectDefinition.cs index 7668918ef..6d8cd43a1 100644 --- a/EpicLoot/MagicItemEffectDefinition.cs +++ b/EpicLoot/MagicItemEffectDefinition.cs @@ -300,12 +300,24 @@ public class ValuesPerRarityDef public ValueDef Mythic; } + [Serializable] + public class ValuesPerItemNameDef + { + public List ItemNames = new List(); + public ValuesPerRarityDef ValuesPerRarity = new ValuesPerRarityDef(); + public ValuesPerRarityDef ValuesPerRarityExceptional = new ValuesPerRarityDef(); + public ValuesPerRarityDef ValuesPerRarityElite = new ValuesPerRarityDef(); + } + public string Type { get; set; } public string DisplayText = ""; public string Description = ""; public MagicItemEffectRequirements Requirements = new MagicItemEffectRequirements(); public ValuesPerRarityDef ValuesPerRarity = new ValuesPerRarityDef(); + public ValuesPerRarityDef ValuesPerRarityExceptional = new ValuesPerRarityDef(); + public ValuesPerRarityDef ValuesPerRarityElite = new ValuesPerRarityDef(); + public List ValuesPerItemName = new List(); public float SelectionWeight = 1; public bool CanBeAugmented = true; public bool CanBeDisenchanted = true; @@ -336,20 +348,51 @@ public bool HasRarityValues() return ValuesPerRarity.Magic != null && ValuesPerRarity.Epic != null && ValuesPerRarity.Rare != null && ValuesPerRarity.Legendary != null; } - public ValueDef GetValuesForRarity(ItemRarity itemRarity) + public ValueDef GetValuesForRarity(ItemRarity itemRarity, string itemName, ItemQuality quality) { - switch (itemRarity) - { - case ItemRarity.Magic: return ValuesPerRarity.Magic; - case ItemRarity.Rare: return ValuesPerRarity.Rare; - case ItemRarity.Epic: return ValuesPerRarity.Epic; - case ItemRarity.Legendary: return ValuesPerRarity.Legendary; - case ItemRarity.Mythic: - // TODO: Mythic Hookup - return null;//ValuesPerRarity.Mythic; - default: - throw new ArgumentOutOfRangeException(nameof(itemRarity), itemRarity, null); + ValueDef ValueForQuality(ValueDef normal, ValueDef exceptional, ValueDef elite) + { + if (quality == ItemQuality.Elite && elite != null) + { + return elite; + } + if (quality == ItemQuality.Exceptional && exceptional != null) + { + return exceptional; + } + return normal; } + + ValueDef ValueForRarity(ValuesPerRarityDef normal, ValuesPerRarityDef exceptional, ValuesPerRarityDef elite) + { + switch (itemRarity) + { + case ItemRarity.Magic: return ValueForQuality(normal?.Magic, exceptional?.Magic, elite?.Magic); + case ItemRarity.Rare: return ValueForQuality(normal?.Rare, exceptional?.Rare, elite?.Rare); + case ItemRarity.Epic: return ValueForQuality(normal?.Epic, exceptional?.Epic, elite?.Epic); + case ItemRarity.Legendary: return ValueForQuality(normal?.Legendary, exceptional?.Legendary, elite?.Legendary); + case ItemRarity.Mythic: + // TODO: Mythic Hookup + return null;//ValuesPerRarity.Mythic; + default: + throw new ArgumentOutOfRangeException(nameof(itemRarity), itemRarity, null); + } + } + + if (string.IsNullOrEmpty(itemName) || ValuesPerItemName == null) + { + return ValueForRarity(ValuesPerRarity, ValuesPerRarityExceptional, ValuesPerRarityElite); + } + + for (var i = 0; i < ValuesPerItemName.Count; i++) + { + if (ValuesPerItemName[i].ItemNames.Contains(itemName)) + { + return ValueForRarity(ValuesPerItemName[i].ValuesPerRarity, ValuesPerItemName[i].ValuesPerRarityExceptional, ValuesPerItemName[i].ValuesPerRarityElite); + } + } + + return ValueForRarity(ValuesPerRarity, ValuesPerRarityExceptional, ValuesPerRarityElite); } } @@ -426,7 +469,7 @@ public static bool IsValuelessEffect(string effectType, ItemRarity rarity) return false; } - return effectDef.GetValuesForRarity(rarity) == null; + return effectDef.GetValuesForRarity(rarity, null, ItemQuality.Normal) == null; } } } diff --git a/EpicLoot/Multiplayer_Player_Patch.cs b/EpicLoot/Multiplayer_Player_Patch.cs index 58e3f3b4c..e9b9cf3d3 100644 --- a/EpicLoot/Multiplayer_Player_Patch.cs +++ b/EpicLoot/Multiplayer_Player_Patch.cs @@ -119,7 +119,7 @@ private static bool DoCheck(Player player, ZDO zdo, string equipKey, string lege itemData = targetItemData.Clone(); itemData.m_durability = float.PositiveInfinity; var magicItemComponent = itemData.Data().GetOrCreate(); - var stubMagicItem = new MagicItem { Rarity = ItemRarity.Legendary, LegendaryID = zdoLegendaryID }; + var stubMagicItem = new MagicItem { Rarity = ItemRarity.Legendary, ItemName = itemData.m_shared.m_name, LegendaryID = zdoLegendaryID }; magicItemComponent.SetMagicItem(stubMagicItem); ForceResetVisEquipment(player, itemData); diff --git a/EpicLoot/Terminal_Patch.cs b/EpicLoot/Terminal_Patch.cs index f2720211e..35b559b17 100644 --- a/EpicLoot/Terminal_Patch.cs +++ b/EpicLoot/Terminal_Patch.cs @@ -186,7 +186,8 @@ public static void Postfix() var lootTable = args.Length > 1 ? args[1] : "Greydwarf"; var level = args.Length > 2 ? int.Parse(args[2]) : 1; var itemIndex = args.Length > 3 ? int.Parse(args[3]) : 0; - LootRoller.PrintLootResolutionTest(lootTable, level, itemIndex); + var distance = args.Length > 4 ? int.Parse(args[4]) : 0; + LootRoller.PrintLootResolutionTest(lootTable, level, distance, itemIndex); })); new Terminal.ConsoleCommand("resetcooldowns", "", (args => { @@ -618,7 +619,7 @@ private static void ReplaceMagicEffect(ItemDrop.ItemData itemData, MagicItem mag return; } - var replacementEffect = LootRoller.RollEffect(replacementEffectDef, magicItem.Rarity); + var replacementEffect = LootRoller.RollEffect(replacementEffectDef, magicItem.Rarity, magicItem.Quality, itemData.m_shared.m_name); magicItem.Effects[index] = replacementEffect; itemData.SaveMagicItem(magicItem); } diff --git a/EpicLoot/TextsDialog_Patch.cs b/EpicLoot/TextsDialog_Patch.cs index 195a7159a..799b0f8cd 100644 --- a/EpicLoot/TextsDialog_Patch.cs +++ b/EpicLoot/TextsDialog_Patch.cs @@ -64,7 +64,8 @@ public static void AddMagicEffectsPage(TextsDialog textsDialog, Player player) { var effect = entry2.Key; var item = entry2.Value; - t.AppendLine($" - {MagicItem.GetEffectText(effect, item.GetRarity(), false)} ({item.GetDecoratedName()})"); + var magicItem = item.GetMagicItem(); + t.AppendLine($" - {MagicItem.GetEffectText(effect, item.GetRarity(), magicItem != null ? magicItem.Quality : ItemQuality.Normal, item.m_shared.m_name, false, magicItem?.LegendaryID)} ({item.GetDecoratedName()})"); } t.AppendLine(); diff --git a/EpicLoot/lootfilters.json b/EpicLoot/lootfilters.json new file mode 100644 index 000000000..e28949c93 --- /dev/null +++ b/EpicLoot/lootfilters.json @@ -0,0 +1,108 @@ +{ + "LootFilters": [ + // example + // { + // "Name": "Magic loot filter", - name of the filter for the reference + // "Whitelist": false, - if set to true, the filter works in the opposite way, that is, the matching item will be dropped even if should be filtered out by other loot filters + // "DropMaterials": true, - if set to true, the item materials will be dropped instead of the item itself, if set to false, there will be no drop at all + // "Items": [], - list of item names (check "Item" fields used in loottables.json) + // "Rarities" : [ + // "Magic", + // "Rare", + // "Epic", + // "Legendary" + // ], + // "Quality": [ + // "Normal", + // "Exceptional", + // "Elite" + // ], + // "Distance": 0, - minimal distance from the center of the world for the filter to be applied + // "Day": 0, - minimal day in the world for the filter to be applied, NOT IMPLEMENTED YET + // } + { + "Name": "Tier0Everything - Normal", + "DropMaterials": true, + "Items": [ + "Club", + "AxeStone", + "Torch", + "Hammer", + "Hoe", + "ArmorRagsLegs", + "ArmorRagsChest", + "ShieldWood", + "ShieldWoodTower" + ], + "Rarities": [ + "Magic", + "Rare", + "Epic" + ], + "Quality": [ + "Normal" + ], + "Distance": 3500 + }, + { + "Name": "Tier1Everything - Normal", + "DropMaterials": true, + "Items": [ + "AxeFlint", + "SpearFlint", + "KnifeFlint", + "Bow", + "ArmorLeatherLegs", + "ArmorLeatherChest", + "HelmetLeather", + "CapeDeerHide", + "PickaxeAntler", + "ShieldBoneTower" + ], + "Rarities": [ + "Magic", + "Rare", + "Epic" + ], + "Quality": [ + "Normal" + ], + "Distance": 3500 + }, + { + "Name": "Tier2Everything - Normal", + "DropMaterials": true, + "Items": [ + "ArmorTrollLeatherLegs", + "ArmorTrollLeatherChest", + "HelmetTrollLeather", + "CapeTrollHide", + "KnifeCopper", + "SledgeStagbreaker", + "SwordBronze", + "AxeBronze", + "MaceBronze", + "AtgeirBronze", + "SpearBronze", + "BowFineWood", + "KnifeChitin", + "SpearChitin", + "ArmorBronzeLegs", + "ArmorBronzeChest", + "HelmetBronze", + "ShieldBronzeBuckler", + "PickaxeBronze", + "Cultivator" + ], + "Rarities": [ + "Magic", + "Rare", + "Epic" + ], + "Quality": [ + "Normal" + ], + "Distance": 5000 + } + ] +} \ No newline at end of file diff --git a/EpicLoot/loottables.json b/EpicLoot/loottables.json index 71a60f7ac..327cfba75 100644 --- a/EpicLoot/loottables.json +++ b/EpicLoot/loottables.json @@ -1456,6 +1456,16 @@ "Loot": [ { "Item": "Tier0Everything", "Weight": 4, "Rarity": [ 97, 2, 1, 0] }, { "Item": "Tier1Everything", "Weight": 1, "Rarity": [ 97, 2, 1, 0] } + ], + "DistanceLoot": [ + { + "Distance": 4000, + "Drops": [ [0, 40], [1, 38], [2, 20], [3, 2] ], + "Loot": [ + { "Item": "Tier0Everything", "Weight": 4, "Rarity": [ 97, 2, 1, 0] }, + { "Item": "Tier1Everything", "Weight": 1, "Rarity": [ 97, 2, 1, 0] } + ] + } ] }, //TreasureChest_blackforest diff --git a/EpicLoot/translations.json b/EpicLoot/translations.json index 21d0c0611..6825c0608 100644 --- a/EpicLoot/translations.json +++ b/EpicLoot/translations.json @@ -99,7 +99,7 @@ "mod_epicloot_effectsperlevel": "Upgrades:", "mod_epicloot_upgrademessage": "$1 upgraded to level $2", "mod_epicloot_unlockmessage": "$1 upgraded to level $2", - "mod_epicloot_bonus": "Bonus!", + "mod_epicloot_bonus": "Bonus!", "mod_epicloot_featureinfo_none": "Select a feature from the list to the left to view upgrade options.", "mod_epicloot_featureinfo_sacrifice": "Sacrifice magic items and trophies, destroying them to produce enchanting materials.\n\nWhen upgraded, gain a chance to recieve double the items normally gained from the sacrifice.", @@ -1254,5 +1254,7 @@ "mod_epicloot_rare": "Rare", "mod_epicloot_epic": "Epic", "mod_epicloot_legendary": "Legendary", - "mod_epicloot_legendarysetlabel": "Legendary Set Item" + "mod_epicloot_legendarysetlabel": "Legendary Set Item", + "mod_epicloot_exceptional": "Exceptional", + "mod_epicloot_elite": "Elite" }