Radius centered around a specific coordinate?

Discussion in 'Plugin Development' started by megasaad44, Apr 30, 2014.

Thread Status:
Not open for further replies.
  1. Offline

    megasaad44

    So i tried a lot of things... ... .. .. .. ..... That errored...

    I need a thing that can set a coordinate that tests for players in a specific location in a specific radius
    OR a cuboid if necessary.

    basically, i have an area in which if a player walks in, something happens, when player leaves it, something happens.
    I don't mind hooking into worldguard if possible..


    Worldguard has no useful event listener that i found for this.
    WG events errored
    The cuboid resource thread also errored with me
    something something everything errored..

    Anything that can legitimately detect if a player is in the radius that is centered around a specific coordinate?
    Also, how do you use the Location method? (which also errored u_u)
     
  2. Offline

    Briggybros

    I would have a task that checks players coordinates every tick. Then check if a player is within a circle by substituting values into this formula..
    (playerX - centreX)^2 + (playerY - centreY)^2 < radius^2
    in java this will return a boolean - true if the player is within the circle, false if they are outside. You can then use your tick checker to check if a players state has changed from true to false (leaving) or false to true (entering).

    For a square/rectangle simply check if the player's coordinates satisfy this equation and do the same..
    |playerX - centreX| < width/2 && |PlayerY - centreY| < height/2
    where | represent the modulus. This will return true if the player is withing the rectangle, and false if not.
     
  3. Offline

    megasaad44

    Briggybros Interesting! Could you show me an example please? My brain only works on examples. :p
     
  4. Offline

    garbagemule

    You keep saying things "errored", but you don't talk about which errors you get. Chances are you're stuck with NullPointerExceptions or other self-induced problems that cannot be attributed to "this method doesn't work".

    The Location class has a distance() method, which returns the distance between itself and another Location, which is exactly what you need - it does for you what Briggybros is trying to make you do manually. Use the tools available to you.

    As for how to check if a Player walks into a region, Briggybros' idea of a repeating task is fine, and certainly better than PlayerMoveEvent (avoid this whenever you can), because it gives you more fine-grained control. For instance, there is probably no reason to check every tick, but only once per second, or maybe even once per five seconds, depending on how much accuracy you need. Secondly, you can limit your search space to players in the same world as the region, instead of looping over ALL players online.

    Finally, since this rapid execution of the same blocks of code could bring a server to its knees if it is not efficient enough, consider using distanceSquared() instead, which will return the squared distance (duh), and then instead of comparing with the radius, you compare with the radius squared. The distance() method is slower because it has to calculate a square root, which could create a bottleneck in a tight loop.

    Happy hacking.

    Edit 1: An example of how to use distanceSquared(), assuming that you have a reference to the World (world) and the Player (player) involved, as well as the radius (radius) of the region and the coordinates of the center block (x, y, z):
    Code:java
    1. Location center = world.getBlockAt(x,y,z).getLocation();
    2. Location point = player.getLocation();
    3. double distSquared = point.distanceSquared(center);
    4. if (distSquared < radius * radius) {
    5. // do stuff
    6. }

    You can make a small optimization of storing the squared radius instead of the radius, as well as the Location of the center block, so you don't have to grab that every time, but those are perhaps unimportant details.

    Edit 2: I'd like to point out that "my brain only works on examples" can often be translated to "I am too lazy to try things out for myself". I personally understand things better with visualizations of abstract concepts, and examples often help, but sometimes you will benefit a lot more from the help people give you if you just dive in and have a go at it. If you understand it, great, if you don't, ask again and explain what you've tried. Then it will be easier for you to reason about why what you did was insufficient, which will help you learn a lot better than simply being spoonfed solutions :)
     
  5. Offline

    megasaad44

    garbagemule That's amazing! I'll try that out! and when i say error i mean i couldn't figure it out for the life of me after googling it for 3 hours...

    Could you explain how i can accomplish the "check every tick per second" thing? I'm guessing it's going to include bukkit scheduler and a loop.
     
  6. Offline

    Briggybros

    So, say you have a class for each area that exists, each class has an enum for which type of area they are, circle or rectangle. The class also has types for the radius, location of centre width and height (in a plan view) (method can be edited for 3D shapes). A HashMap like so will also be needed:
    Code:java
    1. HashMap lastPlayerStatus = new HashMap<String, Boolean>();

    once the class is created, this method can be run;
    Code:java
    1. public void startLoop() {
    2. long delay = 0, wait = 5; //Delay from calling task to running task and wait between runs (in ticks (20 ticks per second (usually)))
    3. final HashMap <String, Boolean> fLastPlayerStatus = lastPlayerStatus; // create a final copy of the lastPlayer status
    4. Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, new Runnable() { // create the task to repeat
    5. public void run() {
    6. HashMap <String, Boolean> playerStatus = new HashMap<String, Boolean>(); // create a hashmap to be compared with the previous
    7. if(this.type = AreaType.CIRCLE) { // check region type
    8. for(Player player : Bukkit.getOnlinePlayers()) { // loop through all players online
    9. if ((player.getLocation().getX() - center.getX())^2 + ((player.getLocation().getZ() - center.getZ())^2 < radius^2)) // checks if the player is in the circle. assumes area class has a centre Location type and a unit for radius
    10. playerStatus.put(player.getName(), true); // sets the player to be in the area
    11. else
    12. playerStatus.put(player.getName(), false); // sets the player not to be in the area
    13. }
    14. } else { // if the area is a square
    15. for(Player player : Bukkit.getOnlinePlayers()) { // loop through all online players
    16. if (Math.mod(player.getLocation().getX() - centre.getX()) < width/2 && Math.mod(player.getLocation().getZ() - centre.getZ()) < height/2) // check if the player is within the rectangle
    17. playerStatus.put(player.getName(), true); // same as above
    18. else
    19. playerStatus.put(player.getName(), false); // same as above
    20. }
    21. }
    22.  
    23. for(Player player : Bukkit.getOnlinePlayers()){ // loop through players again
    24. if (playerStatus.get(player.getName()) != fLastPlayerStatus.get(player.getName())) { // if status of player has changed
    25. //player has left or entered
    26. if (!playerStatus.get(player.getName())) { // player has entered
    27. //Do something
    28. } else { //player has left
    29. //Do something
    30. }
    31. }
    32. }
    33. updateLastStatus(playerStatus);
    34. }}, delay, wait);
    35. }
    36.  
    37. public void updateLastStatus(HashMap map) {
    38. lastPlayerStatus = map; // simply sets the newly filled out hashmap to be checked against next loop
    39. }

    Everything in the code is annotated, feel free to ask any questions you have. The code does not account for if a new player joins between loops, so that will need to be added. I wrote this as quick as I could to help you, so there may be errors, bring them to my attention if you need it clearing up.

    EDIT:
    I come from a less Bukkit orientated background and as garbagemule said you can use all the assets you have available to you. However, if you ever find yourself working outside of Bukkit, the mathematical methods will always work.
     
  7. Offline

    megasaad44

  8. Offline

    garbagemule

    Briggybros
    Please indent your code :( It's so painful to see people trying to help beginners by posting an indecipherable blob of left-aligned but nested code blocks.

    The idea with a map with booleans as values is overcomplicating things. There is absolutely no reason to use a map when the purpose of the data structure is membership testing, which is exactly what a set is for. Besides, there is no reason for the data structure to begin with, because all it does is allow you to iterate the exact same set of players again later to do what you could have done when constructing the data structure.

    Maths are important in programming, and it's important to be able to translate mathematical equations and expression to code, no doubt about it. It is a bit silly then (playful banter, please don't take offense), that you would write "(playerX - centerX)^2" (simplified for readability) in your code, because this has nothing to do with raising the value of the parenthesized expression to the power of two. Instead, this becomes the binary XOR between the value of the parenthesized expression and the number 2 ;) Translating maths to code has many, many more pitfalls than this, and that's why it's important when writing software to be a "programmer first, mathematician second". Syntax and language basics are key, and that's why they are so important to learn from the beginning.

    As a final word of advice - avoid enums unless you are absolutely certain you know what you are doing with them. There are a variety of shapes, not just circles and rectangles, so there is no reason to hardcode this into a big blob full of duplicate code. Compositional design is much cleaner, and infinitely more extensible.

    That was the talk, so I guess I have to walk the walk...

    Let Region be the class responsible for the regions to monitor, and let Shape be an interface representing the shape of a region. A region has a shape, and a shape can be anything. We don't know what shape it is, all we know is that something can be inside or outside of it. As such, it is the responsibility of the shape (not the region) to determine if something is inside or outside. We express this responsibility with the interface and a method called contains(), which takes a Location as its only argument and returns a boolean.
    Code:java
    1. public class Region {
    2. private Shape shape;
    3. private World world;
    4.  
    5. public Region(Shape shape, Plugin plugin, World world) {
    6. this.shape = shape;
    7. this.world = world;
    8. monitor(plugin);
    9. }
    10.  
    11. private void monitor(Plugin plugin) {
    12. ...
    13. }
    14. }
    15.  
    16. public interface Shape {
    17. public boolean contains(Location location);
    18. }


    Great, so to create a region, we first have to specify its shape (we also need its World to get only the relevant players, and a Plugin reference for the scheduler). But since Shape is an interface, we don't care about the implementation, only that we can call its contains() method. It is the responsibility of the shape to determine if something is inside it, not the region's. So we can flesh out the monitor() method of Region.
    Code:java
    1. private void monitor(final Plugin plugin) {
    2. Bukkit.getScheduler().runTaskTimer(plugin, new Runnable() {
    3. @Override
    4. public void run() {
    5. for (Player player : world.getPlayers()) {
    6. if (shape.contains(player.getLocation())) {
    7. // do something with the player
    8. }
    9. }
    10. }
    11. }, 0, 20);
    12. }


    Here we use a delay of 0 seconds and period of 20 ticks, one second, which means the task will run immediately, and then once every second. Very straight-forward. This allows us to support any implementation of the Shape interface, which means we don't bind ourselves to "circles" or "rectangles" only. We can expand to 3D shapes like boxes, cylinders, and spheres, and any kind of polygon, if we know how to test for membership in them. Let's start with the sphere, because it's super simple. Let Circle be a class that implements the Shape interface.
    Code:java
    1. public class Sphere implements Shape {
    2. private Location center;
    3. private double radiusSquared;
    4.  
    5. public Sphere(Location center, double radius) {
    6. this.center = center;
    7. this.radiusSquared = radius * radius;
    8. }
    9.  
    10. @Override
    11. public boolean contains(Location location) {
    12. double distSquared = center.distanceSquared(location);
    13. return distSquared < radiusSquared;
    14. }
    15. }


    Again, very straight-forward. Now, as for circles, it doesn't really make sense to consider a 3D point to be within a 2D area, so you could just generalize to a cylinder instead of a circle, and then use a height of 256 (or world height or whatever) for the cylinder if you want it to span all of the y-axis.

    megasaad44
    Don't blindly copy/paste this stuff into your project. It has not been tested, and it was never meant for production. Use it as inspiration if you want, but implement this yourself. And most important of all, when I say that this approach is better, don't take my word for it. Convince yourself that it is (or isn't) by thinking critically. I gave you the sphere, now work the rest out on your own :)
     
    SuperOmegaCow likes this.
  9. Offline

    megasaad44

    garbagemule Mother of java....
    I'll tinker with this in the mornin! ^_^

    garbagemule Um.. I'm a little confused here. My brain works on examples. Could you show me one using this?

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Jun 7, 2016
  10. Offline

    garbagemule

    I don't believe in spoonfeeding code. You'll learn nothing if I give you everything. My previous post gave you all the pieces for the puzzle, all you gotta do is put them together. If you don't know what interfaces are and how to use them, you should probably try to find a good tutorial on object-oriented programming.

    Programming is a craft like any other - booksmarts are useless on their own. You have to experiment and fail before you can learn and gain experience. If you are confused, then explain why you are confused, what you have tried, and what the result is. Don't just do this.
     
  11. Offline

    Briggybros

    garbagemule I used the hashmap to store the states for each player so that it could be tested against in the next repeat of the task so it could be seen if a change of state has taken place. The OP stated "a player walks in, something happens, when player leaves it, something happens" and you need to know if a player was in the area to say if they've left and visa versa. Using your method, you can tell if a player is or is not in the shape which is fine if you want to apply rules to the player when they're in or out of the area, but it doesn't know between repeats if a player has changed so you would need to log the last results.
     
  12. Offline

    garbagemule

    Briggybros
    There's still no reason to use a map, because all you're doing is mapping "yes/no", which is a decision problem, not a search problem. The map will be slower because it has to store information that isn't necessary. Membership is a boolean relation - by using a set, you can keep track of who is inside the region, and anyone who isn't inside must by definition be outside.
     
  13. Offline

    Briggybros

    garbagemule Oh yes, that makes much more sense than what I was doing xD
     
Thread Status:
Not open for further replies.

Share This Page