mirror of
https://github.com/PaperMC/Paper.git
synced 2025-01-02 17:32:03 +01:00
Added target block ray trace functionality.
By: Raphfrk <raphfrk@gmail.com>
This commit is contained in:
parent
09916f398f
commit
5ba9ae942a
4 changed files with 455 additions and 2 deletions
|
@ -175,6 +175,26 @@ public class Location implements Cloneable {
|
||||||
return pitch;
|
return pitch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a Vector pointing in the direction that this Location is facing
|
||||||
|
*
|
||||||
|
* @return Vector
|
||||||
|
*/
|
||||||
|
public Vector getDirection() {
|
||||||
|
Vector vector = new Vector();
|
||||||
|
|
||||||
|
double rotX = this.getYaw();
|
||||||
|
double rotY = this.getPitch();
|
||||||
|
|
||||||
|
vector.setY(-Math.sin(Math.toRadians(rotY)));
|
||||||
|
|
||||||
|
double h = Math.cos(Math.toRadians(rotY));
|
||||||
|
vector.setX(-h*Math.sin(Math.toRadians(rotX)));
|
||||||
|
vector.setZ(h*Math.cos(Math.toRadians(rotX)));
|
||||||
|
|
||||||
|
return vector;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
|
|
|
@ -1,24 +1,72 @@
|
||||||
|
|
||||||
package org.bukkit.entity;
|
package org.bukkit.entity;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a living entity, such as a monster or player
|
* Represents a living entity, such as a monster or player
|
||||||
*/
|
*/
|
||||||
public interface LivingEntity extends Entity {
|
public interface LivingEntity extends Entity {
|
||||||
/**
|
/**
|
||||||
* Gets the entitys health from 0-20, where 0 is dead and 20 is full
|
* Gets the entity's health from 0-20, where 0 is dead and 20 is full
|
||||||
*
|
*
|
||||||
* @return Health represented from 0-20
|
* @return Health represented from 0-20
|
||||||
*/
|
*/
|
||||||
public int getHealth();
|
public int getHealth();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the entitys health from 0-20, where 0 is dead and 20 is full
|
* Sets the entity's health from 0-20, where 0 is dead and 20 is full
|
||||||
*
|
*
|
||||||
* @param health New health represented from 0-20
|
* @param health New health represented from 0-20
|
||||||
*/
|
*/
|
||||||
public void setHealth(int health);
|
public void setHealth(int health);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the height of the entity's head above its Location
|
||||||
|
*
|
||||||
|
* @return Height of the entity's eyes above its Location
|
||||||
|
*/
|
||||||
|
public double getEyeHeight();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the height of the entity's head above its Location
|
||||||
|
*
|
||||||
|
* @param boolean If set to true, the effects of sneaking will be ignored
|
||||||
|
* @return Height of the entity's eyes above its Location
|
||||||
|
*/
|
||||||
|
public double getEyeHeight(boolean ignoreSneaking);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all blocks along the player's line of sight
|
||||||
|
* List iterates from player's position to target inclusive
|
||||||
|
*
|
||||||
|
* @param HashSet<Byte> HashSet containing all transparent block IDs. If set to null only air is considered transparent.
|
||||||
|
* @param int This is the maximum distance to scan. This may be further limited by the server, but never to less than 100 blocks.
|
||||||
|
* @return List containing all blocks along the player's line of sight
|
||||||
|
*/
|
||||||
|
public List<Block> getLineOfSight(HashSet<Byte> transparent, int maxDistance);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the block that the player has targeted
|
||||||
|
*
|
||||||
|
* @param HashSet<Byte> HashSet containing all transparent block IDs. If set to null only air is considered transparent.
|
||||||
|
* @param int This is the maximum distance to scan. This may be further limited by the server, but never to less than 100 blocks.
|
||||||
|
* @return Block that the player has targeted
|
||||||
|
*/
|
||||||
|
public Block getTargetBlock(HashSet<Byte> transparent, int maxDistance);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the last two blocks along the player's line of sight.
|
||||||
|
* The target block will be the last block in the list.
|
||||||
|
*
|
||||||
|
* @param HashSet<Byte> HashSet containing all transparent block IDs. If set to null only air is considered transparent.
|
||||||
|
* @param int This is the maximum distance to scan. This may be further limited by the server, but never to less than 100 blocks
|
||||||
|
* @return List containing the last 2 blocks along the player's line of sight
|
||||||
|
*/
|
||||||
|
public List<Block> getLastTwoTargetBlocks(HashSet<Byte> transparent, int maxDistance);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Throws an egg from the entity.
|
* Throws an egg from the entity.
|
||||||
*/
|
*/
|
||||||
|
|
372
paper-api/src/main/java/org/bukkit/util/BlockIterator.java
Normal file
372
paper-api/src/main/java/org/bukkit/util/BlockIterator.java
Normal file
|
@ -0,0 +1,372 @@
|
||||||
|
package org.bukkit.util;
|
||||||
|
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.BlockFace;
|
||||||
|
import org.bukkit.entity.LivingEntity;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.lang.IllegalStateException;
|
||||||
|
import java.lang.Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class performs ray tracing and iterates along blocks on a line
|
||||||
|
*
|
||||||
|
* @author raphfrk
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class BlockIterator implements Iterator<Block> {
|
||||||
|
|
||||||
|
private final World world;
|
||||||
|
private final int maxDistance;
|
||||||
|
|
||||||
|
private final int gridSize = 1<<24;
|
||||||
|
|
||||||
|
private boolean end = false;
|
||||||
|
|
||||||
|
private Block[] blockQueue = new Block[3];
|
||||||
|
private int currentBlock = 0;
|
||||||
|
private int currentDistance = 0;
|
||||||
|
private int maxDistanceInt;
|
||||||
|
|
||||||
|
private int secondError;
|
||||||
|
private int thirdError;
|
||||||
|
|
||||||
|
private int secondStep;
|
||||||
|
private int thirdStep;
|
||||||
|
|
||||||
|
private BlockFace mainFace;
|
||||||
|
private BlockFace secondFace;
|
||||||
|
private BlockFace thirdFace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the BlockIterator
|
||||||
|
*
|
||||||
|
* @param world The world to use for tracing
|
||||||
|
* @param start A Vector giving the initial location for the trace
|
||||||
|
* @param direction A Vector pointing in the direction for the trace
|
||||||
|
* @param yOffset The trace begins vertically offset from the start vector by this value
|
||||||
|
* @param maxDistance This is the maximum distance in blocks for the trace. Setting this value above 140 may lead to problems with unloaded chunks. A value of 0 indicates no limit
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public BlockIterator(World world, Vector start, Vector direction, double yOffset, int maxDistance) {
|
||||||
|
this.world = world;
|
||||||
|
this.maxDistance = maxDistance;
|
||||||
|
|
||||||
|
Vector startClone = start.clone();
|
||||||
|
startClone.setY(startClone.getY( )+ yOffset);
|
||||||
|
|
||||||
|
currentDistance = 0;
|
||||||
|
|
||||||
|
double mainDirection = 0;
|
||||||
|
double secondDirection = 0;
|
||||||
|
double thirdDirection = 0;
|
||||||
|
|
||||||
|
double mainPosition = 0;
|
||||||
|
double secondPosition = 0;
|
||||||
|
double thirdPosition = 0;
|
||||||
|
|
||||||
|
Block startBlock = world.getBlockAt((int)Math.floor(startClone.getX()), (int)Math.floor(startClone.getY()), (int)Math.floor(startClone.getZ()));
|
||||||
|
|
||||||
|
if (getXLength(direction) > mainDirection) {
|
||||||
|
mainFace = getXFace(direction);
|
||||||
|
mainDirection = getXLength(direction);
|
||||||
|
mainPosition = getXPosition(direction, startClone, startBlock);
|
||||||
|
|
||||||
|
secondFace = getYFace(direction);
|
||||||
|
secondDirection = getYLength(direction);
|
||||||
|
secondPosition = getYPosition(direction, startClone, startBlock);
|
||||||
|
|
||||||
|
thirdFace = getZFace(direction);
|
||||||
|
thirdDirection = getZLength(direction);
|
||||||
|
thirdPosition = getZPosition(direction, startClone, startBlock);
|
||||||
|
}
|
||||||
|
if (getYLength(direction) > mainDirection) {
|
||||||
|
mainFace = getYFace(direction);
|
||||||
|
mainDirection = getYLength(direction);
|
||||||
|
mainPosition = getYPosition(direction, startClone, startBlock);
|
||||||
|
|
||||||
|
secondFace = getZFace(direction);
|
||||||
|
secondDirection = getZLength(direction);
|
||||||
|
secondPosition = getZPosition(direction, startClone, startBlock);
|
||||||
|
|
||||||
|
thirdFace = getXFace(direction);
|
||||||
|
thirdDirection = getXLength(direction);
|
||||||
|
thirdPosition = getXPosition(direction, startClone, startBlock);
|
||||||
|
}
|
||||||
|
if (getZLength(direction) > mainDirection) {
|
||||||
|
mainFace = getZFace(direction);
|
||||||
|
mainDirection = getZLength(direction);
|
||||||
|
mainPosition = getZPosition(direction, startClone, startBlock);
|
||||||
|
|
||||||
|
secondFace = getXFace(direction);
|
||||||
|
secondDirection = getXLength(direction);
|
||||||
|
secondPosition = getXPosition(direction, startClone, startBlock);
|
||||||
|
|
||||||
|
thirdFace = getYFace(direction);
|
||||||
|
thirdDirection = getYLength(direction);
|
||||||
|
thirdPosition = getYPosition(direction, startClone, startBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
// trace line backwards to find intercept with plane perpendicular to the main axis
|
||||||
|
|
||||||
|
double d = mainPosition/mainDirection; // how far to hit face behind
|
||||||
|
double secondd = secondPosition - secondDirection*d;
|
||||||
|
double thirdd = thirdPosition - thirdDirection*d;
|
||||||
|
|
||||||
|
secondError = (int)(Math.floor(secondd*gridSize));
|
||||||
|
secondStep = (int)(Math.round(secondDirection/mainDirection*gridSize));
|
||||||
|
thirdError = (int)(Math.floor(thirdd*gridSize));
|
||||||
|
thirdStep = (int)(Math.round(thirdDirection/mainDirection*gridSize));
|
||||||
|
|
||||||
|
// Guarantee that the ray will pass though the start block.
|
||||||
|
// It is possible that it would miss due to rounding
|
||||||
|
// This should only move the ray by 1 grid position
|
||||||
|
|
||||||
|
if (secondError + secondStep <= 0) {
|
||||||
|
secondError = -secondStep + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thirdError + thirdStep <= 0) {
|
||||||
|
thirdError = -thirdStep + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Block lastBlock;
|
||||||
|
lastBlock = startBlock.getFace(reverseFace(mainFace));
|
||||||
|
|
||||||
|
if (secondError < 0) {
|
||||||
|
secondError += gridSize;
|
||||||
|
lastBlock = lastBlock.getFace(reverseFace(secondFace));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thirdError < 0) {
|
||||||
|
thirdError += gridSize;
|
||||||
|
lastBlock = lastBlock.getFace(reverseFace(thirdFace));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This means that when the variables are positive, it means that the coord=1 boundary has been crossed
|
||||||
|
secondError -= gridSize;
|
||||||
|
thirdError -= gridSize;
|
||||||
|
|
||||||
|
blockQueue[0] = lastBlock;
|
||||||
|
currentBlock = -1;
|
||||||
|
|
||||||
|
scan();
|
||||||
|
|
||||||
|
boolean startBlockFound = false;
|
||||||
|
|
||||||
|
for (int cnt=currentBlock; cnt>=0; cnt--) {
|
||||||
|
if (blockEquals(blockQueue[cnt], startBlock)) {
|
||||||
|
currentBlock = cnt;
|
||||||
|
startBlockFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!startBlockFound) {
|
||||||
|
throw new IllegalStateException("Start block missed in BlockIterator");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the number of planes passed to give max distance
|
||||||
|
maxDistanceInt = (int)Math.round(maxDistance/(Math.sqrt(mainDirection*mainDirection + secondDirection*secondDirection + thirdDirection*thirdDirection)/mainDirection));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean blockEquals(Block a, Block b) {
|
||||||
|
return a.getX() == b.getX() && a.getY() == b.getY() && a.getZ() == b.getZ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockFace reverseFace(BlockFace face) {
|
||||||
|
switch(face) {
|
||||||
|
case UP: return BlockFace.DOWN;
|
||||||
|
case DOWN: return BlockFace.UP;
|
||||||
|
case NORTH: return BlockFace.SOUTH;
|
||||||
|
case SOUTH: return BlockFace.NORTH;
|
||||||
|
case EAST: return BlockFace.WEST;
|
||||||
|
case WEST: return BlockFace.EAST;
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockFace getXFace(Vector direction) {
|
||||||
|
return ((direction.getX() > 0) ? BlockFace.SOUTH : BlockFace.NORTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockFace getYFace(Vector direction) {
|
||||||
|
return ((direction.getY() > 0) ? BlockFace.UP : BlockFace.DOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockFace getZFace(Vector direction) {
|
||||||
|
return ((direction.getZ() > 0) ? BlockFace.WEST : BlockFace.EAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double getXLength(Vector direction) {
|
||||||
|
return(Math.abs(direction.getX()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private double getYLength(Vector direction) {
|
||||||
|
return(Math.abs(direction.getY()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private double getZLength(Vector direction) {
|
||||||
|
return(Math.abs(direction.getZ()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private double getPosition(double direction, double position, int blockPosition) {
|
||||||
|
return direction > 0 ? (position-blockPosition) : (blockPosition + 1 - position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double getXPosition(Vector direction, Vector position, Block block) {
|
||||||
|
return getPosition(direction.getX(), position.getX(), block.getX());
|
||||||
|
}
|
||||||
|
|
||||||
|
private double getYPosition(Vector direction, Vector position, Block block) {
|
||||||
|
return getPosition(direction.getY(), position.getY(), block.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
private double getZPosition(Vector direction, Vector position, Block block) {
|
||||||
|
return getPosition(direction.getZ(), position.getZ(), block.getZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the BlockIterator
|
||||||
|
*
|
||||||
|
* @param loc The location for the start of the ray trace
|
||||||
|
* @param yOffset The trace begins vertically offset from the start vector by this value
|
||||||
|
* @param maxDistance This is the maximum distance in blocks for the trace. Setting this value above 140 may lead to problems with unloaded chunks. A value of 0 indicates no limit
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public BlockIterator(Location loc, double yOffset, int maxDistance) {
|
||||||
|
this(loc.getWorld(), loc.toVector(), loc.getDirection(), yOffset, maxDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the BlockIterator.
|
||||||
|
*
|
||||||
|
* @param loc The location for the start of the ray trace
|
||||||
|
* @param yOffset The trace begins vertically offset from the start vector by this value
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public BlockIterator(Location loc, double yOffset) {
|
||||||
|
this(loc.getWorld(), loc.toVector(), loc.getDirection(), yOffset, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the BlockIterator.
|
||||||
|
*
|
||||||
|
* @param loc The location for the start of the ray trace
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public BlockIterator(Location loc) {
|
||||||
|
this(loc, 0D);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the BlockIterator.
|
||||||
|
*
|
||||||
|
* @param entity Information from the entity is used to set up the trace
|
||||||
|
* @param maxDistance This is the maximum distance in blocks for the trace. Setting this value above 140 may lead to problems with unloaded chunks. A value of 0 indicates no limit
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public BlockIterator(LivingEntity entity, int maxDistance) {
|
||||||
|
this(entity.getLocation(), entity.getEyeHeight(), maxDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the BlockIterator.
|
||||||
|
*
|
||||||
|
* @param entity Information from the entity is used to set up the trace
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public BlockIterator(LivingEntity entity) {
|
||||||
|
this(entity, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the iteration has more elements
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public boolean hasNext() {
|
||||||
|
scan();
|
||||||
|
return currentBlock != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next Block in the trace
|
||||||
|
*
|
||||||
|
* @return the next Block in the trace
|
||||||
|
*/
|
||||||
|
|
||||||
|
public Block next() {
|
||||||
|
scan();
|
||||||
|
if (currentBlock <= -1) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
} else {
|
||||||
|
return blockQueue[currentBlock--];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove() {
|
||||||
|
throw new UnsupportedOperationException("[BlockIterator] doesn't support block removal");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scan() {
|
||||||
|
if (currentBlock >= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (maxDistance != 0 && currentDistance > maxDistanceInt) {
|
||||||
|
end = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (end) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentDistance++;
|
||||||
|
|
||||||
|
secondError += secondStep;
|
||||||
|
thirdError += thirdStep;
|
||||||
|
|
||||||
|
if (secondError > 0 && thirdError > 0) {
|
||||||
|
blockQueue[2] = blockQueue[0].getFace(mainFace);
|
||||||
|
if (((long)secondStep) * ((long)thirdError) < ((long)thirdStep) * ((long)secondError)) {
|
||||||
|
blockQueue[1] = blockQueue[2].getFace(secondFace);
|
||||||
|
blockQueue[0] = blockQueue[1].getFace(thirdFace);
|
||||||
|
} else {
|
||||||
|
blockQueue[1] = blockQueue[2].getFace(thirdFace);
|
||||||
|
blockQueue[0] = blockQueue[1].getFace(secondFace);
|
||||||
|
}
|
||||||
|
thirdError -= gridSize;
|
||||||
|
secondError -= gridSize;
|
||||||
|
currentBlock = 2;
|
||||||
|
return;
|
||||||
|
} else if (secondError > 0) {
|
||||||
|
blockQueue[1] = blockQueue[0].getFace(mainFace);
|
||||||
|
blockQueue[0] = blockQueue[1].getFace(secondFace);
|
||||||
|
secondError -= gridSize;
|
||||||
|
currentBlock = 1;
|
||||||
|
return;
|
||||||
|
} else if (thirdError > 0) {
|
||||||
|
blockQueue[1] = blockQueue[0].getFace(mainFace);
|
||||||
|
blockQueue[0] = blockQueue[1].getFace(thirdFace);
|
||||||
|
thirdError -= gridSize;
|
||||||
|
currentBlock = 1;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
blockQueue[0] = blockQueue[0].getFace(mainFace);
|
||||||
|
currentBlock = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -126,6 +126,19 @@ public class Vector implements Cloneable {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies another vector
|
||||||
|
*
|
||||||
|
* @param vec
|
||||||
|
* @return the same vector
|
||||||
|
*/
|
||||||
|
public Vector copy(Vector vec) {
|
||||||
|
x = vec.x;
|
||||||
|
y = vec.y;
|
||||||
|
z = vec.z;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the magnitude of the vector, defined as sqrt(x^2+y^2+z^2). The value
|
* Gets the magnitude of the vector, defined as sqrt(x^2+y^2+z^2). The value
|
||||||
* of this method is not cached and uses a costly square-root function, so
|
* of this method is not cached and uses a costly square-root function, so
|
||||||
|
|
Loading…
Reference in a new issue