• Hello Guest! Did you know that ProjectKorra has an official Discord server? A lot of discussion about the official server, development process, and community discussion happens over there. Feel free to join now by clicking the link below.

    Join the Discord Server

TUTORIAL: How to make an advanced move

jedk1

New Member
DO NOT FOLLOW THIS TUTORIAL! IT IS VERY OUTDATED!
FOLLOW THE VIDEO IN MY SIGNATURE!

Ok so most of you have probably seen this thread:
The amazing tutorial made by @Coolade for how to make a basic ability.

Now, that tutorial is good, but it doesn't expand on much other than making you set people on fire. Nothing fancy really. Now lets say you want something that's fancy. You want something that tells you how to use Particles, how to do other Damage to the enemy other than setting them alight, how to set Permissions to default and how to do Cool downs.

What I am going to try and teach you in this tutorial is how to make a move that includes:
Particles
Damaging a target
Setting the Permissions to be default
Setting a Cool down

Before we start, please make sure you have read the following threads:
How to design an Ability - Concept Design - Creating a basic Ability

Ok now lets get started.
Currently in this tutorial:
Part 1 - The Main File & Path.yml
Part 2 - The Permission Class
Part 3 - The Listener. Oh boy...
Part 4 - Particle Types
Part 5 - Adding Config Options

PART 1 - The Main File & Path.yml
(Yes this is just taken from Coolade's tutorial, I'm just adding it here for ease of access)

In this tutorial I will be making a move called 'Discharge' which was suggested by @Gahshunk

