aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de/hysky/skyblocker/skyblock
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/de/hysky/skyblocker/skyblock')
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/FairySouls.java33
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java11
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/diana/MythologicalRitual.java213
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/CreeperBeams.java8
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonBlaze.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonChestProfit.java9
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMapConfigScreen.java1
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/TicTacToe.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonMapUtils.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java238
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java127
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretWaypoint.java144
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretsTracker.java174
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHudConfigScreen.java1
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/BackpackPreview.java235
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorDyeColors.java15
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorTrims.java23
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/CustomItemNames.java11
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/ItemProtection.java9
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java144
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/WikiLookup.java30
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/BackpackPreview.java204
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorDeletorPreview.java (renamed from src/main/java/de/hysky/skyblocker/skyblock/item/CompactorDeletorPreview.java)6
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorPreviewTooltipComponent.java (renamed from src/main/java/de/hysky/skyblocker/skyblock/item/CompactorPreviewTooltipComponent.java)2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ExoticTooltip.java96
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java (renamed from src/main/java/de/hysky/skyblocker/skyblock/item/PriceInfoTooltip.java)285
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java145
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListWidget.java10
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemRegistry.java129
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemRepository.java137
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java90
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/itemlist/SearchResultsWidget.java28
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/itemlist/SkyblockCraftingRecipe.java51
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/rift/EnigmaSouls.java183
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/rift/MirrorverseWaypoints.java62
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/rift/TheRift.java9
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/spidersden/Relics.java5
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonPlayerWidget.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenSkillsWidget.java23
39 files changed, 2067 insertions, 835 deletions
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/FairySouls.java b/src/main/java/de/hysky/skyblocker/skyblock/FairySouls.java
index 24465e06..cef17d8e 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/FairySouls.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/FairySouls.java
@@ -1,6 +1,5 @@
package de.hysky.skyblocker.skyblock;
-import com.google.common.collect.ImmutableSet;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@@ -9,7 +8,8 @@ import com.mojang.brigadier.CommandDispatcher;
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.config.SkyblockerConfig;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
-import de.hysky.skyblocker.utils.NEURepo;
+import de.hysky.skyblocker.utils.Constants;
+import de.hysky.skyblocker.utils.NEURepoManager;
import de.hysky.skyblocker.utils.PosUtils;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.render.RenderHelper;
@@ -32,6 +32,7 @@ import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.*;
import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
@@ -64,25 +65,10 @@ public class FairySouls {
}
private static void loadFairySouls() {
- fairySoulsLoaded = NEURepo.runAsyncAfterLoad(() -> {
- try (BufferedReader reader = new BufferedReader(new FileReader(NEURepo.LOCAL_REPO_DIR.resolve("constants").resolve("fairy_souls.json").toFile()))) {
- for (Map.Entry<String, JsonElement> fairySoulJson : JsonParser.parseReader(reader).getAsJsonObject().asMap().entrySet()) {
- if (fairySoulJson.getKey().equals("//") || fairySoulJson.getKey().equals("Max Souls")) {
- if (fairySoulJson.getKey().equals("Max Souls")) {
- maxSouls = fairySoulJson.getValue().getAsInt();
- }
- continue;
- }
- ImmutableSet.Builder<BlockPos> fairySoulsForLocation = ImmutableSet.builder();
- for (JsonElement fairySoul : fairySoulJson.getValue().getAsJsonArray().asList()) {
- fairySoulsForLocation.add(PosUtils.parsePosString(fairySoul.getAsString()));
- }
- fairySouls.put(fairySoulJson.getKey(), fairySoulsForLocation.build());
- }
- LOGGER.debug("[Skyblocker] Loaded fairy soul locations");
- } catch (IOException e) {
- LOGGER.error("[Skyblocker] Failed to load fairy soul locations", e);
- }
+ fairySoulsLoaded = NEURepoManager.runAsyncAfterLoad(() -> {
+ maxSouls = NEURepoManager.NEU_REPO.getConstants().getFairySouls().getMaxSouls();
+ NEURepoManager.NEU_REPO.getConstants().getFairySouls().getSoulLocations().forEach((location, fairySoulsForLocation) -> fairySouls.put(location, fairySoulsForLocation.stream().map(coordinate -> new BlockPos(coordinate.getX(), coordinate.getY(), coordinate.getZ())).collect(Collectors.toUnmodifiableSet())));
+ LOGGER.debug("[Skyblocker] Loaded {} fairy souls across {} locations", fairySouls.values().stream().mapToInt(Set::size).sum(), fairySouls.size());
try (BufferedReader reader = new BufferedReader(new FileReader(SkyblockerMod.CONFIG_DIR.resolve("found_fairy_souls.json").toFile()))) {
for (Map.Entry<String, JsonElement> foundFairiesForProfileJson : JsonParser.parseReader(reader).getAsJsonObject().asMap().entrySet()) {
@@ -101,6 +87,7 @@ public class FairySouls {
} catch (IOException e) {
LOGGER.error("[Skyblocker] Failed to load found fairy souls", e);
}
+ LOGGER.info("[Skyblocker] Loaded {} fairy souls across {} locations and {} found fairy souls across {} locations in {} profiles", fairySouls.values().stream().mapToInt(Set::size).sum(), fairySouls.size(), foundFairies.values().stream().map(Map::values).flatMap(Collection::stream).mapToInt(Set::size).sum(), foundFairies.values().stream().mapToInt(Map::size).sum(), foundFairies.size());
});
}
@@ -131,12 +118,12 @@ public class FairySouls {
.then(literal("fairySouls")
.then(literal("markAllInCurrentIslandFound").executes(context -> {
FairySouls.markAllFairiesOnCurrentIslandFound();
- context.getSource().sendFeedback(Text.translatable("skyblocker.fairySouls.markAllFound"));
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.fairySouls.markAllFound")));
return 1;
}))
.then(literal("markAllInCurrentIslandMissing").executes(context -> {
FairySouls.markAllFairiesOnCurrentIslandMissing();
- context.getSource().sendFeedback(Text.translatable("skyblocker.fairySouls.markAllMissing"));
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.fairySouls.markAllMissing")));
return 1;
}))));
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java b/src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java
index 5ea5513e..c290e5b8 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/TeleportOverlay.java
@@ -1,8 +1,7 @@
package de.hysky.skyblocker.skyblock;
-import com.mojang.blaze3d.systems.RenderSystem;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
-import de.hysky.skyblocker.skyblock.item.PriceInfoTooltip;
+import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip;
import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.render.RenderHelper;
@@ -27,7 +26,7 @@ public class TeleportOverlay {
private static void render(WorldRenderContext wrc) {
if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().general.teleportOverlay.enableTeleportOverlays && client.player != null && client.world != null) {
ItemStack heldItem = client.player.getMainHandStack();
- String itemId = PriceInfoTooltip.getInternalNameFromNBT(heldItem, true);
+ String itemId = ItemTooltip.getInternalNameFromNBT(heldItem, true);
NbtCompound extraAttributes = ItemUtils.getExtraAttributes(heldItem);
if (itemId != null) {
@@ -103,13 +102,7 @@ public class TeleportOverlay {
@SuppressWarnings("DataFlowIssue")
BlockState state = client.world.getBlockState(pos);
if (!state.isAir() && client.world.getBlockState(pos.up()).isAir() && client.world.getBlockState(pos.up(2)).isAir()) {
- RenderSystem.polygonOffset(-1f, -10f);
- RenderSystem.enablePolygonOffset();
-
RenderHelper.renderFilledIfVisible(wrc, pos, COLOR_COMPONENTS, 0.5f);
-
- RenderSystem.polygonOffset(0f, 0f);
- RenderSystem.disablePolygonOffset();
}
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/diana/MythologicalRitual.java b/src/main/java/de/hysky/skyblocker/skyblock/diana/MythologicalRitual.java
new file mode 100644
index 00000000..e2962702
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/diana/MythologicalRitual.java
@@ -0,0 +1,213 @@
+package de.hysky.skyblocker.skyblock.diana;
+
+import com.mojang.brigadier.Command;
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.ItemUtils;
+import de.hysky.skyblocker.utils.Utils;
+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.rendering.v1.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
+import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
+import net.fabricmc.fabric.api.event.player.UseBlockCallback;
+import net.fabricmc.fabric.api.event.player.UseItemCallback;
+import net.fabricmc.fabric.api.util.TriState;
+import net.minecraft.block.Blocks;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.command.argument.BlockPosArgumentType;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.network.packet.s2c.play.ParticleS2CPacket;
+import net.minecraft.particle.ParticleTypes;
+import net.minecraft.text.Text;
+import net.minecraft.util.ActionResult;
+import net.minecraft.util.DyeColor;
+import net.minecraft.util.Hand;
+import net.minecraft.util.TypedActionResult;
+import net.minecraft.util.hit.BlockHitResult;
+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.apache.commons.math3.stat.regression.SimpleRegression;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument;
+import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
+
+public class MythologicalRitual {
+ private static final Pattern GRIFFIN_BURROW_DUG = Pattern.compile("(?<message>You dug out a Griffin Burrow!|You finished the Griffin burrow chain!) \\((?<index>\\d)/4\\)");
+ private static final float[] ORANGE_COLOR_COMPONENTS = DyeColor.ORANGE.getColorComponents();
+ private static long lastEchoTime;
+ private static final Map<BlockPos, GriffinBurrow> griffinBurrows = new HashMap<>();
+ @Nullable
+ private static BlockPos lastDugBurrowPos;
+ private static GriffinBurrow previousBurrow = new GriffinBurrow(BlockPos.ORIGIN);
+
+ public static void init() {
+ WorldRenderEvents.AFTER_TRANSLUCENT.register(MythologicalRitual::render);
+ AttackBlockCallback.EVENT.register(MythologicalRitual::onAttackBlock);
+ UseBlockCallback.EVENT.register(MythologicalRitual::onUseBlock);
+ UseItemCallback.EVENT.register(MythologicalRitual::onUseItem);
+ ClientReceiveMessageEvents.GAME.register(MythologicalRitual::onChatMessage);
+ ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("diana")
+ .then(literal("clearGriffinBurrows").executes(context -> {
+ griffinBurrows.clear();
+ return Command.SINGLE_SUCCESS;
+ }))
+ .then(literal("clearGriffinBurrow")
+ .then(argument("pos", BlockPosArgumentType.blockPos()).executes(context -> {
+ griffinBurrows.remove(context.getArgument("pos", BlockPos.class));
+ return Command.SINGLE_SUCCESS;
+ }))
+ )
+ )));
+
+ // Put a root burrow so echo detection works without a previous burrow
+ previousBurrow.confirmed = TriState.DEFAULT;
+ griffinBurrows.put(BlockPos.ORIGIN, previousBurrow);
+ }
+
+ public static void onParticle(ParticleS2CPacket packet) {
+ if (isActive()) {
+ if (ParticleTypes.CRIT.equals(packet.getParameters().getType()) || ParticleTypes.ENCHANT.equals(packet.getParameters().getType())) {
+ BlockPos pos = BlockPos.ofFloored(packet.getX(), packet.getY(), packet.getZ()).down();
+ if (MinecraftClient.getInstance().world == null || !MinecraftClient.getInstance().world.getBlockState(pos).isOf(Blocks.GRASS_BLOCK)) {
+ return;
+ }
+ GriffinBurrow burrow = griffinBurrows.computeIfAbsent(pos, GriffinBurrow::new);
+ if (ParticleTypes.CRIT.equals(packet.getParameters().getType())) burrow.critParticle++;
+ if (ParticleTypes.ENCHANT.equals(packet.getParameters().getType())) burrow.enchantParticle++;
+ if (burrow.critParticle >= 5 && burrow.enchantParticle >= 5 && burrow.confirmed == TriState.FALSE) {
+ griffinBurrows.get(pos).init();
+ }
+ } else if (ParticleTypes.DUST.equals(packet.getParameters().getType())) {
+ BlockPos pos = BlockPos.ofFloored(packet.getX(), packet.getY(), packet.getZ()).down(2);
+ GriffinBurrow burrow = griffinBurrows.get(pos);
+ if (burrow == null) {
+ return;
+ }
+ burrow.regression.addData(packet.getX(), packet.getZ());
+ double slope = burrow.regression.getSlope();
+ if (Double.isNaN(slope)) {
+ return;
+ }
+ Vec3d nextBurrowDirection = new Vec3d(100, 0, slope * 100).normalize().multiply(100);
+ if (burrow.nextBurrowPlane == null) {
+ burrow.nextBurrowPlane = new Vec3d[4];
+ }
+ burrow.nextBurrowPlane[0] = Vec3d.of(pos).add(nextBurrowDirection).subtract(0, 50, 0);
+ burrow.nextBurrowPlane[1] = Vec3d.of(pos).subtract(nextBurrowDirection).subtract(0, 50, 0);
+ burrow.nextBurrowPlane[2] = burrow.nextBurrowPlane[1].add(0, 100, 0);
+ burrow.nextBurrowPlane[3] = burrow.nextBurrowPlane[0].add(0, 100, 0);
+ } else if (ParticleTypes.DRIPPING_LAVA.equals(packet.getParameters().getType())) {
+ if (System.currentTimeMillis() > lastEchoTime + 10_000) {
+ return;
+ }
+ if (previousBurrow.echoBurrowDirection == null) {
+ previousBurrow.echoBurrowDirection = new Vec3d[2];
+ }
+ previousBurrow.echoBurrowDirection[0] = previousBurrow.echoBurrowDirection[1];
+ previousBurrow.echoBurrowDirection[1] = new Vec3d(packet.getX(), packet.getY(), packet.getZ());
+ if (previousBurrow.echoBurrowDirection[0] == null || previousBurrow.echoBurrowDirection[1] == null) {
+ return;
+ }
+ Vec3d echoBurrowDirection = previousBurrow.echoBurrowDirection[1].subtract(previousBurrow.echoBurrowDirection[0]).normalize().multiply(100);
+ if (previousBurrow.echoBurrowPlane == null) {
+ previousBurrow.echoBurrowPlane = new Vec3d[4];
+ }
+ previousBurrow.echoBurrowPlane[0] = previousBurrow.echoBurrowDirection[0].add(echoBurrowDirection).subtract(0, 50, 0);
+ previousBurrow.echoBurrowPlane[1] = previousBurrow.echoBurrowDirection[0].subtract(echoBurrowDirection).subtract(0, 50, 0);
+ previousBurrow.echoBurrowPlane[2] = previousBurrow.echoBurrowPlane[1].add(0, 100, 0);
+ previousBurrow.echoBurrowPlane[3] = previousBurrow.echoBurrowPlane[0].add(0, 100, 0);
+ }
+ }
+ }
+
+ public static void render(WorldRenderContext context) {
+ if (isActive()) {
+ for (GriffinBurrow burrow : griffinBurrows.values()) {
+ if (burrow.shouldRender()) {
+ burrow.render(context);
+ }
+ if (burrow.confirmed != TriState.FALSE) {
+ if (burrow.nextBurrowPlane != null) {
+ RenderHelper.renderQuad(context, burrow.nextBurrowPlane, ORANGE_COLOR_COMPONENTS, 0.25F, true);
+ }
+ if (burrow.echoBurrowPlane != null) {
+ RenderHelper.renderQuad(context, burrow.echoBurrowPlane, ORANGE_COLOR_COMPONENTS, 0.25F, true);
+ }
+ }
+ }
+ }
+ }
+
+ public static ActionResult onAttackBlock(PlayerEntity player, World world, Hand hand, BlockPos pos, Direction direction) {
+ return onInteractBlock(pos);
+ }
+
+ public static ActionResult onUseBlock(PlayerEntity player, World world, Hand hand, BlockHitResult hitResult) {
+ return onInteractBlock(hitResult.getBlockPos());
+ }
+
+ @NotNull
+ private static ActionResult onInteractBlock(BlockPos pos) {
+ if (isActive() && griffinBurrows.containsKey(pos)) {
+ lastDugBurrowPos = pos;
+ }
+ return ActionResult.PASS;
+ }
+
+ public static TypedActionResult<ItemStack> onUseItem(PlayerEntity player, World world, Hand hand) {
+ ItemStack stack = player.getStackInHand(hand);
+ if (isActive() && ItemUtils.getItemId(stack).equals("ANCESTRAL_SPADE")) {
+ lastEchoTime = System.currentTimeMillis();
+ }
+ return TypedActionResult.pass(stack);
+ }
+
+ public static void onChatMessage(Text message, boolean overlay) {
+ if (isActive() && GRIFFIN_BURROW_DUG.matcher(message.getString()).matches()) {
+ previousBurrow.confirmed = TriState.FALSE;
+ previousBurrow = griffinBurrows.get(lastDugBurrowPos);
+ previousBurrow.confirmed = TriState.DEFAULT;
+ }
+ }
+
+ private static boolean isActive() {
+ return SkyblockerConfigManager.get().general.mythologicalRitual.enableMythologicalRitualHelper && Utils.getLocationRaw().equals("hub");
+ }
+
+ private static class GriffinBurrow extends Waypoint {
+ private int critParticle;
+ private int enchantParticle;
+ private TriState confirmed = TriState.FALSE;
+ private final SimpleRegression regression = new SimpleRegression();
+ private Vec3d[] nextBurrowPlane;
+ @Nullable
+ private Vec3d[] echoBurrowDirection;
+ private Vec3d[] echoBurrowPlane;
+
+ private GriffinBurrow(BlockPos pos) {
+ super(pos, Type.WAYPOINT, ORANGE_COLOR_COMPONENTS, 0.25F);
+ }
+
+ private void init() {
+ confirmed = TriState.TRUE;
+ regression.clear();
+ }
+
+ @Override
+ public boolean shouldRender() {
+ return super.shouldRender() && confirmed == TriState.TRUE;
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CreeperBeams.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CreeperBeams.java
index 6369291b..689788e1 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CreeperBeams.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/CreeperBeams.java
@@ -237,12 +237,12 @@ public class CreeperBeams {
// render either in a color if not created or faintly green if created
public void render(WorldRenderContext wrc, float[] color) {
if (toDo) {
- RenderHelper.renderOutline(wrc, outlineOne, color, 3);
- RenderHelper.renderOutline(wrc, outlineTwo, color, 3);
+ RenderHelper.renderOutline(wrc, outlineOne, color, 3, false);
+ RenderHelper.renderOutline(wrc, outlineTwo, color, 3, false);
RenderHelper.renderLinesFromPoints(wrc, line, color, 1, 2);
} else {
- RenderHelper.renderOutline(wrc, outlineOne, LIME_COLOR_COMPONENTS, 1);
- RenderHelper.renderOutline(wrc, outlineTwo, LIME_COLOR_COMPONENTS, 1);
+ RenderHelper.renderOutline(wrc, outlineOne, LIME_COLOR_COMPONENTS, 1, false);
+ RenderHelper.renderOutline(wrc, outlineTwo, LIME_COLOR_COMPONENTS, 1, false);
RenderHelper.renderLinesFromPoints(wrc, line, LIME_COLOR_COMPONENTS, 0.75f, 1);
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonBlaze.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonBlaze.java
index cfb16b4d..f49a2f2e 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonBlaze.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonBlaze.java
@@ -128,11 +128,11 @@ public class DungeonBlaze {
*/
private static void renderBlazeOutline(ArmorStandEntity blaze, ArmorStandEntity nextBlaze, WorldRenderContext wrc) {
Box blazeBox = blaze.getBoundingBox().expand(0.3, 0.9, 0.3).offset(0, -1.1, 0);
- RenderHelper.renderOutline(wrc, blazeBox, GREEN_COLOR_COMPONENTS, 5f);
+ RenderHelper.renderOutline(wrc, blazeBox, GREEN_COLOR_COMPONENTS, 5f, false);
if (nextBlaze != null && nextBlaze.isAlive() && nextBlaze != blaze) {
Box nextBlazeBox = nextBlaze.getBoundingBox().expand(0.3, 0.9, 0.3).offset(0, -1.1, 0);
- RenderHelper.renderOutline(wrc, nextBlazeBox, WHITE_COLOR_COMPONENTS, 5f);
+ RenderHelper.renderOutline(wrc, nextBlazeBox, WHITE_COLOR_COMPONENTS, 5f, false);
Vec3d blazeCenter = blazeBox.getCenter();
Vec3d nextBlazeCenter = nextBlazeBox.getCenter();
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonChestProfit.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonChestProfit.java
index 4e6a5240..1e889af3 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonChestProfit.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonChestProfit.java
@@ -4,7 +4,8 @@ import com.google.gson.JsonObject;
import de.hysky.skyblocker.config.SkyblockerConfig;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.mixin.accessor.ScreenAccessor;
-import de.hysky.skyblocker.skyblock.item.PriceInfoTooltip;
+import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType;
import de.hysky.skyblocker.utils.Utils;
import it.unimi.dsi.fastutil.ints.IntBooleanPair;
import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
@@ -54,7 +55,7 @@ public class DungeonChestProfit {
if (!stack.isEmpty()) {
String name = stack.getName().getString();
- String id = PriceInfoTooltip.getInternalNameFromNBT(stack, false);
+ String id = ItemTooltip.getInternalNameFromNBT(stack, false);
//Regular item price
if (id != null) {
@@ -128,8 +129,8 @@ public class DungeonChestProfit {
* was based on complete data.
*/
private static IntBooleanPair getItemPrice(String id) {
- JsonObject bazaarPrices = PriceInfoTooltip.getBazaarPrices();
- JsonObject lbinPrices = PriceInfoTooltip.getLBINPrices();
+ JsonObject bazaarPrices = TooltipInfoType.BAZAAR.getData();
+ JsonObject lbinPrices = TooltipInfoType.LOWEST_BINS.getData();
if (bazaarPrices == null || lbinPrices == null) return IntBooleanPair.of(0, false);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMapConfigScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMapConfigScreen.java
index 69018cd4..c198238b 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMapConfigScreen.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonMapConfigScreen.java
@@ -57,6 +57,7 @@ public class DungeonMapConfigScreen extends Screen {
SkyblockerConfigManager.get().locations.dungeons.mapX = hudX;
SkyblockerConfigManager.get().locations.dungeons.mapY = hudY;
SkyblockerConfigManager.save();
+
this.client.setScreen(parent);
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/TicTacToe.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/TicTacToe.java
index 2d56c8a0..7f249e7d 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/TicTacToe.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/TicTacToe.java
@@ -127,7 +127,7 @@ public class TicTacToe {
private static void solutionRenderer(WorldRenderContext context) {
try {
if (SkyblockerConfigManager.get().locations.dungeons.solveTicTacToe && nextBestMoveToMake != null) {
- RenderHelper.renderOutline(context, nextBestMoveToMake, RED_COLOR_COMPONENTS, 5);
+ RenderHelper.renderOutline(context, nextBestMoveToMake, RED_COLOR_COMPONENTS, 5, false);
}
} catch (Exception e) {
LOGGER.error("[Skyblocker Tic Tac Toe] Encountered an exception while rendering the tic tac toe solution!", e);
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 259cc3f3..73d4a452 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
@@ -7,7 +7,6 @@ import net.minecraft.block.MapColor;
import net.minecraft.item.map.MapIcon;
import net.minecraft.item.map.MapState;
import net.minecraft.util.math.BlockPos;
-import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.Vec3i;
import org.jetbrains.annotations.NotNull;
@@ -173,7 +172,7 @@ public class DungeonMapUtils {
@NotNull
public static Vector2ic getPhysicalRoomPos(double x, double z) {
Vector2i physicalPos = new Vector2i(x + 8.5, z + 8.5, RoundingMode.TRUNCATE);
- return physicalPos.sub(MathHelper.floorMod(physicalPos.x(), 32), MathHelper.floorMod(physicalPos.y(), 32)).sub(8, 8);
+ return physicalPos.sub(Math.floorMod(physicalPos.x(), 32), Math.floorMod(physicalPos.y(), 32)).sub(8, 8);
}
public static Vector2ic[] getPhysicalPosFromMap(Vector2ic mapEntrancePos, int mapRoomSize, Vector2ic physicalEntrancePos, Vector2ic... mapPositions) {
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java
index c2358689..eda08cf6 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonSecrets.java
@@ -1,5 +1,7 @@
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;
@@ -7,15 +9,19 @@ import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
-import it.unimi.dsi.fastutil.objects.Object2ByteMap;
-import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap;
-import it.unimi.dsi.fastutil.objects.ObjectIntPair;
+import com.mojang.brigadier.context.CommandContext;
+import com.mojang.serialization.JsonOps;
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.Constants;
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.Object2ByteOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ObjectIntPair;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
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;
@@ -23,6 +29,9 @@ import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.command.argument.BlockPosArgumentType;
+import net.minecraft.command.argument.PosArgument;
+import net.minecraft.command.argument.TextArgumentType;
import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.mob.AmbientEntity;
@@ -32,11 +41,15 @@ import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.item.map.MapState;
import net.minecraft.resource.Resource;
+import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Identifier;
import net.minecraft.util.hit.BlockHitResult;
+import net.minecraft.util.hit.HitResult;
+import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
+import net.minecraft.util.math.Vec3i;
import net.minecraft.world.World;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
@@ -46,10 +59,14 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
+import java.io.BufferedWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.CompletableFuture;
+import java.util.stream.Stream;
import java.util.zip.InflaterInputStream;
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument;
@@ -58,6 +75,7 @@ import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.lit
public class DungeonSecrets {
protected static final Logger LOGGER = LoggerFactory.getLogger(DungeonSecrets.class);
private static final String DUNGEONS_PATH = "dungeons";
+ private static final Path CUSTOM_WAYPOINTS_DIR = SkyblockerMod.CONFIG_DIR.resolve("custom_secret_waypoints.json");
/**
* Maps the block identifier string to a custom numeric block id used in dungeon rooms data.
*
@@ -97,6 +115,10 @@ public class DungeonSecrets {
private static final Map<Vector2ic, Room> rooms = new HashMap<>();
private static final Map<String, JsonElement> roomsJson = new HashMap<>();
private static final Map<String, JsonElement> waypointsJson = new HashMap<>();
+ /**
+ * The map of dungeon room names to custom waypoints relative to the room.
+ */
+ private static final Table<String, BlockPos, SecretWaypoint> customWaypoints = HashBasedTable.create();
@Nullable
private static CompletableFuture<Void> roomsLoaded;
/**
@@ -119,6 +141,10 @@ public class DungeonSecrets {
return roomsLoaded != null && roomsLoaded.isDone();
}
+ public static Stream<Room> getRoomsStream() {
+ return rooms.values().stream();
+ }
+
@SuppressWarnings("unused")
public static JsonObject getRoomMetadata(String room) {
return roomsJson.get(room).getAsJsonObject();
@@ -129,6 +155,38 @@ public class DungeonSecrets {
}
/**
+ * @see #customWaypoints
+ */
+ public static Map<BlockPos, SecretWaypoint> getCustomWaypoints(String room) {
+ return customWaypoints.row(room);
+ }
+
+ /**
+ * @see #customWaypoints
+ */
+ @SuppressWarnings("UnusedReturnValue")
+ public static SecretWaypoint addCustomWaypoint(String room, SecretWaypoint waypoint) {
+ return customWaypoints.put(room, waypoint.pos, waypoint);
+ }
+
+ /**
+ * @see #customWaypoints
+ */
+ public static void addCustomWaypoints(String room, Collection<SecretWaypoint> waypoints) {
+ for (SecretWaypoint waypoint : waypoints) {
+ addCustomWaypoint(room, waypoint);
+ }
+ }
+
+ /**
+ * @see #customWaypoints
+ */
+ @Nullable
+ public static SecretWaypoint removeCustomWaypoint(String room, BlockPos pos) {
+ return customWaypoints.remove(room, pos);
+ }
+
+ /**
* Loads the dungeon secrets asynchronously from {@code /assets/skyblocker/dungeons}.
* Use {@link #isRoomsLoaded()} to check for completion of loading.
*/
@@ -138,9 +196,10 @@ public class DungeonSecrets {
}
// Execute with MinecraftClient as executor since we need to wait for MinecraftClient#resourceManager to be set
CompletableFuture.runAsync(DungeonSecrets::load, MinecraftClient.getInstance()).exceptionally(e -> {
- LOGGER.error("[Skyblocker] Failed to load dungeon secrets", 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);
@@ -148,7 +207,14 @@ public class DungeonSecrets {
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("markAsMissing").then(markSecretsCommand(false)))
+ .then(literal("getRelativePos").executes(DungeonSecrets::getRelativePos))
+ .then(literal("getRelativeTargetPos").executes(DungeonSecrets::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)))
+ ))));
ClientPlayConnectionEvents.JOIN.register(((handler, sender, client) -> reset()));
}
@@ -158,7 +224,7 @@ public class DungeonSecrets {
for (Map.Entry<Identifier, Resource> resourceEntry : MinecraftClient.getInstance().getResourceManager().findResources(DUNGEONS_PATH, id -> id.getPath().endsWith(".skeleton")).entrySet()) {
String[] path = resourceEntry.getKey().getPath().split("/");
if (path.length != 4) {
- LOGGER.error("[Skyblocker] Failed to load dungeon secrets, invalid resource identifier {}", resourceEntry.getKey());
+ LOGGER.error("[Skyblocker Dungeon Secrets] Failed to load dungeon secrets, invalid resource identifier {}", resourceEntry.getKey());
break;
}
String dungeon = path[1];
@@ -171,9 +237,9 @@ public class DungeonSecrets {
synchronized (roomsMap) {
roomsMap.put(room, rooms);
}
- LOGGER.debug("[Skyblocker] Loaded dungeon secrets dungeon {} room shape {} room {}", dungeon, roomShape, room);
+ LOGGER.debug("[Skyblocker Dungeon Secrets] Loaded dungeon secrets dungeon {} room shape {} room {}", dungeon, roomShape, room);
}).exceptionally(e -> {
- LOGGER.error("[Skyblocker] Failed to load dungeon secrets dungeon {} room shape {} room {}", dungeon, roomShape, room, e);
+ LOGGER.error("[Skyblocker Dungeon Secrets] Failed to load dungeon secrets dungeon {} room shape {} room {}", dungeon, roomShape, room, e);
return null;
}));
}
@@ -181,16 +247,39 @@ public class DungeonSecrets {
try (BufferedReader roomsReader = MinecraftClient.getInstance().getResourceManager().openAsReader(new Identifier(SkyblockerMod.NAMESPACE, "dungeons/dungeonrooms.json")); BufferedReader waypointsReader = MinecraftClient.getInstance().getResourceManager().openAsReader(new Identifier(SkyblockerMod.NAMESPACE, "dungeons/secretlocations.json"))) {
loadJson(roomsReader, roomsJson);
loadJson(waypointsReader, waypointsJson);
- LOGGER.debug("[Skyblocker] Loaded dungeon secrets json");
+ LOGGER.debug("[Skyblocker Dungeon Secrets] Loaded dungeon secret waypoints json");
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker Dungeon Secrets] Failed to load dungeon secret waypoints json", e);
+ }
+ }));
+ dungeonFutures.add(CompletableFuture.runAsync(() -> {
+ try (BufferedReader customWaypointsReader = Files.newBufferedReader(CUSTOM_WAYPOINTS_DIR)) {
+ SkyblockerMod.GSON.fromJson(customWaypointsReader, JsonObject.class).asMap().forEach((room, waypointsJson) ->
+ addCustomWaypoints(room, SecretWaypoint.LIST_CODEC.parse(JsonOps.INSTANCE, waypointsJson).resultOrPartial(LOGGER::error).orElseThrow())
+ );
+ LOGGER.debug("[Skyblocker Dungeon Secrets] Loaded custom dungeon secret waypoints");
} catch (Exception e) {
- LOGGER.error("[Skyblocker] Failed to load dungeon secrets json", e);
+ LOGGER.error("[Skyblocker Dungeon Secrets] Failed to load custom dungeon secret waypoints", e);
}
}));
- roomsLoaded = CompletableFuture.allOf(dungeonFutures.toArray(CompletableFuture[]::new)).thenRun(() -> LOGGER.info("[Skyblocker] Loaded dungeon secrets for {} dungeon(s), {} room shapes, and {} rooms total in {} ms", ROOMS_DATA.size(), ROOMS_DATA.values().stream().mapToInt(Map::size).sum(), ROOMS_DATA.values().stream().map(Map::values).flatMap(Collection::stream).mapToInt(Map::size).sum(), System.currentTimeMillis() - startTime)).exceptionally(e -> {
- LOGGER.error("[Skyblocker] Failed to load dungeon secrets", e);
+ roomsLoaded = CompletableFuture.allOf(dungeonFutures.toArray(CompletableFuture[]::new)).thenRun(() -> LOGGER.info("[Skyblocker Dungeon Secrets] Loaded dungeon secrets for {} dungeon(s), {} room shapes, {} rooms, and {} custom secret waypoints total in {} ms", ROOMS_DATA.size(), ROOMS_DATA.values().stream().mapToInt(Map::size).sum(), ROOMS_DATA.values().stream().map(Map::values).flatMap(Collection::stream).mapToInt(Map::size).sum(), customWaypoints.size(), System.currentTimeMillis() - startTime)).exceptionally(e -> {
+ LOGGER.error("[Skyblocker Dungeon Secrets] Failed to load dungeon secrets", e);
return null;
});
- LOGGER.info("[Skyblocker] Started loading dungeon secrets in (blocked main thread for) {} ms", System.currentTimeMillis() - startTime);
+ LOGGER.info("[Skyblocker Dungeon Secrets] Started loading dungeon secrets in (blocked main thread for) {} ms", System.currentTimeMillis() - startTime);
+ }
+
+ private static void saveCustomWaypoints(MinecraftClient client) {
+ try (BufferedWriter writer = Files.newBufferedWriter(CUSTOM_WAYPOINTS_DIR)) {
+ JsonObject customWaypointsJson = new JsonObject();
+ customWaypoints.rowMap().forEach((room, waypoints) ->
+ customWaypointsJson.add(room, SecretWaypoint.LIST_CODEC.encodeStart(JsonOps.INSTANCE, new ArrayList<>(waypoints.values())).resultOrPartial(LOGGER::error).orElseThrow())
+ );
+ SkyblockerMod.GSON.toJson(customWaypointsJson, writer);
+ LOGGER.info("[Skyblocker Dungeon Secrets] Saved custom dungeon secret waypoints");
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker Dungeon Secrets] Failed to save custom dungeon secret waypoints", e);
+ }
}
private static int[] readRoom(Resource resource) throws RuntimeException {
@@ -203,25 +292,110 @@ public class DungeonSecrets {
/**
* Loads the json from the given {@link BufferedReader} into the given {@link Map}.
+ *
* @param reader the reader to read the json from
- * @param map the map to load into
+ * @param map the map to load into
*/
private static void loadJson(BufferedReader reader, Map<String, JsonElement> map) {
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) {
- return argument("secret", IntegerArgumentType.integer()).executes(context -> {
- int secretIndex = IntegerArgumentType.getInteger(context, "secret");
+ return argument("secretIndex", IntegerArgumentType.integer()).executes(context -> {
+ int secretIndex = IntegerArgumentType.getInteger(context, "secretIndex");
if (markSecrets(secretIndex, found)) {
- context.getSource().sendFeedback(Text.translatable(found ? "skyblocker.dungeons.secrets.markSecretFound" : "skyblocker.dungeons.secrets.markSecretMissing", secretIndex));
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable(found ? "skyblocker.dungeons.secrets.markSecretFound" : "skyblocker.dungeons.secrets.markSecretMissing", secretIndex)));
} else {
- context.getSource().sendError(Text.translatable(found ? "skyblocker.dungeons.secrets.markSecretFoundUnable" : "skyblocker.dungeons.secrets.markSecretMissingUnable", secretIndex));
+ context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable(found ? "skyblocker.dungeons.secrets.markSecretFoundUnable" : "skyblocker.dungeons.secrets.markSecretMissingUnable", secretIndex)));
}
return Command.SINGLE_SUCCESS;
});
}
+ private static int getRelativePos(CommandContext<FabricClientCommandSource> context) {
+ return getRelativePos(context.getSource(), context.getSource().getPlayer().getBlockPos());
+ }
+
+ private static int getRelativeTargetPos(CommandContext<FabricClientCommandSource> context) {
+ if (MinecraftClient.getInstance().crosshairTarget instanceof BlockHitResult blockHitResult && blockHitResult.getType() == HitResult.Type.BLOCK) {
+ return getRelativePos(context.getSource(), blockHitResult.getBlockPos());
+ } else {
+ context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.noTarget")));
+ }
+ return Command.SINGLE_SUCCESS;
+ }
+
+ private static int getRelativePos(FabricClientCommandSource source, BlockPos pos) {
+ 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())));
+ } 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) {
+ return argument("pos", BlockPosArgumentType.blockPos())
+ .then(argument("secretIndex", IntegerArgumentType.integer())
+ .then(argument("category", SecretWaypoint.Category.CategoryArgumentType.category())
+ .then(argument("name", TextArgumentType.text()).executes(context -> {
+ // TODO Less hacky way with custom ClientBlockPosArgumentType
+ BlockPos pos = context.getArgument("pos", PosArgument.class).toAbsoluteBlockPos(new ServerCommandSource(null, context.getSource().getPosition(), context.getSource().getRotation(), null, 0, null, null, null, null));
+ return relative ? addCustomWaypointRelative(context, pos) : addCustomWaypoint(context, pos);
+ }))
+ )
+ );
+ }
+
+ private static int addCustomWaypoint(CommandContext<FabricClientCommandSource> context, BlockPos pos) {
+ Room room = getRoomAtPhysical(pos);
+ if (isRoomMatched(room)) {
+ room.addCustomWaypoint(context, room.actualToRelative(pos));
+ } else {
+ context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.notMatched")));
+ }
+ return Command.SINGLE_SUCCESS;
+ }
+
+ private static int addCustomWaypointRelative(CommandContext<FabricClientCommandSource> context, BlockPos pos) {
+ if (isCurrentRoomMatched()) {
+ currentRoom.addCustomWaypoint(context, pos);
+ } else {
+ context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.notMatched")));
+ }
+ return Command.SINGLE_SUCCESS;
+ }
+
+ private static ArgumentBuilder<FabricClientCommandSource, RequiredArgumentBuilder<FabricClientCommandSource, PosArgument>> removeCustomWaypointCommand(boolean relative) {
+ return argument("pos", BlockPosArgumentType.blockPos())
+ .executes(context -> {
+ // TODO Less hacky way with custom ClientBlockPosArgumentType
+ BlockPos pos = context.getArgument("pos", PosArgument.class).toAbsoluteBlockPos(new ServerCommandSource(null, context.getSource().getPosition(), context.getSource().getRotation(), null, 0, null, null, null, null));
+ return relative ? removeCustomWaypointRelative(context, pos) : removeCustomWaypoint(context, pos);
+ });
+ }
+
+ private static int removeCustomWaypoint(CommandContext<FabricClientCommandSource> context, BlockPos pos) {
+ Room room = getRoomAtPhysical(pos);
+ if (isRoomMatched(room)) {
+ room.removeCustomWaypoint(context, room.actualToRelative(pos));
+ } else {
+ context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.notMatched")));
+ }
+ return Command.SINGLE_SUCCESS;
+ }
+
+ private static int removeCustomWaypointRelative(CommandContext<FabricClientCommandSource> context, BlockPos pos) {
+ if (isCurrentRoomMatched()) {
+ currentRoom.removeCustomWaypoint(context, pos);
+ } else {
+ context.getSource().sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.notMatched")));
+ }
+ return Command.SINGLE_SUCCESS;
+ }
+
/**
* Updates the dungeon. The general idea is similar to the Dungeon Rooms Mod.
* <p></p>
@@ -282,7 +456,7 @@ public class DungeonSecrets {
}
mapEntrancePos = mapEntrancePosAndSize.left();
mapRoomSize = mapEntrancePosAndSize.rightInt();
- LOGGER.info("[Skyblocker] Started dungeon with map room size {}, map entrance pos {}, player pos {}, and physical entrance pos {}", mapRoomSize, mapEntrancePos, client.player.getPos(), physicalEntrancePos);
+ LOGGER.info("[Skyblocker Dungeon Secrets] Started dungeon with map room size {}, map entrance pos {}, player pos {}, and physical entrance pos {}", mapRoomSize, mapEntrancePos, client.player.getPos(), physicalEntrancePos);
}
Vector2ic physicalPos = DungeonMapUtils.getPhysicalRoomPos(client.player.getPos());
@@ -320,7 +494,7 @@ public class DungeonSecrets {
}
return newRoom;
} catch (IllegalArgumentException e) {
- LOGGER.error("[Skyblocker] Failed to create room", e);
+ LOGGER.error("[Skyblocker Dungeon Secrets] Failed to create room", e);
}
return null;
}
@@ -339,9 +513,16 @@ public class DungeonSecrets {
* Used to detect when all secrets in a room are found.
*/
private static void onChatMessage(Text text, boolean overlay) {
+ String message = text.getString();
+
if (overlay && isCurrentRoomMatched()) {
- currentRoom.onChatMessage(text.getString());
+ currentRoom.onChatMessage(message);
}
+
+ 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();
}
/**
@@ -410,6 +591,19 @@ public class DungeonSecrets {
}
/**
+ * Gets the room at the given physical position.
+ *
+ * @param pos the physical position
+ * @return the room at the given physical position, or null if there is no room at the given physical position
+ * @see #rooms
+ * @see DungeonMapUtils#getPhysicalRoomPos(Vec3i)
+ */
+ @Nullable
+ private static Room getRoomAtPhysical(Vec3i pos) {
+ return rooms.get(DungeonMapUtils.getPhysicalRoomPos(pos));
+ }
+
+ /**
* Calls {@link #isRoomMatched(Room)} on {@link #currentRoom}.
*
* @return {@code true} if {@link #currentRoom} is not null and {@link #isRoomMatched(Room)}
@@ -439,7 +633,7 @@ public class DungeonSecrets {
}
/**
- * Resets fields when leaving a dungeon.
+ * Resets fields when leaving a dungeon or entering boss.
*/
private static void reset() {
mapEntrancePos = null;
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 dd7dc91e..ecfcf496 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
@@ -1,14 +1,17 @@
package de.hysky.skyblocker.skyblock.dungeon.secrets;
import com.google.common.collect.HashBasedTable;
-import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Table;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
+import com.mojang.brigadier.arguments.IntegerArgumentType;
+import com.mojang.brigadier.context.CommandContext;
+import de.hysky.skyblocker.utils.Constants;
+import de.hysky.skyblocker.utils.scheduler.Scheduler;
import it.unimi.dsi.fastutil.ints.IntRBTreeSet;
import it.unimi.dsi.fastutil.ints.IntSortedSet;
import it.unimi.dsi.fastutil.ints.IntSortedSets;
-import de.hysky.skyblocker.utils.scheduler.Scheduler;
+import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
import net.fabricmc.fabric.api.util.TriState;
import net.minecraft.block.BlockState;
@@ -21,13 +24,14 @@ 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.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
-import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import org.apache.commons.lang3.tuple.MutableTriple;
import org.apache.commons.lang3.tuple.Triple;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import org.joml.Vector2i;
import org.joml.Vector2ic;
@@ -72,6 +76,9 @@ public class Room {
*/
private TriState matched = TriState.DEFAULT;
private Table<Integer, BlockPos, SecretWaypoint> secretWaypoints;
+ private String name;
+ private Direction direction;
+ private Vector2ic physicalCornerPos;
public Room(@NotNull Type type, @NotNull Vector2ic... physicalPositions) {
this.type = type;
@@ -92,6 +99,13 @@ public class Room {
return matched == TriState.TRUE;
}
+ /**
+ * Not null if {@link #isMatched()}.
+ */
+ public String getName() {
+ return name;
+ }
+
@Override
public String toString() {
return "Room{type=" + type + ", shape=" + shape + ", matched=" + matched + ", segments=" + Arrays.toString(segments.toArray()) + "}";
@@ -145,6 +159,79 @@ public class Room {
}
/**
+ * @see #addCustomWaypoint(int, SecretWaypoint.Category, Text, BlockPos)
+ */
+ protected void addCustomWaypoint(CommandContext<FabricClientCommandSource> context, BlockPos pos) {
+ int secretIndex = IntegerArgumentType.getInteger(context, "secretIndex");
+ SecretWaypoint.Category category = SecretWaypoint.Category.CategoryArgumentType.getCategory(context, "category");
+ Text waypointName = context.getArgument("name", Text.class);
+ addCustomWaypoint(secretIndex, category, waypointName, pos);
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.customWaypointAdded", pos.getX(), pos.getY(), pos.getZ(), name, secretIndex, category, waypointName)));
+ }
+
+ /**
+ * Adds a custom waypoint relative to this room to {@link DungeonSecrets#customWaypoints} and all existing instances of this room.
+ *
+ * @param secretIndex the index of the secret waypoint
+ * @param category the category of the secret waypoint
+ * @param waypointName the name of the secret waypoint
+ * @param pos the position of the secret waypoint relative to this 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));
+ }
+
+ /**
+ * Adds a custom waypoint relative to this room to this instance of the room.
+ *
+ * @param relativeWaypoint the secret waypoint relative to this room to add
+ */
+ private void addCustomWaypoint(SecretWaypoint relativeWaypoint) {
+ SecretWaypoint actualWaypoint = relativeWaypoint.relativeToActual(this);
+ secretWaypoints.put(actualWaypoint.secretIndex, actualWaypoint.pos, actualWaypoint);
+ }
+
+ /**
+ * @see #removeCustomWaypoint(BlockPos)
+ */
+ protected void removeCustomWaypoint(CommandContext<FabricClientCommandSource> context, BlockPos pos) {
+ SecretWaypoint waypoint = removeCustomWaypoint(pos);
+ if (waypoint != null) {
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.customWaypointRemoved", pos.getX(), pos.getY(), pos.getZ(), name, waypoint.secretIndex, waypoint.category, waypoint.name)));
+ } else {
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secrets.customWaypointNotFound", pos.getX(), pos.getY(), pos.getZ(), name)));
+ }
+ }
+
+ /**
+ * Removes a custom waypoint relative to this room from {@link DungeonSecrets#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
+ */
+ @SuppressWarnings("JavadocReference")
+ @Nullable
+ private SecretWaypoint removeCustomWaypoint(BlockPos pos) {
+ SecretWaypoint waypoint = DungeonSecrets.removeCustomWaypoint(name, pos);
+ if (waypoint != null) {
+ DungeonSecrets.getRoomsStream().filter(r -> name.equals(r.getName())).forEach(r -> r.removeCustomWaypoint(waypoint.secretIndex, pos));
+ }
+ return waypoint;
+ }
+
+ /**
+ * Removes a custom waypoint relative to this room from this instance of the room.
+ * @param secretIndex the index of the secret waypoint
+ * @param relativePos the position of the secret waypoint relative to this room
+ */
+ private void removeCustomWaypoint(int secretIndex, BlockPos relativePos) {
+ BlockPos actualPos = relativeToActual(relativePos);
+ secretWaypoints.remove(secretIndex, actualPos);
+ }
+
+ /**
* Updates the room.
* <p></p>
* This method returns immediately if any of the following conditions are met:
@@ -186,8 +273,8 @@ public class Room {
if (pos.getY() < 66 || pos.getY() > 73) {
return true;
}
- int x = MathHelper.floorMod(pos.getX() - 8, 32);
- int z = MathHelper.floorMod(pos.getZ() - 8, 32);
+ int x = Math.floorMod(pos.getX() - 8, 32);
+ int z = Math.floorMod(pos.getZ() - 8, 32);
return (x < 13 || x > 17 || z > 2 && z < 28) && (z < 13 || z > 17 || x > 2 && x < 28);
}
@@ -217,7 +304,7 @@ public class Room {
* </ul>
* <li> If there are exactly one room matching: </li>
* <ul>
- * <li> Call {@link #roomMatched(String, Direction, Vector2ic)}. </li>
+ * <li> Call {@link #roomMatched()}. </li>
* <li> Discard the no longer needed fields to save memory. </li>
* <li> Return {@code true} </li>
* </ul>
@@ -256,7 +343,10 @@ public class Room {
// If one room matches, load the secrets for that room and discard the no longer needed fields.
for (Triple<Direction, Vector2ic, List<String>> directionRooms : possibleRooms) {
if (directionRooms.getRight().size() == 1) {
- roomMatched(directionRooms.getRight().get(0), directionRooms.getLeft(), directionRooms.getMiddle());
+ name = directionRooms.getRight().get(0);
+ direction = directionRooms.getLeft();
+ physicalCornerPos = directionRooms.getMiddle();
+ roomMatched();
discard();
return true;
}
@@ -286,17 +376,18 @@ public class Room {
* @param directionRooms the direction, position, and name of the room
*/
@SuppressWarnings("JavadocReference")
- private void roomMatched(String name, Direction direction, Vector2ic physicalCornerPos) {
- Table<Integer, BlockPos, SecretWaypoint> secretWaypointsMutable = HashBasedTable.create();
+ private void roomMatched() {
+ secretWaypoints = HashBasedTable.create();
for (JsonElement waypointElement : DungeonSecrets.getRoomWaypoints(name)) {
JsonObject waypoint = waypointElement.getAsJsonObject();
String secretName = waypoint.get("secretName").getAsString();
int secretIndex = Integer.parseInt(secretName.substring(0, Character.isDigit(secretName.charAt(1)) ? 2 : 1));
BlockPos pos = DungeonMapUtils.relativeToActual(direction, physicalCornerPos, waypoint);
- secretWaypointsMutable.put(secretIndex, pos, new SecretWaypoint(secretIndex, waypoint, secretName, pos));
+ secretWaypoints.put(secretIndex, pos, new SecretWaypoint(secretIndex, waypoint, secretName, pos));
}
- secretWaypoints = ImmutableTable.copyOf(secretWaypointsMutable);
+ DungeonSecrets.getCustomWaypoints(name).values().forEach(this::addCustomWaypoint);
matched = TriState.TRUE;
+
DungeonSecrets.LOGGER.info("[Skyblocker] Room {} matched after checking {} block(s)", name, checkedBlocks.size());
}
@@ -323,6 +414,20 @@ public class Room {
}
/**
+ * Fails if !{@link #isMatched()}
+ */
+ protected BlockPos actualToRelative(BlockPos pos) {
+ return DungeonMapUtils.actualToRelative(direction, physicalCornerPos, pos);
+ }
+
+ /**
+ * Fails if !{@link #isMatched()}
+ */
+ protected BlockPos relativeToActual(BlockPos pos) {
+ return DungeonMapUtils.relativeToActual(direction, physicalCornerPos, pos);
+ }
+
+ /**
* Calls {@link SecretWaypoint#render(WorldRenderContext)} on {@link #secretWaypoints all secret waypoints}.
*/
protected void render(WorldRenderContext context) {
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 d2a31ea3..0c2d1b34 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
@@ -1,38 +1,61 @@
package de.hysky.skyblocker.skyblock.dungeon.secrets;
import com.google.gson.JsonObject;
-
+import com.mojang.brigadier.context.CommandContext;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.JsonOps;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
import de.hysky.skyblocker.config.SkyblockerConfig;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.utils.render.RenderHelper;
+import de.hysky.skyblocker.utils.waypoint.Waypoint;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
import net.minecraft.client.MinecraftClient;
+import net.minecraft.command.argument.EnumArgumentType;
import net.minecraft.entity.Entity;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
+import net.minecraft.util.StringIdentifiable;
+import net.minecraft.util.dynamic.Codecs;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
+import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.function.Predicate;
+import java.util.function.Supplier;
import java.util.function.ToDoubleFunction;
-public class SecretWaypoint {
+public class SecretWaypoint extends Waypoint {
+ 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),
+ Codecs.TEXT.fieldOf("name").forGetter(secretWaypoint -> secretWaypoint.name),
+ BlockPos.CODEC.fieldOf("pos").forGetter(secretWaypoint -> secretWaypoint.pos)
+ ).apply(instance, SecretWaypoint::new));
+ 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 SkyblockerConfig.SecretWaypoints CONFIG = SkyblockerConfigManager.get().locations.dungeons.secretWaypoints;
+ private static final Supplier<Type> TYPE_SUPPLIER = () -> CONFIG.waypointType;
final int secretIndex;
final Category category;
- private final Text name;
- private final BlockPos pos;
+ final Text name;
private final Vec3d centerPos;
- private boolean missing;
SecretWaypoint(int secretIndex, JsonObject waypoint, String name, BlockPos pos) {
+ this(secretIndex, Category.get(waypoint), name, pos);
+ }
+
+ SecretWaypoint(int secretIndex, Category category, String name, BlockPos pos) {
+ this(secretIndex, category, Text.of(name), pos);
+ }
+
+ SecretWaypoint(int secretIndex, Category category, Text name, BlockPos pos) {
+ super(pos, TYPE_SUPPLIER, category.colorComponents);
this.secretIndex = secretIndex;
- this.category = Category.get(waypoint);
- this.name = Text.of(name);
- this.pos = pos;
+ this.category = category;
+ this.name = name;
this.centerPos = pos.toCenterPos();
- this.missing = true;
}
static ToDoubleFunction<SecretWaypoint> getSquaredDistanceToFunction(Entity entity) {
@@ -43,8 +66,9 @@ public class SecretWaypoint {
return secretWaypoint -> entity.squaredDistanceTo(secretWaypoint.centerPos) <= 36D;
}
- boolean shouldRender() {
- return category.isEnabled() && missing;
+ @Override
+ public boolean shouldRender() {
+ return super.shouldRender() && category.isEnabled();
}
boolean needsInteraction() {
@@ -63,40 +87,47 @@ public class SecretWaypoint {
return category.isBat();
}
- void setFound() {
- this.missing = false;
- }
-
- void setMissing() {
- this.missing = true;
- }
-
/**
* Renders the secret waypoint, including a filled cube, a beacon beam, the name, and the distance from the player.
*/
- void render(WorldRenderContext context) {
- RenderHelper.renderFilledThroughWallsWithBeaconBeam(context, pos, category.colorComponents, 0.5F);
- Vec3d posUp = centerPos.add(0, 1, 0);
- RenderHelper.renderText(context, name, posUp, true);
- double distance = context.camera().getPos().distanceTo(centerPos);
- RenderHelper.renderText(context, Text.literal(Math.round(distance) + "m").formatted(Formatting.YELLOW), posUp, 1, MinecraftClient.getInstance().textRenderer.fontHeight + 1, true);
+ @Override
+ public void render(WorldRenderContext context) {
+ //TODO In the future, shrink the box for wither essence and items so its more realistic
+ super.render(context);
+
+ if (CONFIG.showSecretText) {
+ Vec3d posUp = centerPos.add(0, 1, 0);
+ RenderHelper.renderText(context, name, posUp, true);
+ double distance = context.camera().getPos().distanceTo(centerPos);
+ RenderHelper.renderText(context, Text.literal(Math.round(distance) + "m").formatted(Formatting.YELLOW), posUp, 1, MinecraftClient.getInstance().textRenderer.fontHeight + 1, true);
+ }
+ }
+
+ @NotNull
+ SecretWaypoint relativeToActual(Room room) {
+ return new SecretWaypoint(secretIndex, category, name, room.relativeToActual(pos));
}
- enum Category {
- ENTRANCE(secretWaypoints -> secretWaypoints.enableEntranceWaypoints, 0, 255, 0),
- SUPERBOOM(secretWaypoints -> secretWaypoints.enableSuperboomWaypoints, 255, 0, 0),
- CHEST(secretWaypoints -> secretWaypoints.enableChestWaypoints, 2, 213, 250),
- ITEM(secretWaypoints -> secretWaypoints.enableItemWaypoints, 2, 64, 250),
- BAT(secretWaypoints -> secretWaypoints.enableBatWaypoints, 142, 66, 0),
- WITHER(secretWaypoints -> secretWaypoints.enableWitherWaypoints, 30, 30, 30),
- LEVER(secretWaypoints -> secretWaypoints.enableLeverWaypoints, 250, 217, 2),
- FAIRYSOUL(secretWaypoints -> secretWaypoints.enableFairySoulWaypoints, 255, 85, 255),
- STONK(secretWaypoints -> secretWaypoints.enableStonkWaypoints, 146, 52, 235),
- DEFAULT(secretWaypoints -> secretWaypoints.enableDefaultWaypoints, 190, 255, 252);
+ enum Category implements StringIdentifiable {
+ ENTRANCE("entrance", secretWaypoints -> secretWaypoints.enableEntranceWaypoints, 0, 255, 0),
+ SUPERBOOM("superboom", secretWaypoints -> secretWaypoints.enableSuperboomWaypoints, 255, 0, 0),
+ CHEST("chest", secretWaypoints -> secretWaypoints.enableChestWaypoints, 2, 213, 250),
+ ITEM("item", secretWaypoints -> secretWaypoints.enableItemWaypoints, 2, 64, 250),
+ BAT("bat", secretWaypoints -> secretWaypoints.enableBatWaypoints, 142, 66, 0),
+ WITHER("wither", secretWaypoints -> secretWaypoints.enableWitherWaypoints, 30, 30, 30),
+ LEVER("lever", secretWaypoints -> secretWaypoints.enableLeverWaypoints, 250, 217, 2),
+ FAIRYSOUL("fairysoul", secretWaypoints -> secretWaypoints.enableFairySoulWaypoints, 255, 85, 255),
+ STONK("stonk", secretWaypoints -> secretWaypoints.enableStonkWaypoints, 146, 52, 235),
+ AOTV("aotv", secretWaypoints -> secretWaypoints.enableAotvWaypoints, 252, 98, 3),
+ PEARL("pearl", secretWaypoints -> secretWaypoints.enablePearlWaypoints, 57, 117, 125),
+ DEFAULT("default", secretWaypoints -> secretWaypoints.enableDefaultWaypoints, 190, 255, 252);
+ private static final Codec<Category> CODEC = StringIdentifiable.createCodec(Category::values);
+ private final String name;
private final Predicate<SkyblockerConfig.SecretWaypoints> enabledPredicate;
private final float[] colorComponents;
- Category(Predicate<SkyblockerConfig.SecretWaypoints> enabledPredicate, int... intColorComponents) {
+ Category(String name, Predicate<SkyblockerConfig.SecretWaypoints> enabledPredicate, int... intColorComponents) {
+ this.name = name;
this.enabledPredicate = enabledPredicate;
colorComponents = new float[intColorComponents.length];
for (int i = 0; i < intColorComponents.length; i++) {
@@ -104,19 +135,8 @@ public class SecretWaypoint {
}
}
- private static Category get(JsonObject categoryJson) {
- return switch (categoryJson.get("category").getAsString()) {
- case "entrance" -> Category.ENTRANCE;
- case "superboom" -> Category.SUPERBOOM;
- case "chest" -> Category.CHEST;
- case "item" -> Category.ITEM;
- case "bat" -> Category.BAT;
- case "wither" -> Category.WITHER;
- case "lever" -> Category.LEVER;
- case "fairysoul" -> Category.FAIRYSOUL;
- case "stonk" -> Category.STONK;
- default -> Category.DEFAULT;
- };
+ private static Category get(JsonObject waypointJson) {
+ return CODEC.parse(JsonOps.INSTANCE, waypointJson.get("category")).resultOrPartial(DungeonSecrets.LOGGER::error).orElseThrow();
}
boolean needsInteraction() {
@@ -138,5 +158,29 @@ public class SecretWaypoint {
boolean isEnabled() {
return enabledPredicate.test(SkyblockerConfigManager.get().locations.dungeons.secretWaypoints);
}
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ @Override
+ public String asString() {
+ return name;
+ }
+
+ static class CategoryArgumentType extends EnumArgumentType<Category> {
+ public CategoryArgumentType() {
+ super(Category.CODEC, Category::values);
+ }
+
+ public static CategoryArgumentType category() {
+ return new CategoryArgumentType();
+ }
+
+ public 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/dungeon/secrets/SecretsTracker.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretsTracker.java
new file mode 100644
index 00000000..0690952e
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/SecretsTracker.java
@@ -0,0 +1,174 @@
+package de.hysky.skyblocker.skyblock.dungeon.secrets;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListMgr;
+import de.hysky.skyblocker.skyblock.tabhud.widget.DungeonPlayerWidget;
+import de.hysky.skyblocker.utils.ApiUtils;
+import de.hysky.skyblocker.utils.Constants;
+import de.hysky.skyblocker.utils.Http;
+import de.hysky.skyblocker.utils.Http.ApiResponse;
+import de.hysky.skyblocker.utils.Utils;
+import it.unimi.dsi.fastutil.ints.IntIntPair;
+import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.text.HoverEvent;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Tracks the amount of secrets players get every run
+ */
+public class SecretsTracker {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SecretsTracker.class);
+ private static final Pattern TEAM_SCORE_PATTERN = Pattern.compile(" +Team Score: [0-9]+ \\([A-z+]+\\)");
+
+ private static volatile TrackedRun currentRun = null;
+ private static volatile TrackedRun lastRun = null;
+ private static volatile long lastRunEnded = 0L;
+
+ public static void init() {
+ ClientReceiveMessageEvents.GAME.register(SecretsTracker::onMessage);
+ }
+
+ //If -1 is somehow encountered, it would be very rare, so I just disregard its possibility for now
+ //people would probably recognize if it was inaccurate so yeah
+ private static void calculate(RunPhase phase) {
+ switch (phase) {
+ case START -> CompletableFuture.runAsync(() -> {
+ TrackedRun newlyStartedRun = new TrackedRun();
+
+ //Initialize players in new run
+ for (int i = 0; i < 5; i++) {
+ String playerName = getPlayerNameAt(i + 1);
+
+ //The player name will be blank if there isn't a player at that index
+ if (!playerName.isEmpty()) {
+
+ //If the player was a part of the last run (and didn't have -1 secret count) and that run ended less than 5 mins ago then copy the secrets over
+ if (lastRun != null && System.currentTimeMillis() <= lastRunEnded + 300_000 && lastRun.secretCounts().getOrDefault(playerName, -1) != -1) {
+ newlyStartedRun.secretCounts().put(playerName, lastRun.secretCounts().getInt(playerName));
+ } else {
+ newlyStartedRun.secretCounts().put(playerName, getPlayerSecrets(playerName).leftInt());
+ }
+ }
+ }
+
+ currentRun = newlyStartedRun;
+ });
+
+ case END -> CompletableFuture.runAsync(() -> {
+ //In case the game crashes from something
+ if (currentRun != null) {
+ Object2ObjectOpenHashMap<String, IntIntPair> secretsFound = new Object2ObjectOpenHashMap<>();
+
+ //Update secret counts
+ for (Entry<String> entry : currentRun.secretCounts().object2IntEntrySet()) {
+ String playerName = entry.getKey();
+ int startingSecrets = entry.getIntValue();
+ IntIntPair secretsNow = getPlayerSecrets(playerName);
+ int secretsPlayerFound = secretsNow.leftInt() - startingSecrets;
+
+ secretsFound.put(playerName, IntIntPair.of(secretsPlayerFound, secretsNow.rightInt()));
+ entry.setValue(secretsNow.leftInt());
+ }
+
+ //Print the results all in one go, so its clean and less of a chance of it being broken up
+ for (Map.Entry<String, IntIntPair> entry : secretsFound.entrySet()) {
+ sendResultMessage(entry.getKey(), entry.getValue().leftInt(), entry.getValue().rightInt(), true);
+ }
+
+ //Swap the current and last run as well as mark the run end time
+ lastRunEnded = System.currentTimeMillis();
+ lastRun = currentRun;
+ currentRun = null;
+ } else {
+ sendResultMessage(null, -1, -1, false);
+ }
+ });
+ }
+ }
+
+ private static void sendResultMessage(String player, int secrets, int cacheAge, boolean success) {
+ PlayerEntity playerEntity = MinecraftClient.getInstance().player;
+ if (playerEntity != null) {
+ if (success) {
+ playerEntity.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secretsTracker.feedback", Text.literal(player).styled(Constants.WITH_COLOR.apply(0xf57542)), "§7" + secrets, getCacheText(cacheAge))));
+ } else {
+ playerEntity.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.dungeons.secretsTracker.failFeedback")));
+ }
+ }
+ }
+
+ private static Text getCacheText(int cacheAge) {
+ return Text.literal("\u2139").styled(style -> style.withColor(cacheAge == -1 ? 0x218bff : 0xeac864).withHoverEvent(
+ new HoverEvent(HoverEvent.Action.SHOW_TEXT, cacheAge == -1 ? Text.translatable("skyblocker.api.cache.MISS") : Text.translatable("skyblocker.api.cache.HIT", cacheAge))));
+ }
+
+ private static void onMessage(Text text, boolean overlay) {
+ if (Utils.isInDungeons() && SkyblockerConfigManager.get().locations.dungeons.playerSecretsTracker) {
+ String message = Formatting.strip(text.getString());
+
+ try {
+ if (message.equals("[NPC] Mort: Here, I found this map when I first entered the dungeon.")) calculate(RunPhase.START);
+ if (TEAM_SCORE_PATTERN.matcher(message).matches()) calculate(RunPhase.END);
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker] Encountered an unknown error while trying to track player secrets!", e);
+ }
+ }
+ }
+
+ private static String getPlayerNameAt(int index) {
+ Matcher matcher = PlayerListMgr.regexAt(1 + (index - 1) * 4, DungeonPlayerWidget.PLAYER_PATTERN);
+
+ return matcher != null ? matcher.group("name") : "";
+ }
+
+ private static IntIntPair getPlayerSecrets(String name) {
+ String uuid = ApiUtils.name2Uuid(name);
+
+ if (!uuid.isEmpty()) {
+ try (ApiResponse response = Http.sendHypixelRequest("player", "?uuid=" + uuid)) {
+ return IntIntPair.of(getSecretCountFromAchievements(JsonParser.parseString(response.content()).getAsJsonObject()), response.age());
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker] Encountered an error while trying to fetch {} secret count!", name + "'s", e);
+ }
+ }
+
+ return IntIntPair.of(-1, -1);
+ }
+
+ /**
+ * Gets a player's secret count from their hypixel achievements
+ */
+ private static int getSecretCountFromAchievements(JsonObject playerJson) {
+ JsonObject player = playerJson.get("player").getAsJsonObject();
+ JsonObject achievements = (player.has("achievements")) ? player.get("achievements").getAsJsonObject() : null;
+ return (achievements != null && achievements.has("skyblock_treasure_hunter")) ? achievements.get("skyblock_treasure_hunter").getAsInt() : 0;
+ }
+
+ /**
+ * This will either reflect the value at the start or the end depending on when this is called
+ */
+ private record TrackedRun(Object2IntOpenHashMap<String> secretCounts) {
+ private TrackedRun() {
+ this(new Object2IntOpenHashMap<>());
+ }
+ }
+
+ private enum RunPhase {
+ START, END
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHudConfigScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHudConfigScreen.java
index f4e6ef84..09cb03be 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHudConfigScreen.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHudConfigScreen.java
@@ -62,6 +62,7 @@ public class DwarvenHudConfigScreen extends Screen {
SkyblockerConfigManager.get().locations.dwarvenMines.dwarvenHud.x = hudX;
SkyblockerConfigManager.get().locations.dwarvenMines.dwarvenHud.y = hudY;
SkyblockerConfigManager.save();
+
client.setScreen(parent);
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/BackpackPreview.java b/src/main/java/de/hysky/skyblocker/skyblock/item/BackpackPreview.java
deleted file mode 100644
index 122ffe9b..00000000
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/BackpackPreview.java
+++ /dev/null
@@ -1,235 +0,0 @@
-package de.hysky.skyblocker.skyblock.item;
-
-import com.mojang.blaze3d.systems.RenderSystem;
-import de.hysky.skyblocker.SkyblockerMod;
-import de.hysky.skyblocker.utils.Utils;
-import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
-import net.fabricmc.loader.api.FabricLoader;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.font.TextRenderer;
-import net.minecraft.client.gui.DrawContext;
-import net.minecraft.client.gui.screen.Screen;
-import net.minecraft.client.gui.screen.ingame.HandledScreen;
-import net.minecraft.client.util.math.MatrixStack;
-import net.minecraft.entity.player.PlayerEntity;
-import net.minecraft.inventory.Inventory;
-import net.minecraft.item.ItemStack;
-import net.minecraft.nbt.*;
-import net.minecraft.util.Identifier;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class BackpackPreview {
- private static final Logger LOGGER = LoggerFactory.getLogger(BackpackPreview.class);
- private static final Identifier TEXTURE = new Identifier(SkyblockerMod.NAMESPACE, "textures/gui/inventory_background.png");
- private static final Pattern ECHEST_PATTERN = Pattern.compile("Ender Chest.*\\((\\d+)/\\d+\\)");
- private static final Pattern BACKPACK_PATTERN = Pattern.compile("Backpack.*\\(Slot #(\\d+)\\)");
- private static final int STORAGE_SIZE = 27;
-
- private static final Inventory[] storage = new Inventory[STORAGE_SIZE];
- private static final boolean[] dirty = new boolean[STORAGE_SIZE];
-
- private static String loaded = ""; // uuid + sb profile currently loaded
- private static Path save_dir = null;
-
- public static void init() {
- ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> {
- if (screen instanceof HandledScreen<?> handledScreen) {
- updateStorage(handledScreen);
- }
- });
- }
-
- public static void tick() {
- Utils.update(); // force update isOnSkyblock to prevent crash on disconnect
- if (Utils.isOnSkyblock()) {
- // save all dirty storages
- saveStorage();
- // update save dir based on uuid and sb profile
- String uuid = MinecraftClient.getInstance().getSession().getUuidOrNull().toString().replaceAll("-", "");
- String profile = Utils.getProfile();
- if (profile != null && !profile.isEmpty()) {
- save_dir = FabricLoader.getInstance().getConfigDir().resolve("skyblocker/backpack-preview/" + uuid + "/" + profile);
- save_dir.toFile().mkdirs();
- if (loaded.equals(uuid + "/" + profile)) {
- // mark currently opened storage as dirty
- if (MinecraftClient.getInstance().currentScreen != null) {
- String title = MinecraftClient.getInstance().currentScreen.getTitle().getString();
- int index = getStorageIndexFromTitle(title);
- if (index != -1) dirty[index] = true;
- }
- } else {
- // load storage again because uuid/profile changed
- loaded = uuid + "/" + profile;
- loadStorage();
- }
- }
- }
- }
-
- public static void loadStorage() {
- assert (save_dir != null);
- for (int index = 0; index < STORAGE_SIZE; ++index) {
- storage[index] = null;
- dirty[index] = false;
- File file = save_dir.resolve(index + ".nbt").toFile();
- if (file.isFile()) {
- try {
- NbtCompound root = NbtIo.read(file);
- storage[index] = new DummyInventory(root);
- } catch (Exception e) {
- LOGGER.error("Failed to load backpack preview file: " + file.getName(), e);
- }
- }
- }
- }
-
- private static void saveStorage() {
- assert (save_dir != null);
- for (int index = 0; index < STORAGE_SIZE; ++index) {
- if (dirty[index]) {
- if (storage[index] != null) {
- try {
- NbtCompound root = new NbtCompound();
- NbtList list = new NbtList();
- for (int i = 9; i < storage[index].size(); ++i) {
- ItemStack stack = storage[index].getStack(i);
- NbtCompound item = new NbtCompound();
- if (stack.isEmpty()) {
- item.put("id", NbtString.of("minecraft:air"));
- item.put("Count", NbtInt.of(1));
- } else {
- item.put("id", NbtString.of(stack.getItem().toString()));
- item.put("Count", NbtInt.of(stack.getCount()));
- item.put("tag", stack.getNbt());
- }
- list.add(item);
- }
- root.put("list", list);
- root.put("size", NbtInt.of(storage[index].size() - 9));
- NbtIo.write(root, save_dir.resolve(index + ".nbt").toFile());
- dirty[index] = false;
- } catch (Exception e) {
- LOGGER.error("Failed to save backpack preview file: " + index + ".nbt", e);
- }
- }
- }
- }
- }
-
- public static void updateStorage(HandledScreen<?> screen) {
- String title = screen.getTitle().getString();
- int index = getStorageIndexFromTitle(title);
- if (index != -1) {
- storage[index] = screen.getScreenHandler().slots.get(0).inventory;
- dirty[index] = true;
- }
- }
-
- public static boolean renderPreview(DrawContext context, int index, int mouseX, int mouseY) {
- if (index >= 9 && index < 18) index -= 9;
- else if (index >= 27 && index < 45) index -= 18;
- else return false;
-
- if (storage[index] == null) return false;
- int rows = (storage[index].size() - 9) / 9;
-
- Screen screen = MinecraftClient.getInstance().currentScreen;
- if (screen == null) return false;
- int x = mouseX + 184 >= screen.width ? mouseX - 188 : mouseX + 8;
- int y = Math.max(0, mouseY - 16);
-
- RenderSystem.disableDepthTest();
- RenderSystem.setShaderTexture(0, TEXTURE);
- context.drawTexture(TEXTURE, x, y, 0, 0, 176, 7);
- for (int i = 0; i < rows; ++i) {
- context.drawTexture(TEXTURE, x, y + i * 18 + 7, 0, 7, 176, 18);
- }
- context.drawTexture(TEXTURE, x, y + rows * 18 + 7, 0, 25, 176, 7);
- RenderSystem.enableDepthTest();
-
- MatrixStack matrices = context.getMatrices();
- TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
- for (int i = 9; i < storage[index].size(); ++i) {
- int itemX = x + (i - 9) % 9 * 18 + 8;
- int itemY = y + (i - 9) / 9 * 18 + 8;
- matrices.push();
- matrices.translate(0, 0, 200);
- context.drawItem(storage[index].getStack(i), itemX, itemY);
- context.drawItemInSlot(textRenderer, storage[index].getStack(i), itemX, itemY);
- matrices.pop();
- }
-
- return true;
- }
-
- private static int getStorageIndexFromTitle(String title) {
- Matcher echest = ECHEST_PATTERN.matcher(title);
- if (echest.find()) return Integer.parseInt(echest.group(1)) - 1;
- Matcher backpack = BACKPACK_PATTERN.matcher(title);
- if (backpack.find()) return Integer.parseInt(backpack.group(1)) + 8;
- return -1;
- }
-}
-
-class DummyInventory implements Inventory {
- private final List<ItemStack> stacks;
-
- public DummyInventory(NbtCompound root) {
- stacks = new ArrayList<>(root.getInt("size") + 9);
- for (int i = 0; i < 9; ++i) stacks.add(ItemStack.EMPTY);
- root.getList("list", NbtCompound.COMPOUND_TYPE).forEach(item ->
- stacks.add(ItemStack.fromNbt((NbtCompound) item))
- );
- }
-
- @Override
- public int size() {
- return stacks.size();
- }
-
- @Override
- public boolean isEmpty() {
- return false;
- }
-
- @Override
- public ItemStack getStack(int slot) {
- return stacks.get(slot);
- }
-
- @Override
- public ItemStack removeStack(int slot, int amount) {
- return null;
- }
-
- @Override
- public ItemStack removeStack(int slot) {
- return null;
- }
-
- @Override
- public void setStack(int slot, ItemStack stack) {
- stacks.set(slot, stack);
- }
-
- @Override
- public void markDirty() {
- }
-
- @Override
- public boolean canPlayerUse(PlayerEntity player) {
- return false;
- }
-
- @Override
- public void clear() {
- }
-}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorDyeColors.java b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorDyeColors.java
index 1496c90f..509f79b7 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorDyeColors.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorDyeColors.java
@@ -4,6 +4,7 @@ import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.Constants;
import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.Utils;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
@@ -34,7 +35,7 @@ public class CustomArmorDyeColors {
ItemStack heldItem = source.getPlayer().getMainHandStack();
if (hex != null && !isHexadecimalColor(hex)) {
- source.sendError(Text.translatable("skyblocker.customDyeColors.invalidHex"));
+ source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.invalidHex")));
return Command.SINGLE_SUCCESS;
}
@@ -49,24 +50,24 @@ public class CustomArmorDyeColors {
if (customDyeColors.containsKey(itemUuid)) {
customDyeColors.removeInt(itemUuid);
SkyblockerConfigManager.save();
- source.sendFeedback(Text.translatable("skyblocker.customDyeColors.removed"));
+ source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.removed")));
} else {
- source.sendFeedback(Text.translatable("skyblocker.customDyeColors.neverHad"));
+ source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.neverHad")));
}
} else {
customDyeColors.put(itemUuid, Integer.decode("0x" + hex.replace("#", "")).intValue());
SkyblockerConfigManager.save();
- source.sendFeedback(Text.translatable("skyblocker.customDyeColors.added"));
+ source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.added")));
}
} else {
- source.sendError(Text.translatable("skyblocker.customDyeColors.noItemUuid"));
+ source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.noItemUuid")));
}
} else {
- source.sendError(Text.translatable("skyblocker.customDyeColors.notDyeable"));
+ source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.notDyeable")));
return Command.SINGLE_SUCCESS;
}
} else {
- source.sendError(Text.translatable("skyblocker.customDyeColors.unableToSetColor"));
+ source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.unableToSetColor")));
}
return Command.SINGLE_SUCCESS;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorTrims.java b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorTrims.java
index 9242d47b..cec84b38 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorTrims.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorTrims.java
@@ -3,8 +3,12 @@ package de.hysky.skyblocker.skyblock.item;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.suggestion.SuggestionProvider;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.events.SkyblockEvents;
+import de.hysky.skyblocker.utils.Constants;
import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.Utils;
import dev.isxander.yacl3.config.v2.api.SerialEntry;
@@ -105,38 +109,43 @@ public class CustomArmorTrims {
if (customArmorTrims.containsKey(itemUuid)) {
customArmorTrims.remove(itemUuid);
SkyblockerConfigManager.save();
- source.sendFeedback(Text.translatable("skyblocker.customArmorTrims.removed"));
+ source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.removed")));
} else {
- source.sendFeedback(Text.translatable("skyblocker.customArmorTrims.neverHad"));
+ source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.neverHad")));
}
} else {
// Ensure that the material & trim are valid
ArmorTrimId trimId = new ArmorTrimId(material, pattern);
if (TRIMS_CACHE.get(trimId) == null) {
- source.sendError(Text.translatable("skyblocker.customArmorTrims.invalidMaterialOrPattern"));
+ source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.invalidMaterialOrPattern")));
return Command.SINGLE_SUCCESS;
}
customArmorTrims.put(itemUuid, trimId);
SkyblockerConfigManager.save();
- source.sendFeedback(Text.translatable("skyblocker.customArmorTrims.added"));
+ source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.added")));
}
} else {
- source.sendError(Text.translatable("skyblocker.customArmorTrims.noItemUuid"));
+ source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.noItemUuid")));
}
} else {
- source.sendError(Text.translatable("skyblocker.customArmorTrims.notAnArmorPiece"));
+ source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.notAnArmorPiece")));
return Command.SINGLE_SUCCESS;
}
} else {
- source.sendError(Text.translatable("skyblocker.customArmorTrims.unableToSetTrim"));
+ source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.unableToSetTrim")));
}
return Command.SINGLE_SUCCESS;
}
public record ArmorTrimId(@SerialEntry Identifier material, @SerialEntry Identifier pattern) implements Pair<Identifier, Identifier> {
+ public static final Codec<ArmorTrimId> CODEC = RecordCodecBuilder.create(instance -> instance.group(
+ Identifier.CODEC.fieldOf("material").forGetter(ArmorTrimId::material),
+ Identifier.CODEC.fieldOf("pattern").forGetter(ArmorTrimId::pattern))
+ .apply(instance, ArmorTrimId::new));
+
@Override
public Identifier left() {
return material();
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomItemNames.java b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomItemNames.java
index b6213eb6..e47444cf 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomItemNames.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomItemNames.java
@@ -3,6 +3,7 @@ package de.hysky.skyblocker.skyblock.item;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.Constants;
import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.Utils;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
@@ -42,9 +43,9 @@ public class CustomItemNames {
//Remove custom item name when the text argument isn't passed
customItemNames.remove(itemUuid);
SkyblockerConfigManager.save();
- source.sendFeedback(Text.translatable("skyblocker.customItemNames.removed"));
+ source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customItemNames.removed")));
} else {
- source.sendFeedback(Text.translatable("skyblocker.customItemNames.neverHad"));
+ source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customItemNames.neverHad")));
}
} else {
//If the text is provided then set the item's custom name to it
@@ -55,13 +56,13 @@ public class CustomItemNames {
customItemNames.put(itemUuid, text);
SkyblockerConfigManager.save();
- source.sendFeedback(Text.translatable("skyblocker.customItemNames.added"));
+ source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customItemNames.added")));
}
} else {
- source.sendError(Text.translatable("skyblocker.customItemNames.noItemUuid"));
+ source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customItemNames.noItemUuid")));
}
} else {
- source.sendError(Text.translatable("skyblocker.customItemNames.unableToSetName"));
+ source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customItemNames.unableToSetName")));
}
return Command.SINGLE_SUCCESS;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/ItemProtection.java b/src/main/java/de/hysky/skyblocker/skyblock/item/ItemProtection.java
index ff88ef8d..2d929c28 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/ItemProtection.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/ItemProtection.java
@@ -3,6 +3,7 @@ package de.hysky.skyblocker.skyblock.item;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.Constants;
import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.Utils;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
@@ -44,18 +45,18 @@ public class ItemProtection {
protectedItems.add(itemUuid);
SkyblockerConfigManager.save();
- source.sendFeedback(Text.translatable("skyblocker.itemProtection.added", heldItem.getName()));
+ source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.itemProtection.added", heldItem.getName())));
} else {
protectedItems.remove(itemUuid);
SkyblockerConfigManager.save();
- source.sendFeedback(Text.translatable("skyblocker.itemProtection.removed", heldItem.getName()));
+ source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.itemProtection.removed", heldItem.getName())));
}
} else {
- source.sendFeedback(Text.translatable("skyblocker.itemProtection.noItemUuid"));
+ source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.itemProtection.noItemUuid")));
}
} else {
- source.sendFeedback(Text.translatable("skyblocker.itemProtection.unableToProtect"));
+ source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.itemProtection.unableToProtect")));
}
return Command.SINGLE_SUCCESS;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java b/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java
new file mode 100644
index 00000000..ac9b1bf0
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java
@@ -0,0 +1,144 @@
+package de.hysky.skyblocker.skyblock.item;
+
+import java.io.ByteArrayInputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.util.Base64;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.reflect.TypeToken;
+import com.mojang.util.UndashedUuid;
+
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.utils.Http;
+import de.hysky.skyblocker.utils.Http.ApiResponse;
+import de.hysky.skyblocker.utils.Utils;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.nbt.NbtCompound;
+import net.minecraft.nbt.NbtElement;
+import net.minecraft.nbt.NbtIo;
+import net.minecraft.nbt.NbtList;
+import net.minecraft.util.Util;
+
+public class MuseumItemCache {
+ private static final Logger LOGGER = LoggerFactory.getLogger(MuseumItemCache.class);
+ private static final Path CACHE_FILE = SkyblockerMod.CONFIG_DIR.resolve("museum_item_cache.json");
+ private static final Object2ObjectOpenHashMap<String, Object2ObjectOpenHashMap<String, ProfileMuseumData>> MUSEUM_ITEM_CACHE = new Object2ObjectOpenHashMap<>();
+ private static final Type MAP_TYPE = new TypeToken<Object2ObjectOpenHashMap<String, Object2ObjectOpenHashMap<String, ProfileMuseumData>>>() {}.getType();
+
+ private static CompletableFuture<Void> loaded;
+
+ public static void init() {
+ ClientLifecycleEvents.CLIENT_STARTED.register(MuseumItemCache::load);
+ }
+
+ private static void load(MinecraftClient client) {
+ loaded = CompletableFuture.runAsync(() -> {
+ try (BufferedReader reader = Files.newBufferedReader(CACHE_FILE)) {
+ Object2ObjectOpenHashMap<String, Object2ObjectOpenHashMap<String, ProfileMuseumData>> cachedData = SkyblockerMod.GSON.fromJson(reader, MAP_TYPE);
+
+ MUSEUM_ITEM_CACHE.putAll(cachedData);
+ LOGGER.info("[Skyblocker] Loaded museum items cache");
+ } catch (NoSuchFileException ignored) {
+ } catch (IOException e) {
+ LOGGER.error("[Skyblocker] Failed to load cached museum items", e);
+ }
+ });
+ }
+
+ private static void save() {
+ CompletableFuture.runAsync(() -> {
+ try (BufferedWriter writer = Files.newBufferedWriter(CACHE_FILE)) {
+ SkyblockerMod.GSON.toJson(MUSEUM_ITEM_CACHE, writer);
+ } catch (IOException e) {
+ LOGGER.error("[Skyblocker] Failed to save cached museum items!", e);
+ }
+ });
+ }
+
+ private static void updateData4ProfileMember(String uuid, String profileId) {
+ CompletableFuture.runAsync(() -> {
+ try (ApiResponse response = Http.sendHypixelRequest("skyblock/museum", "?profile=" + profileId)) {
+ //The request was successful
+ if (response.ok()) {
+ JsonObject profileData = JsonParser.parseString(response.content()).getAsJsonObject();
+ JsonObject memberData = profileData.get("members").getAsJsonObject().get(uuid).getAsJsonObject();
+
+ //We call them sets because it could either be a singular item or an entire armour set
+ Map<String, JsonElement> donatedSets = memberData.get("items").getAsJsonObject().asMap();
+
+ //Set of all found item ids on profile
+ ObjectOpenHashSet<String> itemIds = new ObjectOpenHashSet<>();
+
+ for (Map.Entry<String, JsonElement> donatedSet : donatedSets.entrySet()) {
+ //Item is plural here because the nbt is a list
+ String itemsData = donatedSet.getValue().getAsJsonObject().get("items").getAsJsonObject().get("data").getAsString();
+ NbtList items = NbtIo.readCompressed(new ByteArrayInputStream(Base64.getDecoder().decode(itemsData))).getList("i", NbtElement.COMPOUND_TYPE);
+
+ for (int i = 0; i < items.size(); i++) {
+ NbtCompound tag = items.getCompound(i).getCompound("tag");
+
+ if (tag.contains("ExtraAttributes")) {
+ NbtCompound extraAttributes = tag.getCompound("ExtraAttributes");
+
+ if (extraAttributes.contains("id")) itemIds.add(extraAttributes.getString("id"));
+ }
+ }
+ }
+
+ MUSEUM_ITEM_CACHE.get(uuid).put(profileId, new ProfileMuseumData(System.currentTimeMillis(), itemIds));
+ save();
+
+ LOGGER.info("[Skyblocker] Successfully updated museum item cache for profile {}", profileId);
+ }
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker] Failed to refresh museum item data for profile {}", profileId, e);
+ }
+ });
+ }
+
+ /**
+ * The cache is ticked upon switching skyblock servers
+ */
+ public static void tick(String profileId) {
+ if (loaded.isDone()) {
+ String uuid = UndashedUuid.toString(MinecraftClient.getInstance().getSession().getUuidOrNull());
+ Object2ObjectOpenHashMap<String, ProfileMuseumData> playerData = MUSEUM_ITEM_CACHE.computeIfAbsent(uuid, uuid1 -> Util.make(new Object2ObjectOpenHashMap<>(), map -> {
+ map.put(profileId, ProfileMuseumData.EMPTY);
+ }));
+
+ if (playerData.get(profileId).stale()) updateData4ProfileMember(uuid, profileId);
+ }
+ }
+
+ public static boolean hasItemInMuseum(String id) {
+ String uuid = UndashedUuid.toString(MinecraftClient.getInstance().getSession().getUuidOrNull());
+ ObjectOpenHashSet<String> collectedItemIds = MUSEUM_ITEM_CACHE.get(uuid).get(Utils.getProfileId()).collectedItemIds();
+
+ return collectedItemIds != null && collectedItemIds.contains(id);
+ }
+
+ private record ProfileMuseumData(long lastUpdated, ObjectOpenHashSet<String> collectedItemIds) {
+ private static final ProfileMuseumData EMPTY = new ProfileMuseumData(0L, null);
+ private static final long MAX_AGE = 86_400_000;
+
+ private boolean stale() {
+ return System.currentTimeMillis() > lastUpdated + MAX_AGE;
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/WikiLookup.java b/src/main/java/de/hysky/skyblocker/skyblock/item/WikiLookup.java
index d4e6a0df..38121ea3 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/WikiLookup.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/WikiLookup.java
@@ -1,23 +1,25 @@
package de.hysky.skyblocker.skyblock.item;
-import de.hysky.skyblocker.skyblock.itemlist.ItemRegistry;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.itemlist.ItemRepository;
import de.hysky.skyblocker.utils.ItemUtils;
-import de.hysky.skyblocker.utils.Utils;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
-import net.minecraft.client.MinecraftClient;
import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.util.InputUtil;
+import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.screen.slot.Slot;
import net.minecraft.text.Text;
import net.minecraft.util.Util;
import org.lwjgl.glfw.GLFW;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.util.concurrent.CompletableFuture;
public class WikiLookup {
+ private static final Logger LOGGER = LoggerFactory.getLogger(WikiLookup.class);
public static KeyBinding wikiLookup;
- static final MinecraftClient client = MinecraftClient.getInstance();
- static String id;
+ private static String id;
public static void init() {
wikiLookup = KeyBindingHelper.registerKeyBinding(new KeyBinding(
@@ -28,22 +30,22 @@ public class WikiLookup {
));
}
- public static String getSkyblockId(Slot slot) {
+ public static void getSkyblockId(Slot slot) {
//Grabbing the skyblock NBT data
ItemUtils.getItemIdOptional(slot.getStack()).ifPresent(newId -> id = newId);
- return id;
}
- public static void openWiki(Slot slot) {
- if (Utils.isOnSkyblock()) {
- id = getSkyblockId(slot);
+ public static void openWiki(Slot slot, PlayerEntity player) {
+ if (SkyblockerConfigManager.get().general.wikiLookup.enableWikiLookup) {
+ getSkyblockId(slot);
try {
- String wikiLink = ItemRegistry.getWikiLink(id);
+ String wikiLink = ItemRepository.getWikiLink(id, player);
CompletableFuture.runAsync(() -> Util.getOperatingSystem().open(wikiLink));
} catch (IndexOutOfBoundsException | IllegalStateException e) {
- e.printStackTrace();
- if (client.player != null)
- client.player.sendMessage(Text.of("Error while retrieving wiki article..."), false);
+ LOGGER.error("[Skyblocker] Error while retrieving wiki article...", e);
+ if (player != null) {
+ player.sendMessage(Text.of("[Skyblocker] Error while retrieving wiki article, see logs..."), false);
+ }
}
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/BackpackPreview.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/BackpackPreview.java
new file mode 100644
index 00000000..5627b56d
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/BackpackPreview.java
@@ -0,0 +1,204 @@
+package de.hysky.skyblocker.skyblock.item.tooltip;
+
+import com.mojang.blaze3d.systems.RenderSystem;
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.item.ItemRarityBackgrounds;
+import de.hysky.skyblocker.utils.Utils;
+import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.screen.ingame.HandledScreen;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.inventory.Inventory;
+import net.minecraft.inventory.SimpleInventory;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NbtCompound;
+import net.minecraft.nbt.NbtInt;
+import net.minecraft.nbt.NbtIo;
+import net.minecraft.nbt.NbtList;
+import net.minecraft.util.Identifier;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class BackpackPreview {
+ private static final Logger LOGGER = LoggerFactory.getLogger(BackpackPreview.class);
+ private static final Identifier TEXTURE = new Identifier("textures/gui/container/generic_54.png");
+ private static final Pattern ECHEST_PATTERN = Pattern.compile("Ender Chest.*\\((\\d+)/\\d+\\)");
+ private static final Pattern BACKPACK_PATTERN = Pattern.compile("Backpack.*\\(Slot #(\\d+)\\)");
+ private static final int STORAGE_SIZE = 27;
+
+ private static final Storage[] storages = new Storage[STORAGE_SIZE];
+
+ /**
+ * The profile id of the currently loaded backpack preview.
+ */
+ private static String loaded;
+ private static Path saveDir;
+
+ public static void init() {
+ ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> {
+ if (screen instanceof HandledScreen<?> handledScreen) {
+ ScreenEvents.remove(screen).register(screen1 -> updateStorage(handledScreen));
+ }
+ });
+ }
+
+ public static void tick() {
+ Utils.update(); // force update isOnSkyblock to prevent crash on disconnect
+ if (Utils.isOnSkyblock()) {
+ // save all dirty storages
+ saveStorages();
+ // update save dir based on sb profile id
+ String id = MinecraftClient.getInstance().getSession().getUuidOrNull().toString().replaceAll("-", "") + "/" + Utils.getProfileId();
+ if (!id.equals(loaded)) {
+ saveDir = SkyblockerMod.CONFIG_DIR.resolve("backpack-preview/" + id);
+ //noinspection ResultOfMethodCallIgnored
+ saveDir.toFile().mkdirs();
+ // load storage again because profile id changed
+ loaded = id;
+ loadStorages();
+ }
+ }
+ }
+
+ private static void loadStorages() {
+ for (int index = 0; index < STORAGE_SIZE; ++index) {
+ storages[index] = null;
+ File storageFile = saveDir.resolve(index + ".nbt").toFile();
+ if (storageFile.isFile()) {
+ try {
+ storages[index] = Storage.fromNbt(Objects.requireNonNull(NbtIo.read(storageFile)));
+ } catch (Exception e) {
+ LOGGER.error("Failed to load backpack preview file: " + storageFile.getName(), e);
+ }
+ }
+ }
+ }
+
+ private static void saveStorages() {
+ for (int index = 0; index < STORAGE_SIZE; ++index) {
+ if (storages[index] != null && storages[index].dirty) {
+ saveStorage(index);
+ }
+ }
+ }
+
+ private static void saveStorage(int index) {
+ try {
+ NbtIo.write(storages[index].toNbt(), saveDir.resolve(index + ".nbt").toFile());
+ storages[index].markClean();
+ } catch (Exception e) {
+ LOGGER.error("Failed to save backpack preview file: " + index + ".nbt", e);
+ }
+ }
+
+ private static void updateStorage(HandledScreen<?> handledScreen) {
+ String title = handledScreen.getTitle().getString();
+ int index = getStorageIndexFromTitle(title);
+ if (index != -1) {
+ storages[index] = new Storage(handledScreen.getScreenHandler().slots.get(0).inventory, title, true);
+ }
+ }
+
+ public static boolean renderPreview(DrawContext context, Screen screen, int index, int mouseX, int mouseY) {
+ if (index >= 9 && index < 18) index -= 9;
+ else if (index >= 27 && index < 45) index -= 18;
+ else return false;
+
+ if (storages[index] == null) return false;
+ int rows = (storages[index].size() - 9) / 9;
+
+ int x = mouseX + 184 >= screen.width ? mouseX - 188 : mouseX + 8;
+ int y = Math.max(0, mouseY - 16);
+
+ MatrixStack matrices = context.getMatrices();
+ matrices.push();
+ matrices.translate(0f, 0f, 400f);
+
+ RenderSystem.enableDepthTest();
+ context.drawTexture(TEXTURE, x, y, 0, 0, 176, rows * 18 + 17);
+ context.drawTexture(TEXTURE, x, y + rows * 18 + 17, 0, 215, 176, 7);
+
+ TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+ context.drawText(textRenderer, storages[index].name, x + 8, y + 6, 0x404040, false);
+
+ matrices.translate(0f, 0f, 200f);
+ for (int i = 9; i < storages[index].size(); ++i) {
+ ItemStack currentStack = storages[index].getStack(i);
+ int itemX = x + (i - 9) % 9 * 18 + 8;
+ int itemY = y + (i - 9) / 9 * 18 + 18;
+
+ if (SkyblockerConfigManager.get().general.itemInfoDisplay.itemRarityBackgrounds) {
+ ItemRarityBackgrounds.tryDraw(currentStack, context, itemX, itemY);
+ }
+
+ context.drawItem(currentStack, itemX, itemY);
+ context.drawItemInSlot(textRenderer, currentStack, itemX, itemY);
+ }
+
+ matrices.pop();
+
+ return true;
+ }
+
+ private static int getStorageIndexFromTitle(String title) {
+ Matcher echest = ECHEST_PATTERN.matcher(title);
+ if (echest.find()) return Integer.parseInt(echest.group(1)) - 1;
+ Matcher backpack = BACKPACK_PATTERN.matcher(title);
+ if (backpack.find()) return Integer.parseInt(backpack.group(1)) + 8;
+ return -1;
+ }
+
+ static class Storage {
+ private final Inventory inventory;
+ private final String name;
+ private boolean dirty;
+
+ private Storage(Inventory inventory, String name, boolean dirty) {
+ this.inventory = inventory;
+ this.name = name;
+ this.dirty = dirty;
+ }
+
+ private int size() {
+ return inventory.size();
+ }
+
+ private ItemStack getStack(int index) {
+ return inventory.getStack(index);
+ }
+
+ private void markClean() {
+ dirty = false;
+ }
+
+ @NotNull
+ private static Storage fromNbt(NbtCompound root) {
+ SimpleInventory inventory = new SimpleInventory(root.getList("list", NbtCompound.COMPOUND_TYPE).stream().map(NbtCompound.class::cast).map(ItemStack::fromNbt).toArray(ItemStack[]::new));
+ return new Storage(inventory, root.getString("name"), false);
+ }
+
+ @NotNull
+ private NbtCompound toNbt() {
+ NbtCompound root = new NbtCompound();
+ NbtList list = new NbtList();
+ for (int i = 0; i < size(); ++i) {
+ list.add(getStack(i).writeNbt(new NbtCompound()));
+ }
+ root.put("list", list);
+ root.put("size", NbtInt.of(size()));
+ root.putString("name", name);
+ return root;
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/CompactorDeletorPreview.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorDeletorPreview.java
index 7f5b96b9..9cf0356b 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/CompactorDeletorPreview.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorDeletorPreview.java
@@ -1,7 +1,7 @@
-package de.hysky.skyblocker.skyblock.item;
+package de.hysky.skyblocker.skyblock.item.tooltip;
import de.hysky.skyblocker.mixin.accessor.DrawContextInvoker;
-import de.hysky.skyblocker.skyblock.itemlist.ItemRegistry;
+import de.hysky.skyblocker.skyblock.itemlist.ItemRepository;
import de.hysky.skyblocker.utils.ItemUtils;
import it.unimi.dsi.fastutil.ints.IntIntPair;
import it.unimi.dsi.fastutil.ints.IntObjectPair;
@@ -43,7 +43,7 @@ public class CompactorDeletorPreview {
NbtCompound extraAttributes = ItemUtils.getExtraAttributes(stack);
if (extraAttributes == null) return false;
// Get the slots and their items from the nbt, which is in the format personal_compact_<slot_number> or personal_deletor_<slot_number>
- List<IntObjectPair<ItemStack>> slots = extraAttributes.getKeys().stream().filter(slot -> slot.contains(type.toLowerCase().substring(0, 7))).map(slot -> IntObjectPair.of(Integer.parseInt(slot.substring(17)), ItemRegistry.getItemStack(extraAttributes.getString(slot)))).toList();
+ List<IntObjectPair<ItemStack>> slots = extraAttributes.getKeys().stream().filter(slot -> slot.contains(type.toLowerCase().substring(0, 7))).map(slot -> IntObjectPair.of(Integer.parseInt(slot.substring(17)), ItemRepository.getItemStack(extraAttributes.getString(slot)))).toList();
List<TooltipComponent> components = tooltips.stream().map(Text::asOrderedText).map(TooltipComponent::of).collect(Collectors.toList());
IntIntPair dimensions = DIMENSIONS.getOrDefault(size, DEFAULT_DIMENSION);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/CompactorPreviewTooltipComponent.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorPreviewTooltipComponent.java
index 513d7d72..22498c02 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/CompactorPreviewTooltipComponent.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/CompactorPreviewTooltipComponent.java
@@ -1,4 +1,4 @@
-package de.hysky.skyblocker.skyblock.item;
+package de.hysky.skyblocker.skyblock.item.tooltip;
import de.hysky.skyblocker.SkyblockerMod;
import it.unimi.dsi.fastutil.ints.IntIntPair;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ExoticTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ExoticTooltip.java
new file mode 100644
index 00000000..66d94890
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ExoticTooltip.java
@@ -0,0 +1,96 @@
+package de.hysky.skyblocker.skyblock.item.tooltip;
+
+import de.hysky.skyblocker.utils.Constants;
+import net.minecraft.nbt.NbtCompound;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.StringIdentifiable;
+
+public class ExoticTooltip {
+ public static String getExpectedHex(String id) {
+ String color = TooltipInfoType.COLOR.getData().get(id).getAsString();
+ if (color != null) {
+ String[] RGBValues = color.split(",");
+ return String.format("%02X%02X%02X", Integer.parseInt(RGBValues[0]), Integer.parseInt(RGBValues[1]), Integer.parseInt(RGBValues[2]));
+ } else {
+ ItemTooltip.LOGGER.warn("[Skyblocker Exotics] No expected color data found for id {}", id);
+ return null;
+ }
+ }
+
+ public static boolean isException(String id, String hex) {
+ if (id.startsWith("LEATHER") || id.equals("GHOST_BOOTS") || Constants.SEYMOUR_IDS.contains(id)) {
+ return true;
+ }
+ if (id.startsWith("RANCHER")) {
+ return Constants.RANCHERS.contains(hex);
+ }
+ if (id.contains("ADAPTIVE_CHESTPLATE")) {
+ return Constants.ADAPTIVE_CHEST.contains(hex);
+ } else if (id.contains("ADAPTIVE")) {
+ return Constants.ADAPTIVE.contains(hex);
+ }
+ if (id.startsWith("REAPER")) {
+ return Constants.REAPER.contains(hex);
+ }
+ if (id.startsWith("FAIRY")) {
+ return Constants.FAIRY_HEXES.contains(hex);
+ }
+ if (id.startsWith("CRYSTAL")) {
+ return Constants.CRYSTAL_HEXES.contains(hex);
+ }
+ if (id.contains("SPOOK")) {
+ return Constants.SPOOK.contains(hex);
+ }
+ return false;
+ }
+
+ public static DyeType checkDyeType(String hex) {
+ if (Constants.CRYSTAL_HEXES.contains(hex)) {
+ return DyeType.CRYSTAL;
+ }
+ if (Constants.FAIRY_HEXES.contains(hex)) {
+ return DyeType.FAIRY;
+ }
+ if (Constants.OG_FAIRY_HEXES.contains(hex)) {
+ return DyeType.OG_FAIRY;
+ }
+ if (Constants.SPOOK.contains(hex)) {
+ return DyeType.SPOOK;
+ }
+ if (Constants.GLITCHED.contains(hex)) {
+ return DyeType.GLITCHED;
+ }
+ return DyeType.EXOTIC;
+ }
+
+ public static boolean intendedDyed(NbtCompound ItemData) {
+ return ItemData.getCompound("ExtraAttributes").contains("dye_item");
+ }
+
+ public enum DyeType implements StringIdentifiable {
+ CRYSTAL("crystal", Formatting.AQUA),
+ FAIRY("fairy", Formatting.LIGHT_PURPLE),
+ OG_FAIRY("og_fairy", Formatting.DARK_PURPLE),
+ SPOOK("spook", Formatting.RED),
+ GLITCHED("glitched", Formatting.BLUE),
+ EXOTIC("exotic", Formatting.GOLD);
+ private final String name;
+ private final Formatting formatting;
+
+ DyeType(String name, Formatting formatting) {
+ this.name = name;
+ this.formatting = formatting;
+ }
+
+ @Override
+ public String asString() {
+ return name;
+ }
+
+ public MutableText getTranslatedText() {
+ return Text.translatable("skyblocker.exotic." + name).formatted(formatting);
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/PriceInfoTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java
index 0f84deea..e050aff5 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/PriceInfoTooltip.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java
@@ -1,10 +1,10 @@
-package de.hysky.skyblocker.skyblock.item;
+package de.hysky.skyblocker.skyblock.item.tooltip;
-import com.google.gson.Gson;
import com.google.gson.JsonObject;
+import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.config.SkyblockerConfig;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
-import de.hysky.skyblocker.utils.Http;
+import de.hysky.skyblocker.utils.Constants;
import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.scheduler.Scheduler;
@@ -12,36 +12,25 @@ import net.minecraft.client.MinecraftClient;
import net.minecraft.client.item.TooltipContext;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
+import net.minecraft.nbt.NbtElement;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.net.http.HttpHeaders;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;
-public class PriceInfoTooltip {
- private static final Logger LOGGER = LoggerFactory.getLogger(PriceInfoTooltip.class.getName());
+public class ItemTooltip {
+ protected static final Logger LOGGER = LoggerFactory.getLogger(ItemTooltip.class.getName());
private static final MinecraftClient client = MinecraftClient.getInstance();
- private static JsonObject npcPricesJson;
- private static JsonObject bazaarPricesJson;
- private static JsonObject oneDayAvgPricesJson;
- private static JsonObject threeDayAvgPricesJson;
- private static JsonObject lowestPricesJson;
- private static JsonObject isMuseumJson;
- private static JsonObject motesPricesJson;
- private static volatile boolean nullMsgSend = false;
- private final static Gson gson = new Gson();
- private static final Map<String, String> apiAddresses;
- private static long npcHash = 0;
- private static long museumHash = 0;
- private static long motesHash = 0;
-
- public static void onInjectTooltip(ItemStack stack, TooltipContext context, List<Text> lines) {
+ protected static final SkyblockerConfig.ItemTooltip config = SkyblockerConfigManager.get().general.itemTooltip;
+ private static volatile boolean sentNullWarning = false;
+
+ public static void getTooltip(ItemStack stack, TooltipContext context, List<Text> lines) {
if (!Utils.isOnSkyblock() || client.player == null) return;
String name = getInternalNameFromNBT(stack, false);
@@ -54,65 +43,47 @@ public class PriceInfoTooltip {
neuName = internalID;
}
+ if (lines.isEmpty()) {
+ return;
+ }
+
int count = stack.getCount();
boolean bazaarOpened = lines.stream().anyMatch(each -> each.getString().contains("Buy price:") || each.getString().contains("Sell price:"));
- if (SkyblockerConfigManager.get().general.itemTooltip.enableNPCPrice) {
- if (npcPricesJson == null) {
- nullWarning();
- } else if (npcPricesJson.has(internalID)) {
- lines.add(Text.literal(String.format("%-21s", "NPC Price:"))
- .formatted(Formatting.YELLOW)
- .append(getCoinsMessage(npcPricesJson.get(internalID).getAsDouble(), count)));
- }
- }
-
- if (SkyblockerConfigManager.get().general.itemTooltip.enableMotesPrice && Utils.isInTheRift()) {
- if (motesPricesJson == null) {
- nullWarning();
- } else if (motesPricesJson.has(internalID)) {
- lines.add(Text.literal(String.format("%-20s", "Motes Price:"))
- .formatted(Formatting.LIGHT_PURPLE)
- .append(getMotesMessage(motesPricesJson.get(internalID).getAsInt(), count)));
- }
+ if (TooltipInfoType.NPC.isTooltipEnabledAndHasOrNullWarning(internalID)) {
+ lines.add(Text.literal(String.format("%-21s", "NPC Price:"))
+ .formatted(Formatting.YELLOW)
+ .append(getCoinsMessage(TooltipInfoType.NPC.getData().get(internalID).getAsDouble(), count)));
}
boolean bazaarExist = false;
- if (SkyblockerConfigManager.get().general.itemTooltip.enableBazaarPrice && !bazaarOpened) {
- if (bazaarPricesJson == null) {
- nullWarning();
- } else if (bazaarPricesJson.has(name)) {
- JsonObject getItem = bazaarPricesJson.getAsJsonObject(name);
- lines.add(Text.literal(String.format("%-18s", "Bazaar buy Price:"))
- .formatted(Formatting.GOLD)
- .append(getItem.get("buyPrice").isJsonNull()
- ? Text.literal("No data").formatted(Formatting.RED)
- : getCoinsMessage(getItem.get("buyPrice").getAsDouble(), count)));
- lines.add(Text.literal(String.format("%-19s", "Bazaar sell Price:"))
- .formatted(Formatting.GOLD)
- .append(getItem.get("sellPrice").isJsonNull()
- ? Text.literal("No data").formatted(Formatting.RED)
- : getCoinsMessage(getItem.get("sellPrice").getAsDouble(), count)));
- bazaarExist = true;
- }
+ if (TooltipInfoType.BAZAAR.isTooltipEnabledAndHasOrNullWarning(name) && !bazaarOpened) {
+ JsonObject getItem = TooltipInfoType.BAZAAR.getData().getAsJsonObject(name);
+ lines.add(Text.literal(String.format("%-18s", "Bazaar buy Price:"))
+ .formatted(Formatting.GOLD)
+ .append(getItem.get("buyPrice").isJsonNull()
+ ? Text.literal("No data").formatted(Formatting.RED)
+ : getCoinsMessage(getItem.get("buyPrice").getAsDouble(), count)));
+ lines.add(Text.literal(String.format("%-19s", "Bazaar sell Price:"))
+ .formatted(Formatting.GOLD)
+ .append(getItem.get("sellPrice").isJsonNull()
+ ? Text.literal("No data").formatted(Formatting.RED)
+ : getCoinsMessage(getItem.get("sellPrice").getAsDouble(), count)));
+ bazaarExist = true;
}
// bazaarOpened & bazaarExist check for lbin, because Skytils keeps some bazaar item data in lbin api
boolean lbinExist = false;
- if (SkyblockerConfigManager.get().general.itemTooltip.enableLowestBIN && !bazaarOpened && !bazaarExist) {
- if (lowestPricesJson == null) {
- nullWarning();
- } else if (lowestPricesJson.has(name)) {
- lines.add(Text.literal(String.format("%-19s", "Lowest BIN Price:"))
- .formatted(Formatting.GOLD)
- .append(getCoinsMessage(lowestPricesJson.get(name).getAsDouble(), count)));
- lbinExist = true;
- }
+ if (TooltipInfoType.LOWEST_BINS.isTooltipEnabledAndHasOrNullWarning(name) && !bazaarOpened && !bazaarExist) {
+ lines.add(Text.literal(String.format("%-19s", "Lowest BIN Price:"))
+ .formatted(Formatting.GOLD)
+ .append(getCoinsMessage(TooltipInfoType.LOWEST_BINS.getData().get(name).getAsDouble(), count)));
+ lbinExist = true;
}
if (SkyblockerConfigManager.get().general.itemTooltip.enableAvgBIN) {
- if (threeDayAvgPricesJson == null || oneDayAvgPricesJson == null) {
+ if (TooltipInfoType.ONE_DAY_AVERAGE.getData() == null || TooltipInfoType.THREE_DAY_AVERAGE.getData() == null) {
nullWarning();
} else {
/*
@@ -142,16 +113,16 @@ public class PriceInfoTooltip {
}
if (!neuName.isEmpty() && lbinExist) {
- SkyblockerConfig.Average type = SkyblockerConfigManager.get().general.itemTooltip.avg;
+ SkyblockerConfig.Average type = config.avg;
// "No data" line because of API not keeping old data, it causes NullPointerException
if (type == SkyblockerConfig.Average.ONE_DAY || type == SkyblockerConfig.Average.BOTH) {
lines.add(
Text.literal(String.format("%-19s", "1 Day Avg. Price:"))
.formatted(Formatting.GOLD)
- .append(oneDayAvgPricesJson.get(neuName) == null
+ .append(TooltipInfoType.ONE_DAY_AVERAGE.getData().get(neuName) == null
? Text.literal("No data").formatted(Formatting.RED)
- : getCoinsMessage(oneDayAvgPricesJson.get(neuName).getAsDouble(), count)
+ : getCoinsMessage(TooltipInfoType.ONE_DAY_AVERAGE.getData().get(neuName).getAsDouble(), count)
)
);
}
@@ -159,9 +130,9 @@ public class PriceInfoTooltip {
lines.add(
Text.literal(String.format("%-19s", "3 Day Avg. Price:"))
.formatted(Formatting.GOLD)
- .append(threeDayAvgPricesJson.get(neuName) == null
+ .append(TooltipInfoType.THREE_DAY_AVERAGE.getData().get(neuName) == null
? Text.literal("No data").formatted(Formatting.RED)
- : getCoinsMessage(threeDayAvgPricesJson.get(neuName).getAsDouble(), count)
+ : getCoinsMessage(TooltipInfoType.THREE_DAY_AVERAGE.getData().get(neuName).getAsDouble(), count)
)
);
}
@@ -169,35 +140,68 @@ public class PriceInfoTooltip {
}
}
- if (SkyblockerConfigManager.get().general.itemTooltip.enableMuseumDate && !bazaarOpened) {
- if (isMuseumJson == null) {
- nullWarning();
- } else {
- String timestamp = getTimestamp(stack);
-
- if (isMuseumJson.has(internalID)) {
- String itemCategory = isMuseumJson.get(internalID).getAsString();
- String format = switch (itemCategory) {
- case "Weapons" -> "%-18s";
- case "Armor" -> "%-19s";
- default -> "%-20s";
- };
- lines.add(Text.literal(String.format(format, "Museum: (" + itemCategory + ")"))
- .formatted(Formatting.LIGHT_PURPLE)
- .append(Text.literal(timestamp).formatted(Formatting.RED)));
- } else if (!timestamp.isEmpty()) {
- lines.add(Text.literal(String.format("%-21s", "Obtained: "))
- .formatted(Formatting.LIGHT_PURPLE)
- .append(Text.literal(timestamp).formatted(Formatting.RED)));
+ if (TooltipInfoType.MOTES.isTooltipEnabledAndHasOrNullWarning(internalID)) {
+ lines.add(Text.literal(String.format("%-20s", "Motes Price:"))
+ .formatted(Formatting.LIGHT_PURPLE)
+ .append(getMotesMessage(TooltipInfoType.MOTES.getData().get(internalID).getAsInt(), count)));
+ }
+
+ if (TooltipInfoType.MUSEUM.isTooltipEnabled() && !bazaarOpened) {
+ String timestamp = getTimestamp(stack);
+
+ if (TooltipInfoType.MUSEUM.hasOrNullWarning(internalID)) {
+ String itemCategory = TooltipInfoType.MUSEUM.getData().get(internalID).getAsString();
+ String format = switch (itemCategory) {
+ case "Weapons" -> "%-18s";
+ case "Armor" -> "%-19s";
+ default -> "%-20s";
+ };
+ lines.add(Text.literal(String.format(format, "Museum: (" + itemCategory + ")"))
+ .formatted(Formatting.LIGHT_PURPLE)
+ .append(Text.literal(timestamp).formatted(Formatting.RED)));
+ } else if (!timestamp.isEmpty()) {
+ lines.add(Text.literal(String.format("%-21s", "Obtained: "))
+ .formatted(Formatting.LIGHT_PURPLE)
+ .append(Text.literal(timestamp).formatted(Formatting.RED)));
+ }
+ }
+
+ if (TooltipInfoType.COLOR.isTooltipEnabledAndHasOrNullWarning(internalID) && stack.getNbt() != null) {
+ final NbtElement color = stack.getNbt().getCompound("display").get("color");
+
+ if (color != null) {
+ String colorHex = String.format("%06X", Integer.parseInt(color.asString()));
+ String expectedHex = ExoticTooltip.getExpectedHex(internalID);
+
+ boolean correctLine = false;
+ for (Text text : lines) {
+ String existingTooltip = text.getString() + " ";
+ if (existingTooltip.startsWith("Color: ")) {
+ correctLine = true;
+
+ addExoticTooltip(lines, internalID, stack.getNbt(), colorHex, expectedHex, existingTooltip);
+ break;
+ }
+ }
+
+ if (!correctLine) {
+ addExoticTooltip(lines, internalID, stack.getNbt(), colorHex, expectedHex, "");
}
}
}
}
- private static void nullWarning() {
- if (!nullMsgSend && client.player != null) {
- client.player.sendMessage(Text.translatable("skyblocker.itemTooltip.nullMessage"), false);
- nullMsgSend = true;
+ private static void addExoticTooltip(List<Text> lines, String internalID, NbtCompound nbt, String colorHex, String expectedHex, String existingTooltip) {
+ if (expectedHex != null && !colorHex.equalsIgnoreCase(expectedHex) && !ExoticTooltip.isException(internalID, colorHex) && !ExoticTooltip.intendedDyed(nbt)) {
+ final ExoticTooltip.DyeType type = ExoticTooltip.checkDyeType(colorHex);
+ lines.add(1, Text.literal(existingTooltip + Formatting.DARK_GRAY + "(").append(type.getTranslatedText()).append(Formatting.DARK_GRAY + ")"));
+ }
+ }
+
+ public static void nullWarning() {
+ if (!sentNullWarning && client.player != null) {
+ client.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.itemTooltip.nullMessage")), false);
+ sentNullWarning = true;
}
}
@@ -233,6 +237,7 @@ public class PriceInfoTooltip {
return "";
}
+ // TODO What in the world is this?
public static String getInternalNameFromNBT(ItemStack stack, boolean internalIDOnly) {
NbtCompound ea = ItemUtils.getExtraAttributes(stack);
@@ -261,7 +266,7 @@ public class PriceInfoTooltip {
}
case "PET" -> {
if (ea.contains("petInfo")) {
- JsonObject petInfo = gson.fromJson(ea.getString("petInfo"), JsonObject.class);
+ JsonObject petInfo = SkyblockerMod.GSON.fromJson(ea.getString("petInfo"), JsonObject.class);
return "LVL_1_" + petInfo.get("tier").getAsString() + "_" + petInfo.get("type").getAsString();
}
}
@@ -339,86 +344,36 @@ public class PriceInfoTooltip {
public static void init() {
Scheduler.INSTANCE.scheduleCyclic(() -> {
if (!Utils.isOnSkyblock() && 0 < minute++) {
- nullMsgSend = false;
+ sentNullWarning = false;
return;
}
List<CompletableFuture<Void>> futureList = new ArrayList<>();
- if (SkyblockerConfigManager.get().general.itemTooltip.enableAvgBIN) {
- SkyblockerConfig.Average type = SkyblockerConfigManager.get().general.itemTooltip.avg;
-
- if (type == SkyblockerConfig.Average.BOTH || oneDayAvgPricesJson == null || threeDayAvgPricesJson == null || minute % 5 == 0) {
- futureList.add(CompletableFuture.runAsync(() -> {
- oneDayAvgPricesJson = downloadPrices("1 day avg");
- threeDayAvgPricesJson = downloadPrices("3 day avg");
- }));
+
+ TooltipInfoType.NPC.downloadIfEnabled(futureList);
+ TooltipInfoType.BAZAAR.downloadIfEnabled(futureList);
+ TooltipInfoType.LOWEST_BINS.downloadIfEnabled(futureList);
+
+ if (config.enableAvgBIN) {
+ SkyblockerConfig.Average type = config.avg;
+
+ if (type == SkyblockerConfig.Average.BOTH || TooltipInfoType.ONE_DAY_AVERAGE.getData() == null || TooltipInfoType.THREE_DAY_AVERAGE.getData() == null || minute % 5 == 0) {
+ TooltipInfoType.ONE_DAY_AVERAGE.download(futureList);
+ TooltipInfoType.THREE_DAY_AVERAGE.download(futureList);
} else if (type == SkyblockerConfig.Average.ONE_DAY) {
- futureList.add(CompletableFuture.runAsync(() -> oneDayAvgPricesJson = downloadPrices("1 day avg")));
+ TooltipInfoType.ONE_DAY_AVERAGE.download(futureList);
} else if (type == SkyblockerConfig.Average.THREE_DAY) {
- futureList.add(CompletableFuture.runAsync(() -> threeDayAvgPricesJson = downloadPrices("3 day avg")));
+ TooltipInfoType.THREE_DAY_AVERAGE.download(futureList);
}
}
- if (SkyblockerConfigManager.get().general.itemTooltip.enableLowestBIN || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator)
- futureList.add(CompletableFuture.runAsync(() -> lowestPricesJson = downloadPrices("lowest bins")));
-
- if (SkyblockerConfigManager.get().general.itemTooltip.enableBazaarPrice || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator)
- futureList.add(CompletableFuture.runAsync(() -> bazaarPricesJson = downloadPrices("bazaar")));
-
- if (SkyblockerConfigManager.get().general.itemTooltip.enableNPCPrice && npcPricesJson == null)
- futureList.add(CompletableFuture.runAsync(() -> npcPricesJson = downloadPrices("npc")));
-
- if (SkyblockerConfigManager.get().general.itemTooltip.enableMuseumDate && isMuseumJson == null)
- futureList.add(CompletableFuture.runAsync(() -> isMuseumJson = downloadPrices("museum")));
- if (SkyblockerConfigManager.get().general.itemTooltip.enableMotesPrice && motesPricesJson == null)
- futureList.add(CompletableFuture.runAsync(() -> motesPricesJson = downloadPrices("motes")));
+ TooltipInfoType.MOTES.downloadIfEnabled(futureList);
+ TooltipInfoType.MUSEUM.downloadIfEnabled(futureList);
+ TooltipInfoType.COLOR.downloadIfEnabled(futureList);
minute++;
- CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]))
- .whenComplete((unused, throwable) -> nullMsgSend = false);
+ CompletableFuture.allOf(futureList.toArray(CompletableFuture[]::new))
+ .whenComplete((unused, throwable) -> sentNullWarning = false);
}, 1200, true);
}
-
- private static JsonObject downloadPrices(String type) {
- try {
- String url = apiAddresses.get(type);
-
- if (type.equals("npc") || type.equals("museum") || type.equals("motes")) {
- HttpHeaders headers = Http.sendHeadRequest(url);
- long combinedHash = Http.getEtag(headers).hashCode() + Http.getLastModified(headers).hashCode();
-
- switch (type) {
- case "npc": if (npcHash == combinedHash) return npcPricesJson; else npcHash = combinedHash;
- case "museum": if (museumHash == combinedHash) return isMuseumJson; else museumHash = combinedHash;
- case "motes": if (motesHash == combinedHash) return motesPricesJson; else motesHash = combinedHash;
- }
- }
-
- String apiResponse = Http.sendGetRequest(url);
-
- return new Gson().fromJson(apiResponse, JsonObject.class);
- } catch (Exception e) {
- LOGGER.warn("[Skyblocker] Failed to download " + type + " prices!", e);
- return null;
- }
- }
-
- public static JsonObject getBazaarPrices() {
- return bazaarPricesJson;
- }
-
- public static JsonObject getLBINPrices() {
- return lowestPricesJson;
- }
-
- static {
- apiAddresses = new HashMap<>();
- apiAddresses.put("1 day avg", "https://moulberry.codes/auction_averages_lbin/1day.json");
- apiAddresses.put("3 day avg", "https://moulberry.codes/auction_averages_lbin/3day.json");
- apiAddresses.put("bazaar", "https://hysky.de/api/bazaar");
- apiAddresses.put("lowest bins", "https://hysky.de/api/auctions/lowestbins");
- apiAddresses.put("npc", "https://hysky.de/api/npcprice");
- apiAddresses.put("museum", "https://hysky.de/api/museum");
- apiAddresses.put("motes", "https://hysky.de/api/motesprice");
- }
} \ No newline at end of file
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java
new file mode 100644
index 00000000..086fcb00
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java
@@ -0,0 +1,145 @@
+package de.hysky.skyblocker.skyblock.item.tooltip;
+
+import com.google.gson.JsonObject;
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.config.SkyblockerConfig;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.Http;
+import de.hysky.skyblocker.utils.Utils;
+import org.jetbrains.annotations.Nullable;
+
+import java.net.http.HttpHeaders;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Predicate;
+
+public enum TooltipInfoType implements Runnable {
+ NPC("https://hysky.de/api/npcprice", itemTooltip -> itemTooltip.enableNPCPrice, true),
+ BAZAAR("https://hysky.de/api/bazaar", itemTooltip -> itemTooltip.enableBazaarPrice || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator, itemTooltip -> itemTooltip.enableBazaarPrice, false),
+ LOWEST_BINS("https://hysky.de/api/auctions/lowestbins", itemTooltip -> itemTooltip.enableLowestBIN || SkyblockerConfigManager.get().locations.dungeons.dungeonChestProfit.enableProfitCalculator, itemTooltip -> itemTooltip.enableLowestBIN, false),
+ ONE_DAY_AVERAGE("https://moulberry.codes/auction_averages_lbin/1day.json", itemTooltip -> itemTooltip.enableAvgBIN, false),
+ THREE_DAY_AVERAGE("https://moulberry.codes/auction_averages_lbin/3day.json", itemTooltip -> itemTooltip.enableAvgBIN, false),
+ MOTES("https://hysky.de/api/motesprice", itemTooltip -> itemTooltip.enableMotesPrice, itemTooltip -> itemTooltip.enableMotesPrice && Utils.isInTheRift(), true),
+ MUSEUM("https://hysky.de/api/museum", itemTooltip -> itemTooltip.enableMuseumDate, true),
+ COLOR("https://hysky.de/api/color", itemTooltip -> itemTooltip.enableExoticTooltip, true);
+
+ private final String address;
+ private final Predicate<SkyblockerConfig.ItemTooltip> dataEnabled;
+ private final Predicate<SkyblockerConfig.ItemTooltip> tooltipEnabled;
+ private JsonObject data;
+ private final boolean cacheable;
+ private long hash;
+
+ /**
+ * @param address the address to download the data from
+ * @param enabled the predicate to check if the data should be downloaded and the tooltip should be shown
+ * @param cacheable whether the data should be cached
+ */
+ TooltipInfoType(String address, Predicate<SkyblockerConfig.ItemTooltip> enabled, boolean cacheable) {
+ this(address, enabled, enabled, null, cacheable);
+ }
+
+ /**
+ * @param address the address to download the data from
+ * @param dataEnabled the predicate to check if data should be downloaded
+ * @param tooltipEnabled the predicate to check if the tooltip should be shown
+ * @param cacheable whether the data should be cached
+ */
+ TooltipInfoType(String address, Predicate<SkyblockerConfig.ItemTooltip> dataEnabled, Predicate<SkyblockerConfig.ItemTooltip> tooltipEnabled, boolean cacheable) {
+ this(address, dataEnabled, tooltipEnabled, null, cacheable);
+ }
+
+ /**
+ * @param address the address to download the data from
+ * @param dataEnabled the predicate to check if data should be downloaded
+ * @param tooltipEnabled the predicate to check if the tooltip should be shown
+ * @param data the data
+ * @param cacheable whether the data should be cached
+ */
+ TooltipInfoType(String address, Predicate<SkyblockerConfig.ItemTooltip> dataEnabled, Predicate<SkyblockerConfig.ItemTooltip> tooltipEnabled, @Nullable JsonObject data, boolean cacheable) {
+ this.address = address;
+ this.dataEnabled = dataEnabled;
+ this.tooltipEnabled = tooltipEnabled;
+ this.data = data;
+ this.cacheable = cacheable;
+ }
+
+ /**
+ * @return whether the data should be downloaded
+ */
+ private boolean isDataEnabled() {
+ return dataEnabled.test(ItemTooltip.config);
+ }
+
+ /**
+ * @return whether the tooltip should be shown
+ */
+ public boolean isTooltipEnabled() {
+ return tooltipEnabled.test(ItemTooltip.config);
+ }
+
+ public JsonObject getData() {
+ return data;
+ }
+
+ /**
+ * Checks if the data has the given member name and sends a warning message if data is null.
+ *
+ * @param memberName the member name to check
+ * @return whether the data has the given member name or not
+ */
+ public boolean hasOrNullWarning(String memberName) {
+ if (data == null) {
+ ItemTooltip.nullWarning();
+ return false;
+ } else return data.has(memberName);
+ }
+
+ /**
+ * Checks if the tooltip is enabled and the data has the given member name and sends a warning message if data is null.
+ *
+ * @param memberName the member name to check
+ * @return whether the tooltip is enabled and the data has the given member name or not
+ */
+ public boolean isTooltipEnabledAndHasOrNullWarning(String memberName) {
+ return isTooltipEnabled() && hasOrNullWarning(memberName);
+ }
+
+ /**
+ * Downloads the data if it is enabled.
+ *
+ * @param futureList the list to add the future to
+ */
+ public void downloadIfEnabled(List<CompletableFuture<Void>> futureList) {
+ if (isDataEnabled()) {
+ download(futureList);
+ }
+ }
+
+ /**
+ * Downloads the data.
+ *
+ * @param futureList the list to add the future to
+ */
+ public void download(List<CompletableFuture<Void>> futureList) {
+ futureList.add(CompletableFuture.runAsync(this));
+ }
+
+ /**
+ * Downloads the data.
+ */
+ @Override
+ public void run() {
+ try {
+ if (cacheable) {
+ HttpHeaders headers = Http.sendHeadRequest(address);
+ long hash = Http.getEtag(headers).hashCode() + Http.getLastModified(headers).hashCode();
+ if (this.hash == hash) return;
+ else this.hash = hash;
+ }
+ data = SkyblockerMod.GSON.fromJson(Http.sendGetRequest(address), JsonObject.class);
+ } catch (Exception e) {
+ ItemTooltip.LOGGER.warn("[Skyblocker] Failed to download " + this + " prices!", e);
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListWidget.java
index afdcaca8..5570c4f7 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemListWidget.java
@@ -38,7 +38,7 @@ public class ItemListWidget extends RecipeBookWidget {
this.searchField = ((RecipeBookWidgetAccessor) this).getSearchField();
int x = (this.parentWidth - 147) / 2 - this.leftOffset;
int y = (this.parentHeight - 166) / 2;
- if (ItemRegistry.filesImported) {
+ if (ItemRepository.filesImported()) {
this.results = new SearchResultsWidget(this.client, x, y);
this.updateSearchResult();
}
@@ -57,7 +57,7 @@ public class ItemListWidget extends RecipeBookWidget {
context.drawTexture(TEXTURE, i, j, 1, 1, 147, 166);
this.searchField = ((RecipeBookWidgetAccessor) this).getSearchField();
- if (!ItemRegistry.filesImported && !this.searchField.isFocused() && this.searchField.getText().isEmpty()) {
+ if (!ItemRepository.filesImported() && !this.searchField.isFocused() && this.searchField.getText().isEmpty()) {
Text hintText = (Text.literal("Loading...")).formatted(Formatting.ITALIC).formatted(Formatting.GRAY);
context.drawTextWithShadow(this.client.textRenderer, hintText, i + 25, j + 14, -1);
} else if (!this.searchField.isFocused() && this.searchField.getText().isEmpty()) {
@@ -66,7 +66,7 @@ public class ItemListWidget extends RecipeBookWidget {
} else {
this.searchField.render(context, mouseX, mouseY, delta);
}
- if (ItemRegistry.filesImported) {
+ if (ItemRepository.filesImported()) {
if (results == null) {
int x = (this.parentWidth - 147) / 2 - this.leftOffset;
int y = (this.parentHeight - 166) / 2;
@@ -81,14 +81,14 @@ public class ItemListWidget extends RecipeBookWidget {
@Override
public void drawTooltip(DrawContext context, int x, int y, int mouseX, int mouseY) {
- if (this.isOpen() && ItemRegistry.filesImported && results != null) {
+ if (this.isOpen() && ItemRepository.filesImported() && results != null) {
this.results.drawTooltip(context, mouseX, mouseY);
}
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
- if (this.isOpen() && this.client.player != null && !this.client.player.isSpectator() && ItemRegistry.filesImported && this.searchField != null && results != null) {
+ if (this.isOpen() && this.client.player != null && !this.client.player.isSpectator() && ItemRepository.filesImported() && this.searchField != null && results != null) {
if (this.searchField.mouseClicked(mouseX, mouseY, button)) {
this.results.closeRecipeView();
this.searchField.setFocused(true);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemRegistry.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemRegistry.java
deleted file mode 100644
index b958a85d..00000000
--- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemRegistry.java
+++ /dev/null
@@ -1,129 +0,0 @@
-package de.hysky.skyblocker.skyblock.itemlist;
-
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import de.hysky.skyblocker.utils.ItemUtils;
-import de.hysky.skyblocker.utils.NEURepo;
-import net.minecraft.client.MinecraftClient;
-import net.minecraft.item.ItemStack;
-import net.minecraft.item.Items;
-import net.minecraft.text.Text;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Stream;
-
-public class ItemRegistry {
- protected static final Logger LOGGER = LoggerFactory.getLogger(ItemRegistry.class);
- protected static final Path ITEM_LIST_DIR = NEURepo.LOCAL_REPO_DIR.resolve("items");
-
- protected static final List<ItemStack> items = new ArrayList<>();
- protected static final Map<String, ItemStack> itemsMap = new HashMap<>();
- protected static final List<SkyblockCraftingRecipe> recipes = new ArrayList<>();
- public static final MinecraftClient client = MinecraftClient.getInstance();
- public static boolean filesImported = false;
-
- public static void init() {
- NEURepo.runAsyncAfterLoad(ItemStackBuilder::loadPetNums);
- NEURepo.runAsyncAfterLoad(ItemRegistry::importItemFiles);
- }
-
- private static void importItemFiles() {
- List<JsonObject> jsonObjs = new ArrayList<>();
-
- File dir = ITEM_LIST_DIR.toFile();
- File[] files = dir.listFiles();
- if (files == null) {
- return;
- }
- for (File file : files) {
- Path path = ITEM_LIST_DIR.resolve(file.getName());
- try {
- String fileContent = Files.readString(path);
- jsonObjs.add(JsonParser.parseString(fileContent).getAsJsonObject());
- } catch (Exception e) {
- LOGGER.error("Failed to read file " + path, e);
- }
- }
-
- for (JsonObject jsonObj : jsonObjs) {
- String internalName = jsonObj.get("internalname").getAsString();
- ItemStack itemStack = ItemStackBuilder.parseJsonObj(jsonObj);
- items.add(itemStack);
- itemsMap.put(internalName, itemStack);
- }
- for (JsonObject jsonObj : jsonObjs)
- if (jsonObj.has("recipe")) {
- recipes.add(SkyblockCraftingRecipe.fromJsonObject(jsonObj));
- }
-
- items.sort((lhs, rhs) -> {
- String lhsInternalName = ItemUtils.getItemId(lhs);
- String lhsFamilyName = lhsInternalName.replaceAll(".\\d+$", "");
- String rhsInternalName = ItemUtils.getItemId(rhs);
- String rhsFamilyName = rhsInternalName.replaceAll(".\\d+$", "");
- if (lhsFamilyName.equals(rhsFamilyName)) {
- if (lhsInternalName.length() != rhsInternalName.length())
- return lhsInternalName.length() - rhsInternalName.length();
- else return lhsInternalName.compareTo(rhsInternalName);
- }
- return lhsFamilyName.compareTo(rhsFamilyName);
- });
- filesImported = true;
- }
-
- public static String getWikiLink(String internalName) {
- try {
- String fileContent = Files.readString(ITEM_LIST_DIR.resolve(internalName + ".json"));
- JsonObject fileJson = JsonParser.parseString(fileContent).getAsJsonObject();
- //TODO optional official or unofficial wiki link
- try {
- return fileJson.get("info").getAsJsonArray().get(1).getAsString();
- } catch (IndexOutOfBoundsException e) {
- return fileJson.get("info").getAsJsonArray().get(0).getAsString();
- }
- } catch (IOException | NullPointerException e) {
- LOGGER.error("Failed to read item file " + internalName + ".json", e);
- if (client.player != null) {
- client.player.sendMessage(Text.of("Can't locate a wiki article for this item..."), false);
- }
- return null;
- }
- }
-
- public static List<SkyblockCraftingRecipe> getRecipes(String internalName) {
- List<SkyblockCraftingRecipe> result = new ArrayList<>();
- for (SkyblockCraftingRecipe recipe : recipes) {
- if (ItemUtils.getItemId(recipe.result).equals(internalName)) result.add(recipe);
- }
- for (SkyblockCraftingRecipe recipe : recipes)
- for (ItemStack ingredient : recipe.grid) {
- if (!ingredient.getItem().equals(Items.AIR) && ItemUtils.getItemId(ingredient).equals(internalName)) {
- result.add(recipe);
- break;
- }
- }
- return result;
- }
-
- public static Stream<SkyblockCraftingRecipe> getRecipesStream() {
- return recipes.stream();
- }
-
- public static Stream<ItemStack> getItemsStream() {
- return items.stream();
- }
-
- public static ItemStack getItemStack(String internalName) {
- return itemsMap.get(internalName);
- }
-}
-
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemRepository.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemRepository.java
new file mode 100644
index 00000000..bd2ac27a
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemRepository.java
@@ -0,0 +1,137 @@
+package de.hysky.skyblocker.skyblock.itemlist;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.Constants;
+import de.hysky.skyblocker.utils.ItemUtils;
+import de.hysky.skyblocker.utils.NEURepoManager;
+import io.github.moulberry.repo.data.NEUCraftingRecipe;
+import io.github.moulberry.repo.data.NEUItem;
+import io.github.moulberry.repo.data.NEURecipe;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.text.Text;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+public class ItemRepository {
+ protected static final Logger LOGGER = LoggerFactory.getLogger(ItemRepository.class);
+
+ private static final List<ItemStack> items = new ArrayList<>();
+ private static final Map<String, ItemStack> itemsMap = new HashMap<>();
+ private static final List<SkyblockCraftingRecipe> recipes = new ArrayList<>();
+ private static boolean filesImported = false;
+
+ public static void init() {
+ NEURepoManager.runAsyncAfterLoad(ItemStackBuilder::loadPetNums);
+ NEURepoManager.runAsyncAfterLoad(ItemRepository::importItemFiles);
+ }
+
+ private static void importItemFiles() {
+ NEURepoManager.NEU_REPO.getItems().getItems().values().forEach(ItemRepository::loadItem);
+ NEURepoManager.NEU_REPO.getItems().getItems().values().forEach(ItemRepository::loadRecipes);
+
+ items.sort((lhs, rhs) -> {
+ String lhsInternalName = ItemUtils.getItemId(lhs);
+ String lhsFamilyName = lhsInternalName.replaceAll(".\\d+$", "");
+ String rhsInternalName = ItemUtils.getItemId(rhs);
+ String rhsFamilyName = rhsInternalName.replaceAll(".\\d+$", "");
+ if (lhsFamilyName.equals(rhsFamilyName)) {
+ if (lhsInternalName.length() != rhsInternalName.length())
+ return lhsInternalName.length() - rhsInternalName.length();
+ else return lhsInternalName.compareTo(rhsInternalName);
+ }
+ return lhsFamilyName.compareTo(rhsFamilyName);
+ });
+ filesImported = true;
+ }
+
+ private static void loadItem(NEUItem item) {
+ ItemStack stack = ItemStackBuilder.fromNEUItem(item);
+ items.add(stack);
+ itemsMap.put(item.getSkyblockItemId(), stack);
+ }
+
+ private static void loadRecipes(NEUItem item) {
+ for (NEURecipe recipe : item.getRecipes()) {
+ if (recipe instanceof NEUCraftingRecipe neuCraftingRecipe) {
+ recipes.add(SkyblockCraftingRecipe.fromNEURecipe(neuCraftingRecipe));
+ }
+ }
+ }
+
+ public static String getWikiLink(String internalName, PlayerEntity player) {
+ NEUItem item = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(internalName);
+ if (item == null || item.getInfo().isEmpty()) {
+ warnNoWikiLink(player);
+ return null;
+ }
+
+ List<String> info = item.getInfo();
+ String wikiLink0 = info.get(0);
+ String wikiLink1 = info.size() > 1 ? info.get(1) : "";
+ String wikiDomain = SkyblockerConfigManager.get().general.wikiLookup.officialWiki ? "https://wiki.hypixel.net" : "https://hypixel-skyblock.fandom.com";
+ if (wikiLink0.startsWith(wikiDomain)) {
+ return wikiLink0;
+ } else if (wikiLink1.startsWith(wikiDomain)) {
+ return wikiLink1;
+ }
+ warnNoWikiLink(player);
+ return null;
+ }
+
+ private static void warnNoWikiLink(PlayerEntity player) {
+ if (player != null) {
+ player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.wikiLookup.noArticleFound")), false);
+ }
+ }
+
+ public static List<SkyblockCraftingRecipe> getRecipes(String internalName) {
+ List<SkyblockCraftingRecipe> result = new ArrayList<>();
+ for (SkyblockCraftingRecipe recipe : recipes) {
+ if (ItemUtils.getItemId(recipe.getResult()).equals(internalName)) result.add(recipe);
+ }
+ for (SkyblockCraftingRecipe recipe : recipes) {
+ for (ItemStack ingredient : recipe.getGrid()) {
+ if (!ingredient.getItem().equals(Items.AIR) && ItemUtils.getItemId(ingredient).equals(internalName)) {
+ result.add(recipe);
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ public static boolean filesImported() {
+ return filesImported;
+ }
+
+ public static void setFilesImported(boolean filesImported) {
+ ItemRepository.filesImported = filesImported;
+ }
+
+ public static List<ItemStack> getItems() {
+ return items;
+ }
+
+ public static Stream<ItemStack> getItemsStream() {
+ return items.stream();
+ }
+
+ @Nullable
+ public static ItemStack getItemStack(String internalName) {
+ return itemsMap.get(internalName);
+ }
+
+ public static Stream<SkyblockCraftingRecipe> getRecipesStream() {
+ return recipes.stream();
+ }
+}
+
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java
index 34e4a0e7..cc7c0bc1 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java
@@ -1,45 +1,41 @@
package de.hysky.skyblocker.skyblock.itemlist;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
import de.hysky.skyblocker.utils.ItemUtils;
-import de.hysky.skyblocker.utils.NEURepo;
+import de.hysky.skyblocker.utils.NEURepoManager;
+import io.github.moulberry.repo.constants.PetNumbers;
+import io.github.moulberry.repo.data.NEUItem;
+import io.github.moulberry.repo.data.Rarity;
import net.minecraft.item.FireworkRocketItem;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.*;
import net.minecraft.text.Text;
import net.minecraft.util.Pair;
-import java.nio.file.Files;
-import java.nio.file.Path;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ItemStackBuilder {
- private final static Path PETNUMS_PATH = NEURepo.LOCAL_REPO_DIR.resolve("constants/petnums.json");
- private static JsonObject petNums;
+ private static Map<String, Map<Rarity, PetNumbers>> petNums;
public static void loadPetNums() {
try {
- petNums = JsonParser.parseString(Files.readString(PETNUMS_PATH)).getAsJsonObject();
+ petNums = NEURepoManager.NEU_REPO.getConstants().getPetNumbers();
} catch (Exception e) {
- ItemRegistry.LOGGER.error("Failed to load petnums.json");
+ ItemRepository.LOGGER.error("Failed to load petnums.json");
}
}
- public static ItemStack parseJsonObj(JsonObject obj) {
- String internalName = obj.get("internalname").getAsString();
+ public static ItemStack fromNEUItem(NEUItem item) {
+ String internalName = item.getSkyblockItemId();
List<Pair<String, String>> injectors = new ArrayList<>(petData(internalName));
NbtCompound root = new NbtCompound();
- root.put("Count", NbtByte.of((byte)1));
+ root.put("Count", NbtByte.of((byte) 1));
- String id = obj.get("itemid").getAsString();
- int damage = obj.get("damage").getAsInt();
+ String id = item.getMinecraftItemId();
+ int damage = item.getDamage();
root.put("id", NbtString.of(ItemFixerUpper.convertItemId(id, damage)));
NbtCompound tag = new NbtCompound();
@@ -52,16 +48,14 @@ public class ItemStackBuilder {
NbtCompound display = new NbtCompound();
tag.put("display", display);
- String name = injectData(obj.get("displayname").getAsString(), injectors);
+ String name = injectData(item.getDisplayName(), injectors);
display.put("Name", NbtString.of(Text.Serializer.toJson(Text.of(name))));
NbtList lore = new NbtList();
display.put("Lore", lore);
- obj.get("lore").getAsJsonArray().forEach(el ->
- lore.add(NbtString.of(Text.Serializer.toJson(Text.of(injectData(el.getAsString(), injectors)))))
- );
+ item.getLore().forEach(el -> lore.add(NbtString.of(Text.Serializer.toJson(Text.of(injectData(el, injectors))))));
- String nbttag = obj.get("nbttag").getAsString();
+ String nbttag = item.getNbttag();
// add skull texture
Matcher skullUuid = Pattern.compile("(?<=SkullOwner:\\{)Id:\"(.{36})\"").matcher(nbttag);
Matcher skullTexture = Pattern.compile("(?<=Properties:\\{textures:\\[0:\\{Value:)\"(.+?)\"").matcher(nbttag);
@@ -94,53 +88,54 @@ public class ItemStackBuilder {
}
// Add firework star color
- Matcher explosionColorMatcher = Pattern.compile("\\{Explosion:\\{(?:Type:[0-9a-z]+,)?Colors:\\[(?<color>[0-9]+)\\]\\}").matcher(nbttag);
+ Matcher explosionColorMatcher = Pattern.compile("\\{Explosion:\\{(?:Type:[0-9a-z]+,)?Colors:\\[(?<color>[0-9]+)]\\}").matcher(nbttag);
if (explosionColorMatcher.find()) {
NbtCompound explosion = new NbtCompound();
explosion.putInt("Type", FireworkRocketItem.Type.SMALL_BALL.getId()); //Forget about the actual ball type because it probably doesn't matter
- explosion.putIntArray("Colors", new int[] { Integer.parseInt(explosionColorMatcher.group("color")) });
+ explosion.putIntArray("Colors", new int[]{Integer.parseInt(explosionColorMatcher.group("color"))});
tag.put("Explosion", explosion);
}
return ItemStack.fromNbt(root);
}
- // TODO: fix stats for GOLDEN_DRAGON (lv1 -> lv200)
private static List<Pair<String, String>> petData(String internalName) {
List<Pair<String, String>> list = new ArrayList<>();
String petName = internalName.split(";")[0];
- if (!internalName.contains(";") || !petNums.has(petName)) return list;
-
- list.add(new Pair<>("\\{LVL\\}", "1 ➡ 100"));
-
- final String[] rarities = {
- "COMMON",
- "UNCOMMON",
- "RARE",
- "EPIC",
- "LEGENDARY",
- "MYTHIC"
+ if (!internalName.contains(";") || !petNums.containsKey(petName)) return list;
+
+ final Rarity[] rarities = {
+ Rarity.COMMON,
+ Rarity.UNCOMMON,
+ Rarity.RARE,
+ Rarity.EPIC,
+ Rarity.LEGENDARY,
+ Rarity.MYTHIC,
};
- String rarity = rarities[Integer.parseInt(internalName.split(";")[1])];
- JsonObject data = petNums.get(petName).getAsJsonObject().get(rarity).getAsJsonObject();
+ Rarity rarity = rarities[Integer.parseInt(internalName.split(";")[1])];
+ PetNumbers data = petNums.get(petName).get(rarity);
- JsonObject statNumsMin = data.get("1").getAsJsonObject().get("statNums").getAsJsonObject();
- JsonObject statNumsMax = data.get("100").getAsJsonObject().get("statNums").getAsJsonObject();
- Set<Map.Entry<String, JsonElement>> entrySet = statNumsMin.entrySet();
- for (Map.Entry<String, JsonElement> entry : entrySet) {
+ int minLevel = data.getLowLevel();
+ int maxLevel = data.getHighLevel();
+ list.add(new Pair<>("\\{LVL\\}", minLevel + " ➡ " + maxLevel));
+
+ Map<String, Double> statNumsMin = data.getStatsAtLowLevel().getStatNumbers();
+ Map<String, Double> statNumsMax = data.getStatsAtHighLevel().getStatNumbers();
+ Set<Map.Entry<String, Double>> entrySet = statNumsMin.entrySet();
+ for (Map.Entry<String, Double> entry : entrySet) {
String key = entry.getKey();
- String left = "\\{" + key+ "\\}";
- String right = statNumsMin.get(key).getAsString() + " ➡ " + statNumsMax.get(key).getAsString();
+ String left = "\\{" + key + "\\}";
+ String right = statNumsMin.get(key) + " ➡ " + statNumsMax.get(key);
list.add(new Pair<>(left, right));
}
- JsonArray otherNumsMin = data.get("1").getAsJsonObject().get("otherNums").getAsJsonArray();
- JsonArray otherNumsMax = data.get("100").getAsJsonObject().get("otherNums").getAsJsonArray();
+ List<Double> otherNumsMin = data.getStatsAtLowLevel().getOtherNumbers();
+ List<Double> otherNumsMax = data.getStatsAtHighLevel().getOtherNumbers();
for (int i = 0; i < otherNumsMin.size(); ++i) {
String left = "\\{" + i + "\\}";
- String right = otherNumsMin.get(i).getAsString() + " ➡ " + otherNumsMax.get(i).getAsString();
+ String right = otherNumsMin.get(i) + " ➡ " + otherNumsMax.get(i);
list.add(new Pair<>(left, right));
}
@@ -148,8 +143,9 @@ public class ItemStackBuilder {
}
private static String injectData(String string, List<Pair<String, String>> injectors) {
- for (Pair<String, String> injector : injectors)
+ for (Pair<String, String> injector : injectors) {
string = string.replaceAll(injector.getLeft(), injector.getRight());
+ }
return string;
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SearchResultsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SearchResultsWidget.java
index 8a266d65..44e336d9 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SearchResultsWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SearchResultsWidget.java
@@ -72,7 +72,7 @@ public class SearchResultsWidget implements Drawable {
if (!searchText.equals(this.searchText)) {
this.searchText = searchText;
this.searchResults.clear();
- for (ItemStack entry : ItemRegistry.items) {
+ for (ItemStack entry : ItemRepository.getItems()) {
String name = entry.getName().toString().toLowerCase(Locale.ENGLISH);
if (entry.getNbt() == null) {
continue;
@@ -93,16 +93,16 @@ public class SearchResultsWidget implements Drawable {
SkyblockCraftingRecipe recipe = this.recipeResults.get(this.currentPage);
for (ResultButtonWidget button : resultButtons)
button.clearItemStack();
- resultButtons.get(5).setItemStack(recipe.grid.get(0));
- resultButtons.get(6).setItemStack(recipe.grid.get(1));
- resultButtons.get(7).setItemStack(recipe.grid.get(2));
- resultButtons.get(10).setItemStack(recipe.grid.get(3));
- resultButtons.get(11).setItemStack(recipe.grid.get(4));
- resultButtons.get(12).setItemStack(recipe.grid.get(5));
- resultButtons.get(15).setItemStack(recipe.grid.get(6));
- resultButtons.get(16).setItemStack(recipe.grid.get(7));
- resultButtons.get(17).setItemStack(recipe.grid.get(8));
- resultButtons.get(14).setItemStack(recipe.result);
+ resultButtons.get(5).setItemStack(recipe.getGrid().get(0));
+ resultButtons.get(6).setItemStack(recipe.getGrid().get(1));
+ resultButtons.get(7).setItemStack(recipe.getGrid().get(2));
+ resultButtons.get(10).setItemStack(recipe.getGrid().get(3));
+ resultButtons.get(11).setItemStack(recipe.getGrid().get(4));
+ resultButtons.get(12).setItemStack(recipe.getGrid().get(5));
+ resultButtons.get(15).setItemStack(recipe.getGrid().get(6));
+ resultButtons.get(16).setItemStack(recipe.getGrid().get(7));
+ resultButtons.get(17).setItemStack(recipe.getGrid().get(8));
+ resultButtons.get(14).setItemStack(recipe.getResult());
} else {
for (int i = 0; i < resultButtons.size(); ++i) {
int index = this.currentPage * resultButtons.size() + i;
@@ -122,7 +122,7 @@ public class SearchResultsWidget implements Drawable {
RenderSystem.disableDepthTest();
if (this.displayRecipes) {
//Craft text - usually a requirement for the recipe
- String craftText = this.recipeResults.get(this.currentPage).craftText;
+ String craftText = this.recipeResults.get(this.currentPage).getCraftText();
if (textRenderer.getWidth(craftText) > MAX_TEXT_WIDTH) {
drawTooltip(textRenderer, context, craftText, this.parentX + 11, this.parentY + 31, mouseX, mouseY);
craftText = textRenderer.trimToWidth(craftText, MAX_TEXT_WIDTH) + ELLIPSIS;
@@ -130,7 +130,7 @@ public class SearchResultsWidget implements Drawable {
context.drawTextWithShadow(textRenderer, craftText, this.parentX + 11, this.parentY + 31, 0xffffffff);
//Item name
- Text resultText = this.recipeResults.get(this.currentPage).result.getName();
+ Text resultText = this.recipeResults.get(this.currentPage).getResult().getName();
if (textRenderer.getWidth(Formatting.strip(resultText.getString())) > MAX_TEXT_WIDTH) {
drawTooltip(textRenderer, context, resultText, this.parentX + 11, this.parentY + 43, mouseX, mouseY);
resultText = Text.literal(getLegacyFormatting(resultText.getString()) + textRenderer.trimToWidth(Formatting.strip(resultText.getString()), MAX_TEXT_WIDTH) + ELLIPSIS).setStyle(resultText.getStyle());
@@ -202,7 +202,7 @@ public class SearchResultsWidget implements Drawable {
if (internalName.isEmpty()) {
continue;
}
- List<SkyblockCraftingRecipe> recipes = ItemRegistry.getRecipes(internalName);
+ List<SkyblockCraftingRecipe> recipes = ItemRepository.getRecipes(internalName);
if (!recipes.isEmpty()) {
this.recipeResults = recipes;
this.currentPage = 0;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SkyblockCraftingRecipe.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SkyblockCraftingRecipe.java
index b738dfef..f5b379dc 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SkyblockCraftingRecipe.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SkyblockCraftingRecipe.java
@@ -1,6 +1,7 @@
package de.hysky.skyblocker.skyblock.itemlist;
-import com.google.gson.JsonObject;
+import io.github.moulberry.repo.data.NEUCraftingRecipe;
+import io.github.moulberry.repo.data.NEUIngredient;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import org.slf4j.Logger;
@@ -11,37 +12,31 @@ import java.util.List;
public class SkyblockCraftingRecipe {
private static final Logger LOGGER = LoggerFactory.getLogger(SkyblockCraftingRecipe.class);
- String craftText = "";
- final List<ItemStack> grid = new ArrayList<>(9);
- ItemStack result;
-
- public static SkyblockCraftingRecipe fromJsonObject(JsonObject jsonObj) {
- SkyblockCraftingRecipe recipe = new SkyblockCraftingRecipe();
- if (jsonObj.has("crafttext")) recipe.craftText = jsonObj.get("crafttext").getAsString();
- recipe.grid.add(getItemStack(jsonObj.getAsJsonObject("recipe").get("A1").getAsString()));
- recipe.grid.add(getItemStack(jsonObj.getAsJsonObject("recipe").get("A2").getAsString()));
- recipe.grid.add(getItemStack(jsonObj.getAsJsonObject("recipe").get("A3").getAsString()));
- recipe.grid.add(getItemStack(jsonObj.getAsJsonObject("recipe").get("B1").getAsString()));
- recipe.grid.add(getItemStack(jsonObj.getAsJsonObject("recipe").get("B2").getAsString()));
- recipe.grid.add(getItemStack(jsonObj.getAsJsonObject("recipe").get("B3").getAsString()));
- recipe.grid.add(getItemStack(jsonObj.getAsJsonObject("recipe").get("C1").getAsString()));
- recipe.grid.add(getItemStack(jsonObj.getAsJsonObject("recipe").get("C2").getAsString()));
- recipe.grid.add(getItemStack(jsonObj.getAsJsonObject("recipe").get("C3").getAsString()));
- recipe.result = ItemRegistry.itemsMap.get(jsonObj.get("internalname").getAsString());
+ private final String craftText;
+ private final List<ItemStack> grid = new ArrayList<>(9);
+ private ItemStack result;
+
+ public SkyblockCraftingRecipe(String craftText) {
+ this.craftText = craftText;
+ }
+
+ public static SkyblockCraftingRecipe fromNEURecipe(NEUCraftingRecipe neuCraftingRecipe) {
+ SkyblockCraftingRecipe recipe = new SkyblockCraftingRecipe(neuCraftingRecipe.getExtraText() != null ? neuCraftingRecipe.getExtraText() : "");
+ for (NEUIngredient input : neuCraftingRecipe.getInputs()) {
+ recipe.grid.add(getItemStack(input));
+ }
+ recipe.result = getItemStack(neuCraftingRecipe.getOutput());
return recipe;
}
- private static ItemStack getItemStack(String internalName) {
- try {
- if (internalName.length() > 0) {
- int count = internalName.split(":").length == 1 ? 1 : Integer.parseInt(internalName.split(":")[1]);
- internalName = internalName.split(":")[0];
- ItemStack itemStack = ItemRegistry.itemsMap.get(internalName).copy();
- itemStack.setCount(count);
- return itemStack;
+ private static ItemStack getItemStack(NEUIngredient input) {
+ if (input != NEUIngredient.SENTINEL_EMPTY) {
+ ItemStack stack = ItemRepository.getItemStack(input.getItemId());
+ if (stack != null) {
+ return stack.copyWithCount((int) input.getAmount());
+ } else {
+ LOGGER.warn("[Skyblocker Recipe] Unable to find item {}", input.getItemId());
}
- } catch (Exception e) {
- LOGGER.error("[Skyblocker-Recipe] " + internalName, e);
}
return Items.AIR.getDefaultStack();
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/rift/EnigmaSouls.java b/src/main/java/de/hysky/skyblocker/skyblock/rift/EnigmaSouls.java
new file mode 100644
index 00000000..744edd4c
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/rift/EnigmaSouls.java
@@ -0,0 +1,183 @@
+package de.hysky.skyblocker.skyblock.rift;
+
+import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.mojang.brigadier.Command;
+import com.mojang.brigadier.CommandDispatcher;
+
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.config.SkyblockerConfig;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.Constants;
+import de.hysky.skyblocker.utils.PosUtils;
+import de.hysky.skyblocker.utils.Utils;
+import de.hysky.skyblocker.utils.render.RenderHelper;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
+import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.command.CommandRegistryAccess;
+import net.minecraft.text.Text;
+import net.minecraft.util.DyeColor;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.math.BlockPos;
+
+public class EnigmaSouls {
+ private static final Logger LOGGER = LoggerFactory.getLogger(EnigmaSouls.class);
+ private static final Identifier WAYPOINTS_JSON = new Identifier(SkyblockerMod.NAMESPACE, "rift/enigma_soul_waypoints.json");
+ private static final BlockPos[] SOUL_WAYPOINTS = new BlockPos[42];
+ private static final Path FOUND_SOULS_FILE = SkyblockerMod.CONFIG_DIR.resolve("found_enigma_souls.json");
+ private static final Object2ObjectOpenHashMap<String, ObjectOpenHashSet<BlockPos>> FOUND_SOULS = new Object2ObjectOpenHashMap<>();
+ private static final float[] GREEN = DyeColor.GREEN.getColorComponents();
+ private static final float[] RED = DyeColor.RED.getColorComponents();
+
+ private static CompletableFuture<Void> soulsLoaded;
+
+ static void load(MinecraftClient client) {
+ //Load waypoints
+ soulsLoaded = CompletableFuture.runAsync(() -> {
+ try (BufferedReader reader = client.getResourceManager().openAsReader(WAYPOINTS_JSON)) {
+ JsonObject file = JsonParser.parseReader(reader).getAsJsonObject();
+ JsonArray waypoints = file.get("waypoints").getAsJsonArray();
+
+ for (int i = 0; i < waypoints.size(); i++) {
+ JsonObject waypoint = waypoints.get(i).getAsJsonObject();
+ SOUL_WAYPOINTS[i] = new BlockPos(waypoint.get("x").getAsInt(), waypoint.get("y").getAsInt(), waypoint.get("z").getAsInt());
+ }
+
+ } catch (IOException e) {
+ LOGGER.error("[Skyblocker] There was an error while loading enigma soul waypoints!", e);
+ }
+
+ //Load found souls
+ try (BufferedReader reader = Files.newBufferedReader(FOUND_SOULS_FILE)) {
+ for (Map.Entry<String, JsonElement> profile : JsonParser.parseReader(reader).getAsJsonObject().asMap().entrySet()) {
+ ObjectOpenHashSet<BlockPos> foundSoulsOnProfile = new ObjectOpenHashSet<>();
+
+ for (JsonElement foundSoul : profile.getValue().getAsJsonArray().asList()) {
+ foundSoulsOnProfile.add(PosUtils.parsePosString(foundSoul.getAsString()));
+ }
+
+ FOUND_SOULS.put(profile.getKey(), foundSoulsOnProfile);
+ }
+ } catch (NoSuchFileException ignored) {
+ } catch (IOException e) {
+ LOGGER.error("[Skyblocker] There was an error while loading found enigma souls!", e);
+ }
+ });
+ }
+
+ static void save(MinecraftClient client) {
+ JsonObject json = new JsonObject();
+
+ for (Map.Entry<String, ObjectOpenHashSet<BlockPos>> foundSoulsForProfile : FOUND_SOULS.entrySet()) {
+ JsonArray foundSoulsJson = new JsonArray();
+
+ for (BlockPos foundSoul : foundSoulsForProfile.getValue()) {
+ foundSoulsJson.add(PosUtils.getPosString(foundSoul));
+ }
+
+ json.add(foundSoulsForProfile.getKey(), foundSoulsJson);
+ }
+
+ try (BufferedWriter writer = Files.newBufferedWriter(FOUND_SOULS_FILE)) {
+ SkyblockerMod.GSON.toJson(json, writer);
+ } catch (IOException e) {
+ LOGGER.error("[Skyblocker] There was an error while saving found enigma souls!", e);
+ }
+ }
+
+ static void render(WorldRenderContext wrc) {
+ SkyblockerConfig.Rift config = SkyblockerConfigManager.get().locations.rift;
+
+ if (Utils.isInTheRift() && config.enigmaSoulWaypoints && soulsLoaded.isDone()) {
+ for (BlockPos pos : SOUL_WAYPOINTS) {
+ if (isSoulMissing(pos)) {
+ RenderHelper.renderFilledThroughWallsWithBeaconBeam(wrc, pos, GREEN, 0.5f);
+ } else if (config.highlightFoundEnigmaSouls) {
+ RenderHelper.renderFilledThroughWallsWithBeaconBeam(wrc, pos, RED, 0.5f);
+ }
+ }
+ }
+ }
+
+ static void onMessage(Text text, boolean overlay) {
+ if (Utils.isInTheRift() && !overlay) {
+ String message = text.getString();
+
+ if (message.equals("You have already found that Enigma Soul!") || Formatting.strip(message).equals("SOUL! You unlocked an Enigma Soul!")) markClosestSoulAsFound();
+ }
+ }
+
+ static void registerCommands(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) {
+ dispatcher.register(literal(SkyblockerMod.NAMESPACE)
+ .then(literal("rift")
+ .then(literal("enigmaSouls")
+ .then(literal("markAllFound").executes(context -> {
+ markAllFound();
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.rift.enigmaSouls.markAllFound")));
+
+ return Command.SINGLE_SUCCESS;
+ }))
+ .then(literal("markAllMissing").executes(context -> {
+ markAllMissing();
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.rift.enigmaSouls.markAllMissing")));
+
+ return Command.SINGLE_SUCCESS;
+ })))));
+ }
+
+ private static void markClosestSoulAsFound() {
+ ClientPlayerEntity player = MinecraftClient.getInstance().player;
+
+ if (!soulsLoaded.isDone() || player == null) return;
+
+ Arrays.stream(SOUL_WAYPOINTS)
+ .filter(EnigmaSouls::isSoulMissing)
+ .min(Comparator.comparingDouble(soulPos -> soulPos.getSquaredDistance(player.getPos())))
+ .filter(soulPos -> soulPos.getSquaredDistance(player.getPos()) <= 16)
+ .ifPresent(soulPos -> {
+ FOUND_SOULS.computeIfAbsent(Utils.getProfile(), profile -> new ObjectOpenHashSet<>());
+ FOUND_SOULS.get(Utils.getProfile()).add(soulPos);
+ });
+ }
+
+ private static boolean isSoulMissing(BlockPos soulPos) {
+ ObjectOpenHashSet<BlockPos> foundSoulsOnProfile = FOUND_SOULS.get(Utils.getProfile());
+
+ return foundSoulsOnProfile == null || !foundSoulsOnProfile.contains(soulPos);
+ }
+
+ private static void markAllFound() {
+ FOUND_SOULS.computeIfAbsent(Utils.getProfile(), profile -> new ObjectOpenHashSet<>());
+ FOUND_SOULS.get(Utils.getProfile()).addAll(List.of(SOUL_WAYPOINTS));
+ }
+
+ private static void markAllMissing() {
+ ObjectOpenHashSet<BlockPos> foundSoulsOnProfile = FOUND_SOULS.get(Utils.getProfile());
+
+ if (foundSoulsOnProfile != null) foundSoulsOnProfile.clear();
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/rift/MirrorverseWaypoints.java b/src/main/java/de/hysky/skyblocker/skyblock/rift/MirrorverseWaypoints.java
index 06181349..7dda741f 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/rift/MirrorverseWaypoints.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/rift/MirrorverseWaypoints.java
@@ -17,61 +17,59 @@ import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
+import java.util.concurrent.CompletableFuture;
public class MirrorverseWaypoints {
private static final Logger LOGGER = LoggerFactory.getLogger("skyblocker");
- private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
- private static final Identifier WAYPOINTS_JSON = new Identifier(SkyblockerMod.NAMESPACE, "mirrorverse_waypoints.json");
+ private static final Identifier WAYPOINTS_JSON = new Identifier(SkyblockerMod.NAMESPACE, "rift/mirrorverse_waypoints.json");
private static final BlockPos[] LAVA_PATH_WAYPOINTS = new BlockPos[107];
private static final BlockPos[] UPSIDE_DOWN_WAYPOINTS = new BlockPos[66];
private static final BlockPos[] TURBULATOR_WAYPOINTS = new BlockPos[27];
private static final float[] COLOR_COMPONENTS = DyeColor.RED.getColorComponents();
- static {
- loadWaypoints();
- }
+ private static CompletableFuture<Void> waypointsLoaded;
/**
* Loads the waypoint locations into memory
*/
- private static void loadWaypoints() {
- try (BufferedReader reader = CLIENT.getResourceManager().openAsReader(WAYPOINTS_JSON)) {
- JsonObject file = JsonParser.parseReader(reader).getAsJsonObject();
- JsonArray sections = file.get("sections").getAsJsonArray();
+ static void load(MinecraftClient client) {
+ waypointsLoaded = CompletableFuture.runAsync(() -> {
+ try (BufferedReader reader = client.getResourceManager().openAsReader(WAYPOINTS_JSON)) {
+ JsonObject file = JsonParser.parseReader(reader).getAsJsonObject();
+ JsonArray sections = file.get("sections").getAsJsonArray();
- /// Lava Path
- JsonArray lavaPathWaypoints = sections.get(0).getAsJsonObject().get("waypoints").getAsJsonArray();
+ /// Lava Path
+ JsonArray lavaPathWaypoints = sections.get(0).getAsJsonObject().get("waypoints").getAsJsonArray();
- for (int i = 0; i < lavaPathWaypoints.size(); i++) {
- JsonObject point = lavaPathWaypoints.get(i).getAsJsonObject();
- LAVA_PATH_WAYPOINTS[i] = new BlockPos(point.get("x").getAsInt(), point.get("y").getAsInt(), point.get("z").getAsInt());
- }
+ for (int i = 0; i < lavaPathWaypoints.size(); i++) {
+ JsonObject point = lavaPathWaypoints.get(i).getAsJsonObject();
+ LAVA_PATH_WAYPOINTS[i] = new BlockPos(point.get("x").getAsInt(), point.get("y").getAsInt(), point.get("z").getAsInt());
+ }
- /// Upside Down Parkour
- JsonArray upsideDownParkourWaypoints = sections.get(1).getAsJsonObject().get("waypoints").getAsJsonArray();
+ /// Upside Down Parkour
+ JsonArray upsideDownParkourWaypoints = sections.get(1).getAsJsonObject().get("waypoints").getAsJsonArray();
- for (int i = 0; i < upsideDownParkourWaypoints.size(); i++) {
- JsonObject point = upsideDownParkourWaypoints.get(i).getAsJsonObject();
- UPSIDE_DOWN_WAYPOINTS[i] = new BlockPos(point.get("x").getAsInt(), point.get("y").getAsInt(), point.get("z").getAsInt());
- }
+ for (int i = 0; i < upsideDownParkourWaypoints.size(); i++) {
+ JsonObject point = upsideDownParkourWaypoints.get(i).getAsJsonObject();
+ UPSIDE_DOWN_WAYPOINTS[i] = new BlockPos(point.get("x").getAsInt(), point.get("y").getAsInt(), point.get("z").getAsInt());
+ }
- /// Turbulator Parkour
- JsonArray turbulatorParkourWaypoints = sections.get(2).getAsJsonObject().get("waypoints").getAsJsonArray();
+ /// Turbulator Parkour
+ JsonArray turbulatorParkourWaypoints = sections.get(2).getAsJsonObject().get("waypoints").getAsJsonArray();
- for (int i = 0; i < turbulatorParkourWaypoints.size(); i++) {
- JsonObject point = turbulatorParkourWaypoints.get(i).getAsJsonObject();
- TURBULATOR_WAYPOINTS[i] = new BlockPos(point.get("x").getAsInt(), point.get("y").getAsInt(), point.get("z").getAsInt());
+ for (int i = 0; i < turbulatorParkourWaypoints.size(); i++) {
+ JsonObject point = turbulatorParkourWaypoints.get(i).getAsJsonObject();
+ TURBULATOR_WAYPOINTS[i] = new BlockPos(point.get("x").getAsInt(), point.get("y").getAsInt(), point.get("z").getAsInt());
+ }
+ } catch (IOException e) {
+ LOGGER.error("[Skyblocker] Mirrorverse Waypoints failed to load ;(", e);
}
-
- } catch (IOException e) {
- LOGGER.info("[Skyblocker] Mirrorverse Waypoints failed to load ;(");
- e.printStackTrace();
- }
+ });
}
protected static void render(WorldRenderContext wrc) {
//I would also check for the mirrorverse location but the scoreboard stuff is not performant at all...
- if (Utils.isInTheRift() && SkyblockerConfigManager.get().locations.rift.mirrorverseWaypoints) {
+ if (Utils.isInTheRift() && SkyblockerConfigManager.get().locations.rift.mirrorverseWaypoints && waypointsLoaded.isDone()) {
for (BlockPos pos : LAVA_PATH_WAYPOINTS) {
RenderHelper.renderFilledIfVisible(wrc, pos, COLOR_COMPONENTS, 0.5f);
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/rift/TheRift.java b/src/main/java/de/hysky/skyblocker/skyblock/rift/TheRift.java
index b39151d3..02b694b6 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/rift/TheRift.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/rift/TheRift.java
@@ -2,6 +2,9 @@ package de.hysky.skyblocker.skyblock.rift;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.utils.scheduler.Scheduler;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
+import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
public class TheRift {
@@ -13,6 +16,12 @@ public class TheRift {
public static void init() {
WorldRenderEvents.AFTER_TRANSLUCENT.register(MirrorverseWaypoints::render);
WorldRenderEvents.AFTER_TRANSLUCENT.register(EffigyWaypoints::render);
+ WorldRenderEvents.AFTER_TRANSLUCENT.register(EnigmaSouls::render);
+ ClientLifecycleEvents.CLIENT_STARTED.register(MirrorverseWaypoints::load);
+ ClientLifecycleEvents.CLIENT_STARTED.register(EnigmaSouls::load);
+ ClientLifecycleEvents.CLIENT_STOPPING.register(EnigmaSouls::save);
+ ClientReceiveMessageEvents.GAME.register(EnigmaSouls::onMessage);
+ ClientCommandRegistrationCallback.EVENT.register(EnigmaSouls::registerCommands);
Scheduler.INSTANCE.scheduleCyclic(EffigyWaypoints::updateEffigies, SkyblockerConfigManager.get().slayer.vampireSlayer.effigyUpdateFrequency);
Scheduler.INSTANCE.scheduleCyclic(TwinClawsIndicator::updateIce, SkyblockerConfigManager.get().slayer.vampireSlayer.holyIceUpdateFrequency);
Scheduler.INSTANCE.scheduleCyclic(ManiaIndicator::updateMania, SkyblockerConfigManager.get().slayer.vampireSlayer.maniaUpdateFrequency);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/spidersden/Relics.java b/src/main/java/de/hysky/skyblocker/skyblock/spidersden/Relics.java
index e5223874..aaf4d77c 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/spidersden/Relics.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/spidersden/Relics.java
@@ -8,6 +8,7 @@ import com.mojang.brigadier.CommandDispatcher;
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.config.SkyblockerConfig;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.Constants;
import de.hysky.skyblocker.utils.PosUtils;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.render.RenderHelper;
@@ -105,12 +106,12 @@ public class Relics {
.then(literal("relics")
.then(literal("markAllFound").executes(context -> {
Relics.markAllFound();
- context.getSource().sendFeedback(Text.translatable("skyblocker.relics.markAllFound"));
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.relics.markAllFound")));
return 1;
}))
.then(literal("markAllMissing").executes(context -> {
Relics.markAllMissing();
- context.getSource().sendFeedback(Text.translatable("skyblocker.relics.markAllMissing"));
+ context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.relics.markAllMissing")));
return 1;
}))));
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonPlayerWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonPlayerWidget.java
index be1a3c6e..d71eb190 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonPlayerWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/DungeonPlayerWidget.java
@@ -27,7 +27,7 @@ public class DungeonPlayerWidget extends Widget {
// group 3: level (or nothing, if pre dungeon start)
// this regex filters out the ironman icon as well as rank prefixes and emblems
// \[\d*\] (?:\[[A-Za-z]+\] )?(?<name>[A-Za-z0-9_]*) (?:.* )?\((?<class>\S*) ?(?<level>[LXVI]*)\)
- private static final Pattern PLAYER_PATTERN = Pattern
+ public static final Pattern PLAYER_PATTERN = Pattern
.compile("\\[\\d*\\] (?:\\[[A-Za-z]+\\] )?(?<name>[A-Za-z0-9_]*) (?:.* )?\\((?<class>\\S*) ?(?<level>[LXVI]*)\\)");
private static final HashMap<String, ItemStack> ICOS = new HashMap<>();
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenSkillsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenSkillsWidget.java
index e7058fd6..41eee8d6 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenSkillsWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/GardenSkillsWidget.java
@@ -24,9 +24,11 @@ public class GardenSkillsWidget extends Widget {
// group 1: skill name and level
// group 2: progress to next level (without "%")
private static final Pattern SKILL_PATTERN = Pattern
- .compile("\\S*: (?<skill>[A-Za-z]* [0-9]*): (?<progress>\\S*)%");
- // same, but with leading space
- private static final Pattern MS_PATTERN = Pattern.compile("\\S*: (?<skill>[A-Za-z]* [0-9]*): (?<progress>\\S*)%");
+ .compile("Skills: (?<skill>[A-Za-z]* [0-9]*): (?<progress>[0-9.MAX]*)%?");
+
+ // same, more or less
+ private static final Pattern MS_PATTERN = Pattern
+ .compile("Milestone: (?<milestone>[A-Za-z ]* [0-9]*): (?<progress>[0-9.]*)%");
public GardenSkillsWidget() {
super(TITLE, Formatting.YELLOW.getColorValue());
@@ -43,9 +45,14 @@ public class GardenSkillsWidget extends Widget {
String strpcnt = m.group("progress");
String skill = m.group("skill");
- float pcnt = Float.parseFloat(strpcnt);
- pc = new ProgressComponent(Ico.LANTERN, Text.of(skill), pcnt,
- Formatting.GOLD.getColorValue());
+ if (strpcnt.equals("MAX")) {
+ pc = new ProgressComponent(Ico.LANTERN, Text.of(skill), Text.of("MAX"), 100f,
+ Formatting.RED.getColorValue());
+ } else {
+ float pcnt = Float.parseFloat(strpcnt);
+ pc = new ProgressComponent(Ico.LANTERN, Text.of(skill), pcnt,
+ Formatting.GOLD.getColorValue());
+ }
}
this.addComponent(pc);
@@ -66,10 +73,10 @@ public class GardenSkillsWidget extends Widget {
pc2 = new ProgressComponent();
} else {
String strpcnt = m.group("progress");
- String skill = m.group("skill");
+ String milestone = m.group("milestone");
float pcnt = Float.parseFloat(strpcnt);
- pc2 = new ProgressComponent(Ico.MILESTONE, Text.of(skill), pcnt,
+ pc2 = new ProgressComponent(Ico.MILESTONE, Text.of(milestone), pcnt,
Formatting.GREEN.getColorValue());
}