diff --git a/client/fabric_1_21_8/build.gradle.kts b/client/fabric_1_21_8/build.gradle.kts new file mode 100644 index 00000000..df6e2d80 --- /dev/null +++ b/client/fabric_1_21_8/build.gradle.kts @@ -0,0 +1,35 @@ +plugins { + id("fabric-loom") version Versions.fabricLoom +} + +java.sourceCompatibility = JavaVersion.VERSION_21 +java.targetCompatibility = JavaVersion.VERSION_21 + +dependencies { + minecraft("com.mojang:minecraft:1.21.8") + mappings(loom.officialMojangMappings()) + modImplementation("net.fabricmc:fabric-loader:0.19.3") + + modImplementation("net.fabricmc.fabric-api:fabric-api:0.136.1+1.21.8") +} + +tasks { + processResources { + filesMatching("fabric.mod.json") { + expand( + "version" to project.version + ) + } + } + + remapJar { + inputFile.set(shadowJar.get().archiveFile) + + archiveFileName.set("[fabric-1.21.6]AllMusic_Client-${project.version}.jar") + destinationDirectory.set(file("${parent!!.projectDir}/../build")) + } + + build { + dependsOn(remapJar) + } +} diff --git a/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java new file mode 100644 index 00000000..c6f556f6 --- /dev/null +++ b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/AllMusicClient.java @@ -0,0 +1,121 @@ +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.networking.v1.ClientPlayNetworking; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.sounds.SoundSource; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +public class AllMusicClient implements ClientModInitializer, AllMusicBridge { + public static final String MODID = "allmusic_client"; + + public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath("allmusic", "channel"); + public static final Logger LOGGER = LogManager.getLogger("AllMusic Client"); + public static GuiGraphics context; + + public static void update(GuiGraphics draw) { + context = draw; + AllMusicCore.hudUpdate(); + } + + public int getScreenWidth() { + return Minecraft.getInstance().getWindow().getGuiScaledWidth(); + } + + public int getScreenHeight() { + return Minecraft.getInstance().getWindow().getGuiScaledHeight(); + } + + public int getTextWidth(String item) { + return Minecraft.getInstance().font.width(item); + } + + public int getFontHeight() { + return Minecraft.getInstance().font.lineHeight; + } + + public void sendMessage(String data) { + data = "[AllMusic Client]" + data; + LOGGER.warn(data); + String finalData = data; + Minecraft.getInstance().execute(() -> { + if (Minecraft.getInstance().player == null) + return; + Minecraft.getInstance().player.displayClientMessage(Component.literal(finalData), false); + }); + } + + public float getVolume() { + return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS); + } + + @Override + public void stopPlayMusic() { + Minecraft.getInstance().getSoundManager().stop(null, SoundSource.MUSIC); + Minecraft.getInstance().getSoundManager().stop(null, SoundSource.RECORDS); + } + + @Override + public TextFrameBuffer makeTextRender(String name) { + return new CoreRenderTarget(name); + } + + @Override + public TextureRender makeTextureRender(String file) { + return new TexRender(file); + } + + @Override + public PictureFrameBuffer makePictureRender(int size) { + return new PicRender(size); + } + + @Override + public String readText(String file) { + try { + Resource resource = Minecraft.getInstance().getResourceManager().getResource(ResourceLocation.fromNamespaceAndPath(MODID, file)).orElseThrow(); + try (InputStream inputStream = resource.open()) { + byte[] bytes = inputStream.readAllBytes(); + return new String(bytes, StandardCharsets.UTF_8); + } + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + @Override + public InputStream readFile(String file) { + try { + Resource resource = Minecraft.getInstance().getResourceManager().getResource(ResourceLocation.fromNamespaceAndPath(MODID, file)).orElseThrow(); + return resource.open(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + @Override + public void onInitializeClient() { + ClientPlayNetworking.registerGlobalReceiver(MusicCodec.ID, (pack, handler) -> { + AllMusicCore.packDo(pack.pack()); + }); + + AllMusicCore.init(FabricLoader.getInstance().getConfigDir(), this); + } +} diff --git a/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/CoreRenderTarget.java b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/CoreRenderTarget.java new file mode 100644 index 00000000..df88968d --- /dev/null +++ b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/CoreRenderTarget.java @@ -0,0 +1,490 @@ +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 com.mojang.blaze3d.ProjectionType; +import com.mojang.blaze3d.buffers.GpuBuffer; +import com.mojang.blaze3d.buffers.GpuBufferSlice; +import com.mojang.blaze3d.pipeline.RenderPipeline; +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.pipeline.TextureTarget; +import com.mojang.blaze3d.platform.Window; +import com.mojang.blaze3d.systems.RenderPass; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.ByteBufferBuilder; +import com.mojang.blaze3d.vertex.MeshData; +import com.mojang.blaze3d.vertex.VertexFormat; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.font.glyphs.BakedGlyph; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.client.gui.render.TextureSetup; +import net.minecraft.client.gui.render.state.GlyphEffectRenderState; +import net.minecraft.client.gui.render.state.GlyphRenderState; +import net.minecraft.client.gui.render.state.GuiElementRenderState; +import net.minecraft.client.gui.render.state.GuiRenderState; +import net.minecraft.client.renderer.CachedOrthoProjectionMatrixBuffer; +import net.minecraft.client.renderer.MappableRingBuffer; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.client.renderer.fog.FogRenderer; +import net.minecraft.network.chat.Component; +import org.joml.Matrix3x2f; +import org.joml.Matrix4f; +import org.joml.Vector3f; +import org.joml.Vector4f; +import org.lwjgl.system.MemoryUtil; + +import java.nio.ByteBuffer; +import java.util.*; +import java.util.function.Supplier; + +public class CoreRenderTarget extends TextFrameBuffer { + private final RenderTarget target; + private final String name; + + private final GuiRenderer renderer; + private final FogRenderer fogRenderer = new FogRenderer(); + private final GuiRenderState renderState = new GuiRenderState(); + + public CoreRenderTarget(String name) { + this.name = name; + target = new TextureTarget(name, 800, 200, false); + renderer = new GuiRenderer(renderState); + } + + @Override + public void resize(int width, int height) { + Window window = Minecraft.getInstance().getWindow(); + + nowWidth = width * window.getGuiScale(); + nowHeight = height * window.getGuiScale(); + + if (nowWidth > target.width || nowHeight > target.height) { + target.resize(nowWidth, nowHeight); + } + } + + @Override + public void use() { + isDraw = true; + + clear(); + RenderSystem.getDevice().createCommandEncoder().clearColorTexture(target.getColorTexture(), 0); + } + + @Override + public void unUse() { + renderer.render(fogRenderer.getBuffer(FogRenderer.FogMode.NONE), this); + isDraw = false; + } + + @Override + public void drawText(String text, int y, int color, boolean shadow) { + Window window = Minecraft.getInstance().getWindow(); + + var font = Minecraft.getInstance().font; + Component component = MiniMessage.parse(text); + int width = font.width(component.getVisualOrderText()); + + if (width == 0) { + return; + } + + GuiGraphics graphics = new GuiGraphics(Minecraft.getInstance(), renderState); + color = color | 0xFF000000; + graphics.drawString(font, component, 0, y, color, shadow); + + TextItem item = new TextItem(width, font.lineHeight + (shadow ? 1 : 0), y, (float) window.getGuiScale()); + texts.add(item); + } + + /** + * 渲染贴图的一部分到屏幕指定位置 + * + * @param alpha 透明度 + * @param x 屏幕X坐标(左上角) + * @param y 屏幕Y坐标(左上角) + * @param width 需要渲染的宽度 + * @param height 需要渲染的高度 + * @param texX 贴图左上角X坐标 + * @param texY 贴图左上角Y坐标 + * @param scale 贴图缩放 + */ + private void draw(float alpha, float x, float y, float width, float height, float texX, float texY, float scale) { + float w = width / 2; + float h = height / 2; + + Matrix3x2f matrix = new Matrix3x2f().translation(x + w, y + h); + + float x0 = -w; + float x1 = w; + float y0 = -h; + float y1 = h; + + // 计算贴图区域UV + float u0 = texX * scale / target.width; + float v0 = 1 - (texY * scale / target.height); + float u1 = (texX + width) * scale / target.width; + float v1 = 1 - ((texY + height) * scale / target.height); + + int color = 0xFFFFFF00 + (int) (255 * alpha); + + AllMusicClient.context.guiRenderState.submitGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED, + TextureSetup.singleTexture(target.getColorTextureView()), matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.scissorStack.peek())); + } + + public void drawLoop(float alpha, float x, float y, + float texX, float texY, + float textWidth, float textHeight, + int maxWidth, float offsetX, float scale) { + + // 如果宽度不大于最大宽度,直接全部渲染 + if (maxWidth == -1 || textWidth <= maxWidth) { + draw(alpha, x, y, textWidth, textHeight, texX, texY, scale); + return; + } + + if (textWidth - offsetX < maxWidth) { + float nowWith = textWidth - offsetX; + draw(alpha, x, y, nowWith, textHeight, offsetX, texY, scale); + draw(alpha, x + nowWith, y, maxWidth - nowWith, textHeight, 0, texY, scale); + } else { + draw(alpha, x, y, maxWidth, textHeight, offsetX, texY, scale); + } + } + + /** + * 居中百分比显示(根据百分比选择显示贴图的中间部分) + * + * @param alpha 透明度 + * @param startX 起始X坐标(屏幕左上角) + * @param startY 起始Y坐标(屏幕左上角) + * @param texX 贴图左上角X + * @param texY 贴图左上角Y + * @param texWidth 贴图总宽度 + * @param texHeight 贴图高度 + * @param maxWidth 最大渲染宽度 + * @param percent 百分比(0.0-1.0),0%显示左边,100%显示右边 + */ + public void drawByPercent(float alpha, float startX, float startY, + float texX, float texY, + float texWidth, float texHeight, + int maxWidth, float percent, float scale) { + + // 限制百分比范围 + percent = Math.clamp(percent, 0.0f, 1.0f); + + // 如果贴图宽度小于等于最大宽度,直接全部显示 + if (texWidth <= maxWidth) { + draw(alpha, startX, startY, texWidth * percent, texHeight, texX, texY, scale); + return; + } + + // 计算贴图的起始位置(根据百分比) + float maxOffset = texWidth - maxWidth; + float texOffset = maxOffset * percent; + + // 渲染 + draw(alpha, startX, startY, maxWidth, texHeight, texX + texOffset, texY, scale); + } + + @Override + public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) { + for (var item : texts) { + Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir); + + drawLoop(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, getOffset(item) % item.textWidth, item.scale); + } + } + + @Override + public void drawLine(float x, float y, float alpha, int line) { + if (line >= texts.size()) { + return; + } + TextItem item = texts.get(line); + draw(alpha, x, y, item.textWidth, item.textHeight, 0, item.y, item.scale); + } + + @Override + public Point2f getLine(int line) { + if (line >= texts.size()) { + return new Point2f(0, 0); + } + TextItem item = texts.get(line); + return new Point2f(item.textWidth, item.textHeight); + } + + + @Override + public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) { + for (var item : texts) { + Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir); + + drawByPercent(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, state, item.scale); + } + } + + public static class GuiRenderer implements AutoCloseable { + private static final Comparator SCISSOR_COMPARATOR = Comparator.nullsFirst( + Comparator.comparing(ScreenRectangle::top) + .thenComparing(ScreenRectangle::bottom) + .thenComparing(ScreenRectangle::left) + .thenComparing(ScreenRectangle::right) + ); + private static final Comparator TEXTURE_COMPARATOR = Comparator.nullsFirst(Comparator.comparing(TextureSetup::getSortKey)); + private static final Comparator ELEMENT_SORT_COMPARATOR = Comparator.comparing(GuiElementRenderState::scissorArea, SCISSOR_COMPARATOR) + .thenComparing(GuiElementRenderState::pipeline, Comparator.comparing(RenderPipeline::getSortKey)) + .thenComparing(GuiElementRenderState::textureSetup, TEXTURE_COMPARATOR); + private final GuiRenderState renderState; + private final List draws = new ArrayList<>(); + private final List meshesToDraw = new ArrayList<>(); + private final ByteBufferBuilder byteBufferBuilder = new ByteBufferBuilder(786432); + private final Map vertexBuffers = new Object2ObjectOpenHashMap<>(); + private final CachedOrthoProjectionMatrixBuffer guiProjectionMatrixBuffer = new CachedOrthoProjectionMatrixBuffer("gui", 1000.0F, 11000.0F, true); + private ScreenRectangle previousScissorArea; + private RenderPipeline previousPipeline; + private TextureSetup previousTextureSetup; + private BufferBuilder bufferBuilder; + + public GuiRenderer(GuiRenderState state) { + renderState = state; + } + + public void render(GpuBufferSlice gpu, CoreRenderTarget target) { + renderState.forEachText(state -> { + var matrix3x2f = state.pose; + var screenrectangle = state.scissor; + state.ensurePrepared().visit(new Font.GlyphVisitor() { + @Override + public void acceptGlyph(BakedGlyph.GlyphInstance glyph) { + if (glyph.glyph().textureView() != null) { + renderState.submitGlyphToCurrentLayer(new GlyphRenderState(matrix3x2f, glyph, screenrectangle)); + } + } + + @Override + public void acceptEffect(BakedGlyph glyph, BakedGlyph.Effect effect) { + if (glyph.textureView() != null) { + renderState.submitGlyphToCurrentLayer(new GlyphEffectRenderState(matrix3x2f, glyph, effect, screenrectangle)); + } + } + }); + }); + + renderState.sortElements(ELEMENT_SORT_COMPARATOR); + + previousScissorArea = null; + previousPipeline = null; + previousTextureSetup = null; + bufferBuilder = null; + renderState.forEachElement(this::addElementToMesh, GuiRenderState.TraverseRange.BEFORE_BLUR); + if (bufferBuilder != null) { + recordMesh(bufferBuilder, previousPipeline, previousTextureSetup, previousScissorArea); + } + + recordDraws(); + + if (!draws.isEmpty()) { + Minecraft minecraft = Minecraft.getInstance(); + Window window = minecraft.getWindow(); + RenderSystem.setProjectionMatrix( + guiProjectionMatrixBuffer.getBuffer((float) target.target.width / window.getGuiScale(), + (float) target.target.height / window.getGuiScale()), + ProjectionType.ORTHOGRAPHIC + ); + var rendertarget = target.target; + int i = 0; + + for (Draw guirenderer$draw : draws) { + if (guirenderer$draw.indexCount > i) { + i = guirenderer$draw.indexCount; + } + } + + var buffer = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS); + var gpubuffer = buffer.getBuffer(i); + var index = buffer.type(); + var gpubufferslice = RenderSystem.getDynamicUniforms().writeTransform(new Matrix4f().setTranslation(0.0F, 0.0F, -11000.0F), + new Vector4f(1.0F, 1.0F, 1.0F, 1.0F), new Vector3f(), new Matrix4f(), 0.0F); + if (!meshesToDraw.isEmpty()) { + executeDrawRange(() -> "before blur", rendertarget, gpu, gpubufferslice, gpubuffer, + index, Math.min(meshesToDraw.size(), draws.size()), target); + } + } + + for (MappableRingBuffer mappableringbuffer : vertexBuffers.values()) { + mappableringbuffer.rotate(); + } + + draws.clear(); + meshesToDraw.clear(); + renderState.reset(); + } + + private void executeDrawRange(Supplier name, RenderTarget target, GpuBufferSlice buffer1, GpuBufferSlice buffer2, + GpuBuffer buffer3, VertexFormat.IndexType index, int count, CoreRenderTarget target1) { + try (RenderPass renderpass = RenderSystem.getDevice() + .createCommandEncoder() + .createRenderPass(name, target.getColorTextureView(), OptionalInt.empty(), + target.useDepth ? target.getDepthTextureView() : null, OptionalDouble.empty())) { + RenderSystem.bindDefaultUniforms(renderpass); + renderpass.setUniform("Fog", buffer1); + renderpass.setUniform("DynamicTransforms", buffer2); + + for (int i = 0; i < count; i++) { + var item = draws.get(i); + executeDraw(item, renderpass, target1, buffer3, index); + } + } + } + + private void addElementToMesh(GuiElementRenderState state, int range) { + var pipeline = state.pipeline(); + var setup = state.textureSetup(); + var screen = state.scissorArea(); + if (pipeline != previousPipeline || scissorChanged(screen, previousScissorArea) || !setup.equals(previousTextureSetup)) { + if (bufferBuilder != null) { + recordMesh(bufferBuilder, previousPipeline, previousTextureSetup, previousScissorArea); + } + + bufferBuilder = new BufferBuilder(byteBufferBuilder, pipeline.getVertexFormatMode(), pipeline.getVertexFormat()); + previousPipeline = pipeline; + previousTextureSetup = setup; + previousScissorArea = screen; + } + + state.buildVertices(bufferBuilder, 0.0F + range * 0.01F); + } + + private void recordMesh(BufferBuilder builder, RenderPipeline pipeline, TextureSetup texture, ScreenRectangle rectangle) { + var meshdata = builder.buildOrThrow(); + meshesToDraw.add(new MeshToDraw(meshdata, pipeline, texture, rectangle)); + } + + private void recordDraws() { + var map = new Object2IntOpenHashMap(); + + for (MeshToDraw item : meshesToDraw) { + var state = item.mesh.drawState(); + var vertexformat = state.format(); + if (!map.containsKey(vertexformat)) { + map.put(vertexformat, 0); + } + + map.put(vertexformat, map.getInt(vertexformat) + state.vertexCount() * vertexformat.getVertexSize()); + } + + for (var entry : map.object2IntEntrySet()) { + var vertexformat = entry.getKey(); + int i = entry.getIntValue(); + var buffer = vertexBuffers.get(vertexformat); + if (buffer == null || buffer.size() < i) { + if (buffer != null) { + buffer.close(); + } + + vertexBuffers.put(vertexformat, new MappableRingBuffer(() -> "vertex buffer for " + vertexformat, 34, i)); + } + } + + var commandencoder = RenderSystem.getDevice().createCommandEncoder(); + map = new Object2IntOpenHashMap<>(); + + for (MeshToDraw item : meshesToDraw) { + var meshdata = item.mesh; + var state = meshdata.drawState(); + var vertexformat = state.format(); + var mappableringbuffer = vertexBuffers.get(vertexformat); + if (!map.containsKey(vertexformat)) { + map.put(vertexformat, 0); + } + + ByteBuffer bytebuffer = meshdata.vertexBuffer(); + int i = bytebuffer.remaining(); + int j = map.getInt(vertexformat); + + try (var item1 = commandencoder.mapBuffer(mappableringbuffer.currentBuffer().slice(j, i), false, true)) { + MemoryUtil.memCopy(bytebuffer, item1.data()); + } + + map.put(vertexformat, j + i); + draws.add(new Draw(mappableringbuffer.currentBuffer(), j / vertexformat.getVertexSize(), state.mode(), + state.indexCount(), item.pipeline, item.textureSetup, item.scissorArea)); + item.close(); + } + } + + private void executeDraw(Draw draw, RenderPass pass, CoreRenderTarget target, GpuBuffer buffer, VertexFormat.IndexType index) { + var renderpipeline = draw.pipeline(); + pass.setPipeline(renderpipeline); + pass.setVertexBuffer(0, draw.vertexBuffer); + var area = draw.scissorArea(); + if (area != null) { + Window window = Minecraft.getInstance().getWindow(); + + int i = target.target.height; + int j = window.getGuiScale(); + double d0 = area.left() * j; + double d1 = i - area.bottom() * j; + double d2 = area.width() * j; + double d3 = area.height() * j; + pass.enableScissor((int) d0, (int) d1, Math.max(0, (int) d2), Math.max(0, (int) d3)); + } else { + pass.disableScissor(); + } + + if (draw.textureSetup.texure0() != null) { + pass.bindSampler("Sampler0", draw.textureSetup.texure0()); + } + + if (draw.textureSetup.texure1() != null) { + pass.bindSampler("Sampler1", draw.textureSetup.texure1()); + } + + if (draw.textureSetup.texure2() != null) { + pass.bindSampler("Sampler2", draw.textureSetup.texure2()); + } + + pass.setIndexBuffer(buffer, index); + pass.drawIndexed(draw.baseVertex, 0, draw.indexCount, 1); + } + + private boolean scissorChanged(ScreenRectangle screen1, ScreenRectangle screen2) { + if (screen1 == screen2) { + return false; + } else { + return screen1 == null || !screen1.equals(screen2); + } + } + + @Override + public void close() { + byteBufferBuilder.close(); + + guiProjectionMatrixBuffer.close(); + + for (MappableRingBuffer mappableringbuffer : vertexBuffers.values()) { + mappableringbuffer.close(); + } + } + + record Draw(GpuBuffer vertexBuffer, int baseVertex, VertexFormat.Mode mode, int indexCount, + RenderPipeline pipeline, TextureSetup textureSetup, ScreenRectangle scissorArea) { + } + + record MeshToDraw(MeshData mesh, RenderPipeline pipeline, TextureSetup textureSetup, + ScreenRectangle scissorArea) implements AutoCloseable { + @Override + public void close() { + mesh.close(); + } + } + } +} diff --git a/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/FloatRenderState.java b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/FloatRenderState.java new file mode 100644 index 00000000..b3beb395 --- /dev/null +++ b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/FloatRenderState.java @@ -0,0 +1,30 @@ +package com.coloryr.allmusic.client; + +import com.mojang.blaze3d.pipeline.RenderPipeline; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.client.gui.render.TextureSetup; +import net.minecraft.client.gui.render.state.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, float f) { + vertexConsumer.addVertexWith2DPose(pose(), x0(), y0(), f).setUv(u0(), v0()).setColor(color()); + vertexConsumer.addVertexWith2DPose(pose(), x0(), y1(), f).setUv(u0(), v1()).setColor(color()); + vertexConsumer.addVertexWith2DPose(pose(), x1(), y1(), f).setUv(u1(), v1()).setColor(color()); + vertexConsumer.addVertexWith2DPose(pose(), x1(), y0(), f).setUv(u1(), v0()).setColor(color()); + } +} diff --git a/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/MiniMessage.java b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/MiniMessage.java new file mode 100644 index 00000000..54a29ed5 --- /dev/null +++ b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/MiniMessage.java @@ -0,0 +1,648 @@ +package com.coloryr.allmusic.client; + +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.Style; +import net.minecraft.network.chat.TextColor; +import net.minecraft.resources.ResourceLocation; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class MiniMessage { + // 正则表达式匹配标签 + private static final Pattern TAG_PATTERN = Pattern.compile( + "<(/?)(\\w+)(?:\\(([^)]+)\\))?(?::([^>]+))?>" + ); + + // 颜色映射表 + private static final Map COLOR_MAP = new HashMap<>(); + // 装饰器映射 + private static final Map DECORATION_MAP = new HashMap<>(); + + static { + COLOR_MAP.put("black", ChatFormatting.BLACK); + COLOR_MAP.put("dark_blue", ChatFormatting.DARK_BLUE); + COLOR_MAP.put("dark_green", ChatFormatting.DARK_GREEN); + COLOR_MAP.put("dark_aqua", ChatFormatting.DARK_AQUA); + COLOR_MAP.put("dark_red", ChatFormatting.DARK_RED); + COLOR_MAP.put("dark_purple", ChatFormatting.DARK_PURPLE); + COLOR_MAP.put("gold", ChatFormatting.GOLD); + COLOR_MAP.put("gray", ChatFormatting.GRAY); + COLOR_MAP.put("dark_gray", ChatFormatting.DARK_GRAY); + COLOR_MAP.put("blue", ChatFormatting.BLUE); + COLOR_MAP.put("green", ChatFormatting.GREEN); + COLOR_MAP.put("aqua", ChatFormatting.AQUA); + COLOR_MAP.put("red", ChatFormatting.RED); + COLOR_MAP.put("light_purple", ChatFormatting.LIGHT_PURPLE); + COLOR_MAP.put("yellow", ChatFormatting.YELLOW); + COLOR_MAP.put("white", ChatFormatting.WHITE); + } + + static { + DECORATION_MAP.put("bold", ChatFormatting.BOLD); + DECORATION_MAP.put("italic", ChatFormatting.ITALIC); + DECORATION_MAP.put("underlined", ChatFormatting.UNDERLINE); + DECORATION_MAP.put("strikethrough", ChatFormatting.STRIKETHROUGH); + DECORATION_MAP.put("obfuscated", ChatFormatting.OBFUSCATED); + } + + /** + * 解析 MiniMessage 字符串为 Component + */ + public static Component parse(String input) { + MutableComponent root = Component.literal(""); + Stack styleStack = new Stack<>(); + StyleContext currentStyle = new StyleContext(); + styleStack.push(currentStyle); + + StringBuilder textBuffer = new StringBuilder(); + int pos = 0; + int length = input.length(); + + while (pos < length) { + char c = input.charAt(pos); + + if (c == '<') { + // 先处理已积累的文本 + if (textBuffer.length() > 0) { + appendText(root, textBuffer.toString(), currentStyle); + textBuffer.setLength(0); + } + + // 查找标签结束位置 + int endTag = findClosingTag(input, pos); + if (endTag == -1) { + // 没有找到闭合标签,当作普通文本处理 + textBuffer.append(c); + pos++; + continue; + } + + String tagContent = input.substring(pos, endTag + 1); + Matcher matcher = TAG_PATTERN.matcher(tagContent); + + if (matcher.matches()) { + boolean isClosing = !matcher.group(1).isEmpty(); + String tagName = matcher.group(2).toLowerCase(Locale.ROOT); + String params = matcher.group(3); + String value = matcher.group(4); + + if (isClosing) { + // 关闭标签 - 只关闭对应的开始标签 + String expectedOpenTag = matcher.group(2).toLowerCase(Locale.ROOT); + + // 查找对应的开始标签并关闭 + if (styleStack.size() > 1) { + // 从栈顶向下查找匹配的开始标签 + int index = styleStack.size() - 1; + boolean found = false; + + while (index >= 0) { + StyleContext ctx = styleStack.get(index); + if (ctx.tagName != null && ctx.tagName.equals(expectedOpenTag)) { + // 找到匹配的开始标签,关闭到这一层 + while (styleStack.size() > index + 1) { + styleStack.pop(); + } + currentStyle = styleStack.peek(); + found = true; + break; + } + index--; + } + + if (!found) { + // 没找到匹配的标签,忽略此关闭标签 + } + } + } else { + // 处理各种标签 + StyleContext newStyle = currentStyle.copy(); + + switch (tagName) { + case "color": + handleColorTag(newStyle, params, value); + break; + case "shadow": + handleShadowTag(newStyle, params, value); + break; + case "decoration": + handleDecorationTag(newStyle, params, value); + break; + case "reset": + newStyle = new StyleContext(); + break; + case "rainbow": + handleRainbowTag(newStyle, params, value); + break; + case "gradient": + handleGradientTag(newStyle, params, value); + break; + case "transition": + handleTransitionTag(newStyle, params, value); + break; + case "font": + handleFontTag(newStyle, params, value); + break; + default: + // 未知标签,忽略 + break; + } + + // 记录标签名称以便关闭时匹配 + newStyle.tagName = tagName; + styleStack.push(newStyle); + currentStyle = newStyle; + } + } else { + // 无效标签,当作普通文本 + textBuffer.append(tagContent); + } + + pos = endTag + 1; + } else { + textBuffer.append(c); + pos++; + } + } + + // 处理剩余的文本 + if (textBuffer.length() > 0) { + appendText(root, textBuffer.toString(), currentStyle); + } + + return root; + } + + /** + * 处理颜色标签 + * 支持格式: , , + */ + private static void handleColorTag(StyleContext style, String params, String value) { + if (value != null) { + // 格式: 或 + TextColor textColor = parseColor(value); + if (textColor != null) { + style.color = textColor; + // 清除渐变、过渡和彩虹效果 + style.gradient = false; + style.transition = false; + style.rainbow = false; + } + } else if (params != null) { + // 格式: + if (params.equalsIgnoreCase("rgb")) { + String[] parts = value.split(","); + if (parts.length == 3) { + try { + int r = Integer.parseInt(parts[0].trim()); + int g = Integer.parseInt(parts[1].trim()); + int b = Integer.parseInt(parts[2].trim()); + style.color = TextColor.fromRgb((r << 16) | (g << 8) | b); + // 清除渐变、过渡和彩虹效果 + style.gradient = false; + style.transition = false; + style.rainbow = false; + } catch (NumberFormatException e) { + // 忽略解析错误 + } + } + } + } + } + + /** + * 处理阴影标签 + * 支持格式: , , , + */ + private static void handleShadowTag(StyleContext style, String params, String value) { + if (value == null) { + style.shadow = true; + } else if (value.equalsIgnoreCase("true")) { + style.shadow = true; + } else if (value.equalsIgnoreCase("false")) { + style.shadow = false; + } else { + // 自定义阴影颜色 + TextColor shadowColor = parseColor(value); + if (shadowColor != null) { + style.shadow = true; + style.shadowColor = shadowColor; + } + } + } + + /** + * 处理装饰标签 + * 支持格式: , , + */ + private static void handleDecorationTag(StyleContext style, String params, String value) { + if (params == null) return; + + String[] parts = params.split(":"); + String decorationName = parts[0].toLowerCase(Locale.ROOT); + boolean enabled = true; + + if (parts.length > 1) { + enabled = Boolean.parseBoolean(parts[1]); + } else if (value != null) { + enabled = Boolean.parseBoolean(value); + } + + ChatFormatting formatting = DECORATION_MAP.get(decorationName); + if (formatting != null) { + style.decorations.put(formatting, enabled); + } + } + + /** + * 处理彩虹色标签 + * 支持格式: , , , + */ + private static void handleRainbowTag(StyleContext style, String params, String value) { + float saturation = 1.0f; + float brightness = 1.0f; + + if (params != null) { + if (params.equalsIgnoreCase("saturation") && value != null) { + try { + saturation = Float.parseFloat(value); + } catch (NumberFormatException e) { + } + } else if (params.equalsIgnoreCase("brightness") && value != null) { + try { + brightness = Float.parseFloat(value); + } catch (NumberFormatException e) { + } + } + } + + style.rainbow = true; + style.gradient = false; + style.transition = false; + style.rainbowSaturation = saturation; + style.rainbowBrightness = brightness; + } + + /** + * 处理渐变色标签 + * 支持格式: , + * 渐变是逐字符过渡 + */ + private static void handleGradientTag(StyleContext style, String params, String value) { + if (value == null) return; + + String[] colors = value.split(":"); + if (colors.length == 2) { + TextColor start = parseColor(colors[0]); + TextColor end = parseColor(colors[1]); + + if (start != null && end != null) { + style.gradient = true; + style.transition = false; + style.rainbow = false; + style.gradientStart = start; + style.gradientEnd = end; + } + } else if (params != null && params.equalsIgnoreCase("rgb")) { + // 处理 RGB 格式 + String[] color1 = colors[0].split(","); + String[] color2 = colors[1].split(","); + if (color1.length == 3 && color2.length == 3) { + try { + int r1 = Integer.parseInt(color1[0].trim()); + int g1 = Integer.parseInt(color1[1].trim()); + int b1 = Integer.parseInt(color1[2].trim()); + int r2 = Integer.parseInt(color2[0].trim()); + int g2 = Integer.parseInt(color2[1].trim()); + int b2 = Integer.parseInt(color2[2].trim()); + + style.gradient = true; + style.transition = false; + style.rainbow = false; + style.gradientStart = TextColor.fromRgb((r1 << 16) | (g1 << 8) | b1); + style.gradientEnd = TextColor.fromRgb((r2 << 16) | (g2 << 8) | b2); + } catch (NumberFormatException e) { + } + } + } + } + + /** + * 处理过渡色标签 + * 支持格式: , + * 过渡是整个文本块的颜色过渡(类似整体渐变效果) + */ + private static void handleTransitionTag(StyleContext style, String params, String value) { + if (value == null) return; + + String[] colors = value.split(":"); + if (colors.length == 2) { + TextColor start = parseColor(colors[0]); + TextColor end = parseColor(colors[1]); + + if (start != null && end != null) { + style.transition = true; + style.gradient = false; + style.rainbow = false; + style.transitionStart = start; + style.transitionEnd = end; + } + } else if (params != null && params.equalsIgnoreCase("rgb")) { + // 处理 RGB 格式 + String[] color1 = colors[0].split(","); + String[] color2 = colors[1].split(","); + if (color1.length == 3 && color2.length == 3) { + try { + int r1 = Integer.parseInt(color1[0].trim()); + int g1 = Integer.parseInt(color1[1].trim()); + int b1 = Integer.parseInt(color1[2].trim()); + int r2 = Integer.parseInt(color2[0].trim()); + int g2 = Integer.parseInt(color2[1].trim()); + int b2 = Integer.parseInt(color2[2].trim()); + + style.transition = true; + style.gradient = false; + style.rainbow = false; + style.transitionStart = TextColor.fromRgb((r1 << 16) | (g1 << 8) | b1); + style.transitionEnd = TextColor.fromRgb((r2 << 16) | (g2 << 8) | b2); + } catch (NumberFormatException e) { + } + } + } + } + + /** + * 处理字体标签 + * 支持格式: , + */ + private static void handleFontTag(StyleContext style, String params, String value) { + String fontName = value != null ? value : params; + if (fontName != null) { + style.font = fontName; + } + } + + /** + * 解析颜色字符串 + * 支持: 颜色名称, #RRGGBB, #RGB, 十六进制数值 + */ + private static TextColor parseColor(String colorStr) { + if (colorStr == null) return null; + + // 检查是否为预定义颜色名称 + ChatFormatting formatting = COLOR_MAP.get(colorStr.toLowerCase(Locale.ROOT)); + if (formatting != null) { + return TextColor.fromLegacyFormat(formatting); + } + + // 检查十六进制格式 + if (colorStr.startsWith("#")) { + try { + if (colorStr.length() == 7) { + return TextColor.parseColor(colorStr).getOrThrow(); + } else if (colorStr.length() == 4) { + // 扩展 #RGB 到 #RRGGBB + String expanded = "#" + + colorStr.charAt(1) + colorStr.charAt(1) + + colorStr.charAt(2) + colorStr.charAt(2) + + colorStr.charAt(3) + colorStr.charAt(3); + return TextColor.parseColor(expanded).getOrThrow(); + } + } catch (Exception e) { + // 忽略解析错误 + } + } + + return null; + } + + /** + * 查找标签闭合位置 + */ + private static int findClosingTag(String input, int start) { + int pos = start + 1; + while (pos < input.length()) { + char c = input.charAt(pos); + if (c == '>') { + return pos; + } + pos++; + } + return -1; + } + + /** + * 添加文本到组件 + */ + private static void appendText(MutableComponent root, String text, StyleContext styleContext) { + if (text.isEmpty()) return; + + // 如果有渐变效果,需要逐字符处理 + if (styleContext.gradient && styleContext.gradientStart != null && styleContext.gradientEnd != null) { + appendGradientText(root, text, styleContext); + } + // 如果有过渡效果,需要逐字符处理 + else if (styleContext.transition && styleContext.transitionStart != null && styleContext.transitionEnd != null) { + appendTransitionText(root, text, styleContext); + } + // 如果有彩虹效果,需要逐字符处理 + else if (styleContext.rainbow) { + appendRainbowText(root, text, styleContext); + } + // 普通文本 + else { + MutableComponent component = Component.literal(text); + component.setStyle(styleContext.buildStyle()); + root.append(component); + } + } + + /** + * 带渐变效果的文本追加(逐字符颜色变化) + */ + private static void appendGradientText(MutableComponent root, String text, StyleContext styleContext) { + int length = text.length(); + Style baseStyle = styleContext.buildBaseStyle(); + + for (int i = 0; i < length; i++) { + char c = text.charAt(i); + float ratio = (float) i / (length - 1); + TextColor gradientColor = interpolateColor(styleContext.gradientStart, styleContext.gradientEnd, ratio); + + Style charStyle = baseStyle.withColor(gradientColor); + // 重新应用阴影 +// if (styleContext.shadow != null && styleContext.shadow) { +// if (styleContext.shadowColor != null) { +// charStyle = charStyle.withShadowColor(styleContext.shadowColor.getValue() | 0xFF000000); +// } else { +// charStyle = charStyle.withShadowColor(0xFF000000); +// } +// } + + root.append(Component.literal(String.valueOf(c)).withStyle(charStyle)); + } + } + + /** + * 带过渡效果的文本追加(整体颜色过渡) + */ + private static void appendTransitionText(MutableComponent root, String text, StyleContext styleContext) { + Style baseStyle = styleContext.buildBaseStyle(); + + // 过渡效果:为整个文本块创建一个颜色过渡 + MutableComponent transitionComponent = Component.literal(text); + + // 应用过渡样式(这里使用颜色插值,但也可以根据需求调整) + if (styleContext.transitionStart != null && styleContext.transitionEnd != null) { + // 可以使用整体渐变或者固定起始色 + transitionComponent.setStyle(baseStyle.withColor(styleContext.transitionStart)); + } else { + transitionComponent.setStyle(baseStyle); + } + + root.append(transitionComponent); + } + + /** + * 带彩虹效果的文本追加 + */ + private static void appendRainbowText(MutableComponent root, String text, StyleContext styleContext) { + int length = text.length(); + Style baseStyle = styleContext.buildBaseStyle(); + + for (int i = 0; i < length; i++) { + char c = text.charAt(i); + float hue = (float) i / length; + int rgb = java.awt.Color.HSBtoRGB(hue, styleContext.rainbowSaturation, styleContext.rainbowBrightness); + TextColor rainbowColor = TextColor.fromRgb(rgb & 0xFFFFFF); + + Style charStyle = baseStyle.withColor(rainbowColor); + // 重新应用阴影 +// if (styleContext.shadow != null && styleContext.shadow) { +// if (styleContext.shadowColor != null) { +// charStyle = charStyle.withShadowColor(styleContext.shadowColor.getValue() | 0xFF000000); +// } else { +// charStyle = charStyle.withShadowColor(0xFF000000); +// } +// } + + root.append(Component.literal(String.valueOf(c)).withStyle(charStyle)); + } + } + + /** + * 颜色插值 + */ + private static TextColor interpolateColor(TextColor start, TextColor end, float ratio) { + int startRgb = start.getValue(); + int endRgb = end.getValue(); + + int r1 = (startRgb >> 16) & 0xFF; + int g1 = (startRgb >> 8) & 0xFF; + int b1 = startRgb & 0xFF; + + int r2 = (endRgb >> 16) & 0xFF; + int g2 = (endRgb >> 8) & 0xFF; + int b2 = endRgb & 0xFF; + + int r = (int) (r1 + (r2 - r1) * ratio); + int g = (int) (g1 + (g2 - g1) * ratio); + int b = (int) (b1 + (b2 - b1) * ratio); + + return TextColor.fromRgb((r << 16) | (g << 8) | b); + } + + /** + * 样式上下文类,用于追踪嵌套样式 + */ + private static class StyleContext { + String tagName = null; // 记录标签名称,用于关闭匹配 + TextColor color = null; + Boolean shadow = null; + TextColor shadowColor = null; + Map decorations = new HashMap<>(); + boolean rainbow = false; + float rainbowSaturation = 1.0f; + float rainbowBrightness = 1.0f; + boolean gradient = false; + TextColor gradientStart = null; + TextColor gradientEnd = null; + boolean transition = false; + TextColor transitionStart = null; + TextColor transitionEnd = null; + String font = null; + + StyleContext copy() { + StyleContext copy = new StyleContext(); + copy.tagName = this.tagName; + copy.color = this.color; + copy.shadow = this.shadow; + copy.shadowColor = this.shadowColor; + copy.decorations.putAll(this.decorations); + copy.rainbow = this.rainbow; + copy.rainbowSaturation = this.rainbowSaturation; + copy.rainbowBrightness = this.rainbowBrightness; + copy.gradient = this.gradient; + copy.gradientStart = this.gradientStart; + copy.gradientEnd = this.gradientEnd; + copy.transition = this.transition; + copy.transitionStart = this.transitionStart; + copy.transitionEnd = this.transitionEnd; + copy.font = this.font; + return copy; + } + + /** + * 构建基础样式(不含颜色) + */ + Style buildBaseStyle() { + Style style = Style.EMPTY; + + // 应用装饰器 + for (Map.Entry entry : decorations.entrySet()) { + if (entry.getValue()) { + style = style.applyFormat(entry.getKey()); + } + } + + if (font != null) { + try { + style = style.withFont(ResourceLocation.parse(font)); + } catch (Exception e) { + // 忽略字体解析错误 + } + } + + return style; + } + + /** + * 构建完整样式(含颜色) + */ + Style buildStyle() { + Style style = buildBaseStyle(); + + // 只有在没有渐变、过渡和彩虹效果时才应用普通颜色 + if (!gradient && !transition && !rainbow && color != null) { + style = style.withColor(color); + } + +// if (shadow != null && shadow) { +// if (shadowColor != null) { +// style = style.withShadowColor(shadowColor.getValue() | 0xFF000000); +// } else { +// style = style.withShadowColor(0xFF000000); +// } +// } + + return style; + } + } +} \ No newline at end of file diff --git a/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/PicRender.java b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/PicRender.java new file mode 100644 index 00000000..b73f4d06 --- /dev/null +++ b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/PicRender.java @@ -0,0 +1,69 @@ +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.guiRenderState.submitGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED, TextureSetup.singleTexture(rotate ? rotateTexture.getTextureView() : sourceTexture.getTextureView()), matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.scissorStack.peek())); + } +} diff --git a/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/TexRender.java b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/TexRender.java new file mode 100644 index 00000000..e4bde9d8 --- /dev/null +++ b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/TexRender.java @@ -0,0 +1,103 @@ +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.ResourceLocation; +import org.joml.Matrix3x2f; + +public class TexRender extends TextureRender { + private final SimpleTexture sourceTexture; + + public TexRender(String texture) { + super(texture); + ResourceLocation location = ResourceLocation.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.submitGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED, TextureSetup.singleTexture(sourceTexture.getTextureView()), 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.submitGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED, TextureSetup.singleTexture(sourceTexture.getTextureView()), 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.submitGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED, TextureSetup.singleTexture(sourceTexture.getTextureView()), matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.scissorStack.peek())); + } +} diff --git a/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/mixins/GuiShow.java b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/mixins/GuiShow.java new file mode 100644 index 00000000..edb5cb93 --- /dev/null +++ b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/mixins/GuiShow.java @@ -0,0 +1,18 @@ +package com.coloryr.allmusic.client.mixins; + +import com.coloryr.allmusic.client.AllMusicClient; +import net.minecraft.client.DeltaTracker; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiGraphics; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Gui.class) +public class GuiShow { + @Inject(method = {"renderChat"}, at = {@At(value = "RETURN")}) + public void guiShow(GuiGraphics context, DeltaTracker tickCounter, CallbackInfo ci) { + AllMusicClient.update(context); + } +} diff --git a/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/mixins/QuitServer.java b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/mixins/QuitServer.java new file mode 100644 index 00000000..d7855158 --- /dev/null +++ b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/mixins/QuitServer.java @@ -0,0 +1,16 @@ +package com.coloryr.allmusic.client.mixins; + +import com.coloryr.allmusic.client.core.AllMusicCore; +import net.minecraft.client.Minecraft; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Minecraft.class) +public class QuitServer { + @Inject(method = "disconnect(Lnet/minecraft/client/gui/screens/Screen;Z)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;resetData()V")) + public void quit(CallbackInfo info) { + AllMusicCore.onServerQuit(); + } +} diff --git a/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/mixins/Register.java b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/mixins/Register.java new file mode 100644 index 00000000..55a51d8c --- /dev/null +++ b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/mixins/Register.java @@ -0,0 +1,17 @@ +package com.coloryr.allmusic.client.mixins; + +import com.coloryr.allmusic.client.core.AllMusicCore; +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.Minecraft; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Minecraft.class) +public class Register { + @Inject(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/TitleScreen;registerTextures(Lnet/minecraft/client/renderer/texture/TextureManager;)V")) + public void register(CallbackInfo info) { + RenderSystem.queueFencedTask(AllMusicCore::renderInit); + } +} diff --git a/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/mixins/SoundEvent.java b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/mixins/SoundEvent.java new file mode 100644 index 00000000..fad60d82 --- /dev/null +++ b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/mixins/SoundEvent.java @@ -0,0 +1,40 @@ +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 soundInstance, CallbackInfoReturnable cir) { + if (AllMusicCore.isPlay()) { + SoundSource data = soundInstance.getSource(); + switch (data) { + case RECORDS, MUSIC -> 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(); + } + } + } + + @Inject(method = "reload", at = @At("RETURN")) + public void reload(CallbackInfo info) { + AllMusicCore.reload(); + } +} diff --git a/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/mixins/Tick.java b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/mixins/Tick.java new file mode 100644 index 00000000..678182f2 --- /dev/null +++ b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/client/mixins/Tick.java @@ -0,0 +1,17 @@ +package com.coloryr.allmusic.client.mixins; + +import com.coloryr.allmusic.client.core.AllMusicCore; +import net.minecraft.client.Minecraft; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Minecraft.class) +public class Tick { + + @Inject(method = "tick", at = @At("TAIL")) + public void tick(CallbackInfo info) { + AllMusicCore.tick(); + } +} diff --git a/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/comm/AllMusicInit.java b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/comm/AllMusicInit.java new file mode 100644 index 00000000..eab843aa --- /dev/null +++ b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/comm/AllMusicInit.java @@ -0,0 +1,11 @@ +package com.coloryr.allmusic.comm; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; + +public class AllMusicInit implements ModInitializer { + @Override + public void onInitialize() { + PayloadTypeRegistry.playS2C().register(MusicCodec.ID, MusicCodec.CODEC); + } +} diff --git a/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/comm/MusicCodec.java b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/comm/MusicCodec.java new file mode 100644 index 00000000..feb83760 --- /dev/null +++ b/client/fabric_1_21_8/src/main/java/com/coloryr/allmusic/comm/MusicCodec.java @@ -0,0 +1,22 @@ +package com.coloryr.allmusic.comm; + +import com.coloryr.allmusic.codec.MusicPack; +import com.coloryr.allmusic.codec.MusicPacketCodec; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; + +public record MusicCodec(MusicPack pack) implements CustomPacketPayload { + public static final ResourceLocation ChannelID = ResourceLocation.fromNamespaceAndPath("allmusic", "channel"); + + public static final Type ID = new Type<>(ChannelID); + public static final StreamCodec CODEC = + StreamCodec.of((buf, value) -> MusicPacketCodec.pack(buf, value.pack), + buf -> new MusicCodec(MusicPacketCodec.decode(buf))); + + @Override + public Type type() { + return ID; + } +} diff --git a/client/fabric_1_21_8/src/main/resources/allmusic_client.mixins.json b/client/fabric_1_21_8/src/main/resources/allmusic_client.mixins.json new file mode 100644 index 00000000..e139dd95 --- /dev/null +++ b/client/fabric_1_21_8/src/main/resources/allmusic_client.mixins.json @@ -0,0 +1,18 @@ +{ + "required": true, + "package": "com.coloryr.allmusic.client.mixins", + "compatibilityLevel": "JAVA_21", + "client": [ + "GuiShow", + "QuitServer", + "Register", + "SoundEvent", + "Tick" + ], + "injectors": { + "defaultRequire": 1 + }, + "overwrites": { + "requireAnnotations": true + } +} \ No newline at end of file diff --git a/client/fabric_1_21_8/src/main/resources/assets/allmusic_client/icon.png b/client/fabric_1_21_8/src/main/resources/assets/allmusic_client/icon.png new file mode 100644 index 00000000..b2b5fa79 Binary files /dev/null and b/client/fabric_1_21_8/src/main/resources/assets/allmusic_client/icon.png differ diff --git a/client/fabric_1_21_8/src/main/resources/fabric.mod.json b/client/fabric_1_21_8/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..a6a09df2 --- /dev/null +++ b/client/fabric_1_21_8/src/main/resources/fabric.mod.json @@ -0,0 +1,39 @@ +{ + "schemaVersion": 1, + "id": "allmusic_client", + "version": "${version}", + "name": "AllMusic Client", + "description": "AllMusic Client Mod", + "authors": [ + "Color_yr" + ], + "contact": { + "homepage": "https://github.com/Coloryr/AllMusic", + "sources": "https://github.com/Coloryr/AllMusic" + }, + "license": "GPL-3.0", + "icon": "assets/allmusic_client/icon.png", + "environment": "*", + "entrypoints": { + "main": [ + "com.coloryr.allmusic.comm.AllMusicInit" + ], + "client": [ + "com.coloryr.allmusic.client.AllMusicClient" + ] + }, + "mixins": [ + { + "config": "allmusic_client.mixins.json", + "environment": "client" + } + ], + "depends": { + "fabricloader": ">=0.16.14", + "minecraft": ">=1.21.6", + "java": ">=21", + "fabric-api": "*" + }, + "suggests": { + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 3cd4998d..bc0bc12f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -9,6 +9,7 @@ include(":client:fabric_1_21") include(":client:fabric_1_21_3") include(":client:fabric_1_21_4") include(":client:fabric_1_21_6") +include(":client:fabric_1_21_8") include(":client:fabric_1_21_11") include(":client:fabric_26_1") include(":client:fabric_26_2")