package tc.oc.commons.bukkit.listeners; import java.util.HashMap; import java.util.Map; import javax.inject.Singleton; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryView; import tc.oc.commons.bukkit.inventory.InventoryUtils; import tc.oc.commons.core.plugin.PluginFacet; /** * Generic service for tracking inventory window state, and dispatching events to {@link WindowListener}s. */ @Singleton public class WindowManager implements PluginFacet, Listener { private class View { final Player player; final InventoryView window; final WindowListener listener; View(Player player, InventoryView window, WindowListener listener) { this.player = player; this.window = window; this.listener = listener; } void notifyOpen() { listener.windowOpened(window); } void notifyClose() { listener.windowClosed(window); } } private final Map<Player, View> views = new HashMap<>(); private void handleCloseWindow(Player player) { final View view = views.remove(player); if(view != null) { view.notifyClose(); } } /** * Register the given {@link WindowListener} to receive notifications about the given {@link InventoryView}. */ public InventoryView registerWindow(WindowListener listener, InventoryView window) { final Player player = (Player) window.getPlayer(); final View old = views.get(player); if(old == null || !old.window.equals(window)) { if(old != null) { old.notifyClose(); } final View view = new View(player, window, listener); views.put(player, view); view.notifyOpen(); } return window; } /** * Open an {@link InventoryView} window for the given {@link Player} onto the given {@link Inventory}, * and register the given {@link WindowListener} to the window. * * If the player currently has an inventory window open, it is closed and any listener it has is * properly notified. */ public InventoryView openWindow(WindowListener listener, Player player, Inventory inventory) { closeWindow(player); return registerWindow(listener, player.openInventory(inventory)); } /** * Close any inventory window the player has open, and notify any listener registered to it. */ public void closeWindow(Player player) { player.closeInventory(); handleCloseWindow(player); } @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) public void onClick(InventoryClickEvent event) { final View view = views.get(event.getActor()); if(view != null) { if(view.window.equals(event.getView())) { if(view.listener.windowClicked(event.getView(), InventoryUtils.clickedInventory(event), event.getClick(), event.getSlotType(), event.getSlot(), event.getCurrentItem())) { event.setCancelled(true); } } else { // If player clicked in a window other than the one we are tracking for them, // it must have already been closed somehow. handleCloseWindow(event.getActor()); } } } @EventHandler(priority = EventPriority.MONITOR) public void onClose(InventoryCloseEvent event) { handleCloseWindow((Player) event.getPlayer()); } @EventHandler(priority = EventPriority.MONITOR) public void onQuit(PlayerQuitEvent event) { // Probably not necessary handleCloseWindow(event.getPlayer()); } }