Compare commits

...

3 Commits

5 changed files with 397 additions and 9 deletions

View File

@@ -0,0 +1,217 @@
# MiniMessage Config Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Let player-visible configuration text support MiniMessage while keeping existing legacy `&` and `#RRGGBB` formatting compatible.
**Architecture:** Keep `ColorUtil` as the single text-formatting boundary. Add Adventure text serializers there, return legacy `String` values to preserve current command, quest, reward, and API signatures.
**Tech Stack:** Java 21, Maven, Paper API Adventure classes, JUnit 5 for utility tests.
---
## File Structure
- Modify `pom.xml`: add test dependencies for JUnit 5 and Surefire so Maven can run unit tests.
- Create `src/test/java/com/io/yaohun/questengine/util/ColorUtilTest.java`: focused tests for legacy compatibility and MiniMessage parsing.
- Modify `src/main/java/com/io/yaohun/questengine/util/ColorUtil.java`: implement MiniMessage parsing while retaining legacy output.
### Task 1: Add ColorUtil Regression Tests
**Files:**
- Modify: `pom.xml`
- Create: `src/test/java/com/io/yaohun/questengine/util/ColorUtilTest.java`
- [ ] **Step 1: Add JUnit test dependencies**
Add these dependencies under `<dependencies>` in `pom.xml`:
```xml
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.3</version>
<scope>test</scope>
</dependency>
```
Add this plugin under `<plugins>` in `pom.xml`:
```xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
```
- [ ] **Step 2: Write failing tests**
Create `src/test/java/com/io/yaohun/questengine/util/ColorUtilTest.java`:
```java
package com.io.yaohun.questengine.util;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ColorUtilTest {
@Test
void colorKeepsLegacyAmpersandFormatting() {
assertEquals("§a任务 §f完成", ColorUtil.color("&a任务 &f完成"));
}
@Test
void colorKeepsLegacyHexFormatting() {
assertEquals("§x§1§2§3§4§5§6任务", ColorUtil.color("#123456任务"));
}
@Test
void colorSupportsMiniMessageNamedColors() {
assertEquals("§a任务", ColorUtil.color("<green>任务</green>"));
}
@Test
void colorSupportsMiniMessageDecorations() {
assertEquals("§l加粗", ColorUtil.color("<bold>加粗</bold>"));
}
@Test
void colorParsesEveryListEntry() {
assertEquals(
List.of("§a领取", "§c完成"),
ColorUtil.color(List.of("<green>领取</green>", "&c完成"))
);
}
}
```
- [ ] **Step 3: Run tests to verify RED**
Run:
```bash
mvn -q -Dtest=ColorUtilTest test
```
Expected: tests compile, legacy tests pass, MiniMessage tests fail because tags are not parsed yet.
### Task 2: Implement MiniMessage Support
**Files:**
- Modify: `src/main/java/com/io/yaohun/questengine/util/ColorUtil.java`
- Test: `src/test/java/com/io/yaohun/questengine/util/ColorUtilTest.java`
- [ ] **Step 1: Replace ColorUtil implementation**
Update `ColorUtil.java` to:
```java
package com.io.yaohun.questengine.util;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.ChatColor;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class ColorUtil {
private static final Pattern HEX_PATTERN = Pattern.compile("#[a-fA-F0-9]{6}");
private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
private static final LegacyComponentSerializer LEGACY_SERIALIZER = LegacyComponentSerializer.legacySection();
public static String color(String text) {
if (text == null) {
return "";
}
String legacyText = translateLegacyColors(text);
Component component = MINI_MESSAGE.deserialize(legacyText);
return LEGACY_SERIALIZER.serialize(component);
}
public static List<String> color(List<String> list) {
return list.stream().map(ColorUtil::color).collect(Collectors.toList());
}
private static String translateLegacyColors(String text) {
Matcher matcher = HEX_PATTERN.matcher(text);
while (matcher.find()) {
String hexCode = text.substring(matcher.start(), matcher.end());
String replaceSharp = hexCode.replace('#', 'x');
char[] ch = replaceSharp.toCharArray();
StringBuilder builder = new StringBuilder();
for (char c : ch) {
builder.append("&").append(c);
}
text = text.replace(hexCode, builder.toString());
matcher = HEX_PATTERN.matcher(text);
}
return ChatColor.translateAlternateColorCodes('&', text);
}
}
```
- [ ] **Step 2: Run focused tests to verify GREEN**
Run:
```bash
mvn -q -Dtest=ColorUtilTest test
```
Expected: all `ColorUtilTest` tests pass.
### Task 3: Verify Plugin Build
**Files:**
- Verify: full project
- [ ] **Step 1: Run full test suite**
Run:
```bash
mvn test
```
Expected: build succeeds and `ColorUtilTest` passes.
- [ ] **Step 2: Run compile**
Run:
```bash
mvn compile
```
Expected: production code compiles.
- [ ] **Step 3: Review changed files**
Run:
```bash
git diff --check
git status --short
```
Expected: no whitespace errors; only planned files are modified or added.
- [ ] **Step 4: Commit implementation**
Run:
```bash
git add pom.xml src/main/java/com/io/yaohun/questengine/util/ColorUtil.java src/test/java/com/io/yaohun/questengine/util/ColorUtilTest.java docs/superpowers/plans/2026-06-14-minimessage-config.md
git commit -m "feat: 支持配置 MiniMessage 文本"
```

