|
|
发表于 2026-9-5 14:58:48
|
显示全部楼层
local format = format;
local p = "player"
local t = "target"
-- 搜寻附近的可攻击目标
-- maxDistance:最大距离范围,只搜寻该范围内的怪
function searchTarget(maxDistance)
    local hasTarget = ni.unit.exists(t);
    maxDistance = tonumber(maxDistance) or 40;
    if not hasTarget then
        ni.debug.print("目标搜寻:当前没有目标,寻找新目标");
    elseif UnitIsDeadOrGhost(t) then
        ni.debug.print("目标搜寻:目标死亡,寻找新目标");
    elseif not UnitCanAttack(p, t) then
        ni.debug.print("目标搜寻:目标不能攻击,寻找新目标");
    elseif not ni.player.los(t) then
        ni.debug.print("目标搜寻:目标不在视野中,寻找新目标");
    elseif ni.unit.isimmune(t) then
        ni.debug.print("目标搜寻:目标免疫攻击,寻找新目标");
    elseif ni.unit.threat(p, t) == -1 then
        ni.debug.print("目标搜寻:目标没有进入战斗,寻找新目标");
    elseif ni.unit.los(p, t) then
        -- 有可攻击的目标,后面会继续判断该目标是否是最近的目标
    end
   
    -- 找到最近的那个敌人
    local enemies = ni.player.enemiesinrange(maxDistance);
    local enemy = nil;
    for i = 1, #enemies do
        if ni.unit.los(p, enemies
.guid) and ni.unit.threat(p, enemies.guid) ~= -1 then
            if enemy == nil or enemy.distance > enemies
.distance then
                enemy = enemies
;
            end
        end
    end
   
    if enemy == nil then
        ni.debug.print("目标搜寻:附近没有可攻击目标");
        
        if hasTarget then
            ni.player.runtext("/cleartarget");
        end
        return false;
    end
   
    if hasTarget then
        if UnitGUID(t) == enemy.guid then
            ni.debug.print("目标搜寻:当前目标就是最近目标,不用切换");
            return true;
        else
            ni.player.runtext("/cleartarget");
        end
    end
   
    -- 将最近的敌人设置为目标
    ni.player.target(enemy.guid);
   
    -- 面对敌人
    if not ni.unit.isfacing(p, t, 90) then
        --ni.player.moveto(enemy.guid)
        ni.player.lookat(t);
    end
   
    ni.debug.print(format("目标搜寻:搜寻到可攻击目标【%s】", enemy.name));
    return true;
