Math: Circumference Teleportation

Discussion in 'Spigot Plugin Development' started by Bladian, Jun 20, 2015.

  1. I'm basically wanting to make a scatter plugin which will scatter players around an X and Z radius.

    I'm not sure how I want to do it though.
     
  2. clip

    Benefactor

    Get the max X and max Z

    Create a random which gets a number from minimum to max for both x and z.

    Teleport to the location for that x and z
     
  3. You could use the /spreadplayers command in minecraft. xP
     
  4. Use Math.random() (an explanation) to generate two coordinates (x, z), then use World.getHighestBlockYAt() (javadoc) to get the Y coordinate (add 1 to it, so they aren't IN the block), then teleport them there.
     
  5. I feel like he's trying to spread them out consistently, where all players are scattered from each other with a certain distance. So this might not be the best solution for him.

    But ofcourse I can also be completely wrong ;P
     
    • Like Like x 1
  6. If thats the case, Math.sin(), Math.cos(), Math.PI() are the relevant java methods that would be needed, but my math is wayyy to rusty to figure out the formula.

    https://en.wikipedia.org/wiki/Circle#Equations
     
  7. I still think it's best if you used minecrafts own /spreadplayers, or at least code that gets close to it.
    Just using Math.sin could still result in players being to close to each other so you need to loop, and keep testing if all players are far enough.

    My guess is that over at Mojang, they thought this one through. :p
     
    • Agree Agree x 1
  8. Sorry for the late messages. Yeah I do know that Mojang's is great but it takes a long time and doesn't reach the desired shape I want to do this basically.

    -------x------
    - zzzzzzzzzzzz -
    x zzzzzzzzzzzz x
    - zzzzzzzzzzzz -
    -------x------

    (Imagine it's a circle)

    And I need to teleport each one to the x. Having the equal distance between each other.

    What are the sin and cos functions int his case. We've only done sin and cos with triangles so far :p.
     
  9. 2*pi is representing going once around the whole circle (360°). If you divide that by the amount of spawn points you want to have on the circle you get the angle between the different spawn locations. You can then get the x and z coordinates of the different spawn locations by starting somewhere on the circle (example at angle zero) and then going those steps on the circle to get to the next spawn location:

    Pseudo code:
    Code (Text):

    delta = (2 * pi) / amountOfSpawns
    // start somewhere on the circle:
    angle = 0
    for (amountOfSpawns )
        // spawn point:
        x = centerX + radius * sin(angle)
        z = centerZ + radius * cos(angle)
        // go 1 step further on the circle, to get to the next spawn point:
        angle += delta
     
     
  10. sothatsit

    Patron

    sin and cos are from the unit circle. I suggest reading up on it, it will help you a lot with trig. Basicly, the functions are used to get relative x and y values for a given angle. In trig this is used for triangle side lengths but it can be just as easily used to get points evenly distributed on a circle.

    @blablubbabc's post shows how it would be implemented.

    The unit circle: https://www.mathsisfun.com/geometry/unit-circle.html
     
  11. It's a lot easier to simply wrap it in a class for the results though, especially when you can just do the math extremely quick in the constructor:

    Code (Java):
            public SpawnCircle(double radius) {
                double angle = Math.random() * Math.PI * 2; //Random position, use division for spacing
                this.x = Math.cos(angle) * radius;
                this.z = Math.sin(angle) * radius;
            }
     
  12. Why are you using a Math.Random in your example.

    If someone could lead me to a quick trigonometry tutorial i would be grateful.
     
  13. Wait I'm starting to see the logic!

    Okay 2 * pi which is the base.
    Divided by the amount of spawns, gives you a part of the circumference or the Angle.

    Now multiplying by the radius.

    The East/West or right/west is equal to the X coordinates.

    Therefore I multiply by sin.

    The North/South is the Z coordinates.

    Therefore I multiple it by cos. Which gives me the coordinates.

    X and Z.
     
  14. Because it generates a random coordinate along the circle, as the comment stated, you would just use division to determine even spacing.
     
  15. Thanks just looked at a few trig tutorials and I now fully understand it.

    New question. If I wanted the chunks to which they are being teleported at to be fully loaded, so it doesn't cause lag. How would I do it?

    This is what I came up with.

    Code (Text):
    package me.uhc.scatter;

    import me.uhc.core.Core;
    import org.bukkit.*;
    import org.bukkit.entity.Player;

    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Random;

    /**
    * Created by maxmigliorini on 20/06/15.
    */
    public class MethodScatter {

        private String scatter = "§9[§eScatter§9]§e";

        List<Location> locations = new ArrayList<Location>();

        public void chooseLocations(int x, int z, World world) {

            Random random = new Random();

            for (Player all : Bukkit.getOnlinePlayers()) {

                int pX = random.nextInt(x - 0 + 1) + 1;
                int pZ = random.nextInt(z - 0 + 1) + 1;
                int pY = world.getHighestBlockYAt(pX, pZ);

                Location location = new Location(world, pX, pY, pZ);

                locations.add(location);
            }


                Bukkit.broadcastMessage(scatter + "Chunks have been chosen");

        }

        public void loadLocations() {

            final int size = locations.size();


            Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(Core.getInstance(), new Runnable() {

                int i = 0;

                @Override
                public void run() {

                    if (i < size) {

                        Location location = locations.get(i);
                        Chunk chunk = location.getChunk();

                        if (!chunk.isLoaded()) {
                            chunk.load();

                        }
                    }

                }
            }, 0L, 40L);

            Bukkit.broadcastMessage(scatter + "Chunks have been loaded");
        }


        public void scatterPlayers() {

            int i = 0;

            for(Player p : Bukkit.getOnlinePlayers()) {
                if(!locations.isEmpty()) {
                    Location location = locations.get(i);
                    i++;
                    if(location.getBlock().getType() == Material.WATER) {
                        location.getBlock().setType(Material.DIRT);
                    }
                    p.teleport(location);
                    Bukkit.broadcastMessage(scatter + p.getName() + " has been scattered");
                }
                else {
                    break;
                }

            }
            Bukkit.broadcastMessage(scatter + "All players have been scattered");
        }
    }
    Code (Text):
    package me.uhc.scatter;

    import org.bukkit.Chunk;
    import org.bukkit.Location;
    import org.bukkit.event.EventHandler;
    import org.bukkit.event.Listener;
    import org.bukkit.event.world.ChunkUnloadEvent;

    import java.util.List;

    /**
    * Created by maxmigliorini on 21/06/15.
    */
    public class EventScatter implements Listener {


        List<Location> locations = CommandScatter.getInstance().getMethodScatter().locations;

        @EventHandler
        public void onUnload(ChunkUnloadEvent e) {

            Chunk chunk = e.getChunk();

            if(locations.contains(chunk)) {
                e.setCancelled(true);
            }


        }
    }
     
    Code (Text):
    package me.uhc.scatter;

    import org.bukkit.Bukkit;
    import org.bukkit.Sound;
    import org.bukkit.World;
    import org.bukkit.command.Command;
    import org.bukkit.command.CommandExecutor;
    import org.bukkit.command.CommandSender;
    import org.bukkit.entity.Player;

    /**
    * Created by maxmigliorini on 20/06/15.
    */
    public class CommandScatter implements CommandExecutor {

        private static CommandScatter instance;

        public static CommandScatter getInstance() {
            if (instance == null) {
                instance = new CommandScatter();
            }
            return instance;
        }

        public CommandScatter() {
            instance = this;

        }

        private String scatter = "§9[§eScatter§9]§e";

        public MethodScatter getMethodScatter() {
            return methodScatter;
        }

        MethodScatter methodScatter = new MethodScatter();


        @Override
        public boolean onCommand(CommandSender commandSender, Command command, String s, String[] strings) {

            if (command.getName().equalsIgnoreCase("scatter")) {

                if (commandSender.hasPermission("galaxy.scatter")) {

                    if (strings.length < 3 || strings.length > 3) {

                        commandSender.sendMessage(scatter + "The command does not have enough §4arguments");
                        commandSender.sendMessage(scatter + "/scatter <radius X> <radius Z> <world>");

                    } else {



                        World world = Bukkit.getWorld(strings[2]);

                        methodScatter.chooseLocations(Integer.parseInt(strings[0]), Integer.parseInt(strings[1]), world);
                        methodScatter.loadLocations();
                        methodScatter.scatterPlayers();


                    }
                }else {
                    commandSender.sendMessage(scatter + "You do not have the §4permissions  §eto use this command");

                }
            }
            return false;
        }
    }


     
     
  16. sothatsit

    Patron

    Just Loop through the Locations and add their chunks to a list if the chunk is not already in there.

    Something like:
    Code (Text):
    List<Chunk> chunks = new ArrayList<Chunk>();
    for(Location loc : locations) {
        Chunk chunk = loc.getChunk();
        if(!chunks.contains(chunk)) {
            chunks.add(chunk);
            chunk.load(true);
        }
    }
    chunks.clear();
    * Written in browser so might not be completely working.
     
  17. Or... y'know... use a Set
     
  18. sothatsit

    Patron

    What is the advantage of using a Set in this situation?
     
  19. Well, it's guarunteed to only have a single instance of each equivalent object, so you don't need to manually put a constraint on your Collection:

    Code (Java):
    Set<Chunk> chunks = new HashSet<>();
    for (Location l : locations) {
        chunks.add(l.getChunk());
    }

    //Java 8
    Set<Chunk> chunks = locations.stream().map(Location::getChunk).collect(Collectors::toSet);
    By definition, what you're defining is a Set.
     
  20. sothatsit

    Patron

    You would still need to check it anyway to know that you did not already load the chunk. This makes it less efficient as Sets have to check that there are no other entries which are the same when elements are added.