Last active
November 12, 2017 14:35
-
-
Save Cryptite/5fe65cd5cb67991f1e0afead35071d5b to your computer and use it in GitHub Desktop.
Hopper Burnout - Proof of Concept for turning off Hoppers when they try to move too many items
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.bukkit.Location; | |
import org.bukkit.Particle; | |
import org.bukkit.Sound; | |
import org.bukkit.block.Block; | |
import org.bukkit.block.BlockState; | |
import org.bukkit.entity.Player; | |
import org.bukkit.event.EventHandler; | |
import org.bukkit.event.EventPriority; | |
import org.bukkit.event.Listener; | |
import org.bukkit.event.block.BlockBreakEvent; | |
import org.bukkit.event.inventory.InventoryClickEvent; | |
import org.bukkit.event.inventory.InventoryMoveItemEvent; | |
import org.bukkit.event.inventory.InventoryPickupItemEvent; | |
import org.bukkit.event.inventory.InventoryType; | |
import org.bukkit.event.world.ChunkUnloadEvent; | |
import org.bukkit.inventory.Inventory; | |
import java.util.*; | |
import static org.bukkit.ChatColor.*; | |
public class HopperBurnout implements Listener { | |
private Map<StringChunk, Map<StringLocation, Hopper>> watchMap = new HashMap<>(); | |
private final Set<InventoryType> inventoryTypes = new HashSet<>(Arrays.asList(InventoryType.BREWING, | |
InventoryType.CHEST, | |
InventoryType.DISPENSER, | |
InventoryType.DROPPER, | |
InventoryType.FURNACE, | |
InventoryType.HOPPER)); | |
class Hopper { | |
private final StringLocation location; | |
private long firstItem; | |
private boolean burnedOut; | |
int itemsMoved; | |
Hopper(StringLocation location) { | |
this.location = location; | |
firstItem = System.currentTimeMillis(); | |
increment(); | |
} | |
boolean increment() { | |
itemsMoved++; | |
//If it's been a 1s since our first item transferred | |
if (System.currentTimeMillis() - firstItem > 1000) { | |
//Hopper Burnout tends to be over 50 items for a single chest, 100 for a double-chest. | |
if (itemsMoved > 50) { | |
System.out.println(YELLOW + "[Hoppers] " + RED + "BURNOUT" + YELLOW + | |
" with " + GREEN + itemsMoved + " ips" + YELLOW + " at " + location.getLocation().toString()); | |
setBurnedOut(true); | |
playBurnout(); | |
} else { | |
setBurnedOut(false); | |
} | |
} | |
return burnedOut; | |
} | |
private void playBurnout() { | |
Location blockLoc = location.getLocation().add(.5f, 0, .5f); | |
for (Player p : location.getWorld().getPlayers()) { | |
p.spawnParticle(Particle.SMOKE_NORMAL, blockLoc, 10, 0, 0, 0, .005f); | |
} | |
blockLoc.getWorld().playSound(blockLoc, Sound.BLOCK_FIRE_EXTINGUISH, .5f, 1); | |
} | |
void setBurnedOut(boolean burnedOut) { | |
itemsMoved = 0; | |
firstItem = System.currentTimeMillis(); | |
this.burnedOut = burnedOut; | |
} | |
} | |
public HopperBurnout() { | |
} | |
@EventHandler(priority = EventPriority.MONITOR) | |
public void onHopperMoveEvent(InventoryMoveItemEvent e) { | |
if (e.isCancelled()) return; | |
Inventory destination = e.getDestination(); | |
if (destination.getType().equals(InventoryType.HOPPER)) { | |
Block sourceBlock = destination.getLocation().getBlock(); | |
if (incrementBlock(sourceBlock.getLocation())) { | |
e.setCancelled(true); | |
} | |
} | |
} | |
@EventHandler(priority = EventPriority.MONITOR) | |
public void onHopperPickUpEvent(InventoryPickupItemEvent e) { | |
if (e.getInventory().getHolder() instanceof BlockState | |
&& inventoryTypes.contains(e.getInventory().getType())) | |
incrementBlock(((BlockState) e.getInventory().getHolder()).getLocation()); | |
} | |
@EventHandler(priority = EventPriority.MONITOR) | |
public void onChunkUnload(ChunkUnloadEvent e) { | |
StringChunk chunk = new StringChunk(e.getChunk()); | |
if (watchMap.remove(chunk) != null) { | |
System.out.println(YELLOW + "[Hoppers] Chunks: " + watchMap.size()); | |
} | |
} | |
@EventHandler(priority = EventPriority.MONITOR) | |
public void onBlockBreak(BlockBreakEvent e) { | |
StringChunk chunk = new StringChunk(e.getBlock().getChunk()); | |
if (watchMap.containsKey(chunk)) { | |
//Breaking a hopper block should remove the hopper from the watch map. | |
watchMap.get(chunk).remove(new StringLocation(e.getBlock())); | |
} | |
} | |
@EventHandler | |
public void onInventoryClicked(InventoryClickEvent e) { | |
if (!(e.getInventory().getHolder() instanceof BlockState)) return; | |
Location l = ((BlockState) e.getInventory().getHolder()).getLocation(); | |
Hopper hopper = getHopper(l); | |
if (hopper != null) { | |
//Doing any modifications to the hopper's inventory should reset it so that it | |
//can see if it's still burning out | |
hopper.setBurnedOut(false); | |
} | |
} | |
private Hopper getHopper(Location l) { | |
StringChunk chunk = new StringChunk(l.getChunk()); | |
if (watchMap.containsKey(chunk)) { | |
return watchMap.get(chunk).getOrDefault(new StringLocation(l), null); | |
} | |
return null; | |
} | |
private boolean incrementBlock(Location l) { | |
StringChunk chunk = new StringChunk(l.getChunk()); | |
StringLocation loc = new StringLocation(l); | |
Hopper hopper; | |
Map<StringLocation, Hopper> map; | |
if (watchMap.containsKey(chunk)) { | |
map = watchMap.get(chunk); | |
hopper = map.getOrDefault(loc, new Hopper(loc)); | |
map.put(loc, hopper); | |
} else { | |
map = new HashMap<>(); | |
hopper = new Hopper(loc); | |
System.out.println(YELLOW + "[Hoppers] Registered new chunk: " + chunk.toString()); | |
} | |
boolean burntOut = hopper.increment(); | |
map.put(loc, hopper); | |
watchMap.put(chunk, map); | |
return burntOut; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.bukkit.Bukkit; | |
import org.bukkit.Chunk; | |
import java.util.Objects; | |
public class StringChunk { | |
public String world; | |
public int x, z; | |
public StringChunk(Chunk c) { | |
this.world = c.getWorld().getName(); | |
this.x = c.getX(); | |
this.z = c.getZ(); | |
} | |
public StringChunk(String args) { | |
String[] argsSplit = args.split(","); | |
this.world = argsSplit[0]; | |
this.x = Integer.parseInt(argsSplit[1]); | |
this.z = Integer.parseInt(argsSplit[2]); | |
} | |
public Chunk getChunk() { | |
return Bukkit.getWorld(world).getChunkAt(x, z); | |
} | |
public boolean isLoaded() { | |
return Bukkit.getWorld(world).isChunkLoaded(x, z); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(world, x, z); | |
} | |
@Override | |
public boolean equals(Object other) { | |
if (other instanceof Chunk) { | |
Chunk o = (Chunk) other; | |
return x == o.getX() && z == o.getZ() && world.equals(o.getWorld().getName()); | |
} else if (other instanceof StringChunk) { | |
StringChunk o = (StringChunk) other; | |
return x == o.x && z == o.z && world.equals(o.world); | |
} | |
return false; | |
} | |
@Override | |
public String toString() { | |
return world + "," + x + "," + z; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class StringLocation { | |
public String world; | |
public double x, y, z; | |
private float yaw; | |
private float pitch; | |
public StringLocation(Block b) { | |
this.world = b.getWorld().getName(); | |
this.x = b.getX(); | |
this.y = b.getY(); | |
this.z = b.getZ(); | |
} | |
public StringLocation(Location l) { | |
this.world = l.getWorld().getName(); | |
this.x = l.getBlockX(); | |
this.y = l.getBlockY(); | |
this.z = l.getBlockZ(); | |
this.yaw = l.getYaw(); | |
this.pitch = l.getPitch(); | |
} | |
public StringLocation(String string) { | |
if (string == null) return; | |
String[] elems = string.split(","); | |
world = elems[0]; | |
x = Double.parseDouble(elems[1]); | |
y = Double.parseDouble(elems[2]); | |
z = Double.parseDouble(elems[3]); | |
yaw = elems.length > 4 ? Float.parseFloat(elems[4]) : 0f; | |
pitch = elems.length > 5 ? Float.parseFloat(elems[5]) : 0f; | |
} | |
public Chunk getChunk() { | |
return Bukkit.getWorld(world).getChunkAt(getLocation()); | |
} | |
public Location getLocation() { | |
if (yaw != 0 || pitch != 0) { | |
return new Location(getWorld(), x, y, z, yaw, pitch); | |
} | |
return new Location(getWorld(), x, y, z); | |
} | |
public void add(int x, int y, int z) { | |
this.x += x; | |
this.y += y; | |
this.z += z; | |
} | |
public void subtract(int x, int y, int z) { | |
this.x -= x; | |
this.y -= y; | |
this.z -= z; | |
} | |
public boolean equals(Location l) { | |
World world = l.getWorld(); | |
return this.world != null | |
&& world != null | |
&& this.world.equals(world.getName()) | |
&& Double.doubleToLongBits(this.x) == Double.doubleToLongBits(l.getX()) | |
&& Double.doubleToLongBits(this.y) == Double.doubleToLongBits(l.getY()) | |
&& Double.doubleToLongBits(this.z) == Double.doubleToLongBits(l.getZ()) | |
&& Float.floatToIntBits(this.pitch) == Float.floatToIntBits(l.getPitch()) | |
&& Float.floatToIntBits(this.yaw) == Float.floatToIntBits(l.getYaw()); | |
} | |
public boolean looseEquals(Location l) { | |
return getBlockX() == l.getBlockX() | |
&& getBlockY() == l.getBlockY() | |
&& getBlockZ() == l.getBlockZ(); | |
} | |
public Location getCenter() { | |
return getBlock().getLocation().add(.5, .5, .5); | |
} | |
public Block getBlock() { | |
return world != null | |
? Bukkit.getWorld(world).getBlockAt((int) x, (int) y, (int) z) | |
: Bukkit.getWorld("world").getBlockAt(0, 0, 0); | |
} | |
Material getType() { | |
return getBlock().getType(); | |
} | |
void setType(Material m) { | |
getBlock().setType(m); | |
} | |
public int getBlockX() { | |
return (int) x; | |
} | |
public int getBlockY() { | |
return (int) y; | |
} | |
public int getBlockZ() { | |
return (int) z; | |
} | |
public void setX(int x) { | |
this.x = x; | |
} | |
public void setY(int y) { | |
this.y = y; | |
} | |
public void setZ(int z) { | |
this.z = z; | |
} | |
public void setPitch(float pitch) { | |
this.pitch = pitch; | |
} | |
public void setYaw(float yaw) { | |
this.yaw = yaw; | |
} | |
public float getPitch() { | |
return pitch; | |
} | |
public float getYaw() { | |
return yaw; | |
} | |
public double distance(Location location) { | |
return getLocation().distance(location); | |
} | |
public double distance(StringLocation loc) { | |
return Math.sqrt(square(this.x - loc.x) + square(this.y - loc.y) + square(this.z - loc.z)); | |
} | |
public org.bukkit.World getWorld() { | |
return Bukkit.getWorld(world); | |
} | |
public Vector toVector() { | |
return new Vector(getBlockX(), getBlockY(), getBlockZ()); | |
} | |
public void setLocation(Location l) { | |
world = l.getWorld().getName(); | |
x = (int) l.getX(); | |
y = (int) l.getY(); | |
z = (int) l.getZ(); | |
yaw = l.getYaw(); | |
pitch = l.getPitch(); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(world, x, y, z, yaw, pitch); | |
} | |
@Override | |
public boolean equals(Object other) { | |
if (!(other instanceof StringLocation)) { | |
return false; | |
} | |
StringLocation o = (StringLocation) other; | |
return x == o.x && y == o.y && z == o.z && | |
world.equals(o.world); | |
} | |
@Override | |
public String toString() { | |
return LocationUtil.coordsToString(this); | |
} | |
public String toStringBasic() { | |
return LocationUtil.coordsToStringBasic(getLocation()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment