4.0.3 添加歌曲时间修正配置

This commit is contained in:
Color_yr committed 2026-06-25 22:49:30 +08:00
1 parent dedd11eb24
commit e625144021
108 files changed
+5577 -53

No files matched your search

+1 -1
View File
@@ -12,7 +12,7 @@ allprojects {
apply(plugin = "com.gradleup.shadow")
group = "com.coloryr"
version = "4.0.2"
version = "4.0.3"
// 设置项目JDK版本
java.sourceCompatibility = JavaVersion.VERSION_1_8
+1 -1
View File
@@ -5,7 +5,7 @@ plugins {
dependencies {
minecraft("com.mojang:minecraft:1.16.5")
mappings(loom.officialMojangMappings())
modCompileOnly("net.fabricmc:fabric-loader:0.18.5")
modCompileOnly("net.fabricmc:fabric-loader:0.19.3")
modCompileOnly("net.fabricmc.fabric-api:fabric-api:0.42.0+1.16")
}
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.gui.Gui;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.resources.sounds.SoundInstance;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
@@ -1,7 +1,7 @@
{
"required": true,
"minVersion": "0.8",
"package": "com.coloryr.allmusic.client.mixin",
"package": "com.coloryr.allmusic.client.mixins",
"compatibilityLevel": "JAVA_8",
"client": [
"GuiShow",
+2 -2
View File
@@ -8,9 +8,9 @@ java.targetCompatibility = JavaVersion.VERSION_17
dependencies {
minecraft("com.mojang:minecraft:1.20.1")
mappings(loom.officialMojangMappings())
modCompileOnly("net.fabricmc:fabric-loader:0.16.10")
modCompileOnly("net.fabricmc:fabric-loader:0.19.3")
modCompileOnly("net.fabricmc.fabric-api:fabric-api:0.92.3+1.20.1")
modCompileOnly("net.fabricmc.fabric-api:fabric-api:0.92.9+1.20.1")
}
tasks {
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.AllMusicClient;
import net.minecraft.client.gui.Gui;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.resources.sounds.SoundInstance;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
@@ -1,6 +1,6 @@
{
"required": true,
"package": "com.coloryr.allmusic.client.mixin",
"package": "com.coloryr.allmusic.client.mixins",
"compatibilityLevel": "JAVA_17",
"client": [
"GuiShow",
+2 -2
View File
@@ -8,9 +8,9 @@ java.targetCompatibility = JavaVersion.VERSION_21
dependencies {
minecraft("com.mojang:minecraft:1.21")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.16.10")
modImplementation("net.fabricmc:fabric-loader:0.19.3")
modImplementation("net.fabricmc.fabric-api:fabric-api:0.100.1+1.21")
modImplementation("net.fabricmc.fabric-api:fabric-api:0.102.0+1.21")
}
tasks {
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.AllMusicClient;
import net.minecraft.client.DeltaTracker;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.resources.sounds.SoundInstance;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
@@ -1,6 +1,6 @@
{
"required": true,
"package": "com.coloryr.allmusic.client.mixin",
"package": "com.coloryr.allmusic.client.mixins",
"compatibilityLevel": "JAVA_21",
"client": [
"GuiShow",
+2 -2
View File
@@ -8,9 +8,9 @@ java.targetCompatibility = JavaVersion.VERSION_21
dependencies {
minecraft("com.mojang:minecraft:1.21.11")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.18.6")
modImplementation("net.fabricmc:fabric-loader:0.19.3")
modImplementation("net.fabricmc.fabric-api:fabric-api:0.141.3+1.21.11")
modImplementation("net.fabricmc.fabric-api:fabric-api:0.141.4+1.21.11")
compileOnly("icyllis.modernui:ModernUI-Fabric:26.1.2-3.13.0.4")
}
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.AllMusicClient;
import net.minecraft.client.DeltaTracker;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.resources.sounds.SoundInstance;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
@@ -1,6 +1,6 @@
{
"required": true,
"package": "com.coloryr.allmusic.client.mixin",
"package": "com.coloryr.allmusic.client.mixins",
"compatibilityLevel": "JAVA_21",
"client": [
"GuiShow",
+35
View File
@@ -0,0 +1,35 @@
plugins {
id("fabric-loom") version Versions.fabricLoom
}
java.sourceCompatibility = JavaVersion.VERSION_21
java.targetCompatibility = JavaVersion.VERSION_21
dependencies {
minecraft("com.mojang:minecraft:1.21.3")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.19.3")
modImplementation("net.fabricmc.fabric-api:fabric-api:0.114.1+1.21.3")
}
tasks {
processResources {
filesMatching("fabric.mod.json") {
expand(
"version" to project.version
)
}
}
remapJar {
inputFile.set(shadowJar.get().archiveFile)
archiveFileName.set("[fabric-1.21.3]AllMusic_Client-${project.version}.jar")
destinationDirectory.set(file("${parent!!.projectDir}/../build"))
}
build {
dependsOn(remapJar)
}
}
@@ -0,0 +1,127 @@
package com.coloryr.allmusic.client;
import com.coloryr.allmusic.client.core.AllMusicBridge;
import com.coloryr.allmusic.client.core.AllMusicCore;
import com.coloryr.allmusic.client.core.render.PictureFrameBuffer;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.client.core.render.TextureRender;
import com.coloryr.allmusic.comm.MusicCodec;
import com.mojang.blaze3d.systems.RenderSystem;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.sounds.SoundSource;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
public static final String MODID = "allmusic_client";
public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath("allmusic", "channel");
public static final Logger LOGGER = LogManager.getLogger("AllMusic Client");
public static GuiGraphics context;
public static void update(GuiGraphics draw) {
context = draw;
AllMusicCore.hudUpdate();
}
public int getScreenWidth() {
return Minecraft.getInstance().getWindow().getGuiScaledWidth();
}
public int getScreenHeight() {
return Minecraft.getInstance().getWindow().getGuiScaledHeight();
}
public int getTextWidth(String item) {
return Minecraft.getInstance().font.width(item);
}
public int getFontHeight() {
return Minecraft.getInstance().font.lineHeight;
}
public void sendMessage(String data) {
data = "[AllMusic Client]" + data;
LOGGER.warn(data);
String finalData = data;
Minecraft.getInstance().execute(() -> {
if (Minecraft.getInstance().player == null)
return;
Minecraft.getInstance().player.displayClientMessage(Component.literal(finalData), false);
});
}
public float getVolume() {
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
}
@Override
public void stopPlayMusic() {
Minecraft.getInstance().getSoundManager().stop(null, SoundSource.MUSIC);
Minecraft.getInstance().getSoundManager().stop(null, SoundSource.RECORDS);
}
@Override
public TextFrameBuffer makeTextRender(String name) {
return new CoreRenderTarget(name);
}
@Override
public TextureRender makeTextureRender(String file) {
return new TexRender(file);
}
@Override
public PictureFrameBuffer makePictureRender(int size) {
return new PicRender(size);
}
@Override
public String readText(String file) {
try {
Resource resource = Minecraft.getInstance().getResourceManager().getResource(ResourceLocation.fromNamespaceAndPath(MODID, file)).orElseThrow();
try (InputStream inputStream = resource.open()) {
byte[] bytes = inputStream.readAllBytes();
return new String(bytes, StandardCharsets.UTF_8);
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public InputStream readFile(String file) {
try {
Resource resource = Minecraft.getInstance().getResourceManager().getResource(ResourceLocation.fromNamespaceAndPath(MODID, file)).orElseThrow();
return resource.open();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public void onInitializeClient() {
ClientPlayNetworking.registerGlobalReceiver(MusicCodec.ID, (pack, handler) -> {
try {
AllMusicCore.packDo(pack.pack());
} catch (Exception e) {
e.printStackTrace();
}
});
AllMusicCore.init(FabricLoader.getInstance().getConfigDir(), this);
RenderSystem.recordRenderCall(() -> AllMusicCore.renderInit());
}
}
@@ -0,0 +1,172 @@
package com.coloryr.allmusic.client;
import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.codec.HudPosType;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component;
import java.util.ArrayList;
import java.util.List;
public class CoreRenderTarget extends TextFrameBuffer {
private static class TextEntry {
final Component component;
final int y;
final int color;
final boolean shadow;
final int width;
final int height;
TextEntry(Component component, int y, int color, boolean shadow, int width, int height) {
this.component = component;
this.y = y;
this.color = color;
this.shadow = shadow;
this.width = width;
this.height = height;
}
}
private final List<TextEntry> entries = new ArrayList<>();
private final boolean isState;
public CoreRenderTarget(String name) {
isState = name.equals("state");
}
@Override
public void resize(int width, int height) {
nowWidth = width;
nowHeight = height;
}
@Override
public void use() {
isDraw = true;
clear();
entries.clear();
}
@Override
public void unUse() {
isDraw = false;
}
@Override
public void drawText(String text, int y, int color, boolean shadow) {
color = (color & 0x00FFFFFF) | 0xFF000000;
Component component = MiniMessage.parse(text);
Font font = Minecraft.getInstance().font;
int width = font.width(component);
int height = font.lineHeight + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
entries.add(new TextEntry(component, y, color, shadow, width, height));
texts.add(new TextItem(width, height, y, 1.0f));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
int scrollOffset = (int) (offsetX % entry.width);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - scrollOffset, drawY, finalColor, entry.shadow);
if (scrollOffset > 0) {
gui.drawString(font, entry.component, drawX - scrollOffset + entry.width, drawY, finalColor, entry.shadow);
}
gui.disableScissor();
} else {
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
}
}
}
@Override
public void drawLine(float x, float y, float alpha, int line) {
if (texts.isEmpty()) {
return;
}
if (line >= texts.size()) {
return;
}
if (line >= entries.size()) return;
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
TextEntry entry = entries.get(line);
gui.drawString(Minecraft.getInstance().font, entry.component,
(int) x, (int) (y + entry.y),
applyAlpha(entry.color, alpha), entry.shadow);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
float maxOffset = entry.width - maxWidth;
float texOffset = maxOffset * state;
int revealWidth = (int) (maxWidth * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - (int) texOffset, drawY, finalColor, entry.shadow);
gui.disableScissor();
} else {
int revealWidth = (int) (entry.width * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
gui.disableScissor();
}
}
}
@Override
public Point2f getLine(int line) {
if (line >= texts.size()) {
return new Point2f(0, 0);
}
if (line >= entries.size()) return new Point2f(0, 0);
TextEntry entry = entries.get(line);
return new Point2f(entry.width, entry.height);
}
private static int applyAlpha(int color, float alpha) {
int a = Math.clamp((int) (alpha * 255), 0, 255);
return (color & 0x00FFFFFF) | (a << 24);
}
}
@@ -0,0 +1,648 @@
package com.coloryr.allmusic.client;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.Style;
import net.minecraft.network.chat.TextColor;
import net.minecraft.resources.ResourceLocation;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MiniMessage {
// 正则表达式匹配标签
private static final Pattern TAG_PATTERN = Pattern.compile(
"<(/?)(\\w+)(?:\\(([^)]+)\\))?(?::([^>]+))?>"
);
// 颜色映射表
private static final Map<String, ChatFormatting> COLOR_MAP = new HashMap<>();
// 装饰器映射
private static final Map<String, ChatFormatting> DECORATION_MAP = new HashMap<>();
static {
COLOR_MAP.put("black", ChatFormatting.BLACK);
COLOR_MAP.put("dark_blue", ChatFormatting.DARK_BLUE);
COLOR_MAP.put("dark_green", ChatFormatting.DARK_GREEN);
COLOR_MAP.put("dark_aqua", ChatFormatting.DARK_AQUA);
COLOR_MAP.put("dark_red", ChatFormatting.DARK_RED);
COLOR_MAP.put("dark_purple", ChatFormatting.DARK_PURPLE);
COLOR_MAP.put("gold", ChatFormatting.GOLD);
COLOR_MAP.put("gray", ChatFormatting.GRAY);
COLOR_MAP.put("dark_gray", ChatFormatting.DARK_GRAY);
COLOR_MAP.put("blue", ChatFormatting.BLUE);
COLOR_MAP.put("green", ChatFormatting.GREEN);
COLOR_MAP.put("aqua", ChatFormatting.AQUA);
COLOR_MAP.put("red", ChatFormatting.RED);
COLOR_MAP.put("light_purple", ChatFormatting.LIGHT_PURPLE);
COLOR_MAP.put("yellow", ChatFormatting.YELLOW);
COLOR_MAP.put("white", ChatFormatting.WHITE);
}
static {
DECORATION_MAP.put("bold", ChatFormatting.BOLD);
DECORATION_MAP.put("italic", ChatFormatting.ITALIC);
DECORATION_MAP.put("underlined", ChatFormatting.UNDERLINE);
DECORATION_MAP.put("strikethrough", ChatFormatting.STRIKETHROUGH);
DECORATION_MAP.put("obfuscated", ChatFormatting.OBFUSCATED);
}
/**
* 解析 MiniMessage 字符串为 Component
*/
public static Component parse(String input) {
MutableComponent root = Component.literal("");
Stack<StyleContext> styleStack = new Stack<>();
StyleContext currentStyle = new StyleContext();
styleStack.push(currentStyle);
StringBuilder textBuffer = new StringBuilder();
int pos = 0;
int length = input.length();
while (pos < length) {
char c = input.charAt(pos);
if (c == '<') {
// 先处理已积累的文本
if (textBuffer.length() > 0) {
appendText(root, textBuffer.toString(), currentStyle);
textBuffer.setLength(0);
}
// 查找标签结束位置
int endTag = findClosingTag(input, pos);
if (endTag == -1) {
// 没有找到闭合标签,当作普通文本处理
textBuffer.append(c);
pos++;
continue;
}
String tagContent = input.substring(pos, endTag + 1);
Matcher matcher = TAG_PATTERN.matcher(tagContent);
if (matcher.matches()) {
boolean isClosing = !matcher.group(1).isEmpty();
String tagName = matcher.group(2).toLowerCase(Locale.ROOT);
String params = matcher.group(3);
String value = matcher.group(4);
if (isClosing) {
// 关闭标签 - 只关闭对应的开始标签
String expectedOpenTag = matcher.group(2).toLowerCase(Locale.ROOT);
// 查找对应的开始标签并关闭
if (styleStack.size() > 1) {
// 从栈顶向下查找匹配的开始标签
int index = styleStack.size() - 1;
boolean found = false;
while (index >= 0) {
StyleContext ctx = styleStack.get(index);
if (ctx.tagName != null && ctx.tagName.equals(expectedOpenTag)) {
// 找到匹配的开始标签,关闭到这一层
while (styleStack.size() > index + 1) {
styleStack.pop();
}
currentStyle = styleStack.peek();
found = true;
break;
}
index--;
}
if (!found) {
// 没找到匹配的标签,忽略此关闭标签
}
}
} else {
// 处理各种标签
StyleContext newStyle = currentStyle.copy();
switch (tagName) {
case "color":
handleColorTag(newStyle, params, value);
break;
case "shadow":
handleShadowTag(newStyle, params, value);
break;
case "decoration":
handleDecorationTag(newStyle, params, value);
break;
case "reset":
newStyle = new StyleContext();
break;
case "rainbow":
handleRainbowTag(newStyle, params, value);
break;
case "gradient":
handleGradientTag(newStyle, params, value);
break;
case "transition":
handleTransitionTag(newStyle, params, value);
break;
case "font":
handleFontTag(newStyle, params, value);
break;
default:
// 未知标签,忽略
break;
}
// 记录标签名称以便关闭时匹配
newStyle.tagName = tagName;
styleStack.push(newStyle);
currentStyle = newStyle;
}
} else {
// 无效标签,当作普通文本
textBuffer.append(tagContent);
}
pos = endTag + 1;
} else {
textBuffer.append(c);
pos++;
}
}
// 处理剩余的文本
if (textBuffer.length() > 0) {
appendText(root, textBuffer.toString(), currentStyle);
}
return root;
}
/**
* 处理颜色标签
* 支持格式: <color:red>, <color:#FF0000>, <color(rgb):255,0,0>
*/
private static void handleColorTag(StyleContext style, String params, String value) {
if (value != null) {
// 格式: <color:red> 或 <color:#FF0000>
TextColor textColor = parseColor(value);
if (textColor != null) {
style.color = textColor;
// 清除渐变、过渡和彩虹效果
style.gradient = false;
style.transition = false;
style.rainbow = false;
}
} else if (params != null) {
// 格式: <color(rgb):255,0,0>
if (params.equalsIgnoreCase("rgb")) {
String[] parts = value.split(",");
if (parts.length == 3) {
try {
int r = Integer.parseInt(parts[0].trim());
int g = Integer.parseInt(parts[1].trim());
int b = Integer.parseInt(parts[2].trim());
style.color = TextColor.fromRgb((r << 16) | (g << 8) | b);
// 清除渐变、过渡和彩虹效果
style.gradient = false;
style.transition = false;
style.rainbow = false;
} catch (NumberFormatException e) {
// 忽略解析错误
}
}
}
}
}
/**
* 处理阴影标签
* 支持格式: <shadow>, <shadow:true>, <shadow:false>, <shadow:#FF0000>
*/
private static void handleShadowTag(StyleContext style, String params, String value) {
if (value == null) {
style.shadow = true;
} else if (value.equalsIgnoreCase("true")) {
style.shadow = true;
} else if (value.equalsIgnoreCase("false")) {
style.shadow = false;
} else {
// 自定义阴影颜色
TextColor shadowColor = parseColor(value);
if (shadowColor != null) {
style.shadow = true;
style.shadowColor = shadowColor;
}
}
}
/**
* 处理装饰标签
* 支持格式: <decoration:bold>, <decoration:bold:true>, <decoration:bold:false>
*/
private static void handleDecorationTag(StyleContext style, String params, String value) {
if (params == null) return;
String[] parts = params.split(":");
String decorationName = parts[0].toLowerCase(Locale.ROOT);
boolean enabled = true;
if (parts.length > 1) {
enabled = Boolean.parseBoolean(parts[1]);
} else if (value != null) {
enabled = Boolean.parseBoolean(value);
}
ChatFormatting formatting = DECORATION_MAP.get(decorationName);
if (formatting != null) {
style.decorations.put(formatting, enabled);
}
}
/**
* 处理彩虹色标签
* 支持格式: <rainbow>, <rainbow:!>, <rainbow(saturation):0.8>, <rainbow(brightness):0.5>
*/
private static void handleRainbowTag(StyleContext style, String params, String value) {
float saturation = 1.0f;
float brightness = 1.0f;
if (params != null) {
if (params.equalsIgnoreCase("saturation") && value != null) {
try {
saturation = Float.parseFloat(value);
} catch (NumberFormatException e) {
}
} else if (params.equalsIgnoreCase("brightness") && value != null) {
try {
brightness = Float.parseFloat(value);
} catch (NumberFormatException e) {
}
}
}
style.rainbow = true;
style.gradient = false;
style.transition = false;
style.rainbowSaturation = saturation;
style.rainbowBrightness = brightness;
}
/**
* 处理渐变色标签
* 支持格式: <gradient:#FF0000:#00FF00>, <gradient(rgb):255,0,0:0,255,0>
* 渐变是逐字符过渡
*/
private static void handleGradientTag(StyleContext style, String params, String value) {
if (value == null) return;
String[] colors = value.split(":");
if (colors.length == 2) {
TextColor start = parseColor(colors[0]);
TextColor end = parseColor(colors[1]);
if (start != null && end != null) {
style.gradient = true;
style.transition = false;
style.rainbow = false;
style.gradientStart = start;
style.gradientEnd = end;
}
} else if (params != null && params.equalsIgnoreCase("rgb")) {
// 处理 RGB 格式
String[] color1 = colors[0].split(",");
String[] color2 = colors[1].split(",");
if (color1.length == 3 && color2.length == 3) {
try {
int r1 = Integer.parseInt(color1[0].trim());
int g1 = Integer.parseInt(color1[1].trim());
int b1 = Integer.parseInt(color1[2].trim());
int r2 = Integer.parseInt(color2[0].trim());
int g2 = Integer.parseInt(color2[1].trim());
int b2 = Integer.parseInt(color2[2].trim());
style.gradient = true;
style.transition = false;
style.rainbow = false;
style.gradientStart = TextColor.fromRgb((r1 << 16) | (g1 << 8) | b1);
style.gradientEnd = TextColor.fromRgb((r2 << 16) | (g2 << 8) | b2);
} catch (NumberFormatException e) {
}
}
}
}
/**
* 处理过渡色标签
* 支持格式: <transition:#FF0000:#00FF00>, <transition(rgb):255,0,0:0,255,0>
* 过渡是整个文本块的颜色过渡(类似整体渐变效果)
*/
private static void handleTransitionTag(StyleContext style, String params, String value) {
if (value == null) return;
String[] colors = value.split(":");
if (colors.length == 2) {
TextColor start = parseColor(colors[0]);
TextColor end = parseColor(colors[1]);
if (start != null && end != null) {
style.transition = true;
style.gradient = false;
style.rainbow = false;
style.transitionStart = start;
style.transitionEnd = end;
}
} else if (params != null && params.equalsIgnoreCase("rgb")) {
// 处理 RGB 格式
String[] color1 = colors[0].split(",");
String[] color2 = colors[1].split(",");
if (color1.length == 3 && color2.length == 3) {
try {
int r1 = Integer.parseInt(color1[0].trim());
int g1 = Integer.parseInt(color1[1].trim());
int b1 = Integer.parseInt(color1[2].trim());
int r2 = Integer.parseInt(color2[0].trim());
int g2 = Integer.parseInt(color2[1].trim());
int b2 = Integer.parseInt(color2[2].trim());
style.transition = true;
style.gradient = false;
style.rainbow = false;
style.transitionStart = TextColor.fromRgb((r1 << 16) | (g1 << 8) | b1);
style.transitionEnd = TextColor.fromRgb((r2 << 16) | (g2 << 8) | b2);
} catch (NumberFormatException e) {
}
}
}
}
/**
* 处理字体标签
* 支持格式: <font:minecraft:default>, <font:minecraft:uniform>
*/
private static void handleFontTag(StyleContext style, String params, String value) {
String fontName = value != null ? value : params;
if (fontName != null) {
style.font = fontName;
}
}
/**
* 解析颜色字符串
* 支持: 颜色名称, #RRGGBB, #RGB, 十六进制数值
*/
private static TextColor parseColor(String colorStr) {
if (colorStr == null) return null;
// 检查是否为预定义颜色名称
ChatFormatting formatting = COLOR_MAP.get(colorStr.toLowerCase(Locale.ROOT));
if (formatting != null) {
return TextColor.fromLegacyFormat(formatting);
}
// 检查十六进制格式
if (colorStr.startsWith("#")) {
try {
if (colorStr.length() == 7) {
return TextColor.parseColor(colorStr).getOrThrow();
} else if (colorStr.length() == 4) {
// 扩展 #RGB 到 #RRGGBB
String expanded = "#" +
colorStr.charAt(1) + colorStr.charAt(1) +
colorStr.charAt(2) + colorStr.charAt(2) +
colorStr.charAt(3) + colorStr.charAt(3);
return TextColor.parseColor(expanded).getOrThrow();
}
} catch (Exception e) {
// 忽略解析错误
}
}
return null;
}
/**
* 查找标签闭合位置
*/
private static int findClosingTag(String input, int start) {
int pos = start + 1;
while (pos < input.length()) {
char c = input.charAt(pos);
if (c == '>') {
return pos;
}
pos++;
}
return -1;
}
/**
* 添加文本到组件
*/
private static void appendText(MutableComponent root, String text, StyleContext styleContext) {
if (text.isEmpty()) return;
// 如果有渐变效果,需要逐字符处理
if (styleContext.gradient && styleContext.gradientStart != null && styleContext.gradientEnd != null) {
appendGradientText(root, text, styleContext);
}
// 如果有过渡效果,需要逐字符处理
else if (styleContext.transition && styleContext.transitionStart != null && styleContext.transitionEnd != null) {
appendTransitionText(root, text, styleContext);
}
// 如果有彩虹效果,需要逐字符处理
else if (styleContext.rainbow) {
appendRainbowText(root, text, styleContext);
}
// 普通文本
else {
MutableComponent component = Component.literal(text);
component.setStyle(styleContext.buildStyle());
root.append(component);
}
}
/**
* 带渐变效果的文本追加(逐字符颜色变化)
*/
private static void appendGradientText(MutableComponent root, String text, StyleContext styleContext) {
int length = text.length();
Style baseStyle = styleContext.buildBaseStyle();
for (int i = 0; i < length; i++) {
char c = text.charAt(i);
float ratio = (float) i / (length - 1);
TextColor gradientColor = interpolateColor(styleContext.gradientStart, styleContext.gradientEnd, ratio);
Style charStyle = baseStyle.withColor(gradientColor);
// 重新应用阴影
// if (styleContext.shadow != null && styleContext.shadow) {
// if (styleContext.shadowColor != null) {
// charStyle = charStyle.withShadowColor(styleContext.shadowColor.getValue() | 0xFF000000);
// } else {
// charStyle = charStyle.withShadowColor(0xFF000000);
// }
// }
root.append(Component.literal(String.valueOf(c)).withStyle(charStyle));
}
}
/**
* 带过渡效果的文本追加(整体颜色过渡)
*/
private static void appendTransitionText(MutableComponent root, String text, StyleContext styleContext) {
Style baseStyle = styleContext.buildBaseStyle();
// 过渡效果:为整个文本块创建一个颜色过渡
MutableComponent transitionComponent = Component.literal(text);
// 应用过渡样式(这里使用颜色插值,但也可以根据需求调整)
if (styleContext.transitionStart != null && styleContext.transitionEnd != null) {
// 可以使用整体渐变或者固定起始色
transitionComponent.setStyle(baseStyle.withColor(styleContext.transitionStart));
} else {
transitionComponent.setStyle(baseStyle);
}
root.append(transitionComponent);
}
/**
* 带彩虹效果的文本追加
*/
private static void appendRainbowText(MutableComponent root, String text, StyleContext styleContext) {
int length = text.length();
Style baseStyle = styleContext.buildBaseStyle();
for (int i = 0; i < length; i++) {
char c = text.charAt(i);
float hue = (float) i / length;
int rgb = java.awt.Color.HSBtoRGB(hue, styleContext.rainbowSaturation, styleContext.rainbowBrightness);
TextColor rainbowColor = TextColor.fromRgb(rgb & 0xFFFFFF);
Style charStyle = baseStyle.withColor(rainbowColor);
// 重新应用阴影
// if (styleContext.shadow != null && styleContext.shadow) {
// if (styleContext.shadowColor != null) {
// charStyle = charStyle.withShadowColor(styleContext.shadowColor.getValue() | 0xFF000000);
// } else {
// charStyle = charStyle.withShadowColor(0xFF000000);
// }
// }
root.append(Component.literal(String.valueOf(c)).withStyle(charStyle));
}
}
/**
* 颜色插值
*/
private static TextColor interpolateColor(TextColor start, TextColor end, float ratio) {
int startRgb = start.getValue();
int endRgb = end.getValue();
int r1 = (startRgb >> 16) & 0xFF;
int g1 = (startRgb >> 8) & 0xFF;
int b1 = startRgb & 0xFF;
int r2 = (endRgb >> 16) & 0xFF;
int g2 = (endRgb >> 8) & 0xFF;
int b2 = endRgb & 0xFF;
int r = (int) (r1 + (r2 - r1) * ratio);
int g = (int) (g1 + (g2 - g1) * ratio);
int b = (int) (b1 + (b2 - b1) * ratio);
return TextColor.fromRgb((r << 16) | (g << 8) | b);
}
/**
* 样式上下文类,用于追踪嵌套样式
*/
private static class StyleContext {
String tagName = null; // 记录标签名称,用于关闭匹配
TextColor color = null;
Boolean shadow = null;
TextColor shadowColor = null;
Map<ChatFormatting, Boolean> decorations = new HashMap<>();
boolean rainbow = false;
float rainbowSaturation = 1.0f;
float rainbowBrightness = 1.0f;
boolean gradient = false;
TextColor gradientStart = null;
TextColor gradientEnd = null;
boolean transition = false;
TextColor transitionStart = null;
TextColor transitionEnd = null;
String font = null;
StyleContext copy() {
StyleContext copy = new StyleContext();
copy.tagName = this.tagName;
copy.color = this.color;
copy.shadow = this.shadow;
copy.shadowColor = this.shadowColor;
copy.decorations.putAll(this.decorations);
copy.rainbow = this.rainbow;
copy.rainbowSaturation = this.rainbowSaturation;
copy.rainbowBrightness = this.rainbowBrightness;
copy.gradient = this.gradient;
copy.gradientStart = this.gradientStart;
copy.gradientEnd = this.gradientEnd;
copy.transition = this.transition;
copy.transitionStart = this.transitionStart;
copy.transitionEnd = this.transitionEnd;
copy.font = this.font;
return copy;
}
/**
* 构建基础样式(不含颜色)
*/
Style buildBaseStyle() {
Style style = Style.EMPTY;
// 应用装饰器
for (Map.Entry<ChatFormatting, Boolean> entry : decorations.entrySet()) {
if (entry.getValue()) {
style = style.applyFormat(entry.getKey());
}
}
if (font != null) {
try {
style = style.withFont(ResourceLocation.parse(font));
} catch (Exception e) {
// 忽略字体解析错误
}
}
return style;
}
/**
* 构建完整样式(含颜色)
*/
Style buildStyle() {
Style style = buildBaseStyle();
// 只有在没有渐变、过渡和彩虹效果时才应用普通颜色
if (!gradient && !transition && !rainbow && color != null) {
style = style.withColor(color);
}
// if (shadow != null && shadow) {
// if (shadowColor != null) {
// style = style.withShadowColor(shadowColor.getValue() | 0xFF000000);
// } else {
// style = style.withShadowColor(0xFF000000);
// }
// }
return style;
}
}
}
@@ -0,0 +1,88 @@
package com.coloryr.allmusic.client;
import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.PictureFrameBuffer;
import com.coloryr.allmusic.client.mixins.GuiRender;
import com.coloryr.allmusic.codec.HudPosType;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.resources.ResourceLocation;
import org.joml.Matrix4f;
import org.joml.Quaternionf;
import java.io.ByteArrayInputStream;
public class PicRender extends PictureFrameBuffer {
private static final ResourceLocation SOURCE_LOC = ResourceLocation.fromNamespaceAndPath("allmusic_client", "pic_source");
private static final ResourceLocation ROTATE_LOC = ResourceLocation.fromNamespaceAndPath("allmusic_client", "pic_rotate");
private final DynamicTexture sourceTexture;
private final DynamicTexture rotateTexture;
public PicRender(int size) {
sourceTexture = new DynamicTexture(size, size, false);
rotateTexture = new DynamicTexture(size, size, false);
Minecraft.getInstance().getTextureManager().register(SOURCE_LOC, sourceTexture);
Minecraft.getInstance().getTextureManager().register(ROTATE_LOC, rotateTexture);
}
@Override
public void update(byte[] source, byte[] rotate) {
try {
ByteArrayInputStream stream = new ByteArrayInputStream(source);
var image1 = NativeImage.read(stream);
sourceTexture.setPixels(image1);
sourceTexture.upload();
stream = new ByteArrayInputStream(rotate);
var image2 = NativeImage.read(stream);
rotateTexture.setPixels(image2);
rotateTexture.upload();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void draw(boolean rotate, int size, float x, float y, int ang, HudPosType dir, float alpha) {
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
GuiRender render = (GuiRender) gui;
ResourceLocation loc = rotate ? ROTATE_LOC : SOURCE_LOC;
Point2f point = AllMusicHud.getPos(size, size, x, y, dir);
Matrix4f matrix;
int a = size / 2;
if (rotate) {
matrix = new Matrix4f().translationRotate(point.x + a, point.y + a, 0,
new Quaternionf().fromAxisAngleDeg(0, 0, 1, ang));
} else {
matrix = new Matrix4f().translation(point.x + a, point.y + a, 0);
}
int x0 = -a;
int x1 = a;
int y0 = -a;
int y1 = a;
int z = 0;
int u0 = 0;
float u1 = 1;
float v0 = 0;
float v1 = 1;
RenderType renderType = RenderType.guiTexturedOverlay(loc);
VertexConsumer bufferBuilder = render.getBufferSource().getBuffer(renderType);
bufferBuilder.addVertex(matrix, x0, y1, z).setUv(u0, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y1, z).setUv(u1, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y0, z).setUv(u1, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x0, y0, z).setUv(u0, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
}
}
@@ -0,0 +1,126 @@
package com.coloryr.allmusic.client;
import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextureRender;
import com.coloryr.allmusic.client.mixins.GuiRender;
import com.coloryr.allmusic.codec.HudPosType;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import org.joml.Matrix4f;
import java.io.IOException;
public class TexRender extends TextureRender {
private final ResourceLocation location;
public TexRender(String texture) {
super(texture);
location = ResourceLocation.fromNamespaceAndPath(AllMusicClient.MODID, texture);
try {
ResourceManager resourceManager = Minecraft.getInstance().getResourceManager();
var resource = resourceManager.getResource(location).orElseThrow();
try (var input = resource.open()) {
var nativeImage = NativeImage.read(input);
width = nativeImage.getWidth();
height = nativeImage.getHeight();
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void drawPic(float x, float y, float alpha) {
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
GuiRender render = (GuiRender) gui;
float w1 = (float) width / 2;
float h1 = (float) height / 2;
Matrix4f matrix = new Matrix4f().translation(x + w1, y + h1, 0);
float x0 = -w1;
float x1 = w1;
float y0 = -h1;
float y1 = h1;
float z = 0;
float u0 = 0;
float u1 = 1;
float v0 = 0;
float v1 = 1;
RenderType renderType = RenderType.guiTexturedOverlay(location);
VertexConsumer bufferBuilder = render.getBufferSource().getBuffer(renderType);
bufferBuilder.addVertex(matrix, x0, y1, z).setUv(u0, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y1, z).setUv(u1, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y0, z).setUv(u1, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x0, y0, z).setUv(u0, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
}
@Override
public void drawPic(float x, float y, float width, float alpha) {
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
GuiRender render = (GuiRender) gui;
float w1 = (float) (this.width / 2) * width;
float h1 = (float) height / 2;
Matrix4f matrix = new Matrix4f().translation(x + w1, y + h1, 0);
float x0 = -w1;
float x1 = w1;
float y0 = -h1;
float y1 = h1;
float z = 0;
float u0 = 0;
float u1 = width;
float v0 = 0;
float v1 = 1;
RenderType renderType = RenderType.guiTexturedOverlay(location);
VertexConsumer bufferBuilder = render.getBufferSource().getBuffer(renderType);
bufferBuilder.addVertex(matrix, x0, y1, z).setUv(u0, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y1, z).setUv(u1, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y0, z).setUv(u1, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x0, y0, z).setUv(u0, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
}
@Override
public void drawPic(float x, float y, float width, float height, HudPosType dir, float alpha) {
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
GuiRender render = (GuiRender) gui;
Point2f point = AllMusicHud.getPos(width, height, x, y, dir);
float w1 = width / 2;
float h1 = height / 2;
Matrix4f matrix = new Matrix4f().translation(point.x + w1, point.y + h1, 0);
float x0 = -w1;
float x1 = w1;
float y0 = -h1;
float y1 = h1;
float z = 0;
float u0 = 0;
float u1 = 1;
float v0 = 0;
float v1 = 1;
RenderType renderType = RenderType.guiTexturedOverlay(location);
VertexConsumer bufferBuilder = render.getBufferSource().getBuffer(renderType);
bufferBuilder.addVertex(matrix, x0, y1, z).setUv(u0, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y1, z).setUv(u1, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y0, z).setUv(u1, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x0, y0, z).setUv(u0, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
}
}
@@ -0,0 +1,12 @@
package com.coloryr.allmusic.client.mixins;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.MultiBufferSource;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(GuiGraphics.class)
public interface GuiRender {
@Accessor("bufferSource")
MultiBufferSource.BufferSource getBufferSource();
}
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.AllMusicClient;
import net.minecraft.client.DeltaTracker;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
@@ -0,0 +1,38 @@
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.resources.sounds.SoundInstance;
import net.minecraft.client.sounds.SoundManager;
import net.minecraft.sounds.SoundSource;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(SoundManager.class)
public class SoundEvent {
@Inject(method = "play", at = @At("HEAD"), cancellable = true)
public void play(SoundInstance soundInstance, CallbackInfo ci) {
if (AllMusicCore.isPlay()) {
SoundSource data = soundInstance.getSource();
switch (data) {
case RECORDS, MUSIC -> ci.cancel();
}
}
}
@Inject(method = "playDelayed", at = @At("HEAD"), cancellable = true)
public void play(SoundInstance soundInstance, int delay, CallbackInfo info) {
if (AllMusicCore.isPlay()) {
SoundSource data = soundInstance.getSource();
switch (data) {
case RECORDS, MUSIC -> info.cancel();
}
}
}
@Inject(method = "reload", at = @At("RETURN"))
public void reload(CallbackInfo info) {
AllMusicCore.reload();
}
}
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
@@ -0,0 +1,11 @@
package com.coloryr.allmusic.comm;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
public class AllMusicInit implements ModInitializer {
@Override
public void onInitialize() {
PayloadTypeRegistry.playS2C().register(MusicCodec.ID, MusicCodec.CODEC);
}
}
@@ -0,0 +1,22 @@
package com.coloryr.allmusic.comm;
import com.coloryr.allmusic.codec.MusicPack;
import com.coloryr.allmusic.codec.MusicPacketCodec;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
public record MusicCodec(MusicPack pack) implements CustomPacketPayload {
public static final ResourceLocation ChannelID = ResourceLocation.fromNamespaceAndPath("allmusic", "channel");
public static final Type<MusicCodec> ID = new Type<>(ChannelID);
public static final StreamCodec<FriendlyByteBuf, MusicCodec> CODEC =
StreamCodec.of((buf, value) -> MusicPacketCodec.pack(buf, value.pack),
buf -> new MusicCodec(MusicPacketCodec.decode(buf)));
@Override
public Type<? extends CustomPacketPayload> type() {
return ID;
}
}
@@ -0,0 +1,15 @@
{
"required": true,
"package": "com.coloryr.allmusic.client.mixins",
"compatibilityLevel": "JAVA_21",
"client": [
"GuiRender",
"GuiShow",
"QuitServer",
"SoundEvent",
"Tick"
],
"injectors": {
"defaultRequire": 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@@ -0,0 +1,36 @@
{
"schemaVersion": 1,
"id": "allmusic_client",
"version": "${version}",
"name": "AllMusic Client",
"description": "AllMusic Client Mod",
"authors": [
"Color_yr"
],
"contact": {
"homepage": "https://github.com/Coloryr/AllMusic",
"sources": "https://github.com/Coloryr/AllMusic"
},
"license": "GPL-3.0",
"icon": "assets/allmusic_client/icon.png",
"environment": "*",
"entrypoints": {
"main": [
"com.coloryr.allmusic.comm.AllMusicInit"
],
"client": [
"com.coloryr.allmusic.client.AllMusicClient"
]
},
"mixins": [
"allmusic_client.mixins.json"
],
"depends": {
"fabricloader": ">=0.19.3",
"minecraft": ">=1.21.3",
"java": ">=21",
"fabric-api": "*"
},
"suggests": {
}
}
+35
View File
@@ -0,0 +1,35 @@
plugins {
id("fabric-loom") version Versions.fabricLoom
}
java.sourceCompatibility = JavaVersion.VERSION_21
java.targetCompatibility = JavaVersion.VERSION_21
dependencies {
minecraft("com.mojang:minecraft:1.21.4")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.19.3")
modImplementation("net.fabricmc.fabric-api:fabric-api:0.119.4+1.21.4")
}
tasks {
processResources {
filesMatching("fabric.mod.json") {
expand(
"version" to project.version
)
}
}
remapJar {
inputFile.set(shadowJar.get().archiveFile)
archiveFileName.set("[fabric-1.21.4]AllMusic_Client-${project.version}.jar")
destinationDirectory.set(file("${parent!!.projectDir}/../build"))
}
build {
dependsOn(remapJar)
}
}
@@ -0,0 +1,127 @@
package com.coloryr.allmusic.client;
import com.coloryr.allmusic.client.core.AllMusicBridge;
import com.coloryr.allmusic.client.core.AllMusicCore;
import com.coloryr.allmusic.client.core.render.PictureFrameBuffer;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.client.core.render.TextureRender;
import com.coloryr.allmusic.comm.MusicCodec;
import com.mojang.blaze3d.systems.RenderSystem;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.sounds.SoundSource;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
public static final String MODID = "allmusic_client";
public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath("allmusic", "channel");
public static final Logger LOGGER = LogManager.getLogger("AllMusic Client");
public static GuiGraphics context;
public static void update(GuiGraphics draw) {
context = draw;
AllMusicCore.hudUpdate();
}
public int getScreenWidth() {
return Minecraft.getInstance().getWindow().getGuiScaledWidth();
}
public int getScreenHeight() {
return Minecraft.getInstance().getWindow().getGuiScaledHeight();
}
public int getTextWidth(String item) {
return Minecraft.getInstance().font.width(item);
}
public int getFontHeight() {
return Minecraft.getInstance().font.lineHeight;
}
public void sendMessage(String data) {
data = "[AllMusic Client]" + data;
LOGGER.warn(data);
String finalData = data;
Minecraft.getInstance().execute(() -> {
if (Minecraft.getInstance().player == null)
return;
Minecraft.getInstance().player.displayClientMessage(Component.literal(finalData), false);
});
}
public float getVolume() {
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
}
@Override
public void stopPlayMusic() {
Minecraft.getInstance().getSoundManager().stop(null, SoundSource.MUSIC);
Minecraft.getInstance().getSoundManager().stop(null, SoundSource.RECORDS);
}
@Override
public TextFrameBuffer makeTextRender(String name) {
return new CoreRenderTarget(name);
}
@Override
public TextureRender makeTextureRender(String file) {
return new TexRender(file);
}
@Override
public PictureFrameBuffer makePictureRender(int size) {
return new PicRender(size);
}
@Override
public String readText(String file) {
try {
Resource resource = Minecraft.getInstance().getResourceManager().getResource(ResourceLocation.fromNamespaceAndPath(MODID, file)).orElseThrow();
try (InputStream inputStream = resource.open()) {
byte[] bytes = inputStream.readAllBytes();
return new String(bytes, StandardCharsets.UTF_8);
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public InputStream readFile(String file) {
try {
Resource resource = Minecraft.getInstance().getResourceManager().getResource(ResourceLocation.fromNamespaceAndPath(MODID, file)).orElseThrow();
return resource.open();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public void onInitializeClient() {
ClientPlayNetworking.registerGlobalReceiver(MusicCodec.ID, (pack, handler) -> {
try {
AllMusicCore.packDo(pack.pack());
} catch (Exception e) {
e.printStackTrace();
}
});
AllMusicCore.init(FabricLoader.getInstance().getConfigDir(), this);
RenderSystem.recordRenderCall(() -> AllMusicCore.renderInit());
}
}
@@ -0,0 +1,172 @@
package com.coloryr.allmusic.client;
import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.codec.HudPosType;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component;
import java.util.ArrayList;
import java.util.List;
public class CoreRenderTarget extends TextFrameBuffer {
private static class TextEntry {
final Component component;
final int y;
final int color;
final boolean shadow;
final int width;
final int height;
TextEntry(Component component, int y, int color, boolean shadow, int width, int height) {
this.component = component;
this.y = y;
this.color = color;
this.shadow = shadow;
this.width = width;
this.height = height;
}
}
private final List<TextEntry> entries = new ArrayList<>();
private final boolean isState;
public CoreRenderTarget(String name) {
isState = name.equals("state");
}
@Override
public void resize(int width, int height) {
nowWidth = width;
nowHeight = height;
}
@Override
public void use() {
isDraw = true;
clear();
entries.clear();
}
@Override
public void unUse() {
isDraw = false;
}
@Override
public void drawText(String text, int y, int color, boolean shadow) {
color = (color & 0x00FFFFFF) | 0xFF000000;
Component component = MiniMessage.parse(text);
Font font = Minecraft.getInstance().font;
int width = font.width(component);
int height = font.lineHeight + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
entries.add(new TextEntry(component, y, color, shadow, width, height));
texts.add(new TextItem(width, height, y, 1.0f));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
int scrollOffset = (int) (offsetX % entry.width);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - scrollOffset, drawY, finalColor, entry.shadow);
if (scrollOffset > 0) {
gui.drawString(font, entry.component, drawX - scrollOffset + entry.width, drawY, finalColor, entry.shadow);
}
gui.disableScissor();
} else {
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
}
}
}
@Override
public void drawLine(float x, float y, float alpha, int line) {
if (texts.isEmpty()) {
return;
}
if (line >= texts.size()) {
return;
}
if (line >= entries.size()) return;
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
TextEntry entry = entries.get(line);
gui.drawString(Minecraft.getInstance().font, entry.component,
(int) x, (int) (y + entry.y),
applyAlpha(entry.color, alpha), entry.shadow);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
float maxOffset = entry.width - maxWidth;
float texOffset = maxOffset * state;
int revealWidth = (int) (maxWidth * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - (int) texOffset, drawY, finalColor, entry.shadow);
gui.disableScissor();
} else {
int revealWidth = (int) (entry.width * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
gui.disableScissor();
}
}
}
@Override
public Point2f getLine(int line) {
if (line >= texts.size()) {
return new Point2f(0, 0);
}
if (line >= entries.size()) return new Point2f(0, 0);
TextEntry entry = entries.get(line);
return new Point2f(entry.width, entry.height);
}
private static int applyAlpha(int color, float alpha) {
int a = Math.clamp((int) (alpha * 255), 0, 255);
return (color & 0x00FFFFFF) | (a << 24);
}
}
@@ -0,0 +1,648 @@
package com.coloryr.allmusic.client;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.Style;
import net.minecraft.network.chat.TextColor;
import net.minecraft.resources.ResourceLocation;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MiniMessage {
// 正则表达式匹配标签
private static final Pattern TAG_PATTERN = Pattern.compile(
"<(/?)(\\w+)(?:\\(([^)]+)\\))?(?::([^>]+))?>"
);
// 颜色映射表
private static final Map<String, ChatFormatting> COLOR_MAP = new HashMap<>();
// 装饰器映射
private static final Map<String, ChatFormatting> DECORATION_MAP = new HashMap<>();
static {
COLOR_MAP.put("black", ChatFormatting.BLACK);
COLOR_MAP.put("dark_blue", ChatFormatting.DARK_BLUE);
COLOR_MAP.put("dark_green", ChatFormatting.DARK_GREEN);
COLOR_MAP.put("dark_aqua", ChatFormatting.DARK_AQUA);
COLOR_MAP.put("dark_red", ChatFormatting.DARK_RED);
COLOR_MAP.put("dark_purple", ChatFormatting.DARK_PURPLE);
COLOR_MAP.put("gold", ChatFormatting.GOLD);
COLOR_MAP.put("gray", ChatFormatting.GRAY);
COLOR_MAP.put("dark_gray", ChatFormatting.DARK_GRAY);
COLOR_MAP.put("blue", ChatFormatting.BLUE);
COLOR_MAP.put("green", ChatFormatting.GREEN);
COLOR_MAP.put("aqua", ChatFormatting.AQUA);
COLOR_MAP.put("red", ChatFormatting.RED);
COLOR_MAP.put("light_purple", ChatFormatting.LIGHT_PURPLE);
COLOR_MAP.put("yellow", ChatFormatting.YELLOW);
COLOR_MAP.put("white", ChatFormatting.WHITE);
}
static {
DECORATION_MAP.put("bold", ChatFormatting.BOLD);
DECORATION_MAP.put("italic", ChatFormatting.ITALIC);
DECORATION_MAP.put("underlined", ChatFormatting.UNDERLINE);
DECORATION_MAP.put("strikethrough", ChatFormatting.STRIKETHROUGH);
DECORATION_MAP.put("obfuscated", ChatFormatting.OBFUSCATED);
}
/**
* 解析 MiniMessage 字符串为 Component
*/
public static Component parse(String input) {
MutableComponent root = Component.literal("");
Stack<StyleContext> styleStack = new Stack<>();
StyleContext currentStyle = new StyleContext();
styleStack.push(currentStyle);
StringBuilder textBuffer = new StringBuilder();
int pos = 0;
int length = input.length();
while (pos < length) {
char c = input.charAt(pos);
if (c == '<') {
// 先处理已积累的文本
if (textBuffer.length() > 0) {
appendText(root, textBuffer.toString(), currentStyle);
textBuffer.setLength(0);
}
// 查找标签结束位置
int endTag = findClosingTag(input, pos);
if (endTag == -1) {
// 没有找到闭合标签,当作普通文本处理
textBuffer.append(c);
pos++;
continue;
}
String tagContent = input.substring(pos, endTag + 1);
Matcher matcher = TAG_PATTERN.matcher(tagContent);
if (matcher.matches()) {
boolean isClosing = !matcher.group(1).isEmpty();
String tagName = matcher.group(2).toLowerCase(Locale.ROOT);
String params = matcher.group(3);
String value = matcher.group(4);
if (isClosing) {
// 关闭标签 - 只关闭对应的开始标签
String expectedOpenTag = matcher.group(2).toLowerCase(Locale.ROOT);
// 查找对应的开始标签并关闭
if (styleStack.size() > 1) {
// 从栈顶向下查找匹配的开始标签
int index = styleStack.size() - 1;
boolean found = false;
while (index >= 0) {
StyleContext ctx = styleStack.get(index);
if (ctx.tagName != null && ctx.tagName.equals(expectedOpenTag)) {
// 找到匹配的开始标签,关闭到这一层
while (styleStack.size() > index + 1) {
styleStack.pop();
}
currentStyle = styleStack.peek();
found = true;
break;
}
index--;
}
if (!found) {
// 没找到匹配的标签,忽略此关闭标签
}
}
} else {
// 处理各种标签
StyleContext newStyle = currentStyle.copy();
switch (tagName) {
case "color":
handleColorTag(newStyle, params, value);
break;
case "shadow":
handleShadowTag(newStyle, params, value);
break;
case "decoration":
handleDecorationTag(newStyle, params, value);
break;
case "reset":
newStyle = new StyleContext();
break;
case "rainbow":
handleRainbowTag(newStyle, params, value);
break;
case "gradient":
handleGradientTag(newStyle, params, value);
break;
case "transition":
handleTransitionTag(newStyle, params, value);
break;
case "font":
handleFontTag(newStyle, params, value);
break;
default:
// 未知标签,忽略
break;
}
// 记录标签名称以便关闭时匹配
newStyle.tagName = tagName;
styleStack.push(newStyle);
currentStyle = newStyle;
}
} else {
// 无效标签,当作普通文本
textBuffer.append(tagContent);
}
pos = endTag + 1;
} else {
textBuffer.append(c);
pos++;
}
}
// 处理剩余的文本
if (textBuffer.length() > 0) {
appendText(root, textBuffer.toString(), currentStyle);
}
return root;
}
/**
* 处理颜色标签
* 支持格式: <color:red>, <color:#FF0000>, <color(rgb):255,0,0>
*/
private static void handleColorTag(StyleContext style, String params, String value) {
if (value != null) {
// 格式: <color:red> 或 <color:#FF0000>
TextColor textColor = parseColor(value);
if (textColor != null) {
style.color = textColor;
// 清除渐变、过渡和彩虹效果
style.gradient = false;
style.transition = false;
style.rainbow = false;
}
} else if (params != null) {
// 格式: <color(rgb):255,0,0>
if (params.equalsIgnoreCase("rgb")) {
String[] parts = value.split(",");
if (parts.length == 3) {
try {
int r = Integer.parseInt(parts[0].trim());
int g = Integer.parseInt(parts[1].trim());
int b = Integer.parseInt(parts[2].trim());
style.color = TextColor.fromRgb((r << 16) | (g << 8) | b);
// 清除渐变、过渡和彩虹效果
style.gradient = false;
style.transition = false;
style.rainbow = false;
} catch (NumberFormatException e) {
// 忽略解析错误
}
}
}
}
}
/**
* 处理阴影标签
* 支持格式: <shadow>, <shadow:true>, <shadow:false>, <shadow:#FF0000>
*/
private static void handleShadowTag(StyleContext style, String params, String value) {
if (value == null) {
style.shadow = true;
} else if (value.equalsIgnoreCase("true")) {
style.shadow = true;
} else if (value.equalsIgnoreCase("false")) {
style.shadow = false;
} else {
// 自定义阴影颜色
TextColor shadowColor = parseColor(value);
if (shadowColor != null) {
style.shadow = true;
style.shadowColor = shadowColor;
}
}
}
/**
* 处理装饰标签
* 支持格式: <decoration:bold>, <decoration:bold:true>, <decoration:bold:false>
*/
private static void handleDecorationTag(StyleContext style, String params, String value) {
if (params == null) return;
String[] parts = params.split(":");
String decorationName = parts[0].toLowerCase(Locale.ROOT);
boolean enabled = true;
if (parts.length > 1) {
enabled = Boolean.parseBoolean(parts[1]);
} else if (value != null) {
enabled = Boolean.parseBoolean(value);
}
ChatFormatting formatting = DECORATION_MAP.get(decorationName);
if (formatting != null) {
style.decorations.put(formatting, enabled);
}
}
/**
* 处理彩虹色标签
* 支持格式: <rainbow>, <rainbow:!>, <rainbow(saturation):0.8>, <rainbow(brightness):0.5>
*/
private static void handleRainbowTag(StyleContext style, String params, String value) {
float saturation = 1.0f;
float brightness = 1.0f;
if (params != null) {
if (params.equalsIgnoreCase("saturation") && value != null) {
try {
saturation = Float.parseFloat(value);
} catch (NumberFormatException e) {
}
} else if (params.equalsIgnoreCase("brightness") && value != null) {
try {
brightness = Float.parseFloat(value);
} catch (NumberFormatException e) {
}
}
}
style.rainbow = true;
style.gradient = false;
style.transition = false;
style.rainbowSaturation = saturation;
style.rainbowBrightness = brightness;
}
/**
* 处理渐变色标签
* 支持格式: <gradient:#FF0000:#00FF00>, <gradient(rgb):255,0,0:0,255,0>
* 渐变是逐字符过渡
*/
private static void handleGradientTag(StyleContext style, String params, String value) {
if (value == null) return;
String[] colors = value.split(":");
if (colors.length == 2) {
TextColor start = parseColor(colors[0]);
TextColor end = parseColor(colors[1]);
if (start != null && end != null) {
style.gradient = true;
style.transition = false;
style.rainbow = false;
style.gradientStart = start;
style.gradientEnd = end;
}
} else if (params != null && params.equalsIgnoreCase("rgb")) {
// 处理 RGB 格式
String[] color1 = colors[0].split(",");
String[] color2 = colors[1].split(",");
if (color1.length == 3 && color2.length == 3) {
try {
int r1 = Integer.parseInt(color1[0].trim());
int g1 = Integer.parseInt(color1[1].trim());
int b1 = Integer.parseInt(color1[2].trim());
int r2 = Integer.parseInt(color2[0].trim());
int g2 = Integer.parseInt(color2[1].trim());
int b2 = Integer.parseInt(color2[2].trim());
style.gradient = true;
style.transition = false;
style.rainbow = false;
style.gradientStart = TextColor.fromRgb((r1 << 16) | (g1 << 8) | b1);
style.gradientEnd = TextColor.fromRgb((r2 << 16) | (g2 << 8) | b2);
} catch (NumberFormatException e) {
}
}
}
}
/**
* 处理过渡色标签
* 支持格式: <transition:#FF0000:#00FF00>, <transition(rgb):255,0,0:0,255,0>
* 过渡是整个文本块的颜色过渡(类似整体渐变效果)
*/
private static void handleTransitionTag(StyleContext style, String params, String value) {
if (value == null) return;
String[] colors = value.split(":");
if (colors.length == 2) {
TextColor start = parseColor(colors[0]);
TextColor end = parseColor(colors[1]);
if (start != null && end != null) {
style.transition = true;
style.gradient = false;
style.rainbow = false;
style.transitionStart = start;
style.transitionEnd = end;
}
} else if (params != null && params.equalsIgnoreCase("rgb")) {
// 处理 RGB 格式
String[] color1 = colors[0].split(",");
String[] color2 = colors[1].split(",");
if (color1.length == 3 && color2.length == 3) {
try {
int r1 = Integer.parseInt(color1[0].trim());
int g1 = Integer.parseInt(color1[1].trim());
int b1 = Integer.parseInt(color1[2].trim());
int r2 = Integer.parseInt(color2[0].trim());
int g2 = Integer.parseInt(color2[1].trim());
int b2 = Integer.parseInt(color2[2].trim());
style.transition = true;
style.gradient = false;
style.rainbow = false;
style.transitionStart = TextColor.fromRgb((r1 << 16) | (g1 << 8) | b1);
style.transitionEnd = TextColor.fromRgb((r2 << 16) | (g2 << 8) | b2);
} catch (NumberFormatException e) {
}
}
}
}
/**
* 处理字体标签
* 支持格式: <font:minecraft:default>, <font:minecraft:uniform>
*/
private static void handleFontTag(StyleContext style, String params, String value) {
String fontName = value != null ? value : params;
if (fontName != null) {
style.font = fontName;
}
}
/**
* 解析颜色字符串
* 支持: 颜色名称, #RRGGBB, #RGB, 十六进制数值
*/
private static TextColor parseColor(String colorStr) {
if (colorStr == null) return null;
// 检查是否为预定义颜色名称
ChatFormatting formatting = COLOR_MAP.get(colorStr.toLowerCase(Locale.ROOT));
if (formatting != null) {
return TextColor.fromLegacyFormat(formatting);
}
// 检查十六进制格式
if (colorStr.startsWith("#")) {
try {
if (colorStr.length() == 7) {
return TextColor.parseColor(colorStr).getOrThrow();
} else if (colorStr.length() == 4) {
// 扩展 #RGB 到 #RRGGBB
String expanded = "#" +
colorStr.charAt(1) + colorStr.charAt(1) +
colorStr.charAt(2) + colorStr.charAt(2) +
colorStr.charAt(3) + colorStr.charAt(3);
return TextColor.parseColor(expanded).getOrThrow();
}
} catch (Exception e) {
// 忽略解析错误
}
}
return null;
}
/**
* 查找标签闭合位置
*/
private static int findClosingTag(String input, int start) {
int pos = start + 1;
while (pos < input.length()) {
char c = input.charAt(pos);
if (c == '>') {
return pos;
}
pos++;
}
return -1;
}
/**
* 添加文本到组件
*/
private static void appendText(MutableComponent root, String text, StyleContext styleContext) {
if (text.isEmpty()) return;
// 如果有渐变效果,需要逐字符处理
if (styleContext.gradient && styleContext.gradientStart != null && styleContext.gradientEnd != null) {
appendGradientText(root, text, styleContext);
}
// 如果有过渡效果,需要逐字符处理
else if (styleContext.transition && styleContext.transitionStart != null && styleContext.transitionEnd != null) {
appendTransitionText(root, text, styleContext);
}
// 如果有彩虹效果,需要逐字符处理
else if (styleContext.rainbow) {
appendRainbowText(root, text, styleContext);
}
// 普通文本
else {
MutableComponent component = Component.literal(text);
component.setStyle(styleContext.buildStyle());
root.append(component);
}
}
/**
* 带渐变效果的文本追加(逐字符颜色变化)
*/
private static void appendGradientText(MutableComponent root, String text, StyleContext styleContext) {
int length = text.length();
Style baseStyle = styleContext.buildBaseStyle();
for (int i = 0; i < length; i++) {
char c = text.charAt(i);
float ratio = (float) i / (length - 1);
TextColor gradientColor = interpolateColor(styleContext.gradientStart, styleContext.gradientEnd, ratio);
Style charStyle = baseStyle.withColor(gradientColor);
// 重新应用阴影
// if (styleContext.shadow != null && styleContext.shadow) {
// if (styleContext.shadowColor != null) {
// charStyle = charStyle.withShadowColor(styleContext.shadowColor.getValue() | 0xFF000000);
// } else {
// charStyle = charStyle.withShadowColor(0xFF000000);
// }
// }
root.append(Component.literal(String.valueOf(c)).withStyle(charStyle));
}
}
/**
* 带过渡效果的文本追加(整体颜色过渡)
*/
private static void appendTransitionText(MutableComponent root, String text, StyleContext styleContext) {
Style baseStyle = styleContext.buildBaseStyle();
// 过渡效果:为整个文本块创建一个颜色过渡
MutableComponent transitionComponent = Component.literal(text);
// 应用过渡样式(这里使用颜色插值,但也可以根据需求调整)
if (styleContext.transitionStart != null && styleContext.transitionEnd != null) {
// 可以使用整体渐变或者固定起始色
transitionComponent.setStyle(baseStyle.withColor(styleContext.transitionStart));
} else {
transitionComponent.setStyle(baseStyle);
}
root.append(transitionComponent);
}
/**
* 带彩虹效果的文本追加
*/
private static void appendRainbowText(MutableComponent root, String text, StyleContext styleContext) {
int length = text.length();
Style baseStyle = styleContext.buildBaseStyle();
for (int i = 0; i < length; i++) {
char c = text.charAt(i);
float hue = (float) i / length;
int rgb = java.awt.Color.HSBtoRGB(hue, styleContext.rainbowSaturation, styleContext.rainbowBrightness);
TextColor rainbowColor = TextColor.fromRgb(rgb & 0xFFFFFF);
Style charStyle = baseStyle.withColor(rainbowColor);
// 重新应用阴影
// if (styleContext.shadow != null && styleContext.shadow) {
// if (styleContext.shadowColor != null) {
// charStyle = charStyle.withShadowColor(styleContext.shadowColor.getValue() | 0xFF000000);
// } else {
// charStyle = charStyle.withShadowColor(0xFF000000);
// }
// }
root.append(Component.literal(String.valueOf(c)).withStyle(charStyle));
}
}
/**
* 颜色插值
*/
private static TextColor interpolateColor(TextColor start, TextColor end, float ratio) {
int startRgb = start.getValue();
int endRgb = end.getValue();
int r1 = (startRgb >> 16) & 0xFF;
int g1 = (startRgb >> 8) & 0xFF;
int b1 = startRgb & 0xFF;
int r2 = (endRgb >> 16) & 0xFF;
int g2 = (endRgb >> 8) & 0xFF;
int b2 = endRgb & 0xFF;
int r = (int) (r1 + (r2 - r1) * ratio);
int g = (int) (g1 + (g2 - g1) * ratio);
int b = (int) (b1 + (b2 - b1) * ratio);
return TextColor.fromRgb((r << 16) | (g << 8) | b);
}
/**
* 样式上下文类,用于追踪嵌套样式
*/
private static class StyleContext {
String tagName = null; // 记录标签名称,用于关闭匹配
TextColor color = null;
Boolean shadow = null;
TextColor shadowColor = null;
Map<ChatFormatting, Boolean> decorations = new HashMap<>();
boolean rainbow = false;
float rainbowSaturation = 1.0f;
float rainbowBrightness = 1.0f;
boolean gradient = false;
TextColor gradientStart = null;
TextColor gradientEnd = null;
boolean transition = false;
TextColor transitionStart = null;
TextColor transitionEnd = null;
String font = null;
StyleContext copy() {
StyleContext copy = new StyleContext();
copy.tagName = this.tagName;
copy.color = this.color;
copy.shadow = this.shadow;
copy.shadowColor = this.shadowColor;
copy.decorations.putAll(this.decorations);
copy.rainbow = this.rainbow;
copy.rainbowSaturation = this.rainbowSaturation;
copy.rainbowBrightness = this.rainbowBrightness;
copy.gradient = this.gradient;
copy.gradientStart = this.gradientStart;
copy.gradientEnd = this.gradientEnd;
copy.transition = this.transition;
copy.transitionStart = this.transitionStart;
copy.transitionEnd = this.transitionEnd;
copy.font = this.font;
return copy;
}
/**
* 构建基础样式(不含颜色)
*/
Style buildBaseStyle() {
Style style = Style.EMPTY;
// 应用装饰器
for (Map.Entry<ChatFormatting, Boolean> entry : decorations.entrySet()) {
if (entry.getValue()) {
style = style.applyFormat(entry.getKey());
}
}
if (font != null) {
try {
style = style.withFont(ResourceLocation.parse(font));
} catch (Exception e) {
// 忽略字体解析错误
}
}
return style;
}
/**
* 构建完整样式(含颜色)
*/
Style buildStyle() {
Style style = buildBaseStyle();
// 只有在没有渐变、过渡和彩虹效果时才应用普通颜色
if (!gradient && !transition && !rainbow && color != null) {
style = style.withColor(color);
}
// if (shadow != null && shadow) {
// if (shadowColor != null) {
// style = style.withShadowColor(shadowColor.getValue() | 0xFF000000);
// } else {
// style = style.withShadowColor(0xFF000000);
// }
// }
return style;
}
}
}
@@ -0,0 +1,88 @@
package com.coloryr.allmusic.client;
import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.PictureFrameBuffer;
import com.coloryr.allmusic.client.mixins.GuiRender;
import com.coloryr.allmusic.codec.HudPosType;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.vertex.*;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.resources.ResourceLocation;
import org.joml.Matrix4f;
import org.joml.Quaternionf;
import java.io.ByteArrayInputStream;
public class PicRender extends PictureFrameBuffer {
private static final ResourceLocation SOURCE_LOC = ResourceLocation.fromNamespaceAndPath("allmusic_client", "pic_source");
private static final ResourceLocation ROTATE_LOC = ResourceLocation.fromNamespaceAndPath("allmusic_client", "pic_rotate");
private final DynamicTexture sourceTexture;
private final DynamicTexture rotateTexture;
public PicRender(int size) {
sourceTexture = new DynamicTexture(size, size, false);
rotateTexture = new DynamicTexture(size, size, false);
Minecraft.getInstance().getTextureManager().register(SOURCE_LOC, sourceTexture);
Minecraft.getInstance().getTextureManager().register(ROTATE_LOC, rotateTexture);
}
@Override
public void update(byte[] source, byte[] rotate) {
try {
ByteArrayInputStream stream = new ByteArrayInputStream(source);
var image1 = NativeImage.read(stream);
sourceTexture.setPixels(image1);
sourceTexture.upload();
stream = new ByteArrayInputStream(rotate);
var image2 = NativeImage.read(stream);
rotateTexture.setPixels(image2);
rotateTexture.upload();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void draw(boolean rotate, int size, float x, float y, int ang, HudPosType dir, float alpha) {
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
GuiRender render = (GuiRender) gui;
ResourceLocation loc = rotate ? ROTATE_LOC : SOURCE_LOC;
Point2f point = AllMusicHud.getPos(size, size, x, y, dir);
Matrix4f matrix;
int a = size / 2;
if (rotate) {
matrix = new Matrix4f().translationRotate(point.x + a, point.y + a, 0,
new Quaternionf().fromAxisAngleDeg(0, 0, 1, ang));
} else {
matrix = new Matrix4f().translation(point.x + a, point.y + a, 0);
}
int x0 = -a;
int x1 = a;
int y0 = -a;
int y1 = a;
int z = 0;
int u0 = 0;
float u1 = 1;
float v0 = 0;
float v1 = 1;
RenderType renderType = RenderType.guiTexturedOverlay(loc);
VertexConsumer bufferBuilder = render.getBufferSource().getBuffer(renderType);
bufferBuilder.addVertex(matrix, x0, y1, z).setUv(u0, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y1, z).setUv(u1, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y0, z).setUv(u1, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x0, y0, z).setUv(u0, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
}
}
@@ -0,0 +1,126 @@
package com.coloryr.allmusic.client;
import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextureRender;
import com.coloryr.allmusic.client.mixins.GuiRender;
import com.coloryr.allmusic.codec.HudPosType;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import org.joml.Matrix4f;
import java.io.IOException;
public class TexRender extends TextureRender {
private final ResourceLocation location;
public TexRender(String texture) {
super(texture);
location = ResourceLocation.fromNamespaceAndPath(AllMusicClient.MODID, texture);
try {
ResourceManager resourceManager = Minecraft.getInstance().getResourceManager();
var resource = resourceManager.getResource(location).orElseThrow();
try (var input = resource.open()) {
var nativeImage = NativeImage.read(input);
width = nativeImage.getWidth();
height = nativeImage.getHeight();
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void drawPic(float x, float y, float alpha) {
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
GuiRender render = (GuiRender) gui;
float w1 = (float) width / 2;
float h1 = (float) height / 2;
Matrix4f matrix = new Matrix4f().translation(x + w1, y + h1, 0);
float x0 = -w1;
float x1 = w1;
float y0 = -h1;
float y1 = h1;
float z = 0;
float u0 = 0;
float u1 = 1;
float v0 = 0;
float v1 = 1;
RenderType renderType = RenderType.guiTexturedOverlay(location);
VertexConsumer bufferBuilder = render.getBufferSource().getBuffer(renderType);
bufferBuilder.addVertex(matrix, x0, y1, z).setUv(u0, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y1, z).setUv(u1, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y0, z).setUv(u1, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x0, y0, z).setUv(u0, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
}
@Override
public void drawPic(float x, float y, float width, float alpha) {
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
GuiRender render = (GuiRender) gui;
float w1 = (float) (this.width / 2) * width;
float h1 = (float) height / 2;
Matrix4f matrix = new Matrix4f().translation(x + w1, y + h1, 0);
float x0 = -w1;
float x1 = w1;
float y0 = -h1;
float y1 = h1;
float z = 0;
float u0 = 0;
float u1 = width;
float v0 = 0;
float v1 = 1;
RenderType renderType = RenderType.guiTexturedOverlay(location);
VertexConsumer bufferBuilder = render.getBufferSource().getBuffer(renderType);
bufferBuilder.addVertex(matrix, x0, y1, z).setUv(u0, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y1, z).setUv(u1, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y0, z).setUv(u1, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x0, y0, z).setUv(u0, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
}
@Override
public void drawPic(float x, float y, float width, float height, HudPosType dir, float alpha) {
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
GuiRender render = (GuiRender) gui;
Point2f point = AllMusicHud.getPos(width, height, x, y, dir);
float w1 = width / 2;
float h1 = height / 2;
Matrix4f matrix = new Matrix4f().translation(point.x + w1, point.y + h1, 0);
float x0 = -w1;
float x1 = w1;
float y0 = -h1;
float y1 = h1;
float z = 0;
float u0 = 0;
float u1 = 1;
float v0 = 0;
float v1 = 1;
RenderType renderType = RenderType.guiTexturedOverlay(location);
VertexConsumer bufferBuilder = render.getBufferSource().getBuffer(renderType);
bufferBuilder.addVertex(matrix, x0, y1, z).setUv(u0, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y1, z).setUv(u1, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y0, z).setUv(u1, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x0, y0, z).setUv(u0, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
}
}
@@ -0,0 +1,12 @@
package com.coloryr.allmusic.client.mixins;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.MultiBufferSource;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(GuiGraphics.class)
public interface GuiRender {
@Accessor("bufferSource")
MultiBufferSource.BufferSource getBufferSource();
}
@@ -0,0 +1,18 @@
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.AllMusicClient;
import net.minecraft.client.DeltaTracker;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiGraphics;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Gui.class)
public class GuiShow {
@Inject(method = {"renderChat"}, at = {@At(value = "RETURN")})
public void guiShow(GuiGraphics context, DeltaTracker tickCounter, CallbackInfo ci) {
AllMusicClient.update(context);
}
}
@@ -0,0 +1,16 @@
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Minecraft.class)
public class QuitServer {
@Inject(method = "disconnect(Lnet/minecraft/client/gui/screens/Screen;Z)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;resetData()V"))
public void quit(CallbackInfo info) {
AllMusicCore.onServerQuit();
}
}
@@ -0,0 +1,38 @@
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.resources.sounds.SoundInstance;
import net.minecraft.client.sounds.SoundManager;
import net.minecraft.sounds.SoundSource;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(SoundManager.class)
public class SoundEvent {
@Inject(method = "play", at = @At("HEAD"), cancellable = true)
public void play(SoundInstance soundInstance, CallbackInfo ci) {
if (AllMusicCore.isPlay()) {
SoundSource data = soundInstance.getSource();
switch (data) {
case RECORDS, MUSIC -> ci.cancel();
}
}
}
@Inject(method = "playDelayed", at = @At("HEAD"), cancellable = true)
public void play(SoundInstance soundInstance, int delay, CallbackInfo info) {
if (AllMusicCore.isPlay()) {
SoundSource data = soundInstance.getSource();
switch (data) {
case RECORDS, MUSIC -> info.cancel();
}
}
}
@Inject(method = "reload", at = @At("RETURN"))
public void reload(CallbackInfo info) {
AllMusicCore.reload();
}
}
@@ -0,0 +1,17 @@
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Minecraft.class)
public class Tick {
@Inject(method = "tick", at = @At("TAIL"))
public void tick(CallbackInfo info) {
AllMusicCore.tick();
}
}
@@ -0,0 +1,11 @@
package com.coloryr.allmusic.comm;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
public class AllMusicInit implements ModInitializer {
@Override
public void onInitialize() {
PayloadTypeRegistry.playS2C().register(MusicCodec.ID, MusicCodec.CODEC);
}
}
@@ -0,0 +1,22 @@
package com.coloryr.allmusic.comm;
import com.coloryr.allmusic.codec.MusicPack;
import com.coloryr.allmusic.codec.MusicPacketCodec;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
public record MusicCodec(MusicPack pack) implements CustomPacketPayload {
public static final ResourceLocation ChannelID = ResourceLocation.fromNamespaceAndPath("allmusic", "channel");
public static final Type<MusicCodec> ID = new Type<>(ChannelID);
public static final StreamCodec<FriendlyByteBuf, MusicCodec> CODEC =
StreamCodec.of((buf, value) -> MusicPacketCodec.pack(buf, value.pack),
buf -> new MusicCodec(MusicPacketCodec.decode(buf)));
@Override
public Type<? extends CustomPacketPayload> type() {
return ID;
}
}
@@ -0,0 +1,15 @@
{
"required": true,
"package": "com.coloryr.allmusic.client.mixins",
"compatibilityLevel": "JAVA_21",
"client": [
"GuiRender",
"GuiShow",
"QuitServer",
"SoundEvent",
"Tick"
],
"injectors": {
"defaultRequire": 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@@ -0,0 +1,36 @@
{
"schemaVersion": 1,
"id": "allmusic_client",
"version": "${version}",
"name": "AllMusic Client",
"description": "AllMusic Client Mod",
"authors": [
"Color_yr"
],
"contact": {
"homepage": "https://github.com/Coloryr/AllMusic",
"sources": "https://github.com/Coloryr/AllMusic"
},
"license": "GPL-3.0",
"icon": "assets/allmusic_client/icon.png",
"environment": "*",
"entrypoints": {
"main": [
"com.coloryr.allmusic.comm.AllMusicInit"
],
"client": [
"com.coloryr.allmusic.client.AllMusicClient"
]
},
"mixins": [
"allmusic_client.mixins.json"
],
"depends": {
"fabricloader": ">=0.19.3",
"minecraft": ">=1.21.4",
"java": ">=21",
"fabric-api": "*"
},
"suggests": {
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ java.targetCompatibility = JavaVersion.VERSION_21
dependencies {
minecraft("com.mojang:minecraft:1.21.6")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.18.6")
modImplementation("net.fabricmc:fabric-loader:0.19.3")
modImplementation("net.fabricmc.fabric-api:fabric-api:0.128.2+1.21.6")
}
@@ -0,0 +1,18 @@
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.AllMusicClient;
import net.minecraft.client.DeltaTracker;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiGraphics;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Gui.class)
public class GuiShow {
@Inject(method = {"renderChat"}, at = {@At(value = "RETURN")})
public void guiShow(GuiGraphics context, DeltaTracker tickCounter, CallbackInfo ci) {
AllMusicClient.update(context);
}
}
@@ -0,0 +1,16 @@
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Minecraft.class)
public class QuitServer {
@Inject(method = "disconnect(Lnet/minecraft/client/gui/screens/Screen;Z)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;resetData()V"))
public void quit(CallbackInfo info) {
AllMusicCore.onServerQuit();
}
}
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import com.mojang.blaze3d.systems.RenderSystem;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.resources.sounds.SoundInstance;
@@ -0,0 +1,17 @@
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Minecraft.class)
public class Tick {
@Inject(method = "tick", at = @At("TAIL"))
public void tick(CallbackInfo info) {
AllMusicCore.tick();
}
}
@@ -1,6 +1,6 @@
{
"required": true,
"package": "com.coloryr.allmusic.client.mixin",
"package": "com.coloryr.allmusic.client.mixins",
"compatibilityLevel": "JAVA_21",
"client": [
"GuiShow",
+1 -1
View File
@@ -7,7 +7,7 @@ java.targetCompatibility = JavaVersion.VERSION_25
dependencies {
minecraft("com.mojang:minecraft:26.1")
implementation("net.fabricmc:fabric-loader:0.18.6")
implementation("net.fabricmc:fabric-loader:0.19.3")
implementation("net.fabricmc.fabric-api:fabric-api:0.145.1+26.1")
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.AllMusicClient;
import net.minecraft.client.DeltaTracker;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.resources.sounds.SoundInstance;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
@@ -1,6 +1,6 @@
{
"required": true,
"package": "com.coloryr.allmusic.client.mixin",
"package": "com.coloryr.allmusic.client.mixins",
"compatibilityLevel": "JAVA_25",
"client": [
"GuiShow",
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.AllMusicClient;
import net.minecraft.client.DeltaTracker;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.resources.sounds.SoundInstance;
@@ -1,4 +1,4 @@
package com.coloryr.allmusic.client.mixin;
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
@@ -1,6 +1,6 @@
{
"required": true,
"package": "com.coloryr.allmusic.client.mixin",
"package": "com.coloryr.allmusic.client.mixins",
"compatibilityLevel": "JAVA_25",
"client": [
"GuiShow",
+40
View File
@@ -0,0 +1,40 @@
plugins {
id("dev.architectury.loom") version Versions.architecturyLoom
// id("architectury-plugin") version "3.5-SNAPSHOT"
}
java.sourceCompatibility = JavaVersion.VERSION_21
java.targetCompatibility = JavaVersion.VERSION_21
//architectury {
// platformSetupLoomIde()
// neoForge()
//}
repositories {
maven("https://maven.neoforged.net/releases/")
}
dependencies {
minecraft("com.mojang:minecraft:1.21.3")
mappings(loom.officialMojangMappings())
neoForge("net.neoforged:neoforge:21.3.96")
}
tasks {
processResources {
filesMatching("META-INF/neoforge.mods.toml") {
expand("version" to project.version)
}
}
remapJar {
inputFile.set(shadowJar.get().archiveFile)
archiveFileName.set("[neoforge-1.21.3]AllMusic_Client-${project.version}.jar")
destinationDirectory.set(file("${parent!!.projectDir}/../build"))
}
build {
dependsOn(remapJar)
}
}
+1
View File
@@ -0,0 +1 @@
loom.platform=neoforge
@@ -0,0 +1,179 @@
package com.coloryr.allmusic.client;
import com.coloryr.allmusic.client.core.AllMusicBridge;
import com.coloryr.allmusic.client.core.AllMusicCore;
import com.coloryr.allmusic.client.core.render.PictureFrameBuffer;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.client.core.render.TextureRender;
import com.coloryr.allmusic.comm.AllMusicInit;
import com.coloryr.allmusic.comm.MusicCodec;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.sounds.SoundSource;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
import net.neoforged.fml.loading.FMLPaths;
import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent;
import net.neoforged.neoforge.client.event.ClientTickEvent;
import net.neoforged.neoforge.client.event.RenderGuiLayerEvent;
import net.neoforged.neoforge.client.event.sound.PlaySoundSourceEvent;
import net.neoforged.neoforge.client.event.sound.PlayStreamingSourceEvent;
import net.neoforged.neoforge.client.event.sound.SoundEngineLoadEvent;
import net.neoforged.neoforge.client.gui.VanillaGuiLayers;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.neoforged.neoforge.network.handling.IPayloadHandler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@EventBusSubscriber(modid = AllMusicClient.MODID, value = Dist.CLIENT)
public class AllMusicClient implements IPayloadHandler<MusicCodec>, AllMusicBridge {
public static final String MODID = "allmusic_client";
public static final Logger LOGGER = LogManager.getLogger("AllMusic Client");
public static GuiGraphics context;
@SubscribeEvent
public static void setup(final FMLClientSetupEvent event) {
AllMusicClient client = new AllMusicClient();
AllMusicInit.handler = client;
AllMusicCore.init(FMLPaths.CONFIGDIR.get(), client);
event.enqueueWork(AllMusicCore::renderInit);
}
@SubscribeEvent
public static void onLoad(final SoundEngineLoadEvent e) {
AllMusicCore.reload();
}
public void sendMessage(String data) {
data = "[AllMusic Client]" + data;
LOGGER.warn(data);
String finalData = data;
Minecraft.getInstance().execute(() -> {
if (Minecraft.getInstance().player == null)
return;
Minecraft.getInstance().player.displayClientMessage(Component.literal(finalData), false);
});
}
@Override
public void handle(MusicCodec pack, IPayloadContext iPayloadContext) {
try {
AllMusicCore.packDo(pack.pack());
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void stopPlayMusic() {
Minecraft.getInstance().getSoundManager().stop(null, SoundSource.MUSIC);
Minecraft.getInstance().getSoundManager().stop(null, SoundSource.RECORDS);
}
public int getScreenWidth() {
return Minecraft.getInstance().getWindow().getGuiScaledWidth();
}
public int getScreenHeight() {
return Minecraft.getInstance().getWindow().getGuiScaledHeight();
}
public int getTextWidth(String item) {
return Minecraft.getInstance().font.width(item);
}
public int getFontHeight() {
return Minecraft.getInstance().font.lineHeight;
}
public float getVolume() {
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
}
@Override
public TextFrameBuffer makeTextRender(String name) {
return new CoreRenderTarget(name);
}
@Override
public TextureRender makeTextureRender(String file) {
return new TexRender(file);
}
@Override
public PictureFrameBuffer makePictureRender(int size) {
return new PicRender(size);
}
@Override
public String readText(String file) {
try {
Resource resource = Minecraft.getInstance().getResourceManager().getResource(ResourceLocation.fromNamespaceAndPath(MODID, file)).orElseThrow();
try (InputStream inputStream = resource.open()) {
byte[] bytes = inputStream.readAllBytes();
return new String(bytes, StandardCharsets.UTF_8);
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public InputStream readFile(String file) {
try {
Resource resource = Minecraft.getInstance().getResourceManager().getResource(ResourceLocation.fromNamespaceAndPath(MODID, file)).orElseThrow();
return resource.open();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@EventBusSubscriber(modid = MODID, value = Dist.CLIENT)
public static class MusicEvent {
@SubscribeEvent
public static void onTick(ClientTickEvent.Post event) {
AllMusicCore.tick();
}
@SubscribeEvent
public static void onSound(final PlaySoundSourceEvent e) {
if (!AllMusicCore.isPlay()) return;
SoundSource data = e.getSound().getSource();
switch (data) {
case MUSIC, RECORDS -> e.getChannel().stop();
}
}
@SubscribeEvent
public static void onSound(final PlayStreamingSourceEvent e) {
if (!AllMusicCore.isPlay()) return;
SoundSource data = e.getSound().getSource();
switch (data) {
case MUSIC, RECORDS -> e.getChannel().stop();
}
}
@SubscribeEvent
public static void onServerQuit(final ClientPlayerNetworkEvent.LoggingOut e) {
AllMusicCore.onServerQuit();
}
@SubscribeEvent
public static void onRenderOverlay(RenderGuiLayerEvent.Post e) {
if (e.getName().equals(VanillaGuiLayers.CAMERA_OVERLAYS)) {
context = e.getGuiGraphics();
AllMusicCore.hudUpdate();
}
}
}
}
@@ -0,0 +1,172 @@
package com.coloryr.allmusic.client;
import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.codec.HudPosType;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component;
import java.util.ArrayList;
import java.util.List;
public class CoreRenderTarget extends TextFrameBuffer {
private static class TextEntry {
final Component component;
final int y;
final int color;
final boolean shadow;
final int width;
final int height;
TextEntry(Component component, int y, int color, boolean shadow, int width, int height) {
this.component = component;
this.y = y;
this.color = color;
this.shadow = shadow;
this.width = width;
this.height = height;
}
}
private final List<TextEntry> entries = new ArrayList<>();
private final boolean isState;
public CoreRenderTarget(String name) {
isState = name.equals("state");
}
@Override
public void resize(int width, int height) {
nowWidth = width;
nowHeight = height;
}
@Override
public void use() {
isDraw = true;
clear();
entries.clear();
}
@Override
public void unUse() {
isDraw = false;
}
@Override
public void drawText(String text, int y, int color, boolean shadow) {
color = (color & 0x00FFFFFF) | 0xFF000000;
Component component = MiniMessage.parse(text);
Font font = Minecraft.getInstance().font;
int width = font.width(component);
int height = font.lineHeight + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
entries.add(new TextEntry(component, y, color, shadow, width, height));
texts.add(new TextItem(width, height, y, 1.0f));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
int scrollOffset = (int) (offsetX % entry.width);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - scrollOffset, drawY, finalColor, entry.shadow);
if (scrollOffset > 0) {
gui.drawString(font, entry.component, drawX - scrollOffset + entry.width, drawY, finalColor, entry.shadow);
}
gui.disableScissor();
} else {
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
}
}
}
@Override
public void drawLine(float x, float y, float alpha, int line) {
if (texts.isEmpty()) {
return;
}
if (line >= texts.size()) {
return;
}
if (line >= entries.size()) return;
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
TextEntry entry = entries.get(line);
gui.drawString(Minecraft.getInstance().font, entry.component,
(int) x, (int) (y + entry.y),
applyAlpha(entry.color, alpha), entry.shadow);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
float maxOffset = entry.width - maxWidth;
float texOffset = maxOffset * state;
int revealWidth = (int) (maxWidth * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - (int) texOffset, drawY, finalColor, entry.shadow);
gui.disableScissor();
} else {
int revealWidth = (int) (entry.width * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
gui.disableScissor();
}
}
}
@Override
public Point2f getLine(int line) {
if (line >= texts.size()) {
return new Point2f(0, 0);
}
if (line >= entries.size()) return new Point2f(0, 0);
TextEntry entry = entries.get(line);
return new Point2f(entry.width, entry.height);
}
private static int applyAlpha(int color, float alpha) {
int a = Math.clamp((int) (alpha * 255), 0, 255);
return (color & 0x00FFFFFF) | (a << 24);
}
}
@@ -0,0 +1,648 @@
package com.coloryr.allmusic.client;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.Style;
import net.minecraft.network.chat.TextColor;
import net.minecraft.resources.ResourceLocation;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MiniMessage {
// 正则表达式匹配标签
private static final Pattern TAG_PATTERN = Pattern.compile(
"<(/?)(\\w+)(?:\\(([^)]+)\\))?(?::([^>]+))?>"
);
// 颜色映射表
private static final Map<String, ChatFormatting> COLOR_MAP = new HashMap<>();
// 装饰器映射
private static final Map<String, ChatFormatting> DECORATION_MAP = new HashMap<>();
static {
COLOR_MAP.put("black", ChatFormatting.BLACK);
COLOR_MAP.put("dark_blue", ChatFormatting.DARK_BLUE);
COLOR_MAP.put("dark_green", ChatFormatting.DARK_GREEN);
COLOR_MAP.put("dark_aqua", ChatFormatting.DARK_AQUA);
COLOR_MAP.put("dark_red", ChatFormatting.DARK_RED);
COLOR_MAP.put("dark_purple", ChatFormatting.DARK_PURPLE);
COLOR_MAP.put("gold", ChatFormatting.GOLD);
COLOR_MAP.put("gray", ChatFormatting.GRAY);
COLOR_MAP.put("dark_gray", ChatFormatting.DARK_GRAY);
COLOR_MAP.put("blue", ChatFormatting.BLUE);
COLOR_MAP.put("green", ChatFormatting.GREEN);
COLOR_MAP.put("aqua", ChatFormatting.AQUA);
COLOR_MAP.put("red", ChatFormatting.RED);
COLOR_MAP.put("light_purple", ChatFormatting.LIGHT_PURPLE);
COLOR_MAP.put("yellow", ChatFormatting.YELLOW);
COLOR_MAP.put("white", ChatFormatting.WHITE);
}
static {
DECORATION_MAP.put("bold", ChatFormatting.BOLD);
DECORATION_MAP.put("italic", ChatFormatting.ITALIC);
DECORATION_MAP.put("underlined", ChatFormatting.UNDERLINE);
DECORATION_MAP.put("strikethrough", ChatFormatting.STRIKETHROUGH);
DECORATION_MAP.put("obfuscated", ChatFormatting.OBFUSCATED);
}
/**
* 解析 MiniMessage 字符串为 Component
*/
public static Component parse(String input) {
MutableComponent root = Component.literal("");
Stack<StyleContext> styleStack = new Stack<>();
StyleContext currentStyle = new StyleContext();
styleStack.push(currentStyle);
StringBuilder textBuffer = new StringBuilder();
int pos = 0;
int length = input.length();
while (pos < length) {
char c = input.charAt(pos);
if (c == '<') {
// 先处理已积累的文本
if (textBuffer.length() > 0) {
appendText(root, textBuffer.toString(), currentStyle);
textBuffer.setLength(0);
}
// 查找标签结束位置
int endTag = findClosingTag(input, pos);
if (endTag == -1) {
// 没有找到闭合标签,当作普通文本处理
textBuffer.append(c);
pos++;
continue;
}
String tagContent = input.substring(pos, endTag + 1);
Matcher matcher = TAG_PATTERN.matcher(tagContent);
if (matcher.matches()) {
boolean isClosing = !matcher.group(1).isEmpty();
String tagName = matcher.group(2).toLowerCase(Locale.ROOT);
String params = matcher.group(3);
String value = matcher.group(4);
if (isClosing) {
// 关闭标签 - 只关闭对应的开始标签
String expectedOpenTag = matcher.group(2).toLowerCase(Locale.ROOT);
// 查找对应的开始标签并关闭
if (styleStack.size() > 1) {
// 从栈顶向下查找匹配的开始标签
int index = styleStack.size() - 1;
boolean found = false;
while (index >= 0) {
StyleContext ctx = styleStack.get(index);
if (ctx.tagName != null && ctx.tagName.equals(expectedOpenTag)) {
// 找到匹配的开始标签,关闭到这一层
while (styleStack.size() > index + 1) {
styleStack.pop();
}
currentStyle = styleStack.peek();
found = true;
break;
}
index--;
}
if (!found) {
// 没找到匹配的标签,忽略此关闭标签
}
}
} else {
// 处理各种标签
StyleContext newStyle = currentStyle.copy();
switch (tagName) {
case "color":
handleColorTag(newStyle, params, value);
break;
case "shadow":
handleShadowTag(newStyle, params, value);
break;
case "decoration":
handleDecorationTag(newStyle, params, value);
break;
case "reset":
newStyle = new StyleContext();
break;
case "rainbow":
handleRainbowTag(newStyle, params, value);
break;
case "gradient":
handleGradientTag(newStyle, params, value);
break;
case "transition":
handleTransitionTag(newStyle, params, value);
break;
case "font":
handleFontTag(newStyle, params, value);
break;
default:
// 未知标签,忽略
break;
}
// 记录标签名称以便关闭时匹配
newStyle.tagName = tagName;
styleStack.push(newStyle);
currentStyle = newStyle;
}
} else {
// 无效标签,当作普通文本
textBuffer.append(tagContent);
}
pos = endTag + 1;
} else {
textBuffer.append(c);
pos++;
}
}
// 处理剩余的文本
if (textBuffer.length() > 0) {
appendText(root, textBuffer.toString(), currentStyle);
}
return root;
}
/**
* 处理颜色标签
* 支持格式: <color:red>, <color:#FF0000>, <color(rgb):255,0,0>
*/
private static void handleColorTag(StyleContext style, String params, String value) {
if (value != null) {
// 格式: <color:red> 或 <color:#FF0000>
TextColor textColor = parseColor(value);
if (textColor != null) {
style.color = textColor;
// 清除渐变、过渡和彩虹效果
style.gradient = false;
style.transition = false;
style.rainbow = false;
}
} else if (params != null) {
// 格式: <color(rgb):255,0,0>
if (params.equalsIgnoreCase("rgb")) {
String[] parts = value.split(",");
if (parts.length == 3) {
try {
int r = Integer.parseInt(parts[0].trim());
int g = Integer.parseInt(parts[1].trim());
int b = Integer.parseInt(parts[2].trim());
style.color = TextColor.fromRgb((r << 16) | (g << 8) | b);
// 清除渐变、过渡和彩虹效果
style.gradient = false;
style.transition = false;
style.rainbow = false;
} catch (NumberFormatException e) {
// 忽略解析错误
}
}
}
}
}
/**
* 处理阴影标签
* 支持格式: <shadow>, <shadow:true>, <shadow:false>, <shadow:#FF0000>
*/
private static void handleShadowTag(StyleContext style, String params, String value) {
if (value == null) {
style.shadow = true;
} else if (value.equalsIgnoreCase("true")) {
style.shadow = true;
} else if (value.equalsIgnoreCase("false")) {
style.shadow = false;
} else {
// 自定义阴影颜色
TextColor shadowColor = parseColor(value);
if (shadowColor != null) {
style.shadow = true;
style.shadowColor = shadowColor;
}
}
}
/**
* 处理装饰标签
* 支持格式: <decoration:bold>, <decoration:bold:true>, <decoration:bold:false>
*/
private static void handleDecorationTag(StyleContext style, String params, String value) {
if (params == null) return;
String[] parts = params.split(":");
String decorationName = parts[0].toLowerCase(Locale.ROOT);
boolean enabled = true;
if (parts.length > 1) {
enabled = Boolean.parseBoolean(parts[1]);
} else if (value != null) {
enabled = Boolean.parseBoolean(value);
}
ChatFormatting formatting = DECORATION_MAP.get(decorationName);
if (formatting != null) {
style.decorations.put(formatting, enabled);
}
}
/**
* 处理彩虹色标签
* 支持格式: <rainbow>, <rainbow:!>, <rainbow(saturation):0.8>, <rainbow(brightness):0.5>
*/
private static void handleRainbowTag(StyleContext style, String params, String value) {
float saturation = 1.0f;
float brightness = 1.0f;
if (params != null) {
if (params.equalsIgnoreCase("saturation") && value != null) {
try {
saturation = Float.parseFloat(value);
} catch (NumberFormatException e) {
}
} else if (params.equalsIgnoreCase("brightness") && value != null) {
try {
brightness = Float.parseFloat(value);
} catch (NumberFormatException e) {
}
}
}
style.rainbow = true;
style.gradient = false;
style.transition = false;
style.rainbowSaturation = saturation;
style.rainbowBrightness = brightness;
}
/**
* 处理渐变色标签
* 支持格式: <gradient:#FF0000:#00FF00>, <gradient(rgb):255,0,0:0,255,0>
* 渐变是逐字符过渡
*/
private static void handleGradientTag(StyleContext style, String params, String value) {
if (value == null) return;
String[] colors = value.split(":");
if (colors.length == 2) {
TextColor start = parseColor(colors[0]);
TextColor end = parseColor(colors[1]);
if (start != null && end != null) {
style.gradient = true;
style.transition = false;
style.rainbow = false;
style.gradientStart = start;
style.gradientEnd = end;
}
} else if (params != null && params.equalsIgnoreCase("rgb")) {
// 处理 RGB 格式
String[] color1 = colors[0].split(",");
String[] color2 = colors[1].split(",");
if (color1.length == 3 && color2.length == 3) {
try {
int r1 = Integer.parseInt(color1[0].trim());
int g1 = Integer.parseInt(color1[1].trim());
int b1 = Integer.parseInt(color1[2].trim());
int r2 = Integer.parseInt(color2[0].trim());
int g2 = Integer.parseInt(color2[1].trim());
int b2 = Integer.parseInt(color2[2].trim());
style.gradient = true;
style.transition = false;
style.rainbow = false;
style.gradientStart = TextColor.fromRgb((r1 << 16) | (g1 << 8) | b1);
style.gradientEnd = TextColor.fromRgb((r2 << 16) | (g2 << 8) | b2);
} catch (NumberFormatException e) {
}
}
}
}
/**
* 处理过渡色标签
* 支持格式: <transition:#FF0000:#00FF00>, <transition(rgb):255,0,0:0,255,0>
* 过渡是整个文本块的颜色过渡(类似整体渐变效果)
*/
private static void handleTransitionTag(StyleContext style, String params, String value) {
if (value == null) return;
String[] colors = value.split(":");
if (colors.length == 2) {
TextColor start = parseColor(colors[0]);
TextColor end = parseColor(colors[1]);
if (start != null && end != null) {
style.transition = true;
style.gradient = false;
style.rainbow = false;
style.transitionStart = start;
style.transitionEnd = end;
}
} else if (params != null && params.equalsIgnoreCase("rgb")) {
// 处理 RGB 格式
String[] color1 = colors[0].split(",");
String[] color2 = colors[1].split(",");
if (color1.length == 3 && color2.length == 3) {
try {
int r1 = Integer.parseInt(color1[0].trim());
int g1 = Integer.parseInt(color1[1].trim());
int b1 = Integer.parseInt(color1[2].trim());
int r2 = Integer.parseInt(color2[0].trim());
int g2 = Integer.parseInt(color2[1].trim());
int b2 = Integer.parseInt(color2[2].trim());
style.transition = true;
style.gradient = false;
style.rainbow = false;
style.transitionStart = TextColor.fromRgb((r1 << 16) | (g1 << 8) | b1);
style.transitionEnd = TextColor.fromRgb((r2 << 16) | (g2 << 8) | b2);
} catch (NumberFormatException e) {
}
}
}
}
/**
* 处理字体标签
* 支持格式: <font:minecraft:default>, <font:minecraft:uniform>
*/
private static void handleFontTag(StyleContext style, String params, String value) {
String fontName = value != null ? value : params;
if (fontName != null) {
style.font = fontName;
}
}
/**
* 解析颜色字符串
* 支持: 颜色名称, #RRGGBB, #RGB, 十六进制数值
*/
private static TextColor parseColor(String colorStr) {
if (colorStr == null) return null;
// 检查是否为预定义颜色名称
ChatFormatting formatting = COLOR_MAP.get(colorStr.toLowerCase(Locale.ROOT));
if (formatting != null) {
return TextColor.fromLegacyFormat(formatting);
}
// 检查十六进制格式
if (colorStr.startsWith("#")) {
try {
if (colorStr.length() == 7) {
return TextColor.parseColor(colorStr).getOrThrow();
} else if (colorStr.length() == 4) {
// 扩展 #RGB 到 #RRGGBB
String expanded = "#" +
colorStr.charAt(1) + colorStr.charAt(1) +
colorStr.charAt(2) + colorStr.charAt(2) +
colorStr.charAt(3) + colorStr.charAt(3);
return TextColor.parseColor(expanded).getOrThrow();
}
} catch (Exception e) {
// 忽略解析错误
}
}
return null;
}
/**
* 查找标签闭合位置
*/
private static int findClosingTag(String input, int start) {
int pos = start + 1;
while (pos < input.length()) {
char c = input.charAt(pos);
if (c == '>') {
return pos;
}
pos++;
}
return -1;
}
/**
* 添加文本到组件
*/
private static void appendText(MutableComponent root, String text, StyleContext styleContext) {
if (text.isEmpty()) return;
// 如果有渐变效果,需要逐字符处理
if (styleContext.gradient && styleContext.gradientStart != null && styleContext.gradientEnd != null) {
appendGradientText(root, text, styleContext);
}
// 如果有过渡效果,需要逐字符处理
else if (styleContext.transition && styleContext.transitionStart != null && styleContext.transitionEnd != null) {
appendTransitionText(root, text, styleContext);
}
// 如果有彩虹效果,需要逐字符处理
else if (styleContext.rainbow) {
appendRainbowText(root, text, styleContext);
}
// 普通文本
else {
MutableComponent component = Component.literal(text);
component.setStyle(styleContext.buildStyle());
root.append(component);
}
}
/**
* 带渐变效果的文本追加(逐字符颜色变化)
*/
private static void appendGradientText(MutableComponent root, String text, StyleContext styleContext) {
int length = text.length();
Style baseStyle = styleContext.buildBaseStyle();
for (int i = 0; i < length; i++) {
char c = text.charAt(i);
float ratio = (float) i / (length - 1);
TextColor gradientColor = interpolateColor(styleContext.gradientStart, styleContext.gradientEnd, ratio);
Style charStyle = baseStyle.withColor(gradientColor);
// 重新应用阴影
// if (styleContext.shadow != null && styleContext.shadow) {
// if (styleContext.shadowColor != null) {
// charStyle = charStyle.withShadowColor(styleContext.shadowColor.getValue() | 0xFF000000);
// } else {
// charStyle = charStyle.withShadowColor(0xFF000000);
// }
// }
root.append(Component.literal(String.valueOf(c)).withStyle(charStyle));
}
}
/**
* 带过渡效果的文本追加(整体颜色过渡)
*/
private static void appendTransitionText(MutableComponent root, String text, StyleContext styleContext) {
Style baseStyle = styleContext.buildBaseStyle();
// 过渡效果:为整个文本块创建一个颜色过渡
MutableComponent transitionComponent = Component.literal(text);
// 应用过渡样式(这里使用颜色插值,但也可以根据需求调整)
if (styleContext.transitionStart != null && styleContext.transitionEnd != null) {
// 可以使用整体渐变或者固定起始色
transitionComponent.setStyle(baseStyle.withColor(styleContext.transitionStart));
} else {
transitionComponent.setStyle(baseStyle);
}
root.append(transitionComponent);
}
/**
* 带彩虹效果的文本追加
*/
private static void appendRainbowText(MutableComponent root, String text, StyleContext styleContext) {
int length = text.length();
Style baseStyle = styleContext.buildBaseStyle();
for (int i = 0; i < length; i++) {
char c = text.charAt(i);
float hue = (float) i / length;
int rgb = java.awt.Color.HSBtoRGB(hue, styleContext.rainbowSaturation, styleContext.rainbowBrightness);
TextColor rainbowColor = TextColor.fromRgb(rgb & 0xFFFFFF);
Style charStyle = baseStyle.withColor(rainbowColor);
// 重新应用阴影
// if (styleContext.shadow != null && styleContext.shadow) {
// if (styleContext.shadowColor != null) {
// charStyle = charStyle.withShadowColor(styleContext.shadowColor.getValue() | 0xFF000000);
// } else {
// charStyle = charStyle.withShadowColor(0xFF000000);
// }
// }
root.append(Component.literal(String.valueOf(c)).withStyle(charStyle));
}
}
/**
* 颜色插值
*/
private static TextColor interpolateColor(TextColor start, TextColor end, float ratio) {
int startRgb = start.getValue();
int endRgb = end.getValue();
int r1 = (startRgb >> 16) & 0xFF;
int g1 = (startRgb >> 8) & 0xFF;
int b1 = startRgb & 0xFF;
int r2 = (endRgb >> 16) & 0xFF;
int g2 = (endRgb >> 8) & 0xFF;
int b2 = endRgb & 0xFF;
int r = (int) (r1 + (r2 - r1) * ratio);
int g = (int) (g1 + (g2 - g1) * ratio);
int b = (int) (b1 + (b2 - b1) * ratio);
return TextColor.fromRgb((r << 16) | (g << 8) | b);
}
/**
* 样式上下文类,用于追踪嵌套样式
*/
private static class StyleContext {
String tagName = null; // 记录标签名称,用于关闭匹配
TextColor color = null;
Boolean shadow = null;
TextColor shadowColor = null;
Map<ChatFormatting, Boolean> decorations = new HashMap<>();
boolean rainbow = false;
float rainbowSaturation = 1.0f;
float rainbowBrightness = 1.0f;
boolean gradient = false;
TextColor gradientStart = null;
TextColor gradientEnd = null;
boolean transition = false;
TextColor transitionStart = null;
TextColor transitionEnd = null;
String font = null;
StyleContext copy() {
StyleContext copy = new StyleContext();
copy.tagName = this.tagName;
copy.color = this.color;
copy.shadow = this.shadow;
copy.shadowColor = this.shadowColor;
copy.decorations.putAll(this.decorations);
copy.rainbow = this.rainbow;
copy.rainbowSaturation = this.rainbowSaturation;
copy.rainbowBrightness = this.rainbowBrightness;
copy.gradient = this.gradient;
copy.gradientStart = this.gradientStart;
copy.gradientEnd = this.gradientEnd;
copy.transition = this.transition;
copy.transitionStart = this.transitionStart;
copy.transitionEnd = this.transitionEnd;
copy.font = this.font;
return copy;
}
/**
* 构建基础样式(不含颜色)
*/
Style buildBaseStyle() {
Style style = Style.EMPTY;
// 应用装饰器
for (Map.Entry<ChatFormatting, Boolean> entry : decorations.entrySet()) {
if (entry.getValue()) {
style = style.applyFormat(entry.getKey());
}
}
if (font != null) {
try {
style = style.withFont(ResourceLocation.parse(font));
} catch (Exception e) {
// 忽略字体解析错误
}
}
return style;
}
/**
* 构建完整样式(含颜色)
*/
Style buildStyle() {
Style style = buildBaseStyle();
// 只有在没有渐变、过渡和彩虹效果时才应用普通颜色
if (!gradient && !transition && !rainbow && color != null) {
style = style.withColor(color);
}
// if (shadow != null && shadow) {
// if (shadowColor != null) {
// style = style.withShadowColor(shadowColor.getValue() | 0xFF000000);
// } else {
// style = style.withShadowColor(0xFF000000);
// }
// }
return style;
}
}
}
@@ -0,0 +1,88 @@
package com.coloryr.allmusic.client;
import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.PictureFrameBuffer;
import com.coloryr.allmusic.client.mixins.GuiRender;
import com.coloryr.allmusic.codec.HudPosType;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.resources.ResourceLocation;
import org.joml.Matrix4f;
import org.joml.Quaternionf;
import java.io.ByteArrayInputStream;
public class PicRender extends PictureFrameBuffer {
private static final ResourceLocation SOURCE_LOC = ResourceLocation.fromNamespaceAndPath("allmusic_client", "pic_source");
private static final ResourceLocation ROTATE_LOC = ResourceLocation.fromNamespaceAndPath("allmusic_client", "pic_rotate");
private final DynamicTexture sourceTexture;
private final DynamicTexture rotateTexture;
public PicRender(int size) {
sourceTexture = new DynamicTexture(size, size, false);
rotateTexture = new DynamicTexture(size, size, false);
Minecraft.getInstance().getTextureManager().register(SOURCE_LOC, sourceTexture);
Minecraft.getInstance().getTextureManager().register(ROTATE_LOC, rotateTexture);
}
@Override
public void update(byte[] source, byte[] rotate) {
try {
ByteArrayInputStream stream = new ByteArrayInputStream(source);
var image1 = NativeImage.read(stream);
sourceTexture.setPixels(image1);
sourceTexture.upload();
stream = new ByteArrayInputStream(rotate);
var image2 = NativeImage.read(stream);
rotateTexture.setPixels(image2);
rotateTexture.upload();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void draw(boolean rotate, int size, float x, float y, int ang, HudPosType dir, float alpha) {
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
GuiRender render = (GuiRender) gui;
ResourceLocation loc = rotate ? ROTATE_LOC : SOURCE_LOC;
Point2f point = AllMusicHud.getPos(size, size, x, y, dir);
Matrix4f matrix;
int a = size / 2;
if (rotate) {
matrix = new Matrix4f().translationRotate(point.x + a, point.y + a, 0,
new Quaternionf().fromAxisAngleDeg(0, 0, 1, ang));
} else {
matrix = new Matrix4f().translation(point.x + a, point.y + a, 0);
}
int x0 = -a;
int x1 = a;
int y0 = -a;
int y1 = a;
int z = 0;
int u0 = 0;
float u1 = 1;
float v0 = 0;
float v1 = 1;
RenderType renderType = RenderType.guiTexturedOverlay(loc);
VertexConsumer bufferBuilder = render.getBufferSource().getBuffer(renderType);
bufferBuilder.addVertex(matrix, x0, y1, z).setUv(u0, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y1, z).setUv(u1, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y0, z).setUv(u1, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x0, y0, z).setUv(u0, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
}
}
@@ -0,0 +1,126 @@
package com.coloryr.allmusic.client;
import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextureRender;
import com.coloryr.allmusic.client.mixins.GuiRender;
import com.coloryr.allmusic.codec.HudPosType;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import org.joml.Matrix4f;
import java.io.IOException;
public class TexRender extends TextureRender {
private final ResourceLocation location;
public TexRender(String texture) {
super(texture);
location = ResourceLocation.fromNamespaceAndPath(AllMusicClient.MODID, texture);
try {
ResourceManager resourceManager = Minecraft.getInstance().getResourceManager();
var resource = resourceManager.getResource(location).orElseThrow();
try (var input = resource.open()) {
var nativeImage = NativeImage.read(input);
width = nativeImage.getWidth();
height = nativeImage.getHeight();
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void drawPic(float x, float y, float alpha) {
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
GuiRender render = (GuiRender) gui;
float w1 = (float) width / 2;
float h1 = (float) height / 2;
Matrix4f matrix = new Matrix4f().translation(x + w1, y + h1, 0);
float x0 = -w1;
float x1 = w1;
float y0 = -h1;
float y1 = h1;
float z = 0;
float u0 = 0;
float u1 = 1;
float v0 = 0;
float v1 = 1;
RenderType renderType = RenderType.guiTexturedOverlay(location);
VertexConsumer bufferBuilder = render.getBufferSource().getBuffer(renderType);
bufferBuilder.addVertex(matrix, x0, y1, z).setUv(u0, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y1, z).setUv(u1, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y0, z).setUv(u1, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x0, y0, z).setUv(u0, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
}
@Override
public void drawPic(float x, float y, float width, float alpha) {
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
GuiRender render = (GuiRender) gui;
float w1 = (float) (this.width / 2) * width;
float h1 = (float) height / 2;
Matrix4f matrix = new Matrix4f().translation(x + w1, y + h1, 0);
float x0 = -w1;
float x1 = w1;
float y0 = -h1;
float y1 = h1;
float z = 0;
float u0 = 0;
float u1 = width;
float v0 = 0;
float v1 = 1;
RenderType renderType = RenderType.guiTexturedOverlay(location);
VertexConsumer bufferBuilder = render.getBufferSource().getBuffer(renderType);
bufferBuilder.addVertex(matrix, x0, y1, z).setUv(u0, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y1, z).setUv(u1, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y0, z).setUv(u1, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x0, y0, z).setUv(u0, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
}
@Override
public void drawPic(float x, float y, float width, float height, HudPosType dir, float alpha) {
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
GuiRender render = (GuiRender) gui;
Point2f point = AllMusicHud.getPos(width, height, x, y, dir);
float w1 = width / 2;
float h1 = height / 2;
Matrix4f matrix = new Matrix4f().translation(point.x + w1, point.y + h1, 0);
float x0 = -w1;
float x1 = w1;
float y0 = -h1;
float y1 = h1;
float z = 0;
float u0 = 0;
float u1 = 1;
float v0 = 0;
float v1 = 1;
RenderType renderType = RenderType.guiTexturedOverlay(location);
VertexConsumer bufferBuilder = render.getBufferSource().getBuffer(renderType);
bufferBuilder.addVertex(matrix, x0, y1, z).setUv(u0, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y1, z).setUv(u1, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y0, z).setUv(u1, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x0, y0, z).setUv(u0, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
}
}
@@ -0,0 +1,12 @@
package com.coloryr.allmusic.client.mixins;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.MultiBufferSource;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(GuiGraphics.class)
public interface GuiRender {
@Accessor("bufferSource")
MultiBufferSource.BufferSource getBufferSource();
}
@@ -0,0 +1,33 @@
package com.coloryr.allmusic.comm;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.common.Mod;
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.neoforged.neoforge.network.handling.IPayloadHandler;
@Mod(AllMusicInit.MODID)
public class AllMusicInit implements IPayloadHandler<MusicCodec> {
public static final String MODID = "allmusic";
public static IPayloadHandler<MusicCodec> handler;
public AllMusicInit(IEventBus modEventBus, ModContainer modContainer) {
modEventBus.addListener(this::register);
}
public void register(final RegisterPayloadHandlersEvent event) {
event.registrar("1.0")
.optional()
.playToClient(MusicCodec.TYPE, MusicCodec.CODEC, this);
}
@Override
public void handle(MusicCodec data, IPayloadContext iPayloadContext) {
if (handler != null) {
handler.handle(data, iPayloadContext);
}
}
}
@@ -0,0 +1,24 @@
package com.coloryr.allmusic.comm;
import com.coloryr.allmusic.codec.MusicPack;
import com.coloryr.allmusic.codec.MusicPacketCodec;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
public record MusicCodec(MusicPack pack) implements CustomPacketPayload {
public static final ResourceLocation channel =
ResourceLocation.fromNamespaceAndPath("allmusic", "channel");
public static final Type<MusicCodec> TYPE = new Type<>(channel);
public static final StreamCodec<RegistryFriendlyByteBuf, MusicCodec> CODEC = StreamCodec.of((buf, value) -> MusicPacketCodec.pack(buf, value.pack),
(buf) -> {
return new MusicCodec(MusicPacketCodec.decode(buf));
});
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
}
@@ -0,0 +1,39 @@
modLoader = "javafml"
loaderVersion = "[2,)"
license = "GPL-3.0"
issueTrackerURL = "https://github.com/Coloryr/AllMusic/issues/"
[[mods]]
modId = "allmusic"
version = "${version}"
displayName = "AllMusic Client"
displayURL = "https://github.com/Coloryr/AllMusic/"
logoFile = "icon.png"
authors = "Color_yr"
description = '''AllMusic Client Mod'''
[[mods]]
modId = "allmusic_client"
version = "${version}"
displayName = "AllMusic Client"
displayURL = "https://github.com/Coloryr/AllMusic/"
logoFile = "icon.png"
authors = "Color_yr"
description = '''AllMusic Client Mod'''
[[mixins]]
config="allmusic_client.mixins.json"
[[dependencies.allmusic_client]]
modId = "neoforge"
type = "required"
versionRange = "[21.0.167,)"
ordering = "NONE"
side = "CLIENT"
[[dependencies.allmusic_client]]
modId = "minecraft"
type = "required"
versionRange = "[1.21.3,)"
ordering = "NONE"
side = "CLIENT"
@@ -0,0 +1,15 @@
{
"required": true,
"package": "com.coloryr.allmusic.client.mixins",
"compatibilityLevel": "JAVA_21",
"mixins": [
],
"client": [
"GuiRender"
],
"server": [
],
"injectors": {
"defaultRequire": 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

+40
View File
@@ -0,0 +1,40 @@
plugins {
id("dev.architectury.loom") version Versions.architecturyLoom
// id("architectury-plugin") version "3.5-SNAPSHOT"
}
java.sourceCompatibility = JavaVersion.VERSION_21
java.targetCompatibility = JavaVersion.VERSION_21
//architectury {
// platformSetupLoomIde()
// neoForge()
//}
repositories {
maven("https://maven.neoforged.net/releases/")
}
dependencies {
minecraft("com.mojang:minecraft:1.21.4")
mappings(loom.officialMojangMappings())
neoForge("net.neoforged:neoforge:21.4.157")
}
tasks {
processResources {
filesMatching("META-INF/neoforge.mods.toml") {
expand("version" to project.version)
}
}
remapJar {
inputFile.set(shadowJar.get().archiveFile)
archiveFileName.set("[neoforge-1.21.3]AllMusic_Client-${project.version}.jar")
destinationDirectory.set(file("${parent!!.projectDir}/../build"))
}
build {
dependsOn(remapJar)
}
}
+1
View File
@@ -0,0 +1 @@
loom.platform=neoforge
@@ -0,0 +1,179 @@
package com.coloryr.allmusic.client;
import com.coloryr.allmusic.client.core.AllMusicBridge;
import com.coloryr.allmusic.client.core.AllMusicCore;
import com.coloryr.allmusic.client.core.render.PictureFrameBuffer;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.client.core.render.TextureRender;
import com.coloryr.allmusic.comm.AllMusicInit;
import com.coloryr.allmusic.comm.MusicCodec;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.sounds.SoundSource;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
import net.neoforged.fml.loading.FMLPaths;
import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent;
import net.neoforged.neoforge.client.event.ClientTickEvent;
import net.neoforged.neoforge.client.event.RenderGuiLayerEvent;
import net.neoforged.neoforge.client.event.sound.PlaySoundSourceEvent;
import net.neoforged.neoforge.client.event.sound.PlayStreamingSourceEvent;
import net.neoforged.neoforge.client.event.sound.SoundEngineLoadEvent;
import net.neoforged.neoforge.client.gui.VanillaGuiLayers;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.neoforged.neoforge.network.handling.IPayloadHandler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@EventBusSubscriber(modid = AllMusicClient.MODID, value = Dist.CLIENT)
public class AllMusicClient implements IPayloadHandler<MusicCodec>, AllMusicBridge {
public static final String MODID = "allmusic_client";
public static final Logger LOGGER = LogManager.getLogger("AllMusic Client");
public static GuiGraphics context;
@SubscribeEvent
public static void setup(final FMLClientSetupEvent event) {
AllMusicClient client = new AllMusicClient();
AllMusicInit.handler = client;
AllMusicCore.init(FMLPaths.CONFIGDIR.get(), client);
event.enqueueWork(AllMusicCore::renderInit);
}
@SubscribeEvent
public static void onLoad(final SoundEngineLoadEvent e) {
AllMusicCore.reload();
}
public void sendMessage(String data) {
data = "[AllMusic Client]" + data;
LOGGER.warn(data);
String finalData = data;
Minecraft.getInstance().execute(() -> {
if (Minecraft.getInstance().player == null)
return;
Minecraft.getInstance().player.displayClientMessage(Component.literal(finalData), false);
});
}
@Override
public void handle(MusicCodec pack, IPayloadContext iPayloadContext) {
try {
AllMusicCore.packDo(pack.pack());
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void stopPlayMusic() {
Minecraft.getInstance().getSoundManager().stop(null, SoundSource.MUSIC);
Minecraft.getInstance().getSoundManager().stop(null, SoundSource.RECORDS);
}
public int getScreenWidth() {
return Minecraft.getInstance().getWindow().getGuiScaledWidth();
}
public int getScreenHeight() {
return Minecraft.getInstance().getWindow().getGuiScaledHeight();
}
public int getTextWidth(String item) {
return Minecraft.getInstance().font.width(item);
}
public int getFontHeight() {
return Minecraft.getInstance().font.lineHeight;
}
public float getVolume() {
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
}
@Override
public TextFrameBuffer makeTextRender(String name) {
return new CoreRenderTarget(name);
}
@Override
public TextureRender makeTextureRender(String file) {
return new TexRender(file);
}
@Override
public PictureFrameBuffer makePictureRender(int size) {
return new PicRender(size);
}
@Override
public String readText(String file) {
try {
Resource resource = Minecraft.getInstance().getResourceManager().getResource(ResourceLocation.fromNamespaceAndPath(MODID, file)).orElseThrow();
try (InputStream inputStream = resource.open()) {
byte[] bytes = inputStream.readAllBytes();
return new String(bytes, StandardCharsets.UTF_8);
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public InputStream readFile(String file) {
try {
Resource resource = Minecraft.getInstance().getResourceManager().getResource(ResourceLocation.fromNamespaceAndPath(MODID, file)).orElseThrow();
return resource.open();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@EventBusSubscriber(modid = MODID, value = Dist.CLIENT)
public static class MusicEvent {
@SubscribeEvent
public static void onTick(ClientTickEvent.Post event) {
AllMusicCore.tick();
}
@SubscribeEvent
public static void onSound(final PlaySoundSourceEvent e) {
if (!AllMusicCore.isPlay()) return;
SoundSource data = e.getSound().getSource();
switch (data) {
case MUSIC, RECORDS -> e.getChannel().stop();
}
}
@SubscribeEvent
public static void onSound(final PlayStreamingSourceEvent e) {
if (!AllMusicCore.isPlay()) return;
SoundSource data = e.getSound().getSource();
switch (data) {
case MUSIC, RECORDS -> e.getChannel().stop();
}
}
@SubscribeEvent
public static void onServerQuit(final ClientPlayerNetworkEvent.LoggingOut e) {
AllMusicCore.onServerQuit();
}
@SubscribeEvent
public static void onRenderOverlay(RenderGuiLayerEvent.Post e) {
if (e.getName().equals(VanillaGuiLayers.CAMERA_OVERLAYS)) {
context = e.getGuiGraphics();
AllMusicCore.hudUpdate();
}
}
}
}
@@ -0,0 +1,172 @@
package com.coloryr.allmusic.client;
import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.codec.HudPosType;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component;
import java.util.ArrayList;
import java.util.List;
public class CoreRenderTarget extends TextFrameBuffer {
private static class TextEntry {
final Component component;
final int y;
final int color;
final boolean shadow;
final int width;
final int height;
TextEntry(Component component, int y, int color, boolean shadow, int width, int height) {
this.component = component;
this.y = y;
this.color = color;
this.shadow = shadow;
this.width = width;
this.height = height;
}
}
private final List<TextEntry> entries = new ArrayList<>();
private final boolean isState;
public CoreRenderTarget(String name) {
isState = name.equals("state");
}
@Override
public void resize(int width, int height) {
nowWidth = width;
nowHeight = height;
}
@Override
public void use() {
isDraw = true;
clear();
entries.clear();
}
@Override
public void unUse() {
isDraw = false;
}
@Override
public void drawText(String text, int y, int color, boolean shadow) {
color = (color & 0x00FFFFFF) | 0xFF000000;
Component component = MiniMessage.parse(text);
Font font = Minecraft.getInstance().font;
int width = font.width(component);
int height = font.lineHeight + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
entries.add(new TextEntry(component, y, color, shadow, width, height));
texts.add(new TextItem(width, height, y, 1.0f));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
int scrollOffset = (int) (offsetX % entry.width);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - scrollOffset, drawY, finalColor, entry.shadow);
if (scrollOffset > 0) {
gui.drawString(font, entry.component, drawX - scrollOffset + entry.width, drawY, finalColor, entry.shadow);
}
gui.disableScissor();
} else {
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
}
}
}
@Override
public void drawLine(float x, float y, float alpha, int line) {
if (texts.isEmpty()) {
return;
}
if (line >= texts.size()) {
return;
}
if (line >= entries.size()) return;
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
TextEntry entry = entries.get(line);
gui.drawString(Minecraft.getInstance().font, entry.component,
(int) x, (int) (y + entry.y),
applyAlpha(entry.color, alpha), entry.shadow);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
float maxOffset = entry.width - maxWidth;
float texOffset = maxOffset * state;
int revealWidth = (int) (maxWidth * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - (int) texOffset, drawY, finalColor, entry.shadow);
gui.disableScissor();
} else {
int revealWidth = (int) (entry.width * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
gui.disableScissor();
}
}
}
@Override
public Point2f getLine(int line) {
if (line >= texts.size()) {
return new Point2f(0, 0);
}
if (line >= entries.size()) return new Point2f(0, 0);
TextEntry entry = entries.get(line);
return new Point2f(entry.width, entry.height);
}
private static int applyAlpha(int color, float alpha) {
int a = Math.clamp((int) (alpha * 255), 0, 255);
return (color & 0x00FFFFFF) | (a << 24);
}
}
@@ -0,0 +1,648 @@
package com.coloryr.allmusic.client;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.Style;
import net.minecraft.network.chat.TextColor;
import net.minecraft.resources.ResourceLocation;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MiniMessage {
// 正则表达式匹配标签
private static final Pattern TAG_PATTERN = Pattern.compile(
"<(/?)(\\w+)(?:\\(([^)]+)\\))?(?::([^>]+))?>"
);
// 颜色映射表
private static final Map<String, ChatFormatting> COLOR_MAP = new HashMap<>();
// 装饰器映射
private static final Map<String, ChatFormatting> DECORATION_MAP = new HashMap<>();
static {
COLOR_MAP.put("black", ChatFormatting.BLACK);
COLOR_MAP.put("dark_blue", ChatFormatting.DARK_BLUE);
COLOR_MAP.put("dark_green", ChatFormatting.DARK_GREEN);
COLOR_MAP.put("dark_aqua", ChatFormatting.DARK_AQUA);
COLOR_MAP.put("dark_red", ChatFormatting.DARK_RED);
COLOR_MAP.put("dark_purple", ChatFormatting.DARK_PURPLE);
COLOR_MAP.put("gold", ChatFormatting.GOLD);
COLOR_MAP.put("gray", ChatFormatting.GRAY);
COLOR_MAP.put("dark_gray", ChatFormatting.DARK_GRAY);
COLOR_MAP.put("blue", ChatFormatting.BLUE);
COLOR_MAP.put("green", ChatFormatting.GREEN);
COLOR_MAP.put("aqua", ChatFormatting.AQUA);
COLOR_MAP.put("red", ChatFormatting.RED);
COLOR_MAP.put("light_purple", ChatFormatting.LIGHT_PURPLE);
COLOR_MAP.put("yellow", ChatFormatting.YELLOW);
COLOR_MAP.put("white", ChatFormatting.WHITE);
}
static {
DECORATION_MAP.put("bold", ChatFormatting.BOLD);
DECORATION_MAP.put("italic", ChatFormatting.ITALIC);
DECORATION_MAP.put("underlined", ChatFormatting.UNDERLINE);
DECORATION_MAP.put("strikethrough", ChatFormatting.STRIKETHROUGH);
DECORATION_MAP.put("obfuscated", ChatFormatting.OBFUSCATED);
}
/**
* 解析 MiniMessage 字符串为 Component
*/
public static Component parse(String input) {
MutableComponent root = Component.literal("");
Stack<StyleContext> styleStack = new Stack<>();
StyleContext currentStyle = new StyleContext();
styleStack.push(currentStyle);
StringBuilder textBuffer = new StringBuilder();
int pos = 0;
int length = input.length();
while (pos < length) {
char c = input.charAt(pos);
if (c == '<') {
// 先处理已积累的文本
if (textBuffer.length() > 0) {
appendText(root, textBuffer.toString(), currentStyle);
textBuffer.setLength(0);
}
// 查找标签结束位置
int endTag = findClosingTag(input, pos);
if (endTag == -1) {
// 没有找到闭合标签,当作普通文本处理
textBuffer.append(c);
pos++;
continue;
}
String tagContent = input.substring(pos, endTag + 1);
Matcher matcher = TAG_PATTERN.matcher(tagContent);
if (matcher.matches()) {
boolean isClosing = !matcher.group(1).isEmpty();
String tagName = matcher.group(2).toLowerCase(Locale.ROOT);
String params = matcher.group(3);
String value = matcher.group(4);
if (isClosing) {
// 关闭标签 - 只关闭对应的开始标签
String expectedOpenTag = matcher.group(2).toLowerCase(Locale.ROOT);
// 查找对应的开始标签并关闭
if (styleStack.size() > 1) {
// 从栈顶向下查找匹配的开始标签
int index = styleStack.size() - 1;
boolean found = false;
while (index >= 0) {
StyleContext ctx = styleStack.get(index);
if (ctx.tagName != null && ctx.tagName.equals(expectedOpenTag)) {
// 找到匹配的开始标签,关闭到这一层
while (styleStack.size() > index + 1) {
styleStack.pop();
}
currentStyle = styleStack.peek();
found = true;
break;
}
index--;
}
if (!found) {
// 没找到匹配的标签,忽略此关闭标签
}
}
} else {
// 处理各种标签
StyleContext newStyle = currentStyle.copy();
switch (tagName) {
case "color":
handleColorTag(newStyle, params, value);
break;
case "shadow":
handleShadowTag(newStyle, params, value);
break;
case "decoration":
handleDecorationTag(newStyle, params, value);
break;
case "reset":
newStyle = new StyleContext();
break;
case "rainbow":
handleRainbowTag(newStyle, params, value);
break;
case "gradient":
handleGradientTag(newStyle, params, value);
break;
case "transition":
handleTransitionTag(newStyle, params, value);
break;
case "font":
handleFontTag(newStyle, params, value);
break;
default:
// 未知标签,忽略
break;
}
// 记录标签名称以便关闭时匹配
newStyle.tagName = tagName;
styleStack.push(newStyle);
currentStyle = newStyle;
}
} else {
// 无效标签,当作普通文本
textBuffer.append(tagContent);
}
pos = endTag + 1;
} else {
textBuffer.append(c);
pos++;
}
}
// 处理剩余的文本
if (textBuffer.length() > 0) {
appendText(root, textBuffer.toString(), currentStyle);
}
return root;
}
/**
* 处理颜色标签
* 支持格式: <color:red>, <color:#FF0000>, <color(rgb):255,0,0>
*/
private static void handleColorTag(StyleContext style, String params, String value) {
if (value != null) {
// 格式: <color:red> 或 <color:#FF0000>
TextColor textColor = parseColor(value);
if (textColor != null) {
style.color = textColor;
// 清除渐变、过渡和彩虹效果
style.gradient = false;
style.transition = false;
style.rainbow = false;
}
} else if (params != null) {
// 格式: <color(rgb):255,0,0>
if (params.equalsIgnoreCase("rgb")) {
String[] parts = value.split(",");
if (parts.length == 3) {
try {
int r = Integer.parseInt(parts[0].trim());
int g = Integer.parseInt(parts[1].trim());
int b = Integer.parseInt(parts[2].trim());
style.color = TextColor.fromRgb((r << 16) | (g << 8) | b);
// 清除渐变、过渡和彩虹效果
style.gradient = false;
style.transition = false;
style.rainbow = false;
} catch (NumberFormatException e) {
// 忽略解析错误
}
}
}
}
}
/**
* 处理阴影标签
* 支持格式: <shadow>, <shadow:true>, <shadow:false>, <shadow:#FF0000>
*/
private static void handleShadowTag(StyleContext style, String params, String value) {
if (value == null) {
style.shadow = true;
} else if (value.equalsIgnoreCase("true")) {
style.shadow = true;
} else if (value.equalsIgnoreCase("false")) {
style.shadow = false;
} else {
// 自定义阴影颜色
TextColor shadowColor = parseColor(value);
if (shadowColor != null) {
style.shadow = true;
style.shadowColor = shadowColor;
}
}
}
/**
* 处理装饰标签
* 支持格式: <decoration:bold>, <decoration:bold:true>, <decoration:bold:false>
*/
private static void handleDecorationTag(StyleContext style, String params, String value) {
if (params == null) return;
String[] parts = params.split(":");
String decorationName = parts[0].toLowerCase(Locale.ROOT);
boolean enabled = true;
if (parts.length > 1) {
enabled = Boolean.parseBoolean(parts[1]);
} else if (value != null) {
enabled = Boolean.parseBoolean(value);
}
ChatFormatting formatting = DECORATION_MAP.get(decorationName);
if (formatting != null) {
style.decorations.put(formatting, enabled);
}
}
/**
* 处理彩虹色标签
* 支持格式: <rainbow>, <rainbow:!>, <rainbow(saturation):0.8>, <rainbow(brightness):0.5>
*/
private static void handleRainbowTag(StyleContext style, String params, String value) {
float saturation = 1.0f;
float brightness = 1.0f;
if (params != null) {
if (params.equalsIgnoreCase("saturation") && value != null) {
try {
saturation = Float.parseFloat(value);
} catch (NumberFormatException e) {
}
} else if (params.equalsIgnoreCase("brightness") && value != null) {
try {
brightness = Float.parseFloat(value);
} catch (NumberFormatException e) {
}
}
}
style.rainbow = true;
style.gradient = false;
style.transition = false;
style.rainbowSaturation = saturation;
style.rainbowBrightness = brightness;
}
/**
* 处理渐变色标签
* 支持格式: <gradient:#FF0000:#00FF00>, <gradient(rgb):255,0,0:0,255,0>
* 渐变是逐字符过渡
*/
private static void handleGradientTag(StyleContext style, String params, String value) {
if (value == null) return;
String[] colors = value.split(":");
if (colors.length == 2) {
TextColor start = parseColor(colors[0]);
TextColor end = parseColor(colors[1]);
if (start != null && end != null) {
style.gradient = true;
style.transition = false;
style.rainbow = false;
style.gradientStart = start;
style.gradientEnd = end;
}
} else if (params != null && params.equalsIgnoreCase("rgb")) {
// 处理 RGB 格式
String[] color1 = colors[0].split(",");
String[] color2 = colors[1].split(",");
if (color1.length == 3 && color2.length == 3) {
try {
int r1 = Integer.parseInt(color1[0].trim());
int g1 = Integer.parseInt(color1[1].trim());
int b1 = Integer.parseInt(color1[2].trim());
int r2 = Integer.parseInt(color2[0].trim());
int g2 = Integer.parseInt(color2[1].trim());
int b2 = Integer.parseInt(color2[2].trim());
style.gradient = true;
style.transition = false;
style.rainbow = false;
style.gradientStart = TextColor.fromRgb((r1 << 16) | (g1 << 8) | b1);
style.gradientEnd = TextColor.fromRgb((r2 << 16) | (g2 << 8) | b2);
} catch (NumberFormatException e) {
}
}
}
}
/**
* 处理过渡色标签
* 支持格式: <transition:#FF0000:#00FF00>, <transition(rgb):255,0,0:0,255,0>
* 过渡是整个文本块的颜色过渡(类似整体渐变效果)
*/
private static void handleTransitionTag(StyleContext style, String params, String value) {
if (value == null) return;
String[] colors = value.split(":");
if (colors.length == 2) {
TextColor start = parseColor(colors[0]);
TextColor end = parseColor(colors[1]);
if (start != null && end != null) {
style.transition = true;
style.gradient = false;
style.rainbow = false;
style.transitionStart = start;
style.transitionEnd = end;
}
} else if (params != null && params.equalsIgnoreCase("rgb")) {
// 处理 RGB 格式
String[] color1 = colors[0].split(",");
String[] color2 = colors[1].split(",");
if (color1.length == 3 && color2.length == 3) {
try {
int r1 = Integer.parseInt(color1[0].trim());
int g1 = Integer.parseInt(color1[1].trim());
int b1 = Integer.parseInt(color1[2].trim());
int r2 = Integer.parseInt(color2[0].trim());
int g2 = Integer.parseInt(color2[1].trim());
int b2 = Integer.parseInt(color2[2].trim());
style.transition = true;
style.gradient = false;
style.rainbow = false;
style.transitionStart = TextColor.fromRgb((r1 << 16) | (g1 << 8) | b1);
style.transitionEnd = TextColor.fromRgb((r2 << 16) | (g2 << 8) | b2);
} catch (NumberFormatException e) {
}
}
}
}
/**
* 处理字体标签
* 支持格式: <font:minecraft:default>, <font:minecraft:uniform>
*/
private static void handleFontTag(StyleContext style, String params, String value) {
String fontName = value != null ? value : params;
if (fontName != null) {
style.font = fontName;
}
}
/**
* 解析颜色字符串
* 支持: 颜色名称, #RRGGBB, #RGB, 十六进制数值
*/
private static TextColor parseColor(String colorStr) {
if (colorStr == null) return null;
// 检查是否为预定义颜色名称
ChatFormatting formatting = COLOR_MAP.get(colorStr.toLowerCase(Locale.ROOT));
if (formatting != null) {
return TextColor.fromLegacyFormat(formatting);
}
// 检查十六进制格式
if (colorStr.startsWith("#")) {
try {
if (colorStr.length() == 7) {
return TextColor.parseColor(colorStr).getOrThrow();
} else if (colorStr.length() == 4) {
// 扩展 #RGB 到 #RRGGBB
String expanded = "#" +
colorStr.charAt(1) + colorStr.charAt(1) +
colorStr.charAt(2) + colorStr.charAt(2) +
colorStr.charAt(3) + colorStr.charAt(3);
return TextColor.parseColor(expanded).getOrThrow();
}
} catch (Exception e) {
// 忽略解析错误
}
}
return null;
}
/**
* 查找标签闭合位置
*/
private static int findClosingTag(String input, int start) {
int pos = start + 1;
while (pos < input.length()) {
char c = input.charAt(pos);
if (c == '>') {
return pos;
}
pos++;
}
return -1;
}
/**
* 添加文本到组件
*/
private static void appendText(MutableComponent root, String text, StyleContext styleContext) {
if (text.isEmpty()) return;
// 如果有渐变效果,需要逐字符处理
if (styleContext.gradient && styleContext.gradientStart != null && styleContext.gradientEnd != null) {
appendGradientText(root, text, styleContext);
}
// 如果有过渡效果,需要逐字符处理
else if (styleContext.transition && styleContext.transitionStart != null && styleContext.transitionEnd != null) {
appendTransitionText(root, text, styleContext);
}
// 如果有彩虹效果,需要逐字符处理
else if (styleContext.rainbow) {
appendRainbowText(root, text, styleContext);
}
// 普通文本
else {
MutableComponent component = Component.literal(text);
component.setStyle(styleContext.buildStyle());
root.append(component);
}
}
/**
* 带渐变效果的文本追加(逐字符颜色变化)
*/
private static void appendGradientText(MutableComponent root, String text, StyleContext styleContext) {
int length = text.length();
Style baseStyle = styleContext.buildBaseStyle();
for (int i = 0; i < length; i++) {
char c = text.charAt(i);
float ratio = (float) i / (length - 1);
TextColor gradientColor = interpolateColor(styleContext.gradientStart, styleContext.gradientEnd, ratio);
Style charStyle = baseStyle.withColor(gradientColor);
// 重新应用阴影
// if (styleContext.shadow != null && styleContext.shadow) {
// if (styleContext.shadowColor != null) {
// charStyle = charStyle.withShadowColor(styleContext.shadowColor.getValue() | 0xFF000000);
// } else {
// charStyle = charStyle.withShadowColor(0xFF000000);
// }
// }
root.append(Component.literal(String.valueOf(c)).withStyle(charStyle));
}
}
/**
* 带过渡效果的文本追加(整体颜色过渡)
*/
private static void appendTransitionText(MutableComponent root, String text, StyleContext styleContext) {
Style baseStyle = styleContext.buildBaseStyle();
// 过渡效果:为整个文本块创建一个颜色过渡
MutableComponent transitionComponent = Component.literal(text);
// 应用过渡样式(这里使用颜色插值,但也可以根据需求调整)
if (styleContext.transitionStart != null && styleContext.transitionEnd != null) {
// 可以使用整体渐变或者固定起始色
transitionComponent.setStyle(baseStyle.withColor(styleContext.transitionStart));
} else {
transitionComponent.setStyle(baseStyle);
}
root.append(transitionComponent);
}
/**
* 带彩虹效果的文本追加
*/
private static void appendRainbowText(MutableComponent root, String text, StyleContext styleContext) {
int length = text.length();
Style baseStyle = styleContext.buildBaseStyle();
for (int i = 0; i < length; i++) {
char c = text.charAt(i);
float hue = (float) i / length;
int rgb = java.awt.Color.HSBtoRGB(hue, styleContext.rainbowSaturation, styleContext.rainbowBrightness);
TextColor rainbowColor = TextColor.fromRgb(rgb & 0xFFFFFF);
Style charStyle = baseStyle.withColor(rainbowColor);
// 重新应用阴影
// if (styleContext.shadow != null && styleContext.shadow) {
// if (styleContext.shadowColor != null) {
// charStyle = charStyle.withShadowColor(styleContext.shadowColor.getValue() | 0xFF000000);
// } else {
// charStyle = charStyle.withShadowColor(0xFF000000);
// }
// }
root.append(Component.literal(String.valueOf(c)).withStyle(charStyle));
}
}
/**
* 颜色插值
*/
private static TextColor interpolateColor(TextColor start, TextColor end, float ratio) {
int startRgb = start.getValue();
int endRgb = end.getValue();
int r1 = (startRgb >> 16) & 0xFF;
int g1 = (startRgb >> 8) & 0xFF;
int b1 = startRgb & 0xFF;
int r2 = (endRgb >> 16) & 0xFF;
int g2 = (endRgb >> 8) & 0xFF;
int b2 = endRgb & 0xFF;
int r = (int) (r1 + (r2 - r1) * ratio);
int g = (int) (g1 + (g2 - g1) * ratio);
int b = (int) (b1 + (b2 - b1) * ratio);
return TextColor.fromRgb((r << 16) | (g << 8) | b);
}
/**
* 样式上下文类,用于追踪嵌套样式
*/
private static class StyleContext {
String tagName = null; // 记录标签名称,用于关闭匹配
TextColor color = null;
Boolean shadow = null;
TextColor shadowColor = null;
Map<ChatFormatting, Boolean> decorations = new HashMap<>();
boolean rainbow = false;
float rainbowSaturation = 1.0f;
float rainbowBrightness = 1.0f;
boolean gradient = false;
TextColor gradientStart = null;
TextColor gradientEnd = null;
boolean transition = false;
TextColor transitionStart = null;
TextColor transitionEnd = null;
String font = null;
StyleContext copy() {
StyleContext copy = new StyleContext();
copy.tagName = this.tagName;
copy.color = this.color;
copy.shadow = this.shadow;
copy.shadowColor = this.shadowColor;
copy.decorations.putAll(this.decorations);
copy.rainbow = this.rainbow;
copy.rainbowSaturation = this.rainbowSaturation;
copy.rainbowBrightness = this.rainbowBrightness;
copy.gradient = this.gradient;
copy.gradientStart = this.gradientStart;
copy.gradientEnd = this.gradientEnd;
copy.transition = this.transition;
copy.transitionStart = this.transitionStart;
copy.transitionEnd = this.transitionEnd;
copy.font = this.font;
return copy;
}
/**
* 构建基础样式(不含颜色)
*/
Style buildBaseStyle() {
Style style = Style.EMPTY;
// 应用装饰器
for (Map.Entry<ChatFormatting, Boolean> entry : decorations.entrySet()) {
if (entry.getValue()) {
style = style.applyFormat(entry.getKey());
}
}
if (font != null) {
try {
style = style.withFont(ResourceLocation.parse(font));
} catch (Exception e) {
// 忽略字体解析错误
}
}
return style;
}
/**
* 构建完整样式(含颜色)
*/
Style buildStyle() {
Style style = buildBaseStyle();
// 只有在没有渐变、过渡和彩虹效果时才应用普通颜色
if (!gradient && !transition && !rainbow && color != null) {
style = style.withColor(color);
}
// if (shadow != null && shadow) {
// if (shadowColor != null) {
// style = style.withShadowColor(shadowColor.getValue() | 0xFF000000);
// } else {
// style = style.withShadowColor(0xFF000000);
// }
// }
return style;
}
}
}
@@ -0,0 +1,88 @@
package com.coloryr.allmusic.client;
import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.PictureFrameBuffer;
import com.coloryr.allmusic.client.mixins.GuiRender;
import com.coloryr.allmusic.codec.HudPosType;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.resources.ResourceLocation;
import org.joml.Matrix4f;
import org.joml.Quaternionf;
import java.io.ByteArrayInputStream;
public class PicRender extends PictureFrameBuffer {
private static final ResourceLocation SOURCE_LOC = ResourceLocation.fromNamespaceAndPath("allmusic_client", "pic_source");
private static final ResourceLocation ROTATE_LOC = ResourceLocation.fromNamespaceAndPath("allmusic_client", "pic_rotate");
private final DynamicTexture sourceTexture;
private final DynamicTexture rotateTexture;
public PicRender(int size) {
sourceTexture = new DynamicTexture(size, size, false);
rotateTexture = new DynamicTexture(size, size, false);
Minecraft.getInstance().getTextureManager().register(SOURCE_LOC, sourceTexture);
Minecraft.getInstance().getTextureManager().register(ROTATE_LOC, rotateTexture);
}
@Override
public void update(byte[] source, byte[] rotate) {
try {
ByteArrayInputStream stream = new ByteArrayInputStream(source);
var image1 = NativeImage.read(stream);
sourceTexture.setPixels(image1);
sourceTexture.upload();
stream = new ByteArrayInputStream(rotate);
var image2 = NativeImage.read(stream);
rotateTexture.setPixels(image2);
rotateTexture.upload();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void draw(boolean rotate, int size, float x, float y, int ang, HudPosType dir, float alpha) {
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
GuiRender render = (GuiRender) gui;
ResourceLocation loc = rotate ? ROTATE_LOC : SOURCE_LOC;
Point2f point = AllMusicHud.getPos(size, size, x, y, dir);
Matrix4f matrix;
int a = size / 2;
if (rotate) {
matrix = new Matrix4f().translationRotate(point.x + a, point.y + a, 0,
new Quaternionf().fromAxisAngleDeg(0, 0, 1, ang));
} else {
matrix = new Matrix4f().translation(point.x + a, point.y + a, 0);
}
int x0 = -a;
int x1 = a;
int y0 = -a;
int y1 = a;
int z = 0;
int u0 = 0;
float u1 = 1;
float v0 = 0;
float v1 = 1;
RenderType renderType = RenderType.guiTexturedOverlay(loc);
VertexConsumer bufferBuilder = render.getBufferSource().getBuffer(renderType);
bufferBuilder.addVertex(matrix, x0, y1, z).setUv(u0, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y1, z).setUv(u1, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y0, z).setUv(u1, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x0, y0, z).setUv(u0, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
}
}
@@ -0,0 +1,126 @@
package com.coloryr.allmusic.client;
import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextureRender;
import com.coloryr.allmusic.client.mixins.GuiRender;
import com.coloryr.allmusic.codec.HudPosType;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import org.joml.Matrix4f;
import java.io.IOException;
public class TexRender extends TextureRender {
private final ResourceLocation location;
public TexRender(String texture) {
super(texture);
location = ResourceLocation.fromNamespaceAndPath(AllMusicClient.MODID, texture);
try {
ResourceManager resourceManager = Minecraft.getInstance().getResourceManager();
var resource = resourceManager.getResource(location).orElseThrow();
try (var input = resource.open()) {
var nativeImage = NativeImage.read(input);
width = nativeImage.getWidth();
height = nativeImage.getHeight();
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void drawPic(float x, float y, float alpha) {
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
GuiRender render = (GuiRender) gui;
float w1 = (float) width / 2;
float h1 = (float) height / 2;
Matrix4f matrix = new Matrix4f().translation(x + w1, y + h1, 0);
float x0 = -w1;
float x1 = w1;
float y0 = -h1;
float y1 = h1;
float z = 0;
float u0 = 0;
float u1 = 1;
float v0 = 0;
float v1 = 1;
RenderType renderType = RenderType.guiTexturedOverlay(location);
VertexConsumer bufferBuilder = render.getBufferSource().getBuffer(renderType);
bufferBuilder.addVertex(matrix, x0, y1, z).setUv(u0, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y1, z).setUv(u1, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y0, z).setUv(u1, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x0, y0, z).setUv(u0, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
}
@Override
public void drawPic(float x, float y, float width, float alpha) {
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
GuiRender render = (GuiRender) gui;
float w1 = (float) (this.width / 2) * width;
float h1 = (float) height / 2;
Matrix4f matrix = new Matrix4f().translation(x + w1, y + h1, 0);
float x0 = -w1;
float x1 = w1;
float y0 = -h1;
float y1 = h1;
float z = 0;
float u0 = 0;
float u1 = width;
float v0 = 0;
float v1 = 1;
RenderType renderType = RenderType.guiTexturedOverlay(location);
VertexConsumer bufferBuilder = render.getBufferSource().getBuffer(renderType);
bufferBuilder.addVertex(matrix, x0, y1, z).setUv(u0, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y1, z).setUv(u1, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y0, z).setUv(u1, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x0, y0, z).setUv(u0, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
}
@Override
public void drawPic(float x, float y, float width, float height, HudPosType dir, float alpha) {
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
GuiRender render = (GuiRender) gui;
Point2f point = AllMusicHud.getPos(width, height, x, y, dir);
float w1 = width / 2;
float h1 = height / 2;
Matrix4f matrix = new Matrix4f().translation(point.x + w1, point.y + h1, 0);
float x0 = -w1;
float x1 = w1;
float y0 = -h1;
float y1 = h1;
float z = 0;
float u0 = 0;
float u1 = 1;
float v0 = 0;
float v1 = 1;
RenderType renderType = RenderType.guiTexturedOverlay(location);
VertexConsumer bufferBuilder = render.getBufferSource().getBuffer(renderType);
bufferBuilder.addVertex(matrix, x0, y1, z).setUv(u0, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y1, z).setUv(u1, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y0, z).setUv(u1, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x0, y0, z).setUv(u0, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
}
}
@@ -0,0 +1,12 @@
package com.coloryr.allmusic.client.mixins;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.MultiBufferSource;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(GuiGraphics.class)
public interface GuiRender {
@Accessor("bufferSource")
MultiBufferSource.BufferSource getBufferSource();
}
@@ -0,0 +1,33 @@
package com.coloryr.allmusic.comm;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.ModContainer;
import net.neoforged.fml.common.Mod;
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.neoforged.neoforge.network.handling.IPayloadHandler;
@Mod(AllMusicInit.MODID)
public class AllMusicInit implements IPayloadHandler<MusicCodec> {
public static final String MODID = "allmusic";
public static IPayloadHandler<MusicCodec> handler;
public AllMusicInit(IEventBus modEventBus, ModContainer modContainer) {
modEventBus.addListener(this::register);
}
public void register(final RegisterPayloadHandlersEvent event) {
event.registrar("1.0")
.optional()
.playToClient(MusicCodec.TYPE, MusicCodec.CODEC, this);
}
@Override
public void handle(MusicCodec data, IPayloadContext iPayloadContext) {
if (handler != null) {
handler.handle(data, iPayloadContext);
}
}
}
Loaded 100 of 108 files, more files were not shown because too many files have changed in this diff. Show more