jarlyyn 发表于 2025-12-1 16:15:00

杰哥瞎扯淡之伪动态路线规划

更新软件doc的东西,拿出来水一贴

一般来说,Mud的路线规划包括静态规划和动态规划。

这里的静态规划和动态规划的定义为


[*]静态规划:在地图上进行规划时,各个路径的移动互相独立,不会相互干扰。一般的路线规划都是这种。
[*]动态规划:在规划时,一个行动可能会影响到其他行动。比如你进入A出口,可能会关闭b和c出口。这种一般是动态迷宫的解法。


为了解决实际上地图的动态化的需求,引入了上下文(Context)的概念,通过动态的生成 静态地图 的方法,来完成实际需求。

具体来说,把地图分为固定和动态的两个部分。

固定的部分,以静态文件的形式,作为代码的一部分,既代码的固有数据。

动态的部分,由代码在运行时,根据实际的ID和服务器状态进行实时生成。

在每次路线规划时,同时使用固定部分和动态部分生成当前的静态地区信息。

在这里,我将固定部分成为地图信息,而动态部分,称为上下文。

jarlyyn 发表于 2025-12-1 16:16:03

上下文包含的内容包含


[*] Tags 标签,字符串>数字对,匹配 出口中的出口条件,动态的开关出口。
[*] RoomConditions 房间条件,与Tags想法,匹配房间的标签,动态的开关房间。
[*] Rooms 房间列表,额外的临时房间,可以存放个人房屋/迷宫等非通用的房间。
[*] Whitelist 白名单,限制只在指定的房间中规划,较少使用,一般可以使用房间条件进行限制。
[*] Blacklist 黑名单,临时拉黑某些房间,可以应对临时变危险的房间。
[*] Shortcuts 捷径,一般是个人专属的可以飞行到某些地点的指令。
[*] Paths 临时路径,一般用来将临时房间与固定房间相连,也可以在特殊的情况下使用。
[*] BlockedLinks 禁用路径,一般用于被npc拦路或者临时关闭的情况下,临时禁止某两个房间的之间的路径参与规划。
[*] CommandCosts 临时指令消耗,临时调整某些指令的消耗,变相实现路径消耗的动态化。理论上解决实现特殊门派的特殊指令和其他门派的消耗不同的情况。


本质上,上下包含的内容,就是临时加入/开关/修改固定地图中现有的信息,对固定地图进行微调。

jarlyyn 发表于 2025-12-1 16:25:31

实际案例

1.门派专有路径

首先,我定义了一个family.txt,里面有门派的信息,节选如下

桃花岛|1990|huang yaoshi|1995|1994|th
关外胡家|2355|hu fei|2364,1929|1243,1927|hu
雪山寺|2139|jiumo zhi|2364|2140|xs
神龙教|41|pang toutuo|2572|40|sld


很明显,第6个就是门派的tag

在初始化时

      let fam = App.Core.NPC.Family]
      if (fam) {
            Note("引入门派设置")
            App.Params.LocSleep = fam.LocSleep
            App.Params.MasterID = fam.MasterID
            App.Params.LocMaster = fam.LocMaster
            App.Params.LocDazuo = fam.LocDazuo
            App.Params.IDPass = fam.IDPass
      }


会根据score中取得的门派,获取这个IDPass

在维护临时信息的钩子里

    //根据门派调整移动信息
    App.Map.AppendTagsIniter((map) => {
      if (App.Params.IDPass) {
            App.Params.IDPass.split(",").forEach(val => {
                map.SetTag(val.trim(), 1)
            })

      }
    })


这样,在遇到地图有门派信息时


就能限定只有某个门派(比如丐帮)能走这个路径了。

同理也可以通过性别,轻功,经验等临时开关出口。

jarlyyn 发表于 2025-12-1 16:30:20

继续,个人房间。

一些mud(包括北侠)里,可以有个人自建房屋。但是每个人的自建房屋位置可能不同,不同的ID可能有的有房子,有的每房子,怎么办呢。

