aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder
diff options
context:
space:
mode:
authorYasin <a.piri@hotmail.de>2023-10-09 12:58:02 +0200
committerYasin <a.piri@hotmail.de>2023-10-09 12:58:02 +0200
commitbd3f0329d0e391bd84b5f9e3ff207d9dd9815853 (patch)
tree2fd1d1ef625f57acc2e4916c967d8d2393844798 /src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder
parent2315b90da8117f28f66348927afdb621ee4fc815 (diff)
downloadSkyblocker-bd3f0329d0e391bd84b5f9e3ff207d9dd9815853.tar.gz
Skyblocker-bd3f0329d0e391bd84b5f9e3ff207d9dd9815853.tar.bz2
Skyblocker-bd3f0329d0e391bd84b5f9e3ff207d9dd9815853.zip
new pr because fixing merge conflict would take too long
Diffstat (limited to 'src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder')
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenBuilder.java179
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenMaster.java144
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/AlignStage.java83
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/CollideStage.java153
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/PipelineStage.java14
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/PlaceStage.java94
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/StackStage.java114
7 files changed, 781 insertions, 0 deletions
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenBuilder.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenBuilder.java
new file mode 100644
index 00000000..ceeaa365
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenBuilder.java
@@ -0,0 +1,179 @@
+package de.hysky.skyblocker.skyblock.tabhud.screenbuilder;
+
+import java.io.BufferedReader;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.NoSuchElementException;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.Widget;
+import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.AlignStage;
+import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.CollideStage;
+import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.PipelineStage;
+import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.PlaceStage;
+import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline.StackStage;
+import de.hysky.skyblocker.skyblock.tabhud.widget.DungeonPlayerWidget;
+import de.hysky.skyblocker.skyblock.tabhud.widget.ErrorWidget;
+import de.hysky.skyblocker.skyblock.tabhud.widget.EventWidget;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.util.Identifier;
+
+public class ScreenBuilder {
+
+ // layout pipeline
+ private final ArrayList<PipelineStage> layoutPipeline = new ArrayList<>();
+
+ // all widget instances this builder knows
+ private final ArrayList<Widget> instances = new ArrayList<>();
+ // maps alias -> widget instance
+ private final HashMap<String, Widget> objectMap = new HashMap<>();
+
+ private final String builderName;
+
+ /**
+ * Create a ScreenBuilder from a json.
+ */
+ public ScreenBuilder(Identifier ident) {
+
+ try (BufferedReader reader = MinecraftClient.getInstance().getResourceManager().openAsReader(ident)) {
+ this.builderName = ident.getPath();
+
+ JsonObject json = JsonParser.parseReader(reader).getAsJsonObject();
+
+ JsonArray widgets = json.getAsJsonArray("widgets");
+ JsonArray layout = json.getAsJsonArray("layout");
+
+ for (JsonElement w : widgets) {
+ JsonObject widget = w.getAsJsonObject();
+ String name = widget.get("name").getAsString();
+ String alias = widget.get("alias").getAsString();
+
+ Widget wid = instanceFrom(name, widget);
+ objectMap.put(alias, wid);
+ instances.add(wid);
+ }
+
+ for (JsonElement l : layout) {
+ PipelineStage ps = createStage(l.getAsJsonObject());
+ layoutPipeline.add(ps);
+ }
+ } catch (Exception ex) {
+ // rethrow as unchecked exception so that I don't have to catch anything in the ScreenMaster
+ throw new IllegalStateException("Failed to load file " + ident + ". Reason: " + ex.getMessage());
+ }
+ }
+
+ /**
+ * Try to find a class in the widget package that has the supplied name and
+ * call it's constructor. Manual work is required if the class has arguments.
+ */
+ public Widget instanceFrom(String name, JsonObject widget) {
+
+ // do widgets that require args the normal way
+ JsonElement arg;
+ switch (name) {
+ case "EventWidget" -> {
+ return new EventWidget(widget.get("inGarden").getAsBoolean());
+ }
+ case "DungeonPlayerWidget" -> {
+ return new DungeonPlayerWidget(widget.get("player").getAsInt());
+ }
+ case "ErrorWidget" -> {
+ arg = widget.get("text");
+ if (arg == null) {
+ return new ErrorWidget();
+ } else {
+ return new ErrorWidget(arg.getAsString());
+ }
+ }
+ case "Widget" ->
+ // clown case sanity check. don't instantiate the superclass >:|
+ throw new NoSuchElementException(builderName + "[ERROR]: No such Widget type \"Widget\"!");
+ }
+
+ // reflect something together for the "normal" ones.
+
+ // list all packages that might contain widget classes
+ // using Package isn't reliable, as some classes might not be loaded yet,
+ // causing the packages not to show.
+ String packbase = "de.hysky.skyblocker.skyblock.tabhud.widget";
+ String[] packnames = {
+ packbase,
+ packbase + ".rift"
+ };
+
+ // construct the full class name and try to load.
+ Class<?> clazz = null;
+ for (String pn : packnames) {
+ try {
+ clazz = Class.forName(pn + "." + name);
+ } catch (LinkageError | ClassNotFoundException ex) {
+ continue;
+ }
+ }
+
+ // load failed.
+ if (clazz == null) {
+ throw new NoSuchElementException(builderName + "/[ERROR]: No such Widget type \"" + name + "\"!");
+ }
+
+ // return instance of that class.
+ try {
+ Constructor<?> ctor = clazz.getConstructor();
+ return (Widget) ctor.newInstance();
+ } catch (NoSuchMethodException | InstantiationException | IllegalAccessException
+ | IllegalArgumentException | InvocationTargetException | SecurityException ex) {
+ throw new IllegalStateException(builderName + "/" + name + ": Internal error...");
+ }
+ }
+
+ /**
+ * Create a PipelineStage from a json object.
+ */
+ public PipelineStage createStage(JsonObject descr) throws NoSuchElementException {
+
+ String op = descr.get("op").getAsString();
+
+ return switch (op) {
+ case "place" -> new PlaceStage(this, descr);
+ case "stack" -> new StackStage(this, descr);
+ case "align" -> new AlignStage(this, descr);
+ case "collideAgainst" -> new CollideStage(this, descr);
+ default -> throw new NoSuchElementException("No such op " + op + " as requested by " + this.builderName);
+ };
+ }
+
+ /**
+ * Lookup Widget instance from alias name
+ */
+ public Widget getInstance(String name) {
+ if (!this.objectMap.containsKey(name)) {
+ throw new NoSuchElementException("No widget with alias " + name + " in screen " + builderName);
+ }
+ return this.objectMap.get(name);
+ }
+
+ /**
+ * Run the pipeline to build a Screen
+ */
+ public void run(DrawContext context, int screenW, int screenH) {
+
+ for (Widget w : instances) {
+ w.update();
+ }
+ for (PipelineStage ps : layoutPipeline) {
+ ps.run(screenW, screenH);
+ }
+ for (Widget w : instances) {
+ w.render(context);
+ }
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenMaster.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenMaster.java
new file mode 100644
index 00000000..210d8001
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/ScreenMaster.java
@@ -0,0 +1,144 @@
+package de.hysky.skyblocker.skyblock.tabhud.screenbuilder;
+
+import java.io.BufferedReader;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import de.hysky.skyblocker.skyblock.tabhud.TabHud;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerLocator;
+import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
+import net.fabricmc.fabric.api.resource.ResourcePackActivationType;
+import net.fabricmc.fabric.api.resource.SimpleSynchronousResourceReloadListener;
+import net.fabricmc.loader.api.FabricLoader;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.resource.Resource;
+import net.minecraft.resource.ResourceManager;
+import net.minecraft.resource.ResourceType;
+import net.minecraft.util.Identifier;
+
+public class ScreenMaster {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger("skyblocker");
+
+ private static final int VERSION = 1;
+
+ private static final HashMap<String, ScreenBuilder> standardMap = new HashMap<>();
+ private static final HashMap<String, ScreenBuilder> screenAMap = new HashMap<>();
+ private static final HashMap<String, ScreenBuilder> screenBMap = new HashMap<>();
+
+ /**
+ * Load a screen mapping from an identifier
+ */
+ public static void load(Identifier ident) {
+
+ String path = ident.getPath();
+ String[] parts = path.split("/");
+ String screenType = parts[parts.length - 2];
+ String location = parts[parts.length - 1];
+ location = location.replace(".json", "");
+
+ ScreenBuilder sb = new ScreenBuilder(ident);
+ switch (screenType) {
+ case "standard" -> standardMap.put(location, sb);
+ case "screen_a" -> screenAMap.put(location, sb);
+ case "screen_b" -> screenBMap.put(location, sb);
+ }
+ }
+
+ /**
+ * Top level render method.
+ * Calls the appropriate ScreenBuilder with the screen's dimensions
+ */
+ public static void render(DrawContext context, int w, int h) {
+ String location = PlayerLocator.getPlayerLocation().internal;
+ HashMap<String, ScreenBuilder> lookup;
+ if (TabHud.toggleA.isPressed()) {
+ lookup = screenAMap;
+ } else if (TabHud.toggleB.isPressed()) {
+ lookup = screenBMap;
+ } else {
+ lookup = standardMap;
+ }
+
+ ScreenBuilder sb = lookup.get(location);
+ // seems suboptimal, maybe load the default first into all possible values
+ // and then override?
+ if (sb == null) {
+ sb = lookup.get("default");
+ }
+
+ sb.run(context, w, h);
+
+ }
+
+ public static void init() {
+
+ // WHY MUST IT ALWAYS BE SUCH NESTED GARBAGE MINECRAFT KEEP THAT IN DFU FFS
+
+ FabricLoader.getInstance()
+ .getModContainer("skyblocker")
+ .ifPresent(container -> ResourceManagerHelper.registerBuiltinResourcePack(
+ new Identifier("skyblocker", "top_aligned"),
+ container,
+ ResourcePackActivationType.NORMAL));
+
+ ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(
+ // ...why are we instantiating an interface again?
+ new SimpleSynchronousResourceReloadListener() {
+ @Override
+ public Identifier getFabricId() {
+ return new Identifier("skyblocker", "tabhud");
+ }
+
+ @Override
+ public void reload(ResourceManager manager) {
+
+ standardMap.clear();
+ screenAMap.clear();
+ screenBMap.clear();
+
+ int excnt = 0;
+
+ for (Map.Entry<Identifier, Resource> entry : manager
+ .findResources("tabhud", path -> path.getPath().endsWith("version.json"))
+ .entrySet()) {
+
+ try (BufferedReader reader = MinecraftClient.getInstance().getResourceManager()
+ .openAsReader(entry.getKey())) {
+ JsonObject json = JsonParser.parseReader(reader).getAsJsonObject();
+ if (json.get("format_version").getAsInt() != VERSION) {
+ throw new IllegalStateException(String.format("Resource pack isn't compatible! Expected version %d, got %d", VERSION, json.get("format_version").getAsInt()));
+ }
+
+ } catch (Exception ex) {
+ throw new IllegalStateException(
+ "Rejected this resource pack. Reason: " + ex.getMessage());
+ }
+ }
+
+ for (Map.Entry<Identifier, Resource> entry : manager
+ .findResources("tabhud", path -> path.getPath().endsWith(".json") && !path.getPath().endsWith("version.json"))
+ .entrySet()) {
+ try {
+
+ load(entry.getKey());
+ } catch (Exception e) {
+ LOGGER.error(e.getMessage());
+ excnt++;
+ }
+ }
+ if (excnt > 0) {
+ throw new IllegalStateException("This screen definition isn't valid, see above");
+ }
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/AlignStage.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/AlignStage.java
new file mode 100644
index 00000000..7c01a6db
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/AlignStage.java
@@ -0,0 +1,83 @@
+package de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline;
+
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+
+import com.google.gson.JsonObject;
+
+import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenBuilder;
+import de.hysky.skyblocker.skyblock.tabhud.widget.Widget;
+import de.hysky.skyblocker.skyblock.tabhud.util.ScreenConst;
+
+public class AlignStage extends PipelineStage {
+
+ private enum AlignReference {
+ HORICENT("horizontalCenter"),
+ VERTCENT("verticalCenter"),
+ LEFTCENT("leftOfCenter"),
+ RIGHTCENT("rightOfCenter"),
+ TOPCENT("topOfCenter"),
+ BOTCENT("botOfCenter"),
+ TOP("top"),
+ BOT("bot"),
+ LEFT("left"),
+ RIGHT("right");
+
+ private final String str;
+
+ AlignReference(String d) {
+ this.str = d;
+ }
+
+ public static AlignReference parse(String s) throws NoSuchElementException {
+ for (AlignReference d : AlignReference.values()) {
+ if (d.str.equals(s)) {
+ return d;
+ }
+ }
+ throw new NoSuchElementException("\"" + s + "\" is not a valid reference for an align op!");
+ }
+ }
+
+ private final AlignReference reference;
+
+ public AlignStage(ScreenBuilder builder, JsonObject descr) {
+ this.reference = AlignReference.parse(descr.get("reference").getAsString());
+ this.primary = new ArrayList<>(descr.getAsJsonArray("apply_to")
+ .asList()
+ .stream()
+ .map(x -> builder.getInstance(x.getAsString()))
+ .toList());
+ }
+
+ public void run(int screenW, int screenH) {
+ int wHalf, hHalf;
+ for (Widget wid : primary) {
+ switch (this.reference) {
+ case HORICENT -> wid.setX((screenW - wid.getWidth()) / 2);
+ case VERTCENT -> wid.setY((screenH - wid.getHeight()) / 2);
+ case LEFTCENT -> {
+ wHalf = screenW / 2;
+ wid.setX(wHalf - ScreenConst.WIDGET_PAD_HALF - wid.getWidth());
+ }
+ case RIGHTCENT -> {
+ wHalf = screenW / 2;
+ wid.setX(wHalf + ScreenConst.WIDGET_PAD_HALF);
+ }
+ case TOPCENT -> {
+ hHalf = screenH / 2;
+ wid.setY(hHalf - ScreenConst.WIDGET_PAD_HALF - wid.getHeight());
+ }
+ case BOTCENT -> {
+ hHalf = screenH / 2;
+ wid.setY(hHalf + ScreenConst.WIDGET_PAD_HALF);
+ }
+ case TOP -> wid.setY(ScreenConst.getScreenPad());
+ case BOT -> wid.setY(screenH - wid.getHeight() - ScreenConst.getScreenPad());
+ case LEFT -> wid.setX(ScreenConst.getScreenPad());
+ case RIGHT -> wid.setX(screenW - wid.getWidth() - ScreenConst.getScreenPad());
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/CollideStage.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/CollideStage.java
new file mode 100644
index 00000000..d100a52e
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/CollideStage.java
@@ -0,0 +1,153 @@
+package de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline;
+
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+
+import com.google.gson.JsonObject;
+
+import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenBuilder;
+import de.hysky.skyblocker.skyblock.tabhud.widget.Widget;
+import de.hysky.skyblocker.skyblock.tabhud.util.ScreenConst;
+
+public class CollideStage extends PipelineStage {
+
+ private enum CollideDirection {
+ LEFT("left"),
+ RIGHT("right"),
+ TOP("top"),
+ BOT("bot");
+
+ private final String str;
+
+ CollideDirection(String d) {
+ this.str = d;
+ }
+
+ public static CollideDirection parse(String s) throws NoSuchElementException {
+ for (CollideDirection d : CollideDirection.values()) {
+ if (d.str.equals(s)) {
+ return d;
+ }
+ }
+ throw new NoSuchElementException("\"" + s + "\" is not a valid direction for a collide op!");
+ }
+ }
+
+ private final CollideDirection direction;
+
+ public CollideStage(ScreenBuilder builder, JsonObject descr) {
+ this.direction = CollideDirection.parse(descr.get("direction").getAsString());
+ this.primary = new ArrayList<>(descr.getAsJsonArray("widgets")
+ .asList()
+ .stream()
+ .map(x -> builder.getInstance(x.getAsString()))
+ .toList());
+ this.secondary = new ArrayList<>(descr.getAsJsonArray("colliders")
+ .asList()
+ .stream()
+ .map(x -> builder.getInstance(x.getAsString()))
+ .toList());
+ }
+
+ public void run(int screenW, int screenH) {
+ switch (this.direction) {
+ case LEFT -> primary.forEach(w -> collideAgainstL(screenW, w));
+ case RIGHT -> primary.forEach(w -> collideAgainstR(screenW, w));
+ case TOP -> primary.forEach(w -> collideAgainstT(screenH, w));
+ case BOT -> primary.forEach(w -> collideAgainstB(screenH, w));
+ }
+ }
+
+ public void collideAgainstL(int screenW, Widget w) {
+ int yMin = w.getY();
+ int yMax = w.getY() + w.getHeight();
+
+ int xCor = screenW;
+
+ for (Widget other : secondary) {
+ if (other.getY() + other.getHeight() + ScreenConst.WIDGET_PAD < yMin) {
+ // too high, next one
+ continue;
+ }
+
+ if (other.getY() - ScreenConst.WIDGET_PAD > yMax) {
+ // too low, next
+ continue;
+ }
+
+ int xPos = other.getX() - ScreenConst.WIDGET_PAD - w.getWidth();
+ xCor = Math.min(xCor, xPos);
+ }
+ w.setX(xCor);
+ }
+
+ public void collideAgainstR(int screenW, Widget w) {
+ int yMin = w.getY();
+ int yMax = w.getY() + w.getHeight();
+
+ int xCor = 0;
+
+ for (Widget other : secondary) {
+ if (other.getY() + other.getHeight() + ScreenConst.WIDGET_PAD < yMin) {
+ // too high, next one
+ continue;
+ }
+
+ if (other.getY() - ScreenConst.WIDGET_PAD > yMax) {
+ // too low, next
+ continue;
+ }
+
+ int xPos = other.getX() + other.getWidth() + ScreenConst.WIDGET_PAD;
+ xCor = Math.max(xCor, xPos);
+ }
+ w.setX(xCor);
+ }
+
+ public void collideAgainstT(int screenH, Widget w) {
+ int xMin = w.getX();
+ int xMax = w.getX() + w.getWidth();
+
+ int yCor = screenH;
+
+ for (Widget other : secondary) {
+ if (other.getX() + other.getWidth() + ScreenConst.WIDGET_PAD < xMin) {
+ // too far left, next one
+ continue;
+ }
+
+ if (other.getX() - ScreenConst.WIDGET_PAD > xMax) {
+ // too far right, next
+ continue;
+ }
+
+ int yPos = other.getY() - ScreenConst.WIDGET_PAD - w.getHeight();
+ yCor = Math.min(yCor, yPos);
+ }
+ w.setY(yCor);
+ }
+
+ public void collideAgainstB(int screenH, Widget w) {
+ int xMin = w.getX();
+ int xMax = w.getX() + w.getWidth();
+
+ int yCor = 0;
+
+ for (Widget other : secondary) {
+ if (other.getX() + other.getWidth() + ScreenConst.WIDGET_PAD < xMin) {
+ // too far left, next one
+ continue;
+ }
+
+ if (other.getX() - ScreenConst.WIDGET_PAD > xMax) {
+ // too far right, next
+ continue;
+ }
+
+ int yPos = other.getY() + other.getHeight() + ScreenConst.WIDGET_PAD;
+ yCor = Math.max(yCor, yPos);
+ }
+ w.setY(yCor);
+ }
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/PipelineStage.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/PipelineStage.java
new file mode 100644
index 00000000..20e4859e
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/PipelineStage.java
@@ -0,0 +1,14 @@
+package de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline;
+
+import java.util.ArrayList;
+
+import de.hysky.skyblocker.skyblock.tabhud.widget.Widget;
+
+public abstract class PipelineStage {
+
+ protected ArrayList<Widget> primary = null;
+ protected ArrayList<Widget> secondary = null;
+
+ public abstract void run(int screenW, int screenH);
+
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/PlaceStage.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/PlaceStage.java
new file mode 100644
index 00000000..7d57305b
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/PlaceStage.java
@@ -0,0 +1,94 @@
+package de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline;
+
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+
+import com.google.gson.JsonObject;
+
+import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenBuilder;
+import de.hysky.skyblocker.skyblock.tabhud.widget.Widget;
+import de.hysky.skyblocker.skyblock.tabhud.util.ScreenConst;
+
+public class PlaceStage extends PipelineStage {
+
+ private enum PlaceLocation {
+ CENTER("center"),
+ TOPCENT("centerTop"),
+ BOTCENT("centerBot"),
+ LEFTCENT("centerLeft"),
+ RIGHTCENT("centerRight"),
+ TRCORNER("cornerTopRight"),
+ TLCORNER("cornerTopLeft"),
+ BRCORNER("cornerBotRight"),
+ BLCORNER("cornerBotLeft");
+
+ private final String str;
+
+ PlaceLocation(String d) {
+ this.str = d;
+ }
+
+ public static PlaceLocation parse(String s) throws NoSuchElementException {
+ for (PlaceLocation d : PlaceLocation.values()) {
+ if (d.str.equals(s)) {
+ return d;
+ }
+ }
+ throw new NoSuchElementException("\"" + s + "\" is not a valid location for a place op!");
+ }
+ }
+
+ private final PlaceLocation where;
+
+ public PlaceStage(ScreenBuilder builder, JsonObject descr) {
+ this.where = PlaceLocation.parse(descr.get("where").getAsString());
+ this.primary = new ArrayList<>(descr.getAsJsonArray("apply_to")
+ .asList()
+ .stream()
+ .map(x -> builder.getInstance(x.getAsString()))
+ .limit(1)
+ .toList());
+ }
+
+ public void run(int screenW, int screenH) {
+ Widget wid = primary.get(0);
+ switch (where) {
+ case CENTER -> {
+ wid.setX((screenW - wid.getWidth()) / 2);
+ wid.setY((screenH - wid.getHeight()) / 2);
+ }
+ case TOPCENT -> {
+ wid.setX((screenW - wid.getWidth()) / 2);
+ wid.setY(ScreenConst.getScreenPad());
+ }
+ case BOTCENT -> {
+ wid.setX((screenW - wid.getWidth()) / 2);
+ wid.setY((screenH - wid.getHeight()) - ScreenConst.getScreenPad());
+ }
+ case LEFTCENT -> {
+ wid.setX(ScreenConst.getScreenPad());
+ wid.setY((screenH - wid.getHeight()) / 2);
+ }
+ case RIGHTCENT -> {
+ wid.setX((screenW - wid.getWidth()) - ScreenConst.getScreenPad());
+ wid.setY((screenH - wid.getHeight()) / 2);
+ }
+ case TLCORNER -> {
+ wid.setX(ScreenConst.getScreenPad());
+ wid.setY(ScreenConst.getScreenPad());
+ }
+ case TRCORNER -> {
+ wid.setX((screenW - wid.getWidth()) - ScreenConst.getScreenPad());
+ wid.setY(ScreenConst.getScreenPad());
+ }
+ case BLCORNER -> {
+ wid.setX(ScreenConst.getScreenPad());
+ wid.setY((screenH - wid.getHeight()) - ScreenConst.getScreenPad());
+ }
+ case BRCORNER -> {
+ wid.setX((screenW - wid.getWidth()) - ScreenConst.getScreenPad());
+ wid.setY((screenH - wid.getHeight()) - ScreenConst.getScreenPad());
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/StackStage.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/StackStage.java
new file mode 100644
index 00000000..f4fe07e5
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/screenbuilder/pipeline/StackStage.java
@@ -0,0 +1,114 @@
+package de.hysky.skyblocker.skyblock.tabhud.screenbuilder.pipeline;
+
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+
+import com.google.gson.JsonObject;
+
+import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenBuilder;
+import de.hysky.skyblocker.skyblock.tabhud.widget.Widget;
+import de.hysky.skyblocker.skyblock.tabhud.util.ScreenConst;
+
+public class StackStage extends PipelineStage {
+
+ private enum StackDirection {
+ HORIZONTAL("horizontal"),
+ VERTICAL("vertical");
+
+ private final String str;
+
+ StackDirection(String d) {
+ this.str = d;
+ }
+
+ public static StackDirection parse(String s) throws NoSuchElementException {
+ for (StackDirection d : StackDirection.values()) {
+ if (d.str.equals(s)) {
+ return d;
+ }
+ }
+ throw new NoSuchElementException("\"" + s + "\" is not a valid direction for a stack op!");
+ }
+ }
+
+ private enum StackAlign {
+ TOP("top"),
+ BOT("bot"),
+ LEFT("left"),
+ RIGHT("right"),
+ CENTER("center");
+
+ private final String str;
+
+ StackAlign(String d) {
+ this.str = d;
+ }
+
+ public static StackAlign parse(String s) throws NoSuchElementException {
+ for (StackAlign d : StackAlign.values()) {
+ if (d.str.equals(s)) {
+ return d;
+ }
+ }
+ throw new NoSuchElementException("\"" + s + "\" is not a valid alignment for a stack op!");
+ }
+ }
+
+ private final StackDirection direction;
+ private final StackAlign align;
+
+ public StackStage(ScreenBuilder builder, JsonObject descr) {
+ this.direction = StackDirection.parse(descr.get("direction").getAsString());
+ this.align = StackAlign.parse(descr.get("align").getAsString());
+ this.primary = new ArrayList<>(descr.getAsJsonArray("apply_to")
+ .asList()
+ .stream()
+ .map(x -> builder.getInstance(x.getAsString()))
+ .toList());
+ }
+
+ public void run(int screenW, int screenH) {
+ switch (this.direction) {
+ case HORIZONTAL -> stackWidgetsHoriz(screenW);
+ case VERTICAL -> stackWidgetsVert(screenH);
+ }
+ }
+
+ public void stackWidgetsVert(int screenH) {
+ int compHeight = -ScreenConst.WIDGET_PAD;
+ for (Widget wid : primary) {
+ compHeight += wid.getHeight() + 5;
+ }
+
+ int y = switch (this.align) {
+
+ case TOP -> ScreenConst.getScreenPad();
+ case BOT -> (screenH - compHeight) - ScreenConst.getScreenPad();
+ default -> (screenH - compHeight) / 2;
+ };
+
+ for (Widget wid : primary) {
+ wid.setY(y);
+ y += wid.getHeight() + ScreenConst.WIDGET_PAD;
+ }
+ }
+
+ public void stackWidgetsHoriz(int screenW) {
+ int compWidth = -ScreenConst.WIDGET_PAD;
+ for (Widget wid : primary) {
+ compWidth += wid.getWidth() + ScreenConst.WIDGET_PAD;
+ }
+
+ int x = switch (this.align) {
+
+ case LEFT -> ScreenConst.getScreenPad();
+ case RIGHT -> (screenW - compWidth) - ScreenConst.getScreenPad();
+ default -> (screenW - compWidth) / 2;
+ };
+
+ for (Widget wid : primary) {
+ wid.setX(x);
+ x += wid.getWidth() + ScreenConst.WIDGET_PAD;
+ }
+ }
+} \ No newline at end of file