diff --git a/src/main/java/ch/njol/skript/expressions/ExprHighestSolidBlock.java b/src/main/java/ch/njol/skript/expressions/ExprHighestSolidBlock.java deleted file mode 100644 index 8c48e9121ab..00000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprHighestSolidBlock.java +++ /dev/null @@ -1,58 +0,0 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ -package ch.njol.skript.expressions; - -import ch.njol.skript.Skript; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Examples; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.expressions.base.SimplePropertyExpression; -import ch.njol.skript.lang.ExpressionType; - -import org.bukkit.Location; -import org.bukkit.block.Block; -import org.eclipse.jdt.annotation.Nullable; - -@Name("Highest Solid Block") -@Description("Returns the highest solid block at the x and z coordinates of the world of a given location.") -@Examples("highest block at location of arg-player") -@Since("2.2-dev34") -public class ExprHighestSolidBlock extends SimplePropertyExpression { - - static { - Skript.registerExpression(ExprHighestSolidBlock.class, Block.class, ExpressionType.PROPERTY, "highest [(solid|non-air)] block at %locations%"); - } - - @Override - protected String getPropertyName() { - return "highest [(solid|non-air)] block"; - } - - @Nullable - @Override - public Block convert(Location location) { - return location.getWorld().getHighestBlockAt(location); - } - - @Override - public Class getReturnType() { - return Block.class; - } -} \ No newline at end of file diff --git a/src/main/java/ch/njol/skript/expressions/ExprLowestHighestSolidBlock.java b/src/main/java/ch/njol/skript/expressions/ExprLowestHighestSolidBlock.java new file mode 100644 index 00000000000..7f4914ec444 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprLowestHighestSolidBlock.java @@ -0,0 +1,123 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.util.Kleenean; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.generator.WorldInfo; +import org.eclipse.jdt.annotation.Nullable; + +@Name("Lowest/Highest Solid Block") +@Description({ + "An expression to obtain the lowest or highest solid (impassable) block at a location.", + "Note that the y-coordinate of the location is not taken into account for this expression." +}) +@Examples({ + "teleport the player to the block above the highest block at the player", + "set the highest solid block at the player's location to the lowest solid block at the player's location" +}) +@Since("2.2-dev34, INSERT VERSION (lowest solid block, 'non-air' option removed, additional syntax option)") +public class ExprLowestHighestSolidBlock extends SimplePropertyExpression { + + private static final boolean HAS_MIN_HEIGHT = + Skript.classExists("org.bukkit.generator.WorldInfo") && + Skript.methodExists(WorldInfo.class, "getMinHeight"); + + private static final boolean HAS_BLOCK_IS_SOLID = Skript.methodExists(Block.class, "isSolid"); + + // Before 1.15, getHighestSolidBlock actually returned the block directly ABOVE the highest solid block + private static final boolean RETURNS_FIRST_AIR = !Skript.isRunningMinecraft(1, 15); + + static { + Skript.registerExpression(ExprLowestHighestSolidBlock.class, Block.class, ExpressionType.PROPERTY, + "[the] (highest|:lowest) [solid] block (at|of) %locations%", + "%locations%'[s] (highest|:lowest) [solid] block" + ); + } + + private boolean lowest; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + lowest = parseResult.hasTag("lowest"); + return super.init(exprs, matchedPattern, isDelayed, parseResult); + } + + @Override + @Nullable + public Block convert(Location location) { + World world = location.getWorld(); + if (world == null) { + return null; + } + + if (!lowest) { + return getHighestBlockAt(world, location); + } + + location = location.clone(); + location.setY(HAS_MIN_HEIGHT ? world.getMinHeight() : 0); + Block block = location.getBlock(); + int maxHeight = world.getMaxHeight(); + while (block.getY() < maxHeight && !isSolid(block)) { // work our way up + block = block.getRelative(BlockFace.UP); + } + // if this block isn't solid, there are no solid blocks at this location + // getHighestBlockAt is apparently NotNull, so let's just mimic that behavior by returning it + return isSolid(block) ? block : getHighestBlockAt(world, block.getLocation()); + } + + private static Block getHighestBlockAt(World world, Location location) { + Block block = world.getHighestBlockAt(location); + if (RETURNS_FIRST_AIR) { + block = block.getRelative(BlockFace.DOWN); + if (!isSolid(block)) { // if the one right below isn't solid let's just preserve the behavior + block.getRelative(BlockFace.UP); + } + } + return block; + } + + private static boolean isSolid(Block block) { + return HAS_BLOCK_IS_SOLID ? block.isSolid() : block.getType().isSolid(); + } + + @Override + public Class getReturnType() { + return Block.class; + } + + @Override + protected String getPropertyName() { + return (lowest ? "lowest" : "highest") + " solid block"; + } + +} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprLowestHighestSolidBlock.sk b/src/test/skript/tests/syntaxes/expressions/ExprLowestHighestSolidBlock.sk new file mode 100644 index 00000000000..b0f90139f50 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprLowestHighestSolidBlock.sk @@ -0,0 +1,60 @@ +test "lowest/highest solid block (old height)" when running below minecraft "1.18": + + # highest solid block + set {_oldBlock::1} to block data of block at location(0.5, 255.5, 0.5, "world") + set {_oldBlock::2} to block data of block at location(0.5, 254.5, 0.5, "world") + set {_oldBlock::3} to block data of block at location(0.5, 253.5, 0.5, "world") + set block at location(0.5, 255.5, 0.5, "world") to air + set block at location(0.5, 254.5, 0.5, "world") to air + set block at location(0.5, 253.5, 0.5, "world") to dirt + set {_highest} to highest solid block at location(0.5, 64, 0.5, "world") + assert type of {_highest} is dirt with "highest block is not dirt (got '%type of {_highest}%')" + assert location of {_highest} is location(0.5, 253.5, 0.5, "world") with "highest block is not at 0.5,253.5,0.5 (got '%location of {_highest}%')" + set block at location(0.5, 255.5, 0.5, "world") to {_oldBlock::1} + set block at location(0.5, 254.5, 0.5, "world") to {_oldBlock::2} + set block at location(0.5, 253.5, 0.5, "world") to {_oldBlock::3} + + # lowest solid block + set {_oldBlock::1} to block data of block at location(0.5, 0.5, 0.5, "world") + set {_oldBlock::2} to block data of block at location(0.5, 1.5, 0.5, "world") + set {_oldBlock::3} to block data of block at location(0.5, 2.5, 0.5, "world") + set block at location(0.5, 0.5, 0.5, "world") to air + set block at location(0.5, 1.5, 0.5, "world") to air + set block at location(0.5, 2.5, 0.5, "world") to dirt + set {_lowest} to lowest solid block at location(0.5, 64, 0.5, "world") + assert type of {_lowest} is dirt with "lowest block is not dirt (got '%type of {_lowest}%')" + assert location of {_lowest} is location(0.5, 2.5, 0.5, "world") with "lowest block is not at 0.5,2.5,0.5 (got '%location of {_lowest}%')" + set block at location(0.5, 0.5, 0.5, "world") to {_oldBlock::1} + set block at location(0.5, 1.5, 0.5, "world") to {_oldBlock::2} + set block at location(0.5, 2.5, 0.5, "world") to {_oldBlock::3} + +test "lowest/highest solid block (new height)" when running minecraft "1.18": + + # highest solid block + set {_oldBlock::1} to block data of block at location(0.5, 319.5, 0.5, "world") + set {_oldBlock::2} to block data of block at location(0.5, 318.5, 0.5, "world") + set {_oldBlock::3} to block data of block at location(0.5, 317.5, 0.5, "world") + set block at location(0.5, 319.5, 0.5, "world") to air + set block at location(0.5, 318.5, 0.5, "world") to air + set block at location(0.5, 317.5, 0.5, "world") to dirt + set {_highest} to highest solid block at location(0.5, 64, 0.5, "world") + assert type of {_highest} is dirt with "highest block is not dirt (got '%type of {_highest}%')" + assert location of {_highest} is location(0.5, 317.5, 0.5, "world") with "highest block is not at 0.5,317.5,0.5 (got '%location of {_highest}%')" + set block at location(0.5, 319.5, 0.5, "world") to {_oldBlock::1} + set block at location(0.5, 318.5, 0.5, "world") to {_oldBlock::1} + set block at location(0.5, 317.5, 0.5, "world") to {_oldBlock::1} + + # lowest solid block + set {_oldBlock::1} to block data of block at location(0.5, -63.5, 0.5, "world") + set {_oldBlock::2} to block data of block at location(0.5, -62.5, 0.5, "world") + set {_oldBlock::3} to block data of block at location(0.5, -61.5, 0.5, "world") + set block at location(0.5, -63.5, 0.5, "world") to air + set block at location(0.5, -62.5, 0.5, "world") to air + set block at location(0.5, -61.5, 0.5, "world") to dirt + set {_lowest} to lowest solid block at location(0.5, 64, 0.5, "world") + assert type of {_lowest} is dirt with "lowest block is not dirt (got '%type of {_lowest}%')" + assert location of {_lowest} is location(0.5, -61.5, 0.5, "world") with "lowest block is not at 0.5,-61.5,0.5 (got '%location of {_lowest}%')" + set block at location(0.5, -63.5, 0.5, "world") to {_oldBlock::1} + set block at location(0.5, -62.5, 0.5, "world") to {_oldBlock::2} + set block at location(0.5, -61.5, 0.5, "world") to {_oldBlock::3} +