Bump JEI version, make use of createUnregisteredRecipeTransferHandler

This commit is contained in:
DBotThePony 2022-09-12 19:46:04 +07:00
parent 583b467e69
commit 43c081277b
Signed by: DBot
GPG Key ID: DCC23B5715498507
4 changed files with 73 additions and 323 deletions

View File

@ -13,7 +13,7 @@ mc_version=1.19.2
forge_gradle_version=5.1.27
forge_version=43.1.1
jei_version=11.2.0.254
jei_version=11.3.0.262
jupiter_version=5.8.2
the_one_probe_version=6.2.1
mekanism_version=10.3.2.homebaked

View File

@ -1,12 +1,7 @@
package ru.dbotthepony.mc.otm.compat.jei
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap
import it.unimi.dsi.fastutil.ints.Int2IntFunction
import it.unimi.dsi.fastutil.ints.IntArrayList
import it.unimi.dsi.fastutil.ints.IntArraySet
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
import mezz.jei.api.IModPlugin
import mezz.jei.api.JeiPlugin
import mezz.jei.api.constants.RecipeTypes
@ -17,6 +12,7 @@ import mezz.jei.api.helpers.IJeiHelpers
import mezz.jei.api.recipe.RecipeIngredientRole
import mezz.jei.api.recipe.transfer.IRecipeTransferError
import mezz.jei.api.recipe.transfer.IRecipeTransferHandler
import mezz.jei.api.recipe.transfer.IRecipeTransferInfo
import mezz.jei.api.registration.IGuiHandlerRegistration
import mezz.jei.api.registration.IRecipeCatalystRegistration
import mezz.jei.api.registration.IRecipeCategoryRegistration
@ -27,7 +23,6 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.MenuType
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.crafting.CraftingRecipe
import net.minecraft.world.item.crafting.RecipeType
@ -35,14 +30,12 @@ import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.toImmutableList
import ru.dbotthepony.mc.otm.menu.ExoSuitInventoryMenu
import ru.dbotthepony.mc.otm.network.MenuNetworkChannel
import ru.dbotthepony.mc.otm.network.MoveMultipleItemsPacket
import ru.dbotthepony.mc.otm.recipe.EnergyContainerRecipe
import ru.dbotthepony.mc.otm.registry.MItems
import ru.dbotthepony.mc.otm.registry.MRecipes
import java.util.*
import java.util.function.BiFunction
import java.util.stream.Collectors
import kotlin.collections.ArrayList
import kotlin.properties.Delegates
@ -89,6 +82,38 @@ class JEIPlugin : IModPlugin {
val helper = registration.transferHelper
registration.addRecipeTransferHandler(object : IRecipeTransferHandler<ExoSuitInventoryMenu, CraftingRecipe> {
private val transfer = helper.createUnregisteredRecipeTransferHandler(object : IRecipeTransferInfo<ExoSuitInventoryMenu, CraftingRecipe> {
override fun getContainerClass(): Class<out ExoSuitInventoryMenu> {
return ExoSuitInventoryMenu::class.java
}
override fun getMenuType(): Optional<MenuType<ExoSuitInventoryMenu>> {
return Optional.empty()
}
override fun getRecipeType(): mezz.jei.api.recipe.RecipeType<CraftingRecipe> {
return RecipeTypes.CRAFTING
}
override fun canHandle(container: ExoSuitInventoryMenu, recipe: CraftingRecipe): Boolean {
return true
}
override fun getRecipeSlots(
container: ExoSuitInventoryMenu,
recipe: CraftingRecipe
): List<Slot> {
return container.craftingSlots
}
override fun getInventorySlots(
container: ExoSuitInventoryMenu,
recipe: CraftingRecipe
): List<Slot> {
return container.playerInventorySlots
}
})
override fun getContainerClass(): Class<out ExoSuitInventoryMenu> {
return ExoSuitInventoryMenu::class.java
}
@ -122,8 +147,6 @@ class JEIPlugin : IModPlugin {
it.put(4, i)
}
// i wonder why Mezz didn't expose IConnectionToServer
// because without it there is a lot of code duplication!
override fun transferRecipe(
container: ExoSuitInventoryMenu,
recipe: CraftingRecipe,
@ -144,143 +167,50 @@ class JEIPlugin : IModPlugin {
return helper.createUserErrorWithTooltip(TranslatableComponent("jei.tooltip.error.recipe.transfer.too.large.player.inventory"))
}
}
}
val missingItems = inputs.stream()
.filter { !it.isEmpty }
.filter { it.itemStacks.noneMatch { recipeItem -> container.allAccessibleSlots.any { it.mayPickup(player) && it.item.item === recipeItem.item } } }
.collect(Collectors.toList())
val filteredInputs = ArrayList<IRecipeSlotView>()
if (missingItems.isNotEmpty()) {
return helper.createUserErrorForMissingSlots(TranslatableComponent("jei.tooltip.error.recipe.transfer.missing"), missingItems)
}
val emptySlots = container.allAccessibleSlots.stream().filter { it.item.isEmpty }.toList()
val filledCraftingSlots = container.craftingSlots.stream().filter { !it.item.isEmpty }.count()
if (emptySlots.size < filledCraftingSlots) {
return helper.createUserErrorWithTooltip(TranslatableComponent("jei.tooltip.error.recipe.transfer.inventory.full"))
}
if (!doTransfer) {
return null
}
val desiredItems = inputs.stream()
.filter { !it.isEmpty }
.flatMap { it.itemStacks }
.map { it.item }
.distinct()
.collect(Collectors.toSet())
val sources = Object2ObjectArrayMap<Item, MutableList<Slot>>()
for (slot in container.allAccessibleSlots) {
if (!slot.item.isEmpty && slot.mayPickup(player) && slot.item.item in desiredItems) {
sources.computeIfAbsent(slot.item.item, Object2ObjectFunction { ArrayList() }).add(slot)
}
}
val bestSources = Array(inputs.size) {
val ingredient = inputs[it]
if (ingredient.isEmpty) {
kotlin.collections.ArrayDeque()
} else {
var bestMatch: List<Slot>? = null
var bestMatchSize = 0
for (item in ingredient.itemStacks) {
val slots = sources[item.item]
if (slots != null) {
var thisSize = 0
for (slot in slots) thisSize += slot.item.count
if (bestMatchSize < thisSize) {
bestMatchSize = thisSize
bestMatch = slots
}
}
}
val bestMatchNN = bestMatch ?: throw NullPointerException("Could not find best matching set of slots for recipe autofill (this exception should be never reachable)")
val trulyBest: List<Slot>
if (bestMatchNN.size <= 99) {
trulyBest = bestMatchNN
} else {
trulyBest = ArrayList<Slot>(99).also { for (i in 0 .. 39) it.add(bestMatchNN[i]) }
}
kotlin.collections.ArrayDeque<Slot>(trulyBest.size).also {
it.addAll(trulyBest)
it.sortWith { a, b ->
a.item.count.compareTo(b.item.count)
}
}
}
}
val actions = ArrayList<MoveMultipleItemsPacket.Move>()
val nextEmpty = emptySlots.iterator()
val emptiedSlots = IntArraySet()
// clear crafting slots
for (slot in container.craftingSlots) {
if (!slot.item.isEmpty) {
val emptySlot = nextEmpty.next()
actions.add(MoveMultipleItemsPacket.Move(setOf(slot.index), emptySlot.index, slot.item.count))
emptiedSlots.add(slot.index)
for (sourceList in bestSources) {
if (sourceList.isNotEmpty() && sourceList.first().item.item === slot.item.item) {
sourceList.addFirst(emptySlot)
}
}
}
}
val pieWishers = Object2IntArrayMap<Item>()
for (slots in bestSources) {
if (slots.isNotEmpty()) {
pieWishers.compute(slots.first().item.item) { _, count -> (count ?: 0) + 1 }
}
}
var transferCount = if (maxTransfer) 64 else 1
if (transferCount != 1) {
for ((i, ingredient) in inputs.withIndex()) {
if (!ingredient.isEmpty) {
var maxTransferThis = 0
for (slot in bestSources[i]) maxTransferThis += slot.item.count
transferCount = transferCount.coerceAtMost(maxTransferThis / pieWishers.getInt(bestSources[i].first().item.item))
if (i in validSlots) {
filteredInputs.add(ingredient)
}
}
}
val remap = if (container.craftingSlots.size == 9) directMap else smallMap
val outputs = recipeSlots.getSlotViews(RecipeIngredientRole.OUTPUT).toImmutableList()
val catalysts = recipeSlots.getSlotViews(RecipeIngredientRole.CATALYST).toImmutableList()
val render = recipeSlots.getSlotViews(RecipeIngredientRole.RENDER_ONLY).toImmutableList()
for ((i, ingredient) in inputs.withIndex()) {
if (!ingredient.isEmpty) {
actions.add(MoveMultipleItemsPacket.Move(
IntArrayList().also { for (slot in bestSources[i]) if (slot.index !in emptiedSlots) it.add(slot.index) },
container.craftingSlots[remap[i]].index,
transferCount))
val combine = ArrayList<IRecipeSlotView>(filteredInputs.size + outputs.size + render.size)
combine.addAll(filteredInputs)
combine.addAll(outputs)
combine.addAll(render)
val combined = combine.toImmutableList()
val newView = object : IRecipeSlotsView {
override fun getSlotViews(): MutableList<IRecipeSlotView> {
return combined
}
override fun getSlotViews(role: RecipeIngredientRole): MutableList<IRecipeSlotView> {
return when (role) {
RecipeIngredientRole.INPUT -> filteredInputs
RecipeIngredientRole.OUTPUT -> outputs
RecipeIngredientRole.CATALYST -> catalysts
RecipeIngredientRole.RENDER_ONLY -> render
}
}
override fun findSlotByName(slotName: String): Optional<IRecipeSlotView> {
return combined.stream().filter { it.slotName.orElse(null) == slotName }.findAny()
}
}
return this.transfer.transferRecipe(container, recipe, newView, player, maxTransfer, doTransfer)
}
val packetA = MoveMultipleItemsPacket(actions, mapOf())
val result = packetA.execute(player, container)
val packetB = MoveMultipleItemsPacket(actions, result)
MenuNetworkChannel.sendToServer(packetB)
return null
return this.transfer.transferRecipe(container, recipe, recipeSlots, player, maxTransfer, doTransfer)
}
}, RecipeTypes.CRAFTING)
}

