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

Merge branch 'Xiao-MoMi:dev' into dev

This commit is contained in:
Halogly
2025-07-18 14:10:27 +08:00
committed by GitHub
29 changed files with 271 additions and 74 deletions

View File

@@ -42,13 +42,13 @@ public class BukkitCustomItem extends AbstractCustomItem<ItemStack> {
}
@Override
public Item<ItemStack> buildItem(ItemBuildContext context) {
ItemStack item = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(FastNMS.INSTANCE.constructor$ItemStack(this.item, 1));
public Item<ItemStack> buildItem(ItemBuildContext context, int count) {
ItemStack item = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(FastNMS.INSTANCE.constructor$ItemStack(this.item, count));
Item<ItemStack> wrapped = BukkitCraftEngine.instance().itemManager().wrap(item);
for (ItemDataModifier<ItemStack> modifier : dataModifiers()) {
modifier.apply(wrapped, context);
}
return BukkitCraftEngine.instance().itemManager().wrap(wrapped.getItem());
return wrapped;
}
public Object clientItem() {

View File

@@ -15,6 +15,7 @@ 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.util.ItemStackUtils;
import net.momirealms.craftengine.bukkit.util.KeyUtils;
import net.momirealms.craftengine.core.entity.player.Player;
@@ -28,8 +29,6 @@ import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigExce
import net.momirealms.craftengine.core.plugin.logger.Debugger;
import net.momirealms.craftengine.core.util.*;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Registry;
import org.bukkit.event.HandlerList;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -39,6 +38,7 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Function;
public class BukkitItemManager extends AbstractItemManager<ItemStack> {
static {
@@ -56,8 +56,10 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
private final Object bedrockItemHolder;
private final Item<ItemStack> emptyItem;
private final UniqueIdItem<ItemStack> emptyUniqueItem;
private final Function<Object, Integer> decoratedHashOpsGenerator;
private Set<Key> lastRegisteredPatterns = Set.of();
@SuppressWarnings("unchecked")
public BukkitItemManager(BukkitCraftEngine plugin) {
super(plugin);
instance = this;
@@ -68,13 +70,14 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
this.armorEventListener = new ArmorEventListener();
this.networkItemHandler = VersionHelper.isOrAbove1_20_5() ? new ModernNetworkItemHandler() : new LegacyNetworkItemHandler();
this.registerAllVanillaItems();
this.bedrockItemHolder = FastNMS.INSTANCE.method$Registry$getHolderByResourceKey(MBuiltInRegistries.ITEM, FastNMS.INSTANCE.method$ResourceKey$create(MRegistries.ITEM, KeyUtils.toResourceLocation(Key.of("minecraft:bedrock")))).get();;
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.decoratedHashOpsGenerator = VersionHelper.isOrAbove1_21_5() ? (Function<Object, Integer>) FastNMS.INSTANCE.createDecoratedHashOpsGenerator(MRegistryOps.HASHCODE) : null;
}
@SuppressWarnings("unchecked")
@@ -160,11 +163,11 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
@Override
public Optional<BuildableItem<ItemStack>> getVanillaItem(Key key) {
Material material = Registry.MATERIAL.get(KeyUtils.toNamespacedKey(key));
if (material == null) {
ItemStack vanilla = createVanillaItemStack(key);
if (vanilla == null) {
return Optional.empty();
}
return Optional.of(new CloneableConstantItem(key, new ItemStack(material)));
return Optional.of(new CloneableConstantItem(key, this.wrap(vanilla)));
}
@Override
@@ -452,4 +455,9 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
}
return this.emptyItem;
}
@Nullable // 1.21.5+
public Function<Object, Integer> decoratedHashOpsGenerator() {
return decoratedHashOpsGenerator;
}
}

View File

@@ -1,15 +1,16 @@
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 ItemStack item;
private final Item<ItemStack> item;
private final Key id;
public CloneableConstantItem(Key id, ItemStack item) {
public CloneableConstantItem(Key id, Item<ItemStack> item) {
this.item = item;
this.id = id;
}
@@ -19,10 +20,13 @@ public class CloneableConstantItem implements BuildableItem<ItemStack> {
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) {
ItemStack itemStack = this.item.clone();
itemStack.setAmount(count);
return itemStack;
return this.item.copyWithCount(count).getItem();
}
}

View File

@@ -49,7 +49,7 @@ public abstract class BukkitItemFactory<W extends ItemWrapper<ItemStack>> extend
case "1.21.4" -> {
return new ComponentItemFactory1_21_4(plugin);
}
case "1.21.5", "1.21.6", "1.21.7", "1.22", "1.22.1" -> {
case "1.21.5", "1.21.6", "1.21.7", "1.21.8" -> {
return new ComponentItemFactory1_21_5(plugin);
}
default -> throw new IllegalStateException("Unsupported server version: " + plugin.serverVersion());

View File

@@ -561,7 +561,7 @@ public class BukkitRecipeManager extends AbstractRecipeManager<ItemStack> {
}
CustomStoneCuttingRecipe<ItemStack> ceRecipe = new CustomStoneCuttingRecipe<>(
id, recipe.group(), Ingredient.of(holders),
new CustomRecipeResult<>(new CloneableConstantItem(recipe.result().isCustom() ? Key.of("!internal:custom") : Key.of(recipe.result().id()), result), recipe.result().count())
new CustomRecipeResult<>(new CloneableConstantItem(recipe.result().isCustom() ? Key.of("!internal:custom") : Key.of(recipe.result().id()), BukkitItemManager.instance().wrap(result)), recipe.result().count(), null)
);
this.registerInternalRecipe(id, ceRecipe);
}
@@ -590,7 +590,7 @@ public class BukkitRecipeManager extends AbstractRecipeManager<ItemStack> {
}
CustomShapelessRecipe<ItemStack> ceRecipe = new CustomShapelessRecipe<>(
id, recipe.category(), recipe.group(), ingredientList,
new CustomRecipeResult<>(new CloneableConstantItem(recipe.result().isCustom() ? Key.of("!internal:custom") : Key.of(recipe.result().id()), result), recipe.result().count())
new CustomRecipeResult<>(new CloneableConstantItem(recipe.result().isCustom() ? Key.of("!internal:custom") : Key.of(recipe.result().id()), BukkitItemManager.instance().wrap(result)), recipe.result().count(), null)
);
if (hasCustomItemInTag) {
Runnable converted = findNMSRecipeConvertor(ceRecipe).convert(id, ceRecipe);
@@ -627,7 +627,7 @@ public class BukkitRecipeManager extends AbstractRecipeManager<ItemStack> {
CustomShapedRecipe<ItemStack> ceRecipe = new CustomShapedRecipe<>(
id, recipe.category(), recipe.group(),
new CustomShapedRecipe.Pattern<>(recipe.pattern(), ingredients),
new CustomRecipeResult<>(new CloneableConstantItem(recipe.result().isCustom() ? Key.of("!internal:custom") : Key.of(recipe.result().id()), result), recipe.result().count())
new CustomRecipeResult<>(new CloneableConstantItem(recipe.result().isCustom() ? Key.of("!internal:custom") : Key.of(recipe.result().id()), BukkitItemManager.instance().wrap(result)), recipe.result().count(), null)
);
if (hasCustomItemInTag) {
Runnable converted = findNMSRecipeConvertor(ceRecipe).convert(id, ceRecipe);
@@ -651,7 +651,7 @@ public class BukkitRecipeManager extends AbstractRecipeManager<ItemStack> {
id, recipe.category(), recipe.group(),
Ingredient.of(holders),
recipe.cookingTime(), recipe.experience(),
new CustomRecipeResult<>(new CloneableConstantItem(recipe.result().isCustom() ? Key.of("!internal:custom") : Key.of(recipe.result().id()), result), recipe.result().count())
new CustomRecipeResult<>(new CloneableConstantItem(recipe.result().isCustom() ? Key.of("!internal:custom") : Key.of(recipe.result().id()), BukkitItemManager.instance().wrap(result)), recipe.result().count(), null)
);
if (hasCustomItemInTag) {
Runnable converted = findNMSRecipeConvertor(ceRecipe).convert(id, ceRecipe);
@@ -680,7 +680,7 @@ public class BukkitRecipeManager extends AbstractRecipeManager<ItemStack> {
baseHolders.isEmpty() ? null : Ingredient.of(baseHolders),
templateHolders.isEmpty() ? null : Ingredient.of(templateHolders),
additionHolders.isEmpty() ? null : Ingredient.of(additionHolders),
new CustomRecipeResult<>(new CloneableConstantItem(recipe.result().isCustom() ? Key.of("!internal:custom") : Key.of(recipe.result().id()), result), recipe.result().count()),
new CustomRecipeResult<>(new CloneableConstantItem(recipe.result().isCustom() ? Key.of("!internal:custom") : Key.of(recipe.result().id()), BukkitItemManager.instance().wrap(result)), recipe.result().count(), null),
true,
List.of()
);

View File

@@ -10,6 +10,7 @@ import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import net.momirealms.craftengine.core.plugin.command.FlagKeys;
import net.momirealms.craftengine.core.plugin.locale.MessageConstants;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.VersionHelper;
import org.bukkit.NamespacedKey;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@@ -74,9 +75,17 @@ public class GiveItemCommand extends BukkitCommandFeature<CommandSender> {
ItemStack more = builtItem.clone();
more.setAmount(perStackSize);
if (toInv) {
PlayerUtils.putItemsToInventory(player.getInventory(), more, more.getAmount());
if (VersionHelper.isFolia()) {
player.getScheduler().run(plugin().javaPlugin(), (t) -> PlayerUtils.putItemsToInventory(player.getInventory(), more, more.getAmount()), () -> {});
} else {
PlayerUtils.putItemsToInventory(player.getInventory(), more, more.getAmount());
}
} else {
PlayerUtils.dropItem(player, more, false, true, false);
if (VersionHelper.isFolia()) {
player.getScheduler().run(plugin().javaPlugin(), (t) -> PlayerUtils.dropItem(player, more, false, true, false), () -> {});
} else {
PlayerUtils.dropItem(player, more, false, true, false);
}
}
}
}

View File

@@ -188,7 +188,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
registerS2CByteBufPacketConsumer(PacketConsumers.SET_EQUIPMENT, this.packetIds.clientboundSetEquipmentPacket());
registerS2CByteBufPacketConsumer(PacketConsumers.SET_PLAYER_INVENTORY_1_21_2, this.packetIds.clientboundSetPlayerInventoryPacket());
registerC2SByteBufPacketConsumer(PacketConsumers.SET_CREATIVE_MODE_SLOT, this.packetIds.serverboundSetCreativeModeSlotPacket());
registerC2SByteBufPacketConsumer(PacketConsumers.CONTAINER_CLICK_1_20, this.packetIds.serverboundContainerClickPacket());
registerC2SByteBufPacketConsumer(VersionHelper.isOrAbove1_21_5() ? PacketConsumers.CONTAINER_CLICK_1_21_5 : PacketConsumers.CONTAINER_CLICK_1_20, this.packetIds.serverboundContainerClickPacket());
registerC2SByteBufPacketConsumer(PacketConsumers.INTERACT_ENTITY, this.packetIds.serverboundInteractPacket());
}

View File

@@ -2124,23 +2124,6 @@ public class PacketConsumers {
FriendlyByteBuf buf = event.getBuffer();
Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf);
ItemStack itemStack = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf);
if (VersionHelper.isOrAbove1_21_5()) {
Item<ItemStack> wrapped = BukkitItemManager.instance().wrap(itemStack);
if (!wrapped.isEmpty() && wrapped.isCustomItem()) {
Object containerMenu = FastNMS.INSTANCE.field$Player$containerMenu(serverPlayer.serverPlayer());
if (containerMenu != null) {
ItemStack carried = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(FastNMS.INSTANCE.method$AbstractContainerMenu$getCarried(containerMenu));
if (ItemStackUtils.isEmpty(carried)) {
event.setChanged(true);
buf.clear();
buf.writeVarInt(event.packetID());
Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf);
FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, carried);
return;
}
}
}
}
BukkitItemManager.instance().s2c(itemStack, serverPlayer).ifPresent((newItemStack) -> {
event.setChanged(true);
buf.clear();
@@ -2245,9 +2228,70 @@ public class PacketConsumers {
}
};
public static final BiConsumer<NetWorkUser, ByteBufPacketEvent> CONTAINER_CLICK_1_21_5 = (user, event) -> {
try {
FriendlyByteBuf buf = event.getBuffer();
boolean changed = false;
Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf);
Object inventory = FastNMS.INSTANCE.method$Player$getInventory(user.serverPlayer());
int containerId = buf.readContainerId();
int stateId = buf.readVarInt();
short slotNum = buf.readShort();
byte buttonNum = buf.readByte();
int clickType = buf.readVarInt();
int i = buf.readVarInt();
Int2ObjectMap<Object> changedSlots = new Int2ObjectOpenHashMap<>(i);
for (int j = 0; j < i; ++j) {
int k = buf.readShort();
Object hashedStack = FastNMS.INSTANCE.method$StreamDecoder$decode(NetworkReflections.instance$HashedStack$STREAM_CODEC, friendlyBuf);
Object serverSideItemStack = FastNMS.INSTANCE.method$Container$getItem(inventory, k);
Optional<ItemStack> optional = BukkitItemManager.instance().s2c(FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(serverSideItemStack).clone(), ((net.momirealms.craftengine.core.entity.player.Player) user));
if (optional.isPresent()) {
Object clientSideItemStack = FastNMS.INSTANCE.field$CraftItemStack$handle(optional.get());
boolean isSync = FastNMS.INSTANCE.method$HashedStack$matches(hashedStack, clientSideItemStack, BukkitItemManager.instance().decoratedHashOpsGenerator());
if (isSync) {
changed = true;
hashedStack = FastNMS.INSTANCE.method$HashedStack$create(serverSideItemStack, BukkitItemManager.instance().decoratedHashOpsGenerator());
}
}
changedSlots.put(k, hashedStack);
}
Object carriedHashedStack = FastNMS.INSTANCE.method$StreamDecoder$decode(NetworkReflections.instance$HashedStack$STREAM_CODEC, friendlyBuf);
Object containerMenu = FastNMS.INSTANCE.field$Player$containerMenu(user.serverPlayer());
Object serverSideCarriedItemStack = FastNMS.INSTANCE.method$AbstractContainerMenu$getCarried(containerMenu);
Optional<ItemStack> optional = BukkitItemManager.instance().s2c(FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(serverSideCarriedItemStack).clone(), ((net.momirealms.craftengine.core.entity.player.Player) user));
if (optional.isPresent()) {
Object clientSideCarriedItemStack = FastNMS.INSTANCE.field$CraftItemStack$handle(optional.get());
boolean isSync = FastNMS.INSTANCE.method$HashedStack$matches(carriedHashedStack, clientSideCarriedItemStack, BukkitItemManager.instance().decoratedHashOpsGenerator());
if (isSync) {
changed = true;
carriedHashedStack = FastNMS.INSTANCE.method$HashedStack$create(serverSideCarriedItemStack, BukkitItemManager.instance().decoratedHashOpsGenerator());
}
}
if (changed) {
event.setChanged(true);
buf.clear();
buf.writeVarInt(event.packetID());
buf.writeContainerId(containerId);
buf.writeVarInt(stateId);
buf.writeShort(slotNum);
buf.writeByte(buttonNum);
buf.writeVarInt(clickType);
buf.writeVarInt(changedSlots.size());
Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf);
changedSlots.forEach((k, v) -> {
buf.writeShort(k);
FastNMS.INSTANCE.method$StreamEncoder$encode(NetworkReflections.instance$HashedStack$STREAM_CODEC, newFriendlyBuf, v);
});
FastNMS.INSTANCE.method$StreamEncoder$encode(NetworkReflections.instance$HashedStack$STREAM_CODEC, newFriendlyBuf, carriedHashedStack);
}
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to handle ServerboundContainerClickPacket", e);
}
};
public static final BiConsumer<NetWorkUser, ByteBufPacketEvent> CONTAINER_CLICK_1_20 = (user, event) -> {
try {
if (VersionHelper.isOrAbove1_21_5()) return; // 1.21.5+需要其他办法解决同步问题
FriendlyByteBuf buf = event.getBuffer();
boolean changed = false;
Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf);

View File

@@ -3850,4 +3850,27 @@ public final class CoreReflections {
CoreReflections.clazz$BlockHitResult, CoreReflections.clazz$Vec3, CoreReflections.clazz$Direction, CoreReflections.clazz$BlockPos, boolean.class
)
);
public static final Class<?> clazz$HashOps = MiscUtils.requireNonNullIf(
ReflectionUtils.getClazz(BukkitReflectionUtils.assembleMCClass("util.HashOps")),
VersionHelper.isOrAbove1_21_5()
);
public static final Field field$HashOps$CRC32C_INSTANCE = Optional.ofNullable(clazz$HashOps)
.map(it -> ReflectionUtils.getDeclaredField(it, it, 0))
.orElse(null);
public static final Object instance$HashOps$CRC32C_INSTANCE;
static {
try {
if (VersionHelper.isOrAbove1_21_5()) {
instance$HashOps$CRC32C_INSTANCE = field$HashOps$CRC32C_INSTANCE.get(null);
} else {
instance$HashOps$CRC32C_INSTANCE = null;
}
} catch (ReflectiveOperationException e) {
throw new ReflectionInitException("Failed to initialize HashOps", e);
}
}
}

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.bukkit.plugin.reflection.minecraft;
import com.google.common.hash.HashCode;
import com.google.gson.JsonElement;
import com.mojang.serialization.DynamicOps;
import com.mojang.serialization.JsonOps;
@@ -12,6 +13,7 @@ import net.momirealms.sparrow.nbt.Tag;
import net.momirealms.sparrow.nbt.codec.LegacyJavaOps;
import net.momirealms.sparrow.nbt.codec.LegacyNBTOps;
import net.momirealms.sparrow.nbt.codec.NBTOps;
import org.jetbrains.annotations.Nullable;
import static java.util.Objects.requireNonNull;
@@ -21,6 +23,7 @@ public final class MRegistryOps {
public static final DynamicOps<Tag> SPARROW_NBT;
public static final DynamicOps<Object> JAVA;
public static final DynamicOps<JsonElement> JSON;
public static final @Nullable DynamicOps<HashCode> HASHCODE; // 1.21.5+
// 1.20.5+
public static final Class<?> clazz$JavaOps = ReflectionUtils.getClazz("com.mojang.serialization.JavaOps");
@@ -47,6 +50,7 @@ public final class MRegistryOps {
NBT = (DynamicOps<Object>) CoreReflections.method$RegistryOps$create.invoke(null, ReflectionUtils.getDeclaredField(clazz$NbtOps, clazz$NbtOps, 0).get(null), FastNMS.INSTANCE.registryAccess());
JSON = (DynamicOps<JsonElement>) CoreReflections.method$RegistryOps$create.invoke(null, JsonOps.INSTANCE, FastNMS.INSTANCE.registryAccess());
SPARROW_NBT = (DynamicOps<Tag>) CoreReflections.method$RegistryOps$create.invoke(null, VersionHelper.isOrAbove1_20_5() ? NBTOps.INSTANCE : LegacyNBTOps.INSTANCE, FastNMS.INSTANCE.registryAccess());
HASHCODE = VersionHelper.isOrAbove1_21_5() ? (DynamicOps<HashCode>) CoreReflections.method$RegistryOps$create.invoke(null, CoreReflections.instance$HashOps$CRC32C_INSTANCE, FastNMS.INSTANCE.registryAccess()) : null;
} catch (ReflectiveOperationException e) {
throw new ReflectionInitException("Failed to init DynamicOps", e);
}

View File

@@ -1594,4 +1594,31 @@ public final class NetworkReflections {
List.of("network.protocol.common.ClientboundUpdateTagsPacket", "network.protocol.game.ClientboundUpdateTagsPacket")
)
);
// 1.21.5+
public static final Class<?> clazz$HashedStack = MiscUtils.requireNonNullIf(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("network.HashedStack")
),
VersionHelper.isOrAbove1_21_5()
);
// 1.21.5+
public static final Field field$HashedStack$STREAM_CODEC = Optional.ofNullable(clazz$HashedStack)
.map(it -> ReflectionUtils.getDeclaredField(it, clazz$StreamCodec, 0))
.orElse(null);
public static final Object instance$HashedStack$STREAM_CODEC;
static {
try {
if (VersionHelper.isOrAbove1_21_5()) {
instance$HashedStack$STREAM_CODEC = field$HashedStack$STREAM_CODEC.get(null);
} else {
instance$HashedStack$STREAM_CODEC = null;
}
} catch (ReflectiveOperationException e) {
throw new ReflectionInitException("Failed to initialize HashedStack$STREAM_CODEC", e);
}
}
}