Skip to content

Commit af416af

Browse files
authored
Merge pull request #827 from pylonmc/human/register-recipes-galore
Batch Recipe (Un)Registration
2 parents 887df8c + 0092a40 commit af416af

14 files changed

Lines changed: 416 additions & 62 deletions

File tree

nms/src/main/kotlin/io/github/pylonmc/rebar/nms/NmsAccessorImpl.kt

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,21 @@ import io.github.pylonmc.rebar.block.RebarBlock
99
import io.github.pylonmc.rebar.entity.packet.BlockTextureEntity
1010
import io.github.pylonmc.rebar.i18n.PlayerTranslationHandler
1111
import io.github.pylonmc.rebar.item.ItemTypeWrapper
12+
import io.github.pylonmc.rebar.item.RebarItemSchema
1213
import io.github.pylonmc.rebar.nms.entity.BlockTextureEntityImpl
1314
import io.github.pylonmc.rebar.nms.inventory.KeyedContainerListener
1415
import io.github.pylonmc.rebar.nms.packet.PlayerPacketHandler
1516
import io.github.pylonmc.rebar.nms.recipe.AccessibleCachedCheck
1617
import io.github.pylonmc.rebar.nms.recipe.HandlerRecipeBookClick
18+
import io.github.pylonmc.rebar.nms.recipe.RecipeMapper
1719
import io.github.pylonmc.rebar.util.position.BlockPosition
1820
import io.papermc.paper.adventure.PaperAdventure
21+
import io.papermc.paper.datacomponent.DataComponentType
22+
import io.papermc.paper.datacomponent.PaperDataComponentType
1923
import kotlinx.coroutines.Dispatchers
2024
import kotlinx.coroutines.launch
2125
import net.kyori.adventure.text.Component
26+
import net.minecraft.advancements.AdvancementRewards.Builder.recipe
2227
import net.minecraft.commands.arguments.item.ItemParser
2328
import net.minecraft.core.BlockPos
2429
import net.minecraft.core.registries.Registries
@@ -35,31 +40,27 @@ import net.minecraft.world.item.crafting.RecipeManager
3540
import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity
3641
import net.minecraft.world.level.block.state.properties.Property
3742
import net.minecraft.world.phys.BlockHitResult
43+
import org.bukkit.Bukkit
3844
import org.bukkit.Material
3945
import org.bukkit.NamespacedKey
4046
import org.bukkit.World
4147
import org.bukkit.block.Block
4248
import org.bukkit.block.BlockFace
4349
import org.bukkit.craftbukkit.CraftEquipmentSlot
4450
import org.bukkit.craftbukkit.CraftRegistry
51+
import org.bukkit.craftbukkit.CraftServer
4552
import org.bukkit.craftbukkit.CraftWorld
4653
import org.bukkit.craftbukkit.block.CraftBlock
4754
import org.bukkit.craftbukkit.entity.CraftEntity
4855
import org.bukkit.craftbukkit.entity.CraftLivingEntity
4956
import org.bukkit.craftbukkit.entity.CraftPlayer
50-
import org.bukkit.craftbukkit.inventory.CraftInventory
51-
import org.bukkit.craftbukkit.inventory.CraftInventoryView
52-
import org.bukkit.craftbukkit.inventory.CraftItemStack
53-
import org.bukkit.craftbukkit.inventory.CraftItemType
57+
import org.bukkit.craftbukkit.inventory.*
5458
import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer
5559
import org.bukkit.craftbukkit.util.CraftNamespacedKey
5660
import org.bukkit.entity.Entity
5761
import org.bukkit.entity.LivingEntity
5862
import org.bukkit.entity.Player
59-
import org.bukkit.inventory.EquipmentSlot
60-
import org.bukkit.inventory.Inventory
61-
import org.bukkit.inventory.InventoryView
62-
import org.bukkit.inventory.ItemStack
63+
import org.bukkit.inventory.*
6364
import org.bukkit.persistence.PersistentDataContainer
6465
import java.lang.invoke.MethodHandle
6566
import java.lang.invoke.MethodHandles
@@ -269,20 +270,21 @@ object NmsAccessorImpl : NmsAccessor {
269270
if (idEnd == -1) idEnd = input.length
270271

271272
val typeString = input.substring(0, idEnd)
273+
val data = input.substring(idEnd)
272274
val type = ItemTypeWrapper(NamespacedKey.fromString(typeString) ?: throw IllegalArgumentException("Could not find item $typeString"))
273275
if (type is ItemTypeWrapper.Rebar) {
274-
input = "minecraft:air" + input.substring(idEnd)
276+
input = "minecraft:air$data"
275277
}
276278

277279
try {
278-
val reader = StringReader(input)
279-
val itemInput = ItemParser(CraftRegistry.getMinecraftRegistry()).parse(reader);
280-
if (reader.canRead()) {
280+
val reader = if (data.isBlank()) null else StringReader(input)
281+
val itemInput = if (data.isBlank()) null else ItemParser(CraftRegistry.getMinecraftRegistry()).parse(reader!!);
282+
if (reader != null && reader.canRead()) {
281283
throw IllegalArgumentException("Trailing input found when parsing ItemStack: " + reader.remaining);
282284
} else {
283285
val stack = type.createItemStack()
284286
val nmsStack = (stack as CraftItemStack).handle
285-
nmsStack.applyComponents(itemInput.components)
287+
itemInput?.let { nmsStack.applyComponents(it.components) }
286288
return nmsStack.asBukkitMirror()
287289
}
288290
} catch (ex: CommandSyntaxException) {
@@ -309,4 +311,49 @@ object NmsAccessorImpl : NmsAccessor {
309311
nmsPlayer.gameMode.useItemOn(nmsPlayer, level, nmsStack, nmsHand, BlockHitResult(nmsLoc, nmsDirection, nmsPos, false))
310312
}
311313
}
314+
315+
override fun hasRecipe(key: NamespacedKey): Boolean {
316+
return MinecraftServer.getServer().recipeManager.recipes.byKey(CraftNamespacedKey.toResourceKey(Registries.RECIPE, key)) != null
317+
}
318+
319+
override fun registerRecipes(recipes: Iterable<Recipe>, finalize: Boolean) {
320+
val nmsRecipes = recipes.map(RecipeMapper::convertBukkitRecipe)
321+
val recipeManager = MinecraftServer.getServer().recipeManager
322+
for (recipe in nmsRecipes) {
323+
recipeManager.recipes.addRecipe(recipe)
324+
}
325+
326+
if (finalize) {
327+
recipeManager.finalizeRecipeLoading()
328+
}
329+
}
330+
331+
override fun unregisterRecipes(recipes: Iterable<NamespacedKey>, finalize: Boolean) {
332+
val recipeManager = MinecraftServer.getServer().recipeManager
333+
var anyRemoved = false
334+
for (recipeKey in recipes) {
335+
val id = CraftNamespacedKey.toResourceKey(Registries.RECIPE, recipeKey)
336+
anyRemoved = recipeManager.recipes.removeRecipe(id) || anyRemoved
337+
}
338+
339+
if (finalize && anyRemoved) {
340+
recipeManager.finalizeRecipeLoading()
341+
}
342+
}
343+
344+
override fun getOverriddenTypes(itemStack: ItemStack): List<DataComponentType> {
345+
val schema = RebarItemSchema.fromStack(itemStack)
346+
val nmsStack = (itemStack as CraftItemStack).handle
347+
if (schema != null) {
348+
val template = schema.getOriginalTemplate()
349+
val nmsTemplate = (template as CraftItemStack).handle
350+
val types = mutableListOf<DataComponentType>()
351+
for (type in nmsTemplate.components.keySet()) {
352+
if (nmsTemplate.get(type) != nmsStack.get(type)) {
353+
types.add(PaperDataComponentType.minecraftToBukkit(type))
354+
}
355+
}
356+
}
357+
return nmsStack.componentsPatch.entrySet().map { PaperDataComponentType.minecraftToBukkit(it.key) }
358+
}
312359
}
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package io.github.pylonmc.rebar.nms.recipe
2+
3+
import net.minecraft.core.registries.Registries
4+
import net.minecraft.world.item.crafting.AbstractCookingRecipe
5+
import net.minecraft.world.item.crafting.CampfireCookingRecipe
6+
import net.minecraft.world.item.crafting.CraftingRecipe
7+
import net.minecraft.world.item.crafting.RecipeHolder
8+
import net.minecraft.world.item.crafting.ShapedRecipePattern
9+
import net.minecraft.world.item.crafting.SmeltingRecipe
10+
import net.minecraft.world.item.crafting.StonecutterRecipe
11+
import org.bukkit.craftbukkit.CraftServer
12+
import org.bukkit.craftbukkit.inventory.CraftItemStack
13+
import org.bukkit.craftbukkit.inventory.CraftRecipe
14+
import org.bukkit.craftbukkit.inventory.trim.CraftTrimPattern
15+
import org.bukkit.craftbukkit.util.CraftNamespacedKey
16+
import org.bukkit.inventory.BlastingRecipe
17+
import org.bukkit.inventory.CampfireRecipe
18+
import org.bukkit.inventory.FurnaceRecipe
19+
import org.bukkit.inventory.Recipe
20+
import org.bukkit.inventory.ShapedRecipe
21+
import org.bukkit.inventory.ShapelessRecipe
22+
import org.bukkit.inventory.SmithingTransformRecipe
23+
import org.bukkit.inventory.SmithingTrimRecipe
24+
import org.bukkit.inventory.SmokingRecipe
25+
import org.bukkit.inventory.StonecuttingRecipe
26+
import org.bukkit.inventory.TransmuteRecipe
27+
import java.util.Objects
28+
import net.minecraft.world.item.crafting.BlastingRecipe as NmsBlastingRecipe
29+
import net.minecraft.world.item.crafting.Recipe as NmsRecipe
30+
import net.minecraft.world.item.crafting.ShapedRecipe as NmsShapedRecipe
31+
import net.minecraft.world.item.crafting.ShapelessRecipe as NmsShapelessRecipe
32+
import net.minecraft.world.item.crafting.SmithingTransformRecipe as NmsSmithingTransformRecipe
33+
import net.minecraft.world.item.crafting.SmithingTrimRecipe as NmsSmithingTrimRecipe
34+
import net.minecraft.world.item.crafting.SmokingRecipe as NmsSmokingRecipe
35+
import net.minecraft.world.item.crafting.TransmuteRecipe as NmsTransmuteRecipe
36+
37+
/**
38+
* Handles the conversion of bukkit recipes to nms recipes
39+
* This has to be done manually because any existing conversion will individually
40+
* convert and automatically register the recipe to the nms recipe manager and reload it.
41+
*
42+
* This is a problem because reloading the recipe manager is an expensive operation and
43+
* doing it for every single recipe will cause a lot of lag.
44+
*
45+
* By manually converting the recipes we can register them all at once and reload
46+
* the recipe manager only once saving up a LOT of tick time.
47+
*
48+
* Reference [CraftServer.addRecipe] to keep this up to date w/ version changes
49+
*/
50+
object RecipeMapper {
51+
fun convertBukkitRecipe(recipe: Recipe): RecipeHolder<NmsRecipe<*>> {
52+
val nmsRecipe = when (recipe) {
53+
is ShapedRecipe -> convertShapedRecipe(recipe)
54+
is ShapelessRecipe -> convertShapelessRecipe(recipe)
55+
is FurnaceRecipe -> convertFurnaceRecipe(recipe)
56+
is BlastingRecipe -> convertBlastingRecipe(recipe)
57+
is CampfireRecipe -> convertCampfireRecipe(recipe)
58+
is SmokingRecipe -> convertSmokingRecipe(recipe)
59+
is StonecuttingRecipe -> convertStonecuttingRecipe(recipe)
60+
is SmithingTransformRecipe -> convertSmithingTransformRecipe(recipe)
61+
is SmithingTrimRecipe -> convertSmithingTrimRecipe(recipe)
62+
is TransmuteRecipe -> convertTransmuteRecipe(recipe)
63+
else -> {
64+
throw IllegalArgumentException("Unknown recipe type ${recipe.javaClass.canonicalName}, cannot convert to NMS type")
65+
}
66+
}
67+
return RecipeHolder(CraftNamespacedKey.toResourceKey(Registries.RECIPE, recipe.key), nmsRecipe)
68+
}
69+
70+
private fun commonInfo() = NmsRecipe.CommonInfo(true)
71+
72+
private fun convertShapedRecipe(recipe: ShapedRecipe): NmsRecipe<*> {
73+
val ingredients = recipe.choiceMap.filterValues(Objects::nonNull)
74+
.mapValues { CraftRecipe.toIngredient(it.value, false) }
75+
val shape = recipe.shape
76+
for (i in shape.indices) {
77+
val row = shape[i]
78+
shape[i] = row.toCharArray().map { if (it in ingredients) it else ' ' }.joinToString("")
79+
}
80+
return NmsShapedRecipe(
81+
commonInfo(),
82+
CraftingRecipe.CraftingBookInfo(
83+
CraftRecipe.getCategory(recipe.category),
84+
recipe.group
85+
),
86+
ShapedRecipePattern.of(ingredients, *shape),
87+
CraftItemStack.asTemplate(recipe.result)
88+
)
89+
}
90+
91+
private fun convertShapelessRecipe(recipe: ShapelessRecipe): NmsRecipe<*> {
92+
val ingredients = recipe.choiceList.map { CraftRecipe.toIngredient(it, false) }
93+
return NmsShapelessRecipe(
94+
commonInfo(),
95+
CraftingRecipe.CraftingBookInfo(
96+
CraftRecipe.getCategory(recipe.category),
97+
recipe.group
98+
),
99+
CraftItemStack.asTemplate(recipe.result),
100+
ingredients
101+
)
102+
}
103+
104+
private fun convertFurnaceRecipe(recipe: FurnaceRecipe): NmsRecipe<*> {
105+
return SmeltingRecipe(
106+
commonInfo(),
107+
AbstractCookingRecipe.CookingBookInfo(
108+
CraftRecipe.getCategory(recipe.category),
109+
recipe.group
110+
),
111+
CraftRecipe.toIngredient(recipe.inputChoice, true),
112+
CraftItemStack.asTemplate(recipe.result),
113+
recipe.experience,
114+
recipe.cookingTime
115+
)
116+
}
117+
118+
private fun convertBlastingRecipe(recipe: BlastingRecipe): NmsRecipe<*> {
119+
return NmsBlastingRecipe(
120+
commonInfo(),
121+
AbstractCookingRecipe.CookingBookInfo(
122+
CraftRecipe.getCategory(recipe.category),
123+
recipe.group
124+
),
125+
CraftRecipe.toIngredient(recipe.inputChoice, true),
126+
CraftItemStack.asTemplate(recipe.result),
127+
recipe.experience,
128+
recipe.cookingTime
129+
)
130+
}
131+
132+
private fun convertCampfireRecipe(recipe: CampfireRecipe): NmsRecipe<*> {
133+
return CampfireCookingRecipe(
134+
commonInfo(),
135+
AbstractCookingRecipe.CookingBookInfo(
136+
CraftRecipe.getCategory(recipe.category),
137+
recipe.group
138+
),
139+
CraftRecipe.toIngredient(recipe.inputChoice, true),
140+
CraftItemStack.asTemplate(recipe.result),
141+
recipe.experience,
142+
recipe.cookingTime
143+
)
144+
}
145+
146+
private fun convertSmokingRecipe(recipe: SmokingRecipe): NmsRecipe<*> {
147+
return NmsSmokingRecipe(
148+
commonInfo(),
149+
AbstractCookingRecipe.CookingBookInfo(
150+
CraftRecipe.getCategory(recipe.category),
151+
recipe.group
152+
),
153+
CraftRecipe.toIngredient(recipe.inputChoice, true),
154+
CraftItemStack.asTemplate(recipe.result),
155+
recipe.experience,
156+
recipe.cookingTime
157+
)
158+
}
159+
160+
private fun convertStonecuttingRecipe(recipe: StonecuttingRecipe): NmsRecipe<*> {
161+
return StonecutterRecipe(
162+
commonInfo(),
163+
CraftRecipe.toIngredient(recipe.inputChoice, true),
164+
CraftItemStack.asTemplate(recipe.result)
165+
)
166+
}
167+
168+
private fun convertSmithingTransformRecipe(recipe: SmithingTransformRecipe): NmsRecipe<*> {
169+
return NmsSmithingTransformRecipe(
170+
commonInfo(),
171+
CraftRecipe.toPossibleIngredient(recipe.template, false),
172+
CraftRecipe.toIngredient(recipe.base, false),
173+
CraftRecipe.toPossibleIngredient(recipe.addition, false),
174+
CraftItemStack.asTemplate(recipe.result),
175+
recipe.willCopyDataComponents()
176+
)
177+
}
178+
179+
private fun convertSmithingTrimRecipe(recipe: SmithingTrimRecipe): NmsRecipe<*> {
180+
return NmsSmithingTrimRecipe(
181+
commonInfo(),
182+
CraftRecipe.toIngredient(recipe.template, false),
183+
CraftRecipe.toIngredient(recipe.base, false),
184+
CraftRecipe.toIngredient(recipe.addition, false),
185+
CraftTrimPattern.bukkitToMinecraftHolder(recipe.trimPattern),
186+
recipe.willCopyDataComponents()
187+
)
188+
}
189+
190+
private fun convertTransmuteRecipe(recipe: TransmuteRecipe): NmsRecipe<*> {
191+
return NmsTransmuteRecipe(
192+
commonInfo(),
193+
CraftingRecipe.CraftingBookInfo(
194+
CraftRecipe.getCategory(recipe.category),
195+
recipe.group
196+
),
197+
CraftRecipe.toIngredient(recipe.input, true),
198+
CraftRecipe.toIngredient(recipe.material, true),
199+
NmsTransmuteRecipe.DEFAULT_MATERIAL_COUNT,
200+
CraftItemStack.asTemplate(recipe.result),
201+
false
202+
)
203+
}
204+
205+
}

rebar/src/main/kotlin/io/github/pylonmc/rebar/Rebar.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import io.github.pylonmc.rebar.recipe.RecipeType
4141
import io.github.pylonmc.rebar.registry.RebarRegistry
4242
import io.github.pylonmc.rebar.util.delayTicks
4343
import io.github.pylonmc.rebar.item.interfaces.*
44+
import io.github.pylonmc.rebar.nms.NmsAccessor
4445
import io.github.pylonmc.rebar.util.mergeResource
4546
import io.github.pylonmc.rebar.waila.Waila
4647
import io.github.pylonmc.rebar.waila.WailaPlaceholders
@@ -350,6 +351,7 @@ object Rebar : JavaPlugin(), RebarAddon {
350351
}
351352
}
352353

