diff --git a/CraftingSkill/CraftingConfig.cs b/CraftingSkill/CraftingConfig.cs index 9c10898..f593c8b 100644 --- a/CraftingSkill/CraftingConfig.cs +++ b/CraftingSkill/CraftingConfig.cs @@ -16,10 +16,10 @@ namespace CraftingSkill { public class CraftingConfig { - // public int NEXUS_ID = 0; + private ConfigVariable __QuantisedQuality = new ConfigVariable("General", "QuantisedQuality", false); + private ConfigVariable __StochasticVariance = new ConfigVariable("General", "StochasticVariance", 0.0f); // Experience for crafting - private ConfigVariable __ExpScapeTier = new ConfigVariable("ExpGain", "ExpScapeTier", 1.0f); private ConfigVariable __ExpScapePower = new ConfigVariable("ExpGain", "ExpScapePower", 2.0f); private ConfigVariable __ExpScapeLinear = new ConfigVariable("ExpGain", "ExpScapeLinear", 0.16f); @@ -52,6 +52,8 @@ public class CraftingConfig private ConfigVariable __TierModifierDefault = new ConfigVariable("StationBalancing", "TierModifierDefault", 0); // Utility getters + public bool QuantisedQuality {get => __QuantisedQuality.Value; } + public float StochasticVariance {get => __StochasticVariance.Value; } public float ExpScapeTier {get => __ExpScapeTier.Value; } public float ExpScapePower {get => __ExpScapePower.Value; } public float ExpScapeLinear {get => __ExpScapeLinear.Value; } @@ -70,8 +72,8 @@ public class CraftingConfig public int TierModifierInventory {get => __TierModifierInventory.Value; } public int TierModifierWorkbench {get => __TierModifierWorkbench.Value; } public int TierModifierForge {get => __TierModifierForge.Value; } - public int TierModifierCauldron { get => __TierModifierCauldron.Value; } - public int TierModifierOven { get => __TierModifierOven.Value; } + public int TierModifierCauldron {get => __TierModifierCauldron.Value; } + public int TierModifierOven {get => __TierModifierOven.Value; } public int TierModifierStonecutter {get => __TierModifierStonecutter.Value; } public int TierModifierArtisan {get => __TierModifierArtisan.Value; } public int TierModifierDefault {get => __TierModifierDefault.Value; } @@ -83,8 +85,6 @@ public CraftingConfig() public void InitConfig(string mod_id, ConfigFile config) { - // Bind("General", "NexusID", NEXUS_ID, "Nexus mod ID for updates"); - Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == "ModConfigEnforcer"); if (assembly != null) { @@ -109,6 +109,8 @@ public void InitConfig(string mod_id, ConfigFile config) Debug.Log("Mod Config Enforcer not detected."); } + __QuantisedQuality.init(assembly, config, mod_id); + __StochasticVariance.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 4f7cde5..1b605bb 100644 --- a/CraftingSkill/CraftingSkill.cs +++ b/CraftingSkill/CraftingSkill.cs @@ -28,41 +28,12 @@ public class CraftingSkillsPlugin : BaseUnityPlugin Harmony harmony; private static CraftingConfig config = new CraftingConfig(); + private static Dictionary cachedTextures = new Dictionary(); public CraftingSkillsPlugin() { LoadEmbeddedAssembly("fastJSON.dll"); } - private static void LoadEmbeddedAssembly(string assemblyName) - { - // RandyKnapp https://github.com/RandyKnapp/ValheimMods/blob/719e1a6dc419f9075c46b3eb4e3eb53285b7ffda/EpicLoot-Addon-Helheim/Helheim.cs#L58 - var stream = GetManifestResourceStream(assemblyName); - if (stream == null) - { - Debug.LogError($"Could not load embedded assembly ({assemblyName})!"); - return; - } - - using (stream) - { - var data = new byte[stream.Length]; - stream.Read(data, 0, data.Length); - Assembly.Load(data); - } - } - - public static Stream GetManifestResourceStream(string filename) - { - var assembly = Assembly.GetCallingAssembly(); - var fullname = assembly.GetManifestResourceNames().SingleOrDefault(x => x.EndsWith(filename)); - if (!string.IsNullOrEmpty(fullname)) - { - return assembly.GetManifestResourceStream(fullname); - } - - return null; - } - void Awake() { config.InitConfig(MOD_ID, Config); @@ -92,7 +63,6 @@ private static Sprite LoadIconTexture() } } - private static Dictionary cachedTextures = new Dictionary(); private static Texture2D LoadTexture(string filepath) { if (cachedTextures.ContainsKey(filepath)) @@ -104,6 +74,37 @@ private static Texture2D LoadTexture(string filepath) return texture2D; } + private static void LoadEmbeddedAssembly(string assemblyName) + { + // RandyKnapp https://github.com/RandyKnapp/ValheimMods/blob/719e1a6dc419f9075c46b3eb4e3eb53285b7ffda/EpicLoot-Addon-Helheim/Helheim.cs#L58 + var stream = GetManifestResourceStream(assemblyName); + if (stream == null) + { + Debug.LogError($"Could not load embedded assembly ({assemblyName})!"); + return; + } + + using (stream) + { + var data = new byte[stream.Length]; + stream.Read(data, 0, data.Length); + Assembly.Load(data); + } + } + + public static Stream GetManifestResourceStream(string filename) + { + // RandyKnapp https://github.com/RandyKnapp/ValheimMods/blob/719e1a6dc419f9075c46b3eb4e3eb53285b7ffda/EpicLoot-Addon-Helheim/Helheim.cs + var assembly = Assembly.GetCallingAssembly(); + var fullname = assembly.GetManifestResourceNames().SingleOrDefault(x => x.EndsWith(filename)); + if (!string.IsNullOrEmpty(fullname)) + { + return assembly.GetManifestResourceStream(fullname); + } + + return null; + } + [HarmonyPatch(typeof(ItemDrop.ItemData))] public static class ItemDataPatcher { @@ -114,9 +115,11 @@ public static class ItemDataPatcher [HarmonyPatch("GetTooltip", typeof(ItemDrop.ItemData), typeof(int), typeof(bool))] static void GetTooltip(ItemDrop.ItemData item, int qualityLevel, bool crafting, ref string __result) { + // Display craft quality if item has it + // Then display crafting experience if there is a crafting recipe for the item QualityComponent qualityComp = item.Extended()?.GetComponent(); if(qualityComp != null) { - var qcTooltip = qualityComp.GetTooltip(); + var qcTooltip = qualityComp.GetTooltip(config); __result += String.Format( "\n{0}: {1}", CraftQualityLabel, @@ -139,10 +142,10 @@ public static float GetItemQualityScalingFactor(ItemDrop.ItemData item, float mi if (qualityComp == null) { return 1.0f; } - float scalingFactor = min + (max - min) * qualityComp.Quality.Skill; - return scalingFactor; + return min + (max - min) * qualityComp.Quality.ScalingFactor(config); } + // First override the simple methods which returns floats // public float GetArmor(int quality){} // public float GetWeight(){} @@ -199,6 +202,8 @@ public static void GetDamage(ref HitData.DamageTypes __result, ItemDrop.ItemData } } + // Below code handles gaining experience when crafting + public static float GetCraftTierMod(Recipe recipe) { switch (recipe.m_craftingStation?.m_name) @@ -295,7 +300,7 @@ Recipe ___m_craftRecipe if (isWorkbenchRecipe || isForgeRecipe || isNoStation || isStonecutterRecipe || isArtisanRecipe) { float craftExperience = GetCraftExperience(___m_craftRecipe, craftLevel); player.RaiseSkill((Skills.SkillType)CRAFTING_SKILL_ID, craftExperience); - float SkillLevel = player.GetSkillFactor((Skills.SkillType)CRAFTING_SKILL_ID); + // float SkillLevel = player.GetSkillFactor((Skills.SkillType)CRAFTING_SKILL_ID); // ZLog.LogError($"Craft Succeeded => exp={craftExperience}, {SkillLevel}"); } } diff --git a/CraftingSkill/CraftingSkill.csproj b/CraftingSkill/CraftingSkill.csproj index 462dcde..903e74a 100644 --- a/CraftingSkill/CraftingSkill.csproj +++ b/CraftingSkill/CraftingSkill.csproj @@ -135,6 +135,7 @@ + @@ -149,4 +150,4 @@ xcopy /y /d "$(ProjectDir)CraftingSkillIcon.png" "M:\SteamLibrary\SteamApps\common\Valheim\BepInEx\plugins\CraftingSkill" - + \ No newline at end of file diff --git a/CraftingSkill/Quality.cs b/CraftingSkill/Quality.cs index 2d35fe8..10c78c8 100644 --- a/CraftingSkill/Quality.cs +++ b/CraftingSkill/Quality.cs @@ -6,50 +6,16 @@ namespace CraftingSkill { - [Serializable] - public enum QualityTier - { - MIXED = 0, - AWFUL = 1, - POOR = 2, - NORMAL = 3, - FINE = 4, - SUPERIOR = 5, - EXCEPTIONAL = 6, - MASTERWORK = 7, - ARTIFACT = 8 - } - - // Extension method to QualityTier enum - public static class _QualityTier - { - public static string GetTooltip(this QualityTier tier) { - switch ( tier ) { - case QualityTier.MIXED: return "Mixed"; - case QualityTier.AWFUL: return "Awful"; - case QualityTier.POOR: return "Poor"; - case QualityTier.NORMAL: return "Normal"; - case QualityTier.FINE: return "Fine"; - case QualityTier.SUPERIOR: return "Superior"; - case QualityTier.EXCEPTIONAL: return "Exceptional"; - case QualityTier.MASTERWORK: return "Masterwork"; - case QualityTier.ARTIFACT: return "Artifact"; - } - return "UNKNOWN"; - } - } [Serializable] public class Quality { // Skill level when item is created public float Skill = 0; - // Level of station used to create item - public int StationLevel = 0; - // Quantised item quality - public QualityTier Tier = QualityTier.NORMAL; // So we can handle stack merges public int Quantity = 1; + // Level of station used to create item + public int StationLevel = 0; // For random outcomes public float Variance; @@ -57,13 +23,58 @@ public Quality() { Variance = UnityEngine.Random.value; } - public string GetTooltip() + + public string GetTooltip(CraftingConfig config) { + float factor = ScalingFactor(config); + if (config.QuantisedQuality) + { + QualityTier tier = GetQualityTier(factor); + factor = tier.GetFactor(); + return String.Format( + "{0} ({1})", + GetQualityTier(this.Skill).GetTooltip(), + (factor * 100f).ToString("0") + ); + } + return String.Format( - "{0} ({1})", - this.Tier.GetTooltip(), - (this.Skill * 100f).ToString("0") + "{0} / 100", + (factor * 100f).ToString("0") ); } + + public float ScalingFactor(CraftingConfig config) + { + var factor = this.Skill; + + if (config.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 * variance; + // clamp invalid values (level 0 and 100) + factor = Mathf.Clamp(factor, 0.0f, 1.0f); + } + + if (config.QuantisedQuality) + { + QualityTier tier = GetQualityTier(factor); + factor = tier.GetFactor(); + } + return factor; + } + public static QualityTier GetQualityTier(float factor) + { + foreach (QualityTier tier in (QualityTier[])Enum.GetValues(typeof(QualityTier))) + { + if (tier.GetFactor() >= factor) + { + return tier; + } + } + return QualityTier.NORMAL; + } } } diff --git a/CraftingSkill/QualityComponent.cs b/CraftingSkill/QualityComponent.cs index 8aea55b..6f218ac 100644 --- a/CraftingSkill/QualityComponent.cs +++ b/CraftingSkill/QualityComponent.cs @@ -66,10 +66,10 @@ public override BaseExtendedItemComponent Clone() return MemberwiseClone() as BaseExtendedItemComponent; } - public string GetTooltip() + public string GetTooltip(CraftingConfig config) { // Non-formated right hand side of "Quality: XXX" - return this.Quality.GetTooltip(); + return this.Quality.GetTooltip(config); } public static void OnNewExtendedItemData(ExtendedItemData itemdata) @@ -100,21 +100,16 @@ public static void OnNewExtendedItemData(ExtendedItemData itemdata) ZLog.LogWarning("qualityComponent is not null during ExtendedItemData creation"); return; } - - var quality = new Quality(); - quality.Skill = player.GetSkillFactor((Skills.SkillType)CraftingSkillsPlugin.CRAFTING_SKILL_ID); - quality.Quantity = itemdata.m_stack; + + var skill = player.GetSkillFactor((Skills.SkillType)CraftingSkillsPlugin.CRAFTING_SKILL_ID); + var quantity = itemdata.m_stack; CraftingStation station = player.GetCurrentCraftingStation(); - if (station) - { - quality.StationLevel = station.GetLevel(); - } - - var qc = new StackableQuality(); - qc.Qualities.Add(quality); + int stationLevel = station == null ? 0 : station.GetLevel(); + + var quality = new StackableQuality(skill, quantity, stationLevel); - itemdata.AddComponent().SetQuality(qc); + itemdata.AddComponent().SetQuality(quality); } public static void OnLoadExtendedItemData(ExtendedItemData itemdata) diff --git a/CraftingSkill/QualityTier.cs b/CraftingSkill/QualityTier.cs new file mode 100644 index 0000000..5fb39bf --- /dev/null +++ b/CraftingSkill/QualityTier.cs @@ -0,0 +1,59 @@ +using System; + +namespace CraftingSkill +{ + [Serializable] + public enum QualityTier + { + MIXED = 0, + AWFUL = 1, + POOR = 2, + NORMAL = 3, + FINE = 4, + SUPERIOR = 5, + EXCEPTIONAL = 6, + MASTERWORK = 7, + ARTIFACT = 8 + } + + // Extension method to QualityTier enum + public static class _QualityTier + { + public static string GetTooltip(this QualityTier tier) + { + switch (tier) + { + case QualityTier.MIXED: return "Mixed"; + case QualityTier.AWFUL: return "Awful"; + case QualityTier.POOR: return "Poor"; + case QualityTier.NORMAL: return "Normal"; + case QualityTier.FINE: return "Fine"; + case QualityTier.SUPERIOR: return "Superior"; + case QualityTier.EXCEPTIONAL: return "Exceptional"; + case QualityTier.MASTERWORK: return "Masterwork"; + case QualityTier.ARTIFACT: return "Artifact"; + } + return "UNKNOWN"; + } + + public static float GetFactor(this QualityTier tier) + { + switch (tier) + { + // numbers are the tops 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.ARTIFACT: return 1.00f; + } + return 0; + } + } +} diff --git a/CraftingSkill/StackableQuality.cs b/CraftingSkill/StackableQuality.cs index baecba8..38b5b9c 100644 --- a/CraftingSkill/StackableQuality.cs +++ b/CraftingSkill/StackableQuality.cs @@ -18,42 +18,59 @@ public StackableQuality() this.Qualities = new List(); } + public StackableQuality(float skill, int quantity, int stationLevel) + { + this.Qualities = new List(); + + var quality = new Quality(); + quality.Skill = skill; + quality.Quantity = quantity; + quality.StationLevel = stationLevel; + + Qualities.Add(quality); + } + public int Quantity { get { return Qualities.Sum(q => q.Quantity); } } - public float Skill { - get { - return Qualities.Average(q => q.Skill); + public float Skill + { + get + { + //return Qualities.Average(q => q.Skill); + return Qualities.First().Skill; } } public int StationLevel { - get { - return (int)Qualities.Average(q => q.StationLevel); + get + { + //return (int)Qualities.Average(q => q.StationLevel); + return (int)Qualities.First().StationLevel; } } public float Variance { - get { - return Qualities.Average(q => q.Variance); - } - } - public QualityTier Tier { - get { - if (Qualities.Count > 0) { - return QualityTier.MIXED; - } else { - return Qualities[0].Tier; - } + get + { + //return Qualities.Average(q => q.Variance); + return Qualities.First().Variance; } } - public string GetTooltip() + public string GetTooltip(CraftingConfig config) { - if (this.Qualities.Count == 1) { - return this.Qualities[0].GetTooltip(); - } else { + if (this.Qualities.Count == 1) + { + return this.Qualities[0].GetTooltip(config); + } + else + { return "Mixed!"; } } + public float ScalingFactor(CraftingConfig config) + { + return this.Qualities.First().ScalingFactor(config); + } } }