aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
authorKevin <92656833+kevinthegreat1@users.noreply.github.com>2025-07-27 03:04:45 +0800
committerGitHub <noreply@github.com>2025-07-26 15:04:45 -0400
commit75706bd04c8cb1f1cc6126f4348351d9fe34bfc2 (patch)
tree47dea48a182c620f22dcd4989b8c9409b0d706bc /src/main/java
parent8e4cce107291b65ac6f4a6b16d895e196a694db9 (diff)
downloadSkyblocker-75706bd04c8cb1f1cc6126f4348351d9fe34bfc2.tar.gz
Skyblocker-75706bd04c8cb1f1cc6126f4348351d9fe34bfc2.tar.bz2
Skyblocker-75706bd04c8cb1f1cc6126f4348351d9fe34bfc2.zip
Fancy Dungeon Map (#1239)
* Add spirit leap map * Add fancy dungeon map * Fetch profiles on dungeon load and add dungeon loaded event * Add player arrow * Add player marker tracking * Add hover and click to teleport * Apply suggestions * Add show self head option * Only show map in leap overlay while clearing * Add inventory key to close leap overlay * Add leap overlay scaling and migrate config * Add layout widget * Add cache invalidation * keep only the past 100 player matches (5 seconds) * Add player head render helper and clean up RenderHelper * Rename config field * Remove draw head by name * Prevent default decorations * Fix merge conflicts * Delete old FishingHelper.java * Use player list for player detection * Use map decoration list for rendering and account for special player list order * Fix show self head * Improve dungeon player matching error handling * HudHelper not helping
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java50
-rw-r--r--src/main/java/de/hysky/skyblocker/config/configs/DungeonsConfig.java16
-rw-r--r--src/main/java/de/hysky/skyblocker/events/DungeonEvents.java44
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/HandledScreenProviderMixin.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/MapRendererMixin.java22
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/accessors/MapStateAccessor.java14
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/crimson/kuudra/ArrowPoisonWarning.java6
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/crimson/kuudra/DangerWarning.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonClass.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMap.java220
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/LeapOverlay.java174
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/Boulder.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonManager.java23
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java493
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonPlayerManager.java177
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretsTracker.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/fishing/FishingHelper.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/rift/HealingMelonIndicator.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/demonlord/FirePillarAnnouncer.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/vampire/ManiaIndicator.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/vampire/StakeIndicator.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/slayers/boss/vampire/TwinClawsIndicator.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonPlayerWidget.java18
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java65
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/title/TitleContainer.java45
25 files changed, 885 insertions, 516 deletions
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
index 357b31f8..fa67eef8 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
@@ -68,14 +68,6 @@ public class DungeonsCategory {
.controller(ConfigUtils.createBooleanController())
.build())
.option(Option.<Boolean>createBuilder()
- .name(Text.translatable("skyblocker.config.dungeons.spiritLeapOverlay"))
- .description(Text.translatable("skyblocker.config.dungeons.spiritLeapOverlay.@Tooltip"))
- .binding(defaults.dungeons.spiritLeapOverlay,
- () -> config.dungeons.spiritLeapOverlay,
- newValue -> config.dungeons.spiritLeapOverlay = newValue)
- .controller(ConfigUtils.createBooleanController())
- .build())
- .option(Option.<Boolean>createBuilder()
.name(Text.translatable("skyblocker.config.dungeons.starredMobGlow"))
.description(Text.translatable("skyblocker.config.dungeons.starredMobGlow.@Tooltip"))
.binding(defaults.dungeons.starredMobGlow,
@@ -143,6 +135,20 @@ public class DungeonsCategory {
newValue -> config.dungeons.dungeonMap.enableMap = newValue)
.controller(ConfigUtils.createBooleanController())
.build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("skyblocker.config.dungeons.map.fancyMap"))
+ .binding(defaults.dungeons.dungeonMap.fancyMap,
+ () -> config.dungeons.dungeonMap.fancyMap,
+ newValue -> config.dungeons.dungeonMap.fancyMap = newValue)
+ .controller(ConfigUtils.createBooleanController())
+ .build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("skyblocker.config.dungeons.map.showSelfHead"))
+ .binding(defaults.dungeons.dungeonMap.showSelfHead,
+ () -> config.dungeons.dungeonMap.showSelfHead,
+ newValue -> config.dungeons.dungeonMap.showSelfHead = newValue)
+ .controller(ConfigUtils.createBooleanController())
+ .build())
.option(Option.<Float>createBuilder()
.name(Text.translatable("skyblocker.config.dungeons.map.mapScaling"))
.binding(defaults.dungeons.dungeonMap.mapScaling,
@@ -157,6 +163,34 @@ public class DungeonsCategory {
.build())
.build())
+ // Spirit Leap Overlay
+ .group(OptionGroup.createBuilder()
+ .name(Text.translatable("skyblocker.config.dungeons.spiritLeapOverlay"))
+ .collapsed(true)
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("skyblocker.config.dungeons.spiritLeapOverlay.enableLeapOverlay"))
+ .description(Text.translatable("skyblocker.config.dungeons.spiritLeapOverlay.enableLeapOverlay.@Tooltip"))
+ .binding(defaults.dungeons.leapOverlay.enableLeapOverlay,
+ () -> config.dungeons.leapOverlay.enableLeapOverlay,
+ newValue -> config.dungeons.leapOverlay.enableLeapOverlay = newValue)
+ .controller(ConfigUtils.createBooleanController())
+ .build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("skyblocker.config.dungeons.spiritLeapOverlay.showMap"))
+ .binding(defaults.dungeons.leapOverlay.showMap,
+ () -> config.dungeons.leapOverlay.showMap,
+ newValue -> config.dungeons.leapOverlay.showMap = newValue)
+ .controller(ConfigUtils.createBooleanController())
+ .build())
+ .option(Option.<Float>createBuilder()
+ .name(Text.translatable("skyblocker.config.dungeons.spiritLeapOverlay.scale"))
+ .binding(defaults.dungeons.leapOverlay.scale,
+ () -> config.dungeons.leapOverlay.scale,
+ newValue -> config.dungeons.leapOverlay.scale = newValue)
+ .controller(FloatController.createBuilder().range(1f, 2f).slider(0.05f).build())
+ .build())
+ .build())
+
// Puzzle Solver
.group(OptionGroup.createBuilder()
.name(Text.translatable("skyblocker.config.dungeons.puzzle"))
diff --git a/src/main/java/de/hysky/skyblocker/config/configs/DungeonsConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/DungeonsConfig.java
index 81c1cd9b..4fd9ade9 100644
--- a/src/main/java/de/hysky/skyblocker/config/configs/DungeonsConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/configs/DungeonsConfig.java
@@ -19,8 +19,6 @@ public class DungeonsConfig {
public boolean classBasedPlayerGlow = true;
- public boolean spiritLeapOverlay = true;
-
public boolean starredMobGlow = false;
public boolean starredMobBoundingBoxes = true;
@@ -35,6 +33,8 @@ public class DungeonsConfig {
public DungeonMap dungeonMap = new DungeonMap();
+ public SpiritLeapOverlay leapOverlay = new SpiritLeapOverlay();
+
public PuzzleSolvers puzzleSolvers = new PuzzleSolvers();
public TheProfessor theProfessor = new TheProfessor();
@@ -60,6 +60,10 @@ public class DungeonsConfig {
public static class DungeonMap {
public boolean enableMap = true;
+ public boolean fancyMap = true;
+
+ public boolean showSelfHead = true;
+
public float mapScaling = 1f;
public int mapX = 2;
@@ -67,6 +71,14 @@ public class DungeonsConfig {
public int mapY = 2;
}
+ public static class SpiritLeapOverlay {
+ public boolean enableLeapOverlay = true;
+
+ public boolean showMap = true;
+
+ public float scale = 1.2f;
+ }
+
public static class PuzzleSolvers {
public boolean solveTicTacToe = true;
diff --git a/src/main/java/de/hysky/skyblocker/events/DungeonEvents.java b/src/main/java/de/hysky/skyblocker/events/DungeonEvents.java
index 27f9624c..b8448ac3 100644
--- a/src/main/java/de/hysky/skyblocker/events/DungeonEvents.java
+++ b/src/main/java/de/hysky/skyblocker/events/DungeonEvents.java
@@ -7,6 +7,24 @@ import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
public class DungeonEvents {
+ /**
+ * Called when the player loads into a dungeon after the location is sent to the scoreboard.
+ */
+ public static final Event<DungeonLoaded> DUNGEON_LOADED = EventFactory.createArrayBacked(DungeonLoaded.class, callbacks -> () -> {
+ for (DungeonLoaded callback : callbacks) {
+ callback.onDungeonLoaded();
+ }
+ });
+
+ /**
+ * Called after the dungeons starts and after the tab has changed to include additional information about the run such as each player's class.
+ */
+ public static final Event<DungeonStarted> DUNGEON_STARTED = EventFactory.createArrayBacked(DungeonStarted.class, callbacks -> () -> {
+ for (DungeonStarted callback : callbacks) {
+ callback.onDungeonStarted();
+ }
+ });
+
public static final Event<RoomMatched> PUZZLE_MATCHED = EventFactory.createArrayBacked(RoomMatched.class, callbacks -> room -> {
for (RoomMatched callback : callbacks) {
callback.onRoomMatched(room);
@@ -22,28 +40,10 @@ public class DungeonEvents {
}
});
- /**
- * Note: This event fires after the tab has changed to include additional information about the run such as each player's class.
- */
- public static final Event<DungeonStarted> DUNGEON_STARTED = EventFactory.createArrayBacked(DungeonStarted.class, callbacks -> () -> {
- for (DungeonStarted callback : callbacks) {
- callback.onDungeonStarted();
- }
- });
-
- /**
- * Called when the player loads into a dungeon once Mort has been located.
- */
- public static final Event<DungeonLoaded> DUNGEON_LOADED = EventFactory.createArrayBacked(DungeonLoaded.class, callbacks -> () -> {
- for (DungeonLoaded callback : callbacks) {
- callback.onDungeonLoaded();
- }
- });
-
@Environment(EnvType.CLIENT)
@FunctionalInterface
- public interface RoomMatched {
- void onRoomMatched(Room room);
+ public interface DungeonLoaded {
+ void onDungeonLoaded();
}
@Environment(EnvType.CLIENT)
@@ -54,7 +54,7 @@ public class DungeonEvents {
@Environment(EnvType.CLIENT)
@FunctionalInterface
- public interface DungeonLoaded {
- void onDungeonLoaded();
+ public interface RoomMatched {
+ void onRoomMatched(Room room);
}
}
diff --git a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenProviderMixin.java b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenProviderMixin.java
index d1edb51c..29bc6ee9 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenProviderMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenProviderMixin.java
@@ -107,7 +107,7 @@ public interface HandledScreenProviderMixin<T extends ScreenHandler> {
}
// Leap Overlay
- case GenericContainerScreenHandler containerScreenHandler when Utils.isInDungeons() && SkyblockerConfigManager.get().dungeons.spiritLeapOverlay && nameLowercase.contains(LeapOverlay.TITLE.toLowerCase()) -> {
+ case GenericContainerScreenHandler containerScreenHandler when Utils.isInDungeons() && SkyblockerConfigManager.get().dungeons.leapOverlay.enableLeapOverlay && nameLowercase.contains(LeapOverlay.TITLE.toLowerCase()) -> {
client.player.currentScreenHandler = containerScreenHandler;
client.setScreen(new LeapOverlay(containerScreenHandler));
diff --git a/src/main/java/de/hysky/skyblocker/mixins/MapRendererMixin.java b/src/main/java/de/hysky/skyblocker/mixins/MapRendererMixin.java
new file mode 100644
index 00000000..b0af491a
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/mixins/MapRendererMixin.java
@@ -0,0 +1,22 @@
+package de.hysky.skyblocker.mixins;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import com.llamalad7.mixinextras.sugar.Local;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.Utils;
+import net.minecraft.client.render.MapRenderer;
+import net.minecraft.item.map.MapDecoration;
+import net.minecraft.item.map.MapDecorationTypes;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+@Mixin(MapRenderer.class)
+public class MapRendererMixin {
+ @ModifyExpressionValue(method = "createDecoration", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/map/MapDecoration;isAlwaysRendered()Z"))
+ private boolean preventDecorationInDungeons(boolean alwaysRendered, @Local(argsOnly = true) MapDecoration decoration) {
+ // Allow alwaysRendered if
+ // 1. not in dungeons OR
+ // 2. the decoration type is frame (self player) and don't show self head
+ return (!Utils.isInDungeons() || decoration.type().value().equals(MapDecorationTypes.FRAME.value()) && !SkyblockerConfigManager.get().dungeons.dungeonMap.showSelfHead) && alwaysRendered;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/mixins/accessors/MapStateAccessor.java b/src/main/java/de/hysky/skyblocker/mixins/accessors/MapStateAccessor.java
new file mode 100644
index 00000000..cbf7764b
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/mixins/accessors/MapStateAccessor.java
@@ -0,0 +1,14 @@
+package de.hysky.skyblocker.mixins.accessors;
+
+import net.minecraft.item.map.MapDecoration;
+import net.minecraft.item.map.MapState;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+import java.util.Map;
+
+@Mixin(MapState.class)
+public interface MapStateAccessor {
+ @Accessor
+ Map<String, MapDecoration> getDecorations();
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/crimson/kuudra/ArrowPoisonWarning.java b/src/main/java/de/hysky/skyblocker/skyblock/crimson/kuudra/ArrowPoisonWarning.java
index c0d371de..17f6416c 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/crimson/kuudra/ArrowPoisonWarning.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/crimson/kuudra/ArrowPoisonWarning.java
@@ -7,8 +7,8 @@ import de.hysky.skyblocker.config.configs.CrimsonIsleConfig;
import de.hysky.skyblocker.skyblock.crimson.kuudra.Kuudra.KuudraPhase;
import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.Utils;
-import de.hysky.skyblocker.utils.render.RenderHelper;
import de.hysky.skyblocker.utils.render.title.Title;
+import de.hysky.skyblocker.utils.render.title.TitleContainer;
import net.minecraft.client.MinecraftClient;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.BowItem;
@@ -44,9 +44,9 @@ public class ArrowPoisonWarning {
}
if (!hasToxicArrowPoison) {
- RenderHelper.displayInTitleContainerAndPlaySound(NONE_TITLE, THREE_SECONDS);
+ TitleContainer.addTitleAndPlaySound(NONE_TITLE, THREE_SECONDS);
} else if (arrowPoisonAmount < CONFIG.get().arrowPoisonThreshold) {
- RenderHelper.displayInTitleContainerAndPlaySound(LOW_TITLE, THREE_SECONDS);
+ TitleContainer.addTitleAndPlaySound(LOW_TITLE, THREE_SECONDS);
}
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/crimson/kuudra/DangerWarning.java b/src/main/java/de/hysky/skyblocker/skyblock/crimson/kuudra/DangerWarning.java
index 80028405..db9e1e96 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/crimson/kuudra/DangerWarning.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/crimson/kuudra/DangerWarning.java
@@ -5,7 +5,6 @@ import java.util.function.Supplier;
import de.hysky.skyblocker.annotations.Init;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.utils.Utils;
-import de.hysky.skyblocker.utils.render.RenderHelper;
import de.hysky.skyblocker.utils.render.title.Title;
import de.hysky.skyblocker.utils.render.title.TitleContainer;
import de.hysky.skyblocker.utils.scheduler.Scheduler;
@@ -37,7 +36,7 @@ public class DangerWarning {
Title title = getDangerTitle(under);
if (title != null) {
- RenderHelper.displayInTitleContainerAndPlaySound(title);
+ TitleContainer.addTitleAndPlaySound(title);
return;
} else if (i == 5) { //Prevent removing the title prematurely
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonClass.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonClass.java
index 66f03dbe..aeb0d171 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonClass.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonClass.java
@@ -3,6 +3,7 @@ package de.hysky.skyblocker.skyblock.dungeon;
import de.hysky.skyblocker.skyblock.entity.MobGlow;
import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
import net.minecraft.item.ItemStack;
+import net.minecraft.util.math.ColorHelper;
import java.util.Arrays;
import java.util.Map;
@@ -26,7 +27,7 @@ public enum DungeonClass {
DungeonClass(String name, int color, ItemStack icon) {
this.name = name;
- this.color = color;
+ this.color = ColorHelper.fullAlpha(color);
this.icon = icon;
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMap.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMap.java
index 548ba51b..2966b08c 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMap.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMap.java
@@ -3,8 +3,13 @@ package de.hysky.skyblocker.skyblock.dungeon;
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.annotations.Init;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.config.configs.DungeonsConfig;
+import de.hysky.skyblocker.mixins.accessors.MapStateAccessor;
import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager;
+import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonMapUtils;
+import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonPlayerManager;
import de.hysky.skyblocker.utils.Utils;
+import de.hysky.skyblocker.utils.render.RenderHelper;
import de.hysky.skyblocker.utils.scheduler.Scheduler;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
@@ -17,73 +22,174 @@ import net.minecraft.client.render.LightmapTextureManager;
import net.minecraft.client.render.MapRenderState;
import net.minecraft.client.render.MapRenderer;
import net.minecraft.client.render.VertexConsumerProvider;
-import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.MapIdComponent;
+import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.FilledMapItem;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
+import net.minecraft.item.map.MapDecoration;
+import net.minecraft.item.map.MapDecorationTypes;
import net.minecraft.item.map.MapState;
import net.minecraft.util.Identifier;
+import net.minecraft.util.math.RotationAxis;
+import net.minecraft.world.World;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.joml.Vector2d;
+import org.joml.Vector2dc;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.UUID;
public class DungeonMap {
+ private static final Logger LOGGER = LoggerFactory.getLogger(DungeonMap.class);
private static final Identifier DUNGEON_MAP = Identifier.of(SkyblockerMod.NAMESPACE, "dungeon_map");
- private static final MapIdComponent DEFAULT_MAP_ID_COMPONENT = new MapIdComponent(1024);
- private static final MapRenderState MAP_RENDER_STATE = new MapRenderState();
- private static MapIdComponent cachedMapIdComponent = null;
+ private static final MapIdComponent DEFAULT_MAP_ID_COMPONENT = new MapIdComponent(1024);
+ private static final MapRenderState MAP_RENDER_STATE = new MapRenderState();
+ private static MapIdComponent cachedMapIdComponent = null;
- @Init
- public static void init() {
+ @Init
+ public static void init() {
HudLayerRegistrationCallback.EVENT.register(d -> d.attachLayerAfter(IdentifiedLayer.STATUS_EFFECTS, DUNGEON_MAP, (context, tickCounter) -> render(context)));
- ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal("skyblocker")
- .then(ClientCommandManager.literal("hud")
- .then(ClientCommandManager.literal("dungeon")
- .executes(Scheduler.queueOpenScreenCommand(DungeonMapConfigScreen::new))
- )
- )
- ));
- ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> reset());
- }
-
- public static void render(MatrixStack matrices) {
- MinecraftClient client = MinecraftClient.getInstance();
- if (client.player == null || client.world == null) return;
-
- MapIdComponent mapId = getMapIdComponent(client.player.getInventory().getMainStacks().get(8));
-
- MapState state = FilledMapItem.getMapState(mapId, client.world);
- if (state == null) return;
-
- int x = SkyblockerConfigManager.get().dungeons.dungeonMap.mapX;
- int y = SkyblockerConfigManager.get().dungeons.dungeonMap.mapY;
- float scaling = SkyblockerConfigManager.get().dungeons.dungeonMap.mapScaling;
- VertexConsumerProvider.Immediate vertices = client.getBufferBuilders().getEffectVertexConsumers();
- MapRenderer mapRenderer = client.getMapRenderer();
-
- matrices.push();
- matrices.translate(x, y, 0);
- matrices.scale(scaling, scaling, 0f);
- mapRenderer.update(mapId, state, MAP_RENDER_STATE);
- mapRenderer.draw(MAP_RENDER_STATE, matrices, vertices, false, LightmapTextureManager.MAX_LIGHT_COORDINATE);
- vertices.draw();
- matrices.pop();
- }
-
- public static MapIdComponent getMapIdComponent(ItemStack stack) {
- if (stack.isOf(Items.FILLED_MAP) && stack.contains(DataComponentTypes.MAP_ID)) {
- MapIdComponent mapIdComponent = stack.get(DataComponentTypes.MAP_ID);
- cachedMapIdComponent = mapIdComponent;
- return mapIdComponent;
- } else return cachedMapIdComponent != null ? cachedMapIdComponent : DEFAULT_MAP_ID_COMPONENT;
- }
-
- private static void render(DrawContext context) {
- if (Utils.isInDungeons() && DungeonScore.isDungeonStarted() && !DungeonManager.isInBoss() && SkyblockerConfigManager.get().dungeons.dungeonMap.enableMap) {
- render(context.getMatrices());
- }
- }
-
- private static void reset() {
- cachedMapIdComponent = null;
- }
+ ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal("skyblocker")
+ .then(ClientCommandManager.literal("hud")
+ .then(ClientCommandManager.literal("dungeon")
+ .executes(Scheduler.queueOpenScreenCommand(DungeonMapConfigScreen::new))
+ )
+ )
+ ));
+ ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> reset());
+ }
+
+ private static boolean shouldProcess() {
+ return Utils.isInDungeons() && DungeonScore.isDungeonStarted() && !DungeonManager.isInBoss();
+ }
+
+ private static void render(DrawContext context) {
+ DungeonsConfig.DungeonMap dungeonMap = SkyblockerConfigManager.get().dungeons.dungeonMap;
+ if (shouldProcess() && dungeonMap.enableMap) {
+ render(context, dungeonMap.mapX, dungeonMap.mapY, dungeonMap.mapScaling, dungeonMap.fancyMap);
+ }
+ }
+
+ public static void render(DrawContext context, int x, int y, float scale, boolean fancy) {
+ render(context, x, y, scale, fancy, Integer.MIN_VALUE, Integer.MIN_VALUE, null);
+ }
+
+ /**
+ * @return the {@link UUID} of the hovered player head, or null if no player head is hovered.
+ */
+ @Nullable
+ public static UUID render(DrawContext context, int x, int y, float scale, boolean fancy, int mouseX, int mouseY, @Nullable UUID enlarge) {
+ MinecraftClient client = MinecraftClient.getInstance();
+ if (client.player == null || client.world == null) return null;
+
+ MapIdComponent mapId = getMapIdComponent(client.player.getInventory().getMainStacks().get(8));
+ MapState state = FilledMapItem.getMapState(mapId, client.world);
+ if (state == null) return null;
+
+ VertexConsumerProvider.Immediate vertices = client.getBufferBuilders().getEffectVertexConsumers();
+ MapRenderer mapRenderer = client.getMapRenderer();
+
+ context.getMatrices().push();
+ context.getMatrices().translate(x, y, 0);
+ context.getMatrices().scale(scale, scale, 0f);
+ mapRenderer.update(mapId, state, MAP_RENDER_STATE);
+ mapRenderer.draw(MAP_RENDER_STATE, context.getMatrices(), vertices, fancy, LightmapTextureManager.MAX_LIGHT_COORDINATE);
+ vertices.draw();
+
+ UUID hoveredHead = null;
+ if (fancy) hoveredHead = renderPlayerHeads(context, client.world, state, mouseX / scale, mouseY / scale, enlarge);
+ context.getMatrices().pop();
+ return hoveredHead;
+ }
+
+ public static MapIdComponent getMapIdComponent(ItemStack stack) {
+ if (stack.isOf(Items.FILLED_MAP) && stack.contains(DataComponentTypes.MAP_ID)) {
+ MapIdComponent mapIdComponent = stack.get(DataComponentTypes.MAP_ID);
+ cachedMapIdComponent = mapIdComponent;
+ return mapIdComponent;
+ } else return cachedMapIdComponent != null ? cachedMapIdComponent : DEFAULT_MAP_ID_COMPONENT;
+ }
+
+ @Nullable
+ private static UUID renderPlayerHeads(DrawContext context, World world, MapState state, double mouseX, double mouseY, @Nullable UUID enlarge) {
+ if (!DungeonManager.isClearingDungeon()) return null;
+
+ // Used to index through the player list to find which dungeon player corresponds to which map decoration.
+ // Start at 1 because the first entry in the player list is the self player.
+ int i = 1;
+ UUID hovered = null;
+ for (Map.Entry<String, MapDecoration> mapDecoration : ((MapStateAccessor) state).getDecorations().entrySet()) {
+ // Get the corresponding dungeon player for the map decoration.
+ DungeonPlayerManager.DungeonPlayer dungeonPlayer = null;
+ // If the map decoration is the self player, use the first player in this list. The self player is always the first player in the list.
+ if (mapDecoration.getValue().type().value().equals(MapDecorationTypes.FRAME.value())) {
+ if (!SkyblockerConfigManager.get().dungeons.dungeonMap.showSelfHead) continue;
+ dungeonPlayer = DungeonPlayerManager.getPlayers()[0];
+ } else while (i < DungeonPlayerManager.getPlayers().length && (dungeonPlayer == null || !dungeonPlayer.alive())) { // Find the next alive player in the player list.
+ dungeonPlayer = DungeonPlayerManager.getPlayers()[i];
+ i++;
+ }
+
+ // If we still didn't find a valid dungeon player after searching though the entire player list, something is wrong.
+ if (dungeonPlayer == null) {
+ dungeonPlayerError(mapDecoration.getKey(), "not found", i - 1, DungeonPlayerManager.getPlayers(), ((MapStateAccessor) state).getDecorations());
+ continue;
+ } else if (!dungeonPlayer.alive()) {
+ dungeonPlayerError(mapDecoration.getKey(), "not alive", i - 1, DungeonPlayerManager.getPlayers(), ((MapStateAccessor) state).getDecorations());
+ continue;
+ } else if (dungeonPlayer.uuid() == null) {
+ dungeonPlayerError(mapDecoration.getKey(), "has null uuid", i - 1, DungeonPlayerManager.getPlayers(), ((MapStateAccessor) state).getDecorations());
+ continue;
+ }
+ PlayerRenderState player = PlayerRenderState.of(world, dungeonPlayer, mapDecoration.getValue());
+
+ // Actually render the player head
+ context.getMatrices().push();
+ context.getMatrices().translate(player.mapPos().x(), player.mapPos().y(), 0);
+ context.getMatrices().multiply(RotationAxis.POSITIVE_Z.rotationDegrees(player.deg() + 180));
+
+ if (player.uuid().equals(enlarge)) {
+ // Enlarge the player head when the corresponding button is hovered
+ context.getMatrices().scale(2, 2, 1);
+ } else if (hovered == null && isPlayerHovered(player, mouseX, mouseY)) {
+ // Enlarge the player head when hovered
+ context.getMatrices().scale(2, 2, 1);
+ hovered = player.uuid();
+ }
+ RenderHelper.drawPlayerHead(context, -4, -4, 8, player.uuid());
+ context.drawBorder(-5, -5, 10, 10, dungeonPlayer.dungeonClass().color());
+ context.fill(-1, -7, 1, -5, dungeonPlayer.dungeonClass().color());
+ context.getMatrices().pop();
+ }
+ return hovered;
+ }
+
+ private static void dungeonPlayerError(String decorationId, String reason, int i, DungeonPlayerManager.DungeonPlayer[] dungeonPlayers, Map<String, MapDecoration> mapDecorations) {
+ LOGGER.error("[Skyblocker Dungeon Map] Dungeon player for map decoration '{}' {}. Player list index (zero-indexed): {}. Player list: {}. Map decorations: {}", decorationId, reason, i, Arrays.toString(dungeonPlayers), mapDecorations);
+ }
+
+ public static boolean isPlayerHovered(PlayerRenderState player, double mouseX, double mouseY) {
+ return player.mapPos().distanceSquared(mouseX, mouseY) < 16;
+ }
+
+ private static void reset() {
+ cachedMapIdComponent = null;
+ }
+
+ public record PlayerRenderState(UUID uuid, String name, Vector2dc mapPos, float deg) {
+ public static PlayerRenderState of(@NotNull World world, @NotNull DungeonPlayerManager.DungeonPlayer dungeonPlayer, @NotNull MapDecoration mapDecoration) {
+ // Use the player entity if it exists, since it gives the most accurate position and rotation
+ PlayerEntity playerEntity = world.getPlayerByUuid(dungeonPlayer.uuid());
+ Vector2dc mapPos = playerEntity != null ? DungeonMapUtils.getMapPosFromPhysical(DungeonManager.getPhysicalEntrancePos(), DungeonManager.getMapEntrancePos(), DungeonManager.getMapRoomSize(), playerEntity.getPos()) : new Vector2d(mapDecoration.x() / 2d + 64, mapDecoration.z() / 2d + 64);
+ float deg = playerEntity != null ? playerEntity.getYaw() : mapDecoration.rotation() * 360 / 16.0F;
+
+ return new PlayerRenderState(dungeonPlayer.uuid(), dungeonPlayer.name(), mapPos, deg);
+ }
+ }
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/LeapOverlay.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/LeapOverlay.java
index 25e7e16d..c7915890 100644
--- a/