
简单数据库框架
一个用于redscript和lua的反应式、高性能对象数据库。数据具有存档感知能力,并以可读的JSON格式存储。加载存档时自动载入并持久化。订阅数据库更改以实现实时数据处理
查看大图简单数据库框架

一个面向 redscript 和 lua 的响应式高性能对象数据库。数据与存档文件感知,并以可读的 JSON 格式存储。在加载存档时自动加载和持久化。订阅数据库变更,即可在脚本框架间实现实时数据处理。
TL;DR
let dto: ref<MyDataObject> = new MyDataObject();
dto.id = "dtokey";
dto.value = "dtovalue";
dto.name = "My DTO";
dto.type = "SimpleDatabaseFramework.Example.MyDataObject";
dto.Save();
// 保存游戏并退出
...
// 启动游戏并加载存档
let dto: DataObject = player.GetObject("dtokey");
FTLog(dto.name+":"+dto.value);
...
My DTO:dtovalue
该框架包含两种主要的响应式数据机制。数据(瞬态)和对象(持久化)。您可能需要为您的模组提供临时状态感知(事件感知),但这些状态不会跨存档或重启保留。这被称为瞬态数据 API。对于随存档文件存储的永久数据,该框架提供持久化对象行为。两者将在下方的『用法』部分详细讨论。
值得一提的是,数据 API 不使用类或类型,因此更适合动态数据共享。您也可以将对象 API 用于临时数据,只需不保存对象即可——如果您更倾向于在编码中使用对象类和 DTO。选择权在您手中。
可能的使用场景
该框架的一些可能使用场景包括:
- 任务数据——您有想要存储的自定义任务数据,以便在玩家存档加载时重新加载。
- 配置数据——您想为您的模组存储各种用户配置或设置,并使其跨存档保持持久化。同时希望各种 lua/redscript 脚本能立即收到任何配置更改的通知,以便做出响应。
- 刷怪系统状态——您有一个动态刷怪系统,需要将刷怪属性存储在瞬态查找数据库中,每次游戏加载时重置。但同时,当某些数据发生变化时(例如,NPC 执行操作并更新其数据),所有监听脚本都需要立即收到通知。
- 动态载具或 NPC 遥测——您需要存储载具状态或 NPC 行为的动态数据,并在其更改时立即通知各种脚本。
重要!这是一个*早期访问版本,因为我计划在未来的简单任务框架模组中使用该框架(和我的其他简单框架),该模组将利用所有其他简单框架。*

可通过模组管理器安装,或解压到游戏目录。