这时候我就通过临时房间和临时出口来解决这个问题。

我在机器里设置了一个叫house的变量,有房子的可以按一定的格式填入房屋信息。

然后机器里会读取这个变量并分析

            var data = line.split(" ")
            if (data.length != 3) {
                world.Note("解析房屋信息失败,格式应该为 '包子铺 bzp 1558' ")
                return
            }
            var hosuename = data
            var houesid = data
            var houseloc = data


再获取到房间信息后,会动态的生成对应的房子信息

var hosuename = data
            var houesid = data
            var houseloc = data
            App.Mapper.HomeRooms = [
                App.Mapper.NewRoom("1933", `${hosuename}大院`, [
                  App.Mapper.NewExit("n", "1934"),
                  App.Mapper.NewExit("out", houseloc),
                ]),
                App.Mapper.NewRoom("1934", `${hosuename}前庭`, [
                  App.Mapper.NewExit("e", "1936"),
                  App.Mapper.NewExit("push、n。", "1937"),
                  App.Mapper.NewExit("s", "1933"),
                  App.Mapper.NewExit("w", "1935"),
                ]),
                App.Mapper.NewRoom("1935", `右卫舍`, [
                  App.Mapper.NewExit("e", "1934"),
                ]),
                App.Mapper.NewRoom("1936", `左卫舍`, [
                  App.Mapper.NewExit("w", "1934"),
                ]),
                App.Mapper.NewRoom("1937", `走道`, [
                  App.Mapper.NewExit("n", "1938"),
                  App.Mapper.NewExit("push、s。", "1934"),
                ]),
                App.Mapper.NewRoom("1938", `${hosuename}迎客厅`, [
                  App.Mapper.NewExit("n", "1939"),
                  App.Mapper.NewExit("s", "1937"),
                  App.Mapper.NewExit("open door、e", "2533"),
                ]),
                App.Mapper.NewRoom("1939", `议事厅`, [
                  App.Mapper.NewExit("e", "1941"),
                  App.Mapper.NewExit("n", "1942"),
                  App.Mapper.NewExit("s", "1938"),
                  App.Mapper.NewExit("w", "1940"),
                ]),
                App.Mapper.NewRoom("1940", `${hosuename}武厅`, [
                  App.Mapper.NewExit("e", "1939"),
                ]),
                App.Mapper.NewRoom("1941", `${hosuename}武厅`, [
                  App.Mapper.NewExit("w", "1939"),
                ]),
                App.Mapper.NewRoom("1942", `${hosuename}中庭`, [
                  App.Mapper.NewExit("open west、w", "1943"),
                  App.Mapper.NewExit("n", "1944"),
                  App.Mapper.NewExit("s", "1939"),
                ]),
                App.Mapper.NewRoom("1943", `左厢房`, [
                  App.Mapper.NewExit("e", "1942"),
                ]),
                App.Mapper.NewRoom("1944", `后院`, [
                  App.Mapper.NewExit("e", "-1"),
                  App.Mapper.NewExit("n", "1947"),
                  App.Mapper.NewExit("s", "1942"),
                  App.Mapper.NewExit("w", "1945"),
                ]),
                App.Mapper.NewRoom("1945", `厨房`, [
                  App.Mapper.NewExit("e", "1944"),
                ]),
                App.Mapper.NewRoom("1946", `备用`, [
                  App.Mapper.NewExit("e。", "1949"),
                ]),
                App.Mapper.NewRoom("1947", `后花园`, [
                  App.Mapper.NewExit("e", "1948"),
                  App.Mapper.NewExit("s", "1944"),
                  App.Mapper.NewExit("open door、w、close door", "2681"),
                ]),
                App.Mapper.NewRoom("1948", `竹林`, [
                  App.Mapper.NewExit("e", "1949"),
                  App.Mapper.NewExit("w", "1947"),
                ]),
                App.Mapper.NewRoom("1949", `听涛阁`, [
                  App.Mapper.NewExit("w", "1948"),
                ]),
            ]
            world.Note("在位置 " + houseloc + " 添加房屋" + hosuename + "入口[" + houesid + "]")
            App.Mapper.HouseID = houesid
            App.Mapper.HouseLoc = houseloc

