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?

    [​IMG]
    It's a spigot plugin where you 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 work. Code examples aren't perfect and they are certainly improvable in different ways, if you have any advice, just let me know. Oh, I was about to forget it, 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, it checks every tick if a 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 HashMap<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();
        HashSet<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: Apr 21, 2019
    • Useful Useful x 4
    • Like Like x 3
    • 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 :)
     

Share This Page