HuaiShu.
本帖最后由 HuaiShu. 于 2022-9-24 19:19 编辑


我把我的世界玩家人头用数字来代替,怎么写才能使用玩家人头这个数字来打开第二页GUI面板.
编程使用的版本的我的世界:1.12
编程用软件是:idea

美年达呀
本帖最后由 美年达呀 于 2022-9-25 11:53 编辑

标题和内容意义不明
大概理解为在一个Gui窗体中,点击一个物品达到跳转Gui的效果
考虑 ClickInventoryEvent 事件中判断一下打开的窗体的是否为当前你的这个Gui的窗体,同时对相关Gui的Slot做一下监听!
校验方法是否是你这个人头窗体很简单:低版本直接GetTitle比对一下就行

如何达到跳转页面也很简单,在ClickInventoryEvent 在判断点击的窗体的格子是否是你这个玩家头的格子!如果是则通过Player.openInventory(Inventory)打开即可!



HuaiShu.
美年达呀 发表于 2022-9-25 11:52
标题和内容意义不明
大概理解为在一个Gui窗体中,点击一个物品达到跳转Gui的效果
考虑 ClickInventoryEvent ...

这个要怎么写呀,大佬可以教一下吗

2000000
我初步的看了一下您的要求,似乎不是很清楚那么接下来我将以我所理解的进行回答
我将初步理解为您想要在一个已有的GUI内跳转到其他的GUI

首先,我们需要监听一个事件,监听玩家点击背包的事件 (InventoryClickEvent)
接下来 我们需要一些安全措施 来防止意外情况

if (event.getCurrentItem() == null || event.getInventory() == null || event.getWhoClicked() == null || event.getClick() == null) return;


接下来,我们要获取玩家所点击的插槽 (slot)

int rSlot = event.getRawSlot();


接下来,我们需要进行判断,判断玩家所点击的插槽,是否为你需要的点击位置

if (rSlot == 1) player.openInventory();


若玩家所点击的插槽为1 那么将打开其他的inv

一些建议:
1. 在gui内跳转到另一个gui时可以不关闭玩家的背包,这将会有更加丝滑的效果,即玩家的鼠标位置不会重置
2. 如果在旧gui中有计划任务,如物品更新等,那么请使用一些手段来判断玩家是否不在旧gui中,若不在了则对此计划任务进行cancel 否则此任务将一直运行

我知道这个回答可能非常迟, 希望对您有所帮助 :)

三英战吕布
2000000 发表于 2022-10-24 15:10
我初步的看了一下您的要求,似乎不是很清楚那么接下来我将以我所理解的进行回答
我将初步理解为您想要在一 ...

你好,你的第二点建议中的手段指哪些,能举一些例子吗

2000000
本帖最后由 2000000 于 2022-10-25 13:42 编辑
三英战吕布 发表于 2022-10-24 20:33
你好,你的第二点建议中的手段指哪些,能举一些例子吗

好的。
接下来我会以我的想法回答您的问题,我会尽量避免代码投喂,而是让您明白您在写什么东西

首先,让我们制作两个 menu 类,以及两个打开 menu 的 command.

接下来,我将称呼这两个 menu 为 menu1 与 menu2, cmd1 与 cmd2

cmd1 将打开 menu1 菜单, cmd2 将打开 menu2 菜单。


首先,让我们写完这些 menu 以及 commands.


