LUACN论坛

 找回密码
 加入我们

QQ登录

只需一步,快速开始

搜索
热搜: YJWOW MagicStone BoL
查看: 961|回复: 58

【Ni辅助保姆级高级教程】六、自动下图

  [复制链接]
发表于 2024-6-23 22:04:44 | 显示全部楼层 |阅读模式
本帖最后由 吾奶常煽赵子龙 于 2024-6-24 01:23 PM 编辑

前面几篇讲了Ni的一些入门知识。这一篇来讲讲通过Ni如何实现地图传送功能。


游戏背景

主城里有个球形的传送NPC(如图):

(图1)

NPC信息为:

(图2)

假设我们要传送到新手副本下的死亡矿井。步骤是首先和这个NPC对话,然后依次选择【新手副本】、【死亡矿井】:

(图3)


(图4)

我们来看看用Ni该怎么做。

第一步,移动到NPC位置

要使用这个NPC的传送功能,首先肯定是要靠近这个NPC(假设当前我们已经在主城了)。核心代码如下:
[Lua] 纯文本查看 复制代码
-- 将传送员设置为玩家的当前目标
ni.player.target("传送员");

-- 向目标移动
ni.player.moveto("target");

这里我们直接以NPC的名字作为目标移动。大多数情况下这样是没问题的。

但是某些时候,游戏数据有可能没有完整加载(比如某些差点的sf),NPC的名字看到的可能是“未知目标”。这种情况下通过名字处理就会出问题。解决办法是用NPC的ID(比如上面图二里这个NPC的ID是60000),具体用法为:
[Lua] 纯文本查看 复制代码
-- 通过NPC的ID,获取NPC的GUID
-- NPC在玩家附近才能获取得到
local function GetNpcGUIDById(npcId)
    -- ni.objects 会自动缓存玩家周围一定范围内的单位信息,我们直接从ni.objects中遍历查找就可以了
    -- ni.unit.id()用于获取指定单位的ID
    for guid, obj in pairs(ni.objects) do
        if type(guid) == "string" and ni.unit.id(guid) == npcId then
            return guid;
        end
    end
end

-- 根据NPC的ID获取该NPC的GUID

local guid = GetNpcGUIDById(60000);

-- 直接通过npc的guid进行移动
ni.player.moveto(guid);

上面这样直接通过目标的guid进行移动,就可以避免“未知目标”的问题。

wow中每个单位的GUID都是唯一的。官方也有函数可以获取指定单位的GUID,比如获取目标单位的guid:guid = UnitGUID("target")。

第二步:和NPC交互

然后就是和npc对话了:
[Lua] 纯文本查看 复制代码
ni.player.interact(guid);


当然,这里的参数也可以是目标(具体看ni的官方API文档):
[Lua] 纯文本查看 复制代码
ni.player.interact("target");

这样就打开了上面的图3了。

第三步:选择【新手副本】、【死亡矿井】

这个该怎么选呐?这里介绍一个命令:/fstack。它用来显示游戏中各窗口、各组件的名字。比如我们在游戏的聊天窗口输入这个命令后,将鼠标放到上面图3的窗口上,就会有如下显示:

上面会有一个“框架栈”的提示窗(显示在游戏界面的上下左右四个角之一位置)。几个带编号的信息里,最下面是一个<0> UIParent,这个是我们游戏的主界面(可以理解为游戏中的所有组件都是这个窗口的子元素)。UIParent上面的GossipFrame就是我们这个窗口的名字(后续会用到)。我们继续将鼠标放到“新手副本”上面,就会看到下面的提示:

可以看到“新手副本”的组件名字是“GossipTitleButton1”(后续会用到),对于GossipTitleButtonX的选项可以用SelectGossipOption(i)函数来进行选择,我们这里“新手副本”是第一个选项,所以应该是:
[Lua] 纯文本查看 复制代码
SelectGossipOption(1);


同样,第二页的“死亡矿井”是第二项,那么就应该是:
[Lua] 纯文本查看 复制代码
SelectGossipOption(2);


按照上面几步,就可以传送到“死亡矿井”了。完整代码如下:
游客,如果您要查看本帖隐藏内容请回复

上面的代码里用数字标注了大致的执行顺序。

这里我们用到了Ni的几个关键函数:
ni.player.moveto(guid):向指定单位移动;
ni.player.interact(guid):和指定单位交互(目标是友方npc就是对话,目标是矿就是挖矿,目标是草药就是采药,目标是鱼漂就是钓鱼等等);
ni.unit.id(guid):获取指定单位的ID;



