diff --git a/pom.xml b/pom.xml index 2ab7333d..c7c5c178 100644 --- a/pom.xml +++ b/pom.xml @@ -128,6 +128,12 @@ R7 compile + + + org.jgrapht + jgrapht-core + 1.4.0 + @@ -250,6 +256,8 @@ com.google.code.gson:gson org.jdom:jdom2 org.mcstats.bukkit:metrics + org.jgrapht:jgrapht-core + org.jheaps:jheaps @@ -258,4 +266,4 @@ - + \ No newline at end of file diff --git a/src/main/java/org/mctourney/autoreferee/AutoRefMatch.java b/src/main/java/org/mctourney/autoreferee/AutoRefMatch.java index de806c73..70f7e408 100644 --- a/src/main/java/org/mctourney/autoreferee/AutoRefMatch.java +++ b/src/main/java/org/mctourney/autoreferee/AutoRefMatch.java @@ -9,6 +9,7 @@ import java.text.SimpleDateFormat; import java.util.*; import java.util.logging.Level; +import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; @@ -22,6 +23,8 @@ import com.google.common.collect.MapDifference; import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import com.google.gson.Gson; +import com.google.gson.JsonElement; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; @@ -61,6 +64,7 @@ import org.bukkit.material.Redstone; import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; import org.bukkit.scoreboard.DisplaySlot; import org.bukkit.scoreboard.Objective; import org.bukkit.scoreboard.Scoreboard; @@ -87,6 +91,7 @@ import org.mctourney.autoreferee.listeners.ZoneListener; import org.mctourney.autoreferee.regions.AutoRefRegion; import org.mctourney.autoreferee.regions.CuboidRegion; +import org.mctourney.autoreferee.regions.RegionGraph; import org.mctourney.autoreferee.util.ArmorPoints; import org.mctourney.autoreferee.util.BlockData; import org.mctourney.autoreferee.util.BookUtil; @@ -930,6 +935,7 @@ public void run() public AutoRefMatch(World world, boolean tmp, MatchStatus state) { this(world, tmp); setCurrentState(state); } + @SuppressWarnings("deprecation") public AutoRefMatch(World world, boolean tmp) { setPrimaryWorld(world); @@ -968,7 +974,29 @@ public AutoRefMatch(World world, boolean tmp) messageReferees("match", getWorld().getName(), "init"); loadWorldConfiguration(); - + + this.createRegionGraphs(); + + try { + this.loadRegionJSON(); + } catch (FileNotFoundException | ClassCastException e) { + AutoReferee.log("Failed to load " + REGION_CFG_FILENAME); + e.printStackTrace(); + } + + /*if(AutoReferee.getInstance().isExperimentalMode()) { // experimental feature + this.initRegionGraphs(); + + graphTask = + new BukkitRunnable() { + @Override + public void run() { + computeRegionGraphs(); + graphTask = null; + } + }.runTaskAsynchronously(AutoReferee.getInstance()); + }*/ + messageReferees("match", getWorld().getName(), "map", getMapName()); setCurrentState(MatchStatus.WAITING); @@ -1181,7 +1209,7 @@ protected void clearScoreboardData(Scoreboard sb) } protected void loadScoreboardData() - { + { clearScoreboardData(scoreboard); clearScoreboardData( infoboard); @@ -2116,7 +2144,57 @@ public Set getRegions(AutoRefTeam team) public boolean addRegion(AutoRefRegion reg) { return reg != null && !regions.contains(reg) && regions.add(reg); } - + + protected void createRegionGraphs() { + for ( AutoRefTeam t : this.getTeams() ) { + t.createRegionGraph(); + } + } + + protected void initRegionGraphs() { + for ( AutoRefTeam t : this.getTeams() ) { + t.initRegionGraph(); + } + } + + public void computeRegionGraphs() { + for ( AutoRefTeam t : this.getTeams() ) { + t.computeRegionGraph(); + } + } + + public boolean regionGraphsLoaded() { + return this.getTeams().stream() + .allMatch(t -> t.getRegGraph().loaded()); + } + + public static final String REGION_CFG_FILENAME = "regions.json"; + + public void loadRegionJSON( ) throws FileNotFoundException, ClassCastException { + if(!AutoReferee.getInstance().isExperimentalMode()) return; + + File f = new File(this.getWorld().getWorldFolder(), REGION_CFG_FILENAME); + if(!f.exists()) return; + + Gson gson = new Gson(); + Reader reader = new FileReader(f); + + Map data = gson.fromJson(reader, Map.class); + + for( String key : data.keySet() ) { + AutoRefTeam team = this.getTeam(key); + if(team == null) continue; + + Map data2 = (Map) data.get(key); + + List restricted = (List) data2.get("restricted"); + + if(restricted != null) { + team.setRestrictionRegions( team.getRegGraph().fromInts( restricted ) ); + } + } + } + /** * A redstone mechanism necessary to start a match. * @@ -3624,4 +3702,4 @@ public void sendMatchInfo(CommandSender sender) : String.format(ChatColor.GRAY + "The current match time is: " + "%02d:%02d:%02d", timestamp/3600L, (timestamp/60L)%60L, timestamp%60L)); } -} +} \ No newline at end of file diff --git a/src/main/java/org/mctourney/autoreferee/AutoRefTeam.java b/src/main/java/org/mctourney/autoreferee/AutoRefTeam.java index bffd80ae..ae2346e0 100644 --- a/src/main/java/org/mctourney/autoreferee/AutoRefTeam.java +++ b/src/main/java/org/mctourney/autoreferee/AutoRefTeam.java @@ -1,10 +1,12 @@ package org.mctourney.autoreferee; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.logging.Level; +import java.util.stream.Collectors; import com.google.common.collect.Maps; @@ -28,11 +30,13 @@ import org.mctourney.autoreferee.listeners.GoalsInventorySnapshot; import org.mctourney.autoreferee.listeners.ZoneListener; import org.mctourney.autoreferee.regions.AutoRefRegion; +import org.mctourney.autoreferee.regions.AutoRefRegion.Flag; +import org.mctourney.autoreferee.regions.RegionGraph; import org.mctourney.autoreferee.util.BlockData; import org.mctourney.autoreferee.util.Metadatable; import org.mctourney.autoreferee.util.PlayerKit; import org.mctourney.autoreferee.util.PlayerUtil; - +import org.mctourney.autoreferee.util.Vec3; import org.apache.commons.lang.StringUtils; import com.google.common.collect.Sets; @@ -298,7 +302,18 @@ public Location getVictoryMonumentLocation() */ public Set getRegions() { return match.getRegions(this); } - + + /** + * Returns whether a particular Location + * is in team's lane or not + * @author char + * + * @param loc + * @return + */ + public boolean containsLoc(Location loc) + { return this.getRegions().stream().anyMatch(reg -> reg.contains(loc)); } + public boolean addRegion(AutoRefRegion reg) { for (AutoRefRegion ereg : match.getRegions()) @@ -343,6 +358,102 @@ public Location getSpawnLocation() return regs[random.nextInt(spawnRegions.size())].getLocation(); } + private RegionGraph graph; + private Set> restrictedRegions; + + public RegionGraph getRegGraph() { return this.graph; } + public void setRestrictionRegions(Set> regions) + { this.restrictedRegions = regions; } + + public boolean regGraphLoaded() { return this.getRegGraph().loaded(); } + + public void initRegionGraph() { + createRegionGraph(); + graph.computeGraph(); + } + + public void createRegionGraph() { + // this is an experimental feature + if(!AutoReferee.getInstance().isExperimentalMode()) return; + + if(this.getMatch() == null) return; + World w = this.getMatch().getWorld(); + if(w == null) return; + + if(this.getRegions() == null) return; + + graph = new RegionGraph(w, this.getRegions(), AutoReferee.getInstance().getLogger(), this) + .regions(this.getRegions()); + /*.setDungeonOpenings( this.getRegions().stream() + .filter(r -> r.getFlags().contains(Flag.DUNGEON_BOUNDARY)) + .collect(Collectors.toSet()));*/ + } + + // safe from async thread + public void computeRegionGraph() { + // this is an expiremental feature + if(!AutoReferee.getInstance().isExperimentalMode()) return; + if(this.getRegions() == null) return; + + RegionGraph graph = this.getRegGraph(); + if(graph == null) return; + + if(this.getMatch() == null) return; + World w = this.getMatch().getWorld(); + if(w == null) return; + + graph.findConnectedRegions(); + } + + public Set unrestrictedPts() { + if(this.getRegions() == null) return null; + + return this.getRegions().stream() + .filter(reg -> reg.getFlags().contains(Flag.NON_RESTRICTED)) + .map(reg -> reg.getBoundingCuboid().getMinimumPoint().getBlock().getLocation()) + .collect(Collectors.toSet()); + } + + /*public Set dungeonOpenings() { + if(this.getRegions() == null) return null; + + return this.regions().stream() + .filter(r -> r.getFlags().contains(Flag.DUNGEON_BOUNDARY)) + .collect(Collectors.toSet()); + }*/ + + public Set restrictedRegion(Location l) { + if(this.getRegGraph() == null) return null; + + if(this.restrictedRegions != null) { + return this.restrictedRegions.stream() + .filter(reg -> reg.contains( this.getRegGraph().vec(l) )) + .findAny().orElse(null); + } + + if(!this.getRegGraph().loaded()) return null; + if(this.getRegGraph().connectedRegions().isEmpty()) return null; + + return this.getRegGraph().connectedRegions().stream() + .filter(reg -> reg.contains( this.getRegGraph().vec(l) )) + .findAny().orElse(null); + } + + public boolean isRestrictedLoc(Location l) { + boolean def = false; + if(this.getRegGraph() == null) return def; + + if(this.restrictedRegions != null) { + return this.getRegGraph() + .isRestricted(l, this.restrictedRegions, this.getRegions()); + } + + if(!this.getRegGraph().loaded()) return def; + if(this.getRegGraph().connectedRegions().isEmpty()) return def; + + return this.getRegGraph().isInRestrictedArea(l, this.unrestrictedPts()); + } + private Set goals = Sets.newHashSet(); /** @@ -488,7 +599,7 @@ public static AutoRefTeam create(AutoRefMatch match, String name, ChatColor colo } protected void setupScoreboard() - { + { String sbteam = this.getScoreboardTeamName(); // set team data on spectators' scoreboard diff --git a/src/main/java/org/mctourney/autoreferee/AutoReferee.java b/src/main/java/org/mctourney/autoreferee/AutoReferee.java index 5506a3f8..94ab0ce8 100644 --- a/src/main/java/org/mctourney/autoreferee/AutoReferee.java +++ b/src/main/java/org/mctourney/autoreferee/AutoReferee.java @@ -40,6 +40,7 @@ import org.mctourney.autoreferee.commands.PracticeCommands; import org.mctourney.autoreferee.commands.ScoreboardCommands; import org.mctourney.autoreferee.commands.SpectatorCommands; +import org.mctourney.autoreferee.entity.EntityAREnderPearl; import org.mctourney.autoreferee.listeners.CombatListener; import org.mctourney.autoreferee.listeners.ObjectiveTracker; import org.mctourney.autoreferee.listeners.ObjectiveTracer; @@ -353,6 +354,16 @@ public void onEnable() consoleLog = getConfig().getBoolean("console-log", true); consoleLogInColor = getConfig().getBoolean("console-colors", true); + // experimental mode? + if(this.isExperimentalMode()) { + getLogger().info(this.getName() + " loaded in Experimental Mode. This is not intended for regular use!"); + + if(EntityAREnderPearl.patch()) + getLogger().info("Successfully patched EntityEnderPearl!"); + else + getLogger().severe("Failed to patch EntityEnderPearl! Please let a dev know about this."); + } + // setup the map library folder AutoRefMap.getMapLibrary(); @@ -478,6 +489,16 @@ public void sendMessageSync(CommandSender recipient, String ...msgs) catch (IllegalStateException ignored) { } } + /** + * Get whether server is in experimental mode or not + * @author char + * + * @return Whether server is in experimental mode + */ + public boolean isExperimentalMode() { + return this.getConfig().getBoolean("experimental-mode", false); + } + private class SyncMessageTask extends BukkitRunnable { private class RoutedMessage diff --git a/src/main/java/org/mctourney/autoreferee/commands/ConfigurationCommands.java b/src/main/java/org/mctourney/autoreferee/commands/ConfigurationCommands.java index f2599370..720209ac 100644 --- a/src/main/java/org/mctourney/autoreferee/commands/ConfigurationCommands.java +++ b/src/main/java/org/mctourney/autoreferee/commands/ConfigurationCommands.java @@ -1,14 +1,19 @@ package org.mctourney.autoreferee.commands; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileOutputStream; +import java.io.FileWriter; import java.io.IOException; +import java.util.HashMap; import java.util.Set; import org.bukkit.Bukkit; import org.bukkit.ChatColor; +import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; +import org.bukkit.block.Block; import org.bukkit.command.CommandSender; import org.bukkit.entity.Entity; import org.bukkit.entity.NPC; @@ -28,6 +33,7 @@ import org.mctourney.autoreferee.regions.CuboidRegion; import org.mctourney.autoreferee.util.BlockData; import org.mctourney.autoreferee.util.LocationUtil; +import org.mctourney.autoreferee.util.Vec3; import org.mctourney.autoreferee.util.commands.AutoRefCommand; import org.mctourney.autoreferee.util.commands.AutoRefPermission; import org.mctourney.autoreferee.util.commands.CommandHandler; @@ -36,12 +42,14 @@ import org.apache.commons.lang.StringUtils; import com.google.common.collect.Sets; +import com.google.gson.Gson; import org.jdom2.Element; import org.jdom2.JDOMException; import org.jdom2.output.Format; import org.jdom2.output.XMLOutputter; +import com.sk89q.worldedit.IncompleteRegionException; import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.selections.CuboidSelection; import com.sk89q.worldedit.bukkit.selections.Selection; @@ -524,4 +532,109 @@ public boolean scoreboardSave(CommandSender sender, AutoRefMatch match, String[] return true; } + + @AutoRefCommand(name= {"autoref", "regions"}, argmin=1, argmax=1, options="flprs") + @AutoRefPermission(console=false, nodes={"autoreferee.configure"}) + public boolean arRegions(CommandSender sender, AutoRefMatch match, String[] args, CommandLine options) { + if(!AutoReferee.getInstance().isExperimentalMode()) return false; + if ( match == null ) return false; + Player player = (Player) sender; + + WorldEditPlugin worldEdit = AutoReferee.getWorldEdit(); + if (worldEdit == null) + { + // world edit not installed + sender.sendMessage("This method requires WorldEdit installed and running."); + return true; + } + + AutoRefTeam team = match.getTeam(args[0]); + + if(options.hasOption('r')) { + player.sendMessage("Computing Region Graph. This may take a while..."); + //match.cancelGraphTask(); + team.initRegionGraph(); + team.computeRegionGraph(); + player.sendMessage( "Found " + team.getRegGraph().connectedRegions().size() + " regions."); + } + + if(options.hasOption('p')) { + Selection sel = worldEdit.getSelection(player); + if(sel == null || !(sel instanceof CuboidSelection)) return true; + + CuboidSelection csel = (CuboidSelection) sel; + if(!sel.getRegionSelector().isDefined()) return true; + + com.sk89q.worldedit.regions.CuboidRegion reg; + + try { + reg + = (com.sk89q.worldedit.regions.CuboidRegion) csel.getRegionSelector().getRegion(); + } catch (IncompleteRegionException e) { + e.printStackTrace(); + return false; + } + + Location l0 = new Location(player.getWorld(), reg.getPos1().getBlockX(), reg.getPos1().getBlockY(), reg.getPos1().getBlockZ()); + Location l1 = new Location(player.getWorld(), reg.getPos2().getBlockX(), reg.getPos2().getBlockY(), reg.getPos2().getBlockZ()); + + Set path = team.getRegGraph().shortestPath(l0, l1); + + if(path == null) { + player.sendMessage("No path found"); + return true; + } + + path.forEach(b -> b.setType(Material.WOOL)); + } + + if(options.hasOption('f')) { + byte data = 1; + + for( Set reg : team.getRegGraph().regionsWithoutPointsLoc( team.unrestrictedPts() ) ) { + for( Vec3 v : reg ) { + Block b = v.loc(player.getWorld()).getBlock(); + b.setType(Material.WOOL); + b.setData(data); + } + + data = (byte)(data + 1); + } + } + + if(options.hasOption('s')) { + //System.out.println(team.getRegGraph().toJSON( team.unrestrictedPts() )); + + HashMap json = new HashMap(); + + for(AutoRefTeam t : match.getTeams()) { + json.put( t.getName() , t.getRegGraph().toJSON( t.unrestrictedPts() ) ); + } + + Gson gson = new Gson(); + + File f = new File( match.getWorld().getWorldFolder(), AutoRefMatch.REGION_CFG_FILENAME ); + + try (FileWriter writer = new FileWriter( f )) { + gson.toJson(json, writer); + } catch(IOException e) { + player.sendMessage("Error writing file"); + e.printStackTrace(); + } + + player.sendMessage("Successfully wrote " + AutoRefMatch.REGION_CFG_FILENAME + "!"); + } + + if(options.hasOption('l')) { + try { + match.loadRegionJSON(); + player.sendMessage("Successfully loaded regions file"); + } catch (FileNotFoundException | ClassCastException e) { + player.sendMessage("Failed loading regions file"); + e.printStackTrace(); + } + } + + return true; + } } diff --git a/src/main/java/org/mctourney/autoreferee/entity/EntityAREnderPearl.java b/src/main/java/org/mctourney/autoreferee/entity/EntityAREnderPearl.java new file mode 100644 index 00000000..a63ce04f --- /dev/null +++ b/src/main/java/org/mctourney/autoreferee/entity/EntityAREnderPearl.java @@ -0,0 +1,112 @@ +package org.mctourney.autoreferee.entity; + +import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.bukkit.ChatColor; +import org.bukkit.craftbukkit.v1_8_R3.CraftWorld; +import org.bukkit.entity.Player; +import org.mctourney.autoreferee.AutoRefMatch; +import org.mctourney.autoreferee.AutoRefTeam; +import org.mctourney.autoreferee.AutoReferee; +import org.mctourney.autoreferee.regions.AutoRefRegion; + +import com.sk89q.worldedit.Location; + +import net.minecraft.server.v1_8_R3.EntityEnderPearl; +import net.minecraft.server.v1_8_R3.EntityLiving; +import net.minecraft.server.v1_8_R3.EntityPlayer; +import net.minecraft.server.v1_8_R3.EntityTypes; +import net.minecraft.server.v1_8_R3.World; + +public class EntityAREnderPearl extends EntityEnderPearl { + private AutoReferee ar; + private AutoRefTeam team; + private Player player; + private boolean canTravelThroughVoid = false; + + public EntityAREnderPearl(Player player, AutoRefTeam team, boolean travelThroughVoid) { + super(((CraftWorld) ((CraftPlayer) player).getWorld()).getHandle(), ((CraftPlayer) player).getHandle()); + this.team = team; + this.player = player; + this.canTravelThroughVoid = travelThroughVoid; + } + + // Called when pearl updates + @Override + public void t_() { + if(!this.canTravelThroughVoid()) { + if(!this.regions().stream() + .anyMatch(r -> r.distanceToRegion( this.locX, this.locY, this.locZ ) <= 0.0 ) ) { + if(this.getAR() != null) + this.getAR().sendMessageSync(this.player(), ChatColor.RED + "Illegal pearl!"); + + this.die(); return; + } + } + + super.t_(); + } + + public void spawn() { + this.getWorld().addEntity(this); + } + + public static boolean PATCHED = false; + + public static boolean patch() { + try { + registerEntity(EntityAREnderPearl.class, "ThrownAREnderpearl", 14); + PATCHED = true; + + return true; + } catch (Exception ignored) { + return false; + } + } + + private static void registerEntity(Class entityClass, String entityName, int entityId) + throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException{ + //ReflectionUtil.get + + for(String fieldname : Arrays.asList("c", "d", "f", "g")) { + Field field = EntityTypes.class.getDeclaredField(fieldname); + field.setAccessible(true); + + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + + Map m = (Map) field.get(null); + + switch(fieldname) { + case "c": + m.put(entityName, entityClass); + break; + case "d": + m.put(entityClass, entityName); + break; + case "f": + m.put(entityClass, entityId); + break; + case "g": + m.put(entityName, entityId); + break; + } + } + } + + public EntityAREnderPearl setAR(AutoReferee ar) { this.ar = ar; return this; } + + private AutoRefTeam team() { return this.team; } + private Set regions() { return this.team().getRegions(); } + private Player player() { return this.player; } + private boolean canTravelThroughVoid() { return this.canTravelThroughVoid; } + private AutoReferee getAR() { return this.ar; } +} diff --git a/src/main/java/org/mctourney/autoreferee/listeners/ZoneListener.java b/src/main/java/org/mctourney/autoreferee/listeners/ZoneListener.java index 0a5958a3..1aac9584 100644 --- a/src/main/java/org/mctourney/autoreferee/listeners/ZoneListener.java +++ b/src/main/java/org/mctourney/autoreferee/listeners/ZoneListener.java @@ -3,12 +3,14 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.bukkit.ChatColor; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; +import org.bukkit.entity.EnderPearl; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Minecart; @@ -26,6 +28,7 @@ import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.entity.EntityTargetEvent; +import org.bukkit.event.entity.ProjectileHitEvent; import org.bukkit.event.entity.ProjectileLaunchEvent; import org.bukkit.event.player.PlayerBedEnterEvent; import org.bukkit.event.player.PlayerBucketEmptyEvent; @@ -35,6 +38,7 @@ import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerPickupItemEvent; import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import org.bukkit.event.vehicle.VehicleEnterEvent; import org.bukkit.event.weather.WeatherChangeEvent; import org.bukkit.plugin.Plugin; @@ -42,11 +46,13 @@ import org.mctourney.autoreferee.AutoRefPlayer; import org.mctourney.autoreferee.AutoRefTeam; import org.mctourney.autoreferee.AutoReferee; +import org.mctourney.autoreferee.entity.EntityAREnderPearl; import org.mctourney.autoreferee.AutoRefMatch.MatchStatus; import org.mctourney.autoreferee.AutoRefMatch.Role; import org.mctourney.autoreferee.goals.BlockGoal; import org.mctourney.autoreferee.regions.AutoRefRegion; import org.mctourney.autoreferee.regions.AutoRefRegion.Flag; +import org.mctourney.autoreferee.regions.RegionGraph; import org.mctourney.autoreferee.util.BlockData; import org.mctourney.autoreferee.util.LocationUtil; @@ -155,7 +161,9 @@ else if (exit != null && fallspeed < FREEFALL_THRESHOLD && onGround) } } } - + + Set trackedPearls = Sets.newHashSet(); + @EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true) public void enderpearlThrow(ProjectileLaunchEvent event) { @@ -164,7 +172,7 @@ public void enderpearlThrow(ProjectileLaunchEvent event) if (match == null || match.getCurrentState() == MatchStatus.NONE) return; if (event.getEntityType() == EntityType.ENDER_PEARL) - { + { Player player = (Player) event.getEntity().getShooter(); AutoRefPlayer apl = match.getPlayer(player); @@ -174,8 +182,43 @@ public void enderpearlThrow(ProjectileLaunchEvent event) ChatColor.DARK_GRAY + " has thrown an enderpearl while out of bounds."; for (Player ref : match.getReferees()) ref.sendMessage(msg); } + + // Expiremental: track ender pearls + if(!AutoReferee.getInstance().isExperimentalMode()) return; + if(match.getCurrentState() != MatchStatus.PLAYING) return; + if(!EntityAREnderPearl.PATCHED) return; + + if(trackedPearls.contains(event.getEntity())) return; + + if(apl != null && apl.getTeam() != null) { + AutoRefTeam team = apl.getTeam(); + + if(team.isRestrictedLoc(player.getLocation())) { + event.setCancelled(true); + + EntityAREnderPearl pearl = new EntityAREnderPearl(player, team, false) + .setAR(AutoReferee.getInstance()); + + trackedPearls.add(pearl.getBukkitEntity()); + + pearl.spawn(); + } + } + } + } + + @EventHandler(priority=EventPriority.MONITOR) + public void enderpearlHit(ProjectileHitEvent event) { + if(!AutoReferee.getInstance().isExperimentalMode()) return; + + if(event.getEntity() instanceof EnderPearl) { + trackedPearls.remove(event.getEntity()); } } + + private boolean doPreventDungeonExits() { + return AutoReferee.getInstance().isExperimentalMode(); + } public boolean validPlayer(Player player) { @@ -396,7 +439,11 @@ public void creatureSpawn(CreatureSpawnEvent event) { event.setCancelled(true); return; } } - public void teleportEvent(Player pl, Location fm, Location to, String reason) + public static enum TeleportType { + ENDER_PEARL, BED, VEHICLE, MISC + } + + public void teleportEvent(Player pl, Location fm, Location to, String reason, TeleportType type) { // cannot compare locations in different worlds if (fm.getWorld() != to.getWorld()) return; @@ -412,7 +459,7 @@ public void teleportEvent(Player pl, Location fm, Location to, String reason) AutoRefPlayer apl = match.getPlayer(pl); if (apl == null) return; apl.setLastTeleportLocation(to); - + // generate message regarding the teleport event String bedrock = BlockGoal.blockInRange(BlockData.BEDROCK, to, 5) != null ? " (near bedrock)" : ""; String message = apl.getDisplayName() + ChatColor.GRAY + " has teleported @ " + @@ -421,6 +468,48 @@ public void teleportEvent(Player pl, Location fm, Location to, String reason) boolean excludeStreamers = dsq <= LONG_TELE_DISTANCE * LONG_TELE_DISTANCE; for (Player ref : match.getReferees(excludeStreamers)) ref.sendMessage(message); } + + public boolean teleportationLegal(Player pl, Location fm, Location to, AutoRefMatch match, TeleportType type) { + boolean def = true; + if(type == TeleportType.MISC) return def; + // This is an experimental feature + AutoReferee plugin = AutoReferee.getInstance(); + if(!plugin.isExperimentalMode()) return def; + if(match.getCurrentState() != MatchStatus.PLAYING) return def; + + AutoRefTeam t = match.getPlayerTeam(pl); + if(t == null) return def; + + // Prevents teleporting through bedrock + if( RegionGraph.unbreakableInRange(to, 0) != null ) + { return false; } + + // Cannot teleport between regions + if(t.restrictedRegion(fm) != t.restrictedRegion(to)) { + return false; + } + + // Cannot teleport outside of lane + if(!t.containsLoc( to )) { + return false; + } + + if(type == TeleportType.BED || type == TeleportType.VEHICLE) { + //if(match.isPracticeMode()) return def; + + // cannot enter bed/vehicle from void lane + if(!t.containsLoc( fm )) { + return false; + } + + // cannot enter bed/vehicle from far distances + if(fm.distance(to) >= 1.5) { + return false; + } + } + + return def; + } @EventHandler(priority=EventPriority.MONITOR) public void playerTeleport(PlayerTeleportEvent event) @@ -448,8 +537,16 @@ public void playerTeleport(PlayerTeleportEvent event) if (apl.getTeam().hasFlag(event.getTo(), Flag.NO_TELEPORT)) { event.setCancelled(true); return; } + if(event.getCause() == TeleportCause.ENDER_PEARL) { + if(!teleportationLegal(event.getPlayer(), event.getFrom(), event.getTo(), match, TeleportType.ENDER_PEARL)) { + event.getPlayer().sendMessage(ChatColor.RED + "Illegal pearl!"); + event.setCancelled(true); return; + } + } + String reason = "by " + event.getCause().name().toLowerCase().replaceAll("_", " "); - teleportEvent(event.getPlayer(), event.getFrom(), event.getTo(), reason); + teleportEvent(event.getPlayer(), event.getFrom(), event.getTo(), reason, + event.getCause() == TeleportCause.ENDER_PEARL ? TeleportType.ENDER_PEARL : TeleportType.MISC ); break; } @@ -484,16 +581,39 @@ public void playerVehicleEnter(VehicleEnterEvent event) for (Map.Entry, String> e : entityRenames.entrySet()) if (e.getKey().isAssignableFrom(clazz)) vehicleType = e.getValue(); - if (event.getEntered() instanceof Player) + AutoRefMatch match = plugin.getMatch(event.getVehicle().getWorld()); + if (match == null || match.getCurrentState() == MatchStatus.NONE) return; + + if (event.getEntered() instanceof Player) { + Player pl = (Player) event.getEntered(); Location fm = event.getEntered().getLocation(); + Location to = event.getVehicle().getLocation(); TeleportType type = TeleportType.VEHICLE; + + if(!teleportationLegal(pl, fm, to, match, type)) { + pl.sendMessage(ChatColor.RED + "Invalid teleport"); + event.setCancelled(true); return; + } + teleportEvent((Player) event.getEntered(), event.getEntered().getLocation(), - event.getVehicle().getLocation(), "into a " + vehicleType); + event.getVehicle().getLocation(), "into a " + vehicleType, TeleportType.VEHICLE); + } } @EventHandler(priority=EventPriority.MONITOR) public void playerBedEnter(PlayerBedEnterEvent event) { + AutoRefMatch match = plugin.getMatch(event.getPlayer().getWorld()); + if (match == null || match.getCurrentState() == MatchStatus.NONE) return; + + Player pl = event.getPlayer(); Location fm = event.getPlayer().getLocation(); + Location to = event.getBed().getLocation(); TeleportType type = TeleportType.BED; + + if(!teleportationLegal(pl, fm, to, match, type)) { + pl.sendMessage(ChatColor.RED + "Invalid teleport"); + event.setCancelled(true); return; + } + teleportEvent(event.getPlayer(), event.getPlayer().getLocation(), - event.getBed().getLocation(), "into a bed"); + event.getBed().getLocation(), "into a bed", TeleportType.BED); } @EventHandler diff --git a/src/main/java/org/mctourney/autoreferee/regions/AutoRefRegion.java b/src/main/java/org/mctourney/autoreferee/regions/AutoRefRegion.java index 53c85d72..2ff1ade5 100644 --- a/src/main/java/org/mctourney/autoreferee/regions/AutoRefRegion.java +++ b/src/main/java/org/mctourney/autoreferee/regions/AutoRefRegion.java @@ -38,10 +38,12 @@ public static enum Flag NO_ACCESS (1 << 4, false, 'a', "noaccess"), NO_TELEPORT (1 << 5, false, 't', "noteleport"), SPAWNERS_ONLY (1 << 6, false, 'w', "spawnersonly"), - NO_FLOW (1 << 7, true, 'f', "noflow"); + NO_FLOW (1 << 7, true, 'f', "noflow"), + DUNGEON_BOUNDARY (1 << 8, false, 'd', "dungeonboundary"), + NON_RESTRICTED (1 << 9, false, 'r', "nonrestricted"); // generated from above values - public static final String OPTIONS = "abenstwf"; + public static final String OPTIONS = "abdenrstwf"; private int value; private String name; @@ -102,7 +104,9 @@ public void announceRegion(AutoRefPlayer apl) } // these methods need to be implemented - public abstract double distanceToRegion(Location loc); + // NOTE: distanceToRegion is accessed by an async + // task and so MUST NOT ACCESS THE BUKKIT API + public abstract double distanceToRegion(double x, double y, double z); public abstract Location getRandomLocation(Random r); public abstract CuboidRegion getBoundingCuboid(); @@ -164,8 +168,10 @@ public Location getGroundedCenter() return loc; } + public double distanceToRegion(Location loc) { return this.distanceToRegion(loc.getX(), loc.getY(), loc.getZ()); } + public boolean contains(Location loc) - { return distanceToRegion(loc) <= 0.0; } + { return distanceToRegion(loc.getX(), loc.getY(), loc.getZ()) <= 0.0; } public boolean containsBlock(Block block) { return this.contains(block.getLocation().clone().add(0.5, 0.5, 0.5)); } diff --git a/src/main/java/org/mctourney/autoreferee/regions/CuboidRegion.java b/src/main/java/org/mctourney/autoreferee/regions/CuboidRegion.java index 914b4bb5..b431bf01 100644 --- a/src/main/java/org/mctourney/autoreferee/regions/CuboidRegion.java +++ b/src/main/java/org/mctourney/autoreferee/regions/CuboidRegion.java @@ -89,13 +89,13 @@ public static CuboidRegion combine(CuboidRegion a, CuboidRegion b) // distance from region, axially aligned (value less than actual distance, but // appropriate for measurements on cuboid regions) @Override - public double distanceToRegion(Location v) + public double distanceToRegion(double x, double y, double z) { - // garbage-in, garbage-out + /*// garbage-in, garbage-out if (v == null || v.getWorld() != world) - return Double.POSITIVE_INFINITY; + return Double.POSITIVE_INFINITY;*/ - double x = v.getX(), y = v.getY(), z = v.getZ(); + //double x = v.getX(), y = v.getY(), z = v.getZ(); Location mx = getMaximumPoint(), mn = getMinimumPoint(); // return maximum distance from this region diff --git a/src/main/java/org/mctourney/autoreferee/regions/CylinderRegion.java b/src/main/java/org/mctourney/autoreferee/regions/CylinderRegion.java index e91d991d..82f4c8f5 100644 --- a/src/main/java/org/mctourney/autoreferee/regions/CylinderRegion.java +++ b/src/main/java/org/mctourney/autoreferee/regions/CylinderRegion.java @@ -9,6 +9,7 @@ import org.mctourney.autoreferee.AutoRefMatch; import org.mctourney.autoreferee.util.LocationUtil; +import org.mctourney.autoreferee.util.MathUtil; public class CylinderRegion extends AutoRefRegion { @@ -39,10 +40,12 @@ public CylinderRegion(World world, Element elt) } @Override - public double distanceToRegion(Location loc) + public double distanceToRegion(double x0, double y0, double z0) { - return multimax(0, y - loc.getY(), loc.getY() - (y + h), - new Location(world, x, loc.getY(), z).distance(loc) - r); + double dist = MathUtil.dist(x, y, z, x0, y, z0); + + return multimax(0, y - y0, y0 - (y + h), + dist - r); } public Location getBase() diff --git a/src/main/java/org/mctourney/autoreferee/regions/PointRegion.java b/src/main/java/org/mctourney/autoreferee/regions/PointRegion.java index 60ffed54..508c9a8f 100644 --- a/src/main/java/org/mctourney/autoreferee/regions/PointRegion.java +++ b/src/main/java/org/mctourney/autoreferee/regions/PointRegion.java @@ -9,13 +9,16 @@ import org.mctourney.autoreferee.AutoRefMatch; import org.mctourney.autoreferee.util.LocationUtil; +import org.mctourney.autoreferee.util.MathUtil; public class PointRegion extends AutoRefRegion { Location pos = null; - + double x = 0; double y = 0; double z = 0; + public PointRegion(Location loc) - { this.pos = loc.getBlock().getLocation(); this.yaw = (int)loc.getYaw(); } + { this.pos = loc.getBlock().getLocation(); this.yaw = (int)loc.getYaw(); + this.x = pos.getX(); this.y = pos.getY(); this.z = pos.getY(); } public PointRegion(AutoRefMatch match, Element e) { this(match.getWorld(), e); } @@ -30,8 +33,8 @@ public Element toElement() } @Override - public double distanceToRegion(Location loc) - { return pos.distance(loc); } + public double distanceToRegion(double x0, double y0, double z0) + { return MathUtil.dist(x, y, z, x0, y0, z0); } @Override public Location getRandomLocation(Random r) diff --git a/src/main/java/org/mctourney/autoreferee/regions/RegionGraph.java b/src/main/java/org/mctourney/autoreferee/regions/RegionGraph.java new file mode 100644 index 00000000..cf8ca986 --- /dev/null +++ b/src/main/java/org/mctourney/autoreferee/regions/RegionGraph.java @@ -0,0 +1,357 @@ +package org.mctourney.autoreferee.regions; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; +import java.util.stream.Collector; +import java.util.stream.Collectors; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.jgrapht.Graph; +import org.jgrapht.GraphPath; +import org.jgrapht.alg.connectivity.ConnectivityInspector; +import org.jgrapht.alg.shortestpath.DijkstraShortestPath; +import org.jgrapht.graph.DefaultEdge; +import org.jgrapht.graph.SimpleGraph; +import org.json.simple.JSONObject; +import org.mctourney.autoreferee.AutoRefTeam; +import org.mctourney.autoreferee.regions.AutoRefRegion.Flag; +import org.mctourney.autoreferee.util.BlockData; +import org.mctourney.autoreferee.util.Vec3; + +import com.google.common.collect.HashBiMap; +import com.google.common.collect.Sets; +import com.google.gson.Gson; +import com.sk89q.worldedit.Vector; + +public class RegionGraph { + public static final List UNBREAKABLE_BLOCKS + = Arrays.asList(Material.BEDROCK, Material.ENDER_PORTAL_FRAME, Material.BARRIER); + + private Graph graph = new SimpleGraph(DefaultEdge.class); + private HashBiMap vertices = HashBiMap.create(); + private Set> connectedRegions = Sets.newHashSet(); + + private Set mapRegions; + private Set dungeonOpenings = Sets.newHashSet(); + private World world; + + private boolean loaded = false; + private Logger logger; + private AutoRefTeam team; + + public RegionGraph(World w, Set regions, Logger logger, AutoRefTeam team) { + this.mapRegions = regions; + this.world = w; + this.logger = logger; + this.team = team; + } + + // converts block data into a Graph object + // this can then be used to make computations + // (e.g. finding dungeons, bedrock holes) + // + // computationally meh but its ok b/c it will + // save relevant info to a file + public RegionGraph computeGraph() { + CuboidRegion bound = this.boundingBox(); + if(bound == null) return this; + + log("Creating a RegionGraph..."); + + this.setLoaded(false); + + this.graph = new SimpleGraph(DefaultEdge.class); + this.vertices.clear(); + this.connectedRegions.clear(); + + // add all passable blocks as vertices + for(AutoRefRegion r : regions()) { + for(Vec3 vec : blocks(r)) { + Block b = world().getBlockAt(vec.x(), vec.y(), vec.z()); + + // disregard unbreakable blocks and dungeon openings + // so we only have disconnected regions as vertices + if(!this.isDungeonBound(b)) { + Object vertex = new Object(); + this.vertices.put( vec , vertex ); + this.graph().addVertex(vertex); + } + } + } + + // add edges between all blocks + // not separated by unbreakables + for(AutoRefRegion r : regions()) { + for(Vec3 vec : blocks(r)) { + Arrays.asList(world().getBlockAt(vec.x() + 1, vec.y(), vec.z()), + world().getBlockAt(vec.x() - 1, vec.y(), vec.z()), + world().getBlockAt(vec.x(), vec.y() + 1, vec.z()), + world().getBlockAt(vec.x(), vec.y() - 1, vec.z()), + world().getBlockAt(vec.x(), vec.y(), vec.z() + 1), + world().getBlockAt(vec.x(), vec.y(), vec.z() - 1)) + .forEach( b -> { + // if it is not dungeon boundary and is within the region + if(!this.isDungeonBound(b) && regions().stream().anyMatch(reg -> reg.containsBlock(b))) { + Object v1 = this.vertex(vec(b)); if(v1 == null) return; + Object v2 = this.vertex(vec); if(v2 == null) return; + this.graph().addEdge( v1, v2 ); + } + });; + } + } + + return this; + } + + // finds connected areas (e.g. dungeons, the outside world) + // useful for finding bedrock holes, and determining legality + // of enderpearl teleports + public RegionGraph findConnectedRegions() { + log("Computing connected regions in a RegionGraph..."); + + ConnectivityInspector conn = new ConnectivityInspector(this.graph()); + List> components = conn.connectedSets(); + + this.connectedRegions = components.stream() + .map(s -> s.stream() + .map(o -> { + Vec3 vec = this.vertexVec(o); + return vec; + }) + .filter(s2 -> s2 != null) + .collect(Collectors.toSet())).filter(s2 -> !s2.isEmpty()).collect(Collectors.toSet()); + + log("Initialized graph!"); + this.setLoaded(true); + return this; + } + + public HashMap toJSON(Set nonrestricted) { + Set vecs = nonrestricted.stream().map(loc -> vec(loc)).collect(Collectors.toSet()); + Set> restrictedRegions = regionsWithoutPoints(vecs); + + //JSONObject restricted = new JSONObject(); + //JSONObject regions = new JSONObject(); + + /*int i = 0; int j = 0; + for( Set vecl : restrictedRegions ) { + JSONObject region = new JSONObject(); + + for( Vec3 v : vecl ) { + region.put(j, new int[] { v.x(), v.y(), v.z() } ); + j++; + } + + regions.put(i, region); + i++; + }*/ + + + + List> regions = restrictedRegions.stream() + .map( + r -> r.stream().map(v -> (new int[]{ v.x(), v.y(), v.z() }) ) + .collect(Collectors.toList()) + ) + .collect(Collectors.toList()); + + HashMap json = new HashMap(); + json.put("restricted", regions); + + return json; + + /*int i = 0; int j = 0; + for( Set vecl : restrictedRegions ) { + for( Vec3 v : vecl ) { + regions[i][j][0] = v.x(); + regions[i][j][1] = v.y(); + regions[i][j][2] = v.z(); + j++; + } + i++; + }*/ + + //restricted.put("restricted", regions); + //JSONObject r = new JSONObject(); + //r.put(this.team.getName(), restricted); + + //return r; + //restricted.put("", arg1) + } + + public Boolean isInRestrictedArea(Location l, Set nonrestricted) { + if(this.connectedRegions().isEmpty()) return true; + + Set vecs = nonrestricted.stream().map(loc -> vec(loc)).collect(Collectors.toSet()); + Set> restrictedRegions = regionsWithoutPoints(vecs); + if(restrictedRegions == null) return null; + + return this.isRestricted(l, restrictedRegions, this.regions()); + } + + public boolean isRestricted(Location l, Set> restrictedRegions, Set regions) { + return restrictedRegions.stream().anyMatch(s -> s.contains( vec(l) )) || + !regions.stream().anyMatch(r -> r.contains(l)); + } + + public Set> regionsWithPoints(Set pts) { + return this.connectedRegions() + .stream() + .filter( s -> s.stream().anyMatch(v -> pts.contains(v)) ) + .collect(Collectors.toSet()); + } + + public Set> regionsWithoutPoints(Set pts) { + return this.connectedRegions() + .stream() + .filter( s -> s.stream().allMatch(v -> !pts.contains(v)) ) + .collect(Collectors.toSet()); + } + + public Set> regionsWithoutPointsLoc(Set pts) { + Set ptsVec = pts.stream().map(l -> vec(l)).collect(Collectors.toSet()); + return regionsWithoutPoints( ptsVec ); + } + + public Set shortestPath(Location l0, Location l1) { + if(!this.loaded()) return Sets.newHashSet(); + + DijkstraShortestPath path = new DijkstraShortestPath(this.graph()); + GraphPath gpath = path.getPath( vertex(vec(l0)), vertex(vec(l1)) ); + + if(gpath == null) return null; + + return gpath.getVertexList().stream() + .map(v -> this.world().getBlockAt(vertexVec(v).loc(this.world()))) + .collect(Collectors.toSet()); + + //return null; + } + + // creates Vec3 from coords + private Vec3 vec(int x, int y, int z) { + CuboidRegion bound = this.boundingBox(); + if(bound == null) return null; + + int maxX = bound.getMaximumPoint().getBlockX(); int minX = bound.getMinimumPoint().getBlockX(); + int maxY = bound.getMaximumPoint().getBlockY(); int minY = bound.getMinimumPoint().getBlockY(); + int maxZ = bound.getMaximumPoint().getBlockZ(); int minZ = bound.getMinimumPoint().getBlockZ(); + + return new Vec3( x, y, z, maxX, maxY, maxZ, minX, minY, minZ ); + } + + // create Vec3 from Location + public Vec3 vec(Location l) { + return vec(l.getBlockX(), l.getBlockY(), l.getBlockZ()); + } + + // create Vec3 from block + public Vec3 vec(Block b) { + return vec(b.getLocation()); + } + + // helper function + // gets all blocks in an ARRegion + private Set blocks(AutoRefRegion reg) { + CuboidRegion bound = reg.getBoundingCuboid(); + Set r = Sets.newHashSet(); + + int maxX = bound.getMaximumPoint().getBlockX(); int minX = bound.getMinimumPoint().getBlockX(); + int maxY = bound.getMaximumPoint().getBlockY(); int minY = bound.getMinimumPoint().getBlockY(); + int maxZ = bound.getMaximumPoint().getBlockZ(); int minZ = bound.getMinimumPoint().getBlockZ(); + + for( int z = minZ; z <= maxZ; z++ ) { + for(int y = minY; y <= maxY; y++) { + for(int x = minX; x <= maxX; x++) { + if(reg.contains(new Location( world(), x, y, z ))) { + r.add(vec( x, y, z )); + } + } + } + } + + return r; + } + + private CuboidRegion boundingBox() { + Set reg = this.regions().stream() + .map(r -> r.getBoundingCuboid()) + .collect(Collectors.toSet()); + + return reg.stream() + .reduce((a, b) -> CuboidRegion.combine(a, b)) + .orElse(null); + } + + public boolean isDungeonBound(Block b) { + return UNBREAKABLE_BLOCKS.contains(b.getType()) || + this.openings().stream().anyMatch(reg -> reg.containsBlock(b)); + } + + private Graph graph() { return this.graph; } + private HashBiMap vertices() { return this.vertices; } + private Object vertex(Vec3 vec) { return this.vertices().get(vec); } + private Vec3 vertexVec(Object obj) { return this.vertices().inverse().get(obj); } + + private World world() { return this.world; } + private Set regions() { return this.mapRegions; } + public Set openings() { + //return this.dungeonOpenings; + return this.regions().stream() + .filter(r -> r.getFlags().contains(Flag.DUNGEON_BOUNDARY)) + .collect(Collectors.toSet()); + } + public Set> connectedRegions() { return this.connectedRegions; } + + public boolean loaded() { return this.loaded; } + + public RegionGraph regions(Set regions) { this.mapRegions = regions; return this; } + private RegionGraph setLoaded(boolean loaded) { this.loaded = loaded; return this; } + public RegionGraph setDungeonOpenings(Set openings) { + this.dungeonOpenings = openings; return this; + } + + private void log(String msg) { + if(this.logger != null) this.logger.info( msg ); + } + + // Proper block in range + public static Location unbreakableInRange(Location loc, int radius) + { + Block b = loc.getBlock(); + int h = loc.getWorld().getMaxHeight(); + int by = loc.getBlockY(); + + for (int y = -radius; y <= radius; ++y) if (by + y >= 0 && by + y < h) + for (int x = -radius; x <= radius; ++x) + for (int z = -radius; z <= radius; ++z) + { + Block rel = b.getRelative(x, y, z); + if(UNBREAKABLE_BLOCKS.contains(rel.getType())) return rel.getLocation(); + //if (blockdata.matchesBlock(rel)) return rel.getLocation(); + } + + return null; + } + + public Set> fromInts(List>> list) { + return list.stream() + .map(l -> l.stream().map(v -> { + if(v.size() < 3) return null; + int v0 = (int) Math.round(v.get(0)); + int v1 = (int) Math.round(v.get(1)); + int v2 = (int) Math.round(v.get(2)); + return vec(v0, v1, v2); + }) + .filter(v -> v != null) + .collect(Collectors.toSet())).collect(Collectors.toSet()); + } +} diff --git a/src/main/java/org/mctourney/autoreferee/util/MathUtil.java b/src/main/java/org/mctourney/autoreferee/util/MathUtil.java new file mode 100644 index 00000000..feb5fd32 --- /dev/null +++ b/src/main/java/org/mctourney/autoreferee/util/MathUtil.java @@ -0,0 +1,7 @@ +package org.mctourney.autoreferee.util; + +public class MathUtil { + public static double dist( double x, double y, double z, double x0, double y0, double z0 ) { + return Math.sqrt( Math.pow(x - x0, 2) + Math.pow(y - y0, 2) + Math.pow(z - z0, 2) ); + } +} diff --git a/src/main/java/org/mctourney/autoreferee/util/Vec3.java b/src/main/java/org/mctourney/autoreferee/util/Vec3.java new file mode 100644 index 00000000..81c65bfe --- /dev/null +++ b/src/main/java/org/mctourney/autoreferee/util/Vec3.java @@ -0,0 +1,52 @@ +package org.mctourney.autoreferee.util; + +import java.util.List; +import java.util.Set; + +import org.bukkit.Location; +import org.bukkit.World; + +//helper class, needed as keys for hashmap +public class Vec3 { + private int x; + private int y; + private int z; + private int maxX; + private int maxY; + private int maxZ; + private int minX; + private int minY; + private int minZ; + + //public Vec3() { } + public Vec3(int x, int y, int z, int maxX, int maxY, int maxZ, int minX, int minY, int minZ) + { this.x = x; this.y = y; this.z = z; this.maxX = maxX; this.maxY = maxY; this.maxZ = maxZ; } + + public int x() { return this.x; } + public int y() { return this.y; } + public int z() { return this.z; } + + public Vec3 x(int x) { this.x = this.minX + ( (x - this.minX) % (this.width() + 1)); return this; } + public Vec3 y(int y) { this.y = this.minY + ( (y - this.minY) % (this.length() + 1)); return this; } + public Vec3 z(int z) { this.z = this.minZ + ( (z - this.minZ) % (this.height() + 1)); return this; } + + private int width() { return this.maxX - this.minX; } + private int length() { return this.maxY - this.minY; } + private int height() { return this.maxZ - this.minZ; } + + public Location loc(World w) { return new Location(w, x(), y(), z()); } + + @Override + public boolean equals(Object obj) { + if(obj == this) return true; + if(!(obj instanceof Vec3)) return false; + Vec3 vec = (Vec3) obj; + + return (this.x() == vec.x() && this.y() == vec.y() && this.z() == vec.z()); + } + + @Override + public int hashCode() { + return ( x() - this.minX ) + (( y() - this.minY ) * ( this.width() + 1 )) + ((z() - this.minZ) * ( this.width() + 1 ) * (this.length() + 1)); + } +}