-
-
Notifications
You must be signed in to change notification settings - Fork 5
Overrideable Methods
NOTE: This topic assumes a basic understanding of the Java programming language. Basic concepts will not be reviewed.
Every arrow should be unique. They should all perform different tasks and make players feel as though they're experiencing something new when they shoot an arrow. Using our Cake Arrow class from Creating a Custom Alchemical Arrow, the methods capable of being overridden will be covered and explained in as much detail as possible to get you writing your own arrows in no time.
The AlchemicalArrow
class comes with ten different methods that may be overridden, all of which are called by the AlchemicalArrows plugin so you don't have to worry about calling them yourself. All you have to worry about is filling in the method bodies to tell the plugin to do whatever you want it to when an arrow meets certain criteria. Let's cover them, all with examples.
You will notice a common theme with these methods; the inclusion of an AlchemicalArrowEntity parameter. Think of this class as a more powerful version of Bukkit's Arrow interface. You will not use this type to its fullest extent until alchemical arrow entities are covered more in-depth in the Custom Alchemical Arrow Entities wiki page. Until then, all you want to worry about is the AlchemicalArrowEntity#getArrow()
method which gets Bukkit's Arrow.
An update method called every single tick for all arrows of the type represented by your AlchemicalArrow class. What this means is that AlchemicalArrowEntity#getImplementation()
will return an instance of the class overriding the method.
More often than not, this method will be used to display particle effects around the arrow as it moves in the air. Otherwise, it may be used to perform passive effects such that the arrow is still in the world. As an example, let's add some particles to our cake arrow
private static final BlockData CAKE = Material.CAKE.createBlockData(); // 1.13 BlockData for Cake
@Override
public void tick(AlchemicalArrowEntity arrow, Location location) {
location.getWorld().spawnParticle(Particle.BLOCK_CRACK, location, 1, 0.0, 0.0, 0.0, 0.0, CAKE);
}
As implied by the name, this method is called when the arrow has hit a block. This method may be used to affect the terrain nearby or perform actions when the arrow lands. The block parameter passed to this method is the block on which this arrow landed (i.e. Arrow#getAttachedBlock())
Let's use this method to place some cake blocks wherever this cake arrow lands! The world can always use a bit more cake.
@Override
public void onHitBlock(AlchemicalArrowEntity entity, Block block) {
Block toCake = block.getRelative(BlockFace.UP);
if (toCake.getType() != Material.AIR) return; // Let's only replace air
toCake.setType(Material.CAKE);
}
As implied by the name, this method is called when the arrow hits a player in the world. This method may be used to inflict additional damage upon the player or perform any sort of player-related action. The player parameter passed to this method is the player damaged by the arrow.
Let's use this method to satiate the damaged player. We just hit them with an arrow, the least we can do is satiate them.
@Override
public void onHitPlayer(AlchemicalArrowEntity arrow, Player player) {
player.setFoodLevel(20);
player.setSaturation(20.0F);
}
Similar to #onHitPlayer()
, this method is called when the arrow hits an entity in the world. It should be noted that this method will not be called when hitting a player. While the player is an entity, player-specific actions should be handled in the #onHitPlayer()
method. This method may be used to perform entity-related actions on the entity involved. The entity parameter passed to this method is the entity damaged by the arrow.
Let's use this method to turn all hostile entities into cakes! The world could use a lot less hostility and a lot more cake.
@Override
public void onHitEntity(AlchemicalArrowEntity arrow, Entity entity) {
if (!(entity instanceof Monster)) return; // Only hurt monsters!
Location entityLocation = entity.getLocation();
entityLocation.getBlock().setType(Material.CAKE);
entity.getWorld().spawnParticle(Particle.EXPLOSION_NORMAL, entityLocation, 5); // Poof!
}
This method is called when a player has shot an arrow. Typically this method is used to check for permissions or perform actions to the arrow that was shot before it is launched. The player passed to this method is the shooter of the arrow. The boolean result of this method determines whether the shot will succeed (true
) or fail and cancel the shot entirely (false
).
Let's use this method to restrict who can use our cake arrow. We don't want just anyone to use it, so let's give it a permission node.
@Override
public boolean onShootFromPlayer(AlchemicalArrowEntity arrow, Player player) {
return player.hasPermission("myplugin.arrow.cakearrow");
}
Similar to onShootFromPlayer()
, this method is called when a skeleton has shot an arrow. This method serves no purpose in any of the default alchemical arrows, though it may be used to provide skeleton-specific properties to launched arrows. The boolean result of this method determines whether the shot will succeed (true
) or fail and cancel the shot entirely (false
).
We don't really need to do anything to our cake arrow when a skeleton shoots it, therefore a basic example will be provided.
@Override
public boolean onShootFromSkeleton(AlchemicalArrowEntity arrow, Skeleton skeleton) {
skeleton.setFireTicks(60); // Ignite any skeletons that shoot our arrow
}
Similar to onShootFromPlayer()
, this method is called when a block projectile source has shot an arrow. This is primarily directed to dispensers. This method serves no purpose in any of the default alchemical arrows, though it may be used to provide dispenser-specific properties to launched arrows. The boolean result of this method determines whether the shot will succeed (true
) or fail and cancel the shot entirely (false
).
Let's use this method to prohibit dispensers from shooting our arrows. Only entities can experience the deliciousness of cake anyways, so what difference does it really make?
@Override
public boolean onShootFromBlockSource(AlchemicalArrowEntity arrow, BlockProjectileSource source) {
return false;
}
void hitEntityEventHandler(AlchemicalArrowEntity, EntityDamageByEntityEvent), void hitGroundEventHandler(AlchemicalArrowEntity, ProjectileHitEvent) & void shootEventHandler(AlchemicalArrowEntity, ProjectileLaunchEvent)
These three methods are grouped together because they all serve similar purposes, more direct handling of AlchemicalArrow-related events. These methods are no different than their "on"-prefixed counterparts other than the fact that they are called immediately before them and the fact that they pass the event object directly. Generally there is no reason to override these methods, but if absolutely necessary to access the event directly, they are available for use. For this reason, no example will be provided for these methods.
Now that we've gone through and covered all the available methods, we can say that we've finished our cake arrow! If you were to take this into the game, all the effects should work as intended. Give yourself an arrow with the /givearrow <player> myplugin:cake
command and shoot some arrows! Our final class looks like this:
public class AlchemicalArrowCake extends AlchemicalArrow {
private static final BlockData CAKE = Material.CAKE.createBlockData();
private final NamespacedKey key;
private final ItemStack item;
public AlchemicalArrowCake(AlchemicalArrows plugin) {
this.key = new NamespacedKey(plugin, "cake");
this.item = new ItemStack(Material.ARROW);
ItemMeta meta = item.getItemMeta();
meta.setDisplayName(ChatColor.LIGHT_PURPLE + "Cake Arrow");
this.item.setItemMeta(meta);
this.properties.setProperty(ArrowProperty.ALLOW_INFINITY, true);
this.properties.setProperty(ArrowProperty.SKELETON_LOOT_WEIGHT, 100.0);
}
@Override
public NamespacedKey getKey() {
return key;
}
@Override
public String getDisplayName() {
return ChatColor.LIGHT_PURPLE + "Cake Arrow";
}
@Override
public ItemStack getItem() {
return item.clone();
}
@Override
public void tick(AlchemicalArrowEntity arrow, Location location) {
location.getWorld().spawnParticle(Particle.BLOCK_CRACK, location, 1, 0.0, 0.0, 0.0, 0.0, CAKE);
}
@Override
public void onHitBlock(AlchemicalArrowEntity arrow, Block block) {
Block toCake = block.getRelative(BlockFace.NORTH);
if (toCake.getType() != Material.AIR) return;
toCake.setType(Material.CAKE);
}
@Override
public void onHitPlayer(AlchemicalArrowEntity arrow, Player player) {
player.setFoodLevel(20);
player.setSaturation(20.0F);
}
@Override
public void onHitEntity(AlchemicalArrowEntity arrow, Entity entity) {
if (!(entity instanceof Monster)) return;
Location entityLocation = entity.getLocation();
entityLocation.getBlock().setType(Material.CAKE);
entity.getWorld().spawnParticle(Particle.EXPLOSION_NORMAL, entityLocation, 5);
}
@Override
public boolean onShootFromPlayer(AlchemicalArrowEntity arrow, Player player) {
return player.hasPermission("myplugin.arrow.cakearrow");
}
@Override
public boolean onShootFromBlockSource(AlchemicalArrowEntity arrow, BlockProjectileSource source) {
return false;
}
}