diff --git a/src/balor/OpenInv/ACOfflinePlayerInventory.java b/src/balor/OpenInv/ACOfflinePlayerInventory.java new file mode 100644 index 0000000..2b16f10 --- /dev/null +++ b/src/balor/OpenInv/ACOfflinePlayerInventory.java @@ -0,0 +1,52 @@ +/************************************************************************ + * This file is part of AdminCmd. + * + * AdminCmd is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AdminCmd is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AdminCmd. If not, see . + ************************************************************************/ +package balor.OpenInv; + +import org.bukkit.craftbukkit.entity.CraftHumanEntity; +import org.bukkit.entity.Player; + +/** + * @author Balor (aka Antoine Aflalo) + * + */ +public class ACOfflinePlayerInventory extends ACPlayerInventory { + + /** + * @param entityhuman + * @param proprietary + */ + ACOfflinePlayerInventory(final Player proprietary) { + super(proprietary); + + } + + /* + * (non-Javadoc) + * + * @see + * net.minecraft.server.PlayerInventory#onClose(org.bukkit.craftbukkit.entity + * .CraftHumanEntity) + */ + @Override + public void onClose(final CraftHumanEntity who) { + transaction.remove(who); + if (transaction.isEmpty()) { + InventoryManager.INSTANCE.closeOfflineInv(proprietary); + } + } + +} diff --git a/src/balor/OpenInv/ACPlayerInventory.java b/src/balor/OpenInv/ACPlayerInventory.java new file mode 100644 index 0000000..c2d9037 --- /dev/null +++ b/src/balor/OpenInv/ACPlayerInventory.java @@ -0,0 +1,222 @@ +/** + * Copyright (C) 2011-2012 lishid. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package balor.OpenInv; + +import net.minecraft.server.EntityHuman; +import net.minecraft.server.ItemStack; +import net.minecraft.server.PlayerInventory; + +import org.bukkit.craftbukkit.entity.CraftHumanEntity; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; + +/** + * @author lishd (Modded by Balor) {@link https://github.com/lishd/OpenInv/} + * + */ +public class ACPlayerInventory extends PlayerInventory { + + public final ItemStack[] extra = new ItemStack[5]; + public final Player proprietary; + + /** + * @param entityhuman + */ + ACPlayerInventory(final Player proprietary) { + super(((CraftPlayer) proprietary).getHandle()); + this.proprietary = proprietary; + this.armor = player.inventory.armor; + this.items = player.inventory.items; + } + + /* + * (non-Javadoc) + * + * @see + * net.minecraft.server.PlayerInventory#onClose(org.bukkit.craftbukkit.entity + * .CraftHumanEntity) + */ + @Override + public void onClose(final CraftHumanEntity who) { + super.onClose(who); + if (transaction.isEmpty() && !proprietary.isOnline()) { + InventoryManager.INSTANCE.closeOfflineInv(proprietary); + } + } + + @Override + public ItemStack[] getContents() { + final ItemStack[] C = new ItemStack[getSize()]; + System.arraycopy(items, 0, C, 0, items.length); + System.arraycopy(armor, 0, C, items.length, armor.length); + return C; + } + + /* + * (non-Javadoc) + * + * @see net.minecraft.server.PlayerInventory#getSize() + */ + @Override + public int getSize() { + return super.getSize() + 5; + } + + @Override + public boolean a(final EntityHuman entityhuman) { + return true; + } + + @Override + public String getName() { + if (player.name.length() > 16) { + return player.name.substring(0, 16); + } + return player.name; + } + + private int getReversedItemSlotNum(final int i) { + if (i >= 27) { + return i - 27; + } else { + return i + 9; + } + } + + private int getReversedArmorSlotNum(final int i) { + if (i == 0) { + return 3; + } + if (i == 1) { + return 2; + } + if (i == 2) { + return 1; + } + if (i == 3) { + return 0; + } else { + return i; + } + } + + @Override + public ItemStack getItem(int i) { + ItemStack[] is = this.items; + + if (i >= is.length) { + i -= is.length; + is = this.armor; + } else { + i = getReversedItemSlotNum(i); + } + + if (i >= is.length) { + i -= is.length; + is = this.extra; + } else if (is == this.armor) { + i = getReversedArmorSlotNum(i); + } + + return is[i]; + } + + @Override + public ItemStack splitStack(int i, final int j) { + ItemStack[] is = this.items; + + if (i >= is.length) { + i -= is.length; + is = this.armor; + } else { + i = getReversedItemSlotNum(i); + } + + if (i >= is.length) { + i -= is.length; + is = this.extra; + } else if (is == this.armor) { + i = getReversedArmorSlotNum(i); + } + + if (is[i] != null) { + ItemStack itemstack; + + if (is[i].count <= j) { + itemstack = is[i]; + is[i] = null; + return itemstack; + } else { + itemstack = is[i].a(j); + if (is[i].count == 0) { + is[i] = null; + } + + return itemstack; + } + } else { + return null; + } + } + + @Override + public ItemStack splitWithoutUpdate(int i) { + ItemStack[] is = this.items; + + if (i >= is.length) { + i -= is.length; + is = this.armor; + } else { + i = getReversedItemSlotNum(i); + } + + if (i >= is.length) { + i -= is.length; + is = this.extra; + } else if (is == this.armor) { + i = getReversedArmorSlotNum(i); + } + + if (is[i] != null) { + final ItemStack itemstack = is[i]; + + is[i] = null; + return itemstack; + } else { + return null; + } + } + + @Override + public void setItem(int i, final ItemStack itemstack) { + ItemStack[] is = this.items; + + if (i >= is.length) { + i -= is.length; + is = this.armor; + } else { + i = getReversedItemSlotNum(i); + } + + if (i >= is.length) { + i -= is.length; + is = this.extra; + } else if (is == this.armor) { + i = getReversedArmorSlotNum(i); + } + is[i] = itemstack; + } +} diff --git a/src/balor/OpenInv/InventoryManager.java b/src/balor/OpenInv/InventoryManager.java new file mode 100644 index 0000000..1016e6d --- /dev/null +++ b/src/balor/OpenInv/InventoryManager.java @@ -0,0 +1,237 @@ +/************************************************************************ + * This file is part of AdminCmd. + * + * AdminCmd is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AdminCmd is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AdminCmd. If not, see . + ************************************************************************/ +package balor.OpenInv; + +import java.io.File; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import net.minecraft.server.EntityPlayer; +import net.minecraft.server.ItemInWorldManager; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.PlayerInventory; + +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.CraftInventoryPlayer; +import org.bukkit.entity.Player; + +import com.google.common.collect.MapMaker; + +/** + * @author Balor (aka Antoine Aflalo) + * + */ +public class InventoryManager { + public static InventoryManager INSTANCE; + private final Map replacedInv = new MapMaker().makeMap(); + private final Map offlineInv = new MapMaker().makeMap(); + + /** + * + */ + private InventoryManager() { + } + + public static void createInstance() { + if (INSTANCE == null) { + INSTANCE = new InventoryManager(); + } + } + + public void onQuit(final Player p) { + replacedInv.remove(p); + } + + void closeOfflineInv(final Player p) { + onQuit(p); + offlineInv.remove(p.getName()); + p.saveData(); + } + + public void onJoin(Player p) { + ACPlayerInventory inv = offlineInv.get(p.getName()); + if (inv == null) { + return; + } + if (inv instanceof ACOfflinePlayerInventory) { + CraftPlayer cp = (CraftPlayer) p; + PlayerInventory mcInv = ((CraftInventoryPlayer) cp.getInventory()).getInventory(); + mcInv.items = inv.items; + mcInv.armor = inv.armor; + } + } + + /** + * Open the inventory of an offline player + * + * @param sender + * @param name + * @throws PlayerNotFound + * @author lishd {@link https + * ://github.com/lishd/OpenInv/blob/master/src/lishid + * /openinv/commands/OpenInvPluginCommand.java} + * @throws WorldNotFoundException + */ + public Player openOfflineInv(final Player sender, final String name, final String world) + throws PlayerNotFound, WorldNotFoundException { + Player target = null; + final HashMap replace = new HashMap(); + replace.put("player", name); + // Offline inv here... + // See if the player has data files + + // Find the player folder + World worldFound = matchWorld(Bukkit.getWorlds(), world); + if (worldFound == null) + throw new WorldNotFoundException(ChatColor.RED + "The World " + ChatColor.GOLD + world + + ChatColor.RED + " can't be found."); + final File playerfolder = new File(worldFound.getWorldFolder(), "players"); + if (!playerfolder.exists()) { + throw new PlayerNotFound(ChatColor.RED + "The player " + ChatColor.GOLD + name + + ChatColor.RED + " can't be found."); + } + + final String playername = matchUser(Arrays.asList(playerfolder.listFiles()), name); + if (playername == null) { + throw new PlayerNotFound(ChatColor.RED + "The player " + ChatColor.GOLD + name + + ChatColor.RED + " can't be found."); + } + + // Create an entity to load the player data + final MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer(); + final EntityPlayer entity = new EntityPlayer(server, server.getWorldServer(0), playername, + new ItemInWorldManager(server.getWorldServer(0))); + target = (entity == null) ? null : (Player) entity.getBukkitEntity(); + if (target != null) { + target.loadData(); + } else { + throw new PlayerNotFound(ChatColor.RED + "The player " + ChatColor.GOLD + name + + ChatColor.RED + " can't be found."); + } + openInv(sender, target, true); + return target; + } + + /** + * Open the inventory of the connected player + * + * @param sender + * the user who'll see the inventory + * @param target + * player to have his inventory opened + */ + public void openInv(final Player sender, final Player target) { + openInv(sender, target, false); + } + + private void openInv(final Player sender, final Player target, final boolean offline) { + // Permissions checks + if (!sender.hasPermission("OpenInv.override") && target.hasPermission("OpenInv.exempt")) { + sender.sendMessage(ChatColor.RED + target.getDisplayName() + + "'s inventory is protected!"); + return; + } + + if ((!sender.hasPermission("OpenInv.crossworld") && !sender + .hasPermission("OpenInv.override")) && target.getWorld() != sender.getWorld()) { + sender.sendMessage(ChatColor.RED + target.getDisplayName() + " is not in your world!"); + return; + } + final ACPlayerInventory inventory = getInventory(target, offline); + final EntityPlayer eh = ((CraftPlayer) sender).getHandle(); + eh.openContainer(inventory); + } + + private ACPlayerInventory getInventory(final Player player, final boolean offline) { + ACPlayerInventory inventory = replacedInv.get(player); + if (inventory == null) { + if (offline) { + inventory = new ACOfflinePlayerInventory(player); + offlineInv.put(player.getName(), inventory); + } else { + inventory = new ACPlayerInventory(player); + } + } + return inventory; + + } + + private String matchUser(final Collection container, final String search) { + String found = null; + if (search == null) { + return found; + } + final String lowerSearch = search.toLowerCase(); + int delta = Integer.MAX_VALUE; + for (final File file : container) { + final String filename = file.getName(); + final String str = filename.substring(0, filename.length() - 4); + if (!str.toLowerCase().startsWith(lowerSearch)) { + continue; + } + final int curDelta = str.length() - lowerSearch.length(); + if (curDelta < delta) { + found = str; + delta = curDelta; + } + if (curDelta == 0) { + break; + } + + } + return found; + + } + + /** + * Search for the given string in the list and return it. + * + * @param container + * @param search + * @return + */ + private World matchWorld(final Collection container, final String search) { + World found = null; + if (search == null) { + return found; + } + final String lowerSearch = search.toLowerCase(); + int delta = Integer.MAX_VALUE; + for (final World w : container) { + String str = w.getName(); + if (str.toLowerCase().startsWith(lowerSearch)) { + final int curDelta = str.length() - lowerSearch.length(); + if (curDelta < delta) { + found = w; + delta = curDelta; + } + if (curDelta == 0) { + break; + } + } + } + return found; + + } + +} diff --git a/src/balor/OpenInv/PlayerNotFound.java b/src/balor/OpenInv/PlayerNotFound.java new file mode 100644 index 0000000..3b69df9 --- /dev/null +++ b/src/balor/OpenInv/PlayerNotFound.java @@ -0,0 +1,38 @@ +/************************************************************************ + * This file is part of AdminCmd. + * + * AdminCmd is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AdminCmd is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AdminCmd. If not, see . + ************************************************************************/ +package balor.OpenInv; + + +/** + * @author Balor (aka Antoine Aflalo) + * + */ +public class PlayerNotFound extends Exception { + + /** + * + */ + private static final long serialVersionUID = -6841087146104592092L; + + /** + * @param message + */ + public PlayerNotFound(final String message) { + super(message); + } + +} diff --git a/src/balor/OpenInv/WorldNotFoundException.java b/src/balor/OpenInv/WorldNotFoundException.java new file mode 100644 index 0000000..c4a5153 --- /dev/null +++ b/src/balor/OpenInv/WorldNotFoundException.java @@ -0,0 +1,62 @@ +/************************************************************************ + * This file is part of OpenInv. + * + * OpenInv is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * OpenInv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OpenInv. If not, see . + ************************************************************************/ +package balor.OpenInv; + +/** + * @author Balor (aka Antoine Aflalo) + * + */ +public class WorldNotFoundException extends Exception { + + /** + * + */ + private static final long serialVersionUID = 1L; + + /** + * + */ + public WorldNotFoundException() { + // TODO Auto-generated constructor stub + } + + /** + * @param message + */ + public WorldNotFoundException(String message) { + super(message); + // TODO Auto-generated constructor stub + } + + /** + * @param cause + */ + public WorldNotFoundException(Throwable cause) { + super(cause); + // TODO Auto-generated constructor stub + } + + /** + * @param message + * @param cause + */ + public WorldNotFoundException(String message, Throwable cause) { + super(message, cause); + // TODO Auto-generated constructor stub + } + +} diff --git a/src/lishid/openinv/OpenInv.java b/src/lishid/openinv/OpenInv.java index 57c46e1..fc00b40 100644 --- a/src/lishid/openinv/OpenInv.java +++ b/src/lishid/openinv/OpenInv.java @@ -16,7 +16,11 @@ package lishid.openinv; -import lishid.openinv.commands.*; +import lishid.openinv.commands.AnyChestPluginCommand; +import lishid.openinv.commands.OpenInvPluginCommand; +import lishid.openinv.commands.SearchInvPluginCommand; +import lishid.openinv.commands.SilentChestPluginCommand; +import lishid.openinv.commands.ToggleOpenInvPluginCommand; import lishid.openinv.utils.Metrics; import org.bukkit.ChatColor; @@ -25,6 +29,8 @@ import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; +import balor.OpenInv.InventoryManager; + /** * Open other player's inventory * @@ -45,6 +51,7 @@ public class OpenInv extends JavaPlugin { mainPlugin.getConfig().options().copyDefaults(true); mainPlugin.saveConfig(); + InventoryManager.createInstance(); PluginManager pm = getServer().getPluginManager(); pm.registerEvents(playerListener, this); pm.registerEvents(entityListener, this); diff --git a/src/lishid/openinv/OpenInvEntityListener.java b/src/lishid/openinv/OpenInvEntityListener.java index 9d3bac9..b763dd6 100644 --- a/src/lishid/openinv/OpenInvEntityListener.java +++ b/src/lishid/openinv/OpenInvEntityListener.java @@ -18,11 +18,11 @@ package lishid.openinv; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; public class OpenInvEntityListener implements Listener{ OpenInv plugin; diff --git a/src/lishid/openinv/OpenInvPlayerListener.java b/src/lishid/openinv/OpenInvPlayerListener.java index e741339..7a6c60c 100644 --- a/src/lishid/openinv/OpenInvPlayerListener.java +++ b/src/lishid/openinv/OpenInvPlayerListener.java @@ -18,14 +18,12 @@ package lishid.openinv; import java.lang.reflect.Field; -import lishid.openinv.commands.OpenInvPluginCommand; import lishid.openinv.utils.SilentContainerChest; import net.minecraft.server.Block; import net.minecraft.server.EntityPlayer; import net.minecraft.server.IInventory; import net.minecraft.server.InventoryLargeChest; import net.minecraft.server.Packet100OpenWindow; -import net.minecraft.server.Packet101CloseWindow; import net.minecraft.server.TileEntityChest; import net.minecraft.server.World; @@ -34,176 +32,185 @@ import org.bukkit.block.Chest; import org.bukkit.block.Sign; import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; +import org.bukkit.event.Event.Result; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; -import org.bukkit.event.Event.Result; import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerLoginEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; -public class OpenInvPlayerListener implements Listener{ +import balor.OpenInv.InventoryManager; + +public class OpenInvPlayerListener implements Listener { OpenInv plugin; + public OpenInvPlayerListener(OpenInv scrap) { plugin = scrap; } @EventHandler(priority = EventPriority.LOWEST) - public void onPlayerLogin(PlayerLoginEvent event) - { - try{ - for(Player target : OpenInvPluginCommand.offlineInv.keySet()) - { - if(target.getName().equalsIgnoreCase(event.getPlayer().getName())) - { - ((CraftPlayer)OpenInvPluginCommand.offlineInv.get(target).Opener).getHandle().netServerHandler.sendPacket(new Packet101CloseWindow()); - target.saveData(); - OpenInvPluginCommand.offlineInv.remove(target); - event.getPlayer().loadData(); - return; - } - } - } - catch(Exception e){} + public void onJoin(PlayerJoinEvent event) { + InventoryManager.INSTANCE.onJoin(event.getPlayer()); } @EventHandler(priority = EventPriority.MONITOR) - public void onPlayerInteract(PlayerInteractEvent event) - { - if(event.getAction() == Action.RIGHT_CLICK_BLOCK && event.useInteractedBlock() == Result.DENY) + public void onPlayerInteract(PlayerInteractEvent event) { + if (event.getAction() == Action.RIGHT_CLICK_BLOCK + && event.useInteractedBlock() == Result.DENY) return; - - if(event.getAction() == Action.RIGHT_CLICK_BLOCK && event.getClickedBlock().getState() instanceof Chest) - { + + if (event.getAction() == Action.RIGHT_CLICK_BLOCK + && event.getClickedBlock().getState() instanceof Chest) { boolean silentchest = false; boolean anychest = false; int x = event.getClickedBlock().getX(); int y = event.getClickedBlock().getY(); int z = event.getClickedBlock().getZ(); - - if(event.getPlayer().hasPermission("OpenInv.silent") && OpenInv.GetPlayerSilentChestStatus(event.getPlayer().getName())) - { + + if (event.getPlayer().hasPermission("OpenInv.silent") + && OpenInv.GetPlayerSilentChestStatus(event.getPlayer().getName())) { silentchest = true; } - - if(event.getPlayer().hasPermission("OpenInv.anychest") && OpenInv.GetPlayerAnyChestStatus(event.getPlayer().getName())) - { - try - { - //FOR REFERENCE, LOOK AT net.minecraft.server.BlockChest - EntityPlayer player = ((CraftPlayer)event.getPlayer()).getHandle(); + + if (event.getPlayer().hasPermission("OpenInv.anychest") + && OpenInv.GetPlayerAnyChestStatus(event.getPlayer().getName())) { + try { + // FOR REFERENCE, LOOK AT net.minecraft.server.BlockChest + EntityPlayer player = ((CraftPlayer) event.getPlayer()).getHandle(); World world = player.world; - //If block on top - if(world.e(x, y + 1, z)) + // If block on top + if (world.e(x, y + 1, z)) anychest = true; - - //If block next to chest is chest and has a block on top - if ((world.getTypeId(x - 1, y, z) == Block.CHEST.id) && (world.e(x - 1, y + 1, z))) - anychest = true; - if ((world.getTypeId(x + 1, y, z) == Block.CHEST.id) && (world.e(x + 1, y + 1, z))) - anychest = true; - if ((world.getTypeId(x, y, z - 1) == Block.CHEST.id) && (world.e(x, y + 1, z - 1))) - anychest = true; - if ((world.getTypeId(x, y, z + 1) == Block.CHEST.id) && (world.e(x, y + 1, z + 1))) - anychest = true; - } - catch(Exception e) - { - event.getPlayer().sendMessage(ChatColor.RED + "Error while executing openinv. Unsupported CraftBukkit."); + + // If block next to chest is chest and has a block on top + if ((world.getTypeId(x - 1, y, z) == Block.CHEST.id) + && (world.e(x - 1, y + 1, z))) + anychest = true; + if ((world.getTypeId(x + 1, y, z) == Block.CHEST.id) + && (world.e(x + 1, y + 1, z))) + anychest = true; + if ((world.getTypeId(x, y, z - 1) == Block.CHEST.id) + && (world.e(x, y + 1, z - 1))) + anychest = true; + if ((world.getTypeId(x, y, z + 1) == Block.CHEST.id) + && (world.e(x, y + 1, z + 1))) + anychest = true; + } catch (Exception e) { + event.getPlayer().sendMessage( + ChatColor.RED + + "Error while executing openinv. Unsupported CraftBukkit."); e.printStackTrace(); } } - - //If the anychest or silentchest is active - if(anychest || silentchest) - { - EntityPlayer player = ((CraftPlayer)event.getPlayer()).getHandle(); + + // If the anychest or silentchest is active + if (anychest || silentchest) { + EntityPlayer player = ((CraftPlayer) event.getPlayer()).getHandle(); World world = player.world; - Object chest = (TileEntityChest)world.getTileEntity(x, y, z); - if (chest == null) return; - - if(!anychest) - { - if (world.e(x, y + 1, z)) return; - if ((world.getTypeId(x - 1, y, z) == Block.CHEST.id) && (world.e(x - 1, y + 1, z))) return; - if ((world.getTypeId(x + 1, y, z) == Block.CHEST.id) && (world.e(x + 1, y + 1, z))) return; - if ((world.getTypeId(x, y, z - 1) == Block.CHEST.id) && (world.e(x, y + 1, z - 1))) return; - if ((world.getTypeId(x, y, z + 1) == Block.CHEST.id) && (world.e(x, y + 1, z + 1))) return; - } - - if (world.getTypeId(x - 1, y, z) == Block.CHEST.id) chest = new InventoryLargeChest("Large chest", (TileEntityChest)world.getTileEntity(x - 1, y, z), (IInventory)chest); - if (world.getTypeId(x + 1, y, z) == Block.CHEST.id) chest = new InventoryLargeChest("Large chest", (IInventory)chest, (TileEntityChest)world.getTileEntity(x + 1, y, z)); - if (world.getTypeId(x, y, z - 1) == Block.CHEST.id) chest = new InventoryLargeChest("Large chest", (TileEntityChest)world.getTileEntity(x, y, z - 1), (IInventory)chest); - if (world.getTypeId(x, y, z + 1) == Block.CHEST.id) chest = new InventoryLargeChest("Large chest", (IInventory)chest, (TileEntityChest)world.getTileEntity(x, y, z + 1)); - - if(!silentchest) - { - player.openContainer((IInventory)chest); - } - else - { - try{ - int id = 0; - try{ - Field windowID = player.getClass().getDeclaredField("containerCounter"); - windowID.setAccessible(true); - id = windowID.getInt(player); - id = id % 100 + 1; - windowID.setInt(player, id); - } - catch(NoSuchFieldException e) - { } - - player.netServerHandler.sendPacket(new Packet100OpenWindow(id, 0, ((IInventory)chest).getName(), ((IInventory)chest).getSize())); - player.activeContainer = new SilentContainerChest(player.inventory, ((IInventory)chest)); - player.activeContainer.windowId = id; - player.activeContainer.addSlotListener(player); - //event.getPlayer().sendMessage("You are opening a chest silently."); - event.setUseInteractedBlock(Result.DENY); - event.setCancelled(true); - } - catch(Exception e) - { + Object chest = (TileEntityChest) world.getTileEntity(x, y, z); + if (chest == null) + return; + + if (!anychest) { + if (world.e(x, y + 1, z)) + return; + if ((world.getTypeId(x - 1, y, z) == Block.CHEST.id) + && (world.e(x - 1, y + 1, z))) + return; + if ((world.getTypeId(x + 1, y, z) == Block.CHEST.id) + && (world.e(x + 1, y + 1, z))) + return; + if ((world.getTypeId(x, y, z - 1) == Block.CHEST.id) + && (world.e(x, y + 1, z - 1))) + return; + if ((world.getTypeId(x, y, z + 1) == Block.CHEST.id) + && (world.e(x, y + 1, z + 1))) + return; + } + + if (world.getTypeId(x - 1, y, z) == Block.CHEST.id) + chest = new InventoryLargeChest("Large chest", + (TileEntityChest) world.getTileEntity(x - 1, y, z), (IInventory) chest); + if (world.getTypeId(x + 1, y, z) == Block.CHEST.id) + chest = new InventoryLargeChest("Large chest", (IInventory) chest, + (TileEntityChest) world.getTileEntity(x + 1, y, z)); + if (world.getTypeId(x, y, z - 1) == Block.CHEST.id) + chest = new InventoryLargeChest("Large chest", + (TileEntityChest) world.getTileEntity(x, y, z - 1), (IInventory) chest); + if (world.getTypeId(x, y, z + 1) == Block.CHEST.id) + chest = new InventoryLargeChest("Large chest", (IInventory) chest, + (TileEntityChest) world.getTileEntity(x, y, z + 1)); + + if (!silentchest) { + player.openContainer((IInventory) chest); + } else { + try { + int id = 0; + try { + Field windowID = player.getClass().getDeclaredField("containerCounter"); + windowID.setAccessible(true); + id = windowID.getInt(player); + id = id % 100 + 1; + windowID.setInt(player, id); + } catch (NoSuchFieldException e) { + } + + player.netServerHandler.sendPacket(new Packet100OpenWindow(id, 0, + ((IInventory) chest).getName(), ((IInventory) chest).getSize())); + player.activeContainer = new SilentContainerChest(player.inventory, + ((IInventory) chest)); + player.activeContainer.windowId = id; + player.activeContainer.addSlotListener(player); + // event.getPlayer().sendMessage("You are opening a chest silently."); + event.setUseInteractedBlock(Result.DENY); + event.setCancelled(true); + } catch (Exception e) { e.printStackTrace(); - event.getPlayer().sendMessage(ChatColor.RED + "Error while sending silent chest."); + event.getPlayer().sendMessage( + ChatColor.RED + "Error while sending silent chest."); } - } - - if(anychest) - event.getPlayer().sendMessage("You are opening a blocked chest."); - } + } + + if (anychest) + event.getPlayer().sendMessage("You are opening a blocked chest."); + } } - - if(event.getAction() == Action.RIGHT_CLICK_BLOCK && event.getClickedBlock().getState() instanceof Sign) - { + + if (event.getAction() == Action.RIGHT_CLICK_BLOCK + && event.getClickedBlock().getState() instanceof Sign) { Player player = event.getPlayer(); - try{ - Sign sign = ((Sign)event.getClickedBlock().getState()); - if (player.hasPermission("OpenInv.openinv") && sign.getLine(0).equalsIgnoreCase("[openinv]")) - { - String text = sign.getLine(1).trim() + sign.getLine(2).trim() + sign.getLine(3).trim(); + try { + Sign sign = ((Sign) event.getClickedBlock().getState()); + if (player.hasPermission("OpenInv.openinv") + && sign.getLine(0).equalsIgnoreCase("[openinv]")) { + String text = sign.getLine(1).trim() + sign.getLine(2).trim() + + sign.getLine(3).trim(); player.performCommand("openinv " + text); } - } - catch(Exception ex) - { + } catch (Exception ex) { player.sendMessage("Internal Error."); ex.printStackTrace(); } } - - if(event.getAction() == Action.RIGHT_CLICK_AIR || event.getAction() == Action.RIGHT_CLICK_BLOCK) - { + + if (event.getAction() == Action.RIGHT_CLICK_AIR + || event.getAction() == Action.RIGHT_CLICK_BLOCK) { Player player = event.getPlayer(); - if(!(player.getItemInHand().getType().getId() == OpenInv.GetItemOpenInvItem()) - || (!OpenInv.GetPlayerItemOpenInvStatus(player.getName())) - || !player.hasPermission("OpenInv.openinv")) - { + if (!(player.getItemInHand().getType().getId() == OpenInv.GetItemOpenInvItem()) + || (!OpenInv.GetPlayerItemOpenInvStatus(player.getName())) + || !player.hasPermission("OpenInv.openinv")) { return; } - + player.performCommand("openinv"); } } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + InventoryManager.INSTANCE.onQuit(event.getPlayer()); + } } \ No newline at end of file diff --git a/src/lishid/openinv/commands/OpenInvPluginCommand.java b/src/lishid/openinv/commands/OpenInvPluginCommand.java index eaa21e4..7c185c9 100644 --- a/src/lishid/openinv/commands/OpenInvPluginCommand.java +++ b/src/lishid/openinv/commands/OpenInvPluginCommand.java @@ -16,172 +16,88 @@ package lishid.openinv.commands; -import java.io.File; import java.util.HashMap; import lishid.openinv.OpenInv; -import lishid.openinv.utils.PlayerInventoryChest; -import net.minecraft.server.EntityPlayer; -import net.minecraft.server.ItemInWorldManager; -import net.minecraft.server.MinecraftServer; - -import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; -import org.bukkit.craftbukkit.CraftServer; -import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; +import balor.OpenInv.InventoryManager; +import balor.OpenInv.PlayerNotFound; +import balor.OpenInv.WorldNotFoundException; + public class OpenInvPluginCommand implements CommandExecutor { - private final OpenInv plugin; - public static HashMap offlineInv = new HashMap(); - public static HashMap openInvHistory = new HashMap(); - public OpenInvPluginCommand(OpenInv plugin) { - this.plugin = plugin; - } - - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - if(!(sender instanceof Player)) - { - sender.sendMessage(ChatColor.RED + "You can't use this from the console."); - return true; - } - if (!sender.hasPermission("OpenInv.openinv")) { - sender.sendMessage(ChatColor.RED + "You do not have permission to access player inventories"); - return true; - } - - if(args.length > 0 && args[0].equalsIgnoreCase("?")) - { - OpenInv.ShowHelp((Player)sender); - return true; - } - - Player player = (Player)sender; - boolean Offline = false; - - //History management + private final OpenInv plugin; + public static HashMap openInvHistory = new HashMap(); + + public OpenInvPluginCommand(OpenInv plugin) { + this.plugin = plugin; + } + + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage(ChatColor.RED + "You can't use this from the console."); + return true; + } + if (!sender.hasPermission("OpenInv.openinv")) { + sender.sendMessage(ChatColor.RED + + "You do not have permission to access player inventories"); + return true; + } + + if (args.length > 0 && args[0].equalsIgnoreCase("?")) { + OpenInv.ShowHelp((Player) sender); + return true; + } + + Player player = (Player) sender; + + // History management String history = openInvHistory.get(player); - - if(history == null || history == "") - { + + if (history == null || history == "") { history = player.getName(); openInvHistory.put(player, history); } - //Target selecting + // Target selecting Player target; - + String name = ""; - - //Read from history if target is not named + + // Read from history if target is not named if (args.length < 1) { - if(history != null && history != "") - { + if (history != null && history != "") { name = history; - } - else - { + } else { sender.sendMessage(ChatColor.RED + "OpenInv history is empty!"); return true; } - } - else - { + } else { name = args[0]; } - + target = this.plugin.getServer().getPlayer(name); - - if(target == null) - { - //Offline inv here... - try{ - //See if the player has data files - - // Find the player folder - File playerfolder = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "players"); - - // Find player name - for (File playerfile : playerfolder.listFiles()) - { - String filename = playerfile.getName(); - String playername = filename.substring(0, filename.length() - 4); - - if(playername.trim().equalsIgnoreCase(name)) - { - //Create an entity to load the player data - MinecraftServer server = ((CraftServer)this.plugin.getServer()).getServer(); - EntityPlayer entity = new EntityPlayer(server, server.getWorldServer(0), playername, new ItemInWorldManager(server.getWorldServer(0))); - target = (entity == null) ? null : (Player) entity.getBukkitEntity(); - if(target != null) - { - Offline = true; - target.loadData(); - } - else - { - sender.sendMessage(ChatColor.RED + "Player not found!"); - return true; - } - } - } - if(!Offline) - { - sender.sendMessage(ChatColor.RED + "Player not found!"); - return true; - } + if (target == null) { + try { + target = InventoryManager.INSTANCE.openOfflineInv(player, name, player.getWorld().getName()); + } catch (PlayerNotFound e) { + sender.sendMessage(e.getMessage()); + } catch (WorldNotFoundException e) { + sender.sendMessage(e.getMessage()); } - catch(Exception e) - { - sender.sendMessage("Error while retrieving offline player data!"); - e.printStackTrace(); - return true; - } - } - - //Permissions checks - if (!player.hasPermission("OpenInv.override") && target.hasPermission("OpenInv.exempt")) { - sender.sendMessage(ChatColor.RED + target.getDisplayName() + "'s inventory is protected!"); - return true; - } - - if((!player.hasPermission("OpenInv.crossworld") && !player.hasPermission("OpenInv.override")) && - target.getWorld() != player.getWorld()){ - sender.sendMessage(ChatColor.RED + target.getDisplayName() + " is not in your world!"); - return true; + } else { + InventoryManager.INSTANCE.openInv(player, target); } - //Record the target + // Record the target history = target.getName(); openInvHistory.put(player, history); - - //Get the EntityPlayer handle from the sender - EntityPlayer entityplayer = ((CraftPlayer) player).getHandle(); - //Get the EntityPlayer from the Target - EntityPlayer entitytarget = ((CraftPlayer) target).getHandle(); - - //Create the inventory - PlayerInventoryChest inv = new PlayerInventoryChest(entitytarget.inventory, entitytarget); - - //Save data into the inventory for tracking - inv.Opener = player; - inv.Target = target; - - //Saves offline openinv - if(Offline) - { - inv.Offline = true; - offlineInv.put(target, inv); - } - - //Open the inventory - entityplayer.openContainer(inv); - return true; - } + } } diff --git a/src/lishid/openinv/utils/Metrics.java b/src/lishid/openinv/utils/Metrics.java index 364cc43..bcd3e78 100644 --- a/src/lishid/openinv/utils/Metrics.java +++ b/src/lishid/openinv/utils/Metrics.java @@ -1,6 +1,4 @@ -package lishid.openinv.utils; - -/* +/** * Copyright 2011 Tyler Blair. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are @@ -26,12 +24,8 @@ package lishid.openinv.utils; * The views and conclusions contained in the software and documentation are those of the * authors and contributors and should not be interpreted as representing official policies, * either expressed or implied, of anybody else. - */ - -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.PluginDescriptionFile; + **/ +package lishid.openinv.utils; import java.io.BufferedReader; import java.io.File; @@ -49,10 +43,18 @@ import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Set; import java.util.UUID; +import java.util.logging.Level; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; /** *

- * The metrics class obtains data about a plugin and submits statistics about it to the metrics backend. + * The metrics class obtains data about a plugin and submits statistics about it + * to the metrics backend. *

*

* Public methods provided by this class: @@ -65,428 +67,560 @@ import java.util.UUID; */ public class Metrics { - /** - * The current revision number - */ - private final static int REVISION = 5; - - /** - * The base url of the metrics domain - */ - private static final String BASE_URL = "http://metrics.griefcraft.com"; - - /** - * The url used to report a server's status - */ - private static final String REPORT_URL = "/report/%s"; - - /** - * The file where guid and opt out is stored in - */ - private static final String CONFIG_FILE = "plugins/PluginMetrics/config.yml"; - - /** - * The separator to use for custom data. This MUST NOT change unless you are hosting your own - * version of metrics and want to change it. - */ - private static final String CUSTOM_DATA_SEPARATOR = "~~"; - - /** - * Interval of time to ping (in minutes) - */ - private final static int PING_INTERVAL = 10; - - /** - * The plugin this metrics submits for - */ - private final Plugin plugin; - - /** - * All of the custom graphs to submit to metrics - */ - private final Set graphs = Collections.synchronizedSet(new HashSet()); - - /** - * The default graph, used for addCustomData when you don't want a specific graph - */ - private final Graph defaultGraph = new Graph("Default"); - - /** - * The plugin configuration file - */ - private final YamlConfiguration configuration; - - /** - * Unique server id - */ - private final String guid; - - public Metrics(Plugin plugin) throws IOException { - if (plugin == null) { - throw new IllegalArgumentException("Plugin cannot be null"); - } - - this.plugin = plugin; - - // load the config - File file = new File(CONFIG_FILE); - configuration = YamlConfiguration.loadConfiguration(file); - - // add some defaults - configuration.addDefault("opt-out", false); - configuration.addDefault("guid", UUID.randomUUID().toString()); - - // Do we need to create the file? - if (configuration.get("guid", null) == null) { - configuration.options().header("http://metrics.griefcraft.com").copyDefaults(true); - configuration.save(file); - } - - // Load the guid then - guid = configuration.getString("guid"); - } - - /** - * Construct and create a Graph that can be used to separate specific plotters to their own graphs - * on the metrics website. Plotters can be added to the graph object returned. - * - * @param name - * @return Graph object created. Will never return NULL under normal circumstances unless bad parameters are given - */ - public Graph createGraph(String name) { - if (name == null) { - throw new IllegalArgumentException("Graph name cannot be null"); - } - - // Construct the graph object - Graph graph = new Graph(name); - - // Now we can add our graph - graphs.add(graph); - - // and return back - return graph; - } - - /** - * Adds a custom data plotter to the default graph - * - * @param plotter - */ - public void addCustomData(Plotter plotter) { - if (plotter == null) { - throw new IllegalArgumentException("Plotter cannot be null"); - } - - // Add the plotter to the graph o/ - defaultGraph.addPlotter(plotter); - - // Ensure the default graph is included in the submitted graphs - graphs.add(defaultGraph); - } - - /** - * Start measuring statistics. This will immediately create an async repeating task as the plugin and send - * the initial data to the metrics backend, and then after that it will post in increments of - * PING_INTERVAL * 1200 ticks. - */ - public void start() { - // Did we opt out? - if (configuration.getBoolean("opt-out", false)) { - return; - } - - // Begin hitting the server with glorious data - plugin.getServer().getScheduler().scheduleAsyncRepeatingTask(plugin, new Runnable() { - private boolean firstPost = true; - - public void run() { - try { - // We use the inverse of firstPost because if it is the first time we are posting, - // it is not a interval ping, so it evaluates to FALSE - // Each time thereafter it will evaluate to TRUE, i.e PING! - postPlugin(!firstPost); - - // After the first post we set firstPost to false - // Each post thereafter will be a ping - firstPost = false; - } catch (Exception e) { - System.err.println("[Metrics] " + e.getMessage()); - } - } - }, 0, PING_INTERVAL * 1200); - } - - /** - * Generic method that posts a plugin to the metrics website - */ - private void postPlugin(boolean isPing) throws IOException { - // The plugin's description file containg all of the plugin data such as name, version, author, etc - PluginDescriptionFile description = plugin.getDescription(); - - // Construct the post data - String data = encode("guid") + '=' + encode(guid) - + encodeDataPair("version", description.getVersion()) - + encodeDataPair("server", Bukkit.getVersion()) - + encodeDataPair("players", Integer.toString(Bukkit.getServer().getOnlinePlayers().length)) - + encodeDataPair("revision", String.valueOf(REVISION)); - - // If we're pinging, append it - if (isPing) { - data += encodeDataPair("ping", "true"); - } - - // Acquire a lock on the graphs, which lets us make the assumption we also lock everything - // inside of the graph (e.g plotters) - synchronized (graphs) { - Iterator iter = graphs.iterator(); - - while (iter.hasNext()) { - Graph graph = iter.next(); - - // Because we have a lock on the graphs set already, it is reasonable to assume - // that our lock transcends down to the individual plotters in the graphs also. - // Because our methods are private, no one but us can reasonably access this list - // without reflection so this is a safe assumption without adding more code. - for (Plotter plotter : graph.getPlotters()) { - // The key name to send to the metrics server - // The format is C-GRAPHNAME-PLOTTERNAME where separator - is defined at the top - // Legacy (R4) submitters use the format Custom%s, or CustomPLOTTERNAME - String key = String.format("C%s%s%s%s", CUSTOM_DATA_SEPARATOR, graph.getName(), CUSTOM_DATA_SEPARATOR, plotter.getColumnName()); - - // The value to send, which for the foreseeable future is just the string - // value of plotter.getValue() - String value = Integer.toString(plotter.getValue()); - - // Add it to the http post data :) - data += encodeDataPair(key, value); - } - } - } - - // Create the url - URL url = new URL(BASE_URL + String.format(REPORT_URL, description.getName())); - - // Connect to the website - URLConnection connection; - - // Mineshafter creates a socks proxy, so we can safely bypass it - // It does not reroute POST requests so we need to go around it - if (isMineshafterPresent()) { - connection = url.openConnection(Proxy.NO_PROXY); - } else { - connection = url.openConnection(); - } - - connection.setDoOutput(true); - - // Write the data - OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream()); - writer.write(data); - writer.flush(); - - // Now read the response - BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); - String response = reader.readLine(); - - // close resources - writer.close(); - reader.close(); - - if (response.startsWith("ERR")) { - throw new IOException(response); //Throw the exception - } else { - // Is this the first update this hour? - if (response.contains("OK This is your first update this hour")) { - synchronized (graphs) { - Iterator iter = graphs.iterator(); - - while (iter.hasNext()) { - Graph graph = iter.next(); - - for (Plotter plotter : graph.getPlotters()) { - plotter.reset(); - } - } - } - } - } - //if (response.startsWith("OK")) - We should get "OK" followed by an optional description if everything goes right - } - - /** - * Check if mineshafter is present. If it is, we need to bypass it to send POST requests - * - * @return - */ - private boolean isMineshafterPresent() { - try { - Class.forName("mineshafter.MineServer"); - return true; - } catch (Exception e) { - return false; - } - } - - /** - *

Encode a key/value data pair to be used in a HTTP post request. This INCLUDES a & so the first - * key/value pair MUST be included manually, e.g:

- * - * String httpData = encode("guid") + '=' + encode("1234") + encodeDataPair("authors") + ".."; - * - * - * @param key - * @param value - * @return - */ - private static String encodeDataPair(String key, String value) throws UnsupportedEncodingException { - return '&' + encode(key) + '=' + encode(value); - } - - /** - * Encode text as UTF-8 - * - * @param text - * @return - */ - private static String encode(String text) throws UnsupportedEncodingException { - return URLEncoder.encode(text, "UTF-8"); - } - - /** - * Represents a custom graph on the website - */ - public static class Graph { - - /** - * The graph's name, alphanumeric and spaces only :) - * If it does not comply to the above when submitted, it is rejected - */ - private final String name; - - /** - * The set of plotters that are contained within this graph - */ - private final Set plotters = new LinkedHashSet(); - - private Graph(String name) { - this.name = name; - } - - /** - * Gets the graph's name - * - * @return - */ - public String getName() { - return name; - } - - /** - * Add a plotter to the graph, which will be used to plot entries - * - * @param plotter - */ - public void addPlotter(Plotter plotter) { - plotters.add(plotter); - } - - /** - * Remove a plotter from the graph - * - * @param plotter - */ - public void removePlotter(Plotter plotter) { - plotters.remove(plotter); - } - - /** - * Gets an unmodifiable set of the plotter objects in the graph - * - * @return - */ - public Set getPlotters() { - return Collections.unmodifiableSet(plotters); - } - - @Override - public int hashCode() { - return name.hashCode(); - } - - @Override - public boolean equals(Object object) { - if (!(object instanceof Graph)) { - return false; - } - - Graph graph = (Graph) object; - return graph.name.equals(name); - } - - } - - /** - * Interface used to collect custom data for a plugin - */ - public static abstract class Plotter { - - /** - * The plot's name - */ - private final String name; - - /** - * Construct a plotter with the default plot name - */ - public Plotter() { - this("Default"); - } - - /** - * Construct a plotter with a specific plot name - * - * @param name - */ - public Plotter(String name) { - this.name = name; - } - - /** - * Get the current value for the plotted point - * - * @return - */ - public abstract int getValue(); - - /** - * Get the column name for the plotted point - * - * @return the plotted point's column name - */ - public String getColumnName() { - return name; - } - - /** - * Called after the website graphs have been updated - */ - public void reset() { - } - - @Override - public int hashCode() { - return getColumnName().hashCode() + getValue(); - } - - @Override - public boolean equals(Object object) { - if (!(object instanceof Plotter)) { - return false; - } - - Plotter plotter = (Plotter) object; - return plotter.name.equals(name) && plotter.getValue() == getValue(); - } - } + /** + * The current revision number + */ + private final static int REVISION = 5; + + /** + * The base url of the metrics domain + */ + private static final String BASE_URL = "http://mcstats.org"; + + /** + * The url used to report a server's status + */ + private static final String REPORT_URL = "/report/%s"; + + /** + * The file where guid and opt out is stored in + */ + private static final String CONFIG_FILE = "plugins/PluginMetrics/config.yml"; + + /** + * The separator to use for custom data. This MUST NOT change unless you are + * hosting your own version of metrics and want to change it. + */ + private static final String CUSTOM_DATA_SEPARATOR = "~~"; + + /** + * Interval of time to ping (in minutes) + */ + private static final int PING_INTERVAL = 10; + + /** + * The plugin this metrics submits for + */ + private final Plugin plugin; + + /** + * All of the custom graphs to submit to metrics + */ + private final Set graphs = Collections.synchronizedSet(new HashSet()); + + /** + * The default graph, used for addCustomData when you don't want a specific + * graph + */ + private final Graph defaultGraph = new Graph("Default"); + + /** + * The plugin configuration file + */ + private final YamlConfiguration configuration; + + /** + * The plugin configuration file + */ + private final File configurationFile; + + /** + * Unique server id + */ + private final String guid; + + /** + * Lock for synchronization + */ + private final Object optOutLock = new Object(); + + /** + * Id of the scheduled task + */ + private volatile int taskId = -1; + + public Metrics(final Plugin plugin) throws IOException { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + this.plugin = plugin; + + // load the config + configurationFile = new File(CONFIG_FILE); + configuration = YamlConfiguration.loadConfiguration(configurationFile); + + // add some defaults + configuration.addDefault("opt-out", false); + configuration.addDefault("guid", UUID.randomUUID().toString()); + + // Do we need to create the file? + if (configuration.get("guid", null) == null) { + configuration.options().header("http://mcstats.org").copyDefaults(true); + configuration.save(configurationFile); + } + + // Load the guid then + guid = configuration.getString("guid"); + } + + /** + * Construct and create a Graph that can be used to separate specific + * plotters to their own graphs on the metrics website. Plotters can be + * added to the graph object returned. + * + * @param name + * @return Graph object created. Will never return NULL under normal + * circumstances unless bad parameters are given + */ + public Graph createGraph(final String name) { + if (name == null) { + throw new IllegalArgumentException("Graph name cannot be null"); + } + + // Construct the graph object + final Graph graph = new Graph(name); + + // Now we can add our graph + graphs.add(graph); + + // and return back + return graph; + } + + /** + * Adds a custom data plotter to the default graph + * + * @param plotter + */ + public void addCustomData(final Plotter plotter) { + if (plotter == null) { + throw new IllegalArgumentException("Plotter cannot be null"); + } + + // Add the plotter to the graph o/ + defaultGraph.addPlotter(plotter); + + // Ensure the default graph is included in the submitted graphs + graphs.add(defaultGraph); + } + + /** + * Start measuring statistics. This will immediately create an async + * repeating task as the plugin and send the initial data to the metrics + * backend, and then after that it will post in increments of PING_INTERVAL + * * 1200 ticks. + * + * @return True if statistics measuring is running, otherwise false. + */ + public boolean start() { + synchronized (optOutLock) { + // Did we opt out? + if (isOptOut()) { + return false; + } + + // Is metrics already running? + if (taskId >= 0) { + return true; + } + + // Begin hitting the server with glorious data + taskId = plugin.getServer().getScheduler() + .scheduleAsyncRepeatingTask(plugin, new Runnable() { + + private boolean firstPost = true; + + @Override + public void run() { + try { + // This has to be synchronized or it can collide + // with the disable method. + synchronized (optOutLock) { + // Disable Task, if it is running and the + // server owner decided to opt-out + if (isOptOut() && taskId > 0) { + plugin.getServer().getScheduler().cancelTask(taskId); + taskId = -1; + } + } + + // We use the inverse of firstPost because if it + // is the first time we are posting, + // it is not a interval ping, so it evaluates to + // FALSE + // Each time thereafter it will evaluate to + // TRUE, i.e PING! + postPlugin(!firstPost); + + // After the first post we set firstPost to + // false + // Each post thereafter will be a ping + firstPost = false; + } catch (final IOException e) { + Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage()); + } + } + }, 0, PING_INTERVAL * 1200); + + return true; + } + } + + /** + * Has the server owner denied plugin metrics? + * + * @return + */ + public boolean isOptOut() { + synchronized (optOutLock) { + try { + // Reload the metrics file + configuration.load(CONFIG_FILE); + } catch (final IOException ex) { + Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); + return true; + } catch (final InvalidConfigurationException ex) { + Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); + return true; + } + return configuration.getBoolean("opt-out", false); + } + } + + /** + * Enables metrics for the server by setting "opt-out" to false in the + * config file and starting the metrics task. + * + * @throws IOException + */ + public void enable() throws IOException { + // This has to be synchronized or it can collide with the check in the + // task. + synchronized (optOutLock) { + // Check if the server owner has already set opt-out, if not, set + // it. + if (isOptOut()) { + configuration.set("opt-out", false); + configuration.save(configurationFile); + } + + // Enable Task, if it is not running + if (taskId < 0) { + start(); + } + } + } + + /** + * Disables metrics for the server by setting "opt-out" to true in the + * config file and canceling the metrics task. + * + * @throws IOException + */ + public void disable() throws IOException { + // This has to be synchronized or it can collide with the check in the + // task. + synchronized (optOutLock) { + // Check if the server owner has already set opt-out, if not, set + // it. + if (!isOptOut()) { + configuration.set("opt-out", true); + configuration.save(configurationFile); + } + + // Disable Task, if it is running + if (taskId > 0) { + this.plugin.getServer().getScheduler().cancelTask(taskId); + taskId = -1; + } + } + } + + /** + * Generic method that posts a plugin to the metrics website + */ + private void postPlugin(final boolean isPing) throws IOException { + // The plugin's description file containg all of the plugin data such as + // name, version, author, etc + final PluginDescriptionFile description = plugin.getDescription(); + + // Construct the post data + final StringBuilder data = new StringBuilder(); + data.append(encode("guid")).append('=').append(encode(guid)); + encodeDataPair(data, "version", description.getVersion()); + encodeDataPair(data, "server", Bukkit.getVersion()); + encodeDataPair(data, "players", + Integer.toString(Bukkit.getServer().getOnlinePlayers().length)); + encodeDataPair(data, "revision", String.valueOf(REVISION)); + + // If we're pinging, append it + if (isPing) { + encodeDataPair(data, "ping", "true"); + } + + // Acquire a lock on the graphs, which lets us make the assumption we + // also lock everything + // inside of the graph (e.g plotters) + synchronized (graphs) { + final Iterator iter = graphs.iterator(); + + while (iter.hasNext()) { + final Graph graph = iter.next(); + + for (final Plotter plotter : graph.getPlotters()) { + // The key name to send to the metrics server + // The format is C-GRAPHNAME-PLOTTERNAME where separator - + // is defined at the top + // Legacy (R4) submitters use the format Custom%s, or + // CustomPLOTTERNAME + final String key = String.format("C%s%s%s%s", CUSTOM_DATA_SEPARATOR, + graph.getName(), CUSTOM_DATA_SEPARATOR, plotter.getColumnName()); + + // The value to send, which for the foreseeable future is + // just the string + // value of plotter.getValue() + final String value = Integer.toString(plotter.getValue()); + + // Add it to the http post data :) + encodeDataPair(data, key, value); + } + } + } + + // Create the url + final URL url = new URL(BASE_URL + + String.format(REPORT_URL, encode(plugin.getDescription().getName()))); + + // Connect to the website + URLConnection connection; + + // Mineshafter creates a socks proxy, so we can safely bypass it + // It does not reroute POST requests so we need to go around it + if (isMineshafterPresent()) { + connection = url.openConnection(Proxy.NO_PROXY); + } else { + connection = url.openConnection(); + } + + connection.setDoOutput(true); + + // Write the data + final OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream()); + writer.write(data.toString()); + writer.flush(); + + // Now read the response + final BufferedReader reader = new BufferedReader(new InputStreamReader( + connection.getInputStream())); + final String response = reader.readLine(); + + // close resources + writer.close(); + reader.close(); + + if (response == null || response.startsWith("ERR")) { + throw new IOException(response); // Throw the exception + } else { + // Is this the first update this hour? + if (response.contains("OK This is your first update this hour")) { + synchronized (graphs) { + final Iterator iter = graphs.iterator(); + + while (iter.hasNext()) { + final Graph graph = iter.next(); + + for (final Plotter plotter : graph.getPlotters()) { + plotter.reset(); + } + } + } + } + } + } + + /** + * Check if mineshafter is present. If it is, we need to bypass it to send + * POST requests + * + * @return + */ + private boolean isMineshafterPresent() { + try { + Class.forName("mineshafter.MineServer"); + return true; + } catch (final Exception e) { + return false; + } + } + + /** + *

+ * Encode a key/value data pair to be used in a HTTP post request. This + * INCLUDES a & so the first key/value pair MUST be included manually, e.g: + *

+ * + * StringBuffer data = new StringBuffer(); + * data.append(encode("guid")).append('=').append(encode(guid)); + * encodeDataPair(data, "version", description.getVersion()); + * + * + * @param buffer + * @param key + * @param value + * @return + */ + private static void encodeDataPair(final StringBuilder buffer, final String key, + final String value) throws UnsupportedEncodingException { + buffer.append('&').append(encode(key)).append('=').append(encode(value)); + } + + /** + * Encode text as UTF-8 + * + * @param text + * @return + */ + private static String encode(final String text) throws UnsupportedEncodingException { + return URLEncoder.encode(text, "UTF-8"); + } + + /** + * Represents a custom graph on the website + */ + public static class Graph { + + /** + * The graph's name, alphanumeric and spaces only :) If it does not + * comply to the above when submitted, it is rejected + */ + private final String name; + + /** + * The set of plotters that are contained within this graph + */ + private final Set plotters = new LinkedHashSet(); + + private Graph(final String name) { + this.name = name; + } + + /** + * Gets the graph's name + * + * @return + */ + public String getName() { + return name; + } + + /** + * Add a plotter to the graph, which will be used to plot entries + * + * @param plotter + */ + public void addPlotter(final Plotter plotter) { + plotters.add(plotter); + } + + /** + * Remove a plotter from the graph + * + * @param plotter + */ + public void removePlotter(final Plotter plotter) { + plotters.remove(plotter); + } + + /** + * Gets an unmodifiable set of the plotter objects in the graph + * + * @return + */ + public Set getPlotters() { + return Collections.unmodifiableSet(plotters); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public boolean equals(final Object object) { + if (!(object instanceof Graph)) { + return false; + } + + final Graph graph = (Graph) object; + return graph.name.equals(name); + } + + } + + /** + * Interface used to collect custom data for a plugin + */ + public static abstract class Plotter { + + /** + * The plot's name + */ + private final String name; + + /** + * Construct a plotter with the default plot name + */ + public Plotter() { + this("Default"); + } + + /** + * Construct a plotter with a specific plot name + * + * @param name + */ + public Plotter(final String name) { + this.name = name; + } + + /** + * Get the current value for the plotted point + * + * @return + */ + public abstract int getValue(); + + /** + * Get the column name for the plotted point + * + * @return the plotted point's column name + */ + public String getColumnName() { + return name; + } + + /** + * Called after the website graphs have been updated + */ + public void reset() { + } + + @Override + public int hashCode() { + return getColumnName().hashCode() + getValue(); + } + + @Override + public boolean equals(final Object object) { + if (!(object instanceof Plotter)) { + return false; + } + + final Plotter plotter = (Plotter) object; + return plotter.name.equals(name) && plotter.getValue() == getValue(); + } + + } + } \ No newline at end of file diff --git a/src/lishid/openinv/utils/PlayerInventoryChest.java b/src/lishid/openinv/utils/PlayerInventoryChest.java deleted file mode 100644 index 7eb1944..0000000 --- a/src/lishid/openinv/utils/PlayerInventoryChest.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright (C) 2011-2012 lishid. All rights reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package lishid.openinv.utils; - -import java.util.ArrayList; -import java.util.List; - -import org.bukkit.craftbukkit.entity.CraftHumanEntity; -import org.bukkit.entity.HumanEntity; -import org.bukkit.entity.Player; -import org.bukkit.inventory.InventoryHolder; - -import lishid.openinv.commands.OpenInvPluginCommand; -import net.minecraft.server.EntityHuman; -import net.minecraft.server.EntityPlayer; -import net.minecraft.server.IInventory; -import net.minecraft.server.ItemStack; -import net.minecraft.server.PlayerInventory; - -public class PlayerInventoryChest implements IInventory -{ - public boolean Offline = false; - public Player Opener; - EntityPlayer player; - public Player Target; - private ItemStack[] items = new ItemStack[36]; - private ItemStack[] armor = new ItemStack[4]; - private ItemStack[] extra = new ItemStack[5]; - private int maxStack = MAX_STACK; - - public PlayerInventoryChest(PlayerInventory inventory, EntityPlayer entityplayer) - { - player = entityplayer; - this.items = inventory.items; - this.armor = inventory.armor; - } - - public ItemStack[] getContents() - { - ItemStack[] C = new ItemStack[getSize()]; - System.arraycopy(items, 0, C, 0, items.length); - System.arraycopy(items, 0, C, items.length, armor.length); - return C; - } - - public int getSize() - { - return 45; - } - - public ItemStack getItem(int i) - { - ItemStack[] is = this.items; - - if (i >= is.length) - { - i -= is.length; - is = this.armor; - } - else - { - i = getReversedItemSlotNum(i); - } - - if (i >= is.length) - { - i -= is.length; - is = this.extra; - } - else if(is == this.armor) - { - i = getReversedArmorSlotNum(i); - } - - return is[i]; - } - - public ItemStack splitStack(int i, int j) - { - ItemStack[] is = this.items; - - if (i >= is.length) - { - i -= is.length; - is = this.armor; - } - else - { - i = getReversedItemSlotNum(i); - } - - if (i >= is.length) - { - i -= is.length; - is = this.extra; - } - else if(is == this.armor) - { - i = getReversedArmorSlotNum(i); - } - - if (is[i] != null) - { - ItemStack itemstack; - - if (is[i].count <= j) - { - itemstack = is[i]; - is[i] = null; - return itemstack; - } - else - { - itemstack = is[i].a(j); - if (is[i].count == 0) - { - is[i] = null; - } - - return itemstack; - } - } - else - { - return null; - } - } - - public ItemStack splitWithoutUpdate(int i) { - ItemStack[] is = this.items; - - if (i >= is.length) - { - i -= is.length; - is = this.armor; - } - else - { - i = getReversedItemSlotNum(i); - } - - if (i >= is.length) - { - i -= is.length; - is = this.extra; - } - else if(is == this.armor) - { - i = getReversedArmorSlotNum(i); - } - - if (is[i] != null) { - ItemStack itemstack = is[i]; - - is[i] = null; - return itemstack; - } else { - return null; - } - } - - public void setItem(int i, ItemStack itemstack) - { - ItemStack[] is = this.items; - - if (i >= is.length) - { - i -= is.length; - is = this.armor; - } - else - { - i = getReversedItemSlotNum(i); - } - - if (i >= is.length) - { - i -= is.length; - is = this.extra; - } - else if(is == this.armor) - { - i = getReversedArmorSlotNum(i); - } - - /* - - //Effects - if(is == this.extra) - { - if(i == 0) - { - itemstack.setData(0); - } - }*/ - - is[i] = itemstack; - } - - private int getReversedItemSlotNum(int i) - { - if (i >= 27) return i - 27; - else return i + 9; - } - - private int getReversedArmorSlotNum(int i) - { - if (i == 0) return 3; - if (i == 1) return 2; - if (i == 2) return 1; - if (i == 3) return 0; - else return i; - } - - public String getName() - { - if (player.name.length() > 16) return player.name.substring(0, 16); - return player.name; - } - - public int getMaxStackSize() - { - return maxStack; - } - - public boolean a(EntityHuman entityhuman) - { - return true; - } - - public void f() - { - - } - - public void g() - { - try - { - PlayerInventoryChest inv = OpenInvPluginCommand.offlineInv.get(this.Target); - if (inv != null) - { - this.Target.saveData(); - OpenInvPluginCommand.offlineInv.remove(this.Target); - } - } - catch (Exception e) - {} - } - - public void update() - { - - } - - public List transaction = new ArrayList(); - - public void onOpen(CraftHumanEntity who) { - transaction.add(who); - } - - public void onClose(CraftHumanEntity who) { - transaction.remove(who); - } - - public List getViewers() { - return transaction; - } - - @Override - public InventoryHolder getOwner() { - return null; - } - - @Override - public void setMaxStackSize(int size) { - maxStack = size; - } -} \ No newline at end of file