LUA
以下是创建持久化对象并将其与存档文件一起存储的方法。稍后重新加载该特定存档,将使对象可用并保持其先前的状态。
> json = Game.GetPlayer():CreateObject()
> json:SetKeyString("id","newid")
> json:SetKeyString("value","newvalue")
> json = Game.GetPlayer():SetJsonObject(json)
注意:数据存储在位于 r6\storages\SimpleDatabaseFramework 的 .json 文件中。
稍后(或许在重新加载后)检索该对象。状态锁定到存档文件,因此在一个存档中更改同一对象不会跨存档传播。
> json = Game.GetPlayer:GetJsonObject("newid")
> print(json.GetKeyString("value"))
newvalue
存储临时数据
> Game.GetPlayer():SetValue("akey",{"value1","value2","value3"})
获取临时数据
> values = Game.GetPlayer():GetValue("akey")
> for value in values do print(value) end
数据状态监听器(瞬态)
-- 创建数据处理器
function datahandler(tags)
for i, tag in ipairs(tags) do
print("[SimpleDatabaseUpdateObjectMessage EXAMPLE] Object ID ",tag.value)
for i, v in ipairs(Game.GetPlayer():GetValue(tag.value)) do
print("[SimpleDatabaseUpdateObjectMessage EXAMPLE][",tag.value,"] Value ",v)
end
end
end
-- 添加您的数据处理器
eventFramework = GetMod("SimpleEventFramework")
eventFramework:AddEventMessageListener("BaseStatusEffect.SimpleDatabaseUpdateDataMessage", datahandler)
对象状态监听器(持久化)
-- 创建对象处理器
function handler(tags)
for i, tag in ipairs(tags) do
print("[SimpleDatabaseUpdateObjectMessage EXAMPLE] Object ID ",tag.value)
-- 获取已更新的对象
local json = Game.GetPlayer():GetObject(tag.value)
print("[SimpleDatabaseUpdateObjectMessage EXAMPLE] json ID ", json:GetKeyString("id"))
local type = json:GetKeyString("type")
print("[SimpleDatabaseUpdateObjectMessage EXAMPLE] json Type ",type)
if type == "SimpleDatabaseFramework.Example.MyDataObject" then
print("[SimpleDatabaseUpdateObjectMessage EXAMPLE] json Value ",json:GetKeyString("value"))
end
-- 使用 DTO 字段做其他事情,例如 json:GetKeyString("somekey")
-- 实际键字符串将取决于您的对象
end
end
-- 添加您的对象处理器
eventFramework = GetMod("SimpleEventFramework")
eventFramework:AddEventMessageListener("BaseStatusEffect.SimpleDatabaseUpdateObjectMessage", handler)
REDSCRIPT
定义一个自定义数据传输对象(DTO)
public class MyDataObject extends DataObject {
public let myproperty: String;
public let anotherprop: Int32;
}
数据状态监听器。当有临时数据更新时,将调用 Call() 方法,并且更新中的所有数据标签都可在 this.tags 中使用,如下所示。您无需手动发送数据,引擎会自动处理。
import SimpleDatabaseFramework.System.*
import SimpleEventFramework.System.*
public class MyDataListenerClass extends ListenerCallback {
public func Call() -> Void {
for tag in this.tags {
FTLog("[MyDataListenerClass] tag "+this.event+":"+NameToString(tag));
}
StatusEffectHelper.RemoveStatusEffect(this.player, t"BaseStatusEffect.SimpleDatabaseUpdateDataMessage");
}
}
@wrapMethod(PlayerPuppet)
protected cb func OnGameAttached() -> Bool {
let ret: Bool = wrappedMethod();
// 添加数据监听器
let eventFramework = SimpleEventFramework.GetInstance(this);
if IsDefined(eventFramework) {
let listener: ref<MyDataListenerClass> = new MyDataListenerClass();
listener.event = "BaseStatusEffect.SimpleDatabaseUpdateDataMessage";
eventFramework.AddEventMessageListener(listener);
}
}
对象状态监听器
import SimpleDatabaseFramework.System.*
import SimpleEventFramework.System.*
public class MyObjectListenerClass extends DatabaseListenerCallback {
public func Updated(dto: ref<DataObject>) -> Void {
if dto.IsA(n"SimpleDatabaseFramework.Example.MyDataObject") {
let mydto: ref<MyDataObject> = dto as MyDataObject;
FTLog("[SIMPLE DATABASE FRAMEWORK][MyObjectListenerClass] Updated dto "+mydto.value);
}
}
public func Removed(dto: ref<DataObject>) -> Void {
if dto.IsA(n"SimpleDatabaseFramework.Example.MyDataObject") {
let mydto: ref<MyDataObject> = dto as MyDataObject;
FTLog("[SIMPLE DATABASE FRAMEWORK][MyObjectListenerClass] Removed dto "+mydto.value);
}
}
}
public class MyDataObject extends DataObject {
public let value: String;
}
@wrapMethod(PlayerPuppet)
protected cb func OnGameAttached() -> Bool {
let ret: Bool = wrappedMethod();
// 添加对象监听器
let databaseFramework = SimpleDatabaseFramework.GetInstance(this);
if IsDefined(databaseFramework) {
let listener: ref<MyObjectListenerClass> = new MyObjectListenerClass();
databaseFramework.AddListener(listener);
}
return ret;
}
创建并保存对象。当对象被保存时,所有监听器都会被通知,包括 redscript 和 lua 中的对象和数据监听器。
@addMethod(PlayerPuppet)
public func ExampleDTO() -> Void {
FTLog("[SIMPLE DATABASE FRAMEWORK] ExampleDTO");
let dto: ref<MyDataObject> = new MyDataObject();
dto.id = "dtokey";
dto.value = "dtovalue";
dto.name = "My DTO";
dto.type = "SimpleDatabaseFramework.Example.MyDataObject";
dto.Save();
}
对于对象监听器,当任何对象被保存(即更新)时,监听器的 Updated() 方法将被调用,并传递已更新的 DTO 对象。
注意: 您需要将接收到的 DataObject 转换为您为模组创建的任何特定子类类型,如下所示。
public class MyObjectListenerClass extends DatabaseListenerCallback {
public func Updated(dto: ref<DataObject>) -> Void {
if dto.IsA(n"SimpleDatabaseFramework.Example.MyDataObject") {
let mydto: ref<MyDataObject> = dto as MyDataObject;
FTLog("[SIMPLE DATABASE FRAMEWORK][MyObjectListenerClass] Updated dto "+mydto.value);
}
}
...
}
此外,系统还会发出一个数据事件,发送已更新 DTO 的 id。然而,这种方法主要是为了支持 lua 监听器,因为 redscript 类类型对 lua 监听器不可用。这些监听器将根据对象 id 检索对象的 Json 版本。
this.simpleEventFramwork.SendEvent("BaseStatusEffect.SimpleDatabaseUpdateObjectMessage", [dto.id]);
请参阅上方的对象状态监听器示例,了解如何在 lua 中处理这些事件。

