jarlyyn
发表于 2021-11-18 00:08:18
由于这种 到某个地方,执行某个命令,进入某个状态是一种常见的需求,所以将这块提取出来
建立一个Active数据结构
(function(app){
let Active=function(location,cmd,final,nobusy){
this.Location=location
this.Command=cmd?cmd:""
this.FinalState=final?final:""
this.Nobusy=nobusy?true:false
this.Data=null
}
Active.prototype.WithLocation=function(location){
this.Location=location
return this
}
Active.prototype.WithCommand=function(cmd){
this.Command=cmd?cmd:""
return this
}
Active.prototype.WithFinalState=function(final){
this.FinalState=final?final:""
return this
}
Active.prototype.WithNobusy=function(nobusy){
this.Nobusy=nobusy?true:false
return this
}
Active.prototype.WithData=function(data){
this.Data=data
return this
}
Active.prototype.Start=function(){
let transitions
if (this.Nobusy){
transitions=["core.state.active.move","nobusy","core.state.active.execute","nobusy"]
}else{
transitions=["core.state.active.move","core.state.active.execute"]
}
let a=app.Automaton.Push(this.FinalState,transitions)
a.WithData("Active",this)
app.ChangeState("ready")
}
returnhttps://github.com/hellclient-scripts/pkuxkx.noob/blob/b1e774a8530981fb45916e97839ab2ce4bae3772/script/include/active.js
修改goods状态
(function (app) {
let basicstate = Include("core/state/basicstate.js")
let StateGoods=function(){
basicstate.call(this)
this.ID="goods"
}
StateGoods.prototype = Object.create(basicstate.prototype)
StateGoods.prototype.Enter=function(context,oldstatue){
basicstate.prototype.Enter.call(this,context,oldstatue)
let item=app.GetContext("Item")
let a=app.NewActive(item.Location,item.Command,"",true)
a.WithData(item)
a.Start()
}
return StateGoods
})(App)
引入 "core.state.active.move" "core.state.active.execute" "checkitem" 三个状态
https://github.com/hellclient-scripts/pkuxkx.noob/blob/b1e774a8530981fb45916e97839ab2ce4bae3772/script/core/state/active/activemove.js
https://github.com/hellclient-scripts/pkuxkx.noob/blob/b1e774a8530981fb45916e97839ab2ce4bae3772/script/core/state/active/activeexecute.js
https://github.com/hellclient-scripts/pkuxkx.noob/blob/b1e774a8530981fb45916e97839ab2ce4bae3772/script/core/state/statecheckitem.js
jarlyyn
发表于 2021-11-19 13:30:36
接下来把一些重要模块也状态化了。
比如
等待模块,等待一定延时后进入后去状态。为了避免出现问题,做了一个生成unid再判断的流程
(function (app) {
let basicstate = Include("core/state/basicstate.js")
let StateWait=function(){
basicstate.call(this)
this.ID="wait"
this.SN=""
}
StateWait.prototype = Object.create(basicstate.prototype)
StateWait.prototype.OnEvent=function(context,event,data){
switch(event){
case "core.state.wait.after":
if (this.SN==data){
app.ChangeState("ready")
}
break
default:
Move.prototype.Enter.call(this,context,event,data)
}
}
StateWait.prototype.Enter=function(context,oldstatue){
basicstate.prototype.Enter.call(this,context,oldstatue)
let delay=app.GetContext("Delay")
let sn=world.GetUniqueID()
this.SN=sn
world.DoAfterSpecial(delay, 'App.OnStateEvent("core.state.wait.after","'+sn+'")', 12);
}
return StateWait
})(App)
https://github.com/hellclient-scripts/pkuxkx.noob/blob/3b0aac646ea0fafa68348ecbbec06cc53cb41b9e/script/core/state/statewait.js
然后重写了proposal部分,成功返回需要执行的proposal
https://github.com/hellclient-scripts/pkuxkx.noob/blob/3b0aac646ea0fafa68348ecbbec06cc53cb41b9e/script/core/proposal.js
将propsal都状态化
https://github.com/hellclient-scripts/pkuxkx.noob/blob/3b0aac646ea0fafa68348ecbbec06cc53cb41b9e/script/core/proposal/cash.js
https://github.com/hellclient-scripts/pkuxkx.noob/blob/3b0aac646ea0fafa68348ecbbec06cc53cb41b9e/script/core/proposal/drink.js
https://github.com/hellclient-scripts/pkuxkx.noob/blob/3b0aac646ea0fafa68348ecbbec06cc53cb41b9e/script/core/proposal/food.js
就做好了基础的准备了
jarlyyn
发表于 2021-11-19 14:20:25
接下来我们就要改第一个正式的模块,也可以说是一个通用的功能模块
这个模块可以执行一串字符串,比如我们要实现自动配药
那么输入
#prepare||#to yzyp||ask ping yizhi about 工作||n||peiyao||#afterbusy||#delay 5000||s||give cheng yao to ping yizhi||#loop
可以实现
[*]准备(取现,买吃喝)
[*]前往扬州药铺
[*]向平一指打听工作
[*]前往配药房
[*]配药
[*]等待不忙
[*]延迟5秒
[*]回平一指处
[*]给配好的药
[*]循环
这样一个循环队列。很明显,诸如打坐,吐纳,练武功,钓鱼之类都能很容易的使用这形势执行
我们先调整queue.task,分割字符串,并进入queue的主状态 core.state.queue.next
(function (app) {
let Task = Include("core/task/task.js")
let queue = Include("core/queue/queue.js")
var re = /\|\|/
let Queue = function () {
Task.call(this, "queue")
this.Execute = function (data, onFinish, onFail) {
Task.prototype.Execute.call(this, data, onFinish, onFail)
if (!data) {
data = ""
}
let list = data.split(re)
let q=new queue(list)
app.Send("l")
app.Automaton.Push("",["core.state.queue.next"]).WithData("Queue",q)
app.ResponseReady()
}
}
Queue.prototype = Object.create(Task.prototype)
app.RegisterState(new (Include("core/state/queue/queuenext.js"))())
app.RegisterState(new (Include("core/state/queue/queueloop.js"))())
app.RegisterTask(new Queue())
})(App)
https://github.com/hellclient-scripts/pkuxkx.noob/blob/3b0aac646ea0fafa68348ecbbec06cc53cb41b9e/script/core/task/queue.js
core.state.queue.next主状态代码如下
(function (app) {
let basicstate = Include("core/state/basicstate.js")
let StateQueueNext=function(){
basicstate.call(this)
this.ID="core.state.queue.next"
}
StateQueueNext.prototype = Object.create(basicstate.prototype)
StateQueueNext.prototype.Enter=function(context,oldstatue){
basicstate.prototype.Enter.call(this,context,oldstatue)
let queue=app.GetContext("Queue")
if (queue.Remain.length === 0) {
app.Finish()
return
}
let str = queue.Remain.shift()
let current = new Directive(str)
switch (current.Command) {
case "#prepare":
app.StartFullPrepare(this.ID)
break
case "#to":
let c = new Directive(current.Data)
let vehicle
let target
if (c.Data) {
vehicle = c.Command
target = c.Data
} else {
vehicle = ""
target = c.Command
}
app.Drive(vehicle)
app.NewActive(target,"","core.state.queue.next",false).Start()
break
case "#move":
break
case "#afterbusy":
app.Automaton.Push("core.state.queue.next",["nobusy"])
app.ChangeState("ready")
break
case "#do":
app.NewActive("",current.Data,"core.state.queue.next",false).Start()
break
case "#delay":
let data=current.Data
if (isNaN(data) || (data - 0) <= 0) {
throw "delay 的秒数必须为正数"
}
let delay=(data - 0) / 1000
app.Wait(delay,"core.state.queue.next")
break
case "#loop":
if (!app.Stopped) {
queue.Remain = CloneArray(queue.Queue)
}
app.ChangeState("core.state.queue.loop")
break
default:
app.NewActive("",str,"core.state.queue.next",false).Start()
}
}
return StateQueueNext
})(App)
https://github.com/hellclient-scripts/pkuxkx.noob/blob/3b0aac646ea0fafa68348ecbbec06cc53cb41b9e/script/core/state/queue/queuenext.js
很明显,弹出队列里的最新命令,判断并进入相应的状态。本身简单清晰容易维护
对比状态化之前的代码,明显容易维护很多
老代码:https://github.com/hellclient-scripts/pkuxkx.noob/blob/b1e774a8530981fb45916e97839ab2ce4bae3772/script/core/task/queue.js
最后是一个core.state.queue.loop状态。这其实是一个空在状态,用途是进入会回到老状态,触发原状态的enter和leave事件
(function (app) {
let basicstate = Include("core/state/basicstate.js")
let StateQueueLoop=function(){
basicstate.call(this)
this.ID="core.state.queue.loop"
}
StateQueueLoop.prototype = Object.create(basicstate.prototype)
StateQueueLoop.prototype.Enter=function(context,oldstatue){
basicstate.prototype.Enter.call(this,context,oldstatue)
app.ChangeState("core.state.queue.next")
}
return StateQueueLoop
})(App)
https://github.com/hellclient-scripts/pkuxkx.noob/blob/3b0aac646ea0fafa68348ecbbec06cc53cb41b9e/script/core/state/queue/queueloop.js
这个模块就做完了。
我们还可以用来进行抄经工作,分为两块
#prepare||#to qf-sms||#do ask sengren about job
将验证码地址复制进新页面打开,并刷新到人能认出的程度
#to ts-sjy||#do write jing||#afterbusy||#delay 2000||#to qf-sms||#do ask sengren about done
将页面里的验证码输入
然后循环。
jarlyyn
发表于 2021-11-19 14:26:26
状态话后的代码我维护也非常简单
由于自动机的栈是在app.data里的,一个debug马上就可以看到现在程序执行到什么程度,上下文是什么,数据是什么,下一步是什么
如图,当前状态在"wait",等待状态
数据是 Deley=5,等5秒
下一个动作是"core.state.queue.next"
然后调用他的上一级的数据是Queue,内容包括
[
"#prepare",
"#to yzyp",
"ask ping yizhi about 工作",
"n",
"peiyao",
"#afterbusy",
"#delay 5000",
"s",
"give cheng yao to ping yizhi",
"#loop"
]目前剩下需要处理的指令为
[
"s",
"give cheng yao to ping yizhi",
"#loop"
]
jarlyyn
发表于 2021-11-19 14:28:30
好了,下一步就是北侠这个mud做机器人对我最大的考验了
怎么做出一个同时满足
[*]我觉得有意义,至少不丑陋
[*]北侠允许公开讨论和传播
[*]普通玩家愿意用
的机器人了
不的不承认,对我来说,最大的限制是第二点……
jarlyyn
发表于 2021-11-19 17:47:11
搞了一个all 20 id,做读书测试
读书的命令是
#prepare||#to yz-xjk||#do du shu for 50||#delay 1000||#loop
还没做打坐和睡觉的判断模块,效率略低,但能用
会自动买吃喝,取钱。
vicrly
发表于 2021-12-6 15:09:57
这个帖子写的好啊!我已经苦恼好久了,总觉得自己的机器人如同一堆散乱的触发器,没有条理。仔细学习一下,才有醍醐灌顶之感。继续更新啊~
vicrly
发表于 2021-12-6 20:14:36
又仔细研读了一遍,说实话我只看懂了个大概,我还是会一点编程的,感觉普通玩家估计玩不太转,能不能出个mini版?{:7_268:}
有个问题想请教一下楼主。
#prepare||#to yz-xjk||#do du shu for 50||#delay 1000||#loop 这个指令是无限循环的话,如果我要做别的事,比如做任务,是不是要切换指令集?
jarlyyn
发表于 2021-12-7 14:00:01
vicrly 发表于 2021-12-6 08:14 PM
又仔细研读了一遍,说实话我只看懂了个大概,我还是会一点编程的,感觉普通玩家估计玩不太转,能不能出个mi ...
这是一个指令化的执行队列
以||分割,目前有 #prepare,#to,#do,#delay,#loop ,#nobusy这几个指令
如果需要做别的 就做新的队列就行了。
这个只是一个粗的架构,实际真的要做的话,就要对游戏有一定的了解了。
ptolemy
发表于 2022-6-21 13:34:28
屡次想玩,屡次被机器人制作劝退,最多能做做打坐的机器人,而且即便想着要硬啃lua,想起来也不知道从何处下手,楼主这篇帖子有点从思路上让我找到了一点方向,其实之前就是在想,要怎么样从底层来拆分并且一个模块一个模块的机器人慢慢制作,然后可以拼起来,不至于换一本书又要重新复制修改一个机器人,同时读书的时候如果想插入其他的事情,又要如何可以方便的插入。拆开来看真的就是需要有各种各样的状态机,可以杀状态,调用新状态,感觉脑子里突然有了unity里面用状态机视图的既视感。虽然帖子我肯不下,不过以外的让我自己意识到,应该状态机的方式,写ai的思路来对待机器人,可能对于慢慢入手有思路上的帮助,谢谢楼主