跳转至内容

世界

本论坛之外的主题。此处表达的观点和意见可能不代表本论坛及其成员的立场。

海量内容尽在指尖 …

不妨将此视为您专属的全球发现信息流。它汇集了来自互联网各处及其他社区的有趣讨论,一应俱全。

虽然您可以浏览当前的热门内容,但使用该信息流的最佳方式是将其个性化。通过注册账号,您可以关注特定的创作者和主题,从而过滤掉无关信息,只查看对您真正重要的内容。

准备好开始了吗?注册一个账号,即可关注他人、在收到回复时获得通知,并收藏您喜欢的内容。

注册 登录
  • N
    • 可以进行内存占用分析了(不过分析自身很占资源)
      内存占用分析.png

    • #var命令可以处理嵌套命令了!
      #var获取嵌套变量对象.png

    本版本还在测试中,尽请期待 🙂


    是的,不仅可以使用skills[literate]解析嵌套字典,还可以使用skills[2]的写法解析嵌套列表。
  • T

    后边慢慢优化
    2555386c-a39e-456b-bf6e-695debf3c628-image.png

    # 清华西游记状态栏和状态窗口
    import webbrowser, asyncio, ast
    from pymud import Session, IConfig, alias, trigger, timer, gmcp, exception, Trigger, SimpleTrigger, SimpleAlias, GMCPTrigger, Command
    from wcwidth import wcswidth
    from utils.utils import experience_in_M
    class MyConfig(IConfig):
        def __init__(self, session: Session, *args, **kwargs) -> None:
            super().__init__(session, *args, **kwargs)
            # 将自定义的状态窗口函数赋值给会话的status_maker属性,这样会话就会使用该函数来显示状态信息。
            self.session.application.set_status('当前任务:未知') # 未来可以作为当前任务的状态栏
            self.session.status_maker = self.status_window
    
        def __unload__(self):
            super().__unload__()
        # 创建自定义的健康条用作分隔符
        def create_status_bar(self, current, maximum, actual_max, bar_length = 20, bar_char = "—"):
            """
            创建一个健康条,用于显示当前健康值、有效健康值和剩余健康值。
            :param current: 当前健康值
            :param maximum: 当前最大健康值
            :param actual_max: 真实最大值
            :param bar_length: 健康条的长度
            :param bar_char: 健康条的填充字符
            :return: 一个包含健康条的列表
            """
    #   气血: 1153/ 1702 ( 64%)    内力: 10943 / 5659 (100%) (+360)
            bar_line = list()
            char_width = wcswidth(bar_char)
            # 计算有效健康值
            filled_length = int(round(bar_length * current / actual_max / char_width))
            # 计算有效健康值部分的长度
            current_max_length = int(round(bar_length * maximum / actual_max / char_width))
            # 计算剩余部分长度
            remaining_length = bar_length - current_max_length
            # 构造健康条
            bar_line.append(("fg:lightcyan", bar_char * filled_length))
            bar_line.append(("fg:yellow", bar_char * (current_max_length - filled_length)))
            bar_line.append(("fg:red", bar_char * remaining_length))
            return bar_line
        # 自定义状态栏窗口。该函数会被渲染框架高频调用,请注意不要在该函数中 info 或者执行其他输出信息!!!
        def status_window(self):
            styles = {
                "title"         : "bold",
                "value"         : "lightgreen",
                "value.better"  : "lightcyan",
                "value.worse"   : "yellow",
                "value.worst"   : "red"
            }
            
            try:
                formatted_list = list()
                # line 0. hp bar
                kee             = int(self.session.getVariable("kee", 0))
                max_kee         = int(self.session.getVariable("max_kee", 1))
                kee_percent     = int(self.session.getVariable("kee_percent", 1))  
                actual_max_kee  = int(round(max_kee / (kee_percent / 100)))  
    
                sen             = int(self.session.getVariable("sen", 0))
                max_sen         = int(self.session.getVariable("max_sen", 1))
                sen_percent     = int(self.session.getVariable("sen_percent", 1))
                actual_max_sen  = int(round(max_sen / (sen_percent / 100)))            
    
                bar_char = "━"
                screenwidth = self.session.application.get_width()
                bar_length = screenwidth // 2 - 1
                span = screenwidth - 2 * bar_length
                kee_bar   = self.create_status_bar(kee, max_kee, actual_max_kee, bar_length, bar_char)
                sen_bar = self.create_status_bar(sen, max_sen, actual_max_sen, bar_length, bar_char)
    
                formatted_list.extend(kee_bar)
                formatted_list.append(("", " " * span))
                formatted_list.extend(sen_bar)
                formatted_list.append(("", "\n"))
    
                # line 1. char, family, master, deposit, food, water, exp, pot
                formatted_list.append((styles["title"], "【角色】"))
                formatted_list.append((styles["value"], "{0}({1})".format(self.session.getVariable('name'), self.session.getVariable('id'))))
                formatted_list.append(("", " "))
    
                formatted_list.append((styles["title"], "【门派】"))
                formatted_list.append((styles["value"], "{}".format(self.session.getVariable('family'))))
                formatted_list.append(("", " "))
    
                formatted_list.append((styles["title"], "【师父】"))
                formatted_list.append((styles["value"], "{}".format(self.session.getVariable('master'))))
                formatted_list.append(("", " "))
    
                formatted_list.append((styles["title"], "【武学】"))
                formatted_list.append((styles["value"], "{}".format(self.session.getVariable('combat_exp'))))
                formatted_list.append((styles["value"], "({})".format(experience_in_M(self.session.getVariable('combat_exp')))))
                formatted_list.append(("", " "))
                
                formatted_list.append((styles["title"], "【道行】"))
                formatted_list.append((styles["value"], "{}".format(self.session.getVariable('daoxing_exp'))))
                formatted_list.append((styles["value"], "({})".format(experience_in_M(self.session.getVariable('daoxing_exp')))))
                formatted_list.append(("", " "))
    
                formatted_list.append((styles["title"], "【潜能】"))
                formatted_list.append((styles["value"], "{}".format(self.session.getVariable('potential'))))
                
                # line 2. hp
                # a new-line
                formatted_list.append(("", "\n"))
    
                formatted_list.append((styles["title"], "【气血】"))
                if max_kee < actual_max_sen:
                    style = styles["value.worst"]
                elif kee < 0.8 * max_kee:
                    style = styles["value.worse"]
                else:
                    style = styles["value"]            
                if max_kee == 0: 
                    pct1 = pct2 = 0
                else:
                    pct1 = 100.0 * kee / actual_max_kee
                    pct2 = 100.0 * max_kee / actual_max_kee
                formatted_list.append((style, "{0}[{1:3.0f}%] / {2}[{3:3.0f}%]".format(kee, pct1, max_kee, pct2)))
                formatted_list.append(("", " "))
    
                formatted_list.append((styles["title"], "【精神】"))
                if max_sen < actual_max_sen:
                    style = styles["value.worst"]
                elif sen < 0.8 * max_sen:
                    style = styles["value.worse"]
                else:
                    style = styles["value"]
                if max_sen == 0: 
                    pct1 = pct2 = 0
                else:
                    pct1 = 100.0 * sen / actual_max_sen
                    pct2 = 100.0 * max_sen / actual_max_sen
                formatted_list.append((style, "{0}[{1:3.0f}%] / {2}[{3:3.0f}%]".format(sen, pct1, max_sen, pct2)))
                formatted_list.append(("", " "))
    
                formatted_list.append((styles["title"], "【食物】"))            
                food      = int(self.session.getVariable('food', '0'))
                max_food  = int(self.session.getVariable('max_food', 350))
                if food < 100:
                    style = styles["value.worst"]
                elif food < 200:
                    style = styles["value.worse"]
                elif food < max_food:
                    style = styles["value"]
                else:
                    style = styles["value.better"]
                formatted_list.append((style, "{0}/{1}".format(food, max_food)))
                formatted_list.append(("", " "))
    
                formatted_list.append((styles["title"], "【饮水】"))
                water       = int(self.session.getVariable('water', '0'))
                max_water   = int(self.session.getVariable('max_water', 350))
                if water < 100:
                    style = styles["value.worst"]
                elif water < 200:
                    style = styles["value.worse"]
                elif water < max_water:
                    style = styles["value"]
                else:
                    style = styles["value.better"]
                formatted_list.append((style, "{0}/{1}".format(water, max_water)))
                formatted_list.append(("", " "))
    
                formatted_list.append((styles["title"], "【存款】"))
                formatted_list.append((styles["value"], "{}金".format(self.session.getVariable('deposit'))))
                formatted_list.append(("", " "))
                
                # line 3. hp
                # a new-line
                formatted_list.append(("", "\n"))      
    
                force      = int(self.session.getVariable("force", 0))
                max_force   = int(self.session.getVariable("max_force", 1))     
                mana       = int(self.session.getVariable("mana", 0))
                max_mana   = int(self.session.getVariable("max_mana", 1))     
    
                # 内力
                formatted_list.append((styles["title"], "【内力】"))
                if force < 0.6 * max_force:
                    style = styles["value.worst"]
                elif  force < 0.8 * max_force:
                    style = styles["value.worse"]
                elif force < 1.2 * max_force:
                    style = styles["value"]   
                else:
                    style = styles["value.better"]
    
                if max_force == 0: 
                    pct = 0
                else:
                    pct = 100.0 * force / max_force
                formatted_list.append((style, "{0} / {1}[{2:3.0f}%]".format(force, max_force, pct)))
                formatted_list.append(("", " "))
    
                # 精力
                formatted_list.append((styles["title"], "【法力】"))
                if mana < 0.6 * max_mana:
                    style = styles["value.worst"]
                elif mana < 0.8 * max_mana:
                    style = styles["value.worse"]
                elif mana < 1.2 * max_mana:
                    style = styles["value"]   
                else:
                    style = styles["value.better"]
                
                if max_mana == 0: 
                    pct = 0
                else:
                    pct = 100.0 * mana / max_mana
    
                formatted_list.append((style, "{0} / {1}[{2:3.0f}%]".format(mana, max_mana, pct)))
                formatted_list.append(("", " "))            
                return formatted_list
        
            except Exception as e:
                return f"{e}"
    

  • T

    请教个问题,如何通过一个主.py文件来加载不同的模块,文件结构大概如下:

    ├── mud_main.py # 主入口
    ├── pymud.cfg # 配置文件,设置default_script": [" mud_main"]
    ├── triggers/
    │ └── common_triggers.py # 通用trigger
    ├──commands/
    │ └── fight.py

    如何写mud_main.py代码来加载其他文件夹下的模块?


    哦,我不上xkx的论坛的~~~ 不过这个帖子好像之前搜索pyMud的时候看过, 但现在又忘记了 这记性真不行了
  • T

    class Trigger(MatchObject):
    def init(self, session, patterns, *args, **kwargs):
    pass

    其中参数
    patterns:
    匹配模式,应传递字符串(正则表达式或原始数据)。多行触发时,传递一个匹配模式的列表。

    之前记得在群里讨论,这样的多行触发无法预估结束边界,所以会出错,现在有好的解决办法么?


    很多时候都是这么实现的,比如抓房间描述不确定多少行时,用房间名做首行触发,用出口描述做结束触发,中间再判断是什么。
  • N

    TELNET RFC文档概述

    RFC #854 TELNET 协议规范

    0. 基本信息

    WEB页面:https://www.rfc-editor.org/rfc/rfc854.html
    发布日期:1983-05
    本RFC为ARPA互联网社区指定了一个标准。ARPA互联网上的主机预计将采用并实施本标准。
    

    1. 引言

    TELNET协议的目的是提供一种相当通用的、双向的、面向八位字节的通信设施。它的主要目标是允许一种将终端设备和面向终端的过程相互连接的标准方法。可以设想,该协议也可以用于终端通信(“链接”)和过程过程通信(分布式计算)。
    

    2. 一般考虑

    TELNET连接是一种传输控制协议(TCP)连接,用于传输带有穿插TELNET控制信息的数据。
    TELNET协议建立在三个主要思想之上:第一,“网络虚拟终端”(Network Virtual Terminal, NVT)的概念;第二,协商选择权原则;第三,终端和过程的对称视图。
    
    1. 当首次建立TELNET连接时,假设每一端都在“网络虚拟终端”(NVT)发起和终止。NVT是一种假想的设备,它提供规范终端的标准、网络范围的中间表示。这就不需要“服务器”和“用户”主机来保存有关彼此终端特性和终端处理约定的信息。所有主机,包括用户和服务器,都映射其本地设备特性和约定,以便看起来是在处理网络上的NVT,并且每个主机都可以由另一方进行类似的映射。NVT旨在在过度限制(没有为主机提供足够丰富的词汇表来映射到其本地字符集)和过度包容(惩罚使用普通终端的用户)之间取得平衡。
      注:“用户”主机是物理终端通常连接到的主机,“服务器”主机是通常提供某些服务的主机。作为另一种观点,即使在终端到终端或过程到过程通信中也适用,“用户”主机是发起通信的主机。
    2. 协商选项的原则考虑到这样一个事实,即许多主机将希望提供超过NVT内可用服务的额外服务,并且许多用户将拥有复杂的终端,并且希望拥有优雅的服务,而不是最低限度的服务。独立于TELNET协议,但在TELNET协议中结构化的是各种“选项”,这些选项将被批准,并可与“DO,DON’T,WILL,WON’T”结构(如下所述)一起使用,以允许用户和服务器同意为其TELNET连接使用一组更详细(或可能只是不同)的约定。这些选项可能包括更改字符集、回声模式等。
      设置选项使用的基本策略是让任何一方(或双方)发起请求,要求某些选项生效。然后,另一方可以接受或拒绝该请求。如果请求被接受,该选项将立即生效;如果被拒绝,则连接的相关方面保持为针对NVT所指定的。显然,一方可能总是拒绝启用某个选项的请求,并且决不能拒绝禁用某个选项,因为所有各方都必须准备好支持NVT。
      选项协商的语法已经设置好,如果双方同时请求一个选项,双方都会将对方的请求视为对自己的肯定确认。
    3. 协商语法的对称性可能会导致非终止确认循环——各方将传入的命令视为必须确认的新请求,而不是确认。为防止此类循环,以以下规则为准:
    • a. 缔约方只能要求更改选项状态;也就是说,一方不能仅仅为了宣布其处于何种模式而发出“请求”。
    • b. 如果一方收到似乎是进入其已经处于的某种模式的请求,则不应确认该请求。这种不回应对于防止谈判中无休止的循环至关重要。即使模式没有更改,也需要对更改模式的请求发送响应。
    • c. 当一方向第二方发送选项命令时,无论是作为请求还是确认,并且该选项的使用将对从第一方发送到第二方的数据的处理产生任何影响,则该命令必须在希望其生效的点插入数据流中。(需要注意的是,从发送请求到收到确认之间会经过一段时间,这可能是否定的。因此,主机可能希望在请求选项后缓冲数据,直到它知道请求是被接受还是被拒绝,以向用户隐藏“不确定期”。)
      当TELNET连接首次建立时,选项请求可能会来回奔波,因为各方都试图从另一方获得尽可能好的服务。然而,除此之外,还可以使用选项来动态修改连接的特性,以适应不断变化的本地条件。例如,正如稍后将要解释的那样,NVT使用的传输规程非常适合许多“一次一行”的应用程序,如BASIC,但不太适合许多“每次一个字符”的应用,如NLS。当“一次一个字符”规则适合本地进程并协商合适的选项时,服务器可能会选择投入该规则所需的额外处理器开销。然而,当不再需要详细控制时,它可以切换(即协商)回NVT,而不是永久地承受额外的处理开销。
      如果进程仅通过重新请求选项来响应拒绝,则进程发起的请求可能会激发非终止请求循环。为了防止这种循环的发生,被拒绝的请求不应该重复,直到发生变化。在操作上,这可能意味着进程正在运行不同的程序,或者用户已经给出了另一个命令,或者在给定进程和给定选项的上下文中有意义的任何东西。一个很好的经验法则是,重新请求只应作为来自连接另一端的后续信息的结果或当本地人工干预要求时发生。
      选项设计者不应感到受到可用于选项谈判的有限语法的约束。简单语法的目的是让选择变得容易——因为相应地,人们很容易声称对它们一无所知。如果某个特定选项需要比“DO,DON’T,WILL,WON’T”更丰富的谈判结构,那么正确的策略是使用“DO,DO’T,WILL,WON”来确定双方都理解该选项,一旦完成,就可以自由使用更奇特的语法。例如,一方可能会发送更改(建立)线路长度的请求。如果它被接受,那么可以使用不同的语法来实际协商线路长度——这样的“子协商”可能包括最小允许、最大允许和所需线路长度的字段。重要的概念是,在之前的一些(标准)谈判已经确定双方都能够解析扩展的语法之前,这种扩展的谈判永远不应该开始。
      总之,任何一方发送WILL XXX,以表明该方希望(要约)开始执行选项XXX,DO XXX和DOT XXX是其肯定和否定的确认;类似地,发送DO XXX以指示希望(请求)另一方(即DO的接收方)开始执行选项XXX,WILL XXX和WON’T XXX作为肯定和否定的确认。由于NVT是在没有启用选项的情况下剩下的,因此“DON'T”和“WON'T”响应保证使连接处于两端都可以处理的状态。因此,所有主机都可以在完全不知道不支持的选项的情况下实现其TELNET进程,只需对无法理解的任何选项请求返回拒绝(即拒绝)即可。
      TELNET协议已经尽可能地实现了服务器-用户对称,因此它可以轻松自然地涵盖用户-用户(链接)和服务器-服务器(协作进程)的情况。人们希望,但并非绝对需要,各种选择将进一步推动这一意图。无论如何,人们明确承认对称是一种操作原理,而不是一条铁律。
      应查阅配套文件“TELNET选项规范”,以获取有关建立新选项的程序的信息。

    3. 网络虚拟终端(Network Virtual Terminal, NVT)

    网络虚拟终端(NVT)是一种双向字符设备。NVT有一台打印机和一个键盘。打印机响应输入数据,键盘产生输出数据,通过TELNET连接发送,如果需要“回声”,也发送到NVT的打印机。“回声”将不会穿过网络(尽管存在启用“远程”回声操作模式的选项,但不需要主机来实现此选项)。代码集是八位字段中的七位USASCII,此处修改的除外。任何代码转换和时序考虑都是局部问题,不会影响NVT。
    

    3.1 传输数据

    尽管通过网络的TELNET连接本质上是全双工的,但NVT应被视为在线路缓冲模式下运行的半双工设备。也就是说,除非协商了相反的选项,否则以下默认条件适用于通过TELNET连接传输数据:
    
    1. 只要本地缓冲区空间的可用性允许,数据应该在生成数据的主机中累积,直到完整的数据线准备好传输,或者直到出现一些本地定义的要传输的显式信号。该信号可以由过程产生,也可以由人类用户产生。
      这条规则的动机是,对一些主机来说,处理网络输入中断的成本很高,再加上默认的NVT规范,即“回声”不会穿过网络。因此,在其源处缓冲一定数量的数据是合理的。许多系统在每条输入线的末端进行一些处理操作(即使是行式打印机或打卡器也经常以这种方式工作),因此传输应该在线的末端触发。另一方面,用户或过程有时可能会发现有必要或希望提供不终止于线路末端的数据;因此,提醒实施者提供本地发信号通知所有缓冲数据应立即传输的方法。

    2. 当进程已完成向NVT打印机发送数据,并且没有来自NVT键盘的排队输入以供进一步处理时(即,当TELNET连接一端的进程在没有另一端输入的情况下无法继续进行时),该进程必须发送TELNET Go Ahead(GA)命令。
      此规则并不要求从每行末尾的终端发送TELNET GA命令,因为服务器主机通常不需要特殊信号(除了行末尾或其他本地定义的字符)来开始处理。相反,TELNET GA旨在帮助用户的本地主机操作物理上的半双工终端,该终端具有“可锁定”键盘,如IBM 2741。对这种类型的终端的描述可能有助于解释GA命令的正确使用。
      终端计算机连接始终处于用户或计算机的控制之下。双方都不能单方面从对方手中夺取控制权;相反,控制端必须明确地重新隐藏其控制。在终端,硬件的构造是为了在每次“线路”终止时(即,当用户键入“新行”键时)放弃控制。当这种情况发生时,连接的(本地)计算机处理输入数据,决定是否应该生成输出,如果不应该,则将控制权返回给终端。如果应该生成输出,则计算机将保留控制权,直到所有输出都已传输。
      通过网络使用这种类型的终端的困难应该是显而易见的。“本地”计算机在看到线路末端信号后不再能够决定是否保留控制;这个决定只能由处理数据的“远程”计算机做出。因此,TELNET GA命令提供了一种机制,“远程”(服务器)计算机可以向“本地”(用户)计算机发出信号,表示是时候将控制权交给终端用户了。它应该在这些时间发送,并且只有在用户应该被赋予终端控制权时才发送。请注意,GA命令的过早传输可能会导致输出阻塞,因为用户可能会认为传输系统已经暂停,因此他将无法手动扭转线路。

      当然,上述内容不适用于用户到服务器的通信方向。在这个方向上,GA可以在任何时候发送,但不需要发送。此外,如果TELNET连接用于进程到进程的通信,则无需向任何方向发送GA。最后,对于终端到终端的通信,可能在任一方向、一个方向或两个方向上都不需要GA。如果主机计划支持终端到终端的通信,则建议主机向用户提供手动发信号通知通过TELNET连接发送GA的时间到了的方式;然而,这并不是TELNET流程实现者的要求。
      请注意,TELNET模型的对称性要求TELNET连接的每一端都有一个NVT,至少在概念上是这样。

    3.2 控制功能的标准表示

    如本文件导言所述,TELNET协议的主要目标是通过网络提供终端设备和面向终端的进程的标准接口。这种类型的互连的早期经验表明,某些功能是由大多数服务器实现的,但调用这些功能的方法差异很大。对于与多个服务器系统交互的人类用户来说,这些差异非常令人沮丧。因此,TELNET为其中五个函数定义了一个标准表示,如下所述。这些标准表示具有标准但非必需的含义(使用TELNET的其他协议可能需要中断处理(IP)功能除外);也就是说,不向本地用户提供功能的系统不需要向网络用户提供功能,并且可以将功能的标准表示视为“否”操作。另一方面,向本地用户提供功能的系统必须向发送该功能的标准表示的网络用户提供相同的功能。
    
    3.2.1 中断过程 Interrupt Process (IP)
    许多系统提供暂停、中断、中止或终止用户进程操作的功能。当用户认为自己的进程处于无休止的循环中,或者无意中激活了不需要的进程时,经常使用此功能。IP是调用此函数的标准表示形式。实施人员应注意,使用TELNET的其他协议可能需要IP,因此,如果要支持这些其他协议,则应实施IP。
    
    3.2.2 中止输出 Abort Output (AO)
    许多系统提供了一种功能,该功能允许正在生成输出的进程运行到完成(或达到与运行到完成时相同的停止点),但不将输出发送到用户的终端。此外,该功能通常清除已经产生但尚未实际打印(或显示)在用户终端上的任何输出。AO是调用此函数的标准表示。例如,一些子系统通常可以接受用户的命令,向用户的终端发送一个长文本字符串作为响应,最后通过向用户的端子发送一个“提示”字符(前面有<CR><LF>)来表示准备接受下一个命令。如果在传输文本字符串的过程中接收到AO,则合理的实现方式是抑制文本字符串的剩余部分,但传输提示字符和前面的<CR><LF>。(这可能与接收到IP时可能采取的操作不同;IP可能会导致文本字符串被抑制并退出子系统。)
    应该注意的是,通过提供该功能的服务器系统,可能存在系统外部的缓冲区(在网络和用户的本地主机中),这些缓冲区应该被清除;实现这一点的适当方法是向用户系统发送“Synch”信号(如下所述)。
    
    3.3.3 你在吗 Are You There (AYT)
    许多系统提供一种功能,该功能向用户提供一些可见(例如,可打印)的证据,表明系统仍在运行。当系统出乎意料地长时间“静默”时,用户可能会调用此函数,因为计算的长度出乎意料(用户),系统负载异常重等。AYT是调用此函数的标准表示。
    
    3.3.4 删除字符 Erase Character (EC)
    许多系统提供了一种功能,可以从用户提供的数据流中删除最后一个在前未删除的字符或“打印位置”*。此功能通常用于在出现键入错误时编辑键盘输入。EC是调用此函数的标准表示。
    注意:“打印位置”可能包含几个字符,这些字符是重击的结果,或是<char1>BS<char2>等序列的结果。。。
    
    3.3.5 删除行 Erase Line (EL)
    许多系统提供删除当前输入“行”中所有数据的功能。此功能通常用于编辑键盘输入。EL是调用此函数的标准表示。
    

  • N

    因为PyMUD开发需要经常在多个Python版本下测试是否有效,以前使用传统的pyenv来创建虚拟环境解决问题,后来发现操作起来实在太过于繁琐,因此找到了一款新的基于命令行的包管理工具, uv,完整替代了我所有的需求,包括不同Python版本的管理、虚拟环境、依赖管理等等,而且 uv 是通过 rust 开发,支持并发操作,碰到新环境搭建需要 pip 拉取一堆依赖项时,速度极快,已经是我正常开发和运行过程中不可缺少的工具了,现在推荐给大家。

    相关链接
    官宣的我觉得很有用的特点
    • 🚀 用一个工具替代 pip,pip-tools,pipx,poetry,pyenv,twine,virtualenv 等等
    • ⚡️ 比标准pip快10~100倍。
    • 🐍 可用于安装和管理 Python 版本。
    • 🛠️ 管理pypi发布的 Python 包。
    • ⏬ 可以通过curl或者pip安装,而无需安装 Rust 或 Python。
    • 🖥️ 跨平台,支持 macOS、Linux 和 Windows。

    详细信息可参见官方文档 😊


  • N

    不知道为什么,在某些时候,PyMUD启动时偶尔会出现无法识别类型的情况,报错为类似 "Trigger对象不包含session属性" 这种,似乎是不同地方import的Session类型不一致,比如pymud.Session和pymud.session.Session不能被识别为一个类型,因此导致 isinstance(session, Session)判断失败,再进一步就是所有脚本加载均会被认为是从脚本。最终处理都是通过重新处理venv环境、重新指定python版本来解决的,更进一步的原因还没有查到。
    不知道各位用户有没有碰到过类似的问题?


  • N

    PyMUD 0.22.2a2预览版已推送到 pypi.org, 感兴趣的可以尝鲜。相对于 0.22.2a1 版和 0.22.1 版主要修改包括:

    • 功能新增: 可以通过.cfg文件指定 "auto_chars" 来定义启动pymud时自动打开的会话。(0.22.2a1新增)
    • 问题修复: 修复GMCPTrigger类型enabled属性不生效问题。 (0.22.2a1新增)
    • 问题修复: 修复添加/删除系统时钟回调时,可能会导致的异常问题。(0.22.2a2新增)
    • 问题修复: 修复在增加系统时钟回调时,如果回调函数代码错误产生异常,会导致系统时钟停顿的问题。(0.22.2a2新增)

    auto_chars 示例

    • 以下 pymud.cfg 代码可以让运行pymud时自动打开会话并登录 mychar2 和 mychar4 :
    {
        "sessions": {
            "pkuxkx": {
                "host": "mud.pkuxkx.net",
                "port": "8081",
                "encoding": "utf8",
                "autologin": "{0};{1}",
                "default_script": ["main"],
                "chars": {
                    "mychar1": ["mycharid1", "mypassword1"],
                    "mychar2": ["mycharid2", "mypassword2"],
                    "mychar3": ["mycharid3", "mypassword3"],
                    "mychar4": ["mycharid4", "mypassword4"],
                }
            }
        },
        "auto_chars": ["mychar2", "mychar4"]
    }
    

    0.22.2a2版修复的错误概述

    本版修复的两个错误主要解决 websocket 插件会调用系统时钟回调来处理sendOverview,但添加/移除回调或者回调写错都会导致系统时钟停止的问题。系统时钟停止后,会导致sendOverview不会再被调用刷新,且控制台ui的连接时间也不会保持每秒自动刷新,而只是随数据变化刷新。


  • 就知道一个aardwolf


    还有这个网站也是: https://mudstats.com/
  • N

    使用NodeBB搭建的PyMUD论坛正式上线啦!


    @bigb 在 PyMUD论坛上线啦 中说: 邮件确认也可以了,但是等了一天邮件才到。 邮件确认问题是因为我在前天晚上不小心改错了配置,昨天上午修复后重新发的,现在应该没问题。
  • N

    PyMUD的运行好伙伴 - tmux终端复用器

    PyMUD是一个运行于终端的MUD客户端,当运行PyMUD同时还要再执行其他终端命令时,就可以用到tmux终端复用器了,它可以让pymud在无UI的系统中后台运行,可以分屏同时处理游戏以及脚本代码。以下就是tmux的主要使用方法介绍。

    介绍

    tmux 是一个终端复用器,允许用户在一个终端窗口中管理多个独立的终端会话。这使得多任务处理变得更加高效,并且即使断开 SSH 连接,后台任务也会继续运行。

    安装

    在大多数 Linux 发行版中可以通过包管理器安装 tmux

    • Debian/Ubuntu: sudo apt-get install tmux
    • Fedora: sudo dnf install tmux
    • macOS (通过 Homebrew): brew install tmux

    基本命令

    创建会话

    tmux new -s mysession
    

    这里的 -s 参数用于指定会话名称。

    列出所有会话

    tmux ls
    

    关闭会话

    tmux kill-session -t mysession
    

    这里的 -t 参数用于指定要关闭的会话名称。

    附加到会话

    tmux attach -t mysession
    

    分离会话

    当您想要暂时离开某个会话而不终止它时,可以使用快捷键 Ctrl+b 然后按 d 来分离会话。

    快捷键

    以下是一些常用的 tmux 快捷键组合:

    常规操作

    • 分离当前会话: Ctrl+b 然后按 d
    • 列出并选择会话: Ctrl+b 然后按 s
    • 重命名当前会话: Ctrl+b 然后按 $

    窗口管理

    • 新建窗口: Ctrl+b 然后按 c
    • 切换下一个窗口: Ctrl+b 然后按 n
    • 切换上一个窗口: Ctrl+b 然后按 p
    • 关闭当前窗口: Ctrl+d 或者 Ctrl+b 然后按 &

    分屏

    • 水平分割: Ctrl+b 然后按 %
    • 垂直分割: Ctrl+b 然后按 "
    • 切换屏幕方向: Ctrl+b 然后按 Space
    • 关闭当前面板: Ctrl+b 然后按 x

    跨屏复制

    1. 进入复制模式: Ctrl+b 然后按 [
    2. 使用方向键移动光标选择文本。
    3. Enter 复制选定的文本。
    4. 在目标位置粘贴: Ctrl+b 然后按 ]

    示例

    假设我们要创建一个新的名为 projectA 的会话,并在这个会话中打开两个不同的窗口来分别编写代码和查看日志。

    1. 创建并进入新会话:
      tmux new -s pymud
      
    2. 新建第二个窗口:
      Ctrl+b c
      
    3. 查看所有会话列表:
      tmux ls
      
    4. 分割当前窗口以同时显示代码编辑器和终端输出:
      • 水平分割: Ctrl+b %
      • 垂直分割: Ctrl+b "
    5. 在不同窗口间切换:
      • 下一个窗口: Ctrl+b n
      • 上一个窗口: Ctrl+b p
    6. 当完成工作后,退出当前会话但保持其运行状态:
      Ctrl+b d
      
    7. 再次附加到该会话:
      tmux attach -t pymud
      
    8. 最终,如果不再需要此会话,则可将其关闭:
      tmux kill-session -t pymud
      

    以上就是 tmux 的一些基本使用方法和常用快捷键,熟练掌握这些功能可以使你使用PyMUD的效率大大提高。


    windows都有窗口,不需要使用tmux也没关系
  • N

    由于中国大陆访问回程不稳定问题,已将运行服务器升级为中国大陆访问更优的套餐,本站点已完成迁移,欢迎大家继续支持本论坛。

    2026年1月1日


  • B

    后天就是2026年了,谢谢群主你没有踢我。
    我一个农村来的,在这个群里根本没有存在感,也没有人会在意我,也不会说漂亮话,也没有像他们那样骚,口袋也没有什么钱。长得也不高,也不懂很多梗,也不会巴结群管理。
    能看到你们聊天,我就感到很满足了。
    2026年,我要继续看你们聊天


    明天就是2026了