跳转至内容
  • 1 帖子
    88 浏览
    jarlyynJ
    虽然很多客户端和教程介绍机器时,都是用触发来驱动机器。 但是,要做一个稳定,功能强大的机器,被动的靠出发来驱动明显不可行,必须要依靠全面详尽的数据来运行。 在Mud中,有很多指令会对应获取更新数据。常见比如"hp","score","i","skills"等。 我们一般都会根据这些指令的结果来更新数据。 但是,Mud又是动态的,这些数据往往又容易失效。 做任何动作之前猛刷一通指令明显不科学,浪费服务器资源,还干扰用户使用。在这个情况下,我们肯定是要面对有时效性的数据来进行编码。 也就是说,我们在收集数据时,不光光面对是一个采集的问题。还要把数据是做缓存,处理一个缓存失效的问题。 那以缓存的角度来看看到数据的话,实际是要实现以下几个地方 数据的有效期(ttl) 数据回源(发现过期后主动更新数据) 数据的强制更新(更新数据后更新数据和有效期) 数据强制失效 实现的方法其实有很多,我这里说说我的实现方法。 以我的代码为例 在处理数据时,我使用了这么一个结构 let DefaultExecute = function (check) { return check.Command } class Check { constructor(id) { this.#id = id } #id = "" Interval = 0 #last = 0 Execute = DefaultExecute Command = null WithExecute(fn) { this.Execute = fn ? fn : DefaultExecute } ID() { return this.#id } WithCommand(cmd) { if (typeof (cmd) != "function") { this.Command = function () { App.Send(cmd) } } else { this.Command = cmd } return this } WithInterval(interval) { interval = interval - 0 if (isNaN(interval)) { interval = 0 } this.Interval = interval return this } InCooldown() { return (new Date()).getTime() - this.Interval < this.#last } Reset() { this.#last = (new Date()).getTime() } Force() { this.#last = 0 } } 这个机构很明显。 #last 最后一次更新时间 Interval 重新获取数据的间隔(ttl) Execute,Command 更新数据的函数和指令 #id 缓存的 id,可以制定id进行更新 在使用时,我会以这样的形式来注册一个数据检查(更新)器 let checkerSkills = App.Checker.Register("skills", "skills", 300000) 这是注册一个 id为skills,指令为skills,数据有效期为300秒的检查器。 在匹配skill最后的代码最后调用一下Reset function (result) { checkerSkills.Reset() }) 就能按一定的有效期来保存数据了。 然后由于这个是获取技能数据的,所以我绑定了技能提升的触发事件,在技能重置时强制这个缓存数据失效 //技能升级时重置相关的checker App.BindEvent("core.skillimproved", function () { checkerHPM.Force() checkerSkills.Force() checkerJifa.Force() }) 就能比较有效的处理缓存数据了。 在可能需要处理状态的地方,我加入了检查所有预设检查器的代码 //检查的函数 App.Check = () => { App.RaiseEvent(eventBeforeCheck)//触发检查发送指令 let checks = App.Checker.Check()//获取需要执行的检查 if (checks.length == 0) {//无需检查 App.Next() return } App.PushCommands(//一次性执行所有检查 App.Commands.NewFunctionCommand(function () { checks.forEach(check => { check() }); App.Next() }), App.NewSyncCommand(), ) App.Next() } //准备函数,第一个函数时准备id,第二个时准备的上下文(环境变量) App.Prepare = function (id, context) { App.RaiseEvent(eventBeforeCheck) let checks = App.Checker.Check() if (checks.length == 0) { AfterCheck(id, context) return } App.PushCommands( App.Commands.NewFunctionCommand(function () { checks.forEach(check => { check() }); App.Next() }), App.NewSyncCommand(), App.Commands.NewFunctionCommand(function () { AfterCheck(id, context) }), ) App.Next() } 这样就能确保每次在需要数据进行决策(准备)时,使用的是最新的有效的数据。
  • 1 帖子
    61 浏览
    jarlyynJ
    单例模式,是设计模式的一种。它是一种创建型设计模式,它的核心目标是确保一个类只有一个实例,并提供一个全局访问点来获取这个唯一的实例。 听着很牛吧?别管这个,这是被强OOP祸害的语言的鬼话。 单例模式本质就是,全局变量。 好了,使用lua的同学们放下手。全局变量不是你不用加local 来限制使用作用范围的意思。 单例可以通过全局变量来实现,但它强调的还是独一无二性,在英语里要加上the 定冠词那种(嗯,就是黑客帝国的the one)。 说明是一个特殊,独一无二的个体。 在代码开发中,很常见的一种模式是,将整个程序的功能,抽象成一个Applcation,然后建立一个全局单例 Applcation:Instance 来进行代码的开发。 在Javascript的管理,就是建立一个全局的app对象,Lua作为脚本语言,也可以适用这套模式。 那么,问题来了,我们为什么要用这个全局变量(单例)呢? 限制其他全局变量使用。 使用全局变量,就是能方便的在任何代码里进行数据分享/互相调用。 有需要就开一个全局变量,看似开发能快很多,但很快就会让代码失去可维护性,随便动一个变量都生怕整个程序崩了。 当我们有一个特殊的全局变量后,很自然的,我们需要调用的东西都会从这个全局变量(单例上找),不应该再开全局变量。 从这个角度来说,这个单例,就是我们整个机器人的the one 定义应用程序的根(root) 我看过很多全自动机器人代码。有一些,嗯,怎么说呢,让我感觉很乱,无从下手。 代码都是一个个平级的.lua/.js文件,互相之前会相互应用,要看很久才能推测出大概的功能划分。 按数据结构的说法,这些机器人的代码互相之间的关系是一个图(Graph),两两之间都可能有关联,很自由,很强大。 但对于我等普通人来说,难以驾驭。 相对而言,以我的能力,更能处理以数据结构的 树(tree) 的形式来组织的代码。 即从一个根room开始,不停分支,对应着一个一个的节点,和树杈一样。 我们在使用电脑时,最常见的树结构就是目录树,通过一个一个目录来对文件进行组织。 只有了一个固定的根,我们才能很容易的定义文件的代码的结构和组织结构。 给代码一个独一无二的挂载点。 上一个部分讲到了目录这个例子,那么我们扯远点说说Linux的文件管理。 linux下,讲究万物皆文件。不管你是设备,文件系统,文件,目录,都可以用mount的方式挂在到相对于根(root)目录的指定位置下。 那么,当我们使用单例App时,其实也是一样,把我们的代码/对外的结构,添加到单例和他的自元素上,就相当于做一个mount操作。 这时,每个挂载上去的代码,都成了一个单例(单例中的特定部分),都能很方便的找到。 在一般情况下,可能这个优势还不明显。 在javascript和lua中弱类型的脚本语言中,很难精确的定位到一个代码的位置。 那么,你觉得在你的代码中,是找到一个较 room的变量在哪里定义使用方便,还是一个App.Core.Map.Room 方便呢? 代码分组 还是文件目录的例子。 我们一般会怎么组织文件? 先把全放桌面的豪放流请下去。最常见的,其实是是按文件的用途或者特点,分为 文档,视频,照片,作业,学习资料什么的。 同样,既然我们把单例作为文件数来使用,我们也可以把代码和数据进行分区。 比如,主业务逻辑可以放在App.Core.XXX下,代码可以放在App.Data.XXX下,工具函数可以放在App.Utils.xxx下 这样在写代码时,可以一目了然,方便理解和组织。 总结 对于代码来说,全局变量(单例)是必不可少的,但是太多的单例会让代码复杂度指数上升。通过定义一个合适的根单例,我们能更好的组织代码,提可高维护性。
  • 深入浅出制作全自动Mud机器人-组件化

    Script脚本 全自动 机器人 架构
    1
    1 帖子
    48 浏览
    jarlyynJ
    在全自动Mud机器人开发,以及长期的运维更新时,我觉得引入组件化是一件非常重要的底层架构。 组件化,看起来是一个和模块化接近的概念。但本质完全不同。 在我眼里,模块化是将相关的代码进行组织和封装,对外提供统一接口的一种代码组织形式。 组件化的确要依赖于模块化是实现功能。但更重要的是,组件化的目标是 可替代可废弃性。 随着Mud的更新,代码不应该进行大幅度的调整,还是废弃部分组件,新建部分组件,调整部分组件。这样,机器可以先将重点组件进行调整,然后临时禁用不重要的组件,甚至部分影响不大的组件带病工作,实现机器的边开发边运行。 既将代码根据易变程度分为不同的组织,确保核心代码的稳定性,然后组件中可以用各种快速/不健全/临时性的机制确保特殊状态下机器的可用性,以及确保机器处于一种可以从小到大,逐渐成长的状态。 为了实现这个目的,组件化需要实现的功能是 组件标准化 同一类的组件,应该有相同的接口和核心代码进行交互。这样,核心代码才能在调整主键时不需要关注细节和深入。 以我的newhelljs为例,使用了多种组件 角色准备阶段的提案组件 通过通用的Submit参数,实现判断是否进入某个准备阶段 任务本身的[Quest组件](https://github.com/hellclient-scripts/newhelljs/blob/main/script/src/core/quests.js 通过Ready参数,Cooldown属性,OnHUD/OnSummary|OnReport等接口,实现了人物与核心代码的隔离 配置的Condition组件 使得是否接任务/是否使用某个技能等等,都与核心代码无关,完全通过定义新的Condition实现。 很明显,组件标准化和能区分组件代码和核心代码的前提。如果不对组件进行标准化,则可扩展性可维护性也成了无根之水。 控制反转 控制反转(IOC)是一个常见的程序开发思路。 所谓的反转,就是将** 主程序创建类后,初始化类** 反转为 ** 主程序只负责创建类,类自行初始化) 实现了控制反转后,才能实现组件的封装,将组件与核心代码分离。 核心代码只负责两件事情: 如果你随手搜索一下,可能会发现一大堆依赖注入,工厂模式什么的复杂东西。 不用管那些,那些主要是对强OOP,静态编译的Java强调的东西。 对于js/lua的实现,参考我上面的例子中,都会传入一个函数作为参数就可以了。 把初始化函数放在组件的制定位置,核心代码创建组件后,把配置作为参数带入初始化函数就可以了。 这里说的控制反转,只是提醒一下,不要在核心端去设置组件,让组件自包含。 全局注册 全局注册很简单,就是建立一个全局的object/table变量,给每一个组件起一个唯一的字符串主键,将组件或者组件的生成函数都放在里面就行。 全局注册的目的是 在进行组件的替换/更新甚至顺序调整时,无需调整核心代码,只需要调整某个配置即可。 通过同一的调用方式,确保不会有其他位置的代码来直接调用组件内的代码,避免组件修改时出现全局问题。 方便的管理方式,能很容易的明白有多少个可用模块,不用去各个代码目录里寻找。 使用全局注册要注意的点是 只通过 注册的表 与组件交互,不建立直接处理的代码 在多个组件交互时,可以通过给组件加接口/全局事件/核心组里注册特殊毁掉的方式来处理。 重要的点 使用组件模式,其实是按照我们最初的目的来使用,也就是 将代码分为干净的核心代码,和脏(与业务更接近)的组件代码。确保代码指令控制在一个基本的可用度上。 通过配置,设置,能动态的调整激活的模块,实现更复杂的功能。 技术只是手段,代码如何处理,不一定要强求某个模式,还是为了实际目的服务。
  • 我使用的一种机器人代码分层组织方式

    Script脚本 机器人 架构
    1
    1 帖子
    54 浏览
    jarlyynJ
    在写gui程序时,我整理了一套代码组织方式 参看 HellMapManager项目就是按这个架构来组织的。 对于Mud机器人,我觉得这个组织形式也适用。当然,由于主要业务不同,住址形式也会不同。我只是将所有的代码根据对应的层次打个标签,避免不同层级的代码过于混淆。 UI 交互层 这个完全绑定于客户端的实现,在Mud机器人里也相对不重要,可以和Service层混在一起 Service 服务器层。从字面意义上来说,就是为最终用户提供的功能的封装层。UI交互的内容最终绑定到服务层上。最大的用途是防止用户UI直接操作到业务层,做封装和拦截,以及抽象。 Core 核心(业务) 层。对于Mud机器人来说,绝大部分的代码都是核心层。所以实际写机器人的时候Core肯定还要做细分,比如底层核心和任务模块。 Helper 辅助类,业务层和数据层直接的纽带,将数据的细节对业务做一定的封闭。 Adapters 适配器层 抽象底层交互。对于Mud机器人来说,就是将客户端的触发/别名/计时器做一个抽象,以及对应的事件Event框架。引入这个层的话能提升机器人可迁移性和做测试的可能。 Model 模型 在各个业务层中共同的数据结构。在Mud中比如房间,玩家,道具等等。 utils 工具层。比如中文转数字,格式化文字等纯与业务无关的,全局都可能使用的代码
  • 深入浅出制作全自动Mud机器人-运维通知

    Script脚本 全自动 机器人
    1
    1 帖子
    48 浏览
    jarlyynJ
    在引言中,我们提到过,制作全自动机器人的一个重要课题是 怎么才能让循环更稳定,控制更多的资源和状态? 那么,提升稳定性的一个重要的工作,就是当服务(机器人)发生意外时,能及时的通知运维(开发者/用户)查看问题,进行操作。 就是当机器停顿,无法继续,甚至ID死亡时,以方便的手段通知用户,记录问题现场,恢复程序执行。 这也是一个标准的运维工作。 所以我建议参考现在主流的运维工作流程。 邮件形式 Hellclient最早是支持嵌入STMP信息,提供了内建方法可以发送电子邮件。 但在实际使用时,基本处于被废弃状态。 因为现在主流的运维通知终端是手机,安卓或者iOS。 邮件通知在手机上面临了一下问题 手机设备上,邮件客户端无法保证后台常驻,实时性往往有很大问题。 邮件客户端又有各种实现,对于链接的处理形式不同,无法保证唤起我的APP端。 邮件的安全级别很高,所有各个邮件服务器的安全设置很复杂,对用户并不友好。 通知服务 我的Hellclient UI项目曾经实通过腾讯推送实现过原生的推送功能。 但现在这个功能已经被废弃了。 通知服务会涉及到几个难点 用户识别问题。安卓客户端还能自行申请推送服务,iOS不现实,发布时就绑定应用ID。 保活问题。对于国内环境,国产安卓是绕不过去的槛。各种手机的保护和续航是很麻烦的点,只是做一个mud相关的app没法投入这么多精力去处理 这写严重的问题随着新的方案出现,对于Hellclient UI项目属于验证的得不偿失,所以废弃了这个功能。 Webhook 目前的运维主流的通知手段其实还是webhook的形式。 就是在各种工作流软件中,会提供一个标准的,地址带身份验证token的http接口。 主要对这个接口发送特殊格式的内容,就会通过服务器向对应的手机APP发送一条通知。 处于这个通知的专用型,一般都能确保拉起手机中注册过的APP直接处理。 可以说完美的适合了Mud机器人,作为一个标准意义上的服务的运维要求。 而且webhook形式的要求最低,基本不需要客户端做特殊支持,只要提供标准的HTTP接口和JSON支持就可以了。 国内比较常见的Webhook供应商为 企业微信 钉钉 飞书 国外知名的有 Slack 根据我自己的使用体验和经验,我的机器人使用的飞书通知。目前使用体验良好。 如果不喜欢单独安装App的话,也可以考虑使用企业微信。企业微信可以绑定到微信内,我个人有其他业务是使用企业微信通知的。
  • 深入浅出制作全自动Mud机器人-目录

    已固定 Script脚本 全自动 机器人
    1
    1 帖子
    119 浏览
    jarlyynJ
    序 引言 架构 全局单例 组件化 事件总线 选项模式 核心 主逻辑 数模转换 数据与缓存 准备阶段 指令队列 用户队列 任务编排 战斗子模块 战斗系统 移动子模块 移动模块 移动确认 移动失败处理 惯性导航 移动模块拆解 路线规划配置 迷宫 遍历 解密系统 解密系统简介 问与答 随机迷宫地图 延时响应 图形任务 组件 预期管理 同步 对象列表 配置 条件指令 周边 运维通知 附录 整体架构
  • 深入浅出制作全自动Mud机器人-引言

    Script脚本 全自动 机器人
    1
    1 帖子
    86 浏览
    jarlyynJ
    全自动Mud机器人是一种很典型的完美程序 完美的功能,解放人力 高度稳定性的代表 全面处理随机和意外 可以说,是对编写代码有兴趣,有热爱的Dream Project。 很多Mud玩家成为“高手”的标志之一。 当然,也会让很多人觉得,这很难,我做不到。 其实,只要开始做,全自动Mud机器人并没有这么难。 本系列的文章,会涉及编写全自动机器人的方方面面,帮助读者写出一个属于自己的,能顺畅运行的全自动机器人。 当然,在开始前,我们先要解决一个问题: 什么是全自动机器人? 故名思议,全自动机器人就是能不需要人照顾的,自动完成某个人物的机器人。 理论上说,用一个定时器,每隔半小时在公开频道发布你要出售的道具的机器人,也算全自动机器人。 但这明显不符合我们的目标。 在此,我给全自动机器人下一个定义,全自动机器人是符合这样的两个条件而代码 在不需要人力维护的情况下,形成一种稳定循环,能控制状态和各种资源正向发展,不至于陷入困境而被动停摆 能够完成复杂任务获得回报 在这个定义下,我们能够明确的描述出我们这个系列研究的课题: 怎么才能让循环更稳定,控制更多的资源和状态? 怎么才能完成更复杂的任务,获得更好的回报?
  • 自制Mud客户端开发-基础篇

    Mud客户端倒腾 mud客户端
    1
    1 帖子
    369 浏览
    jarlyynJ
    大部分Mud玩家,在有能力制作一个可用的机器人后,都会起自己制作客户端的念头。让自己的机器人在自己的客户端里刷刷的跑,能给一个Mud爱好者带来极大的满足感。 在科技发展到现在,各种语言和框架层出不穷,电脑的硬件也极度丰富,外加AI技术的发展,写一个Mud客户端对普通爱好者已经不是一个遥不可及的事情了。 这系列文章,就是集合我在制作Hellclient时的一些心得体会,希望能给其他同好带来帮助。 而本文,个人希望,类似于武侠小说中的武功总纲,体现出大体框架,然后通过一篇篇细节文章,把整个自建客户端的关键点串起来。 首先,我们要明确,一个Mud客户端要解决几个问题。 我归纳下来,主要是以下几个方面 与Mud服务器的信息交互(telnet/ansi) 用户的快速工具(触发,别名,计时器) 合适的机器配置格式(比如变量) 脚本引擎 用户界面 扩展性 Telnet/ANSI库 telnet可以说是一个老旧的成熟的协议了。大部分的语言都有telnet库,没有telnet库的小众语言利用AI照着现成语言的库抄一个也不算难事。 对于Mud来说,主要是处理一些指令,和subnegotiation协议拓展出来的指令就行。 而ANSI的话,也是定义了另一套显示控制的指令。 从我个人经验来看,telnet和ansi的原始数据在客户端能应该需要一个中间模型(model),能快速的供界面渲染和引擎调用。有些客户端直接渲染,再从渲染完的结果倒过来取数据。个人觉得这样不好,会极大的增加脚本制作的复杂度。 触发/别名/计时器 这三个东西代表了一起机器主要要处理的问题: 服务器返回的响应 用户的输入 时间的流逝 对这个核心,我的思路也有过很多变化。就目前的思路来说: 客户端的触发/别名/计时器,应该从好用,易用的角度出发,让用户能很方便的添加临时功能。 脚本引擎应该抽象出 服务器交互/用户输入/时间层出来,不受客户端限制,不利用客户端的特殊功能。 当然,这只是建议,和一个状态下的想法。整体还是要根据你做客户端的目的来开发。 机器配置 在很多客户端里,变量是和触发/别名/计时器评级的概念。 从我的理解来说,并不是这样。 变量(机器配置)应该是和脚本直接绑定的。 按现代开发的概念,变量至少是 一个Config文件,决定了机器的启动设置。 如果是变量名对应变量值的形式,它更接近于环境变量 这个概念。 同时,个人建议,也是我Hellclient的做法,变量要有备注或注释的空间,可以让用户理解变量怎么使用。 脚本引擎 在真实世界中,嵌入式脚本引擎其实只有两个玩家,javascript和lua。 Lua是老牌玩家,各个语言都有lua的实现或者Binding,语法简单速度快。有可能的话建议使用luajit库,性能还是很不错的。 Javascript又是另一个话题。Javascript是目前使用最广泛的脚本语言,npm上有大量的js库。但嵌入js库不是很容易的一件事情。本质来说,js生态就是v8生态。但不是每个语言都能很容易的嵌入v8引擎。hellclient嵌入v8也是将一个半废弃状态的库进行了一定的魔改。还遇到了Windows下的msys和vc编译器兼容的问题。 所以,个人意见,能方便的用v8的(比如C#有微软官方ClearScript支持,建议一定要支持javascript,lua的话,建议都可以支持一下。 用户界面 就目前的现状来说,除非你使用特殊的技术栈或者技术,不然大部分情况下的软件开发都是跨平台,Windows/Linux/Mac全支持的。 具体到用户界面的形式,其实是3种 浏览器 浏览器形式的(主要是electron,少数如tarui会使用系统自带浏览器) 最大的优势是界面多样话,自定义方便,美观迅速。 缺点是非nodejs方案会多引入与浏览器的交互,性能有上限。而nodejs本身的单线程模型要改造的话工程量很大,强如微软也在vscode里堆了一堆C代码。 客户端 客户端的方案的话,其实主要是qt和skia 两个图形库的方案。qt基本绑定c++,不是那么容易上手(pyqt选手另论)。skia的话,有flutter/dart和avalonia/c# 两员大将,都是不错的方案。 客户端型界面最大的优势是性能有保证。缺点是实现美观/华丽的界面麻烦 无头模式 无头模式就是指提供接口,供网页/其他客户端显示。 理论上说,无头模式一般都会实现一个网页界面。 但他和网页界面的区别是,无头模式由于要支持多种终端,无法充分利用网页界面的华丽/可制定性强,在界面上有很大的妥协。 无头模式的特点是: 1.轻量级,可扩展性高,适合云端运行,通过网页/客户端/app等多种形式连接。 2.界面受限,需要维护多个程序。 Hellclient就是无头模式的客户端。 扩展性 扩展性其实主要就是3个方案 动态链接库(so/dll) 通过lua加载动态链接库可以很容易的对lua进行扩展。性能和功能都是最强大的。但同样的对开发人员的要求也是最高的,安全性上也容易出问题。 Com技术 如果你把自己局限在Windows平台,使用C++/C#开发,activex/com技术是极为强大的。mush本身除了lua的语言也都是通过activex来实现的。但com技术已经是一个事实上逐渐被非主流化的技术,不是很建议绑定的Com技术上 http/websocket技术 通过基于http的api 接口或者websocket实时调用。这个方案在性能上会有所损失,但同时有能够云化集群化的优点。Hellclient就是基于这种方案来提供扩展性。 我的建议 Hellclient是使用go写,采用无头模式,通过http/websocket进行扩展的。 如果让我从头来过,我可能会换成c#,因为go在windows平台下嵌入v8有点蛋疼。 我推荐的语言选型为 c# 综合的王者 flutter/dart 桌面/app全能 electorn/nodejs electorn也是目前桌面端很主流的选择 python web/pyqt方案 这个只适合自用,发布打包都太麻烦了
  • Hellclient UI介绍

    已固定 HellclientUI应用
    1
    1 帖子
    68 浏览
    jarlyynJ
    Hellclient UI是一款由Dart/Flutter 开发的,支持Android/iOS/Windows/Linux/Mac等各平台的Hellclient客户端专用控制软件。 项目地址:https://github.com/hellclient-scripts
  • Hellclient软件介绍

    已固定 Hellclient软件
    1
    1 帖子
    92 浏览
    jarlyynJ
    Hellclient是一款使用go语言开发为运行在vps上设计,支持javascript/lua脚本,通过websocket协议,通过web界面或者Hellclient UI软件访问的容器向Mud客户端。 项目地址为 https://github.com/jarlyyn/hellclient
  • 欢迎访问Hellclient社区

    已固定 已锁定 意见与反馈
    1
    1 帖子
    258 浏览
    jarlyynJ
    欢迎您访问Hellclient社区。 这里是Hellclient客户端及相关mud软件和脚本的沟通社区。欢迎在这里对Hellclient软件,和Mud相关的软件开发和脚本制作进行沟通。 在这里,我们会以下讨论的规范要求每一个用户 仅讨论软件的使用问题和Mud的技术问题。 不要对别的用户,Mud,和机器人进行评论。Mud本身已经是一个粉丝向的小圈子了,大家都是非专业的玩家,请互相保持友好。 由于 Mud 圈本身的互相借鉴和借用的现象很严重,所以本社区不支持任何资料的上传。有代码或程序分享的需要,请上传至github/coding/gitee等平台。 请不要分享破解或者攻击软件/服务器/游戏的经验。 尊重您玩的Mud的规矩,不要讨论和分享针对某个Mud使用,又不被Mud Owner允许的功能。 不建议使用您在游戏里的常用ID,把您在游戏的恩怨情仇蔓延于此。 热爱国家,遵纪守法,不违背公序良俗。 说的有点多,希望我们的社区能能给每一个访问者带来快乐和收获。