end
-- 智能输出
local function SmartAttack()
    -- 人物死亡或灵魂状态
    if UnitIsDeadOrGhost(p) then
        return;
    end
   
    -- 在载具上或移动中
    if UnitInVehicle(p) or ni.unit.ismoving(p) then
        return;
    end
    -- 如果没进战斗
    if not UnitAffectingCombat(p) then
        return;
    end
   
    -- 切换目标
    local targetExist = searchTarget(50);
    -- 玩家当前血量
    local health = ni.player.hp();
    -- 是否有活着的宠物
    local hasPet = (UnitExists("pet") and not UnitIsDead("pet"));
    -- 当前蓝量
    local mana = ni.power.current(p);
    -- 生命低于80%时向目标释放胁迫
    if health < 80 and targetExist and hasPet and ni.player.petcd("胁迫") == 0 then
        ni.spell.cast("胁迫", t);
        return;
    end
   
    -- 没有目标时尝试切换到宠物的目标
    if not targetExist then
        local pt = "pettarget";
        if hasPet and UnitExists(pt) then
            if not UnitIsDeadOrGhost(pt)
               and UnitCanAttack(p, pt)
               and ni.player.los(pt)
               and not ni.unit.isimmune(t) -- 免疫攻击
               and ni.unit.distance(p, pt) <= 31 then
                -- 切换到宠物的目标
                ni.player.target(pt);
            end
        end
        return;
    end
   
    -- 正在施法中
    if ni.unit.iscasting(p) or ni.unit.ischanneling(p) then
        return;
    end
   
    -- 强击光环
    if not ni.unit.aura(p, "强击光环") and ni.spell.available("强击光环") then
        ni.spell.cast("强击光环");
        return;
    end
   
    if mana <= 10 then
        -- 蓝量小于10%时切换到蝰蛇守护
        if not ni.unit.aura(p, "蝰蛇守护") and ni.spell.available("蝰蛇守护") then
            ni.spell.cast("蝰蛇守护");
            return;
        end
    elseif mana < 80 then
        -- 蓝量在10-80%时如果没有龙鹰守护也没有蝰蛇守护,则使用蝰蛇守护
        if not ni.unit.aura(p, "龙鹰守护") and not ni.unit.aura(p, "蝰蛇守护") and ni.spell.available("蝰蛇守护") then
            ni.spell.cast("蝰蛇守护");
            return;
        end
    else
        -- 蓝量高于80%切换到龙鹰守护
        if not ni.unit.aura(p, "龙鹰守护") and ni.spell.available("龙鹰守护") then
            ni.spell.cast("龙鹰守护");
            return;
        end
    end
   
    -- 释放误导(这里的误导是一个光环,和官服不一致)
    if hasPet and not ni.unit.aura(p, "误导") and ni.spell.available("误导") then
        ni.spell.cast("误导", "pet");
        return
    end
   
    -- 目标不在前方左右90度范围内时,转向面对目标
    if not ni.unit.isfacing(p, t, 90) then
        ni.player.lookat(t);
    end
   
    if ni.unit.debuffremaining(t, "毒蛇钉刺", p) <= 0 and ni.spell.available("毒蛇钉刺") then  
        ni.spell.cast("毒蛇钉刺", t);
        return;
    end
   
    local targetHealth = ni.unit.hp(t);
    if targetHealth < 20 and ni.spell.available("杀戮射击") then
        ni.spell.cast("杀戮射击", t);
        return;
    end
   
    if hasPet and ni.spell.available("杀戮命令") then
        ni.spell.cast("杀戮命令");
        return;
    end
   
    if ni.spell.available("奥术射击") then
        ni.spell.cast("奥术射击", t);
        return;
    end
    local isBoss = ni.unit.isboss(t);
    if isBoss then
        if ni.unit.debuffremaining(t, "猎人印记") <= 0 and ni.spell.available("猎人印记") then
            ni.spell.cast("猎人印记", t);
            return;
        end
    end
   
    if UnitPlayerControlled(t) then
        if ni.unit.debuffremaining(t, "瞄准射击") <= 0 and ni.spell.available("瞄准射击") then   
            ni.spell.cast("瞄准射击", t);
            return;
        end
    else
        if ni.spell.available("多重射击") then   
            ni.spell.cast("多重射击", t);
            return;
        end
    end
        
    if hasPet and ni.spell.available("狂野怒火") then
        ni.spell.cast("狂野怒火");
        return;
    end
        
    if isBoss then
        if ni.spell.available("急速射击") then
            ni.spell.cast("急速射击");
            return;
        end
        
        if ni.unit.buffremaining(p, "急速射击") <= 0 and ni.spell.available("准备就绪") then
            ni.spell.cast("准备就绪");
            return;
        end        
    end
   
    ni.spell.cast("稳固射击", t);
end
local needLoot = true;           -- 是否需要拾取
local lootToolName = "范围拾取"; -- 一键拾取道具名字
-- 自动拾取
local function AutoLoot()   
    -- 没有被标记为可以拾取
    if not needLoot then
        return;
    end
   
    -- 在坐骑上(且是移动中)或在载具中,则放弃拾取
    if (ni.unit.ismounted(p) and ni.unit.ismoving(p)) or UnitInVehicle(p) then
        needLoot = false;
        return;
    end
   
    -- 公共CD中或被眩晕了
    if ni.spell.gcd() or ni.player.isstunned() then
        return;
    end
   
    -- 使用一键拾取道具进行拾取
    if ni.player.hasitem(lootToolName) then
        ni.player.useitem(lootToolName);
    end
    needLoot = false;
