北大侠客行MUD论坛

 找回密码
 注册
搜索
热搜: 新手 wiki 升级
查看: 250|回复: 10

杰哥瞎扯蛋之处理触发和计时器的委员会系统

[复制链接]
发表于 2024-10-12 21:33:50 | 显示全部楼层 |阅读模式
在我的新框架里,除了为了便于维护,添加的基本事件触发器(永远不会启用禁用那种)。

其他的所有的模块都基于我新制作的这个委员会(committee)系统。

叫委员会呢,也是我的起名恶趣味,主要是强调两点

1.整个系统的事件和时间处理有分工,有不同的职位。
2.不同的职位不是长期的,有各自的任期,过了任期后,所有已经安排的工作的全部失效,人走茶凉。

职位主要指写代码时的不同层。比如我现在分为

1.连接,每次重连后重置。
2.房间,进入新房间后重置。
3.任务,开始新任务后重置。
4.指令,执行新指令后重置。
5.指令组,进入新的指令组后重置。
6.战斗,脱离和进入战斗后重置。

每个指令下会绑定不同的 触发,计时器,事件函数,以及 任务(核心,独立的触发计时器事件函数的集合)。

通过一个统一的触发和计时器,接入committee,然后依次分发给绑定的各个元素。
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
 楼主| 发表于 2024-10-12 21:42:26 | 显示全部楼层
本帖最后由 jarlyyn 于 2024-10-12 09:54 PM 编辑

关于任务(task)

Task时我新框架的基本编程工具。

他代表一个指定作用空间(职位,别入当前房间内),一个一次性的对将来发生的状况的预期,取代我原来的很多简单状态机的功能。

他可以添加 触发器,计时器,事件函数,作为结束信号。

默认情况下,任何一个结束新号触发时,会使整个任务失效,调用回调函数,将失效相关的信息传去处理。

有点类似 北侠比较流行的wait.lua的功能,但比那个实用和靠谱的多(wait.lua的源代码时通过添加客户端触发器和计时器实现的,明显会污染整个客户端编程环境)

简单来个代码

  1.     App.Map.StepPlan = new App.Plan(
  2.         App.Map.Position,
  3.         function (task) {
  4.             let tt=task.NewTimer(App.Map.StepTimeout).WithName("timeout")
  5.             task.NewCatcher("core.longtimestep",function(){
  6.                 tt.Reset(App.Move.LongtimeStepDelay)
  7.                 return true
  8.             })
  9.             task.NewCatcher("core.wrongway").WithName("wrongway")
  10.             task.NewCatcher("core.walkbusy").WithName("walkbusy")
  11.             task.NewCatcher("core.walkresend").WithName("walkresend")
  12.             task.NewCatcher("core.walkretry").WithName("walkretry")
  13.         },
  14.         function (result) {
  15.             switch (result.Type) {
  16.                 case "cancel":
  17.                     break
  18.                 default:
  19.                     switch (result.Name) {
  20.                         case "timeout":
  21.                             App.Map.OnStepTimeout()
  22.                             break
  23.                         case "wrongway":
  24.                             App.Map.Room.ID=""
  25.                             App.Map.Retry()
  26.                             break
  27.                         case "walkbusy":
  28.                             App.Map.Resend()
  29.                             break
  30.                         case "walkresend":
  31.                             App.Map.Resend(0)
  32.                             break
  33.                         case "walkretry":
  34.                             App.Map.Retry()
  35.                             break
  36.                     }
  37.             }
  38.         }
  39.     )
复制代码

这是一个发送移动指令后,进入新的房间时的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函数设置,可以快速的设置结果。


北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
 楼主| 发表于 2024-10-12 21:50:19 | 显示全部楼层
本帖最后由 jarlyyn 于 2024-10-12 09:52 PM 编辑

task的机制,解决了Mud机器人的一个重要功能

对未来信号的等待预期。

很多时候,机器是被动的,会在那里等待一些预期里的事件发生。

这个等待的过程,有独立的上下文。

等待结束后,会根据实际收到的信好做下一步的处理。

等待过程中,出现了某些新号,不一定直接结束等待,而是调整等待的姿态。
上一贴中,我还引入一个计划(Plan)的概念。

就是一个预设的任务生成器,本质就是个Builder函数。
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
 楼主| 发表于 2024-10-12 21:57:11 | 显示全部楼层
