9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-29 11:59:11 +00:00

Implement Strippable Block

This commit is contained in:
XiaoMoMi
2025-02-14 18:34:41 +08:00
parent 708595399b
commit 80ca2d18d9
22 changed files with 426 additions and 142 deletions

View File

@@ -54,7 +54,7 @@ blocks:
loot:
template: loot_table:normal
arguments:
item: default:palm_log
item: default:stripped_palm_log
settings:
template: block_settings:log
overrides:
@@ -107,7 +107,7 @@ blocks:
loot:
template: loot_table:normal
arguments:
item: default:palm_wood
item: default:stripped_palm_wood
settings:
template: block_settings:log
overrides:

View File

@@ -11,6 +11,7 @@ import net.momirealms.craftengine.core.item.modifier.ItemModifier;
import net.momirealms.craftengine.core.util.Key;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@@ -70,7 +71,7 @@ public class BukkitCustomItem implements CustomItem<ItemStack> {
}
@Override
public ItemBehavior behavior() {
public @NotNull ItemBehavior behavior() {
return this.behavior;
}

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.bukkit.item;
import net.momirealms.craftengine.bukkit.item.behavior.AxeItemBehavior;
import net.momirealms.craftengine.bukkit.item.factory.BukkitItemFactory;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.util.ItemUtils;
@@ -7,6 +8,7 @@ import net.momirealms.craftengine.bukkit.util.MaterialUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.*;
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
import net.momirealms.craftengine.core.item.behavior.ItemBehaviors;
import net.momirealms.craftengine.core.item.modifier.CustomModelDataModifier;
import net.momirealms.craftengine.core.item.modifier.IdModifier;
@@ -35,12 +37,25 @@ import java.nio.file.Path;
import java.util.*;
public class BukkitItemManager extends AbstractItemManager<ItemStack> {
private static final Map<Key, ItemBehavior> VANILLA_ITEM_EXTRA_BEHAVIORS = new HashMap<>();
static {
registerVanillaItemExtraBehavior(AxeItemBehavior.INSTANCE, ItemKeys.WOODEN_AXE, ItemKeys.STONE_AXE, ItemKeys.IRON_AXE, ItemKeys.GOLDEN_AXE, ItemKeys.DIAMOND_AXE, ItemKeys.NETHERITE_AXE);
}
private static void registerVanillaItemExtraBehavior(ItemBehavior behavior, Key... items) {
for (Key key : items) {
VANILLA_ITEM_EXTRA_BEHAVIORS.put(key, behavior);
}
}
private static BukkitItemManager instance;
private final BukkitItemFactory factory;
private final Map<Key, TreeSet<LegacyOverridesModel>> legacyOverrides;
private final Map<Key, TreeMap<Integer, ItemModel>> modernOverrides;
private final BukkitCraftEngine plugin;
private final ItemEventListener itemEventListener;
private final DebugStickListener debugStickListener;
private final Map<Key, List<Holder<Key>>> vanillaItemTags;
private final Map<Key, List<Holder<Key>>> customItemTags;
private final Map<Key, Map<Integer, Key>> cmdConflictChecker;
@@ -55,6 +70,7 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
this.customItemTags = new HashMap<>();
this.cmdConflictChecker = new HashMap<>();
this.itemEventListener = new ItemEventListener(plugin);
this.debugStickListener = new DebugStickListener(plugin);
this.registerAllVanillaItems();
instance = this;
}
@@ -100,6 +116,7 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
public void load() {
super.load();
Bukkit.getPluginManager().registerEvents(this.itemEventListener, plugin.bootstrap());
Bukkit.getPluginManager().registerEvents(this.debugStickListener, plugin.bootstrap());
}
@Override
@@ -109,6 +126,7 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
this.modernOverrides.clear();
this.customItemTags.clear();
HandlerList.unregisterAll(this.itemEventListener);
HandlerList.unregisterAll(this.debugStickListener);
this.cmdConflictChecker.clear();
}
@@ -168,6 +186,28 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
return wrapped.id();
}
@Override
public Optional<List<ItemBehavior>> getItemBehavior(Key key) {
Optional<CustomItem<ItemStack>> customItemOptional = getCustomItem(key);
if (customItemOptional.isPresent()) {
CustomItem<ItemStack> customItem = customItemOptional.get();
Key vanillaMaterial = customItem.material();
ItemBehavior behavior = VANILLA_ITEM_EXTRA_BEHAVIORS.get(vanillaMaterial);
if (behavior != null) {
return Optional.of(List.of(behavior, customItem.behavior()));
} else {
return Optional.of(List.of(customItem.behavior()));
}
} else {
ItemBehavior behavior = VANILLA_ITEM_EXTRA_BEHAVIORS.get(key);
if (behavior != null) {
return Optional.of(List.of(behavior));
} else {
return Optional.empty();
}
}
}
@Override
public Collection<Key> items() {
return new ArrayList<>(customItems.keySet());

View File

@@ -0,0 +1,127 @@
package net.momirealms.craftengine.bukkit.item;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.ComponentUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
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.properties.Property;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.util.MCUtils;
import net.momirealms.craftengine.core.util.MiscUtils;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class DebugStickListener implements Listener {
private final BukkitCraftEngine plugin;
public DebugStickListener(BukkitCraftEngine plugin) {
this.plugin = plugin;
}
@EventHandler(ignoreCancelled = true)
public void onUseDebugStick(PlayerInteractEvent event) {
Block clickedBlock = event.getClickedBlock();
if (clickedBlock == null) return;
ItemStack itemInHand = event.getItem();
if (itemInHand == null) return;
Material material = itemInHand.getType();
if (material != Material.DEBUG_STICK) return;
Player bukkitPlayer = event.getPlayer();
BukkitServerPlayer player = this.plugin.adapt(bukkitPlayer);
if (!(player.canInstabuild() && player.hasPermission("minecraft.debugstick")) && !player.hasPermission("minecraft.debugstick.always")) {
return;
}
if (event.getHand() == EquipmentSlot.OFF_HAND) {
int currentTicks = player.gameTicks();
if (!player.updateLastSuccessfulInteractionTick(currentTicks)) {
event.setCancelled(true);
return;
}
}
Object blockState = BlockStateUtils.blockDataToBlockState(clickedBlock.getBlockData());
int stateId = BlockStateUtils.blockStateToId(blockState);
if (!BlockStateUtils.isVanillaBlock(stateId)) {
event.setCancelled(true);
boolean update = event.getAction() == Action.RIGHT_CLICK_BLOCK;
ImmutableBlockState clickedCustomBlock = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId);
CustomBlock block = clickedCustomBlock.owner().value();
Collection<Property<?>> properties = block.properties();
String blockId = block.id().toString();
try {
if (properties.isEmpty()) {
Object systemChatPacket = Reflections.constructor$ClientboundSystemChatPacket.newInstance(
ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.empty").arguments(Component.text(blockId))), true);
player.sendPacket(systemChatPacket, false);
} else {
Item<ItemStack> wrapped = BukkitItemManager.instance().wrap(itemInHand);
Object storedData = wrapped.getTag("craftengine:debug_stick_state");
if (storedData == null) storedData = new HashMap<>();
if (storedData instanceof Map<?,?> map) {
Map<String, Object> data = MiscUtils.castToMap(map, false);
String currentPropertyName = (String) data.get(blockId);
Property<?> currentProperty = block.getProperty(currentPropertyName);
if (currentProperty == null) {
currentProperty = properties.iterator().next();
}
if (update) {
ImmutableBlockState nextState = cycleState(clickedCustomBlock, currentProperty, player.isSecondaryUseActive());
CraftEngineBlocks.place(clickedBlock.getLocation(), nextState, new UpdateOption.Builder().updateClients().updateKnownShape().build());
Object systemChatPacket = Reflections.constructor$ClientboundSystemChatPacket.newInstance(
ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.update")
.arguments(
Component.text(currentProperty.name()),
Component.text(getNameHelper(nextState, currentProperty))
)), true);
player.sendPacket(systemChatPacket, false);
} else {
currentProperty = getRelative(properties, currentProperty, player.isSecondaryUseActive());
data.put(blockId, currentProperty.name());
wrapped.setTag(data, "craftengine:debug_stick_state");
wrapped.load();
Object systemChatPacket = Reflections.constructor$ClientboundSystemChatPacket.newInstance(
ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.select")
.arguments(
Component.text(currentProperty.name()),
Component.text(getNameHelper(clickedCustomBlock, currentProperty))
)), true);
player.sendPacket(systemChatPacket, false);
}
}
}
} catch (ReflectiveOperationException e) {
plugin.logger().warn("Failed to send system chat packet", e);
}
}
}
private static <T extends Comparable<T>> ImmutableBlockState cycleState(ImmutableBlockState state, Property<T> property, boolean inverse) {
return state.with(property, getRelative(property.possibleValues(), state.get(property), inverse));
}
private static <T> T getRelative(Iterable<T> elements, @Nullable T current, boolean inverse) {
return inverse ? MCUtils.findPreviousInIterable(elements, current) : MCUtils.findNextInIterable(elements, current);
}
private static <T extends Comparable<T>> String getNameHelper(ImmutableBlockState state, Property<T> property) {
return property.valueName(state.get(property));
}
}

