<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Popular Topics]]></title><description><![CDATA[A list of topics that are sorted by post count]]></description><link>https://forum.hellclient.com/popular/weekly</link><generator>RSS for Node</generator><lastBuildDate>Mon, 11 May 2026 08:06:06 GMT</lastBuildDate><atom:link href="https://forum.hellclient.com/popular/weekly.rss" rel="self" type="application/rss+xml"/><pubDate>Fri, 08 May 2026 01:55:24 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[深入浅出制作全自动Mud机器人-延时响应]]></title><description><![CDATA[延时响应是Mud解密中很常见的一个状态。
指当你发出指令后，游戏中的NPC或者场景会在一定时间后，或者断断续续的做出响应。
一般来说，延迟响应为了考虑到玩家的体验，服务器的响应需要进行特别复杂的处理，使用简单的匹配和Mode标记就可以处理。
之所以单独作为一个课题讨论，是因为延时响应的核心处理点是 失效管理。
普通解密，触发器一般是即用即关的，不需要考虑太多的意外状态，通过触发组就能很好的解决了。
而延时解密，因为要考虑到可能会有终止/意外/并行的情况，需要手动的对出发考虑失效和重复生效、计时器延时等状况，触发器组就很捉襟见肘了，一般需要更多的代码工具去进行管理。
我这里是通过自己的Task/Plan模块，将所有的触发/计时器/事件处理封装在一个Task对象内，并强制使用一个作用范围强制失效来解决这个问题的。
参考代码：
    let baohuwait = 28 * 1000
    let matcherKill = /^你对(.+)的(黑衣人|邪派高手|绝世高手)喝道:大胆狂徒,竟敢在这撒野！！/
    //等待NPC出现的计划
    let PlanProtect = new App.Plan(
        App.Positions["Quest"],
        (task) =&gt; {
            task.AddTrigger(matcherKill, (tri, result) =&gt; {
                if (result[1] != App.Data.Player.Score.名字) {
                    return true;
                }
                App.Send("halt")
                let id
                switch (result[2]) {
                    case "黑衣人":
                        id = "heiyi ren"
                        break
                    case "邪派高手":
                        id = "xiepai gaoshou"
                        break
                    case "绝世高手":
                        id = "jueshi gaoshou"
                        break
                }
                Baohu.Data.ID = id
                Baohu.Data.Type = result[2]
            }).WithName("ok")
            let wait = Baohu.Data.Start + baohuwait - $.Now()
            if (wait &gt; 0) {
                task.AddTimer(wait, (timer) =&gt; {
                    Note("准备迎敌")
                    App.Send("halt")
                    App.Core.Heal.TryTouch()
                    $.RaiseStage("prepare")
                    $.RaiseStage("baohu-ready")
                    return true
                }).WithNoRepeat(true)
            }
            task.AddTimer(1100, () =&gt; {
                if (App.Core.Weapon.Touch) {
                    if (($.Now() - Baohu.Data.Start) &lt; baohuwait) {
                        App.Send("halt")
                        App.Core.Heal.TryTouch()
                        $.RaiseStage("pause")
                        $.RaiseStage("wait")
                    }
                }
                return true
            })
            task.AddTimer(3000, () =&gt; {
                let d = (($.Now() - Baohu.Data.Start) / 1000).toFixed(0)
                Note(`保护开始${d}秒`)
                return true
            })
            task.AddTimer(60000, () =&gt; {
                App.Log("保护等待超时")
                return false
            }).WithName("timeout")
            $.RaiseStage("wait")
        },
        (result) =&gt; {
            if (result.Name == "ok") {
                $.PushCommands(
                    $.CounterAttack(`${GetVariable("id")}'s ${Baohu.Data.ID}`, App.NewCombat("baohu").WithTags(`baohu-${Baohu.Data.Type}`).WithPlan(PlanCombat)),
                    $.Function(Baohu.Finish),
                )
                $.Next()
                return
            }
            App.Send("halt")
            App.Log(`保护NPC${Baohu.Data.NPC.ID}失败`)
            Baohu.Fail()
        }
    )