补充知识
在某些情况下我们可能想要遍历当前显示的所有GossipTitleButton,假设又不确定数量有多少,该怎么处理呐?通过上面我们可以看出GossipTitleButton1、GossipTitleButton2这些其实都是一个全局变量。在wow里全局变量除了可以直接访问之外,还可以通过_G访问。比如我们想要遍历当前的所有GossipTitleButton,那么可以这么做:
[Lua] 纯文本查看 复制代码
-- 通过NPC的ID,获取NPC的GUID
-- NPC在玩家附近才能获取得到
local function GetNpcGUIDById(npcId)
     -- ni.objects 会自动缓存玩家周围的单位信息,我们直接从ni.objects中遍历查找就可以了
    -- ni.unit.id()用于获取指定单位的ID
    for guid, obj in pairs(ni.objects) do
        if type(guid) == "string" and ni.unit.id(guid) == npcId then
            return guid;
        end
    end
end

-- 这个加不加看情况,某些服传送有延迟,就需要加一下
local hasTransmitting = false;

local function Test()
    -- 如果在传送了(地图加载),则只检查是否传送到了副本门口,不再重复执行下面的逻辑了。
    if hasTransmitting then
        if GetSubZoneText() == "死亡矿井" then
            print("传送成功,停止脚本");
            
            -- 6: 停止通用脚本
            ni.togglegeneric(ni.vars.profiles.generic);
            hasTransmitting = false;
        end
        return;
    end

    -- 2: 如果正在移动中(向NPC移动)就暂时不处理,直接return
    if ni.player.ismoving() then
        return;
    end

    -- 通过传送NPC的ID,获取该单位的GUID
    local guid = GetNpcGUIDById(60000);

    -- 1: 判断和传送NPC的距离,如果大于1码就向传送NPC移动
    if ni.player.distance(guid) > 1 then
        ni.player.moveto(guid);
        return;
    end

    -- 3: 判断如果传送界面没有打开,就和npc对话
    if not GossipFrame or not GossipFrame:IsVisible() then
        -- 和传送NPC对话
        ni.player.interact(guid);
        -- 界面打开有延迟,这里让脚本延迟个0.5秒
        ni.rotation.delay(0.5);
        return;
    end

    -- 走到这里说明传送界面已经打开了
    -- 判断第一个选项是否显示了,但是由于第一页和第二页都有第一个选项,所以还需要进一步判断
    if GossipTitleButton1:IsVisible() then
        -- 获取第一个选项的文字
        local text = GossipTitleButton1:GetText();
        
        -- 4: 如果文字里包含里"新手副本",就选择这个选项
        -- 注意这里用的是包含,而不是等于,因为text里还可能包含其他信息,比如图标
        if string.find(text, "新手副本") then
            SelectGossipOption(1);
            ni.rotation.delay(0.5);
            return;
        end
    end

    -- 判断第二个选项是否显示了,GossipTitleButton后面的数字是几就代表第几项
    if GossipTitleButton2:IsVisible() then
        -- 获取第二个选项的文字
        local text = GossipTitleButton2:GetText();
        
        -- 5: 为了保险起见,我们还是判断一下选项的文本内容是否包含了死亡矿井
        if string.find(text, "死亡矿井") then
            SelectGossipOption(2);
            
             -- 传送后我们加一个标志,让上面的代码不会重复执行
            hasTransmitting = true;
        end
    end
end

local queue = {
    "进入死亡矿井"
};

local abilities = {
    "进入死亡矿井" = Test
};










本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?加入我们

x

评分

参与人数 1伸手费 +1000 收起 理由
老血 + 1000 很给力!

查看全部评分

回复

使用道具 举报

发表于 2024-6-24 09:26:45 | 显示全部楼层
这里我们用到了Ni的几个关键函数
回复 支持 反对

使用道具 举报

发表于 2024-6-24 09:29:45 | 显示全部楼层
大佬牛皮,膜拜下,让我看看看你藏了撒!
回复 支持 反对

使用道具 举报

发表于 2024-6-24 09:39:29 | 显示全部楼层
内容简单明了通俗易懂
回复 支持 反对

使用道具 举报

发表于 2024-6-24 15:17:52 | 显示全部楼层
感谢大佬的无私奉献
回复 支持 反对

使用道具 举报

发表于 2024-6-24 18:16:13 | 显示全部楼层
大佬牛皮,膜拜下,让我看看看你藏了撒!
感谢大佬的无私奉献
回复 支持 反对

使用道具 举报

发表于 2024-6-24 22:12:21 | 显示全部楼层
大佬牛皮,膜拜下
回复 支持 反对

使用道具 举报

发表于 2024-6-25 00:11:17 | 显示全部楼层
不错的教程,点个赞
回复 支持 反对

使用道具 举报

发表于 2024-6-25 15:15:29 | 显示全部楼层
好的好的继续跟学~岁数大了 有点跟不上
回复 支持 反对

使用道具 举报

发表于 2024-6-28 23:07:58 | 显示全部楼层
感觉是好东西~~~~~~~~回复一下看看内容~~~~~
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 加入我们

本版积分规则

小黑屋|手机版|Archiver|LUACN论坛

GMT+8, 2026-11-13 08:49 AM , Processed in 0.043845 second(s), 7 queries , Gzip On, Redis On.

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表