View File

@@ -0,0 +1,79 @@
# 配置可见文本 MiniMessage 支持设计
## 背景
AuQuestEngine 当前通过 `ColorUtil.color` 将配置中的 `&` 颜色码和 `#RRGGBB` 十六进制颜色转换成 Bukkit legacy 文本。任务配置、语言配置和奖励提示都依赖这个工具,最终通过现有 `sendMessage(String)` 路径发送给玩家。
本次目标是让配置里的玩家可见文本支持 MiniMessage同时保留旧配置兼容性。
## 范围
纳入 MiniMessage 支持的配置文本:
- `lang.yml` 中的语言文本。
- 任务配置中的 `display_name`
- 任务配置中的 `description`
- 任务配置中的 `messages.receive`
- 任务配置中的 `messages.complete`
- 任务配置中的 `rewards.messages`
不纳入本次范围:
- `rewards.commands`,它是控制台命令,不是玩家可见文本。
- 硬编码的命令提示。
- 控制台日志。
- 调试输出。
- API 返回类型调整。
## 方案
采用兼容式解析:
1. `ColorUtil` 继续作为统一文本格式入口。
2. 旧格式 `&a文本``#RRGGBB文本` 继续支持。
3. 新格式 `<green>文本</green>``<gradient:red:blue>文本</gradient>` 等 MiniMessage 标签可用于配置文本。
4. 解析结果仍返回 legacy `String`,保持现有 `Quest``QuestReward``MessageUtil` 和 API 方法签名不变。
## 数据流
配置加载后:
1. 读取 YAML 原始字符串。
2. 通过 `ColorUtil.color` 统一解析。
3. 对 MiniMessage 文本解析成 Adventure `Component`
4.`Component` 序列化为 legacy `String`
5. 现有发送逻辑继续调用 `sendMessage(String)`
列表文本通过 `ColorUtil.color(List<String>)` 逐行处理。
## 兼容性
旧配置无需迁移:
```yml
display_name: "&a[初级] &f新手成长之路"
```
新配置可以使用 MiniMessage
```yml
display_name: "<green>[初级]</green> <white>新手成长之路</white>"
```
如果配置文本混用两种格式,优先保证旧 `&` 颜色码仍能按原行为生效,同时 MiniMessage 标签能被识别。
## 错误处理
MiniMessage 解析应使用非严格模式,避免配置中未知标签或普通尖括号文本导致任务加载失败。无法识别的文本应尽量保留为可见内容,而不是抛出异常中断插件启用或重载。
## 测试
新增针对 `ColorUtil` 的测试,覆盖:
- legacy `&` 颜色码仍可转换。
- `#RRGGBB` 仍可转换。
- MiniMessage 基础颜色标签可转换为 legacy 输出。
- MiniMessage 装饰标签可转换为 legacy 输出。
- 列表文本逐行解析。
实现前先写失败测试,再实现最小改动使测试通过。

11
pom.xml
View File

@@ -85,6 +85,12 @@
<version>3.6.52</version> <version>3.6.52</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.3</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
@@ -124,6 +130,11 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
</plugins> </plugins>
</build> </build>

