北大侠客行MUD论坛

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

杰哥瞎扯蛋之移动模块上篇地图信息

[复制链接]
发表于 2024-10-21 16:43:28 | 显示全部楼层 |阅读模式
以常见的武侠MUD来说,做机器人最核心的是3大模块

1.地图模块,房间系统,涉及到所有移动。
2.用户信息模块,道具信息,涉及到用户的准备活动(确保在一个良好的状态)
3.战斗模块,应对战斗中所有的变数。

其中地图模块是基础中的基础。

这篇瞎扯蛋,主要是来盘一盘mud/mud机器人的地图/房间。
北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
 楼主| 发表于 2024-10-21 16:50:56 | 显示全部楼层
首先,我们要理解下,对于MUD来说,地图是什么,房间是什么。

让我找出一份20年前的某MUD代码(非北侠)。

地图大概是这么样的一个布局


大体上按区域布局,一个房间一个文件。对于一个房间来说,"/d/[区域名]/[房间名].c"大体可以认为是一个为一ID。

每个房间大体是这样的

  1. inherit ROOM;

  2. void create()
  3. {
  4.         set("short", "中央广场");
  5.         set("long", @LONG
  6. 这里是城市的正中心,一个很宽阔的广场,铺着青石地面。一些游手好
  7. 闲的人在这里溜溜达达,经常有艺人在这里表演。中央有一棵大榕树,盘根
  8. 错节,据传已有千年的树龄,是这座城市的历史见证。树干底部有一个很大
  9. 的树洞 (dong)。 你可以看到北边有来自各地的行人来来往往,南面人声鼎
  10. 沸,一派繁华景象,东边不时地传来朗朗的读书声,西边则见不到几个行人,
  11. 一片肃静。
  12. LONG );
  13.         set("no_sleep_room",1);
  14.         set("outdoors", "city");
  15.         set("item_desc", ([
  16.                 "dong" : "这是一个黑不溜湫的大洞,里面不知道有些什么古怪。\n",
  17.         ]));

  18.         set("exits", ([
  19.                 "east" : __DIR__"dongdajie1",
  20.                 "south" : __DIR__"nandajie1",
  21.                 "west" : __DIR__"xidajie1",
  22.                 "north" : __DIR__"beidajie1",
  23.         ]));

  24.         set("objects", ([
  25.                 __DIR__"npc/liapo" : 1,
  26.         ]));

  27.         setup();
  28. }

  29. void init()
  30. {
  31.         add_action("do_enter", "enter");
  32. }

  33. int do_enter(string arg)
  34. {
  35.         object me;
  36.         me = this_player();

  37.         if (! arg || arg == "")
  38.                 return 0;

  39.         if (arg == "dong")
  40.         {
  41.                 if (me->is_busy())
  42.                         return notify_fail("你的动作还没有完成,不能移动。\n");

  43.                 message("vision",
  44.                         me->name() + "一弯腰往洞里走了进去。\n",
  45.                         environment(me), ({me}) );
  46.                 me->move("/d/gaibang/inhole");
  47.                 message("vision",
  48.                         me->name() + "从洞里走了进来。\n",
  49.                         environment(me), ({me}) );
  50.                 return 1;
  51.         }
  52. }      
复制代码


上面是扬州广场

  1. // Room: /city/dangpu.c
  2. // YZC 1995/12/04

  3. inherit ROOM;

  4. void create()
  5. {
  6.         set("short", "当铺");
  7.         set("long", @LONG
  8. 这是一家以买卖公平著称的当铺,一个五尺高的柜台挡在你的面
  9. 前,柜台上摆着一个牌子(paizi), 柜台后坐着唐老板,一双精明的
  10. 上上下下打量着你。
  11. LONG
  12.         );
  13.         set("no_fight", 1);
  14.         set("no_steal", 1);
  15.         set("no_beg",1);
  16.         set("item_desc", ([
  17.                 "paizi" : "公平交易\n
  18. sell        卖
  19. buy         买
  20. redeem      赎
  21. value       估价
  22. ",
  23.         ]));
  24.         set("objects", ([
  25.                 __DIR__"npc/tang" : 1,
  26.         ]));
  27.         set("exits", ([
  28.                 "west" : __DIR__"nandajie1",
  29.                 "down" : __DIR__"xsmidao",
  30.         ]));

  31.         setup();
  32. }

  33. int valid_leave(object me, string dir)
  34. {
  35.         if (dir == "down" && me->query("family/family_name") != "雪山寺")
  36.                 return notify_fail("唐楠眼睛一翻,道:干什么来了,想偷东西啊?\n");

  37.         return ::valid_leave(me, dir);
  38. }
