Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 8 additions & 63 deletions SmithingPlus/ClientTweaks/ShowWorkablePatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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<CollectibleBehaviorQuenchable>()
?.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,
$"<font color=\"{Constants.AnvilWorkableColor}\">$1</font>");
dsc.Replace(localizedString, replacement);
}

[GeneratedRegex(TemperatureRegexPattern)]
public static partial Regex TemperatureValueRegex();

[GeneratedRegex(TemperatureRangeRegexPattern)]
public static partial Regex TemperatureRangeRegex();


public static string SetColor<T>(T value, string color)
{
return $"<font color=\"{color}\">{value}</font>";
}
}
21 changes: 11 additions & 10 deletions SmithingPlus/Common/CollectibleBehaviorRecycledBit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
}
}
Expand Down
86 changes: 62 additions & 24 deletions SmithingPlus/Core.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
using JetBrains.Annotations;
using SmithingPlus.BitsRecovery;
Expand Down Expand Up @@ -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<AssetLocation, SmithingRecipe> SmithingRecipesByOutputCode { get; } = new();
public static Dictionary<AssetLocation, List<SmithingRecipe>> SmithingRecipesByIngredientCode { get; } = new();
public static Dictionary<AssetLocation, List<GridRecipe>> GridRecipesByIngredientCode { get; } = new();

public override void StartPre(ICoreAPI api)
{
Logger = Mod.Logger;
Expand Down Expand Up @@ -75,16 +80,22 @@ private static void AddEntityBehaviors(Entity entity)
public override void AssetsFinalize(ICoreAPI api)
{
base.AssetsFinalize(api);

var recipeRegistry = api.ModLoader.GetModSystem<RecipeRegistrySystem>();
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<CollectibleBehaviorDisplayWorkableTemp>(
api.Side == EnumAppSide.Client &&
Config.ShowWorkableTemperature &&
collObj.GetCollectibleInterface<IAnvilWorkable>() is not null);
collObj.AddBehaviorIf<CollectibleBehaviorQuenchableInfo>(
api.Side == EnumAppSide.Client &&
Config.ShowWorkableTemperature &&
collObj.HasBehavior<CollectibleBehaviorQuenchable>());
collObj.AddBehaviorIf<CollectibleBehaviorScrapeCrucible>(Config.RecoverBitsOnSplit &&
collObj is ItemChisel);
collObj.AddBehaviorIf<CollectibleBehaviorSmeltedContainer>(Config.RecoverBitsOnSplit &&
Expand All @@ -104,26 +115,11 @@ public override void AssetsFinalize(ICoreAPI api)
else if (WildcardUtil.Match(Config.WorkItemSelector, collObj.Code.ToString()))
collObj.AddBehavior<CollectibleBehaviorBrokenToolHead>();

// 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<RecipeRegistrySystem>().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<RecipeRegistrySystem>().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
{
Expand All @@ -143,11 +139,53 @@ public override void AssetsFinalize(ICoreAPI api)
Code = collObj.Code,
StackSize = 1
},
RecipeId = api.ModLoader.GetModSystem<RecipeRegistrySystem>().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<RecipeRegistrySystem>().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<SmithingRecipe>();
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<GridRecipe>();
GridRecipesByIngredientCode[ingCode] = list;
}
list.Add(recipe);
}
}
}

Expand Down
3 changes: 1 addition & 2 deletions SmithingPlus/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
Expand Down
18 changes: 9 additions & 9 deletions SmithingPlus/SmithingPlus.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,39 @@

<ItemGroup>
<Reference Include="VintagestoryAPI">
<HintPath>$(VINTAGE_STORY)/VintagestoryAPI.dll</HintPath>
<HintPath>$(VINTAGE_STORY)\VintagestoryAPI.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="VSSurvivalMod">
<HintPath>$(VINTAGE_STORY)/Mods/VSSurvivalMod.dll</HintPath>
<HintPath>$(VINTAGE_STORY)\Mods\VSSurvivalMod.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VSEssentials">
<HintPath>$(VINTAGE_STORY)/Mods/VSEssentials.dll</HintPath>
<HintPath>$(VINTAGE_STORY)\Mods\VSEssentials.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VSCreativeMod">
<HintPath>$(VINTAGE_STORY)/Mods/VSCreativeMod.dll</HintPath>
<HintPath>$(VINTAGE_STORY)\Mods\VSCreativeMod.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>$(VINTAGE_STORY)/Lib/Newtonsoft.Json.dll</HintPath>
<HintPath>$(VINTAGE_STORY)\Lib\Newtonsoft.Json.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="protobuf-net">
<HintPath>$(VINTAGE_STORY)/Lib/protobuf-net.dll</HintPath>
<HintPath>$(VINTAGE_STORY)\Lib\protobuf-net.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="0Harmony">
<HintPath>$(VINTAGE_STORY)/Lib/0Harmony.dll</HintPath>
<HintPath>$(VINTAGE_STORY)\Lib\0Harmony.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VintagestoryLib">
<HintPath>$(VINTAGE_STORY)/VintagestoryLib.dll</HintPath>
<HintPath>$(VINTAGE_STORY)\VintagestoryLib.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="cairo-sharp">
<HintPath>$(VINTAGE_STORY)/Lib/cairo-sharp.dll</HintPath>
<HintPath>$(VINTAGE_STORY)\Lib\cairo-sharp.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
Expand Down
50 changes: 34 additions & 16 deletions SmithingPlus/Util/CollectibleExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<RecipeRegistrySystem>()
.SmithingRecipes
.FirstOrDefault(r => r.Output.ResolvedItemstack.Collectible.Code.Equals(collObj.Code));
return smithingRecipe;
}

public static IEnumerable<SmithingRecipe> 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<SmithingRecipe>();
}
return
from recipe in api.ModLoader.GetModSystem<RecipeRegistrySystem>().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<GridRecipe> 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<GridRecipe>();
}
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)
Expand Down Expand Up @@ -130,12 +149,11 @@ public static T GetBehavior<T>(this CollectibleObject collObj, bool withInherita
return (T)collObj.GetCollectibleBehavior(typeof(T), withInheritance);
}

/// <summary>
/// Gets the metal properties variant from a CollectibleBehaviorQuenchable behavior.
/// </summary>
public static CollectibleBehaviorQuenchable.MetalPropertyVariant? GetMetalProps(
this CollectibleBehaviorQuenchable behavior)
{
return behavior?.GetField<CollectibleBehaviorQuenchable.MetalPropertyVariant>("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
}
Loading