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.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<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()
@ -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<ImmutableStack>()
val recursiveSkips = ArrayList<ImmutableStack>()
val skips = ArrayList<RecipeEntry>()
val recursiveSkips = ArrayList<RecipeEntry>()
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<Stream<ImmutableStack>>,
inputs: Stream<Stream<RecipeEntry>>,
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<List<ImmutableStack>> = inputs
.map { it.filter { it.multiplier > 0.0 }.collect(ImmutableList.toImmutableList()) }
val inputs: List<List<RecipeEntry>> = inputs
.map { it.filter { it.input.multiplier > 0.0 }.collect(ImmutableList.toImmutableList()) }
.filter { it.isNotEmpty() }
.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
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<ResolvedRecipe>
fun find(server: MinecraftServer, data: JsonObject): Stream<ResolvedRecipe>
}
/**
@ -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<Item, ArrayList<Component>>()

View File

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