Block collisions for arrows

This commit is contained in:
Konstantin Shandurenko 2021-02-22 14:42:46 +03:00
parent 3a251934ec
commit dbfebc50ec
7 changed files with 149 additions and 16 deletions

View File

@ -91,7 +91,8 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
protected Entity vehicle;
// Velocity
protected Vector velocity = new Vector(); // Movement in block per second
protected Vector velocity = new Vector(); // Movement in block per second
protected boolean hasPhysics = true;
protected double gravityDragPerTick;
protected double gravityAcceleration;
@ -483,7 +484,11 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
getVelocity().getZ() / tps
);
this.onGround = CollisionUtils.handlePhysics(this, deltaPos, newPosition, newVelocityOut);
if (this.hasPhysics) {
this.onGround = CollisionUtils.handlePhysics(this, deltaPos, newPosition, newVelocityOut);
} else {
newVelocityOut = deltaPos;
}
// World border collision
final Position finalVelocityPosition = CollisionUtils.applyWorldBorder(instance, position, newPosition);

View File

@ -48,7 +48,7 @@ public class RangedAttackGoal extends GoalSelector {
this.desirableRangeSquared = desirableRange * desirableRange;
this.comeClose = comeClose;
this.spread = spread;
Check.argCondition(desirableRange <= attackRange, "Desirable range can not exceed attack range!");
Check.argCondition(desirableRange > attackRange, "Desirable range can not exceed attack range!");
}
public void setProjectileGenerator(BiFunction<Entity, Position, Projectile> projectileGenerator) {

View File

@ -13,16 +13,21 @@ import java.util.concurrent.ThreadLocalRandom;
public interface Projectile {
static void shoot(@NotNull Projectile projectile, @NotNull Entity shooter, Position to, double spread) {
Check.argCondition(!(projectile instanceof Entity), "Projectile must be an instance of Entity!");
EntityShootEvent event = new EntityShootEvent(shooter, projectile, to, spread);
shooter.callCancellableEvent(EntityShootEvent.class, event, () -> {
Position from = shooter.getPosition().clone().add(0D, shooter.getEyeHeight(), 0D);
shoot(projectile, from, to, event.getSpread());
});
shooter.callEvent(EntityShootEvent.class, event);
if (event.isCancelled()) {
Entity proj = (Entity) projectile;
proj.remove();
return;
}
Position from = shooter.getPosition().clone().add(0D, shooter.getEyeHeight(), 0D);
shoot(projectile, from, to, event.getSpread());
}
@SuppressWarnings("ConstantConditions")
static void shoot(@NotNull Projectile projectile, @NotNull Position from, @NotNull Position to, double spread) {
Check.argCondition(projectile instanceof Entity, "Projectile must be an instance of Entity!");
Check.argCondition(!(projectile instanceof Entity), "Projectile must be an instance of Entity!");
Entity proj = (Entity) projectile;
double dx = to.getX() - from.getX();
double dy = to.getY() - from.getY();
@ -39,17 +44,15 @@ public interface Projectile {
dx += random.nextGaussian() * spread;
dy += random.nextGaussian() * spread;
dz += random.nextGaussian() * spread;
dx *= 2;
dy *= 2;
dz *= 2;
Vector velocity = proj.getVelocity();
velocity.setX(dx);
velocity.setY(dy);
velocity.setZ(dz);
xzLength = Math.sqrt(dx * dx + dz * dz);
double yaw = Math.max(Math.abs(dx), Math.abs(dz));
double pitch = Math.max(Math.abs(dy), Math.abs(xzLength));
proj.setView((float) (yaw * Math.toDegrees(1D)), (float) (pitch * Math.toDegrees(1D)));
velocity.multiply(20);
proj.setView(
(float) Math.toDegrees(Math.atan2(dx, dz)),
(float) Math.toDegrees(Math.atan2(dy, Math.sqrt(dx * dx + dz * dz)))
);
}
}

View File

@ -5,7 +5,11 @@ import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.Metadata;
import net.minestom.server.entity.ObjectEntity;
import net.minestom.server.entity.type.Projectile;
import net.minestom.server.instance.block.Block;
import net.minestom.server.network.packet.server.play.EntityTeleportPacket;
import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.Position;
import net.minestom.server.utils.Vector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -17,11 +21,60 @@ public class EntityAbstractArrow extends ObjectEntity implements Projectile {
private final static byte CRITICAL_BIT = 0x01;
private final static byte NO_CLIP_BIT = 0x02;
private final int shooterID;
private final int shooterID;
EntityAbstractArrow(@Nullable Entity shooter, @NotNull EntityType entityType, @NotNull Position spawnPosition) {
super(entityType, spawnPosition);
this.shooterID = shooter == null ? 0 : shooter.getEntityId();
super.hasPhysics = false;
setBoundingBox(.5F, .5F, .5F);
}
@Override
public void tick(long time) {
Position posBefore = getPosition().clone();
super.tick(time);
Position posNow = getPosition().clone();
if (isStuck(posBefore, posNow)) {
if (super.onGround) {
return;
}
super.onGround = true;
getVelocity().zero();
sendPacketToViewersAndSelf(getVelocityPacket());
setNoGravity(true);
} else {
if (!super.onGround) {
return;
}
super.onGround = false;
setNoGravity(false);
}
}
private boolean isStuck(Position pos, Position posNow) {
if (pos.isSimilar(posNow)) {
return true;
}
double part = .25D; // half of the bounding box
Vector dir = posNow.toVector().subtract(pos.toVector());
int parts = (int) Math.ceil(dir.length() / part);
Position direction = dir.normalize().multiply(part).toPosition();
for (int i = 0; i < parts; ++i) {
if (i == parts - 1) {
pos = posNow;
} else {
pos.add(direction);
}
BlockPosition bpos = pos.toBlockPosition();
Block block = getInstance().getBlock(bpos.getX(), bpos.getY() - 1, bpos.getZ());
if (!block.isAir() && !block.isLiquid()) {
teleport(pos);
return true;
}
}
return false;
}
public void setCritical(boolean value) {

View File

@ -14,6 +14,7 @@ public class EntityArrow extends EntityAbstractArrow {
public EntityArrow(@Nullable Entity shooter, @NotNull Position spawnPosition) {
super(shooter, EntityType.ARROW, spawnPosition);
}
public void setColor(int value) {

View File

@ -42,6 +42,7 @@ public class Main {
commandManager.register(new PotionCommand());
commandManager.register(new TitleCommand());
commandManager.register(new BookCommand());
commandManager.register(new ShootCommand());
commandManager.setUnknownCommandCallback((sender, command) -> sender.sendMessage("unknown command"));

View File

@ -0,0 +1,70 @@
package demo.commands;
import net.minestom.server.command.CommandSender;
import net.minestom.server.command.builder.Arguments;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.command.builder.exception.ArgumentSyntaxException;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.type.Projectile;
import net.minestom.server.entity.type.projectile.EntityArrow;
import net.minestom.server.entity.type.projectile.EntitySpectralArrow;
import java.util.concurrent.ThreadLocalRandom;
/**
* Created by k.shandurenko on 22.02.2021
*/
public class ShootCommand extends Command {
public ShootCommand() {
super("shoot");
setCondition(this::condition);
setDefaultExecutor(this::defaultExecutor);
var typeArg = ArgumentType.Word("type").from("default", "spectral", "colored");
setArgumentCallback(this::onTypeError, typeArg);
addSyntax(this::onShootCommand, typeArg);
}
private boolean condition(CommandSender sender, String commandString) {
if (!sender.isPlayer()) {
sender.sendMessage("The command is only available for player");
return false;
}
return true;
}
private void defaultExecutor(CommandSender sender, Arguments args) {
sender.sendMessage("Correct usage: shoot [default/spectral/colored]");
}
private void onTypeError(CommandSender sender, ArgumentSyntaxException exception) {
sender.sendMessage("SYNTAX ERROR: '" + exception.getInput() + "' should be replaced by 'default', 'spectral' or 'colored'");
}
private void onShootCommand(CommandSender sender, Arguments args) {
Player player = (Player) sender;
String mode = args.getWord("type");
Projectile projectile;
var pos = player.getPosition().clone().add(0D, player.getEyeHeight(), 0D);
switch (mode) {
case "default":
projectile = new EntityArrow(player, pos);
break;
case "spectral":
projectile = new EntitySpectralArrow(player, pos);
break;
case "colored":
projectile = new EntityArrow(player, pos);
((EntityArrow) projectile).setColor(ThreadLocalRandom.current().nextInt());
break;
default:
return;
}
((Entity) projectile).setInstance(player.getInstance());
var dir = pos.getDirection().multiply(30D);
pos = pos.clone().add(dir.getX(), dir.getY(), dir.getZ());
Projectile.shoot(projectile, player, pos, 0D);
}
}