From 384d9954aedefcee6f132d2bdadca11fca682bd7 Mon Sep 17 00:00:00 2001 From: msg-programs Date: Sun, 23 Jul 2023 12:29:32 +0200 Subject: Massive refactor, see details Instead of creating a new widget instance every frame, instantiate once and update content. This means that every widget had to be updated to work like that. This also necessitated some ugly hackery for the dwarven HUD. Many other minor things like moving and renaming classes --- .../tabhud/screenbuilder/ScreenBuilder.java | 166 +++++++++++++++++++++ .../tabhud/screenbuilder/ScreenMaster.java | 82 ++++++++++ .../tabhud/screenbuilder/pipeline/AlignStage.java | 53 +++++++ .../screenbuilder/pipeline/CollideStage.java | 84 +++++++++++ .../screenbuilder/pipeline/PipelineStage.java | 14 ++ .../tabhud/screenbuilder/pipeline/PlaceStage.java | 31 ++++ .../tabhud/screenbuilder/pipeline/StackStage.java | 74 +++++++++ 7 files changed, 504 insertions(+) create mode 100644 src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/ScreenBuilder.java create mode 100644 src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/ScreenMaster.java create mode 100644 src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/AlignStage.java create mode 100644 src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/CollideStage.java create mode 100644 src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/PipelineStage.java create mode 100644 src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/PlaceStage.java create mode 100644 src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/StackStage.java (limited to 'src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder') diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/ScreenBuilder.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/ScreenBuilder.java new file mode 100644 index 00000000..1197ca2d --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/ScreenBuilder.java @@ -0,0 +1,166 @@ +package me.xmrvizzy.skyblocker.skyblock.tabhud.screenbuilder; + +import java.io.BufferedReader; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import me.xmrvizzy.skyblocker.SkyblockerMod; +import me.xmrvizzy.skyblocker.skyblock.tabhud.screenbuilder.pipeline.AlignStage; +import me.xmrvizzy.skyblocker.skyblock.tabhud.screenbuilder.pipeline.CollideStage; +import me.xmrvizzy.skyblocker.skyblock.tabhud.screenbuilder.pipeline.PipelineStage; +import me.xmrvizzy.skyblocker.skyblock.tabhud.screenbuilder.pipeline.PlaceStage; +import me.xmrvizzy.skyblocker.skyblock.tabhud.screenbuilder.pipeline.StackStage; +import me.xmrvizzy.skyblocker.skyblock.tabhud.widget.EmptyWidget; +import me.xmrvizzy.skyblocker.skyblock.tabhud.widget.EventWidget; +import me.xmrvizzy.skyblocker.skyblock.tabhud.widget.Widget; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.util.Identifier; + +public class ScreenBuilder { + + // layout pipeline + private ArrayList layoutPipeline = new ArrayList<>(); + + // all widget instances this builder knows + private ArrayList instances = new ArrayList<>(); + // maps alias -> widget instance + private HashMap objectMap = new HashMap<>(); + + /** + * Create a ScreenBuilder from a json. + */ + public ScreenBuilder(String jsonfile) throws IOException { + + Identifier ident = new Identifier(SkyblockerMod.NAMESPACE, "tabhud/" + jsonfile + ".json"); + BufferedReader reader = MinecraftClient.getInstance().getResourceManager().openAsReader(ident); + JsonObject json = JsonParser.parseReader(reader).getAsJsonObject(); + reader.close(); + + 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) { + layoutPipeline.add(createStage(l.getAsJsonObject())); + } + + } + + /** + * 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 + switch (name) { + case "EventWidget": + return new EventWidget(widget.get("inGarden").getAsBoolean()); + case "Widget": + // clown case sanity check. don't instantiate the superclass >:| + return new EmptyWidget(); + } + + // reflect something together for the "normal" ones. + // TODO don't get package list for every widget; do it once and cache. + // fine for now, as this would only shorten the load time anyways + + // find all packages that might contain widget classes + Package[] packs = Package.getPackages(); + + List packnames = Arrays.stream(packs) + .map(pack -> pack.getName()) + .filter(s -> s.startsWith("me.xmrvizzy.skyblocker.skyblock.tabhud.widget")) + .toList(); + + // 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) { + return new EmptyWidget(); + } + + // return instance of that class. + try { + Constructor ctor = clazz.getConstructor(); + return (Widget) ctor.newInstance(); + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException | SecurityException ex) { + return new EmptyWidget(); + } + } + + /** + * Create a PipelineStage from a json object. + */ + public PipelineStage createStage(JsonObject descr) { + + String op = descr.get("op").getAsString(); + + switch (op) { + case "place": + return new PlaceStage(this, descr); + case "stack": + return new StackStage(this, descr); + case "align": + return new AlignStage(this, descr); + case "collideAgainst": + return new CollideStage(this, descr); + } + return null; + } + + /** + * Lookup Widget instance from alias name + */ + public Widget getInstance(String name) { + return this.objectMap.get(name); + } + + /** + * Run the pipeline to build a Screen + */ + public void run(DrawContext context, int screenW, int screenH) { + // TODO for future: + // no need to update and run pipeline if PlayerListMgr wasn't updated. + + 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/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/ScreenMaster.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/ScreenMaster.java new file mode 100644 index 00000000..97969503 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/ScreenMaster.java @@ -0,0 +1,82 @@ +package me.xmrvizzy.skyblocker.skyblock.tabhud.screenbuilder; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map.Entry; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import me.xmrvizzy.skyblocker.SkyblockerMod; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.util.Identifier; + +public class ScreenMaster { + + private static final Identifier ASSIGNMENT_JSON = new Identifier(SkyblockerMod.NAMESPACE, "tabhud/assignment.json"); + private static final Logger LOGGER = LoggerFactory.getLogger("skyblocker"); + + private static HashMap standardMap = new HashMap<>(); + // private static HashMap screenAMap = new HashMap<>(); + // private static HashMap screenBMap = new HashMap<>(); + + static { + init(); + } + + /** + * Load the assignment json and construct the screen mapping + */ + public static void init() { + try (BufferedReader reader = MinecraftClient.getInstance().getResourceManager().openAsReader(ASSIGNMENT_JSON)) { + + JsonObject json = JsonParser.parseReader(reader).getAsJsonObject(); + JsonObject standard = json.getAsJsonObject("standard"); + for (Entry entry : standard.entrySet()) { + standardMap.put(entry.getKey(), new ScreenBuilder(entry.getValue().getAsString())); + } + + } catch (IOException ioex) { + LOGGER.info("[Skyblocker] Couldn't load tabhud config!"); + ioex.printStackTrace(); + } + } + + /** + * Top level render method. + * Calls the appropriate ScreenBuilder with the screen's dimensions + */ + public static void render(DrawContext context, int w, int h) { + standardMap.get("default").run(context, w, h); + } +} + +/* + * + * stackWidgetsH +stackWidgetsW +--> stack (direction?) horiz/vert (align?) center/top/bottom/left/right + +centerH +centerW +center +--> center (center/horiz/vert) +----> place (one) (where?) center/top/bot/left/right/[corners] + +offCenterL +offCenterR +--> offCenter left/right/top/bot +----> offsetPlace (where?) center/left/right/top/bot (offset to where?) left/right/top/bot + +----> align (any) (reference?) left, leftOfCenter, horizontalCenter, rightOfCenter, right, top, topOfCenter, verticalCenter, botOfCenter, bot + +collideAgainstL +collideAgainstR +--> collideAgainst (from where?) left/right/top/bot + */ \ No newline at end of file diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/AlignStage.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/AlignStage.java new file mode 100644 index 00000000..87638c33 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/AlignStage.java @@ -0,0 +1,53 @@ +package me.xmrvizzy.skyblocker.skyblock.tabhud.screenbuilder.pipeline; + +import java.util.ArrayList; + +import com.google.gson.JsonObject; + +import me.xmrvizzy.skyblocker.skyblock.tabhud.screenbuilder.ScreenBuilder; +import me.xmrvizzy.skyblocker.skyblock.tabhud.widget.Widget; + +public class AlignStage extends PipelineStage { + + private String reference; + + public AlignStage(ScreenBuilder builder, JsonObject descr) { + this.reference = 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 "horizontalCenter": + wid.setX((screenW - wid.getWidth()) / 2); + break; + case "verticalCenter": + wid.setY((screenH - wid.getHeight()) / 2); + break; + case "leftOfCenter": + wHalf = screenW / 2; + wid.setX(wHalf - 3 - wid.getWidth()); + break; + case "rightOfCenter": + wHalf = screenW / 2; + wid.setX(wHalf + 3); + break; + case "topOfCenter": + hHalf = screenH / 2; + wid.setY(hHalf - 3 - wid.getHeight()); + break; + case "botOfCenter": + hHalf = screenH / 2; + wid.setY(hHalf + 3); + break; + } + } + } + +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/CollideStage.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/CollideStage.java new file mode 100644 index 00000000..8275e842 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/CollideStage.java @@ -0,0 +1,84 @@ +package me.xmrvizzy.skyblocker.skyblock.tabhud.screenbuilder.pipeline; + +import java.util.ArrayList; + +import com.google.gson.JsonObject; + +import me.xmrvizzy.skyblocker.skyblock.tabhud.screenbuilder.ScreenBuilder; +import me.xmrvizzy.skyblocker.skyblock.tabhud.widget.Widget; + +public class CollideStage extends PipelineStage { + + private String direction; + + public CollideStage(ScreenBuilder builder, JsonObject descr) { + this.direction = 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) { + if (this.direction.equals("left")) { + primary.forEach(w -> collideAgainstL(screenW, w)); + } else if (this.direction.equals("right")) { + primary.forEach(w -> collideAgainstR(screenW, w)); + } + } + + public void collideAgainstL(int screenW, Widget w) { + int yMin = w.getY(); + int yMax = w.getY() + w.getHeight(); + + int xCor = screenW / 2; + + // assume others to be sorted top-bottom. + for (Widget other : secondary) { + if (other.getY() + other.getHeight() + 5 < yMin) { + // too high, next one + continue; + } + + if (other.getY() - 5 > yMax) { + // too low, next + continue; + } + + int xPos = other.getX() - 5 - 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 = screenW / 2; + + // assume others to be sorted top-bottom. + for (Widget other : secondary) { + if (other.getY() + other.getHeight() + 5 < yMin) { + // too high, next one + continue; + } + + if (other.getY() - 5 > yMax) { + // too low, next + continue; + } + + int xPos = other.getX() + other.getWidth() + 5; + xCor = Math.max(xCor, xPos); + } + w.setX(xCor); + } + +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/PipelineStage.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/PipelineStage.java new file mode 100644 index 00000000..e560058c --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/PipelineStage.java @@ -0,0 +1,14 @@ +package me.xmrvizzy.skyblocker.skyblock.tabhud.screenbuilder.pipeline; + +import java.util.ArrayList; + +import me.xmrvizzy.skyblocker.skyblock.tabhud.widget.Widget; + +public abstract class PipelineStage { + + protected ArrayList primary = null; + protected ArrayList secondary = null; + + public abstract void run(int screenW, int screenH); + +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/PlaceStage.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/PlaceStage.java new file mode 100644 index 00000000..3ed89fda --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/PlaceStage.java @@ -0,0 +1,31 @@ +package me.xmrvizzy.skyblocker.skyblock.tabhud.screenbuilder.pipeline; + +import java.util.ArrayList; + +import com.google.gson.JsonObject; + +import me.xmrvizzy.skyblocker.skyblock.tabhud.screenbuilder.ScreenBuilder; +import me.xmrvizzy.skyblocker.skyblock.tabhud.widget.Widget; + +public class PlaceStage extends PipelineStage { + + private String where; + + public PlaceStage(ScreenBuilder builder, JsonObject descr) { + this.where = 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); + if (where.equals("center")) { + wid.setY((screenH - wid.getHeight()) / 2); + wid.setX((screenW - wid.getWidth()) / 2); + } + } +} \ No newline at end of file diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/StackStage.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/StackStage.java new file mode 100644 index 00000000..d144c67b --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/tabhud/screenbuilder/pipeline/StackStage.java @@ -0,0 +1,74 @@ +package me.xmrvizzy.skyblocker.skyblock.tabhud.screenbuilder.pipeline; + +import java.util.ArrayList; + +import com.google.gson.JsonObject; + +import me.xmrvizzy.skyblocker.skyblock.tabhud.screenbuilder.ScreenBuilder; +import me.xmrvizzy.skyblocker.skyblock.tabhud.widget.Widget; + +public class StackStage extends PipelineStage { + + private String direction; + private String align; + + public StackStage(ScreenBuilder builder, JsonObject descr) { + this.direction = descr.get("direction").getAsString(); + this.align = 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); + break; + case "vertical": + stackWidgetsVert(screenH); + break; + } + } + + public void stackWidgetsVert( int screenH) { + int compHeight = -5; + for (Widget wid : primary) { + compHeight += wid.getHeight() + 5; + } + + int y = switch (this.align) { + + case "top" -> y = 5; + case "bot" -> y = (screenH-compHeight) - 5; + default -> y = (screenH-compHeight)/2; + }; + + for (Widget wid : primary) { + wid.setY(y); + y += wid.getHeight() + 5; + } + } + + public void stackWidgetsHoriz(int screenW) { + // TODO not centered + int compWidth = -5; + for (Widget wid : primary) { + compWidth += wid.getWidth() + 5; + } + + int x = switch (this.align) { + + case "left" -> x = 5; + case "right" -> x = (screenW-compWidth) - 5; + default -> x = (screenW-compWidth)/2; + }; + + for (Widget wid : primary) { + wid.setX(x); + x += wid.getWidth() + 5; + } + } +} \ No newline at end of file -- cgit