一个用于 Minecraft Forge 1.20.1 的 DG-LAB 设备控制库,通过 WebSocket 连接到 DG-LAB 后端,支持设备配对和强度控制。
- Group ID:
org.nooberic - Artifact ID:
dglib - Mod ID:
dglib - 当前版本:
1.3.3 - 兼容版本:Minecraft
1.20.1/ Forge47.4.20
- 当前版本为稳定版本:
1.3.3
建议在依赖方使用:
- 编译期:
compileOnly - 运行期:
runtimeOnly
如果你希望强制前置,则在依赖方的 mods.toml 中声明 mandatory=true。
repositories {
maven {
url = uri("https://nooberic0746.github.io/dg-lib/")
}
}
dependencies {
compileOnly fg.deobf("org.nooberic:dglib:1.3.3")
runtimeOnly fg.deobf("org.nooberic:dglib:1.3.3")
}[[dependencies.yourmodid]]
modId = "dglib"
mandatory = true
versionRange = "[1.3.3,)"
ordering = "AFTER"
side = "BOTH"- 完整实例 API:
org.nooberic.dglib.api.DgLibApi - 客户端静态 API:
org.nooberic.dglib.coyote.api.CoyoteClientApi - 多人联机服务端 API:
org.nooberic.dglib.multiplayer.DgServerCoyoteApi
basic_breathchaosconst
如果你正在编写另一个 Forge 1.20.1 模组,并希望把 DG Lib 作为前置库接入,可以按下面步骤配置。
repositories {
maven {
url = uri("https://nooberic0746.github.io/dg-lib/")
}
}dependencies {
compileOnly fg.deobf("org.nooberic:dglib:1.3.3")
runtimeOnly fg.deobf("org.nooberic:dglib:1.3.3")
}说明:
compileOnly:用于编译时引用 APIruntimeOnly:用于开发运行时把DG Lib一起加载进游戏
[[dependencies.yourmodid]]
modId = "dglib"
mandatory = true
versionRange = "[1.3.3,)"
ordering = "AFTER"
side = "BOTH"请把 yourmodid 替换成你自己的模组 ID。
运行时入口类:
org.nooberic.dglib.api.DgLibApi
示例:
import org.nooberic.dglib.api.DgLibApi;然后根据你的需求调用 DgLibApi 提供的公开能力。
当前发布版本为:
org.nooberic:dglib:1.3.3
对应地,依赖方的 versionRange 可以写成兼容当前版本的范围,例如:
versionRange = "[1.3.3,2.0.0)"- ✅ WebSocket 连接管理
- ✅ 设备自动配对(扫描二维码)
- ✅ 两通道强度控制(0-200 范围,支持
channel=3同时操作 A/B) - ✅ 内置与自定义波形发送(
.frame/Pulse) - ✅ 实时状态查询
- ✅ 自动重连机制
- ✅ 游戏内 GUI 界面
- ✅ 本地调度式
control/controlsoft(同一通道的新任务替换当前任务) - ✅ 直接执行的
set/wave/increase/decrease/clear
所有命令使用统一前缀 /dg
功能:连接到 DG-LAB 后端并自动显示配对二维码界面
使用:
/dg connect
效果:
- 连接到配置的 WebSocket 地址
- 自动弹出二维码 UI(等待约 10 秒或连接成功后立即显示)
- 游戏聊天显示连接状态
功能:断开与 DG-LAB 后端的连接
使用:
/dg disconnect
功能:查询当前设备连接状态和强度信息
使用:
/dg status
输出示例:
[DG Lib] State: PAIRED | Paired: YES | Client: deac65d2 | Target: sd73sjgf
| Strength A: 50/200 | Strength B: 75/200 | Pain A/B: 30/40 | Floor A/B: 10/12
状态值说明:
IDLE:未连接CONNECTING:连接中CONNECTED:已连接,等待配对PAIRED:已配对,设备就绪
功能:检查当前配对状态
使用:
/dg pair
输出示例:
- 已配对:
[DG Lib] Paired! Target: sd73sjgf - 未配对:
[DG Lib] Not paired. Scan /dg qr with your phone.
功能:打开二维码 UI 界面进行设备配对
使用:
/dg qr
界面说明:
- 显示配对二维码
- 扫描二维码使用 DG-LAB 手机应用进行配对
- 按 ESC 关闭界面
功能:打开强度控制设置界面
使用:
/dg ui
界面说明:
- 通道 A 滑动条:控制通道 A 的强度上限(0-200)
- 通道 B 滑动条:控制通道 B 的强度上限(0-200)
- 实时显示当前值
- 拖动滑动条时实时发送指令到设备
- 按 ESC 或点击 "Close" 按钮关闭
功能:直接设置指定通道的强度值
使用:
/dg set 1 150 # 设置通道 A(通道 1)强度为 150
/dg set 2 100 # 设置通道 B(通道 2)强度为 100
/dg set 3 80 # 同时设置通道 A 和 B 为 80
参数说明:
<channel>:通道号(1= 通道 A,2= 通道 B,3= 同时对 A/B 两个通道执行)<value>:强度值(0-200)
返回:
- 成功:
[DG Lib] Set Ch1 to 150 sent. - 双通道成功:
[DG Lib] Set Ch1+2 to 80 sent. - 失败:
[DG Lib] Not paired or unavailable.
安全值处理:
- 当已收到设备回传的通道安全上限后,若设置值超过该上限,会自动下调到安全上限再发送。
- 不会因为超限而直接丢弃操作。
set为直接执行,不进入本地调度器。
功能:使用 0-100 的软强度值设置通道强度。
使用:
/dg setsoft 1 50
/dg setsoft 3 40
参数说明:
<channel>:1= 通道 A,2= 通道 B,3= 同时设置 A/B<value>:软强度值(0-100),会根据通道配置换算为设备强度
setsoft 是直接执行,不进入本地调度器。
功能:直接增加指定通道的强度值
使用:
/dg increase 1 10
/dg increase 3 5
参数说明:
<channel>:通道号(1= 通道 A,2= 通道 B,3= 同时对 A/B 两个通道执行)<delta>:增加值(1-200)
说明:
increase为直接执行,不进入本地调度器。
功能:直接减少指定通道的强度值
使用:
/dg decrease 2 10
/dg decrease 3 5
参数说明:
<channel>:通道号(1= 通道 A,2= 通道 B,3= 同时对 A/B 两个通道执行)<delta>:减少值(1-200)
说明:
decrease为直接执行,不进入本地调度器。
功能:清空指定通道的调度内容,并立即清空当前波形与强度
使用:
/dg clear 1
/dg clear 3
说明:
clear会调用清波并把强度重置为0。channel=3时会同时作用于 A/B 两个通道。- 该命令用于结束当前输出并清空调度状态。
功能:把一个已注册波形加入本地调度器并按指定强度播放
使用:
/dg control 1 80 basic_breath 3
/dg control 2 60 chaos 1
/dg control 3 40 const 5
参数说明:
<channel>:通道号(1= 通道 A,2= 通道 B,3= 同时对 A/B 两个通道分别调度)<strength>:目标强度(0-200)<pulse_id>:已注册波形 ID,例如basic_breath、chaos、const<seconds>:持续时长(整数1-60秒)
调度说明:
control使用本地调度器管理波形播放,而不是简单的一次性直发。- 高优先级(更高强度)的任务会抢占低优先级任务。
- 同一通道的新调度会替换当前调度任务。
channel=3会拆成通道 A / B 两条独立调度任务,各自独立抢占和结束,互不影响。- 调度执行时会按
control传入的目标通道重建运行时波形,不会沿用已注册波形对象里原始的通道字段。
返回:
- 成功:
[DG Lib] Control sent. Ch1 strength=80, pulse='basic_breath', 3.0s. - 双通道成功:
[DG Lib] Control sent. Ch1+2 strength=40, pulse='const', 5.0s. - 波形不存在:
[DG Lib] Unknown pulse id: ...
功能:使用 0-100 的软强度值调度波形。库会按每个通道的配置换算成设备强度。
使用:
/dg controlsoft 1 50 basic_breath 5
/dg controlsoft 3 40 const 10
参数范围与 /dg control 相同,但 <strength> 为 0-100。
功能:直接播放指定波形,不经过调度器强度优先级管理
使用:
/dg wave 1 basic_breath 3
/dg wave 3 chaos 5
参数说明:
<channel>:1= 通道 A,2= 通道 B,3= 同时播放到 A/B<pulse_id>:已注册波形 ID<seconds>:持续时长(1-60 秒,整数)
说明:
- 该命令会把波形内容按给定秒数直接发送到设备。
wave会根据命令中的通道重建运行时波形。wave为直接执行,不进入本地调度器。
需要 OP 权限(权限等级 2),用于指定玩家并远程调用该玩家客户端 DG Lib。
- 示例:
/dg_server set Steve 1 150 - 示例:
/dg_server set Steve 3 80 - 含义:设置
Steve的通道 1,或在channel=3时同时设置 A/B 两个通道。
- 示例:
/dg_server increase Steve 1 10 - 示例:
/dg_server increase Steve 3 5 - 含义:直接增加目标玩家指定通道强度,不走调度器。
- 示例:
/dg_server decrease Steve 2 10 - 示例:
/dg_server decrease Steve 3 5 - 含义:直接减少目标玩家指定通道强度,不走调度器。
- 示例:
/dg_server clear Steve 1 - 示例:
/dg_server clear Steve 3 - 含义:清空目标玩家该通道当前波形并将强度归零,同时清理对应调度状态。
- 示例:
/dg_server control Steve 1 60 basic_breath 5 - 示例:
/dg_server control Steve 3 40 const 10 - 含义:请求
Steve客户端本地 DG Lib 执行调度式波形控制。 - 服务端命令与客户端本地
/dg control的<seconds>都是整数秒(1-60)。
- 示例:
/dg_server wave Steve chaos 5 - 含义:让
Steve客户端直接播放指定波形5秒。
- 示例:
/dg_server status Steve - 含义:通过网络向
Steve客户端请求最新状态并回传显示(含强度、上限、痛感强度、感受下限)。
说明:
- 指令通过服务端 -> 目标玩家客户端网络请求执行,再回传结果。
- 若目标玩家未配对或本地 DG Lib 不可用,指令会返回失败提示。
如果你是模组开发者,可以在代码中直接使用 DG Lib 提供的 API。
import org.nooberic.dglib.api.DgLibApi;
DgLibApi api = DgLibApi.get();// 连接到后端
api.connect();
// 断开连接
api.disconnect();
// 获取当前连接状态
ConnectionState state = api.getConnectionState();
// 可能的值:IDLE, CONNECTING, CONNECTED, PAIRED// 检查是否已配对
boolean isPaired = api.isPaired();// 获取设备当前状态
DeviceStatus status = api.getStatus();
// 访问状态信息
String clientId = status.getClientId(); // 本端 ID
String targetId = status.getTargetId(); // 对端 ID
int channelAStrength = status.getChannelAStrength(); // 通道 A 当前强度
int channelBStrength = status.getChannelBStrength(); // 通道 B 当前强度
int channelALimit = status.getChannelALimit(); // 通道 A 上限
int channelBLimit = status.getChannelBLimit(); // 通道 B 上限
String wsUrl = status.getWsUrl(); // WebSocket 地址
String lastError = status.getLastErrorCode(); // 最后一条错误
// 快速读取通道当前强度和上限
int currentA = api.getCurrentStrength(1); // 通道 A 当前强度
int limitA = api.getStrengthLimit(1); // 通道 A 当前上限
// UI 附加参数(供附属模组读取/写入)
int painA = api.getPainStrength(1); // 通道 A 痛感强度
int floorA = api.getSensationLowerLimit(1); // 通道 A 感受下限
api.setPainStrength(1, 60);
api.setSensationLowerLimit(1, 20);import org.nooberic.dglib.service.StrengthFeedbackListener;
api.setStrengthFeedbackListener(new StrengthFeedbackListener() {
@Override
public void onStrengthFeedback(int aStrength, int bStrength, int aLimit, int bLimit) {
System.out.println("A=" + aStrength + "/" + aLimit + ", B=" + bStrength + "/" + bLimit);
}
});说明:
- 当收到 APP 回传的
strength-A+B+ALimit+BLimit消息时会触发回调。 - 传入
null可取消监听:api.setStrengthFeedbackListener(null); pain/sensation floor会持久化到config/dglib-common.toml,重启游戏后仍保留。
U:打开 DG 强度控制界面I:急停(将 A/B 强度置 0 并断开配对)
// 设置通道强度(1 = 通道 A,2 = 通道 B,3 = 同时操作 A/B,值范围 0-200)
boolean setOk = api.setStrength(1, 150);
// 同时设置两个通道
boolean dualSet = api.setStrength(3, 80);
// 清空当前通道的波形并把强度置 0
boolean clearOk = api.clear(3);
// 增加强度(返回 true 表示成功)
boolean increaseOk = api.increaseStrength(1, 10);
// 减少强度(返回 true 表示成功)
boolean decreaseOk = api.decreaseStrength(1, 10);
// 调度式波形控制(channel: 1=A, 2=B, 3=A+B;seconds 为 1-60 的整数)
boolean controlOk = api.control(3, 50, "const", 2);
// 软强度调度(softStrength 为 0-100)
boolean softControlOk = api.controlSoft(1, 50, "const", 5);
// 发送基础波形(channel: 1=A, 2=B, 3=A+B;seconds: 1-60)
boolean waveOk = api.playBasicWave(1, 5);说明:
setStrength()、clear()、increaseStrength()、decreaseStrength()、playBasicWave()/playPulse()都是直接执行。- 只有
control()会进入本地调度器。 - 注册波形表中保存的是模板数据(
id + frames);真正下发到设备的执行波形会在运行时再附带channel + seconds。
// 初始化 API(通常自动调用,不需要手动调用)
api.initialize();
// 清理资源(模组卸载时自动调用)
api.shutdown();import org.nooberic.dglib.api.DgLibApi;
import org.nooberic.dglib.service.ConnectionState;
public class MyModIntegration {
public void connectAndControl() {
DgLibApi api = DgLibApi.get();
// 连接设备
api.connect();
// 检查连接状态
ConnectionState state = api.getConnectionState();
if (state == ConnectionState.PAIRED) {
// 设置强度
api.setStrength(1, 100); // 通道 A 设置为 100
api.setStrength(2, 80); // 通道 B 设置为 80
// 按已注册波形 ID 调度播放(seconds 为整数秒)
api.control(3, 60, "basic_breath", 2);
// 查询状态
var status = api.getStatus();
System.out.println("Channel A: " + status.getChannelAStrength() + "/" + status.getChannelALimit());
}
}
}当前运行时使用的是 .frame 文件,内容只保留 frames 数组;加载后会转换为内存中的 Pulse 对象。
相关类:
RegisteredPulse:注册表中的波形模板(id + frames)Pulse:发送到设备的运行时波形(channel + seconds + frames)PulseFileParser:解析.frame文件为RegisteredPulsePulseRegistry:全局波形注册表(id ->RegisteredPulse)
src/main/resources/pulse/ 目录用于存放 .frame 文件。
支持的 .frame 内容格式:
- 对象格式(推荐)
{
"frames": [
"0A0A0A0A00000000",
"0A0A0A0A14141414",
"0A0A0A0A64646464"
]
}- 纯数组格式
[
"0A0A0A0A00000000",
"0A0A0A0A14141414",
"0A0A0A0A64646464"
]约束:
frames: 每帧必须为 16 位 HEX 字符串
说明:
- 当前运行时最终只读取
frames;通道和时长由命令或 Java API 在播放时指定。
当前客户端启动时会自动注册以下内置波形:
basic_breathchaosconst
import org.nooberic.dglib.api.DgLibApi;
import org.nooberic.dglib.pulse.RegisteredPulse;
DgLibApi api = DgLibApi.get();
// 注册:文件位于 src/main/resources/pulse/my_wave.frame
boolean ok = api.registerPulse("my_wave_id", "my_wave.frame");
// 全局获取(主模组和附属模组都可按 id 获取)
RegisteredPulse pulse = api.getPulse("my_wave_id");
if (pulse != null) {
api.playPulse(1, pulse, 5);
}可获取全部已注册波形:
var all = api.getAllPulses(); // Map<String, RegisteredPulse>支持在服务端通过 ServerPlayer 获取该玩家对应的 Coyote 远程对象,操作会通过网络下发到该玩家客户端的 DG Lib 执行,再把结果回传服务端。
import net.minecraft.server.level.ServerPlayer;
import org.nooberic.dglib.multiplayer.DgServerCoyoteApi;
import org.nooberic.dglib.multiplayer.ServerCoyote;
ServerPlayer targetPlayer = ...;
ServerCoyote coyote = DgServerCoyoteApi.get().getCoyote(targetPlayer);// 设置强度(异步)
coyote.setStrength(1, 120).thenAccept(success -> {
if (success) {
// 已下发并执行成功
}
});
// 查询当前强度/上限(会向客户端请求并刷新)
coyote.getCurrentStrength(1).thenAccept(aStrength -> {
// A 通道当前强度
});
coyote.getStrengthLimit(1).thenAccept(aLimit -> {
// A 通道当前上限
});
// 调度式波形控制(seconds 当前为整数秒)
coyote.control(3, 50, "const", 5).thenAccept(success -> {
// 让目标客户端同时调度 A/B 两个通道
});
// 获取完整状态快照
coyote.refreshStatus().thenAccept(snapshot -> {
int a = snapshot.getChannelAStrength();
int b = snapshot.getChannelBStrength();
int aLimit = snapshot.getChannelALimit();
int bLimit = snapshot.getChannelBLimit();
});- Player -> Coyote 映射缓存(服务端维护)
- 请求-响应关联(requestId)
- 响应来源校验(必须由目标玩家客户端返回)
- 超时保护(默认 5 秒)
- 玩家下线自动清理映射与挂起请求
- 权限控制:只允许特定身份/关系的玩家发起远程控制
- 同意机制:被控玩家是否允许被谁控制
- 频率限制:避免短时间高频调度造成刷包
- 失败重试:对超时和离线进行可控重试
- 审计日志:记录谁在什么时候控制了谁
默认连接地址配置在游戏配置目录中,路径为:
config/dglib-common.toml
编辑 dglib-common.toml 文件,修改以下配置:
wsUrl = "wss://ws.dungeon-lab.cn/"推荐连接方案:
- 手机扫码优先使用公网端点:
wss://ws.dungeon-lab.cn/(与官方网页示例一致) - 本地 mock 后端调试使用:
ws://你的局域网IP:9999(不要使用127.0.0.1)
当前版本通过命令系统和游戏聊天提供反馈。如需集成事件系统,请参考 Forge 事件总线的使用。
| 值 | 说明 |
|---|---|
IDLE |
未连接 |
CONNECTING |
连接中 |
CONNECTED |
已连接,等待配对 |
PAIRED |
已配对,设备就绪 |
| 错误 | 原因 | 解决方案 |
|---|---|---|
| "Waiting for connection" | 后端未连接 | 检查 WebSocket 地址,确保后端运行 |
| "Not paired or unavailable" | 设备未配对或连接断开 | 执行 /dg qr 扫描二维码重新配对 |
| "Unknown pulse id" | 波形 ID 未注册 | 检查 src/main/resources/pulse/ 中的 .frame 文件是否已注册 |
| "Failed to open QR UI" | 二维码界面打开失败 | 检查游戏日志,尝试重新连接 |
模组使用 SLF4J 日志框架,日志输出到 logs/latest.log。
关键日志前缀:
[DG Lib]- 游戏聊天消息(用户可见)[or.no.dg.cl.JdkWsTransportClient]- WebSocket 连接日志[or.no.dg.se.DgLabServiceImpl]- 服务层日志[QrCodeScreen]/[StrengthControlScreen]- UI 日志
如果你要在自己的模组中使用 DG Lib,需要:
- 添加模组依赖配置(gradle 中)
- 在
src/main/resources/META-INF/mods.toml中声明依赖
待定
如遇到问题,请:
- 检查
logs/latest.log中的错误信息 - 确保 WebSocket 后端正常运行
- 验证 Minecraft 版本为
1.20.1、Forge 版本为47.4.20。