9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-31 12:56:28 +00:00
- 修复可堆叠方块行为
- 尝试向 packetevents 注入自定义方块
This commit is contained in:
jhqwqmc
2025-12-21 22:46:53 +08:00
parent e1a33c17d6
commit a242339f4f
7 changed files with 178 additions and 50 deletions

View File

@@ -13,6 +13,7 @@ import net.momirealms.craftengine.bukkit.compatibility.model.modelengine.ModelEn
import net.momirealms.craftengine.bukkit.compatibility.model.modelengine.ModelEngineUtils;
import net.momirealms.craftengine.bukkit.compatibility.mythicmobs.MythicItemDropListener;
import net.momirealms.craftengine.bukkit.compatibility.mythicmobs.MythicSkillHelper;
import net.momirealms.craftengine.bukkit.compatibility.packetevents.WrappedBlockStateHelper;
import net.momirealms.craftengine.bukkit.compatibility.papi.PlaceholderAPIUtils;
import net.momirealms.craftengine.bukkit.compatibility.permission.LuckPermsEventListeners;
import net.momirealms.craftengine.bukkit.compatibility.quickshop.QuickShopItemExpressionHandler;
@@ -141,6 +142,22 @@ public class BukkitCompatibilityManager implements CompatibilityManager<org.bukk
new QuickShopItemExpressionHandler(this.plugin).register();
logHook("QuickShop-Hikari");
}
if (this.isPluginEnabled("packetevents")) {
try {
WrappedBlockStateHelper.register(null);
} catch (Throwable e) {
this.plugin.logger().warn("Failed to register block to WrappedBlockState", e);
}
logHook("packetevents");
}
if (this.isPluginEnabled("GrimAC")) {
try {
WrappedBlockStateHelper.register("ac{}grim{}grimac{}shaded{}com{}github{}retrooper{}packetevents");
} catch (Throwable e) {
this.plugin.logger().warn("Failed to register block to WrappedBlockState", e);
}
logHook("GrimAC");
}
}
@Override

View File