代码地址
可以看到，我使用了一个叫做PlanProtect的计划(task工厂),创建了一个基于Quest的封装，所有的触发和计时器都绑定在这个Task封装上，并在Quest发生变化时强制失效。
预期管理工具Plan/Task的详细介绍
由于我一直认为Mud机器人是一个工程问题，难点在于可维护性和代码失控。所以，我认为，选用或者开发一个触发失效工具，是十分重要的，也应该是整个机器人的核心驱动逻辑之一。
]]></description><link>https://forum.hellclient.com/topic/49/深入浅出制作全自动mud机器人-延时响应</link><guid isPermaLink="true">https://forum.hellclient.com/topic/49/深入浅出制作全自动mud机器人-延时响应</guid><dc:creator><![CDATA[jarlyyn]]></dc:creator><pubDate>Fri, 08 May 2026 01:55:24 GMT</pubDate></item><item><title><![CDATA[深入浅出制作全自动Mud机器人-随机迷宫地图]]></title><description><![CDATA[随机迷宫以及地图是很多Mud引入的新任务类型。
比如下面就是一个典型的迷宫地图
┌─┬─┬─┬─┬─┬─┬─┬─┐
│★│　　　　　│　　　　　　　│
├　┼─┼　┼─┼　┼─┼─┼　┤
│　│　│　　　│　　　　　│　│
├　┼　┼─┼　┼　┼─┼─┼　┤
│　│　　　　　　　　　│　│　│
├　┼─┼　┼─┼　┼─┼　┼　┤
│　　　│　│　　　　　│　　　│
├─┼　┼　┼─┼─┼─┼　┼　┤
│　│　　　│　│　│　│　│　│
├　┼　┼　┼　┼　┼　┼　┼─┤
│　│　│　　　　　│　│　│　│
├　┼　┼─┼─┼─┼　┼　┼　┤
│　　　│　　　　　　　　　│　│
├─┼─┼　┼─┼　┼─┼　┼　┤
│　　　　　│　　　　　│　　　│
└─┴─┴─┴─┴─┴─┴─┴─┘

这种地图本质就是在服务器创建了一系列的虚拟房间，并建立了房间中之间的联系，最后将这些方向和关系再打印成文字图案。
那么，处理方式也比较直接。
先将文字图标准化，化成比较简单的单字节图，然后再遍历建立临时房间和出口信息就可以。
参考HellMapManager.Dll的范例代码
local hmmlib=require('hmm')
local hmm=hmmlib.new()
hmm.DllEncoding=0 --0 for utf-8, 1 for gbk
local json=require('json')
local file=assert(io.open("hongchen.hmm","r"))
local data=file:read("*a")
file:close();
hmm:call("import",data)
local extrooms={}
local extpaths={}
local function buildMyRoom(entry,roomid,roomname)
    local myroom1=hmmlib.Room.new()
    myroom1.Key="myroom-entry"
    myroom1.Name=roomname.."大厅"
    local myroomexit1=hmmlib.Exit.new()
    myroomexit1.Command="open gate;n"
    myroomexit1.To="myroom-home"
    local myroomexit2=hmmlib.Exit.new()
    myroomexit2.Command="out"
    myroomexit2.To=entry
    myroom1.Exits={myroomexit1,myroomexit2}
    local myroom2=hmmlib.Room.new()
    myroom2.Key="myroom-home"
    myroom2.Name=roomname.."卧室"
    local myroomexit3=hmmlib.Exit.new()
    myroomexit3.Command="open gate;s"
    myroomexit3.To="myroom-entry"
    myroom2.Exits={myroomexit3}
    table.insert(extrooms, myroom1)
    table.insert(extrooms, myroom2)
    local entrypath=hmmlib.Path.new()
    entrypath.From=entry
    entrypath.Command="go "..roomid
    entrypath.To="myroom-entry"
    table.insert(extpaths, entrypath)
end

buildMyRoom("2440","myhouse","大别野")

local mazemaptxt=&lsqb;&lsqb;
┌─┬─┬─┬─┬─┬─┬─┬─┐
│　　　　　　　　　　　　　│　│
├─┼　┼─┼─┼─┼─┼　┼　┤
│　│　　　│　　　　　│　│　│
├　┼　┼─┼　┼　┼─┼　┼　┤
│　　♚　　│　│　│　　　│　│
├─┼　┼　┼　┼　┼─┼　┼　┤
│　　　│　　　│　│　　　　　│
├─┼　┼─┼　┼─┼─┼　┼─┤
│　│　│　　　│　　　　　│　│
├　┼　┼─┼─┼　┼　┼─┼　┤
│　│　　　│　　　│　　　│　│
├　┼　┼─┼─┼　┼　┼　┼　┤
│　│　│　　　　　│　│　　　│
├　┼　┼─┼　┼　┼　┼　┼─┤
│　　　│　　　│　│　│　　★│
└─┴─┴─┴─┴─┴─┴─┴─┘
&rsqb;&rsqb;
--♚为当前位置，★为出口

local Maze={}
Maze.__index=Maze
Maze.new=function()
    local self=setmetatable({},Maze)
        self.RoomPrefix="maze-"
        self.RoomKeys={}
        self.Rooms={}
        self.Paths={}
        self.EntryRoom=""
        self.ExitRooms={}
    return self
