9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-30 04:19:27 +00:00

Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
Arubik
2025-08-04 17:55:21 -05:00
205 changed files with 5554 additions and 4220 deletions

View File

@@ -129,13 +129,12 @@ public final class BukkitCustomBlock extends AbstractCustomBlock {
// set block side properties
CoreReflections.field$BlockBehaviour$explosionResistance.set(nmsBlock, settings.resistance());
CoreReflections.field$BlockBehaviour$soundType.set(nmsBlock, SoundUtils.toSoundType(settings.sounds()));
// 1.21.2以前要在init cache之前设定 isConditionallyFullOpaque
// init cache
CoreReflections.method$BlockStateBase$initCache.invoke(nmsState);
boolean isConditionallyFullOpaque = canOcclude & useShapeForLightOcclusion;
if (!VersionHelper.isOrAbove1_21_2()) {
CoreReflections.field$BlockStateBase$isConditionallyFullOpaque.set(nmsState, isConditionallyFullOpaque);
}
// init cache
CoreReflections.method$BlockStateBase$initCache.invoke(nmsState);
// modify cache
if (VersionHelper.isOrAbove1_21_2()) {
int blockLight = settings.blockLight() != -1 ? settings.blockLight() : CoreReflections.field$BlockStateBase$lightBlock.getInt(immutableBlockState.vanillaBlockState().handle());

View File

@@ -26,7 +26,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
public static final Key SLAB_BLOCK = Key.from("craftengine:slab_block");
public static final Key STAIRS_BLOCK = Key.from("craftengine:stairs_block");
public static final Key PRESSURE_PLATE_BLOCK = Key.from("craftengine:pressure_plate_block");
public static final Key DOUBLE_BLOCK = Key.from("craftengine:double_block");
public static final Key DOUBLE_HIGH_BLOCK = Key.from("craftengine:double_high_block");
public static void init() {
register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE);
@@ -51,6 +51,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
register(SLAB_BLOCK, SlabBlockBehavior.FACTORY);
register(STAIRS_BLOCK, StairsBlockBehavior.FACTORY);
register(PRESSURE_PLATE_BLOCK, PressurePlateBlockBehavior.FACTORY);
register(DOUBLE_BLOCK, DoubleBlockBehavior.FACTORY);
register(DOUBLE_HIGH_BLOCK, DoubleHighBlockBehavior.FACTORY);
}
}

View File

