26.3
No files matched your search
@@ -12,7 +12,7 @@ allprojects {
|
||||
apply(plugin = "com.gradleup.shadow")
|
||||
|
||||
group = "com.coloryr"
|
||||
version = "4.2.2"
|
||||
version = "4.2.3"
|
||||
|
||||
// 设置项目JDK版本
|
||||
java.sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
@@ -69,7 +69,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS)
|
||||
* Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.MASTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -64,7 +64,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS)
|
||||
* Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.MASTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -65,7 +65,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS)
|
||||
* Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.MASTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -65,7 +65,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS)
|
||||
* Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.MASTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -65,7 +65,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS)
|
||||
* Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.MASTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -64,7 +64,7 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
|
||||
return Minecraft.getInstance().options.getFinalSoundSourceVolume(SoundSource.RECORDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -64,7 +64,7 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
|
||||
return Minecraft.getInstance().options.getFinalSoundSourceVolume(SoundSource.RECORDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -66,7 +66,7 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
|
||||
return Minecraft.getInstance().options.getFinalSoundSourceVolume(SoundSource.RECORDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -65,7 +65,7 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
|
||||
return Minecraft.getInstance().options.getFinalSoundSourceVolume(SoundSource.RECORDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
"fabric-api": "*"
|
||||
},
|
||||
"suggests": {
|
||||
"modernui": "*",
|
||||
"voicechat_api": "*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
plugins {
|
||||
id("net.fabricmc.fabric-loom") version Versions.fabricLoom
|
||||
}
|
||||
|
||||
java.sourceCompatibility = JavaVersion.VERSION_25
|
||||
java.targetCompatibility = JavaVersion.VERSION_25
|
||||
|
||||
dependencies {
|
||||
minecraft("com.mojang:minecraft:26.3")
|
||||
implementation("net.fabricmc:fabric-loader:0.19.5")
|
||||
|
||||
implementation("net.fabricmc.fabric-api:fabric-api:0.161.0+26.3")
|
||||
|
||||
compileOnly("de.maxhenkel.voicechat:voicechat-api:2.6.0")
|
||||
}
|
||||
|
||||
tasks {
|
||||
processResources {
|
||||
filesMatching("fabric.mod.json") {
|
||||
expand(
|
||||
"version" to project.version
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
archiveFileName.set("[fabric-26.3]AllMusic_Client-${project.version}.jar")
|
||||
destinationDirectory.set(file("${parent!!.projectDir}/../build"))
|
||||
}
|
||||
|
||||
build {
|
||||
dependsOn(shadowJar)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.coloryr.allmusic.client;
|
||||
|
||||
import com.coloryr.allmusic.client.core.AllMusicBridge;
|
||||
import com.coloryr.allmusic.client.core.AllMusicCore;
|
||||
import com.coloryr.allmusic.client.core.render.PictureFrameBuffer;
|
||||
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
|
||||
import com.coloryr.allmusic.client.core.render.TextureRender;
|
||||
import com.coloryr.allmusic.comm.MusicCodec;
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.multiplayer.ClientPacketListener;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
|
||||
public static final String MODID = "allmusic_client";
|
||||
public static final Logger LOGGER = LogManager.getLogger("AllMusic Client");
|
||||
public static GuiGraphicsExtractor context;
|
||||
|
||||
public static void update(GuiGraphicsExtractor draw) {
|
||||
context = draw;
|
||||
AllMusicCore.hudUpdate();
|
||||
}
|
||||
|
||||
public int getScreenWidth() {
|
||||
return Minecraft.getInstance().getWindow().getGuiScaledWidth();
|
||||
}
|
||||
|
||||
public int getScreenHeight() {
|
||||
return Minecraft.getInstance().getWindow().getGuiScaledHeight();
|
||||
}
|
||||
|
||||
public int getTextWidth(String item) {
|
||||
return Minecraft.getInstance().font.width(item);
|
||||
}
|
||||
|
||||
public int getFontHeight() {
|
||||
return Minecraft.getInstance().font.lineHeight;
|
||||
}
|
||||
|
||||
public void sendMessage(String data) {
|
||||
data = "[AllMusic Client]" + data;
|
||||
LOGGER.warn(data);
|
||||
String finalData = data;
|
||||
Minecraft.getInstance().execute(() -> {
|
||||
if (Minecraft.getInstance().player == null)
|
||||
return;
|
||||
Minecraft.getInstance().player.sendSystemMessage(Component.literal(finalData));
|
||||
});
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getInstance().options.getFinalSoundSourceVolume(SoundSource.RECORDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopPlayMusic() {
|
||||
Minecraft.getInstance().getSoundManager().stop(null, SoundSource.MUSIC);
|
||||
Minecraft.getInstance().getSoundManager().stop(null, SoundSource.RECORDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextFrameBuffer makeTextRender(String name) {
|
||||
return new CoreRenderTarget(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextureRender makeTextureRender(String file) {
|
||||
return new TexRender(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PictureFrameBuffer makePictureRender(int size) {
|
||||
return new PicRender(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readText(String file) {
|
||||
try {
|
||||
var man = Minecraft.getInstance().getResourceManager();
|
||||
Resource resource = man.getResource(Identifier.fromNamespaceAndPath(MODID, file)).orElseThrow();
|
||||
try (InputStream inputStream = resource.open()) {
|
||||
byte[] bytes = inputStream.readAllBytes();
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream readFile(String file) {
|
||||
try {
|
||||
var man = Minecraft.getInstance().getResourceManager();
|
||||
Resource resource = man.getResource(Identifier.fromNamespaceAndPath(MODID, file)).orElseThrow();
|
||||
return resource.open();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void kick() {
|
||||
Minecraft client = Minecraft.getInstance();
|
||||
|
||||
ClientPacketListener packetListener = client.getConnection();
|
||||
if (packetListener != null) {
|
||||
Connection connection = packetListener.getConnection();
|
||||
connection.disconnect(Component.nullToEmpty("Old AllMusic server"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
ClientPlayNetworking.registerGlobalReceiver(MusicCodec.ID, (pack, handler) -> AllMusicCore.packDo(pack.pack()));
|
||||
|
||||
AllMusicCore.init(FabricLoader.getInstance().getConfigDir(), this);
|
||||
|
||||
ClientLifecycleEvents.CLIENT_STOPPING.register((a) -> AllMusicCore.stop());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package com.coloryr.allmusic.client;
|
||||
|
||||
import com.coloryr.allmusic.client.core.AllMusicHud;
|
||||
import com.coloryr.allmusic.client.core.Point2f;
|
||||
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
|
||||
import com.coloryr.allmusic.codec.HudPosType;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CoreRenderTarget extends TextFrameBuffer<Component> {
|
||||
private final boolean isState;
|
||||
|
||||
public CoreRenderTarget(String name) {
|
||||
isState = name.equals("state");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putText(String text, int y, int color, boolean shadow) {
|
||||
color = (color & 0x00FFFFFF) | 0xFF000000;
|
||||
Component component = MiniMessage.parse(text);
|
||||
Font font = Minecraft.getInstance().font;
|
||||
int width = font.width(component);
|
||||
if (width == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int height = font.lineHeight + (shadow ? 1 : 0);
|
||||
if (isState) {
|
||||
y = 0;
|
||||
}
|
||||
texts.add(new TextItem<>(width, height, y, component, shadow, color));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
|
||||
if (texts.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
GuiGraphicsExtractor gui = AllMusicClient.context;
|
||||
if (gui == null) return;
|
||||
Font font = Minecraft.getInstance().font;
|
||||
|
||||
for (TextItem<Component> entry : texts) {
|
||||
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
|
||||
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
|
||||
|
||||
int drawX = (int) point.x;
|
||||
int drawY = (int) (point.y + entry.y);
|
||||
int finalColor = applyAlpha(entry.color, alpha);
|
||||
|
||||
if (maxWidth != -1 && entry.width > maxWidth) {
|
||||
int scrollOffset = (int) getOffset(entry, maxWidth);
|
||||
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
|
||||
gui.text(font, entry.component, drawX - scrollOffset, drawY, finalColor, entry.shadow);
|
||||
if (scrollOffset > 0) {
|
||||
gui.text(font, entry.component, drawX - scrollOffset + entry.width, drawY, finalColor, entry.shadow);
|
||||
}
|
||||
gui.disableScissor();
|
||||
} else {
|
||||
gui.text(font, entry.component, drawX, drawY, finalColor, entry.shadow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawLine(float x, float y, float alpha, int line) {
|
||||
if (texts.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (line >= texts.size()) {
|
||||
return;
|
||||
}
|
||||
GuiGraphicsExtractor gui = AllMusicClient.context;
|
||||
if (gui == null) return;
|
||||
TextItem<Component> entry = texts.get(line);
|
||||
gui.text(Minecraft.getInstance().font, entry.component,
|
||||
(int) x, (int) (y + entry.y),
|
||||
applyAlpha(entry.color, alpha), entry.shadow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
|
||||
if (texts.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
GuiGraphicsExtractor gui = AllMusicClient.context;
|
||||
if (gui == null) return;
|
||||
Font font = Minecraft.getInstance().font;
|
||||
|
||||
for (TextItem<Component> entry : texts) {
|
||||
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
|
||||
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
|
||||
|
||||
int drawX = (int) point.x;
|
||||
int drawY = (int) (point.y + entry.y);
|
||||
int finalColor = applyAlpha(entry.color, alpha);
|
||||
|
||||
if (maxWidth != -1 && entry.width > maxWidth) {
|
||||
float maxOffset = entry.width - maxWidth;
|
||||
float texOffset = maxOffset * state;
|
||||
int revealWidth = (int) (maxWidth * state);
|
||||
|
||||
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
|
||||
gui.text(font, entry.component, drawX - (int) texOffset, drawY, finalColor, entry.shadow);
|
||||
gui.disableScissor();
|
||||
} else {
|
||||
int revealWidth = (int) (entry.width * state);
|
||||
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
|
||||
gui.text(font, entry.component, drawX, drawY, finalColor, entry.shadow);
|
||||
gui.disableScissor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2f getLine(int line) {
|
||||
if (line >= texts.size()) {
|
||||
return new Point2f(0, 0);
|
||||
}
|
||||
TextItem<Component> entry = texts.get(line);
|
||||
return new Point2f(entry.width, entry.height);
|
||||
}
|
||||
|
||||
private static int applyAlpha(int color, float alpha) {
|
||||
int a = (int) (alpha * 255);
|
||||
if (a < 0) a = 0;
|
||||
if (a > 255) a = 255;
|
||||
return (color & 0x00FFFFFF) | (a << 24);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.coloryr.allmusic.client;
|
||||
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import com.mojang.renderpearl.api.pipeline.RenderPipeline;
|
||||
import net.minecraft.client.gui.navigation.ScreenRectangle;
|
||||
import net.minecraft.client.gui.render.TextureSetup;
|
||||
import net.minecraft.client.renderer.state.gui.GuiElementRenderState;
|
||||
import org.joml.Matrix3x2f;
|
||||
|
||||
public record FloatRenderState(RenderPipeline pipeline, TextureSetup textureSetup, Matrix3x2f pose, float x0,
|
||||
float y0, float x1, float y1, float u0, float u1, float v0, float v1, int color,
|
||||
ScreenRectangle scissorArea,
|
||||
ScreenRectangle bounds) implements GuiElementRenderState {
|
||||
public FloatRenderState(RenderPipeline renderPipeline, TextureSetup textureSetup, Matrix3x2f matrix3x2f,
|
||||
float i, float j, float k, float l, float f, float g, float h, float m, int n, ScreenRectangle screenRectangle) {
|
||||
this(renderPipeline, textureSetup, matrix3x2f, i, j, k, l, f, g, h, m, n, screenRectangle, getBounds(i, j, k, l, matrix3x2f, screenRectangle));
|
||||
}
|
||||
|
||||
private static ScreenRectangle getBounds(float i, float j, float k, float l, Matrix3x2f matrix3x2f, ScreenRectangle screenRectangle) {
|
||||
ScreenRectangle screenRectangle2 = (new ScreenRectangle((int) Math.ceil(i + 1), (int) Math.ceil(j + 1), (int) Math.ceil(k - i + 1), (int) Math.ceil(l - j + 1))).transformMaxBounds(matrix3x2f);
|
||||
return screenRectangle != null ? screenRectangle.intersection(screenRectangle2) : screenRectangle2;
|
||||
}
|
||||
|
||||
public void buildVertices(VertexConsumer vertexConsumer) {
|
||||
vertexConsumer.addVertexWith2DPose(pose(), x0(), y0()).setUv(u0(), v0()).setColor(color());
|
||||
vertexConsumer.addVertexWith2DPose(pose(), x0(), y1()).setUv(u0(), v1()).setColor(color());
|
||||
vertexConsumer.addVertexWith2DPose(pose(), x1(), y1()).setUv(u1(), v1()).setColor(color());
|
||||
vertexConsumer.addVertexWith2DPose(pose(), x1(), y0()).setUv(u1(), v0()).setColor(color());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,645 @@
|
||||
package com.coloryr.allmusic.client;
|
||||
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.*;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class MiniMessage {
|
||||
// 正则表达式匹配标签
|
||||
private static final Pattern TAG_PATTERN = Pattern.compile(
|
||||
"<(/?)(\\w+)(?:\\(([^)]+)\\))?(?::([^>]+))?>"
|
||||
);
|
||||
|
||||
// 颜色映射表
|
||||
private static final Map<String, ChatFormatting> COLOR_MAP = new HashMap<>();
|
||||
// 装饰器映射
|
||||
private static final Map<String, ChatFormatting> DECORATION_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
COLOR_MAP.put("black", ChatFormatting.BLACK);
|
||||
COLOR_MAP.put("dark_blue", ChatFormatting.DARK_BLUE);
|
||||
COLOR_MAP.put("dark_green", ChatFormatting.DARK_GREEN);
|
||||
COLOR_MAP.put("dark_aqua", ChatFormatting.DARK_AQUA);
|
||||
COLOR_MAP.put("dark_red", ChatFormatting.DARK_RED);
|
||||
COLOR_MAP.put("dark_purple", ChatFormatting.DARK_PURPLE);
|
||||
COLOR_MAP.put("gold", ChatFormatting.GOLD);
|
||||
COLOR_MAP.put("gray", ChatFormatting.GRAY);
|
||||
COLOR_MAP.put("dark_gray", ChatFormatting.DARK_GRAY);
|
||||
COLOR_MAP.put("blue", ChatFormatting.BLUE);
|
||||
COLOR_MAP.put("green", ChatFormatting.GREEN);
|
||||
COLOR_MAP.put("aqua", ChatFormatting.AQUA);
|
||||
COLOR_MAP.put("red", ChatFormatting.RED);
|
||||
COLOR_MAP.put("light_purple", ChatFormatting.LIGHT_PURPLE);
|
||||
COLOR_MAP.put("yellow", ChatFormatting.YELLOW);
|
||||
COLOR_MAP.put("white", ChatFormatting.WHITE);
|
||||
}
|
||||
|
||||
static {
|
||||
DECORATION_MAP.put("bold", ChatFormatting.BOLD);
|
||||
DECORATION_MAP.put("italic", ChatFormatting.ITALIC);
|
||||
DECORATION_MAP.put("underlined", ChatFormatting.UNDERLINE);
|
||||
DECORATION_MAP.put("strikethrough", ChatFormatting.STRIKETHROUGH);
|
||||
DECORATION_MAP.put("obfuscated", ChatFormatting.OBFUSCATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 MiniMessage 字符串为 Component
|
||||
*/
|
||||
public static Component parse(String input) {
|
||||
MutableComponent root = Component.literal("");
|
||||
Stack<StyleContext> styleStack = new Stack<>();
|
||||
StyleContext currentStyle = new StyleContext();
|
||||
styleStack.push(currentStyle);
|
||||
|
||||
StringBuilder textBuffer = new StringBuilder();
|
||||
int pos = 0;
|
||||
int length = input.length();
|
||||
|
||||
while (pos < length) {
|
||||
char c = input.charAt(pos);
|
||||
|
||||
if (c == '<') {
|
||||
// 先处理已积累的文本
|
||||
if (textBuffer.length() > 0) {
|
||||
appendText(root, textBuffer.toString(), currentStyle);
|
||||
textBuffer.setLength(0);
|
||||
}
|
||||
|
||||
// 查找标签结束位置
|
||||
int endTag = findClosingTag(input, pos);
|
||||
if (endTag == -1) {
|
||||
// 没有找到闭合标签,当作普通文本处理
|
||||
textBuffer.append(c);
|
||||
pos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
String tagContent = input.substring(pos, endTag + 1);
|
||||
Matcher matcher = TAG_PATTERN.matcher(tagContent);
|
||||
|
||||
if (matcher.matches()) {
|
||||
boolean isClosing = !matcher.group(1).isEmpty();
|
||||
String tagName = matcher.group(2).toLowerCase(Locale.ROOT);
|
||||
String params = matcher.group(3);
|
||||
String value = matcher.group(4);
|
||||
|
||||
if (isClosing) {
|
||||
// 关闭标签 - 只关闭对应的开始标签
|
||||
String expectedOpenTag = matcher.group(2).toLowerCase(Locale.ROOT);
|
||||
|
||||
// 查找对应的开始标签并关闭
|
||||
if (styleStack.size() > 1) {
|
||||
// 从栈顶向下查找匹配的开始标签
|
||||
int index = styleStack.size() - 1;
|
||||
boolean found = false;
|
||||
|
||||
while (index >= 0) {
|
||||
StyleContext ctx = styleStack.get(index);
|
||||
if (ctx.tagName != null && ctx.tagName.equals(expectedOpenTag)) {
|
||||
// 找到匹配的开始标签,关闭到这一层
|
||||
while (styleStack.size() > index + 1) {
|
||||
styleStack.pop();
|
||||
}
|
||||
currentStyle = styleStack.peek();
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
index--;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
// 没找到匹配的标签,忽略此关闭标签
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 处理各种标签
|
||||
StyleContext newStyle = currentStyle.copy();
|
||||
|
||||
switch (tagName) {
|
||||
case "color":
|
||||
handleColorTag(newStyle, params, value);
|
||||
break;
|
||||
case "shadow":
|
||||
handleShadowTag(newStyle, params, value);
|
||||
break;
|
||||
case "decoration":
|
||||
handleDecorationTag(newStyle, params, value);
|
||||
break;
|
||||
case "reset":
|
||||
newStyle = new StyleContext();
|
||||
break;
|
||||
case "rainbow":
|
||||
handleRainbowTag(newStyle, params, value);
|
||||
break;
|
||||
case "gradient":
|
||||
handleGradientTag(newStyle, params, value);
|
||||
break;
|
||||
case "transition":
|
||||
handleTransitionTag(newStyle, params, value);
|
||||
break;
|
||||
case "font":
|
||||
handleFontTag(newStyle, params, value);
|
||||
break;
|
||||
default:
|
||||
// 未知标签,忽略
|
||||
break;
|
||||
}
|
||||
|
||||
// 记录标签名称以便关闭时匹配
|
||||
newStyle.tagName = tagName;
|
||||
styleStack.push(newStyle);
|
||||
currentStyle = newStyle;
|
||||
}
|
||||
} else {
|
||||
// 无效标签,当作普通文本
|
||||
textBuffer.append(tagContent);
|
||||
}
|
||||
|
||||
pos = endTag + 1;
|
||||
} else {
|
||||
textBuffer.append(c);
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理剩余的文本
|
||||
if (textBuffer.length() > 0) {
|
||||
appendText(root, textBuffer.toString(), currentStyle);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理颜色标签
|
||||
* 支持格式: <color:red>, <color:#FF0000>, <color(rgb):255,0,0>
|
||||
*/
|
||||
private static void handleColorTag(StyleContext style, String params, String value) {
|
||||
if (value != null) {
|
||||
// 格式: <color:red> 或 <color:#FF0000>
|
||||
TextColor textColor = parseColor(value);
|
||||
if (textColor != null) {
|
||||
style.color = textColor;
|
||||
// 清除渐变、过渡和彩虹效果
|
||||
style.gradient = false;
|
||||
style.transition = false;
|
||||
style.rainbow = false;
|
||||
}
|
||||
} else if (params != null) {
|
||||
// 格式: <color(rgb):255,0,0>
|
||||
if (params.equalsIgnoreCase("rgb")) {
|
||||
String[] parts = value.split(",");
|
||||
if (parts.length == 3) {
|
||||
try {
|
||||
int r = Integer.parseInt(parts[0].trim());
|
||||
int g = Integer.parseInt(parts[1].trim());
|
||||
int b = Integer.parseInt(parts[2].trim());
|
||||
style.color = TextColor.fromRgb((r << 16) | (g << 8) | b);
|
||||
// 清除渐变、过渡和彩虹效果
|
||||
style.gradient = false;
|
||||
style.transition = false;
|
||||
style.rainbow = false;
|
||||
} catch (NumberFormatException e) {
|
||||
// 忽略解析错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理阴影标签
|
||||
* 支持格式: <shadow>, <shadow:true>, <shadow:false>, <shadow:#FF0000>
|
||||
*/
|
||||
private static void handleShadowTag(StyleContext style, String params, String value) {
|
||||
if (value == null) {
|
||||
style.shadow = true;
|
||||
} else if (value.equalsIgnoreCase("true")) {
|
||||
style.shadow = true;
|
||||
} else if (value.equalsIgnoreCase("false")) {
|
||||
style.shadow = false;
|
||||
} else {
|
||||
// 自定义阴影颜色
|
||||
TextColor shadowColor = parseColor(value);
|
||||
if (shadowColor != null) {
|
||||
style.shadow = true;
|
||||
style.shadowColor = shadowColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理装饰标签
|
||||
* 支持格式: <decoration:bold>, <decoration:bold:true>, <decoration:bold:false>
|
||||
*/
|
||||
private static void handleDecorationTag(StyleContext style, String params, String value) {
|
||||
if (params == null) return;
|
||||
|
||||
String[] parts = params.split(":");
|
||||
String decorationName = parts[0].toLowerCase(Locale.ROOT);
|
||||
boolean enabled = true;
|
||||
|
||||
if (parts.length > 1) {
|
||||
enabled = Boolean.parseBoolean(parts[1]);
|
||||
} else if (value != null) {
|
||||
enabled = Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
ChatFormatting formatting = DECORATION_MAP.get(decorationName);
|
||||
if (formatting != null) {
|
||||
style.decorations.put(formatting, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理彩虹色标签
|
||||
* 支持格式: <rainbow>, <rainbow:!>, <rainbow(saturation):0.8>, <rainbow(brightness):0.5>
|
||||
*/
|
||||
private static void handleRainbowTag(StyleContext style, String params, String value) {
|
||||
float saturation = 1.0f;
|
||||
float brightness = 1.0f;
|
||||
|
||||
if (params != null) {
|
||||
if (params.equalsIgnoreCase("saturation") && value != null) {
|
||||
try {
|
||||
saturation = Float.parseFloat(value);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
} else if (params.equalsIgnoreCase("brightness") && value != null) {
|
||||
try {
|
||||
brightness = Float.parseFloat(value);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
style.rainbow = true;
|
||||
style.gradient = false;
|
||||
style.transition = false;
|
||||
style.rainbowSaturation = saturation;
|
||||
style.rainbowBrightness = brightness;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理渐变色标签
|
||||
* 支持格式: <gradient:#FF0000:#00FF00>, <gradient(rgb):255,0,0:0,255,0>
|
||||
* 渐变是逐字符过渡
|
||||
*/
|
||||
private static void handleGradientTag(StyleContext style, String params, String value) {
|
||||
if (value == null) return;
|
||||
|
||||
String[] colors = value.split(":");
|
||||
if (colors.length == 2) {
|
||||
TextColor start = parseColor(colors[0]);
|
||||
TextColor end = parseColor(colors[1]);
|
||||
|
||||
if (start != null && end != null) {
|
||||
style.gradient = true;
|
||||
style.transition = false;
|
||||
style.rainbow = false;
|
||||
style.gradientStart = start;
|
||||
style.gradientEnd = end;
|
||||
}
|
||||
} else if (params != null && params.equalsIgnoreCase("rgb")) {
|
||||
// 处理 RGB 格式
|
||||
String[] color1 = colors[0].split(",");
|
||||
String[] color2 = colors[1].split(",");
|
||||
if (color1.length == 3 && color2.length == 3) {
|
||||
try {
|
||||
int r1 = Integer.parseInt(color1[0].trim());
|
||||
int g1 = Integer.parseInt(color1[1].trim());
|
||||
int b1 = Integer.parseInt(color1[2].trim());
|
||||
int r2 = Integer.parseInt(color2[0].trim());
|
||||
int g2 = Integer.parseInt(color2[1].trim());
|
||||
int b2 = Integer.parseInt(color2[2].trim());
|
||||
|
||||
style.gradient = true;
|
||||
style.transition = false;
|
||||
style.rainbow = false;
|
||||
style.gradientStart = TextColor.fromRgb((r1 << 16) | (g1 << 8) | b1);
|
||||
style.gradientEnd = TextColor.fromRgb((r2 << 16) | (g2 << 8) | b2);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理过渡色标签
|
||||
* 支持格式: <transition:#FF0000:#00FF00>, <transition(rgb):255,0,0:0,255,0>
|
||||
* 过渡是整个文本块的颜色过渡(类似整体渐变效果)
|
||||
*/
|
||||
private static void handleTransitionTag(StyleContext style, String params, String value) {
|
||||
if (value == null) return;
|
||||
|
||||
String[] colors = value.split(":");
|
||||
if (colors.length == 2) {
|
||||
TextColor start = parseColor(colors[0]);
|
||||
TextColor end = parseColor(colors[1]);
|
||||
|
||||
if (start != null && end != null) {
|
||||
style.transition = true;
|
||||
style.gradient = false;
|
||||
style.rainbow = false;
|
||||
style.transitionStart = start;
|
||||
style.transitionEnd = end;
|
||||
}
|
||||
} else if (params != null && params.equalsIgnoreCase("rgb")) {
|
||||
// 处理 RGB 格式
|
||||
String[] color1 = colors[0].split(",");
|
||||
String[] color2 = colors[1].split(",");
|
||||
if (color1.length == 3 && color2.length == 3) {
|
||||
try {
|
||||
int r1 = Integer.parseInt(color1[0].trim());
|
||||
int g1 = Integer.parseInt(color1[1].trim());
|
||||
int b1 = Integer.parseInt(color1[2].trim());
|
||||
int r2 = Integer.parseInt(color2[0].trim());
|
||||
int g2 = Integer.parseInt(color2[1].trim());
|
||||
int b2 = Integer.parseInt(color2[2].trim());
|
||||
|
||||
style.transition = true;
|
||||
style.gradient = false;
|
||||
style.rainbow = false;
|
||||
style.transitionStart = TextColor.fromRgb((r1 << 16) | (g1 << 8) | b1);
|
||||
style.transitionEnd = TextColor.fromRgb((r2 << 16) | (g2 << 8) | b2);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理字体标签
|
||||
* 支持格式: <font:minecraft:default>, <font:minecraft:uniform>
|
||||
*/
|
||||
private static void handleFontTag(StyleContext style, String params, String value) {
|
||||
String fontName = value != null ? value : params;
|
||||
if (fontName != null) {
|
||||
style.font = fontName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析颜色字符串
|
||||
* 支持: 颜色名称, #RRGGBB, #RGB, 十六进制数值
|
||||
*/
|
||||
private static TextColor parseColor(String colorStr) {
|
||||
if (colorStr == null) return null;
|
||||
|
||||
// 检查是否为预定义颜色名称
|
||||
ChatFormatting formatting = COLOR_MAP.get(colorStr.toLowerCase(Locale.ROOT));
|
||||
if (formatting != null) {
|
||||
return TextColor.fromLegacyFormat(formatting);
|
||||
}
|
||||
|
||||
// 检查十六进制格式
|
||||
if (colorStr.startsWith("#")) {
|
||||
try {
|
||||
if (colorStr.length() == 7) {
|
||||
return TextColor.parseColor(colorStr).getOrThrow();
|
||||
} else if (colorStr.length() == 4) {
|
||||
// 扩展 #RGB 到 #RRGGBB
|
||||
String expanded = "#" +
|
||||
colorStr.charAt(1) + colorStr.charAt(1) +
|
||||
colorStr.charAt(2) + colorStr.charAt(2) +
|
||||
colorStr.charAt(3) + colorStr.charAt(3);
|
||||
return TextColor.parseColor(expanded).getOrThrow();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 忽略解析错误
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找标签闭合位置
|
||||
*/
|
||||
private static int findClosingTag(String input, int start) {
|
||||
int pos = start + 1;
|
||||
while (pos < input.length()) {
|
||||
char c = input.charAt(pos);
|
||||
if (c == '>') {
|
||||
return pos;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加文本到组件
|
||||
*/
|
||||
private static void appendText(MutableComponent root, String text, StyleContext styleContext) {
|
||||
if (text.isEmpty()) return;
|
||||
|
||||
// 如果有渐变效果,需要逐字符处理
|
||||
if (styleContext.gradient && styleContext.gradientStart != null && styleContext.gradientEnd != null) {
|
||||
appendGradientText(root, text, styleContext);
|
||||
}
|
||||
// 如果有过渡效果,需要逐字符处理
|
||||
else if (styleContext.transition && styleContext.transitionStart != null && styleContext.transitionEnd != null) {
|
||||
appendTransitionText(root, text, styleContext);
|
||||
}
|
||||
// 如果有彩虹效果,需要逐字符处理
|
||||
else if (styleContext.rainbow) {
|
||||
appendRainbowText(root, text, styleContext);
|
||||
}
|
||||
// 普通文本
|
||||
else {
|
||||
MutableComponent component = Component.literal(text);
|
||||
component.setStyle(styleContext.buildStyle());
|
||||
root.append(component);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 带渐变效果的文本追加(逐字符颜色变化)
|
||||
*/
|
||||
private static void appendGradientText(MutableComponent root, String text, StyleContext styleContext) {
|
||||
int length = text.length();
|
||||
Style baseStyle = styleContext.buildBaseStyle();
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = text.charAt(i);
|
||||
float ratio = (float) i / (length - 1);
|
||||
TextColor gradientColor = interpolateColor(styleContext.gradientStart, styleContext.gradientEnd, ratio);
|
||||
|
||||
Style charStyle = baseStyle.withColor(gradientColor);
|
||||
// 重新应用阴影
|
||||
if (styleContext.shadow != null && styleContext.shadow) {
|
||||
if (styleContext.shadowColor != null) {
|
||||
charStyle = charStyle.withShadowColor(styleContext.shadowColor.getValue() | 0xFF000000);
|
||||
} else {
|
||||
charStyle = charStyle.withShadowColor(0xFF000000);
|
||||
}
|
||||
}
|
||||
|
||||
root.append(Component.literal(String.valueOf(c)).withStyle(charStyle));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 带过渡效果的文本追加(整体颜色过渡)
|
||||
*/
|
||||
private static void appendTransitionText(MutableComponent root, String text, StyleContext styleContext) {
|
||||
Style baseStyle = styleContext.buildBaseStyle();
|
||||
|
||||
// 过渡效果:为整个文本块创建一个颜色过渡
|
||||
MutableComponent transitionComponent = Component.literal(text);
|
||||
|
||||
// 应用过渡样式(这里使用颜色插值,但也可以根据需求调整)
|
||||
if (styleContext.transitionStart != null && styleContext.transitionEnd != null) {
|
||||
// 可以使用整体渐变或者固定起始色
|
||||
transitionComponent.setStyle(baseStyle.withColor(styleContext.transitionStart));
|
||||
} else {
|
||||
transitionComponent.setStyle(baseStyle);
|
||||
}
|
||||
|
||||
root.append(transitionComponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 带彩虹效果的文本追加
|
||||
*/
|
||||
private static void appendRainbowText(MutableComponent root, String text, StyleContext styleContext) {
|
||||
int length = text.length();
|
||||
Style baseStyle = styleContext.buildBaseStyle();
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = text.charAt(i);
|
||||
float hue = (float) i / length;
|
||||
int rgb = java.awt.Color.HSBtoRGB(hue, styleContext.rainbowSaturation, styleContext.rainbowBrightness);
|
||||
TextColor rainbowColor = TextColor.fromRgb(rgb & 0xFFFFFF);
|
||||
|
||||
Style charStyle = baseStyle.withColor(rainbowColor);
|
||||
// 重新应用阴影
|
||||
if (styleContext.shadow != null && styleContext.shadow) {
|
||||
if (styleContext.shadowColor != null) {
|
||||
charStyle = charStyle.withShadowColor(styleContext.shadowColor.getValue() | 0xFF000000);
|
||||
} else {
|
||||
charStyle = charStyle.withShadowColor(0xFF000000);
|
||||
}
|
||||
}
|
||||
|
||||
root.append(Component.literal(String.valueOf(c)).withStyle(charStyle));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 颜色插值
|
||||
*/
|
||||
private static TextColor interpolateColor(TextColor start, TextColor end, float ratio) {
|
||||
int startRgb = start.getValue();
|
||||
int endRgb = end.getValue();
|
||||
|
||||
int r1 = (startRgb >> 16) & 0xFF;
|
||||
int g1 = (startRgb >> 8) & 0xFF;
|
||||
int b1 = startRgb & 0xFF;
|
||||
|
||||
int r2 = (endRgb >> 16) & 0xFF;
|
||||
int g2 = (endRgb >> 8) & 0xFF;
|
||||
int b2 = endRgb & 0xFF;
|
||||
|
||||
int r = (int) (r1 + (r2 - r1) * ratio);
|
||||
int g = (int) (g1 + (g2 - g1) * ratio);
|
||||
int b = (int) (b1 + (b2 - b1) * ratio);
|
||||
|
||||
return TextColor.fromRgb((r << 16) | (g << 8) | b);
|
||||
}
|
||||
|
||||
/**
|
||||
* 样式上下文类,用于追踪嵌套样式
|
||||
*/
|
||||
private static class StyleContext {
|
||||
String tagName = null; // 记录标签名称,用于关闭匹配
|
||||
TextColor color = null;
|
||||
Boolean shadow = null;
|
||||
TextColor shadowColor = null;
|
||||
Map<ChatFormatting, Boolean> decorations = new HashMap<>();
|
||||
boolean rainbow = false;
|
||||
float rainbowSaturation = 1.0f;
|
||||
float rainbowBrightness = 1.0f;
|
||||
boolean gradient = false;
|
||||
TextColor gradientStart = null;
|
||||
TextColor gradientEnd = null;
|
||||
boolean transition = false;
|
||||
TextColor transitionStart = null;
|
||||
TextColor transitionEnd = null;
|
||||
String font = null;
|
||||
|
||||
StyleContext copy() {
|
||||
StyleContext copy = new StyleContext();
|
||||
copy.tagName = this.tagName;
|
||||
copy.color = this.color;
|
||||
copy.shadow = this.shadow;
|
||||
copy.shadowColor = this.shadowColor;
|
||||
copy.decorations.putAll(this.decorations);
|
||||
copy.rainbow = this.rainbow;
|
||||
copy.rainbowSaturation = this.rainbowSaturation;
|
||||
copy.rainbowBrightness = this.rainbowBrightness;
|
||||
copy.gradient = this.gradient;
|
||||
copy.gradientStart = this.gradientStart;
|
||||
copy.gradientEnd = this.gradientEnd;
|
||||
copy.transition = this.transition;
|
||||
copy.transitionStart = this.transitionStart;
|
||||
copy.transitionEnd = this.transitionEnd;
|
||||
copy.font = this.font;
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建基础样式(不含颜色)
|
||||
*/
|
||||
Style buildBaseStyle() {
|
||||
Style style = Style.EMPTY;
|
||||
|
||||
// 应用装饰器
|
||||
for (Map.Entry<ChatFormatting, Boolean> entry : decorations.entrySet()) {
|
||||
if (entry.getValue()) {
|
||||
style = style.applyFormat(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
if (font != null) {
|
||||
try {
|
||||
style = style.withFont(new FontDescription.Resource(Identifier.parse(font)));
|
||||
} catch (Exception e) {
|
||||
// 忽略字体解析错误
|
||||
}
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建完整样式(含颜色)
|
||||
*/
|
||||
Style buildStyle() {
|
||||
Style style = buildBaseStyle();
|
||||
|
||||
// 只有在没有渐变、过渡和彩虹效果时才应用普通颜色
|
||||
if (!gradient && !transition && !rainbow && color != null) {
|
||||
style = style.withColor(color);
|
||||
}
|
||||
|
||||
if (shadow != null && shadow) {
|
||||
if (shadowColor != null) {
|
||||
style = style.withShadowColor(shadowColor.getValue() | 0xFF000000);
|
||||
} else {
|
||||
style = style.withShadowColor(0xFF000000);
|
||||
}
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.coloryr.allmusic.client;
|
||||
|
||||
import com.coloryr.allmusic.client.core.AllMusicHud;
|
||||
import com.coloryr.allmusic.client.core.Point2f;
|
||||
import com.coloryr.allmusic.client.core.render.PictureFrameBuffer;
|
||||
import com.coloryr.allmusic.codec.HudPosType;
|
||||
import com.mojang.blaze3d.platform.NativeImage;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import net.minecraft.client.gui.render.TextureSetup;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.client.renderer.texture.DynamicTexture;
|
||||
import org.joml.Matrix3x2fStack;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
public class PicRender extends PictureFrameBuffer {
|
||||
private final DynamicTexture sourceTexture;
|
||||
private final DynamicTexture rotateTexture;
|
||||
|
||||
public PicRender(int size) {
|
||||
sourceTexture = new DynamicTexture("allmusic source", size, size, false);
|
||||
rotateTexture = new DynamicTexture("allmusic rotate", size, size, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte[] source, byte[] rotate) {
|
||||
try {
|
||||
ByteArrayInputStream stream = new ByteArrayInputStream(source);
|
||||
var image1 = NativeImage.read(stream);
|
||||
sourceTexture.setPixels(image1);
|
||||
sourceTexture.upload();
|
||||
|
||||
stream = new ByteArrayInputStream(rotate);
|
||||
var image2 = NativeImage.read(stream);
|
||||
rotateTexture.setPixels(image2);
|
||||
rotateTexture.upload();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(boolean rotate, int size, float x, float y, int ang, HudPosType dir, float alpha) {
|
||||
Point2f point = AllMusicHud.getPos(size, size, x, y, dir);
|
||||
|
||||
Matrix3x2fStack matrix = new Matrix3x2fStack(2);
|
||||
|
||||
float a = (float) size / 2;
|
||||
|
||||
if (rotate) {
|
||||
matrix.translation(point.x + a, point.y + a);
|
||||
matrix.pushMatrix().rotate((float) Math.toRadians(ang));
|
||||
} else {
|
||||
matrix.translation(point.x + a, point.y + a);
|
||||
}
|
||||
|
||||
float x0 = -a;
|
||||
float x1 = a;
|
||||
float y0 = -a;
|
||||
float y1 = a;
|
||||
float u0 = 0;
|
||||
float u1 = 1;
|
||||
float v0 = 0;
|
||||
float v1 = 1;
|
||||
|
||||
int color = 0xFFFFFF00 + (int) (255 * alpha);
|
||||
|
||||
AllMusicClient.context.guiRenderState.addGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
|
||||
TextureSetup.singleTexture(rotate ? rotateTexture.getTextureView() : sourceTexture.getTextureView(), rotate ? rotateTexture.getSampler() : sourceTexture.getSampler()),
|
||||
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.scissorStack.peek()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.coloryr.allmusic.client;
|
||||
|
||||
import com.coloryr.allmusic.client.core.AllMusicHud;
|
||||
import com.coloryr.allmusic.client.core.Point2f;
|
||||
import com.coloryr.allmusic.client.core.render.TextureRender;
|
||||
import com.coloryr.allmusic.codec.HudPosType;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.render.TextureSetup;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.client.renderer.texture.SimpleTexture;
|
||||
import net.minecraft.client.renderer.texture.TextureContents;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import org.joml.Matrix3x2f;
|
||||
|
||||
public class TexRender extends TextureRender {
|
||||
private final SimpleTexture sourceTexture;
|
||||
|
||||
public TexRender(String texture) {
|
||||
super(texture);
|
||||
Identifier location = Identifier.fromNamespaceAndPath(AllMusicClient.MODID, texture);
|
||||
sourceTexture = new SimpleTexture(location);
|
||||
|
||||
try {
|
||||
TextureContents contents = sourceTexture.loadContents(Minecraft.getInstance().getResourceManager());
|
||||
|
||||
width = contents.image().getWidth();
|
||||
height = contents.image().getHeight();
|
||||
|
||||
sourceTexture.apply(contents);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawPic(float x, float y, float alpha) {
|
||||
Matrix3x2f matrix = new Matrix3x2f();
|
||||
|
||||
float w1 = (float) width / 2;
|
||||
float h1 = (float) height / 2;
|
||||
|
||||
matrix.translation(x + w1, y + h1);
|
||||
|
||||
float x0 = -w1;
|
||||
float x1 = w1;
|
||||
float y0 = -h1;
|
||||
float y1 = h1;
|
||||
float u0 = 0;
|
||||
float u1 = 1;
|
||||
float v0 = 0;
|
||||
float v1 = 1;
|
||||
|
||||
int color = 0xFFFFFF00 + (int) (255 * alpha);
|
||||
|
||||
AllMusicClient.context.guiRenderState.addGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
|
||||
TextureSetup.singleTexture(sourceTexture.getTextureView(), sourceTexture.getSampler()),
|
||||
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.scissorStack.peek()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawPic(float x, float y, float width, float alpha) {
|
||||
Matrix3x2f matrix = new Matrix3x2f();
|
||||
|
||||
float w1 = ((float) this.width / 2) * width;
|
||||
float h1 = (float) height / 2;
|
||||
|
||||
matrix.translation(x + w1, y + h1);
|
||||
|
||||
float x0 = -w1;
|
||||
float x1 = w1;
|
||||
float y0 = -h1;
|
||||
float y1 = h1;
|
||||
float u0 = 0;
|
||||
float u1 = width;
|
||||
float v0 = 0;
|
||||
float v1 = 1;
|
||||
|
||||
int color = 0xFFFFFF00 + (int) (255 * alpha);
|
||||
|
||||
AllMusicClient.context.guiRenderState.addGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
|
||||
TextureSetup.singleTexture(sourceTexture.getTextureView(), sourceTexture.getSampler()),
|
||||
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.scissorStack.peek()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawPic(float x, float y, float width, float height, HudPosType dir, float alpha) {
|
||||
Point2f point = AllMusicHud.getPos(width, height, x, y, dir);
|
||||
|
||||
float w1 = width / 2;
|
||||
float h1 = height / 2;
|
||||
|
||||
Matrix3x2f matrix = new Matrix3x2f().translation(point.x + w1, point.y + h1);
|
||||
|
||||
float x0 = -w1;
|
||||
float x1 = w1;
|
||||
float y0 = -h1;
|
||||
float y1 = h1;
|
||||
float u0 = 0;
|
||||
float u1 = 1;
|
||||
float v0 = 0;
|
||||
float v1 = 1;
|
||||
|
||||
int color = 0xFFFFFF00 + (int) (255 * alpha);
|
||||
|
||||
AllMusicClient.context.guiRenderState.addGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
|
||||
TextureSetup.singleTexture(sourceTexture.getTextureView(), sourceTexture.getSampler()),
|
||||
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.scissorStack.peek()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.coloryr.allmusic.client.mixins;
|
||||
|
||||
import com.coloryr.allmusic.client.AllMusicClient;
|
||||
import net.minecraft.client.DeltaTracker;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.Hud;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(Hud.class)
|
||||
public class GuiShow {
|
||||
@Inject(method = "extractRenderState", at = @At(value = "HEAD"))
|
||||
public void guiShow(GuiGraphicsExtractor graphics, DeltaTracker deltaTracker, CallbackInfo ci) {
|
||||
AllMusicClient.update(graphics);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.coloryr.allmusic.client.mixins;
|
||||
|
||||
import com.coloryr.allmusic.client.core.AllMusicCore;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(Minecraft.class)
|
||||
public class QuitServer {
|
||||
@Inject(method = "disconnect(Lnet/minecraft/client/gui/screens/Screen;ZZ)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;resetData()V"))
|
||||
public void quit(CallbackInfo info) {
|
||||
AllMusicCore.onServerQuit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.coloryr.allmusic.client.mixins;
|
||||
|
||||
import com.coloryr.allmusic.client.core.AllMusicCore;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(Minecraft.class)
|
||||
public class Register {
|
||||
@Inject(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/PacketProcessor;<init>(Ljava/lang/Thread;)V"))
|
||||
public void register(CallbackInfo info) {
|
||||
AllMusicCore.renderInit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.coloryr.allmusic.client.mixins;
|
||||
|
||||
import com.coloryr.allmusic.client.core.AllMusicCore;
|
||||
import net.minecraft.client.resources.sounds.SoundInstance;
|
||||
import net.minecraft.client.sounds.SoundEngine;
|
||||
import net.minecraft.client.sounds.SoundManager;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(SoundManager.class)
|
||||
public class SoundEvent {
|
||||
@Inject(method = "play", at = @At("HEAD"), cancellable = true)
|
||||
public void play(SoundInstance sound, CallbackInfoReturnable<SoundEngine.PlayResult> cir) {
|
||||
if (AllMusicCore.isPlay()) {
|
||||
SoundSource data = sound.getSource();
|
||||
switch (data) {
|
||||
case RECORDS, MUSIC: {
|
||||
cir.cancel();
|
||||
cir.setReturnValue(SoundEngine.PlayResult.NOT_STARTED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "playDelayed", at = @At("HEAD"), cancellable = true)
|
||||
public void play(SoundInstance soundInstance, int delay, CallbackInfo info) {
|
||||
if (AllMusicCore.isPlay()) {
|
||||
SoundSource data = soundInstance.getSource();
|
||||
switch (data) {
|
||||
case RECORDS, MUSIC -> info.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.coloryr.allmusic.client.mixins;
|
||||
|
||||
import com.coloryr.allmusic.client.core.AllMusicCore;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(Minecraft.class)
|
||||
public class Tick {
|
||||
@Inject(method = "tick", at = @At("HEAD"))
|
||||
public void tick(CallbackInfo info) {
|
||||
AllMusicCore.tick();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.coloryr.allmusic.client.others;
|
||||
|
||||
import com.coloryr.allmusic.client.core.AllMusicCore;
|
||||
import de.maxhenkel.voicechat.api.VoicechatPlugin;
|
||||
import de.maxhenkel.voicechat.api.events.ClientReceiveSoundEvent;
|
||||
import de.maxhenkel.voicechat.api.events.EventRegistration;
|
||||
|
||||
public class VoiceChat implements VoicechatPlugin {
|
||||
@Override
|
||||
public String getPluginId() {
|
||||
return "allmusic";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerEvents(EventRegistration registration) {
|
||||
registration.registerEvent(ClientReceiveSoundEvent.EntitySound.class, this::play);
|
||||
registration.registerEvent(ClientReceiveSoundEvent.LocationalSound.class, this::play);
|
||||
registration.registerEvent(ClientReceiveSoundEvent.StaticSound.class, this::play);
|
||||
}
|
||||
|
||||
public void play(ClientReceiveSoundEvent.EntitySound event) {
|
||||
AllMusicCore.chat();
|
||||
}
|
||||
|
||||
public void play(ClientReceiveSoundEvent.LocationalSound event) {
|
||||
AllMusicCore.chat();
|
||||
}
|
||||
|
||||
public void play(ClientReceiveSoundEvent.StaticSound event) {
|
||||
AllMusicCore.chat();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.coloryr.allmusic.comm;
|
||||
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
|
||||
|
||||
public class AllMusicInit implements ModInitializer {
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
PayloadTypeRegistry.clientboundPlay().register(MusicCodec.ID, MusicCodec.CODEC);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.coloryr.allmusic.comm;
|
||||
|
||||
import com.coloryr.allmusic.codec.MusicPack;
|
||||
import com.coloryr.allmusic.codec.MusicPacketCodec;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
|
||||
public record MusicCodec(MusicPack pack) implements CustomPacketPayload {
|
||||
public static final Identifier ChannelID = Identifier.fromNamespaceAndPath("allmusic", "channel");
|
||||
|
||||
public static final Type<MusicCodec> ID = new Type<>(ChannelID);
|
||||
public static final StreamCodec<FriendlyByteBuf, MusicCodec> CODEC =
|
||||
StreamCodec.of((buf, value) -> MusicPacketCodec.pack(buf, value.pack),
|
||||
buf -> new MusicCodec(MusicPacketCodec.decode(buf)));
|
||||
|
||||
@Override
|
||||
public @NonNull Type<? extends CustomPacketPayload> type() {
|
||||
return ID;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"required": true,
|
||||
"package": "com.coloryr.allmusic.client.mixins",
|
||||
"compatibilityLevel": "JAVA_25",
|
||||
"client": [
|
||||
"GuiShow",
|
||||
"QuitServer",
|
||||
"Register",
|
||||
"SoundEvent",
|
||||
"Tick"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
},
|
||||
"overwrites": {
|
||||
"requireAnnotations": true
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "allmusic_client",
|
||||
"version": "${version}",
|
||||
"name": "AllMusic Client",
|
||||
"description": "AllMusic Client Mod",
|
||||
"authors": [
|
||||
"Color_yr"
|
||||
],
|
||||
"contact": {
|
||||
"homepage": "https://github.com/Coloryr/AllMusic",
|
||||
"sources": "https://github.com/Coloryr/AllMusic"
|
||||
},
|
||||
"license": "GPL-3.0",
|
||||
"icon": "assets/allmusic_client/icon.png",
|
||||
"environment": "*",
|
||||
"entrypoints": {
|
||||
"main": [
|
||||
"com.coloryr.allmusic.comm.AllMusicInit"
|
||||
],
|
||||
"client": [
|
||||
"com.coloryr.allmusic.client.AllMusicClient"
|
||||
],
|
||||
"voicechat": [
|
||||
"com.coloryr.allmusic.client.others.VoiceChat"
|
||||
]
|
||||
},
|
||||
"mixins": [
|
||||
{
|
||||
"config": "allmusic_client.mixins.json",
|
||||
"environment": "client"
|
||||
}
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=0.19.5",
|
||||
"minecraft": ">=26.3",
|
||||
"fabric-api": "*"
|
||||
},
|
||||
"suggests": {
|
||||
"voicechat_api": "*"
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,8 @@ public class AllMusicClient implements AllMusicBridge {
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getMinecraft().gameSettings.getSoundLevel(SoundCategory.RECORDS);
|
||||
return Minecraft.getMinecraft().gameSettings.getSoundLevel(SoundCategory.RECORDS)
|
||||
* Minecraft.getMinecraft().gameSettings.getSoundLevel(SoundCategory.MASTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -118,7 +118,8 @@ public class AllMusicClient implements AllMusicBridge {
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS)
|
||||
* Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.MASTER);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
|
||||
@@ -145,7 +145,7 @@ public class AllMusic implements AllMusicBridge {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextFrameBuffer makeTextRender(String name) {
|
||||
public TextFrameBuffer<?> makeTextRender(String name) {
|
||||
return new CoreRenderTarget(name);
|
||||
}
|
||||
|
||||
@@ -218,7 +218,8 @@ public class AllMusic implements AllMusicBridge {
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getMinecraft().gameSettings.getSoundLevel(SoundCategory.RECORDS);
|
||||
return Minecraft.getMinecraft().gameSettings.getSoundLevel(SoundCategory.RECORDS)
|
||||
* Minecraft.getMinecraft().gameSettings.getSoundLevel(SoundCategory.MASTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -93,7 +93,8 @@ public class AllMusicClient implements IPayloadHandler<MusicCodec>, AllMusicBrid
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS)
|
||||
* Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.MASTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -134,7 +134,7 @@ public class AllMusicClient implements IPayloadHandler<MusicCodec>, AllMusicBrid
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
|
||||
return Minecraft.getInstance().options.getFinalSoundSourceVolume(SoundSource.RECORDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -92,7 +92,8 @@ public class AllMusicClient implements IPayloadHandler<MusicCodec>, AllMusicBrid
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS)
|
||||
* Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.MASTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -92,7 +92,8 @@ public class AllMusicClient implements IPayloadHandler<MusicCodec>, AllMusicBrid
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS)
|
||||
* Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.MASTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -128,7 +128,7 @@ public class AllMusicClient implements AllMusicBridge, IPayloadHandler<MusicCode
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
|
||||
return Minecraft.getInstance().options.getFinalSoundSourceVolume(SoundSource.RECORDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -135,7 +135,7 @@ public class AllMusicClient implements IPayloadHandler<MusicCodec>, AllMusicBrid
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
|
||||
return Minecraft.getInstance().options.getFinalSoundSourceVolume(SoundSource.RECORDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,7 +19,6 @@ dependencies {
|
||||
minecraft("com.mojang:minecraft:26.2")
|
||||
neoForge("net.neoforged:neoforge:26.2.0.1-beta")
|
||||
|
||||
// compileOnly("icyllis.modernui:ModernUI-NeoForge:26.1.2-3.13.0.4")
|
||||
compileOnly("de.maxhenkel.voicechat:voicechat-api:2.6.0")
|
||||
}
|
||||
|
||||
|
||||
@@ -135,11 +135,11 @@ public class AllMusicClient implements IPayloadHandler<MusicCodec>, AllMusicBrid
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
|
||||
return Minecraft.getInstance().options.getFinalSoundSourceVolume(SoundSource.RECORDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextFrameBuffer makeTextRender(String name) {
|
||||
public TextFrameBuffer<?> makeTextRender(String name) {
|
||||
return new CoreRenderTarget(name);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
loom.platform=neoforge
|
||||
@@ -0,0 +1,193 @@
|
||||
package com.coloryr.allmusic.client;
|
||||
|
||||
import com.coloryr.allmusic.client.core.AllMusicBridge;
|
||||
import com.coloryr.allmusic.client.core.AllMusicCore;
|
||||
import com.coloryr.allmusic.client.core.render.PictureFrameBuffer;
|
||||
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
|
||||
import com.coloryr.allmusic.client.core.render.TextureRender;
|
||||
import com.coloryr.allmusic.comm.AllMusicInit;
|
||||
import com.coloryr.allmusic.comm.MusicCodec;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.multiplayer.ClientPacketListener;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.ModList;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
import net.neoforged.fml.loading.FMLPaths;
|
||||
import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent;
|
||||
import net.neoforged.neoforge.client.event.ClientTickEvent;
|
||||
import net.neoforged.neoforge.client.event.RenderGuiLayerEvent;
|
||||
import net.neoforged.neoforge.client.event.sound.PlaySoundSourceEvent;
|
||||
import net.neoforged.neoforge.client.event.sound.PlayStreamingSourceEvent;
|
||||
import net.neoforged.neoforge.client.event.sound.SoundEngineLoadEvent;
|
||||
import net.neoforged.neoforge.client.gui.VanillaGuiLayers;
|
||||
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||
import net.neoforged.neoforge.network.handling.IPayloadHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@EventBusSubscriber(modid = AllMusicClient.MODID, value = Dist.CLIENT)
|
||||
public class AllMusicClient implements IPayloadHandler<MusicCodec>, AllMusicBridge {
|
||||
public static final String MODID = "allmusic_client";
|
||||
public static boolean modui;
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger("AllMusic Client");
|
||||
public static GuiGraphicsExtractor context;
|
||||
|
||||
@SubscribeEvent
|
||||
public static void setup(final FMLClientSetupEvent event) {
|
||||
AllMusicClient client = new AllMusicClient();
|
||||
AllMusicInit.handler = client;
|
||||
AllMusicCore.init(FMLPaths.CONFIGDIR.get(), client);
|
||||
|
||||
Minecraft.getInstance().execute(AllMusicCore::renderInit);
|
||||
|
||||
if (ModList.get().isLoaded("modernui")) {
|
||||
modui = true;
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onSound(final PlaySoundSourceEvent e) {
|
||||
if (!AllMusicCore.isPlay()) return;
|
||||
SoundSource data = e.getSound().getSource();
|
||||
switch (data) {
|
||||
case MUSIC, RECORDS -> e.getChannel().stop();
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onSound(final PlayStreamingSourceEvent e) {
|
||||
if (!AllMusicCore.isPlay()) return;
|
||||
SoundSource data = e.getSound().getSource();
|
||||
switch (data) {
|
||||
case MUSIC, RECORDS -> e.getChannel().stop();
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onServerQuit(final ClientPlayerNetworkEvent.LoggingOut e) {
|
||||
AllMusicCore.onServerQuit();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRenderOverlay(RenderGuiLayerEvent.Post e) {
|
||||
if (e.getName().equals(VanillaGuiLayers.CAMERA_OVERLAYS)) {
|
||||
context = e.getGuiGraphics();
|
||||
AllMusicCore.hudUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onTick(ClientTickEvent.Post event) {
|
||||
AllMusicCore.tick();
|
||||
}
|
||||
|
||||
public void sendMessage(String data) {
|
||||
data = "[AllMusic Client]" + data;
|
||||
LOGGER.warn(data);
|
||||
String finalData = data;
|
||||
Minecraft.getInstance().execute(() -> {
|
||||
if (Minecraft.getInstance().player == null)
|
||||
return;
|
||||
Minecraft.getInstance().player.sendSystemMessage(Component.literal(finalData));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MusicCodec pack, IPayloadContext iPayloadContext) {
|
||||
try {
|
||||
AllMusicCore.packDo(pack.pack());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopPlayMusic() {
|
||||
Minecraft.getInstance().getSoundManager().stop(null, SoundSource.MUSIC);
|
||||
Minecraft.getInstance().getSoundManager().stop(null, SoundSource.RECORDS);
|
||||
}
|
||||
|
||||
public int getScreenWidth() {
|
||||
return Minecraft.getInstance().getWindow().getGuiScaledWidth();
|
||||
}
|
||||
|
||||
public int getScreenHeight() {
|
||||
return Minecraft.getInstance().getWindow().getGuiScaledHeight();
|
||||
}
|
||||
|
||||
public int getTextWidth(String item) {
|
||||
return Minecraft.getInstance().font.width(item);
|
||||
}
|
||||
|
||||
public int getFontHeight() {
|
||||
return Minecraft.getInstance().font.lineHeight;
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getInstance().options.getFinalSoundSourceVolume(SoundSource.RECORDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextFrameBuffer makeTextRender(String name) {
|
||||
return new CoreRenderTarget(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextureRender makeTextureRender(String file) {
|
||||
return new TexRender(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PictureFrameBuffer makePictureRender(int size) {
|
||||
return new PicRender(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readText(String file) {
|
||||
try {
|
||||
var man = Minecraft.getInstance().getResourceManager();
|
||||
Resource resource = man.getResource(Identifier.fromNamespaceAndPath(MODID, file)).orElseThrow();
|
||||
try (InputStream inputStream = resource.open()) {
|
||||
byte[] bytes = inputStream.readAllBytes();
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream readFile(String file) {
|
||||
try {
|
||||
var man = Minecraft.getInstance().getResourceManager();
|
||||
Resource resource = man.getResource(Identifier.fromNamespaceAndPath(MODID, file)).orElseThrow();
|
||||
return resource.open();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void kick() {
|
||||
Minecraft client = Minecraft.getInstance();
|
||||
|
||||
ClientPacketListener packetListener = client.getConnection();
|
||||
if (packetListener != null) {
|
||||
Connection connection = packetListener.getConnection();
|
||||
connection.disconnect(Component.nullToEmpty("Old AllMusic server"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.coloryr.allmusic.client;
|
||||
|
||||
import com.coloryr.allmusic.client.core.AllMusicHud;
|
||||
import com.coloryr.allmusic.client.core.Point2f;
|
||||
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
|
||||
import com.coloryr.allmusic.codec.HudPosType;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public class CoreRenderTarget extends TextFrameBuffer<Component> {
|
||||
private final boolean isState;
|
||||
|
||||
public CoreRenderTarget(String name) {
|
||||
isState = name.equals("state");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putText(String text, int y, int color, boolean shadow) {
|
||||
color = (color & 0x00FFFFFF) | 0xFF000000;
|
||||
Component component = MiniMessage.parse(text);
|
||||
Font font = Minecraft.getInstance().font;
|
||||
int width = font.width(component);
|
||||
if (width == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int height = font.lineHeight + (shadow ? 1 : 0);
|
||||
if (isState) {
|
||||
y = 0;
|
||||
}
|
||||
texts.add(new TextItem<>(width, height, y, component, shadow, color));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
|
||||
if (texts.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
GuiGraphicsExtractor gui = AllMusicClient.context;
|
||||
if (gui == null) return;
|
||||
Font font = Minecraft.getInstance().font;
|
||||
|
||||
for (TextItem<Component> entry : texts) {
|
||||
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
|
||||
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
|
||||
|
||||
int drawX = (int) point.x;
|
||||
int drawY = (int) (point.y + entry.y);
|
||||
int finalColor = applyAlpha(entry.color, alpha);
|
||||
|
||||
if (maxWidth != -1 && entry.width > maxWidth) {
|
||||
int scrollOffset = (int) getOffset(entry, maxWidth);
|
||||
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
|
||||
gui.text(font, entry.component, drawX - scrollOffset, drawY, finalColor, entry.shadow);
|
||||
if (scrollOffset > 0) {
|
||||
gui.text(font, entry.component, drawX - scrollOffset + entry.width, drawY, finalColor, entry.shadow);
|
||||
}
|
||||
gui.disableScissor();
|
||||
} else {
|
||||
gui.text(font, entry.component, drawX, drawY, finalColor, entry.shadow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawLine(float x, float y, float alpha, int line) {
|
||||
if (texts.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (line >= texts.size()) {
|
||||
return;
|
||||
}
|
||||
GuiGraphicsExtractor gui = AllMusicClient.context;
|
||||
if (gui == null) return;
|
||||
TextItem<Component> entry = texts.get(line);
|
||||
gui.text(Minecraft.getInstance().font, entry.component,
|
||||
(int) x, (int) (y + entry.y),
|
||||
applyAlpha(entry.color, alpha), entry.shadow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
|
||||
if (texts.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
GuiGraphicsExtractor gui = AllMusicClient.context;
|
||||
if (gui == null) return;
|
||||
Font font = Minecraft.getInstance().font;
|
||||
|
||||
for (TextItem<Component> entry : texts) {
|
||||
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
|
||||
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
|
||||
|
||||
int drawX = (int) point.x;
|
||||
int drawY = (int) (point.y + entry.y);
|
||||
int finalColor = applyAlpha(entry.color, alpha);
|
||||
|
||||
if (maxWidth != -1 && entry.width > maxWidth) {
|
||||
float maxOffset = entry.width - maxWidth;
|
||||
float texOffset = maxOffset * state;
|
||||
int revealWidth = (int) (maxWidth * state);
|
||||
|
||||
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
|
||||
gui.text(font, entry.component, drawX - (int) texOffset, drawY, finalColor, entry.shadow);
|
||||
gui.disableScissor();
|
||||
} else {
|
||||
int revealWidth = (int) (entry.width * state);
|
||||
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
|
||||
gui.text(font, entry.component, drawX, drawY, finalColor, entry.shadow);
|
||||
gui.disableScissor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2f getLine(int line) {
|
||||
if (line >= texts.size()) {
|
||||
return new Point2f(0, 0);
|
||||
}
|
||||
TextItem<Component> entry = texts.get(line);
|
||||
return new Point2f(entry.width, entry.height);
|
||||
}
|
||||
|
||||
private static int applyAlpha(int color, float alpha) {
|
||||
int a = (int) (alpha * 255);
|
||||
if (a < 0) a = 0;
|
||||
if (a > 255) a = 255;
|
||||
return (color & 0x00FFFFFF) | (a << 24);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.coloryr.allmusic.client;
|
||||
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import com.mojang.renderpearl.api.pipeline.RenderPipeline;
|
||||
import net.minecraft.client.gui.navigation.ScreenRectangle;
|
||||
import net.minecraft.client.gui.render.TextureSetup;
|
||||
import net.minecraft.client.renderer.state.gui.GuiElementRenderState;
|
||||
import org.joml.Matrix3x2f;
|
||||
|
||||
public record FloatRenderState(RenderPipeline pipeline, TextureSetup textureSetup, Matrix3x2f pose, float x0,
|
||||
float y0, float x1, float y1, float u0, float u1, float v0, float v1, int color,
|
||||
ScreenRectangle scissorArea,
|
||||
ScreenRectangle bounds) implements GuiElementRenderState {
|
||||
public FloatRenderState(RenderPipeline renderPipeline, TextureSetup textureSetup, Matrix3x2f matrix3x2f,
|
||||
float i, float j, float k, float l, float f, float g, float h, float m, int n, ScreenRectangle screenRectangle) {
|
||||
this(renderPipeline, textureSetup, matrix3x2f, i, j, k, l, f, g, h, m, n, screenRectangle, getBounds(i, j, k, l, matrix3x2f, screenRectangle));
|
||||
}
|
||||
|
||||
private static ScreenRectangle getBounds(float i, float j, float k, float l, Matrix3x2f matrix3x2f, ScreenRectangle screenRectangle) {
|
||||
ScreenRectangle screenRectangle2 = (new ScreenRectangle((int) Math.ceil(i + 1), (int) Math.ceil(j + 1), (int) Math.ceil(k - i + 1), (int) Math.ceil(l - j + 1))).transformMaxBounds(matrix3x2f);
|
||||
return screenRectangle != null ? screenRectangle.intersection(screenRectangle2) : screenRectangle2;
|
||||
}
|
||||
|
||||
public void buildVertices(VertexConsumer vertexConsumer) {
|
||||
vertexConsumer.addVertexWith2DPose(pose(), x0(), y0()).setUv(u0(), v0()).setColor(color());
|
||||
vertexConsumer.addVertexWith2DPose(pose(), x0(), y1()).setUv(u0(), v1()).setColor(color());
|
||||
vertexConsumer.addVertexWith2DPose(pose(), x1(), y1()).setUv(u1(), v1()).setColor(color());
|
||||
vertexConsumer.addVertexWith2DPose(pose(), x1(), y0()).setUv(u1(), v0()).setColor(color());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,645 @@
|
||||
package com.coloryr.allmusic.client;
|
||||
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.*;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class MiniMessage {
|
||||
// 正则表达式匹配标签
|
||||
private static final Pattern TAG_PATTERN = Pattern.compile(
|
||||
"<(/?)(\\w+)(?:\\(([^)]+)\\))?(?::([^>]+))?>"
|
||||
);
|
||||
|
||||
// 颜色映射表
|
||||
private static final Map<String, ChatFormatting> COLOR_MAP = new HashMap<>();
|
||||
// 装饰器映射
|
||||
private static final Map<String, ChatFormatting> DECORATION_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
COLOR_MAP.put("black", ChatFormatting.BLACK);
|
||||
COLOR_MAP.put("dark_blue", ChatFormatting.DARK_BLUE);
|
||||
COLOR_MAP.put("dark_green", ChatFormatting.DARK_GREEN);
|
||||
COLOR_MAP.put("dark_aqua", ChatFormatting.DARK_AQUA);
|
||||
COLOR_MAP.put("dark_red", ChatFormatting.DARK_RED);
|
||||
COLOR_MAP.put("dark_purple", ChatFormatting.DARK_PURPLE);
|
||||
COLOR_MAP.put("gold", ChatFormatting.GOLD);
|
||||
COLOR_MAP.put("gray", ChatFormatting.GRAY);
|
||||
COLOR_MAP.put("dark_gray", ChatFormatting.DARK_GRAY);
|
||||
COLOR_MAP.put("blue", ChatFormatting.BLUE);
|
||||
COLOR_MAP.put("green", ChatFormatting.GREEN);
|
||||
COLOR_MAP.put("aqua", ChatFormatting.AQUA);
|
||||
COLOR_MAP.put("red", ChatFormatting.RED);
|
||||
COLOR_MAP.put("light_purple", ChatFormatting.LIGHT_PURPLE);
|
||||
COLOR_MAP.put("yellow", ChatFormatting.YELLOW);
|
||||
COLOR_MAP.put("white", ChatFormatting.WHITE);
|
||||
}
|
||||
|
||||
static {
|
||||
DECORATION_MAP.put("bold", ChatFormatting.BOLD);
|
||||
DECORATION_MAP.put("italic", ChatFormatting.ITALIC);
|
||||
DECORATION_MAP.put("underlined", ChatFormatting.UNDERLINE);
|
||||
DECORATION_MAP.put("strikethrough", ChatFormatting.STRIKETHROUGH);
|
||||
DECORATION_MAP.put("obfuscated", ChatFormatting.OBFUSCATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 MiniMessage 字符串为 Component
|
||||
*/
|
||||
public static Component parse(String input) {
|
||||
MutableComponent root = Component.literal("");
|
||||
Stack<StyleContext> styleStack = new Stack<>();
|
||||
StyleContext currentStyle = new StyleContext();
|
||||
styleStack.push(currentStyle);
|
||||
|
||||
StringBuilder textBuffer = new StringBuilder();
|
||||
int pos = 0;
|
||||
int length = input.length();
|
||||
|
||||
while (pos < length) {
|
||||
char c = input.charAt(pos);
|
||||
|
||||
if (c == '<') {
|
||||
// 先处理已积累的文本
|
||||
if (textBuffer.length() > 0) {
|
||||
appendText(root, textBuffer.toString(), currentStyle);
|
||||
textBuffer.setLength(0);
|
||||
}
|
||||
|
||||
// 查找标签结束位置
|
||||
int endTag = findClosingTag(input, pos);
|
||||
if (endTag == -1) {
|
||||
// 没有找到闭合标签,当作普通文本处理
|
||||
textBuffer.append(c);
|
||||
pos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
String tagContent = input.substring(pos, endTag + 1);
|
||||
Matcher matcher = TAG_PATTERN.matcher(tagContent);
|
||||
|
||||
if (matcher.matches()) {
|
||||
boolean isClosing = !matcher.group(1).isEmpty();
|
||||
String tagName = matcher.group(2).toLowerCase(Locale.ROOT);
|
||||
String params = matcher.group(3);
|
||||
String value = matcher.group(4);
|
||||
|
||||
if (isClosing) {
|
||||
// 关闭标签 - 只关闭对应的开始标签
|
||||
String expectedOpenTag = matcher.group(2).toLowerCase(Locale.ROOT);
|
||||
|
||||
// 查找对应的开始标签并关闭
|
||||
if (styleStack.size() > 1) {
|
||||
// 从栈顶向下查找匹配的开始标签
|
||||
int index = styleStack.size() - 1;
|
||||
boolean found = false;
|
||||
|
||||
while (index >= 0) {
|
||||
StyleContext ctx = styleStack.get(index);
|
||||
if (ctx.tagName != null && ctx.tagName.equals(expectedOpenTag)) {
|
||||
// 找到匹配的开始标签,关闭到这一层
|
||||
while (styleStack.size() > index + 1) {
|
||||
styleStack.pop();
|
||||
}
|
||||
currentStyle = styleStack.peek();
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
index--;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
// 没找到匹配的标签,忽略此关闭标签
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 处理各种标签
|
||||
StyleContext newStyle = currentStyle.copy();
|
||||
|
||||
switch (tagName) {
|
||||
case "color":
|
||||
handleColorTag(newStyle, params, value);
|
||||
break;
|
||||
case "shadow":
|
||||
handleShadowTag(newStyle, params, value);
|
||||
break;
|
||||
case "decoration":
|
||||
handleDecorationTag(newStyle, params, value);
|
||||
break;
|
||||
case "reset":
|
||||
newStyle = new StyleContext();
|
||||
break;
|
||||
case "rainbow":
|
||||
handleRainbowTag(newStyle, params, value);
|
||||
break;
|
||||
case "gradient":
|
||||
handleGradientTag(newStyle, params, value);
|
||||
break;
|
||||
case "transition":
|
||||
handleTransitionTag(newStyle, params, value);
|
||||
break;
|
||||
case "font":
|
||||
handleFontTag(newStyle, params, value);
|
||||
break;
|
||||
default:
|
||||
// 未知标签,忽略
|
||||
break;
|
||||
}
|
||||
|
||||
// 记录标签名称以便关闭时匹配
|
||||
newStyle.tagName = tagName;
|
||||
styleStack.push(newStyle);
|
||||
currentStyle = newStyle;
|
||||
}
|
||||
} else {
|
||||
// 无效标签,当作普通文本
|
||||
textBuffer.append(tagContent);
|
||||
}
|
||||
|
||||
pos = endTag + 1;
|
||||
} else {
|
||||
textBuffer.append(c);
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理剩余的文本
|
||||
if (textBuffer.length() > 0) {
|
||||
appendText(root, textBuffer.toString(), currentStyle);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理颜色标签
|
||||
* 支持格式: <color:red>, <color:#FF0000>, <color(rgb):255,0,0>
|
||||
*/
|
||||
private static void handleColorTag(StyleContext style, String params, String value) {
|
||||
if (value != null) {
|
||||
// 格式: <color:red> 或 <color:#FF0000>
|
||||
TextColor textColor = parseColor(value);
|
||||
if (textColor != null) {
|
||||
style.color = textColor;
|
||||
// 清除渐变、过渡和彩虹效果
|
||||
style.gradient = false;
|
||||
style.transition = false;
|
||||
style.rainbow = false;
|
||||
}
|
||||
} else if (params != null) {
|
||||
// 格式: <color(rgb):255,0,0>
|
||||
if (params.equalsIgnoreCase("rgb")) {
|
||||
String[] parts = value.split(",");
|
||||
if (parts.length == 3) {
|
||||
try {
|
||||
int r = Integer.parseInt(parts[0].trim());
|
||||
int g = Integer.parseInt(parts[1].trim());
|
||||
int b = Integer.parseInt(parts[2].trim());
|
||||
style.color = TextColor.fromRgb((r << 16) | (g << 8) | b);
|
||||
// 清除渐变、过渡和彩虹效果
|
||||
style.gradient = false;
|
||||
style.transition = false;
|
||||
style.rainbow = false;
|
||||
} catch (NumberFormatException e) {
|
||||
// 忽略解析错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理阴影标签
|
||||
* 支持格式: <shadow>, <shadow:true>, <shadow:false>, <shadow:#FF0000>
|
||||
*/
|
||||
private static void handleShadowTag(StyleContext style, String params, String value) {
|
||||
if (value == null) {
|
||||
style.shadow = true;
|
||||
} else if (value.equalsIgnoreCase("true")) {
|
||||
style.shadow = true;
|
||||
} else if (value.equalsIgnoreCase("false")) {
|
||||
style.shadow = false;
|
||||
} else {
|
||||
// 自定义阴影颜色
|
||||
TextColor shadowColor = parseColor(value);
|
||||
if (shadowColor != null) {
|
||||
style.shadow = true;
|
||||
style.shadowColor = shadowColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理装饰标签
|
||||
* 支持格式: <decoration:bold>, <decoration:bold:true>, <decoration:bold:false>
|
||||
*/
|
||||
private static void handleDecorationTag(StyleContext style, String params, String value) {
|
||||
if (params == null) return;
|
||||
|
||||
String[] parts = params.split(":");
|
||||
String decorationName = parts[0].toLowerCase(Locale.ROOT);
|
||||
boolean enabled = true;
|
||||
|
||||
if (parts.length > 1) {
|
||||
enabled = Boolean.parseBoolean(parts[1]);
|
||||
} else if (value != null) {
|
||||
enabled = Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
ChatFormatting formatting = DECORATION_MAP.get(decorationName);
|
||||
if (formatting != null) {
|
||||
style.decorations.put(formatting, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理彩虹色标签
|
||||
* 支持格式: <rainbow>, <rainbow:!>, <rainbow(saturation):0.8>, <rainbow(brightness):0.5>
|
||||
*/
|
||||
private static void handleRainbowTag(StyleContext style, String params, String value) {
|
||||
float saturation = 1.0f;
|
||||
float brightness = 1.0f;
|
||||
|
||||
if (params != null) {
|
||||
if (params.equalsIgnoreCase("saturation") && value != null) {
|
||||
try {
|
||||
saturation = Float.parseFloat(value);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
} else if (params.equalsIgnoreCase("brightness") && value != null) {
|
||||
try {
|
||||
brightness = Float.parseFloat(value);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
style.rainbow = true;
|
||||
style.gradient = false;
|
||||
style.transition = false;
|
||||
style.rainbowSaturation = saturation;
|
||||
style.rainbowBrightness = brightness;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理渐变色标签
|
||||
* 支持格式: <gradient:#FF0000:#00FF00>, <gradient(rgb):255,0,0:0,255,0>
|
||||
* 渐变是逐字符过渡
|
||||
*/
|
||||
private static void handleGradientTag(StyleContext style, String params, String value) {
|
||||
if (value == null) return;
|
||||
|
||||
String[] colors = value.split(":");
|
||||
if (colors.length == 2) {
|
||||
TextColor start = parseColor(colors[0]);
|
||||
TextColor end = parseColor(colors[1]);
|
||||
|
||||
if (start != null && end != null) {
|
||||
style.gradient = true;
|
||||
style.transition = false;
|
||||
style.rainbow = false;
|
||||
style.gradientStart = start;
|
||||
style.gradientEnd = end;
|
||||
}
|
||||
} else if (params != null && params.equalsIgnoreCase("rgb")) {
|
||||
// 处理 RGB 格式
|
||||
String[] color1 = colors[0].split(",");
|
||||
String[] color2 = colors[1].split(",");
|
||||
if (color1.length == 3 && color2.length == 3) {
|
||||
try {
|
||||
int r1 = Integer.parseInt(color1[0].trim());
|
||||
int g1 = Integer.parseInt(color1[1].trim());
|
||||
int b1 = Integer.parseInt(color1[2].trim());
|
||||
int r2 = Integer.parseInt(color2[0].trim());
|
||||
int g2 = Integer.parseInt(color2[1].trim());
|
||||
int b2 = Integer.parseInt(color2[2].trim());
|
||||
|
||||
style.gradient = true;
|
||||
style.transition = false;
|
||||
style.rainbow = false;
|
||||
style.gradientStart = TextColor.fromRgb((r1 << 16) | (g1 << 8) | b1);
|
||||
style.gradientEnd = TextColor.fromRgb((r2 << 16) | (g2 << 8) | b2);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理过渡色标签
|
||||
* 支持格式: <transition:#FF0000:#00FF00>, <transition(rgb):255,0,0:0,255,0>
|
||||
* 过渡是整个文本块的颜色过渡(类似整体渐变效果)
|
||||
*/
|
||||
private static void handleTransitionTag(StyleContext style, String params, String value) {
|
||||
if (value == null) return;
|
||||
|
||||
String[] colors = value.split(":");
|
||||
if (colors.length == 2) {
|
||||
TextColor start = parseColor(colors[0]);
|
||||
TextColor end = parseColor(colors[1]);
|
||||
|
||||
if (start != null && end != null) {
|
||||
style.transition = true;
|
||||
style.gradient = false;
|
||||
style.rainbow = false;
|
||||
style.transitionStart = start;
|
||||
style.transitionEnd = end;
|
||||
}
|
||||
} else if (params != null && params.equalsIgnoreCase("rgb")) {
|
||||
// 处理 RGB 格式
|
||||
String[] color1 = colors[0].split(",");
|
||||
String[] color2 = colors[1].split(",");
|
||||
if (color1.length == 3 && color2.length == 3) {
|
||||
try {
|
||||
int r1 = Integer.parseInt(color1[0].trim());
|
||||
int g1 = Integer.parseInt(color1[1].trim());
|
||||
int b1 = Integer.parseInt(color1[2].trim());
|
||||
int r2 = Integer.parseInt(color2[0].trim());
|
||||
int g2 = Integer.parseInt(color2[1].trim());
|
||||
int b2 = Integer.parseInt(color2[2].trim());
|
||||
|
||||
style.transition = true;
|
||||
style.gradient = false;
|
||||
style.rainbow = false;
|
||||
style.transitionStart = TextColor.fromRgb((r1 << 16) | (g1 << 8) | b1);
|
||||
style.transitionEnd = TextColor.fromRgb((r2 << 16) | (g2 << 8) | b2);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理字体标签
|
||||
* 支持格式: <font:minecraft:default>, <font:minecraft:uniform>
|
||||
*/
|
||||
private static void handleFontTag(StyleContext style, String params, String value) {
|
||||
String fontName = value != null ? value : params;
|
||||
if (fontName != null) {
|
||||
style.font = fontName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析颜色字符串
|
||||
* 支持: 颜色名称, #RRGGBB, #RGB, 十六进制数值
|
||||
*/
|
||||
private static TextColor parseColor(String colorStr) {
|
||||
if (colorStr == null) return null;
|
||||
|
||||
// 检查是否为预定义颜色名称
|
||||
ChatFormatting formatting = COLOR_MAP.get(colorStr.toLowerCase(Locale.ROOT));
|
||||
if (formatting != null) {
|
||||
return TextColor.fromLegacyFormat(formatting);
|
||||
}
|
||||
|
||||
// 检查十六进制格式
|
||||
if (colorStr.startsWith("#")) {
|
||||
try {
|
||||
if (colorStr.length() == 7) {
|
||||
return TextColor.parseColor(colorStr).getOrThrow();
|
||||
} else if (colorStr.length() == 4) {
|
||||
// 扩展 #RGB 到 #RRGGBB
|
||||
String expanded = "#" +
|
||||
colorStr.charAt(1) + colorStr.charAt(1) +
|
||||
colorStr.charAt(2) + colorStr.charAt(2) +
|
||||
colorStr.charAt(3) + colorStr.charAt(3);
|
||||
return TextColor.parseColor(expanded).getOrThrow();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 忽略解析错误
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找标签闭合位置
|
||||
*/
|
||||
private static int findClosingTag(String input, int start) {
|
||||
int pos = start + 1;
|
||||
while (pos < input.length()) {
|
||||
char c = input.charAt(pos);
|
||||
if (c == '>') {
|
||||
return pos;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加文本到组件
|
||||
*/
|
||||
private static void appendText(MutableComponent root, String text, StyleContext styleContext) {
|
||||
if (text.isEmpty()) return;
|
||||
|
||||
// 如果有渐变效果,需要逐字符处理
|
||||
if (styleContext.gradient && styleContext.gradientStart != null && styleContext.gradientEnd != null) {
|
||||
appendGradientText(root, text, styleContext);
|
||||
}
|
||||
// 如果有过渡效果,需要逐字符处理
|
||||
else if (styleContext.transition && styleContext.transitionStart != null && styleContext.transitionEnd != null) {
|
||||
appendTransitionText(root, text, styleContext);
|
||||
}
|
||||
// 如果有彩虹效果,需要逐字符处理
|
||||
else if (styleContext.rainbow) {
|
||||
appendRainbowText(root, text, styleContext);
|
||||
}
|
||||
// 普通文本
|
||||
else {
|
||||
MutableComponent component = Component.literal(text);
|
||||
component.setStyle(styleContext.buildStyle());
|
||||
root.append(component);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 带渐变效果的文本追加(逐字符颜色变化)
|
||||
*/
|
||||
private static void appendGradientText(MutableComponent root, String text, StyleContext styleContext) {
|
||||
int length = text.length();
|
||||
Style baseStyle = styleContext.buildBaseStyle();
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = text.charAt(i);
|
||||
float ratio = (float) i / (length - 1);
|
||||
TextColor gradientColor = interpolateColor(styleContext.gradientStart, styleContext.gradientEnd, ratio);
|
||||
|
||||
Style charStyle = baseStyle.withColor(gradientColor);
|
||||
// 重新应用阴影
|
||||
if (styleContext.shadow != null && styleContext.shadow) {
|
||||
if (styleContext.shadowColor != null) {
|
||||
charStyle = charStyle.withShadowColor(styleContext.shadowColor.getValue() | 0xFF000000);
|
||||
} else {
|
||||
charStyle = charStyle.withShadowColor(0xFF000000);
|
||||
}
|
||||
}
|
||||
|
||||
root.append(Component.literal(String.valueOf(c)).withStyle(charStyle));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 带过渡效果的文本追加(整体颜色过渡)
|
||||
*/
|
||||
private static void appendTransitionText(MutableComponent root, String text, StyleContext styleContext) {
|
||||
Style baseStyle = styleContext.buildBaseStyle();
|
||||
|
||||
// 过渡效果:为整个文本块创建一个颜色过渡
|
||||
MutableComponent transitionComponent = Component.literal(text);
|
||||
|
||||
// 应用过渡样式(这里使用颜色插值,但也可以根据需求调整)
|
||||
if (styleContext.transitionStart != null && styleContext.transitionEnd != null) {
|
||||
// 可以使用整体渐变或者固定起始色
|
||||
transitionComponent.setStyle(baseStyle.withColor(styleContext.transitionStart));
|
||||
} else {
|
||||
transitionComponent.setStyle(baseStyle);
|
||||
}
|
||||
|
||||
root.append(transitionComponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 带彩虹效果的文本追加
|
||||
*/
|
||||
private static void appendRainbowText(MutableComponent root, String text, StyleContext styleContext) {
|
||||
int length = text.length();
|
||||
Style baseStyle = styleContext.buildBaseStyle();
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = text.charAt(i);
|
||||
float hue = (float) i / length;
|
||||
int rgb = java.awt.Color.HSBtoRGB(hue, styleContext.rainbowSaturation, styleContext.rainbowBrightness);
|
||||
TextColor rainbowColor = TextColor.fromRgb(rgb & 0xFFFFFF);
|
||||
|
||||
Style charStyle = baseStyle.withColor(rainbowColor);
|
||||
// 重新应用阴影
|
||||
if (styleContext.shadow != null && styleContext.shadow) {
|
||||
if (styleContext.shadowColor != null) {
|
||||
charStyle = charStyle.withShadowColor(styleContext.shadowColor.getValue() | 0xFF000000);
|
||||
} else {
|
||||
charStyle = charStyle.withShadowColor(0xFF000000);
|
||||
}
|
||||
}
|
||||
|
||||
root.append(Component.literal(String.valueOf(c)).withStyle(charStyle));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 颜色插值
|
||||
*/
|
||||
private static TextColor interpolateColor(TextColor start, TextColor end, float ratio) {
|
||||
int startRgb = start.getValue();
|
||||
int endRgb = end.getValue();
|
||||
|
||||
int r1 = (startRgb >> 16) & 0xFF;
|
||||
int g1 = (startRgb >> 8) & 0xFF;
|
||||
int b1 = startRgb & 0xFF;
|
||||
|
||||
int r2 = (endRgb >> 16) & 0xFF;
|
||||
int g2 = (endRgb >> 8) & 0xFF;
|
||||
int b2 = endRgb & 0xFF;
|
||||
|
||||
int r = (int) (r1 + (r2 - r1) * ratio);
|
||||
int g = (int) (g1 + (g2 - g1) * ratio);
|
||||
int b = (int) (b1 + (b2 - b1) * ratio);
|
||||
|
||||
return TextColor.fromRgb((r << 16) | (g << 8) | b);
|
||||
}
|
||||
|
||||
/**
|
||||
* 样式上下文类,用于追踪嵌套样式
|
||||
*/
|
||||
private static class StyleContext {
|
||||
String tagName = null; // 记录标签名称,用于关闭匹配
|
||||
TextColor color = null;
|
||||
Boolean shadow = null;
|
||||
TextColor shadowColor = null;
|
||||
Map<ChatFormatting, Boolean> decorations = new HashMap<>();
|
||||
boolean rainbow = false;
|
||||
float rainbowSaturation = 1.0f;
|
||||
float rainbowBrightness = 1.0f;
|
||||
boolean gradient = false;
|
||||
TextColor gradientStart = null;
|
||||
TextColor gradientEnd = null;
|
||||
boolean transition = false;
|
||||
TextColor transitionStart = null;
|
||||
TextColor transitionEnd = null;
|
||||
String font = null;
|
||||
|
||||
StyleContext copy() {
|
||||
StyleContext copy = new StyleContext();
|
||||
copy.tagName = this.tagName;
|
||||
copy.color = this.color;
|
||||
copy.shadow = this.shadow;
|
||||
copy.shadowColor = this.shadowColor;
|
||||
copy.decorations.putAll(this.decorations);
|
||||
copy.rainbow = this.rainbow;
|
||||
copy.rainbowSaturation = this.rainbowSaturation;
|
||||
copy.rainbowBrightness = this.rainbowBrightness;
|
||||
copy.gradient = this.gradient;
|
||||
copy.gradientStart = this.gradientStart;
|
||||
copy.gradientEnd = this.gradientEnd;
|
||||
copy.transition = this.transition;
|
||||
copy.transitionStart = this.transitionStart;
|
||||
copy.transitionEnd = this.transitionEnd;
|
||||
copy.font = this.font;
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建基础样式(不含颜色)
|
||||
*/
|
||||
Style buildBaseStyle() {
|
||||
Style style = Style.EMPTY;
|
||||
|
||||
// 应用装饰器
|
||||
for (Map.Entry<ChatFormatting, Boolean> entry : decorations.entrySet()) {
|
||||
if (entry.getValue()) {
|
||||
style = style.applyFormat(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
if (font != null) {
|
||||
try {
|
||||
style = style.withFont(new FontDescription.Resource(Identifier.parse(font)));
|
||||
} catch (Exception e) {
|
||||
// 忽略字体解析错误
|
||||
}
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建完整样式(含颜色)
|
||||
*/
|
||||
Style buildStyle() {
|
||||
Style style = buildBaseStyle();
|
||||
|
||||
// 只有在没有渐变、过渡和彩虹效果时才应用普通颜色
|
||||
if (!gradient && !transition && !rainbow && color != null) {
|
||||
style = style.withColor(color);
|
||||
}
|
||||
|
||||
if (shadow != null && shadow) {
|
||||
if (shadowColor != null) {
|
||||
style = style.withShadowColor(shadowColor.getValue() | 0xFF000000);
|
||||
} else {
|
||||
style = style.withShadowColor(0xFF000000);
|
||||
}
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.coloryr.allmusic.client;
|
||||
|
||||
import com.coloryr.allmusic.client.core.AllMusicHud;
|
||||
import com.coloryr.allmusic.client.core.Point2f;
|
||||
import com.coloryr.allmusic.client.core.render.PictureFrameBuffer;
|
||||
import com.coloryr.allmusic.codec.HudPosType;
|
||||
import com.mojang.blaze3d.platform.NativeImage;
|
||||
import net.minecraft.client.gui.render.TextureSetup;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.client.renderer.texture.DynamicTexture;
|
||||
import org.joml.Matrix3x2fStack;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
public class PicRender extends PictureFrameBuffer {
|
||||
private final DynamicTexture sourceTexture;
|
||||
private final DynamicTexture rotateTexture;
|
||||
|
||||
public PicRender(int size) {
|
||||
sourceTexture = new DynamicTexture("allmusic source", size, size, false);
|
||||
rotateTexture = new DynamicTexture("allmusic rotate", size, size, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte[] source, byte[] rotate) {
|
||||
try {
|
||||
ByteArrayInputStream stream = new ByteArrayInputStream(source);
|
||||
var image1 = NativeImage.read(stream);
|
||||
sourceTexture.setPixels(image1);
|
||||
sourceTexture.upload();
|
||||
|
||||
stream = new ByteArrayInputStream(rotate);
|
||||
var image2 = NativeImage.read(stream);
|
||||
rotateTexture.setPixels(image2);
|
||||
rotateTexture.upload();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(boolean rotate, int size, float x, float y, int ang, HudPosType dir, float alpha) {
|
||||
Point2f point = AllMusicHud.getPos(size, size, x, y, dir);
|
||||
|
||||
Matrix3x2fStack matrix = new Matrix3x2fStack(2);
|
||||
|
||||
float a = (float) size / 2;
|
||||
|
||||
if (rotate) {
|
||||
matrix.translation(point.x + a, point.y + a);
|
||||
matrix.pushMatrix().rotate((float) Math.toRadians(ang));
|
||||
} else {
|
||||
matrix.translation(point.x + a, point.y + a);
|
||||
}
|
||||
|
||||
float x0 = -a;
|
||||
float x1 = a;
|
||||
float y0 = -a;
|
||||
float y1 = a;
|
||||
float u0 = 0;
|
||||
float u1 = 1;
|
||||
float v0 = 0;
|
||||
float v1 = 1;
|
||||
|
||||
int color = 0xFFFFFF00 + (int) (255 * alpha);
|
||||
|
||||
AllMusicClient.context.submitGuiElementRenderState(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
|
||||
TextureSetup.singleTexture(rotate ? rotateTexture.getTextureView() : sourceTexture.getTextureView(), rotate ? rotateTexture.getSampler() : sourceTexture.getSampler()),
|
||||
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.peekScissorStack()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.coloryr.allmusic.client;
|
||||
|
||||
import com.coloryr.allmusic.client.core.AllMusicHud;
|
||||
import com.coloryr.allmusic.client.core.Point2f;
|
||||
import com.coloryr.allmusic.client.core.render.TextureRender;
|
||||
import com.coloryr.allmusic.codec.HudPosType;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.render.TextureSetup;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.client.renderer.texture.SimpleTexture;
|
||||
import net.minecraft.client.renderer.texture.TextureContents;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import org.joml.Matrix3x2f;
|
||||
|
||||
public class TexRender extends TextureRender {
|
||||
private final SimpleTexture sourceTexture;
|
||||
|
||||
public TexRender(String texture) {
|
||||
super(texture);
|
||||
Identifier location = Identifier.fromNamespaceAndPath(AllMusicClient.MODID, texture);
|
||||
sourceTexture = new SimpleTexture(location);
|
||||
|
||||
try {
|
||||
TextureContents contents = sourceTexture.loadContents(Minecraft.getInstance().getResourceManager());
|
||||
|
||||
width = contents.image().getWidth();
|
||||
height = contents.image().getHeight();
|
||||
|
||||
sourceTexture.apply(contents);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawPic(float x, float y, float alpha) {
|
||||
Matrix3x2f matrix = new Matrix3x2f();
|
||||
|
||||
float w1 = (float) width / 2;
|
||||
float h1 = (float) height / 2;
|
||||
|
||||
matrix.translation(x + w1, y + h1);
|
||||
|
||||
float x0 = -w1;
|
||||
float x1 = w1;
|
||||
float y0 = -h1;
|
||||
float y1 = h1;
|
||||
float u0 = 0;
|
||||
float u1 = 1;
|
||||
float v0 = 0;
|
||||
float v1 = 1;
|
||||
|
||||
int color = 0xFFFFFF00 + (int) (255 * alpha);
|
||||
|
||||
AllMusicClient.context.submitGuiElementRenderState(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
|
||||
TextureSetup.singleTexture(sourceTexture.getTextureView(), sourceTexture.getSampler()),
|
||||
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.peekScissorStack()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawPic(float x, float y, float width, float alpha) {
|
||||
Matrix3x2f matrix = new Matrix3x2f();
|
||||
|
||||
float w1 = ((float) this.width / 2) * width;
|
||||
float h1 = (float) height / 2;
|
||||
|
||||
matrix.translation(x + w1, y + h1);
|
||||
|
||||
float x0 = -w1;
|
||||
float x1 = w1;
|
||||
float y0 = -h1;
|
||||
float y1 = h1;
|
||||
float u0 = 0;
|
||||
float u1 = width;
|
||||
float v0 = 0;
|
||||
float v1 = 1;
|
||||
|
||||
int color = 0xFFFFFF00 + (int) (255 * alpha);
|
||||
|
||||
AllMusicClient.context.submitGuiElementRenderState(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
|
||||
TextureSetup.singleTexture(sourceTexture.getTextureView(), sourceTexture.getSampler()),
|
||||
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.peekScissorStack()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawPic(float x, float y, float width, float height, HudPosType dir, float alpha) {
|
||||
Point2f point = AllMusicHud.getPos(width, height, x, y, dir);
|
||||
|
||||
float w1 = width / 2;
|
||||
float h1 = height / 2;
|
||||
|
||||
Matrix3x2f matrix = new Matrix3x2f().translation(point.x + w1, point.y + h1);
|
||||
|
||||
float x0 = -w1;
|
||||
float x1 = w1;
|
||||
float y0 = -h1;
|
||||
float y1 = h1;
|
||||
float u0 = 0;
|
||||
float u1 = 1;
|
||||
float v0 = 0;
|
||||
float v1 = 1;
|
||||
|
||||
int color = 0xFFFFFF00 + (int) (255 * alpha);
|
||||
|
||||
AllMusicClient.context.submitGuiElementRenderState(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
|
||||
TextureSetup.singleTexture(sourceTexture.getTextureView(), sourceTexture.getSampler()),
|
||||
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.peekScissorStack()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.coloryr.allmusic.client.others;
|
||||
|
||||
import com.coloryr.allmusic.client.core.AllMusicCore;
|
||||
import de.maxhenkel.voicechat.api.ForgeVoicechatPlugin;
|
||||
import de.maxhenkel.voicechat.api.VoicechatPlugin;
|
||||
import de.maxhenkel.voicechat.api.events.ClientReceiveSoundEvent;
|
||||
import de.maxhenkel.voicechat.api.events.EventRegistration;
|
||||
|
||||
@ForgeVoicechatPlugin
|
||||
public class VoiceChat implements VoicechatPlugin {
|
||||
@Override
|
||||
public String getPluginId() {
|
||||
return "allmusic";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerEvents(EventRegistration registration) {
|
||||
registration.registerEvent(ClientReceiveSoundEvent.EntitySound.class, this::play);
|
||||
registration.registerEvent(ClientReceiveSoundEvent.LocationalSound.class, this::play);
|
||||
registration.registerEvent(ClientReceiveSoundEvent.StaticSound.class, this::play);
|
||||
}
|
||||
|
||||
public void play(ClientReceiveSoundEvent.EntitySound event) {
|
||||
AllMusicCore.chat();
|
||||
}
|
||||
|
||||
public void play(ClientReceiveSoundEvent.LocationalSound event) {
|
||||
AllMusicCore.chat();
|
||||
}
|
||||
|
||||
public void play(ClientReceiveSoundEvent.StaticSound event) {
|
||||
AllMusicCore.chat();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.coloryr.allmusic.comm;
|
||||
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.fml.ModContainer;
|
||||
import net.neoforged.fml.common.Mod;
|
||||
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
|
||||
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||
import net.neoforged.neoforge.network.handling.IPayloadHandler;
|
||||
|
||||
@Mod(AllMusicInit.MODID)
|
||||
public class AllMusicInit implements IPayloadHandler<MusicCodec> {
|
||||
|
||||
public static final String MODID = "allmusic";
|
||||
|
||||
public static IPayloadHandler<MusicCodec> handler;
|
||||
|
||||
public AllMusicInit(IEventBus modEventBus, ModContainer modContainer) {
|
||||
modEventBus.addListener(this::register);
|
||||
}
|
||||
|
||||
public void register(final RegisterPayloadHandlersEvent event) {
|
||||
event.registrar("1.0")
|
||||
.optional()
|
||||
.playToClient(MusicCodec.TYPE, MusicCodec.CODEC, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MusicCodec data, IPayloadContext iPayloadContext) {
|
||||
if (handler != null) {
|
||||
handler.handle(data, iPayloadContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.coloryr.allmusic.comm;
|
||||
|
||||
import com.coloryr.allmusic.codec.MusicPack;
|
||||
import com.coloryr.allmusic.codec.MusicPacketCodec;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
public record MusicCodec(MusicPack pack) implements CustomPacketPayload {
|
||||
public static final Identifier channel =
|
||||
Identifier.fromNamespaceAndPath("allmusic", "channel");
|
||||
|
||||
public static final Type<MusicCodec> TYPE = new Type<>(channel);
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, MusicCodec> CODEC = StreamCodec.of((buf, value) -> MusicPacketCodec.pack(buf, value.pack),
|
||||
(buf) -> {
|
||||
return new MusicCodec(MusicPacketCodec.decode(buf));
|
||||
});
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
modLoader = "javafml"
|
||||
loaderVersion = "[2,)"
|
||||
license = "GPL-3.0"
|
||||
issueTrackerURL = "https://github.com/Coloryr/AllMusic/issues"
|
||||
|
||||
[[mods]]
|
||||
modId = "allmusic"
|
||||
version = "${version}"
|
||||
displayName = "AllMusic"
|
||||
displayURL = "https://github.com/Coloryr/AllMusic/"
|
||||
logoFile = "icon.png"
|
||||
authors = "Color_yr"
|
||||
description = '''AllMusic Mod'''
|
||||
|
||||
[[mods]]
|
||||
modId = "allmusic_client"
|
||||
version = "${version}"
|
||||
displayName = "AllMusic Client"
|
||||
displayURL = "https://github.com/Coloryr/AllMusic/"
|
||||
logoFile = "icon.png"
|
||||
authors = "Color_yr"
|
||||
description = '''AllMusic Client Mod'''
|
||||
|
||||
[[dependencies.allmusic_client]]
|
||||
modId = "neoforge"
|
||||
type = "required"
|
||||
versionRange = "[26.3.0.6-beta,)"
|
||||
ordering = "NONE"
|
||||
side = "CLIENT"
|
||||
|
||||
[[dependencies.allmusic_client]]
|
||||
modId = "minecraft"
|
||||
type = "required"
|
||||
versionRange = "[26.3,)"
|
||||
ordering = "NONE"
|
||||
side = "CLIENT"
|
||||
|
||||
[[dependencies.allmusic_client]]
|
||||
modId="voicechat_api"
|
||||
type="optional"
|
||||
ordering="AFTER"
|
||||
side="CLIENT"
|
||||
|
After Width: | Height: | Size: 32 KiB |
@@ -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)
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "allmusic",
|
||||
"version": "${version}",
|
||||
"name": "AllMusic",
|
||||
"description": "AllMusic Mod",
|
||||
"authors": [
|
||||
"Color_yr"
|
||||
],
|
||||
"contact": {
|
||||
"homepage": "https://github.com/Coloryr/AllMusic",
|
||||
"sources": "https://github.com/Coloryr/AllMusic"
|
||||
},
|
||||
"license": "GPL-3.0",
|
||||
"icon": "assets/allmusic/icon.png",
|
||||
"environment": "*",
|
||||
"entrypoints": {
|
||||
"main": [
|
||||
"com.coloryr.allmusic.comm.AllMusicInit",
|
||||
"com.coloryr.allmusic.server.AllMusicServer"
|
||||
],
|
||||
"client": [
|
||||
"com.coloryr.allmusic.client.AllMusicClient"
|
||||
],
|
||||
"voicechat": [
|
||||
"com.coloryr.allmusic.client.others.VoiceChat"
|
||||
]
|
||||
},
|
||||
"mixins": [
|
||||
{
|
||||
"config": "allmusic_client.mixins.json",
|
||||
"environment": "client"
|
||||
},
|
||||
{
|
||||
"config": "allmusic_server.mixins.json",
|
||||
"environment": "server"
|
||||
}
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=0.19.3",
|
||||
"fabric-api": "*",
|
||||
"minecraft": ">=26.2",
|
||||
"adventure-platform-fabric": ">=7.0.0"
|
||||
},
|
||||
"suggests": {
|
||||
"modernui": "*",
|
||||
"voicechat_api": "*"
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
loom.platform=neoforge
|
||||
@@ -0,0 +1,71 @@
|
||||
modLoader = "javafml"
|
||||
loaderVersion = "[2,)"
|
||||
license = "GPL-3.0"
|
||||
issueTrackerURL = "https://github.com/Coloryr/AllMusic/issues/"
|
||||
|
||||
[[mods]]
|
||||
modId = "allmusic"
|
||||
version = "${version}"
|
||||
displayName = "AllMusic"
|
||||
displayURL = "https://github.com/Coloryr/AllMusic/"
|
||||
logoFile = "icon.png"
|
||||
authors = "Color_yr"
|
||||
description = '''AllMusic Mod'''
|
||||
|
||||
[[mods]]
|
||||
modId = "allmusic_client"
|
||||
version = "${version}"
|
||||
displayName = "AllMusic Client"
|
||||
displayURL = "https://github.com/Coloryr/AllMusic/"
|
||||
logoFile = "icon.png"
|
||||
authors = "Color_yr"
|
||||
description = '''AllMusic Client Mod'''
|
||||
|
||||
[[mods]]
|
||||
modId = "allmusic_server"
|
||||
version = "${version}"
|
||||
displayName = "AllMusic Server"
|
||||
displayURL = "https://github.com/Coloryr/AllMusic/"
|
||||
logoFile = "icon.png"
|
||||
authors = "Color_yr"
|
||||
description = '''AllMusic Server Mod'''
|
||||
|
||||
[[dependencies.allmusic_client]]
|
||||
modId = "neoforge"
|
||||
type = "required"
|
||||
versionRange = "[26.1.0.19-beta,)"
|
||||
ordering = "NONE"
|
||||
side = "CLIENT"
|
||||
|
||||
[[dependencies.allmusic_client]]
|
||||
modId = "minecraft"
|
||||
type = "required"
|
||||
versionRange = "[26.1,)"
|
||||
ordering = "NONE"
|
||||
side = "CLIENT"
|
||||
|
||||
[[dependencies.allmusic_client]]
|
||||
modId="modernui"
|
||||
type="optional"
|
||||
ordering="AFTER"
|
||||
side="CLIENT"
|
||||
|
||||
[[dependencies.allmusic_client]]
|
||||
modId="voicechat_api"
|
||||
type="optional"
|
||||
ordering="AFTER"
|
||||
side="CLIENT"
|
||||
|
||||
[[dependencies.allmusic_server]]
|
||||
modId = "neoforge"
|
||||
type = "required"
|
||||
versionRange = "[26.1.0.19-beta,)"
|
||||
ordering = "NONE"
|
||||
side = "BOTH"
|
||||
|
||||
[[dependencies.allmusic_server]]
|
||||
modId = "minecraft"
|
||||
type = "required"
|
||||
versionRange = "[26.1,)"
|
||||
ordering = "NONE"
|
||||
side = "BOTH"
|
||||
|
After Width: | Height: | Size: 32 KiB |
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
loom.platform=neoforge
|
||||
@@ -0,0 +1,65 @@
|
||||
modLoader = "javafml"
|
||||
loaderVersion = "[2,)"
|
||||
license = "GPL-3.0"
|
||||
issueTrackerURL = "https://github.com/Coloryr/AllMusic/issues/"
|
||||
|
||||
[[mods]]
|
||||
modId = "allmusic"
|
||||
version = "${version}"
|
||||
displayName = "AllMusic"
|
||||
displayURL = "https://github.com/Coloryr/AllMusic/"
|
||||
logoFile = "icon.png"
|
||||
authors = "Color_yr"
|
||||
description = '''AllMusic Mod'''
|
||||
|
||||
[[mods]]
|
||||
modId = "allmusic_client"
|
||||
version = "${version}"
|
||||
displayName = "AllMusic Client"
|
||||
displayURL = "https://github.com/Coloryr/AllMusic/"
|
||||
logoFile = "icon.png"
|
||||
authors = "Color_yr"
|
||||
description = '''AllMusic Client Mod'''
|
||||
|
||||
[[mods]]
|
||||
modId = "allmusic_server"
|
||||
version = "${version}"
|
||||
displayName = "AllMusic Server"
|
||||
displayURL = "https://github.com/Coloryr/AllMusic/"
|
||||
logoFile = "icon.png"
|
||||
authors = "Color_yr"
|
||||
description = '''AllMusic Server Mod'''
|
||||
|
||||
[[dependencies.allmusic_client]]
|
||||
modId = "neoforge"
|
||||
type = "required"
|
||||
versionRange = "[26.3.0.6-beta,)"
|
||||
ordering = "NONE"
|
||||
side = "CLIENT"
|
||||
|
||||
[[dependencies.allmusic_client]]
|
||||
modId = "minecraft"
|
||||
type = "required"
|
||||
versionRange = "[26.3,)"
|
||||
ordering = "NONE"
|
||||
side = "CLIENT"
|
||||
|
||||
[[dependencies.allmusic_client]]
|
||||
modId="voicechat_api"
|
||||
type="optional"
|
||||
ordering="AFTER"
|
||||
side="CLIENT"
|
||||
|
||||
[[dependencies.allmusic_server]]
|
||||
modId = "neoforge"
|
||||
type = "required"
|
||||
versionRange = "[26.3.0.6-beta,)"
|
||||
ordering = "NONE"
|
||||
side = "BOTH"
|
||||
|
||||
[[dependencies.allmusic_server]]
|
||||
modId = "minecraft"
|
||||
type = "required"
|
||||
versionRange = "[26.3,)"
|
||||
ordering = "NONE"
|
||||
side = "BOTH"
|
||||
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,37 @@
|
||||
plugins {
|
||||
id("net.fabricmc.fabric-loom") version Versions.fabricLoom
|
||||
}
|
||||
|
||||
java.sourceCompatibility = JavaVersion.VERSION_25
|
||||
java.targetCompatibility = JavaVersion.VERSION_25
|
||||
|
||||
dependencies {
|
||||
minecraft("com.mojang:minecraft:26.3")
|
||||
implementation("net.fabricmc:fabric-loader:0.19.5")
|
||||
|
||||
implementation("net.fabricmc.fabric-api:fabric-api:0.161.0+26.3")
|
||||
|
||||
implementation(include("net.kyori:adventure-platform-fabric:7.2.0")!!)
|
||||
}
|
||||
|
||||
tasks {
|
||||
processResources {
|
||||
filesMatching("fabric.mod.json") {
|
||||
expand(
|
||||
"version" to project.version
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// loom 的 jar-in-jar 嵌套默认只挂在 jar 任务上,需要手动接到 shadowJar
|
||||
loom.nestJars(named("shadowJar", Jar::class), configurations.getByName("include"))
|
||||
|
||||
shadowJar {
|
||||
archiveFileName.set("[fabric-26.3]AllMusic_Server-${project.version}.jar")
|
||||
destinationDirectory.set(file("${parent!!.projectDir}/../build"))
|
||||
}
|
||||
|
||||
build {
|
||||
dependsOn(shadowJar)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.coloryr.allmusic.comm;
|
||||
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
|
||||
|
||||
public class AllMusicInit implements ModInitializer {
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
PayloadTypeRegistry.clientboundPlay().register(MusicCodec.ID, MusicCodec.CODEC);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.coloryr.allmusic.comm;
|
||||
|
||||
import com.coloryr.allmusic.codec.MusicPack;
|
||||
import com.coloryr.allmusic.codec.MusicPacketCodec;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
|
||||
public record MusicCodec(MusicPack pack) implements CustomPacketPayload {
|
||||
public static final Identifier ChannelID = Identifier.fromNamespaceAndPath("allmusic", "channel");
|
||||
|
||||
public static final Type<MusicCodec> ID = new Type<>(ChannelID);
|
||||
public static final StreamCodec<FriendlyByteBuf, MusicCodec> CODEC =
|
||||
StreamCodec.of((buf, value) -> MusicPacketCodec.pack(buf, value.pack),
|
||||
buf -> new MusicCodec(MusicPacketCodec.decode(buf)));
|
||||
|
||||
@Override
|
||||
public @NonNull Type<? extends CustomPacketPayload> type() {
|
||||
return ID;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.coloryr.allmusic.server;
|
||||
|
||||
import com.coloryr.allmusic.server.core.AllMusic;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class AllMusicServer implements ModInitializer {
|
||||
public static MinecraftServer server;
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
AllMusic.log = new LogFabric();
|
||||
AllMusic.side = new SideFabric();
|
||||
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment)
|
||||
-> CommandFabric.instance.register(dispatcher));
|
||||
|
||||
ServerLifecycleEvents.SERVER_STARTED.register((a) -> {
|
||||
server = a;
|
||||
AllMusic.init(new File(AllMusic.SERVER_DIR));
|
||||
AllMusic.start();
|
||||
Tasks.init();
|
||||
});
|
||||
|
||||
ServerLifecycleEvents.SERVER_STOPPING.register((a) -> {
|
||||
AllMusic.stop();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.coloryr.allmusic.server;
|
||||
|
||||
import com.coloryr.allmusic.server.core.command.CommandEX;
|
||||
import com.mojang.brigadier.Command;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.suggestion.SuggestionProvider;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class CommandFabric implements Command<CommandSourceStack>, Predicate<CommandSourceStack>, SuggestionProvider<CommandSourceStack> {
|
||||
public static CommandFabric instance = new CommandFabric();
|
||||
|
||||
public void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
dispatcher.register(((Commands.literal("music").requires(this)).executes(this))
|
||||
.then(Commands.argument("args", StringArgumentType.greedyString())
|
||||
.suggests(this).executes(this)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int run(CommandContext<CommandSourceStack> context) {
|
||||
var item = context.getSource();
|
||||
|
||||
var input = context.getInput();
|
||||
var temp = input.split(" ");
|
||||
var arg = new String[temp.length - 1];
|
||||
System.arraycopy(temp, 1, arg, 0, arg.length);
|
||||
|
||||
CommandEX.execute(item, context.getSource().getTextName(), arg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(CommandSourceStack stack) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Suggestions> getSuggestions(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) {
|
||||
var item = context.getSource();
|
||||
|
||||
var input = context.getInput();
|
||||
var temp = input.split(" ");
|
||||
var arg = new String[input.endsWith(" ") ? temp.length : temp.length - 1];
|
||||
System.arraycopy(temp, 1, arg, 0, temp.length - 1);
|
||||
|
||||
builder = builder.createOffset(builder.getInput().lastIndexOf(32) + 1);
|
||||
|
||||
List<String> results = CommandEX.getTabList(item, item.getTextName(), arg);
|
||||
|
||||
String remaining = builder.getRemaining().trim();
|
||||
|
||||
for (String s : results) {
|
||||
if (s.startsWith(remaining)) {
|
||||
builder.suggest(s);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.buildFuture();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.coloryr.allmusic.server;
|
||||
|
||||
import com.coloryr.allmusic.server.core.side.IAllMusicLogger;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
|
||||
public class LogFabric implements IAllMusicLogger {
|
||||
@Override
|
||||
public void data(Component data) {
|
||||
AllMusicServer.server.sendMessage(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package com.coloryr.allmusic.server;
|
||||
|
||||
import com.coloryr.allmusic.codec.MusicPack;
|
||||
import com.coloryr.allmusic.comm.MusicCodec;
|
||||
import com.coloryr.allmusic.server.core.AllMusic;
|
||||
import com.coloryr.allmusic.server.core.objs.music.PlayerAddMusicObj;
|
||||
import com.coloryr.allmusic.server.core.objs.music.SongInfoObj;
|
||||
import com.coloryr.allmusic.server.core.side.BaseSide;
|
||||
import com.coloryr.allmusic.server.event.MusicAddEvent;
|
||||
import com.coloryr.allmusic.server.event.MusicPlayEvent;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.permissions.PermissionSet;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
|
||||
public class SideFabric extends BaseSide {
|
||||
@Override
|
||||
public void runTask(Runnable run) {
|
||||
AllMusicServer.server.execute(run);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runTask(Runnable run1, int delay) {
|
||||
Tasks.add(new TaskItem() {{
|
||||
tick = delay;
|
||||
run = run1;
|
||||
}});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkPermission(Object player) {
|
||||
CommandSourceStack source = (CommandSourceStack) player;
|
||||
return source.permissions() == PermissionSet.ALL_PERMISSIONS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayer(Object player) {
|
||||
CommandSourceStack source = (CommandSourceStack) player;
|
||||
return source.isPlayer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkPermission(Object player, String permission) {
|
||||
return checkPermission(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needPlay(boolean islist) {
|
||||
for (var player : AllMusicServer.server.getPlayerList().getPlayers()) {
|
||||
if (!com.coloryr.allmusic.server.core.AllMusic.isSkip(player.getName().getString(), null, false, islist)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<?> getPlayers() {
|
||||
return AllMusicServer.server.getPlayerList().getPlayers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPlayerName(Object player) {
|
||||
if (player instanceof ServerPlayer player1) {
|
||||
return player1.getName().getString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPlayerServer(Object player) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Object player, MusicPack pack) {
|
||||
if (player instanceof ServerPlayer player1) {
|
||||
send(player1, pack);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPlayer(String player) {
|
||||
return AllMusicServer.server.getPlayerList().getPlayer(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendBar(Object player, net.kyori.adventure.text.Component data) {
|
||||
if (player instanceof ServerPlayer player1) {
|
||||
player1.sendActionBar(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFolder() {
|
||||
return new File(AllMusic.SERVER_DIR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void broadcast(net.kyori.adventure.text.Component message) {
|
||||
for (var player : AllMusicServer.server.getPlayerList().getPlayers()) {
|
||||
if (!AllMusic.isSkip(player.getName().getString(), null, false)) {
|
||||
player.sendMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(Object obj, net.kyori.adventure.text.Component message) {
|
||||
if (obj instanceof CommandSourceStack source) {
|
||||
source.sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMusicPlay(SongInfoObj obj) {
|
||||
return MusicPlayEvent.EVENT.invoker().interact(obj) != InteractionResult.PASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMusicAdd(Object obj, PlayerAddMusicObj music) {
|
||||
CommandSourceStack source = (CommandSourceStack) obj;
|
||||
return MusicAddEvent.EVENT.invoker().interact(source.getPlayer(), music) != InteractionResult.PASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component miniMessage(String input) {
|
||||
MiniMessage mm = MiniMessage.miniMessage();
|
||||
return mm.deserialize(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component miniMessageRun(String input, String command) {
|
||||
Component component = miniMessage(input);
|
||||
return component.clickEvent(ClickEvent.runCommand(command));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component miniMessageSuggest(String input, String command) {
|
||||
Component component = miniMessage(input);
|
||||
return component.clickEvent(ClickEvent.suggestCommand(command));
|
||||
}
|
||||
|
||||
private void send(ServerPlayer players, MusicPack data) {
|
||||
if (players == null)
|
||||
return;
|
||||
runTask(() -> ServerPlayNetworking.send(players, new MusicCodec(data)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.coloryr.allmusic.server;
|
||||
|
||||
public class TaskItem {
|
||||
public int tick;
|
||||
public Runnable run;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.coloryr.allmusic.server;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public class Tasks {
|
||||
private static final List<TaskItem> taskItems = new CopyOnWriteArrayList<>();
|
||||
|
||||
public static void init() {
|
||||
AllMusicServer.server.addTickable(Tasks::tick);
|
||||
}
|
||||
|
||||
public static void tick() {
|
||||
for (var item : new ArrayList<>(taskItems)) {
|
||||
item.tick--;
|
||||
if (item.tick <= 0) {
|
||||
taskItems.remove(item);
|
||||
item.run.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void add(TaskItem item) {
|
||||
taskItems.add(item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.coloryr.allmusic.server.event;
|
||||
|
||||
import com.coloryr.allmusic.server.core.objs.music.PlayerAddMusicObj;
|
||||
import net.fabricmc.fabric.api.event.Event;
|
||||
import net.fabricmc.fabric.api.event.EventFactory;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
|
||||
public interface MusicAddEvent {
|
||||
Event<MusicAddEvent> EVENT = EventFactory.createArrayBacked(MusicAddEvent.class,
|
||||
(listeners) -> (player, music) -> {
|
||||
for (MusicAddEvent listener : listeners) {
|
||||
InteractionResult result = listener.interact(player, music);
|
||||
|
||||
if (result != InteractionResult.PASS) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return InteractionResult.PASS;
|
||||
});
|
||||
|
||||
InteractionResult interact(ServerPlayer player, PlayerAddMusicObj music);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.coloryr.allmusic.server.event;
|
||||
|
||||
import com.coloryr.allmusic.server.core.objs.music.SongInfoObj;
|
||||
import net.fabricmc.fabric.api.event.Event;
|
||||
import net.fabricmc.fabric.api.event.EventFactory;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
|
||||
public interface MusicPlayEvent {
|
||||
Event<MusicPlayEvent> EVENT = EventFactory.createArrayBacked(MusicPlayEvent.class,
|
||||
(listeners) -> (music) -> {
|
||||
for (MusicPlayEvent listener : listeners) {
|
||||
InteractionResult result = listener.interact(music);
|
||||
|
||||
if (result != InteractionResult.PASS) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return InteractionResult.PASS;
|
||||
});
|
||||
|
||||
/**
|
||||
* 音乐播放事件,返回PASS表示通过
|
||||
*
|
||||
* @param music 歌曲信息
|
||||
* @return 是否通过
|
||||
*/
|
||||
InteractionResult interact(SongInfoObj music);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.coloryr.allmusic.server.mixin;
|
||||
|
||||
import com.coloryr.allmusic.server.core.AllMusic;
|
||||
import com.coloryr.allmusic.server.core.music.PlayMusic;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.network.CommonListenerCookie;
|
||||
import net.minecraft.server.players.PlayerList;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(PlayerList.class)
|
||||
public class ServerPlayerMixin {
|
||||
@Inject(at = @At("HEAD"), method = "remove")
|
||||
private void remove(ServerPlayer entity, CallbackInfo info) {
|
||||
PlayMusic.removeNowPlayPlayer(entity.getName().getString());
|
||||
}
|
||||
|
||||
@Inject(at = @At("TAIL"), method = "placeNewPlayer")
|
||||
private void add(Connection connection, ServerPlayer player, CommonListenerCookie clientData, CallbackInfo ci) {
|
||||
AllMusic.joinPlay(player.getName().getString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"required": true,
|
||||
"minVersion": "0.8",
|
||||
"package": "com.coloryr.allmusic.server.mixin",
|
||||
"compatibilityLevel": "JAVA_25",
|
||||
"server": [
|
||||
],
|
||||
"mixins": [
|
||||
"ServerPlayerMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
},
|
||||
"overwrites": {
|
||||
"requireAnnotations": true
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "allmusic_server",
|
||||
"version": "${version}",
|
||||
"name": "AllMusic Server",
|
||||
"description": "AllMusic Server Mod",
|
||||
"authors": [
|
||||
"Color_yr"
|
||||
],
|
||||
"contact": {
|
||||
"homepage": "https://github.com/Coloryr/AllMusic",
|
||||
"sources": "https://github.com/Coloryr/AllMusic"
|
||||
},
|
||||
"license": "GPL-3.0",
|
||||
"icon": "assets/allmusic_server/icon.png",
|
||||
"environment": "*",
|
||||
"entrypoints": {
|
||||
"main": [
|
||||
"com.coloryr.allmusic.comm.AllMusicInit",
|
||||
"com.coloryr.allmusic.server.AllMusicServer"
|
||||
]
|
||||
},
|
||||
"mixins": [
|
||||
{
|
||||
"config": "allmusic_server.mixins.json",
|
||||
"environment": "server"
|
||||
}
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=0.19.5",
|
||||
"minecraft": ">=26.3",
|
||||
"fabric-api": "*",
|
||||
"adventure-platform-fabric": ">=7.2.0"
|
||||
},
|
||||
"suggests": {
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
loom.platform=neoforge
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.coloryr.allmusic.comm;
|
||||
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.fml.ModContainer;
|
||||
import net.neoforged.fml.common.Mod;
|
||||
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
|
||||
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||
import net.neoforged.neoforge.network.handling.IPayloadHandler;
|
||||
|
||||
@Mod(AllMusicInit.MODID)
|
||||
public class AllMusicInit implements IPayloadHandler<MusicCodec> {
|
||||
|
||||
public static final String MODID = "allmusic";
|
||||
|
||||
public static IPayloadHandler<MusicCodec> handler;
|
||||
|
||||
public AllMusicInit(IEventBus modEventBus, ModContainer modContainer) {
|
||||
modEventBus.addListener(this::register);
|
||||
}
|
||||
|
||||
public void register(final RegisterPayloadHandlersEvent event) {
|
||||
event.registrar("1.0")
|
||||
.optional()
|
||||
.playToClient(MusicCodec.TYPE, MusicCodec.CODEC, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MusicCodec data, IPayloadContext iPayloadContext) {
|
||||
if (handler != null) {
|
||||
handler.handle(data, iPayloadContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.coloryr.allmusic.comm;
|
||||
|
||||
import com.coloryr.allmusic.codec.MusicPack;
|
||||
import com.coloryr.allmusic.codec.MusicPacketCodec;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
public record MusicCodec(MusicPack pack) implements CustomPacketPayload {
|
||||
public static final Identifier channel =
|
||||
Identifier.fromNamespaceAndPath("allmusic", "channel");
|
||||
|
||||
public static final Type<MusicCodec> TYPE = new Type<>(channel);
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, MusicCodec> CODEC = StreamCodec.of((buf, value) -> MusicPacketCodec.pack(buf, value.pack),
|
||||
(buf) -> {
|
||||
return new MusicCodec(MusicPacketCodec.decode(buf));
|
||||
});
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.coloryr.allmusic.server;
|
||||
|
||||
import com.coloryr.allmusic.server.core.AllMusic;
|
||||
import com.coloryr.allmusic.server.core.music.PlayMusic;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import net.kyori.adventure.platform.modcommon.MinecraftServerAudiences;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
import net.neoforged.neoforge.event.RegisterCommandsEvent;
|
||||
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
|
||||
import net.neoforged.neoforge.event.server.ServerStartedEvent;
|
||||
import net.neoforged.neoforge.event.server.ServerStoppingEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@EventBusSubscriber(modid = "allmusic_server", value = Dist.DEDICATED_SERVER)
|
||||
public class AllMusicServer {
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger("AllMusic Server");
|
||||
public static MinecraftServer server;
|
||||
public static MinecraftServerAudiences audiences;
|
||||
|
||||
@SubscribeEvent
|
||||
public static void commonSetup(final FMLCommonSetupEvent event) {
|
||||
AllMusic.log = new LogNeoForge();
|
||||
AllMusic.side = new SideNeoForge();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRegisterCommands(RegisterCommandsEvent event) {
|
||||
if (event.getCommandSelection() != Commands.CommandSelection.INTEGRATED) {
|
||||
CommandDispatcher<CommandSourceStack> dispatcher = event.getDispatcher();
|
||||
CommandNeoForge.instance.register(dispatcher);
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onServerStarting(ServerStartedEvent event) {
|
||||
if (event.getServer().isDedicatedServer()) {
|
||||
audiences = MinecraftServerAudiences.of(event.getServer());
|
||||
server = event.getServer();
|
||||
AllMusic.init(new File(AllMusic.SERVER_DIR));
|
||||
AllMusic.start();
|
||||
Tasks.init();
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onServerStopping(ServerStoppingEvent event) {
|
||||
if (event.getServer().isDedicatedServer()) {
|
||||
AllMusic.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onPlayerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) {
|
||||
if (server != null) {
|
||||
PlayMusic.removeNowPlayPlayer(event.getEntity().getName().getString());
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) {
|
||||
if (server != null) {
|
||||
AllMusic.joinPlay(event.getEntity().getName().getString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.coloryr.allmusic.server;
|
||||
|
||||
import com.coloryr.allmusic.server.core.command.CommandEX;
|
||||
import com.mojang.brigadier.Command;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.suggestion.SuggestionProvider;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class CommandNeoForge implements Command<CommandSourceStack>, Predicate<CommandSourceStack>, SuggestionProvider<CommandSourceStack> {
|
||||
public static CommandNeoForge instance = new CommandNeoForge();
|
||||
|
||||
public void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
dispatcher.register(((Commands.literal("music").requires(this)).executes(this))
|
||||
.then(Commands.argument("args", StringArgumentType.greedyString())
|
||||
.suggests(this).executes(this)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int run(CommandContext<CommandSourceStack> context) {
|
||||
var item = context.getSource();
|
||||
|
||||
var input = context.getInput();
|
||||
var temp = input.split(" ");
|
||||
var arg = new String[temp.length - 1];
|
||||
System.arraycopy(temp, 1, arg, 0, arg.length);
|
||||
|
||||
CommandEX.execute(item, context.getSource().getTextName(), arg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(CommandSourceStack stack) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Suggestions> getSuggestions(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) {
|
||||
var item = context.getSource();
|
||||
|
||||
var input = context.getInput();
|
||||
var temp = input.split(" ");
|
||||
var arg = new String[input.endsWith(" ") ? temp.length : temp.length - 1];
|
||||
System.arraycopy(temp, 1, arg, 0, temp.length - 1);
|
||||
|
||||
if (input.endsWith(" "))
|
||||
builder = builder.createOffset(builder.getInput().lastIndexOf(32) + 1);
|
||||
|
||||
List<String> results = CommandEX.getTabList(item, item.getTextName(), arg);
|
||||
|
||||
for (String s : results) {
|
||||
builder.suggest(s);
|
||||
}
|
||||
|
||||
return builder.buildFuture();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.coloryr.allmusic.server;
|
||||
|
||||
import com.coloryr.allmusic.server.core.side.IAllMusicLogger;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
public class LogNeoForge implements IAllMusicLogger {
|
||||
@Override
|
||||
public void data(Component data) {
|
||||
AllMusicServer.audiences.console().sendMessage(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package com.coloryr.allmusic.server;
|
||||
|
||||
import com.coloryr.allmusic.codec.MusicPack;
|
||||
import com.coloryr.allmusic.comm.MusicCodec;
|
||||
import com.coloryr.allmusic.server.core.AllMusic;
|
||||
import com.coloryr.allmusic.server.core.objs.music.PlayerAddMusicObj;
|
||||
import com.coloryr.allmusic.server.core.objs.music.SongInfoObj;
|
||||
import com.coloryr.allmusic.server.core.side.BaseSide;
|
||||
import com.coloryr.allmusic.server.event.MusicAddEvent;
|
||||
import com.coloryr.allmusic.server.event.MusicPlayEvent;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.permissions.PermissionSet;
|
||||
import net.neoforged.neoforge.common.NeoForge;
|
||||
import net.neoforged.neoforge.network.PacketDistributor;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
|
||||
public class SideNeoForge extends BaseSide {
|
||||
@Override
|
||||
public void runTask(Runnable run) {
|
||||
AllMusicServer.server.execute(run);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runTask(Runnable run1, int delay) {
|
||||
Tasks.add(new TaskItem() {{
|
||||
tick = delay;
|
||||
run = run1;
|
||||
}});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkPermission(Object player, String permission) {
|
||||
return checkPermission(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkPermission(Object player) {
|
||||
CommandSourceStack source = (CommandSourceStack) player;
|
||||
return source.permissions() == PermissionSet.ALL_PERMISSIONS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayer(Object player) {
|
||||
CommandSourceStack source = (CommandSourceStack) player;
|
||||
return source.isPlayer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needPlay(boolean islist) {
|
||||
for (ServerPlayer player : AllMusicServer.server.getPlayerList().getPlayers()) {
|
||||
if (!AllMusic.isSkip(player.getName().getString(), null, false, islist)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<?> getPlayers() {
|
||||
return AllMusicServer.server.getPlayerList().getPlayers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPlayerName(Object player) {
|
||||
if (player instanceof ServerPlayer player1) {
|
||||
return player1.getName().getString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPlayerServer(Object player) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Object player, MusicPack pack) {
|
||||
if (player instanceof ServerPlayer player1) {
|
||||
send(player1, pack);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPlayer(String player) {
|
||||
return AllMusicServer.server.getPlayerList().getPlayerByName(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendBar(Object player, Component data) {
|
||||
if (player instanceof ServerPlayer player1) {
|
||||
AllMusicServer.audiences.audience(player1).sendActionBar(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFolder() {
|
||||
return new File(AllMusic.SERVER_DIR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void broadcast(Component message) {
|
||||
for (ServerPlayer player : AllMusicServer.server.getPlayerList().getPlayers()) {
|
||||
if (!AllMusic.isSkip(player.getName().getString(), null, false)) {
|
||||
AllMusicServer.audiences.audience(player).sendActionBar(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(Object obj, Component message) {
|
||||
if (obj instanceof CommandSourceStack source) {
|
||||
AllMusicServer.audiences.audience(source).sendActionBar(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMusicPlay(SongInfoObj obj) {
|
||||
MusicPlayEvent event = new MusicPlayEvent(obj);
|
||||
NeoForge.EVENT_BUS.post(event);
|
||||
return event.isCancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMusicAdd(Object obj, PlayerAddMusicObj music) {
|
||||
CommandSourceStack source = (CommandSourceStack) obj;
|
||||
MusicAddEvent event = new MusicAddEvent(music, source.getPlayer());
|
||||
NeoForge.EVENT_BUS.post(event);
|
||||
return event.isCancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component miniMessage(String input) {
|
||||
MiniMessage mm = MiniMessage.miniMessage();
|
||||
return mm.deserialize(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component miniMessageRun(String input, String command) {
|
||||
Component component = miniMessage(input);
|
||||
return component.clickEvent(ClickEvent.runCommand(command));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component miniMessageSuggest(String input, String command) {
|
||||
Component component = miniMessage(input);
|
||||
return component.clickEvent(ClickEvent.suggestCommand(command));
|
||||
}
|
||||
|
||||
private void send(ServerPlayer players, MusicPack data) {
|
||||
if (players == null)
|
||||
return;
|
||||
runTask(() -> PacketDistributor.sendToPlayer(players, new MusicCodec(data)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.coloryr.allmusic.server;
|
||||
|
||||
public class TaskItem {
|
||||
public int tick;
|
||||
public Runnable run;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.coloryr.allmusic.server;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public class Tasks {
|
||||
private static final List<TaskItem> taskItems = new CopyOnWriteArrayList<>();
|
||||
|
||||
public static void init() {
|
||||
AllMusicServer.server.addTickable(Tasks::tick);
|
||||
}
|
||||
|
||||
public static void tick() {
|
||||
for (TaskItem item : new ArrayList<>(taskItems)) {
|
||||
item.tick--;
|
||||
if (item.tick <= 0) {
|
||||
taskItems.remove(item);
|
||||
item.run.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void add(TaskItem item) {
|
||||
taskItems.add(item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.coloryr.allmusic.server.event;
|
||||
|
||||
import com.coloryr.allmusic.server.core.objs.music.PlayerAddMusicObj;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.neoforged.bus.api.Event;
|
||||
|
||||
public class MusicAddEvent extends Event {
|
||||
/**
|
||||
* 添加的音乐
|
||||
*/
|
||||
private final PlayerAddMusicObj music;
|
||||
/**
|
||||
* 添加者
|
||||
*/
|
||||
private final ServerPlayer player;
|
||||
|
||||
private boolean cancel;
|
||||
|
||||
public MusicAddEvent(PlayerAddMusicObj id, ServerPlayer player) {
|
||||
this.music = id;
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
public boolean isCancel() {
|
||||
return cancel;
|
||||
}
|
||||
|
||||
public void setCancel(boolean cancel) {
|
||||
this.cancel = cancel;
|
||||
}
|
||||
|
||||
public ServerPlayer getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
public PlayerAddMusicObj getMusic() {
|
||||
return music;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.coloryr.allmusic.server.event;
|
||||
|
||||
import com.coloryr.allmusic.server.core.objs.music.SongInfoObj;
|
||||
import net.neoforged.bus.api.Event;
|
||||
|
||||
public class MusicPlayEvent extends Event {
|
||||
private final SongInfoObj music;
|
||||
private boolean cancel;
|
||||
|
||||
public MusicPlayEvent(SongInfoObj music) {
|
||||
this.music = music;
|
||||
}
|
||||
|
||||
public boolean isCancel() {
|
||||
return cancel;
|
||||
}
|
||||
|
||||
public void setCancel(boolean cancel) {
|
||||
this.cancel = cancel;
|
||||
}
|
||||
|
||||
public SongInfoObj getMusic() {
|
||||
return music;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
modLoader = "javafml"
|
||||
loaderVersion = "[2,)"
|
||||
license = "GPL-3.0"
|
||||
issueTrackerURL = "https://github.com/Coloryr/AllMusic/issues"
|
||||
|
||||
[[mods]]
|
||||
modId = "allmusic"
|
||||
version = "${version}"
|
||||
displayName = "AllMusic"
|
||||
displayURL = "https://github.com/Coloryr/AllMusic/"
|
||||
logoFile = "icon.png"
|
||||
authors = "Color_yr"
|
||||
description = '''AllMusic Mod'''
|
||||
|
||||
[[mods]]
|
||||
modId = "allmusic_server"
|
||||
version = "${version}"
|
||||
displayName = "AllMusic Server"
|
||||
displayURL = "https://github.com/Coloryr/AllMusic/"
|
||||
logoFile = "icon.png"
|
||||
authors = "Color_yr"
|
||||
description = '''AllMusic Server Mod'''
|
||||
|
||||
[[dependencies.allmusic_server]]
|
||||
modId = "neoforge"
|
||||
type = "required"
|
||||
versionRange = "[26.3.0.6-beta,)"
|
||||
ordering = "NONE"
|
||||
side = "BOTH"
|
||||
|
||||
[[dependencies.allmusic_server]]
|
||||
modId = "minecraft"
|
||||
type = "required"
|
||||
versionRange = "[26.3,)"
|
||||
ordering = "NONE"
|
||||
side = "BOTH"
|
||||
|
After Width: | Height: | Size: 32 KiB |
@@ -482,7 +482,7 @@ public class PlayMusic {
|
||||
*/
|
||||
private static boolean checkDeep(MusicObj music) {
|
||||
for (MusicObj obj : deep) {
|
||||
if (Objects.equals(obj.id, music.id) && obj.api == music.api) {
|
||||
if (Objects.equals(obj.id, music.id) && Objects.equals(obj.api, music.api)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||