end
function Maze:buildRoomKey(x,y)
    return self.RoomPrefix..tostring(x).."-"..tostring(y)
end
function Maze:buildExit(room,tokey,command)
    local exit1=hmmlib.Exit.new()
    exit1.Command=command
    exit1.To=tokey
    table.insert(room.Exits, exit1)
end
function Maze:readmap(maptxt)
    local maplines={}
    for line in string.gmatch(maptxt, "[^\r\n]+") do
        if #line&gt;0 then
        --因为utf8不定长，进行处理
        line = string.gsub(line,"　"," ")
        line = string.gsub(line,"│","+")
        line = string.gsub(line,"─","+")
        line = string.gsub(line,"├","+")
        line = string.gsub(line,"┤","+")
        line = string.gsub(line,"┴","+")
        line = string.gsub(line,"┬","+")
        line = string.gsub(line,"└","+")
        line = string.gsub(line,"┘","+")
        line = string.gsub(line,"┌","+")
        line = string.gsub(line,"┐","+")
        line = string.gsub(line,"┼","+")
        line = string.gsub(line,"♚","I")
        line = string.gsub(line,"★","O")
        table.insert(maplines, line)
        end
    end
    local roomwidth=(#maplines[1]-1)/2
    local roomheight=(#maplines-1)/2
    local currentY=1
    --按行处理
    while currentY&lt;=roomheight do
        local currentX=1
        --处理每个房间
            while currentX&lt;=roomwidth do
                local roomkey=self:buildRoomKey(currentX-1, currentY-1)
                table.insert(self.RoomKeys, roomkey)
                local room=hmmlib.Room.new()
                room.Key=roomkey
                if maplines[currentY*2]:sub(currentX*2, currentX*2)=="I" then
                    self.EntryRoom=roomkey
                end
                if maplines[currentY*2]:sub(currentX*2, currentX*2)=="O" then
                    table.insert(self.ExitRooms, roomkey)
                end
                if maplines[currentY*2]:sub(currentX*2+1, currentX*2+1)==" " then
                    self:buildExit(room,self:buildRoomKey(currentX,currentY-1),"e")
                end
                if maplines[currentY*2]:sub(currentX*2-1, currentX*2-1)==" " then
                    self:buildExit(room,self:buildRoomKey(currentX-2,currentY-1),"w")                    
                end
                if maplines[currentY*2+1]:sub(currentX*2, currentX*2)==" " then
                    self:buildExit(room,self:buildRoomKey(currentX-1,currentY),"s")
                end
                if maplines[currentY*2-1]:sub(currentX*2, currentX*2)==" " then
                    self:buildExit(room,self:buildRoomKey(currentX-1,currentY-2),"n")                    
                end
                table.insert(self.Rooms, room)
                currentX=currentX+1
            end
        currentY=currentY+1
    end
end
function Maze:applyTo(environment)
    for _, room in ipairs(self.Rooms) do
        table.insert(environment.Rooms, room)
    end
    for _, path in ipairs(self.Paths) do
        table.insert(environment.Paths, path)
    end
end

local mymaze=Maze.new()
mymaze:readmap(mazemaptxt)
local query=hmmlib.QueryPathAny.new()
query.From={"2522"}
query.Target={"myroom-home"}
query.Environment=hmmlib.Environment.new()
query.Environment.Rooms=extrooms
query.Environment.Paths=extpaths
local result=json.decode(hmm:call("querypathany", json.encode(query)))
if (result==nil) then
    print("No home path found")
else
    print("Home path found:")
    print(json.encode(result))
end

-- 遍历迷宫后离开
local queryall=hmmlib.QueryPath.new()
queryall.Start=mymaze.EntryRoom
queryall.Target=mymaze.RoomKeys
queryall.Environment=hmmlib.Environment.new()

mymaze:applyTo(queryall.Environment)
result=json.decode(hmm:call("querypathall", json.encode(queryall)))
if (result==nil) then
    print("No full maze path found")
else
    print("Full maze path found:")
    print(json.encode(result))
end
--遍历完成，离开路径
query=hmmlib.QueryPathAny.new()
query.From={result["To"]}
query.Target=mymaze.ExitRooms
query.Environment=hmmlib.Environment.new()
mymaze:applyTo(query.Environment)
result=json.decode(hmm:call("querypathany", json.encode(query)))
if (result==nil) then
    print("No maze path found")
else
    print("Maze path found:")
    print(json.encode(result))
end

在处理动态地图时，核心还是不要想的太复杂，直接找出Room的对应元素，以及相应的Exit，然后根据需要遍历或者导航即可。
]]></description><link>https://forum.hellclient.com/topic/48/深入浅出制作全自动mud机器人-随机迷宫地图</link><guid isPermaLink="true">https://forum.hellclient.com/topic/48/深入浅出制作全自动mud机器人-随机迷宫地图</guid><dc:creator><![CDATA[jarlyyn]]></dc:creator><pubDate>Tue, 05 May 2026 17:15:49 GMT</pubDate></item><item><title><![CDATA[深入浅出制作全自动Mud机器人-问与答]]></title><description><![CDATA[问与答是一个最为典型简单的Mud解密，是大部分Mud任务的基础与核心，可以作为我们研究Mud解密的入口。
对于一个标准的问答对话来说，一般是

