Make special recipe finder for crafting table recipes, make use of residue logic

This commit is contained in:
DBotThePony 2023-03-10 22:48:34 +07:00
parent 574035774f
commit 8ce67a7fae
Signed by: DBot
GPG Key ID: DCC23B5715498507
2 changed files with 206 additions and 31 deletions

View File

@ -15,6 +15,7 @@ import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
import it.unimi.dsi.fastutil.objects.Object2BooleanFunction import it.unimi.dsi.fastutil.objects.Object2BooleanFunction
import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap 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.Reference2BooleanFunction
import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap
import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction 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.tags.TagKey
import net.minecraft.util.profiling.ProfilerFiller import net.minecraft.util.profiling.ProfilerFiller
import net.minecraft.world.Container 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.Item
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.crafting.Recipe import net.minecraft.world.item.crafting.Recipe
import net.minecraft.world.item.crafting.RecipeType import net.minecraft.world.item.crafting.RecipeType
import net.minecraft.world.level.ItemLike import net.minecraft.world.level.ItemLike
import net.minecraftforge.common.capabilities.ForgeCapabilities import net.minecraftforge.common.capabilities.ForgeCapabilities
import net.minecraftforge.common.crafting.IShapedRecipe
import net.minecraftforge.event.AddReloadListenerEvent import net.minecraftforge.event.AddReloadListenerEvent
import net.minecraftforge.event.OnDatapackSyncEvent import net.minecraftforge.event.OnDatapackSyncEvent
import net.minecraftforge.event.RegisterCommandsEvent 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.capability.drive.IMatteryDrive
import ru.dbotthepony.mc.otm.client.isShiftDown import ru.dbotthepony.mc.otm.client.isShiftDown
import ru.dbotthepony.mc.otm.client.minecraft 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.container.stream
import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.TextComponent
@ -494,7 +500,7 @@ object MatterManager {
ResolvedRecipe( ResolvedRecipe(
it.ingredients.stream() it.ingredients.stream()
.filter { !it.isActuallyEmpty } .filter { !it.isActuallyEmpty }
.map { it.items.stream().map(::ImmutableStack) }, .map { it.items.stream().map(::RecipeEntry) },
ImmutableStack(it.resultItem), ImmutableStack(it.resultItem),
isCritical = isCritical, isCritical = isCritical,
name = it.id, 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<ArrayList<RecipeEntry>>()
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<RecipeEntry>()
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<ResourceLocation, Pair<Finder, JsonObject>> = ImmutableMap.of() private var foundResolvers: Map<ResourceLocation, Pair<Finder, JsonObject>> = ImmutableMap.of()
@ -586,7 +677,7 @@ object MatterManager {
for (input1 in recipe.inputs) { for (input1 in recipe.inputs) {
for (input in input1) { 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 { private fun doTryToBacktrack(item: Item, makeCommentary: Boolean): Result {
val recipes = input2Recipes[item] 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) if (makeCommentary)
comment(item, TextComponent("Item '${item.registryName}' has no recipes")) comment(item, TextComponent("Item '${item.registryName}' has no recipes"))
@ -713,28 +804,29 @@ object MatterManager {
inputsLoop@ for ((i, inputs) in recipe.inputs.withIndex()) { inputsLoop@ for ((i, inputs) in recipe.inputs.withIndex()) {
var minimal: IMatterValue? = null var minimal: IMatterValue? = null
var minimalMultiplier = 0.0 var minimalMultiplier = 0.0
val skips = ArrayList<ImmutableStack>() val skips = ArrayList<RecipeEntry>()
val recursiveSkips = ArrayList<ImmutableStack>() val recursiveSkips = ArrayList<RecipeEntry>()
var hadResidue = false
innerInputsLoop@ for (input in inputs) { innerInputsLoop@ for (entry in inputs) {
val ivalue = determineValue(input.item) val ivalue = determineValue(entry.input.item)
if (ivalue.isMissing) { 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) { if (recipe.isCritical && !lenientStage) {
return Result.MISSING return Result.MISSING
} else { } else {
if (recipe.isCritical) { if (recipe.isCritical) {
skips.add(input) skips.add(entry)
continue@innerInputsLoop continue@innerInputsLoop
} else { } else {
continue@recipesLoop continue@recipesLoop
} }
} }
} else if (ivalue.isSkipped) { } else if (ivalue.isSkipped) {
comment(item, TextComponent("Input '${input.item.registryName}' at input slot $i in ${recipe.formattedName} is recursive")) comment(item, TextComponent("Input $entry at input slot $i in ${recipe.formattedName} is recursive"))
recursiveSkips.add(input) recursiveSkips.add(entry)
if (inputs.size == 1) { if (inputs.size == 1) {
hadSkips = true hadSkips = true
@ -744,14 +836,52 @@ object MatterManager {
} }
} }
if (minimal == null || minimal > ivalue.value!!) { var realValue = ivalue.value!!
minimal = ivalue.value val ivalue2 = entry.remainder?.item?.let(::determineValue)
minimalMultiplier = input.multiplier
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) { 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 return Result.MISSING
} else if (skips.isNotEmpty()) { } else if (skips.isNotEmpty()) {
/** /**
@ -769,7 +899,7 @@ object MatterManager {
continue 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) { if (input !in skips) {
for (tag in list) for (tag in list)
@ -786,7 +916,7 @@ object MatterManager {
val result = filtered.all { tagSetsMissing.getInt(it.key) == skips.size } val result = filtered.all { tagSetsMissing.getInt(it.key) == skips.size }
if (!result) { 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 return Result.MISSING
} else { } else {
comment(item, TextComponent("${recipe.formattedName} with inputs at slot $i without matter values were allowed to be skipped due to next tags:")) 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) { 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 return Result.MISSING
} }
@ -1241,8 +1376,20 @@ object MatterManager {
constructor(item: ItemStack) : this(item.item, item.count) 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( class ResolvedRecipe(
inputs: Stream<Stream<ImmutableStack>>, inputs: Stream<Stream<RecipeEntry>>,
val output: ImmutableStack, val output: ImmutableStack,
val transformMatterValue: (Decimal) -> Decimal = { it }, val transformMatterValue: (Decimal) -> Decimal = { it },
val transformComplexity: (Double) -> Double = { 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 * (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: * "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 * * All recipes allow backtracking
*/ */
val allowBacktrack: Boolean = true, val allowBacktrack: Boolean = true,
) { ) {
val inputs: List<List<ImmutableStack>> = inputs val inputs: List<List<RecipeEntry>> = inputs
.map { it.filter { it.multiplier > 0.0 }.collect(ImmutableList.toImmutableList()) } .map { it.filter { it.input.multiplier > 0.0 }.collect(ImmutableList.toImmutableList()) }
.filter { it.isNotEmpty() } .filter { it.isNotEmpty() }
.collect(ImmutableList.toImmutableList()) .collect(ImmutableList.toImmutableList())
constructor(
inputs: Collection<Collection<ImmutableStack>>,
output: ImmutableStack,
transformMatterValue: (Decimal) -> Decimal = { it },
transformComplexity: (Double) -> Double = { it },
) : this(inputs.stream().map { it.stream() }, output, transformMatterValue, transformComplexity)
val formattedName: String val formattedName: String
get() = if (name == null) "One of recipes" else "Recipe '$name'" 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 { fun interface Finder {
@ -1293,7 +1457,7 @@ object MatterManager {
* *
* You can safely call [MatterManager.getDirect] inside returned stream, both parallel and sequential. * You can safely call [MatterManager.getDirect] inside returned stream, both parallel and sequential.
*/ */
fun find(server: MinecraftServer, json: JsonObject): Stream<ResolvedRecipe> fun find(server: MinecraftServer, data: JsonObject): Stream<ResolvedRecipe>
} }
/** /**
@ -1304,7 +1468,18 @@ object MatterManager {
return Registry.direct(value) return Registry.direct(value)
} }
/**
* Access recipe finders registry
*
* @throws IllegalStateException if calling too early
*/
@JvmStatic val recipeFinders get() = Resolver.delegate.get() @JvmStatic val recipeFinders get() = Resolver.delegate.get()
/**
* Access recipe finders registry key
*
* Use this with your [DeferredRegister]
*/
@JvmStatic val recipeFindersKey get() = Resolver.delegate.key @JvmStatic val recipeFindersKey get() = Resolver.delegate.key
private val commentary = Reference2ObjectOpenHashMap<Item, ArrayList<Component>>() private val commentary = Reference2ObjectOpenHashMap<Item, ArrayList<Component>>()

View File

@ -1,5 +1,5 @@
{ {
"recipe_type": "minecraft:crafting", "recipe_type": "minecraft:crafting",
"type": "overdrive_that_matters:simple", "type": "overdrive_that_matters:crafting",
"is_critical": true "is_critical": true
} }