diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 6abb835..d8e5ba3 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -11,18 +11,18 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Validate Gradle Wrapper - uses: gradle/wrapper-validation-action@v2 + uses: gradle/actions/wrapper-validation@v5 - name: Setup Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'microsoft' java-version: 21 - name: Build run: ./gradlew build - name: Upload Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: Artifact path: build/libs diff --git a/README.md b/README.md index aed9271..b638af6 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,45 @@ # Fabric Coral +English | [中文](README_zh.md) + [![GitHub Action](https://github.com/UnknownBuild/Coral/workflows/Build/badge.svg)](https://github.com/UnknownBuild/Coral/actions/workflows/main.yaml) [![GitHub Release](https://img.shields.io/github/v/release/UnknownBuild/Coral)](https://github.com/UnknownBuild/Coral/releases) -Coral 是 我的世界(Minecraft) 的一个工具及扩展模组,目前仅提供 Fabric 版本。 +> English version is translated by tool. + +Coral is a tool and extension mod for Minecraft, currently only available for Fabric version. ![coral](src/main/resources/assets/coral/icon.png) -## 功能 +## Feature -Coral 为生存游戏提供了一系列工具与扩展,在不修改原版生存玩法的同时提供辅助功能,比如 +Coral provides a series of tools and extensions for survival games, without modifying the vanilla survival game mode, such as -- 命令扩展:here(自身高亮)、player(玩家命令增强)、wru(询问位置); -- 辅助特性:睡觉提示、死亡提示等。 +- Command Extensions: here(self highlight), player(player command enhancement), wru(ask for location); +- Auxiliary Features: sleep reminder, death reminder, etc. -Coral 适用于客户端版本和服务端版本,但部分特性仅生效于客户端或服务端。 +Coral is applying for both client and server side, but some features only apply for client of server side. -## 安装与使用 +## Install and use -Coral 模组的工作依赖于 Fabric。 +Coral relies on Fabric. -请阅读 [https://fabricmc.net/use/](https://fabricmc.net/use/) 安装好 Fabric,然后下载对应 Minecraft 版本的 Coral 到 mods 文件夹。 +Please read [https://fabricmc.net/use/](https://fabricmc.net/use/), install Fabric and download the corresponding version of Coral for Minecraft to folder mods. -以下为各个 Minecraft 版本的 Coral 最新版本和维护情况,更多版本可前往 [Release](https://github.com/UnknownBuild/Coral/releases/) 下载。 +The followings are the latest versions and maintenance status of Coral for various Minecraft version. For more versions, please visit [Release](https://github.com/UnknownBuild/Coral/releases/) and download. -| Minecraft 版本 | Coral 最新版本 | 状态 | -|:------------:|:-----------------------------------------------------------------:|:----:| -| 1.21.x | v0.3.0 | 维护中 | -| 1.20.x 及更低版本 | [v0.2.2](https://github.com/UnknownBuild/Coral/releases/tag/v0.x) | 不再维护 | +| Minecraft Version | Coral Latest Version | Status | +|:--:|:--:|:--:| +| 1.21.9+ | [v0.4.0](https://github.com/UnknownBuild/Coral/releases/tag/v0.4.0) | Under Maintained | +| 1.21 ~ 1.21.8 | [v0.3.0](https://github.com/UnknownBuild/Coral/releases/tag/v0.3.0) | No Longer Maintained | +| 1.20.x and lower versions | [v0.2.2](https://github.com/UnknownBuild/Coral/releases/tag/v0.x) | No Longer Maintained | -Coral 可以通过配置文件启用或关闭特定功能,关于 Coral 的配置请阅读 [Coral配置指南](https://github.com/UnknownBuild/Coral/blob/master/docs/config_zh.md) 。 +Coral can enable or disable specific features through config file. For the information on Coral configuration, please refer to [Coral Configuration Guides](https://github.com/UnknownBuild/Coral/blob/master/docs/config.md). -## 问题与建议 +## Questions and suggestions -如果您对于 Coral 有任何问题反馈、建议或特性请求,请通过 [Issues](https://github.com/UnknownBuild/Coral/issues) 提交相关内容。 +If you have any feedback, suggestions or feature requests, please contact use in [Issues](https://github.com/UnknownBuild/Coral/issues). -## 许可协议 +## License -Coral 模组基于 [MIT License](https://github.com/UnknownBuild/Coral/blob/master/LICENSE) 开放源码。 +Based on [MIT License](https://github.com/UnknownBuild/Coral/blob/master/LICENSE). diff --git a/README_zh.md b/README_zh.md new file mode 100644 index 0000000..5c0be6e --- /dev/null +++ b/README_zh.md @@ -0,0 +1,43 @@ +# Fabric Coral + +[English](README.md) | 中文 + +[![GitHub Action](https://github.com/UnknownBuild/Coral/workflows/Build/badge.svg)](https://github.com/UnknownBuild/Coral/actions/workflows/main.yaml) +[![GitHub Release](https://img.shields.io/github/v/release/UnknownBuild/Coral)](https://github.com/UnknownBuild/Coral/releases) + +Coral 是 我的世界(Minecraft) 的一个工具及扩展模组,目前仅提供 Fabric 版本。 + +![coral](src/main/resources/assets/coral/icon.png) + +## 功能 + +Coral 为生存游戏提供了一系列工具与扩展,在不修改原版生存玩法的同时提供辅助功能,比如 + +- 命令扩展:here(自身高亮)、player(玩家命令增强)、wru(询问位置); +- 辅助特性:睡觉提示、死亡提示等。 + +Coral 适用于客户端版本和服务端版本,但部分特性仅生效于客户端或服务端。 + +## 安装与使用 + +Coral 模组的工作依赖于 Fabric。 + +请阅读 [https://fabricmc.net/use/](https://fabricmc.net/use/) 安装好 Fabric,然后下载对应 Minecraft 版本的 Coral 到 mods 文件夹。 + +以下为各个 Minecraft 版本的 Coral 最新版本和维护情况,更多版本可前往 [Release](https://github.com/UnknownBuild/Coral/releases/) 下载。 + +| Minecraft 版本 | Coral 最新版本 | 状态 | +|:--:|:--:|:--:| +| 1.21.9+ | [v0.4.0](https://github.com/UnknownBuild/Coral/releases/tag/v0.4.0) | 维护中 | +| 1.21 ~ 1.21.8 | [v0.3.0](https://github.com/UnknownBuild/Coral/releases/tag/v0.3.0) | 不再维护 | +| 1.20.x 及更低版本 | [v0.2.2](https://github.com/UnknownBuild/Coral/releases/tag/v0.x) | 不再维护 | + +Coral 可以通过配置文件启用或关闭特定功能,关于 Coral 的配置请阅读 [Coral配置指南](https://github.com/UnknownBuild/Coral/blob/master/docs/config_zh.md) 。 + +## 问题与建议 + +如果您对于 Coral 有任何问题反馈、建议或特性请求,请通过 [Issues](https://github.com/UnknownBuild/Coral/issues) 提交相关内容。 + +## 许可协议 + +Coral 模组基于 [MIT License](https://github.com/UnknownBuild/Coral/blob/master/LICENSE) 开放源码。 diff --git a/build.gradle b/build.gradle index f0c126e..9fcae2b 100644 --- a/build.gradle +++ b/build.gradle @@ -3,13 +3,13 @@ plugins { id 'maven-publish' } -sourceCompatibility = JavaVersion.VERSION_21 -targetCompatibility = JavaVersion.VERSION_21 - -archivesBaseName = project.archives_base_name -version = "${project.mod_version}+${project.minecraft_version}" as Object +version = "${project.mod_version}+${project.minecraft_version}" group = project.maven_group +base { + archivesName = project.archives_base_name +} + repositories { maven { url 'https://maven.aliyun.com/nexus/content/groups/public' @@ -24,19 +24,13 @@ dependencies { minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - - // Fabric API - Set apiModules = ["fabric-api-base", "fabric-command-api-v2"] - apiModules.forEach { - include(modImplementation(fabricApi.module(it, project.fabric_version))) - } } processResources { inputs.property "version", project.version filesMatching("fabric.mod.json") { - expand "version": project.version + expand "version": inputs.properties.version } } @@ -49,18 +43,24 @@ java { // if it is present. // If you remove this line, sources will not be generated. withSourcesJar() + + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } jar { + inputs.property "archivesName", project.base.archivesName + from("LICENSE") { - rename { "${it}_${project.archivesBaseName}" } + rename { "${it}_${inputs.properties.archivesName}"} } } // configure the maven publication publishing { publications { - mavenJava(MavenPublication) { + create("mavenJava", MavenPublication) { + artifactId = project.archives_base_name from components.java } } diff --git a/docs/config.md b/docs/config.md new file mode 100644 index 0000000..2526b86 --- /dev/null +++ b/docs/config.md @@ -0,0 +1,150 @@ +# Coral Configuration Guides + +English | [中文](config_zh.md) + +> English version is translated by tool. + +Coral configuration file is written by Java Properties. If you are not familiar with Properties, we also provides some explanations below. The configuration file for Minecraft server also uses the Properties file. + +## About configuration files + +### Configuration path + +The configuration file name for Coral must be coral.properties, and mod will read the configuration from the following path. + +``` +coral.properties +config/coral.properties +configs/coral.properties +``` + +The module requires only one configuration file. If there ere multiple configuration files in the above path, mod will not be able to start. If there are no custom configuration file, mod will use the default configuration. + +### Configuration format + +The configuration file is written by Java Properties, in the following format. + +```properties +# Comments starting with sign '#', it can be used to write some text for recording or prompting purposes. +# All configurations exist in the form of =, where key is the configuration item and value is the value, for example +game=Minecraft +player.id=1 +player.is_online=true +player.name=MegaShow +``` + +### Configuration References + +Coral provides two preset configurations: default configuration and empty configuration. + +- default: default configuration, enable most of Coral features. +- empty: empty configurations, disable all Coral features. + +When using Coral, you can specify reference configurations through use keyword, and then add individual features according to your own needs. + +```properties +use=empty +feature.xxx=true +``` + +### Translation and style + +Most of Coral features require to display text information to players, and you can modify the language of text through configuration file. + +```properties +language=en_us +``` + +Currently, Coral supports Simplified Chinese and English, default with English. + +| Lanugage | Code | +|:----:|:-----:| +| Simplified Chinese | zh_cn | +| English | en_us | + +If you are not satisfied with the language support, you can customize the language file. + +```properties +language=zh_cn +language.path=config/lang.json +``` + +The above configuration will prioritize reading text information from config/lang.json. If it does not exist in the file, the default text will be read. Coral's default language file can be found in [sources](https://github.com/UnknownBuild/Coral/tree/master/src/main/resources/assets/coral/lang). + +Coral uses the same style schema for different languages. If you are not satisfied with the default style, you can customize the style file. + +```properties +style.path=config/style.json +``` + +Coral's default style file can be found in [sources](https://github.com/UnknownBuild/Coral/tree/master/src/main/resources/assets/coral/style.json). + +## Command supports + +### here + +I am here. Give players their own highlight effect and broadcast their location to the world. + +``` +/here +``` + +This command only works on dedicated server or LAN mode, not support for single player mode. + +| Configuration Item | Type | Default Value | Description | +|-----------------------|-----|------|-----------| +| command.here | boolean | true | enable this command | +| command.here.duration | integer | 30 | higlight effect duration (seconds) | + +### player + +Enhances player command, provides richer querying features. + +After enables this feature, the server will create file coral_player.data in folder world/data. This file is used to store player online information and is only used to query command. Even if the file is deleted, it will not affect gaming. + +``` +/player list # List current online players, same as /list uuids, but supports clicks to copy uuid +/player listall # List all player on this server, some player's names will not be display +``` + +listall command requires the executing player to have level 3 or higher permissions. + +| Configuration Item | Type | Default Value | Description | +|----------------|-----|------|---------| +| command.player | boolean | true | enable this command | + +### wru + +Where are you? Ask about player's location. + +``` +/wru +``` + +This command only works on dedicated server or LAN mode, not support for single player mode. + +| Configuration Item | Type | Default Value | Description | +|-------------|-----|------|---------| +| command.wru | boolean | true | enable this command | + +## Feature supports + +### call_sleep + +Sleep now. When a player enters sleep status, broadcast a sleep request to the world to prompt other players to sleep. + +This command only works on dedicated server or LAN mode, not support for single player mode. + +| Configuration Item | Type | Default Value | Description | +|--------------------|-----|------|---------| +| feature.call_sleep | boolean | true | enable this feature | + +### death_info + +Come and pick up your things. When a player dies, broadcast the death location to the world. + +This command works on all modes. + +| Configuration Item | Type | Default Value | Description | +|--------------------|-----|------|---------| +| feature.death_info | boolean | true | enable this feature | diff --git a/docs/config_zh.md b/docs/config_zh.md index eef788c..a1b59a8 100644 --- a/docs/config_zh.md +++ b/docs/config_zh.md @@ -1,5 +1,7 @@ # Coral 配置指南 +[English](config.md) | 中文 + Coral 的配置文件使用 Java Properties 配置约定编写,如果你不了解 Properties,下面我们也对此做了一些说明。Minecraft 服务器的配置文件也是采用 Properties 文件。 ## 关于配置文件 @@ -51,12 +53,12 @@ Coral 大多数功能需要向玩家们显示文本信息,通过配置文件 language=zh_cn ``` -目前 Coral 支持简体中文和英文,默认为简体中文。 +目前 Coral 支持简体中文和英文,默认为英文。 -| 语言 | 代码 | -| :------: |:-----:| +| 语言 | 代码 | +|:----:|:-----:| | 简体中文 | zh_cn | -| 英文 | en_us | +| 英文 | en_us | 如果您对语言的支持不满意,可以自定义编写语言文件。 @@ -65,7 +67,7 @@ language=zh_cn language.path=config/lang.json ``` -以上配置将优先从 config/lang.json 读取文本信息,如果该文件中不存在,再根据 language 字段读取简体中文默认文本。Coral的预设语言文件在 [源码](https://github.com/UnknownBuild/Coral/tree/master/src/main/resources/assets/coral/lang) 中可查看。 +以上配置将优先从 config/lang.json 读取文本信息,如果该文件中不存在,再根据 language 字段读取默认文本。Coral的预设语言文件在 [源码](https://github.com/UnknownBuild/Coral/tree/master/src/main/resources/assets/coral/lang) 中可查看。 Coral 针对不同语言使用了同一套样式方案,如果您对样式的支持不满意,可以自定义编写样式文件。 @@ -87,15 +89,17 @@ Coral 的预设样式文件在 [源码](https://github.com/UnknownBuild/Coral/tr 该命令仅在专用服务器或单人游戏局域网模式下生效,不支持单人游戏。 -| 配置项 | 类型 | 默认值 | 说明 | -|--|--|--|--| -| command.here | 布尔值 | true | 是否启用该命令 | -| command.here.duration | 整型 | 30 | 发光效果时长(秒) | +| 配置项 | 类型 | 默认值 | 说明 | +|-----------------------|-----|------|-----------| +| command.here | 布尔值 | true | 是否启用该命令 | +| command.here.duration | 整型 | 30 | 发光效果时长(秒) | ### player 玩家命令增强。提供更丰富的查询功能。 +启用该功能后,服务器将在 world/data 下创建文件 coral_player.data,该文件用于存储玩家在线信息,仅用于辅助玩家信息查询,即使被删除也不影响服务器游玩。 + ``` /player list # 列举当前在线玩家名单, 等同于 /list uuids, 但支持点击复制 uuid /player listall # 列举服务器所有玩家名单, 部分玩家不会展示名称 @@ -103,9 +107,9 @@ Coral 的预设样式文件在 [源码](https://github.com/UnknownBuild/Coral/tr listall 命令要求执行玩家拥有等级 3 及以上权限。 -| 配置项 | 类型 | 默认值 | 说明 | -|----------------|--|--|--| -| command.player | 布尔值 | true | 是否启用该命令 | +| 配置项 | 类型 | 默认值 | 说明 | +|----------------|-----|------|---------| +| command.player | 布尔值 | true | 是否启用该命令 | ### wru @@ -117,9 +121,9 @@ listall 命令要求执行玩家拥有等级 3 及以上权限。 该命令仅在专用服务器或单人游戏局域网模式下生效,不支持单人游戏。 -| 配置项 | 类型 | 默认值 | 说明 | -|-----------------------|--|--|--| -| command.wru | 布尔值 | true | 是否启用该命令 | +| 配置项 | 类型 | 默认值 | 说明 | +|-------------|-----|------|---------| +| command.wru | 布尔值 | true | 是否启用该命令 | ## 特性支持 @@ -129,8 +133,8 @@ listall 命令要求执行玩家拥有等级 3 及以上权限。 该命令仅在专用服务器或单人游戏局域网模式下生效,不支持单人游戏。 -| 配置项 | 类型 | 默认值 | 说明 | -|--------------------|--|--|---------| +| 配置项 | 类型 | 默认值 | 说明 | +|--------------------|-----|------|---------| | feature.call_sleep | 布尔值 | true | 是否启用该特性 | ### death_info @@ -139,6 +143,6 @@ listall 命令要求执行玩家拥有等级 3 及以上权限。 该命令在所有模式中均生效。 -| 配置项 | 类型 | 默认值 | 说明 | -|--------------------|--|--|---------| +| 配置项 | 类型 | 默认值 | 说明 | +|--------------------|-----|------|---------| | feature.death_info | 布尔值 | true | 是否启用该特性 | diff --git a/gradle.properties b/gradle.properties index 7336def..26461ac 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,17 +1,21 @@ # Done to increase the memory available to gradle. org.gradle.jvmargs=-Xmx1G +# Mirrors +loom_libraries_base=https://bmclapi2.bangbang93.com/maven/ +loom_resources_base=https://bmclapi2.bangbang93.com/assets/ +loom_version_manifests=https://bmclapi2.bangbang93.com/mc/game/version_manifest.json +loom_experimental_versions=https://maven.fabricmc.net/net/minecraft/experimental_versions.json +loom_fabric_repository=https://repository.hanbings.io/proxy/ + # Fabric Properties # check these on https://fabricmc.net/versions.html -minecraft_version=1.21.8 -yarn_mappings=1.21.8+build.1 -loader_version=0.16.14 -loom_version=1.11-SNAPSHOT - -# Fabric API -fabric_version=0.130.0+1.21.8 +minecraft_version=1.21.9 +yarn_mappings=1.21.9+build.1 +loader_version=0.17.3 +loom_version=1.12-SNAPSHOT # Mod Properties -mod_version = 0.3.0 +mod_version = 0.4.0 maven_group = studio.xmatrix.minecraft archives_base_name = coral diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 04bedef..419bab4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://mirrors.aliyun.com/gradle/distributions/v8.14.3/gradle-8.14.3-bin.zip +distributionUrl=https\://mirrors.aliyun.com/gradle/distributions/v9.2.0/gradle-9.2.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/java/studio/xmatrix/minecraft/coral/CoralMod.java b/src/main/java/studio/xmatrix/minecraft/coral/CoralMod.java index 6d24472..7281705 100644 --- a/src/main/java/studio/xmatrix/minecraft/coral/CoralMod.java +++ b/src/main/java/studio/xmatrix/minecraft/coral/CoralMod.java @@ -1,7 +1,7 @@ package studio.xmatrix.minecraft.coral; import net.fabricmc.api.ModInitializer; -import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; +import studio.xmatrix.minecraft.coral.command.CommandCallback; import studio.xmatrix.minecraft.coral.command.HereCommand; import studio.xmatrix.minecraft.coral.command.PlayerCommand; import studio.xmatrix.minecraft.coral.command.WRUCommand; @@ -18,7 +18,7 @@ public void onInitialize() { Style.init(); // 注册命令 - CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { + CommandCallback.init(dispatcher -> { HereCommand.register(dispatcher); PlayerCommand.register(dispatcher); WRUCommand.register(dispatcher); diff --git a/src/main/java/studio/xmatrix/minecraft/coral/command/CommandCallback.java b/src/main/java/studio/xmatrix/minecraft/coral/command/CommandCallback.java new file mode 100644 index 0000000..328e70d --- /dev/null +++ b/src/main/java/studio/xmatrix/minecraft/coral/command/CommandCallback.java @@ -0,0 +1,20 @@ +package studio.xmatrix.minecraft.coral.command; + +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.server.command.ServerCommandSource; + +public class CommandCallback { + private static Event callback; + + public static void init(Event callback) { + CommandCallback.callback = callback; + } + + public static void register(CommandDispatcher dispatcher) { + callback.apply(dispatcher); + } + + public interface Event { + void apply(CommandDispatcher dispatcher); + } +} diff --git a/src/main/java/studio/xmatrix/minecraft/coral/command/HereCommand.java b/src/main/java/studio/xmatrix/minecraft/coral/command/HereCommand.java index bb5d401..97dc27b 100644 --- a/src/main/java/studio/xmatrix/minecraft/coral/command/HereCommand.java +++ b/src/main/java/studio/xmatrix/minecraft/coral/command/HereCommand.java @@ -38,7 +38,7 @@ private static int executeHere(ServerCommandSource source) throws CommandSyntaxE // 广播信息到聊天框 String coordinateText = String.format("[%d, %d, %d]", player.getBlockX(), player.getBlockY(), player.getBlockZ()); MutableText text = Language.formatStyle("coral.command.here", player.getDisplayName(), - Dimension.getStyleText(player.getWorld().getRegistryKey()), coordinateText); + Dimension.getStyleText(player.getEntityWorld().getRegistryKey()), coordinateText); source.getServer().getPlayerManager().broadcast(text, false); source.sendFeedback(() -> Language.formatStyle("coral.command.here.feedback", duration), false); diff --git a/src/main/java/studio/xmatrix/minecraft/coral/command/PlayerCommand.java b/src/main/java/studio/xmatrix/minecraft/coral/command/PlayerCommand.java index 648d9d1..1eb84ac 100644 --- a/src/main/java/studio/xmatrix/minecraft/coral/command/PlayerCommand.java +++ b/src/main/java/studio/xmatrix/minecraft/coral/command/PlayerCommand.java @@ -2,30 +2,29 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.server.PlayerConfigEntry; import net.minecraft.server.command.CommandManager; import net.minecraft.server.command.ServerCommandSource; -import net.minecraft.text.ClickEvent; -import net.minecraft.text.HoverEvent; -import net.minecraft.text.Text; -import net.minecraft.text.Texts; +import net.minecraft.text.*; import net.minecraft.util.Formatting; import net.minecraft.util.WorldSavePath; -import org.apache.commons.lang3.tuple.Triple; +import org.jetbrains.annotations.NotNull; import studio.xmatrix.minecraft.coral.config.Config; import studio.xmatrix.minecraft.coral.config.Language; import studio.xmatrix.minecraft.coral.mixin.command.player.MinecraftServerAccessor; +import studio.xmatrix.minecraft.coral.store.CoralPlayerState; import studio.xmatrix.minecraft.coral.util.FileUtil; import java.io.File; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Objects; -import java.util.UUID; +import java.text.SimpleDateFormat; +import java.util.*; /** * player 命令提供了玩家查询和转移的扩展 */ public class PlayerCommand { + private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + public static void register(CommandDispatcher dispatcher) { if (!Config.getBoolean("command.player")) { return; @@ -37,71 +36,138 @@ public static void register(CommandDispatcher dispatcher) { .then(CommandManager.literal("listall").requires(s -> s.hasPermissionLevel(3)).executes(c -> executeListAll(c.getSource())))); } + /** + * 展示当前在线玩家列表 + */ private static int executeList(ServerCommandSource source) { // 输出当前在线玩家列表 var players = source.getServer().getPlayerManager().getPlayerList(); - var playerText = Texts.join(players, Text.literal("\n"), p -> createPlayerText(p.getUuidAsString(), p.getGameProfile().getName(), 0)); + var playerText = Texts.join(players, Text.literal("\n"), p -> + createPlayerText(new PlayerData(p.getUuid(), p.getGameProfile().name(), true, 0, null), false)); + if (!players.isEmpty()) { + playerText = Text.literal(":\n").append(playerText); + } var text = Language.formatStyle("coral.command.player.list", players.size(), playerText); source.sendFeedback(() -> text, false); return Command.SINGLE_SUCCESS; } + /** + * 展示所有玩家列表 + */ private static int executeListAll(ServerCommandSource source) { - var players = new ArrayList>(); // uuid, name, level - var playerMap = new HashSet(); + var players = new ArrayList(); + var playerMap = new HashSet(); var opList = source.getServer().getPlayerManager().getOpList(); // 获取服务器在线玩家列表 for (var p : source.getServer().getPlayerManager().getPlayerList()) { - var op = opList.get(p.getGameProfile()); + var op = opList.get(p.getPlayerConfigEntry()); var opLevel = op != null ? op.getPermissionLevel() : 0; - players.add(Triple.of(p.getUuidAsString(), p.getGameProfile().getName(), opLevel)); - playerMap.add(p.getUuidAsString()); + players.add(new PlayerData(p.getUuid(), p.getGameProfile().name(), true, opLevel, null)); + playerMap.add(p.getUuid()); } - // 获取服务器玩家数据文件夹下的玩家列表 + // 获取本地存储的离线玩家列表 var playerDataDir = ((MinecraftServerAccessor) source.getServer()).getSession().getDirectory(WorldSavePath.PLAYERDATA).toFile(); - var userCache = Objects.requireNonNull(source.getServer().getUserCache()); + var coralPlayers = CoralPlayerState.fromServer(source.getServer()).getPlayers(); for (var file : Objects.requireNonNull(playerDataDir.listFiles(File::isFile))) { // 获取用户 uuid - var uuidStr = FileUtil.getPrefixName(file); - if (uuidStr.length() != 36 || playerMap.contains(uuidStr)) { + UUID uuid; + try { + uuid = UUID.fromString(FileUtil.getPrefixName(file)); + } catch (IllegalArgumentException e) { continue; } - playerMap.add(uuidStr); - var uuid = UUID.fromString(uuidStr); - // 获取用户名称和判断是否为管理员 - String playerName = ""; - int opLevel = 0; - var gameProfile = userCache.getByUuid(uuid); - if (gameProfile.isPresent()) { - playerName = gameProfile.get().getName(); - var op = opList.get(gameProfile.get()); - opLevel = op != null ? op.getPermissionLevel() : 0; + if (playerMap.contains(uuid)) { + continue; + } + playerMap.add(uuid); + + // 获取用户信息 + var op = opList.get(new PlayerConfigEntry(uuid, "")); + var opLevel = op != null ? op.getPermissionLevel() : 0; + var coralPlayer = coralPlayers.get(uuid); + if (coralPlayer != null) { + players.add(new PlayerData(uuid, coralPlayer.getName(), false, opLevel, coralPlayer.getPlayTime())); + } else { + players.add(new PlayerData(uuid, null, false, opLevel, null)); } - players.add(Triple.ofNonNull(uuidStr, playerName, opLevel)); } - players.sort((p1, p2) -> p1.getRight() > p2.getRight() ? -1 : - (p1.getRight().equals(p2.getRight()) ? p1.getLeft().compareTo(p2.getLeft()) : 1)); + Collections.sort(players); // 输出信息到聊天框 - var playerText = Texts.join(players, Text.literal("\n"), p -> createPlayerText(p.getLeft(), p.getMiddle(), p.getRight())); + var playerText = Texts.join(players, Text.literal("\n"), p -> createPlayerText(p, true)); + if (!players.isEmpty()) { + playerText = Text.literal(":\n").append(playerText); + } var text = Language.formatStyle("coral.command.player.listall", players.size(), playerText); source.sendFeedback(() -> text, false); return Command.SINGLE_SUCCESS; } - private static Text createPlayerText(String uuidStr, String name, int permissionLevel) { - var text = Text.empty(); - if (name != null && !name.isEmpty()) { - text.append(name + " "); + private static Text createPlayerText(PlayerData player, boolean showStatus) { + // 默认展示玩家名称, 如果没有名称则展示 uuid + MutableText nameText; + String copyString; + if (player.name != null && !player.name.isEmpty()) { + nameText = Text.literal(player.name); + copyString = String.format("%s [%s]", player.name, player.uuid); + } else { + nameText = Texts.bracketed(Text.literal(player.uuid.toString())).formatted(Formatting.GRAY); + copyString = String.format("[%s]", player.uuid); + } + nameText.styled(style -> style.withClickEvent(new ClickEvent.CopyToClipboard(copyString)) + .withHoverEvent(new HoverEvent.ShowText(Text.translatable("chat.copy.click")))); + + // 添加在线状态和权限 + MutableText statusText = null; + if (showStatus) { + if (player.online) { + statusText = Language.format("coral.command.player.listall.online"); + } else { + statusText = Language.format("coral.command.player.listall.offline"); + if (player.playTime != null) { + statusText.append(Texts.DEFAULT_SEPARATOR) + .append(Language.formatStyle("coral.command.player.listall.playtime", dateFormat.format(player.playTime))); + } + } + if (player.permissionLevel > 0) { + statusText.append(Texts.DEFAULT_SEPARATOR) + .append(Language.formatStyle("coral.command.player.listall.operator", player.permissionLevel)); + statusText.formatted(Formatting.LIGHT_PURPLE); + } } - if (permissionLevel > 0) { - text.append(Text.literal(String.format("(op:%d) ", permissionLevel)).formatted(Formatting.LIGHT_PURPLE)); + + MutableText text = Text.empty(); + text.append(player.online ? nameText : nameText.formatted(Formatting.GRAY)); + if (showStatus) { + text.append(" ").append(player.online ? Texts.bracketed(statusText) : + Texts.bracketed(statusText.formatted(Formatting.GRAY)).formatted(Formatting.GRAY)); } - text.append(Texts.bracketed(Text.literal(uuidStr)).formatted(Formatting.GRAY) - .styled(style -> style.withClickEvent(new ClickEvent.CopyToClipboard(uuidStr)) - .withHoverEvent(new HoverEvent.ShowText(Text.translatable("chat.copy.click"))))); return text; } + + // 玩家数据 + private record PlayerData(UUID uuid, String name, boolean online, int permissionLevel, + Date playTime) implements Comparable { + @Override + public int compareTo(@NotNull PlayerData obj) { + // 在线用户优先 + if (online != obj.online) { + return online ? -1 : 1; + } + // 权限高的优先 + if (permissionLevel != obj.permissionLevel) { + return -(permissionLevel - obj.permissionLevel); + } + // 游玩时间最新的优先, 如果不存在游玩时间记录则排在最后 + if (playTime != null && obj.playTime != null) { + return -playTime.compareTo(obj.playTime); + } else if (playTime == null && obj.playTime == null) { + return 0; + } + return playTime != null ? -1 : 1; + } + } } diff --git a/src/main/java/studio/xmatrix/minecraft/coral/mixin/CoralMixinPlugin.java b/src/main/java/studio/xmatrix/minecraft/coral/mixin/CoralMixinPlugin.java index 15aa879..f30cd79 100644 --- a/src/main/java/studio/xmatrix/minecraft/coral/mixin/CoralMixinPlugin.java +++ b/src/main/java/studio/xmatrix/minecraft/coral/mixin/CoralMixinPlugin.java @@ -43,6 +43,9 @@ public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { String configName = mixinClassName.substring(start, end); // 判断功能是否启用 + if (configName.equals("base")) { + return true; // 基础 Mixin, 默认启用 + } return Config.getBoolean(configName); } diff --git a/src/main/java/studio/xmatrix/minecraft/coral/mixin/base/CommandManagerMixin.java b/src/main/java/studio/xmatrix/minecraft/coral/mixin/base/CommandManagerMixin.java new file mode 100644 index 0000000..23e589e --- /dev/null +++ b/src/main/java/studio/xmatrix/minecraft/coral/mixin/base/CommandManagerMixin.java @@ -0,0 +1,25 @@ +package studio.xmatrix.minecraft.coral.mixin.base; + +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.command.CommandRegistryAccess; +import net.minecraft.server.command.CommandManager; +import net.minecraft.server.command.ServerCommandSource; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import studio.xmatrix.minecraft.coral.command.CommandCallback; + +@Mixin(CommandManager.class) +public abstract class CommandManagerMixin { + @Final + @Shadow + private CommandDispatcher dispatcher; + + @Inject(method = "", at = @At("RETURN")) + private void init(CommandManager.RegistrationEnvironment environment, CommandRegistryAccess commandRegistryAccess, CallbackInfo ci) { + CommandCallback.register(dispatcher); + } +} diff --git a/src/main/java/studio/xmatrix/minecraft/coral/mixin/base/PersistentStateManagerMixin.java b/src/main/java/studio/xmatrix/minecraft/coral/mixin/base/PersistentStateManagerMixin.java new file mode 100644 index 0000000..6f4d3d0 --- /dev/null +++ b/src/main/java/studio/xmatrix/minecraft/coral/mixin/base/PersistentStateManagerMixin.java @@ -0,0 +1,21 @@ +package studio.xmatrix.minecraft.coral.mixin.base; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.mojang.datafixers.DataFixer; +import net.minecraft.datafixer.DataFixTypes; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.world.PersistentStateManager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(PersistentStateManager.class) +public abstract class PersistentStateManagerMixin { + @WrapOperation(method = "readNbt", at = @At(value = "INVOKE", target = "Lnet/minecraft/datafixer/DataFixTypes;update(Lcom/mojang/datafixers/DataFixer;Lnet/minecraft/nbt/NbtCompound;II)Lnet/minecraft/nbt/NbtCompound;")) + private NbtCompound handleNullDataFixType(DataFixTypes dataFixTypes, DataFixer dataFixer, NbtCompound nbt, int oldVersion, int newVersion, Operation original) { + if (dataFixTypes == null) { + return nbt; + } + return original.call(dataFixTypes, dataFixer, nbt, oldVersion, newVersion); + } +} diff --git a/src/main/java/studio/xmatrix/minecraft/coral/mixin/command/player/PlayerManagerMixin.java b/src/main/java/studio/xmatrix/minecraft/coral/mixin/command/player/PlayerManagerMixin.java new file mode 100644 index 0000000..952ad65 --- /dev/null +++ b/src/main/java/studio/xmatrix/minecraft/coral/mixin/command/player/PlayerManagerMixin.java @@ -0,0 +1,24 @@ +package studio.xmatrix.minecraft.coral.mixin.command.player; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.PlayerManager; +import net.minecraft.server.network.ServerPlayerEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import studio.xmatrix.minecraft.coral.store.CoralPlayerState; + +@Mixin(PlayerManager.class) +public abstract class PlayerManagerMixin { + @Shadow + public abstract MinecraftServer getServer(); + + @Inject(method = "savePlayerData", at = @At("RETURN")) + private void savePlayerData(ServerPlayerEntity player, CallbackInfo ci) { + // 将玩家数据写入本地存储 + var state = CoralPlayerState.fromServer(getServer()); + state.updatePlayer(player); + } +} diff --git a/src/main/java/studio/xmatrix/minecraft/coral/mixin/feature/call_sleep/ServerPlayerEntityMixin.java b/src/main/java/studio/xmatrix/minecraft/coral/mixin/feature/call_sleep/ServerPlayerEntityMixin.java index 1febdea..c7a0778 100644 --- a/src/main/java/studio/xmatrix/minecraft/coral/mixin/feature/call_sleep/ServerPlayerEntityMixin.java +++ b/src/main/java/studio/xmatrix/minecraft/coral/mixin/feature/call_sleep/ServerPlayerEntityMixin.java @@ -2,33 +2,37 @@ import com.mojang.authlib.GameProfile; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.MutableText; import net.minecraft.world.World; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import studio.xmatrix.minecraft.coral.config.Language; -import java.util.Objects; - @Mixin(ServerPlayerEntity.class) public abstract class ServerPlayerEntityMixin extends PlayerEntity { + @Final + @Shadow + private MinecraftServer server; + private ServerPlayerEntityMixin(World world, GameProfile gameProfile) { super(world, gameProfile); } - @Inject(method = "sleep", at = @At(value = "RETURN")) + @Inject(method = "sleep", at = @At("RETURN")) private void sleep(CallbackInfo ci) { // 如果是单人游戏且非局域网模式, 不启用该功能 - var server = Objects.requireNonNull(this.getServer()); - if (!server.isRemote()) { + if (!this.server.isRemote()) { return; } // 广播消息提示睡觉 MutableText text = Language.formatStyle("coral.feature.call_sleep", this.getDisplayName()); - server.getPlayerManager().broadcast(text, false); + this.server.getPlayerManager().broadcast(text, false); } } diff --git a/src/main/java/studio/xmatrix/minecraft/coral/mixin/feature/death_info/ServerPlayerEntityMixin.java b/src/main/java/studio/xmatrix/minecraft/coral/mixin/feature/death_info/ServerPlayerEntityMixin.java index b68cece..fff464c 100644 --- a/src/main/java/studio/xmatrix/minecraft/coral/mixin/feature/death_info/ServerPlayerEntityMixin.java +++ b/src/main/java/studio/xmatrix/minecraft/coral/mixin/feature/death_info/ServerPlayerEntityMixin.java @@ -2,10 +2,14 @@ import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; import net.minecraft.text.MutableText; import net.minecraft.world.World; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -16,16 +20,23 @@ @Mixin(ServerPlayerEntity.class) public abstract class ServerPlayerEntityMixin extends LivingEntity { + @Final + @Shadow + private MinecraftServer server; + protected ServerPlayerEntityMixin(EntityType entityType, World world) { super(entityType, world); } - @Inject(method = "onDeath", at = @At(value = "RETURN")) + @Shadow + public abstract ServerWorld getEntityWorld(); + + @Inject(method = "onDeath", at = @At("RETURN")) private void onDeath(CallbackInfo ci) { // 广播死亡地址 String coordinateText = String.format("[%d, %d, %d]", this.getBlockX(), this.getBlockY(), this.getBlockZ()); MutableText text = Language.formatStyle("coral.feature.death_info", this.getDisplayName(), - Dimension.getStyleText(this.getWorld().getRegistryKey()), coordinateText); - Objects.requireNonNull(this.getServer()).getPlayerManager().broadcast(text, false); + Dimension.getStyleText(this.getEntityWorld().getRegistryKey()), coordinateText); + Objects.requireNonNull(this.server).getPlayerManager().broadcast(text, false); } } diff --git a/src/main/java/studio/xmatrix/minecraft/coral/store/CoralPlayer.java b/src/main/java/studio/xmatrix/minecraft/coral/store/CoralPlayer.java new file mode 100644 index 0000000..8cef44f --- /dev/null +++ b/src/main/java/studio/xmatrix/minecraft/coral/store/CoralPlayer.java @@ -0,0 +1,35 @@ +package studio.xmatrix.minecraft.coral.store; + +import java.util.Date; + +/** + * Coral 专属存储的玩家信息 + */ +public class CoralPlayer { + private String name; + private Date playTime; + + CoralPlayer() { + } + + CoralPlayer(String name, Date playTime) { + this.name = name; + this.playTime = playTime; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Date getPlayTime() { + return playTime; + } + + public void setPlayTime(Date playTime) { + this.playTime = playTime; + } +} diff --git a/src/main/java/studio/xmatrix/minecraft/coral/store/CoralPlayerState.java b/src/main/java/studio/xmatrix/minecraft/coral/store/CoralPlayerState.java new file mode 100644 index 0000000..f98c481 --- /dev/null +++ b/src/main/java/studio/xmatrix/minecraft/coral/store/CoralPlayerState.java @@ -0,0 +1,85 @@ +package studio.xmatrix.minecraft.coral.store; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.*; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtOps; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.world.PersistentState; +import net.minecraft.world.PersistentStateType; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class CoralPlayerState extends PersistentState { + private static final String KEY = "coral_player"; + private static final Codec CODEC = Codec.of(new Encoder<>() { + @Override + public DataResult encode(CoralPlayerState state, DynamicOps ops, T prefix) { + //noinspection unchecked + return DataResult.success((T) state.toNbt()); + } + }, new Decoder<>() { + @Override + public DataResult> decode(DynamicOps ops, T input) { + var nbt = (NbtCompound) ops.convertTo(NbtOps.INSTANCE, input); + return DataResult.success(Pair.of(fromNbt(nbt), ops.empty())); + } + }); + private final HashMap players = new HashMap<>(); + + private static CoralPlayerState fromNbt(NbtCompound nbt) { + var state = new CoralPlayerState(); + var playersNbtOp = nbt.getCompound("players"); + playersNbtOp.ifPresent(playersNbt -> { + playersNbt.getKeys().forEach(key -> { + var playerNbtOp = playersNbt.getCompound(key); + if (playerNbtOp.isEmpty()) { + return; + } + var playerNbt = playerNbtOp.get(); + var name = playerNbt.getString("name").orElse(""); + long playTime = playerNbt.getLong("play_time").orElse(0L); + if (name.isEmpty() || playTime == 0) { + return; + } + state.players.put(UUID.fromString(key), new CoralPlayer(name, new Date(playTime))); + }); + }); + return state; + } + + public static CoralPlayerState fromServer(MinecraftServer server) { + var persistentStateManager = server.getOverworld().getPersistentStateManager(); + var type = new PersistentStateType<>(KEY, CoralPlayerState::new, CoralPlayerState.CODEC, null); + return persistentStateManager.getOrCreate(type); + } + + public NbtCompound toNbt() { + var nbt = new NbtCompound(); + var playersNbt = new NbtCompound(); + players.forEach((uuid, player) -> { + var playerNbt = new NbtCompound(); + playerNbt.putString("name", player.getName()); + playerNbt.putLong("play_time", player.getPlayTime().getTime()); + playersNbt.put(uuid.toString(), playerNbt); + }); + nbt.put("players", playersNbt); + return nbt; + } + + public Map getPlayers() { + return players; + } + + // 更新玩家信息 + public void updatePlayer(ServerPlayerEntity serverPlayer) { + var player = players.computeIfAbsent(serverPlayer.getUuid(), uuid -> new CoralPlayer()); + player.setName(serverPlayer.getGameProfile().name()); + player.setPlayTime(new Date()); + markDirty(); // 标记数据已更新 + } +} diff --git a/src/main/resources/assets/coral/coral-config-default.properties b/src/main/resources/assets/coral/coral-config-default.properties index 83881fd..5af0a05 100644 --- a/src/main/resources/assets/coral/coral-config-default.properties +++ b/src/main/resources/assets/coral/coral-config-default.properties @@ -1,5 +1,5 @@ # Coral mod default config -language=zh_cn +language=en_us language.path= style.path= diff --git a/src/main/resources/assets/coral/coral-config-empty.properties b/src/main/resources/assets/coral/coral-config-empty.properties index db454ec..1e7b7f3 100644 --- a/src/main/resources/assets/coral/coral-config-empty.properties +++ b/src/main/resources/assets/coral/coral-config-empty.properties @@ -1,5 +1,5 @@ # Coral mod empty config -language=zh_cn +language=en_us language.path= style.path= diff --git a/src/main/resources/assets/coral/lang/en_us.json b/src/main/resources/assets/coral/lang/en_us.json index 2058209..4a2cf6f 100644 --- a/src/main/resources/assets/coral/lang/en_us.json +++ b/src/main/resources/assets/coral/lang/en_us.json @@ -1,13 +1,17 @@ { "coral.command.here": "%1$s says hello in the %2$s %3$s", "coral.command.here.feedback": "You will be highlighted for %1$s seconds", - "coral.command.player.list": "There are %1$s players online:\n%2$s", - "coral.command.player.listall": "There are total %1$s players in server:\n%2$s", + "coral.command.player.list": "There are %1$s players online%2$s", + "coral.command.player.listall": "There are total %1$s players in server%2$s", + "coral.command.player.listall.offline": "Offline", + "coral.command.player.listall.online": "Online", + "coral.command.player.listall.operator": "Operator(%1$s)", + "coral.command.player.listall.playtime": "LastPlay:%1$s", "coral.command.wru": "%1$s is asking %2$s's location", "coral.command.wru.err_self": "cannot ask yourself", "coral.dimension.end": "End", "coral.dimension.nether": "Nether", - "coral.dimension.over_world": "OverWorld", + "coral.dimension.over_world": "OverWorld", "coral.feature.call_sleep": "%1$s calls you to sleep", "coral.feature.death_info": "%1$s died in %2$s %3$s" } diff --git a/src/main/resources/assets/coral/lang/zh_cn.json b/src/main/resources/assets/coral/lang/zh_cn.json index 204ba5e..c105905 100644 --- a/src/main/resources/assets/coral/lang/zh_cn.json +++ b/src/main/resources/assets/coral/lang/zh_cn.json @@ -1,8 +1,12 @@ { "coral.command.here": "%1$s在%2$s%3$s向大家打招呼", "coral.command.here.feedback": "您将会被高亮%1$s秒", - "coral.command.player.list": "当前共有%1$s名玩家在线:\n%2$s", - "coral.command.player.listall": "服务器共有%1$s名玩家:\n%2$s", + "coral.command.player.list": "当前共有%1$s名玩家在线%2$s", + "coral.command.player.listall": "服务器共有%1$s名玩家%2$s", + "coral.command.player.listall.offline": "离线", + "coral.command.player.listall.online": "在线", + "coral.command.player.listall.operator": "管理员(%1$s)", + "coral.command.player.listall.playtime": "最近游玩:%1$s", "coral.command.wru": "%1$s在询问%2$s的位置", "coral.command.wru.err_self": "不允许询问自己", "coral.dimension.end": "末地", diff --git a/src/main/resources/coral.mixins.json b/src/main/resources/coral.mixins.json index 5b48dcd..40cd828 100644 --- a/src/main/resources/coral.mixins.json +++ b/src/main/resources/coral.mixins.json @@ -4,7 +4,10 @@ "compatibilityLevel": "JAVA_21", "plugin": "studio.xmatrix.minecraft.coral.mixin.CoralMixinPlugin", "mixins": [ + "base.CommandManagerMixin", + "base.PersistentStateManagerMixin", "command.player.MinecraftServerAccessor", + "command.player.PlayerManagerMixin", "feature.call_sleep.ServerPlayerEntityMixin", "feature.death_info.ServerPlayerEntityMixin" ], diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 73b8fd6..8959f7f 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -29,8 +29,7 @@ "depends": { "java": ">=21", "minecraft": "1.21.x", - "fabricloader": ">=0.15.11", - "fabric-command-api-v2": "*" + "fabricloader": ">=0.16.13" }, "suggests": {} }