diff --git a/build.gradle.kts b/build.gradle.kts index 8d8bee6..d8b2963 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,22 +3,22 @@ plugins { id("maven-publish") } -group = "me.iwareq.fakeinventories" -version = "1.1.9-MOT" +group = "com.luminiadev.fakeinventories" +version = "1.2.0" java { toolchain { - languageVersion = JavaLanguageVersion.of(17) + languageVersion = JavaLanguageVersion.of(21) } } repositories { mavenCentral() - maven("https://repo.lanink.cn/repository/maven-public/") + maven("https://repo.luminiadev.com/snapshots") } dependencies { - compileOnly("cn.nukkit:Nukkit:MOT-SNAPSHOT") + compileOnly("com.koshakmine:Lumi:1.2.0-SNAPSHOT") compileOnly("org.projectlombok:lombok:1.18.38") annotationProcessor("org.projectlombok:lombok:1.18.38") } diff --git a/src/main/java/me/iwareq/fakeinventories/FakeInventories.java b/src/main/java/me/iwareq/fakeinventories/FakeInventories.java index 3242220..561cb2a 100644 --- a/src/main/java/me/iwareq/fakeinventories/FakeInventories.java +++ b/src/main/java/me/iwareq/fakeinventories/FakeInventories.java @@ -2,7 +2,6 @@ import cn.nukkit.block.BlockID; import cn.nukkit.blockentity.BlockEntity; -import cn.nukkit.event.EventHandler; import cn.nukkit.event.EventPriority; import cn.nukkit.event.Listener; import cn.nukkit.event.inventory.InventoryTransactionEvent; @@ -10,10 +9,11 @@ import cn.nukkit.inventory.transaction.action.SlotChangeAction; import cn.nukkit.item.Item; import cn.nukkit.plugin.PluginBase; +import lombok.Getter; import me.iwareq.fakeinventories.block.DoubleFakeBlock; import me.iwareq.fakeinventories.block.FakeBlock; +import me.iwareq.fakeinventories.block.FakeBlockOffset; import me.iwareq.fakeinventories.block.SingleFakeBlock; -import lombok.Getter; import java.util.EnumMap; import java.util.Map; @@ -24,10 +24,12 @@ public class FakeInventories extends PluginBase implements Listener { private static FakeInventories instance; private static final Map FAKE_BLOCKS = new EnumMap<>(InventoryType.class); + private static FakeBlockOffset FAKE_BLOCK_OFFSET; @Override public void onLoad() { - instance = this; + FakeInventories.instance = this; + this.saveDefaultConfig(); FAKE_BLOCKS.put(InventoryType.CHEST, new SingleFakeBlock(BlockID.CHEST, BlockEntity.CHEST)); FAKE_BLOCKS.put(InventoryType.ENDER_CHEST, new SingleFakeBlock(BlockID.ENDER_CHEST, BlockEntity.ENDER_CHEST)); FAKE_BLOCKS.put(InventoryType.DOUBLE_CHEST, new DoubleFakeBlock(BlockID.CHEST, BlockEntity.CHEST)); @@ -38,25 +40,24 @@ public void onLoad() { FAKE_BLOCKS.put(InventoryType.DROPPER, new SingleFakeBlock(BlockID.DROPPER, InventoryType.DROPPER.getDefaultTitle())); FAKE_BLOCKS.put(InventoryType.HOPPER, new SingleFakeBlock(BlockID.HOPPER_BLOCK, BlockEntity.HOPPER)); FAKE_BLOCKS.put(InventoryType.SHULKER_BOX, new SingleFakeBlock(BlockID.SHULKER_BOX, BlockEntity.SHULKER_BOX)); + FAKE_BLOCK_OFFSET = FakeBlockOffset.valueOf(this.getConfig().getString("fake-block-offset-mode").toUpperCase()); } @Override public void onEnable() { - this.getServer().getPluginManager().registerEvents(this, this); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onInventoryTransaction(InventoryTransactionEvent event) { - event.getTransaction().getActions().forEach(action -> { - if (action instanceof SlotChangeAction slotChange) { - if (slotChange.getInventory() instanceof FakeInventory inventory) { - int slot = slotChange.getSlot(); - Item sourceItem = action.getSourceItem(); - inventory.handle(slot, sourceItem, event); + this.getServer().getPluginManager().subscribeEvent(InventoryTransactionEvent.class, event -> { + event.getTransaction().getActions().forEach(action -> { + if (action instanceof SlotChangeAction slotChange) { + if (slotChange.getInventory() instanceof FakeInventory inventory) { + int slot = slotChange.getSlot(); + Item sourceItem = action.getSourceItem(); + inventory.handle(slot, sourceItem, event); + } } - } - }); + }); + }, EventPriority.MONITOR, this); } + public static FakeBlock getFakeBlock(InventoryType inventoryType) { FakeBlock fakeBlock = FAKE_BLOCKS.get(inventoryType); if (fakeBlock == null) { @@ -64,4 +65,8 @@ public static FakeBlock getFakeBlock(InventoryType inventoryType) { } return fakeBlock; } + + public static FakeBlockOffset getFakeBlockOffset() { + return FAKE_BLOCK_OFFSET; + } } \ No newline at end of file diff --git a/src/main/java/me/iwareq/fakeinventories/FakeInventory.java b/src/main/java/me/iwareq/fakeinventories/FakeInventory.java index ff78a55..e72062a 100644 --- a/src/main/java/me/iwareq/fakeinventories/FakeInventory.java +++ b/src/main/java/me/iwareq/fakeinventories/FakeInventory.java @@ -15,6 +15,7 @@ import me.iwareq.fakeinventories.util.ItemHandler; import java.util.HashMap; +import java.util.List; import java.util.Map; public class FakeInventory extends BaseInventory { @@ -48,7 +49,12 @@ public void onOpen(Player player) { packet.windowId = player.getWindowId(this); packet.type = this.getType().getNetworkType(); - Vector3 position = this.fakeBlock.getPositions(player).get(0); + List positions = this.fakeBlock.getPlacePositions(player); + if (positions.isEmpty()) { + return; + } + + Vector3 position = positions.get(0); packet.x = position.getFloorX(); packet.y = position.getFloorY(); packet.z = position.getFloorZ(); diff --git a/src/main/java/me/iwareq/fakeinventories/block/DoubleFakeBlock.java b/src/main/java/me/iwareq/fakeinventories/block/DoubleFakeBlock.java index b30be3b..b921295 100644 --- a/src/main/java/me/iwareq/fakeinventories/block/DoubleFakeBlock.java +++ b/src/main/java/me/iwareq/fakeinventories/block/DoubleFakeBlock.java @@ -16,21 +16,20 @@ public DoubleFakeBlock(int blockId, String tileId) { } @Override - public List getPositions(Player player) { - Vector3 blockPosition = player.getPosition().add(this.getOffset(player)).floor(); - DimensionData dimensionData = player.getLevel().getDimensionData(); - if (blockPosition.getFloorY() >= dimensionData.getMinHeight() && blockPosition.getFloorY() < dimensionData.getMaxHeight()) { - if ((blockPosition.getFloorX() & 1) == 1) { - return Arrays.asList(blockPosition, blockPosition.east()); + public List getPlacePositions(Player player) { + Vector3 position = player.getPosition().add(this.getOffset(player)).floor(); + DimensionData dimension = player.getLevel().getDimensionData(); + if (position.getFloorY() >= dimension.getMinHeight() && position.getFloorY() < dimension.getMaxHeight()) { + if ((position.getFloorX() & 1) == 1) { + return Arrays.asList(position, position.east()); } - return Arrays.asList(blockPosition, blockPosition.west()); + return Arrays.asList(position, position.west()); } - return Collections.emptyList(); } @Override - protected Vector3 getOffset(Player player) { + public Vector3 getOffset(Player player) { Vector3 offset = super.getOffset(player); offset.x *= 1.5; offset.z *= 1.5; @@ -39,9 +38,8 @@ protected Vector3 getOffset(Player player) { @Override protected CompoundTag getBlockEntityDataAt(Vector3 position, String title) { - int pairX = (position.getFloorX() & 1) == 1 ? 1 : -1; return super.getBlockEntityDataAt(position, title) - .putInt("pairx", position.getFloorX() + pairX) + .putInt("pairx", position.getFloorX() + ((position.getFloorX() & 1) == 1 ? 1 : -1)) .putInt("pairz", position.getFloorZ()); } } \ No newline at end of file diff --git a/src/main/java/me/iwareq/fakeinventories/block/FakeBlock.java b/src/main/java/me/iwareq/fakeinventories/block/FakeBlock.java index 5a72ead..ae4b090 100644 --- a/src/main/java/me/iwareq/fakeinventories/block/FakeBlock.java +++ b/src/main/java/me/iwareq/fakeinventories/block/FakeBlock.java @@ -3,30 +3,30 @@ import cn.nukkit.Player; import cn.nukkit.level.DimensionData; import cn.nukkit.math.Vector3; +import me.iwareq.fakeinventories.FakeInventories; import java.util.Collections; import java.util.List; +import java.util.Set; -public abstract class FakeBlock { +public interface FakeBlock { - public abstract void create(Player player, String title); + void create(Player player, String title); - public abstract void remove(Player player); + void remove(Player player); - public List getPositions(Player player) { - Vector3 blockPosition = player.getPosition().add(this.getOffset(player)).floor(); - DimensionData dimensionData = player.getLevel().getDimensionData(); - if (blockPosition.getFloorY() >= dimensionData.getMinHeight() && blockPosition.getFloorY() < dimensionData.getMaxHeight()) - return Collections.singletonList(blockPosition); + Set getLastPositions(Player player); + default List getPlacePositions(Player player) { + Vector3 position = player.getPosition().add(this.getOffset(player)).floor(); + DimensionData dimension = player.getLevel().getDimensionData(); + if (position.getFloorY() >= dimension.getMinHeight() && position.getFloorY() < dimension.getMaxHeight()) { + return Collections.singletonList(position); + } return Collections.emptyList(); } - protected Vector3 getOffset(Player player) { - Vector3 offset = player.getDirectionVector(); - offset.x *= -(1 + player.getWidth()); - offset.y *= -(1 + player.getHeight()); - offset.z *= -(1 + player.getWidth()); - return offset; + default Vector3 getOffset(Player player) { + return FakeInventories.getFakeBlockOffset().getOffset(player); } } diff --git a/src/main/java/me/iwareq/fakeinventories/block/FakeBlockOffset.java b/src/main/java/me/iwareq/fakeinventories/block/FakeBlockOffset.java new file mode 100644 index 0000000..4657c75 --- /dev/null +++ b/src/main/java/me/iwareq/fakeinventories/block/FakeBlockOffset.java @@ -0,0 +1,26 @@ +package me.iwareq.fakeinventories.block; + +import cn.nukkit.Player; +import cn.nukkit.math.Vector3; +import me.iwareq.fakeinventories.util.InventoryUtils; + +public enum FakeBlockOffset { + STANDARD { + @Override + public Vector3 getOffset(Player player) { + Vector3 offset = player.getDirectionVector(); + offset.x *= -(1 + player.getWidth()); + offset.y *= -(1 + player.getHeight()); + offset.z *= -(1 + player.getWidth()); + return offset; + } + }, + GEYSER { + @Override + public Vector3 getOffset(Player player) { + return InventoryUtils.findAvailableOffset(player); + } + }; + + public abstract Vector3 getOffset(Player player); +} diff --git a/src/main/java/me/iwareq/fakeinventories/block/SingleFakeBlock.java b/src/main/java/me/iwareq/fakeinventories/block/SingleFakeBlock.java index 808a278..4b14c4f 100644 --- a/src/main/java/me/iwareq/fakeinventories/block/SingleFakeBlock.java +++ b/src/main/java/me/iwareq/fakeinventories/block/SingleFakeBlock.java @@ -1,37 +1,38 @@ package me.iwareq.fakeinventories.block; import cn.nukkit.Player; +import cn.nukkit.blockentity.BlockEntity; import cn.nukkit.level.GlobalBlockPalette; import cn.nukkit.math.Vector3; import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.network.protocol.BlockEntityDataPacket; import cn.nukkit.network.protocol.UpdateBlockPacket; +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import java.io.IOException; import java.nio.ByteOrder; -import java.util.List; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; @Slf4j @RequiredArgsConstructor -public class SingleFakeBlock extends FakeBlock { +public class SingleFakeBlock implements FakeBlock { protected final int blockId; protected final String tileId; - protected List lastPositions; + protected final Map> lastPositions = new Object2ObjectArrayMap<>(); @Override public void create(Player player, String title) { - List positions = this.getPositions(player); - - this.lastPositions = positions; - - positions.forEach(position -> { + this.createAndGetLastPositions(player).addAll(this.getPlacePositions(player)); + this.getPlacePositions(player).forEach(position -> { UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); updateBlockPacket.blockRuntimeId = GlobalBlockPalette.getOrCreateRuntimeId(player.protocol, this.blockId, 0); - updateBlockPacket.flags = UpdateBlockPacket.FLAG_ALL_PRIORITY; + updateBlockPacket.flags = UpdateBlockPacket.FLAG_NETWORK; updateBlockPacket.x = position.getFloorX(); updateBlockPacket.y = position.getFloorY(); updateBlockPacket.z = position.getFloorZ(); @@ -53,20 +54,33 @@ public void create(Player player, String title) { @Override public void remove(Player player) { - this.lastPositions.forEach(position -> { + this.getLastPositions(player).forEach(position -> { UpdateBlockPacket packet = new UpdateBlockPacket(); packet.blockRuntimeId = GlobalBlockPalette.getOrCreateRuntimeId(player.protocol, player.getLevel().getBlock(position).getFullId()); - packet.flags = UpdateBlockPacket.FLAG_ALL_PRIORITY; + packet.flags = UpdateBlockPacket.FLAG_NETWORK; packet.x = position.getFloorX(); packet.y = position.getFloorY(); packet.z = position.getFloorZ(); player.dataPacket(packet); }); + this.lastPositions.remove(player); } protected CompoundTag getBlockEntityDataAt(Vector3 position, String title) { - return new CompoundTag() + return BlockEntity.getDefaultCompound(position, title) .putString("id", tileId) + .putBoolean("isMovable", true) .putString("CustomName", title); } + + public Set createAndGetLastPositions(Player player) { + if (!lastPositions.containsKey(player)) { + lastPositions.put(player, new HashSet<>()); + } + return lastPositions.get(player); + } + + public Set getLastPositions(Player player) { + return lastPositions.getOrDefault(player, new HashSet<>()); + } } \ No newline at end of file diff --git a/src/main/java/me/iwareq/fakeinventories/util/InventoryUtils.java b/src/main/java/me/iwareq/fakeinventories/util/InventoryUtils.java new file mode 100644 index 0000000..f5a0a4d --- /dev/null +++ b/src/main/java/me/iwareq/fakeinventories/util/InventoryUtils.java @@ -0,0 +1,48 @@ +package me.iwareq.fakeinventories.util; + +import cn.nukkit.Player; +import cn.nukkit.block.BlockEntityHolder; +import cn.nukkit.level.Level; +import cn.nukkit.math.Vector3; +import cn.nukkit.block.Block; + +public class InventoryUtils { + + /** + * Finds a usable offset relative to the player's position for placing a fake inventory block. + * Returns null if no valid offset is found. + */ + public static Vector3 findAvailableOffset(Player player) { + Level level = player.getLevel(); + + int minY = level.getDimensionData().getMinHeight(); + int maxY = level.getDimensionData().getMaxHeight(); + + Vector3 playerPos = player.getPosition(); + + // Offset above the player + Vector3 offsetAbove = new Vector3(0, 2, 0); + Vector3 posAbove = playerPos.add(offsetAbove); + if (posAbove.getY() >= minY && posAbove.getY() < maxY && canUseWorldSpace(level, posAbove)) { + return offsetAbove; + } + + // Offset 4 blocks below the player + Vector3 offsetBelow = new Vector3(0, -4, 0); + Vector3 posBelow = playerPos.add(offsetBelow); + if (posBelow.getY() >= minY && posBelow.getY() < maxY && canUseWorldSpace(level, posBelow)) { + return offsetBelow; + } + + return null; + } + + /** + * Checks if the block space can be used as a "fake" block location. + */ + private static boolean canUseWorldSpace(Level level, Vector3 pos) { + Block block = level.getBlock(pos); + // Valid if the block is air or a simple block without a BlockEntity + return block.isAir() || !(block instanceof BlockEntityHolder); + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..75e1035 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,5 @@ +# Fake block offset working mode +# Available: +# STANDARD - Standard working mode +# GEYSER - Geyser like offset mode +fake-block-offset-mode: "GEYSER" \ No newline at end of file