跳转至内容
  • 欢迎
  • 版块
  • 最新
  • 标签
  • 热门
  • 用户
  • 群组
皮肤
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • 默认(不使用皮肤)
  • 不使用皮肤
折叠
品牌标识

Hellclient 社区

administrators

私有

帖子


  • 深入浅出制作全自动Mud机器人-数据与缓存
    jarlyynJ jarlyyn

    虽然很多客户端和教程介绍机器时,都是用触发来驱动机器。

    但是,要做一个稳定,功能强大的机器,被动的靠出发来驱动明显不可行,必须要依靠全面详尽的数据来运行。

    在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()
        }
    

    这样就能确保每次在需要数据进行决策(准备)时,使用的是最新的有效的数据。

    Script脚本 全自动 机器人 范例代码

  • 深入浅出制作全自动Mud机器人-全局单例
    jarlyynJ jarlyyn

    单例模式,是设计模式的一种。它是一种创建型设计模式,它的核心目标是确保一个类只有一个实例,并提供一个全局访问点来获取这个唯一的实例。

    听着很牛吧?别管这个,这是被强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下

    这样在写代码时,可以一目了然,方便理解和组织。

    总结

    对于代码来说,全局变量(单例)是必不可少的,但是太多的单例会让代码复杂度指数上升。通过定义一个合适的根单例,我们能更好的组织代码,提可高维护性。

    Script脚本 全自动 机器人 设计模式

  • 深入浅出制作全自动Mud机器人-组件化
    jarlyynJ jarlyyn

    在全自动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脚本 全自动 机器人 架构

  • 我使用的一种机器人代码分层组织方式
    jarlyynJ jarlyyn

    在写gui程序时,我整理了一套代码组织方式

    参看

    HellMapManager项目就是按这个架构来组织的。

    对于Mud机器人,我觉得这个组织形式也适用。当然,由于主要业务不同,住址形式也会不同。我只是将所有的代码根据对应的层次打个标签,避免不同层级的代码过于混淆。

    • UI 交互层 这个完全绑定于客户端的实现,在Mud机器人里也相对不重要,可以和Service层混在一起
    • Service 服务器层。从字面意义上来说,就是为最终用户提供的功能的封装层。UI交互的内容最终绑定到服务层上。最大的用途是防止用户UI直接操作到业务层,做封装和拦截,以及抽象。
    • Core 核心(业务) 层。对于Mud机器人来说,绝大部分的代码都是核心层。所以实际写机器人的时候Core肯定还要做细分,比如底层核心和任务模块。
    • Helper 辅助类,业务层和数据层直接的纽带,将数据的细节对业务做一定的封闭。
    • Adapters 适配器层 抽象底层交互。对于Mud机器人来说,就是将客户端的触发/别名/计时器做一个抽象,以及对应的事件Event框架。引入这个层的话能提升机器人可迁移性和做测试的可能。
    • Model 模型 在各个业务层中共同的数据结构。在Mud中比如房间,玩家,道具等等。
    • utils 工具层。比如中文转数字,格式化文字等纯与业务无关的,全局都可能使用的代码
    Script脚本 机器人 架构

  • 深入浅出制作全自动Mud机器人-运维通知
    jarlyynJ jarlyyn

    在引言中,我们提到过,制作全自动机器人的一个重要课题是

    • 怎么才能让循环更稳定,控制更多的资源和状态?

    那么,提升稳定性的一个重要的工作,就是当服务(机器人)发生意外时,能及时的通知运维(开发者/用户)查看问题,进行操作。

    就是当机器停顿,无法继续,甚至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的话,也可以考虑使用企业微信。企业微信可以绑定到微信内,我个人有其他业务是使用企业微信通知的。

    Script脚本 全自动 机器人

  • 深入浅出制作全自动Mud机器人-目录
    jarlyynJ jarlyyn
    • 引言
    • 全局单例
    • 数据与缓存
    • 组件化
    • 运维通知
    Script脚本 全自动 机器人

  • 深入浅出制作全自动Mud机器人-引言
    jarlyynJ jarlyyn

    全自动Mud机器人是一种很典型的完美程序

    • 完美的功能,解放人力
    • 高度稳定性的代表
    • 全面处理随机和意外

    可以说,是对编写代码有兴趣,有热爱的Dream Project。

    很多Mud玩家成为“高手”的标志之一。

    当然,也会让很多人觉得,这很难,我做不到。

    其实,只要开始做,全自动Mud机器人并没有这么难。

    本系列的文章,会涉及编写全自动机器人的方方面面,帮助读者写出一个属于自己的,能顺畅运行的全自动机器人。

    当然,在开始前,我们先要解决一个问题:

    什么是全自动机器人?

    故名思议,全自动机器人就是能不需要人照顾的,自动完成某个人物的机器人。

    理论上说,用一个定时器,每隔半小时在公开频道发布你要出售的道具的机器人,也算全自动机器人。

    但这明显不符合我们的目标。

    在此,我给全自动机器人下一个定义,全自动机器人是符合这样的两个条件而代码

    • 在不需要人力维护的情况下,形成一种稳定循环,能控制状态和各种资源正向发展,不至于陷入困境而被动停摆
    • 能够完成复杂任务获得回报

    在这个定义下,我们能够明确的描述出我们这个系列研究的课题:

    怎么才能让循环更稳定,控制更多的资源和状态?

    怎么才能完成更复杂的任务,获得更好的回报?

    Script脚本 全自动 机器人

  • 自制Mud客户端开发-基础篇
    jarlyynJ jarlyyn

    大部分Mud玩家,在有能力制作一个可用的机器人后,都会起自己制作客户端的念头。让自己的机器人在自己的客户端里刷刷的跑,能给一个Mud爱好者带来极大的满足感。

    在科技发展到现在,各种语言和框架层出不穷,电脑的硬件也极度丰富,外加AI技术的发展,写一个Mud客户端对普通爱好者已经不是一个遥不可及的事情了。

    这系列文章,就是集合我在制作Hellclient时的一些心得体会,希望能给其他同好带来帮助。

    而本文,个人希望,类似于武侠小说中的武功总纲,体现出大体框架,然后通过一篇篇细节文章,把整个自建客户端的关键点串起来。

    首先,我们要明确,一个Mud客户端要解决几个问题。

    我归纳下来,主要是以下几个方面

    • 与Mud服务器的信息交互(telnet/ansi)
    • 用户的快速工具(触发,别名,计时器)
    • 合适的机器配置格式(比如变量)
    • 脚本引擎
    • 用户界面
    • 扩展性

    Telnet/ANSI库

    telnet可以说是一个老旧的成熟的协议了。大部分的语言都有telnet库,没有telnet库的小众语言利用AI照着现成语言的库抄一个也不算难事。

    对于Mud来说,主要是处理一些指令,和subnegotiation协议拓展出来的指令就行。

    而ANSI的话,也是定义了另一套显示控制的指令。

    从我个人经验来看,telnet和ansi的原始数据在客户端能应该需要一个中间模型(model),能快速的供界面渲染和引擎调用。有些客户端直接渲染,再从渲染完的结果倒过来取数据。个人觉得这样不好,会极大的增加脚本制作的复杂度。

    触发/别名/计时器

    这三个东西代表了一起机器主要要处理的问题:

    • 服务器返回的响应
    • 用户的输入
    • 时间的流逝

    对这个核心,我的思路也有过很多变化。就目前的思路来说:

    1. 客户端的触发/别名/计时器,应该从好用,易用的角度出发,让用户能很方便的添加临时功能。
    2. 脚本引擎应该抽象出 服务器交互/用户输入/时间层出来,不受客户端限制,不利用客户端的特殊功能。

    当然,这只是建议,和一个状态下的想法。整体还是要根据你做客户端的目的来开发。

    机器配置

    在很多客户端里,变量是和触发/别名/计时器评级的概念。

    从我的理解来说,并不是这样。

    变量(机器配置)应该是和脚本直接绑定的。

    按现代开发的概念,变量至少是 一个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个方案

    1. 动态链接库(so/dll)
      通过lua加载动态链接库可以很容易的对lua进行扩展。性能和功能都是最强大的。但同样的对开发人员的要求也是最高的,安全性上也容易出问题。
    2. Com技术
      如果你把自己局限在Windows平台,使用C++/C#开发,activex/com技术是极为强大的。mush本身除了lua的语言也都是通过activex来实现的。但com技术已经是一个事实上逐渐被非主流化的技术,不是很建议绑定的Com技术上
    3. http/websocket技术
      通过基于http的api 接口或者websocket实时调用。这个方案在性能上会有所损失,但同时有能够云化集群化的优点。Hellclient就是基于这种方案来提供扩展性。

    我的建议

    Hellclient是使用go写,采用无头模式,通过http/websocket进行扩展的。

    如果让我从头来过,我可能会换成c#,因为go在windows平台下嵌入v8有点蛋疼。

    我推荐的语言选型为

    1. c# 综合的王者
    2. flutter/dart 桌面/app全能
    3. electorn/nodejs electorn也是目前桌面端很主流的选择
    4. python web/pyqt方案 这个只适合自用,发布打包都太麻烦了
    Mud客户端倒腾 mud客户端

  • Hellclient UI介绍
    jarlyynJ jarlyyn

    Hellclient UI是一款由Dart/Flutter 开发的,支持Android/iOS/Windows/Linux/Mac等各平台的Hellclient客户端专用控制软件。

    项目地址:https://github.com/hellclient-scripts

    HellclientUI应用

  • Hellclient软件介绍
    jarlyynJ jarlyyn

    Hellclient是一款使用go语言开发为运行在vps上设计,支持javascript/lua脚本,通过websocket协议,通过web界面或者Hellclient UI软件访问的容器向Mud客户端。

    项目地址为 https://github.com/jarlyyn/hellclient

    Hellclient软件

成员列表

jarlyynJ jarlyyn
  • 登录

  • 没有帐号? 注册

  • 登录或注册以进行搜索。
由Herbrhythm提供技术支持
  • 第一个帖子
    最后一个帖子
0
  • 欢迎
  • 版块
  • 最新
  • 标签
  • 热门
  • 用户
  • 群组