feat: 指令界面配置

This commit is contained in:
叁玖领域 2026-06-10 14:46:36 +08:00
parent 41203ae1c6
commit 52ca9cb066
30 changed files with 1575 additions and 24 deletions

View File

@ -134,8 +134,7 @@ dependencies {
modImplementation("com.lowdragmc.ldlib:ldlib-forge-1.20.1:1.0.49") {
transitive = false
}
modImplementation("top.r3944realms.lib39:lib39:1.20.1-${lib39_version}")
implementation(jarJar("top.r3944realms.dg_lab.api:CommonApi:${dg_lab_version}"))
modImplementation("top.r3944realms.lib39:lib39-forge-1.20.1:${lib39_version}")
implementation(jarJar("top.r3944realms.dg_lab:Common:${dg_lab_version}"))
}
mixin {
@ -255,11 +254,6 @@ publishing {
}
}
// ===================== =====================
tasks.named('build') {
dependsOn sourceJar, apiSourceJar, apiJar, apiJavadocJar
}
tasks.named('clean') {
delete fileTree(dir: "${project.projectDir}/mcmodsrepo")
}

View File

@ -64,7 +64,7 @@ mod_authors= R3944Realms, LeisureTimeDock
mod_description=Use DgLab PowerBox to improve Player's game sense.
mod_credits=
lib39_version=0.4.1
lib39_version=0.5.6
mutil_version=1.20.1-6.0.0
dg_lab_version=4.4.14.18
dg_lab_version=4.4.14.20
mutil_version_range=[6.0.0,]

View File

@ -1,2 +1,2 @@
// 1.20.1 2026-03-08T19:34:45.1815278 Languages: zh_tw
4eb9507db96ec64d8176a664e22e36bf8763d09d assets/dglabgame/lang/zh_tw.json
// 1.20.1 2026-06-10T14:38:47.691308 Languages: zh_tw
de843a2665a924368d9ef5ba456d22eb7e24d89b assets/dglabgame/lang/zh_tw.json

View File

@ -1,2 +1,2 @@
// 1.20.1 2026-03-08T19:34:45.1719971 Languages: zh_cn
9bb1075f2b018219335d8c3784d293cdd80464ac assets/dglabgame/lang/zh_cn.json
// 1.20.1 2026-06-10T14:38:47.6853295 Languages: zh_cn
1fff14dbc8d8f08ca771484db1a902dbefbb4709 assets/dglabgame/lang/zh_cn.json

View File

@ -1,2 +1,2 @@
// 1.20.1 2026-03-08T19:34:45.1785204 Languages: lzh
41c40b3de74f63b246b0e67bbd74c5e220cd1e69 assets/dglabgame/lang/lzh.json
// 1.20.1 2026-06-10T14:38:47.690311 Languages: lzh
a51fc9b128855ce2e4ae7e81c861ec5a23681574 assets/dglabgame/lang/lzh.json

View File

@ -1,2 +1,2 @@
// 1.20.1 2026-03-08T19:34:45.1765211 Languages: en_us
ca592519439edf564117cb3e2d7f98ae82defe15 assets/dglabgame/lang/en_us.json
// 1.20.1 2026-06-10T14:38:47.6883175 Languages: en_us
543ea588d9607a5f1d99e220157eb42903c4f4a2 assets/dglabgame/lang/en_us.json

View File

@ -1,4 +1,24 @@
{
"dglabgame.command.clear.sent": "Clear command sent for channel %s (%s target(s))",
"dglabgame.command.client.created": "DGLab client \"%s\" created (%s:%s)",
"dglabgame.command.client.list": "Registered clients: %s",
"dglabgame.command.client.none": "No DGLab clients registered",
"dglabgame.command.client.not_found": "Client \"%s\" not found",
"dglabgame.command.client.removed": "DGLab client \"%s\" removed",
"dglabgame.command.client.started": "DGLab client \"%s\" started",
"dglabgame.command.client.stopped": "DGLab client \"%s\" stopped",
"dglabgame.command.feedback.sent": "Feedback value %s sent to %s target(s)",
"dglabgame.command.invalid_channel": "Invalid channel. Use A or B.",
"dglabgame.command.invalid_policy": "Invalid policy. Use goto/set, increase/inc, or decrease/dec.",
"dglabgame.command.no_output": "No active DGLab output available. Start the server or a client first.",
"dglabgame.command.pulse.sent": "Pulse waveform sent to channel %s (%s target(s))",
"dglabgame.command.server.already_running": "DGLab server is already running",
"dglabgame.command.server.not_created": "DGLab server has not been created yet",
"dglabgame.command.server.not_running": "DGLab server is not running",
"dglabgame.command.server.started": "DGLab WebSocket server started on port %s",
"dglabgame.command.server.status": "Server status: %s, connected apps: %s",
"dglabgame.command.server.stopped": "DGLab server stopped",
"dglabgame.command.strength.sent": "Strength [%s %s %s] sent to %s target(s)",
"dglabgame.name": "DG Lab Game",
"dglabgame.user_agreement.accept": "§2§lAccept",
"dglabgame.user_agreement.line1": "1. This mod is developed by §e§lR3944Realms §r, completely free and open source.",
@ -8,6 +28,22 @@
"dglabgame.user_agreement.line5": "5. §4Prohibited for any illegal or immoral activities.",
"dglabgame.user_agreement.line6": "6. §4Developers are not responsible for any personal injury or property damage.",
"dglabgame.user_agreement.refuse": "§4§lRefuse",
"dglabgame.user_agreement.reject_use": "Not agreed to user guidelines, usage rejected",
"gui.dglabgame.client.label": "Client:",
"gui.dglabgame.feedback.send": "Send Feedback:",
"gui.dglabgame.qrcode.copy": "Copy QR URL to clipboard",
"gui.dglabgame.server.max_conn": "Max:",
"gui.dglabgame.server.port": "Port:",
"gui.dglabgame.server.status": "Status: %s",
"gui.dglabgame.strength.channel_a": "Channel A",
"gui.dglabgame.strength.channel_b": "Channel B",
"gui.dglabgame.tab.feedback": "Feedback",
"gui.dglabgame.tab.qrcode": "QR Code",
"gui.dglabgame.tab.server": "Server",
"gui.dglabgame.tab.strength": "Strength",
"gui.dglabgame.tab.waveform": "Waveform",
"gui.dglabgame.waveform.channel": "Channel:",
"key.categories.dglabgame": "DG Lab Game",
"key.dglabgame.management": "Open Management",
"key.dglabgame.test": "Test"
}

View File

@ -1,4 +1,24 @@
{
"dglabgame.command.clear.sent": "清除之令已送至通道 %s (%s 標的)",
"dglabgame.command.client.created": "DGLab 客戶 \"%s\" 已建 (%s:%s)",
"dglabgame.command.client.list": "已註客戶: %s",
"dglabgame.command.client.none": "無已註之 DGLab 客戶",
"dglabgame.command.client.not_found": "未見客戶 \"%s\"",
"dglabgame.command.client.removed": "DGLab 客戶 \"%s\" 已除",
"dglabgame.command.client.started": "DGLab 客戶 \"%s\" 已啟",
"dglabgame.command.client.stopped": "DGLab 客戶 \"%s\" 已停",
"dglabgame.command.feedback.sent": "回應值 %s 已送至 %s 標的",
"dglabgame.command.invalid_channel": "通道有誤,當用 A 或 B。",
"dglabgame.command.invalid_policy": "策略有誤,當用 goto/set、increase/inc 或 decrease/dec。",
"dglabgame.command.no_output": "無可用之 DGLab 輸出。請先啟伺服器或客戶。",
"dglabgame.command.pulse.sent": "脈衝波形已送至通道 %s (%s 標的)",
"dglabgame.command.server.already_running": "DGLab 伺服器已在運作中",
"dglabgame.command.server.not_created": "DGLab 伺服器尚未建",
"dglabgame.command.server.not_running": "DGLab 伺服器未啟",
"dglabgame.command.server.started": "DGLab WebSocket 伺服器已於埠 %s 啟",
"dglabgame.command.server.status": "伺服器態: %s, 已連應用: %s",
"dglabgame.command.server.stopped": "DGLab 伺服器已停",
"dglabgame.command.strength.sent": "強度 [%s %s %s] 已送至 %s 標的",
"dglabgame.name": "郊狼戯",
"dglabgame.user_agreement.accept": "§2§l認",
"dglabgame.user_agreement.line1": "1. 本模組乃 §e§lR3944Realms §r 所製,全然免費而開源。",
@ -8,6 +28,22 @@
"dglabgame.user_agreement.line5": "5. §4禁作非法不德之事。",
"dglabgame.user_agreement.line6": "6. §4凡人身之傷、財物之損開發者概不負責。",
"dglabgame.user_agreement.refuse": "§4§l否",
"dglabgame.user_agreement.reject_use": "未允用戶之約,拒用",
"gui.dglabgame.client.label": "客戶:",
"gui.dglabgame.feedback.send": "發回應:",
"gui.dglabgame.qrcode.copy": "複QR URL",
"gui.dglabgame.server.max_conn": "至多:",
"gui.dglabgame.server.port": "埠:",
"gui.dglabgame.server.status": "態: %s",
"gui.dglabgame.strength.channel_a": "通道甲",
"gui.dglabgame.strength.channel_b": "通道乙",
"gui.dglabgame.tab.feedback": "回應",
"gui.dglabgame.tab.qrcode": "二維碼",
"gui.dglabgame.tab.server": "伺服器",
"gui.dglabgame.tab.strength": "強度",
"gui.dglabgame.tab.waveform": "波形",
"gui.dglabgame.waveform.channel": "通道:",
"key.categories.dglabgame": "郊狼戯",
"key.dglabgame.management": "啟管理",
"key.dglabgame.test": "式"
}

View File

