|
|
本帖最后由 吾奶常煽赵子龙 于 2024-6-21 10:33 AM 编辑
上一篇我们讲了Ni脚本的简单结构。这一篇我们通过改写一个魔蜂的脚本来进一步熟悉下ni脚本该怎么写。
(这一篇需要有一定的编程基础,不然可能看不懂,这里篇幅有限,就不讲基础语法了)
下面是一个由魔蜂脚本改写的LR输出脚本。为了方便阅读,不会看懵,代码风格尽量保持魔蜂脚本的风格。直接上代码:
所有输出逻辑都在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] 纯文本查看 复制代码
[/size]
[size=3]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[i].guid) and ni.unit.threat(p, enemies[i].guid) ~= -1 then
if enemy == nil or enemy.distance > enemies[i].distance then
enemy = enemies[i];
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);
这样将整个输出逻辑拆分成多个小功能,看着更加的简单明了。
配置界面
上面脚本中有个自动拾取的功能。但是有时候我们不想自动拾取怎么办?是停止脚本还是临时修改脚本?
这时候可以增加一个配置界面,通过配置来开启/关闭自动拾取。见代码:
添加配置界面的步骤如下:
第一步:定义界面配置项内容,也就是上面代码中的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] 纯文本查看 复制代码
local queue = {
"死亡检查",
"切换目标",
"召唤宠物",
"守护检查",
"输出",
};
local abilities = {
["死亡检查"] = function() if UnitIsDeadOrGhost("player") return true end end,
["切换目标"] = function() ... end,
["召唤宠物"] = function() ... end,
["守护检查"] = function() ... end,
["输出"] = function() ... end,
};
[Lua] 纯文本查看 复制代码
[/size]
[size=3]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);[/size]
[size=3]
另外entry还有其他用法:
[Lua] 纯文本查看 复制代码
-- 一个文本输入框
{
type = "input",
value = "",
width = 225,
height = 65,
wordwrap = true,
key = "WhatToPrint"
}
-- 值的读取方式:读取value中的值
local text = [size=3]GetSetting([/size]"WhatToPrint"[size=3]);[/size]
在配置项可以显示图标,比如上面的 text = ni.spell.icon("乱射") .. " AOE配置" 就是在文字 AOE配置 前面添加乱射技能的图标。
更加详细的可以参考 Loader.DLL.v0.0.65\addon\Rotations\Generic\GUIExample.lua。
配置使用优化
上面在读取配置信息的时候使用的 GetSetting() 函数,是通过遍历items来查看对应key的值。还有另外一种方式:
[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 = [size=3]GetSetting([/size]"ActionEvent"[size=3]);[/size]
在 items 中可以添加一个 callback,当配置被修改后会将最新的值回调给GUICallback,然后在GUICallback中将最新的值保存起来,要使用的地方直接使用 enables.AutoLootEnabled 来判断就可以了:
[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"
}
配置项的key命名规则建议使用lua的变量命名规则:以字母或下划线开头,不包含空格。比如上面的 key = "AutoLootEnabled" 。这样我们就可以直接将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"
},
};
如果包含了空格或者是中文,比如:key = "Auto Loot Enabled"。那么在使用的时候就只能这样:
[Lua] 纯文本查看 复制代码
local function AutoLoot()
-- 直接使用 enables.AutoLootEnabled 进行判断
if not enables.AutoLootEnabled then
-- 没有开启自动拾取
return;
end
...
end
|
|