Merge branch 'master' into gradle-proguard
This commit is contained in:
commit
dbde993a76
@ -18,7 +18,7 @@ For Impact 4.3, there is no Baritone integration yet, so you will want `baritone
|
|||||||
|
|
||||||
Any official release will be GPG signed by leijurv (44A3EA646EADAC6A) and ZeroMemes (73A788379A197567). Please verify that the hash of the file you download is in `checksums.txt` and that `checksums_signed.asc` is a valid signature by those two public keys of `checksums.txt`.
|
Any official release will be GPG signed by leijurv (44A3EA646EADAC6A) and ZeroMemes (73A788379A197567). Please verify that the hash of the file you download is in `checksums.txt` and that `checksums_signed.asc` is a valid signature by those two public keys of `checksums.txt`.
|
||||||
|
|
||||||
The build is deterministic, and you can verify Travis did it properly by running `docker build --no-cache -t cabaletta/baritone . && docker run --rm -it cabaletta/baritone sh scripts/build.sh` yourself and comparing the shasum. Note that for some godawful reason this doesn't work on Mac, the shasums are different even though docker is supposed to work the same everywhere. I get the same shasums as Travis when the host is Linux though.
|
The build is fully deterministic and reproducible, and you can verify Travis did it properly by running `docker build --no-cache -t cabaletta/baritone . && docker run --rm -it cabaletta/baritone sh scripts/build.sh` yourself and comparing the shasum. This works identically on Travis, Mac, and Linux (if you have docker on Windows, I'd be grateful if you could let me know if it works there too).
|
||||||
|
|
||||||
### Building Baritone yourself
|
### Building Baritone yourself
|
||||||
There are a few steps to this
|
There are a few steps to this
|
||||||
|
@ -18,15 +18,16 @@
|
|||||||
package baritone.pathing.calc;
|
package baritone.pathing.calc;
|
||||||
|
|
||||||
import baritone.Baritone;
|
import baritone.Baritone;
|
||||||
|
import baritone.api.pathing.calc.IPath;
|
||||||
import baritone.api.pathing.goals.Goal;
|
import baritone.api.pathing.goals.Goal;
|
||||||
import baritone.api.pathing.movement.ActionCosts;
|
import baritone.api.pathing.movement.ActionCosts;
|
||||||
import baritone.api.pathing.calc.IPath;
|
|
||||||
import baritone.api.utils.BetterBlockPos;
|
import baritone.api.utils.BetterBlockPos;
|
||||||
import baritone.pathing.calc.openset.BinaryHeapOpenSet;
|
import baritone.pathing.calc.openset.BinaryHeapOpenSet;
|
||||||
import baritone.pathing.movement.CalculationContext;
|
import baritone.pathing.movement.CalculationContext;
|
||||||
import baritone.pathing.movement.Moves;
|
import baritone.pathing.movement.Moves;
|
||||||
import baritone.utils.BlockStateInterface;
|
import baritone.utils.BlockStateInterface;
|
||||||
import baritone.utils.Helper;
|
import baritone.utils.Helper;
|
||||||
|
import baritone.utils.pathing.BetterWorldBorder;
|
||||||
import baritone.utils.pathing.MutableMoveResult;
|
import baritone.utils.pathing.MutableMoveResult;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -63,6 +64,7 @@ public final class AStarPathFinder extends AbstractNodeCostSearch implements Hel
|
|||||||
CalculationContext calcContext = new CalculationContext();
|
CalculationContext calcContext = new CalculationContext();
|
||||||
MutableMoveResult res = new MutableMoveResult();
|
MutableMoveResult res = new MutableMoveResult();
|
||||||
HashSet<Long> favored = favoredPositions.orElse(null);
|
HashSet<Long> favored = favoredPositions.orElse(null);
|
||||||
|
BetterWorldBorder worldBorder = new BetterWorldBorder(world().getWorldBorder());
|
||||||
BlockStateInterface.clearCachedChunk();
|
BlockStateInterface.clearCachedChunk();
|
||||||
long startTime = System.nanoTime() / 1000000L;
|
long startTime = System.nanoTime() / 1000000L;
|
||||||
boolean slowPath = Baritone.settings().slowPath.get();
|
boolean slowPath = Baritone.settings().slowPath.get();
|
||||||
@ -106,6 +108,12 @@ public final class AStarPathFinder extends AbstractNodeCostSearch implements Hel
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!moves.dynamicXZ && !worldBorder.entirelyContains(newX, newZ)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (currentNode.y + moves.yOffset > 256 || currentNode.y + moves.yOffset < 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
res.reset();
|
res.reset();
|
||||||
moves.apply(calcContext, currentNode.x, currentNode.y, currentNode.z, res);
|
moves.apply(calcContext, currentNode.x, currentNode.y, currentNode.z, res);
|
||||||
numMovementsConsidered++;
|
numMovementsConsidered++;
|
||||||
@ -113,6 +121,12 @@ public final class AStarPathFinder extends AbstractNodeCostSearch implements Hel
|
|||||||
if (actionCost >= ActionCosts.COST_INF) {
|
if (actionCost >= ActionCosts.COST_INF) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (actionCost <= 0) {
|
||||||
|
throw new IllegalStateException(moves + " calculated implausible cost " + actionCost);
|
||||||
|
}
|
||||||
|
if (moves.dynamicXZ && !worldBorder.entirelyContains(res.x, res.z)) { // see issue #218
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// check destination after verifying it's not COST_INF -- some movements return a static IMPOSSIBLE object with COST_INF and destination being 0,0,0 to avoid allocating a new result for every failed calculation
|
// check destination after verifying it's not COST_INF -- some movements return a static IMPOSSIBLE object with COST_INF and destination being 0,0,0 to avoid allocating a new result for every failed calculation
|
||||||
if (!moves.dynamicXZ && (res.x != newX || res.z != newZ)) {
|
if (!moves.dynamicXZ && (res.x != newX || res.z != newZ)) {
|
||||||
throw new IllegalStateException(moves + " " + res.x + " " + newX + " " + res.z + " " + newZ);
|
throw new IllegalStateException(moves + " " + res.x + " " + newX + " " + res.z + " " + newZ);
|
||||||
@ -120,9 +134,6 @@ public final class AStarPathFinder extends AbstractNodeCostSearch implements Hel
|
|||||||
if (!moves.dynamicY && res.y != currentNode.y + moves.yOffset) {
|
if (!moves.dynamicY && res.y != currentNode.y + moves.yOffset) {
|
||||||
throw new IllegalStateException(moves + " " + res.x + " " + newX + " " + res.z + " " + newZ);
|
throw new IllegalStateException(moves + " " + res.x + " " + newX + " " + res.z + " " + newZ);
|
||||||
}
|
}
|
||||||
if (actionCost <= 0) {
|
|
||||||
throw new IllegalStateException(moves + " calculated implausible cost " + actionCost);
|
|
||||||
}
|
|
||||||
long hashCode = BetterBlockPos.longHash(res.x, res.y, res.z);
|
long hashCode = BetterBlockPos.longHash(res.x, res.y, res.z);
|
||||||
if (favoring && favored.contains(hashCode)) {
|
if (favoring && favored.contains(hashCode)) {
|
||||||
// see issue #18
|
// see issue #18
|
||||||
|
@ -21,6 +21,7 @@ import baritone.Baritone;
|
|||||||
import baritone.api.pathing.movement.ActionCosts;
|
import baritone.api.pathing.movement.ActionCosts;
|
||||||
import baritone.utils.Helper;
|
import baritone.utils.Helper;
|
||||||
import baritone.utils.ToolSet;
|
import baritone.utils.ToolSet;
|
||||||
|
import baritone.utils.pathing.BetterWorldBorder;
|
||||||
import net.minecraft.enchantment.EnchantmentHelper;
|
import net.minecraft.enchantment.EnchantmentHelper;
|
||||||
import net.minecraft.entity.player.InventoryPlayer;
|
import net.minecraft.entity.player.InventoryPlayer;
|
||||||
import net.minecraft.init.Items;
|
import net.minecraft.init.Items;
|
||||||
@ -44,6 +45,7 @@ public class CalculationContext implements Helper {
|
|||||||
private final int maxFallHeightBucket;
|
private final int maxFallHeightBucket;
|
||||||
private final double waterWalkSpeed;
|
private final double waterWalkSpeed;
|
||||||
private final double breakBlockAdditionalCost;
|
private final double breakBlockAdditionalCost;
|
||||||
|
private final BetterWorldBorder worldBorder;
|
||||||
|
|
||||||
public CalculationContext() {
|
public CalculationContext() {
|
||||||
this(new ToolSet());
|
this(new ToolSet());
|
||||||
@ -68,6 +70,32 @@ public class CalculationContext implements Helper {
|
|||||||
// why cache these things here, why not let the movements just get directly from settings?
|
// why cache these things here, why not let the movements just get directly from settings?
|
||||||
// because if some movements are calculated one way and others are calculated another way,
|
// because if some movements are calculated one way and others are calculated another way,
|
||||||
// then you get a wildly inconsistent path that isn't optimal for either scenario.
|
// then you get a wildly inconsistent path that isn't optimal for either scenario.
|
||||||
|
this.worldBorder = new BetterWorldBorder(world().getWorldBorder());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canPlaceThrowawayAt(int x, int y, int z) {
|
||||||
|
if (!hasThrowaway()) { // only true if allowPlace is true, see constructor
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isPossiblyProtected(x, y, z)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return worldBorder.canPlaceAt(x, z); // TODO perhaps MovementHelper.canPlaceAgainst could also use this?
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canBreakAt(int x, int y, int z) {
|
||||||
|
if (!allowBreak()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isPossiblyProtected(x, y, z)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPossiblyProtected(int x, int y, int z) {
|
||||||
|
// TODO more protection logic here; see #220
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ToolSet getToolSet() {
|
public ToolSet getToolSet() {
|
||||||
|
@ -48,10 +48,6 @@ import java.util.Optional;
|
|||||||
*/
|
*/
|
||||||
public interface MovementHelper extends ActionCosts, Helper {
|
public interface MovementHelper extends ActionCosts, Helper {
|
||||||
|
|
||||||
static boolean avoidBreaking(BetterBlockPos pos, IBlockState state) {
|
|
||||||
return avoidBreaking(pos.x, pos.y, pos.z, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
static boolean avoidBreaking(int x, int y, int z, IBlockState state) {
|
static boolean avoidBreaking(int x, int y, int z, IBlockState state) {
|
||||||
Block b = state.getBlock();
|
Block b = state.getBlock();
|
||||||
return b == Blocks.ICE // ice becomes water, and water can mess up the path
|
return b == Blocks.ICE // ice becomes water, and water can mess up the path
|
||||||
@ -74,10 +70,6 @@ public interface MovementHelper extends ActionCosts, Helper {
|
|||||||
return canWalkThrough(pos.x, pos.y, pos.z, BlockStateInterface.get(pos));
|
return canWalkThrough(pos.x, pos.y, pos.z, BlockStateInterface.get(pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean canWalkThrough(BetterBlockPos pos, IBlockState state) {
|
|
||||||
return canWalkThrough(pos.x, pos.y, pos.z, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
static boolean canWalkThrough(int x, int y, int z) {
|
static boolean canWalkThrough(int x, int y, int z) {
|
||||||
return canWalkThrough(x, y, z, BlockStateInterface.get(x, y, z));
|
return canWalkThrough(x, y, z, BlockStateInterface.get(x, y, z));
|
||||||
}
|
}
|
||||||
@ -139,10 +131,6 @@ public interface MovementHelper extends ActionCosts, Helper {
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
static boolean fullyPassable(BlockPos pos) {
|
|
||||||
return fullyPassable(BlockStateInterface.get(pos));
|
|
||||||
}
|
|
||||||
|
|
||||||
static boolean fullyPassable(int x, int y, int z) {
|
static boolean fullyPassable(int x, int y, int z) {
|
||||||
return fullyPassable(BlockStateInterface.get(x, y, z));
|
return fullyPassable(BlockStateInterface.get(x, y, z));
|
||||||
}
|
}
|
||||||
@ -170,10 +158,6 @@ public interface MovementHelper extends ActionCosts, Helper {
|
|||||||
return block.isPassable(null, null);
|
return block.isPassable(null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean isReplacable(BlockPos pos, IBlockState state) {
|
|
||||||
return isReplacable(pos.getX(), pos.getY(), pos.getZ(), state);
|
|
||||||
}
|
|
||||||
|
|
||||||
static boolean isReplacable(int x, int y, int z, IBlockState state) {
|
static boolean isReplacable(int x, int y, int z, IBlockState state) {
|
||||||
// for MovementTraverse and MovementAscend
|
// for MovementTraverse and MovementAscend
|
||||||
// block double plant defaults to true when the block doesn't match, so don't need to check that case
|
// block double plant defaults to true when the block doesn't match, so don't need to check that case
|
||||||
@ -343,15 +327,6 @@ public interface MovementHelper extends ActionCosts, Helper {
|
|||||||
return state.isBlockNormalCube();
|
return state.isBlockNormalCube();
|
||||||
}
|
}
|
||||||
|
|
||||||
static double getMiningDurationTicks(CalculationContext context, BetterBlockPos position, boolean includeFalling) {
|
|
||||||
IBlockState state = BlockStateInterface.get(position);
|
|
||||||
return getMiningDurationTicks(context, position.x, position.y, position.z, state, includeFalling);
|
|
||||||
}
|
|
||||||
|
|
||||||
static double getMiningDurationTicks(CalculationContext context, BetterBlockPos position, IBlockState state, boolean includeFalling) {
|
|
||||||
return getMiningDurationTicks(context, position.x, position.y, position.z, state, includeFalling);
|
|
||||||
}
|
|
||||||
|
|
||||||
static double getMiningDurationTicks(CalculationContext context, int x, int y, int z, boolean includeFalling) {
|
static double getMiningDurationTicks(CalculationContext context, int x, int y, int z, boolean includeFalling) {
|
||||||
return getMiningDurationTicks(context, x, y, z, BlockStateInterface.get(x, y, z), includeFalling);
|
return getMiningDurationTicks(context, x, y, z, BlockStateInterface.get(x, y, z), includeFalling);
|
||||||
}
|
}
|
||||||
@ -359,7 +334,7 @@ public interface MovementHelper extends ActionCosts, Helper {
|
|||||||
static double getMiningDurationTicks(CalculationContext context, int x, int y, int z, IBlockState state, boolean includeFalling) {
|
static double getMiningDurationTicks(CalculationContext context, int x, int y, int z, IBlockState state, boolean includeFalling) {
|
||||||
Block block = state.getBlock();
|
Block block = state.getBlock();
|
||||||
if (!canWalkThrough(x, y, z, state)) {
|
if (!canWalkThrough(x, y, z, state)) {
|
||||||
if (!context.allowBreak()) {
|
if (!context.canBreakAt(x, y, z)) {
|
||||||
return COST_INF;
|
return COST_INF;
|
||||||
}
|
}
|
||||||
if (avoidBreaking(x, y, z, state)) {
|
if (avoidBreaking(x, y, z, state)) {
|
||||||
|
@ -148,7 +148,7 @@ public enum Moves {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
DESCEND_EAST(+1, 0, 0, false, true) {
|
DESCEND_EAST(+1, -1, 0, false, true) {
|
||||||
@Override
|
@Override
|
||||||
public Movement apply0(BetterBlockPos src) {
|
public Movement apply0(BetterBlockPos src) {
|
||||||
MutableMoveResult res = new MutableMoveResult();
|
MutableMoveResult res = new MutableMoveResult();
|
||||||
@ -166,7 +166,7 @@ public enum Moves {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
DESCEND_WEST(-1, 0, 0, false, true) {
|
DESCEND_WEST(-1, -1, 0, false, true) {
|
||||||
@Override
|
@Override
|
||||||
public Movement apply0(BetterBlockPos src) {
|
public Movement apply0(BetterBlockPos src) {
|
||||||
MutableMoveResult res = new MutableMoveResult();
|
MutableMoveResult res = new MutableMoveResult();
|
||||||
@ -184,7 +184,7 @@ public enum Moves {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
DESCEND_NORTH(0, 0, -1, false, true) {
|
DESCEND_NORTH(0, -1, -1, false, true) {
|
||||||
@Override
|
@Override
|
||||||
public Movement apply0(BetterBlockPos src) {
|
public Movement apply0(BetterBlockPos src) {
|
||||||
MutableMoveResult res = new MutableMoveResult();
|
MutableMoveResult res = new MutableMoveResult();
|
||||||
@ -202,7 +202,7 @@ public enum Moves {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
DESCEND_SOUTH(0, 0, +1, false, true) {
|
DESCEND_SOUTH(0, -1, +1, false, true) {
|
||||||
@Override
|
@Override
|
||||||
public Movement apply0(BetterBlockPos src) {
|
public Movement apply0(BetterBlockPos src) {
|
||||||
MutableMoveResult res = new MutableMoveResult();
|
MutableMoveResult res = new MutableMoveResult();
|
||||||
|
@ -72,7 +72,7 @@ public class MovementAscend extends Movement {
|
|||||||
}
|
}
|
||||||
boolean hasToPlace = false;
|
boolean hasToPlace = false;
|
||||||
if (!MovementHelper.canWalkOn(destX, y, destZ, toPlace)) {
|
if (!MovementHelper.canWalkOn(destX, y, destZ, toPlace)) {
|
||||||
if (!context.hasThrowaway()) {
|
if (!context.canPlaceThrowawayAt(destX, y, destZ)) {
|
||||||
return COST_INF;
|
return COST_INF;
|
||||||
}
|
}
|
||||||
if (toPlace.getBlock() != Blocks.AIR && !BlockStateInterface.isWater(toPlace.getBlock()) && !MovementHelper.isReplacable(destX, y, destZ, toPlace)) {
|
if (toPlace.getBlock() != Blocks.AIR && !BlockStateInterface.isWater(toPlace.getBlock()) && !MovementHelper.isReplacable(destX, y, destZ, toPlace)) {
|
||||||
|
@ -118,7 +118,7 @@ public class MovementParkour extends Movement {
|
|||||||
int destX = x + 4 * xDiff;
|
int destX = x + 4 * xDiff;
|
||||||
int destZ = z + 4 * zDiff;
|
int destZ = z + 4 * zDiff;
|
||||||
IBlockState toPlace = BlockStateInterface.get(destX, y - 1, destZ);
|
IBlockState toPlace = BlockStateInterface.get(destX, y - 1, destZ);
|
||||||
if (!context.hasThrowaway()) {
|
if (!context.canPlaceThrowawayAt(destX, y - 1, destZ)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (toPlace.getBlock() != Blocks.AIR && !BlockStateInterface.isWater(toPlace.getBlock()) && !MovementHelper.isReplacable(destX, y - 1, destZ, toPlace)) {
|
if (toPlace.getBlock() != Blocks.AIR && !BlockStateInterface.isWater(toPlace.getBlock()) && !MovementHelper.isReplacable(destX, y - 1, destZ, toPlace)) {
|
||||||
|
@ -76,7 +76,7 @@ public class MovementPillar extends Movement {
|
|||||||
return LADDER_UP_ONE_COST;
|
return LADDER_UP_ONE_COST;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!context.hasThrowaway() && !ladder) {
|
if (!ladder && !context.canPlaceThrowawayAt(x, y, z)) {
|
||||||
return COST_INF;
|
return COST_INF;
|
||||||
}
|
}
|
||||||
double hardness = MovementHelper.getMiningDurationTicks(context, x, y + 2, z, toBreak, true);
|
double hardness = MovementHelper.getMiningDurationTicks(context, x, y + 2, z, toBreak, true);
|
||||||
|
@ -108,7 +108,7 @@ public class MovementTraverse extends Movement {
|
|||||||
if (BlockStateInterface.isWater(destOn.getBlock()) && throughWater) {
|
if (BlockStateInterface.isWater(destOn.getBlock()) && throughWater) {
|
||||||
return COST_INF;
|
return COST_INF;
|
||||||
}
|
}
|
||||||
if (!context.hasThrowaway()) {
|
if (!context.canPlaceThrowawayAt(destX, y - 1, destZ)) {
|
||||||
return COST_INF;
|
return COST_INF;
|
||||||
}
|
}
|
||||||
double hardness1 = MovementHelper.getMiningDurationTicks(context, destX, y, destZ, pb0, false);
|
double hardness1 = MovementHelper.getMiningDurationTicks(context, destX, y, destZ, pb0, false);
|
||||||
|
46
src/main/java/baritone/utils/pathing/BetterWorldBorder.java
Normal file
46
src/main/java/baritone/utils/pathing/BetterWorldBorder.java
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Baritone.
|
||||||
|
*
|
||||||
|
* Baritone is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Baritone 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 Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package baritone.utils.pathing;
|
||||||
|
|
||||||
|
import baritone.utils.Helper;
|
||||||
|
import net.minecraft.world.border.WorldBorder;
|
||||||
|
|
||||||
|
public class BetterWorldBorder implements Helper {
|
||||||
|
double minX;
|
||||||
|
double maxX;
|
||||||
|
double minZ;
|
||||||
|
double maxZ;
|
||||||
|
|
||||||
|
public BetterWorldBorder(WorldBorder border) {
|
||||||
|
this.minX = border.minX();
|
||||||
|
this.maxX = border.maxX();
|
||||||
|
this.minZ = border.minZ();
|
||||||
|
this.maxZ = border.maxZ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean entirelyContains(int x, int z) {
|
||||||
|
return x + 1 > minX && x < maxX && z + 1 > minZ && z < maxZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canPlaceAt(int x, int z) {
|
||||||
|
// move it in 1 block on all sides
|
||||||
|
// because we can't place a block at the very edge against a block outside the border
|
||||||
|
// it won't let us right click it
|
||||||
|
return x > minX && x + 1 < maxX && z > minZ && z + 1 < maxZ;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user