diff options
Diffstat (limited to 'src/main/java/de/hysky/skyblocker')
25 files changed, 1358 insertions, 137 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java index 2ee6120e..fc72ea3f 100644 --- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java +++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java @@ -5,10 +5,12 @@ import com.google.gson.GsonBuilder; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.debug.Debug; import de.hysky.skyblocker.skyblock.*; +import de.hysky.skyblocker.skyblock.chat.ChatRuleAnnouncementScreen; import de.hysky.skyblocker.skyblock.crimson.kuudra.Kuudra; import de.hysky.skyblocker.skyblock.chat.ChatRulesHandler; import de.hysky.skyblocker.skyblock.dungeon.*; import de.hysky.skyblocker.skyblock.dungeon.partyfinder.PartyFinderScreen; +import de.hysky.skyblocker.skyblock.dungeon.puzzle.boulder.Boulder; import de.hysky.skyblocker.skyblock.dungeon.puzzle.CreeperBeams; import de.hysky.skyblocker.skyblock.dungeon.puzzle.DungeonBlaze; import de.hysky.skyblocker.skyblock.dungeon.puzzle.TicTacToe; @@ -18,6 +20,7 @@ import de.hysky.skyblocker.skyblock.dungeon.secrets.SecretsTracker; import de.hysky.skyblocker.skyblock.dwarven.CrystalsHud; import de.hysky.skyblocker.skyblock.dwarven.CrystalsLocationsManager; import de.hysky.skyblocker.skyblock.dwarven.DwarvenHud; +import de.hysky.skyblocker.skyblock.end.TheEnd; import de.hysky.skyblocker.skyblock.end.BeaconHighlighter; import de.hysky.skyblocker.skyblock.item.*; import de.hysky.skyblocker.skyblock.item.tooltip.BackpackPreview; @@ -109,6 +112,7 @@ public class SkyblockerMod implements ClientModInitializer { ChatMessageListener.init(); Shortcuts.init(); ChatRulesHandler.init(); + ChatRuleAnnouncementScreen.init(); DiscordRPCManager.init(); LividColor.init(); FishingHelper.init(); @@ -123,6 +127,7 @@ public class SkyblockerMod implements ClientModInitializer { FireFreezeStaffTimer.init(); GuardianHealth.init(); TheRift.init(); + TheEnd.init(); SearchOverManager.init(); TitleContainer.init(); ScreenMaster.init(); @@ -137,6 +142,7 @@ public class SkyblockerMod implements ClientModInitializer { SpecialEffects.init(); ItemProtection.init(); CreeperBeams.init(); + Boulder.init(); ItemRarityBackgrounds.init(); MuseumItemCache.init(); SecretsTracker.init(); diff --git a/src/main/java/de/hysky/skyblocker/config/HudConfigScreen.java b/src/main/java/de/hysky/skyblocker/config/HudConfigScreen.java new file mode 100644 index 00000000..c33e2f54 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/config/HudConfigScreen.java @@ -0,0 +1,83 @@ +package de.hysky.skyblocker.config; + +import de.hysky.skyblocker.skyblock.tabhud.widget.Widget; +import de.hysky.skyblocker.utils.render.RenderHelper; +import it.unimi.dsi.fastutil.ints.IntIntImmutablePair; +import it.unimi.dsi.fastutil.ints.IntIntPair; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.Text; + +import java.awt.*; + +public abstract class HudConfigScreen extends Screen { + private final Widget widget; + private final Screen parent; + + private int hudX = 0; + private int hudY = 0; + public HudConfigScreen(Text title, Widget widget, Screen parent) { + super(title); + this.widget = widget; + this.parent = parent; + + int[] posFromConfig = getPosFromConfig(SkyblockerConfigManager.get()); + hudX = posFromConfig[0]; + hudY = posFromConfig[1]; + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta) { + super.render(context, mouseX, mouseY, delta); + renderBackground(context, mouseX, mouseY, delta); + renderWidget(context, hudX, hudY); + context.drawCenteredTextWithShadow(textRenderer, "Right Click To Reset Position", width / 2, height / 2, Color.GRAY.getRGB()); + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + IntIntPair dims = getDimensions(); + if (RenderHelper.pointIsInArea(mouseX, mouseY, hudX, hudY, hudX + dims.leftInt(), hudY + dims.rightInt()) && button == 0) { + hudX = (int) Math.max(Math.min(mouseX - (double) dims.leftInt() / 2, this.width - dims.leftInt()), 0); + hudY = (int) Math.max(Math.min(mouseY - (double) dims.rightInt() / 2, this.height - dims.rightInt()), 0); + } + return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (button == 1) { + IntIntPair dims = getDimensions(); + hudX = this.width / 2 - dims.leftInt(); + hudY = this.height / 2 - dims.rightInt(); + } + return super.mouseClicked(mouseX, mouseY, button); + } + + abstract protected int[] getPosFromConfig(SkyblockerConfig config); + + protected IntIntPair getDimensions() { + return new IntIntImmutablePair(widget.getHeight(), widget.getWidth()); + } + + @Override + public void close() { + SkyblockerConfig skyblockerConfig = SkyblockerConfigManager.get(); + savePos(skyblockerConfig, hudX, hudY); + SkyblockerConfigManager.save(); + + client.setScreen(parent); + } + + /** + * This method should save the passed position to the config + * <p> + * NOTE: The parent class will call {@link SkyblockerConfigManager#save()} right after this method + * @param configManager the config so you don't have to get it + * @param x x + * @param y y + */ + abstract protected void savePos(SkyblockerConfig configManager, int x, int y); + + abstract protected void renderWidget(DrawContext context, int x, int y); +} diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java index 175c3bdf..ea1f7d43 100644 --- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java @@ -175,6 +175,9 @@ public class SkyblockerConfig { public boolean dontStripSkinAlphaValues = true; @SerialEntry + public boolean dungeonQuality = true; + + @SerialEntry public boolean visitorHelper = true; @SerialEntry @@ -652,6 +655,9 @@ public class SkyblockerConfig { public Rift rift = new Rift(); @SerialEntry + public TheEnd end = new TheEnd(); + + @SerialEntry public SpidersDen spidersDen = new SpidersDen(); @SerialEntry @@ -714,6 +720,9 @@ public class SkyblockerConfig { public boolean solveWaterboard = true; @SerialEntry + public boolean solveBoulder = true; + + @SerialEntry public boolean fireFreezeStaffTimer = true; @SerialEntry @@ -1042,6 +1051,24 @@ public class SkyblockerConfig { public int mcGrubberStacks = 0; } + public static class TheEnd { + + @SerialEntry + public boolean hudEnabled = true; + + @SerialEntry + public boolean enableBackground = true; + + @SerialEntry + public boolean waypoint = true; + + @SerialEntry + public int x = 10; + + @SerialEntry + public int y = 10; + } + public static class SpidersDen { @SerialEntry public Relics relics = new Relics(); @@ -1159,6 +1186,15 @@ public class SkyblockerConfig { @SerialEntry public ChatFilterResult hideDicer = ChatFilterResult.PASS; + + @SerialEntry + public ChatRuleConfig chatRuleConfig = new ChatRuleConfig(); + } + public static class ChatRuleConfig { + @SerialEntry + public int announcementLength = 60; + @SerialEntry + public int announcementScale = 3; } public enum Info { diff --git a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java index 3b685f9a..5eb9a066 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/DungeonsCategory.java @@ -409,6 +409,14 @@ public class DungeonsCategory { .controller(ConfigUtils::createBooleanController) .build()) .option(Option.<Boolean>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.solveBoulder")) + .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.solveBoulder.@Tooltip"))) + .binding(defaults.locations.dungeons.solveBoulder, + () -> config.locations.dungeons.solveBoulder, + newValue -> config.locations.dungeons.solveBoulder = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.fireFreezeStaffTimer")) .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.locations.dungeons.fireFreezeStaffTimer.@Tooltip"))) .binding(defaults.locations.dungeons.fireFreezeStaffTimer, 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 bb333f79..8a7d832c 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/GeneralCategory.java @@ -79,6 +79,14 @@ public class GeneralCategory { .flag(OptionFlag.ASSET_RELOAD) .build()) .option(Option.<Boolean>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.general.dungeonQuality")) + .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.general.dungeonQuality.@Tooltip"))) + .binding(defaults.general.dungeonQuality, + () -> config.general.dungeonQuality, + newValue -> config.general.dungeonQuality = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() .name(Text.translatable("text.autoconfig.skyblocker.option.general.visitorHelper")) .binding(defaults.general.visitorHelper, () -> config.general.visitorHelper, diff --git a/src/main/java/de/hysky/skyblocker/config/categories/LocationsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/LocationsCategory.java index 9bdcf2e9..75c83a9b 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/LocationsCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/LocationsCategory.java @@ -2,11 +2,11 @@ package de.hysky.skyblocker.config.categories; import de.hysky.skyblocker.config.ConfigUtils; import de.hysky.skyblocker.config.SkyblockerConfig; -import dev.isxander.yacl3.api.ConfigCategory; -import dev.isxander.yacl3.api.Option; -import dev.isxander.yacl3.api.OptionDescription; -import dev.isxander.yacl3.api.OptionGroup; +import de.hysky.skyblocker.skyblock.end.EndHudConfigScreen; +import de.hysky.skyblocker.skyblock.end.TheEnd; +import dev.isxander.yacl3.api.*; import dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder; +import net.minecraft.client.MinecraftClient; import net.minecraft.text.Text; public class LocationsCategory { @@ -79,6 +79,49 @@ public class LocationsCategory { .build()) .build()) + // The end + .group(OptionGroup.createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.locations.end")) + .collapsed(false) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.locations.end.hudEnabled")) + .binding(defaults.locations.end.hudEnabled, + () -> config.locations.end.hudEnabled, + newValue -> config.locations.end.hudEnabled = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.locations.dwarvenMines.dwarvenHud.enableBackground")) // Reusing that string cuz sure + .binding(defaults.locations.end.enableBackground, + () -> config.locations.end.enableBackground, + newValue -> config.locations.end.enableBackground = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.locations.end.waypoint")) + .binding(defaults.locations.end.waypoint, + () -> config.locations.end.waypoint, + newValue -> config.locations.end.waypoint = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(ButtonOption.createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.locations.end.screen")) + .text(Text.translatable("text.skyblocker.open")) // Reusing again lol + .action((screen, opt) -> MinecraftClient.getInstance().setScreen(new EndHudConfigScreen(screen))) + .build()) + .option(ButtonOption.createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.locations.end.resetName")) + .text(Text.translatable("text.autoconfig.skyblocker.option.locations.end.resetText")) + .action((screen, opt) -> { + TheEnd.zealotsKilled = 0; + TheEnd.zealotsSinceLastEye = 0; + TheEnd.eyes = 0; + }) + .build()) + .build() + + ) + //Spider's Den .group(OptionGroup.createBuilder() .name(Text.translatable("text.autoconfig.skyblocker.option.locations.spidersDen")) diff --git a/src/main/java/de/hysky/skyblocker/config/categories/MessageFilterCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/MessageFilterCategory.java index ddad018a..0f95bcaa 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/MessageFilterCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/MessageFilterCategory.java @@ -6,6 +6,7 @@ import de.hysky.skyblocker.skyblock.chat.ChatRulesConfigScreen; import de.hysky.skyblocker.skyblock.dwarven.CrystalsHudConfigScreen; import de.hysky.skyblocker.utils.chat.ChatFilterResult; import dev.isxander.yacl3.api.*; +import dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder; import net.minecraft.client.MinecraftClient; import net.minecraft.text.Text; @@ -136,7 +137,22 @@ public class MessageFilterCategory { .text(Text.translatable("text.skyblocker.open")) .action((screen, opt) -> MinecraftClient.getInstance().setScreen(new ChatRulesConfigScreen(screen))) .build()) - + .option(Option.<Integer>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.announcementLength")) + .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.announcementLength.@Tooltip"))) + .binding(defaults.messages.chatRuleConfig.announcementLength, + () -> config.messages.chatRuleConfig.announcementLength, + newValue -> config.messages.chatRuleConfig.announcementLength = newValue) + .controller(opt -> IntegerSliderControllerBuilder.create(opt).range(5, 200).step(1)) + .build()) + .option(Option.<Integer>createBuilder() + .name(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.announcementScale")) + .description(OptionDescription.of(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.announcementScale.@Tooltip"))) + .binding(defaults.messages.chatRuleConfig.announcementScale, + () -> config.messages.chatRuleConfig.announcementScale, + newValue -> config.messages.chatRuleConfig.announcementScale = newValue) + .controller(opt -> IntegerSliderControllerBuilder.create(opt).range(1, 8).step(1)) + .build()) .build()) .build(); } diff --git a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java index 1f56fe34..4c414212 100644 --- a/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixin/ClientPlayNetworkHandlerMixin.java @@ -4,6 +4,7 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.WrapWithCondition; import com.llamalad7.mixinextras.sugar.Local; import de.hysky.skyblocker.skyblock.FishingHelper; +import de.hysky.skyblocker.skyblock.end.TheEnd; import de.hysky.skyblocker.skyblock.dungeon.DungeonScore; import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; import de.hysky.skyblocker.skyblock.end.BeaconHighlighter; @@ -52,12 +53,6 @@ public abstract class ClientPlayNetworkHandlerMixin { return !Utils.isOnHypixel(); } - @ModifyExpressionValue(method = "onEntityStatus", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/play/EntityStatusS2CPacket;getEntity(Lnet/minecraft/world/World;)Lnet/minecraft/entity/Entity;")) - private Entity skyblocker$onEntityDeath(Entity entity, @Local(argsOnly = true) EntityStatusS2CPacket packet) { - if (packet.getStatus() == EntityStatuses.PLAY_DEATH_SOUND_OR_ADD_PROJECTILE_HIT_PARTICLES) DungeonScore.handleEntityDeath(entity); - return entity; - } - @WrapWithCondition(method = "onPlayerList", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)V", remap = false)) private boolean skyblocker$cancelPlayerListWarning(Logger instance, String format, Object arg1, Object arg2) { return !Utils.isOnHypixel(); @@ -87,4 +82,13 @@ public abstract class ClientPlayNetworkHandlerMixin { private void skyblocker$onParticle(ParticleS2CPacket packet, CallbackInfo ci) { MythologicalRitual.onParticle(packet); } + + @ModifyExpressionValue(method = "onEntityStatus", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/packet/s2c/play/EntityStatusS2CPacket;getEntity(Lnet/minecraft/world/World;)Lnet/minecraft/entity/Entity;")) + private Entity skyblocker$onEntityDeath(Entity entity, @Local(argsOnly = true) EntityStatusS2CPacket packet) { + if (packet.getStatus() == EntityStatuses.PLAY_DEATH_SOUND_OR_ADD_PROJECTILE_HIT_PARTICLES) { + DungeonScore.handleEntityDeath(entity); + TheEnd.onEntityDeath(entity); + } + return entity; + } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRule.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRule.java index 9643b413..97431305 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRule.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRule.java @@ -3,6 +3,8 @@ package de.hysky.skyblocker.skyblock.chat; import de.hysky.skyblocker.utils.Utils; import net.minecraft.client.sound.Sound; import net.minecraft.item.ItemStack; +import net.minecraft.sound.SoundEvent; +import net.minecraft.sound.SoundEvents; import java.util.List; import java.util.Objects; @@ -28,7 +30,7 @@ public class ChatRule { private Boolean showActionBar; private Boolean showAnnouncement; private String replaceMessage; //todo extract parts of original message - private Sound customSound; + private SoundEvent customSound; /** * Creates a chat rule with default options. */ @@ -49,22 +51,6 @@ public class ChatRule { this.customSound = null; } - - public ChatRule(String name, Boolean enabled, Boolean isPartialMatch, Boolean isRegex, Boolean isIgnoreCase, String filter, String validLocation, List<ItemStack> validItems, Boolean hideMessage, Boolean showActionBar, Boolean showAnnouncement, String replaceMessage, Sound customSound) { - this.name = name; - this.enabled = enabled; - this.isPartialMatch = isPartialMatch; - this.isRegex = isRegex; - this.isIgnoreCase = isIgnoreCase; - this.filter = filter; - this.validLocations = validLocation; - this.hideMessage = hideMessage; - this.showActionBar = showActionBar; - this.showAnnouncement = showAnnouncement; - this.replaceMessage = replaceMessage; - this.customSound = customSound; - } - public Boolean getEnabled() { //todo remove unused getters and set return enabled; } @@ -137,11 +123,11 @@ public class ChatRule { this.replaceMessage = replaceMessage; } - public Sound getCustomSound() { + public SoundEvent getCustomSound() { return customSound; } - public void setCustomSound(Sound customSound) { + public void setCustomSound(SoundEvent customSound) { this.customSound = customSound; } @@ -174,7 +160,7 @@ public class ChatRule { } //filter - if (testFilter.isEmpty()) return false; + if (testFilter.isBlank()) return false; if(isRegex) { if (isPartialMatch) { if (! Pattern.compile(testFilter).matcher(testString).find()) return false; @@ -190,7 +176,7 @@ public class ChatRule { } //location - if (validLocations.isEmpty()){ //if no locations do not check + if (validLocations.isBlank()){ //if no locations do not check return true; } String rawLocation = Utils.getLocationRaw(); @@ -208,7 +194,7 @@ public class ChatRule { } } } - if (isLocationValid == null || !isLocationValid){//if location is not in the list at all and is a not a "!" location or and is a normal location + if (isLocationValid == null || isLocationValid){//if location is not in the list at all and is a not a "!" location or and is a normal location return true; } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleAnnouncementScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleAnnouncementScreen.java new file mode 100644 index 00000000..e6300808 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleAnnouncementScreen.java @@ -0,0 +1,53 @@ +package de.hysky.skyblocker.skyblock.chat; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.dwarven.CrystalsHudConfigScreen; +import de.hysky.skyblocker.utils.scheduler.Scheduler; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; + +public class ChatRuleAnnouncementScreen { + + private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); + private static float timer; + + private static Text text = null; + + public static void init() { + HudRenderCallback.EVENT.register((context, tickDelta) -> { + if (timer <= 0 || text == null) { + return; + } + render(context, tickDelta); + }); + } + + /** + * renders {@link ChatRuleAnnouncementScreen#text} to the middle of the top of the screen. + * @param context render context + * @param tickDelta difference from last render to remove from timer + */ + private static void render(DrawContext context, float tickDelta) { + int scale = SkyblockerConfigManager.get().messages.chatRuleConfig.announcementScale; + //decrement timer + timer -= tickDelta; + //scale text up and center + MatrixStack matrices = context.getMatrices(); + matrices.push(); + matrices.translate(context.getScaledWindowWidth() / 2f, context.getScaledWindowHeight() * 0.3, 0f); + matrices.scale(scale, scale, 0f); + //render text + context.drawCenteredTextWithShadow(CLIENT.textRenderer,text,0, 0, 0xFFFFFF); + + matrices.pop(); + } + protected static void setText(Text newText) { + text = newText; + timer = SkyblockerConfigManager.get().messages.chatRuleConfig.announcementLength; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java index e866520f..f46af79b 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRuleConfigScreen.java @@ -6,16 +6,32 @@ import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.sound.SoundEvent; +import net.minecraft.sound.SoundEvents; +import net.minecraft.text.MutableText; import net.minecraft.text.Text; import java.awt.*; +import java.util.Map; + +import static java.util.Map.entry; public class ChatRuleConfigScreen extends Screen { private static final int SPACER_X = 5; private static final int SPACER_Y = 25; + private final Map<MutableText, SoundEvent> soundsLookup = Map.ofEntries( + entry(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.sounds.pling"), SoundEvents.BLOCK_NOTE_BLOCK_PLING.value()), + entry(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.sounds.cave"), SoundEvents.AMBIENT_CAVE.value()), + entry(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.sounds.zombie"), SoundEvents.ENTITY_ZOMBIE_AMBIENT), + entry(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.sounds.crit"), SoundEvents.ENTITY_PLAYER_ATTACK_CRIT), + entry(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.sounds.arrowHit"), SoundEvents.ENTITY_ARROW_HIT_PLAYER), + entry(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.sounds.amethyst"), SoundEvents.BLOCK_AMETHYST_BLOCK_HIT), + entry(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.sounds.anvil"), SoundEvents.BLOCK_ANVIL_LAND) + );//todo amathis / more sounds + private final int chatRuleIndex; private final ChatRule chatRule; @@ -32,8 +48,8 @@ public class ChatRuleConfigScreen extends Screen { private ButtonWidget hideMessageToggle; private ButtonWidget actionBarToggle; private ButtonWidget announcementToggle; + private ButtonWidget soundsToggle; private TextFieldWidget replaceMessageInput; - //todo custom sound thing //textLocations private IntIntPair nameLabelTextPos; @@ -51,11 +67,10 @@ public class ChatRuleConfigScreen extends Screen { private IntIntPair hideMessageTextPos; private IntIntPair actionBarTextPos; private IntIntPair announcementTextPos; - - private IntIntPair replaceMessageLabelTextPos; - private IntIntPair customSoundLabelTextPos; + private IntIntPair replaceMessageLabelTextPos; + private int currentSoundIndex; private final Screen parent; @@ -66,6 +81,7 @@ public class ChatRuleConfigScreen extends Screen { this.chatRuleIndex = chatRuleIndex; this.chatRule = ChatRulesHandler.chatRuleList.get(chatRuleIndex); this.parent = parent; + this.currentSoundIndex = soundsLookup.values().stream().toList().indexOf(chatRule.getCustomSound()); } @Override @@ -88,6 +104,7 @@ public class ChatRuleConfigScreen extends Screen { filterLabelTextPos = currentPos; lineXOffset = client.textRenderer.getWidth(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.filter")) + SPACER_X; filterInput = new TextFieldWidget(MinecraftClient.getInstance().textRenderer, currentPos.leftInt() + lineXOffset, currentPos.rightInt(), 200, 20, Text.of("")); + filterInput.setMaxLength(96); filterInput.setText(chatRule.getFilter()); currentPos = IntIntPair.of(currentPos.leftInt(),currentPos.rightInt() + SPACER_Y); lineXOffset = 0; @@ -163,16 +180,33 @@ public class ChatRuleConfigScreen extends Screen { .position(currentPos.leftInt() + lineXOffset, currentPos.rightInt()) .size(75,20) .build(); + lineXOffset += 75 + SPACER_X; + customSoundLabelTextPos = IntIntPair.of(currentPos.leftInt() + lineXOffset,currentPos.rightInt()); + lineXOffset += client.textRenderer.getWidth(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.sounds")) + SPACER_X; + soundsToggle = ButtonWidget.builder(getSoundName(), a -> { + currentSoundIndex += 1; + if (currentSoundIndex == soundsLookup.size()) { + currentSoundIndex = -1; + } + MutableText newText = getSoundName(); + soundsToggle.setMessage(newText); + chatRule.setCustomSound(soundsLookup.get(newText)); + }) + .position(currentPos.leftInt() + lineXOffset, currentPos.rightInt()) + .size(100,20) + .build(); currentPos = IntIntPair.of(currentPos.leftInt(),currentPos.rightInt() + SPACER_Y); + replaceMessageLabelTextPos = currentPos; lineXOffset = client.textRenderer.getWidth(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.replace")) + SPACER_X; replaceMessageInput = new TextFieldWidget(MinecraftClient.getInstance().textRenderer, currentPos.leftInt() + lineXOffset, currentPos.rightInt(), 200, 20, Text.of("")); + replaceMessageInput.setMaxLength(96); replaceMessageInput.setText(chatRule.getReplaceMessage()); finishButton = ButtonWidget.builder(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.finish"), a -> { close(); }) - .position((int) (this.width * 0.66), this.height - SPACER_Y) + .position(this.width - 75 - SPACER_Y, this.height - SPACER_Y) .size(75,20) .build(); @@ -185,13 +219,14 @@ public class ChatRuleConfigScreen extends Screen { addDrawableChild(hideMessageToggle); addDrawableChild(actionBarToggle); addDrawableChild(announcementToggle); + addDrawableChild(soundsToggle); addDrawableChild(replaceMessageInput); addDrawableChild(finishButton); } /** * works out the width of the maximum line - * @return + * @return the max used width */ private int getMaxUsedWidth() { if (client == null) return 0; @@ -237,6 +272,7 @@ public class ChatRuleConfigScreen extends Screen { context.drawTextWithShadow(this.textRenderer,Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.hideMessage"), hideMessageTextPos.leftInt(), hideMessageTextPos.rightInt() + yOffset, 0xFFFFFF); context.drawTextWithShadow(this.textRenderer,Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.actionBar"), actionBarTextPos.leftInt(), actionBarTextPos.rightInt() + yOffset, 0xFFFFFF); context.drawTextWithShadow(this.textRenderer,Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.announcement"), announcementTextPos.leftInt(), announcementTextPos.rightInt() + yOffset, 0xFFFFFF); + context.drawTextWithShadow(this.textRenderer,Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.sounds"), customSoundLabelTextPos.leftInt(), customSoundLabelTextPos.rightInt() + yOffset, 0xFFFFFF); context.drawTextWithShadow(this.textRenderer,Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.replace"), replaceMessageLabelTextPos.leftInt(), replaceMessageLabelTextPos.rightInt() + yOffset, 0xFFFFFF); } @@ -257,4 +293,13 @@ public class ChatRuleConfigScreen extends Screen { ChatRulesHandler.chatRuleList.set(chatRuleIndex,chatRule); } + + private MutableText getSoundName() { + if (currentSoundIndex == -1){ + return Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.sounds.none"); + } + return soundsLookup.keySet().stream().toList().get(currentSoundIndex); + } + + } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigListWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigListWidget.java index 07397f98..4e8038d6 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigListWidget.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigListWidget.java @@ -4,17 +4,19 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.Element; import net.minecraft.client.gui.Selectable; +import net.minecraft.client.gui.screen.ConfirmScreen; import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; import net.minecraft.client.gui.screen.narration.NarrationPart; import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.gui.widget.ElementListWidget; +import net.minecraft.screen.ScreenTexts; import net.minecraft.text.Text; import org.jetbrains.annotations.Nullable; import java.awt.*; import java.util.List; -public class ChatRulesConfigListWidget extends ElementListWidget<ChatRulesConfigListWidget.chatRuleConfigEntry> { +public class ChatRulesConfigListWidget extends ElementListWidget<ChatRulesConfigListWidget.AbstractChatRuleEntry> { private final ChatRulesConfigScreen screen; @@ -25,6 +27,8 @@ public class ChatRulesConfigListWidget extends ElementListWidget<ChatRulesConfig super(minecraftClient, width, height, y, itemHeight); this.screen = screen; this.hasChanged = false; + //add labels + addEntry(new chatRuleLabelsEntry()); //add entry fall all existing rules for (int i = 0; i < (long) ChatRulesHandler.chatRuleList.size(); i++){ addEntry(new chatRuleConfigEntry(i)); @@ -41,20 +45,15 @@ public class ChatRulesConfigListWidget extends ElementListWidget<ChatRulesConfig return super.getScrollbarPositionX() + 50; } - @Override - public void setSelected(@Nullable ChatRulesConfigListWidget.chatRuleConfigEntry entry) { - super.setSelected(entry); - screen.updateButtons(); - } protected void addRuleAfterSelected() { hasChanged = true; int newIndex = children().indexOf(getSelectedOrNull()) + 1; ChatRulesHandler.chatRuleList.add(newIndex, new ChatRule()); - children().add(newIndex, new chatRuleConfigEntry(newIndex)); + children().add(newIndex + 1, new chatRuleConfigEntry(newIndex)); } - protected boolean removeEntry(chatRuleConfigEntry entry) { + protected boolean removeEntry(AbstractChatRuleEntry entry) { hasChanged = true; return super.removeEntry(entry); } @@ -67,28 +66,52 @@ public class ChatRulesConfigListWidget extends ElementListWidget<ChatRulesConfig protected boolean hasChanges(){ - return (hasChanged || children().stream().anyMatch(chatRuleConfigEntry::hasChange)); + return (hasChanged || children().stream().filter(chatRuleConfigEntry.class::isInstance).map(chatRuleConfigEntry.class::cast).anyMatch(chatRuleConfigEntry::isChange)); + } + + protected static abstract class AbstractChatRuleEntry extends Entry<ChatRulesConfigListWidget.AbstractChatRuleEntry> { } - public class chatRuleConfigEntry extends Entry<chatRuleConfigEntry> { + private class chatRuleLabelsEntry extends AbstractChatRuleEntry { + + @Override + public List<? extends Selectable> selectableChildren() { + return List.of(); + } - private static final int SPACING = 20; + @Override + public List<? extends Element> children() { + return List.of(); + } + + @Override + public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + context.drawCenteredTextWithShadow(client.textRenderer, Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleName"), width / 2 - 125, y + 5, 0xFFFFFF); + context.drawCenteredTextWithShadow(client.textRenderer, Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleEnabled"), width / 2, y + 5, 0xFFFFFF); + context.drawCenteredTextWithShadow(client.textRenderer, Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.modify"), width / 2 + 100, y + 5, 0xFFFFFF); + } + } + private class chatRuleConfigEntry extends AbstractChatRuleEntry { //data private int chatRuleIndex; private ChatRule chatRule; + private final List<? extends Element> children; - private final List<? extends Element> children; - //widgets - private final ButtonWidget enabledWidget; - private final ButtonWidget openConfigWidget; + //widgets + private final ButtonWidget enabledButton; + private final ButtonWidget openConfigButton; + private final ButtonWidget deleteButton; + //text locations - private final int labelX; - private final int enabledX; + private final int nameX = width / 2 - 125; + + //saved data + private double oldScrollAmount = 0; public chatRuleConfigEntry(int chatRuleIndex) { @@ -96,48 +119,57 @@ public class ChatRulesConfigListWidget extends ElementListWidget<ChatRulesConfig this.chatRule = ChatRulesHandler.chatRuleList.get(chatRuleIndex); //initialize the widgets - int currentX = width / 2 - 160; - labelX = currentX; - currentX += client.textRenderer.getWidth("Rule: \"" + chatRule.getName() + "\""); - currentX += SPACING; //spacer - - enabledX = currentX; - currentX += client.textRenderer.getWidth("Enabled:"); - enabledWidget = ButtonWidget.builder(enabledButtonText() , a -> { + + enabledButton = ButtonWidget.builder(enabledButtonText() , a -> { toggleEnabled(); }) .size(50,20) - .position(currentX,5) + .position(width / 2 - 25,5) .build() ; - currentX += 50; - currentX += SPACING; //spacer - openConfigWidget = ButtonWidget.builder(Text.of("Edit Rule"), a -> { + openConfigButton = ButtonWidget.builder(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.editRule"), a -> { client.setScreen(new ChatRuleConfigScreen(screen, chatRuleIndex)); }) - .size(100,20) - .position(currentX,5) + .size(50,20) + .position(width / 2 + 45,5) .build() ; + deleteButton = ButtonWidget.builder(Text.translatable("selectServer.delete"), a -> { + oldScrollAmount = getScrollAmount(); + client.setScreen(new ConfirmScreen(this::deleteEntry, Text.translatable("skyblocker.shortcuts.deleteQuestion"), Text.translatable("skyblocker.shortcuts.deleteWarning", chatRule.getName()), Text.translatable("selectServer.deleteButton"), ScreenTexts.CANCEL)); + }) + .size(50,20) + .position(width / 2 + 105,5) + .build() + ; + - children = List.of(enabledWidget, openConfigWidget); + children = List.of(enabledButton, openConfigButton, deleteButton); } private Text enabledButtonText() { if (chatRule.getEnabled()){ - return Text.literal("TRUE").withColor(Color.green.getRGB()); + return Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.true").withColor(Color.green.getRGB()); }else { - return Text.literal("FALSE").withColor(Color.red.getRGB()); + return Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen.false").withColor(Color.red.getRGB()); } } private void toggleEnabled() { hasChanged = true; chatRule.setEnabled(!chatRule.getEnabled()); - enabledWidget.setMessage(enabledButtonText()); + enabledButton.setMessage(enabledButtonText()); } - + private void deleteEntry(boolean confirmedAction) { + if (confirmedAction){ + //delete this + ChatRulesHandler.chatRuleList.remove(chatRuleIndex); + removeEntry(this); + } + client.setScreen(screen); + setScrollAmount(oldScrollAmount); + } @Override public List<? extends Selectable> selectableChildren() { @@ -162,16 +194,17 @@ public class ChatRulesConfigListWidget extends ElementListWidget<ChatRulesConfig @Override public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { //todo get strings form en_us.json //widgets - enabledWidget.setY(y); - enabledWidget.render(context, mouseX, mouseY, tickDelta); - openConfigWidget.setY(y); - openConfigWidget.render(context, mouseX, mouseY, tickDelta); + enabledButton.setY(y); + enabledButton.render(context, mouseX, mouseY, tickDelta); + openConfigButton.setY(y); + openConfigButton.render(context, mouseX, mouseY, tickDelta); + deleteButton.setY(y); + deleteButton.render(context, mouseX, mouseY, tickDelta); //text - context.drawTextWithShadow(client.textRenderer, "Rule: \"" + chatRule.getName() + "\"", labelX, y + 5, 0xFFFFFF); - context.drawTextWithShadow(client.textRenderer, "enabled:", enabledX, y + 5, 0xFFFFFF); + context.drawCenteredTextWithShadow(client.textRenderer, chatRule.getName(), nameX, y + 5, 0xFFFFFF); } - public boolean hasChange() { + public boolean isChange() { return (!chatRule.getEnabled().equals(ChatRulesHandler.chatRuleList.get(chatRuleIndex).getEnabled())); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigScreen.java index bbc3f14b..2cbb735b 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigScreen.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesConfigScreen.java @@ -14,10 +14,8 @@ import net.minecraft.text.Text; public class ChatRulesConfigScreen extends Screen { private ChatRulesConfigListWidget chatRulesConfigListWidget; - private ButtonWidget buttonDelete; private ButtonWidget buttonNew; private ButtonWidget buttonDone; - private double scrollAmount; private final Screen parent; public ChatRulesConfigScreen() { @@ -25,7 +23,7 @@ public class ChatRulesConfigScreen extends Screen { } public ChatRulesConfigScreen(Screen parent) { - super(Text.translatable("skyblocker.shortcuts.config")); //todo correct name for whole code + super(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.ruleScreen")); this.parent = parent; } @@ -41,44 +39,21 @@ public class ChatRulesConfigScreen extends Screen { addDrawableChild(chatRulesConfigListWidget); GridWidget gridWidget = new GridWidget(); gridWidget.getMainPositioner().marginX(5).marginY(2); - GridWidget.Adder adder = gridWidget.createAdder(2); - buttonDelete = ButtonWidget.builder(Text.translatable("selectServer.delete"), button -> { - ChatRulesConfigListWidget.chatRuleConfigEntry currentChatRuleConfigEntry = chatRulesConfigListWidget.getSelectedOrNull(); - if (client != null && currentChatRuleConfigEntry != null ) { - scrollAmount = chatRulesConfigListWidget.getScrollAmount(); - client.setScreen(new ConfirmScreen(this::deleteEntry, Text.translatable("skyblocker.shortcuts.deleteQuestion"), Text.translatable("skyblocker.shortcuts.deleteWarning", currentChatRuleConfigEntry), Text.translatable("selectServer.deleteButton"), ScreenTexts.CANCEL)); //todo load text for this config - } - }).build(); - adder.add(buttonDelete); - buttonNew = ButtonWidget.builder(Text.translatable("skyblocker.shortcuts.new"), buttonNew -> chatRulesConfigListWidget.addRuleAfterSelected()).build(); - adder.add(buttonNew); + GridWidget.Adder adder = gridWidget.createAdder(3); adder.add(ButtonWidget.builder(ScreenTexts.CANCEL, button -> { if (client != null) { close(); } }).build()); + buttonNew = ButtonWidget.builder(Text.translatable("text.autoconfig.skyblocker.option.messages.chatRules.screen.new"), buttonNew -> chatRulesConfigListWidget.addRuleAfterSelected()).build(); + adder.add(buttonNew); buttonDone = ButtonWidget.builder(ScreenTexts.DONE, button -> { chatRulesConfigListWidget.saveRules(); if (client != null) { close(); } - }).tooltip(Tooltip.of(Text.translatable("skyblocker.shortcuts.commandSuggestionTooltip"))).build(); - adder.add(buttonDone); - gridWidget.refreshPositions(); - SimplePositioningWidget.setPos(gridWidget, 0, this.height - 64, this.width, 64); + }).build(); gridWidget.forEachChild(this::addDrawableChild); - updateButtons(); - } - - private void deleteEntry(boolean confirmedAction) { - if (client != null) { - ChatRulesConfigListWidget.chatRuleConfigEntry currentChatRuleConfigEntry = chatRulesConfigListWidget.getSelectedOrNull(); - if (confirmedAction && currentChatRuleConfigEntry != null) { - chatRulesConfigListWidget.removeEntry(currentChatRuleConfigEntry); - } - client.setScreen(this); // Re-inits the screen and keeps the old instance of ShortcutsConfigListWidget - chatRulesConfigListWidget.setScrollAmount(scrollAmount); - } } @Override @@ -101,8 +76,4 @@ public class ChatRulesConfigScreen extends Screen { this.client.setScreen(parent); } } - - protected void updateButtons() { - buttonDelete.active = chatRulesConfigListWidget.getSelectedOrNull() != null; - } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesHandler.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesHandler.java index f8823589..29ee1386 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesHandler.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ChatRulesHandler.java @@ -1,16 +1,12 @@ package de.hysky.skyblocker.skyblock.chat; -import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; import de.hysky.skyblocker.SkyblockerMod; -import de.hysky.skyblocker.skyblock.shortcut.Shortcuts; import de.hysky.skyblocker.utils.Utils; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; -import net.fabricmc.fabric.api.client.message.v1.ClientSendMessageEvents; import net.minecraft.client.MinecraftClient; +import net.minecraft.sound.SoundEvents; import net.minecraft.text.Text; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,7 +25,7 @@ import java.util.Map; public class ChatRulesHandler { private static final MinecraftClient CLIENT = MinecraftClient.getInstance(); private static final Logger LOGGER = LoggerFactory.getLogger(ChatRule.class); - private static final Path CHAT_RULE_FILE = SkyblockerMod.CONFIG_DIR.resolve("chatRules.json"); + private static final Path CHAT_RULE_FILE = SkyblockerMod.CONFIG_DIR.resolve("chat_rules.json"); protected static final List<ChatRule> chatRuleList = new ArrayList<>(); @@ -74,27 +70,36 @@ public class ChatRulesHandler { if (!Utils.isOnSkyblock()) return true; //do not work not on skyblock if (overlay) return true; //ignore messages in overlay String plain = trimItemColor(message.getString()); - for (ChatRule rule : chatRuleList) { + for (ChatRule rule : chatRuleList) { if (rule.isMatch(plain)) { //get a replacement message Text newMessage; - if (!rule.getReplaceMessage().isEmpty()){ + if (!rule.getReplaceMessage().isBlank()) { newMessage = Text.of(rule.getReplaceMessage()); } else { - newMessage =message; + newMessage = message; } - //todo show announcement + if (rule.getShowAnnouncement()) { + ChatRuleAnnouncementScreen.setText(newMessage); + } //show in action bar if (rule.getShowActionBar() && CLIENT.player != null) { CLIENT.player.sendMessage(newMessage, true); } + //hide message - if (!rule.getHideMessage() && CLIENT.player != null){ + if (!rule.getHideMessage() && CLIENT.player != null) { CLIENT.player.sendMessage(newMessage, false); } + + //play sound + if (rule.getCustomSound() != null && CLIENT.player != null) { + CLIENT.player.playSound(rule.getCustomSound(), 100f, 0.1f); + } + //do not send original message return false; } @@ -102,7 +107,7 @@ public class ChatRulesHandler { return true; } private static String trimItemColor(String str) { - if (str.isEmpty()) return str; + if (str.isBlank()) return str; return str.replaceAll("§[0-9a-g]", ""); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/Boulder.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/Boulder.java new file mode 100644 index 00000000..878c8d0a --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/Boulder.java @@ -0,0 +1,221 @@ +package de.hysky.skyblocker.skyblock.dungeon.puzzle.boulder; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.dungeon.puzzle.DungeonPuzzle; +import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; +import de.hysky.skyblocker.skyblock.dungeon.secrets.Room; +import de.hysky.skyblocker.utils.render.RenderHelper; +import de.hysky.skyblocker.utils.render.title.Title; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.util.DyeColor; +import net.minecraft.util.Formatting; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.BlockView; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.List; + +public class Boulder extends DungeonPuzzle { + private static final Logger LOGGER = LoggerFactory.getLogger(Boulder.class.getName()); + private static final Boulder INSTANCE = new Boulder(); + private static final float[] RED_COLOR_COMPONENTS = DyeColor.RED.getColorComponents(); + private static final float[] ORANGE_COLOR_COMPONENTS = DyeColor.ORANGE.getColorComponents(); + private static final int BASE_Y = 65; + static Vec3d[] linePoints; + static Box boundingBox; + + private Boulder() { + super("boulder", "boxes-room"); + } + + public static void init() { + } + + @Override + public void tick(MinecraftClient client) { + + if (!shouldSolve() || !SkyblockerConfigManager.get().locations.dungeons.solveBoulder || client.world == null || !DungeonManager.isCurrentRoomMatched()) { + return; + } + + Room room = DungeonManager.getCurrentRoom(); + + BlockPos chestPos = new BlockPos(15, BASE_Y, 29); + BlockPos start = new BlockPos(25, BASE_Y, 25); + BlockPos end = new BlockPos(5, BASE_Y, 8); + // Create a target BoulderObject for the puzzle + BoulderObject target = new BoulderObject(chestPos.getX(), chestPos.getX(), chestPos.getZ(), "T"); + // Create a BoulderBoard representing the puzzle's grid + BoulderBoard board = new BoulderBoard(8, 7, target); + + // Populate the BoulderBoard grid with BoulderObjects based on block types in the room + int column = 1; + for (int z = start.getZ(); z > end.getZ(); z--) { + int row = 0; + for (int x = start.getX(); x > end.getX(); x--) { + if (Math.abs(start.getX() - x) % 3 == 1 && Math.abs(start.getZ() - z) % 3 == 1) { + String blockType = getBlockType(client.world, x, BASE_Y, z); + board.placeObject(column, row, new BoulderObject(x, BASE_Y, z, blockType)); + row++; + } + } + if (row == board.getWidth()) { + column++; + } + } + + // Generate initial game states for the A* solver + char[][] boardArray = board.getBoardCharArray(); + List<BoulderSolver.GameState> initialStates = Arrays.asList( + new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 0), + new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 1), + new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 2), + new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 3), + new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 4), + new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 5), + new BoulderSolver.GameState(boardArray, board.getHeight() - 1, 6) + ); + + // Solve the puzzle using the A* algorithm + List<int[]> solution = BoulderSolver.aStarSolve(initialStates); + + if (solution != null) { + linePoints = new Vec3d[solution.size()]; + int index = 0; + // Convert solution coordinates to Vec3d points for rendering + for (int[] coord : solution) { + int x = coord[0]; + int y = coord[1]; + // Convert relative coordinates to actual coordinates + linePoints[index++] = Vec3d.ofCenter(room.relativeToActual(board.getObject3DPosition(x, y))); + } + + BlockPos button = null; + if (linePoints != null && linePoints.length > 0) { + // Check for buttons along the path of the solution + for (int i = 0; i < linePoints.length - 1; i++) { + Vec3d point1 = linePoints[i]; + Vec3d point2 = linePoints[i + 1]; + button = checkForButtonBlocksOnLine(client.world, point1, point2); + if (button != null) { + // If a button is found, calculate its bounding box + boundingBox = getBlockBoundingBox(client.world, button); + break; + } + } + if (button == null){ + // If no button is found along the path the puzzle is solved; reset the puzzle + reset(); + } + } + } else { + // If no solution is found, display a title message and reset the puzzle + Title title = new Title("skyblocker.dungeons.puzzle.boulder.noSolution", Formatting.GREEN); + RenderHelper.displayInTitleContainerAndPlaySound(title, 15); + reset(); + } + } + + /** + * Retrieves the type of block at the specified position in the world. + * If the block is Birch or Jungle plank, it will return "B"; otherwise, it will return ".". + * + * @param world The client world. + * @param x The x-coordinate of the block. + * @param y The y-coordinate of the block. + * @param z The z-coordinate of the block. + * @return The type of block at the specified position. + */ + public static String getBlockType(ClientWorld world, int x, int y, int z) { + Block block = world.getBlockState(DungeonManager.getCurrentRoom().relativeToActual(new BlockPos(x, y, z))).getBlock(); + return (block == Blocks.BIRCH_PLANKS || block == Blocks.JUNGLE_PLANKS) ? "B" : "."; + } + + /** + * Checks for blocks along the line between two points in the world. + * Returns the position of a block if it found a button on the line, if any. + * + * @param world The client world. + * @param point1 The starting point of the line. + * @param point2 The ending point of the line. + * @return The position of the block found on the line, or null if no block is found. + */ + private static BlockPos checkForButtonBlocksOnLine(ClientWorld world, Vec3d point1, Vec3d point2) { + double x1 = point1.getX(); + double y1 = point1.getY() + 1; + double z1 = point1.getZ(); + + double x2 = point2.getX(); + double y2 = point2.getY() + 1; + double z2 = point2.getZ(); + + int steps = (int) Math.max(Math.abs(x2 - x1), Math.max(Math.abs(y2 - y1), Math.abs(z2 - z1))); + + double xStep = (x2 - x1) / steps; + double yStep = (y2 - y1) / steps; + double zStep = (z2 - z1) / steps; + + + for (int step = 0; step <= steps; step++) { + double currentX = x1 + step * xStep; + double currentY = y1 + step * yStep; + double currentZ = z1 + step * zStep; + + BlockPos blockPos = BlockPos.ofFloored(currentX, currentY, currentZ); + Block block = world.getBlockState(blockPos).getBlock(); + + if (block == Blocks.STONE_BUTTON) { + return blockPos; + } + + } + return null; + } + + /** + * Retrieves the bounding box of a block in the world. + * + * @param world The client world. + * @param pos The position of the block. + * @return The bounding box of the block. + */ + public static Box getBlockBoundingBox(BlockView world, BlockPos pos) { + BlockState blockState = world.getBlockState(pos); + return blockState.getOutlineShape(world, pos).getBoundingBox().offset(pos); + } + + @Override + public void render(WorldRenderContext context) { + if (!shouldSolve() || !SkyblockerConfigManager.get().locations.dungeons.solveBoulder || !DungeonManager.isCurrentRoomMatched()) + return; + float alpha = 1.0f; + float lineWidth = 5.0f; + + if (linePoints != null && linePoints.length > 0) { + for (int i = 0; i < linePoints.length - 1; i++) { + Vec3d startPoint = linePoints[i]; + Vec3d endPoint = linePoints[i + 1]; + RenderHelper.renderLinesFromPoints(context, new Vec3d[]{startPoint, endPoint}, ORANGE_COLOR_COMPONENTS, alpha, lineWidth, true); + } + if (boundingBox != null) { + RenderHelper.renderOutline(context, boundingBox, RED_COLOR_COMPONENTS, 5, false); + } + } + } + + @Override + public void reset() { + super.reset(); + linePoints = null; + boundingBox = null; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderBoard.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderBoard.java new file mode 100644 index 00000000..d29097e4 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderBoard.java @@ -0,0 +1,129 @@ +package de.hysky.skyblocker.skyblock.dungeon.puzzle.boulder; + +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; + +/** + * Represents the game board for the Boulder puzzle, managing the grid of BoulderObjects. + * This class handles operations such as placing objects on the board, retrieving objects, + * and generating a character representation of the game board. + */ +public class BoulderBoard { + private final int height; + private final int width; + private final BoulderObject[][] grid; + + /** + * Constructs a BoulderBoard with the specified height, width, and target BoulderObject. + * + * @param height The height of the board. + * @param width The width of the board. + * @param target The target BoulderObject that needs to be reached to solve the puzzle. + */ + public BoulderBoard(int height, int width, BoulderObject target) { + this.height = height; + this.width = width; + this.grid = new BoulderObject[height][width]; + + int offsetX = target.x() - 23; + int y = 65; + + for (int z = 0; z < width; z++) { + if (z == width / 2) { + grid[0][z] = target; + } else { + grid[0][z] = new BoulderObject(offsetX, y, z, "B"); + } + grid[height - 1][z] = new BoulderObject(24 - (3 * z), y, 6, "P"); + } + } + + /** + * Retrieves the BoulderObject at the specified position on the board. + * + * @param x The x-coordinate of the position. + * @param y The y-coordinate of the position. + * @return The BoulderObject at the specified position, or null if no object is present. + */ + public BoulderObject getObjectAtPosition(int x, int y) { + return isValidPosition(x, y) ? grid[x][y] : null; + } + + /** + * Retrieves the 3D position of the BoulderObject at the specified position on the board. + * + * @param x The x-coordinate of the position. + * @param y The y-coordinate of the position. + * @return The BlockPos representing the 3D position of the BoulderObject, + * or null if no object is present at the specified position. + */ + public BlockPos getObject3DPosition(int x, int y) { + BoulderObject object = getObjectAtPosition(x, y); + return (object != null) ? object.get3DPosition().offset(Direction.Axis.Y, -1) : null; + } + + /** + * Places a BoulderObject at the specified position on the board. + * + * @param x The x-coordinate of the position. + * @param y The y-coordinate of the position. + * @param object The BoulderObject to place on the board. + */ + public void placeObject(int x, int y, BoulderObject object) { + grid[x][y] = object; + } + + public int getHeight() { + return height; + } + + public int getWidth() { + return width; + } + + /** + * Checks whether the specified position is valid within the bounds of the game board. + * + * @param x The x-coordinate of the position to check. + * @param y The y-coordinate of the position to check. + * @return {@code true} if the position is valid within the bounds of the board, {@code false} otherwise. + */ + private boolean isValidPosition(int x, int y) { + return x >= 0 && y >= 0 && x < height && y < width; + } + + /** + * Generates a character array representation of the game board. + * Each character represents a type of BoulderObject or an empty space. + * + * @return A 2D character array representing the game board. + */ + public char[][] getBoardCharArray() { + char[][] boardCharArray = new char[height][width]; + for (int x = 0; x < height; x++) { + for (int y = 0; y < width; y++) { + BoulderObject boulderObject = grid[x][y]; + boardCharArray[x][y] = (boulderObject != null) ? boulderObject.type().charAt(0) : '.'; + } + } + return boardCharArray; + } + + /** + * Prints the current state of the game board to the console. + * Each character represents a type of BoulderObject or an empty space. + */ + public String boardToString() { + StringBuilder sb = new StringBuilder(); + for (int x = 0; x < height; x++) { + for (int y = 0; y < width; y++) { + BoulderObject boulderObject = grid[x][y]; + String displayChar = (boulderObject != null) ? boulderObject.type() : "."; + sb.append(displayChar); + } + sb.append("\n"); + } + return sb.toString(); + } + +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderObject.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderObject.java new file mode 100644 index 00000000..645dd456 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderObject.java @@ -0,0 +1,9 @@ +package de.hysky.skyblocker.skyblock.dungeon.puzzle.boulder; + +import net.minecraft.util.math.BlockPos; + +public record BoulderObject(int x, int y, int z, String type) { + public BlockPos get3DPosition() { + return new BlockPos(x, y, z); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderSolver.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderSolver.java new file mode 100644 index 00000000..f407ce8a --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/puzzle/boulder/BoulderSolver.java @@ -0,0 +1,202 @@ +package de.hysky.skyblocker.skyblock.dungeon.puzzle.boulder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.PriorityQueue; +import java.util.Set; +import it.unimi.dsi.fastutil.Pair; + +/** + * A utility class that provides methods to solve the Boulder puzzle using the A* search algorithm. + * The BoulderSolver class is responsible for finding the shortest path from the starting position + * to the target position by exploring possible moves and evaluating their costs. + */ +public class BoulderSolver { + + /** + * Finds the shortest path to solve the Boulder puzzle using the A* search algorithm. + * + * @param initialStates The list of initial game states from which to start the search. + * @return A list of coordinates representing the shortest path to solve the puzzle, + * or null if no solution is found within the maximum number of iterations. + */ + public static List<int[]> aStarSolve(List<GameState> initialStates) { + Set<GameState> visited = new HashSet<>(); + PriorityQueue<Pair<GameState, List<int[]>>> queue = new PriorityQueue<>(new AStarComparator()); + + for (GameState initialState : initialStates) { + queue.add(Pair.of(initialState, new ArrayList<>())); + } + + int maxIterations = 10000; + int iterations = 0; + + while (!queue.isEmpty() && iterations < maxIterations) { + Pair<GameState, List<int[]>> pair = queue.poll(); + GameState state = pair.left(); + List<int[]> path = pair.right(); + + if (state.isSolved()) { + return path; + } + + if (visited.contains(state)) { + continue; + } + visited.add(state); + + int[] currentCoord = {state.playerX, state.playerY}; + path.add(currentCoord); + + for (int[] direction : new int[][]{{-1, 0}, {0, -1}, {0, 1}, {1, 0}}) { + GameState newState = new GameState(state.grid, state.playerX, state.playerY); + if (newState.movePlayer(direction[0], direction[1])) { + queue.add(Pair.of(newState, new ArrayList<>(path))); + } + } + iterations++; + } + + return null; + } + + /** + * A comparator used to compare game states based on their A* search cost. + * States with lower costs are prioritized for exploration. + */ + private static class AStarComparator implements Comparator<Pair<GameState, List<int[]>>> { + /** + * Compares two pairs of game states and their associated paths based on their costs. + * + * @param a The first pair to compare. + * @param b The second pair to compare. + * @return A negative integer if a has a lower cost than b, + * a positive integer if a has a higher cost than b, + * or zero if both have the same cost. + */ + @Override + public int compare(Pair<GameState, List<int[]>> a, Pair<GameState, List<int[]>> b) { + int costA = a.right().size() + a.left().heuristic(); + int costB = b.right().size() + b.left().heuristic(); + return Integer.compare(costA, costB); + } + } + + /** + * Represents the game state for the Boulder puzzle, including the current grid configuration + * and the position of the theoretical player. + */ + public static class GameState { + private final char[][] grid; + private int playerX; + private int playerY; + + /** + * Constructs a new game state with the specified grid and theoretical player position. + * + * @param grid The grid representing the Boulder puzzle configuration. + * @param playerX The x-coordinate of the player's position. + * @param playerY The y-coordinate of the player's position. + */ + public GameState(char[][] grid, int playerX, int playerY) { + this.grid = copyGrid(grid); + this.playerX = playerX; + this.playerY = playerY; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + GameState gameState = (GameState) obj; + return Arrays.deepEquals(grid, gameState.grid) && playerX == gameState.playerX && playerY == gameState.playerY; + } + + @Override + public int hashCode() { + int result = Arrays.deepHashCode(grid); + result = 31 * result + playerX; + result = 31 * result + playerY; + return result; + } + + /** + * Moves the theoretical player in the specified direction and updates the game state accordingly. + * + * @param dx The change in x-coordinate (horizontal movement). + * @param dy The change in y-coordinate (vertical movement). + * @return true if the move is valid and the player is moved, false otherwise. + */ + public boolean movePlayer(int dx, int dy) { + int newX = playerX + dx; + int newY = playerY + dy; + + if (isValidPosition(newX, newY)) { + if (grid[newX][newY] == 'B') { + int nextToBoxX = newX + dx; + int nextToBoxY = newY + dy; + if (isValidPosition(nextToBoxX, nextToBoxY) && grid[nextToBoxX][nextToBoxY] == '.') { + grid[newX][newY] = '.'; + grid[nextToBoxX][nextToBoxY] = 'B'; + playerX = newX; + playerY = newY; + return true; + } + } else { + playerX = newX; + playerY = newY; + return true; + } + } + return false; + } + + private boolean isValidPosition(int x, int y) { + return x >= 0 && y >= 0 && x < grid.length && y < grid[0].length; + } + + /** + * Checks if the puzzle is solved, i.e., if the player is positioned on the target BoulderObject. + * + * @return true if the theoretical puzzle is solved, false otherwise. + */ + public boolean isSolved() { + return grid[playerX][playerY] == 'T'; + } + + /** + * Calculates the heuristic value for the current game state, representing the estimated + * distance from the player's position to the target BoulderObject. + * + * @return The heuristic value for the current game state. + */ + public int heuristic() { + // should be improved maybe prioritize empty path first + for (int i = 0; i < grid.length; i++) { + for (int j = 0; j < grid[0].length; j++) { + if (grid[i][j] == 'T') { + return Math.abs(playerX - i) + Math.abs(playerY - j); + } + } + } + return Integer.MAX_VALUE; + } + + /** + * Creates a deep copy of the grid array to avoid modifying the original grid. + * + * @param original The original grid array to copy. + * @return A deep copy of the original grid array. + */ + private char[][] copyGrid(char[][] original) { + char[][] copy = new char[original.length][]; + for (int i = 0; i < original.length; i++) { + copy[i] = original[i].clone(); + } + return copy; + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java index 08dcde7d..5913a3c6 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/DwarvenHud.java @@ -37,7 +37,7 @@ public class DwarvenHud { "(?:Lava Springs|Cliffside Veins|Rampart's Quarry|Upper Mines|Royal Mines) Mithril", "(?:Lava Springs|Cliffside Veins|Rampart's Quarry|Upper Mines|Royal Mines) Titanium", "Goblin Raid", - "(?:Powder Ghast|Star Sentry) Puncher", + "(?:Powder Ghast|Star Sentry|Treasure Hoarder) Puncher", "(?<!Lucky )Raffle", "Lucky Raffle", "2x Mithril Powder Collector", diff --git a/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudConfigScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudConfigScreen.java new file mode 100644 index 00000000..2502afd7 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudConfigScreen.java @@ -0,0 +1,35 @@ +package de.hysky.skyblocker.skyblock.end; + +import de.hysky.skyblocker.config.HudConfigScreen; +import de.hysky.skyblocker.config.SkyblockerConfig; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.Text; + +public class EndHudConfigScreen extends HudConfigScreen { + public EndHudConfigScreen(Screen parent) { + super(Text.literal("End HUD Config"), EndHudWidget.INSTANCE, parent); + } + + @Override + protected int[] getPosFromConfig(SkyblockerConfig config) { + return new int[]{ + config.locations.end.x, + config.locations.end.y, + }; + } + + @Override + protected void savePos(SkyblockerConfig configManager, int x, int y) { + configManager.locations.end.x = x; + configManager.locations.end.y = y; + } + + @Override + protected void renderWidget(DrawContext context, int x, int y) { + EndHudWidget.INSTANCE.setX(x); + EndHudWidget.INSTANCE.setY(y); + EndHudWidget.INSTANCE.render(context, SkyblockerConfigManager.get().locations.end.enableBackground); + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudWidget.java new file mode 100644 index 00000000..59a637dd --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/end/EndHudWidget.java @@ -0,0 +1,65 @@ +package de.hysky.skyblocker.skyblock.end; + +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.tabhud.widget.Widget; +import de.hysky.skyblocker.skyblock.tabhud.widget.component.IcoTextComponent; +import de.hysky.skyblocker.skyblock.tabhud.widget.component.PlainTextComponent; +import net.minecraft.enchantment.Enchantments; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.text.NumberFormat; + +public class EndHudWidget extends Widget { + private static final MutableText TITLE = Text.literal("The End").formatted(Formatting.LIGHT_PURPLE, Formatting.BOLD); + + public static final EndHudWidget INSTANCE = new EndHudWidget(TITLE, Formatting.DARK_PURPLE.getColorValue()); + + public EndHudWidget(MutableText title, Integer colorValue) { + super(title, colorValue); + this.setX(5); + this.setY(5); + this.update(); + } + + private static final ItemStack ENDERMAN_HEAD = new ItemStack(Items.PLAYER_HEAD); + private static final ItemStack POPPY = new ItemStack(Items.POPPY); + + static { + ENDERMAN_HEAD.getOrCreateNbt().putString("SkullOwner", "MHF_Enderman"); + POPPY.addEnchantment(Enchantments.INFINITY, 1); + + INSTANCE.setX(SkyblockerConfigManager.get().locations.end.x); + INSTANCE.setY(SkyblockerConfigManager.get().locations.end.y); + } + + + @Override + public void updateContent() { + // Zealots + addComponent(new IcoTextComponent(ENDERMAN_HEAD, Text.literal("Zealots").formatted(Formatting.BOLD))); + addComponent(new PlainTextComponent(Text.translatable("skyblocker.end.hud.zealotsSinceLastEye", TheEnd.zealotsSinceLastEye))); + addComponent(new PlainTextComponent(Text.translatable("skyblocker.end.hud.zealotsTotalKills", TheEnd.zealotsKilled))); + NumberFormat instance = NumberFormat.getInstance(); + instance.setMinimumFractionDigits(0); + instance.setMaximumFractionDigits(2); + String avg = TheEnd.eyes == 0 ? "???" : instance.format((float)TheEnd.zealotsKilled / TheEnd.eyes); + addComponent(new PlainTextComponent(Text.translatable("skyblocker.end.hud.avgKillsPerEye", avg))); + + // Endstone protector + addComponent(new IcoTextComponent(POPPY, Text.literal("Endstone Protector").formatted(Formatting.BOLD))); + if (TheEnd.stage == 5) { + addComponent(new PlainTextComponent(Text.translatable("skyblocker.end.hud.stage", "IMMINENT"))); + } else { + addComponent(new PlainTextComponent(Text.translatable("skyblocker.end.hud.stage", String.valueOf(TheEnd.stage)))); + } + if (TheEnd.currentProtectorLocation == null) { + addComponent(new PlainTextComponent(Text.translatable("skyblocker.end.hud.location", "?"))); + } else { + addComponent(new PlainTextComponent(Text.translatable("skyblocker.end.hud.location", TheEnd.currentProtectorLocation.name()))); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/end/TheEnd.java b/src/main/java/de/hysky/skyblocker/skyblock/end/TheEnd.java new file mode 100644 index 00000000..1db27769 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/end/TheEnd.java @@ -0,0 +1,249 @@ +package de.hysky.skyblocker.skyblock.end; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.waypoint.Waypoint; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientChunkEvents; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; +import net.fabricmc.fabric.api.event.player.AttackEntityCallback; +import net.minecraft.block.Blocks; +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.Entity; +import net.minecraft.entity.decoration.ArmorStandEntity; +import net.minecraft.entity.mob.EndermanEntity; +import net.minecraft.text.Text; +import net.minecraft.util.ActionResult; +import net.minecraft.util.DyeColor; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.ChunkPos; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +public class TheEnd { + protected static final Logger LOGGER = LoggerFactory.getLogger(TheEnd.class); + + public static Set<UUID> hitZealots = new HashSet<>(); + public static int zealotsSinceLastEye = 0; + public static int zealotsKilled = 0; + public static int eyes = 0; + /** + * needs to be saved? + */ + private static boolean dirty = false; + private static String currentProfile = ""; + private static JsonObject PROFILES_STATS; + + private static final Path FILE = SkyblockerMod.CONFIG_DIR.resolve("end.json"); + public static List<ProtectorLocation> protectorLocations = List.of( + new ProtectorLocation(-649, -219, Text.translatable("skyblocker.end.hud.protectorLocations.left")), + new ProtectorLocation(-644, -269, Text.translatable("skyblocker.end.hud.protectorLocations.front")), + new ProtectorLocation(-689, -273, Text.translatable("skyblocker.end.hud.protectorLocations.center")), + new ProtectorLocation(-727, -284, Text.translatable("skyblocker.end.hud.protectorLocations.back")), + new ProtectorLocation(-639, -328, Text.translatable("skyblocker.end.hud.protectorLocations.rightFront")), + new ProtectorLocation(-678, -332, Text.translatable("skyblocker.end.hud.protectorLocations.rightBack")) + ); + + public static ProtectorLocation currentProtectorLocation = null; + public static int stage = 0; + + public static void init() { + AttackEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> { + if (entity instanceof EndermanEntity enderman && isZealot(enderman)) { + hitZealots.add(enderman.getUuid()); + } + return ActionResult.PASS; + }); + + + HudRenderCallback.EVENT.register((drawContext, tickDelta) -> { + if (!Utils.isInTheEnd()) return; + if (!SkyblockerConfigManager.get().locations.end.hudEnabled) return; + + EndHudWidget.INSTANCE.render(drawContext, SkyblockerConfigManager.get().locations.end.enableBackground); + }); + + ClientChunkEvents.CHUNK_LOAD.register((world, chunk) -> { + String lowerCase = Utils.getIslandArea().toLowerCase(); + if (Utils.isInTheEnd() || lowerCase.contains("the end") || lowerCase.contains("dragon's nest")) { + ChunkPos pos = chunk.getPos(); + // + Box box = new Box(pos.getStartX(), 0, pos.getStartZ(), pos.getEndX(), 1, pos.getEndZ()); + locationsLoop: for (ProtectorLocation protectorLocation : protectorLocations) { + if (box.contains(protectorLocation.x, 0.5, protectorLocation.z)) { + //MinecraftClient.getInstance().player.sendMessage(Text.literal("Checking: ").append(protectorLocation.name));//MinecraftClient.getInstance().player.sendMessage(Text.literal(pos.getStartX() + " " + pos.getStartZ() + " " + pos.getEndX() + " " + pos.getEndZ())); + for (int i = 0; i < 5; i++) { + if (world.getBlockState(new BlockPos(protectorLocation.x, i+5, protectorLocation.z)).isOf(Blocks.PLAYER_HEAD)) { + stage = i + 1; + currentProtectorLocation = protectorLocation; + EndHudWidget.INSTANCE.update(); + break locationsLoop; + } + } + } + } + if (currentProfile.isEmpty()) load(); // Wacky fix for when you join skyblock, and you are directly in the end (profile id isn't parsed yet most of the time) + } + + + }); + // Reset when changing island + // TODO: Replace when a changed island event is added + ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> { + resetLocation(); + save(); + load(); + }); + // Save when leaving as well + ClientLifecycleEvents.CLIENT_STOPPING.register((client) -> save()); + + ClientReceiveMessageEvents.GAME.register((message, overlay) -> { + if (Utils.isInTheEnd()) return; + String lowerCase = message.getString().toLowerCase(); + if (lowerCase.contains("tremor") && stage != 0) stage += 1; // TODO: If stage is 0 re-scan. + else if (lowerCase.contains("rises from below")) stage = 5; + else if (lowerCase.contains("protector down") || lowerCase.contains("has risen")) resetLocation(); + else return; + EndHudWidget.INSTANCE.update(); + }); + + WorldRenderEvents.AFTER_TRANSLUCENT.register(TheEnd::renderWaypoint); + ClientLifecycleEvents.CLIENT_STARTED.register((client -> loadFile())); + } + + private static void resetLocation() { + stage = 0; + currentProtectorLocation = null; + } + + public static void onEntityDeath(Entity entity) { + if (!(entity instanceof EndermanEntity enderman) || !isZealot(enderman)) return; + if (hitZealots.contains(enderman.getUuid())) { + //MinecraftClient.getInstance().player.sendMessage(Text.literal("You killed a zealot!!!")); + if (isSpecialZealot(enderman)) { + zealotsSinceLastEye = 0; + eyes++; + } + else zealotsSinceLastEye++; + zealotsKilled++; + dirty = true; + hitZealots.remove(enderman.getUuid()); + EndHudWidget.INSTANCE.update(); + } + } + + public static boolean isZealot(EndermanEntity enderman) { + if (enderman.getName().getString().toLowerCase().contains("zealot")) return true; // Future-proof. If they someday decide to actually rename the entities + assert MinecraftClient.getInstance().world != null; + List<ArmorStandEntity> entities = MinecraftClient.getInstance().world.getEntitiesByClass( + ArmorStandEntity.class, + enderman.getDimensions(null).getBoxAt(enderman.getPos()).expand(1), + armorStandEntity -> armorStandEntity.getName().getString().toLowerCase().contains("zealot")); + if (entities.isEmpty()) { + return false; + } + return entities.get(0).getName().getString().toLowerCase().contains("zealot"); + } + + public static boolean isSpecialZealot(EndermanEntity enderman) { + return isZealot(enderman) && enderman.getCarriedBlock() != null && enderman.getCarriedBlock().isOf(Blocks.END_PORTAL_FRAME); + } + + /** + * Loads if needed + */ + public static void load() { + if (!Utils.isOnSkyblock() || Utils.getProfileId().isEmpty()) return; + String id = MinecraftClient.getInstance().getSession().getUuidOrNull().toString().replaceAll("-", ""); + String profile = Utils.getProfileId(); + if (!profile.equals(currentProfile) && PROFILES_STATS != null) { + currentProfile = profile; + JsonElement jsonElement = PROFILES_STATS.get(id); + if (jsonElement == null) return; + JsonElement jsonElement1 = jsonElement.getAsJsonObject().get(profile); + if (jsonElement1 == null) return; + zealotsKilled = jsonElement1.getAsJsonObject().get("totalZealotKills").getAsInt(); + zealotsSinceLastEye = jsonElement1.getAsJsonObject().get("zealotsSinceLastEye").getAsInt(); + eyes = jsonElement1.getAsJsonObject().get("eyes").getAsInt(); + EndHudWidget.INSTANCE.update(); + } + } + + private static void loadFile() { + CompletableFuture.runAsync(() -> { + try (BufferedReader reader = Files.newBufferedReader(FILE)) { + PROFILES_STATS = SkyblockerMod.GSON.fromJson(reader, JsonObject.class); + LOGGER.debug("[Skyblocker End] Loaded end stats"); + } catch (NoSuchFileException ignored) { + PROFILES_STATS = new JsonObject(); + } catch (Exception e) { + LOGGER.error("[Skyblocker End] Failed to load end stats", e); + } + }); + } + + /** + * Saves if dirty + */ + public static void save() { + if (dirty && PROFILES_STATS != null) { + String uuid = MinecraftClient.getInstance().getSession().getUuidOrNull().toString().replaceAll("-", ""); + JsonObject jsonObject = PROFILES_STATS.getAsJsonObject(uuid); + if (jsonObject == null) { + PROFILES_STATS.add(uuid, new JsonObject()); + jsonObject = PROFILES_STATS.getAsJsonObject(uuid); + } + + jsonObject.add(currentProfile, new JsonObject()); + JsonElement jsonElement1 = jsonObject.get(currentProfile); + + jsonElement1.getAsJsonObject().addProperty("totalZealotKills", zealotsKilled); + jsonElement1.getAsJsonObject().addProperty("zealotsSinceLastEye", zealotsSinceLastEye); + jsonElement1.getAsJsonObject().addProperty("eyes", eyes); + + if (Utils.isOnSkyblock()) { + CompletableFuture.runAsync(TheEnd::performSave); + } else { + performSave(); + } + } + } + + private static void performSave() { + try (BufferedWriter writer = Files.newBufferedWriter(FILE)) { + SkyblockerMod.GSON.toJson(PROFILES_STATS, writer); + LOGGER.info("[Skyblocker End] Saved end stats"); + dirty = false; + } catch (Exception e) { + LOGGER.error("[Skyblocker End] Failed to save end stats", e); + } + } + + private static void renderWaypoint(WorldRenderContext context) { + if (!SkyblockerConfigManager.get().locations.end.waypoint) return; + if (currentProtectorLocation == null || stage != 5) return; + currentProtectorLocation.waypoint().render(context); + } + + public record ProtectorLocation(int x, int z, Text name, Waypoint waypoint) { + public ProtectorLocation(int x, int z, Text name) { + this(x, z, name, new Waypoint(new BlockPos(x, 0, z), Waypoint.Type.WAYPOINT, DyeColor.MAGENTA.getColorComponents())); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java index d0d58606..90513a4b 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/entity/MobGlow.java @@ -1,12 +1,14 @@ package de.hysky.skyblocker.skyblock.entity; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.end.TheEnd; import de.hysky.skyblocker.skyblock.dungeon.LividColor; import de.hysky.skyblocker.utils.SlayerUtils; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.render.culling.OcclusionCulling; import net.minecraft.entity.Entity; import net.minecraft.entity.decoration.ArmorStandEntity; +import net.minecraft.entity.mob.EndermanEntity; import net.minecraft.entity.passive.BatEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; @@ -71,6 +73,9 @@ public class MobGlow { && isNukekubiHead(armorStandEntity); } + // Special Zelot + if (entity instanceof EndermanEntity enderman && TheEnd.isSpecialZealot(enderman)) return true; + return false; } @@ -92,6 +97,7 @@ public class MobGlow { default -> 0xf57738; }; } + if (entity instanceof EndermanEntity enderman && TheEnd.isSpecialZealot(enderman)) return Formatting.RED.getColorValue(); // copypaste nukekebi head logic if (entity instanceof ArmorStandEntity armorStandEntity && isNukekubiHead(armorStandEntity)) return 0x990099; 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 e4e18f8b..19f2e6fd 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 @@ -140,6 +140,18 @@ public class ItemTooltip { } } + if (SkyblockerConfigManager.get().general.dungeonQuality) { + NbtCompound ea = ItemUtils.getExtraAttributes(stack); + if (ea != null && ea.contains("baseStatBoostPercentage")) { + int baseStatBoostPercentage = ea.getInt("baseStatBoostPercentage"); + if (baseStatBoostPercentage == 50) { + lines.add(Text.literal(String.format("%-17s", "Item Quality:") + baseStatBoostPercentage + "/50").formatted(Formatting.RED).formatted(Formatting.BOLD)); + } else { + lines.add(Text.literal(String.format("%-21s", "Item Quality:") + baseStatBoostPercentage + "/50").formatted(Formatting.BLUE)); + } + } + } + if (TooltipInfoType.MOTES.isTooltipEnabledAndHasOrNullWarning(internalID)) { lines.add(Text.literal(String.format("%-20s", "Motes Price:")) .formatted(Formatting.LIGHT_PURPLE) @@ -282,7 +294,6 @@ public class ItemTooltip { return internalName; } - private static Text getCoinsMessage(double price, int count) { // Format the price string once String priceString = String.format(Locale.ENGLISH, "%1$,.1f", price); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/TableComponent.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/TableComponent.java index dbc0bf55..2d813b56 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/TableComponent.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/TableComponent.java @@ -25,11 +25,7 @@ public class TableComponent extends Component { // pad extra to add a vertical line later this.cellW = Math.max(this.cellW, c.width + PAD_S + PAD_L); - - // assume all rows are equally high so overwriting doesn't matter - // if this wasn't the case, drawing would need more math - // not doing any of that if it's not needed - this.cellH = c.height + PAD_S; + this.cellH = Math.max(c.height + PAD_S, cellH); this.width = this.cellW * this.cols; this.height = (this.cellH * this.rows) - PAD_S / 2; @@ -40,8 +36,9 @@ public class TableComponent extends Component { public void render(DrawContext context, int xpos, int ypos) { for (int x = 0; x < cols; x++) { for (int y = 0; y < rows; y++) { - if (comps[x][y] != null) { - comps[x][y].render(context, xpos + (x * cellW), ypos + y * cellH); + Component comp = comps[x][y]; + if (comp != null) { + comp.render(context, xpos + (x * cellW), ypos + y * cellH + (cellH / 2 - comp.height / 2)); } } // add a line before the col if we're not drawing the first one |