Link to his suggestion: http://projectkorra.com/threads/fire-discharge.503/
I'm going to assume you have your build paths set up already and your project structure set up already. Firstly you want to create a file called 'path.yml' in your src folder. Inside your 'path.yml' file you need to have:
main-class: <your package name>.korra.abilities.<your move name>Information
Once you have that, save that file and close it because we won't be needing it again. Now in your package which I'm assuming is called
<your package name>.korra.abilties
you need to create 3 class files.
<your move name>Information.class
<your move name>Listener.class
<your move name>Permissions.class
Now you have your files made, open up <your move name>Information.class. Then you want to put the following inside it (This part is mostly taken from Coolade's tutorial):
(Please do not just copy and paste the code as you won't learn if you do)
Pastebin - DischargeInformation.java
Now, there are 3 lines that are different in this class, 2 in the onThisLoad() method:
Code:
ProjectKorra.plugin.getServer().getPluginManager().addPermission(new DischargePermissions().dischargeDefault);
ProjectKorra.plugin.getServer().getPluginManager().getPermission("bending.ability.Discharge").setDefault(PermissionDefault.TRUE);
and 1 in the stop() method:
Code:
ProjectKorra.plugin.getServer().getPluginManager().removePermission(new DischargePermissions().dischargeDefault);
Update: There is also an additional method in this class that isn't shown in Coolade's tutorial and that is:
Code:
    /*
     * Decide if this ability can be used in a protected area or not, much like deciding if its Blaze or Tremorsense.
     */
    public boolean isHarmlessAbility(){
        return false;
    }
These methods register and un-register the permission node:
bending.ability.Discharge
The permission will be loaded and set to default every time the move is loaded, and then removed on unloading. This makes it easier for server owners as they won't have to set-up perms.

PART 2 - The Permissions Class

Ok this is gonna be nice and simple. Open up your <your move name>Permissions.class and what you want to enter is this:
Code:
package <your package name>.korra.abilities;

import org.bukkit.permissions.Permission;

public class DischargePermissions{
    //Register our default permission variable
    public Permission dischargeDefault;

    public DischargePermissions(){
        super();
        //Register our permission
        dischargeDefault = new Permission("bending.ability.Discharge");
    }
    //...what? That's it, seriously... for this file anyway
}
This class is simple, we are simply defining the permission node, and yes you could easily add more permissions here, by simply adding another:
Code:
public Permission anotherPermission;
and:
anotherPermission = new Permission("bending.ability.AnotherPermission");
 
Last edited:

jedk1

New Member
PART 3 - The Listener. Oh boy...
Ok so this next bit is going to become very confusing. So we have our basic listener class, it looks a little something like this:
This is good for starting a basic move but is missing a couple of checks. Firstly, we should add a BendingPlayer variable, so under where you have 'final Player player = event.getPlayer();', you want to add:
Code:
final BendingPlayer bPlayer = Methods.getBendingPlayer(player.getName());
This will return the BendingPlayer of the Player who left clicked.
Next we need to add a check to see if the player is on a cool down. For this we add this line of code:
Code:
if(bPlayer.isOnCooldown("Discharge");

    return;
We check to see if the player is on a cool down for our move 'Discharge', if so, return so the move won't execute.

Oh and we should also probably define some variables at the start of our listener, just in case we decide to add a config at a later date, it will make adjusting the move much easier:
Code:
public class DischargeListener implements Listener{

    //Set up some variables to make it easier later.
    public double attackDamage = 2.0; //Damage is equal to 2.0 =  1 heart (1.0 = half a heart (Yes I am making a one direction reference here too)).
    public double knockbackSpeed = 3.0; //The higher this number the further the target will get knocked back.
    public int particleCount = 5; //How many particles per tick should appear of the "bolt" line.
    public int range = 15; //How many blocks can this reach?.
    public int cooldownTime = 5; //How long must the player wait before using it again? (in seconds).

    //Define what we will class as "passable blocks". These blocks are usually what the player could walk through. This will come in handy later.
    Material[] passableBlocks = {Material.AIR, Material.DOUBLE_PLANT, Material.VINE, Material.LONG_GRASS, Material.DEAD_BUSH, Material.RED_ROSE,
            Material.YELLOW_FLOWER, Material.RED_MUSHROOM, Material.BROWN_MUSHROOM, Material.TORCH, Material.WEB, Material.SAPLING,
            Material.SNOW, Material.CARPET};
We can now get onto making our actual move. In @Gahshunk's suggestion for Discharge, the Left Click ability says it would release a lash of electricity at the players foe, shocking them and knocking them back a bit. So for this we will be using Particles, Damaging Entities and Vectors. (Don't expect a miracle here, I'm terrible at vectors).

Right lets start by getting our 'Line'. This line will be what we spawn the particles on, and will also be how we damage our target. This goes after where we have completed our checks, if you are unsure, check the full listener class (link at the bottom).
Code:
        //Get players eye location.

        Location playerEyeLocation = player.getEyeLocation();

        //Calculate the yaw and pitch.
        double yaw  = Math.toRadians(playerEyeLocation.getYaw() + 90);
        double pitch = Math.toRadians(playerEyeLocation.getPitch() + 90);

        //Some fancy math that is confusing.
        double x = Math.sin(pitch) * Math.cos(yaw);
        double y = Math.sin(pitch) * Math.sin(yaw);
        double z = Math.cos(pitch);

        //Get a new instance of the player's location.
        final Location loc = playerEyeLocation.clone();
Here we get a bunch of locations, and we will be using this to create our line.
Next we must loop through each location and spawn a particle there, so you now want to add:
Code:
for(int i = 1; i <= range; i++){
    loc.add(i*x, i*z, i*y);
    loc.subtract(i*x, i*z, i*y);
}
//Dont ask me why it goes x z y, It's confusing.
What we are doing here is, for every time it loops, add 1 to the value of 'i'. Then using the location of the player, add the distance away from the player in the direction they are looking (Don't worry if you are confused, it confuses me too).

Next we are going to check if the block at each location is what we defined as 'passable' (a.k.a The player could normally pass through it, such as air, vines, tall grass, etc) and if it is, then we create particles and will run a check to see if there is a target entity nearby. So inside that for loop add:
Code:
if(Arrays.asList(passableBlocks).contains(loc.getBlock().getType())){

}else{
    break;
}
Which will give us so far:
Now we need to check for nearby entities so we can damage them, for that, we will use this method:
Code:
//Check if there's an entity nearby.
for(final Entity target : Methods.getEntitiesAroundPoint(loc, 2.0)){
//Check that the entity is living and it isn't the player.
    if(target instanceof LivingEntity && (target.getEntityId() != player.getEntityId())){
    }
}
Next up we will now add to inside the check to see if the block was passable. This is hard to explain, so please read all the comments that are added to this bit of code as they explain it alot:
Code:
//Spawn magical particle.
ParticleEffect.MAGIC_CRIT.display(loc, 0F, 0F, 0F, 0.2F, particleCount);

//Check if there's an entity nearby.
for(final Entity target : Methods.getEntitiesAroundPoint(loc, 2.0)){

    //Check that the entity is living and it isn't the player.
    if(target instanceof LivingEntity && (target.getEntityId() != player.getEntityId())){

        //Create a vector for which we will knock back the target.
        Vector knockbackVector = target.getLocation().toVector().subtract(loc.toVector()).normalize();
        //Finally set the targets velocity (speed/motion) to the knockback vector, thus pushing the target back.
        target.setVelocity(knockbackVector.multiply(knockbackSpeed));
       
        //Loop for 10 ticks (0.5 seconds) spawning magical particles.
        new BukkitRunnable(){
            //A counter so we can cancel the runnable later.
            int count;
            @Override
            public void run(){

                //Add one to the counter each time.
                count++;

                //If count is equal to 10, cancel the runnable.
                if(count == 10)
                    cancel();

                //Spawn magical particles.
                ParticleEffect.MAGIC_CRIT.display(target.getLocation(), (float) Math.random(), (float) Math.random(), (float) Math.random(), 0.3F, 50);
            }
        //Run the BukkitRunnable with a start delay of 0L, and then to repeat every 1L (1 tick) until it is cancelled, which we do above with the cancel();.
        }.runTaskTimer(ProjectKorra.plugin, 0L, 1L);

        /*
         * Damage the entity by the variable we set at the start.
         * Again, here we use a count to be able to break out of the runnable.
         * Here I didn't have to use a runnable, but to make the move seem like it was electrical,
         * we will damage the target twice, with a 0.5 second gap in between.
         */
        new BukkitRunnable(){
            int count;
            @Override
            public void run(){
                count++;
                if(count == 2)
                    cancel();
                //Damage the target by the amount we defined in our attackDamage variable we assigned at the start.
                Methods.damageEntity(player, target, attackDamage);       
            }
        }.runTaskTimer(ProjectKorra.plugin, 0L, 10L);
    }

    //Because an entity was found, we don't want the move to travel further, so we break out the for loop.
    break;
}
And thats most of the Listener class done! Now we just need to add one tiny last bit. Before the final curly bracket on our onLeftClick method, we want to add:
Code:
bPlayer.addCooldown("Discharge", cooldownTime * 1000L);
This will give the move a cool down period after left click. 1000L = 1 second, and as we are multiplying by one of our variables we defined at the start, we should get a 5 second cool down.

And I do believe that's it! I will provide all the links to the completed files below, I have commented a lot on them so please read the comments as you will learn from them! I hope you enjoyed this sort of tutorial thing, it took me a while to put it together!

Links to the completed Files:
What the final result should be:
 
Last edited:

jedk1

New Member
Part 4 - Particle Types
Ok so I would have added this but the post can't be any longer than 10,000 characters so I'm adding this here.
To spawn particles I used this method:
Code:
ParticleEffect.MAGIC_CRIT.display(target.getLocation(), (float) Math.random(), (float) Math.random(), (float) Math.random(), 0.3F, 50);
However, say you want to spawn Fire Bending particles or Air Bending particles, you can use these methods instead:
Code:
Methods.playAirbendingParticles(loc, amount);
Methods.playFirebendingParticles(loc);
Block Particles
Or say you wanted to spawn block breaking particles, you use the following method:
Code:
ParticleEffect.displayBlockCrack(location, id, data, offsetX, offsetY, offsetZ, amount);
//id = Block ID
//data = Metadata of block
Heres an example that will spawn 50 Light Blue Wool particles:
Code:
ParticleEffect.displayBlockCrack(target.getLocation(), 35, (byte) 3, (float) Math.random(), (float) Math.random(), (float) Math.random(), 50);
Item Particles
How about creating Item Particles, like those you get when you eat?:
Code:
ParticleEffect.displayIconCrack(location, id, offsetX, offsetY, offsetZ, speed, amount);
//id = Item ID
Heres an example that spawns 50 Emerald particles:
Code:
ParticleEffect.displayIconCrack(target.getLocation(), 388, (float) Math.random(), (float) Math.random(), (float) Math.random(), 0.3F, 50);
Part 5 - Adding Config Options
This is something I'm going to need to teach you, as most moves usually come with a config to allow server owners maximum control over the moves. And I'm going to say this now. I will be changing some of the methods I previously showed you how to use, so they function correctly with configs.

Ok, In our DischargeInformation.java file, we need to modify and add a couple of things. At the start, beneath where you have:
Code:
public class DischargeInformation extends AbilityModule {
You should add:
Code:
private Plugin pk;
This will get us a private instance of the Plugin class which we can use later in the file.

Next up, in our onThisLoad() method, we need to add this line which will load a method we are about to make:
Code:
loadConfig();
This must be placed before we register the listener!
Our onThisLoad() method should now look similar to:
Code:
public void onThisLoad() {
    ProjectKorra.plugin.getLogger().info("Discharge developed by jedk1 has been loaded!");
    loadConfig();
    ProjectKorra.plugin.getServer().getPluginManager().registerEvents(new DischargeListener(), ProjectKorra.plugin);
    ProjectKorra.plugin.getServer().getPluginManager().addPermission(new DischargePermissions().dischargeDefault);
    ProjectKorra.plugin.getServer().getPluginManager().getPermission("bending.ability.Discharge").setDefault(PermissionDefault.TRUE);
}
Now we need to make the loadConfig() method. Beneath the onThisLoad() method, create a new method:
Code:
public void loadConfig(){

}
Inside you want to add:
Code:
PluginManager pm = Bukkit.getPluginManager();
pk = pm.getPlugin("ProjectKorra");
FileConfiguration config = pk.getConfig();
This is where that 'private Plugin pk' comes into play. This grabs us the config for ProjectKorra. Next we need to add something that adds our config variables to the config:
Code:
config.addDefault("ExtraAbilities.[Your Name].Discharge.damage", Double.valueOf(2.0));
config.addDefault("ExtraAbilities.[Your Name].Discharge.knockback", Double.valueOf(3.0));
config.addDefault("ExtraAbilities.[Your Name].Discharge.range", Integer.valueOf(15));
config.addDefault("ExtraAbilities.[Your Name].Discharge.particles", Integer.valueOf(5));
config.addDefault("ExtraAbilities.[Your Name].Discharge.cooldown", Integer.valueOf(5000));
And then finally:
Code:
pk.saveConfig();
And thats all we need to add to our DischargeInformation class. Now we go to our DischargeListener class.
First thing you are going to want to change is our variables we setup at the start. Modify them so they look like this:
Code:
public double attackDamage = ProjectKorra.plugin.getConfig().getDouble("ExtraAbilities.[Your Name].Discharge.damage");
public double knockbackSpeed = ProjectKorra.plugin.getConfig().getDouble("ExtraAbilities.[Your Name].Discharge.knockback");
public int particleCount = ProjectKorra.plugin.getConfig().getInt("ExtraAbilities.[Your Name].Discharge.particles");
public int range = ProjectKorra.plugin.getConfig().getInt("ExtraAbilities.[Your Name].Discharge.range");
public int cooldownTime = ProjectKorra.plugin.getConfig().getInt("ExtraAbilities.[Your Name].Discharge.cooldown");
And then one last line we need to edit is where we set the cooldown. In Part 3 or wherever I said that we should do some math to make it seconds, we are removing that and leaving it as a raw value. So go change it to:
Code:
bPlayer.addCooldown("Discharge", cooldownTime);
And that's it! We have successfully added Discharge to the ProjectKorra config! It should appear at the end like:
ExtraAbilities:
[Your Name]:
Discharge:
damage: 2.0
knockback: 3.0
range: 15
particles: 5
cooldown: 5000
Links to the completed Files:
 
Last edited:

jedk1

New Member
Done! Now all you have to do is delete the ones you sent to me xD
way ahead of ya c: - next ill do the tutorial on how i make blocks fly. But that will be tomorrow maybe, because parts 1, 2 and 3 took up 2 posts nearly maxing out the 10000 character count xD
 

jedk1

New Member
Updated. Changed a lot in the listener class, Remember I said that there was probably a method for getting the nearby entities easier and a Dev would correct me on it? Well I was right, @runefist showed me the correct way of doing it and now I'm showing you guys! So please! Re-read the listener class, examine the changes I have made!
Changes start around Line:96-97 in the final file!
 

Joeri

Verified Member
Could you post on how to use the uhm, I have no idea whats it's name is. Its the waterbending block also used for earth. just block manipulation (Real blocks instead of fallingblock or particles)
 

jedk1

New Member
Could you post on how to use the uhm, I have no idea whats it's name is. Its the waterbending block thingy
how to make blocks travel? once ive talked with some people about how im going to approach teaching it, yes.
 
Top