Added CanPlaceOn and CanDestroy properties for ItemStacks and implemented checks for them

This commit is contained in:
Németh Noel 2021-02-28 19:37:31 +01:00
parent 419ebe7553
commit 7abf6ba9e7
4 changed files with 158 additions and 9 deletions

View File

@ -67,6 +67,9 @@ public class ItemStack implements DataContainer, PublicCloneable<ItemStack> {
private StackingRule stackingRule; private StackingRule stackingRule;
private Data data; private Data data;
private Set<String> canDestroy;
private Set<String> canPlaceOn;
{ {
if (defaultStackingRule == null) if (defaultStackingRule == null)
defaultStackingRule = VANILLA_STACKING_RULE; defaultStackingRule = VANILLA_STACKING_RULE;
@ -83,6 +86,9 @@ public class ItemStack implements DataContainer, PublicCloneable<ItemStack> {
this.enchantmentMap = new Object2ShortOpenHashMap<>(); this.enchantmentMap = new Object2ShortOpenHashMap<>();
this.attributes = new ArrayList<>(); this.attributes = new ArrayList<>();
this.canDestroy = new HashSet<>();
this.canPlaceOn = new HashSet<>();
this.itemMeta = findMeta(); this.itemMeta = findMeta();
} }
@ -195,6 +201,76 @@ public class ItemStack implements DataContainer, PublicCloneable<ItemStack> {
isSimilar((ItemStack) o) && ((ItemStack) o).getAmount() == getAmount(); 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);
}
/**
* Adds the block to the list of blocks that
* this item can be placed on.
* @param block the block's namespaceID
*/
public void addCanPlaceOn(String block) {
canPlaceOn.add(block);
}
/**
* Removes the block from the set of blocks that
* this item can be placed on.
* @param block the block's namespaceID
*/
public void removeCanPlaceOn(String block) {
canPlaceOn.remove(block);
}
/**
* Gets the blocks that this item can be placed on
* @return an unmodifiable {@link Set} of blocks
*/
public Set<String> getCanPlaceOn() {
return Collections.unmodifiableSet(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);
}
/**
* Adds the block to the set of blocks that can be destroyed by this item.
* @param block the block's namespaceID
*/
public void addCanDestroy(String block) {
canDestroy.add(block);
}
/**
* Removes the block from the set of blocks that can be destroyed by this item.
* @param block the block's namespaceID
*/
public void removeCanDestroy(String block) {
canDestroy.remove(block);
}
/**
* Gets the blocks that this item can destroy
* @return an unmodifiable {@link Set} of blocks
*/
public Set<String> getCanDestroy() {
return Collections.unmodifiableSet(canDestroy);
}
/** /**
* Gets the item damage (durability). * Gets the item damage (durability).
* *
@ -555,7 +631,9 @@ public class ItemStack implements DataContainer, PublicCloneable<ItemStack> {
hideFlag != 0 || hideFlag != 0 ||
customModelData != 0 || customModelData != 0 ||
(itemMeta != null && itemMeta.hasNbt()) || (itemMeta != null && itemMeta.hasNbt()) ||
(data != null && !data.isEmpty()); (data != null && !data.isEmpty()) ||
canDestroy.size() != 0 ||
canPlaceOn.size() != 0;
} }
/** /**
@ -594,6 +672,9 @@ public class ItemStack implements DataContainer, PublicCloneable<ItemStack> {
itemStack.hideFlag = hideFlag; itemStack.hideFlag = hideFlag;
itemStack.customModelData = customModelData; itemStack.customModelData = customModelData;
itemStack.canPlaceOn = new HashSet<>(canPlaceOn);
itemStack.canDestroy = new HashSet<>(canDestroy);
if (itemMeta != null) if (itemMeta != null)
itemStack.itemMeta = itemMeta.clone(); itemStack.itemMeta = itemMeta.clone();

View File

@ -19,6 +19,8 @@ import net.minestom.server.inventory.PlayerInventory;
import net.minestom.server.item.ItemStack; import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material; import net.minestom.server.item.Material;
import net.minestom.server.network.packet.client.play.ClientPlayerBlockPlacementPacket; import net.minestom.server.network.packet.client.play.ClientPlayerBlockPlacementPacket;
import net.minestom.server.network.packet.client.play.ClientPlayerDiggingPacket;
import net.minestom.server.network.packet.server.play.BlockChangePacket;
import net.minestom.server.utils.BlockPosition; import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.Direction; import net.minestom.server.utils.Direction;
import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.chunk.ChunkUtils;
@ -72,14 +74,19 @@ public class BlockPlacementListener {
final Material useMaterial = usedItem.getMaterial(); final Material useMaterial = usedItem.getMaterial();
// Verify if the player can place the block // Verify if the player can place the block
boolean canPlaceBlock = true;
{ {
if (useMaterial == Material.AIR) { // Can't place air if (useMaterial == Material.AIR) { // Can't place air
return; 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 // Get the newly placed block position
@ -89,6 +96,16 @@ public class BlockPlacementListener {
blockPosition.add(offsetX, offsetY, offsetZ); 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); final Chunk chunk = instance.getChunkAt(blockPosition);

View File

@ -1,5 +1,6 @@
package net.minestom.server.listener; package net.minestom.server.listener;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player; import net.minestom.server.entity.Player;
import net.minestom.server.event.item.ItemUpdateStateEvent; import net.minestom.server.event.item.ItemUpdateStateEvent;
import net.minestom.server.event.player.PlayerStartDiggingEvent; import net.minestom.server.event.player.PlayerStartDiggingEvent;
@ -10,6 +11,7 @@ import net.minestom.server.instance.block.CustomBlock;
import net.minestom.server.inventory.PlayerInventory; import net.minestom.server.inventory.PlayerInventory;
import net.minestom.server.item.ItemStack; import net.minestom.server.item.ItemStack;
import net.minestom.server.item.StackingRule; import net.minestom.server.item.StackingRule;
import net.minestom.server.item.attribute.ItemAttribute;
import net.minestom.server.network.packet.client.play.ClientPlayerDiggingPacket; import net.minestom.server.network.packet.client.play.ClientPlayerDiggingPacket;
import net.minestom.server.network.packet.server.play.AcknowledgePlayerDiggingPacket; import net.minestom.server.network.packet.server.play.AcknowledgePlayerDiggingPacket;
import net.minestom.server.network.packet.server.play.EntityEffectPacket; import net.minestom.server.network.packet.server.play.EntityEffectPacket;
@ -17,6 +19,7 @@ import net.minestom.server.network.packet.server.play.RemoveEntityEffectPacket;
import net.minestom.server.potion.Potion; import net.minestom.server.potion.Potion;
import net.minestom.server.potion.PotionEffect; import net.minestom.server.potion.PotionEffect;
import net.minestom.server.utils.BlockPosition; import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.List; import java.util.List;
@ -36,8 +39,23 @@ public class PlayerDiggingListener {
return; return;
if (status == ClientPlayerDiggingPacket.Status.STARTED_DIGGING) { if (status == ClientPlayerDiggingPacket.Status.STARTED_DIGGING) {
final short blockStateId = instance.getBlockStateId(blockPosition); 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() || final boolean instantBreak = player.isCreative() ||
player.isInstantBreak() || player.isInstantBreak() ||
Block.fromStateId(blockStateId).breaksInstantaneously(); Block.fromStateId(blockStateId).breaksInstantaneously();

View File

@ -26,10 +26,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.UUID;
// for lack of a better name // for lack of a better name
public final class NBTUtils { public final class NBTUtils {
@ -124,6 +121,7 @@ public final class NBTUtils {
return item; return item;
} }
@SuppressWarnings("ConstantConditions")
public static void loadDataIntoItem(@NotNull ItemStack item, @NotNull NBTCompound nbt) { public static void loadDataIntoItem(@NotNull ItemStack item, @NotNull NBTCompound nbt) {
if (nbt.containsKey("Damage")) item.setDamage(nbt.getInt("Damage")); if (nbt.containsKey("Damage")) item.setDamage(nbt.getInt("Damage"));
if (nbt.containsKey("Unbreakable")) item.setUnbreakable(nbt.getAsByte("Unbreakable") == 1); 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.addCanPlaceOn(x.getValue()));
}
}
//CanDestroy
{
if (nbt.containsKey("CanDestroy")) {
NBTList<NBTString> canPlaceOn = nbt.getList("CanDestroy");
canPlaceOn.forEach(x -> item.addCanDestroy(x.getValue()));
}
}
} }
public static void loadEnchantments(NBTList<NBTCompound> enchantments, EnchantmentSetter setter) { public static void loadEnchantments(NBTList<NBTCompound> enchantments, EnchantmentSetter setter) {
@ -368,6 +381,26 @@ public final class NBTUtils {
} }
} }
// End ownership // 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.toString())));
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.toString())));
itemNBT.set("CanPlaceOn", list);
}
}
} }
/** /**