@@ -0,0 +1,110 @@
package net.momirealms.craftengine.bukkit.compatibility.packetevents;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.util.ReflectionUtils;
import org.jetbrains.annotations.Nullable;
import java.lang.invoke.MethodHandle;
import java.util.Collections;
import java.util.Map;
public final class WrappedBlockStateHelper {
private static MethodHandle methodHandle$WrappedBlockState$BY_STRING$getter;
private static MethodHandle methodHandle$WrappedBlockState$BY_ID$getter;
private static MethodHandle methodHandle$WrappedBlockState$INTO_STRING$getter;
private static MethodHandle methodHandle$WrappedBlockState$INTO_ID$getter;
private static MethodHandle methodHandle$WrappedBlockState$DEFAULT_STATES$getter;
private static MethodHandle methodHandle$WrappedBlockState$loadMappings;
private static MethodHandle methodHandle$WrappedBlockState$constructor;
private static MethodHandle methodHandle$StateTypes$builder;
private static MethodHandle methodHandle$StateTypes$builder$name;
private static MethodHandle methodHandle$StateTypes$builder$isBlocking;
private static MethodHandle methodHandle$StateTypes$builder$setMaterial;
private static MethodHandle methodHandle$StateTypes$builder$build;
private static MethodHandle methodHandle$StateTypes$REGISTRY$getter;
private static MethodHandle methodHandle$StateTypes$REGISTRY$getTypesBuilder;
private static MethodHandle methodHandle$StateTypes$REGISTRY$load;
private static MethodHandle methodHandle$StateTypes$REGISTRY$unloadFileMappings;
private static Object instance$MaterialType$STONE;
private static Object clientVersion;
private WrappedBlockStateHelper() {}
@SuppressWarnings("unchecked")
public static void register(@Nullable String packageName) throws Throwable {
init(packageName);
byte mappingsIndex = (byte) methodHandle$WrappedBlockState$loadMappings.invoke(clientVersion);
Map<String, Object>[] BY_STRING = (Map<String, Object>[]) methodHandle$WrappedBlockState$BY_STRING$getter.invoke();
Map<Integer, Object>[] BY_ID = (Map<Integer, Object>[]) methodHandle$WrappedBlockState$BY_ID$getter.invoke();
Map<Object, String>[] INTO_STRING = (Map<Object, String>[]) methodHandle$WrappedBlockState$INTO_STRING$getter.invoke();
Map<Object, Integer>[] INTO_ID = (Map<Object, Integer>[]) methodHandle$WrappedBlockState$INTO_ID$getter.invoke();
Map<Object, Object>[] DEFAULT_STATES = (Map<Object, Object>[]) methodHandle$WrappedBlockState$DEFAULT_STATES$getter.invoke();
Map<String, Object> stringWrappedBlockStateMap = BY_STRING[mappingsIndex];
Map<Integer, Object> integerWrappedBlockStateMap = BY_ID[mappingsIndex];
Map<Object, String> wrappedBlockStateStringMap = INTO_STRING[mappingsIndex];
Map<Object, Integer> wrappedBlockStateIntegerMap = INTO_ID[mappingsIndex];
Map<Object, Object> stateTypeWrappedBlockStateMap = DEFAULT_STATES[mappingsIndex];
Object typesBuilder = methodHandle$StateTypes$REGISTRY$getTypesBuilder.invoke(methodHandle$StateTypes$REGISTRY$getter.invoke());
methodHandle$StateTypes$REGISTRY$load.invoke(typesBuilder);
for (int i = 0; i < Config.serverSideBlocks(); i++) {
String blockId = "craftengine:custom_" + i;
int id = BlockStateUtils.vanillaBlockStateCount() + i;
Object stateType = methodHandle$StateTypes$builder$build.invoke(
methodHandle$StateTypes$builder$setMaterial.invoke(
methodHandle$StateTypes$builder$isBlocking.invoke(
methodHandle$StateTypes$builder$name.invoke(
methodHandle$StateTypes$builder.invoke(),
blockId
), true
), instance$MaterialType$STONE
)
);
Object wrappedBlockState = methodHandle$WrappedBlockState$constructor.invoke(stateType, Collections.emptyMap(), id, mappingsIndex);
stringWrappedBlockStateMap.put(blockId, wrappedBlockState);
integerWrappedBlockStateMap.put(id, wrappedBlockState);
wrappedBlockStateStringMap.put(wrappedBlockState, blockId);
wrappedBlockStateIntegerMap.put(wrappedBlockState, id);
stateTypeWrappedBlockStateMap.put(stateType, wrappedBlockState);
}
methodHandle$StateTypes$REGISTRY$unloadFileMappings.invoke(typesBuilder);
}
private static void init(@Nullable String packageName) throws Throwable {
packageName = (packageName != null ? packageName : "com{}github{}retrooper{}packetevents").replace("{}", ".");
Class<?> clazz$WrappedBlockState = Class.forName(packageName + ".protocol.world.states.WrappedBlockState");
Class<?> clazz$PacketEvents = Class.forName(packageName + ".PacketEvents");
Class<?> clazz$PacketEventsAPI = Class.forName(packageName + ".PacketEventsAPI");
Class<?> clazz$ServerManager = Class.forName(packageName + ".manager.server.ServerManager");
Class<?> clazz$ServerVersion = Class.forName(packageName + ".manager.server.ServerVersion");
Class<?> clazz$ClientVersion = Class.forName(packageName + ".protocol.player.ClientVersion");
Class<?> clazz$StateType = Class.forName(packageName + ".protocol.world.states.type.StateType");
Class<?> clazz$StateTypes = Class.forName(packageName + ".protocol.world.states.type.StateTypes");
Class<?> clazz$StateTypes$Builder = Class.forName(packageName + ".protocol.world.states.type.StateTypes$Builder");
Class<?> clazz$MaterialType = Class.forName(packageName + ".protocol.world.MaterialType");
Class<?> clazz$VersionedRegistry = Class.forName(packageName + ".util.mappings.VersionedRegistry");
Class<?> clazz$TypesBuilder = Class.forName(packageName + ".util.mappings.TypesBuilder");
MethodHandle methodHandle$PacketEvents$getAPI = ReflectionUtils.unreflectMethod(clazz$PacketEvents.getDeclaredMethod("getAPI"));
MethodHandle methodHandle$PacketEventsAPI$getServerManager = ReflectionUtils.unreflectMethod(clazz$PacketEventsAPI.getDeclaredMethod("getServerManager"));
MethodHandle methodHandle$ServerManager$getVersion = ReflectionUtils.unreflectMethod(clazz$ServerManager.getDeclaredMethod("getVersion"));
MethodHandle methodHandle$ServerVersion$toClientVersion = ReflectionUtils.unreflectMethod(clazz$ServerVersion.getDeclaredMethod("toClientVersion"));
methodHandle$WrappedBlockState$BY_STRING$getter = ReflectionUtils.unreflectGetter(clazz$WrappedBlockState.getDeclaredField("BY_STRING"));
methodHandle$WrappedBlockState$BY_ID$getter = ReflectionUtils.unreflectGetter(clazz$WrappedBlockState.getDeclaredField("BY_ID"));
methodHandle$WrappedBlockState$INTO_STRING$getter = ReflectionUtils.unreflectGetter(clazz$WrappedBlockState.getDeclaredField("INTO_STRING"));
methodHandle$WrappedBlockState$INTO_ID$getter = ReflectionUtils.unreflectGetter(clazz$WrappedBlockState.getDeclaredField("INTO_ID"));
methodHandle$WrappedBlockState$DEFAULT_STATES$getter = ReflectionUtils.unreflectGetter(clazz$WrappedBlockState.getDeclaredField("DEFAULT_STATES"));
methodHandle$WrappedBlockState$loadMappings = ReflectionUtils.unreflectMethod(clazz$WrappedBlockState.getDeclaredMethod("loadMappings", clazz$ClientVersion));
methodHandle$WrappedBlockState$constructor = ReflectionUtils.unreflectConstructor(clazz$WrappedBlockState.getDeclaredConstructor(clazz$StateType, Map.class, int.class, byte.class));
methodHandle$StateTypes$builder = ReflectionUtils.unreflectMethod(clazz$StateTypes.getDeclaredMethod("builder"));
methodHandle$StateTypes$builder$name = ReflectionUtils.unreflectMethod(clazz$StateTypes$Builder.getDeclaredMethod("name", String.class));
methodHandle$StateTypes$builder$isBlocking = ReflectionUtils.unreflectMethod(clazz$StateTypes$Builder.getDeclaredMethod("isBlocking", boolean.class));
methodHandle$StateTypes$builder$setMaterial = ReflectionUtils.unreflectMethod(clazz$StateTypes$Builder.getDeclaredMethod("setMaterial", clazz$MaterialType));
methodHandle$StateTypes$builder$build = ReflectionUtils.unreflectMethod(clazz$StateTypes$Builder.getDeclaredMethod("build"));
methodHandle$StateTypes$REGISTRY$getter = ReflectionUtils.unreflectGetter(clazz$StateTypes.getDeclaredField("REGISTRY"));
methodHandle$StateTypes$REGISTRY$getTypesBuilder = ReflectionUtils.unreflectMethod(clazz$VersionedRegistry.getDeclaredMethod("getTypesBuilder"));
methodHandle$StateTypes$REGISTRY$load = ReflectionUtils.unreflectMethod(clazz$TypesBuilder.getDeclaredMethod("load"));
methodHandle$StateTypes$REGISTRY$unloadFileMappings = ReflectionUtils.unreflectMethod(clazz$TypesBuilder.getDeclaredMethod("unloadFileMappings"));
instance$MaterialType$STONE = clazz$MaterialType.getDeclaredField("STONE").get(null);
clientVersion = methodHandle$ServerVersion$toClientVersion.invoke(methodHandle$ServerManager$getVersion.invoke(methodHandle$PacketEventsAPI$getServerManager.invoke(methodHandle$PacketEvents$getAPI.invoke())));
}
}

