Redscript配置框架

Redscript配置框架

女孩必须做她需要的事。为Redscript和游戏内提供动态内容的配置框架。(づ。◕‿‿◕。)づ

- 由 Coffee2a 提供的演示 -

一些简短的 GIF 动图,展示了该框架在“真第一人称相机 2.0”中的实际应用。

演示 演示 演示 演示

- 简介 -

我发现自己希望为我的模组提供一个更好的配置工具。具体来说,我希望能在游戏中配置我的“真第一人称相机 2.0”模组,而不必每次都跳出游戏去菜单里调整。我还希望能有一种更轻松、更好的方式来浏览选项……

经过一番调查,我意识到我的选择要么是不做,要么就是使用 CET 作为桥梁,利用其覆盖层系统。在我为《赛博朋克 2077》制作模组的生涯中,我确实已经变得非常不喜欢 CET,所以那并不是一个真正的选项……那么,一个女孩该怎么办呢?自己动手搭建吧,你这个懒丫头!哦,那好吧……

那么,我们这里有什么呢?我们有一个基于弹出窗口的配置框架,从现在起,我所有的模组都将使用它。它支持多标签页、子标签页、子章节标题、下拉列表、工具提示、滑块、开/关按钮、其他按钮、+/- 步进器,甚至支持图片。较长的选项页面会自动滚动,因此即使一个模组有几百个设置,也能保持整洁和可读性。到发布时,它还支持两种样式,并计划在未来实现按模组着色功能。

它的工作原理是利用优秀的 RedData 和 RedFilesystem 模组来扩展 Redscript 的能力,使其能够轻松读写 JSON,并使用 JSON 格式存储所有配置。

想要使用该框架的模组可以通过几种不同的方式来实现,所有方式都将在下面详细说明,但简而言之:将一个预先准备好的 JSON 文件放入框架的存储目录,进行 API 调用,或者直接进行代码调用。

关于这一切还有太多可以说的,但我总是容易唠叨个不停,所以让我重新戴上编码帽,深入下面的细节。对于所有用户 - 太长不看版会为你解释清楚!再见!

太长不看版:动态配置优于静态配置 - 而且更美观!

- 2.0.0 版本更新内容 -

版本 2.0.0 将配置器转变为一个完整的配置中心

  • 每个“模组设置”模组都出现在中心中 - 通过同一个面板浏览和编辑它们,无需任何人额外操作。“模组设置”本身会继续保存它们的值,我们只是正确地渲染它们。
  • 在主菜单中配置模组 - 主菜单(以及暂停和死亡菜单)上的一个新按钮,甚至在加载存档之前就能打开中心。
  • 无需“模组设置”的热键 - 框架现在通过内置的 DVRCFInput 插件自行应用按键绑定。支持键盘和手柄捕获,外加可选的按住修饰键,这样同一个按键可以服务于多个模组。
  • 预设和收藏集 - 从任意模组的面板将其当前设置保存为命名预设,然后将多个模组的预设捆绑到一个收藏集中,并一键加载它们。
  • 选择器工具 - 排序模式、筛选框、跨所有模组的设置搜索、收藏星标(可关闭)以及按框架着色功能。
  • 大型 UI - 一个最大化的卡片布局:左侧是分类导航,大型模组卡片带有标题图片和简短描述,模组面板、维基和日志查看器也采用同样的大尺寸处理。可针对不同上下文(菜单按钮、菜单热键、游戏内)进行切换。
  • 游戏内维基 - 模组可以将其 Nexus 页面作为文本文件附带,它会在游戏内渲染,包括格式、图片等,并带有可点击的“目录”图例。这个页面本身就可以在游戏内阅读。
  • 日志查看器 - 安装了 RedLogger 插件后,“日志”按钮会在游戏内显示每个模组的日志文件,支持会话翻页和文本搜索。
  • 隐藏“模组设置”菜单项 - 可选;中心反正会涵盖那些模组,所以菜单可以保持干净。

- 配置器选项 -

