Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
37 changes: 21 additions & 16 deletions src/main/java/me/iwareq/fakeinventories/FakeInventories.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@

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;
import cn.nukkit.inventory.InventoryType;
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;
Expand All @@ -24,10 +24,12 @@ public class FakeInventories extends PluginBase implements Listener {
private static FakeInventories instance;

private static final Map<InventoryType, FakeBlock> 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));
Expand All @@ -38,30 +40,33 @@ 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) {
throw new NullPointerException("FakeBlock for " + inventoryType.name() + " inventory not found!");
}
return fakeBlock;
}

public static FakeBlockOffset getFakeBlockOffset() {
return FAKE_BLOCK_OFFSET;
}
}
8 changes: 7 additions & 1 deletion src/main/java/me/iwareq/fakeinventories/FakeInventory.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<Vector3> 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();
Expand Down
20 changes: 9 additions & 11 deletions src/main/java/me/iwareq/fakeinventories/block/DoubleFakeBlock.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,20 @@ public DoubleFakeBlock(int blockId, String tileId) {
}

@Override
public List<Vector3> 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<Vector3> 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;
Expand All @@ -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());
}
}
28 changes: 14 additions & 14 deletions src/main/java/me/iwareq/fakeinventories/block/FakeBlock.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vector3> 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<Vector3> getLastPositions(Player player);

default List<Vector3> 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);
}
}
26 changes: 26 additions & 0 deletions src/main/java/me/iwareq/fakeinventories/block/FakeBlockOffset.java
Original file line number Diff line number Diff line change
@@ -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);
}
38 changes: 26 additions & 12 deletions src/main/java/me/iwareq/fakeinventories/block/SingleFakeBlock.java
Original file line number Diff line number Diff line change
@@ -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<Vector3> lastPositions;
protected final Map<Player, Set<Vector3>> lastPositions = new Object2ObjectArrayMap<>();

@Override
public void create(Player player, String title) {
List<Vector3> 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();
Expand All @@ -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<Vector3> createAndGetLastPositions(Player player) {
if (!lastPositions.containsKey(player)) {
lastPositions.put(player, new HashSet<>());
}
return lastPositions.get(player);
}

public Set<Vector3> getLastPositions(Player player) {
return lastPositions.getOrDefault(player, new HashSet<>());
}
}
48 changes: 48 additions & 0 deletions src/main/java/me/iwareq/fakeinventories/util/InventoryUtils.java
Original file line number Diff line number Diff line change
@@ -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<?>);
}
}
5 changes: 5 additions & 0 deletions src/main/resources/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Fake block offset working mode
# Available:
# STANDARD - Standard working mode
# GEYSER - Geyser like offset mode
fake-block-offset-mode: "GEYSER"