-
-
Notifications
You must be signed in to change notification settings - Fork 5
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.
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.
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.
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.
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.
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?
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?