Skip to content

Alchemical Arrow Entities

Parker Hawke edited this page Jun 10, 2018 · 3 revisions

Alchemical Arrow Entities

NOTE: This topic assumes a basic understanding of the Java programming language. Basic concepts will not be reviewed

In very specific situations, your arrow may need to hold additional data whilst in the world. If you've read the Creating a Custom Alchemical Arrow wiki page, you will know that AlchemicalArrow implementations are nothing more than data classes that provide a template of sorts for new arrows. As such, it cannot store arrow-specific data. With the addition of the AlchemicalArrowEntity class, data may be held in an object which is mapped directly to the in-world arrow.

default AlchemicalArrowEntity createNewArrow(Arrow) & The Water Arrow

In the last wiki page, 10 methods were listed as overrideable in the AlchemicalArrow class. While those methods may be useful for the majority of developers, there is still one final method that was not yet covered, the default createNewArrow(Arrow) method. The default implementation found in AlchemicalArrow is as follows:

public default AlchemicalArrowEntity createNewArrow(Arrow arrow) {
    return new AlchemicalArrowEntity(this, arrow);
}

For most arrows, this implementation will suffice. However, taking for example the Water Arrow in AlchemicalArrows 3, a custom AlchemicalArrowEntity had to be created in order to store information about the arrow's initial and projected velocity. Its class looks a little something like this:

public final class ArrowEntityWater extends AlchemicalArrowEntity {

    private final Vector velocity;

    public ArrowEntityWater(AlchemicalArrow type, Arrow arrow, Vector initialVelocity) {
        super(type, arrow); // Parent constructor
        this.velocity = initialVelocity;
    }

    public Vector getVelocity(double multiplier) {
        Vector toReturn = initialVelocity.clone();
        this.initialVelocity.multiply(multiplier);
        return toReturn;
    }

    public Vector getVelocity() {
        return velocity.clone();
    }

}

Let's break down this class a little bit.

public final class ArrowEntityWater extends AlchemicalArrowEntity

The class declaration is crucial. As you can see, this class extends AlchemicalArrowEntity, the base wrapper class for an in-world alchemical arrow. For every in-world arrow launched of type alchemicalarrows:water, a new instance of this class will be created.

super(type, arrow);

The AlchemicalArrowEntity class requires a constructor for AlchemicalArrow and Arrow. This is so the class knows exactly what type of arrow it is and what arrow to wrap in world. It is recommended that you do the same in your constructor, otherwise AlchemicalArrows will not work with your custom arrow type. You may add any additional parameters to your constructor as you are ultimately the one constructing an instance of it, though the aforementioned parameters must be present.

After the super constructor has been invoked, your class will have access to these fields properly titled implementation and arrow respectively. They are protected and finalized fields which your entity may interact with. Do note that the the implementation field will be of type AlchemicalArrow, however, if you only expect to have this object constructor for water arrows, you may safely cast it as such. (i.e. (WaterArrow) implementation), though it is not likely that you will require explicit casting as the majority of the methods in AlchemicalArrows are provided for you.

Additional Methods and Fields

Of course, the velocity field and getVelocity(double) & getVelocity() methods in the class. These methods are the data-oriented features for the water arrow that are required for each individual arrow. These are used internally by the AlchemicalArrowWater class to manipulate the velocity of the arrow over time. For any additional data you would like to store, treat this object as if it were an Arrow class. In fact, it is a wrapper for the Arrow class, therefore any data you populate this class with will effectively be tied to the in-world arrow.

Overriding #createNewArrow(Arrow)

When an alchemical arrow is shot from a bow, the plugin needs to know what type of arrow to create. In order to do so, it references the arrow registry, finds the appropriate AlchemicalArrow implementation by comparing the results of their #getItem() method and invokes the #createNewArrow() method on the resulting implementation. As seen above, by default, a regular AlchemicalArrowEntity will be returned. In order for AlchemicalArrows to recognize your custom AlchemicalArrowEntity implementation, you must override the #createNewArrow() method and return your custom entity. If you look at AlchemicalArrowWater's implementation, you will see that's exactly what it does.

@Override
public AlchemicalArrowEntity createNewArrow(Arrow arrow) {
    return new ArrowEntityWater(this, arrow, arrow.getVelocity());
}

Now when the method is called internally, it will return an instance of your custom class masked as an AlchemicalArrowEntity type. That's all! Your new entity will be recognized by the plugin. But how do you use that data you stored?

The AlchemicalArrowEntity Parameters Explained

If you've read the Overrideable Methods wiki page or written your own AlchemicalArrow implementation, you will know that most methods pass an instance of AlchemicalArrowEntity. Surprise! These parameters are far more useful than just AlchemicalArrowEntity#getArrow()! This parameter will now be an instance of whatever type you returned in the #createNewArrow(Arrow) method. You may cast it safely to your custom arrow implementation and access all its data. Take for example the Water arrow again.

public void tick(AlchemicalArrowEntity arrow, Location location) {
    if (!(arrow instanceof AlchemicalEntityWater)) return; // Just being SUPER safe, but it should be alright

    Arrow bukkitArrow = arrow.getArrow();
    location.getWorld().spawnParticle(Particle.WATER_WAKE, location, 3, 0.1, 0.1, 0.1, 0.01);

    // The use of the AlchemicalArrowEntity, ArrowEntityWater
    if (bukkit.getLocation().getBlock().getType() == Material.WATER) {
        bukkitArrow.setVelocity(((ArrowEntityWater) arrow).getVelocity(0.9995)); // Accessing #getVelocity()
    }
}

As you can see, the arrow parameter was casted to ArrowEntityWater and its #getVelocity(double) method was called. That parameter isn't as useless as you thought it was, now is it?