aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
authorKevin <92656833+kevinthegreat1@users.noreply.github.com>2025-01-25 12:58:08 -0500
committerGitHub <noreply@github.com>2025-01-26 01:58:08 +0800
commit922313c645385901614a2642834d0276db73de27 (patch)
tree16b89628703ac5c0fd5dcef97cee9b242b77e4cd /src/main/java
parentf73a04e4eb009cb15066c20f233fd6f11c87511e (diff)
downloadSkyblocker-922313c645385901614a2642834d0276db73de27.tar.gz
Skyblocker-922313c645385901614a2642834d0276db73de27.tar.bz2
Skyblocker-922313c645385901614a2642834d0276db73de27.zip
Add client game test (#1106)
* Add SkyblockerGameTest * Run in CI * Use Xvfb * Use one workflow file * Always upload screenshots * Update fabric api to 0.114.0 * Add screenshot comparison * Make fancy status bars consistent
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerGameTest.java60
-rw-r--r--src/main/java/de/hysky/skyblocker/debug/Debug.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/debug/SnapshotDebug.java8
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/fancybars/BarPositioner.java11
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/fancybars/FancyStatusBars.java76
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/fancybars/StatusBarType.java78
6 files changed, 181 insertions, 54 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerGameTest.java b/src/main/java/de/hysky/skyblocker/SkyblockerGameTest.java
new file mode 100644
index 00000000..d3da8969
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerGameTest.java
@@ -0,0 +1,60 @@
+package de.hysky.skyblocker;
+
+import de.hysky.skyblocker.debug.SnapshotDebug;
+import de.hysky.skyblocker.skyblock.fancybars.FancyStatusBars;
+import it.unimi.dsi.fastutil.Pair;
+import net.fabricmc.fabric.api.client.gametest.v1.ClientGameTestContext;
+import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
+import net.fabricmc.fabric.api.client.gametest.v1.TestScreenshotComparisonOptions;
+import net.fabricmc.fabric.api.client.gametest.v1.TestSingleplayerContext;
+import net.minecraft.client.gui.screen.world.WorldCreator;
+import net.minecraft.registry.RegistryKeys;
+import net.minecraft.world.gen.WorldPresets;
+
+@SuppressWarnings("UnstableApiUsage")
+public class SkyblockerGameTest implements FabricClientGameTest {
+ @Override
+ public void runTest(ClientGameTestContext context) {
+ try (TestSingleplayerContext singleplayer = context.worldBuilder().adjustSettings(worldCreator -> {
+ worldCreator.setWorldType(new WorldCreator.WorldType(worldCreator.getGeneratorOptionsHolder().getCombinedRegistryManager().getOrThrow(RegistryKeys.WORLD_PRESET).getOrThrow(WorldPresets.DEFAULT)));
+ worldCreator.setSeed(String.valueOf(SnapshotDebug.AARON_WORLD_SEED));
+ }).create()) {
+ // Set up the world
+ singleplayer.getServer().runCommand("/fill 180 63 -13 184 67 -17 air");
+ singleplayer.getServer().runCommand("/setblock 175 66 -4 minecraft:barrier");
+ singleplayer.getServer().runCommand("/tp @a 175 67 -4");
+
+ context.runOnClient(client -> {
+ assert client.player != null;
+ client.player.setYaw(180);
+ client.player.setPitch(20);
+ });
+
+ // Save the current fancy status bars config and reset it to default
+ var config = context.computeOnClient(client -> {
+ var curConfig = FancyStatusBars.statusBars.entrySet().stream().map(e -> Pair.of(e.getKey(), e.getValue().toJson())).toList();
+
+ int[] counts = new int[7];
+ FancyStatusBars.statusBars.forEach((type, bar) -> {
+ bar.anchor = type.getDefaultAnchor();
+ bar.gridY = type.getDefaultGridY();
+ bar.gridX = counts[type.getDefaultAnchor().ordinal()]++;
+ });
+ FancyStatusBars.placeBarsInPositioner();
+ FancyStatusBars.updatePositions();
+ return curConfig;
+ });
+
+ // Take a screenshot and compare it
+ singleplayer.getClientWorld().waitForChunksRender();
+ context.assertScreenshotEquals(TestScreenshotComparisonOptions.of("skyblocker_render").saveWithFileName("skyblocker_render"));
+
+ // Restore the fancy status bars config
+ context.runOnClient(client -> {
+ config.forEach(pair -> FancyStatusBars.statusBars.get(pair.key()).loadFromJson(pair.value()));
+ FancyStatusBars.placeBarsInPositioner();
+ FancyStatusBars.updatePositions();
+ });
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/debug/Debug.java b/src/main/java/de/hysky/skyblocker/debug/Debug.java
index 2a9f9a60..cfd0b528 100644
--- a/src/main/java/de/hysky/skyblocker/debug/Debug.java
+++ b/src/main/java/de/hysky/skyblocker/debug/Debug.java
@@ -44,7 +44,7 @@ public class Debug {
private static boolean keyDown = false;
public static boolean debugEnabled() {
- return DEBUG_ENABLED || FabricLoader.getInstance().isDevelopmentEnvironment();
+ return DEBUG_ENABLED || FabricLoader.getInstance().isDevelopmentEnvironment() || SnapshotDebug.isInSnapshot();
}
public static boolean webSocketDebug() {
diff --git a/src/main/java/de/hysky/skyblocker/debug/SnapshotDebug.java b/src/main/java/de/hysky/skyblocker/debug/SnapshotDebug.java
index 0f70d651..ba89d942 100644
--- a/src/main/java/de/hysky/skyblocker/debug/SnapshotDebug.java
+++ b/src/main/java/de/hysky/skyblocker/debug/SnapshotDebug.java
@@ -14,14 +14,14 @@ public class SnapshotDebug {
private static final float[] RED = { 1.0f, 0.0f, 0.0f };
private static final float ALPHA = 0.5f;
private static final float LINE_WIDTH = 8f;
- private static final long AARON_WORLD_SEED = 5629719634239627355L;
+ public static final long AARON_WORLD_SEED = 5629719634239627355L;
- private static boolean isInSnapshot() {
+ public static boolean isInSnapshot() {
return !SharedConstants.getGameVersion().isStable();
}
static void init() {
- if (isInSnapshot()) {
+ if (Debug.debugEnabled()) {
WorldRenderEvents.AFTER_TRANSLUCENT.register(SnapshotDebug::renderTest);
}
}
@@ -32,7 +32,7 @@ public class SnapshotDebug {
RenderHelper.renderLinesFromPoints(wrc, new Vec3d[] { new Vec3d(173, 66, -7.5), new Vec3d(178, 66, -7.5) }, RED, ALPHA, LINE_WIDTH, false);
RenderHelper.renderQuad(wrc, new Vec3d[] { new Vec3d(183, 66, -16), new Vec3d(183, 63, -16), new Vec3d(183, 63, -14), new Vec3d(183, 66, -14) }, RED, ALPHA, false);
RenderHelper.renderText(wrc, Text.of("Skyblocker on " + SharedConstants.getGameVersion().getName() + "!"), new Vec3d(175.5, 67.5, -7.5), false);
- } else {
+ } else if (isInSnapshot()) {
RenderHelper.renderFilledWithBeaconBeam(wrc, new BlockPos(-3, 63, 5), RED, ALPHA, true);
RenderHelper.renderOutline(wrc, new BlockPos(-3, 63, 5), RED, 5, true); // Use waypoint default line width
RenderHelper.renderLinesFromPoints(wrc, new Vec3d[] { new Vec3d(-2, 65, 6.5), new Vec3d(3, 65, 6.5) }, RED, ALPHA, LINE_WIDTH, false);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/fancybars/BarPositioner.java b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/BarPositioner.java
index 00b09dcf..ffb0b66a 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/fancybars/BarPositioner.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/BarPositioner.java
@@ -5,11 +5,14 @@ import net.minecraft.client.gui.ScreenRect;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.util.*;
+import java.util.EnumMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
public class BarPositioner {
- private final Map<BarAnchor, LinkedList<LinkedList<StatusBar>>> map = new HashMap<>(BarAnchor.values().length);
+ private final Map<BarAnchor, LinkedList<LinkedList<StatusBar>>> map = new EnumMap<>(BarAnchor.class);
public BarPositioner() {
for (BarAnchor value : BarAnchor.values()) {
@@ -145,6 +148,10 @@ public class BarPositioner {
}
}
+ public void clear() {
+ map.replaceAll((barAnchor, rows) -> new LinkedList<>());
+ }
+
public enum BarAnchor {
HOTBAR_LEFT(true, false,
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/fancybars/FancyStatusBars.java b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/FancyStatusBars.java
index cc6a3571..adc5d2b2 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/fancybars/FancyStatusBars.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/FancyStatusBars.java
@@ -14,21 +14,18 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.ScreenPos;
-import net.minecraft.text.Text;
-import net.minecraft.util.Identifier;
import net.minecraft.util.math.MathHelper;
+import org.jetbrains.annotations.VisibleForTesting;
import org.lwjgl.glfw.GLFW;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.awt.*;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
-import java.util.List;
import java.util.*;
import java.util.concurrent.CompletableFuture;
@@ -40,58 +37,43 @@ public class FancyStatusBars {
private final StatusBarTracker statusBarTracker = SkyblockerMod.getInstance().statusBarTracker;
public static BarPositioner barPositioner = new BarPositioner();
- public static Map<String, StatusBar> statusBars = new HashMap<>();
+ public static Map<StatusBarType, StatusBar> statusBars = new EnumMap<>(StatusBarType.class);
public static boolean isHealthFancyBarVisible() {
- StatusBar health = statusBars.get("health");
+ StatusBar health = statusBars.get(StatusBarType.HEALTH);
return health.anchor != null || health.inMouse;
}
public static boolean isExperienceFancyBarVisible() {
- StatusBar experience = statusBars.get("experience");
+ StatusBar experience = statusBars.get(StatusBarType.EXPERIENCE);
return experience.anchor != null || experience.inMouse;
}
@SuppressWarnings("deprecation")
@Init
public static void init() {
- statusBars.put("health", new StatusBar(Identifier.of(SkyblockerMod.NAMESPACE, "bars/icons/health"),
- new Color[]{new Color(255, 0, 0), new Color(255, 220, 0)},
- true, new Color(255, 85, 85), Text.translatable("skyblocker.bars.config.health")));
- statusBars.put("intelligence", new StatusBar(Identifier.of(SkyblockerMod.NAMESPACE, "bars/icons/intelligence"),
- new Color[]{new Color(0, 255, 255), new Color(180, 0, 255)},
- true, new Color(85, 255, 255), Text.translatable("skyblocker.bars.config.intelligence")));
- statusBars.put("defense", new StatusBar(Identifier.of(SkyblockerMod.NAMESPACE, "bars/icons/defense"),
- new Color[]{new Color(255, 255, 255)},
- false, new Color(185, 185, 185), Text.translatable("skyblocker.bars.config.defense")));
- statusBars.put("experience", new StatusBar(Identifier.of(SkyblockerMod.NAMESPACE, "bars/icons/experience"),
- new Color[]{new Color(100, 230, 70)},
- false, new Color(128, 255, 32), Text.translatable("skyblocker.bars.config.experience")));
- statusBars.put("speed", new StatusBar(Identifier.of(SkyblockerMod.NAMESPACE, "bars/icons/speed"),
- new Color[]{new Color(255, 255, 255)},
- false, new Color(185, 185, 185), Text.translatable("skyblocker.bars.config.speed")));
+ statusBars.put(StatusBarType.HEALTH, StatusBarType.HEALTH.newStatusBar());
+ statusBars.put(StatusBarType.INTELLIGENCE, StatusBarType.INTELLIGENCE.newStatusBar());
+ statusBars.put(StatusBarType.DEFENSE, StatusBarType.DEFENSE.newStatusBar());
+ statusBars.put(StatusBarType.EXPERIENCE, StatusBarType.EXPERIENCE.newStatusBar());
+ statusBars.put(StatusBarType.SPEED, StatusBarType.SPEED.newStatusBar());
// Fetch from old status bar config
int[] counts = new int[3]; // counts for RIGHT, LAYER1, LAYER2
- StatusBar health = statusBars.get("health");
-
UIAndVisualsConfig.LegacyBarPositions barPositions = SkyblockerConfigManager.get().uiAndVisuals.bars.barPositions;
- initBarPosition(health, counts, barPositions.healthBarPosition);
- StatusBar intelligence = statusBars.get("intelligence");
- initBarPosition(intelligence, counts, barPositions.manaBarPosition);
- StatusBar defense = statusBars.get("defense");
- initBarPosition(defense, counts, barPositions.defenceBarPosition);
- StatusBar experience = statusBars.get("experience");
- initBarPosition(experience, counts, barPositions.experienceBarPosition);
- StatusBar speed = statusBars.get("speed");
- initBarPosition(speed, counts, UIAndVisualsConfig.LegacyBarPosition.RIGHT);
+ initBarPosition(statusBars.get(StatusBarType.HEALTH), counts, barPositions.healthBarPosition);
+ initBarPosition(statusBars.get(StatusBarType.INTELLIGENCE), counts, barPositions.manaBarPosition);
+ initBarPosition(statusBars.get(StatusBarType.DEFENSE), counts, barPositions.defenceBarPosition);
+ initBarPosition(statusBars.get(StatusBarType.EXPERIENCE), counts, barPositions.experienceBarPosition);
+ initBarPosition(statusBars.get(StatusBarType.SPEED), counts, UIAndVisualsConfig.LegacyBarPosition.RIGHT);
CompletableFuture.supplyAsync(FancyStatusBars::loadBarConfig).thenAccept(object -> {
if (object != null) {
for (String s : object.keySet()) {
- if (statusBars.containsKey(s)) {
+ StatusBarType type = StatusBarType.from(s);
+ if (statusBars.containsKey(type)) {
try {
- statusBars.get(s).loadFromJson(object.get(s).getAsJsonObject());
+ statusBars.get(type).loadFromJson(object.get(s).getAsJsonObject());
} catch (Exception e) {
LOGGER.error("[Skyblocker] Failed to load {} status bar", s, e);
}
@@ -145,13 +127,13 @@ public class FancyStatusBars {
private static boolean configLoaded = false;
- private static void placeBarsInPositioner() {
- List<StatusBar> original = statusBars.values().stream().toList();
-
+ @VisibleForTesting
+ public static void placeBarsInPositioner() {
+ barPositioner.clear();
for (BarPositioner.BarAnchor barAnchor : BarPositioner.BarAnchor.allAnchors()) {
- List<StatusBar> barList = new ArrayList<>(original.stream().filter(bar -> bar.anchor == barAnchor).toList());
+ List<StatusBar> barList = statusBars.values().stream().filter(bar -> bar.anchor == barAnchor)
+ .sorted(Comparator.<StatusBar>comparingInt(bar -> bar.gridY).thenComparingInt(bar -> bar.gridX)).toList();
if (barList.isEmpty()) continue;
- barList.sort((a, b) -> a.gridY == b.gridY ? Integer.compare(a.gridX, b.gridX) : Integer.compare(a.gridY, b.gridY));
int y = -1;
int rowNum = -1;
@@ -179,7 +161,7 @@ public class FancyStatusBars {
public static void saveBarConfig() {
JsonObject output = new JsonObject();
- statusBars.forEach((s, statusBar) -> output.add(s, statusBar.toJson()));
+ statusBars.forEach((s, statusBar) -> output.add(s.asString(), statusBar.toJson()));
try (BufferedWriter writer = Files.newBufferedWriter(FILE)) {
SkyblockerMod.GSON.toJson(output, writer);
LOGGER.info("[Skyblocker] Saved status bars config");
@@ -315,16 +297,16 @@ public class FancyStatusBars {
for (StatusBar statusBar : barCollection) {
if (statusBar.anchor != null) statusBar.render(context, -1, -1, client.getRenderTickCounter().getLastFrameDuration());
}
- StatusBarTracker.Resource health = statusBarTracker.getHealth();
- statusBars.get("health").updateValues(health.value() / (float) health.max(), health.overflow() / (float) health.max(), health.value());
+ StatusBarTracker.Resource health = statusBarTracker.getHealth();
+ statusBars.get(StatusBarType.HEALTH).updateValues(health.value() / (float) health.max(), health.overflow() / (float) health.max(), health.value());
StatusBarTracker.Resource intelligence = statusBarTracker.getMana();
- statusBars.get("intelligence").updateValues(intelligence.value() / (float) intelligence.max(), intelligence.overflow() / (float) intelligence.max(), intelligence.value());
+ statusBars.get(StatusBarType.INTELLIGENCE).updateValues(intelligence.value() / (float) intelligence.max(), intelligence.overflow() / (float) intelligence.max(), intelligence.value());
int defense = statusBarTracker.getDefense();
- statusBars.get("defense").updateValues(defense / (defense + 100.f), 0, defense);
+ statusBars.get(StatusBarType.DEFENSE).updateValues(defense / (defense + 100.f), 0, defense);
StatusBarTracker.Resource speed = statusBarTracker.getSpeed();
- statusBars.get("speed").updateValues(speed.value() / (float) speed.max(), 0, speed.value());
- statusBars.get("experience").updateValues(player.experienceProgress, 0, player.experienceLevel);
+ statusBars.get(StatusBarType.SPEED).updateValues(speed.value() / (float) speed.max(), 0, speed.value());
+ statusBars.get(StatusBarType.EXPERIENCE).updateValues(player.experienceProgress, 0, player.experienceLevel);
return true;
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/fancybars/StatusBarType.java b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/StatusBarType.java
new file mode 100644
index 00000000..0ab924e1
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/fancybars/StatusBarType.java
@@ -0,0 +1,78 @@
+package de.hysky.skyblocker.skyblock.fancybars;
+
+import de.hysky.skyblocker.SkyblockerMod;
+import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.StringIdentifiable;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.*;
+
+public enum StatusBarType implements StringIdentifiable {
+ HEALTH("health", BarPositioner.BarAnchor.HOTBAR_TOP, 0, new Color[]{new Color(255, 0, 0), new Color(255, 220, 0)}, true, new Color(255, 85, 85), Text.translatable("skyblocker.bars.config.health")),
+ INTELLIGENCE("intelligence", BarPositioner.BarAnchor.HOTBAR_TOP, 0, new Color[]{new Color(0, 255, 255), new Color(180, 0, 255)}, true, new Color(85, 255, 255), Text.translatable("skyblocker.bars.config.intelligence")),
+ DEFENSE("defense", BarPositioner.BarAnchor.HOTBAR_RIGHT, 0, new Color[]{new Color(255, 255, 255)}, false, new Color(185, 185, 185), Text.translatable("skyblocker.bars.config.defense")),
+ EXPERIENCE("experience", BarPositioner.BarAnchor.HOTBAR_TOP, 1, new Color[]{new Color(100, 230, 70)}, false, new Color(128, 255, 32), Text.translatable("skyblocker.bars.config.experience")),
+ SPEED("speed", BarPositioner.BarAnchor.HOTBAR_RIGHT, 0, new Color[]{new Color(255, 255, 255)}, false, new Color(185, 185, 185), Text.translatable("skyblocker.bars.config.speed"));
+
+ private final String id;
+ private final BarPositioner.BarAnchor defaultAnchor;
+ private final int defaultGridY;
+ private final Color[] colors;
+ private final boolean hasOverflow;
+ @Nullable
+ private final Color textColor;
+ private final Text name;
+
+ StatusBarType(String id, BarPositioner.BarAnchor defaultAnchor, int defaultGridY, Color[] colors, boolean hasOverflow, @Nullable Color textColor, Text name) {
+ this.id = id;
+ this.defaultAnchor = defaultAnchor;
+ this.defaultGridY = defaultGridY;
+ this.colors = colors;
+ this.hasOverflow = hasOverflow;
+ this.textColor = textColor;
+ this.name = name;
+ }
+
+ public static StatusBarType from(String id) {
+ for (StatusBarType type : values()) {
+ if (type.id.equals(id)) {
+ return type;
+ }
+ }
+ throw new IllegalArgumentException("Unknown status bar type: " + id);
+ }
+
+ @Override
+ public String asString() {
+ return id;
+ }
+
+ public BarPositioner.BarAnchor getDefaultAnchor() {
+ return defaultAnchor;
+ }
+
+ public int getDefaultGridY() {
+ return defaultGridY;
+ }
+
+ public Color[] getColors() {
+ return colors;
+ }
+
+ public boolean hasOverflow() {
+ return hasOverflow;
+ }
+
+ public @Nullable Color getTextColor() {
+ return textColor;
+ }
+
+ public Text getName() {
+ return name;
+ }
+
+ public StatusBar newStatusBar() {
+ return new StatusBar(Identifier.of(SkyblockerMod.NAMESPACE, "bars/icons/" + id), colors, hasOverflow, textColor, name);
+ }
+}