View File

@ -331,3 +331,7 @@ fun FriendlyByteBuf.readItemType(): Item? {
operator fun <T : Comparable<T>> StateHolder<*, *>.get(value: Property<T>): T {
return getValue(value)
}
fun <T> List<T>.toImmutableList(): ImmutableList<T> {
return ImmutableList.copyOf(this)
}

View File

@ -65,189 +65,6 @@ class SetCarriedPacket(val item: ItemStack) : MatteryPacket {
}
}
class MoveMultipleItemsPacket(val actions: Collection<Move>, val changedSlots: Map<Int, ItemStack>, val error: Throwable? = null) : MatteryPacket {
data class Move(val sourceSlots: Collection<Int>, val destinationSlot: Int, val count: Int) {
fun write(buff: FriendlyByteBuf) {
buff.writeInt(sourceSlots.size)
for (slot in sourceSlots) {
buff.writeInt(slot)
}
buff.writeInt(destinationSlot)
buff.writeInt(count)
}
companion object {
fun read(buff: FriendlyByteBuf): Move {
val listSize = buff.readInt()
require(listSize <= 100) { "Too many source slots! ($listSize received)" }
val seen = IntAVLTreeSet()
val list = IntArrayList()
for (i in 0 until listSize) {
val index = buff.readInt()
if (seen.add(index)) {
list.add(index)
}
}
val destinationSlot = buff.readInt()
val count = buff.readInt()
return Move(list, destinationSlot, count)
}
}
}
override fun write(buff: FriendlyByteBuf) {
buff.writeInt(actions.size)
for (action in actions) {
action.write(buff)
}
buff.writeInt(changedSlots.size)
for ((k, v) in changedSlots) {
buff.writeInt(k)
buff.writeItem(v)
}
}
fun execute(ply: Player, container: AbstractContainerMenu): Map<Int, ItemStack> {
val changedSlots = Int2ObjectArrayMap<ItemStack>()
for ((sourceSlots, destinationSlotIndex, count) in actions) {
val destinationSlot = container.slots[destinationSlotIndex]
if (!destinationSlot.item.isEmpty) {
// clear slot first
continue
}
val realSourceSlots = sourceSlots.map { container.slots[it] }
if (realSourceSlots.isEmpty() || realSourceSlots.all { it.item.isEmpty }) {
continue
}
val type = realSourceSlots.first { !it.item.isEmpty }.item
if (!realSourceSlots.all { it.item.isEmpty || it.item.item === type.item }) {
continue
}
if (!destinationSlot.mayPlace(type)) {
continue
}
var pickedUp = 0
val maxPickup = destinationSlot.getMaxStackSize(type).coerceAtMost(count)
for (slot in realSourceSlots) {
if (slot.mayPickup(ply) && !slot.item.isEmpty) {
if (!destinationSlot.item.isEmpty && !ItemStack.isSameItemSameTags(destinationSlot.item, slot.item)) {
continue
}
val old = pickedUp
pickedUp = (pickedUp + slot.item.count).coerceAtMost(maxPickup)
val diff = pickedUp - old
val copy = slot.item.copy().also { it.count = diff }
slot.item.count -= diff
if (slot.item.isEmpty) {
slot.set(ItemStack.EMPTY)
changedSlots[slot.index] = ItemStack.EMPTY
} else {
slot.setChanged()
changedSlots[slot.index] = slot.item.copy()
}
if (destinationSlot.item.isEmpty) {
destinationSlot.set(copy)
} else {
destinationSlot.item.count += diff
destinationSlot.setChanged()
}
if (pickedUp == maxPickup) {
break
}
}
}
if (pickedUp != 0) {
changedSlots[destinationSlot.index] = destinationSlot.item.copy()
}
}
return changedSlots
}
override fun play(context: Supplier<NetworkEvent.Context>) {
context.packetHandled = true
if (error != null) {
context.sender!!.connection.disconnect(TextComponent("Caught an exception processing ${MoveMultipleItemsPacket::class.qualifiedName}: $error"))
LOGGER.error("Processing multi move input from player ${context.sender} has thrown an exception!", error)
return
}
context.enqueueWork {
try {
val container = context.sender!!.containerMenu
if (container is ExoSuitInventoryMenu) {
execute(context.sender!!, container)
for ((index, contents) in changedSlots) {
container.setRemoteSlot(index, contents)
}
}
} catch (err: Throwable) {
context.sender!!.connection.disconnect(TextComponent("Caught an exception processing ${MoveMultipleItemsPacket::class.qualifiedName}: $err"))
LOGGER.error("Processing multi move input from player ${context.sender} has thrown an exception!", err)
}
}
}
companion object {
private val LOGGER = LogManager.getLogger()
fun read(buff: FriendlyByteBuf): MoveMultipleItemsPacket {
try {
val actionCount = buff.readInt()
require(actionCount <= 40) { "Too many actions!" }
val actions = ArrayList<Move>(actionCount)
for (i in 0 until actionCount) {
actions.add(Move.read(buff))
}
val changedSlotCount = buff.readInt()
require(changedSlotCount <= 40) { "Too many changed slots!" }
val changedSlots = Int2ObjectAVLTreeMap<ItemStack>()
for (i in 0 until changedSlotCount) {
changedSlots.put(buff.readInt(), buff.readItem())
}
return MoveMultipleItemsPacket(actions, changedSlots)
} catch (err: Throwable) {
return MoveMultipleItemsPacket(listOf(), mapOf(), err)
}
}
}
}
object MenuNetworkChannel : MatteryNetworkChannel(
version = "1",
name = "menu"
@ -272,7 +89,6 @@ object MenuNetworkChannel : MatteryNetworkChannel(
add(BooleanPlayerInputPacket::class.java, BooleanPlayerInputPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER)
// menu specific
add(MoveMultipleItemsPacket::class.java, MoveMultipleItemsPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER)
// Item monitor
add(ItemMonitorPlayerSettings::class.java, ItemMonitorPlayerSettings.Companion::read)