wendaokoujin 发表于 2025-4-20 18:11:54

北大侠客行Vscode版-js机器人示例讲解

我们以多个示例来讲解新版本客户端机器人代码的编写客户端核心函数其实就几个:
api.alias()
api.action()
api.send()
api.echo()
其它的都是标准的 js 开发


第一个示例:找师父学特殊内功

data/attachment/forum/367bdae758002a7dfb5beb6ace96850e.png

我们一行行来分析
1. api.loops.xuemaster = true;
[解释]:api是一个全局对象,里面包含了最基本的函数,也可以用它来记录全局变量,这里约定死循环的结束标识都记录在api.loops里,当然这不是必须的,可以随便用一个局部变量也没问题

2. api.alias('xuemaster.stop', function () { api.loops.xuemaster = false })
[解释]:这里定义一个别名,输入 xuemaster.stop 指令的时候,可以停止这个学技能的循环

3. api.alias('xuemaster', async () => {
[解释]:这里定义一个别名,输入 xuemaster 指令的时候,循环启动

4. while (api.loops.xuemaster) {
[解释]:死循环,但是如果api.loops.xuemaster=false,会结束循环

5. if (api.session.player.data.neili < 30) {
[解释]:api.session.player 是玩家的实时数据对象,里面包含了GMCP相关数据,主要是房间信息,人物属性,人物属性包含了各种血量、内力、精力、经验等等,都是实时的数据,你可以通过 api.echo(JSON.stringify(api.session.player)) 来打印详情,这句话的意思如果内力小于30就开始睡觉

6. await sys.retry('sleep', '你一觉醒来,精神抖擞地活动了几下手脚', 5, 60);
[解释]:这里的sys对象是一些工具函数的封装,第一次打开游戏的时候会自动创建 init.js ,sys对象里面包含了很多函数,可以直接在里面看源码,这个函数(sys.retry) 的意思是尝试 sleep,然后等待醒过来,会尝试60次,每隔5秒尝试一次,如果中间醒过来了,这个函数就执行结束,也就是 '你一觉醒来,精神抖擞地活动了几下手脚'触发了就表示醒过来
这里sleep 有多种可能性,比如马上睡着或者提示 刚睡过,不能再睡,我们在这里不管这么多,直接用同步等待的方式,直接不停的sleep,直到睡醒

7. if (api.session.player.data.jing < 20) {
[解释]:同样判断一下当时的精力如果小于 20 的话就吸气

8. sys.sendAndEcho("e2");
[解释]:看名称就知道,就是发送指令并打印指令到屏幕(便于调试),封装的 api.send() 和 api.echo()

9.await sys.sleep(1);
[解释]:等待1秒


10. this.autofoodwater();
[解释]:这个是封装的一个函数,如果食物<200就吃干粮,水小于200就在剑心居drink,这个函数很多地方用的上,就封装成一个函数

11. await sys.sleep(1);
[解释]:等待1秒

14. sys.sendAndEcho('xue ding for huagong-dafa 5;xue ding for literate 5');

[解释]:真正开始学技能

总结:尽量使用同步的方式,代码看上去逻辑很清晰,如果设置多个action也能异步实现,但是不够简洁。 当然这个只是示例,还有一些特殊情况,需要处理,比如师父跑了,比如没潜能了,比如超出师父的最高等级了等等,都需要处理,不过还是尽量按照同步的方式,做不到再考虑用action 和变量来处理


wendaokoujin 发表于 2025-4-22 20:39:08

本帖最后由 wendaokoujin 于 2025-4-22 08:43 PM 编辑

示例二api.alias 和 api.action 的基本用法,这个都是最基本的功能,但是要比一般的客户端功能更强,因为加入了函数

一. alias
1. api.alias 是设置别名
参数1是别名,参数2可以是指令,可以是函数,也可以是一个数组,数组里可以是字符串和函数,如果涉及到变量,用 $1,$2 替代变量,
api.alias("test_alias1", "pi;hi");
api.alias("test_alias2", () => { api.send("pi;hi"); });
api.alias("test_alias3", ["pi","hi",() => { api.send("jump;hi");}]);
data/attachment/forum/aefbf39c06f9db6bbab790b6fcb31e62.png
api.alias("test_alias4", "hi $1");
api.alias("test_alias5", function(args){
    api.send("hi " + args);
});
data/attachment/forum/9cf692c8a3b30964bd446acd5987e1b2.png
data/attachment/forum/3eecca708e4b32af1b890403e08389c2.png

2. api.unalias 取消别称
api.unalias("test_alias1");

二. action
1. api.action
参数1是action的文本或正则,参数2是指令,可以是可以是函数,也可以是一个数组,数组里可以是字符串和函数

api.action("test_action1", "pi;hi");
api.action("test_action2", () => { api.send("pi;hi"); });
api.action("test_action3", ["pi","hi",() => { api.send("jump;hi");}]);
data/attachment/forum/79b419640928fbe6e1aec2a8520a9494.png
data/attachment/forum/1538dd6037411f0d5d9c89048cecf943.png
data/attachment/forum/070351a7b856648ed122cad9c061a9f9.png
这里的 @test 是用来模拟触发action的

api.action(/http:\/\/fullme\.pkuxkx\.net\/robot\.php\?filename=(.*)/, '@fullme $1')
action可以是js的正则表达式,如果不会写js的正则,AI可以帮你

api.action(["test_action4","test_action5"], "hi");
还有一个特殊的用法,就是action还可以是一个字符串数组,可以用来匹配多行文本。





wendaokoujin 发表于 2025-4-22 21:34:46

示例三 完整的剑心居打坐、吐纳、学习技能、读书、领悟和练习的示例,大家可以参考修改
class Jianxinju {
//剑心居机器人
constructor(api, sys) {
    this.api = api;
    this.sys = sys;
    this.init();
}
init() {
    const self = this; // 保存this引用
    this.api.alias("learn_skills.stop", () => (self.api.loops.learn_skills = false));
    this.api.alias("learn_skills.start", async () => self.learn_skills());

    this.api.alias("read_shediao.stop", () => (self.api.loops.read_shediao = false));
    this.api.alias("read_shediao.start", async () => self.read_shediao());

    this.api.alias("dazuo.stop", () => (self.api.loops.dazuo = false));
    this.api.alias("dazuo.start", async () => self.dazuo());

    this.api.alias("tuna.stop", () => (self.api.loops.tuna = false));
    this.api.alias("tuna.start", async () => self.tuna());

    this.api.alias("lingwu_lianxi.stop", () => (self.api.loops.lingwu_lianxi = false));
    this.api.alias("lingwu_lianxi.start", async () => self.lingwu_lianxi());

    this.api.action("你的内力修为似乎已经达到了瓶颈。", "dazuo.stop");
    this.api.action("你的精力修为已经达到了瓶颈。", "tuna.stop");
    this.sys.actionOnce("你附近没有 ding 这个人,请用 id here 指令看看周围人物 id 。", "clap 3;teacher;", 60); //找师傅,60秒内只触发一次
}
//自动补充食物水
async auto_food_water() {
    let food = this.api.session.player.data.food;
    let water = this.api.session.player.data.water;
    if (water < 200) {
      this.sys.sendAndEcho("drink");
      await this.sys.sleep(1);
    }
    if (food < 200) {
      this.sys.sendAndEcho("eat gan liang");
      await this.sys.sleep(1);
    }
}
//睡眠恢复jing去做消耗jing的动作
async recover_jing() {
    if (this.api.session.player.data.neili < 30) {
      await this.sys.retry("sleep", "你一觉醒来,精神抖擞地活动了几下手脚", 5, 60);
    }
    if (this.api.session.player.data.jing < 50) {
      this.sys.sendAndEcho("e2");
      await this.sys.sleep(1);
    }
    this.auto_food_water();
    await this.sys.sleep(1);
}
async learn_skills() {
    this.api.loops.learn_skills = true;
    while (this.api.loops.learn_skills) {
      await this.recover_jing();
      this.sys.sendAndEcho("xue ding for huagong-dafa 5;xue ding for poison 5");
    }
    this.sys.log("学习技能循环结束");
}

async read_shediao() {
    this.api.loops.read_shediao = true;
    while (this.api.loops.read_shediao) {
      await this.recover_jing();
      this.sys.sendAndEcho("read shediao 5");
    }
    this.sys.log("读射雕循环结束");
}
async dazuo() {
    this.api.loops.dazuo = true;
    while (this.api.loops.dazuo) {
      let max_neili = this.api.session.player.data.max_neili;
      let neili = this.api.session.player.data.neili;
      let neilipercent = neili / max_neili;
      let qi = this.api.session.player.data.qi;
      if (neilipercent <= 1.2 && qi < 100) {
      await this.sys.retry("sleep", "你一觉醒来,精神抖擞地活动了几下手脚", 5, 30);
      }
      this.auto_food_water();
      if (neili > max_neili * 2 - 30) {
      await this.sys.try("dazuo 10", "你运功完毕,深深吸了口气,站了起来。", 10);
      } else {
      await this.sys.try("dazuo max", "你运功完毕,深深吸了口气,站了起来。", 60);
      }
      await this.sys.sleep(1);
    }
    this.sys.log("打坐循环结束");
}
async tuna() {
    this.api.loops.tuna = true;
    while (this.api.loops.tuna) {
      await this.recover_jing();
      let max_jingli = this.api.session.player.data.max_jingli;
      let jingli = this.api.session.player.data.jingli;
      if (jingli > max_jingli * 2 - 20) {
      await this.sys.try("tuna 10", '你吐纳完毕,睁开双眼,站了起来。', 10);
      } else {
      await this.sys.try("tuna max", '你吐纳完毕,睁开双眼,站了起来。', 60);
      }
    }
    this.sys.log("吐纳循环结束");
}
async lingwu_lianxi() {
    let self = this;
    let point = 20;
    let cmds = [
      'lian throwing ' + point, 'lingwu throwing ' + point,
      'lingwu force ' + point,
      'lian parry ' + point, 'lingwu parry ' + point,
      'lian dodge ' + point, 'lingwu dodge ' + point,
      'lian strike ' + point, 'lingwu strike ' + point]
    this.api.loops.lingwu_lianxi = true;
    this.api.vars.lingwu_lianxi = 0;
    this.api.action('你需要提高基本功,不然练得再多也没有用', () => (self.api.vars.lingwu_lianxi++));
    this.api.action('不能通过练习来提高了', () => (self.api.vars.lingwu_lianxi++));
    this.api.action('你已经达到你的技能等级极限', () => (self.api.vars.lingwu_lianxi++));
    this.api.action('你的基本功夫比你的高级功夫还高', () => (self.api.vars.lingwu_lianxi++));
    this.api.action('你需要提高基本功,不然练得再多也没有用', () => (self.api.vars.lingwu_lianxi++));
    this.api.action('受限于天资', () => (self.api.vars.lingwu_lianxi++));
    this.api.action('必须空手', 'unwield all;');
    try {
      while (this.api.loops.lingwu_lianxi) {
      this.auto_food_water();
      if (this.api.vars.lingwu_lianxi >= cmds.length) {
          this.api.vars.lingwu_lianxi = 0;
      }
      let neili = this.api.session.player.data.neili;
      let qi = this.api.session.player.data.qi;
      let jing = this.api.session.player.data.jing;
      this.sys.log('当前内力:' + neili + ' 当前气:' + qi+ ' 当前精力:' + jing)
      if (neili < 100 && (qi < 100|| jing < 100)) {
          await this.sys.retry("sleep", "你一觉醒来,精神抖擞地活动了几下手脚", 5, 30);
      }
      await this.sys.sleep(1);
      this.api.send('e1;e2;' + cmds)
      }
      this.sys.log("领悟练习结束");
    } catch (e) {
      this.sys.log("领悟练习失败:" + e.message);
      this.api.loops.lingwu_lianxi = false;
    }
}
}

module.exports = Jianxinju;


heiantls 发表于 2025-4-30 16:16:13

怎么加载脚本啊?

wendaokoujin 发表于 2025-5-1 06:37:32

heiantls 发表于 2025-4-30 04:16 PM
怎么加载脚本啊?
第一次打开游戏界面会自动创建2个js文件,index.js是整个脚本的入口,另外一个是init.js,是一些初始化的工具函数集合,然后你可以在index.js 里加载自己的js脚本,加载的方法可以参考index.js 加载 init.js 的写法,其实和正常的js没区别。
data/attachment/forum/6bd257ad37f6c8fda432321f0306f2ed.png

wendaokoujin 发表于 2025-5-1 17:04:12

增加GMCP事件触发,需要玩家自己定义这个回调函数,然后在回调函数里加上自己的代码,参考如下:
data/attachment/forum/024a5ea70e6aaa5b011505b81dccce10.png

type表示GMCP的类型,主要包括人物状态发生变化,人物所处的房间发生变化,人物战斗相关数据变化

wendaokoujin 发表于 2025-5-2 11:10:56

增加对 Lua 脚本的支持,参考示例
-- 插件入口
-- 1. 提供以下事件
-- onSend(text) 发送指令的监听
-- onGMCP(command, data) GMCP消息的监听
-- onReceive(text) 接收消息的监听
-- onReceiveRaw(text) 接收原始消息的监听

-- 2. 提供以下系统函数,都是以js_开头
-- js_send(text) 发送指令
-- js_echo(text) 输出文本到屏幕
-- js_fullme(name) fullme消息
-- js_test(text) 测试输出
-- js_go(target) 逍遥游
-- js_moyu() 摸鱼
-- js_note(text) 记事
-- js_disload() 取消加载lua脚本
-- js_reload() 重新加载lua脚本
-- js_set_timeout(delay,func) 设置定时器


local yourName = '' -- 你的名字
local yourPassword = '' -- 你的密码

function onReceive(text)
    if text:find('请输入new。):') then
      if yourName ~= '' then
            js_send(yourName)
      end
    end

    if text:find('此ID档案已存在,请输入密码:') then
      if yourPassword ~= '' then
            js_send(yourPassword)
            js_set_timeout(2,function()
                js_echo('欢迎进入江湖世界', false)
                js_send('look;hp;score;look')
            end)
      end
    end
    if text:find('您要将另一个连线中的相同人物赶出去') then
      js_send('y')
    end
    local regex = 'http://fullme%.pkuxkx%.net/robot%.php%?filename=(.*)'
    if text:find('fullme.pkuxkx.com') then
      js_fullme( text:match(regex))
    end
end

function onSend(text)
    js_echo('监控到发送指令:' .. text,false)
    if text:find('@go ') then
      -- 逍遥游
      js_go(text:match('@go (.*)'))
      return
    end
    if text:find('@note ') then
      -- 记事
      js_note(text:match('@note (.*)'))
      return
    end
    if text:find('@test') then
      -- 测试
      js_test(text:match('@test (.*)'))
      return
    end
    if text:find('@reload') then
      js_reload()
      return
    end
end
function onGMCP(command, data)
    js_echo(command,true)
    js_echo(data,true)
end没有封装alias和action,需要自己根据onReceive来生成

ppmm 发表于 2025-5-3 17:21:51

wendaokoujin 发表于 2025-5-2 11:10 AM
增加对 Lua 脚本的支持,参考示例
没有封装alias和action,需要自己根据onReceive来生成
...

有支持Python的打算吗

wendaokoujin 发表于 2025-5-3 17:29:30

ppmm 发表于 2025-5-3 05:21 PM
有支持Python的打算吗

可以支持,不过lua是轻量级的,可以直接在js里运行,所以效率高,python得是独立的进程,和js交互数据就没有这么通畅了。

jarlyyn 发表于 2025-5-3 17:50:48

wendaokoujin 发表于 2025-5-3 05:29 PM
可以支持,不过lua是轻量级的,可以直接在js里运行,所以效率高,python得是独立的进程,和js交互数据就 ...

这个主要看你的借口实现里有没有使用对象。

python的优势是能方便的调用各种库,所以不太能以vm的形式运行,必须走IPC.

IPC或者RPC的话,纯C风格的接口应该没问题,最多走一遍序列化,带类就比较蛋疼了。
页: [1] 2
查看完整版本: 北大侠客行Vscode版-js机器人示例讲解