View File

@@ -78,6 +78,10 @@ paper {
register("ViaVersion") { required = false }
register("QuickShop-Hikari") { required = false }
// PacketEvents
register("GrimAC") { required = false }
register("packetevents") { required = false }
// Geyser
register("Geyser-Spigot") { required = false }
register("floodgate") { required = false }

View File

@@ -9,6 +9,7 @@ import net.momirealms.craftengine.core.block.BlockBehavior;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.behavior.CanBeReplacedBlockBehavior;
import net.momirealms.craftengine.core.block.behavior.IsPathFindableBlockBehavior;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.block.properties.type.SlabType;
@@ -25,7 +26,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
public class SlabBlockBehavior extends BukkitBlockBehavior implements IsPathFindableBlockBehavior {
public class SlabBlockBehavior extends BukkitBlockBehavior implements IsPathFindableBlockBehavior, CanBeReplacedBlockBehavior {
public static final Factory FACTORY = new Factory();
private final Property<SlabType> typeProperty;

View File

@@ -1,100 +1,87 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.core.block.BlockBehavior;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.UpdateOption;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.behavior.CanBeReplacedBlockBehavior;
import net.momirealms.craftengine.core.block.properties.IntegerProperty;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.context.UseOnContext;
import net.momirealms.craftengine.core.item.context.BlockPlaceContext;
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.sound.SoundData;
import net.momirealms.craftengine.core.util.ItemUtils;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
import org.bukkit.Location;
import org.bukkit.inventory.ItemStack;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class StackableBlockBehavior extends BukkitBlockBehavior {
public class StackableBlockBehavior extends BukkitBlockBehavior implements CanBeReplacedBlockBehavior {
public static final Factory FACTORY = new Factory();
private final IntegerProperty amountProperty;
private final List<Key> items;
private final SoundData stackSound;
private final String propertyName;
public StackableBlockBehavior(CustomBlock block, IntegerProperty amountProperty, List<Key> items, SoundData stackSound) {
public StackableBlockBehavior(CustomBlock block, IntegerProperty amountProperty, List<Key> items, String propertyName) {
super(block);
this.amountProperty = amountProperty;
this.items = items;
this.stackSound = stackSound;
this.propertyName = propertyName;
}
@Override
@SuppressWarnings("unchecked")
public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) {
Player player = context.getPlayer();
if (player == null || player.isSecondaryUseActive()) {
return InteractionResult.PASS;
public boolean canBeReplaced(BlockPlaceContext context, ImmutableBlockState state) {
if (super.canBeReplaced(context, state)) {
return true;
}
Item<ItemStack> item = (Item<ItemStack>) context.getItem();
if (context.isSecondaryUseActive()) {
return false;
}
Item<?> item = context.getItem();
if (ItemUtils.isEmpty(item)) {
return InteractionResult.PASS;
return false;
}
if (!this.items.contains(item.id())) {
return InteractionResult.PASS;
return false;
}
BlockPos pos = context.getClickedPos();
World world = context.getLevel();
if (state.get(this.amountProperty) >= this.amountProperty.max) {
return InteractionResult.SUCCESS_AND_CANCEL;
Property<?> property = state.owner().value().getProperty(this.propertyName);
if (property == null || property.valueClass() != Integer.class) {
return false;
}
updateStackableBlock(state, pos, world, item, player, context.getHand());
return InteractionResult.SUCCESS_AND_CANCEL;
return (Integer) state.get(property) < this.amountProperty.max;
}
private void updateStackableBlock(ImmutableBlockState state, BlockPos pos, World world, Item<ItemStack> item, Player player, InteractionHand hand) {
ImmutableBlockState nextStage = state.cycle(this.amountProperty);
Location location = new Location((org.bukkit.World) world.platformWorld(), pos.x(), pos.y(), pos.z());
FastNMS.INSTANCE.method$LevelWriter$setBlock(world.serverWorld(), LocationUtils.toBlockPos(pos), nextStage.customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags());
if (this.stackSound != null) {
world.playBlockSound(new Vec3d(location.getX(), location.getY(), location.getZ()), this.stackSound);
@Override
public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) {
Object world = context.getLevel().serverWorld();
Object pos = LocationUtils.toBlockPos(context.getClickedPos());
ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, pos)).orElse(null);
if (blockState == null) {
return state;
}
if (!player.isCreativeMode()) {
item.count(item.count() - 1);
Property<?> property = blockState.owner().value().getProperty(this.propertyName);
if (property == null || property.valueClass() != Integer.class) {
return state;
}
player.swingHand(hand);
return blockState.cycle(property);
}
public static class Factory implements BlockBehaviorFactory {
@Override
@SuppressWarnings("unchecked")
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
String propertyName = String.valueOf(arguments.getOrDefault("property", "amount"));
IntegerProperty amount = (IntegerProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty(propertyName), () -> {
throw new LocalizedResourceConfigException("warning.config.block.behavior.stackable.missing_property", propertyName);
});
Map<String, Object> sounds = (Map<String, Object>) arguments.get("sounds");
SoundData stackSound = null;
if (sounds != null) {
stackSound = Optional.ofNullable(sounds.get("stack")).map(obj -> SoundData.create(obj, SoundData.SoundValue.FIXED_1, SoundData.SoundValue.FIXED_1)).orElse(null);
}
Object itemsObj = ResourceConfigUtils.requireNonNullOrThrow(arguments.get("items"), "warning.config.block.behavior.stackable.missing_items");
List<Key> items = MiscUtils.getAsStringList(itemsObj).stream().map(Key::of).toList();
return new StackableBlockBehavior(block, amount, items, stackSound);
return new StackableBlockBehavior(block, amount, items, propertyName);
}
}
}

View File

@@ -15,7 +15,7 @@ import java.util.Optional;
import java.util.concurrent.Callable;
public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior
implements FallOnBlockBehavior, PlaceLiquidBlockBehavior, IsPathFindableBlockBehavior {
implements FallOnBlockBehavior, PlaceLiquidBlockBehavior, IsPathFindableBlockBehavior, CanBeReplacedBlockBehavior {
private final AbstractBlockBehavior[] behaviors;
public UnsafeCompositeBlockBehavior(CustomBlock customBlock, List<AbstractBlockBehavior> behaviors) {
@@ -258,8 +258,8 @@ public class UnsafeCompositeBlockBehavior extends BukkitBlockBehavior
@Override
public boolean canBeReplaced(BlockPlaceContext context, ImmutableBlockState state) {
for (AbstractBlockBehavior behavior : this.behaviors) {
if (!behavior.canBeReplaced(context, state)) {
return false;
if (behavior instanceof CanBeReplacedBlockBehavior canBeReplacedBlockBehavior) {
return canBeReplacedBlockBehavior.canBeReplaced(context, state);
}
}
return super.canBeReplaced(context, state);