找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 673|回复: 1

[教程] 极简手机聊天信息系统(纯代码,新手也能用)

[复制链接]
发表于 2026-1-13 01:36:41 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

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

×
本帖最后由 Maz马 于 2026-4-13 21:13 编辑

26/4/13更新
(在此之前使用的注意新的版本,更新了模拟聊天记录的功能,已经看过的消息不会再次请求互动,而是从最后一条继续,
拷贝代码出错的话,看最下方进行适配

原帖出处:
https://nighten.itch.io/yet-another-phone-renpy

但是原帖是一个工程包,相当杂乱,而且布局用的都是绝对数据,很难适配
刚好最近帮人写用到了,我就,来都来了,那就顺手(

1.提取了参数,方便小白使用,我相信东一榔头西一棒槌的布局是大家最头疼的...
2.写了缩略图放大功能
3.自动匹配角色的头像 头像框 对话框,支持群聊,多角色对话
4.模拟聊天记录,再次进入同组信息会在上次阅读的最后一条继续,而之前的不会请求互动

因为我还是改了以及优化了很多关键性的东西,不亚于原创
使用的话还是希望署名一下我@Maz马(不署也行,CC0)
会screen的也可以自行扩展

第一部分 系统(直接复制为一个新的rpy文件)
[RenPy] 纯文本查看 复制代码
image alphaimage = "#0000"                # 用于测试的占位透明图
### 布局变量 (只要按需插入图片、根据你自己项目调整,其他部分完全不用管)################################################################
# 覆写旁白角色,添加发送信息的音效回调函数,不希望旁白有音效直接注释掉
define nvl_narrator = NVLCharacter(who_style='nvl_label',what_style='nvl_thought',window_style='nvl_entry',type='nvl',mode='nvl',clear=False,kind=adv,callback=Phone_NarratorSound)
 
# 用于旁白信息的音频
define audio.phonenarrator = "audio/sound_phone_narratortext.mp3"
# 用于己方信息的音频
define audio.phonesend = "audio/sound_phone_sendtext.mp3"
# 用于对方信息的音频
define audio.phonereceive = "audio/sound_phone_receivetext.mp3"
 
default phone_bg = "alphaimage"             # 手机背景图片
default phone_fg = "alphaimage"             # 手机外框图片
 
# 默认 头像尺寸
default phone_avatar_xysize = (120,120)
# 默认 头像图片
default phone_avatar = "alphaimage"
# 默认 头像框图片
default phone_avatar_frame = "alphaimage"
 
# 默认 旁白的气泡
default phone_n_frame = "alphaimage"
# 默认 发信者的气泡
default phone_s_frame = "alphaimage"
# 默认 收信者的气泡
default phone_r_frame = "alphaimage"
 
default phone_n_color = "#FFFF"            # 旁白文本颜色
default phone_s_color = "#FFFF"            # 发信者文本颜色
default phone_r_color = "#000F"            # 收信者文本颜色
 
default phone_xy = (0.5,0.5)                 # 手机界面在屏幕上的坐标
default phone_xysize = (900,1750)            # 手机界面的尺寸(就是外框的尺寸)
 
default p_frame_xy = (0.5,0.56)              # 聊天窗口在手机界面里的坐标
default p_frame_xysize = (890,1200)          # 聊天窗口的尺寸,一般略小于手机(就是比外框略小)
default p_frame_spacing = 10                 # 聊天消息之间的上下间隔
 
default p_narrator_width = 580               # 旁白消息的最大宽度
default p_narrator_padding = (20,10,20,15)   # 旁白消息文本到气泡框架的左上右下边距
default p_narrator_textsize = 34             # 旁白消息的字体大小
 
default p_message_width = 580                # 普通消息的最大宽度
default p_message_padding = (20,10,20,15)    # 普通消息文本到气泡框架的左上右下边距
default p_message_textsize = 34              # 普通消息的字体大小
 
default p_message_xoffset = 0                # 普通消息距离侧方边界的偏移
 
### 系统变量 ############################################################################# 
define gui.nvl_thought_width = 0             # 旁白消息的最小宽度(覆写gui,否则上方的最大宽度会受到影响)
define gui.nvl_text_width = 0                # 普通消息的最小宽度(覆写gui,否则上方的最大宽度会受到影响)

default nvl_mode = "phone"                   # 区分正常NVL模式和手机组件的变量
define config.nvl_list_length = 999          # 最大对话记录数(在gui中也存在一条,这里直接覆写)
default phone_message_dict = {}              # 信息组名字:信息列表
default phone_message_last = {}              # 信息组名字:信息列表中,上次阅读的最后一条信息
default phone_message_list = None            # 控制当前使用哪个信息列表
# say回调函数
define config.say_arguments_callback = phone_message_dict
 
 
# 这里的最小宽度是消息气泡的最小宽度,如果文本比最小宽度长则自适应。否则文本比最小宽度短,气泡也会维持最小宽度。
# 上方的最大宽度,是字数到达一定宽度后,文本换行的宽度。一般来说,最小宽度需要一个比最大宽度小的值。
# 由于这里的变量是主要的接口和覆写原生自带的配置参数,因此单独划分
 
#########################################################################################
 
init -1 python:
 
    #可入参的showscreen文本标签
    def show_screen_handler(target):
        if "," in target:
            screen_name, params_str = target.split(",", 1)
            params = {}
            for pair in params_str.split(","):
                if "=" in pair:
                    key, val = pair.split("=", 1)
                    params[key.strip()] = val.strip()
            renpy.show_screen(screen_name, **params)
        else:
            renpy.show_screen(target)
        return True
 
    config.hyperlink_handlers["show"] = show_screen_handler
 
    def showscreen_tag(tag, argument, contents):
        return [
            (renpy.TEXT_TAG, f"a=show:{argument}"),
            *contents,
            (renpy.TEXT_TAG, "/a")
        ]
 
    config.custom_text_tags["showscreen"] = showscreen_tag
 
    # 旁白消息音效      
    def Phone_NarratorSound(event,interact=True,**kwargs):
        if event == "show_done":
            renpy.sound.play(audio.phonenarrator)
    # 己方消息音效
    def Phone_SendSound(event,interact=True,**kwargs):
        if event == "show_done":
            renpy.sound.play(audio.phonesend)
    # 对方消息音效
    def Phone_ReceiveSound(event,interact=True,**kwargs):
        if event == "show_done":
            renpy.sound.play(audio.phonereceive)

    # 手机字典集
    def phone_message_dict(who,interact=True):
        # 如果当前流程phone_message_list(信息组的名字)非None,即为应用了某个信息组
        if store.phone_message_list:
            # phone_message_list(信息组的名字)不在字典中,动态注册新的信息组列表
            if store.phone_message_list not in store.phone_message_dict:
                store.phone_message_dict[store.phone_message_list] = []
            # 通过翻译系统获取当前对话哈希值,锁定唯一对话,避免说话角色相同,内容相同,但不是同一句话的情况
            message_hash = renpy.get_translation_identifier()
            # 如果当前哈希,在当前的信息组列表中,并且不是上次阅读的最后一句话,跳过互动
            if message_hash in store.phone_message_dict[store.phone_message_list]:
                if message_hash != store.phone_message_last[store.phone_message_list]:
                    interact = False
            # 否则,把哈希记录在当前的信息组列表,并在另一个字典记录这句话的哈希,也就是形成映射
            else:
                store.phone_message_dict[store.phone_message_list].append(message_hash)
                store.phone_message_last[store.phone_message_list] = message_hash
            # 映射关系字典
            # 信息组名字:已读信息的列表
            # 信息组名字:上次阅读的最后一条信息
        return (), {"interact": interact} # 返回需不需要互动的参数给renpy的say系统


# 消息动画效果(左右弹出,1为右,-1为左)
transform message_appear(pDirection):
    alpha 0.0
    xoffset 50 * pDirection
    parallel:
        ease 0.5 alpha 1.0
    parallel:
        easein_back 0.5 xoffset 0
# 头像动画效果(快速放大)
transform message_appear_icon():
    zoom 0.0
    easein_back 0.5 zoom 1.0
# 旁白动画效果(向上弹出)
transform message_narrator():
    alpha 0.0
    yoffset -50
    parallel:
        ease 0.5 alpha 1.0
    parallel:
        easein_back 0.5 yoffset 0
 
# 一个用于聊天中缩略图放大功能的界面
screen phone_cg(phonecg):
    zorder 102
    modal True
    frame:
        anchor (0.5,0.5)
        xysize phone_xysize
        pos phone_xy
        background phone_bg
        foreground phone_fg
        imagebutton:
            align (0.5,0.5)
            idle phonecg
            action Hide("phone_cg")
# 手机对话界面主屏幕
screen phone_dialogue(dialogue,items=None):
    zorder 101
    frame:
        anchor (0.5,0.5)
        xysize phone_xysize
        pos phone_xy
        background phone_bg
        foreground phone_fg
 
        viewport: # 可滚动区域
            anchor (0.5,0.5)
            xysize p_frame_xysize
            pos p_frame_xy
            draggable True
            mousewheel True
            yinitial 1.0 # 跟踪底部
            vbox:
                xalign 0.5
                xsize p_frame_xysize[0]
                spacing p_frame_spacing
                use phone_nvltext(dialogue)
                # 选项部分
                if items != None:
                    vbox:
                        xalign 1.0
                        xoffset -phone_avatar_xysize[0]//2-p_message_xoffset
                        for i in items:
                            textbutton i.caption:
                                background Frame(phone_s_frame,30,30,30,30) at message_narrator
                                padding p_message_padding
                                xsize p_message_width
                                text_size p_message_textsize
                                text_xalign 0.5
                                action i.action
# 手机消息显示组件
screen phone_nvltext(dialogue):
    zorder 101
    # 动态刷新角色数据库
    $ update_phone_avatars()
    # 初始化 上一条消息发送者
    $ previous_d_who = None
    # 遍历所有对话
    for id_d,d in enumerate(dialogue):
        # 旁白消息
        if d.who == None:
            frame:
                align (0.5,0.5)
                background Frame(phone_n_frame,30,30,30,30)
                padding p_narrator_padding
                if d.current:
                    at message_narrator
                text d.what:
                    align (0.5,0.5)
                    xsize p_narrator_width
                    size p_narrator_textsize
                    color phone_n_color
                    text_align 0.5
                    italic True
                    slow_cps False
                    id d.what_id
        # 角色消息
        else:
            # 从字典获取头像,头像框,如果为None或捕获不到,使用默认
            if d.who in phone_avatars and phone_avatars[d.who]["avatar"]:
                $ message_avatar = phone_avatars[d.who]["avatar"]
            else:
                $ message_avatar = phone_avatar
            if d.who in phone_avatars and phone_avatars[d.who]["avatar_frame"]:
                $ message_avatar_frame = phone_avatars[d.who]["avatar_frame"]
            else:
                $ message_avatar_frame = phone_avatar_frame
            # 从字典获取对话框,如果为None或捕获不到,使用默认
            if d.who in phone_avatars and phone_avatars[d.who]["frame"]:
                $ message_frame = phone_avatars[d.who]["frame"]
            elif d.who == mc_nvl.name:
                $ message_frame = phone_s_frame
            else:
                $ message_frame = phone_r_frame
            hbox:
                if d.who == mc_nvl.name:
                    box_reverse True
                    xalign 1.0 xoffset -p_message_xoffset
                else:
                    xalign 0.0 xoffset p_message_xoffset
                spacing 10
                fixed:
                    xysize phone_avatar_xysize
                    if d.current: # 只有当前消息才会应用动画
                        at message_appear_icon()
                    add message_avatar align (0.5,0.5)       # 添加头像
                    add message_avatar_frame align (0.5,0.5) # 添加头像框
                vbox:
                    #if d.who != mc_nvl.name and previous_d_who != d.who:
                    #取消注释添加缩进:玩家不显示姓名,同时,只在新角色说话时才显示姓名
                    text d.who:
                        if d.who == mc_nvl.name:
                            textalign 1.0
                            xalign 1.0
                    # 消息气泡
                    frame:
                        background Frame(message_frame,30,30,30,30)
                        padding p_message_padding
                        if d.current:                 # 只有当前消息才会应用动画
                            if d.who == mc_nvl.name:
                                at message_appear(1)  # 发送消息从右出现
                            else:
                                at message_appear(-1) # 接收消息从左出现
                        # 消息文本
                        text d.what:
                            align (0.5,0.5)
                            xsize p_message_width
                            size p_message_textsize
                            slow_cps False
                            if d.who == mc_nvl.name:
                                color phone_s_color
                                text_align 1.0
                            else:
                                color phone_r_color
                            id d.what_id
        # 更新 上一条消息发送者
        $ previous_d_who = d.who


第二部分 使用方法(这是需要你根据需求自行编写的部分和使用格式)
[RenPy] 纯文本查看 复制代码
# 测试用图片-头像
image phone tx01:
    Solid("#0b0")
    xysize(30,30)
image phone tx02:
    Solid("#b00")
    xysize(30,30)
image phone tx03:
    Solid("#000")
    xysize(30,30)
# 测试用图片-可放大的CG
image phone cg01:
    Solid("#000")
    xysize(300,300)
image phone cg01ex:
    Solid("#000")
    xysize(608,1080)
image phone cg02:
    Solid("#000")
    xysize(300,300) 
image phone cg02ex:
    xysize(608,1080)
    Solid("#000")
    pause 0.5
    Solid("#00f")
    pause 0.5
    Solid("#0ff")
    pause 0.5
    Solid("#fff")
    pause 0.5
    repeat
# 定义角色

# 写法如下,只是增加了kind=nvl参数,然后callback=Phone_NarratorSound/SendSound/ReceiveSound(发信息时角色自动使用的音效,对应第一部分设置的三个音效)
define mc_nvl = Character("Nighten",kind=nvl,callback=Phone_SendSound)# 这是第一视角的角色,定义一个新的或者对应上你的角色
#define mc_nvl = 和你的主角内容相同,只是把kind=nvl加进去,再填callback=音效函数

define niko_nvl = Character("妮可",kind=nvl,callback=Phone_ReceiveSound)# 范例角色
define ll_nvl = Character("lovelive",kind=nvl,callback=Phone_ReceiveSound)# 范例角色

# 数据列表,由于这个系统读取的不是实例,而是记录了字符串,所以要多写一个字典来查找角色
# 结构为 {某个角色:某个角色的数据字典}
# 角色数据字典结构为 {"avatar":角色头像,"avatar_frame":角色头像框,"frame":角色对话框}
# 角色数据均可填None,为None时系统使用第一部分设置的默认头像/头像框/对话框 

# 照抄例子写应该不难


init python:
    def update_phone_avatars():
        store.phone_avatars = {
            # 玩家(发送方)
            renpy.translate_string(mc_nvl.name):{"avatar":"phone tx01","avatar_frame":None,"frame":None},

            renpy.translate_string(niko_nvl.name):{"avatar":"phone tx02","avatar_frame":None,"frame":None},
            renpy.translate_string(ll_nvl.name):{"avatar":"phone tx03","avatar_frame":None,"frame":None},}

label start:
    "开始"
    call phonelabel_xx
    "现在,你完成了"
    return

# 如何写一个手机信息事件:
label phonelabel_xx:
    $ nvl_mode = "phone" # 先打开手机模式
    $ phone_message_list = "test" # 设置当前消息组的名称,同一个名称下的内容会被记录为一组聊天,可随意起名
    nvl_narrator "这是旁白的写法"
    niko_nvl "使用专用的角色开始对话"
    niko_nvl "这样插入一张 emoji 或 小图{image=phone/emoji/clap.png}"
    menu(nvl=True):
        nvl_narrator "选项菜单要入参 nvl=True"
        "选项1":
            pass
        "选项2":
            pass
    mc_nvl "这是另一边的角色在说话"
    niko_nvl "然后是 一个可放大的图像,点击图像将会放大,但形状要自己裁切"
    niko_nvl "{showscreen=phone_cg,phonecg=phone cg01ex}{image=phone cg01}{/showscreen}"
    niko_nvl "phonecg=phone cg01ex 是放大的图片,image=phone cg01 是缩略图"
    niko_nvl "然后是 一个可播放的图像,点击图像将会放大,且开始播放"
    niko_nvl "{showscreen=phone_cg,phonecg=phone cg02ex}{image=phone cg02}{/showscreen}"
    niko_nvl "phonecg=phone cg02ex 是放大的帧动画,image=phone cg02 是缩略图"
    mc_nvl "现在尝试互动,这是右边的角色在说话"
    ll_nvl "现在尝试多角色对话"
    niko_nvl "这是左边的 [niko_nvl] 在说话"
    ll_nvl "这是左边的 [ll_nvl] 在说话"
    $ phone_message_list = None # 重置当前的消息组
    $ nvl_mode = None # 关闭手机模式(养成封闭习惯)
    nvl clear # 清空nvl界面,释放缓存
    return


第三部分 修改(和原生的项目工程对接,仅修改一句话)
以上部分可以直接建成新文件,以下是需要在源文件screen.rpy中修改的,只加一句话
[RenPy] 纯文本查看 复制代码
screen nvl(dialogue, items=None):
    zorder 101
    ### 在这里增加应用手机系统的分支 ###
    if nvl_mode == "phone":
        use phone_dialogue(dialogue,items)
    else:
    ### 以下是源代码只增加了缩进没有任何改动 ###
        window:
            style "nvl_window"

            has vbox:
                spacing gui.nvl_spacing

            ## 在 vpgrid 或 vbox 中显示对话框。
            if gui.nvl_height:

                vpgrid:
                    cols 1
                    yinitial 1.0

                    use nvl_dialogue(dialogue)

            else:

                use nvl_dialogue(dialogue)

            ## 显示菜单,如果给定的话。如果 config.narrator_menu 设置为 True,则菜单
            ## 可能显示不正确。
            for i in items:

                textbutton i.caption:
                    action i.action
                    style "nvl_button"

    add SideImage() xalign 0.0 yalign 1.0


上方是完整代码以及使用方式
下方
新手可以完全不看,是我更新过程中,逐步解决的问题以及思路,有一定难度以及理解要求,
但假如遇到问题,可以看看下方尝试解决,实在解决不了可以留言

1/25之前更新处

[RenPy] 纯文本查看 复制代码
#在第二部分
default phone_avatars = {...}
#修改为
init python:
    def update_phone_avatars():
        store.phone_avatars = {...}


#在第一部分
# 手机消息显示组件
screen phone_nvltext(dialogue):
    zorder 101
    # 添加了这一句
    $ update_phone_avatars()


原因:
游戏生命周期中,default在初始化时被决定,因此立即翻译函数并不会被执行,或只执行一次
[RenPy] 纯文本查看 复制代码
renpy.translate_string()

所以,这个字典会被锁死在最初的游戏语言,一旦切换语言,字典就会对不上,没有动态化
所以将其注册为函数,每次打开界面时运行,实现动态匹配多语言角色

4/13之前更新处
在第一部分添加了
[RenPy] 纯文本查看 复制代码
# say回调函数
define config.say_arguments_callback = phone_message_dict

init python:

    # 手机字典集
    def phone_message_dict(who,interact=True):
        # 如果当前流程phone_message_list(信息组的名字)非None,即为应用了某个信息组
        if store.phone_message_list:
            # phone_message_list(信息组的名字)不在字典中,动态注册新的信息组列表
            if store.phone_message_list not in store.phone_message_dict:
                store.phone_message_dict[store.phone_message_list] = []
            # 通过翻译系统获取当前对话哈希值,锁定唯一对话,避免说话角色相同,内容相同,但不是同一句话的情况
            message_hash = renpy.get_translation_identifier()
            # 如果当前哈希,在当前的信息组列表中,并且不是上次阅读的最后一句话,跳过互动
            if message_hash in store.phone_message_dict[store.phone_message_list]:
                if message_hash != store.phone_message_last[store.phone_message_list]:
                    interact = False
            # 否则,把哈希记录在当前的信息组列表,并在另一个字典记录这句话的哈希,也就是形成映射
            else:
                store.phone_message_dict[store.phone_message_list].append(message_hash)
                store.phone_message_last[store.phone_message_list] = message_hash
            # 映射关系字典
            # 信息组名字:已读信息的列表
            # 信息组名字:上次阅读的最后一条信息
        return (), {"interact": interact} # 返回需不需要互动的参数给renpy的say系统


然后在使用时,写信息label时,用$ phone_message_list =“xx”来包裹(相同的字符串名字会被视作同一栏信息)

[RenPy] 纯文本查看 复制代码
label phonelabel_xx:
    $ nvl_mode = "phone" # 先打开手机模式
    $ phone_message_list = "test" # 设置当前消息组的名称
    "聊天消息的内容"
    $ phone_message_list = None # 重置当前的消息组
    $ nvl_mode = None # 关闭手机模式(养成封闭习惯)
    return

思考过程:
实际上聊天记录这个功能就是已读消息不需要互动直接跳过
恰好,原生say系统中,在角色定义里有一个参数为interact,决定了当前角色的对话需不需要用户互动,也就是“点击继续”的dismiss行为
我一直苦恼怎么把interact自动的,非手动覆盖传给say,毕竟一条条写判断也太糖了
实际尝试,因为最开始就有一个interact的方向,所以看源码和查阅文档后
发现文档中给出了一个配置项config.say_arguments_callback,这是一个回调函数,会在say发起时自动调用
那么问题就轻松解决了,只要记录一下哈希就可以了。

额外内容:聊天记录中的分支顺序问题
在聊天记录功能完成之后就有一个问题,假设你的聊天事件中具有外部开关管控的内容
[RenPy] 纯文本查看 复制代码
label phonelabel_xx:
    $ nvl_mode = "phone" # 先打开手机模式
    $ phone_message_list = "test" # 设置当前消息组的名称
    nvl_narrator "1"
    if ss:
        nvl_narrator "2"
    if cc:
        nvl_narrator "3"
    nvl_narrator "4"
    $ phone_message_list = None # 重置当前的消息组
    $ nvl_mode = None # 关闭手机模式(养成封闭习惯)
    nvl clear # 清空nvl界面,释放缓存
    return


那么聊天记录中呈现的将会是顺序显示1,2,3,4
而不是根据玩家解锁顺序把新的消息放在最下方
例如先不开开关,然后开cc,再开ss,我们期望的顺序应该是1,4,3,2
解决这个问题,我有一个方案,但暂时没有进行整合,这里先进行提出
那就是记录时间表,用队列形式进行排序,并且label的变更内容做成新的label,而不是单独的对话

[RenPy] 纯文本查看 复制代码
default phonelabel_xx_list = []
label phonelabel_xx:
    $ nvl_mode = "phone" # 先打开手机模式
    $ phone_message_list = "test"
    nvl_narrator "1"
    # 假设这里开始分支
    if ss and "phonelabel_xx_1" not in phonelabel_xx_list:
        $ phonelabel_xx_list.append("phonelabel_xx_1")
    if cc and "phonelabel_xx_2" not in phonelabel_xx_list:
        $ phonelabel_xx_list.append("phonelabel_xx_2")
    if phonelabel_xx_list != []
        $ index = 0
        $ renpy.jump(phonelabel_xx_list[index])
    else:
        pass
    $ phone_message_list = None
    $ nvl_mode = None
    nvl clear
    jump start

label phonelabel_xx_1:
    $ nvl_mode = "phone" # 先打开手机模式
    $ phone_message_list = "test"
    "2"
    $ index = phonelabel_xx_list.index("phonelabel_xx_1")+1
    if index <= len(phonelabel_xx_list):
        $ renpy.jump(phonejs_kbn[index])
    else:
        pass
    $ phone_message_list = None
    $ nvl_mode = None
    nvl clear
    return

label phonelabel_xx_2:
    $ phone_message_list = "test"
    "3"
    $ index = phonelabel_xx_list.index("phonelabel_xx_2")+1
    if index < len(phonelabel_xx_list):
        $ renpy.jump(phonejs_kbn[index])
    else:
        pass
    $ phone_message_list = None
    $ nvl_mode = None
    nvl clear
    return

此时,聊天记录的顺序除了最开始的label,分支内容则由按照开关解锁的顺序队列进行,而不是依托于label语法糖的严格上下顺序。


这一点上使用有些微难度,但这本质是因为这个系统依托于renpy本身的语法糖,能够很好兼容say,nvl,choice,history等模块
假如完全独立于renpy的系统之外构筑一个使用类、函数注册等方法的系统,手动管理消息组列表,自然不需要这么麻烦
但也会失去renpy本身的便捷语法,同时还要独立接入其他非核心的模块(choice,history),这是另一个层次上的麻烦,而且在系统的架构上也更加复杂
























发表于 2026-1-25 14:22:26 | 显示全部楼层
收藏了,逻辑上有参考意义,谢谢分享
回复 支持 抱歉

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|RenPy中文空间 ( 苏ICP备17067825号 )

GMT+8, 2026-4-15 16:06 , Processed in 0.018510 second(s), 6 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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