复制代码


这是扬州当铺

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
 楼主| 发表于 2024-10-21 17:00:47 | 显示全部楼层
从代码和我们实际的游戏体验可以得出,每个房间有一些基本属性

1.唯一ID。当然,这个只有wiz可以看到,我们只能自己编(唯一标识)
2.出口列表,对应了房间和房间之间的直接关联。 开始方向->方向指令->目标房间,这是最简单的格式(常规出口)
3.特殊出口/指令。每个房间可以通过 add_action 添加指令,加入响应的特殊指令,执行 me->move 指令,进行移动(特殊的移动)
4.移动验证指令valid_leave,在移动时加入条件判断,确定一个出口是否能成功使用(移动的条件)
5.objects,房间对象的列表(道具清单)
6.特殊设置,比如是否是室外,是否是可以睡觉/灌水等等(特殊属性)
7.其他文字描述。
按照我们实际使用的目的,其实这类信息分为两类

1.地图关系类,就是所谓的点对点地图,不管是不是用户的当前房间都要关注,比如 1,2,3,4,6
2.房间信息类,就是当前房间的信息,决定了用户和环境的交互,包括1,2,5,7

很明显,对于一个地图模块来说,需要将地图关系类以合适的易于维护的格式储存起来。

同时,要和维护用户的状态道具一样,维护一份实时的当前房间信息清单,用来做代码驱动的辅助依据。
北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
 楼主| 发表于 2024-10-21 17:01:43 | 显示全部楼层
补一个add_action的例子

  1. //Room: xiaoshulin1.c 小树林
  2. //Date: Oct. 2 1997 by That

  3. inherit ROOM;
  4. void create()
  5. {
  6.       set("short","小树林");
  7.       set("long",@LONG
  8. 这是峨嵋山金顶华藏庵外的一片小树林。林中没有路,但地上依稀有些足
  9. 迹,似乎刚有人走过。北面有一扇小窗。
  10. LONG);
  11.       set("outdoors", "emei");
  12.       set("exits",([ /* sizeof() == 1 */
  13.           "south"   : __DIR__"xiaoshulin2",
  14.       ]));
  15.       set("no_clean_up", 0);
  16.       setup();
  17. }

  18. void init()
  19. {
  20.       add_action("do_jump", "jump");
  21. }
  22. int do_jump(string arg)
  23. {
  24.       object me;
  25.       if (!arg || arg !="chuang") return 1;
  26.       me = this_player();
  27.       message_vision("$N趁人不注意,跳进窗里。。\n",me);
  28.       me->move(__DIR__"hcawest");
  29.       message_vision("$N从华藏庵外跳窗进来。\n",me);
  30.       return 1;
  31. }
复制代码


北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
 楼主| 发表于 2024-10-21 17:10:17 | 显示全部楼层
关于地图数据的维护

地图数据维护的核心是 不同ID房间之间的关联。

已知房间A,房间B,求两者关系C。

这个C可以是一个指令(east/west之类),可以有条件,符合条件才能进入(比如各门派专属路径),可以是迷宫(A地到B地之间是一个迷宫,包括所有非直接指令,比如武当新人下山可能被拦要去ask song),在计算路径时应该有权重(比如北侠那蜗牛马车)。

