From 8ce67a7fae457aaf65b9cfca195b4cb386279b49 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Fri, 10 Mar 2023 22:48:34 +0700 Subject: [PATCH] Make special recipe finder for crafting table recipes, make use of residue logic --- .../mc/otm/matter/MatterManager.kt | 235 +++++++++++++++--- .../otm_recipe_finder/crafting.json | 2 +- 2 files changed, 206 insertions(+), 31 deletions(-) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt index 3c93b2f80..3ceaa4060 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt @@ -15,6 +15,7 @@ import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream import it.unimi.dsi.fastutil.objects.Object2BooleanFunction import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap import it.unimi.dsi.fastutil.objects.Object2IntArrayMap +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet import it.unimi.dsi.fastutil.objects.Reference2BooleanFunction import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction @@ -35,12 +36,16 @@ import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener import net.minecraft.tags.TagKey import net.minecraft.util.profiling.ProfilerFiller import net.minecraft.world.Container +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.AbstractContainerMenu +import net.minecraft.world.inventory.CraftingContainer import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack import net.minecraft.world.item.crafting.Recipe import net.minecraft.world.item.crafting.RecipeType import net.minecraft.world.level.ItemLike import net.minecraftforge.common.capabilities.ForgeCapabilities +import net.minecraftforge.common.crafting.IShapedRecipe import net.minecraftforge.event.AddReloadListenerEvent import net.minecraftforge.event.OnDatapackSyncEvent import net.minecraftforge.event.RegisterCommandsEvent @@ -64,6 +69,7 @@ import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive import ru.dbotthepony.mc.otm.client.isShiftDown import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.container.set import ru.dbotthepony.mc.otm.container.stream import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.TextComponent @@ -494,7 +500,7 @@ object MatterManager { ResolvedRecipe( it.ingredients.stream() .filter { !it.isActuallyEmpty } - .map { it.items.stream().map(::ImmutableStack) }, + .map { it.items.stream().map(::RecipeEntry) }, ImmutableStack(it.resultItem), isCritical = isCritical, name = it.id, @@ -511,6 +517,91 @@ object MatterManager { } } } + + registrar.register("crafting") { + Finder { server, data -> + val allowBacktrack = data["allow_backtrack"]?.asBoolean ?: true + val ignoreDamageables = data["ignore_damageables"]?.asBoolean ?: false + val isCritical = data["is_critical"]?.asBoolean ?: true + var stream = server.recipeManager.byType(RecipeType.CRAFTING).values.parallelStream().filter { !it.isIncomplete } + + if (ignoreDamageables) { + stream = stream.filter { it.ingredients.stream().flatMap { it.items.stream() }.noneMatch { it.isDamageableItem } } + } + + stream.map { + val width: Int + val height: Int + + if (it is IShapedRecipe<*>) { + width = it.recipeWidth + height = it.recipeHeight + } else { + width = it.ingredients.size + height = 1 + } + + val container = CraftingContainer(object : AbstractContainerMenu(null, 0) { + override fun quickMoveStack(pPlayer: Player, pIndex: Int): ItemStack { + return ItemStack.EMPTY + } + + override fun stillValid(pPlayer: Player): Boolean { + return false + } + }, width, height) + + val realIngredients = ArrayList>() + + for (c in it.ingredients.indices) { + if (it.ingredients[c].isActuallyEmpty) { + continue + } + + for ((i, ingredient) in it.ingredients.withIndex()) { + if (i != c) { + container[i] = if (ingredient.isActuallyEmpty) ItemStack.EMPTY else ingredient.items.firstOrNull() ?: ItemStack.EMPTY + } + } + + val result = ArrayList() + + for (item in it.ingredients[c].items) { + container[c] = item + + if (!it.assemble(container).isEmpty) { + val residue = it.getRemainingItems(container) + + val thisResidue = residue[c] + + if (thisResidue.isEmpty) { + result.add(RecipeEntry(item)) + } else { + result.add(RecipeEntry(ImmutableStack(item), ImmutableStack(thisResidue))) + } + } + } + + realIngredients.add(result) + } + + ResolvedRecipe( + realIngredients.stream().map { it.stream() }, + ImmutableStack(it.resultItem), + isCritical = isCritical, + name = it.id, + allowBacktrack = allowBacktrack + ) + }.filter { + if (it.inputs.isEmpty()) { + LOGGER.warn("${it.formattedName} with output '${it.output.item.registryName}' ended up with no inputs!") + false + } else { + true + } + } + } + } } private var foundResolvers: Map> = ImmutableMap.of() @@ -586,7 +677,7 @@ object MatterManager { for (input1 in recipe.inputs) { for (input in input1) { - input2Recipes.computeIfAbsent(input.item, Reference2ObjectFunction { ReferenceOpenHashSet() }).add(recipe) + input2Recipes.computeIfAbsent(input.input.item, Reference2ObjectFunction { ReferenceOpenHashSet() }).add(recipe) } } } @@ -606,7 +697,7 @@ object MatterManager { private fun doTryToBacktrack(item: Item, makeCommentary: Boolean): Result { val recipes = input2Recipes[item] - if (recipes == null || recipes.isEmpty() || !recipes.all { it.allowBacktrack } || !recipes.all { it.inputs.all { it.all { it.item == item } } }) { + if (recipes == null || recipes.isEmpty() || !recipes.all { it.allowBacktrack } || !recipes.all { it.inputs.all { it.all { it.input.item == item } } }) { if (makeCommentary) comment(item, TextComponent("Item '${item.registryName}' has no recipes")) @@ -713,28 +804,29 @@ object MatterManager { inputsLoop@ for ((i, inputs) in recipe.inputs.withIndex()) { var minimal: IMatterValue? = null var minimalMultiplier = 0.0 - val skips = ArrayList() - val recursiveSkips = ArrayList() + val skips = ArrayList() + val recursiveSkips = ArrayList() + var hadResidue = false - innerInputsLoop@ for (input in inputs) { - val ivalue = determineValue(input.item) + innerInputsLoop@ for (entry in inputs) { + val ivalue = determineValue(entry.input.item) if (ivalue.isMissing) { - comment(item, TextComponent("Input '${input.item.registryName}' at input slot $i in ${recipe.formattedName} has no matter value")) + comment(item, TextComponent("Input $entry at input slot $i in ${recipe.formattedName} has no matter value")) if (recipe.isCritical && !lenientStage) { return Result.MISSING } else { if (recipe.isCritical) { - skips.add(input) + skips.add(entry) continue@innerInputsLoop } else { continue@recipesLoop } } } else if (ivalue.isSkipped) { - comment(item, TextComponent("Input '${input.item.registryName}' at input slot $i in ${recipe.formattedName} is recursive")) - recursiveSkips.add(input) + comment(item, TextComponent("Input $entry at input slot $i in ${recipe.formattedName} is recursive")) + recursiveSkips.add(entry) if (inputs.size == 1) { hadSkips = true @@ -744,14 +836,52 @@ object MatterManager { } } - if (minimal == null || minimal > ivalue.value!!) { - minimal = ivalue.value - minimalMultiplier = input.multiplier + var realValue = ivalue.value!! + val ivalue2 = entry.remainder?.item?.let(::determineValue) + + if (ivalue2 != null) { + if (ivalue2.isMissing) { + comment(item, TextComponent("Remainder of $entry at input slot $i in ${recipe.formattedName} has no matter value")) + + if (recipe.isCritical && !lenientStage) { + return Result.MISSING + } else { + if (recipe.isCritical) { + skips.add(entry) + continue@innerInputsLoop + } else { + continue@recipesLoop + } + } + } else if (ivalue2.isSkipped) { + comment(item, TextComponent("Remainder of $entry at input slot $i in ${recipe.formattedName} is recursive")) + recursiveSkips.add(entry) + + if (inputs.size == 1) { + hadSkips = true + continue@recipesLoop + } else { + continue@innerInputsLoop + } + } + + realValue = MatterValue(realValue.matter - ivalue2.value!!.matter, realValue.complexity - ivalue2.value.complexity) + hadResidue = true + + if (realValue.matter.isNegative || realValue.complexity < 0.0) { + comment(item, TextComponent("Entry $entry at input slot $i in ${recipe.formattedName} ended up with negative matter value or/and complexity")) + return Result.MISSING + } + } + + if (minimal == null || minimal > realValue) { + minimal = realValue + minimalMultiplier = entry.input.multiplier } } if (skips.size > inputs.size / 2 || skips.isNotEmpty() && inputs.size == 1) { - comment(item, TextComponent("More than half inputs (${skips.joinToString(", ") { it.item.registryName.toString() }}) at input slot $i in ${recipe.formattedName} have no matter values")) + comment(item, TextComponent("More than half inputs (${skips.joinToString(", ")}) at input slot $i in ${recipe.formattedName} have no matter values")) return Result.MISSING } else if (skips.isNotEmpty()) { /** @@ -769,7 +899,7 @@ object MatterManager { continue } - val list = manager.getReverseTag(input.item).getOrNull()?.tagKeys?.collect(ImmutableList.toImmutableList()) ?: ImmutableList.of() + val list = manager.getReverseTag(input.input.item).getOrNull()?.tagKeys?.collect(ImmutableList.toImmutableList()) ?: ImmutableList.of() if (input !in skips) { for (tag in list) @@ -786,7 +916,7 @@ object MatterManager { val result = filtered.all { tagSetsMissing.getInt(it.key) == skips.size } if (!result) { - comment(item, TextComponent("More than half inputs (${skips.joinToString(", ") { it.item.registryName.toString() }}) at input slot $i in ${recipe.formattedName} have no matter values")) + comment(item, TextComponent("More than half inputs (${skips.joinToString(", ")}) at input slot $i in ${recipe.formattedName} have no matter values")) return Result.MISSING } else { comment(item, TextComponent("${recipe.formattedName} with inputs at slot $i without matter values were allowed to be skipped due to next tags:")) @@ -797,8 +927,13 @@ object MatterManager { } } + if (minimal != null && !minimal.hasMatterValue && hadResidue && inputs.size == 1) { + // ингредиент ничего не стоит ибо не потребляется в крафте + continue@inputsLoop + } + if (minimal == null || !minimal.hasMatterValue) { - comment(item, TextComponent("'${recipe.formattedName}' has invalid input at slot $i (possible inputs: ${inputs.joinToString(", ", transform = { it.item.registryName.toString() }) }) (???)")) + comment(item, TextComponent("'${recipe.formattedName}' has invalid input at slot $i (possible inputs: ${inputs.joinToString(", ")}) (???)")) return Result.MISSING } @@ -1241,8 +1376,20 @@ object MatterManager { constructor(item: ItemStack) : this(item.item, item.count) } + data class RecipeEntry( + val input: ImmutableStack, + val remainder: ImmutableStack? = null + ) { + constructor(item: Item, count: Int) : this(ImmutableStack(item, count)) + constructor(item: ItemStack) : this(ImmutableStack(item)) + + override fun toString(): String { + return "RecipeEntry[${input.item.registryName} to ${remainder?.item?.registryName}]" + } + } + class ResolvedRecipe( - inputs: Stream>, + inputs: Stream>, val output: ImmutableStack, val transformMatterValue: (Decimal) -> Decimal = { it }, val transformComplexity: (Double) -> Double = { it }, @@ -1260,25 +1407,42 @@ object MatterManager { * (e.g. 4 rabbit hides -> 1 leather), we can assume that we can determine matter value of ingredients based on matter value of output * * "Backtrack" can happen if everything of the next holds true: - * * All recipes with item in question contains only that item; and + * * All recipes with item in question contains only that item; * * All recipes allow backtracking */ val allowBacktrack: Boolean = true, ) { - val inputs: List> = inputs - .map { it.filter { it.multiplier > 0.0 }.collect(ImmutableList.toImmutableList()) } + val inputs: List> = inputs + .map { it.filter { it.input.multiplier > 0.0 }.collect(ImmutableList.toImmutableList()) } .filter { it.isNotEmpty() } .collect(ImmutableList.toImmutableList()) - constructor( - inputs: Collection>, - output: ImmutableStack, - transformMatterValue: (Decimal) -> Decimal = { it }, - transformComplexity: (Double) -> Double = { it }, - ) : this(inputs.stream().map { it.stream() }, output, transformMatterValue, transformComplexity) - val formattedName: String get() = if (name == null) "One of recipes" else "Recipe '$name'" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ResolvedRecipe) return false + + if (output != other.output) return false + if (transformMatterValue != other.transformMatterValue) return false + if (transformComplexity != other.transformComplexity) return false + if (isCritical != other.isCritical) return false + if (allowBacktrack != other.allowBacktrack) return false + if (inputs != other.inputs) return false + + return true + } + + override fun hashCode(): Int { + var result = output.hashCode() + result = 31 * result + transformMatterValue.hashCode() + result = 31 * result + transformComplexity.hashCode() + result = 31 * result + isCritical.hashCode() + result = 31 * result + allowBacktrack.hashCode() + result = 31 * result + inputs.hashCode() + return result + } } fun interface Finder { @@ -1293,7 +1457,7 @@ object MatterManager { * * You can safely call [MatterManager.getDirect] inside returned stream, both parallel and sequential. */ - fun find(server: MinecraftServer, json: JsonObject): Stream + fun find(server: MinecraftServer, data: JsonObject): Stream } /** @@ -1304,7 +1468,18 @@ object MatterManager { return Registry.direct(value) } + /** + * Access recipe finders registry + * + * @throws IllegalStateException if calling too early + */ @JvmStatic val recipeFinders get() = Resolver.delegate.get() + + /** + * Access recipe finders registry key + * + * Use this with your [DeferredRegister] + */ @JvmStatic val recipeFindersKey get() = Resolver.delegate.key private val commentary = Reference2ObjectOpenHashMap>() diff --git a/src/main/resources/data/overdrive_that_matters/otm_recipe_finder/crafting.json b/src/main/resources/data/overdrive_that_matters/otm_recipe_finder/crafting.json index b24f469cb..2e0353a3a 100644 --- a/src/main/resources/data/overdrive_that_matters/otm_recipe_finder/crafting.json +++ b/src/main/resources/data/overdrive_that_matters/otm_recipe_finder/crafting.json @@ -1,5 +1,5 @@ { "recipe_type": "minecraft:crafting", - "type": "overdrive_that_matters:simple", + "type": "overdrive_that_matters:crafting", "is_critical": true } \ No newline at end of file