9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-27 10:59:07 +00:00

配方重构

This commit is contained in:
XiaoMoMi
2025-08-03 21:07:43 +08:00
parent 200c3911f5
commit 44e2f234de
74 changed files with 1227 additions and 2394 deletions

View File

@@ -21,12 +21,12 @@ public class BukkitCustomItem extends AbstractCustomItem<ItemStack> {
private final Object item;
private final Object clientItem;
public BukkitCustomItem(UniqueKey id, Object item, Object clientItem, Key materialKey, Key clientBoundMaterialKey,
public BukkitCustomItem(boolean isVanillaItem, UniqueKey id, Object item, Object clientItem, Key materialKey, Key clientBoundMaterialKey,
List<ItemBehavior> behaviors,
List<ItemDataModifier<ItemStack>> modifiers, List<ItemDataModifier<ItemStack>> clientBoundModifiers,
ItemSettings settings,
Map<EventTrigger, List<Function<PlayerOptionalContext>>> events) {
super(id, materialKey, clientBoundMaterialKey, behaviors, modifiers, clientBoundModifiers, settings, events);
super(isVanillaItem, id, materialKey, clientBoundMaterialKey, behaviors, modifiers, clientBoundModifiers, settings, events);
this.item = item;
this.clientItem = clientItem;
}
@@ -64,6 +64,7 @@ public class BukkitCustomItem extends AbstractCustomItem<ItemStack> {
}
public static class BuilderImpl implements Builder<ItemStack> {
private boolean isVanillaItem;
private UniqueKey id;
private Key itemKey;
private final Object item;
@@ -80,6 +81,12 @@ public class BukkitCustomItem extends AbstractCustomItem<ItemStack> {
this.clientBoundItem = clientBoundItem;
}
@Override
public Builder<ItemStack> isVanillaItem(boolean is) {
this.isVanillaItem = is;
return this;
}
@Override
public Builder<ItemStack> id(UniqueKey id) {
this.id = id;
@@ -150,7 +157,7 @@ public class BukkitCustomItem extends AbstractCustomItem<ItemStack> {
public CustomItem<ItemStack> build() {
this.modifiers.addAll(this.settings.modifiers());
this.clientBoundModifiers.addAll(this.settings.clientBoundModifiers());
return new BukkitCustomItem(this.id, this.item, this.clientBoundItem, this.itemKey, this.clientBoundItemKey, List.copyOf(this.behaviors),
return new BukkitCustomItem(this.isVanillaItem, this.id, this.item, this.clientBoundItem, this.itemKey, this.clientBoundItemKey, List.copyOf(this.behaviors),
List.copyOf(this.modifiers), List.copyOf(this.clientBoundModifiers), this.settings, this.events);
}
}

View File

@@ -17,7 +17,7 @@ import net.momirealms.craftengine.bukkit.util.ItemStackUtils;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.*;
import net.momirealms.craftengine.core.item.modifier.IdModifier;
import net.momirealms.craftengine.core.item.recipe.DatapackRecipeResult;
import net.momirealms.craftengine.core.item.recipe.UniqueIdItem;
import net.momirealms.craftengine.core.pack.AbstractPackManager;
import net.momirealms.craftengine.core.plugin.config.Config;
@@ -70,10 +70,9 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
this.bedrockItemHolder = FastNMS.INSTANCE.method$Registry$getHolderByResourceKey(MBuiltInRegistries.ITEM, FastNMS.INSTANCE.method$ResourceKey$create(MRegistries.ITEM, KeyUtils.toResourceLocation(Key.of("minecraft:bedrock")))).get();
this.registerCustomTrimMaterial();
this.loadLastRegisteredPatterns();
ItemStack emptyStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(CoreReflections.instance$ItemStack$EMPTY);
this.emptyItem = this.wrap(emptyStack);
this.emptyUniqueItem = new UniqueIdItem<>(UniqueKey.AIR, this.emptyItem);
this.emptyItem = this.factory.wrap(emptyStack);
this.emptyUniqueItem = UniqueIdItem.of(this.emptyItem);
this.decoratedHashOpsGenerator = VersionHelper.isOrAbove1_21_5() ? (Function<Object, Integer>) FastNMS.INSTANCE.createDecoratedHashOpsGenerator(MRegistryOps.HASHCODE) : null;
}
@@ -158,13 +157,33 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
}
}
@Override
public Item<ItemStack> build(DatapackRecipeResult result) {
if (result.components() == null) {
ItemStack itemStack = createVanillaItemStack(Key.of(result.id()));
return wrap(itemStack).count(result.count());
} else {
// 低版本无法应用nbt或组件,所以这里是1.20.5+
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("id", result.id());
jsonObject.addProperty("count", result.count());
jsonObject.add("components", result.components());
Object nmsStack = CoreReflections.instance$ItemStack$CODEC.parse(MRegistryOps.JSON, jsonObject)
.resultOrPartial((itemId) -> plugin.logger().severe("Tried to load invalid item: '" + itemId + "'")).orElse(null);
if (nmsStack == null) {
return this.emptyItem;
}
return wrap(FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(nmsStack));
}
}
@Override
public Optional<BuildableItem<ItemStack>> getVanillaItem(Key key) {
ItemStack vanilla = createVanillaItemStack(key);
if (vanilla == null) {
return Optional.empty();
}
return Optional.of(new CloneableConstantItem(key, this.wrap(vanilla)));
return Optional.of(CloneableConstantItem.of(this.wrap(vanilla)));
}
@Override
@@ -356,23 +375,10 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
@Override
public @NotNull Item<ItemStack> wrap(ItemStack itemStack) {
if (itemStack == null) return this.emptyItem;
if (itemStack == null || itemStack.isEmpty()) return this.emptyItem;
return this.factory.wrap(itemStack);
}
@Override
public Key itemId(ItemStack itemStack) {
Item<ItemStack> wrapped = wrap(itemStack);
return wrapped.id();
}
@Override
public Key customItemId(ItemStack itemStack) {
Item<ItemStack> wrapped = wrap(itemStack);
if (!wrapped.hasTag(IdModifier.CRAFT_ENGINE_ID)) return null;
return wrapped.id();
}
@Override
protected CustomItem.Builder<ItemStack> createPlatformItemBuilder(UniqueKey id, Key materialId, Key clientBoundMaterialId) {
Object item = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.ITEM, KeyUtils.toResourceLocation(materialId));

View File

@@ -1,32 +0,0 @@
package net.momirealms.craftengine.bukkit.item;
import net.momirealms.craftengine.core.item.BuildableItem;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.util.Key;
import org.bukkit.inventory.ItemStack;
public class CloneableConstantItem implements BuildableItem<ItemStack> {
private final Item<ItemStack> item;
private final Key id;
public CloneableConstantItem(Key id, Item<ItemStack> item) {
this.item = item;
this.id = id;
}
@Override
public Key id() {
return this.id;
}
@Override
public Item<ItemStack> buildItem(ItemBuildContext context, int count) {
return this.item.copyWithCount(count);
}
@Override
public ItemStack buildItemStack(ItemBuildContext context, int count) {
return this.item.copyWithCount(count).getItem();
}
}

View File

@@ -8,7 +8,6 @@ import net.momirealms.craftengine.bukkit.util.ItemTags;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.core.item.ExternalItemSource;
import net.momirealms.craftengine.core.item.ItemFactory;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.item.ItemWrapper;
import net.momirealms.craftengine.core.item.data.JukeboxPlayable;
import net.momirealms.craftengine.core.item.setting.EquipmentData;
@@ -66,6 +65,11 @@ public abstract class BukkitItemFactory<W extends ItemWrapper<ItemStack>> extend
}
}
@Override
protected boolean isEmpty(W item) {
return FastNMS.INSTANCE.method$ItemStack$isEmpty(item.getLiteralObject());
}
@SuppressWarnings("deprecation")
@Override
protected byte[] toByteArray(W item) {
@@ -80,12 +84,15 @@ public abstract class BukkitItemFactory<W extends ItemWrapper<ItemStack>> extend
@Override
protected Key vanillaId(W item) {
Object i = FastNMS.INSTANCE.method$ItemStack$getItem(item.getLiteralObject());
if (i == null) return ItemKeys.AIR;
if (i == null) return null;
return KeyUtils.resourceLocationToKey(FastNMS.INSTANCE.method$Registry$getKey(MBuiltInRegistries.ITEM, i));
}
@Override
protected Key id(W item) {
if (FastNMS.INSTANCE.method$ItemStack$isEmpty(item.getLiteralObject())) {
return null;
}
return customId(item).orElse(vanillaId(item));
}
@@ -96,6 +103,9 @@ public abstract class BukkitItemFactory<W extends ItemWrapper<ItemStack>> extend
@Override
protected UniqueKey recipeIngredientID(W item) {
if (FastNMS.INSTANCE.method$ItemStack$isEmpty(item.getLiteralObject())) {
return null;
}
if (this.hasExternalRecipeSource) {
for (ExternalItemSource<ItemStack> source : this.recipeIngredientSources) {
String id = source.id(item.getItem());

View File

@@ -31,11 +31,6 @@ public class ComponentItemFactory1_20_5 extends BukkitItemFactory<ComponentItemW
super(plugin);
}
@Override
protected boolean isEmpty(ComponentItemWrapper item) {
return item.getItem().isEmpty();
}
@Override
protected void customId(ComponentItemWrapper item, Key id) {
FastNMS.INSTANCE.setCustomItemId(item.getLiteralObject(), id.toString());

View File

@@ -67,11 +67,6 @@ public class UniversalItemFactory extends BukkitItemFactory<LegacyItemWrapper> {
return item.remove(path);
}
@Override
protected boolean isEmpty(LegacyItemWrapper item) {
return item.getItem().isEmpty();
}
@Override
protected Optional<Key> customId(LegacyItemWrapper item) {
Object id = item.getJavaTag(IdModifier.CRAFT_ENGINE_ID);

View File

@@ -1,82 +0,0 @@
package net.momirealms.craftengine.bukkit.item.recipe;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.util.ItemStackUtils;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.item.ItemManager;
import net.momirealms.craftengine.core.item.recipe.Recipe;
import net.momirealms.craftengine.core.item.recipe.RecipeType;
import net.momirealms.craftengine.core.item.recipe.UniqueIdItem;
import net.momirealms.craftengine.core.item.recipe.input.CraftingInput;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.util.Key;
import org.bukkit.block.Crafter;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.CrafterCraftEvent;
import org.bukkit.inventory.CraftingRecipe;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.List;
public class CrafterEventListener implements Listener {
private final ItemManager<ItemStack> itemManager;
private final BukkitRecipeManager recipeManager;
private final BukkitCraftEngine plugin;
public CrafterEventListener(BukkitCraftEngine plugin, BukkitRecipeManager recipeManager, ItemManager<ItemStack> itemManager) {
this.itemManager = itemManager;
this.recipeManager = recipeManager;
this.plugin = plugin;
}
@EventHandler
public void onCrafting(CrafterCraftEvent event) {
if (!Config.enableRecipeSystem()) return;
CraftingRecipe recipe = event.getRecipe();
if (!(event.getBlock().getState() instanceof Crafter crafter)) {
return;
}
Key recipeId = Key.of(recipe.getKey().namespace(), recipe.getKey().value());
boolean isCustom = this.recipeManager.isCustomRecipe(recipeId);
// Maybe it's recipe from other plugins, then we ignore it
if (!isCustom) {
return;
}
Inventory inventory = crafter.getInventory();
ItemStack[] ingredients = inventory.getStorageContents();
List<UniqueIdItem<ItemStack>> uniqueIdItems = new ArrayList<>();
for (ItemStack itemStack : ingredients) {
if (ItemStackUtils.isEmpty(itemStack)) {
uniqueIdItems.add(this.itemManager.uniqueEmptyItem());
} else {
Item<ItemStack> wrappedItem = this.itemManager.wrap(itemStack);
uniqueIdItems.add(new UniqueIdItem<>(wrappedItem.recipeIngredientId(), wrappedItem));
}
}
CraftingInput<ItemStack> input;
if (ingredients.length == 9) {
input = CraftingInput.of(3, 3, uniqueIdItems);
} else if (ingredients.length == 4) {
input = CraftingInput.of(2, 2, uniqueIdItems);
} else {
return;
}
Recipe<ItemStack> ceRecipe = this.recipeManager.recipeByInput(RecipeType.CRAFTING, input);
if (ceRecipe != null) {
event.setResult(ceRecipe.assemble(input, ItemBuildContext.EMPTY));
return;
}
// clear result if not met
event.setCancelled(true);
}
}

View File

@@ -4,12 +4,9 @@ import com.destroystokyo.paper.event.inventory.PrepareResultEvent;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.item.ComponentTypes;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.injector.RecipeInjector;
import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRecipeTypes;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.ComponentUtils;
import net.momirealms.craftengine.bukkit.util.InventoryUtils;
@@ -28,21 +25,14 @@ import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.context.ContextHolder;
import net.momirealms.craftengine.core.util.*;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.Campfire;
import org.bukkit.block.Furnace;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.*;
import org.bukkit.event.inventory.*;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.*;
import org.bukkit.inventory.view.AnvilView;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
@@ -74,7 +64,7 @@ public class RecipeEventListener implements Listener {
ItemStack item = event.getCurrentItem();
if (ItemStackUtils.isEmpty(item)) return;
if (ItemStackUtils.isEmpty(fuelStack)) {
SingleItemInput<ItemStack> input = new SingleItemInput<>(getUniqueIdItem(item));
SingleItemInput<ItemStack> input = new SingleItemInput<>(ItemStackUtils.getUniqueIdItem(item));
RecipeType recipeType;
if (furnaceInventory.getType() == InventoryType.FURNACE) {
recipeType = RecipeType.SMELTING;
@@ -258,166 +248,9 @@ public class RecipeEventListener implements Listener {
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onFurnaceInventoryOpen(InventoryOpenEvent event) {
if (!Config.enableRecipeSystem()) return;
if (!(event.getInventory() instanceof FurnaceInventory furnaceInventory)) {
return;
}
Furnace furnace = furnaceInventory.getHolder();
try {
Object blockEntity = CraftBukkitReflections.field$CraftBlockEntityState$tileEntity.get(furnace);
RecipeInjector.injectCookingBlockEntity(blockEntity);
} catch (Exception e) {
this.plugin.logger().warn("Failed to inject cooking block entity", e);
}
}
// for 1.20.1-1.21.1
@EventHandler(ignoreCancelled = true)
public void onBlockIgnite(BlockIgniteEvent event) {
if (!Config.enableRecipeSystem()) return;
if (VersionHelper.isOrAbove1_21_2()) return;
Block block = event.getBlock();
Material material = block.getType();
if (material == Material.CAMPFIRE) {
if (block.getState() instanceof Campfire campfire) {
try {
Object blockEntity = CraftBukkitReflections.field$CraftBlockEntityState$tileEntity.get(campfire);
RecipeInjector.injectCookingBlockEntity(blockEntity);
} catch (Exception e) {
this.plugin.logger().warn("Failed to inject cooking block entity", e);
}
}
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onPlaceBlock(BlockPlaceEvent event) {
if (!Config.enableRecipeSystem()) return;
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 = CraftBukkitReflections.field$CraftBlockEntityState$tileEntity.get(furnace);
RecipeInjector.injectCookingBlockEntity(blockEntity);
} catch (Exception e) {
plugin.logger().warn("Failed to inject cooking block entity", e);
}
}
} else if (!VersionHelper.isOrAbove1_21_2() && material == Material.CAMPFIRE) {
if (block.getState() instanceof Campfire campfire) {
try {
Object blockEntity = CraftBukkitReflections.field$CraftBlockEntityState$tileEntity.get(campfire);
RecipeInjector.injectCookingBlockEntity(blockEntity);
} catch (Exception e) {
this.plugin.logger().warn("Failed to inject cooking block entity", e);
}
}
}
}
// for 1.21.2+
@EventHandler(ignoreCancelled = true)
public void onPutItemOnCampfire(PlayerInteractEvent event) {
if (!Config.enableRecipeSystem()) return;
if (!VersionHelper.isOrAbove1_21_2()) return;
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
Block clicked = event.getClickedBlock();
if (clicked == null) return;
Material type = clicked.getType();
if (type != Material.CAMPFIRE && type != Material.SOUL_CAMPFIRE) return;
if (clicked.getState() instanceof Campfire campfire) {
try {
Object blockEntity = CraftBukkitReflections.field$CraftBlockEntityState$tileEntity.get(campfire);
RecipeInjector.injectCookingBlockEntity(blockEntity);
} catch (Exception e) {
this.plugin.logger().warn("Failed to inject cooking block entity", e);
}
}
ItemStack itemStack = event.getItem();
if (ItemStackUtils.isEmpty(itemStack)) return;
try {
@SuppressWarnings("unchecked")
Optional<Object> optionalMCRecipe = FastNMS.INSTANCE.method$RecipeManager$getRecipeFor(
BukkitRecipeManager.minecraftRecipeManager(),
MRecipeTypes.CAMPFIRE_COOKING,
CoreReflections.constructor$SingleRecipeInput.newInstance(FastNMS.INSTANCE.method$CraftItemStack$asNMSCopy(itemStack)),
FastNMS.INSTANCE.field$CraftWorld$ServerLevel(event.getPlayer().getWorld()),
null
);
if (optionalMCRecipe.isEmpty()) {
return;
}
SingleItemInput<ItemStack> input = new SingleItemInput<>(getUniqueIdItem(itemStack));
CustomCampfireRecipe<ItemStack> ceRecipe = (CustomCampfireRecipe<ItemStack>) this.recipeManager.recipeByInput(RecipeType.CAMPFIRE_COOKING, input);
if (ceRecipe == null) {
event.setCancelled(true);
}
} catch (Exception e) {
this.plugin.logger().warn("Failed to handle interact campfire", e);
}
}
// for 1.21.2+
@SuppressWarnings("UnstableApiUsage")
@EventHandler(ignoreCancelled = true)
public void onCampfireCook(CampfireStartEvent event) {
if (!Config.enableRecipeSystem()) return;
if (!VersionHelper.isOrAbove1_21_2()) return;
CampfireRecipe recipe = event.getRecipe();
Key recipeId = new Key(recipe.getKey().namespace(), recipe.getKey().value());
boolean isCustom = this.recipeManager.isCustomRecipe(recipeId);
if (!isCustom) {
return;
}
ItemStack itemStack = event.getSource();
SingleItemInput<ItemStack> input = new SingleItemInput<>(getUniqueIdItem(itemStack));
CustomCampfireRecipe<ItemStack> ceRecipe = (CustomCampfireRecipe<ItemStack>) this.recipeManager.recipeByInput(RecipeType.CAMPFIRE_COOKING, input);
if (ceRecipe == null) {
event.setTotalCookTime(Integer.MAX_VALUE);
return;
}
event.setTotalCookTime(ceRecipe.cookingTime());
}
// for 1.21.2+
@EventHandler(ignoreCancelled = true)
public void onCampfireCook(BlockCookEvent event) {
if (!Config.enableRecipeSystem()) return;
if (!VersionHelper.isOrAbove1_21_2()) return;
Material type = event.getBlock().getType();
if (type != Material.CAMPFIRE && type != Material.SOUL_CAMPFIRE) return;
CampfireRecipe recipe = (CampfireRecipe) event.getRecipe();
if (recipe == null) return;
Key recipeId = new Key(recipe.getKey().namespace(), recipe.getKey().value());
boolean isCustom = this.recipeManager.isCustomRecipe(recipeId);
if (!isCustom) {
return;
}
ItemStack itemStack = event.getSource();
SingleItemInput<ItemStack> input = new SingleItemInput<>(getUniqueIdItem(itemStack));
CustomCampfireRecipe<ItemStack> ceRecipe = (CustomCampfireRecipe<ItemStack>) this.recipeManager.recipeByInput(RecipeType.CAMPFIRE_COOKING, input);
if (ceRecipe == null) {
event.setCancelled(true);
return;
}
event.setResult(ceRecipe.result(ItemBuildContext.EMPTY));
}
// Paper only
@EventHandler
public void onPrepareResult(PrepareResultEvent event) {
// if (!ConfigManager.enableRecipeSystem()) return;
if (event.getInventory() instanceof CartographyInventory cartographyInventory) {
if (ItemStackUtils.hasCustomItem(cartographyInventory.getStorageContents())) {
event.setResult(new ItemStack(Material.AIR));
@@ -745,98 +578,35 @@ public class RecipeEventListener implements Listener {
return new Pair<>(first, second);
}
// 不是完美的解决方案,仍然需要更多的探讨
// TODO 生成类代理掉ResultSlot并注入menu的slots对象修改掉onTake方法
// TODO 对于耐久度降低的配方应该注册special recipe
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onCraft(CraftItemEvent event) {
org.bukkit.inventory.Recipe recipe = event.getRecipe();
if (!(recipe instanceof ShapelessRecipe) && !(recipe instanceof ShapedRecipe)) return;
HumanEntity humanEntity = event.getWhoClicked();
if (!(humanEntity instanceof Player player)) return;
CraftingInventory inventory = event.getInventory();
ItemStack result = inventory.getResult();
if (result == null) return;
ItemStack[] usedItems = inventory.getMatrix();
ItemStack[] replacements = new ItemStack[usedItems.length];
boolean hasReplacement = false;
for (int i = 0; i < usedItems.length; i++) {
ItemStack usedItem = usedItems[i];
if (ItemStackUtils.isEmpty(usedItem)) continue;
if (usedItem.getAmount() != 1) continue;
Item<ItemStack> wrapped = BukkitItemManager.instance().wrap(usedItem);
if (ItemUtils.isEmpty(wrapped)) continue;
Optional<CustomItem<ItemStack>> optionalCustomItem = wrapped.getCustomItem();
if (optionalCustomItem.isPresent()) {
CustomItem<ItemStack> customItem = optionalCustomItem.get();
Key remainingItem = customItem.settings().craftRemainder();
if (remainingItem != null) {
replacements[i] = BukkitItemManager.instance().buildItemStack(remainingItem, this.plugin.adapt(player));
hasReplacement = true;
}
}
}
if (!hasReplacement) return;
Runnable delayedTask = () -> {
for (int i = 0; i < replacements.length; i++) {
if (replacements[i] == null) continue;
inventory.setItem(i + 1, replacements[i]);
}
};
if (VersionHelper.isFolia()) {
player.getScheduler().run(this.plugin.javaPlugin(), (t) -> delayedTask.run(), () -> {});
} else {
this.plugin.scheduler().sync().runDelayed(delayedTask);
}
}
@EventHandler(ignoreCancelled = true)
public void onCraftingRecipe(PrepareItemCraftEvent event) {
if (!Config.enableRecipeSystem()) return;
org.bukkit.inventory.Recipe recipe = event.getRecipe();
// we only handle shaped and shapeless recipes
boolean shapeless = recipe instanceof ShapelessRecipe;
boolean shaped = recipe instanceof ShapedRecipe;
if (!shaped && !shapeless) return;
CraftingRecipe craftingRecipe = (CraftingRecipe) recipe;
if (!(recipe instanceof CraftingRecipe craftingRecipe)) return;
Key recipeId = Key.of(craftingRecipe.getKey().namespace(), craftingRecipe.getKey().value());
boolean isCustom = this.recipeManager.isCustomRecipe(recipeId);
// Maybe it's recipe from other plugins, then we ignore it
if (!isCustom) {
Optional<Recipe<ItemStack>> optionalRecipe = this.recipeManager.recipeById(recipeId);
// 也许是其他插件注册的配方,直接无视
if (optionalRecipe.isEmpty()) {
return;
}
CraftingInventory inventory = event.getInventory();
if (!(optionalRecipe.get() instanceof CustomCraftingTableRecipe<ItemStack> craftingTableRecipe)) {
inventory.setResult(null);
return;
}
CraftingInput<ItemStack> input = getCraftingInput(inventory);
if (input == null) return;
Player player = InventoryUtils.getPlayerFromInventoryEvent(event);
BukkitServerPlayer serverPlayer = this.plugin.adapt(player);
Recipe<ItemStack> ceRecipe = this.recipeManager.recipeByInput(RecipeType.CRAFTING, input, recipeId);
if (ceRecipe != null) {
inventory.setResult(ceRecipe.assemble(input, new ItemBuildContext(serverPlayer, ContextHolder.EMPTY)));
serverPlayer.setLastUsedRecipe(ceRecipe.id());
if (!ceRecipe.id().equals(recipeId)) {
correctCraftingRecipeUsed(inventory, ceRecipe);
}
return;
}
// clear result if not met
inventory.setResult(null);
inventory.setResult(craftingTableRecipe.assemble(input, new ItemBuildContext(serverPlayer, ContextHolder.EMPTY)));
}
private CraftingInput<ItemStack> getCraftingInput(CraftingInventory inventory) {
ItemStack[] ingredients = inventory.getMatrix();
List<UniqueIdItem<ItemStack>> uniqueIdItems = new ArrayList<>();
for (ItemStack itemStack : ingredients) {
uniqueIdItems.add(getUniqueIdItem(itemStack));
uniqueIdItems.add(ItemStackUtils.getUniqueIdItem(itemStack));
}
CraftingInput<ItemStack> input;
if (ingredients.length == 9) {
input = CraftingInput.of(3, 3, uniqueIdItems);
@@ -848,17 +618,6 @@ public class RecipeEventListener implements Listener {
return input;
}
private void correctCraftingRecipeUsed(CraftingInventory inventory, Recipe<ItemStack> recipe) {
Object holderOrRecipe = this.recipeManager.nmsRecipeHolderByRecipe(recipe);
if (holderOrRecipe == null) {
return;
}
Object resultInventory = FastNMS.INSTANCE.method$CraftInventoryCrafting$getResultInventory(inventory);
FastNMS.INSTANCE.method$ResultContainer$setRecipeUsed(resultInventory, holderOrRecipe);
Object matrixInventory = FastNMS.INSTANCE.method$CraftInventoryCrafting$getMatrixInventory(inventory);
FastNMS.INSTANCE.method$CraftingContainer$setCurrentRecipe(matrixInventory, holderOrRecipe);
}
@EventHandler(ignoreCancelled = true)
public void onSmithingTrim(PrepareSmithingEvent event) {
SmithingInventory inventory = event.getInventory();
@@ -880,31 +639,17 @@ public class RecipeEventListener implements Listener {
}
Key recipeId = Key.of(recipe.getKey().namespace(), recipe.getKey().value());
boolean isCustom = this.recipeManager.isCustomRecipe(recipeId);
// Maybe it's recipe from other plugins, then we ignore it
if (!isCustom) {
Optional<Recipe<ItemStack>> optionalRecipe = this.recipeManager.recipeById(recipeId);
if (optionalRecipe.isEmpty()) {
return;
}
SmithingInput<ItemStack> input = new SmithingInput<>(
getUniqueIdItem(inventory.getInputEquipment()),
getUniqueIdItem(inventory.getInputTemplate()),
getUniqueIdItem(inventory.getInputMineral())
);
Recipe<ItemStack> ceRecipe = this.recipeManager.recipeByInput(RecipeType.SMITHING, input, recipeId);
if (ceRecipe == null) {
if (!(optionalRecipe.get() instanceof CustomSmithingTrimRecipe<ItemStack> smithingTrimRecipe)) {
event.setResult(null);
return;
}
Player player = InventoryUtils.getPlayerFromInventoryEvent(event);
CustomSmithingTrimRecipe<ItemStack> trimRecipe = (CustomSmithingTrimRecipe<ItemStack>) ceRecipe;
ItemStack result = trimRecipe.assemble(input, new ItemBuildContext(this.plugin.adapt(player), ContextHolder.EMPTY));
ItemStack result = smithingTrimRecipe.assemble(getSmithingInput(inventory), new ItemBuildContext(this.plugin.adapt(player), ContextHolder.EMPTY));
event.setResult(result);
if (!ceRecipe.id().equals(recipeId)) {
correctSmithingRecipeUsed(inventory, ceRecipe);
}
}
@EventHandler(ignoreCancelled = true)
@@ -912,59 +657,25 @@ public class RecipeEventListener implements Listener {
if (!Config.enableRecipeSystem()) return;
SmithingInventory inventory = event.getInventory();
if (!(inventory.getRecipe() instanceof SmithingTransformRecipe recipe)) return;
Key recipeId = Key.of(recipe.getKey().namespace(), recipe.getKey().value());
boolean isCustom = this.recipeManager.isCustomRecipe(recipeId);
// Maybe it's recipe from other plugins, then we ignore it
if (!isCustom) {
Optional<Recipe<ItemStack>> optionalRecipe = this.recipeManager.recipeById(recipeId);
if (optionalRecipe.isEmpty()) {
return;
}
ItemStack base = inventory.getInputEquipment();
ItemStack template = inventory.getInputTemplate();
ItemStack addition = inventory.getInputMineral();
SmithingInput<ItemStack> input = new SmithingInput<>(
getUniqueIdItem(base),
getUniqueIdItem(template),
getUniqueIdItem(addition)
);
Recipe<ItemStack> ceRecipe = this.recipeManager.recipeByInput(RecipeType.SMITHING, input, recipeId);
if (ceRecipe == null) {
if (!(optionalRecipe.get() instanceof CustomSmithingTransformRecipe<ItemStack> smithingTransformRecipe)) {
event.setResult(null);
return;
}
Player player = InventoryUtils.getPlayerFromInventoryEvent(event);
CustomSmithingTransformRecipe<ItemStack> transformRecipe = (CustomSmithingTransformRecipe<ItemStack>) ceRecipe;
ItemStack processed = transformRecipe.assemble(input, new ItemBuildContext(this.plugin.adapt(player), ContextHolder.EMPTY));
ItemStack processed = smithingTransformRecipe.assemble(getSmithingInput(inventory), new ItemBuildContext(this.plugin.adapt(player), ContextHolder.EMPTY));
event.setResult(processed);
if (!ceRecipe.id().equals(recipeId)) {
correctSmithingRecipeUsed(inventory, ceRecipe);
}
}
private void correctSmithingRecipeUsed(SmithingInventory inventory, Recipe<ItemStack> recipe) {
Object holderOrRecipe = this.recipeManager.nmsRecipeHolderByRecipe(recipe);
if (holderOrRecipe == null) {
return;
}
try {
Object resultInventory = CraftBukkitReflections.field$CraftResultInventory$resultInventory.get(inventory);
CoreReflections.field$ResultContainer$recipeUsed.set(resultInventory, holderOrRecipe);
} catch (ReflectiveOperationException e) {
this.plugin.logger().warn("Failed to correct used recipe", e);
}
}
private UniqueIdItem<ItemStack> getUniqueIdItem(@Nullable ItemStack itemStack) {
if (ItemStackUtils.isEmpty(itemStack)) {
return this.itemManager.uniqueEmptyItem();
} else {
Item<ItemStack> wrappedItem = this.itemManager.wrap(itemStack);
return new UniqueIdItem<>(wrappedItem.recipeIngredientId(), wrappedItem);
}
private SmithingInput<ItemStack> getSmithingInput(SmithingInventory inventory) {
return new SmithingInput<>(
ItemStackUtils.getUniqueIdItem(inventory.getInputEquipment()),
ItemStackUtils.getUniqueIdItem(inventory.getInputTemplate()),
ItemStackUtils.getUniqueIdItem(inventory.getInputMineral())
);
}
}

View File

@@ -60,28 +60,12 @@ public class BukkitPackManager extends AbstractPackManager implements Listener {
@Override
public void unload() {
super.unload();
if (ReloadCommand.RELOAD_PACK_FLAG) {
if (VersionHelper.isOrAbove1_20_2()) {
this.resetServerSettings();
}
}
}
@Override
public void disable() {
super.disable();
HandlerList.unregisterAll(this);
this.resetServerSettings();
}
public void resetServerSettings() {
try {
Object settings = CoreReflections.field$DedicatedServer$settings.get(CoreReflections.method$MinecraftServer$getServer.invoke(null));
Object properties = CoreReflections.field$DedicatedServerSettings$properties.get(settings);
CoreReflections.field$DedicatedServerProperties$serverResourcePackInfo.set(properties, Optional.empty());
} catch (Exception e) {
this.plugin.logger().warn("Failed to reset resource pack settings", e);
}
}
@EventHandler(priority = EventPriority.MONITOR)

View File

@@ -6,32 +6,22 @@ import it.unimi.dsi.fastutil.ints.IntList;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.This;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.item.ComponentTypes;
import net.momirealms.craftengine.bukkit.item.recipe.BukkitRecipeManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRecipeTypes;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRegistries;
import net.momirealms.craftengine.bukkit.util.ItemTags;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.core.item.CustomItem;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.item.data.FireworkExplosion;
import net.momirealms.craftengine.core.item.recipe.CustomCookingRecipe;
import net.momirealms.craftengine.core.item.recipe.RecipeType;
import net.momirealms.craftengine.core.item.recipe.UniqueIdItem;
import net.momirealms.craftengine.core.item.recipe.input.SingleItemInput;
import net.momirealms.craftengine.core.util.*;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -46,43 +36,13 @@ import java.util.function.Function;
import java.util.function.Predicate;
public class RecipeInjector {
private static Class<?> clazz$InjectedCacheChecker;
private static Class<?> clazz$InjectedArmorDyeRecipe;
private static Class<?> clazz$InjectedRepairItemRecipe;
private static Class<?> clazz$InjectedFireworkStarFadeRecipe;
public static void init() {
ByteBuddy byteBuddy = new ByteBuddy(ClassFileVersion.JAVA_V17);
clazz$InjectedCacheChecker = byteBuddy
.subclass(Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING)
.name("net.momirealms.craftengine.bukkit.entity.InjectedCacheChecker")
.implement(CoreReflections.clazz$RecipeManager$CachedCheck)
.implement(InjectedCacheCheck.class)
.defineField("recipeType", Object.class, Visibility.PUBLIC)
.method(ElementMatchers.named("recipeType"))
.intercept(FieldAccessor.ofField("recipeType"))
.defineField("customRecipeType", RecipeType.class, Visibility.PUBLIC)
.method(ElementMatchers.named("customRecipeType"))
.intercept(FieldAccessor.ofField("customRecipeType"))
.defineField("lastRecipe", Object.class, Visibility.PUBLIC)
.method(ElementMatchers.named("lastRecipe"))
.intercept(FieldAccessor.ofField("lastRecipe"))
.defineField("lastCustomRecipe", Key.class, Visibility.PUBLIC)
.method(ElementMatchers.named("lastCustomRecipe"))
.intercept(FieldAccessor.ofField("lastCustomRecipe"))
.method(ElementMatchers.named("getRecipeFor").or(ElementMatchers.named("a")))
.intercept(MethodDelegation.to(
VersionHelper.isOrAbove1_21_2() ?
GetRecipeForMethodInterceptor1_21_2.INSTANCE :
(VersionHelper.isOrAbove1_21() ?
GetRecipeForMethodInterceptor1_21.INSTANCE :
VersionHelper.isOrAbove1_20_5() ?
GetRecipeForMethodInterceptor1_20_5.INSTANCE :
GetRecipeForMethodInterceptor1_20.INSTANCE)
))
.make()
.load(RecipeInjector.class.getClassLoader())
.getLoaded();
ElementMatcher.Junction<MethodDescription> matches = (VersionHelper.isOrAbove1_21() ?
ElementMatchers.takesArguments(CoreReflections.clazz$CraftingInput, CoreReflections.clazz$Level) :
ElementMatchers.takesArguments(CoreReflections.clazz$CraftingContainer, CoreReflections.clazz$Level)
@@ -151,35 +111,6 @@ public class RecipeInjector {
}
}
public static void injectCookingBlockEntity(Object entity) throws ReflectiveOperationException {
if (CoreReflections.clazz$AbstractFurnaceBlockEntity.isInstance(entity)) {
Object quickCheck = CoreReflections.field$AbstractFurnaceBlockEntity$quickCheck.get(entity);
if (clazz$InjectedCacheChecker.isInstance(quickCheck)) return; // already injected
Object recipeType = FastNMS.INSTANCE.field$AbstractFurnaceBlockEntity$recipeType(entity);
InjectedCacheCheck injectedChecker = (InjectedCacheCheck) ReflectionUtils.UNSAFE.allocateInstance(clazz$InjectedCacheChecker);
if (recipeType == MRecipeTypes.SMELTING) {
injectedChecker.customRecipeType(RecipeType.SMELTING);
injectedChecker.recipeType(MRecipeTypes.SMELTING);
} else if (recipeType == MRecipeTypes.BLASTING) {
injectedChecker.customRecipeType(RecipeType.BLASTING);
injectedChecker.recipeType(MRecipeTypes.BLASTING);
} else if (recipeType == MRecipeTypes.SMOKING) {
injectedChecker.customRecipeType(RecipeType.SMOKING);
injectedChecker.recipeType(MRecipeTypes.SMOKING);
} else {
throw new IllegalStateException("RecipeType " + recipeType + " not supported");
}
CoreReflections.field$AbstractFurnaceBlockEntity$quickCheck.set(entity, injectedChecker);
} else if (!VersionHelper.isOrAbove1_21_2() && CoreReflections.clazz$CampfireBlockEntity.isInstance(entity)) {
Object quickCheck = CoreReflections.field$CampfireBlockEntity$quickCheck.get(entity);
if (clazz$InjectedCacheChecker.isInstance(quickCheck)) return; // already injected
InjectedCacheCheck injectedChecker = (InjectedCacheCheck) ReflectionUtils.UNSAFE.allocateInstance(clazz$InjectedCacheChecker);
injectedChecker.customRecipeType(RecipeType.CAMPFIRE_COOKING);
injectedChecker.recipeType(MRecipeTypes.CAMPFIRE_COOKING);
CoreReflections.field$CampfireBlockEntity$quickCheck.set(entity, injectedChecker);
}
}
private static final Function<Object, Integer> INGREDIENT_SIZE_GETTER =
VersionHelper.isOrAbove1_21() ?
FastNMS.INSTANCE::method$CraftingInput$size :
@@ -471,185 +402,4 @@ public class RecipeInjector {
private static boolean isVanillaDyeItem(Item<ItemStack> item) {
return CoreReflections.clazz$DyeItem.isInstance(FastNMS.INSTANCE.method$ItemStack$getItem(item.getLiteralObject()));
}
@SuppressWarnings("DuplicatedCode")
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) {
InjectedCacheCheck injectedCacheCheck = (InjectedCacheCheck) thisObj;
Object lastRecipeResourceLocation = injectedCacheCheck.lastRecipe();
Optional<Pair<Object, Object>> optionalRecipe = FastNMS.INSTANCE.method$RecipeManager$getRecipeFor(BukkitRecipeManager.minecraftRecipeManager(), injectedCacheCheck.recipeType(), args[0], args[1], lastRecipeResourceLocation);
if (optionalRecipe.isEmpty()) {
return Optional.empty();
}
Pair<Object, Object> resourceLocationAndRecipe = optionalRecipe.get();
Object rawRecipeResourceLocation = resourceLocationAndRecipe.getFirst();
Key rawRecipeKey = Key.of(rawRecipeResourceLocation.toString());
BukkitRecipeManager recipeManager = BukkitRecipeManager.instance();
boolean isCustom = recipeManager.isCustomRecipe(rawRecipeKey);
if (!isCustom) {
injectedCacheCheck.lastRecipe(rawRecipeResourceLocation);
return Optional.of(resourceLocationAndRecipe.getSecond());
}
ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(
injectedCacheCheck.recipeType() == MRecipeTypes.CAMPFIRE_COOKING ?
FastNMS.INSTANCE.field$SimpleContainer$items(args[0]).getFirst() :
FastNMS.INSTANCE.field$AbstractFurnaceBlockEntity$getItem(args[0], 0)
);
Item<ItemStack> wrappedItem = BukkitItemManager.instance().wrap(itemStack);
SingleItemInput<ItemStack> input = new SingleItemInput<>(new UniqueIdItem<>(wrappedItem.recipeIngredientId(), wrappedItem));
CustomCookingRecipe<ItemStack> ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.recipeByInput(injectedCacheCheck.customRecipeType(), input, injectedCacheCheck.lastCustomRecipe());
if (ceRecipe == null) {
return Optional.empty();
}
injectedCacheCheck.lastCustomRecipe(ceRecipe.id());
if (!ceRecipe.id().equals(rawRecipeKey)) {
injectedCacheCheck.lastRecipe(KeyUtils.toResourceLocation(ceRecipe.id()));
}
return Optional.ofNullable(recipeManager.nmsRecipeHolderByRecipe(ceRecipe));
}
}
@SuppressWarnings("DuplicatedCode")
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) {
InjectedCacheCheck injectedCacheCheck = (InjectedCacheCheck) thisObj;
Object lastRecipeResourceLocation = injectedCacheCheck.lastRecipe();
Optional<Object> optionalRecipe = (Optional<Object>) FastNMS.INSTANCE.method$RecipeManager$getRecipeFor(BukkitRecipeManager.minecraftRecipeManager(), injectedCacheCheck.recipeType(), args[0], args[1], lastRecipeResourceLocation);
if (optionalRecipe.isEmpty()) {
return Optional.empty();
}
Object rawRecipeHolder = optionalRecipe.get();
Object rawRecipeResourceLocation = FastNMS.INSTANCE.field$RecipeHolder$id(rawRecipeHolder);
Key rawRecipeKey = Key.of(rawRecipeResourceLocation.toString());
BukkitRecipeManager recipeManager = BukkitRecipeManager.instance();
ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(
injectedCacheCheck.recipeType() == MRecipeTypes.CAMPFIRE_COOKING ?
FastNMS.INSTANCE.field$SimpleContainer$items(args[0]).getFirst() :
FastNMS.INSTANCE.field$AbstractFurnaceBlockEntity$getItem(args[0], 0)
);
boolean isCustom = recipeManager.isCustomRecipe(rawRecipeKey);
if (!isCustom) {
injectedCacheCheck.lastRecipe(rawRecipeResourceLocation);
return optionalRecipe;
}
Item<ItemStack> wrappedItem = BukkitItemManager.instance().wrap(itemStack);
SingleItemInput<ItemStack> input = new SingleItemInput<>(new UniqueIdItem<>(wrappedItem.recipeIngredientId(), wrappedItem));
CustomCookingRecipe<ItemStack> ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.recipeByInput(injectedCacheCheck.customRecipeType(), input, injectedCacheCheck.lastCustomRecipe());
if (ceRecipe == null) {
return Optional.empty();
}
injectedCacheCheck.lastCustomRecipe(ceRecipe.id());
if (!ceRecipe.id().equals(rawRecipeKey)) {
injectedCacheCheck.lastRecipe(KeyUtils.toResourceLocation(ceRecipe.id()));
}
return Optional.ofNullable(recipeManager.nmsRecipeHolderByRecipe(ceRecipe));
}
}
@SuppressWarnings("DuplicatedCode")
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) {
InjectedCacheCheck injectedCacheCheck = (InjectedCacheCheck) thisObj;
Object lastRecipeResourceLocation = injectedCacheCheck.lastRecipe();
Optional<Object> optionalRecipe = (Optional<Object>) FastNMS.INSTANCE.method$RecipeManager$getRecipeFor(BukkitRecipeManager.minecraftRecipeManager(), injectedCacheCheck.recipeType(), args[0], args[1], lastRecipeResourceLocation);
if (optionalRecipe.isEmpty()) {
return Optional.empty();
}
Object rawRecipeHolder = optionalRecipe.get();
Object rawRecipeResourceLocation = FastNMS.INSTANCE.field$RecipeHolder$id(rawRecipeHolder);
Key rawRecipeKey = Key.of(rawRecipeResourceLocation.toString());
BukkitRecipeManager recipeManager = BukkitRecipeManager.instance();
boolean isCustom = recipeManager.isCustomRecipe(rawRecipeKey);
if (!isCustom) {
injectedCacheCheck.lastRecipe(rawRecipeResourceLocation);
return optionalRecipe;
}
ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(FastNMS.INSTANCE.field$SingleRecipeInput$item(args[0]));
Item<ItemStack> wrappedItem = BukkitItemManager.instance().wrap(itemStack);
SingleItemInput<ItemStack> input = new SingleItemInput<>(new UniqueIdItem<>(wrappedItem.recipeIngredientId(), wrappedItem));
CustomCookingRecipe<ItemStack> ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.recipeByInput(injectedCacheCheck.customRecipeType(), input, injectedCacheCheck.lastCustomRecipe());
if (ceRecipe == null) {
return Optional.empty();
}
injectedCacheCheck.lastCustomRecipe(ceRecipe.id());
if (!ceRecipe.id().equals(rawRecipeKey)) {
injectedCacheCheck.lastRecipe(KeyUtils.toResourceLocation(ceRecipe.id()));
}
return Optional.ofNullable(recipeManager.nmsRecipeHolderByRecipe(ceRecipe));
}
}
@SuppressWarnings("DuplicatedCode")
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) {
InjectedCacheCheck injectedCacheCheck = (InjectedCacheCheck) thisObj;
Object lastRecipeResourceKey = injectedCacheCheck.lastRecipe();
Optional<Object> optionalRecipe = (Optional<Object>) FastNMS.INSTANCE.method$RecipeManager$getRecipeFor(BukkitRecipeManager.minecraftRecipeManager(), injectedCacheCheck.recipeType(), args[0], args[1], lastRecipeResourceKey);
if (optionalRecipe.isEmpty()) {
return Optional.empty();
}
// 获取配方的基础信息
Object recipeHolder = optionalRecipe.get();
Object rawRecipeResourceKey = FastNMS.INSTANCE.field$RecipeHolder$id(recipeHolder);
Object rawRecipeResourceLocation = FastNMS.INSTANCE.field$ResourceKey$location(rawRecipeResourceKey);
Key rawRecipeKey = Key.of(rawRecipeResourceLocation.toString());
BukkitRecipeManager recipeManager = BukkitRecipeManager.instance();
// 来自其他插件注册的自定义配方
boolean isCustom = recipeManager.isCustomRecipe(rawRecipeKey);
if (!isCustom) {
injectedCacheCheck.lastRecipe(rawRecipeResourceKey);
return optionalRecipe;
}
// 获取唯一内存地址id
ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(FastNMS.INSTANCE.field$SingleRecipeInput$item(args[0]));
Item<ItemStack> wrappedItem = BukkitItemManager.instance().wrap(itemStack);
SingleItemInput<ItemStack> input = new SingleItemInput<>(new UniqueIdItem<>(wrappedItem.recipeIngredientId(), wrappedItem));
CustomCookingRecipe<ItemStack> ceRecipe = (CustomCookingRecipe<ItemStack>) recipeManager.recipeByInput(injectedCacheCheck.customRecipeType(), input, injectedCacheCheck.lastCustomRecipe());
// 这个ce配方并不存在那么应该返回空
if (ceRecipe == null) {
return Optional.empty();
}
// 记录上一次使用的配方(ce)
injectedCacheCheck.lastCustomRecipe(ceRecipe.id());
// 更新上一次使用的配方(nms)
if (!ceRecipe.id().equals(rawRecipeKey)) {
injectedCacheCheck.lastRecipe(FastNMS.INSTANCE.method$ResourceKey$create(MRegistries.RECIPE, KeyUtils.toResourceLocation(ceRecipe.id())));
}
return Optional.ofNullable(recipeManager.nmsRecipeHolderByRecipe(ceRecipe));
}
}
}

View File

@@ -128,7 +128,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
this.plugin.javaPlugin().getServer().getMessenger().registerOutgoingPluginChannel(this.plugin.javaPlugin(), MOD_CHANNEL);
// Inject server channel
try {
Object server = CoreReflections.method$MinecraftServer$getServer.invoke(null);
Object server = FastNMS.INSTANCE.method$MinecraftServer$getServer();
Object serverConnection = CoreReflections.field$MinecraftServer$connection.get(server);
@SuppressWarnings("unchecked")
List<ChannelFuture> channels = (List<ChannelFuture>) CoreReflections.field$ServerConnectionListener$channels.get(serverConnection);

View File

@@ -2849,10 +2849,6 @@ public final class CoreReflections {
ReflectionUtils.getClazz(BukkitReflectionUtils.assembleMCClass("server.MinecraftServer"))
);
public static final Method method$MinecraftServer$getServer = requireNonNull(
ReflectionUtils.getMethod(clazz$MinecraftServer, new String[] { "getServer" })
);
public static final Field field$MinecraftServer$registries = requireNonNull(
ReflectionUtils.getDeclaredField(clazz$MinecraftServer, clazz$LayeredRegistryAccess, 0)
);

View File

@@ -93,8 +93,6 @@ public class BukkitServerPlayer extends Player {
// for client visual sync
private int resentSoundTick;
private int resentSwingTick;
// cache used recipe
private Key lastUsedRecipe = null;
// has fabric client mod or not
private boolean hasClientMod = false;
// cache if player can break blocks
@@ -885,14 +883,6 @@ public class BukkitServerPlayer extends Player {
return resentSwingTick == gameTicks();
}
public Key lastUsedRecipe() {
return lastUsedRecipe;
}
public void setLastUsedRecipe(Key lastUsedRecipe) {
this.lastUsedRecipe = lastUsedRecipe;
}
public boolean clientModEnabled() {
return this.hasClientMod;
}

View File

@@ -129,15 +129,11 @@ public class InteractUtils {
});
registerInteraction(BlockKeys.SOUL_CAMPFIRE, (player, item, blockState, result) -> {
if (!Config.enableRecipeSystem()) return false;
return BukkitRecipeManager.instance().recipeByInput(RecipeType.CAMPFIRE_COOKING, new SingleItemInput<>(new UniqueIdItem<>(
item.recipeIngredientId(), item
))) != null;
return BukkitRecipeManager.instance().recipeByInput(RecipeType.CAMPFIRE_COOKING, new SingleItemInput<>(UniqueIdItem.of(item))) != null;
});
registerInteraction(BlockKeys.CAMPFIRE, (player, item, blockState, result) -> {
if (!Config.enableRecipeSystem()) return false;
return BukkitRecipeManager.instance().recipeByInput(RecipeType.CAMPFIRE_COOKING, new SingleItemInput<>(new UniqueIdItem<>(
item.recipeIngredientId(), item
))) != null;
return BukkitRecipeManager.instance().recipeByInput(RecipeType.CAMPFIRE_COOKING, new SingleItemInput<>(UniqueIdItem.of(item))) != null;
});
registerInteraction(BlockKeys.CHISELED_BOOKSHELF, (player, item, blockState, result) -> {
if (!(blockState instanceof ChiseledBookshelf chiseledBookshelf)) return false;

View File

@@ -3,11 +3,14 @@ package net.momirealms.craftengine.bukkit.util;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.bukkit.CraftBukkitReflections;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.recipe.UniqueIdItem;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
public class ItemStackUtils {
public final class ItemStackUtils {
private ItemStackUtils() {}
@@ -43,4 +46,9 @@ public class ItemStackUtils {
return FastNMS.INSTANCE.method$CraftItemStack$asCraftCopy(itemStack);
}
}
public static UniqueIdItem<ItemStack> getUniqueIdItem(@Nullable ItemStack itemStack) {
Item<ItemStack> wrappedItem = BukkitItemManager.instance().wrap(itemStack);
return UniqueIdItem.of(wrappedItem);
}
}

View File

@@ -1,50 +1,7 @@
package net.momirealms.craftengine.bukkit.util;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.VersionHelper;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class RecipeUtils {
private RecipeUtils() {}
@SuppressWarnings("unchecked")
public static List<Object> getIngredientsFromShapedRecipe(Object recipe) {
List<Object> ingredients = new ArrayList<>();
try {
if (VersionHelper.isOrAbove1_20_3()) {
Object pattern = CoreReflections.field$1_20_3$ShapedRecipe$pattern.get(recipe);
if (VersionHelper.isOrAbove1_21_2()) {
List<Optional<Object>> optionals = (List<Optional<Object>>) CoreReflections.field$ShapedRecipePattern$ingredients1_21_2.get(pattern);
for (Optional<Object> optional : optionals) {
optional.ifPresent(ingredients::add);
}
} else {
List<Object> objectList = (List<Object>) CoreReflections.field$ShapedRecipePattern$ingredients1_20_3.get(pattern);
for (Object object : objectList) {
Object[] values = (Object[]) CoreReflections.field$Ingredient$values.get(object);
// is empty or not
if (values.length != 0) {
ingredients.add(object);
}
}
}
} else {
List<Object> objectList = (List<Object>) CoreReflections.field$1_20_1$ShapedRecipe$recipeItems.get(recipe);
for (Object object : objectList) {
Object[] values = (Object[]) CoreReflections.field$Ingredient$values.get(object);
if (values.length != 0) {
ingredients.add(object);
}
}
}
} catch (ReflectiveOperationException e) {
CraftEngine.instance().logger().warn("Failed to get ingredients from shaped recipe", e);
}
return ingredients;
}
}

View File

@@ -403,13 +403,6 @@ public class BukkitWorldManager implements WorldManager, Listener {
(injected) -> sections[finalI] = injected);
}
}
if (Config.enableRecipeSystem()) {
@SuppressWarnings("unchecked")
Map<Object, Object> blockEntities = (Map<Object, Object>) FastNMS.INSTANCE.field$ChunkAccess$blockEntities(levelChunk);
for (Object blockEntity : blockEntities.values()) {
RecipeInjector.injectCookingBlockEntity(blockEntity);
}
}
} catch (ReflectiveOperationException e) {
this.plugin.logger().warn("Failed to restore chunk at " + chunk.getX() + " " + chunk.getZ(), e);
return;