个人比较喜欢用MUD远古大神zsz使用的一套地图文件格式,如果你在其他mud用过mapper.exe的话可能也涉及过。我加入路径权重和反向标签额,但基本格式不变,大概是

  1. 0=中央广场|e:59,enter dong:1927,n:22,s:40,w:1,
  2. 1=西大街|e:0,n:2,s:5,w:7,
  3. 2=衙门大门|n·:3,s:1,
  4. 3=衙门正厅|e:1550,n:4,s:2,w:21,
  5. 4=内宅|s:3,
  6. 5=兵营大门|n:1,s·:6,
  7. 6=兵营|n:5,
  8. 7=西大街|e:1,n:9,s·:8,w:13,
  9. 8=扬州武馆|n:7,
  10. 9=财主大门|n:10,s:7,
  11. 10=财主大院|n:11,s:9,
  12. 11=财主后院|s:10,w·:12,n:2914,
  13. 12=财主西厢|e:11,
  14. 13=西门|e:7,w:14,n:2880,
  15. 14=西门大道|e:13,s·:15,w:19,
  16. 15=武道场|n:14,se:16,sw:18,
  17. 16=武道场|nw:15,sw:17,
  18. 17=武道场|ne:16,nw:18,
  19. 18=武道场|ne:15,se:17,
  20. 19=关洛道|e:14,w:20,
  21. 20=函谷关|e:19,s:77,w:244,
  22. 21=西厅|e:3,
  23. 22=北大街|e·:26,n:24,s:0,w:23,
  24. 23=钱庄|e:22,
  25. 24=北大街|e:27,n:34,s:22,w·:25,
  26. 25=武庙|e:24,nw:1552,u:1551,w:2900,
  27. 26=客店|menter0·:2046,s:1553,u·:-1,w:22,
  28. 27=醉仙楼|e·:28,u:29,w:24,
  29. 28=马厩|goto beijing:1353,w:27,
  30. 29=醉仙楼二楼|d:27,e·:30,
  31. 30=醉仙楼大堂|e:31,n:32,s:33,w:29,
  32. 31=玫瑰宴厅|w:30,
  33. 32=芙蓉宴厅|s:30,
  34. 33=牡丹宴厅|n:30,
  35. 34=北门|n:35,s:24,w:1558,
  36. 35=大驿道|n:36,s:34,
  37. 36=大驿道|n:37,s:35,
  38. 37=大驿道|e·:38,n:39,s:36,
  39. 38=小院|w:37,e:-1,
  40. 39=汉水南岸|sl1>cross:1073,sl1>sl>back:2817,s:37,e:2762,drive>yell boat。:2882,
复制代码

(参考数据,明显不是北侠的地图。)

个人不在地图里记录太多信息,就算北侠版本的地图也就多记一个区域,房间名@区域的格式。

其他文字信息/固定npc由于更新频繁不固定,我是直接做了个在线服务来进行查询的。并且不定期用街景机器人爬一圈,确保信息及时。

这和房间信息的关联是完全不同层次的两块内容,我不太喜欢都放在一起。
北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
 楼主| 发表于 2024-10-21 17:16:04 | 显示全部楼层
当前房间的维护

当前房间可以说重中之重,如果写模块时没有注意维护那以后会一直很蛋疼……