此处显示的所有示例都可以在 [u]Cyberpunk 2077\r6\scripts\SimpleDataFramework\Example.reds[/u] 文件中找到并运行。
对于 lua,您可以使用以下命令初始化示例代码
> sdf = GetMod("SimpleDatabaseFramework")
> sdf:Example()
function SimpleDatabaseFramework:Example()
eventFramework = GetMod("SimpleEventFramework")
eventFramework:AddEventMessageListener("BaseStatusEffect.SimpleDatabaseUpdateObjectMessage", handler)
eventFramework = GetMod("SimpleEventFramework")
eventFramework:AddEventMessageListener("BaseStatusEffect.SimpleDatabaseUpdateDataMessage", datahandler)
end
并参考 [u]Cyberpunk 2077\bin\x64\plugins\cyber_engine_tweaks\mods\SimpleDatabaseFramework\init.lua[/u] 中的代码。
查看我的其他简单框架

SimpleDatabaseFramework API 文档
SimpleDatabaseFramework 模组提供了一个简单的 API,用于跨游戏会话存储和检索持久化数据。它提供了一个简单的键值存储(用于字符串数组)和一个更复杂的对象存储(将数据序列化为 JSON 文件)。
概述
该框架旨在作为其他模组的集中式数据库,减少每个模组自行实现文件 I/O 和数据持久化逻辑的需求。它主要通过添加到 PlayerPuppet 类的方法来公开其 API,以便使用。
有两种主要的数据存储方式:
- 作为简单的键值对,其中值是字符串数组。
- 作为复杂的
DataObject实例,可序列化为 JSON 并从 JSON 反序列化。
快速入门
要使用该框架,您可以直接在 PlayerPuppet 实例上调用 API 方法。该框架是一个自动初始化的可脚本化系统。
PlayerPuppet API
这些方法可以在任何 PlayerPuppet 实例上直接调用(例如,通过 Game.GetPlayer() 获取的本地玩家)。
键值存储
SetValue(key, values) -> Void
存储与唯一键关联的字符串数组。如果键已存在,其值将被覆盖。
key: 数据的唯一字符串标识符。values: 要存储的字符串数组。
GetValue(key) -> array
检索给定键的字符串数组。
key: 要检索的数据的键。
RemoveValue(key) -> Void
从数据库中移除一个键及其关联的字符串数组。
key: 要移除的数据的键。
对象存储
SetObject(dto) -> Void
保存或更新扩展 DataObject 的自定义数据对象。对象的 id 字段用作唯一键。
dto: 要保存的数据对象。
GetObject(key) -> ref
按 ID 检索自定义数据对象。您需要将返回的 DataObject 转换为您的特定自定义类型。
key: 要检索的对象的唯一id。
RemoveObject(key) -> Void
使用其键从数据库中移除自定义数据对象。
key: 要移除的对象的唯一id。
底层 JSON API
这些函数适用于需要直接操作 JsonObject 的更高级用途。
CreateObject() -> ref
创建并返回一个新的、空的 JsonObject。
SetJsonObject(json) -> Void
在数据库中存储一个 JsonObject。该对象必须有一个名为 "id" 的字符串字段,该字段将用作键。
GetJsonObject(key) -> ref
按键从数据库中检索一个 JsonObject。
创建自定义数据对象
要存储复杂数据,您需要定义一个扩展 DataObject 的类。该基类提供了基本的 id、type 和 name 字段,以及一个 Save() 方法。
public class MyDataObject extends DataObject {
public let myCustomString: String;
public let myCustomNumber: Int32;
}
要保存此对象的实例:
let myDto: ref<MyDataObject> = new MyDataObject();
myDto.id = "unique_identifier_for_my_object";
myDto.type = "MyMod.MyDataObject"; // 存储类型是一个好习惯
myDto.name = "My First Object";
myDto.myCustomString = "Hello World!";
myDto.myCustomNumber = 123;
// 这会在内部调用 player.SetObject(myDto)
myDto.Save();
监听数据库变更
您可以通过创建一个扩展 DatabaseListenerCallback 的监听器类,来监听数据库中对象的更新和移除。
1. 定义您的监听器
创建一个扩展 DatabaseListenerCallback 的类,并实现 Updated 和 Removed 函数。
public class MyObjectListener extends DatabaseListenerCallback {
public func Updated(dto: ref<DataObject>) -> Void {
// 检查更新的对象是否是您感兴趣的
if dto.IsA(n"MyMod.MyDataObject") {
let mydto: ref<MyDataObject> = dto as MyDataObject;
Log("MyDataObject with id " + mydto.id + " was updated!");
}
}
public func Removed(dto: ref<DataObject>) -> Void {
if dto.IsA(n"MyMod.MyDataObject") {
let mydto: ref<MyDataObject> = dto as MyDataObject;
Log("MyDataObject with id " + mydto.id + " was removed!");
}
}
}
2. 注册您的监听器
在您的 OnGameAttached 或其他初始化逻辑中,获取 SimpleDatabaseFramework 实例并添加您的监听器。
@wrapMethod(PlayerPuppet)
protected cb func OnGameAttached() -> Bool {
let ret: Bool = wrappedMethod();
let databaseFramework = SimpleDatabaseFramework.GetInstance(this);
if IsDefined(databaseFramework) {
let listener: ref<MyObjectListener> = new MyObjectListener();
databaseFramework.AddListener(listener);
}
return ret;
}
示例用法
以下是一个完整的示例,说明如何定义、保存、检索和监听自定义数据对象的更改。
module MyMod.Example
import SimpleDatabaseFramework.System.*
// 1. 定义您的自定义数据对象
public class MySettingsObject extends DataObject {
public let volume: Float;
public let enabled: Bool;
}
// 2. 定义一个变更监听器
public class MySettingsListener extends DatabaseListenerCallback {
public func Updated(dto: ref<DataObject>) -> Void {
if dto.IsA(n"MyMod.Example.MySettingsObject") {
let settings: ref<MySettingsObject> = dto as MySettingsObject;
Log("Settings updated! New volume: " + settings.volume);
}
}
public func Removed(dto: ref<DataObject>) -> Void {
// 如有必要,处理移除情况
}
}
@addMethod(PlayerPuppet)
public func SaveMySettings(volume: Float, enabled: Bool) -> Void {
let settings: ref<MySettingsObject> = new MySettingsObject();
settings.id = "my_mod_settings";
settings.type = "MyMod.Example.MySettingsObject";
settings.name = "My Mod Settings";
settings.volume = volume;
settings.enabled = enabled;
settings.Save(); // 这将触发我们监听器中的 'Updated' 函数
}
@addMethod(PlayerPuppet)
public func LoadMySettings() -> ref<MySettingsObject> {
let dto = this.GetObject("my_mod_settings");
if IsDefined(dto) {
return dto as MySettingsObject;
}
return null;
}
// 3. 在游戏启动时注册监听器
@wrapMethod(PlayerPuppet)
protected cb func OnGameAttached() -> Bool {
wrappedMethod();
let databaseFramework = SimpleDatabaseFramework.GetInstance(this);
if IsDefined(databaseFramework) {
databaseFramework.AddListener(new MySettingsListener());
}
return true;
}
正在加载版本记录…



正在加载评论…
评论在新手盒子客户端中发表,这里同步展示。