end
local queue = {
    "Attack",
    "Loot"
};
local abilities = {
    ["Attack"] = SmartAttack,
    ["Loot"] = AutoLoot
};
local function CombatEventCatcher(event, ...)
    -- 处理战斗日志事件,当有目标死亡时标记可以进行一次范围拾取
    if event == "COMBAT_LOG_EVENT_UNFILTERED" then        
        local _, subevent, _, sourceName, _, _, destName = ...;
        if subevent == "UNIT_DIED" and destName ~= UnitName(p) then
            needLoot = true;
        end
    end
end
local function OnLoad()
    ni.vars.debug = true
    -- 启动脚本的时候注册游戏事件(不清楚游戏事件的可以先了解下wow的event机制)
    ni.combatlog.registerhandler("Hunter-Auto-Attack-Event", CombatEventCatcher);
end
local function OnUnload()
    -- 停止脚本的时候取消注册的游戏事件
    ni.combatlog.unregisterhandler("Hunter-Auto-Attack-Event");
end
ni.bootstrap.profile("智能输出-猎人", queue, abilities, OnLoad, OnUnload);
所有输出逻辑都在SmartAttack()函数里。上面的代码注释已经非常详细了,具体逻辑就不再赘述了,只解释几个ni的函数。
ni.debug.print:用于输出调试信息,当 ni.vars.debug = true 的时候,会在游戏的综合窗口里打印日志信息。
ni.player.enemiesinrange:获取玩家周围指定范围内的敌人。实际调用的是:ni.unit.enemiesinrange(unit, range)。用这个可以获取任何单位指定范围内的敌人。
ni.unit.threat(unit1, unit2):获取unit1对unit2的威胁值状态,-1表示没有威胁,也就是unit2没有进入战斗。返回值的具体含义可以参考官方函数UnitThreatSituation。
ni.player.runtext:用于执行宏命令,比如代码中的 ni.player.runtext("/cleartarget") 就是执行 /cleartarget 这个清除目标的宏。
ni.unit.isfacing(unit1, unit2, 90):检查unit2是否在unit1正前方左右90度的扇形范围内。
ni.player.lookat(unit):转向,面对unit。
ni.unit.isimmune(unit):判断指定单位是否免疫攻击。
ni.player.isstunned():判断玩家是否被眩晕了。
ni.player.los(unit):判断玩家和指定目标是否有视线碰撞(两者之间有没有卡视角)。
ni.spell.available(spell):判断指定技能是否准备好可以释放了。
ni.player.hasitem(item):判断玩家背包中是否有指定物品。
ni.player.itemcd(item):获取指定物品的cd时间。
ni.player.itemready(item):判断指定物品是否准备好可以使用了。
ni.player.useitem(item):使用背包中的指定物品。
其他ni的函数看不明白的可以去官方文档查看。
注意:官方文档更新不及时,如果发现api效果和文档描述不一致的时候,可以去 Loader.DLL.v0.0.65\addon\Core\ 下面的源码里查看具体的实现。
上面我们将输出逻辑全部写在了一个函数里,其实我们还可以利用queue顺序执行且可中断的机制,可以将逻辑拆成多个行为放到queue里。比如:
[Lua] 纯文本查看 复制代码
local queue = {
    "死亡检查",
    "切换目标",
    "召唤宠物",
    "守护检查",
    "输出",
};
local abilities = {
    ["死亡检查"] = function() if UnitIsDeadOrGhost("player") return true end end,
    ["切换目标"] = function() ... end,
    ["召唤宠物"] = function() ... end,
    ["守护检查"] = function() ... end,
    ["输出"] = function() ... end,
};
这样将整个输出逻辑拆分成多个小功能,看着更加的简单明了。
配置界面
上面脚本中有个自动拾取的功能。但是有时候我们不想自动拾取怎么办?是停止脚本还是临时修改脚本?
这时候可以增加一个配置界面,通过配置来开启/关闭自动拾取。见代码:
本帖隐藏的内容
[Lua] 纯文本查看 复制代码
local format = format;
local p = "player"
local t = "target"
local items = {
    settingsfile = "Hunter.json",
    { type = "title", text = "LR输出配置" },
    { type = "separator" },
    {
        type = "entry",
        text = "自动拾取",
        tooltip = "目标死亡后进行自动拾取。",
        enabled = false,
        key = "AutoLoot"
    },
};
local function GetSetting(name)
    for k, v in ipairs(items) do
        if v.type == "entry"
        and v.key ~= nil
        and v.key == name then
            return v.value, v.enabled;
        end
        
        if v.type == "dropdown"
        and v.key ~= nil
        and v.key == name then
            for k2, v2 in pairs(v.menu) do
                if v2.selected then
                    return v2.value;
                end
            end
        end
        
        if v.type == "input"
        and v.key ~= nil
        and v.key == name then
            return v.value;
        end
    end