View File

@@ -2,31 +2,25 @@ package net.momirealms.craftengine.bukkit.item;
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.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.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.UpdateOption;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.entity.player.InteractionHand;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
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.util.Direction;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MCUtils;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.world.BlockHitResult;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Vec3d;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player;
@@ -39,7 +33,6 @@ import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import javax.annotation.Nullable;
import java.util.*;
public class ItemEventListener implements Listener {
@@ -62,26 +55,28 @@ public class ItemEventListener implements Listener {
return;
}
InteractionHand hand = event.getHand() == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND;
// it's breaking the block
if (action == Action.LEFT_CLICK_BLOCK && event.getPlayer().getGameMode() == GameMode.CREATIVE) {
return;
}
CustomBlockInteractEvent interactEvent = new CustomBlockInteractEvent(
event.getPlayer(),
block.getLocation(),
event.getInteractionPoint(),
BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId),
hand,
event.getHand() == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND,
action == Action.RIGHT_CLICK_BLOCK ? CustomBlockInteractEvent.Action.RIGHT_CLICK : CustomBlockInteractEvent.Action.LEFT_CLICK
);
if (EventUtils.fireAndCheckCancel(interactEvent)) {
event.setCancelled(true);
return;
}
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onInteractAtBlock(PlayerInteractEvent event) {
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
if (event.useItemInHand() == Event.Result.DENY) return;
if (event.useInteractedBlock() == Event.Result.DENY) 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();
@@ -90,49 +85,58 @@ public class ItemEventListener implements Listener {
InteractionHand hand = event.getHand() == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND;
if (hand == InteractionHand.OFF_HAND) {
int currentTicks = player.gameTicks();
// The client will send multiple packets to the server if the client thinks it should
// However, if the main hand item interaction is successful, the off-hand item should be blocked.
if (!player.updateLastSuccessfulInteractionTick(currentTicks)) {
event.setCancelled(true);
return;
}
}
// Gets the item in hand
Item<ItemStack> itemInHand = player.getItemInHand(hand);
if (itemInHand == null) return;
Optional<CustomItem<ItemStack>> customItem = itemInHand.getCustomItem();
Optional<List<ItemBehavior>> optionalItemBehaviors = itemInHand.getItemBehavior();
Material material = itemInHand.getItem().getType();
// is custom item
if (customItem.isPresent()) {
if (material.isBlock()) {
// has custom item behavior
if (optionalItemBehaviors.isPresent()) {
// do not allow to place block if it's a vanilla block
if (itemInHand.isBlockItem() && itemInHand.isCustomItem()) {
event.setCancelled(true);
}
CustomItem<ItemStack> item = customItem.get();
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);
if (!player.isSecondaryUseActive()) {
if (InteractUtils.isInteractable(BlockStateUtils.getRealBlockId(clickedBlock), bukkitPlayer, clickedBlock.getBlockData(), hitResult, itemInHand)) return;
for (ItemBehavior itemBehavior : optionalItemBehaviors.get()) {
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);
if (!player.isSecondaryUseActive()) {
// if it's interactable on server, cancel the behaviors
if (InteractUtils.isInteractable(BlockStateUtils.getBlockOwnerId(clickedBlock), bukkitPlayer, clickedBlock.getBlockData(), hitResult, itemInHand)) return;
}
InteractionResult result = itemBehavior.useOnBlock(new UseOnContext(player, hand, hitResult));
int maxY = player.level().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));
}
if (result != InteractionResult.PASS) {
return;
}
}
InteractionResult result = item.behavior().useOnBlock(new UseOnContext(player, hand, hitResult));
int maxY = player.level().worldHeight().getMaxBuildHeight() - 1;
if (direction == Direction.UP
&& result != InteractionResult.SUCCESS
&& pos.y() >= maxY
&& item.behavior() instanceof BlockItemBehavior
) {
player.sendActionBar(Component.translatable("build.tooHigh").arguments(Component.text(maxY)).color(NamedTextColor.RED));
}
} else if (material.isBlock()) {
// vanilla item
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);
if (BlockStateUtils.isVanillaBlock(stateId)) {
return;
}
ImmutableBlockState againCustomBlock = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId);
if (againCustomBlock.isEmpty()) {
ImmutableBlockState againCustomBlock = BukkitBlockManager.instance().getImmutableBlockState(stateId);
if (againCustomBlock == null || againCustomBlock.isEmpty()) {
return;
}
BlockPos pos = LocationUtils.toBlockPos(clickedBlock.getLocation());
@@ -140,7 +144,7 @@ public class ItemEventListener implements Listener {
Direction direction = DirectionUtils.toDirection(event.getBlockFace());
BlockHitResult hitResult = new BlockHitResult(vec3d, direction, pos, false);
try {
BlockData craftBlockData = (BlockData) Reflections.method$CraftBlockData$createData.invoke(null, againCustomBlock.vanillaBlockState().handle());
BlockData craftBlockData = BlockStateUtils.createBlockData(againCustomBlock.vanillaBlockState().handle());
if (InteractUtils.isInteractable(Key.of(clickedBlock.getType().getKey().asString()), bukkitPlayer, craftBlockData, hitResult, itemInHand)) {
if (!player.isSecondaryUseActive()) {
player.setResendSound();
@@ -155,92 +159,4 @@ public class ItemEventListener implements Listener {
}
}
}
@EventHandler(ignoreCancelled = true)
public void onUseDebugStick(PlayerInteractEvent event) {
Block clickedBlock = event.getClickedBlock();
if (clickedBlock == null) return;
ItemStack itemInHand = event.getItem();
if (itemInHand == null) return;
Material material = itemInHand.getType();
if (material != Material.DEBUG_STICK) return;
Player bukkitPlayer = event.getPlayer();
BukkitServerPlayer player = this.plugin.adapt(bukkitPlayer);
if (!(player.canInstabuild() && player.hasPermission("minecraft.debugstick")) && !player.hasPermission("minecraft.debugstick.always")) {
return;
}
if (event.getHand() == EquipmentSlot.OFF_HAND) {
int currentTicks = player.gameTicks();
if (!player.updateLastSuccessfulInteractionTick(currentTicks)) {
event.setCancelled(true);
return;
}
}
Object blockState = BlockStateUtils.blockDataToBlockState(clickedBlock.getBlockData());
int stateId = BlockStateUtils.blockStateToId(blockState);
if (!BlockStateUtils.isVanillaBlock(stateId)) {
event.setCancelled(true);
boolean update = event.getAction() == Action.RIGHT_CLICK_BLOCK;
ImmutableBlockState clickedCustomBlock = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId);
CustomBlock block = clickedCustomBlock.owner().value();
Collection<Property<?>> properties = block.properties();
String blockId = block.id().toString();
try {
if (properties.isEmpty()) {
Object systemChatPacket = Reflections.constructor$ClientboundSystemChatPacket.newInstance(
ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.empty").arguments(Component.text(blockId))), true);
player.sendPacket(systemChatPacket, false);
} else {
Item<ItemStack> wrapped = BukkitItemManager.instance().wrap(itemInHand);
Object storedData = wrapped.getTag("craftengine:debug_stick_state");
if (storedData == null) storedData = new HashMap<>();
if (storedData instanceof Map<?,?> map) {
Map<String, Object> data = MiscUtils.castToMap(map, false);
String currentPropertyName = (String) data.get(blockId);
Property<?> currentProperty = block.getProperty(currentPropertyName);
if (currentProperty == null) {
currentProperty = properties.iterator().next();
}
if (update) {
ImmutableBlockState nextState = cycleState(clickedCustomBlock, currentProperty, player.isSecondaryUseActive());
CraftEngineBlocks.place(clickedBlock.getLocation(), nextState, new UpdateOption.Builder().updateClients().updateKnownShape().build());
Object systemChatPacket = Reflections.constructor$ClientboundSystemChatPacket.newInstance(
ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.update")
.arguments(
Component.text(currentProperty.name()),
Component.text(getNameHelper(nextState, currentProperty))
)), true);
player.sendPacket(systemChatPacket, false);
} else {
currentProperty = getRelative(properties, currentProperty, player.isSecondaryUseActive());
data.put(blockId, currentProperty.name());
wrapped.setTag(data, "craftengine:debug_stick_state");
wrapped.load();
Object systemChatPacket = Reflections.constructor$ClientboundSystemChatPacket.newInstance(
ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.select")
.arguments(
Component.text(currentProperty.name()),
Component.text(getNameHelper(clickedCustomBlock, currentProperty))
)), true);
player.sendPacket(systemChatPacket, false);
}
}
}
} catch (ReflectiveOperationException e) {
plugin.logger().warn("Failed to send system chat packet", e);
}
}
}
private static <T extends Comparable<T>> ImmutableBlockState cycleState(ImmutableBlockState state, Property<T> property, boolean inverse) {
return state.with(property, getRelative(property.possibleValues(), state.get(property), inverse));
}
private static <T> T getRelative(Iterable<T> elements, @Nullable T current, boolean inverse) {
return inverse ? MCUtils.findPreviousInIterable(elements, current) : MCUtils.findNextInIterable(elements, current);
}
private static <T extends Comparable<T>> String getNameHelper(ImmutableBlockState state, Property<T> property) {
return property.valueName(state.get(property));
}
}

