001    package org.bukkit.command.defaults;
002    
003    import com.google.common.collect.Lists;
004    import com.google.common.collect.Maps;
005    import com.google.common.collect.Sets;
006    import java.util.List;
007    import java.util.Map;
008    import java.util.Random;
009    import java.util.Set;
010    
011    import org.bukkit.Bukkit;
012    import org.bukkit.ChatColor;
013    import org.bukkit.Location;
014    import org.bukkit.World;
015    import org.bukkit.command.CommandSender;
016    import org.bukkit.entity.Player;
017    import org.bukkit.scoreboard.Team;
018    
019    public class SpreadPlayersCommand extends VanillaCommand {
020        private static final Random random = new Random();
021    
022        public SpreadPlayersCommand() {
023            super("spreadplayers");
024            this.description = "Spreads players around a point";
025            this.usageMessage = "/spreadplayers <x> <z> <spreadDistance> <maxRange> <respectTeams true|false> <player ...>";
026            this.setPermission("bukkit.command.spreadplayers");
027        }
028    
029        @Override
030        public boolean execute(CommandSender sender, String commandLabel, String[] args) {
031            if (!testPermission(sender)) {
032                return true;
033            }
034    
035            if (args.length < 6) {
036                sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
037                return false;
038            }
039    
040            final double x = getDouble(sender, args[0], -30000000, 30000000);
041            final double z = getDouble(sender, args[1], -30000000, 30000000);
042            final double distance = getDouble(sender, args[2]);
043            final double range = getDouble(sender, args[3]);
044    
045            if (distance < 0.0D) {
046                sender.sendMessage(ChatColor.RED + "Distance is too small.");
047                return false;
048            }
049    
050            if (range < distance + 1.0D) {
051                sender.sendMessage(ChatColor.RED + "Max range is too small.");
052                return false;
053            }
054    
055            final String respectTeams = args[4];
056            boolean teams = false;
057    
058            if (respectTeams.equalsIgnoreCase("true")) {
059                teams = true;
060            } else if (!respectTeams.equalsIgnoreCase("false")) {
061                sender.sendMessage(String.format(ChatColor.RED + "'%s' is not true or false", args[4]));
062                return false;
063            }
064    
065            List<Player> players = Lists.newArrayList();
066            World world = null;
067    
068            for (int i = 5; i < args.length; i++) {
069                Player player = Bukkit.getPlayerExact(args[i]);
070                if (player == null) {
071                    continue;
072                }
073    
074                if (world == null) {
075                    world = player.getWorld();
076                }
077                players.add(player);
078            }
079    
080            if (world == null) {
081                return true;
082            }
083    
084            final double xRangeMin = x - range;
085            final double zRangeMin = z - range;
086            final double xRangeMax = x + range;
087            final double zRangeMax = z + range;
088    
089            final int spreadSize = teams ? getTeams(players) : players.size();
090    
091            final Location[] locations = getSpreadLocations(world, spreadSize, xRangeMin, zRangeMin, xRangeMax, zRangeMax);
092            final int rangeSpread = range(world, distance, xRangeMin, zRangeMin, xRangeMax, zRangeMax, locations);
093    
094            if (rangeSpread == -1) {
095                sender.sendMessage(String.format("Could not spread %d %s around %s,%s (too many players for space - try using spread of at most %s)", spreadSize, teams ? "teams" : "players", x, z));
096                return false;
097            }
098    
099            final double distanceSpread = spread(world, players, locations, teams);
100    
101            sender.sendMessage(String.format("Succesfully spread %d %s around %s,%s", locations.length, teams ? "teams" : "players", x, z));
102            if (locations.length > 1) {
103                sender.sendMessage(String.format("(Average distance between %s is %s blocks apart after %s iterations)", teams ? "teams" : "players",  String.format("%.2f", distanceSpread), rangeSpread));
104            }
105            return true;
106        }
107    
108        private int range(World world, double distance, double xRangeMin, double zRangeMin, double xRangeMax, double zRangeMax, Location[] locations) {
109            boolean flag = true;
110            double max;
111    
112            int i;
113    
114            for (i = 0; i < 10000 && flag; ++i) {
115                flag = false;
116                max = Float.MAX_VALUE;
117    
118                Location loc1;
119                int j;
120    
121                for (int k = 0; k < locations.length; ++k) {
122                    Location loc2 = locations[k];
123    
124                    j = 0;
125                    loc1 = new Location(world, 0, 0, 0);
126    
127                    for (int l = 0; l < locations.length; ++l) {
128                        if (k != l) {
129                            Location loc3 = locations[l];
130                            double dis = loc2.distanceSquared(loc3);
131    
132                            max = Math.min(dis, max);
133                            if (dis < distance) {
134                                ++j;
135                                loc1.add(loc3.getX() - loc2.getX(), 0, 0);
136                                loc1.add(loc3.getZ() - loc2.getZ(), 0, 0);
137                            }
138                        }
139                    }
140    
141                    if (j > 0) {
142                        loc2.setX(loc2.getX() / j);
143                        loc2.setZ(loc2.getZ() / j);
144                        double d7 = Math.sqrt(loc1.getX() * loc1.getX() + loc1.getZ() * loc1.getZ());
145    
146                        if (d7 > 0.0D) {
147                            loc1.setX(loc1.getX() / d7);
148                            loc2.add(-loc1.getX(), 0, -loc1.getZ());
149                        } else {
150                            double x = xRangeMin >= xRangeMax ? xRangeMin : random.nextDouble() * (xRangeMax - xRangeMin) + xRangeMin;
151                            double z = zRangeMin >= zRangeMax ? zRangeMin : random.nextDouble() * (zRangeMax - zRangeMin) + zRangeMin;
152                            loc2.setX(x);
153                            loc2.setZ(z);
154                        }
155    
156                        flag = true;
157                    }
158    
159                    boolean swap = false;
160    
161                    if (loc2.getX() < xRangeMin) {
162                        loc2.setX(xRangeMin);
163                        swap = true;
164                    } else if (loc2.getX() > xRangeMax) {
165                        loc2.setX(xRangeMax);
166                        swap = true;
167                    }
168    
169                    if (loc2.getZ() < zRangeMin) {
170                        loc2.setZ(zRangeMin);
171                        swap = true;
172                    } else if (loc2.getZ() > zRangeMax) {
173                        loc2.setZ(zRangeMax);
174                        swap = true;
175                    }
176                    if (swap) {
177                        flag = true;
178                    }
179                }
180    
181                if (!flag) {
182                    Location[] locs = locations;
183                    int i1 = locations.length;
184    
185                    for (j = 0; j < i1; ++j) {
186                        loc1 = locs[j];
187                        if (world.getHighestBlockYAt(loc1) == 0) {
188                            double x = xRangeMin >= xRangeMax ? xRangeMin : random.nextDouble() * (xRangeMax - xRangeMin) + xRangeMin;
189                            double z = zRangeMin >= zRangeMax ? zRangeMin : random.nextDouble() * (zRangeMax - zRangeMin) + zRangeMin;
190                            locations[i] = (new Location(world, x, 0, z));
191                            loc1.setX(x);
192                            loc1.setZ(z);
193                            flag = true;
194                        }
195                    }
196                }
197            }
198    
199            if (i >= 10000) {
200                return -1;
201            } else {
202                return i;
203            }
204        }
205    
206        private double spread(World world, List<Player> list, Location[] locations, boolean teams) {
207            double distance = 0.0D;
208            int i = 0;
209            Map<Team, Location> hashmap = Maps.newHashMap();
210    
211            for (int j = 0; j < list.size(); ++j) {
212                Player player = list.get(j);
213                Location location;
214    
215                if (teams) {
216                    Team team = player.getScoreboard().getPlayerTeam(player);
217    
218                    if (!hashmap.containsKey(team)) {
219                        hashmap.put(team, locations[i++]);
220                    }
221    
222                    location = hashmap.get(team);
223                } else {
224                    location = locations[i++];
225                }
226    
227                player.teleport(new Location(world, Math.floor(location.getX()) + 0.5D, world.getHighestBlockYAt((int) location.getX(), (int) location.getZ()), Math.floor(location.getZ()) + 0.5D));
228                double value = Double.MAX_VALUE;
229    
230                for (int k = 0; k < locations.length; ++k) {
231                    if (location != locations[k]) {
232                        double d = location.distanceSquared(locations[k]);
233                        value = Math.min(d, value);
234                    }
235                }
236    
237                distance += value;
238            }
239    
240            distance /= list.size();
241            return distance;
242        }
243    
244        private int getTeams(List<Player> players) {
245            Set<Team> teams = Sets.newHashSet();
246    
247            for (Player player : players) {
248                teams.add(player.getScoreboard().getPlayerTeam(player));
249            }
250    
251            return teams.size();
252        }
253    
254        private Location[] getSpreadLocations(World world, int size, double xRangeMin, double zRangeMin, double xRangeMax, double zRangeMax) {
255            Location[] locations = new Location[size];
256    
257            for (int i = 0; i < size; ++i) {
258                double x = xRangeMin >= xRangeMax ? xRangeMin : random.nextDouble() * (xRangeMax - xRangeMin) + xRangeMin;
259                double z = zRangeMin >= zRangeMax ? zRangeMin : random.nextDouble() * (zRangeMax - zRangeMin) + zRangeMin;
260                locations[i] = (new Location(world, x, 0, z));
261            }
262    
263            return locations;
264        }
265    }