配置器现在会适应你的需求。面板标题中的选项按钮会打开一个属于它自己的设置页面:

  • 整体缩放 - 一起放大或缩小整个面板、窗口、控件和文本。专为高分辨率、客厅/电视游玩和掌机设计。
  • 文本大小 - 在缩放之外独立调节字号,以便在不放大窗口的情况下获得更大或更小的文本。
  • 手柄按钮 - 为每个滑块的两侧添加 - 和 + 按钮,方便控制器使用。
  • 记住上次位置 - 重新打开配置器时,会回到你上次离开的位置:同一个模组、标签页、子标签页和滚动位置。在微调设置时能省去大量点击。不会在会话之间持久保留
  • 打开热键 + 修饰键 - 直接在面板中重新绑定中心的打开按键(默认为 F8),支持键盘或手柄,并可选择按住修饰键。再次按下即可关闭中心。
  • 隐藏“模组设置”菜单项 - 从主菜单、暂停菜单和死亡菜单中移除“模组设置”;其模组仍可在中心中完全编辑。
  • 隐藏“模组设置”模组 - 另一个方向:将“模组设置”模组完全排除在中心之外(列表、搜索和维基),并从它们自己的菜单中进行配置。
  • “配置模组”入口(主开关) - 一次性在所有菜单中关闭“配置模组”入口;打开热键仍然可以进入。
  • 显示在主 / 暂停 / 死亡菜单 - 或者只在你想要的菜单中保留该入口。
  • 主 / 暂停 / 死亡菜单位置 - 每次将该入口在列表中上移一个位置。由于三个列表包含的项目不同,需分别设置。
  • 显示收藏星标 - 切换模组列表中的收藏星标;关闭表示仅使用普通排序,已保存的收藏仍会保留。
  • 按框架为模组着色 - 在选择器中根据模组来源为其着色:红色代表 RCF,琥珀色代表“模组设置”。
  • 筛选框、布局和维基调校 - 针对选择器筛选、控件偏移、日志文本大小和维基渲染的精细调节旋钮。
  • 大型 UI 开关 - 分别为主菜单按钮、主菜单热键和游戏内打开选择最大化的卡片布局。
  • 大型 UI - 收缩长模组名称 - 默认关闭:名称在卡片和导航中保持完整大小,那里有足够的空间。开启后恢复紧凑列表的收缩以适应功能。
  • 深色覆盖层 - 对话框后面使用黑色背景而不是白色;在大屏幕上对眼睛更友好。
  • 平面主菜单窗口 - 将主菜单窗口渲染为平面,而不是游戏默认的弯曲鱼眼效果。

你更改时所有内容都会实时缩放,你的选择会在会话之间保留。

- 预设和收藏集 -

每个模组的面板标题中都有一个预设按钮。可以将模组当前设置以任意名称保存,每个模组可以保留任意数量的预设,并在同一对话框中应用或删除它们。这也适用于“模组设置”的模组。

收藏集位于中心的“选项”页面。创建一个收藏集,从你已保存的所有内容中为每个模组勾选至多一个预设,为其命名,完成。加载一个收藏集会一键应用所有成员预设 - 非常适合在不同的整体游戏风格之间切换,或分享一个已知良好的配置。点击收藏集的名称可以查看其成员并移除单独的条目。

- 维基 -

选择器中的维基按钮会列出所有附带游戏内文档的已安装模组。页面是纯 Nexus BBCode,因此模组的 Nexus 描述就是其游戏内手册:格式、颜色、代码框和章节大小都会渲染,一个可点击的目录图例会从章节标题中生成,并且使用可选的 RedIMGRetriever 插件,甚至远程图片也能显示。自 2.1.5 版本(插件 0.3.0+)起,这包括动画 GIF,并且 Nexus 为你写入的 [img width=...] 尺寸也会被遵循 - 没有尺寸则使用自然分辨率,绝不会拉伸填充。

想让你的模组页面出现在维基中?附带一个文件 - 无需代码:

r6\storages\RedscriptConfigFramework\<modId>.docs.txt

- 模组卡片(面向作者) -

大型 UI 将每个模组呈现为一张卡片:一张标题图片、一个分类和两行描述。这些信息从哪里来?在你文档旁边放置一个可选文件,同样无需代码:

r6\storages\RedscriptConfigFramework\<modId>.card.json

格式(所有三个字段均可选):

{
"category": "沉浸",
"desc": "关于该模组的一两句话。最多 110 个字符。",
"image": "https://staticdelivery.nexusmods.com/mods/3333/images/headers/xxxx.jpg"
}

