From ff2262b0dbd289cef2ffff58df046e8892ce819e Mon Sep 17 00:00:00 2001 From: Intybyte Date: Sun, 23 Nov 2025 21:27:42 +0100 Subject: [PATCH 1/6] PylonServerPlaceRecipe Implementation --- .../core/i18n/packet/PlayerPacketHandler.kt | 3 + .../pylon/core/nms/HandlerRecipeBookClick.kt | 66 +++++ .../pylonmc/pylon/core/nms/NmsAccessorImpl.kt | 54 ++++ .../pylon/core/nms/PylonServerPlaceRecipe.kt | 277 ++++++++++++++++++ .../nms/util/StackedItemContentsWrapper.kt | 43 +++ .../io/github/pylonmc/pylon/core/PylonCore.kt | 45 +-- .../pylonmc/pylon/core/nms/NmsAccessor.kt | 4 + .../pylon/core/recipe/RecipeCompletion.kt | 12 + 8 files changed, 483 insertions(+), 21 deletions(-) create mode 100644 nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/HandlerRecipeBookClick.kt create mode 100644 nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/PylonServerPlaceRecipe.kt create mode 100644 nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/util/StackedItemContentsWrapper.kt create mode 100644 pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/recipe/RecipeCompletion.kt diff --git a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/i18n/packet/PlayerPacketHandler.kt b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/i18n/packet/PlayerPacketHandler.kt index 5cb4a4edd..989ed5fcb 100644 --- a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/i18n/packet/PlayerPacketHandler.kt +++ b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/i18n/packet/PlayerPacketHandler.kt @@ -2,9 +2,12 @@ package io.github.pylonmc.pylon.core.i18n.packet +import com.github.shynixn.mccoroutine.bukkit.launch +import com.github.shynixn.mccoroutine.bukkit.minecraftDispatcher import io.github.pylonmc.pylon.core.PylonCore import io.github.pylonmc.pylon.core.i18n.PlayerTranslationHandler import io.github.pylonmc.pylon.core.item.PylonItem +import io.github.pylonmc.pylon.core.nms.HandlerRecipeBookClick import io.github.pylonmc.pylon.core.util.editData import io.netty.channel.ChannelDuplexHandler import io.netty.channel.ChannelHandlerContext diff --git a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/HandlerRecipeBookClick.kt b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/HandlerRecipeBookClick.kt new file mode 100644 index 000000000..6a920498a --- /dev/null +++ b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/HandlerRecipeBookClick.kt @@ -0,0 +1,66 @@ +package io.github.pylonmc.pylon.core.nms + +import net.minecraft.server.level.ServerLevel +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.inventory.AbstractCraftingMenu +import net.minecraft.world.inventory.RecipeBookMenu.PostPlaceAction +import net.minecraft.world.item.crafting.CraftingRecipe +import net.minecraft.world.item.crafting.RecipeHolder +import java.lang.invoke.MethodHandle +import java.lang.invoke.MethodHandles +import java.lang.invoke.MethodType + +class HandlerRecipeBookClick(val player: ServerPlayer) { + + fun handlePylonItemPlacement( + menu: AbstractCraftingMenu, + useMaxItems: Boolean, + recipe: RecipeHolder<*>?, + level: ServerLevel?, + ): PostPlaceAction { + val recipeHolder = recipe as RecipeHolder + + init() + + beginPlacingRecipe.invokeExact(menu) + var postPlaceAction: PostPlaceAction + try { + val inputGridSlots = menu.inputGridSlots + postPlaceAction = PylonServerPlaceRecipe.placeRecipe( + menu, + player, + inputGridSlots, + inputGridSlots, + recipeHolder, + useMaxItems + ) + } finally { + finishPlacingRecipe.invokeExact(menu, level, recipe) + } + + return postPlaceAction + } + + companion object { + + var initialized = false + lateinit var beginPlacingRecipe: MethodHandle + lateinit var finishPlacingRecipe: MethodHandle + + fun init() { + if (initialized) return + + initialized = true + val lookup = MethodHandles.privateLookupIn( + AbstractCraftingMenu::class.java, + MethodHandles.lookup() + ) + + val beginPlacingRecipeType = MethodType.methodType(Void.TYPE) + beginPlacingRecipe = lookup.findVirtual(AbstractCraftingMenu::class.java, "beginPlacingRecipe", beginPlacingRecipeType) + + val finishPlacingRecipeType = MethodType.methodType(Void.TYPE, ServerLevel::class.java, RecipeHolder::class.java) + finishPlacingRecipe = lookup.findVirtual(AbstractCraftingMenu::class.java, "finishPlacingRecipe", finishPlacingRecipeType) + } + } +} diff --git a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessorImpl.kt b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessorImpl.kt index 45d255ca4..4595d6af2 100644 --- a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessorImpl.kt +++ b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessorImpl.kt @@ -1,10 +1,22 @@ package io.github.pylonmc.pylon.core.nms +import com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent +import com.github.shynixn.mccoroutine.bukkit.asyncDispatcher +import com.github.shynixn.mccoroutine.bukkit.launch +import com.github.shynixn.mccoroutine.bukkit.ticks +import io.github.pylonmc.pylon.core.PylonCore import io.github.pylonmc.pylon.core.i18n.PlayerTranslationHandler import io.github.pylonmc.pylon.core.i18n.packet.PlayerPacketHandler import io.papermc.paper.adventure.PaperAdventure +import kotlinx.coroutines.delay import net.kyori.adventure.text.Component +import net.minecraft.core.registries.Registries import net.minecraft.nbt.TextComponentTagVisitor +import net.minecraft.network.protocol.game.ClientboundPlaceGhostRecipePacket +import net.minecraft.resources.ResourceKey +import net.minecraft.server.MinecraftServer +import net.minecraft.world.inventory.AbstractCraftingMenu +import net.minecraft.world.inventory.RecipeBookMenu.PostPlaceAction import net.minecraft.world.item.Item import org.bukkit.Material import org.bukkit.World @@ -18,6 +30,7 @@ import org.bukkit.craftbukkit.entity.CraftPlayer import org.bukkit.craftbukkit.inventory.CraftItemStack import org.bukkit.craftbukkit.inventory.CraftItemType import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer +import org.bukkit.craftbukkit.util.CraftNamespacedKey import org.bukkit.entity.LivingEntity import org.bukkit.entity.Player import org.bukkit.inventory.EquipmentSlot @@ -88,4 +101,45 @@ object NmsAccessorImpl : NmsAccessor { } return map.toSortedMap().toSortedMap(compareBy { possibleValues[it] ?: 0 }.reversed()) } + + override fun handleRecipeBookClick(event: PlayerRecipeBookClickEvent) { + val serverPlayer = (event.player as CraftPlayer).handle + val menu = serverPlayer.containerMenu + + if (menu is AbstractCraftingMenu) { + val server = MinecraftServer.getServer() + val recipeName = event.recipe + val recipeHolder = server.recipeManager + .byKey(ResourceKey.create( + Registries.RECIPE, CraftNamespacedKey.toMinecraft(recipeName) + )) + .orElse(null) ?: return + + val postPlaceAction = HandlerRecipeBookClick(serverPlayer).handlePylonItemPlacement( + menu, + event.isMakeAll, + recipeHolder, + serverPlayer.level(), + ) + + + val displayRecipes = recipeHolder.value().display() + if (postPlaceAction == PostPlaceAction.PLACE_GHOST_RECIPE && !displayRecipes.isEmpty()) { + PylonCore.javaPlugin.launch(PylonCore.asyncDispatcher) { + val max = displayRecipes.size + for (i in 0.. +) : CraftingMenuAccess { + private lateinit var delegate: ServerPlaceRecipe<*> + + init { + StackedItemContentsWrapper.initialize() + initialize() + } + + override fun clearCraftingContent() { + menu.resultSlots.clearContent() + menu.craftSlots.clearContent() + } + + override fun recipeMatches(recipe1: RecipeHolder): Boolean { + return recipe1.value()!!.matches( + menu.craftSlots.asCraftInput(), + player.level() + ) + } + + override fun fillCraftSlotsStackedContents(stackedItemContents: StackedItemContents) { + for (stack in menu.craftSlots.contents) { + stackedItemContents.accountStackPylon(stack) + } + } + + private fun tryPlaceRecipe(recipe: RecipeHolder, stackedItemContents: StackedItemContents): PostPlaceAction { + if (stackedItemContents.canCraft(recipe.value()!!, null)) { + this.placeRecipe(recipe, stackedItemContents) + this.player.inventory.setChanged() + return PostPlaceAction.NOTHING + } else { + this.clearGrid() + this.player.inventory.setChanged() + return PostPlaceAction.PLACE_GHOST_RECIPE + } + } + + private fun clearGrid() { + methodMap["clearGrid"]!!.invokeExact(delegate) + } + + private fun placeRecipe(recipe: RecipeHolder, stackedItemContents: StackedItemContents) { + val flag = this.recipeMatches(recipe) + val rcp = recipe.value()!! + val biggestCraftableStack = stackedItemContents.getBiggestCraftableStack(rcp, null) + if (flag) { + for (slot in this.inputGridSlots) { + val item = slot.item + if (!item.isEmpty && min(biggestCraftableStack, item.maxStackSize) < item.count + 1) { + return + } + } + } + + val i = this.calculateAmountToCraft(biggestCraftableStack, flag) + val list: MutableList = ArrayList() + var selectedRecipe: Recipe<*> = recipe.value()!! + Objects.requireNonNull(list) + if (!stackedItemContents.canCraft(selectedRecipe, i) { e: ItemOrExact? -> list.add(e!!) }) return + + val i1: Int = clampToMaxStackSize(i, list) + if (i1 != i) { + list.clear() + selectedRecipe = recipe.value()!! + Objects.requireNonNull(list) + if (!stackedItemContents.canCraft( + selectedRecipe, + i1 + ) { e: ItemOrExact? -> list.add(e!!) } + ) { + return + } + } + + this.clearGrid() + PlaceRecipeHelper.placeRecipe( + this.menu.gridWidth, + this.menu.gridHeight, + recipe.value(), + recipe.value()!!.placementInfo().slotsToIngredientIndex() + ) { item1: Int?, slot1: Int, _: Int, _: Int -> + if (item1 != -1) { + val slot2 = this.inputGridSlots[slot1] + val holder = list[item1!!] + var i2 = i1 + + while (i2 > 0) { + i2 = this.moveItemToGrid(slot2, holder, i2) + if (i2 == -1) { + return@placeRecipe + } + } + } + } + } + + private fun calculateAmountToCraft(max: Int, recipeMatches: Boolean): Int { + return methodMap["calculateAmountToCraft"]!!.invokeExact(delegate, max, recipeMatches) as Int + } + + private fun moveItemToGrid(slot: Slot, item: ItemOrExact, count: Int): Int { + val item1 = slot.item + val matchingSlot = findSlotMatchingCraftingIngredient(this.player.inventory.contents, item, item1) + if (matchingSlot == -1) { + return -1 + } + + val item2 = this.player.inventory.getItem(matchingSlot) + val itemStack = if (count < item2.count) { + this.player.inventory.removeItem(matchingSlot, count) + } else { + this.player.inventory.removeItemNoUpdate(matchingSlot) + } + + val count1 = itemStack.count + if (item1.isEmpty) { + slot.set(itemStack) + } else { + item1.grow(count1) + } + + return count - count1 + } + + private fun testClearGrid(): Boolean { + return methodMap["testClearGrid"]!!.invokeExact(delegate) as Boolean + } + + fun findSlotMatchingCraftingIngredient(items: List, item: ItemOrExact, stack: ItemStack): Int { + for (i in items.indices) { + val itemStack = items[i] + if (itemStack.isEmpty) continue + + if (!item.matches(itemStack)) continue + + if (item is ItemOrExact.Item) { + if (!Inventory.isUsableForCrafting(itemStack)) continue + if (PylonItem.isPylonItem(itemStack.bukkitStack)) continue // skip our pylon items + } + + if (stack.isEmpty || ItemStack.isSameItemSameComponents(stack, itemStack)) { + return i + } + } + + return -1 + } + + companion object { + var initialized = false + val methodMap = HashMap() + lateinit var constructor: MethodHandle + + fun initialize() { + if (initialized) return + initialized = true + + val lookup = MethodHandles.privateLookupIn(ServerPlaceRecipe::class.java, MethodHandles.lookup()) + + for (method in ServerPlaceRecipe::class.java.declaredMethods) { + methodMap[method.name] = lookup.unreflect(method) + } + + constructor = lookup.findConstructor( + ServerPlaceRecipe::class.java, + MethodType.methodType( + Void.TYPE, + CraftingMenuAccess::class.java, + Inventory::class.java, + Boolean::class.javaPrimitiveType, // boolean + Integer::class.javaPrimitiveType, // int + Integer::class.javaPrimitiveType, // int + List::class.java, + List::class.java + ) + ) + } + + fun > makeDelegate( + menu: CraftingMenuAccess, + inventory: Inventory, + useMaxItems: Boolean, + gridWidth: Int, + gridHeight: Int, + inputGridSlots: List, + slotsToClear: List + ): ServerPlaceRecipe { + return constructor.invokeExact( + menu, + inventory, + useMaxItems, + gridWidth, + gridHeight, + inputGridSlots, + slotsToClear + ) as ServerPlaceRecipe + } + + fun placeRecipe( + menu: AbstractCraftingMenu, + player: ServerPlayer, + inputGridSlots: MutableList, + slotsToClear: MutableList, + recipe: RecipeHolder, + useMaxItems: Boolean + ): PostPlaceAction { + val serverPlaceRecipe = PylonServerPlaceRecipe( + menu, + player, + inputGridSlots + ) + + if (!player.isCreative && !serverPlaceRecipe.testClearGrid()) { + return PostPlaceAction.NOTHING + } + + serverPlaceRecipe.delegate = makeDelegate( + serverPlaceRecipe, + player.inventory, + useMaxItems, + menu.gridWidth, + menu.gridHeight, + inputGridSlots, + slotsToClear + ) + + val stackedItemContents = StackedItemContents() + stackedItemContents.initializeExtras(recipe.value()!!, null) + + for (itemStack in player.inventory) { + stackedItemContents.accountStackPylon(itemStack) + } + + serverPlaceRecipe.fillCraftSlotsStackedContents(stackedItemContents) + return serverPlaceRecipe.tryPlaceRecipe(recipe, stackedItemContents) + } + + private fun clampToMaxStackSize(amount: Int, items: MutableList): Int { + var amount = amount + for (holder in items) { + amount = min(amount, holder.maxStackSize) + } + + return amount + } + } +} diff --git a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/util/StackedItemContentsWrapper.kt b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/util/StackedItemContentsWrapper.kt new file mode 100644 index 000000000..01ea574b1 --- /dev/null +++ b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/util/StackedItemContentsWrapper.kt @@ -0,0 +1,43 @@ +package io.github.pylonmc.pylon.core.nms.util + +import io.github.pylonmc.pylon.core.item.PylonItem +import io.github.pylonmc.pylon.core.nms.util.StackedItemContentsWrapper.rawGetter +import io.papermc.paper.inventory.recipe.ItemOrExact +import net.minecraft.world.entity.player.StackedContents +import net.minecraft.world.entity.player.StackedItemContents +import net.minecraft.world.item.ItemStack +import java.lang.invoke.MethodHandle +import java.lang.invoke.MethodHandles +import kotlin.math.min + +object StackedItemContentsWrapper { + var initialized = false + lateinit var rawGetter: MethodHandle + + fun initialize() { + if (initialized) return + val lookup = MethodHandles.privateLookupIn(StackedItemContents::class.java, MethodHandles.lookup()) + rawGetter = lookup.findGetter(StackedItemContents::class.java, "raw", StackedContents::class.java) + + initialized = true + } +} + +fun StackedItemContents.getRaw(): StackedContents { + return rawGetter.invokeExact(this) as StackedContents +} + +fun StackedItemContents.accountStackPylon(stack: ItemStack, maxStackSize: Int = stack.maxStackSize) { + if (stack.isEmpty) return + + val min = min(maxStackSize, stack.count) + + // Determine if this is a Pylon item + if (PylonItem.isPylonItem(stack.bukkitStack)) { + val r = ItemOrExact.Exact(stack.copy()) + this.getRaw().account(r, min) + return + } + + this.accountStack(stack, maxStackSize) +} \ No newline at end of file diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/PylonCore.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/PylonCore.kt index c81561529..b14ddf64f 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/PylonCore.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/PylonCore.kt @@ -32,6 +32,7 @@ import io.github.pylonmc.pylon.core.item.research.Research import io.github.pylonmc.pylon.core.metrics.PylonMetrics import io.github.pylonmc.pylon.core.recipe.ConfigurableRecipeType import io.github.pylonmc.pylon.core.recipe.PylonRecipeListener +import io.github.pylonmc.pylon.core.recipe.RecipeCompletion import io.github.pylonmc.pylon.core.recipe.RecipeType import io.github.pylonmc.pylon.core.registry.PylonRegistry import io.github.pylonmc.pylon.core.resourcepack.armor.ArmorTextureEngine @@ -103,36 +104,38 @@ object PylonCore : JavaPlugin(), PylonAddon { // Add any keys that are missing from global config - saveDefaultConfig will not do anything if config already present mergeGlobalConfig(PylonCore, "config.yml", "config.yml") - Bukkit.getPluginManager().registerEvents(PylonTranslator, this) - Bukkit.getPluginManager().registerEvents(PylonAddon, this) + val pm = Bukkit.getPluginManager() + pm.registerEvents(PylonTranslator, this) + pm.registerEvents(PylonAddon, this) PylonMetrics // initialize metrics by referencing it // Anything that listens for addon registration must be above this line registerWithPylon() - Bukkit.getPluginManager().registerEvents(BlockStorage, this) - Bukkit.getPluginManager().registerEvents(BlockListener, this) - Bukkit.getPluginManager().registerEvents(PylonItemListener, this) + pm.registerEvents(BlockStorage, this) + pm.registerEvents(BlockListener, this) + pm.registerEvents(PylonItemListener, this) Bukkit.getScheduler().runTaskTimer(this, PylonInventoryTicker(), 0, PylonConfig.inventoryTickerBaseRate) - Bukkit.getPluginManager().registerEvents(TickManager, this) - Bukkit.getPluginManager().registerEvents(MultiblockCache, this) - Bukkit.getPluginManager().registerEvents(EntityStorage, this) - Bukkit.getPluginManager().registerEvents(EntityListener, this) - Bukkit.getPluginManager().registerEvents(Research, this) - Bukkit.getPluginManager().registerEvents(PylonGuiBlock, this) - Bukkit.getPluginManager().registerEvents(PylonEntityHolderBlock, this) - Bukkit.getPluginManager().registerEvents(PylonSimpleMultiblock, this) - Bukkit.getPluginManager().registerEvents(PylonFluidBufferBlock, this) - Bukkit.getPluginManager().registerEvents(PylonFluidTank, this) - Bukkit.getPluginManager().registerEvents(PylonRecipeListener, this) - Bukkit.getPluginManager().registerEvents(FluidPipePlacementService, this) - Bukkit.getPluginManager().registerEvents(PylonTickingBlock, this) - Bukkit.getPluginManager().registerEvents(PylonGuide, this) + pm.registerEvents(TickManager, this) + pm.registerEvents(MultiblockCache, this) + pm.registerEvents(EntityStorage, this) + pm.registerEvents(EntityListener, this) + pm.registerEvents(Research, this) + pm.registerEvents(PylonGuiBlock, this) + pm.registerEvents(PylonEntityHolderBlock, this) + pm.registerEvents(PylonSimpleMultiblock, this) + pm.registerEvents(PylonFluidBufferBlock, this) + pm.registerEvents(PylonFluidTank, this) + pm.registerEvents(PylonRecipeListener, this) + pm.registerEvents(FluidPipePlacementService, this) + pm.registerEvents(PylonTickingBlock, this) + pm.registerEvents(PylonGuide, this) + pm.registerEvents(RecipeCompletion, this) if (PylonConfig.WailaConfig.enabled) { PylonGuide.settingsPage.addSetting(PageButton(PlayerSettingsPage.wailaSettings)) - Bukkit.getPluginManager().registerEvents(Waila, this) + pm.registerEvents(Waila, this) } PylonGuide.settingsPage.addSetting(PageButton(PlayerSettingsPage.resourcePackSettings)) @@ -150,7 +153,7 @@ object PylonCore : JavaPlugin(), PylonAddon { if (PylonConfig.BlockTextureConfig.enabled) { PlayerSettingsPage.resourcePackSettings.addSetting(PageButton(PlayerSettingsPage.blockTextureSettings)) - Bukkit.getPluginManager().registerEvents(BlockTextureEngine, this) + pm.registerEvents(BlockTextureEngine, this) BlockTextureEngine.updateOccludingCacheJob.start() } diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessor.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessor.kt index 68948dea7..82d3aa045 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessor.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessor.kt @@ -1,9 +1,11 @@ package io.github.pylonmc.pylon.core.nms +import com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent import io.github.pylonmc.pylon.core.i18n.PlayerTranslationHandler import net.kyori.adventure.text.Component import org.bukkit.block.Block import org.bukkit.Material +import org.bukkit.NamespacedKey import org.bukkit.World import org.bukkit.entity.LivingEntity import org.bukkit.entity.Player @@ -35,6 +37,8 @@ interface NmsAccessor { fun getStateProperties(block: Block, custom: Map> = mutableMapOf()): Map + fun handleRecipeBookClick(event: PlayerRecipeBookClickEvent) + companion object { val instance = Class.forName("io.github.pylonmc.pylon.core.nms.NmsAccessorImpl") .getDeclaredField("INSTANCE") diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/recipe/RecipeCompletion.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/recipe/RecipeCompletion.kt new file mode 100644 index 000000000..40e621bb8 --- /dev/null +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/recipe/RecipeCompletion.kt @@ -0,0 +1,12 @@ +package io.github.pylonmc.pylon.core.recipe + +import com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent +import io.github.pylonmc.pylon.core.nms.NmsAccessor +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener + +object RecipeCompletion : Listener { + + @EventHandler + private fun onRecipeBookClick(e: PlayerRecipeBookClickEvent) = NmsAccessor.instance.handleRecipeBookClick(e) +} \ No newline at end of file From afa2272a474331c3e59ad57a6493876cfd904f33 Mon Sep 17 00:00:00 2001 From: Intybyte Date: Sun, 23 Nov 2025 21:52:04 +0100 Subject: [PATCH 2/6] Fix order --- .../pylonmc/pylon/core/nms/PylonServerPlaceRecipe.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/PylonServerPlaceRecipe.kt b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/PylonServerPlaceRecipe.kt index 7a29d65e8..889f082dc 100644 --- a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/PylonServerPlaceRecipe.kt +++ b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/PylonServerPlaceRecipe.kt @@ -240,10 +240,6 @@ class PylonServerPlaceRecipe private constructor( inputGridSlots ) - if (!player.isCreative && !serverPlaceRecipe.testClearGrid()) { - return PostPlaceAction.NOTHING - } - serverPlaceRecipe.delegate = makeDelegate( serverPlaceRecipe, player.inventory, @@ -254,6 +250,10 @@ class PylonServerPlaceRecipe private constructor( slotsToClear ) + if (!player.isCreative && !serverPlaceRecipe.testClearGrid()) { + return PostPlaceAction.NOTHING + } + val stackedItemContents = StackedItemContents() stackedItemContents.initializeExtras(recipe.value()!!, null) From d682eeac072380e965e827b2e0834a14aba04d0f Mon Sep 17 00:00:00 2001 From: Intybyte Date: Sat, 29 Nov 2025 20:23:49 +0100 Subject: [PATCH 3/6] Cleanup --- .../core/i18n/packet/PlayerPacketHandler.kt | 2 +- .../pylonmc/pylon/core/nms/NmsAccessorImpl.kt | 3 +- .../{ => recipe}/HandlerRecipeBookClick.kt | 12 +++--- .../{ => recipe}/PylonServerPlaceRecipe.kt | 38 +++++++++---------- .../util/StackedItemContentsWrapper.kt | 8 ++-- .../pylonmc/pylon/core/nms/NmsAccessor.kt | 1 - 6 files changed, 30 insertions(+), 34 deletions(-) rename nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/{ => recipe}/HandlerRecipeBookClick.kt (87%) rename nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/{ => recipe}/PylonServerPlaceRecipe.kt (89%) rename nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/{ => recipe}/util/StackedItemContentsWrapper.kt (81%) diff --git a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/i18n/packet/PlayerPacketHandler.kt b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/i18n/packet/PlayerPacketHandler.kt index 989ed5fcb..ed72a9593 100644 --- a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/i18n/packet/PlayerPacketHandler.kt +++ b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/i18n/packet/PlayerPacketHandler.kt @@ -7,7 +7,7 @@ import com.github.shynixn.mccoroutine.bukkit.minecraftDispatcher import io.github.pylonmc.pylon.core.PylonCore import io.github.pylonmc.pylon.core.i18n.PlayerTranslationHandler import io.github.pylonmc.pylon.core.item.PylonItem -import io.github.pylonmc.pylon.core.nms.HandlerRecipeBookClick +import io.github.pylonmc.pylon.core.nms.recipe.HandlerRecipeBookClick import io.github.pylonmc.pylon.core.util.editData import io.netty.channel.ChannelDuplexHandler import io.netty.channel.ChannelHandlerContext diff --git a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessorImpl.kt b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessorImpl.kt index 4595d6af2..ff0f988df 100644 --- a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessorImpl.kt +++ b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessorImpl.kt @@ -3,12 +3,11 @@ package io.github.pylonmc.pylon.core.nms import com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent import com.github.shynixn.mccoroutine.bukkit.asyncDispatcher import com.github.shynixn.mccoroutine.bukkit.launch -import com.github.shynixn.mccoroutine.bukkit.ticks import io.github.pylonmc.pylon.core.PylonCore import io.github.pylonmc.pylon.core.i18n.PlayerTranslationHandler import io.github.pylonmc.pylon.core.i18n.packet.PlayerPacketHandler +import io.github.pylonmc.pylon.core.nms.recipe.HandlerRecipeBookClick import io.papermc.paper.adventure.PaperAdventure -import kotlinx.coroutines.delay import net.kyori.adventure.text.Component import net.minecraft.core.registries.Registries import net.minecraft.nbt.TextComponentTagVisitor diff --git a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/HandlerRecipeBookClick.kt b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/recipe/HandlerRecipeBookClick.kt similarity index 87% rename from nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/HandlerRecipeBookClick.kt rename to nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/recipe/HandlerRecipeBookClick.kt index 6a920498a..b08ee41e2 100644 --- a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/HandlerRecipeBookClick.kt +++ b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/recipe/HandlerRecipeBookClick.kt @@ -1,9 +1,9 @@ -package io.github.pylonmc.pylon.core.nms +package io.github.pylonmc.pylon.core.nms.recipe import net.minecraft.server.level.ServerLevel import net.minecraft.server.level.ServerPlayer import net.minecraft.world.inventory.AbstractCraftingMenu -import net.minecraft.world.inventory.RecipeBookMenu.PostPlaceAction +import net.minecraft.world.inventory.RecipeBookMenu import net.minecraft.world.item.crafting.CraftingRecipe import net.minecraft.world.item.crafting.RecipeHolder import java.lang.invoke.MethodHandle @@ -17,16 +17,16 @@ class HandlerRecipeBookClick(val player: ServerPlayer) { useMaxItems: Boolean, recipe: RecipeHolder<*>?, level: ServerLevel?, - ): PostPlaceAction { + ): RecipeBookMenu.PostPlaceAction { val recipeHolder = recipe as RecipeHolder init() beginPlacingRecipe.invokeExact(menu) - var postPlaceAction: PostPlaceAction + var postPlaceAction: RecipeBookMenu.PostPlaceAction try { val inputGridSlots = menu.inputGridSlots - postPlaceAction = PylonServerPlaceRecipe.placeRecipe( + postPlaceAction = PylonServerPlaceRecipe.Companion.placeRecipe( menu, player, inputGridSlots, @@ -63,4 +63,4 @@ class HandlerRecipeBookClick(val player: ServerPlayer) { finishPlacingRecipe = lookup.findVirtual(AbstractCraftingMenu::class.java, "finishPlacingRecipe", finishPlacingRecipeType) } } -} +} \ No newline at end of file diff --git a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/PylonServerPlaceRecipe.kt b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/recipe/PylonServerPlaceRecipe.kt similarity index 89% rename from nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/PylonServerPlaceRecipe.kt rename to nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/recipe/PylonServerPlaceRecipe.kt index 889f082dc..3e26e8332 100644 --- a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/PylonServerPlaceRecipe.kt +++ b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/recipe/PylonServerPlaceRecipe.kt @@ -1,17 +1,16 @@ -package io.github.pylonmc.pylon.core.nms +package io.github.pylonmc.pylon.core.nms.recipe import io.github.pylonmc.pylon.core.item.PylonItem -import io.github.pylonmc.pylon.core.nms.util.StackedItemContentsWrapper -import io.github.pylonmc.pylon.core.nms.util.accountStackPylon +import io.github.pylonmc.pylon.core.nms.recipe.util.StackedItemContentsWrapper +import io.github.pylonmc.pylon.core.nms.recipe.util.accountStackPylon import io.papermc.paper.inventory.recipe.ItemOrExact import net.minecraft.recipebook.PlaceRecipeHelper import net.minecraft.recipebook.ServerPlaceRecipe -import net.minecraft.recipebook.ServerPlaceRecipe.CraftingMenuAccess import net.minecraft.server.level.ServerPlayer import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.StackedItemContents import net.minecraft.world.inventory.AbstractCraftingMenu -import net.minecraft.world.inventory.RecipeBookMenu.PostPlaceAction +import net.minecraft.world.inventory.RecipeBookMenu import net.minecraft.world.inventory.Slot import net.minecraft.world.item.ItemStack import net.minecraft.world.item.crafting.CraftingRecipe @@ -20,16 +19,18 @@ import net.minecraft.world.item.crafting.RecipeHolder import java.lang.invoke.MethodHandle import java.lang.invoke.MethodHandles import java.lang.invoke.MethodType -import java.util.* +import java.util.ArrayList +import java.util.HashMap +import java.util.Objects import kotlin.math.min class PylonServerPlaceRecipe private constructor( private val menu: AbstractCraftingMenu, private val player: ServerPlayer, private val inputGridSlots: MutableList -) : CraftingMenuAccess { +) : ServerPlaceRecipe.CraftingMenuAccess { private lateinit var delegate: ServerPlaceRecipe<*> - + init { StackedItemContentsWrapper.initialize() initialize() @@ -53,15 +54,15 @@ class PylonServerPlaceRecipe private constructor( } } - private fun tryPlaceRecipe(recipe: RecipeHolder, stackedItemContents: StackedItemContents): PostPlaceAction { + private fun tryPlaceRecipe(recipe: RecipeHolder, stackedItemContents: StackedItemContents): RecipeBookMenu.PostPlaceAction { if (stackedItemContents.canCraft(recipe.value()!!, null)) { this.placeRecipe(recipe, stackedItemContents) this.player.inventory.setChanged() - return PostPlaceAction.NOTHING + return RecipeBookMenu.PostPlaceAction.NOTHING } else { this.clearGrid() this.player.inventory.setChanged() - return PostPlaceAction.PLACE_GHOST_RECIPE + return RecipeBookMenu.PostPlaceAction.PLACE_GHOST_RECIPE } } @@ -165,7 +166,7 @@ class PylonServerPlaceRecipe private constructor( if (item is ItemOrExact.Item) { if (!Inventory.isUsableForCrafting(itemStack)) continue - if (PylonItem.isPylonItem(itemStack.bukkitStack)) continue // skip our pylon items + if (PylonItem.Companion.isPylonItem(itemStack.bukkitStack)) continue // skip our pylon items } if (stack.isEmpty || ItemStack.isSameItemSameComponents(stack, itemStack)) { @@ -195,7 +196,7 @@ class PylonServerPlaceRecipe private constructor( ServerPlaceRecipe::class.java, MethodType.methodType( Void.TYPE, - CraftingMenuAccess::class.java, + ServerPlaceRecipe.CraftingMenuAccess::class.java, Inventory::class.java, Boolean::class.javaPrimitiveType, // boolean Integer::class.javaPrimitiveType, // int @@ -207,7 +208,7 @@ class PylonServerPlaceRecipe private constructor( } fun > makeDelegate( - menu: CraftingMenuAccess, + menu: ServerPlaceRecipe.CraftingMenuAccess, inventory: Inventory, useMaxItems: Boolean, gridWidth: Int, @@ -233,7 +234,7 @@ class PylonServerPlaceRecipe private constructor( slotsToClear: MutableList, recipe: RecipeHolder, useMaxItems: Boolean - ): PostPlaceAction { + ): RecipeBookMenu.PostPlaceAction { val serverPlaceRecipe = PylonServerPlaceRecipe( menu, player, @@ -244,14 +245,13 @@ class PylonServerPlaceRecipe private constructor( serverPlaceRecipe, player.inventory, useMaxItems, - menu.gridWidth, - menu.gridHeight, + menu.gridWidth, menu.gridHeight, inputGridSlots, slotsToClear ) if (!player.isCreative && !serverPlaceRecipe.testClearGrid()) { - return PostPlaceAction.NOTHING + return RecipeBookMenu.PostPlaceAction.NOTHING } val stackedItemContents = StackedItemContents() @@ -274,4 +274,4 @@ class PylonServerPlaceRecipe private constructor( return amount } } -} +} \ No newline at end of file diff --git a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/util/StackedItemContentsWrapper.kt b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/recipe/util/StackedItemContentsWrapper.kt similarity index 81% rename from nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/util/StackedItemContentsWrapper.kt rename to nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/recipe/util/StackedItemContentsWrapper.kt index 01ea574b1..61b9f4684 100644 --- a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/util/StackedItemContentsWrapper.kt +++ b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/recipe/util/StackedItemContentsWrapper.kt @@ -1,7 +1,7 @@ -package io.github.pylonmc.pylon.core.nms.util +package io.github.pylonmc.pylon.core.nms.recipe.util import io.github.pylonmc.pylon.core.item.PylonItem -import io.github.pylonmc.pylon.core.nms.util.StackedItemContentsWrapper.rawGetter +import io.github.pylonmc.pylon.core.nms.recipe.util.StackedItemContentsWrapper.rawGetter import io.papermc.paper.inventory.recipe.ItemOrExact import net.minecraft.world.entity.player.StackedContents import net.minecraft.world.entity.player.StackedItemContents @@ -23,9 +23,7 @@ object StackedItemContentsWrapper { } } -fun StackedItemContents.getRaw(): StackedContents { - return rawGetter.invokeExact(this) as StackedContents -} +fun StackedItemContents.getRaw(): StackedContents = rawGetter.invokeExact(this) as StackedContents fun StackedItemContents.accountStackPylon(stack: ItemStack, maxStackSize: Int = stack.maxStackSize) { if (stack.isEmpty) return diff --git a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessor.kt b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessor.kt index 82d3aa045..6e10df580 100644 --- a/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessor.kt +++ b/pylon-core/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessor.kt @@ -5,7 +5,6 @@ import io.github.pylonmc.pylon.core.i18n.PlayerTranslationHandler import net.kyori.adventure.text.Component import org.bukkit.block.Block import org.bukkit.Material -import org.bukkit.NamespacedKey import org.bukkit.World import org.bukkit.entity.LivingEntity import org.bukkit.entity.Player From 45f5063966d1c72d149003b6728ce3fe0a222b4e Mon Sep 17 00:00:00 2001 From: Intybyte Date: Sun, 30 Nov 2025 20:57:32 +0100 Subject: [PATCH 4/6] Cleanup 2 --- .../pylonmc/pylon/core/nms/NmsAccessorImpl.kt | 60 +++++++++---------- .../core/nms/recipe/HandlerRecipeBookClick.kt | 2 +- .../core/nms/recipe/PylonServerPlaceRecipe.kt | 12 ++-- 3 files changed, 36 insertions(+), 38 deletions(-) diff --git a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessorImpl.kt b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessorImpl.kt index ff0f988df..7100f1770 100644 --- a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessorImpl.kt +++ b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessorImpl.kt @@ -105,40 +105,38 @@ object NmsAccessorImpl : NmsAccessor { val serverPlayer = (event.player as CraftPlayer).handle val menu = serverPlayer.containerMenu - if (menu is AbstractCraftingMenu) { - val server = MinecraftServer.getServer() - val recipeName = event.recipe - val recipeHolder = server.recipeManager - .byKey(ResourceKey.create( - Registries.RECIPE, CraftNamespacedKey.toMinecraft(recipeName) - )) - .orElse(null) ?: return - - val postPlaceAction = HandlerRecipeBookClick(serverPlayer).handlePylonItemPlacement( - menu, - event.isMakeAll, - recipeHolder, - serverPlayer.level(), - ) - - - val displayRecipes = recipeHolder.value().display() - if (postPlaceAction == PostPlaceAction.PLACE_GHOST_RECIPE && !displayRecipes.isEmpty()) { - PylonCore.javaPlugin.launch(PylonCore.asyncDispatcher) { - val max = displayRecipes.size - for (i in 0..): Boolean { - return recipe1.value()!!.matches( + return recipe1.value().matches( menu.craftSlots.asCraftInput(), player.level() ) @@ -55,7 +55,7 @@ class PylonServerPlaceRecipe private constructor( } private fun tryPlaceRecipe(recipe: RecipeHolder, stackedItemContents: StackedItemContents): RecipeBookMenu.PostPlaceAction { - if (stackedItemContents.canCraft(recipe.value()!!, null)) { + if (stackedItemContents.canCraft(recipe.value(), null)) { this.placeRecipe(recipe, stackedItemContents) this.player.inventory.setChanged() return RecipeBookMenu.PostPlaceAction.NOTHING @@ -72,7 +72,7 @@ class PylonServerPlaceRecipe private constructor( private fun placeRecipe(recipe: RecipeHolder, stackedItemContents: StackedItemContents) { val flag = this.recipeMatches(recipe) - val rcp = recipe.value()!! + val rcp = recipe.value() val biggestCraftableStack = stackedItemContents.getBiggestCraftableStack(rcp, null) if (flag) { for (slot in this.inputGridSlots) { @@ -85,14 +85,14 @@ class PylonServerPlaceRecipe private constructor( val i = this.calculateAmountToCraft(biggestCraftableStack, flag) val list: MutableList = ArrayList() - var selectedRecipe: Recipe<*> = recipe.value()!! + var selectedRecipe: Recipe<*> = recipe.value() Objects.requireNonNull(list) if (!stackedItemContents.canCraft(selectedRecipe, i) { e: ItemOrExact? -> list.add(e!!) }) return val i1: Int = clampToMaxStackSize(i, list) if (i1 != i) { list.clear() - selectedRecipe = recipe.value()!! + selectedRecipe = recipe.value() Objects.requireNonNull(list) if (!stackedItemContents.canCraft( selectedRecipe, @@ -108,7 +108,7 @@ class PylonServerPlaceRecipe private constructor( this.menu.gridWidth, this.menu.gridHeight, recipe.value(), - recipe.value()!!.placementInfo().slotsToIngredientIndex() + recipe.value().placementInfo().slotsToIngredientIndex() ) { item1: Int?, slot1: Int, _: Int, _: Int -> if (item1 != -1) { val slot2 = this.inputGridSlots[slot1] From a130c947f8b3e126754e07c68a4e050fd9b077dd Mon Sep 17 00:00:00 2001 From: Intybyte Date: Sun, 30 Nov 2025 21:08:36 +0100 Subject: [PATCH 5/6] Cleanup 3 --- .../core/nms/recipe/PylonServerPlaceRecipe.kt | 48 ++++--------------- 1 file changed, 10 insertions(+), 38 deletions(-) diff --git a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/recipe/PylonServerPlaceRecipe.kt b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/recipe/PylonServerPlaceRecipe.kt index baef7f3cf..1c9d54119 100644 --- a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/recipe/PylonServerPlaceRecipe.kt +++ b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/recipe/PylonServerPlaceRecipe.kt @@ -14,14 +14,10 @@ import net.minecraft.world.inventory.RecipeBookMenu import net.minecraft.world.inventory.Slot import net.minecraft.world.item.ItemStack import net.minecraft.world.item.crafting.CraftingRecipe -import net.minecraft.world.item.crafting.Recipe import net.minecraft.world.item.crafting.RecipeHolder import java.lang.invoke.MethodHandle import java.lang.invoke.MethodHandles import java.lang.invoke.MethodType -import java.util.ArrayList -import java.util.HashMap -import java.util.Objects import kotlin.math.min class PylonServerPlaceRecipe private constructor( @@ -72,8 +68,8 @@ class PylonServerPlaceRecipe private constructor( private fun placeRecipe(recipe: RecipeHolder, stackedItemContents: StackedItemContents) { val flag = this.recipeMatches(recipe) - val rcp = recipe.value() - val biggestCraftableStack = stackedItemContents.getBiggestCraftableStack(rcp, null) + val selectedRecipe = recipe.value() + val biggestCraftableStack = stackedItemContents.getBiggestCraftableStack(selectedRecipe, null) if (flag) { for (slot in this.inputGridSlots) { val item = slot.item @@ -84,16 +80,12 @@ class PylonServerPlaceRecipe private constructor( } val i = this.calculateAmountToCraft(biggestCraftableStack, flag) - val list: MutableList = ArrayList() - var selectedRecipe: Recipe<*> = recipe.value() - Objects.requireNonNull(list) + val list = ArrayList() if (!stackedItemContents.canCraft(selectedRecipe, i) { e: ItemOrExact? -> list.add(e!!) }) return val i1: Int = clampToMaxStackSize(i, list) if (i1 != i) { list.clear() - selectedRecipe = recipe.value() - Objects.requireNonNull(list) if (!stackedItemContents.canCraft( selectedRecipe, i1 @@ -107,8 +99,8 @@ class PylonServerPlaceRecipe private constructor( PlaceRecipeHelper.placeRecipe( this.menu.gridWidth, this.menu.gridHeight, - recipe.value(), - recipe.value().placementInfo().slotsToIngredientIndex() + selectedRecipe, + selectedRecipe.placementInfo().slotsToIngredientIndex() ) { item1: Int?, slot1: Int, _: Int, _: Int -> if (item1 != -1) { val slot2 = this.inputGridSlots[slot1] @@ -166,7 +158,7 @@ class PylonServerPlaceRecipe private constructor( if (item is ItemOrExact.Item) { if (!Inventory.isUsableForCrafting(itemStack)) continue - if (PylonItem.Companion.isPylonItem(itemStack.bukkitStack)) continue // skip our pylon items + if (PylonItem.isPylonItem(itemStack.bukkitStack)) continue // skip our pylon items } if (stack.isEmpty || ItemStack.isSameItemSameComponents(stack, itemStack)) { @@ -207,26 +199,6 @@ class PylonServerPlaceRecipe private constructor( ) } - fun > makeDelegate( - menu: ServerPlaceRecipe.CraftingMenuAccess, - inventory: Inventory, - useMaxItems: Boolean, - gridWidth: Int, - gridHeight: Int, - inputGridSlots: List, - slotsToClear: List - ): ServerPlaceRecipe { - return constructor.invokeExact( - menu, - inventory, - useMaxItems, - gridWidth, - gridHeight, - inputGridSlots, - slotsToClear - ) as ServerPlaceRecipe - } - fun placeRecipe( menu: AbstractCraftingMenu, player: ServerPlayer, @@ -241,21 +213,21 @@ class PylonServerPlaceRecipe private constructor( inputGridSlots ) - serverPlaceRecipe.delegate = makeDelegate( - serverPlaceRecipe, + serverPlaceRecipe.delegate = constructor.invokeExact( + serverPlaceRecipe as ServerPlaceRecipe.CraftingMenuAccess, // important for invokeExact player.inventory, useMaxItems, menu.gridWidth, menu.gridHeight, inputGridSlots, slotsToClear - ) + ) as ServerPlaceRecipe if (!player.isCreative && !serverPlaceRecipe.testClearGrid()) { return RecipeBookMenu.PostPlaceAction.NOTHING } val stackedItemContents = StackedItemContents() - stackedItemContents.initializeExtras(recipe.value()!!, null) + stackedItemContents.initializeExtras(recipe.value(), null) for (itemStack in player.inventory) { stackedItemContents.accountStackPylon(itemStack) From eba4a157c8615db6c559f544558dfab801ec8026 Mon Sep 17 00:00:00 2001 From: Intybyte Date: Tue, 9 Dec 2025 10:38:45 +0100 Subject: [PATCH 6/6] Documentation --- .../pylonmc/pylon/core/nms/NmsAccessorImpl.kt | 23 +++++++++---------- .../core/nms/recipe/HandlerRecipeBookClick.kt | 4 ++++ .../core/nms/recipe/PylonServerPlaceRecipe.kt | 18 +++++++++++++++ .../recipe/util/StackedItemContentsWrapper.kt | 9 ++++++++ 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessorImpl.kt b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessorImpl.kt index 7100f1770..09f798f67 100644 --- a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessorImpl.kt +++ b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/NmsAccessorImpl.kt @@ -123,20 +123,19 @@ object NmsAccessorImpl : NmsAccessor { val displayRecipes = recipeHolder.value().display() - if (postPlaceAction == PostPlaceAction.PLACE_GHOST_RECIPE && !displayRecipes.isEmpty()) { - PylonCore.javaPlugin.launch(PylonCore.asyncDispatcher) { - val max = displayRecipes.size - for (i in 0.., stackedItemContents: StackedItemContents): RecipeBookMenu.PostPlaceAction { if (stackedItemContents.canCraft(recipe.value(), null)) { this.placeRecipe(recipe, stackedItemContents) @@ -66,6 +75,9 @@ class PylonServerPlaceRecipe private constructor( methodMap["clearGrid"]!!.invokeExact(delegate) } + /** + * We override this because we need to use our own moveItemToGrid + */ private fun placeRecipe(recipe: RecipeHolder, stackedItemContents: StackedItemContents) { val flag = this.recipeMatches(recipe) val selectedRecipe = recipe.value() @@ -121,6 +133,9 @@ class PylonServerPlaceRecipe private constructor( return methodMap["calculateAmountToCraft"]!!.invokeExact(delegate, max, recipeMatches) as Int } + /** + * We override this because we need to use our own findSlotMatchingCraftingIngredient + */ private fun moveItemToGrid(slot: Slot, item: ItemOrExact, count: Int): Int { val item1 = slot.item val matchingSlot = findSlotMatchingCraftingIngredient(this.player.inventory.contents, item, item1) @@ -149,6 +164,9 @@ class PylonServerPlaceRecipe private constructor( return methodMap["testClearGrid"]!!.invokeExact(delegate) as Boolean } + /** + * Behave like normal, however skip pylon items from material matches + */ fun findSlotMatchingCraftingIngredient(items: List, item: ItemOrExact, stack: ItemStack): Int { for (i in items.indices) { val itemStack = items[i] diff --git a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/recipe/util/StackedItemContentsWrapper.kt b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/recipe/util/StackedItemContentsWrapper.kt index 61b9f4684..081a8fff4 100644 --- a/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/recipe/util/StackedItemContentsWrapper.kt +++ b/nms/src/main/kotlin/io/github/pylonmc/pylon/core/nms/recipe/util/StackedItemContentsWrapper.kt @@ -25,6 +25,15 @@ object StackedItemContentsWrapper { fun StackedItemContents.getRaw(): StackedContents = rawGetter.invokeExact(this) as StackedContents +/** + * + * Behaves like the StackedItemContents#accountStack, however for pylon items we instead + * account them as ItemOrExact#Exact when added to the StackedContents that handles crafting, + * so that only exact picks, and not material pick, will show up as valid + * + * @param stack stack to add + * @param maxStackSize max stack size of the itemstack, by default obtained with DataComponents#MAX_STACK_SIZE + */ fun StackedItemContents.accountStackPylon(stack: ItemStack, maxStackSize: Int = stack.maxStackSize) { if (stack.isEmpty) return