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:
@@ -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()) {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user