Recipe matching system rewrite#715
Conversation
| operator fun contains(itemStack: ItemStack): Boolean { | ||
| for (item in items) { | ||
| if (item.createItemStack().matchesWithoutData(itemStack, ignoreComponents, true)) { | ||
| return true | ||
| } | ||
| } | ||
| return false | ||
| } |
There was a problem hiding this comment.
Not sure how I feel about creating an itemstack just to get it garbage collected this many times potentially.
Maybe we could have a cache or something so that there's just one immutable instance used or something but yeah.
There was a problem hiding this comment.
made it so rebar items match against the template item but the vanilla item is a record so i cant put a cache field, do you think its worth it to derecord it
|
you can never hate mojang enough bro |
|
everythings ready for review, the thing above i think is a minor enough edge case ithink its fine to leave in cuz i genuinely dont know what to do with it besides ticking the furnace itself |
| @Suppress("UnstableApiUsage") | ||
| private inline fun <T : CookingRecipe<T>, U : CookingRecipeWrapper> loadCookingRecipe( | ||
| key: NamespacedKey, | ||
| config: ConfigSection, | ||
| defaultCookingTime: Int, | ||
| cons: (NamespacedKey, ItemStack, RecipeChoice, Float, Int) -> T | ||
| ): T { | ||
| recipeCons: (NamespacedKey, ItemStack, RecipeChoice, Float, Int) -> T, | ||
| wrapperCons: (T, RecipeInput.Item) -> U | ||
| ): U { | ||
| val cookingTime = config.get("cookingtime", ConfigAdapter.INTEGER, defaultCookingTime) | ||
| val experience = config.get("experience", ConfigAdapter.FLOAT, 0f) | ||
| val ingredient = config.getOrThrow("ingredient", ConfigAdapter.RECIPE_INPUT_ITEM) | ||
| if (ingredient.representativeItems.any{ it.hasData(DataComponentTypes.MAX_DAMAGE) }) { | ||
| ingredient.ignoreComponents.add(DataComponentTypes.DAMAGE) | ||
| } | ||
| val result = config.getOrThrow("result", ConfigAdapter.ITEM_STACK) | ||
| val recipe = cons(key, result, ingredient.asRecipeChoice(), experience, cookingTime) | ||
| val recipe = recipeCons(key, result, ingredient.asRecipeChoice(), experience, cookingTime) | ||
| config.get("category", ConfigAdapter.ENUM.from<CookingBookCategory>())?.let { recipe.category = it } | ||
| config.get("group", ConfigAdapter.STRING)?.let { recipe.group = it } | ||
| return recipe | ||
| return wrapperCons(recipe, ingredient) | ||
| } |
There was a problem hiding this comment.
Could we make this not generic (i.e. not take in the constructors as parameters)? This is extremely hard to read as-is, I think it would be a lot more readable split up even though it's more code
|
|
||
| sealed class CookingRecipeWrapper(final override val recipe: CookingRecipe<*>) : VanillaRecipeWrapper { | ||
| override val inputs: List<RecipeInput> = listOf(recipe.inputChoice.asRecipeInput()) | ||
| sealed class CookingRecipeWrapper(final override val recipe: CookingRecipe<*>, val recipeInput: RecipeInput.Item) : VanillaRecipeWrapper { |
There was a problem hiding this comment.
What's the purpose of the new recipeInput parameter? I can't find where it's used if anywhere?
There was a problem hiding this comment.
its used in the matches method which needs the RecipeInput.Item instead of RecipeChoice to properly match rebar items w/out durability (or any other component) for reasons outlined here pylonmc/pylon#962 (comment)
| fun matches(items: List<ItemStack?>): Boolean { | ||
| if (RebarRecipe.matchesShaped(items, recipeInput, 3, 3)) return true | ||
| val mirror = items.toMutableList() | ||
| while (mirror.size < 9) mirror.add(null) | ||
| mirror[2] = mirror[0].also{ mirror[0] = mirror[2] } | ||
| mirror[5] = mirror[3].also{ mirror[3] = mirror[5] } | ||
| mirror[8] = mirror[6].also{ mirror[6] = mirror[8] } | ||
| return RebarRecipe.matchesShaped(mirror, recipeInput, 3, 3) | ||
| } |
There was a problem hiding this comment.
I believe that shaped recipes are automatically mirrored already when added to the server. Or is this because we are manually reimplementing matchesShaped? If so, this should really be in matchesShaped, not in here
| class LRUCache<K, V>(private val initialCapacity: Int) : | ||
| LinkedHashMap<K, V>(initialCapacity, 0.75f, true) { | ||
|
|
||
| override fun removeEldestEntry(eldest: MutableMap.MutableEntry<K, V>?): Boolean { | ||
| return size > initialCapacity | ||
| } | ||
| } No newline at end of file |
There was a problem hiding this comment.
Needs documenting and maybe should also be internal
There was a problem hiding this comment.
A lot of the code in this file is incredibly cursed, e.g. the manual re-implementation of shaped crafting, searchRecipes (which I think is only used by the vanilla wrappers?), etc. I would much prefer to keep this file as clean as possible because it's an important public interface. Can we move this code closer to where it is being used to 1) clean up this file and 2) make it clearer why half of these methods exist? It took me a solid 10-15 minutes just to understand why we were manually re-implementing shaped crafting partially because of the indirection, having this logic in e.g. RecipeListener would be good
| } | ||
| fun clearCache() { cache.clear() } | ||
|
|
||
| inline fun <reified T : RebarRecipe> searchRecipes(recipeType: RecipeType<out T>, hint: NamespacedKey?, hash: Int, pred: Predicate<T>): T? { |
| private fun cacheKey(hash: Int, recipeType: RecipeType<*>) = 31 * hash + recipeType.hashCode() | ||
|
|
||
| fun getCached(hash: Int, recipeType: RecipeType<*>) = cache[cacheKey(hash, recipeType)] | ||
| fun isCached(hash: Int, recipeType: RecipeType<*>) = cacheKey(hash, recipeType) in cache | ||
| fun cache(hash: Int, recipeType: RecipeType<*>, recipe: RebarRecipe?) { | ||
| cache[cacheKey(hash, recipeType)] = recipe | ||
| } | ||
| fun clearCache() { cache.clear() } |
There was a problem hiding this comment.
How would caching work in practice for Pylon recipes? Could you maybe demonstrate it by adding a cache to a few Pylon recipes so we can see what this looks like from a user perspective?
|
Another thing I should flag, recipe autocompletion from the crafting book is very likely to be broken by these changes and will need testing |

This was originally supposed to be the companion to pylonmc/pylon#962 to let damaged items be smelted, but then i decided to make it more general for any component, and now i wanna just overhaul the recipe matching in
RecipeListenerAs of writing, this branch works with pylonmc/pylon#962
Also this is my first time writing kotlin so apologies if its absolute dogsh*t