杰哥瞎扯蛋之处理触发和计时器的委员会系统
在我的新框架里,除了为了便于维护,添加的基本事件触发器(永远不会启用禁用那种)。其他的所有的模块都基于我新制作的这个委员会(committee)系统。
叫委员会呢,也是我的起名恶趣味,主要是强调两点
1.整个系统的事件和时间处理有分工,有不同的职位。
2.不同的职位不是长期的,有各自的任期,过了任期后,所有已经安排的工作的全部失效,人走茶凉。
职位主要指写代码时的不同层。比如我现在分为
1.连接,每次重连后重置。
2.房间,进入新房间后重置。
3.任务,开始新任务后重置。
4.指令,执行新指令后重置。
5.指令组,进入新的指令组后重置。
6.战斗,脱离和进入战斗后重置。
每个指令下会绑定不同的 触发,计时器,事件函数,以及 任务(核心,独立的触发计时器事件函数的集合)。
通过一个统一的触发和计时器,接入committee,然后依次分发给绑定的各个元素。
本帖最后由 jarlyyn 于 2024-10-12 09:54 PM 编辑
关于任务(task)
Task时我新框架的基本编程工具。
他代表一个指定作用空间(职位,别入当前房间内),一个一次性的对将来发生的状况的预期,取代我原来的很多简单状态机的功能。
他可以添加 触发器,计时器,事件函数,作为结束信号。
默认情况下,任何一个结束新号触发时,会使整个任务失效,调用回调函数,将失效相关的信息传去处理。
有点类似 北侠比较流行的wait.lua的功能,但比那个实用和靠谱的多(wait.lua的源代码时通过添加客户端触发器和计时器实现的,明显会污染整个客户端编程环境)
简单来个代码
App.Map.StepPlan = new App.Plan(
App.Map.Position,
function (task) {
let tt=task.NewTimer(App.Map.StepTimeout).WithName("timeout")
task.NewCatcher("core.longtimestep",function(){
tt.Reset(App.Move.LongtimeStepDelay)
return true
})
task.NewCatcher("core.wrongway").WithName("wrongway")
task.NewCatcher("core.walkbusy").WithName("walkbusy")
task.NewCatcher("core.walkresend").WithName("walkresend")
task.NewCatcher("core.walkretry").WithName("walkretry")
},
function (result) {
switch (result.Type) {
case "cancel":
break
default:
switch (result.Name) {
case "timeout":
App.Map.OnStepTimeout()
break
case "wrongway":
App.Map.Room.ID=""
App.Map.Retry()
break
case "walkbusy":
App.Map.Resend()
break
case "walkresend":
App.Map.Resend(0)
break
case "walkretry":
App.Map.Retry()
break
}
}
}
)
这是一个发送移动指令后,进入新的房间时的Task
里面明显监听了多个事件。
retry事件,会重试行走(重新计算路线,比如发现某条路被拦住过不去后,打上标签,重新计算路线)
busy事件,会1秒后重走
resend事件,立刻重发指令(比如拿武器被拦了,unwield后重新走)
timeout,超时。
longtimestep,一个长时间的移动,比如北侠的爬树,重设timeout未更长的事件,同时return true,使得当前任务不会结束。
当没有return true的事件触发,或者整个职位换任期(进入新的房间)后,task失效,开始进入结算流程,更具result的数据进行结算和处理。
整个模拟了我的简单状态机,熟悉wait.lua的也会发现和wait.lua也比较像。
result有Type属性,cancel,timer,trigger,event,来决定了结束的类型。
有Name和Data属性,可以通过WithName和WithData函数设置,可以快速的设置结果。
本帖最后由 jarlyyn 于 2024-10-12 09:52 PM 编辑
task的机制,解决了Mud机器人的一个重要功能
对未来信号的等待预期。
很多时候,机器是被动的,会在那里等待一些预期里的事件发生。
这个等待的过程,有独立的上下文。
等待结束后,会根据实际收到的信好做下一步的处理。
等待过程中,出现了某些新号,不一定直接结束等待,而是调整等待的姿态。
上一贴中,我还引入一个计划(Plan)的概念。
就是一个预设的任务生成器,本质就是个Builder函数。
再来个简单的应用,取代开关触发组
App.BindEvent("core.skills", App.Core.OnSkills)
// ┌─────────────┬─────────────┬──────┬──────┐
// │名称 │ID │描述 │级别 │
// ├───四项基本功夫────┼─────────────┼──────┼──────┤
// │基本内功 │force │【不足挂齿】│71 / 56 │
// │□叫花内功 │jiaohua-neigong │【不足挂齿】│71 / 7 │
// ├─────────────┼─────────────┼──────┼──────┤
// │基本轻功 │dodge │【不堪一击】│12 / 58 │
// │基本拳脚 │unarmed │【不堪一击】│ 2 / 40 │
// │基本招架 │parry │【不堪一击】│ 1 / 0 │
// ├───一项知识技能────┼─────────────┼──────┼──────┤
// │读书写字 │literate │【新学乍用】│26 / 97 │
// └─────────────┴─────────────┴──────┴──────┘
var matcherSkillsType = /^├─+.+项([^─]+)─+┼─+┼─+┼─+┤$/
var matcherSkills = /^│(|□)(\S+)\s+│(\S+)\s+│【.+】│\s*(\d+) \/\s*(\d+)\s*│$/
var matcherSkillsEnd = /^└─*┴─*┴─*┴─*┘$/
var PlanOnSkills = new App.Plan(App.Positions.Connect,
function (task) {
task.NewTrigger(matcherSkillsType, function (trigger, result, event) {
LastType = result
return true
})
task.NewTrigger(matcherSkills, function (trigger, result, event) {
let skill = {}
skill.ID = result
skill["名称"] = result
skill["激发"] = (result.trim() != "")
skill["类型"] = LastType
skill["等级"] = result - 0
skill["进度"] = result - 0
App.Data.Player.Skills = skill
return true
})
task.NewTimer(5000)
task.NewTrigger(matcherSkillsEnd)
},
function (result) {
checkerSkills.Reset()
})
在当前连接(连接的Position)下,到MatcherSkillsEnd或5秒为止,处理每个匹配 matcherSkillsType 和 matcherSkills的 行,记录用户所有的技能。
都在一个文件里,不需要来回切换编辑器和客户端维护,维护比较简单。
本帖最后由 jarlyyn 于 2024-10-12 10:00 PM 编辑
let checkerHP = App.Checker.Register("hp", "yun recover;yun regenerate;hp", 5000)
// ┌───个人状态────────────┬───────────────────┐
// │【精气】 160 / 160 │【精力】 0 / 0 (+ 0) │
// │【气血】 523 / 523 │【内力】 1724 / 920 (+ 0) │
// │【食物】 638 / 400 [很饱] │【潜能】 14376 │
// │【饮水】 224 / 400 [缺水] │【经验】 145283 │
// │ │【体会】 38 │
// ├───────────────────┴───────────────────┤
// │【状态】 健康、平和 │
// └──────────────────────────────口口口口─────┘
var matcherHPLine1 = /^│【精气】\s*(-?\d+)\s*\/\s+(-?\d+)\s*\[\s*(-?\d+)%\]\s+│【精力】\s+(-?\d+)\s+ \/\s*(-?\d+)\s+\(\+\s*(\d+)\)\s+│$/
var matcherHPLine2 = /^│【气血】\s*(-?\d+)\s*\/\s+(-?\d+)\s*\[\s*(-?\d+)%\]\s+│【内力】\s+(-?\d+)\s+ \/\s*(-?\d+)\s+\(\+\s*(\d+)\)\s+│$/
var matcherHPLine3 = /^│【食物】\s*(-?\d+)\s*\/\s+(-?\d+)\s*\[.+\]\s+│【潜能】\s+(-?\d+)\s+│$/
var matcherHPLine4 = /^│【饮水】\s*(-?\d+)\s*\/\s+(-?\d+)\s*\[.+\]\s+│【经验】\s+(-?\d+)\s+│$/
var matcherHPLine5 = /^│\s+│【体会】\s+(-?\d+)\s+│$/
var matcherHPEnd = /^└─+.+─+─┘$/
var PlanOnHP = new App.Plan(App.Positions.Connect,
function (task) {
task.NewTrigger(matcherHPLine1, function (trigger, result, event) {
App.Data.Player.HP["当前精气"] = result - 0
App.Data.Player.HP["精气上限"] = result - 0
App.Data.Player.HP["精气百分比"] = result - 0
App.Data.Player.HP["当前精力"] = result - 0
App.Data.Player.HP["精力上限"] = result - 0
App.Data.Player.HP["加精"] = result - 0
return true
})
task.NewTrigger(matcherHPLine2, function (trigger, result, event) {
App.Data.Player.HP["当前气血"] = result - 0
App.Data.Player.HP["精气气血"] = result - 0
App.Data.Player.HP["气血百分比"] = result - 0
App.Data.Player.HP["当前内力"] = result - 0
App.Data.Player.HP["内力上限"] = result - 0
App.Data.Player.HP["加力"] = result - 0
return true
})
task.NewTrigger(matcherHPLine3, function (trigger, result, event) {
App.Data.Player.HP["当前食物"] = result - 0
App.Data.Player.HP["最大食物"] = result - 0
App.Data.Player.HP["潜能"] = result - 0
return true
})
task.NewTrigger(matcherHPLine4, function (trigger, result, event) {
App.Data.Player.HP["当前饮水"] = result - 0
App.Data.Player.HP["最大饮水"] = result - 0
App.Data.Player.HP["经验"] = result - 0
return true
})
task.NewTrigger(matcherHPLine5, function (trigger, result, event) {
App.Data.Player.HP["体会"] = result - 0
return true
})
task.NewTimer(5000)
task.NewTrigger(matcherHPEnd)
},
function (result) {
checkerHP.Reset()
})
这个道理一样,应该不需要多解释。
let matcherOnHeal = /^ (\S{2,8})正坐在地下(修炼内力)。$/
let matcherOnObj = /^ ((\S+) )?(\S*「.+」)?(\S+)\(([^\(\)]+)\)( \[.+\])?(( <.+>)*)$/
var PlanOnExit = new App.Plan(App.Positions.Connect,
function (task) {
task.NewTrigger(matcherOnObj, function (trigger, result, event) {
let item = new objectModule.Object(result, result, App.History.CurrentOutput).
WithParam("身份", result).
WithParam("外号", result).
WithParam("描述", result || "").
WithParam("状态", result || "").
WithParam("动作", "")
App.Map.Room.Data.Objects.Append(item)
event.Context.Set("core.room.onobject", true)
return true
})
task.NewTrigger(matcherOnHeal, function (trigger, result, event) {
let item = new objectModule.Object(result, "", App.History.CurrentOutput).
WithParam("动作", "result")
App.Map.Room.Data.Objects.Append(item)
event.Context.Set("core.room.onobject", true)
return true
})
task.NewCatcher("line", function (catcher, event) {
return event.Context.Get("core.room.onobject")
})
}, function (result) {
if (result.Type != "cancel") {
if (App.Map.Room.Name && !App.Map.Room.ID){
let idlist=App.Map.Data.RoomsByName
if (idlist&&idlist.length==1){
App.Map.Room.ID=idlist
}
}
App.RaiseEvent(new App.Event("core.roomentry"))
}
})
这个开始有点特殊了。
这个是抓房间里的NPC的触发。逻辑基本和北侠的接进。
我的框架里trigger一定是比event要优先的
也就是说,如果匹配到NPC,则不结束,并给当前行的事件打个标记。
如果行匹配结束时没有标记,说明这行不是npc,npc信息结束。
房间信息到此为止。
北侠我现有机器的逻辑和这个差不多。
但是因为意外(BUG)更多,我还加入了GA判断和超时判断。
到目前为止写的最复杂的是这个
//你向店小二打听有关『123』的消息。
let matcherAsk = /^你向(.+)打听有关『.+』的消息。$/
//但是很显然的,$n现在的状况没有办法给$N任何答覆。
//$n摇摇头,说道:没听说过。
//$n疑惑地看着$N,摇了摇头。
//$n睁大眼睛望着$N,显然不知道$P在说什么。
//$n耸了耸肩,很抱歉地说:无可奉告。
//$n说道:嗯....这我可不清楚,你最好问问别人吧。
//$n想了一会儿,说道:对不起,你问的事我实在没有印象。
let matcherUnknown = /^(但是很显然的,.+现在的状况没有办法给你任何答覆。|.+摇摇头,说道:没听说过。|.+疑惑地看着.+,摇了摇头|.+睁大眼睛望着你,显然不知道你在说什么。|.+耸了耸肩,很抱歉地说:无可奉告。|.+说道:嗯....这我可不清楚,你最好问问别人吧。|.+想了一会儿,说道:对不起,你问的事我实在没有印象。|.+对你说道:实在是对不起,我什么也不知道呀!|.+疑惑地看着你,摇了摇头。)$/
let matcherRetry = /^(.+说道:阿嚏!有点感冒,不好意思。|.+说道:等...等等,你说什么?没听清楚。|.+说道:嗯,稍等啊,就好... 好了,你刚才说啥?|.+说道:这个... 这个... 哦,好了,啊?你问我呢?|.+说道:唉呦!... 不好意思,是你问我么?|.+说道:就好... 就好... 好了,你说啥?|.+说道:你干啥?没看我忙着呢么?|.+说道:\!\@\#\$%\^\&\*)$/
//这个地方不能讲话。
//这里没有这个人。
//$N对着$n自言自语....
//$N自己自言自语。
//你现在的精神不太好,没法和别人套瓷。
let matcherFail = /^(这个地方不能讲话。|这里没有这个人。|.+对着.+自言自语....|你自己自言自语。|你现在的精神不太好,没法和别人套瓷。)$/
let PlanOnAsk = new App.Plan(App.Positions.Connect,
function (task) {
App.Sync(function () { task.Cancel("sync") })
task.NewTrigger(matcherAsk, function (result) {
if (App.Data.Ask.Mode == 0) {
App.Data.Name = result
App.Data.Ask.Mode = 1
}
return true
})
task.NewTrigger(matcherUnknown, function (result) {
if (App.Data.Ask.LineNumber == 1 && App.Data.Ask.Mode == 1) {
App.Data.Ask.Result = "unknown"
App.Data.Ask.Mode = 2
}
return true
})
task.NewTrigger(matcherRetry, function (result) {
if (App.Data.Ask.LineNumber == 1 && App.Data.Ask.Mode == 1) {
App.Data.Ask.Result = "retry"
App.Data.Ask.Mode = 2
}
return true
})
task.NewTrigger(matcherFail, function (result) {
if (App.Data.Ask.LineNumber == 1 && App.Data.Ask.Mode == 1) {
App.Data.Ask.Result = "fail"
App.Data.Ask.Mode = 2
}
return true
})
task.NewCatcher("line", function (catcher, event) {
if (App.Data.Ask.Mode != 0) {
App.Data.Ask.LineNumber++
}
if (App.Data.Ask.Mode == 1 && App.Data.Ask.LineNumber > 1) {
if (App.Data.Ask.Answers.length > (App.Data.Ask.Length > 0 ? App.Data.Ask.Length : MaxAnswer)) {
App.Data.Ask.Mode = 2
return true
}
App.Data.Ask.Result = "ok"
App.Data.Ask.Answers.push(App.History.GetLine())
}
return true
})
},
function (result) {
if (App.Data.Ask.Result == "fail") {
App.Fail()
return
}
if (App.Data.Ask.Result == "retry") {
App.Commands.Insert(
App.Commands.NewWaitCommand(1000),
App.Commands.NewFunctionCommand(function () {
App.Ask(App.Data.Ask.ID, App.Data.Ask.Question, App.Data.Ask.Length)
}),
)
}
App.Next()
},
)
记录向NPC ask的信息。
出现你向NPC打听 XXXX时,开始进入准备记录模式
打听失败,则标记为失败
NPC忙,则标记为重试,1秒后重试。
如果是正常状态,记录聊天记录直到指定行为止。
遇到和ASK一起发送的同步指令(相当于response),退出等待,进行后续处理。
实际使用大概是这样
let re = /.+说道:据说(.+)急需一批(.+)。嘿!你说他想干什么?$/
App.Quest.Beiqi.Check = function () {
if (App.Quests.Stopped){
App.Next()
return
}
if (App.Data.Ask.Answers.length == 0) {
App.Fail()
return
}
let answer = App.Data.Ask.Answers
let result = answer.Line.match(re)
if (result) {
let key = App.Quest.Beiqi.Data.Current.Key + "-" + result
let receiver = App.Quest.Beiqi.Receivers
if (receiver == null) {
App.Fatal("beiqi","未知的收货人" + key)
return
}
App.Quest.Beiqi.Data.Quest = {
Receiver: receiver,
Item: result,
}
App.Quest.Beiqi.Give()
return
}
App.Fatal("beiqi","未知的回答:" + answer)
}直接处理 App.Data.Ask.Answers 和 App.Data.Ask.Answers.Result可以直接处理最后依次的主动ask
完全不需要多行匹配之类的东西。
又是给程序员写的框架吗这种只能自娱自乐呀 整个Committee模块,解决的就是 失效 和 快速实现等待的功能。
和事件系统一起,解决了同步事件处理和异步事件处理,实现了条件反射功能。
为实现主动带目的性的机器功能(指令/指令对列)打下了基础。
基本模块说完了。
看看那下一次说说新的移动模块还是指令模块了。 总体来说,这个模块还是为了降低机器复杂度。
实现Timer,Trigger和Timer的异步处理。
然后指令(Command)系统处理的是函数/代码的异步问题。
这两个模块,就是为了以接近同步代码的形式处理异步逻辑。
使得流程代码能压平,成为一个流,而不是乱线团,便于编写和维护。
页:
[1]
2