diff options
19 files changed, 664 insertions, 89 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..eb1c4b81 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,54 @@ +name: Bug Report +description: Create a report to help us improve +labels: [bug] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + + Before continuing to make the issue, please make sure there are no similar issues on [the issue tracker](https://github.com/SkyblockerMod/Skyblocker/issues). If there are, consider contributing your information in there instead! + - type: textarea + id: what-happened + attributes: + label: What happened? + description: 'Also tell us, what did you expect to happen? Please provide some reproduction steps: What did you do to trigger the bug?' + placeholder: A bug happened! + validations: + required: true + - type: textarea + id: screenshots + attributes: + label: Screenshots + description: If applicable, add screenshots to help explain your problem. + placeholder: You can add images by clicking on the button in the bar that will appear once you click this textbox. + validations: + required: false + - type: textarea + id: logs + attributes: + label: Log output + description: 'Please upload a client log file by dragging and dropping it into this section. Usually this is found at `.minecraft/logs/latest.log`. If you are not sure how to find `.minecraft`, please see this article: https://minecraft.fandom.com/wiki/.minecraft#Locating_.minecraft. Please do not upload a compressed (`.log.gz`) file, they are very difficult for us to read when viewing issue reports. The log file instantly tells us important information like the versions of any other installed mods or if there are errors so it is very very important that you include it in your report.' + - type: input + id: minecraft-version + attributes: + label: Minecraft Version + description: What version of Minecraft are you running? If you do not know what version you are using, look in the bottom left corner of the main menu in game. + placeholder: ex. Minecraft 1.20.1 + validations: + required: true + - type: input + id: skyblocker-version + attributes: + label: Skyblocker Version + description: What version of Skyblocker are you running? Every part is important! If you do not know what version you are using, look at the file name in your "mods" folder. + placeholder: ex. skyblocker-1.12.0+1.20.1.jar + validations: + required: true + - type: textarea + id: additional-context + attributes: + label: Additional context + description: 'Add any other context about the problem here. If you are proficient at reading log files and think there is an especially relevant section, feel free to paste it in a code block here - that is not required, though.' + validations: + required: false
\ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..bd55b806 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Join the Skyblocker Discord server! + url: https://discord.com/invite/aNNJHQykck + about: Get support on our Discord Server
\ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/crash_report.yml b/.github/ISSUE_TEMPLATE/crash_report.yml new file mode 100644 index 00000000..825ddb3d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/crash_report.yml @@ -0,0 +1,79 @@ +name: Crash Report +description: Create a report to inform us of a constant crash +labels: [crash] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this crash report! + + Before continuing to make the issue, please make sure there are no similar issues on [the issue tracker](https://github.com/SkyblockerMod/Skyblocker/issues). If there are, consider contributing your information in there instead! + + Also, make sure you are using the latest version of the mod! If not, try updating to see if it resolves your issue. + - type: input + id: minecraft-version + attributes: + label: Minecraft Version + description: What version of Minecraft are you running? If you do not know what version you are using, look in the bottom left corner of the main menu in game. + placeholder: ex. Minecraft 1.20.1 + validations: + required: true + - type: input + id: skyblocker-version + attributes: + label: Skyblocker Version + description: What version of Skyblocker are you running? Every part is important! If you do not know what version you are using, look at the file name in your "mods" folder. + placeholder: ex. skyblocker-1.12.0+1.20.1.jar + validations: + required: true + - type: textarea + id: reproduction-steps + attributes: + label: Reproduction Steps + description: Provide information on how to reproduce this game crash. You can either fill this section in like the example below or do something else -- just make sure your instructions are minimal and clear, as other people will need to be able to replicate your issue. + placeholder: | + 1. Place a Redstone Lamp in front of a Redstone Repeater + 2. Use a Lever to activate the Redstone Repeater + 3. Game Crashes + validations: + required: true + - type: textarea + id: logs + attributes: + label: Crash Report file and latest.log + description: | + Upload your crash report file and latest.log as an attachment to this issue (drag-and-drop) or to a service such as GitHub Gist (paste a link). This information is critical in resolving your issue! + + Tip: Messages like "Exit code 0" from your launcher are not what you're looking for. If your launcher does not provide a button to view the most recent crash report, check your game's "crash-reports" folder for the most recent crash report file. + + This will be automatically formatted into code, so no need for backticks. + placeholder: | + ---- Minecraft Crash Report ---- + // Don't be sad, have a hug! <3 + + Time: 2023-08-16 11:56:19 + Description: Unexpected error + + java.lang.NullPointerException: Cannot invoke "java.util.HashMap.get(Object)" because the return value of "java.util.HashMap.get(Object)" is null + at me.xmrvizzy.skyblocker.skyblock.dungeon.secrets.Room.<init>(Room.java:80) + at me.xmrvizzy.skyblocker.skyblock.dungeon.secrets.DungeonSecrets.newRoom(DungeonSecrets.java:297) + at me.xmrvizzy.skyblocker.skyblock.dungeon.secrets.DungeonSecrets.update(DungeonSecrets.java:263) + at me.xmrvizzy.skyblocker.utils.Scheduler$CyclicTask.run(Scheduler.java:102) + at me.xmrvizzy.skyblocker.utils.Scheduler$ScheduledTask.run(Scheduler.java:120) + at me.xmrvizzy.skyblocker.utils.Scheduler.runTask(Scheduler.java:88) + at me.xmrvizzy.skyblocker.utils.Scheduler.tick(Scheduler.java:76) + at me.xmrvizzy.skyblocker.SkyblockerMod.tick(SkyblockerMod.java:116) + at net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents.lambda$static$2(ClientTickEvents.java:43) + at net.minecraft.class_310.handler$ble000$fabric-lifecycle-events-v1$onEndTick(class_310.java:11022) + at net.minecraft.class_310.method_1574(class_310.java:1957) + at net.minecraft.class_310.method_1523(class_310.java:1181) + at net.minecraft.class_310.method_1514(class_310.java:802) + at net.minecraft.client.main.Main.main(Main.java:250) + render: shell + - type: textarea + id: additional-context + attributes: + label: Additional context + description: Provide any additional information or context which may be relevant to the issue. This is optional + validations: + required: false
\ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000..cb42e124 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,21 @@ +name: Feature Request +description: Ask for new feature +labels: [enhancement] +body: + - type: markdown + attributes: + value: | + Please search for existing issues before submitting a new one. + - type: textarea + id: feature-description + attributes: + label: Describe the new feature. + description: Provide as much details as possible. + validations: + required: true + - type: textarea + id: info + attributes: + label: Images + description: Upload any images to help describe your feature request here. + placeholder: You can add images by clicking on the button in the bar that will appear once you click this textbox.
\ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 8db62203..f47fe78d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,5 @@ org.gradle.jvmargs=-Xmx1G -Dfile.encoding=UTF-8 -Duser.language=en -Duser.country=US +org.gradle.parallel=true # Fabric Properties (https://fabricmc.net/versions.html) ## 1.20 diff --git a/src/main/java/me/xmrvizzy/skyblocker/SkyblockerMod.java b/src/main/java/me/xmrvizzy/skyblocker/SkyblockerMod.java index fe05e793..c9705a3b 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/SkyblockerMod.java +++ b/src/main/java/me/xmrvizzy/skyblocker/SkyblockerMod.java @@ -10,6 +10,7 @@ import me.xmrvizzy.skyblocker.skyblock.*; import me.xmrvizzy.skyblocker.skyblock.dungeon.DungeonBlaze; import me.xmrvizzy.skyblocker.skyblock.dungeon.DungeonMap; import me.xmrvizzy.skyblocker.skyblock.dungeon.LividColor; +import me.xmrvizzy.skyblocker.skyblock.dungeon.TicTacToe; import me.xmrvizzy.skyblocker.skyblock.dwarven.DwarvenHud; import me.xmrvizzy.skyblocker.skyblock.item.CustomArmorDyeColors; import me.xmrvizzy.skyblocker.skyblock.item.CustomArmorTrims; @@ -96,10 +97,12 @@ public class SkyblockerMod implements ClientModInitializer { CustomItemNames.init(); CustomArmorDyeColors.init(); CustomArmorTrims.init(); + TicTacToe.init(); containerSolverManager.init(); scheduler.scheduleCyclic(Utils::update, 20); scheduler.scheduleCyclic(DiscordRPCManager::updateDataAndPresence, 100); scheduler.scheduleCyclic(DungeonBlaze::update, 4); + scheduler.scheduleCyclic(TicTacToe::tick, 4); scheduler.scheduleCyclic(LividColor::update, 10); scheduler.scheduleCyclic(BackpackPreview::tick, 50); scheduler.scheduleCyclic(DwarvenHud::update, 40); diff --git a/src/main/java/me/xmrvizzy/skyblocker/config/SkyblockerConfig.java b/src/main/java/me/xmrvizzy/skyblocker/config/SkyblockerConfig.java index 104c9c7a..e6961819 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/config/SkyblockerConfig.java +++ b/src/main/java/me/xmrvizzy/skyblocker/config/SkyblockerConfig.java @@ -79,7 +79,7 @@ public class SkyblockerConfig implements ConfigData { @ConfigEntry.Category("button6") @ConfigEntry.Gui.CollapsibleObject() - public QuickNavItem button6 = new QuickNavItem(true, new ItemData("ender_chest"), "Storage", "/storage"); + public QuickNavItem button6 = new QuickNavItem(true, new ItemData("ender_chest"), "(?:Rift )?Storage(?: \\(1/2\\))?", "/storage"); @ConfigEntry.Category("button7") @ConfigEntry.Gui.CollapsibleObject() @@ -413,10 +413,14 @@ public class SkyblockerConfig implements ConfigData { public float mapScaling = 1f; public int mapX = 2; public int mapY = 2; + @ConfigEntry.Gui.Tooltip + public boolean starredMobGlow = false; public boolean solveThreeWeirdos = true; @ConfigEntry.Gui.Tooltip public boolean blazesolver = true; public boolean solveTrivia = true; + @ConfigEntry.Gui.Tooltip + public boolean solveTicTacToe = true; @ConfigEntry.Gui.CollapsibleObject public LividColor lividColor = new LividColor(); @ConfigEntry.Gui.CollapsibleObject() diff --git a/src/main/java/me/xmrvizzy/skyblocker/mixin/DrawContextMixin.java b/src/main/java/me/xmrvizzy/skyblocker/mixin/DrawContextMixin.java index cd0ccefd..6c059741 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/mixin/DrawContextMixin.java +++ b/src/main/java/me/xmrvizzy/skyblocker/mixin/DrawContextMixin.java @@ -12,12 +12,14 @@ import net.minecraft.client.render.RenderLayer; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; +import net.minecraft.util.Formatting; import net.minecraft.util.math.ColorHelper; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; -import java.awt.*; + +import java.awt.Color; import java.util.regex.Pattern; @Mixin(DrawContext.class) @@ -31,39 +33,58 @@ public abstract class DrawContextMixin { @Inject(method = "drawItemInSlot(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V", at = @At("HEAD")) public void skyblocker$renderItemBar(@Arg ItemStack stack, @Arg(ordinal = 0) int x, @Arg(ordinal = 1) int y) { - if (Utils.isOnSkyblock() && SkyblockerConfig.get().locations.dwarvenMines.enableDrillFuel) { - if (!stack.isEmpty()) { - NbtCompound tag = stack.getNbt(); - if (tag != null && tag.contains("ExtraAttributes")) { - if (tag.getCompound("ExtraAttributes").contains("drill_fuel")) { - float current = 3000.0F; - float max = 3000.0F; + if (!Utils.isOnSkyblock() || !SkyblockerConfig.get().locations.dwarvenMines.enableDrillFuel || stack.isEmpty()) { + return; + } - for (String line : ItemUtils.getTooltipStrings(stack)) { - if (line.contains("Fuel: ")) { - String clear = Pattern.compile("[^0-9 /]").matcher(line).replaceAll("").trim(); - String[] split = clear.split("/"); - current = Integer.parseInt(split[0]); - max = Integer.parseInt(split[1]) * 1000; - break; - } - } + NbtCompound tag = stack.getNbt(); + if (tag == null || !tag.contains("ExtraAttributes")) { + return; + } - matrices.push(); - matrices.translate(0f, 0f, 200f); - RenderSystem.disableDepthTest(); + NbtCompound extraAttributes = tag.getCompound("ExtraAttributes"); + if (!extraAttributes.contains("drill_fuel") && !extraAttributes.getString("id").equals("PICKONIMBUS")) { + return; + } + matrices.push(); + matrices.translate(0f, 0f, 200f); + RenderSystem.disableDepthTest(); - float hue = Math.max(0.0F, 1.0F - (max - current) / max); - int width = Math.round(current / max * 13.0F); - Color color = Color.getHSBColor(hue / 3.0F, 1.0F, 1.0F); - this.fill(RenderLayer.getGuiOverlay(), x + 2, y + 13, x + 15, y + 15, 0xFF000000); - this.fill(RenderLayer.getGuiOverlay(), x + 2, y + 13, x + 2 + width, y + 14, ColorHelper.Argb.getArgb(color.getAlpha(), color.getRed(), color.getGreen(), color.getBlue())); + float current = 0.0F; + float max = 0.0F; + String clearFormatting = ""; - matrices.pop(); - RenderSystem.enableDepthTest(); + for (String line : ItemUtils.getTooltipStrings(stack)) { + clearFormatting = Formatting.strip(line); + if (line.contains("Fuel: ")) { + if (clearFormatting != null) { + String clear = Pattern.compile("[^0-9 /]").matcher(clearFormatting).replaceAll("").trim(); + String[] split = clear.split("/"); + current = Integer.parseInt(split[0]); + max = Integer.parseInt(split[1]) * 1000; + } + break; + } else if (line.contains("uses.")) { + if (clearFormatting != null) { + int startIndex = clearFormatting.lastIndexOf("after") + 6; + int endIndex = clearFormatting.indexOf("uses", startIndex); + if (startIndex >= 0 && endIndex > startIndex) { + String usesString = clearFormatting.substring(startIndex, endIndex).trim(); + current = Integer.parseInt(usesString); + max = 5000; } } + break; } } + + float hue = Math.max(0.0F, 1.0F - (max - current) / max); + int width = Math.round(current / max * 13.0F); + Color color = Color.getHSBColor(hue / 3.0F, 1.0F, 1.0F); + this.fill(RenderLayer.getGuiOverlay(), x + 2, y + 13, x + 15, y + 15, 0xFF000000); + this.fill(RenderLayer.getGuiOverlay(), x + 2, y + 13, x + 2 + width, y + 14, ColorHelper.Argb.getArgb(color.getAlpha(), color.getRed(), color.getGreen(), color.getBlue())); + + matrices.pop(); + RenderSystem.enableDepthTest(); } }
\ No newline at end of file diff --git a/src/main/java/me/xmrvizzy/skyblocker/mixin/WorldRendererMixin.java b/src/main/java/me/xmrvizzy/skyblocker/mixin/WorldRendererMixin.java new file mode 100644 index 00000000..3b796c38 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/mixin/WorldRendererMixin.java @@ -0,0 +1,33 @@ +package me.xmrvizzy.skyblocker.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.sugar.Local; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef; + +import me.xmrvizzy.skyblocker.config.SkyblockerConfig; +import me.xmrvizzy.skyblocker.skyblock.dungeon.StarredMobGlow; +import net.minecraft.client.render.WorldRenderer; +import net.minecraft.entity.Entity; + +@Mixin(WorldRenderer.class) +public class WorldRendererMixin { + + @ModifyExpressionValue(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;hasOutline(Lnet/minecraft/entity/Entity;)Z")) + private boolean skyblocker$shouldStarredMobGlow(boolean original, @Local Entity entity, @Share("isGlowingStarredMob") LocalBooleanRef isGlowingStarredMob) { + boolean isAStarredMobThatShouldGlow = SkyblockerConfig.get().locations.dungeons.starredMobGlow && StarredMobGlow.shouldMobGlow(entity); + + isGlowingStarredMob.set(isAStarredMobThatShouldGlow); + + return original || isAStarredMobThatShouldGlow; + } + + @ModifyVariable(method = "render", at = @At("STORE"), ordinal = 0) + private int skyblocker$modifyGlowColor(int color, @Local Entity entity, @Share("isGlowingStarredMob") LocalBooleanRef isGlowingStarredMob) { + return isGlowingStarredMob.get() ? StarredMobGlow.getGlowColor(entity) : color; + } +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/CroesusHelper.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/CroesusHelper.java index 44c8803f..def1eb76 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/CroesusHelper.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/CroesusHelper.java @@ -25,7 +25,7 @@ public class CroesusHelper extends ContainerSolver { List<ColorHighlight> highlights = new ArrayList<>(); for (Map.Entry<Integer, ItemStack> entry : slots.entrySet()) { ItemStack stack = entry.getValue(); - if (stack != null && stack.getNbt() != null && stack.getNbt().toString().contains("No more Chests to open!")) { + if (stack != null && stack.getNbt() != null && stack.getNbt().toString().contains("Opened Chest:")) { highlights.add(ColorHighlight.gray(entry.getKey())); } } diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonBlaze.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonBlaze.java index d0dcf1e1..c3e12c3d 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonBlaze.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/DungeonBlaze.java @@ -9,8 +9,10 @@ import me.xmrvizzy.skyblocker.utils.color.QuadColor; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.world.ClientWorld; -import net.minecraft.entity.Entity; +import net.minecraft.entity.decoration.ArmorStandEntity; +import net.minecraft.predicate.entity.EntityPredicates; import net.minecraft.util.math.Box; import net.minecraft.util.math.Vec3d; import org.slf4j.Logger; @@ -20,92 +22,133 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; +/** + * This class provides functionality to render outlines around Blaze entities + */ public class DungeonBlaze { private static final Logger LOGGER = LoggerFactory.getLogger(DungeonBlaze.class.getName()); - private static final float[] WHITE_COLOR_COMPONENTS = { 1.0f, 1.0f, 1.0f }; - static Entity highestBlaze = null; - static Entity lowestBlaze = null; - static Entity nextHighestBlaze = null; - static Entity nextLowestBlaze = null; - static boolean renderHooked = false; - + private static final float[] WHITE_COLOR_COMPONENTS = {1.0f, 1.0f, 1.0f}; + private static final QuadColor outlineColorGreen = QuadColor.single(0.0F, 1.0f, 0.0f, 1f); + private static final QuadColor outlineColorWhite = QuadColor.single(1.0f, 1.0f, 1.0f, 1.0f); + + private static ArmorStandEntity highestBlaze = null; + private static ArmorStandEntity lowestBlaze = null; + private static ArmorStandEntity nextHighestBlaze = null; + private static ArmorStandEntity nextLowestBlaze = null; + private static boolean renderHooked = false; + + /** + * Updates the state of Blaze entities and triggers the rendering process if necessary. + */ public static void update() { ClientWorld world = MinecraftClient.getInstance().world; - if (world == null || !Utils.isInDungeons()) return; - if (!renderHooked){ - + ClientPlayerEntity player = MinecraftClient.getInstance().player; + if (world == null || player == null || !Utils.isInDungeons()) return; + if (!renderHooked) { WorldRenderEvents.BEFORE_DEBUG_RENDER.register(DungeonBlaze::blazeRenderer); renderHooked = true; } - Iterable<Entity> entities = world.getEntities(); - List<ObjectIntPair<Entity>> blazes = new ArrayList<>(); + List<ObjectIntPair<ArmorStandEntity>> blazes = getBlazesInWorld(world, player); + sortBlazes(blazes); + updateBlazeEntities(blazes); + } - for (Entity entity : entities) { - String blazeName = entity.getName().getString(); - + /** + * Retrieves Blaze entities in the world and parses their health information. + * + * @param world The client world to search for Blaze entities. + * @return A list of Blaze entities and their associated health. + */ + private static List<ObjectIntPair<ArmorStandEntity>> getBlazesInWorld(ClientWorld world, ClientPlayerEntity player) { + List<ObjectIntPair<ArmorStandEntity>> blazes = new ArrayList<>(); + for (ArmorStandEntity blaze : world.getEntitiesByClass(ArmorStandEntity.class, player.getBoundingBox().expand(500D), EntityPredicates.NOT_MOUNTED)) { + String blazeName = blaze.getName().getString(); if (blazeName.contains("Blaze") && blazeName.contains("/")) { try { int health = Integer.parseInt(blazeName.substring(blazeName.indexOf("/") + 1, blazeName.length() - 1)); - - blazes.add(ObjectIntPair.of(entity, health)); - } catch (NumberFormatException ex) { - ex.printStackTrace(); + blazes.add(ObjectIntPair.of(blaze, health)); + } catch (NumberFormatException e) { + handleException(e); } } } + return blazes; + } - // Order the blazes in the list from the lowest health to the highest health + /** + * Sorts the Blaze entities based on their health values. + * + * @param blazes The list of Blaze entities to be sorted. + */ + private static void sortBlazes(List<ObjectIntPair<ArmorStandEntity>> blazes) { blazes.sort(Comparator.comparingInt(ObjectIntPair::rightInt)); + } - // Ensure that there are blazes in the list + /** + * Updates information about Blaze entities based on sorted list. + * + * @param blazes The sorted list of Blaze entities with associated health values. + */ + private static void updateBlazeEntities(List<ObjectIntPair<ArmorStandEntity>> blazes) { if (!blazes.isEmpty()) { lowestBlaze = blazes.get(0).left(); - int highestIndex = blazes.size() - 1; highestBlaze = blazes.get(highestIndex).left(); - - // If there's more than 1 blaze if (blazes.size() > 1) { - nextLowestBlaze = blazes.get(1).left(); - nextHighestBlaze = blazes.get(highestIndex - 1).left(); + nextLowestBlaze = blazes.get(1).left(); + nextHighestBlaze = blazes.get(highestIndex - 1).left(); } } - } + + /** + * Renders outlines for Blaze entities based on health and position. + * + * @param wrc The WorldRenderContext used for rendering. + */ public static void blazeRenderer(WorldRenderContext wrc) { - QuadColor outlineColorRed = QuadColor.single( 0.0F, 1.0F, 0.0F, 1f); - QuadColor outlineColorGreen = QuadColor.single(1.0F, 0.0F, 0.0F, 1f); - QuadColor outlineColorWhite = QuadColor.single(1.0f, 1.0f, 1.0f, 1.0f); - try { - if (highestBlaze != null && lowestBlaze != null && highestBlaze.isAlive() && lowestBlaze.isAlive() && SkyblockerConfig.get().locations.dungeons.blazesolver){ - /* Outline */ + if (highestBlaze != null && lowestBlaze != null && highestBlaze.isAlive() && lowestBlaze.isAlive() && SkyblockerConfig.get().locations.dungeons.blazesolver) { if (highestBlaze.getY() < 69) { - Box blaze = highestBlaze.getBoundingBox().expand(0.3, 0.9, 0.3).offset(0, -1.1, 0); - RenderUtils.drawBoxOutline(blaze, outlineColorRed, 5f); - - if (nextHighestBlaze != null && nextHighestBlaze.isAlive() && nextHighestBlaze != highestBlaze) { - Box nextBlaze = nextHighestBlaze.getBoundingBox().expand(0.3, 0.9, 0.3).offset(0, -1.1, 0); - RenderUtils.drawBoxOutline(nextBlaze, outlineColorWhite, 5f); - RenderHelper.renderLinesFromPoints(wrc, new Vec3d[] { blaze.getCenter(), nextBlaze.getCenter() }, WHITE_COLOR_COMPONENTS, 1f, 5f); - } + renderBlazeOutline(highestBlaze, nextHighestBlaze, wrc); } - - /* Outline */ if (lowestBlaze.getY() > 69) { - Box blaze = lowestBlaze.getBoundingBox().expand(0.3, 0.9, 0.3).offset(0, -1.1, 0); - RenderUtils.drawBoxOutline(blaze, outlineColorRed, 5f); - - if (nextLowestBlaze != null && nextLowestBlaze.isAlive() && nextLowestBlaze != lowestBlaze) { - Box nextBlaze = nextLowestBlaze.getBoundingBox().expand(0.3, 0.9, 0.3).offset(0, -1.1, 0); - RenderUtils.drawBoxOutline(nextBlaze, outlineColorWhite, 5f); - RenderHelper.renderLinesFromPoints(wrc, new Vec3d[] { blaze.getCenter(), nextBlaze.getCenter() }, WHITE_COLOR_COMPONENTS, 1f, 5f); - } + renderBlazeOutline(lowestBlaze, nextLowestBlaze, wrc); } } } catch (Exception e) { - LOGGER.warn("[Skyblocker BlazeRenderer] " + e); - e.printStackTrace(); + handleException(e); } } -}
\ No newline at end of file + + /** + * Renders outlines for Blaze entities and connections between them. + * + * @param blaze The Blaze entity for which to render an outline. + * @param nextBlaze The next Blaze entity for connection rendering. + * @param wrc The WorldRenderContext used for rendering. + */ + private static void renderBlazeOutline(ArmorStandEntity blaze, ArmorStandEntity nextBlaze, WorldRenderContext wrc) { + Box blazeBox = blaze.getBoundingBox().expand(0.3, 0.9, 0.3).offset(0, -1.1, 0); + RenderUtils.drawBoxOutline(blazeBox, DungeonBlaze.outlineColorGreen, 5f); + + if (nextBlaze != null && nextBlaze.isAlive() && nextBlaze != blaze) { + Box nextBlazeBox = nextBlaze.getBoundingBox().expand(0.3, 0.9, 0.3).offset(0, -1.1, 0); + RenderUtils.drawBoxOutline(nextBlazeBox, DungeonBlaze.outlineColorWhite, 5f); + + Vec3d blazeCenter = blazeBox.getCenter(); + Vec3d nextBlazeCenter = nextBlazeBox.getCenter(); + + RenderHelper.renderLinesFromPoints(wrc, new Vec3d[]{blazeCenter, nextBlazeCenter}, WHITE_COLOR_COMPONENTS, 1f, 5f); + } + } + + /** + * Handles exceptions by logging and printing stack traces. + * + * @param e The exception to handle. + */ + private static void handleException(Exception e) { + LOGGER.warn("[Skyblocker BlazeRenderer] Encountered an unknown exception", e); + } +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/StarredMobGlow.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/StarredMobGlow.java new file mode 100644 index 00000000..b45242f9 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/StarredMobGlow.java @@ -0,0 +1,51 @@ +package me.xmrvizzy.skyblocker.skyblock.dungeon; + +import java.util.List; + +import me.xmrvizzy.skyblocker.utils.Utils; +import me.xmrvizzy.skyblocker.utils.culling.OcclusionCulling; +import net.minecraft.entity.Entity; +import net.minecraft.entity.decoration.ArmorStandEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.predicate.entity.EntityPredicates; +import net.minecraft.util.math.Box; + +public class StarredMobGlow { + + public static boolean shouldMobGlow(Entity entity) { + Box box = entity.getBoundingBox(); + + if (Utils.isInDungeons() && !entity.isInvisible() && OcclusionCulling.isVisible(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ)) { + //Minibosses + if (entity instanceof PlayerEntity) { + switch (entity.getName().getString()) { + case "Lost Adventurer": return true; + case "Shadow Assassin": return true; + case "Diamond Guy": return true; + } + } + + //Regular Mobs + if (!(entity instanceof ArmorStandEntity)) { + Box searchBox = box.expand(0, 2, 0); + List<ArmorStandEntity> armorStands = entity.getWorld().getEntitiesByClass(ArmorStandEntity.class, searchBox, EntityPredicates.NOT_MOUNTED); + + if (!armorStands.isEmpty() && armorStands.get(0).getName().getString().contains("✯")) return true; + } + } + + return false; + } + + public static int getGlowColor(Entity entity) { + if (entity instanceof PlayerEntity) { + switch (entity.getName().getString()) { + case "Lost Adventurer": return 0xfee15c; + case "Shadow Assassin": return 0x5b2cb2; + case "Diamond Guy": return 0x57c2f7; + } + } + + return 0xf57738; + } +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/TicTacToe.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/TicTacToe.java new file mode 100644 index 00000000..f5d55d2c --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/dungeon/TicTacToe.java @@ -0,0 +1,138 @@ +package me.xmrvizzy.skyblocker.skyblock.dungeon; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import me.xmrvizzy.skyblocker.config.SkyblockerConfig; +import me.xmrvizzy.skyblocker.utils.RenderUtils; +import me.xmrvizzy.skyblocker.utils.Utils; +import me.xmrvizzy.skyblocker.utils.color.QuadColor; +import me.xmrvizzy.skyblocker.utils.tictactoe.TicTacToeUtils; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; +import net.minecraft.block.Block; +import net.minecraft.block.Blocks; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.decoration.ItemFrameEntity; +import net.minecraft.item.FilledMapItem; +import net.minecraft.item.map.MapState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Direction; + +/** + * Thanks to Danker for a reference implementation! + */ +public class TicTacToe { + private static final Logger LOGGER = LoggerFactory.getLogger(TicTacToe.class); + private static final QuadColor RED = QuadColor.single(1.0f, 0.0f, 0.0f, 1f); + private static Box nextBestMoveToMake = null; + + public static void init() { + WorldRenderEvents.BEFORE_DEBUG_RENDER.register(TicTacToe::solutionRenderer); + } + + public static void tick() { + MinecraftClient client = MinecraftClient.getInstance(); + ClientWorld world = client.world; + ClientPlayerEntity player = client.player; + + nextBestMoveToMake = null; + + if (world == null || player == null || !Utils.isInDungeons()) return; + + //Search within 21 blocks for item frames that contain maps + Box searchBox = new Box(player.getX() - 21, player.getY() - 21, player.getZ() - 21, player.getX() + 21, player.getY() + 21, player.getZ() + 21); + List<ItemFrameEntity> itemFramesThatHoldMaps = world.getEntitiesByClass(ItemFrameEntity.class, searchBox, ItemFrameEntity::containsMap); + + try { + //Only attempt to solve if its the player's turn + if (itemFramesThatHoldMaps.size() != 9 && itemFramesThatHoldMaps.size() % 2 == 1) { + char[][] board = new char[3][3]; + BlockPos leftmostRow = null; + int sign = 1; + char facing = 'X'; + + for (ItemFrameEntity itemFrame : itemFramesThatHoldMaps) { + MapState mapState = world.getMapState(FilledMapItem.getMapName(itemFrame.getMapId().getAsInt())); + + if (mapState == null) continue; + + int column = 0, row; + sign = 1; + + //Find position of the item frame relative to where it is on the tic tac toe board + if (itemFrame.getHorizontalFacing() == Direction.SOUTH || itemFrame.getHorizontalFacing() == Direction.WEST) sign = -1; + BlockPos itemFramePos = BlockPos.ofFloored(itemFrame.getX(), itemFrame.getY(), itemFrame.getZ()); + + for (int i = 2; i >= 0; i--) { + int realI = i * sign; + BlockPos blockPos = itemFramePos; + + if (itemFrame.getX() % 0.5 == 0) { + blockPos = itemFramePos.add(realI, 0, 0); + } else if (itemFrame.getZ() % 0.5 == 0) { + blockPos = itemFramePos.add(0, 0, realI); + facing = 'Z'; + } + + Block block = world.getBlockState(blockPos).getBlock(); + if (block == Blocks.AIR || block == Blocks.STONE_BUTTON) { + leftmostRow = blockPos; + column = i; + + break; + } + } + + //Determine the row of the item frame + if (itemFrame.getY() == 72.5) { + row = 0; + } else if (itemFrame.getY() == 71.5) { + row = 1; + } else if (itemFrame.getY() == 70.5) { + row = 2; + } else { + continue; + } + + + //Get the color of the middle pixel of the map which determines whether its X or O + int middleColor = mapState.colors[8256] & 255; + + if (middleColor == 114) { + board[row][column] = 'X'; + } else if (middleColor == 33) { + board[row][column] = 'O'; + } + + int bestMove = TicTacToeUtils.getBestMove(board) - 1; + + if (leftmostRow != null) { + double drawX = facing == 'X' ? leftmostRow.getX() - sign * (bestMove % 3) : leftmostRow.getX(); + double drawY = 72 - (double) (bestMove / 3); + double drawZ = facing == 'Z' ? leftmostRow.getZ() - sign * (bestMove % 3) : leftmostRow.getZ(); + + nextBestMoveToMake = new Box(drawX, drawY, drawZ, drawX + 1, drawY + 1, drawZ + 1); + } + } + } + } catch (Exception e) { + LOGGER.error("[Skyblocker Tic Tac Toe] Encountered an exception while determining a tic tac toe solution!", e); + } + } + + private static void solutionRenderer(WorldRenderContext context) { + try { + if (SkyblockerConfig.get().locations.dungeons.solveTicTacToe && nextBestMoveToMake != null) { + RenderUtils.drawBoxOutline(nextBestMoveToMake, RED, 5); + } + } catch (Exception e) { + LOGGER.error("[Skyblocker Tic Tac Toe] Encountered an exception while rendering the tic tac toe solution!", e); + } + } +} diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/CustomArmorTrims.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/CustomArmorTrims.java index ffc3f67d..6c648da9 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/CustomArmorTrims.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/item/CustomArmorTrims.java @@ -42,7 +42,10 @@ public class CustomArmorTrims { private static void initializeTrimCache() { ClientPlayerEntity player = MinecraftClient.getInstance().player; - if (!trimsInitialized && player != null) { + if (trimsInitialized || player == null) { + return; + } + try { TRIMS_CACHE.clear(); DynamicRegistryManager registryManager = player.networkHandler.getRegistryManager(); for (Identifier material : registryManager.get(RegistryKeys.TRIM_MATERIAL).getIds()) { @@ -62,6 +65,8 @@ public class CustomArmorTrims { LOGGER.info("[Skyblocker] Successfully cached all armor trims!"); trimsInitialized = true; + } catch (Exception e) { + LOGGER.error("[Skyblocker] Encountered an exception while caching armor trims", e); } } diff --git a/src/main/java/me/xmrvizzy/skyblocker/skyblock/quicknav/QuickNav.java b/src/main/java/me/xmrvizzy/skyblocker/skyblock/quicknav/QuickNav.java index 1d58435e..351cef48 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/skyblock/quicknav/QuickNav.java +++ b/src/main/java/me/xmrvizzy/skyblocker/skyblock/quicknav/QuickNav.java @@ -18,8 +18,8 @@ public class QuickNav { private static final String dungeonHubIconNbt = "{id:\"minecraft:player_head\",Count:1,tag:{SkullOwner:{Id:[I;1605800870,415127827,-1236127084,15358548],Properties:{textures:[{Value:\"e3RleHR1cmVzOntTS0lOOnt1cmw6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzg5MWQ1YjI3M2ZmMGJjNTBjOTYwYjJjZDg2ZWVmMWM0MGExYjk0MDMyYWU3MWU3NTQ3NWE1NjhhODI1NzQyMSJ9fX0=\"}]}}}}"; public static void init() { - ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> { - if (Utils.isOnSkyblock() && SkyblockerConfig.get().quickNav.enableQuickNav && screen instanceof HandledScreen<?>) { + ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> { + if (Utils.isOnSkyblock() && SkyblockerConfig.get().quickNav.enableQuickNav && screen instanceof HandledScreen<?> && client.player != null && !client.player.isCreative()) { String screenTitle = screen.getTitle().getString().trim(); List<QuickNavButton> buttons = QuickNav.init(screenTitle); for (QuickNavButton button : buttons) Screens.getButtons(screen).add(button); diff --git a/src/main/java/me/xmrvizzy/skyblocker/utils/Utils.java b/src/main/java/me/xmrvizzy/skyblocker/utils/Utils.java index e85020aa..839e0dae 100644 --- a/src/main/java/me/xmrvizzy/skyblocker/utils/Utils.java +++ b/src/main/java/me/xmrvizzy/skyblocker/utils/Utils.java @@ -28,6 +28,7 @@ import java.util.List; * Utility variables and methods for retrieving Skyblock related information. */ public class Utils { + private static final String ALTERNATE_HYPIXEL_ADDRESS = System.getProperty("skyblocker.alternateHypixelAddress", ""); private static final String PROFILE_PREFIX = "Profile: "; private static boolean isOnHypixel = false; private static boolean isOnSkyblock = false; @@ -129,10 +130,9 @@ public class Utils { return; } String string = sidebar.toString(); - String serverAddress = (client.getCurrentServerEntry() != null) ? client.getCurrentServerEntry().address.toLowerCase() : ""; if (sidebar.isEmpty()) return; - if (serverAddress.contains("hypixel.net") || serverAddress.contains("hypixel.io")) { + if (isConnectedToHypixel(client)) { if (!isOnHypixel) { isOnHypixel = true; } @@ -154,6 +154,14 @@ public class Utils { leaveSkyblock(); } } + + private static boolean isConnectedToHypixel(MinecraftClient client) { + String serverAddress = (client.getCurrentServerEntry() != null) ? client.getCurrentServerEntry().address.toLowerCase() : ""; + String serverBrand = (client.player != null && client.player.getServerBrand() != null) ? client.player.getServerBrand() : ""; + boolean isOnHypixel = (serverAddress.equalsIgnoreCase(ALTERNATE_HYPIXEL_ADDRESS) || serverAddress.contains("hypixel.net") || serverAddress.contains("hypixel.io") || serverBrand.contains("Hypixel BungeeCord")); + + return isOnHypixel; + } private static void leaveSkyblock() { if (isOnSkyblock) { diff --git a/src/main/java/me/xmrvizzy/skyblocker/utils/tictactoe/TicTacToeUtils.java b/src/main/java/me/xmrvizzy/skyblocker/utils/tictactoe/TicTacToeUtils.java new file mode 100644 index 00000000..5e5c8f89 --- /dev/null +++ b/src/main/java/me/xmrvizzy/skyblocker/utils/tictactoe/TicTacToeUtils.java @@ -0,0 +1,104 @@ +package me.xmrvizzy.skyblocker.utils.tictactoe; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class TicTacToeUtils { + + public static int getBestMove(char[][] board) { + HashMap<Integer, Integer> moves = new HashMap<>(); + for (int row = 0; row < board.length; row++) { + for (int col = 0; col < board[row].length; col++) { + if (board[row][col] != '\0') continue; + board[row][col] = 'O'; + int score = alphabeta(board, Integer.MIN_VALUE, Integer.MAX_VALUE, false, 0); + board[row][col] = '\0'; + moves.put(row * 3 + col + 1, score); + } + } + return Collections.max(moves.entrySet(), Map.Entry.comparingByValue()).getKey(); + } + + public static boolean hasMovesLeft(char[][] board) { + for (char[] rows : board) { + for (char col : rows) { + if (col == '\0') return true; + } + } + return false; + } + + public static int getBoardRanking(char[][] board) { + for (int row = 0; row < 3; row++) { + if (board[row][0] == board[row][1] && board[row][0] == board[row][2]) { + if (board[row][0] == 'X') { + return -10; + } else if (board[row][0] == 'O') { + return 10; + } + } + } + + for (int col = 0; col < 3; col++) { + if (board[0][col] == board[1][col] && board[0][col] == board[2][col]) { + if (board[0][col] == 'X') { + return -10; + } else if (board[0][col] == 'O') { + return 10; + } + } + } + + if (board[0][0] == board[1][1] && board[0][0] == board[2][2]) { + if (board[0][0] == 'X') { + return -10; + } else if (board[0][0] == 'O') { + return 10; + } + } else if (board[0][2] == board[1][1] && board[0][2] == board[2][0]) { + if (board[0][2] == 'X') { + return -10; + } else if (board[0][2] == 'O') { + return 10; + } + } + + return 0; + } + public static int alphabeta(char[][] board, int alpha, int beta, boolean max, int depth) { + int score = getBoardRanking(board); + if (score == 10 || score == -10) return score; + if (!hasMovesLeft(board)) return 0; + + if (max) { + int bestScore = Integer.MIN_VALUE; + for (int row = 0; row < 3; row++) { + for (int col = 0; col < 3; col++) { + if (board[row][col] == '\0') { + board[row][col] = 'O'; + bestScore = Math.max(bestScore, alphabeta(board, alpha, beta, false, depth + 1)); + board[row][col] = '\0'; + alpha = Math.max(alpha, bestScore); + if (beta <= alpha) break; // Pruning + } + } + } + return bestScore - depth; + } else { + int bestScore = Integer.MAX_VALUE; + for (int row = 0; row < 3; row++) { + for (int col = 0; col < 3; col++) { + if (board[row][col] == '\0') { + board[row][col] = 'X'; + bestScore = Math.min(bestScore, alphabeta(board, alpha, beta, true, depth + 1)); + board[row][col] = '\0'; + beta = Math.min(beta, bestScore); + if (beta <= alpha) break; // Pruning + } + } + } + return bestScore + depth; + } + } +}
\ No newline at end of file diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 578917e2..87fa9b3a 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -204,10 +204,14 @@ "text.autoconfig.skyblocker.option.locations.dungeons.mapScaling": "Map Scaling", "text.autoconfig.skyblocker.option.locations.dungeons.mapX": "Map X", "text.autoconfig.skyblocker.option.locations.dungeons.mapY": "Map Y", + "text.autoconfig.skyblocker.option.locations.dungeons.starredMobGlow": "Starred Mob Glow", + "text.autoconfig.skyblocker.option.locations.dungeons.starredMobGlow.@Tooltip": "Applies the glowing effect to starred mobs that are visible.", "text.autoconfig.skyblocker.option.locations.dungeons.solveThreeWeirdos": "Solve Three Weirdos Puzzle", "text.autoconfig.skyblocker.option.locations.dungeons.blazesolver": "Solve Blaze Puzzle", "text.autoconfig.skyblocker.option.locations.dungeons.blazesolver.@Tooltip": "Boxes the correct blaze in green, also draws a line to and boxes the next blaze to kill in white.", "text.autoconfig.skyblocker.option.locations.dungeons.solveTrivia": "Solve Trivia Puzzle", + "text.autoconfig.skyblocker.option.locations.dungeons.solveTicTacToe": "Solve Tic Tac Toe Puzzle", + "text.autoconfig.skyblocker.option.locations.dungeons.solveTicTacToe.@Tooltip": "Puts a red box around the next best move for you to make!", "text.autoconfig.skyblocker.option.locations.dungeons.lividColor": "Livid Color", "text.autoconfig.skyblocker.option.locations.dungeons.lividColor.enableLividColor": "Enable Livid Color", "text.autoconfig.skyblocker.option.locations.dungeons.lividColor.enableLividColor.@Tooltip": "Send the livid color in the chat during the Livid boss fight.", diff --git a/src/main/resources/skyblocker.mixins.json b/src/main/resources/skyblocker.mixins.json index 266a653b..6ba7dc22 100644 --- a/src/main/resources/skyblocker.mixins.json +++ b/src/main/resources/skyblocker.mixins.json @@ -19,6 +19,7 @@ "PlayerListHudMixin", "PlayerSkinProviderMixin", "ScoreboardMixin", + "WorldRendererMixin", "accessor.BeaconBlockEntityRendererInvoker", "accessor.FrustumInvoker", "accessor.HandledScreenAccessor", |