hollow-cube/projectile-physics-improvements

* Add better protectiles

* Cleanup

* better physics

* Add filter

* Update EntityCollision.java

* Negate filter check
This commit is contained in:
iam 2023-06-27 17:40:57 -04:00 committed by GitHub
parent 8a5c610b7b
commit 0400e2dda4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 468 additions and 50 deletions

View File

@ -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,

View File

@ -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;

View File

@ -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);
}

View File

@ -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
);
}
}

View File

@ -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
) {
}

View File

@ -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));
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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,

View 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);
}
}
}

View File

@ -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);
}