diff --git a/SmithingPlus/ClientTweaks/ShowWorkablePatches.cs b/SmithingPlus/ClientTweaks/ShowWorkablePatches.cs index b288549..acabf85 100644 --- a/SmithingPlus/ClientTweaks/ShowWorkablePatches.cs +++ b/SmithingPlus/ClientTweaks/ShowWorkablePatches.cs @@ -9,11 +9,8 @@ namespace SmithingPlus.ClientTweaks; [HarmonyPatchCategory(Core.ClientTweaksCategories.ShowWorkablePatches)] -public partial class ShowWorkablePatches +public class ShowWorkablePatches { - private const string TemperatureRegexPattern = @"(\d+(.*)°C)"; - private const string TemperatureRangeRegexPattern = @"(\s*\(\d+°C\s*-\s*\d+°C\))"; - [HarmonyPostfix] [HarmonyPatch(typeof(BlockEntityAnvil), nameof(BlockEntityAnvil.GetBlockInfo))] [HarmonyPriority(Priority.VeryLow)] @@ -37,68 +34,16 @@ public static void Postfix_BlockEntityAnvil_GetBlockInfo(BlockEntityAnvil __inst public static void Postfix_BlockEntityForge_GetBlockInfo(BlockEntityForge __instance, IPlayer forPlayer, StringBuilder dsc) { - if (__instance.WorkItemStack is not { } workItemStack) return; + if (__instance.WorkItemStack == null) return; var temperature = - (int)workItemStack.Collectible.GetTemperature(__instance.Api.World, workItemStack); - var workableTemp = workItemStack.GetWorkableTemperature(); - - var metalProps = workItemStack.Collectible - .GetBehavior() - ?.GetMetalProps(); - if (metalProps != null) - { - var quenchIteration = workItemStack.Attributes.GetInt("quenchIteration"); - var temperIteration = workItemStack.Attributes.GetInt("temperIteration"); - - var localizedStringQ = Lang.Get("itemstack-quenchable", - metalProps.quenchMinTemp, - metalProps.quenchMaxTemp); - - dsc.AppendLine(localizedStringQ); - - if (temperature > metalProps.quenchMinTemp && temperature < metalProps.quenchMaxTemp) - { - var replacementQ = TemperatureRangeRegex().Replace(localizedStringQ, - SetColor("$1", Constants.QuenchableColor)); - - dsc.Replace(localizedStringQ, replacementQ); - } - - if (quenchIteration > temperIteration) - { - var localizedStringT = Lang.Get("itemstack-temperable", - metalProps.temperMinTemp, - metalProps.temperMaxTemp); - - dsc.AppendLine(localizedStringT); - - if (temperature > metalProps.temperMinTemp && temperature < metalProps.temperMaxTemp) - { - var replacementT = TemperatureRangeRegex().Replace(localizedStringT, - SetColor("$1", Constants.QuenchableColor)); - - dsc.Replace(localizedStringT, replacementT); - } - } - } - - if (temperature < workableTemp) return; + (int)__instance.WorkItemStack.Collectible.GetTemperature(__instance.Api.World, __instance.WorkItemStack); + var workableTemp = __instance.WorkItemStack.GetWorkableTemperature(); + if (!(temperature > workableTemp)) return; var localizedString = Lang.Get("forge-contentsandtemp", __instance.WorkItemStack.StackSize, __instance.WorkItemStack.GetName(), temperature); - var replacement = TemperatureValueRegex().Replace(localizedString, - SetColor("$1", Constants.AnvilWorkableColor)); + const string pattern = @"(\d+(.*)°C)"; + var replacement = Regex.Replace(localizedString, pattern, + $"$1"); dsc.Replace(localizedString, replacement); } - - [GeneratedRegex(TemperatureRegexPattern)] - public static partial Regex TemperatureValueRegex(); - - [GeneratedRegex(TemperatureRangeRegexPattern)] - public static partial Regex TemperatureRangeRegex(); - - - public static string SetColor(T value, string color) - { - return $"{value}"; - } } \ No newline at end of file diff --git a/SmithingPlus/Common/CollectibleBehaviorRecycledBit.cs b/SmithingPlus/Common/CollectibleBehaviorRecycledBit.cs index 53f0748..8373a93 100644 --- a/SmithingPlus/Common/CollectibleBehaviorRecycledBit.cs +++ b/SmithingPlus/Common/CollectibleBehaviorRecycledBit.cs @@ -45,16 +45,6 @@ public override void OnCreatedByCrafting( var stack = slot.Itemstack; if (stack == null) continue; - var consumedStackSize = - 0; // Use this NOT stack.StackSize because that could have more items than the recipe requires - foreach (var ingredient in byRecipe.RecipeIngredients) - { - if (!ingredient.SatisfiesAsIngredient(stack) || ingredient.ResolvedItemStack == null) - continue; - consumedStackSize = ingredient.ResolvedItemStack.StackSize; - break; - } - var voxelsForThisStack = 0; // Work item with serialized voxel field @@ -73,6 +63,17 @@ public override void OnCreatedByCrafting( var cheapestOutput = Math.Max(cheapestRecipe.Output.ResolvedItemStack.StackSize, 1); var recipeMaterialVoxels = cheapestRecipe.Voxels.VoxelCount(); var voxelsPerItem = Math.Max(recipeMaterialVoxels / cheapestOutput, 0); + + var consumedStackSize = + 0; // Use this NOT stack.StackSize because that could have more items than the recipe requires + foreach (var ingredient in byRecipe.RecipeIngredients) + { + if (!ingredient.SatisfiesAsIngredient(stack) || ingredient.ResolvedItemStack == null) + continue; + consumedStackSize = ingredient.ResolvedItemStack.StackSize; + break; + } + voxelsForThisStack = voxelsPerItem * consumedStackSize; } } diff --git a/SmithingPlus/Core.cs b/SmithingPlus/Core.cs index 0e4b3eb..62af906 100644 --- a/SmithingPlus/Core.cs +++ b/SmithingPlus/Core.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using HarmonyLib; using JetBrains.Annotations; using SmithingPlus.BitsRecovery; @@ -28,6 +29,10 @@ public partial class Core : ModSystem public static Harmony HarmonyInstance { get; private set; } public static ServerConfig Config => ConfigLoader.Config; + public static Dictionary SmithingRecipesByOutputCode { get; } = new(); + public static Dictionary> SmithingRecipesByIngredientCode { get; } = new(); + public static Dictionary> GridRecipesByIngredientCode { get; } = new(); + public override void StartPre(ICoreAPI api) { Logger = Mod.Logger; @@ -75,16 +80,22 @@ private static void AddEntityBehaviors(Entity entity) public override void AssetsFinalize(ICoreAPI api) { base.AssetsFinalize(api); + + var recipeRegistry = api.ModLoader.GetModSystem(); + var recipes = recipeRegistry.SmithingRecipes; + var ingotCode = new AssetLocation("game:ingot-copper"); + var ingotRecipe = api.Side.IsServer() + ? recipes.FirstOrDefault(r => + r.Ingredient?.Code?.Equals(ingotCode) == true && + r.Output.ResolvedItemstack?.Collectible.Code.Equals(ingotCode) == true) + : null; + foreach (var collObj in api.World.Collectibles.Where(c => c?.Code != null)) { collObj.AddBehaviorIf( api.Side == EnumAppSide.Client && Config.ShowWorkableTemperature && collObj.GetCollectibleInterface() is not null); - collObj.AddBehaviorIf( - api.Side == EnumAppSide.Client && - Config.ShowWorkableTemperature && - collObj.HasBehavior()); collObj.AddBehaviorIf(Config.RecoverBitsOnSplit && collObj is ItemChisel); collObj.AddBehaviorIf(Config.RecoverBitsOnSplit && @@ -104,26 +115,11 @@ public override void AssetsFinalize(ICoreAPI api) else if (WildcardUtil.Match(Config.WorkItemSelector, collObj.Code.ToString())) collObj.AddBehavior(); - // Adds workable-only smithing recipes to make ingots. - // This is a bit hacky as workable crafting uses the original - // ingot recipes, so to have these recipes be present we need ingot -> ingot recipes. - // These won't show up when smithing with - // ingots, however, - // since the original ingot recipe (in the smithingplus domain) has "recipeAttributes": - // { "workableRecipe": true } - // A better solution would be - // to define the recipe with code instead of cloning an ingot recipe defined in the assets if (api.Side.IsClient()) continue; - var ingotCode = new AssetLocation("game:ingot-copper"); - var ingotRecipe = api.ModLoader.GetModSystem().SmithingRecipes - .FirstOrDefault(r => - r.Ingredient?.Code?.Equals(ingotCode) == true && - r.Output.ResolvedItemstack?.Collectible.Code.Equals(ingotCode) == true); if (ingotRecipe?.Ingredient == null) continue; if (!WildcardUtil.Match(Config.IngotSelector, collObj.Code.ToString())) continue; - if (api.ModLoader.GetModSystem().SmithingRecipes - .Any(r => r.Ingredient?.Code?.Equals(collObj.Code) == true && - r.Output.ResolvedItemstack?.Collectible.Code.Equals(collObj.Code) == true)) continue; + if (recipes.Any(r => r.Ingredient?.Code?.Equals(collObj.Code) == true && + r.Output.ResolvedItemstack?.Collectible.Code.Equals(collObj.Code) == true)) continue; Logger.VerboseDebug($"Adding workable-only ingot recipe for {collObj.Code}"); var newRecipe = new SmithingRecipe { @@ -143,11 +139,53 @@ public override void AssetsFinalize(ICoreAPI api) Code = collObj.Code, StackSize = 1 }, - RecipeId = api.ModLoader.GetModSystem().SmithingRecipes.Count + 1 + RecipeId = recipes.Count + 1 }; newRecipe.Ingredient.Resolve(api.World, $"[{ModId}] add ingot smithing recipe"); newRecipe.Output.Resolve(api.World, $"[{ModId}] add ingot smithing recipe"); - api.ModLoader.GetModSystem().SmithingRecipes.Add(newRecipe); + recipes.Add(newRecipe); + } + + SmithingRecipesByOutputCode.Clear(); + foreach (var recipe in recipes) + { + var outputCode = recipe.Output?.ResolvedItemstack?.Collectible?.Code; + if (outputCode != null && !SmithingRecipesByOutputCode.ContainsKey(outputCode)) + SmithingRecipesByOutputCode[outputCode] = recipe; + } + + SmithingRecipesByIngredientCode.Clear(); + foreach (var recipe in recipes) + { + if (recipe.Ingredients == null) continue; + foreach (var ing in recipe.Ingredients) + { + var ingCode = ing?.ResolvedItemStack?.Collectible?.Code; + if (ingCode == null) continue; + if (!SmithingRecipesByIngredientCode.TryGetValue(ingCode, out var list)) + { + list = new List(); + SmithingRecipesByIngredientCode[ingCode] = list; + } + list.Add(recipe); + } + } + + GridRecipesByIngredientCode.Clear(); + foreach (var recipe in api.World.GridRecipes) + { + if (recipe.RecipeIngredients == null) continue; + foreach (var ing in recipe.RecipeIngredients) + { + var ingCode = ing?.ResolvedItemStack?.Collectible?.Code; + if (ingCode == null) continue; + if (!GridRecipesByIngredientCode.TryGetValue(ingCode, out var list)) + { + list = new List(); + GridRecipesByIngredientCode[ingCode] = list; + } + list.Add(recipe); + } } } diff --git a/SmithingPlus/Properties/launchSettings.json b/SmithingPlus/Properties/launchSettings.json index 64b79ab..e0dd709 100644 --- a/SmithingPlus/Properties/launchSettings.json +++ b/SmithingPlus/Properties/launchSettings.json @@ -33,8 +33,7 @@ "DOTNET_gcServer": "1", "DOTNET_GCNoAffinitize": "1", "DOTNET_TieredPGO": "1", - "DYLD_LIBRARY_PATH": "$(VINTAGE_STORY)/Lib", - "DOTNET_HOTRELOAD_ENABLED": "1" + "DYLD_LIBRARY_PATH": "$(VINTAGE_STORY)/Lib" } } } diff --git a/SmithingPlus/SmithingPlus.csproj b/SmithingPlus/SmithingPlus.csproj index 78175f1..db6319c 100644 --- a/SmithingPlus/SmithingPlus.csproj +++ b/SmithingPlus/SmithingPlus.csproj @@ -11,39 +11,39 @@ - $(VINTAGE_STORY)/VintagestoryAPI.dll + $(VINTAGE_STORY)\VintagestoryAPI.dll false - $(VINTAGE_STORY)/Mods/VSSurvivalMod.dll + $(VINTAGE_STORY)\Mods\VSSurvivalMod.dll False - $(VINTAGE_STORY)/Mods/VSEssentials.dll + $(VINTAGE_STORY)\Mods\VSEssentials.dll False - $(VINTAGE_STORY)/Mods/VSCreativeMod.dll + $(VINTAGE_STORY)\Mods\VSCreativeMod.dll False - $(VINTAGE_STORY)/Lib/Newtonsoft.Json.dll + $(VINTAGE_STORY)\Lib\Newtonsoft.Json.dll False - $(VINTAGE_STORY)/Lib/protobuf-net.dll + $(VINTAGE_STORY)\Lib\protobuf-net.dll False - $(VINTAGE_STORY)/Lib/0Harmony.dll + $(VINTAGE_STORY)\Lib\0Harmony.dll False - $(VINTAGE_STORY)/VintagestoryLib.dll + $(VINTAGE_STORY)\VintagestoryLib.dll false - $(VINTAGE_STORY)/Lib/cairo-sharp.dll + $(VINTAGE_STORY)\Lib\cairo-sharp.dll False diff --git a/SmithingPlus/Util/CollectibleExtensions.cs b/SmithingPlus/Util/CollectibleExtensions.cs index 30bec46..65680fa 100644 --- a/SmithingPlus/Util/CollectibleExtensions.cs +++ b/SmithingPlus/Util/CollectibleExtensions.cs @@ -73,33 +73,52 @@ public static bool MatchesToolHeadSelector(this CollectibleObject collObj, bool public static SmithingRecipe? GetSmithingRecipe(this CollectibleObject collObj, ICoreAPI api) { - var smithingRecipe = api.ModLoader + var cache = Core.SmithingRecipesByOutputCode; + if (cache.Count > 0) + { + cache.TryGetValue(collObj.Code, out var cached); + return cached; + } + return api.ModLoader .GetModSystem() .SmithingRecipes .FirstOrDefault(r => r.Output.ResolvedItemstack.Collectible.Code.Equals(collObj.Code)); - return smithingRecipe; } public static IEnumerable GetSmithingRecipesAsIngredient(this CollectibleObject collObj, ICoreAPI api) { - var smithingRecipes = + var cache = Core.SmithingRecipesByIngredientCode; + if (cache.Count > 0) + { + return cache.TryGetValue(collObj.Code, out var cached) + ? cached + : System.Linq.Enumerable.Empty(); + } + return from recipe in api.ModLoader.GetModSystem().SmithingRecipes from ing in recipe.Ingredients - where ing.ResolvedItemStack?.Collectible?.Code?.Equals(collObj.Code) is true + where ing.ResolvedItemStack is not null && + ing.ResolvedItemStack.Collectible.Code.Equals(collObj.Code) select recipe; - return smithingRecipes; } public static IEnumerable GetGridRecipesAsIngredient(this CollectibleObject collObj, ICoreAPI api) { - var gridRecipes = + var cache = Core.GridRecipesByIngredientCode; + if (cache.Count > 0) + { + return cache.TryGetValue(collObj.Code, out var cached) + ? cached + : System.Linq.Enumerable.Empty(); + } + return from recipe in api.World.GridRecipes + where recipe.RecipeIngredients != null from ing in recipe.RecipeIngredients where ing is { ResolvedItemStack.Collectible: not null } && - ing.ResolvedItemStack?.Collectible?.Code?.Equals(collObj.Code) is true + ing.ResolvedItemStack.Collectible.Code.Equals(collObj.Code) select recipe; - return gridRecipes; } public static CollectibleObject? CollectibleWithVariant(this CollectibleObject collObj, string type, string value) @@ -130,12 +149,11 @@ public static T GetBehavior(this CollectibleObject collObj, bool withInherita return (T)collObj.GetCollectibleBehavior(typeof(T), withInheritance); } - /// - /// Gets the metal properties variant from a CollectibleBehaviorQuenchable behavior. - /// - public static CollectibleBehaviorQuenchable.MetalPropertyVariant? GetMetalProps( - this CollectibleBehaviorQuenchable behavior) - { - return behavior?.GetField("metalProps"); - } + /* + Regex matching is slow. + Only use when first assigning behaviors. + At runtime, check for CollectibleBehaviorRepairableTool instead. + */ + + // Same as above, check for CollectibleBehaviorRepairableToolHead or CollectibleBehaviorCastToolHead instead } \ No newline at end of file diff --git a/SmithingPlus/Util/Constants.cs b/SmithingPlus/Util/Constants.cs index dcbb3ec..0f2bae8 100644 --- a/SmithingPlus/Util/Constants.cs +++ b/SmithingPlus/Util/Constants.cs @@ -3,7 +3,6 @@ namespace SmithingPlus.Util; public static class Constants { internal const string AnvilWorkableColor = "#00A36C"; - internal const string QuenchableColor = "darkcyan"; internal const string Sp = "sp"; } diff --git a/SmithingPlus/Util/ItemStackExtensions.cs b/SmithingPlus/Util/ItemStackExtensions.cs index e647817..2ed9fde 100644 --- a/SmithingPlus/Util/ItemStackExtensions.cs +++ b/SmithingPlus/Util/ItemStackExtensions.cs @@ -1,8 +1,6 @@ #nullable enable -using System; using System.Collections.Generic; using System.Linq; -using SmithingPlus.Common.Metal; using Vintagestory.API.Common; using Vintagestory.API.Datastructures; using Vintagestory.GameContent; @@ -110,7 +108,7 @@ internal static void CloneRepairedToolStackOrAttributes(this ItemStack itemStack internal static int GetBrokenCount(this ItemStack itemStack) { var repairedStack = itemStack.GetRepairedToolStack(); - return repairedStack?.GetBrokenCount() ?? itemStack.Attributes?.GetInt(ModStackAttributes.BrokenCount) ?? 0; + return repairedStack?.GetBrokenCount() ?? (itemStack.Attributes?.GetInt(ModStackAttributes.BrokenCount) ?? 0); } public static bool CodeMatches(this ItemStack stack, ItemStack that) @@ -120,9 +118,7 @@ public static bool CodeMatches(this ItemStack stack, ItemStack that) public static float GetWorkableTemperature(this ItemStack stack) { - var meltingPoint = stack.Collectible.CombustibleProps?.MeltingPoint - ?? stack.GetOrCacheMetalMaterial(Core.Api)?.MetalBitItem?.CombustibleProps?.MeltingPoint - ?? 0f; + var meltingPoint = stack.Collectible.CombustibleProps?.MeltingPoint ?? 0.0f; var defaultTemperature = meltingPoint / 2f; return stack.ItemAttributes?["workableTemperature"]?.AsFloat(defaultTemperature) ?? defaultTemperature; } @@ -224,47 +220,4 @@ public static bool IsCastTool(this ItemStack stack) { return stack.Attributes.GetBool(ModStackAttributes.CastTool); } - - /// - /// Get the metal bits that should be recovered when this broken/shattered item is destroyed. - /// Takes into account the item's durability percentage and metal bit ratio. - /// - public static ItemStack? GetShatteredBitsStack(this ItemStack brokenStack, ICoreAPI api) - { - var metalMaterial = brokenStack.GetOrCacheMetalMaterial(api); - if (metalMaterial?.MetalBitStack == null) - return null; - - var voxelsInStack = 0; - // Work item with serialized voxel field - if (brokenStack.Collectible is ItemWorkItem) - { - var bytes = brokenStack.Attributes.GetBytes("voxels"); - var voxels = BlockEntityAnvil.deserializeVoxels(bytes); - voxelsInStack = voxels.MaterialCount(); - } - // Finished smithed item -> get via cheapest smithing recipe to prevent abuse of the mechanic - else - { - var cheapestRecipe = brokenStack.GetCheapestSmithingRecipe(api); - if (cheapestRecipe is { Output.ResolvedItemStack: not null }) - { - var cheapestOutput = Math.Max(cheapestRecipe.Output.ResolvedItemStack.StackSize, 1); - var recipeMaterialVoxels = cheapestRecipe.Voxels.VoxelCount(); - var voxelsPerItem = Math.Max(recipeMaterialVoxels / cheapestOutput, 0); - voxelsInStack = voxelsPerItem * brokenStack.StackSize; - } - } - - var durabilityPercentage = brokenStack.GetDurabilityPercentage() ?? 1f; - var reducedVoxels = voxelsInStack * durabilityPercentage; - var recoveredBits = (int)MathF.Floor(reducedVoxels / Core.Config.VoxelsPerBit); - - if (recoveredBits <= 0) - return null; - - var bitsStack = metalMaterial.MetalBitStack.Clone(); - bitsStack.StackSize = recoveredBits; - return bitsStack; - } } \ No newline at end of file diff --git a/SmithingPlus/assets/smithingplus/patches/compatibility/xskills-metalbit.json b/SmithingPlus/assets/smithingplus/patches/compatibility/xskills-metalbit.json index 7743c1d..0e04d26 100644 --- a/SmithingPlus/assets/smithingplus/patches/compatibility/xskills-metalbit.json +++ b/SmithingPlus/assets/smithingplus/patches/compatibility/xskills-metalbit.json @@ -9,7 +9,7 @@ "side": "Server", "dependsOn": [ { - "modid": "xskills" + "modid": "xskillsforked" } ] }, @@ -28,7 +28,7 @@ "side": "Server", "dependsOn": [ { - "modid": "xskills" + "modid": "xskillsforked" } ] } diff --git a/SmithingPlus/assets/smithingplus/patches/metalbit.json b/SmithingPlus/assets/smithingplus/patches/metalbit.json index d6021db..354013d 100644 --- a/SmithingPlus/assets/smithingplus/patches/metalbit.json +++ b/SmithingPlus/assets/smithingplus/patches/metalbit.json @@ -14,17 +14,6 @@ "isValue": "true" } }, - { - "file": "game:itemtypes/resource/nugget.json", - "op": "move", - "frompath": "/behaviorsByType/*", - "path": "/smithingplustemp", - "side": "Server", - "condition": { - "when": "SmithingPlus_WorkableBits", - "isValue": "true" - } - }, { "file": "game:itemtypes/resource/nugget.json", "op": "addMerge", @@ -56,17 +45,6 @@ "isValue": "true" } }, - { - "file": "game:itemtypes/resource/nugget.json", - "op": "move", - "frompath": "/smithingplustemp", - "path": "/behaviorsByType/*", - "side": "Server", - "condition": { - "when": "SmithingPlus_WorkableBits", - "isValue": "true" - } - }, { "file": "game:itemtypes/resource/metalbit.json", "op": "addMerge", diff --git a/SmithingPlus/changelog.md b/SmithingPlus/changelog.md index 2aba204..844789e 100644 --- a/SmithingPlus/changelog.md +++ b/SmithingPlus/changelog.md @@ -11,10 +11,7 @@ - TODO [Fix] Fix nugget recipes etc with better system - TODO [Feature] Add better quenching and tempering tooltips -# v1.9.0-rc.1 +# v1.9.0-dev.2 -- **Feature**: Items shattered when quenching now drop bits, proportional to durability. Idea: 🐰 -- **Feature**: Now forge tooltips show when an item is ready to quench or temper. -- **Feature**: Quenchable/temperable temperatures in item tooltips will now be highlighted in cyan when they are ready to - quench/temper -- **Fix**: Native copper nuggets could not be used when smithing anymore \ No newline at end of file +- **Fix**: Fix handbook crash with null drop itemstack attribute in certain mold items +- **Tweak**: Remove tool buffs (sharpening, etc) on repair. Can be adjusted in config "ToolRepairForgettableAttributes". \ No newline at end of file diff --git a/SmithingPlus/modinfo.json b/SmithingPlus/modinfo.json index da052c2..c02ba81 100644 --- a/SmithingPlus/modinfo.json +++ b/SmithingPlus/modinfo.json @@ -7,7 +7,7 @@ "jayu" ], "description": "Metal tool repair, smithing tweaks and quality of life improvements.", - "version": "1.9.0-rc.1", + "version": "1.9.0-dev.2", "dependencies": { "game": "" }