[Tutorial] How to make particle trails - (For wands/spells)

Discussion in 'Resources' started by Windy Day, Jan 3, 2014.

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

    Windy Day

    Hello, as of lately, I seem to have been seeing a lot of people wondering how to create particle trails, mostly for the purpose of wands or spells. So here I am making a resource so that when people ask this question they can be linked to here.

    To get started we will need a blockiterator. This is what will actually create the particle trail.
    Code:
        //block iterator with while loop   
                      Location location = player.getEyeLocation(); // get the player's eye location so we can get the blocks in their line of sight
                      BlockIterator blocksToAdd = new BlockIterator(location, Y-Offset Double, Distance); //intialize the blockiterator and name it blocksToAdd
                      Location blockToAdd; //intialize a location called blockToAdd
                          while(blocksToAdd.hasNext()) {
                              blockToAdd = blocksToAdd.next().getLocation();  //set blockToAdd to the next block's location from the iterator
                              if (blockToAdd.getblock.getType() != Material.AIR || !shotprojectiledata.containsKey(projectlie)) { //check that if the trail hits a wall or has hit a player the effect will stop by exiting the while loop
                              break;
                              }
                              player.getWorld().playEffect(blockToAdd, Effect.EFFECT, DATA); //play an effect (optional)
                              //anything else you want to do
                          }
                    }
    Y - Offset : This is a double value that is the vertical offset from Location location. Normally you will want to keep this 0D
    Distance : This is the distance that the blockiterator will "reach" or extend

    If we ran this code we would now have a nice particle trail in the direction the player is looking. However, it wont damage anything, well that comes next.


    There are many ways you can go about making the trail damage entities. In this tutorial I will be shooting a snowball from the player, making it invisible, then setting up a custom damage event. You can approach this however you would like though.
    Code:
    Snowball projectile = player.launchProjectile(Snowball.class);
    This will shoot the snowball from the player, we will call the snow ball projectile so we can hide it later. First though, we will store some data about the snowball.

    I made this class so that we can have an object to store the data we will need about the snowball in a more compact way. I will explain why and where we need firedfrom, range, and damage when we use it.
    Code:
        public class EntityData {
            private Location firedfrom;
            private Integer range;
            private Double damage;
     
            //constructor
            public EntityData(Location loc, Integer range, Double damage) {
                this.firedfrom = loc;
                this.range = range;
                this.damage = damage;
            }
            public Location getFiredFrom() {
                return firedfrom;
            }
            public Integer getRange() {
                return range;
            }
            public Double getDamage() {
                return damage;
            }
        }
    Next we must store the data somehow. This can be done with a hashmap, that way we can set the key to the projectile and the value to the projectile's data.
    Code:
        public final static WeakHashMap<Entity, EntityData> shotprojectiledata = new WeakHashMap<Entity, EntityData>();
    
    Thanks stirnate for the tip about using a weakhashmap

    Ok, now that we have everything we need to store the data, lets go back and actually use this.
    Code:
    Snowball projectile = player.launchProjectile(Snowball.class);
    EntityData data = new EntityData(projectile.getLocation(), range, damage);// create a new EntityData object
    shotprojectiledata.put(projectile, data); //store the projectile and data
    
    **NOTE** set the range to the length (distance) or the blockiterator. This way if the particle trail is 15 blocks long an entity won't be damaged that is 100 blocks away from the end of the trail.


    To put this store data to use, we are going to make a EntityDamageByEntityEvent to apply the custom damage to any snowball hitting a player or other entity.Make sure you register your events.
    Code:
    @EventHandler
        public void onHit(EntityDamageByEntityEvent event) {
            if (event.getDamager() instanceof Snowball) { //check if the damager is a snowball
                if (shotprojectiledata.containsKey(event.getDamager())) { //check if the snowball is one that is supposed to be doing a different damage
                    EntityData eventdata = shotprojectiledata.get(event.getDamager()); //get the data we stored about the projectile
                    if (event.getEntity().getLocation().distance(eventdata.getFiredFrom())<=eventdata.getRange()) { //check to see if the event is taking place outside of the range
                        event.setDamage(eventdata.getDamage()); //set the custom damage to the value stored earlier
                        shotprojectiledata.remove(event.getDamager()); //we can remove the projectile now so that the hashmap won't have unnecessary data in it.
                    }
                }
            }
        }

    Finally, we need will need to make the snowball appear invisible so that players don't see a snowball flying though the particle trail. To do this we will send the client packets that the snowball isn't there.
    Code:
                        for (Player p : Bukkit.getOnlinePlayers()) {
                            ((CraftPlayer)p).getHandle().playerConnection.sendPacket(new PacketPlayOutEntityDestroy(((CraftSnowball) projectile).getHandle().getId()));
                        }
    This will make the snowball to all the players online.


    All together without the event you should have something like this:
    Code:
                        Location location = player.getEyeLocation();
                          BlockIterator blocksToAdd = new BlockIterator(location, 0D, 15);
                          Location blockToAdd;
                          Snowball projectile = player.launchProjectile(Snowball.class);
                          EntityData data = new EntityData(projectile.getLocation(), 15, 5D);
                          shotprojectiledata.put(projectile, data);
                          while(blocksToAdd.hasNext()) {
                              blockToAdd = blocksToAdd.next().getLocation();
                              if (blockToAdd.getblock.getType() != Material.AIR || !shotprojectiledata.containsKey(projectlie)) {
                              break;
                              }
                              player.getWorld().playEffect(blockToAdd, Effect.STEP_SOUND, Material.FIRE);
                          }
                        for (Player p : Bukkit.getOnlinePlayers()) {
                            ((CraftPlayer)p).getHandle().playerConnection.sendPacket(new PacketPlayOutEntityDestroy(((CraftSnowball) projectile).getHandle().getId()));
                        }
    This is my first resource so I would appreciate any feed back you have, good or bad. Hope this is of usefulness.
     
  2. Offline

    bobacadodl

    Nice tutorial! Another way to go about the EntityData is to use Metadata instead of a hashmap to store it
     
  3. Offline

    Josh014

    Hey man, it was ok that you was away from my problem I had it busy that time too. So yea, thi really helps me I am gonna go get some sleep it is 2 am here. Hehe. But i just wante to say thank you for this and sending me a note about this post :). See you later on the forums!

    - Josh
     
  4. Offline

    Windy Day

    Thanks, good to know, I will probably try to use that in the future.
    No problem, I'm glad to be at least trying to help you. See you around.
     
  5. Offline

    Josh014

    Windy Day

    Btw I think you missed something in your thread because this goes through the players and walls. ;)
     
  6. Offline

    krists23

    Can you please post all code together in pastebin?
    Btw tnx for tutorial. :)
     
  7. Offline

    Windy Day

    To stop it when it hits a wall or player you should be able to just add this to the while loop:
    Code:
                                  if (blockToAdd.getBlock().getType() != Material.AIR ||!shotprojectiledata.containsKey(projectile)) {
                                      break;
                                  }
    Here is a pastebin: http://pastebin.com/zkrdCd4w
     
  8. Offline

    stirante

    Windy Day Holding Entity in HashMap isn't very good. If entity dies it's still in memory and can't be deleted by garbage collector. I think WeakHashMap would be better choice. In WeakHashMap entry is deleted when key is deleted by garbage collector (nice thing to know what strong and weak references are).
    EDIT: When entity dies HashMap would hold it which would create memory leaks. WeakHashMap in this case will delete entity data and entity. Forgot to mention that.
     
    biel likes this.
  9. Offline

    Windy Day

    Good tip! Thanks I defiantly see why that would be better in this situation.
    Side note to everyone: I updated the thread to include stopping when it hits a wall or player also.
     
  10. Offline

    Captain Dory

    Hi,

    I'm using this code for a plugin I'm making, but I'm having some issues:
    http://pastebin.com/5uiSPU5Y
    What can you see wrong with this code?

    Thanks~
    Dory
     
  11. Offline

    Windy Day

    Sure. The problem appears to be that you have everything inside of the playerinteractevent. Sorry for when I said put where ever you want I meant put in whatever class you want I should have been more clear.

    Here it is re-arranged:
    http://pastebin.com/V1cJ9i2E
    That for the most part should be correct but I arranged it in pastebin so there may be a mistake.
     
  12. Offline

    Captain Dory

    Thanks! I'll try this and edit if it worked.
    Edit: Some stuff is undefined, not resolved to a variable, etc. I'm only a beginner programmer so i probably shouldn't be doing this anyway xD
    I'll come back to it in a bit :3
     
  13. Offline

    Windy Day

    Ok, if you want I can enter it in an IDE tomorrow, fix it up, and PM you it. Plus answer any questions so you can understand and learn. Good luck too, programming is fun but it's not always easy.
     
  14. Offline

    Captain Dory

    That would be great, thanks :)
     
  15. Offline

    sipsi133

    Does this work with 1.7.2 and 1.7.4?
     
  16. Offline

    Windy Day

    Yes. However, because it uses CraftPlayer and CraftSnowball requires craftbukkit and is version dependent because of that. Updating is easy though, all you have to do is add the new craftbukkit jar to your library then re import the whatever is from craftbukkit.
    So this:
    Code:
    import org.bukkit.craftbukkit.v1_6_R3.entity.CraftPlayer;
    Becomes:
    Code:
    import org.bukkit.craftbukkit.v1_7_R2.entity.CraftPlayer;
     
  17. Offline

    mbaxter ʇıʞʞnq ɐ sɐɥ ı

    Removed posts discussing unofficial builds.
     
    ChipDev likes this.
  18. Offline

    Snipey

    When this plays through it does not create a particle trail all it does is shoot a snowball :/
     
  19. Offline

    Windy Day

    It worked when I tested it. If you want you can PM me the code that you have and I could take a look at it.
     
  20. Offline

    jusjus112

    Windy Day
    you have:
    Code:java
    1. Location location = player.getEyeLocation();
    2. BlockIterator blocksToAdd = new BlockIterator(location, 0D, 15);
    3. Location blockToAdd;
    4. while(blocksToAdd.hasNext()) {
    5. if (blockToAdd.getBlock().getType() != Material.AIR ||!shotprojectiledata.containsKey(projectile)) { //MY error
    6. break;
    7. }
    8. blockToAdd = blocksToAdd.next().getLocation();
    9. player.getWorld().playEffect(blockToAdd, Effect.STEP_SOUND, Material.FIRE);
    10. }
    11. Snowball projectile = player.launchProjectile(Snowball.class); //idk where?
    12. EntityData data = new EntityData(projectile.getLocation(), 15, 5D);
    13. shotprojectiledata.put(projectile, data);
    14. for (Player p : Bukkit.getOnlinePlayers()) {
    15. ((CraftPlayer)p).getHandle().playerConnection.sendPacket(new PacketPlayOutEntityDestroy(((CraftSnowball) projectile).getHandle().getId()));
    16. }
    17. }
    18. }
    19. }

    but on line 5 after !shotprojectiledata.containsKey is projectile an error! it says "projectile cannot be resolved to a variable" where do i put the
    Code:java
    1. Snowball projectile = player.launchProjectile(Snowball.class);
     
  21. Offline

    Windy Day

    Ok, I see the problem now. The objects must be initialized before we can use them. So just move
    Code:
    Snowball projectile = player.launchProjectile(Snowball.class);
    EntityData data = new EntityData(projectile.getLocation(), 15, 5D);
    shotprojectiledata.put(projectile, data);
    to above the while statement.
     
  22. Offline

    jusjus112

    Windy Day
    it works. but then it gives me an error on "blocktoadd.getType"
    Code:java
    1. if (blockToAdd.getBlock().getType()
    2. // my error in blockToAdd.

    it says "The local variable blockToAdd may not have been initialized"
     
  23. Offline

    Windy Day

    You still have this above the while statement correct?
    1. Location blockToAdd;
     
  24. Offline

    GrimmjowHD

    That does nothing. What is blockToAdd? You need to initialize it somewhere. Right now it isn't anything.
    You should do:
    Code:java
    1. Snowball projectile = player.launchProjectile(Snowball.class);
    2. Location blockToAdd = projectile.getLocation();
     
  25. Offline

    Windy Day

    It just initializes the variable. Then in the while statement we set it to the location in the iterator if there is a next. You could set it to the projectile's location but blockToAdd is for storing the location currently being iterated over.
     
  26. i want to remove the snowball after the trail is enden. where do i have to put e.getDamager().remove?

    *ended

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 13, 2016
  27. Offline

    Windy Day

    When the snowball hits someone bukkit should remove the snowball. However, it would go in the EntityDamageByEntityEvent.
     
  28. I'm sorry i was so unclear. I meant, if the trail of particles is stopped, the snowball has to be removed from the world. So at the part were we set the damage of the snowball to normal again, i want to remove the snowball. Is there a way to do this?

    so stupid... i removed the snowball in the entitydamageevent, what means that the entity is already hit.... i had to put it into the playerinteractevent. now it works all fine

    one more question. is there a way to damage an entity without throwing a snowball? because a snowball is fired at the playerEyeLocation what causes some issues if you stand behind a block and shoot to beneath. SORRY FOR BAD ENGLISH

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 13, 2016
  29. Offline

    Windy Day

    Yes, you could find another way but I used snowballs because it is by far the easiest way I have found. If you want to do it another way you are going to have to do some digging around.
     
  30. okay. i will post if i have another way to damage the player
     
Thread Status:
Not open for further replies.

Share This Page