House Plugin Help

I am making a house plugin for my private server and I need help. When I set a house, I specify two points, each with the x, y and z. I need to count every block in between these two points and store it into a Collection. How would I do this advance operation and what Collection should I use?
x1, y1, z1 is the X, Y and Z for the first point, and so on.

CommentedConfigurationNode config;

int x1, y1, z1, x2, y2, z2;
x1 = 34;
y1 = 62;
z1 = 35;
x2 = 78;
y2 = 110;
z2 = 78;

The only thing I can think of for checking each and every block in a 3D area is using a triple nested for loop, which is very inneficient iirc.

Some rough pseudo code to give you an idea of what you’re looking at:

/*
 * We are going to assume we have some fancy Vector class, but in reality this is
 * just a x, y and z coordinate set that you can recreate in many, many ways.
 */
Vector pointA = new Vector(20, 20, 20);;
Vector pointB = new Vector(40, 40, 40);
//I think Collection is an interface and not a class, but I'm too lazy to look it up.
//You would replace Collection with whatever subclass is best suited for your task.
Collection<Block> blockCollection = new Collection();

//And so the fun part begins
for (int x = pointA.getX(); x <= pointB.getX(); x++) {
    for (int y = pointA.getY(); y <= pointB.getY(); y++) {
        for (int z = pointA.getZ(); z <= pointB.getZ(); z++) {
            //This line is about 98% pseudo code
            blockCollection.add(World.getBlockAtLocation(x, y, z);
        }
    }
}

And if my midnight-trying-to-find-reasons-to-stay-awake self is right, that should give you a collection with every single block in between two points. There isn’t much that is more efficient than a nested for loop as far as iteration goes… If you have three dimensions to go through, it kinda just has to happen. The performance hits in this case are going to be in situations where you have thousands of blocks to iterate through at a time. Not to mention Collections aren’t quite known for being swift at setting/returning values.

I hope my thoughts are somewhat collective… I feel like I just rambled.

Edit: This is in fact horribly inefficient as stated below by others.

Whoa, whoa, whoa. Hold on.

Why do you need to store every block? You can find the number of blocks with simple math, and check if a person is inside that area, again, with simple math. No need for a Collection of the blocks themselves.

I’m not using any Sponge code, so don’t expect to copy/paste this.

public boolean isPlayerInArea(Location a, Location b, Location playerLoc) {
    int ax = a.getX(); int ay = a.getY(); int az = a.getZ();
    int bx = b.getX(); int by = b.getY(); int bz = b.getZ();
    int px = playerLoc.getX(); int py = playerLoc.getY(); int pz = playerLog.getZ();

    return px >= ax && px <= bx && py >= ay
           && py <= by && pz >= az && pz <= bz;
}
2 Likes

You just had to snipe me .-. Had half of a post written out, and everything. Anyway Sponge-Dependent code that’s cooler than @FerusGrim’s:

boolean isInsideCube(Vector3i pointToCheck, Vector3i startOfCube, Vector3i endOfCube) {
   int px = pointToCheck.getX(), py = pointToCheck.getY(), pz = pointToCheck.getZ();
   int ax = startOfCube.getX(), ay = startOfCube.getY(), az = startOfcube.getZ();
   int bx = endOfCube.getX(), by = endOfCube.getY(), bz = endOfCub.getZ();

  return px >= ax && px <= bx && py >= ay && py <= by && pz >= az && pz <= bz;
}
2 Likes

Yeah, well. At least I didn’t call my area a square. Shots fired~

5 Likes

I don’t know what your talking about. (Totally didn’t edit my post or anything to remove the ‘isInsideSquare’ when it should’ve been ‘isInsideCube’).

Mhmm, sure. :stuck_out_tongue:

1 Like

I want to store every block because I want to let the owner builder in his/her house.

You dont necessarily need to store the block itself; storing the block holds no purpose. Instead what you should be tracking is the points of the area where they can build, and check if their location is within those values.

I’d write pseudo code, but phonepls.

Yeah idiot me didn’t think about that when I wrote that code block above. You wouldn’t be storing a horribly large collection if you just checked if a block being placed or destroyed falls within the bounds. No need to store the actual blocks anywhere unless your goal is to chew up memory and CPU time.

3 Likes

Then how would I do this? A house area is not just a square - it could be any shape. So how could I track the points as @Xemiru suggested?

@frogocomics Above details how to detect if there is a location between two points, which is what you originally asked for.

Assuming that the space you’re referring to is not a rectangle, you would have to track additional points (and repeat the math suggested above for said points).

Now you know. :wink:

I want to make it so that when you use a command and specify two coordinates, it plugin creates with a Collection with all the coordinates. This command could be used twice, and the coordinates specified by these two commands are merged together and save to the configuration. If there is a better way to do this, please reply, email me at [email protected] or PM me.

Just do it now and optimize when needed.

What do you mean? Are there any better ways of doing this?

Wait… so how do I do this again? This may be a little advanced for me…

Code:

package com.blockenton.city.modules.land.command;

import com.google.inject.Inject;
import ninja.leaping.configurate.commented.CommentedConfigurationNode;
import org.slf4j.Logger;
import org.spongepowered.api.entity.player.Player;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.util.command.CommandCallable;
import org.spongepowered.api.util.command.CommandException;
import org.spongepowered.api.util.command.CommandSource;
import org.spongepowered.api.world.Location;

import java.util.Collections;
import java.util.List;
import java.util.Vector;

import static java.lang.Long.parseLong;
import static java.lang.Integer.parseInt;
import static org.spongepowered.api.text.Texts.*;
import static org.spongepowered.api.text.format.TextColors.*;
import static org.spongepowered.api.text.format.TextStyles.*;

public class SetFarmlandCommand implements CommandCallable {

    @Inject private Logger logger;

    private Logger getLogger() {
        return logger;
    }

    private CommentedConfigurationNode config;

    public boolean call(CommandSource source, String arguments, List<String> parents) throws CommandException {
        String[] args = arguments.split("\\s+");

        if(!(source instanceof Player)) {
            source.sendMessage(builder("Blockenton").style(BOLD).color(BLUE)
                    .append(builder("| ").color(GRAY)
                            .append(builder("You must be a player to use this command!").color(DARK_RED)
                                    .build()).build()).build());
            return false;
        }

        Player player = (Player) source;

        if(!(args.length == 7)) {
            player.sendMessage(builder("Blockenton").style(BOLD).color(BLUE)
                    .append(builder("| ").color(GRAY).append(builder("Please provide ").color(DARK_RED)
                            .append(builder("(").style(BOLD).color(GRAY)
                                    .append(builder("7").color(DARK_RED)
                                            .append(builder(")").style(BOLD).color(GRAY)
                                                    .append(builder(" arguments.").color(DARK_RED)
                                                            .build()).build()).build()).build()).build()).build()).build());
            return false;
        }

        /*
        Format: **-***-******
        country:city:landid
         */
        String[] id = args[7].split("-|_");

        if(!(id.length == 3)) {
            player.sendMessage(builder("Blockenton").style(BOLD).color(BLUE)
                    .append(builder("| ").color(GRAY)
                            .append(builder("Correct address format is aa-bbbb-ccccc-ddd-eeee.").color(DARK_RED)
                                    .build()).build()).build());
            return false;
        }

        int[] ids = new int[3];

        for(int i = 0; i < id.length; i++) {
            ids[i] = parseInt(id[1]);
        }
        long x1, y1, z1, x2, y2, z2, x, y, z;

        try {
            x1 = parseLong(args[1]);
            y1 = parseLong(args[2]);
            z1 = parseLong(args[3]);
            x2 = parseLong(args[4]);
            y2 = parseLong(args[5]);
            z2 = parseLong(args[6]);
        } catch(NumberFormatException e) {
            player.sendMessage(builder("Blockenton").style(BOLD).color(BLUE)
                    .append(builder("| ").color(GRAY)
                            .append(builder("Please provide valid coordinates!").color(DARK_RED)
                                    .build()).build()).build());
            return false;
        }

        x = x2 - x1;
        y = y2 - y1;
        z = z2 - z1;

        long area = x * y;

        if(area > 4000) {
            return false;
        }

        Location location = player.getLocation();

        long playerX, playerY, playerZ, chunkX, chunkY, chunkZ;
        playerX = (long) location.getX();
        playerY = (long) location.getY();
        playerZ = (long) location.getZ();


        config.getNode("farmland").getNode(args[7]).getNode("address").getNode("country").setValue(ids[1]);
        config.getNode("farmland").getNode(args[7]).getNode("address").getNode("city").setValue(ids[2]);
        config.getNode("farmland").getNode(args[7]).getNode("address").getNode("landid").setValue(ids[3]);

        return true;
    }

    public boolean testPermission(CommandSource source) {
        return source.hasPermission("city.land.setfarmland");
    }

    public String getShortDescription(CommandSource source) {
        return "Sets a specified area to become a farmland for private player use.";
    }

    public Text getHelp(CommandSource source) {
        return builder("Blockenton").style(BOLD).color(BLUE)
                .append(builder("| ").color(GRAY)
                        .append(builder("Usage: /<city:land:setfarmland|land:setfarmland|setfarmland> <x1> <y1> <z1> <x2> <y2> <z2> <landid>").color(DARK_RED)
                                .build()).build()).build();
    }

    public String getUsage(CommandSource source) {
        return "/<city:land:setfarmland|land:setfarmland|setfarmland> <x1> <y1> <z1> <x2> <y2> <z2> <landid>";
    }

    public List<String> getSuggestions(CommandSource source, String s) throws CommandException {
        return Collections.emptyList();
    }
}

Don’t do that. Forums are so everybody can learn from eachother. So it should be publicly visible.

4 Likes

Do safechecks for each axis.

/* Example Scenario:
 *    Point 1 is situated at -192, 70, 89
 *    Point 2 is situated at -162, 100, 105
 *
 *    Objective: find out if something is within those points
 */

// Reminder that this is example pseudocode. The best way to store this is to instantiate a new Location or a Vector3, instead of an array of 3 values.
private int[] loc1 = new int[3] {-192, 70, 89};
private int[] loc2 = new int[3] {-162, 100, 105};

/**
 * Figures out which of the two given values is the lesser
 * one, and returns it.
 */
public int lowerBound(int i1, int i2) {
    return i1 <= i2 ? i1 : i2;
}

/**
 * Figures out which of the two given values is the greater
 * one, and returns it.
 */
public int upperBound(int i1, i2} {
    return i1 >= i2 ? i1 : i2;
}

/* We need the above two methods to be able to easily compare the values in order to figure out whether or not the location is within the specified boundaries. We do this by checking every axis, if the checked location's x is between the lower bound and the upper bound values of the two boundaries. */

public boolean isInArea(Location loc) {
    if(loc.x < lowerBound(loc1[0], loc2[0]) || loc.x > upperBound(loc1[0], loc2[0])) {
        // Here, we're checking the X axis. If the location's X is lesser than the lower boundary, or greater than the upper boundary, then we return false, since its not in between.
        return false;
    }

    //We do the same for the other two axes.
    if(loc.y < lowerBound(loc1[1], loc2[1]) || loc.x > upperBound(loc1[1], loc2[1])) {
        return false;
    }
    
    if(loc.z < lowerBound(loc1[2], loc2[2]) || loc.z > upperBound(loc1[2], loc2[2])) {
        return false;
    }

    // If the code has reached this point, then it means all above checks have passed; the location's xyz values are in between the boundaries. So we'll just return true.
    return true;
}

Code is not guaranteed to work. Which you shouldn’t care about; the point of me posting examples is for you to read, understand, and learn from; not to copy and be uneducated, which’ll lead to the same question time and time again. There’re improvements on this code you can make, like replacing the int[] arrays I used to store the locations with a Location or a Vector3 object.

EDIT:
This is also gonna obviously be different for a different shape; this is for a cube area. If you were to do a polygon with no generic shape, you’ll have to figure out how checking that would work.

1 Like