end;
-- 搜寻附近的可攻击目标
-- maxDistance:最大距离范围,只搜寻该范围内的怪
function searchTarget(maxDistance)
    ...
end
-- 智能输出
local function SmartAttack()
    ...
end
local needLoot = true;           -- 是否需要拾取
local lootToolName = "范围拾取"; -- 一键拾取道具名字
-- 自动拾取
local function AutoLoot()
    local _, autoLootEnabled = GetSetting("AutoLoot")
    if not autoLootEnabled then
        -- 没有开启自动拾取
        return;
    end
   
    -- 没有被标记为可以拾取
    if not needLoot then
        return;
    end
   
    -- 在坐骑上(且是移动中)或在载具中,则放弃拾取
    if (ni.unit.ismounted(p) and ni.unit.ismoving(p)) or UnitInVehicle(p) then
        needLoot = false;
        return;
    end
   
    -- 公共CD中或被眩晕了
    if ni.spell.gcd() or ni.player.isstunned() then
        return;
    end
   
    -- 使用一键拾取道具进行拾取
    if ni.player.hasitem(lootToolName) then
        ni.player.useitem(lootToolName);
    end
    needLoot = false;
end
local queue = {
    "Attack",
    "Loot"
};
local abilities = {
    ["Attack"] = SmartAttack,
    ["Loot"] = AutoLoot
};
local function CombatEventCatcher(event, ...)
    -- 处理战斗日志事件,当有目标死亡时标记可以进行一次范围拾取
    if event == "COMBAT_LOG_EVENT_UNFILTERED" then        
        local _, subevent, _, sourceName, _, _, destName = ...;
        if subevent == "UNIT_DIED" and destName ~= UnitName(p) then
            needLoot = true;
        end
    end
end
local function OnLoad()
    ni.vars.debug = true
   
    -- 启动脚本的时候注册游戏事件(不清楚游戏事件的可以先了解下wow的event机制)
    ni.combatlog.registerhandler("Hunter-Auto-Attack-Event", CombatEventCatcher);
   
    -- 添加配置界面
    ni.GUI.AddFrame("Hunter-Auto-Attack-Ui", items);
end
local function OnUnload()
    -- 停止脚本的时候取消注册的游戏事件
    ni.combatlog.unregisterhandler("Hunter-Auto-Attack-Event");
   
    -- 销毁配置界面
    ni.GUI.DestroyFrame("Hunter-Auto-Attack-Ui");
end
ni.bootstrap.profile("智能输出-猎人", queue, abilities, OnLoad, OnUnload);
添加配置界面的步骤如下:
第一步:定义界面配置项内容,也就是上面代码中的items。其中:
            settingsfile:指定保存配置的文件名,将保存在 Loader.DLL.v0.0.65\addon\Settings\ 目录下面;
            { type = "title", text = "LR输出配置" }:type是"title",表示是一个标题(其实就是一个普通文本),text是界面上显示的文本内容;
            { type = "separator" }:type是"separator",表示是一个分割线;
            { type = "entry", ... }:type是"entry"的有多种样式,text是界面上显示的文字,tooltip是鼠标放上去后的提示文字,enabled是选项的开启/关闭状态(默认状态),key是该项配置的唯一key,后续将通过这个key来获取该配置的值。
