This commit is contained in:
Color_yr committed 2026-09-19 13:54:26 +08:00
1 parent 93cf694530
commit 910c569ba7
96 files changed
+4254 -25

No files matched your search

+1 -1
View File
@@ -12,7 +12,7 @@ allprojects {
apply(plugin = "com.gradleup.shadow")
group = "com.coloryr"
version = "4.2.2"
version = "4.2.3"
// 设置项目JDK版本
java.sourceCompatibility = JavaVersion.VERSION_1_8
@@ -69,7 +69,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
}
public float getVolume() {
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS)
* Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.MASTER);
}
@Override
@@ -64,7 +64,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
}
public float getVolume() {
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS)
* Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.MASTER);
}
@Override
@@ -65,7 +65,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
}
public float getVolume() {
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS)
* Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.MASTER);
}
@Override
@@ -65,7 +65,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
}
public float getVolume() {
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS)
* Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.MASTER);
}
@Override
@@ -65,7 +65,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
}
public float getVolume() {
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS)
* Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.MASTER);
}
@Override
@@ -64,7 +64,7 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
}
public float getVolume() {
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
return Minecraft.getInstance().options.getFinalSoundSourceVolume(SoundSource.RECORDS);
}
@Override
@@ -64,7 +64,7 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
}
public float getVolume() {
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
return Minecraft.getInstance().options.getFinalSoundSourceVolume(SoundSource.RECORDS);
}
@Override
@@ -66,7 +66,7 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
}
public float getVolume() {
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
return Minecraft.getInstance().options.getFinalSoundSourceVolume(SoundSource.RECORDS);
}
@Override
@@ -65,7 +65,7 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
}
public float getVolume() {
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
return Minecraft.getInstance().options.getFinalSoundSourceVolume(SoundSource.RECORDS);
}
@Override
@@ -37,7 +37,6 @@
"fabric-api": "*"
},
"suggests": {
"modernui": "*",
"voicechat_api": "*"
}
}
+34
View File
@@ -0,0 +1,34 @@
plugins {
id("net.fabricmc.fabric-loom") version Versions.fabricLoom
}
java.sourceCompatibility = JavaVersion.VERSION_25
java.targetCompatibility = JavaVersion.VERSION_25
dependencies {
minecraft("com.mojang:minecraft:26.3")
implementation("net.fabricmc:fabric-loader:0.19.5")
implementation("net.fabricmc.fabric-api:fabric-api:0.161.0+26.3")
compileOnly("de.maxhenkel.voicechat:voicechat-api:2.6.0")
}
tasks {
processResources {
filesMatching("fabric.mod.json") {
expand(
"version" to project.version
)
}
}
shadowJar {
archiveFileName.set("[fabric-26.3]AllMusic_Client-${project.version}.jar")
destinationDirectory.set(file("${parent!!.projectDir}/../build"))
}
build {
dependsOn(shadowJar)
}
}
@@ -0,0 +1,135 @@
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 net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
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.GuiGraphicsExtractor;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
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 Logger LOGGER = LogManager.getLogger("AllMusic Client");
public static GuiGraphicsExtractor context;
public static void update(GuiGraphicsExtractor 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.sendSystemMessage(Component.literal(finalData));
});
}
public float getVolume() {
return Minecraft.getInstance().options.getFinalSoundSourceVolume(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 {
var man = Minecraft.getInstance().getResourceManager();
Resource resource = man.getResource(Identifier.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 {
var man = Minecraft.getInstance().getResourceManager();
Resource resource = man.getResource(Identifier.fromNamespaceAndPath(MODID, file)).orElseThrow();
return resource.open();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public void kick() {
Minecraft client = Minecraft.getInstance();
ClientPacketListener packetListener = client.getConnection();
if (packetListener != null) {
Connection connection = packetListener.getConnection();
connection.disconnect(Component.nullToEmpty("Old AllMusic server"));
}
}
@Override
public void onInitializeClient() {
ClientPlayNetworking.registerGlobalReceiver(MusicCodec.ID, (pack, handler) -> AllMusicCore.packDo(pack.pack()));
AllMusicCore.init(FabricLoader.getInstance().getConfigDir(), this);
ClientLifecycleEvents.CLIENT_STOPPING.register((a) -> AllMusicCore.stop());
}
}
@@ -0,0 +1,136 @@
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.GuiGraphicsExtractor;
import net.minecraft.network.chat.Component;
import java.util.ArrayList;
import java.util.List;
public class CoreRenderTarget extends TextFrameBuffer<Component> {
private final boolean isState;
public CoreRenderTarget(String name) {
isState = name.equals("state");
}
@Override
public void putText(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);
if (width == 0) {
return;
}
int height = font.lineHeight + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
texts.add(new TextItem<>(width, height, y, component, shadow, color));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphicsExtractor gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextItem<Component> entry : texts) {
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) getOffset(entry, maxWidth);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.text(font, entry.component, drawX - scrollOffset, drawY, finalColor, entry.shadow);
if (scrollOffset > 0) {
gui.text(font, entry.component, drawX - scrollOffset + entry.width, drawY, finalColor, entry.shadow);
}
gui.disableScissor();
} else {
gui.text(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;
}
GuiGraphicsExtractor gui = AllMusicClient.context;
if (gui == null) return;
TextItem<Component> entry = texts.get(line);
gui.text(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;
}
GuiGraphicsExtractor gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextItem<Component> entry : texts) {
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 + revealWidth, drawY + entry.height);
gui.text(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.text(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);
}
TextItem<Component> entry = texts.get(line);
return new Point2f(entry.width, entry.height);
}
private static int applyAlpha(int color, float alpha) {
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
}
@@ -0,0 +1,30 @@
package com.coloryr.allmusic.client;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.renderpearl.api.pipeline.RenderPipeline;
import net.minecraft.client.gui.navigation.ScreenRectangle;
import net.minecraft.client.gui.render.TextureSetup;
import net.minecraft.client.renderer.state.gui.GuiElementRenderState;
import org.joml.Matrix3x2f;
public record FloatRenderState(RenderPipeline pipeline, TextureSetup textureSetup, Matrix3x2f pose, float x0,
float y0, float x1, float y1, float u0, float u1, float v0, float v1, int color,
ScreenRectangle scissorArea,
ScreenRectangle bounds) implements GuiElementRenderState {
public FloatRenderState(RenderPipeline renderPipeline, TextureSetup textureSetup, Matrix3x2f matrix3x2f,
float i, float j, float k, float l, float f, float g, float h, float m, int n, ScreenRectangle screenRectangle) {
this(renderPipeline, textureSetup, matrix3x2f, i, j, k, l, f, g, h, m, n, screenRectangle, getBounds(i, j, k, l, matrix3x2f, screenRectangle));
}
private static ScreenRectangle getBounds(float i, float j, float k, float l, Matrix3x2f matrix3x2f, ScreenRectangle screenRectangle) {
ScreenRectangle screenRectangle2 = (new ScreenRectangle((int) Math.ceil(i + 1), (int) Math.ceil(j + 1), (int) Math.ceil(k - i + 1), (int) Math.ceil(l - j + 1))).transformMaxBounds(matrix3x2f);
return screenRectangle != null ? screenRectangle.intersection(screenRectangle2) : screenRectangle2;
}
public void buildVertices(VertexConsumer vertexConsumer) {
vertexConsumer.addVertexWith2DPose(pose(), x0(), y0()).setUv(u0(), v0()).setColor(color());
vertexConsumer.addVertexWith2DPose(pose(), x0(), y1()).setUv(u0(), v1()).setColor(color());
vertexConsumer.addVertexWith2DPose(pose(), x1(), y1()).setUv(u1(), v1()).setColor(color());
vertexConsumer.addVertexWith2DPose(pose(), x1(), y0()).setUv(u1(), v0()).setColor(color());
}
}
@@ -0,0 +1,645 @@
package com.coloryr.allmusic.client;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.*;
import net.minecraft.resources.Identifier;
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(new FontDescription.Resource(Identifier.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,72 @@
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.codec.HudPosType;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.gui.render.TextureSetup;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.client.renderer.texture.DynamicTexture;
import org.joml.Matrix3x2fStack;
import java.io.ByteArrayInputStream;
public class PicRender extends PictureFrameBuffer {
private final DynamicTexture sourceTexture;
private final DynamicTexture rotateTexture;
public PicRender(int size) {
sourceTexture = new DynamicTexture("allmusic source", size, size, false);
rotateTexture = new DynamicTexture("allmusic rotate", size, size, false);
}
@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) {
Point2f point = AllMusicHud.getPos(size, size, x, y, dir);
Matrix3x2fStack matrix = new Matrix3x2fStack(2);
float a = (float) size / 2;
if (rotate) {
matrix.translation(point.x + a, point.y + a);
matrix.pushMatrix().rotate((float) Math.toRadians(ang));
} else {
matrix.translation(point.x + a, point.y + a);
}
float x0 = -a;
float x1 = a;
float y0 = -a;
float y1 = a;
float u0 = 0;
float u1 = 1;
float v0 = 0;
float v1 = 1;
int color = 0xFFFFFF00 + (int) (255 * alpha);
AllMusicClient.context.guiRenderState.addGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
TextureSetup.singleTexture(rotate ? rotateTexture.getTextureView() : sourceTexture.getTextureView(), rotate ? rotateTexture.getSampler() : sourceTexture.getSampler()),
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.scissorStack.peek()));
}
}
@@ -0,0 +1,109 @@
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.codec.HudPosType;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.render.TextureSetup;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.client.renderer.texture.SimpleTexture;
import net.minecraft.client.renderer.texture.TextureContents;
import net.minecraft.resources.Identifier;
import org.joml.Matrix3x2f;
public class TexRender extends TextureRender {
private final SimpleTexture sourceTexture;
public TexRender(String texture) {
super(texture);
Identifier location = Identifier.fromNamespaceAndPath(AllMusicClient.MODID, texture);
sourceTexture = new SimpleTexture(location);
try {
TextureContents contents = sourceTexture.loadContents(Minecraft.getInstance().getResourceManager());
width = contents.image().getWidth();
height = contents.image().getHeight();
sourceTexture.apply(contents);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void drawPic(float x, float y, float alpha) {
Matrix3x2f matrix = new Matrix3x2f();
float w1 = (float) width / 2;
float h1 = (float) height / 2;
matrix.translation(x + w1, y + h1);
float x0 = -w1;
float x1 = w1;
float y0 = -h1;
float y1 = h1;
float u0 = 0;
float u1 = 1;
float v0 = 0;
float v1 = 1;
int color = 0xFFFFFF00 + (int) (255 * alpha);
AllMusicClient.context.guiRenderState.addGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
TextureSetup.singleTexture(sourceTexture.getTextureView(), sourceTexture.getSampler()),
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.scissorStack.peek()));
}
@Override
public void drawPic(float x, float y, float width, float alpha) {
Matrix3x2f matrix = new Matrix3x2f();
float w1 = ((float) this.width / 2) * width;
float h1 = (float) height / 2;
matrix.translation(x + w1, y + h1);
float x0 = -w1;
float x1 = w1;
float y0 = -h1;
float y1 = h1;
float u0 = 0;
float u1 = width;
float v0 = 0;
float v1 = 1;
int color = 0xFFFFFF00 + (int) (255 * alpha);
AllMusicClient.context.guiRenderState.addGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
TextureSetup.singleTexture(sourceTexture.getTextureView(), sourceTexture.getSampler()),
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.scissorStack.peek()));
}
@Override
public void drawPic(float x, float y, float width, float height, HudPosType dir, float alpha) {
Point2f point = AllMusicHud.getPos(width, height, x, y, dir);
float w1 = width / 2;
float h1 = height / 2;
Matrix3x2f matrix = new Matrix3x2f().translation(point.x + w1, point.y + h1);
float x0 = -w1;
float x1 = w1;
float y0 = -h1;
float y1 = h1;
float u0 = 0;
float u1 = 1;
float v0 = 0;
float v1 = 1;
int color = 0xFFFFFF00 + (int) (255 * alpha);
AllMusicClient.context.guiRenderState.addGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
TextureSetup.singleTexture(sourceTexture.getTextureView(), sourceTexture.getSampler()),
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.scissorStack.peek()));
}
}
@@ -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.GuiGraphicsExtractor;
import net.minecraft.client.gui.Hud;
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(Hud.class)
public class GuiShow {
@Inject(method = "extractRenderState", at = @At(value = "HEAD"))
public void guiShow(GuiGraphicsExtractor graphics, DeltaTracker deltaTracker, CallbackInfo ci) {
AllMusicClient.update(graphics);
}
}
@@ -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;ZZ)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;resetData()V"))
public void quit(CallbackInfo info) {
AllMusicCore.onServerQuit();
}
}
@@ -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 Register {
@Inject(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/PacketProcessor;<init>(Ljava/lang/Thread;)V"))
public void register(CallbackInfo info) {
AllMusicCore.renderInit();
}
}
@@ -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.SoundEngine;
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;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(SoundManager.class)
public class SoundEvent {
@Inject(method = "play", at = @At("HEAD"), cancellable = true)
public void play(SoundInstance sound, CallbackInfoReturnable<SoundEngine.PlayResult> cir) {
if (AllMusicCore.isPlay()) {
SoundSource data = sound.getSource();
switch (data) {
case RECORDS, MUSIC: {
cir.cancel();
cir.setReturnValue(SoundEngine.PlayResult.NOT_STARTED);
}
}
}
}
@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();
}
}
}
}
@@ -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 Tick {
@Inject(method = "tick", at = @At("HEAD"))
public void tick(CallbackInfo info) {
AllMusicCore.tick();
}
}
@@ -0,0 +1,32 @@
package com.coloryr.allmusic.client.others;
import com.coloryr.allmusic.client.core.AllMusicCore;
import de.maxhenkel.voicechat.api.VoicechatPlugin;
import de.maxhenkel.voicechat.api.events.ClientReceiveSoundEvent;
import de.maxhenkel.voicechat.api.events.EventRegistration;
public class VoiceChat implements VoicechatPlugin {
@Override
public String getPluginId() {
return "allmusic";
}
@Override
public void registerEvents(EventRegistration registration) {
registration.registerEvent(ClientReceiveSoundEvent.EntitySound.class, this::play);
registration.registerEvent(ClientReceiveSoundEvent.LocationalSound.class, this::play);
registration.registerEvent(ClientReceiveSoundEvent.StaticSound.class, this::play);
}
public void play(ClientReceiveSoundEvent.EntitySound event) {
AllMusicCore.chat();
}
public void play(ClientReceiveSoundEvent.LocationalSound event) {
AllMusicCore.chat();
}
public void play(ClientReceiveSoundEvent.StaticSound event) {
AllMusicCore.chat();
}
}
@@ -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.clientboundPlay().register(MusicCodec.ID, MusicCodec.CODEC);
}
}
@@ -0,0 +1,23 @@
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.Identifier;
import org.jspecify.annotations.NonNull;
public record MusicCodec(MusicPack pack) implements CustomPacketPayload {
public static final Identifier ChannelID = Identifier.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 @NonNull Type<? extends CustomPacketPayload> type() {
return ID;
}
}
@@ -0,0 +1,18 @@
{
"required": true,
"package": "com.coloryr.allmusic.client.mixins",
"compatibilityLevel": "JAVA_25",
"client": [
"GuiShow",
"QuitServer",
"Register",
"SoundEvent",
"Tick"
],
"injectors": {
"defaultRequire": 1
},
"overwrites": {
"requireAnnotations": true
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@@ -0,0 +1,42 @@
{
"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"
],
"voicechat": [
"com.coloryr.allmusic.client.others.VoiceChat"
]
},
"mixins": [
{
"config": "allmusic_client.mixins.json",
"environment": "client"
}
],
"depends": {
"fabricloader": ">=0.19.5",
"minecraft": ">=26.3",
"fabric-api": "*"
},
"suggests": {
"voicechat_api": "*"
}
}
@@ -127,7 +127,8 @@ public class AllMusicClient implements AllMusicBridge {
}
public float getVolume() {
return Minecraft.getMinecraft().gameSettings.getSoundLevel(SoundCategory.RECORDS);
return Minecraft.getMinecraft().gameSettings.getSoundLevel(SoundCategory.RECORDS)
* Minecraft.getMinecraft().gameSettings.getSoundLevel(SoundCategory.MASTER);
}
@Override
@@ -118,7 +118,8 @@ public class AllMusicClient implements AllMusicBridge {
}
public float getVolume() {
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS)
* Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.MASTER);
}
@SubscribeEvent
@@ -145,7 +145,7 @@ public class AllMusic implements AllMusicBridge {
}
@Override
public TextFrameBuffer makeTextRender(String name) {
public TextFrameBuffer<?> makeTextRender(String name) {
return new CoreRenderTarget(name);
}
@@ -218,7 +218,8 @@ public class AllMusic implements AllMusicBridge {
}
public float getVolume() {
return Minecraft.getMinecraft().gameSettings.getSoundLevel(SoundCategory.RECORDS);
return Minecraft.getMinecraft().gameSettings.getSoundLevel(SoundCategory.RECORDS)
* Minecraft.getMinecraft().gameSettings.getSoundLevel(SoundCategory.MASTER);
}
@Override
@@ -93,7 +93,8 @@ public class AllMusicClient implements IPayloadHandler<MusicCodec>, AllMusicBrid
}
public float getVolume() {
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS)
* Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.MASTER);
}
@Override
@@ -134,7 +134,7 @@ public class AllMusicClient implements IPayloadHandler<MusicCodec>, AllMusicBrid
}
public float getVolume() {
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
return Minecraft.getInstance().options.getFinalSoundSourceVolume(SoundSource.RECORDS);
}
@Override
@@ -92,7 +92,8 @@ public class AllMusicClient implements IPayloadHandler<MusicCodec>, AllMusicBrid
}
public float getVolume() {
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS)
* Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.MASTER);
}
@Override
@@ -92,7 +92,8 @@ public class AllMusicClient implements IPayloadHandler<MusicCodec>, AllMusicBrid
}
public float getVolume() {
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS)
* Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.MASTER);
}
@Override
@@ -128,7 +128,7 @@ public class AllMusicClient implements AllMusicBridge, IPayloadHandler<MusicCode
}
public float getVolume() {
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
return Minecraft.getInstance().options.getFinalSoundSourceVolume(SoundSource.RECORDS);
}
@Override
@@ -135,7 +135,7 @@ public class AllMusicClient implements IPayloadHandler<MusicCodec>, AllMusicBrid
}
public float getVolume() {
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
return Minecraft.getInstance().options.getFinalSoundSourceVolume(SoundSource.RECORDS);
}
@Override
-1
View File
@@ -19,7 +19,6 @@ dependencies {
minecraft("com.mojang:minecraft:26.2")
neoForge("net.neoforged:neoforge:26.2.0.1-beta")
// compileOnly("icyllis.modernui:ModernUI-NeoForge:26.1.2-3.13.0.4")
compileOnly("de.maxhenkel.voicechat:voicechat-api:2.6.0")
}
@@ -135,11 +135,11 @@ public class AllMusicClient implements IPayloadHandler<MusicCodec>, AllMusicBrid
}
public float getVolume() {
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
return Minecraft.getInstance().options.getFinalSoundSourceVolume(SoundSource.RECORDS);
}
@Override
public TextFrameBuffer makeTextRender(String name) {
public TextFrameBuffer<?> makeTextRender(String name) {
return new CoreRenderTarget(name);
}
+40
View File
@@ -0,0 +1,40 @@
plugins {
id("dev.architectury.loom-no-remap") version Versions.architecturyLoom
// id("architectury-plugin") version "3.5-SNAPSHOT"
}
java.sourceCompatibility = JavaVersion.VERSION_25
java.targetCompatibility = JavaVersion.VERSION_25
//architectury {
// platformSetupLoomIde()
// neoForge()
//}
repositories {
maven("https://maven.neoforged.net/releases/")
}
dependencies {
minecraft("com.mojang:minecraft:26.3")
neoForge("net.neoforged:neoforge:26.3.0.6-beta")
compileOnly("de.maxhenkel.voicechat:voicechat-api:2.6.0")
}
tasks {
processResources {
filesMatching("META-INF/neoforge.mods.toml") {
expand("version" to project.version)
}
}
shadowJar {
archiveFileName.set("[neoforge-26.3]AllMusic_Client-${project.version}.jar")
destinationDirectory.set(file("${parent!!.projectDir}/../build"))
}
build {
dependsOn(shadowJar)
}
}
+1
View File
@@ -0,0 +1 @@
loom.platform=neoforge
@@ -0,0 +1,193 @@
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.GuiGraphicsExtractor;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
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.ModList;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 boolean modui;
public static final Logger LOGGER = LoggerFactory.getLogger("AllMusic Client");
public static GuiGraphicsExtractor context;
@SubscribeEvent
public static void setup(final FMLClientSetupEvent event) {
AllMusicClient client = new AllMusicClient();
AllMusicInit.handler = client;
AllMusicCore.init(FMLPaths.CONFIGDIR.get(), client);
Minecraft.getInstance().execute(AllMusicCore::renderInit);
if (ModList.get().isLoaded("modernui")) {
modui = true;
}
}
@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();
}
}
@SubscribeEvent
public static void onTick(ClientTickEvent.Post event) {
AllMusicCore.tick();
}
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.sendSystemMessage(Component.literal(finalData));
});
}
@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.getFinalSoundSourceVolume(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 {
var man = Minecraft.getInstance().getResourceManager();
Resource resource = man.getResource(Identifier.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 {
var man = Minecraft.getInstance().getResourceManager();
Resource resource = man.getResource(Identifier.fromNamespaceAndPath(MODID, file)).orElseThrow();
return resource.open();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public void kick() {
Minecraft client = Minecraft.getInstance();
ClientPacketListener packetListener = client.getConnection();
if (packetListener != null) {
Connection connection = packetListener.getConnection();
connection.disconnect(Component.nullToEmpty("Old AllMusic server"));
}
}
}
@@ -0,0 +1,133 @@
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.GuiGraphicsExtractor;
import net.minecraft.network.chat.Component;
public class CoreRenderTarget extends TextFrameBuffer<Component> {
private final boolean isState;
public CoreRenderTarget(String name) {
isState = name.equals("state");
}
@Override
public void putText(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);
if (width == 0) {
return;
}
int height = font.lineHeight + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
texts.add(new TextItem<>(width, height, y, component, shadow, color));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphicsExtractor gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextItem<Component> entry : texts) {
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) getOffset(entry, maxWidth);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.text(font, entry.component, drawX - scrollOffset, drawY, finalColor, entry.shadow);
if (scrollOffset > 0) {
gui.text(font, entry.component, drawX - scrollOffset + entry.width, drawY, finalColor, entry.shadow);
}
gui.disableScissor();
} else {
gui.text(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;
}
GuiGraphicsExtractor gui = AllMusicClient.context;
if (gui == null) return;
TextItem<Component> entry = texts.get(line);
gui.text(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;
}
GuiGraphicsExtractor gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextItem<Component> entry : texts) {
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 + revealWidth, drawY + entry.height);
gui.text(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.text(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);
}
TextItem<Component> entry = texts.get(line);
return new Point2f(entry.width, entry.height);
}
private static int applyAlpha(int color, float alpha) {
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
}
@@ -0,0 +1,30 @@
package com.coloryr.allmusic.client;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.renderpearl.api.pipeline.RenderPipeline;
import net.minecraft.client.gui.navigation.ScreenRectangle;
import net.minecraft.client.gui.render.TextureSetup;
import net.minecraft.client.renderer.state.gui.GuiElementRenderState;
import org.joml.Matrix3x2f;
public record FloatRenderState(RenderPipeline pipeline, TextureSetup textureSetup, Matrix3x2f pose, float x0,
float y0, float x1, float y1, float u0, float u1, float v0, float v1, int color,
ScreenRectangle scissorArea,
ScreenRectangle bounds) implements GuiElementRenderState {
public FloatRenderState(RenderPipeline renderPipeline, TextureSetup textureSetup, Matrix3x2f matrix3x2f,
float i, float j, float k, float l, float f, float g, float h, float m, int n, ScreenRectangle screenRectangle) {
this(renderPipeline, textureSetup, matrix3x2f, i, j, k, l, f, g, h, m, n, screenRectangle, getBounds(i, j, k, l, matrix3x2f, screenRectangle));
}
private static ScreenRectangle getBounds(float i, float j, float k, float l, Matrix3x2f matrix3x2f, ScreenRectangle screenRectangle) {
ScreenRectangle screenRectangle2 = (new ScreenRectangle((int) Math.ceil(i + 1), (int) Math.ceil(j + 1), (int) Math.ceil(k - i + 1), (int) Math.ceil(l - j + 1))).transformMaxBounds(matrix3x2f);
return screenRectangle != null ? screenRectangle.intersection(screenRectangle2) : screenRectangle2;
}
public void buildVertices(VertexConsumer vertexConsumer) {
vertexConsumer.addVertexWith2DPose(pose(), x0(), y0()).setUv(u0(), v0()).setColor(color());
vertexConsumer.addVertexWith2DPose(pose(), x0(), y1()).setUv(u0(), v1()).setColor(color());
vertexConsumer.addVertexWith2DPose(pose(), x1(), y1()).setUv(u1(), v1()).setColor(color());
vertexConsumer.addVertexWith2DPose(pose(), x1(), y0()).setUv(u1(), v0()).setColor(color());
}
}
@@ -0,0 +1,645 @@
package com.coloryr.allmusic.client;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.*;
import net.minecraft.resources.Identifier;
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(new FontDescription.Resource(Identifier.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,71 @@
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.codec.HudPosType;
import com.mojang.blaze3d.platform.NativeImage;
import net.minecraft.client.gui.render.TextureSetup;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.client.renderer.texture.DynamicTexture;
import org.joml.Matrix3x2fStack;
import java.io.ByteArrayInputStream;
public class PicRender extends PictureFrameBuffer {
private final DynamicTexture sourceTexture;
private final DynamicTexture rotateTexture;
public PicRender(int size) {
sourceTexture = new DynamicTexture("allmusic source", size, size, false);
rotateTexture = new DynamicTexture("allmusic rotate", size, size, false);
}
@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) {
Point2f point = AllMusicHud.getPos(size, size, x, y, dir);
Matrix3x2fStack matrix = new Matrix3x2fStack(2);
float a = (float) size / 2;
if (rotate) {
matrix.translation(point.x + a, point.y + a);
matrix.pushMatrix().rotate((float) Math.toRadians(ang));
} else {
matrix.translation(point.x + a, point.y + a);
}
float x0 = -a;
float x1 = a;
float y0 = -a;
float y1 = a;
float u0 = 0;
float u1 = 1;
float v0 = 0;
float v1 = 1;
int color = 0xFFFFFF00 + (int) (255 * alpha);
AllMusicClient.context.submitGuiElementRenderState(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
TextureSetup.singleTexture(rotate ? rotateTexture.getTextureView() : sourceTexture.getTextureView(), rotate ? rotateTexture.getSampler() : sourceTexture.getSampler()),
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.peekScissorStack()));
}
}
@@ -0,0 +1,109 @@
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.codec.HudPosType;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.render.TextureSetup;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.client.renderer.texture.SimpleTexture;
import net.minecraft.client.renderer.texture.TextureContents;
import net.minecraft.resources.Identifier;
import org.joml.Matrix3x2f;
public class TexRender extends TextureRender {
private final SimpleTexture sourceTexture;
public TexRender(String texture) {
super(texture);
Identifier location = Identifier.fromNamespaceAndPath(AllMusicClient.MODID, texture);
sourceTexture = new SimpleTexture(location);
try {
TextureContents contents = sourceTexture.loadContents(Minecraft.getInstance().getResourceManager());
width = contents.image().getWidth();
height = contents.image().getHeight();
sourceTexture.apply(contents);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void drawPic(float x, float y, float alpha) {
Matrix3x2f matrix = new Matrix3x2f();
float w1 = (float) width / 2;
float h1 = (float) height / 2;
matrix.translation(x + w1, y + h1);
float x0 = -w1;
float x1 = w1;
float y0 = -h1;
float y1 = h1;
float u0 = 0;
float u1 = 1;
float v0 = 0;
float v1 = 1;
int color = 0xFFFFFF00 + (int) (255 * alpha);
AllMusicClient.context.submitGuiElementRenderState(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
TextureSetup.singleTexture(sourceTexture.getTextureView(), sourceTexture.getSampler()),
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.peekScissorStack()));
}
@Override
public void drawPic(float x, float y, float width, float alpha) {
Matrix3x2f matrix = new Matrix3x2f();
float w1 = ((float) this.width / 2) * width;
float h1 = (float) height / 2;
matrix.translation(x + w1, y + h1);
float x0 = -w1;
float x1 = w1;
float y0 = -h1;
float y1 = h1;
float u0 = 0;
float u1 = width;
float v0 = 0;
float v1 = 1;
int color = 0xFFFFFF00 + (int) (255 * alpha);
AllMusicClient.context.submitGuiElementRenderState(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
TextureSetup.singleTexture(sourceTexture.getTextureView(), sourceTexture.getSampler()),
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.peekScissorStack()));
}
@Override
public void drawPic(float x, float y, float width, float height, HudPosType dir, float alpha) {
Point2f point = AllMusicHud.getPos(width, height, x, y, dir);
float w1 = width / 2;
float h1 = height / 2;
Matrix3x2f matrix = new Matrix3x2f().translation(point.x + w1, point.y + h1);
float x0 = -w1;
float x1 = w1;
float y0 = -h1;
float y1 = h1;
float u0 = 0;
float u1 = 1;
float v0 = 0;
float v1 = 1;
int color = 0xFFFFFF00 + (int) (255 * alpha);
AllMusicClient.context.submitGuiElementRenderState(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
TextureSetup.singleTexture(sourceTexture.getTextureView(), sourceTexture.getSampler()),
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.peekScissorStack()));
}
}
@@ -0,0 +1,34 @@
package com.coloryr.allmusic.client.others;
import com.coloryr.allmusic.client.core.AllMusicCore;
import de.maxhenkel.voicechat.api.ForgeVoicechatPlugin;
import de.maxhenkel.voicechat.api.VoicechatPlugin;
import de.maxhenkel.voicechat.api.events.ClientReceiveSoundEvent;
import de.maxhenkel.voicechat.api.events.EventRegistration;
@ForgeVoicechatPlugin
public class VoiceChat implements VoicechatPlugin {
@Override
public String getPluginId() {
return "allmusic";
}
@Override
public void registerEvents(EventRegistration registration) {
registration.registerEvent(ClientReceiveSoundEvent.EntitySound.class, this::play);
registration.registerEvent(ClientReceiveSoundEvent.LocationalSound.class, this::play);
registration.registerEvent(ClientReceiveSoundEvent.StaticSound.class, this::play);
}
public void play(ClientReceiveSoundEvent.EntitySound event) {
AllMusicCore.chat();
}
public void play(ClientReceiveSoundEvent.LocationalSound event) {
AllMusicCore.chat();
}
public void play(ClientReceiveSoundEvent.StaticSound event) {
AllMusicCore.chat();
}
}
@@ -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.Identifier;
public record MusicCodec(MusicPack pack) implements CustomPacketPayload {
public static final Identifier channel =
Identifier.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,42 @@
modLoader = "javafml"
loaderVersion = "[2,)"
license = "GPL-3.0"
issueTrackerURL = "https://github.com/Coloryr/AllMusic/issues"
[[mods]]
modId = "allmusic"
version = "${version}"
displayName = "AllMusic"
displayURL = "https://github.com/Coloryr/AllMusic/"
logoFile = "icon.png"
authors = "Color_yr"
description = '''AllMusic 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'''
[[dependencies.allmusic_client]]
modId = "neoforge"
type = "required"
versionRange = "[26.3.0.6-beta,)"
ordering = "NONE"
side = "CLIENT"
[[dependencies.allmusic_client]]
modId = "minecraft"
type = "required"
versionRange = "[26.3,)"
ordering = "NONE"
side = "CLIENT"
[[dependencies.allmusic_client]]
modId="voicechat_api"
type="optional"
ordering="AFTER"
side="CLIENT"
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

+68
View File
@@ -0,0 +1,68 @@
plugins {
id("net.fabricmc.fabric-loom") version Versions.fabricLoom
}
java.sourceCompatibility = JavaVersion.VERSION_25
java.targetCompatibility = JavaVersion.VERSION_25
// 直接引用 client 和 server 模块的源码目录,无需手动复制
val clientSourceDir = project(":client:fabric_26_3").projectDir.resolve("src/main/java/com/coloryr/allmusic/client")
val serverSourceDir = project(":server:fabric_26_3").projectDir.resolve("src/main/java/com/coloryr/allmusic/server")
val commSourceDir = project(":server:fabric_26_3").projectDir.resolve("src/main/java/com/coloryr/allmusic/comm")
val clientResDir = project(":client:fabric_26_3").projectDir.resolve("src/main/resources")
val serverResDir = project(":server:fabric_26_3").projectDir.resolve("src/main/resources")
sourceSets {
main {
java {
setSrcDirs(listOf(clientSourceDir, serverSourceDir, commSourceDir))
}
resources {
setSrcDirs(listOf(
file("src/main/resources"), // onejar 自己的合并资源(fabric.mod.json)
clientResDir,
serverResDir
))
}
}
}
dependencies {
minecraft("com.mojang:minecraft:26.3")
implementation("net.fabricmc:fabric-loader:0.19.5")
implementation("net.fabricmc.fabric-api:fabric-api:0.161.0+26.3")
compileOnly("de.maxhenkel.voicechat:voicechat-api:2.6.0")
implementation(include("net.kyori:adventure-platform-fabric:7.2.0")!!)
}
tasks {
processResources {
// onejar 自己的 fabric.mod.json 优先(排在 srcDirs 第一位),
// client/server 的 fabric.mod.json 作为重复项被排除
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
filesMatching("fabric.mod.json") {
expand(
"version" to project.version
)
}
}
// loom 的 jar-in-jar 嵌套默认只挂在 jar 任务上,需要手动接到 shadowJar
loom.nestJars(named("shadowJar", Jar::class), configurations.getByName("include"))
shadowJar {
archiveFileName.set("[fabric-26.3]AllMusic-${project.version}.jar")
destinationDirectory.set(file("${parent!!.projectDir}/../build"))
// relocate("net.kyori", "com.coloryr.allmusic.libs.net.kyori")
// relocate("com.google.gson", "com.coloryr.allmusic.libs.com.google.gson")
}
build {
dependsOn(shadowJar)
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@@ -0,0 +1,49 @@
{
"schemaVersion": 1,
"id": "allmusic",
"version": "${version}",
"name": "AllMusic",
"description": "AllMusic Mod",
"authors": [
"Color_yr"
],
"contact": {
"homepage": "https://github.com/Coloryr/AllMusic",
"sources": "https://github.com/Coloryr/AllMusic"
},
"license": "GPL-3.0",
"icon": "assets/allmusic/icon.png",
"environment": "*",
"entrypoints": {
"main": [
"com.coloryr.allmusic.comm.AllMusicInit",
"com.coloryr.allmusic.server.AllMusicServer"
],
"client": [
"com.coloryr.allmusic.client.AllMusicClient"
],
"voicechat": [
"com.coloryr.allmusic.client.others.VoiceChat"
]
},
"mixins": [
{
"config": "allmusic_client.mixins.json",
"environment": "client"
},
{
"config": "allmusic_server.mixins.json",
"environment": "server"
}
],
"depends": {
"fabricloader": ">=0.19.3",
"fabric-api": "*",
"minecraft": ">=26.2",
"adventure-platform-fabric": ">=7.0.0"
},
"suggests": {
"modernui": "*",
"voicechat_api": "*"
}
}
+75
View File
@@ -0,0 +1,75 @@
import net.fabricmc.loom.build.nesting.NestableJarGenerationTask
plugins {
id("dev.architectury.loom-no-remap") version Versions.architecturyLoom
// id("architectury-plugin") version "3.5-SNAPSHOT"
}
java.sourceCompatibility = JavaVersion.VERSION_25
java.targetCompatibility = JavaVersion.VERSION_25
// 直接引用 client 和 server 模块的源码目录,无需手动复制
val clientSourceDir = project(":client:neoforge_26_2").projectDir.resolve("src/main/java/com/coloryr/allmusic/client")
val serverSourceDir = project(":server:neoforge_26_2").projectDir.resolve("src/main/java/com/coloryr/allmusic/server")
val commSourceDir = project(":server:neoforge_26_2").projectDir.resolve("src/main/java/com/coloryr/allmusic/comm")
val clientResDir = project(":client:neoforge_26_2").projectDir.resolve("src/main/resources")
sourceSets {
main {
java {
setSrcDirs(listOf(clientSourceDir, serverSourceDir, commSourceDir))
}
resources {
setSrcDirs(listOf(
file("src/main/resources"), // onejar 自己的合并资源(fabric.mod.json)
clientResDir,
))
}
}
}
//architectury {
// platformSetupLoomIde()
// neoForge()
//}
repositories {
maven("https://maven.neoforged.net/releases/")
}
dependencies {
minecraft("com.mojang:minecraft:26.2")
neoForge("net.neoforged:neoforge:26.2.0.1-beta")
compileOnly("de.maxhenkel.voicechat:voicechat-api:2.6.0")
implementation(include("net.kyori:adventure-platform-neoforge:7.0.0")!!)
}
tasks {
processResources {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
filesMatching("META-INF/neoforge.mods.toml") {
expand("version" to project.version)
}
}
// loom 的 jar-in-jar 嵌套默认只挂在 jar 任务上,需要手动接到 shadowJar
val processIncludeJars = named("processIncludeJars", NestableJarGenerationTask::class)
val shadowJarTask = named("shadowJar", Jar::class)
shadowJarTask.configure { dependsOn(processIncludeJars) }
loom.nestJars(
shadowJarTask,
files(fileTree(processIncludeJars.flatMap { it.outputDirectory }).matching { include("**/*.jar") })
)
shadowJar {
archiveFileName.set("[neoforge-26.2]AllMusic-${project.version}.jar")
destinationDirectory.set(file("${parent!!.projectDir}/../build"))
}
build {
dependsOn(shadowJar)
}
}
+1
View File
@@ -0,0 +1 @@
loom.platform=neoforge
@@ -0,0 +1,71 @@
modLoader = "javafml"
loaderVersion = "[2,)"
license = "GPL-3.0"
issueTrackerURL = "https://github.com/Coloryr/AllMusic/issues/"
[[mods]]
modId = "allmusic"
version = "${version}"
displayName = "AllMusic"
displayURL = "https://github.com/Coloryr/AllMusic/"
logoFile = "icon.png"
authors = "Color_yr"
description = '''AllMusic 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'''
[[mods]]
modId = "allmusic_server"
version = "${version}"
displayName = "AllMusic Server"
displayURL = "https://github.com/Coloryr/AllMusic/"
logoFile = "icon.png"
authors = "Color_yr"
description = '''AllMusic Server Mod'''
[[dependencies.allmusic_client]]
modId = "neoforge"
type = "required"
versionRange = "[26.1.0.19-beta,)"
ordering = "NONE"
side = "CLIENT"
[[dependencies.allmusic_client]]
modId = "minecraft"
type = "required"
versionRange = "[26.1,)"
ordering = "NONE"
side = "CLIENT"
[[dependencies.allmusic_client]]
modId="modernui"
type="optional"
ordering="AFTER"
side="CLIENT"
[[dependencies.allmusic_client]]
modId="voicechat_api"
type="optional"
ordering="AFTER"
side="CLIENT"
[[dependencies.allmusic_server]]
modId = "neoforge"
type = "required"
versionRange = "[26.1.0.19-beta,)"
ordering = "NONE"
side = "BOTH"
[[dependencies.allmusic_server]]
modId = "minecraft"
type = "required"
versionRange = "[26.1,)"
ordering = "NONE"
side = "BOTH"
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

+75
View File
@@ -0,0 +1,75 @@
import net.fabricmc.loom.build.nesting.NestableJarGenerationTask
plugins {
id("dev.architectury.loom-no-remap") version Versions.architecturyLoom
// id("architectury-plugin") version "3.5-SNAPSHOT"
}
java.sourceCompatibility = JavaVersion.VERSION_25
java.targetCompatibility = JavaVersion.VERSION_25
// 直接引用 client 和 server 模块的源码目录,无需手动复制
val clientSourceDir = project(":client:neoforge_26_3").projectDir.resolve("src/main/java/com/coloryr/allmusic/client")
val serverSourceDir = project(":server:neoforge_26_3").projectDir.resolve("src/main/java/com/coloryr/allmusic/server")
val commSourceDir = project(":server:neoforge_26_3").projectDir.resolve("src/main/java/com/coloryr/allmusic/comm")
val clientResDir = project(":client:neoforge_26_3").projectDir.resolve("src/main/resources")
sourceSets {
main {
java {
setSrcDirs(listOf(clientSourceDir, serverSourceDir, commSourceDir))
}
resources {
setSrcDirs(listOf(
file("src/main/resources"), // onejar 自己的合并资源(fabric.mod.json)
clientResDir,
))
}
}
}
//architectury {
// platformSetupLoomIde()
// neoForge()
//}
repositories {
maven("https://maven.neoforged.net/releases/")
}
dependencies {
minecraft("com.mojang:minecraft:26.3")
neoForge("net.neoforged:neoforge:26.3.0.6-beta")
compileOnly("de.maxhenkel.voicechat:voicechat-api:2.6.0")
implementation(include("net.kyori:adventure-platform-neoforge:7.2.0")!!)
}
tasks {
processResources {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
filesMatching("META-INF/neoforge.mods.toml") {
expand("version" to project.version)
}
}
// loom 的 jar-in-jar 嵌套默认只挂在 jar 任务上,需要手动接到 shadowJar
val processIncludeJars = named("processIncludeJars", NestableJarGenerationTask::class)
val shadowJarTask = named("shadowJar", Jar::class)
shadowJarTask.configure { dependsOn(processIncludeJars) }
loom.nestJars(
shadowJarTask,
files(fileTree(processIncludeJars.flatMap { it.outputDirectory }).matching { include("**/*.jar") })
)
shadowJar {
archiveFileName.set("[neoforge-26.3]AllMusic-${project.version}.jar")
destinationDirectory.set(file("${parent!!.projectDir}/../build"))
}
build {
dependsOn(shadowJar)
}
}
+1
View File
@@ -0,0 +1 @@
loom.platform=neoforge
@@ -0,0 +1,65 @@
modLoader = "javafml"
loaderVersion = "[2,)"
license = "GPL-3.0"
issueTrackerURL = "https://github.com/Coloryr/AllMusic/issues/"
[[mods]]
modId = "allmusic"
version = "${version}"
displayName = "AllMusic"
displayURL = "https://github.com/Coloryr/AllMusic/"
logoFile = "icon.png"
authors = "Color_yr"
description = '''AllMusic 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'''
[[mods]]
modId = "allmusic_server"
version = "${version}"
displayName = "AllMusic Server"
displayURL = "https://github.com/Coloryr/AllMusic/"
logoFile = "icon.png"
authors = "Color_yr"
description = '''AllMusic Server Mod'''
[[dependencies.allmusic_client]]
modId = "neoforge"
type = "required"
versionRange = "[26.3.0.6-beta,)"
ordering = "NONE"
side = "CLIENT"
[[dependencies.allmusic_client]]
modId = "minecraft"
type = "required"
versionRange = "[26.3,)"
ordering = "NONE"
side = "CLIENT"
[[dependencies.allmusic_client]]
modId="voicechat_api"
type="optional"
ordering="AFTER"
side="CLIENT"
[[dependencies.allmusic_server]]
modId = "neoforge"
type = "required"
versionRange = "[26.3.0.6-beta,)"
ordering = "NONE"
side = "BOTH"
[[dependencies.allmusic_server]]
modId = "minecraft"
type = "required"
versionRange = "[26.3,)"
ordering = "NONE"
side = "BOTH"
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

+37
View File
@@ -0,0 +1,37 @@
plugins {
id("net.fabricmc.fabric-loom") version Versions.fabricLoom
}
java.sourceCompatibility = JavaVersion.VERSION_25
java.targetCompatibility = JavaVersion.VERSION_25
dependencies {
minecraft("com.mojang:minecraft:26.3")
implementation("net.fabricmc:fabric-loader:0.19.5")
implementation("net.fabricmc.fabric-api:fabric-api:0.161.0+26.3")
implementation(include("net.kyori:adventure-platform-fabric:7.2.0")!!)
}
tasks {
processResources {
filesMatching("fabric.mod.json") {
expand(
"version" to project.version
)
}
}
// loom 的 jar-in-jar 嵌套默认只挂在 jar 任务上,需要手动接到 shadowJar
loom.nestJars(named("shadowJar", Jar::class), configurations.getByName("include"))
shadowJar {
archiveFileName.set("[fabric-26.3]AllMusic_Server-${project.version}.jar")
destinationDirectory.set(file("${parent!!.projectDir}/../build"))
}
build {
dependsOn(shadowJar)
}
}
@@ -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.clientboundPlay().register(MusicCodec.ID, MusicCodec.CODEC);
}
}
@@ -0,0 +1,23 @@
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.Identifier;
import org.jspecify.annotations.NonNull;
public record MusicCodec(MusicPack pack) implements CustomPacketPayload {
public static final Identifier ChannelID = Identifier.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 @NonNull Type<? extends CustomPacketPayload> type() {
return ID;
}
}
@@ -0,0 +1,33 @@
package com.coloryr.allmusic.server;
import com.coloryr.allmusic.server.core.AllMusic;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.minecraft.server.MinecraftServer;
import java.io.File;
public class AllMusicServer implements ModInitializer {
public static MinecraftServer server;
@Override
public void onInitialize() {
AllMusic.log = new LogFabric();
AllMusic.side = new SideFabric();
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment)
-> CommandFabric.instance.register(dispatcher));
ServerLifecycleEvents.SERVER_STARTED.register((a) -> {
server = a;
AllMusic.init(new File(AllMusic.SERVER_DIR));
AllMusic.start();
Tasks.init();
});
ServerLifecycleEvents.SERVER_STOPPING.register((a) -> {
AllMusic.stop();
});
}
}
@@ -0,0 +1,69 @@
package com.coloryr.allmusic.server;
import com.coloryr.allmusic.server.core.command.CommandEX;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
public class CommandFabric implements Command<CommandSourceStack>, Predicate<CommandSourceStack>, SuggestionProvider<CommandSourceStack> {
public static CommandFabric instance = new CommandFabric();
public void register(CommandDispatcher<CommandSourceStack> dispatcher) {
dispatcher.register(((Commands.literal("music").requires(this)).executes(this))
.then(Commands.argument("args", StringArgumentType.greedyString())
.suggests(this).executes(this)));
}
@Override
public int run(CommandContext<CommandSourceStack> context) {
var item = context.getSource();
var input = context.getInput();
var temp = input.split(" ");
var arg = new String[temp.length - 1];
System.arraycopy(temp, 1, arg, 0, arg.length);
CommandEX.execute(item, context.getSource().getTextName(), arg);
return 0;
}
@Override
public boolean test(CommandSourceStack stack) {
return true;
}
@Override
public CompletableFuture<Suggestions> getSuggestions(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) {
var item = context.getSource();
var input = context.getInput();
var temp = input.split(" ");
var arg = new String[input.endsWith(" ") ? temp.length : temp.length - 1];
System.arraycopy(temp, 1, arg, 0, temp.length - 1);
builder = builder.createOffset(builder.getInput().lastIndexOf(32) + 1);
List<String> results = CommandEX.getTabList(item, item.getTextName(), arg);
String remaining = builder.getRemaining().trim();
for (String s : results) {
if (s.startsWith(remaining)) {
builder.suggest(s);
}
}
return builder.buildFuture();
}
}
@@ -0,0 +1,12 @@
package com.coloryr.allmusic.server;
import com.coloryr.allmusic.server.core.side.IAllMusicLogger;
import net.kyori.adventure.text.Component;
public class LogFabric implements IAllMusicLogger {
@Override
public void data(Component data) {
AllMusicServer.server.sendMessage(data);
}
}
@@ -0,0 +1,157 @@
package com.coloryr.allmusic.server;
import com.coloryr.allmusic.codec.MusicPack;
import com.coloryr.allmusic.comm.MusicCodec;
import com.coloryr.allmusic.server.core.AllMusic;
import com.coloryr.allmusic.server.core.objs.music.PlayerAddMusicObj;
import com.coloryr.allmusic.server.core.objs.music.SongInfoObj;
import com.coloryr.allmusic.server.core.side.BaseSide;
import com.coloryr.allmusic.server.event.MusicAddEvent;
import com.coloryr.allmusic.server.event.MusicPlayEvent;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.permissions.PermissionSet;
import net.minecraft.world.InteractionResult;
import java.io.File;
import java.util.Collection;
public class SideFabric extends BaseSide {
@Override
public void runTask(Runnable run) {
AllMusicServer.server.execute(run);
}
@Override
public void runTask(Runnable run1, int delay) {
Tasks.add(new TaskItem() {{
tick = delay;
run = run1;
}});
}
@Override
public boolean checkPermission(Object player) {
CommandSourceStack source = (CommandSourceStack) player;
return source.permissions() == PermissionSet.ALL_PERMISSIONS;
}
@Override
public boolean isPlayer(Object player) {
CommandSourceStack source = (CommandSourceStack) player;
return source.isPlayer();
}
@Override
public boolean checkPermission(Object player, String permission) {
return checkPermission(player);
}
@Override
public boolean needPlay(boolean islist) {
for (var player : AllMusicServer.server.getPlayerList().getPlayers()) {
if (!com.coloryr.allmusic.server.core.AllMusic.isSkip(player.getName().getString(), null, false, islist)) {
return true;
}
}
return false;
}
@Override
public Collection<?> getPlayers() {
return AllMusicServer.server.getPlayerList().getPlayers();
}
@Override
public String getPlayerName(Object player) {
if (player instanceof ServerPlayer player1) {
return player1.getName().getString();
}
return null;
}
@Override
public String getPlayerServer(Object player) {
return null;
}
@Override
public void send(Object player, MusicPack pack) {
if (player instanceof ServerPlayer player1) {
send(player1, pack);
}
}
@Override
public Object getPlayer(String player) {
return AllMusicServer.server.getPlayerList().getPlayer(player);
}
@Override
public void sendBar(Object player, net.kyori.adventure.text.Component data) {
if (player instanceof ServerPlayer player1) {
player1.sendActionBar(data);
}
}
@Override
public File getFolder() {
return new File(AllMusic.SERVER_DIR);
}
@Override
public void broadcast(net.kyori.adventure.text.Component message) {
for (var player : AllMusicServer.server.getPlayerList().getPlayers()) {
if (!AllMusic.isSkip(player.getName().getString(), null, false)) {
player.sendMessage(message);
}
}
}
@Override
public void sendMessage(Object obj, net.kyori.adventure.text.Component message) {
if (obj instanceof CommandSourceStack source) {
source.sendMessage(message);
}
}
@Override
public boolean onMusicPlay(SongInfoObj obj) {
return MusicPlayEvent.EVENT.invoker().interact(obj) != InteractionResult.PASS;
}
@Override
public boolean onMusicAdd(Object obj, PlayerAddMusicObj music) {
CommandSourceStack source = (CommandSourceStack) obj;
return MusicAddEvent.EVENT.invoker().interact(source.getPlayer(), music) != InteractionResult.PASS;
}
@Override
public Component miniMessage(String input) {
MiniMessage mm = MiniMessage.miniMessage();
return mm.deserialize(input);
}
@Override
public Component miniMessageRun(String input, String command) {
Component component = miniMessage(input);
return component.clickEvent(ClickEvent.runCommand(command));
}
@Override
public Component miniMessageSuggest(String input, String command) {
Component component = miniMessage(input);
return component.clickEvent(ClickEvent.suggestCommand(command));
}
private void send(ServerPlayer players, MusicPack data) {
if (players == null)
return;
runTask(() -> ServerPlayNetworking.send(players, new MusicCodec(data)));
}
}
@@ -0,0 +1,6 @@
package com.coloryr.allmusic.server;
public class TaskItem {
public int tick;
public Runnable run;
}
@@ -0,0 +1,27 @@
package com.coloryr.allmusic.server;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class Tasks {
private static final List<TaskItem> taskItems = new CopyOnWriteArrayList<>();
public static void init() {
AllMusicServer.server.addTickable(Tasks::tick);
}
public static void tick() {
for (var item : new ArrayList<>(taskItems)) {
item.tick--;
if (item.tick <= 0) {
taskItems.remove(item);
item.run.run();
}
}
}
public static void add(TaskItem item) {
taskItems.add(item);
}
}
@@ -0,0 +1,24 @@
package com.coloryr.allmusic.server.event;
import com.coloryr.allmusic.server.core.objs.music.PlayerAddMusicObj;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionResult;
public interface MusicAddEvent {
Event<MusicAddEvent> EVENT = EventFactory.createArrayBacked(MusicAddEvent.class,
(listeners) -> (player, music) -> {
for (MusicAddEvent listener : listeners) {
InteractionResult result = listener.interact(player, music);
if (result != InteractionResult.PASS) {
return result;
}
}
return InteractionResult.PASS;
});
InteractionResult interact(ServerPlayer player, PlayerAddMusicObj music);
}
@@ -0,0 +1,29 @@
package com.coloryr.allmusic.server.event;
import com.coloryr.allmusic.server.core.objs.music.SongInfoObj;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.world.InteractionResult;
public interface MusicPlayEvent {
Event<MusicPlayEvent> EVENT = EventFactory.createArrayBacked(MusicPlayEvent.class,
(listeners) -> (music) -> {
for (MusicPlayEvent listener : listeners) {
InteractionResult result = listener.interact(music);
if (result != InteractionResult.PASS) {
return result;
}
}
return InteractionResult.PASS;
});
/**
* 音乐播放事件,返回PASS表示通过
*
* @param music 歌曲信息
* @return 是否通过
*/
InteractionResult interact(SongInfoObj music);
}
@@ -0,0 +1,25 @@
package com.coloryr.allmusic.server.mixin;
import com.coloryr.allmusic.server.core.AllMusic;
import com.coloryr.allmusic.server.core.music.PlayMusic;
import net.minecraft.network.Connection;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.CommonListenerCookie;
import net.minecraft.server.players.PlayerList;
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(PlayerList.class)
public class ServerPlayerMixin {
@Inject(at = @At("HEAD"), method = "remove")
private void remove(ServerPlayer entity, CallbackInfo info) {
PlayMusic.removeNowPlayPlayer(entity.getName().getString());
}
@Inject(at = @At("TAIL"), method = "placeNewPlayer")
private void add(Connection connection, ServerPlayer player, CommonListenerCookie clientData, CallbackInfo ci) {
AllMusic.joinPlay(player.getName().getString());
}
}
@@ -0,0 +1,17 @@
{
"required": true,
"minVersion": "0.8",
"package": "com.coloryr.allmusic.server.mixin",
"compatibilityLevel": "JAVA_25",
"server": [
],
"mixins": [
"ServerPlayerMixin"
],
"injectors": {
"defaultRequire": 1
},
"overwrites": {
"requireAnnotations": true
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@@ -0,0 +1,37 @@
{
"schemaVersion": 1,
"id": "allmusic_server",
"version": "${version}",
"name": "AllMusic Server",
"description": "AllMusic Server Mod",
"authors": [
"Color_yr"
],
"contact": {
"homepage": "https://github.com/Coloryr/AllMusic",
"sources": "https://github.com/Coloryr/AllMusic"
},
"license": "GPL-3.0",
"icon": "assets/allmusic_server/icon.png",
"environment": "*",
"entrypoints": {
"main": [
"com.coloryr.allmusic.comm.AllMusicInit",
"com.coloryr.allmusic.server.AllMusicServer"
]
},
"mixins": [
{
"config": "allmusic_server.mixins.json",
"environment": "server"
}
],
"depends": {
"fabricloader": ">=0.19.5",
"minecraft": ">=26.3",
"fabric-api": "*",
"adventure-platform-fabric": ">=7.2.0"
},
"suggests": {
}
}
+51
View File
@@ -0,0 +1,51 @@
import net.fabricmc.loom.build.nesting.NestableJarGenerationTask
plugins {
id("dev.architectury.loom-no-remap") version Versions.architecturyLoom
// id("architectury-plugin") version "3.5-SNAPSHOT"
}
java.sourceCompatibility = JavaVersion.VERSION_25
java.targetCompatibility = JavaVersion.VERSION_25
//architectury {
// platformSetupLoomIde()
// neoForge()
//}
repositories {
maven("https://maven.neoforged.net/releases/")
}
dependencies {
minecraft("com.mojang:minecraft:26.3")
neoForge("net.neoforged:neoforge:26.3.0.6-beta")
implementation(include("net.kyori:adventure-platform-neoforge:7.2.0")!!)
}
tasks {
processResources {
filesMatching("META-INF/neoforge.mods.toml") {
expand("version" to project.version)
}
}
// loom 的 jar-in-jar 嵌套默认只挂在 jar 任务上,需要手动接到 shadowJar
val processIncludeJars = named("processIncludeJars", NestableJarGenerationTask::class)
val shadowJarTask = named("shadowJar", Jar::class)
shadowJarTask.configure { dependsOn(processIncludeJars) }
loom.nestJars(
shadowJarTask,
files(fileTree(processIncludeJars.flatMap { it.outputDirectory }).matching { include("**/*.jar") })
)
shadowJar {
archiveFileName.set("[neoforge-26.3]AllMusic_Server-${project.version}.jar")
destinationDirectory.set(file("${parent!!.projectDir}/../build"))
}
build {
dependsOn(shadowJar)
}
}
+1
View File
@@ -0,0 +1 @@
loom.platform=neoforge
@@ -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.Identifier;
public record MusicCodec(MusicPack pack) implements CustomPacketPayload {
public static final Identifier channel =
Identifier.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,74 @@
package com.coloryr.allmusic.server;
import com.coloryr.allmusic.server.core.AllMusic;
import com.coloryr.allmusic.server.core.music.PlayMusic;
import com.mojang.brigadier.CommandDispatcher;
import net.kyori.adventure.platform.modcommon.MinecraftServerAudiences;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.server.MinecraftServer;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
import net.neoforged.neoforge.event.RegisterCommandsEvent;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
import net.neoforged.neoforge.event.server.ServerStartedEvent;
import net.neoforged.neoforge.event.server.ServerStoppingEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
@EventBusSubscriber(modid = "allmusic_server", value = Dist.DEDICATED_SERVER)
public class AllMusicServer {
public static final Logger LOGGER = LoggerFactory.getLogger("AllMusic Server");
public static MinecraftServer server;
public static MinecraftServerAudiences audiences;
@SubscribeEvent
public static void commonSetup(final FMLCommonSetupEvent event) {
AllMusic.log = new LogNeoForge();
AllMusic.side = new SideNeoForge();
}
@SubscribeEvent
public static void onRegisterCommands(RegisterCommandsEvent event) {
if (event.getCommandSelection() != Commands.CommandSelection.INTEGRATED) {
CommandDispatcher<CommandSourceStack> dispatcher = event.getDispatcher();
CommandNeoForge.instance.register(dispatcher);
}
}
@SubscribeEvent
public static void onServerStarting(ServerStartedEvent event) {
if (event.getServer().isDedicatedServer()) {
audiences = MinecraftServerAudiences.of(event.getServer());
server = event.getServer();
AllMusic.init(new File(AllMusic.SERVER_DIR));
AllMusic.start();
Tasks.init();
}
}
@SubscribeEvent
public static void onServerStopping(ServerStoppingEvent event) {
if (event.getServer().isDedicatedServer()) {
AllMusic.stop();
}
}
@SubscribeEvent
public static void onPlayerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) {
if (server != null) {
PlayMusic.removeNowPlayPlayer(event.getEntity().getName().getString());
}
}
@SubscribeEvent
public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) {
if (server != null) {
AllMusic.joinPlay(event.getEntity().getName().getString());
}
}
}
@@ -0,0 +1,66 @@
package com.coloryr.allmusic.server;
import com.coloryr.allmusic.server.core.command.CommandEX;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
public class CommandNeoForge implements Command<CommandSourceStack>, Predicate<CommandSourceStack>, SuggestionProvider<CommandSourceStack> {
public static CommandNeoForge instance = new CommandNeoForge();
public void register(CommandDispatcher<CommandSourceStack> dispatcher) {
dispatcher.register(((Commands.literal("music").requires(this)).executes(this))
.then(Commands.argument("args", StringArgumentType.greedyString())
.suggests(this).executes(this)));
}
@Override
public int run(CommandContext<CommandSourceStack> context) {
var item = context.getSource();
var input = context.getInput();
var temp = input.split(" ");
var arg = new String[temp.length - 1];
System.arraycopy(temp, 1, arg, 0, arg.length);
CommandEX.execute(item, context.getSource().getTextName(), arg);
return 0;
}
@Override
public boolean test(CommandSourceStack stack) {
return true;
}
@Override
public CompletableFuture<Suggestions> getSuggestions(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) {
var item = context.getSource();
var input = context.getInput();
var temp = input.split(" ");
var arg = new String[input.endsWith(" ") ? temp.length : temp.length - 1];
System.arraycopy(temp, 1, arg, 0, temp.length - 1);
if (input.endsWith(" "))
builder = builder.createOffset(builder.getInput().lastIndexOf(32) + 1);
List<String> results = CommandEX.getTabList(item, item.getTextName(), arg);
for (String s : results) {
builder.suggest(s);
}
return builder.buildFuture();
}
}
@@ -0,0 +1,11 @@
package com.coloryr.allmusic.server;
import com.coloryr.allmusic.server.core.side.IAllMusicLogger;
import net.kyori.adventure.text.Component;
public class LogNeoForge implements IAllMusicLogger {
@Override
public void data(Component data) {
AllMusicServer.audiences.console().sendMessage(data);
}
}
@@ -0,0 +1,161 @@
package com.coloryr.allmusic.server;
import com.coloryr.allmusic.codec.MusicPack;
import com.coloryr.allmusic.comm.MusicCodec;
import com.coloryr.allmusic.server.core.AllMusic;
import com.coloryr.allmusic.server.core.objs.music.PlayerAddMusicObj;
import com.coloryr.allmusic.server.core.objs.music.SongInfoObj;
import com.coloryr.allmusic.server.core.side.BaseSide;
import com.coloryr.allmusic.server.event.MusicAddEvent;
import com.coloryr.allmusic.server.event.MusicPlayEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.permissions.PermissionSet;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.network.PacketDistributor;
import java.io.File;
import java.util.Collection;
public class SideNeoForge extends BaseSide {
@Override
public void runTask(Runnable run) {
AllMusicServer.server.execute(run);
}
@Override
public void runTask(Runnable run1, int delay) {
Tasks.add(new TaskItem() {{
tick = delay;
run = run1;
}});
}
@Override
public boolean checkPermission(Object player, String permission) {
return checkPermission(player);
}
@Override
public boolean checkPermission(Object player) {
CommandSourceStack source = (CommandSourceStack) player;
return source.permissions() == PermissionSet.ALL_PERMISSIONS;
}
@Override
public boolean isPlayer(Object player) {
CommandSourceStack source = (CommandSourceStack) player;
return source.isPlayer();
}
@Override
public boolean needPlay(boolean islist) {
for (ServerPlayer player : AllMusicServer.server.getPlayerList().getPlayers()) {
if (!AllMusic.isSkip(player.getName().getString(), null, false, islist)) {
return true;
}
}
return false;
}
@Override
public Collection<?> getPlayers() {
return AllMusicServer.server.getPlayerList().getPlayers();
}
@Override
public String getPlayerName(Object player) {
if (player instanceof ServerPlayer player1) {
return player1.getName().getString();
}
return null;
}
@Override
public String getPlayerServer(Object player) {
return null;
}
@Override
public void send(Object player, MusicPack pack) {
if (player instanceof ServerPlayer player1) {
send(player1, pack);
}
}
@Override
public Object getPlayer(String player) {
return AllMusicServer.server.getPlayerList().getPlayerByName(player);
}
@Override
public void sendBar(Object player, Component data) {
if (player instanceof ServerPlayer player1) {
AllMusicServer.audiences.audience(player1).sendActionBar(data);
}
}
@Override
public File getFolder() {
return new File(AllMusic.SERVER_DIR);
}
@Override
public void broadcast(Component message) {
for (ServerPlayer player : AllMusicServer.server.getPlayerList().getPlayers()) {
if (!AllMusic.isSkip(player.getName().getString(), null, false)) {
AllMusicServer.audiences.audience(player).sendActionBar(message);
}
}
}
@Override
public void sendMessage(Object obj, Component message) {
if (obj instanceof CommandSourceStack source) {
AllMusicServer.audiences.audience(source).sendActionBar(message);
}
}
@Override
public boolean onMusicPlay(SongInfoObj obj) {
MusicPlayEvent event = new MusicPlayEvent(obj);
NeoForge.EVENT_BUS.post(event);
return event.isCancel();
}
@Override
public boolean onMusicAdd(Object obj, PlayerAddMusicObj music) {
CommandSourceStack source = (CommandSourceStack) obj;
MusicAddEvent event = new MusicAddEvent(music, source.getPlayer());
NeoForge.EVENT_BUS.post(event);
return event.isCancel();
}
@Override
public Component miniMessage(String input) {
MiniMessage mm = MiniMessage.miniMessage();
return mm.deserialize(input);
}
@Override
public Component miniMessageRun(String input, String command) {
Component component = miniMessage(input);
return component.clickEvent(ClickEvent.runCommand(command));
}
@Override
public Component miniMessageSuggest(String input, String command) {
Component component = miniMessage(input);
return component.clickEvent(ClickEvent.suggestCommand(command));
}
private void send(ServerPlayer players, MusicPack data) {
if (players == null)
return;
runTask(() -> PacketDistributor.sendToPlayer(players, new MusicCodec(data)));
}
}
@@ -0,0 +1,6 @@
package com.coloryr.allmusic.server;
public class TaskItem {
public int tick;
public Runnable run;
}
@@ -0,0 +1,27 @@
package com.coloryr.allmusic.server;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class Tasks {
private static final List<TaskItem> taskItems = new CopyOnWriteArrayList<>();
public static void init() {
AllMusicServer.server.addTickable(Tasks::tick);
}
public static void tick() {
for (TaskItem item : new ArrayList<>(taskItems)) {
item.tick--;
if (item.tick <= 0) {
taskItems.remove(item);
item.run.run();
}
}
}
public static void add(TaskItem item) {
taskItems.add(item);
}
}
@@ -0,0 +1,39 @@
package com.coloryr.allmusic.server.event;
import com.coloryr.allmusic.server.core.objs.music.PlayerAddMusicObj;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.bus.api.Event;
public class MusicAddEvent extends Event {
/**
* 添加的音乐
*/
private final PlayerAddMusicObj music;
/**
* 添加者
*/
private final ServerPlayer player;
private boolean cancel;
public MusicAddEvent(PlayerAddMusicObj id, ServerPlayer player) {
this.music = id;
this.player = player;
}
public boolean isCancel() {
return cancel;
}
public void setCancel(boolean cancel) {
this.cancel = cancel;
}
public ServerPlayer getPlayer() {
return player;
}
public PlayerAddMusicObj getMusic() {
return music;
}
}
@@ -0,0 +1,25 @@
package com.coloryr.allmusic.server.event;
import com.coloryr.allmusic.server.core.objs.music.SongInfoObj;
import net.neoforged.bus.api.Event;
public class MusicPlayEvent extends Event {
private final SongInfoObj music;
private boolean cancel;
public MusicPlayEvent(SongInfoObj music) {
this.music = music;
}
public boolean isCancel() {
return cancel;
}
public void setCancel(boolean cancel) {
this.cancel = cancel;
}
public SongInfoObj getMusic() {
return music;
}
}
@@ -0,0 +1,36 @@
modLoader = "javafml"
loaderVersion = "[2,)"
license = "GPL-3.0"
issueTrackerURL = "https://github.com/Coloryr/AllMusic/issues"
[[mods]]
modId = "allmusic"
version = "${version}"
displayName = "AllMusic"
displayURL = "https://github.com/Coloryr/AllMusic/"
logoFile = "icon.png"
authors = "Color_yr"
description = '''AllMusic Mod'''
[[mods]]
modId = "allmusic_server"
version = "${version}"
displayName = "AllMusic Server"
displayURL = "https://github.com/Coloryr/AllMusic/"
logoFile = "icon.png"
authors = "Color_yr"
description = '''AllMusic Server Mod'''
[[dependencies.allmusic_server]]
modId = "neoforge"
type = "required"
versionRange = "[26.3.0.6-beta,)"
ordering = "NONE"
side = "BOTH"
[[dependencies.allmusic_server]]
modId = "minecraft"
type = "required"
versionRange = "[26.3,)"
ordering = "NONE"
side = "BOTH"
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@@ -482,7 +482,7 @@ public class PlayMusic {
*/
private static boolean checkDeep(MusicObj music) {
for (MusicObj obj : deep) {
if (Objects.equals(obj.id, music.id) && obj.api == music.api) {
if (Objects.equals(obj.id, music.id) && Objects.equals(obj.api, music.api)) {
return true;
}
}
+8
View File
@@ -14,6 +14,7 @@ include(":client:fabric_1_21_8")
include(":client:fabric_1_21_11")
include(":client:fabric_26_1")
include(":client:fabric_26_2")
include(":client:fabric_26_3")
include(":client:forge_1_7_10")
include(":client:forge_1_12_2")
@@ -27,6 +28,7 @@ include(":client:neoforge_1_21_6")
include(":client:neoforge_1_21_11")
include(":client:neoforge_26_1")
include(":client:neoforge_26_2")
include(":client:neoforge_26_3")
include(":server")
@@ -38,6 +40,7 @@ include(":server:fabric_1_21_6")
include(":server:fabric_1_21_11")
include(":server:fabric_26_1")
include(":server:fabric_26_2")
include(":server:fabric_26_3")
include(":server:forge_1_7_10")
include(":server:forge_1_12_2")
@@ -50,6 +53,7 @@ include(":server:neoforge_1_21_6")
include(":server:neoforge_1_21_11")
include(":server:neoforge_26_1")
include(":server:neoforge_26_2")
include(":server:neoforge_26_3")
include(":server:spigot")
include(":server:paper")
@@ -68,11 +72,15 @@ include(":onejar:fabric_1_21_6")
include(":onejar:fabric_1_21_11")
include(":onejar:fabric_26_1")
include(":onejar:fabric_26_2")
include(":onejar:fabric_26_3")
include(":onejar:neoforge_1_21")
include(":onejar:neoforge_1_21_4")
include(":onejar:neoforge_1_21_6")
include(":onejar:neoforge_1_21_11")
include(":onejar:neoforge_26_1")
include(":onejar:neoforge_26_2")
include(":onejar:neoforge_26_3")
pluginManagement {
repositories {