9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-31 04:46:37 +00:00

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

# Conflicts:
#	bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java
This commit is contained in:
jhqwqmc
2025-05-13 19:26:49 +08:00
54 changed files with 700 additions and 564 deletions

View File

@@ -31,7 +31,6 @@ import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigExce
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.registry.Registries;
import net.momirealms.craftengine.core.registry.WritableRegistry;
import net.momirealms.craftengine.core.util.*;
import org.bukkit.Bukkit;
@@ -49,7 +48,6 @@ import java.io.File;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
public class BukkitBlockManager extends AbstractBlockManager {
private static BukkitBlockManager instance;
@@ -252,7 +250,7 @@ public class BukkitBlockManager extends AbstractBlockManager {
for (int i = 0; i < size; i++) {
states[i] = new PackedBlockState(BlockStateUtils.idToBlockState(i), i);
}
BlockRegistryMirror.init(states);
BlockRegistryMirror.init(states, new PackedBlockState(Reflections.instance$Blocks$STONE$defaultState, BlockStateUtils.blockStateToId(Reflections.instance$Blocks$STONE$defaultState)));
}
private void registerEmptyBlock() {
@@ -460,36 +458,8 @@ public class BukkitBlockManager extends AbstractBlockManager {
}
}
Object eventsObj = ResourceConfigUtils.get(section, "events");
EnumMap<EventTrigger, List<Function<PlayerBlockActionContext>>> events = new EnumMap<>(EventTrigger.class);
if (eventsObj instanceof Map<?, ?> eventsSection) {
Map<String, Object> eventsSectionMap = MiscUtils.castToMap(eventsSection, false);
for (Map.Entry<String, Object> eventEntry : eventsSectionMap.entrySet()) {
try {
EventTrigger eventTrigger = EventTrigger.valueOf(eventEntry.getKey().toUpperCase(Locale.ENGLISH));
if (eventEntry.getValue() instanceof List<?> list) {
if (list.size() == 1) {
events.put(eventTrigger, List.of(BlockEventFunctions.fromMap(MiscUtils.castToMap(list.get(0), false))));
} else if (list.size() == 2) {
events.put(eventTrigger, List.of(
BlockEventFunctions.fromMap(MiscUtils.castToMap(list.get(0), false)),
BlockEventFunctions.fromMap(MiscUtils.castToMap(list.get(1), false))
));
} else {
List<Function<PlayerBlockActionContext>> eventsList = new ArrayList<>();
for (Object event : list) {
eventsList.add(BlockEventFunctions.fromMap(MiscUtils.castToMap(event, false)));
}
events.put(eventTrigger, eventsList);
}
} else if (eventEntry.getValue() instanceof Map<?, ?> eventSection) {
events.put(eventTrigger, List.of(BlockEventFunctions.fromMap(MiscUtils.castToMap(eventSection, false))));
}
} catch (IllegalArgumentException e) {
throw new LocalizedResourceConfigException("warning.config.event.invalid_trigger", eventEntry.getKey());
}
}
}
Object eventsObj = ResourceConfigUtils.get(section, "events", "event");
EnumMap<EventTrigger, List<Function<PlayerBlockActionContext>>> events = parseEvents(eventsObj);
Map<String, Object> behaviors = MiscUtils.castToMap(section.getOrDefault("behavior", Map.of()), false);
CustomBlock block = BukkitCustomBlock.builder(id)
@@ -543,6 +513,55 @@ public class BukkitBlockManager extends AbstractBlockManager {
}
}
private EnumMap<EventTrigger, List<Function<PlayerBlockActionContext>>> parseEvents(Object eventsObj) {
EnumMap<EventTrigger, List<Function<PlayerBlockActionContext>>> events = new EnumMap<>(EventTrigger.class);
if (eventsObj instanceof Map<?, ?> eventsSection) {
Map<String, Object> eventsSectionMap = MiscUtils.castToMap(eventsSection, false);
for (Map.Entry<String, Object> eventEntry : eventsSectionMap.entrySet()) {
try {
EventTrigger eventTrigger = EventTrigger.byName(eventEntry.getKey());
if (eventEntry.getValue() instanceof List<?> list) {
if (list.size() == 1) {
events.put(eventTrigger, List.of(BlockEventFunctions.fromMap(MiscUtils.castToMap(list.get(0), false))));
} else if (list.size() == 2) {
events.put(eventTrigger, List.of(
BlockEventFunctions.fromMap(MiscUtils.castToMap(list.get(0), false)),
BlockEventFunctions.fromMap(MiscUtils.castToMap(list.get(1), false))
));
} else {
List<Function<PlayerBlockActionContext>> eventsList = new ArrayList<>();
for (Object event : list) {
eventsList.add(BlockEventFunctions.fromMap(MiscUtils.castToMap(event, false)));
}
events.put(eventTrigger, eventsList);
}
} else if (eventEntry.getValue() instanceof Map<?, ?> eventSection) {
events.put(eventTrigger, List.of(BlockEventFunctions.fromMap(MiscUtils.castToMap(eventSection, false))));
}
} catch (IllegalArgumentException e) {
throw new LocalizedResourceConfigException("warning.config.event.invalid_trigger", eventEntry.getKey());
}
}
} else if (eventsObj instanceof List<?> list) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> eventsList = (List<Map<String, Object>>) list;
for (Map<String, Object> eventSection : eventsList) {
Object onObj = eventSection.get("on");
if (onObj == null) {
throw new LocalizedResourceConfigException("warning.config.event.missing_trigger");
}
try {
EventTrigger eventTrigger = EventTrigger.byName(onObj.toString());
Function<PlayerBlockActionContext> function = BlockEventFunctions.fromMap(eventSection);
events.computeIfAbsent(eventTrigger, k -> new ArrayList<>(4)).add(function);
} catch (IllegalArgumentException e) {
throw new LocalizedResourceConfigException("warning.config.event.invalid_trigger", onObj.toString());
}
}
}
return events;
}
private Map<String, Property<?>> parseProperties(Map<String, Object> propertiesSection) {
Map<String, Property<?>> properties = new HashMap<>();
for (Map.Entry<String, Object> entry : propertiesSection.entrySet()) {

View File

@@ -89,7 +89,7 @@ public class AxeItemBehavior extends ItemBehavior {
bukkitPlayer.setStatistic(Statistic.USE_ITEM, material, bukkitPlayer.getStatistic(Statistic.USE_ITEM, material) + 1);
// resend swing if it's not interactable on client side
if (!InteractUtils.isInteractable(BlockStateUtils.getBlockOwnerIdFromState(state.vanillaBlockState().handle()),
if (!InteractUtils.isInteractable(
bukkitPlayer, BlockStateUtils.fromBlockData(state.vanillaBlockState().handle()),
context.getHitResult(), item
) || player.isSecondaryUseActive()) {

View File

@@ -1,5 +1,7 @@
package net.momirealms.craftengine.bukkit.item.behavior;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks;
import net.momirealms.craftengine.bukkit.api.event.CustomBlockAttemptPlaceEvent;
import net.momirealms.craftengine.bukkit.api.event.CustomBlockPlaceEvent;
@@ -21,6 +23,7 @@ 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.plugin.locale.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.util.Direction;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.world.BlockPos;
@@ -38,7 +41,6 @@ import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import javax.annotation.Nullable;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
@@ -57,34 +59,32 @@ public class BlockItemBehavior extends ItemBehavior {
}
public InteractionResult place(BlockPlaceContext context) {
if (!context.canPlace()) {
return InteractionResult.FAIL;
}
Optional<CustomBlock> optionalBlock = BukkitBlockManager.instance().blockById(this.blockId);
if (optionalBlock.isEmpty()) {
CraftEngine.instance().logger().warn("Failed to place unknown block " + this.blockId);
return InteractionResult.FAIL;
}
CustomBlock block = optionalBlock.get();
BlockPlaceContext placeContext = updatePlacementContext(context);
if (placeContext == null) {
if (!context.canPlace()) {
return InteractionResult.FAIL;
}
ImmutableBlockState blockStateToPlace = getPlacementState(placeContext, block);
CustomBlock block = optionalBlock.get();
BlockPos pos = context.getClickedPos();
int maxY = context.getLevel().worldHeight().getMaxBuildHeight() - 1;
if (context.getClickedFace() == Direction.UP && pos.y() >= maxY) {
context.getPlayer().sendActionBar(Component.translatable("build.tooHigh").arguments(Component.text(maxY)).color(NamedTextColor.RED));
return InteractionResult.FAIL;
}
ImmutableBlockState blockStateToPlace = getPlacementState(context, block);
if (blockStateToPlace == null) {
return InteractionResult.FAIL;
}
Player player = placeContext.getPlayer();
BlockPos pos = placeContext.getClickedPos();
BlockPos againstPos = placeContext.getAgainstPos();
World world = (World) placeContext.getLevel().platformWorld();
Player player = context.getPlayer();
BlockPos againstPos = context.getAgainstPos();
World world = (World) context.getLevel().platformWorld();
Location placeLocation = new Location(world, pos.x(), pos.y(), pos.z());
int gameTicks = player.gameTicks();
if (!player.updateLastSuccessfulInteractionTick(gameTicks)) {
return InteractionResult.FAIL;
}
Block bukkitBlock = world.getBlockAt(placeLocation);
Block againstBlock = world.getBlockAt(againstPos.x(), againstPos.y(), againstPos.z());
org.bukkit.entity.Player bukkitPlayer = (org.bukkit.entity.Player) player.platformPlayer();
@@ -105,6 +105,11 @@ public class BlockItemBehavior extends ItemBehavior {
}
}
int gameTicks = player.gameTicks();
if (!player.updateLastSuccessfulInteractionTick(gameTicks)) {
return InteractionResult.FAIL;
}
// trigger event
CustomBlockAttemptPlaceEvent attemptPlaceEvent = new CustomBlockAttemptPlaceEvent(bukkitPlayer, placeLocation.clone(), blockStateToPlace,
DirectionUtils.toBlockFace(context.getClickedFace()), bukkitBlock, context.getHand());
@@ -117,7 +122,7 @@ public class BlockItemBehavior extends ItemBehavior {
// place custom block
CraftEngineBlocks.place(placeLocation, blockStateToPlace, UpdateOption.UPDATE_ALL_IMMEDIATE, false);
// call bukkit event
BlockPlaceEvent bukkitPlaceEvent = new BlockPlaceEvent(bukkitBlock, previousState, againstBlock, (ItemStack) placeContext.getItem().getItem(), bukkitPlayer, true, context.getHand() == InteractionHand.MAIN_HAND ? EquipmentSlot.HAND : EquipmentSlot.OFF_HAND);
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);
@@ -133,23 +138,17 @@ public class BlockItemBehavior extends ItemBehavior {
}
if (!player.isCreativeMode()) {
Item<?> item = placeContext.getItem();
Item<?> item = context.getItem();
item.count(item.count() - 1);
item.load();
}
player.swingHand(placeContext.getHand());
placeContext.getLevel().playBlockSound(new Vec3d(pos.x() + 0.5, pos.y() + 0.5, pos.z() + 0.5), blockStateToPlace.sounds().placeSound());
player.swingHand(context.getHand());
context.getLevel().playBlockSound(new Vec3d(pos.x() + 0.5, pos.y() + 0.5, pos.z() + 0.5), blockStateToPlace.sounds().placeSound());
world.sendGameEvent(bukkitPlayer, GameEvent.BLOCK_PLACE, new Vector(pos.x(), pos.y(), pos.z()));
return InteractionResult.SUCCESS;
}
// for child class to override
@Nullable
public BlockPlaceContext updatePlacementContext(BlockPlaceContext context) {
return context;
}
protected ImmutableBlockState getPlacementState(BlockPlaceContext context, CustomBlock block) {
ImmutableBlockState state = block.getStateForPlacement(context);
return state != null && this.canPlace(context, state) ? state : null;

View File

@@ -54,6 +54,7 @@ public abstract class BukkitItemFactory<W extends ItemWrapper<ItemStack>> extend
@Override
protected boolean isBlockItem(W item) {
// todo 这个 isBlockItem 他考虑组件了吗???
return item.getItem().getType().isBlock();
}

View File

@@ -1,13 +1,12 @@
package net.momirealms.craftengine.bukkit.item.listener;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.momirealms.craftengine.bukkit.api.event.CustomBlockInteractEvent;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.item.behavior.BlockItemBehavior;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.*;
import net.momirealms.craftengine.bukkit.world.BukkitBlockInWorld;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
@@ -15,6 +14,11 @@ import net.momirealms.craftengine.core.item.CustomItem;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
import net.momirealms.craftengine.core.item.context.UseOnContext;
import net.momirealms.craftengine.core.plugin.context.ContextHolder;
import net.momirealms.craftengine.core.plugin.context.PlayerBlockActionContext;
import net.momirealms.craftengine.core.plugin.context.parameter.CommonParameters;
import net.momirealms.craftengine.core.plugin.event.EventTrigger;
import net.momirealms.craftengine.core.util.ClickType;
import net.momirealms.craftengine.core.util.Direction;
import net.momirealms.craftengine.core.world.BlockHitResult;
import net.momirealms.craftengine.core.world.BlockPos;
@@ -24,7 +28,6 @@ import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
@@ -44,37 +47,140 @@ public class ItemEventListener implements Listener {
this.plugin = plugin;
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH)
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onInteractBlock(PlayerInteractEvent event) {
Action action = event.getAction();
if (action != Action.LEFT_CLICK_BLOCK && action != Action.RIGHT_CLICK_BLOCK) {
Player player = event.getPlayer();
if (
(action != Action.LEFT_CLICK_BLOCK && action != Action.RIGHT_CLICK_BLOCK) || /* block is required */
(player.getGameMode() == GameMode.SPECTATOR) || /* no spectator interactions */
(action == Action.LEFT_CLICK_BLOCK && player.getGameMode() == GameMode.CREATIVE) /* it's breaking the block */
) {
return;
}
BukkitServerPlayer serverPlayer = this.plugin.adapt(player);
InteractionHand hand = event.getHand() == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND;
// 如果本tick内主手已被处理则不处理副手
// 这是因为客户端可能会同时发主副手交互包,但实际上只能处理其中一个
if (this.cancelEventIfHasInteraction(event, serverPlayer, hand)) {
return;
}
// some common data
Block block = Objects.requireNonNull(event.getClickedBlock());
Object blockState = BlockStateUtils.blockDataToBlockState(block.getBlockData());
BlockData blockData = block.getBlockData();
Object blockState = BlockStateUtils.blockDataToBlockState(blockData);
ImmutableBlockState immutableBlockState = null;
int stateId = BlockStateUtils.blockStateToId(blockState);
if (BlockStateUtils.isVanillaBlock(stateId)) {
return;
// 处理自定义方块
if (!BlockStateUtils.isVanillaBlock(stateId)) {
immutableBlockState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId);
// call the event if it's custom
CustomBlockInteractEvent interactEvent = new CustomBlockInteractEvent(
player,
block.getLocation(),
event.getInteractionPoint(),
immutableBlockState,
block,
event.getBlockFace(),
hand,
action.isRightClick() ? CustomBlockInteractEvent.Action.RIGHT_CLICK : CustomBlockInteractEvent.Action.LEFT_CLICK,
event.getItem()
);
if (EventUtils.fireAndCheckCancel(interactEvent)) {
event.setCancelled(true);
return;
}
// run custom functions
CustomBlock customBlock = immutableBlockState.owner().value();
PlayerBlockActionContext context = PlayerBlockActionContext.of(serverPlayer, new BukkitBlockInWorld(block), ContextHolder.builder()
.withParameter(CommonParameters.BLOCK_STATE, immutableBlockState)
.withParameter(CommonParameters.PLAYER, serverPlayer)
.withParameter(CommonParameters.WORLD, serverPlayer.world())
.withParameter(CommonParameters.LOCATION, new Vec3d(block.getX(), block.getY(), block.getZ()))
.withParameter(CommonParameters.CLICK_TYPE, action.isRightClick() ? ClickType.RIGHT : ClickType.LEFT)
.build());
customBlock.execute(context, EventTrigger.CLICK);
if (action.isRightClick()) customBlock.execute(context, EventTrigger.RIGHT_CLICK);
else customBlock.execute(context, EventTrigger.LEFT_CLICK);
}
// it's breaking the block
if (action == Action.LEFT_CLICK_BLOCK && event.getPlayer().getGameMode() == GameMode.CREATIVE) {
return;
}
Item<ItemStack> itemInHand = serverPlayer.getItemInHand(hand);
Optional<CustomItem<ItemStack>> optionalCustomItem = itemInHand == null ? Optional.empty() : itemInHand.getCustomItem();
boolean hasItem = itemInHand != null;
boolean hasCustomItem = optionalCustomItem.isPresent();
CustomBlockInteractEvent interactEvent = new CustomBlockInteractEvent(
event.getPlayer(),
block.getLocation(),
event.getInteractionPoint(),
BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId),
block,
event.getBlockFace(),
event.getHand() == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND,
action.isRightClick() ? CustomBlockInteractEvent.Action.RIGHT_CLICK : CustomBlockInteractEvent.Action.LEFT_CLICK,
event.getItem()
);
if (EventUtils.fireAndCheckCancel(interactEvent)) {
event.setCancelled(true);
// interact block with items
if (hasItem && action == Action.RIGHT_CLICK_BLOCK) {
Location interactionPoint = Objects.requireNonNull(event.getInteractionPoint(), "interaction point should not be null");
Direction direction = DirectionUtils.toDirection(event.getBlockFace());
BlockPos pos = LocationUtils.toBlockPos(block.getLocation());
Vec3d vec3d = new Vec3d(interactionPoint.getX(), interactionPoint.getY(), interactionPoint.getZ());
BlockHitResult hitResult = new BlockHitResult(vec3d, direction, pos, false);
// handle block item
if (itemInHand.isBlockItem()) {
// vanilla item
if (!hasCustomItem) {
// interact a custom block
if (immutableBlockState != null) {
// client won't have sounds if the clientside block is interactable
// so we should check and resend sounds on BlockPlaceEvent
BlockData craftBlockData = BlockStateUtils.fromBlockData(immutableBlockState.vanillaBlockState().handle());
if (InteractUtils.isInteractable(player, craftBlockData, hitResult, itemInHand)) {
if (!serverPlayer.isSecondaryUseActive()) {
serverPlayer.setResendSound();
}
} else {
if (BlockStateUtils.isReplaceable(immutableBlockState.customBlockState().handle()) && !BlockStateUtils.isReplaceable(immutableBlockState.vanillaBlockState().handle())) {
serverPlayer.setResendSwing();
}
}
}
}
// custom item
else {
if (optionalCustomItem.get().settings().canPlaceRelatedVanillaBlock()) {
// 如果用户设置了允许放置对应的原版方块,那么直接返回。
// 这种情况下最好是return以避免同时触发多个behavior发生冲突
// 当用户选择其作为原版方块放下时,自定义行为可能已经不重要了?
return;
} else {
// todo 实际上这里的处理并不正确,因为判断玩家是否能够放置那个方块需要更加细节的判断。比如玩家无法对着树叶放置火把,但是交互事件依然触发,此情况下不可丢弃自定义行为。
if (serverPlayer.isSecondaryUseActive() || !InteractUtils.isInteractable(player, blockData, hitResult, itemInHand)) {
event.setCancelled(true);
}
}
}
}
// 检查其他的物品行为,物品行为理论只在交互时处理
Optional<List<ItemBehavior>> optionalItemBehaviors = itemInHand.getItemBehavior();
// 物品类型是否包含自定义物品行为,行为不一定来自于自定义物品,部分原版物品也包含了新的行为
if (optionalItemBehaviors.isPresent()) {
// 检测是否可交互应当只判断原版方块,因为自定义方块早就判断过了,如果可交互不可能到这一步
boolean interactable = immutableBlockState == null && InteractUtils.isInteractable(player, blockData, hitResult, itemInHand);
// 如果方块可交互但是玩家没shift那么原版的方块交互优先取消自定义物品的behavior
// todo 如果我的物品行为允许某些交互呢?是否值得进一步处理?
if (!serverPlayer.isSecondaryUseActive() && interactable) {
return;
}
// 依次执行物品行为
for (ItemBehavior itemBehavior : optionalItemBehaviors.get()) {
InteractionResult result = itemBehavior.useOnBlock(new UseOnContext(serverPlayer, hand, hitResult));
if (result == InteractionResult.SUCCESS_AND_CANCEL) {
event.setCancelled(true);
return;
}
// 非pass的情况直接结束
if (result != InteractionResult.PASS) {
return;
}
}
}
}
}
@@ -112,109 +218,6 @@ public class ItemEventListener implements Listener {
}
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onInteractAtBlock(PlayerInteractEvent event) {
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
if (event.useItemInHand() == Event.Result.DENY || event.useInteractedBlock() == Event.Result.DENY) return;
Location interactionPoint = event.getInteractionPoint();
if (interactionPoint == null) return;
Player bukkitPlayer = event.getPlayer();
Block clickedBlock = Objects.requireNonNull(event.getClickedBlock());
BukkitServerPlayer player = this.plugin.adapt(bukkitPlayer);
InteractionHand hand = event.getHand() == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND;
if (cancelEventIfHasInteraction(event, player, hand)) {
return;
}
// Gets the item in hand
Item<ItemStack> itemInHand = player.getItemInHand(hand);
if (itemInHand == null) return;
Optional<List<ItemBehavior>> optionalItemBehaviors = itemInHand.getItemBehavior();
// has custom item behavior
if (optionalItemBehaviors.isPresent()) {
BlockPos pos = LocationUtils.toBlockPos(clickedBlock.getLocation());
Vec3d vec3d = new Vec3d(interactionPoint.getX(), interactionPoint.getY(), interactionPoint.getZ());
Direction direction = DirectionUtils.toDirection(event.getBlockFace());
BlockHitResult hitResult = new BlockHitResult(vec3d, direction, pos, false);
boolean interactable = InteractUtils.isInteractable(BlockStateUtils.getBlockOwnerId(clickedBlock), bukkitPlayer, clickedBlock.getBlockData(), hitResult, itemInHand);
// do not allow to place block if it's a vanilla block
Optional<CustomItem<ItemStack>> optionalCustomItem = itemInHand.getCustomItem();
if (itemInHand.isBlockItem() && optionalCustomItem.isPresent()) {
// it's a custom item, but now it's ignored
if (optionalCustomItem.get().settings().canPlaceRelatedVanillaBlock()) {
return;
}
if (!interactable || player.isSecondaryUseActive()) {
event.setCancelled(true);
}
}
if (!player.isSecondaryUseActive() && interactable) {
// if it's interactable on server, cancel the custom behaviors
return;
}
// no spectator interactions
if (player.isSpectatorMode()) {
return;
}
for (ItemBehavior itemBehavior : optionalItemBehaviors.get()) {
InteractionResult result = itemBehavior.useOnBlock(new UseOnContext(player, hand, hitResult));
if (result == InteractionResult.SUCCESS_AND_CANCEL) {
event.setCancelled(true);
return;
}
int maxY = player.world().worldHeight().getMaxBuildHeight() - 1;
if (direction == Direction.UP
&& result != InteractionResult.SUCCESS
&& pos.y() >= maxY
&& itemBehavior instanceof BlockItemBehavior
) {
player.sendActionBar(Component.translatable("build.tooHigh").arguments(Component.text(maxY)).color(NamedTextColor.RED));
return;
}
if (result != InteractionResult.PASS) {
return;
}
}
return;
}
// it's a vanilla block
if (itemInHand.isBlockItem() && !itemInHand.isCustomItem()) {
// client won't have sounds if the fake block is interactable
// so we should check and resend sounds on interact
Object blockState = BlockStateUtils.blockDataToBlockState(clickedBlock.getBlockData());
int stateId = BlockStateUtils.blockStateToId(blockState);
ImmutableBlockState againstCustomBlock = BukkitBlockManager.instance().getImmutableBlockState(stateId);
if (againstCustomBlock == null || againstCustomBlock.isEmpty()) {
return;
}
BlockPos pos = LocationUtils.toBlockPos(clickedBlock.getLocation());
Vec3d vec3d = new Vec3d(interactionPoint.getX(), interactionPoint.getY(), interactionPoint.getZ());
Direction direction = DirectionUtils.toDirection(event.getBlockFace());
BlockHitResult hitResult = new BlockHitResult(vec3d, direction, pos, false);
try {
BlockData craftBlockData = BlockStateUtils.fromBlockData(againstCustomBlock.vanillaBlockState().handle());
if (InteractUtils.isInteractable(KeyUtils.namespacedKey2Key(craftBlockData.getMaterial().getKey()), bukkitPlayer, craftBlockData, hitResult, itemInHand)) {
if (!player.isSecondaryUseActive()) {
player.setResendSound();
}
} else {
if (BlockStateUtils.isReplaceable(againstCustomBlock.customBlockState().handle()) && !BlockStateUtils.isReplaceable(againstCustomBlock.vanillaBlockState().handle())) {
player.setResendSwing();
}
}
} catch (ReflectiveOperationException e) {
plugin.logger().warn("Failed to get CraftBlockData", e);
}
}
}
private boolean cancelEventIfHasInteraction(PlayerInteractEvent event, BukkitServerPlayer player, InteractionHand hand) {
if (hand == InteractionHand.OFF_HAND) {
int currentTicks = player.gameTicks();

View File

@@ -0,0 +1,38 @@
package net.momirealms.craftengine.bukkit.plugin.classpath;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.plugin.Plugin;
import net.momirealms.craftengine.core.plugin.classpath.ClassPathAppender;
import net.momirealms.craftengine.core.plugin.classpath.URLClassLoaderAccess;
import java.net.MalformedURLException;
import java.net.URLClassLoader;
import java.nio.file.Path;
public class BukkitClassPathAppender implements ClassPathAppender {
private final URLClassLoaderAccess classLoaderAccess;
public BukkitClassPathAppender(ClassLoader classLoader) throws IllegalAccessException {
if (Reflections.clazz$PaperPluginClassLoader != null && Reflections.clazz$PaperPluginClassLoader.isInstance(classLoader)) {
URLClassLoader libraryClassLoader = (URLClassLoader) Reflections.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");
}
}
public BukkitClassPathAppender(Plugin plugin) throws IllegalAccessException {
this(plugin.getClass().getClassLoader());
}
@Override
public void addJarToClasspath(Path file) {
try {
this.classLoaderAccess.addURL(file.toUri().toURL());
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -1,7 +1,6 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.bukkit.util.BlockTags;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import org.bukkit.Material;

View File

@@ -317,7 +317,6 @@ public class BukkitServerPlayer extends Player {
public void tick() {
// not fully online
if (serverPlayer() == null) return;
if (VersionHelper.isFolia()) {
try {
Object serverPlayer = serverPlayer();
@@ -329,7 +328,7 @@ public class BukkitServerPlayer extends Player {
} else {
this.gameTicks = FastNMS.INSTANCE.field$MinecraftServer$currentTick();
}
if (this.gameTicks % 15 == 0) {
if (this.gameTicks % 30 == 0) {
this.updateGUI();
}
if (this.isDestroyingBlock) {

View File

@@ -101,8 +101,11 @@ public class BlockStateUtils {
}
public static Key getBlockOwnerId(Block block) {
BlockData data = block.getBlockData();
Object blockState = blockDataToBlockState(data);
return getBlockOwnerId(block.getBlockData());
}
public static Key getBlockOwnerId(BlockData block) {
Object blockState = blockDataToBlockState(block);
return getBlockOwnerIdFromState(blockState);
}
@@ -188,8 +191,12 @@ public class BlockStateUtils {
Reflections.field$BlockStateBase$replaceable.set(state, replaceable);
}
public static boolean isReplaceable(Object state) throws ReflectiveOperationException {
return (boolean) Reflections.field$BlockStateBase$replaceable.get(state);
public static boolean isReplaceable(Object state) {
try {
return (boolean) Reflections.field$BlockStateBase$replaceable.get(state);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to get replaceable property", e);
}
}
public static void setCanOcclude(Object state, boolean canOcclude) throws ReflectiveOperationException {

View File

@@ -273,9 +273,10 @@ public class InteractUtils {
}
}
public static boolean isInteractable(Key block, org.bukkit.entity.Player player, BlockData state, BlockHitResult hit, Item<ItemStack> item) {
if (INTERACTIONS.containsKey(block)) {
return INTERACTIONS.get(block).apply(player, item, state, hit);
public static boolean isInteractable(Player player, BlockData state, BlockHitResult hit, Item<ItemStack> item) {
Key blockType = BlockStateUtils.getBlockOwnerId(state);
if (INTERACTIONS.containsKey(blockType)) {
return INTERACTIONS.get(blockType).apply(player, item, state, hit);
} else {
return false;
}

View File

@@ -27,6 +27,7 @@ import sun.misc.Unsafe;
import java.io.BufferedReader;
import java.lang.invoke.VarHandle;
import java.lang.reflect.*;
import java.net.URLClassLoader;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.CompletableFuture;
@@ -6688,6 +6689,14 @@ public class Reflections {
.map(clazz -> ReflectionUtils.getTheOnlyConstructor(clazz))
.orElse(null);
public static final Class<?> clazz$PaperPluginClassLoader = ReflectionUtils.getClazz(
"io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader"
);
public static final Field field$PaperPluginClassLoader$libraryLoader = Optional.ofNullable(clazz$PaperPluginClassLoader)
.map(it -> ReflectionUtils.getDeclaredField(it, URLClassLoader.class, 0))
.orElse(null);
public static final Method method$SoundSource$values = requireNonNull(
ReflectionUtils.getStaticMethod(
clazz$SoundSource, clazz$SoundSource.arrayType()

View File

@@ -5,7 +5,6 @@ import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.core.util.FriendlyByteBuf;
import net.momirealms.craftengine.core.util.Key;
import java.util.ArrayList;
import java.util.HashMap;