summaryrefslogtreecommitdiff
path: root/src/main/java/de/pepich/chestapi/ClickableInventory.java
blob: d3fc8a70349f0ab8fb656462ad2fe9f25fca9296 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
package de.pepich.chestapi;

import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;

import java.util.HashMap;

public class ClickableInventory implements Listener
{
	private int open = 0;
	private final String title;
	private DefaultSize size;
	private HashMap<Integer, CallbackHandler> handlers = new HashMap<Integer, CallbackHandler>();
	private CallbackHandler background;
	private CallbackHandler fallback;
	private CallbackHandler close;
	
	private Inventory inventory;
	private final InventoryHolder holder;
	
	/** Creates a new ClickableInventory with the given name.</br>
	 * The ClickableInventory will be created with a DefaultSize as specified by {@link DefaultSize#DYNAMIC_AUTO(int, int) DYNAMIC_AUTO(9, 54)}.
	 * 
	 * @param title The name to be displayed. */
	public ClickableInventory(final String title)
	{
		this(title, DefaultSize.DYNAMIC_AUTO(9, 54));
	}
	
	/** Creates a new ClickableInventory with the given name and the specified DefaultSize.
	 * 
	 * @param title The name to be displayed.
	 * @param size The DefaultSize for the inventory to be used. */
	public ClickableInventory(final String title, final DefaultSize size)
	{
		this.size = size;
		this.title = title;
		holder = new CustomHolder(this);
		if (size.allowSquareShape() && size.getPreferredSize() == 9)
			inventory = Bukkit.createInventory(holder, InventoryType.DROPPER, title);
		else
			inventory = Bukkit.createInventory(holder, size.getPreferredSize(), title);
	}
	
	/** Displays the inventory.
	 * 
	 * @param player The player that is going to see the inventory. */
	public void show(Player player)
	{
		player.openInventory(inventory);
		if (open == 0)
			Bukkit.getPluginManager().registerEvents(this, ChestAPI.getPlugin());
		open++;
	}
	
	// ------------------------------------------------------------- //
	// -------------------- GETTERS AND SETTERS -------------------- //
	// ------------------------------------------------------------- //
	// If you want docs on these, go fork off. Getters/Setters. Duh. //
	
	public String getName()
	{
		return title;
	}
	
	public int getFirstFree()
	{
		if (handlers.size() == inventory.getSize())
			return -1;
		for (int i = 1; i <= inventory.getSize(); i++)
			if (!handlers.containsKey(i))
				return i;
		return -1;
	}
	
	public int getLastFree()
	{
		if (handlers.size() == inventory.getSize())
			return -1;
		for (int i = inventory.getSize(); i > 0; i--)
			if (!handlers.containsKey(i))
				return i;
		return -1;
	}
	
	public int getFirstUsed()
	{
		if (handlers.size() == inventory.getSize())
			return -1;
		for (int i = 1; i <= inventory.getSize(); i++)
			if (handlers.containsKey(i))
				return i;
		return -1;
	}
	
	public int getLastUsed()
	{
		if (handlers.size() == inventory.getSize())
			return -1;
		for (int i = inventory.getSize(); i > 0; i--)
			if (handlers.containsKey(i))
				return i;
		return -1;
	}
	
	protected Inventory getInventory()
	{
		return inventory;
	}
	
	// ------------------------------------------------------------- //
	// ----------------------- SETUP METHODS ----------------------- //
	// ------------------------------------------------------------- //
	
	/** Assigns an item and a handler to the given item slot.<br/>
	 * Setting a handler to null will NOT remove it, but it will flag the slot as "used", meaning that it will be skipped when searching for the first/last free spot.
	 * 
	 * @param slot An integer value representing the slot for the ItemStack. Must be any of -998, [-3:max_length].<br/>
	 *        The value -998 represents background clicks.<br/>
	 *        The value -3 represents the "close handler", which will be fired when the player closes the inventory.<br/>
	 *        The value -2 represents the "fallback handler", which will be used if no appropriate handler could be found.<br/>
	 *        The value -1 represents the last free slot, 0 points to the first free slot. If an actual number is given, the specified slot will be overwritten.<br/>
	 *        The first slot is numbered with 1, as opposed to starting at 0, due to the additional meaning that 0 has.
	 * @param item An ItemStack that will be displayed. Set to null if you want to assign functionality to an empty slot.
	 * @param handler A CallbackHandler which will be notified on click. When the handler is null then the handler will be removed, but the item will still be assigned.
	 * @throws IllegalArgumentException when the location does not exist or when one of -1 or 0 are specified and the inventory is already full. */
	public void set(int slot, final ItemStack item, final CallbackHandler handler)
	{
		if (slot > inventory.getSize())
			resize(slot);
		if (slot < -1 || slot > size.getMaxSize())
			throw new IllegalArgumentException(
					"The given slot (" + slot + ") is out of bounds (-1, 0, [1:" + size.getMaxSize() + "])");
		else
		{
			if (slot == -998)
				background = handler;
			else if (slot == -2)
				fallback = handler;
			else if (slot == -3)
				close = handler;
			else
			{
				if (slot == 0)
					slot = getFirstFree();
				else if (slot == -1)
					slot = getLastFree();
				if (slot == -1)
					throw new IllegalArgumentException("No free spot could be found!");
					
				if (item == null)
					inventory.remove(inventory.getItem(slot - 1));
				else
					inventory.setItem(slot - 1, item);
					
				handlers.put(slot, handler);
			}
		}
		if (size.doAutoResize())
			resize();
	}
	
