前言
最近在玩乌龟魔兽世界(Turtle-WoW)《艾泽拉斯之谜》,一个基于 60 级香草时代的故事进行拓展的资料片,它“旨在走一条与燃烧远征完全不同的道路, 强调魔兽世界中熟悉的艾泽拉斯,而不是与燃烧军团的宇宙战斗”。这部资料片增加了新的种族,修改了部分技能与天赋,增加了新的五人副本与团队副本,还有新的声望、地图、任务、装备物品等等。除此之外幻化系统和一命硬核模式也增添不少乐趣。
游戏基于 WoW 1.12 版本,存在很多不便,遨游艾泽拉斯大陆之余,我尝试了很多有趣/有用的插件(Addon),同时由于部分插件存在兼容性的问题,我们需要对其进行优化修复,或者自行编写脚本宏来辅助游戏提升便利性。在其间我发现一个神级插件 LazyScript,通过编写脚本可以实现一键操作。
1、LazyScript 介绍
项目主页:https://github.com/laytya/LazyScript/
使用 WiKi:https://github.com/laytya/LazyScript/wiki/
LazyScript 是魔兽世界的一种脚本语言执行器,底层基于 Lua 编程语言,它能够在指定的条件下施放某些技能或能力,这是通过编写一个 “脚本” 来实现的,该脚本由一系列动作和判断条件组成。其能实现的功能受限于使用者的能力与想法,默认自带了各个职业的一些基础脚本,但是想满足自己的功能需求和使用场景,最好的办法就是编写新的脚本,有能力的话完全就能一键做到任何事。
1.1 传统的 WoW Lua 宏命令,或者自己创建一个插件
【众多的技能列表】
60 年代号称香草时代,是众多玩家的美好回忆,但同时其复杂的技能列表导致一系列繁琐的操作,好在早期的 WoW 版本中暴雪开放了大量的 API,为玩家带来了各种便利,通过插件以及宏,可以一定程度上完善游戏体验,帮助 PvE 玩家解放双手。WoW 插件使用 Lua 这一种轻量小巧的脚本语言进行开发。我们举两个宏的例子:
-
一键删除背包中的所有灰色品质的物品
/run ClearCursor()local g,i,j,s,a,b=gsub;for i=0,4 do for j=1,GetContainerNumSlots(i)do s=GetContainerItemLink(i,j)if(s)then a,b,s=GetItemInfo(g(g(s,".*\124H",""),"\124h.*",""))if(s==0)then PickupContainerItem(i,j)DeleteCursorItem()end;end;end;end
-
德鲁伊猫形态根据条件自动施放技能、进行攻击等,节选 Source: https://www.bilibili.com/read/cv27358549/
-- 获取当前目标的连击点数,如果小于 3 则施放撕碎技能
/script if GetComboPoints("target") < 3 then CastSpellByName("撕碎") end;
-- 获取玩家自身的能量值,如果小于等于 27 并未处于节能施法状态则施放 “猎豹形态(变形)” 技能
/run local rage1 = UnitMana("player");if rage1 <= 27 and not buffed("节能施法") then CastSpellByName("猎豹形态(变形)") end;
看起来语法很复杂,但是比起一个个点击摧毁背包中的灰色垃圾物品和紧盯屏幕上角色的状态要方便很多了。
如果你有兴趣,可以查阅 WoW 的 API 列表以及学习 Lua 编程语言。参考链接如下:
【API 文档】
当然你也可以自己创建一个有界面的插件。
1.2 LazyScript 一键操作,完全解放双手
但是上面的宏命令还是太复杂了,如果想要实现一键全技能,那么需要臃肿的代码量来支持,复杂的条件判断及循环增加了巨大的开发和维护成本,不一定能带来流畅的体验。有没有好办法呢,答案是 LazyScript,我们来看使用 LazyScript 的示例:
-------------------------------------------------
--【Druid Cat in Dungeons】
-- 元素猫副本一键输出脚本 by 思兼
-------------------------------------------------
-- 起手如果不是猫形态则变身,和结尾取消变身呼应,配合头盔狼心附魔+激怒天赋获取能量
cat-ifNotHasBuff=cat
-- 更换武器,也可以根据目标类型使用最合适的装备和饰品
equipMainHand=无尽黑暗之刃-ifNotEquipped=无尽黑暗之刃-ifHasBuff=cat
equipOffHand=修复的电灯-ifNotEquipped=修复的电灯-ifHasBuff=cat
-- 战斗状态,防止破隐身,CCd失能,使用武器,精灵火野性和普通技能名称不同
-- ravage60/tigersFury30/shred60(48)/bite35/claw42
-- TODO:修改顺序和范围TargetInLongRange Blind/Long/Medium/Melee
-- TODO:频繁变身会取消猛虎之怒
-- TODO:终结技撕咬会清空能量,撕扯不会
-- 默认优先打终结技,使用节能施法
-- 爪击等也有 1s 共享冷却(GCD)
-- tigersFury-ifTargetInLongRange-ifNotHasBuff=tigersFury
feralFire-ifTargetInMeleeRange-ifNotTargetHasDebuff=faerieFire-ifInCombat
stopAll-ifTargetIs=CCd
rip-=5cp-ifNotTargetHasDebuff=rip-ifTargetElite-ifLastUsed>12s=rip-ifNotTargetType=元素生物
rip-=5cp-ifNotTargetHasDebuff=rip-ifTargetBoss-ifLastUsed>12s=rip-ifNotTargetType=元素生物
bite-=5cp-ifTargetHasDebuff=rip
bite-=5cp-ifTargetType=元素生物,机械
bite-=5cp-ifNotTargetElite
bite-=5cp-ifNotTargetBoss
bite-ifLastChance-ifInGroup
bite-ifKillShot
shred-ifNotBehindAttackJustFailed-ifHasBuff=clearcasting
shred-ifNotBehindAttackJustFailed-ifPlayer>52energy
shred-ifNotBehindAttackJustFailed-<3cp
-- 上一行对应上述 /script if GetComboPoints("target") < 3 then CastSpellByName("撕碎") end;
cower-ifTargetOfTarget-ifInGroup-ifInInstance
cancelBuff=cat-ifHasBuff=cat-ifNotHasBuff=clearcasting-ifPlayer<27energy
-- 上一行对应上述 /run local rage1 = UnitMana("player");if rage1 <= 27 and not buffed("节能施法") then CastSpellByName("猎豹形态(变形)") end;
同样是根据一系列条件来执行对应的动作,我们可以看到使用 LazyScript 写的脚本更加简洁易懂。除此之外,
- 由于 LazyScript 仅有一个激活脚本,为了实现更多功能,它支持脚本复用和根据条件触发引用脚本, 从而提升效率。
- LazyScript 支持调用游戏内已有的宏,也支持被其他宏调用,从而拓展功能,缓解 LazyScript 同时只有一个脚本激活的问题。
举例如下:
脚本复用:
脚本内相同的部分可以分离到另一个脚本中,并使用 includeForm 将其包含在其他脚本中。如下将脚本 “打断” 的内容加入新脚本的最前面,相当于你将整个脚本复制粘贴进去了。当你修改更改脚本 “打断” 的内容时,它将自动更新此脚本。
includeForm=打断
evisc-5cp
bs
如果需要根据条件 “跳转” 到另一脚本,可以使用 callForm,如下所示,仅当目标正在施法且没有昏迷时,才会调用 “打断” 脚本。当目标没有施法时,不会调用任何 “打断” 脚本的内容,从而提升脚本运行效率。
callForm=打断-ifTargetIsCasting-ifNotTargetIs=Stunned
riposte
evisc-5cp
ss
被宏调用或者调用宏:
---- 系统宏 【游泳】 :德鲁伊变身水栖形态后,装备加快游泳速度的装备 ----
-- /施放 水栖形态(变形)
/equipslot 13 60470
/equipslot 14 60860
-------------------------------------------------------------
-- 1. 在脚本中调用上述宏:
action=游泳-ifHasbuff=aquatic
-- 2. 在自定义宏中调用 LazyScript:自动加 Buff
/run -- cast("Frost Armor")
/ls do intellect@player-ifNotHasBuff=intellect,brilliance
/ls do iceArmor-ifNotHasBuff=mageArmor,iceArmor
/ls do dampenMagic@player-ifNotHasBuff=dampenMagic-ifShiftDown
/ls do iceBarrier-ifGotTalent=Ice%sBarrier
1.3 使用 LazyScript 及面临的问题
当 LazyScript 宏运行时,LazyScript 插件将从上到下读取动作和判断条件列表,直到找到一个满足判断条件的行,然后执行对应的操作。所以使用这一神级插件拢共分三步:
- 设置默认激活脚本
- 创建一个自定义宏命令,内容为
/lazyscript
或 /ls
- 将宏命令拖到动作条上,使用时狂按它
最初我的方法是将此宏的游戏内快捷键设置为鼠标滚轮上下,由此别人打本是狂按各种技能,我是狂滚鼠标滚轮,时间久了手指也很崩溃好吧。
所以如何进一步让手指头歇一歇呢,由此终于引出了本文的主角。当然比起这个,自己调试 LazyScript 更有趣,不信你试试。在踩了各种坑之后,我总结出一个规律,判断条件不要怕多,确定一行的动作一定执行或者一定不执行。
2、基于 Pico 的简易按键连点器
2.1 解放手指头的方案
我想了一下,需求就是一直按 ls 宏命令的快捷键。
- 软件方案:尝试使用按键精灵写一个脚本,优点是简单方便。缺点是支持的控制按键有限(如F10/F12等),无法使用单个鼠标侧键启动/停止。此外换电脑玩的话还需要再次安装软件。启停需要按电脑上的指定按键,比较麻烦。硬件方案:使用自定义功能的 HID 输入设备,按下开始按钮后循环按下宏快捷键,按下停止按钮后,停止操作。
2.2 候选者登场
硬件方案要选择合适的设备模拟为 HID 输入设备(键盘鼠标等),翻了下手头的,我们把目光转向三名选手:
- 树莓派 Pico
- micro:bit
- DFRobot Sparrow
2.2.1 Micro:Bit 蓝牙键盘
板载资源相当丰富,按钮/指示灯/蓝牙/可控灯板/麦克风等。我的第一优选方案,结合GamePad可以手柄控制,岂不美哉?
蓝牙键盘(HID)
参考库:microbit-pxt-blehid 0.0.24 (bsiever/microbit-pxt-blehid)
实际测试下来不稳定/不好用,着急用,暂时不研究了。
串口处理转发
另一种方案就是电脑下运行一个监听服务器,接收 Micro:Bit 串口传来的数据,对应处理,模拟按键。
参考库:
在 Windows 下使用 Python/安装组件比较麻烦/有报错,暂时不研究。
之前做过DFrobot Sparrow(Arduino Leonardo Like)做的 HID 设备,不过它的 Bootloader 有问题,最近没空弄。
【脑洞大赛】Sparrow 开发板化身电脑音量调节器
2.2.3 树莓派 Pico
板载资源:
- BootSel 按钮,用于烧录固件
- LED 指示灯
有点不够看啊,但是上面俩伙计不给力,只能 Pico 出场了。
2.3 Pico 获胜,如何应对
Pico 其实很强大,毕竟有可编程 IO,不过有如下问题:
- BootSel 默认用于上传固件,无法作为普通按钮使用
- 只有一个板载按钮,要实现按一下切换启停
参考资料如下:
我们使用 ArduinoIDE 编写程序,完整代码如下:
#include <Keyboard.h>
// 键盘连点器 by 思兼
int ledState = LOW; // 初始状态为灭
int lastButtonState = LOW; // 上一次按钮的状态
int currentButtonState = LOW; // 当前按钮的状态
char f12Key = KEY_F12; //自定义按钮
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, ledState);
Keyboard.begin();
}
void loop() {
do {
currentButtonState = BOOTSEL; //0101
delay(50); // 延迟一段时间,防止按钮抖动影响
if (currentButtonState == HIGH && lastButtonState == LOW) {
ledState = !ledState; // 切换灯的状态10
digitalWrite(LED_BUILTIN, ledState);
}
if (ledState) {
// Keyboard.write('b');
Keyboard.write(f12Key);
delay(500);
}
lastButtonState = currentButtonState; //010
} while (ledState); //0
}
由此我们实现了如下功能:
- Pico 插入电脑后会被识别为 USB 键盘
- 按下 BootSel 按钮,点亮状态指示 LED,同时间隔 0.5s 循环向电脑发送 F12(自定义执行 LazyScript 宏的快捷键)
- 再次按下 BootSel 按钮,熄灭 LED,停止发送 F12
演示视频如下:
【Link】
3、超进化:脚踏式键盘连点器
虽然实现了即插即用,不需要再每台电脑上安装软件,虽然大部分时间都开着自动连点即可,但是小按钮(板载或外置)关闭开启还是麻烦,毕竟要手离开键盘鼠标,有没有更好的办法哩?我们干脆做一个脚踏式开关,<u>真-解放双手</u>。
thangs.com 上搜索下有没有思路,关键词 foot switch
,找到一款设计还不错的:USB Foot Switch Controller 或者这个:USB Foot Switch Teensy 3.1
改改模型适配 Pico,让家里的 3D 打印机起来干活,配上微动开关美滋滋。我们同时需要一个状态指示器,可以在物理桌面上放置一个LED等,也可以写一个后端程序,在软件电脑桌面上显示图标等。
4、究极进化:魔兽世界控制台
前段时间朋友问我可不可以用那种设计师键盘控制搭载 Klipper 固件的 3D 打印机,正好可以用来操控魔兽世界。大概长这样:
那我们干脆自己用 Pico 做一个,配合魔兽世界的快捷键/自定义宏等实现各种功能,比如:
- 一键开启 LazyScript 自动脚本
- 一键变身游泳状态,换上游泳装备;或者上坐骑装备胡萝卜
- 旋钮缩放界面/调整视角
- 一键冲锋龙蛋开火车
- 圣骑士无敌炉石,顺便聊天频道留言:”哥已不在江湖,但是江湖上还有哥的传说!“