aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerMod.java17
-rw-r--r--src/main/java/de/hysky/skyblocker/config/ConfigUtils.java20
-rw-r--r--src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java48
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java113
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java21
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/MessageFilterCategory.java8
-rw-r--r--src/main/java/de/hysky/skyblocker/config/controllers/EnumDropdownController.java93
-rw-r--r--src/main/java/de/hysky/skyblocker/config/controllers/EnumDropdownControllerBuilder.java27
-rw-r--r--src/main/java/de/hysky/skyblocker/config/controllers/EnumDropdownControllerBuilderImpl.java27
-rw-r--r--src/main/java/de/hysky/skyblocker/config/controllers/EnumDropdownControllerElement.java26
-rw-r--r--src/main/java/de/hysky/skyblocker/debug/Debug.java27
-rw-r--r--src/main/java/de/hysky/skyblocker/debug/DumpPlayersCommand.java31
-rw-r--r--src/main/java/de/hysky/skyblocker/events/ClientPlayerBlockBreakEvent.java23
-rw-r--r--src/main/java/de/hysky/skyblocker/events/DungeonEvents.java30
-rw-r--r--src/main/java/de/hysky/skyblocker/mixin/BatEntityMixin.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java23
-rw-r--r--src/main/java/de/hysky/skyblocker/mixin/ClientPlayerEntityMixin.java5
-rw-r--r--src/main/java/de/hysky/skyblocker/mixin/ClientPlayerInteractionManagerMixin.java27
-rw-r--r--src/main/java/de/hysky/skyblocker/mixin/DataTrackerMixin.java19
-rw-r--r--src/main/java/de/hysky/skyblocker/mixin/DrawContextMixin.java6
-rw-r--r--src/main/java/de/hysky/skyblocker/mixin/EntityRenderDispatcherMixin.java18
-rw-r--r--src/main/java/de/hysky/skyblocker/mixin/ItemStackMixin.java6
-rw-r--r--src/main/java/de/hysky/skyblocker/mixin/LeverBlockMixin.java6
-rw-r--r--src/main/java/de/hysky/skyblocker/mixin/LivingEntityRendererMixin.java18
-rw-r--r--src/main/java/de/hysky/skyblocker/mixin/MinecraftClientMixin.java36
-rw-r--r--src/main/java/de/hysky/skyblocker/mixin/PlayerListHudMixin.java8
-rw-r--r--src/main/java/de/hysky/skyblocker/mixin/PlayerSkinTextureMixin.java40
-rw-r--r--src/main/java/de/hysky/skyblocker/mixin/accessor/ItemStackAccessor.java14
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java17
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/Tips.java106
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java76
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/LividColor.java30
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/CreeperBeams.java (renamed from src/main/java/de/hysky/skyblocker/skyblock/dungeon/CreeperBeams.java)52
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/DungeonBlaze.java (renamed from src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonBlaze.java)30
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/DungeonPuzzle.java59
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/ThreeWeirdos.java (renamed from src/main/java/de/hysky/skyblocker/skyblock/dungeon/ThreeWeirdos.java)2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/TicTacToe.java (renamed from src/main/java/de/hysky/skyblocker/skyblock/dungeon/TicTacToe.java)35
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/Trivia.java (renamed from src/main/java/de/hysky/skyblocker/skyblock/dungeon/Trivia.java)29
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/waterboard/Cell.java51
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/waterboard/Switch.java39
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/waterboard/Waterboard.java451
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DebugRoom.java60
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonManager.java (renamed from src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java)164
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java204
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java17
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/filters/ToggleSkyMallFilter.java16
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/ItemCooldowns.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/ItemRarityBackgrounds.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/PlayerHeadHashCache.java64
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockItemRarity.java1
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java45
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigListWidget.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/FireSaleWidget.java15
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/waypoint/MythologicalRitual.java18
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/BasePlaceholderScreen.java19
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/ItemUtils.java107
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/JoinWorldPlaceholderScreen.java9
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/ReconfiguringPlaceholderScreen.java22
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/Tickable.java7
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/chat/ChatMessageListener.java7
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java7
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/Renderable.java7
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/culling/OcclusionCuller.java45
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/culling/OcclusionCulling.java42
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/culling/ReducedWorldProvider.java19
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/culling/WorldProvider.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/scheduler/MessageScheduler.java20
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/scheduler/Scheduler.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java18
71 files changed, 2036 insertions, 635 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
index 1e3c4e7d..681e24f3 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
@@ -5,9 +5,12 @@ import com.google.gson.GsonBuilder;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.debug.Debug;
import de.hysky.skyblocker.skyblock.*;
-import de.hysky.skyblocker.skyblock.waypoint.MythologicalRitual;
import de.hysky.skyblocker.skyblock.dungeon.*;
-import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonSecrets;
+import de.hysky.skyblocker.skyblock.dungeon.puzzle.CreeperBeams;
+import de.hysky.skyblocker.skyblock.dungeon.puzzle.DungeonBlaze;
+import de.hysky.skyblocker.skyblock.dungeon.puzzle.TicTacToe;
+import de.hysky.skyblocker.skyblock.dungeon.puzzle.waterboard.Waterboard;
+import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager;
import de.hysky.skyblocker.skyblock.dungeon.secrets.SecretsTracker;
import de.hysky.skyblocker.skyblock.dwarven.DwarvenHud;
import de.hysky.skyblocker.skyblock.item.*;
@@ -18,11 +21,12 @@ import de.hysky.skyblocker.skyblock.quicknav.QuickNav;
import de.hysky.skyblocker.skyblock.rift.TheRift;
import de.hysky.skyblocker.skyblock.shortcut.Shortcuts;
import de.hysky.skyblocker.skyblock.special.SpecialEffects;
-import de.hysky.skyblocker.skyblock.waypoint.Relics;
import de.hysky.skyblocker.skyblock.tabhud.TabHud;
import de.hysky.skyblocker.skyblock.tabhud.screenbuilder.ScreenMaster;
import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
import de.hysky.skyblocker.skyblock.waypoint.FairySouls;
+import de.hysky.skyblocker.skyblock.waypoint.MythologicalRitual;
+import de.hysky.skyblocker.skyblock.waypoint.Relics;
import de.hysky.skyblocker.utils.ApiUtils;
import de.hysky.skyblocker.utils.NEURepoManager;
import de.hysky.skyblocker.utils.Utils;
@@ -79,8 +83,10 @@ public class SkyblockerMod implements ClientModInitializer {
ClientTickEvents.END_CLIENT_TICK.register(this::tick);
Utils.init();
SkyblockerConfigManager.init();
+ Tips.init();
NEURepoManager.init();
ItemRepository.init();
+ PlayerHeadHashCache.init();
HotbarSlotLock.init();
ItemTooltip.init();
WikiLookup.init();
@@ -98,8 +104,10 @@ public class SkyblockerMod implements ClientModInitializer {
FishingHelper.init();
TabHud.init();
DungeonMap.init();
- DungeonSecrets.init();
+ DungeonManager.init();
DungeonBlaze.init();
+ Waterboard.init();
+ DungeonScore.init();
ChestValue.init();
FireFreezeStaffTimer.init();
GuardianHealth.init();
@@ -127,7 +135,6 @@ public class SkyblockerMod implements ClientModInitializer {
statusBarTracker.init();
Scheduler.INSTANCE.scheduleCyclic(Utils::update, 20);
Scheduler.INSTANCE.scheduleCyclic(DiscordRPCManager::updateDataAndPresence, 200);
- Scheduler.INSTANCE.scheduleCyclic(TicTacToe::tick, 4);
Scheduler.INSTANCE.scheduleCyclic(LividColor::update, 10);
Scheduler.INSTANCE.scheduleCyclic(BackpackPreview::tick, 50);
Scheduler.INSTANCE.scheduleCyclic(DwarvenHud::update, 40);
diff --git a/src/main/java/de/hysky/skyblocker/config/ConfigUtils.java b/src/main/java/de/hysky/skyblocker/config/ConfigUtils.java
index 9a7a41b5..8b0f27a7 100644
--- a/src/main/java/de/hysky/skyblocker/config/ConfigUtils.java
+++ b/src/main/java/de/hysky/skyblocker/config/ConfigUtils.java
@@ -1,9 +1,7 @@
package de.hysky.skyblocker.config;
import dev.isxander.yacl3.api.Option;
-import dev.isxander.yacl3.api.controller.BooleanControllerBuilder;
-import dev.isxander.yacl3.api.controller.EnumControllerBuilder;
-import dev.isxander.yacl3.api.controller.ValueFormatter;
+import dev.isxander.yacl3.api.controller.*;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import org.apache.commons.lang3.StringUtils;
@@ -11,7 +9,7 @@ import org.apache.commons.lang3.StringUtils;
import java.util.function.Function;
public class ConfigUtils {
- public static final Function<Formatting, String> FORMATTING_TO_STRING = formatting -> StringUtils.capitalize(formatting.getName().replaceAll("_", " "));
+ public static final ValueFormatter<Formatting> FORMATTING_FORMATTER = formatting -> Text.literal(StringUtils.capitalize(formatting.getName().replaceAll("_", " ")));
public static final ValueFormatter<Float> FLOAT_TWO_FORMATTER = value -> Text.literal(String.format("%,.2f", value).replaceAll("[\u00a0\u202F]", " "));
public static BooleanControllerBuilder createBooleanController(Option<Boolean> opt) {
@@ -22,4 +20,18 @@ public class ConfigUtils {
public static <E extends Enum<E>> EnumControllerBuilder<E> createEnumCyclingListController(Option<E> opt) {
return EnumControllerBuilder.create(opt).enumClass((Class<E>) opt.binding().defaultValue().getClass());
}
+
+ /**
+ * Creates a factory for {@link EnumDropdownControllerBuilder}s with the given function for converting enum constants to texts.
+ * Use this if a custom formatter function for an enum is needed.
+ * Use it like this:
+ * <pre>{@code Option.<MyEnum>createBuilder().controller(ConfigUtils.getEnumDropdownControllerFactory(MY_CUSTOM_ENUM_TO_TEXT_FUNCTION))}</pre>
+ *
+ * @param formatter The function used to convert enum constants to texts used for display, suggestion, and validation
+ * @param <E> the enum type
+ * @return a factory for {@link EnumDropdownControllerBuilder}s
+ */
+ public static <E extends Enum<E>> Function<Option<E>, ControllerBuilder<E>> getEnumDropdownControllerFactory(ValueFormatter<E> formatter) {
+ return opt -> EnumDropdownControllerBuilder.create(opt).formatValue(formatter);
+ }
}
diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
index 8604913c..609e7c2f 100644
--- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java
@@ -150,6 +150,9 @@ public class SkyblockerConfig {
public static class General {
@SerialEntry
+ public boolean enableTips = true;
+
+ @SerialEntry
public boolean acceptReparty = true;
@SerialEntry
@@ -163,6 +166,9 @@ public class SkyblockerConfig {
@SerialEntry
public boolean hideStatusEffectOverlay = false;
+
+ @SerialEntry
+ public boolean dontStripSkinAlphaValues = true;
@SerialEntry
public TabHudConf tabHud = new TabHudConf();
@@ -591,6 +597,9 @@ public class SkyblockerConfig {
public DoorHighlight doorHighlight = new DoorHighlight();
@SerialEntry
+ public DungeonScore dungeonScore = new DungeonScore();
+
+ @SerialEntry
public DungeonChestProfit dungeonChestProfit = new DungeonChestProfit();
@SerialEntry
@@ -630,6 +639,9 @@ public class SkyblockerConfig {
public boolean solveTicTacToe = true;
@SerialEntry
+ public boolean solveWaterboard = true;
+
+ @SerialEntry
public boolean fireFreezeStaffTimer = true;
@SerialEntry
@@ -644,10 +656,10 @@ public class SkyblockerConfig {
public static class SecretWaypoints {
@SerialEntry
- public boolean enableSecretWaypoints = true;
+ public boolean enableRoomMatching = true;
@SerialEntry
- public boolean noInitSecretWaypoints = false;
+ public boolean enableSecretWaypoints = true;
@SerialEntry
public Waypoint.Type waypointType = Waypoint.Type.WAYPOINT;
@@ -715,6 +727,32 @@ public class SkyblockerConfig {
}
}
+ public static class DungeonScore {
+ @SerialEntry
+ public boolean enableDungeonScore270Message = false;
+
+ @SerialEntry
+ public boolean enableDungeonScore270Title = false;
+
+ @SerialEntry
+ public boolean enableDungeonScore270Sound = false;
+
+ @SerialEntry
+ public String dungeonScore270Message = "270 Score Reached!";
+
+ @SerialEntry
+ public boolean enableDungeonScore300Message = true;
+
+ @SerialEntry
+ public boolean enableDungeonScore300Title = true;
+
+ @SerialEntry
+ public boolean enableDungeonScore300Sound = true;
+
+ @SerialEntry
+ public String dungeonScore300Message = "300 Score Reached!";
+ }
+
public static class DungeonChestProfit {
@SerialEntry
public boolean enableProfitCalculator = true;
@@ -749,6 +787,9 @@ public class SkyblockerConfig {
public boolean enableLividColorText = true;
@SerialEntry
+ public boolean enableLividColorTitle = true;
+
+ @SerialEntry
public String lividColorText = "The livid color is [color]";
}
@@ -920,6 +961,9 @@ public class SkyblockerConfig {
public ChatFilterResult hideShowOff = ChatFilterResult.PASS;
@SerialEntry
+ public ChatFilterResult hideToggleSkyMall = ChatFilterResult.PASS;
+
+ @SerialEntry
public boolean hideMana = false;
}
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 246611cc..ad64e2e4 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
@@ -12,7 +12,6 @@ import dev.isxander.yacl3.api.OptionGroup;
import dev.isxander.yacl3.api.controller.FloatFieldControllerBuilder;
import dev.isxander.yacl3.api.controller.IntegerFieldControllerBuilder;
import dev.isxander.yacl3.api.controller.StringControllerBuilder;
-import de.hysky.skyblocker.config.controllers.EnumDropdownControllerBuilder;
import de.hysky.skyblocker.skyblock.dungeon.DungeonMapConfigScreen;
import net.minecraft.client.MinecraftClient;
import net.minecraft.text.Text;
@@ -29,21 +28,21 @@ public class DungeonsCategory {
.name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints"))
.collapsed(true)
.option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.enableRoomMatching"))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.enableRoomMatching.@Tooltip")))
+ .binding(defaults.locations.dungeons.secretWaypoints.enableRoomMatching,
+ () -> config.locations.dungeons.secretWaypoints.enableRoomMatching,
+ newValue -> config.locations.dungeons.secretWaypoints.enableRoomMatching = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .flag(OptionFlag.GAME_RESTART)
+ .build())
+ .option(Option.<Boolean>createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.enableSecretWaypoints"))
.binding(defaults.locations.dungeons.secretWaypoints.enableSecretWaypoints,
() -> config.locations.dungeons.secretWaypoints.enableSecretWaypoints,
newValue -> config.locations.dungeons.secretWaypoints.enableSecretWaypoints = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
- .option(Option.<Boolean>createBuilder()
- .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.noInitSecretWaypoints"))
- .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.secretWaypoints.noInitSecretWaypoints.@Tooltip")))
- .binding(defaults.locations.dungeons.secretWaypoints.noInitSecretWaypoints,
- () -> config.locations.dungeons.secretWaypoints.noInitSecretWaypoints,
- newValue -> config.locations.dungeons.secretWaypoints.noInitSecretWaypoints = newValue)
- .controller(ConfigUtils::createBooleanController)
- .flag(OptionFlag.GAME_RESTART)
- .build())
.option(Option.<Type>createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.general.waypoints.waypointType"))
.description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.general.waypoints.waypointType.@Tooltip")))
@@ -168,6 +167,76 @@ public class DungeonsCategory {
.build())
.build())
+ //Dungeon Score
+ .group(OptionGroup.createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore"))
+ .collapsed(true)
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.enableDungeonScoreMessage", 270))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.enableDungeonScoreMessage.@Tooltip", 270)))
+ .binding(defaults.locations.dungeons.dungeonScore.enableDungeonScore270Message,
+ () -> config.locations.dungeons.dungeonScore.enableDungeonScore270Message,
+ newValue -> config.locations.dungeons.dungeonScore.enableDungeonScore270Message = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.enableDungeonScoreTitle", 270))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.enableDungeonScoreTitle.@Tooltip", 270)))
+ .binding(defaults.locations.dungeons.dungeonScore.enableDungeonScore270Title,
+ () -> config.locations.dungeons.dungeonScore.enableDungeonScore270Title,
+ newValue -> config.locations.dungeons.dungeonScore.enableDungeonScore270Title = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.enableDungeonScoreSound", 270))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.enableDungeonScoreSound.@Tooltip", 270)))
+ .binding(defaults.locations.dungeons.dungeonScore.enableDungeonScore270Sound,
+ () -> config.locations.dungeons.dungeonScore.enableDungeonScore270Sound,
+ newValue -> config.locations.dungeons.dungeonScore.enableDungeonScore270Sound = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<String>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.dungeonScoreMessage", 270))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.dungeonScoreMessage.@Tooltip", 270, 270)))
+ .binding(defaults.locations.dungeons.dungeonScore.dungeonScore270Message,
+ () -> config.locations.dungeons.dungeonScore.dungeonScore270Message,
+ newValue -> config.locations.dungeons.dungeonScore.dungeonScore270Message = newValue)
+ .controller(StringControllerBuilder::create)
+ .build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.enableDungeonScoreMessage", 300))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.enableDungeonScoreMessage.@Tooltip", 300)))
+ .binding(defaults.locations.dungeons.dungeonScore.enableDungeonScore300Message,
+ () -> config.locations.dungeons.dungeonScore.enableDungeonScore300Message,
+ newValue -> config.locations.dungeons.dungeonScore.enableDungeonScore300Message = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.enableDungeonScoreTitle", 300))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.enableDungeonScoreTitle.@Tooltip", 300)))
+ .binding(defaults.locations.dungeons.dungeonScore.enableDungeonScore300Title,
+ () -> config.locations.dungeons.dungeonScore.enableDungeonScore300Title,
+ newValue -> config.locations.dungeons.dungeonScore.enableDungeonScore300Title = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.enableDungeonScoreSound", 300))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.enableDungeonScoreSound.@Tooltip", 300)))
+ .binding(defaults.locations.dungeons.dungeonScore.enableDungeonScore300Sound,
+ () -> config.locations.dungeons.dungeonScore.enableDungeonScore300Sound,
+ newValue -> config.locations.dungeons.dungeonScore.enableDungeonScore300Sound = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<String>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.dungeonScoreMessage", 300))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonScore.dungeonScoreMessage.@Tooltip", 300, 300)))
+ .binding(defaults.locations.dungeons.dungeonScore.dungeonScore300Message,
+ () -> config.locations.dungeons.dungeonScore.dungeonScore300Message,
+ newValue -> config.locations.dungeons.dungeonScore.dungeonScore300Message = newValue)
+ .controller(StringControllerBuilder::create)
+ .build())
+ .build())
+
//Dungeon Chest Profit
.group(OptionGroup.createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit"))
@@ -209,21 +278,21 @@ public class DungeonsCategory {
.binding(defaults.locations.dungeons.dungeonChestProfit.neutralColor,
() -> config.locations.dungeons.dungeonChestProfit.neutralColor,
newValue -> config.locations.dungeons.dungeonChestProfit.neutralColor = newValue)
- .controller(EnumDropdownControllerBuilder.getFactory(ConfigUtils.FORMATTING_TO_STRING))
+ .controller(ConfigUtils.getEnumDropdownControllerFactory(ConfigUtils.FORMATTING_FORMATTER))
.build())
.option(Option.<Formatting>createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.profitColor"))
.binding(defaults.locations.dungeons.dungeonChestProfit.profitColor,
() -> config.locations.dungeons.dungeonChestProfit.profitColor,
newValue -> config.locations.dungeons.dungeonChestProfit.profitColor = newValue)
- .controller(EnumDropdownControllerBuilder.getFactory(ConfigUtils.FORMATTING_TO_STRING))
+ .controller(ConfigUtils.getEnumDropdownControllerFactory(ConfigUtils.FORMATTING_FORMATTER))
.build())
.option(Option.<Formatting>createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.dungeonChestProfit.lossColor"))
.binding(defaults.locations.dungeons.dungeonChestProfit.lossColor,
() -> config.locations.dungeons.dungeonChestProfit.lossColor,
newValue -> config.locations.dungeons.dungeonChestProfit.lossColor = newValue)
- .controller(EnumDropdownControllerBuilder.getFactory(ConfigUtils.FORMATTING_TO_STRING))
+ .controller(ConfigUtils.getEnumDropdownControllerFactory(ConfigUtils.FORMATTING_FORMATTER))
.build())
.option(Option.<Formatting>createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.general.chestValue.incompleteColor"))
@@ -231,7 +300,7 @@ public class DungeonsCategory {
.binding(defaults.locations.dungeons.dungeonChestProfit.incompleteColor,
() -> config.locations.dungeons.dungeonChestProfit.incompleteColor,
newValue -> config.locations.dungeons.dungeonChestProfit.incompleteColor = newValue)
- .controller(EnumDropdownControllerBuilder.getFactory(ConfigUtils.FORMATTING_TO_STRING))
+ .controller(ConfigUtils.getEnumDropdownControllerFactory(ConfigUtils.FORMATTING_FORMATTER))
.build())
.build())
@@ -318,6 +387,14 @@ public class DungeonsCategory {
.controller(ConfigUtils::createBooleanController)
.build())
.option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.solveWaterboard"))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.solveWaterboard.@Tooltip")))
+ .binding(defaults.locations.dungeons.solveWaterboard,
+ () -> config.locations.dungeons.solveWaterboard,
+ newValue -> config.locations.dungeons.solveWaterboard = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Boolean>createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.fireFreezeStaffTimer"))
.description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.fireFreezeStaffTimer.@Tooltip")))
.binding(defaults.locations.dungeons.fireFreezeStaffTimer,
@@ -354,6 +431,14 @@ public class DungeonsCategory {
newValue -> config.locations.dungeons.lividColor.enableLividColorText = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.lividColor.enableLividColorTitle"))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.lividColor.enableLividColorTitle.@Tooltip")))
+ .binding(defaults.locations.dungeons.lividColor.enableLividColorTitle,
+ () -> config.locations.dungeons.lividColor.enableLividColorTitle,
+ newValue -> config.locations.dungeons.lividColor.enableLividColorTitle = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
.option(Option.<String>createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.lividColor.lividColorText"))
.description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.lividColor.lividColorText.@Tooltip")))
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java
index 8d8c6f46..3b9cbe60 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java
@@ -2,7 +2,6 @@ package de.hysky.skyblocker.config.categories;
import de.hysky.skyblocker.config.ConfigUtils;
import de.hysky.skyblocker.config.SkyblockerConfig;
-import de.hysky.skyblocker.config.controllers.EnumDropdownControllerBuilder;
import de.hysky.skyblocker.skyblock.shortcut.ShortcutsConfigScreen;
import de.hysky.skyblocker.utils.render.title.TitleContainerConfigScreen;
import de.hysky.skyblocker.utils.waypoint.Waypoint;
@@ -22,6 +21,13 @@ public class GeneralCategory {
//Ungrouped Options
.option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.general.enableTips"))
+ .binding(defaults.general.enableTips,
+ () -> config.general.enableTips,
+ newValue -> config.general.enableTips = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Boolean>createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.general.acceptReparty"))
.binding(defaults.general.acceptReparty,
() -> config.general.acceptReparty,
@@ -56,6 +62,15 @@ public class GeneralCategory {
newValue -> config.general.hideStatusEffectOverlay = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.general.dontStripSkinAlphaValues"))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.general.dontStripSkinAlphaValues.@Tooltip")))
+ .binding(defaults.general.dontStripSkinAlphaValues,
+ () -> config.general.dontStripSkinAlphaValues,
+ newValue -> config.general.dontStripSkinAlphaValues = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .flag(OptionFlag.ASSET_RELOAD)
+ .build())
//Tab Hud
.group(OptionGroup.createBuilder()
@@ -474,7 +489,7 @@ public class GeneralCategory {
.binding(defaults.general.chestValue.color,
() -> config.general.chestValue.color,
newValue -> config.general.chestValue.color = newValue)
- .controller(EnumDropdownControllerBuilder.getFactory(ConfigUtils.FORMATTING_TO_STRING))
+ .controller(ConfigUtils.getEnumDropdownControllerFactory(ConfigUtils.FORMATTING_FORMATTER))
.build())
.option(Option.<Formatting>createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.general.chestValue.incompleteColor"))
@@ -482,7 +497,7 @@ public class GeneralCategory {
.binding(defaults.general.chestValue.incompleteColor,
() -> config.general.chestValue.incompleteColor,
newValue -> config.general.chestValue.incompleteColor = newValue)
- .controller(EnumDropdownControllerBuilder.getFactory(ConfigUtils.FORMATTING_TO_STRING))
+ .controller(ConfigUtils.getEnumDropdownControllerFactory(ConfigUtils.FORMATTING_FORMATTER))
.build())
.build())
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/MessageFilterCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/MessageFilterCategory.java
index c63b933d..37f24d8c 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/MessageFilterCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/MessageFilterCategory.java
@@ -87,6 +87,14 @@ public class MessageFilterCategory {
newValue -> config.messages.hideShowOff = newValue)
.controller(ConfigUtils::createEnumCyclingListController)
.build())
+ .option(Option.<ChatFilterResult>createBuilder()
+ .name(Text.translatable("text.autoconfig.skyblocker.option.messages.hideToggleSkyMall"))
+ .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.messages.hideToggleSkyMall.@Tooltip")))
+ .binding(defaults.messages.hideToggleSkyMall,
+ () -> config.messages.hideToggleSkyMall,
+ newValue -> config.messages.hideToggleSkyMall = newValue)
+ .controller(ConfigUtils::createEnumCyclingListController)
+ .build())
.option(Option.<Boolean>createBuilder()
.name(Text.translatable("text.autoconfig.skyblocker.option.messages.hideMana"))
.binding(defaults.messages.hideMana,
diff --git a/src/main/java/de/hysky/skyblocker/config/controllers/EnumDropdownController.java b/src/main/java/de/hysky/skyblocker/config/controllers/EnumDropdownController.java
deleted file mode 100644
index 0b9a809d..00000000
--- a/src/main/java/de/hysky/skyblocker/config/controllers/EnumDropdownController.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package de.hysky.skyblocker.config.controllers;
-
-import dev.isxander.yacl3.api.Option;
-import dev.isxander.yacl3.api.utils.Dimension;
-import dev.isxander.yacl3.gui.AbstractWidget;
-import dev.isxander.yacl3.gui.YACLScreen;
-import dev.isxander.yacl3.gui.controllers.dropdown.AbstractDropdownController;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Arrays;
-import java.util.function.Function;
-import java.util.stream.Stream;
-
-public class EnumDropdownController<E extends Enum<E>> extends AbstractDropdownController<E> {
- /**
- * The function used to convert enum constants to strings used for display, suggestion, and validation. Defaults to {@link Enum#toString}.
- */
- protected final Function<E, String> toString;
-
- protected EnumDropdownController(Option<E> option, Function<E, String> toString) {
- super(option);
- this.toString = toString;
- }
-
- @Override
- public String getString() {
- return toString.apply(option().pendingValue());
- }
-
- @Override
- public void setFromString(String value) {
- option().requestSet(getEnumFromString(value));
- }
-
- /**
- * Searches through enum constants for one whose {@link #toString} result equals {@code value}
- *
- * @return The enum constant associated with the {@code value} or the pending value if none are found
- * @implNote The return value of {@link #toString} on each enum constant should be unique in order to ensure accuracy
- */
- private E getEnumFromString(String value) {
- value = value.toLowerCase();
- for (E constant : option().pendingValue().getDeclaringClass().getEnumConstants()) {
- if (toString.apply(constant).toLowerCase().equals(value)) return constant;
- }
-
- return option().pendingValue();
- }
-
- @Override
- public boolean isValueValid(String value) {
- value = value.toLowerCase();
- for (E constant : option().pendingValue().getDeclaringClass().getEnumConstants()) {
- if (toString.apply(constant).equals(value)) return true;
- }
-
- return false;
- }
-
- @Override
- protected String getValidValue(String value, int offset) {
- return getValidEnumConstants(value)
- .skip(offset)
- .findFirst()
- .orElseGet(this::getString);
- }
-
- /**
- * Filters and sorts through enum constants for those whose {@link #toString} result equals {@code value}
- *
- * @return a sorted stream containing enum constants associated with the {@code value}
- * @implNote The return value of {@link #toString} on each enum constant should be unique in order to ensure accuracy
- */
- @NotNull
- protected Stream<String> getValidEnumConstants(String value) {
- String valueLowerCase = value.toLowerCase();
- return Arrays.stream(option().pendingValue().getDeclaringClass().getEnumConstants())
- .map(this.toString)
- .filter(constant -> constant.toLowerCase().contains(valueLowerCase))
- .sorted((s1, s2) -> {
- String s1LowerCase = s1.toLowerCase();
- String s2LowerCase = s2.toLowerCase();
- if (s1LowerCase.startsWith(valueLowerCase) && !s2LowerCase.startsWith(valueLowerCase)) return -1;
- if (!s1LowerCase.startsWith(valueLowerCase) && s2LowerCase.startsWith(valueLowerCase)) return 1;
- return s1.compareTo(s2);
- });
- }
-
- @Override
- public AbstractWidget provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) {
- return new EnumDropdownControllerElement<>(this, screen, widgetDimension);
- }
-}
diff --git a/src/main/java/de/hysky/skyblocker/config/controllers/EnumDropdownControllerBuilder.java b/src/main/java/de/hysky/skyblocker/config/controllers/EnumDropdownControllerBuilder.java
deleted file mode 100644
index d451a88c..00000000
--- a/src/main/java/de/hysky/skyblocker/config/controllers/EnumDropdownControllerBuilder.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package de.hysky.skyblocker.config.controllers;
-
-import dev.isxander.yacl3.api.Option;
-import dev.isxander.yacl3.api.controller.ControllerBuilder;
-
-import java.util.function.Function;
-
-public interface EnumDropdownControllerBuilder<E extends Enum<E>> extends ControllerBuilder<E> {
- EnumDropdownControllerBuilder<E> toString(Function<E, String> toString);
-
- static <E extends Enum<E>> EnumDropdownControllerBuilder<E> create(Option<E> option) {
- return new EnumDropdownControllerBuilderImpl<>(option);
- }
-
- /**
- * Creates a factory for {@link EnumDropdownControllerBuilder}s with the given function for converting enum constants to strings.
- * Use this if a custom toString function for an enum is needed.
- * Use it like this:
- * <pre>{@code Option.<MyEnum>createBuilder().controller(createEnumDropdownControllerBuilder.getFactory(MY_CUSTOM_ENUM_TO_STRING_FUNCTION))}</pre>
- * @param toString The function used to convert enum constants to strings used for display, suggestion, and validation
- * @return a factory for {@link EnumDropdownControllerBuilder}s
- * @param <E> the enum type
- */
- static <E extends Enum<E>> Function<Option<E>, ControllerBuilder<E>> getFactory(Function<E, String> toString) {
- return opt -> EnumDropdownControllerBuilder.create(opt).toString(toString);
- }
-}
diff --git a/src/main/java/de/hysky/skyblocker/config/controllers/EnumDropdownControllerBuilderImpl.java b/src/main/java/de/hysky/skyblocker/config/controllers/EnumDropdownControllerBuilderImpl.java
deleted file mode 100644
index 8f6dbb2a..00000000
--- a/src/main/java/de/hysky/skyblocker/config/controllers/EnumDropdownControllerBuilderImpl.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package de.hysky.skyblocker.config.controllers;
-
-import dev.isxander.yacl3.api.Controller;
-import dev.isxander.yacl3.api.Option;
-import dev.isxander.yacl3.impl.controller.AbstractControllerBuilderImpl;
-
-import java.util.function.Function;
-
-public class EnumDropdownControllerBuilderImpl<E extends Enum<E>> extends AbstractControllerBuilderImpl<E> implements EnumDropdownControllerBuilder<E> {
- private Function<E, String> toString = Enum::toString;
-
- public EnumDropdownControllerBuilderImpl(Option<E> option) {
- super(option);
- }
-
- @Override
- public EnumDropdownControllerBuilder<E> toString(Function<E, String> toString) {
- this.toString = toString;
- return this;
- }
-
- @SuppressWarnings("UnstableApiUsage")
- @Override
- public Controller<E> build() {
- return new EnumDropdownController<>(option, toString);
- }
-}
diff --git a/src/main/java/de/hysky/skyblocker/config/controllers/EnumDropdownControllerElement.java b/src/main/java/de/hysky/skyblocker/config/controllers/EnumDropdownControllerElement.java
deleted file mode 100644
index 2a8de609..00000000
--- a/src/main/java/de/hysky/skyblocker/config/controllers/EnumDropdownControllerElement.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package de.hysky.skyblocker.config.controllers;
-
-import dev.isxander.yacl3.api.utils.Dimension;
-import dev.isxander.yacl3.gui.YACLScreen;
-import dev.isxander.yacl3.gui.controllers.dropdown.AbstractDropdownControllerElement;
-
-import java.util.List;
-
-public class EnumDropdownControllerElement<E extends Enum<E>> extends AbstractDropdownControllerElement<E, String> {
- private final EnumDropdownController<E> controller;
-
- public EnumDropdownControllerElement(EnumDropdownController<E> control, YACLScreen screen, Dimension<Integer> dim) {
- super(control, screen, dim);
- this.controller = control;
- }
-
- @Override
- public List<String> computeMatchingValues() {
- return controller.getValidEnumConstants(inputField).toList();
- }
-
- @Override
- public String getString(String object) {
- return object;
- }
-}
diff --git a/src/main/java/de/hysky/skyblocker/debug/Debug.java b/src/main/java/de/hysky/skyblocker/debug/Debug.java
index 1fc22d2a..86adcac6 100644
--- a/src/main/java/de/hysky/skyblocker/debug/Debug.java
+++ b/src/main/java/de/hysky/skyblocker/debug/Debug.java
@@ -1,14 +1,37 @@
package de.hysky.skyblocker.debug;
+import com.mojang.brigadier.Command;
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.utils.ItemUtils;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
+import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.fabricmc.loader.api.FabricLoader;
+import net.minecraft.text.Text;
+
+import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
public class Debug {
private static final boolean DEBUG_ENABLED = Boolean.parseBoolean(System.getProperty("skyblocker.debug", "false"));
+ public static boolean debugEnabled() {
+ return DEBUG_ENABLED || FabricLoader.getInstance().isDevelopmentEnvironment();
+ }
+
public static void init() {
- if (DEBUG_ENABLED || FabricLoader.getInstance().isDevelopmentEnvironment()) {
- ClientCommandRegistrationCallback.EVENT.register(DumpPlayersCommand::register);
+ if (debugEnabled()) {
+ ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("debug")
+ .then(dumpPlayersCommand())
+ .then(ItemUtils.dumpHeldItemNbtCommand())
+ )));
}
}
+
+ private static LiteralArgumentBuilder<FabricClientCommandSource> dumpPlayersCommand() {
+ return literal("dumpPlayers")
+ .executes(context -> {
+ context.getSource().getWorld().getPlayers().forEach(player -> context.getSource().sendFeedback(Text.of("'" + player.getName().getString() + "'")));
+ return Command.SINGLE_SUCCESS;
+ });
+ }
}
diff --git a/src/main/java/de/hysky/skyblocker/debug/DumpPlayersCommand.java b/src/main/java/de/hysky/skyblocker/debug/DumpPlayersCommand.java
deleted file mode 100644
index 5f6e0362..00000000
--- a/src/main/java/de/hysky/skyblocker/debug/DumpPlayersCommand.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package de.hysky.skyblocker.debug;
-
-import com.mojang.brigadier.Command;
-import com.mojang.brigadier.CommandDispatcher;
-import de.hysky.skyblocker.SkyblockerMod;
-import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
-import net.minecraft.command.CommandRegistryAccess;
-import net.minecraft.entity.player.PlayerEntity;
-import net.minecraft.text.Text;
-
-import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
-
-public class DumpPlayersCommand {
-
- static void register(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) {
- dispatcher.register(literal(SkyblockerMod.NAMESPACE)
- .then(literal("debug")
- .then(literal("dumpPlayers")
- .executes(context -> {
- FabricClientCommandSource source = context.getSource();
-
- source.getWorld().getEntities().forEach(e -> {
- if (e instanceof PlayerEntity player) {
- source.sendFeedback(Text.of("'" + player.getName().getString() + "'"));
- }
- });
-
- return Command.SINGLE_SUCCESS;
- }))));
- }
-}
diff --git a/src/main/java/de/hysky/skyblocker/events/ClientPlayerBlockBreakEvent.java b/src/main/java/de/hysky/skyblocker/events/ClientPlayerBlockBreakEvent.java
deleted file mode 100644
index 83ac716f..00000000
--- a/src/main/java/de/hysky/skyblocker/events/ClientPlayerBlockBreakEvent.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package de.hysky.skyblocker.events;
-
-import net.fabricmc.fabric.api.event.Event;
-import net.fabricmc.fabric.api.event.EventFactory;
-import net.minecraft.block.BlockState;
-import net.minecraft.entity.player.PlayerEntity;
-import net.minecraft.util.math.BlockPos;
-import net.minecraft.world.World;
-
-// Fabric API currently doesn't have an event for this
-public class ClientPlayerBlockBreakEvent {
- public static final Event<AfterBlockBreak> AFTER = EventFactory.createArrayBacked(AfterBlockBreak.class,
- (listeners) -> (world, player, pos, state) -> {
- for (AfterBlockBreak listener : listeners) {
- listener.afterBlockBreak(world, player, pos, state);
- }
- });
-
- @FunctionalInterface
- public interface AfterBlockBreak {
- void afterBlockBreak(World world, PlayerEntity player, BlockPos pos, BlockState state);
- }
-}
diff --git a/src/main/java/de/hysky/skyblocker/events/DungeonEvents.java b/src/main/java/de/hysky/skyblocker/events/DungeonEvents.java
new file mode 100644
index 00000000..9efa3607
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/events/DungeonEvents.java
@@ -0,0 +1,30 @@
+package de.hysky.skyblocker.events;
+
+import de.hysky.skyblocker.skyblock.dungeon.secrets.Room;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.fabric.api.event.Event;
+import net.fabricmc.fabric.api.event.EventFactory;
+
+public class DungeonEvents {
+ public static final Event<RoomMatched> PUZZLE_MATCHED = EventFactory.createArrayBacked(RoomMatched.class, callbacks -> room -> {
+ for (RoomMatched callback : callbacks) {
+ callback.onRoomMatched(room);
+ }
+ });
+
+ public static final Event<RoomMatched> ROOM_MATCHED = EventFactory.createArrayBacked(RoomMatched.class, callbacks -> room -> {
+ for (RoomMatched callback : callbacks) {
+ callback.onRoomMatched(room);
+ }
+ if (room.getType() == Room.Type.PUZZLE) {
+ PUZZLE_MATCHED.invoker().onRoomMatched(room);
+ }
+ });
+
+ @Environment(EnvType.CLIENT)
+ @FunctionalInterface
+ public interface RoomMatched {
+ void onRoomMatched(Room room);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/mixin/BatEntityMixin.java b/src/main/java/de/hysky/skyblocker/mixin/BatEntityMixin.java
index dc2fa673..fa97e546 100644
--- a/src/main/java/de/hysky/skyblocker/mixin/BatEntityMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixin/BatEntityMixin.java
@@ -1,6 +1,6 @@
package de.hysky.skyblocker.mixin;
-import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonSecrets;
+import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.mob.AmbientEntity;
import net.minecraft.entity.passive.BatEntity;
@@ -16,6 +16,6 @@ public abstract class BatEntityMixin extends AmbientEntity {
@Override
public void onRemoved() {
super.onRemoved();
- DungeonSecrets.onBatRemoved(this);
+ DungeonManager.onBatRemoved(this);
}
}
diff --git a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java
index f177d2f8..4015dfa5 100644
--- a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java
@@ -3,32 +3,35 @@ package de.hysky.skyblocker.mixin;
import com.llamalad7.mixinextras.injector.WrapWithCondition;
import com.llamalad7.mixinextras.sugar.Local;
import de.hysky.skyblocker.skyblock.FishingHelper;
+import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager;
import de.hysky.skyblocker.skyblock.waypoint.MythologicalRitual;
-import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonSecrets;
import de.hysky.skyblocker.utils.Utils;
-import dev.cbyrne.betterinject.annotations.Inject;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.network.packet.s2c.play.ParticleS2CPacket;
import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket;
+import net.minecraft.util.Identifier;
+
import org.slf4j.Logger;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ClientPlayNetworkHandler.class)
public abstract class ClientPlayNetworkHandlerMixin {
@Inject(method = "onPlaySound", at = @At("RETURN"))
- private void skyblocker$onPlaySound(PlaySoundS2CPacket packet) {
+ private void skyblocker$onPlaySound(PlaySoundS2CPacket packet, CallbackInfo ci) {
FishingHelper.onSound(packet);
}
@ModifyVariable(method = "onItemPickupAnimation", at = @At(value = "STORE", ordinal = 0))
private ItemEntity skyblocker$onItemPickup(ItemEntity itemEntity, @Local LivingEntity collector) {
- DungeonSecrets.onItemPickup(itemEntity, collector, collector == MinecraftClient.getInstance().player);
+ DungeonManager.onItemPickup(itemEntity, collector, collector == MinecraftClient.getInstance().player);
return itemEntity;
}
@@ -46,9 +49,19 @@ public abstract class ClientPlayNetworkHandlerMixin {
private boolean skyblocker$cancelTeamWarning(Logger instance, String format, Object... arg) {
return !Utils.isOnHypixel();
}
+
+ @WrapWithCondition(method = { "onScoreboardScoreUpdate", "onScoreboardScoreReset" }, at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;)V", remap = false))
+ private boolean skyblocker$cancelUnknownScoreboardObjectiveWarnings(Logger instance, String message, Object objectiveName) {
+ return !Utils.isOnHypixel();
+ }
+
+ @WrapWithCondition(method = "warnOnUnknownPayload", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;)V", remap = false))
+ private boolean skyblocker$dropBadlionPacketWarnings(Logger instance, String message, Object identifier) {
+ return !(Utils.isOnHypixel() && ((Identifier) identifier).getNamespace().equals("badlion"));
+ }
@Inject(method = "onParticle", at = @At("RETURN"))
- private void skyblocker$onParticle(ParticleS2CPacket packet) {
+ private void skyblocker$onParticle(ParticleS2CPacket packet, CallbackInfo ci) {
MythologicalRitual.onParticle(packet);
}
}
diff --git a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerEntityMixin.java b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerEntityMixin.java
index 6813d654..4abadcea 100644
--- a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerEntityMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerEntityMixin.java
@@ -2,7 +2,6 @@ package de.hysky.skyblocker.mixin;
import com.mojang.authlib.GameProfile;
-import dev.cbyrne.betterinject.annotations.Inject;
import de.hysky.skyblocker.skyblock.item.HotbarSlotLock;
import de.hysky.skyblocker.skyblock.item.ItemProtection;
import de.hysky.skyblocker.skyblock.rift.HealingMelonIndicator;
@@ -12,6 +11,8 @@ import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.world.ClientWorld;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ClientPlayerEntity.class)
@@ -29,7 +30,7 @@ public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntity
}
@Inject(method = "updateHealth", at = @At("RETURN"))
- public void skyblocker$updateHealth() {
+ public void skyblocker$updateHealth(CallbackInfo ci) {
HealingMelonIndicator.updateHealth();
}
} \ No newline at end of file
diff --git a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerInteractionManagerMixin.java b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerInteractionManagerMixin.java
deleted file mode 100644
index fab9a1ea..00000000
--- a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayerInteractionManagerMixin.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package de.hysky.skyblocker.mixin;
-
-import de.hysky.skyblocker.events.ClientPlayerBlockBreakEvent;
-import net.minecraft.block.BlockState;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.network.ClientPlayerInteractionManager;
-import net.minecraft.util.math.BlockPos;
-import net.minecraft.world.World;
-import org.spongepowered.asm.mixin.Final;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Shadow;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
-import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
-
-@Mixin(ClientPlayerInteractionManager.class)
-public class ClientPlayerInteractionManagerMixin {
- @Shadow
- @Final
- private MinecraftClient client;
-
- @Inject(method = "breakBlock", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/Block;onBroken(Lnet/minecraft/world/WorldAccess;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;)V"), locals = LocalCapture.CAPTURE_FAILHARD)
- private void skyblocker$onBlockBroken(BlockPos pos, CallbackInfoReturnable<Boolean> cir, World world, BlockState blockState) {
- ClientPlayerBlockBreakEvent.AFTER.invoker().afterBlockBreak(world, this.client.player, pos, blockState);
- }
-}
diff --git a/src/main/java/de/hysky/skyblocker/mixin/DataTrackerMixin.java b/src/main/java/de/hysky/skyblocker/mixin/DataTrackerMixin.java
new file mode 100644
index 00000000..03786876
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/mixin/DataTrackerMixin.java
@@ -0,0 +1,19 @@
+package de.hysky.skyblocker.mixin;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+import de.hysky.skyblocker.utils.Utils;
+import net.minecraft.entity.data.DataTracker;
+
+@Mixin(DataTracker.class)
+public class DataTrackerMixin {
+
+ @Inject(method = "copyToFrom", at = @At(value = "NEW", target = "Ljava/lang/IllegalStateException;", shift = At.Shift.BEFORE), cancellable = true)
+ public void skyblocker$ignoreInvalidDataExceptions(CallbackInfo ci) {
+ //These exceptions cause annoying small lag spikes for some reason
+ if (Utils.isOnHypixel()) ci.cancel();
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/mixin/DrawContextMixin.java b/src/main/java/de/hysky/skyblocker/mixin/DrawContextMixin.java
index 10666874..b33a8b23 100644
--- a/src/main/java/de/hysky/skyblocker/mixin/DrawContextMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixin/DrawContextMixin.java
@@ -8,8 +8,6 @@ import de.hysky.skyblocker.skyblock.item.AttributeShards;
import de.hysky.skyblocker.skyblock.item.ItemCooldowns;
import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.Utils;
-import dev.cbyrne.betterinject.annotations.Arg;
-import dev.cbyrne.betterinject.annotations.Inject;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.util.math.MatrixStack;
@@ -21,6 +19,8 @@ import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(DrawContext.class)
public abstract class DrawContextMixin {
@@ -32,7 +32,7 @@ public abstract class DrawContextMixin {
public abstract int drawText(TextRenderer textRenderer, @Nullable String text, int x, int y, int color, boolean shadow);
@Inject(method = "drawItemInSlot(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V", at = @At("HEAD"))
- private void skyblocker$renderAttributeShardDisplay(@Arg TextRenderer textRenderer, @Arg ItemStack stack, @Arg(ordinal = 0) int x, @Arg(ordinal = 1) int y, @Local(argsOnly = true) LocalRef<String> countOverride) {
+ private void skyblocker$renderAttributeShardDisplay(CallbackInfo ci, @Local(argsOnly = true) TextRenderer textRenderer, @Local(argsOnly = true) ItemStack stack, @Local(argsOnly = true, ordinal = 0) int x, @Local(argsOnly = true, ordinal = 1) int y, @Local(argsOnly = true) LocalRef<String> countOverride) {
if (!SkyblockerConfigManager.get().general.itemInfoDisplay.attributeShardInfo) return;
if (Utils.isOnSkyblock()) {
diff --git a/src/main/java/de/hysky/skyblocker/mixin/EntityRenderDispatcherMixin.java b/src/main/java/de/hysky/skyblocker/mixin/EntityRenderDispatcherMixin.java
new file mode 100644
index 00000000..5cf88588
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/mixin/EntityRenderDispatcherMixin.java
@@ -0,0 +1,18 @@
+package de.hysky.skyblocker.mixin;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import de.hysky.skyblocker.debug.Debug;
+import de.hysky.skyblocker.utils.Utils;
+import net.minecraft.client.render.entity.EntityRenderDispatcher;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.decoration.ArmorStandEntity;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+@Mixin(EntityRenderDispatcher.class)
+public class EntityRenderDispatcherMixin {
+ @ModifyExpressionValue(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;isInvisible()Z", ordinal = 1))
+ private <E extends Entity> boolean skyblocker$armorStandHitboxVisible(boolean invisible, E entity) {
+ return (!(entity instanceof ArmorStandEntity) || !Utils.isOnHypixel() || !Debug.debugEnabled()) && invisible;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/mixin/ItemStackMixin.java b/src/main/java/de/hysky/skyblocker/mixin/ItemStackMixin.java
index c5b2438a..2a1ed125 100644
--- a/src/main/java/de/hysky/skyblocker/mixin/ItemStackMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixin/ItemStackMixin.java
@@ -1,11 +1,9 @@
package de.hysky.skyblocker.mixin;
-
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.Utils;
-import dev.cbyrne.betterinject.annotations.Inject;
import it.unimi.dsi.fastutil.ints.IntIntPair;
import net.minecraft.item.ItemStack;
import net.minecraft.text.Text;
@@ -13,6 +11,8 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ItemStack.class)
public abstract class ItemStackMixin {
@@ -39,7 +39,7 @@ public abstract class ItemStackMixin {
* Updates the durability of this item stack every tick when in the inventory.
*/
@Inject(method = "inventoryTick", at = @At("TAIL"))
- private void skyblocker$updateDamage() {
+ private void skyblocker$updateDamage(CallbackInfo ci) {
if (!skyblocker$shouldProcess()) {
return;
}
diff --git a/src/main/java/de/hysky/skyblocker/mixin/LeverBlockMixin.java b/src/main/java/de/hysky/skyblocker/mixin/LeverBlockMixin.java
index 97c0a7c0..a1cfe44b 100644
--- a/src/main/java/de/hysky/skyblocker/mixin/LeverBlockMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixin/LeverBlockMixin.java
@@ -8,10 +8,10 @@ import net.minecraft.block.WallMountedBlock;
import net.minecraft.util.shape.VoxelShape;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
-import dev.cbyrne.betterinject.annotations.Arg;
-import dev.cbyrne.betterinject.annotations.Inject;
+import com.llamalad7.mixinextras.sugar.Local;
@Mixin(LeverBlock.class)
public abstract class LeverBlockMixin extends WallMountedBlock {
@@ -20,7 +20,7 @@ public abstract class LeverBlockMixin extends WallMountedBlock {
}
@Inject(method = "getOutlineShape", at = @At("HEAD"), cancellable = true)
- public void skyblocker$onGetOutlineShape(@Arg BlockState state, CallbackInfoReturnable<VoxelShape> cir) {
+ public void skyblocker$onGetOutlineShape(CallbackInfoReturnable<VoxelShape> cir, @Local(argsOnly = true) BlockState state) {
if (Utils.isOnSkyblock()) {
VoxelShape shape = OldLever.getShape(state.get(FACE), state.get(FACING));
if (shape != null) cir.setReturnValue(shape);
diff --git a/src/main/java/de/hysky/skyblocker/mixin/LivingEntityRendererMixin.java b/src/main/java/de/hysky/skyblocker/mixin/LivingEntityRendererMixin.java
new file mode 100644
index 00000000..cf927f0c
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/mixin/LivingEntityRendererMixin.java
@@ -0,0 +1,18 @@
+package de.hysky.skyblocker.mixin;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import de.hysky.skyblocker.debug.Debug;
+import de.hysky.skyblocker.utils.Utils;
+import net.minecraft.client.render.entity.LivingEntityRenderer;
+import net.minecraft.entity.LivingEntity;
+import net.minecraft.entity.decoration.ArmorStandEntity;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+@Mixin(LivingEntityRenderer.class)
+public class LivingEntityRendererMixin {
+ @ModifyExpressionValue(method = "render(Lnet/minecraft/entity/LivingEntity;FFLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/LivingEntityRenderer;isVisible(Lnet/minecraft/entity/LivingEntity;)Z"))
+ private <T extends LivingEntity> boolean skyblocker$armorStandVisible(boolean visible, T entity) {
+ return entity instanceof ArmorStandEntity && Utils.isOnHypixel() && Debug.debugEnabled() || visible;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/mixin/MinecraftClientMixin.java b/src/main/java/de/hysky/skyblocker/mixin/MinecraftClientMixin.java
index 29889c28..cf8aab78 100644
--- a/src/main/java/de/hysky/skyblocker/mixin/MinecraftClientMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixin/MinecraftClientMixin.java
@@ -1,25 +1,57 @@
package de.hysky.skyblocker.mixin;
import de.hysky.skyblocker.skyblock.item.HotbarSlotLock;
+import de.hysky.skyblocker.utils.JoinWorldPlaceholderScreen;
+import de.hysky.skyblocker.utils.ReconfiguringPlaceholderScreen;
import de.hysky.skyblocker.utils.Utils;
import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.screen.DownloadingTerrainScreen;
+import net.minecraft.client.gui.screen.ReconfiguringScreen;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.ClientPlayerEntity;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
-import dev.cbyrne.betterinject.annotations.Inject;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.ModifyArg;
+import org.spongepowered.asm.mixin.injection.ModifyVariable;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(MinecraftClient.class)
public abstract class MinecraftClientMixin {
+
+ @Shadow
+ @Nullable
+ public abstract ClientPlayNetworkHandler getNetworkHandler();
+
@Shadow
@Nullable
public ClientPlayerEntity player;
@Inject(method = "handleInputEvents", at = @At("HEAD"))
- public void skyblocker$handleInputEvents() {
+ public void skyblocker$handleInputEvents(CallbackInfo ci) {
if (Utils.isOnSkyblock()) {
HotbarSlotLock.handleInputEvents(player);
}
}
+
+ //Remove Downloading Terrain Screen and Reconfiguring Screen
+ @ModifyVariable(at = @At("HEAD"), method = "setScreen", ordinal = 0, argsOnly = true)
+ public Screen modifySetScreen(Screen screen) {
+ if (Utils.isOnSkyblock()) {
+ if (screen instanceof DownloadingTerrainScreen) {
+ return null;
+ } else if (screen instanceof ReconfiguringScreen && this.getNetworkHandler() != null) {
+ return new ReconfiguringPlaceholderScreen(this.getNetworkHandler().getConnection());
+ }
+ }
+ return screen;
+ }
+
+ @ModifyArg(method = "joinWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;reset(Lnet/minecraft/client/gui/screen/Screen;)V"), index = 0)
+ private Screen modifyJoinWorld(Screen screen) {
+ return Utils.isOnSkyblock() ? new JoinWorldPlaceholderScreen() : screen;
+ }
} \ No newline at end of file
diff --git a/src/main/java/de/hysky/skyblocker/mixin/PlayerListHudMixin.java b/src/main/java/de/hysky/skyblocker/mixin/PlayerListHudMixin.java
index 7330b1c1..9a9589d1 100644
--- a/src/main/java/de/hysky/skyblocker/mixin/PlayerListHudMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixin/PlayerListHudMixin.java
@@ -15,10 +15,10 @@ import net.minecraft.text.Text;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-import dev.cbyrne.betterinject.annotations.Arg;
-import dev.cbyrne.betterinject.annotations.Inject;
+import com.llamalad7.mixinextras.sugar.Local;
@Environment(EnvType.CLIENT)
@Mixin(PlayerListHud.class)
@@ -26,8 +26,8 @@ public class PlayerListHudMixin {
@Shadow
private Text footer;
- @Inject(at = @At("HEAD"), method = "render(Lnet/minecraft/client/gui/DrawContext;ILnet/minecraft/scoreboard/Scoreboard;Lnet/minecraft/scoreboard/ScoreboardObjective;)V", cancellable = true)
- public void skyblocker$renderTabHud(@Arg DrawContext context, @Arg int w, CallbackInfo info) {
+ @Inject(at = @At("HEAD"), method = "render", cancellable = true)
+ public void skyblocker$renderTabHud(CallbackInfo info, @Local(argsOnly = true) DrawContext context, @Local(argsOnly = true) int w) {
if (!Utils.isOnSkyblock() || !SkyblockerConfigManager.get().general.tabHud.tabHudEnabled || TabHud.defaultTgl.isPressed()) {
return;
}
diff --git a/src/main/java/de/hysky/skyblocker/mixin/PlayerSkinTextureMixin.java b/src/main/java/de/hysky/skyblocker/mixin/PlayerSkinTextureMixin.java
new file mode 100644
index 00000000..50f478b8
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/mixin/PlayerSkinTextureMixin.java
@@ -0,0 +1,40 @@
+package de.hysky.skyblocker.mixin;
+
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+import com.llamalad7.mixinextras.injector.WrapWithCondition;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.item.PlayerHeadHashCache;
+import de.hysky.skyblocker.utils.Utils;
+import net.minecraft.client.texture.NativeImage;
+import net.minecraft.client.texture.PlayerSkinTexture;
+
+@Mixin(PlayerSkinTexture.class)
+public class PlayerSkinTextureMixin {
+ @Shadow
+ @Final
+ private String url;
+
+ @Unique
+ private boolean isSkyblockSkinTexture;
+
+ @Inject(method = "remapTexture", at = @At("HEAD"))
+ private void skyblocker$determineSkinSource(CallbackInfoReturnable<NativeImage> cir) {
+ if (Utils.isOnSkyblock()) {
+ int skinHash = PlayerHeadHashCache.getSkinHash(this.url).hashCode();
+ this.isSkyblockSkinTexture = PlayerHeadHashCache.contains(skinHash);
+ }
+ }
+
+ @WrapWithCondition(method = "remapTexture", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/texture/PlayerSkinTexture;stripAlpha(Lnet/minecraft/client/texture/NativeImage;IIII)V"))
+ private boolean skyblocker$dontStripAlphaValues(NativeImage image, int x1, int y1, int x2, int y2) {
+ return !(SkyblockerConfigManager.get().general.dontStripSkinAlphaValues && this.isSkyblockSkinTexture);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/mixin/accessor/ItemStackAccessor.java b/src/main/java/de/hysky/skyblocker/mixin/accessor/ItemStackAccessor.java
new file mode 100644
index 00000000..e3bd69aa
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/mixin/accessor/ItemStackAccessor.java
@@ -0,0 +1,14 @@
+package de.hysky.skyblocker.mixin.accessor;
+
+import net.minecraft.item.ItemStack;
+import net.minecraft.text.Style;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+@Mixin(ItemStack.class)
+public interface ItemStackAccessor {
+ @Accessor
+ static Style getLORE_STYLE() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java b/src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java
index 956f1008..5cdca216 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/ChestValue.java
@@ -20,7 +20,6 @@ import net.minecraft.item.ItemStack;
import net.minecraft.screen.GenericContainerScreenHandler;
import net.minecraft.screen.slot.Slot;
import net.minecraft.text.Text;
-import net.minecraft.util.Formatting;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -210,21 +209,13 @@ public class ChestValue {
return stack.getTooltip(client.player, TooltipContext.BASIC).stream().map(Text::getString).filter(line -> line.contains(searchString)).findAny().orElse(null);
}
- private static Text getProfitText(long profit, boolean hasIncompleteData) {
+ static Text getProfitText(long profit, boolean hasIncompleteData) {
SkyblockerConfig.DungeonChestProfit config = SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit;
- return getProfitText(profit, hasIncompleteData, config.neutralThreshold, config.neutralColor, config.profitColor, config.lossColor, config.incompleteColor);
+ return Text.literal((profit > 0 ? " +" : ' ') + FORMATTER.format(profit) + " Coins").formatted(hasIncompleteData ? config.incompleteColor : (Math.abs(profit) < config.neutralThreshold) ? config.neutralColor : (profit > 0) ? config.profitColor : config.lossColor);
}
- static Text getProfitText(long profit, boolean hasIncompleteData, int neutralThreshold, Formatting neutralColor, Formatting profitColor, Formatting lossColor, Formatting incompleteColor) {
- return Text.literal((profit > 0 ? " +" : ' ') + FORMATTER.format(profit) + " Coins").formatted(hasIncompleteData ? incompleteColor : (Math.abs(profit) < neutralThreshold) ? neutralColor : (profit > 0) ? profitColor : lossColor);
- }
-
- private static Text getValueText(long value, boolean hasIncompleteData) {
+ static Text getValueText(long value, boolean hasIncompleteData) {
SkyblockerConfig.ChestValue config = SkyblockerConfigManager.get().general.chestValue;
- return getValueText(value, hasIncompleteData, config.color, config.incompleteColor);
- }
-
- static Text getValueText(long value, boolean hasIncompleteData, Formatting color, Formatting incompleteColor) {
- return Text.literal(' ' + FORMATTER.format(value) + " Coins").formatted(hasIncompleteData ? incompleteColor : color);
+ return Text.literal(' ' + FORMATTER.format(value) + " Coins").formatted(hasIncompleteData ? config.incompleteColor : config.color);
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/Tips.java b/src/main/java/de/hysky/skyblocker/skyblock/Tips.java
new file mode 100644
index 00000000..94a5fe14
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/Tips.java
@@ -0,0 +1,106 @@
+package de.hysky.skyblocker.skyblock;
+
+import com.mojang.brigadier.Command;
+import com.mojang.brigadier.CommandDispatcher;
+import com.mojang.brigadier.context.CommandContext;
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.events.SkyblockEvents;
+import de.hysky.skyblocker.utils.Constants;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
+import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.command.CommandRegistryAccess;
+import net.minecraft.text.ClickEvent;
+import net.minecraft.text.Text;
+
+import java.util.List;
+import java.util.Random;
+import java.util.function.Supplier;
+
+import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
+
+public class Tips {
+ private static final Random RANDOM = new Random();
+ private static final List<Supplier<Text>> TIPS = List.of(
+ getTipFactory("skyblocker.tips.customItemNames", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker custom renameItem"),
+ getTipFactory("skyblocker.tips.customArmorDyeColors", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker custom dyeColor"),
+ getTipFactory("skyblocker.tips.customArmorTrims", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker custom armorTrim"),
+ getTipFactory("skyblocker.tips.fancyTabExtraInfo"),
+ getTipFactory("skyblocker.tips.helpCommand", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker help"),
+ getTipFactory("skyblocker.tips.discordRichPresence", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker config"),
+ getTipFactory("skyblocker.tips.customDungeonSecretWaypoints", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker dungeons secrets addWaypoint"),
+ getTipFactory("skyblocker.tips.shortcuts", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker shortcuts"),
+ getTipFactory("skyblocker.tips.gallery", ClickEvent.Action.OPEN_URL, "https://hysky.de/skyblocker/gallery"),
+ getTipFactory("skyblocker.tips.itemRarityBackground", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker config"),
+ getTipFactory("skyblocker.tips.modMenuUpdate"),
+ getTipFactory("skyblocker.tips.issues", ClickEvent.Action.OPEN_URL, "https://github.com/SkyblockerMod/Skyblocker"),
+ getTipFactory("skyblocker.tips.beta", ClickEvent.Action.OPEN_URL, "https://github.com/SkyblockerMod/Skyblocker/actions"),
+ getTipFactory("skyblocker.tips.discord", ClickEvent.Action.OPEN_URL, "https://discord.gg/aNNJHQykck"),
+ getTipFactory("skyblocker.tips.flameOverlay", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker config"),
+ getTipFactory("skyblocker.tips.wikiLookup", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker config"),
+ getTipFactory("skyblocker.tips.fairySoulsEnigmaSoulsRelics", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker fairySouls"),
+ getTipFactory("skyblocker.tips.quickNav", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker config")
+ );
+
+ private static boolean sentTip = false;
+
+ private static Supplier<Text> getTipFactory(String key) {
+ return () -> Text.translatable(key);
+ }
+
+ private static Supplier<Text> getTipFactory(String key, ClickEvent.Action clickAction, String value) {
+ return () -> Text.translatable(key).styled(style -> style.withClickEvent(new ClickEvent(clickAction, value)));
+ }
+
+ public static void init() {
+ ClientCommandRegistrationCallback.EVENT.register(Tips::registerTipsCommand);
+ SkyblockEvents.JOIN.register(Tips::sendNextTip);
+ }
+
+ private static void registerTipsCommand(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) {
+ dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("tips")
+ .then(literal("enable").executes(Tips::enableTips))
+ .then(literal("disable").executes(Tips::disableTips))
+ .then(literal("next").executes(Tips::nextTip))
+ ));
+ }
+
+ private static void sendNextTip() {
+ MinecraftClient client = MinecraftClient.getInstance();
+ if (client.player != null && SkyblockerConfigManager.get().general.enableTips && !sentTip) {
+ client.player.sendMessage(nextTip(), false);
+ sentTip = true;
+ }
+ }
+
+ private static int enableTips(CommandContext<FabricClientCommandSource> context) {
+ SkyblockerConfigManager.get().general.enableTips = true;
+ SkyblockerConfigManager.save();
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.tips.enabled")).append(" ").append(Text.translatable("skyblocker.tips.clickDisable").styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/skyblocker tips disable")))));
+ return Command.SINGLE_SUCCESS;
+ }
+
+ private static int disableTips(CommandContext<FabricClientCommandSource> context) {
+ SkyblockerConfigManager.get().general.enableTips = false;
+ SkyblockerConfigManager.save();
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.tips.disabled")).append(" ").append(Text.translatable("skyblocker.tips.clickEnable").styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/skyblocker tips enable")))));
+ return Command.SINGLE_SUCCESS;
+ }
+
+ private static int nextTip(CommandContext<FabricClientCommandSource> context) {
+ context.getSource().sendFeedback(nextTip());
+ return Command.SINGLE_SUCCESS;
+ }
+
+ private static Text nextTip() {
+ return Constants.PREFIX.get().append(Text.translatable("skyblocker.tips.tip", nextTipInternal()))
+ .append(Text.translatable("skyblocker.tips.clickNextTip").styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/skyblocker tips next"))))
+ .append(" ")
+ .append(Text.translatable("skyblocker.tips.clickDisable").styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/skyblocker tips disable"))));
+ }
+
+ private static Text nextTipInternal() {
+ return TIPS.get(RANDOM.nextInt(TIPS.size())).get();
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java
new file mode 100644
index 00000000..d67d6988
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonScore.java
@@ -0,0 +1,76 @@
+package de.hysky.skyblocker.skyblock.dungeon;
+
+import de.hysky.skyblocker.config.SkyblockerConfig;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager;
+import de.hysky.skyblocker.utils.Constants;
+import de.hysky.skyblocker.utils.Utils;
+import de.hysky.skyblocker.utils.scheduler.MessageScheduler;
+import de.hysky.skyblocker.utils.scheduler.Scheduler;
+import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.sound.SoundEvents;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class DungeonScore {
+ private static final SkyblockerConfig.DungeonScore CONFIG = SkyblockerConfigManager.get().locations.dungeons.dungeonScore;
+ private static final Pattern DUNGEON_CLEARED_PATTERN = Pattern.compile("Cleared: (?<cleared>\\d+)% \\((?<score>\\d+)\\)");
+ private static boolean sent270;
+ private static boolean sent300;
+
+ public static void init() {
+ Scheduler.INSTANCE.scheduleCyclic(DungeonScore::tick, 20);
+ ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> reset());
+ }
+
+ public static void tick() {
+ MinecraftClient client = MinecraftClient.getInstance();
+ if (!Utils.isInDungeons() || client.player == null) {
+ reset();
+ return;
+ }
+
+ for (String sidebarLine : Utils.STRING_SCOREBOARD) {
+ Matcher dungeonClearedMatcher = DUNGEON_CLEARED_PATTERN.matcher(sidebarLine);
+ if (!dungeonClearedMatcher.matches()) {
+ continue;
+ }
+ int score = Integer.parseInt(dungeonClearedMatcher.group("score"));
+ if (!DungeonManager.isInBoss()) score += 28;
+ if (!sent270 && score >= 270 && score < 300) {
+ if (CONFIG.enableDungeonScore270Message) {
+ MessageScheduler.INSTANCE.sendMessageAfterCooldown(Constants.PREFIX.get().getString() + CONFIG.dungeonScore270Message.replaceAll("\\[score]", "270"));
+ }
+ if (CONFIG.enableDungeonScore270Title) {
+ client.inGameHud.setDefaultTitleFade();
+ client.inGameHud.setTitle(Constants.PREFIX.get().append(CONFIG.dungeonScore270Message.replaceAll("\\[score]", "270")));
+ }
+ if (CONFIG.enableDungeonScore270Sound) {
+ client.player.playSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 100f, 0.1f);
+ }
+ sent270 = true;
+ }
+ if (!sent300 && score >= 300) {
+ if (CONFIG.enableDungeonScore300Message) {
+ MessageScheduler.INSTANCE.sendMessageAfterCooldown(Constants.PREFIX.get().getString() + CONFIG.dungeonScore300Message.replaceAll("\\[score]", "300"));
+ }
+ if (CONFIG.enableDungeonScore300Title) {
+ client.inGameHud.setDefaultTitleFade();
+ client.inGameHud.setTitle(Constants.PREFIX.get().append(CONFIG.dungeonScore300Message.replaceAll("\\[score]", "300")));
+ }
+ if (CONFIG.enableDungeonScore300Sound) {
+ client.player.playSound(SoundEvents.BLOCK_NOTE_BLOCK_PLING.value(), 100f, 0.1f);
+ }
+ sent300 = true;
+ }
+ break;
+ }
+ }
+
+ private static void reset() {
+ sent270 = false;
+ sent300 = false;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/LividColor.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/LividColor.java
index f40b7859..b1ed5a22 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/LividColor.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/LividColor.java
@@ -2,6 +2,7 @@ package de.hysky.skyblocker.skyblock.dungeon;
import de.hysky.skyblocker.config.SkyblockerConfig;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.Constants;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.scheduler.MessageScheduler;
import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
@@ -9,6 +10,8 @@ import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.client.MinecraftClient;
import net.minecraft.registry.Registries;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.math.BlockPos;
@@ -37,13 +40,14 @@ public class LividColor {
"Doctor Livid", Formatting.GRAY,
"Vendetta Livid", Formatting.WHITE
);
+ public static final SkyblockerConfig.LividColor CONFIG = SkyblockerConfigManager.get().locations.dungeons.lividColor;
private static int tenTicks = 0;
private static Formatting color;
public static void init() {
ClientReceiveMessageEvents.GAME.register((message, overlay) -> {
SkyblockerConfig.LividColor config = SkyblockerConfigManager.get().locations.dungeons.lividColor;
- if ((config.enableLividColorText || config.enableLividColorGlow) && message.getString().equals("[BOSS] Livid: I respect you for making it to here, but I'll be your undoing.")) {
+ if ((config.enableLividColorText || config.enableLividColorTitle || config.enableLividColorGlow) && message.getString().equals("[BOSS] Livid: I respect you for making it to here, but I'll be your undoing.")) {
tenTicks = 8;
}
});
@@ -53,14 +57,14 @@ public class LividColor {
MinecraftClient client = MinecraftClient.getInstance();
if (tenTicks != 0) {
SkyblockerConfig.LividColor config = SkyblockerConfigManager.get().locations.dungeons.lividColor;
- if ((config.enableLividColorText || config.enableLividColorGlow) && Utils.isInDungeons() && client.world != null) {
+ if ((config.enableLividColorText || config.enableLividColorTitle || config.enableLividColorGlow) && Utils.isInDungeons() && client.world != null) {
if (tenTicks == 1) {
- onLividColorFound(Blocks.RED_WOOL);
+ onLividColorFound(client, Blocks.RED_WOOL);
return;
}
Block color = client.world.getBlockState(new BlockPos(5, 110, 42)).getBlock();
if (WOOL_TO_FORMATTING.containsKey(color) && !color.equals(Blocks.RED_WOOL)) {
- onLividColorFound(color);
+ onLividColorFound(client, color);
return;
}
tenTicks--;
@@ -70,11 +74,21 @@ public class LividColor {
}
}
- private static void onLividColorFound(Block color) {
+ private static void onLividColorFound(MinecraftClient client, Block color) {
LividColor.color = WOOL_TO_FORMATTING.get(color);
- if (SkyblockerConfigManager.get().locations.dungeons.lividColor.enableLividColorText) {
- String colorString = Registries.BLOCK.getId(color).getPath();
- MessageScheduler.INSTANCE.sendMessageAfterCooldown(SkyblockerConfigManager.get().locations.dungeons.lividColor.lividColorText.replaceAll("\\[color]", colorString.substring(0, colorString.length() - 5)));
+ String colorString = Registries.BLOCK.getId(color).getPath();
+ colorString = colorString.substring(0, colorString.length() - 5).toUpperCase();
+ String[] messageParts = CONFIG.lividColorText.split("\\[color]");
+ MutableText message = Constants.PREFIX.get().append(messageParts[0]);
+ for (int i = 1; i < messageParts.length; i++) {
+ message = message.append(Text.literal(colorString).formatted(LividColor.color)).append(Text.of(messageParts[i]));
+ }
+ if (CONFIG.enableLividColorText) {
+ MessageScheduler.INSTANCE.sendMessageAfterCooldown(message.getString());
+ }
+ if (CONFIG.enableLividColorTitle) {
+ client.inGameHud.setDefaultTitleFade();
+ client.inGameHud.setTitle(message);
}
tenTicks = 0;
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CreeperBeams.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/CreeperBeams.java
index 5c7a01f9..db195003 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CreeperBeams.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/CreeperBeams.java
@@ -1,13 +1,10 @@
-package de.hysky.skyblocker.skyblock.dungeon;
+package de.hysky.skyblocker.skyblock.dungeon.puzzle;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.render.RenderHelper;
-import de.hysky.skyblocker.utils.scheduler.Scheduler;
import it.unimi.dsi.fastutil.objects.ObjectDoublePair;
-import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
-import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.client.MinecraftClient;
@@ -27,8 +24,7 @@ import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
-public class CreeperBeams {
-
+public class CreeperBeams extends DungeonPuzzle {
private static final Logger LOGGER = LoggerFactory.getLogger(CreeperBeams.class.getName());
private static final float[][] COLORS = {
@@ -41,56 +37,55 @@ public class CreeperBeams {
private static final int FLOOR_Y = 68;
private static final int BASE_Y = 74;
+ private static final CreeperBeams INSTANCE = new CreeperBeams();
private static ArrayList<Beam> beams = new ArrayList<>();
private static BlockPos base = null;
- private static boolean solved = false;
+
+ private CreeperBeams() {
+ super("creeper", "creeper-room");
+ }
public static void init() {
- Scheduler.INSTANCE.scheduleCyclic(CreeperBeams::update, 20);
- WorldRenderEvents.BEFORE_DEBUG_RENDER.register(CreeperBeams::render);
- ClientPlayConnectionEvents.JOIN.register(((handler, sender, client) -> reset()));
}
- private static void reset() {
+ @Override
+ public void reset() {
+ super.reset();
beams.clear();
base = null;
- solved = false;
}
- private static void update() {
+ @Override
+ public void tick(MinecraftClient client) {
// don't do anything if the room is solved
- if (solved) {
+ if (!shouldSolve()) {
return;
}
- MinecraftClient client = MinecraftClient.getInstance();
- ClientWorld world = client.world;
- ClientPlayerEntity player = client.player;
-
// clear state if not in dungeon
- if (world == null || player == null || !Utils.isInDungeons()) {
+ if (client.world == null || client.player == null || !Utils.isInDungeons()) {
return;
}
// try to find base if not found and solve
if (base == null) {
- base = findCreeperBase(player, world);
+ base = findCreeperBase(client.player, client.world);
if (base == null) {
return;
}
Vec3d creeperPos = new Vec3d(base.getX() + 0.5, BASE_Y + 1.75, base.getZ() + 0.5);
- ArrayList<BlockPos> targets = findTargets(world, base);
+ ArrayList<BlockPos> targets = findTargets(client.world, base);
beams = findLines(creeperPos, targets);
}
// update the beam states
- beams.forEach(b -> b.updateState(world));
+ beams.forEach(b -> b.updateState(client.world));
// check if the room is solved
- if (!isTarget(world, base)) {
- solved = true;
+ if (!isTarget(client.world, base)) {
+ reset();
}
}
@@ -176,10 +171,11 @@ public class CreeperBeams {
return result;
}
- private static void render(WorldRenderContext wrc) {
+ @Override
+ public void render(WorldRenderContext wrc) {
// don't render if solved or disabled
- if (solved || !SkyblockerConfigManager.get().locations.dungeons.creeperSolver) {
+ if (!shouldSolve() || !SkyblockerConfigManager.get().locations.dungeons.creeperSolver) {
return;
}
@@ -239,11 +235,11 @@ public class CreeperBeams {
if (toDo) {
RenderHelper.renderOutline(wrc, outlineOne, color, 3, false);
RenderHelper.renderOutline(wrc, outlineTwo, color, 3, false);
- RenderHelper.renderLinesFromPoints(wrc, line, color, 1, 2);
+ RenderHelper.renderLinesFromPoints(wrc, line, color, 1, 2, false);
} else {
RenderHelper.renderOutline(wrc, outlineOne, GREEN_COLOR_COMPONENTS, 1, false);
RenderHelper.renderOutline(wrc, outlineTwo, GREEN_COLOR_COMPONENTS, 1, false);
- RenderHelper.renderLinesFromPoints(wrc, line, GREEN_COLOR_COMPONENTS, 0.75f, 1);
+ RenderHelper.renderLinesFromPoints(wrc, line, GREEN_COLOR_COMPONENTS, 0.75f, 1, false);
}
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonBlaze.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/DungeonBlaze.java
index f49a2f2e..6b435d3c 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonBlaze.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/DungeonBlaze.java
@@ -1,12 +1,10 @@
-package de.hysky.skyblocker.skyblock.dungeon;
+package de.hysky.skyblocker.skyblock.dungeon.puzzle;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.render.RenderHelper;
-import de.hysky.skyblocker.utils.scheduler.Scheduler;
import it.unimi.dsi.fastutil.objects.ObjectIntPair;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
-import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.world.ClientWorld;
@@ -24,29 +22,34 @@ import java.util.List;
/**
* This class provides functionality to render outlines around Blaze entities
*/
-public class DungeonBlaze {
+public class DungeonBlaze extends DungeonPuzzle {
private static final Logger LOGGER = LoggerFactory.getLogger(DungeonBlaze.class.getName());
private static final float[] GREEN_COLOR_COMPONENTS = {0.0F, 1.0F, 0.0F};
private static final float[] WHITE_COLOR_COMPONENTS = {1.0f, 1.0f, 1.0f};
+ private static final DungeonBlaze INSTANCE = new DungeonBlaze();
private static ArmorStandEntity highestBlaze = null;
private static ArmorStandEntity lowestBlaze = null;
private static ArmorStandEntity nextHighestBlaze = null;
private static ArmorStandEntity nextLowestBlaze = null;
+ private DungeonBlaze() {
+ super("blaze", "blaze-room-1-high", "blaze-room-1-low");
+ }
+
public static void init() {
- Scheduler.INSTANCE.scheduleCyclic(DungeonBlaze::update, 4);
- WorldRenderEvents.BEFORE_DEBUG_RENDER.register(DungeonBlaze::blazeRenderer);
}
/**
* Updates the state of Blaze entities and triggers the rendering process if necessary.
*/
- public static void update() {
- ClientWorld world = MinecraftClient.getInstance().world;
- ClientPlayerEntity player = MinecraftClient.getInstance().player;
- if (world == null || player == null || !Utils.isInDungeons()) return;
- List<ObjectIntPair<ArmorStandEntity>> blazes = getBlazesInWorld(world, player);
+ @Override
+ public void tick(MinecraftClient client) {
+ if (!shouldSolve()) {
+ return;
+ }
+ if (client.world == null || client.player == null || !Utils.isInDungeons()) return;
+ List<ObjectIntPair<ArmorStandEntity>> blazes = getBlazesInWorld(client.world, client.player);
sortBlazes(blazes);
updateBlazeEntities(blazes);
}
@@ -104,7 +107,8 @@ public class DungeonBlaze {
*
* @param wrc The WorldRenderContext used for rendering.
*/
- public static void blazeRenderer(WorldRenderContext wrc) {
+ @Override
+ public void render(WorldRenderContext wrc) {
try {
if (highestBlaze != null && lowestBlaze != null && highestBlaze.isAlive() && lowestBlaze.isAlive() && SkyblockerConfigManager.get().locations.dungeons.blazeSolver) {
if (highestBlaze.getY() < 69) {
@@ -137,7 +141,7 @@ public class DungeonBlaze {
Vec3d blazeCenter = blazeBox.getCenter();
Vec3d nextBlazeCenter = nextBlazeBox.getCenter();
- RenderHelper.renderLinesFromPoints(wrc, new Vec3d[]{blazeCenter, nextBlazeCenter}, WHITE_COLOR_COMPONENTS, 1f, 5f);
+ RenderHelper.renderLinesFromPoints(wrc, new Vec3d[]{blazeCenter, nextBlazeCenter}, WHITE_COLOR_COMPONENTS, 1f, 5f, false);
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/DungeonPuzzle.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/DungeonPuzzle.java
new file mode 100644
index 00000000..f5e0461d
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/DungeonPuzzle.java
@@ -0,0 +1,59 @@
+package de.hysky.skyblocker.skyblock.dungeon.puzzle;
+
+import com.mojang.brigadier.Command;
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.events.DungeonEvents;
+import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager;
+import de.hysky.skyblocker.skyblock.dungeon.secrets.Room;
+import de.hysky.skyblocker.utils.Constants;
+import de.hysky.skyblocker.utils.Tickable;
+import de.hysky.skyblocker.utils.render.Renderable;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
+import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Set;
+
+import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
+
+public abstract class DungeonPuzzle implements Tickable, Renderable {
+ protected final String puzzleName;
+ @NotNull
+ private final Set<String> roomNames;
+ private boolean shouldSolve;
+
+ public DungeonPuzzle(String puzzleName, String... roomName) {
+ this(puzzleName, Set.of(roomName));
+ }
+
+ public DungeonPuzzle(String puzzleName, @NotNull Set<String> roomNames) {
+ this.puzzleName = puzzleName;
+ this.roomNames = roomNames;
+ DungeonEvents.PUZZLE_MATCHED.register(room -> {
+ if (roomNames.contains(room.getName())) {
+ room.addSubProcess(this);
+ shouldSolve = true;
+ }
+ });
+ ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("dungeons").then(literal("puzzle").then(literal(puzzleName).then(literal("solve").executes(context -> {
+ Room currentRoom = DungeonManager.getCurrentRoom();
+ if (currentRoom != null) {
+ reset();
+ currentRoom.addSubProcess(this);
+ context.getSource().sendFeedback(Constants.PREFIX.get().append("§aSolving " + puzzleName + " puzzle in the current room."));
+ } else {
+ context.getSource().sendError(Constants.PREFIX.get().append("§cCurrent room is null."));
+ }
+ return Command.SINGLE_SUCCESS;
+ })))))));
+ ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> reset());
+ }
+
+ public boolean shouldSolve() {
+ return shouldSolve;
+ }
+
+ public void reset() {
+ shouldSolve = false;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/ThreeWeirdos.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/ThreeWeirdos.java
index e1ab2fa8..c5e55f93 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/ThreeWeirdos.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/ThreeWeirdos.java
@@ -1,4 +1,4 @@
-package de.hysky.skyblocker.skyblock.dungeon;
+package de.hysky.skyblocker.skyblock.dungeon.puzzle;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.utils.chat.ChatFilterResult;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/TicTacToe.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/TicTacToe.java
index 7f249e7d..c8043288 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/TicTacToe.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/TicTacToe.java
@@ -1,16 +1,13 @@
-package de.hysky.skyblocker.skyblock.dungeon;
+package de.hysky.skyblocker.skyblock.dungeon.puzzle;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.render.RenderHelper;
import de.hysky.skyblocker.utils.tictactoe.TicTacToeUtils;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
-import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.network.ClientPlayerEntity;
-import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.decoration.ItemFrameEntity;
import net.minecraft.item.FilledMapItem;
import net.minecraft.item.map.MapState;
@@ -25,27 +22,32 @@ import java.util.List;
/**
* Thanks to Danker for a reference implementation!
*/
-public class TicTacToe {
+public class TicTacToe extends DungeonPuzzle {
private static final Logger LOGGER = LoggerFactory.getLogger(TicTacToe.class);
private static final float[] RED_COLOR_COMPONENTS = {1.0F, 0.0F, 0.0F};
+ private static final TicTacToe INSTANCE = new TicTacToe();
private static Box nextBestMoveToMake = null;
+ private TicTacToe() {
+ super("tic-tac-toe", "tic-tac-toe-1");
+ }
+
public static void init() {
- WorldRenderEvents.BEFORE_DEBUG_RENDER.register(TicTacToe::solutionRenderer);
}
- public static void tick() {
- MinecraftClient client = MinecraftClient.getInstance();
- ClientWorld world = client.world;
- ClientPlayerEntity player = client.player;
+ @Override
+ public void tick(MinecraftClient client) {
+ if (!shouldSolve()) {
+ return;
+ }
nextBestMoveToMake = null;
- if (world == null || player == null || !Utils.isInDungeons()) return;
+ if (client.world == null || client.player == null || !Utils.isInDungeons()) return;
//Search within 21 blocks for item frames that contain maps
- Box searchBox = new Box(player.getX() - 21, player.getY() - 21, player.getZ() - 21, player.getX() + 21, player.getY() + 21, player.getZ() + 21);
- List<ItemFrameEntity> itemFramesThatHoldMaps = world.getEntitiesByClass(ItemFrameEntity.class, searchBox, ItemFrameEntity::containsMap);
+ Box searchBox = new Box(client.player.getX() - 21, client.player.getY() - 21, client.player.getZ() - 21, client.player.getX() + 21, client.player.getY() + 21, client.player.getZ() + 21);
+ List<ItemFrameEntity> itemFramesThatHoldMaps = client.world.getEntitiesByClass(ItemFrameEntity.class, searchBox, ItemFrameEntity::containsMap);
try {
//Only attempt to solve if its the player's turn
@@ -56,7 +58,7 @@ public class TicTacToe {
char facing = 'X';
for (ItemFrameEntity itemFrame : itemFramesThatHoldMaps) {
- MapState mapState = world.getMapState(FilledMapItem.getMapName(itemFrame.getMapId().getAsInt()));
+ MapState mapState = client.world.getMapState(FilledMapItem.getMapName(itemFrame.getMapId().getAsInt()));
if (mapState == null) continue;
@@ -78,7 +80,7 @@ public class TicTacToe {
facing = 'Z';
}
- Block block = world.getBlockState(blockPos).getBlock();
+ Block block = client.world.getBlockState(blockPos).getBlock();
if (block == Blocks.AIR || block == Blocks.STONE_BUTTON) {
leftmostRow = blockPos;
column = i;
@@ -124,7 +126,8 @@ public class TicTacToe {
}
}
- private static void solutionRenderer(WorldRenderContext context) {
+ @Override
+ public void render(WorldRenderContext context) {
try {
if (SkyblockerConfigManager.get().locations.dungeons.solveTicTacToe && nextBestMoveToMake != null) {
RenderHelper.renderOutline(context, nextBestMoveToMake, RED_COLOR_COMPONENTS, 5, false);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/Trivia.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/Trivia.java
index 53368c14..c331bd48 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/Trivia.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/Trivia.java
@@ -1,4 +1,4 @@
-package de.hysky.skyblocker.skyblock.dungeon;
+package de.hysky.skyblocker.skyblock.dungeon.puzzle;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.skyblock.waypoint.FairySouls;
@@ -10,11 +10,15 @@ import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+
+import com.mojang.logging.LogUtils;
import java.util.*;
import java.util.regex.Matcher;
public class Trivia extends ChatPatternListener {
+ private static final Logger LOGGER = LogUtils.getLogger();
private static final Map<String, String[]> answers;
private List<String> solutions = Collections.emptyList();
@@ -42,14 +46,19 @@ public class Trivia extends ChatPatternListener {
}
private void updateSolutions(String question) {
- String trimmedQuestion = question.trim();
- if (trimmedQuestion.equals("What SkyBlock year is it?")) {
- long currentTime = System.currentTimeMillis() / 1000L;
- long diff = currentTime - 1560276000;
- int year = (int) (diff / 446400 + 1);
- solutions = Collections.singletonList("Year " + year);
- } else {
- solutions = Arrays.asList(answers.get(trimmedQuestion));
+ try {
+ String trimmedQuestion = question.trim();
+ if (trimmedQuestion.equals("What SkyBlock year is it?")) {
+ long currentTime = System.currentTimeMillis() / 1000L;
+ long diff = currentTime - 1560276000;
+ int year = (int) (diff / 446400 + 1);
+ solutions = Collections.singletonList("Year " + year);
+ } else {
+ String[] questionAnswers = answers.get(trimmedQuestion);
+ if (questionAnswers != null) solutions = Arrays.asList(questionAnswers);
+ }
+ } catch (Exception e) { //Hopefully the solver doesn't go south
+ LOGGER.error("[Skyblocker] Failed to update the Trivia puzzle answers!", e);
}
}
@@ -66,7 +75,7 @@ public class Trivia extends ChatPatternListener {
answers.put("What is the status of Goldor?", new String[]{"The Wither Lords"});
answers.put("What is the status of Storm?", new String[]{"The Wither Lords"});
answers.put("What is the status of Necron?", new String[]{"The Wither Lords"});
- answers.put("What is the status of Maxor, Storm, Goldor and Necron?", new String[]{"The Wither Lords"});
+ answers.put("What is the status of Maxor, Storm, Goldor, and Necron?", new String[]{"The Wither Lords"});
answers.put("Which brother is on the Spider's Den?", new String[]{"Rick"});
answers.put("What is the name of Rick's brother?", new String[]{"Pat"});
answers.put("What is the name of the Painter in the Hub?", new String[]{"Marco"});
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/waterboard/Cell.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/waterboard/Cell.java
new file mode 100644
index 00000000..0279fed8
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/waterboard/Cell.java
@@ -0,0 +1,51 @@
+package de.hysky.skyblocker.skyblock.dungeon.puzzle.waterboard;
+
+public class Cell {
+ public static final Cell BLOCK = new Cell(Type.BLOCK);
+ public static final Cell EMPTY = new Cell(Type.EMPTY);
+ public final Type type;
+
+ private Cell(Type type) {
+ this.type = type;
+ }
+
+ public boolean isOpen() {
+ return type == Type.EMPTY;
+ }
+
+ public static class SwitchCell extends Cell {
+ public final int id;
+ private boolean open;
+
+ public SwitchCell(int id) {
+ super(Type.SWITCH);
+ this.id = id;
+ }
+
+ public static SwitchCell ofOpened(int id) {
+ SwitchCell switchCell = new SwitchCell(id);
+ switchCell.open = true;
+ return switchCell;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return super.equals(obj) || obj instanceof SwitchCell switchCell && id == switchCell.id && open == switchCell.open;
+ }
+
+ @Override
+ public boolean isOpen() {
+ return open;
+ }
+
+ public void toggle() {
+ open = !open;
+ }
+ }
+
+ public enum Type {
+ BLOCK,
+ EMPTY,
+ SWITCH
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/waterboard/Switch.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/waterboard/Switch.java
new file mode 100644
index 00000000..bb8da61d
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/waterboard/Switch.java
@@ -0,0 +1,39 @@
+package de.hysky.skyblocker.skyblock.dungeon.puzzle.waterboard;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.AbstractCollection;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class Switch extends AbstractCollection<Cell.SwitchCell> {
+ public final int id;
+ public final List<Cell.SwitchCell> cells = new ArrayList<>();
+
+ public Switch(int id) {
+ this.id = id;
+ }
+
+ @Override
+ @NotNull
+ public Iterator<Cell.SwitchCell> iterator() {
+ return cells.iterator();
+ }
+
+ @Override
+ public int size() {
+ return cells.size();
+ }
+
+ @Override
+ public boolean add(Cell.SwitchCell cell) {
+ return cells.add(cell);
+ }
+
+ public void toggle() {
+ for (Cell.SwitchCell cell : cells) {
+ cell.toggle();
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/waterboard/Waterboard.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/waterboard/Waterboard.java
new file mode 100644
index 00000000..3244996a
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/waterboard/Waterboard.java
@@ -0,0 +1,451 @@
+package de.hysky.skyblocker.skyblock.dungeon.puzzle.waterboard;
+
+import com.google.common.collect.Multimap;
+import com.google.common.collect.MultimapBuilder;
+import com.mojang.brigadier.Command;
+import com.mojang.brigadier.arguments.IntegerArgumentType;
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.debug.Debug;
+import de.hysky.skyblocker.skyblock.dungeon.puzzle.DungeonPuzzle;
+import de.hysky.skyblocker.skyblock.dungeon.puzzle.waterboard.Cell.SwitchCell;
+import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager;
+import de.hysky.skyblocker.skyblock.dungeon.secrets.Room;
+import de.hysky.skyblocker.utils.Constants;
+import de.hysky.skyblocker.utils.render.RenderHelper;
+import de.hysky.skyblocker.utils.scheduler.Scheduler;
+import de.hysky.skyblocker.utils.waypoint.Waypoint;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntMaps;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
+import net.fabricmc.fabric.api.event.player.UseBlockCallback;
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockState;
+import net.minecraft.block.Blocks;
+import net.minecraft.block.LeverBlock;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.fluid.Fluids;
+import net.minecraft.fluid.WaterFluid;
+import net.minecraft.util.ActionResult;
+import net.minecraft.util.DyeColor;
+import net.minecraft.util.Hand;
+import net.minecraft.util.hit.BlockHitResult;
+import net.minecraft.util.hit.HitResult;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Direction;
+import net.minecraft.util.math.Vec3d;
+import net.minecraft.world.World;
+import org.joml.Vector2i;
+import org.joml.Vector2ic;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+
+import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument;
+import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
+
+public class Waterboard extends DungeonPuzzle {
+ private static final Logger LOGGER = LoggerFactory.getLogger(Waterboard.class);
+ public static final Waterboard INSTANCE = new Waterboard();
+ private static final Object2IntMap<Block> SWITCH_BLOCKS = Object2IntMaps.unmodifiable(new Object2IntOpenHashMap<>(Map.of(
+ Blocks.COAL_BLOCK, 1,
+ Blocks.GOLD_BLOCK, 2,
+ Blocks.QUARTZ_BLOCK, 3,
+ Blocks.DIAMOND_BLOCK, 4,
+ Blocks.EMERALD_BLOCK, 5,
+ Blocks.TERRACOTTA, 6
+ )));
+ private static final BlockPos[] SWITCH_POSITIONS = new BlockPos[]{
+ new BlockPos(20, 61, 10),
+ new BlockPos(20, 61, 15),
+ new BlockPos(20, 61, 20),
+ new BlockPos(10, 61, 20),
+ new BlockPos(10, 61, 15),
+ new BlockPos(10, 61, 10)
+ };
+ public static final BlockPos WATER_LEVER = new BlockPos(15, 60, 5);
+ private static final float[] LIME_COLOR_COMPONENTS = DyeColor.LIME.getColorComponents();
+
+ private CompletableFuture<Void> solve;
+ private final Cell[][] cells = new Cell[19][19];
+ private final Switch[] switches = new Switch[]{new Switch(0), new Switch(1), new Switch(2), new Switch(3), new Switch(4), new Switch(5)};
+ private int doors = 0;
+ private final Result[] results = new Result[64];
+ private int currentCombination;
+ private final IntList bestCombinations = new IntArrayList();
+ private final Waypoint[] waypoints = new Waypoint[7];
+ /**
+ * Used to check the water lever state since the block state does not update immediately after the lever is toggled.
+ */
+ private boolean bestCombinationsUpdated;
+
+ private Waterboard() {
+ super("waterboard", "water-puzzle");
+ UseBlockCallback.EVENT.register(this::onUseBlock);
+ if (Debug.debugEnabled()) {
+ ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("dungeons").then(literal("puzzle").then(literal(puzzleName)
+ .then(literal("printBoard").executes(context -> {
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(boardToString(cells)));
+ return Command.SINGLE_SUCCESS;
+ })).then(literal("printDoors").executes(context -> {
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(Integer.toBinaryString(INSTANCE.doors)));
+ return Command.SINGLE_SUCCESS;
+ })).then(literal("printSimulationResults").then(argument("combination", IntegerArgumentType.integer(0, 63)).executes(context -> {
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(results[IntegerArgumentType.getInteger(context, "combination")].toString()));
+ return Command.SINGLE_SUCCESS;
+ }))).then(literal("printCurrentCombination").executes(context -> {
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(Integer.toBinaryString(INSTANCE.currentCombination)));
+ return Command.SINGLE_SUCCESS;
+ })).then(literal("printBestCombination").executes(context -> {
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(INSTANCE.bestCombinations.toString()));
+ return Command.SINGLE_SUCCESS;
+ }))
+ )))));
+ }
+ }
+
+ public static void init() {
+ }
+
+ private static String boardToString(Cell[][] cells) {
+ StringBuilder sb = new StringBuilder();
+ for (Cell[] row : cells) {
+ sb.append("\n");
+ for (Cell cell : row) {
+ if (cell == null) {
+ sb.append('?');
+ } else if (cell instanceof SwitchCell switchCell) {
+ sb.append(switchCell.id);
+ } else switch (cell.type) {
+ case BLOCK -> sb.append('#');
+ case EMPTY -> sb.append('.');
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public void tick(MinecraftClient client) {
+ if (!SkyblockerConfigManager.get().locations.dungeons.solveWaterboard || client.world == null || !DungeonManager.isCurrentRoomMatched() || solve != null && !solve.isDone()) {
+ return;
+ }
+ Room room = DungeonManager.getCurrentRoom();
+ solve = CompletableFuture.runAsync(() -> {
+ Changed changed = updateBoard(client.world, room);
+ if (changed == Changed.NONE) {
+ return;
+ }
+ if (results[0] == null) {
+ updateSwitches();
+ simulateCombinations();
+ clearSwitches();
+ }
+ if (bestCombinations.isEmpty() || changed.doorChanged()) {
+ findBestCombinations();
+ bestCombinationsUpdated = true;
+ }
+ }).exceptionally(e -> {
+ LOGGER.error("[Skyblocker Waterboard] Encountered an unknown exception while solving waterboard.", e);
+ return null;
+ });
+ if (waypoints[0] == null) {
+ for (int i = 0; i < 6; i++) {
+ waypoints[i] = new Waypoint(room.relativeToActual(SWITCH_POSITIONS[i]), Waypoint.Type.HIGHLIGHT, LIME_COLOR_COMPONENTS);
+ }
+ waypoints[6] = new Waypoint(room.relativeToActual(WATER_LEVER), Waypoint.Type.HIGHLIGHT, LIME_COLOR_COMPONENTS);
+ waypoints[6].setFound();
+ }
+ }
+
+ private Changed updateBoard(World world, Room room) {
+ // Parse the waterboard.
+ BlockPos.Mutable pos = new BlockPos.Mutable(24, 78, 26);
+ Changed changed = Changed.NONE;
+ for (int row = 0; row < cells.length; pos.move(cells[row].length, -1, 0), row++) {
+ for (int col = 0; col < cells[row].length; pos.move(Direction.WEST), col++) {
+ Cell cell = parseBlock(world, room, pos);
+ if (!cell.equals(cells[row][col])) {
+ cells[row][col] = cell;
+ changed = changed.onCellChanged();
+ }
+ }
+ }
+
+ // Parse door states.
+ pos.set(15, 57, 15);
+ int prevDoors = doors;
+ doors = 0;
+ for (int i = 0; i < 5; pos.move(Direction.SOUTH), i++) {
+ doors |= world.getBlockState(room.relativeToActual(pos)).isAir() ? 1 << i : 0;
+ }
+ if (doors != prevDoors) {
+ changed = changed.onDoorChanged();
+ }
+
+ // Parse current combination of switches based on the levers.
+ currentCombination = 0;
+ for (int i = 0; i < 6; i++) {
+ currentCombination |= getSwitchState(world, room, i);
+ }
+
+ return changed;
+ }
+
+ private Cell parseBlock(World world, Room room, BlockPos.Mutable pos) {
+ // Check if the block is a switch.
+ BlockState state = world.getBlockState(room.relativeToActual(pos));
+ int switch_ = SWITCH_BLOCKS.getInt(state.getBlock());
+ if (switch_-- > 0) {
+ return new SwitchCell(switch_);
+ }
+ // Check if the block is an opened switch by checking the block behind it.
+ int switchBehind = SWITCH_BLOCKS.getInt(world.getBlockState(room.relativeToActual(pos.move(Direction.SOUTH))).getBlock());
+ pos.move(Direction.NORTH);
+ if (switchBehind-- > 0) {
+ return SwitchCell.ofOpened(switchBehind);
+ }
+
+ // Check if the block is empty otherwise the block is a wall.
+ return state.isAir() || state.isOf(Blocks.WATER) ? Cell.EMPTY : Cell.BLOCK;
+ }
+
+ private static int getSwitchState(World world, Room room, int i) {
+ BlockState state = world.getBlockState(room.relativeToActual(SWITCH_POSITIONS[i]));
+ return state.contains(LeverBlock.POWERED) && state.get(LeverBlock.POWERED) ? 1 << i : 0;
+ }
+
+ private void updateSwitches() {
+ clearSwitches();
+ for (Cell[] row : cells) {
+ for (Cell cell : row) {
+ if (cell instanceof SwitchCell switchCell) {
+ switches[switchCell.id].add(switchCell);
+ }
+ }
+ }
+ }
+
+ private void simulateCombinations() {
+ for (int combination = 0; combination < (1 << 6); combination++) {
+ for (int switchIndex = 0; switchIndex < 6; switchIndex++) {
+ if ((combination & (1 << switchIndex)) != 0) {
+ switches[switchIndex].toggle();
+ }
+ }
+ results[combination] = simulateCombination();
+ for (int switchIndex = 0; switchIndex < 6; switchIndex++) {
+ if ((combination & (1 << switchIndex)) != 0) {
+ switches[switchIndex].toggle();
+ }
+ }
+ }
+ }
+
+ private Result simulateCombination() {
+ List<Vector2i> waters = new ArrayList<>();
+ waters.add(new Vector2i(9, 0));
+ Result result = new Result();
+ while (!waters.isEmpty()) {
+ List<Vector2i> newWaters = new ArrayList<>();
+ for (Iterator<Vector2i> watersIt = waters.iterator(); watersIt.hasNext(); ) {
+ Vector2i water = watersIt.next();
+ // Check if the water has reached a door.
+ if (water.y == 18) {
+ switch (water.x) {
+ case 0 -> result.reachedDoors |= 1 << 4;
+ case 4 -> result.reachedDoors |= 1 << 3;
+ case 9 -> result.reachedDoors |= 1 << 2;
+ case 14 -> result.reachedDoors |= 1 << 1;
+ case 18 -> result.reachedDoors |= 1;
+ }
+ watersIt.remove();
+ continue;
+ }
+ // Check if the water can flow down.
+ if (water.y < 18 && cells[water.y + 1][water.x].isOpen()) {
+ result.putPath(water, 0);
+ water.add(0, 1);
+ continue;
+ }
+
+ // Get the offset to the first block on the left and the right that can flow down.
+ int leftFlowDownOffset = findFlowDown(water, false);
+ int rightFlowDownOffset = findFlowDown(water, true);
+ // Check if left down is in range and is closer than right down.
+ // Note 1: The yarn name "getFlowSpeed" is incorrect as it actually returns the maximum distance that water will check for a hole to flow towards.
+ // Note 2: Skyblock's maximum offset is 5 instead of 4 for some reason.
+ if (-leftFlowDownOffset <= ((WaterFluid) Fluids.WATER).getFlowSpeed(null) + 1 && -leftFlowDownOffset < rightFlowDownOffset) {
+ result.putPath(water, leftFlowDownOffset);
+ water.add(leftFlowDownOffset, 1);
+ continue;
+ }
+ // Check if right down is in range and closer than left down.
+ if (rightFlowDownOffset <= ((WaterFluid) Fluids.WATER).getFlowSpeed(null) + 1 && rightFlowDownOffset < -leftFlowDownOffset) {
+ result.putPath(water, rightFlowDownOffset);
+ water.add(rightFlowDownOffset, 1);
+ continue;
+ }
+
+ // Else flow to both sides if in range.
+ if (leftFlowDownOffset > Integer.MIN_VALUE + 1) {
+ result.putPath(water, leftFlowDownOffset);
+ newWaters.add(new Vector2i(water).add(leftFlowDownOffset, 1));
+ }
+ if (rightFlowDownOffset < Integer.MAX_VALUE) {
+ result.putPath(water, rightFlowDownOffset);
+ newWaters.add(new Vector2i(water).add(rightFlowDownOffset, 1));
+ }
+ watersIt.remove();
+ }
+ waters.addAll(newWaters);
+ }
+ return result;
+ }
+
+ /**
+ * Finds the first block on the left that can flow down.
+ */
+ private int findFlowDown(Vector2i water, boolean direction) {
+ for (int i = 0; water.x + i >= 0 && water.x + i < 19 && i > -8 && i < 8 && cells[water.y][water.x + i].isOpen(); i += direction ? 1 : -1) {
+ if (cells[water.y + 1][water.x + i].isOpen()) {
+ return i;
+ }
+ }
+ return direction ? Integer.MAX_VALUE : Integer.MIN_VALUE + 1;
+ }
+
+ private void findBestCombinations() {
+ bestCombinations.clear();
+ for (int combination = 0, bestScore = 0; combination < (1 << 6); combination++) {
+ int newScore = Integer.bitCount(results[combination].reachedDoors ^ doors);
+ if (newScore >= bestScore) {
+ if (newScore > bestScore) {
+ bestCombinations.clear();
+ bestScore = newScore;
+ }
+ bestCombinations.add(combination);
+ }
+ }
+ }
+
+ @Override
+ public void render(WorldRenderContext context) {
+ if (!SkyblockerConfigManager.get().locations.dungeons.solveWaterboard || !DungeonManager.isCurrentRoomMatched()) return;
+ Room room = DungeonManager.getCurrentRoom();
+
+ // Render the best combination.
+ @SuppressWarnings("resource")
+ BlockState state = context.world().getBlockState(room.relativeToActual(WATER_LEVER));
+ // bestCombinationsUpdated is needed because bestCombinations does not update immediately after the lever is turned off.
+ if (waypoints[0] != null && bestCombinationsUpdated && state.contains(LeverBlock.POWERED) && !state.get(LeverBlock.POWERED)) {
+ bestCombinations.intStream().mapToObj(bestCombination -> currentCombination ^ bestCombination).min(Comparator.comparingInt(Integer::bitCount)).ifPresent(bestDifference -> {
+ for (int i = 0; i < 6; i++) {
+ if ((bestDifference & 1 << i) != 0) {
+ waypoints[i].render(context);
+ }
+ }
+ if (bestDifference == 0 && !waypoints[6].shouldRender()) {
+ waypoints[6].setMissing();
+ }
+ });
+ }
+ if (waypoints[6] != null && waypoints[6].shouldRender()) {
+ waypoints[6].render(context);
+ }
+
+ // Render the current path of the water.
+ BlockPos.Mutable pos = new BlockPos.Mutable(15, 79, 26);
+ RenderHelper.renderLinesFromPoints(context, new Vec3d[]{Vec3d.ofCenter(room.relativeToActual(pos)), Vec3d.ofCenter(room.relativeToActual(pos.move(Direction.DOWN)))}, LIME_COLOR_COMPONENTS, 1f, 5f, true);
+ Result currentResult = results[currentCombination];
+ if (currentResult != null) {
+ for (Map.Entry<Vector2ic, Integer> entry : currentResult.path.entries()) {
+ Vec3d start = Vec3d.ofCenter(room.relativeToActual(pos.set(24 - entry.getKey().x(), 78 - entry.getKey().y(), 26)));
+ Vec3d middle = Vec3d.ofCenter(room.relativeToActual(pos.move(Direction.WEST, entry.getValue())));
+ Vec3d end = Vec3d.ofCenter(room.relativeToActual(pos.move(Direction.DOWN)));
+ RenderHelper.renderLinesFromPoints(context, new Vec3d[]{start, middle}, LIME_COLOR_COMPONENTS, 1f, 5f, true);
+ RenderHelper.renderLinesFromPoints(context, new Vec3d[]{middle, end}, LIME_COLOR_COMPONENTS, 1f, 5f, true);
+ }
+ }
+ }
+
+ private ActionResult onUseBlock(PlayerEntity player, World world, Hand hand, BlockHitResult blockHitResult) {
+ BlockState state = world.getBlockState(blockHitResult.getBlockPos());
+ if (SkyblockerConfigManager.get().locations.dungeons.solveWaterboard && blockHitResult.getType() == HitResult.Type.BLOCK && waypoints[6] != null && DungeonManager.isCurrentRoomMatched() && blockHitResult.getBlockPos().equals(DungeonManager.getCurrentRoom().relativeToActual(WATER_LEVER)) && state.contains(LeverBlock.POWERED)) {
+ if (!state.get(LeverBlock.POWERED)) {
+ bestCombinationsUpdated = false;
+ Scheduler.INSTANCE.schedule(() -> waypoints[6].setMissing(), 50);
+ }
+ waypoints[6].setFound();
+ }
+ return ActionResult.PASS;
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ solve = null;
+ for (Cell[] row : cells) {
+ Arrays.fill(row, null);
+ }
+ clearSwitches();
+ doors = 0;
+ Arrays.fill(results, null);
+ currentCombination = 0;
+ bestCombinations.clear();
+ Arrays.fill(waypoints, null);
+ }
+
+ public void clearSwitches() {
+ for (Switch switch_ : switches) {
+ switch_.clear();
+ }
+ }
+
+ private enum Changed {
+ NONE, CELL, DOOR, BOTH;
+
+ private boolean cellChanged() {
+ return this == CELL || this == BOTH;
+ }
+
+ private boolean doorChanged() {
+ return this == DOOR || this == BOTH;
+ }
+
+ private Changed onCellChanged() {
+ return switch (this) {
+ case NONE, CELL -> Changed.CELL;
+ case DOOR, BOTH -> Changed.BOTH;
+ };
+ }
+
+ private Changed onDoorChanged() {
+ return switch (this) {
+ case NONE, DOOR -> Changed.DOOR;
+ case CELL, BOTH -> Changed.BOTH;
+ };
+ }
+ }
+
+ public static class Result {
+ private int reachedDoors;
+ private final Multimap<Vector2ic, Integer> path = MultimapBuilder.hashKeys().arrayListValues().build();
+
+ public boolean putPath(Vector2i water, int offset) {
+ return path.put(new Vector2i(water), offset);
+ }
+
+ @Override
+ public String toString() {
+ return "Result[reachedDoors=" + Integer.toBinaryString(reachedDoors) + ", path=" + path + ']';
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DebugRoom.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DebugRoom.java
new file mode 100644
index 00000000..931d1d69
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DebugRoom.java
@@ -0,0 +1,60 @@
+package de.hysky.skyblocker.skyblock.dungeon.secrets;
+
+import de.hysky.skyblocker.utils.waypoint.Waypoint;
+import it.unimi.dsi.fastutil.ints.IntRBTreeSet;
+import it.unimi.dsi.fastutil.ints.IntSortedSet;
+import it.unimi.dsi.fastutil.ints.IntSortedSets;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
+import net.minecraft.client.world.ClientWorld;
+import net.minecraft.registry.Registries;
+import net.minecraft.util.math.BlockPos;
+import org.apache.commons.lang3.tuple.MutableTriple;
+import org.joml.Vector2ic;
+
+import java.util.*;
+
+public class DebugRoom extends Room {
+ private final List<Waypoint> checkedBlocks = Collections.synchronizedList(new ArrayList<>());
+
+ public DebugRoom(Type type, Vector2ic... physicalPositions) {
+ super(type, physicalPositions);
+ }
+
+ public static DebugRoom ofSinglePossibleRoom(Type type, Vector2ic physicalPositions, String roomName, int[] roomData, Direction direction) {
+ return ofSinglePossibleRoom(type, new Vector2ic[]{physicalPositions}, roomName, roomData, direction);
+ }
+
+ public static DebugRoom ofSinglePossibleRoom(Type type, Vector2ic[] physicalPositions, String roomName, int[] roomData, Direction direction) {
+ DebugRoom room = new DebugRoom(type, physicalPositions);
+ IntSortedSet segmentsX = IntSortedSets.unmodifiable(new IntRBTreeSet(room.segments.stream().mapToInt(Vector2ic::x).toArray()));
+ IntSortedSet segmentsY = IntSortedSets.unmodifiable(new IntRBTreeSet(room.segments.stream().mapToInt(Vector2ic::y).toArray()));
+ room.roomsData = Map.of(roomName, roomData);
+ room.possibleRooms = List.of(MutableTriple.of(direction, DungeonMapUtils.getPhysicalCornerPos(direction, segmentsX, segmentsY), List.of(roomName)));
+ return room;
+ }
+
+ @Override
+ protected boolean checkBlock(ClientWorld world, BlockPos pos) {
+ byte id = DungeonManager.NUMERIC_ID.getByte(Registries.BLOCK.getId(world.getBlockState(pos).getBlock()).toString());
+ if (id == 0) {
+ return false;
+ }
+ for (MutableTriple<Direction, Vector2ic, List<String>> directionRooms : possibleRooms) {
+ int block = posIdToInt(DungeonMapUtils.actualToRelative(directionRooms.getLeft(), directionRooms.getMiddle(), pos), id);
+ for (String room : directionRooms.getRight()) {
+ checkedBlocks.add(new Waypoint(pos, SecretWaypoint.TYPE_SUPPLIER, Arrays.binarySearch(roomsData.get(room), block) >= 0 ? Room.GREEN_COLOR_COMPONENTS : Room.RED_COLOR_COMPONENTS));
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void render(WorldRenderContext context) {
+ super.render(context);
+ synchronized (checkedBlocks) {
+ for (Waypoint checkedBlock : checkedBlocks) {
+ checkedBlock.render(context);
+ }
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonManager.java
index 7f401fdb..32f0b7e3 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonManager.java
@@ -7,16 +7,20 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.IntegerArgumentType;
-import com.mojang.brigadier.builder.ArgumentBuilder;
+import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.serialization.JsonOps;
import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.config.SkyblockerConfig;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.debug.Debug;
import de.hysky.skyblocker.utils.Constants;
+import de.hysky.skyblocker.utils.Tickable;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.scheduler.Scheduler;
import it.unimi.dsi.fastutil.objects.Object2ByteMap;
+import it.unimi.dsi.fastutil.objects.Object2ByteMaps;
import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectIntPair;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
@@ -27,8 +31,9 @@ import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
+import net.minecraft.block.Blocks;
import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.command.CommandSource;
import net.minecraft.command.argument.BlockPosArgumentType;
import net.minecraft.command.argument.PosArgument;
import net.minecraft.command.argument.TextArgumentType;
@@ -37,10 +42,12 @@ import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.mob.AmbientEntity;
import net.minecraft.entity.passive.BatEntity;
+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.MapState;
+import net.minecraft.registry.Registry;
import net.minecraft.resource.Resource;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text;
@@ -75,18 +82,18 @@ import java.util.zip.InflaterInputStream;
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument;
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
-public class DungeonSecrets {
- protected static final Logger LOGGER = LoggerFactory.getLogger(DungeonSecrets.class);
+public class DungeonManager {
+ protected static final Logger LOGGER = LoggerFactory.getLogger(DungeonManager.class);
private static final String DUNGEONS_PATH = "dungeons";
private static final Path CUSTOM_WAYPOINTS_DIR = SkyblockerMod.CONFIG_DIR.resolve("custom_secret_waypoints.json");
private static final Pattern KEY_FOUND = Pattern.compile("^(?:\\[.+] )?(?<name>\\w+) has obtained (?<type>Wither|Blood) Key!$");
/**
* Maps the block identifier string to a custom numeric block id used in dungeon rooms data.
*
- * @implNote Not using {@link net.minecraft.registry.Registry#getId(Object) Registry#getId(Block)} and {@link net.minecraft.block.Blocks Blocks} since this is also used by {@link de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonRoomsDFU DungeonRoomsDFU}, which runs outside of Minecraft.
+ * @implNote Not using {@link Registry#getId(Object) Registry#getId(Block)} and {@link Blocks Blocks} since this is also used by {@link de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonRoomsDFU DungeonRoomsDFU}, which runs outside of Minecraft.
*/
@SuppressWarnings("JavadocReference")
- protected static final Object2ByteMap<String> NUMERIC_ID = new Object2ByteOpenHashMap<>(Map.ofEntries(
+ protected static final Object2ByteMap<String> NUMERIC_ID = Object2ByteMaps.unmodifiable(new Object2ByteOpenHashMap<>(Map.ofEntries(
Map.entry("minecraft:stone", (byte) 1),
Map.entry("minecraft:diorite", (byte) 2),
Map.entry("minecraft:polished_diorite", (byte) 3),
@@ -108,7 +115,7 @@ public class DungeonSecrets {
Map.entry("minecraft:gray_terracotta", (byte) 19),
Map.entry("minecraft:cyan_terracotta", (byte) 20),
Map.entry("minecraft:black_terracotta", (byte) 21)
- ));
+ )));
/**
* Block data for dungeon rooms. See {@link de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonRoomsDFU DungeonRoomsDFU} for format details and how it's generated.
* All access to this map must check {@link #isRoomsLoaded()} to prevent concurrent modification.
@@ -140,6 +147,7 @@ public class DungeonSecrets {
@Nullable
private static Vector2ic physicalEntrancePos;
private static Room currentRoom;
+ private static boolean inBoss;
public static boolean isRoomsLoaded() {
return roomsLoaded != null && roomsLoaded.isDone();
@@ -151,11 +159,13 @@ public class DungeonSecrets {
@SuppressWarnings("unused")
public static JsonObject getRoomMetadata(String room) {
- return roomsJson.get(room).getAsJsonObject();
+ JsonElement value = roomsJson.get(room);
+ return value != null ? value.getAsJsonObject() : null;
}
public static JsonArray getRoomWaypoints(String room) {
- return waypointsJson.get(room).getAsJsonArray();
+ JsonElement value = waypointsJson.get(room);
+ return value != null ? value.getAsJsonArray() : null;
}
/**
@@ -190,35 +200,58 @@ public class DungeonSecrets {
return customWaypoints.remove(room, pos);
}
+ public static Room getCurrentRoom() {
+ return currentRoom;
+ }
+
+ public static boolean isInBoss() {
+ return inBoss;
+ }
+
/**
* Loads the dungeon secrets asynchronously from {@code /assets/skyblocker/dungeons}.
* Use {@link #isRoomsLoaded()} to check for completion of loading.
*/
public static void init() {
- if (SkyblockerConfigManager.get().locations.dungeons.secretWaypoints.noInitSecretWaypoints) {
+ if (!SkyblockerConfigManager.get().locations.dungeons.secretWaypoints.enableRoomMatching) {
return;
}
// Execute with MinecraftClient as executor since we need to wait for MinecraftClient#resourceManager to be set
- CompletableFuture.runAsync(DungeonSecrets::load, MinecraftClient.getInstance()).exceptionally(e -> {
+ CompletableFuture.runAsync(DungeonManager::load, MinecraftClient.getInstance()).exceptionally(e -> {
LOGGER.error("[Skyblocker Dungeon Secrets] Failed to load dungeon secrets", e);
return null;
});
- ClientLifecycleEvents.CLIENT_STOPPING.register(DungeonSecrets::saveCustomWaypoints);
- Scheduler.INSTANCE.scheduleCyclic(DungeonSecrets::update, 10);
- WorldRenderEvents.AFTER_TRANSLUCENT.register(DungeonSecrets::render);
- ClientReceiveMessageEvents.GAME.register(DungeonSecrets::onChatMessage);
- ClientReceiveMessageEvents.GAME_CANCELED.register(DungeonSecrets::onChatMessage);
+ ClientLifecycleEvents.CLIENT_STOPPING.register(DungeonManager::saveCustomWaypoints);
+ Scheduler.INSTANCE.scheduleCyclic(DungeonManager::update, 5);
+ WorldRenderEvents.AFTER_TRANSLUCENT.register(DungeonManager::render);
+ ClientReceiveMessageEvents.GAME.register(DungeonManager::onChatMessage);
+ ClientReceiveMessageEvents.GAME_CANCELED.register(DungeonManager::onChatMessage);
UseBlockCallback.EVENT.register((player, world, hand, hitResult) -> onUseBlock(world, hitResult));
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("dungeons").then(literal("secrets")
.then(literal("markAsFound").then(markSecretsCommand(true)))
.then(literal("markAsMissing").then(markSecretsCommand(false)))
- .then(literal("getRelativePos").executes(DungeonSecrets::getRelativePos))
- .then(literal("getRelativeTargetPos").executes(DungeonSecrets::getRelativeTargetPos))
+ .then(literal("getRelativePos").executes(DungeonManager::getRelativePos))
+ .then(literal("getRelativeTargetPos").executes(DungeonManager::getRelativeTargetPos))
.then(literal("addWaypoint").then(addCustomWaypointCommand(false)))
.then(literal("addWaypointRelatively").then(addCustomWaypointCommand(true)))
.then(literal("removeWaypoint").then(removeCustomWaypointCommand(false)))
.then(literal("removeWaypointRelatively").then(removeCustomWaypointCommand(true)))
))));
+ if (Debug.debugEnabled()) {
+ ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("dungeons").then(literal("secrets")
+ .then(literal("matchAgainst").then(matchAgainstCommand()))
+ .then(literal("clearSubProcesses").executes(context -> {
+ if (currentRoom != null) {
+ currentRoom.tickables.clear();
+ currentRoom.renderables.clear();
+ context.getSource().sendFeedback(Constants.PREFIX.get().append("§rCleared sub processes in the current room."));
+ } else {
+ context.getSource().sendError(Constants.PREFIX.get().append("§cCurrent room is null."));
+ }
+ return Command.SINGLE_SUCCESS;
+ }))
+ ))));
+ }
ClientPlayConnectionEvents.JOIN.register(((handler, sender, client) -> reset()));
}
@@ -304,7 +337,7 @@ public class DungeonSecrets {
SkyblockerMod.GSON.fromJson(reader, JsonObject.class).asMap().forEach((room, jsonElement) -> map.put(room.toLowerCase().replaceAll(" ", "-"), jsonElement));
}
- private static ArgumentBuilder<FabricClientCommandSource, RequiredArgumentBuilder<FabricClientCommandSource, Integer>> markSecretsCommand(boolean found) {
+ private static RequiredArgumentBuilder<FabricClientCommandSource, Integer> markSecretsCommand(boolean found) {
return argument("secretIndex", IntegerArgumentType.integer()).executes(context -> {
int secretIndex = IntegerArgumentType.getInteger(context, "secretIndex");
if (markSecrets(secretIndex, found)) {
@@ -333,14 +366,14 @@ public class DungeonSecrets {
Room room = getRoomAtPhysical(pos);
if (isRoomMatched(room)) {
BlockPos relativePos = currentRoom.actualToRelative(pos);
- source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.posMessage", currentRoom.getName(), relativePos.getX(), relativePos.getY(), relativePos.getZ())));
+ source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.posMessage", currentRoom.getName(), currentRoom.getDirection().asString(), relativePos.getX(), relativePos.getY(), relativePos.getZ())));
} else {
source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.notMatched")));
}
return Command.SINGLE_SUCCESS;
}
- private static ArgumentBuilder<FabricClientCommandSource, RequiredArgumentBuilder<FabricClientCommandSource, PosArgument>> addCustomWaypointCommand(boolean relative) {
+ private static RequiredArgumentBuilder<FabricClientCommandSource, PosArgument> addCustomWaypointCommand(boolean relative) {
return argument("pos", BlockPosArgumentType.blockPos())
.then(argument("secretIndex", IntegerArgumentType.integer())
.then(argument("category", SecretWaypoint.Category.CategoryArgumentType.category())
@@ -372,7 +405,7 @@ public class DungeonSecrets {
return Command.SINGLE_SUCCESS;
}
- private static ArgumentBuilder<FabricClientCommandSource, RequiredArgumentBuilder<FabricClientCommandSource, PosArgument>> removeCustomWaypointCommand(boolean relative) {
+ private static RequiredArgumentBuilder<FabricClientCommandSource, PosArgument> removeCustomWaypointCommand(boolean relative) {
return argument("pos", BlockPosArgumentType.blockPos())
.executes(context -> {
// TODO Less hacky way with custom ClientBlockPosArgumentType
@@ -400,6 +433,61 @@ public class DungeonSecrets {
return Command.SINGLE_SUCCESS;
}
+ private static RequiredArgumentBuilder<FabricClientCommandSource, String> matchAgainstCommand() {
+ return argument("room", StringArgumentType.string()).suggests((context, builder) -> CommandSource.suggestMatching(ROOMS_DATA.values().stream().map(Map::values).flatMap(Collection::stream).map(Map::keySet).flatMap(Collection::stream), builder)).then(argument("direction", Room.Direction.DirectionArgumentType.direction()).executes(context -> {
+ if (physicalEntrancePos == null || mapEntrancePos == null || mapRoomSize == 0) {
+ context.getSource().sendError(Constants.PREFIX.get().append("§cYou are not in a dungeon."));
+ return Command.SINGLE_SUCCESS;
+ }
+ MinecraftClient client = MinecraftClient.getInstance();
+ if (client.player == null || client.world == null) {
+ context.getSource().sendError(Constants.PREFIX.get().append("§cFailed to get player or world."));
+ return Command.SINGLE_SUCCESS;
+ }
+ ItemStack stack = client.player.getInventory().main.get(8);
+ if (!stack.isOf(Items.FILLED_MAP)) {
+ context.getSource().sendError(Constants.PREFIX.get().append("§cFailed to get dungeon map."));
+ return Command.SINGLE_SUCCESS;
+ }
+ MapState map = FilledMapItem.getMapState(FilledMapItem.getMapId(stack), client.world);
+ if (map == null) {
+ context.getSource().sendError(Constants.PREFIX.get().append("§cFailed to get dungeon map state."));
+ return Command.SINGLE_SUCCESS;
+ }
+
+ String roomName = StringArgumentType.getString(context, "room");
+ Room.Direction direction = Room.Direction.DirectionArgumentType.getDirection(context, "direction");
+
+ Room room = newDebugRoom(roomName, direction, client.player, map);
+ if (room == null) {
+ context.getSource().sendError(Constants.PREFIX.get().append("§cFailed to find room with name " + roomName + "."));
+ return Command.SINGLE_SUCCESS;
+ }
+ if (currentRoom != null) {
+ currentRoom.addSubProcess(room);
+ context.getSource().sendFeedback(Constants.PREFIX.get().append("§rMatching room " + roomName + " with direction " + direction + " against current room."));
+ } else {
+ context.getSource().sendError(Constants.PREFIX.get().append("§cCurrent room is null."));
+ }
+
+ return Command.SINGLE_SUCCESS;
+ }));
+ }
+
+ @Nullable
+ private static Room newDebugRoom(String roomName, Room.Direction direction, PlayerEntity player, MapState map) {
+ Room room = null;
+ int[] roomData;
+ if ((roomData = ROOMS_DATA.get("catacombs").get(Room.Shape.PUZZLE.shape).get(roomName)) != null) {
+ room = DebugRoom.ofSinglePossibleRoom(Room.Type.PUZZLE, DungeonMapUtils.getPhysicalRoomPos(player.getPos()), roomName, roomData, direction);
+ } else if ((roomData = ROOMS_DATA.get("catacombs").get(Room.Shape.TRAP.shape).get(roomName)) != null) {
+ room = DebugRoom.ofSinglePossibleRoom(Room.Type.TRAP, DungeonMapUtils.getPhysicalRoomPos(player.getPos()), roomName, roomData, direction);
+ } else if ((roomData = ROOMS_DATA.get("catacombs").values().stream().map(Map::entrySet).flatMap(Collection::stream).filter(entry -> entry.getKey().equals(roomName)).findAny().map(Map.Entry::getValue).orElse(null)) != null) {
+ room = DebugRoom.ofSinglePossibleRoom(Room.Type.ROOM, DungeonMapUtils.getPhysicalPosFromMap(mapEntrancePos, mapRoomSize, physicalEntrancePos, DungeonMapUtils.getRoomSegments(map, DungeonMapUtils.getMapRoomPos(map, mapEntrancePos, mapRoomSize), mapRoomSize, Room.Type.ROOM.color)), roomName, roomData, direction);
+ }
+ return room;
+ }
+
/**
* Updates the dungeon. The general idea is similar to the Dungeon Rooms Mod.
* <p></p>
@@ -421,14 +509,11 @@ public class DungeonSecrets {
* <li> Create a new room. </li>
* </ul>
* <li> Sets {@link #currentRoom} to the current room, either created from the previous step or from {@link #rooms}. </li>
- * <li> Calls {@link Room#update()} on {@link #currentRoom}. </li>
+ * <li> Calls {@link Tickable#tick(MinecraftClient)} on {@link #currentRoom}. </li>
* </ul>
*/
@SuppressWarnings("JavadocReference")
private static void update() {
- if (!SkyblockerConfigManager.get().locations.dungeons.secretWaypoints.enableSecretWaypoints) {
- return;
- }
if (!Utils.isInDungeons()) {
if (mapEntrancePos != null) {
reset();
@@ -436,16 +521,15 @@ public class DungeonSecrets {
return;
}
MinecraftClient client = MinecraftClient.getInstance();
- ClientPlayerEntity player = client.player;
- if (player == null || client.world == null) {
+ if (client.player == null || client.world == null) {
return;
}
if (physicalEntrancePos == null) {
- Vec3d playerPos = player.getPos();
+ Vec3d playerPos = client.player.getPos();
physicalEntrancePos = DungeonMapUtils.getPhysicalRoomPos(playerPos);
currentRoom = newRoom(Room.Type.ENTRANCE, physicalEntrancePos);
}
- ItemStack stack = player.getInventory().main.get(8);
+ ItemStack stack = client.player.getInventory().main.get(8);
if (!stack.isOf(Items.FILLED_MAP)) {
return;
}
@@ -477,9 +561,13 @@ public class DungeonSecrets {
}
}
if (room != null && currentRoom != room) {
+ if (currentRoom != null && room.getType() == Room.Type.FAIRY) {
+ currentRoom.nextRoom = room;
+ room.keyFound = currentRoom.keyFound;
+ }
currentRoom = room;
}
- currentRoom.update();
+ currentRoom.tick(client);
}
/**
@@ -561,7 +649,10 @@ public class DungeonSecrets {
if (message.equals("[BOSS] Bonzo: Gratz for making it this far, but I'm basically unbeatable.") || message.equals("[BOSS] Scarf: This is where the journey ends for you, Adventurers.")
|| message.equals("[BOSS] The Professor: I was burdened with terrible news recently...") || message.equals("[BOSS] Thorn: Welcome Adventurers! I am Thorn, the Spirit! And host of the Vegan Trials!")
|| message.equals("[BOSS] Livid: Welcome, you've arrived right on time. I am Livid, the Master of Shadows.") || message.equals("[BOSS] Sadan: So you made it all the way here... Now you wish to defy me? Sadan?!")
- || message.equals("[BOSS] Maxor: WELL! WELL! WELL! LOOK WHO'S HERE!")) reset();
+ || message.equals("[BOSS] Maxor: WELL! WELL! WELL! LOOK WHO'S HERE!")) {
+ reset();
+ inBoss = true;
+ }
}
/**
@@ -647,7 +738,7 @@ public class DungeonSecrets {
*
* @return {@code true} if {@link #currentRoom} is not null and {@link #isRoomMatched(Room)}
*/
- private static boolean isCurrentRoomMatched() {
+ public static boolean isCurrentRoomMatched() {
return isRoomMatched(currentRoom);
}
@@ -663,12 +754,12 @@ public class DungeonSecrets {
}
/**
- * Checks if the player is in a dungeon and {@link de.hysky.skyblocker.config.SkyblockerConfig.Dungeons#secretWaypoints Secret Waypoints} is enabled.
+ * Checks if {@link SkyblockerConfig.SecretWaypoints#enableRoomMatching room matching} is enabled and the player is in a dungeon.
*
- * @return whether dungeon secrets should be processed
+ * @return whether room matching and dungeon secrets should be processed
*/
private static boolean shouldProcess() {
- return SkyblockerConfigManager.get().locations.dungeons.secretWaypoints.enableSecretWaypoints && Utils.isInDungeons();
+ return SkyblockerConfigManager.get().locations.dungeons.secretWaypoints.enableRoomMatching && Utils.isInDungeons();
}
/**
@@ -680,5 +771,6 @@ public class DungeonSecrets {
physicalEntrancePos = null;
rooms.clear();
currentRoom = null;
+ inBoss = false;
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java
index 01f2c9fc..b12bba62 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java
@@ -130,7 +130,7 @@ public class DungeonMapUtils {
return null;
}
Vector2ic offset = new Vector2i(mapEntrancePos.x() % mapRoomSizeWithGap, mapEntrancePos.y() % mapRoomSizeWithGap);
- return mapPos.add(2, 2).sub(offset).sub(mapPos.x() % mapRoomSizeWithGap, mapPos.y() % mapRoomSizeWithGap).add(offset);
+ return mapPos.add(2, 2).sub(offset).sub(Math.floorMod(mapPos.x(), mapRoomSizeWithGap), Math.floorMod(mapPos.y(), mapRoomSizeWithGap)).add(offset);
}
/**
@@ -271,7 +271,7 @@ public class DungeonMapUtils {
queue.add(newMapPos);
}
}
- DungeonSecrets.LOGGER.debug("[Skyblocker] Found dungeon room segments: {}", Arrays.toString(segments.toArray()));
+ DungeonManager.LOGGER.debug("[Skyblocker] Found dungeon room segments: {}", Arrays.toString(segments.toArray()));
return segments.toArray(Vector2ic[]::new);
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java
index 7797513f..a1bafc20 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java
@@ -2,13 +2,18 @@ package de.hysky.skyblocker.skyblock.dungeon.secrets;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
+import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.context.CommandContext;
+import com.mojang.serialization.Codec;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.events.DungeonEvents;
import de.hysky.skyblocker.utils.Constants;
+import de.hysky.skyblocker.utils.Tickable;
import de.hysky.skyblocker.utils.render.RenderHelper;
+import de.hysky.skyblocker.utils.render.Renderable;
import de.hysky.skyblocker.utils.scheduler.Scheduler;
import it.unimi.dsi.fastutil.ints.IntRBTreeSet;
import it.unimi.dsi.fastutil.ints.IntSortedSet;
@@ -22,11 +27,13 @@ import net.minecraft.block.MapColor;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.world.ClientWorld;
+import net.minecraft.command.argument.EnumArgumentType;
import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.mob.AmbientEntity;
import net.minecraft.registry.Registries;
import net.minecraft.text.Text;
+import net.minecraft.util.StringIdentifiable;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
@@ -44,16 +51,16 @@ import java.util.concurrent.CompletableFuture;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-public class Room {
+public class Room implements Tickable, Renderable {
private static final Pattern SECRET_INDEX = Pattern.compile("^(\\d+)");
private static final Pattern SECRETS = Pattern.compile("§7(\\d{1,2})/(\\d{1,2}) Secrets");
private static final Vec3d DOOR_SIZE = new Vec3d(3, 4, 3);
- private static final float[] RED_COLOR_COMPONENTS = {1, 0, 0};
- private static final float[] GREEN_COLOR_COMPONENTS = {0, 1, 0};
+ protected static final float[] RED_COLOR_COMPONENTS = {1, 0, 0};
+ protected static final float[] GREEN_COLOR_COMPONENTS = {0, 1, 0};
@NotNull
private final Type type;
@NotNull
- private final Set<Vector2ic> segments;
+ final Set<Vector2ic> segments;
/**
* The shape of the room. See {@link #getShape(IntSortedSet, IntSortedSet)}.
@@ -63,11 +70,11 @@ public class Room {
/**
* The room data containing all rooms for a specific dungeon and {@link #shape}.
*/
- private Map<String, int[]> roomsData;
+ protected Map<String, int[]> roomsData;
/**
* Contains all possible dungeon rooms for this room. The list is gradually shrunk by checking blocks until only one room is left.
*/
- private List<MutableTriple<Direction, Vector2ic, List<String>>> possibleRooms;
+ protected List<MutableTriple<Direction, Vector2ic, List<String>>> possibleRooms;
/**
* Contains all blocks that have been checked to prevent checking the same block multiple times.
*/
@@ -75,7 +82,7 @@ public class Room {
/**
* The task that is used to check blocks. This is used to ensure only one such task can run at a time.
*/
- private CompletableFuture<Void> findRoom;
+ protected CompletableFuture<Void> findRoom;
private int doubleCheckBlocks;
/**
* Represents the matching state of the room with the following possible values:
@@ -84,17 +91,24 @@ public class Room {
* <li>{@link MatchState#MATCHED} means that the room has a unique match ans has been double checked.</li>
* <li>{@link MatchState#FAILED} means that the room has been checked and there is no match.</li>
*/
- private MatchState matchState = MatchState.MATCHING;
+ protected MatchState matchState = MatchState.MATCHING;
private Table<Integer, BlockPos, SecretWaypoint> secretWaypoints;
private String name;
private Direction direction;
private Vector2ic physicalCornerPos;
+ protected List<Tickable> tickables = new ArrayList<>();
+ protected List<Renderable> renderables = new ArrayList<>();
+ /**
+ * Stores the next room in the dungeon. Currently only used if the next room is the fairy room.
+ */
+ @Nullable
+ protected Room nextRoom;
@Nullable
private BlockPos doorPos;
@Nullable
private Box doorBox;
- private boolean keyFound;
+ protected boolean keyFound;
public Room(@NotNull Type type, @NotNull Vector2ic... physicalPositions) {
this.type = type;
@@ -102,7 +116,7 @@ public class Room {
IntSortedSet segmentsX = IntSortedSets.unmodifiable(new IntRBTreeSet(segments.stream().mapToInt(Vector2ic::x).toArray()));
IntSortedSet segmentsY = IntSortedSets.unmodifiable(new IntRBTreeSet(segments.stream().mapToInt(Vector2ic::y).toArray()));
shape = getShape(segmentsX, segmentsY);
- roomsData = DungeonSecrets.ROOMS_DATA.getOrDefault("catacombs", Collections.emptyMap()).getOrDefault(shape.shape.toLowerCase(), Collections.emptyMap());
+ roomsData = DungeonManager.ROOMS_DATA.getOrDefault("catacombs", Collections.emptyMap()).getOrDefault(shape.shape.toLowerCase(), Collections.emptyMap());
possibleRooms = getPossibleRooms(segmentsX, segmentsY);
}
@@ -122,6 +136,13 @@ public class Room {
return name;
}
+ /**
+ * Not null if {@link #isMatched()}.
+ */
+ public Direction getDirection() {
+ return direction;
+ }
+
@Override
public String toString() {
return "Room{type=%s, segments=%s, shape=%s, matchState=%s, name=%s, direction=%s, physicalCornerPos=%s}".formatted(type, Arrays.toString(segments.toArray()), shape, matchState, name, direction, physicalCornerPos);
@@ -129,12 +150,16 @@ public class Room {
@NotNull
private Shape getShape(IntSortedSet segmentsX, IntSortedSet segmentsY) {
- return switch (segments.size()) {
- case 1 -> Shape.ONE_BY_ONE;
- case 2 -> Shape.ONE_BY_TWO;
- case 3 -> segmentsX.size() == 2 && segmentsY.size() == 2 ? Shape.L_SHAPE : Shape.ONE_BY_THREE;
- case 4 -> segmentsX.size() == 2 && segmentsY.size() == 2 ? Shape.TWO_BY_TWO : Shape.ONE_BY_FOUR;
- default -> throw new IllegalArgumentException("There are no matching room shapes with this set of physical positions: " + Arrays.toString(segments.toArray()));
+ return switch (type) {
+ case PUZZLE -> Shape.PUZZLE;
+ case TRAP -> Shape.TRAP;
+ default -> switch (segments.size()) {
+ case 1 -> Shape.ONE_BY_ONE;
+ case 2 -> Shape.ONE_BY_TWO;
+ case 3 -> segmentsX.size() == 2 && segmentsY.size() == 2 ? Shape.L_SHAPE : Shape.ONE_BY_THREE;
+ case 4 -> segmentsX.size() == 2 && segmentsY.size() == 2 ? Shape.TWO_BY_TWO : Shape.ONE_BY_FOUR;
+ default -> throw new IllegalArgumentException("There are no matching room shapes with this set of physical positions: " + Arrays.toString(segments.toArray()));
+ };
};
}
@@ -150,7 +175,7 @@ public class Room {
@NotNull
private Direction[] getPossibleDirections(IntSortedSet segmentsX, IntSortedSet segmentsY) {
return switch (shape) {
- case ONE_BY_ONE, TWO_BY_TWO -> Direction.values();
+ case ONE_BY_ONE, TWO_BY_TWO, PUZZLE, TRAP -> Direction.values();
case ONE_BY_TWO, ONE_BY_THREE, ONE_BY_FOUR -> {
if (segmentsX.size() > 1 && segmentsY.size() == 1) {
yield new Direction[]{Direction.NW, Direction.SE};
@@ -186,7 +211,7 @@ public class Room {
}
/**
- * Adds a custom waypoint relative to this room to {@link DungeonSecrets#customWaypoints} and all existing instances of this room.
+ * Adds a custom waypoint relative to this room to {@link DungeonManager#customWaypoints} and all existing instances of this room.
*
* @param secretIndex the index of the secret waypoint
* @param category the category of the secret waypoint
@@ -196,8 +221,8 @@ public class Room {
@SuppressWarnings("JavadocReference")
private void addCustomWaypoint(int secretIndex, SecretWaypoint.Category category, Text waypointName, BlockPos pos) {
SecretWaypoint waypoint = new SecretWaypoint(secretIndex, category, waypointName, pos);
- DungeonSecrets.addCustomWaypoint(name, waypoint);
- DungeonSecrets.getRoomsStream().filter(r -> name.equals(r.getName())).forEach(r -> r.addCustomWaypoint(waypoint));
+ DungeonManager.addCustomWaypoint(name, waypoint);
+ DungeonManager.getRoomsStream().filter(r -> name.equals(r.getName())).forEach(r -> r.addCustomWaypoint(waypoint));
}
/**
@@ -223,7 +248,7 @@ public class Room {
}
/**
- * Removes a custom waypoint relative to this room from {@link DungeonSecrets#customWaypoints} and all existing instances of this room.
+ * Removes a custom waypoint relative to this room from {@link DungeonManager#customWaypoints} and all existing instances of this room.
*
* @param pos the position of the secret waypoint relative to this room
* @return the removed secret waypoint or {@code null} if there was no secret waypoint at the given position
@@ -231,9 +256,9 @@ public class Room {
@SuppressWarnings("JavadocReference")
@Nullable
private SecretWaypoint removeCustomWaypoint(BlockPos pos) {
- SecretWaypoint waypoint = DungeonSecrets.removeCustomWaypoint(name, pos);
+ SecretWaypoint waypoint = DungeonManager.removeCustomWaypoint(name, pos);
if (waypoint != null) {
- DungeonSecrets.getRoomsStream().filter(r -> name.equals(r.getName())).forEach(r -> r.removeCustomWaypoint(waypoint.secretIndex, pos));
+ DungeonManager.getRoomsStream().filter(r -> name.equals(r.getName())).forEach(r -> r.removeCustomWaypoint(waypoint.secretIndex, pos));
}
return waypoint;
}
@@ -249,6 +274,11 @@ public class Room {
secretWaypoints.remove(secretIndex, actualPos);
}
+ public <T extends Tickable & Renderable> void addSubProcess(T process) {
+ tickables.add(process);
+ renderables.add(process);
+ }
+
/**
* Updates the room.
* <p></p>
@@ -268,16 +298,19 @@ public class Room {
* </ul>
*/
@SuppressWarnings("JavadocReference")
- protected void update() {
- MinecraftClient client = MinecraftClient.getInstance();
- ClientWorld world = client.world;
- if (world == null) {
+ @Override
+ public void tick(MinecraftClient client) {
+ if (client.world == null) {
return;
}
+ for (Tickable tickable : tickables) {
+ tickable.tick(client);
+ }
+
// Wither and blood door
if (SkyblockerConfigManager.get().locations.dungeons.doorHighlight.enableDoorHighlight && doorPos == null) {
- doorPos = DungeonMapUtils.getWitherBloodDoorPos(world, segments);
+ doorPos = DungeonMapUtils.getWitherBloodDoorPos(client.world, segments);
if (doorPos != null) {
doorBox = new Box(doorPos.getX(), doorPos.getY(), doorPos.getZ(), doorPos.getX() + DOOR_SIZE.getX(), doorPos.getY() + DOOR_SIZE.getY(), doorPos.getZ() + DOOR_SIZE.getZ());
}
@@ -285,7 +318,7 @@ public class Room {
// Room scanning and matching
// Logical AND has higher precedence than logical OR
- if (!type.needsScanning() || matchState != MatchState.MATCHING && matchState != MatchState.DOUBLE_CHECKING || !DungeonSecrets.isRoomsLoaded() || findRoom != null && !findRoom.isDone()) {
+ if (!type.needsScanning() || matchState != MatchState.MATCHING && matchState != MatchState.DOUBLE_CHECKING || !DungeonManager.isRoomsLoaded() || findRoom != null && !findRoom.isDone()) {
return;
}
ClientPlayerEntity player = client.player;
@@ -294,12 +327,12 @@ public class Room {
}
findRoom = CompletableFuture.runAsync(() -> {
for (BlockPos pos : BlockPos.iterate(player.getBlockPos().add(-5, -5, -5), player.getBlockPos().add(5, 5, 5))) {
- if (segments.contains(DungeonMapUtils.getPhysicalRoomPos(pos)) && notInDoorway(pos) && checkedBlocks.add(pos) && checkBlock(world, pos)) {
+ if (segments.contains(DungeonMapUtils.getPhysicalRoomPos(pos)) && notInDoorway(pos) && checkedBlocks.add(pos) && checkBlock(client.world, pos)) {
break;
}
}
}).exceptionally(e -> {
- DungeonSecrets.LOGGER.error("[Skyblocker Dungeon Secrets] Encountered an unknown exception while matching room {}", this, e);
+ DungeonManager.LOGGER.error("[Skyblocker Dungeon Secrets] Encountered an unknown exception while matching room {}", this, e);
return null;
});
}
@@ -318,7 +351,7 @@ public class Room {
* <p></p>
* This method:
* <ul>
- * <li> Checks if the block type is included in the dungeon rooms data. See {@link DungeonSecrets#NUMERIC_ID}. </li>
+ * <li> Checks if the block type is included in the dungeon rooms data. See {@link DungeonManager#NUMERIC_ID}. </li>
* <li> For each possible direction: </li>
* <ul>
* <li> Rotate and convert the position to a relative position. See {@link DungeonMapUtils#actualToRelative(Direction, Vector2ic, BlockPos)}. </li>
@@ -358,8 +391,8 @@ public class Room {
* @param pos the position of the block to check
* @return whether room matching should end. Either a match is found or there are no valid rooms left
*/
- private boolean checkBlock(ClientWorld world, BlockPos pos) {
- byte id = DungeonSecrets.NUMERIC_ID.getByte(Registries.BLOCK.getId(world.getBlockState(pos).getBlock()).toString());
+ protected boolean checkBlock(ClientWorld world, BlockPos pos) {
+ byte id = DungeonManager.NUMERIC_ID.getByte(Registries.BLOCK.getId(world.getBlockState(pos).getBlock()).toString());
if (id == 0) {
return false;
}
@@ -375,9 +408,10 @@ public class Room {
}
int matchingRoomsSize = possibleRooms.stream().map(Triple::getRight).mapToInt(Collection::size).sum();
- if (matchingRoomsSize == 0) {
+ if (matchingRoomsSize == 0) synchronized (this) {
// If no rooms match, reset the fields and scan again after 50 ticks.
- DungeonSecrets.LOGGER.warn("[Skyblocker Dungeon Secrets] No dungeon room matched after checking {} block(s) including double checking {} block(s)", checkedBlocks.size(), doubleCheckBlocks);
+ matchState = MatchState.FAILED;
+ DungeonManager.LOGGER.warn("[Skyblocker Dungeon Secrets] No dungeon room matched after checking {} block(s) including double checking {} block(s)", checkedBlocks.size(), doubleCheckBlocks);
Scheduler.INSTANCE.schedule(() -> matchState = MatchState.MATCHING, 50);
reset();
return true;
@@ -388,18 +422,20 @@ public class Room {
name = directionRoom.getRight().get(0);
direction = directionRoom.getLeft();
physicalCornerPos = directionRoom.getMiddle();
- DungeonSecrets.LOGGER.info("[Skyblocker Dungeon Secrets] Room {} matched after checking {} block(s), starting double checking", name, checkedBlocks.size());
+ DungeonManager.LOGGER.info("[Skyblocker Dungeon Secrets] Room {} matched after checking {} block(s), starting double checking", name, checkedBlocks.size());
roomMatched();
return false;
} else if (matchState == MatchState.DOUBLE_CHECKING && ++doubleCheckBlocks >= 10) {
// If double-checked, set state to matched and discard the no longer needed fields.
- DungeonSecrets.LOGGER.info("[Skyblocker Dungeon Secrets] Room {} matched after checking {} block(s) including double checking {} block(s)", name, checkedBlocks.size(), doubleCheckBlocks);
+ matchState = MatchState.MATCHED;
+ DungeonEvents.ROOM_MATCHED.invoker().onRoomMatched(this);
+ DungeonManager.LOGGER.info("[Skyblocker Dungeon Secrets] Room {} confirmed after checking {} block(s) including double checking {} block(s)", name, checkedBlocks.size(), doubleCheckBlocks);
discard();
return true;
}
return false;
} else {
- DungeonSecrets.LOGGER.debug("[Skyblocker Dungeon Secrets] {} room(s) remaining after checking {} block(s)", matchingRoomsSize, checkedBlocks.size());
+ DungeonManager.LOGGER.debug("[Skyblocker Dungeon Secrets] {} room(s) remaining after checking {} block(s)", matchingRoomsSize, checkedBlocks.size());
return false;
}
}
@@ -411,12 +447,12 @@ public class Room {
* @param id the custom numeric block id
* @return the encoded integer
*/
- private int posIdToInt(BlockPos pos, byte id) {
+ protected int posIdToInt(BlockPos pos, byte id) {
return pos.getX() << 24 | pos.getY() << 16 | pos.getZ() << 8 | id;
}
/**
- * Loads the secret waypoints for the room from {@link DungeonSecrets#waypointsJson} once it has been matched
+ * Loads the secret waypoints for the room from {@link DungeonManager#waypointsJson} once it has been matched
* and sets {@link #matchState} to {@link MatchState#DOUBLE_CHECKING}.
*
* @param directionRooms the direction, position, and name of the room
@@ -424,23 +460,25 @@ public class Room {
@SuppressWarnings("JavadocReference")
private void roomMatched() {
secretWaypoints = HashBasedTable.create();
- for (JsonElement waypointElement : DungeonSecrets.getRoomWaypoints(name)) {
- JsonObject waypoint = waypointElement.getAsJsonObject();
- String secretName = waypoint.get("secretName").getAsString();
- Matcher secretIndexMatcher = SECRET_INDEX.matcher(secretName);
- int secretIndex = secretIndexMatcher.find() ? Integer.parseInt(secretIndexMatcher.group(1)) : 0;
- BlockPos pos = DungeonMapUtils.relativeToActual(direction, physicalCornerPos, waypoint);
- secretWaypoints.put(secretIndex, pos, new SecretWaypoint(secretIndex, waypoint, secretName, pos));
+ JsonArray secretWaypointsJson = DungeonManager.getRoomWaypoints(name);
+ if (secretWaypointsJson != null) {
+ for (JsonElement waypointElement : secretWaypointsJson) {
+ JsonObject waypoint = waypointElement.getAsJsonObject();
+ String secretName = waypoint.get("secretName").getAsString();
+ Matcher secretIndexMatcher = SECRET_INDEX.matcher(secretName);
+ int secretIndex = secretIndexMatcher.find() ? Integer.parseInt(secretIndexMatcher.group(1)) : 0;
+ BlockPos pos = DungeonMapUtils.relativeToActual(direction, physicalCornerPos, waypoint);
+ secretWaypoints.put(secretIndex, pos, new SecretWaypoint(secretIndex, waypoint, secretName, pos));
+ }
}
- DungeonSecrets.getCustomWaypoints(name).values().forEach(this::addCustomWaypoint);
+ DungeonManager.getCustomWaypoints(name).values().forEach(this::addCustomWaypoint);
matchState = MatchState.DOUBLE_CHECKING;
}
/**
* Resets fields for another round of matching after room matching fails.
*/
- private void reset() {
- matchState = MatchState.FAILED;
+ protected void reset() {
IntSortedSet segmentsX = IntSortedSets.unmodifiable(new IntRBTreeSet(segments.stream().mapToInt(Vector2ic::x).toArray()));
IntSortedSet segmentsY = IntSortedSets.unmodifiable(new IntRBTreeSet(segments.stream().mapToInt(Vector2ic::y).toArray()));
possibleRooms = getPossibleRooms(segmentsX, segmentsY);
@@ -457,7 +495,6 @@ public class Room {
* These fields are no longer needed and are discarded to save memory.
*/
private void discard() {
- matchState = MatchState.MATCHED;
roomsData = null;
possibleRooms = null;
checkedBlocks = null;
@@ -467,25 +504,32 @@ public class Room {
/**
* Fails if !{@link #isMatched()}
*/
- protected BlockPos actualToRelative(BlockPos pos) {
+ public BlockPos actualToRelative(BlockPos pos) {
return DungeonMapUtils.actualToRelative(direction, physicalCornerPos, pos);
}
/**
* Fails if !{@link #isMatched()}
*/
- protected BlockPos relativeToActual(BlockPos pos) {
+ public BlockPos relativeToActual(BlockPos pos) {
return DungeonMapUtils.relativeToActual(direction, physicalCornerPos, pos);
}
/**
* Calls {@link SecretWaypoint#render(WorldRenderContext)} on {@link #secretWaypoints all secret waypoints} and renders a highlight around the wither or blood door, if it exists.
*/
- protected void render(WorldRenderContext context) {
- if (isMatched()) {
- for (SecretWaypoint secretWaypoint : secretWaypoints.values()) {
- if (secretWaypoint.shouldRender()) {
- secretWaypoint.render(context);
+ @Override
+ public void render(WorldRenderContext context) {
+ for (Renderable renderable : renderables) {
+ renderable.render(context);
+ }
+
+ synchronized (this) {
+ if (SkyblockerConfigManager.get().locations.dungeons.secretWaypoints.enableSecretWaypoints && isMatched()) {
+ for (SecretWaypoint secretWaypoint : secretWaypoints.values()) {
+ if (secretWaypoint.shouldRender()) {
+ secretWaypoint.render(context);
+ }
}
}
}
@@ -580,7 +624,7 @@ public class Room {
*/
private void onSecretFound(SecretWaypoint secretWaypoint, String msg, Object... args) {
secretWaypoints.row(secretWaypoint.secretIndex).values().forEach(SecretWaypoint::setFound);
- DungeonSecrets.LOGGER.info(msg, args);
+ DungeonManager.LOGGER.info(msg, args);
}
protected boolean markSecrets(int secretIndex, boolean found) {
@@ -594,6 +638,9 @@ public class Room {
}
protected void keyFound() {
+ if (nextRoom != null && nextRoom.type == Type.FAIRY) {
+ nextRoom.keyFound = true;
+ }
keyFound = true;
}
@@ -623,13 +670,15 @@ public class Room {
}
}
- private enum Shape {
+ protected enum Shape {
ONE_BY_ONE("1x1"),
ONE_BY_TWO("1x2"),
ONE_BY_THREE("1x3"),
ONE_BY_FOUR("1x4"),
L_SHAPE("L-shape"),
- TWO_BY_TWO("2x2");
+ TWO_BY_TWO("2x2"),
+ PUZZLE("puzzle"),
+ TRAP("trap");
final String shape;
Shape(String shape) {
@@ -642,11 +691,36 @@ public class Room {
}
}
- public enum Direction {
- NW, NE, SW, SE
+ public enum Direction implements StringIdentifiable {
+ NW("northwest"), NE("northeast"), SW("southwest"), SE("southeast");
+ private static final Codec<Direction> CODEC = StringIdentifiable.createCodec(Direction::values);
+ private final String name;
+
+ Direction(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String asString() {
+ return name;
+ }
+
+ static class DirectionArgumentType extends EnumArgumentType<Direction> {
+ DirectionArgumentType() {
+ super(CODEC, Direction::values);
+ }
+
+ static DirectionArgumentType direction() {
+ return new DirectionArgumentType();
+ }
+
+ static <S> Direction getDirection(CommandContext<S> context, String name) {
+ return context.getArgument(name, Direction.class);
+ }
+ }
}
- public enum MatchState {
+ protected enum MatchState {
MATCHING, DOUBLE_CHECKING, MATCHED, FAILED
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java
index 75a0c20f..44df9d8c 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java
@@ -29,7 +29,7 @@ import java.util.function.Supplier;
import java.util.function.ToDoubleFunction;
public class SecretWaypoint extends Waypoint {
- protected static final Logger LOGGER = LoggerFactory.getLogger(SecretWaypoint.class);
+ private static final Logger LOGGER = LoggerFactory.getLogger(SecretWaypoint.class);
public static final Codec<SecretWaypoint> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.INT.fieldOf("secretIndex").forGetter(secretWaypoint -> secretWaypoint.secretIndex),
Category.CODEC.fieldOf("category").forGetter(secretWaypoint -> secretWaypoint.category),
@@ -39,7 +39,7 @@ public class SecretWaypoint extends Waypoint {
public static final Codec<List<SecretWaypoint>> LIST_CODEC = CODEC.listOf();
static final List<String> SECRET_ITEMS = List.of("Decoy", "Defuse Kit", "Dungeon Chest Key", "Healing VIII", "Inflatable Jerry", "Spirit Leap", "Training Weights", "Trap", "Treasure Talisman");
private static final Supplier<SkyblockerConfig.SecretWaypoints> CONFIG = () -> SkyblockerConfigManager.get().locations.dungeons.secretWaypoints;
- private static final Supplier<Type> TYPE_SUPPLIER = () -> CONFIG.get().waypointType;
+ static final Supplier<Type> TYPE_SUPPLIER = () -> CONFIG.get().waypointType;
final int secretIndex;
final Category category;
final Text name;
@@ -90,8 +90,13 @@ public class SecretWaypoint extends Waypoint {
return category.isBat();
}
+ @Override
+ public boolean equals(Object obj) {
+ return super.equals(obj) || obj instanceof SecretWaypoint other && secretIndex == other.secretIndex && category == other.category && name.equals(other.name) && pos.equals(other.pos);
+ }
+
/**
- * Renders the secret waypoint, including a filled cube, a beacon beam, the name, and the distance from the player.
+ * Renders the secret waypoint, including a waypoint through {@link Waypoint#render(WorldRenderContext)}, the name, and the distance from the player.
*/
@Override
public void render(WorldRenderContext context) {
@@ -173,15 +178,15 @@ public class SecretWaypoint extends Waypoint {
}
static class CategoryArgumentType extends EnumArgumentType<Category> {
- public CategoryArgumentType() {
+ CategoryArgumentType() {
super(Category.CODEC, Category::values);
}
- public static CategoryArgumentType category() {
+ static CategoryArgumentType category() {
return new CategoryArgumentType();
}
- public static <S> Category getCategory(CommandContext<S> context, String name) {
+ static <S> Category getCategory(CommandContext<S> context, String name) {
return context.getArgument(name, Category.class);
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java
index 5e0995e6..fa9f6e4f 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java
@@ -19,7 +19,7 @@ public class MobGlow {
public static boolean shouldMobGlow(Entity entity) {
Box box = entity.getBoundingBox();
- if (!entity.isInvisible() && OcclusionCulling.isVisible(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ)) {
+ if (!entity.isInvisible() && OcclusionCulling.getReducedCuller().isVisible(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ)) {
String name = entity.getName().getString();
// Dungeons
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/filters/ToggleSkyMallFilter.java b/src/main/java/de/hysky/skyblocker/skyblock/filters/ToggleSkyMallFilter.java
new file mode 100644
index 00000000..94d10e3a
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/filters/ToggleSkyMallFilter.java
@@ -0,0 +1,16 @@
+package de.hysky.skyblocker.skyblock.filters;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.chat.ChatFilterResult;
+
+public class ToggleSkyMallFilter extends SimpleChatFilter {
+
+ public ToggleSkyMallFilter() {
+ super("^§8§oYou can disable this messaging by toggling Sky Mall in your /hotm!$");
+ }
+
+ @Override
+ protected ChatFilterResult state() {
+ return SkyblockerConfigManager.get().messages.hideToggleSkyMall;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/ItemCooldowns.java b/src/main/java/de/hysky/skyblocker/skyblock/item/ItemCooldowns.java
index f1c9239c..b28e5da1 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/ItemCooldowns.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/ItemCooldowns.java
@@ -2,8 +2,8 @@ package de.hysky.skyblocker.skyblock.item;
import com.google.common.collect.ImmutableList;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
-import de.hysky.skyblocker.events.ClientPlayerBlockBreakEvent;
import de.hysky.skyblocker.utils.ItemUtils;
+import net.fabricmc.fabric.api.event.client.player.ClientPlayerBlockBreakEvents;
import net.fabricmc.fabric.api.event.player.UseItemCallback;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
@@ -26,7 +26,7 @@ public class ItemCooldowns {
private static final Map<String, CooldownEntry> ITEM_COOLDOWNS = new HashMap<>();
public static void init() {
- ClientPlayerBlockBreakEvent.AFTER.register(ItemCooldowns::afterBlockBreak);
+ ClientPlayerBlockBreakEvents.AFTER.register(ItemCooldowns::afterBlockBreak);
UseItemCallback.EVENT.register(ItemCooldowns::onItemInteract);
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/ItemRarityBackgrounds.java b/src/main/java/de/hysky/skyblocker/skyblock/item/ItemRarityBackgrounds.java
index 8867af91..c9cdb99a 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/ItemRarityBackgrounds.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/ItemRarityBackgrounds.java
@@ -28,6 +28,7 @@ public class ItemRarityBackgrounds {
private static final Supplier<Sprite> SPRITE = () -> MinecraftClient.getInstance().getGuiAtlasManager().getSprite(CONFIG.itemRarityBackgroundStyle.tex);
private static final ImmutableMap<String, SkyblockItemRarity> LORE_RARITIES = ImmutableMap.ofEntries(
Map.entry("ADMIN", SkyblockItemRarity.ADMIN),
+ Map.entry("ULTIMATE", SkyblockItemRarity.ULTIMATE),
Map.entry("SPECIAL", SkyblockItemRarity.SPECIAL), //Very special is the same color so this will cover it
Map.entry("DIVINE", SkyblockItemRarity.DIVINE),
Map.entry("MYTHIC", SkyblockItemRarity.MYTHIC),
@@ -36,8 +37,7 @@ public class ItemRarityBackgrounds {
Map.entry("EPIC", SkyblockItemRarity.EPIC),
Map.entry("RARE", SkyblockItemRarity.RARE),
Map.entry("UNCOMMON", SkyblockItemRarity.UNCOMMON),
- Map.entry("COMMON", SkyblockItemRarity.COMMON)
- );
+ Map.entry("COMMON", SkyblockItemRarity.COMMON));
private static final Int2ReferenceOpenHashMap<SkyblockItemRarity> CACHE = new Int2ReferenceOpenHashMap<>();
public static void init() {
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/PlayerHeadHashCache.java b/src/main/java/de/hysky/skyblocker/skyblock/item/PlayerHeadHashCache.java
new file mode 100644
index 00000000..2a1688fc
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/PlayerHeadHashCache.java
@@ -0,0 +1,64 @@
+package de.hysky.skyblocker.skyblock.item;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Base64;
+import java.util.concurrent.CompletableFuture;
+
+import org.apache.commons.io.FilenameUtils;
+import org.slf4j.Logger;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import com.mojang.logging.LogUtils;
+
+import de.hysky.skyblocker.utils.Http;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+
+public class PlayerHeadHashCache {
+ private static final Logger LOGGER = LogUtils.getLogger();
+ private static final IntOpenHashSet CACHE = new IntOpenHashSet();
+
+ public static void init() {
+ CompletableFuture.runAsync(PlayerHeadHashCache::loadSkins);
+ }
+
+ private static void loadSkins() {
+ try {
+ String response = Http.sendGetRequest("https://api.hypixel.net/v2/resources/skyblock/items");
+ JsonArray items = JsonParser.parseString(response).getAsJsonObject().getAsJsonArray("items");
+
+ items.asList().stream()
+ .map(JsonElement::getAsJsonObject)
+ .filter(item -> item.get("material").getAsString().equals("SKULL_ITEM"))
+ .filter(item -> item.has("skin"))
+ .map(item -> Base64.getDecoder().decode(item.get("skin").getAsString()))
+ .map(String::new)
+ .map(profile -> JsonParser.parseString(profile).getAsJsonObject())
+ .map(profile -> profile.getAsJsonObject("textures").getAsJsonObject("SKIN").get("url").getAsString())
+ .map(PlayerHeadHashCache::getSkinHash)
+ .mapToInt(String::hashCode)
+ .forEach(CACHE::add);
+
+ LOGGER.info("[Skyblocker Player Head Hash Cache] Successfully cached the hashes of all player head items!");
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker Player Head Hash Cache] Failed to cache skin hashes!", e);
+ }
+ }
+
+ //From MinecraftProfileTexture#getHash
+ public static String getSkinHash(String url) {
+ try {
+ return FilenameUtils.getBaseName(new URL(url).getPath());
+ } catch (MalformedURLException e) {
+ LOGGER.error("[Skyblocker Player Head Hash Cache] Malformed Skin URL! URL: {}", url, e);
+ }
+
+ return "";
+ }
+
+ public static boolean contains(int hash) {
+ return CACHE.contains(hash);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockItemRarity.java b/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockItemRarity.java
index 07a566af..4addeac6 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockItemRarity.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockItemRarity.java
@@ -4,6 +4,7 @@ import net.minecraft.util.Formatting;
public enum SkyblockItemRarity {
ADMIN(Formatting.DARK_RED),
+ ULTIMATE(Formatting.DARK_RED),
VERY_SPECIAL(Formatting.RED),
SPECIAL(Formatting.RED),
DIVINE(Formatting.AQUA),
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java
index adc23bbb..e4e18f8b 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java
@@ -11,6 +11,7 @@ import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.scheduler.Scheduler;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.item.TooltipContext;
+import net.minecraft.item.DyeableItem;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
@@ -20,8 +21,6 @@ import net.minecraft.util.Formatting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;
@@ -148,7 +147,7 @@ public class ItemTooltip {
}
if (TooltipInfoType.OBTAINED.isTooltipEnabled()) {
- String timestamp = getTimestamp(stack);
+ String timestamp = ItemUtils.getTimestamp(stack);
if (!timestamp.isEmpty()) {
lines.add(Text.literal(String.format("%-21s", "Obtained: "))
@@ -183,10 +182,10 @@ public class ItemTooltip {
}
if (TooltipInfoType.COLOR.isTooltipEnabledAndHasOrNullWarning(internalID) && stack.getNbt() != null) {
- final NbtElement color = stack.getNbt().getCompound("display").get("color");
+ boolean hasCustomDye = SkyblockerConfigManager.get().general.customDyeColors.containsKey(ItemUtils.getItemUuid(stack));
- if (color != null) {
- String colorHex = String.format("%06X", Integer.parseInt(color.asString()));
+ if (!hasCustomDye && stack.getItem() instanceof DyeableItem item && item.hasColor(stack)) {
+ String colorHex = String.format("%06X", item.getColor(stack));
String expectedHex = ExoticTooltip.getExpectedHex(internalID);
boolean correctLine = false;
@@ -221,43 +220,11 @@ public class ItemTooltip {
}
}
- /**
- * this method converts the "timestamp" variable into the same date format as Hypixel represents it in the museum.
- * Currently, there are two types of timestamps the legacy which is built like this
- * "dd/MM/yy hh:mm" ("25/04/20 16:38") and the current which is built like this
- * "MM/dd/yy hh:mm aa" ("12/24/20 11:08 PM"). Since Hypixel transforms the two formats into one format without
- * taking into account of their formats, we do the same. The final result looks like this
- * "MMMM dd, yyyy" (December 24, 2020).
- * Since the legacy format has a 25 as "month" SimpleDateFormat converts the 25 into 2 years and 1 month and makes
- * "25/04/20 16:38" -> "January 04, 2022" instead of "April 25, 2020".
- * This causes the museum rank to be much worse than it should be.
- *
- * @param stack the item under the pointer
- * @return if the item have a "Timestamp" it will be shown formated on the tooltip
- */
- public static String getTimestamp(ItemStack stack) {
- NbtCompound ea = ItemUtils.getExtraAttributes(stack);
-
- if (ea != null && ea.contains("timestamp", 8)) {
- SimpleDateFormat nbtFormat = new SimpleDateFormat("MM/dd/yy");
-
- try {
- Date date = nbtFormat.parse(ea.getString("timestamp"));
- SimpleDateFormat skyblockerFormat = new SimpleDateFormat("MMMM dd, yyyy", Locale.ENGLISH);
- return skyblockerFormat.format(date);
- } catch (ParseException e) {
- LOGGER.warn("[Skyblocker-tooltip] getTimestamp", e);
- }
- }
-
- return "";
- }
-
// TODO What in the world is this?
public static String getInternalNameFromNBT(ItemStack stack, boolean internalIDOnly) {
NbtCompound ea = ItemUtils.getExtraAttributes(stack);
- if (ea == null || !ea.contains(ItemUtils.ID, 8)) {
+ if (ea == null || !ea.contains(ItemUtils.ID, NbtElement.STRING_TYPE)) {
return null;
}
String internalName = ea.getString(ItemUtils.ID);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigListWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigListWidget.java
index 43647ec6..df5c4de1 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigListWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/ShortcutsConfigListWidget.java
@@ -194,6 +194,8 @@ public class ShortcutsConfigListWidget extends ElementListWidget<ShortcutsConfig
this.category = category;
target = new TextFieldWidget(MinecraftClient.getInstance().textRenderer, width / 2 - 160, 5, 150, 20, category.targetName);
replacement = new TextFieldWidget(MinecraftClient.getInstance().textRenderer, width / 2 + 10, 5, 150, 20, category.replacementName);
+ target.setMaxLength(48);
+ replacement.setMaxLength(48);
target.setText(targetString);
replacement.setText(category.shortcutsMap.getOrDefault(targetString, ""));
children = List.of(target, replacement);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/FireSaleWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/FireSaleWidget.java
index e08b4acf..b77941c2 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/FireSaleWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/FireSaleWidget.java
@@ -6,6 +6,7 @@ import java.util.regex.Pattern;
import de.hysky.skyblocker.skyblock.tabhud.util.Colors;
import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent;
import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent;
import de.hysky.skyblocker.skyblock.tabhud.widget.component.ProgressComponent;
import net.minecraft.text.MutableText;
@@ -17,7 +18,7 @@ import net.minecraft.util.Formatting;
public class FireSaleWidget extends Widget {
- private static final MutableText TITLE = Text.literal("Fire Sale").formatted(Formatting.DARK_AQUA,
+ private static final MutableText TITLE = Text.literal("Fire Sales").formatted(Formatting.DARK_AQUA,
Formatting.BOLD);
// matches a fire sale item
@@ -32,20 +33,20 @@ public class FireSaleWidget extends Widget {
@Override
public void updateContent() {
- String event = PlayerListMgr.strAt(46);
+ Text event = PlayerListMgr.textAt(46);
if (event == null) {
- this.addComponent(new PlainTextComponent(Text.literal("No Fire Sale!").formatted(Formatting.GRAY)));
+ this.addComponent(new PlainTextComponent(Text.literal("No Fire Sales!").formatted(Formatting.GRAY)));
return;
}
- if (event.contains("Starts In")) {
- this.addSimpleIcoText(Ico.CLOCK, "Starts in:", Formatting.DARK_AQUA, 46);
+ if (event.getString().contains("starting in")) {
+ this.addComponent(new IcoTextComponent(Ico.CLOCK, event));
return;
}
for (int i = 46;; i++) {
- Matcher m = PlayerListMgr.regexAt( i, FIRE_PATTERN);
+ Matcher m = PlayerListMgr.regexAt(i, FIRE_PATTERN);
if (m == null) {
break;
}
@@ -57,7 +58,5 @@ public class FireSaleWidget extends Widget {
ProgressComponent pc = new ProgressComponent(Ico.GOLD, itemTxt, prgressTxt, pcnt, Colors.pcntToCol(pcnt));
this.addComponent(pc);
}
-
}
-
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/MythologicalRitual.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/MythologicalRitual.java
index ad58c868..6629c377 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/MythologicalRitual.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/MythologicalRitual.java
@@ -9,6 +9,7 @@ import de.hysky.skyblocker.utils.render.RenderHelper;
import de.hysky.skyblocker.utils.waypoint.Waypoint;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
+import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
@@ -58,9 +59,10 @@ public class MythologicalRitual {
UseBlockCallback.EVENT.register(MythologicalRitual::onUseBlock);
UseItemCallback.EVENT.register(MythologicalRitual::onUseItem);
ClientReceiveMessageEvents.GAME.register(MythologicalRitual::onChatMessage);
+ ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> reset());
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("diana")
.then(literal("clearGriffinBurrows").executes(context -> {
- griffinBurrows.clear();
+ reset();
return Command.SINGLE_SUCCESS;
}))
.then(literal("clearGriffinBurrow")
@@ -144,10 +146,10 @@ public class MythologicalRitual {
}
if (burrow.confirmed != TriState.FALSE) {
if (burrow.nextBurrowLine != null) {
- RenderHelper.renderLinesFromPoints(context, burrow.nextBurrowLine, ORANGE_COLOR_COMPONENTS, 0.5F, 5F);
+ RenderHelper.renderLinesFromPoints(context, burrow.nextBurrowLine, ORANGE_COLOR_COMPONENTS, 0.5F, 5F, false);
}
if (burrow.echoBurrowLine != null) {
- RenderHelper.renderLinesFromPoints(context, burrow.echoBurrowLine, ORANGE_COLOR_COMPONENTS, 0.5F, 5F);
+ RenderHelper.renderLinesFromPoints(context, burrow.echoBurrowLine, ORANGE_COLOR_COMPONENTS, 0.5F, 5F, false);
}
}
}
@@ -189,6 +191,16 @@ public class MythologicalRitual {
private static boolean isActive() {
return SkyblockerConfigManager.get().general.mythologicalRitual.enableMythologicalRitualHelper && Utils.getLocationRaw().equals("hub");
}
+
+ private static void reset() {
+ griffinBurrows.clear();
+ lastDugBurrowPos = null;
+ previousBurrow = new GriffinBurrow(BlockPos.ORIGIN);
+
+ // Put a root burrow so echo detection works without a previous burrow
+ previousBurrow.confirmed = TriState.DEFAULT;
+ griffinBurrows.put(BlockPos.ORIGIN, previousBurrow);
+ }
private static class GriffinBurrow extends Waypoint {
private int critParticle;
diff --git a/src/main/java/de/hysky/skyblocker/utils/BasePlaceholderScreen.java b/src/main/java/de/hysky/skyblocker/utils/BasePlaceholderScreen.java
new file mode 100644
index 00000000..b362dcb7
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/BasePlaceholderScreen.java
@@ -0,0 +1,19 @@
+package de.hysky.skyblocker.utils;
+
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.text.Text;
+
+public abstract class BasePlaceholderScreen extends Screen {
+ public BasePlaceholderScreen(Text title) {
+ super(title);
+ }
+
+ @Override
+ public void render(DrawContext context, int mouseX, int mouseY, float delta) {
+ }
+
+ @Override
+ public void renderBackground(DrawContext context, int mouseX, int mouseY, float delta) {
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java
index ed46677d..880ebe76 100644
--- a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java
+++ b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java
@@ -1,45 +1,49 @@
package de.hysky.skyblocker.utils;
+import com.mojang.brigadier.Command;
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import de.hysky.skyblocker.mixin.accessor.ItemStackAccessor;
import it.unimi.dsi.fastutil.ints.IntIntPair;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.item.TooltipContext;
+import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
+import net.minecraft.nbt.NbtElement;
import net.minecraft.nbt.StringNbtReader;
import net.minecraft.text.Text;
+import net.minecraft.text.Texts;
import net.minecraft.util.Formatting;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
import java.util.function.Predicate;
import java.util.regex.Pattern;
+import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
+
public class ItemUtils {
+ private static final Logger LOGGER = LoggerFactory.getLogger(ItemUtils.class);
public static final String EXTRA_ATTRIBUTES = "ExtraAttributes";
public static final String ID = "id";
public static final String UUID = "uuid";
+ private static final DateTimeFormatter OBTAINED_DATE_FORMATTER = DateTimeFormatter.ofPattern("MMMM d, yyyy").withZone(ZoneId.systemDefault()).localizedBy(Locale.ENGLISH);
+ private static final SimpleDateFormat OLD_OBTAINED_DATE_FORMAT = new SimpleDateFormat("MM/dd/yy");
public static final Pattern NOT_DURABILITY = Pattern.compile("[^0-9 /]");
public static final Predicate<String> FUEL_PREDICATE = line -> line.contains("Fuel: ");
- public static List<Text> getTooltips(ItemStack item) {
- MinecraftClient client = MinecraftClient.getInstance();
- return client.player == null || item == null ? Collections.emptyList() : item.getTooltip(client.player, TooltipContext.Default.BASIC);
- }
-
- @Nullable
- public static String getTooltip(ItemStack item, Predicate<String> predicate) {
- for (Text line : getTooltips(item)) {
- String string = line.getString();
- if (predicate.test(string)) {
- return string;
- }
- }
-
- return null;
+ public static LiteralArgumentBuilder<FabricClientCommandSource> dumpHeldItemNbtCommand() {
+ return literal("dumpHeldItemNbt").executes(context -> {
+ context.getSource().sendFeedback(Text.literal("[Skyblocker Debug] Held Item Nbt: " + context.getSource().getPlayer().getMainHandStack().writeNbt(new NbtCompound())));
+ return Command.SINGLE_SUCCESS;
+ });
}
/**
@@ -105,6 +109,44 @@ public class ItemUtils {
return extraAttributes != null ? extraAttributes.getString(UUID) : "";
}
+ /**
+ * This method converts the "timestamp" variable into the same date format as Hypixel represents it in the museum.
+ * Currently, there are two types of string timestamps the legacy which is built like this
+ * "dd/MM/yy hh:mm" ("25/04/20 16:38") and the current which is built like this
+ * "MM/dd/yy hh:mm aa" ("12/24/20 11:08 PM"). Since Hypixel transforms the two formats into one format without
+ * taking into account of their formats, we do the same. The final result looks like this
+ * "MMMM dd, yyyy" (December 24, 2020).
+ * Since the legacy format has a 25 as "month" SimpleDateFormat converts the 25 into 2 years and 1 month and makes
+ * "25/04/20 16:38" -> "January 04, 2022" instead of "April 25, 2020".
+ * This causes the museum rank to be much worse than it should be.
+ * <p>
+ * This also handles the long timestamp format introduced in January 2024 where the timestamp is in epoch milliseconds.
+ *
+ * @param stack the item under the pointer
+ * @return if the item have a "Timestamp" it will be shown formated on the tooltip
+ */
+ public static String getTimestamp(ItemStack stack) {
+ NbtCompound ea = getExtraAttributes(stack);
+
+ if (ea != null && ea.contains("timestamp", NbtElement.LONG_TYPE)) {
+ Instant date = Instant.ofEpochMilli(ea.getLong("timestamp"));
+
+ return OBTAINED_DATE_FORMATTER.format(date);
+ }
+
+ if (ea != null && ea.contains("timestamp", NbtElement.STRING_TYPE)) {
+ try {
+ Instant date = OLD_OBTAINED_DATE_FORMAT.parse(ea.getString("timestamp")).toInstant();
+
+ return OBTAINED_DATE_FORMATTER.format(date);
+ } catch (ParseException e) {
+ LOGGER.warn("[Skyblocker Item Utils] Encountered an unknown exception while parsing time stamp of item {} with extra attributes {}", stack, ea, e);
+ }
+ }
+
+ return "";
+ }
+
public static boolean hasCustomDurability(@NotNull ItemStack stack) {
NbtCompound extraAttributes = getExtraAttributes(stack);
return extraAttributes != null && (extraAttributes.contains("drill_fuel") || extraAttributes.getString(ID).equals("PICKONIMBUS"));
@@ -123,7 +165,7 @@ public class ItemUtils {
return IntIntPair.of(pickonimbusDurability, 5000);
}
- String drillFuel = Formatting.strip(getTooltip(stack, FUEL_PREDICATE));
+ String drillFuel = Formatting.strip(getNbtTooltip(stack, FUEL_PREDICATE));
if (drillFuel != null) {
String[] drillFuelStrings = NOT_DURABILITY.matcher(drillFuel).replaceAll("").trim().split("/");
return IntIntPair.of(Integer.parseInt(drillFuelStrings[0]), Integer.parseInt(drillFuelStrings[1]) * 1000);
@@ -132,6 +174,27 @@ public class ItemUtils {
return null;
}
+ @Nullable
+ public static String getNbtTooltip(ItemStack item, Predicate<String> predicate) {
+ for (Text line : getNbtTooltips(item)) {
+ String string = line.getString();
+ if (predicate.test(string)) {
+ return string;
+ }
+ }
+
+ return null;
+ }
+
+ public static List<Text> getNbtTooltips(ItemStack item) {
+ NbtCompound displayNbt = item.getSubNbt("display");
+ if (displayNbt == null || !displayNbt.contains("Lore", NbtElement.LIST_TYPE)) {
+ return Collections.emptyList();
+ }
+
+ return displayNbt.getList("Lore", NbtElement.STRING_TYPE).stream().map(NbtElement::asString).map(Text.Serialization::fromJson).filter(Objects::nonNull).map(text -> Texts.setStyleIfAbsent(text, ItemStackAccessor.getLORE_STYLE())).map(Text.class::cast).toList();
+ }
+
public static ItemStack getSkyblockerStack() {
try {
return ItemStack.fromNbt(StringNbtReader.parse("{id:\"minecraft:player_head\",Count:1,tag:{SkullOwner:{Id:[I;-300151517,-631415889,-1193921967,-1821784279],Properties:{textures:[{Value:\"e3RleHR1cmVzOntTS0lOOnt1cmw6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDdjYzY2ODc0MjNkMDU3MGQ1NTZhYzUzZTA2NzZjYjU2M2JiZGQ5NzE3Y2Q4MjY5YmRlYmVkNmY2ZDRlN2JmOCJ9fX0=\"}]}}}}"));
diff --git a/src/main/java/de/hysky/skyblocker/utils/JoinWorldPlaceholderScreen.java b/src/main/java/de/hysky/skyblocker/utils/JoinWorldPlaceholderScreen.java
new file mode 100644
index 00000000..f64bcf6c
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/JoinWorldPlaceholderScreen.java
@@ -0,0 +1,9 @@
+package de.hysky.skyblocker.utils;
+
+import net.minecraft.text.Text;
+
+public final class JoinWorldPlaceholderScreen extends BasePlaceholderScreen {
+ public JoinWorldPlaceholderScreen() {
+ super(Text.translatable("connect.joining"));
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/ReconfiguringPlaceholderScreen.java b/src/main/java/de/hysky/skyblocker/utils/ReconfiguringPlaceholderScreen.java
new file mode 100644
index 00000000..4d415615
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/ReconfiguringPlaceholderScreen.java
@@ -0,0 +1,22 @@
+package de.hysky.skyblocker.utils;
+
+import net.minecraft.network.ClientConnection;
+import net.minecraft.text.Text;
+
+public final class ReconfiguringPlaceholderScreen extends BasePlaceholderScreen {
+ private final ClientConnection connection;
+
+ public ReconfiguringPlaceholderScreen(final ClientConnection connection) {
+ super(Text.translatable("connect.reconfiguring"));
+ this.connection = connection;
+ }
+
+ @Override
+ public void tick() {
+ if (this.connection.isOpen()) {
+ this.connection.tick();
+ } else {
+ this.connection.handleDisconnection();
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/Tickable.java b/src/main/java/de/hysky/skyblocker/utils/Tickable.java
new file mode 100644
index 00000000..dff34e19
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/Tickable.java
@@ -0,0 +1,7 @@
+package de.hysky.skyblocker.utils;
+
+import net.minecraft.client.MinecraftClient;
+
+public interface Tickable {
+ void tick(MinecraftClient client);
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/chat/ChatMessageListener.java b/src/main/java/de/hysky/skyblocker/utils/chat/ChatMessageListener.java
index 2c75ef0a..ebdb6f09 100644
--- a/src/main/java/de/hysky/skyblocker/utils/chat/ChatMessageListener.java
+++ b/src/main/java/de/hysky/skyblocker/utils/chat/ChatMessageListener.java
@@ -5,8 +5,8 @@ import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.skyblock.barn.HungryHiker;
import de.hysky.skyblocker.skyblock.barn.TreasureHunter;
import de.hysky.skyblocker.skyblock.dungeon.Reparty;
-import de.hysky.skyblocker.skyblock.dungeon.ThreeWeirdos;
-import de.hysky.skyblocker.skyblock.dungeon.Trivia;
+import de.hysky.skyblocker.skyblock.dungeon.puzzle.ThreeWeirdos;
+import de.hysky.skyblocker.skyblock.dungeon.puzzle.Trivia;
import de.hysky.skyblocker.skyblock.dwarven.Fetchur;
import de.hysky.skyblocker.skyblock.dwarven.Puzzler;
import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
@@ -53,7 +53,8 @@ public interface ChatMessageListener {
new MoltenWaveFilter(),
new TeleportPadFilter(),
new AutopetFilter(),
- new ShowOffFilter()
+ new ShowOffFilter(),
+ new ToggleSkyMallFilter()
};
// Register all listeners to EVENT
for (ChatMessageListener listener : listeners) {
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java
index 0f73df16..05514d02 100644
--- a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java
+++ b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java
@@ -60,7 +60,7 @@ public class RenderHelper {
renderFilled(context, Vec3d.of(pos), dimensions, colorComponents, alpha, true);
}
} else {
- if (OcclusionCulling.isVisible(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + dimensions.x, pos.getY() + dimensions.y, pos.getZ() + dimensions.z)) {
+ if (OcclusionCulling.getRegularCuller().isVisible(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + dimensions.x, pos.getY() + dimensions.y, pos.getZ() + dimensions.z)) {
renderFilled(context, Vec3d.of(pos), dimensions, colorComponents, alpha, false);
}
}
@@ -140,8 +140,9 @@ public class RenderHelper {
* @param colorComponents An array of R, G and B color components
* @param alpha The alpha of the lines
* @param lineWidth The width of the lines
+ * @param throughWalls Whether to render through walls or not
*/
- public static void renderLinesFromPoints(WorldRenderContext context, Vec3d[] points, float[] colorComponents, float alpha, float lineWidth) {
+ public static void renderLinesFromPoints(WorldRenderContext context, Vec3d[] points, float[] colorComponents, float alpha, float lineWidth, boolean throughWalls) {
Vec3d camera = context.camera().getPos();
MatrixStack matrices = context.matrixStack();
@@ -163,6 +164,7 @@ public class RenderHelper {
RenderSystem.defaultBlendFunc();
RenderSystem.disableCull();
RenderSystem.enableDepthTest();
+ RenderSystem.depthFunc(throughWalls ? GL11.GL_ALWAYS : GL11.GL_LEQUAL);
buffer.begin(DrawMode.LINE_STRIP, VertexFormats.LINES);
@@ -182,6 +184,7 @@ public class RenderHelper {
GL11.glDisable(GL11.GL_LINE_SMOOTH);
RenderSystem.lineWidth(1f);
RenderSystem.enableCull();
+ RenderSystem.depthFunc(GL11.GL_LEQUAL);
}
public static void renderQuad(WorldRenderContext context, Vec3d[] points, float[] colorComponents, float alpha, boolean throughWalls) {
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/Renderable.java b/src/main/java/de/hysky/skyblocker/utils/render/Renderable.java
new file mode 100644
index 00000000..b7743153
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/render/Renderable.java
@@ -0,0 +1,7 @@
+package de.hysky.skyblocker.utils.render;
+
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
+
+public interface Renderable {
+ void render(WorldRenderContext context);
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/culling/OcclusionCuller.java b/src/main/java/de/hysky/skyblocker/utils/render/culling/OcclusionCuller.java
new file mode 100644
index 00000000..3c48a47e
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/render/culling/OcclusionCuller.java
@@ -0,0 +1,45 @@
+package de.hysky.skyblocker.utils.render.culling;
+
+import com.logisticscraft.occlusionculling.OcclusionCullingInstance;
+import com.logisticscraft.occlusionculling.cache.ArrayOcclusionCache;
+import com.logisticscraft.occlusionculling.util.Vec3d;
+
+import de.hysky.skyblocker.utils.render.FrustumUtils;
+import net.minecraft.client.MinecraftClient;
+
+public class OcclusionCuller {
+ private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
+
+ private final OcclusionCullingInstance instance;
+
+ // Reused objects to reduce allocation overhead
+ private final Vec3d cameraPos = new Vec3d(0, 0, 0);
+ private final Vec3d min = new Vec3d(0, 0, 0);
+ private final Vec3d max = new Vec3d(0, 0, 0);
+
+ OcclusionCuller(int tracingDistance, WorldProvider worldProvider, double aabbExpansion) {
+ this.instance = new OcclusionCullingInstance(tracingDistance, worldProvider, new ArrayOcclusionCache(tracingDistance), aabbExpansion);
+ }
+
+ private void updateCameraPos() {
+ var camera = CLIENT.gameRenderer.getCamera().getPos();
+ cameraPos.set(camera.x, camera.y, camera.z);
+ }
+
+ /**
+ * This first checks checks if the bounding box is within the camera's FOV, if
+ * it is then it checks for whether it's occluded or not.
+ *
+ * @return A boolean representing whether the bounding box is fully visible or
+ * not as per the instance's settings.
+ */
+ public boolean isVisible(double x1, double y1, double z1, double x2, double y2, double z2) {
+ if (!FrustumUtils.isVisible(x1, y1, z1, x2, y2, z2)) return false;
+
+ updateCameraPos();
+ min.set(x1, y1, z1);
+ max.set(x2, y2, z2);
+
+ return instance.isAABBVisible(min, max, cameraPos);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/culling/OcclusionCulling.java b/src/main/java/de/hysky/skyblocker/utils/render/culling/OcclusionCulling.java
index 5f8d1592..b1ff10dd 100644
--- a/src/main/java/de/hysky/skyblocker/utils/render/culling/OcclusionCulling.java
+++ b/src/main/java/de/hysky/skyblocker/utils/render/culling/OcclusionCulling.java
@@ -1,47 +1,23 @@
package de.hysky.skyblocker.utils.render.culling;
-import com.logisticscraft.occlusionculling.OcclusionCullingInstance;
-import com.logisticscraft.occlusionculling.cache.ArrayOcclusionCache;
-import com.logisticscraft.occlusionculling.util.Vec3d;
-import de.hysky.skyblocker.utils.render.FrustumUtils;
-import net.minecraft.client.MinecraftClient;
-
public class OcclusionCulling {
private static final int TRACING_DISTANCE = 128;
- private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
- private static OcclusionCullingInstance instance = null;
-
- // Reused objects to reduce allocation overhead
- private static final Vec3d cameraPos = new Vec3d(0, 0, 0);
- private static final Vec3d min = new Vec3d(0, 0, 0);
- private static final Vec3d max = new Vec3d(0, 0, 0);
+ private static OcclusionCuller regularCuller = null;
+ private static OcclusionCuller reducedCuller = null;
/**
- * Initializes the occlusion culling instance
+ * Initializes the occlusion culling instances
*/
public static void init() {
- instance = new OcclusionCullingInstance(TRACING_DISTANCE, new WorldProvider(), new ArrayOcclusionCache(TRACING_DISTANCE), 2);
+ regularCuller = new OcclusionCuller(TRACING_DISTANCE, new WorldProvider(), 2);
+ reducedCuller = new OcclusionCuller(TRACING_DISTANCE, new ReducedWorldProvider(), 0);
}
- private static void updateCameraPos() {
- var camera = CLIENT.gameRenderer.getCamera().getPos();
- cameraPos.set(camera.x, camera.y, camera.z);
+ public static OcclusionCuller getRegularCuller() {
+ return regularCuller;
}
- /**
- * This first checks checks if the bounding box is within the camera's FOV, if
- * it is then it checks for whether it's occluded or not.
- *
- * @return A boolean representing whether the bounding box is fully visible or
- * not.
- */
- public static boolean isVisible(double x1, double y1, double z1, double x2, double y2, double z2) {
- if (!FrustumUtils.isVisible(x1, y1, z1, x2, y2, z2)) return false;
-
- updateCameraPos();
- min.set(x1, y1, z1);
- max.set(x2, y2, z2);
-
- return instance.isAABBVisible(min, max, cameraPos);
+ public static OcclusionCuller getReducedCuller() {
+ return reducedCuller;
}
}
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/culling/ReducedWorldProvider.java b/src/main/java/de/hysky/skyblocker/utils/render/culling/ReducedWorldProvider.java
new file mode 100644
index 00000000..5a2b9d87
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/render/culling/ReducedWorldProvider.java
@@ -0,0 +1,19 @@
+package de.hysky.skyblocker.utils.render.culling;
+
+import net.minecraft.block.BlockState;
+import net.minecraft.registry.tag.BlockTags;
+import net.minecraft.util.math.BlockPos;
+
+public class ReducedWorldProvider extends WorldProvider {
+
+ @Override
+ public boolean isOpaqueFullCube(int x, int y, int z) {
+ BlockPos pos = new BlockPos(x, y, z);
+ BlockState state = this.world.getBlockState(pos);
+
+ //Fixes edge cases where stairs etc aren't treated as being full blocks for the use case
+ boolean isException = state.isIn(BlockTags.STAIRS) || state.isIn(BlockTags.WALLS) || state.isIn(BlockTags.FENCES);
+
+ return isException || this.world.getBlockState(pos).isOpaqueFullCube(this.world, pos);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/culling/WorldProvider.java b/src/main/java/de/hysky/skyblocker/utils/render/culling/WorldProvider.java
index 7ee0f0ed..1d2db238 100644
--- a/src/main/java/de/hysky/skyblocker/utils/render/culling/WorldProvider.java
+++ b/src/main/java/de/hysky/skyblocker/utils/render/culling/WorldProvider.java
@@ -7,7 +7,7 @@ import net.minecraft.util.math.BlockPos;
public class WorldProvider implements DataProvider {
private final static MinecraftClient CLIENT = MinecraftClient.getInstance();
- private ClientWorld world = null;
+ protected ClientWorld world = null;
@Override
public boolean prepareChunk(int chunkX, int chunkZ) {
diff --git a/src/main/java/de/hysky/skyblocker/utils/scheduler/MessageScheduler.java b/src/main/java/de/hysky/skyblocker/utils/scheduler/MessageScheduler.java
index a67d8da0..43194938 100644
--- a/src/main/java/de/hysky/skyblocker/utils/scheduler/MessageScheduler.java
+++ b/src/main/java/de/hysky/skyblocker/utils/scheduler/MessageScheduler.java
@@ -1,6 +1,8 @@
package de.hysky.skyblocker.utils.scheduler;
import net.minecraft.client.MinecraftClient;
+import net.minecraft.util.StringHelper;
+import org.apache.commons.lang3.StringUtils;
/**
* A scheduler for sending chat messages or commands. Use the instance in {@link #INSTANCE}. Do not instantiate this class.
@@ -34,13 +36,17 @@ public class MessageScheduler extends Scheduler {
}
private void sendMessage(String message) {
- if (MinecraftClient.getInstance().player != null) {
- if (message.startsWith("/")) {
- MinecraftClient.getInstance().player.networkHandler.sendCommand(message.substring(1));
- } else {
- MinecraftClient.getInstance().inGameHud.getChatHud().addToMessageHistory(message);
- MinecraftClient.getInstance().player.networkHandler.sendChatMessage(message);
- }
+ MinecraftClient client = MinecraftClient.getInstance();
+ if (client.player == null) {
+ Scheduler.LOGGER.error("[Skyblocker Message Scheduler] Tried to send a message while player is null: {}", message);
+ return;
+ }
+ message = StringHelper.truncateChat(StringUtils.normalizeSpace(message.trim()));
+ if (message.startsWith("/")) {
+ client.player.networkHandler.sendCommand(message.substring(1));
+ } else {
+ client.inGameHud.getChatHud().addToMessageHistory(message);
+ client.player.networkHandler.sendChatMessage(message);
}
}
diff --git a/src/main/java/de/hysky/skyblocker/utils/scheduler/Scheduler.java b/src/main/java/de/hysky/skyblocker/utils/scheduler/Scheduler.java
index 139ac05e..2f5375fe 100644
--- a/src/main/java/de/hysky/skyblocker/utils/scheduler/Scheduler.java
+++ b/src/main/java/de/hysky/skyblocker/utils/scheduler/Scheduler.java
@@ -20,7 +20,7 @@ import java.util.function.Supplier;
* A scheduler for running tasks at a later time. Tasks will be run synchronously on the main client thread. Use the instance stored in {@link #INSTANCE}. Do not instantiate this class.
*/
public class Scheduler {
- private static final Logger LOGGER = LoggerFactory.getLogger(Scheduler.class);
+ protected static final Logger LOGGER = LoggerFactory.getLogger(Scheduler.class);
public static final Scheduler INSTANCE = new Scheduler();
private int currentTick = 0;
private final AbstractInt2ObjectMap<List<ScheduledTask>> tasks = new Int2ObjectOpenHashMap<>();
diff --git a/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java b/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java
index eb30cf8d..2f9c9f63 100644
--- a/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java
+++ b/src/main/java/de/hysky/skyblocker/utils/waypoint/Waypoint.java
@@ -19,15 +19,19 @@ public class Waypoint {
final boolean throughWalls;
private boolean shouldRender;
- protected Waypoint(BlockPos pos, Supplier<Type> typeSupplier, float[] colorComponents) {
+ public Waypoint(BlockPos pos, Type type, float[] colorComponents) {
+ this(pos, type, colorComponents, DEFAULT_HIGHLIGHT_ALPHA);
+ }
+
+ public Waypoint(BlockPos pos, Supplier<Type> typeSupplier, float[] colorComponents) {
this(pos, typeSupplier, colorComponents, DEFAULT_HIGHLIGHT_ALPHA, DEFAULT_LINE_WIDTH);
}
- protected Waypoint(BlockPos pos, Type type, float[] colorComponents, float alpha) {
+ public Waypoint(BlockPos pos, Type type, float[] colorComponents, float alpha) {
this(pos, () -> type, colorComponents, alpha, DEFAULT_LINE_WIDTH);
}
- protected Waypoint(BlockPos pos, Supplier<Type> typeSupplier, float[] colorComponents, float alpha, float lineWidth) {
+ public Waypoint(BlockPos pos, Supplier<Type> typeSupplier, float[] colorComponents, float alpha, float lineWidth) {
this(pos, typeSupplier, colorComponents, alpha, lineWidth, true);
}
@@ -35,11 +39,11 @@ public class Waypoint {
this(pos, typeSupplier, colorComponents, DEFAULT_HIGHLIGHT_ALPHA, DEFAULT_LINE_WIDTH, throughWalls);
}
- protected Waypoint(BlockPos pos, Supplier<Type> typeSupplier, float[] colorComponents, float alpha, float lineWidth, boolean throughWalls) {
+ public Waypoint(BlockPos pos, Supplier<Type> typeSupplier, float[] colorComponents, float alpha, float lineWidth, boolean throughWalls) {
this(pos, typeSupplier, colorComponents, alpha, lineWidth, throughWalls, true);
}
- protected Waypoint(BlockPos pos, Supplier<Type> typeSupplier, float[] colorComponents, float alpha, float lineWidth, boolean throughWalls, boolean shouldRender) {
+ public Waypoint(BlockPos pos, Supplier<Type> typeSupplier, float[] colorComponents, float alpha, float lineWidth, boolean throughWalls, boolean shouldRender) {
this.pos = pos;
this.box = new Box(pos);
this.typeSupplier = typeSupplier;
@@ -62,6 +66,10 @@ public class Waypoint {
this.shouldRender = true;
}
+ public void toggle() {
+ this.shouldRender = !this.shouldRender;
+ }
+
protected float[] getColorComponents() {
return colorComponents;
}