规则:

  • 文件名必须等于你注册的 modId - 与 docs.txt 的约定完全一致。名称错误 = 该文件永远不会被找到。
  • category 将你的模组分组到左侧导航中,显示时保留你编写的大小写。没有分类的模组会归入“未分类”下。尽量复用现有名称(沉浸、玩法、战斗、工具……),而不是发明近乎重复的名称。
  • desc 在加载时被限制为 110 个字符,因此它永远不可能超出卡片的两行区域 - 写得简短有力。
  • image 是一个远程 URL,通过可选的 RedIMGRetriever 插件获取。卡片框使用 Nexus 标题图片的确切比例 1300 x 372,因此你模组页面的标题图片完美契合 - 在你的 Nexus 页面上右键点击它并复制地址即可。GIF 横幅会进行动画显示(2.1.5+,插件 0.3.0+)。如果没有插件(或没有该字段),则会显示一个带有模组首字母的占位板,大小相同。
  • 转义你的反斜杠 - 它是 JSON,所以像 ¯(°_o)/¯ 这样的颜文字必须使用双反斜杠编写,否则整个文件将无法解析。格式错误的文件会回退为普通卡片,并在 RCF 频道记录一条警告,可在日志查看器中直接看到。

- 日志查看器 -

安装了可选的 RedLogger 插件后,选择器标题中会出现一个日志按钮。它会列出每个通过 RedLogger 写入的模组,每个模组一个部分,首先显示最新会话的文件。上一个下一个可以翻看每个模组最近五个会话,搜索会高亮匹配项并跳转到第一个。

一个来源按钮会循环切换查看器到模组技术栈写入的每个日志目录:RedScript(RedLogger 自己的文件)、RED4extFrameworks(Codeware、TweakXL、ArchiveXL 等)、CETCET Mods。模组列表会跟随所选择的来源,只要行带有标签,级别颜色就会应用,并且 RedLogger 自己文件夹之外的所有内容都严格只读。需要 RedLogger 1.3.0+(如果安装了 RedLogger,对于此框架版本,它必须是 1.3.0+)。

- 重置为默认值 -

每个模组的面板中都有一个重置按钮,可以将选项恢复到模组作者发布时的值。确认对话框会适应模组:单页模组提供单个“重置整个模组”选项,带标签页的模组会添加“重置此标签页”,带有子标签页的模组还会添加“重置此子标签页”,这样你可以只清除正在实验的部分,或让整个模组重新开始。

- 计划更新 -

一系列改进正在进行中,包括框架本身以及围绕它的工具和文档。

  1. 可扩展性 - 缩放窗口的能力。完成
  2. 文本大小 - 设置文本大小。完成
  3. 更好的手柄支持。完成
  4. 重置按钮。完成
  5. 记住你所在的位置。完成
  6. 翻译支持。完成
  7. 将“模组设置”模组集成到菜单中。完成
  8. 模组筛选。完成
  9. 各种排序方法。完成
  10. 设置搜索。完成
  11. 每个模组的预设。完成
  12. 选定模组的预设。完成!(收藏集)
  13. 将原生设置模组集成到菜单中。
  14. 维基中所有页面的远程图片。完成
  15. 添加对播放 GIF 文件的支持。

工具和文档:

  1. 基于 Python 的转换器,从“模组设置”转换到此框架。进行中
  2. 包含详细模组制作者资源的 Readme.md。进行中
  3. 带有清晰注释代码的演示模组。进行中

希望这些即将到来的变化和补充能有助于推广,但与此同时,如果你想使用该框架并有疑问,请随时通过私信、评论或 Discord 与我联系!

- 框架技术细节 -

框架的核心是一小组 RedScript 系统加上一个 Codeware 弹出窗口。模组从不构建任何 UI,也从不接触任何文件。它将一个模式(对其设置的描述)和一个提供者(一个通过名称读写这些设置的小类)交给框架。框架负责渲染面板、处理鼠标和手柄输入、绘制工具提示,并将所有内容保存到磁盘。

- 各组件如何协作 -

  • 中心 - 控制器。热键打开和关闭它;它显示模组选择器,然后是所选模组的面板。
  • 注册表 - 本次会话中已注册模组的实时列表。
  • 提供者 - 你与框架的契约。每个模组一个小类:构建模式,按键获取和设置值,处理按钮。
  • 存储 - 持久化。框架拥有每个配置文件;你的模组完全不进行任何文件操作。

