mirror of
https://github.com/Coloryr/AllMusic.git
synced 2026-11-08 18:54:44 +00:00
26.2
This commit is contained in:
1 parent
6800b95f1f
commit
1e3dc17741
36 files changed
+3339
-2
No files matched your search
@@ -1,5 +1,5 @@
|
||||
object Versions {
|
||||
const val fabricLoom = "1.16-SNAPSHOT"
|
||||
const val fabricLoom = "1.17-SNAPSHOT"
|
||||
const val architecturyLoom = "1.14-SNAPSHOT"
|
||||
const val httpclient5 = "5.6.1"
|
||||
const val httpcore5 = "5.4.2"
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
plugins {
|
||||
id("net.fabricmc.fabric-loom") version Versions.fabricLoom
|
||||
}
|
||||
|
||||
java.sourceCompatibility = JavaVersion.VERSION_25
|
||||
java.targetCompatibility = JavaVersion.VERSION_25
|
||||
|
||||
dependencies {
|
||||
minecraft("com.mojang:minecraft:26.2")
|
||||
implementation("net.fabricmc:fabric-loader:0.19.3")
|
||||
|
||||
implementation("net.fabricmc.fabric-api:fabric-api:0.152.1+26.2")
|
||||
|
||||
// compileOnly("icyllis.modernui:ModernUI-Fabric:26.1.2-3.13.0.4")
|
||||
// compileOnly("de.maxhenkel.voicechat:voicechat-api:2.6.0")
|
||||
}
|
||||
|
||||
tasks {
|
||||
processResources {
|
||||
filesMatching("fabric.mod.json") {
|
||||
expand(
|
||||
"version" to project.version
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
archiveFileName.set("[fabric-26.2]AllMusic_Client-${project.version}.jar")
|
||||
destinationDirectory.set(file("${parent!!.projectDir}/../build"))
|
||||
}
|
||||
|
||||
build {
|
||||
dependsOn(shadowJar)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
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 com.mojang.blaze3d.textures.GpuTexture;
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
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.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.renderer.texture.AbstractTexture;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
|
||||
public static final String MODID = "allmusic_client";
|
||||
public static final Identifier ID = Identifier.fromNamespaceAndPath("allmusic", "channel");
|
||||
public static final Logger LOGGER = LogManager.getLogger("AllMusic Client");
|
||||
public static boolean modui;
|
||||
public static GuiGraphicsExtractor context;
|
||||
|
||||
public static void update(GuiGraphicsExtractor draw) {
|
||||
context = draw;
|
||||
AllMusicCore.hudUpdate();
|
||||
}
|
||||
|
||||
public int getScreenWidth() {
|
||||
return Minecraft.getInstance().getWindow().getGuiScaledWidth();
|
||||
}
|
||||
|
||||
public int getScreenHeight() {
|
||||
return Minecraft.getInstance().getWindow().getGuiScaledHeight();
|
||||
}
|
||||
|
||||
public int getTextWidth(String item) {
|
||||
return Minecraft.getInstance().font.width(item);
|
||||
}
|
||||
|
||||
public int getFontHeight() {
|
||||
return Minecraft.getInstance().font.lineHeight;
|
||||
}
|
||||
|
||||
public void sendMessage(String data) {
|
||||
data = "[AllMusic Client]" + data;
|
||||
LOGGER.warn(data);
|
||||
String finalData = data;
|
||||
Minecraft.getInstance().execute(() -> {
|
||||
if (Minecraft.getInstance().player == null)
|
||||
return;
|
||||
Minecraft.getInstance().player.sendSystemMessage(Component.literal(finalData));
|
||||
});
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getInstance().options.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 {
|
||||
var man = Minecraft.getInstance().getResourceManager();
|
||||
Resource resource = man.getResource(Identifier.fromNamespaceAndPath(MODID, file)).orElseThrow();
|
||||
try (InputStream inputStream = resource.open()) {
|
||||
byte[] bytes = inputStream.readAllBytes();
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream readFile(String file) {
|
||||
try {
|
||||
var man = Minecraft.getInstance().getResourceManager();
|
||||
Resource resource = man.getResource(Identifier.fromNamespaceAndPath(MODID, file)).orElseThrow();
|
||||
return resource.open();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
ClientPlayNetworking.registerGlobalReceiver(MusicCodec.ID, (pack, handler) -> {
|
||||
AllMusicCore.packDo(pack.pack());
|
||||
});
|
||||
|
||||
if (FabricLoader.getInstance().isModLoaded("modernui")) {
|
||||
modui = true;
|
||||
}
|
||||
if (FabricLoader.getInstance().isModLoaded("modernui")) {
|
||||
modui = true;
|
||||
}
|
||||
|
||||
AllMusicCore.init(FabricLoader.getInstance().getConfigDir(), this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
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.GpuFormat;
|
||||
import com.mojang.blaze3d.ProjectionType;
|
||||
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.textures.FilterMode;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.font.TextRenderable;
|
||||
import net.minecraft.client.gui.navigation.ScreenRectangle;
|
||||
import net.minecraft.client.gui.render.TextureSetup;
|
||||
import net.minecraft.client.renderer.*;
|
||||
import net.minecraft.client.renderer.state.gui.*;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.util.profiling.Profiler;
|
||||
import org.joml.*;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.lang.Math;
|
||||
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 GuiRenderState renderState = new GuiRenderState();
|
||||
|
||||
public CoreRenderTarget(String name) {
|
||||
this.name = name;
|
||||
target = new TextureTarget(name, 800, 200, false, GpuFormat.RGBA8_UNORM);
|
||||
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(), new Vector4f(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unUse() {
|
||||
isDraw = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawText(String text, int y, int color, boolean shadow) {
|
||||
var minecraft = Minecraft.getInstance();
|
||||
Window window = minecraft.getWindow();
|
||||
|
||||
var font = Minecraft.getInstance().font;
|
||||
Component component = MiniMessage.parse(text);
|
||||
int width = font.width(component.getVisualOrderText());
|
||||
|
||||
int i = (int) minecraft.mouseHandler.getScaledXPos(window);
|
||||
int j = (int) minecraft.mouseHandler.getScaledYPos(window);
|
||||
|
||||
GuiGraphicsExtractor graphics = new GuiGraphicsExtractor(Minecraft.getInstance(), renderState, i, j);
|
||||
color = color | 0xFF000000;
|
||||
graphics.text(font, component, 0, y, color, shadow);
|
||||
|
||||
renderer.render(this);
|
||||
|
||||
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.addGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
|
||||
TextureSetup.singleTexture(target.getColorTextureView(), RenderSystem.getSamplerCache().getRepeat(FilterMode.NEAREST)),
|
||||
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, offsetX % 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;
|
||||
private final GuiRenderState renderState;
|
||||
private final List<Draw> draws = new ArrayList<>();
|
||||
private final StagedVertexBuffer vertexBuffer = new StagedVertexBuffer(() -> "GUI Vertex Buffer", 786432);
|
||||
private final Projection guiProjection = new Projection();
|
||||
private final ProjectionMatrixBuffer guiProjectionMatrixBuffer = new ProjectionMatrixBuffer("gui");
|
||||
private final CubeMap cubeMap = new CubeMap(Identifier.withDefaultNamespace("textures/gui/title/background/panorama"));
|
||||
private @Nullable ScreenRectangle previousScissorArea = null;
|
||||
private @Nullable RenderPipeline previousPipeline = null;
|
||||
private @Nullable TextureSetup previousTextureSetup = null;
|
||||
private StagedVertexBuffer.@Nullable Draw previousDraw;
|
||||
|
||||
public GuiRenderer(final GuiRenderState renderState) {
|
||||
this.renderState = renderState;
|
||||
}
|
||||
|
||||
public void render(CoreRenderTarget target) {
|
||||
var profiler = Profiler.get();
|
||||
if (this.renderState.panoramaRenderState != null) {
|
||||
this.cubeMap.render(10.0F, this.renderState.panoramaRenderState.spin());
|
||||
}
|
||||
|
||||
profiler.push("prepare");
|
||||
this.prepare();
|
||||
profiler.popPush("upload");
|
||||
this.vertexBuffer.upload();
|
||||
profiler.popPush("draw");
|
||||
this.draw(target);
|
||||
profiler.popPush("endFrame");
|
||||
this.vertexBuffer.endDraw();
|
||||
this.vertexBuffer.endFrame();
|
||||
this.draws.clear();
|
||||
this.renderState.reset();
|
||||
if (SharedConstants.DEBUG_SHUFFLE_UI_RENDERING_ORDER) {
|
||||
RenderPipeline.updateSortKeySeed();
|
||||
TextureSetup.updateSortKeySeed();
|
||||
}
|
||||
|
||||
profiler.pop();
|
||||
}
|
||||
|
||||
private void prepare() {
|
||||
this.prepareText();
|
||||
this.renderState.sortElements(ELEMENT_SORT_COMPARATOR);
|
||||
this.addElementsToMeshes();
|
||||
}
|
||||
|
||||
private void addElementsToMeshes() {
|
||||
this.previousScissorArea = null;
|
||||
this.previousPipeline = null;
|
||||
this.previousTextureSetup = null;
|
||||
this.previousDraw = null;
|
||||
this.renderState.forEachElement(this::addElementToMesh, GuiRenderState.TraverseRange.BEFORE_BLUR);
|
||||
}
|
||||
|
||||
private void draw(CoreRenderTarget target) {
|
||||
if (!this.draws.isEmpty()) {
|
||||
var minecraft = Minecraft.getInstance();
|
||||
var windowState = minecraft.gameRenderer.gameRenderState().windowRenderState;
|
||||
this.guiProjection.setupOrtho(1000.0F, 11000.0F, (float) target.target.width / (float) windowState.guiScale,
|
||||
(float) target.target.height / (float) windowState.guiScale, true);
|
||||
RenderSystem.setProjectionMatrix(this.guiProjectionMatrixBuffer.getBuffer(this.guiProjection), ProjectionType.ORTHOGRAPHIC);
|
||||
var mainRenderTarget = target.target;
|
||||
var dynamicTransforms = RenderSystem.getDynamicUniforms().writeTransform((new Matrix4f()).setTranslation(0.0F, 0.0F, -11000.0F));
|
||||
if (!this.draws.isEmpty()) {
|
||||
this.executeDrawRange(() -> "GUI before blur", mainRenderTarget, dynamicTransforms, this.draws.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void executeDrawRange(Supplier<String> label, RenderTarget mainRenderTarget, GpuBufferSlice dynamicTransforms, int endIndex) {
|
||||
try (var renderPass = RenderSystem.getDevice().createCommandEncoder()
|
||||
.createRenderPass(label, mainRenderTarget.getColorTextureView(),
|
||||
Optional.empty(), mainRenderTarget.useDepth ? mainRenderTarget.getDepthTextureView() : null, OptionalDouble.empty())) {
|
||||
RenderSystem.bindDefaultUniforms(renderPass);
|
||||
renderPass.setUniform("DynamicTransforms", dynamicTransforms);
|
||||
|
||||
for (int i = 0; i < endIndex; ++i) {
|
||||
Draw draw = this.draws.get(i);
|
||||
this.executeDraw(draw, renderPass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addElementToMesh(final GuiElementRenderState elementState) {
|
||||
var pipeline = elementState.pipeline();
|
||||
var textureSetup = elementState.textureSetup();
|
||||
var scissorArea = elementState.scissorArea();
|
||||
if (this.previousDraw == null || pipeline != this.previousPipeline || this.scissorChanged(scissorArea, this.previousScissorArea) || !textureSetup.equals(this.previousTextureSetup)) {
|
||||
this.previousPipeline = pipeline;
|
||||
this.previousTextureSetup = textureSetup;
|
||||
this.previousScissorArea = scissorArea;
|
||||
this.previousDraw = this.vertexBuffer.appendDraw(pipeline.getVertexFormatBinding(0), pipeline.getPrimitiveTopology());
|
||||
this.draws.add(new Draw(this.previousDraw, pipeline, textureSetup, scissorArea));
|
||||
}
|
||||
|
||||
elementState.buildVertices(this.vertexBuffer.getVertexBuilder(Objects.requireNonNull(this.previousDraw)));
|
||||
}
|
||||
|
||||
private void prepareText() {
|
||||
this.renderState.forEachText((text) -> {
|
||||
var pose = text.pose;
|
||||
var scissor = text.scissor;
|
||||
text.ensurePrepared().visit(new Font.GlyphVisitor() {
|
||||
public void acceptRenderable(final TextRenderable renderable) {
|
||||
renderState.addGlyphToCurrentLayer(new GlyphRenderState(pose, renderable, scissor));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void executeDraw(final Draw draw, final RenderPass renderPass) {
|
||||
var executeInfo = this.vertexBuffer.getExecuteInfo(draw.draw);
|
||||
if (executeInfo != null) {
|
||||
var pipeline = draw.pipeline();
|
||||
renderPass.setPipeline(pipeline);
|
||||
renderPass.setVertexBuffer(0, executeInfo.vertexBuffer().slice());
|
||||
var scissorArea = draw.scissorArea();
|
||||
if (scissorArea != null) {
|
||||
this.enableScissor(scissorArea, renderPass);
|
||||
} else {
|
||||
renderPass.disableScissor();
|
||||
}
|
||||
|
||||
if (draw.textureSetup.texure0() != null) {
|
||||
renderPass.bindTexture("Sampler0", draw.textureSetup.texure0(), draw.textureSetup.sampler0());
|
||||
}
|
||||
|
||||
if (draw.textureSetup.texure1() != null) {
|
||||
renderPass.bindTexture("Sampler1", draw.textureSetup.texure1(), draw.textureSetup.sampler1());
|
||||
}
|
||||
|
||||
if (draw.textureSetup.texure2() != null) {
|
||||
renderPass.bindTexture("Sampler2", draw.textureSetup.texure2(), draw.textureSetup.sampler2());
|
||||
}
|
||||
|
||||
renderPass.setIndexBuffer(executeInfo.indexBuffer(), executeInfo.indexType());
|
||||
renderPass.drawIndexed(executeInfo.indexCount(), 1, executeInfo.firstIndex(), executeInfo.baseVertex(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean scissorChanged(final @Nullable ScreenRectangle newScissor, final @Nullable ScreenRectangle oldScissor) {
|
||||
if (newScissor == oldScissor) {
|
||||
return false;
|
||||
} else if (newScissor != null) {
|
||||
return !newScissor.equals(oldScissor);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void enableScissor(final ScreenRectangle rectangle, final RenderPass renderPass) {
|
||||
var window = Minecraft.getInstance().gameRenderer.gameRenderState().windowRenderState;
|
||||
int guiScale = window.guiScale;
|
||||
double left = (rectangle.left() * guiScale);
|
||||
double top = (rectangle.top() * guiScale);
|
||||
double right = Math.min(rectangle.right() * guiScale, window.width);
|
||||
double bottom = Math.min(rectangle.bottom() * guiScale, window.height);
|
||||
renderPass.enableScissor((int) left, window.height - (int) bottom, Math.max(0, (int) (right - left)), Math.max(0, (int) (bottom - top)));
|
||||
}
|
||||
|
||||
public void close() {
|
||||
this.vertexBuffer.close();
|
||||
this.guiProjectionMatrixBuffer.close();
|
||||
this.cubeMap.close();
|
||||
}
|
||||
|
||||
static {
|
||||
ELEMENT_SORT_COMPARATOR = Comparator.comparing(GuiElementRenderState::scissorArea, SCISSOR_COMPARATOR)
|
||||
.thenComparing(GuiElementRenderState::pipeline, Comparator.comparing(RenderPipeline::getSortKey))
|
||||
.thenComparing(GuiElementRenderState::textureSetup, TEXTURE_COMPARATOR);
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
private record Draw(StagedVertexBuffer.Draw draw, RenderPipeline pipeline, TextureSetup textureSetup,
|
||||
@Nullable ScreenRectangle scissorArea) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.renderer.state.gui.GuiElementRenderState;
|
||||
import org.joml.Matrix3x2f;
|
||||
|
||||
public record FloatRenderState(RenderPipeline pipeline, TextureSetup textureSetup, Matrix3x2f pose, float x0,
|
||||
float y0, float x1, float y1, float u0, float u1, float v0, float v1, int color,
|
||||
ScreenRectangle scissorArea,
|
||||
ScreenRectangle bounds) implements GuiElementRenderState {
|
||||
public FloatRenderState(RenderPipeline renderPipeline, TextureSetup textureSetup, Matrix3x2f matrix3x2f,
|
||||
float i, float j, float k, float l, float f, float g, float h, float m, int n, ScreenRectangle screenRectangle) {
|
||||
this(renderPipeline, textureSetup, matrix3x2f, i, j, k, l, f, g, h, m, n, screenRectangle, getBounds(i, j, k, l, matrix3x2f, screenRectangle));
|
||||
}
|
||||
|
||||
private static ScreenRectangle getBounds(float i, float j, float k, float l, Matrix3x2f matrix3x2f, ScreenRectangle screenRectangle) {
|
||||
ScreenRectangle screenRectangle2 = (new ScreenRectangle((int) Math.ceil(i + 1), (int) Math.ceil(j + 1), (int) Math.ceil(k - i + 1), (int) Math.ceil(l - j + 1))).transformMaxBounds(matrix3x2f);
|
||||
return screenRectangle != null ? screenRectangle.intersection(screenRectangle2) : screenRectangle2;
|
||||
}
|
||||
|
||||
public void buildVertices(VertexConsumer vertexConsumer) {
|
||||
vertexConsumer.addVertexWith2DPose(pose(), x0(), y0()).setUv(u0(), v0()).setColor(color());
|
||||
vertexConsumer.addVertexWith2DPose(pose(), x0(), y1()).setUv(u0(), v1()).setColor(color());
|
||||
vertexConsumer.addVertexWith2DPose(pose(), x1(), y1()).setUv(u1(), v1()).setColor(color());
|
||||
vertexConsumer.addVertexWith2DPose(pose(), x1(), y0()).setUv(u1(), v0()).setColor(color());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,645 @@
|
||||
package com.coloryr.allmusic.client;
|
||||
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.*;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class MiniMessage {
|
||||
// 正则表达式匹配标签
|
||||
private static final Pattern TAG_PATTERN = Pattern.compile(
|
||||
"<(/?)(\\w+)(?:\\(([^)]+)\\))?(?::([^>]+))?>"
|
||||
);
|
||||
|
||||
// 颜色映射表
|
||||
private static final Map<String, ChatFormatting> COLOR_MAP = new HashMap<>();
|
||||
// 装饰器映射
|
||||
private static final Map<String, ChatFormatting> DECORATION_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
COLOR_MAP.put("black", ChatFormatting.BLACK);
|
||||
COLOR_MAP.put("dark_blue", ChatFormatting.DARK_BLUE);
|
||||
COLOR_MAP.put("dark_green", ChatFormatting.DARK_GREEN);
|
||||
COLOR_MAP.put("dark_aqua", ChatFormatting.DARK_AQUA);
|
||||
COLOR_MAP.put("dark_red", ChatFormatting.DARK_RED);
|
||||
COLOR_MAP.put("dark_purple", ChatFormatting.DARK_PURPLE);
|
||||
COLOR_MAP.put("gold", ChatFormatting.GOLD);
|
||||
COLOR_MAP.put("gray", ChatFormatting.GRAY);
|
||||
COLOR_MAP.put("dark_gray", ChatFormatting.DARK_GRAY);
|
||||
COLOR_MAP.put("blue", ChatFormatting.BLUE);
|
||||
COLOR_MAP.put("green", ChatFormatting.GREEN);
|
||||
COLOR_MAP.put("aqua", ChatFormatting.AQUA);
|
||||
COLOR_MAP.put("red", ChatFormatting.RED);
|
||||
COLOR_MAP.put("light_purple", ChatFormatting.LIGHT_PURPLE);
|
||||
COLOR_MAP.put("yellow", ChatFormatting.YELLOW);
|
||||
COLOR_MAP.put("white", ChatFormatting.WHITE);
|
||||
}
|
||||
|
||||
static {
|
||||
DECORATION_MAP.put("bold", ChatFormatting.BOLD);
|
||||
DECORATION_MAP.put("italic", ChatFormatting.ITALIC);
|
||||
DECORATION_MAP.put("underlined", ChatFormatting.UNDERLINE);
|
||||
DECORATION_MAP.put("strikethrough", ChatFormatting.STRIKETHROUGH);
|
||||
DECORATION_MAP.put("obfuscated", ChatFormatting.OBFUSCATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 MiniMessage 字符串为 Component
|
||||
*/
|
||||
public static Component parse(String input) {
|
||||
MutableComponent root = Component.literal("");
|
||||
Stack<StyleContext> styleStack = new Stack<>();
|
||||
StyleContext currentStyle = new StyleContext();
|
||||
styleStack.push(currentStyle);
|
||||
|
||||
StringBuilder textBuffer = new StringBuilder();
|
||||
int pos = 0;
|
||||
int length = input.length();
|
||||
|
||||
while (pos < length) {
|
||||
char c = input.charAt(pos);
|
||||
|
||||
if (c == '<') {
|
||||
// 先处理已积累的文本
|
||||
if (textBuffer.length() > 0) {
|
||||
appendText(root, textBuffer.toString(), currentStyle);
|
||||
textBuffer.setLength(0);
|
||||
}
|
||||
|
||||
// 查找标签结束位置
|
||||
int endTag = findClosingTag(input, pos);
|
||||
if (endTag == -1) {
|
||||
// 没有找到闭合标签,当作普通文本处理
|
||||
textBuffer.append(c);
|
||||
pos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
String tagContent = input.substring(pos, endTag + 1);
|
||||
Matcher matcher = TAG_PATTERN.matcher(tagContent);
|
||||
|
||||
if (matcher.matches()) {
|
||||
boolean isClosing = !matcher.group(1).isEmpty();
|
||||
String tagName = matcher.group(2).toLowerCase(Locale.ROOT);
|
||||
String params = matcher.group(3);
|
||||
String value = matcher.group(4);
|
||||
|
||||
if (isClosing) {
|
||||
// 关闭标签 - 只关闭对应的开始标签
|
||||
String expectedOpenTag = matcher.group(2).toLowerCase(Locale.ROOT);
|
||||
|
||||
// 查找对应的开始标签并关闭
|
||||
if (styleStack.size() > 1) {
|
||||
// 从栈顶向下查找匹配的开始标签
|
||||
int index = styleStack.size() - 1;
|
||||
boolean found = false;
|
||||
|
||||
while (index >= 0) {
|
||||
StyleContext ctx = styleStack.get(index);
|
||||
if (ctx.tagName != null && ctx.tagName.equals(expectedOpenTag)) {
|
||||
// 找到匹配的开始标签,关闭到这一层
|
||||
while (styleStack.size() > index + 1) {
|
||||
styleStack.pop();
|
||||
}
|
||||
currentStyle = styleStack.peek();
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
index--;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
// 没找到匹配的标签,忽略此关闭标签
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 处理各种标签
|
||||
StyleContext newStyle = currentStyle.copy();
|
||||
|
||||
switch (tagName) {
|
||||
case "color":
|
||||
handleColorTag(newStyle, params, value);
|
||||
break;
|
||||
case "shadow":
|
||||
handleShadowTag(newStyle, params, value);
|
||||
break;
|
||||
case "decoration":
|
||||
handleDecorationTag(newStyle, params, value);
|
||||
break;
|
||||
case "reset":
|
||||
newStyle = new StyleContext();
|
||||
break;
|
||||
case "rainbow":
|
||||
handleRainbowTag(newStyle, params, value);
|
||||
break;
|
||||
case "gradient":
|
||||
handleGradientTag(newStyle, params, value);
|
||||
break;
|
||||
case "transition":
|
||||
handleTransitionTag(newStyle, params, value);
|
||||
break;
|
||||
case "font":
|
||||
handleFontTag(newStyle, params, value);
|
||||
break;
|
||||
default:
|
||||
// 未知标签,忽略
|
||||
break;
|
||||
}
|
||||
|
||||
// 记录标签名称以便关闭时匹配
|
||||
newStyle.tagName = tagName;
|
||||
styleStack.push(newStyle);
|
||||
currentStyle = newStyle;
|
||||
}
|
||||
} else {
|
||||
// 无效标签,当作普通文本
|
||||
textBuffer.append(tagContent);
|
||||
}
|
||||
|
||||
pos = endTag + 1;
|
||||
} else {
|
||||
textBuffer.append(c);
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理剩余的文本
|
||||
if (textBuffer.length() > 0) {
|
||||
appendText(root, textBuffer.toString(), currentStyle);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理颜色标签
|
||||
* 支持格式: <color:red>, <color:#FF0000>, <color(rgb):255,0,0>
|
||||
*/
|
||||
private static void handleColorTag(StyleContext style, String params, String value) {
|
||||
if (value != null) {
|
||||
// 格式: <color:red> 或 <color:#FF0000>
|
||||
TextColor textColor = parseColor(value);
|
||||
if (textColor != null) {
|
||||
style.color = textColor;
|
||||
// 清除渐变、过渡和彩虹效果
|
||||
style.gradient = false;
|
||||
style.transition = false;
|
||||
style.rainbow = false;
|
||||
}
|
||||
} else if (params != null) {
|
||||
// 格式: <color(rgb):255,0,0>
|
||||
if (params.equalsIgnoreCase("rgb")) {
|
||||
String[] parts = value.split(",");
|
||||
if (parts.length == 3) {
|
||||
try {
|
||||
int r = Integer.parseInt(parts[0].trim());
|
||||
int g = Integer.parseInt(parts[1].trim());
|
||||
int b = Integer.parseInt(parts[2].trim());
|
||||
style.color = TextColor.fromRgb((r << 16) | (g << 8) | b);
|
||||
// 清除渐变、过渡和彩虹效果
|
||||
style.gradient = false;
|
||||
style.transition = false;
|
||||
style.rainbow = false;
|
||||
} catch (NumberFormatException e) {
|
||||
// 忽略解析错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理阴影标签
|
||||
* 支持格式: <shadow>, <shadow:true>, <shadow:false>, <shadow:#FF0000>
|
||||
*/
|
||||
private static void handleShadowTag(StyleContext style, String params, String value) {
|
||||
if (value == null) {
|
||||
style.shadow = true;
|
||||
} else if (value.equalsIgnoreCase("true")) {
|
||||
style.shadow = true;
|
||||
} else if (value.equalsIgnoreCase("false")) {
|
||||
style.shadow = false;
|
||||
} else {
|
||||
// 自定义阴影颜色
|
||||
TextColor shadowColor = parseColor(value);
|
||||
if (shadowColor != null) {
|
||||
style.shadow = true;
|
||||
style.shadowColor = shadowColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理装饰标签
|
||||
* 支持格式: <decoration:bold>, <decoration:bold:true>, <decoration:bold:false>
|
||||
*/
|
||||
private static void handleDecorationTag(StyleContext style, String params, String value) {
|
||||
if (params == null) return;
|
||||
|
||||
String[] parts = params.split(":");
|
||||
String decorationName = parts[0].toLowerCase(Locale.ROOT);
|
||||
boolean enabled = true;
|
||||
|
||||
if (parts.length > 1) {
|
||||
enabled = Boolean.parseBoolean(parts[1]);
|
||||
} else if (value != null) {
|
||||
enabled = Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
ChatFormatting formatting = DECORATION_MAP.get(decorationName);
|
||||
if (formatting != null) {
|
||||
style.decorations.put(formatting, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理彩虹色标签
|
||||
* 支持格式: <rainbow>, <rainbow:!>, <rainbow(saturation):0.8>, <rainbow(brightness):0.5>
|
||||
*/
|
||||
private static void handleRainbowTag(StyleContext style, String params, String value) {
|
||||
float saturation = 1.0f;
|
||||
float brightness = 1.0f;
|
||||
|
||||
if (params != null) {
|
||||
if (params.equalsIgnoreCase("saturation") && value != null) {
|
||||
try {
|
||||
saturation = Float.parseFloat(value);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
} else if (params.equalsIgnoreCase("brightness") && value != null) {
|
||||
try {
|
||||
brightness = Float.parseFloat(value);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
style.rainbow = true;
|
||||
style.gradient = false;
|
||||
style.transition = false;
|
||||
style.rainbowSaturation = saturation;
|
||||
style.rainbowBrightness = brightness;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理渐变色标签
|
||||
* 支持格式: <gradient:#FF0000:#00FF00>, <gradient(rgb):255,0,0:0,255,0>
|
||||
* 渐变是逐字符过渡
|
||||
*/
|
||||
private static void handleGradientTag(StyleContext style, String params, String value) {
|
||||
if (value == null) return;
|
||||
|
||||
String[] colors = value.split(":");
|
||||
if (colors.length == 2) {
|
||||
TextColor start = parseColor(colors[0]);
|
||||
TextColor end = parseColor(colors[1]);
|
||||
|
||||
if (start != null && end != null) {
|
||||
style.gradient = true;
|
||||
style.transition = false;
|
||||
style.rainbow = false;
|
||||
style.gradientStart = start;
|
||||
style.gradientEnd = end;
|
||||
}
|
||||
} else if (params != null && params.equalsIgnoreCase("rgb")) {
|
||||
// 处理 RGB 格式
|
||||
String[] color1 = colors[0].split(",");
|
||||
String[] color2 = colors[1].split(",");
|
||||
if (color1.length == 3 && color2.length == 3) {
|
||||
try {
|
||||
int r1 = Integer.parseInt(color1[0].trim());
|
||||
int g1 = Integer.parseInt(color1[1].trim());
|
||||
int b1 = Integer.parseInt(color1[2].trim());
|
||||
int r2 = Integer.parseInt(color2[0].trim());
|
||||
int g2 = Integer.parseInt(color2[1].trim());
|
||||
int b2 = Integer.parseInt(color2[2].trim());
|
||||
|
||||
style.gradient = true;
|
||||
style.transition = false;
|
||||
style.rainbow = false;
|
||||
style.gradientStart = TextColor.fromRgb((r1 << 16) | (g1 << 8) | b1);
|
||||
style.gradientEnd = TextColor.fromRgb((r2 << 16) | (g2 << 8) | b2);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理过渡色标签
|
||||
* 支持格式: <transition:#FF0000:#00FF00>, <transition(rgb):255,0,0:0,255,0>
|
||||
* 过渡是整个文本块的颜色过渡(类似整体渐变效果)
|
||||
*/
|
||||
private static void handleTransitionTag(StyleContext style, String params, String value) {
|
||||
if (value == null) return;
|
||||
|
||||
String[] colors = value.split(":");
|
||||
if (colors.length == 2) {
|
||||
TextColor start = parseColor(colors[0]);
|
||||
TextColor end = parseColor(colors[1]);
|
||||
|
||||
if (start != null && end != null) {
|
||||
style.transition = true;
|
||||
style.gradient = false;
|
||||
style.rainbow = false;
|
||||
style.transitionStart = start;
|
||||
style.transitionEnd = end;
|
||||
}
|
||||
} else if (params != null && params.equalsIgnoreCase("rgb")) {
|
||||
// 处理 RGB 格式
|
||||
String[] color1 = colors[0].split(",");
|
||||
String[] color2 = colors[1].split(",");
|
||||
if (color1.length == 3 && color2.length == 3) {
|
||||
try {
|
||||
int r1 = Integer.parseInt(color1[0].trim());
|
||||
int g1 = Integer.parseInt(color1[1].trim());
|
||||
int b1 = Integer.parseInt(color1[2].trim());
|
||||
int r2 = Integer.parseInt(color2[0].trim());
|
||||
int g2 = Integer.parseInt(color2[1].trim());
|
||||
int b2 = Integer.parseInt(color2[2].trim());
|
||||
|
||||
style.transition = true;
|
||||
style.gradient = false;
|
||||
style.rainbow = false;
|
||||
style.transitionStart = TextColor.fromRgb((r1 << 16) | (g1 << 8) | b1);
|
||||
style.transitionEnd = TextColor.fromRgb((r2 << 16) | (g2 << 8) | b2);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理字体标签
|
||||
* 支持格式: <font:minecraft:default>, <font:minecraft:uniform>
|
||||
*/
|
||||
private static void handleFontTag(StyleContext style, String params, String value) {
|
||||
String fontName = value != null ? value : params;
|
||||
if (fontName != null) {
|
||||
style.font = fontName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析颜色字符串
|
||||
* 支持: 颜色名称, #RRGGBB, #RGB, 十六进制数值
|
||||
*/
|
||||
private static TextColor parseColor(String colorStr) {
|
||||
if (colorStr == null) return null;
|
||||
|
||||
// 检查是否为预定义颜色名称
|
||||
ChatFormatting formatting = COLOR_MAP.get(colorStr.toLowerCase(Locale.ROOT));
|
||||
if (formatting != null) {
|
||||
return TextColor.fromLegacyFormat(formatting);
|
||||
}
|
||||
|
||||
// 检查十六进制格式
|
||||
if (colorStr.startsWith("#")) {
|
||||
try {
|
||||
if (colorStr.length() == 7) {
|
||||
return TextColor.parseColor(colorStr).getOrThrow();
|
||||
} else if (colorStr.length() == 4) {
|
||||
// 扩展 #RGB 到 #RRGGBB
|
||||
String expanded = "#" +
|
||||
colorStr.charAt(1) + colorStr.charAt(1) +
|
||||
colorStr.charAt(2) + colorStr.charAt(2) +
|
||||
colorStr.charAt(3) + colorStr.charAt(3);
|
||||
return TextColor.parseColor(expanded).getOrThrow();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 忽略解析错误
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找标签闭合位置
|
||||
*/
|
||||
private static int findClosingTag(String input, int start) {
|
||||
int pos = start + 1;
|
||||
while (pos < input.length()) {
|
||||
char c = input.charAt(pos);
|
||||
if (c == '>') {
|
||||
return pos;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加文本到组件
|
||||
*/
|
||||
private static void appendText(MutableComponent root, String text, StyleContext styleContext) {
|
||||
if (text.isEmpty()) return;
|
||||
|
||||
// 如果有渐变效果,需要逐字符处理
|
||||
if (styleContext.gradient && styleContext.gradientStart != null && styleContext.gradientEnd != null) {
|
||||
appendGradientText(root, text, styleContext);
|
||||
}
|
||||
// 如果有过渡效果,需要逐字符处理
|
||||
else if (styleContext.transition && styleContext.transitionStart != null && styleContext.transitionEnd != null) {
|
||||
appendTransitionText(root, text, styleContext);
|
||||
}
|
||||
// 如果有彩虹效果,需要逐字符处理
|
||||
else if (styleContext.rainbow) {
|
||||
appendRainbowText(root, text, styleContext);
|
||||
}
|
||||
// 普通文本
|
||||
else {
|
||||
MutableComponent component = Component.literal(text);
|
||||
component.setStyle(styleContext.buildStyle());
|
||||
root.append(component);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 带渐变效果的文本追加(逐字符颜色变化)
|
||||
*/
|
||||
private static void appendGradientText(MutableComponent root, String text, StyleContext styleContext) {
|
||||
int length = text.length();
|
||||
Style baseStyle = styleContext.buildBaseStyle();
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = text.charAt(i);
|
||||
float ratio = (float) i / (length - 1);
|
||||
TextColor gradientColor = interpolateColor(styleContext.gradientStart, styleContext.gradientEnd, ratio);
|
||||
|
||||
Style charStyle = baseStyle.withColor(gradientColor);
|
||||
// 重新应用阴影
|
||||
if (styleContext.shadow != null && styleContext.shadow) {
|
||||
if (styleContext.shadowColor != null) {
|
||||
charStyle = charStyle.withShadowColor(styleContext.shadowColor.getValue() | 0xFF000000);
|
||||
} else {
|
||||
charStyle = charStyle.withShadowColor(0xFF000000);
|
||||
}
|
||||
}
|
||||
|
||||
root.append(Component.literal(String.valueOf(c)).withStyle(charStyle));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 带过渡效果的文本追加(整体颜色过渡)
|
||||
*/
|
||||
private static void appendTransitionText(MutableComponent root, String text, StyleContext styleContext) {
|
||||
Style baseStyle = styleContext.buildBaseStyle();
|
||||
|
||||
// 过渡效果:为整个文本块创建一个颜色过渡
|
||||
MutableComponent transitionComponent = Component.literal(text);
|
||||
|
||||
// 应用过渡样式(这里使用颜色插值,但也可以根据需求调整)
|
||||
if (styleContext.transitionStart != null && styleContext.transitionEnd != null) {
|
||||
// 可以使用整体渐变或者固定起始色
|
||||
transitionComponent.setStyle(baseStyle.withColor(styleContext.transitionStart));
|
||||
} else {
|
||||
transitionComponent.setStyle(baseStyle);
|
||||
}
|
||||
|
||||
root.append(transitionComponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 带彩虹效果的文本追加
|
||||
*/
|
||||
private static void appendRainbowText(MutableComponent root, String text, StyleContext styleContext) {
|
||||
int length = text.length();
|
||||
Style baseStyle = styleContext.buildBaseStyle();
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = text.charAt(i);
|
||||
float hue = (float) i / length;
|
||||
int rgb = java.awt.Color.HSBtoRGB(hue, styleContext.rainbowSaturation, styleContext.rainbowBrightness);
|
||||
TextColor rainbowColor = TextColor.fromRgb(rgb & 0xFFFFFF);
|
||||
|
||||
Style charStyle = baseStyle.withColor(rainbowColor);
|
||||
// 重新应用阴影
|
||||
if (styleContext.shadow != null && styleContext.shadow) {
|
||||
if (styleContext.shadowColor != null) {
|
||||
charStyle = charStyle.withShadowColor(styleContext.shadowColor.getValue() | 0xFF000000);
|
||||
} else {
|
||||
charStyle = charStyle.withShadowColor(0xFF000000);
|
||||
}
|
||||
}
|
||||
|
||||
root.append(Component.literal(String.valueOf(c)).withStyle(charStyle));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 颜色插值
|
||||
*/
|
||||
private static TextColor interpolateColor(TextColor start, TextColor end, float ratio) {
|
||||
int startRgb = start.getValue();
|
||||
int endRgb = end.getValue();
|
||||
|
||||
int r1 = (startRgb >> 16) & 0xFF;
|
||||
int g1 = (startRgb >> 8) & 0xFF;
|
||||
int b1 = startRgb & 0xFF;
|
||||
|
||||
int r2 = (endRgb >> 16) & 0xFF;
|
||||
int g2 = (endRgb >> 8) & 0xFF;
|
||||
int b2 = endRgb & 0xFF;
|
||||
|
||||
int r = (int) (r1 + (r2 - r1) * ratio);
|
||||
int g = (int) (g1 + (g2 - g1) * ratio);
|
||||
int b = (int) (b1 + (b2 - b1) * ratio);
|
||||
|
||||
return TextColor.fromRgb((r << 16) | (g << 8) | b);
|
||||
}
|
||||
|
||||
/**
|
||||
* 样式上下文类,用于追踪嵌套样式
|
||||
*/
|
||||
private static class StyleContext {
|
||||
String tagName = null; // 记录标签名称,用于关闭匹配
|
||||
TextColor color = null;
|
||||
Boolean shadow = null;
|
||||
TextColor shadowColor = null;
|
||||
Map<ChatFormatting, Boolean> decorations = new HashMap<>();
|
||||
boolean rainbow = false;
|
||||
float rainbowSaturation = 1.0f;
|
||||
float rainbowBrightness = 1.0f;
|
||||
boolean gradient = false;
|
||||
TextColor gradientStart = null;
|
||||
TextColor gradientEnd = null;
|
||||
boolean transition = false;
|
||||
TextColor transitionStart = null;
|
||||
TextColor transitionEnd = null;
|
||||
String font = null;
|
||||
|
||||
StyleContext copy() {
|
||||
StyleContext copy = new StyleContext();
|
||||
copy.tagName = this.tagName;
|
||||
copy.color = this.color;
|
||||
copy.shadow = this.shadow;
|
||||
copy.shadowColor = this.shadowColor;
|
||||
copy.decorations.putAll(this.decorations);
|
||||
copy.rainbow = this.rainbow;
|
||||
copy.rainbowSaturation = this.rainbowSaturation;
|
||||
copy.rainbowBrightness = this.rainbowBrightness;
|
||||
copy.gradient = this.gradient;
|
||||
copy.gradientStart = this.gradientStart;
|
||||
copy.gradientEnd = this.gradientEnd;
|
||||
copy.transition = this.transition;
|
||||
copy.transitionStart = this.transitionStart;
|
||||
copy.transitionEnd = this.transitionEnd;
|
||||
copy.font = this.font;
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建基础样式(不含颜色)
|
||||
*/
|
||||
Style buildBaseStyle() {
|
||||
Style style = Style.EMPTY;
|
||||
|
||||
// 应用装饰器
|
||||
for (Map.Entry<ChatFormatting, Boolean> entry : decorations.entrySet()) {
|
||||
if (entry.getValue()) {
|
||||
style = style.applyFormat(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
if (font != null) {
|
||||
try {
|
||||
style = style.withFont(new FontDescription.Resource(Identifier.parse(font)));
|
||||
} catch (Exception e) {
|
||||
// 忽略字体解析错误
|
||||
}
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建完整样式(含颜色)
|
||||
*/
|
||||
Style buildStyle() {
|
||||
Style style = buildBaseStyle();
|
||||
|
||||
// 只有在没有渐变、过渡和彩虹效果时才应用普通颜色
|
||||
if (!gradient && !transition && !rainbow && color != null) {
|
||||
style = style.withColor(color);
|
||||
}
|
||||
|
||||
if (shadow != null && shadow) {
|
||||
if (shadowColor != null) {
|
||||
style = style.withShadowColor(shadowColor.getValue() | 0xFF000000);
|
||||
} else {
|
||||
style = style.withShadowColor(0xFF000000);
|
||||
}
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.coloryr.allmusic.client;
|
||||
|
||||
import com.coloryr.allmusic.client.core.AllMusicHud;
|
||||
import com.coloryr.allmusic.client.core.Point2f;
|
||||
import com.coloryr.allmusic.client.core.render.PictureFrameBuffer;
|
||||
import com.coloryr.allmusic.codec.HudPosType;
|
||||
import com.mojang.blaze3d.platform.NativeImage;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.textures.FilterMode;
|
||||
import net.minecraft.client.gui.render.TextureSetup;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.client.renderer.texture.DynamicTexture;
|
||||
import org.joml.Matrix3x2fStack;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
public class PicRender extends PictureFrameBuffer {
|
||||
private final DynamicTexture sourceTexture;
|
||||
private final DynamicTexture rotateTexture;
|
||||
|
||||
public PicRender(int size) {
|
||||
sourceTexture = new DynamicTexture("allmusic source", size, size, false);
|
||||
rotateTexture = new DynamicTexture("allmusic rotate", size, size, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte[] source, byte[] rotate) {
|
||||
try {
|
||||
ByteArrayInputStream stream = new ByteArrayInputStream(source);
|
||||
var image1 = NativeImage.read(stream);
|
||||
sourceTexture.setPixels(image1);
|
||||
sourceTexture.upload();
|
||||
|
||||
stream = new ByteArrayInputStream(rotate);
|
||||
var image2 = NativeImage.read(stream);
|
||||
rotateTexture.setPixels(image2);
|
||||
rotateTexture.upload();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(boolean rotate, int size, float x, float y, int ang, HudPosType dir, float alpha) {
|
||||
Point2f point = AllMusicHud.getPos(size, size, x, y, dir);
|
||||
|
||||
Matrix3x2fStack matrix = new Matrix3x2fStack(2);
|
||||
|
||||
float a = (float) size / 2;
|
||||
|
||||
if (rotate) {
|
||||
matrix.translation(point.x + a, point.y + a);
|
||||
matrix.pushMatrix().rotate((float) Math.toRadians(ang));
|
||||
} else {
|
||||
matrix.translation(point.x + a, point.y + a);
|
||||
}
|
||||
|
||||
float x0 = -a;
|
||||
float x1 = a;
|
||||
float y0 = -a;
|
||||
float y1 = a;
|
||||
float u0 = 0;
|
||||
float u1 = 1;
|
||||
float v0 = 0;
|
||||
float v1 = 1;
|
||||
|
||||
int color = 0xFFFFFF00 + (int) (255 * alpha);
|
||||
|
||||
AllMusicClient.context.guiRenderState.addGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
|
||||
TextureSetup.singleTexture(rotate ? rotateTexture.getTextureView() : sourceTexture.getTextureView(), RenderSystem.getSamplerCache().getRepeat(FilterMode.NEAREST)),
|
||||
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.scissorStack.peek()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
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 com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.textures.FilterMode;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.render.TextureSetup;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.client.renderer.texture.SimpleTexture;
|
||||
import net.minecraft.client.renderer.texture.TextureContents;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import org.joml.Matrix3x2f;
|
||||
|
||||
public class TexRender extends TextureRender {
|
||||
private final SimpleTexture sourceTexture;
|
||||
|
||||
public TexRender(String texture) {
|
||||
super(texture);
|
||||
Identifier location = Identifier.fromNamespaceAndPath(AllMusicClient.MODID, texture);
|
||||
sourceTexture = new SimpleTexture(location);
|
||||
|
||||
try {
|
||||
TextureContents contents = sourceTexture.loadContents(Minecraft.getInstance().getResourceManager());
|
||||
|
||||
width = contents.image().getWidth();
|
||||
height = contents.image().getHeight();
|
||||
|
||||
sourceTexture.apply(contents);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawPic(float x, float y, float alpha) {
|
||||
Matrix3x2f matrix = new Matrix3x2f();
|
||||
|
||||
float w1 = (float) width / 2;
|
||||
float h1 = (float) height / 2;
|
||||
|
||||
matrix.translation(x + w1, y + h1);
|
||||
|
||||
float x0 = -w1;
|
||||
float x1 = w1;
|
||||
float y0 = -h1;
|
||||
float y1 = h1;
|
||||
float u0 = 0;
|
||||
float u1 = 1;
|
||||
float v0 = 0;
|
||||
float v1 = 1;
|
||||
|
||||
int color = 0xFFFFFF00 + (int) (255 * alpha);
|
||||
|
||||
AllMusicClient.context.guiRenderState.addGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
|
||||
TextureSetup.singleTexture(sourceTexture.getTextureView(), RenderSystem.getSamplerCache().getRepeat(FilterMode.NEAREST)),
|
||||
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.scissorStack.peek()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawPic(float x, float y, float width, float alpha) {
|
||||
Matrix3x2f matrix = new Matrix3x2f();
|
||||
|
||||
float w1 = ((float) this.width / 2) * width;
|
||||
float h1 = (float) height / 2;
|
||||
|
||||
matrix.translation(x + w1, y + h1);
|
||||
|
||||
float x0 = -w1;
|
||||
float x1 = w1;
|
||||
float y0 = -h1;
|
||||
float y1 = h1;
|
||||
float u0 = 0;
|
||||
float u1 = width;
|
||||
float v0 = 0;
|
||||
float v1 = 1;
|
||||
|
||||
int color = 0xFFFFFF00 + (int) (255 * alpha);
|
||||
|
||||
AllMusicClient.context.guiRenderState.addGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
|
||||
TextureSetup.singleTexture(sourceTexture.getTextureView(), RenderSystem.getSamplerCache().getRepeat(FilterMode.NEAREST)),
|
||||
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.scissorStack.peek()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawPic(float x, float y, float width, float height, HudPosType dir, float alpha) {
|
||||
Point2f point = AllMusicHud.getPos(width, height, x, y, dir);
|
||||
|
||||
float w1 = width / 2;
|
||||
float h1 = height / 2;
|
||||
|
||||
Matrix3x2f matrix = new Matrix3x2f().translation(point.x + w1, point.y + h1);
|
||||
|
||||
float x0 = -w1;
|
||||
float x1 = w1;
|
||||
float y0 = -h1;
|
||||
float y1 = h1;
|
||||
float u0 = 0;
|
||||
float u1 = 1;
|
||||
float v0 = 0;
|
||||
float v1 = 1;
|
||||
|
||||
int color = 0xFFFFFF00 + (int) (255 * alpha);
|
||||
|
||||
AllMusicClient.context.guiRenderState.addGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
|
||||
TextureSetup.singleTexture(sourceTexture.getTextureView(), RenderSystem.getSamplerCache().getRepeat(FilterMode.NEAREST)),
|
||||
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.scissorStack.peek()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.coloryr.allmusic.client.mixin;
|
||||
|
||||
import com.coloryr.allmusic.client.AllMusicClient;
|
||||
import net.minecraft.client.DeltaTracker;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.Hud;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(Hud.class)
|
||||
public class GuiShow {
|
||||
@Inject(method = "extractRenderState", at = @At(value = "HEAD"))
|
||||
public void guiShow(GuiGraphicsExtractor graphics, DeltaTracker deltaTracker, CallbackInfo ci) {
|
||||
AllMusicClient.update(graphics);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.coloryr.allmusic.client.mixin;
|
||||
|
||||
import com.coloryr.allmusic.client.core.AllMusicCore;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(Minecraft.class)
|
||||
public class QuitServer {
|
||||
@Inject(method = "disconnect(Lnet/minecraft/client/gui/screens/Screen;ZZ)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GameRenderer;resetData()V"))
|
||||
public void quit(CallbackInfo info) {
|
||||
AllMusicCore.onServerQuit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.coloryr.allmusic.client.mixin;
|
||||
|
||||
import com.coloryr.allmusic.client.core.AllMusicCore;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(Minecraft.class)
|
||||
public class Register {
|
||||
@Inject(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/PacketProcessor;<init>(Ljava/lang/Thread;)V"))
|
||||
public void register(CallbackInfo info) {
|
||||
AllMusicCore.renderInit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.coloryr.allmusic.client.mixin;
|
||||
|
||||
import com.coloryr.allmusic.client.core.AllMusicCore;
|
||||
import net.minecraft.client.resources.sounds.SoundInstance;
|
||||
import net.minecraft.client.sounds.SoundEngine;
|
||||
import net.minecraft.client.sounds.SoundManager;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(SoundManager.class)
|
||||
public class SoundEvent {
|
||||
@Inject(method = "play", at = @At("HEAD"), cancellable = true)
|
||||
public void play(SoundInstance sound, CallbackInfoReturnable<SoundEngine.PlayResult> cir) {
|
||||
if (AllMusicCore.isPlay()) {
|
||||
SoundSource data = sound.getSource();
|
||||
switch (data) {
|
||||
case RECORDS, MUSIC: {
|
||||
cir.cancel();
|
||||
cir.setReturnValue(SoundEngine.PlayResult.NOT_STARTED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "playDelayed", at = @At("HEAD"), cancellable = true)
|
||||
public void play(SoundInstance soundInstance, int delay, CallbackInfo info) {
|
||||
if (AllMusicCore.isPlay()) {
|
||||
SoundSource data = soundInstance.getSource();
|
||||
switch (data) {
|
||||
case RECORDS, MUSIC -> info.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "reload", at = @At("RETURN"))
|
||||
public void reload(CallbackInfo info) {
|
||||
AllMusicCore.reload();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.coloryr.allmusic.client.mixin;
|
||||
|
||||
import com.coloryr.allmusic.client.core.AllMusicCore;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(Minecraft.class)
|
||||
public class Tick {
|
||||
@Inject(method = "tick", at = @At("HEAD"))
|
||||
public void tick(CallbackInfo info) {
|
||||
AllMusicCore.tick();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//package com.coloryr.allmusic.client.others;
|
||||
//
|
||||
//import icyllis.modernui.mc.text.ModernPreparedText;
|
||||
//import net.minecraft.client.gui.Font;
|
||||
//import net.minecraft.client.gui.navigation.ScreenRectangle;
|
||||
//import net.minecraft.client.renderer.state.gui.GuiRenderState;
|
||||
//import org.joml.Matrix3x2fc;
|
||||
//
|
||||
//public class ModernUI {
|
||||
// public static void text(Font.PreparedText preparedText, GuiRenderState state, Matrix3x2fc pose, ScreenRectangle scissor) {
|
||||
// if (preparedText instanceof ModernPreparedText) {
|
||||
// ((ModernPreparedText) preparedText).submitRuns(state, pose, scissor);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,32 @@
|
||||
//package com.coloryr.allmusic.client.others;
|
||||
//
|
||||
//import com.coloryr.allmusic.client.core.AllMusicCore;
|
||||
//import de.maxhenkel.voicechat.api.VoicechatPlugin;
|
||||
//import de.maxhenkel.voicechat.api.events.ClientReceiveSoundEvent;
|
||||
//import de.maxhenkel.voicechat.api.events.EventRegistration;
|
||||
//
|
||||
//public class VoiceChat implements VoicechatPlugin {
|
||||
// @Override
|
||||
// public String getPluginId() {
|
||||
// return "allmusic";
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void registerEvents(EventRegistration registration) {
|
||||
// registration.registerEvent(ClientReceiveSoundEvent.EntitySound.class, this::play);
|
||||
// registration.registerEvent(ClientReceiveSoundEvent.LocationalSound.class, this::play);
|
||||
// registration.registerEvent(ClientReceiveSoundEvent.StaticSound.class, this::play);
|
||||
// }
|
||||
//
|
||||
// public void play(ClientReceiveSoundEvent.EntitySound event) {
|
||||
// AllMusicCore.chat();
|
||||
// }
|
||||
//
|
||||
// public void play(ClientReceiveSoundEvent.LocationalSound event) {
|
||||
// AllMusicCore.chat();
|
||||
// }
|
||||
//
|
||||
// public void play(ClientReceiveSoundEvent.StaticSound event) {
|
||||
// AllMusicCore.chat();
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.coloryr.allmusic.comm;
|
||||
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
|
||||
|
||||
public class AllMusicInit implements ModInitializer {
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
PayloadTypeRegistry.clientboundPlay().register(MusicCodec.ID, MusicCodec.CODEC);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.coloryr.allmusic.comm;
|
||||
|
||||
import com.coloryr.allmusic.codec.MusicPack;
|
||||
import com.coloryr.allmusic.codec.MusicPacketCodec;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
|
||||
public record MusicCodec(MusicPack pack) implements CustomPacketPayload {
|
||||
public static final Identifier ChannelID = Identifier.fromNamespaceAndPath("allmusic", "channel");
|
||||
|
||||
public static final Type<MusicCodec> ID = new Type<>(ChannelID);
|
||||
public static final StreamCodec<FriendlyByteBuf, MusicCodec> CODEC =
|
||||
StreamCodec.of((buf, value) -> MusicPacketCodec.pack(buf, value.pack),
|
||||
buf -> new MusicCodec(MusicPacketCodec.decode(buf)));
|
||||
|
||||
@Override
|
||||
public @NonNull Type<? extends CustomPacketPayload> type() {
|
||||
return ID;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"required": true,
|
||||
"package": "com.coloryr.allmusic.client.mixin",
|
||||
"compatibilityLevel": "JAVA_25",
|
||||
"client": [
|
||||
"GuiShow",
|
||||
"QuitServer",
|
||||
"Register",
|
||||
"SoundEvent",
|
||||
"Tick"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
},
|
||||
"overwrites": {
|
||||
"requireAnnotations": true
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "allmusic_client",
|
||||
"version": "${version}",
|
||||
"name": "AllMusic Client",
|
||||
"description": "AllMusic Client Mod",
|
||||
"authors": [
|
||||
"Color_yr"
|
||||
],
|
||||
"contact": {
|
||||
"homepage": "https://github.com/Coloryr/AllMusic",
|
||||
"sources": "https://github.com/Coloryr/AllMusic"
|
||||
},
|
||||
"license": "GPL-3.0",
|
||||
"icon": "assets/allmusic_client/icon.png",
|
||||
"environment": "*",
|
||||
"entrypoints": {
|
||||
"main": [
|
||||
"com.coloryr.allmusic.comm.AllMusicInit"
|
||||
],
|
||||
"client": [
|
||||
"com.coloryr.allmusic.client.AllMusicClient"
|
||||
],
|
||||
"voicechat": [
|
||||
"com.coloryr.allmusic.client.others.VoiceChat"
|
||||
]
|
||||
},
|
||||
"mixins": [
|
||||
{
|
||||
"config": "allmusic_client.mixins.json",
|
||||
"environment": "client"
|
||||
}
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=0.19.3",
|
||||
"minecraft": "~26.2-",
|
||||
"java": ">=25",
|
||||
"fabric-api": "*"
|
||||
},
|
||||
"suggests": {
|
||||
"modernui": "*",
|
||||
"voicechat_api": "*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
plugins {
|
||||
id("dev.architectury.loom-no-remap") version Versions.architecturyLoom
|
||||
// id("architectury-plugin") version "3.5-SNAPSHOT"
|
||||
}
|
||||
|
||||
java.sourceCompatibility = JavaVersion.VERSION_25
|
||||
java.targetCompatibility = JavaVersion.VERSION_25
|
||||
|
||||
//architectury {
|
||||
// platformSetupLoomIde()
|
||||
// neoForge()
|
||||
//}
|
||||
|
||||
repositories {
|
||||
maven("https://maven.neoforged.net/releases/")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
minecraft("com.mojang:minecraft:26.2")
|
||||
neoForge("net.neoforged:neoforge:26.2.0.1-beta")
|
||||
|
||||
// compileOnly("icyllis.modernui:ModernUI-NeoForge:26.1.2-3.13.0.4")
|
||||
// compileOnly("de.maxhenkel.voicechat:voicechat-api:2.6.0")
|
||||
}
|
||||
|
||||
tasks {
|
||||
processResources {
|
||||
filesMatching("META-INF/neoforge.mods.toml") {
|
||||
expand("version" to project.version)
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
archiveFileName.set("[neoforge-26.2]AllMusic_Client-${project.version}.jar")
|
||||
destinationDirectory.set(file("${parent!!.projectDir}/../build"))
|
||||
}
|
||||
|
||||
build {
|
||||
dependsOn(shadowJar)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
loom.platform=neoforge
|
||||
@@ -0,0 +1,185 @@
|
||||
package com.coloryr.allmusic.client;
|
||||
|
||||
import com.coloryr.allmusic.client.core.AllMusicBridge;
|
||||
import com.coloryr.allmusic.client.core.AllMusicCore;
|
||||
import com.coloryr.allmusic.client.core.render.PictureFrameBuffer;
|
||||
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
|
||||
import com.coloryr.allmusic.client.core.render.TextureRender;
|
||||
import com.coloryr.allmusic.comm.AllMusicInit;
|
||||
import com.coloryr.allmusic.comm.MusicCodec;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.ModList;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
import net.neoforged.fml.loading.FMLPaths;
|
||||
import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent;
|
||||
import net.neoforged.neoforge.client.event.ClientTickEvent;
|
||||
import net.neoforged.neoforge.client.event.RenderGuiLayerEvent;
|
||||
import net.neoforged.neoforge.client.event.sound.PlaySoundSourceEvent;
|
||||
import net.neoforged.neoforge.client.event.sound.PlayStreamingSourceEvent;
|
||||
import net.neoforged.neoforge.client.event.sound.SoundEngineLoadEvent;
|
||||
import net.neoforged.neoforge.client.gui.VanillaGuiLayers;
|
||||
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||
import net.neoforged.neoforge.network.handling.IPayloadHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@EventBusSubscriber(modid = AllMusicClient.MODID, value = Dist.CLIENT)
|
||||
public class AllMusicClient implements IPayloadHandler<MusicCodec>, AllMusicBridge {
|
||||
public static final String MODID = "allmusic_client";
|
||||
public static boolean modui;
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger("AllMusic Client");
|
||||
public static GuiGraphicsExtractor context;
|
||||
|
||||
@SubscribeEvent
|
||||
public static void setup(final FMLClientSetupEvent event) {
|
||||
AllMusicClient client = new AllMusicClient();
|
||||
AllMusicInit.handler = client;
|
||||
AllMusicCore.init(FMLPaths.CONFIGDIR.get(), client);
|
||||
|
||||
Minecraft.getInstance().execute(AllMusicCore::renderInit);
|
||||
|
||||
if (ModList.get().isLoaded("modernui")) {
|
||||
modui = true;
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onSound(final PlaySoundSourceEvent e) {
|
||||
if (!AllMusicCore.isPlay()) return;
|
||||
SoundSource data = e.getSound().getSource();
|
||||
switch (data) {
|
||||
case MUSIC, RECORDS -> e.getChannel().stop();
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onSound(final PlayStreamingSourceEvent e) {
|
||||
if (!AllMusicCore.isPlay()) return;
|
||||
SoundSource data = e.getSound().getSource();
|
||||
switch (data) {
|
||||
case MUSIC, RECORDS -> e.getChannel().stop();
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onServerQuit(final ClientPlayerNetworkEvent.LoggingOut e) {
|
||||
AllMusicCore.onServerQuit();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onLoad(final SoundEngineLoadEvent e) {
|
||||
AllMusicCore.reload();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRenderOverlay(RenderGuiLayerEvent.Post e) {
|
||||
if (e.getName().equals(VanillaGuiLayers.CAMERA_OVERLAYS)) {
|
||||
context = e.getGuiGraphics();
|
||||
AllMusicCore.hudUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onTick(ClientTickEvent.Post event) {
|
||||
AllMusicCore.tick();
|
||||
}
|
||||
|
||||
public void sendMessage(String data) {
|
||||
data = "[AllMusic Client]" + data;
|
||||
LOGGER.warn(data);
|
||||
String finalData = data;
|
||||
Minecraft.getInstance().execute(() -> {
|
||||
if (Minecraft.getInstance().player == null)
|
||||
return;
|
||||
Minecraft.getInstance().player.sendSystemMessage(Component.literal(finalData));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MusicCodec pack, IPayloadContext iPayloadContext) {
|
||||
try {
|
||||
AllMusicCore.packDo(pack.pack());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopPlayMusic() {
|
||||
Minecraft.getInstance().getSoundManager().stop(null, SoundSource.MUSIC);
|
||||
Minecraft.getInstance().getSoundManager().stop(null, SoundSource.RECORDS);
|
||||
}
|
||||
|
||||
public int getScreenWidth() {
|
||||
return Minecraft.getInstance().getWindow().getGuiScaledWidth();
|
||||
}
|
||||
|
||||
public int getScreenHeight() {
|
||||
return Minecraft.getInstance().getWindow().getGuiScaledHeight();
|
||||
}
|
||||
|
||||
public int getTextWidth(String item) {
|
||||
return Minecraft.getInstance().font.width(item);
|
||||
}
|
||||
|
||||
public int getFontHeight() {
|
||||
return Minecraft.getInstance().font.lineHeight;
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return Minecraft.getInstance().options.getSoundSourceVolume(SoundSource.RECORDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextFrameBuffer makeTextRender(String name) {
|
||||
return new CoreRenderTarget(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextureRender makeTextureRender(String file) {
|
||||
return new TexRender(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PictureFrameBuffer makePictureRender(int size) {
|
||||
return new PicRender(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readText(String file) {
|
||||
try {
|
||||
var man = Minecraft.getInstance().getResourceManager();
|
||||
Resource resource = man.getResource(Identifier.fromNamespaceAndPath(MODID, file)).orElseThrow();
|
||||
try (InputStream inputStream = resource.open()) {
|
||||
byte[] bytes = inputStream.readAllBytes();
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream readFile(String file) {
|
||||
try {
|
||||
var man = Minecraft.getInstance().getResourceManager();
|
||||
Resource resource = man.getResource(Identifier.fromNamespaceAndPath(MODID, file)).orElseThrow();
|
||||
return resource.open();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
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.GpuFormat;
|
||||
import com.mojang.blaze3d.ProjectionType;
|
||||
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.textures.FilterMode;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.font.TextRenderable;
|
||||
import net.minecraft.client.gui.navigation.ScreenRectangle;
|
||||
import net.minecraft.client.gui.render.TextureSetup;
|
||||
import net.minecraft.client.renderer.*;
|
||||
import net.minecraft.client.renderer.state.gui.GlyphRenderState;
|
||||
import net.minecraft.client.renderer.state.gui.GuiElementRenderState;
|
||||
import net.minecraft.client.renderer.state.gui.GuiRenderState;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.util.profiling.Profiler;
|
||||
import org.joml.Matrix3x2f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector4f;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
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 GuiRenderState renderState = new GuiRenderState();
|
||||
|
||||
public CoreRenderTarget(String name) {
|
||||
this.name = name;
|
||||
target = new TextureTarget(name, 800, 200, false, GpuFormat.RGBA8_UNORM);
|
||||
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(), new Vector4f(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unUse() {
|
||||
isDraw = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawText(String text, int y, int color, boolean shadow) {
|
||||
var minecraft = Minecraft.getInstance();
|
||||
Window window = minecraft.getWindow();
|
||||
|
||||
var font = Minecraft.getInstance().font;
|
||||
Component component = MiniMessage.parse(text);
|
||||
int width = font.width(component.getVisualOrderText());
|
||||
|
||||
int i = (int) minecraft.mouseHandler.getScaledXPos(window);
|
||||
int j = (int) minecraft.mouseHandler.getScaledYPos(window);
|
||||
|
||||
GuiGraphicsExtractor graphics = new GuiGraphicsExtractor(Minecraft.getInstance(), renderState, i, j);
|
||||
color = color | 0xFF000000;
|
||||
graphics.text(font, component, 0, y, color, shadow);
|
||||
|
||||
renderer.render(this);
|
||||
|
||||
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.submitGuiElementRenderState(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
|
||||
TextureSetup.singleTexture(target.getColorTextureView(), RenderSystem.getSamplerCache().getRepeat(FilterMode.NEAREST)),
|
||||
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.peekScissorStack()));
|
||||
}
|
||||
|
||||
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, offsetX % 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;
|
||||
private final GuiRenderState renderState;
|
||||
private final List<Draw> draws = new ArrayList<>();
|
||||
private final StagedVertexBuffer vertexBuffer = new StagedVertexBuffer(() -> "GUI Vertex Buffer", 786432);
|
||||
private final Projection guiProjection = new Projection();
|
||||
private final ProjectionMatrixBuffer guiProjectionMatrixBuffer = new ProjectionMatrixBuffer("gui");
|
||||
private final CubeMap cubeMap = new CubeMap(Identifier.withDefaultNamespace("textures/gui/title/background/panorama"));
|
||||
private @Nullable ScreenRectangle previousScissorArea = null;
|
||||
private @Nullable RenderPipeline previousPipeline = null;
|
||||
private @Nullable TextureSetup previousTextureSetup = null;
|
||||
private StagedVertexBuffer.@Nullable Draw previousDraw;
|
||||
|
||||
public GuiRenderer(final GuiRenderState renderState) {
|
||||
this.renderState = renderState;
|
||||
}
|
||||
|
||||
public void render(CoreRenderTarget target) {
|
||||
var profiler = Profiler.get();
|
||||
if (this.renderState.panoramaRenderState != null) {
|
||||
this.cubeMap.render(10.0F, this.renderState.panoramaRenderState.spin());
|
||||
}
|
||||
|
||||
profiler.push("prepare");
|
||||
this.prepare();
|
||||
profiler.popPush("upload");
|
||||
this.vertexBuffer.upload();
|
||||
profiler.popPush("draw");
|
||||
this.draw(target);
|
||||
profiler.popPush("endFrame");
|
||||
this.vertexBuffer.endDraw();
|
||||
this.vertexBuffer.endFrame();
|
||||
this.draws.clear();
|
||||
this.renderState.reset();
|
||||
if (SharedConstants.DEBUG_SHUFFLE_UI_RENDERING_ORDER) {
|
||||
RenderPipeline.updateSortKeySeed();
|
||||
TextureSetup.updateSortKeySeed();
|
||||
}
|
||||
|
||||
profiler.pop();
|
||||
}
|
||||
|
||||
private void prepare() {
|
||||
this.prepareText();
|
||||
this.renderState.sortElements(ELEMENT_SORT_COMPARATOR);
|
||||
this.addElementsToMeshes();
|
||||
}
|
||||
|
||||
private void addElementsToMeshes() {
|
||||
this.previousScissorArea = null;
|
||||
this.previousPipeline = null;
|
||||
this.previousTextureSetup = null;
|
||||
this.previousDraw = null;
|
||||
this.renderState.forEachElement(this::addElementToMesh, GuiRenderState.TraverseRange.BEFORE_BLUR);
|
||||
}
|
||||
|
||||
private void draw(CoreRenderTarget target) {
|
||||
if (!this.draws.isEmpty()) {
|
||||
var minecraft = Minecraft.getInstance();
|
||||
var windowState = minecraft.gameRenderer.gameRenderState().windowRenderState;
|
||||
this.guiProjection.setupOrtho(1000.0F, 11000.0F, (float) target.target.width / (float) windowState.guiScale,
|
||||
(float) target.target.height / (float) windowState.guiScale, true);
|
||||
RenderSystem.setProjectionMatrix(this.guiProjectionMatrixBuffer.getBuffer(this.guiProjection), ProjectionType.ORTHOGRAPHIC);
|
||||
var mainRenderTarget = target.target;
|
||||
var dynamicTransforms = RenderSystem.getDynamicUniforms().writeTransform((new Matrix4f()).setTranslation(0.0F, 0.0F, -11000.0F));
|
||||
if (!this.draws.isEmpty()) {
|
||||
this.executeDrawRange(() -> "GUI before blur", mainRenderTarget, dynamicTransforms, this.draws.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void executeDrawRange(Supplier<String> label, RenderTarget mainRenderTarget, GpuBufferSlice dynamicTransforms, int endIndex) {
|
||||
try (var renderPass = RenderSystem.getDevice().createCommandEncoder()
|
||||
.createRenderPass(label, mainRenderTarget.getColorTextureView(),
|
||||
Optional.empty(), mainRenderTarget.useDepth ? mainRenderTarget.getDepthTextureView() : null, OptionalDouble.empty())) {
|
||||
RenderSystem.bindDefaultUniforms(renderPass);
|
||||
renderPass.setUniform("DynamicTransforms", dynamicTransforms);
|
||||
|
||||
for (int i = 0; i < endIndex; ++i) {
|
||||
Draw draw = this.draws.get(i);
|
||||
this.executeDraw(draw, renderPass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addElementToMesh(final GuiElementRenderState elementState) {
|
||||
var pipeline = elementState.pipeline();
|
||||
var textureSetup = elementState.textureSetup();
|
||||
var scissorArea = elementState.scissorArea();
|
||||
if (this.previousDraw == null || pipeline != this.previousPipeline || this.scissorChanged(scissorArea, this.previousScissorArea) || !textureSetup.equals(this.previousTextureSetup)) {
|
||||
this.previousPipeline = pipeline;
|
||||
this.previousTextureSetup = textureSetup;
|
||||
this.previousScissorArea = scissorArea;
|
||||
this.previousDraw = this.vertexBuffer.appendDraw(pipeline.getVertexFormatBinding(0), pipeline.getPrimitiveTopology());
|
||||
this.draws.add(new Draw(this.previousDraw, pipeline, textureSetup, scissorArea));
|
||||
}
|
||||
|
||||
elementState.buildVertices(this.vertexBuffer.getVertexBuilder(Objects.requireNonNull(this.previousDraw)));
|
||||
}
|
||||
|
||||
private void prepareText() {
|
||||
this.renderState.forEachText((text) -> {
|
||||
var pose = text.pose;
|
||||
var scissor = text.scissor;
|
||||
text.ensurePrepared().visit(new Font.GlyphVisitor() {
|
||||
public void acceptRenderable(final TextRenderable renderable) {
|
||||
renderState.addGlyphToCurrentLayer(new GlyphRenderState(pose, renderable, scissor));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void executeDraw(final Draw draw, final RenderPass renderPass) {
|
||||
var executeInfo = this.vertexBuffer.getExecuteInfo(draw.draw);
|
||||
if (executeInfo != null) {
|
||||
var pipeline = draw.pipeline();
|
||||
renderPass.setPipeline(pipeline);
|
||||
renderPass.setVertexBuffer(0, executeInfo.vertexBuffer().slice());
|
||||
var scissorArea = draw.scissorArea();
|
||||
if (scissorArea != null) {
|
||||
this.enableScissor(scissorArea, renderPass);
|
||||
} else {
|
||||
renderPass.disableScissor();
|
||||
}
|
||||
|
||||
if (draw.textureSetup.texure0() != null) {
|
||||
renderPass.bindTexture("Sampler0", draw.textureSetup.texure0(), draw.textureSetup.sampler0());
|
||||
}
|
||||
|
||||
if (draw.textureSetup.texure1() != null) {
|
||||
renderPass.bindTexture("Sampler1", draw.textureSetup.texure1(), draw.textureSetup.sampler1());
|
||||
}
|
||||
|
||||
if (draw.textureSetup.texure2() != null) {
|
||||
renderPass.bindTexture("Sampler2", draw.textureSetup.texure2(), draw.textureSetup.sampler2());
|
||||
}
|
||||
|
||||
renderPass.setIndexBuffer(executeInfo.indexBuffer(), executeInfo.indexType());
|
||||
renderPass.drawIndexed(executeInfo.indexCount(), 1, executeInfo.firstIndex(), executeInfo.baseVertex(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean scissorChanged(final @Nullable ScreenRectangle newScissor, final @Nullable ScreenRectangle oldScissor) {
|
||||
if (newScissor == oldScissor) {
|
||||
return false;
|
||||
} else if (newScissor != null) {
|
||||
return !newScissor.equals(oldScissor);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void enableScissor(final ScreenRectangle rectangle, final RenderPass renderPass) {
|
||||
var window = Minecraft.getInstance().gameRenderer.gameRenderState().windowRenderState;
|
||||
int guiScale = window.guiScale;
|
||||
double left = (rectangle.left() * guiScale);
|
||||
double top = (rectangle.top() * guiScale);
|
||||
double right = Math.min(rectangle.right() * guiScale, window.width);
|
||||
double bottom = Math.min(rectangle.bottom() * guiScale, window.height);
|
||||
renderPass.enableScissor((int) left, window.height - (int) bottom, Math.max(0, (int) (right - left)), Math.max(0, (int) (bottom - top)));
|
||||
}
|
||||
|
||||
public void close() {
|
||||
this.vertexBuffer.close();
|
||||
this.guiProjectionMatrixBuffer.close();
|
||||
this.cubeMap.close();
|
||||
}
|
||||
|
||||
static {
|
||||
ELEMENT_SORT_COMPARATOR = Comparator.comparing(GuiElementRenderState::scissorArea, SCISSOR_COMPARATOR)
|
||||
.thenComparing(GuiElementRenderState::pipeline, Comparator.comparing(RenderPipeline::getSortKey))
|
||||
.thenComparing(GuiElementRenderState::textureSetup, TEXTURE_COMPARATOR);
|
||||
}
|
||||
|
||||
private record Draw(StagedVertexBuffer.Draw draw, RenderPipeline pipeline, TextureSetup textureSetup,
|
||||
@Nullable ScreenRectangle scissorArea) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.renderer.state.gui.GuiElementRenderState;
|
||||
import org.joml.Matrix3x2f;
|
||||
|
||||
public record FloatRenderState(RenderPipeline pipeline, TextureSetup textureSetup, Matrix3x2f pose, float x0,
|
||||
float y0, float x1, float y1, float u0, float u1, float v0, float v1, int color,
|
||||
ScreenRectangle scissorArea,
|
||||
ScreenRectangle bounds) implements GuiElementRenderState {
|
||||
public FloatRenderState(RenderPipeline renderPipeline, TextureSetup textureSetup, Matrix3x2f matrix3x2f,
|
||||
float i, float j, float k, float l, float f, float g, float h, float m, int n, ScreenRectangle screenRectangle) {
|
||||
this(renderPipeline, textureSetup, matrix3x2f, i, j, k, l, f, g, h, m, n, screenRectangle, getBounds(i, j, k, l, matrix3x2f, screenRectangle));
|
||||
}
|
||||
|
||||
private static ScreenRectangle getBounds(float i, float j, float k, float l, Matrix3x2f matrix3x2f, ScreenRectangle screenRectangle) {
|
||||
ScreenRectangle screenRectangle2 = (new ScreenRectangle((int) Math.ceil(i + 1), (int) Math.ceil(j + 1), (int) Math.ceil(k - i + 1), (int) Math.ceil(l - j + 1))).transformMaxBounds(matrix3x2f);
|
||||
return screenRectangle != null ? screenRectangle.intersection(screenRectangle2) : screenRectangle2;
|
||||
}
|
||||
|
||||
public void buildVertices(VertexConsumer vertexConsumer) {
|
||||
vertexConsumer.addVertexWith2DPose(pose(), x0(), y0()).setUv(u0(), v0()).setColor(color());
|
||||
vertexConsumer.addVertexWith2DPose(pose(), x0(), y1()).setUv(u0(), v1()).setColor(color());
|
||||
vertexConsumer.addVertexWith2DPose(pose(), x1(), y1()).setUv(u1(), v1()).setColor(color());
|
||||
vertexConsumer.addVertexWith2DPose(pose(), x1(), y0()).setUv(u1(), v0()).setColor(color());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,645 @@
|
||||
package com.coloryr.allmusic.client;
|
||||
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.*;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class MiniMessage {
|
||||
// 正则表达式匹配标签
|
||||
private static final Pattern TAG_PATTERN = Pattern.compile(
|
||||
"<(/?)(\\w+)(?:\\(([^)]+)\\))?(?::([^>]+))?>"
|
||||
);
|
||||
|
||||
// 颜色映射表
|
||||
private static final Map<String, ChatFormatting> COLOR_MAP = new HashMap<>();
|
||||
// 装饰器映射
|
||||
private static final Map<String, ChatFormatting> DECORATION_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
COLOR_MAP.put("black", ChatFormatting.BLACK);
|
||||
COLOR_MAP.put("dark_blue", ChatFormatting.DARK_BLUE);
|
||||
COLOR_MAP.put("dark_green", ChatFormatting.DARK_GREEN);
|
||||
COLOR_MAP.put("dark_aqua", ChatFormatting.DARK_AQUA);
|
||||
COLOR_MAP.put("dark_red", ChatFormatting.DARK_RED);
|
||||
COLOR_MAP.put("dark_purple", ChatFormatting.DARK_PURPLE);
|
||||
COLOR_MAP.put("gold", ChatFormatting.GOLD);
|
||||
COLOR_MAP.put("gray", ChatFormatting.GRAY);
|
||||
COLOR_MAP.put("dark_gray", ChatFormatting.DARK_GRAY);
|
||||
COLOR_MAP.put("blue", ChatFormatting.BLUE);
|
||||
COLOR_MAP.put("green", ChatFormatting.GREEN);
|
||||
COLOR_MAP.put("aqua", ChatFormatting.AQUA);
|
||||
COLOR_MAP.put("red", ChatFormatting.RED);
|
||||
COLOR_MAP.put("light_purple", ChatFormatting.LIGHT_PURPLE);
|
||||
COLOR_MAP.put("yellow", ChatFormatting.YELLOW);
|
||||
COLOR_MAP.put("white", ChatFormatting.WHITE);
|
||||
}
|
||||
|
||||
static {
|
||||
DECORATION_MAP.put("bold", ChatFormatting.BOLD);
|
||||
DECORATION_MAP.put("italic", ChatFormatting.ITALIC);
|
||||
DECORATION_MAP.put("underlined", ChatFormatting.UNDERLINE);
|
||||
DECORATION_MAP.put("strikethrough", ChatFormatting.STRIKETHROUGH);
|
||||
DECORATION_MAP.put("obfuscated", ChatFormatting.OBFUSCATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 MiniMessage 字符串为 Component
|
||||
*/
|
||||
public static Component parse(String input) {
|
||||
MutableComponent root = Component.literal("");
|
||||
Stack<StyleContext> styleStack = new Stack<>();
|
||||
StyleContext currentStyle = new StyleContext();
|
||||
styleStack.push(currentStyle);
|
||||
|
||||
StringBuilder textBuffer = new StringBuilder();
|
||||
int pos = 0;
|
||||
int length = input.length();
|
||||
|
||||
while (pos < length) {
|
||||
char c = input.charAt(pos);
|
||||
|
||||
if (c == '<') {
|
||||
// 先处理已积累的文本
|
||||
if (textBuffer.length() > 0) {
|
||||
appendText(root, textBuffer.toString(), currentStyle);
|
||||
textBuffer.setLength(0);
|
||||
}
|
||||
|
||||
// 查找标签结束位置
|
||||
int endTag = findClosingTag(input, pos);
|
||||
if (endTag == -1) {
|
||||
// 没有找到闭合标签,当作普通文本处理
|
||||
textBuffer.append(c);
|
||||
pos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
String tagContent = input.substring(pos, endTag + 1);
|
||||
Matcher matcher = TAG_PATTERN.matcher(tagContent);
|
||||
|
||||
if (matcher.matches()) {
|
||||
boolean isClosing = !matcher.group(1).isEmpty();
|
||||
String tagName = matcher.group(2).toLowerCase(Locale.ROOT);
|
||||
String params = matcher.group(3);
|
||||
String value = matcher.group(4);
|
||||
|
||||
if (isClosing) {
|
||||
// 关闭标签 - 只关闭对应的开始标签
|
||||
String expectedOpenTag = matcher.group(2).toLowerCase(Locale.ROOT);
|
||||
|
||||
// 查找对应的开始标签并关闭
|
||||
if (styleStack.size() > 1) {
|
||||
// 从栈顶向下查找匹配的开始标签
|
||||
int index = styleStack.size() - 1;
|
||||
boolean found = false;
|
||||
|
||||
while (index >= 0) {
|
||||
StyleContext ctx = styleStack.get(index);
|
||||
if (ctx.tagName != null && ctx.tagName.equals(expectedOpenTag)) {
|
||||
// 找到匹配的开始标签,关闭到这一层
|
||||
while (styleStack.size() > index + 1) {
|
||||
styleStack.pop();
|
||||
}
|
||||
currentStyle = styleStack.peek();
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
index--;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
// 没找到匹配的标签,忽略此关闭标签
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 处理各种标签
|
||||
StyleContext newStyle = currentStyle.copy();
|
||||
|
||||
switch (tagName) {
|
||||
case "color":
|
||||
handleColorTag(newStyle, params, value);
|
||||
break;
|
||||
case "shadow":
|
||||
handleShadowTag(newStyle, params, value);
|
||||
break;
|
||||
case "decoration":
|
||||
handleDecorationTag(newStyle, params, value);
|
||||
break;
|
||||
case "reset":
|
||||
newStyle = new StyleContext();
|
||||
break;
|
||||
case "rainbow":
|
||||
handleRainbowTag(newStyle, params, value);
|
||||
break;
|
||||
case "gradient":
|
||||
handleGradientTag(newStyle, params, value);
|
||||
break;
|
||||
case "transition":
|
||||
handleTransitionTag(newStyle, params, value);
|
||||
break;
|
||||
case "font":
|
||||
handleFontTag(newStyle, params, value);
|
||||
break;
|
||||
default:
|
||||
// 未知标签,忽略
|
||||
break;
|
||||
}
|
||||
|
||||
// 记录标签名称以便关闭时匹配
|
||||
newStyle.tagName = tagName;
|
||||
styleStack.push(newStyle);
|
||||
currentStyle = newStyle;
|
||||
}
|
||||
} else {
|
||||
// 无效标签,当作普通文本
|
||||
textBuffer.append(tagContent);
|
||||
}
|
||||
|
||||
pos = endTag + 1;
|
||||
} else {
|
||||
textBuffer.append(c);
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理剩余的文本
|
||||
if (textBuffer.length() > 0) {
|
||||
appendText(root, textBuffer.toString(), currentStyle);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理颜色标签
|
||||
* 支持格式: <color:red>, <color:#FF0000>, <color(rgb):255,0,0>
|
||||
*/
|
||||
private static void handleColorTag(StyleContext style, String params, String value) {
|
||||
if (value != null) {
|
||||
// 格式: <color:red> 或 <color:#FF0000>
|
||||
TextColor textColor = parseColor(value);
|
||||
if (textColor != null) {
|
||||
style.color = textColor;
|
||||
// 清除渐变、过渡和彩虹效果
|
||||
style.gradient = false;
|
||||
style.transition = false;
|
||||
style.rainbow = false;
|
||||
}
|
||||
} else if (params != null) {
|
||||
// 格式: <color(rgb):255,0,0>
|
||||
if (params.equalsIgnoreCase("rgb")) {
|
||||
String[] parts = value.split(",");
|
||||
if (parts.length == 3) {
|
||||
try {
|
||||
int r = Integer.parseInt(parts[0].trim());
|
||||
int g = Integer.parseInt(parts[1].trim());
|
||||
int b = Integer.parseInt(parts[2].trim());
|
||||
style.color = TextColor.fromRgb((r << 16) | (g << 8) | b);
|
||||
// 清除渐变、过渡和彩虹效果
|
||||
style.gradient = false;
|
||||
style.transition = false;
|
||||
style.rainbow = false;
|
||||
} catch (NumberFormatException e) {
|
||||
// 忽略解析错误
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理阴影标签
|
||||
* 支持格式: <shadow>, <shadow:true>, <shadow:false>, <shadow:#FF0000>
|
||||
*/
|
||||
private static void handleShadowTag(StyleContext style, String params, String value) {
|
||||
if (value == null) {
|
||||
style.shadow = true;
|
||||
} else if (value.equalsIgnoreCase("true")) {
|
||||
style.shadow = true;
|
||||
} else if (value.equalsIgnoreCase("false")) {
|
||||
style.shadow = false;
|
||||
} else {
|
||||
// 自定义阴影颜色
|
||||
TextColor shadowColor = parseColor(value);
|
||||
if (shadowColor != null) {
|
||||
style.shadow = true;
|
||||
style.shadowColor = shadowColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理装饰标签
|
||||
* 支持格式: <decoration:bold>, <decoration:bold:true>, <decoration:bold:false>
|
||||
*/
|
||||
private static void handleDecorationTag(StyleContext style, String params, String value) {
|
||||
if (params == null) return;
|
||||
|
||||
String[] parts = params.split(":");
|
||||
String decorationName = parts[0].toLowerCase(Locale.ROOT);
|
||||
boolean enabled = true;
|
||||
|
||||
if (parts.length > 1) {
|
||||
enabled = Boolean.parseBoolean(parts[1]);
|
||||
} else if (value != null) {
|
||||
enabled = Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
ChatFormatting formatting = DECORATION_MAP.get(decorationName);
|
||||
if (formatting != null) {
|
||||
style.decorations.put(formatting, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理彩虹色标签
|
||||
* 支持格式: <rainbow>, <rainbow:!>, <rainbow(saturation):0.8>, <rainbow(brightness):0.5>
|
||||
*/
|
||||
private static void handleRainbowTag(StyleContext style, String params, String value) {
|
||||
float saturation = 1.0f;
|
||||
float brightness = 1.0f;
|
||||
|
||||
if (params != null) {
|
||||
if (params.equalsIgnoreCase("saturation") && value != null) {
|
||||
try {
|
||||
saturation = Float.parseFloat(value);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
} else if (params.equalsIgnoreCase("brightness") && value != null) {
|
||||
try {
|
||||
brightness = Float.parseFloat(value);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
style.rainbow = true;
|
||||
style.gradient = false;
|
||||
style.transition = false;
|
||||
style.rainbowSaturation = saturation;
|
||||
style.rainbowBrightness = brightness;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理渐变色标签
|
||||
* 支持格式: <gradient:#FF0000:#00FF00>, <gradient(rgb):255,0,0:0,255,0>
|
||||
* 渐变是逐字符过渡
|
||||
*/
|
||||
private static void handleGradientTag(StyleContext style, String params, String value) {
|
||||
if (value == null) return;
|
||||
|
||||
String[] colors = value.split(":");
|
||||
if (colors.length == 2) {
|
||||
TextColor start = parseColor(colors[0]);
|
||||
TextColor end = parseColor(colors[1]);
|
||||
|
||||
if (start != null && end != null) {
|
||||
style.gradient = true;
|
||||
style.transition = false;
|
||||
style.rainbow = false;
|
||||
style.gradientStart = start;
|
||||
style.gradientEnd = end;
|
||||
}
|
||||
} else if (params != null && params.equalsIgnoreCase("rgb")) {
|
||||
// 处理 RGB 格式
|
||||
String[] color1 = colors[0].split(",");
|
||||
String[] color2 = colors[1].split(",");
|
||||
if (color1.length == 3 && color2.length == 3) {
|
||||
try {
|
||||
int r1 = Integer.parseInt(color1[0].trim());
|
||||
int g1 = Integer.parseInt(color1[1].trim());
|
||||
int b1 = Integer.parseInt(color1[2].trim());
|
||||
int r2 = Integer.parseInt(color2[0].trim());
|
||||
int g2 = Integer.parseInt(color2[1].trim());
|
||||
int b2 = Integer.parseInt(color2[2].trim());
|
||||
|
||||
style.gradient = true;
|
||||
style.transition = false;
|
||||
style.rainbow = false;
|
||||
style.gradientStart = TextColor.fromRgb((r1 << 16) | (g1 << 8) | b1);
|
||||
style.gradientEnd = TextColor.fromRgb((r2 << 16) | (g2 << 8) | b2);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理过渡色标签
|
||||
* 支持格式: <transition:#FF0000:#00FF00>, <transition(rgb):255,0,0:0,255,0>
|
||||
* 过渡是整个文本块的颜色过渡(类似整体渐变效果)
|
||||
*/
|
||||
private static void handleTransitionTag(StyleContext style, String params, String value) {
|
||||
if (value == null) return;
|
||||
|
||||
String[] colors = value.split(":");
|
||||
if (colors.length == 2) {
|
||||
TextColor start = parseColor(colors[0]);
|
||||
TextColor end = parseColor(colors[1]);
|
||||
|
||||
if (start != null && end != null) {
|
||||
style.transition = true;
|
||||
style.gradient = false;
|
||||
style.rainbow = false;
|
||||
style.transitionStart = start;
|
||||
style.transitionEnd = end;
|
||||
}
|
||||
} else if (params != null && params.equalsIgnoreCase("rgb")) {
|
||||
// 处理 RGB 格式
|
||||
String[] color1 = colors[0].split(",");
|
||||
String[] color2 = colors[1].split(",");
|
||||
if (color1.length == 3 && color2.length == 3) {
|
||||
try {
|
||||
int r1 = Integer.parseInt(color1[0].trim());
|
||||
int g1 = Integer.parseInt(color1[1].trim());
|
||||
int b1 = Integer.parseInt(color1[2].trim());
|
||||
int r2 = Integer.parseInt(color2[0].trim());
|
||||
int g2 = Integer.parseInt(color2[1].trim());
|
||||
int b2 = Integer.parseInt(color2[2].trim());
|
||||
|
||||
style.transition = true;
|
||||
style.gradient = false;
|
||||
style.rainbow = false;
|
||||
style.transitionStart = TextColor.fromRgb((r1 << 16) | (g1 << 8) | b1);
|
||||
style.transitionEnd = TextColor.fromRgb((r2 << 16) | (g2 << 8) | b2);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理字体标签
|
||||
* 支持格式: <font:minecraft:default>, <font:minecraft:uniform>
|
||||
*/
|
||||
private static void handleFontTag(StyleContext style, String params, String value) {
|
||||
String fontName = value != null ? value : params;
|
||||
if (fontName != null) {
|
||||
style.font = fontName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析颜色字符串
|
||||
* 支持: 颜色名称, #RRGGBB, #RGB, 十六进制数值
|
||||
*/
|
||||
private static TextColor parseColor(String colorStr) {
|
||||
if (colorStr == null) return null;
|
||||
|
||||
// 检查是否为预定义颜色名称
|
||||
ChatFormatting formatting = COLOR_MAP.get(colorStr.toLowerCase(Locale.ROOT));
|
||||
if (formatting != null) {
|
||||
return TextColor.fromLegacyFormat(formatting);
|
||||
}
|
||||
|
||||
// 检查十六进制格式
|
||||
if (colorStr.startsWith("#")) {
|
||||
try {
|
||||
if (colorStr.length() == 7) {
|
||||
return TextColor.parseColor(colorStr).getOrThrow();
|
||||
} else if (colorStr.length() == 4) {
|
||||
// 扩展 #RGB 到 #RRGGBB
|
||||
String expanded = "#" +
|
||||
colorStr.charAt(1) + colorStr.charAt(1) +
|
||||
colorStr.charAt(2) + colorStr.charAt(2) +
|
||||
colorStr.charAt(3) + colorStr.charAt(3);
|
||||
return TextColor.parseColor(expanded).getOrThrow();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 忽略解析错误
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找标签闭合位置
|
||||
*/
|
||||
private static int findClosingTag(String input, int start) {
|
||||
int pos = start + 1;
|
||||
while (pos < input.length()) {
|
||||
char c = input.charAt(pos);
|
||||
if (c == '>') {
|
||||
return pos;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加文本到组件
|
||||
*/
|
||||
private static void appendText(MutableComponent root, String text, StyleContext styleContext) {
|
||||
if (text.isEmpty()) return;
|
||||
|
||||
// 如果有渐变效果,需要逐字符处理
|
||||
if (styleContext.gradient && styleContext.gradientStart != null && styleContext.gradientEnd != null) {
|
||||
appendGradientText(root, text, styleContext);
|
||||
}
|
||||
// 如果有过渡效果,需要逐字符处理
|
||||
else if (styleContext.transition && styleContext.transitionStart != null && styleContext.transitionEnd != null) {
|
||||
appendTransitionText(root, text, styleContext);
|
||||
}
|
||||
// 如果有彩虹效果,需要逐字符处理
|
||||
else if (styleContext.rainbow) {
|
||||
appendRainbowText(root, text, styleContext);
|
||||
}
|
||||
// 普通文本
|
||||
else {
|
||||
MutableComponent component = Component.literal(text);
|
||||
component.setStyle(styleContext.buildStyle());
|
||||
root.append(component);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 带渐变效果的文本追加(逐字符颜色变化)
|
||||
*/
|
||||
private static void appendGradientText(MutableComponent root, String text, StyleContext styleContext) {
|
||||
int length = text.length();
|
||||
Style baseStyle = styleContext.buildBaseStyle();
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = text.charAt(i);
|
||||
float ratio = (float) i / (length - 1);
|
||||
TextColor gradientColor = interpolateColor(styleContext.gradientStart, styleContext.gradientEnd, ratio);
|
||||
|
||||
Style charStyle = baseStyle.withColor(gradientColor);
|
||||
// 重新应用阴影
|
||||
if (styleContext.shadow != null && styleContext.shadow) {
|
||||
if (styleContext.shadowColor != null) {
|
||||
charStyle = charStyle.withShadowColor(styleContext.shadowColor.getValue() | 0xFF000000);
|
||||
} else {
|
||||
charStyle = charStyle.withShadowColor(0xFF000000);
|
||||
}
|
||||
}
|
||||
|
||||
root.append(Component.literal(String.valueOf(c)).withStyle(charStyle));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 带过渡效果的文本追加(整体颜色过渡)
|
||||
*/
|
||||
private static void appendTransitionText(MutableComponent root, String text, StyleContext styleContext) {
|
||||
Style baseStyle = styleContext.buildBaseStyle();
|
||||
|
||||
// 过渡效果:为整个文本块创建一个颜色过渡
|
||||
MutableComponent transitionComponent = Component.literal(text);
|
||||
|
||||
// 应用过渡样式(这里使用颜色插值,但也可以根据需求调整)
|
||||
if (styleContext.transitionStart != null && styleContext.transitionEnd != null) {
|
||||
// 可以使用整体渐变或者固定起始色
|
||||
transitionComponent.setStyle(baseStyle.withColor(styleContext.transitionStart));
|
||||
} else {
|
||||
transitionComponent.setStyle(baseStyle);
|
||||
}
|
||||
|
||||
root.append(transitionComponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 带彩虹效果的文本追加
|
||||
*/
|
||||
private static void appendRainbowText(MutableComponent root, String text, StyleContext styleContext) {
|
||||
int length = text.length();
|
||||
Style baseStyle = styleContext.buildBaseStyle();
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = text.charAt(i);
|
||||
float hue = (float) i / length;
|
||||
int rgb = java.awt.Color.HSBtoRGB(hue, styleContext.rainbowSaturation, styleContext.rainbowBrightness);
|
||||
TextColor rainbowColor = TextColor.fromRgb(rgb & 0xFFFFFF);
|
||||
|
||||
Style charStyle = baseStyle.withColor(rainbowColor);
|
||||
// 重新应用阴影
|
||||
if (styleContext.shadow != null && styleContext.shadow) {
|
||||
if (styleContext.shadowColor != null) {
|
||||
charStyle = charStyle.withShadowColor(styleContext.shadowColor.getValue() | 0xFF000000);
|
||||
} else {
|
||||
charStyle = charStyle.withShadowColor(0xFF000000);
|
||||
}
|
||||
}
|
||||
|
||||
root.append(Component.literal(String.valueOf(c)).withStyle(charStyle));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 颜色插值
|
||||
*/
|
||||
private static TextColor interpolateColor(TextColor start, TextColor end, float ratio) {
|
||||
int startRgb = start.getValue();
|
||||
int endRgb = end.getValue();
|
||||
|
||||
int r1 = (startRgb >> 16) & 0xFF;
|
||||
int g1 = (startRgb >> 8) & 0xFF;
|
||||
int b1 = startRgb & 0xFF;
|
||||
|
||||
int r2 = (endRgb >> 16) & 0xFF;
|
||||
int g2 = (endRgb >> 8) & 0xFF;
|
||||
int b2 = endRgb & 0xFF;
|
||||
|
||||
int r = (int) (r1 + (r2 - r1) * ratio);
|
||||
int g = (int) (g1 + (g2 - g1) * ratio);
|
||||
int b = (int) (b1 + (b2 - b1) * ratio);
|
||||
|
||||
return TextColor.fromRgb((r << 16) | (g << 8) | b);
|
||||
}
|
||||
|
||||
/**
|
||||
* 样式上下文类,用于追踪嵌套样式
|
||||
*/
|
||||
private static class StyleContext {
|
||||
String tagName = null; // 记录标签名称,用于关闭匹配
|
||||
TextColor color = null;
|
||||
Boolean shadow = null;
|
||||
TextColor shadowColor = null;
|
||||
Map<ChatFormatting, Boolean> decorations = new HashMap<>();
|
||||
boolean rainbow = false;
|
||||
float rainbowSaturation = 1.0f;
|
||||
float rainbowBrightness = 1.0f;
|
||||
boolean gradient = false;
|
||||
TextColor gradientStart = null;
|
||||
TextColor gradientEnd = null;
|
||||
boolean transition = false;
|
||||
TextColor transitionStart = null;
|
||||
TextColor transitionEnd = null;
|
||||
String font = null;
|
||||
|
||||
StyleContext copy() {
|
||||
StyleContext copy = new StyleContext();
|
||||
copy.tagName = this.tagName;
|
||||
copy.color = this.color;
|
||||
copy.shadow = this.shadow;
|
||||
copy.shadowColor = this.shadowColor;
|
||||
copy.decorations.putAll(this.decorations);
|
||||
copy.rainbow = this.rainbow;
|
||||
copy.rainbowSaturation = this.rainbowSaturation;
|
||||
copy.rainbowBrightness = this.rainbowBrightness;
|
||||
copy.gradient = this.gradient;
|
||||
copy.gradientStart = this.gradientStart;
|
||||
copy.gradientEnd = this.gradientEnd;
|
||||
copy.transition = this.transition;
|
||||
copy.transitionStart = this.transitionStart;
|
||||
copy.transitionEnd = this.transitionEnd;
|
||||
copy.font = this.font;
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建基础样式(不含颜色)
|
||||
*/
|
||||
Style buildBaseStyle() {
|
||||
Style style = Style.EMPTY;
|
||||
|
||||
// 应用装饰器
|
||||
for (Map.Entry<ChatFormatting, Boolean> entry : decorations.entrySet()) {
|
||||
if (entry.getValue()) {
|
||||
style = style.applyFormat(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
if (font != null) {
|
||||
try {
|
||||
style = style.withFont(new FontDescription.Resource(Identifier.parse(font)));
|
||||
} catch (Exception e) {
|
||||
// 忽略字体解析错误
|
||||
}
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建完整样式(含颜色)
|
||||
*/
|
||||
Style buildStyle() {
|
||||
Style style = buildBaseStyle();
|
||||
|
||||
// 只有在没有渐变、过渡和彩虹效果时才应用普通颜色
|
||||
if (!gradient && !transition && !rainbow && color != null) {
|
||||
style = style.withColor(color);
|
||||
}
|
||||
|
||||
if (shadow != null && shadow) {
|
||||
if (shadowColor != null) {
|
||||
style = style.withShadowColor(shadowColor.getValue() | 0xFF000000);
|
||||
} else {
|
||||
style = style.withShadowColor(0xFF000000);
|
||||
}
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.coloryr.allmusic.client;
|
||||
|
||||
import com.coloryr.allmusic.client.core.AllMusicHud;
|
||||
import com.coloryr.allmusic.client.core.Point2f;
|
||||
import com.coloryr.allmusic.client.core.render.PictureFrameBuffer;
|
||||
import com.coloryr.allmusic.codec.HudPosType;
|
||||
import com.mojang.blaze3d.platform.NativeImage;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.textures.FilterMode;
|
||||
import net.minecraft.client.gui.render.TextureSetup;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.client.renderer.texture.DynamicTexture;
|
||||
import org.joml.Matrix3x2fStack;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
public class PicRender extends PictureFrameBuffer {
|
||||
private final DynamicTexture sourceTexture;
|
||||
private final DynamicTexture rotateTexture;
|
||||
|
||||
public PicRender(int size) {
|
||||
sourceTexture = new DynamicTexture("allmusic source", size, size, false);
|
||||
rotateTexture = new DynamicTexture("allmusic rotate", size, size, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(byte[] source, byte[] rotate) {
|
||||
try {
|
||||
ByteArrayInputStream stream = new ByteArrayInputStream(source);
|
||||
var image1 = NativeImage.read(stream);
|
||||
sourceTexture.setPixels(image1);
|
||||
sourceTexture.upload();
|
||||
|
||||
stream = new ByteArrayInputStream(rotate);
|
||||
var image2 = NativeImage.read(stream);
|
||||
rotateTexture.setPixels(image2);
|
||||
rotateTexture.upload();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(boolean rotate, int size, float x, float y, int ang, HudPosType dir, float alpha) {
|
||||
Point2f point = AllMusicHud.getPos(size, size, x, y, dir);
|
||||
|
||||
Matrix3x2fStack matrix = new Matrix3x2fStack(2);
|
||||
|
||||
float a = (float) size / 2;
|
||||
|
||||
if (rotate) {
|
||||
matrix.translation(point.x + a, point.y + a);
|
||||
matrix.pushMatrix().rotate((float) Math.toRadians(ang));
|
||||
} else {
|
||||
matrix.translation(point.x + a, point.y + a);
|
||||
}
|
||||
|
||||
float x0 = -a;
|
||||
float x1 = a;
|
||||
float y0 = -a;
|
||||
float y1 = a;
|
||||
float u0 = 0;
|
||||
float u1 = 1;
|
||||
float v0 = 0;
|
||||
float v1 = 1;
|
||||
|
||||
int color = 0xFFFFFF00 + (int) (255 * alpha);
|
||||
|
||||
AllMusicClient.context.submitGuiElementRenderState(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
|
||||
TextureSetup.singleTexture(rotate ? rotateTexture.getTextureView() : sourceTexture.getTextureView(), RenderSystem.getSamplerCache().getRepeat(FilterMode.NEAREST)),
|
||||
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.peekScissorStack()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
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 com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.textures.FilterMode;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.render.TextureSetup;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.client.renderer.texture.SimpleTexture;
|
||||
import net.minecraft.client.renderer.texture.TextureContents;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import org.joml.Matrix3x2f;
|
||||
|
||||
public class TexRender extends TextureRender {
|
||||
private final SimpleTexture sourceTexture;
|
||||
|
||||
public TexRender(String texture) {
|
||||
super(texture);
|
||||
Identifier location = Identifier.fromNamespaceAndPath(AllMusicClient.MODID, texture);
|
||||
sourceTexture = new SimpleTexture(location);
|
||||
|
||||
try {
|
||||
TextureContents contents = sourceTexture.loadContents(Minecraft.getInstance().getResourceManager());
|
||||
|
||||
width = contents.image().getWidth();
|
||||
height = contents.image().getHeight();
|
||||
|
||||
sourceTexture.apply(contents);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawPic(float x, float y, float alpha) {
|
||||
Matrix3x2f matrix = new Matrix3x2f();
|
||||
|
||||
float w1 = (float) width / 2;
|
||||
float h1 = (float) height / 2;
|
||||
|
||||
matrix.translation(x + w1, y + h1);
|
||||
|
||||
float x0 = -w1;
|
||||
float x1 = w1;
|
||||
float y0 = -h1;
|
||||
float y1 = h1;
|
||||
float u0 = 0;
|
||||
float u1 = 1;
|
||||
float v0 = 0;
|
||||
float v1 = 1;
|
||||
|
||||
int color = 0xFFFFFF00 + (int) (255 * alpha);
|
||||
|
||||
AllMusicClient.context.submitGuiElementRenderState(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
|
||||
TextureSetup.singleTexture(sourceTexture.getTextureView(), RenderSystem.getSamplerCache().getRepeat(FilterMode.NEAREST)),
|
||||
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.peekScissorStack()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawPic(float x, float y, float width, float alpha) {
|
||||
Matrix3x2f matrix = new Matrix3x2f();
|
||||
|
||||
float w1 = ((float) this.width / 2) * width;
|
||||
float h1 = (float) height / 2;
|
||||
|
||||
matrix.translation(x + w1, y + h1);
|
||||
|
||||
float x0 = -w1;
|
||||
float x1 = w1;
|
||||
float y0 = -h1;
|
||||
float y1 = h1;
|
||||
float u0 = 0;
|
||||
float u1 = width;
|
||||
float v0 = 0;
|
||||
float v1 = 1;
|
||||
|
||||
int color = 0xFFFFFF00 + (int) (255 * alpha);
|
||||
|
||||
AllMusicClient.context.submitGuiElementRenderState(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
|
||||
TextureSetup.singleTexture(sourceTexture.getTextureView(), RenderSystem.getSamplerCache().getRepeat(FilterMode.NEAREST)),
|
||||
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.peekScissorStack()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawPic(float x, float y, float width, float height, HudPosType dir, float alpha) {
|
||||
Point2f point = AllMusicHud.getPos(width, height, x, y, dir);
|
||||
|
||||
float w1 = width / 2;
|
||||
float h1 = height / 2;
|
||||
|
||||
Matrix3x2f matrix = new Matrix3x2f().translation(point.x + w1, point.y + h1);
|
||||
|
||||
float x0 = -w1;
|
||||
float x1 = w1;
|
||||
float y0 = -h1;
|
||||
float y1 = h1;
|
||||
float u0 = 0;
|
||||
float u1 = 1;
|
||||
float v0 = 0;
|
||||
float v1 = 1;
|
||||
|
||||
int color = 0xFFFFFF00 + (int) (255 * alpha);
|
||||
|
||||
AllMusicClient.context.submitGuiElementRenderState(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
|
||||
TextureSetup.singleTexture(sourceTexture.getTextureView(), RenderSystem.getSamplerCache().getRepeat(FilterMode.NEAREST)),
|
||||
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.peekScissorStack()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//package com.coloryr.allmusic.client.others;
|
||||
//
|
||||
//import icyllis.modernui.mc.text.ModernPreparedText;
|
||||
//import net.minecraft.client.gui.Font;
|
||||
//import net.minecraft.client.gui.navigation.ScreenRectangle;
|
||||
//import net.minecraft.client.renderer.state.gui.GuiRenderState;
|
||||
//import org.joml.Matrix3x2fc;
|
||||
//
|
||||
//public class ModernUI {
|
||||
// public static void text(Font.PreparedText preparedText, GuiRenderState state, Matrix3x2fc pose, ScreenRectangle scissor) {
|
||||
// if (preparedText instanceof ModernPreparedText) {
|
||||
// ((ModernPreparedText) preparedText).submitRuns(state, pose, scissor);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,34 @@
|
||||
//package com.coloryr.allmusic.client.others;
|
||||
//
|
||||
//import com.coloryr.allmusic.client.core.AllMusicCore;
|
||||
//import de.maxhenkel.voicechat.api.ForgeVoicechatPlugin;
|
||||
//import de.maxhenkel.voicechat.api.VoicechatPlugin;
|
||||
//import de.maxhenkel.voicechat.api.events.ClientReceiveSoundEvent;
|
||||
//import de.maxhenkel.voicechat.api.events.EventRegistration;
|
||||
//
|
||||
//@ForgeVoicechatPlugin
|
||||
//public class VoiceChat implements VoicechatPlugin {
|
||||
// @Override
|
||||
// public String getPluginId() {
|
||||
// return "allmusic";
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void registerEvents(EventRegistration registration) {
|
||||
// registration.registerEvent(ClientReceiveSoundEvent.EntitySound.class, this::play);
|
||||
// registration.registerEvent(ClientReceiveSoundEvent.LocationalSound.class, this::play);
|
||||
// registration.registerEvent(ClientReceiveSoundEvent.StaticSound.class, this::play);
|
||||
// }
|
||||
//
|
||||
// public void play(ClientReceiveSoundEvent.EntitySound event) {
|
||||
// AllMusicCore.chat();
|
||||
// }
|
||||
//
|
||||
// public void play(ClientReceiveSoundEvent.LocationalSound event) {
|
||||
// AllMusicCore.chat();
|
||||
// }
|
||||
//
|
||||
// public void play(ClientReceiveSoundEvent.StaticSound event) {
|
||||
// AllMusicCore.chat();
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.coloryr.allmusic.comm;
|
||||
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.fml.ModContainer;
|
||||
import net.neoforged.fml.common.Mod;
|
||||
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
|
||||
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||
import net.neoforged.neoforge.network.handling.IPayloadHandler;
|
||||
|
||||
@Mod(AllMusicInit.MODID)
|
||||
public class AllMusicInit implements IPayloadHandler<MusicCodec> {
|
||||
|
||||
public static final String MODID = "allmusic";
|
||||
|
||||
public static IPayloadHandler<MusicCodec> handler;
|
||||
|
||||
public AllMusicInit(IEventBus modEventBus, ModContainer modContainer) {
|
||||
modEventBus.addListener(this::register);
|
||||
}
|
||||
|
||||
public void register(final RegisterPayloadHandlersEvent event) {
|
||||
event.registrar("1.0")
|
||||
.optional()
|
||||
.playToClient(MusicCodec.TYPE, MusicCodec.CODEC, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MusicCodec data, IPayloadContext iPayloadContext) {
|
||||
if (handler != null) {
|
||||
handler.handle(data, iPayloadContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.coloryr.allmusic.comm;
|
||||
|
||||
import com.coloryr.allmusic.codec.MusicPack;
|
||||
import com.coloryr.allmusic.codec.MusicPacketCodec;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
public record MusicCodec(MusicPack pack) implements CustomPacketPayload {
|
||||
public static final Identifier channel =
|
||||
Identifier.fromNamespaceAndPath("allmusic", "channel");
|
||||
|
||||
public static final Type<MusicCodec> TYPE = new Type<>(channel);
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, MusicCodec> CODEC = StreamCodec.of((buf, value) -> MusicPacketCodec.pack(buf, value.pack),
|
||||
(buf) -> {
|
||||
return new MusicCodec(MusicPacketCodec.decode(buf));
|
||||
});
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
modLoader = "javafml"
|
||||
loaderVersion = "[2,)"
|
||||
license = "GPL-3.0"
|
||||
issueTrackerURL = "https://github.com/Coloryr/AllMusic/issues"
|
||||
|
||||
[[mods]]
|
||||
modId = "allmusic"
|
||||
version = "${version}"
|
||||
displayName = "AllMusic Client"
|
||||
displayURL = "https://github.com/Coloryr/AllMusic/"
|
||||
logoFile = "icon.png"
|
||||
authors = "Color_yr"
|
||||
description = '''AllMusic Client Mod'''
|
||||
|
||||
[[mods]]
|
||||
modId = "allmusic_client"
|
||||
version = "${version}"
|
||||
displayName = "AllMusic Client"
|
||||
displayURL = "https://github.com/Coloryr/AllMusic/"
|
||||
logoFile = "icon.png"
|
||||
authors = "Color_yr"
|
||||
description = '''AllMusic Client Mod'''
|
||||
|
||||
[[dependencies.allmusic_client]]
|
||||
modId = "neoforge"
|
||||
type = "required"
|
||||
versionRange = "[26.2.0.1-beta,)"
|
||||
ordering = "NONE"
|
||||
side = "CLIENT"
|
||||
|
||||
[[dependencies.allmusic_client]]
|
||||
modId = "minecraft"
|
||||
type = "required"
|
||||
versionRange = "[26.2,)"
|
||||
ordering = "NONE"
|
||||
side = "CLIENT"
|
||||
|
||||
[[dependencies.allmusic_client]]
|
||||
modId="modernui"
|
||||
type="optional"
|
||||
ordering="AFTER"
|
||||
side="CLIENT"
|
||||
|
||||
[[dependencies.allmusic_client]]
|
||||
modId="voicechat_api"
|
||||
type="optional"
|
||||
ordering="AFTER"
|
||||
side="CLIENT"
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
@@ -22,6 +22,6 @@ side = "BOTH"
|
||||
[[dependencies.allmusic_server]]
|
||||
modId = "minecraft"
|
||||
type = "required"
|
||||
versionRange = "[1.21,1.21.1)"
|
||||
versionRange = "[1.21,)"
|
||||
ordering = "NONE"
|
||||
side = "BOTH"
|
||||
@@ -9,6 +9,7 @@ include(":client:fabric_1_21")
|
||||
include(":client:fabric_1_21_6")
|
||||
include(":client:fabric_1_21_11")
|
||||
include(":client:fabric_26_1")
|
||||
include(":client:fabric_26_2")
|
||||
//include(":client:forge_1_7_10")
|
||||
//include(":client:forge_1_12_2")
|
||||
include(":client:forge_1_16_5")
|
||||
@@ -17,6 +18,7 @@ include(":client:neoforge_1_21")
|
||||
include(":client:neoforge_1_21_6")
|
||||
include(":client:neoforge_1_21_11")
|
||||
include(":client:neoforge_26_1")
|
||||
include(":client:neoforge_26_2")
|
||||
|
||||
include(":server")
|
||||
|
||||
|
||||
Reference in new issue
Block a user