From bcb353fda871cf0fdc8d63a140172ec15018cbcf Mon Sep 17 00:00:00 2001 From: SirSmurfy2 Date: Thu, 13 Aug 2026 13:38:40 -0400 Subject: [PATCH 1/2] Initial Commit --- .../skript/classes/data/BukkitClasses.java | 86 ++----- .../skript/expressions/ExprBlockData.java | 76 ------ .../njol/skript/expressions/ExprBlocks.java | 25 +- .../skript/expressions/ExprDirection.java | 196 -------------- .../expressions/ExprVectorFromDirection.java | 1 + .../skript/bukkit/block/BlockModule.java | 2 + .../block/blockdata/BlockDataClassInfo.java | 82 ++++++ .../block/blockdata/BlockDataHolder.java | 230 +++++++++++++++++ .../block/blockdata/BlockDataModule.java | 38 +++ .../bukkit/block/blockdata/BlockDataTag.java | 238 +++++++++++++++++ .../block/blockdata/BlockDataValueType.java | 187 ++++++++++++++ .../blockdata/elements/CondBlockDataTag.java | 88 +++++++ .../blockdata/elements/ExprBlockData.java | 98 +++++++ .../blockdata/elements/ExprBlockDataTags.java | 109 ++++++++ .../elements/ExprBlockDataValues.java | 240 ++++++++++++++++++ .../skript/bukkit/misc/MiscModule.java | 1 + .../elements/expressions/ExprDirection.java | 216 ++++++++++++++++ .../syntaxes/expressions/ExprBlockData.sk | 117 +++++++-- 18 files changed, 1663 insertions(+), 367 deletions(-) delete mode 100644 src/main/java/ch/njol/skript/expressions/ExprBlockData.java delete mode 100644 src/main/java/ch/njol/skript/expressions/ExprDirection.java create mode 100644 src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataClassInfo.java create mode 100644 src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataHolder.java create mode 100644 src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataModule.java create mode 100644 src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataTag.java create mode 100644 src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataValueType.java create mode 100644 src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/CondBlockDataTag.java create mode 100644 src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/ExprBlockData.java create mode 100644 src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/ExprBlockDataTags.java create mode 100644 src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/ExprBlockDataValues.java create mode 100644 src/main/java/org/skriptlang/skript/bukkit/misc/elements/expressions/ExprDirection.java diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java index 7089b80c64a..18a0d31df27 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -2,28 +2,43 @@ import ch.njol.skript.Skript; import ch.njol.skript.bukkitutil.EntityUtils; -import ch.njol.skript.classes.*; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.EnumClassInfo; +import ch.njol.skript.classes.Parser; +import ch.njol.skript.classes.PatternedParser; +import ch.njol.skript.classes.Serializer; import ch.njol.skript.classes.registry.RegistryClassInfo; import ch.njol.skript.expressions.ExprDamageCause; import ch.njol.skript.expressions.base.EventValueExpression; import ch.njol.skript.lang.ParseContext; import ch.njol.skript.lang.util.SimpleLiteral; import ch.njol.skript.registrations.Classes; -import ch.njol.skript.util.BlockUtils; import ch.njol.yggdrasil.Fields; import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.world.MoonPhase; import net.kyori.adventure.text.Component; -import org.bukkit.*; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.Difficulty; +import org.bukkit.FireworkEffect; +import org.bukkit.GameMode; +import org.bukkit.GameRule; +import org.bukkit.Material; +import org.bukkit.SoundCategory; +import org.bukkit.World; import org.bukkit.World.Environment; import org.bukkit.attribute.Attribute; import org.bukkit.block.Biome; import org.bukkit.block.BlockState; import org.bukkit.block.DoubleChest; import org.bukkit.block.banner.PatternType; -import org.bukkit.block.data.BlockData; import org.bukkit.command.CommandSender; -import org.bukkit.entity.*; +import org.bukkit.entity.EntitySnapshot; +import org.bukkit.entity.Item; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.Vehicle; +import org.bukkit.entity.Villager; import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; import org.bukkit.event.entity.EntityDamageEvent.DamageCause; import org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason; @@ -85,67 +100,6 @@ public BukkitClasses() {} .defaultExpression(new EventValueExpression<>(Projectile.class)) .changer(DefaultChangers.nonLivingEntityChanger)); - Classes.registerClass(new ClassInfo<>(BlockData.class, "blockdata") - .user("block ?datas?") - .name("Block Data") - .description("Block data is the detailed information about a block, referred to in Minecraft as BlockStates, " + - "allowing for the manipulation of different aspects of the block, including shape, waterlogging, direction the block is facing, " + - "and so much more. Information regarding each block's optional data can be found on Minecraft's Wiki. Find the block you're " + - "looking for and scroll down to 'Block States'. Different states must be separated by a semicolon (see examples). " + - "The 'minecraft:' namespace is optional, as well as are underscores.") - .examples("set block at player to campfire[lit=false]", - "set target block of player to oak stairs[facing=north;waterlogged=true]", - "set block at player to grass_block[snowy=true]", - "set loop-block to minecraft:chest[facing=north]", - "set block above player to oak_log[axis=y]", - "set target block of player to minecraft:oak_leaves[distance=2;persistent=false]") - .after("itemtype") - .since("2.5") - .parser(new Parser<>() { - @Nullable - @Override - public BlockData parse(String input, ParseContext context) { - return BlockUtils.createBlockData(input); - } - - @Override - public String toString(BlockData o, int flags) { - return o.getAsString().replace(",", ";"); - } - - @Override - public String toVariableNameString(BlockData o) { - return "blockdata:" + o.getAsString(); - } - }) - .serializer(new Serializer<>() { - @Override - public Fields serialize(BlockData blockData) { - return Fields.singletonObject("blockdata", blockData.getAsString()); - } - - @Override - protected BlockData deserialize(Fields fields) throws StreamCorruptedException { - String data = fields.getObject("blockdata", String.class); - assert data != null; - try { - return Bukkit.createBlockData(data); - } catch (IllegalArgumentException ex) { - throw new StreamCorruptedException("Invalid block data: " + data); - } - } - - @Override - public boolean mustSyncDeserialization() { - return true; - } - - @Override - protected boolean canBeInstantiated() { - return false; - } - }).cloner(BlockData::clone)); - Classes.registerClass(new ClassInfo<>(World.class, "world") .user("worlds?") .name("World") diff --git a/src/main/java/ch/njol/skript/expressions/ExprBlockData.java b/src/main/java/ch/njol/skript/expressions/ExprBlockData.java deleted file mode 100644 index 64ba08c6b61..00000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprBlockData.java +++ /dev/null @@ -1,76 +0,0 @@ -package ch.njol.skript.expressions; - -import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Example; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.expressions.base.SimplePropertyExpression; -import ch.njol.util.coll.CollectionUtils; -import org.bukkit.block.Block; -import org.bukkit.block.data.BlockData; -import org.bukkit.entity.BlockDisplay; -import org.bukkit.entity.FallingBlock; -import org.bukkit.event.Event; -import org.jetbrains.annotations.Nullable; - -@Name("Block Data") -@Description({ - "Get the block data associated with a block.", - "This data can also be used to set blocks." -}) -@Example("set {_data} to block data of target block") -@Example("set block at player to {_data}") -@Example("set block data of target block to oak_stairs[facing=south;waterlogged=true]") -@Since("2.5, 2.5.2 (set), 2.10 (block displays)") -public class ExprBlockData extends SimplePropertyExpression { - - static { - register(ExprBlockData.class, BlockData.class, "block[ ]data", "blocks/displays/entities"); - } - - @Override - public @Nullable BlockData convert(Object object) { - if (object instanceof Block block) - return block.getBlockData(); - if (object instanceof BlockDisplay blockDisplay) - return blockDisplay.getBlock(); - if (object instanceof FallingBlock fallingBlock) - return fallingBlock.getBlockData(); - return null; - - } - - @Override - public Class @Nullable [] acceptChange(ChangeMode mode) { - if (mode == ChangeMode.SET) - return CollectionUtils.array(BlockData.class); - return null; - } - - @Override - public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { - assert delta != null; // reset/delete not supported - BlockData blockData = ((BlockData) delta[0]); - for (Object object : getExpr().getArray(event)) { - if (object instanceof Block block) { - block.setBlockData(blockData); - } else if (object instanceof BlockDisplay blockDisplay) { - blockDisplay.setBlock(blockData); - } else if (object instanceof FallingBlock fallingBlock) { - fallingBlock.setBlockData(blockData); - } - } - } - - @Override - public Class getReturnType() { - return BlockData.class; - } - - @Override - protected String getPropertyName() { - return "block data"; - } - -} diff --git a/src/main/java/ch/njol/skript/expressions/ExprBlocks.java b/src/main/java/ch/njol/skript/expressions/ExprBlocks.java index d4acbcc7af4..7acb0525497 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprBlocks.java +++ b/src/main/java/ch/njol/skript/expressions/ExprBlocks.java @@ -1,16 +1,5 @@ package ch.njol.skript.expressions; -import java.util.Iterator; - -import org.bukkit.Chunk; -import org.bukkit.Location; -import org.bukkit.block.Block; -import org.bukkit.event.Event; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.Nullable; - -import com.google.common.collect.Lists; - import ch.njol.skript.Skript; import ch.njol.skript.SkriptConfig; import ch.njol.skript.doc.Description; @@ -26,6 +15,16 @@ import ch.njol.skript.util.Direction; import ch.njol.util.Kleenean; import ch.njol.util.coll.iterator.ArrayIterator; +import com.google.common.collect.Lists; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.event.Event; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.misc.elements.expressions.ExprDirection; + +import java.util.Iterator; @Name("Blocks") @Description({"Blocks relative to other blocks or between other blocks.", @@ -144,8 +143,8 @@ public Iterator iterator(Event event) { return null; // start block + (max - 1) == max int distance = SkriptConfig.maxTargetBlockDistance.value() - 1; - if (this.direction instanceof ExprDirection) { - Expression numberExpression = ((ExprDirection) this.direction).amount; + if (this.direction instanceof ExprDirection exprDirection) { + Expression numberExpression = exprDirection.getAmount(); if (numberExpression != null) { Number number = numberExpression.getSingle(event); if (number != null) diff --git a/src/main/java/ch/njol/skript/expressions/ExprDirection.java b/src/main/java/ch/njol/skript/expressions/ExprDirection.java deleted file mode 100644 index b0a709accc8..00000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprDirection.java +++ /dev/null @@ -1,196 +0,0 @@ -package ch.njol.skript.expressions; - -import org.bukkit.Location; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.entity.Entity; -import org.bukkit.event.Event; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.Nullable; - -import ch.njol.skript.Skript; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Example; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.util.SimpleExpression; -import ch.njol.skript.util.Direction; -import ch.njol.util.Kleenean; -import ch.njol.util.Math2; - -/** - * @author Peter Güttinger - */ -@Name("Direction") -@Description("A helper expression for the direction type.") -@Example("thrust the player upwards") -@Example("set the block behind the player to water") -@Example(""" - loop blocks above the player: - set {_rand} to a random integer between 1 and 10 - set the block {_rand} meters south east of the loop-block to stone - """) -@Example("block in horizontal facing of the clicked entity from the player is air") -@Example("spawn a creeper 1.5 meters horizontally behind the player") -@Example("spawn a TNT 5 meters above and 2 meters horizontally behind the player") -@Example("thrust the last spawned TNT in the horizontal direction of the player with speed 0.2") -@Example("push the player upwards and horizontally forward at speed 0.5") -@Example("push the clicked entity in in the direction of the player at speed -0.5") -@Example("open the inventory of the block 2 blocks below the player to the player") -@Example("teleport the clicked entity behind the player") -@Example("grow a regular tree 2 meters horizontally behind the player") -@Since("1.0 (basic), 2.0 (extended)") -public class ExprDirection extends SimpleExpression { - - private final static BlockFace[] byMark = new BlockFace[] { - BlockFace.UP, BlockFace.DOWN, - BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST, - BlockFace.NORTH_EAST, BlockFace.NORTH_WEST, BlockFace.SOUTH_EAST, BlockFace.SOUTH_WEST}; - private final static int UP = 0, DOWN = 1, - NORTH = 2, SOUTH = 3, EAST = 4, WEST = 5, - NORTH_EAST = 6, NORTH_WEST = 7, SOUTH_EAST = 8, SOUTH_WEST = 9; - - static { - // TODO think about parsing statically & dynamically (also in general) - // "at": see LitAt - // TODO direction of %location% (from|relative to) %location% - Skript.registerExpression(ExprDirection.class, Direction.class, ExpressionType.COMBINED, - "[%-number% [(block|met(er|re))[s]] [to the]] (" + - NORTH + "¦north[(-| |)(" + (NORTH_EAST ^ NORTH) + "¦east|" + (NORTH_WEST ^ NORTH) + "¦west)][(ward(s|ly|)|er(n|ly|))] [of]" + - "|" + SOUTH + "¦south[(-| |)(" + (SOUTH_EAST ^ SOUTH) + "¦east|" + (SOUTH_WEST ^ SOUTH) + "¦west)][(ward(s|ly|)|er(n|ly|))] [of]" + - "|(" + EAST + "¦east|" + WEST + "¦west)[(ward(s|ly|)|er(n|ly|))] [of]" + - "|" + UP + "¦above|" + UP + "¦over|(" + UP + "¦up|" + DOWN + "¦down)[ward(s|ly|)]|" + DOWN + "¦below|" + DOWN + "¦under[neath]|" + DOWN + "¦beneath" + - ") [%-direction%]", - "[%-number% [(block|met(er|re))[s]]] in [the] (0¦direction|1¦horizontal direction|2¦facing|3¦horizontal facing) of %entity/block% (of|from|)", - "[%-number% [(block|met(er|re))[s]]] in %entity/block%'[s] (0¦direction|1¦horizontal direction|2¦facing|3¦horizontal facing) (of|from|)", - "[%-number% [(block|met(er|re))[s]]] (0¦in[ ]front [of]|0¦forward[s]|2¦behind|2¦backwards|[to the] (1¦right|-1¦left) [of])", - "[%-number% [(block|met(er|re))[s]]] horizontal[ly] (0¦in[ ]front [of]|0¦forward[s]|2¦behind|2¦backwards|to the (1¦right|-1¦left) [of])"); - } - - @Nullable - Expression amount; - - @Nullable - private Vector direction; - @Nullable - private ExprDirection next; - - @Nullable - private Expression relativeTo; - boolean horizontal; - boolean facing; - - private double yaw; - - @SuppressWarnings("unchecked") - @Override - public boolean init(final Expression[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) { - amount = (Expression) exprs[0]; - switch (matchedPattern) { - case 0: - direction = new Vector(byMark[parseResult.mark].getModX(), byMark[parseResult.mark].getModY(), byMark[parseResult.mark].getModZ()); - if (exprs[1] != null) { - if (!(exprs[1] instanceof ExprDirection) || ((ExprDirection) exprs[1]).direction == null) - return false; - next = (ExprDirection) exprs[1]; - } - break; - case 1: - case 2: - relativeTo = exprs[1]; - horizontal = parseResult.mark % 2 != 0; - facing = parseResult.mark >= 2; - break; - case 3: - case 4: - yaw = Math.PI / 2 * parseResult.mark; - horizontal = matchedPattern == 4; - } - return true; - } - - @Override - @Nullable - protected Direction[] get(final Event e) { - final Number n = amount != null ? amount.getSingle(e) : 1; - if (n == null) - return new Direction[0]; - final double ln = n.doubleValue(); - if (direction != null) { - final Vector v = direction.clone().multiply(ln); - ExprDirection d = next; - while (d != null) { - final Number n2 = d.amount != null ? d.amount.getSingle(e) : 1; - if (n2 == null) - return new Direction[0]; - assert d.direction != null; // checked in init() - v.add(d.direction.clone().multiply(n2.doubleValue())); - d = d.next; - } - assert v != null; - return new Direction[] {new Direction(v)}; - } else if (relativeTo != null) { - final Object o = relativeTo.getSingle(e); - if (o == null) - return new Direction[0]; - if (o instanceof Block) { - final BlockFace f = Direction.getFacing((Block) o); - if (f == BlockFace.SELF || horizontal && (f == BlockFace.UP || f == BlockFace.DOWN)) - return new Direction[] {Direction.ZERO}; - return new Direction[] {new Direction(f, ln)}; - } else { - final Location l = ((Entity) o).getLocation(); - if (!horizontal) { - if (!facing) { - final Vector v = l.getDirection().normalize().multiply(ln); - assert v != null; - return new Direction[] {new Direction(v)}; - } - final double pitch = Direction.pitchToRadians(l.getPitch()); - assert pitch >= -Math.PI / 2 && pitch <= Math.PI / 2; - if (pitch > Math.PI / 4) - return new Direction[] {new Direction(new double[] {0, ln, 0})}; - if (pitch < -Math.PI / 4) - return new Direction[] {new Direction(new double[] {0, -ln, 0})}; - } - double yaw = Direction.yawToRadians(l.getYaw()); - if (horizontal && !facing) { - return new Direction[] {new Direction(new double[] {Math.cos(yaw) * ln, 0, Math.sin(yaw) * ln})}; - } - yaw = Math2.mod(yaw, 2 * Math.PI); - if (yaw >= Math.PI / 4 && yaw < 3 * Math.PI / 4) - return new Direction[] {new Direction(new double[] {0, 0, ln})}; - if (yaw >= 3 * Math.PI / 4 && yaw < 5 * Math.PI / 4) - return new Direction[] {new Direction(new double[] {-ln, 0, 0})}; - if (yaw >= 5 * Math.PI / 4 && yaw < 7 * Math.PI / 4) - return new Direction[] {new Direction(new double[] {0, 0, -ln})}; - assert yaw >= 0 && yaw < Math.PI / 4 || yaw >= 7 * Math.PI / 4 && yaw < 2 * Math.PI; - return new Direction[] {new Direction(new double[] {ln, 0, 0})}; - } - } else { - return new Direction[] {new Direction(horizontal ? Direction.IGNORE_PITCH : 0, yaw, ln)}; - } - } - - @Override - public boolean isSingle() { - return true; - } - - @Override - public Class getReturnType() { - return Direction.class; - } - - @Override - public String toString(final @Nullable Event e, final boolean debug) { - final Expression relativeTo = this.relativeTo; - return (amount != null ? amount.toString(e, debug) + " meter(s) " : "") + (direction != null ? Direction.toString(direction) : - relativeTo != null ? " in " + (horizontal ? "horizontal " : "") + (facing ? "facing" : "direction") + " of " + relativeTo.toString(e, debug) : - (horizontal ? "horizontally " : "") + Direction.toString(0, yaw, 1)); - } - -} diff --git a/src/main/java/ch/njol/skript/expressions/ExprVectorFromDirection.java b/src/main/java/ch/njol/skript/expressions/ExprVectorFromDirection.java index b75881118c8..90af64f8ba8 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprVectorFromDirection.java +++ b/src/main/java/ch/njol/skript/expressions/ExprVectorFromDirection.java @@ -14,6 +14,7 @@ import org.bukkit.event.Event; import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.misc.elements.expressions.ExprDirection; @Name("Vectors - Create from Direction") @Description({ diff --git a/src/main/java/org/skriptlang/skript/bukkit/block/BlockModule.java b/src/main/java/org/skriptlang/skript/bukkit/block/BlockModule.java index a3180678d40..d8b4dd4ed94 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/block/BlockModule.java +++ b/src/main/java/org/skriptlang/skript/bukkit/block/BlockModule.java @@ -3,6 +3,7 @@ import org.skriptlang.skript.addon.AddonModule; import org.skriptlang.skript.addon.HierarchicalAddonModule; import org.skriptlang.skript.addon.SkriptAddon; +import org.skriptlang.skript.bukkit.block.blockdata.BlockDataModule; import org.skriptlang.skript.bukkit.block.furnace.FurnaceModule; import org.skriptlang.skript.bukkit.block.sign.SignModule; @@ -17,6 +18,7 @@ public BlockModule(AddonModule parentModule) { @Override public Iterable children() { return List.of( + new BlockDataModule(this), new FurnaceModule(this), new SignModule(this) ); diff --git a/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataClassInfo.java new file mode 100644 index 00000000000..8d194b4cd31 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataClassInfo.java @@ -0,0 +1,82 @@ +package org.skriptlang.skript.bukkit.block.blockdata; + +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.classes.Parser; +import ch.njol.skript.classes.Serializer; +import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.util.BlockUtils; +import ch.njol.yggdrasil.Fields; +import org.bukkit.Bukkit; +import org.bukkit.block.data.BlockData; +import org.jetbrains.annotations.Nullable; + +import java.io.StreamCorruptedException; + +public class BlockDataClassInfo extends ClassInfo { + + public BlockDataClassInfo() { + super(BlockData.class, "blockdata"); + this.user("block ?datas?") + .name("Block Data") + .description(""" + Block data is the detailed information about a block, referred to in Minecraft as BlockStates, \ + allowing for the manipulation of different aspects of the block, including shape, waterlogging, \ + direction the block is facing, and so much more. + Information regarding each block's optional data can be found on Minecraft's Wiki. Find the block you're \ + looking for and scroll down to 'Block States'. Different states must be separated by a semicolon (see examples). + The 'minecraft:' namespace is optional, as well as are underscores. + """) + .examples("set block at player to campfire[lit=false]", + "set target block of player to oak stairs[facing=north;waterlogged=true]", + "set block at player to grass_block[snowy=true]", + "set loop-block to minecraft:chest[facing=north]", + "set block above player to oak_log[axis=y]", + "set target block of player to minecraft:oak_leaves[distance=2;persistent=false]") + .after("itemtype") + .since("2.5") + .parser(new Parser<>() { + @Override + public @Nullable BlockData parse(String input, ParseContext context) { + return BlockUtils.createBlockData(input); + } + + @Override + public String toString(BlockData blockData, int flags) { + return blockData.getAsString().replace(",", ";"); + } + + @Override + public String toVariableNameString(BlockData blockData) { + return "blockdata:" + blockData.getAsString(); + } + }) + .serializer(new Serializer<>() { + @Override + public Fields serialize(BlockData blockData) { + return Fields.singletonObject("blockdata", blockData.getAsString()); + } + + @Override + protected BlockData deserialize(Fields fields) throws StreamCorruptedException { + String data = fields.getObject("blockdata", String.class); + assert data != null; + try { + return Bukkit.createBlockData(data); + } catch (IllegalArgumentException ex) { + throw new StreamCorruptedException("Invalid block data: " + data); + } + } + + @Override + public boolean mustSyncDeserialization() { + return true; + } + + @Override + protected boolean canBeInstantiated() { + return false; + } + }).cloner(BlockData::clone); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataHolder.java b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataHolder.java new file mode 100644 index 00000000000..de4d789d3db --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataHolder.java @@ -0,0 +1,230 @@ +package org.skriptlang.skript.bukkit.block.blockdata; + +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.lang.SyntaxElement; +import ch.njol.util.StringUtils; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.BlockDisplay; +import org.bukkit.entity.FallingBlock; +import org.bukkit.inventory.meta.BlockDataMeta; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Helper class for listing types that contain {@link BlockData}. + * @param The object type that contains {@link BlockData}. + */ +public interface BlockDataHolder { + + /** + * {@link BlockDataHolder} for {@link Block}s. + */ + BlockDataHolder BLOCK = new BlockDataHolder<>() { + // + @Override + public String getPattern(boolean plural) { + return plural ? "blocks" : "block"; + } + + @Override + public Class getTypeClass() { + return Block.class; + } + + @Override + public BlockData getBlockData(Block block) { + return block.getBlockData(); + } + + @Override + public void setBlockData(Block block, BlockData blockData) { + block.setBlockData(blockData); + } + // + }; + + /** + * {@link BlockDataHolder} for {@link BlockData}s. + */ + BlockDataHolder BLOCK_DATA = new BlockDataHolder<>() { + // + @Override + public String getPattern(boolean plural) { + return plural ? "blockdatas" : "blockdata"; + } + + @Override + public Class getTypeClass() { + return BlockData.class; + } + + @Override + public BlockData getBlockData(BlockData blockData) { + return blockData; + } + + @Override + public void setBlockData(BlockData blockData, BlockData blockData2) { + blockData2.copyTo(blockData); + } + // + }; + + /** + * {@link BlockDataHolder} for {@link ItemType}s. + */ + BlockDataHolder ITEMTYPE = new BlockDataHolder<>() { + // + @Override + public String getPattern(boolean plural) { + return plural ? "itemtypes" : "itemtype"; + } + + @Override + public Class getTypeClass() { + return ItemType.class; + } + + @Override + public boolean isType(Object object) { + return object instanceof ItemType itemType && itemType.getItemMeta() instanceof BlockDataMeta && itemType.getMaterial().isBlock(); + } + + @Override + public BlockData getBlockData(ItemType itemType) { + return ((BlockDataMeta) itemType.getItemMeta()).getBlockData(itemType.getMaterial()); + } + + @Override + public void setBlockData(ItemType itemType, BlockData blockData) { + BlockDataMeta blockDataMeta = (BlockDataMeta) itemType.getItemMeta(); + blockDataMeta.setBlockData(blockData); + itemType.setItemMeta(blockDataMeta); + } + // + }; + + /** + * {@link BlockDataHolder} for {@link BlockDisplay}s. + */ + BlockDataHolder BLOCK_DISPLAY = new BlockDataHolder<>() { + // + @Override + public String getPattern(boolean plural) { + return plural ? "displays" : "display"; + } + + @Override + public Class getTypeClass() { + return BlockDisplay.class; + } + + @Override + public BlockData getBlockData(BlockDisplay blockDisplay) { + return blockDisplay.getBlock(); + } + + @Override + public void setBlockData(BlockDisplay blockDisplay, BlockData blockData) { + blockDisplay.setBlock(blockData); + } + // + }; + + /** + * {@link BlockDataHolder} for {@link FallingBlock}s. + */ + BlockDataHolder FALLING_BLOCK = new BlockDataHolder<>() { + // + @Override + public String getPattern(boolean plural) { + return plural ? "entities" : "entity"; + } + + @Override + public Class getTypeClass() { + return FallingBlock.class; + } + + @Override + public BlockData getBlockData(FallingBlock fallingBlock) { + return fallingBlock.getBlockData(); + } + + @Override + public void setBlockData(FallingBlock fallingBlock, BlockData blockData) { + fallingBlock.setBlockData(blockData); + } + // + }; + + /** + * List of all {@link BlockDataHolder}s currently supported. + */ + List> HOLDERS = List.of(BLOCK, BLOCK_DATA, ITEMTYPE, BLOCK_DISPLAY, FALLING_BLOCK); + + /** + * Combined pattern of all {@link BlockDataHolder}s plural type with "/". + * Used for patterns when registering a {@link SyntaxElement}. + */ + String PLURAL_PATTERN_TYPES = StringUtils.join(HOLDERS.stream() + .map(holder -> holder.getPattern(true)).collect(Collectors.toSet()), + "/"); + + /** + * Retrieves the {@link BlockDataHolder} that handles {@code object}. + * @param object The {@link Object} to get a {@link BlockDataHolder} for. + * @return The resulting {@link BlockDataHolder} if found, otherwise {@code null}. + */ + static @Nullable BlockDataHolder getHolder(Object object) { + for (BlockDataHolder holder : HOLDERS) { + if (holder.isType(object)) + return holder; + } + return null; + } + + /** + * @return The singular pattern used for registering {@link SyntaxElement}s. + */ + default String getPattern() { + return getPattern(false); + } + + /** + * @param plural Whether the returned pattern should be plural or singular. + * @return The resulting singular or plural pattern used for registering {@link SyntaxElement}s. + */ + String getPattern(boolean plural); + + /** + * @return The {@link Class} {@code this} handles. + */ + Class getTypeClass(); + + /** + * Whether {@code object} is handled by {@code this}. + * @param object The {@link Object} to check. + * @return {@code true} if handled, otherwise {@code false}. + */ + default boolean isType(Object object) { + return getTypeClass().isInstance(object); + } + + /** + * @param type The object to get the {@link BlockData} from. + * @return The {@link BlockData} of {@code type}. + */ + BlockData getBlockData(Type type); + + /** + * Sets the {@link BlockData} on {@code type} to {@code blockData}. + * @param type The object to change the {@link BlockData} of. + * @param blockData The {@link BlockData} to change to. + */ + void setBlockData(Type type, BlockData blockData); + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataModule.java b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataModule.java new file mode 100644 index 00000000000..91c87469eea --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataModule.java @@ -0,0 +1,38 @@ +package org.skriptlang.skript.bukkit.block.blockdata; + +import ch.njol.skript.registrations.Classes; +import org.skriptlang.skript.addon.AddonModule; +import org.skriptlang.skript.addon.HierarchicalAddonModule; +import org.skriptlang.skript.addon.SkriptAddon; +import org.skriptlang.skript.bukkit.block.blockdata.elements.CondBlockDataTag; +import org.skriptlang.skript.bukkit.block.blockdata.elements.ExprBlockData; +import org.skriptlang.skript.bukkit.block.blockdata.elements.ExprBlockDataTags; +import org.skriptlang.skript.bukkit.block.blockdata.elements.ExprBlockDataValues; + +public class BlockDataModule extends HierarchicalAddonModule { + + public BlockDataModule(AddonModule parent) { + super(parent); + } + + @Override + protected void initSelf(SkriptAddon addon) { + Classes.registerClass(new BlockDataClassInfo()); + } + + @Override + protected void loadSelf(SkriptAddon addon) { + register(addon, + CondBlockDataTag::register, + ExprBlockData::register, + ExprBlockDataTags::register, + ExprBlockDataValues::register + ); + } + + @Override + public String name() { + return "blockdata"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataTag.java b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataTag.java new file mode 100644 index 00000000000..d9766944d70 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataTag.java @@ -0,0 +1,238 @@ +package org.skriptlang.skript.bukkit.block.blockdata; + +import org.bukkit.Bukkit; +import org.bukkit.block.data.BlockData; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Container for holding the key, raw value, and typed value object of a {@link BlockData} tag. + * @see BlockDataValueType for typed value objects other than a string. + */ +public class BlockDataTag { + + private static final Pattern BLOCKDATA_PATTERN = Pattern.compile(".*\\[(?(?:.+=.+,?)+)?]"); + + /** + * Gets a {@link BlockDataTag} matching {@code key} from {@code blockData}. + * @param blockData The {@link BlockData} to get the {@link BlockDataTag} from. + * @param key The key of the tag to retrieve. + * @return The resulting {@link BlockDataTag} if found, otherwise {@code null}. + */ + public static @Nullable BlockDataTag of(BlockData blockData, String key) { + BlockDataTag[] tags = of(blockData); + if (tags == null) + return null; + for (BlockDataTag tag : tags) { + if (tag.key.equals(key)) + return tag; + } + return null; + } + + /** + * Gets all {@link BlockDataTag}s matching any of the {@code keys} from {@code blockData}. + * @param blockData The {@link BlockData} to get the {@link BlockDataTag}s from. + * @param keys The keys of the tags to retrieve. + * @return The resulting {@link BlockDataTag}s if any are found, otherwise {@code null}. + */ + public static BlockDataTag @Nullable [] of(BlockData blockData, Collection keys) { + return of(blockData, keys.toArray(String[]::new)); + } + + /** + * Gets all {@link BlockDataTag}s matching any of the {@code keys} from {@code blockData}. + * @param blockData The {@link BlockData} to get the {@link BlockDataTag}s from. + * @param keys The keys of the tags to retrieve. + * @return The resulting {@link BlockDataTag}s if any are found, otherwise {@code null}. + */ + public static BlockDataTag @Nullable [] of(BlockData blockData, String... keys) { + BlockDataTag[] tags = of(blockData); + if (tags == null) + return null; + return Arrays.stream(tags) + .filter(tag -> Arrays.stream(keys).anyMatch(key -> key.equalsIgnoreCase(tag.getKey()))) + .toArray(BlockDataTag[]::new); + } + + /** + * Gets all {@link BlockDataTag}s from {@code blockData}. + * @param blockData The {@link BlockData} to get the {@link BlockDataTag}s from. + * @return The resulting {@link BlockDataTag}s if any exist, otherwise {@code null}. + */ + public static BlockDataTag @Nullable [] of(BlockData blockData) { + String[] tags = getTags(blockData); + if (tags == null || tags.length == 0) + return null; + List dataTags = new ArrayList<>(); + for (String tag : tags) { + String[] split = tag.split("="); + assert split.length >= 2; + dataTags.add(new BlockDataTag(split[0], split[1])); + } + return dataTags.toArray(BlockDataTag[]::new); + } + + /** + * Gets all the tags of {@code blockData}. + * @param blockData The {@link BlockData} to get the tags from. + * @return The resulting {@link String} array of the tags in the form of "key=value". + */ + private static String @Nullable [] getTags(BlockData blockData) { + String dataString = blockData.getAsString(false); + Matcher matcher = BLOCKDATA_PATTERN.matcher(dataString); + if (!matcher.matches()) + return null; + String tagGroup = matcher.group("tags"); + if (tagGroup == null || tagGroup.isBlank()) + return null; + return tagGroup.split(","); + } + + private final String key; + private String rawValue; + private Object value; + private BlockDataValueType valueType = BlockDataValueType.STRING; + + /** + * Construct a new {@link BlockDataTag} with the data from a {@link BlockData}'s tag. + * @param key The key of a {@link BlockData} tag. + * @param rawValue The raw/string value of a {@link BlockData} tag. + */ + public BlockDataTag(String key, String rawValue) { + this.key = key.toLowerCase(Locale.ENGLISH); + this.rawValue = rawValue; + this.value = rawValue; + setValueType(); + } + + /** + * Checks if the {@link #rawValue} can be parsed as one of the {@link BlockDataValueType}s. + */ + private void setValueType() { + if (rawValue == null) + return; + for (BlockDataValueType type : BlockDataValueType.TYPES) { + if (type == BlockDataValueType.STRING) + continue; + Object newValue = type.parse(rawValue); + if (newValue != null) { + value = newValue; + valueType = type; + return; + } + } + } + + /** + * @return The key of the {@link BlockData} tag used to construct {@code this}. + */ + public String getKey() { + return key; + } + + /** + * @return The raw/string value used for {@link #key}. + */ + public String getRawValue() { + return rawValue; + } + + /** + * @return The value for {@link #key}. Can be a {@link String} or any of the types in {@link BlockDataValueType}. + */ + public Object getValue() { + return value; + } + + /** + * @return The {@link BlockDataValueType} if the {@link #rawValue} successfully parsed, otherwise {@code null}. + */ + public @Nullable BlockDataValueType getValueType() { + return valueType; + } + + /** + * Attempts to change the value of {@code this} by ensuring {@code value} can be parsed with {@link #valueType} if not {@code null}. + * @param value The value to change to. + * @return {@code true} if the change was successful, otherwise {@code false}. + */ + public boolean attemptValueChange(@Nullable Object value) { + // Regardless of type, value can be changed to null + if (value == null) { + this.value = null; + this.rawValue = null; + return true; + } + // Parses the value as the intended type + Object newValue = valueType.parse(value); + if (newValue != null) { + this.value = newValue; + this.rawValue = newValue.toString(); + return true; + } + return false; + } + + /** + * Whether the {@link #valueType} of {@code this} can be checked to ensure a value is valid. + * @return {@code true} if can be checked, otherwise {@code false}. + */ + public boolean hasValidityCheck() { + return valueType.hasValidityCheck(); + } + + /** + * Checks the validity of {@link #value} to ensure {@code blockData} supports it. + * @param blockData The {@link BlockData} to check if it supports {@link #value}. + * @return {@code true} if it's supported, otherwise {@code false}. + */ + public boolean checkValidity(BlockData blockData) { + String dataString = blockData.getMaterial().getKey() + "[" + this + "]"; + try { + Bukkit.createBlockData(dataString); + return true; + } catch (Exception ignored) {} + return false; + } + + /** + * @return {@link #rawValue} or string representation of the converted value defined by {@link BlockDataValueType#toStringConversion(Object)}. + */ + public String getConversionString() { + if (rawValue == null) + return ""; + if (valueType.requiresConversion()) { + //noinspection unchecked + return valueType.toStringConversion(value); + } + return rawValue; + } + + @Override + public int hashCode() { + return valueType.hashCode() + key.hashCode() + value.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof BlockDataTag other)) + return false; + return valueType == other.valueType && key.equalsIgnoreCase(other.key) && value.equals(other.value); + } + + @Override + public String toString() { + if (rawValue == null) + return ""; + return key + "=" + getConversionString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataValueType.java b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataValueType.java new file mode 100644 index 00000000000..213176451a5 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataValueType.java @@ -0,0 +1,187 @@ +package org.skriptlang.skript.bukkit.block.blockdata; + +import ch.njol.skript.Skript; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.util.ContextlessEvent; +import ch.njol.skript.log.RetainingLogHandler; +import ch.njol.skript.log.SkriptLogger; +import ch.njol.skript.util.Direction; +import ch.njol.util.coll.iterator.SingleItemIterator; +import org.bukkit.Bukkit; +import org.bukkit.block.data.BlockData; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.misc.elements.expressions.ExprDirection; + +import java.util.List; +import java.util.Locale; + +/** + * Helper class for determining the value of a {@link BlockData} tag can be an object other than a {@link String}. + * @param The type of value + */ +public interface BlockDataValueType { + + /** + * {@link BlockDataValueType} for {@link BlockData} tags with {@link Integer} values. + */ + BlockDataValueType INTEGER = new BlockDataValueType<>() { + @Override + public Class getTypeClass() { + return Integer.class; + } + + @Override + public @Nullable Integer parse(String string) { + return string.matches("\\d+") ? Integer.parseInt(string) : null; + } + }; + + /** + * {@link BlockDataValueType} for {@link BlockData} tags with {@link Boolean} values. + */ + BlockDataValueType BOOLEAN = new BlockDataValueType<>() { + @Override + public Class getTypeClass() { + return Boolean.class; + } + + @Override + public @Nullable Boolean parse(String string) { + return (string.equalsIgnoreCase("true") || string.equalsIgnoreCase("false")) + ? Boolean.valueOf(string) : null; + } + + @Override + public boolean hasValidityCheck() { + return false; + } + }; + + /** + * {@link BlockDataValueType} for {@link BlockData} tags with {@link Direction} values. + */ + BlockDataValueType DIRECTION = new BlockDataValueType<>() { + + @Override + public Class getTypeClass() { + return Direction.class; + } + + @Override + public @Nullable Direction parse(String string) { + RetainingLogHandler logHandler = SkriptLogger.startRetainingLog(); + Expression expr = null; + try { + expr = SkriptParser.parseStatic(string.replace("_", " "), new SingleItemIterator<>(ExprDirection.syntaxInfo), ""); + } finally { + logHandler.clear(); + logHandler.printErrors(); + } + if (expr == null || !expr.getReturnType().equals(Direction.class)) + return null; + return (Direction) expr.getSingle(ContextlessEvent.get()); + } + + @Override + public boolean requiresConversion() { + return true; + } + + @Override + public String toStringConversion(Direction direction) { + return Direction.toNearestBlockFace(direction.getDirection()).toString().toLowerCase(Locale.ENGLISH); + } + + }; + + + /** + * {@link BlockDataValueType} for {@link BlockData} tags with {@link String} values. + * This is the default value type and will match against any value. + */ + BlockDataValueType STRING = new BlockDataValueType<>() { + @Override + public Class getTypeClass() { + return String.class; + } + + @Override + public @Nullable String parse(Object object) { + if (object instanceof String string) + return string; + return object.toString(); + } + + @Override + public @Nullable String parse(String string) { + return string; + } + + }; + + /** + * List of all {@link BlockDataValueType}s currently supported. + */ + List> TYPES = List.of(INTEGER, BOOLEAN, DIRECTION, STRING); + + /** + * List of all {@link Class}es that all {@link BlockDataValueType}s handle. + */ + Class[] TYPE_CLASSES = TYPES.stream().map(BlockDataValueType::getTypeClass).toArray(Class[]::new); + + /** + * @return The {@link Class} {@code this} is bound to. + */ + Class getTypeClass(); + + /** + * Checks if {@code object} is instance of {@code TYPE} or a {@link String} that can be parsed into {@code TYPE}. + * @param object The {@link Object} to parse. + * @return The parsed {@code TYPE} if successful, otherwise {@code null}. + */ + @SuppressWarnings("unchecked") + default @Nullable Type parse(Object object) { + if (getTypeClass().isInstance(object)) { + return (Type) object; + } else if (object instanceof String string) { + return parse(string); + } + return null; + } + + /** + * Attempts to parse {@code string} into {@code TYPE}. + * @param string The {@link String} to parse. + * @return The parsed {@code TYPE} if successful, otherwise {@code null}. + */ + @Nullable Type parse(String string); + + /** + * Whether {@code this} requires converting {@code Type} for stringification + * via {@link #toStringConversion(Object)} + * @return {@code true} if conversion required, otherwise {@code false}. + */ + default boolean requiresConversion() { + return false; + } + + /** + * Converts {@code type} for specified stringification. + * Primarily for converting {@link Skript} types into {@link Bukkit} types and to be used in {@link BlockData}. + * @param type The {@code Type} handled by {@code this}. + * @return The converted string representation/ + */ + default @Nullable String toStringConversion(Type type) { + return null; + } + + /** + * Whether {@code this} can be checked to ensure a value is valid. + * @return {@code true} if can be checked, otherwise {@code false}. + */ + default boolean hasValidityCheck() { + return true; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/CondBlockDataTag.java b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/CondBlockDataTag.java new file mode 100644 index 00000000000..0d21b0b6014 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/CondBlockDataTag.java @@ -0,0 +1,88 @@ +package org.skriptlang.skript.bukkit.block.blockdata.elements; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.block.blockdata.BlockDataHolder; +import org.skriptlang.skript.bukkit.block.blockdata.BlockDataTag; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.Arrays; + +@Name("Has Block Data") +@Description("Whether the blockdata of a block or block related object has the specified tag.") +@Example(""" + if {_block} has blockdata "waterlogged": + set the blockdata "waterlogged" of {_block} to true + """) +@Example(""" + if {_stairs} is tagged with "facing" blockdata: + set the blockdata tag "facing" of {_stairs} to "north" + """) +@Since("INSERT VERSISON") +public class CondBlockDataTag extends Condition { + + public static void register(SyntaxRegistry registry) { + String types = "%" + BlockDataHolder.PLURAL_PATTERN_TYPES + "%"; + registry.register(SyntaxRegistry.CONDITION, SyntaxInfo.simple( + CondBlockDataTag.class, + CondBlockDataTag::new, + types + " (has|have) [the] block[ ]data [tag[s]] %strings%", + types + " (is|are) tagged with [the] block[ ]data [tag[s]] %strings%", + types + "(is|are) tagged with %strings% block[ ]data", + types + " (does not|doesn't|do not| don't) have [the] block[ ]data [tag[s]] %strings%", + types + " (is not|isn't|are not|aren't) tagged with [the] block[ ]data [tag[s]] %strings%", + types + " (is not|isn't|are not|aren't) tagged with %strings% block[ ]data" + )); + } + + private Expression objects; + private Expression strings; + private boolean negate; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + objects = exprs[0]; + //noinspection unchecked + strings = (Expression) exprs[1]; + negate = matchedPattern >= 3; + return true; + } + + @Override + public boolean check(Event event) { + String[] strings = this.strings.getArray(event); + return objects.check(event, object -> { + BlockDataHolder holder = BlockDataHolder.getHolder(object); + if (holder == null) + return false; + //noinspection unchecked + BlockDataTag[] tags = BlockDataTag.of(holder.getBlockData(object), strings); + if (tags == null) + return negate; + return SimpleExpression.check(strings, string -> Arrays.stream(tags).anyMatch(tag -> tag.getKey().equalsIgnoreCase(string)), + negate, this.strings.getAnd()); + }); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return new SyntaxStringBuilder(event, debug) + .append(objects) + .append(objects.isSingle() ? "does" : "do") + .appendIf(negate, "not") + .append("have blockdata tags", strings) + .toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/ExprBlockData.java b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/ExprBlockData.java new file mode 100644 index 00000000000..58495216dbf --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/ExprBlockData.java @@ -0,0 +1,98 @@ +package org.skriptlang.skript.bukkit.block.blockdata.elements; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.Bukkit; +import org.bukkit.block.data.BlockData; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.block.blockdata.BlockDataHolder; +import org.skriptlang.skript.registration.SyntaxRegistry; + +@Name("Block Data") +@Description(""" + The block data associated with a block or block related objects. \ + (i.e. blocks, block displays, falling blocks, items) + """) +@Example("set {_data} to block data of target block") +@Example("set block at player to {_data}") +@Example("set block data of target block to oak_stairs[facing=south;waterlogged=true]") +@Example("reset the blockdata of {_block}") +@Since({ + "2.5", + "2.5.2 (set)", + "2.10 (block displays)", + "INSERT VERSION (items, reset)" +}) +public class ExprBlockData extends SimplePropertyExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, infoBuilder( + ExprBlockData.class, + BlockData.class, + "block[ ]data", + BlockDataHolder.PLURAL_PATTERN_TYPES, + false + ).supplier(ExprBlockData::new) + .build()); + } + + @Override + public @Nullable BlockData convert(Object object) { + BlockDataHolder holder = BlockDataHolder.getHolder(object); + if (holder == null) + return null; + //noinspection unchecked + return holder.getBlockData(object); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.SET) { + return CollectionUtils.array(BlockData.class); + } else if (mode == ChangeMode.RESET) { + return CollectionUtils.array(); + } + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + BlockData newBlockData = delta == null ? null : ((BlockData) delta[0]); + for (Object object : getExpr().getArray(event)) { + BlockDataHolder holder = BlockDataHolder.getHolder(object); + if (holder == null) + continue; + if (newBlockData != null) { + //noinspection unchecked + holder.setBlockData(object, newBlockData); + } + if (mode != ChangeMode.RESET) + continue; + //noinspection unchecked + BlockData blockData = holder.getBlockData(object); + String dataString = blockData.getMaterial().getKey() + "[]"; + try { + BlockData newData = Bukkit.createBlockData(dataString); + //noinspection unchecked + holder.setBlockData(object, newData); + } catch (Exception ignored) {} + } + } + + @Override + public Class getReturnType() { + return BlockData.class; + } + + @Override + protected String getPropertyName() { + return "block data"; + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/ExprBlockDataTags.java b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/ExprBlockDataTags.java new file mode 100644 index 00000000000..fd35b1bf2df --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/ExprBlockDataTags.java @@ -0,0 +1,109 @@ +package org.skriptlang.skript.bukkit.block.blockdata.elements; + +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.Bukkit; +import org.bukkit.block.data.BlockData; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.block.blockdata.BlockDataHolder; +import org.skriptlang.skript.bukkit.block.blockdata.BlockDataTag; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@Name("Block Data Tags") +@Description("All the tags of a block or block related object's blockdata.") +@Example("set {_tags::*} to all of the blockdata tags of block at location(0, 0, 0)") +@Example("set {_tags::*} to all of the blockdata tags of an oak slab") +@Example("reset the blockdata tags of {_block}") +@Since("INSERT VERSION") +public class ExprBlockDataTags extends SimpleExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, SyntaxInfo.Expression.simple( + ExprBlockDataTags.class, + ExprBlockDataTags::new, + String.class, + "[all [of the]|the] block[ ]data tags of %" + BlockDataHolder.PLURAL_PATTERN_TYPES + "%" + )); + } + + private Expression objects; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + objects = exprs[0]; + return true; + } + + @Override + protected String @Nullable [] get(Event event) { + List dataTags = new ArrayList<>(); + this.objects.stream(event).forEach(object -> { + BlockDataHolder holder = BlockDataHolder.getHolder(object); + if (holder == null) + return; + //noinspection unchecked + BlockDataTag[] tags = BlockDataTag.of(holder.getBlockData(object)); + if (tags == null) + return; + Arrays.stream(tags).forEach(tag -> dataTags.add(tag.getKey())); + }); + return dataTags.toArray(String[]::new); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.RESET) + return CollectionUtils.array(); + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + for (Object object : objects.getArray(event)) { + BlockDataHolder holder = BlockDataHolder.getHolder(object); + if (holder == null) + continue; + //noinspection unchecked + BlockData blockData = holder.getBlockData(object); + String dataString = blockData.getMaterial().getKey() + "[]"; + try { + BlockData newData = Bukkit.createBlockData(dataString); + //noinspection unchecked + holder.setBlockData(object, newData); + } catch (Exception ignored) {} + } + } + + @Override + public boolean isSingle() { + return false; + } + + @Override + public Class getReturnType() { + return String.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return new SyntaxStringBuilder(event, debug) + .append("all of the blockdata tags of", objects) + .toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/ExprBlockDataValues.java b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/ExprBlockDataValues.java new file mode 100644 index 00000000000..7a9cfd4e41d --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/ExprBlockDataValues.java @@ -0,0 +1,240 @@ +package org.skriptlang.skript.bukkit.block.blockdata.elements; + +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.util.Utils; +import ch.njol.util.Kleenean; +import ch.njol.util.StringUtils; +import ch.njol.util.coll.CollectionUtils; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.block.data.BlockData; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.bukkit.block.blockdata.BlockDataHolder; +import org.skriptlang.skript.bukkit.block.blockdata.BlockDataTag; +import org.skriptlang.skript.bukkit.block.blockdata.BlockDataValueType; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +@Name("Block Data Values") +@Description(""" + The value of a blockdata tag on a block or block related object. + Note that specific blockdata tags can return, and be changed to, objects other than strings. + Examples: + - tag "waterlogged" will always return a boolean value, and can be changed to a boolean value or string of a boolean: "true" + - tag "facing" will always return a direction value, and can be changed to a direction value or string of a direction: "north" + - tag "pickles" will always return an integer value, and can be changed to an integer value or string of an integer: "1" + """) +@Example(""" + set the blockdata tag "waterlogged" of {_campfire} to false + set block data "waterlogged" of {_campfire} to "true" + if block data value "waterlogged" of {_campfire} is "true": + # FAILS + else if the block data tag value "waterlogged" of {_campfire} is true: + # PASSES + """) +@Example(""" + set the blockdata tag "facing" of {_oakStairs} to north + set block data "facing" of {_oakStairs} to "west" + if block data value "facing" of {_oakStairs} is "west": + # FAILS + else if the block data tag value "facing" of {_oakStairs} is west: + # PASSES + """) +@Example(""" + set the blockdata tag "pickles" of {_seaPickle} to 1 + set block data "pickles" of {_seaPickle} to "5" + if block data value "pickles" of {_seaPickle} is "5": + # FAILS + else if the block data tag value "pickles" of {_seaPickle} is 5: + # PASSES + """) +@Example(""" + set the blockdata tag "half" of {_oakStairs} to "top" + if block data value "half" of {_oakStairs} is "top": + # PASSES + """) +@Example(""" + reset the block data "waterlogged", "facing", "half", "shape" of {_oakStairs} + """) +@Since("INSERT VERSION") +public class ExprBlockDataValues extends SimpleExpression { + + public static void register(SyntaxRegistry registry) { + registry.register(SyntaxRegistry.EXPRESSION, SyntaxInfo.Expression.simple( + ExprBlockDataValues.class, + ExprBlockDataValues::new, + Object.class, + "[the] block[ ]data [tag[s]] [value[s]] %strings% of %" + BlockDataHolder.PLURAL_PATTERN_TYPES + "%" + )); + } + + private Expression strings; + private Expression objects; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + strings = (Expression) exprs[0]; + objects = exprs[1]; + return true; + } + + @Override + protected Object @Nullable [] get(Event event) { + String[] strings = this.strings.getArray(event); + List values = new ArrayList<>(); + objects.stream(event).map(object -> { + BlockDataHolder holder = BlockDataHolder.getHolder(object); + if (holder == null) + return null; + //noinspection unchecked + return BlockDataTag.of(holder.getBlockData(object), strings); + }) + .filter(Objects::nonNull) + .forEach(tags -> values.addAll(Arrays.stream(tags).map(BlockDataTag::getValue).toList())); + return values.toArray(); + } + + @Override + public Class @Nullable [] acceptChange(ChangeMode mode) { + if (mode == ChangeMode.SET) { + return BlockDataValueType.TYPE_CLASSES; + } else if (mode == ChangeMode.RESET) { + return CollectionUtils.array(); + } + return null; + } + + @Override + public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { + Object change = delta != null ? delta[0] : null; + Set strings = Arrays.stream(this.strings.getArray(event)).map(String::toLowerCase).collect(Collectors.toSet()); + Map, List> rejected = new HashMap<>(); + Map, Map>> invalid = new HashMap<>(); + + for (Object object : objects.getArray(event)) { + BlockDataHolder holder = BlockDataHolder.getHolder(object); + if (holder == null) + continue; + //noinspection unchecked + BlockData blockData = holder.getBlockData(object); + BlockDataTag[] tags; + if (mode == ChangeMode.SET) { + tags = BlockDataTag.of(blockData, strings); + } else { + tags = BlockDataTag.of(blockData); + } + if (tags == null) + continue; + for (BlockDataTag tag : tags) { + if (!strings.contains(tag.getKey())) + continue; + if (!tag.attemptValueChange(change)) { + BlockDataValueType valueType = tag.getValueType(); + assert valueType != null; + rejected.computeIfAbsent(valueType, list -> new ArrayList<>()).add(tag.getKey()); + strings.remove(tag.getKey()); + } + } + String dataString = blockData.getMaterial().getKey() + "[" + + StringUtils.join(Arrays.stream(tags).filter(tag -> tag.getRawValue() != null).toList(), ",") + "]"; + try { + BlockData newData = Bukkit.createBlockData(dataString); + //noinspection unchecked + holder.setBlockData(object, mode == ChangeMode.SET ? blockData.merge(newData) : newData); + } catch (Exception exception) { + for (BlockDataTag tag : tags) { + if (!tag.hasValidityCheck()) + continue; + if (tag.checkValidity(blockData)) + continue; + invalid.computeIfAbsent(tag.getValueType(), map -> new HashMap<>()) + .computeIfAbsent(tag, list -> new ArrayList<>()) + .add(blockData.getMaterial()); + } + } + } + if (!rejected.isEmpty()) { + List messages = new ArrayList<>(); + for (Entry, List> entry : rejected.entrySet()) { + BlockDataValueType valueType = entry.getKey(); + List tags = entry.getValue(); + if (tags == null || tags.isEmpty()) + continue; + String message = "The blockdata tag"; + if (tags.size() > 1) + message += "s"; + String classInfoName = Classes.getSuperClassInfo(valueType.getTypeClass()).getName().toString(); + message += " '" + StringUtils.join(tags, ", ", ", and ") + "' can only be changed to " + Utils.a(classInfoName) + + " value."; + messages.add(message); + } + if (!messages.isEmpty()) + error(StringUtils.join(messages, "\n\t ")); + } + if (!invalid.isEmpty()) { + List messages = new ArrayList<>(); + for (Entry, Map>> entry : invalid.entrySet()) { + BlockDataValueType valueType = entry.getKey(); + Map> map = entry.getValue(); + String classInfoName = Classes.getSuperClassInfo(valueType.getTypeClass()).getName().toString(); + for (Entry> tagEntry : map.entrySet()) { + BlockDataTag tag = tagEntry.getKey(); + List materials = tagEntry.getValue(); + String message = "The blockdata tag '" + tag.getKey() + "' does not support the " + classInfoName + + " value '" + tag.getConversionString() + "' for the block type" + (materials.size() > 1 ? "s" : "") + ": " + + StringUtils.join(materials.stream() + .map(ItemType::new) + .toList(), ", ", ", and "); + messages.add(message); + } + } + if (!messages.isEmpty()) + error(StringUtils.join(messages, "\n\t ")); + } + } + + @Override + public boolean isSingle() { + return strings.isSingle() && objects.isSingle(); + } + + @Override + public Class[] possibleReturnTypes() { + return BlockDataValueType.TYPE_CLASSES; + } + + @Override + public Class getReturnType() { + return Object.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return new SyntaxStringBuilder(event, debug) + .append("the blockdata tag", strings, "of", objects) + .toString(); + } + +} diff --git a/src/main/java/org/skriptlang/skript/bukkit/misc/MiscModule.java b/src/main/java/org/skriptlang/skript/bukkit/misc/MiscModule.java index 8557a4c9d69..7566e4dd8a6 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/misc/MiscModule.java +++ b/src/main/java/org/skriptlang/skript/bukkit/misc/MiscModule.java @@ -18,6 +18,7 @@ protected void loadSelf(SkriptAddon addon) { EffRotate::register, ExprBroadcastMessage::register, ExprColorOf::register, + ExprDirection::register, ExprItemOfEntity::register, ExprMOTD::register, ExprQuaternionAxisAngle::register, diff --git a/src/main/java/org/skriptlang/skript/bukkit/misc/elements/expressions/ExprDirection.java b/src/main/java/org/skriptlang/skript/bukkit/misc/elements/expressions/ExprDirection.java new file mode 100644 index 00000000000..d3ce279e42d --- /dev/null +++ b/src/main/java/org/skriptlang/skript/bukkit/misc/elements/expressions/ExprDirection.java @@ -0,0 +1,216 @@ +package org.skriptlang.skript.bukkit.misc.elements.expressions; + +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.SyntaxStringBuilder; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.util.Direction; +import ch.njol.util.Kleenean; +import ch.njol.util.Math2; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.registration.DefaultSyntaxInfos; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; + +/** + * @author Peter Güttinger + */ +@Name("Direction") +@Description("A helper expression for the direction type.") +@Example("thrust the player upwards") +@Example("set the block behind the player to water") +@Example(""" + loop blocks above the player: + set {_rand} to a random integer between 1 and 10 + set the block {_rand} meters south east of the loop-block to stone + """) +@Example("block in horizontal facing of the clicked entity from the player is air") +@Example("spawn a creeper 1.5 meters horizontally behind the player") +@Example("spawn a TNT 5 meters above and 2 meters horizontally behind the player") +@Example("thrust the last spawned TNT in the horizontal direction of the player with speed 0.2") +@Example("push the player upwards and horizontally forward at speed 0.5") +@Example("push the clicked entity in in the direction of the player at speed -0.5") +@Example("open the inventory of the block 2 blocks below the player to the player") +@Example("teleport the clicked entity behind the player") +@Example("grow a regular tree 2 meters horizontally behind the player") +@Since("1.0 (basic), 2.0 (extended)") +public class ExprDirection extends SimpleExpression { + + public static DefaultSyntaxInfos.Expression syntaxInfo; + + private final static BlockFace[] byMark = new BlockFace[] { + BlockFace.UP, BlockFace.DOWN, + BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST, + BlockFace.NORTH_EAST, BlockFace.NORTH_WEST, BlockFace.SOUTH_EAST, BlockFace.SOUTH_WEST}; + private final static int UP = 0, DOWN = 1, + NORTH = 2, SOUTH = 3, EAST = 4, WEST = 5, + NORTH_EAST = 6, NORTH_WEST = 7, SOUTH_EAST = 8, SOUTH_WEST = 9; + + public static void register(SyntaxRegistry registry) { + // TODO think about parsing statically & dynamically (also in general) + // "at": see LitAt + // TODO direction of %location% (from|relative to) %location% + + syntaxInfo = SyntaxInfo.Expression.simple( + ExprDirection.class, + ExprDirection::new, + Direction.class, + "[%-number% [(block|met(er|re))[s]] [to the]] (" + + NORTH + "¦north[(-| |)(" + (NORTH_EAST ^ NORTH) + "¦east|" + (NORTH_WEST ^ NORTH) + "¦west)][(ward(s|ly|)|er(n|ly|))] [of]" + + "|" + SOUTH + "¦south[(-| |)(" + (SOUTH_EAST ^ SOUTH) + "¦east|" + (SOUTH_WEST ^ SOUTH) + "¦west)][(ward(s|ly|)|er(n|ly|))] [of]" + + "|(" + EAST + "¦east|" + WEST + "¦west)[(ward(s|ly|)|er(n|ly|))] [of]" + + "|" + UP + "¦above|" + UP + "¦over|(" + UP + "¦up|" + DOWN + "¦down)[ward(s|ly|)]|" + DOWN + "¦below|" + DOWN + "¦under[neath]|" + DOWN + "¦beneath" + + ") [%-direction%]", + "[%-number% [(block|met(er|re))[s]]] in [the] (0¦direction|1¦horizontal direction|2¦facing|3¦horizontal facing) of %entity/block% (of|from|)", + "[%-number% [(block|met(er|re))[s]]] in %entity/block%'[s] (0¦direction|1¦horizontal direction|2¦facing|3¦horizontal facing) (of|from|)", + "[%-number% [(block|met(er|re))[s]]] (0¦in[ ]front [of]|0¦forward[s]|2¦behind|2¦backwards|[to the] (1¦right|-1¦left) [of])", + "[%-number% [(block|met(er|re))[s]]] horizontal[ly] (0¦in[ ]front [of]|0¦forward[s]|2¦behind|2¦backwards|to the (1¦right|-1¦left) [of])" + ); + + registry.register(SyntaxRegistry.EXPRESSION, syntaxInfo); + } + + @Nullable Expression amount; + + private @Nullable Vector direction; + private @Nullable ExprDirection next; + + private @Nullable Expression relativeTo; + boolean horizontal; + boolean facing; + + private double yaw; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + //noinspection unchecked + amount = (Expression) exprs[0]; + switch (matchedPattern) { + case 0: + direction = new Vector(byMark[parseResult.mark].getModX(), byMark[parseResult.mark].getModY(), byMark[parseResult.mark].getModZ()); + if (exprs[1] != null) { + if (!(exprs[1] instanceof ExprDirection exprDirection) || (exprDirection.direction == null)) + return false; + next = (ExprDirection) exprs[1]; + } + break; + case 1: + case 2: + relativeTo = exprs[1]; + horizontal = parseResult.mark % 2 != 0; + facing = parseResult.mark >= 2; + break; + case 3: + case 4: + yaw = Math.PI / 2 * parseResult.mark; + horizontal = matchedPattern == 4; + } + return true; + } + + public @Nullable Expression getAmount() { + return amount; + } + + @Override + protected Direction @Nullable [] get(Event event) { + Number number = amount != null ? amount.getSingle(event) : 1; + if (number == null) + return new Direction[0]; + double doubleValue = number.doubleValue(); + if (direction != null) { + Vector vector = direction.clone().multiply(doubleValue); + ExprDirection exprDirection = next; + while (exprDirection != null) { + Number number1 = exprDirection.amount != null ? exprDirection.amount.getSingle(event) : 1; + if (number1 == null) + return new Direction[0]; + assert exprDirection.direction != null; // checked in init() + vector.add(exprDirection.direction.clone().multiply(number1.doubleValue())); + exprDirection = exprDirection.next; + } + return new Direction[] {new Direction(vector)}; + } else if (relativeTo != null) { + Object object = relativeTo.getSingle(event); + if (object == null) + return new Direction[0]; + if (object instanceof Block block) { + BlockFace blockFace = Direction.getFacing(block); + if (blockFace == BlockFace.SELF || horizontal && (blockFace == BlockFace.UP || blockFace == BlockFace.DOWN)) + return new Direction[] {Direction.ZERO}; + return new Direction[] {new Direction(blockFace, doubleValue)}; + } else { + Location location = ((Entity) object).getLocation(); + if (!horizontal) { + if (!facing) { + Vector vector = location.getDirection().normalize().multiply(doubleValue); + assert vector != null; + return new Direction[] {new Direction(vector)}; + } + double pitch = Direction.pitchToRadians(location.getPitch()); + assert pitch >= -Math.PI / 2 && pitch <= Math.PI / 2; + if (pitch > Math.PI / 4) + return new Direction[] {new Direction(new double[] {0, doubleValue, 0})}; + if (pitch < -Math.PI / 4) + return new Direction[] {new Direction(new double[] {0, -doubleValue, 0})}; + } + double yaw = Direction.yawToRadians(location.getYaw()); + if (horizontal && !facing) { + return new Direction[] {new Direction(new double[] {Math.cos(yaw) * doubleValue, 0, Math.sin(yaw) * doubleValue})}; + } + yaw = Math2.mod(yaw, 2 * Math.PI); + if (yaw >= Math.PI / 4 && yaw < 3 * Math.PI / 4) + return new Direction[] {new Direction(new double[] {0, 0, doubleValue})}; + if (yaw >= 3 * Math.PI / 4 && yaw < 5 * Math.PI / 4) + return new Direction[] {new Direction(new double[] {-doubleValue, 0, 0})}; + if (yaw >= 5 * Math.PI / 4 && yaw < 7 * Math.PI / 4) + return new Direction[] {new Direction(new double[] {0, 0, -doubleValue})}; + assert yaw >= 0 && yaw < Math.PI / 4 || yaw >= 7 * Math.PI / 4 && yaw < 2 * Math.PI; + return new Direction[] {new Direction(new double[] {doubleValue, 0, 0})}; + } + } else { + return new Direction[] {new Direction(horizontal ? Direction.IGNORE_PITCH : 0, yaw, doubleValue)}; + } + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return Direction.class; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + Expression relativeTo = this.relativeTo; + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug) + .appendIf(amount != null, amount, "meter(s)"); + if (direction != null) { + builder.append(Direction.toString(direction)); + } else { + if (relativeTo != null) { + builder.append("in") + .appendIf(horizontal, "horizontal") + .append(facing ? "facing" : "direction", "of", relativeTo); + } else { + builder.appendIf(horizontal, "horizontally") + .append(Direction.toString(0, yaw, 1)); + } + } + return builder.toString(); + } + +} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprBlockData.sk b/src/test/skript/tests/syntaxes/expressions/ExprBlockData.sk index 799e8286bba..6cfeb77589e 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprBlockData.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprBlockData.sk @@ -1,22 +1,107 @@ +using error catching + +test "invalid block data": + set {_block} to campfire[] + catch runtime errors: + set blockdata "waterlogged" of {_block} to "1" + set {_error} to "The blockdata tag 'waterlogged' can only be changed to a boolean (yes/no) value." + assert last caught runtime errors contains {_error} with "Boolean BlockData should error with non-boolean value" + + set {_block} to sea pickles[] + catch runtime errors: + set blockdata "pickles" of {_block} to "a" + set {_error} to "The blockdata tag 'pickles' can only be changed to an integer value." + assert last caught runtime errors contains {_error} with "Integer BlockData should error with non-integer value" + + set {_block} to oak stairs[] + catch runtime errors: + set blockdata "facing" of {_block} to 1 + set {_error} to "The blockdata tag 'facing' can only be changed to a direction value." + assert last caught runtime errors contains {_error} with "Direction BlockData should error with non-direction value" + + catch runtime errors: + set blockdata "facing" of {_block} to north east + set {_error} to "The blockdata tag 'facing' does not support the direction value 'north_east' for the block type: oak stairs" + assert last caught runtime errors contains {_error} with "Direction BlockData should error with non-supported direction value for 'facing'" + + catch runtime errors: + set blockdata "half" of {_block} to "left" + set {_error} to "The blockdata tag 'half' does not support the text value 'left' for the block type: oak stairs" + assert last caught runtime errors contains {_error} with "String BlockData should error with non-supported string value for 'half'" + + test "block data": - set {_b} to block at test-location - set block at {_b} to campfire[lit=false;waterlogged=true] - assert block at {_b} is campfire[lit=false] with "block at spawn should be an unlit campfire" + set {_loc} to test-location + set block at {_loc} to campfire[lit=false;waterlogged=true] + + assert block at {_loc} is campfire[lit=false] with "Block should match with partial BlockData - lit" + assert block at {_loc} is campfire[waterlogged=true] with "Block should match with partial BlockData - waterlogged" + assert block at {_loc} is campfire[lit=false;waterlogged=true] with "Block should match full BlockData" + assert block at {_loc} is campfire[] with "Block should match with no specified BlockData" + assert block at {_loc} is not campfire[lit=true;waterlogged=false] with "Block should not match with inverted BlockData" + + set {_data} to blockdata of block at {_loc} + assert "%{_data}%" contains "campfire", "lit=false" and "waterlogged=true" with "BlockData string did not contain specified parts" - assert block at {_b} = campfire[lit=false;waterlogged=true] with "block should have been an unlit, waterlogged campfire" - assert block at {_b} = campfire[waterlogged=true] with "block should have been a waterlogged campfire" - assert block at {_b} = campfire[] with "block should have been a campfire" - assert block at {_b} != campfire[lit=true;waterlogged=false] with "block should not have been an unlit, waterlogged campfire" + assert {_data} is campfire[lit=false] with "BlockData should match with partial BlockData - lit" + assert {_data} is campfire[waterlogged=true] with "BlockData should match with partial BlockData - waterlogged" + assert {_data} is campfire[lit=false;waterlogged=true] with "BlockData should match full BlockData" + assert {_data} is campfire[] with "BlockData should match with no specified BlockData" + assert {_data} is not campfire[lit=true;waterlogged=false] with "BlockData should not match with inverted BlockData" - set {_data} to block data of block at {_b} - assert "%{_data}%" contains "campfire", "lit=false" and "waterlogged=true" with "block data for campfire did not match" + set {_tags::*} to "lit" and "waterlogged" + loop {_tags::*}: + assert blockdata (loop-value) of (block at {_loc}) is set with "BlockData '" + (loop-value) +"' was not found on Block" + assert (block at {_loc}) has blockdata (loop-value) with "Block did not have BlockData '" + (loop-value) + "'" + assert blockdata (loop-value) of {_data} is set with "BlockData '" + (loop-value) +"' was not found on BlockData" + assert {_data} has blockdata (loop-value) with "BlockData did not have BlockData '" + (loop-value) + "'" + assert (block at {_loc}) is tagged with blockdata {_tags::*} with "Block did not have all BlockData tags" + assert {_data} is tagged with blockdata {_tags::*} with "BlockData did not have all BlockData tags" - set block at {_b} to air + assert blockdata tags of (block at {_loc}) contains {_tags::*} with "Block's BlockData tags did not have all specified tags" + assert blockdata tags of {_data} contains {_tags::*} with "BlockData's tags did not have all specified tags" + + set blockdata "waterlogged" of (block at {_loc}) to false + assert blockdata "waterlogged" of (block at {_loc}) is false with "BlockData 'waterlogged' was not set to false (boolean)" + set blockdata "lit" of (block at {_loc}) to "true" + assert blockdata "lit" of (block at {_loc}) is true with "BlockData 'lit' was not set to true (string)" + + set block at {_loc} to air test "falling block block data": - spawn a falling block at test-location: - set {_e} to entity - assert block data of {_e} is stone[] with "Falling block should default to stone" - set block data of {_e} to sand[] - assert block data of {_e} is sand[] with "Falling block should update to sand" - clear entity within {_e} + spawn a falling block at test-location: + set {_entity} to entity + assert block data of {_entity} is stone[] with "FallingBlock should default to stone" + set block data of {_entity} to oak stairs[] + assert block data of {_entity} is oak stairs[] with "FallingBlock should update to oak stairs" + + set {_tags::*} to "facing", "shape", "half" and "waterlogged" + assert {_entity} has blockdata {_tags::*} with "FallingBlock BlockData did not have specified tags" + set blockdata "facing" of {_entity} to east + assert blockdata "facing" of {_entity} is east with "FallingBlock 'facing' was not set to 'east'" + assert blockdata of {_entity} is oak stairs[facing=east;waterlogged=false] with "FallingBlock should match with partial BlockData" + + clear entity within {_entity} + +test "block display block data": + spawn a block display at test-location: + set {_entity} to entity + set block data of {_entity} to oak stairs[] + assert block data of {_entity} is oak stairs[] with "BlockDisplay should update to oak stairs" + + set {_tags::*} to "facing", "shape", "half" and "waterlogged" + assert {_entity} has blockdata {_tags::*} with "BlockDisplay BlockData did not have specified tags" + set blockdata "facing" of {_entity} to east + assert blockdata "facing" of {_entity} is east with "BlockDisplay 'facing' was not set to 'east'" + assert blockdata of {_entity} is oak stairs[facing=east;waterlogged=false] with "BlockDisplay should match with partial BlockData" + + clear entity within {_entity} + +test "itemtype block data": + set {_item} to oak stairs + set {_tags::*} to "facing", "shape", "half" and "waterlogged" + assert {_item} has blockdata {_tags::*} with "ItemType BlockData did not have specified tags" + set blockdata "facing" of {_item} to east + assert blockdata "facing" of {_item} is east with "ItemType 'facing' was not set to 'east'" + assert blockdata of {_item} is oak stairs[facing=east;waterlogged=false] with "ItemType should match with partial BlockData" + From 27b7d5497f10e7e5fe9b8ceef36386010a9bfe6d Mon Sep 17 00:00:00 2001 From: SirSmurfy2 Date: Thu, 13 Aug 2026 14:48:13 -0400 Subject: [PATCH 2/2] Partial Changes --- .../skript/classes/data/BukkitClasses.java | 23 +---- .../block/blockdata/BlockDataClassInfo.java | 85 ++++++++++--------- .../block/blockdata/BlockDataValueType.java | 11 ++- .../blockdata/elements/ExprBlockData.java | 7 +- .../elements/ExprBlockDataValues.java | 32 +++---- .../elements/expressions/ExprDirection.java | 14 +-- .../syntaxes/expressions/ExprBlockData.sk | 1 - 7 files changed, 83 insertions(+), 90 deletions(-) diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java index 18a0d31df27..51bc664ff82 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -2,11 +2,7 @@ import ch.njol.skript.Skript; import ch.njol.skript.bukkitutil.EntityUtils; -import ch.njol.skript.classes.ClassInfo; -import ch.njol.skript.classes.EnumClassInfo; -import ch.njol.skript.classes.Parser; -import ch.njol.skript.classes.PatternedParser; -import ch.njol.skript.classes.Serializer; +import ch.njol.skript.classes.*; import ch.njol.skript.classes.registry.RegistryClassInfo; import ch.njol.skript.expressions.ExprDamageCause; import ch.njol.skript.expressions.base.EventValueExpression; @@ -17,15 +13,7 @@ import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.world.MoonPhase; import net.kyori.adventure.text.Component; -import org.bukkit.Bukkit; -import org.bukkit.Chunk; -import org.bukkit.Difficulty; -import org.bukkit.FireworkEffect; -import org.bukkit.GameMode; -import org.bukkit.GameRule; -import org.bukkit.Material; -import org.bukkit.SoundCategory; -import org.bukkit.World; +import org.bukkit.*; import org.bukkit.World.Environment; import org.bukkit.attribute.Attribute; import org.bukkit.block.Biome; @@ -33,12 +21,7 @@ import org.bukkit.block.DoubleChest; import org.bukkit.block.banner.PatternType; import org.bukkit.command.CommandSender; -import org.bukkit.entity.EntitySnapshot; -import org.bukkit.entity.Item; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Projectile; -import org.bukkit.entity.Vehicle; -import org.bukkit.entity.Villager; +import org.bukkit.entity.*; import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; import org.bukkit.event.entity.EntityDamageEvent.DamageCause; import org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason; diff --git a/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataClassInfo.java b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataClassInfo.java index 8d194b4cd31..456163b8642 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataClassInfo.java +++ b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataClassInfo.java @@ -34,49 +34,58 @@ public BlockDataClassInfo() { "set target block of player to minecraft:oak_leaves[distance=2;persistent=false]") .after("itemtype") .since("2.5") - .parser(new Parser<>() { - @Override - public @Nullable BlockData parse(String input, ParseContext context) { - return BlockUtils.createBlockData(input); - } + .parser(new BlockDataParser()) + .serializer(new BlockDataSerializer()) + .cloner(BlockData::clone); + } + + private static class BlockDataParser extends Parser { + // + @Override + public @Nullable BlockData parse(String input, ParseContext context) { + return BlockUtils.createBlockData(input); + } - @Override - public String toString(BlockData blockData, int flags) { - return blockData.getAsString().replace(",", ";"); - } + @Override + public String toString(BlockData blockData, int flags) { + return blockData.getAsString().replace(",", ";"); + } + + @Override + public String toVariableNameString(BlockData blockData) { + return "blockdata:" + blockData.getAsString(); + } + // + } - @Override - public String toVariableNameString(BlockData blockData) { - return "blockdata:" + blockData.getAsString(); - } - }) - .serializer(new Serializer<>() { - @Override - public Fields serialize(BlockData blockData) { - return Fields.singletonObject("blockdata", blockData.getAsString()); - } + private static class BlockDataSerializer extends Serializer { + // + @Override + public Fields serialize(BlockData blockData) { + return Fields.singletonObject("blockdata", blockData.getAsString()); + } - @Override - protected BlockData deserialize(Fields fields) throws StreamCorruptedException { - String data = fields.getObject("blockdata", String.class); - assert data != null; - try { - return Bukkit.createBlockData(data); - } catch (IllegalArgumentException ex) { - throw new StreamCorruptedException("Invalid block data: " + data); - } - } + @Override + protected BlockData deserialize(Fields fields) throws StreamCorruptedException { + String data = fields.getObject("blockdata", String.class); + assert data != null; + try { + return Bukkit.createBlockData(data); + } catch (IllegalArgumentException ex) { + throw new StreamCorruptedException("Invalid block data: " + data); + } + } - @Override - public boolean mustSyncDeserialization() { - return true; - } + @Override + public boolean mustSyncDeserialization() { + return true; + } - @Override - protected boolean canBeInstantiated() { - return false; - } - }).cloner(BlockData::clone); + @Override + protected boolean canBeInstantiated() { + return false; + } + // } } diff --git a/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataValueType.java b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataValueType.java index 213176451a5..82484d5a3f9 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataValueType.java +++ b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/BlockDataValueType.java @@ -26,6 +26,7 @@ public interface BlockDataValueType { * {@link BlockDataValueType} for {@link BlockData} tags with {@link Integer} values. */ BlockDataValueType INTEGER = new BlockDataValueType<>() { + // @Override public Class getTypeClass() { return Integer.class; @@ -35,12 +36,14 @@ public Class getTypeClass() { public @Nullable Integer parse(String string) { return string.matches("\\d+") ? Integer.parseInt(string) : null; } + // }; /** * {@link BlockDataValueType} for {@link BlockData} tags with {@link Boolean} values. */ BlockDataValueType BOOLEAN = new BlockDataValueType<>() { + // @Override public Class getTypeClass() { return Boolean.class; @@ -56,13 +59,14 @@ public Class getTypeClass() { public boolean hasValidityCheck() { return false; } + // }; /** * {@link BlockDataValueType} for {@link BlockData} tags with {@link Direction} values. */ BlockDataValueType DIRECTION = new BlockDataValueType<>() { - + // @Override public Class getTypeClass() { return Direction.class; @@ -92,7 +96,7 @@ public boolean requiresConversion() { public String toStringConversion(Direction direction) { return Direction.toNearestBlockFace(direction.getDirection()).toString().toLowerCase(Locale.ENGLISH); } - + // }; @@ -101,6 +105,7 @@ public String toStringConversion(Direction direction) { * This is the default value type and will match against any value. */ BlockDataValueType STRING = new BlockDataValueType<>() { + // @Override public Class getTypeClass() { return String.class; @@ -117,7 +122,7 @@ public Class getTypeClass() { public @Nullable String parse(String string) { return string; } - + // }; /** diff --git a/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/ExprBlockData.java b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/ExprBlockData.java index 58495216dbf..cecc82833db 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/ExprBlockData.java +++ b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/ExprBlockData.java @@ -62,24 +62,21 @@ public static void register(SyntaxRegistry registry) { } @Override + @SuppressWarnings("unchecked") public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { BlockData newBlockData = delta == null ? null : ((BlockData) delta[0]); for (Object object : getExpr().getArray(event)) { BlockDataHolder holder = BlockDataHolder.getHolder(object); if (holder == null) continue; - if (newBlockData != null) { - //noinspection unchecked + if (newBlockData != null) holder.setBlockData(object, newBlockData); - } if (mode != ChangeMode.RESET) continue; - //noinspection unchecked BlockData blockData = holder.getBlockData(object); String dataString = blockData.getMaterial().getKey() + "[]"; try { BlockData newData = Bukkit.createBlockData(dataString); - //noinspection unchecked holder.setBlockData(object, newData); } catch (Exception ignored) {} } diff --git a/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/ExprBlockDataValues.java b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/ExprBlockDataValues.java index 7a9cfd4e41d..6cbfb86fe6b 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/ExprBlockDataValues.java +++ b/src/main/java/org/skriptlang/skript/bukkit/block/blockdata/elements/ExprBlockDataValues.java @@ -46,36 +46,36 @@ - tag "pickles" will always return an integer value, and can be changed to an integer value or string of an integer: "1" """) @Example(""" - set the blockdata tag "waterlogged" of {_campfire} to false - set block data "waterlogged" of {_campfire} to "true" - if block data value "waterlogged" of {_campfire} is "true": + set blockdata "waterlogged" of {_campfire} to false + set blockdata "waterlogged" of {_campfire} to "true" + if blockdata "waterlogged" of {_campfire} is "true": # FAILS - else if the block data tag value "waterlogged" of {_campfire} is true: + else if blockdata "waterlogged" of {_campfire} is true: # PASSES """) @Example(""" - set the blockdata tag "facing" of {_oakStairs} to north - set block data "facing" of {_oakStairs} to "west" - if block data value "facing" of {_oakStairs} is "west": + set blockdata "facing" of {_oakStairs} to north + set blockdata "facing" of {_oakStairs} to "west" + if blockdata "facing" of {_oakStairs} is "west": # FAILS - else if the block data tag value "facing" of {_oakStairs} is west: + else if blockdata "facing" of {_oakStairs} is west: # PASSES """) @Example(""" - set the blockdata tag "pickles" of {_seaPickle} to 1 - set block data "pickles" of {_seaPickle} to "5" - if block data value "pickles" of {_seaPickle} is "5": + set blockdata "pickles" of {_seaPickle} to 1 + set blockdata "pickles" of {_seaPickle} to "5" + if blockdata value "pickles" of {_seaPickle} is "5": # FAILS - else if the block data tag value "pickles" of {_seaPickle} is 5: + else if blockdata tag value "pickles" of {_seaPickle} is 5: # PASSES """) @Example(""" - set the blockdata tag "half" of {_oakStairs} to "top" - if block data value "half" of {_oakStairs} is "top": + set blockdata "half" of {_oakStairs} to "top" + if blockdata "half" of {_oakStairs} is "top": # PASSES """) @Example(""" - reset the block data "waterlogged", "facing", "half", "shape" of {_oakStairs} + reset blockdata "waterlogged", "facing", "half" and "shape" of {_oakStairs} """) @Since("INSERT VERSION") public class ExprBlockDataValues extends SimpleExpression { @@ -129,7 +129,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye @Override public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { Object change = delta != null ? delta[0] : null; - Set strings = Arrays.stream(this.strings.getArray(event)).map(String::toLowerCase).collect(Collectors.toSet()); + Set strings = this.strings.stream(event).map(String::toLowerCase).collect(Collectors.toSet()); Map, List> rejected = new HashMap<>(); Map, Map>> invalid = new HashMap<>(); diff --git a/src/main/java/org/skriptlang/skript/bukkit/misc/elements/expressions/ExprDirection.java b/src/main/java/org/skriptlang/skript/bukkit/misc/elements/expressions/ExprDirection.java index d3ce279e42d..fd6abd9edcd 100644 --- a/src/main/java/org/skriptlang/skript/bukkit/misc/elements/expressions/ExprDirection.java +++ b/src/main/java/org/skriptlang/skript/bukkit/misc/elements/expressions/ExprDirection.java @@ -52,6 +52,7 @@ public class ExprDirection extends SimpleExpression { BlockFace.UP, BlockFace.DOWN, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST, BlockFace.NORTH_EAST, BlockFace.NORTH_WEST, BlockFace.SOUTH_EAST, BlockFace.SOUTH_WEST}; + private final static int UP = 0, DOWN = 1, NORTH = 2, SOUTH = 3, EAST = 4, WEST = 5, NORTH_EAST = 6, NORTH_WEST = 7, SOUTH_EAST = 8, SOUTH_WEST = 9; @@ -96,24 +97,23 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye //noinspection unchecked amount = (Expression) exprs[0]; switch (matchedPattern) { - case 0: + case 0 -> { direction = new Vector(byMark[parseResult.mark].getModX(), byMark[parseResult.mark].getModY(), byMark[parseResult.mark].getModZ()); if (exprs[1] != null) { if (!(exprs[1] instanceof ExprDirection exprDirection) || (exprDirection.direction == null)) return false; next = (ExprDirection) exprs[1]; } - break; - case 1: - case 2: + } + case 1, 2 -> { relativeTo = exprs[1]; horizontal = parseResult.mark % 2 != 0; facing = parseResult.mark >= 2; - break; - case 3: - case 4: + } + case 3, 4 -> { yaw = Math.PI / 2 * parseResult.mark; horizontal = matchedPattern == 4; + } } return true; } diff --git a/src/test/skript/tests/syntaxes/expressions/ExprBlockData.sk b/src/test/skript/tests/syntaxes/expressions/ExprBlockData.sk index 6cfeb77589e..00a3c54ff33 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprBlockData.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprBlockData.sk @@ -29,7 +29,6 @@ test "invalid block data": set {_error} to "The blockdata tag 'half' does not support the text value 'left' for the block type: oak stairs" assert last caught runtime errors contains {_error} with "String BlockData should error with non-supported string value for 'half'" - test "block data": set {_loc} to test-location set block at {_loc} to campfire[lit=false;waterlogged=true]