再来个简单的应用,取代开关触发组

  1.     App.BindEvent("core.skills", App.Core.OnSkills)
  2.     // ┌─────────────┬─────────────┬──────┬──────┐
  3.     // │名称                      │ID                        │描述        │级别        │
  4.     // ├───四项基本功夫────┼─────────────┼──────┼──────┤
  5.     // │  基本内功                │force                     │【不足挂齿】│  71 / 56   │
  6.     // │□叫花内功                │jiaohua-neigong           │【不足挂齿】│  71 / 7    │
  7.     // ├─────────────┼─────────────┼──────┼──────┤
  8.     // │  基本轻功                │dodge                     │【不堪一击】│  12 / 58   │
  9.     // │  基本拳脚                │unarmed                   │【不堪一击】│   2 / 40   │
  10.     // │  基本招架                │parry                     │【不堪一击】│   1 / 0    │
  11.     // ├───一项知识技能────┼─────────────┼──────┼──────┤
  12.     // │  读书写字                │literate                  │【新学乍用】│  26 / 97   │
  13.     // └─────────────┴─────────────┴──────┴──────┘
  14.     var matcherSkillsType = /^├─+.+项([^─]+)─+┼─+┼─+┼─+┤$/
  15.     var matcherSkills = /^│(  |□)(\S+)\s+│(\S+)\s+│【.+】│\s*(\d+) \/\s*(\d+)\s*│$/
  16.     var matcherSkillsEnd = /^└─*┴─*┴─*┴─*┘$/
  17.     var PlanOnSkills = new App.Plan(App.Positions.Connect,
  18.         function (task) {
  19.             task.NewTrigger(matcherSkillsType, function (trigger, result, event) {
  20.                 LastType = result[1]
  21.                 return true
  22.             })
  23.             task.NewTrigger(matcherSkills, function (trigger, result, event) {
  24.                 let skill = {}
  25.                 skill.ID = result[3]
  26.                 skill["名称"] = result[2]
  27.                 skill["激发"] = (result[1].trim() != "")
  28.                 skill["类型"] = LastType
  29.                 skill["等级"] = result[4] - 0
  30.                 skill["进度"] = result[5] - 0
  31.                 App.Data.Player.Skills[skill.ID] = skill
  32.                 return true
  33.             })
  34.             task.NewTimer(5000)
  35.             task.NewTrigger(matcherSkillsEnd)
  36.         },
  37.         function (result) {
  38.             checkerSkills.Reset()
  39.         })
复制代码


在当前连接(连接的Position)下,到MatcherSkillsEnd或5秒为止,处理每个匹配 matcherSkillsType 和 matcherSkills的 行,记录用户所有的技能。


都在一个文件里,不需要来回切换编辑器和客户端维护,维护比较简单。
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
 楼主| 发表于 2024-10-12 21:58:50 | 显示全部楼层
