1.17.x Setting fall damage to 0 only after using a custom item

Discussion in 'Spigot Plugin Development' started by Jessica44486, Jul 12, 2021.

  1. I am trying to create a custom item that upon right clicking, makes you leap forward in the direction you are facing. This is what I have so far:


    Code (Java):
    package me.grox.items;

    import me.grox.firstplugin.FirstPlugin;
    import org.bukkit.ChatColor;
    import org.bukkit.Color;
    import org.bukkit.Material;
    import org.bukkit.command.Command;
    import org.bukkit.command.CommandExecutor;
    import org.bukkit.command.CommandSender;
    import org.bukkit.enchantments.Enchantment;
    import org.bukkit.entity.Player;
    import org.bukkit.entity.ThrownPotion;
    import org.bukkit.event.EventHandler;
    import org.bukkit.event.Listener;
    import org.bukkit.event.block.Action;
    import org.bukkit.event.entity.EntityDamageEvent;
    import org.bukkit.event.player.PlayerInteractEvent;
    import org.bukkit.event.player.PlayerMoveEvent;
    import org.bukkit.inventory.ItemFlag;
    import org.bukkit.inventory.ItemStack;
    import org.bukkit.inventory.meta.ItemMeta;
    import org.bukkit.util.Vector;

    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;

    public class LongJump implements CommandExecutor, Listener {

        static FirstPlugin plugin;
        String itemPartialName = "'s Rocket Leap";
        Map<String, Long> cooldowns = new HashMap<>();
        List<String> playersFalling = new ArrayList<>();
        int itemLevel = 13;

        public LongJump(FirstPlugin firstPlugin) {
            plugin = firstPlugin;
        }


        @Override
        public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {

            if (label.equalsIgnoreCase("longjump")) {
                if (!(sender instanceof Player)) {
                    sender.sendMessage(ChatColor.DARK_RED + plugin.getConfig().getString("messages.no-permission"));
                    return true;
                }
                Player player = (Player) sender;
                player.getInventory().addItem(giveItem(player.getName(), player));
                return true;
            }

            return false;
        }

        public ItemStack giveItem(String name, Player player) {

    //        AttributeModifier modifier = new AttributeModifier(UUID.randomUUID(), "generic.spellDamage", itemLevelDouble, AttributeModifier.Operation.ADD_NUMBER);
            ItemStack item = new ItemStack(Material.FIREWORK_ROCKET);
            ItemMeta meta = item.getItemMeta();

    //        meta.addAttributeModifier(Attribute.GENERIC_ATTACK_DAMAGE, modifier);
            meta.setDisplayName(ChatColor.DARK_AQUA + name + itemPartialName);
            meta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
            meta.addEnchant(Enchantment.PROTECTION_FALL, 10, true);

            List<String> lore = new ArrayList<>();
            lore.add("");
            lore.add(ChatColor.GOLD + "" + ChatColor.ITALIC + "Intermediate Item");
            lore.add(ChatColor.RED + "" + ChatColor.ITALIC + "Red Rocket Jump");
            meta.setLore(lore);
            item.setItemMeta(meta);


            return item;

        }


        @EventHandler
        public void onClick(PlayerInteractEvent event) {


            Player player = event.getPlayer();


            // NULL CHECK
            if (event.getItem() == null || event.getItem().getItemMeta() == null || event.getItem().getItemMeta().getDisplayName() == null) {
                return;
            }

            //EFFECT
            if (player.getInventory().getItemInMainHand().getItemMeta().getDisplayName().equals(ChatColor.DARK_AQUA + player.getName() + itemPartialName)
                    && (event.getAction().equals(Action.RIGHT_CLICK_BLOCK) || event.getAction().equals(Action.RIGHT_CLICK_AIR))) {

                event.setCancelled(true);
                //Right Click
                if (event.getAction().equals(Action.RIGHT_CLICK_BLOCK) || event.getAction().equals(Action.RIGHT_CLICK_AIR)) {

                    //COOLDOWN
                    if (cooldowns.containsKey(player.getName())) {
                        //player is inside hashmap


                        if (cooldowns.get(player.getName()) > System.currentTimeMillis()) {

                            // they still have time left in the cooldown
                            long timeLeft = (cooldowns.get(player.getName()) - System.currentTimeMillis()) / 1000;
                            player.sendMessage(ChatColor.GOLD + "On cooldown for " + timeLeft + " more second(s)!");
                            return;
                        }
                    }

                    cooldowns.put(player.getName(), System.currentTimeMillis() + 2 * 1000);

                    System.out.println("It worked");
                    String itemDisplayName = event.getPlayer().getInventory().getItemInMainHand().getItemMeta().getDisplayName();
                    Material material = player.getInventory().getItemInMainHand().getType();
                    player.setCooldown(material, 40);
                    playersFalling.add(player.getName());





                    //Jump direction and velocity
                    Vector playerVector = player.getEyeLocation().getDirection();
                    player.setVelocity(playerVector);
    //                thrownPotion.setItem(giveItem(player.getName(), player));
                    //

                    System.out.println(material);
                    System.out.println(playerVector);
                }


            }
        }

        @EventHandler
        public void onFall(EntityDamageEvent event) {

            if ((event.getCause().equals(EntityDamageEvent.DamageCause.FALL))
                    && playersFalling.contains("GosuGokuSSJ3")) {
                event.setDamage(0.0);
            }

        }

        @EventHandler
        public void onMove(PlayerMoveEvent event) {
            Player player = event.getPlayer();
            for (int x = -1; x <= 1; x++) {
                for (int y = -1; y <= 1; y++) {
                    for (int z = -1; z <= 1; z++) {
                        if (!(event.getPlayer().getLocation().getBlock().getRelative(x, y, z).getType() == Material.AIR)) {
                            System.out.println("it is in here");
                            playersFalling.remove(player.getName());
                        }
                    }
                }
            }
        }




    }
     

    I made a list of strings to store users who have used the item and upon landing removing them from the list, but I am unsure of a way to remove them upon landing.
     
  2. Heya! I would suggest checking if the players in the players list are on the ground. You can do this by either using a repeating Runnable, or use the PlayerMoveEvent. You'll notice that the Player.isOnGround() method is deprecated, but the reason is stupid and you can use it. If you really want to get rid of the deprecation, upcast the player to an entity and use the Entity.isOnGround() method.

    Also, it would be a lot better if you separated your main class into multiple classes, because that makes it way more readable and makes creating larger projects a lot easier if you plan on doing so. Make a class for your command, your PlayerInteractEvent listener, and your checking Runnable or your PlayerMoveEvent listener. The giveItem(String name, Player player) method can either be put into it's own class maybe called ItemHandler or just straight into the command class. Your choice!
     
    #2 CablePlays, Jul 12, 2021
    Last edited: Jul 12, 2021
    • Useful Useful x 1
  3. Thank you, that's great advice! I will clean up my code. I ran into another issue however:

    I tried using PlayerMoveEvent to use the isOnGround method, but it ends up removing the player name before being able to make it to my "onFall" method. Here is the code:


    Code (Text):
        @EventHandler
        public void onLand(PlayerMoveEvent event) {
            Player player = event.getPlayer();
            Entity entity = (Player) player;

            if (entity.isOnGround()){
                System.out.println("onLand");
                playersInAir.remove(entity.getName());
            }
        }
    Code (Text):
    @EventHandler
        public void onFall(EntityDamageEvent event) {
            Entity entity = event.getEntity();
            System.out.println(entity);
            System.out.println(entity.getName());

            if (event.getCause().equals(EntityDamageEvent.DamageCause.FALL) && playersInAir.contains(entity.getName())) {
                System.out.println("onFall");
                    event.setDamage(0.0);
                    playersInAir.remove(entity.getName());

            }

        }
    Also you mentioned that the alternative was using a runnable. Well I'm not sure where to put the runnable because if I put it in my onClick method, it will only run upon rightclicking and I need it to be running constantly right?
     
    • Like Like x 1
  4. Okay, first your solution, hopefully:
    Add a delayed runnable inside of your PlayerMoveEvent where you remove the players from the list. This way, it will wait a bit after the player has landed before removing, allowing time for the EntityDamageEvent to work. Try using a delay time of like 10 ticks, and if it works possibly even remove the delay parameter for 0 ticks and see if that works too.
    If you want to make it more efficient so that you don't fire multiple runnables, add a new list containing players that are being removed, and check if the player is NOT in that list before starting a new runnable. Then, once the runnable fires, remove the player from the in air list and the players being removed list.

    The runnable I'm talking about is a repeating runnable, so it runs repeatedly the entire time your plugin instance if running. You put it in its own class that implements Runnable, and add the run method with your code for checking which is fired from the main class. But I do think that the way you've done it is better.

    One final thing, upcasting is casting a Player to an Entity. But the way you've done it you don't need any casting, so you can remove the Player cast to entity because it's not really doing anything and is quite strange. XD

    Anyway I'm doing online learning now and it's boring so ask for help and I'll be here. :)
     
    • Useful Useful x 1
  5. You don't need any runnable to do this. Simply just listen to EntityDamageEvent and see the damage cause. If player is wearing the armor and the damage cause is FALL - then cancel the event.
     
    #5 aglerr, Jul 13, 2021
    Last edited: Jul 13, 2021
  6. But that would prevent all fall damage. All we want to do is prevent the player from taking damage after being launched into the air from a special item.
     
  7. How about adding the players that use an item to a list for 10s (ie) then checking for damage cause on PlayerDamageEvent?
     
  8. Well he got a list of player that used the skill already, all you need to do is listen to EntityDamageEvent, no need for runnable.

    If the player is in the list and the damage cause is FALL, cancel the event and remove the player from the list.
     
    • Agree Agree x 2
  9. That's smarter.
     
  10. Thank you this was super helpful it worked! I appreciate the help, you're super good at this!

    I have another question regarding my custom rocket item:

    I recently found out about this neat method where it displays a cooldown in the hotbar when you use an item similar to an ender pearl. It's called setCooldown, but you need to pass a material and an integer. This is a problem because my custom items share materials with other items. For example, this rocket item uses the material firework and it causes other fireworks to go on cooldown even though they shouldn't. Is there a way around this like making my own custom material?
     
    • Friendly Friendly x 1
  11. Code (Text):
        @EventHandler
        public void onFall(EntityDamageEvent event) {

            if ((event.getCause().equals(EntityDamageEvent.DamageCause.FALL))
                    && playersFalling.contains("GosuGokuSSJ3")) {
                event.setDamage(0.0);
                playersFalling.remove(GosuGokuSSJ3);
            }

        }
    Never worry checking in an ArrayList or a Map if an object is present just if you want to remove it. You can safely remove the object and in case that it was not inside, such ArrayList or Map will be unchanged.

    I recommend you storing OfflinePlayer#getUniqueId instead of saving player names since players can change their names but their UUID doesn’t.
    It won’t be a problem for this case since in the worst scenario in this case would be a player leaping and disconnecting and then renaming and rejoining and getting fall damage. Just take it in mind for a next time.

    Remove the whole onMove method since you don’t seem to need it anymore, it will be fired each time they move (let’s say that you got 50 players walking and jumping, it will run your code every time PlayerMoveEvent is fired).

    Have fun!
     
    #11 ProMoRRom, Jul 13, 2021
    Last edited: Jul 13, 2021
    • Useful Useful x 1
  12. No since it applies to Material. I had the same problem with netherite hoes and a weapon but left and right click worked without issues so it was only visual. What I did was creating a HashMap and of UUID and Long and do the cooldown by myself.
    Hope it helps.
     
  13. And you were able to get the visual in the hotbar? I have a cooldown system already that works, I just need the ender pearl cooldown visual because it looks nice.
     
  14. The white animation that you see on your inventory is applied to every item of the same Material…
    Shame since I agree with you, it looks really nice.
     
  15. But then if I don't take damage, then I will still be in the list and regular fall damage wouldn't apply to me anymore.
     
  16. In my logic once you fall you reach ground.
    Then the cycle restarts and if you use the item it will add you again to the list to which will remove you if you get damaged.
    You should also add a cooldown anyways in case they don’t get fall damage (let’s say that the fall distance was less than 3) then the cooldown would at the end run:
    playersFalling.remove(e.getPlayer().getName());

    If you still want to know when they exactly fall, remember that Hypixel in their Skyblock gamemode when they made the Grappling Hook didn’t mind wanting to know exactly when the player landed each time (even if the player didn’t recieve fall damage). They only removed the player from the list once the cooldown reached 0!

    Hope it helps!
     
    #16 ProMoRRom, Jul 13, 2021
    Last edited: Jul 13, 2021
  17. If you're still on the list, you will got removed from the list if you take fall damage. I can't think of the best solution since I don't really understand what you exactly want yet.
     
  18. Perhaps you could modify how that method is called by using 'lore' or something else from your custom Rocket Item?
    Similar to your
    Code (Text):
     //EFFECT
            if (player.getInventory().getItemInMainHand().getItemMeta().getDisplayName().equals(ChatColor.DARK_AQUA + player.getName() + itemPartialName)
     
    • Agree Agree x 1
  19. Yeah, because if you don't use the item name, you can change it without breaking your code. But don't use the lore, use the item's PersistentDataContainer because you can put whatever you like in there and the only visual thing that changes is the number of tags the item has. :)
     
    • Like Like x 1