menu:

    public void openMenu1(Player player) {
        Inventory menu1 = Bukkit.createInventory(player, 9, "menu1");
        ItemStack item1 = new ItemStack(Material.DIAMOND_SWORD,1);
        ItemMeta meta = item1.getItemMeta();
        new BukkitRunnable() {
            int i = 0;
            @Override
            public void run() {
                meta.setDisplayName(String.valueOf(i));
                item1.setItemMeta(meta);
                menu1.setItem(1, item1);
                Bukkit.getLogger().info("[menu1] set done");
                i ++;
            }
        }.runTaskTimerAsynchronously(main.instance, 0, 20);
        player.openInventory(menu1);
    }
    public void openMenu2(Player player) {
        Inventory menu2 = Bukkit.createInventory(player, 9, "menu2");
        ItemStack item1 = new ItemStack(Material.WOOD_SWORD,1);
        ItemMeta meta = item1.getItemMeta();
        new BukkitRunnable() {
            int i = 0;
            @Override
            public void run() {
                meta.setDisplayName("menu2 " + i);
                item1.setItemMeta(meta);
                menu2.setItem(1, item1);
                Bukkit.getLogger().info("[menu2] set done");
                i ++;
            }
        }.runTaskTimerAsynchronously(main.instance, 0, 20);
        player.openInventory(menu2);
    }


注册指令:


        getCommand("cmd1").setExecutor(new cmd1());
        getCommand("cmd2").setExecutor(new cmd2());

首先我们需要避免一个问题,那就是尽量的不使用 sout (System.out.println) 输出文本,而是使用 Bukkit.getLogger 进行输出


System.out.println 是一个同步方法,在高并发的情况下,会严重影响性能

详细请自行查看源码 源码中含有 synchronized 同步锁


让我们来理解一下上面所写的代码:


我们创建了一个 Inventory 名为 menu1 大小为 9 格 标题为 menu1

随后 我们创建了一个 ItemStack 名为 item1 为钻石剑 数量为 1

我们获取了 item1 的 ItemMeta 名为 meta

随后,我们创建了一个计划任务,首先我们定义了 int 变量 i 为 0

此任务执行的内容为: 设置 meta 的名字为 i  (setDisplayName 需要传入 String 作为名字,但是 i 为 int 类型,所以我们需要使用 String.valueOf(i) 转换为 String 类型)

随后,我们设置了 item1 的 ItemMeta 为 meta 并且将 menu1 的 1 插槽设置为了物品 item1

设置完成后,我们使用 Bukkit.getLogger().info 向控制台输出信息 并且将 i 的数值增加 每20ticks执行一次 (20ticks = 1s)

随后打开背包


menu2 与 menu1 相似,只不过物品不同,与信息内容不同


接下来让我们构建出来 并在游戏内进行测试。





可以看到 物品的更新是没有任何问题的





控制台的消息也没有任何问题


接下来,让我们关闭背包,我们可以发现,这个计划任务依然在进行,它依然在控制台不停的输出消息。哪怕我们已经离线,此计划任务依然在继续运行。





如果玩家进行多次这样的操作,那么这将会是一个比较严重的效率问题,接下来我们使用 cancel 来尝试解决在这个问题


首先我们需要判断此玩家是否离线,如果离线的话,则直接 cancel 此计划任务




                if (!player.isOnline()) {
                    cancel();
                    return;
                }




当玩家处于离线状态,那么我们 cancel 此计划任务

并且使用 return 来避免剩余的代码被执行 (若只是cancel 依然会执行剩余代码)


接下来,我们需要创建一个 HashMap 来保存其玩家的状态 (是否开启或关闭背包) 并在计划任务内对玩家的状态进行判断 具体如下


我们先创建一个 InventoryManager 类 并创建一个 HashMap 来存储, 并写两个方法来获取以及设置玩家状态 如下:



public class InventoryManager {
    private static final Map<UUID, Boolean> closedInv = new HashMap<>();

    public static boolean getClosedInv(Player player) {
        return closedInv.getOrDefault(player.getUniqueId(), true); // getOrDefault 意思就是当Map中有这个key时 就查找key对应的value值 如果没有就使用默认值 即 true
    }

    public static void setClosedInv(Player player, boolean flag, Plugin plugin) {
        // 由于hashmap 线程不是安全的 所以我们需要同步执行put
        Bukkit.getScheduler().runTask(plugin, () -> closedInv.put(player.getUniqueId(), flag));  // put 插入 注: 一个key所对应的value值只会有一个,所以如果玩家在put之前已经拥有value 则会直接覆盖 不用先remove
    }
}