354+
NmsAccessor.processRecipeQueue()
353355
val end = System.currentTimeMillis()
354356
logger.info("Loaded recipes in ${(end - start) / 1000.0}s")
355357
RebarConfigurableRecipesLoadedEvent().callEvent()

rebar/src/main/kotlin/io/github/pylonmc/rebar/config/adapter/ConfigAdapter.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package io.github.pylonmc.rebar.config.adapter
22

3-
import io.github.pylonmc.rebar.datatypes.RebarSerializers.KEYED
43
import io.github.pylonmc.rebar.fluid.tags.FluidTemperature
4+
import io.github.pylonmc.rebar.item.ItemTypeWrapper
55
import io.github.pylonmc.rebar.registry.RebarRegistry
66
import io.github.pylonmc.rebar.util.RandomizedSound
77
import net.kyori.adventure.sound.Sound
@@ -50,6 +50,7 @@ interface ConfigAdapter<T> {
5050
@JvmField val KEYED = KeyedConfigAdapter
5151
@JvmField val NAMESPACED_KEY = ConfigAdapter { NamespacedKey.fromString(STRING.convert(it))!! }
5252
@JvmField val MATERIAL = KEYED.fromRegistry(Registry.MATERIAL)
53+
@JvmField val ITEM_TYPE_WRAPPER = KEYED.fromGetter { ItemTypeWrapper(it) }
5354
@JvmField val ITEM_STACK = ItemStackConfigAdapter
5455
@JvmField val BLOCK_DATA = ConfigAdapter { Bukkit.createBlockData(STRING.convert(it)) }
5556

@@ -133,6 +134,7 @@ interface ConfigAdapter<T> {
133134
@JvmField val RECIPE_INPUT = RecipeInputConfigAdapter
134135
@JvmField val RECIPE_INPUT_ITEM = RecipeInputItemAdapter
135136
@JvmField val RECIPE_INPUT_FLUID = RecipeInputFluidAdapter
137+
@JvmField val RECIPE_CHOICE = RecipeChoiceConfigAdapter
136138
@JvmField val ITEM_TAG = ItemTagConfigAdapter
137139
@JvmField val WEIGHTED_SET = WeightedSetConfigAdapter
138140
@JvmField val CULLING_PRESET = CullingPresetConfigAdapter

0 commit comments

Comments
 (0)