mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-04 23:47:59 +01:00
hollow-cube/projectile-physics-improvements
* Add better protectiles * Cleanup * better physics * Add filter * Update EntityCollision.java * Negate filter check
This commit is contained in:
parent
8a5c610b7b
commit
0400e2dda4
@ -24,10 +24,11 @@ final class BlockCollision {
|
||||
static PhysicsResult handlePhysics(@NotNull BoundingBox boundingBox,
|
||||
@NotNull Vec velocity, @NotNull Pos entityPosition,
|
||||
@NotNull Block.Getter getter,
|
||||
@Nullable PhysicsResult lastPhysicsResult) {
|
||||
@Nullable PhysicsResult lastPhysicsResult,
|
||||
boolean singleCollision) {
|
||||
if (velocity.isZero()) {
|
||||
// TODO should return a constant
|
||||
return new PhysicsResult(entityPosition, Vec.ZERO, false, false, false, false, velocity, null, Block.AIR);
|
||||
return new PhysicsResult(entityPosition, Vec.ZERO, false, false, false, false, velocity, new Point[3], new Shape[3], false, SweepResult.NO_COLLISION);
|
||||
}
|
||||
// Fast-exit using cache
|
||||
final PhysicsResult cachedResult = cachedPhysics(velocity, entityPosition, getter, lastPhysicsResult);
|
||||
@ -35,7 +36,7 @@ final class BlockCollision {
|
||||
return cachedResult;
|
||||
}
|
||||
// Expensive AABB computation
|
||||
return stepPhysics(boundingBox, velocity, entityPosition, getter);
|
||||
return stepPhysics(boundingBox, velocity, entityPosition, getter, singleCollision);
|
||||
}
|
||||
|
||||
static Entity canPlaceBlockAt(Instance instance, Point blockPos, Block b) {
|
||||
@ -67,30 +68,35 @@ final class BlockCollision {
|
||||
|
||||
private static PhysicsResult cachedPhysics(Vec velocity, Pos entityPosition,
|
||||
Block.Getter getter, PhysicsResult lastPhysicsResult) {
|
||||
if (lastPhysicsResult != null) {
|
||||
if (lastPhysicsResult != null && lastPhysicsResult.collisionShapes()[1] instanceof ShapeImpl shape) {
|
||||
Block collisionBlockY = shape.block();
|
||||
|
||||
// Fast exit if entity hasn't moved
|
||||
if (lastPhysicsResult.collisionY()
|
||||
&& velocity.y() == lastPhysicsResult.originalDelta().y()
|
||||
&& lastPhysicsResult.collidedBlockY() != null
|
||||
&& getter.getBlock(lastPhysicsResult.collidedBlockY(), Block.Getter.Condition.TYPE) == lastPhysicsResult.blockTypeY()
|
||||
// Check block below to fast exit gravity
|
||||
&& getter.getBlock(lastPhysicsResult.collisionPoints()[1].sub(0, Vec.EPSILON, 0), Block.Getter.Condition.TYPE) == collisionBlockY
|
||||
&& velocity.x() == 0 && velocity.z() == 0
|
||||
&& entityPosition.samePoint(lastPhysicsResult.newPosition())
|
||||
&& lastPhysicsResult.blockTypeY() != Block.AIR) {
|
||||
&& collisionBlockY != Block.AIR) {
|
||||
return lastPhysicsResult;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private static PhysicsResult stepPhysics(@NotNull BoundingBox boundingBox,
|
||||
@NotNull Vec velocity, @NotNull Pos entityPosition,
|
||||
@NotNull Block.Getter getter) {
|
||||
@NotNull Block.Getter getter, boolean singleCollision) {
|
||||
// Allocate once and update values
|
||||
SweepResult finalResult = new SweepResult(1 - Vec.EPSILON, 0, 0, 0, null);
|
||||
SweepResult finalResult = new SweepResult(1 - Vec.EPSILON, 0, 0, 0, null, null);
|
||||
|
||||
boolean foundCollisionX = false, foundCollisionY = false, foundCollisionZ = false;
|
||||
Point collisionYBlock = null;
|
||||
Block blockYType = Block.AIR;
|
||||
|
||||
Point[] collidedPoints = new Point[3];
|
||||
Shape[] collisionShapes = new Shape[3];
|
||||
|
||||
boolean hasCollided = false;
|
||||
|
||||
// Query faces to get the points needed for collision
|
||||
final Vec[] allFaces = calculateFaces(velocity, boundingBox);
|
||||
@ -100,36 +106,48 @@ final class BlockCollision {
|
||||
// Looping until there are no collisions will allow the entity to move in axis other than the collision axis after a collision.
|
||||
while (result.collisionX() || result.collisionY() || result.collisionZ()) {
|
||||
// Reset final result
|
||||
finalResult.res = 1 - Vec.EPSILON;
|
||||
finalResult.normalX = 0;
|
||||
finalResult.normalY = 0;
|
||||
finalResult.normalZ = 0;
|
||||
|
||||
if (result.collisionX()) foundCollisionX = true;
|
||||
if (result.collisionZ()) foundCollisionZ = true;
|
||||
if (result.collisionY()) {
|
||||
if (result.collisionX()) {
|
||||
foundCollisionX = true;
|
||||
collisionShapes[0] = finalResult.collidedShape;
|
||||
collidedPoints[0] = finalResult.collidedPosition;
|
||||
hasCollided = true;
|
||||
if (singleCollision) break;
|
||||
} else if (result.collisionZ()) {
|
||||
foundCollisionZ = true;
|
||||
collisionShapes[2] = finalResult.collidedShape;
|
||||
collidedPoints[2] = finalResult.collidedPosition;
|
||||
hasCollided = true;
|
||||
if (singleCollision) break;
|
||||
} else if (result.collisionY()) {
|
||||
foundCollisionY = true;
|
||||
// If we are only moving in the y-axis
|
||||
if (!result.collisionX() && !result.collisionZ() && velocity.x() == 0 && velocity.z() == 0) {
|
||||
collisionYBlock = result.collidedBlockY();
|
||||
blockYType = result.blockTypeY();
|
||||
}
|
||||
collisionShapes[1] = finalResult.collidedShape;
|
||||
collidedPoints[1] = finalResult.collidedPosition;
|
||||
hasCollided = true;
|
||||
if (singleCollision) break;
|
||||
}
|
||||
|
||||
// If all axis have had collisions, break
|
||||
if (foundCollisionX && foundCollisionY && foundCollisionZ) break;
|
||||
// If the entity isn't moving, break
|
||||
if (result.newVelocity().isZero()) break;
|
||||
|
||||
finalResult.res = 1 - Vec.EPSILON;
|
||||
result = computePhysics(boundingBox, result.newVelocity(), result.newPosition(), getter, allFaces, finalResult);
|
||||
}
|
||||
|
||||
finalResult.res = result.res().res;
|
||||
|
||||
final double newDeltaX = foundCollisionX ? 0 : velocity.x();
|
||||
final double newDeltaY = foundCollisionY ? 0 : velocity.y();
|
||||
final double newDeltaZ = foundCollisionZ ? 0 : velocity.z();
|
||||
|
||||
return new PhysicsResult(result.newPosition(), new Vec(newDeltaX, newDeltaY, newDeltaZ),
|
||||
newDeltaY == 0 && velocity.y() < 0,
|
||||
foundCollisionX, foundCollisionY, foundCollisionZ, velocity, collisionYBlock, blockYType);
|
||||
foundCollisionX, foundCollisionY, foundCollisionZ, velocity, collidedPoints, collisionShapes, hasCollided, finalResult);
|
||||
}
|
||||
|
||||
private static PhysicsResult computePhysics(@NotNull BoundingBox boundingBox,
|
||||
@ -165,7 +183,7 @@ final class BlockCollision {
|
||||
|
||||
return new PhysicsResult(finalPos, new Vec(remainingX, remainingY, remainingZ),
|
||||
collisionY, collisionX, collisionY, collisionZ,
|
||||
Vec.ZERO, finalResult.collidedShapePosition, finalResult.blockType);
|
||||
Vec.ZERO, null, null, false, finalResult);
|
||||
}
|
||||
|
||||
private static void slowPhysics(@NotNull BoundingBox boundingBox,
|
||||
|
@ -9,6 +9,8 @@ import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* See https://wiki.vg/Entity_metadata#Mobs_2
|
||||
*/
|
||||
@ -51,9 +53,8 @@ public final class BoundingBox implements Shape {
|
||||
@ApiStatus.Experimental
|
||||
public boolean intersectBoxSwept(@NotNull Point rayStart, @NotNull Point rayDirection, @NotNull Point shapePos, @NotNull BoundingBox moving, @NotNull SweepResult finalResult) {
|
||||
if (RayUtils.BoundingBoxIntersectionCheck(moving, rayStart, rayDirection, this, shapePos, finalResult) ) {
|
||||
finalResult.collidedShapePosition = shapePos;
|
||||
finalResult.collidedPosition = rayStart.add(rayDirection.mul(finalResult.res));
|
||||
finalResult.collidedShape = this;
|
||||
finalResult.blockType = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -160,6 +161,52 @@ public final class BoundingBox implements Shape {
|
||||
return relativeEnd().z();
|
||||
}
|
||||
|
||||
public Iterator<Point> getBlocks(Point point) {
|
||||
return new PointIterator(this, point);
|
||||
}
|
||||
|
||||
static class PointIterator implements Iterator<Point> {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int z = 0;
|
||||
|
||||
private final int minX, minY, minZ, maxX, maxY, maxZ;
|
||||
|
||||
public PointIterator(BoundingBox boundingBox, Point p) {
|
||||
minX = (int) Math.floor(boundingBox.minX() + p.x());
|
||||
minY = (int) Math.floor(boundingBox.minY() + p.y());
|
||||
minZ = (int) Math.floor(boundingBox.minZ() + p.z());
|
||||
maxX = (int) Math.floor(boundingBox.maxX() + p.x());
|
||||
maxY = (int) Math.floor(boundingBox.maxY() + p.y());
|
||||
maxZ = (int) Math.floor(boundingBox.maxZ() + p.z());
|
||||
x = minX;
|
||||
y = minY;
|
||||
z = minZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return x <= maxX && y <= maxY && z <= maxZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point next() {
|
||||
var res = new Vec(x, y, z);
|
||||
|
||||
x++;
|
||||
if (x > maxX) {
|
||||
x = minX;
|
||||
y++;
|
||||
if (y > maxY) {
|
||||
y = minY;
|
||||
z++;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
@ -14,6 +14,8 @@ import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
@ApiStatus.Internal
|
||||
@ApiStatus.Experimental
|
||||
public final class CollisionUtils {
|
||||
@ -25,6 +27,55 @@ public final class CollisionUtils {
|
||||
* All bounding boxes inside the full blocks are checked for collisions with the entity.
|
||||
*
|
||||
* @param entity the entity to move
|
||||
* @param entityVelocity the velocity of the entity
|
||||
* @param lastPhysicsResult the last physics result, can be null
|
||||
* @param singleCollision if the entity should only collide with one block
|
||||
* @return the result of physics simulation
|
||||
*/
|
||||
public static PhysicsResult handlePhysics(@NotNull Entity entity, @NotNull Vec entityVelocity,
|
||||
@Nullable PhysicsResult lastPhysicsResult, boolean singleCollision) {
|
||||
final Instance instance = entity.getInstance();
|
||||
assert instance != null;
|
||||
return handlePhysics(instance, entity.getChunk(),
|
||||
entity.getBoundingBox(),
|
||||
entity.getPosition(), entityVelocity,
|
||||
lastPhysicsResult, singleCollision);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param entity the entity to move
|
||||
* @param entityVelocity the velocity of the entity
|
||||
* @return the closest entity we collide with
|
||||
*/
|
||||
public static PhysicsResult checkEntityCollisions(@NotNull Entity entity, @NotNull Vec entityVelocity) {
|
||||
final Instance instance = entity.getInstance();
|
||||
assert instance != null;
|
||||
return checkEntityCollisions(instance, entity.getBoundingBox(), entity.getPosition(), entityVelocity, 3, e -> true, null);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param velocity the velocity of the entity
|
||||
* @param extendRadius the largest entity bounding box we can collide with
|
||||
* Measured from bottom center to top corner
|
||||
* This is used to extend the search radius for entities we collide with
|
||||
* For players this is (0.3^2 + 0.3^2 + 1.8^2) ^ (1/3) ~= 1.51
|
||||
* @return the closest entity we collide with
|
||||
*/
|
||||
public static PhysicsResult checkEntityCollisions(@NotNull Instance instance, BoundingBox boundingBox, Point pos, @NotNull Vec velocity, double extendRadius, Function<Entity, Boolean> entityFilter, PhysicsResult blockResult) {
|
||||
return EntityCollision.checkCollision(instance, boundingBox, pos, velocity, extendRadius, entityFilter, blockResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves an entity with physics applied (ie checking against blocks)
|
||||
* <p>
|
||||
* Works by getting all the full blocks that an entity could interact with.
|
||||
* All bounding boxes inside the full blocks are checked for collisions with the entity.
|
||||
*
|
||||
* @param entity the entity to move
|
||||
* @param entityVelocity the velocity of the entity
|
||||
* @param lastPhysicsResult the last physics result, can be null
|
||||
* @return the result of physics simulation
|
||||
*/
|
||||
public static PhysicsResult handlePhysics(@NotNull Entity entity, @NotNull Vec entityVelocity,
|
||||
@ -34,7 +85,7 @@ public final class CollisionUtils {
|
||||
return handlePhysics(instance, entity.getChunk(),
|
||||
entity.getBoundingBox(),
|
||||
entity.getPosition(), entityVelocity,
|
||||
lastPhysicsResult);
|
||||
lastPhysicsResult, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -49,11 +100,11 @@ public final class CollisionUtils {
|
||||
public static PhysicsResult handlePhysics(@NotNull Instance instance, @Nullable Chunk chunk,
|
||||
@NotNull BoundingBox boundingBox,
|
||||
@NotNull Pos position, @NotNull Vec velocity,
|
||||
@Nullable PhysicsResult lastPhysicsResult) {
|
||||
@Nullable PhysicsResult lastPhysicsResult, boolean singleCollision) {
|
||||
final Block.Getter getter = new ChunkCache(instance, chunk != null ? chunk : instance.getChunkAt(position), Block.STONE);
|
||||
return BlockCollision.handlePhysics(boundingBox,
|
||||
velocity, position,
|
||||
getter, lastPhysicsResult);
|
||||
getter, lastPhysicsResult, singleCollision);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,7 +124,8 @@ public final class CollisionUtils {
|
||||
final PhysicsResult result = handlePhysics(instance, chunk,
|
||||
BoundingBox.ZERO,
|
||||
Pos.fromPoint(start), Vec.fromPoint(end.sub(start)),
|
||||
null);
|
||||
null, false);
|
||||
|
||||
return shape.intersectBox(end.sub(result.newPosition()).sub(Vec.EPSILON), BoundingBox.ZERO);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,68 @@
|
||||
package net.minestom.server.collision;
|
||||
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.coordinate.Pos;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import java.util.function.Function;
|
||||
|
||||
final class EntityCollision {
|
||||
public static PhysicsResult checkCollision(Instance instance, BoundingBox boundingBox, Point point, Vec entityVelocity, double extendRadius, Function<Entity, Boolean> entityFilter, PhysicsResult res) {
|
||||
double minimumRes = res != null ? res.res().res : Double.MAX_VALUE;
|
||||
|
||||
if (instance == null) return null;
|
||||
SweepResult sweepResult = new SweepResult(minimumRes, 0, 0, 0, null, null);
|
||||
|
||||
double closestDistance = minimumRes;
|
||||
Entity closestEntity = null;
|
||||
|
||||
var maxDistance = Math.pow(boundingBox.height() * boundingBox.height() + boundingBox.depth()/2 * boundingBox.depth()/2 + boundingBox.width()/2 * boundingBox.width()/2, 1/3.0);
|
||||
double projectileDistance = entityVelocity.length();
|
||||
|
||||
for (Entity e : instance.getNearbyEntities(point, extendRadius + maxDistance + projectileDistance)) {
|
||||
if (!entityFilter.apply(e)) continue;
|
||||
if (!e.hasCollision()) continue;
|
||||
|
||||
// Overlapping with entity, math can't be done we return the entity
|
||||
if (e.getBoundingBox().intersectBox(e.getPosition().sub(point), boundingBox)) {
|
||||
var p = Pos.fromPoint(point);
|
||||
|
||||
return new PhysicsResult(p,
|
||||
Vec.ZERO,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
entityVelocity,
|
||||
new Pos[] {p, p, p},
|
||||
new Shape[] {e, e, e},
|
||||
true,
|
||||
sweepResult);
|
||||
}
|
||||
|
||||
// Check collisions with entity
|
||||
e.getBoundingBox().intersectBoxSwept(point, entityVelocity, e.getPosition(), boundingBox, sweepResult);
|
||||
|
||||
if (sweepResult.res < closestDistance && sweepResult.res < 1) {
|
||||
closestDistance = sweepResult.res;
|
||||
closestEntity = e;
|
||||
}
|
||||
}
|
||||
|
||||
Pos[] collisionPoints = new Pos[3];
|
||||
|
||||
return new PhysicsResult(Pos.fromPoint(point).add(entityVelocity.mul(closestDistance)),
|
||||
Vec.ZERO,
|
||||
sweepResult.normalY == -1,
|
||||
sweepResult.normalX != 0,
|
||||
sweepResult.normalY != 0,
|
||||
sweepResult.normalZ != 0,
|
||||
entityVelocity,
|
||||
collisionPoints,
|
||||
new Shape[] {closestEntity, closestEntity, closestEntity},
|
||||
sweepResult.normalX != 0 || sweepResult.normalZ != 0 || sweepResult.normalY != 0,
|
||||
sweepResult
|
||||
);
|
||||
}
|
||||
}
|
@ -3,11 +3,33 @@ package net.minestom.server.collision;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.coordinate.Pos;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ApiStatus.Experimental
|
||||
public record PhysicsResult(Pos newPosition, Vec newVelocity, boolean isOnGround,
|
||||
boolean collisionX, boolean collisionY, boolean collisionZ,
|
||||
Vec originalDelta, Point collidedBlockY, Block blockTypeY) {
|
||||
/**
|
||||
* The result of a physics simulation.
|
||||
* @param newPosition the new position of the entity
|
||||
* @param newVelocity the new velocity of the entity
|
||||
* @param isOnGround if the entity is on the ground
|
||||
* @param collisionX if the entity collided on the X axis
|
||||
* @param collisionY if the entity collided on the Y axis
|
||||
* @param collisionZ if the entity collided on the Z axis
|
||||
* @param originalDelta the velocity delta of the entity
|
||||
* @param collisionPoints the points where the entity collided
|
||||
* @param collisionShapes the shapes the entity collided with
|
||||
*/
|
||||
public record PhysicsResult(
|
||||
Pos newPosition,
|
||||
Vec newVelocity,
|
||||
boolean isOnGround,
|
||||
boolean collisionX,
|
||||
boolean collisionY,
|
||||
boolean collisionZ,
|
||||
Vec originalDelta,
|
||||
@NotNull Point[] collisionPoints,
|
||||
@NotNull Shape[] collisionShapes,
|
||||
boolean hasCollision,
|
||||
SweepResult res
|
||||
) {
|
||||
}
|
||||
|
@ -174,6 +174,6 @@ final class RayUtils {
|
||||
}
|
||||
|
||||
public static boolean BoundingBoxRayIntersectionCheck(Vec start, Vec direction, BoundingBox boundingBox, Pos position) {
|
||||
return BoundingBoxIntersectionCheck(BoundingBox.ZERO, start, direction, boundingBox, position, new SweepResult(Double.MAX_VALUE, 0, 0, 0, null));
|
||||
return BoundingBoxIntersectionCheck(BoundingBox.ZERO, start, direction, boundingBox, position, new SweepResult(Double.MAX_VALUE, 0, 0, 0, null, null));
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
final class ShapeImpl implements Shape {
|
||||
public final class ShapeImpl implements Shape {
|
||||
private static final Pattern PATTERN = Pattern.compile("\\d.\\d{1,3}", Pattern.MULTILINE);
|
||||
private final BoundingBox[] collisionBoundingBoxes;
|
||||
private final Point relativeStart, relativeEnd;
|
||||
@ -165,16 +165,15 @@ final class ShapeImpl implements Shape {
|
||||
for (BoundingBox blockSection : collisionBoundingBoxes) {
|
||||
// Update final result if the temp result collision is sooner than the current final result
|
||||
if (RayUtils.BoundingBoxIntersectionCheck(moving, rayStart, rayDirection, blockSection, shapePos, finalResult)) {
|
||||
finalResult.collidedShapePosition = shapePos;
|
||||
finalResult.collidedPosition = rayStart.add(rayDirection.mul(finalResult.res));
|
||||
finalResult.collidedShape = this;
|
||||
finalResult.blockType = block();
|
||||
hitBlock = true;
|
||||
}
|
||||
}
|
||||
return hitBlock;
|
||||
}
|
||||
|
||||
private Block block() {
|
||||
public Block block() {
|
||||
Block block = this.block;
|
||||
if (block == null) this.block = block = Block.fromStateId((short) blockEntry.stateId());
|
||||
return block;
|
||||
|
@ -1,13 +1,15 @@
|
||||
package net.minestom.server.collision;
|
||||
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.coordinate.Pos;
|
||||
|
||||
public final class SweepResult {
|
||||
public static SweepResult NO_COLLISION = new SweepResult(Double.MAX_VALUE, 0, 0, 0, null, Pos.ZERO);
|
||||
|
||||
final class SweepResult {
|
||||
double res;
|
||||
double normalX, normalY, normalZ;
|
||||
Point collidedShapePosition;
|
||||
Block blockType;
|
||||
Point collidedPosition;
|
||||
Point collidedPos;
|
||||
Shape collidedShape;
|
||||
|
||||
/**
|
||||
@ -18,11 +20,12 @@ final class SweepResult {
|
||||
* @param normalY -1 if intersected on bottom, 1 if intersected on top
|
||||
* @param normalZ -1 if intersected on front, 1 if intersected on back
|
||||
*/
|
||||
public SweepResult(double res, double normalX, double normalY, double normalZ, Shape collidedShape) {
|
||||
public SweepResult(double res, double normalX, double normalY, double normalZ, Shape collidedShape, Point collidedPos) {
|
||||
this.res = res;
|
||||
this.normalX = normalX;
|
||||
this.normalY = normalY;
|
||||
this.normalZ = normalZ;
|
||||
this.collidedShape = collidedShape;
|
||||
this.collidedPos = collidedPos;
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,7 @@ import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.ServerProcess;
|
||||
import net.minestom.server.Tickable;
|
||||
import net.minestom.server.Viewable;
|
||||
import net.minestom.server.collision.BoundingBox;
|
||||
import net.minestom.server.collision.CollisionUtils;
|
||||
import net.minestom.server.collision.PhysicsResult;
|
||||
import net.minestom.server.collision.*;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.coordinate.Pos;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
@ -30,6 +28,7 @@ import net.minestom.server.instance.EntityTracker;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.instance.InstanceManager;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.instance.block.BlockFace;
|
||||
import net.minestom.server.instance.block.BlockHandler;
|
||||
import net.minestom.server.network.packet.server.CachedPacket;
|
||||
import net.minestom.server.network.packet.server.LazyPacket;
|
||||
@ -85,7 +84,7 @@ import java.util.function.UnaryOperator;
|
||||
* To create your own entity you probably want to extends {@link LivingEntity} or {@link EntityCreature} instead.
|
||||
*/
|
||||
public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, EventHandler<EntityEvent>, Taggable,
|
||||
PermissionHandler, HoverEventSource<ShowEntity>, Sound.Emitter {
|
||||
PermissionHandler, HoverEventSource<ShowEntity>, Sound.Emitter, Shape {
|
||||
|
||||
private static final int VELOCITY_UPDATE_INTERVAL = 1;
|
||||
|
||||
@ -111,6 +110,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
||||
protected Vec velocity = Vec.ZERO; // Movement in block per second
|
||||
protected boolean lastVelocityWasZero = true;
|
||||
protected boolean hasPhysics = true;
|
||||
protected boolean hasCollision = true;
|
||||
|
||||
/**
|
||||
* The amount of drag applied on the Y axle.
|
||||
@ -627,9 +627,10 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
||||
}
|
||||
|
||||
// Update velocity
|
||||
if (hasVelocity || !newVelocity.isZero()) {
|
||||
if (!noGravity && (hasVelocity || !newVelocity.isZero())) {
|
||||
updateVelocity(wasOnGround, flying, positionBeforeMove, newVelocity);
|
||||
}
|
||||
|
||||
// Verify if velocity packet has to be sent
|
||||
if (this.ticks % VELOCITY_UPDATE_INTERVAL == 0) {
|
||||
if (!isPlayer && (hasVelocity || !lastVelocityWasZero)) {
|
||||
@ -1729,6 +1730,35 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
|
||||
return nearby.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOccluded(@NotNull Shape shape, @NotNull BlockFace face) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersectBox(@NotNull Point positionRelative, @NotNull BoundingBox boundingBox) {
|
||||
return boundingBox.intersectBox(positionRelative, boundingBox);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean intersectBoxSwept(@NotNull Point rayStart, @NotNull Point rayDirection, @NotNull Point shapePos, @NotNull BoundingBox moving, @NotNull SweepResult finalResult) {
|
||||
return boundingBox.intersectBoxSwept(rayStart, rayDirection, shapePos, moving, finalResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Point relativeStart() {
|
||||
return boundingBox.relativeStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Point relativeEnd() {
|
||||
return boundingBox.relativeEnd();
|
||||
}
|
||||
|
||||
public boolean hasCollision() {
|
||||
return hasCollision;
|
||||
}
|
||||
|
||||
public enum Pose {
|
||||
STANDING,
|
||||
FALL_FLYING,
|
||||
|
179
src/main/java/net/minestom/server/entity/PlayerProjectile.java
Normal file
179
src/main/java/net/minestom/server/entity/PlayerProjectile.java
Normal file
@ -0,0 +1,179 @@
|
||||
package net.minestom.server.entity;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.collision.CollisionUtils;
|
||||
import net.minestom.server.collision.PhysicsResult;
|
||||
import net.minestom.server.collision.ShapeImpl;
|
||||
import net.minestom.server.coordinate.Point;
|
||||
import net.minestom.server.coordinate.Pos;
|
||||
import net.minestom.server.coordinate.Vec;
|
||||
import net.minestom.server.entity.metadata.ProjectileMeta;
|
||||
import net.minestom.server.event.EventDispatcher;
|
||||
import net.minestom.server.event.entity.EntityShootEvent;
|
||||
import net.minestom.server.event.entity.projectile.ProjectileCollideWithBlockEvent;
|
||||
import net.minestom.server.event.entity.projectile.ProjectileCollideWithEntityEvent;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class PlayerProjectile extends LivingEntity {
|
||||
private final Entity shooter;
|
||||
private long cooldown = 0;
|
||||
|
||||
public PlayerProjectile(Entity shooter, EntityType type) {
|
||||
super(type);
|
||||
this.shooter = shooter;
|
||||
this.hasCollision = false;
|
||||
setup();
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
super.hasPhysics = false;
|
||||
if (getEntityMeta() instanceof ProjectileMeta) {
|
||||
((ProjectileMeta) getEntityMeta()).setShooter(this.shooter);
|
||||
}
|
||||
}
|
||||
|
||||
public void shoot(Point from, double power, double spread) {
|
||||
var to = from.add(shooter.getPosition().direction());
|
||||
shoot(from, to, power, spread);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) {
|
||||
var res = super.setInstance(instance, spawnPosition);
|
||||
|
||||
Pos insideBlock = checkInsideBlock(instance);
|
||||
// Check if we're inside of a block
|
||||
if (insideBlock != null) {
|
||||
var e = new ProjectileCollideWithBlockEvent(this, Pos.fromPoint(spawnPosition), instance.getBlock(spawnPosition));
|
||||
MinecraftServer.getGlobalEventHandler().call(e);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public void shoot(@NotNull Point from, @NotNull Point to, double power, double spread) {
|
||||
var instance = shooter.getInstance();
|
||||
if (instance == null) return;
|
||||
|
||||
float yaw = -shooter.getPosition().yaw();
|
||||
float pitch = -shooter.getPosition().pitch();
|
||||
|
||||
double pitchDiff = pitch - 45;
|
||||
if (pitchDiff == 0) pitchDiff = 0.0001;
|
||||
double pitchAdjust = pitchDiff * 0.002145329238474369D;
|
||||
|
||||
double dx = to.x() - from.x();
|
||||
double dy = to.y() - from.y() + pitchAdjust;
|
||||
double dz = to.z() - from.z();
|
||||
if (!hasNoGravity()) {
|
||||
final double xzLength = Math.sqrt(dx * dx + dz * dz);
|
||||
dy += xzLength * 0.20000000298023224D;
|
||||
}
|
||||
|
||||
final double length = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
dx /= length;
|
||||
dy /= length;
|
||||
dz /= length;
|
||||
Random random = ThreadLocalRandom.current();
|
||||
spread *= 0.007499999832361937D;
|
||||
dx += random.nextGaussian() * spread;
|
||||
dy += random.nextGaussian() * spread;
|
||||
dz += random.nextGaussian() * spread;
|
||||
|
||||
final EntityShootEvent shootEvent = new EntityShootEvent(this.shooter, this, from, power, spread);
|
||||
EventDispatcher.call(shootEvent);
|
||||
if (shootEvent.isCancelled()) {
|
||||
remove();
|
||||
return;
|
||||
}
|
||||
|
||||
final double mul = 20 * power;
|
||||
Vec v = new Vec(dx * mul, dy * mul * 0.9, dz * mul);
|
||||
|
||||
this.setInstance(instance, new Pos(from.x(), from.y(), from.z(), yaw, pitch)).whenComplete((result, throwable) -> {
|
||||
if (throwable != null) {
|
||||
throwable.printStackTrace();
|
||||
} else {
|
||||
this.setVelocity(v);
|
||||
}
|
||||
});
|
||||
|
||||
cooldown = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
private Pos checkInsideBlock(@NotNull Instance instance) {
|
||||
var iterator = this.getBoundingBox().getBlocks(this.getPosition());
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
var block = iterator.next();
|
||||
Block b = instance.getBlock(block);
|
||||
var hit = b.registry().collisionShape().intersectBox(this.getPosition().sub(block), this.getBoundingBox());
|
||||
if (hit) return Pos.fromPoint(block);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshPosition(@NotNull Pos newPosition) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(long time) {
|
||||
final Pos posBefore = getPosition();
|
||||
super.tick(time);
|
||||
final Pos posNow = getPosition();
|
||||
|
||||
Vec diff = Vec.fromPoint(posNow.sub(posBefore));
|
||||
PhysicsResult result = CollisionUtils.handlePhysics(
|
||||
instance, this.getChunk(),
|
||||
this.getBoundingBox(),
|
||||
posBefore, diff,
|
||||
null, true
|
||||
);
|
||||
|
||||
if (cooldown + 500 < System.currentTimeMillis()) {
|
||||
float yaw = (float) Math.toDegrees(Math.atan2(diff.x(), diff.z()));
|
||||
float pitch = (float) Math.toDegrees(Math.atan2(diff.y(), Math.sqrt(diff.x() * diff.x() + diff.z() * diff.z())));
|
||||
super.refreshPosition(new Pos(posNow.x(), posNow.y(), posNow.z(), yaw, pitch));
|
||||
cooldown = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
PhysicsResult collided = CollisionUtils.checkEntityCollisions(instance, this.getBoundingBox(), posBefore, diff, 3, (e) -> e != this, result);
|
||||
if (collided != null && collided.collisionShapes()[0] != shooter) {
|
||||
if (collided.collisionShapes()[0] instanceof Entity entity) {
|
||||
var e = new ProjectileCollideWithEntityEvent(this, collided.newPosition(), entity);
|
||||
MinecraftServer.getGlobalEventHandler().call(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.hasCollision()) {
|
||||
Block hitBlock = null;
|
||||
Point hitPoint = null;
|
||||
if (result.collisionShapes()[0] instanceof ShapeImpl block) {
|
||||
hitBlock = block.block();
|
||||
hitPoint = result.collisionPoints()[0];
|
||||
}
|
||||
if (result.collisionShapes()[1] instanceof ShapeImpl block) {
|
||||
hitBlock = block.block();
|
||||
hitPoint = result.collisionPoints()[1];
|
||||
}
|
||||
if (result.collisionShapes()[2] instanceof ShapeImpl block) {
|
||||
hitBlock = block.block();
|
||||
hitPoint = result.collisionPoints()[2];
|
||||
}
|
||||
|
||||
if (hitBlock == null) return;
|
||||
|
||||
var e = new ProjectileCollideWithBlockEvent(this, Pos.fromPoint(hitPoint), hitBlock);
|
||||
MinecraftServer.getGlobalEventHandler().call(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -410,12 +410,12 @@ public class EntityBlockPhysicsIntegrationTest {
|
||||
|
||||
BoundingBox bb = new Entity(EntityType.ZOMBIE).getBoundingBox();
|
||||
|
||||
SweepResult sweepResultFinal = new SweepResult(1, 0, 0, 0, null);
|
||||
SweepResult sweepResultFinal = new SweepResult(1, 0, 0, 0, null, null);
|
||||
|
||||
bb.intersectBoxSwept(z1, movement, z2, bb, sweepResultFinal);
|
||||
bb.intersectBoxSwept(z1, movement, z3, bb, sweepResultFinal);
|
||||
|
||||
assertEquals(new Pos(11, 0, 0), sweepResultFinal.collidedShapePosition);
|
||||
assertEqualsPoint(new Pos(10.4, 0.52, 0), sweepResultFinal.collidedPosition);
|
||||
assertEquals(sweepResultFinal.collidedShape, bb);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user