Rewriting projectiles

This commit is contained in:
Konstantin Shandurenko 2021-03-01 12:59:16 +03:00
parent 419ebe7553
commit de15c4b6c5
18 changed files with 156 additions and 172 deletions

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