Resource [1.8, 1.13] Custom Block Breaking | Change block hardness

Discussion in 'Spigot Plugin Development' started by Ytnoos, Mar 4, 2019.

  1. Custom block breaking system
    Six months ago I tried to replicate CosmicPrisons' CBB (custom block breaking) system. It has been difficult to make, I searched on the Internet for a long time without finding any thread that would've been useful for this project, but, after a few months, I did it, and I have finally decided to create a guide that will explain how to make this damn CBB system.


    What is a CBB system?

    It's a system that can edit block hardness. For example, you will be able to:
    • Break blocks faster/slower with your hand than your diamond pickaxe or every item you want;
    • Make unbreakable blocks;
    • Allow players to break the same block faster;
    • Keep blocks broken;
    and much more.


    Premises

    With this guide, I will give you some examples and I will explain to you how a CBB system should be made, it won't work while copy-pasting. Code examples aren't perfect and they are certainly improvable in different ways, if you have any advice, just let me know. Sorry for my English, I'm Italian (xd).


    How does it work?

    First of all, it removes any type of break animation when a player tries to break a block by adding him a slow dig potion effect with an amplifier equal to -1.
    Then, every tick the player is digging a block, gets the custom durability of that block and sends packets animation properly to near players.


    How could I develop it?

    You obviously need to have a decent java knowledge and you have to know what NMS and packets are. If you don't have it, don't try to copy-paste sources trying to make it works, it won't.

    So, let's start the real guide:

    1. Add to the player the slow dig potion effect:
    You have to choose when adding it and when removing it, because if a player has this effect, it won't be able to break any blocks, in my occasion I enabled it only for a mining world full of ores.

    Code (Java):
    public void addSlowDig(Player player, int duration) {
            player.addPotionEffect(new PotionEffect(PotionEffectType.SLOW_DIGGING, duration, -1, false, false), true);
    }

    public void removeSlowDig(Player player) {
            player.removePotionEffect(PotionEffectType.SLOW_DIGGING);
    }

    2. Create the broken block object:
    Code (Java):
    public class BrokenBlock {

        private int time;
        private int oldAnimation;
        private double damage = -1;
        private Block block;
        private Date lastDamage;

        public BrokenBlock(Block block, int time) {
            this.block = block;
            this.time = time;
            lastDamage = new Date();
        }

        public void incrementDamage(Player from, double multiplier) {
            if (isBroken()) return;

            time = event.getBrokenBlock().getTime();
            damage = event.getBrokenBlock().getDamage();
            multiplier = event.getMultiplier();

            damage += multiplier;
            int animation = getAnimation();

            if (animation != oldAnimation) {
                if (animation < 10) {
                    sendBreakPacket(animation)
                    lastDamage = new Date();
                } else {
                    breakBlock(from);
                    return;
                }
            }

            oldAnimation = animation;
        }

        public boolean isBroken() {
            return getAnimation() >= 10;
        }

        public void breakBlock(Player breaker) {
            destroyBlockObject();
            SoundPlayerUtils.playBlockSound(block);
            if (breaker == null) return;
            ((CraftPlayer) breaker).getHandle().playerInteractManager.breakBlock(getBlockPosition(block));
        }

        public void destroyBlockObject() {
            sendBreakPacket(-1);
            //  Here you have to remove your BrokenBlock using the BrokenBlocksService, on the next step
            BrokenBlocksService.removeBrokenBlock(block.getLocation());
        }

        public int getAnimation() {
            return (int) (damage / time * 11) - 1;
        }

        public void sendBreakPacket(int animation, Block block) {
            ((CraftServer) Bukkit.getServer()).getHandle().sendPacketNearby(null, block.getX(), block.getY(), block.getZ(), 120, ((CraftWorld) block.getLocation().getWorld()).getHandle().dimension,
                    new PacketPlayOutBlockBreakAnimation(getBlockEntityId(block), getBlockPosition(block), animation));
        }


        private BlockPosition getBlockPosition(Block block) {
            return new BlockPosition(block.getX(), block.getY(), block.getZ());
        }

        private int getBlockEntityId(Block block) {
        return ((block.getX() & 0xFFF) << 20 | (block.getZ() & 0xFFF) << 8) | (block.getY() & 0xFF);
        }
    }

    3. Create a service for a better broken blocks management:
    Code (Java):
    public class BrokenBlocksService {

        @Getter
        private static Map<Location, BrokenBlock> brokenBlocks = new HashMap<>();

        public void createBrokenBlock(Block block) {
            createBrokenBlock(block, -1);
        }

        public void createBrokenBlock(Block block, int time) {
            if (isBrokenBlock(block.getLocation())) return;
            BrokenBlock brokenBlock;
            if (time == -1) brokenBlock = new BrokenBlock(block);
            else brokenBlock = new BrokenBlock(block, time);
            brokenBlocks.put(block.getLocation(), brokenBlock);
        }

        public void removeBrokenBlock(Location location) {
            brokenBlocks.remove(location);
        }

        public BrokenBlock getBrokenBlock(Location location) {
            createBrokenBlock(location.getBlock());
            return brokenBlocks.get(location);
        }

        public boolean isBrokenBlock(Location location) {
            return brokenBlocks.containsKey(location);
        }
    }
     


    4. Create a broken block object when a player hits a block:
    Don't forget to register this listener on the main class, if you want you can:
    1. Check if the player has the slow dig effect;
    2. Save some blocks' durability in a config, so you can get a block's durability by owning its Material;
    3. Check if the event.getClickedBlock() is a block that should be broken;
    Code (Java):
    @EventHandler
    public void onBlockDamage(BlockDamageEvent event) {
        BrokenBlocksService brokenBlocksService = // Get the BrokenBlocksService instance
        brokenBlocksService.createBrokenBlock(event.getClickedBlock(), time);
    }

    5. Increase block damage on player animation:
    You should save the HashSet transparentBlocks out of the method, you don't want to create the same HashSet every time.
    Code (Java):
    @EventHandler
    public void onPlayerAnimation(PlayerAnimationEvent event) {
        EntityPlayer entityplayer = ((CraftPlayer) event.getPlayer()).getHandle();
        Set<Material> transparentBlocks = new HashSet<>();
        transparentBlocks.add(Material.STATIONARY_WATER);
        transparentBlocks.add(Material.AIR);
        Block block = entityplayer.getBukkitEntity().getTargetBlock(transparentBlocks, 5);
        Location blockPosition = block.getLocation();
        BrokenBlocksService brokenBlocksService = // Get the BrokenBlocksService instance

        if (!brokenBlocksService.isBrokenBlock(blockPosition)) return;

        Player player = entityplayer.getBukkitEntity();
        ItemStack itemStack = player.getItemInHand();

        double distanceX = blockPosition.getX() - entityplayer.locX;
        double distanceY = blockPosition.getY() - entityplayer.locY;
        double distanceZ = blockPosition.getZ() - entityplayer.locZ;

        if (distanceX * distanceX + distanceY * distanceY + distanceZ * distanceZ >= 1024.0D) return;
        brokenBlocksService.getBrokenBlock(blockPosition).incrementDamage(player, multiplier);
    }

    Add-ons:
    I give you some classes that are utils class or that may be useful for some features.
    SoundPlayerUtils
    RemoveOldBlockTask - Removes block break animations if last damage time is too high
    Reflection 1.13 (by TheDarkSword01) - Packets for 1.13
    CustomBlockBreakSystem (by TheDarkSword01) - Github repository


    The end:
    If you didn't understand something or you found issues, typos, grammar errors, java errors, etc... feel free to contact me on the forum or on telegram.

    I hope I have been helpful, thanks for reading.:coffee:
     
    #1 Ytnoos, Mar 4, 2019
    Last edited: Mar 22, 2020
    • Useful Useful x 6
    • Like Like x 4
    • Winner Winner x 1
  2. Nice resource, best developer ever
     
    • Like Like x 2
  3. Nice, good job ytnoos ..
     
    • Like Like x 1
  4. Awesome :):coffee:
     
    • Friendly Friendly x 1
  5. Have you leaked CosmicPrison? ?
     
  6. Not really but almost :ROFLMAO:
     
    • Optimistic Optimistic x 1
  7. Keep up with your good work! I can see you can push it bit more to make it more efficient and add more capabilities.

    Looking forward to the future update.
     
    • Like Like x 1
  8. A well-written tutorial.

    Just one extremely small nitpick; this is just a style preference I have, but I usually write comments like this:

    // Get the BrokenBlocksService instance

    Rather than:

    //Get the BrokenBlocksService instance

    Nothing to think about if you don't agree, but I'd personally compare the latter with a code without blanks/whitespace/spaces.

    Apart from that, nicely done!
     
    • Useful Useful x 1
  9. Thank you for the answer, Next update will be about time/multiplier management.

    Thanks for your reply, I edited the comments
     
    • Like Like x 1
  10. This is a good resource, just a couple of suggestions /ideas.

    In the runnable, you are using nms code. For that, use an obf helper (A method with a name that explains what the obfuscated method does). I am not sure what EntityPlayer.ar is so I can't say.

    I have a similar system in place, but instead of using Entity#getTargetBlock I work my way out using a combination of BlockDamageEvent (to initialize the block class) and PlayerAnimationEvent when he swing the hand. When there are quite some players, having a runnable with that intensity running every tick can create tps problems.
     
  11. Oh, I didn't even know the PlayerAnimationEvent. By the way, is BlockDamageEvent fired with the slow dig at -1
     
  12. Yes, it is fired.
     
  13. Interesting, I will test it and I will make an update, thanks :)
     
  14. How can i get the multiplier from an item?
     
  15. Default multiplier is 1, but you can increase it when you want, for example:
    • 1.5 for wooden tools;
    • 2 for stone tools;
    • 2.5 for iron tools;
    • 3 for gold and diamond tools
    • 5 for your best custom tool that breaks things at the speed of light
     
  16. Updated the resource with BlockDamageEvent and PlayerAnimationEvent.
     
  17. What is this method? "getBlockEntityId(block)"
     
  18. I forgot it.
    Code (Java):
    int getBlockEntityId(Block block) {
        return ((block.getX() & 0xFFF) << 20 | (block.getZ() & 0xFFF) << 8) | (block.getY() & 0xFF);
    }
    Also added on the resource
     
  19. I translated all NMS code using Reflection: https://pastebin.com/K2P0MGE4
    If you want you can add it.
    (The version tested is 1.13.2)
     
  20. Awesome, thank you so much :)