mirror of
https://github.com/Coloryr/AllMusic.git
synced 2026-11-08 18:54:44 +00:00
4.0.3 添加歌曲时间修正配置
This commit is contained in:
1 parent
dedd11eb24
commit
e625144021
108 files changed
+5577
-53
No files matched your search
+1
-1
@@ -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
|
||||
|
||||
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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",
|
||||
|
||||
@@ -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
-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.gui.Gui;
|
||||
+1
-1
@@ -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
-1
@@ -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
-1
@@ -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",
|
||||
|
||||
@@ -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
-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
-1
@@ -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
-1
@@ -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
-1
@@ -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",
|
||||
|
||||
@@ -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
-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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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",
|
||||
|
||||
@@ -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
-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
-1
@@ -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
-1
@@ -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": {
|
||||
}
|
||||
}
|
||||
@@ -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": {
|
||||
}
|
||||
}
|
||||
@@ -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
-1
@@ -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
-1
@@ -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",
|
||||
|
||||
@@ -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
-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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+172
@@ -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);
|
||||
}
|
||||
}
|
||||
+12
@@ -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 |
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+172
@@ -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);
|
||||
}
|
||||
}
|
||||
+12
@@ -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
Reference in new issue
Block a user