Tutorial Creating Fake Projectiles and their uses

Discussion in 'Resources' started by mine2012craft, Dec 29, 2016.

Thread Status:
Not open for further replies.
  1. Offline

    mine2012craft

    Hello,

    So we are going to create some "projectiles" today. I'm not talking arrows or snowballs, I am talking about particle projectiles, such as fireballs.

    These projectiles will not be instantaneous, but instead they will travel slowly to their destination.

    I will also teach you about how you can use these in three different ways: Shooting it in a straight line, make it a homing projectile, and make it orbit around you.

    Normal Fireball-Type Projectile

    But first, we must actually create the projectile. Start by making your usual class that implements a Listener. I'm going to name mine "MagicalProjectile"

    Code:
    import org.bukkit.event.Listener;
    
    public class MagicalProjectile implements Listener{
    
    }
    Now, lets create a event for this item. We will use the PlayerAnimationEvent, which will detect if the player swung his arm or not. If the player did, we will then create a player variabe, and check if the player has a blaze rod in his hand, but after we have a null checker

    Now, your code should look similar to this:
    Code:
    @EventHandler
        public void onUse(PlayerAnimationEvent event){
            if (event.getAnimationType() == PlayerAnimationType.ARM_SWING){
                Player player = event.getPlayer();
                if (player.getInventory().getItemInMainHand() != null){
                    if (player.getInventory().getItemInMainHand().getType() == Material.BLAZE_ROD){
                       
                    }
                       
                }
            }
           
        }
    Now let us create the projectile. We will be using a BukkitRunnable in this case to create the projectile.
    Make sure to implement all of your unimplemented classes

    Code:
                        new BukkitRunnable(){
    
                            @Override
                            public void run() {
                                // TODO Auto-generated method stub
                               
                            }
                           
                        }.runTaskTimer(plugin, delay, period);
    Notice how there is a .runTaskTimer(variables) at the bottom. We will fill that in with variables that suit us, starting with the plugin variable.

    For this, you need to create your constructor that links your class to your main class.

    Code:
        public MagicalProjectile (Core core){
           
        }
    Now, create a variable that is a instance of your main class above the constructor, and put it in your constructor, defining it as the core.

    Code:
        privateCore plugin;
       
        public MagicalProjectile (Core core){
            plugin = core;
        }
    Now input that first variable into the plugin variable in the .runTaskTimer

    Next, the delay. This is how long we want it to delay itself before it actually starts running (Meaning if we set the delay to 60 ticks, it would wait 60 ticks until it starts to run).

    Lets set this to 0, since we want this to run immediately. Now the period is the frequency the runnable is running at. An example is that if we set it to 1 tick, it will run every tick. Let us set it to 1 tick, as we dont want our projectile to seem glitchy.

    Code:
    }.runTaskTimer(plugin, 0, 1);
    Now lets start coding in our runnable. First, we want to define how long this projectile will last. We will make it last 3 seconds before it exausts itself, so create a double variable above the run method named "time"
    Code:
        new BukkitRunnable(){
                 double time = 0;
                            @Override
                            public void run() {
    Now, in your run method, we will increment that variable by 1, and then have a check checking if the time variable equals 60. If so, cancel the runnable.

    Code:
    time += 1;
                               
                    if (time == 60){
                     this.cancel();
                                }
    Now to create the projectile itself. Lets start by getting the players Location. Put it above the run method, like we did with the "time" variable. Make sure to make the Player variable above a final variable.

    We will also get the players direction. This will be the direction the player will cast the projectile.

    Code:
    //In above code:
    final Player player = event.getPlayer();
    
    //Later Code below   
    
    Location loc = player.getLocation();
                            Vector direction = loc.getDirection().normalize();
                            @Override
                            public void run() {
    Now, inside our run, lets get the loc variable, and add the direction variable to it, plus 1.5 to its y. this will make it eye level. We will also subtract the y after adding the y, as doing so will temporarily make the y at eye level, and not keep adding. If we didn't add the subtracting y, the projectile would go straight up, and we don't want that.

    Code:
                                loc.add(direction);
                                loc.add(0, 1.5, 0);
                               
                               
                               
                                loc.subtract(0, 1.5, 0);
    Now, time for visuals. We will use a particle to show off the projectile. Please keep in mind that I am using a Particle Library that can be found and downloaded here: https://bukkit.org/threads/1-8-particleeffect-v1-7.154406/

    Now, this is important: Make sure to place the particle between the loc.add and loc.subtract. If you put it outside, the projectile wouldn't be eye level.

    Code:
                                loc.add(direction);
                                loc.add(0, 1.5, 0);
                               
                                ParticleEffect.CRIT.display(0, 0, 0, 0.1F, 3, loc, 40);
                               
                                loc.subtract(0, 1.5, 0);
    Now if you want it to deal damage, that is simple.

    inside the two loc.add and subtracts, create a for loop that loops through all the entities in the world (or chunk)

    Code:
                                for (Entity e : loc.getWorld().getEntities()){
                                   
                                }
    now, check if the entity is 1 block away from the location, then check if it is not our player, and check if it is alive. If so, cast it to a damageable and damage it.

    Code:
    for (Entity e : loc.getWorld().getEntities()){
                                    if (e.getLocation().distance(loc) < 1){
                                        if (e != player){
                                            if (e.getType().isAlive()){
                                                Damageable d = (Damageable) e;
                                                d.damage(5);
                                            }
                                        }
                                    }
                                }
    In the end, your current code should look like this:

    Code:
    public class MagicalProjectile implements Listener{
        Core plugin;
       
        public MagicalProjectile (Core core){
            plugin = core;
        }
       
        @EventHandler
        public void onUse(PlayerAnimationEvent event){
            if (event.getAnimationType() == PlayerAnimationType.ARM_SWING){
                final Player player = event.getPlayer();
                if (player.getInventory().getItemInMainHand() != null){
                    if (player.getInventory().getItemInMainHand().getType() == Material.BLAZE_ROD){
                       
                        new BukkitRunnable(){
                            double time = 0;
                            Location loc = player.getLocation();
                            Vector direction = loc.getDirection().normalize();
                            @Override
                            public void run() {
                                time += 1;
                               
                                loc.add(direction);
                                loc.add(0, 1.5, 0);
                               
                                ParticleEffect.CRIT.display(0, 0, 0, 0.1F, 3, loc, 40);
                                for (Entity e : loc.getWorld().getEntities()){
                                    if (e.getLocation().distance(loc) < 1){
                                        if (e != player){
                                            if (e.getType().isAlive()){
                                                Damageable d = (Damageable) e;
                                                d.damage(5, player);
                                                 this.cancel();
                                            }
                                        }
                                    }
                                }
                                loc.subtract(0, 1.5, 0);
                               
                                if (time == 60){
                                    this.cancel();
                                }
                               
                            }
                           
                        }.runTaskTimer(plugin, 0, 1);
                       
                    }
                       
                }
            }
           
        }
    That is your projectile! It will deal damage, and acts like a projectile (except it has no gravity, haven't really practiced adding gravity yet)

    But what if we want to make it home in on nearby entities? Well that is a little extra work.

    Heat-Seeking/Homing Particles

    Create a for loop that once again loops through all entities in a world, and check if the distance between the location and them is less than 5. Then, check to make sure its not the player, and if it is alive.

    Code:
        for (Entity e : loc.getWorld().getEntities()){
                                    if (e.getLocation().distance(loc) < 5){
                                        if (e != player){
                                            if (e.getType().isAlive()){
                                                 this.cancel();
    
                                            }
                                        }
                                    }
                                }
    Now, if the Entity has met all of thse conditions, lets make it home in on the targeted entity. Now if we want to make it home on the entity forever, we need to create another projectile, while also destroying the previous one. Now I'm pretty sure there's much better ways of doing this, but this is the way I found out how to do this.

    Create the same BukkitRunnable for our first projectile inside the for loop, except erase all variables except for the time variable.

    Code:
    new BukkitRunnable(){
                                                    double time = 0;
    
                                                    @Override
                                                    public void run() {
                                                        time += 1;
                                                       
                                                   
                                                       
                                                        if (time == 60){
                                                            this.cancel();
                                                        }
                                                       
                                                    }
                                                   
                                                }.runTaskTimer(plugin, 0, 1);
    Now to make this target the entity permanently, we must rapidly grab the entities Location, then, convert it into a vector. Make sure to also convert the Entity variable into a final. Then, get the player's loc variable, and make it a vector too.

    Code:
         Location eloc = e.getLocation();
                     Vector source = loc.toVector();
                     Vector target = eloc.toVector();
    Now, to get the direction our projectile must head in, we must subtract the target variable from our source variable, and then normalize it.

    Code:
                                                         Vector direction = target.subtract(source);
                                                         direction.normalize();
    Now, add this new direction variable to our Player's loc variable, as that was what originally we used to create the projectile, plus and subtract 1.5 to our y. Then, add our particle effect!

    Code:
        loc.add(direction);
                                                        loc.add(0,1.5,0);
                                                       
                                                        ParticleEffect.CRIT.display(0, 0, 0, 0.1F, 3, loc, 40);
                                                       
                                                        loc.subtract(0, 1.5, 0);
    Now, to deal damage. Use the same damage loop as used previously. Make sure to cancel the runnable on damage. Oh and, you might need to increase the distance for this one just in case.

    Code:
    for (Entity e : loc.getWorld().getEntities()){
                                    if (e.getLocation().distance(loc) < 2){
                                        if (e != player){
                                            if (e.getType().isAlive()){
                                                Damageable d = (Damageable) e;
                                                d.damage(5, player);
                                                this.cancel();
                                            }
                                        }
                                    }
                                }
    Now your projectile should properly deal damge and home in on targets.

    Orbiting Particles

    Finally, lets make it orbit us and also deal damage to nearby targets!

    First, lets start with a fresh, clean slate for our Runnable.

    Code:
    new BukkitRunnable(){
                            double time = 0;
                            Location loc = player.getLocation();
                            @Override
                            public void run() {
                                time += 1;
                               
                               
                                if (time == 60){
                                    this.cancel();
                                }
                               
                            }
                           
                        }.runTaskTimer(plugin, 0, 1);
    Now to make it orbit us will take some math. Start by creating a degrees variable that is a double above the run method.

    Code:
                            double degrees = 0;
    Now, we will increment this degrees variable by 10 in our run method. Also, if we want this to make a full rotation, we might have to increase the time until exaustion, so do that as well.

    Code:
        @Override
                            public void run() {
                                time += 1;
                                degrees += 10;
                               
                               
                                if (time == 180){
                                    this.cancel();
                                }
                               
                            }
    Now, we are going to make a double variable that converts these degrees into radians.

    Code:
                                double radians = Math.toRadians(degrees);
    Finally, lets add it to our location. We will be using cos for the x, and sin for the z. We will also add 1 to the y to make it visible.

    We will also subtract it so it dosent fly off.

    Code:
                                loc.add(Math.cos(radians), 1, Math.sin(radians));
                               
                                loc.subtract(Math.cos(radians), 1, Math.sin(radians));
    NOTE: Don't think I'm some mathmatician. I did need some help to create this.

    Now, add the particles and the for loop to deal damage whilst also canceling it!

    Code:
        loc.add(Math.cos(radians), 1, Math.sin(radians));
                                 ParticleEffect.CRIT_MAGIC.display(0, 0F, 0, 0.1F, 5, loc, 40);
                                    for (Entity e : loc.getChunk().getEntities()){
                                        if (e.getLocation().distance(loc) < 1.25){
                                   if (!e.equals(player)){
                                   if(e.getType().isAlive()) {
                                       Damageable d = (Damageable) e;
                                   d.damage(8, player); 
                                       }
                                   }
                               }                                   
                           }
                                loc.subtract(Math.cos(radians), 1, Math.sin(radians));                    
    Now you should see a circle revolving around you!

    If you wish for the circle to follow you, move the player Location variable inside the run method. If you wish to increase the circles radius, multiply the x and z value by your desired radius. The defaulted is 1.

    Code:
                                loc.add(Math.cos(radians)*2, 1, Math.sin(radians)*2);
                               
                                loc.subtract(Math.cos(radians)*2, 1, Math.sin(radians)*2);


    And that is it! Keep in mind that I had to do some researching on this, and have a little bit of help, so this wasn't all on me.

    Have a nice day! Please notify me if you notice and bugs in my code.
     
    ChipDev likes this.
  2. Offline

    ChipDev

  3. there is a way to stop the projectile when collides with a block?
    BTW i love this tutorial, is very well explained, and i love to play with vectors
     
  4. Offline

    mine2012craft

    The way I do it is in the runnable, when you update the location, get the block the location is at/in. Check if it's a solid block, and if so, cancel.
     
Thread Status:
Not open for further replies.

Share This Page