mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-22 08:02:31 +01:00
Merge pull request #146 from RinesThaix/goals
Arrows and RangedAttackGoal
This commit is contained in:
commit
71f1e51df3
@ -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);
|
||||
@ -862,6 +867,16 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
|
||||
return getPosition().getDistance(entity.getPosition());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the distance squared between two entities.
|
||||
*
|
||||
* @param entity the entity to get the distance from
|
||||
* @return the distance squared between this and {@code entity}
|
||||
*/
|
||||
public double getDistanceSquared(@NotNull Entity entity) {
|
||||
return getPosition().getDistanceSquared(entity.getPosition());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity vehicle or null.
|
||||
*
|
||||
|
@ -22,6 +22,7 @@ import net.minestom.server.sound.Sound;
|
||||
import net.minestom.server.sound.SoundCategory;
|
||||
import net.minestom.server.utils.BlockPosition;
|
||||
import net.minestom.server.utils.Position;
|
||||
import net.minestom.server.utils.Vector;
|
||||
import net.minestom.server.utils.block.BlockIterator;
|
||||
import net.minestom.server.utils.time.CooldownUtils;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
@ -590,6 +591,30 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler {
|
||||
return blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current entity has line of sight to the given one.
|
||||
* If so, it doesn't mean that the given entity is IN line of sight of the current,
|
||||
* but the current one can rotate so that it will be true.
|
||||
*
|
||||
* @param entity the entity to be checked.
|
||||
* @return if the current entity has line of sight to the given one.
|
||||
*/
|
||||
public boolean hasLineOfSight(Entity entity) {
|
||||
Vector start = getPosition().toVector().add(0D, getEyeHeight(), 0D);
|
||||
Vector end = entity.getPosition().toVector().add(0D, getEyeHeight(), 0D);
|
||||
Vector direction = end.subtract(start);
|
||||
int maxDistance = (int) Math.ceil(direction.length());
|
||||
|
||||
Iterator<BlockPosition> it = new BlockIterator(start, direction.normalize(), 0D, maxDistance);
|
||||
while (it.hasNext()) {
|
||||
Block block = Block.fromStateId(getInstance().getBlockStateId(it.next()));
|
||||
if (!block.isAir() && !block.isLiquid()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the target (not-air) {@link BlockPosition} of the entity.
|
||||
*
|
||||
|
@ -0,0 +1,121 @@
|
||||
package net.minestom.server.entity.ai.goal;
|
||||
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.entity.EntityCreature;
|
||||
import net.minestom.server.entity.ai.GoalSelector;
|
||||
import net.minestom.server.entity.pathfinding.Navigator;
|
||||
import net.minestom.server.entity.type.Projectile;
|
||||
import net.minestom.server.entity.type.projectile.EntityArrow;
|
||||
import net.minestom.server.utils.Position;
|
||||
import net.minestom.server.utils.time.CooldownUtils;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public class RangedAttackGoal extends GoalSelector {
|
||||
|
||||
private long lastShot;
|
||||
private final int delay;
|
||||
private final TimeUnit timeUnit;
|
||||
private final int attackRangeSquared;
|
||||
private final int desirableRangeSquared;
|
||||
private final boolean comeClose;
|
||||
private final double power;
|
||||
private final double spread;
|
||||
|
||||
private BiFunction<Entity, Position, Projectile> projectileGenerator;
|
||||
|
||||
private boolean stop;
|
||||
|
||||
/**
|
||||
* @param entityCreature the entity to add the goal to.
|
||||
* @param delay the delay between each shots.
|
||||
* @param attackRange the allowed range the entity can shoot others.
|
||||
* @param desirableRange the desirable range: the entity will try to stay no further than this distance.
|
||||
* @param comeClose whether entity should go as close as possible to the target whether target is not in line of sight.
|
||||
* @param spread shot spread (0 for best accuracy).
|
||||
* @param power shot power (1 for normal).
|
||||
* @param timeUnit the unit of the delay.
|
||||
*/
|
||||
public RangedAttackGoal(@NotNull EntityCreature entityCreature, int delay, int attackRange, int desirableRange, boolean comeClose, double power, double spread, @NotNull TimeUnit timeUnit) {
|
||||
super(entityCreature);
|
||||
this.delay = delay;
|
||||
this.timeUnit = timeUnit;
|
||||
this.attackRangeSquared = attackRange * attackRange;
|
||||
this.desirableRangeSquared = desirableRange * desirableRange;
|
||||
this.comeClose = comeClose;
|
||||
this.power = power;
|
||||
this.spread = spread;
|
||||
Check.argCondition(desirableRange > attackRange, "Desirable range can not exceed attack range!");
|
||||
}
|
||||
|
||||
public void setProjectileGenerator(BiFunction<Entity, Position, Projectile> projectileGenerator) {
|
||||
this.projectileGenerator = projectileGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldStart() {
|
||||
return findAndUpdateTarget() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
Entity target = findAndUpdateTarget();
|
||||
Check.notNull(target, "The target is not expected to be null!");
|
||||
this.entityCreature.getNavigator().setPathTo(target.getPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(long time) {
|
||||
Entity target = findAndUpdateTarget();
|
||||
if (target == null) {
|
||||
this.stop = true;
|
||||
return;
|
||||
}
|
||||
double distanceSquared = this.entityCreature.getDistanceSquared(target);
|
||||
boolean comeClose = false;
|
||||
if (distanceSquared <= this.attackRangeSquared) {
|
||||
if (!CooldownUtils.hasCooldown(time, this.lastShot, this.timeUnit, this.delay)) {
|
||||
if (this.entityCreature.hasLineOfSight(target)) {
|
||||
Position to = target.getPosition().clone().add(0D, target.getEyeHeight(), 0D);
|
||||
|
||||
BiFunction<Entity, Position, Projectile> projectileGenerator = this.projectileGenerator;
|
||||
if (projectileGenerator == null) {
|
||||
projectileGenerator = EntityArrow::new;
|
||||
}
|
||||
Projectile projectile = projectileGenerator.apply(this.entityCreature, new Position(0D, 0D, 0D));
|
||||
|
||||
Projectile.shoot(projectile, this.entityCreature, to, this.power, this.spread);
|
||||
this.lastShot = time;
|
||||
} else {
|
||||
comeClose = this.comeClose;
|
||||
}
|
||||
}
|
||||
}
|
||||
Navigator navigator = this.entityCreature.getNavigator();
|
||||
Position pathPosition = navigator.getPathPosition();
|
||||
if (!comeClose && distanceSquared <= this.desirableRangeSquared) {
|
||||
if (pathPosition != null) {
|
||||
navigator.setPathTo(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Position targetPosition = target.getPosition();
|
||||
if (pathPosition == null || !pathPosition.isSimilar(targetPosition)) {
|
||||
navigator.setPathTo(targetPosition);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnd() {
|
||||
return this.stop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
// Stop following the target
|
||||
this.entityCreature.getNavigator().setPathTo(null);
|
||||
}
|
||||
}
|
@ -1,4 +1,58 @@
|
||||
package net.minestom.server.entity.type;
|
||||
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.event.entity.EntityShootEvent;
|
||||
import net.minestom.server.utils.Position;
|
||||
import net.minestom.server.utils.Vector;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public interface Projectile {
|
||||
|
||||
static void shoot(@NotNull Projectile projectile, @NotNull Entity shooter, Position to, double power, double spread) {
|
||||
Check.argCondition(!(projectile instanceof Entity), "Projectile must be an instance of Entity!");
|
||||
EntityShootEvent event = new EntityShootEvent(shooter, projectile, to, power, spread);
|
||||
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.getPower(), event.getSpread());
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
static void shoot(@NotNull Projectile projectile, @NotNull Position from, @NotNull Position to, double power, double spread) {
|
||||
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();
|
||||
double dz = to.getZ() - from.getZ();
|
||||
double xzLength = Math.sqrt(dx * dx + dz * dz);
|
||||
dy += xzLength * 0.20000000298023224D;
|
||||
|
||||
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;
|
||||
Vector velocity = proj.getVelocity();
|
||||
velocity.setX(dx);
|
||||
velocity.setY(dy);
|
||||
velocity.setZ(dz);
|
||||
velocity.multiply(20 * power);
|
||||
proj.setView(
|
||||
(float) Math.toDegrees(Math.atan2(dx, dz)),
|
||||
(float) Math.toDegrees(Math.atan2(dy, Math.sqrt(dx * dx + dz * dz)))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,177 @@
|
||||
package net.minestom.server.entity.type.projectile;
|
||||
|
||||
import net.minestom.server.entity.*;
|
||||
import net.minestom.server.entity.damage.DamageType;
|
||||
import net.minestom.server.entity.type.Projectile;
|
||||
import net.minestom.server.instance.Chunk;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
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;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class EntityAbstractArrow extends ObjectEntity implements Projectile {
|
||||
|
||||
private final static byte CRITICAL_BIT = 0x01;
|
||||
private final static byte NO_CLIP_BIT = 0x02;
|
||||
|
||||
private final Entity shooter;
|
||||
|
||||
EntityAbstractArrow(@Nullable Entity shooter, @NotNull EntityType entityType, @NotNull Position spawnPosition) {
|
||||
super(entityType, spawnPosition);
|
||||
this.shooter = shooter;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether an arrow is stuck in block / hit an entity.
|
||||
*
|
||||
* @param pos position right before current tick.
|
||||
* @param posNow position after current tick.
|
||||
* @return if an arrow is stuck in block / hit an entity.
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private boolean isStuck(Position pos, Position posNow) {
|
||||
if (pos.isSimilar(posNow)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Instance instance = getInstance();
|
||||
Chunk chunk = null;
|
||||
Collection<Entity> entities = null;
|
||||
|
||||
/*
|
||||
What we're about to do is to discretely jump from the previous position to the new one.
|
||||
For each point we will be checking blocks and entities we're in.
|
||||
*/
|
||||
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 we're at last part, we can't just add another direction-vector, because we can exceed end point.
|
||||
if (i == parts - 1) {
|
||||
pos.setX(posNow.getX());
|
||||
pos.setY(posNow.getY());
|
||||
pos.setZ(posNow.getZ());
|
||||
} else {
|
||||
pos.add(direction);
|
||||
}
|
||||
BlockPosition bpos = pos.toBlockPosition();
|
||||
Block block = instance.getBlock(bpos.getX(), bpos.getY() - 1, bpos.getZ());
|
||||
if (!block.isAir() && !block.isLiquid()) {
|
||||
teleport(pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
Chunk currentChunk = instance.getChunkAt(pos);
|
||||
if (currentChunk != chunk) {
|
||||
chunk = currentChunk;
|
||||
entities = instance.getChunkEntities(chunk)
|
||||
.stream()
|
||||
.filter(entity -> entity instanceof LivingEntity)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
/*
|
||||
We won't check collisions with entities for first ticks of arrow's life, because it spawns in the
|
||||
shooter and will immediately damage him.
|
||||
*/
|
||||
if (getAliveTicks() < 3) {
|
||||
continue;
|
||||
}
|
||||
Optional<Entity> victimOptional = entities.stream()
|
||||
.filter(entity -> entity.getBoundingBox().intersect(pos.getX(), pos.getY(), pos.getZ()))
|
||||
.findAny();
|
||||
if (victimOptional.isPresent()) {
|
||||
LivingEntity victim = (LivingEntity) victimOptional.get();
|
||||
victim.setArrowCount(victim.getArrowCount() + 1);
|
||||
victim.damage(DamageType.fromProjectile(this.shooter, this), 2F);
|
||||
remove();
|
||||
return super.onGround;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setCritical(boolean value) {
|
||||
modifyMask(CRITICAL_BIT, value);
|
||||
}
|
||||
|
||||
public boolean isCritical() {
|
||||
return (getMask() & CRITICAL_BIT) != 0;
|
||||
}
|
||||
|
||||
public void setNoClip(boolean value) {
|
||||
modifyMask(NO_CLIP_BIT, value);
|
||||
}
|
||||
|
||||
public boolean isNoClip() {
|
||||
return (getMask() & NO_CLIP_BIT) != 0;
|
||||
}
|
||||
|
||||
public void setPiercingLevel(byte value) {
|
||||
this.metadata.setIndex((byte) 8, Metadata.Byte(value));
|
||||
}
|
||||
|
||||
public byte getPiercingLevel() {
|
||||
return this.metadata.getIndex((byte) 8, (byte) 0);
|
||||
}
|
||||
|
||||
private byte getMask() {
|
||||
return this.metadata.getIndex((byte) 7, (byte) 0);
|
||||
}
|
||||
|
||||
private void setMask(byte mask) {
|
||||
this.metadata.setIndex((byte) 7, Metadata.Byte(mask));
|
||||
}
|
||||
|
||||
private void modifyMask(byte bit, boolean value) {
|
||||
byte mask = getMask();
|
||||
boolean isPresent = (mask & bit) == bit;
|
||||
if (isPresent == value) {
|
||||
return;
|
||||
}
|
||||
if (value) {
|
||||
mask |= bit;
|
||||
} else {
|
||||
mask &= ~bit;
|
||||
}
|
||||
setMask(mask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getObjectData() {
|
||||
return this.shooter == null ? 0 : this.shooter.getEntityId() + 1;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package net.minestom.server.entity.type.projectile;
|
||||
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.entity.EntityType;
|
||||
import net.minestom.server.entity.Metadata;
|
||||
import net.minestom.server.utils.Position;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class EntityArrow extends EntityAbstractArrow {
|
||||
|
||||
public EntityArrow(@Nullable Entity shooter, @NotNull Position spawnPosition) {
|
||||
super(shooter, EntityType.ARROW, spawnPosition);
|
||||
|
||||
}
|
||||
|
||||
public void setColor(int value) {
|
||||
this.metadata.setIndex((byte) 9, Metadata.VarInt(value));
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return this.metadata.getIndex((byte) 9, -1);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package net.minestom.server.entity.type.projectile;
|
||||
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.entity.EntityType;
|
||||
import net.minestom.server.utils.Position;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class EntitySpectralArrow extends EntityAbstractArrow {
|
||||
|
||||
public EntitySpectralArrow(@Nullable Entity shooter, @NotNull Position spawnPosition) {
|
||||
super(shooter, EntityType.SPECTRAL_ARROW, spawnPosition);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package net.minestom.server.event.entity;
|
||||
|
||||
import net.minestom.server.entity.Entity;
|
||||
import net.minestom.server.entity.type.Projectile;
|
||||
import net.minestom.server.event.CancellableEvent;
|
||||
import net.minestom.server.event.EntityEvent;
|
||||
import net.minestom.server.utils.Position;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Called with {@link Projectile#shoot(Projectile, Entity, Position, double)}.
|
||||
*/
|
||||
public class EntityShootEvent extends EntityEvent implements CancellableEvent {
|
||||
|
||||
private final Projectile projectile;
|
||||
private final Position to;
|
||||
private double power;
|
||||
private double spread;
|
||||
|
||||
private boolean cancelled;
|
||||
|
||||
public EntityShootEvent(@NotNull Entity entity, @NotNull Projectile projectile, @NotNull Position to, double power, double spread) {
|
||||
super(entity);
|
||||
this.projectile = projectile;
|
||||
this.to = to;
|
||||
this.power = power;
|
||||
this.spread = spread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the projectile.
|
||||
*
|
||||
* @return the projectile.
|
||||
*/
|
||||
public Projectile getProjectile() {
|
||||
return this.projectile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position projectile was shot to.
|
||||
*
|
||||
* @return the position projectile was shot to.
|
||||
*/
|
||||
public Position getTo() {
|
||||
return this.to;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets shot spread.
|
||||
*
|
||||
* @return shot spread.
|
||||
*/
|
||||
public double getSpread() {
|
||||
return this.spread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets shot spread.
|
||||
*
|
||||
* @param spread shot spread.
|
||||
*/
|
||||
public void setSpread(double spread) {
|
||||
this.spread = spread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets shot power.
|
||||
*
|
||||
* @return shot power.
|
||||
*/
|
||||
public double getPower() {
|
||||
return this.power;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets shot power.
|
||||
*
|
||||
* @param power shot power.
|
||||
*/
|
||||
public void setPower(double power) {
|
||||
this.power = power;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
this.cancelled = cancel;
|
||||
}
|
||||
|
||||
}
|
@ -345,23 +345,19 @@ public class BlockIterator implements Iterator<BlockPosition> {
|
||||
thirdError -= gridSize;
|
||||
secondError -= gridSize;
|
||||
currentBlock = 2;
|
||||
return;
|
||||
} else if (secondError > 0) {
|
||||
blockQueue[1] = blockQueue[0].getRelative(mainFace);
|
||||
blockQueue[0] = blockQueue[1].getRelative(secondFace);
|
||||
secondError -= gridSize;
|
||||
currentBlock = 1;
|
||||
return;
|
||||
} else if (thirdError > 0) {
|
||||
blockQueue[1] = blockQueue[0].getRelative(mainFace);
|
||||
blockQueue[0] = blockQueue[1].getRelative(thirdFace);
|
||||
thirdError -= gridSize;
|
||||
currentBlock = 1;
|
||||
return;
|
||||
} else {
|
||||
blockQueue[0] = blockQueue[0].getRelative(mainFace);
|
||||
currentBlock = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"));
|
||||
|
||||
|
67
src/test/java/demo/commands/ShootCommand.java
Normal file
67
src/test/java/demo/commands/ShootCommand.java
Normal file
@ -0,0 +1,67 @@
|
||||
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;
|
||||
|
||||
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, 1D, 0D);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user