@ -1,4 +1,24 @@
{
"dglabgame.command.clear.sent": "清除指令已发送至通道 %s (%s 个目标)",
"dglabgame.command.client.created": "DGLab 客户端 \"%s\" 已创建 (%s:%s)",
"dglabgame.command.client.list": "已注册的客户端: %s",
"dglabgame.command.client.none": "没有已注册的 DGLab 客户端",
"dglabgame.command.client.not_found": "未找到客户端 \"%s\"",
"dglabgame.command.client.removed": "DGLab 客户端 \"%s\" 已移除",
"dglabgame.command.client.started": "DGLab 客户端 \"%s\" 已启动",
"dglabgame.command.client.stopped": "DGLab 客户端 \"%s\" 已关闭",
"dglabgame.command.feedback.sent": "反馈值 %s 已发送至 %s 个目标",
"dglabgame.command.invalid_channel": "无效的通道。请使用 A 或 B。",
"dglabgame.command.invalid_policy": "无效的策略。请使用 goto/set, increase/inc 或 decrease/dec。",
"dglabgame.command.no_output": "没有可用的 DGLab 输出。请先启动服务器或客户端。",
"dglabgame.command.pulse.sent": "脉冲波形已发送至通道 %s (%s 个目标)",
"dglabgame.command.server.already_running": "DGLab 服务器已在运行中",
"dglabgame.command.server.not_created": "DGLab 服务器尚未创建",
"dglabgame.command.server.not_running": "DGLab 服务器未在运行",
"dglabgame.command.server.started": "DGLab WebSocket 服务器已在端口 %s 启动",
"dglabgame.command.server.status": "服务器状态: %s, 已连接应用: %s",
"dglabgame.command.server.stopped": "DGLab 服务器已关闭",
"dglabgame.command.strength.sent": "强度 [%s %s %s] 已发送至 %s 个目标",
"dglabgame.name": "郊狼游戏",
"dglabgame.user_agreement.accept": "§2§l接受",
"dglabgame.user_agreement.line1": "1. 本模组由 §e§lR3944Realms §r开发完全免费开源。",
@ -8,6 +28,22 @@
"dglabgame.user_agreement.line5": "5. §4禁止用于任何违法或不道德的活动。",
"dglabgame.user_agreement.line6": "6. §4开发者不对任何人身伤害或财产损失负责。",
"dglabgame.user_agreement.refuse": "§4§l拒绝",
"dglabgame.user_agreement.reject_use": "未同意用户使用准则,拒绝使用",
"gui.dglabgame.client.label": "客户端:",
"gui.dglabgame.feedback.send": "发送反馈:",
"gui.dglabgame.qrcode.copy": "复制QR URL到剪贴板",
"gui.dglabgame.server.max_conn": "最大:",
"gui.dglabgame.server.port": "端口:",
"gui.dglabgame.server.status": "状态: %s",
"gui.dglabgame.strength.channel_a": "通道A",
"gui.dglabgame.strength.channel_b": "通道B",
"gui.dglabgame.tab.feedback": "反馈",
"gui.dglabgame.tab.qrcode": "二维码",
"gui.dglabgame.tab.server": "服务器",
"gui.dglabgame.tab.strength": "强度",
"gui.dglabgame.tab.waveform": "波形",
"gui.dglabgame.waveform.channel": "通道:",
"key.categories.dglabgame": "郊狼游戏",
"key.dglabgame.management": "打开管理",
"key.dglabgame.test": "测试"
}

View File

@ -1,4 +1,24 @@
{
"dglabgame.command.clear.sent": "清除指令已傳送至通道 %s (%s 個目標)",
"dglabgame.command.client.created": "DGLab 客戶端 \"%s\" 已創建 (%s:%s)",
"dglabgame.command.client.list": "已註冊的客戶端: %s",
"dglabgame.command.client.none": "沒有已註冊的 DGLab 客戶端",
"dglabgame.command.client.not_found": "未找到客戶端 \"%s\"",
"dglabgame.command.client.removed": "DGLab 客戶端 \"%s\" 已移除",
"dglabgame.command.client.started": "DGLab 客戶端 \"%s\" 已啟動",
"dglabgame.command.client.stopped": "DGLab 客戶端 \"%s\" 已關閉",
"dglabgame.command.feedback.sent": "回饋值 %s 已傳送至 %s 個目標",
"dglabgame.command.invalid_channel": "無效的通道。請使用 A 或 B。",
"dglabgame.command.invalid_policy": "無效的策略。請使用 goto/set, increase/inc 或 decrease/dec。",
"dglabgame.command.no_output": "沒有可用的 DGLab 輸出。請先啟動伺服器或客戶端。",
"dglabgame.command.pulse.sent": "脈衝波形已傳送至通道 %s (%s 個目標)",
"dglabgame.command.server.already_running": "DGLab 伺服器已在執行中",
"dglabgame.command.server.not_created": "DGLab 伺服器尚未創建",
"dglabgame.command.server.not_running": "DGLab 伺服器未在執行",
"dglabgame.command.server.started": "DGLab WebSocket 伺服器已在連接埠 %s 啟動",
"dglabgame.command.server.status": "伺服器狀態: %s, 已連線應用: %s",
"dglabgame.command.server.stopped": "DGLab 伺服器已關閉",
"dglabgame.command.strength.sent": "強度 [%s %s %s] 已傳送至 %s 個目標",
"dglabgame.name": "郊狼游戲",
"dglabgame.user_agreement.accept": "§2§l接受",
"dglabgame.user_agreement.line1": "1. 本模組由 §e§lR3944Realms §r 開發,完全免費開源。",
@ -8,6 +28,22 @@
"dglabgame.user_agreement.line5": "5. §4禁止用於任何違法或不道德之活動。",
"dglabgame.user_agreement.line6": "6. §4開發者不對任何人身份害或財產損失負責。",
"dglabgame.user_agreement.refuse": "§4§l拒絕",
"dglabgame.user_agreement.reject_use": "未同意使用者準則,拒絕使用",
"gui.dglabgame.client.label": "客戶端:",
"gui.dglabgame.feedback.send": "發送回饋:",
"gui.dglabgame.qrcode.copy": "複製QR URL到剪貼板",
"gui.dglabgame.server.max_conn": "最大:",
"gui.dglabgame.server.port": "連接埠:",
"gui.dglabgame.server.status": "狀態: %s",
"gui.dglabgame.strength.channel_a": "通道A",
"gui.dglabgame.strength.channel_b": "通道B",
"gui.dglabgame.tab.feedback": "回饋",
"gui.dglabgame.tab.qrcode": "QR碼",
"gui.dglabgame.tab.server": "伺服器",
"gui.dglabgame.tab.strength": "強度",
"gui.dglabgame.tab.waveform": "波形",
"gui.dglabgame.waveform.channel": "通道:",
"key.categories.dglabgame": "郊狼游戲",
"key.dglabgame.management": "開啟管理",
"key.dglabgame.test": "测试"
}

View File

@ -3,10 +3,14 @@ package top.leisuretimedock.dglabgame;
import com.mojang.logging.LogUtils;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import top.leisuretimedock.dglabgame.config.ClientConfig;
import top.leisuretimedock.dglabgame.config.CommonConfig;
import top.leisuretimedock.dglabgame.core.network.DLGNetworkHandler;
@ -19,6 +23,8 @@ public class DGLabGame {
}
public DGLabGame() {
DLGNetworkHandler.register();
ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, CommonConfig.SPEC);
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, ClientConfig.SPEC);
}
@Contract("_ -> new")
public static @NotNull ResourceLocation rl(String path) {

View File

@ -15,4 +15,12 @@ public class DLGKeyMapping {
GLFW.GLFW_KEY_Z,
"key.categories.dglabgame"
);
public static final KeyMapping KEY_MANAGEMENT = new KeyMapping(
"key.dglabgame.management",
KeyConflictContext.IN_GAME,
KeyModifier.NONE,
InputConstants.Type.KEYSYM,
GLFW.GLFW_KEY_M,
"key.categories.dglabgame"
);
}

View File