当前房间的维护主要就是在进入房间后进行重置(注意,进入房间和look的行为时一样的,所以我有个特殊的标志位,通过#look可以保持房间主要信息不重置,不是新房间)

进入新房间后,清理所有信息,包括房间ID。然后客是分析和记录。

将各种数据(最重要的时房间对象列表)维护好后,确定房间信息结束,再抛出时间,进行各种处理,比如遍历的判断,移动成功后的房间ID变更,继续走下一个房间等等。

我在其他mud的房间信息处理代码

  1.     App.BindEvent("core.roomname", App.Core.Room.OnName)
  2.     reExit = /[a-z]+/g
  3.     App.Core.Room.OnExit = function (event) {
  4.         event.Context.Propose(function () {
  5.             let result = [...event.Data.Wildcards[1].matchAll(reExit)].map(data => data[0]).sort()
  6.             // App.Data.Room.Exits = result
  7.             App.Map.Room.WithExits(result)
  8.             App.RaiseEvent(new App.Event("room.onexit"))
  9.             PlanOnExit.Execute()
  10.         })
  11.     }
  12.     App.BindEvent("core.onexit", App.Core.Room.OnExit)
  13.     let matcherOnHeal = /^    (\S{2,8})正坐在地下(修炼内力)。$/
  14.     let matcherOnObj = /^    ((\S+) )?(\S*「.+」)?(\S+)\(([^\(\)]+)\)( \[.+\])?(( <.+>)*)$/
  15.     var PlanOnExit = new App.Plan(App.Positions.Connect,
  16.         function (task) {
  17.             task.AddTrigger(matcherOnObj, function (trigger, result, event) {
  18.                 let item = new objectModule.Object(result[4], result[5], App.History.CurrentOutput).
  19.                     WithParam("身份", result[2]).
  20.                     WithParam("外号", result[3]).
  21.                     WithParam("描述", result[6] || "").
  22.                     WithParam("状态", result[7] || "").
  23.                     WithParam("动作", "")
  24.                 App.Map.Room.Data.Objects.Append(item)
  25.                 event.Context.Set("core.room.onobject", true)
  26.                 return true
  27.             })
  28.             task.AddTrigger(matcherOnHeal, function (trigger, result, event) {
  29.                 let item = new objectModule.Object(result[1], "", App.History.CurrentOutput).
  30.                     WithParam("动作", "result[2]")
  31.                 App.Map.Room.Data.Objects.Append(item)
  32.                 event.Context.Set("core.room.onobject", true)
  33.                 return true
  34.             })

  35.             task.AddCatcher("line", function (catcher, event) {
  36.                 return event.Context.Get("core.room.onobject")
  37.             })
  38.         }, function (result) {
  39.             if (result.Type != "cancel") {
  40.                 if (App.Map.Room.Name && !App.Map.Room.ID) {
  41.                     let idlist = App.Map.Data.RoomsByName[App.Map.Room.Name]
  42.                     if (idlist && idlist.length == 1) {
  43.                         App.Map.Room.ID = idlist[0]
  44.                     }
  45.                 }
  46.                 App.RaiseEvent(new App.Event("core.roomentry"))
  47.             }
  48.         })
复制代码


很明显,重点在记录房间内的对象上,以及做了一个房间id的简单匹配(北侠的复杂的多,不过属于不能公开内容,就不展示了。)
北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
 楼主| 发表于 2024-10-21 17:20:28 | 显示全部楼层
  1. {
  2.   "ID": "",
  3.   "Name": "钱庄",
  4.   "Zone": "",
  5.   "Exits": [
  6.     "east"
  7.   ],
  8.   "Data": {
  9.     "Objects": {
  10.       "Items": [
  11.         {
  12.           "Data": null,
  13.           "ID": "Jjc",
  14.           "Key": "",
  15.           "IDLower": "jjc",
  16.           "Label": "丐柒",
  17.           "Params": {
  18.             "身份": "丐帮第十八代传人",
  19.             "描述": "",
  20.             "状态": " <断线中>",
  21.             "动作": ""
  22.           },
  23.           "Mode": 0
  24.         },
  25.         {
  26.           "Data": null,
  27.           "ID": "Gba",
  28.           "Key": "",
  29.           "IDLower": "gba",
  30.           "Label": "霸丐",
  31.           "Params": {
  32.             "身份": "丐帮第十八代传人",
  33.             "描述": "",
  34.             "状态": "",
  35.             "动作": ""
  36.           },
  37.           "Mode": 0
  38.         },
  39.         {
  40.           "Data": null,
  41.           "ID": "Qian yankai",
  42.           "Key": "",
  43.           "IDLower": "qian yankai",
  44.           "Label": "钱眼开",
  45.           "Params": {
  46.             "外号": "钱庄老板「铁公鸡」",
  47.             "描述": "",
  48.             "状态": "",
  49.             "动作": ""
  50.           },
  51.           "Mode": 0
  52.         }
  53.       ]
  54.     }
  55.   }
  56. }
复制代码

我日常维护的房间数据,其中Items的Data是个懒加载的数据。


在这个模块下,我要找NPC是这样找的


  1.     let Checker = function (wanted) {
  2.         let result = map.Room.Data.Objects.FindByName(wanted.Target)
  3.         for (var obj of result) {
  4.             if (obj.ID.indexOf(" ") > 0) {
  5.                 if (MQ.Data.NPC && MQ.Data.NPC.Zone) {
  6.                     MQ.Data.NPC.SetZone(MQ.Data.NPC.Zone)
  7.                 }
  8.                 return obj
  9.             }
  10.         }
  11.         if (App.Map.Room.ID) {
  12.             map.Room.Data.Objects.Items.forEach((item) => {
  13.                 if (item.ID.indexOf(" ") > 0 && item.Label.length < 5) {
  14.                     App.Core.HelpFind.OnNPC(item.Label, item.ID, App.Map.Room.ID)
  15.                 }
  16.             })
  17.         }
  18.         return null
  19.     }
复制代码


可以FindByName,FindByID,FindByLabel,FindByIDLower,取出符合条件的列表,通过First方法取出第一个来处理或者判断有没有

或者直接遍历使用。
北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
 楼主| 发表于 2024-10-21 17:34:18 | 显示全部楼层
地图模块的第三个重要功能:当前状态状态。

一般来说,地图数据和当前房间信息是代码的基础。但是地图模块还有个非常重要的功能,维护当前的移动状态。

MUD的地图是动态的,从我的角度来看, 纯静态的地图很不好用。

比如有些房间,男的可以进去,女的不能进。桃花的可以进去,其他的门派不能进去,轻功好的可以进去,轻功不好的不可以进去。

再比如,有些房间移动时可能发生意外,比如被拦路,比如季节时间不同,需要根据当前状态,动态生成和调整路径。

我的地图模块有两个函数

  1.         FlashTags() {
  2.             this.#tags = {}
  3.             Mapper.flashtags()
  4.             Mapper.ResetTemporary()
  5.             this.#blocked = []
  6.             this.#temporaryPaths=[]
  7.         }
复制代码




  1.         InitTags() {
  2.             this.FlashTags()
  3.             if (this.Move != null) {
  4.                 this.Move.InitTags(this)
  5.             }
  6.             this.#tagsIniter.forEach(fn => {
  7.                 fn(this)
  8.             })
  9.             for (var key in this.#tags) {
  10.                 if (this.#tags[key]) {
  11.                     Mapper.settag(key, true)
  12.                 }
  13.             }
  14.             this.#temporaryPaths.forEach(tp => {
  15.                 Mapper.AddTemporaryPath(tp.from, tp.path)
  16.             })
  17.         }