HashMap 内存储玩家的UUID 以及 布尔值


请注意: HashMap不是线程安全的


接下来,我们就需要在玩家开启带有 计划任务 的 gui 时,将该玩家的 closedInv 设置为 false

随后,当此玩家关闭背包时 (InventoryCloseEvent) 我们需要设置此玩家的 closedInv 为true

并且在计划任务内,对此玩家的 closedInv 状态进行判断,若为 true 则 cancel 并 return


这看起来非常简单,让我们自己动手写一下


当玩家关闭 Inventory 的时候,我们赋予 true 如下



    @EventHandler

    public void onPlayerCloseInventory(InventoryCloseEvent event) {

        InventoryManager.setClosedInv((Player) event.getPlayer(), true, main.instance);

    }


计划任务则可以这样写:


        new BukkitRunnable() {
            int i = 0;
            @Override
            public void run() {
                if (!player.isOnline() || InventoryManager.getClosedInv(player)) {
                    cancel();
                    return;
                }

                meta.setDisplayName(String.valueOf(i));
                item1.setItemMeta(meta);
                menu1.setItem(1, item1);
                Bukkit.getLogger().info("[menu1] set done");
                i ++;
            }
        }.runTaskTimerAsynchronously(main.instance, 20, 20); // 这里第二个参数为等待多少ticks 开始执行这个任务 第三个为间隔


为什么要等待20ticks才执行这个计划任务?


(我们假设,有一个玩家A 打开了带有计划任务的gui 当玩家在关闭此gui的时候 我们put了一个true)

(由于此计划任务在设置为false 的前面,也就是说如果不延迟,那么这个计划任务检查到玩家A的 closedInv 则是false 这将意味着这个gui还没打开 但是计划任务就已经结束了)


接下来,我们要在玩家开启带有计划任务的gui时 给予一个false


        player.openInventory(menu1);
        InventoryManager.setClosedInv(player, false, main.instance);


接下来 我们已经完成了 menu1 的完整代码如下:


    public void openMenu1(Player player) {
        Inventory menu1 = Bukkit.createInventory(player, 9, "menu1");

        ItemStack item1 = new ItemStack(Material.DIAMOND_SWORD,1);
        ItemMeta meta = item1.getItemMeta();

        new BukkitRunnable() {
            int i = 0;
            @Override
            public void run() {
                if (!player.isOnline() || InventoryManager.getClosedInv(player)) {
                    cancel();
                    return;
                }

                meta.setDisplayName(String.valueOf(i));
                item1.setItemMeta(meta);
                menu1.setItem(1, item1);
                Bukkit.getLogger().info("[menu1] set done");
                i ++;
            }
        }.runTaskTimerAsynchronously(main.instance, 20, 20);

        player.openInventory(menu1);
        InventoryManager.setClosedInv(player, false, main.instance);
    }


因为我写这个回复的时候脑袋不是非常清醒,可能有一些地方不够细致,望谅解


希望能够帮到您 :)







烂柯人beta
简单看了下 我理解为你需要做一个可以翻页的gui

如果是 则可以通过自定义InventoryHolder来实现你所说的功能

具体代码手机打起来太费劲 这里放几个比较关键的点

首先新建类并继承InventoryHolder

然后声明int page作为页数标记

然后在构建函数中初始化page为1或者传入的参数

最后在创建一个新的inventory是使用该自定义holder 比如

Bukkit.createInventory(new MyHolder(),27,title)

当要判定当前页数时就先获取holder

inv.getHolder().getPage()

P.S. 建议构造函数传入page 当需要其他参数时最好写方法来修改参数

三英战吕布
2000000 发表于 2022-10-25 13:41
好的。
接下来我会以我的想法回答您的问题,我会尽量避免代码投喂,而是让您明白您在写什么东西

感谢dalao,明白了