并在每次规划房间时,加入这些临时房间和临时出入口

    App.Mapper.InitTag = function (map) {
      if (App.Mapper.HomeRooms.length) {
            map.AddTemporaryRooms(App.Mapper.HomeRooms)
      }
      if (App.Mapper.Paths.length) {
            App.Mapper.Paths.forEach((p) => {
                map.AddTemporaryPath(p)
            })
      }
}

jarlyyn 发表于 2025-12-1 16:35:32

继续,NPC拦路

这个其实在北侠里特别有用,因为不光npc会拦路,大佬也可能会拦路。

首先,当然是做触发,每个mud出发不一样,我就不贴了。

然后,就是判断当前的移动的出发和结束位置。如果能获取到,就把这两个房间之间的连接屏蔽掉,再重新规划路线重新移动

      if (App.Map.Move) {
            if (App.Map.Room.ID) {
                let step = App.Map.Move.GetLastStep()
                if (step.Target) {
                  App.Core.Blocker.Block(App.Map.Room.ID, step.Target)
                }
            }
            App.Map.InitTags()
            App.Map.Retry()
      }


屏蔽的代码是带时间加入一个大表里

    App.Core.Blocker.Block = (from, to) => {
      App.Core.Blocker.Blocked["form>to"] = {
            Created: (new Date()).getTime(),
            From: from,
            To: to,
      }
    }


然后每次计算路径的时候,判断是否过期

    //初始化地图信息时拦截拉黑的出口
    App.Map.AppendTagsIniter((map) => {
      for (var key in App.Core.Blocker.Blocked) {
            let blocked = App.Core.Blocker.Blocked
            if ((new Date()).getTime() - blocked.Created > 100000) {
                delete (App.Core.Blocker.Blocked)
            } else {
                map.BlockPath(blocked.From, blocked.To)
            }
      }
    })


嗯,这样本来大佬1个id就能拦住你了,这下至少得4个 {:7_279:}

jarlyyn 发表于 2025-12-1 16:40:19

记下来是固定迷宫。

我这个迷宫是每次生成有固定地图的,直接读地图