复制代码


在每次移动时清除移动状态,并调用注册的函数进行状态的初始化。

我先在用的特殊状态包括

  • tag,作为最简单的开关接口,决定某个路径能或不能走。最典型的是性别,门派,轻功是否符合某个条件
  • BlockedPath,拦截的路径,被NPC然接后,会调用移动的Retry方法,屏蔽当前的房间移动,重新计算遍历和行走路线
  • Whitelist/Blacklist,可以选择移动只在固定的房间或者不在固定的房间内。
  • TemparyPath,临时路径,比如有些路径需要随机刷的NPC传送的话,可以在当前房间有对应的NPC临时开启。


一般来说,用Tag和BlockedPath,配合移动的迷宫模块,能解决绝大部分的mud移动的问题了。
北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
 楼主| 发表于 2024-10-21 17:36:48 | 显示全部楼层
基础部分差不多到这里了。

下篇应该是移动模块。

主要包含

1.移动的通用实现
2.迷宫和DFS模块
3.动态计算和重置,动态生成路径的 移动/多房间/固定路径遍历
4.移动的快照和恢复,可以在移动中插入战斗/手动移动,并还原继续原移动。
北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
发表于 2024-11-6 13:09:33 | 显示全部楼层
继续,不要停~
收获颇多!
北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2024-12-5 09:30 AM , Processed in 0.013533 second(s), 15 queries .

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

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