1
1
package cat .nyaa .HamsterEcoHelper .quest .gui ;
2
2
3
+ import cat .nyaa .HamsterEcoHelper .I18n ;
3
4
import org .bukkit .Bukkit ;
5
+ import org .bukkit .Material ;
4
6
import org .bukkit .entity .Player ;
5
7
import org .bukkit .event .inventory .InventoryClickEvent ;
6
8
import org .bukkit .inventory .Inventory ;
7
9
import org .bukkit .inventory .InventoryHolder ;
8
10
import org .bukkit .inventory .ItemStack ;
11
+ import org .bukkit .inventory .meta .ItemMeta ;
9
12
10
- import java .util .HashMap ;
11
13
import java .util .Map ;
12
- import java .util .SortedMap ;
14
+ import java .util .TreeMap ;
13
15
import java .util .UUID ;
14
16
15
17
/**
19
21
* otherwise synchronization issues will occur
20
22
*/
21
23
public abstract class PaginatedListGui implements InventoryHolder {
22
-
23
- protected final Map <UUID , Inventory > openedUI = new HashMap <>();
24
- protected final Map <UUID , Integer > openedUiPages = new HashMap <>();
24
+ protected final Map <UUID , Inventory > openedUI = new TreeMap <>();
25
+ protected final Map <UUID , Integer > openedUiPages = new TreeMap <>(); // pages count from 0
25
26
protected final String basicTitle ;
26
27
27
- private PairList <String , ItemStack > currentItems ;
28
+ /* the following three variables are updated (and should only be update)
29
+ * in {@link #contentChanged()}
30
+ */
31
+ private boolean guiFrozen = false ;
32
+ private PairList <String , ItemStack > fullContentCache = null ;
33
+ private int maxPage ; // how may pages this gui has. pageIdx in range [0, maxPage)
28
34
29
35
protected PaginatedListGui (String title ) {
30
36
basicTitle = title ;
37
+ contentChanged ();
31
38
}
32
39
33
40
/**
34
41
* Show the player this GUI
35
42
*/
36
43
public void openFor (Player player ) {
37
- openedUiPages .put (player .getUniqueId (), 1 );
38
- Inventory inv = Bukkit .createInventory (this , 54 , basicTitle + " - Page 1" );
39
- currentItems = getFullGuiContent ();
40
- ItemStack [] t = currentItems .getValues (0 ,45 ).toArray (new ItemStack [0 ]);
41
- inv .setContents (t );
42
- openedUI .put (player .getUniqueId (), inv );
43
- player .openInventory (inv );
44
- // TODO
44
+ showPlayerPage (player , 0 );
45
+ }
46
+
47
+ /**
48
+ * Show page to player.
49
+ * If page not in range, close gui & clear player record.
50
+ * @param p
51
+ * @param page
52
+ */
53
+ private void showPlayerPage (Player p , int page ) {
54
+ UUID id = p .getUniqueId ();
55
+ if (page >=0 && page < maxPage ) {
56
+ Inventory inv = Bukkit .createInventory (this , 54 , basicTitle + I18n .format ("user.quest.title_page" , page + 1 ));
57
+ ItemStack [] tmp = fullContentCache .getValues (page *45 , (page +1 )*45 ).toArray (new ItemStack [0 ]);
58
+ inv .setContents (tmp );
59
+
60
+ // set prev/next page buttons
61
+ if (page == 0 ) {
62
+ inv .setItem (45 , getNamedItem (Material .BARRIER , I18n .format ("user.quest.first_page" )));
63
+ } else {
64
+ inv .setItem (45 , getNamedItem (Material .ARROW , I18n .format ("user.quest.prev_page" )));
65
+ }
66
+ if (page == maxPage - 1 ) {
67
+ inv .setItem (53 , getNamedItem (Material .BARRIER , I18n .format ("user.quest.last_page" )));
68
+ } else {
69
+ inv .setItem (53 , getNamedItem (Material .ARROW , I18n .format ("user.quest.next_page" )));
70
+ }
71
+
72
+ openedUiPages .put (id , page );
73
+ openedUI .put (id , inv );
74
+ p .openInventory (inv );
75
+ } else {
76
+ openedUiPages .remove (id );
77
+ openedUI .remove (id );
78
+ if (isCurrentGui (p )) p .closeInventory ();
79
+ }
80
+ }
81
+
82
+ public ItemStack getNamedItem (Material material , String title ) {
83
+ ItemStack stack = new ItemStack (material );
84
+ ItemMeta m = stack .getItemMeta ();
85
+ m .setDisplayName (title );
86
+ stack .setItemMeta (m );
87
+ return stack ;
88
+ }
89
+
90
+ /**
91
+ * Check if the inventory opened by the player
92
+ * is managed by this GUI object
93
+ * i.e. inventoryHolder == this
94
+ */
95
+ public boolean isCurrentGui (Player p ) {
96
+ return p .getOpenInventory () != null &&
97
+ p .getOpenInventory ().getTopInventory () != null &&
98
+ p .getOpenInventory ().getTopInventory ().getHolder () == this ;
45
99
}
46
100
47
101
/**
@@ -63,34 +117,105 @@ public final Inventory getInventory() {
63
117
* @param ev
64
118
*/
65
119
public void onInventoryClicked (InventoryClickEvent ev ) {
66
- // TODO
120
+ // TODO change all System.err
67
121
ev .setCancelled (true );
122
+ if (guiFrozen ) return ;
68
123
if (ev .getClickedInventory () == null ) return ;
69
124
if (!(ev .getWhoClicked () instanceof Player )) {
70
125
System .err .print ("inventory not clicked by player?" );
71
126
return ;
72
127
}
73
- if (ev .getClickedInventory () != openedUI .get (ev .getWhoClicked ().getUniqueId ())) {
74
- System .err .print ("user clicked an unknown inventory." );
75
- System .err .print ("clicked:" +ev .getInventory ());
76
- System .err .print ("expected:" +openedUI .get (ev .getWhoClicked ().getUniqueId ()));
77
- //return;
78
- // TODO it seems bukkit does some magic when opening an inventory
79
- // so we need another way to track inventories (not by "==")
128
+ Player p = (Player )ev .getWhoClicked ();
129
+ if (!openedUI .containsKey (p .getUniqueId ()) || !openedUiPages .containsKey (p .getUniqueId ())) {
130
+ System .err .print ("user not registered in gui" );
131
+ return ;
80
132
}
81
- Integer page = openedUiPages . get ( ev .getWhoClicked (). getUniqueId () );
82
- if (page == null ) {
83
- System .err .print ("user clicked an unknown inventory page no. " );
133
+ Inventory inv = ev .getClickedInventory ( );
134
+ if (! inv . equals ( openedUI . get ( p . getUniqueId ())) ) {
135
+ System .err .print ("user clicked on unknown inventory" );
84
136
return ;
85
137
}
86
-
87
- itemClicked ((Player )ev .getWhoClicked (), currentItems .getKey ((page -1 )*45 +ev .getSlot ()));
138
+ Integer page = openedUiPages .get (ev .getWhoClicked ().getUniqueId ());
139
+ Integer slot = ev .getSlot ();
140
+ if (slot >= 0 && slot < 45 ) { //clicked on item
141
+ String key = fullContentCache .getKey (page * 45 + slot );
142
+ if (key != null ) {
143
+ itemClicked (p , key );
144
+ } else {
145
+ // TODO user clicked on an empty slot. Need better handling
146
+ //System.err.print("user clicked slot out of range");
147
+ }
148
+ } else if (slot == 45 ) { // previous page
149
+ if (page - 1 >= 0 ) showPlayerPage (p , page - 1 );
150
+ } else if (slot == 53 ) { // next page
151
+ if (page + 1 < maxPage ) showPlayerPage (p , page + 1 );
152
+ } else if (slot > 45 && slot < 53 ) { // custom buttons
153
+ // TODO not implemented
154
+ } else { // unknown buttons
155
+ System .err .print ("user clicked on unknown slot" );
156
+ }
88
157
}
89
158
90
159
/**
91
160
* Should be implemented by subclasses.
161
+ * PaginatedListGui will cache the returned List.
162
+ * When the content is changed, subclass should call {@link #contentChanged}
163
+ *
92
164
* @return An ordered map of items to be shown in this GUI
93
165
*/
94
166
protected abstract PairList <String , ItemStack > getFullGuiContent ();
167
+
168
+ /**
169
+ * This method will be called when a player clicked on an item.
170
+ * TODO: subclasses may need another cache to store the itemKeys, maybe we can avoid this?
171
+ * @param itemKey the key returned by {@link #getFullGuiContent()}
172
+ */
95
173
protected abstract void itemClicked (Player player , String itemKey );
174
+
175
+ /**
176
+ * Invoked when all inventories of this gui are closed by players.
177
+ * Subclasses may or may not implement this.
178
+ * TODO not implemented, inventory close event
179
+ */
180
+ protected void guiCompletelyClosed () {
181
+
182
+ }
183
+
184
+ /**
185
+ * Subclasses should call this method when there's any change
186
+ * on the contents.
187
+ * When invoked, PaginatedListGui will invalid the cache immediately
188
+ * call {@link #getFullGuiContent()} again, and refresh all opened inventories.
189
+ * TODO: players may unintentionally click on the wrong item if there are many players competing.
190
+ * TODO: adding a short cooldown time may be a good idea?
191
+ */
192
+ protected void contentChanged () {
193
+ guiFrozen = true ; // TODO gui may not need to be frozen, further investigation needed.
194
+
195
+ // update cache
196
+ fullContentCache = getFullGuiContent ();
197
+ maxPage = fullContentCache .size () <= 0 ? 1 :(fullContentCache .size ()-1 )/45 +1 ;
198
+
199
+ // update opened inventories
200
+ if (!openedUI .isEmpty () || !openedUiPages .isEmpty ()) {
201
+ Map <Player , Integer > newPages = new TreeMap <>();
202
+
203
+ for (UUID id : openedUI .keySet ()) {
204
+ Player p = Bukkit .getPlayer (id );
205
+ if (p == null ) continue ;
206
+ Integer page = openedUiPages .get (id );
207
+ if (page == null ) continue ;
208
+ if (page < 0 ) continue ;
209
+ if (page >= maxPage ) page = maxPage - 1 ;
210
+ newPages .put (p , page );
211
+ }
212
+ openedUI .clear ();
213
+ openedUiPages .clear ();
214
+ for (Map .Entry <Player , Integer > e : newPages .entrySet ()) {
215
+ showPlayerPage (e .getKey (), e .getValue ());
216
+ }
217
+ }
218
+
219
+ guiFrozen = false ;
220
+ }
96
221
}
0 commit comments