	/** Removes an assigned action from the inventory. Most be any of -1, 0, [1:max_length].<br/>
	 * Calling this method will free up a slot again so that it can be filled up by using the "first/last free" selector.
	 * 
	 * @param slot An integer value representing the slot for the ItemStack. Must be any of -998, [-3:max_length].<br/>
	 *        The value -998 represents background clicks.<br/>
	 *        The value -3 represents the "close handler", which will be fired when the player closes the inventory.<br/>
	 *        The value -2 represents the "fallback handler", which will be used if no appropriate handler could be found.<br/>
	 *        The value -1 represents the last used slot, 0 points to the first used slot. If an actual number is given, the specified slot will be overwritten.<br/>
	 *        The first slot is numbered with 1, as opposed to starting at 0, due to the additional meaning that 0 has.
	 * @throws IllegalArgumentException when the location does not exist. */
	public void remove(int slot)
	{
		if (slot == -998)
			background = null;
		else if (slot == -2)
			fallback = null;
		else if (slot == -3)
			close = null;
		else
		{
			if (slot < -1 || slot > size.getMaxSize())
				throw new IllegalArgumentException(
						"The given slot (" + slot + ") is out of bounds (-1, 0, [1:" + size.getMaxSize() + "])");
			if (slot == 0)
				slot = getFirstUsed();
			else if (slot == -1)
				slot = getLastUsed();
			if (slot == -1)
				throw new IllegalArgumentException("No free spot could be found!");
			handlers.remove(slot);
			inventory.setItem(slot - 1, null);
			if (size.doAutoResize())
				resize();
		}
	}
	
	/** This method removes all TRAILING empty lines.<br/>
	 * This method will utilize hopper and dropper inventories if possible. Dropper inventories can be disabled by selection a RECTANGLE size constraint.<br/>
	 * Can not be used on final sized inventories.
	 * 
	 * @return the new size.
	 * @throws IllegalAccessError if the Inventories size is final. */
	public int resize()
	{
		if (size.isFinalSize())
			throw new IllegalAccessError("Can not resize an inventory with a FINAL size constraint.");
		final int target_size = getLastUsed();
		Inventory new_inventory;
		if (target_size <= 5)
			new_inventory = Bukkit.createInventory(holder, InventoryType.HOPPER, title);
		else if (target_size <= 9 && size.allowSquareShape())
			new_inventory = Bukkit.createInventory(holder, InventoryType.DROPPER, title);
		else
			new_inventory = Bukkit.createInventory(holder, target_size + ((9 - (target_size % 9)) % 9), title);
		for (int i = 0; i < new_inventory.getSize() && i < inventory.getSize(); i++)
			new_inventory.setItem(i, inventory.getItem(i));
		inventory = new_inventory;
		return inventory.getSize();
	}
	
	/** This method removes all TRAILING empty lines.<br/>
	 * This method will utilize hopper and dropper inventories if possible. Dropper inventories can be disabled by selection a RECTANGLE size constraint.<br/>
	 * Can not be used on final sized inventories.
	 * 
	 * @return the new size.
	 * @throws IllegalAccessError if the Inventories size is final. */
	private void resize(int target_size)
	{
		if (size.isFinalSize())
			throw new IllegalAccessError("Can not resize an inventory with a FINAL size constraint.");
		target_size = Math.max(target_size, getLastUsed());
		Inventory new_inventory;
		if (target_size <= 5)
			new_inventory = Bukkit.createInventory(holder, InventoryType.HOPPER, title);
		else if (target_size <= 9 && size.allowSquareShape())
			new_inventory = Bukkit.createInventory(holder, InventoryType.DROPPER, title);
		else
			new_inventory = Bukkit.createInventory(holder, target_size + ((9 - (target_size % 9)) % 9), title);
		for (int i = 0; i < new_inventory.getSize() && i < inventory.getSize(); i++)
			new_inventory.setItem(i, inventory.getItem(i));
		inventory = new_inventory;
	}
	
	// ------------------------------------------------------------- //
	// ------------------------- LISTENERS ------------------------- //
	// ------------------------------------------------------------- //
	
	@EventHandler
	public void onInventoryClose(InventoryCloseEvent event)
	{
		if (this.inventory.equals(event.getInventory()))
		{
			open--;
			if (open == 0)
				event.getHandlers().unregister(this);
			if (close != null)
				close.run((Player) event.getPlayer(), ClickType.UNKNOWN);
		}
	}
	
	@EventHandler
	public void onInventoryClick(InventoryClickEvent event)
	{
		if (this.inventory.equals(event.getInventory()))
		{
			event.setCancelled(true);
			final int slot = event.getSlot() + 1;
			CallbackHandler handler;
			if (slot == -998)
				handler = background;
			else
				handler = handlers.get(slot);
			if ((handler = (handler == null ? fallback : handler)) != null)
				handler.run((Player) event.getWhoClicked(), event.getClick());
		}
	}
}

class CustomHolder implements InventoryHolder
{
	ClickableInventory inv;
	
	protected CustomHolder(ClickableInventory inv)
	{
		this.inv = inv;
	}
	
	@Override
	public Inventory getInventory()
	{
		return inv.getInventory();
	}
}