aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/de/hysky/skyblocker/SkyblockerMod.java1
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java9
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java8
-rw-r--r--src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java7
-rw-r--r--src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java20
-rw-r--r--src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/events/SkyblockEvents.java16
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/GenericContainerScreenHandlerMixin.java21
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java17
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/MinecraftClientMixin.java12
-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/chocolatefactory/EggFinder.java63
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockInventoryScreen.java194
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PetLevelAdder.java6
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java12
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java14
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java115
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java12
-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.java69
-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.java11
-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/tabhud/util/Ico.java1
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java49
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/Http.java21
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/ItemUtils.java37
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/SkyblockTime.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/Utils.java65
-rw-r--r--src/main/resources/assets/skyblocker/lang/en_us.json21
-rw-r--r--src/main/resources/assets/skyblocker/textures/gui/sprites/equipment/empty_icon.pngbin0 -> 173 bytes
50 files changed, 1069 insertions, 324 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
index 1f766000..f1eb4321 100644
--- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
+++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java
@@ -186,6 +186,7 @@ public class SkyblockerMod implements ClientModInitializer {
DojoManager.init();
RenderHelper.init();
FancyStatusBars.init();
+ SkyblockInventoryScreen.initEquipment();
EventNotifications.init();
containerSolverManager.init();
statusBarTracker.init();
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java
index bf6eefd5..96bb226d 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java
@@ -4,6 +4,7 @@ import de.hysky.skyblocker.SkyblockerScreen;
import de.hysky.skyblocker.config.ConfigUtils;
import de.hysky.skyblocker.config.SkyblockerConfig;
import de.hysky.skyblocker.config.configs.GeneralConfig;
+import de.hysky.skyblocker.skyblock.item.tooltip.adders.CraftPriceTooltip;
import de.hysky.skyblocker.skyblock.shortcut.ShortcutsConfigScreen;
import dev.isxander.yacl3.api.*;
import dev.isxander.yacl3.api.controller.FloatSliderControllerBuilder;
@@ -163,6 +164,14 @@ public class GeneralCategory {
newValue -> config.general.itemTooltip.enableBazaarPrice = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
+ .option(Option.<GeneralConfig.Craft>createBuilder()
+ .name(Text.translatable("skyblocker.config.general.itemTooltip.craft"))
+ .binding(defaults.general.itemTooltip.enableCraftingCost,
+ () -> config.general.itemTooltip.enableCraftingCost,
+ newValue -> config.general.itemTooltip.enableCraftingCost = newValue)
+ .listener((Option<GeneralConfig.Craft> ignored, GeneralConfig.Craft ignored2) -> CraftPriceTooltip.clearCache())
+ .controller(ConfigUtils::createEnumCyclingListController)
+ .build())
.option(Option.<Boolean>createBuilder()
.name(Text.translatable("skyblocker.config.general.itemTooltip.enableObtainedDate"))
.binding(defaults.general.itemTooltip.enableObtainedDate,
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 1518dfe7..56dbca94 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/HelperCategory.java
@@ -196,6 +196,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())
.build();
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java
index a2a0f815..889b253a 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java
@@ -68,6 +68,13 @@ public class UIAndVisualsCategory {
newValue -> config.uiAndVisuals.hideStatusEffectOverlay = newValue)
.controller(ConfigUtils::createBooleanController)
.build())
+ .option(Option.<Boolean>createBuilder()
+ .name(Text.translatable("skyblocker.config.uiAndVisuals.showEquipmentInInventory"))
+ .binding(defaults.uiAndVisuals.showEquipmentInInventory,
+ () -> config.uiAndVisuals.showEquipmentInInventory,
+ newValue -> config.uiAndVisuals.showEquipmentInInventory = newValue)
+ .controller(ConfigUtils::createBooleanController)
+ .build())
//Chest Value FIXME change dropdown to color controller
.group(OptionGroup.createBuilder()
diff --git a/src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java
index 9f612028..754e15f1 100644
--- a/src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java
@@ -115,6 +115,9 @@ public class GeneralConfig {
public boolean enableBazaarPrice = true;
@SerialEntry
+ public Craft enableCraftingCost = Craft.OFF;
+
+ @SerialEntry
public boolean enableObtainedDate = true;
@SerialEntry
@@ -139,6 +142,23 @@ public class GeneralConfig {
}
}
+ public enum Craft {
+ SELL_ORDER, BUY_ORDER, OFF;
+
+ @Override
+ public String toString() {
+ return I18n.translate("skyblocker.config.general.itemTooltip.craft." + name());
+ }
+
+ public String getOrder() {
+ return switch (this) {
+ case SELL_ORDER -> "sellPrice";
+ case BUY_ORDER -> "buyPrice";
+ case OFF -> null;
+ };
+ }
+ }
+
public static class ItemInfoDisplay {
@SerialEntry
public boolean slotText = 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 33047f1c..636d76da 100644
--- a/src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/configs/HelperConfig.java
@@ -90,5 +90,8 @@ public class HelperConfig {
@SerialEntry
public boolean enableTimeTowerReminder = true;
+
+ @SerialEntry
+ public boolean straySound = true;
}
}
diff --git a/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java
index e016988b..80bdb1c9 100644
--- a/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java
@@ -28,6 +28,9 @@ public class UIAndVisualsConfig {
public boolean hideStatusEffectOverlay = false;
@SerialEntry
+ public boolean showEquipmentInInventory = true;
+
+ @SerialEntry
public ChestValue chestValue = new ChestValue();
@SerialEntry
diff --git a/src/main/java/de/hysky/skyblocker/events/SkyblockEvents.java b/src/main/java/de/hysky/skyblocker/events/SkyblockEvents.java
index c268103d..93426143 100644
--- a/src/main/java/de/hysky/skyblocker/events/SkyblockEvents.java
+++ b/src/main/java/de/hysky/skyblocker/events/SkyblockEvents.java
@@ -26,6 +26,16 @@ public final class SkyblockEvents {
}
});
+ /**
+ * Called when the player's Skyblock profile changes.
+ * @implNote This is called upon receiving the chat message for the profile change rather than the exact moment of profile change, so it may be delayed by a few seconds.
+ */
+ public static final Event<ProfileChange> PROFILE_CHANGE = EventFactory.createArrayBacked(ProfileChange.class, callbacks -> (prev, profile) -> {
+ for (ProfileChange callback : callbacks) {
+ callback.onSkyblockProfileChange(prev, profile);
+ }
+ });
+
@Environment(EnvType.CLIENT)
@FunctionalInterface
public interface SkyblockJoin {
@@ -43,4 +53,10 @@ public final class SkyblockEvents {
public interface SkyblockLocationChange {
void onSkyblockLocationChange(Location location);
}
+
+ @Environment(EnvType.CLIENT)
+ @FunctionalInterface
+ public interface ProfileChange {
+ void onSkyblockProfileChange(String prevProfileId, String profileId);
+ }
}
diff --git a/src/main/java/de/hysky/skyblocker/mixins/GenericContainerScreenHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixins/GenericContainerScreenHandlerMixin.java
index f75af09a..3c3dbd52 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/GenericContainerScreenHandlerMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/GenericContainerScreenHandlerMixin.java
@@ -2,7 +2,11 @@ package de.hysky.skyblocker.mixins;
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.skyblock.dungeon.partyfinder.PartyFinderScreen;
+import de.hysky.skyblocker.skyblock.item.SkyblockInventoryScreen;
+import de.hysky.skyblocker.utils.Utils;
import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.screen.ingame.GenericContainerScreen;
import net.minecraft.item.ItemStack;
import net.minecraft.screen.GenericContainerScreenHandler;
import net.minecraft.screen.ScreenHandler;
@@ -22,8 +26,21 @@ public abstract class GenericContainerScreenHandlerMixin extends ScreenHandler {
public void setStackInSlot(int slot, int revision, ItemStack stack) {
super.setStackInSlot(slot, revision, stack);
SkyblockerMod.getInstance().containerSolverManager.markDirty();
- if (MinecraftClient.getInstance().currentScreen instanceof PartyFinderScreen screen) {
- screen.markDirty();
+
+ Screen currentScreen = MinecraftClient.getInstance().currentScreen;
+ switch (currentScreen) {
+ case PartyFinderScreen screen -> screen.markDirty();
+ case GenericContainerScreen screen when screen.getTitle().getString().toLowerCase().contains("equipment") -> {
+ int line = slot/9;
+ if (line > 0 && line < 5 && slot % 9 == 1) {
+ boolean empty = stack.getName().getString().trim().toLowerCase().startsWith("empty");
+ if (Utils.isInTheRift())
+ SkyblockInventoryScreen.equipment_rift[line - 1] = empty ? ItemStack.EMPTY : stack;
+ else
+ SkyblockInventoryScreen.equipment[line - 1] = empty ? ItemStack.EMPTY : stack;
+ }
+ }
+ case null, default -> {}
}
}
diff --git a/src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java
index 0d833c22..2194e7a8 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java
@@ -1,11 +1,16 @@
package de.hysky.skyblocker.mixins;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.skyblock.itemlist.ItemListWidget;
import de.hysky.skyblocker.utils.Utils;
+import net.minecraft.client.gui.screen.ButtonTextures;
import net.minecraft.client.gui.screen.ingame.InventoryScreen;
import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget;
+import net.minecraft.client.gui.widget.ButtonWidget;
+import net.minecraft.client.gui.widget.TexturedButtonWidget;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@@ -15,4 +20,16 @@ public abstract class InventoryScreenMixin {
private RecipeBookWidget skyblocker$replaceRecipeBook(RecipeBookWidget original) {
return SkyblockerConfigManager.get().general.itemList.enableItemList && Utils.isOnSkyblock() ? new ItemListWidget() : original;
}
+
+ @WrapOperation(method = "init", at = @At(value = "NEW", target = "(IIIILnet/minecraft/client/gui/screen/ButtonTextures;Lnet/minecraft/client/gui/widget/ButtonWidget$PressAction;)Lnet/minecraft/client/gui/widget/TexturedButtonWidget;"))
+ private TexturedButtonWidget skyblocker$moveButton(int x, int y, int width, int height, ButtonTextures textures, ButtonWidget.PressAction pressAction, Operation<TexturedButtonWidget> original) {
+ if (!Utils.isOnSkyblock() || !SkyblockerConfigManager.get().uiAndVisuals.showEquipmentInInventory) return original.call(x, y, width, height, textures, pressAction);
+ return new TexturedButtonWidget(x + 21, y, width, height, textures, pressAction);
+ }
+
+ @WrapOperation(method = "method_19891", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/widget/ButtonWidget;setPosition(II)V"))
+ private void skyblocker$moveButtonWhenPressed(ButtonWidget instance, int i, int j, Operation<Void> original) {
+ if (!Utils.isOnSkyblock() || !SkyblockerConfigManager.get().uiAndVisuals.showEquipmentInInventory) original.call(instance, i, j);
+ else instance.setPosition(i + 21, j);
+ }
}
diff --git a/src/main/java/de/hysky/skyblocker/mixins/MinecraftClientMixin.java b/src/main/java/de/hysky/skyblocker/mixins/MinecraftClientMixin.java
index b04f958f..f91ddc86 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/MinecraftClientMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/MinecraftClientMixin.java
@@ -1,7 +1,11 @@
package de.hysky.skyblocker.mixins;
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.skyblock.item.HotbarSlotLock;
import de.hysky.skyblocker.skyblock.item.ItemProtection;
+import de.hysky.skyblocker.skyblock.item.SkyblockInventoryScreen;
import de.hysky.skyblocker.utils.JoinWorldPlaceholderScreen;
import de.hysky.skyblocker.utils.ReconfiguringPlaceholderScreen;
import de.hysky.skyblocker.utils.Utils;
@@ -9,8 +13,10 @@ import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.DownloadingTerrainScreen;
import net.minecraft.client.gui.screen.ReconfiguringScreen;
import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.screen.ingame.InventoryScreen;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.entity.player.PlayerEntity;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -54,4 +60,10 @@ public abstract class MinecraftClientMixin {
private Screen modifyJoinWorld(Screen screen) {
return Utils.isOnSkyblock() ? new JoinWorldPlaceholderScreen() : screen;
}
+
+ @WrapOperation(method = "handleInputEvents", at = @At(value = "NEW", target = "(Lnet/minecraft/entity/player/PlayerEntity;)Lnet/minecraft/client/gui/screen/ingame/InventoryScreen;"))
+ private InventoryScreen skyblocker$skyblockInventoryScreen(PlayerEntity player, Operation<InventoryScreen> original) {
+ if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().uiAndVisuals.showEquipmentInInventory) return new SkyblockInventoryScreen(player);
+ else return original.call(player);
+ }
} \ No newline at end of file
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/chocolatefactory/EggFinder.java b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/EggFinder.java
index 42883c4f..620da37c 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/EggFinder.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/chocolatefactory/EggFinder.java
@@ -24,7 +24,6 @@ import net.minecraft.text.ClickEvent;
import net.minecraft.text.HoverEvent;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
-import org.apache.commons.lang3.mutable.MutableObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,6 +33,7 @@ import java.util.regex.Pattern;
public class EggFinder {
private static final Pattern eggFoundPattern = Pattern.compile("^(?:HOPPITY'S HUNT You found a Chocolate|You have already collected this Chocolate) (Breakfast|Lunch|Dinner)");
+ private static final Pattern newEggPattern = Pattern.compile("^HOPPITY'S HUNT A Chocolate (Breakfast|Lunch|Dinner) Egg has appeared!$");
private static final Logger logger = LoggerFactory.getLogger("Skyblocker Egg Finder");
private static final LinkedList<ArmorStandEntity> armorStandQueue = new LinkedList<>();
private static final Location[] possibleLocations = {Location.CRIMSON_ISLE, Location.CRYSTAL_HOLLOWS, Location.DUNGEON_HUB, Location.DWARVEN_MINES, Location.HUB, Location.THE_END, Location.THE_PARK, Location.GOLD_MINE, Location.DEEP_CAVERNS, Location.SPIDERS_DEN, Location.THE_FARMING_ISLAND};
@@ -96,7 +96,7 @@ public class EggFinder {
for (ItemStack itemStack : armorStand.getArmorItems()) {
ItemUtils.getHeadTextureOptional(itemStack).ifPresent(texture -> {
for (EggType type : EggType.entries) { //Compare blockPos rather than entity to avoid incorrect matches when the entity just moves rather than a new one being spawned elsewhere
- if (texture.equals(type.texture) && (type.egg.getValue() == null || !type.egg.getValue().entity.getBlockPos().equals(armorStand.getBlockPos()))) {
+ if (texture.equals(type.texture) && (type.egg == null || !type.egg.entity.getBlockPos().equals(armorStand.getBlockPos()))) {
handleFoundEgg(armorStand, type);
return;
}
@@ -109,27 +109,29 @@ public class EggFinder {
if (!SkyblockerConfigManager.get().helpers.chocolateFactory.enableEggFinder) return;
isLocationCorrect = false;
for (EggType type : EggType.entries) {
- type.egg.setValue(null);
+ type.egg = null;
}
}
private static void handleFoundEgg(ArmorStandEntity entity, EggType eggType) {
- eggType.egg.setValue(new Egg(entity, new Waypoint(entity.getBlockPos().up(2), SkyblockerConfigManager.get().helpers.chocolateFactory.waypointType, ColorUtils.getFloatComponents(eggType.color))));
-
- if (!SkyblockerConfigManager.get().helpers.chocolateFactory.sendEggFoundMessages) return;
- MinecraftClient.getInstance().player.sendMessage(Constants.PREFIX.get()
- .append("Found a ")
- .append(Text.literal("Chocolate " + eggType + " Egg")
- .withColor(eggType.color))
- .append(" at " + entity.getBlockPos().up(2).toShortString() + "!")
- .styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/skyblocker eggFinder shareLocation " + entity.getBlockX() + " " + (entity.getBlockY() + 2) + " " + entity.getBlockZ() + " " + eggType))
- .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.literal("Click to share the location in chat!").formatted(Formatting.GREEN)))));
+ eggType.egg = new Egg(entity, new Waypoint(entity.getBlockPos().up(2), SkyblockerConfigManager.get().helpers.chocolateFactory.waypointType, ColorUtils.getFloatComponents(eggType.color)));
+
+ if (!SkyblockerConfigManager.get().helpers.chocolateFactory.sendEggFoundMessages || System.currentTimeMillis() - eggType.messageLastSent < 1000) return;
+ eggType.messageLastSent = System.currentTimeMillis();
+ MinecraftClient.getInstance().player.sendMessage(
+ Constants.PREFIX.get()
+ .append("Found a ")
+ .append(Text.literal("Chocolate " + eggType + " Egg")
+ .withColor(eggType.color))
+ .append(" at " + entity.getBlockPos().up(2).toShortString() + "!")
+ .styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/skyblocker eggFinder shareLocation " + entity.getBlockX() + " " + (entity.getBlockY() + 2) + " " + entity.getBlockZ() + " " + eggType))
+ .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.literal("Click to share the location in chat!").formatted(Formatting.GREEN)))));
}
private static void renderWaypoints(WorldRenderContext context) {
if (!SkyblockerConfigManager.get().helpers.chocolateFactory.enableEggFinder) return;
for (EggType type : EggType.entries) {
- Egg egg = type.egg.getValue();
+ Egg egg = type.egg;
if (egg != null && egg.waypoint.shouldRender()) egg.waypoint.render(context);
}
}
@@ -139,30 +141,47 @@ public class EggFinder {
Matcher matcher = eggFoundPattern.matcher(text.getString());
if (matcher.find()) {
try {
- Egg egg = EggType.valueOf(matcher.group(1).toUpperCase()).egg.getValue();
+ Egg egg = EggType.valueOf(matcher.group(1).toUpperCase()).egg;
if (egg != null) egg.waypoint.setFound();
} catch (IllegalArgumentException e) {
logger.error("[Skyblocker Egg Finder] Failed to find egg type for egg found message. Tried to match against: " + matcher.group(0), e);
}
}
+
+ matcher.usePattern(newEggPattern);
+ if (matcher.find()) {
+ try {
+ EggType.valueOf(matcher.group(1).toUpperCase()).egg = null;
+ } catch (IllegalArgumentException e) {
+ logger.error("[Skyblocker Egg Finder] Failed to find egg type for egg spawn message. Tried to match against: " + matcher.group(0), e);
+ }
+ }
}
- record Egg(ArmorStandEntity entity, Waypoint waypoint) { }
+ record Egg(ArmorStandEntity entity, Waypoint waypoint) {}
+ @SuppressWarnings("DataFlowIssue") //Removes that pesky "unboxing of Integer might cause NPE" warning when we already know it's not null
enum EggType {
- LUNCH(new MutableObject<>(), Formatting.BLUE.getColorValue(), "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjU2ODExMiwKICAicHJvZmlsZUlkIiA6ICI3NzUwYzFhNTM5M2Q0ZWQ0Yjc2NmQ4ZGUwOWY4MjU0NiIsCiAgInByb2ZpbGVOYW1lIiA6ICJSZWVkcmVsIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzdhZTZkMmQzMWQ4MTY3YmNhZjk1MjkzYjY4YTRhY2Q4NzJkNjZlNzUxZGI1YTM0ZjJjYmM2NzY2YTAzNTZkMGEiCiAgICB9CiAgfQp9"),
- DINNER(new MutableObject<>(), Formatting.GREEN.getColorValue(), "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjY0OTcwMSwKICAicHJvZmlsZUlkIiA6ICI3NGEwMzQxNWY1OTI0ZTA4YjMyMGM2MmU1NGE3ZjJhYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJNZXp6aXIiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTVlMzYxNjU4MTlmZDI4NTBmOTg1NTJlZGNkNzYzZmY5ODYzMTMxMTkyODNjMTI2YWNlMGM0Y2M0OTVlNzZhOCIKICAgIH0KICB9Cn0"),
- BREAKFAST(new MutableObject<>(), Formatting.GOLD.getColorValue(), "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjY3MzE0OSwKICAicHJvZmlsZUlkIiA6ICJiN2I4ZTlhZjEwZGE0NjFmOTY2YTQxM2RmOWJiM2U4OCIsCiAgInByb2ZpbGVOYW1lIiA6ICJBbmFiYW5hbmFZZzciLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTQ5MzMzZDg1YjhhMzE1ZDAzMzZlYjJkZjM3ZDhhNzE0Y2EyNGM1MWI4YzYwNzRmMWI1YjkyN2RlYjUxNmMyNCIKICAgIH0KICB9Cn0");
+ LUNCH(Formatting.BLUE.getColorValue(), "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjU2ODExMiwKICAicHJvZmlsZUlkIiA6ICI3NzUwYzFhNTM5M2Q0ZWQ0Yjc2NmQ4ZGUwOWY4MjU0NiIsCiAgInByb2ZpbGVOYW1lIiA6ICJSZWVkcmVsIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzdhZTZkMmQzMWQ4MTY3YmNhZjk1MjkzYjY4YTRhY2Q4NzJkNjZlNzUxZGI1YTM0ZjJjYmM2NzY2YTAzNTZkMGEiCiAgICB9CiAgfQp9"),
+ DINNER(Formatting.GREEN.getColorValue(), "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjY0OTcwMSwKICAicHJvZmlsZUlkIiA6ICI3NGEwMzQxNWY1OTI0ZTA4YjMyMGM2MmU1NGE3ZjJhYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJNZXp6aXIiLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTVlMzYxNjU4MTlmZDI4NTBmOTg1NTJlZGNkNzYzZmY5ODYzMTMxMTkyODNjMTI2YWNlMGM0Y2M0OTVlNzZhOCIKICAgIH0KICB9Cn0"),
+ BREAKFAST(Formatting.GOLD.getColorValue(), "ewogICJ0aW1lc3RhbXAiIDogMTcxMTQ2MjY3MzE0OSwKICAicHJvZmlsZUlkIiA6ICJiN2I4ZTlhZjEwZGE0NjFmOTY2YTQxM2RmOWJiM2U4OCIsCiAgInByb2ZpbGVOYW1lIiA6ICJBbmFiYW5hbmFZZzciLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTQ5MzMzZDg1YjhhMzE1ZDAzMzZlYjJkZjM3ZDhhNzE0Y2EyNGM1MWI4YzYwNzRmMWI1YjkyN2RlYjUxNmMyNCIKICAgIH0KICB9Cn0");
- public final MutableObject<Egg> egg;
+ private Egg egg = null;
public final int color;
public final String texture;
+ /*
+ When a new egg spawns in the player's range, the order of packets/messages goes like this:
+ set_equipment -> new egg message -> set_entity_data
+ We have to set the egg to null to prevent the highlight from staying where it was before the new egg spawned,
+ and doing so causes the found message to get sent twice. This is the reason for the existence of this field, so that we can not send the 2nd message.
+ This doesn't fix the field being set twice, but that's not an issue anyway. It'd be much harder to fix the highlight issue mentioned above if it wasn't being set twice.
+ */
+ private long messageLastSent = 0;
//This is to not create an array each time we iterate over the values
public static final ObjectImmutableList<EggType> entries = ObjectImmutableList.of(BREAKFAST, LUNCH, DINNER);
- EggType(MutableObject<Egg> egg, int color, String texture) {
- this.egg = egg;
+ EggType(int color, String texture) {
this.color = color;
this.texture = texture;
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockInventoryScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockInventoryScreen.java
new file mode 100644
index 00000000..42a52a85
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/SkyblockInventoryScreen.java
@@ -0,0 +1,194 @@
+package de.hysky.skyblocker.skyblock.item;
+
+import com.mojang.serialization.Codec;
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.events.SkyblockEvents;
+import de.hysky.skyblocker.mixins.accessors.SlotAccessor;
+import de.hysky.skyblocker.utils.ItemUtils;
+import de.hysky.skyblocker.utils.Utils;
+import de.hysky.skyblocker.utils.scheduler.MessageScheduler;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.ingame.InventoryScreen;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.inventory.Inventory;
+import net.minecraft.inventory.SimpleInventory;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.NbtOps;
+import net.minecraft.nbt.StringNbtReader;
+import net.minecraft.nbt.visitor.StringNbtWriter;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.util.Identifier;
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+/**
+ * Opened here {@code de.hysky.skyblocker.mixins.MinecraftClientMixin#skyblocker$skyblockInventoryScreen}
+ * <br>
+ * Book button is moved here {@code de.hysky.skyblocker.mixins.InventoryScreenMixin#skyblocker}
+ */
+public class SkyblockInventoryScreen extends InventoryScreen {
+ private static final Logger LOGGER = LoggerFactory.getLogger("Equipment");
+ private static final Supplier<ItemStack[]> EMPTY_EQUIPMENT = () -> new ItemStack[]{ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY};
+ public static final ItemStack[] equipment = EMPTY_EQUIPMENT.get();
+ public static final ItemStack[] equipment_rift = EMPTY_EQUIPMENT.get();
+ private static final Codec<ItemStack[]> CODEC = ItemUtils.EMPTY_ALLOWING_ITEMSTACK_CODEC.listOf(4, 8) // min size at 4 for backwards compat
+ .xmap(itemStacks -> itemStacks.toArray(ItemStack[]::new), List::of).fieldOf("items").codec();
+
+ private static final Identifier SLOT_TEXTURE = Identifier.ofVanilla("container/slot");
+ private static final Identifier EMPTY_SLOT = Identifier.of(SkyblockerMod.NAMESPACE, "equipment/empty_icon");
+ private static final Path FOLDER = SkyblockerMod.CONFIG_DIR.resolve("equipment");
+
+ private final Slot[] equipmentSlots = new Slot[4];
+
+ private static void save(String profileId) {
+ try {
+ Files.createDirectories(FOLDER);
+ } catch (IOException e) {
+ LOGGER.error("[Skyblocker] Failed to create folder for equipment!", e);
+ }
+ Path resolve = FOLDER.resolve(profileId + ".nbt");
+
+ try (BufferedWriter writer = Files.newBufferedWriter(resolve)) {
+
+ writer.write(new StringNbtWriter().apply(CODEC.encodeStart(NbtOps.INSTANCE, ArrayUtils.addAll(equipment, equipment_rift)).getOrThrow()));
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker] Failed to save Equipment data", e);
+ }
+ }
+
+ private static void load(String profileId) {
+ Path resolve = FOLDER.resolve(profileId + ".nbt");
+ CompletableFuture.supplyAsync(() -> {
+ try (BufferedReader reader = Files.newBufferedReader(resolve)) {
+ return CODEC.parse(NbtOps.INSTANCE, StringNbtReader.parse(reader.lines().collect(Collectors.joining()))).getOrThrow();
+ } catch (NoSuchFileException ignored) {
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker] Failed to load Equipment data", e);
+ }
+ return EMPTY_EQUIPMENT.get();
+ // Schedule on main thread to avoid any async weirdness
+ }).thenAccept(itemStacks -> MinecraftClient.getInstance().execute(() -> {
+ System.arraycopy(itemStacks, 0, equipment, 0, Math.min(itemStacks.length, 4));
+ if (itemStacks.length <= 4) return;
+ System.arraycopy(itemStacks, 4, equipment_rift, 0, Math.clamp(itemStacks.length - 4, 0, 4));
+ }));
+ }
+
+ public static void initEquipment() {
+
+ SkyblockEvents.PROFILE_CHANGE.register(((prevProfileId, profileId) -> {
+ if (!prevProfileId.isEmpty()) CompletableFuture.runAsync(() -> save(prevProfileId)).thenRun(() -> load(profileId));
+ else load(profileId);
+ }));
+
+ ClientLifecycleEvents.CLIENT_STOPPING.register(client1 -> {
+ String profileId = Utils.getProfileId();
+ if (!profileId.isBlank()) {
+ CompletableFuture.runAsync(() -> save(profileId));
+ }
+ });
+ }
+
+ public SkyblockInventoryScreen(PlayerEntity player) {
+ super(player);
+ SimpleInventory inventory = new SimpleInventory(Utils.isInTheRift() ? equipment_rift: equipment);
+
+ Slot slot = handler.slots.get(45);
+ ((SlotAccessor) slot).setX(slot.x + 21);
+ for (int i = 0; i < 4; i++) {
+ equipmentSlots[i] = new EquipmentSlot(inventory, i, 77, 8 + i * 18);
+ }
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ for (Slot equipmentSlot : equipmentSlots) {
+ if (isPointWithinBounds(equipmentSlot.x, equipmentSlot.y, 16, 16, mouseX, mouseY)) {
+ MessageScheduler.INSTANCE.sendMessageAfterCooldown("/equipment");
+ return true;
+ }
+ }
+ return super.mouseClicked(mouseX, mouseY, button);
+ }
+
+ /**
+ * Draws the equipment slots in the foreground layer after vanilla slots are drawn
+ * in {@link net.minecraft.client.gui.screen.ingame.HandledScreen#render(DrawContext, int, int, float) HandledScreen#render(DrawContext, int, int, float)}.
+ */
+ @Override
+ protected void drawForeground(DrawContext context, int mouseX, int mouseY) {
+ for (Slot equipmentSlot : equipmentSlots) {
+ drawSlot(context, equipmentSlot);
+ if (isPointWithinBounds(equipmentSlot.x, equipmentSlot.y, 16, 16, mouseX, mouseY)) drawSlotHighlight(context, equipmentSlot.x, equipmentSlot.y, 0);
+ }
+
+ super.drawForeground(context, mouseX, mouseY);
+ }
+
+ @Override
+ protected void drawMouseoverTooltip(DrawContext context, int x, int y) {
+ super.drawMouseoverTooltip(context, x, y);
+ if (!handler.getCursorStack().isEmpty()) return;
+ for (Slot equipmentSlot : equipmentSlots) {
+ if (isPointWithinBounds(equipmentSlot.x, equipmentSlot.y, 16, 16, x, y) && equipmentSlot.hasStack()) {
+ ItemStack itemStack = equipmentSlot.getStack();
+ context.drawTooltip(this.textRenderer, this.getTooltipFromItem(itemStack), itemStack.getTooltipData(), x, y);
+ }
+ }
+ }
+
+ @Override
+ public void removed() {
+ super.removed();
+ // put the handler back how it was, the handler is the same while the player is alive/in the same world
+ Slot slot = handler.slots.get(45);
+ ((SlotAccessor) slot).setX(slot.x - 21);
+ }
+
+ @Override
+ protected void drawBackground(DrawContext context, float delta, int mouseX, int mouseY) {
+ super.drawBackground(context, delta, mouseX, mouseY);
+ for (int i = 0; i < 4; i++) {
+ context.drawGuiTexture(SLOT_TEXTURE, x + 76 + (i == 3 ? 21 : 0), y + 7 + i * 18, 18, 18);
+ }
+ }
+
+ @Override
+ protected void drawSlot(DrawContext context, Slot slot) {
+ super.drawSlot(context, slot);
+ if (slot instanceof EquipmentSlot && !slot.hasStack()) {
+ context.drawGuiTexture(EMPTY_SLOT, slot.x, slot.y, 16, 16);
+ }
+ }
+
+ private static class EquipmentSlot extends Slot {
+
+ public EquipmentSlot(Inventory inventory, int index, int x, int y) {
+ super(inventory, index, x, y);
+ }
+
+ @Override
+ public boolean canTakeItems(PlayerEntity playerEntity) {
+ return false;
+ }
+
+ @Override
+ public boolean canInsert(ItemStack stack) {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PetLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PetLevelAdder.java
index 88d48fbf..3049cd3f 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PetLevelAdder.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PetLevelAdder.java
@@ -2,11 +2,11 @@ package de.hysky.skyblocker.skyblock.item.slottext.adders;
import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
import de.hysky.skyblocker.skyblock.item.slottext.SlotTextAdder;
+import de.hysky.skyblocker.utils.ItemUtils;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.screen.slot.Slot;
import net.minecraft.text.Text;
-import net.minecraft.util.Formatting;
import org.apache.commons.lang3.math.NumberUtils;
import org.jetbrains.annotations.NotNull;
@@ -22,8 +22,8 @@ public class PetLevelAdder extends SlotTextAdder {
ItemStack itemStack = slot.getStack();
if (!itemStack.isOf(Items.PLAYER_HEAD)) return List.of();
String level = CatacombsLevelAdder.getBracketedLevelFromName(itemStack);
- if (!NumberUtils.isDigits(level)) return List.of();
- if ("100".equals(level) || "200".equals(level)) return List.of();
+ if (!NumberUtils.isDigits(level) || "100".equals(level) || "200".equals(level)) return List.of();
+ if (!ItemUtils.getItemId(itemStack).equals("PET")) return List.of();
return List.of(SlotText.topLeft(Text.literal(level).withColor(0xFFDDC1)));
}
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java
index 49d170b9..955ebc87 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java
@@ -2,6 +2,7 @@ package de.hysky.skyblocker.skyblock.item.tooltip;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.config.configs.GeneralConfig;
+import de.hysky.skyblocker.skyblock.item.tooltip.adders.CraftPriceTooltip;
import de.hysky.skyblocker.utils.Constants;
import de.hysky.skyblocker.utils.Utils;
import de.hysky.skyblocker.utils.scheduler.Scheduler;
@@ -129,6 +130,8 @@ public class ItemTooltip {
LOGGER.error("Encountered unknown error while downloading tooltip data", e);
return null;
});
+
+ CraftPriceTooltip.clearCache();
}, 1200, true);
}
} \ No newline at end of file
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java
index d82b2682..92adf49d 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipInfoType.java
@@ -1,29 +1,23 @@
package de.hysky.skyblocker.skyblock.item.tooltip;
-import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
-import com.google.gson.JsonPrimitive;
-import com.google.gson.stream.JsonReader;
import de.hysky.skyblocker.SkyblockerMod;
-import de.hysky.skyblocker.config.SkyblockerConfig;
import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.config.configs.GeneralConfig;
import de.hysky.skyblocker.utils.Http;
import de.hysky.skyblocker.utils.Utils;
+import org.jetbrains.annotations.Nullable;
-import java.io.StringReader;
import java.net.http.HttpHeaders;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Predicate;
-import org.jetbrains.annotations.Nullable;
-
public enum TooltipInfoType implements Runnable {
NPC("https://hysky.de/api/npcprice", itemTooltip -> itemTooltip.enableNPCPrice, true),
- BAZAAR("https://hysky.de/api/bazaar", itemTooltip -> itemTooltip.enableBazaarPrice || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.croesusProfit || SkyblockerConfigManager.get().uiAndVisuals.chestValue.enableChestValue, itemTooltip -> itemTooltip.enableBazaarPrice, false),
- LOWEST_BINS("https://hysky.de/api/auctions/lowestbins", itemTooltip -> itemTooltip.enableLowestBIN || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.croesusProfit || SkyblockerConfigManager.get().uiAndVisuals.chestValue.enableChestValue, itemTooltip -> itemTooltip.enableLowestBIN, false),
+ BAZAAR("https://hysky.de/api/bazaar", itemTooltip -> itemTooltip.enableBazaarPrice || itemTooltip.enableCraftingCost.getOrder() != null || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.croesusProfit || SkyblockerConfigManager.get().uiAndVisuals.chestValue.enableChestValue, itemTooltip -> itemTooltip.enableBazaarPrice, false),
+ LOWEST_BINS("https://hysky.de/api/auctions/lowestbins", itemTooltip -> itemTooltip.enableLowestBIN || itemTooltip.enableCraftingCost.getOrder() != null || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.enableProfitCalculator || SkyblockerConfigManager.get().dungeons.dungeonChestProfit.croesusProfit || SkyblockerConfigManager.get().uiAndVisuals.chestValue.enableChestValue, itemTooltip -> itemTooltip.enableLowestBIN, false),
ONE_DAY_AVERAGE("https://hysky.de/api/auctions/lowestbins/average/1day.json", itemTooltip -> itemTooltip.enableAvgBIN, false),
THREE_DAY_AVERAGE("https://hysky.de/api/auctions/lowestbins/average/3day.json", itemTooltip -> itemTooltip.enableAvgBIN || SkyblockerConfigManager.get().uiAndVisuals.searchOverlay.enableAuctionHouse, itemTooltip -> itemTooltip.enableAvgBIN, false),
MOTES("https://hysky.de/api/motesprice", itemTooltip -> itemTooltip.enableMotesPrice, itemTooltip -> itemTooltip.enableMotesPrice && Utils.isInTheRift(), true),
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java
index e3a2ef04..cb8efb0c 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/TooltipManager.java
@@ -3,6 +3,7 @@ package de.hysky.skyblocker.skyblock.item.tooltip;
import de.hysky.skyblocker.mixins.accessors.HandledScreenAccessor;
import de.hysky.skyblocker.skyblock.chocolatefactory.ChocolateFactorySolver;
import de.hysky.skyblocker.skyblock.item.tooltip.adders.*;
+import de.hysky.skyblocker.skyblock.item.tooltip.adders.CraftPriceTooltip;
import de.hysky.skyblocker.utils.Utils;
import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback;
import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
@@ -27,12 +28,13 @@ public class TooltipManager {
new BazaarPriceTooltip(2),
new LBinTooltip(3),
new AvgBinTooltip(4),
- new DungeonQualityTooltip(5),
- new MotesTooltip(6),
- new ObtainedDateTooltip(7),
- new MuseumTooltip(8),
- new ColorTooltip(9),
- new AccessoryTooltip(10),
+ new CraftPriceTooltip(5),
+ new DungeonQualityTooltip(6),
+ new MotesTooltip(7),
+ new ObtainedDateTooltip(8),
+ new MuseumTooltip(9),
+ new ColorTooltip(10),
+ new AccessoryTooltip(11),
};
private static final ArrayList<TooltipAdder> currentScreenAdders = new ArrayList<>();
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java
new file mode 100644
index 00000000..f7af446e
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java
@@ -0,0 +1,115 @@
+package de.hysky.skyblocker.skyblock.item.tooltip.adders;
+
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
+import de.hysky.skyblocker.config.configs.GeneralConfig;
+import de.hysky.skyblocker.skyblock.item.tooltip.ItemTooltip;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipAdder;
+import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType;
+import de.hysky.skyblocker.utils.NEURepoManager;
+import io.github.moulberry.repo.data.NEUIngredient;
+import io.github.moulberry.repo.data.NEUItem;
+import io.github.moulberry.repo.data.NEURecipe;
+import net.minecraft.item.ItemStack;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class CraftPriceTooltip extends TooltipAdder {
+ protected static final Logger LOGGER = LoggerFactory.getLogger(CraftPriceTooltip.class.getName());
+ private static final Map<String, Double> cachedCraftCosts = new ConcurrentHashMap<>();
+ private static final int MAX_RECURSION_DEPTH = 15;
+
+ public CraftPriceTooltip(int priority) {
+ super(priority);
+ }
+
+ @Override
+ public void addToTooltip(@Nullable Slot focusedSloFt, ItemStack stack, List<Text> lines) {
+ if (SkyblockerConfigManager.get().general.itemTooltip.enableCraftingCost == GeneralConfig.Craft.OFF) return;
+
+ String internalID = stack.getSkyblockId();
+ if (stack.getNeuName() == null || internalID == null) return;
+
+ if (TooltipInfoType.LOWEST_BINS.getData() == null || TooltipInfoType.BAZAAR.getData() == null) {
+ ItemTooltip.nullWarning();
+ return;
+ }
+
+ NEUItem neuItem = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(internalID);
+ if (neuItem == null) return;
+
+ List<NEURecipe> neuRecipes = neuItem.getRecipes();
+ if (neuRecipes.isEmpty() || neuRecipes.getFirst() instanceof io.github.moulberry.repo.data.NEUKatUpgradeRecipe) return;
+
+ try {
+ double totalCraftCost = getItemCost(neuRecipes.getFirst(), 0);
+
+ if (totalCraftCost == 0) return;
+
+ int amountInStack;
+ if (lines.get(1).getString().endsWith("Sack")) {
+ String line = lines.get(3).getSiblings().get(1).getString().replace(",", "");
+ amountInStack = NumberUtils.isParsable(line) && !line.equals("0") ? Integer.parseInt(line) : stack.getCount();
+ } else amountInStack = stack.getCount();
+
+ neuRecipes.getFirst().getAllOutputs().stream().findFirst().ifPresent(outputIngredient ->
+ lines.add(Text.literal(String.format("%-20s", "Crafting Price:")).formatted(Formatting.GOLD)
+ .append(ItemTooltip.getCoinsMessage(totalCraftCost / outputIngredient.getAmount(), amountInStack))));
+
+ } catch (Exception e) {
+ LOGGER.error("[Skyblocker Craft Price] Error calculating craftprice tooltip for: " + internalID, e);
+ }
+ }
+
+ private double getItemCost(NEURecipe recipe, int depth) {
+ if (depth >= MAX_RECURSION_DEPTH) return -1;
+
+ double totalCraftCost = 0;
+ for (NEUIngredient input : recipe.getAllInputs()) {
+ String inputItemName = input.getItemId();
+ double inputItemCount = input.getAmount();
+ if (cachedCraftCosts.containsKey(inputItemName)) {
+ totalCraftCost += cachedCraftCosts.get(inputItemName) * inputItemCount;
+ continue;
+ }
+
+ double itemCost = 0;
+
+ if (TooltipInfoType.BAZAAR.getData().has(inputItemName)) {
+ itemCost = TooltipInfoType.BAZAAR.getData().getAsJsonObject(inputItemName).get(SkyblockerConfigManager.get().general.itemTooltip.enableCraftingCost.getOrder()).getAsDouble();
+ } else if (TooltipInfoType.LOWEST_BINS.getData().has(inputItemName)) {
+ itemCost = TooltipInfoType.LOWEST_BINS.getData().get(inputItemName).getAsDouble();
+ }
+
+ if (itemCost > 0) {
+ cachedCraftCosts.put(inputItemName, itemCost);
+ }
+
+ NEUItem neuItem = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(inputItemName);
+ if (neuItem != null) {
+ List<NEURecipe> neuRecipes = neuItem.getRecipes();
+ if (!neuRecipes.isEmpty()) {
+ double craftCost = getItemCost(neuRecipes.getFirst(), depth + 1);
+ if (craftCost != -1) itemCost = Math.min(itemCost, craftCost);
+ cachedCraftCosts.put(inputItemName, itemCost);
+ }
+ }
+
+ totalCraftCost += itemCost * inputItemCount;
+ }
+ return totalCraftCost;
+ }
+
+ public static void clearCache() {
+ cachedCraftCosts.clear();
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java
index 672201d5..d556c9b2 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/NpcPriceTooltip.java
@@ -7,6 +7,7 @@ import net.minecraft.item.ItemStack;
import net.minecraft.screen.slot.Slot;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
+import org.apache.commons.lang3.math.NumberUtils;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@@ -20,9 +21,18 @@ public class NpcPriceTooltip extends TooltipAdder {
public void addToTooltip(@Nullable Slot focusedSlot, ItemStack stack, List<Text> lines) {
final String internalID = stack.getSkyblockId();
if (internalID != null && TooltipInfoType.NPC.isTooltipEnabledAndHasOrNullWarning(internalID)) {
+ int amount;
+ if (lines.get(1).getString().endsWith("Sack")) {
+ //The amount is in the 2nd sibling of the 3rd line of the lore. here V
+ //Example line: empty[style={color=dark_purple,!italic}, siblings=[literal{Stored: }[style={color=gray}], literal{0}[style={color=dark_gray}], literal{/20k}[style={color=gray}]]
+ String line = lines.get(3).getSiblings().get(1).getString().replace(",", "");
+ amount = NumberUtils.isParsable(line) && !line.equals("0") ? Integer.parseInt(line) : stack.getCount();
+ } else {
+ amount = stack.getCount();
+ }
lines.add(Text.literal(String.format("%-21s", "NPC Sell Price:"))
.formatted(Formatting.YELLOW)
- .append(ItemTooltip.getCoinsMessage(TooltipInfoType.NPC.getData().get(internalID).getAsDouble(), stack.getCount())));
+ .append(ItemTooltip.getCoinsMessage(TooltipInfoType.NPC.getData().get(internalID).getAsDouble(), amount)));
}
}
}
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..306fe279 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,16 @@ 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;
+ 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..f3045c11 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
@@ -12,10 +12,7 @@ import de.hysky.skyblocker.utils.ItemUtils;
import de.hysky.skyblocker.utils.NEURepoManager;
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;
@@ -44,9 +41,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());
@@ -114,6 +111,8 @@ public class ItemLoader {
// Set Count
stack.setCount(containerContent.getCompound(i).getInt("Count"));
+ 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/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/OrderedWaypoints.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java
index f8930882..11ec1b8d 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/OrderedWaypoints.java
@@ -1,28 +1,5 @@
package de.hysky.skyblocker.skyblock.waypoint;
-import static com.mojang.brigadier.arguments.StringArgumentType.getString;
-import static com.mojang.brigadier.arguments.StringArgumentType.word;
-import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument;
-import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.nio.file.Files;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.Path;
-import java.util.Base64;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Semaphore;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.GZIPOutputStream;
-
-import org.slf4j.Logger;
-
import com.google.common.primitives.Floats;
import com.google.gson.Gson;
import com.google.gson.JsonParser;
@@ -33,8 +10,8 @@ import com.mojang.logging.LogUtils;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import com.mojang.serialization.codecs.RecordCodecBuilder;
-
import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.config.SkyblockerConfigManager;
import de.hysky.skyblocker.skyblock.item.CustomArmorDyeColors;
import de.hysky.skyblocker.utils.Constants;
import de.hysky.skyblocker.utils.Utils;
@@ -59,6 +36,28 @@ import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
+import org.slf4j.Logger;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.util.Base64;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Semaphore;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+import static com.mojang.brigadier.arguments.StringArgumentType.getString;
+import static com.mojang.brigadier.arguments.StringArgumentType.word;
+import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument;
+import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
public class OrderedWaypoints {
private static final Logger LOGGER = LogUtils.getLogger();
@@ -400,7 +399,7 @@ public class OrderedWaypoints {
private int waypointIndex;
OrderedWaypoint(BlockPos pos, float[] colorComponents) {
- super(pos, Type.WAYPOINT, colorComponents);
+ super(pos, () -> SkyblockerConfigManager.get().uiAndVisuals.waypoints.waypointType, colorComponents);
}
private BlockPos getPos() {
diff --git a/src/main/java/de/hysky/skyblocker/utils/Http.java b/src/main/java/de/hysky/skyblocker/utils/Http.java
index 1adf75d3..051bd52e 100644
--- a/src/main/java/de/hysky/skyblocker/utils/Http.java
+++ b/src/main/java/de/hysky/skyblocker/utils/Http.java
@@ -33,7 +33,7 @@ public class Http {
.followRedirects(Redirect.NORMAL)
.build();
- private static ApiResponse sendCacheableGetRequest(String url, @Nullable String token) throws IOException, InterruptedException {
+ public static ApiResponse sendCacheableGetRequest(String url, @Nullable String token) throws IOException, InterruptedException {
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.GET()
.header("Accept", "application/json")
@@ -66,9 +66,8 @@ public class Http {
.build();
HttpResponse<InputStream> response = HTTP_CLIENT.send(request, BodyHandlers.ofInputStream());
- InputStream decodedInputStream = getDecodedInputStream(response);
- return decodedInputStream;
+ return getDecodedInputStream(response);
}
public static String sendGetRequest(String url) throws IOException, InterruptedException {
@@ -125,16 +124,12 @@ public class Http {
String encoding = getContentEncoding(response.headers());
try {
- switch (encoding) {
- case "":
- return response.body();
- case "gzip":
- return new GZIPInputStream(response.body());
- case "deflate":
- return new InflaterInputStream(response.body());
- default:
- throw new UnsupportedOperationException("The server sent content in an unexpected encoding: " + encoding);
- }
+ return switch (encoding) {
+ case "" -> response.body();
+ case "gzip" -> new GZIPInputStream(response.body());
+ case "deflate" -> new InflaterInputStream(response.body());
+ default -> throw new UnsupportedOperationException("The server sent content in an unexpected encoding: " + encoding);
+ };
} catch (IOException e) {
throw new UncheckedIOException(e);
}
diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java
index de7a0f9e..e43ce783 100644
--- a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java
+++ b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java
@@ -10,9 +10,9 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import com.mojang.serialization.codecs.RecordCodecBuilder;
-import de.hysky.skyblocker.skyblock.item.tooltip.adders.ObtainedDateTooltip;
import de.hysky.skyblocker.SkyblockerMod;
import de.hysky.skyblocker.skyblock.item.tooltip.TooltipInfoType;
+import de.hysky.skyblocker.skyblock.item.tooltip.adders.ObtainedDateTooltip;
import it.unimi.dsi.fastutil.doubles.DoubleBooleanPair;
import it.unimi.dsi.fastutil.ints.IntIntPair;
import it.unimi.dsi.fastutil.longs.LongBooleanPair;
@@ -62,8 +62,13 @@ public class ItemUtils {
});
}
+ /**
+ * Gets the nbt in the custom data component of the item stack.
+ * @return The {@link DataComponentTypes#CUSTOM_DATA custom data} of the itemstack,
+ * or an empty {@link NbtCompound} if the itemstack is missing a custom data component
+ */
@SuppressWarnings("deprecation")
- public static NbtCompound getCustomData(@NotNull ComponentHolder stack) {
+ public static @NotNull NbtCompound getCustomData(@NotNull ComponentHolder stack) {
return stack.getOrDefault(DataComponentTypes.CUSTOM_DATA, NbtComponent.DEFAULT).getNbt();
}
@@ -73,7 +78,7 @@ public class ItemUtils {
* @param stack the item stack to get the internal name from
* @return an optional containing the internal name of the item stack
*/
- public static Optional<String> getItemIdOptional(@NotNull ItemStack stack) {
+ public static @NotNull Optional<String> getItemIdOptional(@NotNull ItemStack stack) {
NbtCompound customData = getCustomData(stack);
return customData.contains(ID) ? Optional.of(customData.getString(ID)) : Optional.empty();
}
@@ -84,7 +89,7 @@ public class ItemUtils {
* @param stack the item stack to get the internal name from
* @return the internal name of the item stack, or an empty string if the item stack is null or does not have an internal name
*/
- public static String getItemId(@NotNull ItemStack stack) {
+ public static @NotNull String getItemId(@NotNull ItemStack stack) {
return getCustomData(stack).getString(ID);
}
@@ -94,7 +99,7 @@ public class ItemUtils {
* @param stack the item stack to get the UUID from
* @return an optional containing the UUID of the item stack
*/
- public static Optional<String> getItemUuidOptional(@NotNull ItemStack stack) {
+ public static @NotNull Optional<String> getItemUuidOptional(@NotNull ItemStack stack) {
NbtCompound customData = getCustomData(stack);
return customData.contains(UUID) ? Optional.of(customData.getString(UUID)) : Optional.empty();
}
@@ -105,7 +110,7 @@ public class ItemUtils {
* @param stack the item stack to get the UUID from
* @return the UUID of the item stack, or an empty string if the item stack is null or does not have a UUID
*/
- public static String getItemUuid(@NotNull ComponentHolder stack) {
+ public static @NotNull String getItemUuid(@NotNull ComponentHolder stack) {
return getCustomData(stack).getString(UUID);
}
@@ -115,7 +120,7 @@ public class ItemUtils {
* @return An {@link LongBooleanPair} with the {@code left long} representing the item's price,
* and the {@code right boolean} indicating if the price was based on complete data.
*/
- public static DoubleBooleanPair getItemPrice(@NotNull ItemStack stack) {
+ public static @NotNull DoubleBooleanPair getItemPrice(@NotNull ItemStack stack) {
return getItemPrice(getItemId(stack));
}
@@ -125,7 +130,7 @@ public class ItemUtils {
* @return An {@link LongBooleanPair} with the {@code left long} representing the item's price,
* and the {@code right boolean} indicating if the price was based on complete data.
*/
- public static DoubleBooleanPair getItemPrice(@Nullable String id) {
+ public static @NotNull DoubleBooleanPair getItemPrice(@Nullable String id) {
JsonObject bazaarPrices = TooltipInfoType.BAZAAR.getData();
JsonObject lowestBinPrices = TooltipInfoType.LOWEST_BINS.getData();
@@ -168,15 +173,15 @@ public class ItemUtils {
public static boolean hasCustomDurability(@NotNull ItemStack stack) {
NbtCompound customData = getCustomData(stack);
- return customData != null && (customData.contains("drill_fuel") || customData.getString(ID).equals("PICKONIMBUS"));
+ return !customData.isEmpty() && (customData.contains("drill_fuel") || customData.getString(ID).equals("PICKONIMBUS"));
}
@Nullable
public static IntIntPair getDurability(@NotNull ItemStack stack) {
NbtCompound customData = getCustomData(stack);
- if (customData == null) return null;
+ if (customData.isEmpty()) return null;
- // TODO Calculate drill durability based on the drill_fuel flag, fuel_tank flag, and hotm level
+ // TODO Calculate drill durability based on the drill_fuel flag, fuel_tank flag, and hotm level
// TODO Cache the max durability and only update the current durability on inventory tick
int pickonimbusDurability = customData.getInt("pickonimbus_durability");
@@ -218,15 +223,15 @@ public class ItemUtils {
return null;
}
- public static List<Text> getLore(ItemStack item) {
+ public static @NotNull List<Text> getLore(ItemStack item) {
return item.getOrDefault(DataComponentTypes.LORE, LoreComponent.DEFAULT).styledLines();
}
- public static PropertyMap propertyMapWithTexture(String textureValue) {
+ public static @NotNull PropertyMap propertyMapWithTexture(String textureValue) {
return Codecs.GAME_PROFILE_PROPERTY_MAP.parse(JsonOps.INSTANCE, JsonParser.parseString("[{\"name\":\"textures\",\"value\":\"" + textureValue + "\"}]")).getOrThrow();
}
- public static String getHeadTexture(ItemStack stack) {
+ public static @NotNull String getHeadTexture(@NotNull ItemStack stack) {
if (!stack.isOf(Items.PLAYER_HEAD) || !stack.contains(DataComponentTypes.PROFILE)) return "";
ProfileComponent profile = stack.get(DataComponentTypes.PROFILE);
@@ -238,13 +243,13 @@ public class ItemUtils {
.orElse("");
}
- public static Optional<String> getHeadTextureOptional(ItemStack stack) {
+ public static @NotNull Optional<String> getHeadTextureOptional(ItemStack stack) {
String texture = getHeadTexture(stack);
if (texture.isBlank()) return Optional.empty();
return Optional.of(texture);
}
- public static ItemStack getSkyblockerStack() {
+ public static @NotNull ItemStack getSkyblockerStack() {
try {
ItemStack stack = new ItemStack(Items.PLAYER_HEAD);
stack.set(DataComponentTypes.PROFILE, new ProfileComponent(Optional.of("SkyblockerStack"), Optional.of(java.util.UUID.randomUUID()), propertyMapWithTexture("e3RleHR1cmVzOntTS0lOOnt1cmw6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDdjYzY2ODc0MjNkMDU3MGQ1NTZhYzUzZTA2NzZjYjU2M2JiZGQ5NzE3Y2Q4MjY5YmRlYmVkNmY2ZDRlN2JmOCJ9fX0=")));
diff --git a/src/main/java/de/hysky/skyblocker/utils/SkyblockTime.java b/src/main/java/de/hysky/skyblocker/utils/SkyblockTime.java
index 045ecc4e..ec3effaf 100644
--- a/src/main/java/de/hysky/skyblocker/utils/SkyblockTime.java
+++ b/src/main/java/de/hysky/skyblocker/utils/SkyblockTime.java
@@ -21,10 +21,10 @@ public class SkyblockTime {
public static void init() {
updateTime();
//ScheduleCyclic already runs the task upon scheduling, so there's no need to call updateTime() here
- Scheduler.INSTANCE.schedule(() -> Scheduler.INSTANCE.scheduleCyclic(SkyblockTime::updateTime, 1200 * 24), (int) (1200000 - (getSkyblockMillis() % 1200000)) / 50);
+ Scheduler.INSTANCE.schedule(() -> Scheduler.INSTANCE.scheduleCyclic(SkyblockTime::updateTime, 1200 * 20), (int) (1200000 - (getSkyblockMillis() % 1200000)) / 50);
}
- private static long getSkyblockMillis() {
+ public static long getSkyblockMillis() {
return System.currentTimeMillis() - SKYBLOCK_EPOCH;
}
diff --git a/src/main/java/de/hysky/skyblocker/utils/Utils.java b/src/main/java/de/hysky/skyblocker/utils/Utils.java
index dad7ec02..051110b2 100644
--- a/src/main/java/de/hysky/skyblocker/utils/Utils.java
+++ b/src/main/java/de/hysky/skyblocker/utils/Utils.java
@@ -3,7 +3,6 @@ package de.hysky.skyblocker.utils;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
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;
@@ -22,11 +21,11 @@ import net.minecraft.scoreboard.*;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
+import org.apache.http.client.HttpResponseException;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.IOException;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
@@ -77,7 +76,8 @@ public class Utils {
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 = "";
/**
@@ -194,11 +194,16 @@ public class Utils {
}
public static void init() {
- SkyblockEvents.JOIN.register(() -> tickMayorCache(false));
+ SkyblockEvents.JOIN.register(() -> {
+ if (!mayorTickScheduled) {
+ tickMayorCache();
+ scheduleMayorTick();
+ 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
- Scheduler.INSTANCE.scheduleCyclic(() -> tickMayorCache(true), 24_000, true); // Update every 20 minutes
}
/**
@@ -361,8 +366,8 @@ public class Utils {
// TODO: Combine with `ChocolateFactorySolver.formatTime` and move into `SkyblockTime`.
public static Text getDurationText(int timeInSeconds) {
int seconds = timeInSeconds % 60;
- int minutes = (timeInSeconds/60) % 60;
- int hours = (timeInSeconds/3600);
+ int minutes = (timeInSeconds / 60) % 60;
+ int hours = (timeInSeconds / 3600);
MutableText time = Text.empty();
if (hours > 0) {
@@ -463,7 +468,11 @@ public class Utils {
if (message.startsWith(PROFILE_MESSAGE_PREFIX)) {
profile = message.substring(PROFILE_MESSAGE_PREFIX.length()).split("§b")[0];
} else if (message.startsWith(PROFILE_ID_PREFIX)) {
+ String prevProfileId = profileId;
profileId = message.substring(PROFILE_ID_PREFIX.length());
+ if (!prevProfileId.equals(profileId)) {
+ SkyblockEvents.PROFILE_CHANGE.invoker().onSkyblockProfileChange(prevProfileId, profileId);
+ }
MuseumItemCache.tick(profileId);
}
@@ -482,27 +491,47 @@ public class Utils {
location = Location.UNKNOWN;
}
- private static void tickMayorCache(boolean refresh) {
- if (!mayor.isEmpty() && !refresh) return;
+ 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
+ long millisUntilNextMayorChange = currentYearMillis > 105600000L ? 446400000L - currentYearMillis + 105600000L : 105600000L - currentYearMillis;
+ Scheduler.INSTANCE.schedule(Utils::tickMayorCache, (int) (millisUntilNextMayorChange / 50) + 5 * 60 * 20); // 5 extra minutes to allow the cache to expire. This is a simpler than checking age and subtracting from max age and rescheduling again.
+ }
+ private static void tickMayorCache() {
CompletableFuture.supplyAsync(() -> {
try {
- JsonObject json = JsonParser.parseString(Http.sendGetRequest("https://api.hypixel.net/v2/resources/skyblock/election")).getAsJsonObject();
- if (json.get("success").getAsBoolean()) return json.get("mayor").getAsJsonObject().get("name").getAsString();
- throw new IOException(json.get("cause").getAsString());
+ Http.ApiResponse response = Http.sendCacheableGetRequest("https://api.hypixel.net/v2/resources/skyblock/election", null); //Authentication is not required for this endpoint
+ if (!response.ok()) throw new HttpResponseException(response.statusCode(), response.content());
+ JsonObject json = JsonParser.parseString(response.content()).getAsJsonObject();
+ if (!json.get("success").getAsBoolean()) throw new RuntimeException("Request failed!"); //Can't find a more appropriate exception to throw here.
+ return json.get("mayor").getAsJsonObject().get("name").getAsString();
} catch (Exception e) {
- LOGGER.error("[Skyblocker] Failed to get mayor status!", e);
+ throw new RuntimeException(e); //Wrap the exception to be handled by the exceptionally block
+ }
+ }).exceptionally(throwable -> {
+ LOGGER.error("[Skyblocker] Failed to get mayor status!", throwable.getCause());
+ if (mayorTickRetryAttempts < 5) {
+ int minutes = 5 * 1 << mayorTickRetryAttempts; //5, 10, 20, 40, 80 minutes
+ mayorTickRetryAttempts++;
+ LOGGER.warn("[Skyblocker] Retrying in {} minutes.", minutes);
+ Scheduler.INSTANCE.schedule(Utils::tickMayorCache, minutes * 60 * 20);
+ } else {
+ LOGGER.warn("[Skyblocker] Failed to get mayor status after 5 retries! Stopping further retries until next reboot.");
+ }
+ return ""; //Have to return a value for the thenAccept block.
+ }).thenAccept(result -> {
+ if (!result.isEmpty()) {
+ mayor = result;
+ LOGGER.info("[Skyblocker] Mayor set to {}.", mayor);
+ scheduleMayorTick(); //Ends up as a cyclic task with finer control over scheduled time
}
- return "";
- }).thenAccept(s -> {
- if (!s.isEmpty()) mayor = s;
});
-
}
/**
* Used to avoid triggering things like chat rules or chat listeners infinitely, do not use otherwise.
- *
+ * <p>
* Bypasses MessageHandler#onGameMessage
*/
public static void sendMessageToBypassEvents(Text message) {
diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json
index cf1cc4c2..bf154048 100644
--- a/src/main/resources/assets/skyblocker/lang/en_us.json
+++ b/src/main/resources/assets/skyblocker/lang/en_us.json
@@ -239,6 +239,11 @@
"skyblocker.config.general.itemTooltip.enableAccessoriesHelper.@Tooltip[5]": "You don't own any accessory from this family.",
"skyblocker.config.general.itemTooltip.enableAvgBIN": "Enable Avg. BIN Price",
"skyblocker.config.general.itemTooltip.enableBazaarPrice": "Enable Bazaar buy/sell Price",
+ "skyblocker.config.general.itemTooltip.craft": "Crafting Cost",
+ "skyblocker.config.general.itemTooltip.craft.@Tooltip": "You can choose which Bazaar order type to use in crafting calculation",
+ "skyblocker.config.general.itemTooltip.craft.OFF": "Off",
+ "skyblocker.config.general.itemTooltip.craft.SELL_ORDER": "Sell Order",
+ "skyblocker.config.general.itemTooltip.craft.BUY_ORDER": "Buy Order",
"skyblocker.config.general.itemTooltip.enableExoticTooltip": "Enable Exotic Tooltip",
"skyblocker.config.general.itemTooltip.enableExoticTooltip.@Tooltip": "Displays the type of exotic below the item's name if an armor piece is exotic.",
"skyblocker.config.general.itemTooltip.enableLowestBIN": "Enable Lowest BIN Price",
@@ -289,6 +294,8 @@
"skyblocker.config.helpers.chocolateFactory.enableTimeTowerReminder.@Tooltip": "Sends a message in chat when your Time Tower deactivates.",
"skyblocker.config.helpers.chocolateFactory.sendEggFoundMessages": "Send Egg Found Messages",
"skyblocker.config.helpers.chocolateFactory.sendEggFoundMessages.@Tooltip": "Sends a message in chat when an egg is found in the current island.",
+ "skyblocker.config.helpers.chocolateFactory.straySound": "Stray Rabbit Sound",
+ "skyblocker.config.helpers.chocolateFactory.straySound.@Tooltip": "Repeatedly plays a ding while a stray rabbit is present. If it is golden, the sound will be more frequent and different.",
"skyblocker.config.helpers.chocolateFactory.waypointType": "Egg Waypoint Type",
"skyblocker.config.helpers.chocolateFactory.waypointType.@Tooltip": "Waypoint: Displays a highlight and a beacon beam.\n\nOutlined Waypoint: Displays both a waypoint and an outline.\n\nHighlight: Only displays a highlight.\n\nOutlined Highlight: Displays both a highlight and an outline.\n\nOutline: Only displays an outline.",
@@ -615,6 +622,8 @@
"skyblocker.config.uiAndVisuals.searchOverlay.maxSuggestions": "Maximum Suggestions",
"skyblocker.config.uiAndVisuals.searchOverlay.maxSuggestions.@Tooltip": "The maximum number of suggested items to show.",
+ "skyblocker.config.uiAndVisuals.showEquipmentInInventory": "Show Equipment in Inventory",
+
"skyblocker.config.uiAndVisuals.tabHud": "Fancy tab HUD (Temporarily disabled outside dungeons)",
"skyblocker.config.uiAndVisuals.tabHud.enableHudBackground": "Enable HUD Background",
"skyblocker.config.uiAndVisuals.tabHud.enableHudBackground.@Tooltip": "Enables the background of the non-tab HUD.",
@@ -936,5 +945,17 @@
"skyblocker.waypoints.ordered.import.coleWeight.success": "Successfully imported waypoints from the Cole Weight format.",
"skyblocker.waypoints.ordered.import.coleWeight.fail": "§cFailed to import waypoints from the Cole Weight format. Make sure to have the waypoint data copied to your clipboard!",
+ "skyblocker.profileviewer.inventory.inventory": "Inventory",
+ "skyblocker.profileviewer.inventory.armor": "Armor",
+ "skyblocker.profileviewer.inventory.equipment": "Equipment",
+ "skyblocker.profileviewer.inventory.enderchest": "Enderchest",
+ "skyblocker.profileviewer.inventory.backpack": "Backpack",
+ "skyblocker.profileviewer.inventory.wardrobe": "Wardrobe",
+ "skyblocker.profileviewer.inventory.pets": "Pets",
+ "skyblocker.profileviewer.inventory.accessoryBag": "Accessory Bag",
+ "skyblocker.profileviewer.inventory.inactive": "Locked Slot",
+ "skyblocker.profileviewer.inventory.inactive.description.backpack": "The selected backpack",
+ "skyblocker.profileviewer.inventory.inactive.description.general": "does not contain this slot",
+
"emi.category.skyblocker.skyblock": "Skyblock"
}
diff --git a/src/main/resources/assets/skyblocker/textures/gui/sprites/equipment/empty_icon.png b/src/main/resources/assets/skyblocker/textures/gui/sprites/equipment/empty_icon.png
new file mode 100644
index 00000000..be89af40
--- /dev/null
+++ b/src/main/resources/assets/skyblocker/textures/gui/sprites/equipment/empty_icon.png
Binary files differ