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 }