这是地图生成类

    let matcherLine = /^([◎─ ●│]+)$/
    //副本迷宫解析类
    class Maze {
      Prefix = "mazemap"
      Rooms = []
      Paths = []
      Landmark = {}
      AddRoom(x, y) {
            let room = `${this.Prefix}-${x}-${y}`
            this.Rooms.push(room)
            return room
      }
      AddRoomPath(x, y, x2, y2, to, from) {
            this.AddPath(`${this.Prefix}-${x}-${y}`, `${this.Prefix}-${x2}-${y2}`, to)
            this.AddPath(`${this.Prefix}-${x2}-${y2}`, `${this.Prefix}-${x}-${y}`, from)
      }
      AddPath(from, to, command) {
            let path = App.Mapper.HMM.Path.New()
            path.From = from
            path.To = to
            path.Command = command
            this.Paths.push(path)
      }
      Install() {
            App.Core.Fuben.CurrentRooms=[]
            this.Rooms.forEach((room) => {
                App.Core.Fuben.CurrentRooms.push(
                  App.Mapper.NewRoom(room,"")
                )
            })
      }
      Destory() {

      }


读地图的代码,解析地图并添加房间/路径

            task.AddTrigger(matcherLine, (tri, result) => {
                task.Data = "ok"
                let data = result
                data = data.replaceAll("", " ")
                if (linenum % 2 == 0) {
                  let x = 0
                  for (var i = 0; i < data.length; i = i + 2) {
                        let room = App.Core.Fuben.Current.AddRoom(x, y)
                        if (data == "●") {
                            let newline = new line.Line()
                            App.History.CurrentOutput.Words.forEach((w) => {
                              newline.AppendWord(w.CopyStyle(w.Text.replaceAll("", " ")))
                            })
                            let l = newline.Slice(i, 1)
                            switch (l.Words.Color) {
                              case "Green":
                                    App.Core.Fuben.Current.Landmark["entry"] = room
                                    break
                              case "Magenta":
                                    App.Core.Fuben.Current.Landmark["exit"] = room
                                    break
                            }
                        }
                        if (i > 0 && data == "─") {
                            App.Core.Fuben.Current.AddRoomPath(x - 1, y, x, y, "e", "w")
                        }
                        x++
                  }
                  y++
                } else {
                  for (var i = 0; i < data.length; i = i + 2) {
                        let x = 0
                        for (var i = 0; i < data.length; i = i + 2) {
                            if (data == "│") {
                              App.Core.Fuben.Current.AddRoomPath(x, y - 1, x, y, "s", "n")
                            }
                            x++
                        }
                  }
                }
                linenum++
                return true
            })


然后添加出入口

    Qinling.AddApth = () => {
      App.Core.Fuben.Current.AddPath("2820", App.Core.Fuben.Current.Landmark["entry"], "s")
      App.Core.Fuben.Current.AddPath(App.Core.Fuben.Current.Landmark["entry"], "2820", "n")
      App.Core.Fuben.Current.AddPath("2823", App.Core.Fuben.Current.Landmark["exit"], "n")
      App.Core.Fuben.Current.AddPath(App.Core.Fuben.Current.Landmark["exit"], "2823", "s")
    }


然后规划地图的时候把这些代码都规划进去

    App.Core.Fuben.OnInitTags = (map) => {
      if (App.Core.Fuben.Current) {
            App.Core.Fuben.Current.Paths.forEach((p) => {
                map.AddTemporaryPath(p)
            })
            map.AddTemporaryRooms(App.Core.Fuben.CurrentRooms)
      }
    }



就直接能用标准的路径规划来规划迷宫了。

jarlyyn 发表于 2025-12-1 16:46:14

再来个飞行的demo

我这个mud里,有宠物的情况下,可以在室外飞行到固定的几个地点

所以我建立了很多Shortcut



没有出发点,限制房间是室外,限制环境条件是ride

然后给室外的房间打上标记



再加上几个出发维护ride标签

    //响应没有马的状况
    App.Engine.SetFilter("core.ride.nohorse", function (event) {
      var cmd = GetVariable("cmd_ride") || ""
      cmd = cmd.trim()
      if ((Mode == 0 || canretry()) && cmd) {
            Mode = 1
            App.Send(cmd + ";whistle;" + cmd)
            App.RaiseEvent(event)
            return
      }
      LastTry = (new Date()).getTime()
      Mode = 2
      App.Map.InitTags()
      App.RaiseEvent(event)
    })
    //响应临时不骑马的状态
    App.Engine.SetFilter("core.ride.later", function (event) {
      LastTry = (new Date()).getTime()
      Mode = 2
      App.RaiseEvent(event)
    })


规划的时候再加入ride的计算
    //判断是否可以raid
    ridable = function () {
      var cmd = GetVariable("cmd_ride") || ""
      cmd = cmd.trim()
      if (cmd) {
            if (Mode == 0 || Mode == 1) {
                return 1
            }
            if (canretry()) {
                Mode = 0
                return 1
            }
      }
      return 0

    }
    let canretry = () => {
      return (new Date()).getTime() - LastTry > 5 * 1000
    }
    App.Map.AppendTagsIniter(function () {
      App.Map.SetTag("ride", ridable())
    })


就无痛无感的加入飞行的功能了。

jarlyyn 发表于 2025-12-1 16:47:29

就到这里,就到这里

打完收工
页: [1]
查看完整版本: 杰哥瞎扯淡之伪动态路线规划