aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerMod.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java22
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java8
-rw-r--r--src/main/java/de/hysky/skyblocker/config/configs/DungeonsConfig.java11
-rw-r--r--src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java12
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java11
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/PlayerSkinTextureMixin.java51
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/PetCache.java5
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java26
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/ControlTestHelper.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/Reparty.java172
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/LightsOn.java46
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/SimonSays.java122
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java5
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/itemlist/SearchResultsWidget.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java6
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java47
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerTextWidget.java17
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/GenericCategory.java71
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonClassWidget.java15
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonFloorRunsWidget.java35
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonMiscStatsWidgets.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonsPage.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Inventory.java19
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/InventoryPage.java16
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Pet.java275
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PlayerInventory.java27
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/BackpackItemLoader.java13
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/ItemLoader.java21
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillWidget.java17
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillsPage.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayerWidget.java14
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayersPage.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/LevelFinder.java17
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/ProfileViewerUtils.java (renamed from src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SkullCreator.java)18
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SubPageSelectButton.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/shortcut/Shortcuts.java19
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/Ico.java1
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/waypoint/Waypoints.java32
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsShareScreen.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/TextTransformer.java92
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/Utils.java126
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java20
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/waypoint/NamedWaypoint.java6
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/waypoint/WaypointCategory.java4
46 files changed, 1067 insertions, 377 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
index f1eb4321..8dd1419d 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
@@ -14,6 +14,8 @@ import de.hysky.skyblocker.skyblock.chocolatefactory.TimeTowerReminder;
import de.hysky.skyblocker.skyblock.crimson.dojo.DojoManager;
import de.hysky.skyblocker.skyblock.crimson.kuudra.Kuudra;
import de.hysky.skyblocker.skyblock.dungeon.*;
+import de.hysky.skyblocker.skyblock.dungeon.device.LightsOn;
+import de.hysky.skyblocker.skyblock.dungeon.device.SimonSays;
import de.hysky.skyblocker.skyblock.dungeon.partyfinder.PartyFinderScreen;
import de.hysky.skyblocker.skyblock.dungeon.puzzle.*;
import de.hysky.skyblocker.skyblock.dungeon.puzzle.boulder.Boulder;
@@ -151,6 +153,8 @@ public class SkyblockerMod implements ClientModInitializer {
Silverfish.init();
IceFill.init();
DungeonScore.init();
+ SimonSays.init();
+ LightsOn.init();
PartyFinderScreen.initClass();
ChestValue.init();
FireFreezeStaffTimer.init();
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
index 28ace441..017e9186 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java
@@ -269,6 +269,28 @@ public class DungeonsCategory {
.build())
.build())
+ // Devices (F7/M7)
+ .group(OptionGroup.createBuilder()
+ .name(Text.translatable("skyblocker.config.dungeons.devices"))
+ .collapsed(true)
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("skyblocker.config.dungeons.devices.solveSimonSays"))
+ .description(OptionDescription.of(Text.translatable("skyblocker.config.dungeons.devices.solveSimonSays.@Tooltip")))
+ .binding(defaults.dungeons.devices.solveSimonSays,
+ () -> config.dungeons.devices.solveSimonSays,
+ newValue -> config.dungeons.devices.solveSimonSays = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("skyblocker.config.dungeons.devices.solveLightsOn"))
+ .description(OptionDescription.of(Text.translatable("skyblocker.config.dungeons.devices.solveLightsOn.@Tooltip")))
+ .binding(defaults.dungeons.devices.solveLightsOn,
+ () -> config.dungeons.devices.solveLightsOn,
+ newValue -> config.dungeons.devices.solveLightsOn = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
+ .build())
+
// Dungeon Secret Waypoints
.group(OptionGroup.createBuilder()
.name(Text.translatable("skyblocker.config.dungeons.secretWaypoints"))
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java
index 56e2ed4d..ec2c561c 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java
@@ -197,6 +197,14 @@ public class HelperCategory {
newValue -> config.helpers.chocolateFactory.enableTimeTowerReminder = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("skyblocker.config.helpers.chocolateFactory.straySound"))
+ .description(OptionDescription.of(Text.translatable("skyblocker.config.helpers.chocolateFactory.straySound.@Tooltip")))
+ .binding(defaults.helpers.chocolateFactory.straySound,
+ () -> config.helpers.chocolateFactory.straySound,
+ newValue -> config.helpers.chocolateFactory.straySound = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
.build())
//Bazaar
diff --git a/src/main/java/de/hysky/skyblocker/config/configs/DungeonsConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/DungeonsConfig.java
index 7b394b53..1a0cad9d 100644
--- a/src/main/java/de/hysky/skyblocker/config/configs/DungeonsConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/configs/DungeonsConfig.java
@@ -42,6 +42,9 @@ public class DungeonsConfig {
public Terminals terminals = new Terminals();
@SerialEntry
+ public Devices devices = new Devices();
+
+ @SerialEntry
public SecretWaypoints secretWaypoints = new SecretWaypoints();
@SerialEntry
@@ -135,6 +138,14 @@ public class DungeonsConfig {
public boolean blockIncorrectClicks = false;
}
+ public static class Devices {
+ @SerialEntry
+ public boolean solveSimonSays = true;
+
+ @SerialEntry
+ public boolean solveLightsOn = true;
+ }
+
public static class SecretWaypoints {
@SerialEntry
public boolean enableRoomMatching = true;
diff --git a/src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java
index 6ddb1a74..e009f680 100644
--- a/src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java
@@ -93,6 +93,9 @@ public class HelperConfig {
@SerialEntry
public boolean enableTimeTowerReminder = true;
+
+ @SerialEntry
+ public boolean straySound = true;
}
public static class Bazaar {
diff --git a/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java
index 6c10e5d2..a2d7887b 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java
@@ -1,7 +1,7 @@
package de.hysky.skyblocker.mixins;
-
import de.hysky.skyblocker.skyblock.crimson.dojo.DojoManager;
+import de.hysky.skyblocker.skyblock.dungeon.device.SimonSays;
import de.hysky.skyblocker.utils.Utils;
import net.minecraft.block.BlockState;
import net.minecraft.client.world.ClientWorld;
@@ -11,13 +11,21 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import com.llamalad7.mixinextras.sugar.Local;
+
@Mixin(ClientWorld.class)
public class ClientWorldMixin {
+ /**
+ * @implNote The {@code pos} can be mutable when this is called by chunk delta updates, so if you want to copy it into memory
+ * (e.g. store it in a field/list/map) make sure to duplicate it via {@link BlockPos#toImmutable()}.
+ */
@Inject(method = "handleBlockUpdate", at = @At("RETURN"))
- private void skyblocker$handleBlockUpdate(BlockPos pos, BlockState state, int flags, CallbackInfo ci) {
+ private void skyblocker$handleBlockUpdate(CallbackInfo ci, @Local(argsOnly = true) BlockPos pos, @Local(argsOnly = true) BlockState state) {
if (Utils.isInCrimson()) {
DojoManager.onBlockUpdate(pos.toImmutable(), state);
}
+
+ SimonSays.onBlockUpdate(pos, state);
}
}
diff --git a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java
index f2e3e907..709b8697 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java
@@ -247,10 +247,21 @@ public abstract class HandledScreenMixin<T extends ScreenHandler> extends Screen
return;
}
// Prevent salvaging
+ // TODO in future maybe also block clicking the salvage button if a protected item manages to get into the menu
if (title.equals("Salvage Items") && ItemProtection.isItemProtected(stack)) {
ci.cancel();
return;
}
+ // Prevent Trading
+ if (title.startsWith("You ") && ItemProtection.isItemProtected(stack)) { //Terrible way to detect the trade menu lol
+ ci.cancel();
+ return;
+ }
+ // Prevent Auctioning
+ if ((title.equals("Create BIN Auction") || title.equals("Create Auction")) && ItemProtection.isItemProtected(stack)) {
+ ci.cancel();
+ return;
+ }
switch (this.handler) {
case GenericContainerScreenHandler genericContainerScreenHandler when genericContainerScreenHandler.getRows() == 6 -> {
diff --git a/src/main/java/de/hysky/skyblocker/mixins/PlayerSkinTextureMixin.java b/src/main/java/de/hysky/skyblocker/mixins/PlayerSkinTextureMixin.java
index 828d32e3..9b9691c5 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/PlayerSkinTextureMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/PlayerSkinTextureMixin.java
@@ -1,5 +1,8 @@
package de.hysky.skyblocker.mixins;
+import java.awt.Color;
+import java.util.Set;
+
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -9,32 +12,62 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
+import com.llamalad7.mixinextras.sugar.Share;
+import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.skyblock.item.PlayerHeadHashCache;
import de.hysky.skyblocker.utils.Utils;
import net.minecraft.client.texture.NativeImage;
import net.minecraft.client.texture.PlayerSkinTexture;
+import net.minecraft.util.math.ColorHelper;
@Mixin(PlayerSkinTexture.class)
public class PlayerSkinTextureMixin {
+ @Unique
+ private static final Set<String> STRIP_DE_FACTO_TRANSPARENT_PIXELS = Set.of(
+ "4f3b91b6aa7124f30ed4ad1b2bb012a82985a33640555e18e792f96af8f58ec6", /*Titanium Necklace*/
+ "49821410631186c6f3fbbae5f0ef5b947f475eb32027a8aad0a456512547c209", /*Titanium Cloak*/
+ "4162303bcdd770aebe7fd19fa26371390a7515140358548084361b5056cdc4e6" /*Titanium Belt*/);
+ @Unique
+ private static final float BRIGHTNESS_THRESHOLD = 0.1f;
+
@Shadow
@Final
private String url;
- @Unique
- private boolean isSkyblockSkinTexture;
-
@Inject(method = "remapTexture", at = @At("HEAD"))
- private void skyblocker$determineSkinSource(CallbackInfoReturnable<NativeImage> cir) {
- if (Utils.isOnSkyblock()) {
- int skinHash = PlayerHeadHashCache.getSkinHash(this.url).hashCode();
- this.isSkyblockSkinTexture = PlayerHeadHashCache.contains(skinHash);
+ private void skyblocker$determineSkinSource(NativeImage image, CallbackInfoReturnable<NativeImage> cir, @Share("isSkyblockSkinTexture") LocalBooleanRef isSkyblockSkinTexture) {
+ if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().uiAndVisuals.dontStripSkinAlphaValues) {
+ String skinTextureHash = PlayerHeadHashCache.getSkinHash(this.url);
+ int skinHash = skinTextureHash.hashCode();
+ isSkyblockSkinTexture.set(PlayerHeadHashCache.contains(skinHash));
+
+ //Hypixel had the grand idea of using black pixels in place of actual transparent pixels on the titanium equipment so here we go!
+ if (STRIP_DE_FACTO_TRANSPARENT_PIXELS.contains(skinTextureHash)) {
+ stripDeFactoTransparentPixels(image);
+ }
}
}
@WrapWithCondition(method = "remapTexture", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/texture/PlayerSkinTexture;stripAlpha(Lnet/minecraft/client/texture/NativeImage;IIII)V"))
- private boolean skyblocker$dontStripAlphaValues(NativeImage image, int x1, int y1, int x2, int y2) {
- return !(SkyblockerConfigManager.get().uiAndVisuals.dontStripSkinAlphaValues && this.isSkyblockSkinTexture);
+ private boolean skyblocker$dontStripAlphaValues(NativeImage image, int x1, int y1, int x2, int y2, @Share("isSkyblockSkinTexture") LocalBooleanRef isSkyblockSkinTexture) {
+ return !isSkyblockSkinTexture.get();
+ }
+
+ @Unique
+ private static void stripDeFactoTransparentPixels(NativeImage image) {
+ int height = image.getHeight();
+ int width = image.getWidth();
+
+ for (int x = 0; x < width; x++) {
+ for (int y = 0; y < height; y++) {
+ int color = image.getColor(x, y);
+ float[] hsb = Color.RGBtoHSB(ColorHelper.Abgr.getRed(color), ColorHelper.Abgr.getGreen(color), ColorHelper.Abgr.getBlue(color), null);
+
+ //Work around "fake" transparent pixels - Thanks Hypixel I totally appreciate this!
+ if (hsb[2] <= BRIGHTNESS_THRESHOLD) image.setColor(x, y, ColorHelper.Abgr.withAlpha(0x00, color & 0x00FFFFFF));
+ }
+ }
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/PetCache.java b/src/main/java/de/hysky/skyblocker/skyblock/PetCache.java
index 8d0406cb..8ddcd60e 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/PetCache.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/PetCache.java
@@ -133,13 +133,14 @@ public class PetCache {
return CACHED_PETS.containsKey(uuid) && CACHED_PETS.get(uuid).containsKey(profileId) ? CACHED_PETS.get(uuid).get(profileId) : null;
}
- public record PetInfo(String type, double exp, String tier, Optional<String> uuid, Optional<String> item) {
+ public record PetInfo(String type, double exp, String tier, Optional<String> uuid, Optional<String> item, Optional<String> skin) {
public static final Codec<PetInfo> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.STRING.fieldOf("type").forGetter(PetInfo::type),
Codec.DOUBLE.fieldOf("exp").forGetter(PetInfo::exp),
Codec.STRING.fieldOf("tier").forGetter(PetInfo::tier),
Codec.STRING.optionalFieldOf("uuid").forGetter(PetInfo::uuid),
- Codec.STRING.optionalFieldOf("heldItem").forGetter(PetInfo::item))
+ Codec.STRING.optionalFieldOf("heldItem").forGetter(PetInfo::item),
+ Codec.STRING.optionalFieldOf("skin").forGetter(PetInfo::skin))
.apply(instance, PetInfo::new));
private static final Codec<Object2ObjectOpenHashMap<String, Object2ObjectOpenHashMap<String, PetInfo>>> SERIALIZATION_CODEC = Codec.unboundedMap(Codec.STRING,
Codec.unboundedMap(Codec.STRING, CODEC).xmap(Object2ObjectOpenHashMap::new, Object2ObjectOpenHashMap::new)
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java
index f984d751..db81382c 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/ChocolateFactorySolver.java
@@ -9,9 +9,13 @@ import de.hysky.skyblocker.utils.render.gui.ColorHighlight;
import de.hysky.skyblocker.utils.render.gui.ContainerSolver;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.sound.PositionedSoundInstance;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.screen.slot.Slot;
+import net.minecraft.sound.SoundEvents;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
@@ -49,6 +53,7 @@ public class ChocolateFactorySolver extends ContainerSolver {
private static boolean isTimeTowerActive = false;
private static int bestUpgrade = -1;
private static int bestAffordableUpgrade = -1;
+ private static StraySound ding = StraySound.NONE;
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#,###.#", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
@Override
@@ -65,6 +70,7 @@ public class ChocolateFactorySolver extends ContainerSolver {
isTimeTowerActive = false;
bestUpgrade = -1;
bestAffordableUpgrade = -1;
+ ding = StraySound.NONE;
}
//Slots, for ease of maintenance rather than using magic numbers everywhere.
@@ -78,8 +84,20 @@ public class ChocolateFactorySolver extends ContainerSolver {
private static final byte STRAY_RABBIT_START = 0;
private static final byte STRAY_RABBIT_END = 26;
+ private static int dingTick = 0;
+
public ChocolateFactorySolver() {
super("^Chocolate Factory$"); //There are multiple screens that fit the pattern `^Chocolate Factory`, so the $ is required
+ ClientTickEvents.START_CLIENT_TICK.register(ChocolateFactorySolver::onTick);
+ }
+
+ private static void onTick(MinecraftClient client) {
+ if (ding != StraySound.NONE) {
+ dingTick = (++dingTick) % (ding == StraySound.NORMAL ? 5 : 3);
+ if (dingTick == 0) {
+ client.getSoundManager().play(PositionedSoundInstance.master(ding == StraySound.NORMAL ? SoundEvents.BLOCK_NOTE_BLOCK_PLING.value() : SoundEvents.BLOCK_NOTE_BLOCK_HARP.value(), 1.f, 1.f));
+ }
+ }
}
@Override
@@ -239,12 +257,14 @@ public class ChocolateFactorySolver extends ContainerSolver {
}
private static List<ColorHighlight> getStrayRabbitHighlight(Int2ObjectMap<ItemStack> slots) {
+ ding = StraySound.NONE;
final List<ColorHighlight> highlights = new ArrayList<>();
for (byte i = STRAY_RABBIT_START; i <= STRAY_RABBIT_END; i++) {
ItemStack item = slots.get(i);
if (!item.isOf(Items.PLAYER_HEAD)) continue;
String name = item.getName().getString();
if (name.equals("CLICK ME!") || name.startsWith("Golden Rabbit - ")) {
+ if (SkyblockerConfigManager.get().helpers.chocolateFactory.straySound) ding = name.startsWith("Golden") ? StraySound.GOLDEN : StraySound.NORMAL;
highlights.add(ColorHighlight.green(i));
}
}
@@ -253,6 +273,12 @@ public class ChocolateFactorySolver extends ContainerSolver {
private record Rabbit(double cpsIncrease, long cost, int slot) { }
+ private enum StraySound {
+ NONE,
+ NORMAL,
+ GOLDEN
+ }
+
public static final class Tooltip extends TooltipAdder {
public Tooltip() {
super("^Chocolate Factory$", 0); //The priority doesn't really matter here as this is the only tooltip adder for the Chocolate Factory.
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/ControlTestHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/ControlTestHelper.java
index 2f616a1e..f63d2fa2 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/ControlTestHelper.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/crimson/dojo/ControlTestHelper.java
@@ -67,7 +67,7 @@ public class ControlTestHelper {
float tickDelta = context.tickCounter().getTickDelta(false);
//how long until net update
double updatePercent = (double) (System.currentTimeMillis() - lastUpdate) / 150;
- Vec3d aimPos = correctWitherSkeleton.getEyePos().add(pingOffset.multiply(updatePercent)).add(lastPingOffset.multiply(1 - updatePercent));
+ Vec3d aimPos = correctWitherSkeleton.getCameraPosVec(tickDelta).add(pingOffset.multiply(updatePercent)).add(lastPingOffset.multiply(1 - updatePercent));
Box targetBox = new Box(aimPos.add(-0.5, -0.5, -0.5), aimPos.add(0.5, 0.5, 0.5));
boolean playerLookingAtBox = targetBox.raycast(CLIENT.player.getCameraPosVec(tickDelta), CLIENT.player.getCameraPosVec(tickDelta).add(CLIENT.player.getRotationVec(tickDelta).multiply(30))).isPresent();
float[] boxColor = playerLookingAtBox ? Color.GREEN.getColorComponents(new float[]{0, 0, 0}) : Color.LIGHT_GRAY.getColorComponents(new float[]{0, 0, 0});
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/Reparty.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/Reparty.java
index 6165ac6a..80753c1d 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/Reparty.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/Reparty.java
@@ -1,94 +1,112 @@
package de.hysky.skyblocker.skyblock.dungeon;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.Constants;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.chat.ChatFilterResult;
import de.hysky.skyblocker.utils.chat.ChatPatternListener;
import de.hysky.skyblocker.utils.scheduler.MessageScheduler;
import de.hysky.skyblocker.utils.scheduler.Scheduler;
+import net.azureaaron.hmapi.data.party.PartyRole;
+import net.azureaaron.hmapi.events.HypixelPacketEvents;
+import net.azureaaron.hmapi.network.HypixelNetworking;
+import net.azureaaron.hmapi.network.packet.s2c.ErrorS2CPacket;
+import net.azureaaron.hmapi.network.packet.s2c.HypixelS2CPacket;
+import net.azureaaron.hmapi.network.packet.v2.s2c.PartyInfoS2CPacket;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.text.Text;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+
+import org.slf4j.Logger;
+
+import com.mojang.brigadier.Command;
+import com.mojang.logging.LogUtils;
public class Reparty extends ChatPatternListener {
- private static final MinecraftClient client = MinecraftClient.getInstance();
- public static final Pattern PLAYER = Pattern.compile(" ([a-zA-Z0-9_]{2,16}) ●");
- private static final int BASE_DELAY = 10;
-
- private String[] players;
- private int playersSoFar;
- private boolean repartying;
- private String partyLeader;
-
- public Reparty() {
- super("^(?:You are not currently in a party\\." +
- "|Party (?:Membe|Moderato)rs(?: \\(([0-9]+)\\)|:( .*))" +
- "|([\\[A-z+\\]]* )?(?<disband>.*) has disbanded .*" +
- "|.*\n([\\[A-z+\\]]* )?(?<invite>.*) has invited you to join their party!" +
- "\nYou have 60 seconds to accept. Click here to join!\n.*)$");
-
- this.repartying = false;
- ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal("rp").executes(context -> {
- if (!Utils.isOnSkyblock() || this.repartying || client.player == null) return 0;
- this.repartying = true;
- MessageScheduler.INSTANCE.sendMessageAfterCooldown("/p list");
- return 0;
- })));
- }
-
- @Override
- public ChatFilterResult state() {
- return (SkyblockerConfigManager.get().general.acceptReparty || this.repartying) ? ChatFilterResult.FILTER : ChatFilterResult.PASS;
- }
-
- @Override
- public boolean onMatch(Text message, Matcher matcher) {
- if (matcher.group(1) != null && repartying) {
- this.playersSoFar = 0;
- this.players = new String[Integer.parseInt(matcher.group(1)) - 1];
- } else if (matcher.group(2) != null && repartying) {
- Matcher m = PLAYER.matcher(matcher.group(2));
- while (m.find()) {
- this.players[playersSoFar++] = m.group(1);
- }
- } else if (matcher.group("disband") != null && !matcher.group("disband").equals(client.getSession().getUsername())) {
- partyLeader = matcher.group("disband");
- Scheduler.INSTANCE.schedule(() -> partyLeader = null, 61);
- return false;
- } else if (matcher.group("invite") != null && matcher.group("invite").equals(partyLeader)) {
- String command = "/party accept " + partyLeader;
- sendCommand(command, 0);
- return false;
- } else {
- this.repartying = false;
- return false;
- }
- if (this.playersSoFar == this.players.length) {
- reparty();
- }
- return false;
- }
-
- private void reparty() {
- ClientPlayerEntity playerEntity = client.player;
- if (playerEntity == null) {
- this.repartying = false;
- return;
- }
- sendCommand("/p disband", 1);
- for (int i = 0; i < this.players.length; ++i) {
- String command = "/p invite " + this.players[i];
- sendCommand(command, i + 2);
- }
- Scheduler.INSTANCE.schedule(() -> this.repartying = false, this.players.length + 2);
- }
-
- private void sendCommand(String command, int delay) {
- MessageScheduler.INSTANCE.queueMessage(command, delay * BASE_DELAY);
- }
+ private static final Logger LOGGER = LogUtils.getLogger();
+ private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
+ private static final int BASE_DELAY = 10;
+
+ private boolean repartying;
+ private String partyLeader;
+
+ public Reparty() {
+ super("^(?:([\\[A-z+\\]]* )?(?<disband>.*) has disbanded .*" +
+ "|.*\n([\\[A-z+\\]]* )?(?<invite>.*) has invited you to join their party!" +
+ "\nYou have 60 seconds to accept. Click here to join!\n.*)$");
+
+ this.repartying = false;
+ HypixelPacketEvents.PARTY_INFO.register(this::onPacket);
+ ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal("rp").executes(context -> {
+ if (!Utils.isOnSkyblock() || this.repartying || CLIENT.player == null) return 0;
+
+ this.repartying = true;
+ HypixelNetworking.sendPartyInfoC2SPacket(2);
+
+ return Command.SINGLE_SUCCESS;
+ })));
+ }
+
+ private void onPacket(HypixelS2CPacket packet) {
+ switch (packet) {
+ case PartyInfoS2CPacket(var inParty, var members) when this.repartying -> {
+ UUID ourUuid = Objects.requireNonNull(CLIENT.getSession().getUuidOrNull());
+
+ if (inParty && members.get(ourUuid) == PartyRole.LEADER) {
+ sendCommand("/p disband", 1);
+ int count = 0;
+
+ for (Map.Entry<UUID, PartyRole> entry : members.entrySet()) {
+ UUID uuid = entry.getKey();
+ PartyRole role = entry.getValue();
+
+ //Don't invite ourself
+ if (role != PartyRole.LEADER) sendCommand("/p " + uuid.toString(), ++count + 2);
+ }
+
+ Scheduler.INSTANCE.schedule(() -> this.repartying = false, count * BASE_DELAY);
+ } else {
+ CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.reparty.notInPartyOrNotLeader")));
+ this.repartying = false;
+ }
+ }
+
+ case ErrorS2CPacket(var id, var error) when id.equals(PartyInfoS2CPacket.ID) && this.repartying -> {
+ CLIENT.player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.reparty.error")));
+ LOGGER.error("[Skyblocker Reparty] The party info packet returned an unexpected error! {}", error);
+
+ this.repartying = false;
+ }
+
+ default -> {} //Do nothing
+ }
+ }
+
+ @Override
+ public ChatFilterResult state() {
+ return SkyblockerConfigManager.get().general.acceptReparty ? ChatFilterResult.FILTER : ChatFilterResult.PASS;
+ }
+
+ @Override
+ public boolean onMatch(Text message, Matcher matcher) {
+ if (matcher.group("disband") != null && !matcher.group("disband").equals(CLIENT.getSession().getUsername())) {
+ partyLeader = matcher.group("disband");
+ Scheduler.INSTANCE.schedule(() -> partyLeader = null, 61);
+ } else if (matcher.group("invite") != null && matcher.group("invite").equals(partyLeader)) {
+ String command = "/party accept " + partyLeader;
+ sendCommand(command, 0);
+ }
+
+ return false;
+ }
+
+ private void sendCommand(String command, int delay) {
+ MessageScheduler.INSTANCE.queueMessage(command, delay * BASE_DELAY);
+ }
} \ No newline at end of file
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/LightsOn.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/LightsOn.java
new file mode 100644
index 00000000..555a8e4b
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/LightsOn.java
@@ -0,0 +1,46 @@
+package de.hysky.skyblocker.skyblock.dungeon.device;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.dungeon.DungeonBoss;
+import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager;
+import de.hysky.skyblocker.utils.ColorUtils;
+import de.hysky.skyblocker.utils.Utils;
+import de.hysky.skyblocker.utils.render.RenderHelper;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
+import net.minecraft.block.BlockState;
+import net.minecraft.block.Blocks;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.world.ClientWorld;
+import net.minecraft.state.property.Properties;
+import net.minecraft.util.DyeColor;
+import net.minecraft.util.math.BlockPos;
+
+public class LightsOn {
+ private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
+ private static final BlockPos TOP_LEFT = new BlockPos(62, 136, 142);
+ private static final BlockPos TOP_RIGHT = new BlockPos(58, 136, 142);
+ private static final BlockPos MIDDLE_TOP = new BlockPos(60, 135, 142);
+ private static final BlockPos MIDDLE_BOTTOM = new BlockPos(60, 134, 142);
+ private static final BlockPos BOTTOM_LEFT = new BlockPos(62, 133, 142);
+ private static final BlockPos BOTTOM_RIGHT = new BlockPos(58, 133, 142);
+ private static final BlockPos[] LEVERS = { TOP_LEFT, TOP_RIGHT, MIDDLE_TOP, MIDDLE_BOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT };
+ private static final float[] RED = ColorUtils.getFloatComponents(DyeColor.RED);
+
+ public static void init() {
+ WorldRenderEvents.AFTER_TRANSLUCENT.register(LightsOn::render);
+ }
+
+ private static void render(WorldRenderContext context) {
+ if (SkyblockerConfigManager.get().dungeons.devices.solveLightsOn && Utils.isInDungeons() && DungeonManager.isInBoss() && DungeonManager.getBoss() == DungeonBoss.MAXOR) {
+ for (BlockPos lever : LEVERS) {
+ ClientWorld world = CLIENT.world;
+ BlockState state = world.getBlockState(lever);
+
+ if (state.getBlock().equals(Blocks.LEVER) && state.contains(Properties.POWERED) && !state.get(Properties.POWERED)) {
+ RenderHelper.renderFilled(context, lever, RED, 0.5f, false);
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/SimonSays.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/SimonSays.java
new file mode 100644
index 00000000..5aa97dd9
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/SimonSays.java
@@ -0,0 +1,122 @@
+package de.hysky.skyblocker.skyblock.dungeon.device;
+
+import java.util.Objects;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.dungeon.DungeonBoss;
+import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager;
+import de.hysky.skyblocker.utils.Boxes;
+import de.hysky.skyblocker.utils.ColorUtils;
+import de.hysky.skyblocker.utils.Utils;
+import de.hysky.skyblocker.utils.render.RenderHelper;
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import it.unimi.dsi.fastutil.objects.ObjectList;
+import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
+import it.unimi.dsi.fastutil.objects.ObjectSet;
+import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
+import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
+import net.fabricmc.fabric.api.event.player.UseBlockCallback;
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockState;
+import net.minecraft.block.Blocks;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.world.ClientWorld;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.util.ActionResult;
+import net.minecraft.util.DyeColor;
+import net.minecraft.util.Hand;
+import net.minecraft.util.hit.BlockHitResult;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Box;
+import net.minecraft.util.math.Vec3d;
+import net.minecraft.world.World;
+
+public class SimonSays {
+ private static final Box BOARD_AREA = Box.enclosing(new BlockPos(111, 123, 92), new BlockPos(111, 120, 95));
+ private static final Box BUTTONS_AREA = Box.enclosing(new BlockPos(110, 123, 92), new BlockPos(110, 120, 95));
+ private static final BlockPos START_BUTTON = new BlockPos(110, 121, 91);
+ private static final float[] GREEN = ColorUtils.getFloatComponents(DyeColor.LIME);
+ private static final float[] YELLOW = ColorUtils.getFloatComponents(DyeColor.YELLOW);
+ private static final ObjectSet<BlockPos> CLICKED_BUTTONS = new ObjectOpenHashSet<>();
+ private static final ObjectList<BlockPos> SIMON_PATTERN = new ObjectArrayList<>();
+
+ public static void init() {
+ UseBlockCallback.EVENT.register(SimonSays::onBlockInteract);
+ ClientPlayConnectionEvents.JOIN.register((_handler, _sender, _client) -> reset());
+ WorldRenderEvents.AFTER_TRANSLUCENT.register(SimonSays::render);
+ }
+
+ //When another player is pressing the buttons hypixel doesnt send block or block state updates
+ //so you can't see it which means the solver can only count the buttons you press yourself
+ private static ActionResult onBlockInteract(PlayerEntity player, World world, Hand hand, BlockHitResult hitResult) {
+ if (shouldProcess()) {
+ BlockPos pos = hitResult.getBlockPos();
+ Block block = world.getBlockState(pos).getBlock();
+
+ if (block.equals(Blocks.STONE_BUTTON)) {
+ if (BUTTONS_AREA.contains(Vec3d.of(pos))) {
+ CLICKED_BUTTONS.add(new BlockPos(pos)); //Copy just in case it becomes mutable in the future
+ } else if (pos.equals(START_BUTTON)) {
+ reset();
+ }
+ }
+ }
+
+ //This could also be used to cancel incorrect clicks in the future
+ return ActionResult.PASS;
+ }
+
+ //If the player goes out of the range required to receive block/chunk updates then their solver won't detect stuff but that
+ //doesn't matter because if they're doing pre-4 or something they won't be doing the ss, and if they end up needing to they can
+ //just reset it or have the other person finish the current sequence first then let them do it.
+ public static void onBlockUpdate(BlockPos pos, BlockState state) {
+ if (shouldProcess()) {
+ Vec3d posVec = Vec3d.of(pos);
+ Block block = state.getBlock();
+
+ if (BOARD_AREA.contains(posVec) && block.equals(Blocks.SEA_LANTERN)) {
+ SIMON_PATTERN.add(pos.toImmutable()); //Convert to immutable because chunk delta updates use the mutable variant
+ } else if (BUTTONS_AREA.contains(posVec) && block.equals(Blocks.AIR)) {
+ //Upon reaching the showing of the next sequence we need to reset the state so that we don't show old data
+ //Otherwise, the nextIndex will go beyond 5 and that can cause bugs, it also helps with the other case noted above
+ reset();
+ }
+ }
+ }
+
+ private static void render(WorldRenderContext context) {
+ if (shouldProcess()) {
+ int buttonsRendered = 0;
+
+ for (BlockPos pos : SIMON_PATTERN) {
+ //Offset to west (x - 1) to get the position of the button from the sea lantern block
+ BlockPos buttonPos = pos.west();
+ ClientWorld world = Objects.requireNonNull(MinecraftClient.getInstance().world); //Should never be null here
+ BlockState state = world.getBlockState(buttonPos);
+
+ //If the button hasn't been clicked yet
+ //Also don't do anything if the button isn't there which means the device is showing the sequence
+ if (!CLICKED_BUTTONS.contains(buttonPos) && state.getBlock().equals(Blocks.STONE_BUTTON)) {
+ Box outline = RenderHelper.getBlockBoundingBox(world, state, buttonPos);
+ float[] colour = buttonsRendered == 0 ? GREEN : YELLOW;
+
+ RenderHelper.renderFilled(context, Boxes.getMinVec(outline), Boxes.getLengthVec(outline), colour, 0.5f, true);
+ RenderHelper.renderOutline(context, outline, colour, 5f, true);
+
+ if (++buttonsRendered == 2) return;
+ }
+ }
+ }
+ }
+
+ private static boolean shouldProcess() {
+ return SkyblockerConfigManager.get().dungeons.devices.solveSimonSays &&
+ Utils.isInDungeons() && DungeonManager.isInBoss() && DungeonManager.getBoss() == DungeonBoss.MAXOR;
+ }
+
+ private static void reset() {
+ CLICKED_BUTTONS.clear();
+ SIMON_PATTERN.clear();
+ }
+}
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 01ffc144..ab61b684 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/ItemStackBuilder.java
@@ -2,6 +2,7 @@ package de.hysky.skyblocker.skyblock.itemlist;
import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.NEURepoManager;
+import de.hysky.skyblocker.utils.TextTransformer;
import io.github.moulberry.repo.constants.PetNumbers;
import io.github.moulberry.repo.data.NEUItem;
import io.github.moulberry.repo.data.Rarity;
@@ -53,10 +54,10 @@ public class ItemStackBuilder {
// Item Name
String name = injectData(item.getDisplayName(), injectors);
- stack.set(DataComponentTypes.CUSTOM_NAME, Text.of(name));
+ stack.set(DataComponentTypes.CUSTOM_NAME, TextTransformer.fromLegacy(name));
// Lore
- stack.set(DataComponentTypes.LORE, new LoreComponent(item.getLore().stream().map(line -> Text.of(injectData(line, injectors))).toList()));
+ stack.set(DataComponentTypes.LORE, new LoreComponent(item.getLore().stream().map(line -> TextTransformer.fromLegacy(injectData(line, injectors))).map(Text.class::cast).toList()));
String nbttag = item.getNbttag();
// add skull texture
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 dfb53628..bb236526 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SearchResultsWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/SearchResultsWidget.java
@@ -73,6 +73,8 @@ public class SearchResultsWidget implements Drawable, Element {
}
protected void updateSearchResult(String searchText) {
+ searchText = searchText.toLowerCase(Locale.ENGLISH);
+
if (!searchText.equals(this.searchText)) {
this.searchText = searchText;
this.searchResults.clear();
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java
index d867a0e6..16f7eb28 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java
@@ -1,7 +1,7 @@
package de.hysky.skyblocker.skyblock.profileviewer;
import com.mojang.blaze3d.systems.RenderSystem;
-import de.hysky.skyblocker.skyblock.profileviewer.utils.SkullCreator;
+import de.hysky.skyblocker.skyblock.profileviewer.utils.ProfileViewerUtils;
import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder;
@@ -22,9 +22,9 @@ public class ProfileViewerNavButton extends ClickableWidget {
private static final Map<String, ItemStack> HEAD_ICON = Map.ofEntries(
Map.entry("Skills", Ico.IRON_SWORD),
- Map.entry("Slayers", SkullCreator.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzkzMzZkN2NjOTVjYmY2Njg5ZjVlOGM5NTQyOTRlYzhkMWVmYzQ5NGE0MDMxMzI1YmI0MjdiYzgxZDU2YTQ4NGQifX19")),
+ Map.entry("Slayers", ProfileViewerUtils.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzkzMzZkN2NjOTVjYmY2Njg5ZjVlOGM5NTQyOTRlYzhkMWVmYzQ5NGE0MDMxMzI1YmI0MjdiYzgxZDU2YTQ4NGQifX19")),
Map.entry("Pets", Ico.BONE),
- Map.entry("Dungeons", SkullCreator.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzliNTY4OTViOTY1OTg5NmFkNjQ3ZjU4NTk5MjM4YWY1MzJkNDZkYjljMWIwMzg5YjhiYmViNzA5OTlkYWIzM2QifX19")),
+ Map.entry("Dungeons", ProfileViewerUtils.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzliNTY4OTViOTY1OTg5NmFkNjQ3ZjU4NTk5MjM4YWY1MzJkNDZkYjljMWIwMzg5YjhiYmViNzA5OTlkYWIzM2QifX19")),
Map.entry("Inventories", Ico.E_CHEST),
Map.entry("Collections", Ico.PAINTING)
);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java
index 1d0b21ca..f74526a4 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java
@@ -27,6 +27,7 @@ import net.minecraft.client.gui.widget.ClickableWidget;
import net.minecraft.client.network.OtherClientPlayerEntity;
import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.client.util.SkinTextures;
+import net.minecraft.command.CommandSource;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerModelPart;
import net.minecraft.text.Text;
@@ -55,6 +56,7 @@ public class ProfileViewerScreen extends Screen {
private String playerName;
private JsonObject hypixelProfile;
private JsonObject playerProfile;
+ private boolean profileNotFound = false;
private int activePage = 0;
private static final String[] PAGE_NAMES = {"Skills", "Slayers", "Dungeons", "Inventories", "Collections"};
@@ -73,6 +75,8 @@ public class ProfileViewerScreen extends Screen {
}
private void initialisePagesAndWidgets() {
+ if (profileNotFound) return;
+
textWidget = new ProfileViewerTextWidget(hypixelProfile, playerProfile);
CompletableFuture<Void> skillsFuture = CompletableFuture.runAsync(() -> profileViewerPages[0] = new SkillsPage(hypixelProfile, playerProfile));
@@ -105,6 +109,7 @@ public class ProfileViewerScreen extends Screen {
button.render(context, mouseX, mouseY, delta);
}
+
if (textWidget != null) textWidget.render(context, textRenderer, rootX + 8, rootY + 120);
drawPlayerEntity(context, playerName != null ? playerName : "Loading...", rootX, rootY, mouseX, mouseY);
@@ -112,7 +117,7 @@ public class ProfileViewerScreen extends Screen {
profileViewerPages[activePage].markWidgetsAsVisible();
profileViewerPages[activePage].render(context, mouseX, mouseY, delta, rootX + 93, rootY + 7);
} else {
- context.drawText(textRenderer, "Loading...", rootX + 180, rootY + 80, Color.WHITE.getRGB(), true);
+ context.drawText(textRenderer, profileNotFound ? "No Profile" : "Loading...", rootX + 180, rootY + 80, Color.WHITE.getRGB(), true);
}
}
@@ -120,18 +125,24 @@ public class ProfileViewerScreen extends Screen {
if (entity != null)
drawEntity(context, rootX + 9, rootY + 16, rootX + 89, rootY + 124, 42, 0.0625F, mouseX, mouseY, entity);
context.drawCenteredTextWithShadow(textRenderer, username.length() > 15 ? username.substring(0, 15) : username, rootX + 47, rootY + 14, Color.WHITE.getRGB());
-
}
private CompletableFuture<Void> fetchPlayerData(String username) {
CompletableFuture<Void> profileFuture = ProfileUtils.fetchFullProfile(username).thenAccept(profiles -> {
- this.hypixelProfile = profiles.getAsJsonArray("profiles").asList().stream()
- .map(JsonElement::getAsJsonObject)
- .filter(profile -> profile.getAsJsonPrimitive("selected").getAsBoolean())
- .findFirst()
- .orElseThrow(() -> new IllegalStateException("No selected profile found!"));
-
- this.playerProfile = hypixelProfile.getAsJsonObject("members").get(ApiUtils.name2Uuid(username)).getAsJsonObject();
+ try {
+ Optional<JsonObject> selectedProfile = profiles.getAsJsonArray("profiles").asList().stream()
+ .map(JsonElement::getAsJsonObject)
+ .filter(profile -> profile.getAsJsonPrimitive("selected").getAsBoolean())
+ .findFirst();
+
+ if (selectedProfile.isPresent()) {
+ this.hypixelProfile = selectedProfile.get();
+ this.playerProfile = hypixelProfile.getAsJsonObject("members").get(ApiUtils.name2Uuid(username)).getAsJsonObject();
+ }
+ } catch (Exception e) {
+ this.profileNotFound = true;
+ LOGGER.warn("[Skyblocker Profile Viewer] Error while looking for profile", e);
+ }
});
CompletableFuture<Void> minecraftProfileFuture = SkullBlockEntityAccessor.invokeFetchProfileByName(username).thenAccept(profile -> {
@@ -156,12 +167,14 @@ public class ProfileViewerScreen extends Screen {
entity.setCustomNameVisible(false);
}).exceptionally(ex -> {
this.playerName = "User not found";
+ this.profileNotFound = true;
return null;
});
return CompletableFuture.allOf(profileFuture, minecraftProfileFuture);
}
+
public void onNavButtonClick(ProfileViewerNavButton clickedButton) {
if (profileViewerPages[activePage] != null) profileViewerPages[activePage].markWidgetsAsInvisible();
for (ProfileViewerNavButton button : profileViewerNavButtons) {
@@ -188,10 +201,11 @@ public class ProfileViewerScreen extends Screen {
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
LiteralArgumentBuilder<FabricClientCommandSource> literalArgumentBuilder = ClientCommandManager.literal("pv")
- .then(ClientCommandManager.argument("username", StringArgumentType.string())
- .executes(Scheduler.queueOpenScreenFactoryCommand(context -> new ProfileViewerScreen(StringArgumentType.getString(context, "username"))))
- )
- .executes(Scheduler.queueOpenScreenCommand(() -> new ProfileViewerScreen(MinecraftClient.getInstance().getSession().getUsername())));
+ .then(ClientCommandManager.argument("username", StringArgumentType.string())
+ .suggests((source, builder) -> CommandSource.suggestMatching(getPlayerSuggestions(source.getSource()), builder))
+ .executes(Scheduler.queueOpenScreenFactoryCommand(context -> new ProfileViewerScreen(StringArgumentType.getString(context, "username"))))
+ )
+ .executes(Scheduler.queueOpenScreenCommand(() -> new ProfileViewerScreen(MinecraftClient.getInstance().getSession().getUsername())));
dispatcher.register(literalArgumentBuilder);
dispatcher.register(ClientCommandManager.literal(SkyblockerMod.NAMESPACE).then(literalArgumentBuilder));
});
@@ -227,4 +241,11 @@ public class ProfileViewerScreen extends Screen {
}
return Collections.emptyMap();
}
+
+ /**
+ * Ensures that "dummy" players aren't included in command suggestions
+ */
+ private static String[] getPlayerSuggestions(FabricClientCommandSource source) {
+ return source.getPlayerNames().stream().filter(playerName -> playerName.matches("[A-Za-z0-9_]+")).toArray(String[]::new);
+ }
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerTextWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerTextWidget.java
index 4ee2dbba..58c238f8 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerTextWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerTextWidget.java
@@ -1,6 +1,7 @@
package de.hysky.skyblocker.skyblock.profileviewer;
import com.google.gson.JsonObject;
+import de.hysky.skyblocker.skyblock.profileviewer.utils.ProfileViewerUtils;
import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
@@ -36,20 +37,8 @@ public class ProfileViewerTextWidget {
context.drawText(textRenderer, "§n"+PROFILE_NAME, root_x + 14, root_y + 3, Colors.WHITE, true);
context.drawText(textRenderer, "§aLevel:§r " + SKYBLOCK_LEVEL, root_x + 2, root_y + 6 + ROW_GAP, Colors.WHITE, true);
- context.drawText(textRenderer, "§6Purse:§r " + formatCoins(PURSE), root_x + 2, root_y + 6 + ROW_GAP * 2, Colors.WHITE, true);
- context.drawText(textRenderer, "§6Bank:§r " + formatCoins(BANK), root_x + 2, root_y + 6 + ROW_GAP * 3, Colors.WHITE, true);
+ context.drawText(textRenderer, "§6Purse:§r " + ProfileViewerUtils.numLetterFormat(PURSE), root_x + 2, root_y + 6 + ROW_GAP * 2, Colors.WHITE, true);
+ context.drawText(textRenderer, "§6Bank:§r " + ProfileViewerUtils.numLetterFormat(BANK), root_x + 2, root_y + 6 + ROW_GAP * 3, Colors.WHITE, true);
context.drawText(textRenderer, "§6NW:§r " + "Soon™", root_x + 2, root_y + 6 + ROW_GAP * 4, Colors.WHITE, true );
}
-
- private String formatCoins(double amount) {
- if (amount >= 1_000_000_000) {
- return String.format("%.4gB", amount / 1_000_000_000);
- } else if (amount >= 1_000_000) {
- return String.format("%.4gM", amount / 1_000_000);
- } else if (amount >= 1_000) {
- return String.format("%.4gK", amount / 1_000);
- } else {
- return String.valueOf((int)(amount));
- }
- }
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/GenericCategory.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/GenericCategory.java
index ef26332e..a9f83ca8 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/GenericCategory.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/collections/GenericCategory.java
@@ -5,13 +5,12 @@ import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.skyblock.itemlist.ItemRepository;
import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage;
import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
-import de.hysky.skyblocker.utils.NEURepoManager;
-import io.github.moulberry.repo.data.NEUItem;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.LoreComponent;
+import net.minecraft.component.type.NbtComponent;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.tooltip.TooltipType;
@@ -21,17 +20,18 @@ import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
import java.awt.*;
-import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.List;
-import java.util.*;
+import java.util.Map;
import static de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen.fetchCollectionsData;
+import static de.hysky.skyblocker.skyblock.profileviewer.utils.ProfileViewerUtils.COMMA_FORMATTER;
public class GenericCategory implements ProfileViewerPage {
private final String category;
private final LinkedList<ItemStack> collections = new LinkedList<>();
private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
- private static final NumberFormat FORMATTER = NumberFormat.getInstance(Locale.US);
private static final Identifier BUTTON_TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/button_icon_toggled.png");
private static final int COLUMN_GAP = 26;
private static final int ROW_GAP = 34;
@@ -61,40 +61,50 @@ public class GenericCategory implements ProfileViewerPage {
JsonObject playerCollection = pProfile.getAsJsonObject("collection");
for (String collection : collectionsMap.get(this.category)) {
- Map<String, NEUItem> items = NEURepoManager.NEU_REPO.getItems().getItems();
- ItemStack itemStack = items.values().stream()
- .filter(i -> Formatting.strip(i.getSkyblockItemId()).equals(ICON_TRANSLATION.getOrDefault(collection, collection).replace(':', '-')))
- .findFirst()
- .map(NEUItem::getSkyblockItemId)
- .map(ItemRepository::getItemStack)
- .map(ItemStack::copy)
- .orElse(Ico.BARRIER.copy());
+ ItemStack itemStack = ItemRepository.getItemStack(ICON_TRANSLATION.getOrDefault(collection, collection).replace(':', '-'));
+ itemStack = itemStack == null ? Ico.BARRIER.copy() : itemStack.copy();
+
+ if (itemStack.getItem().getName().getString().equals("Barrier")) {
+ itemStack.set(DataComponentTypes.CUSTOM_NAME, Text.of(collection));
+ System.out.println(collection);
+ System.out.println(this.category);
+ }
+
+ Style style = Style.EMPTY.withColor(Formatting.WHITE).withItalic(false);
+ itemStack.set(DataComponentTypes.CUSTOM_NAME, Text.literal(Formatting.strip(itemStack.getComponents().get(DataComponentTypes.CUSTOM_NAME).getString())).setStyle(style));
- if (itemStack.getItem().getName().getString().equals("Barrier")) itemStack.set(DataComponentTypes.ITEM_NAME, Text.of(collection));
int personalColl = playerCollection != null && playerCollection.has(collection) ? playerCollection.get(collection).getAsInt() : 0;
- int coopColl = 0;
+ int totalCollection = 0;
for (String member : hProfile.get("members").getAsJsonObject().keySet()) {
if (!hProfile.getAsJsonObject("members").getAsJsonObject(member).has("collection")) continue;
JsonObject memberColl = hProfile.getAsJsonObject("members").getAsJsonObject(member).getAsJsonObject("collection");
- coopColl += memberColl.has(collection) ? memberColl.get(collection).getAsInt() : 0;
+ totalCollection += memberColl.has(collection) ? memberColl.get(collection).getAsInt() : 0;
}
- int collectionTier = calculateTier(coopColl, tierRequirementsMap.get(collection));
+ int collectionTier = calculateTier(totalCollection, tierRequirementsMap.get(collection));
List<Integer> tierRequirements = tierRequirementsMap.get(collection);
List<Text> lore = new ArrayList<>();
- Style style = Style.EMPTY.withItalic(false);
- lore.add(Text.literal("Collection: " + FORMATTER.format(personalColl)).setStyle(style).formatted(Formatting.YELLOW));
+ lore.add(Text.literal("Collection Item").setStyle(style).formatted(Formatting.DARK_GRAY));
+ lore.add(Text.empty());
+
if (hProfile.get("members").getAsJsonObject().keySet().size() > 1) {
- lore.add(Text.literal("Co-op Collection: " + FORMATTER.format(coopColl)).setStyle(style).formatted(Formatting.AQUA));
+ lore.add(Text.literal("Personal: " + COMMA_FORMATTER.format(personalColl)).setStyle(style).formatted(Formatting.GOLD));
+ lore.add(Text.literal("Co-op Collection: " + COMMA_FORMATTER.format(totalCollection-personalColl)).setStyle(style).formatted(Formatting.AQUA));
}
- lore.add(Text.literal("Collection Tier: " + collectionTier).setStyle(style).formatted(Formatting.LIGHT_PURPLE));
- itemStack.set(DataComponentTypes.LORE, new LoreComponent(lore));
+ lore.add(Text.literal("Collection: " + COMMA_FORMATTER.format(totalCollection)).setStyle(style).formatted(Formatting.YELLOW));
+
+ lore.add(Text.empty());
+ lore.add(Text.literal("Collection Tier: " + collectionTier + "/" + tierRequirements.size()).setStyle(style).formatted(Formatting.LIGHT_PURPLE));
if (collectionTier == tierRequirements.size()) itemStack.set(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, true);
+ itemStack.set(DataComponentTypes.LORE, new LoreComponent(lore));
+
+ itemStack.set(DataComponentTypes.CUSTOM_DATA, NbtComponent.DEFAULT);
+
collections.add(itemStack);
}
}
@@ -116,11 +126,18 @@ public class GenericCategory implements ProfileViewerPage {
ItemStack itemStack = collections.get(i);
List<Text> lore = itemStack.getOrDefault(DataComponentTypes.LORE, LoreComponent.DEFAULT).lines();
for (Text text : lore) {
- if (!text.getString().startsWith("Collection Tier: ")) continue;
- int cTier = Integer.parseInt(text.getString().substring("Collection Tier: ".length()));
- Color colour = Boolean.TRUE.equals(itemStack.get(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE)) ? Color.MAGENTA : Color.darkGray;
- context.drawText(textRenderer, Text.literal(toRomanNumerals(cTier)), x + 9 - (textRenderer.getWidth(toRomanNumerals(cTier)) / 2), y + 21, colour.getRGB(), false);
- break;
+ if (text.getString().startsWith("Collection Tier: ")) {
+ String tierText = text.getString().substring("Collection Tier: ".length());
+ if (tierText.contains("/")) {
+ String[] parts = tierText.split("/");
+ int cTier = Integer.parseInt(parts[0].trim());
+ Color colour = itemStack.hasGlint() ? Color.MAGENTA : Color.darkGray;
+ //DO NOT CHANGE THIS METHOD CALL! Aaron's Mod mixes in here to provide chroma text for max collections
+ //and changing the method called here will break that! Consult Aaron before making any changes :)
+ context.drawText(textRenderer, Text.literal(toRomanNumerals(cTier)), x + 9 - (textRenderer.getWidth(toRomanNumerals(cTier)) / 2), y + 21, colour.getRGB(), false);
+ }
+ break;
+ }
}
if (mouseX > x && mouseX < x + 16 && mouseY > y && mouseY < y + 16) {
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonClassWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonClassWidget.java
index 3b847b1b..37953a2b 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonClassWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonClassWidget.java
@@ -3,15 +3,20 @@ package de.hysky.skyblocker.skyblock.profileviewer.dungeons;
import com.google.gson.JsonObject;
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.skyblock.profileviewer.utils.LevelFinder;
+import de.hysky.skyblocker.skyblock.profileviewer.utils.ProfileViewerUtils;
import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
import de.hysky.skyblocker.utils.render.RenderHelper;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.item.ItemStack;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
import java.awt.*;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
public class DungeonClassWidget {
@@ -48,7 +53,7 @@ public class DungeonClassWidget {
}
}
- public void render(DrawContext context, int x, int y) {
+ public void render(DrawContext context, int mouseX, int mouseY, int x, int y) {
context.drawTexture(TEXTURE, x, y, 0, 0, 109, 26, 109, 26);
context.drawItem(stack, x + 3, y + 5);
if (active) context.drawTexture(ACTIVE_TEXTURE, x + 3, y + 5, 0, 0, 16, 16, 16, 16);
@@ -57,6 +62,12 @@ public class DungeonClassWidget {
Color fillColor = classLevel.level >= CLASS_CAP ? Color.MAGENTA : Color.GREEN;
context.drawGuiTexture(BAR_BACK, x + 30, y + 15, 75, 6);
RenderHelper.renderNineSliceColored(context, BAR_FILL, x + 30, y + 15, (int) (75 * classLevel.fill), 6, fillColor);
- }
+ if (mouseX > x + 30 && mouseX < x + 105 && mouseY > y + 12 && mouseY < y + 22){
+ List<Text> tooltipText = new ArrayList<>();
+ tooltipText.add(Text.literal(this.className).formatted(Formatting.GREEN));
+ tooltipText.add(Text.literal("XP: " + ProfileViewerUtils.COMMA_FORMATTER.format(this.classLevel.xp)).formatted(Formatting.GOLD));
+ context.drawTooltip(textRenderer, tooltipText, mouseX, mouseY);
+ }
+ }
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonFloorRunsWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonFloorRunsWidget.java
index 7c9206c0..b592266a 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonFloorRunsWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonFloorRunsWidget.java
@@ -11,6 +11,8 @@ import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
import java.awt.*;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
public class DungeonFloorRunsWidget {
@@ -26,8 +28,7 @@ public class DungeonFloorRunsWidget {
} catch (Exception ignored) {}
}
- // TODO: Hovering on each floor should probably showcase best run times in a tooltip
- public void render(DrawContext context, int x, int y) {
+ public void render(DrawContext context, int mouseX ,int mouseY, int x, int y) {
context.drawTexture(TEXTURE, x, y, 0, 0, 109, 110, 109, 110);
context.drawText(textRenderer, Text.literal("Floor Runs").formatted(Formatting.BOLD), x + 6, y + 4, Color.WHITE.getRGB(), true);
@@ -36,12 +37,33 @@ public class DungeonFloorRunsWidget {
for (String dungeon : DUNGEONS) {
JsonObject dungeonData;
try {
- dungeonData = dungeonsStats.getAsJsonObject(dungeon).getAsJsonObject(dungeon.equals("catacombs") ? "times_played" : "tier_completions");
+ dungeonData = dungeonsStats.getAsJsonObject(dungeon).getAsJsonObject("tier_completions");
for (Map.Entry<String, JsonElement> entry : dungeonData.entrySet()) {
if (entry.getKey().equals("total")) continue;
String textToRender = String.format((dungeon.equals("catacombs") ? "§aF" : "§cM") + "%s§r %s", entry.getKey(), entry.getValue().getAsInt());
context.drawText(textRenderer, textToRender, columnX + 2, elementY + 2, Color.WHITE.getRGB(), true);
+ if (!entry.getKey().equals("0") && mouseX >= columnX && mouseX <= columnX + 40 && mouseY >= elementY && mouseY <= elementY + 9) {
+ List<Text> tooltipText = new ArrayList<>();
+ tooltipText.add(Text.literal("Personal Bests").formatted(Formatting.BOLD, Formatting.LIGHT_PURPLE));
+
+ JsonObject fastestTimes = dungeonsStats.getAsJsonObject(dungeon).getAsJsonObject("fastest_time_s");
+ if (fastestTimes != null && fastestTimes.has(entry.getKey())) {
+ tooltipText.add(Text.literal("S Run: " + formatTime(fastestTimes.get(entry.getKey()).getAsLong())).formatted(Formatting.GOLD));
+ }
+
+ fastestTimes = dungeonsStats.getAsJsonObject(dungeon).getAsJsonObject("fastest_time_s_plus");
+ if (fastestTimes != null && fastestTimes.has(entry.getKey())) {
+ tooltipText.add(Text.literal("S+ Run: " + formatTime(fastestTimes.get(entry.getKey()).getAsLong())).formatted(Formatting.GOLD));
+ }
+
+ fastestTimes = dungeonsStats.getAsJsonObject(dungeon).getAsJsonObject("fastest_time");
+ if (fastestTimes != null && fastestTimes.has(entry.getKey()) && tooltipText.size() == 1) {
+ tooltipText.add(Text.literal("Completion: " + formatTime(fastestTimes.get(entry.getKey()).getAsLong())).formatted(Formatting.GOLD));
+ }
+
+ context.drawTooltip(textRenderer, tooltipText, mouseX, mouseY);
+ }
elementY += 11;
}
@@ -52,4 +74,11 @@ public class DungeonFloorRunsWidget {
}
}
}
+
+ private String formatTime(long milliseconds) {
+ long seconds = milliseconds / 1000;
+ long minutes = seconds / 60;
+ seconds %= 60;
+ return String.format("%2d:%02d", minutes, seconds);
+ }
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonMiscStatsWidgets.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonMiscStatsWidgets.java
index 679cc575..780eec24 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonMiscStatsWidgets.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonMiscStatsWidgets.java
@@ -31,7 +31,7 @@ public class DungeonMiscStatsWidgets {
secrets = DUNGEONS_DATA.get("secrets").getAsInt();
for (String dungeon : DUNGEONS) {
- JsonObject dungeonData = DUNGEONS_DATA.getAsJsonObject("dungeon_types").getAsJsonObject(dungeon).getAsJsonObject(dungeon.equals("catacombs") ? "times_played" : "tier_completions");
+ JsonObject dungeonData = DUNGEONS_DATA.getAsJsonObject("dungeon_types").getAsJsonObject(dungeon).getAsJsonObject("tier_completions");
int runs = 0;
for (Map.Entry<String, JsonElement> entry : dungeonData.entrySet()) {
String key = entry.getKey();
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonsPage.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonsPage.java
index b1398661..e0051c88 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonsPage.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/dungeons/DungeonsPage.java
@@ -30,10 +30,10 @@ public class DungeonsPage implements ProfileViewerPage {
public void render(DrawContext context, int mouseX, int mouseY, float delta, int rootX, int rootY) {
dungeonHeaderWidget.render(context, rootX, rootY);
- dungeonFloorRunsWidget.render(context, rootX + 113, rootY + 56);
+ dungeonFloorRunsWidget.render(context, mouseX, mouseY, rootX + 113, rootY + 56);
dungeonMiscStatsWidgets.render(context, rootX + 113, rootY);
for (int i = 0; i < dungeonClassWidgetsList.size(); i++) {
- dungeonClassWidgetsList.get(i).render(context, rootX, rootY + 28 + i * 28);
+ dungeonClassWidgetsList.get(i).render(context, mouseX, mouseY, rootX, rootY + 28 + i * 28);
}
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Inventory.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Inventory.java
index a2f7d9d6..126c55ec 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Inventory.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Inventory.java
@@ -1,6 +1,8 @@
package de.hysky.skyblocker.skyblock.profileviewer.inventory;
import com.google.gson.JsonObject;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.item.ItemRarityBackgrounds;
import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage;
import de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders.ItemLoader;
import it.unimi.dsi.fastutil.ints.IntIntPair;
@@ -8,6 +10,7 @@ import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.widget.ClickableWidget;
+import net.minecraft.client.resource.language.I18n;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.tooltip.TooltipType;
@@ -16,6 +19,7 @@ import net.minecraft.util.Identifier;
import java.awt.*;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
public class Inventory implements ProfileViewerPage {
@@ -49,7 +53,7 @@ public class Inventory implements ProfileViewerPage {
context.drawTexture(TEXTURE, rootX, rootYAdjusted + dimensions.leftInt() * 18 + 17, 0, 215, dimensions.rightInt() * 18 + 7, 7);
context.drawTexture(TEXTURE, rootX + dimensions.rightInt() * 18 + 7, rootYAdjusted + dimensions.leftInt() * 18 + 17, 169, 215, 7, 7);
- context.drawText(textRenderer, containerName, rootX + 7, rootYAdjusted + 7, Color.DARK_GRAY.getRGB(), false);
+ context.drawText(textRenderer, I18n.translate("skyblocker.profileviewer.inventory." + containerName), rootX + 7, rootYAdjusted + 7, Color.DARK_GRAY.getRGB(), false);
if (containerList.size() > itemsPerPage) {
previousPage.setX(rootX + 44);
@@ -65,6 +69,7 @@ public class Inventory implements ProfileViewerPage {
int startIndex = activePage * itemsPerPage;
int endIndex = Math.min(startIndex + itemsPerPage, containerList.size());
+ List<Text> tooltip = Collections.emptyList();
for (int i = 0; i < endIndex - startIndex; i++) {
if (containerList.get(startIndex + i) == ItemStack.EMPTY) continue;
int column = i % dimensions.rightInt();
@@ -72,14 +77,20 @@ public class Inventory implements ProfileViewerPage {
int x = rootX + 8 + column * 18;
int y = rootYAdjusted + 18 + row * 18;
+
+ if (SkyblockerConfigManager.get().general.itemInfoDisplay.itemRarityBackgrounds) {
+ ItemRarityBackgrounds.tryDraw(containerList.get(startIndex + i), context, x, y);
+ }
+
context.drawItem(containerList.get(startIndex + i), x, y);
context.drawItemInSlot(textRenderer, containerList.get(startIndex + i), x, y);
- if (mouseX > x && mouseX < x + 16 && mouseY > y && mouseY < y + 16) {
- List<Text> tooltip = containerList.get(startIndex + i).getTooltip(Item.TooltipContext.DEFAULT, MinecraftClient.getInstance().player, TooltipType.BASIC);
- context.drawTooltip(textRenderer, tooltip, mouseX, mouseY);
+ if (mouseX > x -1 && mouseX < x + 16 && mouseY > y - 1 && mouseY < y + 16) {
+ tooltip = containerList.get(startIndex + i).getTooltip(Item.TooltipContext.DEFAULT, MinecraftClient.getInstance().player, TooltipType.BASIC);
}
}
+
+ if (!tooltip.isEmpty()) context.drawTooltip(textRenderer, tooltip, mouseX, mouseY);
}
public void nextPage() {
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/InventoryPage.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/InventoryPage.java
index 8b0cbefc..6aa92ef6 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/InventoryPage.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/InventoryPage.java
@@ -6,7 +6,7 @@ import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen;
import de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders.BackpackItemLoader;
import de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders.PetsInventoryItemLoader;
import de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders.WardrobeInventoryItemLoader;
-import de.hysky.skyblocker.skyblock.profileviewer.utils.SkullCreator;
+import de.hysky.skyblocker.skyblock.profileviewer.utils.ProfileViewerUtils;
import de.hysky.skyblocker.skyblock.profileviewer.utils.SubPageSelectButton;
import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
import it.unimi.dsi.fastutil.ints.IntIntPair;
@@ -22,15 +22,15 @@ import java.util.List;
import java.util.Map;
public class InventoryPage implements ProfileViewerPage {
- private static final String[] INVENTORY_PAGES = {"Inventory", "Enderchest", "Backpack", "Wardrobe", "Pets", "Accessory Bag"};
+ private static final String[] INVENTORY_PAGES = {"inventory", "enderchest", "backpack", "wardrobe", "pets", "accessoryBag"};
private static final int TOTAL_HEIGHT = 165;
private static final Map<String, ItemStack> ICON_MAP = Map.ofEntries(
- Map.entry("Wardrobe", Ico.L_CHESTPLATE),
- Map.entry("Inventory", Ico.CHEST),
- Map.entry("Backpack", SkullCreator.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzYyZjNiM2EwNTQ4MWNkZTc3MjQwMDA1YzBkZGNlZTFjMDY5ZTU1MDRhNjJjZTA5Nzc4NzlmNTVhMzkzOTYxNDYifX19")),
- Map.entry("Pets", Ico.BONE),
- Map.entry("Enderchest", Ico.E_CHEST),
- Map.entry("Accessory Bag", SkullCreator.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTYxYTkxOGMwYzQ5YmE4ZDA1M2U1MjJjYjkxYWJjNzQ2ODkzNjdiNGQ4YWEwNmJmYzFiYTkxNTQ3MzA5ODVmZiJ9fX0="))
+ Map.entry("wardrobe", Ico.L_CHESTPLATE),
+ Map.entry("inventory", Ico.CHEST),
+ Map.entry("backpack", ProfileViewerUtils.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzYyZjNiM2EwNTQ4MWNkZTc3MjQwMDA1YzBkZGNlZTFjMDY5ZTU1MDRhNjJjZTA5Nzc4NzlmNTVhMzkzOTYxNDYifX19")),
+ Map.entry("pets", Ico.BONE),
+ Map.entry("enderchest", Ico.E_CHEST),
+ Map.entry("accessoryBag", ProfileViewerUtils.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTYxYTkxOGMwYzQ5YmE4ZDA1M2U1MjJjYjkxYWJjNzQ2ODkzNjdiNGQ4YWEwNmJmYzFiYTkxNTQ3MzA5ODVmZiJ9fX0="))
);
private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Pet.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Pet.java
index b3389d39..857198e1 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Pet.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/Pet.java
@@ -4,71 +4,107 @@ import de.hysky.skyblocker.skyblock.PetCache;
import de.hysky.skyblocker.skyblock.itemlist.ItemFixerUpper;
import de.hysky.skyblocker.skyblock.itemlist.ItemRepository;
import de.hysky.skyblocker.skyblock.profileviewer.utils.LevelFinder;
+import de.hysky.skyblocker.skyblock.profileviewer.utils.ProfileViewerUtils;
import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
import de.hysky.skyblocker.utils.ItemUtils;
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 io.github.moulberry.repo.util.PetId;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.LoreComponent;
import net.minecraft.component.type.ProfileComponent;
import net.minecraft.item.ItemStack;
-import net.minecraft.nbt.NbtCompound;
-import net.minecraft.nbt.NbtString;
import net.minecraft.registry.Registries;
+import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
-import net.minecraft.util.Pair;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.regex.Matcher;
-import java.util.stream.Collectors;
+import java.util.regex.Pattern;
import static de.hysky.skyblocker.skyblock.itemlist.ItemStackBuilder.SKULL_TEXTURE_PATTERN;
-import static de.hysky.skyblocker.skyblock.itemlist.ItemStackBuilder.SKULL_UUID_PATTERN;
+import static de.hysky.skyblocker.skyblock.profileviewer.utils.ProfileViewerUtils.numLetterFormat;
public class Pet {
private final String name;
private final double xp;
private final String tier;
private final Optional<String> heldItem;
+ private final Optional<String> skin;
+ private final Optional<String> skinTexture;
private final int level;
+ private final double perecentageToLevel;
+ private final long levelXP;
+ private final long nextLevelXP;
private final ItemStack icon;
+ private final Pattern statsMatcher = Pattern.compile("\\{[A-Za-z_]+}");
+ private final Pattern numberMatcher = Pattern.compile("\\{\\d+}");
+
+
+
private static final Map<String, Integer> TIER_MAP = Map.of(
"COMMON", 0, "UNCOMMON", 1, "RARE", 2, "EPIC", 3, "LEGENDARY", 4, "MYTHIC", 5
);
+ private static final Int2ObjectMap<Formatting> RARITY_COLOR_MAP = Int2ObjectMaps.unmodifiable(new Int2ObjectOpenHashMap<>(Map.of(
+ 0, Formatting.WHITE, // COMMON
+ 1, Formatting.GREEN, // UNCOMMON
+ 2, Formatting.BLUE, // RARE
+ 3, Formatting.DARK_PURPLE, // EPIC
+ 4, Formatting.GOLD, // LEGENDARY
+ 5, Formatting.LIGHT_PURPLE, // MYTHIC
+ 6, Formatting.AQUA // DIVINE (future proofing, because why not)
+ )));
+
public Pet(PetCache.PetInfo petData) {
+ LevelFinder.LevelInfo info = LevelFinder.getLevelInfo(petData.type().equals("GOLDEN_DRAGON") ? "PET_GREG" : "PET_" + petData.tier(), (long) petData.exp());
this.name = petData.type();
this.xp = petData.exp();
this.heldItem = petData.item();
- if ((heldItem.isPresent() && heldItem.get().equals("PET_ITEM_TIER_BOOST"))) {
- this.tier = switch (petData.tier()) {
- case "COMMON" -> "UNCOMMON";
- case "UNCOMMON" -> "RARE";
- case "RARE" -> "EPIC";
- case "EPIC" -> "LEGENDARY";
- case "LEGENDARY" -> "MYTHIC";
- default -> petData.tier();
- };
- } else {
- this.tier = petData.tier();
- }
- this.level = LevelFinder.getLevelInfo(this.name.equals("GOLDEN_DRAGON") ? "PET_GREG" : "PET_" + this.tier, (long) xp).level;
+ this.skin = petData.skin();
+ this.skinTexture = calculateSkinTexture();
+ this.tier = petData.tier();
+ this.level = info.level;
+ this.perecentageToLevel = info.fill;
+ this.levelXP = info.levelXP;
+ this.nextLevelXP = info.nextLevelXP;
this.icon = createIcon();
}
- public String getName() { return name; }
- public long getXP() { return (long) xp; }
- public int getTier() { return TIER_MAP.getOrDefault(tier, 0); }
- public String getTierAsString() { return tier; }
- public String getSkin() { return null; }
+ private String getName() {
+ return name;
+ }
+
+ public long getXP() {
+ return (long) xp;
+ }
+
+ private int getTier() {
+ return TIER_MAP.getOrDefault(tier, 0);
+ }
+
+ public String getTierAsString() {
+ return tier;
+ }
+
+ private Optional<String> calculateSkinTexture() {
+ if (this.skin.isPresent()) {
+ NEUItem item = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId("PET_SKIN_" + this.skin.get());
+ if (item == null) return Optional.empty();
+ Matcher skullTexture = SKULL_TEXTURE_PATTERN.matcher(item.getNbttag());
+ if (skullTexture.find()) return Optional.of(skullTexture.group(1));
+ }
+ return Optional.empty();
+ }
public int getLevel() { return level; }
public ItemStack getIcon() { return icon; }
@@ -78,19 +114,15 @@ public class Pet {
Map<String, NEUItem> items = NEURepoManager.NEU_REPO.getItems().getItems();
if (items == null) return Ico.BARRIER;
- String targetItemId = this.getName() + ";" + this.getTier();
- NEUItem item = items.values().stream()
- .filter(i -> Formatting.strip(i.getSkyblockItemId()).equals(targetItemId))
- .findFirst().orElse(null);
+ String targetItemId = this.getName() + ";" + (this.getTier() + (heldItem.isPresent() && heldItem.get().equals("PET_ITEM_TIER_BOOST") ? 1 : 0));
+ NEUItem item = NEURepoManager.NEU_REPO.getItems().getItems().get(targetItemId);
- NEUItem petItem = null;
- if (this.heldItem.isPresent()) {
- petItem = items.values().stream()
- .filter(i -> Formatting.strip(i.getSkyblockItemId()).equals(this.heldItem.get()))
- .findFirst().orElse(null);
+ // For cases life RIFT_FERRET Where it can be tier boosted into a pet that otherwise can't exist
+ if (item == null && heldItem.isPresent() && heldItem.get().equals("PET_ITEM_TIER_BOOST")) {
+ item = NEURepoManager.NEU_REPO.getItems().getItems().get(getName() + ";" + getTier());
}
- return fromNEUItem(item, petItem);
+ return fromNEUItem(item, this.heldItem.map(ItemRepository::getItemStack).orElse(null));
}
/**
@@ -100,87 +132,134 @@ public class Pet {
* the NBT Data into modern DataComponentTypes before returning the final ItemStack </p
*
* @param item The NEUItem representing the pet.
- * @param helditem The NEUItem representing the held item, if any.
+ * @param heldItem The ItemStack of the pet's held item, if any.
* @return The ItemStack representing the pet with all its properties set.
*/
- private ItemStack fromNEUItem(NEUItem item, NEUItem helditem) {
- if (item == null) return Ico.BARRIER;
- List<Pair<String, String>> injectors = new ArrayList<>(createLoreReplacers(item.getSkyblockItemId(), helditem));
+ private ItemStack fromNEUItem(NEUItem item, ItemStack heldItem) {
+ if (item == null) {
+ ItemStack errIcon = Ico.BARRIER.copy();
+ errIcon.set(DataComponentTypes.CUSTOM_NAME, Text.of(this.getName()));
+ return errIcon;
+ }
+
Identifier itemId = Identifier.of(ItemFixerUpper.convertItemId(item.getMinecraftItemId(), item.getDamage()));
- ItemStack stack = new ItemStack(Registries.ITEM.get(itemId));
-
- NbtCompound customData = new NbtCompound();
- customData.put(ItemUtils.ID, NbtString.of(item.getSkyblockItemId()));
- stack.set(DataComponentTypes.CUSTOM_NAME, Text.of(injectData(item.getDisplayName(), injectors)));
-
- stack.set(DataComponentTypes.LORE, new LoreComponent(
- item.getLore().stream().map(line -> injectData(line, injectors))
- .filter(line -> !line.contains("SKIP")).map(Text::of)
- .collect(Collectors.toList())));
-
- Matcher skullUuid = SKULL_UUID_PATTERN.matcher(item.getNbttag());
- Matcher skullTexture = SKULL_TEXTURE_PATTERN.matcher(item.getNbttag());
- if (skullUuid.find() && skullTexture.find()) {
- UUID uuid = UUID.fromString(skullUuid.group(1));
- String textureValue = this.getSkin() == null ? skullTexture.group(1) : this.getSkin();
- stack.set(DataComponentTypes.PROFILE, new ProfileComponent(
- Optional.of(item.getSkyblockItemId()), Optional.of(uuid),
- ItemUtils.propertyMapWithTexture(textureValue)));
+ ItemStack petStack = new ItemStack(Registries.ITEM.get(itemId)).copy();
+
+ List<Text> formattedLore = !(name.equals("GOLDEN_DRAGON") && level < 101) ? processLore(item.getLore(), heldItem) : buildGoldenDragonEggLore(item.getLore());
+
+ // Calculate and display XP for level
+ Style style = Style.EMPTY.withItalic(false);
+ if (level != 100 && level != 200) {
+ String progress = "Progress to Level " + this.level + ": §e" + fixDecimals(this.perecentageToLevel * 100, true) + "%";
+ formattedLore.add(formattedLore.size() - 1, Text.literal(progress).setStyle(style).formatted(Formatting.GRAY));
+ String string = "§2§m ".repeat((int) Math.round(perecentageToLevel * 30)) + "§f§m ".repeat(30 - (int) Math.round(perecentageToLevel * 30));
+ formattedLore.add(formattedLore.size() - 1, Text.literal(string + "§r§e " + numLetterFormat(levelXP) + "§6/§e" + numLetterFormat(nextLevelXP)).setStyle(style));
+ formattedLore.add(formattedLore.size() - 1, Text.empty());
+ } else {
+ formattedLore.add(formattedLore.size() - 1, Text.literal("MAX LEVEL").setStyle(style).formatted(Formatting.AQUA, Formatting.BOLD));
+ formattedLore.add(formattedLore.size() - 1, Text.literal("▸ " + ProfileViewerUtils.COMMA_FORMATTER.format((long) xp) + " XP").setStyle(style).formatted(Formatting.DARK_GRAY));
+ formattedLore.add(formattedLore.size() - 1, Text.empty());
}
- return stack;
+
+ // Skin Head Texture
+ if (skinTexture.isPresent()) {
+ formattedLore.set(0, Text.of(formattedLore.getFirst().getString() + ", " + Formatting.strip(NEURepoManager.NEU_REPO.getItems().getItems().get("PET_SKIN_" + skin.get()).getDisplayName())));
+ petStack.set(DataComponentTypes.PROFILE, new ProfileComponent(
+ Optional.of(item.getSkyblockItemId()), Optional.of(UUID.randomUUID()),
+ ItemUtils.propertyMapWithTexture(this.skinTexture.get())));
+ } else {
+ Matcher skullTexture = SKULL_TEXTURE_PATTERN.matcher(item.getNbttag());
+ if (skullTexture.find()) {
+ petStack.set(DataComponentTypes.PROFILE, new ProfileComponent(
+ Optional.of(item.getSkyblockItemId()), Optional.of(UUID.randomUUID()),
+ ItemUtils.propertyMapWithTexture(skullTexture.group(1))));
+ }
+ }
+
+ if ((boosted())) formattedLore.set(formattedLore.size() - 1, Text.literal(Rarity.values()[getTier() + 1].toString()).setStyle(style).formatted(Formatting.BOLD, RARITY_COLOR_MAP.get(getTier() + 1)));
+
+ // Update the lore and name
+ petStack.set(DataComponentTypes.LORE, new LoreComponent(formattedLore));
+ String displayName = Formatting.strip(item.getDisplayName()).replace("[Lvl {LVL}]", "§7[Lvl " + this.level + "]§r");
+ petStack.set(DataComponentTypes.CUSTOM_NAME, Text.literal(displayName).setStyle(style).formatted(RARITY_COLOR_MAP.get(this.getTier() + (boosted() ? 1 : 0))));
+ return petStack;
}
/**
- * Generates a list of placeholder-replacement pairs for the itemName of a pet item.
- * <p> This method uses the pet's data from the NEU repository and uses PetInfo to generate replacers, and optionally
- * includes data about a held item. </p>
+ * Iterates through a Pet's lore injecting interpolated stat numbers based on pet level
*
- * @param itemSkyblockID The initial itemName string containing the pet's name and tier separated by a semicolon.
- * @param helditem The NEUItem representing the held item, if any.
- * @return A list of placeholder-replacement pairs to be used for injecting data into the pet item's itemName.
+ * @param lore the raw lore data stored in NEU Repo
+ * @param heldItem the pet's held item, if any
+ * @return Formatted lore with injected stats inserted into the tooltip
*/
- private List<Pair<String, String>> createLoreReplacers(String itemSkyblockID, NEUItem helditem) {
- List<Pair<String, String>> list = new ArrayList<>();
- Map<@PetId String, Map<Rarity, PetNumbers>> petNums = NEURepoManager.NEU_REPO.getConstants().getPetNumbers();
- String petName = itemSkyblockID.split(";")[0];
- if (!itemSkyblockID.contains(";") || !petNums.containsKey(petName)) return list;
-
- Rarity rarity = Rarity.values()[Integer.parseInt(itemSkyblockID.split(";")[1])];
- try {
- PetNumbers data = petNums.get(petName).get(rarity);
- list.add(new Pair<>("\\{LVL\\}", String.valueOf(this.level)));
- data.interpolatedStatsAtLevel(this.level).getStatNumbers().forEach((key, value) ->
- list.add(new Pair<>("\\{" + key + "\\}", fixDecimals(value, true))));
-
- List<Double> otherNumsMin = data.interpolatedStatsAtLevel(this.level).getOtherNumbers();
- for (int i = 0; i < otherNumsMin.size(); ++i) {
- list.add(new Pair<>("\\{" + i + "\\}", fixDecimals(otherNumsMin.get(i), false)));
+ private List<Text> processLore(List<String> lore, ItemStack heldItem) {
+ Map<String, Map<Rarity, PetNumbers>> petNums = NEURepoManager.NEU_REPO.getConstants().getPetNumbers();
+ Rarity rarity = Rarity.values()[getTier()];
+ PetNumbers data = petNums.get(getName()).get(rarity);
+ List<Text> formattedLore = new ArrayList<>();
+
+ for (String line : lore) {
+ if (line.contains("Right-click to add this") || line.contains("pet menu!")) continue;
+
+ String formattedLine = line;
+
+ Matcher stats = statsMatcher.matcher(formattedLine);
+ Matcher other = numberMatcher.matcher(formattedLine);
+
+ while (stats.find()) {
+ String placeholder = stats.group();
+ String statKey = placeholder.substring(1, placeholder.length() - 1);
+ String statValue = String.valueOf(fixDecimals(data.interpolatedStatsAtLevel(this.level).getStatNumbers().get(statKey), true));
+ formattedLine = formattedLine.replace(placeholder, statValue);
}
- list.add(new Pair<>("Right-click to add this pet to",
- helditem != null ? "§r§6Held Item: " + helditem.getDisplayName() : "SKIP"));
- list.add(new Pair<>("pet menu!", "SKIP"));
- } catch (Exception e) {
- if (petName.equals("GOLDEN_DRAGON")) {
- list.add(new Pair<>("Golden Dragon",
- "§r§7[Lvl " + this.level + "] " + "§6Golden Dragon Egg §c[Not Supported by NEU-Repo]"));
+ while (other.find()) {
+ String placeholder = other.group();
+ int numberKey = Integer.parseInt(placeholder.substring(1, placeholder.length() - 1));
+ String statValue = String.valueOf(fixDecimals(data.interpolatedStatsAtLevel(this.level).getOtherNumbers().get(numberKey), false));
+ formattedLine = formattedLine.replace(placeholder, statValue);
}
+
+ formattedLore.add(Text.of(formattedLine));
}
- return list;
- }
- private String injectData(String string, List<Pair<String, String>> injectors) {
- for (Pair<String, String> injector : injectors) {
- if (string.contains(injector.getLeft())) return injector.getRight();
- string = string.replaceAll(injector.getLeft(), injector.getRight());
+
+ if (heldItem != null) {
+ formattedLore.set(formattedLore.size() - 2, Text.of("§r§6Held Item: " + heldItem.getName().getString()));
+ formattedLore.add(formattedLore.size() - 1, Text.empty());
}
- return string;
+
+ return formattedLore;
+ }
+
+ /**
+ * NEU Repo doesn't distinguish between the Egg and the hatched GoldenDragon pet so hardcoded lore :eues:
+ * @param lore the existing lore
+ * @return Fully formatted GoldenDragonEgg Lore
+ */
+ private List<Text> buildGoldenDragonEggLore(List<String> lore) {
+ List<Text> formattedLore = new ArrayList<>();
+ Style style = Style.EMPTY.withItalic(false);
+
+ formattedLore.add(Text.of(lore.getFirst()));
+ formattedLore.add(Text.empty());
+ formattedLore.add(Text.literal("Perks:").setStyle(style).formatted(Formatting.GRAY));
+ formattedLore.add(Text.literal("???").setStyle(style).formatted(Formatting.RED, Formatting.BOLD));
+ formattedLore.add(Text.empty());
+ formattedLore.add(Text.literal("Hatches at level §b100").setStyle(style).formatted(Formatting.GRAY));
+ formattedLore.add(Text.empty());
+ formattedLore.add(Text.of(lore.getLast()));
+
+ return formattedLore;
}
private String fixDecimals(double num, boolean truncate) {
- if (num % 1 == 0) return String.valueOf((int) num);
- BigDecimal roundedNum = new BigDecimal(num).setScale(3, RoundingMode.HALF_UP);
- return truncate && num > 1 ? String.valueOf(roundedNum.intValue())
- : roundedNum.stripTrailingZeros().toPlainString();
+ if (num % 1 == 0) return String.valueOf((int) (num));
+ BigDecimal roundedNum = new BigDecimal(num).setScale(truncate ? 1 : 3, RoundingMode.HALF_UP);
+ return roundedNum.stripTrailingZeros().toPlainString();
+ }
+
+ private boolean boosted() {
+ return this.heldItem.isPresent() && this.heldItem.get().equals("PET_ITEM_TIER_BOOST");
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PlayerInventory.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PlayerInventory.java
index 26673693..e210ca9a 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PlayerInventory.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/PlayerInventory.java
@@ -1,12 +1,15 @@
package de.hysky.skyblocker.skyblock.profileviewer.inventory;
import com.google.gson.JsonObject;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.skyblock.item.ItemRarityBackgrounds;
import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerPage;
import de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders.InventoryItemLoader;
import it.unimi.dsi.fastutil.ints.IntIntPair;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.resource.language.I18n;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.tooltip.TooltipType;
@@ -14,12 +17,14 @@ import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import java.awt.*;
+import java.util.Collections;
import java.util.List;
public class PlayerInventory implements ProfileViewerPage {
private static final Identifier TEXTURE = Identifier.of("textures/gui/container/generic_54.png");
private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
private final List<ItemStack> containerList;
+ private List<Text> tooltip = Collections.emptyList();
public PlayerInventory(JsonObject inventory) {
this.containerList = new InventoryItemLoader().loadItems(inventory);
@@ -27,17 +32,19 @@ public class PlayerInventory implements ProfileViewerPage {
// Z-STACKING forces this nonsense of separating the Background texture and Item Drawing :(
public void render(DrawContext context, int mouseX, int mouseY, float delta, int rootX, int rootY) {
- drawContainerTextures(context, "Armour", rootX, rootY + 108, IntIntPair.of(1, 4));
- drawContainerTextures(context, "Inventory", rootX, rootY + 2, IntIntPair.of(4, 9));
- drawContainerTextures(context, "Equipment", rootX + 90, rootY + 108, IntIntPair.of(1, 4));
+ drawContainerTextures(context, "armor", rootX, rootY + 108, IntIntPair.of(1, 4));
+ drawContainerTextures(context, "inventory", rootX, rootY + 2, IntIntPair.of(4, 9));
+ drawContainerTextures(context, "equipment", rootX + 90, rootY + 108, IntIntPair.of(1, 4));
+ tooltip.clear();
drawContainerItems(context, rootX, rootY + 108, IntIntPair.of(1, 4), 36, 40, mouseX, mouseY);
drawContainerItems(context, rootX, rootY + 2, IntIntPair.of(4, 9), 0, 36, mouseX, mouseY);
drawContainerItems(context, rootX + 90, rootY + 108, IntIntPair.of(1, 4), 40, containerList.size(), mouseX, mouseY);
+ if (!tooltip.isEmpty()) context.drawTooltip(textRenderer, tooltip, mouseX, mouseY);
}
private void drawContainerTextures(DrawContext context, String containerName, int rootX, int rootY, IntIntPair dimensions) {
- if (containerName.equals("Inventory")) {
+ if (containerName.equals("inventory")) {
context.drawTexture(TEXTURE, rootX, rootY + dimensions.leftInt() + 10, 0, 136, dimensions.rightInt() * 18 + 7, dimensions.leftInt() * 18 + 17);
context.drawTexture(TEXTURE, rootX + dimensions.rightInt() * 18 + 7, rootY, 169, 0, 7, dimensions.leftInt() * 18 + 21);
context.drawTexture(TEXTURE, rootX, rootY, 0, 0, dimensions.rightInt() * 18 + 7, 14);
@@ -48,8 +55,7 @@ public class PlayerInventory implements ProfileViewerPage {
context.drawTexture(TEXTURE, rootX, rootY + dimensions.leftInt() * 18 + 17, 0, 215, dimensions.rightInt() * 18 + 7, 7);
context.drawTexture(TEXTURE, rootX + dimensions.rightInt() * 18 + 7, rootY + dimensions.leftInt() * 18 + 17, 169, 215, 7, 7);
}
-
- context.drawText(textRenderer, containerName, rootX + 7, rootY + 7, Color.DARK_GRAY.getRGB(), false);
+ context.drawText(textRenderer, I18n.translate("skyblocker.profileviewer.inventory." + containerName), rootX + 7, rootY + 7, Color.DARK_GRAY.getRGB(), false);
}
private void drawContainerItems(DrawContext context, int rootX, int rootY, IntIntPair dimensions, int startIndex, int endIndex, int mouseX, int mouseY) {
@@ -61,12 +67,15 @@ public class PlayerInventory implements ProfileViewerPage {
int x = rootX + 8 + column * 18;
int y = (rootY + 18 + row * 18) + (dimensions.leftInt() > 1 && row + 1 == dimensions.leftInt() ? 4 : 0);
+ if (SkyblockerConfigManager.get().general.itemInfoDisplay.itemRarityBackgrounds) {
+ ItemRarityBackgrounds.tryDraw(containerList.get(startIndex + i), context, x, y);
+ }
+
context.drawItem(containerList.get(startIndex + i), x, y);
context.drawItemInSlot(textRenderer, containerList.get(startIndex + i), x, y);
- if (mouseX > x && mouseX < x + 16 && mouseY > y && mouseY < y + 16) {
- List<Text> tooltip = containerList.get(startIndex + i).getTooltip(Item.TooltipContext.DEFAULT, MinecraftClient.getInstance().player, TooltipType.BASIC);
- context.drawTooltip(textRenderer, tooltip, mouseX, mouseY);
+ if (mouseX > x -1 && mouseX < x + 16 && mouseY > y - 1 && mouseY < y + 16) {
+ tooltip = containerList.get(startIndex + i).getTooltip(Item.TooltipContext.DEFAULT, MinecraftClient.getInstance().player, TooltipType.BASIC);
}
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/BackpackItemLoader.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/BackpackItemLoader.java
index 99e728be..dee2bfaf 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/BackpackItemLoader.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/BackpackItemLoader.java
@@ -2,7 +2,11 @@ package de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import net.minecraft.component.DataComponentTypes;
+import net.minecraft.component.type.LoreComponent;
import net.minecraft.item.ItemStack;
+import net.minecraft.text.Text;
import java.util.ArrayList;
import java.util.List;
@@ -23,9 +27,12 @@ public class BackpackItemLoader extends ItemLoader {
for (int i = 0; i < sortedEntries.size(); i++) {
backpackItems.addAll(super.loadItems(sortedEntries.get(i).getValue().getAsJsonObject()));
- int padding = (i + 1) * 45 % (backpackItems.isEmpty() ? 1 : backpackItems.size());
- for (int j = 0; j < padding; j++) {
- backpackItems.add(ItemStack.EMPTY);
+ int paddingNeeded = (45 - (backpackItems.size() % 45)) % 45;
+ for (int j = 0; j < paddingNeeded; j++) {
+ ItemStack paddingItem = Ico.GRAY_DYE.copy();
+ paddingItem.set(DataComponentTypes.CUSTOM_NAME, Text.translatable("skyblocker.profileviewer.inventory.inactive"));
+ paddingItem.set(DataComponentTypes.LORE, new LoreComponent(List.of(Text.translatable("skyblocker.profileviewer.inventory.inactive.description.backpack"),Text.translatable("skyblocker.profileviewer.inventory.inactive.description.general"))));
+ backpackItems.add(paddingItem);
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/ItemLoader.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/ItemLoader.java
index 9d9b1b07..11280af1 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/ItemLoader.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/inventory/itemLoaders/ItemLoader.java
@@ -3,6 +3,7 @@ package de.hysky.skyblocker.skyblock.profileviewer.inventory.itemLoaders;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.mojang.serialization.JsonOps;
+
import de.hysky.skyblocker.skyblock.PetCache;
import de.hysky.skyblocker.skyblock.itemlist.ItemRepository;
import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerScreen;
@@ -10,12 +11,10 @@ import de.hysky.skyblocker.skyblock.profileviewer.inventory.Pet;
import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.NEURepoManager;
+import de.hysky.skyblocker.utils.TextTransformer;
import io.github.moulberry.repo.data.NEUItem;
import net.minecraft.component.DataComponentTypes;
-import net.minecraft.component.type.AttributeModifiersComponent;
-import net.minecraft.component.type.DyedColorComponent;
-import net.minecraft.component.type.LoreComponent;
-import net.minecraft.component.type.ProfileComponent;
+import net.minecraft.component.type.*;
import net.minecraft.datafixer.fix.ItemIdFix;
import net.minecraft.datafixer.fix.ItemInstanceTheFlatteningFix;
import net.minecraft.item.ItemStack;
@@ -24,6 +23,7 @@ import net.minecraft.registry.Registries;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
+import net.minecraft.util.Util;
import java.io.ByteArrayInputStream;
import java.util.*;
@@ -44,9 +44,9 @@ public class ItemLoader {
}
NbtCompound nbttag = containerContent.getCompound(i).getCompound("tag");
- String internalName = nbttag.getCompound("ExtraAttributes").getString("id");
+ NbtCompound extraAttributes = nbttag.getCompound("ExtraAttributes");
+ String internalName = extraAttributes.getString("id");
if (internalName.equals("PET")) {
- NbtCompound extraAttributes = nbttag .getCompound("ExtraAttributes");
PetCache.PetInfo petInfo = PetCache.PetInfo.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseString(extraAttributes.getString("petInfo"))).getOrThrow();
Pet pet = new Pet(petInfo);
itemList.add(pet.getIcon());
@@ -82,13 +82,13 @@ public class ItemLoader {
// Item Name
- stack.set(DataComponentTypes.CUSTOM_NAME, Text.of(nbttag.getCompound("display").getString("Name")));
+ stack.set(DataComponentTypes.CUSTOM_NAME, TextTransformer.fromLegacy(nbttag.getCompound("display").getString("Name")));
// Lore
NbtList loreList = nbttag.getCompound("display").getList("Lore", 8);
stack.set(DataComponentTypes.LORE, new LoreComponent(loreList.stream()
.map(NbtElement::asString)
- .map(Text::literal)
+ .map(TextTransformer::fromLegacy)
.collect(Collectors.toList())));
// add skull texture
@@ -114,6 +114,11 @@ public class ItemLoader {
// Set Count
stack.setCount(containerContent.getCompound(i).getInt("Count"));
+ // Attach an override for Aaron's Mod so that these ItemStacks will work with the mod's features even when not in Skyblock
+ extraAttributes.put("aaron-mod", Util.make(new NbtCompound(), comp -> comp.putBoolean("alwaysDisplaySkyblockInfo", true)));
+
+ stack.set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(extraAttributes));
+
itemList.add(stack);
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillWidget.java
index 3a3870f3..51f5e5f4 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillWidget.java
@@ -2,16 +2,20 @@ package de.hysky.skyblocker.skyblock.profileviewer.skills;
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.skyblock.profileviewer.utils.LevelFinder;
-import de.hysky.skyblocker.skyblock.profileviewer.utils.SkullCreator;
+import de.hysky.skyblocker.skyblock.profileviewer.utils.ProfileViewerUtils;
import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
import de.hysky.skyblocker.utils.render.RenderHelper;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.item.ItemStack;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
import java.awt.*;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
public class SkillWidget {
@@ -33,7 +37,7 @@ public class SkillWidget {
Map.entry("Alchemy", Ico.BREWING_STAND),
Map.entry("Taming", Ico.SPAWN_EGG),
Map.entry("Carpentry", Ico.CRAFTING_TABLE),
- Map.entry("Catacombs", SkullCreator.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzliNTY4OTViOTY1OTg5NmFkNjQ3ZjU4NTk5MjM4YWY1MzJkNDZkYjljMWIwMzg5YjhiYmViNzA5OTlkYWIzM2QifX19")),
+ Map.entry("Catacombs", ProfileViewerUtils.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzliNTY4OTViOTY1OTg5NmFkNjQ3ZjU4NTk5MjM4YWY1MzJkNDZkYjljMWIwMzg5YjhiYmViNzA5OTlkYWIzM2QifX19")),
Map.entry("Runecraft", Ico.MAGMA_CREAM),
Map.entry("Social", Ico.EMERALD)
);
@@ -75,7 +79,7 @@ public class SkillWidget {
}
- public void render(DrawContext context, int x, int y) {
+ public void render(DrawContext context, int mouseX, int mouseY, int x, int y) {
context.drawItem(this.stack, x + 3, y + 2);
context.drawText(textRenderer, SKILL_NAME + " " + SKILL_LEVEL.level, x + 31, y + 2, Color.white.hashCode(), false);
@@ -91,5 +95,12 @@ public class SkillWidget {
context.drawGuiTexture(BAR_BACK, x + 30, y + 12, 75, 6);
RenderHelper.renderNineSliceColored(context, BAR_FILL, x + 30, y + 12, (int) (75 * SKILL_LEVEL.fill), 6, fillColor);
+
+ if (mouseX > x + 30 && mouseX < x + 105 && mouseY > y + 10 && mouseY < y + 19){
+ List<Text> tooltipText = new ArrayList<>();
+ tooltipText.add(Text.literal(this.SKILL_NAME).formatted(Formatting.GREEN));
+ tooltipText.add(Text.literal("XP: " + ProfileViewerUtils.COMMA_FORMATTER.format(this.SKILL_LEVEL.xp)).formatted(Formatting.GOLD));
+ context.drawTooltip(textRenderer, tooltipText, mouseX, mouseY);
+ }
}
} \ No newline at end of file
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillsPage.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillsPage.java
index c331bbdd..952e5620 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillsPage.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/skills/SkillsPage.java
@@ -41,7 +41,7 @@ public class SkillsPage implements ProfileViewerPage {
int x = (i < 6) ? rootX : column2;
int y = rootY + (i % 6) * ROW_GAP;
context.drawTexture(TEXTURE, x, y, 0, 0, 109, 26, 109, 26);
- skillWidgets.get(i).render(context, x, y + 3);
+ skillWidgets.get(i).render(context, mouseX, mouseY, x, y + 3);
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayerWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayerWidget.java
index a9c05c11..978c58c4 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayerWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayerWidget.java
@@ -3,15 +3,20 @@ package de.hysky.skyblocker.skyblock.profileviewer.slayers;
import com.google.gson.JsonObject;
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.skyblock.profileviewer.utils.LevelFinder;
+import de.hysky.skyblocker.skyblock.profileviewer.utils.ProfileViewerUtils;
import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
import de.hysky.skyblocker.utils.render.RenderHelper;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.item.ItemStack;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
import java.awt.*;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
public class SlayerWidget {
@@ -53,7 +58,7 @@ public class SlayerWidget {
} catch (Exception ignored) {}
}
- public void render(DrawContext context, int x, int y) {
+ public void render(DrawContext context, int mouseX, int mouseY, int x, int y) {
context.drawTexture(TEXTURE, x, y, 0, 0, 109, 26, 109, 26);
context.drawTexture(this.item, x + 1, y + 3, 0, 0, 20, 20, 20, 20);
context.drawText(textRenderer, slayerName + " " + slayerLevel.level, x + 31, y + 5, Color.white.hashCode(), false);
@@ -67,6 +72,13 @@ public class SlayerWidget {
context.drawGuiTexture(BAR_BACK, x + 30, y + 15, 75, 6);
Color fillColor = slayerLevel.fill == 1 ? Color.MAGENTA : Color.green;
RenderHelper.renderNineSliceColored(context, BAR_FILL, x + 30, y + 15, (int) (75 * slayerLevel.fill), 6, fillColor);
+
+ if (mouseX > x + 30 && mouseX < x + 105 && mouseY > y + 12 && mouseY < y + 22){
+ List<Text> tooltipText = new ArrayList<>();
+ tooltipText.add(Text.literal(this.slayerName).formatted(Formatting.GREEN));
+ tooltipText.add(Text.literal("XP: " + ProfileViewerUtils.COMMA_FORMATTER.format(this.slayerLevel.xp)).formatted(Formatting.GOLD));
+ context.drawTooltip(textRenderer, tooltipText, mouseX, mouseY);
+ }
}
private int findTotalKills() {
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayersPage.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayersPage.java
index 08e2ca06..528bd3ac 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayersPage.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/slayers/SlayersPage.java
@@ -26,7 +26,7 @@ public class SlayersPage implements ProfileViewerPage {
public void render(DrawContext context, int mouseX, int mouseY, float delta, int rootX, int rootY) {
for (int i = 0; i < slayerWidgets.size(); i++) {
- slayerWidgets.get(i).render(context, rootX, rootY + i * ROW_GAP);
+ slayerWidgets.get(i).render(context, mouseX, mouseY, rootX, rootY + i * ROW_GAP);
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/LevelFinder.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/LevelFinder.java
index b52fd579..f53df0f5 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/LevelFinder.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/LevelFinder.java
@@ -8,15 +8,20 @@ public class LevelFinder {
public long xp;
public int level;
public double fill;
+ public long levelXP;
+ public long nextLevelXP;
public LevelInfo(long xp, int level) {
this.xp = xp;
this.level = level;
}
- public LevelInfo(int level, double fill) {
+ public LevelInfo(long xp, int level, double fill, double levelXP, double nextLevelXP) {
+ this.xp = xp;
this.level = level;
this.fill = fill;
+ this.levelXP = (long) levelXP;
+ this.nextLevelXP = (long) nextLevelXP;
}
}
@@ -255,16 +260,20 @@ public class LevelFinder {
for (int i = boundaries.size() - 1; i >= 0 ; i--) {
if (xp >= boundaries.get(i).xp) {
double fill;
+ double xpInCurrentLevel;
+ double levelXPRange;
if (i < boundaries.getLast().level) {
double currentLevelXP = boundaries.get(i).xp;
double nextLevelXP = boundaries.get(i + 1).xp;
- double levelXPRange = nextLevelXP - currentLevelXP;
- double xpInCurrentLevel = xp - currentLevelXP;
+ levelXPRange = nextLevelXP - currentLevelXP;
+ xpInCurrentLevel = xp - currentLevelXP;
fill = xpInCurrentLevel / levelXPRange;
} else {
fill = 1.0;
+ xpInCurrentLevel = xp - boundaries.getLast().xp;
+ levelXPRange = boundaries.getLast().xp - boundaries.get(boundaries.size()-2).xp;
}
- return new LevelInfo(boundaries.get(i).level, fill);
+ return new LevelInfo(xp, boundaries.get(i).level, fill, xpInCurrentLevel, levelXPRange);
}
}
return new LevelInfo(0L, 0);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SkullCreator.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/ProfileViewerUtils.java
index b074952c..8dadedaf 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SkullCreator.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/ProfileViewerUtils.java
@@ -8,10 +8,14 @@ import net.minecraft.component.type.ProfileComponent;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
+import java.text.NumberFormat;
+import java.util.Locale;
import java.util.Optional;
import java.util.UUID;
-public class SkullCreator {
+public class ProfileViewerUtils {
+ public static final NumberFormat COMMA_FORMATTER = NumberFormat.getNumberInstance(Locale.US);
+
public static ItemStack createSkull(String textureB64) {
ItemStack skull = new ItemStack(Items.PLAYER_HEAD);
try {
@@ -24,4 +28,16 @@ public class SkullCreator {
}
return skull;
}
+
+ public static String numLetterFormat(double amount) {
+ if (amount >= 1_000_000_000) {
+ return String.format("%.4gB", amount / 1_000_000_000);
+ } else if (amount >= 1_000_000) {
+ return String.format("%.4gM", amount / 1_000_000);
+ } else if (amount >= 1_000) {
+ return String.format("%.4gK", amount / 1_000);
+ } else {
+ return String.valueOf((int)(amount));
+ }
+ }
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SubPageSelectButton.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SubPageSelectButton.java
index 4c9dcda4..8398747d 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SubPageSelectButton.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/utils/SubPageSelectButton.java
@@ -34,7 +34,7 @@ public class SubPageSelectButton extends ClickableWidget {
@Override
protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) {
context.fill(this.getX(), this.getY(), this.getX() + 20, this.getY() + 20, Color.BLACK.getRGB());
- context.drawTexture(TEXTURES.get(toggled, isHovered()), this.getX() + 1, this.getY() + 1,0, 0, 18, 18, 18, 18);
+ context.drawTexture(TEXTURES.get(toggled, (mouseX > getX() && mouseX < getX() + 19 && mouseY > getY() && mouseY < getY() + 19)), this.getX() + 1, this.getY() + 1,0, 0, 18, 18, 18, 18);
context.drawItem(ICON, this.getX() + 2, this.getY() + 2);
if ((mouseX > getX() + 1 && mouseX < getX() + 19 && mouseY > getY() + 1 && mouseY < getY() + 19)) {
LoreComponent lore = ICON.get(DataComponentTypes.LORE);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/shortcut/Shortcuts.java b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/Shortcuts.java
index 21d66805..dfde789e 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/shortcut/Shortcuts.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/shortcut/Shortcuts.java
@@ -5,6 +5,7 @@ import com.google.gson.reflect.TypeToken;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
+import com.mojang.brigadier.tree.CommandNode;
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.utils.scheduler.Scheduler;
@@ -156,9 +157,21 @@ public class Shortcuts {
dispatcher.register(literal(key.substring(1)));
}
}
- for (String key : commandArgs.keySet()) {
- if (key.startsWith("/")) {
- dispatcher.register(literal(key.substring(1)).then(argument("args", StringArgumentType.greedyString())));
+ for (Map.Entry<String, String> set : commandArgs.entrySet()) {
+ if (set.getKey().startsWith("/")) {
+ CommandNode<FabricClientCommandSource> redirectLocation = dispatcher.getRoot();
+ for (String word : set.getValue().substring(1).split(" ")) {
+ redirectLocation = redirectLocation.getChild(word);
+ if (redirectLocation == null) {
+ break;
+ }
+ }
+ if (redirectLocation == null) {
+ dispatcher.register(literal(set.getKey().substring(1)).then(argument("args", StringArgumentType.greedyString())));
+ }
+ else {
+ dispatcher.register(literal(set.getKey().substring(1)).redirect(redirectLocation));
+ }
}
}
dispatcher.register(literal(SkyblockerMod.NAMESPACE).then(literal("help").executes(context -> {
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/Ico.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/Ico.java
index b37a3883..f182a949 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/Ico.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/util/Ico.java
@@ -73,6 +73,7 @@ public class Ico {
public static final ItemStack EXPERIENCE_BOTTLE = new ItemStack(Items.EXPERIENCE_BOTTLE);
public static final ItemStack PINK_DYE = new ItemStack(Items.PINK_DYE);
public static final ItemStack LIME_DYE = new ItemStack(Items.LIME_DYE);
+ public static final ItemStack GRAY_DYE = new ItemStack(Items.GRAY_DYE);
public static final ItemStack ENCHANTED_BOOK = new ItemStack(Items.ENCHANTED_BOOK);
public static final ItemStack SPIDER_EYE = new ItemStack(Items.SPIDER_EYE);
public static final ItemStack BLUE_ICE = new ItemStack(Items.BLUE_ICE);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Waypoints.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Waypoints.java
index 18096117..6866aba3 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Waypoints.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Waypoints.java
@@ -11,6 +11,7 @@ import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.Location;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.scheduler.Scheduler;
import de.hysky.skyblocker.utils.waypoint.WaypointCategory;
@@ -21,11 +22,14 @@ import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.toast.SystemToast;
+import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;
@@ -33,6 +37,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
+import java.util.zip.GZIPInputStream;
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
@@ -62,37 +67,42 @@ public class Waypoints {
}
}
- public static List<WaypointCategory> fromSkytilsBase64(String base64, String defaultIsland) {
+ public static List<WaypointCategory> fromSkytils(String waypointsString, String defaultIsland) {
try {
- if (base64.startsWith("<Skytils-Waypoint-Data>(V")) {
- int version = Integer.parseInt(base64.substring(26, base64.indexOf(')')));
+ if (waypointsString.startsWith("<Skytils-Waypoint-Data>(V")) {
+ int version = Integer.parseInt(waypointsString.substring(25, waypointsString.indexOf(')')));
+ waypointsString = waypointsString.substring(waypointsString.indexOf(':') + 1);
if (version == 1) {
- return fromSkytilsJson(new String(Base64.getDecoder().decode(base64.substring(base64.indexOf(':') + 1))), defaultIsland);
+ try (GZIPInputStream reader = new GZIPInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(waypointsString)))) {
+ return fromSkytilsJson(IOUtils.toString(reader, StandardCharsets.UTF_8), defaultIsland);
+ }
} else {
- LOGGER.error("[Skyblocker Waypoints] Unknown Skytils waypoint data version: " + version);
+ LOGGER.error("[Skyblocker Waypoints] Unknown Skytils waypoint data version: {}", version);
}
- } else return fromSkytilsJson(new String(Base64.getDecoder().decode(base64)), defaultIsland);
+ } else return fromSkytilsJson(new String(Base64.getDecoder().decode(waypointsString)), defaultIsland);
} catch (NumberFormatException e) {
LOGGER.error("[Skyblocker Waypoints] Encountered exception while parsing Skytils waypoint data version", e);
- } catch (IllegalArgumentException e) {
+ } catch (Exception e) {
LOGGER.error("[Skyblocker Waypoints] Encountered exception while decoding Skytils waypoint data", e);
}
return Collections.emptyList();
}
- public static List<WaypointCategory> fromSkytilsJson(String waypointCategories, String defaultIsland) {
+ public static List<WaypointCategory> fromSkytilsJson(String waypointCategoriesString, String defaultIsland) {
JsonArray waypointCategoriesJson;
try {
- waypointCategoriesJson = SkyblockerMod.GSON.fromJson(waypointCategories, JsonObject.class).getAsJsonArray("categories");
+ waypointCategoriesJson = SkyblockerMod.GSON.fromJson(waypointCategoriesString, JsonObject.class).getAsJsonArray("categories");
} catch (JsonSyntaxException e) {
+ // Handle the case where there is only a single json list of waypoints and no category data.
JsonObject waypointCategoryJson = new JsonObject();
waypointCategoryJson.addProperty("name", "New Category");
waypointCategoryJson.addProperty("island", defaultIsland);
- waypointCategoryJson.add("waypoints", SkyblockerMod.GSON.fromJson(waypointCategories, JsonArray.class));
+ waypointCategoryJson.add("waypoints", SkyblockerMod.GSON.fromJson(waypointCategoriesString, JsonArray.class));
waypointCategoriesJson = new JsonArray();
waypointCategoriesJson.add(waypointCategoryJson);
}
- return SKYTILS_CODEC.parse(JsonOps.INSTANCE, waypointCategoriesJson).resultOrPartial(LOGGER::error).orElseThrow();
+ List<WaypointCategory> waypointCategories = SKYTILS_CODEC.parse(JsonOps.INSTANCE, waypointCategoriesJson).resultOrPartial(LOGGER::error).orElseThrow();
+ return waypointCategories.stream().map(waypointCategory -> Location.from(waypointCategory.island()) == Location.UNKNOWN ? waypointCategory.withIsland(defaultIsland) : waypointCategory).toList();
}
public static String toSkytilsBase64(List<WaypointCategory> waypointCategories) {
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsShareScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsShareScreen.java
index aee21ec8..eaca845a 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsShareScreen.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/WaypointsShareScreen.java
@@ -31,7 +31,7 @@ public class WaypointsShareScreen extends AbstractWaypointsScreen<WaypointsScree
GridWidget.Adder adder = gridWidget.createAdder(2);
adder.add(ButtonWidget.builder(Text.translatable("skyblocker.waypoints.importWaypointsSkytils"), buttonImport -> {
try {
- List<WaypointCategory> waypointCategories = Waypoints.fromSkytilsBase64(client.keyboard.getClipboard(), island);
+ List<WaypointCategory> waypointCategories = Waypoints.fromSkytils(client.keyboard.getClipboard(), island);
for (WaypointCategory waypointCategory : waypointCategories) {
selectedWaypoints.addAll(waypointCategory.waypoints());
waypoints.put(waypointCategory.island(), waypointCategory);
diff --git a/src/main/java/de/hysky/skyblocker/utils/TextTransformer.java b/src/main/java/de/hysky/skyblocker/utils/TextTransformer.java
new file mode 100644
index 00000000..b8fb5101
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/utils/TextTransformer.java
@@ -0,0 +1,92 @@
+package de.hysky.skyblocker.utils;
+
+import org.jetbrains.annotations.NotNull;
+
+import it.unimi.dsi.fastutil.chars.CharList;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Style;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+/**
+ * Contains utilities for transforming text. These methods are from Aaron's Mod.
+ *
+ * @author AzureAaron
+ */
+public class TextTransformer {
+ private static final CharList FORMAT_CODES = CharList.of('4', 'c', '6', 'e', '2', 'a','b', '3', '1', '9', 'd', '5', 'f', '7', '8', '0', 'r', 'k', 'l', 'm', 'n', 'o');
+
+ /**
+ * Converts strings with section symbol/legacy formatting to MutableText objects.
+ *
+ * @param legacy The string with legacy formatting to be transformed
+ * @return A {@link MutableText} object matching the exact formatting of the input
+ *
+ * @author AzureAaron
+ */
+ public static MutableText fromLegacy(@NotNull String legacy) {
+ MutableText newText = Text.empty();
+ StringBuilder builder = new StringBuilder();
+ Formatting formatting = null;
+ boolean bold = false;
+ boolean italic = false;
+ boolean underline = false;
+ boolean strikethrough = false;
+ boolean obfuscated = false;
+
+ for (int i = 0; i < legacy.length(); i++) {
+ //If we've encountered a new formatting code then append the text from the previous "sequence" and reset state
+ if (i != 0 && legacy.charAt(i - 1) == '§' && FORMAT_CODES.contains(Character.toLowerCase(legacy.charAt(i))) && !builder.isEmpty()) {
+ newText.append(Text.literal(builder.toString()).setStyle(Style.EMPTY
+ .withColor(formatting)
+ .withBold(bold)
+ .withItalic(italic)
+ .withUnderline(underline)
+ .withStrikethrough(strikethrough)
+ .withObfuscated(obfuscated)));
+
+ //Erase all characters in the builder so we can reuse it, also clear formatting
+ builder.delete(0, builder.length());
+ formatting = null;
+ bold = false;
+ italic = false;
+ underline = false;
+ strikethrough = false;
+ obfuscated = false;
+ }
+
+ if (i != 0 && legacy.charAt(i - 1) == '§') {
+ Formatting fmt = Formatting.byCode(legacy.charAt(i));
+
+ switch (fmt) {
+ case BOLD -> bold = true;
+ case ITALIC -> italic = true;
+ case UNDERLINE -> underline = true;
+ case STRIKETHROUGH -> strikethrough = true;
+ case OBFUSCATED -> obfuscated = true;
+
+ default -> formatting = fmt;
+ }
+
+ continue;
+ }
+
+ //This character isn't the start of a formatting sequence or this character isn't part of a formatting sequence
+ if (legacy.charAt(i) != '§' && (i == 0 || (i != 0 && legacy.charAt(i - 1) != '§'))) {
+ builder.append(legacy.charAt(i));
+ }
+
+ // We've read the last character so append the last text with all of the formatting
+ if (i == legacy.length() - 1) {
+ newText.append(Text.literal(builder.toString()).setStyle(Style.EMPTY
+ .withColor(formatting)
+ .withBold(bold)
+ .withItalic(italic)
+ .withUnderline(underline)
+ .withStrikethrough(strikethrough)
+ .withObfuscated(obfuscated)));
+ }
+ }
+ return newText;
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/Utils.java b/src/main/java/de/hysky/skyblocker/utils/Utils.java
index 051110b2..bbeb11b5 100644
--- a/src/main/java/de/hysky/skyblocker/utils/Utils.java
+++ b/src/main/java/de/hysky/skyblocker/utils/Utils.java
@@ -6,21 +6,27 @@ import com.mojang.util.UndashedUuid;
import de.hysky.skyblocker.events.SkyblockEvents;
import de.hysky.skyblocker.mixins.accessors.MessageHandlerAccessor;
import de.hysky.skyblocker.skyblock.item.MuseumItemCache;
-import de.hysky.skyblocker.utils.scheduler.MessageScheduler;
import de.hysky.skyblocker.utils.scheduler.Scheduler;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import net.azureaaron.hmapi.data.server.Environment;
+import net.azureaaron.hmapi.events.HypixelPacketEvents;
+import net.azureaaron.hmapi.network.HypixelNetworking;
+import net.azureaaron.hmapi.network.packet.s2c.ErrorS2CPacket;
+import net.azureaaron.hmapi.network.packet.s2c.HelloS2CPacket;
+import net.azureaaron.hmapi.network.packet.s2c.HypixelS2CPacket;
+import net.azureaaron.hmapi.network.packet.v1.s2c.LocationUpdateS2CPacket;
import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
-import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
-import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.MinecraftClient;
-import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.scoreboard.*;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
+import net.minecraft.util.Util;
+
import org.apache.http.client.HttpResponseException;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
@@ -45,7 +51,7 @@ public class Utils {
private static boolean isOnSkyblock = false;
private static boolean isInjected = false;
/**
- * Current Skyblock location (from /locraw)
+ * Current Skyblock location (from the Mod API)
*/
@NotNull
private static Location location = Location.UNKNOWN;
@@ -60,10 +66,12 @@ public class Utils {
@NotNull
private static String profileId = "";
/**
- * The following fields store data returned from /locraw: {@link #server}, {@link #gameType}, {@link #locationRaw}, and {@link #map}.
+ * The following fields store data returned from the Mod API: {@link #environment}, {@link #server}, {@link #gameType}, {@link #locationRaw}, and {@link #map}.
*/
@SuppressWarnings("JavadocDeclaration")
@NotNull
+ private static Environment environment = Environment.PRODUCTION;
+ @NotNull
private static String server = "";
@NotNull
private static String gameType = "";
@@ -71,11 +79,6 @@ public class Utils {
private static String locationRaw = "";
@NotNull
private static String map = "";
- private static long clientWorldJoinTime = 0;
- private static boolean sentLocRaw = false;
- private static boolean canSendLocRaw = false;
- //This is required to prevent the location change event from being fired twice.
- private static boolean locationChanged = true;
private static boolean mayorTickScheduled = false;
private static int mayorTickRetryAttempts = 0;
private static String mayor = "";
@@ -146,7 +149,7 @@ public class Utils {
}
/**
- * @return the location parsed from /locraw.
+ * @return the location parsed from the Mod API.
*/
@NotNull
public static Location getLocation() {
@@ -154,7 +157,17 @@ public class Utils {
}
/**
- * @return the server parsed from /locraw.
+ * Can be used to restrict features to being active only on the Alpha network.
+ *
+ * @return the current environment parsed from the Mod API.
+ */
+ @NotNull
+ public static Environment getEnvironment() {
+ return environment;
+ }
+
+ /**
+ * @return the server parsed from the Mod API.
*/
@NotNull
public static String getServer() {
@@ -162,7 +175,7 @@ public class Utils {
}
/**
- * @return the game type parsed from /locraw.
+ * @return the game type parsed from the Mod API.
*/
@NotNull
public static String getGameType() {
@@ -170,7 +183,7 @@ public class Utils {
}
/**
- * @return the location raw parsed from /locraw.
+ * @return the location raw parsed from the the Mod API.
*/
@NotNull
public static String getLocationRaw() {
@@ -178,7 +191,7 @@ public class Utils {
}
/**
- * @return the map parsed from /locraw.
+ * @return the map parsed from the Mod API.
*/
@NotNull
public static String getMap() {
@@ -201,20 +214,23 @@ public class Utils {
mayorTickScheduled = true;
}
});
- ClientPlayConnectionEvents.JOIN.register(Utils::onClientWorldJoin);
ClientReceiveMessageEvents.ALLOW_GAME.register(Utils::onChatMessage);
ClientReceiveMessageEvents.GAME_CANCELED.register(Utils::onChatMessage); // Somehow this works even though onChatMessage returns a boolean
+
+ //Register Mod API stuff
+ HypixelNetworking.registerToEvents(Util.make(new Object2IntOpenHashMap<>(), map -> map.put(LocationUpdateS2CPacket.ID, 1)));
+ HypixelPacketEvents.HELLO.register(Utils::onPacket);
+ HypixelPacketEvents.LOCATION_UPDATE.register(Utils::onPacket);
}
/**
- * Updates all the fields stored in this class from the sidebar, player list, and /locraw.
+ * Updates all the fields stored in this class from the sidebar, and player list.
*/
public static void update() {
MinecraftClient client = MinecraftClient.getInstance();
updateScoreboard(client);
updatePlayerPresenceFromScoreboard(client);
updateFromPlayerList(client);
- updateLocRaw();
}
/**
@@ -244,7 +260,7 @@ public class Utils {
if (!isInjected) {
isInjected = true;
}
- isOnSkyblock = true;
+ isOnSkyblock = true; //TODO in the future we can probably replace these skyblock checks entirely with the Mod API
SkyblockEvents.JOIN.invoker().onSkyblockJoin();
}
} else {
@@ -260,7 +276,7 @@ public class Utils {
String serverAddress = (client.getCurrentServerEntry() != null) ? client.getCurrentServerEntry().address.toLowerCase() : "";
String serverBrand = (client.player != null && client.player.networkHandler != null && client.player.networkHandler.getBrand() != null) ? client.player.networkHandler.getBrand() : "";
- return serverAddress.equalsIgnoreCase(ALTERNATE_HYPIXEL_ADDRESS) || serverAddress.contains("hypixel.net") || serverAddress.contains("hypixel.io") || serverBrand.contains("Hypixel BungeeCord");
+ return (!serverAddress.isEmpty() && serverAddress.equalsIgnoreCase(ALTERNATE_HYPIXEL_ADDRESS)) || serverAddress.contains("hypixel.net") || serverAddress.contains("hypixel.io") || serverBrand.contains("Hypixel BungeeCord");
}
private static void onLeaveSkyblock() {
@@ -395,25 +411,39 @@ public class Utils {
}
}
- public static void onClientWorldJoin(ClientPlayNetworkHandler handler, PacketSender sender, MinecraftClient client) {
- clientWorldJoinTime = System.currentTimeMillis();
- resetLocRawInfo();
- }
+ private static void onPacket(HypixelS2CPacket packet) {
+ switch (packet) {
+ case HelloS2CPacket(var environment) -> {
+ Utils.environment = environment;
+ }
- /**
- * Sends /locraw to the server if the player is on skyblock and on a new island.
- */
- private static void updateLocRaw() {
- if (isOnSkyblock) {
- long currentTime = System.currentTimeMillis();
- if (!sentLocRaw && canSendLocRaw && currentTime > clientWorldJoinTime + 1000) {
- MessageScheduler.INSTANCE.sendMessageAfterCooldown("/locraw");
- sentLocRaw = true;
- canSendLocRaw = false;
- locationChanged = true;
+ case LocationUpdateS2CPacket(var serverName, var serverType, var _lobbyName, var mode, var map) -> {
+ Utils.server = serverName;
+ Utils.gameType = serverType.orElse("");
+ Utils.locationRaw = mode.orElse("");
+ Utils.location = Location.from(locationRaw);
+ Utils.map = map.orElse("");
+
+ SkyblockEvents.LOCATION_CHANGE.invoker().onSkyblockLocationChange(location);
}
- } else {
- resetLocRawInfo();
+
+ case ErrorS2CPacket(var id, var error) when id.equals(LocationUpdateS2CPacket.ID) -> {
+ server = "";
+ gameType = "";
+ locationRaw = "";
+ location = Location.UNKNOWN;
+ map = "";
+
+ ClientPlayerEntity player = MinecraftClient.getInstance().player;
+
+ if (player != null) {
+ player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.utils.locationUpdateError").formatted(Formatting.RED)));
+ }
+
+ LOGGER.error("[Skyblocker] Failed to update your current location! Some features of the mod may not work correctly :( - Error: {}", error);
+ }
+
+ default -> {} //Do Nothing
}
}
@@ -423,6 +453,7 @@ public class Utils {
*
* @param message json message from chat
*/
+ @Deprecated
private static void parseLocRaw(String message) {
JsonObject locRaw = JsonParser.parseString(message).getAsJsonObject();
@@ -441,11 +472,6 @@ public class Utils {
if (locRaw.has("map")) {
map = locRaw.get("map").getAsString();
}
-
- if (locationChanged) {
- SkyblockEvents.LOCATION_CHANGE.invoker().onSkyblockLocationChange(location);
- locationChanged = false;
- }
}
/**
@@ -458,10 +484,6 @@ public class Utils {
if (message.startsWith("{\"server\":") && message.endsWith("}")) {
parseLocRaw(message);
- boolean shouldFilter = !sentLocRaw;
- sentLocRaw = false;
-
- return shouldFilter;
}
if (isOnSkyblock) {
@@ -481,16 +503,6 @@ public class Utils {
return true;
}
- private static void resetLocRawInfo() {
- sentLocRaw = false;
- canSendLocRaw = true;
- server = "";
- gameType = "";
- locationRaw = "";
- map = "";
- location = Location.UNKNOWN;
- }
-
private static void scheduleMayorTick() {
long currentYearMillis = SkyblockTime.getSkyblockMillis() % 446400000L; //446400000ms is 1 year, 105600000ms is the amount of time from early spring 1st to late spring 27th
// If current time is past late spring 27th, the next mayor change is at next year's spring 27th, otherwise it's at this year's spring 27th
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java
index a5b9bf6b..1b16b138 100644
--- a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java
+++ b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java
@@ -11,6 +11,7 @@ import de.hysky.skyblocker.utils.render.title.TitleContainer;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.fabricmc.fabric.api.event.Event;
+import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
@@ -20,6 +21,7 @@ import net.minecraft.client.texture.Scaling;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.BufferAllocator;
import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.client.world.ClientWorld;
import net.minecraft.sound.SoundEvents;
import net.minecraft.text.OrderedText;
import net.minecraft.text.Text;
@@ -64,18 +66,22 @@ public class RenderHelper {
}
public static void renderFilled(WorldRenderContext context, BlockPos pos, Vec3d dimensions, float[] colorComponents, float alpha, boolean throughWalls) {
+ renderFilled(context, Vec3d.of(pos), dimensions, colorComponents, alpha, throughWalls);
+ }
+
+ public static void renderFilled(WorldRenderContext context, Vec3d pos, Vec3d dimensions, float[] colorComponents, float alpha, boolean throughWalls) {
if (throughWalls) {
if (FrustumUtils.isVisible(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + dimensions.x, pos.getY() + dimensions.y, pos.getZ() + dimensions.z)) {
- renderFilled(context, Vec3d.of(pos), dimensions, colorComponents, alpha, true);
+ renderFilledInternal(context, pos, dimensions, colorComponents, alpha, true);
}
} else {
if (OcclusionCulling.getRegularCuller().isVisible(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + dimensions.x, pos.getY() + dimensions.y, pos.getZ() + dimensions.z)) {
- renderFilled(context, Vec3d.of(pos), dimensions, colorComponents, alpha, false);
+ renderFilledInternal(context, pos, dimensions, colorComponents, alpha, false);
}
}
}
- private static void renderFilled(WorldRenderContext context, Vec3d pos, Vec3d dimensions, float[] colorComponents, float alpha, boolean throughWalls) {
+ private static void renderFilledInternal(WorldRenderContext context, Vec3d pos, Vec3d dimensions, float[] colorComponents, float alpha, boolean throughWalls) {
MatrixStack matrices = context.matrixStack();
Vec3d camera = context.camera().getPos();
@@ -330,6 +336,14 @@ public class RenderHelper {
}
}
+ public static Box getBlockBoundingBox(ClientWorld world, BlockPos pos) {
+ return getBlockBoundingBox(world, world.getBlockState(pos), pos);
+ }
+
+ public static Box getBlockBoundingBox(ClientWorld world, BlockState state, BlockPos pos) {
+ return state.getOutlineShape(world, pos).asCuboid().getBoundingBox().offset(pos);
+ }
+
/**
* Adds the title to {@link TitleContainer} and {@link #playNotificationSound() plays the notification sound} if the title is not in the {@link TitleContainer} already.
* No checking needs to be done on whether the title is in the {@link TitleContainer} already by the caller.
diff --git a/src/main/java/de/hysky/skyblocker/utils/waypoint/NamedWaypoint.java b/src/main/java/de/hysky/skyblocker/utils/waypoint/NamedWaypoint.java
index 2f02b51f..5ba920c0 100644
--- a/src/main/java/de/hysky/skyblocker/utils/waypoint/NamedWaypoint.java
+++ b/src/main/java/de/hysky/skyblocker/utils/waypoint/NamedWaypoint.java
@@ -6,11 +6,13 @@ import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.utils.ColorUtils;
import de.hysky.skyblocker.utils.render.RenderHelper;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext;
import net.minecraft.text.Text;
import net.minecraft.text.TextCodecs;
import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.ColorHelper;
import net.minecraft.util.math.Vec3d;
import java.util.Objects;
@@ -32,7 +34,7 @@ public class NamedWaypoint extends Waypoint {
Codec.INT.fieldOf("y").forGetter(waypoint -> waypoint.pos.getY()),
Codec.INT.fieldOf("z").forGetter(waypoint -> waypoint.pos.getZ()),
Codec.either(Codec.STRING, Codec.INT).xmap(either -> either.map(str -> str, Object::toString), Either::left).fieldOf("name").forGetter(waypoint -> waypoint.name.getString()),
- Codec.INT.fieldOf("color").forGetter(waypoint -> (int) (waypoint.alpha * 255) << 24 | (int) (waypoint.colorComponents[0] * 255) << 16 | (int) (waypoint.colorComponents[1] * 255) << 8 | (int) (waypoint.colorComponents[2] * 255)),
+ Codec.INT.optionalFieldOf("color", ColorHelper.Argb.getArgb(128, 0, 255, 0)).forGetter(waypoint -> (int) (waypoint.alpha * 255) << 24 | (int) (waypoint.colorComponents[0] * 255) << 16 | (int) (waypoint.colorComponents[1] * 255) << 8 | (int) (waypoint.colorComponents[2] * 255)),
Codec.BOOL.fieldOf("enabled").forGetter(Waypoint::shouldRender)
).apply(instance, NamedWaypoint::fromSkytils));
public final Text name;
@@ -69,7 +71,7 @@ public class NamedWaypoint extends Waypoint {
if (alpha == 0) {
alpha = DEFAULT_HIGHLIGHT_ALPHA;
}
- return new NamedWaypoint(new BlockPos(x, y, z), name, new float[]{((color & 0x00FF0000) >> 16) / 255f, ((color & 0x0000FF00) >> 8) / 255f, (color & 0x000000FF) / 255f}, alpha, enabled);
+ return new NamedWaypoint(new BlockPos(x, y, z), name, ColorUtils.getFloatComponents(color), alpha, enabled);
}
public NamedWaypoint copy() {
diff --git a/src/main/java/de/hysky/skyblocker/utils/waypoint/WaypointCategory.java b/src/main/java/de/hysky/skyblocker/utils/waypoint/WaypointCategory.java
index db2a6d82..8bfef7f4 100644
--- a/src/main/java/de/hysky/skyblocker/utils/waypoint/WaypointCategory.java
+++ b/src/main/java/de/hysky/skyblocker/utils/waypoint/WaypointCategory.java
@@ -29,6 +29,10 @@ public record WaypointCategory(String name, String island, List<NamedWaypoint> w
return new WaypointCategory(name, island(), waypoints());
}
+ public WaypointCategory withIsland(String island) {
+ return new WaypointCategory(name(), island, waypoints());
+ }
+
public WaypointCategory deepCopy() {
return new WaypointCategory(name(), island(), waypoints().stream().map(NamedWaypoint::copy).collect(Collectors.toList()));
}