Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lowest solid block #5284

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*
* 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<Location, Block> {

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;
}
APickledWalrus marked this conversation as resolved.
Show resolved Hide resolved

if (!lowest) {
return getHighestBlockAt(world, location);
}
APickledWalrus marked this conversation as resolved.
Show resolved Hide resolved

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<? extends Block> getReturnType() {
return Block.class;
}

@Override
protected String getPropertyName() {
return (lowest ? "lowest" : "highest") + " solid block";
}

}
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +21 to +23
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will have issues with conflicting with other tests. In the future you'll be able to use event-block after one of my pulls, I forget which one.

Most tests revert the block back to what it was.

This one is problematic because it's near 0,0,0 either new location or I suggest making it restore

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would imagine that a test should make no expectation regarding the state of the world - so if it is expecting a block to be something at a location, that should be explicitly set at the beginning of the test

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's a good principle to abide by, but setting any blocks you set back to air seems like good defensive behavior in case a test isn't abiding by that.

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}