From 5e75dccf629a9a7c113e1179a34706c55688a801 Mon Sep 17 00:00:00 2001 From: Annan Fay Yearian Date: Sun, 24 Oct 2021 22:56:38 +0200 Subject: [PATCH] Added stacking, arrows can now each have their own quality. Also fixed many bugs and overall improvements. --- CraftingSkill/CraftingConfig.cs | 5 +- CraftingSkill/CraftingSkill.cs | 210 +++++++++++++++++++---- CraftingSkill/Properties/AssemblyInfo.cs | 4 +- CraftingSkill/Quality.cs | 49 ++++-- CraftingSkill/QualityComponent.cs | 77 ++++++--- CraftingSkill/QualityTier.cs | 16 +- CraftingSkill/StackableQuality.cs | 140 +++++++++++++-- 7 files changed, 405 insertions(+), 96 deletions(-) diff --git a/CraftingSkill/CraftingConfig.cs b/CraftingSkill/CraftingConfig.cs index f593c8b..ed33e02 100644 --- a/CraftingSkill/CraftingConfig.cs +++ b/CraftingSkill/CraftingConfig.cs @@ -18,6 +18,7 @@ public class CraftingConfig { private ConfigVariable __QuantisedQuality = new ConfigVariable("General", "QuantisedQuality", false); private ConfigVariable __StochasticVariance = new ConfigVariable("General", "StochasticVariance", 0.0f); + private ConfigVariable __DebugTooltips = new ConfigVariable("General", "DebugTooltips", false); // Experience for crafting private ConfigVariable __ExpScapeTier = new ConfigVariable("ExpGain", "ExpScapeTier", 1.0f); @@ -54,6 +55,7 @@ public class CraftingConfig // Utility getters public bool QuantisedQuality {get => __QuantisedQuality.Value; } public float StochasticVariance {get => __StochasticVariance.Value; } + public bool DebugTooltips {get => __DebugTooltips.Value; } public float ExpScapeTier {get => __ExpScapeTier.Value; } public float ExpScapePower {get => __ExpScapePower.Value; } public float ExpScapeLinear {get => __ExpScapeLinear.Value; } @@ -98,7 +100,7 @@ public void InitConfig(string mod_id, ConfigFile config) var serverConfigReceivedDelegateType = (Type)traverse.Type("ServerConfigReceivedDelegate").GetValue(); Type[] paramTypes = { typeof(string), typeof(ConfigFile), serverConfigReceivedDelegateType }; traverse.Method("RegisterMod", paramTypes).GetValue(mod_id, config, null); - } catch (Exception e) { + } catch (Exception) { // registering mod failed, API may have changed // pretend MCE doesn't exist assembly = null; @@ -111,6 +113,7 @@ public void InitConfig(string mod_id, ConfigFile config) __QuantisedQuality.init(assembly, config, mod_id); __StochasticVariance.init(assembly, config, mod_id); + __DebugTooltips.init(assembly, config, mod_id); __ExpScapeTier.init(assembly, config, mod_id); __ExpScapePower.init(assembly, config, mod_id); __ExpScapeLinear.init(assembly, config, mod_id); diff --git a/CraftingSkill/CraftingSkill.cs b/CraftingSkill/CraftingSkill.cs index debca9c..154622b 100644 --- a/CraftingSkill/CraftingSkill.cs +++ b/CraftingSkill/CraftingSkill.cs @@ -22,12 +22,12 @@ public class CraftingSkillsPlugin : BaseUnityPlugin { public const String MOD_ID = "annanfay.mod.crafting_skill"; public const string MOD_NAME = "CraftingSkill"; - public const string MOD_VERSION = "0.0.2"; + public const string MOD_VERSION = "0.0.3"; public const int CRAFTING_SKILL_ID = 1605; Harmony harmony; - private static CraftingConfig config = new CraftingConfig(); + public static CraftingConfig config = new CraftingConfig(); private static Dictionary cachedTextures = new Dictionary(); public CraftingSkillsPlugin() { @@ -43,7 +43,7 @@ void Awake() SkillInjector.RegisterNewSkill(CRAFTING_SKILL_ID, "Crafting", "Craft higher quality items as you level", 1.0f, LoadIconTexture(), Skills.SkillType.Unarmed); - ExtendedItemData.LoadExtendedItemData += QualityComponent.OnNewExtendedItemData; + ExtendedItemData.LoadExtendedItemData += QualityComponent.OnLoadExtendedItemData; ExtendedItemData.NewExtendedItemData += QualityComponent.OnNewExtendedItemData; } @@ -105,6 +105,45 @@ public static Stream GetManifestResourceStream(string filename) return null; } + + public static void DropHeadFix(ItemDrop.ItemData item) + { + // if item stack is less than quality stack remove from front until they match + // this happens after using arrows, consuming food, etc. + + QualityComponent comp = item.Extended()?.GetComponent(); + // item will be deleted if required, so no need to handle stack 0 case + if (comp != null && item.m_stack > 0 && item.m_stack < comp.Quality.Quantity) { + int toRemove = comp.Quality.Quantity - item.m_stack; + if (config.DebugTooltips) + { + ZLog.Log("DropHeadFix APPLIED #" + item.Extended()?.GetUniqueId() + " | " + item.m_stack + " < " + comp.Quality.Quantity); + } + comp.Quality.Shift(toRemove); + comp.Save(); + } + } + + public static void DropTailFix(ItemDrop.ItemData item) + { + // called in handlers explicitly when new item stacks are created + // discards fron back of stack until counts match + + QualityComponent comp = item.Extended()?.GetComponent(); + if (comp == null) return; + + if (config.DebugTooltips) + { + ZLog.Log("DropTailFix? #" + item.Extended()?.GetUniqueId() + " | " + item.m_stack + " < " + comp.Quality.Quantity); + } + // item will be deleted if required, so no need to handle stack 0 case + if (item.m_stack > 0 && item.m_stack < comp.Quality.Quantity) { + int toRemove = comp.Quality.Quantity - item.m_stack; + comp.Quality.Pop(toRemove); + } + comp.Save(); + } + [HarmonyPatch(typeof(ItemDrop.ItemData))] public static class ItemDataPatcher { @@ -127,7 +166,7 @@ static void GetTooltip(ItemDrop.ItemData item, int qualityLevel, bool crafting, ); } Recipe recipe = ObjectDB.instance.GetRecipe(item); - if (recipe != null) { + if (isCraftingRecipe(recipe)) { __result += String.Format( "\n{0}: {1}", CraftExperienceLabel, @@ -145,18 +184,18 @@ public static float GetItemQualityScalingFactor(ItemDrop.ItemData item, float mi return min + (max - min) * qualityComp.Quality.ScalingFactor(config); } - // First override the simple methods which returns floats - // public float GetArmor(int quality){} - // public float GetWeight(){} - // public float GetMaxDurability(int quality){} - // public float GetBaseBlockPower(int quality){} - // public float GetDeflectionForce(int quality){} + // public float GetArmor(int quality){} + // public float GetWeight(){} + // public float GetMaxDurability(int quality){} + // public float GetBaseBlockPower(int quality){} + // public float GetDeflectionForce(int quality){} // public float GetArmor(int quality){} [HarmonyPostfix] [HarmonyPatch("GetArmor", typeof(int))] public static float GetArmor(float __result, ItemDrop.ItemData __instance) { + DropHeadFix(__instance); return __result * GetItemQualityScalingFactor(__instance, config.ArmorStart, config.ArmorStop); } @@ -173,6 +212,7 @@ public static float GetWeight(float __result, ItemDrop.ItemData __instance) [HarmonyPatch("GetMaxDurability", typeof(int))] public static float GetMaxDurability(float __result, ItemDrop.ItemData __instance) { + DropHeadFix(__instance); return __result * GetItemQualityScalingFactor(__instance, config.MaxDurabilityStart, config.MaxDurabilityStop); } @@ -181,6 +221,7 @@ public static float GetMaxDurability(float __result, ItemDrop.ItemData __instanc [HarmonyPatch("GetBaseBlockPower", typeof(int))] public static float GetBaseBlockPower(float __result, ItemDrop.ItemData __instance) { + DropHeadFix(__instance); return __result * GetItemQualityScalingFactor(__instance, config.BaseBlockPowerStart, config.BaseBlockPowerStop); } @@ -189,6 +230,7 @@ public static float GetBaseBlockPower(float __result, ItemDrop.ItemData __instan [HarmonyPatch("GetDeflectionForce", typeof(int))] public static float GetDeflectionForce(float __result, ItemDrop.ItemData __instance) { + DropHeadFix(__instance); return __result * GetItemQualityScalingFactor(__instance, config.DeflectionForceStart, config.DeflectionForceStop); } @@ -197,6 +239,7 @@ public static float GetDeflectionForce(float __result, ItemDrop.ItemData __insta [HarmonyPatch("GetDamage", typeof(int))] public static void GetDamage(ref HitData.DamageTypes __result, ItemDrop.ItemData __instance) { + DropHeadFix(__instance); float scalingFactor = GetItemQualityScalingFactor(__instance, config.DamageStart, config.DamageStop); __result.Modify(scalingFactor); } @@ -248,6 +291,27 @@ public static float GetCraftExperience(Recipe recipe, int craftLevel) return exp; } + public static bool isCraftingRecipe(Recipe recipe) + { + // # Full list of stations used in recipes as of 0.147.3: + // # - identifier: `$piece_forge` in game name: Forge + // # - identifier: `$piece_workbench` in game name: Workbench + // # - identifier: `$piece_cauldron` in game name: Cauldron + // # - identifier: `$piece_stonecutter` in game name: Stonecutter + // See also (added at some point after above list): + // - $piece_artisanstation + // - $piece_oven + if (recipe == null) return false; + + string craftingStationName = recipe?.m_craftingStation?.m_name; + bool isNoStation = craftingStationName == null; + bool isForgeRecipe = craftingStationName == "$piece_forge"; + bool isWorkbenchRecipe = craftingStationName == "$piece_workbench"; + bool isStonecutterRecipe = craftingStationName == "$piece_stonecutter"; + bool isArtisanRecipe = craftingStationName == "$piece_artisanstation"; + return isWorkbenchRecipe || isForgeRecipe || isNoStation || isStonecutterRecipe || isArtisanRecipe; + } + [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] public static class InventoryGuiPatcherDoCrafting { @@ -281,23 +345,7 @@ Recipe ___m_craftRecipe return; } // ZLog.LogError($"Craft Succeeded? {___m_craftRecipe.m_item.m_itemData.m_shared.m_name} {craftLevel} {___m_craftRecipe.m_craftingStation?.m_name}"); - - // # Full list of stations used in recipes as of 0.147.3: - // # - identifier: `$piece_forge` in game name: Forge - // # - identifier: `$piece_workbench` in game name: Workbench - // # - identifier: `$piece_cauldron` in game name: Cauldron - // # - identifier: `$piece_stonecutter` in game name: Stonecutter - // See also (added at some point after above list): - // - $piece_artisanstation - // - $piece_oven - - string craftingStationName = ___m_craftRecipe.m_craftingStation?.m_name; - bool isNoStation = craftingStationName == null; - bool isForgeRecipe = craftingStationName == "$piece_forge"; - bool isWorkbenchRecipe = craftingStationName == "$piece_workbench"; - bool isStonecutterRecipe = craftingStationName == "$piece_stonecutter"; - bool isArtisanRecipe = craftingStationName == "$piece_artisanstation"; - if (isWorkbenchRecipe || isForgeRecipe || isNoStation || isStonecutterRecipe || isArtisanRecipe) { + if (isCraftingRecipe(___m_craftRecipe)) { float craftExperience = GetCraftExperience(___m_craftRecipe, craftLevel); player.RaiseSkill((Skills.SkillType)CRAFTING_SKILL_ID, craftExperience); // float SkillLevel = player.GetSkillFactor((Skills.SkillType)CRAFTING_SKILL_ID); @@ -316,7 +364,7 @@ static void Prefix(InventoryGui __instance, // private fields ItemDrop.ItemData ___m_craftUpgradeItem, int ___m_craftVariant, - List m_tempWornItems + List ___m_tempWornItems ) { if (Player.m_localPlayer == null) @@ -329,10 +377,10 @@ static void Prefix(InventoryGui __instance, { return; } - m_tempWornItems.Clear(); - Player.m_localPlayer.GetInventory().GetWornItems(m_tempWornItems); + ___m_tempWornItems.Clear(); + Player.m_localPlayer.GetInventory().GetWornItems(___m_tempWornItems); var CanRepair = __instance.GetType().GetMethod("CanRepair", BindingFlags.NonPublic | BindingFlags.Instance); - foreach (ItemDrop.ItemData tempWornItem in m_tempWornItems) + foreach (ItemDrop.ItemData tempWornItem in ___m_tempWornItems) { if ((bool)CanRepair.Invoke(__instance, new object[] { tempWornItem })) { @@ -342,13 +390,101 @@ static void Prefix(InventoryGui __instance, } } } + } + + [HarmonyPatch] + static class ItemDataClonePatches + { + [HarmonyPatch(typeof(DropTable), "AddItemToList")] + static void Postfix(List toDrop, DropTable.DropData data) { + DropTailFix(toDrop.Last()); + } + + [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemDrop.ItemData), typeof(int), typeof(int), typeof(int)})] + static void Postfix(Inventory __instance, ItemDrop.ItemData item, int amount, int x, int y) { + if (config.DebugTooltips) + { + ZLog.Log("AddItem(amount:" + amount + ", x:"+x+", y:"+y+")"); + } + + var target = __instance.GetItemAt(x, y); + + QualityComponent comp = item.Extended()?.GetComponent(); + if (config.DebugTooltips) + { + ZLog.Log("... AddItem, source #" + item.Extended()?.GetUniqueId() + " | " + comp); + } + if (comp == null) return; + + QualityComponent targetComp = target.Extended()?.GetComponent(); + if (config.DebugTooltips) + { + ZLog.Log("... AddItem, target = #" + target.Extended()?.GetUniqueId() + " | " + targetComp + " " + (targetComp == null ? "NULL" : (target.m_stack + " > " + targetComp.Quality.Quantity))); + } + // existing item, needs extra quality data + if (targetComp != null && target.m_stack > targetComp.Quality.Quantity) { + targetComp.Quality.MergeInto(comp.Quality); + targetComp.Save(); + } + DropTailFix(target); + } + + + [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(ItemDrop.ItemData) })] + static void Postfix(Inventory __instance, ItemDrop.ItemData item, List ___m_inventory) { + // We need to recover from an item stack being automatically spread across multiple stacks in target inventory + // Example: Have stacks of 99, 99 and 99 arrows in your inventory. Crafting 20 arrows will deposit 1 arrow + // in each stack and 17 in a new stack. + + // item is a reference to item (maybe also in inventory) which we take qualities from as needed + // it will likely have more qualities than stacksize, while targets for fixing have more stack than qualities + + QualityComponent comp = item.Extended()?.GetComponent(); + + if (comp == null) + { + return; + } + + var sourceQuality = comp.Quality; + if (item.m_shared.m_maxStackSize > 1) + { + foreach (ItemDrop.ItemData other in ___m_inventory) + { + if (item.m_shared.m_name != other.m_shared.m_name && item.m_quality != other.m_quality) + { + continue; + } + + QualityComponent targetComp = other.Extended()?.GetComponent(); + if (targetComp == null || other.m_stack <= targetComp.Quality.Quantity) + { + continue; + } + var needed = other.m_stack - targetComp.Quality.Quantity; + var quals = sourceQuality.Shift(needed); + targetComp.Quality.Qualities.AddRange(quals); + targetComp.Save(); + + Debug.Assert(other.m_stack == targetComp.Quality.Quantity); + // DropTailFix(target); + } + if (sourceQuality.Quantity > 0) { + comp.Save(); + } + } + } - // static void Postfix(InventoryGui __instance, Player player, - // // private fields - // Recipe ___m_craftRecipe - // ) - // { - // } + [HarmonyPatch(typeof(ItemDrop), "DropItem")] + static void Postfix(ref ItemDrop __result, ItemDrop.ItemData item, int amount, Vector3 position, Quaternion rotation) { + DropTailFix(__result.m_itemData); + } + + [HarmonyPatch(typeof(ItemStand), "UpdateAttach")] + static void Postfix(ItemStand __instance, ItemDrop.ItemData ___m_queuedItem) { + // Drop on the origin item as we can't easily access ZDO stored item + DropTailFix(___m_queuedItem); + } } } } diff --git a/CraftingSkill/Properties/AssemblyInfo.cs b/CraftingSkill/Properties/AssemblyInfo.cs index 2331fe2..cb2e1ab 100644 --- a/CraftingSkill/Properties/AssemblyInfo.cs +++ b/CraftingSkill/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.0.1.2")] -[assembly: AssemblyFileVersion("0.0.1.2")] +[assembly: AssemblyVersion("0.0.3.0")] +[assembly: AssemblyFileVersion("0.0.3.0")] diff --git a/CraftingSkill/Quality.cs b/CraftingSkill/Quality.cs index a33f424..13c5cf1 100644 --- a/CraftingSkill/Quality.cs +++ b/CraftingSkill/Quality.cs @@ -23,57 +23,76 @@ public Quality() { Variance = UnityEngine.Random.value; } + public Quality Clone() + { + return MemberwiseClone() as Quality; + } + + public string DebugInfo() { + return String.Format( + "x{3} x{0} ~{1} L{2}", + Skill, + Variance, + StationLevel, + Quantity + ); + } public string GetTooltip(CraftingConfig config) { - float factor = ScalingFactor(config); + string debugInfo = config.DebugTooltips ? " #debug: " + DebugInfo() : ""; + + float factor = ScalingFactor(config.StochasticVariance, config.QuantisedQuality); if (config.QuantisedQuality) { QualityTier tier = GetQualityTier(factor); - factor = tier.GetFactor(); + float preQuantisation = ScalingFactor(config.StochasticVariance, false); return String.Format( - "{0} ({1}) #debug: {2} {3} {4}", - GetQualityTier(factor).GetTooltip(), - (factor * 100f).ToString("0"), - (Skill * 100f).ToString("0"), Variance, StationLevel + "{0} ({1} [{2}]){3}", + (preQuantisation * 100f).ToString("0"), + tier.GetTooltip(), + (tier.GetFactor() * 100f).ToString("0"), + debugInfo ); } - return String.Format( - "{0} / 100 #debug: {1} {2} {3}", + "{0} / 100 ({1}){2}", (factor * 100f).ToString("0"), - (Skill * 100f).ToString("0"), Variance, StationLevel + GetQualityTier(factor).GetTooltip(), + debugInfo ); } - public float ScalingFactor(CraftingConfig config) + public float ScalingFactor(float StochasticVariance, bool QuantisedQuality) { var factor = this.Skill; - if (config.StochasticVariance > 0) + if (StochasticVariance > 0) { // map 0,1 to -1,+1 var variance = 2.0f * (this.Variance - 0.5f); // scale by config, add to factor - factor += (config.StochasticVariance/100.0f) * variance; + factor += (StochasticVariance / 100.0f) * variance; // clamp invalid values (level 0 and 100) factor = Mathf.Clamp(factor, 0.0f, 1.0f); } - if (config.QuantisedQuality) + if (QuantisedQuality) { QualityTier tier = GetQualityTier(factor); factor = tier.GetFactor(); } return factor; } + public static QualityTier GetQualityTier(float factor) { //Debug.Log("GetQualityTier: ", factor); - foreach (QualityTier tier in (QualityTier[])Enum.GetValues(typeof(QualityTier))) + var tiers = (QualityTier[])Enum.GetValues(typeof(QualityTier)); + foreach (QualityTier tier in tiers.OrderByDescending(x => x)) { //Debug.Log("GetQualityTier -> ", tier.GetFactor(), " >= ", factor, tier.GetFactor() >= factor); - if (tier.GetFactor() >= factor) + if (tier.GetFactor() <= factor) { return tier; } diff --git a/CraftingSkill/QualityComponent.cs b/CraftingSkill/QualityComponent.cs index f0a0754..dc42c20 100644 --- a/CraftingSkill/QualityComponent.cs +++ b/CraftingSkill/QualityComponent.cs @@ -27,8 +27,18 @@ public void SetQuality(StackableQuality quality) Save(); } + public override BaseExtendedItemComponent Clone() + { + // ZLog.LogWarning("Clone! ???, stack: ???, quality stack:" + Quality.Quantity); + var clone = MemberwiseClone() as QualityComponent; + clone.Quality = this.Quality.Clone(); + return clone as BaseExtendedItemComponent; + } + public override string Serialize() { + // ZLog.LogError($"[{nameof(QualityComponent)}] Serialising NOW1!!! {JSON.ToJSON(this.Quality, _saveParams)}"); + // ZLog.LogError($"[{nameof(QualityComponent)}] ................!!! {this.GetTooltip(new CraftingConfig())}"); return JSON.ToJSON(this.Quality, _saveParams); } @@ -38,8 +48,9 @@ public override void Deserialize(string data) { this.Quality = JSON.ToObject(data, _saveParams); } - catch (Exception) + catch (Exception e) { + ZLog.LogError($"[{nameof(QualityComponent)}] Error while deserialising Quality json data! ({ItemData?.m_shared?.m_name}): {e?.Message}"); LegacyDeserialize(data); } } @@ -54,32 +65,56 @@ public void LegacyDeserialize(string data) stackable.Qualities.Add(quality); this.Quality = stackable; } - catch (Exception) + catch (Exception e) { - ZLog.LogError($"[{nameof(QualityComponent)}] Could not deserialize Quality json data! ({ItemData?.m_shared?.m_name})"); + ZLog.LogError($"[{nameof(QualityComponent)}] Could not deserialize Quality json data! ({ItemData?.m_shared?.m_name}): {e?.Message}"); throw; } } - public override BaseExtendedItemComponent Clone() - { - return MemberwiseClone() as BaseExtendedItemComponent; - } - public string GetTooltip(CraftingConfig config) { // Non-formated right hand side of "Quality: XXX" return this.Quality.GetTooltip(config); } + public static void OnLoadExtendedItemData(ExtendedItemData itemdata) + { + QualityComponent _qualityComponent = itemdata.GetComponent(); + if (CraftingSkillsPlugin.config.DebugTooltips) + { + if (_qualityComponent != null) + { + ZLog.LogWarning("Load EID! " + itemdata.m_shared.m_name + ", stack:" + itemdata.m_stack + ", quality stack:" + _qualityComponent.Quality.Quantity); + } else { + ZLog.LogWarning("Load EID! " + itemdata.m_shared.m_name + ", stack:" + itemdata.m_stack); + } + } + OnExtendedItemData(itemdata); + } + public static void OnNewExtendedItemData(ExtendedItemData itemdata) + { + QualityComponent _qualityComponent = itemdata.GetComponent(); + + if (CraftingSkillsPlugin.config.DebugTooltips) + { + if (_qualityComponent != null) + { + ZLog.LogWarning("New EID! " + itemdata + ", stack:" + itemdata.m_stack + ", quality stack:" + _qualityComponent.Quality.Quantity); + } else { + ZLog.LogWarning("New EID! " + itemdata + ", stack:" + itemdata.m_stack); + } + } + OnExtendedItemData(itemdata); + } + + public static void OnExtendedItemData(ExtendedItemData itemdata) { // This gets triggered on generated items and crafted items - // Also on items moved between containers, etc. - // ZLog.LogError("OnNewExtendedItemData!"); Recipe recipe = ObjectDB.instance.GetRecipe(itemdata); - if (recipe == null) + if (!CraftingSkillsPlugin.isCraftingRecipe(recipe)) { return; } @@ -97,10 +132,18 @@ public static void OnNewExtendedItemData(ExtendedItemData itemdata) return; } + // items are added on player load, in which case there won't be a current station, unless the no cost cheat active + // we still cannot avoid setting quality on hammers, torches, etc. + + var currentStation = player.GetCurrentCraftingStation(); + var requiredStation = recipe.GetRequiredStation(itemdata.m_quality); + if (!player.NoCostCheat() && requiredStation != null && currentStation?.m_name != requiredStation?.m_name) { + return; + } + QualityComponent qualityComponent = itemdata.GetComponent(); if (qualityComponent != null) { - ZLog.LogWarning("qualityComponent is not null during ExtendedItemData creation"); return; } @@ -118,15 +161,5 @@ public static void OnNewExtendedItemData(ExtendedItemData itemdata) // Quality may have changed durability on our new item, so fix it itemdata.m_durability = itemdata.GetMaxDurability(); } - - public static void OnLoadExtendedItemData(ExtendedItemData itemdata) - { - // ZLog.LogError("OnLoadExtendedItemData!"); - // QualityComponent qualityComponent = itemdata.GetComponent(); - // if (qualityComponent != null) - // { - // ZLog.LogError("qualityComponent! q.Skill=" + qualityComponent.Quality.Skill); - // } - } } } diff --git a/CraftingSkill/QualityTier.cs b/CraftingSkill/QualityTier.cs index 5fb39bf..aa8c418 100644 --- a/CraftingSkill/QualityTier.cs +++ b/CraftingSkill/QualityTier.cs @@ -40,17 +40,17 @@ public static float GetFactor(this QualityTier tier) { switch (tier) { - // numbers are the tops of bins + // numbers are the bottoms of bins // So Awful is from 0 to 6 // Poor from 7 22 // Artifact is 100 only - case QualityTier.AWFUL: return 0.06f; - case QualityTier.POOR: return 0.22f; - case QualityTier.NORMAL: return 0.38f; - case QualityTier.FINE: return 0.54f; - case QualityTier.SUPERIOR: return 0.70f; - case QualityTier.EXCEPTIONAL: return 0.86f; - case QualityTier.MASTERWORK: return 0.99f; + case QualityTier.AWFUL: return 0.00f; + case QualityTier.POOR: return 0.07f; + case QualityTier.NORMAL: return 0.23f; + case QualityTier.FINE: return 0.39f; + case QualityTier.SUPERIOR: return 0.55f; + case QualityTier.EXCEPTIONAL: return 0.71f; + case QualityTier.MASTERWORK: return 0.87f; case QualityTier.ARTIFACT: return 1.00f; } return 0; diff --git a/CraftingSkill/StackableQuality.cs b/CraftingSkill/StackableQuality.cs index 38b5b9c..c91c574 100644 --- a/CraftingSkill/StackableQuality.cs +++ b/CraftingSkill/StackableQuality.cs @@ -11,13 +11,11 @@ public class StackableQuality { public List Qualities; - // Defaults return weighted average over stack - public StackableQuality() { this.Qualities = new List(); } - + public StackableQuality(float skill, int quantity, int stationLevel) { this.Qualities = new List(); @@ -30,8 +28,23 @@ public StackableQuality(float skill, int quantity, int stationLevel) Qualities.Add(quality); } - public int Quantity { + public StackableQuality Clone() + { + var clone = MemberwiseClone() as StackableQuality; + clone.Qualities = new List(this.Qualities.Count); + this.Qualities.ForEach((item) => { + clone.Qualities.Add(item.Clone()); + }); + return clone; + } + + public int Quantity + { get { + if (Qualities.Count == 0) + { + return 0; + } return Qualities.Sum(q => q.Quantity); } } @@ -39,38 +52,143 @@ public float Skill { get { + if (Qualities.Count == 0) + { + return 0; + } //return Qualities.Average(q => q.Skill); return Qualities.First().Skill; } } - public int StationLevel { + public int StationLevel + { get { + if (Qualities.Count == 0) + { + return 0; + } //return (int)Qualities.Average(q => q.StationLevel); return (int)Qualities.First().StationLevel; } } - public float Variance { + public float Variance + { get { + if (Qualities.Count == 0) + { + return 0; + } //return Qualities.Average(q => q.Variance); return Qualities.First().Variance; } } + public float ScalingFactor(CraftingConfig config) + { + if (Qualities.Count == 0) + { + return 0; + } + return this.Qualities.First().ScalingFactor(config.StochasticVariance, config.QuantisedQuality); + } + + + public string DebugInfo() + { + return " Stack{\n " + ( + String.Join( + "\n ", + this.Qualities.Select(x => x.DebugInfo()) + ) + ) + "\n}"; + } + public string GetTooltip(CraftingConfig config) { - if (this.Qualities.Count == 1) + string debugInfo = CraftingSkillsPlugin.config.DebugTooltips ? DebugInfo() : ""; + + if (this.Qualities.Count == 0) { - return this.Qualities[0].GetTooltip(config); + return "CraftingQuality Debug Info: " + DebugInfo(); + } + else if (this.Qualities.Count == 1) + { + return this.Qualities[0].GetTooltip(config) + debugInfo; } else { - return "Mixed!"; + return "Mixed! " + StackSummary(config) + debugInfo; } } - public float ScalingFactor(CraftingConfig config) + public string StackSummary(CraftingConfig config) + { + var sorted = this.Qualities.Select(x => x.ScalingFactor(config.StochasticVariance, config.QuantisedQuality)); + var min = sorted.Min(); + var max = sorted.Max(); + return "(" + (min * 100f).ToString("0") + "-" + (max * 100f).ToString("0") + ")"; + } + internal void MergeInto(StackableQuality other) + { + other.Qualities.ForEach(q => { + // if functionally the same as tail we merge into + // otherwise we preserve order and stack on end + // (prevents a bit of bloat) + var tail = this.Qualities.Last(); + if (tail.Variance == q.Variance && tail.Skill == q.Skill && tail.Variance == q.Variance && tail.StationLevel == q.StationLevel) { + tail.Quantity += q.Quantity; + } else { + this.Qualities.Add(q.Clone()); + } + }); + } + + internal List Shift(int n) + { + // remove and return n from start + var shifted = new List(); + + if (n > this.Quantity) + { + n = this.Quantity; + } + + int needed = n; + while (needed > 0) + { + var first = this.Qualities.First(); + Quality toAdd; + if (first.Quantity <= needed) + { + this.Qualities.Remove(first); + toAdd = first; + } + else + { + first.Quantity -= needed; + toAdd = first.Clone(); + toAdd.Quantity = needed; + } + shifted.Add(toAdd); + needed -= toAdd.Quantity; + } + + // Avoid killing ourself on corrupt data by always preserving one item + if (this.Qualities.Count == 0) + { + this.Qualities.Add(shifted.Last().Clone()); + } + return shifted; + } + + internal List Pop(int n) { - return this.Qualities.First().ScalingFactor(config); + // remove and return n from end + // (Bad implemention here) + this.Qualities.Reverse(); + var popped = this.Shift(n); + this.Qualities.Reverse(); + return popped; } } }