diff --git a/build.gradle.kts b/build.gradle.kts index 9a3d0dec..546b1714 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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 diff --git a/client/fabric_1_16_5/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java b/client/fabric_1_16_5/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java index 2d1f477d..3acfb136 100644 --- a/client/fabric_1_16_5/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java +++ b/client/fabric_1_16_5/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java @@ -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 diff --git a/client/fabric_1_20_1/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java b/client/fabric_1_20_1/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java index b40fd326..53f2ae8e 100644 --- a/client/fabric_1_20_1/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java +++ b/client/fabric_1_20_1/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java @@ -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 diff --git a/client/fabric_1_21/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java b/client/fabric_1_21/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java index 31dcb7d6..a2826149 100644 --- a/client/fabric_1_21/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java +++ b/client/fabric_1_21/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java @@ -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 diff --git a/client/fabric_1_21_3/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java b/client/fabric_1_21_3/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java index 8cd4f646..61ca28d8 100644 --- a/client/fabric_1_21_3/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java +++ b/client/fabric_1_21_3/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java @@ -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 diff --git a/client/fabric_1_21_4/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java b/client/fabric_1_21_4/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java index 8cd4f646..61ca28d8 100644 --- a/client/fabric_1_21_4/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java +++ b/client/fabric_1_21_4/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java @@ -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 diff --git a/client/fabric_1_21_6/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java b/client/fabric_1_21_6/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java index 5e9068cf..0dc948f7 100644 --- a/client/fabric_1_21_6/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java +++ b/client/fabric_1_21_6/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java @@ -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 diff --git a/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java index 5e9068cf..0dc948f7 100644 --- a/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java +++ b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java @@ -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 diff --git a/client/fabric_26_1/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java b/client/fabric_26_1/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java index c04442c0..1c9d88a2 100644 --- a/client/fabric_26_1/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java +++ b/client/fabric_26_1/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java @@ -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 diff --git a/client/fabric_26_2/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java b/client/fabric_26_2/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java index 3b22c550..cc5dd9bc 100644 --- a/client/fabric_26_2/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java +++ b/client/fabric_26_2/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java @@ -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 diff --git a/client/fabric_26_2/src/main/resources/fabric.mod.json b/client/fabric_26_2/src/main/resources/fabric.mod.json index 99bd6f13..16495270 100644 --- a/client/fabric_26_2/src/main/resources/fabric.mod.json +++ b/client/fabric_26_2/src/main/resources/fabric.mod.json @@ -37,7 +37,6 @@ "fabric-api": "*" }, "suggests": { - "modernui": "*", "voicechat_api": "*" } } \ No newline at end of file diff --git a/client/fabric_26_3/build.gradle.kts b/client/fabric_26_3/build.gradle.kts new file mode 100644 index 00000000..eb685e4a --- /dev/null +++ b/client/fabric_26_3/build.gradle.kts @@ -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) + } +} diff --git a/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java new file mode 100644 index 00000000..97073e42 --- /dev/null +++ b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java @@ -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()); + } +} diff --git a/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/CoreRenderTarget.java b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/CoreRenderTarget.java new file mode 100644 index 00000000..9124fb0d --- /dev/null +++ b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/CoreRenderTarget.java @@ -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 { + 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 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 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 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 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); + } +} \ No newline at end of file diff --git a/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/FloatRenderState.java b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/FloatRenderState.java new file mode 100644 index 00000000..d143b08b --- /dev/null +++ b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/FloatRenderState.java @@ -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()); + } +} diff --git a/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/MiniMessage.java b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/MiniMessage.java new file mode 100644 index 00000000..88129399 --- /dev/null +++ b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/MiniMessage.java @@ -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 COLOR_MAP = new HashMap<>(); + // 装饰器映射 + private static final Map 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 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; + } + + /** + * 处理颜色标签 + * 支持格式: , , + */ + private static void handleColorTag(StyleContext style, String params, String value) { + if (value != null) { + // 格式: 或 + TextColor textColor = parseColor(value); + if (textColor != null) { + style.color = textColor; + // 清除渐变、过渡和彩虹效果 + style.gradient = false; + style.transition = false; + style.rainbow = false; + } + } else if (params != null) { + // 格式: + 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) { + // 忽略解析错误 + } + } + } + } + } + + /** + * 处理阴影标签 + * 支持格式: , , , + */ + 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; + } + } + } + + /** + * 处理装饰标签 + * 支持格式: , , + */ + 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); + } + } + + /** + * 处理彩虹色标签 + * 支持格式: , , , + */ + 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; + } + + /** + * 处理渐变色标签 + * 支持格式: , + * 渐变是逐字符过渡 + */ + 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) { + } + } + } + } + + /** + * 处理过渡色标签 + * 支持格式: , + * 过渡是整个文本块的颜色过渡(类似整体渐变效果) + */ + 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) { + } + } + } + } + + /** + * 处理字体标签 + * 支持格式: , + */ + 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 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 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; + } + } +} \ No newline at end of file diff --git a/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/PicRender.java b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/PicRender.java new file mode 100644 index 00000000..fca17cc4 --- /dev/null +++ b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/PicRender.java @@ -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())); + } +} diff --git a/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/TexRender.java b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/TexRender.java new file mode 100644 index 00000000..5ee542f5 --- /dev/null +++ b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/TexRender.java @@ -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())); + } +} diff --git a/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/mixins/GuiShow.java b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/mixins/GuiShow.java new file mode 100644 index 00000000..7c1dcb83 --- /dev/null +++ b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/mixins/GuiShow.java @@ -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); + } +} diff --git a/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/mixins/QuitServer.java b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/mixins/QuitServer.java new file mode 100644 index 00000000..f77e15c4 --- /dev/null +++ b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/mixins/QuitServer.java @@ -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(); + } +} diff --git a/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/mixins/Register.java b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/mixins/Register.java new file mode 100644 index 00000000..0d9d49ea --- /dev/null +++ b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/mixins/Register.java @@ -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 = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/PacketProcessor;(Ljava/lang/Thread;)V")) + public void register(CallbackInfo info) { + AllMusicCore.renderInit(); + } +} diff --git a/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/mixins/SoundEvent.java b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/mixins/SoundEvent.java new file mode 100644 index 00000000..5bd2ce83 --- /dev/null +++ b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/mixins/SoundEvent.java @@ -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 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(); + } + } + } +} diff --git a/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/mixins/Tick.java b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/mixins/Tick.java new file mode 100644 index 00000000..ff2cc10c --- /dev/null +++ b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/mixins/Tick.java @@ -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(); + } +} diff --git a/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/others/VoiceChat.java b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/others/VoiceChat.java new file mode 100644 index 00000000..62eb1259 --- /dev/null +++ b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/client/others/VoiceChat.java @@ -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(); + } +} diff --git a/client/fabric_26_3/src/main/java/com/coloryr/allmusic/comm/AllMusicInit.java b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/comm/AllMusicInit.java new file mode 100644 index 00000000..8d0e96d4 --- /dev/null +++ b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/comm/AllMusicInit.java @@ -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); + } +} diff --git a/client/fabric_26_3/src/main/java/com/coloryr/allmusic/comm/MusicCodec.java b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/comm/MusicCodec.java new file mode 100644 index 00000000..6b0cee70 --- /dev/null +++ b/client/fabric_26_3/src/main/java/com/coloryr/allmusic/comm/MusicCodec.java @@ -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 ID = new Type<>(ChannelID); + public static final StreamCodec CODEC = + StreamCodec.of((buf, value) -> MusicPacketCodec.pack(buf, value.pack), + buf -> new MusicCodec(MusicPacketCodec.decode(buf))); + + @Override + public @NonNull Type type() { + return ID; + } +} diff --git a/client/fabric_26_3/src/main/resources/allmusic_client.mixins.json b/client/fabric_26_3/src/main/resources/allmusic_client.mixins.json new file mode 100644 index 00000000..2d2425a6 --- /dev/null +++ b/client/fabric_26_3/src/main/resources/allmusic_client.mixins.json @@ -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 + } +} \ No newline at end of file diff --git a/client/fabric_26_3/src/main/resources/assets/allmusic_client/icon.png b/client/fabric_26_3/src/main/resources/assets/allmusic_client/icon.png new file mode 100644 index 00000000..b2b5fa79 Binary files /dev/null and b/client/fabric_26_3/src/main/resources/assets/allmusic_client/icon.png differ diff --git a/client/fabric_26_3/src/main/resources/fabric.mod.json b/client/fabric_26_3/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..06b7c0a8 --- /dev/null +++ b/client/fabric_26_3/src/main/resources/fabric.mod.json @@ -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": "*" + } +} \ No newline at end of file diff --git a/client/forge_1_12_2/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java b/client/forge_1_12_2/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java index cc00281f..6b029639 100644 --- a/client/forge_1_12_2/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java +++ b/client/forge_1_12_2/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java @@ -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 diff --git a/client/forge_1_20_1/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java b/client/forge_1_20_1/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java index 100500c7..b9388a1d 100644 --- a/client/forge_1_20_1/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java +++ b/client/forge_1_20_1/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java @@ -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 diff --git a/client/forge_1_7_10/src/main/java/com/coloryr/allmusic/client/AllMusic.java b/client/forge_1_7_10/src/main/java/com/coloryr/allmusic/client/AllMusic.java index 273a568a..2b76b382 100644 --- a/client/forge_1_7_10/src/main/java/com/coloryr/allmusic/client/AllMusic.java +++ b/client/forge_1_7_10/src/main/java/com/coloryr/allmusic/client/AllMusic.java @@ -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 diff --git a/client/neoforge_1_21/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java b/client/neoforge_1_21/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java index bff92c27..ed56ac5c 100644 --- a/client/neoforge_1_21/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java +++ b/client/neoforge_1_21/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java @@ -93,7 +93,8 @@ public class AllMusicClient implements IPayloadHandler, 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 diff --git a/client/neoforge_1_21_11/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java b/client/neoforge_1_21_11/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java index 0f0b373c..6dac142f 100644 --- a/client/neoforge_1_21_11/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java +++ b/client/neoforge_1_21_11/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java @@ -134,7 +134,7 @@ public class AllMusicClient implements IPayloadHandler, AllMusicBrid } public float getVolume() { - return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS); + return Minecraft.getInstance().options.getFinalSoundSourceVolume(SoundSource.RECORDS); } @Override diff --git a/client/neoforge_1_21_3/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java b/client/neoforge_1_21_3/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java index e9e710f7..986c2888 100644 --- a/client/neoforge_1_21_3/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java +++ b/client/neoforge_1_21_3/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java @@ -92,7 +92,8 @@ public class AllMusicClient implements IPayloadHandler, 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 diff --git a/client/neoforge_1_21_4/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java b/client/neoforge_1_21_4/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java index e9e710f7..986c2888 100644 --- a/client/neoforge_1_21_4/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java +++ b/client/neoforge_1_21_4/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java @@ -92,7 +92,8 @@ public class AllMusicClient implements IPayloadHandler, 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 diff --git a/client/neoforge_1_21_6/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java b/client/neoforge_1_21_6/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java index d1d79080..311fc18c 100644 --- a/client/neoforge_1_21_6/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java +++ b/client/neoforge_1_21_6/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java @@ -128,7 +128,7 @@ public class AllMusicClient implements AllMusicBridge, IPayloadHandler, AllMusicBrid } public float getVolume() { - return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS); + return Minecraft.getInstance().options.getFinalSoundSourceVolume(SoundSource.RECORDS); } @Override diff --git a/client/neoforge_26_2/build.gradle.kts b/client/neoforge_26_2/build.gradle.kts index d9e38334..af44dd08 100644 --- a/client/neoforge_26_2/build.gradle.kts +++ b/client/neoforge_26_2/build.gradle.kts @@ -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") } diff --git a/client/neoforge_26_2/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java b/client/neoforge_26_2/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java index c2eed088..f864acbd 100644 --- a/client/neoforge_26_2/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java +++ b/client/neoforge_26_2/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java @@ -135,11 +135,11 @@ public class AllMusicClient implements IPayloadHandler, 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); } diff --git a/client/neoforge_26_3/build.gradle.kts b/client/neoforge_26_3/build.gradle.kts new file mode 100644 index 00000000..aa89b82c --- /dev/null +++ b/client/neoforge_26_3/build.gradle.kts @@ -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) + } +} diff --git a/client/neoforge_26_3/gradle.properties b/client/neoforge_26_3/gradle.properties new file mode 100644 index 00000000..7da18ea6 --- /dev/null +++ b/client/neoforge_26_3/gradle.properties @@ -0,0 +1 @@ +loom.platform=neoforge diff --git a/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java b/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java new file mode 100644 index 00000000..e8f1067d --- /dev/null +++ b/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java @@ -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, 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")); + } + } +} diff --git a/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/CoreRenderTarget.java b/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/CoreRenderTarget.java new file mode 100644 index 00000000..fe90072e --- /dev/null +++ b/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/CoreRenderTarget.java @@ -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 { + 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 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 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 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 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); + } +} \ No newline at end of file diff --git a/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/FloatRenderState.java b/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/FloatRenderState.java new file mode 100644 index 00000000..d143b08b --- /dev/null +++ b/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/FloatRenderState.java @@ -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()); + } +} diff --git a/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/MiniMessage.java b/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/MiniMessage.java new file mode 100644 index 00000000..88129399 --- /dev/null +++ b/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/MiniMessage.java @@ -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 COLOR_MAP = new HashMap<>(); + // 装饰器映射 + private static final Map 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 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; + } + + /** + * 处理颜色标签 + * 支持格式: , , + */ + private static void handleColorTag(StyleContext style, String params, String value) { + if (value != null) { + // 格式: 或 + TextColor textColor = parseColor(value); + if (textColor != null) { + style.color = textColor; + // 清除渐变、过渡和彩虹效果 + style.gradient = false; + style.transition = false; + style.rainbow = false; + } + } else if (params != null) { + // 格式: + 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) { + // 忽略解析错误 + } + } + } + } + } + + /** + * 处理阴影标签 + * 支持格式: , , , + */ + 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; + } + } + } + + /** + * 处理装饰标签 + * 支持格式: , , + */ + 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); + } + } + + /** + * 处理彩虹色标签 + * 支持格式: , , , + */ + 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; + } + + /** + * 处理渐变色标签 + * 支持格式: , + * 渐变是逐字符过渡 + */ + 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) { + } + } + } + } + + /** + * 处理过渡色标签 + * 支持格式: , + * 过渡是整个文本块的颜色过渡(类似整体渐变效果) + */ + 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) { + } + } + } + } + + /** + * 处理字体标签 + * 支持格式: , + */ + 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 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 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; + } + } +} \ No newline at end of file diff --git a/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/PicRender.java b/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/PicRender.java new file mode 100644 index 00000000..51fa9e23 --- /dev/null +++ b/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/PicRender.java @@ -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())); + } +} diff --git a/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/TexRender.java b/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/TexRender.java new file mode 100644 index 00000000..90c89199 --- /dev/null +++ b/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/TexRender.java @@ -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())); + } +} diff --git a/client/neoforge_26_2/src/main/java/com/coloryr/allmusic/client/others/ModernUI.java b/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/others/ModernUI.java similarity index 100% rename from client/neoforge_26_2/src/main/java/com/coloryr/allmusic/client/others/ModernUI.java rename to client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/others/ModernUI.java diff --git a/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/others/VoiceChat.java b/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/others/VoiceChat.java new file mode 100644 index 00000000..9caead22 --- /dev/null +++ b/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/client/others/VoiceChat.java @@ -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(); + } +} diff --git a/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/comm/AllMusicInit.java b/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/comm/AllMusicInit.java new file mode 100644 index 00000000..d9282960 --- /dev/null +++ b/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/comm/AllMusicInit.java @@ -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 { + + public static final String MODID = "allmusic"; + + public static IPayloadHandler 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); + } + } +} diff --git a/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/comm/MusicCodec.java b/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/comm/MusicCodec.java new file mode 100644 index 00000000..434fa510 --- /dev/null +++ b/client/neoforge_26_3/src/main/java/com/coloryr/allmusic/comm/MusicCodec.java @@ -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 TYPE = new Type<>(channel); + public static final StreamCodec CODEC = StreamCodec.of((buf, value) -> MusicPacketCodec.pack(buf, value.pack), + (buf) -> { + return new MusicCodec(MusicPacketCodec.decode(buf)); + }); + + @Override + public Type type() { + return TYPE; + } +} diff --git a/client/neoforge_26_3/src/main/resources/META-INF/neoforge.mods.toml b/client/neoforge_26_3/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 00000000..544e45e7 --- /dev/null +++ b/client/neoforge_26_3/src/main/resources/META-INF/neoforge.mods.toml @@ -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" \ No newline at end of file diff --git a/client/neoforge_26_3/src/main/resources/icon.png b/client/neoforge_26_3/src/main/resources/icon.png new file mode 100644 index 00000000..b2b5fa79 Binary files /dev/null and b/client/neoforge_26_3/src/main/resources/icon.png differ diff --git a/onejar/fabric_26_3/build.gradle.kts b/onejar/fabric_26_3/build.gradle.kts new file mode 100644 index 00000000..c109d33a --- /dev/null +++ b/onejar/fabric_26_3/build.gradle.kts @@ -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) + } +} diff --git a/onejar/fabric_26_3/src/main/resources/assets/allmusic/icon.png b/onejar/fabric_26_3/src/main/resources/assets/allmusic/icon.png new file mode 100644 index 00000000..b2b5fa79 Binary files /dev/null and b/onejar/fabric_26_3/src/main/resources/assets/allmusic/icon.png differ diff --git a/onejar/fabric_26_3/src/main/resources/fabric.mod.json b/onejar/fabric_26_3/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..6f009cfc --- /dev/null +++ b/onejar/fabric_26_3/src/main/resources/fabric.mod.json @@ -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": "*" + } +} diff --git a/onejar/neoforge_26_2/build.gradle.kts b/onejar/neoforge_26_2/build.gradle.kts new file mode 100644 index 00000000..ec10623e --- /dev/null +++ b/onejar/neoforge_26_2/build.gradle.kts @@ -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) + } +} diff --git a/onejar/neoforge_26_2/gradle.properties b/onejar/neoforge_26_2/gradle.properties new file mode 100644 index 00000000..7da18ea6 --- /dev/null +++ b/onejar/neoforge_26_2/gradle.properties @@ -0,0 +1 @@ +loom.platform=neoforge diff --git a/onejar/neoforge_26_2/src/main/resources/META-INF/neoforge.mods.toml b/onejar/neoforge_26_2/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 00000000..3aab326f --- /dev/null +++ b/onejar/neoforge_26_2/src/main/resources/META-INF/neoforge.mods.toml @@ -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" \ No newline at end of file diff --git a/onejar/neoforge_26_2/src/main/resources/icon.png b/onejar/neoforge_26_2/src/main/resources/icon.png new file mode 100644 index 00000000..b2b5fa79 Binary files /dev/null and b/onejar/neoforge_26_2/src/main/resources/icon.png differ diff --git a/onejar/neoforge_26_3/build.gradle.kts b/onejar/neoforge_26_3/build.gradle.kts new file mode 100644 index 00000000..ef978c25 --- /dev/null +++ b/onejar/neoforge_26_3/build.gradle.kts @@ -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) + } +} diff --git a/onejar/neoforge_26_3/gradle.properties b/onejar/neoforge_26_3/gradle.properties new file mode 100644 index 00000000..7da18ea6 --- /dev/null +++ b/onejar/neoforge_26_3/gradle.properties @@ -0,0 +1 @@ +loom.platform=neoforge diff --git a/onejar/neoforge_26_3/src/main/resources/META-INF/neoforge.mods.toml b/onejar/neoforge_26_3/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 00000000..e2e8e100 --- /dev/null +++ b/onejar/neoforge_26_3/src/main/resources/META-INF/neoforge.mods.toml @@ -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" \ No newline at end of file diff --git a/onejar/neoforge_26_3/src/main/resources/icon.png b/onejar/neoforge_26_3/src/main/resources/icon.png new file mode 100644 index 00000000..b2b5fa79 Binary files /dev/null and b/onejar/neoforge_26_3/src/main/resources/icon.png differ diff --git a/server/fabric_26_3/build.gradle.kts b/server/fabric_26_3/build.gradle.kts new file mode 100644 index 00000000..eca95f96 --- /dev/null +++ b/server/fabric_26_3/build.gradle.kts @@ -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) + } +} diff --git a/server/fabric_26_3/src/main/java/com/coloryr/allmusic/comm/AllMusicInit.java b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/comm/AllMusicInit.java new file mode 100644 index 00000000..8d0e96d4 --- /dev/null +++ b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/comm/AllMusicInit.java @@ -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); + } +} diff --git a/server/fabric_26_3/src/main/java/com/coloryr/allmusic/comm/MusicCodec.java b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/comm/MusicCodec.java new file mode 100644 index 00000000..6b0cee70 --- /dev/null +++ b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/comm/MusicCodec.java @@ -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 ID = new Type<>(ChannelID); + public static final StreamCodec CODEC = + StreamCodec.of((buf, value) -> MusicPacketCodec.pack(buf, value.pack), + buf -> new MusicCodec(MusicPacketCodec.decode(buf))); + + @Override + public @NonNull Type type() { + return ID; + } +} diff --git a/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/AllMusicServer.java b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/AllMusicServer.java new file mode 100644 index 00000000..bc0bf10c --- /dev/null +++ b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/AllMusicServer.java @@ -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(); + }); + } +} diff --git a/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/CommandFabric.java b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/CommandFabric.java new file mode 100644 index 00000000..5bf02199 --- /dev/null +++ b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/CommandFabric.java @@ -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, Predicate, SuggestionProvider { + public static CommandFabric instance = new CommandFabric(); + + public void register(CommandDispatcher 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 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 getSuggestions(CommandContext 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 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(); + } +} \ No newline at end of file diff --git a/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/LogFabric.java b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/LogFabric.java new file mode 100644 index 00000000..6b749f8e --- /dev/null +++ b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/LogFabric.java @@ -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); + } +} diff --git a/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/SideFabric.java b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/SideFabric.java new file mode 100644 index 00000000..c6192e39 --- /dev/null +++ b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/SideFabric.java @@ -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))); + } +} diff --git a/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/TaskItem.java b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/TaskItem.java new file mode 100644 index 00000000..40f9a5b9 --- /dev/null +++ b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/TaskItem.java @@ -0,0 +1,6 @@ +package com.coloryr.allmusic.server; + +public class TaskItem { + public int tick; + public Runnable run; +} diff --git a/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/Tasks.java b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/Tasks.java new file mode 100644 index 00000000..d4e72d39 --- /dev/null +++ b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/Tasks.java @@ -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 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); + } +} diff --git a/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/event/MusicAddEvent.java b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/event/MusicAddEvent.java new file mode 100644 index 00000000..6ef1835c --- /dev/null +++ b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/event/MusicAddEvent.java @@ -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 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); +} diff --git a/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/event/MusicPlayEvent.java b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/event/MusicPlayEvent.java new file mode 100644 index 00000000..852895f1 --- /dev/null +++ b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/event/MusicPlayEvent.java @@ -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 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); +} diff --git a/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/mixin/ServerPlayerMixin.java b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/mixin/ServerPlayerMixin.java new file mode 100644 index 00000000..60e133d4 --- /dev/null +++ b/server/fabric_26_3/src/main/java/com/coloryr/allmusic/server/mixin/ServerPlayerMixin.java @@ -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()); + } +} diff --git a/server/fabric_26_3/src/main/resources/allmusic_server.mixins.json b/server/fabric_26_3/src/main/resources/allmusic_server.mixins.json new file mode 100644 index 00000000..6e607dbc --- /dev/null +++ b/server/fabric_26_3/src/main/resources/allmusic_server.mixins.json @@ -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 + } +} diff --git a/server/fabric_26_3/src/main/resources/assets/allmusic_server/icon.png b/server/fabric_26_3/src/main/resources/assets/allmusic_server/icon.png new file mode 100644 index 00000000..b2b5fa79 Binary files /dev/null and b/server/fabric_26_3/src/main/resources/assets/allmusic_server/icon.png differ diff --git a/server/fabric_26_3/src/main/resources/fabric.mod.json b/server/fabric_26_3/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..b8127dee --- /dev/null +++ b/server/fabric_26_3/src/main/resources/fabric.mod.json @@ -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": { + } +} diff --git a/server/neoforge_26_3/build.gradle.kts b/server/neoforge_26_3/build.gradle.kts new file mode 100644 index 00000000..441c444f --- /dev/null +++ b/server/neoforge_26_3/build.gradle.kts @@ -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) + } +} diff --git a/server/neoforge_26_3/gradle.properties b/server/neoforge_26_3/gradle.properties new file mode 100644 index 00000000..7da18ea6 --- /dev/null +++ b/server/neoforge_26_3/gradle.properties @@ -0,0 +1 @@ +loom.platform=neoforge diff --git a/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/comm/AllMusicInit.java b/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/comm/AllMusicInit.java new file mode 100644 index 00000000..d9282960 --- /dev/null +++ b/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/comm/AllMusicInit.java @@ -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 { + + public static final String MODID = "allmusic"; + + public static IPayloadHandler 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); + } + } +} diff --git a/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/comm/MusicCodec.java b/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/comm/MusicCodec.java new file mode 100644 index 00000000..434fa510 --- /dev/null +++ b/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/comm/MusicCodec.java @@ -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 TYPE = new Type<>(channel); + public static final StreamCodec CODEC = StreamCodec.of((buf, value) -> MusicPacketCodec.pack(buf, value.pack), + (buf) -> { + return new MusicCodec(MusicPacketCodec.decode(buf)); + }); + + @Override + public Type type() { + return TYPE; + } +} diff --git a/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/AllMusicServer.java b/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/AllMusicServer.java new file mode 100644 index 00000000..2e1109f1 --- /dev/null +++ b/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/AllMusicServer.java @@ -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 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()); + } + } +} diff --git a/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/CommandNeoForge.java b/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/CommandNeoForge.java new file mode 100644 index 00000000..f6275e4e --- /dev/null +++ b/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/CommandNeoForge.java @@ -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, Predicate, SuggestionProvider { + public static CommandNeoForge instance = new CommandNeoForge(); + + public void register(CommandDispatcher 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 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 getSuggestions(CommandContext 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 results = CommandEX.getTabList(item, item.getTextName(), arg); + + for (String s : results) { + builder.suggest(s); + } + + return builder.buildFuture(); + } +} diff --git a/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/LogNeoForge.java b/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/LogNeoForge.java new file mode 100644 index 00000000..2b12188a --- /dev/null +++ b/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/LogNeoForge.java @@ -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); + } +} diff --git a/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/SideNeoForge.java b/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/SideNeoForge.java new file mode 100644 index 00000000..7e79134a --- /dev/null +++ b/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/SideNeoForge.java @@ -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))); + } +} diff --git a/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/TaskItem.java b/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/TaskItem.java new file mode 100644 index 00000000..40f9a5b9 --- /dev/null +++ b/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/TaskItem.java @@ -0,0 +1,6 @@ +package com.coloryr.allmusic.server; + +public class TaskItem { + public int tick; + public Runnable run; +} diff --git a/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/Tasks.java b/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/Tasks.java new file mode 100644 index 00000000..b861abcc --- /dev/null +++ b/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/Tasks.java @@ -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 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); + } +} \ No newline at end of file diff --git a/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/event/MusicAddEvent.java b/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/event/MusicAddEvent.java new file mode 100644 index 00000000..7c730137 --- /dev/null +++ b/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/event/MusicAddEvent.java @@ -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; + } +} diff --git a/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/event/MusicPlayEvent.java b/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/event/MusicPlayEvent.java new file mode 100644 index 00000000..b16e0f95 --- /dev/null +++ b/server/neoforge_26_3/src/main/java/com/coloryr/allmusic/server/event/MusicPlayEvent.java @@ -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; + } +} diff --git a/server/neoforge_26_3/src/main/resources/META-INF/neoforge.mods.toml b/server/neoforge_26_3/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 00000000..177695c3 --- /dev/null +++ b/server/neoforge_26_3/src/main/resources/META-INF/neoforge.mods.toml @@ -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" \ No newline at end of file diff --git a/server/neoforge_26_3/src/main/resources/icon.png b/server/neoforge_26_3/src/main/resources/icon.png new file mode 100644 index 00000000..b2b5fa79 Binary files /dev/null and b/server/neoforge_26_3/src/main/resources/icon.png differ diff --git a/server/src/main/java/com/coloryr/allmusic/server/core/music/PlayMusic.java b/server/src/main/java/com/coloryr/allmusic/server/core/music/PlayMusic.java index 5156a7d3..c7f211a1 100644 --- a/server/src/main/java/com/coloryr/allmusic/server/core/music/PlayMusic.java +++ b/server/src/main/java/com/coloryr/allmusic/server/core/music/PlayMusic.java @@ -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; } } diff --git a/settings.gradle.kts b/settings.gradle.kts index c1a4fd26..235ffed8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -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 {