Merge pull request #155 from RinesThaix/entities

Projectile improvements, custom entity position synchronization cooldown support
This commit is contained in:
TheMode 2021-03-02 13:38:50 +01:00 committed by GitHub
commit 3971777e56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 351 additions and 173 deletions

View File

@ -101,6 +101,7 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission
// Network synchronization, send the absolute position of the entity each X milliseconds
private static final UpdateOption SYNCHRONIZATION_COOLDOWN = new UpdateOption(1500, TimeUnit.MILLISECOND);
private UpdateOption customSynchronizationCooldown;
private long lastAbsoluteSynchronizationTime;
// Events
@ -670,7 +671,7 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission
}
// Scheduled synchronization
if (!CooldownUtils.hasCooldown(time, lastAbsoluteSynchronizationTime, SYNCHRONIZATION_COOLDOWN)) {
if (!CooldownUtils.hasCooldown(time, lastAbsoluteSynchronizationTime, getSynchronizationCooldown())) {
this.lastAbsoluteSynchronizationTime = time;
sendSynchronization();
}
@ -1485,6 +1486,22 @@ public class Entity implements Viewable, EventHandler, DataContainer, Permission
this.lastAbsoluteSynchronizationTime = 0;
}
/**
* Set custom cooldown for position synchronization.
*
* @param cooldown custom cooldown for position synchronization.
*/
public void setCustomSynchronizationCooldown(@Nullable UpdateOption cooldown) {
this.customSynchronizationCooldown = cooldown;
}
private UpdateOption getSynchronizationCooldown() {
if (this.customSynchronizationCooldown != null) {
return this.customSynchronizationCooldown;
}
return SYNCHRONIZATION_COOLDOWN;
}
public enum Pose {
STANDING,
FALL_FLYING,

View File

@ -0,0 +1,177 @@
package net.minestom.server.entity.ai.goal;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityCreature;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.ai.GoalSelector;
import net.minestom.server.entity.pathfinding.Navigator;
import net.minestom.server.entity.type.projectile.EntityProjectile;
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.Function;
/**
* Allows entity to perform both melee and ranged attacks.
*/
public class CombinedAttackGoal extends GoalSelector {
private final int meleeRangeSquared;
private final int meleeDelay;
private final TimeUnit meleeTimeUnit;
private final int rangedRangeSquared;
private final double rangedPower;
private final double rangedSpread;
private final int rangedDelay;
private final TimeUnit rangedTimeUnit;
private final int desirableRangeSquared;
private final boolean comeClose;
private Function<Entity, EntityProjectile> projectileGenerator;
private long lastAttack;
private boolean stop;
private Entity cachedTarget;
/**
* @param entityCreature the entity to add the goal to.
* @param meleeRange the allowed range the entity can hit others in melee.
* @param rangedRange the allowed range the entity can shoot others.
* @param rangedPower shot power (1 for normal).
* @param rangedSpread shot spread (0 for best accuracy).
* @param delay the delay between any attacks.
* @param timeUnit the unit of the delay.
* @param desirableRange the desirable range: the entity will try to stay no further than this distance.
* @param comeClose if entity should go as close as possible to the target whether target is not in line of sight for a ranged attack.
*/
public CombinedAttackGoal(@NotNull EntityCreature entityCreature,
int meleeRange, int rangedRange, double rangedPower, double rangedSpread,
int delay, TimeUnit timeUnit,
int desirableRange, boolean comeClose) {
this(
entityCreature,
meleeRange, delay, timeUnit,
rangedRange, rangedPower, rangedSpread, delay, timeUnit,
desirableRange, comeClose
);
}
/**
* @param entityCreature the entity to add the goal to.
* @param meleeRange the allowed range the entity can hit others in melee.
* @param meleeDelay the delay between melee attacks.
* @param meleeTimeUnit the unit of the melee delay.
* @param rangedRange the allowed range the entity can shoot others.
* @param rangedPower shot power (1 for normal).
* @param rangedSpread shot spread (0 for best accuracy).
* @param rangedDelay the delay between ranged attacks.
* @param rangedTimeUnit the unit of the ranged delay.
* @param desirableRange the desirable range: the entity will try to stay no further than this distance.
* @param comeClose if entity should go as close as possible to the target whether target is not in line of sight for a ranged attack.
*/
public CombinedAttackGoal(@NotNull EntityCreature entityCreature,
int meleeRange, int meleeDelay, TimeUnit meleeTimeUnit,
int rangedRange, double rangedPower, double rangedSpread, int rangedDelay, TimeUnit rangedTimeUnit,
int desirableRange, boolean comeClose) {
super(entityCreature);
this.meleeRangeSquared = meleeRange * meleeRange;
this.meleeDelay = meleeDelay;
this.meleeTimeUnit = meleeTimeUnit;
this.rangedRangeSquared = rangedRange * rangedRange;
this.rangedPower = rangedPower;
this.rangedSpread = rangedSpread;
this.rangedDelay = rangedDelay;
this.rangedTimeUnit = rangedTimeUnit;
this.desirableRangeSquared = desirableRange * desirableRange;
this.comeClose = comeClose;
Check.argCondition(desirableRange > rangedRange, "Desirable range can not exceed ranged range!");
}
public void setProjectileGenerator(Function<Entity, EntityProjectile> projectileGenerator) {
this.projectileGenerator = projectileGenerator;
}
@Override
public boolean shouldStart() {
this.cachedTarget = findTarget();
return this.cachedTarget != null;
}
@Override
public void start() {
this.entityCreature.getNavigator().setPathTo(this.cachedTarget.getPosition());
}
@Override
public void tick(long time) {
Entity target;
if (this.cachedTarget != null) {
target = this.cachedTarget;
this.cachedTarget = null;
} else {
target = findTarget();
}
if (target == null) {
this.stop = true;
return;
}
double distanceSquared = this.entityCreature.getDistanceSquared(target);
boolean comeClose = false;
// First of all, checking if to perform melee or ranged attack depending on the distance to target.
if (distanceSquared <= this.meleeRangeSquared) {
if (!CooldownUtils.hasCooldown(time, this.lastAttack, this.meleeTimeUnit, this.meleeDelay)) {
this.entityCreature.attack(target, true);
this.lastAttack = time;
}
} else if (distanceSquared <= this.rangedRangeSquared) {
if (!CooldownUtils.hasCooldown(time, this.lastAttack, this.rangedTimeUnit, this.rangedDelay)) {
if (this.entityCreature.hasLineOfSight(target)) {
// If target is on line of entity sight, ranged attack can be performed
Position to = target.getPosition().clone().add(0D, target.getEyeHeight(), 0D);
Function<Entity, EntityProjectile> projectileGenerator = this.projectileGenerator;
if (projectileGenerator == null) {
projectileGenerator = shooter -> new EntityProjectile(shooter, EntityType.ARROW);
}
EntityProjectile projectile = projectileGenerator.apply(this.entityCreature);
projectile.setInstance(this.entityCreature.getInstance(), this.entityCreature.getPosition());
projectile.shoot(to, this.rangedPower, this.rangedSpread);
this.lastAttack = time;
} else {
// Otherwise deciding whether to go to the enemy.
comeClose = this.comeClose;
}
}
}
Navigator navigator = this.entityCreature.getNavigator();
Position pathPosition = navigator.getPathPosition();
// If we don't want to come close and we're already within desirable range, no movement is needed.
if (!comeClose && distanceSquared <= this.desirableRangeSquared) {
if (pathPosition != null) {
navigator.setPathTo(null);
}
return;
}
// Otherwise going to the target.
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);
}
}

