Merge pull request #154 from Kebab11noel/master

Added CanPlaceOn and CanDestroy for ItemStacks
This commit is contained in:
TheMode 2021-03-02 18:30:23 +01:00 committed by GitHub
commit 85998cabd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 124 additions and 10 deletions

View File

@ -67,6 +67,9 @@ public class ItemStack implements DataContainer, PublicCloneable<ItemStack> {
private StackingRule stackingRule;
private Data data;
private Set<String> canDestroy;
private Set<String> canPlaceOn;
{
if (defaultStackingRule == null)
defaultStackingRule = VANILLA_STACKING_RULE;
@ -83,6 +86,9 @@ public class ItemStack implements DataContainer, PublicCloneable<ItemStack> {
this.enchantmentMap = new Object2ShortOpenHashMap<>();
this.attributes = new ArrayList<>();
this.canDestroy = new HashSet<>();
this.canPlaceOn = new HashSet<>();
this.itemMeta = findMeta();
}
@ -185,7 +191,9 @@ public class ItemStack implements DataContainer, PublicCloneable<ItemStack> {
itemStack.attributes.equals(attributes) &&
itemStack.hideFlag == hideFlag &&
sameMeta &&
dataCheck;
dataCheck &&
itemStack.canPlaceOn.equals(canPlaceOn) &&
itemStack.canDestroy.equals(canDestroy);
}
}
@ -195,6 +203,42 @@ public class ItemStack implements DataContainer, PublicCloneable<ItemStack> {
isSimilar((ItemStack) o) && ((ItemStack) o).getAmount() == getAmount();
}
/**
* Checks if this item can be placed on the block.
* This should be enforced only for adventure mode players.
* @param block the block's namespaceID
* @return <code>true</code> if it can be placed, <code>false</code> otherwise
*/
public boolean canPlaceOn(String block) {
return canPlaceOn.contains(block);
}
/**
* Gets the blocks that this item can be placed on
* @return the {@link Set} of blocks
*/
public Set<String> getCanPlaceOn() {
return canPlaceOn;
}
/**
* Checks if this item is allowed to break the provided block.
* This should be enforced only for adventure mode players.
* @param block the block's namespaceID
* @return <code>true</code> if this item can destroy it, otherwise <code>false</code>
*/
public boolean canDestroy(String block) {
return canDestroy.contains(block);
}
/**
* Gets the blocks that this item can destroy
* @return the {@link Set} of blocks
*/
public Set<String> getCanDestroy() {
return canDestroy;
}
/**
* Gets the item damage (durability).
*
@ -555,7 +599,9 @@ public class ItemStack implements DataContainer, PublicCloneable<ItemStack> {
hideFlag != 0 ||
customModelData != 0 ||
(itemMeta != null && itemMeta.hasNbt()) ||
(data != null && !data.isEmpty());
(data != null && !data.isEmpty()) ||
!canDestroy.isEmpty() ||
!canPlaceOn.isEmpty();
}
/**
@ -594,6 +640,9 @@ public class ItemStack implements DataContainer, PublicCloneable<ItemStack> {
itemStack.hideFlag = hideFlag;
itemStack.customModelData = customModelData;
itemStack.canPlaceOn = new HashSet<>(canPlaceOn);
itemStack.canDestroy = new HashSet<>(canDestroy);
if (itemMeta != null)
itemStack.itemMeta = itemMeta.clone();

View File

@ -19,6 +19,7 @@ import net.minestom.server.inventory.PlayerInventory;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.network.packet.client.play.ClientPlayerBlockPlacementPacket;
import net.minestom.server.network.packet.server.play.BlockChangePacket;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.Direction;
import net.minestom.server.utils.chunk.ChunkUtils;
@ -72,14 +73,19 @@ public class BlockPlacementListener {
final Material useMaterial = usedItem.getMaterial();
// Verify if the player can place the block
boolean canPlaceBlock = true;
{
if (useMaterial == Material.AIR) { // Can't place air
return;
}
if (player.getGameMode().equals(GameMode.ADVENTURE)) { // Can't place in adventure mode
return;
}
//Check if the player is allowed to place blocks based on their game mode
if (player.getGameMode() == GameMode.SPECTATOR) {
canPlaceBlock = false; //Spectators can't place blocks
} else if (player.getGameMode() == GameMode.ADVENTURE) {
//Check if the block can placed on the block
canPlaceBlock = usedItem.canPlaceOn(instance.getBlock(blockPosition).getName());
}
}
// Get the newly placed block position
@ -89,6 +95,16 @@ public class BlockPlacementListener {
blockPosition.add(offsetX, offsetY, offsetZ);
if(!canPlaceBlock) {
//Send a block change with AIR as block to keep the client in sync,
//using refreshChunk results in the client not being in sync
//after rapid invalid block placements
BlockChangePacket blockChangePacket = new BlockChangePacket();
blockChangePacket.blockPosition = blockPosition;
blockChangePacket.blockStateId = Block.AIR.getBlockId();
player.getPlayerConnection().sendPacket(blockChangePacket);
return;
}
final Chunk chunk = instance.getChunkAt(blockPosition);

View File

@ -1,5 +1,6 @@
package net.minestom.server.listener;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.event.item.ItemUpdateStateEvent;
import net.minestom.server.event.player.PlayerStartDiggingEvent;
@ -36,8 +37,23 @@ public class PlayerDiggingListener {
return;
if (status == ClientPlayerDiggingPacket.Status.STARTED_DIGGING) {
final short blockStateId = instance.getBlockStateId(blockPosition);
//Check if the player is allowed to break blocks based on their game mode
if (player.getGameMode() == GameMode.SPECTATOR) {
sendAcknowledgePacket(player, blockPosition, blockStateId,
ClientPlayerDiggingPacket.Status.STARTED_DIGGING, false);
return; //Spectators can't break blocks
} else if (player.getGameMode() == GameMode.ADVENTURE) {
//Check if the item can break the block with the current item
ItemStack itemInMainHand = player.getItemInMainHand();
if (!itemInMainHand.canDestroy(instance.getBlock(blockPosition).getName())) {
sendAcknowledgePacket(player, blockPosition, blockStateId,
ClientPlayerDiggingPacket.Status.STARTED_DIGGING, false);
return;
}
}
final boolean instantBreak = player.isCreative() ||
player.isInstantBreak() ||
Block.fromStateId(blockStateId).breaksInstantaneously();

View File

@ -26,10 +26,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.*;
// for lack of a better name
public final class NBTUtils {
@ -124,6 +121,7 @@ public final class NBTUtils {
return item;
}
@SuppressWarnings("ConstantConditions")
public static void loadDataIntoItem(@NotNull ItemStack item, @NotNull NBTCompound nbt) {
if (nbt.containsKey("Damage")) item.setDamage(nbt.getInt("Damage"));
if (nbt.containsKey("Unbreakable")) item.setUnbreakable(nbt.getAsByte("Unbreakable") == 1);
@ -222,6 +220,21 @@ public final class NBTUtils {
}
}
}
//CanPlaceOn
{
if (nbt.containsKey("CanPlaceOn")) {
NBTList<NBTString> canPlaceOn = nbt.getList("CanPlaceOn");
canPlaceOn.forEach(x -> item.getCanPlaceOn().add(x.getValue()));
}
}
//CanDestroy
{
if (nbt.containsKey("CanDestroy")) {
NBTList<NBTString> canPlaceOn = nbt.getList("CanDestroy");
canPlaceOn.forEach(x -> item.getCanDestroy().add(x.getValue()));
}
}
}
public static void loadEnchantments(NBTList<NBTCompound> enchantments, EnchantmentSetter setter) {
@ -368,6 +381,26 @@ public final class NBTUtils {
}
}
// End ownership
//CanDestroy
{
Set<String> canDestroy = itemStack.getCanDestroy();
if (canDestroy.size() > 0) {
NBTList<NBTString> list = new NBTList<>(NBTTypes.TAG_String);
canDestroy.forEach(x -> list.add(new NBTString(x)));
itemNBT.set("CanDestroy", list);
}
}
//CanDestroy
{
Set<String> canPlaceOn = itemStack.getCanPlaceOn();
if (canPlaceOn.size() > 0) {
NBTList<NBTString> list = new NBTList<>(NBTTypes.TAG_String);
canPlaceOn.forEach(x -> list.add(new NBTString(x)));
itemNBT.set("CanPlaceOn", list);
}
}
}
/**