流程很简单。你的模组在存档加载时注册一次。框架从磁盘读取你保存的值,并在面板打开之前将它们推送到你的提供者中,这样你的模组就已经在正确的设置下运行了。当用户关闭面板时,框架将当前值写回。

开箱即用自带两种视觉样式:经典(文本标签页,“真第一人称”外观)和按钮(带边框的按钮标签页)。你的提供者选择其中一种。打开热键默认为 F8,可在中心自己的“选项”页面上重新绑定。

- JSON -

框架将 JSON 用于两件事:一种无需任何 RedScript 即可注册整个配置面板的方式,以及存储每个模组值的地方。两者都通过优秀的 RedData 和 RedFileSystem 模组运行,这两个模组是必需的。

- 使用 JSON 文件注册 -

将一个名为 <modId>.schema.json 的单一文件放入框架的存储文件夹:

r6\storages\RedscriptConfigFramework\<modId>.schema.json

框架会在加载时找到它,根据它构建面板,注册你的模组,并为你存储值。无需提供者类,无需注册调用,完全不需要 RedScript。该文件自上而下描述面板:

{
  "modId": "mymod",
  "displayName": "我的模组",
  "description": "配置我的模组。",
  "tabStyle": "Classic",
  "tabs": [
    {
      "name": "常规",
      "sections": [
        {
          "name": "行为",
          "rows": [
            { "kind": "label", "label": "一个简短的标题。" },
            { "kind": "header", "label": "一个子章节标题。" },
            { "kind": "toggle", "key": "enabled", "label": "启用", "tooltip": "可选。", "default": true },
            { "kind": "slider", "key": "strength", "label": "强度", "min": 0.0, "max": 100.0, "step": 5.0, "isInt": false, "default": 60.0 },
            { "kind": "stepper", "key": "level", "label": "等级", "min": 1.0, "max": 10.0, "step": 1.0, "isInt": true, "default": 3 },
            { "kind": "dropdown", "key": "mode", "label": "模式", "options": ["关闭", "低", "高"], "default": 1 },
            { "kind": "button", "key": "reset", "label": "重置默认值" },
            { "kind": "image", "atlas": "base\\...\\some.inkatlas", "part": "part_name", "width": 600.0, "height": 200.0 }
          ]
        }
      ]
    }
  ]
}

数值范围字段(min、max、step、width、height)应使用小数点书写。重置按钮是无需脚本的面板可以连接的唯一按钮;它将所有值恢复到默认值。除此之外的任何功能(运行你自己的代码的按钮)都需要下面的 API 路径。

- 值的存储位置 -

无论模组以何种方式注册,其当前值都存在于它们自己的平面文件中:

r6\storages\RedscriptConfigFramework\<modId>.json

它是一个键到值的平面对象,每个键对应模式中的一个绑定键:

{
  "enabled": true,
  "strength": 75.0,
  "mode": 2
}

每个模组一个文件是刻意为之。一个损坏的值不会影响其他人的设置,卸载模组就是干净地删除一个文件,并且在故障排除时该文件易于阅读和手动编辑。你永远不需要自己打开或解析它;框架在注册时加载它,并在面板关闭时写入它,首次运行时用默认值初始化,因此它始终存在。

- API -

集成就是一个小类和一个注册调用。

- 提供者 -

子类化 DVRCF_Provider。BuildSchema 描述你的设置;get 和 set 方法将每个键绑定到你的模组实际存储该值的位置(配置系统、持久字段等)。

public class MyModProvider extends DVRCF_Provider {
  public func BuildSchema() -> ref<DVRCF_Schema> {
    return DVRCF_SchemaBuilder.New("我的模组")
      .Tab("常规")
      .Section("行为")
      .Toggle("enabled", "启用").Tip("打开或关闭模组。")
      .Slider("strength", "强度", 0.0, 100.0, 5.0, false)
      .Build();
  }

  public func GetBool(key: String) -> Bool {
    if Equals(key, "enabled") { return MyConfig.Get().enabled; }
    return false;
  }
  public func SetBool(key: String, value: Bool) -> Void {
    if Equals(key, "enabled") { MyConfig.Get().enabled = value; }
  }