View File

@ -2,17 +2,17 @@ package net.minestom.server.entity.ai.goal;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityCreature;
import net.minestom.server.entity.EntityType;
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.entity.type.projectile.EntityProjectile;
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;
import java.util.function.Function;
public class RangedAttackGoal extends GoalSelector {
@ -25,7 +25,7 @@ public class RangedAttackGoal extends GoalSelector {
private final double power;
private final double spread;
private BiFunction<Entity, Position, Projectile> projectileGenerator;
private Function<Entity, EntityProjectile> projectileGenerator;
private boolean stop;
private Entity cachedTarget;
@ -52,7 +52,7 @@ public class RangedAttackGoal extends GoalSelector {
Check.argCondition(desirableRange > attackRange, "Desirable range can not exceed attack range!");
}
public void setProjectileGenerator(BiFunction<Entity, Position, Projectile> projectileGenerator) {
public void setProjectileGenerator(Function<Entity, EntityProjectile> projectileGenerator) {
this.projectileGenerator = projectileGenerator;
}
@ -87,13 +87,14 @@ public class RangedAttackGoal extends GoalSelector {
if (this.entityCreature.hasLineOfSight(target)) {
Position to = target.getPosition().clone().add(0D, target.getEyeHeight(), 0D);
BiFunction<Entity, Position, Projectile> projectileGenerator = this.projectileGenerator;
Function<Entity, EntityProjectile> projectileGenerator = this.projectileGenerator;
if (projectileGenerator == null) {
projectileGenerator = EntityArrow::new;
projectileGenerator = shooter -> new EntityProjectile(shooter, EntityType.ARROW);
}
Projectile projectile = projectileGenerator.apply(this.entityCreature, new Position(0D, 0D, 0D));
EntityProjectile projectile = projectileGenerator.apply(this.entityCreature);
projectile.setInstance(this.entityCreature.getInstance(), this.entityCreature.getPosition());
Projectile.shoot(projectile, this.entityCreature, to, this.power, this.spread);
projectile.shoot(to, this.power, this.spread);
this.lastShot = time;
} else {
comeClose = this.comeClose;

View File

@ -0,0 +1,13 @@
package net.minestom.server.entity.metadata;
import net.minestom.server.entity.Entity;
import org.jetbrains.annotations.Nullable;
public interface ProjectileMeta {
@Nullable
Entity getShooter();
void setShooter(@Nullable Entity shooter);
}

View File

@ -3,10 +3,11 @@ package net.minestom.server.entity.metadata.arrow;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Metadata;
import net.minestom.server.entity.metadata.ObjectDataProvider;
import net.minestom.server.entity.metadata.ProjectileMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ArrowMeta extends AbstractArrowMeta implements ObjectDataProvider {
public class ArrowMeta extends AbstractArrowMeta implements ObjectDataProvider, ProjectileMeta {
private Entity shooter;
@ -22,11 +23,13 @@ public class ArrowMeta extends AbstractArrowMeta implements ObjectDataProvider {
super.metadata.setIndex((byte) 9, Metadata.VarInt(value));
}
@Override
@Nullable
public Entity getShooter() {
return this.shooter;
}
@Override
public void setShooter(@Nullable Entity shooter) {
this.shooter = shooter;
}

View File

@ -3,10 +3,11 @@ package net.minestom.server.entity.metadata.arrow;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Metadata;
import net.minestom.server.entity.metadata.ObjectDataProvider;
import net.minestom.server.entity.metadata.ProjectileMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class SpectralArrowMeta extends AbstractArrowMeta implements ObjectDataProvider {
public class SpectralArrowMeta extends AbstractArrowMeta implements ObjectDataProvider, ProjectileMeta {
private Entity shooter;
@ -14,11 +15,13 @@ public class SpectralArrowMeta extends AbstractArrowMeta implements ObjectDataPr
super(entity, metadata);
}
@Override
@Nullable
public Entity getShooter() {
return this.shooter;
}
@Override
public void setShooter(@Nullable Entity shooter) {
this.shooter = shooter;
}

View File

@ -3,11 +3,12 @@ package net.minestom.server.entity.metadata.item;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Metadata;
import net.minestom.server.entity.metadata.ObjectDataProvider;
import net.minestom.server.entity.metadata.ProjectileMeta;
import net.minestom.server.item.Material;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class FireballMeta extends ItemContainingMeta implements ObjectDataProvider {
public class FireballMeta extends ItemContainingMeta implements ObjectDataProvider, ProjectileMeta {
private Entity shooter;
@ -15,11 +16,13 @@ public class FireballMeta extends ItemContainingMeta implements ObjectDataProvid
super(entity, metadata, Material.AIR);
}
@Override
@Nullable
public Entity getShooter() {
return shooter;
}
@Override
public void setShooter(@Nullable Entity shooter) {
this.shooter = shooter;
}

View File

@ -3,11 +3,12 @@ package net.minestom.server.entity.metadata.item;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Metadata;
import net.minestom.server.entity.metadata.ObjectDataProvider;
import net.minestom.server.entity.metadata.ProjectileMeta;
import net.minestom.server.item.Material;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class SmallFireballMeta extends ItemContainingMeta implements ObjectDataProvider {
public class SmallFireballMeta extends ItemContainingMeta implements ObjectDataProvider, ProjectileMeta {
private Entity shooter;
@ -15,11 +16,13 @@ public class SmallFireballMeta extends ItemContainingMeta implements ObjectDataP
super(entity, metadata, Material.FIRE_CHARGE);
}
@Override
@Nullable
public Entity getShooter() {
return shooter;
}
@Override
public void setShooter(@Nullable Entity shooter) {
this.shooter = shooter;
}

View File

@ -4,10 +4,11 @@ import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Metadata;
import net.minestom.server.entity.metadata.EntityMeta;
import net.minestom.server.entity.metadata.ObjectDataProvider;
import net.minestom.server.entity.metadata.ProjectileMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class DragonFireballMeta extends EntityMeta implements ObjectDataProvider {
public class DragonFireballMeta extends EntityMeta implements ObjectDataProvider, ProjectileMeta {
private Entity shooter;
@ -15,11 +16,13 @@ public class DragonFireballMeta extends EntityMeta implements ObjectDataProvider
super(entity, metadata);
}
@Override
@Nullable
public Entity getShooter() {
return shooter;
}
@Override
public void setShooter(@Nullable Entity shooter) {
this.shooter = shooter;
}

View File

@ -3,11 +3,12 @@ package net.minestom.server.entity.metadata.other;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Metadata;
import net.minestom.server.entity.metadata.EntityMeta;
import net.minestom.server.entity.metadata.ProjectileMeta;
import net.minestom.server.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class FireworkRocketMeta extends EntityMeta {
public class FireworkRocketMeta extends EntityMeta implements ProjectileMeta {
private Entity shooter;
@ -24,11 +25,13 @@ public class FireworkRocketMeta extends EntityMeta {
super.metadata.setIndex((byte) 7, Metadata.Slot(value));
}
@Override
@Nullable
public Entity getShooter() {
return this.shooter;
}
@Override
public void setShooter(@Nullable Entity value) {
this.shooter = value;
Integer entityID = value == null ? null : value.getEntityId();

View File

@ -4,10 +4,11 @@ import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Metadata;
import net.minestom.server.entity.metadata.EntityMeta;
import net.minestom.server.entity.metadata.ObjectDataProvider;
import net.minestom.server.entity.metadata.ProjectileMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class WitherSkullMeta extends EntityMeta implements ObjectDataProvider {
public class WitherSkullMeta extends EntityMeta implements ObjectDataProvider, ProjectileMeta {
private Entity shooter;
@ -23,11 +24,13 @@ public class WitherSkullMeta extends EntityMeta implements ObjectDataProvider {
super.metadata.setIndex((byte) 7, Metadata.Boolean(value));
}
@Override
@Nullable
public Entity getShooter() {
return shooter;
}
@Override
public void setShooter(@Nullable Entity shooter) {
this.shooter = shooter;
}

View File

@ -1,67 +0,0 @@
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 org.jetbrains.annotations.Nullable;
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)))
);
}
/**
* Gets the shooter of this projectile.
*
* @return the shooter of this projectile.
*/
@Nullable
Entity getShooter();
}

View File

@ -1,26 +0,0 @@
package net.minestom.server.entity.type.projectile;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.ObjectEntity;
import net.minestom.server.entity.type.Projectile;
import net.minestom.server.utils.Position;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public abstract class AbstractProjectile extends Entity implements Projectile {
private final Entity shooter;
public AbstractProjectile(@Nullable Entity shooter, @NotNull EntityType entityType, @NotNull Position spawnPosition) {
super(entityType, spawnPosition);
setGravity(0.02f, 0.04f, 1.96f);
this.shooter = shooter;
}
@Override
public Entity getShooter() {
return this.shooter;
}
}

View File

@ -1,18 +0,0 @@
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.entity.metadata.arrow.ArrowMeta;
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);
((ArrowMeta) getEntityMeta()).setShooter(shooter);
}
}

View File

@ -13,10 +13,10 @@ import org.jetbrains.annotations.Nullable;
* @deprecated Use {@link net.minestom.server.entity.metadata.item.EyeOfEnderMeta} instead.
*/
@Deprecated
public class EntityEyeOfEnder extends AbstractProjectile {
public class EntityEyeOfEnder extends Entity {
public EntityEyeOfEnder(@Nullable Entity shooter, @NotNull Position spawnPosition) {
super(shooter, EntityType.EYE_OF_ENDER, spawnPosition);
super(EntityType.EYE_OF_ENDER, spawnPosition);
}

View File

@ -12,10 +12,10 @@ import org.jetbrains.annotations.Nullable;
* @deprecated Use {@link net.minestom.server.entity.metadata.item.ThrownPotionMeta} instead.
*/
@Deprecated
public class EntityPotion extends AbstractProjectile {
public class EntityPotion extends Entity {
public EntityPotion(@Nullable Entity shooter, @NotNull Position spawnPosition, @NotNull ItemStack potion) {
super(shooter, EntityType.POTION, spawnPosition);
super(EntityType.POTION, spawnPosition);
setBoundingBox(0.25f, 0.25f, 0.25f);
setPotion(potion);
}

View File

@ -3,8 +3,9 @@ package net.minestom.server.entity.type.projectile;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.LivingEntity;
import net.minestom.server.entity.Metadata;
import net.minestom.server.entity.metadata.ProjectileMeta;
import net.minestom.server.event.entity.EntityAttackEvent;
import net.minestom.server.event.entity.EntityShootEvent;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block;
@ -16,17 +17,94 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
public class EntityAbstractArrow extends AbstractProjectile {
/**
* Class that allows to instantiate entities with projectile-like physics handling.
*/
public class EntityProjectile extends Entity {
EntityAbstractArrow(@Nullable Entity shooter, @NotNull EntityType entityType, @NotNull Position spawnPosition) {
super(shooter, entityType, spawnPosition);
private final Entity shooter;
public EntityProjectile(@Nullable Entity shooter, @NotNull EntityType entityType) {
super(entityType);
this.shooter = shooter;
}
@Deprecated
public EntityProjectile(@Nullable Entity shooter, @NotNull EntityType entityType, @NotNull Position spawnPosition) {
super(entityType, spawnPosition);
this.shooter = shooter;
}
private void setup() {
super.hasPhysics = false;
if (getEntityMeta() instanceof ProjectileMeta) {
((ProjectileMeta) getEntityMeta()).setShooter(this.shooter);
}
setBoundingBox(.5F, .5F, .5F);
}
@Nullable
public Entity getShooter() {
return this.shooter;
}
/**
* Called when this projectile is stuck in blocks.
* Probably you want to do nothing with arrows in such case and to remove other types of projectiles.
*/
public void onStuck() {
}
/**
* Called when this projectile unstucks.
* Probably you want to add some random velocity to arrows in such case.
*/
public void onUnstuck() {
}
public void shoot(Position to, double power, double spread) {
EntityShootEvent event = new EntityShootEvent(this.shooter, this, to, power, spread);
this.shooter.callEvent(EntityShootEvent.class, event);
if (event.isCancelled()) {
remove();
return;
}
Position from = this.shooter.getPosition().clone().add(0D, this.shooter.getEyeHeight(), 0D);
shoot(from, to, event.getPower(), event.getSpread());
}
private void shoot(@NotNull Position from, @NotNull Position to, double power, double spread) {
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;
super.velocity.setX(dx);
super.velocity.setY(dy);
super.velocity.setZ(dz);
super.velocity.multiply(20 * power);
setView(
(float) Math.toDegrees(Math.atan2(dx, dz)),
(float) Math.toDegrees(Math.atan2(dy, Math.sqrt(dx * dx + dz * dz)))
);
}
@Override
public void tick(long time) {
Position posBefore = getPosition().clone();
@ -40,12 +118,14 @@ public class EntityAbstractArrow extends AbstractProjectile {
getVelocity().zero();
sendPacketToViewersAndSelf(getVelocityPacket());
setNoGravity(true);
onStuck();
} else {
if (!super.onGround) {
return;
}
super.onGround = false;
setNoGravity(false);
onUnstuck();
}
}

View File

@ -1,17 +0,0 @@
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.arrow.SpectralArrowMeta;
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);
((SpectralArrowMeta) getEntityMeta()).setShooter(shooter);
}
}

View File

@ -1,25 +1,24 @@
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, double)}.
* Called with {@link net.minestom.server.entity.type.projectile.EntityProjectile#shoot(Position, double, double)}
*/
public class EntityShootEvent extends EntityEvent implements CancellableEvent {
private final Projectile projectile;
private final Entity 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) {
public EntityShootEvent(@NotNull Entity entity, @NotNull Entity projectile, @NotNull Position to, double power, double spread) {
super(entity);
this.projectile = projectile;
this.to = to;
@ -32,7 +31,7 @@ public class EntityShootEvent extends EntityEvent implements CancellableEvent {
*
* @return the projectile.
*/
public Projectile getProjectile() {
public Entity getProjectile() {
return this.projectile;
}

View File

@ -5,12 +5,10 @@ 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.EntityType;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.metadata.arrow.ArrowMeta;
import net.minestom.server.entity.type.Projectile;
import net.minestom.server.entity.type.projectile.EntityArrow;
import net.minestom.server.entity.type.projectile.EntitySpectralArrow;
import net.minestom.server.entity.type.projectile.EntityProjectile;
import java.util.concurrent.ThreadLocalRandom;
@ -42,28 +40,28 @@ public class ShootCommand extends Command {
}
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);
Player player = (Player) sender;
String mode = args.getWord("type");
EntityProjectile projectile;
switch (mode) {
case "default":
projectile = new EntityArrow(player, pos);
projectile = new EntityProjectile(player, EntityType.ARROW);
break;
case "spectral":
projectile = new EntitySpectralArrow(player, pos);
projectile = new EntityProjectile(player, EntityType.SPECTRAL_ARROW);
break;
case "colored":
projectile = new EntityArrow(player, pos);
var meta = (ArrowMeta) ((Entity) projectile).getEntityMeta();
projectile = new EntityProjectile(player, EntityType.ARROW);
var meta = (ArrowMeta) projectile.getEntityMeta();
meta.setColor(ThreadLocalRandom.current().nextInt());
break;
default:
return;
}
((Entity) projectile).setInstance(player.getInstance());
var pos = player.getPosition().clone().add(0D, player.getEyeHeight(), 0D);
projectile.setInstance(player.getInstance(), pos);
var dir = pos.getDirection().multiply(30D);
pos = pos.clone().add(dir.getX(), dir.getY(), dir.getZ());
Projectile.shoot(projectile, player, pos, 1D, 0D);
projectile.shoot(pos, 1D, 0D);
}
}