@ -8,11 +8,11 @@ import com.lowdragmc.lowdraglib.gui.widget.Widget;
import com.lowdragmc.lowdraglib.gui.widget.WidgetGroup;
import net.minecraft.client.Minecraft;
import org.jetbrains.annotations.Nullable;
import top.leisuretimedock.dglabgame.config.CommonConfig;
import top.leisuretimedock.dglabgame.core.network.DLGNetworkHandler;
import top.leisuretimedock.dglabgame.core.network.toServer.AcceptUserAgreementPacket;
import top.leisuretimedock.dglabgame.util.UILoader;
import java.awt.*;
import java.util.Optional;
import java.util.Timer;
import java.util.TimerTask;
@ -26,11 +26,11 @@ public class ConfirmGui implements ILdlibGui {
widgetGroup.ifPresent(group -> {
Widget yes = group.getFirstWidgetById("yes");
if (yes instanceof ButtonWidget yesButton) {
ImageWidget imageWidget = new ImageWidget(yesButton.getPositionX() + 3, yesButton.getPositionY() + 3, yesButton.getSizeWidth() - 6, yesButton.getSizeHeight() - 6, new ColorRectTexture(Color.DARK_GRAY));
ImageWidget imageWidget = new ImageWidget(yesButton.getPositionX() + 3, yesButton.getPositionY() + 3, yesButton.getSizeWidth() - 6, yesButton.getSizeHeight() - 6, new ColorRectTexture(0xff373737));
group.addWidget(imageWidget);
yesButton.setActive(false);
Timer timer = new Timer();
final int[] countdown = {5};
final int[] countdown = {CommonConfig.COUNTDOWN_SECONDS.get()};
timer.scheduleAtFixedRate(
new TimerTask() {
@Override

View File

@ -0,0 +1,374 @@
package top.leisuretimedock.dglabgame.client.gui;
import com.lowdragmc.lowdraglib.gui.texture.ColorRectTexture;
import com.lowdragmc.lowdraglib.gui.texture.ResourceBorderTexture;
import com.lowdragmc.lowdraglib.gui.texture.ResourceTexture;
import com.lowdragmc.lowdraglib.gui.texture.TextTexture;
import com.lowdragmc.lowdraglib.gui.widget.*;
import com.r3944realms.dg_lab.api.manager.Status;
import com.r3944realms.dg_lab.api.message.IPowerBoxMsg;
import com.r3944realms.dg_lab.api.message.argType.Channel;
import com.r3944realms.dg_lab.api.message.argType.ChangePolicy;
import com.r3944realms.dg_lab.api.message.data.PulseWaveListGenerator;
import com.r3944realms.dg_lab.api.websocket.message.MessageDirection;
import com.r3944realms.dg_lab.api.websocket.message.PowerBoxMessage;
import com.r3944realms.dg_lab.manager.DGPBClientManager;
import com.r3944realms.dg_lab.websocket.sharedData.ClientPowerBoxSharedData;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import top.leisuretimedock.dglabgame.config.ClientConfig;
import top.leisuretimedock.dglabgame.config.CommonConfig;
import top.leisuretimedock.dglabgame.core.manager.DGLabManagerHolder;
import top.leisuretimedock.dglabgame.core.manager.ServerManager;
import top.leisuretimedock.dglabgame.core.network.DLGNetworkHandler;
import top.leisuretimedock.dglabgame.core.network.toServer.UpdateServerConfigPacket;
import top.leisuretimedock.dglabgame.core.network.toServer.UpdateStrengthConfigPacket;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.util.Optional;
@OnlyIn(Dist.CLIENT)
public class DGLabManagementGui implements ILdlibGui {
private static final int W = 380;
private static final int H = 260;
private static final String CLIENT_ID = "dglabgame-gui";
private String currentPage = "qrcode";
@Override
public WidgetGroup createGui() {
WidgetGroup root = new WidgetGroup(0, 0, W, H);
root.setBackground(new ColorRectTexture(0xff1a1a1a));
// top nav bar
root.addWidget(navButton("qrcode", 5, 5, 55, 18, "gui.dglabgame.tab.qrcode"));
root.addWidget(navButton("strength", 65, 5, 55, 18, "gui.dglabgame.tab.strength"));
root.addWidget(navButton("waveform", 125, 5, 55, 18, "gui.dglabgame.tab.waveform"));
root.addWidget(navButton("feedback", 185, 5, 55, 18, "gui.dglabgame.tab.feedback"));
boolean isAdmin = Minecraft.getInstance().player != null
&& Minecraft.getInstance().player.hasPermissions(4);
if (isAdmin) {
root.addWidget(navButton("server", 245, 5, 55, 18, "gui.dglabgame.tab.server"));
}
// content area
root.addWidget(buildContentArea());
// bottom bar: client selector
root.addWidget(buildBottomBar());
return root;
}
private ButtonWidget navButton(String page, int x, int y, int w, int h, String langKey) {
ButtonWidget btn = new ButtonWidget(x, y, w, h,
new TextTexture(Component.translatable(langKey).getString(), 0xffffffff),
cd -> switchPage(page)
);
btn.setBackground(new ColorRectTexture(page.equals(currentPage) ? 0xff4444aa : 0xff333333));
return btn;
}
private void switchPage(String page) {
currentPage = page;
display();
}
private WidgetGroup buildContentArea() {
WidgetGroup area = new WidgetGroup(5, 28, W - 10, H - 62);
area.setBackground(new ColorRectTexture(0xff222222));
switch (currentPage) {
case "qrcode": buildQrCodeTab(area); break;
case "strength": buildStrengthTab(area); break;
case "waveform": buildWaveformTab(area); break;
case "feedback": buildFeedbackTab(area); break;
case "server": buildServerTab(area); break;
}
return area;
}
// ==================== QR Code Tab ====================
private void buildQrCodeTab(WidgetGroup group) {
int y = 10;
TextFieldWidget urlField = new TextFieldWidget(10, y, W - 40, 15, () -> "", s -> {});
urlField.setCurrentString("");
urlField.setBordered(true);
group.addWidget(urlField);
group.addWidget(new ButtonWidget(W - 45, y, 22, 15, cd -> {
String url = getQrCodeUrl();
Toolkit.getDefaultToolkit().getSystemClipboard()
.setContents(new StringSelection(url), null);
}).setBackground(new ColorRectTexture(0xff336633)));
y += 22;
group.addWidget(new ImageWidget(10, y, W - 40, 14,
new TextTexture(Component.translatable("gui.dglabgame.qrcode.copy").getString(), 0xffaaaaaa)));
y += 20;
var names = DGLabManagerHolder.getClientManager().getClientNames();
for (String name : names) {
group.addWidget(new ImageWidget(10, y, W - 40, 10,
new TextTexture(name, 0xffcccccc)));
y += 14;
}
}
private String getQrCodeUrl() {
var names = DGLabManagerHolder.getClientManager().getClientNames();
for (String name : names) {
Optional<DGPBClientManager> client = DGLabManagerHolder.getClientManager().getClient(name);
if (client.isPresent()) {
ClientPowerBoxSharedData sd = client.get().getSharedData();
if (sd != null && sd.rqCodeUrl != null && !sd.rqCodeUrl.isEmpty()) {
return sd.rqCodeUrl;
}
}
}
return "";
}
// ==================== Strength Tab ====================
private void buildStrengthTab(WidgetGroup group) {
int y = 8;
group.addWidget(new ImageWidget(10, y, 60, 10,
new TextTexture(Component.translatable("gui.dglabgame.strength.channel_a").getString(), 0xffff4444)));
y += 14;
group.addWidget(strengthField("maxA", W / 2 - 55, y, CommonConfig.MAX_STRENGTH_A.get()));
group.addWidget(strengthField("minA", W / 2 + 15, y, CommonConfig.MIN_STRENGTH_A.get()));
y += 20;
group.addWidget(new ImageWidget(10, y, 60, 10,
new TextTexture(Component.translatable("gui.dglabgame.strength.channel_b").getString(), 0xff4444ff)));
y += 14;
group.addWidget(strengthField("maxB", W / 2 - 55, y, CommonConfig.MAX_STRENGTH_B.get()));
group.addWidget(strengthField("minB", W / 2 + 15, y, CommonConfig.MIN_STRENGTH_B.get()));
y += 22;
group.addWidget(new ButtonWidget(W / 2 - 30, y, 60, 16, cd -> {
TextFieldWidget maxAF = (TextFieldWidget) group.getFirstWidgetById("maxA");
TextFieldWidget minAF = (TextFieldWidget) group.getFirstWidgetById("minA");
TextFieldWidget maxBF = (TextFieldWidget) group.getFirstWidgetById("maxB");
TextFieldWidget minBF = (TextFieldWidget) group.getFirstWidgetById("minB");
try {
int maxA = Integer.parseInt(maxAF.getCurrentString());
int minA = Integer.parseInt(minAF.getCurrentString());
int maxB = Integer.parseInt(maxBF.getCurrentString());
int minB = Integer.parseInt(minBF.getCurrentString());
DLGNetworkHandler.CHANNEL.sendToServer(new UpdateStrengthConfigPacket(maxA, minA, maxB, minB));
} catch (NumberFormatException ignored) {}
}).setBackground(new ColorRectTexture(0xff336633)));
}
private TextFieldWidget strengthField(String id, int x, int y, int value) {
TextFieldWidget field = new TextFieldWidget(x, y, 50, 14, () -> "", s -> {});
field.setCurrentString(String.valueOf(value));
field.setBordered(true);
field.setId(id);
return field;
}
// ==================== Waveform Tab ====================
private void buildWaveformTab(WidgetGroup group) {
int y = 8;
String[] labels = {"f1", "s1", "f2", "s2", "f3", "s3", "f4", "s4"};
int[] defaults = {80, 50, 80, 50, 80, 50, 80, 50};
group.addWidget(new ImageWidget(10, y, 80, 10,
new TextTexture(Component.translatable("gui.dglabgame.waveform.channel").getString(), 0xffcccccc)));
group.addWidget(new ButtonWidget(W / 2 + 40, y - 2, 40, 14, cd -> {})
.setBackground(new ColorRectTexture(0xff4444aa)));
y += 16;
for (int i = 0; i < 4; i++) {
int fx = 10 + i * (W - 20) / 4;
TextFieldWidget ff = new TextFieldWidget(fx, y, 35, 12, () -> "", s -> {});
ff.setCurrentString(String.valueOf(defaults[i * 2]));
ff.setBordered(true);
ff.setId("wf_f" + (i + 1));
group.addWidget(ff);
TextFieldWidget sf = new TextFieldWidget(fx + 40, y, 35, 12, () -> "", s -> {});
sf.setCurrentString(String.valueOf(defaults[i * 2 + 1]));
sf.setBordered(true);
sf.setId("wf_s" + (i + 1));
group.addWidget(sf);
}
y += 18;
group.addWidget(new ButtonWidget(W / 2 - 30, y, 60, 16, cd -> {
try {
int f1 = parseIntField(group, "wf_f1"), f2 = parseIntField(group, "wf_f2");
int f3 = parseIntField(group, "wf_f3"), f4 = parseIntField(group, "wf_f4");
int s1 = parseIntField(group, "wf_s1"), s2 = parseIntField(group, "wf_s2");
int s3 = parseIntField(group, "wf_s3"), s4 = parseIntField(group, "wf_s4");
var waveList = PulseWaveListGenerator.pulseWave(
new int[]{f1, f2, f3, f4}, new int[]{s1, s2, s3, s4});
IPowerBoxMsg.Pulse msg = new IPowerBoxMsg.Pulse(Channel.A, waveList, 0);
PowerBoxMessage pbm = msg.toPowerBoxMessage(CLIENT_ID, CLIENT_ID,
MessageDirection.DirectType.CLIENT_TO_SERVER);
DGLabManagerHolder.getServerManager().ifPresent(mgr -> mgr.sendToAll(pbm));
} catch (NumberFormatException ignored) {}
}).setBackground(new ColorRectTexture(0xff994400)));
group.addWidget(new ButtonWidget(W / 2 + 38, y, 60, 16, cd -> {
try {
int f1 = parseIntField(group, "wf_f1"), f2 = parseIntField(group, "wf_f2");
int f3 = parseIntField(group, "wf_f3"), f4 = parseIntField(group, "wf_f4");
int s1 = parseIntField(group, "wf_s1"), s2 = parseIntField(group, "wf_s2");
int s3 = parseIntField(group, "wf_s3"), s4 = parseIntField(group, "wf_s4");
var waveList = PulseWaveListGenerator.pulseWave(
new int[]{f1, f2, f3, f4}, new int[]{s1, s2, s3, s4});
IPowerBoxMsg.Clear clear = new IPowerBoxMsg.Clear(Channel.A);
IPowerBoxMsg.Pulse pulse = new IPowerBoxMsg.Pulse(Channel.A, waveList, 0);
PowerBoxMessage clearMsg = clear.toPowerBoxMessage(CLIENT_ID, CLIENT_ID,
MessageDirection.DirectType.CLIENT_TO_SERVER);
PowerBoxMessage pulseMsg = pulse.toPowerBoxMessage(CLIENT_ID, CLIENT_ID,
MessageDirection.DirectType.CLIENT_TO_SERVER);
DGLabManagerHolder.getServerManager().ifPresent(mgr -> {
mgr.sendToAll(clearMsg);
mgr.sendToAll(pulseMsg);
});
} catch (NumberFormatException ignored) {}
}).setBackground(new ColorRectTexture(0xff994400)));
}
private int parseIntField(WidgetGroup group, String id) {
Widget w = group.getFirstWidgetById(id);
if (w instanceof TextFieldWidget tf) {
return Integer.parseInt(tf.getCurrentString());
}
throw new NumberFormatException();
}
// ==================== Feedback Tab ====================
private void buildFeedbackTab(WidgetGroup group) {
int y = 8;
group.addWidget(new ImageWidget(10, y, 80, 10,
new TextTexture(Component.translatable("gui.dglabgame.feedback.send").getString(), 0xffcccccc)));
y += 16;
var presets = ClientConfig.FEEDBACK_PRESETS.get();
int cols = 4;
int btnW = (W - 30) / cols;
int row = 0, col = 0;
for (String presetStr : presets) {
try {
int val = Integer.parseInt(presetStr.trim());
int bx = 10 + col * (btnW + 2);
int by = y + row * 18;
group.addWidget(new ButtonWidget(bx, by, btnW, 14, cd -> sendFeedback(val))
.setBackground(new ColorRectTexture(0xff444488)));
col++;
if (col >= cols) { col = 0; row++; }
} catch (NumberFormatException ignored) {}
}
y += (row + 1) * 18 + 6;
TextFieldWidget customField = new TextFieldWidget(10, y, 50, 14, () -> "", s -> {});
customField.setCurrentString("5");
customField.setBordered(true);
customField.setId("feedback_custom");
group.addWidget(customField);
group.addWidget(new ButtonWidget(65, y, 50, 14, cd -> {
try {
TextFieldWidget f = (TextFieldWidget) group.getFirstWidgetById("feedback_custom");
sendFeedback(Integer.parseInt(f.getCurrentString()));
} catch (NumberFormatException ignored) {}
}).setBackground(new ColorRectTexture(0xff444488)));
}
private void sendFeedback(int value) {
IPowerBoxMsg.Feedback msg = new IPowerBoxMsg.Feedback(Math.max(0, Math.min(10, value)));
PowerBoxMessage pbm = msg.toPowerBoxMessage(CLIENT_ID, CLIENT_ID,
MessageDirection.DirectType.CLIENT_TO_SERVER);
DGLabManagerHolder.getServerManager().ifPresent(mgr -> mgr.sendToAll(pbm));
}
// ==================== Server Tab ====================
private void buildServerTab(WidgetGroup group) {
int y = 8;
group.addWidget(new ImageWidget(10, y, 60, 10,
new TextTexture(Component.translatable("gui.dglabgame.server.port").getString(), 0xffcccccc)));
TextFieldWidget portField = new TextFieldWidget(80, y, 60, 14, () -> "", s -> {});
portField.setCurrentString(String.valueOf(CommonConfig.SERVER_PORT.get()));
portField.setBordered(true);
portField.setId("server_port");
group.addWidget(portField);
y += 18;
group.addWidget(new ImageWidget(10, y, 60, 10,
new TextTexture(Component.translatable("gui.dglabgame.server.max_conn").getString(), 0xffcccccc)));
TextFieldWidget maxField = new TextFieldWidget(80, y, 60, 14, () -> "", s -> {});
maxField.setCurrentString(String.valueOf(CommonConfig.MAX_CONNECTIONS.get()));
maxField.setBordered(true);
maxField.setId("max_connections");
group.addWidget(maxField);
y += 20;
group.addWidget(new ButtonWidget(10, y, 60, 16, cd -> {
try {
TextFieldWidget pf = (TextFieldWidget) group.getFirstWidgetById("server_port");
TextFieldWidget mf = (TextFieldWidget) group.getFirstWidgetById("max_connections");
int port = Integer.parseInt(pf.getCurrentString());
int max = Integer.parseInt(mf.getCurrentString());
DLGNetworkHandler.CHANNEL.sendToServer(new UpdateServerConfigPacket(port, max));
} catch (NumberFormatException ignored) {}
}).setBackground(new ColorRectTexture(0xff336633)));
Optional<ServerManager> mgr = DGLabManagerHolder.getServerManager();
String statusText = mgr.map(m -> m.getStatus().name()).orElse("NO SERVER");
group.addWidget(new ImageWidget(80, y + 2, 120, 10,
new TextTexture(Component.translatable("gui.dglabgame.server.status", statusText).getString(), 0xffaaaaaa)));
y += 20;
group.addWidget(new ButtonWidget(10, y, 60, 16, cd -> {
Optional<ServerManager> sm = DGLabManagerHolder.getServerManager();
if (sm.isEmpty() || sm.get().getStatus() == Status.STOPPED) {
DGLabManagerHolder.getOrCreateServerManager().start();
}
}).setBackground(new ColorRectTexture(0xff226622)));
group.addWidget(new ButtonWidget(80, y, 60, 16, cd -> {
DGLabManagerHolder.getServerManager().ifPresent(ServerManager::stop);
}).setBackground(new ColorRectTexture(0xff662222)));
}
// ==================== Bottom Bar ====================
private WidgetGroup buildBottomBar() {
WidgetGroup bar = new WidgetGroup(5, H - 30, W - 10, 22);
bar.setBackground(ResourceBorderTexture.BORDERED_BACKGROUND);
bar.addWidget(new ImageWidget(10, 4, 40, 10,
new TextTexture(Component.translatable("gui.dglabgame.client.label").getString(), 0xffaaaaaa)));
var names = DGLabManagerHolder.getClientManager().getClientNames();
String current = names.isEmpty() ? "none" : names.iterator().next();
bar.addWidget(new ButtonWidget(55, 2, 60, 14, cd -> {
DGLabManagerHolder.getClientManager().getClient(current).ifPresent(DGPBClientManager::start);
}).setBackground(new ColorRectTexture(0xff226622)));
bar.addWidget(new ButtonWidget(120, 2, 60, 14, cd -> {
DGLabManagerHolder.getClientManager().getClient(current).ifPresent(DGPBClientManager::stop);
}).setBackground(new ColorRectTexture(0xff662222)));
return bar;
}
}

View File

@ -0,0 +1,39 @@
package top.leisuretimedock.dglabgame.config;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.ForgeConfigSpec;
@OnlyIn(Dist.CLIENT)
public class ClientConfig {
public static final ForgeConfigSpec SPEC;
public static final ForgeConfigSpec.BooleanValue ENABLE_SSL;
public static final ForgeConfigSpec.IntValue CONNECTION_TIMEOUT;
public static final ForgeConfigSpec.ConfigValue<java.util.List<? extends String>> FEEDBACK_PRESETS;
static {
ForgeConfigSpec.Builder builder = new ForgeConfigSpec.Builder();
builder.push("connection");
ENABLE_SSL = builder
.comment("Whether to use SSL/WSS for the WebSocket connection to the DGLab relay.",
"Requires a valid SSL certificate on the relay server side.")
.define("enableSSL", false);
CONNECTION_TIMEOUT = builder
.comment("Connection timeout in milliseconds for WebSocket handshake.")
.defineInRange("connectionTimeout", 5000, 1000, 30000);
builder.pop();
builder.push("feedback");
FEEDBACK_PRESETS = builder
.comment("Preset feedback message labels.",
"These appear as quick-select buttons in the management GUI.")
.defineList("presets",
java.util.List.of("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"),
o -> o instanceof String && !((String) o).isBlank());
builder.pop();
SPEC = builder.build();
}
}

View File

@ -0,0 +1,91 @@
package top.leisuretimedock.dglabgame.config;
import net.minecraftforge.common.ForgeConfigSpec;
public class CommonConfig {
public static final ForgeConfigSpec SPEC;
public static final ForgeConfigSpec.ConfigValue<String> ADDRESS;
public static final ForgeConfigSpec.IntValue PORT;
public static final ForgeConfigSpec.IntValue MAX_STRENGTH_A;
public static final ForgeConfigSpec.IntValue MAX_STRENGTH_B;
public static final ForgeConfigSpec.IntValue MIN_STRENGTH_A;
public static final ForgeConfigSpec.IntValue MIN_STRENGTH_B;
public static final ForgeConfigSpec.IntValue MAX_FREQUENCY;
public static final ForgeConfigSpec.IntValue MIN_FREQUENCY;
public static final ForgeConfigSpec.IntValue MAX_PULSE_STRENGTH;
public static final ForgeConfigSpec.IntValue ANTI_SHAKE_DELAY;
public static final ForgeConfigSpec.IntValue COUNTDOWN_SECONDS;
public static final ForgeConfigSpec.IntValue SERVER_PORT;
public static final ForgeConfigSpec.IntValue MAX_CONNECTIONS;
static {
ForgeConfigSpec.Builder builder = new ForgeConfigSpec.Builder();
builder.push("websocket");
ADDRESS = builder
.comment("WebSocket server address for the DGLab PowerBox relay.",
"Change this if the relay is running on a different machine.")
.define("address", "127.0.0.1",
o -> o instanceof String && !((String) o).isBlank());
PORT = builder
.comment("WebSocket server port for the DGLab PowerBox relay.")
.defineInRange("port", 9000, 1, 65535);
builder.pop();
builder.push("strength_limits");
MAX_STRENGTH_A = builder
.comment("Maximum allowed strength for channel A.")
.defineInRange("maxStrengthA", 200, 0, 200);
MAX_STRENGTH_B = builder
.comment("Maximum allowed strength for channel B.")
.defineInRange("maxStrengthB", 200, 0, 200);
MIN_STRENGTH_A = builder
.comment("Minimum allowed strength for channel A.")
.defineInRange("minStrengthA", 0, 0, 200);
MIN_STRENGTH_B = builder
.comment("Minimum allowed strength for channel B.")
.defineInRange("minStrengthB", 0, 0, 200);
builder.pop();
builder.push("pulse");
MAX_FREQUENCY = builder
.comment("Maximum pulse frequency in Hz.")
.defineInRange("maxFrequency", 240, 10, 240);
MIN_FREQUENCY = builder
.comment("Minimum pulse frequency in Hz.")
.defineInRange("minFrequency", 10, 10, 240);
MAX_PULSE_STRENGTH = builder
.comment("Maximum pulse strength (0-100).")
.defineInRange("maxPulseStrength", 100, 0, 100);
ANTI_SHAKE_DELAY = builder
.comment("Anti-shake debounce delay in milliseconds.",
"Prevents rapid repeated pulse triggers within this window.")
.defineInRange("antiShakeDelay", 500, 0, 5000);
builder.pop();
builder.push("agreement");
COUNTDOWN_SECONDS = builder
.comment("Countdown duration in seconds for the user agreement confirmation screen.",
"Set to 0 to allow instant confirmation (not recommended).")
.defineInRange("countdownSeconds", 5, 0, 60);
builder.pop();
builder.push("server");
SERVER_PORT = builder
.comment("Port for the DGLab WebSocket server to listen on.",
"Restart the server for changes to take effect.")
.defineInRange("serverPort", 9000, 1, 65535);
MAX_CONNECTIONS = builder
.comment("Maximum number of concurrent WebSocket connections allowed.",
"Additional connections beyond this limit will be rejected.")
.defineInRange("maxConnections", 128, 1, 1024);
builder.pop();
SPEC = builder.build();
}
}

View File

@ -1,15 +1,15 @@
package top.leisuretimedock.dglabgame.core.capability;
import net.minecraftforge.common.capabilities.Capability;
import top.r3944realms.lib39.core.sync.CachedSyncManager;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class DGLabDataSyncManager extends CachedSyncManager<UUID, AbstractDGLabData> {
public static Map<UUID, AbstractDGLabData> playerDungeonData = new HashMap<>();
public class DGLabDataSyncManager extends CachedSyncManager<Capability<AbstractDGLabData>, AbstractDGLabData> {
public static Map<Capability<AbstractDGLabData>, AbstractDGLabData> playerDungeonData = new HashMap<>();
@Override
public Map<UUID, AbstractDGLabData> getSyncMap() {
public Map<Capability<AbstractDGLabData>, AbstractDGLabData> getSyncMap() {
return playerDungeonData;
}
}

View File

@ -34,4 +34,8 @@ public final class EntityDGLabData extends AbstractDGLabData {
public int entityId() {
return entity.getId();
}
@Override
public void update() {
}
}

View File

@ -0,0 +1,410 @@
package top.leisuretimedock.dglabgame.core.command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.r3944realms.dg_lab.api.manager.Status;
import com.r3944realms.dg_lab.api.message.IPowerBoxMsg;
import com.r3944realms.dg_lab.api.message.argType.Channel;
import com.r3944realms.dg_lab.api.message.argType.ChangePolicy;
import com.r3944realms.dg_lab.api.message.data.PulseWaveListGenerator;
import com.r3944realms.dg_lab.api.websocket.message.MessageDirection;
import com.r3944realms.dg_lab.api.websocket.message.PowerBoxMessage;
import com.r3944realms.dg_lab.api.websocket.message.role.WebSocketClientRole;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.Component;
import top.leisuretimedock.dglabgame.config.CommonConfig;
import top.leisuretimedock.dglabgame.core.manager.ClientManager;
import top.leisuretimedock.dglabgame.core.manager.DGLabManagerHolder;
import top.leisuretimedock.dglabgame.core.manager.ServerManager;
import java.util.Optional;
public class DGLabCommands {
private static final String CLIENT_ID = "dglabgame";
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
dispatcher.register(
Commands.literal("dglab")
.requires(source -> source.hasPermission(2))
.then(serverNode())
.then(clientNode())
.then(strengthNode())
.then(pulseNode())
.then(clearNode())
.then(feedbackNode())
);
}
// ==================== Server Commands ====================
private static com.mojang.brigadier.builder.LiteralArgumentBuilder<CommandSourceStack> serverNode() {
return Commands.literal("server")
.then(Commands.literal("start")
.executes(ctx -> serverStart(ctx, CommonConfig.PORT.get()))
.then(Commands.argument("port", IntegerArgumentType.integer(1, 65535))
.executes(ctx -> serverStart(ctx, IntegerArgumentType.getInteger(ctx, "port")))
)
)
.then(Commands.literal("stop")
.executes(DGLabCommands::serverStop)
)
.then(Commands.literal("status")
.executes(DGLabCommands::serverStatus)
);
}
private static int serverStart(CommandContext<CommandSourceStack> ctx, int port) {
Optional<ServerManager> existing = DGLabManagerHolder.getServerManager();
if (existing.isPresent() && existing.get().getStatus() != Status.STOPPED) {
ctx.getSource().sendSuccess(
() -> Component.translatable("dglabgame.command.server.already_running"), false
);
return 0;
}
ServerManager mgr = DGLabManagerHolder.getOrCreateServerManager();
mgr.start();
ctx.getSource().sendSuccess(
() -> Component.translatable("dglabgame.command.server.started", port), true
);
return 1;
}
private static int serverStop(CommandContext<CommandSourceStack> ctx) {
Optional<ServerManager> existing = DGLabManagerHolder.getServerManager();
if (existing.isEmpty() || existing.get().getStatus() == Status.STOPPED) {
ctx.getSource().sendFailure(
Component.translatable("dglabgame.command.server.not_running")
);
return 0;
}
existing.get().stop();
ctx.getSource().sendSuccess(
() -> Component.translatable("dglabgame.command.server.stopped"), true
);
return 1;
}
private static int serverStatus(CommandContext<CommandSourceStack> ctx) {
Optional<ServerManager> existing = DGLabManagerHolder.getServerManager();
if (existing.isEmpty()) {
ctx.getSource().sendSuccess(
() -> Component.translatable("dglabgame.command.server.not_created"), false
);
} else {
ServerManager mgr = existing.get();
Status status = mgr.getStatus();
int clients = mgr.getConnectedClients().size();
ctx.getSource().sendSuccess(
() -> Component.translatable("dglabgame.command.server.status", status.name(), clients), false
);
}
return 1;
}
// ==================== Client Commands ====================
private static com.mojang.brigadier.builder.LiteralArgumentBuilder<CommandSourceStack> clientNode() {
return Commands.literal("client")
.then(Commands.literal("create")
.then(Commands.argument("name", StringArgumentType.word())
.executes(ctx -> clientCreate(ctx,
StringArgumentType.getString(ctx, "name"),
CommonConfig.ADDRESS.get(),
CommonConfig.PORT.get()))
.then(Commands.argument("address", StringArgumentType.word())
.executes(ctx -> clientCreate(ctx,
StringArgumentType.getString(ctx, "name"),
StringArgumentType.getString(ctx, "address"),
CommonConfig.PORT.get()))
.then(Commands.argument("port", IntegerArgumentType.integer(1, 65535))
.executes(ctx -> clientCreate(ctx,
StringArgumentType.getString(ctx, "name"),
StringArgumentType.getString(ctx, "address"),
IntegerArgumentType.getInteger(ctx, "port")))
)
)
)
)
.then(Commands.literal("remove")
.then(Commands.argument("name", StringArgumentType.word())
.executes(DGLabCommands::clientRemove)
)
)
.then(Commands.literal("start")
.then(Commands.argument("name", StringArgumentType.word())
.executes(DGLabCommands::clientStart)
)
)
.then(Commands.literal("stop")
.then(Commands.argument("name", StringArgumentType.word())
.executes(DGLabCommands::clientStop)
)
)
.then(Commands.literal("list")
.executes(DGLabCommands::clientList)
);
}
private static int clientCreate(CommandContext<CommandSourceStack> ctx, String name, String address, int port) {
ClientManager mgr = DGLabManagerHolder.getClientManager();
WebSocketClientRole role = WebSocketClientRole.of(CLIENT_ID + "_" + name);
mgr.createClient(name, role, address, port);
ctx.getSource().sendSuccess(
() -> Component.translatable("dglabgame.command.client.created", name, address, port), true
);
return 1;
}
private static int clientRemove(CommandContext<CommandSourceStack> ctx) {
String name = StringArgumentType.getString(ctx, "name");
ClientManager mgr = DGLabManagerHolder.getClientManager();
if (mgr.getClient(name).isEmpty()) {
ctx.getSource().sendFailure(
Component.translatable("dglabgame.command.client.not_found", name)
);
return 0;
}
mgr.removeClient(name);
ctx.getSource().sendSuccess(
() -> Component.translatable("dglabgame.command.client.removed", name), true
);
return 1;
}
private static int clientStart(CommandContext<CommandSourceStack> ctx) {
String name = StringArgumentType.getString(ctx, "name");
ClientManager mgr = DGLabManagerHolder.getClientManager();
Optional<com.r3944realms.dg_lab.manager.DGPBClientManager> client = mgr.getClient(name);
if (client.isEmpty()) {
ctx.getSource().sendFailure(
Component.translatable("dglabgame.command.client.not_found", name)
);
return 0;
}
client.get().start();
ctx.getSource().sendSuccess(
() -> Component.translatable("dglabgame.command.client.started", name), true
);
return 1;
}
private static int clientStop(CommandContext<CommandSourceStack> ctx) {
String name = StringArgumentType.getString(ctx, "name");
ClientManager mgr = DGLabManagerHolder.getClientManager();
Optional<com.r3944realms.dg_lab.manager.DGPBClientManager> client = mgr.getClient(name);
if (client.isEmpty()) {
ctx.getSource().sendFailure(
Component.translatable("dglabgame.command.client.not_found", name)
);
return 0;
}
client.get().stop();
ctx.getSource().sendSuccess(
() -> Component.translatable("dglabgame.command.client.stopped", name), true
);
return 1;
}
private static int clientList(CommandContext<CommandSourceStack> ctx) {
ClientManager mgr = DGLabManagerHolder.getClientManager();
var names = mgr.getClientNames();
if (names.isEmpty()) {
ctx.getSource().sendSuccess(
() -> Component.translatable("dglabgame.command.client.none"), false
);
} else {
String list = String.join(", ", names);
ctx.getSource().sendSuccess(
() -> Component.translatable("dglabgame.command.client.list", list), false
);
}
return 1;
}
// ==================== Device Commands ====================
private static com.mojang.brigadier.builder.LiteralArgumentBuilder<CommandSourceStack> strengthNode() {
return Commands.literal("strength")
.then(Commands.argument("channel", StringArgumentType.word())
.then(Commands.argument("policy", StringArgumentType.word())
.then(Commands.argument("value", IntegerArgumentType.integer(0, 200))
.executes(DGLabCommands::strengthExec)
)
)
);
}
private static int strengthExec(CommandContext<CommandSourceStack> ctx) {
Channel channel = parseChannel(StringArgumentType.getString(ctx, "channel"));
if (channel == null) {
ctx.getSource().sendFailure(Component.translatable("dglabgame.command.invalid_channel"));
return 0;
}
ChangePolicy policy = parsePolicy(StringArgumentType.getString(ctx, "policy"));
if (policy == null) {
ctx.getSource().sendFailure(Component.translatable("dglabgame.command.invalid_policy"));
return 0;
}
int value = IntegerArgumentType.getInteger(ctx, "value");
IPowerBoxMsg.StrengthChange msg = new IPowerBoxMsg.StrengthChange(channel, policy, value);
int sent = sendToAll(msg);
if (sent == 0) {
ctx.getSource().sendFailure(Component.translatable("dglabgame.command.no_output"));
return 0;
}
ctx.getSource().sendSuccess(
() -> Component.translatable("dglabgame.command.strength.sent", channel.name(), policy.name(), value, sent), true
);
return 1;
}
private static com.mojang.brigadier.builder.LiteralArgumentBuilder<CommandSourceStack> pulseNode() {
return Commands.literal("pulse")
.then(Commands.argument("channel", StringArgumentType.word())
.then(Commands.argument("f1", IntegerArgumentType.integer(10, 240))
.then(Commands.argument("s1", IntegerArgumentType.integer(0, 100))
.then(Commands.argument("f2", IntegerArgumentType.integer(10, 240))
.then(Commands.argument("s2", IntegerArgumentType.integer(0, 100))
.then(Commands.argument("f3", IntegerArgumentType.integer(10, 240))
.then(Commands.argument("s3", IntegerArgumentType.integer(0, 100))
.then(Commands.argument("f4", IntegerArgumentType.integer(10, 240))
.then(Commands.argument("s4", IntegerArgumentType.integer(0, 100))
.executes(DGLabCommands::pulseExec)
.then(Commands.argument("timer", IntegerArgumentType.integer(0, 60000))
.executes(DGLabCommands::pulseExec)
)
)
)
)
)
)
)
)
)
);
}
private static int pulseExec(CommandContext<CommandSourceStack> ctx) {
Channel channel = parseChannel(StringArgumentType.getString(ctx, "channel"));
if (channel == null) {
ctx.getSource().sendFailure(Component.translatable("dglabgame.command.invalid_channel"));
return 0;
}
int f1 = IntegerArgumentType.getInteger(ctx, "f1");
int s1 = IntegerArgumentType.getInteger(ctx, "s1");
int f2 = IntegerArgumentType.getInteger(ctx, "f2");
int s2 = IntegerArgumentType.getInteger(ctx, "s2");
int f3 = IntegerArgumentType.getInteger(ctx, "f3");
int s3 = IntegerArgumentType.getInteger(ctx, "s3");
int f4 = IntegerArgumentType.getInteger(ctx, "f4");
int s4 = IntegerArgumentType.getInteger(ctx, "s4");
var waveList = PulseWaveListGenerator.pulseWave(
new int[]{f1, f2, f3, f4},
new int[]{s1, s2, s3, s4}
);
Integer timer = null;
try {
timer = IntegerArgumentType.getInteger(ctx, "timer");
} catch (IllegalArgumentException ignored) {
}
IPowerBoxMsg.Pulse msg = new IPowerBoxMsg.Pulse(channel, waveList, timer);
int sent = sendToAll(msg);
if (sent == 0) {
ctx.getSource().sendFailure(Component.translatable("dglabgame.command.no_output"));
return 0;
}
ctx.getSource().sendSuccess(
() -> Component.translatable("dglabgame.command.pulse.sent", channel.name(), sent), true
);
return 1;
}
private static com.mojang.brigadier.builder.LiteralArgumentBuilder<CommandSourceStack> clearNode() {
return Commands.literal("clear")
.then(Commands.argument("channel", StringArgumentType.word())
.executes(DGLabCommands::clearExec)
);
}
private static int clearExec(CommandContext<CommandSourceStack> ctx) {
Channel channel = parseChannel(StringArgumentType.getString(ctx, "channel"));
if (channel == null) {
ctx.getSource().sendFailure(Component.translatable("dglabgame.command.invalid_channel"));
return 0;
}
IPowerBoxMsg.Clear msg = new IPowerBoxMsg.Clear(channel);
int sent = sendToAll(msg);
if (sent == 0) {
ctx.getSource().sendFailure(Component.translatable("dglabgame.command.no_output"));
return 0;
}
ctx.getSource().sendSuccess(
() -> Component.translatable("dglabgame.command.clear.sent", channel.name(), sent), true
);
return 1;
}
private static com.mojang.brigadier.builder.LiteralArgumentBuilder<CommandSourceStack> feedbackNode() {
return Commands.literal("feedback")
.then(Commands.argument("value", IntegerArgumentType.integer(0, 10))
.executes(DGLabCommands::feedbackExec)
);
}
private static int feedbackExec(CommandContext<CommandSourceStack> ctx) {
int value = IntegerArgumentType.getInteger(ctx, "value");
IPowerBoxMsg.Feedback msg = new IPowerBoxMsg.Feedback(value);
int sent = sendToAll(msg);
if (sent == 0) {
ctx.getSource().sendFailure(Component.translatable("dglabgame.command.no_output"));
return 0;
}
ctx.getSource().sendSuccess(
() -> Component.translatable("dglabgame.command.feedback.sent", value, sent), true
);
return 1;
}
// ==================== Send Helpers ====================
private static int sendToAll(IPowerBoxMsg msg) {
int sent = 0;
Optional<ServerManager> serverMgr = DGLabManagerHolder.getServerManager();
if (serverMgr.isPresent()) {
ServerManager mgr = serverMgr.get();
if (mgr.getStatus() == Status.RUNNING) {
PowerBoxMessage pbm = msg.toPowerBoxMessage(CLIENT_ID, CLIENT_ID,
MessageDirection.DirectType.SERVER_TO_CLIENT);
mgr.sendToAll(pbm);
sent += mgr.getConnectedClients().size();
}
}
return sent;
}
private static Channel parseChannel(String s) {
return switch (s.toLowerCase()) {
case "a" -> Channel.A;
case "b" -> Channel.B;
default -> null;
};
}
private static ChangePolicy parsePolicy(String s) {
return switch (s.toLowerCase()) {
case "increase", "inc", "+" -> ChangePolicy.INCREASE;
case "decrease", "dec", "-" -> ChangePolicy.DECREASE;
case "goto", "set", "=" -> ChangePolicy.GOTO;
default -> null;
};
}
}

View File

@ -16,6 +16,7 @@ import net.minecraftforge.fml.loading.FMLEnvironment;
import top.leisuretimedock.dglabgame.DGLabGame;
import top.leisuretimedock.dglabgame.client.DLGKeyMapping;
import top.leisuretimedock.dglabgame.client.gui.ConfirmGui;
import top.leisuretimedock.dglabgame.client.gui.DGLabManagementGui;
import top.leisuretimedock.dglabgame.core.capability.DGLabDataProvider;
public class ClientEventHandler {
@ -33,6 +34,15 @@ public class ClientEventHandler {
} else player.displayClientMessage(Component.literal("You had accept use agreements").withStyle(ChatFormatting.RED).withStyle(ChatFormatting.BOLD), true);
});
}
if (DLGKeyMapping.KEY_MANAGEMENT.isDown()) {
player.getCapability(DGLabDataProvider.DG_LAB_DATA_CAPABILITY).ifPresent(data -> {
if (data.hasAcceptUseAgreements()) {
new DGLabManagementGui().display();
} else {
player.displayClientMessage(Component.translatable("dglabgame.user_agreement.reject_use").withStyle(ChatFormatting.RED), true);
}
});
}
}
}
@net.minecraftforge.fml.common.Mod.EventBusSubscriber(modid = DGLabGame.MOD_ID, bus = net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
@ -40,6 +50,7 @@ public class ClientEventHandler {
@SubscribeEvent
public static void onRegisterKeyMappings (RegisterKeyMappingsEvent event) {
event.register(DLGKeyMapping.KEY_TEST);
event.register(DLGKeyMapping.KEY_MANAGEMENT);
}
}

View File

@ -4,10 +4,12 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent;
import net.minecraftforge.data.event.GatherDataEvent;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.event.RegisterCommandsEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import top.leisuretimedock.dglabgame.DGLabGame;
import top.leisuretimedock.dglabgame.core.capability.DGLabDataProvider;
import top.leisuretimedock.dglabgame.core.capability.DGLabDataSyncManager;
import top.leisuretimedock.dglabgame.core.command.DGLabCommands;
import top.leisuretimedock.dglabgame.core.register.DLGCapabilities;
import top.leisuretimedock.dglabgame.datagen.DLGDataGenEvent;
import top.r3944realms.lib39.api.event.SyncManagerRegisterEvent;
@ -41,5 +43,10 @@ public class CommonEventHandler {
public static void attachCapability(AttachCapabilitiesEvent<?> event) {
DLGCapabilities.attachCapability(event);
}
@SubscribeEvent
public static void onRegisterCommands(RegisterCommandsEvent event) {
DGLabCommands.register(event.getDispatcher());
}
}
}

View File

@ -0,0 +1,67 @@
package top.leisuretimedock.dglabgame.core.manager;
import com.r3944realms.dg_lab.api.websocket.message.role.WebSocketClientRole;
import com.r3944realms.dg_lab.manager.DGPBClientManager;
import com.r3944realms.dg_lab.websocket.PowerBoxWSClient;
import com.r3944realms.dg_lab.websocket.handler.client.DefaultClientOperation;
import com.r3944realms.dg_lab.websocket.sharedData.ClientPowerBoxSharedData;
import top.leisuretimedock.dglabgame.config.CommonConfig;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class ClientManager implements IManager<Map<String, DGPBClientManager>> {
private final Map<String, DGPBClientManager> clients = new HashMap<>();
public DGPBClientManager createClient(String key, WebSocketClientRole role) {
return createClient(key, role, CommonConfig.ADDRESS.get(), CommonConfig.PORT.get());
}
public DGPBClientManager createClient(String key, WebSocketClientRole role, String address, int port) {
ClientPowerBoxSharedData sharedData = new ClientPowerBoxSharedData(
CommonConfig.ANTI_SHAKE_DELAY.get()
);
sharedData.address = address;
sharedData.port = port;
PowerBoxWSClient wsClient = new PowerBoxWSClient(
sharedData, role, new DefaultClientOperation(),
address, port
);
DGPBClientManager client = new DGPBClientManager(wsClient);
clients.putIfAbsent(key, client);
return client;
}
public java.util.Set<String> getClientNames() {
return clients.keySet();
}
public void addClient(String key, DGPBClientManager client) {
clients.putIfAbsent(key, client);
}
public void removeClient(String key) {
Optional.ofNullable(clients.remove(key)).ifPresent(DGPBClientManager::stop);
}
public Optional<DGPBClientManager> getClient(String key) {
return Optional.ofNullable(clients.get(key));
}
@Override
public void startAll() {
clients.values().forEach(DGPBClientManager::start);
}
@Override
public void stopAll() {
clients.values().forEach(DGPBClientManager::stop);
}
@Override
public Optional<Map<String, DGPBClientManager>> getInstance() {
return Optional.of(clients);
}
}

View File

@ -0,0 +1,44 @@
package top.leisuretimedock.dglabgame.core.manager;
import com.r3944realms.dg_lab.api.websocket.message.role.WebSocketServerRole;
import com.r3944realms.dg_lab.manager.DGPBServerManager;
import com.r3944realms.dg_lab.websocket.PowerBoxWSServer;
import com.r3944realms.dg_lab.websocket.handler.server.DefaultServerOperation;
import com.r3944realms.dg_lab.websocket.sharedData.ServerPowerBoxSharedData;
import top.leisuretimedock.dglabgame.config.CommonConfig;
import java.util.Optional;
public class DGLabManagerHolder {
private static ServerManager serverManager;
private static final ClientManager clientManager = new ClientManager();
public static ClientManager getClientManager() {
return clientManager;
}
public static Optional<ServerManager> getServerManager() {
return Optional.ofNullable(serverManager);
}
public static ServerManager getOrCreateServerManager() {
if (serverManager == null) {
ServerPowerBoxSharedData sharedData = new ServerPowerBoxSharedData();
WebSocketServerRole role = WebSocketServerRole.of("dglabgame-server");
PowerBoxWSServer server = new PowerBoxWSServer(
sharedData, role, new DefaultServerOperation(),
CommonConfig.SERVER_PORT.get()
);
DGPBServerManager dgpbServerManager = new DGPBServerManager(server);
serverManager = new ServerManager(dgpbServerManager);
}
return serverManager;
}
public static void setServerManager(ServerManager manager) {
if (serverManager != null) {
serverManager.stopAll();
}
serverManager = manager;
}
}

View File

@ -0,0 +1,9 @@
package top.leisuretimedock.dglabgame.core.manager;
import java.util.Optional;
public interface IManager<T> {
void startAll();
void stopAll();
Optional<T> getInstance();
}

View File

@ -0,0 +1,75 @@
package top.leisuretimedock.dglabgame.core.manager;
import com.r3944realms.dg_lab.api.manager.Status;
import com.r3944realms.dg_lab.api.websocket.message.Message;
import com.r3944realms.dg_lab.api.websocket.sharedData.ISharedData;
import com.r3944realms.dg_lab.manager.DGPBServerManager;
import com.r3944realms.dg_lab.manager.IDGLabManager;
import com.r3944realms.dg_lab.websocket.sharedData.ServerPowerBoxSharedData;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
import java.util.Set;
public class ServerManager implements IManager<DGPBServerManager>, IDGLabManager {
private final DGPBServerManager serverManager;
public ServerManager(@NotNull DGPBServerManager serverManager) {
this.serverManager = serverManager;
}
@Override
public void start() {
serverManager.start();
}
@Override
public void stop() {
serverManager.stop();
}
@Override
public ISharedData getSharedData() {
return serverManager.getSharedData();
}
@Override
public Status getStatus() {
return serverManager.getStatus();
}
@Override
public void setStatus(Status status) {
serverManager.setStatus(status);
}
@Override
public void startAll() {
start();
}
@Override
public void stopAll() {
stop();
}
@Override
public Optional<DGPBServerManager> getInstance() {
return Optional.of(serverManager);
}
public void sendToAll(Message message) {
ISharedData sd = serverManager.getSharedData();
if (sd instanceof ServerPowerBoxSharedData spsd) {
spsd.connections.keySet().forEach(id -> serverManager.send(id, message));
}
}
public Set<String> getConnectedClients() {
ISharedData sd = serverManager.getSharedData();
if (sd instanceof ServerPowerBoxSharedData spsd) {
return spsd.connections.keySet();
}
return Set.of();
}
}

View File

@ -5,6 +5,8 @@ import net.minecraftforge.network.NetworkRegistry;
import net.minecraftforge.network.simple.SimpleChannel;
import top.leisuretimedock.dglabgame.DGLabGame;
import top.leisuretimedock.dglabgame.core.network.toServer.AcceptUserAgreementPacket;
import top.leisuretimedock.dglabgame.core.network.toServer.UpdateServerConfigPacket;
import top.leisuretimedock.dglabgame.core.network.toServer.UpdateStrengthConfigPacket;
import java.util.concurrent.atomic.AtomicInteger;
@ -22,6 +24,16 @@ public class DLGNetworkHandler {
.decoder(AcceptUserAgreementPacket::decode)
.consumerMainThread(AcceptUserAgreementPacket::handle)
.add();
CHANNEL.messageBuilder(UpdateServerConfigPacket.class, getIndex(), NetworkDirection.PLAY_TO_SERVER)
.encoder(UpdateServerConfigPacket::encode)
.decoder(UpdateServerConfigPacket::decode)
.consumerMainThread(UpdateServerConfigPacket::handle)
.add();
CHANNEL.messageBuilder(UpdateStrengthConfigPacket.class, getIndex(), NetworkDirection.PLAY_TO_SERVER)
.encoder(UpdateStrengthConfigPacket::encode)
.decoder(UpdateStrengthConfigPacket::decode)
.consumerMainThread(UpdateStrengthConfigPacket::handle)
.add();
}
public static int getIndex() {

View File

@ -0,0 +1,33 @@
package top.leisuretimedock.dglabgame.core.network.toServer;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkEvent;
import org.jetbrains.annotations.NotNull;
import top.leisuretimedock.dglabgame.config.CommonConfig;
import java.util.function.Supplier;
public record UpdateServerConfigPacket(int serverPort, int maxConnections) {
public void encode(FriendlyByteBuf buf) {
buf.writeVarInt(serverPort);
buf.writeVarInt(maxConnections);
}
public static UpdateServerConfigPacket decode(FriendlyByteBuf buf) {
return new UpdateServerConfigPacket(buf.readVarInt(), buf.readVarInt());
}
public void handle(@NotNull Supplier<NetworkEvent.Context> contextSupplier) {
NetworkEvent.Context context = contextSupplier.get();
context.enqueueWork(() -> {
ServerPlayer sender = context.getSender();
if (sender != null && sender.hasPermissions(4)) {
CommonConfig.SERVER_PORT.set(serverPort);
CommonConfig.MAX_CONNECTIONS.set(maxConnections);
CommonConfig.SPEC.save();
}
context.setPacketHandled(true);
});
}
}

View File

@ -0,0 +1,37 @@
package top.leisuretimedock.dglabgame.core.network.toServer;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkEvent;
import org.jetbrains.annotations.NotNull;
import top.leisuretimedock.dglabgame.config.CommonConfig;
import java.util.function.Supplier;
public record UpdateStrengthConfigPacket(int maxA, int minA, int maxB, int minB) {
public void encode(FriendlyByteBuf buf) {
buf.writeVarInt(maxA);
buf.writeVarInt(minA);
buf.writeVarInt(maxB);
buf.writeVarInt(minB);
}
public static UpdateStrengthConfigPacket decode(FriendlyByteBuf buf) {
return new UpdateStrengthConfigPacket(buf.readVarInt(), buf.readVarInt(), buf.readVarInt(), buf.readVarInt());
}
public void handle(@NotNull Supplier<NetworkEvent.Context> contextSupplier) {
NetworkEvent.Context context = contextSupplier.get();
context.enqueueWork(() -> {
ServerPlayer sender = context.getSender();
if (sender != null) {
CommonConfig.MAX_STRENGTH_A.set(maxA);
CommonConfig.MIN_STRENGTH_A.set(minA);
CommonConfig.MAX_STRENGTH_B.set(maxB);
CommonConfig.MIN_STRENGTH_B.set(minB);
CommonConfig.SPEC.save();
}
context.setPacketHandled(true);
});
}
}

View File

@ -61,6 +61,15 @@ public enum DLGLangKey implements ILangKeyValueCollection {
)
);
addLang(
LangKeyValue.ofKey("dglabgame.user_agreement.reject_use", ModPartEnum.NAME,
"Not agreed to user guidelines, usage rejected",
"未同意用户使用准则,拒绝使用",
"未同意使用者準則,拒絕使用",
"未允用戶之約,拒用"
)
);
addLang(
LangKeyValue.ofKey("dglabgame.user_agreement.line1", ModPartEnum.NAME,
"1. This mod is developed by §e§lR3944Realms §r, completely free and open source.",
@ -139,6 +148,183 @@ public enum DLGLangKey implements ILangKeyValueCollection {
// 6. 开发者不对任何人身伤害或财产损失负责
// 6. Developers are not responsible for any personal injury or property damage.
initCommandKeys();
}
private void initCommandKeys() {
// Server commands
addLang(LangKeyValue.ofKey("dglabgame.command.server.started", ModPartEnum.NAME,
"DGLab WebSocket server started on port %s",
"DGLab WebSocket 服务器已在端口 %s 启动",
"DGLab WebSocket 伺服器已在連接埠 %s 啟動",
"DGLab WebSocket 伺服器已於埠 %s 啟"
));
addLang(LangKeyValue.ofKey("dglabgame.command.server.already_running", ModPartEnum.NAME,
"DGLab server is already running",
"DGLab 服务器已在运行中",
"DGLab 伺服器已在執行中",
"DGLab 伺服器已在運作中"
));
addLang(LangKeyValue.ofKey("dglabgame.command.server.stopped", ModPartEnum.NAME,
"DGLab server stopped",
"DGLab 服务器已关闭",
"DGLab 伺服器已關閉",
"DGLab 伺服器已停"
));
addLang(LangKeyValue.ofKey("dglabgame.command.server.not_running", ModPartEnum.NAME,
"DGLab server is not running",
"DGLab 服务器未在运行",
"DGLab 伺服器未在執行",
"DGLab 伺服器未啟"
));
addLang(LangKeyValue.ofKey("dglabgame.command.server.not_created", ModPartEnum.NAME,
"DGLab server has not been created yet",
"DGLab 服务器尚未创建",
"DGLab 伺服器尚未創建",
"DGLab 伺服器尚未建"
));
addLang(LangKeyValue.ofKey("dglabgame.command.server.status", ModPartEnum.NAME,
"Server status: %s, connected apps: %s",
"服务器状态: %s, 已连接应用: %s",
"伺服器狀態: %s, 已連線應用: %s",
"伺服器態: %s, 已連應用: %s"
));
// Client commands
addLang(LangKeyValue.ofKey("dglabgame.command.client.created", ModPartEnum.NAME,
"DGLab client \"%s\" created (%s:%s)",
"DGLab 客户端 \"%s\" 已创建 (%s:%s)",
"DGLab 客戶端 \"%s\" 已創建 (%s:%s)",
"DGLab 客戶 \"%s\" 已建 (%s:%s)"
));
addLang(LangKeyValue.ofKey("dglabgame.command.client.removed", ModPartEnum.NAME,
"DGLab client \"%s\" removed",
"DGLab 客户端 \"%s\" 已移除",
"DGLab 客戶端 \"%s\" 已移除",
"DGLab 客戶 \"%s\" 已除"
));
addLang(LangKeyValue.ofKey("dglabgame.command.client.started", ModPartEnum.NAME,
"DGLab client \"%s\" started",
"DGLab 客户端 \"%s\" 已启动",
"DGLab 客戶端 \"%s\" 已啟動",
"DGLab 客戶 \"%s\" 已啟"
));
addLang(LangKeyValue.ofKey("dglabgame.command.client.stopped", ModPartEnum.NAME,
"DGLab client \"%s\" stopped",
"DGLab 客户端 \"%s\" 已关闭",
"DGLab 客戶端 \"%s\" 已關閉",
"DGLab 客戶 \"%s\" 已停"
));
addLang(LangKeyValue.ofKey("dglabgame.command.client.not_found", ModPartEnum.NAME,
"Client \"%s\" not found",
"未找到客户端 \"%s\"",
"未找到客戶端 \"%s\"",
"未見客戶 \"%s\""
));
addLang(LangKeyValue.ofKey("dglabgame.command.client.none", ModPartEnum.NAME,
"No DGLab clients registered",
"没有已注册的 DGLab 客户端",
"沒有已註冊的 DGLab 客戶端",
"無已註之 DGLab 客戶"
));
addLang(LangKeyValue.ofKey("dglabgame.command.client.list", ModPartEnum.NAME,
"Registered clients: %s",
"已注册的客户端: %s",
"已註冊的客戶端: %s",
"已註客戶: %s"
));
// Device commands
addLang(LangKeyValue.ofKey("dglabgame.command.invalid_channel", ModPartEnum.NAME,
"Invalid channel. Use A or B.",
"无效的通道。请使用 A 或 B。",
"無效的通道。請使用 A 或 B。",
"通道有誤,當用 A 或 B。"
));
addLang(LangKeyValue.ofKey("dglabgame.command.invalid_policy", ModPartEnum.NAME,
"Invalid policy. Use goto/set, increase/inc, or decrease/dec.",
"无效的策略。请使用 goto/set, increase/inc 或 decrease/dec。",
"無效的策略。請使用 goto/set, increase/inc 或 decrease/dec。",
"策略有誤,當用 goto/set、increase/inc 或 decrease/dec。"
));
addLang(LangKeyValue.ofKey("dglabgame.command.no_output", ModPartEnum.NAME,
"No active DGLab output available. Start the server or a client first.",
"没有可用的 DGLab 输出。请先启动服务器或客户端。",
"沒有可用的 DGLab 輸出。請先啟動伺服器或客戶端。",
"無可用之 DGLab 輸出。請先啟伺服器或客戶。"
));
addLang(LangKeyValue.ofKey("dglabgame.command.strength.sent", ModPartEnum.NAME,
"Strength [%s %s %s] sent to %s target(s)",
"强度 [%s %s %s] 已发送至 %s 个目标",
"強度 [%s %s %s] 已傳送至 %s 個目標",
"強度 [%s %s %s] 已送至 %s 標的"
));
addLang(LangKeyValue.ofKey("dglabgame.command.pulse.sent", ModPartEnum.NAME,
"Pulse waveform sent to channel %s (%s target(s))",
"脉冲波形已发送至通道 %s (%s 个目标)",
"脈衝波形已傳送至通道 %s (%s 個目標)",
"脈衝波形已送至通道 %s (%s 標的)"
));
addLang(LangKeyValue.ofKey("dglabgame.command.clear.sent", ModPartEnum.NAME,
"Clear command sent for channel %s (%s target(s))",
"清除指令已发送至通道 %s (%s 个目标)",
"清除指令已傳送至通道 %s (%s 個目標)",
"清除之令已送至通道 %s (%s 標的)"
));
addLang(LangKeyValue.ofKey("dglabgame.command.feedback.sent", ModPartEnum.NAME,
"Feedback value %s sent to %s target(s)",
"反馈值 %s 已发送至 %s 个目标",
"回饋值 %s 已傳送至 %s 個目標",
"回應值 %s 已送至 %s 標的"
));
// Management GUI
addLang(LangKeyValue.ofKey("key.dglabgame.management", ModPartEnum.NAME,
"Open Management", "打开管理", "開啟管理", "啟管理"
));
addLang(LangKeyValue.ofKey("gui.dglabgame.tab.qrcode", ModPartEnum.NAME,
"QR Code", "二维码", "QR碼", "二維碼"
));
addLang(LangKeyValue.ofKey("gui.dglabgame.tab.strength", ModPartEnum.NAME,
"Strength", "强度", "強度", "強度"
));
addLang(LangKeyValue.ofKey("gui.dglabgame.tab.waveform", ModPartEnum.NAME,
"Waveform", "波形", "波形", "波形"
));
addLang(LangKeyValue.ofKey("gui.dglabgame.tab.feedback", ModPartEnum.NAME,
"Feedback", "反馈", "回饋", "回應"
));
addLang(LangKeyValue.ofKey("gui.dglabgame.tab.server", ModPartEnum.NAME,
"Server", "服务器", "伺服器", "伺服器"
));
addLang(LangKeyValue.ofKey("gui.dglabgame.qrcode.copy", ModPartEnum.NAME,
"Copy QR URL to clipboard", "复制QR URL到剪贴板", "複製QR URL到剪貼板", "複QR URL"
));
addLang(LangKeyValue.ofKey("gui.dglabgame.strength.channel_a", ModPartEnum.NAME,
"Channel A", "通道A", "通道A", "通道甲"
));
addLang(LangKeyValue.ofKey("gui.dglabgame.strength.channel_b", ModPartEnum.NAME,
"Channel B", "通道B", "通道B", "通道乙"
));
addLang(LangKeyValue.ofKey("gui.dglabgame.waveform.channel", ModPartEnum.NAME,
"Channel:", "通道:", "通道:", "通道:"
));
addLang(LangKeyValue.ofKey("gui.dglabgame.feedback.send", ModPartEnum.NAME,
"Send Feedback:", "发送反馈:", "發送回饋:", "發回應:"
));
addLang(LangKeyValue.ofKey("gui.dglabgame.server.port", ModPartEnum.NAME,
"Port:", "端口:", "連接埠:", "埠:"
));
addLang(LangKeyValue.ofKey("gui.dglabgame.server.max_conn", ModPartEnum.NAME,
"Max:", "最大:", "最大:", "至多:"
));
addLang(LangKeyValue.ofKey("gui.dglabgame.server.status", ModPartEnum.NAME,
"Status: %s", "状态: %s", "狀態: %s", "態: %s"
));
addLang(LangKeyValue.ofKey("gui.dglabgame.client.label", ModPartEnum.NAME,
"Client:", "客户端:", "客戶端:", "客戶:"
));
}
@Contract(pure = true)
@Override