// Make sure to register this as an item public class CustomArrowEntity extends AbstractArrow { public CustomArrowEntity(EntityType type, Level world) { super(type, world); } public CustomArrowEntity(EntityType type, double x, double y, double z, Level world) { super(type, x, y, z, world); } public CustomArrowEntity(EntityType type, LivingEntity shooter, Level world) { super(type, shooter, world); } @Override protected @NotNull ItemStack getPickupItem() { return new ItemStack(ItemInit.CUSTOM_ARROW.get()); } protected void onHitEntity(@NotNull EntityHitResult ray) { Entity entity = ray.getEntity(); // Do something if the arrow hits an enemy super.onHitEntity(ray); } @Override protected void tickDespawn() { // You could check how many ticks have gone by and despawn the arrow, or do something else. Here it just despawns immediately this.discard(); } @Override public @NotNull Packet getAddEntityPacket() { return NetworkHooks.getEntitySpawningPacket(this); } // Adapted from skeleton entity public static void shoot(LivingEntity shooter, LivingEntity target, Level level) { CustomArrowEntity arrow = new CustomArrowEntity(EntityInit.CUSTOM_ARROW_ENTITY.get(), shooter, level); double vx = target.getX() - shooter.getX(); double vz = target.getZ() - shooter.getZ(); double v = Math.sqrt(vx * vx + vz * vz); double vy = target.getY((double) 2 / 3) - arrow.getY() + v * .2; arrow.shoot(vx, vy, vz, 1.5f, 2); level.addFreshEntity(arrow); } }