第二步:创建配置界面,OnLoad函数里的:ni.GUI.AddFrame("Hunter-Auto-Attack-Ui", items) 表示用items的配置信息创建一个配置界面。第一个参数是一个key,后续用来销毁界面(见OnUnload)。
由于上面代码是在OnLoad中创建配置界面的,所以当脚本启动后才能看得见配置脚本。配置界面在屏幕中间顶部,鼠标放上去后配置界面就会显示出来。
第三步:获取配置(具体见上面的AutoLoot()函数)。调用上面的 GetSetting() 方法获取配置的值,参数就是每个配置项的key。
配置项目除了上面的entry,还有input和dropdown:
[Lua] 纯文本查看 复制代码
-- 一个文本输入框
{
        type = "input",
        value = "",
        width = 225,
        height = 65,
        wordwrap = true,
        key = "WhatToPrint"
}
-- 值的读取方式:读取value中的值
local text =
GetSetting("WhatToPrint");
[Lua] 纯文本查看 复制代码
-- 下拉选择框
{
        type = "dropdown",
        menu = {
           -- selected = true 是默认选中项
            { selected = true,  value = 1, text = "跳跃" },
            { selected = false, value = 2, text = "向前移动" },
            { selected = false, value = 3, text = "向后移动" },
            { selected = false, value = 4, text = "向右平移" },
            { selected = false, value = 5, text = "向左平移" },
        },
        key = "ActionEvent"
}
-- 值的读取方式:读取value中的值
local value =
GetSetting("ActionEvent");
另外entry还有其他用法:
[Lua] 纯文本查看 复制代码
{
        type = "entry",
        text = ni.spell.icon("乱射") .. " AOE配置",
        tooltip = "周围怪物数量达到设定值后使用|cFF00FF00乱射|r。",
        enabled = false,
        value = 3,
        min = 2,
        max = 10,
        step = 1,
        width = 40,
        key = "AoE"
}
在配置项可以显示图标,比如上面的 text = ni.spell.icon("乱射") .. " AOE配置" 就是在文字 AOE配置 前面添加乱射技能的图标。
更加详细的可以参考 Loader.DLL.v0.0.65\addon\Rotations\Generic\GUIExample.lua。
配置使用优化
上面在读取配置信息的时候使用的 GetSetting() 函数,是通过遍历items来查看对应key的值。还有另外一种方式:
[Lua] 纯文本查看 复制代码
local enables = {
     AutoLootEnabled = false,
};
local function GUICallback(key, item_type, value)
             if item_type == "enabled" then
                          enables[key] = value;
             end
end
local items = {
         settingsfile = "Hunter.json",
         callback = GUICallback,
         { type = "title", text = "LR输出配置" },
         { type = "separator" },
         {
                  type = "entry",
                  text = "自动拾取",
                  tooltip = "目标死亡后进行自动拾取。",
                  enabled = enables.AutoLootEnabled,
                  key = "AutoLootEnabled"
         },
};
在 items 中可以添加一个 callback,当配置被修改后会将最新的值回调给GUICallback,然后在GUICallback中将最新的值保存起来,要使用的地方直接使用 enables.AutoLootEnabled 来判断就可以了:
[Lua] 纯文本查看 复制代码
local function AutoLoot()
    -- 直接使用 enables.AutoLootEnabled 进行判断
    if not enables.AutoLootEnabled then
        -- 没有开启自动拾取
        return;
    end
   
    ...
end
配置项的key命名规则建议使用lua的变量命名规则:以字母或下划线开头,不包含空格。比如上面的 key = "AutoLootEnabled" 。这样我们就可以直接将key当做变量使用,比如:
[Lua] 纯文本查看 复制代码
if not enables.AutoLootEnabled then
    return
end
如果包含了空格或者是中文,比如:key = "Auto  Loot  Enabled"。那么在使用的时候就只能这样:
[Lua] 纯文本查看 复制代码
if not enables["Auto  Loot  Enabled"] then
    return
end
|
|