Search code examples
javabukkit

BUKKIT - Check for block under player


When a player damages another player, I am checking if they are on above a BEDROCK block, and if they are not, cancel the event. (To prevent the players from damaging eachother)

Instead of only checking for one block below the player, I want to check if there is a BEDROCK block 1, 2, or 3 blocks below the player. (The amount thats set as zoneHeight) I tried doing this using a for loop, but can't manage to get it working.

This is what I have right now:

@EventHandler
public void onEntityDamageByEntity(EntityDamageByEntityEvent event) {
    
    // Get damager and damagee
    Player damager = (Player) event.getDamager();
    Player damagee = (Player) event.getEntity();
    
    // Get block 
    Material zoneBlock = Material.BEDROCK;
    int zoneHeight = 3;
    
    for(int x=zoneHeight; x>0; x--) {
        // Allow pvp by default
        String allowPvp = "true";
        String zoneMessage = "";
        Material damagerLocation = damager.getLocation().add(0,-x,0).getBlock().getType();
        Material damageeLocation = damagee.getLocation().add(0,-x,0).getBlock().getType();
        
        if(damagerLocation != zoneBlock) {
            allowPvp = "false";
            zoneMessage = "§cYou are in a no-PVP zone.";
        }
        else if(damageeLocation != zoneBlock) {
            allowPvp = "false";
            zoneMessage = "§cThat player is in a no-PVP zone.";
        }
        if(x == 1 && allowPvp == "false") {
            event.setCancelled(true);
            damager.sendMessage(zoneMessage);
        }
    }
}

Question:

How can I check for a BEDROCK block from 1 to 3 blocks below the player?


Solution

  • Instead of using a String to keep track of a true / false value you can use the built in boolean data type.

    You are casting both Entity variables (event.getDamager() and event.getEntity()) to the Player type which will cause a ClassCastException whenever an EntityDamageByEntityEvent event is called not involving two players (mobs, arrows, etc.). Checking whether the entities are in fact instances of the Player class can be done with the instanceof keyword.

    Note that while the x variable in your for loop decrements from 3 to 1, your last condition inside the loop that contains the code which cancels the event always checks if x equals 1, so none of the code in that if statement will ever be executed when x is equal to 2 or 3.

    If you were to only remove the x == 1 part of the last if statement, your code would send the message to the damager every time it finds a bedrock block underneath either of the players (so possibly three times). The way your loop would be set up right now (without the x == 1 condition), all six blocks underneath the two players need to be bedrock for damage to be dealt, since any non-bedrock block that is found causes cancellation.

    I'm assuming you only want to check whether at least one of the three blocks below each player is bedrock ("if there is a bedrock block 1, 2, or 3 blocks below the player") so you'd have to write this a little differently.

    Here is a method that we can use that checks whether any depth blocks beneath a player are composed of type material (cloning the player's location is important so that we don't modify the player's actual location):

    public static boolean isMatBelow(Player player, Material material, int depth) {
        Location location = player.getLocation().clone(); // Cloned location
        for (int blocks = 1; blocks <= depth; blocks++) { // From 1 to depth
            location.subtract(0, 1, 0); // Move one block down
            if (location.getBlock().getType() == material) { // If this is the material -> return true (break/exit loop)
                return true;
            }
        }
        return false; // No such material was found in all blocks -> return false
    }
    

    Then we can use this method to check whether both players have at least one block of bedrock below them, while also sending the damager only one message:

    @EventHandler
    public void onEntityDamageByEntity(EntityDamageByEntityEvent event) {
        // Check whether both entities are players
        if (event.getDamager() instanceof Player && event.getEntity() instanceof Player) {
            Player damager = (Player) event.getDamager(); // Player doing the damage
            Player hurt = (Player) event.getEntity(); // Player getting hurt
    
            int height = 3; // The height you want to check
            Material pvpMaterial = Material.BEDROCK; // The material we are checking for
    
            boolean belowDamager = isMatBelow(damager, pvpMaterial, height); // Boolean whether a bedrock block is below the damager
            boolean belowHurt = isMatBelow(hurt, pvpMaterial, height); // Boolean whether a bedrock block is below the hurt player
            if (!belowDamager || !belowHurt) { // If there is NO bedrock between the damager or the hurt player
                // Create message -> if there isn't bedrock below the damager, initialize with first string, otherwise use the second string
                String message = (!belowDamager) ? "You are in a no-PVP zone." : "That player is in a no-PVP zone.";
                damager.sendMessage(message);
                event.setCancelled(true);
            }
        }
    }
    

    If you'd like to check whether all three blocks are bedrock underneath the player you could modify the isMatBelow(...) method to only return true if every single block is of the specified material.