本帖最后由 jarlyyn 于 2024-10-12 10:00 PM 编辑

  1.     let checkerHP = App.Checker.Register("hp", "yun recover;yun regenerate;hp", 5000)
  2.     // ┌───个人状态────────────┬───────────────────┐
  3.     // │【精气】 160     / 160      [100%]    │【精力】 0       / 0       (+   0)    │
  4.     // │【气血】 523     / 523      [100%]    │【内力】 1724    / 920     (+   0)    │
  5.     // │【食物】 638     / 400      [很饱]    │【潜能】 14376                        │
  6.     // │【饮水】 224     / 400      [缺水]    │【经验】 145283                       │
  7.     // │                                      │【体会】 38                           │
  8.     // ├───────────────────┴───────────────────┤
  9.     // │【状态】 健康、平和                                                           │
  10.     // └──────────────────────────────口口口口─────┘
  11.     var matcherHPLine1 = /^│【精气】\s*(-?\d+)\s*\/\s+(-?\d+)\s*\[\s*(-?\d+)%\]\s+│【精力】\s+(-?\d+)\s+ \/\s*(-?\d+)\s+\(\+\s*(\d+)\)\s+│$/
  12.     var matcherHPLine2 = /^│【气血】\s*(-?\d+)\s*\/\s+(-?\d+)\s*\[\s*(-?\d+)%\]\s+│【内力】\s+(-?\d+)\s+ \/\s*(-?\d+)\s+\(\+\s*(\d+)\)\s+│$/
  13.     var matcherHPLine3 = /^│【食物】\s*(-?\d+)\s*\/\s+(-?\d+)\s*\[.+\]\s+│【潜能】\s+(-?\d+)\s+│$/
  14.     var matcherHPLine4 = /^│【饮水】\s*(-?\d+)\s*\/\s+(-?\d+)\s*\[.+\]\s+│【经验】\s+(-?\d+)\s+│$/
  15.     var matcherHPLine5 = /^│\s+│【体会】\s+(-?\d+)\s+│$/
  16.     var matcherHPEnd = /^└─+.+─+─┘$/

  17.     var PlanOnHP = new App.Plan(App.Positions.Connect,
  18.         function (task) {
  19.             task.NewTrigger(matcherHPLine1, function (trigger, result, event) {
  20.                 App.Data.Player.HP["当前精气"] = result[1] - 0
  21.                 App.Data.Player.HP["精气上限"] = result[2] - 0
  22.                 App.Data.Player.HP["精气百分比"] = result[3] - 0
  23.                 App.Data.Player.HP["当前精力"] = result[4] - 0
  24.                 App.Data.Player.HP["精力上限"] = result[5] - 0
  25.                 App.Data.Player.HP["加精"] = result[6] - 0
  26.                 return true
  27.             })
  28.             task.NewTrigger(matcherHPLine2, function (trigger, result, event) {
  29.                 App.Data.Player.HP["当前气血"] = result[1] - 0
  30.                 App.Data.Player.HP["精气气血"] = result[2] - 0
  31.                 App.Data.Player.HP["气血百分比"] = result[3] - 0
  32.                 App.Data.Player.HP["当前内力"] = result[4] - 0
  33.                 App.Data.Player.HP["内力上限"] = result[5] - 0
  34.                 App.Data.Player.HP["加力"] = result[6] - 0
  35.                 return true
  36.             })
  37.             task.NewTrigger(matcherHPLine3, function (trigger, result, event) {
  38.                 App.Data.Player.HP["当前食物"] = result[1] - 0
  39.                 App.Data.Player.HP["最大食物"] = result[2] - 0
  40.                 App.Data.Player.HP["潜能"] = result[3] - 0
  41.                 return true
  42.             })
  43.             task.NewTrigger(matcherHPLine4, function (trigger, result, event) {
  44.                 App.Data.Player.HP["当前饮水"] = result[1] - 0
  45.                 App.Data.Player.HP["最大饮水"] = result[2] - 0
  46.                 App.Data.Player.HP["经验"] = result[3] - 0
  47.                 return true
  48.             })
  49.             task.NewTrigger(matcherHPLine5, function (trigger, result, event) {
  50.                 App.Data.Player.HP["体会"] = result[1] - 0
  51.                 return true
  52.             })
  53.             task.NewTimer(5000)
  54.             task.NewTrigger(matcherHPEnd)
  55.         },
  56.         function (result) {
  57.             checkerHP.Reset()
  58.         })
复制代码
这个道理一样,应该不需要多解释。
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
 楼主| 发表于 2024-10-12 22:04:33 | 显示全部楼层
  1.     let matcherOnHeal = /^    (\S{2,8})正坐在地下(修炼内力)。$/
  2.     let matcherOnObj = /^    ((\S+) )?(\S*「.+」)?(\S+)\(([^\(\)]+)\)( \[.+\])?(( <.+>)*)$/
  3.     var PlanOnExit = new App.Plan(App.Positions.Connect,
  4.         function (task) {
  5.             task.NewTrigger(matcherOnObj, function (trigger, result, event) {
  6.                 let item = new objectModule.Object(result[4], result[5], App.History.CurrentOutput).
  7.                     WithParam("身份", result[2]).
  8.                     WithParam("外号", result[3]).
  9.                     WithParam("描述", result[6] || "").
  10.                     WithParam("状态", result[7] || "").
  11.                     WithParam("动作", "")
  12.                 App.Map.Room.Data.Objects.Append(item)
  13.                 event.Context.Set("core.room.onobject", true)
  14.                 return true
  15.             })
  16.             task.NewTrigger(matcherOnHeal, function (trigger, result, event) {
  17.                 let item = new objectModule.Object(result[1], "", App.History.CurrentOutput).
  18.                     WithParam("动作", "result[2]")
  19.                 App.Map.Room.Data.Objects.Append(item)
  20.                 event.Context.Set("core.room.onobject", true)
  21.                 return true
  22.             })

  23.             task.NewCatcher("line", function (catcher, event) {
  24.                 return event.Context.Get("core.room.onobject")
  25.             })
  26.         }, function (result) {
  27.             if (result.Type != "cancel") {
  28.                 if (App.Map.Room.Name && !App.Map.Room.ID){
  29.                     let idlist=App.Map.Data.RoomsByName[App.Map.Room.Name]
  30.                     if (idlist&&idlist.length==1){
  31.                         App.Map.Room.ID=idlist[0]
  32.                     }
  33.                 }
  34.                 App.RaiseEvent(new App.Event("core.roomentry"))
  35.             }
  36.         })