  public func GetFloat(key: String) -> Float {
    if Equals(key, "strength") { return MyConfig.Get().strength; }
    return 0.0;
  }
  public func SetFloat(key: String, value: Float) -> Void {
    if Equals(key, "strength") { MyConfig.Get().strength = value; }
  }
}

每行都带有一个字符串键,框架通过该键调用你的提供者:

  • 开关绑定一个布尔值(GetBool / SetBool)。
  • 滑块步进器绑定一个浮点数,或者当最后一个参数(isInt)为真时绑定一个整数(GetFloat/SetFloat,或 GetInt/SetInt)。屏幕上的值会根据步长四舍五入到合理的小数位数,因此步长为 0.5 时显示“17.5”,步长为 10 时显示“20”。
  • 下拉列表绑定一个整数,即所选选项的索引(GetInt / SetInt)。
  • 按钮使用其键调用 OnButton。
  • 标签分组(子章节标题)和图片仅用于显示 - 它们不绑定任何内容。

GetTabStyle 选择外观(默认为经典):

public func GetTabStyle() -> DVRCF_TabStyle {
  return DVRCF_TabStyle.Buttons;
}

- 构建模式 -

模式构建器是流畅的。标签页和章节都是可选的;一个扁平行列表会变成一个隐式的标签页和章节。多于一个标签页会提供一个顶部标签栏,一个标签页中多于一个章节会提供一个左侧子导航。在一个章节内,.Group(...) 会放置一个子章节标题,将长选项列表分解为带标签的簇。

DVRCF_SchemaBuilder.New("我的模组")
  .Tab("常规")
  .Section("行为")
  .Label("一个简短的标题。")
  .Group("电源")
  .Toggle("enabled", "启用").Tip("可选的悬停描述。")
  .Slider("volume", "音量", 0.0, 100.0, 5.0, false)
  .Group("时序")
  .Stepper("count", "计数", 0.0, 10.0, 1.0, true)
  .Dropdown("mode", "模式", modeOptions)
  .Button("reset", "重置默认值")
  .Tab("关于")
  .Image("base\\...\\some.inkatlas", "part_name", 600.0, 200.0)
  .Build()

.Tip(文本) 会将悬停工具提示附加到它前面紧挨着的行。.Group(文本) 会在章节内渲染一个子章节标题(一个加粗带下划线的标题)。.Image 绘制 inkatlas 的一部分。超过面板高度的章节会自动滚动。

- 注册 -

在会话准备好后注册一次:

protected cb func OnSessionReady(event: ref<GameSessionEvent>) -> Void {
  DVRCF.Register(
    this.GetGameInstance(),
    "MyMod", // modId,也是 json 文件名
    "我的模组", // 在选择器和面板标题中显示
    "配置我的模组。", // 简短描述
    new MyModProvider()
  );
}

这就是整个集成过程。该模组现在会出现在中心中,渲染其面板,并持久化。

要直接将用户发送到你自己的页面(例如从你自己的热键),可以深度链接到中心:

DVRCF_Hub.Get(gi).OpenModConfig("MyMod");

- 从你自己的代码中读取值 -

如果你使用 JSON 文件注册,你不拥有这些值,框架才拥有,所以直接从文件中按键和模组读取。这也适用于任何模组,无论它是如何注册的:

let on: Bool = DVRCF.GetBool(gi, "mymod", "enabled");
let strength: Float = DVRCF.GetFloat(gi, "mymod", "strength");
let mode: Int32 = DVRCF.GetInt(gi, "mymod", "mode");

还有匹配的 SetBool / SetFloat / SetInt 调用,可以写入一个值并立即保存,适用于需要从代码更改设置的情况。

- 更新日志 -

