9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-26 10:29:20 +00:00
This commit is contained in:
XiaoMoMi
2025-04-16 04:00:59 +08:00
parent 08894c7c0e
commit add10d3975
17 changed files with 179 additions and 106 deletions

View File

@@ -148,6 +148,8 @@ block:
# - Use `client-bound-item-data` to safely sync custom block data to clients.
# Documentation: https://mo-mi.gitbook.io/xiaomomi-plugins/craftengine/plugin-wiki/craftengine/add-new-contents/items/item-data/client-bound-item-data
simplify-adventure-break-check: false
# Similar to the option above, but designed for block placement
simplify-adventure-place-check: false
# Whether plugin should predict the next block to break
# This can help improve mining experience to some extent at the cost of performance
predict-breaking:

View File

@@ -105,7 +105,7 @@ public class BlockEventListener implements Listener {
Location location = block.getLocation();
BukkitServerPlayer serverPlayer = this.plugin.adapt(player);
// double check adventure mode to prevent dupe
if (!FastNMS.INSTANCE.mayBuild(serverPlayer.serverPlayer()) && !serverPlayer.canBreak(LocationUtils.toBlockPos(location))) {
if (!FastNMS.INSTANCE.mayBuild(serverPlayer.serverPlayer()) && !serverPlayer.canBreak(LocationUtils.toBlockPos(location), null)) {
return;
}

View File

@@ -145,8 +145,8 @@ public class BukkitCustomItem implements CustomItem<ItemStack> {
private Key id;
private Material material;
private Key materialKey;
private ItemSettings settings;
private final List<ItemBehavior> behaviors = new ArrayList<>();
private ItemSettings settings = ItemSettings.of();
private final List<ItemDataModifier<ItemStack>> modifiers = new ArrayList<>();
private final List<ItemDataModifier<ItemStack>> clientBoundModifiers = new ArrayList<>();
@@ -208,7 +208,7 @@ public class BukkitCustomItem implements CustomItem<ItemStack> {
@Override
public CustomItem<ItemStack> build() {
this.modifiers.addAll(this.settings.modifiers());
return new BukkitCustomItem(id, materialKey, material, modifiers, clientBoundModifiers, behaviors, settings);
return new BukkitCustomItem(this.id, this.materialKey, this.material, this.modifiers, this.clientBoundModifiers, this.behaviors, this.settings);
}
}
}

View File

@@ -76,12 +76,12 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
this.debugStickListener = new DebugStickListener(plugin);
this.itemParser = new ItemParser();
this.registerAllVanillaItems();
if (plugin.hasMod()) {
if (plugin.hasMod() && VersionHelper.isVersionNewerThan1_20_5()) {
Class<?> clazz$CustomStreamCodec = ReflectionUtils.getClazz("net.momirealms.craftengine.mod.item.CustomStreamCodec");
if (clazz$CustomStreamCodec != null) {
Field cProcessor = ReflectionUtils.getDeclaredField(clazz$CustomStreamCodec, Function.class, 0);
Field sProcessor = ReflectionUtils.getDeclaredField(clazz$CustomStreamCodec, Function.class, 1);
Function<Object, Object> c = (raw) -> {
Field s2cProcessor = ReflectionUtils.getDeclaredField(clazz$CustomStreamCodec, Function.class, 0);
Field c2sProcessor = ReflectionUtils.getDeclaredField(clazz$CustomStreamCodec, Function.class, 1);
Function<Object, Object> s2c = (raw) -> {
ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(raw);
Item<ItemStack> wrapped = this.wrap(itemStack.clone());
Optional<CustomItem<ItemStack>> customItem = wrapped.getCustomItem();
@@ -99,7 +99,7 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
return wrapped.getLiteralObject();
};
Function<Object, Object> s = (raw) -> {
Function<Object, Object> c2s = (raw) -> {
ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(raw);
Item<ItemStack> wrapped = this.wrap(itemStack);
Optional<CustomItem<ItemStack>> customItem = wrapped.getCustomItem();
@@ -117,10 +117,10 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
return wrapped.getLiteralObject();
};
try {
assert cProcessor != null;
cProcessor.set(null, c);
assert sProcessor != null;
sProcessor.set(null, s);
assert s2cProcessor != null;
s2cProcessor.set(null, s2c);
assert c2sProcessor != null;
c2sProcessor.set(null, c2s);
} catch (ReflectiveOperationException e) {
plugin.logger().warn("Failed to load custom stream codec", e);
}
@@ -267,7 +267,10 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
.orElseGet(() -> ((WritableRegistry<Key>) BuiltInRegistries.OPTIMIZED_ITEM_ID)
.register(new ResourceKey<>(BuiltInRegistries.OPTIMIZED_ITEM_ID.key().location(), id), id));
boolean isVanillaItem = id.namespace().equals("minecraft") && Registry.MATERIAL.get(new NamespacedKey(id.namespace(), id.value())) != null;
String materialStringId = (String) section.get("material");
if (isVanillaItem)
materialStringId = id.value();
if (materialStringId == null) {
TranslationManager.instance().log("warning.config.item.lack_material", path.toString(), id.toString());
return;
@@ -284,8 +287,6 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
Key itemModelKey = null;
CustomItem.Builder<ItemStack> itemBuilder = BukkitCustomItem.builder().id(id).material(materialId);
itemBuilder.dataModifier(new IdModifier<>(id));
boolean hasItemModelSection = section.containsKey("item-model");
// To get at least one model provider
@@ -341,6 +342,10 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
}
}
// Add it here to make sure that ce id is always applied
if (!isVanillaItem)
itemBuilder.dataModifier(new IdModifier<>(id));
// Get item data
Map<String, Object> clientSideDataSection = MiscUtils.castToMap(section.get("client-bound-data"), true);
if (clientSideDataSection != null) {
@@ -355,10 +360,17 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
}
}
ItemSettings itemSettings;
if (section.containsKey("settings")) {
Map<String, Object> settings = MiscUtils.castToMap(section.get("settings"), false);
itemBuilder.settings(ItemSettings.fromMap(settings));
itemSettings = ItemSettings.fromMap(settings);
} else {
itemSettings = ItemSettings.of();
}
if (isVanillaItem) {
itemSettings.canPlaceRelatedVanillaBlock(true);
}
itemBuilder.settings(itemSettings);
CustomItem<ItemStack> customItem = itemBuilder.build();
customItems.put(id, customItem);

View File

@@ -5,10 +5,7 @@ import net.momirealms.craftengine.bukkit.api.event.CustomBlockAttemptPlaceEvent;
import net.momirealms.craftengine.bukkit.api.event.CustomBlockPlaceEvent;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.util.DirectionUtils;
import net.momirealms.craftengine.bukkit.util.EventUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.bukkit.util.*;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.UpdateOption;
@@ -22,6 +19,7 @@ import net.momirealms.craftengine.core.item.context.BlockPlaceContext;
import net.momirealms.craftengine.core.item.context.UseOnContext;
import net.momirealms.craftengine.core.pack.Pack;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.world.BlockPos;
@@ -81,11 +79,6 @@ public class BlockItemBehavior extends ItemBehavior {
World world = (World) placeContext.getLevel().platformWorld();
Location placeLocation = new Location(world, pos.x(), pos.y(), pos.z());
// todo adventure check
if (player.isAdventureMode()) {
return InteractionResult.FAIL;
}
int gameTicks = player.gameTicks();
if (!player.updateLastSuccessfulInteractionTick(gameTicks)) {
return InteractionResult.FAIL;
@@ -95,6 +88,22 @@ public class BlockItemBehavior extends ItemBehavior {
Block againstBlock = world.getBlockAt(againstPos.x(), againstPos.y(), againstPos.z());
org.bukkit.entity.Player bukkitPlayer = (org.bukkit.entity.Player) player.platformPlayer();
if (player.isAdventureMode()) {
Object againstBlockState = BlockStateUtils.blockDataToBlockState(againstBlock.getBlockData());
int stateId = BlockStateUtils.blockStateToId(againstBlockState);
if (BlockStateUtils.isVanillaBlock(stateId)) {
if (!AdventureModeUtils.canPlace(context.getItem(), context.getLevel(), againstPos, againstBlockState)) {
return InteractionResult.FAIL;
}
} else {
ImmutableBlockState customState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId);
// custom block
if (!AdventureModeUtils.canPlace(context.getItem(), context.getLevel(), againstPos, Config.simplifyAdventurePlaceCheck() ? customState.vanillaBlockState().handle() : againstBlockState)) {
return InteractionResult.FAIL;
}
}
}
// trigger event
CustomBlockAttemptPlaceEvent attemptPlaceEvent = new CustomBlockAttemptPlaceEvent(bukkitPlayer, placeLocation.clone(), blockStateToPlace,
DirectionUtils.toBlockFace(context.getClickedFace()), bukkitBlock, context.getHand());

View File

@@ -9,6 +9,7 @@ import net.momirealms.craftengine.core.item.ItemWrapper;
import net.momirealms.craftengine.core.item.modifier.IdModifier;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.Key;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import java.util.Objects;
@@ -43,7 +44,10 @@ public abstract class BukkitItemFactory extends ItemFactory<CraftEngine, RTagIte
@Override
protected Key id(ItemWrapper<ItemStack> item) {
Object id = item.get(IdModifier.CRAFT_ENGINE_ID);
if (id == null) return Key.of(item.getItem().getType().getKey().asString());
if (id == null) {
NamespacedKey key = item.getItem().getType().getKey();
return Key.of(key.getNamespace(), key.getKey());
}
return Key.of(id.toString());
}

View File

@@ -1131,14 +1131,14 @@ public class PacketConsumers {
return;
}
if (player.isAdventureMode()) {
if (Config.simplifyAdventureCheck()) {
if (Config.simplifyAdventureBreakCheck()) {
ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId);
if (!player.canBreak(pos, state.vanillaBlockState().handle())) {
player.preventMiningBlock();
return;
}
} else {
if (!player.canBreak(pos)) {
if (!player.canBreak(pos, null)) {
player.preventMiningBlock();
return;
}

View File

@@ -157,49 +157,13 @@ public class BukkitServerPlayer extends Player {
}
@Override
public boolean canBreak(BlockPos pos) {
Item<ItemStack> stackItem = getItemInHand(InteractionHand.MAIN_HAND);
Object itemStack = stackItem == null ? Reflections.instance$ItemStack$EMPTY : stackItem.getLiteralObject();
Object blockPos = LocationUtils.toBlockPos(pos);
try {
Object blockInWorld = Reflections.constructor$BlockInWorld.newInstance(level().serverWorld(), blockPos, false);
if (VersionHelper.isVersionNewerThan1_20_5()) {
if (Reflections.method$ItemStack$canBreakBlockInAdventureMode != null && !(boolean) Reflections.method$ItemStack$canBreakBlockInAdventureMode.invoke(itemStack, blockInWorld)) {
return false;
}
} else {
if (Reflections.method$ItemStack$canDestroy != null && !(boolean) Reflections.method$ItemStack$canDestroy.invoke(itemStack, Reflections.instance$BuiltInRegistries$BLOCK, blockInWorld)) {
return false;
}
}
} catch (ReflectiveOperationException e) {
this.plugin.logger().warn("Failed to run canBreak check", e);
return false;
}
return true;
public boolean canBreak(BlockPos pos, @Nullable Object state) {
return AdventureModeUtils.canBreak(platformPlayer().getInventory().getItemInMainHand(), new Location(platformPlayer().getWorld(), pos.x(), pos.y(), pos.z()), state);
}
public boolean canBreak(BlockPos pos, Object state) {
Item<ItemStack> stackItem = getItemInHand(InteractionHand.MAIN_HAND);
Object itemStack = stackItem == null ? Reflections.instance$ItemStack$EMPTY : stackItem.getLiteralObject();
Object blockPos = LocationUtils.toBlockPos(pos);
try {
Object blockInWorld = Reflections.constructor$BlockInWorld.newInstance(level().serverWorld(), blockPos, false);
Reflections.field$BlockInWorld$state.set(blockInWorld, state);
if (VersionHelper.isVersionNewerThan1_20_5()) {
if (Reflections.method$ItemStack$canBreakBlockInAdventureMode != null && !(boolean) Reflections.method$ItemStack$canBreakBlockInAdventureMode.invoke(itemStack, blockInWorld)) {
return false;
}
} else {
if (Reflections.method$ItemStack$canDestroy != null && !(boolean) Reflections.method$ItemStack$canDestroy.invoke(itemStack, Reflections.instance$BuiltInRegistries$BLOCK, blockInWorld)) {
return false;
}
}
} catch (ReflectiveOperationException e) {
this.plugin.logger().warn("Failed to run canBreak check", e);
return false;
}
return true;
@Override
public boolean canPlace(BlockPos pos, @Nullable Object state) {
return AdventureModeUtils.canPlace(platformPlayer().getInventory().getItemInMainHand(), new Location(platformPlayer().getWorld(), pos.x(), pos.y(), pos.z()), state);
}
@Override
@@ -569,7 +533,7 @@ public class BukkitServerPlayer extends Player {
// can break now
if (this.miningProgress >= 1f) {
// for simplified adventure break, switch mayBuild temporarily
if (isAdventureMode() && Config.simplifyAdventureCheck()) {
if (isAdventureMode() && Config.simplifyAdventureBreakCheck()) {
// check the appearance state
if (canBreak(hitPos, customState.vanillaBlockState().handle())) {
// Error might occur so we use try here

View File

@@ -0,0 +1,62 @@
package net.momirealms.craftengine.bukkit.util;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.World;
import org.bukkit.Location;
import org.bukkit.inventory.ItemStack;
@SuppressWarnings("DuplicatedCode")
public class AdventureModeUtils {
private AdventureModeUtils() {}
public static boolean canBreak(ItemStack itemStack, Location pos) {
return canPlace(itemStack, pos, null);
}
public static boolean canBreak(ItemStack itemStack, Location pos, Object state) {
Object blockPos = LocationUtils.toBlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ());
Object blockInWorld = FastNMS.INSTANCE.constructor$BlockInWorld(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(pos.getWorld()), blockPos, false);
if (state != null) {
try {
Reflections.field$BlockInWorld$state.set(blockInWorld, state);
} catch (ReflectiveOperationException e) {
CraftEngine.instance().logger().warn("Failed to set field$BlockInWorld$state", e);
return false;
}
}
return FastNMS.INSTANCE.canBreakInAdventureMode(FastNMS.INSTANCE.field$CraftItemStack$handle(itemStack), blockInWorld);
}
public static boolean canPlace(Item<?> itemStack, World world, BlockPos pos, Object state) {
Object blockPos = LocationUtils.toBlockPos(pos);
Object item = itemStack == null ? Reflections.instance$ItemStack$Air : itemStack.getLiteralObject();
Object blockInWorld = FastNMS.INSTANCE.constructor$BlockInWorld(FastNMS.INSTANCE.field$CraftWorld$ServerLevel((org.bukkit.World) world.platformWorld()), blockPos, false);
if (state != null) {
try {
Reflections.field$BlockInWorld$state.set(blockInWorld, state);
} catch (ReflectiveOperationException e) {
CraftEngine.instance().logger().warn("Failed to set field$BlockInWorld$state", e);
return false;
}
}
return FastNMS.INSTANCE.canPlaceInAdventureMode(item, blockInWorld);
}
public static boolean canPlace(ItemStack itemStack, Location pos, Object state) {
Object blockPos = LocationUtils.toBlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ());
Object blockInWorld = FastNMS.INSTANCE.constructor$BlockInWorld(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(pos.getWorld()), blockPos, false);
if (state != null) {
try {
Reflections.field$BlockInWorld$state.set(blockInWorld, state);
} catch (ReflectiveOperationException e) {
CraftEngine.instance().logger().warn("Failed to set field$BlockInWorld$state", e);
return false;
}
}
return FastNMS.INSTANCE.canPlaceInAdventureMode(FastNMS.INSTANCE.field$CraftItemStack$handle(itemStack), blockInWorld);
}
}

View File

@@ -14,17 +14,12 @@ import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.core.util.ReflectionUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.World;
import org.bukkit.block.BlockState;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.block.BlockPhysicsEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.inventory.*;
import org.jetbrains.annotations.Nullable;
import sun.misc.Unsafe;
@@ -5780,24 +5775,24 @@ public class Reflections {
)
);
public static final Constructor<?> constructor$BlockInWorld = requireNonNull(
ReflectionUtils.getConstructor(
clazz$BlockInWorld, 0
)
);
// public static final Constructor<?> constructor$BlockInWorld = requireNonNull(
// ReflectionUtils.getConstructor(
// clazz$BlockInWorld, 0
// )
// );
// 1.20.5+
public static final Method method$ItemStack$canBreakBlockInAdventureMode =
ReflectionUtils.getMethod(
clazz$ItemStack, new String[]{"canBreakBlockInAdventureMode"}, clazz$BlockInWorld
);
// // 1.20.5+
// public static final Method method$ItemStack$canBreakBlockInAdventureMode =
// ReflectionUtils.getMethod(
// clazz$ItemStack, new String[]{"canBreakBlockInAdventureMode"}, clazz$BlockInWorld
// );
// 1.20 ~ 1.20.4
// instance$BuiltInRegistries$BLOCK
public static final Method method$ItemStack$canDestroy =
ReflectionUtils.getMethod(
clazz$ItemStack,new String[]{"b"}, clazz$Registry, clazz$BlockInWorld
);
// // 1.20 ~ 1.20.4
// // instance$BuiltInRegistries$BLOCK
// public static final Method method$ItemStack$canDestroy =
// ReflectionUtils.getMethod(
// clazz$ItemStack,new String[]{"b"}, clazz$Registry, clazz$BlockInWorld
// );
public static final Method method$BlockStateBase$getBlock = requireNonNull(
ReflectionUtils.getMethod(