复制代码


这个开始有点特殊了。

这个是抓房间里的NPC的触发。逻辑基本和北侠的接进。

我的框架里trigger一定是比event要优先的

也就是说,如果匹配到NPC,则不结束,并给当前行的事件打个标记。

如果行匹配结束时没有标记,说明这行不是npc,npc信息结束。

房间信息到此为止。

北侠我现有机器的逻辑和这个差不多。

但是因为意外(BUG)更多,我还加入了GA判断和超时判断。
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
 楼主| 发表于 2024-10-12 22:09:48 | 显示全部楼层
到目前为止写的最复杂的是这个

  1.     //你向店小二打听有关『123』的消息。
  2.     let matcherAsk = /^你向(.+)打听有关『.+』的消息。$/
  3.     //但是很显然的,$n现在的状况没有办法给$N任何答覆。
  4.     //$n摇摇头,说道:没听说过。
  5.     //$n疑惑地看着$N,摇了摇头。
  6.     //$n睁大眼睛望着$N,显然不知道$P在说什么。
  7.     //$n耸了耸肩,很抱歉地说:无可奉告。
  8.     //$n说道:嗯....这我可不清楚,你最好问问别人吧。
  9.     //$n想了一会儿,说道:对不起,你问的事我实在没有印象。
  10.     let matcherUnknown = /^(但是很显然的,.+现在的状况没有办法给你任何答覆。|.+摇摇头,说道:没听说过。|.+疑惑地看着.+,摇了摇头|.+睁大眼睛望着你,显然不知道你在说什么。|.+耸了耸肩,很抱歉地说:无可奉告。|.+说道:嗯....这我可不清楚,你最好问问别人吧。|.+想了一会儿,说道:对不起,你问的事我实在没有印象。|.+对你说道:实在是对不起,我什么也不知道呀!|.+疑惑地看着你,摇了摇头。)$/
  11.     let matcherRetry = /^(.+说道:阿嚏!有点感冒,不好意思。|.+说道:等...等等,你说什么?没听清楚。|.+说道:嗯,稍等啊,就好... 好了,你刚才说啥?|.+说道:这个... 这个... 哦,好了,啊?你问我呢?|.+说道:唉呦!... 不好意思,是你问我么?|.+说道:就好... 就好... 好了,你说啥?|.+说道:你干啥?没看我忙着呢么?|.+说道:\!\@\#\$%\^\&\*)$/
  12.     //这个地方不能讲话。
  13.     //这里没有这个人。
  14.     //$N对着$n自言自语....
  15.     //$N自己自言自语。
  16.     //你现在的精神不太好,没法和别人套瓷。
  17.     let matcherFail = /^(这个地方不能讲话。|这里没有这个人。|.+对着.+自言自语....|你自己自言自语。|你现在的精神不太好,没法和别人套瓷。)$/
  18.     let PlanOnAsk = new App.Plan(App.Positions.Connect,
  19.         function (task) {
  20.             App.Sync(function () { task.Cancel("sync") })
  21.             task.NewTrigger(matcherAsk, function (result) {
  22.                 if (App.Data.Ask.Mode == 0) {
  23.                     App.Data.Name = result[1]
  24.                     App.Data.Ask.Mode = 1
  25.                 }
  26.                 return true
  27.             })
  28.             task.NewTrigger(matcherUnknown, function (result) {
  29.                 if (App.Data.Ask.LineNumber == 1 && App.Data.Ask.Mode == 1) {
  30.                     App.Data.Ask.Result = "unknown"
  31.                     App.Data.Ask.Mode = 2
  32.                 }
  33.                 return true
  34.             })
  35.             task.NewTrigger(matcherRetry, function (result) {
  36.                 if (App.Data.Ask.LineNumber == 1 && App.Data.Ask.Mode == 1) {
  37.                     App.Data.Ask.Result = "retry"
  38.                     App.Data.Ask.Mode = 2
  39.                 }
  40.                 return true
  41.             })
  42.             task.NewTrigger(matcherFail, function (result) {
  43.                 if (App.Data.Ask.LineNumber == 1 && App.Data.Ask.Mode == 1) {
  44.                     App.Data.Ask.Result = "fail"
  45.                     App.Data.Ask.Mode = 2
  46.                 }
  47.                 return true
  48.             })
  49.             task.NewCatcher("line", function (catcher, event) {
  50.                 if (App.Data.Ask.Mode != 0) {
  51.                     App.Data.Ask.LineNumber++
  52.                 }
  53.                 if (App.Data.Ask.Mode == 1 && App.Data.Ask.LineNumber > 1) {
  54.                     if (App.Data.Ask.Answers.length > (App.Data.Ask.Length > 0 ? App.Data.Ask.Length : MaxAnswer)) {
  55.                         App.Data.Ask.Mode = 2
  56.                         return true
  57.                     }
  58.                     App.Data.Ask.Result = "ok"
  59.                     App.Data.Ask.Answers.push(App.History.GetLine())
  60.                 }
  61.                 return true
  62.             })

  63.         },
  64.         function (result) {
  65.             if (App.Data.Ask.Result == "fail") {
  66.                 App.Fail()
  67.                 return
  68.             }
  69.             if (App.Data.Ask.Result == "retry") {
  70.                 App.Commands.Insert(
  71.                     App.Commands.NewWaitCommand(1000),
  72.                     App.Commands.NewFunctionCommand(function () {
  73.                         App.Ask(App.Data.Ask.ID, App.Data.Ask.Question, App.Data.Ask.Length)
  74.                     }),
  75.                 )
  76.             }
  77.             App.Next()
  78.         },
  79.     )
复制代码


记录向NPC ask的信息。

出现你向NPC打听 XXXX时,开始进入准备记录模式
打听失败,则标记为失败
NPC忙,则标记为重试,1秒后重试。
如果是正常状态,记录聊天记录直到指定行为止。

遇到和ASK一起发送的同步指令(相当于response),退出等待,进行后续处理。

实际使用大概是这样


  1.     let re = /.+说道:据说(.+)急需一批(.+)。嘿!你说他想干什么?$/
  2.     App.Quest.Beiqi.Check = function () {
  3.         if (App.Quests.Stopped){
  4.             App.Next()
  5.             return
  6.         }
  7.         if (App.Data.Ask.Answers.length == 0) {
  8.             App.Fail()
  9.             return
  10.         }
  11.         let answer = App.Data.Ask.Answers[0]
  12.         let result = answer.Line.match(re)
  13.         if (result) {
  14.             let key = App.Quest.Beiqi.Data.Current.Key + "-" + result[1]
  15.             let receiver = App.Quest.Beiqi.Receivers[key]
  16.             if (receiver == null) {
  17.                 App.Fatal("beiqi","未知的收货人" + key)
  18.                 return
  19.             }
  20.             App.Quest.Beiqi.Data.Quest = {
  21.                 Receiver: receiver,
  22.                 Item: result[2],
  23.             }
  24.             App.Quest.Beiqi.Give()
  25.             return
  26.         }
  27.         App.Fatal("beiqi","未知的回答:" + answer)
  28.     }
复制代码
直接处理 App.Data.Ask.Answers 和 App.Data.Ask.Answers.Result可以直接处理最后依次的主动ask


完全不需要多行匹配之类的东西。
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
发表于 2024-10-12 22:12:07 | 显示全部楼层
又是给程序员写的框架吗  这种只能自娱自乐呀
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
 楼主| 发表于 2024-10-12 22:12:59 | 显示全部楼层
整个Committee模块,解决的就是 失效 和 快速实现等待的功能。

和事件系统一起,解决了同步事件处理和异步事件处理,实现了条件反射功能。

为实现主动带目的性的机器功能(指令/指令对列)打下了基础。

基本模块说完了。

看看那下一次说说新的移动模块还是指令模块了。
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
 楼主| 发表于 2024-10-12 22:15:46 | 显示全部楼层
总体来说,这个模块还是为了降低机器复杂度。

实现Timer,Trigger和Timer的异步处理。

然后指令(Command)系统处理的是函数/代码的异步问题。

这两个模块,就是为了以接近同步代码的形式处理异步逻辑。

使得流程代码能压平,成为一个流,而不是乱线团,便于编写和维护。
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|小黑屋|北大侠客行MUD ( 京ICP备16065414号-1 )

GMT+8, 2024-11-1 09:25 AM , Processed in 0.014907 second(s), 15 queries .

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表