View File

@@ -1,5 +1,8 @@
package com.io.yaohun.questengine.util; package com.io.yaohun.questengine.util;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import java.util.List; import java.util.List;
@@ -9,13 +12,34 @@ import java.util.stream.Collectors;
public class ColorUtil { public class ColorUtil {
private static final Pattern HEX_PATTERN = Pattern.compile("#[a-fA-F0-9]{6}"); private static final Pattern LEGACY_HEX_PATTERN = Pattern.compile("(?<!<)#[a-fA-F0-9]{6}");
private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
private static final LegacyComponentSerializer LEGACY_SERIALIZER = LegacyComponentSerializer.builder()
.character('§')
.hexColors()
.useUnusualXRepeatedCharacterHexFormat()
.build();
public static String color(String text) { public static String color(String text) {
if (text == null) { if (text == null) {
return ""; return "";
} }
Matcher matcher = HEX_PATTERN.matcher(text); if (text.indexOf('§') >= 0) {
Component component = LEGACY_SERIALIZER.deserialize(text);
return LEGACY_SERIALIZER.serialize(component);
}
String hexText = translateHexColors(text);
Component component = MINI_MESSAGE.deserialize(hexText);
String serialized = LEGACY_SERIALIZER.serialize(component);
return ChatColor.translateAlternateColorCodes('&', serialized);
}
public static List<String> color(List<String> list) {
return list.stream().map(ColorUtil::color).collect(Collectors.toList());
}
private static String translateHexColors(String text) {
Matcher matcher = LEGACY_HEX_PATTERN.matcher(text);
while (matcher.find()) { while (matcher.find()) {
String hexCode = text.substring(matcher.start(), matcher.end()); String hexCode = text.substring(matcher.start(), matcher.end());
String replaceSharp = hexCode.replace('#', 'x'); String replaceSharp = hexCode.replace('#', 'x');
@@ -25,13 +49,9 @@ public class ColorUtil {
builder.append("&").append(c); builder.append("&").append(c);
} }
text = text.replace(hexCode, builder.toString()); text = text.replace(hexCode, builder.toString());
matcher = HEX_PATTERN.matcher(text); matcher = LEGACY_HEX_PATTERN.matcher(text);
} }
return ChatColor.translateAlternateColorCodes('&', text); return text;
}
public static List<String> color(List<String> list) {
return list.stream().map(ColorUtil::color).collect(Collectors.toList());
} }
} }

View File

@@ -0,0 +1,61 @@
package com.io.yaohun.questengine.util;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ColorUtilTest {
@Test
void colorKeepsLegacyAmpersandFormatting() {
assertEquals("§a任务 §f完成", ColorUtil.color("&a任务 &f完成"));
}
@Test
void colorKeepsLegacyHexFormatting() {
assertEquals("§x§1§2§3§4§5§6任务", ColorUtil.color("#123456任务"));
}
@Test
void colorSupportsMiniMessageNamedColors() {
assertEquals("§a任务", ColorUtil.color("<green>任务</green>"));
}
@Test
void colorSupportsMiniMessageDecorations() {
assertEquals("§l加粗", ColorUtil.color("<bold>加粗</bold>"));
}
@Test
void colorSupportsPureMiniMessageHexTags() {
assertEquals(
"§fꑟ §x§f§4§9§e§0§b已接取任务 §x§f§1§f§c§d§e资源采集",
ColorUtil.color("<!i><white>ꑟ <#f49e0b>已接取任务 <#f1fcde>资源采集")
);
}
@Test
void colorSupportsMixedMiniMessageAndLegacyFormatting() {
assertEquals("§a领取§r §c奖励", ColorUtil.color("<green>领取</green> &c奖励"));
}
@Test
void colorCanBeCalledOnAlreadyColoredText() {
assertEquals("§a任务", ColorUtil.color(ColorUtil.color("<green>任务</green>")));
}
@Test
void colorLeavesUnknownMiniMessageTagsVisible() {
assertEquals("<quest>任务</quest>", ColorUtil.color("<quest>任务</quest>"));
}
@Test
void colorParsesEveryListEntry() {
assertEquals(
List.of("§a领取", "§c完成"),
ColorUtil.color(List.of("<green>领取</green>", "&c完成"))
);
}
}