diff --git a/build.gradle b/build.gradle index db51ea6..672cf3c 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,7 @@ plugins { id 'maven-publish' id 'com.github.johnrengelman.shadow' version '8.1.1' id 'net.neoforged.moddev.legacyforge' version '2.0.103' + id 'com.dorongold.task-tree' version '2.1.1' } java { @@ -382,3 +383,122 @@ idea { tasks.withType(GenerateModuleMetadata) { enabled = false } +tasks.register('showTaskTree') { + doLast { + def showTaskDeps + showTaskDeps = { task, prefix = '' -> + println "${prefix}${task.name}" + task.getTaskDependencies().getDependencies(task).each { dep -> + showTaskDeps(dep, prefix + ' ') + } + } + + def targetTask = tasks.findByName('build') + if (targetTask) { + println "构建任务依赖树:" + showTaskDeps(targetTask) + } else { + println "未找到 build 任务" + } + } +} +/**
+ build
+ ├── check
+ │   └── test
+ │       ├── compileTestJava
+ │       │   ├── classes
+ │       │   │   ├── compileJava
+ │       │   │   │   └── createMcpToSrg
+ │       │   │   │       └── extractSrg
+ │       │   │   │           └── downloadMcpConfig
+ │       │   │   └── processResources
+ │       │   └── compileJava
+ │       │       └── createMcpToSrg
+ │       │           └── extractSrg
+ │       │               └── downloadMcpConfig
+ │       ├── testClasses
+ │       │   ├── processTestResources
+ │       │   └── compileTestJava
+ │       │       ├── classes
+ │       │       │   ├── compileJava
+ │       │       │   │   └── createMcpToSrg
+ │       │       │   │       └── extractSrg
+ │       │       │   │           └── downloadMcpConfig
+ │       │       │   └── processResources
+ │       │       └── compileJava
+ │       │           └── createMcpToSrg
+ │       │               └── extractSrg
+ │       │                   └── downloadMcpConfig
+ │       ├── classes
+ │       │   ├── compileJava
+ │       │   │   └── createMcpToSrg
+ │       │   │       └── extractSrg
+ │       │   │           └── downloadMcpConfig
+ │       │   └── processResources
+ │       └── compileJava
+ │           └── createMcpToSrg
+ │               └── extractSrg
+ │                   └── downloadMcpConfig
+ └── assemble
+ ├── reobfJarJar
+ │   ├── createMcpToSrg
+ │   │   └── extractSrg
+ │   │       └── downloadMcpConfig
+ │   ├── configureReobfTaskForReobfJarJar
+ │   └── proguard
+ │       └── jarJar
+ │           ├── classes
+ │           │   ├── compileJava
+ │           │   │   └── createMcpToSrg
+ │           │   │       └── extractSrg
+ │           │   │           └── downloadMcpConfig
+ │           │   └── processResources
+ │           ├── compileJava
+ │           │   └── createMcpToSrg
+ │           │       └── extractSrg
+ │           │           └── downloadMcpConfig
+ │           └── addMixinsToJarJar
+ │               └── compileJava
+ │                   └── createMcpToSrg
+ │                       └── extractSrg
+ │                           └── downloadMcpConfig
+ ├── reobfJar
+ │   ├── jar
+ │   │   ├── classes
+ │   │   │   ├── compileJava
+ │   │   │   │   └── createMcpToSrg
+ │   │   │   │       └── extractSrg
+ │   │   │   │           └── downloadMcpConfig
+ │   │   │   └── processResources
+ │   │   ├── compileJava
+ │   │   │   └── createMcpToSrg
+ │   │   │       └── extractSrg
+ │   │   │           └── downloadMcpConfig
+ │   │   └── addMixinsToJar
+ │   │       └── compileJava
+ │   │           └── createMcpToSrg
+ │   │               └── extractSrg
+ │   │                   └── downloadMcpConfig
+ │   ├── createMcpToSrg
+ │   │   └── extractSrg
+ │   │       └── downloadMcpConfig
+ │   └── configureReobfTaskForReobfJar
+ └── jar
+ ├── classes
+ │   ├── compileJava
+ │   │   └── createMcpToSrg
+ │   │       └── extractSrg
+ │   │           └── downloadMcpConfig
+ │   └── processResources
+ ├── compileJava
+ │   └── createMcpToSrg
+ │       └── extractSrg
+ │           └── downloadMcpConfig
+ └── addMixinsToJar
+ └── compileJava
+ └── createMcpToSrg
+ └── extractSrg
+ └── downloadMcpConfig
+ 
+ */ \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 7df8c52..9e0d342 100644 --- a/gradle.properties +++ b/gradle.properties @@ -33,7 +33,7 @@ mod_name=3944Realms 's Lib Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=MIT # The mod version. See https://semver.org/ -mod_version=0.0.17 +mod_version=0.0.18 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/generated/resources/.cache/1de3d2ee724999f84a11b20b51c37030049be277 b/src/generated/resources/.cache/1de3d2ee724999f84a11b20b51c37030049be277 index 6acc8d5..acf9f54 100644 --- a/src/generated/resources/.cache/1de3d2ee724999f84a11b20b51c37030049be277 +++ b/src/generated/resources/.cache/1de3d2ee724999f84a11b20b51c37030049be277 @@ -1,2 +1,2 @@ -// 1.20.1 2025-10-25T19:14:21.1829335 Languages: zh_tw -4cb94c651f6aa74538a2ab25cb183cffd75be688 assets/lib39/lang/zh_tw.json +// 1.20.1 2025-11-22T23:38:13.2517748 Languages: zh_tw +84dba66c4c768fd7754eaabe990c96f14a30c1df assets/lib39/lang/zh_tw.json diff --git a/src/generated/resources/.cache/2a65ee2815744be1ef1ffdae1c9a37f2a9cbe2ac b/src/generated/resources/.cache/2a65ee2815744be1ef1ffdae1c9a37f2a9cbe2ac index d291622..eb816d6 100644 --- a/src/generated/resources/.cache/2a65ee2815744be1ef1ffdae1c9a37f2a9cbe2ac +++ b/src/generated/resources/.cache/2a65ee2815744be1ef1ffdae1c9a37f2a9cbe2ac @@ -1,2 +1,2 @@ -// 1.20.1 2025-10-25T19:14:21.1764262 Languages: zh_cn -a1db601a0fca923c5434b8b0843773a3115d0b59 assets/lib39/lang/zh_cn.json +// 1.20.1 2025-11-22T23:38:13.249775 Languages: zh_cn +48655f133966c9a854c57b560d070850af5a289f assets/lib39/lang/zh_cn.json diff --git a/src/generated/resources/.cache/2dbf84d84cf6f7b7a95fea9038e192dbf226e5f5 b/src/generated/resources/.cache/2dbf84d84cf6f7b7a95fea9038e192dbf226e5f5 index c17c4ef..5a2f35d 100644 --- a/src/generated/resources/.cache/2dbf84d84cf6f7b7a95fea9038e192dbf226e5f5 +++ b/src/generated/resources/.cache/2dbf84d84cf6f7b7a95fea9038e192dbf226e5f5 @@ -1,3 +1,4 @@ -// 1.20.1 2025-10-25T18:42:29.3405259 Item Models: lib39 +// 1.20.1 2025-11-22T23:38:13.2527751 Item Models: lib39 14f581c8f8e7f0de004c57a180f371e60e7b12ae assets/lib39/models/item/fabric.json +70583055336790fc837836ea6b49d16cfc8b64b8 assets/lib39/models/item/forge.json 447b36747d0aa8748dcd86715f4cce2cff19aca7 assets/lib39/models/item/neoforge.json diff --git a/src/generated/resources/.cache/82018c5420b46ddbb7071e62df09fdecd98133e6 b/src/generated/resources/.cache/82018c5420b46ddbb7071e62df09fdecd98133e6 index b518cbb..9003b0a 100644 --- a/src/generated/resources/.cache/82018c5420b46ddbb7071e62df09fdecd98133e6 +++ b/src/generated/resources/.cache/82018c5420b46ddbb7071e62df09fdecd98133e6 @@ -1,2 +1,2 @@ -// 1.20.1 2025-10-25T19:14:21.1804258 Languages: lzh -098df025475d3f4d9d2abefa91a7d38c44644ba4 assets/lib39/lang/lzh.json +// 1.20.1 2025-11-22T23:38:13.2517748 Languages: lzh +a12c11d89d484a0e4f193ebd11b63722b8c501df assets/lib39/lang/lzh.json diff --git a/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 b/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 index 99da09b..d94a19a 100644 --- a/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 +++ b/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 @@ -1,2 +1,2 @@ -// 1.20.1 2025-10-25T19:14:21.1784269 Languages: en_us -c6b6aadca0a922823a8c949ebde93f8f999737f9 assets/lib39/lang/en_us.json +// 1.20.1 2025-11-22T23:38:13.2507757 Languages: en_us +65df86271a054138e168311be826408455b3c33a assets/lib39/lang/en_us.json diff --git a/src/generated/resources/assets/lib39/lang/en_us.json b/src/generated/resources/assets/lib39/lang/en_us.json index 1989dd6..98e9097 100644 --- a/src/generated/resources/assets/lib39/lang/en_us.json +++ b/src/generated/resources/assets/lib39/lang/en_us.json @@ -1,4 +1,5 @@ { "item.lib39.fabric": "Fabric", + "item.lib39.forge": "Forge", "item.lib39.neoforge": "NeoForge" } \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/lang/lzh.json b/src/generated/resources/assets/lib39/lang/lzh.json index 865d7b3..99cd302 100644 --- a/src/generated/resources/assets/lib39/lang/lzh.json +++ b/src/generated/resources/assets/lib39/lang/lzh.json @@ -1,4 +1,5 @@ { "item.lib39.fabric": "織", + "item.lib39.forge": "砧", "item.lib39.neoforge": "狸" } \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/lang/zh_cn.json b/src/generated/resources/assets/lib39/lang/zh_cn.json index ad3ca87..19f82cb 100644 --- a/src/generated/resources/assets/lib39/lang/zh_cn.json +++ b/src/generated/resources/assets/lib39/lang/zh_cn.json @@ -1,4 +1,5 @@ { "item.lib39.fabric": "织布", + "item.lib39.forge": "铁砧", "item.lib39.neoforge": "小狐狸" } \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/lang/zh_tw.json b/src/generated/resources/assets/lib39/lang/zh_tw.json index e14a166..ad24e76 100644 --- a/src/generated/resources/assets/lib39/lang/zh_tw.json +++ b/src/generated/resources/assets/lib39/lang/zh_tw.json @@ -1,4 +1,5 @@ { "item.lib39.fabric": "織布", + "item.lib39.forge": "铁砧", "item.lib39.neoforge": "狐狸" } \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/item/forge.json b/src/generated/resources/assets/lib39/models/item/forge.json new file mode 100644 index 0000000..8989f8c --- /dev/null +++ b/src/generated/resources/assets/lib39/models/item/forge.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "lib39:item/forge" + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/lib39/Lib39.java b/src/main/java/top/r3944realms/lib39/Lib39.java index e294953..5718f1d 100644 --- a/src/main/java/top/r3944realms/lib39/Lib39.java +++ b/src/main/java/top/r3944realms/lib39/Lib39.java @@ -1,8 +1,11 @@ package top.r3944realms.lib39; +import net.minecraft.resources.ResourceLocation; import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.loading.FMLEnvironment; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import top.r3944realms.lib39.core.network.NetworkHandler; @@ -29,6 +32,11 @@ public class Lib39 { initialize(); } + /** + * The constant ENABLE_EXAMPLES_PROPERTY_KEY. + */ + public static final String ENABLE_EXAMPLES_PROPERTY_KEY = "lib39.enable_examples"; + /** * Initialize. */ @@ -43,6 +51,17 @@ public class Lib39 { } + /** + * Rl resource location. + * + * @param path the path + * @return the resource location + */ + @Contract("_ -> new") + public static @NotNull ResourceLocation rl(String path) { + return new ResourceLocation(MOD_ID, path); + } + /** * The type Mod info. */ @@ -66,7 +85,7 @@ public class Lib39 { * @return the boolean */ static boolean shouldRegisterExamples() { - return !FMLEnvironment.production; + return !FMLEnvironment.production || Boolean.getBoolean(ENABLE_EXAMPLES_PROPERTY_KEY); } /** diff --git a/src/main/java/top/r3944realms/lib39/api/event/SyncManagerRegisterEvent.java b/src/main/java/top/r3944realms/lib39/api/event/SyncManagerRegisterEvent.java index ebb61a4..0f106a6 100644 --- a/src/main/java/top/r3944realms/lib39/api/event/SyncManagerRegisterEvent.java +++ b/src/main/java/top/r3944realms/lib39/api/event/SyncManagerRegisterEvent.java @@ -4,6 +4,7 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.Entity; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.eventbus.api.Event; +import org.jetbrains.annotations.NotNull; import top.r3944realms.lib39.core.sync.ISyncData; import top.r3944realms.lib39.core.sync.ISyncManager; import top.r3944realms.lib39.core.sync.SyncData2Manager; @@ -159,7 +160,7 @@ public class SyncManagerRegisterEvent extends Event { ResourceLocation id, ISyncManager syncManager, Function> dataProvider, - Class... allowedEntityClasses + Class @NotNull ... allowedEntityClasses ) { registerSyncManager(id, syncManager, dataProvider); if (allowedEntityClasses.length > 0) { diff --git a/src/main/java/top/r3944realms/lib39/client/gui/component/WheelWidget.java b/src/main/java/top/r3944realms/lib39/client/gui/component/WheelWidget.java new file mode 100644 index 0000000..a4ecbfe --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/client/gui/component/WheelWidget.java @@ -0,0 +1,769 @@ +package top.r3944realms.lib39.client.gui.component; + +import com.mojang.blaze3d.platform.Window; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.*; +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.renderer.ShaderInstance; +import net.minecraft.network.chat.Component; +import org.joml.Matrix4f; +import org.joml.Vector2f; +import top.r3944realms.lib39.client.shader.Lib39Shaders; +import top.r3944realms.lib39.util.MathUtil; +import top.r3944realms.lib39.util.lang.FourConsumer; +import top.r3944realms.lib39.util.lang.Pair; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * The type Wheel widget. + * + * @author QiuShui1012 + */ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +public class WheelWidget extends AbstractWidget { + /** + * The constant IGNORE_CURSOR_MOVE_LENGTH. + */ + public static final int IGNORE_CURSOR_MOVE_LENGTH = 15; + private static final Vector2f ROTATION_START = new Vector2f(0, 1); + private static final int SELECTION_EFFECT_COLOR = 0xddFFFF00; + private static final int SELECTION_EFFECT_RADIUS = 20; + + private final Minecraft minecraft = Minecraft.getInstance(); + private final Vector2f centerPos; + private final float ringInnerRadius; + private final float ringOuterRadius; + private final int delay; + private final int animationMs; + private final int closingAnimationMs; //ms + private final int ringColor; + private final int selectionEffectColor; + private final int selectionEffectRadius; + private final float selectionAnimationSpeedFactor; + private final int textColor; + private final float textScale; + private final List sections = new ArrayList<>(); + + private long displayTime = System.currentTimeMillis(); + private float currentAngle = 0; + + /** + * Gets current section index. + * + * @return the current section index + */ + public int getCurrentSectionIndex() { + return currentSectionIndex; + } + + /** + * Is closing animation started boolean. + * + * @return the boolean + */ + public boolean isClosingAnimationStarted() { + return closingAnimationStarted; + } + + private int currentSectionIndex = -1; + private Vector2f selectionEffectPos; + private boolean animationStarted = false; + + /** + * Sets closing animation started. + * + * @param closingAnimationStarted the closing animation started + */ + public void setClosingAnimationStarted(boolean closingAnimationStarted) { + this.closingAnimationStarted = closingAnimationStarted; + } + + private boolean closingAnimationStarted = false; + + /** + * Instantiates a new Wheel widget. + * + * @param x the x + * @param y the y + * @param width the width + * @param height the height + * @param ringInnerRadius the ring inner radius + * @param ringOuterRadius the ring outer radius + * @param textScale the text scale + * @param sections the sections + */ + public WheelWidget( + int x, int y, int width, int height, + float ringInnerRadius, float ringOuterRadius, float textScale, + List>> sections + ) { + this(x, y, width, height, Component.empty(), ringInnerRadius, ringOuterRadius, textScale, sections); + } + + /** + * Instantiates a new Wheel widget. + * + * @param x the x + * @param y the y + * @param width the width + * @param height the height + * @param ringInnerRadius the ring inner radius + * @param ringOuterRadius the ring outer radius + * @param textScale the text scale + * @param degreeOffsetAngle the degree offset angle + * @param sections the sections + */ + public WheelWidget( + int x, int y, int width, int height, + float ringInnerRadius, float ringOuterRadius, float textScale, float degreeOffsetAngle, + List>> sections + ) { + this(x, y, width, height, Component.empty(), ringInnerRadius, ringOuterRadius, textScale, degreeOffsetAngle, sections); + } + + /** + * Instantiates a new Wheel widget. + * + * @param x the x + * @param y the y + * @param width the width + * @param height the height + * @param ringInnerRadius the ring inner radius + * @param ringOuterRadius the ring outer radius + * @param sections the sections + */ + public WheelWidget( + int x, int y, int width, int height, + float ringInnerRadius, float ringOuterRadius, + List>> sections + ) { + this(x, y, width, height, Component.empty(), ringInnerRadius, ringOuterRadius, sections); + } + + /** + * Instantiates a new Wheel widget. + * + * @param x the x + * @param y the y + * @param width the width + * @param height the height + * @param message the message + * @param ringInnerRadius the ring inner radius + * @param ringOuterRadius the ring outer radius + * @param textScale the text scale + * @param degreeOffsetAngle the degree offset angle + * @param sections the sections + */ + public WheelWidget( + int x, int y, int width, int height, Component message, + float ringInnerRadius, float ringOuterRadius, float textScale, float degreeOffsetAngle, + List>> sections + ) { + this( + x, y, width, height, message, + ringInnerRadius, ringOuterRadius, + 150, 300, 150, + 0x00000000, + 0xddffff00, 20, 5f, + 0xfdfdfd, textScale, degreeOffsetAngle, + sections + ); + } + + /** + * Instantiates a new Wheel widget. + * + * @param x the x + * @param y the y + * @param width the width + * @param height the height + * @param message the message + * @param ringInnerRadius the ring inner radius + * @param ringOuterRadius the ring outer radius + * @param sections the sections + */ + public WheelWidget( + int x, int y, int width, int height, Component message, + float ringInnerRadius, float ringOuterRadius, + List>> sections + ) { + this( + x, y, width, height, message, + ringInnerRadius, ringOuterRadius, + 150, 300, 150, + 0x00000000, + 0xddffff00, 20, 5f, + 0xfdfdfd, 1f, 0f, + sections + ); + } + + /** + * Instantiates a new Wheel widget. + * + * @param x the x + * @param y the y + * @param width the width + * @param height the height + * @param message the message + * @param ringInnerRadius the ring inner radius + * @param ringOuterRadius the ring outer radius + * @param degreeOffsetAngle the degree offset angle + * @param sections the sections + */ + public WheelWidget( + int x, int y, int width, int height, Component message, + float ringInnerRadius, float ringOuterRadius, float degreeOffsetAngle, + List>> sections + ) { + this( + x, y, width, height, message, + ringInnerRadius, ringOuterRadius, + 150, 300, 150, + 0x00000000, + 0xddffff00, 20, 5f, + 0xfdfdfd, 1f, degreeOffsetAngle, + sections + ); + } + + /** + * Instantiates a new Wheel widget. + * + * @param x the x + * @param y the y + * @param width the width + * @param height the height + * @param ringInnerRadius the ring inner radius + * @param ringOuterRadius the ring outer radius + * @param delay the delay + * @param animationMs the animation ms + * @param closingAnimationMs the closing animation ms + * @param ringColor the ring color + * @param selectionEffectColor the selection effect color + * @param selectionEffectRadius the selection effect radius + * @param selectionAnimationSpeedFactor the selection animation speed factor + * @param textColor the text color + * @param textScale the text scale + * @param sections the sections + */ + public WheelWidget( + int x, int y, int width, int height, + float ringInnerRadius, float ringOuterRadius, + int delay, int animationMs, int closingAnimationMs, + int ringColor, + int selectionEffectColor, int selectionEffectRadius, float selectionAnimationSpeedFactor, + int textColor, float textScale, + List>> sections + ) { + this( + x, y, width, height, Component.empty(), + ringInnerRadius, ringOuterRadius, + delay, animationMs, closingAnimationMs, + ringColor, + selectionEffectColor, selectionEffectRadius, selectionAnimationSpeedFactor, + textColor, textScale, 0f, + sections + ); + } + + /** + * Instantiates a new Wheel widget. + * + * @param x the x + * @param y the y + * @param width the width + * @param height the height + * @param message the message + * @param ringInnerRadius the ring inner radius + * @param ringOuterRadius the ring outer radius + * @param delay the delay + * @param animationMs the animation ms + * @param closingAnimationMs the closing animation ms + * @param ringColor the ring color + * @param selectionEffectColor the selection effect color + * @param selectionEffectRadius the selection effect radius + * @param selectionAnimationSpeedFactor the selection animation speed factor + * @param textColor the text color + * @param textScale the text scale + * @param degreeOffsetAngle the degree offset angle + * @param sections the sections + */ + public WheelWidget( + int x, int y, int width, int height, Component message, + float ringInnerRadius, float ringOuterRadius, + int delay, int animationMs, int closingAnimationMs, + int ringColor, + int selectionEffectColor, int selectionEffectRadius, float selectionAnimationSpeedFactor, + int textColor, float textScale, float degreeOffsetAngle, + List>> sections + ) { + super(x, y, width, height, message); + this.centerPos = new Vector2f(this.getX() + this.getWidth() / 2f, this.getY() + this.getHeight() / 2f); + this.ringInnerRadius = Math.max(ringInnerRadius, IGNORE_CURSOR_MOVE_LENGTH); + this.ringOuterRadius = ringOuterRadius; + this.delay = delay; + this.animationMs = animationMs; + this.closingAnimationMs = closingAnimationMs; + this.ringColor = ringColor; + this.selectionEffectColor = selectionEffectColor; + this.selectionEffectRadius = selectionEffectRadius; + this.selectionAnimationSpeedFactor = selectionAnimationSpeedFactor; + this.textColor = textColor; + this.textScale = textScale; + float degreeEachRotation = 360f / sections.size(); + for (int i = 0; i < sections.size(); i++) { + Pair> section = sections.get(i); + float rotation = MathUtil.clampWithProportion((degreeEachRotation * i + degreeOffsetAngle) % 360, 0, 360); + Vector2f rotated = MathUtil.rotationDegrees(ROTATION_START, rotation) + .mul(1, -1) + .mul(this.getSectionCircleDiameter()) + .add(this.centerPos); + float detectionStart = (float) (Math.toRadians(rotation - degreeEachRotation / 2f) + Math.PI * 2); + float detectionEnd = (float) (Math.toRadians(rotation + degreeEachRotation / 2f) + Math.PI * 2); + detectionStart = detectionStart % (float) (Math.PI * 2); + detectionEnd = detectionEnd % (float) (Math.PI * 2); + this.sections.add(new WheelSection( + rotated, + (float) (Math.toRadians(rotation) % (Math.PI * 2)), + detectionStart, + detectionEnd, + section.first, + section.second + )); + } + this.selectionEffectPos = MathUtil.rotate( + MathUtil.copy(ROTATION_START) + .mul(this.getSectionCircleDiameter()), + this.currentAngle + ); + } + + /** + * Gets section circle diameter. + * + * @return the section circle diameter + */ +// 滚轮选择器中每个扇形的圆形直径 + public float getSectionCircleDiameter() { + return this.ringOuterRadius + this.ringInnerRadius; + } + + /** + * Sets current index. + * + * @param index the index + * @return the current index + */ + public WheelWidget setCurrentIndex(int index) { + this.currentSectionIndex = index; + this.currentAngle = this.sections.get(index).angle; + this.selectionEffectPos = MathUtil.rotate( + MathUtil.copy(ROTATION_START) + .mul(this.getSectionCircleDiameter()), + this.currentAngle + ); + return this; + } + + /** + * Gets section size. + * + * @return the section size + */ + public int getSectionSize() { + return this.sections.size(); + } + + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double delta) { + if (delta > 0) { + if (this.currentSectionIndex == this.getSectionSize() - 1) { + this.currentSectionIndex = 0; + } else { + this.currentSectionIndex++; + } + } else if (delta < 0) { + if (this.currentSectionIndex == 0) { + this.currentSectionIndex = this.getSectionSize() - 1; + } else { + this.currentSectionIndex--; + } + } + for (WheelSection section : this.sections) { + if (this.sections.indexOf(section) == this.currentSectionIndex) { + this.currentAngle = section.angle; + return true; + } + } + return true; + } + + /** + * Check mouse pos. + * + * @param mouseX the mouse x + * @param mouseY the mouse y + */ + public void checkMousePos(double mouseX, double mouseY) { + if (this.closingAnimationStarted) return; + float centerX = this.centerPos.x; + float centerY = this.centerPos.y; + // 鼠标距离屏幕中心的位置向量 + Vector2f cursorPos = new Vector2f((float) mouseX - centerX, (float) mouseY - centerY); + + if (cursorPos.length() < IGNORE_CURSOR_MOVE_LENGTH) return; + + Vector2f rotationStart = new Vector2f(0, 1); + cursorPos.normalize(); + // 计算夹角弧度 + double rot = Math.acos(rotationStart.dot(cursorPos) / (rotationStart.length() * cursorPos.length())); + double rotation = cursorPos.x < 0 ? Math.PI - rot : Math.PI + rot; + for (WheelSection section : this.sections) { + if (section.angleStart > section.angleEnd && rotation >= section.angleStart + || rotation >= section.angleStart && rotation <= section.angleEnd + ) { + this.currentAngle = section.angle; + this.currentSectionIndex = this.sections.indexOf(section); + break; + } + } + } + + /** + * Should render boolean. + * + * @return the boolean + */ + public boolean shouldRender() { + if (this.animationStarted) return true; + return (this.displayTime + this.delay) <= System.currentTimeMillis(); + } + + @Override + public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + this.checkMousePos(mouseX, mouseY); + this.renderWidget(guiGraphics, mouseX, mouseY, partialTick); + } + + @Override + protected void renderWidget(GuiGraphics guiGraphics, int i, int i1, float v) { + RenderSystem.enableDepthTest(); + RenderSystem.enableBlend(); + this.renderClosingAnimation(guiGraphics); + if (!this.shouldRender()) { + return; + } + if (this.closingAnimationStarted) return; + if (!this.animationStarted) { + this.animationStarted = true; + this.displayTime = System.currentTimeMillis(); + } + PoseStack poseStack = guiGraphics.pose(); + float delta = this.displayTime + this.animationMs - System.currentTimeMillis(); + if (delta > 0) { + float progress = 1 - (delta / this.animationMs); + progress = (float) (-Math.pow(progress, 2) + 2 * progress); + if (progress == 0) return; + this.renderProgressAnimation(guiGraphics, progress); + return; + } + renderRing( + guiGraphics, + this.centerPos.x, + this.centerPos.y, + this.ringColor, + this.ringInnerRadius * 2, + this.ringOuterRadius * 2 + ); + this.renderSelection(guiGraphics); + for (WheelSection value : this.sections) { + float x = value.center.x; + float y = value.center.y; + poseStack.pushPose(); + poseStack.translate(x - 10, y - 10, 100); + value.renderer.accept(guiGraphics, poseStack, 20, 20); + poseStack.popPose(); + poseStack.pushPose(); + float coordinateScale = 0.7f; + float offsetX = 0.1f * this.width; + float offsetY = 0.1f * this.height; + float adjustedX = (x - offsetX) / coordinateScale; + float adjustedY = (y - offsetY - 20 * this.textScale) / coordinateScale; + + poseStack.translate(offsetX, offsetY, 0); + poseStack.scale(coordinateScale, coordinateScale, coordinateScale); + poseStack.translate(adjustedX, adjustedY, 0); + poseStack.scale(this.textScale / coordinateScale, this.textScale / coordinateScale, this.textScale / coordinateScale); + guiGraphics.drawCenteredString( + minecraft.font, + value.subTitle, + 0, + 0, + (0xff << 24) | this.textColor + ); + poseStack.popPose(); + } + RenderSystem.disableDepthTest(); + RenderSystem.disableBlend(); + } + + /** + * Render closing animation. + * + * @param guiGraphics the gui graphics + */ + public void renderClosingAnimation(GuiGraphics guiGraphics) { + if (!this.closingAnimationStarted) return; + float delta = this.displayTime + this.closingAnimationMs - System.currentTimeMillis(); + float progress = delta / this.closingAnimationMs; + if(progress >= 1 || progress <= 0) { + this.minecraft.setScreen(null); + } + this.renderProgressAnimation(guiGraphics, progress); + } + private void renderProgressAnimation(GuiGraphics guiGraphics, float progress) { + progress = (float) (-Math.pow(progress, 2) + 2 * progress); + if (progress == 0) return; + PoseStack poseStack = guiGraphics.pose(); + poseStack.pushPose(); + renderRing( + guiGraphics, + this.centerPos.x, + this.centerPos.y, + this.ringColor, + this.ringInnerRadius * 2 * progress, + this.ringOuterRadius * 2 * progress + ); + poseStack.popPose(); + if(this.currentSectionIndex != -1) { + WheelSection section = this.sections.get(this.currentSectionIndex); + Vector2f center = new Vector2f( + (section.center.x - this.centerPos.x) / this.getSectionCircleDiameter(), + (section.center.y - this.centerPos.y) / this.getSectionCircleDiameter() + ).mul(this.getSectionCircleDiameter() * progress).add(this.centerPos.x, this.centerPos.y); + renderSelectionEffect( + guiGraphics, + center.x, + center.y, + this.selectionEffectColor, + this.selectionEffectRadius + ); + } + for (WheelSection value : this.sections) { + if (sections.get(0) != value) continue; + Vector2f center = new Vector2f( + (value.center.x - this.centerPos.x) / this.getSectionCircleDiameter(), + (value.center.y - this.centerPos.y) / this.getSectionCircleDiameter() + ).mul(this.getSectionCircleDiameter() * progress).add(this.centerPos.x, this.centerPos.y); + float x = center.x; + float y = center.y; + poseStack.pushPose(); + poseStack.translate(x - 10, y - 10, 100); + value.renderer.accept(guiGraphics, poseStack, 20, 20); + poseStack.pushPose(); + } + } + + /** + * Render ring. + * + * @param guiGraphics the gui graphics + * @param centerX the center x + * @param centerY the center y + * @param color the color + * @param innerRadius the inner radius + * @param outerRadius the outer radius + */ + public static void renderRing( + GuiGraphics guiGraphics, + float centerX, + float centerY, + int color, + float innerRadius, // 改为半径 + float outerRadius // 改为半径 + ) { + PoseStack poseStack = guiGraphics.pose(); + poseStack.pushPose(); + + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + + // 计算足够大的绘制区域来覆盖整个环形(基于外半径) + float margin = outerRadius + 100f; // 使用半径计算边距 + float x1 = centerX - margin; + float y1 = centerY - margin; + float x2 = centerX + margin; + float y2 = centerY + margin; + + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + + Matrix4f matrix = poseStack.last().pose(); + buffer.vertex(matrix, x1, y1, -300).color(color).endVertex(); + buffer.vertex(matrix, x1, y2, -300).color(color).endVertex(); + buffer.vertex(matrix, x2, y2, -300).color(color).endVertex(); + buffer.vertex(matrix, x2, y1, -300).color(color).endVertex(); + + setupRingShader(centerX, centerY, innerRadius, outerRadius); + + BufferUploader.drawWithShader(buffer.end()); + poseStack.popPose(); + } + + private static void setupRingShader(float centerX, float centerY, float innerRadius, float outerRadius) { + Window window = Minecraft.getInstance().getWindow(); + float guiScale = (float) window.getGuiScale(); + + RenderSystem.setShader(Lib39Shaders::getRingShader); + + // 转换到像素坐标(考虑GUI缩放) + float pixelCenterX = centerX * guiScale; + float pixelCenterY = window.getHeight() - (centerY * guiScale); // 翻转Y坐标 + + // 半径考虑GUI缩放 + float pixelInnerRadius = innerRadius * guiScale; + float pixelOuterRadius = outerRadius * guiScale; + float pixelAntiAliasing = 2.0f * guiScale; // 抗锯齿范围 + + System.out.println("Shader Params - Center: (" + pixelCenterX + ", " + pixelCenterY + + "), InnerRadius: " + pixelInnerRadius + ", OuterRadius: " + pixelOuterRadius); + + ShaderInstance shader = Lib39Shaders.getRingShader(); + shader.safeGetUniform("Center").set(pixelCenterX, pixelCenterY); + shader.safeGetUniform("InnerRadius").set(pixelInnerRadius); + shader.safeGetUniform("OuterRadius").set(pixelOuterRadius); + shader.safeGetUniform("AntiAliasing").set(pixelAntiAliasing); + shader.safeGetUniform("ColorModulator").set(1.0f, 1.0f, 1.0f, .5f); + } + + private void renderSelection(GuiGraphics guiGraphics) { + float selectionEffectAngle = MathUtil.angle( + MathUtil.copy(ROTATION_START), + this.selectionEffectPos + ); + + float diffAngle = this.currentAngle - selectionEffectAngle; + + if (diffAngle > Math.PI) { + diffAngle -= (float) (Math.PI * 2); + } else if (diffAngle < -Math.PI) { + diffAngle += (float) (Math.PI * 2); + } + + this.selectionEffectPos = MathUtil.rotate( + this.selectionEffectPos, + diffAngle / this.selectionAnimationSpeedFactor + ); + + Vector2f pos = MathUtil.copy(this.selectionEffectPos) + .mul(1, -1) + .add(this.centerPos); + + // 调用时使用半径 + renderSelectionEffect( + guiGraphics, + pos.x, + pos.y, + SELECTION_EFFECT_COLOR, + SELECTION_EFFECT_RADIUS // 确保这是半径值 + ); + } + + /** + * Render selection effect. + * + * @param guiGraphics the gui graphics + * @param centerX the center x + * @param centerY the center y + * @param color the color + * @param radius the radius + */ + public static void renderSelectionEffect( + GuiGraphics guiGraphics, + float centerX, + float centerY, + int color, + float radius + ) { + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.disableDepthTest(); + PoseStack poseStack = guiGraphics.pose(); + Matrix4f matrix4f = poseStack.last().pose(); + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + + float x1 = centerX - radius - 5; + float y1 = centerY - radius - 5; + float x2 = centerX + radius + 5; + float y2 = centerY + radius + 5; + buffer.vertex(matrix4f, x1, y1, -200).color(color).endVertex(); + buffer.vertex(matrix4f, x1, y2, -200).color(color).endVertex(); + buffer.vertex(matrix4f, x2, y2, -200).color(color).endVertex(); + buffer.vertex(matrix4f, x2, y1, -200).color(color).endVertex(); + + Window window = Minecraft.getInstance().getWindow(); + float guiScale = (float) window.getGuiScale(); + RenderSystem.setShader(Lib39Shaders::getSelectionShader); + System.out.println("Selection Effect Params:"); + System.out.println(" Center: " + centerX + ", " + centerY); + System.out.println(" Radius: " + radius); + System.out.println(" GUI Scale: " + guiScale); + System.out.println(" Framebuffer: " + window.getWidth() + "x" + window.getHeight()); + Lib39Shaders.getSelectionShader() + .safeGetUniform("Center") + .set(centerX * guiScale, centerY * guiScale); + Lib39Shaders.getSelectionShader() + .safeGetUniform("FramebufferSize") + .set((float) window.getWidth(), (float) window.getHeight()); + Lib39Shaders.getSelectionShader() + .safeGetUniform("Radius") + .set(radius * guiScale); + Lib39Shaders.getSelectionShader() + .safeGetUniform("AntiAliasingRadius") + .set(guiScale); // 根据需要调整 + RenderSystem.setShaderColor(1, 1, 1, 1); + BufferUploader.drawWithShader(Objects.requireNonNull(buffer.end())); + RenderSystem.enableDepthTest(); + } + + /** + * On closing. + */ + public void onClosing() { + if (this.shouldRender() && !this.closingAnimationStarted) { + this.displayTime = System.currentTimeMillis(); + this.closingAnimationStarted = true; + } else { + this.minecraft.setScreen(null); + } + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { + } + + /** + * The type Wheel section. + */ + public record WheelSection( + Vector2f center, + float angle, + float angleStart, + float angleEnd, + Component subTitle, + FourConsumer renderer + ) { + } +} diff --git a/src/main/java/top/r3944realms/lib39/client/renderer/RadialMenuRenderer.java b/src/main/java/top/r3944realms/lib39/client/renderer/RadialMenuRenderer.java new file mode 100644 index 0000000..a20d515 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/client/renderer/RadialMenuRenderer.java @@ -0,0 +1,381 @@ +package top.r3944realms.lib39.client.renderer; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.*; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.network.chat.Component; +import net.minecraft.util.Mth; +import net.minecraft.world.item.ItemStack; +import org.joml.Matrix4f; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +/** + * 圆形径向菜单渲染器 + * 用于创建美观的圆形选择菜单 + * + * @param the type parameter + */ +public class RadialMenuRenderer { + /** + * The constant DEFAULT_INNER_RADIUS. + */ +// 默认配置常量 + public static final float DEFAULT_INNER_RADIUS = 30f; + /** + * The constant DEFAULT_OUTER_RADIUS. + */ + public static final float DEFAULT_OUTER_RADIUS = 80f; + /** + * The constant DEFAULT_MIDDLE_RADIUS. + */ + public static final float DEFAULT_MIDDLE_RADIUS = 55f; + /** + * The constant DEFAULT_SEGMENTS. + */ + public static final int DEFAULT_SEGMENTS = 64; + + // 配置选项 + private final float innerRadius; + private final float outerRadius; + private final float middleRadius; + private final int segments; + private final boolean enableHoverAnimation; + private final ColorScheme colorScheme; + + // 状态 + private int hoveredIndex = -1; + private final float[] hoverAnimations; + private long lastAnimationTime = 0; + + /** + * The type Color scheme. + */ + public static class ColorScheme { + /** + * The Normal color. + */ + public final float[] normalColor; + /** + * The Hovered color. + */ + public final float[] hoveredColor; + /** + * The Selected color. + */ + public final float[] selectedColor; + /** + * The Background color. + */ + public final float[] backgroundColor; + + /** + * Instantiates a new Color scheme. + * + * @param normalColor the normal color + * @param hoveredColor the hovered color + * @param selectedColor the selected color + * @param backgroundColor the background color + */ + public ColorScheme(float[] normalColor, float[] hoveredColor, float[] selectedColor, float[] backgroundColor) { + this.normalColor = normalColor; + this.hoveredColor = hoveredColor; + this.selectedColor = selectedColor; + this.backgroundColor = backgroundColor; + } + + /** + * The constant DEFAULT. + */ +// 预定义颜色方案 + public static final ColorScheme DEFAULT = new ColorScheme( + new float[]{0.3f, 0.3f, 0.8f, 0.6f}, // 正常 - 蓝色 + new float[]{0.9f, 0.7f, 0.1f, 0.8f}, // 悬停 - 金色 + new float[]{0.2f, 0.8f, 0.2f, 0.9f}, // 选中 - 绿色 + new float[]{0.1f, 0.1f, 0.1f, 0.7f} // 背景 + ); + + /** + * The constant FIRE. + */ + public static final ColorScheme FIRE = new ColorScheme( + new float[]{0.8f, 0.3f, 0.1f, 0.6f}, // 正常 - 红色 + new float[]{1.0f, 0.5f, 0.0f, 0.8f}, // 悬停 - 橙色 + new float[]{1.0f, 0.9f, 0.0f, 0.9f}, // 选中 - 黄色 + new float[]{0.2f, 0.1f, 0.0f, 0.7f} // 背景 + ); + + /** + * The constant NATURE. + */ + public static final ColorScheme NATURE = new ColorScheme( + new float[]{0.2f, 0.6f, 0.3f, 0.6f}, // 正常 - 绿色 + new float[]{0.4f, 0.8f, 0.4f, 0.8f}, // 悬停 - 亮绿 + new float[]{0.1f, 0.9f, 0.7f, 0.9f}, // 选中 - 青绿 + new float[]{0.1f, 0.2f, 0.1f, 0.7f} // 背景 + ); + } + + + /** + * Instantiates a new Radial menu renderer. + */ + public RadialMenuRenderer() { + this(DEFAULT_INNER_RADIUS, DEFAULT_OUTER_RADIUS, ColorScheme.DEFAULT); + } + + /** + * Instantiates a new Radial menu renderer. + * + * @param innerRadius the inner radius + * @param outerRadius the outer radius + */ + public RadialMenuRenderer(float innerRadius, float outerRadius) { + this(innerRadius, outerRadius, ColorScheme.DEFAULT); + } + + /** + * Instantiates a new Radial menu renderer. + * + * @param innerRadius the inner radius + * @param outerRadius the outer radius + * @param colorScheme the color scheme + */ + public RadialMenuRenderer(float innerRadius, float outerRadius, ColorScheme colorScheme) { + this(innerRadius, outerRadius, (innerRadius + outerRadius) / 2f, DEFAULT_SEGMENTS, true, colorScheme); + } + + /** + * Instantiates a new Radial menu renderer. + * + * @param innerRadius the inner radius + * @param outerRadius the outer radius + * @param middleRadius the middle radius + * @param segments the segments + * @param enableHoverAnimation the enable hover animation + * @param colorScheme the color scheme + */ + public RadialMenuRenderer(float innerRadius, float outerRadius, float middleRadius, + int segments, boolean enableHoverAnimation, ColorScheme colorScheme) { + this.innerRadius = innerRadius; + this.outerRadius = outerRadius; + this.middleRadius = middleRadius; + this.segments = segments; + this.enableHoverAnimation = enableHoverAnimation; + this.colorScheme = colorScheme; + this.hoverAnimations = new float[0]; + } + + /** + * 渲染圆形菜单 + * + * @param guiGraphics the gui graphics + * @param entries the entries + * @param titleProvider the title provider + * @param iconProvider the icon provider + * @param selectedIndex the selected index + * @param trackMouse the track mouse + */ + public void render(GuiGraphics guiGraphics, List entries, + Function titleProvider, + Function iconProvider, + int selectedIndex, boolean trackMouse) { + if (entries.isEmpty()) return; + + // 更新动画状态 + updateHoverAnimations(entries.size()); + + // 设置渲染状态 + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.setShader(GameRenderer::getPositionColorShader); + + float centerX = guiGraphics.guiWidth() / 2f; + float centerY = guiGraphics.guiHeight() / 2f; + + guiGraphics.pose().pushPose(); + guiGraphics.pose().translate(centerX, centerY, 0f); + + // 渲染所有扇形区域 + renderSectors(guiGraphics, entries, selectedIndex); + + // 渲染图标和文本 + renderIconsAndText(guiGraphics, entries, titleProvider, iconProvider); + + guiGraphics.pose().popPose(); + + RenderSystem.disableBlend(); + } + + /** + * 渲染扇形区域 + */ + private void renderSectors(GuiGraphics guiGraphics, List entries, int selectedIndex) { + int count = entries.size(); + float angleSize = 360f / count; + + for (int i = 0; i < count; i++) { + float startAngle = -90f + i * angleSize; + float currentOuterRadius = outerRadius; + + // 悬停动画效果 + if (enableHoverAnimation && i < hoverAnimations.length) { + currentOuterRadius += hoverAnimations[i] * 5f; + } + + // 颜色设置 + float[] color = getSectorColor(i, selectedIndex, entries.get(i)); + + // 绘制扇形 + drawSector(guiGraphics, startAngle, angleSize, innerRadius, currentOuterRadius, color); + } + } + + + /** + * 获取扇形颜色 + */ + private float[] getSectorColor(int index, int selectedIndex, T entry) { + if (index == selectedIndex) { + return colorScheme.selectedColor; // 选中状态 + } else if (index == hoveredIndex) { + return colorScheme.hoveredColor; // 悬停状态 + } else { + return colorScheme.normalColor; // 普通状态 + } + } + + /** + * 绘制单个扇形 + */ + private void drawSector(GuiGraphics guiGraphics, float startAngle, float angleSize, + float innerRadius, float outerRadius, float[] color) { + BufferBuilder buffer = Tesselator.getInstance().getBuilder(); + buffer.begin(VertexFormat.Mode.TRIANGLE_STRIP, DefaultVertexFormat.POSITION_COLOR); + + Matrix4f matrix = guiGraphics.pose().last().pose(); + float segments = Math.max(8, this.segments * (angleSize / 360f)); + + for (int i = 0; i <= segments; i++) { + float progress = i / segments; + float angle = startAngle + progress * angleSize; + float rad = angle * Mth.DEG_TO_RAD; + + float cos = Mth.cos(rad); + float sin = Mth.sin(rad); + + // 外圈顶点 + buffer.vertex(matrix, outerRadius * cos, outerRadius * sin, 0) + .color(color[0], color[1], color[2], color[3]).endVertex(); + // 内圈顶点 + buffer.vertex(matrix, innerRadius * cos, innerRadius * sin, 0) + .color(color[0], color[1], color[2], color[3] * 0.6f).endVertex(); + } + + BufferUploader.drawWithShader(buffer.end()); + } + + /** + * 渲染图标和文本 + */ + private void renderIconsAndText(GuiGraphics guiGraphics, List entries, + Function titleProvider, + Function iconProvider) { + int count = entries.size(); + var font = Minecraft.getInstance().font; + + for (int i = 0; i < count; i++) { + T entry = entries.get(i); + float angle = (-90f + 360f * (i + 0.5f) / count) * Mth.DEG_TO_RAD; + + // 计算位置 + float x = Mth.cos(angle) * middleRadius; + float y = Mth.sin(angle) * middleRadius; + + // 渲染图标 + ItemStack icon = iconProvider.apply(entry); + if (!icon.isEmpty()) { + guiGraphics.renderItem(icon, (int)(x - 8), (int)(y - 8)); + } + + // 渲染文本 + Component title = titleProvider.apply(entry); + guiGraphics.pose().pushPose(); + guiGraphics.pose().translate(x, y + 12, 0); + guiGraphics.pose().scale(0.7f, 0.7f, 0.7f); + guiGraphics.drawString(font, title, -font.width(title) / 2, 0, 0xFFFFFF, true); + guiGraphics.pose().popPose(); + } + } + + /** + * 更新悬停动画 + */ + private void updateHoverAnimations(int entryCount) { + if (!enableHoverAnimation) return; + + long currentTime = System.currentTimeMillis(); + float deltaTime = Math.min((currentTime - lastAnimationTime) / 1000f, 0.1f); + lastAnimationTime = currentTime; + + // 确保数组大小正确 + if (hoverAnimations.length != entryCount) { + // 这里需要重新初始化数组,实际使用时应该处理数组大小变化 + } + + // 更新动画值 + for (int i = 0; i < hoverAnimations.length && i < entryCount; i++) { + if (i == hoveredIndex) { + hoverAnimations[i] = Mth.clamp(hoverAnimations[i] + deltaTime * 2f, 0f, 1f); + } else { + hoverAnimations[i] = Mth.clamp(hoverAnimations[i] - deltaTime * 3f, 0f, 1f); + } + } + } + + /** + * 获取鼠标下的条目索引 + * + * @param entries the entries + * @param mouseX the mouse x + * @param mouseY the mouse y + * @return the hovered entry + */ + public int getHoveredEntry(List entries, double mouseX, double mouseY) { + float centerX = Minecraft.getInstance().getWindow().getGuiScaledWidth() / 2f; + float centerY = Minecraft.getInstance().getWindow().getGuiScaledHeight() / 2f; + + double relX = mouseX - centerX; + double relY = mouseY - centerY; + double distance = Math.sqrt(relX * relX + relY * relY); + + // 检查是否在有效范围内 + if (distance < innerRadius || distance > outerRadius) { + hoveredIndex = -1; + return -1; + } + + // 计算角度 + double angle = Math.atan2(relY, relX) * Mth.RAD_TO_DEG; + angle = (angle + 450) % 360; // 标准化到 0-360 + + int count = entries.size(); + int index = (int) (angle / (360f / count)) % count; + + hoveredIndex = index; + return index; + } + + /** + * 清除状态 + */ + public void clearState() { + hoveredIndex = -1; + // 重置动画数组 + Arrays.fill(hoverAnimations, 0f); + } +} diff --git a/src/main/java/top/r3944realms/lib39/client/shader/Lib39Shaders.java b/src/main/java/top/r3944realms/lib39/client/shader/Lib39Shaders.java new file mode 100644 index 0000000..03b7f9b --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/client/shader/Lib39Shaders.java @@ -0,0 +1,73 @@ +package top.r3944realms.lib39.client.shader; + +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.ShaderInstance; +import net.minecraftforge.client.event.RegisterShadersEvent; +import top.r3944realms.lib39.Lib39; + +/** + * The type Lib 39 shaders. + */ +public class Lib39Shaders { + /** + * The Minecraft. + */ + static final Minecraft MINECRAFT = Minecraft.getInstance(); + + /** + * Gets ring shader. + * + * @return the ring shader + */ + public static ShaderInstance getRingShader() { + return ringShader; + } + + /** + * The Ring shader. + */ + static ShaderInstance ringShader; + + /** + * Gets selection shader. + * + * @return the selection shader + */ + public static ShaderInstance getSelectionShader() { + return selectionShader; + } + + /** + * The Selection shader. + */ + static ShaderInstance selectionShader; + + /** + * Register. + * + * @param event the event + */ + public static void register(RegisterShadersEvent event) { + try { + event.registerShader( + new ShaderInstance( + event.getResourceProvider(), + Lib39.rl("ring"), + DefaultVertexFormat.POSITION_COLOR + ), + it -> ringShader = it + ); + event.registerShader( + new ShaderInstance( + event.getResourceProvider(), + Lib39.rl("selection"), + DefaultVertexFormat.POSITION_COLOR + ), + it -> selectionShader = it + ); + } catch (Exception e) { + Lib39.LOGGER.error("Failed to register shader", e); + } + } +} diff --git a/src/main/java/top/r3944realms/lib39/core/event/ClientEventHandler.java b/src/main/java/top/r3944realms/lib39/core/event/ClientEventHandler.java index f148493..bcb43b5 100644 --- a/src/main/java/top/r3944realms/lib39/core/event/ClientEventHandler.java +++ b/src/main/java/top/r3944realms/lib39/core/event/ClientEventHandler.java @@ -1,5 +1,11 @@ package top.r3944realms.lib39.core.event; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.RegisterShadersEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.client.shader.Lib39Shaders; + /** * The type Client handler. */ @@ -7,7 +13,18 @@ public class ClientEventHandler { /** * The type Mod. */ - public static class Mod extends ClientEventHandler {} + @net.minecraftforge.fml.common.Mod.EventBusSubscriber(value = Dist.CLIENT, bus = net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus.MOD, modid = Lib39.MOD_ID) + public static class Mod extends ClientEventHandler { + /** + * On register shaders. + * + * @param event the event + */ + @SubscribeEvent + public static void onRegisterShaders(RegisterShadersEvent event) { + Lib39Shaders.register(event); + } + } /** * The type Game. diff --git a/src/main/java/top/r3944realms/lib39/core/event/CommonEventHandler.java b/src/main/java/top/r3944realms/lib39/core/event/CommonEventHandler.java index a4951fa..f383129 100644 --- a/src/main/java/top/r3944realms/lib39/core/event/CommonEventHandler.java +++ b/src/main/java/top/r3944realms/lib39/core/event/CommonEventHandler.java @@ -186,6 +186,7 @@ public class CommonEventHandler { IEventBus gameBus = MinecraftForge.EVENT_BUS; compatManager = new CompatManager(modBus, gameBus); MinecraftForge.EVENT_BUS.post(new RegisterCompatEvent(compatManager)); + compatManager.initializeAll(); }); } diff --git a/src/main/java/top/r3944realms/lib39/datagen/provider/SimpleLootTableProvider.java b/src/main/java/top/r3944realms/lib39/datagen/provider/SimpleLootTableProvider.java new file mode 100644 index 0000000..4bc1dcf --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/datagen/provider/SimpleLootTableProvider.java @@ -0,0 +1,42 @@ +package top.r3944realms.lib39.datagen.provider; + +import net.minecraft.data.PackOutput; +import net.minecraft.data.loot.LootTableProvider; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.level.storage.loot.ValidationContext; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.Set; + +/** + * The type Simple loot table provider. + */ +public class SimpleLootTableProvider extends LootTableProvider { + /** + * Instantiates a new Simple loot table provider. + * + * @param output the output + * @param subProvidersWrapper the sub providers wrapper + */ + public SimpleLootTableProvider(PackOutput output, @NotNull SubProvidersWrapper subProvidersWrapper) { + super(output, Set.of(), subProvidersWrapper.entries); + } + + /** + * Instantiates a new Simple loot table provider. + * + * @param output the output + * @param requiredTables the required tables + * @param subProvidersWrapper the sub providers wrapper + */ + public SimpleLootTableProvider(PackOutput output, Set requiredTables, @NotNull SubProvidersWrapper subProvidersWrapper) { + super(output, requiredTables, subProvidersWrapper.entries); + } + + @Override + protected void validate(@NotNull Map map, @NotNull ValidationContext validationcontext) { + map.forEach((id, table) -> table.validate(validationcontext)); + } +} diff --git a/src/main/java/top/r3944realms/lib39/datagen/provider/SubProvidersWrapper.java b/src/main/java/top/r3944realms/lib39/datagen/provider/SubProvidersWrapper.java new file mode 100644 index 0000000..d32fe75 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/datagen/provider/SubProvidersWrapper.java @@ -0,0 +1,46 @@ +package top.r3944realms.lib39.datagen.provider; + +import net.minecraft.data.loot.LootTableProvider; +import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; +import top.r3944realms.lib39.datagen.provider.subprovider.BlockLootTables; + +import java.util.ArrayList; +import java.util.List; + +/** + * The type Sub providers wrapper. + */ +public class SubProvidersWrapper { + /** + * The Entries. + */ + public List entries = new ArrayList<>(); + + /** + * Instantiates a new Sub providers wrapper. + */ + public SubProvidersWrapper() {} + + /** + * Add entry sub providers wrapper. + * + * @param entry the entry + * @return the sub providers wrapper + */ + public SubProvidersWrapper addEntry(LootTableProvider.SubProviderEntry entry) { + entries.add(entry); + return this; + } + + /** + * Add block entry sub providers wrapper. + * + * @param blockLootTables the block loot tables + * @return the sub providers wrapper + */ + public SubProvidersWrapper addBlockEntry(BlockLootTables blockLootTables) { + entries.add(new LootTableProvider.SubProviderEntry(() -> blockLootTables, LootContextParamSets.BLOCK)); + return this; + } + +} diff --git a/src/main/java/top/r3944realms/lib39/datagen/provider/subprovider/BlockLootTables.java b/src/main/java/top/r3944realms/lib39/datagen/provider/subprovider/BlockLootTables.java new file mode 100644 index 0000000..0329a67 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/datagen/provider/subprovider/BlockLootTables.java @@ -0,0 +1,373 @@ +package top.r3944realms.lib39.datagen.provider.subprovider; + +import net.minecraft.advancements.critereon.ItemPredicate; +import net.minecraft.data.loot.BlockLootSubProvider; +import net.minecraft.world.flag.FeatureFlags; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.FlowerPotBlock; +import net.minecraft.world.level.block.state.properties.Property; +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.level.storage.loot.entries.LootItem; +import net.minecraft.world.level.storage.loot.functions.ApplyBonusCount; +import net.minecraft.world.level.storage.loot.functions.SetItemCountFunction; +import net.minecraft.world.level.storage.loot.predicates.LootItemBlockStatePropertyCondition; +import net.minecraft.world.level.storage.loot.predicates.MatchTool; +import net.minecraft.world.level.storage.loot.providers.number.ConstantValue; +import net.minecraft.world.level.storage.loot.providers.number.UniformGenerator; +import net.minecraftforge.registries.RegistryObject; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.function.Function; + +/** + * The type Block loot tables. + */ +@SuppressWarnings("unused") +public class BlockLootTables extends BlockLootSubProvider { + + private final List blockEntries = new ArrayList<>(); + + /** + * Instantiates a new Block loot tables. + */ + public BlockLootTables() { + super(Set.of(), FeatureFlags.REGISTRY.allFlags()); + } + + // ==================== 流畅 API 构建方法 ==================== + + /** + * 添加自掉落的方块 + * + * @param block the block + */ + public void dropSelf(RegistryObject block) { + addEntry(block, this::createSingleItemTable); + } + + /** + * 批量添加自掉落的方块 + * + * @param blocks the blocks + * @return the block loot tables + */ + @Contract("_ -> this") + @SafeVarargs + public final BlockLootTables dropSelf(RegistryObject @NotNull ... blocks) { + for (RegistryObject block : blocks) { + dropSelf(block); + } + return this; + } + + /** + * 添加需要丝绸之触才掉落的方块 + * + * @param block the block + * @return the block loot tables + */ + public BlockLootTables dropWhenSilkTouch(RegistryObject block) { + return addEntry(block, BlockLootSubProvider::createSilkTouchOnlyTable); + } + + /** + * 添加掉落其他物品的方块 + * + * @param block the block + * @param item the item + * @return the block loot tables + */ + public BlockLootTables dropOther(RegistryObject block, RegistryObject item) { + return addEntry(block, pBlock -> this.createSingleItemTable(item.get())); + } + + /** + * 添加只能被剪子剪下的方块 + * + * @param block the block + * @return the block loot tables + */ + public BlockLootTables dropWhenShears(RegistryObject block) { + return addEntry(block, BlockLootSubProvider::createShearsOnlyDrop); + } + + /** + * 添加矿物的掉落表(支持时运附魔) + * + * @param block the block + * @param oreItem the ore item + * @return the block loot tables + */ + public BlockLootTables dropOre(RegistryObject block, RegistryObject oreItem) { + return addEntry(block, b -> this.createOreDrop(b, oreItem.get())); + } + + /** + * 添加红石矿石掉落表 + * + * @param block the block + * @return the block loot tables + */ + public BlockLootTables dropRedstoneOre(RegistryObject block) { + return addEntry(block, this::createRedstoneOreDrops); + } + + /** + * 添加青金石矿石掉落表 + * + * @param block the block + * @return the block loot tables + */ + public BlockLootTables dropLapisOre(RegistryObject block) { + return addEntry(block, this::createLapisOreDrops); + } + + /** + * 添加铜矿石掉落表 + * + * @param block the block + * @return the block loot tables + */ + public BlockLootTables dropCopperOre(RegistryObject block) { + return addEntry(block, this::createCopperOreDrops); + } + + /** + * 添加地毯类方块的掉落(一次掉落2个) + * + * @param block the block + * @return the block loot tables + */ + public BlockLootTables dropCarpet(RegistryObject block) { + return addEntry(block, b -> LootTable.lootTable() + .withPool(LootPool.lootPool() + .setRolls(ConstantValue.exactly(1)) + .add(LootItem.lootTableItem(b) + .apply(SetItemCountFunction.setCount(ConstantValue.exactly(2.0F)))))); + } + + /** + * 添加台阶方块的掉落 + * + * @param block the block + * @return the block loot tables + */ + public BlockLootTables dropSlab(RegistryObject block) { + return addEntry(block, this::createSlabItemTable); + } + + /** + * 添加门方块的掉落(只掉落下半部分) + * + * @param block the block + * @return the block loot tables + */ + public BlockLootTables dropDoor(RegistryObject block) { + return addEntry(block, this::createDoorTable); + } + + /** + * 添加花盆的掉落 + * + * @param block the block + * @return the block loot tables + */ + public BlockLootTables dropFlowerPot(RegistryObject block) { + return addEntry(block, (pBlock) -> this.createPotFlowerItemTable(((FlowerPotBlock)pBlock).getContent())); + } + + /** + * 添加树叶的掉落 + * + * @param leavesBlock the leaves block + * @param saplingBlock the sapling block + * @param chances the chances + * @return the block loot tables + */ + public BlockLootTables dropLeaves(RegistryObject leavesBlock, + RegistryObject saplingBlock, + float... chances) { + return addEntry(leavesBlock, b -> this.createLeavesDrops(b, saplingBlock.get(), chances)); + } + + /** + * 添加橡树叶的掉落(包含苹果) + * + * @param leavesBlock the leaves block + * @param saplingBlock the sapling block + * @param chances the chances + * @return the block loot tables + */ + public BlockLootTables dropOakLeaves(RegistryObject leavesBlock, + RegistryObject saplingBlock, + float... chances) { + return addEntry(leavesBlock, b -> this.createOakLeavesDrops(b, saplingBlock.get(), chances)); + } + + /** + * 添加农作物的掉落 + * + * @param cropBlock the crop block + * @param cropItem the crop item + * @param seedsItem the seeds item + * @param ageProperty the age property + * @param maxAge the max age + * @return the block loot tables + */ + public BlockLootTables dropCrop(RegistryObject cropBlock, + RegistryObject cropItem, + RegistryObject seedsItem, + Property ageProperty, + int maxAge) { + return addEntry(cropBlock, b -> this.createCropDrops( + b, + cropItem.get(), + seedsItem.get(), + LootItemBlockStatePropertyCondition.hasBlockStateProperties(b) + .setProperties(net.minecraft.advancements.critereon.StatePropertiesPredicate.Builder.properties() + .hasProperty(ageProperty, maxAge)) + )); + } + + /** + * 自定义掉落表 + * + * @param block the block + * @param factory the factory + * @return the block loot tables + */ + public BlockLootTables custom(RegistryObject block, Function factory) { + return addEntry(block, factory); + } + + /** + * 没有掉落 + * + * @param block the block + * @return the block loot tables + */ + public BlockLootTables noDrop(RegistryObject block) { + return addEntry(block, b -> noDrop()); + } + + // ==================== 批量操作方法 ==================== + + /** + * 批量操作一系列方块 + * + * @param blocks the blocks + * @param operation the operation + * @return the block loot tables + */ + public BlockLootTables batch(@NotNull Iterable> blocks, + Function, BlockLootTables> operation) { + for (RegistryObject block : blocks) { + operation.apply(block); + } + return this; + } + + /** + * 对一组方块应用相同的操作 + * + * @param operation the operation + * @param blocks the blocks + * @return the block loot tables + */ + @Contract("_, _ -> this") + @SafeVarargs + public final BlockLootTables applyToAll(Function, BlockLootTables> operation, + RegistryObject @NotNull ... blocks) { + for (RegistryObject block : blocks) { + operation.apply(block); + } + return this; + } + + // ==================== 构建方法 ==================== + + /** + * 构建并返回自身(用于流畅API链式调用) + * + * @return the block loot tables + */ + public BlockLootTables build() { + return this; + } + + @Override + protected void generate() { + for (BlockEntry entry : blockEntries) { + this.add(entry.block.get(), entry.factory); + } + } + + // ==================== 内部类和方法 ==================== + + private BlockLootTables addEntry(RegistryObject block, Function factory) { + blockEntries.add(new BlockEntry(block, factory)); + return this; + } + + private record BlockEntry(RegistryObject block, Function factory) {} + + // ==================== 静态工厂方法 ==================== + + /** + * 创建新的战利品表生成器 + * + * @return the block loot tables + */ + @Contract(" -> new") + public static @NotNull BlockLootTables create() { + return new BlockLootTables(); + } + + /** + * 快速创建基本矿物的掉落表 + * + * @param oreBlock the ore block + * @param oreItem the ore item + * @return the loot table .@ not null builder + */ + public static LootTable.@NotNull Builder simpleOreDrop(Block oreBlock, Item oreItem) { + return LootTable.lootTable() + .withPool(LootPool.lootPool() + .setRolls(ConstantValue.exactly(1)) + .add(LootItem.lootTableItem(oreItem) + .when(MatchTool.toolMatches(ItemPredicate.Builder.item() + .hasEnchantment(new net.minecraft.advancements.critereon.EnchantmentPredicate( + Enchantments.SILK_TOUCH, + net.minecraft.advancements.critereon.MinMaxBounds.Ints.atLeast(1) + ))).invert()) + .apply(SetItemCountFunction.setCount(UniformGenerator.between(1, 1))) + .apply(ApplyBonusCount.addOreBonusCount(Enchantments.BLOCK_FORTUNE)))); + } + + /** + * 快速创建石质方块的掉落表(需要镐子) + * + * @param stoneBlock the stone block + * @param dropItem the drop item + * @return the loot table .@ not null builder + */ + public static LootTable.@NotNull Builder stoneDrop(Block stoneBlock, Item dropItem) { + return LootTable.lootTable() + .withPool(LootPool.lootPool() + .setRolls(ConstantValue.exactly(1)) + .add(LootItem.lootTableItem(dropItem) + .when(MatchTool.toolMatches(ItemPredicate.Builder.item() + .hasEnchantment(new net.minecraft.advancements.critereon.EnchantmentPredicate( + Enchantments.SILK_TOUCH, + net.minecraft.advancements.critereon.MinMaxBounds.Ints.atLeast(1) + )))))); + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/lib39/example/client/screen/ForgeScreen.java b/src/main/java/top/r3944realms/lib39/example/client/screen/ForgeScreen.java new file mode 100644 index 0000000..3d4c634 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/example/client/screen/ForgeScreen.java @@ -0,0 +1,127 @@ +package top.r3944realms.lib39.example.client.screen; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Renderable; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.network.chat.Component; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector2f; +import top.r3944realms.lib39.client.gui.component.WheelWidget; +import top.r3944realms.lib39.util.lang.FourConsumer; +import top.r3944realms.lib39.util.lang.Pair; + +import java.util.List; +import java.util.Objects; + +import static top.r3944realms.lib39.client.gui.component.WheelWidget.IGNORE_CURSOR_MOVE_LENGTH; + +/** + * The type Forge screen. + */ +public class ForgeScreen extends Screen { + private final LocalPlayer player = Objects.requireNonNull(Minecraft.getInstance().player); + private final InteractionHand hand; + private final int mode; + + /** + * The Wheel. + */ + public WheelWidget wheel; + + /** + * Instantiates a new Forge screen. + * + * @param hand the hand + * @param mode the mode + */ + public ForgeScreen(InteractionHand hand, int mode) { + super(Component.literal("Test")); + this.hand = hand; + this.mode = mode; + } + + @Override + protected void init() { + int leftPos = (this.width - 75) / 2; + int topPos = (this.height - 75) / 2; + ItemStack holding = player.getItemInHand(this.hand); + WheelWidget wheel = new WheelWidget( + leftPos, topPos, 75, 75, + 12.5f, 32.5f, 0.75f, + List.of( + Pair.of( + Component.literal("auto"), + renderItem(holding)), + Pair.of( + Component.literal("axe"), + renderItem(holding)), + Pair.of( + Component.literal("shovel"), + renderItem(holding)), + Pair.of( + Component.literal("hoe"), + renderItem(holding)), + Pair.of( + Component.literal("pickaxe"), + renderItem(holding)) + ) + ).setCurrentIndex(this.wheel != null ? this.wheel.getCurrentSectionIndex() : this.mode); + this.clearWidgets(); + this.wheel = this.addRenderableWidget(wheel); + } + + @Contract(pure = true) + private static @NotNull FourConsumer renderItem(ItemStack holding) { + return (graphics, pose, width, height) -> { + ItemStack stack = holding.copy(); + graphics.renderItem(stack, 2, 2, 9910597); + }; + } + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double dragX, double dragY) { + if (wheel != null && wheel.isClosingAnimationStarted()) return true; + float screenCenterX = this.width / 2f; + float screenCenterY = this.height / 2f; + Vector2f cursorVec2 = new Vector2f( + (float) mouseX - screenCenterX, + (float) mouseY - screenCenterY + ); + if (cursorVec2.length() < IGNORE_CURSOR_MOVE_LENGTH) { + return true; + } + return super.mouseDragged(mouseX, mouseY, button, dragX, dragY); + } + + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { + if (wheel != null ) { + wheel.onClosing(); + } + return super.mouseReleased(mouseX, mouseY, button); + } + + @Override + public void removed() { + super.removed(); + } + + + @Override + public void render(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + for (Renderable renderable : this.renderables) { + renderable.render(guiGraphics, mouseX, mouseY, partialTick); + } + } + + @Override + public boolean isPauseScreen() { + return false; + } + +} diff --git a/src/main/java/top/r3944realms/lib39/example/content/item/ForgeItem.java b/src/main/java/top/r3944realms/lib39/example/content/item/ForgeItem.java new file mode 100644 index 0000000..8c47461 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/example/content/item/ForgeItem.java @@ -0,0 +1,33 @@ +package top.r3944realms.lib39.example.content.item; + +import net.minecraft.client.Minecraft; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.example.client.screen.ForgeScreen; + +/** + * The type Forge item. + */ +public class ForgeItem extends Item { + /** + * Instantiates a new Forge item. + * + * @param properties the properties + */ + public ForgeItem(Properties properties) { + super(properties); + } + + @Override + public @NotNull InteractionResultHolder use(@NotNull Level level, @NotNull Player player, @NotNull InteractionHand usedHand) { + if (level.isClientSide() && usedHand == InteractionHand.MAIN_HAND) { + Minecraft.getInstance().setScreen(new ForgeScreen(usedHand, 0)); + } + return super.use(level, player, usedHand); + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/lib39/example/core/register/ExLib39Items.java b/src/main/java/top/r3944realms/lib39/example/core/register/ExLib39Items.java index de21c86..78e9561 100644 --- a/src/main/java/top/r3944realms/lib39/example/core/register/ExLib39Items.java +++ b/src/main/java/top/r3944realms/lib39/example/core/register/ExLib39Items.java @@ -7,6 +7,7 @@ import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.RegistryObject; import top.r3944realms.lib39.Lib39; import top.r3944realms.lib39.example.content.item.FabricItem; +import top.r3944realms.lib39.example.content.item.ForgeItem; import top.r3944realms.lib39.example.content.item.NeoForgeItem; /** @@ -38,6 +39,16 @@ public class ExLib39Items { .stacksTo(1) .fireResistant() )); + /** + * The constant FORGE. + */ + public static final RegistryObject FORGE = + ITEMS.register("forge", + () -> new ForgeItem( + new Item.Properties() + .stacksTo(1) + .fireResistant() + )); /** * Register. diff --git a/src/main/java/top/r3944realms/lib39/example/datagen/data/ExLib39LangKeys.java b/src/main/java/top/r3944realms/lib39/example/datagen/data/ExLib39LangKeys.java index 48c65a0..392b35a 100644 --- a/src/main/java/top/r3944realms/lib39/example/datagen/data/ExLib39LangKeys.java +++ b/src/main/java/top/r3944realms/lib39/example/datagen/data/ExLib39LangKeys.java @@ -40,6 +40,10 @@ public enum ExLib39LangKeys implements ILangKeyValueCollection { ExLib39Items.NEOFORGE, ModPartEnum.ITEM, "NeoForge", "小狐狸", "狐狸", "狸", true )); + addLang(LangKeyValue.ofSupplier( + ExLib39Items.FORGE, ModPartEnum.ITEM, + "Forge", "铁砧", "铁砧", "砧", true + )); } diff --git a/src/main/java/top/r3944realms/lib39/util/MathUtil.java b/src/main/java/top/r3944realms/lib39/util/MathUtil.java new file mode 100644 index 0000000..35a5580 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/util/MathUtil.java @@ -0,0 +1,195 @@ +package top.r3944realms.lib39.util; + +import it.unimi.dsi.fastutil.ints.Int2DoubleMap; +import it.unimi.dsi.fastutil.ints.Int2DoubleOpenHashMap; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Vec3i; +import org.joml.Vector2f; + +import static java.lang.Math.*; + +/** + * 直接拿铁砧工艺的 + */ +public class MathUtil { + /** + * Calc a vector2 that equals to a vector2 rotated an angle + * + * @param v origin vector, wont be changed + * @param deg angle rotated, in degrees + * @return rotated vector2 + */ + public static Vector2f rotationDegrees(Vector2f v, float deg) { + return rotate(v, (float) toRadians(deg)); + } + + /** + * Calc a vector2 that equals to a vector2 rotated an angle + * + * @param v origin vector, wont be changed + * @param d angle rotated, in radians + * @return rotated vector2 + */ + public static Vector2f rotate(Vector2f v, float d) { + return new Vector2f( + (float) (v.x * cos(d) - v.y * sin(d)), + (float) (v.x * sin(d) + v.y * cos(d)) + ); + } + + /** + * Copy vector 2 f. + * + * @param v the v + * @return the vector 2 f + */ + public static Vector2f copy(Vector2f v) { + return new Vector2f(v.x, v.y); + } + + /** + * Angle float. + * + * @param from the from + * @param to the to + * @return Angle in radians + */ + public static float angle(Vector2f from, Vector2f to) { + return (float) ((atan2(to.y, to.x) - atan2(from.y, from.x)) % (Math.PI * 2)); + } + + /** + * Angle degrees float. + * + * @param from the from + * @param to the to + * @return Angle in degrees + */ + public static float angleDegrees(Vector2f from, Vector2f to) { + return (float) toDegrees(angle(from, to)); + } + + /** + * Safe divide float. + * + * @param a the a + * @param b the b + * @return the float + */ + public static float safeDivide(float a, float b) { + if (a == b) return 1; + return a / b; + } + + /** + * Is in range boolean. + * + * @param value the value + * @param min the min + * @param max the max + * @return the boolean + */ + public static boolean isInRange(double value, double min, double max) { + if (min > max) { + double min1 = min; + min = max; + max = min1; + } + + return value > min && value < max; + } + + /** + * Is in range boolean. + * + * @param valueX the value x + * @param valueY the value y + * @param minX the min x + * @param minY the min y + * @param maxX the max x + * @param maxY the max y + * @return the boolean + */ + public static boolean isInRange(double valueX, double valueY, double minX, double minY, double maxX, double maxY) { + if (minX > maxX) { + double minX1 = minX; + minX = maxX; + maxX = minX1; + } + if (minY > maxY) { + double minY1 = minY; + minY = maxY; + maxY = minY1; + } + + return valueX > minX && valueX < maxX && valueY > minY && valueY < maxY; + } + + /** + * Dist vec 3 i. + * + * @param a the a + * @param b the b + * @return the vec 3 i + */ + public static Vec3i dist(BlockPos a, BlockPos b) { + return new Vec3i(a.getX() - b.getX(), a.getY() - b.getY(), a.getZ() - b.getZ()); + } + + /** + * Gets direction. + * + * @param from the from + * @param to the to + * @return the direction + */ + public static Direction getDirection(BlockPos from, BlockPos to) { + return Direction.fromDelta(from.getX() - to.getX(), from.getY() - to.getY(), from.getZ() - to.getZ()); + } + + private static final Int2DoubleMap FACTORIAL_CACHE = new Int2DoubleOpenHashMap(); + + /** + * Factorial double. + * + * @param value the value + * @return the double + */ + public static double factorial(int value) { + if (value < 1) return 1; + if (FACTORIAL_CACHE.containsKey(value)) return FACTORIAL_CACHE.get(value); + double result = 1; + for (int i = 2; i <= value; i++) { + result *= i; + } + FACTORIAL_CACHE.put(value, result); + return result; + } + + /** + * Clamp with proportion float. + * + * @param value the value + * @param min the min + * @param max the max + * @return the float + */ + public static float clampWithProportion(float value, float min, float max) { + float length = Math.abs(max - min); + if (length == 0) throw new IllegalArgumentException("The min value " + min + " cannot be equal to the max value" + max + "!"); + + if (value > max) { + while (value > max + length) { + value -= length; + } + return max - (max - value); + } else if (value < min) { + while (value < min + length) { + value += length; + } + return min + (value - min); + } + return value; + } +} diff --git a/src/main/java/top/r3944realms/lib39/util/command/CommandHelpHelper.java b/src/main/java/top/r3944realms/lib39/util/command/CommandHelpHelper.java new file mode 100644 index 0000000..dc80535 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/util/command/CommandHelpHelper.java @@ -0,0 +1,7 @@ +package top.r3944realms.lib39.util.command; + +/** + * The type Command help helper. + */ +public class CommandHelpHelper { +} diff --git a/src/main/java/top/r3944realms/lib39/util/lang/FourConsumer.java b/src/main/java/top/r3944realms/lib39/util/lang/FourConsumer.java new file mode 100644 index 0000000..fbe2512 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/util/lang/FourConsumer.java @@ -0,0 +1,36 @@ +package top.r3944realms.lib39.util.lang; + +/** + * The interface Four consumer. + * + * @param the type parameter + * @param the type parameter + * @param the type parameter + * @param the type parameter + */ +public interface FourConsumer { + /** + * Accept. + * + * @param x the x + * @param y the y + * @param z the z + * @param w the w + */ + void accept(X x, Y y, Z z, W w); + + /** + * Noop. + * + * @param the type parameter + * @param the type parameter + * @param the type parameter + * @param the type parameter + * @param x the x + * @param y the y + * @param z the z + * @param w the w + */ + static void noop(X x, Y y, Z z, W w) { + } +} diff --git a/src/main/java/top/r3944realms/lib39/util/nbt/NBTWriter.java b/src/main/java/top/r3944realms/lib39/util/nbt/NBTWriter.java index 7235330..d70cdc8 100644 --- a/src/main/java/top/r3944realms/lib39/util/nbt/NBTWriter.java +++ b/src/main/java/top/r3944realms/lib39/util/nbt/NBTWriter.java @@ -585,6 +585,21 @@ public class NBTWriter { return this; } + /** + * Uuid value if nbt writer. + * + * @param key the key + * @param value the value + * @param condition the condition + * @return the nbt writer + */ + public NBTWriter uuidValueIf(String key, UUID value, boolean condition) { + if (condition && value != null) { + root.putUUID(key, value); + } + return this; + } + /** * Boolean value if nbt writer. * diff --git a/src/main/java/top/r3944realms/lib39/util/resolve/EntityListResolve.java b/src/main/java/top/r3944realms/lib39/util/resolve/EntityListResolve.java new file mode 100644 index 0000000..6d786ed --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/util/resolve/EntityListResolve.java @@ -0,0 +1,161 @@ +package top.r3944realms.lib39.util.resolve; + +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.entity.EntityType; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.Lib39; + +import java.util.ArrayList; +import java.util.List; + +/** + * The type Entity list resolve. + */ +public abstract class EntityListResolve { + /** + * The Result. + */ + protected EntityListResolve.EntityResolveResult result; + + /** + * The type Entity resolve result. + */ + public static class EntityResolveResult { + /** + * The Entity list. + */ + protected final List entityList = new ArrayList<>(); + /** + * The Tag list. + */ + protected final List tagList = new ArrayList<>(); + /** + * The Mod list. + */ + protected final List modList = new ArrayList<>(); + + /** + * The enum Type. + */ + public enum Type { + /** + * Entity type. + */ + ENTITY, + /** + * Tag type. + */ + TAG, + /** + * Mod type. + */ + MOD + } + + /** + * Gets map. + * + * @param type the type + * @return the map + */ + public List getMap(@NotNull Type type) { + return switch (type) { + case ENTITY -> entityList; + case TAG -> tagList; + case MOD -> modList; + }; + } + + /** + * Update. + * + * @param entity the entity + * @param tag the tag + * @param mod the mod + */ + public void update(List entity, List tag, List mod) { + entityList.clear(); + entityList.addAll(entity); + tagList.clear(); + tagList.addAll(tag); + modList.clear(); + modList.addAll(mod); + } + } + + /** + * Resolve entity resolve result. + * + * @param configs the configs + * @return the entity resolve result + */ + public EntityResolveResult resolve(@NotNull List configs) { + List entityList = new ArrayList<>(); + List tagList = new ArrayList<>(); + List modList = new ArrayList<>(); + for (String config : configs) { + if (!isMatch(config)) continue; + + try { + String[] entities = resolveEntities(config); + for (String e : entities) { + String trimmed = e.trim(); + if (trimmed.equals("*")) modList.add("*"); + else if (trimmed.startsWith("#")) { + String body = trimmed.substring(1).trim(); + if (body.contains(":")) tagList.add(body); + else modList.add(body); + } else entityList.add(trimmed); + } + result.update(entityList, tagList, modList); + } catch (NumberFormatException ex) { + Lib39.LOGGER.error("Invalid offset config: {}", config); + } + } + return result; + } + + /** + * Is match boolean. + * + * @param input the input + * @return the boolean + */ + protected abstract boolean isMatch(String input); + + /** + * Resolve entities string [ ]. + * + * @param input the input + * @return the string [ ] + */ + protected abstract String[] resolveEntities(String input); + + /** + * Is entity in list boolean. + * + * @param type the type + * @return the boolean + */ + @SuppressWarnings("deprecation") + public boolean isEntityInList(EntityType type) { + String entityId = type.builtInRegistryHolder().key().location().toString(); + String modId = entityId.split(":")[0]; + for (String rs : result.entityList) { + if (rs.equals(entityId)) return true; + } + for(String rs : result.tagList) { + String body = rs.substring(1); + ResourceLocation tagId = new ResourceLocation(body); + TagKey> tag = TagKey.create(Registries.ENTITY_TYPE, tagId); + if (type.builtInRegistryHolder().is(tag)) return true; + } + for(String rs : result.modList) { + String body = rs.substring(1); + if (!body.contains(":") && body.equals(modId)) return true; + } + return false; + } +} diff --git a/src/main/java/top/r3944realms/lib39/util/resolve/EntityMapResolve.java b/src/main/java/top/r3944realms/lib39/util/resolve/EntityMapResolve.java new file mode 100644 index 0000000..059eb2a --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/util/resolve/EntityMapResolve.java @@ -0,0 +1,214 @@ +package top.r3944realms.lib39.util.resolve; + +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.entity.EntityType; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.Lib39; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The type Entity map resolve. + * + * @param the type parameter + */ +public abstract class EntityMapResolve { + /** + * The Result. + */ + protected EntityResolveResult result; + + /** + * The type Entity resolve result. + * + * @param the type parameter + */ + public static class EntityResolveResult { + /** + * The Entity map. + */ + protected final Map entityMap = new HashMap<>(); + /** + * The Tag map. + */ + protected final Map tagMap = new HashMap<>(); + /** + * The Mod map. + */ + protected final Map modMap = new HashMap<>(); + + /** + * The enum Type. + */ + public enum Type { + /** + * Entity type. + */ + ENTITY, + /** + * Tag type. + */ + TAG, + /** + * Mod type. + */ + MOD + } + + /** + * Gets map. + * + * @param type the type + * @return the map + */ + public Map getMap(@NotNull Type type) { + return switch (type) { + case ENTITY -> entityMap; + case TAG -> tagMap; + case MOD -> modMap; + }; + } + + /** + * Update. + * + * @param entity the entity + * @param tag the tag + * @param mod the mod + */ + public void update(Map entity, Map tag, Map mod) { + entityMap.clear(); + entityMap.putAll(entity); + tagMap.clear(); + tagMap.putAll(tag); + modMap.clear(); + modMap.putAll(mod); + } + } + + /** + * Resolve entity resolve result. + * + * @param configs the configs + * @return the entity resolve result + */ + public EntityResolveResult resolve(@NotNull List configs) { + Map entityMap = new HashMap<>(); + Map tagMap = new HashMap<>(); + Map modMap = new HashMap<>(); + + for (String config : configs) { + if (!isMatch(config)) continue; + + try { + T t = resolveT(config); + + String[] entities = resolveEntities(config); + for (String e : entities) { + String trimmed = e.trim(); + if (trimmed.equals("*")) modMap.put("*", t); + else if (trimmed.startsWith("#")) { + String body = trimmed.substring(1).trim(); + if (body.contains(":")) tagMap.put(body, t); + else modMap.put(body, t); + } else entityMap.put(trimmed, t); + } + result.update(entityMap, tagMap, modMap); + } catch (NumberFormatException ex) { + Lib39.LOGGER.error("Invalid offset config: {}", config); + } + } + return result; + } + + /** + * Is match boolean. + * + * @param input the input + * @return the boolean + */ + protected abstract boolean isMatch(String input); + + /** + * Resolve t t. + * + * @param input the input + * @return the t + */ + protected abstract T resolveT(String input); + + /** + * Resolve entities string [ ]. + * + * @param input the input + * @return the string [ ] + */ + protected abstract String[] resolveEntities(String input); + /** + * 查找实体对应的值,如果找到返回匹配结果,否则返回null + */ + @SuppressWarnings("deprecation") + private EntityMatchResult findEntityMatch(EntityType type) { + String entityId = type.builtInRegistryHolder().key().location().toString(); + String modId = entityId.split(":")[0]; + + // 检查实体ID匹配 + for (String rs : result.entityMap.keySet()) { + if (rs.equals(entityId)) { + return new EntityMatchResult<>(EntityResolveResult.Type.ENTITY, rs, result.entityMap.get(rs)); + } + } + + // 检查标签匹配 + for (String rs : result.tagMap.keySet()) { + String body = rs.startsWith("#") ? rs.substring(1) : rs; + ResourceLocation tagId = new ResourceLocation(body); + TagKey> tag = TagKey.create(Registries.ENTITY_TYPE, tagId); + if (type.builtInRegistryHolder().is(tag)) { + return new EntityMatchResult<>(EntityResolveResult.Type.TAG, rs, result.tagMap.get(rs)); + } + } + + // 检查模组匹配 + for (String rs : result.modMap.keySet()) { + String body = rs.startsWith("#") ? rs.substring(1) : rs; + if (!body.contains(":") && body.equals(modId)) { + return new EntityMatchResult<>(EntityResolveResult.Type.MOD, rs, result.modMap.get(rs)); + } + } + + return null; + } + + /** + * Is entity t in map boolean. + * + * @param type the type + * @return the boolean + */ + public boolean isEntityTInMap(EntityType type) { + return findEntityMatch(type) != null; + } + + /** + * Gets entity t in map. + * + * @param type the type + * @return the entity t in map + */ + public T getEntityTInMap(EntityType type) { + EntityMatchResult match = findEntityMatch(type); + return match != null ? match.value : null; + } + + /** + * 内部类,用于存储实体匹配结果 + */ + private record EntityMatchResult(EntityResolveResult.Type type, String key, T value) { + } + +} diff --git a/src/main/resources/assets/lib39/shaders/core/ring.fsh b/src/main/resources/assets/lib39/shaders/core/ring.fsh new file mode 100644 index 0000000..39aa703 --- /dev/null +++ b/src/main/resources/assets/lib39/shaders/core/ring.fsh @@ -0,0 +1,49 @@ +#version 150 + +in vec4 vertexColor; + +uniform vec4 ColorModulator; +uniform vec2 Center; +uniform float InnerRadius; +uniform float OuterRadius; +uniform float AntiAliasing; + +out vec4 fragColor; + +void main() { + float dist = distance(gl_FragCoord.xy, Center); + float alpha = 0.0; + + // 确保内外半径合理 + if (OuterRadius <= InnerRadius) { + vec4 color = vertexColor; + color.a = 0; + fragColor = color; + } + + // 计算环形 alpha + if (dist >= InnerRadius && dist <= OuterRadius) { + alpha = 1.0; + + // 内边缘抗锯齿 + if (dist < InnerRadius + AntiAliasing) { + float fade = (dist - InnerRadius) / AntiAliasing; + alpha *= fade; + } + + // 外边缘抗锯齿 + if (dist > OuterRadius - AntiAliasing) { + float fade = 1.0 - (dist - (OuterRadius - AntiAliasing)) / AntiAliasing; + alpha *= fade; + } + } + + vec4 color = vertexColor; + color.a *= alpha; + + if (alpha > 0.0) { + fragColor = color * ColorModulator; + } else { + discard; + } +} \ No newline at end of file diff --git a/src/main/resources/assets/lib39/shaders/core/ring.json b/src/main/resources/assets/lib39/shaders/core/ring.json new file mode 100644 index 0000000..756ea0c --- /dev/null +++ b/src/main/resources/assets/lib39/shaders/core/ring.json @@ -0,0 +1,44 @@ +{ + "vertex": "position_color", + "fragment": "lib39:ring", + "samplers": [ + ], + "uniforms": [ + { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, + { + "name": "Center", + "type": "float", + "count": 2, + "values": [ + 0.0, + 0.0 + ] + }, + { + "name": "InnerRadius", + "type": "float", + "count": 1, + "values": [ + 0.0 + ] + }, + { + "name": "OuterRadius", + "type": "float", + "count": 1, + "values": [ + 0.0 + ] + }, + { + "name": "AntiAliasing", + "type": "float", + "count": 1, + "values": [ + 1.25 + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/lib39/shaders/core/selection.fsh b/src/main/resources/assets/lib39/shaders/core/selection.fsh new file mode 100644 index 0000000..27e9904 --- /dev/null +++ b/src/main/resources/assets/lib39/shaders/core/selection.fsh @@ -0,0 +1,23 @@ +#version 150 + +in vec4 vertexColor; + +uniform vec4 ColorModulator; +uniform vec2 FramebufferSize; +uniform vec2 Center; +uniform float Radius; +uniform float AntiAliasingRadius; + +out vec4 fragColor; + +void main() { + vec2 fragPos = vec2(gl_FragCoord.x, FramebufferSize.y - gl_FragCoord.y); + float distance = distance(fragPos, Center); + vec4 color = vec4(0, 0, 0, 0); + if (distance <= Radius) { + color = vertexColor; + color.a = smoothstep(Radius, 0.0, distance) * vertexColor.a; + } + + fragColor = color * ColorModulator; +} \ No newline at end of file diff --git a/src/main/resources/assets/lib39/shaders/core/selection.json b/src/main/resources/assets/lib39/shaders/core/selection.json new file mode 100644 index 0000000..b9a2e52 --- /dev/null +++ b/src/main/resources/assets/lib39/shaders/core/selection.json @@ -0,0 +1,45 @@ +{ + "vertex": "position_color", + "fragment": "lib39:selection", + "samplers": [ + ], + "uniforms": [ + { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, + { + "name": "Center", + "type": "float", + "count": 2, + "values": [ + 0.0, + 0.0 + ] + }, + { + "name": "FramebufferSize", + "type": "float", + "count": 2, + "values": [ + 0.0, + 0.0 + ] + }, + { + "name": "Radius", + "type": "float", + "count": 1, + "values": [ + 0.0 + ] + }, + { + "name": "AntiAliasingRadius", + "type": "float", + "count": 1, + "values": [ + 1.5 + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/lib39/textures/item/forge.png b/src/main/resources/assets/lib39/textures/item/forge.png new file mode 100644 index 0000000..22a6c8f Binary files /dev/null and b/src/main/resources/assets/lib39/textures/item/forge.png differ