1.15.2 Automatic Crossbow Reloading System going little haywire

Discussion in 'Spigot Plugin Development' started by TheShermanTanker, Jan 21, 2020.

  1. For a minigame I had in mind, I made a system to automatically reload a crossbow if they were holding a crossbow in their hand without the player needing to right click to reload the crossbow. However it doesn't seem to be working properly, if the player is playing normally the crossbow will fail to load properly, but if the player has entered the pause menu (Press the escape key while in game) then the crossbow automatically loads as it should...

    (Note: In the video below the only times I am pressing right click is to fire the crossbow. When the crossbow is reloading I am not clicking anything)


    If you notice, the Crossbow is randomly spazzing out every once in a while. That's it trying to load but failing for some reason

    Source code:
    CrossbowRunnable.java
    Code (Java):
    package me.theshermantanker.plugin;

    import org.bukkit.ChatColor;
    import org.bukkit.Material;
    import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer;
    import org.bukkit.entity.Player;
    import org.bukkit.inventory.PlayerInventory;
    import org.bukkit.scheduler.BukkitRunnable;

    import net.minecraft.server.v1_15_R1.EnumHand;

    public class CrossbowRunnable extends BukkitRunnable {
       
        int ticks = 25;
        Player player;
        GameTick updater;
        private boolean active = true;
       
        public CrossbowRunnable(Player player, GameTick updater) {
            this.player = player;
            this.runTaskTimer(PluginCore.plugin, 0L, 0L);
        }
       
        public boolean isActive() {
            return active;
        }
       
        @Override
        public void run() {
            System.out.println(ticks);
            PlayerInventory inventory = player.getInventory();
            if(this.active == false) return;
            if(this.ticks <= 0) {
                ((CraftPlayer) this.player).getHandle().clearActiveItem();
                this.active = false;
                this.cancel();
            }
            if(this.ticks == 25) {
               
                if(inventory.getItemInMainHand().getType() == Material.CROSSBOW && inventory.getItemInMainHand().getItemMeta().hasDisplayName() && inventory.getItemInMainHand().getItemMeta().getDisplayName().equals(ChatColor.GREEN + "" + ChatColor.BOLD + "Marksman's Crossbow")) {
                    ((CraftPlayer) this.player).getHandle().playerInteractManager.a(((CraftPlayer) this.player).getHandle(), ((CraftPlayer) this.player).getHandle().getWorldServer(), ((CraftPlayer) this.player).getHandle().getItemInMainHand(), EnumHand.MAIN_HAND);
                } else if(inventory.getItemInOffHand().getType() == Material.CROSSBOW && inventory.getItemInOffHand().getItemMeta().hasDisplayName() && inventory.getItemInOffHand().getItemMeta().getDisplayName().equals(ChatColor.GREEN + "" + ChatColor.BOLD + "Marksman's Crossbow")) {
                    ((CraftPlayer) this.player).getHandle().playerInteractManager.a(((CraftPlayer) this.player).getHandle(), ((CraftPlayer) this.player).getHandle().getWorldServer(), ((CraftPlayer) this.player).getHandle().getItemInOffHand(), EnumHand.OFF_HAND);
                } else {
                    this.active = false;
                    this.cancel();
                }
            }
           
            if(inventory.getItemInMainHand().getType() == Material.CROSSBOW && inventory.getItemInMainHand().getItemMeta().hasDisplayName() && inventory.getItemInMainHand().getItemMeta().getDisplayName().equals(ChatColor.GREEN + "" + ChatColor.BOLD + "Marksman's Crossbow")) {
               
            } else if(inventory.getItemInOffHand().getType() == Material.CROSSBOW && inventory.getItemInOffHand().getItemMeta().hasDisplayName() && inventory.getItemInOffHand().getItemMeta().getDisplayName().equals(ChatColor.GREEN + "" + ChatColor.BOLD + "Marksman's Crossbow")) {
               
            } else {
                this.active = false;
                this.cancel();
            }
           
            this.ticks--;
        }

    }
    GameTick.java
    Code (Java):
    package me.theshermantanker.plugin;

    import java.util.HashMap;
    import java.util.Map;

    import org.bukkit.ChatColor;
    import org.bukkit.FluidCollisionMode;
    import org.bukkit.Location;
    import org.bukkit.Material;
    import org.bukkit.Sound;
    import org.bukkit.World;
    import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer;
    import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftItemStack;
    import org.bukkit.entity.Player;
    import org.bukkit.inventory.ItemStack;
    import org.bukkit.scheduler.BukkitRunnable;
    import org.bukkit.util.RayTraceResult;
    import org.bukkit.util.Vector;

    import net.minecraft.server.v1_15_R1.NBTTagCompound;
    import net.minecraft.server.v1_15_R1.PlayerConnection;

    public class GameTick extends BukkitRunnable {
       
        GeneralEventHandler handler;
        Map<Player, CrossbowRunnable> map = new HashMap<Player, CrossbowRunnable>();
        Map<Player, Integer> warning = new HashMap<Player, Integer>();
       
        public GameTick(GeneralEventHandler handler) {
            this.handler = handler;
        }
       
        @Override
        public void run() {
           
            if(!handler.marksmen.isEmpty()) {
               
                if(!warning.isEmpty()) {
                    for(Player player : warning.keySet()) {
                        warning.put(player, warning.get(player) - 1);
                    }
                }
               
                if(!map.isEmpty()) {
                    System.out.println(map);
                    for(Player player : map.keySet()) {
                        if(!map.get(player).isActive()) {
                            map.remove(player);
                        }
                    }
                }
               
                for(Player player : handler.marksmen.keySet()) {
                    ItemStack item = player.getInventory().getItemInMainHand();
                    if(!(item.getType() == Material.CROSSBOW) && item.getItemMeta().hasDisplayName() && item.getItemMeta().getDisplayName().equals(ChatColor.GREEN + "" + ChatColor.BOLD + "Marksman's Crossbow")) {
                        item = player.getInventory().getItemInOffHand();
                    }
                    if(item.getType() == Material.CROSSBOW && item.getItemMeta().hasDisplayName() && item.getItemMeta().getDisplayName().equals(ChatColor.GREEN + "" + ChatColor.BOLD + "Marksman's Crossbow")) {
                        net.minecraft.server.v1_15_R1.ItemStack stack = CraftItemStack.asNMSCopy(item);
                        NBTTagCompound tag = stack.getTag();
                        if(!(tag == null)) {
                            System.out.println(tag.getBoolean("Charged"));
                            if(!tag.getBoolean("Charged")) {
                                if(!map.containsKey(player)) {
                                    CrossbowRunnable runnable = new CrossbowRunnable(player, this);
                                    map.put(player, runnable);
                                }
                            }
                        }
                    }
                    if(handler.marksmen.get(player)) {
                        World world = player.getWorld();
                        PlayerConnection connection = ((CraftPlayer) player).getHandle().playerConnection;
                        Location freecam = new Location(player.getWorld(), connection.savedFreecamPosition.x, connection.savedFreecamPosition.y + player.getEyeHeight(), connection.savedFreecamPosition.z, connection.freecamYaw, connection.freecamPitch);
                        RayTraceResult result = world.rayTrace(freecam, freecam.getDirection(), 100.0D, FluidCollisionMode.ALWAYS, true, 0.5D, null);
                        Vector target = result.getHitPosition();
                        Location location = player.getLocation();
                        double x = target.getX();
                        double y = target.getY();
                        double z = target.getZ();
                        double distX = x - location.getX();
                        double distZ = z - location.getZ();
                        double distance = Math.sqrt(distX * distX + distZ * distZ);
                        double offset = y - player.getEyeLocation().getY();
                        float gravity = 20;
                       
                        double range = 80 * 80 * 80 * 80;
                        range /= 20;
                        range -= 2 * offset * 80 * 80;
                        range /= 20;
                        range = Math.sqrt(range);
                       
                        if(distance <= range) {
                            ((CraftPlayer) player).getHandle().yaw = (float) Math.toDegrees(Math.atan2(z - location.getZ(), x - location.getX())) - 90.0F;
                            ((CraftPlayer) player).getHandle().pitch = (float) -Math.toDegrees(Math.atan((80 * 80 + Math.sqrt(80 * 80 * 80 * 80 - (gravity * (gravity * distance * distance + 2 * offset * 80 * 80)))) / (gravity * distance)));
                        } else {
                            if(!warning.containsKey(player) || warning.get(player) <= 0) {
                                player.sendMessage(ChatColor.DARK_RED + "You have exceeded the range of the Crossbow by " + Math.round(distance - range) + " blocks. Targeting functions will no longer work");
                                player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 10, 1);
                                warning.put(player, 200);
                            }
                        }
                       
                    }
                }
            }
        }
       
        /* Removed due to precision of x, y and z values being too low to work properly
        private final Location getFreecamTargetLocation(Player player, int range) {
            PlayerConnection connection = ((CraftPlayer) player).getHandle().playerConnection;
            Location freecam = new Location(player.getWorld(), connection.savedFreecamPosition.x, connection.savedFreecamPosition.y, connection.savedFreecamPosition.z, connection.freecamYaw, connection.freecamPitch);
            BlockIterator iterator = new BlockIterator(freecam, player.getEyeHeight(), range);
            Block lastBlock = iterator.next();
            while (iterator.hasNext()) {
                lastBlock = iterator.next();
                if (lastBlock.getType() == Material.AIR) {
                    continue;
                }
                break;
            }
            return lastBlock.getLocation();
        }
        */

       
    }
    GeneralEventHandler.java
    Code (Java):
    package me.theshermantanker.plugin;

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

    import org.bukkit.Bukkit;
    import org.bukkit.ChatColor;
    import org.bukkit.Material;
    import org.bukkit.attribute.Attribute;
    import org.bukkit.attribute.AttributeModifier;
    import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer;
    import org.bukkit.enchantments.Enchantment;
    import org.bukkit.entity.Arrow;
    import org.bukkit.entity.Player;
    import org.bukkit.entity.ZombieVillager;
    import org.bukkit.event.EventHandler;
    import org.bukkit.event.Listener;
    import org.bukkit.event.block.Action;
    import org.bukkit.event.entity.CreatureSpawnEvent;
    import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
    import org.bukkit.event.entity.EntityShootBowEvent;
    import org.bukkit.event.player.PlayerInteractEvent;
    import org.bukkit.event.player.PlayerJoinEvent;
    import org.bukkit.event.player.PlayerQuitEvent;
    import org.bukkit.inventory.EquipmentSlot;
    import org.bukkit.inventory.ItemStack;
    import org.bukkit.inventory.meta.ItemMeta;
    import org.bukkit.util.Vector;

    import net.minecraft.server.v1_15_R1.PacketPlayOutAbilities;

    public class GeneralEventHandler implements Listener {
       
        List<SpawnReason> list = new ArrayList<SpawnReason>();
        PluginCore plugin;
        Map<Player, Boolean> marksmen = new HashMap<Player, Boolean>();
       
       
        public GeneralEventHandler(PluginCore plugin) {
            this.plugin = plugin;
            list.add(SpawnReason.DEFAULT);
            list.add(SpawnReason.DISPENSE_EGG);
            list.add(SpawnReason.NATURAL);
            list.add(SpawnReason.REINFORCEMENTS);
            list.add(SpawnReason.SPAWNER);
            list.add(SpawnReason.SPAWNER_EGG);
            list.add(SpawnReason.TRAP);
            list.add(SpawnReason.VILLAGE_INVASION);
        }
       
        @EventHandler
        public void onJoin(PlayerJoinEvent event) {
            Player player = event.getPlayer();
            if(player.isOp()) {
                marksmen.put(player, false);
                ItemStack crossbow = new ItemStack(Material.CROSSBOW, 1);
                ItemMeta meta = crossbow.getItemMeta();
                meta.setUnbreakable(true);
                meta.setDisplayName(ChatColor.GREEN + "" + ChatColor.BOLD + "Marksman's Crossbow");
                AttributeModifier modifier = new AttributeModifier(UUID.randomUUID(), "AttackSpeedModifier", 200, AttributeModifier.Operation.ADD_NUMBER, EquipmentSlot.HAND);
                AttributeModifier melee = new AttributeModifier(UUID.randomUUID(), "DamageModifier", 2, AttributeModifier.Operation.ADD_NUMBER, EquipmentSlot.HAND);
                AttributeModifier offhandmodifier = new AttributeModifier(UUID.randomUUID(), "AttackSpeedModifier", 200, AttributeModifier.Operation.ADD_NUMBER, EquipmentSlot.OFF_HAND);
                AttributeModifier offhandmelee = new AttributeModifier(UUID.randomUUID(), "DamageModifier", 2, AttributeModifier.Operation.ADD_NUMBER, EquipmentSlot.OFF_HAND);
                meta.addAttributeModifier(Attribute.GENERIC_ATTACK_SPEED, modifier);
                meta.addAttributeModifier(Attribute.GENERIC_ATTACK_DAMAGE, melee);
                meta.addAttributeModifier(Attribute.GENERIC_ATTACK_SPEED, offhandmodifier);
                meta.addAttributeModifier(Attribute.GENERIC_ATTACK_DAMAGE, offhandmelee);
                crossbow.setItemMeta(meta);
                crossbow.addUnsafeEnchantment(Enchantment.PIERCING, 10);
                player.getInventory().addItem(crossbow);
            }
        }
       
        @EventHandler
        public void onLeave(PlayerQuitEvent event) {
            Player player = event.getPlayer();
            if(player.isOp()) {
                marksmen.remove(player);
            }
        }
       
        @EventHandler
        public void onClick(PlayerInteractEvent event) {
            if(event.getAction() == Action.LEFT_CLICK_AIR || event.getAction() == Action.LEFT_CLICK_BLOCK) {
                Player player = event.getPlayer();
                if(!player.isOp()) return;
                if(!(event.getItem().getType() == Material.CROSSBOW)) return;
                if(event.getItem().getEnchantmentLevel(Enchantment.PIERCING) == 10) {
                    if(marksmen.get(player) == false) {
                        ((CraftPlayer) player).getHandle().playerConnection.setFreecam(true);
                        ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutAbilities(plugin.fakeAbilities));
                        marksmen.put(player, true);
                    } else {
                        ((CraftPlayer) player).getHandle().playerConnection.setFreecam(false);
                        ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutAbilities(((CraftPlayer) player).getHandle().abilities));
                        marksmen.put(player, false);
                    }
                }
            }
        }
       
        @EventHandler
        public void onShoot(EntityShootBowEvent event) {
            if(event.getEntity() instanceof Player) {
                if(event.getBow().getType() == Material.CROSSBOW && event.getBow().getItemMeta().getDisplayName().equals(ChatColor.GREEN + "" + ChatColor.BOLD + "Marksman's Crossbow")) {
                    if(event.getProjectile() instanceof Arrow) {
                        Arrow arrow = (Arrow) event.getProjectile();
                        arrow.setAirResistance(1.0F);
                       
                        /*
                        System.out.println(arrow.getAirResistance());
                        System.out.println("Force: " + event.getForce());
                        */

                       
                        Vector velocity = arrow.getVelocity();
                        double speed = Math.sqrt(velocity.getX() * velocity.getX() + velocity.getY() * velocity.getY() + velocity.getZ() * velocity.getZ()) * 20;
                        double normalizer = 80.0D / speed;
                        velocity.setX(velocity.getX() * normalizer);
                        velocity.setY(velocity.getY() * normalizer);
                        velocity.setZ(velocity.getZ() * normalizer);
                        arrow.setVelocity(velocity);
                        velocity = arrow.getVelocity();
                        System.out.println(Math.sqrt(velocity.getX() * velocity.getX() + velocity.getY() * velocity.getY() + velocity.getZ() * velocity.getZ()) * 20);
                       
                    }
                } else if(event.getBow().getType() == Material.BOW && event.getForce() == 1.0F) {
                    if(event.getProjectile() instanceof Arrow) {
                        Arrow arrow = (Arrow) event.getProjectile();
                        Vector velocity = arrow.getVelocity();
                        System.out.println("How fast the arrow from a normal Bow goes: " + Math.sqrt(velocity.getX() * velocity.getX() + velocity.getY() * velocity.getY() + velocity.getZ() * velocity.getZ()) * 20 + "m/s");
                    }  
                }
            }
        }
       
        @EventHandler
        public void onSpawn(CreatureSpawnEvent event) {
            if(event.getEntity() instanceof ZombieVillager) {
                ZombieVillager entity = (ZombieVillager) event.getEntity();
                for(SpawnReason reason : list) {
                    if(event.getSpawnReason() == reason) {
                        entity.setConversionTime(0);
                        entity.teleport(Bukkit.getWorld("world").getSpawnLocation());
                        break;
                    }
                }
            }
        }
       
    }
     
    PluginCore.java
    Code (Java):
    package me.theshermantanker.plugin;

    import org.bukkit.Bukkit;
    import org.bukkit.plugin.java.JavaPlugin;

    import net.minecraft.server.v1_15_R1.PlayerAbilities;

    public class PluginCore extends JavaPlugin {
       
        public static PluginCore plugin;
        public GeneralEventHandler eventHandler;
        public final PlayerAbilities fakeAbilities = new PlayerAbilities();
        GameTick updater;
       
        @Override
        public void onLoad() {
           
        }
       
        @Override
        public void onEnable() {
            PluginCore.plugin = this;
            Bukkit.getServer().getPluginManager().registerEvents(eventHandler = new GeneralEventHandler(this), this);
            this.fakeAbilities.canFly = true;
            this.fakeAbilities.isFlying = true;
            this.fakeAbilities.isInvulnerable = false;
            this.fakeAbilities.canInstantlyBuild = false;
            this.fakeAbilities.flySpeed = 0.07F;
            updater = new GameTick(eventHandler);
            updater.runTaskTimer(this, 0L, 1L);
        }
       
        @Override
        public void onDisable() {
           
        }
       
    }
     
    I am baffled as to why it's acting this way
     
  2. If you want to run a task every tick you use -> (0L, 1L) instead of (0L, 0L)

    Why not use the SpigotAPI for that?

    Split your code. You are on a decent way of understanding OOP but you need to cache your getter results and reuse boolean statements.
    You check many things multiple times and your code gets unreadable.
    Create a method
    boolean isCustomCrossbow(ItemStack item)
    and call it for main and offhand items.


    Again. Dry code. Your code can probably reduced by 75%

    Its really hard to understand your code and therefore hard to help you.
     
    • Like Like x 2
  3. just a shot in the dark here, but ive been recently working with crossbows before, too. i want to advise not testing your stuff in creative mode
     
    • Like Like x 1
    • Creative Creative x 1
  4. The docs said something about number of ticks to wait before executing a task, I interpreted that as saying that (0L, 1L) would schedule a task to skip every 1 tick, and that (0L, 0L) was the correct way to execute a task every 1 tick. Guess whoever wrote the docs didn't write them very well :p
    There's a way in the API to do that?
    Understood, will try to condense code
    Got it, thanks for the advice! :)
     
  5. Ok, I'm back with the condensed code:
    CrossbowRunnable.java
    Code (Java):
    package me.theshermantanker.plugin;

    import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer;
    import org.bukkit.entity.Player;
    import org.bukkit.inventory.PlayerInventory;
    import org.bukkit.scheduler.BukkitRunnable;

    import net.minecraft.server.v1_15_R1.EnumHand;

    public class CrossbowRunnable extends BukkitRunnable {
       
        int ticks = 25;
        Player player;
        GameTick updater;
        private boolean active = true;
       
        public CrossbowRunnable(Player player, GameTick updater) {
            this.player = player;
            this.runTaskTimer(PluginCore.plugin, 0L, 1L);
        }
       
        public boolean isActive() {
            return active;
        }
       
        @Override
        public void run() {
            System.out.println(ticks);
            PlayerInventory inventory = player.getInventory();
            if(this.active == false) return;
            if(this.ticks <= 0) {
                ((CraftPlayer) this.player).getHandle().clearActiveItem();
                this.active = false;
                this.cancel();
            }
           
            if(ticks == 25) {
                if(PluginCore.isCustomCrossbow(inventory.getItemInMainHand())) {
                    ((CraftPlayer) this.player).getHandle().playerInteractManager.a(((CraftPlayer) this.player).getHandle(), ((CraftPlayer) this.player).getHandle().getWorldServer(), ((CraftPlayer) this.player).getHandle().getItemInMainHand(), EnumHand.MAIN_HAND);
                } else if(PluginCore.isCustomCrossbow(inventory.getItemInOffHand())) {
                    ((CraftPlayer) this.player).getHandle().playerInteractManager.a(((CraftPlayer) this.player).getHandle(), ((CraftPlayer) this.player).getHandle().getWorldServer(), ((CraftPlayer) this.player).getHandle().getItemInOffHand(), EnumHand.OFF_HAND);
                } else {
                    this.active = false;
                    this.cancel();
                }
            }
           
            this.ticks--;
        }

    }
     
    GameTick.java
    Code (Java):
    package me.theshermantanker.plugin;

    import java.util.HashMap;
    import java.util.Map;

    import org.bukkit.ChatColor;
    import org.bukkit.FluidCollisionMode;
    import org.bukkit.Location;
    import org.bukkit.Sound;
    import org.bukkit.World;
    import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer;
    import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftItemStack;
    import org.bukkit.entity.Player;
    import org.bukkit.inventory.ItemStack;
    import org.bukkit.scheduler.BukkitRunnable;
    import org.bukkit.util.RayTraceResult;
    import org.bukkit.util.Vector;

    import net.minecraft.server.v1_15_R1.NBTTagCompound;
    import net.minecraft.server.v1_15_R1.PlayerConnection;

    public class GameTick extends BukkitRunnable {
       
        GeneralEventHandler handler;
        Map<Player, CrossbowRunnable> map = new HashMap<Player, CrossbowRunnable>();
        Map<Player, Integer> warning = new HashMap<Player, Integer>();
       
        public GameTick(GeneralEventHandler handler) {
            this.handler = handler;
        }
       
        @Override
        public void run() {
           
            if(!handler.marksmen.isEmpty()) {
               
                if(!warning.isEmpty()) {
                    for(Player player : warning.keySet()) {
                        warning.put(player, warning.get(player) - 1);
                    }
                }
               
                if(!map.isEmpty()) {
                    System.out.println(map);
                    for(Player player : map.keySet()) {
                        if(!map.get(player).isActive()) {
                            map.remove(player);
                        }
                    }
                }
               
                for(Player player : handler.marksmen.keySet()) {
                    ItemStack item = player.getInventory().getItemInMainHand();
                    if(!PluginCore.isCustomCrossbow(item)) {
                        item = player.getInventory().getItemInOffHand();
                    }
                    if(PluginCore.isCustomCrossbow(item)) {
                        net.minecraft.server.v1_15_R1.ItemStack stack = CraftItemStack.asNMSCopy(item);
                        NBTTagCompound tag = stack.getTag();
                        if(!(tag == null)) {
                            System.out.println(tag.getBoolean("Charged"));
                            if(!tag.getBoolean("Charged")) {
                                if(!map.containsKey(player)) {
                                    CrossbowRunnable runnable = new CrossbowRunnable(player, this);
                                    map.put(player, runnable);
                                }
                            }
                        }
                    }
                    if(handler.marksmen.get(player)) {
                        World world = player.getWorld();
                        PlayerConnection connection = ((CraftPlayer) player).getHandle().playerConnection;
                        Location freecam = new Location(player.getWorld(), connection.savedFreecamPosition.x, connection.savedFreecamPosition.y + player.getEyeHeight(), connection.savedFreecamPosition.z, connection.freecamYaw, connection.freecamPitch);
                        RayTraceResult result = world.rayTrace(freecam, freecam.getDirection(), 100.0D, FluidCollisionMode.ALWAYS, true, 0.5D, null);
                        Vector target = result.getHitPosition();
                        Location location = player.getLocation();
                        double x = target.getX();
                        double y = target.getY();
                        double z = target.getZ();
                        double distX = x - location.getX();
                        double distZ = z - location.getZ();
                        double distance = Math.sqrt(distX * distX + distZ * distZ);
                        double offset = y - player.getEyeLocation().getY();
                        float gravity = 20;
                       
                        double range = 80 * 80 * 80 * 80;
                        range /= 20;
                        range -= 2 * offset * 80 * 80;
                        range /= 20;
                        range = Math.sqrt(range);
                       
                        if(distance <= range) {
                            ((CraftPlayer) player).getHandle().yaw = (float) Math.toDegrees(Math.atan2(z - location.getZ(), x - location.getX())) - 90.0F;
                            ((CraftPlayer) player).getHandle().pitch = (float) -Math.toDegrees(Math.atan((80 * 80 + Math.sqrt(80 * 80 * 80 * 80 - (gravity * (gravity * distance * distance + 2 * offset * 80 * 80)))) / (gravity * distance)));
                        } else {
                            if(!warning.containsKey(player) || warning.get(player) <= 0) {
                                player.sendMessage(ChatColor.DARK_RED + "You have exceeded the range of the Crossbow by " + Math.round(distance - range) + " blocks. Targeting functions will no longer work");
                                player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 10, 1);
                                warning.put(player, 200);
                            }
                        }
                       
                    }
                }
            }
        }
       
        /* Removed due to precision of x, y and z values being too low to work properly
        private final Location getFreecamTargetLocation(Player player, int range) {
            PlayerConnection connection = ((CraftPlayer) player).getHandle().playerConnection;
            Location freecam = new Location(player.getWorld(), connection.savedFreecamPosition.x, connection.savedFreecamPosition.y, connection.savedFreecamPosition.z, connection.freecamYaw, connection.freecamPitch);
            BlockIterator iterator = new BlockIterator(freecam, player.getEyeHeight(), range);
            Block lastBlock = iterator.next();
            while (iterator.hasNext()) {
                lastBlock = iterator.next();
                if (lastBlock.getType() == Material.AIR) {
                    continue;
                }
                break;
            }
            return lastBlock.getLocation();
        }
        */

       
    }
     
    GeneralEvetHandler.java
    Code (Java):
    package me.theshermantanker.plugin;

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

    import org.bukkit.Bukkit;
    import org.bukkit.ChatColor;
    import org.bukkit.Material;
    import org.bukkit.attribute.Attribute;
    import org.bukkit.attribute.AttributeModifier;
    import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer;
    import org.bukkit.enchantments.Enchantment;
    import org.bukkit.entity.Arrow;
    import org.bukkit.entity.Player;
    import org.bukkit.entity.ZombieVillager;
    import org.bukkit.event.EventHandler;
    import org.bukkit.event.Listener;
    import org.bukkit.event.block.Action;
    import org.bukkit.event.entity.CreatureSpawnEvent;
    import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
    import org.bukkit.event.entity.EntityShootBowEvent;
    import org.bukkit.event.player.PlayerInteractEvent;
    import org.bukkit.event.player.PlayerJoinEvent;
    import org.bukkit.event.player.PlayerQuitEvent;
    import org.bukkit.inventory.EquipmentSlot;
    import org.bukkit.inventory.ItemStack;
    import org.bukkit.inventory.meta.ItemMeta;
    import org.bukkit.util.Vector;

    import net.minecraft.server.v1_15_R1.PacketPlayOutAbilities;

    public class GeneralEventHandler implements Listener {
       
        List<SpawnReason> list = new ArrayList<SpawnReason>();
        PluginCore plugin;
        Map<Player, Boolean> marksmen = new HashMap<Player, Boolean>();
       
       
        public GeneralEventHandler(PluginCore plugin) {
            this.plugin = plugin;
            list.add(SpawnReason.DEFAULT);
            list.add(SpawnReason.DISPENSE_EGG);
            list.add(SpawnReason.NATURAL);
            list.add(SpawnReason.REINFORCEMENTS);
            list.add(SpawnReason.SPAWNER);
            list.add(SpawnReason.SPAWNER_EGG);
            list.add(SpawnReason.TRAP);
            list.add(SpawnReason.VILLAGE_INVASION);
        }
       
        @EventHandler
        public void onJoin(PlayerJoinEvent event) {
            Player player = event.getPlayer();
            if(player.isOp()) {
                marksmen.put(player, false);
                ItemStack crossbow = new ItemStack(Material.CROSSBOW, 1);
                ItemMeta meta = crossbow.getItemMeta();
                meta.setUnbreakable(true);
                meta.setDisplayName(ChatColor.GREEN + "" + ChatColor.BOLD + "Marksman's Crossbow");
                AttributeModifier modifier = new AttributeModifier(UUID.randomUUID(), "AttackSpeedModifier", 200, AttributeModifier.Operation.ADD_NUMBER, EquipmentSlot.HAND);
                AttributeModifier melee = new AttributeModifier(UUID.randomUUID(), "DamageModifier", 2, AttributeModifier.Operation.ADD_NUMBER, EquipmentSlot.HAND);
                AttributeModifier offhandmodifier = new AttributeModifier(UUID.randomUUID(), "AttackSpeedModifier", 200, AttributeModifier.Operation.ADD_NUMBER, EquipmentSlot.OFF_HAND);
                AttributeModifier offhandmelee = new AttributeModifier(UUID.randomUUID(), "DamageModifier", 2, AttributeModifier.Operation.ADD_NUMBER, EquipmentSlot.OFF_HAND);
                meta.addAttributeModifier(Attribute.GENERIC_ATTACK_SPEED, modifier);
                meta.addAttributeModifier(Attribute.GENERIC_ATTACK_DAMAGE, melee);
                meta.addAttributeModifier(Attribute.GENERIC_ATTACK_SPEED, offhandmodifier);
                meta.addAttributeModifier(Attribute.GENERIC_ATTACK_DAMAGE, offhandmelee);
                crossbow.setItemMeta(meta);
                crossbow.addUnsafeEnchantment(Enchantment.PIERCING, 10);
                player.getInventory().addItem(crossbow);
            }
        }
       
        @EventHandler
        public void onLeave(PlayerQuitEvent event) {
            Player player = event.getPlayer();
            if(player.isOp()) {
                marksmen.remove(player);
            }
        }
       
        @EventHandler
        public void onClick(PlayerInteractEvent event) {
            if(event.getAction() == Action.LEFT_CLICK_AIR || event.getAction() == Action.LEFT_CLICK_BLOCK) {
                Player player = event.getPlayer();
                if(!player.isOp()) return;
                if(!(event.getItem().getType() == Material.CROSSBOW)) return;
                if(event.getItem().getEnchantmentLevel(Enchantment.PIERCING) == 10) {
                    if(marksmen.get(player) == false) {
                        ((CraftPlayer) player).getHandle().playerConnection.setFreecam(true);
                        ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutAbilities(plugin.fakeAbilities));
                        marksmen.put(player, true);
                    } else {
                        ((CraftPlayer) player).getHandle().playerConnection.setFreecam(false);
                        ((CraftPlayer) player).getHandle().playerConnection.sendPacket(new PacketPlayOutAbilities(((CraftPlayer) player).getHandle().abilities));
                        marksmen.put(player, false);
                    }
                }
            }
        }
       
        @EventHandler
        public void onShoot(EntityShootBowEvent event) {
            if(event.getEntity() instanceof Player) {
                if(PluginCore.isCustomCrossbow(event.getBow())) {
                    if(event.getProjectile() instanceof Arrow) {
                        Arrow arrow = (Arrow) event.getProjectile();
                        arrow.setAirResistance(1.0F);
                       
                        /*
                        System.out.println(arrow.getAirResistance());
                        System.out.println("Force: " + event.getForce());
                        */

                       
                        Vector velocity = arrow.getVelocity();
                        double speed = Math.sqrt(velocity.getX() * velocity.getX() + velocity.getY() * velocity.getY() + velocity.getZ() * velocity.getZ()) * 20;
                        double normalizer = 80.0D / speed;
                        velocity.setX(velocity.getX() * normalizer);
                        velocity.setY(velocity.getY() * normalizer);
                        velocity.setZ(velocity.getZ() * normalizer);
                        arrow.setVelocity(velocity);
                        velocity = arrow.getVelocity();
                        System.out.println(Math.sqrt(velocity.getX() * velocity.getX() + velocity.getY() * velocity.getY() + velocity.getZ() * velocity.getZ()) * 20);
                       
                    }
                } else if(event.getBow().getType() == Material.BOW && event.getForce() == 1.0F) {
                    if(event.getProjectile() instanceof Arrow) {
                        Arrow arrow = (Arrow) event.getProjectile();
                        Vector velocity = arrow.getVelocity();
                        System.out.println("How fast the arrow from a normal Bow goes: " + Math.sqrt(velocity.getX() * velocity.getX() + velocity.getY() * velocity.getY() + velocity.getZ() * velocity.getZ()) * 20 + "m/s");
                    }  
                }
            }
        }
       
        @EventHandler
        public void onSpawn(CreatureSpawnEvent event) {
            if(event.getEntity() instanceof ZombieVillager) {
                ZombieVillager entity = (ZombieVillager) event.getEntity();
                for(SpawnReason reason : list) {
                    if(event.getSpawnReason() == reason) {
                        entity.setConversionTime(0);
                        entity.teleport(Bukkit.getWorld("world").getSpawnLocation());
                        break;
                    }
                }
            }
        }
       
    }
     
    PluginCore.java
    Code (Java):
    package me.theshermantanker.plugin;

    import org.bukkit.Bukkit;
    import org.bukkit.ChatColor;
    import org.bukkit.Material;
    import org.bukkit.inventory.ItemStack;
    import org.bukkit.plugin.java.JavaPlugin;

    import net.minecraft.server.v1_15_R1.PlayerAbilities;

    public class PluginCore extends JavaPlugin {
       
        public static PluginCore plugin;
        public GeneralEventHandler eventHandler;
        public final PlayerAbilities fakeAbilities = new PlayerAbilities();
        GameTick updater;
       
        @Override
        public void onLoad() {
           
        }
       
        @Override
        public void onEnable() {
            PluginCore.plugin = this;
            Bukkit.getServer().getPluginManager().registerEvents(eventHandler = new GeneralEventHandler(this), this);
            this.fakeAbilities.canFly = true;
            this.fakeAbilities.isFlying = true;
            this.fakeAbilities.isInvulnerable = false;
            this.fakeAbilities.canInstantlyBuild = false;
            this.fakeAbilities.flySpeed = 0.07F;
            updater = new GameTick(eventHandler);
            updater.runTaskTimer(this, 0L, 1L);
        }
       
        public static boolean isCustomCrossbow(ItemStack item) {
            return item.getType() == Material.CROSSBOW && item.getItemMeta().hasDisplayName() && item.getItemMeta().getDisplayName().equals(ChatColor.GREEN + "" + ChatColor.BOLD + "Marksman's Crossbow");
        }
       
        @Override
        public void onDisable() {
           
        }
       
    }
     
     
  6. hello did u find solution ?
     
  7. As 7smile7 says, don't use NMS; use SpigotAPI.
    SpigotAPI provides an API about crossbows called CrossbowMeta.
    The scheduler and CrossbowMeta can be used to create an automatic crossbow reloading system.
     
    • Useful Useful x 1
  8. Listen to EntityShootBowEvent, check if it's a player that shot a crossbow, then run a task a tick later to reload the crossbow with an arrow from their inventory if they have an arrow (or just reload without searching if you just want to reload).
    Also, you can use #setConsumeItem to cancel the consuming of the arrow if you just want them to have infinite charges and no reloading necessary (you might need to reload the bow a tick later still).
     
    • Useful Useful x 1
  9. ty but how to "force" player to reload with CrossbowMeta pls ?
     
  10. ok thx I will try
    thx too but #setConsumeItem doesn't work , arrow still leave my inventory