9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2026-01-06 15:52:03 +00:00

Inject Cooking Recipes

This commit is contained in:
XiaoMoMi
2025-02-15 04:13:06 +08:00
parent f832614755
commit a20a008f65
11 changed files with 576 additions and 155 deletions

View File

@@ -51,6 +51,7 @@ public class BukkitRecipeManager implements RecipeManager<ItemStack> {
private static Object minecraftRecipeManager;
private static final List<Object> injectedIngredients = new ArrayList<>();
private static final IdentityHashMap<Recipe<ItemStack>, Object> recipeToMcRecipeHolder = new IdentityHashMap<>();
private static BukkitRecipeManager instance;
static {
BUKKIT_RECIPE_REGISTER.put(RecipeTypes.SHAPED, (key, recipe) -> {
@@ -185,6 +186,7 @@ public class BukkitRecipeManager implements RecipeManager<ItemStack> {
private final CrafterEventListener crafterEventListener;
private final PaperRecipeEventListener paperRecipeEventListener;
private final Map<Key, List<Recipe<ItemStack>>> recipes;
private final Map<Key, Recipe<ItemStack>> byId;
private final VanillaRecipeReader recipeReader;
private final List<NamespacedKey> injectedDataPackRecipes;
private final List<NamespacedKey> registeredCustomRecipes;
@@ -197,8 +199,10 @@ public class BukkitRecipeManager implements RecipeManager<ItemStack> {
private Object stolenFeatureFlagSet;
public BukkitRecipeManager(BukkitCraftEngine plugin) {
instance = this;
this.plugin = plugin;
this.recipes = new HashMap<>();
this.byId = new HashMap<>();
this.injectedDataPackRecipes = new ArrayList<>();
this.registeredCustomRecipes = new ArrayList<>();
this.dataPackRecipes = new HashSet<>();
@@ -238,6 +242,11 @@ public class BukkitRecipeManager implements RecipeManager<ItemStack> {
return this.customRecipes.contains(key);
}
@Override
public Optional<Recipe<ItemStack>> getRecipeById(Key key) {
return Optional.ofNullable(this.byId.get(key));
}
@Override
public void load() {
if (!ConfigManager.enableRecipeSystem()) return;
@@ -268,6 +277,7 @@ public class BukkitRecipeManager implements RecipeManager<ItemStack> {
HandlerList.unregisterAll(this.paperRecipeEventListener);
}
this.recipes.clear();
this.byId.clear();
this.dataPackRecipes.clear();
this.customRecipes.clear();
@@ -325,6 +335,7 @@ public class BukkitRecipeManager implements RecipeManager<ItemStack> {
return this.recipes.getOrDefault(type, List.of());
}
// example: stone button
public void addVanillaInternalRecipe(Key id, Recipe<ItemStack> recipe) {
this.customRecipes.add(id);
this.recipes.computeIfAbsent(recipe.type(), k -> new ArrayList<>()).add(recipe);
@@ -343,6 +354,25 @@ public class BukkitRecipeManager implements RecipeManager<ItemStack> {
return null;
}
@Nullable
@Override
public Recipe<ItemStack> getRecipe(Key type, RecipeInput input, Key lastRecipe) {
List<Recipe<ItemStack>> recipes = this.recipes.get(type);
if (recipes == null) return null;
if (lastRecipe != null) {
Recipe<ItemStack> last = byId.get(lastRecipe);
if (last != null && last.matches(input)) {
return last;
}
for (Recipe<ItemStack> recipe : recipes) {
if (recipe.matches(input)) {
return recipe;
}
}
}
return null;
}
@Override
public CompletableFuture<Void> delayedLoad() {
if (!ConfigManager.enableRecipeSystem()) return CompletableFuture.completedFuture(null);
@@ -807,4 +837,12 @@ public class BukkitRecipeManager implements RecipeManager<ItemStack> {
public Object getRecipeHolderByRecipe(Recipe<ItemStack> recipe) {
return recipeToMcRecipeHolder.get(recipe);
}
public static Object minecraftRecipeManager() {
return minecraftRecipeManager;
}
public static BukkitRecipeManager instance() {
return instance;
}
}

View File

@@ -42,25 +42,17 @@ public class CrafterEventListener implements Listener {
return;
}
Inventory inventory = crafter.getInventory();
ItemStack[] ingredients = inventory.getStorageContents();
Key recipeId = Key.of(recipe.getKey().namespace(), recipe.getKey().value());
boolean isCustom = this.recipeManager.isCustomRecipe(recipeId);
// if the recipe is a vanilla one, custom items should never be ingredients
if (this.recipeManager.isDataPackRecipe(recipeId) && !isCustom) {
if (ItemUtils.hasCustomItem(ingredients)) {
event.setCancelled(true);
}
return;
}
// Maybe it's recipe from other plugins, then we ignore it
if (!isCustom) {
return;
}
Inventory inventory = crafter.getInventory();
ItemStack[] ingredients = inventory.getStorageContents();
List<OptimizedIDItem<ItemStack>> optimizedIDItems = new ArrayList<>();
for (ItemStack itemStack : ingredients) {
if (ItemUtils.isEmpty(itemStack)) {

View File

@@ -1,12 +1,12 @@
package net.momirealms.craftengine.bukkit.item.recipe;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.injector.BukkitInjector;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.ItemUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemManager;
import net.momirealms.craftengine.core.item.recipe.CookingInput;
import net.momirealms.craftengine.core.item.recipe.OptimizedIDItem;
import net.momirealms.craftengine.core.item.recipe.Recipe;
import net.momirealms.craftengine.core.item.recipe.RecipeTypes;
@@ -15,15 +15,17 @@ import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.VersionHelper;
import org.bukkit.Chunk;
import org.bukkit.Material;
import org.bukkit.block.*;
import org.bukkit.block.Block;
import org.bukkit.block.Furnace;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.*;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.event.world.EntitiesLoadEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.inventory.PrepareItemCraftEvent;
import org.bukkit.inventory.*;
import java.util.ArrayList;
@@ -42,6 +44,36 @@ public class RecipeEventListener implements Listener {
this.plugin = plugin;
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onFurnaceInventoryOpen(InventoryOpenEvent event) {
if (!(event.getInventory() instanceof FurnaceInventory furnaceInventory)) {
return;
}
Furnace furnace = furnaceInventory.getHolder();
try {
Object blockEntity = Reflections.field$CraftBlockEntityState$tileEntity.get(furnace);
BukkitInjector.injectFurnaceBlockEntity(blockEntity);
} catch (Exception e) {
plugin.logger().warn("Failed to inject cooking block entity", e);
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onPlaceBlock(BlockPlaceEvent event) {
Block block = event.getBlock();
Material material = block.getType();
if (material == Material.FURNACE || material == Material.BLAST_FURNACE || material == Material.SMOKER) {
if (block.getState() instanceof Furnace furnace) {
try {
Object blockEntity = Reflections.field$CraftBlockEntityState$tileEntity.get(furnace);
BukkitInjector.injectFurnaceBlockEntity(blockEntity);
} catch (Exception e) {
plugin.logger().warn("Failed to inject cooking block entity", e);
}
}
}
}
@EventHandler(ignoreCancelled = true)
public void onClickCartographyTable(InventoryClickEvent event) {
if (VersionHelper.isPaper()) return;
@@ -55,13 +87,6 @@ public class RecipeEventListener implements Listener {
});
}
@EventHandler(ignoreCancelled = true)
public void onChunkLoad(ChunkLoadEvent event) {
for (BlockState state : event.getChunk().getTileEntities()) {
}
}
@EventHandler(ignoreCancelled = true)
public void onSpecialRecipe(PrepareItemCraftEvent event) {
org.bukkit.inventory.Recipe recipe = event.getRecipe();
@@ -89,24 +114,15 @@ public class RecipeEventListener implements Listener {
CraftingRecipe craftingRecipe = (CraftingRecipe) recipe;
Key recipeId = Key.of(craftingRecipe.getKey().namespace(), craftingRecipe.getKey().value());
CraftingInventory inventory = event.getInventory();
ItemStack[] ingredients = inventory.getMatrix();
boolean isCustom = this.recipeManager.isCustomRecipe(recipeId);
// if the recipe is a vanilla one but not injected, custom items should never be ingredients
if (this.recipeManager.isDataPackRecipe(recipeId) && !isCustom) {
if (ItemUtils.hasCustomItem(ingredients)) {
inventory.setResult(null);
}
return;
}
// Maybe it's recipe from other plugins, then we ignore it
if (!isCustom) {
return;
}
CraftingInventory inventory = event.getInventory();
ItemStack[] ingredients = inventory.getMatrix();
List<OptimizedIDItem<ItemStack>> optimizedIDItems = new ArrayList<>();
for (ItemStack itemStack : ingredients) {
if (ItemUtils.isEmpty(itemStack)) {
@@ -142,6 +158,8 @@ public class RecipeEventListener implements Listener {
}
BukkitServerPlayer serverPlayer = this.plugin.adapt(player);
// though it might be inaccurate after reloading
// but it's worthy to cache
Recipe<ItemStack> lastRecipe = serverPlayer.lastUsedRecipe();
if (lastRecipe != null && (lastRecipe.type() == RecipeTypes.SHAPELESS || lastRecipe.type() == RecipeTypes.SHAPED )) {
if (lastRecipe.matches(input)) {
@@ -170,7 +188,10 @@ public class RecipeEventListener implements Listener {
private void correctCraftingRecipeUsed(CraftingInventory inventory, Recipe<ItemStack> recipe) {
Object holderOrRecipe = recipeManager.getRecipeHolderByRecipe(recipe);
if (holderOrRecipe == null) return;
if (holderOrRecipe == null) {
// it's a vanilla recipe but not injected
return;
}
try {
Object resultInventory = Reflections.field$CraftInventoryCrafting$resultInventory.get(inventory);
Reflections.field$ResultContainer$recipeUsed.set(resultInventory, holderOrRecipe);
@@ -178,110 +199,4 @@ public class RecipeEventListener implements Listener {
plugin.logger().warn("Failed to correct used recipe", e);
}
}
// TODO find a lighter way
@EventHandler(ignoreCancelled = true)
public void onFurnaceBurn(FurnaceBurnEvent event) {
if (!(event.getBlock().getState() instanceof Furnace furnace)) {
return;
}
FurnaceInventory inventory = furnace.getInventory();
}
@EventHandler(ignoreCancelled = true)
public void onFurnaceStartSmelt(FurnaceStartSmeltEvent event) {
CookingRecipe<?> recipe = event.getRecipe();
Key recipeId = Key.of(recipe.getKey().namespace(), recipe.getKey().value());
boolean isCustom = this.recipeManager.isCustomRecipe(recipeId);
ItemStack sourceItem = event.getSource();
if (this.recipeManager.isDataPackRecipe(recipeId) && !isCustom) {
if (ItemUtils.isCustomItem(sourceItem)) {
event.setTotalCookTime(Integer.MAX_VALUE);
}
return;
}
if (!isCustom) {
return;
}
Item<ItemStack> wrappedItem = this.itemManager.wrap(sourceItem);
Optional<Holder.Reference<Key>> idHolder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(wrappedItem.id());
if (idHolder.isEmpty()) {
event.setTotalCookTime(Integer.MAX_VALUE);
return;
}
CookingInput<ItemStack> input = new CookingInput<>(new OptimizedIDItem<>(idHolder.get(), event.getSource()));
net.momirealms.craftengine.core.item.recipe.CookingRecipe<ItemStack> ceRecipe;
if (recipe instanceof FurnaceRecipe) {
ceRecipe = (net.momirealms.craftengine.core.item.recipe.CookingRecipe<ItemStack>) this.recipeManager.getRecipe(RecipeTypes.SMELTING, input);
} else if (recipe instanceof SmokingRecipe) {
ceRecipe = (net.momirealms.craftengine.core.item.recipe.CookingRecipe<ItemStack>) this.recipeManager.getRecipe(RecipeTypes.SMOKING, input);
} else if (recipe instanceof BlastingRecipe) {
ceRecipe = (net.momirealms.craftengine.core.item.recipe.CookingRecipe<ItemStack>) this.recipeManager.getRecipe(RecipeTypes.BLASTING, input);
} else {
event.setTotalCookTime(Integer.MAX_VALUE);
return;
}
if (ceRecipe == null) {
event.setTotalCookTime(Integer.MAX_VALUE);
return;
}
event.setTotalCookTime(ceRecipe.cookingTime());
}
@EventHandler(ignoreCancelled = true)
public void onFurnaceSmelt(FurnaceSmeltEvent event) {
CookingRecipe<?> recipe = event.getRecipe();
if (recipe == null) return;
Key recipeId = Key.of(recipe.getKey().namespace(), recipe.getKey().value());
boolean isCustom = this.recipeManager.isCustomRecipe(recipeId);
if (this.recipeManager.isDataPackRecipe(recipeId) && !isCustom) {
if (ItemUtils.isCustomItem(event.getSource())) {
event.setCancelled(true);
}
return;
}
if (!isCustom) {
return;
}
Item<ItemStack> wrappedItem = this.itemManager.wrap(event.getSource());
Optional<Holder.Reference<Key>> idHolder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(wrappedItem.id());
if (idHolder.isEmpty()) {
event.setCancelled(true);
return;
}
CookingInput<ItemStack> input = new CookingInput<>(new OptimizedIDItem<>(idHolder.get(), event.getSource()));
if (recipe instanceof FurnaceRecipe furnaceRecipe) {
Recipe<ItemStack> ceRecipe = this.recipeManager.getRecipe(RecipeTypes.SMELTING, input);
if (ceRecipe != null) {
event.setResult(ceRecipe.getResult(null));
return;
}
} else if (recipe instanceof SmokingRecipe smokingRecipe) {
Recipe<ItemStack> ceRecipe = this.recipeManager.getRecipe(RecipeTypes.SMOKING, input);
if (ceRecipe != null) {
event.setResult(ceRecipe.getResult(null));
return;
}
} else if (recipe instanceof BlastingRecipe blastingRecipe) {
Recipe<ItemStack> ceRecipe = this.recipeManager.getRecipe(RecipeTypes.BLASTING, input);
if (ceRecipe != null) {
event.setResult(ceRecipe.getResult(null));
return;
}
}
event.setCancelled(true);
}
}

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.bukkit.plugin.injector;
import com.mojang.datafixers.util.Pair;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.description.modifier.Visibility;
@@ -12,6 +13,8 @@ import net.bytebuddy.implementation.bind.annotation.*;
import net.bytebuddy.matcher.ElementMatchers;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.block.BukkitBlockShape;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.item.recipe.BukkitRecipeManager;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.NoteBlockChainUpdateUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
@@ -19,8 +22,14 @@ import net.momirealms.craftengine.core.block.BlockKeys;
import net.momirealms.craftengine.core.block.EmptyBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.StatePredicate;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.recipe.CookingInput;
import net.momirealms.craftengine.core.item.recipe.OptimizedIDItem;
import net.momirealms.craftengine.core.item.recipe.RecipeTypes;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ReflectionUtils;
import net.momirealms.craftengine.core.util.SectionPosUtils;
@@ -30,6 +39,7 @@ import net.momirealms.craftengine.core.world.SectionPos;
import net.momirealms.craftengine.core.world.chunk.CESection;
import net.momirealms.craftengine.shared.ObjectHolder;
import net.momirealms.craftengine.shared.block.*;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
import java.lang.invoke.MethodHandle;
@@ -38,7 +48,9 @@ import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
@@ -64,8 +76,33 @@ public class BukkitInjector {
private static Field field$CraftEngineBlock$shape;
private static Field field$CraftEngineBlock$isNoteBlock;
private static Class<?> clazz$InjectedCacheChecker;
private static Field field$InjectedCacheChecker$recipeType;
private static Field field$InjectedCacheChecker$lastRecipe;
public static void init() {
try {
clazz$InjectedCacheChecker = byteBuddy
.subclass(Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING)
.implement(Reflections.clazz$RecipeManager$CachedCheck)
.defineField("recipeType", Reflections.clazz$RecipeType, Visibility.PUBLIC)
.defineField("lastRecipe", Object.class, Visibility.PUBLIC)
.method(ElementMatchers.named("getRecipeFor").or(ElementMatchers.named("a")))
.intercept(MethodDelegation.to(
VersionHelper.isVersionNewerThan1_21_2() ?
GetRecipeForMethodInterceptor1_21_2.INSTANCE :
(VersionHelper.isVersionNewerThan1_21() ?
GetRecipeForMethodInterceptor1_21.INSTANCE :
VersionHelper.isVersionNewerThan1_20_5() ?
GetRecipeForMethodInterceptor1_20_5.INSTANCE :
GetRecipeForMethodInterceptor1_20.INSTANCE)
))
.make()
.load(BukkitInjector.class.getClassLoader())
.getLoaded();
field$InjectedCacheChecker$recipeType = clazz$InjectedCacheChecker.getDeclaredField("recipeType");
field$InjectedCacheChecker$lastRecipe = clazz$InjectedCacheChecker.getDeclaredField("lastRecipe");
// Paletted Container
clazz$InjectedPalettedContainer = byteBuddy
.subclass(Reflections.clazz$PalettedContainer)
@@ -215,6 +252,16 @@ public class BukkitInjector {
}
}
public static void injectFurnaceBlockEntity(Object entity) throws ReflectiveOperationException {
if (!Reflections.clazz$AbstractFurnaceBlockEntity.isInstance(entity)) return;
Object quickCheck = Reflections.field$AbstractFurnaceBlockEntity$quickCheck.get(entity);
if (clazz$InjectedCacheChecker.isInstance(quickCheck)) return; // already injected
Object recipeType = Reflections.field$AbstractFurnaceBlockEntity$recipeType.get(entity);
Object injectedChecker = Reflections.UNSAFE.allocateInstance(clazz$InjectedCacheChecker);
field$InjectedCacheChecker$recipeType.set(injectedChecker, recipeType);
Reflections.field$AbstractFurnaceBlockEntity$quickCheck.set(entity, injectedChecker);
}
public static Object getOptimizedItemDisplayFactory() {
return instance$OptimizedItemDisplayFactory;
}
@@ -256,6 +303,229 @@ public class BukkitInjector {
}
}
public static class GetRecipeForMethodInterceptor1_20 {
public static final GetRecipeForMethodInterceptor1_20 INSTANCE = new GetRecipeForMethodInterceptor1_20();
@SuppressWarnings("unchecked")
@RuntimeType
public Object intercept(@This Object thisObj, @AllArguments Object[] args) throws Exception {
Object mcRecipeManager = BukkitRecipeManager.minecraftRecipeManager();
Object type = field$InjectedCacheChecker$recipeType.get(thisObj);
Object lastRecipe = field$InjectedCacheChecker$lastRecipe.get(thisObj);
Optional<Pair<Object, Object>> optionalRecipe = (Optional<Pair<Object, Object>>) Reflections.method$RecipeManager$getRecipeFor0.invoke(mcRecipeManager, type, args[0], args[1], lastRecipe);
if (optionalRecipe.isPresent()) {
Pair<Object, Object> pair = optionalRecipe.get();
Object resourceLocation = pair.getFirst();
Key recipeId = Key.of(resourceLocation.toString());
BukkitRecipeManager recipeManager = BukkitRecipeManager.instance();
List<Object> items = (List<Object>) Reflections.field$AbstractFurnaceBlockEntity$items.get(args[0]);
ItemStack itemStack = (ItemStack) Reflections.method$CraftItemStack$asCraftMirror.invoke(null, items.get(0));
// it's a recipe from other plugins
boolean isCustom = recipeManager.isCustomRecipe(recipeId);
if (!isCustom) {
field$InjectedCacheChecker$lastRecipe.set(thisObj, resourceLocation);
return optionalRecipe;
}
Item<ItemStack> wrappedItem = BukkitItemManager.instance().wrap(itemStack);
Optional<Holder.Reference<Key>> idHolder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(wrappedItem.id());
if (idHolder.isEmpty()) {
return Optional.empty();
}
CookingInput<ItemStack> input = new CookingInput<>(new OptimizedIDItem<>(idHolder.get(), itemStack));
net.momirealms.craftengine.core.item.recipe.CookingRecipe<ItemStack> ceRecipe;
if (type == Reflections.instance$RecipeType$SMELTING) {
ceRecipe = (net.momirealms.craftengine.core.item.recipe.CookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMELTING, input);
} else if (type == Reflections.instance$RecipeType$BLASTING) {
ceRecipe = (net.momirealms.craftengine.core.item.recipe.CookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.BLASTING, input);
} else if (type == Reflections.instance$RecipeType$SMOKING) {
ceRecipe = (net.momirealms.craftengine.core.item.recipe.CookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMOKING, input);
} else {
return Optional.empty();
}
if (ceRecipe == null) {
return Optional.empty();
}
// It doesn't matter at all
field$InjectedCacheChecker$lastRecipe.set(thisObj, resourceLocation);
return Optional.of(Optional.ofNullable(recipeManager.getRecipeHolderByRecipe(ceRecipe)).orElse(pair.getSecond()));
} else {
return Optional.empty();
}
}
}
public static class GetRecipeForMethodInterceptor1_20_5 {
public static final GetRecipeForMethodInterceptor1_20_5 INSTANCE = new GetRecipeForMethodInterceptor1_20_5();
@SuppressWarnings("unchecked")
@RuntimeType
public Object intercept(@This Object thisObj, @AllArguments Object[] args) throws Exception {
Object mcRecipeManager = BukkitRecipeManager.minecraftRecipeManager();
Object type = field$InjectedCacheChecker$recipeType.get(thisObj);
Object lastRecipe = field$InjectedCacheChecker$lastRecipe.get(thisObj);
Optional<Object> optionalRecipe = (Optional<Object>) Reflections.method$RecipeManager$getRecipeFor0.invoke(mcRecipeManager, type, args[0], args[1], lastRecipe);
if (optionalRecipe.isPresent()) {
Object holder = optionalRecipe.get();
Object id = Reflections.field$RecipeHolder$id.get(holder);
Key recipeId = Key.of(id.toString());
BukkitRecipeManager recipeManager = BukkitRecipeManager.instance();
List<Object> items = (List<Object>) Reflections.field$AbstractFurnaceBlockEntity$items.get(args[0]);
ItemStack itemStack = (ItemStack) Reflections.method$CraftItemStack$asCraftMirror.invoke(null, items.get(0));
// it's a recipe from other plugins
boolean isCustom = recipeManager.isCustomRecipe(recipeId);
if (!isCustom) {
field$InjectedCacheChecker$lastRecipe.set(thisObj, id);
return optionalRecipe;
}
Item<ItemStack> wrappedItem = BukkitItemManager.instance().wrap(itemStack);
Optional<Holder.Reference<Key>> idHolder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(wrappedItem.id());
if (idHolder.isEmpty()) {
return Optional.empty();
}
CookingInput<ItemStack> input = new CookingInput<>(new OptimizedIDItem<>(idHolder.get(), itemStack));
net.momirealms.craftengine.core.item.recipe.CookingRecipe<ItemStack> ceRecipe;
if (type == Reflections.instance$RecipeType$SMELTING) {
ceRecipe = (net.momirealms.craftengine.core.item.recipe.CookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMELTING, input);
} else if (type == Reflections.instance$RecipeType$BLASTING) {
ceRecipe = (net.momirealms.craftengine.core.item.recipe.CookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.BLASTING, input);
} else if (type == Reflections.instance$RecipeType$SMOKING) {
ceRecipe = (net.momirealms.craftengine.core.item.recipe.CookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMOKING, input);
} else {
return Optional.empty();
}
if (ceRecipe == null) {
return Optional.empty();
}
// It doesn't matter at all
field$InjectedCacheChecker$lastRecipe.set(thisObj, id);
return Optional.of(Optional.ofNullable(recipeManager.getRecipeHolderByRecipe(ceRecipe)).orElse(holder));
} else {
return Optional.empty();
}
}
}
public static class GetRecipeForMethodInterceptor1_21 {
public static final GetRecipeForMethodInterceptor1_21 INSTANCE = new GetRecipeForMethodInterceptor1_21();
@SuppressWarnings("unchecked")
@RuntimeType
public Object intercept(@This Object thisObj, @AllArguments Object[] args) throws Exception {
Object mcRecipeManager = BukkitRecipeManager.minecraftRecipeManager();
Object type = field$InjectedCacheChecker$recipeType.get(thisObj);
Object lastRecipe = field$InjectedCacheChecker$lastRecipe.get(thisObj);
Optional<Object> optionalRecipe = (Optional<Object>) Reflections.method$RecipeManager$getRecipeFor0.invoke(mcRecipeManager, type, args[0], args[1], lastRecipe);
if (optionalRecipe.isPresent()) {
Object holder = optionalRecipe.get();
Object id = Reflections.field$RecipeHolder$id.get(holder);
Key recipeId = Key.of(id.toString());
BukkitRecipeManager recipeManager = BukkitRecipeManager.instance();
ItemStack itemStack = (ItemStack) Reflections.method$CraftItemStack$asCraftMirror.invoke(null, Reflections.field$SingleRecipeInput$item.get(args[0]));
// it's a recipe from other plugins
boolean isCustom = recipeManager.isCustomRecipe(recipeId);
if (!isCustom) {
field$InjectedCacheChecker$lastRecipe.set(thisObj, id);
return optionalRecipe;
}
Item<ItemStack> wrappedItem = BukkitItemManager.instance().wrap(itemStack);
Optional<Holder.Reference<Key>> idHolder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(wrappedItem.id());
if (idHolder.isEmpty()) {
return Optional.empty();
}
CookingInput<ItemStack> input = new CookingInput<>(new OptimizedIDItem<>(idHolder.get(), itemStack));
net.momirealms.craftengine.core.item.recipe.CookingRecipe<ItemStack> ceRecipe;
if (type == Reflections.instance$RecipeType$SMELTING) {
ceRecipe = (net.momirealms.craftengine.core.item.recipe.CookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMELTING, input);
} else if (type == Reflections.instance$RecipeType$BLASTING) {
ceRecipe = (net.momirealms.craftengine.core.item.recipe.CookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.BLASTING, input);
} else if (type == Reflections.instance$RecipeType$SMOKING) {
ceRecipe = (net.momirealms.craftengine.core.item.recipe.CookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMOKING, input);
} else {
return Optional.empty();
}
if (ceRecipe == null) {
return Optional.empty();
}
// It doesn't matter at all
field$InjectedCacheChecker$lastRecipe.set(thisObj, id);
return Optional.of(Optional.ofNullable(recipeManager.getRecipeHolderByRecipe(ceRecipe)).orElse(holder));
} else {
return Optional.empty();
}
}
}
public static class GetRecipeForMethodInterceptor1_21_2 {
public static final GetRecipeForMethodInterceptor1_21_2 INSTANCE = new GetRecipeForMethodInterceptor1_21_2();
@SuppressWarnings("unchecked")
@RuntimeType
public Object intercept(@This Object thisObj, @AllArguments Object[] args) throws Exception {
Object mcRecipeManager = BukkitRecipeManager.minecraftRecipeManager();
Object type = field$InjectedCacheChecker$recipeType.get(thisObj);
Object lastRecipe = field$InjectedCacheChecker$lastRecipe.get(thisObj);
Optional<Object> optionalRecipe = (Optional<Object>) Reflections.method$RecipeManager$getRecipeFor1.invoke(mcRecipeManager, type, args[0], args[1], lastRecipe);
if (optionalRecipe.isPresent()) {
Object holder = optionalRecipe.get();
Object id = Reflections.field$RecipeHolder$id.get(holder);
Object resourceLocation = Reflections.field$ResourceKey$location.get(id);
Key recipeId = Key.of(resourceLocation.toString());
BukkitRecipeManager recipeManager = BukkitRecipeManager.instance();
ItemStack itemStack = (ItemStack) Reflections.method$CraftItemStack$asCraftMirror.invoke(null, Reflections.field$SingleRecipeInput$item.get(args[0]));
// it's a recipe from other plugins
boolean isCustom = recipeManager.isCustomRecipe(recipeId);
if (!isCustom) {
field$InjectedCacheChecker$lastRecipe.set(thisObj, id);
return optionalRecipe;
}
Item<ItemStack> wrappedItem = BukkitItemManager.instance().wrap(itemStack);
Optional<Holder.Reference<Key>> idHolder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(wrappedItem.id());
if (idHolder.isEmpty()) {
return Optional.empty();
}
CookingInput<ItemStack> input = new CookingInput<>(new OptimizedIDItem<>(idHolder.get(), itemStack));
net.momirealms.craftengine.core.item.recipe.CookingRecipe<ItemStack> ceRecipe;
if (type == Reflections.instance$RecipeType$SMELTING) {
ceRecipe = (net.momirealms.craftengine.core.item.recipe.CookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMELTING, input);
} else if (type == Reflections.instance$RecipeType$BLASTING) {
ceRecipe = (net.momirealms.craftengine.core.item.recipe.CookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.BLASTING, input);
} else if (type == Reflections.instance$RecipeType$SMOKING) {
ceRecipe = (net.momirealms.craftengine.core.item.recipe.CookingRecipe<ItemStack>) recipeManager.getRecipe(RecipeTypes.SMOKING, input);
} else {
return Optional.empty();
}
if (ceRecipe == null) {
return Optional.empty();
}
// It doesn't matter at all
field$InjectedCacheChecker$lastRecipe.set(thisObj, id);
return Optional.of(Optional.ofNullable(recipeManager.getRecipeHolderByRecipe(ceRecipe)).orElse(holder));
} else {
return Optional.empty();
}
}
}
public static class PalettedContainerMethodInterceptor {
public static final PalettedContainerMethodInterceptor INSTANCE = new PalettedContainerMethodInterceptor();

View File

@@ -1711,6 +1711,41 @@ public class Reflections {
)
);
public static final Class<?> clazz$BlockEntity = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("world.level.block.entity.BlockEntity"),
BukkitReflectionUtils.assembleMCClass("world.level.block.entity.TileEntity")
)
);
public static final Class<?> clazz$AbstractFurnaceBlockEntity = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("world.level.block.entity.AbstractFurnaceBlockEntity"),
BukkitReflectionUtils.assembleMCClass("world.level.block.entity.TileEntityFurnace")
)
);
public static final Field field$ChunkAccess$blockEntities;
static {
Field targetField = null;
for (Field field : clazz$ChunkAccess.getDeclaredFields()) {
if (Map.class.isAssignableFrom(field.getType())) {
Type genericType = field.getGenericType();
if (genericType instanceof ParameterizedType parameterizedType) {
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
if (actualTypeArguments.length == 2 &&
actualTypeArguments[0].equals(clazz$BlockPos) &&
actualTypeArguments[1].equals(clazz$BlockEntity)) {
field.setAccessible(true);
targetField = field;
}
}
}
}
field$ChunkAccess$blockEntities = targetField;
}
public static final Method method$LevelChunkSection$setBlockState = requireNonNull(
ReflectionUtils.getMethod(
clazz$LevelChunkSection, clazz$BlockState, int.class, int.class, int.class, clazz$BlockState, boolean.class
@@ -1816,6 +1851,13 @@ public class Reflections {
)
);
public static final Class<?> clazz$RecipeType = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("world.item.crafting.RecipeType"),
BukkitReflectionUtils.assembleMCClass("world.item.crafting.Recipes")
)
);
public static final Object instance$BuiltInRegistries$BLOCK;
public static final Object instance$BuiltInRegistries$ITEM;
public static final Object instance$BuiltInRegistries$ATTRIBUTE;
@@ -1825,6 +1867,7 @@ public class Reflections {
public static final Object instance$BuiltInRegistries$PARTICLE_TYPE;
public static final Object instance$BuiltInRegistries$ENTITY_TYPE;
public static final Object instance$BuiltInRegistries$FLUID;
public static final Object instance$BuiltInRegistries$RECIPE_TYPE;
public static final Object instance$InternalRegistries$DIMENSION_TYPE;
public static final Object instance$Registries$BLOCK;
@@ -1835,8 +1878,9 @@ public class Reflections {
public static final Object instance$Registries$SOUND_EVENT;
public static final Object instance$Registries$PARTICLE_TYPE;
public static final Object instance$Registries$ENTITY_TYPE;
public static final Object instance$Registries$DIMENSION_TYPE;
public static final Object instance$Registries$FLUID;
public static final Object instance$Registries$RECIPE_TYPE;
public static final Object instance$Registries$DIMENSION_TYPE;
public static final Object instance$registryAccess;
@@ -1853,6 +1897,7 @@ public class Reflections {
Object registries$EntityType = null;
Object registries$Item = null;
Object registries$Fluid = null;
Object registries$RecipeType = null;
for (Field field : fields) {
Type fieldType = field.getGenericType();
if (fieldType instanceof ParameterizedType paramType) {
@@ -1866,6 +1911,8 @@ public class Reflections {
registries$ParticleType = field.get(null);
} else if (rawType == clazz$EntityType) {
registries$EntityType = field.get(null);
} else if (rawType == clazz$RecipeType) {
registries$RecipeType = field.get(null);
}
} else {
if (type == clazz$Block) {
@@ -1900,6 +1947,7 @@ public class Reflections {
instance$Registries$PARTICLE_TYPE = requireNonNull(registries$ParticleType);
instance$Registries$ENTITY_TYPE = requireNonNull(registries$EntityType);
instance$Registries$FLUID = requireNonNull(registries$Fluid);
instance$Registries$RECIPE_TYPE = requireNonNull(registries$RecipeType);
Object server = method$MinecraftServer$getServer.invoke(null);
Object registries = field$MinecraftServer$registries.get(server);
instance$registryAccess = field$LayeredRegistryAccess$composite.get(registries);
@@ -1913,6 +1961,7 @@ public class Reflections {
instance$BuiltInRegistries$PARTICLE_TYPE = method$RegistryAccess$registryOrThrow.invoke(instance$registryAccess, registries$ParticleType);
instance$BuiltInRegistries$ENTITY_TYPE = method$RegistryAccess$registryOrThrow.invoke(instance$registryAccess, registries$EntityType);
instance$BuiltInRegistries$FLUID = method$RegistryAccess$registryOrThrow.invoke(instance$registryAccess, registries$Fluid);
instance$BuiltInRegistries$RECIPE_TYPE = method$RegistryAccess$registryOrThrow.invoke(instance$registryAccess, registries$RecipeType);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
@@ -3359,6 +3408,28 @@ public class Reflections {
}
}
public static final Object instance$RecipeType$CRAFTING;
public static final Object instance$RecipeType$SMELTING;
public static final Object instance$RecipeType$BLASTING;
public static final Object instance$RecipeType$SMOKING;
public static final Object instance$RecipeType$CAMPFIRE_COOKING;
public static final Object instance$RecipeType$STONECUTTING;
public static final Object instance$RecipeType$SMITHING;
static {
try {
instance$RecipeType$CRAFTING = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$RECIPE_TYPE, method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "crafting"));
instance$RecipeType$SMELTING = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$RECIPE_TYPE, method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "smelting"));
instance$RecipeType$BLASTING = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$RECIPE_TYPE, method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "blasting"));
instance$RecipeType$SMOKING = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$RECIPE_TYPE, method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "smoking"));
instance$RecipeType$CAMPFIRE_COOKING = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$RECIPE_TYPE, method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "campfire_cooking"));
instance$RecipeType$STONECUTTING = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$RECIPE_TYPE, method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "stonecutting"));
instance$RecipeType$SMITHING = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$RECIPE_TYPE, method$ResourceLocation$fromNamespaceAndPath.invoke(null, "minecraft", "smithing"));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public static final Method method$BlockState$getShape = requireNonNull(
ReflectionUtils.getMethod(
clazz$BlockStateBase, clazz$VoxelShape, new String[]{"getShape", "a"}, clazz$BlockGetter, clazz$BlockPos, clazz$CollisionContext
@@ -3841,6 +3912,13 @@ public class Reflections {
)
);
public static final Class<?> clazz$RecipeManager$CachedCheck = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("world.item.crafting.RecipeManager$CachedCheck"),
BukkitReflectionUtils.assembleMCClass("world.item.crafting.CraftingManager$a")
)
);
public static final Method method$RecipeManager$finalizeRecipeLoading =
ReflectionUtils.getMethod(
clazz$RecipeManager, new String[]{"finalizeRecipeLoading"}
@@ -4034,6 +4112,10 @@ public class Reflections {
.map(it -> ReflectionUtils.getDeclaredField(it, 1))
.orElse(null);
public static final Field field$RecipeHolder$id = Optional.ofNullable(clazz$RecipeHolder)
.map(it -> ReflectionUtils.getDeclaredField(it, 0))
.orElse(null);
public static final Class<?> clazz$ShapelessRecipe = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("world.item.crafting.ShapelessRecipe"),
@@ -4175,7 +4257,8 @@ public class Reflections {
// for 1.20.1-1.21.1
public static final Class<?> clazz$AbstractCookingRecipe = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("world.item.crafting.AbstractCookingRecipe")
BukkitReflectionUtils.assembleMCClass("world.item.crafting.AbstractCookingRecipe"),
BukkitReflectionUtils.assembleMCClass("world.item.crafting.RecipeCooking")
)
);
@@ -4244,4 +4327,60 @@ public class Reflections {
clazz$CraftCampfireRecipe, clazz$CraftCampfireRecipe, CampfireRecipe.class
)
);
public static final Field field$AbstractFurnaceBlockEntity$recipeType = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$AbstractFurnaceBlockEntity, clazz$RecipeType, 0
)
);
public static final Field field$AbstractFurnaceBlockEntity$quickCheck = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$AbstractFurnaceBlockEntity, clazz$RecipeManager$CachedCheck, 0
)
);
// 1.21+
public static final Class<?> clazz$RecipeInput = ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("world.item.crafting.RecipeInput")
);
public static final Class<?> clazz$SingleRecipeInput = ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("world.item.crafting.SingleRecipeInput")
);
// 1.20.1-1.21.1
public static final Method method$RecipeManager$getRecipeFor0 =
ReflectionUtils.getMethod(
clazz$RecipeManager, Optional.class, clazz$RecipeType, clazz$Container, clazz$Level, clazz$ResourceLocation
);
// 1.21.2+
public static final Method method$RecipeManager$getRecipeFor1 =
ReflectionUtils.getMethod(
clazz$RecipeManager, Optional.class, clazz$RecipeType, clazz$RecipeInput, clazz$Level, clazz$ResourceKey
);
// 1.21+
public static final Field field$SingleRecipeInput$item = Optional.ofNullable(clazz$SingleRecipeInput)
.map(it -> ReflectionUtils.getDeclaredField(it, clazz$ItemStack, 0))
.orElse(null);
public static final Field field$AbstractFurnaceBlockEntity$items = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$AbstractFurnaceBlockEntity, clazz$NonNullList, 0
)
);
public static final Class<?> clazz$CraftBlockEntityState = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleCBClass("block.CraftBlockEntityState")
)
);
public static final Field field$CraftBlockEntityState$tileEntity = requireNonNull(
ReflectionUtils.getDeclaredField(
clazz$CraftBlockEntityState, 0
)
);
}

View File

@@ -4,6 +4,7 @@ import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.injector.BukkitInjector;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.plugin.config.ConfigManager;
import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.ChunkPos;
@@ -268,6 +269,13 @@ public class BukkitWorldManager implements WorldManager, Listener {
}
BukkitInjector.injectLevelChunkSection(section, ceSection, ceWorld, new SectionPos(pos.x, ceChunk.sectionY(i), pos.z));
}
if (ConfigManager.enableRecipeSystem()) {
@SuppressWarnings("unchecked")
Map<Object, Object> blockEntities = (Map<Object, Object>) Reflections.field$ChunkAccess$blockEntities.get(levelChunk);
for (Object blockEntity : blockEntities.values()) {
BukkitInjector.injectFurnaceBlockEntity(blockEntity);
}
}
} catch (ReflectiveOperationException e) {
this.plugin.logger().warn("Failed to restore chunk at " + chunk.getX() + " " + chunk.getZ(), e);
return;