View File

@@ -0,0 +1,98 @@
package net.momirealms.craftengine.bukkit.item.behavior;
import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.block.behavior.StrippableBlockBehavior;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.EventUtils;
import net.momirealms.craftengine.bukkit.util.MaterialUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.bukkit.world.BukkitWorldBlock;
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.entity.player.InteractionHand;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory;
import net.momirealms.craftengine.core.item.context.UseOnContext;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.sparrow.nbt.CompoundTag;
import org.bukkit.GameEvent;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.block.Block;
import org.bukkit.event.entity.EntityChangeBlockEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import java.util.Map;
import java.util.Optional;
public class AxeItemBehavior extends ItemBehavior {
public static final Factory FACTORY = new Factory();
public static final AxeItemBehavior INSTANCE = new AxeItemBehavior();
private static final Key AXE_STRIP_SOUND = Key.of("minecraft:item.axe.strip");
@Override
public InteractionResult useOnBlock(UseOnContext context) {
BukkitWorldBlock clicked = (BukkitWorldBlock) context.getLevel().getBlockAt(context.getClickedPos());
Block block = clicked.block();
ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockDataToId(block.getBlockData()));
if (state == null || state.isEmpty()) return InteractionResult.PASS;
if (!(state.behavior() instanceof StrippableBlockBehavior blockBehavior)) {
return InteractionResult.PASS;
}
Player player = context.getPlayer();
@SuppressWarnings("unchecked")
Item<ItemStack> offHandItem = (Item<ItemStack>) player.getItemInHand(InteractionHand.OFF_HAND);
// is using a shield
if (context.getHand() == InteractionHand.MAIN_HAND && offHandItem != null && offHandItem.vanillaId().equals(ItemKeys.SHIELD) && !player.isSecondaryUseActive()) {
return InteractionResult.PASS;
}
Optional<CustomBlock> optionalNewCustomBlock = BukkitBlockManager.instance().getBlock(blockBehavior.stripped());
if (optionalNewCustomBlock.isEmpty()) {
CraftEngine.instance().logger().warn("stripped block " + blockBehavior.stripped() + " does not exist");
return InteractionResult.FAIL;
}
CustomBlock newCustomBlock = optionalNewCustomBlock.get();
CompoundTag compoundTag = state.propertiesNbt();
ImmutableBlockState newState = newCustomBlock.getBlockState(compoundTag);
org.bukkit.entity.Player bukkitPlayer = ((org.bukkit.entity.Player) player.platformPlayer());
// Call bukkit event
EntityChangeBlockEvent event = new EntityChangeBlockEvent(bukkitPlayer, block, BlockStateUtils.createBlockData(newState.customBlockState().handle()));
if (EventUtils.fireAndCheckCancel(event)) {
return InteractionResult.PASS;
}
BlockPos pos = context.getClickedPos();
context.getLevel().playBlockSound(Vec3d.atCenterOf(pos), AXE_STRIP_SOUND, 1, 1);
CraftEngineBlocks.place(block.getLocation(), newState, UpdateOption.UPDATE_ALL_IMMEDIATE);
block.getWorld().sendGameEvent(bukkitPlayer, GameEvent.BLOCK_CHANGE, new Vector(pos.x(), pos.y(), pos.z()));
Item<?> item = context.getItem();
Material material = MaterialUtils.getMaterial(item.vanillaId());
bukkitPlayer.setStatistic(Statistic.USE_ITEM, material, bukkitPlayer.getStatistic(Statistic.USE_ITEM, material) + 1);
ItemStack itemStack = (ItemStack) item.getItem();
itemStack.damage(1, bukkitPlayer);
return InteractionResult.SUCCESS;
}
public static class Factory implements ItemBehaviorFactory {
@Override
public ItemBehavior create(Key id, Map<String, Object> arguments) {
return INSTANCE;
}
}
}