1.0.0 - 首次公开发布。 1.0.1 - 修复了 int32 向零截断的问题,导致 -19 上的 0.5 步长使选项卡住。 1.0.2 - 更改了 JSON 中不存在值的处理方式。 1.1.0 - 可缩放 GUI(整体缩放 + 文本大小)、面板内选项页面、滑块上的手柄 - / + 按钮、记住上次位置,以及一个适应每个模组结构的重置为默认值按钮。 1.2.0 - 添加了两个新选项来控制字符间距;应该有助于使用西里尔字母或其他有类似要求的字体的用户。感谢 8LOMU8 的建议。 1.3.0 - 添加了动态列表恢复 API。 2.0.0 - 重大更新,众多功能:在中心渲染“模组设置”模组、主菜单和暂停菜单中的“配置模组”按钮、框架拥有的热键(支持手柄和按住修饰键,内置 DVRCFInput 插件)、预设和收藏集、排序、筛选、设置搜索、收藏、游戏内维基、RedLogger 日志查看器,以及针对所有 CP2077 语言的本地化。新增需求 - 见下文! 2.1.0 - 大型 UI:一个最大化的卡片布局,带有分类、标题图片和描述(模组附带可选的 card.json - 参见上面的模组卡片),应用于选择器、模组面板、维基和日志,并支持按上下文切换。日志查看器升级:带显隐过滤器的颜色编码日志级别(RedLogger 1.2.0+),以及通过“来源”按钮浏览模组技术栈中的每个日志目录(如果安装了 RedLogger,则需要 1.3.0+)。此外还有“深色覆盖层”选项、“平面主菜单窗口”选项,以及一个可筛选的维基导航。 2.1.1 - 内置的 DVRCFInput 已更新:地址哈希变为软失败。 2.1.2 - 修复:主菜单窗口忽略屏幕分辨率,在 1080p 下尺寸加倍。修复:打开热键在游戏内也能关闭中心,而不仅仅是在主菜单。修复:在游戏仍加载到主菜单时按下该热键可能导致没有菜单也没有窗口。新增:死亡菜单中的“配置模组”入口、一个主开关以及这三个菜单中该入口的每个菜单开关和每个菜单位置,以及“隐藏模组设置模组”选项。 2.1.3 - 修复:在大型 UI 中,一个足以填满左列的类别列表使其余部分无法访问 - 现在它可以滚动。“模组设置”模组现在拥有自己的类别,而不是填满“未分类”。大型 UI 模组名称现在保持完整大小,并带有一个新选项可再次缩小长名称。 2.1.4 - 为拥有自己的配置热键的模组添加了新 API:ToggleModConfig 会直接打开中心到该模组的面板,并在再次按下时关闭它,它也适用于“记住我所在的位置”选项。 2.1.5 - 支持使用 RedIMGRetriever(0.3.0+)播放动画 GIF。现在可以使用尺寸(宽度、高度)正确设置图像大小。

- 需求 -

此模组要求你安装以下组件: Redscript Codeware RedFileSystem RedData Input Loader RED4ext

可选: Mod Settings(其模组随后会显示在中心中) RedLogger - 1.3.0+(游戏内日志查看器) RedIMGRetriever(维基中的远程图片;0.3.0+ 可播放 GIF 动画)

- 安装 -

  1. 安装所有依赖项。
  2. 将下载的文件解压到你的主游戏目录中。

- 卸载 -

删除目录 \Cyberpunk 2077\r6\scripts\RedscriptConfigFramework 删除目录 \Cyberpunk 2077\r6\storages\RedscriptConfigFramework 删除目录 \Cyberpunk 2077\red4ext\plugins\DVRCFInput 删除文件 \Cyberpunk 2077\r6\input\dvrcf.xml

- 致谢 -

内置的 DVRCFInput 插件的输入覆盖逻辑源自 Jack Humbert 的 mod_settings,并链接了 psiberx 的 RedLib 和 WopsS 的 RED4ext.SDK。均为 MIT 许可。

感谢 CET 和 redscript 社区提供的工具和文档。

一如既往,特别感谢我出色的测试者和合作者:NightlyNowCoffee2aApoKrytia

GUI 缩放基于 8LOMU8 贡献的方法 - 使面板的尺寸和文本可缩放 - 通过框架自身的配置重新实现。

感谢 Spuddeh 审视 RCF 并说“扶好我的啤酒!”,这样我就能看看他的 NC Zoning Board 模组,然后说“扶好我的红酒!”并用大型 UI 选项重制了我的 UI!

感谢所有继续为我们所有人免费创作内容的优秀模组作者!

DigitalVixen 模组套件的一部分。 你现在可以在 Discord 上找到我,[点击这里](https://discord.gg/TRuuDSrGWJ)!