主动发起ask
游戏提示 你向XXXX打听关于XXXX的事情
可能有部分干扰，没听清之类
紧跟着你打听的内容，NPC会进行回复。同时NPC也会恢复其他玩家的问题，这时可能对你有所干扰。

这时，很多老玩家可能会向你介绍多行匹配这一神器。
但很可惜，这只是试图逃避正确解决问答问题的逃课行为。
多行匹配完全不适合问答解密上。因为多行匹配无法进行逻辑处理，无法引入状态。也无法标准化处理。
因此，多行匹配在问答问题上，只能算是一个补丁。如果你的机器建立在大量补丁上，很快就会带来难以解决的架构问题。
那么，怎么来进行一个标准化的问答呢？
很简单，对NPC的回答进行录制。
在ask npc时，同时发送你的ask指令和同步指令。对应的，你会预期两个服务器的回复

你想XXX大厅关于XXX的事情:开始录制，把这行之后的每一行都追加到一个数组里
你的同步响应：结束录制，ask结束，进行下一步处理。

这样，就把问答问题，转换为对一个字符串数组的按行分析。
再配合简单的状态机形式，一般就是一个局部的Mode变量，就行解决大部分的问题了。
以hongchenjs的长安任务为例，需要向梁兴禄发起ask 任务，处理的代码如下
    Changan.Check = function () {
        if (App.Data.Ask.Answers.length) {
            if (App.Data.Ask.Answers[0].Line == "梁兴禄对你说道：本府不是已经给你派发了差事，完不成的话就先跟我取消。") {
                Changan.Fail();
                return;
            }
            if (App.Data.Ask.Answers[0].Line == "梁兴禄对你说道：你的江湖经验已经很高了，再在衙门里也混不出个名堂了。") {
                Quest.Cooldown(10 * 60 * 60 * 1000)
                App.Next();
                return

            }
            if (App.Data.Ask.Answers[0].Line == "梁兴禄盯着你看了看，说道：“你刚取消过一次任务，过一分钟再来吧。”") {
                Quest.Cooldown(60 * 1000)
                // App.Core.Timeslice.Change("")
                App.Next();
                return
            }
            if (App.Data.Ask.Answers[0].Line.match(matcherTooMany)) {
                Quest.Cooldown(4 * 3600 * 1000)
                // App.Core.Timeslice.Change("")
                App.Next();
                return
            }
            if (App.Data.Ask.Answers[0].Line == "梁兴禄对你说道：嗯，既然你有心为衙门出力，我便给你个差事。") {
                Changan.All++;
                App.Send("changanjob")
                if (App.Data.Ask.Answers[1].Line == "梁兴禄说着便拿出一份公函交给你。") {
                    Changan.Gonghan()
                    return
                }
                Changan.Check2()
                return
            }
        }
        App.Fail()
    }
    Changan.Check2 = function () {
        let result = App.Data.Ask.Answers[1].Line.match(matcherCode)
        if (result) {
            Changan.Data.Name = result[2];
            for (var i = 3; i &lt; App.Data.Ask.Answers.length; i++) {
                let token = App.Data.Ask.Answers[i].Line.match(matcherCodeToken)
                if (token) {
                    Changan.Data.Code += token[1].trim()
                } else {
                    break
                }
            }
            Changan.Data.Type = "code"
            Changan.DoCode()
            return
        } else {
            App.Log("未知的长安任务对话")
            Changan.Fail()
            return
        }
    }


代码地址
能很好的看出怎么用标准录制的问答信息，来解决复杂谜题。
]]></description><link>https://forum.hellclient.com/topic/47/深入浅出制作全自动mud机器人-问与答</link><guid isPermaLink="true">https://forum.hellclient.com/topic/47/深入浅出制作全自动mud机器人-问与答</guid><dc:creator><![CDATA[jarlyyn]]></dc:creator><pubDate>Mon, 04 May 2026 16:49:27 GMT</pubDate></item></channel></rss>