diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
index 1f405ce..2c89f9c 100644
--- a/.github/workflows/gradle.yml
+++ b/.github/workflows/gradle.yml
@@ -35,7 +35,7 @@ jobs:
- name: capture build artifacts
if: ${{ runner.os == 'Linux' && matrix.java == '21' }} # Only upload artifacts built from the latest java on one OS
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: Dynamichud
path: build/libs/
diff --git a/.gitignore b/.gitignore
index 3c37caf..12044e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -116,3 +116,5 @@ run/
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
+
+*.profileconfig.json
diff --git a/build.gradle b/build.gradle
index 782b183..ac1ac6e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,5 +1,5 @@
plugins {
- id 'fabric-loom' version '1.7-SNAPSHOT'
+ id 'fabric-loom' version '1.9-SNAPSHOT'
id 'maven-publish'
}
@@ -34,8 +34,6 @@ dependencies {
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
modImplementation "dev.isxander:yet-another-config-lib:${project.yacl_version}"
-
- modApi "com.terraformersmc:modmenu:11.0.1"
}
processResources {
diff --git a/gradle.properties b/gradle.properties
index da95953..df1ba47 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -4,15 +4,16 @@ org.gradle.parallel=true
# Fabric Properties
# check these on https://fabricmc.net/develop
-minecraft_version=1.21
-yarn_mappings=1.21+build.2
-loader_version=0.15.11
+minecraft_version=1.21.4
+yarn_mappings=1.21.4+build.1
+loader_version=0.16.9
# Mod Properties
-mod_version = 2.1.0
+# need versioning system
+mod_version = 3.0.0
maven_group = com.tanishisherewith
archives_base_name = dynamichud
# Dependencies
-fabric_version=0.100.3+1.21
-yacl_version=3.5.0+1.21-fabric
\ No newline at end of file
+fabric_version=0.111.0+1.21.4
+yacl_version=3.6.6+1.21.4-fabric
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2617362..4eaec46 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/src/main/java/com/tanishisherewith/dynamichud/DynamicHUD.java b/src/main/java/com/tanishisherewith/dynamichud/DynamicHUD.java
index 2f2f4be..ef01878 100644
--- a/src/main/java/com/tanishisherewith/dynamichud/DynamicHUD.java
+++ b/src/main/java/com/tanishisherewith/dynamichud/DynamicHUD.java
@@ -1,52 +1,22 @@
package com.tanishisherewith.dynamichud;
import com.tanishisherewith.dynamichud.config.GlobalConfig;
-import com.tanishisherewith.dynamichud.screens.AbstractMoveableScreen;
-import com.tanishisherewith.dynamichud.utils.BooleanPool;
-import com.tanishisherewith.dynamichud.widget.Widget;
-import com.tanishisherewith.dynamichud.widget.WidgetManager;
-import com.tanishisherewith.dynamichud.widget.WidgetRenderer;
-import com.tanishisherewith.dynamichud.widgets.TextWidget;
+import com.tanishisherewith.dynamichud.integration.IntegrationManager;
import net.fabricmc.api.ClientModInitializer;
-import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
-import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback;
-import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
-import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
-import net.fabricmc.loader.api.FabricLoader;
-import net.fabricmc.loader.api.metadata.ModMetadata;
import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.option.KeyBinding;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Objects;
-
+@Environment(EnvType.CLIENT)
public class DynamicHUD implements ClientModInitializer {
- /**
- * This is a map to store the list of widgets for each widget file to be saved.
- *
- * Allows saving widgets across different mods with same save file name.
- */
- public static final HashMap> FILE_MAP = new HashMap<>();
- public static final Logger logger = LoggerFactory.getLogger("DynamicHud");
- private static final List widgetRenderers = new ArrayList<>();
public static MinecraftClient MC = MinecraftClient.getInstance();
+ public static final Logger logger = LoggerFactory.getLogger("DynamicHud");
public static String MOD_ID = "dynamichud";
- public static void addWidgetRenderer(WidgetRenderer widgetRenderer) {
- widgetRenderers.add(widgetRenderer);
- }
-
- public static List getWidgetRenderers() {
- return widgetRenderers;
- }
-
public static void printInfo(String msg) {
logger.info(msg);
}
@@ -55,133 +25,16 @@ public static void printWarn(String msg) {
logger.warn(msg);
}
- /**
- * Opens the MovableScreen when the specified key is pressed.
- *
- * @param key The key to listen for
- * @param screen The AbstractMoveableScreen instance to use to set the screen
- */
- public static void openDynamicScreen(KeyBinding key, AbstractMoveableScreen screen) {
- if (key.wasPressed()) {
- MC.setScreen(screen);
- }
- }
-
@Override
public void onInitializeClient() {
- printInfo("Initialising DynamicHud");
-
- // Add WidgetData of included widgets
- WidgetManager.registerCustomWidgets(
- TextWidget.DATA
- );
+ printInfo("Initialising DynamicHUD");
//YACL load
GlobalConfig.HANDLER.load();
- printInfo("Integrating mods...");
- FabricLoader.getInstance()
- .getEntrypointContainers("dynamicHud", DynamicHudIntegration.class)
- .forEach(entrypoint -> {
- ModMetadata metadata = entrypoint.getProvider().getMetadata();
- String modId = metadata.getId();
-
- printInfo(String.format("Supported mod with id %s was found!", modId));
-
- AbstractMoveableScreen screen;
- KeyBinding binding;
- WidgetRenderer widgetRenderer;
- File widgetsFile;
- try {
- DynamicHudIntegration DHIntegration = entrypoint.getEntrypoint();
-
- //Calls the init method
- DHIntegration.init();
-
- //Gets the widget file to save and load the widgets from
- widgetsFile = DHIntegration.getWidgetsFile();
-
- // Adds / loads widgets from file
- if (widgetsFile.exists()) {
- WidgetManager.loadWidgets(widgetsFile);
- } else {
- DHIntegration.addWidgets();
- }
-
- //Calls the second init method
- DHIntegration.initAfter();
-
- // Get the instance of AbstractMoveableScreen
- screen = Objects.requireNonNull( DHIntegration.getMovableScreen());
-
- // Get the keybind to open the screen instance
- binding = DHIntegration.getKeyBind();
-
- //Register custom widget datas by WidgetManager.registerCustomWidgets();
- DHIntegration.registerCustomWidgets();
-
- //WidgetRenderer with widgets instance
- widgetRenderer = DHIntegration.getWidgetRenderer();
- addWidgetRenderer(widgetRenderer);
-
- List widgets = FILE_MAP.get(widgetsFile.getName());
-
- if (widgets == null || widgets.isEmpty()) {
- FILE_MAP.put(widgetsFile.getName(), widgetRenderer.getWidgets());
- } else {
- widgets.addAll(widgetRenderer.getWidgets());
- FILE_MAP.put(widgetsFile.getName(), widgets);
- }
-
- //Register events for rendering, saving, loading, and opening the hudEditor
- ClientTickEvents.START_CLIENT_TICK.register((client) -> openDynamicScreen(binding, screen));
-
- /* === Saving === */
-
- //When a player exits a world (SinglePlayer worlds) or a server stops
- ServerLifecycleEvents.SERVER_STOPPING.register(server -> saveWidgetsSafely(widgetsFile, FILE_MAP.get(widgetsFile.getName())));
-
- // When a resource pack is reloaded.
- ServerLifecycleEvents.END_DATA_PACK_RELOAD.register((server, resourceManager, s) -> saveWidgetsSafely(widgetsFile, FILE_MAP.get(widgetsFile.getName())));
-
- //When player disconnects
- ServerPlayConnectionEvents.DISCONNECT.register((handler, packetSender) -> saveWidgetsSafely(widgetsFile, FILE_MAP.get(widgetsFile.getName())));
-
- //When minecraft closes
- ClientLifecycleEvents.CLIENT_STOPPING.register((minecraftClient) -> saveWidgetsSafely(widgetsFile, FILE_MAP.get(widgetsFile.getName())));
-
- printInfo(String.format("Integration of mod %s was successful", modId));
- } catch (Throwable e) {
- if (e instanceof IOException) {
- logger.warn("An error has occurred while loading widgets of mod {}", modId, e);
- } else {
- logger.warn("Mod {} has improper implementation of DynamicHUD", modId, e);
- }
- }
- });
- printInfo("(DynamicHUD) Integration of mods found was successful");
-
-
- //Global config saving (YACL)
- ServerLifecycleEvents.SERVER_STOPPING.register(server -> GlobalConfig.HANDLER.save());
- ServerLifecycleEvents.END_DATA_PACK_RELOAD.register((server, resourceManager, s) -> GlobalConfig.HANDLER.save());
- ServerPlayConnectionEvents.DISCONNECT.register((handler, packetSender) -> GlobalConfig.HANDLER.save());
- ClientLifecycleEvents.CLIENT_STOPPING.register((minecraftClient) -> {
- GlobalConfig.HANDLER.save();
- });
-
+ IntegrationManager.integrate();
//In game screen render.
HudRenderCallback.EVENT.register(new HudRender());
}
-
- private void saveWidgetsSafely(File widgetsFile, List widgets) {
- try {
- WidgetManager.saveWidgets(widgetsFile, widgets);
- } catch (IOException e) {
- logger.error("Failed to save widgets. Widgets passed: {}", widgets);
- throw new RuntimeException(e);
- }
- }
-
}
diff --git a/src/main/java/com/tanishisherewith/dynamichud/DynamicHudTest.java b/src/main/java/com/tanishisherewith/dynamichud/DynamicHudTest.java
deleted file mode 100644
index 0883606..0000000
--- a/src/main/java/com/tanishisherewith/dynamichud/DynamicHudTest.java
+++ /dev/null
@@ -1,99 +0,0 @@
-package com.tanishisherewith.dynamichud;
-
-import com.tanishisherewith.dynamichud.screens.AbstractMoveableScreen;
-import com.tanishisherewith.dynamichud.utils.DynamicValueRegistry;
-import com.tanishisherewith.dynamichud.widget.Widget;
-import com.tanishisherewith.dynamichud.widget.WidgetManager;
-import com.tanishisherewith.dynamichud.widget.WidgetRenderer;
-import com.tanishisherewith.dynamichud.widgets.TextWidget;
-import net.minecraft.client.gui.screen.TitleScreen;
-import net.minecraft.text.Text;
-
-import java.util.List;
-
-public class DynamicHudTest implements DynamicHudIntegration {
- TextWidget FPSWidget;
- TextWidget HelloWidget;
- TextWidget DynamicHUDWidget;
- DynamicValueRegistry registry;
- WidgetRenderer renderer;
-
- @Override
- public void init() {
- //Global registry
- DynamicValueRegistry.registerGlobal("FPS", () -> "FPS: " + DynamicHUD.MC.getCurrentFps());
-
- //Local registry
- registry = new DynamicValueRegistry(DynamicHUD.MOD_ID);
- registry.registerLocal("Hello", () -> "Hello " + DynamicHUD.MC.getSession().getUsername() + "!");
- registry.registerLocal("DynamicHUD", () -> "DynamicHUD");
-
-
- FPSWidget = new TextWidget.Builder()
- .setX(250)
- .setY(100)
- .setDraggable(true)
- .rainbow(false)
- .setDRKey("FPS")
- .setModID(DynamicHUD.MOD_ID)
- .shouldScale(false)
- .build();
-
- HelloWidget = new TextWidget.Builder()
- .setX(200)
- .setY(100)
- .setDraggable(true)
- .rainbow(false)
- .setDRKey("Hello")
- .setDVR(registry)
- .setModID(DynamicHUD.MOD_ID)
- .shouldScale(true)
- .build();
-
- DynamicHUDWidget = new TextWidget.Builder()
- .setX(5)
- .setY(5)
- .setDraggable(false)
- .rainbow(true)
- .setDRKey("DynamicHUD")
- .setDVR(registry)
- .setModID(DynamicHUD.MOD_ID)
- .shouldScale(true)
- .build();
-
- }
-
- @Override
- public void addWidgets() {
- WidgetManager.addWidget(FPSWidget);
- WidgetManager.addWidget(HelloWidget);
- WidgetManager.addWidget(DynamicHUDWidget);
- }
-
- @Override
- public void registerCustomWidgets() {
- //WidgetManager.addWidgetData(MyWidget.DATA);
- }
-
- public void initAfter() {
- List widgets = WidgetManager.getWidgetsForMod(DynamicHUD.MOD_ID);
-
- renderer = new WidgetRenderer(widgets);
- renderer.shouldRenderInGameHud(true);
-
- //This will make widgets render in the titlescreen as well.
- renderer.addScreen(TitleScreen.class);
- }
-
- @Override
- public AbstractMoveableScreen getMovableScreen() {
- return new AbstractMoveableScreen(Text.literal("Editor Screen"), renderer) {
- };
- }
-
- @Override
- public WidgetRenderer getWidgetRenderer() {
- return renderer;
- }
-
-}
diff --git a/src/main/java/com/tanishisherewith/dynamichud/HudRender.java b/src/main/java/com/tanishisherewith/dynamichud/HudRender.java
index a7832cf..63ae883 100644
--- a/src/main/java/com/tanishisherewith/dynamichud/HudRender.java
+++ b/src/main/java/com/tanishisherewith/dynamichud/HudRender.java
@@ -1,5 +1,6 @@
package com.tanishisherewith.dynamichud;
+import com.tanishisherewith.dynamichud.integration.IntegrationManager;
import com.tanishisherewith.dynamichud.widget.WidgetRenderer;
import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback;
import net.minecraft.client.gui.DrawContext;
@@ -9,11 +10,11 @@
* Using the fabric event {@link HudRenderCallback} to render widgets in the game HUD.
* Mouse positions are passed in the negatives even though theoretically it's in the centre of the screen.
*/
-public class HudRender implements HudRenderCallback{
+public class HudRender implements HudRenderCallback {
@Override
public void onHudRender(DrawContext drawContext, RenderTickCounter tickCounter) {
- for (WidgetRenderer widgetRenderer : DynamicHUD.getWidgetRenderers()) {
+ for (WidgetRenderer widgetRenderer : IntegrationManager.getWidgetRenderers()) {
widgetRenderer.renderWidgets(drawContext, -120, -120);
}
}
diff --git a/src/main/java/com/tanishisherewith/dynamichud/IntegrationTest.java b/src/main/java/com/tanishisherewith/dynamichud/IntegrationTest.java
new file mode 100644
index 0000000..e6c010c
--- /dev/null
+++ b/src/main/java/com/tanishisherewith/dynamichud/IntegrationTest.java
@@ -0,0 +1,111 @@
+package com.tanishisherewith.dynamichud;
+
+import com.tanishisherewith.dynamichud.integration.DynamicHudConfigurator;
+import com.tanishisherewith.dynamichud.integration.DynamicHudIntegration;
+import com.tanishisherewith.dynamichud.screens.AbstractMoveableScreen;
+import com.tanishisherewith.dynamichud.utils.DynamicValueRegistry;
+import com.tanishisherewith.dynamichud.widget.Widget;
+import com.tanishisherewith.dynamichud.widgets.GraphWidget;
+import com.tanishisherewith.dynamichud.widgets.TextWidget;
+import net.minecraft.client.gui.screen.TitleScreen;
+import net.minecraft.text.Text;
+
+import java.awt.*;
+
+public class IntegrationTest implements DynamicHudIntegration {
+ TextWidget FPSWidget;
+ TextWidget HelloWidget;
+ TextWidget DynamicHUDWidget;
+ GraphWidget graphWidget;
+ DynamicValueRegistry registry;
+
+ @Override
+ public void init() {
+ //Global registry
+ // We recommend using the syntax "modid:key_name" for easier debugging and to prevent data conflicts in global registries.
+ DynamicValueRegistry.registerGlobal("dynamichud:FPS", () -> "FPS: " + DynamicHUD.MC.getCurrentFps());
+
+ //Local registry
+ registry = new DynamicValueRegistry(DynamicHUD.MOD_ID);
+ registry.registerLocal("Hello", () -> "Hello " + DynamicHUD.MC.getSession().getUsername() + "!");
+ registry.registerLocal("DynamicHUD", () -> "DynamicHUD");
+ registry.registerLocal("FPS", () -> DynamicHUD.MC.getCurrentFps());
+
+ FPSWidget = new TextWidget.Builder()
+ .setX(250)
+ .setY(150)
+ .setDraggable(true)
+ .rainbow(false)
+ .registryKey("dynamichud:FPS")
+ .setModID(DynamicHUD.MOD_ID)
+ .shouldScale(false)
+ .build();
+
+ HelloWidget = new TextWidget.Builder()
+ .setX(200)
+ .setY(100)
+ .setDraggable(true)
+ .rainbow(false)
+ .registryKey("Hello")
+ .registryID(registry.getId())
+ .setModID(DynamicHUD.MOD_ID)
+ .shouldScale(true)
+ .shadow(true)
+ .build();
+
+ DynamicHUDWidget = new TextWidget.Builder()
+ .setX(0)
+ .setY(0)
+ .setDraggable(false)
+ .rainbow(true)
+ .registryKey("DynamicHUD")
+ .registryID(registry.getId())
+ .setModID(DynamicHUD.MOD_ID)
+ .shouldScale(true)
+ .build();
+
+ graphWidget = new GraphWidget.GraphWidgetBuilder()
+ .setX(250)
+ .setY(100)
+ .label("FPS Chart")
+ .graphColor(Color.CYAN)
+ .anchor(Widget.Anchor.CENTER)
+ .height(100)
+ .width(150)
+ .gridLines(10)
+ .backgroundColor(Color.DARK_GRAY)
+ .lineThickness(1f)
+ .maxDataPoints(100)
+ .maxValue(120)
+ .minValue(30)
+ .setModID(DynamicHUD.MOD_ID)
+ .setDraggable(true)
+ .setDisplay(true)
+ .showGrid(true)
+ .registryKey("FPS")
+ .registryID(registry.getId())
+ .build()
+ .autoUpdateRange();
+ }
+
+ @Override
+ public DynamicHudConfigurator configure(DynamicHudConfigurator configurator) {
+ configurator.addWidget(FPSWidget)
+ .addWidget(HelloWidget)
+ .addWidget(DynamicHUDWidget)
+ .addWidget(graphWidget)
+ .configureRenderer(renderer -> {
+ //Already true by default
+ //renderer.shouldRenderInGameHud(true);
+ renderer.addScreen(TitleScreen.class);
+ })
+ .withMoveableScreen(config -> new AbstractMoveableScreen(Text.literal("Editor Screen"), config.getRenderer()) {});
+
+ return configurator;
+ }
+
+ @Override
+ public void registerCustomWidgets() {
+ //WidgetManager.addWidgetData(MyWidget.DATA);
+ }
+}
diff --git a/src/main/java/com/tanishisherewith/dynamichud/ModMenuIntegration.java b/src/main/java/com/tanishisherewith/dynamichud/ModMenuIntegration.java
deleted file mode 100644
index 30fffb4..0000000
--- a/src/main/java/com/tanishisherewith/dynamichud/ModMenuIntegration.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.tanishisherewith.dynamichud;
-
-import com.tanishisherewith.dynamichud.config.GlobalConfig;
-import com.terraformersmc.modmenu.api.ConfigScreenFactory;
-import com.terraformersmc.modmenu.api.ModMenuApi;
-import net.minecraft.client.gui.screen.Screen;
-
-
-public class ModMenuIntegration implements ModMenuApi {
- public static Screen YACL_CONFIG_SCREEN = GlobalConfig.get().createYACLGUI();
-
- @Override
- public ConfigScreenFactory> getModConfigScreenFactory() {
- return parent -> YACL_CONFIG_SCREEN;
- }
-}
diff --git a/src/main/java/com/tanishisherewith/dynamichud/config/GlobalConfig.java b/src/main/java/com/tanishisherewith/dynamichud/config/GlobalConfig.java
index d8b5690..e832380 100644
--- a/src/main/java/com/tanishisherewith/dynamichud/config/GlobalConfig.java
+++ b/src/main/java/com/tanishisherewith/dynamichud/config/GlobalConfig.java
@@ -1,16 +1,18 @@
package com.tanishisherewith.dynamichud.config;
import dev.isxander.yacl3.api.*;
-import dev.isxander.yacl3.api.controller.BooleanControllerBuilder;
-import dev.isxander.yacl3.api.controller.FloatSliderControllerBuilder;
+import dev.isxander.yacl3.api.controller.*;
import dev.isxander.yacl3.config.v2.api.ConfigClassHandler;
import dev.isxander.yacl3.config.v2.api.SerialEntry;
import dev.isxander.yacl3.config.v2.api.serializer.GsonConfigSerializerBuilder;
import net.fabricmc.loader.api.FabricLoader;
+import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
+import java.awt.*;
+
public final class GlobalConfig {
public static final ConfigClassHandler HANDLER = ConfigClassHandler.createBuilder(GlobalConfig.class)
.id(Identifier.of("dynamichud", "dynamichud_config"))
@@ -19,9 +21,10 @@ public final class GlobalConfig {
.setJson5(true)
.build())
.build();
+
private static final GlobalConfig INSTANCE = new GlobalConfig();
/**
- * Common scale for all widgets. Set by the user using YACL.
+ * Common scale for all widgets.
*/
@SerialEntry
private float scale = 1.0f;
@@ -32,11 +35,30 @@ public final class GlobalConfig {
@SerialEntry
private boolean showColorPickerPreview = true;
+ @SerialEntry
+ private boolean renderInDebugScreen = false;
+
+ @SerialEntry
+ private final boolean forceSameContextMenuSkin = true;
+
+ //These package names are getting seriously long
+ @SerialEntry
+ private com.tanishisherewith.dynamichud.utils.contextmenu.options.Option.Complexity complexity = com.tanishisherewith.dynamichud.utils.contextmenu.options.Option.Complexity.Simple;
+
+ @SerialEntry
+ private int snapSize = 100;
+
+ @SerialEntry
+ private Color hudActiveColor = new Color(0, 0, 0, 128);
+
+ @SerialEntry
+ private Color hudInactiveColor = new Color(255, 0, 0, 128);
+
public static GlobalConfig get() {
return INSTANCE;
}
- public final Screen createYACLGUI() {
+ public Screen createYACLGUI() {
return YetAnotherConfigLib.createBuilder()
.title(Text.literal("DynamicHUD config screen."))
.category(ConfigCategory.createBuilder()
@@ -51,6 +73,12 @@ public final Screen createYACLGUI() {
.binding(1.0f, () -> this.scale, newVal -> this.scale = newVal)
.controller(floatOption -> FloatSliderControllerBuilder.create(floatOption).range(0.1f, 2.5f).step(0.1f))
.build())
+ .option(Option.createBuilder()
+ .name(Text.literal("Render in debug screen"))
+ .description(OptionDescription.of(Text.literal("Renders widgets even when the debug screen is on")))
+ .binding(true, () -> this.renderInDebugScreen, newVal -> this.renderInDebugScreen = newVal)
+ .controller(booleanOption -> BooleanControllerBuilder.create(booleanOption).yesNoFormatter())
+ .build())
.option(Option.createBuilder()
.name(Text.literal("Show Color picker preview"))
.description(OptionDescription.of(Text.literal("Shows the preview below your mouse pointer on selecting color from the screen. Note: You may drop some frames with the preview on.")))
@@ -63,12 +91,41 @@ public final Screen createYACLGUI() {
.binding(true, () -> this.displayDescriptions, newVal -> this.displayDescriptions = newVal)
.controller(booleanOption -> BooleanControllerBuilder.create(booleanOption).yesNoFormatter())
.build())
+ .option(Option.createBuilder()
+ .name(Text.literal("Snap Size"))
+ .description(OptionDescription.of(Text.literal("Grid size for snapping widgets")))
+ .binding(100, () -> this.snapSize, newVal -> this.snapSize = newVal)
+ .controller(integerOption -> IntegerFieldControllerBuilder.create(integerOption).range(10, 500))
+ .build())
+ .build())
+ .option(Option.createBuilder()
+ .name(Text.literal("Widget HUD Active Background Color"))
+ .description(OptionDescription.of(Text.literal("Color of the background of the widget when it will be rendered")))
+ .binding(new Color(0, 0, 0, 128), () -> this.hudActiveColor, newVal -> this.hudActiveColor = newVal)
+ .controller(ColorControllerBuilder::create)
+ .build())
+ .option(Option.createBuilder()
+ .name(Text.literal("Widget HUD Inactive Background Color"))
+ .description(OptionDescription.of(Text.literal("Color of the background of the widget when it will NOT be rendered")))
+ .binding(new Color(255, 0, 0, 128), () -> this.hudInactiveColor, newVal -> this.hudInactiveColor = newVal)
+ .controller(ColorControllerBuilder::create)
+ .build())
+ .option(Option.createBuilder()
+ .name(Text.literal("Settings Complexity"))
+ .description(OptionDescription.of(Text.literal("The level of options to display. Options equal to or below this level will be displayed")))
+ .binding(com.tanishisherewith.dynamichud.utils.contextmenu.options.Option.Complexity.Simple, () -> this.complexity, newVal -> this.complexity = newVal)
+ .controller((option) -> EnumControllerBuilder.create(option)
+ .enumClass(com.tanishisherewith.dynamichud.utils.contextmenu.options.Option.Complexity.class)
+ .formatValue(value -> Text.of(value.name()))
+ )
.build())
.build())
+ .save(HANDLER::save)
.build()
- .generateScreen(null);
+ .generateScreen(MinecraftClient.getInstance().currentScreen);
}
- public float getScale(){
+
+ public float getScale() {
return scale;
}
@@ -79,4 +136,24 @@ public boolean showColorPickerPreview() {
public boolean shouldDisplayDescriptions() {
return displayDescriptions;
}
+
+ public boolean renderInDebugScreen() {
+ return renderInDebugScreen;
+ }
+
+ public int getSnapSize() {
+ return snapSize;
+ }
+
+ public Color getHudInactiveColor() {
+ return hudInactiveColor;
+ }
+
+ public Color getHudActiveColor() {
+ return hudActiveColor;
+ }
+
+ public com.tanishisherewith.dynamichud.utils.contextmenu.options.Option.Complexity complexity() {
+ return complexity;
+ }
}
diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/ColorHelper.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/ColorHelper.java
index 1faa386..a564ad9 100644
--- a/src/main/java/com/tanishisherewith/dynamichud/helpers/ColorHelper.java
+++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/ColorHelper.java
@@ -1,8 +1,14 @@
package com.tanishisherewith.dynamichud.helpers;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gl.Framebuffer;
+import net.minecraft.client.util.Window;
import net.minecraft.util.math.MathHelper;
+import org.lwjgl.opengl.GL11;
+import org.lwjgl.opengl.GL30;
import java.awt.*;
+import java.nio.ByteBuffer;
/**
* This class provides helper methods for working with colors.
@@ -111,6 +117,38 @@ public static float[] getRainbowColor() {
return rainbow;
}
+ /**
+ * @param color Target color.
+ * @return Alpha of the color.
+ */
+ public static float getAlpha(int color) {
+ return (float) (color >> 24 & 255) / 255.0F;
+ }
+
+ /**
+ * @param color Target color.
+ * @return Red value of the color.
+ */
+ public static float getRed(int color) {
+ return (float) (color >> 16 & 255) / 255.0F;
+ }
+
+ /**
+ * @param color Target color.
+ * @return Green value of the color.
+ */
+ public static float getGreen(int color) {
+ return (float) (color >> 8 & 255) / 255.0F;
+ }
+
+ /**
+ * @param color Target color.
+ * @return Blue value of the color.
+ */
+ public static float getBlue(int color) {
+ return (float) (color & 255) / 255.0F;
+ }
+
/**
* Rainbow color with custom speed.
*
@@ -136,6 +174,49 @@ public static Color changeAlpha(Color color, int alpha) {
return new Color(0);
}
+ public static int[] getMousePixelColor(double mouseX, double mouseY) {
+ MinecraftClient client = MinecraftClient.getInstance();
+ Framebuffer framebuffer = client.getFramebuffer();
+ Window window = client.getWindow();
+
+ // Get the window and framebuffer dimensions
+ int windowWidth = window.getWidth();
+ int windowHeight = window.getHeight();
+ int framebufferWidth = framebuffer.textureWidth;
+ int framebufferHeight = framebuffer.textureHeight;
+
+ // Calculate scaling factors
+ double scaleX = (double) framebufferWidth / windowWidth;
+ double scaleY = (double) framebufferHeight / windowHeight;
+
+ // Convert mouse coordinates to framebuffer coordinates
+ int x = (int) (mouseX * scaleX);
+ int y = (int) ((windowHeight - mouseY) * scaleY);
+
+ // Ensure the coordinates are within the framebuffer bounds
+ if (x < 0 || x >= framebufferWidth || y < 0 || y >= framebufferHeight) {
+ System.err.println("Mouse coordinates are out of bounds");
+ return null;
+ }
+
+ // Allocate a buffer to store the pixel data
+ ByteBuffer buffer = ByteBuffer.allocateDirect(4); // 4 bytes for RGBA
+
+ // Bind the framebuffer for reading
+ GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, framebuffer.fbo);
+
+ // Read the pixel at the mouse position
+ GL11.glReadPixels(x, y, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer);
+
+ // Extract the color components from the buffer
+ int red = buffer.get(0) & 0xFF;
+ int green = buffer.get(1) & 0xFF;
+ int blue = buffer.get(2) & 0xFF;
+ int alpha = buffer.get(3) & 0xFF;
+
+ return new int[]{red, green, blue, alpha};
+ }
+
public static int fromRGBA(int r, int g, int b, int a) {
return (r << 16) + (g << 8) + (b) + (a << 24);
}
diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/DrawHelper.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/DrawHelper.java
index c310b07..bbf0b4a 100644
--- a/src/main/java/com/tanishisherewith/dynamichud/helpers/DrawHelper.java
+++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/DrawHelper.java
@@ -1,19 +1,30 @@
package com.tanishisherewith.dynamichud.helpers;
import com.mojang.blaze3d.platform.GlStateManager;
+import com.mojang.blaze3d.systems.ProjectionType;
import com.mojang.blaze3d.systems.RenderSystem;
import com.tanishisherewith.dynamichud.DynamicHUD;
+import com.tanishisherewith.dynamichud.widget.WidgetBox;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gl.ShaderProgramKeys;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.render.*;
import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.text.Text;
+import net.minecraft.util.Util;
import net.minecraft.util.math.MathHelper;
+import org.jetbrains.annotations.NotNull;
import org.joml.Matrix4f;
+import org.lwjgl.opengl.GL40;
import org.lwjgl.opengl.GL40C;
import java.awt.*;
+import java.util.Objects;
+
+import static com.tanishisherewith.dynamichud.helpers.TextureHelper.mc;
/**
- * Credits: HeliosClient
+ * Credits: HeliosClient
*/
public class DrawHelper {
@@ -46,8 +57,8 @@ public static void drawGradient(Matrix4f matrix4f, float x, float y, float width
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.blendFunc(GlStateManager.SrcFactor.SRC_ALPHA, GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA);
- RenderSystem.setShader(GameRenderer::getPositionColorProgram);
-
+ RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR);
+
switch (direction) {
case LEFT_RIGHT:
bufferBuilder.vertex(matrix4f, x, y + height, 0.0F).color(startRed, startGreen, startBlue, startAlpha);
@@ -81,8 +92,14 @@ public static void drawGradient(Matrix4f matrix4f, float x, float y, float width
}
public static void enableScissor(int x, int y, int width, int height) {
- double scaleFactor = DynamicHUD.MC.getWindow().getScaleFactor();
+ enableScissor(x, y, width, height, mc.getWindow().getScaleFactor());
+ }
+ public static void enableScissor(WidgetBox box) {
+ enableScissor((int) box.x, (int) box.y, (int) box.getWidth(), (int) box.getHeight(), mc.getWindow().getScaleFactor());
+ }
+
+ public static void enableScissor(int x, int y, int width, int height, double scaleFactor) {
int scissorX = (int) (x * scaleFactor);
int scissorY = (int) (DynamicHUD.MC.getWindow().getHeight() - ((y + height) * scaleFactor));
int scissorWidth = (int) (width * scaleFactor);
@@ -117,7 +134,7 @@ public static void drawRectangle(Matrix4f matrix4f, float x, float y, float widt
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.blendFunc(GlStateManager.SrcFactor.SRC_ALPHA, GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA);
- RenderSystem.setShader(GameRenderer::getPositionColorProgram);
+ RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR);
tessellator.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_COLOR);
@@ -204,7 +221,7 @@ public static void drawRainbowGradientRectangle(Matrix4f matrix4f, float x, floa
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.blendFunc(GlStateManager.SrcFactor.SRC_ALPHA, GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA);
- RenderSystem.setShader(GameRenderer::getPositionColorProgram);
+ RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR);
for (int i = 0; i <= width; i++) {
float hue = (float) i / width;
@@ -239,14 +256,43 @@ public static void drawRainbowGradientRectangle(Matrix4f matrix4f, float x, floa
RenderSystem.disableBlend();
}
+ /**
+ * Draw chroma text (text with a nice rainbow effect)
+ *
+ * @param drawContext A drawContext object
+ * @param text The text to display
+ * @param x X pos of text
+ * @param y Y pos of text
+ * @param speed Speed of rainbow
+ * @param saturation Saturation of the rainbow colors
+ * @param brightness Brightness of the rainbow colors
+ * @param spread How much the color difference should be between each character (ideally between 0.001 to 0.2)
+ * @param shadow Whether to render the text as shadow.
+ */
+ public static void drawChromaText(@NotNull DrawContext drawContext, String text, int x, int y, float speed, float saturation, float brightness, float spread, boolean shadow) {
+ long time = System.currentTimeMillis();
+ int length = text.length();
+
+ for (int i = 0; i < length; i++) {
+ float hue = (time % (int) (5000 / speed)) / (5000f / speed) + (i * spread); // Adjust the hue based on time and character position
+ hue = MathHelper.floorMod(hue, 1.0f); // hue should stay within the range [0, 1]
+
+ // Convert the hue to an RGB color
+ int color = Color.HSBtoRGB(hue, saturation, brightness);
+
+ // Draw the character with the calculated color
+ drawContext.drawText(mc.textRenderer, String.valueOf(text.charAt(i)), x + mc.textRenderer.getWidth(text.substring(0, i)), y, color, shadow);
+ }
+ }
+
- public static void drawRainbowGradient(Matrix4f matrix, float x, float y, float width, float height) {
+ public static void drawRainbowGradient(float x, float y, float width, float height) {
Matrix4f matrix4f = RenderSystem.getModelViewMatrix();
RenderSystem.enableBlend();
RenderSystem.colorMask(false, false, false, true);
RenderSystem.clearColor(0.0F, 0.0F, 0.0F, 0.0F);
- RenderSystem.clear(GL40C.GL_COLOR_BUFFER_BIT, false);
+ RenderSystem.clear(GL40C.GL_COLOR_BUFFER_BIT);
RenderSystem.colorMask(true, true, true, true);
drawRectangle(matrix4f, x, y, width, height, Color.BLACK.getRGB());
@@ -294,7 +340,7 @@ public static void drawOutlineCircle(Matrix4f matrix4f, float xCenter, float yCe
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder bufferBuilder = tessellator.begin(VertexFormat.DrawMode.DEBUG_LINES, VertexFormats.POSITION_COLOR);
- RenderSystem.setShader(GameRenderer::getPositionColorProgram);
+ RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR);
for (int i = 0; i <= 360; i++) {
double x = xCenter + Math.sin(Math.toRadians(i)) * radius;
@@ -327,7 +373,7 @@ public static void drawFilledCircle(Matrix4f matrix4f, float xCenter, float yCen
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder bufferBuilder = tessellator.begin(VertexFormat.DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_COLOR);
- RenderSystem.setShader(GameRenderer::getPositionColorProgram);
+ RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR);
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.blendFunc(GlStateManager.SrcFactor.SRC_ALPHA, GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA);
@@ -387,7 +433,7 @@ public static void drawFilledArc(Matrix4f matrix4f, float x, float y, float radi
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder bufferBuilder = tessellator.begin(VertexFormat.DrawMode.DEBUG_LINES, VertexFormats.POSITION_COLOR);
- RenderSystem.setShader(GameRenderer::getPositionColorProgram);
+ RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR);
RenderSystem.enableBlend();
for (float angle = startAngle; angle <= endAngle; angle += 1.0F) {
@@ -430,7 +476,7 @@ public static void drawFilledGradientQuadrant(Matrix4f matrix4f, float xCenter,
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder bufferBuilder = tessellator.begin(VertexFormat.DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_COLOR);
- RenderSystem.setShader(GameRenderer::getPositionColorProgram);
+ RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR);
RenderSystem.enableBlend();
bufferBuilder.vertex(matrix4f, xCenter, yCenter, 0).color(startRed, startGreen, startBlue, startAlpha);
@@ -473,7 +519,7 @@ public static void drawArc(Matrix4f matrix4f, float xCenter, float yCenter, floa
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder bufferBuilder = tessellator.begin(VertexFormat.DrawMode.TRIANGLE_STRIP, VertexFormats.POSITION_COLOR);
- RenderSystem.setShader(GameRenderer::getPositionColorProgram);
+ RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR);
RenderSystem.enableBlend();
for (int i = startAngle; i <= endAngle; i++) {
@@ -508,9 +554,9 @@ public static void drawFilledQuadrant(Matrix4f matrix4f, float xCenter, float yC
float alpha = (float) (color >> 24 & 255) / 255.0F;
Tessellator tessellator = Tessellator.getInstance();
- BufferBuilder bufferBuilder = tessellator.begin(VertexFormat.DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_COLOR);
+ BufferBuilder bufferBuilder = tessellator.begin(VertexFormat.DrawMode.TRIANGLE_FAN, VertexFormats.POSITION_COLOR);
- RenderSystem.setShader(GameRenderer::getPositionColorProgram);
+ RenderSystem.setShader(ShaderProgramKeys.POSITION_COLOR);
RenderSystem.enableBlend();
bufferBuilder.vertex(matrix4f, xCenter, yCenter, 0).color(red, green, blue, alpha);
@@ -743,7 +789,7 @@ public static void drawRoundedGradientRectangle(Matrix4f matrix, Color color1, C
RenderSystem.enableBlend();
RenderSystem.colorMask(false, false, false, true);
RenderSystem.clearColor(0.0F, 0.0F, 0.0F, 0.0F);
- RenderSystem.clear(GL40C.GL_COLOR_BUFFER_BIT, false);
+ RenderSystem.clear(GL40C.GL_COLOR_BUFFER_BIT);
RenderSystem.colorMask(true, true, true, true);
drawRoundedRectangle(matrix, x, y, TL, TR, BL, BR, width, height, (int) radius, color1.getRGB());
@@ -792,6 +838,18 @@ public static void drawOutlinedBox(DrawContext drawContext, int x1, int y1, int
drawContext.fill(x2 - 1, y1 + 1, x2, y2 - 1, color);
}
+ public static void unscaledProjection() {
+ RenderSystem.setProjectionMatrix(new Matrix4f().setOrtho(0, mc.getWindow().getFramebufferWidth(), mc.getWindow().getFramebufferHeight(), 0, 1000, 21000), ProjectionType.ORTHOGRAPHIC);
+ }
+
+ public static void scaledProjection() {
+ RenderSystem.setProjectionMatrix(new Matrix4f().setOrtho(0, (float) (mc.getWindow().getFramebufferWidth() / mc.getWindow().getScaleFactor()), (float) (mc.getWindow().getFramebufferHeight() / mc.getWindow().getScaleFactor()), 0, 1000, 21000), ProjectionType.ORTHOGRAPHIC);
+ }
+
+ public static void customScaledProjection(float scale) {
+ RenderSystem.setProjectionMatrix(new Matrix4f().setOrtho(0, mc.getWindow().getFramebufferWidth() / scale, mc.getWindow().getFramebufferHeight() / scale, 0, 1000, 21000), ProjectionType.ORTHOGRAPHIC);
+ }
+
/**
* This method assumes that the x, y coords are the origin of a widget.
*
@@ -836,6 +894,32 @@ public static void stopScaling(MatrixStack matrices) {
matrices.pop(); // Restore the previous transformation state
}
+ /**
+ * From minecraft
+ */
+ public static void drawScrollableText(DrawContext context, TextRenderer textRenderer, Text text, int centerX, int startX, int startY, int endX, int endY, int color) {
+ int i = textRenderer.getWidth(text);
+ int var10000 = startY + endY;
+ Objects.requireNonNull(textRenderer);
+ int j = (var10000 - 9) / 2 + 1;
+ int k = endX - startX;
+ int l;
+ if (i > k) {
+ l = i - k;
+ double d = (double) Util.getMeasuringTimeMs() / 1000.0;
+ double e = Math.max((double) l * 0.5, 3.0);
+ double f = Math.sin(1.5707963267948966 * Math.cos(6.283185307179586 * d / e)) / 2.0 + 0.5;
+ double g = MathHelper.lerp(f, 0.0, l);
+ context.enableScissor(startX, startY, endX, endY);
+ context.drawTextWithShadow(textRenderer, text, startX - (int) g, j, color);
+ context.disableScissor();
+ } else {
+ l = MathHelper.clamp(centerX, startX + i / 2, endX - i / 2);
+ context.drawCenteredTextWithShadow(textRenderer, text, l, j, color);
+ }
+
+ }
+
public enum Direction {
/* LEFT_RIGHT means from left to right. Same for others */
LEFT_RIGHT, TOP_BOTTOM, RIGHT_LEFT, BOTTOM_TOP
diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/TextureHelper.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/TextureHelper.java
index 8e8bcdd..48b5779 100644
--- a/src/main/java/com/tanishisherewith/dynamichud/helpers/TextureHelper.java
+++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/TextureHelper.java
@@ -14,7 +14,7 @@ public class TextureHelper {
static MinecraftClient mc = MinecraftClient.getInstance();
public static NativeImage loadTexture(Identifier textureId) {
- if(mc.getResourceManager().getResource(textureId).isPresent()) {
+ if (mc.getResourceManager().getResource(textureId).isPresent()) {
try (InputStream inputStream = mc.getResourceManager().getResource(textureId).get().getInputStream()) {
return NativeImage.read(inputStream);
} catch (IOException e) {
@@ -24,7 +24,7 @@ public static NativeImage loadTexture(Identifier textureId) {
return null;
}
- public static NativeImage resizeTexture(NativeImage image, int newWidth, int newHeight) {
+ public static NativeImage resizeTexture(NativeImage image, int newWidth, int newHeight) {
NativeImage result = new NativeImage(newWidth, newHeight, false);
int oldWidth = image.getWidth();
@@ -35,62 +35,64 @@ public static NativeImage resizeTexture(NativeImage image, int newWidth, int ne
int srcX = x * oldWidth / newWidth;
int srcY = y * oldHeight / newHeight;
- result.setColor(x, y, image.getColor(srcX, srcY));
+ result.setColorArgb(x, y, image.getColorArgb(srcX, srcY));
}
}
return result;
}
+
public static NativeImage resizeTextureUsingBilinearInterpolation(NativeImage image, int newWidth, int newHeight) {
NativeImage result = new NativeImage(newWidth, newHeight, false);
- float x_ratio = ((float)(image.getWidth()-1))/newWidth;
- float y_ratio = ((float)(image.getHeight()-1))/newHeight;
+ float x_ratio = ((float) (image.getWidth() - 1)) / newWidth;
+ float y_ratio = ((float) (image.getHeight() - 1)) / newHeight;
float x_diff, y_diff, blue, red, green;
- int offset, a, b, c, d, index;
+ int a, b, c, d;
- for (int i=0;i>8)&0xff)*(1-x_diff)*(1-y_diff) + ((b>>8)&0xff)*(x_diff)*(1-y_diff) +
- ((c>>8)&0xff)*(y_diff)*(1-x_diff) + ((d>>8)&0xff)*(x_diff*y_diff);
+ green = ((a >> 8) & 0xff) * (1 - x_diff) * (1 - y_diff) + ((b >> 8) & 0xff) * (x_diff) * (1 - y_diff) +
+ ((c >> 8) & 0xff) * (y_diff) * (1 - x_diff) + ((d >> 8) & 0xff) * (x_diff * y_diff);
// Red element
- red = ((a>>16)&0xff)*(1-x_diff)*(1-y_diff) + ((b>>16)&0xff)*(x_diff)*(1-y_diff) +
- ((c>>16)&0xff)*(y_diff)*(1-x_diff) + ((d>>16)&0xff)*(x_diff*y_diff);
+ red = ((a >> 16) & 0xff) * (1 - x_diff) * (1 - y_diff) + ((b >> 16) & 0xff) * (x_diff) * (1 - y_diff) +
+ ((c >> 16) & 0xff) * (y_diff) * (1 - x_diff) + ((d >> 16) & 0xff) * (x_diff * y_diff);
- result.setColor(j, i,
- ((((int)red)<<16)&0xff0000) |
- ((((int)green)<<8)&0xff00) |
- ((int)blue)&0xff);
+ result.setColorArgb(j, i,
+ ((((int) red) << 16) & 0xff0000) |
+ ((((int) green) << 8) & 0xff00) |
+ ((int) blue) & 0xff);
}
}
return result;
}
- public static NativeImage invertTexture(NativeImage image) {
+
+ public static NativeImage invertTexture(NativeImage image) {
int width = image.getWidth();
int height = image.getHeight();
NativeImage result = new NativeImage(width, height, false);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
- int argb = image.getColor(x, y);
+ int argb = image.getColorArgb(x, y);
int alpha = (argb >> 24) & 0xFF;
int red = 255 - ((argb >> 16) & 0xFF);
@@ -99,7 +101,7 @@ public static NativeImage invertTexture(NativeImage image) {
int newArgb = (alpha << 24) | (red << 16) | (green << 8) | blue;
- result.setColor(x, y, newArgb);
+ result.setColorArgb(x, y, newArgb);
}
}
@@ -117,11 +119,11 @@ public static NativeImage rotateTexture(NativeImage image, int degrees) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
- int newX = (int)((x - centerX) * Math.cos(angle) - (y - centerY) * Math.sin(angle) + centerX);
- int newY = (int)((x - centerX) * Math.sin(angle) + (y - centerY) * Math.cos(angle) + centerY);
+ int newX = (int) ((x - centerX) * Math.cos(angle) - (y - centerY) * Math.sin(angle) + centerX);
+ int newY = (int) ((x - centerX) * Math.sin(angle) + (y - centerY) * Math.cos(angle) + centerY);
if (newX >= 0 && newX < width && newY >= 0 && newY < height) {
- result.setColor(newY, newX, image.getColor(x, y));
+ result.setColorArgb(newY, newX, image.getColorArgb(x, y));
}
}
}
@@ -136,7 +138,7 @@ private static NativeImage flipTextureHorizontally(NativeImage image) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
- result.setColor(width - x - 1, y, image.getColor(x, y));
+ result.setColorArgb(width - x - 1, y, image.getColorArgb(x, y));
}
}
@@ -150,7 +152,7 @@ private static NativeImage flipTextureVertically(NativeImage image) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
- result.setColor(x, height - y - 1, image.getColor(x, y));
+ result.setColorArgb(x, height - y - 1, image.getColorArgb(x, y));
}
}
@@ -172,7 +174,7 @@ public static NativeImage applyGrayScaleFilter(NativeImage image) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
- int argb = image.getColor(x, y);
+ int argb = image.getColorArgb(x, y);
int alpha = (argb >> 24) & 0xFF;
int red = (argb >> 16) & 0xFF;
@@ -182,18 +184,19 @@ public static NativeImage applyGrayScaleFilter(NativeImage image) {
int gray = (red + green + blue) / 3;
int newArgb = (alpha << 24) | (gray << 16) | (gray << 8) | gray;
- result.setColor(x, y, newArgb);
+ result.setColorArgb(x, y, newArgb);
}
}
return result;
}
+
public static NativeImage cropTexture(NativeImage image, int x, int y, int width, int height) {
NativeImage result = new NativeImage(width, height, false);
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
- result.setColor(j, i, image.getColor(x + j, y + i));
+ result.setColorArgb(j, i, image.getColorArgb(x + j, y + i));
}
}
@@ -207,7 +210,7 @@ public static NativeImage tintTexture(NativeImage image, int color) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
- int argb = image.getColor(x, y);
+ int argb = image.getColorArgb(x, y);
int alpha = (argb >> 24) & 0xFF;
int red = ((argb >> 16) & 0xFF) * ((color >> 16) & 0xFF) / 255;
@@ -216,7 +219,7 @@ public static NativeImage tintTexture(NativeImage image, int color) {
int newArgb = (alpha << 24) | (red << 16) | (green << 8) | blue;
- result.setColor(x, y, newArgb);
+ result.setColorArgb(x, y, newArgb);
}
}
@@ -230,8 +233,8 @@ public static NativeImage overlayTexture(NativeImage image, NativeImage overlay)
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
- int argb1 = image.getColor(x, y);
- int argb2 = overlay.getColor(x, y);
+ int argb1 = image.getColorArgb(x, y);
+ int argb2 = overlay.getColorArgb(x, y);
int alpha = Math.max((argb1 >> 24) & 0xFF, (argb2 >> 24) & 0xFF);
int red = Math.min(255, ((argb1 >> 16) & 0xFF) + ((argb2 >> 16) & 0xFF));
@@ -240,7 +243,7 @@ public static NativeImage overlayTexture(NativeImage image, NativeImage overlay)
int newArgb = (alpha << 24) | (red << 16) | (green << 8) | blue;
- result.setColor(x, y, newArgb);
+ result.setColorArgb(x, y, newArgb);
}
}
@@ -255,7 +258,7 @@ public static int getAverageColor(NativeImage image) {
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
- int argb = image.getColor(x, y);
+ int argb = image.getColorArgb(x, y);
redTotal += (argb >> 16) & 0xFF;
greenTotal += (argb >> 8) & 0xFF;
@@ -263,12 +266,10 @@ public static int getAverageColor(NativeImage image) {
}
}
- int redAverage = (int)(redTotal / pixelCount);
- int greenAverage = (int)(greenTotal / pixelCount);
- int blueAverage = (int)(blueTotal / pixelCount);
+ int redAverage = (int) (redTotal / pixelCount);
+ int greenAverage = (int) (greenTotal / pixelCount);
+ int blueAverage = (int) (blueTotal / pixelCount);
return (redAverage << 16) | (greenAverage << 8) | blueAverage;
}
-
-
}
diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/Animation.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/Animation.java
new file mode 100644
index 0000000..2ce05cc
--- /dev/null
+++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/Animation.java
@@ -0,0 +1,65 @@
+package com.tanishisherewith.dynamichud.helpers.animationhelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class Animation {
+ public long startTime;
+ public long duration;
+ protected boolean running = false;
+ protected boolean finished = false;
+ protected EasingType easing = EasingType.LINEAR;
+ protected final List completionCallbacks = new ArrayList<>();
+
+ public void start() {
+ startTime = System.currentTimeMillis();
+ running = true;
+ finished = false;
+ }
+
+ public void stop() {
+ running = false;
+ finished = true;
+ }
+
+ public void update() {
+ if (!running || finished) return;
+
+ long elapsed = System.currentTimeMillis() - startTime;
+ float progress = Math.min(elapsed / (float) duration, 1.0f);
+ float easedProgress = Easing.apply(easing, progress);
+
+ applyAnimation(easedProgress);
+
+ if (progress >= 1.0f) {
+ finish();
+ }
+ }
+
+ protected abstract void applyAnimation(float progress);
+
+ public Animation duration(long durationMs) {
+ this.duration = durationMs;
+ return this;
+ }
+
+ public Animation easing(EasingType easing) {
+ this.easing = easing;
+ return this;
+ }
+
+ public Animation onComplete(Runnable callback) {
+ completionCallbacks.add(callback);
+ return this;
+ }
+
+ public void finish() {
+ finished = true;
+ running = false;
+ completionCallbacks.forEach(Runnable::run);
+ }
+
+ public boolean isFinished() {
+ return finished;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/AnimationProperty.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/AnimationProperty.java
new file mode 100644
index 0000000..20a80b0
--- /dev/null
+++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/AnimationProperty.java
@@ -0,0 +1,8 @@
+package com.tanishisherewith.dynamichud.helpers.animationhelper;
+
+// AnimationProperty.java
+public interface AnimationProperty {
+ T get();
+
+ void set(T value);
+}
\ No newline at end of file
diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/Easing.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/Easing.java
new file mode 100644
index 0000000..75998ea
--- /dev/null
+++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/Easing.java
@@ -0,0 +1,87 @@
+package com.tanishisherewith.dynamichud.helpers.animationhelper;
+
+public class Easing {
+ public static float apply(EasingType easingType, float progress) {
+ return switch (easingType) {
+ case LINEAR -> progress;
+ case EASE_IN_SINE -> (float) (1 - Math.cos((progress * Math.PI) / 2));
+ case EASE_OUT_SINE -> (float) Math.sin((progress * Math.PI) / 2);
+ case EASE_IN_OUT_SINE -> (float) (-(Math.cos(Math.PI * progress) - 1) / 2);
+ case EASE_IN_QUAD -> progress * progress;
+ case EASE_OUT_QUAD -> 1 - (1 - progress) * (1 - progress);
+ case EASE_IN_OUT_QUAD ->
+ progress < 0.5 ? 2 * progress * progress : (float) (1 - Math.pow(-2 * progress + 2, 2) / 2);
+ case EASE_IN_CUBIC -> progress * progress * progress;
+ case EASE_OUT_CUBIC -> (float) (1 - Math.pow(1 - progress, 3));
+ case EASE_IN_OUT_CUBIC ->
+ progress < 0.5 ? 4 * progress * progress * progress : (float) (1 - Math.pow(-2 * progress + 2, 3) / 2);
+ case EASE_IN_QUART -> progress * progress * progress * progress;
+ case EASE_OUT_QUART -> (float) (1 - Math.pow(1 - progress, 4));
+ case EASE_IN_OUT_QUART ->
+ progress < 0.5 ? 8 * progress * progress * progress * progress : (float) (1 - Math.pow(-2 * progress + 2, 4) / 2);
+ case EASE_IN_QUINT -> progress * progress * progress * progress * progress;
+ case EASE_OUT_QUINT -> (float) (1 - Math.pow(1 - progress, 5));
+ case EASE_IN_OUT_QUINT ->
+ progress < 0.5 ? 16 * progress * progress * progress * progress * progress : (float) (1 - Math.pow(-2 * progress + 2, 5) / 2);
+ case EASE_IN_EXPO -> (float) (progress == 0 ? 0 : Math.pow(2, 10 * progress - 10));
+ case EASE_OUT_EXPO -> (float) (progress == 1 ? 1 : 1 - Math.pow(2, -10 * progress));
+ case EASE_IN_OUT_EXPO -> {
+ if (progress == 0 || progress == 1) yield progress;
+ yield (float) (progress < 0.5
+ ? Math.pow(2, 20 * progress - 10) / 2
+ : (2 - Math.pow(2, -20 * progress + 10)) / 2);
+ }
+ case EASE_IN_CIRC -> (float) (1 - Math.sqrt(1 - Math.pow(progress, 2)));
+ case EASE_OUT_CIRC -> (float) Math.sqrt(1 - Math.pow(progress - 1, 2));
+ case EASE_IN_OUT_CIRC -> progress < 0.5
+ ? (float) ((1 - Math.sqrt(1 - Math.pow(2 * progress, 2))) / 2)
+ : (float) ((Math.sqrt(1 - Math.pow(-2 * progress + 2, 2)) + 1) / 2);
+ case EASE_IN_BACK -> (float) (2.70158 * progress * progress * progress - 1.70158 * progress * progress);
+ case EASE_OUT_BACK -> {
+ float c1 = 1.70158f;
+ float c3 = c1 + 1;
+ yield (float) (1 + c3 * Math.pow(progress - 1, 3) + c1 * Math.pow(progress - 1, 2));
+ }
+ case EASE_IN_OUT_BACK -> {
+ float c1 = 1.70158f;
+ float c2 = c1 * 1.525f;
+ yield (float) (progress < 0.5
+ ? (Math.pow(2 * progress, 2) * ((c2 + 1) * 2 * progress - c2)) / 2
+ : (Math.pow(2 * progress - 2, 2) * ((c2 + 1) * (progress * 2 - 2) + c2) + 2) / 2);
+ }
+ case EASE_IN_ELASTIC -> {
+ float c4 = (float) (2 * Math.PI / 3);
+ yield progress == 0 ? 0 : progress == 1 ? 1 : (float) (-Math.pow(2, 10 * progress - 10) * Math.sin((progress * 10 - 10.75) * c4));
+ }
+ case EASE_OUT_ELASTIC -> {
+ float c4 = (float) (2 * Math.PI / 3);
+ yield progress == 0 ? 0 : progress == 1 ? 1 : (float) (Math.pow(2, -10 * progress) * Math.sin((progress * 10 - 0.75) * c4) + 1);
+ }
+ case EASE_IN_OUT_ELASTIC -> {
+ float c5 = (float) (2 * Math.PI / 4.5);
+ yield progress == 0 ? 0 : progress == 1 ? 1 : progress < 0.5
+ ? (float) (-(Math.pow(2, 20 * progress - 10) * Math.sin((20 * progress - 11.125) * c5)) / 2)
+ : (float) (Math.pow(2, -20 * progress + 10) * Math.sin((20 * progress - 11.125) * c5) / 2 + 1);
+ }
+ case EASE_IN_BOUNCE -> 1 - bounceOut(1 - progress);
+ case EASE_OUT_BOUNCE -> bounceOut(progress);
+ case EASE_IN_OUT_BOUNCE -> progress < 0.5
+ ? (1 - bounceOut(1 - 2 * progress)) / 2
+ : (1 + bounceOut(2 * progress - 1)) / 2;
+ };
+ }
+
+ private static float bounceOut(float progress) {
+ float n1 = 7.5625f;
+ float d1 = 2.75f;
+ if (progress < 1 / d1) {
+ return n1 * progress * progress;
+ } else if (progress < 2 / d1) {
+ return n1 * (progress -= 1.5f / d1) * progress + 0.75f;
+ } else if (progress < 2.5 / d1) {
+ return n1 * (progress -= 2.25f / d1) * progress + 0.9375f;
+ } else {
+ return n1 * (progress -= 2.625f / d1) * progress + 0.984375f;
+ }
+ }
+}
diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/EasingType.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/EasingType.java
new file mode 100644
index 0000000..8316475
--- /dev/null
+++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/EasingType.java
@@ -0,0 +1,35 @@
+package com.tanishisherewith.dynamichud.helpers.animationhelper;
+
+public enum EasingType {
+ LINEAR,
+ EASE_IN_SINE,
+ EASE_OUT_SINE,
+ EASE_IN_OUT_SINE,
+ EASE_IN_QUAD,
+ EASE_OUT_QUAD,
+ EASE_IN_OUT_QUAD,
+ EASE_IN_CUBIC,
+ EASE_OUT_CUBIC,
+ EASE_IN_OUT_CUBIC,
+ EASE_IN_QUART,
+ EASE_OUT_QUART,
+ EASE_IN_OUT_QUART,
+ EASE_IN_QUINT,
+ EASE_OUT_QUINT,
+ EASE_IN_OUT_QUINT,
+ EASE_IN_EXPO,
+ EASE_OUT_EXPO,
+ EASE_IN_OUT_EXPO,
+ EASE_IN_CIRC,
+ EASE_OUT_CIRC,
+ EASE_IN_OUT_CIRC,
+ EASE_IN_BACK,
+ EASE_OUT_BACK,
+ EASE_IN_OUT_BACK,
+ EASE_IN_ELASTIC,
+ EASE_OUT_ELASTIC,
+ EASE_IN_OUT_ELASTIC,
+ EASE_IN_BOUNCE,
+ EASE_OUT_BOUNCE,
+ EASE_IN_OUT_BOUNCE
+}
diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/CompositeAnimation.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/CompositeAnimation.java
new file mode 100644
index 0000000..27f091c
--- /dev/null
+++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/CompositeAnimation.java
@@ -0,0 +1,100 @@
+package com.tanishisherewith.dynamichud.helpers.animationhelper.animations;
+
+import com.tanishisherewith.dynamichud.helpers.animationhelper.Animation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CompositeAnimation extends Animation {
+ private final List children = new ArrayList<>();
+ private final boolean parallel;
+ private int currentChildIndex = 0;
+ private long[] childStartTimes;
+
+ public CompositeAnimation(boolean parallel) {
+ this.parallel = parallel;
+ }
+
+ public CompositeAnimation add(Animation animation) {
+ children.add(animation);
+ return this;
+ }
+
+ @Override
+ public void start() {
+ super.start();
+ if (parallel) {
+ children.forEach(Animation::start);
+ } else {
+ // Calculate total duration as sum of children's durations
+ this.duration = children.stream().mapToLong(a -> a.duration).sum();
+ this.childStartTimes = new long[children.size()];
+ long accumulated = 0;
+ for (int i = 0; i < children.size(); i++) {
+ childStartTimes[i] = accumulated;
+ accumulated += children.get(i).duration;
+ }
+ startChild(0);
+ }
+ }
+
+ private void startChild(int index) {
+ if (index < children.size()) {
+ Animation child = children.get(index);
+ child.start();
+ // Adjust child's start time to match group timeline
+ child.startTime = this.startTime + childStartTimes[index];
+ }
+ }
+
+ @Override
+ protected void applyAnimation(float progress) {
+ if (parallel) {
+ children.forEach(Animation::update);
+ } else {
+ long elapsed = System.currentTimeMillis() - startTime;
+
+ // Find active child
+ for (int i = 0; i < children.size(); i++) {
+ long childDuration = children.get(i).duration;
+ if (elapsed < childStartTimes[i] + childDuration) {
+ if (currentChildIndex != i) {
+ currentChildIndex = i;
+ startChild(i);
+ }
+ children.get(i).update();
+ break;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void stop() {
+ super.stop();
+ children.forEach(Animation::stop);
+ }
+
+ @Override
+ public boolean isFinished() {
+ if (parallel) {
+ return children.stream().allMatch(Animation::isFinished);
+ } else {
+ long elapsed = System.currentTimeMillis() - startTime;
+ return elapsed >= duration;
+ }
+ }
+
+ @Override
+ public void finish() {
+ // Ensure all children finish properly
+ if (!parallel) {
+ children.forEach(child -> {
+ if (!child.isFinished()) {
+ child.finish();
+ }
+ });
+ }
+ super.finish();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/MathAnimations.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/MathAnimations.java
new file mode 100644
index 0000000..6850fff
--- /dev/null
+++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/MathAnimations.java
@@ -0,0 +1,113 @@
+package com.tanishisherewith.dynamichud.helpers.animationhelper.animations;
+
+import com.tanishisherewith.dynamichud.helpers.animationhelper.Easing;
+import com.tanishisherewith.dynamichud.helpers.animationhelper.EasingType;
+import net.minecraft.util.math.Vec2f;
+import net.minecraft.util.math.random.Random;
+
+public class MathAnimations {
+ /// SHAKE: Random offset animation with smooth decay
+ public static float shake(float intensity, float frequency, float decay) {
+ long time = System.currentTimeMillis();
+ return (float) (Math.sin(time * frequency) *
+ Math.exp(-decay * time) * intensity);
+ }
+
+ /// 2D Shake with different X/Y frequencies
+ public static Vec2f shake2D(float intensity, float freqX, float freqY) {
+ return new Vec2f(
+ (float) Math.sin(System.currentTimeMillis() * freqX) * intensity,
+ (float) Math.cos(System.currentTimeMillis() * freqY) * intensity
+ );
+ }
+
+ /// FLICKER: Random flashing effect
+ public static float flicker(float min, float max, float chance) {
+ Random rand = Random.create();
+ return rand.nextFloat() < chance ?
+ min + (max - min) * rand.nextFloat() :
+ max;
+ }
+
+ /// CIRCULAR MOTION: Perfect for rotation/orbital animations
+ public static Vec2f circularMotion(float radius, float speed, float phase) {
+ double angle = Math.toRadians((System.currentTimeMillis() * speed) % 360 + phase);
+ return new Vec2f(
+ (float) (Math.cos(angle) * radius),
+ (float) (Math.sin(angle) * radius)
+ );
+ }
+
+ /// SAWTOOTH WAVE: Linear rise with sudden drop
+ public static float sawtooth(float period, float min, float max) {
+ float phase = (System.currentTimeMillis() % period) / period;
+ return min + (max - min) * phase;
+ }
+
+ /// TRIANGULAR WAVE: Linear rise and fall
+ public static float triangleWave(float period, float min, float max) {
+ float halfPeriod = period / 2;
+ float phase = (System.currentTimeMillis() % period);
+ float value = phase < halfPeriod ?
+ (phase / halfPeriod) :
+ 2 - (phase / halfPeriod);
+ return min + (max - min) * value;
+ }
+
+ /// BOUNCE: Simulates physical bouncing
+ public static float bounce(float dropHeight, float gravity, float dampening) {
+ float t = System.currentTimeMillis() / 1000f;
+ return (float) (dropHeight * Math.abs(Math.sin(t * Math.sqrt(gravity))) *
+ Math.exp(-dampening * t));
+ }
+
+ /// PULSE: Smooth heartbeat-like effect
+ public static float pulse1(float base, float amplitude, float frequency) {
+ return (float) (base + amplitude *
+ (0.5 + 0.5 * Math.sin(System.currentTimeMillis() * frequency)));
+ }
+
+ /// SPIRAL: Circular motion with expanding radius
+ public static Vec2f spiral(float baseRadius, float expansionRate, float speed) {
+ float t = System.currentTimeMillis() / 1000f;
+ return new Vec2f(
+ (float) ((baseRadius + expansionRate * t) * Math.cos(t * speed)),
+ (float) ((baseRadius + expansionRate * t) * Math.sin(t * speed))
+ );
+ }
+
+ /// Continuous pulsating effect using sine wave
+ public static float pulse2(float speed, float min, float max) {
+ return (float) ((Math.sin(System.currentTimeMillis() * speed) + 1) / 2 * (max - min) + min);
+ }
+
+ /// Linear interpolation between values over time
+ public static float lerp(float start, float end, long startTime, float duration) {
+ return lerp(start, end, startTime, duration, EasingType.LINEAR);
+ }
+
+ /// Linear interpolation between values over time with easing
+ public static float lerp(float start, float end, long startTime, float duration, EasingType easing) {
+ float progress = (System.currentTimeMillis() - startTime) / duration;
+ progress = Math.min(1, Math.max(0, progress)); // Clamp 0-1
+ return start + (end - start) * Easing.apply(easing, progress);
+ }
+
+ /// Bouncing animation using quadratic ease-out
+ public static float bounce(float start, float end, long startTime, float duration) {
+ float time = System.currentTimeMillis() - startTime;
+ time /= duration;
+ return end * (1 - (time - 1) * (time - 1)) + start;
+ }
+
+ /// Continuous rotation using modulo
+ public static float continuousRotation(float speed) {
+ return (System.currentTimeMillis() % (360_000 / speed)) * (speed / 1000);
+ }
+
+ /// Elastic wobble effect
+ public static float elasticWobble(float speed, float magnitude) {
+ return (float) (Math.sin(System.currentTimeMillis() * speed) *
+ Math.exp(-0.001 * System.currentTimeMillis()) * magnitude);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/ValueAnimation.java b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/ValueAnimation.java
new file mode 100644
index 0000000..9d19d3b
--- /dev/null
+++ b/src/main/java/com/tanishisherewith/dynamichud/helpers/animationhelper/animations/ValueAnimation.java
@@ -0,0 +1,50 @@
+package com.tanishisherewith.dynamichud.helpers.animationhelper.animations;
+
+import com.tanishisherewith.dynamichud.helpers.animationhelper.Animation;
+import com.tanishisherewith.dynamichud.helpers.animationhelper.AnimationProperty;
+import com.tanishisherewith.dynamichud.helpers.animationhelper.Easing;
+import com.tanishisherewith.dynamichud.helpers.animationhelper.EasingType;
+
+public class ValueAnimation extends Animation {
+ private final AnimationProperty property;
+ private float startValue;
+ private float endValue;
+ private EasingType easing;
+ private float value;
+
+ public ValueAnimation(AnimationProperty property, float start, float end, EasingType easingType) {
+ this.property = property;
+ this.startValue = start;
+ this.endValue = end;
+ this.easing = easingType;
+ }
+
+ public ValueAnimation(AnimationProperty property, float start, float end) {
+ this(property, start, end, EasingType.LINEAR);
+ }
+
+ @Override
+ protected void applyAnimation(float progress) {
+ this.value = startValue + (endValue - startValue) * Easing.apply(easing, progress);
+ property.set(value);
+ }
+
+ public ValueAnimation easing(EasingType easing) {
+ this.easing = easing;
+ return this;
+ }
+
+ public ValueAnimation startValue(float startValue) {
+ this.startValue = startValue;
+ return this;
+ }
+
+ public ValueAnimation endValue(float endValue) {
+ this.endValue = endValue;
+ return this;
+ }
+
+ public float getValue() {
+ return value;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/tanishisherewith/dynamichud/integration/DefaultIntegrationImpl.java b/src/main/java/com/tanishisherewith/dynamichud/integration/DefaultIntegrationImpl.java
new file mode 100644
index 0000000..33ae2b7
--- /dev/null
+++ b/src/main/java/com/tanishisherewith/dynamichud/integration/DefaultIntegrationImpl.java
@@ -0,0 +1,30 @@
+package com.tanishisherewith.dynamichud.integration;
+
+import com.tanishisherewith.dynamichud.widget.WidgetManager;
+import com.tanishisherewith.dynamichud.widgets.GraphWidget;
+import com.tanishisherewith.dynamichud.widgets.ItemWidget;
+import com.tanishisherewith.dynamichud.widgets.TextWidget;
+
+/**
+ * The default implementation for included widgets.
+ */
+public class DefaultIntegrationImpl implements DynamicHudIntegration {
+ @Override
+ public DynamicHudConfigurator configure(DynamicHudConfigurator configurator) {
+ configurator.markAsUtility = true;
+ return configurator;
+ }
+
+ @Override
+ public void init() {
+ }
+
+ @Override
+ public void registerCustomWidgets() {
+ WidgetManager.registerCustomWidgets(
+ TextWidget.DATA,
+ ItemWidget.DATA,
+ GraphWidget.DATA
+ );
+ }
+}
diff --git a/src/main/java/com/tanishisherewith/dynamichud/integration/DynamicHudConfigurator.java b/src/main/java/com/tanishisherewith/dynamichud/integration/DynamicHudConfigurator.java
new file mode 100644
index 0000000..7340598
--- /dev/null
+++ b/src/main/java/com/tanishisherewith/dynamichud/integration/DynamicHudConfigurator.java
@@ -0,0 +1,141 @@
+package com.tanishisherewith.dynamichud.integration;
+
+import com.tanishisherewith.dynamichud.DynamicHUD;
+import com.tanishisherewith.dynamichud.screens.AbstractMoveableScreen;
+import com.tanishisherewith.dynamichud.widget.Widget;
+import com.tanishisherewith.dynamichud.widget.WidgetManager;
+import com.tanishisherewith.dynamichud.widget.WidgetRenderer;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
+import net.fabricmc.fabric.api.event.Event;
+import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
+import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
+import org.jetbrains.annotations.ApiStatus;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import static com.tanishisherewith.dynamichud.integration.IntegrationManager.FILE_MAP;
+
+public class DynamicHudConfigurator {
+ private final List widgets = new ArrayList<>();
+ private WidgetRenderer renderer;
+ private Consumer> saveHandler = widgetsList -> {};
+ private AbstractMoveableScreen screen = null;
+ public boolean markAsUtility = false; // A.k.a we don't want this mod to display a hud.
+
+ public DynamicHudConfigurator addWidget(Widget widget) {
+ this.widgets.add(widget);
+ return this;
+ }
+
+ /**
+ * Configure the existing renderer object with this method
+ */
+ public DynamicHudConfigurator configureRenderer(Consumer wrConsumer) {
+ if (renderer == null) {
+ this.renderer = new WidgetRenderer(widgets);
+ }
+ wrConsumer.accept(renderer);
+ return this;
+ }
+
+ public DynamicHudConfigurator configureRenderer(Consumer wrConsumer, List widgets) {
+ this.renderer = new WidgetRenderer(widgets);
+ wrConsumer.accept(renderer);
+ return this;
+ }
+
+ /**
+ * Override the present widget renderer with your own instance
+ */
+ public DynamicHudConfigurator overrideRenderer(WidgetRenderer renderer) {
+ this.renderer = renderer;
+ return this;
+ }
+
+ /**
+ * Called before saving these widgets
+ */
+ public DynamicHudConfigurator onSave(Consumer> saveHandler) {
+ this.saveHandler = saveHandler;
+ return this;
+ }
+
+ /**
+ * Returns the movable screen for the hud screen.
+ *
+ *
+ * !! Should never be null !!
+ *
+ *
+ */
+ public DynamicHudConfigurator withMoveableScreen(Function screenProvider) {
+ this.screen = screenProvider.apply(this);
+ return this;
+ }
+
+ /**
+ * Batch operation
+ */
+ public DynamicHudConfigurator modifyWidgets(Consumer operation) {
+ widgets.forEach(operation);
+ return this;
+ }
+
+ public List getWidgets() {
+ return Collections.unmodifiableList(widgets);
+ }
+
+ public WidgetRenderer getRenderer() {
+ return renderer;
+ }
+
+ public final Consumer> getSaveHandler() {
+ return saveHandler;
+ }
+
+ public final AbstractMoveableScreen getMovableScreen() {
+ return screen;
+ }
+
+ /**
+ * Internal method to save these widgets using fabric API events. Should not be called anywhere else except when loading the DHIntegration on startup.
+ */
+ @ApiStatus.Internal
+ public void setupSaveEvents(File widgetsFile) {
+ /* === Saving === */
+ // Each mod is hooked to the fabric's event system to save its widget.
+
+ //When a player exits a world (SinglePlayer worlds) or a server stops
+ ServerLifecycleEvents.SERVER_STOPPING.register(server -> saveWidgetsSafely(widgetsFile, FILE_MAP.get(widgetsFile.getName())));
+
+ // When a resource pack is reloaded.
+ ServerLifecycleEvents.END_DATA_PACK_RELOAD.register((server, resourceManager, s) -> saveWidgetsSafely(widgetsFile, FILE_MAP.get(widgetsFile.getName())));
+
+ //When player disconnects
+ ServerPlayConnectionEvents.DISCONNECT.register((handler, packetSender) -> saveWidgetsSafely(widgetsFile, FILE_MAP.get(widgetsFile.getName())));
+
+ //When minecraft closes
+ ClientLifecycleEvents.CLIENT_STOPPING.register((mc)-> saveWidgetsSafely(widgetsFile, FILE_MAP.get(widgetsFile.getName())));
+ }
+
+ @ApiStatus.Internal
+ public final void registerWidgets() {
+ widgets.forEach(WidgetManager::addWidget);
+ }
+
+ private void saveWidgetsSafely(File widgetsFile, List widgets) {
+ try {
+ this.saveHandler.accept(widgets);
+ WidgetManager.saveWidgets(widgetsFile, widgets);
+ } catch (Throwable e) {
+ DynamicHUD.logger.error("Failed to save widgets. Widgets passed: {}", widgets);
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/com/tanishisherewith/dynamichud/DynamicHudIntegration.java b/src/main/java/com/tanishisherewith/dynamichud/integration/DynamicHudIntegration.java
similarity index 64%
rename from src/main/java/com/tanishisherewith/dynamichud/DynamicHudIntegration.java
rename to src/main/java/com/tanishisherewith/dynamichud/integration/DynamicHudIntegration.java
index 484f981..bcc32de 100644
--- a/src/main/java/com/tanishisherewith/dynamichud/DynamicHudIntegration.java
+++ b/src/main/java/com/tanishisherewith/dynamichud/integration/DynamicHudIntegration.java
@@ -1,5 +1,6 @@
-package com.tanishisherewith.dynamichud;
+package com.tanishisherewith.dynamichud.integration;
+import com.tanishisherewith.dynamichud.IntegrationTest;
import com.tanishisherewith.dynamichud.screens.AbstractMoveableScreen;
import com.tanishisherewith.dynamichud.widget.WidgetData;
import com.tanishisherewith.dynamichud.widget.WidgetManager;
@@ -9,34 +10,15 @@
import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.util.InputUtil;
import org.lwjgl.glfw.GLFW;
-import org.lwjgl.system.NonnullDefault;
import java.io.File;
/**
* This interface provides methods for integrating DynamicHud into a mod.
+ * @see IntegrationTest
+ * @see DefaultIntegrationImpl
*/
public interface DynamicHudIntegration {
- /**
- * The category for the key binding.
- */
- String KEYBIND_CATEGORY = "DynamicHud";
-
- /**
- * The translation key for the editor screen.
- */
- String TRANSLATION_KEY = "DynamicHud Editor Screen";
-
- /**
- * The input type for the key binding.
- */
- InputUtil.Type INPUT_TYPE = InputUtil.Type.KEYSYM;
-
- /**
- * The key code for the key binding.
- */
- int KEY = GLFW.GLFW_KEY_RIGHT_SHIFT;
-
/**
* The key binding for opening the editor screen.
*/
@@ -62,6 +44,14 @@ public interface DynamicHudIntegration {
*/
File WIDGETS_FILE = new File(FILE_DIRECTORY, FILENAME);
+
+ /**
+ * Entry point for configuring DynamicHUD integration.
+ *
+ * @param configurator Configuration context
+ */
+ DynamicHudConfigurator configure(DynamicHudConfigurator configurator);
+
/**
* Initializes the DynamicHud integration.
*
@@ -71,9 +61,10 @@ public interface DynamicHudIntegration {
void init();
/**
- * To be used to add widgets using {@link WidgetManager}.
+ * This method is called after widgets from the widget file have been loaded successfully and added to the renderer.
*/
- void addWidgets();
+ default void postWidgetLoading(WidgetRenderer renderer) {
+ }
/**
* To register custom widgets. This method can be overridden by implementations.
@@ -85,22 +76,13 @@ public interface DynamicHudIntegration {
* WidgetManager.registerCustomWidget(TextWidget.DATA);
* }
*
- *
+ *
* Custom widgets can be registered in any method in the interface
* but to avoid any errors and mishaps it is recommended you add them here
*/
default void registerCustomWidgets() {
}
- /**
- * Performs any necessary initialization after the widgets have been added. This method can be overridden by implementations.
- *
- * Suggested to be used to initialize a {@link WidgetRenderer} object with the added widgets.
- *
- */
- default void initAfter() {
- }
-
/**
* Returns the file where widgets are to be saved and loaded from.
*
@@ -118,26 +100,4 @@ default File getWidgetsFile() {
default KeyBinding getKeyBind() {
return EDITOR_SCREEN_KEY_BINDING;
}
-
- /**
- * Returns the movable screen for the DynamicHud.
- *
- *
- * !! Should never be null !!
- *
- *
- *
- * @return The movable screen.
- */
- AbstractMoveableScreen getMovableScreen();
-
- /**
- * To return a {@link WidgetRenderer} object.
- * By default, it returns a widget renderer consisting of all widgets in the {@link WidgetManager}
- *
- * @return The widget renderer.
- */
- default WidgetRenderer getWidgetRenderer() {
- return new WidgetRenderer(WidgetManager.getWidgets());
- }
}
diff --git a/src/main/java/com/tanishisherewith/dynamichud/integration/IntegrationManager.java b/src/main/java/com/tanishisherewith/dynamichud/integration/IntegrationManager.java
new file mode 100644
index 0000000..485a616
--- /dev/null
+++ b/src/main/java/com/tanishisherewith/dynamichud/integration/IntegrationManager.java
@@ -0,0 +1,221 @@
+package com.tanishisherewith.dynamichud.integration;
+
+import com.tanishisherewith.dynamichud.DynamicHUD;
+import com.tanishisherewith.dynamichud.internal.ModError;
+import com.tanishisherewith.dynamichud.internal.WarningScreen;
+import com.tanishisherewith.dynamichud.screens.AbstractMoveableScreen;
+import com.tanishisherewith.dynamichud.utils.BooleanPool;
+import com.tanishisherewith.dynamichud.widget.Widget;
+import com.tanishisherewith.dynamichud.widget.WidgetManager;
+import com.tanishisherewith.dynamichud.widget.WidgetRenderer;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.loader.api.FabricLoader;
+import net.fabricmc.loader.api.ModContainer;
+import net.fabricmc.loader.api.entrypoint.EntrypointContainer;
+import net.fabricmc.loader.api.metadata.ModMetadata;
+import net.minecraft.client.gui.screen.TitleScreen;
+import net.minecraft.client.option.KeyBinding;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+import static com.tanishisherewith.dynamichud.DynamicHUD.printInfo;
+
+public final class IntegrationManager {
+
+ /**
+ * This is a map to store the list of widgets for each widget file to be saved.
+ *
+ * Allows saving widgets across different mods with same save file name.
+ */
+ public static final Map> FILE_MAP = new HashMap<>();
+ private static final List widgetRenderers = new ArrayList<>();
+
+ private static boolean enableTestIntegration = false;
+
+
+ public static void addWidgetRenderer(WidgetRenderer widgetRenderer) {
+ widgetRenderers.add(widgetRenderer);
+ }
+
+ public static List getWidgetRenderers() {
+ return widgetRenderers;
+ }
+
+ /**
+ * Opens the MovableScreen when the specified key is pressed.
+ *
+ * @param key The key to listen for
+ * @param screen The AbstractMoveableScreen instance to use to set the screen
+ */
+ public static void openScreen(KeyBinding key, AbstractMoveableScreen screen) {
+ if (key.wasPressed()) {
+ DynamicHUD.MC.setScreen(screen);
+ }
+ }
+
+ private static void checkToEnableTestIntegration() {
+ String[] args = FabricLoader.getInstance().getLaunchArguments(true);
+ for (int i = 0; i < args.length; i++) {
+ if (args[i].equals("--dynamicHudTest") && i + 1 < args.length) {
+ enableTestIntegration = Boolean.parseBoolean(args[i + 1]);
+ break;
+ }
+ }
+ }
+
+ public static void integrate() {
+ checkToEnableTestIntegration();
+
+ printInfo("Integrating mods...");
+
+ var integrations = new ArrayList<>(getRegisteredIntegrations());
+
+ if (enableTestIntegration) {
+ EntrypointContainer testIntegration = getTestIntegration();
+ if (testIntegration != null) {
+ integrations.add(testIntegration);
+ printInfo("Test integration enabled and loaded successfully.");
+ }
+ }
+
+ List bad_implementations = new ArrayList<>();
+
+ integrations.forEach(container -> {
+ //Register custom widget data's by WidgetManager.registerCustomWidgets() first for every entrypoint
+ container.getEntrypoint().registerCustomWidgets();
+ });
+
+ for (var entrypoint : integrations) {
+ ModMetadata metadata = entrypoint.getProvider().getMetadata();
+ String modId = metadata.getId();
+
+ AbstractMoveableScreen screen;
+ KeyBinding binding;
+ WidgetRenderer widgetRenderer;
+ File widgetsFile;
+ try {
+ DynamicHudIntegration DHIntegration = entrypoint.getEntrypoint();
+
+ //Calls the init method
+ DHIntegration.init();
+
+ DynamicHudConfigurator configurator = DHIntegration.configure(new DynamicHudConfigurator());
+
+ if (configurator.markAsUtility) {
+ printInfo(String.format("Supported utility mod with id %s was found!", modId));
+ continue;
+ }
+
+ printInfo(String.format("Supported mod with id %s was found!", modId));
+
+ //Gets the widget file to save and load the widgets from
+ widgetsFile = DHIntegration.getWidgetsFile();
+
+ // Adds / loads widgets from file
+ if (WidgetManager.doesWidgetFileExist(widgetsFile)) {
+ List widgets = WidgetManager.loadWidgets(widgetsFile);
+ configurator.configureRenderer(renderer -> renderer.clearAndAdd(widgets));
+ DHIntegration.postWidgetLoading(configurator.getRenderer());
+ } else {
+ configurator.registerWidgets();
+ }
+
+
+ // Get the instance of AbstractMoveableScreen
+ screen = Objects.requireNonNull(configurator.getMovableScreen(), "AbstractMovableScreen instance should not be null!");
+
+ // Get the keybind to open the screen instance
+ binding = DHIntegration.getKeyBind();
+
+ //WidgetRenderer with widgets instance
+ widgetRenderer = configurator.getRenderer();
+
+ addWidgetRenderer(widgetRenderer);
+
+ updateFileMap(widgetsFile.getName(), widgetRenderer.getWidgets());
+
+ //Register events for rendering, saving, loading, and opening the hudEditor
+ ClientTickEvents.START_CLIENT_TICK.register((client) -> openScreen(binding, screen));
+
+ configurator.setupSaveEvents(widgetsFile);
+
+ printInfo(String.format("Integration of mod %s was successful", modId));
+ } catch (Throwable e) {
+ if (e instanceof IOException) {
+ DynamicHUD.logger.warn("An IO error has occurred while loading widgets of mod {}", modId, e);
+ } else {
+ DynamicHUD.logger.error("Mod {} has improper implementation of DynamicHUD", modId, e);
+ }
+ bad_implementations.add(new ModError(modId, e.getMessage().trim()));
+ }
+ }
+ printInfo("(DynamicHUD) Integration of supported mods was successful");
+
+
+ // Sheesh
+ if (!bad_implementations.isEmpty()) {
+ BooleanPool.put("WarningScreenFlag", false);
+
+ ClientTickEvents.START_CLIENT_TICK.register((client) -> {
+ if (BooleanPool.get("WarningScreenFlag")) return;
+
+ if (DynamicHUD.MC.currentScreen instanceof TitleScreen) {
+ DynamicHUD.MC.setScreen(new WarningScreen(bad_implementations));
+ BooleanPool.put("WarningScreenFlag", true);
+ }
+ });
+ }
+
+ }
+
+ private static void updateFileMap(String fileName, List widgets) {
+ FILE_MAP.compute(fileName, (k, v) -> {
+ // Concat existing and the new widget list.
+ if (v == null) return new ArrayList<>(widgets);
+ v.addAll(widgets);
+ return v;
+ });
+ }
+
+ private static List> getRegisteredIntegrations() {
+ return new ArrayList<>(FabricLoader.getInstance()
+ .getEntrypointContainers("dynamicHud", DynamicHudIntegration.class));
+ }
+
+ /**
+ * This makes it so that if minecraft is launched with the program arguments
+ *
+ * {@code --dynamicHudTest true}
+ *
+ * then it will
+ * load the {@link com.tanishisherewith.dynamichud.IntegrationTest} class as an entrypoint, eliminating any errors due to human incapacity of
+ * adding/removing a single line from the `fabric.mod.json`
+ */
+ private static EntrypointContainer getTestIntegration() {
+ DynamicHudIntegration testIntegration;
+ try {
+ Class> testClass = Class.forName("com.tanishisherewith.dynamichud.IntegrationTest");
+ testIntegration = (DynamicHudIntegration) testClass.getDeclaredConstructor().newInstance();
+ } catch (ClassNotFoundException e) {
+ DynamicHUD.logger.info("DynamicHudTest class not found. Skipping test integration.");
+ return null;
+ } catch (Exception e) {
+ DynamicHUD.logger.error("Error instantiating DynamicHudTest", e);
+ return null;
+ }
+
+ return new EntrypointContainer<>() {
+ @Override
+ public DynamicHudIntegration getEntrypoint() {
+ return testIntegration;
+ }
+
+ @Override
+ public ModContainer getProvider() {
+ return FabricLoader.getInstance().getModContainer(DynamicHUD.MOD_ID).orElseThrow();
+ }
+ };
+ }
+}
diff --git a/src/main/java/com/tanishisherewith/dynamichud/internal/ModError.java b/src/main/java/com/tanishisherewith/dynamichud/internal/ModError.java
new file mode 100644
index 0000000..57ab93d
--- /dev/null
+++ b/src/main/java/com/tanishisherewith/dynamichud/internal/ModError.java
@@ -0,0 +1,4 @@
+package com.tanishisherewith.dynamichud.internal;
+
+public record ModError(String modName, String errorMessage) {
+}
diff --git a/src/main/java/com/tanishisherewith/dynamichud/internal/System.java b/src/main/java/com/tanishisherewith/dynamichud/internal/System.java
new file mode 100644
index 0000000..e3ab2ab
--- /dev/null
+++ b/src/main/java/com/tanishisherewith/dynamichud/internal/System.java
@@ -0,0 +1,29 @@
+package com.tanishisherewith.dynamichud.internal;
+
+import java.util.*;
+
+public abstract class System {
+ // A map to store all instances of DynamicValueRegistry by modId
+ private static final Map, Map>> instanceRegistry = new HashMap<>();
+
+ public static void registerInstance(Object instance, String modId) {
+ Class> cls = instance.getClass();
+ Map> modMap = instanceRegistry.computeIfAbsent(cls, k -> new HashMap<>());
+ Set
*/
-public class DynamicValueRegistry extends System {
- /**
- * A map that holds the global registry of suppliers.
- *
- * @see #localRegistry
- */
- private static final Map> globalRegistry = new HashMap<>();
+public class DynamicValueRegistry {
+ private static final Map REGISTRY_BY_ID = new HashMap<>();
+ private static final Map> GLOBAL_REGISTRY = new HashMap<>();
+ public static final String GLOBAL_ID = "global";
+
+ private final String id; // Unique identifier for this registry instance
+ private final Map> localRegistry = new HashMap<>();
/**
- * A map that holds the local registry of suppliers.
+ * Constructor for a local registry with a unique ID.
*
- * @see #globalRegistry
+ * @param modId The mod ID or unique identifier for grouping registries.
+ * @param registryId A unique ID for this registry instance.
*/
- private final Map> localRegistry = new HashMap<>();
+ public DynamicValueRegistry(String modId, String registryId) {
+ this.id = registryId.trim();
+ System.registerInstance(this, modId);
+ REGISTRY_BY_ID.put(this.id, this);
+ }
/**
- * Constructor for the DynamicValueRegistry class.
+ * Constructor for a local registry using with registryId as modID.
*
- * @param modId The ID of the mod for which this registry is being created. Doesn't need to be modId, it can simply be used as a standard unique identifier string.
+ * @param modId The mod ID or unique identifier for grouping registries.
*/
public DynamicValueRegistry(String modId) {
- super(modId);
- instances.computeIfAbsent(modId, k -> new ArrayList<>()).add(this);
+ this.id = modId;
+ System.registerInstance(this, modId);
+ REGISTRY_BY_ID.put(modId, this);
}
/**
* Registers a supplier in the global registry.
*
- * @param key The key under which the supplier is to be registered.
- * @param supplier The supplier to be registered.
+ * @param key The key for the supplier.
+ * @param supplier The supplier providing values of type T.
*/
- public static void registerGlobal(String key, Supplier> supplier) {
- globalRegistry.put(key, supplier);
+ public static void registerGlobal(String key, Supplier supplier) {
+ GLOBAL_REGISTRY.put(key, supplier);
}
/**
* Retrieves a supplier from the global registry.
*
- * @param key The key of the supplier to be retrieved.
- * @return The supplier registered under the given key, or null if no such supplier exists.
+ * @param key The key of the supplier.
+ * @return The supplier, or null if not found.
*/
public static Supplier> getGlobal(String key) {
- return globalRegistry.get(key);
+ return GLOBAL_REGISTRY.get(key);
}
/**
* Registers a supplier in the local registry.
*
- * @param key The key under which the supplier is to be registered.
- * @param supplier The supplier to be registered.
+ * @param key The key for the supplier.
+ * @param supplier The supplier providing values of type T.
*/
public void registerLocal(String key, Supplier> supplier) {
localRegistry.put(key, supplier);
}
/**
- * Retrieves a supplier from the local registry, falling back to the global registry if necessary.
+ * Retrieves a supplier from the local or global registry.
*
- * @param key The key of the supplier to be retrieved.
- * @return The supplier registered under the given key, or null if no such supplier exists.
+ * @param key The key of the supplier.
+ * @return The supplier, or null if not found.
*/
public Supplier> get(String key) {
- // First, try to get the supplier from the local registry
- Supplier> supplier = localRegistry.get(key);
+ return localRegistry.getOrDefault(key, null);
+ }
+
+ /**
+ * Gets the registry instance by its unique ID.
+ *
+ * @param registryId The unique ID of the registry.
+ * @return The registry instance, or null if not found.
+ */
+ public static DynamicValueRegistry getById(String registryId) {
+ return REGISTRY_BY_ID.get(registryId);
+ }
+
+ /**
+ * Gets the registry instance by its unique ID but throws an error if the instance is not present
+ *
+ * @param registryId The unique ID of the registry.
+ * @return The registry instance, or null if not found.
+ * @throws IllegalStateException If a registry for the id was not found
+ */
+ public static DynamicValueRegistry getByIdSafe(String registryId) {
+ if (!REGISTRY_BY_ID.containsKey(registryId)) {
+ throw new IllegalStateException("DynamicValueRegistry for id: " + registryId + " not found");
+ }
+ return REGISTRY_BY_ID.get(registryId);
+ }
+
+ /**
+ * @param registryID the registry id
+ * @return whether the given id matches the global registry id or not
+ */
+ public static boolean isGlobal(String registryID) {
+ return registryID.equals(GLOBAL_ID);
+ }
- // If the supplier is not in the local registry, try the global registry
- if (supplier == null) {
- supplier = globalRegistry.get(key);
+ /**
+ * Directly get the supplier for a given key and registry id
+ *
+ * @param registryID The registry ID
+ * @param key the registry key
+ * @return supplier as returned by the registry with the given key
+ */
+ public static Supplier> getValue(String registryID, String key) {
+ if (registryID.isEmpty() || key.isEmpty()) throw new IllegalArgumentException();
+
+ if (registryID.equals(GLOBAL_ID)) {
+ return getGlobal(key);
}
+ return getByIdSafe(registryID).get(key);
+ }
- return supplier;
+ /**
+ * Retrieves all registry instances for a mod ID.
+ *
+ * @param modId The mod ID.
+ * @return A list of registries for the mod.
+ */
+ public static List getInstances(String modId) {
+ return System.getInstances(DynamicValueRegistry.class, modId);
+ }
+
+ /**
+ * Removes a supplier from the global registry.
+ *
+ * @param key The key of the supplier.
+ */
+ public static void removeGlobal(String key) {
+ GLOBAL_REGISTRY.remove(key);
+ }
+
+ /**
+ * Removes a supplier from the local registry.
+ *
+ * @param key The key of the supplier.
+ */
+ public void removeLocal(String key) {
+ localRegistry.remove(key);
}
/**
- * Sets the local registry to the given map.
+ * Gets the unique ID of this registry.
*
- * @param map The map to be set as the local registry.
+ * @return The registry ID.
*/
- public void setLocalRegistry(Map> map) {
- localRegistry.putAll(map);
+ public String getId() {
+ return id;
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/Input.java b/src/main/java/com/tanishisherewith/dynamichud/utils/Input.java
new file mode 100644
index 0000000..2e621d7
--- /dev/null
+++ b/src/main/java/com/tanishisherewith/dynamichud/utils/Input.java
@@ -0,0 +1,22 @@
+package com.tanishisherewith.dynamichud.utils;
+
+
+public interface Input {
+ boolean mouseClicked(double mouseX, double mouseY, int button);
+
+ boolean mouseReleased(double mouseX, double mouseY, int button);
+
+ boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY);
+
+ void keyPressed(int key, int scanCode, int modifiers);
+
+ void keyReleased(int key, int scanCode, int modifiers);
+
+ void mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount);
+
+ default boolean isMouseOver(double mouseX, double mouseY, double x, double y, double width, double height) {
+ return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height;
+ }
+
+ void charTyped(char c, int modifiers);
+}
diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/System.java b/src/main/java/com/tanishisherewith/dynamichud/utils/System.java
deleted file mode 100644
index 3cbc6a0..0000000
--- a/src/main/java/com/tanishisherewith/dynamichud/utils/System.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.tanishisherewith.dynamichud.utils;
-
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-public abstract class System {
- // A map to store all instances of DynamicValueRegistry by modId
- protected static final Map> instances = new ConcurrentHashMap<>();
- protected final String modId;
-
- public System(String modId) {
- this.modId = modId;
- }
-
- public static List getInstances(String modId) {
- return instances.get(modId);
- }
-
- public String getModId() {
- return modId;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/Util.java b/src/main/java/com/tanishisherewith/dynamichud/utils/Util.java
index b7f2ccc..4210d61 100644
--- a/src/main/java/com/tanishisherewith/dynamichud/utils/Util.java
+++ b/src/main/java/com/tanishisherewith/dynamichud/utils/Util.java
@@ -26,4 +26,13 @@ public enum Quadrant {
UPPER_LEFT, UPPER_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT
}
+ public static boolean errorIfTrue(boolean expression, String message, Object... objects) {
+ if (!expression) DynamicHUD.logger.error(message, objects);
+ return expression;
+ }
+
+ public static boolean warnIfTrue(boolean expression, String message, Object... objects) {
+ if (expression) DynamicHUD.logger.warn(message, objects);
+ return expression;
+ }
}
diff --git a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenu.java b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenu.java
index 688d7b8..48a2cf6 100644
--- a/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenu.java
+++ b/src/main/java/com/tanishisherewith/dynamichud/utils/contextmenu/ContextMenu.java
@@ -1,90 +1,130 @@
package com.tanishisherewith.dynamichud.utils.contextmenu;
+import com.tanishisherewith.dynamichud.DynamicHUD;
import com.tanishisherewith.dynamichud.helpers.DrawHelper;
+import com.tanishisherewith.dynamichud.internal.System;
+import com.tanishisherewith.dynamichud.utils.Input;
+import com.tanishisherewith.dynamichud.utils.contextmenu.contextmenuscreen.ContextMenuScreenFactory;
+import com.tanishisherewith.dynamichud.utils.contextmenu.contextmenuscreen.ContextMenuScreenRegistry;
+import com.tanishisherewith.dynamichud.utils.contextmenu.contextmenuscreen.DefaultContextMenuScreenFactory;
+import com.tanishisherewith.dynamichud.utils.contextmenu.options.Option;
+import com.tanishisherewith.dynamichud.widget.WidgetBox;
import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.util.math.MathHelper;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.lwjgl.glfw.GLFW;
import java.awt.*;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.Objects;
-public class ContextMenu {
- private final List