This commit is contained in:
Color_yr committed 2026-06-30 23:26:09 +08:00
1 parent 2a410060b9
commit d61354991e
18 files changed
+1695

No files matched your search

+35
View File
@@ -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)
}
}
@@ -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);
}
}
@@ -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<ScreenRectangle> SCISSOR_COMPARATOR = Comparator.nullsFirst(
Comparator.comparing(ScreenRectangle::top)
.thenComparing(ScreenRectangle::bottom)
.thenComparing(ScreenRectangle::left)
.thenComparing(ScreenRectangle::right)
);
private static final Comparator<TextureSetup> TEXTURE_COMPARATOR = Comparator.nullsFirst(Comparator.comparing(TextureSetup::getSortKey));
private static final Comparator<GuiElementRenderState> 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<Draw> draws = new ArrayList<>();
private final List<MeshToDraw> meshesToDraw = new ArrayList<>();
private final ByteBufferBuilder byteBufferBuilder = new ByteBufferBuilder(786432);
private final Map<VertexFormat, MappableRingBuffer> 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<String> 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<VertexFormat>();
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();
}
}
}
}
@@ -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());
}
}
@@ -0,0 +1,648 @@
package com.coloryr.allmusic.client;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.Style;
import net.minecraft.network.chat.TextColor;
import net.minecraft.resources.ResourceLocation;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MiniMessage {
// 正则表达式匹配标签
private static final Pattern TAG_PATTERN = Pattern.compile(
"<(/?)(\\w+)(?:\\(([^)]+)\\))?(?::([^>]+))?>"
);
// 颜色映射表
private static final Map<String, ChatFormatting> COLOR_MAP = new HashMap<>();
// 装饰器映射
private static final Map<String, ChatFormatting> DECORATION_MAP = new HashMap<>();
static {
COLOR_MAP.put("black", ChatFormatting.BLACK);
COLOR_MAP.put("dark_blue", ChatFormatting.DARK_BLUE);
COLOR_MAP.put("dark_green", ChatFormatting.DARK_GREEN);
COLOR_MAP.put("dark_aqua", ChatFormatting.DARK_AQUA);
COLOR_MAP.put("dark_red", ChatFormatting.DARK_RED);
COLOR_MAP.put("dark_purple", ChatFormatting.DARK_PURPLE);
COLOR_MAP.put("gold", ChatFormatting.GOLD);
COLOR_MAP.put("gray", ChatFormatting.GRAY);
COLOR_MAP.put("dark_gray", ChatFormatting.DARK_GRAY);
COLOR_MAP.put("blue", ChatFormatting.BLUE);
COLOR_MAP.put("green", ChatFormatting.GREEN);
COLOR_MAP.put("aqua", ChatFormatting.AQUA);
COLOR_MAP.put("red", ChatFormatting.RED);
COLOR_MAP.put("light_purple", ChatFormatting.LIGHT_PURPLE);
COLOR_MAP.put("yellow", ChatFormatting.YELLOW);
COLOR_MAP.put("white", ChatFormatting.WHITE);
}
static {
DECORATION_MAP.put("bold", ChatFormatting.BOLD);
DECORATION_MAP.put("italic", ChatFormatting.ITALIC);
DECORATION_MAP.put("underlined", ChatFormatting.UNDERLINE);
DECORATION_MAP.put("strikethrough", ChatFormatting.STRIKETHROUGH);
DECORATION_MAP.put("obfuscated", ChatFormatting.OBFUSCATED);
}
/**
* 解析 MiniMessage 字符串为 Component
*/
public static Component parse(String input) {
MutableComponent root = Component.literal("");
Stack<StyleContext> styleStack = new Stack<>();
StyleContext currentStyle = new StyleContext();
styleStack.push(currentStyle);
StringBuilder textBuffer = new StringBuilder();
int pos = 0;
int length = input.length();
while (pos < length) {
char c = input.charAt(pos);
if (c == '<') {
// 先处理已积累的文本
if (textBuffer.length() > 0) {
appendText(root, textBuffer.toString(), currentStyle);
textBuffer.setLength(0);
}
// 查找标签结束位置
int endTag = findClosingTag(input, pos);
if (endTag == -1) {
// 没有找到闭合标签,当作普通文本处理
textBuffer.append(c);
pos++;
continue;
}
String tagContent = input.substring(pos, endTag + 1);
Matcher matcher = TAG_PATTERN.matcher(tagContent);
if (matcher.matches()) {
boolean isClosing = !matcher.group(1).isEmpty();
String tagName = matcher.group(2).toLowerCase(Locale.ROOT);
String params = matcher.group(3);
String value = matcher.group(4);
if (isClosing) {
// 关闭标签 - 只关闭对应的开始标签
String expectedOpenTag = matcher.group(2).toLowerCase(Locale.ROOT);
// 查找对应的开始标签并关闭
if (styleStack.size() > 1) {
// 从栈顶向下查找匹配的开始标签
int index = styleStack.size() - 1;
boolean found = false;
while (index >= 0) {
StyleContext ctx = styleStack.get(index);
if (ctx.tagName != null && ctx.tagName.equals(expectedOpenTag)) {
// 找到匹配的开始标签,关闭到这一层
while (styleStack.size() > index + 1) {
styleStack.pop();
}
currentStyle = styleStack.peek();
found = true;
break;
}
index--;
}
if (!found) {
// 没找到匹配的标签,忽略此关闭标签
}
}
} else {
// 处理各种标签
StyleContext newStyle = currentStyle.copy();
switch (tagName) {
case "color":
handleColorTag(newStyle, params, value);
break;
case "shadow":
handleShadowTag(newStyle, params, value);
break;
case "decoration":
handleDecorationTag(newStyle, params, value);
break;
case "reset":
newStyle = new StyleContext();
break;
case "rainbow":
handleRainbowTag(newStyle, params, value);
break;
case "gradient":
handleGradientTag(newStyle, params, value);
break;
case "transition":
handleTransitionTag(newStyle, params, value);
break;
case "font":
handleFontTag(newStyle, params, value);
break;
default:
// 未知标签,忽略
break;
}
// 记录标签名称以便关闭时匹配
newStyle.tagName = tagName;
styleStack.push(newStyle);
currentStyle = newStyle;
}
} else {
// 无效标签,当作普通文本
textBuffer.append(tagContent);
}
pos = endTag + 1;
} else {
textBuffer.append(c);
pos++;
}
}
// 处理剩余的文本
if (textBuffer.length() > 0) {
appendText(root, textBuffer.toString(), currentStyle);
}
return root;
}
/**
* 处理颜色标签
* 支持格式: <color:red>, <color:#FF0000>, <color(rgb):255,0,0>
*/
private static void handleColorTag(StyleContext style, String params, String value) {
if (value != null) {
// 格式: <color:red> 或 <color:#FF0000>
TextColor textColor = parseColor(value);
if (textColor != null) {
style.color = textColor;
// 清除渐变、过渡和彩虹效果
style.gradient = false;
style.transition = false;
style.rainbow = false;
}
} else if (params != null) {
// 格式: <color(rgb):255,0,0>
if (params.equalsIgnoreCase("rgb")) {
String[] parts = value.split(",");
if (parts.length == 3) {
try {
int r = Integer.parseInt(parts[0].trim());
int g = Integer.parseInt(parts[1].trim());
int b = Integer.parseInt(parts[2].trim());
style.color = TextColor.fromRgb((r << 16) | (g << 8) | b);
// 清除渐变、过渡和彩虹效果
style.gradient = false;
style.transition = false;
style.rainbow = false;
} catch (NumberFormatException e) {
// 忽略解析错误
}
}
}
}
}
/**
* 处理阴影标签
* 支持格式: <shadow>, <shadow:true>, <shadow:false>, <shadow:#FF0000>
*/
private static void handleShadowTag(StyleContext style, String params, String value) {
if (value == null) {
style.shadow = true;
} else if (value.equalsIgnoreCase("true")) {
style.shadow = true;
} else if (value.equalsIgnoreCase("false")) {
style.shadow = false;
} else {
// 自定义阴影颜色
TextColor shadowColor = parseColor(value);
if (shadowColor != null) {
style.shadow = true;
style.shadowColor = shadowColor;
}
}
}
/**
* 处理装饰标签
* 支持格式: <decoration:bold>, <decoration:bold:true>, <decoration:bold:false>
*/
private static void handleDecorationTag(StyleContext style, String params, String value) {
if (params == null) return;
String[] parts = params.split(":");
String decorationName = parts[0].toLowerCase(Locale.ROOT);
boolean enabled = true;
if (parts.length > 1) {
enabled = Boolean.parseBoolean(parts[1]);
} else if (value != null) {
enabled = Boolean.parseBoolean(value);
}
ChatFormatting formatting = DECORATION_MAP.get(decorationName);
if (formatting != null) {
style.decorations.put(formatting, enabled);
}
}
/**
* 处理彩虹色标签
* 支持格式: <rainbow>, <rainbow:!>, <rainbow(saturation):0.8>, <rainbow(brightness):0.5>
*/
private static void handleRainbowTag(StyleContext style, String params, String value) {
float saturation = 1.0f;
float brightness = 1.0f;
if (params != null) {
if (params.equalsIgnoreCase("saturation") && value != null) {
try {
saturation = Float.parseFloat(value);
} catch (NumberFormatException e) {
}
} else if (params.equalsIgnoreCase("brightness") && value != null) {
try {
brightness = Float.parseFloat(value);
} catch (NumberFormatException e) {
}
}
}
style.rainbow = true;
style.gradient = false;
style.transition = false;
style.rainbowSaturation = saturation;
style.rainbowBrightness = brightness;
}
/**
* 处理渐变色标签
* 支持格式: <gradient:#FF0000:#00FF00>, <gradient(rgb):255,0,0:0,255,0>
* 渐变是逐字符过渡
*/
private static void handleGradientTag(StyleContext style, String params, String value) {
if (value == null) return;
String[] colors = value.split(":");
if (colors.length == 2) {
TextColor start = parseColor(colors[0]);
TextColor end = parseColor(colors[1]);
if (start != null && end != null) {
style.gradient = true;
style.transition = false;
style.rainbow = false;
style.gradientStart = start;
style.gradientEnd = end;
}
} else if (params != null && params.equalsIgnoreCase("rgb")) {
// 处理 RGB 格式
String[] color1 = colors[0].split(",");
String[] color2 = colors[1].split(",");
if (color1.length == 3 && color2.length == 3) {
try {
int r1 = Integer.parseInt(color1[0].trim());
int g1 = Integer.parseInt(color1[1].trim());
int b1 = Integer.parseInt(color1[2].trim());
int r2 = Integer.parseInt(color2[0].trim());
int g2 = Integer.parseInt(color2[1].trim());
int b2 = Integer.parseInt(color2[2].trim());
style.gradient = true;
style.transition = false;
style.rainbow = false;
style.gradientStart = TextColor.fromRgb((r1 << 16) | (g1 << 8) | b1);
style.gradientEnd = TextColor.fromRgb((r2 << 16) | (g2 << 8) | b2);
} catch (NumberFormatException e) {
}
}
}
}
/**
* 处理过渡色标签
* 支持格式: <transition:#FF0000:#00FF00>, <transition(rgb):255,0,0:0,255,0>
* 过渡是整个文本块的颜色过渡(类似整体渐变效果)
*/
private static void handleTransitionTag(StyleContext style, String params, String value) {
if (value == null) return;
String[] colors = value.split(":");
if (colors.length == 2) {
TextColor start = parseColor(colors[0]);
TextColor end = parseColor(colors[1]);
if (start != null && end != null) {
style.transition = true;
style.gradient = false;
style.rainbow = false;
style.transitionStart = start;
style.transitionEnd = end;
}
} else if (params != null && params.equalsIgnoreCase("rgb")) {
// 处理 RGB 格式
String[] color1 = colors[0].split(",");
String[] color2 = colors[1].split(",");
if (color1.length == 3 && color2.length == 3) {
try {
int r1 = Integer.parseInt(color1[0].trim());
int g1 = Integer.parseInt(color1[1].trim());
int b1 = Integer.parseInt(color1[2].trim());
int r2 = Integer.parseInt(color2[0].trim());
int g2 = Integer.parseInt(color2[1].trim());
int b2 = Integer.parseInt(color2[2].trim());
style.transition = true;
style.gradient = false;
style.rainbow = false;
style.transitionStart = TextColor.fromRgb((r1 << 16) | (g1 << 8) | b1);
style.transitionEnd = TextColor.fromRgb((r2 << 16) | (g2 << 8) | b2);
} catch (NumberFormatException e) {
}
}
}
}
/**
* 处理字体标签
* 支持格式: <font:minecraft:default>, <font:minecraft:uniform>
*/
private static void handleFontTag(StyleContext style, String params, String value) {
String fontName = value != null ? value : params;
if (fontName != null) {
style.font = fontName;
}
}
/**
* 解析颜色字符串
* 支持: 颜色名称, #RRGGBB, #RGB, 十六进制数值
*/
private static TextColor parseColor(String colorStr) {
if (colorStr == null) return null;
// 检查是否为预定义颜色名称
ChatFormatting formatting = COLOR_MAP.get(colorStr.toLowerCase(Locale.ROOT));
if (formatting != null) {
return TextColor.fromLegacyFormat(formatting);
}
// 检查十六进制格式
if (colorStr.startsWith("#")) {
try {
if (colorStr.length() == 7) {
return TextColor.parseColor(colorStr).getOrThrow();
} else if (colorStr.length() == 4) {
// 扩展 #RGB 到 #RRGGBB
String expanded = "#" +
colorStr.charAt(1) + colorStr.charAt(1) +
colorStr.charAt(2) + colorStr.charAt(2) +
colorStr.charAt(3) + colorStr.charAt(3);
return TextColor.parseColor(expanded).getOrThrow();
}
} catch (Exception e) {
// 忽略解析错误
}
}
return null;
}
/**
* 查找标签闭合位置
*/
private static int findClosingTag(String input, int start) {
int pos = start + 1;
while (pos < input.length()) {
char c = input.charAt(pos);
if (c == '>') {
return pos;
}
pos++;
}
return -1;
}
/**
* 添加文本到组件
*/
private static void appendText(MutableComponent root, String text, StyleContext styleContext) {
if (text.isEmpty()) return;
// 如果有渐变效果,需要逐字符处理
if (styleContext.gradient && styleContext.gradientStart != null && styleContext.gradientEnd != null) {
appendGradientText(root, text, styleContext);
}
// 如果有过渡效果,需要逐字符处理
else if (styleContext.transition && styleContext.transitionStart != null && styleContext.transitionEnd != null) {
appendTransitionText(root, text, styleContext);
}
// 如果有彩虹效果,需要逐字符处理
else if (styleContext.rainbow) {
appendRainbowText(root, text, styleContext);
}
// 普通文本
else {
MutableComponent component = Component.literal(text);
component.setStyle(styleContext.buildStyle());
root.append(component);
}
}
/**
* 带渐变效果的文本追加(逐字符颜色变化)
*/
private static void appendGradientText(MutableComponent root, String text, StyleContext styleContext) {
int length = text.length();
Style baseStyle = styleContext.buildBaseStyle();
for (int i = 0; i < length; i++) {
char c = text.charAt(i);
float ratio = (float) i / (length - 1);
TextColor gradientColor = interpolateColor(styleContext.gradientStart, styleContext.gradientEnd, ratio);
Style charStyle = baseStyle.withColor(gradientColor);
// 重新应用阴影
// if (styleContext.shadow != null && styleContext.shadow) {
// if (styleContext.shadowColor != null) {
// charStyle = charStyle.withShadowColor(styleContext.shadowColor.getValue() | 0xFF000000);
// } else {
// charStyle = charStyle.withShadowColor(0xFF000000);
// }
// }
root.append(Component.literal(String.valueOf(c)).withStyle(charStyle));
}
}
/**
* 带过渡效果的文本追加(整体颜色过渡)
*/
private static void appendTransitionText(MutableComponent root, String text, StyleContext styleContext) {
Style baseStyle = styleContext.buildBaseStyle();
// 过渡效果:为整个文本块创建一个颜色过渡
MutableComponent transitionComponent = Component.literal(text);
// 应用过渡样式(这里使用颜色插值,但也可以根据需求调整)
if (styleContext.transitionStart != null && styleContext.transitionEnd != null) {
// 可以使用整体渐变或者固定起始色
transitionComponent.setStyle(baseStyle.withColor(styleContext.transitionStart));
} else {
transitionComponent.setStyle(baseStyle);
}
root.append(transitionComponent);
}
/**
* 带彩虹效果的文本追加
*/
private static void appendRainbowText(MutableComponent root, String text, StyleContext styleContext) {
int length = text.length();
Style baseStyle = styleContext.buildBaseStyle();
for (int i = 0; i < length; i++) {
char c = text.charAt(i);
float hue = (float) i / length;
int rgb = java.awt.Color.HSBtoRGB(hue, styleContext.rainbowSaturation, styleContext.rainbowBrightness);
TextColor rainbowColor = TextColor.fromRgb(rgb & 0xFFFFFF);
Style charStyle = baseStyle.withColor(rainbowColor);
// 重新应用阴影
// if (styleContext.shadow != null && styleContext.shadow) {
// if (styleContext.shadowColor != null) {
// charStyle = charStyle.withShadowColor(styleContext.shadowColor.getValue() | 0xFF000000);
// } else {
// charStyle = charStyle.withShadowColor(0xFF000000);
// }
// }
root.append(Component.literal(String.valueOf(c)).withStyle(charStyle));
}
}
/**
* 颜色插值
*/
private static TextColor interpolateColor(TextColor start, TextColor end, float ratio) {
int startRgb = start.getValue();
int endRgb = end.getValue();
int r1 = (startRgb >> 16) & 0xFF;
int g1 = (startRgb >> 8) & 0xFF;
int b1 = startRgb & 0xFF;
int r2 = (endRgb >> 16) & 0xFF;
int g2 = (endRgb >> 8) & 0xFF;
int b2 = endRgb & 0xFF;
int r = (int) (r1 + (r2 - r1) * ratio);
int g = (int) (g1 + (g2 - g1) * ratio);
int b = (int) (b1 + (b2 - b1) * ratio);
return TextColor.fromRgb((r << 16) | (g << 8) | b);
}
/**
* 样式上下文类,用于追踪嵌套样式
*/
private static class StyleContext {
String tagName = null; // 记录标签名称,用于关闭匹配
TextColor color = null;
Boolean shadow = null;
TextColor shadowColor = null;
Map<ChatFormatting, Boolean> decorations = new HashMap<>();
boolean rainbow = false;
float rainbowSaturation = 1.0f;
float rainbowBrightness = 1.0f;
boolean gradient = false;
TextColor gradientStart = null;
TextColor gradientEnd = null;
boolean transition = false;
TextColor transitionStart = null;
TextColor transitionEnd = null;
String font = null;
StyleContext copy() {
StyleContext copy = new StyleContext();
copy.tagName = this.tagName;
copy.color = this.color;
copy.shadow = this.shadow;
copy.shadowColor = this.shadowColor;
copy.decorations.putAll(this.decorations);
copy.rainbow = this.rainbow;
copy.rainbowSaturation = this.rainbowSaturation;
copy.rainbowBrightness = this.rainbowBrightness;
copy.gradient = this.gradient;
copy.gradientStart = this.gradientStart;
copy.gradientEnd = this.gradientEnd;
copy.transition = this.transition;
copy.transitionStart = this.transitionStart;
copy.transitionEnd = this.transitionEnd;
copy.font = this.font;
return copy;
}
/**
* 构建基础样式(不含颜色)
*/
Style buildBaseStyle() {
Style style = Style.EMPTY;
// 应用装饰器
for (Map.Entry<ChatFormatting, Boolean> entry : decorations.entrySet()) {
if (entry.getValue()) {
style = style.applyFormat(entry.getKey());
}
}
if (font != null) {
try {
style = style.withFont(ResourceLocation.parse(font));
} catch (Exception e) {
// 忽略字体解析错误
}
}
return style;
}
/**
* 构建完整样式(含颜色)
*/
Style buildStyle() {
Style style = buildBaseStyle();
// 只有在没有渐变、过渡和彩虹效果时才应用普通颜色
if (!gradient && !transition && !rainbow && color != null) {
style = style.withColor(color);
}
// if (shadow != null && shadow) {
// if (shadowColor != null) {
// style = style.withShadowColor(shadowColor.getValue() | 0xFF000000);
// } else {
// style = style.withShadowColor(0xFF000000);
// }
// }
return style;
}
}
}
@@ -0,0 +1,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()));
}
}
@@ -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()));
}
}
@@ -0,0 +1,18 @@
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.AllMusicClient;
import net.minecraft.client.DeltaTracker;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiGraphics;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Gui.class)
public class GuiShow {
@Inject(method = {"renderChat"}, at = {@At(value = "RETURN")})
public void guiShow(GuiGraphics context, DeltaTracker tickCounter, CallbackInfo ci) {
AllMusicClient.update(context);
}
}
@@ -0,0 +1,16 @@
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Minecraft.class)
public class QuitServer {
@Inject(method = "disconnect(Lnet/minecraft/client/gui/screens/Screen;Z)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;resetData()V"))
public void quit(CallbackInfo info) {
AllMusicCore.onServerQuit();
}
}
@@ -0,0 +1,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 = "<init>", 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);
}
}
@@ -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<SoundEngine.PlayResult> 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();
}
}
@@ -0,0 +1,17 @@
package com.coloryr.allmusic.client.mixins;
import com.coloryr.allmusic.client.core.AllMusicCore;
import net.minecraft.client.Minecraft;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Minecraft.class)
public class Tick {
@Inject(method = "tick", at = @At("TAIL"))
public void tick(CallbackInfo info) {
AllMusicCore.tick();
}
}
@@ -0,0 +1,11 @@
package com.coloryr.allmusic.comm;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
public class AllMusicInit implements ModInitializer {
@Override
public void onInitialize() {
PayloadTypeRegistry.playS2C().register(MusicCodec.ID, MusicCodec.CODEC);
}
}
@@ -0,0 +1,22 @@
package com.coloryr.allmusic.comm;
import com.coloryr.allmusic.codec.MusicPack;
import com.coloryr.allmusic.codec.MusicPacketCodec;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
public record MusicCodec(MusicPack pack) implements CustomPacketPayload {
public static final ResourceLocation ChannelID = ResourceLocation.fromNamespaceAndPath("allmusic", "channel");
public static final Type<MusicCodec> ID = new Type<>(ChannelID);
public static final StreamCodec<FriendlyByteBuf, MusicCodec> CODEC =
StreamCodec.of((buf, value) -> MusicPacketCodec.pack(buf, value.pack),
buf -> new MusicCodec(MusicPacketCodec.decode(buf)));
@Override
public Type<? extends CustomPacketPayload> type() {
return ID;
}
}
@@ -0,0 +1,18 @@
{
"required": true,
"package": "com.coloryr.allmusic.client.mixins",
"compatibilityLevel": "JAVA_21",
"client": [
"GuiShow",
"QuitServer",
"Register",
"SoundEvent",
"Tick"
],
"injectors": {
"defaultRequire": 1
},
"overwrites": {
"requireAnnotations": true
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@@ -0,0 +1,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": {
}
}
+1
View File
@@ -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")