@@ -1,6 +1,5 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
@@ -20,12 +19,11 @@ import net.momirealms.craftengine.core.world.*;
import java.util.Map;
import java.util.concurrent.Callable;
public class DoubleBlockBehavior extends BukkitBlockBehavior {
public class DoubleHighBlockBehavior extends BukkitBlockBehavior {
public static final Factory FACTORY = new Factory();
private final Property<DoubleBlockHalf> halfProperty;
public DoubleBlockBehavior(CustomBlock customBlock, Property<DoubleBlockHalf> halfProperty) {
public DoubleHighBlockBehavior(CustomBlock customBlock, Property<DoubleBlockHalf> halfProperty) {
super(customBlock);
this.halfProperty = halfProperty;
}
@@ -83,8 +81,8 @@ public class DoubleBlockBehavior extends BukkitBlockBehavior {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
Property<DoubleBlockHalf> half = (Property<DoubleBlockHalf>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("half"), "warning.config.block.behavior.double.missing_half");
return new DoubleBlockBehavior(block, half);
Property<DoubleBlockHalf> half = (Property<DoubleBlockHalf>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("half"), "warning.config.block.behavior.double_high.missing_half");
return new DoubleHighBlockBehavior(block, half);
}
}
}

View File

@@ -13,6 +13,7 @@ import net.momirealms.craftengine.core.entity.furniture.Furniture;
import net.momirealms.craftengine.core.entity.furniture.FurnitureElement;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.data.FireworkExplosion;
import net.momirealms.craftengine.core.util.Color;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.world.WorldPosition;
import org.bukkit.Material;
@@ -65,7 +66,7 @@ public class BukkitFurnitureElement extends AbstractFurnitureElement {
}
}
private synchronized List<Object> getCachedValues(@Nullable Integer color, int @Nullable [] colors) {
private synchronized List<Object> getCachedValues(@Nullable Color color, int @Nullable [] colors) {
List<Object> cachedValues = new ArrayList<>(this.commonValues);
Item<ItemStack> item = BukkitItemManager.instance().createWrappedItem(item(), null);
if (item == null) {

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

@@ -12,15 +12,12 @@ import net.momirealms.craftengine.bukkit.item.listener.DebugStickListener;
import net.momirealms.craftengine.bukkit.item.listener.ItemEventListener;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBuiltInRegistries;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRegistries;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRegistryOps;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.*;
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;
@@ -73,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;
}
@@ -161,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
@@ -359,31 +375,18 @@ 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));
Object clientBoundItem = materialId == clientBoundMaterialId ? item : FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.ITEM, KeyUtils.toResourceLocation(clientBoundMaterialId));
if (item == null) {
if (item == MItems.AIR) {
throw new LocalizedResourceConfigException("warning.config.item.invalid_material", materialId.toString());
}
if (clientBoundItem == null) {
if (clientBoundItem == MItems.AIR) {
throw new LocalizedResourceConfigException("warning.config.item.invalid_material", clientBoundMaterialId.toString());
}
return BukkitCustomItem.builder(item, clientBoundItem)

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

@@ -7,7 +7,7 @@ import net.momirealms.craftengine.core.item.CustomItem;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemBuildContext;
import net.momirealms.craftengine.core.item.NetworkItemHandler;
import net.momirealms.craftengine.core.item.modifier.ArgumentModifier;
import net.momirealms.craftengine.core.item.modifier.ArgumentsModifier;
import net.momirealms.craftengine.core.item.modifier.ItemDataModifier;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config;
@@ -74,7 +74,7 @@ public final class LegacyNetworkItemHandler implements NetworkItemHandler<ItemSt
return new OtherItem(wrapped, hasDifferentMaterial).process();
} else {
CompoundTag tag = new CompoundTag();
Tag argumentTag = wrapped.getTag(ArgumentModifier.ARGUMENTS_TAG);
Tag argumentTag = wrapped.getTag(ArgumentsModifier.ARGUMENTS_TAG);
ItemBuildContext context;
if (argumentTag instanceof CompoundTag arguments) {
ContextHolder.Builder builder = ContextHolder.builder();

View File

@@ -4,7 +4,7 @@ import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.*;
import net.momirealms.craftengine.core.item.modifier.ArgumentModifier;
import net.momirealms.craftengine.core.item.modifier.ArgumentsModifier;
import net.momirealms.craftengine.core.item.modifier.ItemDataModifier;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config;
@@ -60,6 +60,7 @@ public final class ModernNetworkItemHandler implements NetworkItemHandler<ItemSt
@Override
public Optional<Item<ItemStack>> s2c(Item<ItemStack> wrapped, Player player) {
Item<ItemStack> original = wrapped;
Optional<CustomItem<ItemStack>> optionalCustomItem = wrapped.getCustomItem();
if (optionalCustomItem.isEmpty()) {
if (!Config.interceptItem()) return Optional.empty();
@@ -76,7 +77,7 @@ public final class ModernNetworkItemHandler implements NetworkItemHandler<ItemSt
return new OtherItem(wrapped, hasDifferentMaterial).process();
} else {
CompoundTag customData = Optional.ofNullable(wrapped.getSparrowNBTComponent(ComponentTypes.CUSTOM_DATA)).map(CompoundTag.class::cast).orElse(new CompoundTag());
CompoundTag arguments = customData.getCompound(ArgumentModifier.ARGUMENTS_TAG);
CompoundTag arguments = customData.getCompound(ArgumentsModifier.ARGUMENTS_TAG);
ItemBuildContext context;
if (arguments == null) {
context = ItemBuildContext.of(player);
@@ -89,7 +90,7 @@ public final class ModernNetworkItemHandler implements NetworkItemHandler<ItemSt
}
CompoundTag tag = new CompoundTag();
for (ItemDataModifier<ItemStack> modifier : customItem.clientBoundDataModifiers()) {
modifier.prepareNetworkItem(wrapped, context, tag);
modifier.prepareNetworkItem(original, context, tag);
}
for (ItemDataModifier<ItemStack> modifier : customItem.clientBoundDataModifiers()) {
modifier.apply(wrapped, context);

View File

@@ -47,6 +47,8 @@ import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -125,15 +127,19 @@ public class BlockItemBehavior extends BlockBoundItemBehavior {
// it's just world + pos
BlockState previousState = bukkitBlock.getState();
List<BlockState> revertStates = new ArrayList<>(2);
revertStates.add(previousState);
// place custom block
CraftEngineBlocks.place(placeLocation, blockStateToPlace, UpdateOption.UPDATE_ALL_IMMEDIATE, false);
placeBlock(placeLocation, blockStateToPlace, revertStates);
if (player != null) {
// call bukkit event
BlockPlaceEvent bukkitPlaceEvent = new BlockPlaceEvent(bukkitBlock, previousState, againstBlock, (ItemStack) context.getItem().getItem(), bukkitPlayer, true, context.getHand() == InteractionHand.MAIN_HAND ? EquipmentSlot.HAND : EquipmentSlot.OFF_HAND);
if (EventUtils.fireAndCheckCancel(bukkitPlaceEvent)) {
// revert changes
previousState.update(true, false);
for (BlockState state : revertStates) {
state.update(true, false);
}
return InteractionResult.FAIL;
}
@@ -141,7 +147,9 @@ public class BlockItemBehavior extends BlockBoundItemBehavior {
CustomBlockPlaceEvent customPlaceEvent = new CustomBlockPlaceEvent(bukkitPlayer, placeLocation.clone(), blockStateToPlace, world.getBlockAt(placeLocation), context.getHand());
if (EventUtils.fireAndCheckCancel(customPlaceEvent)) {
// revert changes
previousState.update(true, false);
for (BlockState state : revertStates) {
state.update(true, false);
}
return InteractionResult.FAIL;
}
}
@@ -215,6 +223,10 @@ public class BlockItemBehavior extends BlockBoundItemBehavior {
}
}
protected boolean placeBlock(Location location, ImmutableBlockState blockState, List<BlockState> revertStates) {
return CraftEngineBlocks.place(location, blockState, UpdateOption.UPDATE_ALL_IMMEDIATE, false);
}
@Override
public Key block() {
return this.blockId;

View File

@@ -11,6 +11,7 @@ public class BukkitItemBehaviors extends ItemBehaviors {
public static final Key FLINT_AND_STEEL_ITEM = Key.from("craftengine:flint_and_steel_item");
public static final Key COMPOSTABLE_ITEM = Key.from("craftengine:compostable_item");
public static final Key AXE_ITEM = Key.from("craftengine:axe_item");
public static final Key DOUBLE_HIGH_BLOCK_ITEM = Key.from("craftengine:double_high_block_item");
public static void init() {
register(EMPTY, EmptyItemBehavior.FACTORY);
@@ -20,5 +21,6 @@ public class BukkitItemBehaviors extends ItemBehaviors {
register(FLINT_AND_STEEL_ITEM, FlintAndSteelItemBehavior.FACTORY);
register(COMPOSTABLE_ITEM, CompostableItemBehavior.FACTORY);
register(AXE_ITEM, AxeItemBehavior.FACTORY);
register(DOUBLE_HIGH_BLOCK_ITEM, DoubleHighBlockItemBehavior.FACTORY);
}
}

View File

@@ -0,0 +1,61 @@
package net.momirealms.craftengine.bukkit.item.behavior;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MFluids;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.UpdateOption;
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import org.bukkit.Location;
import org.bukkit.block.BlockState;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
public class DoubleHighBlockItemBehavior extends BlockItemBehavior {
public static final Factory FACTORY = new Factory();
public DoubleHighBlockItemBehavior(Key blockId) {
super(blockId);
}
@Override
protected boolean placeBlock(Location location, ImmutableBlockState blockState, List<BlockState> revertState) {
Object level = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(location.getWorld());
Object blockPos = FastNMS.INSTANCE.constructor$BlockPos(location.getBlockX(), location.getBlockY() + 1, location.getBlockZ());
UpdateOption option = UpdateOption.builder().updateNeighbors().updateClients().updateImmediate().updateKnownShape().build();
Object fluidData = FastNMS.INSTANCE.method$BlockGetter$getFluidState(level, blockPos);
Object stateToPlace = fluidData == MFluids.WATER$defaultState ? MBlocks.WATER$defaultState : MBlocks.AIR$defaultState;
revertState.add(location.getWorld().getBlockAt(location.getBlockX(), location.getBlockY() + 1, location.getBlockZ()).getState());
FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, stateToPlace, option.flags());
return super.placeBlock(location, blockState, revertState);
}
public static class Factory implements ItemBehaviorFactory {
@Override
public ItemBehavior create(Pack pack, Path path, Key key, Map<String, Object> arguments) {
Object id = arguments.get("block");
if (id == null) {
throw new LocalizedResourceConfigException("warning.config.item.behavior.double_high.missing_block", new IllegalArgumentException("Missing required parameter 'block' for double_high_block_item behavior"));
}
if (id instanceof Map<?, ?> map) {
if (map.containsKey(key.toString())) {
// 防呆
BukkitBlockManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map.get(key.toString()), false));
} else {
BukkitBlockManager.instance().parser().parseSection(pack, path, key, MiscUtils.castToMap(map, false));
}
return new DoubleHighBlockItemBehavior(key);
} else {
return new DoubleHighBlockItemBehavior(Key.of(id.toString()));
}
}
}
}

View File

@@ -66,6 +66,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) {
@@ -86,6 +91,9 @@ public abstract class BukkitItemFactory<W extends ItemWrapper<ItemStack>> extend
@Override
protected Key id(W item) {
if (FastNMS.INSTANCE.method$ItemStack$isEmpty(item.getLiteralObject())) {
return ItemKeys.AIR;
}
return customId(item).orElse(vanillaId(item));
}
@@ -96,6 +104,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());
@@ -108,14 +119,10 @@ public abstract class BukkitItemFactory<W extends ItemWrapper<ItemStack>> extend
}
@Override
protected boolean is(W item, Key itemTag) {
protected boolean hasItemTag(W item, Key itemTag) {
Object literalObject = item.getLiteralObject();
Object tag = ItemTags.getOrCreate(itemTag);
try {
return (boolean) CoreReflections.method$ItemStack$isTag.invoke(literalObject, tag);
} catch (ReflectiveOperationException e) {
return false;
}
return FastNMS.INSTANCE.method$ItemStack$is(literalObject, tag);
}
@Override

View File

@@ -10,20 +10,20 @@ import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBuiltInReg
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRegistryOps;
import net.momirealms.craftengine.bukkit.util.EnchantmentUtils;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.core.attribute.AttributeModifier;
import net.momirealms.craftengine.core.item.ComponentKeys;
import net.momirealms.craftengine.core.item.data.Enchantment;
import net.momirealms.craftengine.core.item.data.FireworkExplosion;
import net.momirealms.craftengine.core.item.data.Trim;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.*;
import net.momirealms.sparrow.nbt.CompoundTag;
import net.momirealms.sparrow.nbt.ListTag;
import net.momirealms.sparrow.nbt.Tag;
import org.bukkit.inventory.ItemStack;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.nio.charset.StandardCharsets;
import java.util.*;
public class ComponentItemFactory1_20_5 extends BukkitItemFactory<ComponentItemWrapper> {
@@ -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());
@@ -128,7 +123,7 @@ public class ComponentItemFactory1_20_5 extends BukkitItemFactory<ComponentItemW
valueTag = MRegistryOps.JAVA.convertTo(MRegistryOps.SPARROW_NBT, value);
}
CompoundTag rootTag = (CompoundTag) item.getSparrowNBTComponent(ComponentTypes.CUSTOM_DATA).orElse(new CompoundTag());
CompoundTag rootTag = (CompoundTag) item.getSparrowNBTComponent(ComponentTypes.CUSTOM_DATA).orElseGet(CompoundTag::new);
if (path == null || path.length == 0) {
if (valueTag instanceof CompoundTag) {
@@ -386,23 +381,23 @@ public class ComponentItemFactory1_20_5 extends BukkitItemFactory<ComponentItemW
}
@Override
protected Optional<Integer> dyedColor(ComponentItemWrapper item) {
protected Optional<Color> dyedColor(ComponentItemWrapper item) {
if (!item.hasComponent(ComponentTypes.DYED_COLOR)) return Optional.empty();
Object javaObj = getJavaComponent(item, ComponentTypes.DYED_COLOR);
if (javaObj instanceof Integer integer) {
return Optional.of(integer);
return Optional.of(Color.fromDecimal(integer));
} else if (javaObj instanceof Map<?, ?> map) {
return Optional.of((int) map.get("rgb"));
return Optional.of(Color.fromDecimal((int) map.get("rgb")));
}
return Optional.empty();
}
@Override
protected void dyedColor(ComponentItemWrapper item, Integer color) {
protected void dyedColor(ComponentItemWrapper item, Color color) {
if (color == null) {
item.resetComponent(ComponentTypes.DYED_COLOR);
} else {
item.setJavaComponent(ComponentTypes.DYED_COLOR, color);
item.setJavaComponent(ComponentTypes.DYED_COLOR, color.color());
}
}
@@ -586,4 +581,26 @@ public class ComponentItemFactory1_20_5 extends BukkitItemFactory<ComponentItemW
Object itemStack2 = FastNMS.INSTANCE.method$ItemStack$transmuteCopy(itemStack1, newItem, amount);
return new ComponentItemWrapper(FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(itemStack2));
}
@Override
protected void attributeModifiers(ComponentItemWrapper item, List<AttributeModifier> modifierList) {
CompoundTag compoundTag = (CompoundTag) item.getSparrowNBTComponent(ComponentKeys.ATTRIBUTE_MODIFIERS).orElseGet(CompoundTag::new);
ListTag modifiers = new ListTag();
compoundTag.put("modifiers", modifiers);
for (AttributeModifier modifier : modifierList) {
CompoundTag modifierTag = new CompoundTag();
modifierTag.putString("type", modifier.type());
modifierTag.putString("slot", modifier.slot().name().toLowerCase(Locale.ENGLISH));
if (VersionHelper.isOrAbove1_21()) {
modifierTag.putString("id", modifier.id().toString());
} else {
modifierTag.putIntArray("uuid", UUIDUtils.uuidToIntArray(UUID.nameUUIDFromBytes(modifier.id().toString().getBytes(StandardCharsets.UTF_8))));
modifierTag.putString("name", modifier.id().toString());
}
modifierTag.putDouble("amount", modifier.amount());
modifierTag.putString("operation", modifier.operation().id());
modifiers.add(modifierTag);
}
item.setSparrowNBTComponent(ComponentKeys.ATTRIBUTE_MODIFIERS, compoundTag);
}
}

View File

@@ -5,15 +5,20 @@ import com.google.gson.JsonElement;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.item.ComponentItemWrapper;
import net.momirealms.craftengine.bukkit.item.ComponentTypes;
import net.momirealms.craftengine.core.attribute.AttributeModifier;
import net.momirealms.craftengine.core.item.ComponentKeys;
import net.momirealms.craftengine.core.item.data.JukeboxPlayable;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.AdventureHelper;
import net.momirealms.craftengine.core.util.GsonHelper;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.sparrow.nbt.CompoundTag;
import net.momirealms.sparrow.nbt.ListTag;
import net.momirealms.sparrow.nbt.Tag;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
public class ComponentItemFactory1_21_5 extends ComponentItemFactory1_21_4 {
@@ -123,4 +128,29 @@ public class ComponentItemFactory1_21_5 extends ComponentItemFactory1_21_4 {
protected void jukeboxSong(ComponentItemWrapper item, JukeboxPlayable data) {
item.setJavaComponent(ComponentTypes.JUKEBOX_PLAYABLE, data.song());
}
@Override
protected void attributeModifiers(ComponentItemWrapper item, List<AttributeModifier> modifierList) {
ListTag modifiers = new ListTag();
for (AttributeModifier modifier : modifierList) {
CompoundTag modifierTag = new CompoundTag();
modifierTag.putString("type", modifier.type());
modifierTag.putString("slot", modifier.slot().name().toLowerCase(Locale.ENGLISH));
modifierTag.putString("id", modifier.id().toString());
modifierTag.putDouble("amount", modifier.amount());
modifierTag.putString("operation", modifier.operation().id());
AttributeModifier.Display display = modifier.display();
if (VersionHelper.isOrAbove1_21_6() && display != null) {
CompoundTag displayTag = new CompoundTag();
AttributeModifier.Display.Type displayType = display.type();
displayTag.putString("type", displayType.name().toLowerCase(Locale.ENGLISH));
if (displayType == AttributeModifier.Display.Type.OVERRIDE) {
displayTag.put("value", AdventureHelper.componentToTag(display.value()));
}
modifierTag.put("display", displayTag);
}
modifiers.add(modifierTag);
}
item.setSparrowNBTComponent(ComponentKeys.ATTRIBUTE_MODIFIERS, modifiers);
}
}

View File

@@ -5,13 +5,18 @@ import net.momirealms.craftengine.bukkit.item.LegacyItemWrapper;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBuiltInRegistries;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.core.attribute.AttributeModifier;
import net.momirealms.craftengine.core.item.data.Enchantment;
import net.momirealms.craftengine.core.item.data.FireworkExplosion;
import net.momirealms.craftengine.core.item.data.Trim;
import net.momirealms.craftengine.core.item.modifier.IdModifier;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.Color;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.SkullUtils;
import net.momirealms.craftengine.core.util.UUIDUtils;
import net.momirealms.sparrow.nbt.CompoundTag;
import net.momirealms.sparrow.nbt.ListTag;
import net.momirealms.sparrow.nbt.Tag;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
@@ -62,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);
@@ -169,17 +169,17 @@ public class UniversalItemFactory extends BukkitItemFactory<LegacyItemWrapper> {
}
@Override
protected Optional<Integer> dyedColor(LegacyItemWrapper item) {
protected Optional<Color> dyedColor(LegacyItemWrapper item) {
if (!item.hasTag("display", "color")) return Optional.empty();
return Optional.of(item.getJavaTag("display", "color"));
return Optional.of(Color.fromDecimal(item.getJavaTag("display", "color")));
}
@Override
protected void dyedColor(LegacyItemWrapper item, Integer color) {
protected void dyedColor(LegacyItemWrapper item, Color color) {
if (color == null) {
item.remove("display", "color");
} else {
item.setTag(color, "display", "color");
item.setTag(color.color(), "display", "color");
}
}
@@ -343,4 +343,20 @@ public class UniversalItemFactory extends BukkitItemFactory<LegacyItemWrapper> {
FastNMS.INSTANCE.method$ItemStack$setTag(newItemStack, FastNMS.INSTANCE.method$CompoundTag$copy(FastNMS.INSTANCE.field$ItemStack$getOrCreateTag(item.getLiteralObject())));
return new LegacyItemWrapper(FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(newItemStack));
}
@Override
protected void attributeModifiers(LegacyItemWrapper item, List<AttributeModifier> modifiers) {
ListTag listTag = new ListTag();
for (AttributeModifier modifier : modifiers) {
CompoundTag modifierTag = new CompoundTag();
modifierTag.putString("AttributeName", modifier.type());
modifierTag.putString("Name", modifier.id().toString());
modifierTag.putString("Slot", modifier.slot().name().toLowerCase(Locale.ENGLISH));
modifierTag.putInt("Operation", modifier.operation().ordinal());
modifierTag.putDouble("Amount", modifier.amount());
modifierTag.putIntArray("UUID", UUIDUtils.uuidToIntArray(UUID.nameUUIDFromBytes(modifier.id().toString().getBytes(StandardCharsets.UTF_8))));
listTag.add(modifierTag);
}
item.setTag(listTag, "AttributeModifiers");
}
}

View File

@@ -36,6 +36,7 @@ import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Openable;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
@@ -66,14 +67,22 @@ public class ItemEventListener implements Listener {
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onInteractEntity(PlayerInteractEntityEvent event) {
BukkitServerPlayer serverPlayer = this.plugin.adapt(event.getPlayer());
Player player = event.getPlayer();
Entity entity = event.getRightClicked();
BukkitServerPlayer serverPlayer = this.plugin.adapt(player);
if (serverPlayer == null) return;
InteractionHand hand = event.getHand() == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND;
// prevent duplicated interact air events
serverPlayer.updateLastInteractEntityTick(hand);
Item<ItemStack> itemInHand = serverPlayer.getItemInHand(hand);
if (ItemUtils.isEmpty(itemInHand)) return;
Optional<CustomItem<ItemStack>> optionalCustomItem = itemInHand.getCustomItem();
if (optionalCustomItem.isEmpty()) return;
// 如果目标实体与手中物品可以产生交互,那么忽略
if (InteractUtils.isEntityInteractable(player, entity, itemInHand)) return;
Cancellable cancellable = Cancellable.of(event::isCancelled, event::setCancelled);
PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder()
@@ -263,25 +272,7 @@ public class ItemEventListener implements Listener {
}
}
// execute item right click functions
if (hasCustomItem) {
Cancellable dummy = Cancellable.dummy();
PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder()
.withParameter(DirectContextParameters.BLOCK, new BukkitBlockInWorld(block))
.withOptionalParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, immutableBlockState)
.withOptionalParameter(DirectContextParameters.ITEM_IN_HAND, itemInHand)
.withParameter(DirectContextParameters.POSITION, LocationUtils.toWorldPosition(block.getLocation()))
.withParameter(DirectContextParameters.HAND, hand)
.withParameter(DirectContextParameters.EVENT, dummy)
);
CustomItem<ItemStack> customItem = optionalCustomItem.get();
customItem.execute(context, EventTrigger.RIGHT_CLICK);
if (dummy.isCancelled()) {
event.setCancelled(true);
return;
}
}
// 优先检查物品行为,再执行自定义事件
// 检查其他的物品行为,物品行为理论只在交互时处理
Optional<List<ItemBehavior>> optionalItemBehaviors = itemInHand.getItemBehavior();
// 物品类型是否包含自定义物品行为,行为不一定来自于自定义物品,部分原版物品也包含了新的行为
@@ -309,8 +300,31 @@ public class ItemEventListener implements Listener {
}
}
}
// 执行物品右键事件
if (hasCustomItem) {
// 要求服务端侧这个方块不可交互,或玩家处于潜行状态
if (serverPlayer.isSecondaryUseActive() || !InteractUtils.isInteractable(player, blockData, hitResult, itemInHand)) {
Cancellable dummy = Cancellable.dummy();
PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder()
.withParameter(DirectContextParameters.BLOCK, new BukkitBlockInWorld(block))
.withOptionalParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, immutableBlockState)
.withOptionalParameter(DirectContextParameters.ITEM_IN_HAND, itemInHand)
.withParameter(DirectContextParameters.POSITION, LocationUtils.toWorldPosition(block.getLocation()))
.withParameter(DirectContextParameters.HAND, hand)
.withParameter(DirectContextParameters.EVENT, dummy)
);
CustomItem<ItemStack> customItem = optionalCustomItem.get();
customItem.execute(context, EventTrigger.RIGHT_CLICK);
if (dummy.isCancelled()) {
event.setCancelled(true);
return;
}
}
}
}
// 执行物品左键事件
if (hasCustomItem && action == Action.LEFT_CLICK_BLOCK) {
Cancellable dummy = Cancellable.dummy();
PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder()
@@ -340,11 +354,16 @@ public class ItemEventListener implements Listener {
return;
// Gets the item in hand
InteractionHand hand = event.getHand() == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND;
// prevents duplicated events
if (serverPlayer.lastInteractEntityCheck(hand)) {
return;
}
Item<ItemStack> itemInHand = serverPlayer.getItemInHand(hand);
// should never be null
if (ItemUtils.isEmpty(itemInHand)) return;
// todo 真的需要这个吗
// TODO 有必要存在吗?
if (cancelEventIfHasInteraction(event, serverPlayer, hand)) {
return;
}
@@ -449,7 +468,7 @@ public class ItemEventListener implements Listener {
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void onEntityDamage(EntityDamageEvent event) {
if (event.getEntity() instanceof org.bukkit.entity.Item item) {
Optional.ofNullable(this.plugin.itemManager().wrap(item.getItemStack()))
Optional.of(this.plugin.itemManager().wrap(item.getItemStack()))
.flatMap(Item::getCustomItem)
.ifPresent(it -> {
if (it.settings().invulnerable().contains(DamageCauseUtils.fromBukkit(event.getCause()))) {
@@ -459,6 +478,7 @@ public class ItemEventListener implements Listener {
}
}
// 禁止附魔
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void onEnchant(PrepareItemEnchantEvent event) {
ItemStack itemToEnchant = event.getItem();
@@ -471,6 +491,7 @@ public class ItemEventListener implements Listener {
}
}
// 自定义堆肥改了
@EventHandler(ignoreCancelled = true)
public void onCompost(CompostItemEvent event) {
ItemStack itemToCompost = event.getItem();
@@ -480,6 +501,7 @@ public class ItemEventListener implements Listener {
event.setWillRaiseLevel(RandomUtils.generateRandomFloat(0, 1) < optionalCustomItem.get().settings().compostProbability());
}
// 用于附魔台纠正
@EventHandler(ignoreCancelled = true)
public void onInventoryClick(InventoryClickEvent event) {
if (!(event.getInventory() instanceof EnchantingInventory inventory)) return;

View File

@@ -1,87 +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.RecipeTypes;
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(RecipeTypes.SHAPELESS, input);
if (ceRecipe != null) {
event.setResult(ceRecipe.assemble(input, ItemBuildContext.EMPTY));
return;
}
ceRecipe = this.recipeManager.recipeByInput(RecipeTypes.SHAPED, 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,14 +64,14 @@ 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));
Key recipeType;
SingleItemInput<ItemStack> input = new SingleItemInput<>(ItemStackUtils.getUniqueIdItem(item));
RecipeType recipeType;
if (furnaceInventory.getType() == InventoryType.FURNACE) {
recipeType = RecipeTypes.SMELTING;
recipeType = RecipeType.SMELTING;
} else if (furnaceInventory.getType() == InventoryType.BLAST_FURNACE) {
recipeType = RecipeTypes.BLASTING;
recipeType = RecipeType.BLASTING;
} else {
recipeType = RecipeTypes.SMOKING;
recipeType = RecipeType.SMOKING;
}
Recipe<ItemStack> ceRecipe = this.recipeManager.recipeByInput(recipeType, input);
@@ -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.nmsRecipeManager(),
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(RecipeTypes.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(RecipeTypes.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(RecipeTypes.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));
@@ -481,7 +314,7 @@ public class RecipeEventListener implements Listener {
// 如果禁止在铁砧使用两个相同物品修复
firstCustom.ifPresent(it -> {
if (!it.settings().canRepair()) {
if (it.settings().canRepair() == Tristate.FALSE) {
event.setResult(null);
}
});
@@ -522,7 +355,7 @@ public class RecipeEventListener implements Listener {
Key firstId = wrappedFirst.id();
Optional<CustomItem<ItemStack>> optionalCustomTool = wrappedFirst.getCustomItem();
// 物品无法被修复
if (optionalCustomTool.isPresent() && !optionalCustomTool.get().settings().canRepair()) {
if (optionalCustomTool.isPresent() && optionalCustomTool.get().settings().canRepair() == Tristate.FALSE) {
return;
}
@@ -535,7 +368,7 @@ public class RecipeEventListener implements Listener {
repairItem = item;
break;
}
if (wrappedFirst.is(tag)) {
if (wrappedFirst.hasItemTag(tag)) {
repairItem = item;
break;
}
@@ -679,89 +512,52 @@ public class RecipeEventListener implements Listener {
}
// only handle repair items for the moment
@EventHandler(ignoreCancelled = true)
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void onSpecialRecipe(PrepareItemCraftEvent event) {
// if (!ConfigManager.enableRecipeSystem()) return;
org.bukkit.inventory.Recipe recipe = event.getRecipe();
if (recipe == null)
return;
if (!(recipe instanceof ComplexRecipe complexRecipe))
return;
CraftingInventory inventory = event.getInventory();
ItemStack result = inventory.getResult();
if (ItemStackUtils.isEmpty(result))
return;
boolean hasCustomItem = ItemStackUtils.hasCustomItem(inventory.getMatrix());
if (!hasCustomItem) {
if (!hasCustomItem)
return;
}
if (!CraftBukkitReflections.clazz$CraftComplexRecipe.isInstance(complexRecipe)) {
inventory.setResult(null);
return;
}
try {
Object mcRecipe = CraftBukkitReflections.field$CraftComplexRecipe$recipe.get(complexRecipe);
// Repair recipe
if (CoreReflections.clazz$ArmorDyeRecipe.isInstance(mcRecipe) || CoreReflections.clazz$FireworkStarFadeRecipe.isInstance(mcRecipe)) {
return;
}
// 处理修复配方,在此处理才能使用玩家参数构建物品
if (CoreReflections.clazz$RepairItemRecipe.isInstance(mcRecipe)) {
// repair item
ItemStack[] itemStacks = inventory.getMatrix();
Pair<ItemStack, ItemStack> onlyTwoItems = getTheOnlyTwoItem(itemStacks);
if (onlyTwoItems.left() == null || onlyTwoItems.right() == null) {
inventory.setResult(null);
return;
}
Item<ItemStack> left = plugin.itemManager().wrap(onlyTwoItems.left());
Item<ItemStack> right = plugin.itemManager().wrap(onlyTwoItems.right());
if (!left.id().equals(right.id())) {
inventory.setResult(null);
return;
}
int totalDamage = right.damage().orElse(0) + left.damage().orElse(0);
int totalMaxDamage = left.maxDamage() + right.maxDamage();
// should be impossible, but take care
if (totalDamage >= totalMaxDamage) {
inventory.setResult(null);
return;
}
Player player = InventoryUtils.getPlayerFromInventoryEvent(event);
Optional<CustomItem<ItemStack>> customItemOptional = plugin.itemManager().getCustomItem(left.id());
Pair<ItemStack, ItemStack> theOnlyTwoItem = getTheOnlyTwoItem(inventory.getMatrix());
if (theOnlyTwoItem == null) return;
Item<ItemStack> first = BukkitItemManager.instance().wrap(theOnlyTwoItem.left());
Item<ItemStack> right = BukkitItemManager.instance().wrap(theOnlyTwoItem.right());
int max = Math.max(first.maxDamage(), right.maxDamage());
int durability1 = first.maxDamage() - first.damage().orElse(0);
int durability2 = right.maxDamage() - right.damage().orElse(0);
int finalDurability = durability1 + durability2 + max * 5 / 100;
Optional<CustomItem<ItemStack>> customItemOptional = plugin.itemManager().getCustomItem(first.id());
if (customItemOptional.isEmpty()) {
inventory.setResult(null);
return;
}
CustomItem<ItemStack> customItem = customItemOptional.get();
if (!customItem.settings().canRepair()) {
inventory.setResult(null);
return;
}
Item<ItemStack> newItem = customItem.buildItem(ItemBuildContext.of(plugin.adapt(player)));
int remainingDurability = totalMaxDamage - totalDamage;
int newItemDamage = Math.max(0, newItem.maxDamage() - remainingDurability);
newItem.damage(newItemDamage);
Player player = InventoryUtils.getPlayerFromInventoryEvent(event);
Item<ItemStack> newItem = customItemOptional.get().buildItem(plugin.adapt(player));
newItem.maxDamage(max);
newItem.damage(Math.max(max - finalDurability, 0));
inventory.setResult(newItem.getItem());
} else if (CoreReflections.clazz$ArmorDyeRecipe.isInstance(mcRecipe) || CoreReflections.clazz$FireworkStarFadeRecipe.isInstance(mcRecipe)) {
ItemStack[] itemStacks = inventory.getMatrix();
for (ItemStack itemStack : itemStacks) {
if (itemStack == null) continue;
Item<ItemStack> item = plugin.itemManager().wrap(itemStack);
Optional<CustomItem<ItemStack>> optionalCustomItem = item.getCustomItem();
if (optionalCustomItem.isPresent() && !optionalCustomItem.get().settings().dyeable()) {
inventory.setResult(null);
return;
}
}
} else {
inventory.setResult(null);
return;
}
// 其他配方不允许使用自定义物品
inventory.setResult(null);
} catch (Exception e) {
this.plugin.logger().warn("Failed to handle minecraft custom recipe", e);
this.plugin.logger().warn("Failed to handle custom recipe", e);
}
}
@@ -772,134 +568,54 @@ public class RecipeEventListener implements Listener {
if (itemStack == null) continue;
if (first == null) {
first = itemStack;
} else if (second == null) {
} else {
if (second != null) {
return null;
}
second = itemStack;
}
}
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();
if (recipe == null)
return;
// we only handle shaped and shapeless recipes
boolean shapeless = event.getRecipe() instanceof ShapelessRecipe;
boolean shaped = event.getRecipe() 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();
ItemStack[] ingredients = inventory.getMatrix();
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);
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);
} else if (ingredients.length == 4) {
input = CraftingInput.of(2, 2, uniqueIdItems);
} else {
return;
}
Player player = InventoryUtils.getPlayerFromInventoryEvent(event);
BukkitServerPlayer serverPlayer = this.plugin.adapt(player);
Key lastRecipe = serverPlayer.lastUsedRecipe();
Recipe<ItemStack> ceRecipe = this.recipeManager.recipeByInput(RecipeTypes.SHAPELESS, input, lastRecipe);
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;
}
ceRecipe = this.recipeManager.recipeByInput(RecipeTypes.SHAPED, input, lastRecipe);
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);
}
private void correctCraftingRecipeUsed(CraftingInventory inventory, Recipe<ItemStack> recipe) {
Object holderOrRecipe = this.recipeManager.nmsRecipeHolderByRecipe(recipe);
if (holderOrRecipe == null) {
return;
}
try {
Object resultInventory = CraftBukkitReflections.field$CraftInventoryCrafting$resultInventory.get(inventory);
CoreReflections.field$ResultContainer$recipeUsed.set(resultInventory, holderOrRecipe);
} catch (ReflectiveOperationException e) {
plugin.logger().warn("Failed to correct used recipe", e);
return null;
}
return input;
}
@EventHandler(ignoreCancelled = true)
@@ -923,30 +639,21 @@ 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(RecipeTypes.SMITHING_TRIM, input);
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));
event.setResult(result);
if (!ceRecipe.id().equals(recipeId)) {
correctSmithingRecipeUsed(inventory, ceRecipe);
SmithingInput<ItemStack> input = getSmithingInput(inventory);
if (smithingTrimRecipe.matches(input)) {
Player player = InventoryUtils.getPlayerFromInventoryEvent(event);
ItemStack result = smithingTrimRecipe.assemble(getSmithingInput(inventory), new ItemBuildContext(this.plugin.adapt(player), ContextHolder.EMPTY));
event.setResult(result);
} else {
event.setResult(null);
}
}
@@ -955,59 +662,30 @@ 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(RecipeTypes.SMITHING_TRANSFORM, input);
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));
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();
SmithingInput<ItemStack> input = getSmithingInput(inventory);
if (smithingTransformRecipe.matches(input)) {
Player player = InventoryUtils.getPlayerFromInventoryEvent(event);
ItemStack processed = smithingTransformRecipe.assemble(input, new ItemBuildContext(this.plugin.adapt(player), ContextHolder.EMPTY));
event.setResult(processed);
} else {
Item<ItemStack> wrappedItem = this.itemManager.wrap(itemStack);
return new UniqueIdItem<>(wrappedItem.recipeIngredientId(), wrappedItem);
event.setResult(null);
}
}
private SmithingInput<ItemStack> getSmithingInput(SmithingInventory inventory) {
return new SmithingInput<>(
ItemStackUtils.getUniqueIdItem(inventory.getInputEquipment()),
ItemStackUtils.getUniqueIdItem(inventory.getInputTemplate()),
ItemStackUtils.getUniqueIdItem(inventory.getInputMineral())
);
}
}

View File

@@ -3,7 +3,6 @@ package net.momirealms.craftengine.bukkit.pack;
import net.momirealms.craftengine.bukkit.api.event.AsyncResourcePackGenerateEvent;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.command.feature.ReloadCommand;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.EventUtils;
import net.momirealms.craftengine.bukkit.util.ResourcePackUtils;
@@ -22,7 +21,6 @@ import org.bukkit.event.player.PlayerJoinEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
public class BukkitPackManager extends AbstractPackManager implements Listener {
@@ -60,28 +58,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

@@ -3,6 +3,7 @@ package net.momirealms.craftengine.bukkit.plugin.classpath;
import net.momirealms.craftengine.core.plugin.classpath.ClassPathAppender;
import net.momirealms.craftengine.core.plugin.classpath.URLClassLoaderAccess;
import net.momirealms.craftengine.core.util.ReflectionUtils;
import org.bukkit.Bukkit;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
@@ -17,27 +18,38 @@ public class PaperClassPathAppender implements ClassPathAppender {
public static final Field field$PaperPluginClassLoader$libraryLoader = Optional.ofNullable(clazz$PaperPluginClassLoader)
.map(it -> ReflectionUtils.getDeclaredField(it, URLClassLoader.class, 0))
.orElse(null);
private final URLClassLoaderAccess classLoaderAccess;
private final URLClassLoaderAccess libraryClassLoaderAccess;
// todo 是否有更好的方法让库被其他插件共享
public PaperClassPathAppender(ClassLoader classLoader) {
try {
if (clazz$PaperPluginClassLoader != null && clazz$PaperPluginClassLoader.isInstance(classLoader)) {
URLClassLoader libraryClassLoader = (URLClassLoader) field$PaperPluginClassLoader$libraryLoader.get(classLoader);
this.classLoaderAccess = URLClassLoaderAccess.create(libraryClassLoader);
} else if (classLoader instanceof URLClassLoader) {
this.classLoaderAccess = URLClassLoaderAccess.create((URLClassLoader) classLoader);
} else {
throw new IllegalStateException("ClassLoader is not instance of URLClassLoader");
// 这个类加载器用于加载重定位后的依赖库,这样所有插件都能访问到
ClassLoader bukkitClassLoader = Bukkit.class.getClassLoader();
if (bukkitClassLoader instanceof URLClassLoader urlClassLoader) {
this.libraryClassLoaderAccess = URLClassLoaderAccess.create(urlClassLoader);
} else {
// ignite会把Bukkit放置于EmberClassLoader中获取其父DynamicClassLoader
if (bukkitClassLoader.getClass().getName().equals("space.vectrix.ignite.launch.ember.EmberClassLoader") && bukkitClassLoader.getParent() instanceof URLClassLoader urlClassLoader) {
this.libraryClassLoaderAccess = URLClassLoaderAccess.create(urlClassLoader);
return;
}
try {
// 最次的方案使用paper自带的classloader去加载依赖这种情况会发生依赖隔离
if (clazz$PaperPluginClassLoader != null && clazz$PaperPluginClassLoader.isInstance(classLoader)) {
URLClassLoader libraryClassLoader = (URLClassLoader) field$PaperPluginClassLoader$libraryLoader.get(classLoader);
this.libraryClassLoaderAccess = URLClassLoaderAccess.create(libraryClassLoader);
} else {
throw new IllegalStateException("ClassLoader is not instance of URLClassLoader");
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to instantiate PaperPluginClassLoader", e);
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to instantiate PaperPluginClassLoader", e);
}
}
@Override
public void addJarToClasspath(Path file) {
try {
this.classLoaderAccess.addURL(file.toUri().toURL());
this.libraryClassLoaderAccess.addURL(file.toUri().toURL());
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}

View File

@@ -44,7 +44,7 @@ public class BukkitSenderFactory extends SenderFactory<BukkitCraftEngine, Comman
if (sender instanceof Player player) {
FastNMS.INSTANCE.method$Connection$send(
FastNMS.INSTANCE.field$ServerGamePacketListenerImpl$connection(FastNMS.INSTANCE.field$Player$connection(FastNMS.INSTANCE.method$CraftPlayer$getHandle(player))),
FastNMS.INSTANCE.constructor$ClientboundSystemChatPacket(ComponentUtils.adventureToMinecraft(message), false));
FastNMS.INSTANCE.constructor$ClientboundSystemChatPacket(ComponentUtils.adventureToMinecraft(message), false), null);
} else if (sender instanceof ConsoleCommandSender commandSender) {
commandSender.sendMessage(LegacyComponentSerializer.legacySection().serialize(message));
} else if (sender instanceof RemoteConsoleCommandSender commandSender) {

View File

@@ -1,23 +1,12 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.momirealms.craftengine.bukkit.entity.data.HappyGhastData;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.incendo.cloud.Command;
import org.incendo.cloud.bukkit.parser.location.LocationParser;
import org.incendo.cloud.parser.standard.IntegerParser;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class TestCommand extends BukkitCommandFeature<CommandSender> {
@@ -29,35 +18,9 @@ public class TestCommand extends BukkitCommandFeature<CommandSender> {
public Command.Builder<? extends CommandSender> assembleCommand(org.incendo.cloud.CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.senderType(Player.class)
.required("location", LocationParser.locationParser())
.required("remove", IntegerParser.integerParser())
.handler(context -> {
Player player = context.sender();
int removeEntityId = context.get("remove");
if (removeEntityId >= 0) {
try {
Object packet = NetworkReflections.constructor$ClientboundRemoveEntitiesPacket.newInstance((Object) new int[]{removeEntityId});
plugin().adapt(player).sendPacket(packet, true);
player.sendMessage("发送成功");
} catch (ReflectiveOperationException e) {
player.sendMessage("发送失败");
}
return;
}
Location location = context.get("location");
int entityId = CoreReflections.instance$Entity$ENTITY_COUNTER.incrementAndGet();
List<Object> packets = new ArrayList<>();
List<Object> cachedShulkerValues = new ArrayList<>();
HappyGhastData.MobFlags.addEntityDataIfNotDefaultValue((byte) 0x01, cachedShulkerValues); // NO AI
// HappyGhastData.SharedFlags.addEntityDataIfNotDefaultValue((byte) 0x20, cachedShulkerValues); // Invisible
HappyGhastData.StaysStill.addEntityDataIfNotDefaultValue(true, cachedShulkerValues);
packets.add(FastNMS.INSTANCE.constructor$ClientboundAddEntityPacket(
entityId, UUID.randomUUID(), location.x(), location.y(), location.z(), 0, location.getYaw(),
MEntityTypes.HAPPY_GHAST, 0, CoreReflections.instance$Vec3$Zero, 0
));
packets.add(FastNMS.INSTANCE.constructor$ClientboundSetEntityDataPacket(entityId, List.copyOf(cachedShulkerValues)));
plugin().adapt(player).sendPackets(packets, true);
player.sendMessage("发送成功 id: " + entityId);
player.sendMessage("客户端模组状态: " + BukkitNetworkManager.instance().getUser(player).clientModEnabled());
});
}

View File

@@ -98,7 +98,7 @@ public final class BlockStateGenerator {
if (vec3 == null) return List.of();
Object tool = FastNMS.INSTANCE.method$LootParams$Builder$getOptionalParameter(builder, MLootContextParams.TOOL);
Item<ItemStack> item = BukkitItemManager.instance().wrap(tool == null || FastNMS.INSTANCE.method$ItemStack$isEmpty(tool) ? null : FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(tool));
Item<ItemStack> item = BukkitItemManager.instance().wrap(tool == null ? null : FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(tool));
Object optionalPlayer = FastNMS.INSTANCE.method$LootParams$Builder$getOptionalParameter(builder, MLootContextParams.THIS_ENTITY);
if (!CoreReflections.clazz$Player.isInstance(optionalPlayer)) {
optionalPlayer = null;
@@ -107,7 +107,7 @@ public final class BlockStateGenerator {
// do not drop if it's not the correct tool
BlockSettings settings = state.settings();
if (optionalPlayer != null && settings.requireCorrectTool()) {
if (item == null) return List.of();
if (item.isEmpty()) return List.of();
if (!settings.isCorrectTool(item.id()) &&
(!settings.respectToolComponent() || !FastNMS.INSTANCE.method$ItemStack$isCorrectToolForDrops(tool, state.customBlockState().handle()))) {
return List.of();

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.bukkit.plugin.injector;
import net.momirealms.craftengine.core.item.recipe.RecipeType;
import net.momirealms.craftengine.core.util.Key;
public interface InjectedCacheCheck {
@@ -8,9 +9,9 @@ public interface InjectedCacheCheck {
void recipeType(Object recipeType);
Key customRecipeType();
RecipeType customRecipeType();
void customRecipeType(Key customRecipeType);
void customRecipeType(RecipeType customRecipeType);
Object lastRecipe();

View File

@@ -1,284 +1,405 @@
package net.momirealms.craftengine.bukkit.plugin.injector;
import com.mojang.datafixers.util.Pair;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.description.method.MethodDescription;
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.recipe.BukkitRecipeManager;
import net.momirealms.craftengine.bukkit.item.ComponentTypes;
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.recipe.CustomCookingRecipe;
import net.momirealms.craftengine.core.item.recipe.RecipeTypes;
import net.momirealms.craftengine.core.item.recipe.UniqueIdItem;
import net.momirealms.craftengine.core.item.recipe.input.SingleItemInput;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.ReflectionUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.item.data.FireworkExplosion;
import net.momirealms.craftengine.core.util.*;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
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"))
ElementMatcher.Junction<MethodDescription> matches = (VersionHelper.isOrAbove1_21() ?
ElementMatchers.takesArguments(CoreReflections.clazz$CraftingInput, CoreReflections.clazz$Level) :
ElementMatchers.takesArguments(CoreReflections.clazz$CraftingContainer, CoreReflections.clazz$Level)
).and(ElementMatchers.returns(boolean.class));
ElementMatcher.Junction<MethodDescription> assemble = (
VersionHelper.isOrAbove1_21() ?
ElementMatchers.takesArguments(CoreReflections.clazz$CraftingInput, CoreReflections.clazz$HolderLookup$Provider) :
VersionHelper.isOrAbove1_20_5() ?
ElementMatchers.takesArguments(CoreReflections.clazz$CraftingContainer, CoreReflections.clazz$HolderLookup$Provider) :
ElementMatchers.takesArguments(CoreReflections.clazz$CraftingContainer, CoreReflections.clazz$RegistryAccess)
).and(ElementMatchers.returns(CoreReflections.clazz$ItemStack));
.defineField("customRecipeType", Key.class, Visibility.PUBLIC)
.method(ElementMatchers.named("customRecipeType"))
.intercept(FieldAccessor.ofField("customRecipeType"))
clazz$InjectedArmorDyeRecipe = byteBuddy
.subclass(CoreReflections.clazz$ArmorDyeRecipe, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING)
.name("net.momirealms.craftengine.bukkit.item.recipe.ArmorDyeRecipe")
.method(matches)
.intercept(MethodDelegation.to(DyeMatchesInterceptor.INSTANCE))
.method(assemble)
.intercept(MethodDelegation.to(DyeAssembleInterceptor.INSTANCE))
.make()
.load(RecipeInjector.class.getClassLoader())
.getLoaded();
.defineField("lastRecipe", Object.class, Visibility.PUBLIC)
.method(ElementMatchers.named("lastRecipe"))
.intercept(FieldAccessor.ofField("lastRecipe"))
clazz$InjectedFireworkStarFadeRecipe = byteBuddy
.subclass(CoreReflections.clazz$FireworkStarFadeRecipe)
.name("net.momirealms.craftengine.bukkit.item.recipe.FireworkStarFadeRecipe")
.method(matches)
.intercept(MethodDelegation.to(FireworkStarFadeMatchesInterceptor.INSTANCE))
.method(assemble)
.intercept(MethodDelegation.to(FireworkStarFadeAssembleInterceptor.INSTANCE))
.make()
.load(RecipeInjector.class.getClassLoader())
.getLoaded();
.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)
))
clazz$InjectedRepairItemRecipe = byteBuddy
.subclass(CoreReflections.clazz$RepairItemRecipe, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING)
.name("net.momirealms.craftengine.bukkit.item.recipe.RepairItemRecipe")
// 只修改match逻辑合并需要在事件里处理否则无法应用变量
.method(matches)
.intercept(MethodDelegation.to(RepairMatchesInterceptor.INSTANCE))
.make()
.load(RecipeInjector.class.getClassLoader())
.getLoaded();
}
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(RecipeTypes.SMELTING);
injectedChecker.recipeType(MRecipeTypes.SMELTING);
} else if (recipeType == MRecipeTypes.BLASTING) {
injectedChecker.customRecipeType(RecipeTypes.BLASTING);
injectedChecker.recipeType(MRecipeTypes.BLASTING);
} else if (recipeType == MRecipeTypes.SMOKING) {
injectedChecker.customRecipeType(RecipeTypes.SMOKING);
injectedChecker.recipeType(MRecipeTypes.SMOKING);
public static Object createRepairItemRecipe(Key id) throws ReflectiveOperationException {
return createSpecialRecipe(id, clazz$InjectedRepairItemRecipe);
}
public static Object createCustomDyeRecipe(Key id) throws ReflectiveOperationException {
return createSpecialRecipe(id, clazz$InjectedArmorDyeRecipe);
}
public static Object createFireworkStarFadeRecipe(Key id) throws ReflectiveOperationException {
return createSpecialRecipe(id, clazz$InjectedFireworkStarFadeRecipe);
}
@NotNull
private static Object createSpecialRecipe(Key id, Class<?> clazz$InjectedRepairItemRecipe) throws InstantiationException, IllegalAccessException, java.lang.reflect.InvocationTargetException {
if (VersionHelper.isOrAbove1_20_2()) {
Constructor<?> constructor = ReflectionUtils.getConstructor(clazz$InjectedRepairItemRecipe, CoreReflections.clazz$CraftingBookCategory);
return constructor.newInstance(CoreReflections.instance$CraftingBookCategory$MISC);
} else {
Constructor<?> constructor = ReflectionUtils.getConstructor(clazz$InjectedRepairItemRecipe, CoreReflections.clazz$ResourceLocation, CoreReflections.clazz$CraftingBookCategory);
return constructor.newInstance(KeyUtils.toResourceLocation(id), CoreReflections.instance$CraftingBookCategory$MISC);
}
}
private static final Function<Object, Integer> INGREDIENT_SIZE_GETTER =
VersionHelper.isOrAbove1_21() ?
FastNMS.INSTANCE::method$CraftingInput$size :
FastNMS.INSTANCE::method$Container$getContainerSize;
private static final BiFunction<Object, Integer, Object> INGREDIENT_GETTER =
VersionHelper.isOrAbove1_21() ?
FastNMS.INSTANCE::method$CraftingInput$getItem :
FastNMS.INSTANCE::method$Container$getItem;
private static final Function<Object, Boolean> REPAIR_INGREDIENT_COUNT_CHECKER =
VersionHelper.isOrAbove1_21() ?
(input) -> FastNMS.INSTANCE.method$CraftingInput$ingredientCount(input) != 2 :
(container) -> false;
public static class FireworkStarFadeMatchesInterceptor {
public static final FireworkStarFadeMatchesInterceptor INSTANCE = new FireworkStarFadeMatchesInterceptor();
@RuntimeType
public Object intercept(@AllArguments Object[] args) {
Object input = args[0];
if (DYE_INGREDIENT_COUNT_CHECKER.apply(input)) {
return false;
}
boolean hasDye = false;
boolean hasFireworkStar = false;
int size = INGREDIENT_SIZE_GETTER.apply(input);
for (int i = 0; i < size; i++) {
Object itemStack = INGREDIENT_GETTER.apply(input, i);
if (FastNMS.INSTANCE.method$ItemStack$isEmpty(itemStack)) {
continue;
}
Item<ItemStack> wrapped = BukkitItemManager.instance().wrap(FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(itemStack));
if (isFireworkDye(wrapped)) {
hasDye = true;
} else {
if (!wrapped.id().equals(ItemKeys.FIREWORK_STAR)) {
return false;
}
if (hasFireworkStar) {
return false;
}
hasFireworkStar = true;
}
}
return hasDye && hasFireworkStar;
}
}
public static class FireworkStarFadeAssembleInterceptor {
public static final FireworkStarFadeAssembleInterceptor INSTANCE = new FireworkStarFadeAssembleInterceptor();
@RuntimeType
public Object intercept(@AllArguments Object[] args) {
IntList colors = new IntArrayList();
Item<ItemStack> starItem = null;
Object input = args[0];
int size = INGREDIENT_SIZE_GETTER.apply(input);
for (int i = 0; i < size; i++) {
Object itemStack = INGREDIENT_GETTER.apply(input, i);
if (FastNMS.INSTANCE.method$ItemStack$isEmpty(itemStack)) {
continue;
}
Item<ItemStack> wrapped = BukkitItemManager.instance().wrap(FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(itemStack));
if (isFireworkDye(wrapped)) {
Color color = getFireworkColor(wrapped);
if (color == null) {
return CoreReflections.instance$ItemStack$EMPTY;
}
colors.add(color.color());
} else if (wrapped.id().equals(ItemKeys.FIREWORK_STAR)) {
starItem = wrapped.copyWithCount(1);
}
}
if (starItem == null || colors.isEmpty()) {
return CoreReflections.instance$ItemStack$EMPTY;
}
FireworkExplosion explosion = starItem.fireworkExplosion().orElse(FireworkExplosion.DEFAULT);
starItem.fireworkExplosion(explosion.withFadeColors(colors));
return starItem.getLiteralObject();
}
}
public static class RepairMatchesInterceptor {
public static final RepairMatchesInterceptor INSTANCE = new RepairMatchesInterceptor();
@RuntimeType
public Object intercept(@AllArguments Object[] args) {
Object input = args[0];
if (REPAIR_INGREDIENT_COUNT_CHECKER.apply(input)) {
return false;
}
return getItemsToCombine(input) != null;
}
}
@Nullable
private static Pair<Item<ItemStack>, Item<ItemStack>> getItemsToCombine(Object input) {
Item<ItemStack> item1 = null;
Item<ItemStack> item2 = null;
int size = INGREDIENT_SIZE_GETTER.apply(input);
for (int i = 0; i < size; i++) {
Object itemStack = INGREDIENT_GETTER.apply(input, i);
if (FastNMS.INSTANCE.method$ItemStack$isEmpty(itemStack)) {
continue;
}
Item<ItemStack> wrapped = BukkitItemManager.instance().wrap(FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(itemStack));
if (item1 == null) {
item1 = wrapped;
} else {
throw new IllegalStateException("RecipeType " + recipeType + " not supported");
if (item2 != null) {
return null;
}
item2 = wrapped;
}
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(RecipeTypes.CAMPFIRE_COOKING);
injectedChecker.recipeType(MRecipeTypes.CAMPFIRE_COOKING);
CoreReflections.field$CampfireBlockEntity$quickCheck.set(entity, injectedChecker);
}
if (item1 == null || item2 == null) {
return null;
}
if (!canCombine(item1, item2)) {
return null;
}
return new Pair<>(item1, item2);
}
private static boolean canCombine(Item<ItemStack> input1, Item<ItemStack> input2) {
if (input1.count() != 1 || !isDamageableItem(input1)) return false;
if (input2.count() != 1 || !isDamageableItem(input2)) return false;
if (!input1.id().equals(input2.id())) return false;
Optional<CustomItem<ItemStack>> customItem = input1.getCustomItem();
return customItem.isEmpty() || customItem.get().settings().canRepair() != Tristate.FALSE;
}
private static boolean isDamageableItem(Item<ItemStack> item) {
if (VersionHelper.isOrAbove1_20_5()) {
return item.hasComponent(ComponentTypes.MAX_DAMAGE) && item.hasComponent(ComponentTypes.DAMAGE);
} else {
return FastNMS.INSTANCE.method$Item$canBeDepleted(FastNMS.INSTANCE.method$ItemStack$getItem(item.getLiteralObject()));
}
}
@SuppressWarnings("DuplicatedCode")
public static class GetRecipeForMethodInterceptor1_20 {
public static final GetRecipeForMethodInterceptor1_20 INSTANCE = new GetRecipeForMethodInterceptor1_20();
private static final Function<Object, Boolean> DYE_INGREDIENT_COUNT_CHECKER =
VersionHelper.isOrAbove1_21() ?
(input) -> FastNMS.INSTANCE.method$CraftingInput$ingredientCount(input) < 2 :
(container) -> false;
public static class DyeMatchesInterceptor {
public static final DyeMatchesInterceptor INSTANCE = new DyeMatchesInterceptor();
@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.nmsRecipeManager(), injectedCacheCheck.recipeType(), args[0], args[1], lastRecipeResourceLocation);
if (optionalRecipe.isEmpty()) {
return Optional.empty();
public Object intercept(@AllArguments Object[] args) {
Object input = args[0];
if (DYE_INGREDIENT_COUNT_CHECKER.apply(input)) {
return false;
}
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());
int size = INGREDIENT_SIZE_GETTER.apply(input);
Item<ItemStack> itemToDye = null;
boolean hasDye = false;
for (int i = 0; i < size; i++) {
Object itemStack = INGREDIENT_GETTER.apply(input, i);
if (FastNMS.INSTANCE.method$ItemStack$isEmpty(itemStack)) {
continue;
}
Item<ItemStack> wrapped = BukkitItemManager.instance().wrap(FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(itemStack));
if (isDyeable(wrapped)) {
if (itemToDye != null) {
return false;
}
itemToDye = wrapped;
} else {
if (!isArmorDye(wrapped)) {
return false;
}
hasDye = true;
}
}
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));
return hasDye && itemToDye != null;
}
}
@SuppressWarnings("DuplicatedCode")
public static class GetRecipeForMethodInterceptor1_20_5 {
public static final GetRecipeForMethodInterceptor1_20_5 INSTANCE = new GetRecipeForMethodInterceptor1_20_5();
public static class DyeAssembleInterceptor {
public static final DyeAssembleInterceptor INSTANCE = new DyeAssembleInterceptor();
@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.nmsRecipeManager(), injectedCacheCheck.recipeType(), args[0], args[1], lastRecipeResourceLocation);
if (optionalRecipe.isEmpty()) {
return Optional.empty();
public Object intercept(@AllArguments Object[] args) {
List<Color> colors = new ArrayList<>();
Item<ItemStack> itemToDye = null;
Object input = args[0];
int size = INGREDIENT_SIZE_GETTER.apply(input);
for (int i = 0; i < size; i++) {
Object itemStack = INGREDIENT_GETTER.apply(input, i);
if (FastNMS.INSTANCE.method$ItemStack$isEmpty(itemStack)) {
continue;
}
Item<ItemStack> wrapped = BukkitItemManager.instance().wrap(FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(itemStack));
if (isDyeable(wrapped)) {
itemToDye = wrapped.copyWithCount(1);
} else {
Color dyeColor = getDyeColor(wrapped);
if (dyeColor != null) {
colors.add(dyeColor);
} else {
return CoreReflections.instance$ItemStack$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;
if (itemToDye == null || itemToDye.isEmpty() || colors.isEmpty()) {
return CoreReflections.instance$ItemStack$EMPTY;
}
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));
return itemToDye.applyDyedColors(colors).getLiteralObject();
}
}
@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.nmsRecipeManager(), 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));
@Nullable
private static Color getDyeColor(final Item<ItemStack> dyeItem) {
Optional<CustomItem<ItemStack>> optionalCustomItem = dyeItem.getCustomItem();
if (optionalCustomItem.isPresent()) {
CustomItem<ItemStack> customItem = optionalCustomItem.get();
return Optional.ofNullable(customItem.settings().dyeColor()).orElseGet(() -> getVanillaDyeColor(dyeItem));
}
return getVanillaDyeColor(dyeItem);
}
@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.nmsRecipeManager(), 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));
@Nullable
private static Color getFireworkColor(final Item<ItemStack> dyeItem) {
Optional<CustomItem<ItemStack>> optionalCustomItem = dyeItem.getCustomItem();
if (optionalCustomItem.isPresent()) {
CustomItem<ItemStack> customItem = optionalCustomItem.get();
return Optional.ofNullable(customItem.settings().fireworkColor()).orElseGet(() -> getVanillaFireworkColor(dyeItem));
}
return getVanillaFireworkColor(dyeItem);
}
private static final Predicate<Item<ItemStack>> IS_DYEABLE =
VersionHelper.isOrAbove1_20_5() ?
(item -> item.hasItemTag(ItemTags.DYEABLE)) :
(item -> {
Object itemLike = FastNMS.INSTANCE.method$ItemStack$getItem(item.getLiteralObject());
return CoreReflections.clazz$DyeableLeatherItem.isInstance(itemLike);
});
private static boolean isDyeable(final Item<ItemStack> item) {
Optional<CustomItem<ItemStack>> optionalCustomItem = item.getCustomItem();
if (optionalCustomItem.isPresent()) {
CustomItem<ItemStack> customItem = optionalCustomItem.get();
if (customItem.settings().dyeable() == Tristate.FALSE) {
return false;
}
if (customItem.settings().dyeable() == Tristate.TRUE) {
return true;
}
}
return IS_DYEABLE.test(item);
}
@Nullable
private static Color getVanillaDyeColor(final Item<ItemStack> item) {
Object itemStack = item.getLiteralObject();
Object dyeItem = FastNMS.INSTANCE.method$ItemStack$getItem(itemStack);
if (!CoreReflections.clazz$DyeItem.isInstance(dyeItem)) return null;
return Color.fromDecimal(FastNMS.INSTANCE.method$DyeColor$getTextureDiffuseColor(FastNMS.INSTANCE.method$DyeItem$getDyeColor(dyeItem)));
}
@Nullable
private static Color getVanillaFireworkColor(final Item<ItemStack> item) {
Object itemStack = item.getLiteralObject();
Object dyeItem = FastNMS.INSTANCE.method$ItemStack$getItem(itemStack);
if (!CoreReflections.clazz$DyeItem.isInstance(dyeItem)) return null;
return Color.fromDecimal(FastNMS.INSTANCE.method$DyeColor$getFireworkColor(FastNMS.INSTANCE.method$DyeItem$getDyeColor(dyeItem)));
}
private static boolean isArmorDye(Item<ItemStack> dyeItem) {
Optional<CustomItem<ItemStack>> optionalCustomItem = dyeItem.getCustomItem();
if (optionalCustomItem.isPresent()) {
CustomItem<ItemStack> customItem = optionalCustomItem.get();
return customItem.settings().dyeColor() != null || isVanillaDyeItem(dyeItem);
}
return isVanillaDyeItem(dyeItem);
}
private static boolean isFireworkDye(Item<ItemStack> dyeItem) {
Optional<CustomItem<ItemStack>> optionalCustomItem = dyeItem.getCustomItem();
if (optionalCustomItem.isPresent()) {
CustomItem<ItemStack> customItem = optionalCustomItem.get();
return customItem.settings().fireworkColor() != null || isVanillaDyeItem(dyeItem);
}
return isVanillaDyeItem(dyeItem);
}
private static boolean isVanillaDyeItem(Item<ItemStack> item) {
return CoreReflections.clazz$DyeItem.isInstance(FastNMS.INSTANCE.method$ItemStack$getItem(item.getLiteralObject()));
}
}

View File

@@ -67,10 +67,10 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
C2S_GAME_BYTE_BUFFER_PACKET_HANDLERS[id] = function;
}
private final BiConsumer<ChannelHandler, Object> packetConsumer;
private final BiConsumer<ChannelHandler, List<Object>> packetsConsumer;
private final BiConsumer<Channel, Object> immediatePacketConsumer;
private final BiConsumer<Channel, List<Object>> immediatePacketsConsumer;
private final TriConsumer<ChannelHandler, Object, Object> packetConsumer;
private final TriConsumer<ChannelHandler, List<Object>, Object> packetsConsumer;
private final TriConsumer<Channel, Object, Runnable> immediatePacketConsumer;
private final TriConsumer<Channel, List<Object>, Runnable> immediatePacketsConsumer;
private final BukkitCraftEngine plugin;
private final Map<ChannelPipeline, BukkitServerPlayer> users = new ConcurrentHashMap<>();
@@ -105,21 +105,30 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
this.registerPacketHandlers();
// set up packet senders
this.packetConsumer = FastNMS.INSTANCE::method$Connection$send;
this.packetsConsumer = ((connection, packets) -> {
this.packetsConsumer = ((connection, packets, sendListener) -> {
Object bundle = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets);
this.packetConsumer.accept(connection, bundle);
this.packetConsumer.accept(connection, bundle, sendListener);
});
this.immediatePacketConsumer = ChannelOutboundInvoker::writeAndFlush;
this.immediatePacketsConsumer = (channel, packets) -> {
this.immediatePacketConsumer = (channel, packet, sendListener) -> {
ChannelFuture future = channel.writeAndFlush(packet);
if (sendListener == null) return;
future.addListener((ChannelFutureListener) channelFuture -> {
sendListener.run();
if (!channelFuture.isSuccess()) {
channelFuture.channel().pipeline().fireExceptionCaught(channelFuture.cause());
}
});
};
this.immediatePacketsConsumer = (channel, packets, sendListener) -> {
Object bundle = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets);
this.immediatePacketConsumer.accept(channel, bundle);
this.immediatePacketConsumer.accept(channel, bundle, sendListener);
};
// set up mod channel
this.plugin.javaPlugin().getServer().getMessenger().registerIncomingPluginChannel(this.plugin.javaPlugin(), MOD_CHANNEL, this);
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);
@@ -127,7 +136,8 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
Channel channel = future.channel();
injectServerChannel(channel);
this.injectedChannels.add(channel);
}, (object) -> {});
}, (object) -> {
});
CoreReflections.field$ServerConnectionListener$channels.set(serverConnection, monitor);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to init server connection", e);
@@ -206,7 +216,8 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
this.resetUserArray();
if (VersionHelper.isFolia()) {
player.getScheduler().runAtFixedRate(plugin.javaPlugin(), (t) -> user.tick(),
() -> {}, 1, 1);
() -> {
}, 1, 1);
}
}
}
@@ -305,20 +316,20 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
}
@Override
public void sendPacket(@NotNull NetWorkUser player, Object packet, boolean immediately) {
public void sendPacket(@NotNull NetWorkUser player, Object packet, boolean immediately, Runnable sendListener) {
if (immediately) {
this.immediatePacketConsumer.accept(player.nettyChannel(), packet);
this.immediatePacketConsumer.accept(player.nettyChannel(), packet, sendListener);
} else {
this.packetConsumer.accept(player.connection(), packet);
this.packetConsumer.accept(player.connection(), packet, sendListener != null ? FastNMS.INSTANCE.method$PacketSendListener$thenRun(sendListener) : null);
}
}
@Override
public void sendPackets(@NotNull NetWorkUser player, List<Object> packet, boolean immediately) {
public void sendPackets(@NotNull NetWorkUser player, List<Object> packet, boolean immediately, Runnable sendListener) {
if (immediately) {
this.immediatePacketsConsumer.accept(player.nettyChannel(), packet);
this.immediatePacketsConsumer.accept(player.nettyChannel(), packet, sendListener);
} else {
this.packetsConsumer.accept(player.connection(), packet);
this.packetsConsumer.accept(player.connection(), packet, sendListener != null ? FastNMS.INSTANCE.method$PacketSendListener$thenRun(sendListener) : null);
}
}
@@ -685,7 +696,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
ByteBuf temp = ctx.alloc().buffer();
try {
if (compressor != null) {
callEncode(compressor, ctx, input, temp);
callEncode(compressor, ctx, input, temp);
}
} finally {
input.clear().writeBytes(temp);

View File

@@ -29,6 +29,7 @@ import net.momirealms.craftengine.bukkit.plugin.network.handler.*;
import net.momirealms.craftengine.bukkit.plugin.network.payload.DiscardedPayload;
import net.momirealms.craftengine.bukkit.plugin.network.payload.NetWorkDataTypes;
import net.momirealms.craftengine.bukkit.plugin.network.payload.Payload;
import net.momirealms.craftengine.bukkit.plugin.network.payload.UnknownPayload;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBuiltInRegistries;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
@@ -105,7 +106,7 @@ public class PacketConsumers {
byte yHeadRot = buf.readByte();
int data = buf.readVarInt();
// Falling blocks
int remapped = remap(data);
int remapped = user.clientModEnabled() ? remapMOD(data) : remap(data);
if (remapped != data) {
int xa = buf.readShort();
int ya = buf.readShort();
@@ -428,7 +429,7 @@ public class PacketConsumers {
if (user.clientModEnabled() && !BlockStateUtils.isVanillaBlock(before)) {
return;
}
int state = remap(before);
int state = user.clientModEnabled() ? remapMOD(before) : remap(before);
if (state == before) {
return;
}
@@ -450,7 +451,7 @@ public class PacketConsumers {
BlockPos blockPos = buf.readBlockPos();
int state = buf.readInt();
boolean global = buf.readBoolean();
int newState = remap(state);
int newState = user.clientModEnabled() ? remapMOD(state) : remap(state);
if (newState == state) {
return;
}
@@ -1006,7 +1007,7 @@ public class PacketConsumers {
if (!CoreReflections.clazz$BlockParticleOption.isInstance(option)) return;
Object blockState = FastNMS.INSTANCE.field$BlockParticleOption$blockState(option);
int id = BlockStateUtils.blockStateToId(blockState);
int remapped = remap(id);
int remapped = user.clientModEnabled() ? remapMOD(id) : remap(id);
if (remapped == id) return;
Object type = FastNMS.INSTANCE.method$BlockParticleOption$getType(option);
Object remappedOption = FastNMS.INSTANCE.constructor$BlockParticleOption(type, BlockStateUtils.idToBlockState(remapped));
@@ -1046,7 +1047,7 @@ public class PacketConsumers {
if (!CoreReflections.clazz$BlockParticleOption.isInstance(option)) return;
Object blockState = FastNMS.INSTANCE.field$BlockParticleOption$blockState(option);
int id = BlockStateUtils.blockStateToId(blockState);
int remapped = remap(id);
int remapped = user.clientModEnabled() ? remapMOD(id) : remap(id);
if (remapped == id) return;
Object type = FastNMS.INSTANCE.method$BlockParticleOption$getType(option);
Object remappedOption = FastNMS.INSTANCE.constructor$BlockParticleOption(type, BlockStateUtils.idToBlockState(remapped));
@@ -1086,7 +1087,7 @@ public class PacketConsumers {
if (!CoreReflections.clazz$BlockParticleOption.isInstance(option)) return;
Object blockState = FastNMS.INSTANCE.field$BlockParticleOption$blockState(option);
int id = BlockStateUtils.blockStateToId(blockState);
int remapped = remap(id);
int remapped = user.clientModEnabled() ? remapMOD(id) : remap(id);
if (remapped == id) return;
Object type = FastNMS.INSTANCE.method$BlockParticleOption$getType(option);
Object remappedOption = FastNMS.INSTANCE.constructor$BlockParticleOption(type, BlockStateUtils.idToBlockState(remapped));
@@ -1601,6 +1602,10 @@ public class PacketConsumers {
}
mainThreadTask = () -> {
if (!furniture.isValid()) {
return;
}
FurnitureInteractEvent interactEvent = new FurnitureInteractEvent(serverPlayer.platformPlayer(), furniture, hand, interactionPoint);
if (EventUtils.fireAndCheckCancel(interactEvent)) {
return;
@@ -1622,7 +1627,7 @@ public class PacketConsumers {
}
// 必须从网络包层面处理,否则无法获取交互的具体实体
if (serverPlayer.isSecondaryUseActive() && itemInHand != null) {
if (serverPlayer.isSecondaryUseActive() && !itemInHand.isEmpty()) {
// try placing another furniture above it
AABB hitBox = furniture.aabbByEntityId(entityId);
if (hitBox == null) return;
@@ -1884,34 +1889,38 @@ public class PacketConsumers {
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> CUSTOM_PAYLOAD = (user, event, packet) -> {
try {
if (!VersionHelper.isOrAbove1_20_5()) return;
if (!VersionHelper.isOrAbove1_20_2()) return;
Object payload = NetworkReflections.methodHandle$ServerboundCustomPayloadPacket$payloadGetter.invokeExact(packet);
Payload clientPayload;
if (NetworkReflections.clazz$DiscardedPayload.isInstance(payload)) {
Payload discardedPayload = DiscardedPayload.from(payload);
if (discardedPayload == null || !discardedPayload.channel().equals(NetworkManager.MOD_CHANNEL_KEY))
clientPayload = DiscardedPayload.from(payload);
} else if (!VersionHelper.isOrAbove1_20_5() && NetworkReflections.clazz$UnknownPayload.isInstance(payload)) {
clientPayload = UnknownPayload.from(payload);
} else {
return;
}
if (clientPayload == null || !clientPayload.channel().equals(NetworkManager.MOD_CHANNEL_KEY))
return;
FriendlyByteBuf buf = clientPayload.toBuffer();
NetWorkDataTypes dataType = buf.readEnumConstant(NetWorkDataTypes.class);
if (dataType == NetWorkDataTypes.CLIENT_CUSTOM_BLOCK) {
int clientBlockRegistrySize = dataType.decode(buf);
int serverBlockRegistrySize = RegistryUtils.currentBlockRegistrySize();
if (clientBlockRegistrySize != serverBlockRegistrySize) {
user.kick(Component.translatable(
"disconnect.craftengine.block_registry_mismatch",
TranslationArgument.numeric(clientBlockRegistrySize),
TranslationArgument.numeric(serverBlockRegistrySize)
));
return;
FriendlyByteBuf buf = discardedPayload.toBuffer();
NetWorkDataTypes<?> dataType = NetWorkDataTypes.readType(buf);
if (dataType == NetWorkDataTypes.CLIENT_CUSTOM_BLOCK) {
int clientBlockRegistrySize = dataType.as(Integer.class).decode(buf);
int serverBlockRegistrySize = RegistryUtils.currentBlockRegistrySize();
if (clientBlockRegistrySize != serverBlockRegistrySize) {
user.kick(Component.translatable(
"disconnect.craftengine.block_registry_mismatch",
TranslationArgument.numeric(clientBlockRegistrySize),
TranslationArgument.numeric(serverBlockRegistrySize)
));
return;
}
user.setClientModState(true);
} else if (dataType == NetWorkDataTypes.CANCEL_BLOCK_UPDATE) {
if (!VersionHelper.isOrAbove1_20_2()) return;
if (dataType.as(Boolean.class).decode(buf)) {
FriendlyByteBuf bufPayload = new FriendlyByteBuf(Unpooled.buffer());
dataType.writeType(bufPayload);
dataType.as(Boolean.class).encode(bufPayload, true);
user.sendCustomPayload(NetworkManager.MOD_CHANNEL_KEY, bufPayload.array());
}
}
user.setClientModState(true);
} else if (dataType == NetWorkDataTypes.CANCEL_BLOCK_UPDATE) {
if (dataType.decode(buf)) {
FriendlyByteBuf bufPayload = new FriendlyByteBuf(Unpooled.buffer());
bufPayload.writeEnumConstant(dataType);
dataType.encode(bufPayload, true);
user.sendCustomPayload(NetworkManager.MOD_CHANNEL_KEY, bufPayload.array());
}
}
} catch (Throwable e) {

View File

@@ -1,64 +1,28 @@
package net.momirealms.craftengine.bukkit.plugin.network.payload;
import io.netty.buffer.ByteBuf;
import net.momirealms.craftengine.core.util.FriendlyByteBuf;
public enum NetWorkDataTypes {
CLIENT_CUSTOM_BLOCK(NetWorkCodecs.INTEGER),
CANCEL_BLOCK_UPDATE(NetWorkCodecs.BOOLEAN);
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;
private final NetWorkCodec<?> codec;
public class NetWorkDataTypes<T> {
private static final Map<Integer, NetWorkDataTypes<?>> id2NetWorkDataTypes = new HashMap<>();
public static final NetWorkDataTypes<Integer> CLIENT_CUSTOM_BLOCK =
new NetWorkDataTypes<>(0, FriendlyByteBuf::readInt, FriendlyByteBuf::writeInt);
public static final NetWorkDataTypes<Boolean> CANCEL_BLOCK_UPDATE =
new NetWorkDataTypes<>(1, FriendlyByteBuf::readBoolean, FriendlyByteBuf::writeBoolean);
static {
register(CLIENT_CUSTOM_BLOCK);
register(CANCEL_BLOCK_UPDATE);
NetWorkDataTypes(NetWorkCodec<?> codec) {
this.codec = codec;
}
private static void register(NetWorkDataTypes<?> type) {
id2NetWorkDataTypes.put(type.id, type);
public NetWorkCodec<?> codec() {
return codec;
}
private final int id;
private final Function<FriendlyByteBuf, T> decoder;
private final BiConsumer<FriendlyByteBuf, T> encoder;
public NetWorkDataTypes(int id, Function<FriendlyByteBuf, T> decoder, BiConsumer<FriendlyByteBuf, T> encoder) {
this.id = id;
this.decoder = decoder;
this.encoder = encoder;
@SuppressWarnings("unchecked")
public <V> V decode(ByteBuf buf) {
return (V) codec.decode(buf);
}
public T decode(FriendlyByteBuf buf) {
return decoder.apply(buf);
}
public void encode(FriendlyByteBuf buf, T data) {
encoder.accept(buf, data);
}
public int id() {
return id;
}
public void writeType(FriendlyByteBuf buf) {
buf.writeVarInt(id);
}
public static NetWorkDataTypes<?> readType(FriendlyByteBuf buf) {
int id = buf.readVarInt();
return id2NetWorkDataTypes.get(id);
}
@SuppressWarnings({"unchecked", "unused"})
public <R> NetWorkDataTypes<R> as(Class<R> clazz) {
return (NetWorkDataTypes<R>) this;
@SuppressWarnings("unchecked")
public <V> void encode(ByteBuf buf, V value) {
((NetWorkCodec<V>) codec).encode(buf, value);
}
}

View File

@@ -0,0 +1,28 @@
package net.momirealms.craftengine.bukkit.plugin.network.payload;
import io.netty.buffer.ByteBuf;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.NetworkReflections;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.FriendlyByteBuf;
import net.momirealms.craftengine.core.util.Key;
public record UnknownPayload(Key channel, ByteBuf rawPayload) implements Payload{
public static UnknownPayload from(Object payload) {
try {
Object id = NetworkReflections.field$UnknownPayload$id.get(payload);
ByteBuf data = (ByteBuf) NetworkReflections.field$UnknownPayload$data.get(payload);
Key channel = KeyUtils.resourceLocationToKey(id);
return new UnknownPayload(channel, data);
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to create UnknownPayload", e);
return null;
}
}
@Override
public FriendlyByteBuf toBuffer() {
return new FriendlyByteBuf(rawPayload);
}
}

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)
);
@@ -3746,7 +3742,7 @@ public final class CoreReflections {
.orElse( null);
public static final Field field$ServerCommonPacketListenerImpl$closed = MiscUtils.requireNonNullIf(
ReflectionUtils.getDeclaredField(clazz$ServerCommonPacketListenerImpl, boolean.class, VersionHelper.isOrAbove1_21_6() ? 1 : 2),
ReflectionUtils.getDeclaredField(clazz$ServerCommonPacketListenerImpl, "closed", "n"),
VersionHelper.isOrAbove1_20_5()
);
@@ -3759,11 +3755,14 @@ public final class CoreReflections {
methodHandle$ServerConfigurationPacketListenerImpl$finishCurrentTask =
ReflectionUtils.unreflectMethod(method$ServerConfigurationPacketListenerImpl$finishCurrentTask)
.asType(MethodType.methodType(void.class, Object.class, Object.class));
} else {
methodHandle$ServerConfigurationPacketListenerImpl$finishCurrentTask = null;
}
if (VersionHelper.isOrAbove1_20_5()) {
methodHandle$ServerCommonPacketListenerImpl$closedSetter =
ReflectionUtils.unreflectSetter(field$ServerCommonPacketListenerImpl$closed)
.asType(MethodType.methodType(void.class, Object.class, boolean.class));
} else {
methodHandle$ServerConfigurationPacketListenerImpl$finishCurrentTask = null;
methodHandle$ServerCommonPacketListenerImpl$closedSetter = null;
}
} catch (ReflectiveOperationException e) {
@@ -3896,4 +3895,74 @@ public final class CoreReflections {
throw new ReflectionInitException("Failed to initialize SnowLayerBlock$LAYERS", e);
}
}
public static final Class<?> clazz$DyeItem = requireNonNull(
BukkitReflectionUtils.findReobfOrMojmapClass(
"world.item.ItemDye",
"world.item.DyeItem"
)
);
public static final Method method$Recipe$matches = requireNonNull(
VersionHelper.isOrAbove1_21() ?
ReflectionUtils.getMethod(clazz$Recipe, boolean.class, clazz$RecipeInput, clazz$Level) :
ReflectionUtils.getMethod(clazz$Recipe, boolean.class, clazz$Container, clazz$Level)
);
public static final Method method$Recipe$assemble = requireNonNull(
VersionHelper.isOrAbove1_21() ?
ReflectionUtils.getMethod(clazz$Recipe, clazz$ItemStack, clazz$RecipeInput, clazz$HolderLookup$Provider) :
VersionHelper.isOrAbove1_20_5() ?
ReflectionUtils.getMethod(clazz$Recipe, clazz$ItemStack, clazz$Container, clazz$HolderLookup$Provider) :
ReflectionUtils.getMethod(clazz$Recipe, clazz$ItemStack, clazz$Container, clazz$RegistryAccess)
);
public static final Class<?> clazz$CraftingBookCategory = requireNonNull(
BukkitReflectionUtils.findReobfOrMojmapClass(
"world.item.crafting.CraftingBookCategory",
"world.item.crafting.CraftingBookCategory"
)
);
public static final Method method$CraftingBookCategory$values = requireNonNull(
ReflectionUtils.getStaticMethod(clazz$CraftingBookCategory, clazz$CraftingBookCategory.arrayType())
);
public static final Object instance$CraftingBookCategory$BUILDING;
public static final Object instance$CraftingBookCategory$REDSTONE;
public static final Object instance$CraftingBookCategory$EQUIPMENT;
public static final Object instance$CraftingBookCategory$MISC;
static {
try {
Object[] values = (Object[]) method$CraftingBookCategory$values.invoke(null);
instance$CraftingBookCategory$BUILDING = values[0];
instance$CraftingBookCategory$REDSTONE = values[1];
instance$CraftingBookCategory$EQUIPMENT = values[2];
instance$CraftingBookCategory$MISC = values[3];
} catch (ReflectiveOperationException e) {
throw new ReflectionInitException("Failed to initialize CraftingBookCategory", e);
}
}
public static final Class<?> clazz$CraftingInput = MiscUtils.requireNonNullIf(
BukkitReflectionUtils.findReobfOrMojmapClass(
"world.item.crafting.CraftingInput",
"world.item.crafting.CraftingInput"
), VersionHelper.isOrAbove1_21()
);
public static final Class<?> clazz$CraftingContainer = requireNonNull(
BukkitReflectionUtils.findReobfOrMojmapClass(
"world.inventory.InventoryCrafting",
"world.inventory.CraftingContainer"
)
);
public static final Class<?> clazz$DyeableLeatherItem = MiscUtils.requireNonNullIf(
BukkitReflectionUtils.findReobfOrMojmapClass(
"world.item.IDyeable",
"world.item.DyeableLeatherItem"
), !VersionHelper.isOrAbove1_20_5()
);
}

View File

@@ -18,6 +18,8 @@ public final class MBlocks {
public static final Object SHULKER_BOX;
public static final Object COMPOSTER;
public static final Object SNOW;
public static final Object WATER;
public static final Object WATER$defaultState;
private static Object getById(String id) {
Object rl = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", id);
@@ -37,5 +39,7 @@ public final class MBlocks {
SHULKER_BOX = getById("shulker_box");
COMPOSTER = getById("composter");
SNOW = getById("snow");
WATER = getById("water");
WATER$defaultState = FastNMS.INSTANCE.method$Block$defaultState(WATER);
}
}

View File

@@ -7,6 +7,7 @@ public final class MFluids {
private MFluids() {}
public static final Object WATER;
public static final Object WATER$defaultState;
public static final Object FLOWING_WATER;
public static final Object LAVA;
public static final Object FLOWING_LAVA;
@@ -21,6 +22,7 @@ public final class MFluids {
static {
try {
WATER = getById("water");
WATER$defaultState = CoreReflections.method$Fluid$defaultFluidState.invoke(WATER);
FLOWING_WATER = getById("flowing_water");
LAVA = getById("lava");
FLOWING_LAVA = getById("flowing_lava");

View File

@@ -1,6 +1,9 @@
package net.momirealms.craftengine.bukkit.plugin.reflection.minecraft;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.Nullable;
public final class MItems {
private MItems() {}
@@ -9,11 +12,17 @@ public final class MItems {
public static final Object WATER_BUCKET;
public static final Object BARRIER;
@Nullable
private static Object getById(String id) {
Object rl = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", id);
return FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.ITEM, rl);
}
@Nullable
public static Object getById(Key id) {
return FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.ITEM, KeyUtils.toResourceLocation(id));
}
static {
AIR = getById("air");
WATER_BUCKET = getById("water_bucket");

View File

@@ -1248,7 +1248,9 @@ public final class NetworkReflections {
);
public static final Constructor<?> constructor$ClientboundCustomPayloadPacket = requireNonNull(
ReflectionUtils.getConstructor(clazz$ClientboundCustomPayloadPacket, 0)
VersionHelper.isOrAbove1_20_2()
? ReflectionUtils.getConstructor(clazz$ClientboundCustomPayloadPacket, clazz$CustomPacketPayload)
: ReflectionUtils.getConstructor(clazz$ClientboundCustomPayloadPacket, CoreReflections.clazz$ResourceLocation, CoreReflections.clazz$FriendlyByteBuf)
);
// 1.20.2+
@@ -1621,4 +1623,27 @@ public final class NetworkReflections {
throw new ReflectionInitException("Failed to initialize HashedStack$STREAM_CODEC", e);
}
}
// 1.20.2~1.20.4
public static final Class<?> clazz$UnknownPayload = MiscUtils.requireNonNullIf(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("network.protocol.common.ServerboundCustomPayloadPacket$UnknownPayload")
),
VersionHelper.isOrAbove1_20_2() && !VersionHelper.isOrAbove1_20_5()
);
// 1.20.2~1.20.4
public static final Field field$UnknownPayload$id = Optional.ofNullable(clazz$UnknownPayload)
.map(it -> ReflectionUtils.getDeclaredField(it, CoreReflections.clazz$ResourceLocation, 0))
.orElse(null);
// 1.20.2~1.20.4
public static final Field field$UnknownPayload$data = Optional.ofNullable(clazz$UnknownPayload)
.map(it -> ReflectionUtils.getDeclaredField(it, ByteBuf.class, 0))
.orElse(null);
// 1.20.2~1.20.4
public static final Constructor<?> constructor$UnknownPayload = Optional.ofNullable(clazz$UnknownPayload)
.map(ReflectionUtils::getTheOnlyConstructor)
.orElse(null);
}

View File

@@ -76,6 +76,9 @@ public class BukkitServerPlayer extends Player {
private Key clientSideDimension;
// check main hand/offhand interaction
private int lastSuccessfulInteraction;
// to prevent duplicated events
private int lastInteractEntityWithMainHand;
private int lastInteractEntityWithOffHand;
// re-sync attribute timely to prevent some bugs
private long lastAttributeSyncTime;
// for breaking blocks
@@ -90,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
@@ -234,6 +235,24 @@ public class BukkitServerPlayer extends Player {
return this.lastSuccessfulInteraction;
}
@Override
public void updateLastInteractEntityTick(@NotNull InteractionHand hand) {
if (hand == InteractionHand.MAIN_HAND) {
this.lastInteractEntityWithMainHand = gameTicks();
} else {
this.lastInteractEntityWithOffHand = gameTicks();
}
}
@Override
public boolean lastInteractEntityCheck(@NotNull InteractionHand hand) {
if (hand == InteractionHand.MAIN_HAND) {
return gameTicks() == this.lastInteractEntityWithMainHand;
} else {
return gameTicks() == this.lastInteractEntityWithOffHand;
}
}
@Override
public int gameTicks() {
return this.gameTicks;
@@ -302,17 +321,29 @@ public class BukkitServerPlayer extends Player {
this.plugin.networkManager().sendPacket(this, packet, immediately);
}
@Override
public void sendPacket(Object packet, boolean immediately, Runnable sendListener) {
this.plugin.networkManager().sendPacket(this, packet, immediately, sendListener);
}
@Override
public void sendCustomPayload(Key channel, byte[] data) {
try {
Object channelKey = KeyUtils.toResourceLocation(channel);
Object dataPayload;
if (DiscardedPayload.useNewMethod) {
dataPayload = NetworkReflections.constructor$DiscardedPayload.newInstance(channelKey, data);
Object responsePacket;
if (VersionHelper.isOrAbove1_20_2()) {
Object dataPayload;
if (NetworkReflections.clazz$UnknownPayload != null) {
dataPayload = NetworkReflections.constructor$UnknownPayload.newInstance(channelKey, Unpooled.wrappedBuffer(data));
} else if (DiscardedPayload.useNewMethod) {
dataPayload = NetworkReflections.constructor$DiscardedPayload.newInstance(channelKey, data);
} else {
dataPayload = NetworkReflections.constructor$DiscardedPayload.newInstance(channelKey, Unpooled.wrappedBuffer(data));
}
responsePacket = NetworkReflections.constructor$ClientboundCustomPayloadPacket.newInstance(dataPayload);
} else {
dataPayload = NetworkReflections.constructor$DiscardedPayload.newInstance(channelKey, Unpooled.wrappedBuffer(data));
responsePacket = NetworkReflections.constructor$ClientboundCustomPayloadPacket.newInstance(channelKey, FastNMS.INSTANCE.constructor$FriendlyByteBuf(Unpooled.wrappedBuffer(data)));
}
Object responsePacket = NetworkReflections.constructor$ClientboundCustomPayloadPacket.newInstance(dataPayload);
this.sendPacket(responsePacket, true);
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to send custom payload to " + name(), e);
@@ -324,8 +355,10 @@ public class BukkitServerPlayer extends Player {
try {
Object reason = ComponentUtils.adventureToMinecraft(message);
Object kickPacket = NetworkReflections.constructor$ClientboundDisconnectPacket.newInstance(reason);
this.sendPacket(kickPacket, true);
this.nettyChannel().disconnect();
this.sendPacket(kickPacket, false, () -> FastNMS.INSTANCE.method$Connection$disconnect(this.connection(), reason));
this.nettyChannel().config().setAutoRead(false);
Runnable handleDisconnection = () -> FastNMS.INSTANCE.method$Connection$handleDisconnection(this.connection());
FastNMS.INSTANCE.method$BlockableEventLoop$scheduleOnMain(handleDisconnection);
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to kick " + name(), e);
}
@@ -336,6 +369,11 @@ public class BukkitServerPlayer extends Player {
this.plugin.networkManager().sendPackets(this, packet, immediately);
}
@Override
public void sendPackets(List<Object> packet, boolean immediately, Runnable sendListener) {
this.plugin.networkManager().sendPackets(this, packet, immediately, sendListener);
}
@Override
public void simulatePacket(Object packet) {
this.plugin.networkManager().simulatePacket(this, packet);
@@ -600,7 +638,7 @@ public class BukkitServerPlayer extends Player {
if (canInstabuild() && (itemMaterial == Material.DEBUG_STICK
|| itemMaterial == Material.TRIDENT
|| (VersionHelper.isOrAbove1_20_5() && itemMaterial == MaterialUtils.MACE)
|| item.is(ItemTags.SWORDS))) {
|| item.hasItemTag(ItemTags.SWORDS))) {
return;
}
}
@@ -691,7 +729,8 @@ public class BukkitServerPlayer extends Player {
FastNMS.INSTANCE.method$CraftPlayer$getHandle(player)
)
),
packet
packet,
null
);
}
}
@@ -844,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

@@ -12,6 +12,7 @@ import net.momirealms.craftengine.core.sound.JukeboxSong;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.VersionHelper;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@@ -26,6 +27,37 @@ public class BukkitSoundManager extends AbstractSoundManager {
}
}
@Override
protected void registerSounds(Collection<Key> sounds) {
if (sounds.isEmpty()) return;
Object registry = MBuiltInRegistries.SOUND_EVENT;
try {
CoreReflections.field$MappedRegistry$frozen.set(registry, false);
for (Key soundEventId : sounds) {
Object resourceLocation = KeyUtils.toResourceLocation(soundEventId);
// 检查之前有没有注册过了
Object soundEvent = FastNMS.INSTANCE.method$Registry$getValue(registry, resourceLocation);
// 只有没注册才注册,否则会报错
if (soundEvent == null) {
soundEvent = VersionHelper.isOrAbove1_21_2() ?
CoreReflections.constructor$SoundEvent.newInstance(resourceLocation, Optional.of(0)) :
CoreReflections.constructor$SoundEvent.newInstance(resourceLocation, 0, false);
Object holder = CoreReflections.method$Registry$registerForHolder.invoke(null, registry, resourceLocation, soundEvent);
CoreReflections.method$Holder$Reference$bindValue.invoke(holder, soundEvent);
CoreReflections.field$Holder$Reference$tags.set(holder, Set.of());
int id = FastNMS.INSTANCE.method$Registry$getId(registry, soundEvent);
super.customSoundsInRegistry.put(id, soundEventId);
}
}
} catch (Exception e) {
this.plugin.logger().warn("Failed to register jukebox songs.", e);
} finally {
try {
CoreReflections.field$MappedRegistry$frozen.set(registry, true);
} catch (ReflectiveOperationException ignored) {}
}
}
@Override
protected void registerSongs(Map<Key, JukeboxSong> songs) {
if (songs.isEmpty()) return;
@@ -40,13 +72,12 @@ public class BukkitSoundManager extends AbstractSoundManager {
Object soundId = KeyUtils.toResourceLocation(jukeboxSong.sound());
// 检查之前有没有注册过了
Object song = FastNMS.INSTANCE.method$Registry$getValue(registry, resourceLocation);
Object soundEvent = VersionHelper.isOrAbove1_21_2() ?
CoreReflections.constructor$SoundEvent.newInstance(soundId, Optional.of(jukeboxSong.range())) :
CoreReflections.constructor$SoundEvent.newInstance(soundId, jukeboxSong.range(), false);
Object soundHolder = CoreReflections.method$Holder$direct.invoke(null, soundEvent);
// 只有没注册才注册,否则会报错
if (song == null) {
Object soundEvent = VersionHelper.isOrAbove1_21_2() ?
CoreReflections.constructor$SoundEvent.newInstance(soundId, Optional.of(jukeboxSong.range())) :
CoreReflections.constructor$SoundEvent.newInstance(soundId, jukeboxSong.range(), false);
Object soundHolder = CoreReflections.method$Holder$direct.invoke(null, soundEvent);
song = CoreReflections.constructor$JukeboxSong.newInstance(soundHolder, ComponentUtils.adventureToMinecraft(jukeboxSong.description()), jukeboxSong.lengthInSeconds(), jukeboxSong.comparatorOutput());
Object holder = CoreReflections.method$Registry$registerForHolder.invoke(null, registry, resourceLocation, song);
CoreReflections.method$Holder$Reference$bindValue.invoke(holder, song);

View File

@@ -2,6 +2,8 @@ package net.momirealms.craftengine.bukkit.util;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBuiltInRegistries;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.BlockPos;
import org.bukkit.Location;
@@ -15,7 +17,8 @@ import java.util.function.Consumer;
public class EntityUtils {
private EntityUtils() {}
private EntityUtils() {
}
public static BlockPos getOnPos(Player player) {
try {
@@ -34,4 +37,11 @@ public class EntityUtils {
return LegacyEntityUtils.spawnEntity(world, loc, type, function);
}
}
}
public static Key getEntityType(Entity entity) {
Object nmsEntity = FastNMS.INSTANCE.method$CraftEntity$getHandle(entity);
Object entityType = FastNMS.INSTANCE.method$Entity$getType(nmsEntity);
Object id = FastNMS.INSTANCE.method$Registry$getKey(MBuiltInRegistries.ENTITY_TYPE, entityType);
return KeyUtils.resourceLocationToKey(id);
}
}

View File

@@ -1,10 +1,15 @@
package net.momirealms.craftengine.bukkit.util;
import io.papermc.paper.entity.Shearable;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.item.behavior.BlockItemBehavior;
import net.momirealms.craftengine.bukkit.item.recipe.BukkitRecipeManager;
import net.momirealms.craftengine.core.block.BlockKeys;
import net.momirealms.craftengine.core.entity.EntityTypeKeys;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.item.recipe.RecipeTypes;
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
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.plugin.CraftEngine;
@@ -12,26 +17,35 @@ import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.util.Direction;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.QuadFunction;
import net.momirealms.craftengine.core.util.TriFunction;
import net.momirealms.craftengine.core.world.BlockHitResult;
import net.momirealms.craftengine.core.world.BlockPos;
import org.bukkit.GameMode;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Levelled;
import org.bukkit.block.data.type.Bell;
import org.bukkit.entity.Player;
import org.bukkit.block.data.type.ChiseledBookshelf;
import org.bukkit.block.data.type.RespawnAnchor;
import org.bukkit.entity.*;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class InteractUtils {
private static final Map<Key, QuadFunction<Player, Item<ItemStack>, BlockData, BlockHitResult, Boolean>> INTERACTIONS = new HashMap<>();
private static final Map<Key, QuadFunction<Player, Item<ItemStack>, BlockData, BlockHitResult, Boolean>> WILL_CONSUME = new HashMap<>();
private static final Map<Key, TriFunction<Player, Entity, @Nullable Item<ItemStack>, Boolean>> ENTITY_INTERACTIONS = new HashMap<>();
private static final Key NOTE_BLOCK_TOP_INSTRUMENTS = Key.of("minecraft:noteblock_top_instruments");
private InteractUtils() {}
static {
registerInteraction(BlockKeys.NOTE_BLOCK, (player, item, blockState, result) -> result.getDirection() != Direction.UP || !item.is(NOTE_BLOCK_TOP_INSTRUMENTS));
registerInteraction(BlockKeys.NOTE_BLOCK, (player, item, blockState, result) -> result.getDirection() != Direction.UP || !item.hasItemTag(NOTE_BLOCK_TOP_INSTRUMENTS));
registerInteraction(BlockKeys.CAKE, (player, item, blockState, result) -> !canEat(player, false));
registerInteraction(BlockKeys.CANDLE_CAKE, (player, item, blockState, result) -> !canEat(player, false));
registerInteraction(BlockKeys.WHITE_CANDLE_CAKE, (player, item, blockState, result) -> !canEat(player, false));
@@ -50,6 +64,43 @@ public class InteractUtils {
registerInteraction(BlockKeys.GREEN_CANDLE_CAKE, (player, item, blockState, result) -> !canEat(player, false));
registerInteraction(BlockKeys.RED_CANDLE_CAKE, (player, item, blockState, result) -> !canEat(player, false));
registerInteraction(BlockKeys.BLACK_CANDLE_CAKE, (player, item, blockState, result) -> !canEat(player, false));
registerInteraction(BlockKeys.COMMAND_BLOCK, (player, item, blockState, result) -> player.isOp() && player.getGameMode() == GameMode.CREATIVE);
registerInteraction(BlockKeys.CHAIN_COMMAND_BLOCK, (player, item, blockState, result) -> player.isOp() && player.getGameMode() == GameMode.CREATIVE);
registerInteraction(BlockKeys.REPEATING_COMMAND_BLOCK, (player, item, blockState, result) -> player.isOp() && player.getGameMode() == GameMode.CREATIVE);
registerInteraction(BlockKeys.JIGSAW, (player, item, blockState, result) -> player.isOp() && player.getGameMode() == GameMode.CREATIVE);
registerInteraction(BlockKeys.STRUCTURE_BLOCK, (player, item, blockState, result) -> player.isOp() && player.getGameMode() == GameMode.CREATIVE);
registerInteraction(BlockKeys.TEST_INSTANCE_BLOCK, (player, item, blockState, result) -> player.isOp() && player.getGameMode() == GameMode.CREATIVE);
registerInteraction(BlockKeys.TEST_BLOCK, (player, item, blockState, result) -> player.isOp() && player.getGameMode() == GameMode.CREATIVE);
registerInteraction(BlockKeys.LIGHT, (player, item, blockState, result) -> item.vanillaId().equals(ItemKeys.LIGHT));
registerInteraction(BlockKeys.LODESTONE, (player, item, blockState, result) -> item.vanillaId().equals(ItemKeys.COMPASS));
registerInteraction(BlockKeys.BEE_NEST, (player, item, blockState, result) -> {
Key id = item.vanillaId();
return ItemKeys.SHEARS.equals(id) || ItemKeys.GLASS_BOTTLE.equals(id);
});
registerInteraction(BlockKeys.BEEHIVE, (player, item, blockState, result) -> {
Key id = item.vanillaId();
return ItemKeys.SHEARS.equals(id) || ItemKeys.GLASS_BOTTLE.equals(id);
});
registerInteraction(BlockKeys.POWDER_SNOW, (player, item, blockState, result) -> item.vanillaId().equals(ItemKeys.BUCKET));
registerInteraction(BlockKeys.REDSTONE_ORE, (player, item, blockState, result) -> {
Optional<List<ItemBehavior>> behaviors = item.getItemBehavior();
if (behaviors.isPresent()) {
for (ItemBehavior behavior : behaviors.get()) {
if (behavior instanceof BlockItemBehavior) return false;
}
}
return true;
});
registerInteraction(BlockKeys.DEEPSLATE_REDSTONE_ORE, (player, item, blockState, result) -> {
Optional<List<ItemBehavior>> behaviors = item.getItemBehavior();
if (behaviors.isPresent()) {
for (ItemBehavior behavior : behaviors.get()) {
if (behavior instanceof BlockItemBehavior) return false;
}
}
return true;
});
registerInteraction(BlockKeys.BELL, (player, item, blockState, result) -> {
Direction direction = result.getDirection();
BlockPos pos = result.getBlockPos();
@@ -78,17 +129,26 @@ public class InteractUtils {
});
registerInteraction(BlockKeys.SOUL_CAMPFIRE, (player, item, blockState, result) -> {
if (!Config.enableRecipeSystem()) return false;
return BukkitRecipeManager.instance().recipeByInput(RecipeTypes.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(RecipeTypes.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;
return DirectionUtils.toDirection(chiseledBookshelf.getFacing()) == result.getDirection();
});
registerInteraction(BlockKeys.COMPOSTER, (player, item, blockState, result) -> {
if (item.getItem().getType().isCompostable()) return true;
return blockState instanceof Levelled levelled && levelled.getLevel() == levelled.getMaximumLevel();
});
registerInteraction(BlockKeys.RESPAWN_ANCHOR, (player, item, blockState, result) -> {
if (item.vanillaId().equals(ItemKeys.GLOWSTONE)) return true;
return blockState instanceof RespawnAnchor respawnAnchor && respawnAnchor.getCharges() != 0;
});
registerInteraction(BlockKeys.DECORATED_POT, (player, item, blockState, result) -> true);
registerInteraction(BlockKeys.FLOWER_POT, (player, item, blockState, result) -> true);
registerInteraction(BlockKeys.HOPPER, (player, item, blockState, result) -> true);
registerInteraction(BlockKeys.DISPENSER, (player, item, blockState, result) -> true);
registerInteraction(BlockKeys.DROPPER, (player, item, blockState, result) -> true);
@@ -264,14 +324,129 @@ public class InteractUtils {
registerInteraction(BlockKeys.RED_BED, (player, item, blockState, result) -> true);
registerInteraction(BlockKeys.BLACK_BED, (player, item, blockState, result) -> true);
registerInteraction(BlockKeys.DRAGON_EGG, (player, item, blockState, result) -> true);
registerInteraction(BlockKeys.REPEATING_COMMAND_BLOCK, (player, item, blockState, result) -> true);
registerInteraction(BlockKeys.CHAIN_COMMAND_BLOCK, (player, item, blockState, result) -> true);
registerInteraction(BlockKeys.COMMAND_BLOCK, (player, item, blockState, result) -> true);
}
static {
registerWillConsume(BlockKeys.CACTUS, (player, item, blockState, result) ->
result.getDirection() == Direction.UP && item.id().equals(ItemKeys.CACTUS));
registerWillConsume(BlockKeys.CAULDRON, (player, item, blockState, result) -> {
Key id = item.vanillaId();
return ItemKeys.WATER_BUCKET.equals(id) || ItemKeys.LAVA_BUCKET.equals(id);
});
registerWillConsume(BlockKeys.LAVA_CAULDRON, (player, item, blockState, result) -> {
Key id = item.vanillaId();
return ItemKeys.BUCKET.equals(id) || ItemKeys.LAVA_BUCKET.equals(id) || ItemKeys.WATER_BUCKET.equals(id);
});
registerWillConsume(BlockKeys.WATER_CAULDRON, (player, item, blockState, result) -> {
if (blockState instanceof Levelled levelled && levelled.getLevel() == levelled.getMaximumLevel())
return item.vanillaId().equals(ItemKeys.BUCKET);
Key id = item.vanillaId();
return ItemKeys.GLASS_BOTTLE.equals(id) || ItemKeys.WATER_BUCKET.equals(id) || ItemKeys.LAVA_BUCKET.equals(id);
});
}
static {
registerEntityInteraction(EntityTypeKeys.BEE, (player, entity, item) -> canFeed(entity, item));
registerEntityInteraction(EntityTypeKeys.FOX, (player, entity, item) -> canFeed(entity, item));
registerEntityInteraction(EntityTypeKeys.FROG, (player, entity, item) -> canFeed(entity, item));
registerEntityInteraction(EntityTypeKeys.PANDA, (player, entity, item) -> canFeed(entity, item));
registerEntityInteraction(EntityTypeKeys.HOGLIN, (player, entity, item) -> canFeed(entity, item));
registerEntityInteraction(EntityTypeKeys.OCELOT, (player, entity, item) -> canFeed(entity, item));
registerEntityInteraction(EntityTypeKeys.RABBIT, (player, entity, item) -> canFeed(entity, item));
registerEntityInteraction(EntityTypeKeys.TURTLE, (player, entity, item) -> canFeed(entity, item));
registerEntityInteraction(EntityTypeKeys.CHICKEN, (player, entity, item) -> canFeed(entity, item));
registerEntityInteraction(EntityTypeKeys.SNIFFER, (player, entity, item) -> canFeed(entity, item));
registerEntityInteraction(EntityTypeKeys.AXOLOTL, (player, entity, item) ->
canFeed(entity, item) || (item != null && item.vanillaId().equals(ItemKeys.WATER_BUCKET)));
registerEntityInteraction(EntityTypeKeys.COD, (player, entity, item) ->
item != null && item.vanillaId().equals(ItemKeys.WATER_BUCKET));
registerEntityInteraction(EntityTypeKeys.SALMON, (player, entity, item) ->
item != null && item.vanillaId().equals(ItemKeys.WATER_BUCKET));
registerEntityInteraction(EntityTypeKeys.TROPICAL_FISH, (player, entity, item) ->
item != null && item.vanillaId().equals(ItemKeys.WATER_BUCKET));
registerEntityInteraction(EntityTypeKeys.PUFFERFISH, (player, entity, item) ->
item != null && item.vanillaId().equals(ItemKeys.WATER_BUCKET));
registerEntityInteraction(EntityTypeKeys.TADPOLE, (player, entity, item) ->
item != null && item.vanillaId().equals(ItemKeys.WATER_BUCKET));
registerEntityInteraction(EntityTypeKeys.SNOW_GOLEM, (player, entity, item) ->
shearable(entity, item));
registerEntityInteraction(EntityTypeKeys.SHEEP, (player, entity, item) ->
canFeed(entity, item) || shearable(entity, item));
registerEntityInteraction(EntityTypeKeys.BOGGED, (player, entity, item) ->
canFeed(entity, item) || shearable(entity, item));
registerEntityInteraction(EntityTypeKeys.MOOSHROOM, (player, entity, item) ->
canFeed(entity, item) || shearable(entity, item) || (item != null && (item.vanillaId().equals(ItemKeys.BUCKET) || item.vanillaId().equals(ItemKeys.BOWL))));
registerEntityInteraction(EntityTypeKeys.COW, (player, entity, item) ->
canFeed(entity, item) || (item != null && item.vanillaId().equals(ItemKeys.BUCKET)));
registerEntityInteraction(EntityTypeKeys.GOAT, (player, entity, item) ->
canFeed(entity, item) || (item != null && item.vanillaId().equals(ItemKeys.BUCKET)));
registerEntityInteraction(EntityTypeKeys.CREEPER, (player, entity, item) ->
item != null && item.vanillaId().equals(ItemKeys.FLINT_AND_STEEL));
registerEntityInteraction(EntityTypeKeys.PIGLIN, (player, entity, item) ->
item != null && item.vanillaId().equals(ItemKeys.GOLD_INGOT));
registerEntityInteraction(EntityTypeKeys.ARMADILLO, (player, entity, item) ->
canFeed(entity, item) || (item != null && item.vanillaId().equals(ItemKeys.BRUSH)));
registerEntityInteraction(EntityTypeKeys.ZOMBIE_HORSE, (player, entity, item) ->
entity instanceof Tameable tameable && tameable.isTamed());
registerEntityInteraction(EntityTypeKeys.SKELETON_HORSE, (player, entity, item) ->
entity instanceof Tameable tameable && tameable.isTamed());
registerEntityInteraction(EntityTypeKeys.PIG, (player, entity, item) ->
canFeed(entity, item) || (item != null && item.vanillaId().equals(ItemKeys.SADDLE) && !hasSaddle(player, entity)) || (hasSaddle(player, entity) && !player.isSneaking()));
registerEntityInteraction(EntityTypeKeys.STRIDER, (player, entity, item) ->
canFeed(entity, item) || (item != null && item.vanillaId().equals(ItemKeys.SADDLE) && !hasSaddle(player, entity)) || (hasSaddle(player, entity) && !player.isSneaking()));
registerEntityInteraction(EntityTypeKeys.WOLF, (player, entity, item) -> canFeed(entity, item) || isPetOwner(player, entity));
registerEntityInteraction(EntityTypeKeys.CAT, (player, entity, item) -> canFeed(entity, item) || isPetOwner(player, entity));
registerEntityInteraction(EntityTypeKeys.BOAT, (player, entity, item) -> !player.isSneaking());
registerEntityInteraction(EntityTypeKeys.OAK_BOAT, (player, entity, item) -> !player.isSneaking());
registerEntityInteraction(EntityTypeKeys.SPRUCE_BOAT, (player, entity, item) -> !player.isSneaking());
registerEntityInteraction(EntityTypeKeys.BIRCH_BOAT, (player, entity, item) -> !player.isSneaking());
registerEntityInteraction(EntityTypeKeys.JUNGLE_BOAT, (player, entity, item) -> !player.isSneaking());
registerEntityInteraction(EntityTypeKeys.ACACIA_BOAT, (player, entity, item) -> !player.isSneaking());
registerEntityInteraction(EntityTypeKeys.DARK_OAK_BOAT, (player, entity, item) -> !player.isSneaking());
registerEntityInteraction(EntityTypeKeys.MANGROVE_BOAT, (player, entity, item) -> !player.isSneaking());
registerEntityInteraction(EntityTypeKeys.CHERRY_BOAT, (player, entity, item) -> !player.isSneaking());
registerEntityInteraction(EntityTypeKeys.PALE_OAK_BOAT, (player, entity, item) -> !player.isSneaking());
registerEntityInteraction(EntityTypeKeys.BAMBOO_RAFT, (player, entity, item) -> !player.isSneaking());
registerEntityInteraction(EntityTypeKeys.MINECART, (player, entity, item) -> !player.isSneaking());
registerEntityInteraction(EntityTypeKeys.PARROT, (player, entity, item) -> {
if (item != null && item.hasItemTag(Key.of("parrot_poisonous_food"))) return true;
return canFeed(entity, item) || isPetOwner(player, entity);
});
registerEntityInteraction(EntityTypeKeys.HAPPY_GHAST, (player, entity, item) -> {
if (item != null && item.vanillaId().equals(ItemKeys.HARNESS)) return true;
if (entity instanceof HappyGhast happyGhast && !player.isSneaking()) {
ItemStack bodyItem = happyGhast.getEquipment().getItem(EquipmentSlot.BODY);
return BukkitItemManager.instance().wrap(bodyItem).hasItemTag(Key.of("harnesses"));
}
return canFeed(entity, item);
});
registerEntityInteraction(EntityTypeKeys.ALLAY, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.HORSE, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.DONKEY, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.MULE, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.VILLAGER, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.WANDERING_TRADER, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.LLAMA, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.TRADER_LLAMA, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.CAMEL, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.ITEM_FRAME, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.GLOW_ITEM_FRAME, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.INTERACTION, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.CHEST_BOAT, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.OAK_CHEST_BOAT, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.SPRUCE_CHEST_BOAT, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.BIRCH_CHEST_BOAT, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.JUNGLE_CHEST_BOAT, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.ACACIA_CHEST_BOAT, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.DARK_OAK_CHEST_BOAT, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.MANGROVE_CHEST_BOAT, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.CHERRY_CHEST_BOAT, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.PALE_OAK_CHEST_BOAT, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.BAMBOO_CHEST_RAFT, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.CHEST_MINECART, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.FURNACE_MINECART, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.HOPPER_MINECART, (player, entity, item) -> true);
registerEntityInteraction(EntityTypeKeys.COMMAND_BLOCK_MINECART, (player, entity, item) -> true);
}
private static void registerInteraction(Key key, QuadFunction<org.bukkit.entity.Player, Item<ItemStack>, BlockData, BlockHitResult, Boolean> function) {
@@ -288,6 +463,13 @@ public class InteractUtils {
}
}
private static void registerEntityInteraction(Key key, TriFunction<Player, Entity, @Nullable Item<ItemStack>, Boolean> function) {
var previous = ENTITY_INTERACTIONS.put(key, function);
if (previous != null) {
CraftEngine.instance().logger().warn("Duplicated entity interaction check: " + key);
}
}
public static boolean isInteractable(Player player, BlockData state, BlockHitResult hit, @Nullable Item<ItemStack> item) {
Key blockType = BlockStateUtils.getBlockOwnerIdFromData(state);
if (INTERACTIONS.containsKey(blockType)) {
@@ -297,6 +479,11 @@ public class InteractUtils {
}
}
public static boolean isEntityInteractable(Player player, Entity entity, @Nullable Item<ItemStack> item) {
TriFunction<Player, Entity, Item<ItemStack>, Boolean> func = ENTITY_INTERACTIONS.get(EntityUtils.getEntityType(entity));
return func != null && func.apply(player, entity, item);
}
public static boolean willConsume(Player player, BlockData state, BlockHitResult hit, @Nullable Item<ItemStack> item) {
if (item == null) return false;
Key blockType = BlockStateUtils.getBlockOwnerIdFromData(state);
@@ -306,8 +493,24 @@ public class InteractUtils {
return false;
}
}
private static boolean canEat(Player player, boolean ignoreHunger) {
return ignoreHunger || player.isInvulnerable() || player.getFoodLevel() < 20;
}
private static boolean canFeed(Entity entity, Item<ItemStack> item) {
return entity instanceof Animals && item.hasItemTag(Key.of(EntityUtils.getEntityType(entity).value() + "_food"));
}
private static boolean hasSaddle(Player player, Entity entity) {
return entity instanceof Steerable steerable && steerable.hasSaddle() && !player.isSneaking();
}
private static boolean shearable(Entity entity, Item<ItemStack> item) {
return entity instanceof Shearable shearable && item.vanillaId().equals(ItemKeys.SHEARS) && shearable.readyToBeSheared();
}
private static boolean isPetOwner(Player player, Entity entity) {
return entity instanceof Tameable tameable && tameable.isTamed() && player.getUniqueId().equals(tameable.getOwnerUniqueId());
}
}

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

@@ -12,6 +12,7 @@ public class ItemTags {
public static final Key AXES = Key.of("minecraft:axes");
public static final Key SWORDS = Key.of("minecraft:swords");
public static final Key DYEABLE = Key.of("minecraft:dyeable");
private ItemTags() {}

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

@@ -2,7 +2,6 @@ package net.momirealms.craftengine.bukkit.world;
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.injector.WorldStorageInjector;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
@@ -88,6 +87,11 @@ public class BukkitWorldManager implements WorldManager, Listener {
}
}
@Override
public CEWorld[] getWorlds() {
return this.worldArray;
}
private void resetWorldArray() {
this.worldArray = this.worlds.values().toArray(new CEWorld[0]);
}
@@ -218,7 +222,6 @@ public class BukkitWorldManager implements WorldManager, Listener {
this.lastVisitedUUID = null;
}
this.resetWorldArray();
} finally {
this.worldMapLock.writeLock().unlock();
}
@@ -328,80 +331,75 @@ public class BukkitWorldManager implements WorldManager, Listener {
Object chunkSource = FastNMS.INSTANCE.method$ServerLevel$getChunkSource(worldServer);
Object levelChunk = FastNMS.INSTANCE.method$ServerChunkCache$getChunkAtIfLoadedMainThread(chunkSource, chunk.getX(), chunk.getZ());
Object[] sections = FastNMS.INSTANCE.method$ChunkAccess$getSections(levelChunk);
for (int i = 0; i < ceSections.length; i++) {
CESection ceSection = ceSections[i];
Object section = sections[i];
if (Config.syncCustomBlocks()) {
Object statesContainer = FastNMS.INSTANCE.field$LevelChunkSection$states(section);
Object data = CoreReflections.varHandle$PalettedContainer$data.get(statesContainer);
Object palette = CoreReflections.field$PalettedContainer$Data$palette.get(data);
boolean requiresSync = false;
if (CoreReflections.clazz$SingleValuePalette.isInstance(palette)) {
Object onlyBlockState = CoreReflections.field$SingleValuePalette$value.get(palette);
if (BlockStateUtils.isCustomBlock(onlyBlockState)) {
synchronized (sections) {
for (int i = 0; i < ceSections.length; i++) {
CESection ceSection = ceSections[i];
Object section = sections[i];
if (Config.syncCustomBlocks()) {
Object statesContainer = FastNMS.INSTANCE.field$LevelChunkSection$states(section);
Object data = CoreReflections.varHandle$PalettedContainer$data.get(statesContainer);
Object palette = CoreReflections.field$PalettedContainer$Data$palette.get(data);
boolean requiresSync = false;
if (CoreReflections.clazz$SingleValuePalette.isInstance(palette)) {
Object onlyBlockState = CoreReflections.field$SingleValuePalette$value.get(palette);
if (BlockStateUtils.isCustomBlock(onlyBlockState)) {
requiresSync = true;
}
} else if (CoreReflections.clazz$LinearPalette.isInstance(palette)) {
Object[] blockStates = (Object[]) CoreReflections.field$LinearPalette$values.get(palette);
for (Object blockState : blockStates) {
if (blockState != null) {
if (BlockStateUtils.isCustomBlock(blockState)) {
requiresSync = true;
break;
}
}
}
} else if (CoreReflections.clazz$HashMapPalette.isInstance(palette)) {
Object biMap = CoreReflections.field$HashMapPalette$values.get(palette);
Object[] blockStates = (Object[]) CoreReflections.field$CrudeIncrementalIntIdentityHashBiMap$keys.get(biMap);
for (Object blockState : blockStates) {
if (blockState != null) {
if (BlockStateUtils.isCustomBlock(blockState)) {
requiresSync = true;
break;
}
}
}
} else {
requiresSync = true;
}
} else if (CoreReflections.clazz$LinearPalette.isInstance(palette)) {
Object[] blockStates = (Object[]) CoreReflections.field$LinearPalette$values.get(palette);
for (Object blockState : blockStates) {
if (blockState != null) {
if (BlockStateUtils.isCustomBlock(blockState)) {
requiresSync = true;
break;
}
}
}
} else if (CoreReflections.clazz$HashMapPalette.isInstance(palette)) {
Object biMap = CoreReflections.field$HashMapPalette$values.get(palette);
Object[] blockStates = (Object[]) CoreReflections.field$CrudeIncrementalIntIdentityHashBiMap$keys.get(biMap);
for (Object blockState : blockStates) {
if (blockState != null) {
if (BlockStateUtils.isCustomBlock(blockState)) {
requiresSync = true;
break;
}
}
}
} else {
requiresSync = true;
}
if (requiresSync) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 16; y++) {
Object mcState = FastNMS.INSTANCE.method$LevelChunkSection$getBlockState(section, x, y, z);
Optional<ImmutableBlockState> optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(mcState);
if (optionalCustomState.isPresent()) {
ceSection.setBlockState(x, y, z, optionalCustomState.get());
if (requiresSync) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 16; y++) {
Object mcState = FastNMS.INSTANCE.method$LevelChunkSection$getBlockState(section, x, y, z);
Optional<ImmutableBlockState> optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(mcState);
if (optionalCustomState.isPresent()) {
ceSection.setBlockState(x, y, z, optionalCustomState.get());
}
}
}
}
}
}
}
if (Config.restoreCustomBlocks()) {
if (!ceSection.statesContainer().isEmpty()) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 16; y++) {
ImmutableBlockState customState = ceSection.getBlockState(x, y, z);
if (!customState.isEmpty() && customState.customBlockState() != null) {
FastNMS.INSTANCE.method$LevelChunkSection$setBlockState(section, x, y, z, customState.customBlockState().handle(), false);
if (Config.restoreCustomBlocks()) {
if (!ceSection.statesContainer().isEmpty()) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 16; y++) {
ImmutableBlockState customState = ceSection.getBlockState(x, y, z);
if (!customState.isEmpty() && customState.customBlockState() != null) {
FastNMS.INSTANCE.method$LevelChunkSection$setBlockState(section, x, y, z, customState.customBlockState().handle(), false);
}
}
}
}
}
}
}
int finalI = i;
WorldStorageInjector.injectLevelChunkSection(section, ceSection, ceChunk, new SectionPos(pos.x, ceChunk.sectionY(i), pos.z),
(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);
int finalI = i;
WorldStorageInjector.injectLevelChunkSection(section, ceSection, ceChunk, new SectionPos(pos.x, ceChunk.sectionY(i), pos.z),
(injected) -> sections[finalI] = injected);
}
}
} catch (ReflectiveOperationException e) {