2727public class LandmarkMenu implements InventoryHolder {
2828
2929 private static final String LANDMARK_KEY = "landmark_name" ;
30+ private static final String NAVIGATION_KEY = "landmark_nav" ;
31+ private static final String NAV_PREVIOUS = "previous" ;
32+ private static final String NAV_NEXT = "next" ;
3033 private static final int INVENTORY_COLUMNS = 9 ;
31- private static final int MAX_MENU_COLUMNS = 7 ;
34+ private static final int MENU_COLUMNS = 7 ;
3235 private final LandmarkPlugin plugin ;
3336 private final UUID playerId ;
3437 private Inventory inventory ;
3538 private final MiniMessage miniMessage = MiniMessage .miniMessage ();
3639 private final NamespacedKey landmarkKey ;
40+ private final NamespacedKey navigationKey ;
41+ private int currentPage = 0 ;
42+ private int rowsPerPage ;
43+ private int slotsPerPage ;
44+ private int totalPages = 1 ;
3745
3846 public LandmarkMenu (LandmarkPlugin plugin , Player player ) {
3947 this .plugin = plugin ;
4048 this .playerId = player .getUniqueId ();
4149 this .landmarkKey = new NamespacedKey (plugin , LANDMARK_KEY );
50+ this .navigationKey = new NamespacedKey (plugin , NAVIGATION_KEY );
4251 }
4352
4453 @ Override
@@ -50,14 +59,24 @@ public void open() {
5059 Component title = plugin .getConfigManager ().getMessage ("gui.title" , "<gold>锚点传送菜单</gold>" );
5160 int size = plugin .getConfigManager ().getConfig ().getInt ("gui.size" , 54 );
5261 this .inventory = Bukkit .createInventory (this , size , title );
62+ this .currentPage = 0 ;
5363 initializeItems ();
5464 getPlayer ().openInventory (inventory );
5565 }
5666
5767 private void initializeItems () {
68+ if (inventory == null ) {
69+ return ;
70+ }
71+
72+ inventory .clear ();
5873 setupBorder ();
74+
75+ List <Map .Entry <String , Landmark >> orderedLandmarks = plugin .getLandmarkManager ().getOrderedLandmarks ();
76+ recomputePagination (orderedLandmarks .size ());
77+ setupLandmarks (orderedLandmarks );
78+ setupNavigationControls ();
5979 setupCurrentLocation ();
60- setupLandmarks ();
6180 }
6281
6382 private void setupBorder () {
@@ -95,12 +114,17 @@ private ItemStack createBorderItem() {
95114 }
96115
97116 private void setupCurrentLocation () {
117+ Player player = getPlayer ();
118+ if (player == null ) {
119+ return ;
120+ }
121+
98122 Landmark currentLandmark = null ;
99123 boolean isAtAnyLandmark = false ;
100124
101125 for (Map .Entry <String , Landmark > entry : plugin .getLandmarkManager ().getLandmarks ().entrySet ()) {
102- if (plugin .getLandmarkManager ().isLandmarkUnlocked (getPlayer () , entry .getKey ())
103- && plugin .getLandmarkManager ().isPlayerNearLandmark (getPlayer () , entry .getValue ().getLocation ())) {
126+ if (plugin .getLandmarkManager ().isLandmarkUnlocked (player , entry .getKey ())
127+ && plugin .getLandmarkManager ().isPlayerNearLandmark (player , entry .getValue ().getLocation ())) {
104128 isAtAnyLandmark = true ;
105129 currentLandmark = entry .getValue ();
106130 break ;
@@ -115,26 +139,34 @@ private void setupCurrentLocation() {
115139 }
116140 }
117141
118- private void setupLandmarks () {
119- int maxMenuRow = Math .max (1 , (inventory .getSize () / INVENTORY_COLUMNS ) - 2 );
120- for (Map .Entry <String , Landmark > entry : plugin .getLandmarkManager ().getLandmarks ().entrySet ()) {
142+ private void setupLandmarks (List <Map .Entry <String , Landmark >> orderedLandmarks ) {
143+ if (slotsPerPage <= 0 ) {
144+ return ;
145+ }
146+
147+ Player player = getPlayer ();
148+ if (player == null ) {
149+ return ;
150+ }
151+
152+ int startIndex = currentPage * slotsPerPage ;
153+ int endIndex = Math .min (startIndex + slotsPerPage , orderedLandmarks .size ());
154+
155+ for (int index = startIndex ; index < endIndex ; index ++) {
156+ Map .Entry <String , Landmark > entry = orderedLandmarks .get (index );
121157 Landmark landmark = entry .getValue ();
122- int row = landmark .getMenuRow ();
123- int col = landmark .getMenuColumn ();
124158
125- // 确保位置在有效范围内(第2-倒数第二行,即索引1-maxMenuRow,列1-7)
126- if (row < 1 || row > maxMenuRow || col < 1 || col > MAX_MENU_COLUMNS ) {
127- plugin .getSLF4JLogger ().warn ("锚点 {} 的位置 ({}, {}) 超出有效范围,将在下次加载时自动修复" ,
128- landmark .getName (), row , col );
159+ int offset = index - startIndex ;
160+ int row = (offset / MENU_COLUMNS ) + 1 ;
161+ int col = (offset % MENU_COLUMNS ) + 1 ;
162+ int slot = (row * INVENTORY_COLUMNS ) + col ;
163+ if (slot < 0 || slot >= inventory .getSize ()) {
129164 continue ;
130165 }
131166
132- // 计算实际槽位(row 为去除边框后的行索引)
133- int slot = (row * INVENTORY_COLUMNS ) + col ;
134- boolean isUnlocked = plugin .getLandmarkManager ().isLandmarkUnlocked (getPlayer (), entry .getKey ());
167+ boolean isUnlocked = plugin .getLandmarkManager ().isLandmarkUnlocked (player , entry .getKey ());
135168 ItemStack item = createLandmarkItem (landmark , isUnlocked );
136169
137- // 存储锚点名称到物品中
138170 ItemMeta meta = item .getItemMeta ();
139171 if (meta != null ) {
140172 PersistentDataContainer container = meta .getPersistentDataContainer ();
@@ -146,6 +178,70 @@ private void setupLandmarks() {
146178 }
147179 }
148180
181+ private void recomputePagination (int totalLandmarks ) {
182+ rowsPerPage = Math .max (1 , (inventory .getSize () / INVENTORY_COLUMNS ) - 2 );
183+ slotsPerPage = Math .max (1 , rowsPerPage * MENU_COLUMNS );
184+ totalPages = Math .max (1 , (int ) Math .ceil (totalLandmarks / (double ) slotsPerPage ));
185+ if (currentPage >= totalPages ) {
186+ currentPage = totalPages - 1 ;
187+ }
188+ }
189+
190+ private void setupNavigationControls () {
191+ int bottomRowStart = inventory .getSize () - INVENTORY_COLUMNS ;
192+ if (bottomRowStart < 0 ) {
193+ return ;
194+ }
195+
196+ ItemStack indicator = createPageIndicatorItem ();
197+ inventory .setItem (bottomRowStart + (INVENTORY_COLUMNS - 1 ), indicator );
198+
199+ if (totalPages <= 1 ) {
200+ return ;
201+ }
202+
203+ ItemStack previous = createNavigationItem (
204+ "<#c7a3ed><bold>上一页</bold>" ,
205+ "<gray><bold>已是第一页</bold>" ,
206+ NAV_PREVIOUS ,
207+ currentPage > 0 );
208+ ItemStack next = createNavigationItem (
209+ "<#c7a3ed><bold>下一页</bold>" ,
210+ "<gray><bold>已是最后一页</bold>" ,
211+ NAV_NEXT ,
212+ currentPage < totalPages - 1 );
213+
214+ inventory .setItem (bottomRowStart + 1 , previous );
215+ inventory .setItem (bottomRowStart + (INVENTORY_COLUMNS - 2 ), next );
216+ }
217+
218+ private ItemStack createNavigationItem (String activeName , String inactiveName , String action , boolean enabled ) {
219+ Material material = enabled ? Material .ARROW : Material .GRAY_STAINED_GLASS_PANE ;
220+ String displayName = enabled ? activeName : inactiveName ;
221+
222+ ItemStack item = new ItemStack (material );
223+ ItemMeta meta = item .getItemMeta ();
224+ if (meta != null ) {
225+ meta .displayName (miniMessage .deserialize (displayName ));
226+ if (enabled ) {
227+ meta .getPersistentDataContainer ().set (navigationKey , PersistentDataType .STRING , action );
228+ }
229+ item .setItemMeta (meta );
230+ }
231+ return item ;
232+ }
233+
234+ private ItemStack createPageIndicatorItem () {
235+ ItemStack item = new ItemStack (Material .PAPER );
236+ ItemMeta meta = item .getItemMeta ();
237+ if (meta != null ) {
238+ String title = String .format ("<#c7a3ed><bold>第 %d / %d 页</bold>" , currentPage + 1 , totalPages );
239+ meta .displayName (miniMessage .deserialize (title ));
240+ item .setItemMeta (meta );
241+ }
242+ return item ;
243+ }
244+
149245 private ItemStack createCurrentLocationItem (boolean isAtAnyLandmark , Landmark currentLandmark ) {
150246 Material material = isAtAnyLandmark
151247 ? Material .valueOf (Objects .requireNonNull (plugin .getConfigManager ().getConfig ().getString ("gui.items.current.material" , "CONDUIT" )))
@@ -319,6 +415,12 @@ private void handleItemClick(ItemStack item, Player player) {
319415 }
320416
321417 PersistentDataContainer container = meta .getPersistentDataContainer ();
418+ String navigationAction = container .get (navigationKey , PersistentDataType .STRING );
419+ if (navigationAction != null ) {
420+ handleNavigationClick (navigationAction );
421+ return ;
422+ }
423+
322424 String landmarkName = container .get (landmarkKey , PersistentDataType .STRING );
323425
324426 if (landmarkName != null && plugin .getLandmarkManager ().isLandmarkUnlocked (player , landmarkName )) {
@@ -329,6 +431,16 @@ private void handleItemClick(ItemStack item, Player player) {
329431 }
330432 }
331433
434+ private void handleNavigationClick (String action ) {
435+ if (NAV_PREVIOUS .equals (action ) && currentPage > 0 ) {
436+ currentPage --;
437+ initializeItems ();
438+ } else if (NAV_NEXT .equals (action ) && currentPage < totalPages - 1 ) {
439+ currentPage ++;
440+ initializeItems ();
441+ }
442+ }
443+
332444 private Player getPlayer () {
333445 return Bukkit .getPlayer (playerId );
334446 }
0 commit comments