View File

@@ -8,10 +8,12 @@ public class BukkitItemBehaviors extends ItemBehaviors {
public static final Key EMPTY = Key.from("craftengine:empty");
public static final Key BLOCK_ITEM = Key.from("craftengine:block_item");
public static final Key FURNITURE_ITEM = Key.from("craftengine:furniture_item");
public static final Key AXE_ITEM = Key.from("craftengine:axe_item");
public static void init() {
register(EMPTY, (args, id) -> EmptyItemBehavior.INSTANCE);
register(BLOCK_ITEM, BlockItemBehavior.FACTORY);
register(FURNITURE_ITEM, FurnitureItemBehavior.FACTORY);
register(AXE_ITEM, AxeItemBehavior.FACTORY);
}
}

View File

@@ -50,6 +50,11 @@ public abstract class BukkitItemFactory extends ItemFactory<CraftEngine, RTagIte
return Optional.of(Key.of(id.toString()));
}
@Override
protected boolean isBlockItem(ItemWrapper<ItemStack> item) {
return item.getItem().getType().isBlock();
}
@Override
protected Key vanillaId(ItemWrapper<ItemStack> item) {
return Key.of(item.getItem().getType().getKey().asString());

View File

@@ -316,7 +316,7 @@ public class PacketConsumers {
Key itemId = state.settings().itemId();
// no item available
if (itemId == null) return;
BlockData data = (BlockData) Reflections.method$CraftBlockData$createData.invoke(null, state.vanillaBlockState().handle());
BlockData data = BlockStateUtils.createBlockData(state.vanillaBlockState().handle());
// compare item
if (data == null || !data.getMaterial().equals(item.getType())) return;
ItemStack itemStack = BukkitCraftEngine.instance().itemManager().buildCustomItemStack(itemId, (BukkitServerPlayer) user);

View File

@@ -28,6 +28,7 @@ import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.util.RayTraceResult;
import org.jetbrains.annotations.Nullable;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
@@ -360,7 +361,7 @@ public class BukkitServerPlayer extends Player {
if (canInstabuild() && (itemMaterial == Material.DEBUG_STICK
|| itemMaterial == Material.TRIDENT
|| (VersionHelper.isVersionNewerThan1_20_5() && itemMaterial == MaterialUtils.MACE)
|| item.is(Key.of("minecraft:swords")))) {
|| item.is(ItemTags.SWORDS))) {
return;
}
}
@@ -482,6 +483,7 @@ public class BukkitServerPlayer extends Player {
return DirectionUtils.toDirection(platformPlayer().getFacing());
}
@Nullable
@Override
public Item<ItemStack> getItemInHand(InteractionHand hand) {
PlayerInventory inventory = platformPlayer().getInventory();

View File

@@ -8,6 +8,7 @@ import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.event.block.BlockPhysicsEvent;
import java.lang.reflect.InvocationTargetException;
import java.util.IdentityHashMap;
public class BlockStateUtils {
@@ -23,6 +24,14 @@ public class BlockStateUtils {
hasInit = true;
}
public static BlockData createBlockData(Object blockState) {
try {
return (BlockData) Reflections.method$CraftBlockData$createData.invoke(null, blockState);
} catch (InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static int blockDataToId(BlockData blockData) {
try {
Object blockState = Reflections.field$CraftBlockData$data.get(blockData);
@@ -32,13 +41,13 @@ public class BlockStateUtils {
}
}
public static Key getRealBlockId(Block block) {
public static Key getBlockOwnerId(Block block) {
BlockData data = block.getBlockData();
Object blockState = blockDataToBlockState(data);
return getRealBlockIdFromState(blockState);
return getBlockOwnerIdFromState(blockState);
}
public static Key getRealBlockIdFromState(Object blockState) {
public static Key getBlockOwnerIdFromState(Object blockState) {
String id = blockState.toString();
int first = id.indexOf('{');
int last = id.indexOf('}');

View File

@@ -8,6 +8,9 @@ import java.util.Map;
public class ItemTags {
private static final Map<Key, Object> CACHE = new HashMap<>();
public static final Key AXES = Key.of("minecraft:axes");
public static final Key SWORDS = Key.of("minecraft:swords");
public static Object getOrCreate(Key key) {
Object value = CACHE.get(key);
if (value == null) {

View File

@@ -2689,6 +2689,42 @@ public class Reflections {
}
}
public static final Class<?> clazz$EquipmentSlot = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("world.entity.EquipmentSlot"),
BukkitReflectionUtils.assembleMCClass("world.entity.EnumItemSlot")
)
);
public static final Method method$EquipmentSlot$values = requireNonNull(
ReflectionUtils.getStaticMethod(
clazz$EquipmentSlot, clazz$EquipmentSlot.arrayType()
)
);
public static final Object instance$EquipmentSlot$MAINHAND;
public static final Object instance$EquipmentSlot$OFFHAND;
public static final Object instance$EquipmentSlot$FEET;
public static final Object instance$EquipmentSlot$LEGS;
public static final Object instance$EquipmentSlot$CHEST;
public static final Object instance$EquipmentSlot$HEAD;
// public static final Object instance$EquipmentSlot$BODY;
static {
try {
Object[] values = (Object[]) method$EquipmentSlot$values.invoke(null);
instance$EquipmentSlot$MAINHAND = values[0];
instance$EquipmentSlot$OFFHAND = values[1];
instance$EquipmentSlot$FEET = values[2];
instance$EquipmentSlot$LEGS = values[3];
instance$EquipmentSlot$CHEST = values[4];
instance$EquipmentSlot$HEAD = values[5];
// instance$EquipmentSlot$BODY = values[6];
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public static final Method method$Block$defaultBlockState = requireNonNull(
ReflectionUtils.getMethod(
clazz$Block, clazz$BlockState
@@ -4124,4 +4160,11 @@ public class Reflections {
clazz$CraftInventoryCrafting, clazz$Container, 0
)
);
public static final Class<?> clazz$LivingEntity = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("world.entity.LivingEntity"),
BukkitReflectionUtils.assembleMCClass("world.entity.EntityLiving")
)
);
}

View File

@@ -97,7 +97,7 @@ public class ImmutableBlockState extends BlockStateHolder {
this.vanillaBlockState = vanillaBlockState;
}
private CompoundTag propertiesNbt() {
public CompoundTag propertiesNbt() {
CompoundTag properties = new CompoundTag();
for (Property<?> property : getProperties()) {
Comparable<?> value = get(property);

View File

@@ -5,11 +5,13 @@ import net.momirealms.craftengine.core.entity.Entity;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.plugin.network.NetWorkUser;
import net.momirealms.craftengine.core.world.BlockPos;
import org.jetbrains.annotations.Nullable;
public abstract class Player extends Entity implements NetWorkUser {
public abstract boolean isSecondaryUseActive();
@Nullable
public abstract Item<?> getItemInHand(InteractionHand hand);
@Override

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.core.item;
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
import net.momirealms.craftengine.core.util.Key;
import java.util.List;
@@ -42,6 +43,21 @@ public class AbstractItem<W extends ItemWrapper<I>, I> implements Item<I> {
return ((ItemManager<I>) factory.plugin.itemManager()).getCustomItem(id());
}
@Override
public Optional<List<ItemBehavior>> getItemBehavior() {
return factory.plugin.itemManager().getItemBehavior(id());
}
@Override
public boolean isCustomItem() {
return factory.plugin.itemManager().getCustomItem(id()).isPresent();
}
@Override
public boolean isBlockItem() {
return factory.isBlockItem(item);
}
@Override
public Key id() {
return this.factory.id(this.item);

View File

@@ -4,6 +4,7 @@ import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
import net.momirealms.craftengine.core.item.modifier.ItemModifier;
import net.momirealms.craftengine.core.util.Key;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@@ -23,6 +24,7 @@ public interface CustomItem<I> extends BuildableItem<I> {
Item<I> buildItem(Player player);
@NotNull
ItemBehavior behavior();
interface Builder<I> {

View File

@@ -1,5 +1,6 @@
package net.momirealms.craftengine.core.item;
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
import net.momirealms.craftengine.core.util.Key;
import java.util.List;
@@ -16,6 +17,12 @@ public interface Item<I> {
Optional<CustomItem<I>> getCustomItem();
Optional<List<ItemBehavior>> getItemBehavior();
boolean isCustomItem();
boolean isBlockItem();
Key id();
Key vanillaId();

View File

@@ -102,4 +102,6 @@ public abstract class ItemFactory<P extends Plugin, W extends ItemWrapper<I>, I>
protected abstract void maxStackSize(ItemWrapper<I> item, Integer maxStackSize);
protected abstract boolean is(ItemWrapper<I> item, Key itemTag);
protected abstract boolean isBlockItem(ItemWrapper<I> item);
}

View File

@@ -11,4 +11,10 @@ public class ItemKeys {
public static final Key FISHING_ROD = Key.of("minecraft:fishing_rod");
public static final Key ELYTRA = Key.of("minecraft:elytra");
public static final Key GOAT_HORN = Key.of("minecraft:goat_horn");
public static final Key WOODEN_AXE = Key.of("minecraft:wooden_axe");
public static final Key STONE_AXE = Key.of("minecraft:stone_axe");
public static final Key IRON_AXE = Key.of("minecraft:iron_axe");
public static final Key GOLDEN_AXE = Key.of("minecraft:golden_axe");
public static final Key DIAMOND_AXE = Key.of("minecraft:diamond_axe");
public static final Key NETHERITE_AXE = Key.of("minecraft:netherite_axe");
}

View File

@@ -1,6 +1,7 @@
package net.momirealms.craftengine.core.item;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.behavior.ItemBehavior;
import net.momirealms.craftengine.core.pack.LegacyOverridesModel;
import net.momirealms.craftengine.core.pack.LoadingSequence;
import net.momirealms.craftengine.core.pack.generator.ModelGenerator;
@@ -42,6 +43,8 @@ public interface ItemManager<T> extends Reloadable, ModelGenerator, ConfigSectio
Optional<CustomItem<T>> getCustomItem(Key key);
Optional<List<ItemBehavior>> getItemBehavior(Key key);
Optional<? extends BuildableItem<T>> getVanillaItem(Key key);
default Optional<? extends BuildableItem<T>> getBuildableItem(Key key) {

View File

@@ -4,7 +4,7 @@ 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.item.context.UseOnContext;
import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.World;
public abstract class ItemBehavior {
@@ -12,7 +12,7 @@ public abstract class ItemBehavior {
return InteractionResult.PASS;
}
public InteractionResult use(CEWorld context, Player player, InteractionHand hand) {
public InteractionResult use(World world, Player player, InteractionHand hand) {
return InteractionResult.PASS;
}
}