diff --git a/.gitmodules b/.gitmodules index ff5e49108..0f76f3979 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,7 @@ [submodule "hypixel-api"] path = hypixel-api url = https://github.com/Skytils/hypixel-api + +[submodule "ws-shared"] + path = ws-shared + url = https://github.com/Skytils/ws-shared \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index d2fa017a0..897fd582b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,7 +34,7 @@ plugins { signing } -version = "1.9.8" +version = "1.10.0-pre9" group = "gg.skytils" repositories { @@ -42,6 +42,7 @@ repositories { mavenCentral() maven("https://repo.sk1er.club/repository/maven-public/") maven("https://repo.sk1er.club/repository/maven-releases/") + maven("https://repo.hypixel.net/repository/Hypixel/") maven("https://jitpack.io") { mavenContent { includeGroupAndSubgroups("com.github") @@ -115,7 +116,7 @@ dependencies { } shadowMe(platform(kotlin("bom"))) - shadowMe(platform(ktor("bom", "2.3.9", addSuffix = false))) + shadowMe(platform(ktor("bom", "2.3.11", addSuffix = false))) shadowMe(ktor("serialization-kotlinx-json")) @@ -147,11 +148,14 @@ dependencies { shadowMe(project(":events")) shadowMe(project(":hypixel-api:types")) + shadowMe(project(":ws-shared")) shadowMe("org.bouncycastle:bcpg-jdk18on:1.78.1") { exclude(module = "bcprov-jdk18on") } compileOnly("org.bouncycastle:bcprov-jdk18on:1.78.1") + shadowMe("net.hypixel:mod-api:0.4.0") + shadowMe(annotationProcessor("io.github.llamalad7:mixinextras-common:0.3.5")!!) annotationProcessor("org.spongepowered:mixin:0.8.5:processor") @@ -218,6 +222,7 @@ tasks { relocate("kotlinx.serialization", "gg.skytils.ktx-serialization") relocate("kotlinx.coroutines", "gg.skytils.ktx-coroutines") relocate("gg.essential.vigilance", "gg.skytils.vigilance") + relocate("net.hypixel", "gg.skytils.hypixel-net") exclude( "**/LICENSE_MixinExtras", diff --git a/settings.gradle.kts b/settings.gradle.kts index 19334a791..427b3e7b8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -37,3 +37,4 @@ pluginManagement { rootProject.name = "SkytilsMod" include("events") include("hypixel-api:types") +include("ws-shared") diff --git a/src/main/java/gg/skytils/skytilsmod/mixins/transformers/accessors/AccessorHypixelModAPI.java b/src/main/java/gg/skytils/skytilsmod/mixins/transformers/accessors/AccessorHypixelModAPI.java new file mode 100644 index 000000000..f8315fb15 --- /dev/null +++ b/src/main/java/gg/skytils/skytilsmod/mixins/transformers/accessors/AccessorHypixelModAPI.java @@ -0,0 +1,36 @@ +/* + * Skytils - Hypixel Skyblock Quality of Life Mod + * Copyright (C) 2020-2024 Skytils + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package gg.skytils.skytilsmod.mixins.transformers.accessors; + +import net.hypixel.modapi.HypixelModAPI; +import net.hypixel.modapi.packet.HypixelPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; + +import java.util.function.Predicate; + +@Mixin(HypixelModAPI.class) +public interface AccessorHypixelModAPI { + @Accessor + Predicate getPacketSender(); + + @Invoker + void invokeSendRegisterPacket(boolean alwaysSendIfNotEmpty); +} diff --git a/src/main/java/gg/skytils/skytilsmod/mixins/transformers/accessors/AccessorHypixelPacketRegistry.java b/src/main/java/gg/skytils/skytilsmod/mixins/transformers/accessors/AccessorHypixelPacketRegistry.java new file mode 100644 index 000000000..4e30a711b --- /dev/null +++ b/src/main/java/gg/skytils/skytilsmod/mixins/transformers/accessors/AccessorHypixelPacketRegistry.java @@ -0,0 +1,32 @@ +/* + * Skytils - Hypixel Skyblock Quality of Life Mod + * Copyright (C) 2020-2024 Skytils + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package gg.skytils.skytilsmod.mixins.transformers.accessors; + +import net.hypixel.modapi.packet.HypixelPacket; +import net.hypixel.modapi.packet.PacketRegistry; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Map; + +@Mixin(PacketRegistry.class) +public interface AccessorHypixelPacketRegistry { + @Accessor + Map, String> getClassToIdentifier(); +} diff --git a/src/main/java/gg/skytils/skytilsmod/mixins/transformers/network/MixinNetworkManager.java b/src/main/java/gg/skytils/skytilsmod/mixins/transformers/network/MixinNetworkManager.java index 0ec81b92c..e7bd3fb28 100644 --- a/src/main/java/gg/skytils/skytilsmod/mixins/transformers/network/MixinNetworkManager.java +++ b/src/main/java/gg/skytils/skytilsmod/mixins/transformers/network/MixinNetworkManager.java @@ -19,6 +19,7 @@ package gg.skytils.skytilsmod.mixins.transformers.network; import gg.skytils.skytilsmod.mixins.hooks.network.NetworkManagerHookKt; +import gg.skytils.skytilsmod.utils.Utils; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import net.minecraft.network.EnumPacketDirection; diff --git a/src/main/java/gg/skytils/skytilsmod/mixins/transformers/renderer/MixinItemRenderer.java b/src/main/java/gg/skytils/skytilsmod/mixins/transformers/renderer/MixinItemRenderer.java index 5189e2e0d..391ee1b8e 100644 --- a/src/main/java/gg/skytils/skytilsmod/mixins/transformers/renderer/MixinItemRenderer.java +++ b/src/main/java/gg/skytils/skytilsmod/mixins/transformers/renderer/MixinItemRenderer.java @@ -31,7 +31,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(ItemRenderer.class) -public class MixinItemRenderer { +public abstract class MixinItemRenderer { @Shadow private ItemStack itemToRender; diff --git a/src/main/java/gg/skytils/skytilsmod/mixins/transformers/renderer/MixinLayerCape.java b/src/main/java/gg/skytils/skytilsmod/mixins/transformers/renderer/MixinLayerCape.java index 7681d6e02..995a8666c 100644 --- a/src/main/java/gg/skytils/skytilsmod/mixins/transformers/renderer/MixinLayerCape.java +++ b/src/main/java/gg/skytils/skytilsmod/mixins/transformers/renderer/MixinLayerCape.java @@ -38,7 +38,7 @@ public abstract class MixinLayerCape implements LayerRenderer { + @WrapOperation(method = "doRenderLayer", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/EntityLivingBase;isSneaking()Z")) + private boolean disableSneakOffset(EntityLivingBase instance, Operation original) { + return (!(instance instanceof EntityPlayer) || !instance.isChild()) && original.call(instance); + } + @Inject(method = "doRenderLayer", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;color(FFFF)V", shift = At.Shift.AFTER), cancellable = true) private void renderCustomHeadLayer(EntityLivingBase entity, float p_177141_2_, float p_177141_3_, float partialTicks, float p_177141_5_, float p_177141_6_, float p_177141_7_, float scale, CallbackInfo ci) { LayerCustomHeadHookKt.renderCustomHeadLayer(entity, p_177141_2_, p_177141_3_, partialTicks, p_177141_5_, p_177141_6_, p_177141_7_, scale, ci); diff --git a/src/main/java/gg/skytils/skytilsmod/mixins/transformers/renderer/MixinModelBiped.java b/src/main/java/gg/skytils/skytilsmod/mixins/transformers/renderer/MixinModelBiped.java new file mode 100644 index 000000000..3b7fd7661 --- /dev/null +++ b/src/main/java/gg/skytils/skytilsmod/mixins/transformers/renderer/MixinModelBiped.java @@ -0,0 +1,49 @@ +/* + * Skytils - Hypixel Skyblock Quality of Life Mod + * Copyright (C) 2020-2024 Skytils + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package gg.skytils.skytilsmod.mixins.transformers.renderer; + +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.client.model.ModelBase; +import net.minecraft.client.model.ModelBiped; +import net.minecraft.client.model.ModelRenderer; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ModelBiped.class) +public abstract class MixinModelBiped extends ModelBase { + @Shadow public ModelRenderer bipedHeadwear; + + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/GlStateManager;popMatrix()V", ordinal = 0)) + private void renderChildHeadPost(Entity entityIn, float f, float g, float h, float i, float j, float scale, CallbackInfo ci) { + if (this.isChild && entityIn instanceof EntityPlayer) { + this.bipedHeadwear.render(scale); + } + } + + @WrapWithCondition(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/ModelRenderer;render(F)V", ordinal = 6)) + private boolean renderChildHeadwear(ModelRenderer instance, float j, @Local(argsOnly = true) Entity entityIn) { + return !this.isChild || !(entityIn instanceof EntityPlayer); + } +} diff --git a/src/main/java/gg/skytils/skytilsmod/mixins/transformers/renderer/MixinRenderManager.java b/src/main/java/gg/skytils/skytilsmod/mixins/transformers/renderer/MixinRenderManager.java index 2e76c5972..75709aee4 100644 --- a/src/main/java/gg/skytils/skytilsmod/mixins/transformers/renderer/MixinRenderManager.java +++ b/src/main/java/gg/skytils/skytilsmod/mixins/transformers/renderer/MixinRenderManager.java @@ -28,7 +28,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(RenderManager.class) -public class MixinRenderManager { +public abstract class MixinRenderManager { @Inject(method = "shouldRender", at = @At("HEAD"), cancellable = true) private void shouldRender(Entity entityIn, ICamera camera, double camX, double camY, double camZ, CallbackInfoReturnable cir) { RenderManagerHookKt.shouldRender(entityIn, camera, camX, camY, camZ, cir); diff --git a/src/main/kotlin/gg/skytils/skytilsmod/Skytils.kt b/src/main/kotlin/gg/skytils/skytilsmod/Skytils.kt index 803584951..e137637b8 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/Skytils.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/Skytils.kt @@ -25,7 +25,7 @@ import gg.skytils.skytilsmod.commands.impl.* import gg.skytils.skytilsmod.commands.stats.impl.CataCommand import gg.skytils.skytilsmod.commands.stats.impl.SlayerCommand import gg.skytils.skytilsmod.core.* -import gg.skytils.skytilsmod.tweaker.DependencyLoader +import gg.skytils.skytilsmod.events.impl.HypixelPacketEvent import gg.skytils.skytilsmod.events.impl.MainReceivePacketEvent import gg.skytils.skytilsmod.events.impl.PacketEvent import gg.skytils.skytilsmod.features.impl.crimson.KuudraChestProfit @@ -46,6 +46,7 @@ import gg.skytils.skytilsmod.features.impl.farming.TreasureHunter import gg.skytils.skytilsmod.features.impl.farming.VisitorHelper import gg.skytils.skytilsmod.features.impl.funny.Funny import gg.skytils.skytilsmod.features.impl.handlers.* +import gg.skytils.skytilsmod.features.impl.mining.CHWaypoints import gg.skytils.skytilsmod.features.impl.mining.MiningFeatures import gg.skytils.skytilsmod.features.impl.mining.StupidTreasureChestOpeningThing import gg.skytils.skytilsmod.features.impl.misc.* @@ -62,6 +63,7 @@ import gg.skytils.skytilsmod.gui.OptionsGui import gg.skytils.skytilsmod.gui.ReopenableGUI import gg.skytils.skytilsmod.listeners.ChatListener import gg.skytils.skytilsmod.listeners.DungeonListener +import gg.skytils.skytilsmod.listeners.ServerPayloadInterceptor import gg.skytils.skytilsmod.localapi.LocalAPI import gg.skytils.skytilsmod.mixins.extensions.ExtensionEntityLivingBase import gg.skytils.skytilsmod.mixins.hooks.entity.EntityPlayerSPHook @@ -69,9 +71,11 @@ import gg.skytils.skytilsmod.mixins.hooks.util.MouseHelperHook import gg.skytils.skytilsmod.mixins.transformers.accessors.AccessorCommandHandler import gg.skytils.skytilsmod.mixins.transformers.accessors.AccessorGuiStreamUnavailable import gg.skytils.skytilsmod.mixins.transformers.accessors.AccessorSettingsGui +import gg.skytils.skytilsmod.tweaker.DependencyLoader import gg.skytils.skytilsmod.utils.* import gg.skytils.skytilsmod.utils.graphics.ScreenRenderer import gg.skytils.skytilsmod.utils.graphics.colors.CustomColor +import gg.skytils.skytilsws.client.WSClient import io.ktor.client.* import io.ktor.client.engine.cio.* import io.ktor.client.plugins.* @@ -88,6 +92,7 @@ import net.minecraft.client.gui.GuiButton import net.minecraft.client.gui.GuiGameOver import net.minecraft.client.gui.GuiIngameMenu import net.minecraft.client.gui.GuiScreen +import net.minecraft.client.network.NetHandlerPlayClient import net.minecraft.client.settings.KeyBinding import net.minecraft.inventory.ContainerChest import net.minecraft.launchwrapper.Launch @@ -209,6 +214,23 @@ class Skytils { } } + val trustManager by lazy { + val backingManager = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply { + init(null as KeyStore?) + }.trustManagers.first { it is X509TrustManager } as X509TrustManager + + val ourManager = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply { + Skytils::class.java.getResourceAsStream("/skytilscacerts.jks").use { + val ourKs = KeyStore.getInstance(KeyStore.getDefaultType()).apply { + load(it, "skytilsontop".toCharArray()) + } + init(ourKs) + } + }.trustManagers.first { it is X509TrustManager } as X509TrustManager + + UnionX509TrustManager(backingManager, ourManager) + } + val client = HttpClient(CIO) { install(ContentEncoding) { customEncoder(BrotliEncoder, 1.0F) @@ -238,20 +260,7 @@ class Skytils { socketTimeout = 10000 } https { - val backingManager = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply { - init(null as KeyStore?) - }.trustManagers.first { it is X509TrustManager } as X509TrustManager - - val ourManager = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply { - Skytils::class.java.getResourceAsStream("/skytilscacerts.jks").use { - val ourKs = KeyStore.getInstance(KeyStore.getDefaultType()).apply { - load(it, "skytilsontop".toCharArray()) - } - init(ourKs) - } - }.trustManagers.first { it is X509TrustManager } as X509TrustManager - - trustManager = UnionX509TrustManager(backingManager, ourManager) + trustManager = Skytils.trustManager } } } @@ -289,6 +298,7 @@ class Skytils { LocalAPI, MayorInfo, SBInfo, + ServerPayloadInterceptor, SoundQueue, UpdateChecker, @@ -305,6 +315,7 @@ class Skytils { BoulderSolver, ChatTabs, ChangeAllToSameColorSolver, + CHWaypoints, DungeonChestProfit, ClickInOrderSolver, NamespacedCommands, @@ -526,6 +537,7 @@ class Skytils { @SubscribeEvent fun onConnect(event: FMLNetworkEvent.ClientConnectedToServerEvent) { + Utils.lastNHPC = event.handler as? NetHandlerPlayClient Utils.isOnHypixel = mc.runCatching { !event.isLocal && (thePlayer?.clientBrand?.lowercase()?.contains("hypixel") ?: currentServerData?.serverIP?.lowercase()?.contains("hypixel") ?: false) @@ -534,6 +546,15 @@ class Skytils { IO.launch { TrophyFish.loadFromApi() } + + IO.launch { + WSClient.openConnection() + } + } + + @SubscribeEvent + fun onHypixelPacketFail(event: HypixelPacketEvent.FailedEvent) { + UChat.chat("$failPrefix Mod API request failed: ${event.reason}") } @SubscribeEvent(priority = EventPriority.HIGHEST) @@ -556,8 +577,10 @@ class Skytils { } } if (!Utils.isOnHypixel && event.packet is S3FPacketCustomPayload && event.packet.channelName == "MC|Brand") { - if (event.packet.bufferData.readStringFromBuffer(Short.MAX_VALUE.toInt()).lowercase().contains("hypixel")) + val brand = event.packet.bufferData.readStringFromBuffer(Short.MAX_VALUE.toInt()) + if (brand.lowercase().contains("hypixel")) { Utils.isOnHypixel = true + } } if (Utils.inDungeons || !Utils.isOnHypixel || event.packet !is S38PacketPlayerListItem || (event.packet.action != S38PacketPlayerListItem.Action.UPDATE_DISPLAY_NAME && @@ -577,9 +600,14 @@ class Skytils { @SubscribeEvent fun onDisconnect(event: FMLNetworkEvent.ClientDisconnectionFromServerEvent) { + Utils.lastNHPC = null Utils.isOnHypixel = false Utils.skyblock = false Utils.dungeons = false + + IO.launch { + WSClient.closeConnection() + } } @SubscribeEvent diff --git a/src/main/kotlin/gg/skytils/skytilsmod/commands/impl/HollowWaypointCommand.kt b/src/main/kotlin/gg/skytils/skytilsmod/commands/impl/HollowWaypointCommand.kt index 55c403356..8841206e7 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/commands/impl/HollowWaypointCommand.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/commands/impl/HollowWaypointCommand.kt @@ -27,7 +27,7 @@ import gg.skytils.skytilsmod.Skytils.Companion.mc import gg.skytils.skytilsmod.Skytils.Companion.prefix import gg.skytils.skytilsmod.Skytils.Companion.successPrefix import gg.skytils.skytilsmod.commands.BaseCommand -import gg.skytils.skytilsmod.features.impl.mining.MiningFeatures +import gg.skytils.skytilsmod.features.impl.mining.CHWaypoints import gg.skytils.skytilsmod.utils.append import gg.skytils.skytilsmod.utils.setHoverText import net.minecraft.client.entity.EntityPlayerSP @@ -47,13 +47,13 @@ object HollowWaypointCommand : BaseCommand("skytilshollowwaypoint", listOf("sthw } if (args.isEmpty()) { val message = UMessage("$prefix §eWaypoints:\n") - for (loc in MiningFeatures.CrystalHollowsMap.Locations.entries) { + for (loc in CHWaypoints.CrystalHollowsMap.Locations.entries) { if (!loc.loc.exists()) continue message.append("${loc.displayName} ") message.append(copyMessage("${loc.cleanName}: ${loc.loc}")) message.append(removeMessage(loc.id)) } - for ((key, value) in MiningFeatures.waypoints) { + for ((key, value) in CHWaypoints.waypoints) { message.append("§e$key ") message.append(copyMessage("$key: ${value.x} ${value.y} ${value.z}")) message.append(removeMessage(key)) @@ -81,13 +81,13 @@ object HollowWaypointCommand : BaseCommand("skytilshollowwaypoint", listOf("sthw y = match.groups["y"]!!.value.toDouble() z = match.groups["z"]!!.value.toDouble() } - val internalLoc = MiningFeatures.CrystalHollowsMap.Locations.entries.find { it.id == loc }?.loc + val internalLoc = CHWaypoints.CrystalHollowsMap.Locations.entries.find { it.id == loc }?.loc if (internalLoc != null) { internalLoc.locX = (x - 200).coerceIn(0.0, 624.0) internalLoc.locY = y internalLoc.locZ = (z - 200).coerceIn(0.0, 624.0) } else { - MiningFeatures.waypoints[loc] = BlockPos(x, y, z) + CHWaypoints.waypoints[loc] = BlockPos(x, y, z) } UChat.chat("$successPrefix §aSuccessfully created waypoint $loc") } @@ -95,11 +95,11 @@ object HollowWaypointCommand : BaseCommand("skytilshollowwaypoint", listOf("sthw "remove", "delete" -> { if (args.size >= 2) { val name = args.drop(1).joinToString(" ") - if (MiningFeatures.CrystalHollowsMap.Locations.entries + if (CHWaypoints.CrystalHollowsMap.Locations.entries .find { it.id == name }?.loc?.reset() != null ) UChat.chat("$successPrefix §aSuccessfully removed waypoint ${name}!") - else if (MiningFeatures.waypoints.remove(name) != null) + else if (CHWaypoints.waypoints.remove(name) != null) UChat.chat("$successPrefix §aSuccessfully removed waypoint $name") else UChat.chat("$failPrefix §cWaypoint $name does not exist") @@ -108,8 +108,8 @@ object HollowWaypointCommand : BaseCommand("skytilshollowwaypoint", listOf("sthw } "clear" -> { - MiningFeatures.CrystalHollowsMap.Locations.entries.forEach { it.loc.reset() } - MiningFeatures.waypoints.clear() + CHWaypoints.CrystalHollowsMap.Locations.entries.forEach { it.loc.reset() } + CHWaypoints.waypoints.clear() UChat.chat("$successPrefix §aSuccessfully cleared all waypoints.") } diff --git a/src/main/kotlin/gg/skytils/skytilsmod/commands/impl/SkytilsCommand.kt b/src/main/kotlin/gg/skytils/skytilsmod/commands/impl/SkytilsCommand.kt index c4e527b97..20fa0b11e 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/commands/impl/SkytilsCommand.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/commands/impl/SkytilsCommand.kt @@ -45,18 +45,23 @@ import gg.skytils.skytilsmod.features.impl.misc.Ping import gg.skytils.skytilsmod.features.impl.misc.PricePaid import gg.skytils.skytilsmod.features.impl.slayer.SlayerFeatures import gg.skytils.skytilsmod.features.impl.trackers.Tracker -import gg.skytils.skytilsmod.gui.* +import gg.skytils.skytilsmod.gui.OptionsGui import gg.skytils.skytilsmod.gui.editing.ElementaEditingGui import gg.skytils.skytilsmod.gui.editing.VanillaEditingGui import gg.skytils.skytilsmod.gui.features.* import gg.skytils.skytilsmod.gui.profile.ProfileGui import gg.skytils.skytilsmod.gui.updater.UpdateGui import gg.skytils.skytilsmod.gui.waypoints.WaypointsGui +import gg.skytils.skytilsmod.listeners.ServerPayloadInterceptor.getResponse import gg.skytils.skytilsmod.localapi.LocalAPI +import gg.skytils.skytilsmod.mixins.transformers.accessors.AccessorHypixelPacketRegistry import gg.skytils.skytilsmod.utils.* import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import net.hypixel.modapi.HypixelModAPI +import net.hypixel.modapi.packet.ClientboundHypixelPacket +import net.hypixel.modapi.packet.impl.serverbound.ServerboundVersionedPacket import net.minecraft.client.entity.EntityPlayerSP import net.minecraft.client.gui.GuiScreen import net.minecraft.command.WrongUsageException @@ -376,6 +381,31 @@ object SkytilsCommand : BaseCommand("skytils", listOf("st")) { } } + "hypixelpacket" -> { + val registry = HypixelModAPI.getInstance().registry + val id = args.getOrNull(1) ?: return UChat.chat("$failPrefix §cInput a packet type!") + if (id == "list") { + UChat.chat("$successPrefix §eAvailable types: ${registry.identifiers.joinToString(", ")}") + } else if (!registry.isRegistered(id)) { + UChat.chat("$failPrefix §cPacket not found!") + } else { + registry as AccessorHypixelPacketRegistry + val packetClass = registry.classToIdentifier.entries.find { it.value == id && ServerboundVersionedPacket::class.java.isAssignableFrom(it.key) } + ?: return UChat.chat("$failPrefix §cPacket not found!") + val packet = packetClass.key.newInstance() as ServerboundVersionedPacket + UChat.chat("$successPrefix §aPacket created: $packet") + Skytils.IO.launch { + runCatching { + packet.getResponse() + }.onFailure { + UChat.chat("$failPrefix §cFailed to get packet response: ${it.message}") + }.onSuccess { response -> + UChat.chat("$successPrefix §aPacket response: $response") + } + } + } + } + else -> UChat.chat("$failPrefix §cThis command doesn't exist!\n §cUse §f/skytils help§c for a full list of commands") } } diff --git a/src/main/kotlin/gg/skytils/skytilsmod/core/Config.kt b/src/main/kotlin/gg/skytils/skytilsmod/core/Config.kt index f7bd0699b..ec80224f7 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/core/Config.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/core/Config.kt @@ -1132,7 +1132,7 @@ object Config : Vigilant( @Property( type = PropertyType.SWITCH, name = "Boulder Solver", - description = "§b[WIP] §rShow which boxes to move on the Boulder puzzle.", + description = "Show which boxes to move on the Boulder puzzle.", category = "Dungeons", subcategory = "Solvers", i18nName = "skytils.config.dungeons.solvers.boulder_solver", i18nCategory = "skytils.config.dungeons", @@ -1140,6 +1140,16 @@ object Config : Vigilant( ) var boulderSolver = false + @Property( + type = PropertyType.COLOR, name = "Boulder Solver Color", + description = "Color of the box that shows which button to click in the Boulder puzzle.", + category = "Dungeons", subcategory = "Solvers", + i18nName = "skytils.config.dungeons.solvers.boulder_solver_color", + i18nCategory = "skytils.config.dungeons", + i18nSubcategory = "skytils.config.dungeons.solvers" + ) + var boulderSolverColor = Color(255, 0, 0, 255) + @Property( type = PropertyType.SWITCH, name = "Creeper Beams Solver", description = "Shows pairs on the Creeper Beams puzzle.", @@ -1182,7 +1192,7 @@ object Config : Vigilant( @Property( type = PropertyType.COLOR, name = "Teleport Maze Solver Color", - description = "Color of the thing that shows which pads you've stepped on in the Teleport Maze puzzle.", + description = "Color of the box that shows which pads you've stepped on in the Teleport Maze puzzle.", category = "Dungeons", subcategory = "Solvers", i18nName = "skytils.config.dungeons.solvers.teleport_maze_solver_color", i18nCategory = "skytils.config.dungeons", @@ -1200,9 +1210,19 @@ object Config : Vigilant( ) var threeWeirdosSolver = false + @Property( + type = PropertyType.COLOR, name = "Three Weirdos Solver Color", + description = "Color of the chest to click on the Three Weirdos puzzle.", + category = "Dungeons", subcategory = "Solvers", + i18nName = "skytils.config.dungeons.solvers.three_weirdos_solver_color", + i18nCategory = "skytils.config.dungeons", + i18nSubcategory = "skytils.config.dungeons.solvers" + ) + var threeWeirdosSolverColor = Color(255, 0, 0, 255) + @Property( type = PropertyType.SWITCH, name = "Tic Tac Toe Solver", - description = "§b[WIP] §rDisplays the best move on the Tic Tac Toe puzzle.", + description = "Displays the best move on the Tic Tac Toe puzzle.", category = "Dungeons", subcategory = "Solvers", i18nName = "skytils.config.dungeons.solvers.tic_tac_toe_solver", i18nCategory = "skytils.config.dungeons", @@ -1212,7 +1232,7 @@ object Config : Vigilant( @Property( type = PropertyType.COLOR, name = "Tic Tac Toe Solver Color", - description = "Color of the thing that displays the best move on the Tic Tac Toe puzzle.", + description = "Color of the outline that displays the best move on the Tic Tac Toe puzzle.", category = "Dungeons", subcategory = "Solvers", i18nName = "skytils.config.dungeons.solvers.tic_tac_toe_solver_color", i18nCategory = "skytils.config.dungeons", @@ -3287,10 +3307,10 @@ object Config : Vigilant( var fishingHookAge = false @Property( - type = PropertyType.SWITCH, name = "Tropy Fish Tracker", + type = PropertyType.SWITCH, name = "Trophy Fish Tracker", description = "Tracks trophy fish caught.", category = "Miscellaneous", subcategory = "Quality of Life", - i18nName = "skytils.config.miscellaneous.quality_of_life.tropy_fish_tracker", + i18nName = "skytils.config.miscellaneous.quality_of_life.trophy_fish_tracker", i18nCategory = "skytils.config.miscellaneous", i18nSubcategory = "skytils.config.miscellaneous.quality_of_life" ) diff --git a/src/main/kotlin/gg/skytils/skytilsmod/events/impl/HypixelPacketEvent.kt b/src/main/kotlin/gg/skytils/skytilsmod/events/impl/HypixelPacketEvent.kt new file mode 100644 index 000000000..e25f707c7 --- /dev/null +++ b/src/main/kotlin/gg/skytils/skytilsmod/events/impl/HypixelPacketEvent.kt @@ -0,0 +1,44 @@ +/* + * Skytils - Hypixel Skyblock Quality of Life Mod + * Copyright (C) 2020-2023 Skytils + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package gg.skytils.skytilsmod.events.impl + +import gg.skytils.skytilsmod.events.SkytilsEvent +import net.hypixel.modapi.error.ErrorReason +import net.hypixel.modapi.packet.ClientboundHypixelPacket +import net.minecraftforge.fml.common.eventhandler.Cancelable + +@Cancelable +abstract class HypixelPacketEvent : SkytilsEvent() { + abstract val direction: Direction + + class ReceiveEvent(val packet: ClientboundHypixelPacket) : HypixelPacketEvent() { + override val direction: Direction = Direction.INBOUND + } + + class SendEvent(val type: String) : HypixelPacketEvent() { + override val direction: Direction = Direction.OUTBOUND + } + + class FailedEvent(val type: String, val reason: ErrorReason) : HypixelPacketEvent() { + override val direction: Direction = Direction.OUTBOUND + } + + enum class Direction { + INBOUND, OUTBOUND + } +} \ No newline at end of file diff --git a/src/main/kotlin/gg/skytils/skytilsmod/events/impl/skyblock/LocrawReceivedEvent.kt b/src/main/kotlin/gg/skytils/skytilsmod/events/impl/skyblock/LocationChangeEvent.kt similarity index 80% rename from src/main/kotlin/gg/skytils/skytilsmod/events/impl/skyblock/LocrawReceivedEvent.kt rename to src/main/kotlin/gg/skytils/skytilsmod/events/impl/skyblock/LocationChangeEvent.kt index 10282ea87..3fa40d124 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/events/impl/skyblock/LocrawReceivedEvent.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/events/impl/skyblock/LocationChangeEvent.kt @@ -1,6 +1,6 @@ /* * Skytils - Hypixel Skyblock Quality of Life Mod - * Copyright (C) 2020-2023 Skytils + * Copyright (C) 2020-2024 Skytils * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published @@ -19,6 +19,6 @@ package gg.skytils.skytilsmod.events.impl.skyblock import gg.skytils.skytilsmod.events.SkytilsEvent -import gg.skytils.skytilsmod.utils.LocrawObject +import net.hypixel.modapi.packet.impl.clientbound.event.ClientboundLocationPacket -data class LocrawReceivedEvent(val loc: LocrawObject) : SkytilsEvent() \ No newline at end of file +data class LocationChangeEvent(val packet: ClientboundLocationPacket) : SkytilsEvent() diff --git a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/DungeonChestProfit.kt b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/DungeonChestProfit.kt index ac73bb5f2..3884a2879 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/DungeonChestProfit.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/DungeonChestProfit.kt @@ -53,7 +53,7 @@ object DungeonChestProfit { private val element = DungeonChestProfitElement() private var rerollBypass = false private val essenceRegex = Regex("§d(?\\w+) Essence §8x(?\\d+)") - private val croesusChestRegex = Regex("^(Master Mode|The)? Catacombs - Floor (IV|V?I{0,3})$") + private val croesusChestRegex = Regex("^(Master Mode )?The Catacombs - Flo(or (IV|V?I{0,3}))?$") @SubscribeEvent fun onGUIDrawnEvent(event: GuiContainerEvent.ForegroundDrawnEvent) { @@ -126,7 +126,7 @@ object DungeonChestProfit { val stack = event.slot.stack ?: return if (stack.item == Items.skull) { val name = stack.displayName - if (!(name == "§cThe Catacombs" || name == "§cMaster Mode Catacombs")) return + if (!(name == "§cThe Catacombs" || name == "§cMaster Mode The Catacombs")) return val lore = ItemUtil.getItemLore(stack) event.slot highlight when { lore.any { line -> line == "§aNo more Chests to open!" } -> { @@ -325,4 +325,4 @@ object DungeonChestProfit { Skytils.guiManager.registerElement(this) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/catlas/core/CatlasConfig.kt b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/catlas/core/CatlasConfig.kt index 50bf6cc0e..e69b81d5e 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/catlas/core/CatlasConfig.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/catlas/core/CatlasConfig.kt @@ -226,6 +226,17 @@ object CatlasConfig : Vigilant( ) var mapRoomSecrets = 0 + // TODO: Add translation + @Property( + name = "Found Room Secrets", + type = PropertyType.SELECTOR, + description = "Shows found secrets of rooms on map.", + category = "Rooms", + options = ["Off", "On", "Replace Total"], + i18nCategory = "catlas.config.rooms" + ) + var foundRoomSecrets = 0 + @Property( name = "Color Text", type = PropertyType.SWITCH, diff --git a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/catlas/core/CatlasElement.kt b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/catlas/core/CatlasElement.kt index 293aa2498..3877ee4af 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/catlas/core/CatlasElement.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/catlas/core/CatlasElement.kt @@ -142,7 +142,7 @@ object CatlasElement : GuiElement(name = "Dungeon Map", x = 0, y = 0) { DungeonInfo.uniqueRooms.forEach { unq -> val room = unq.mainRoom - if (room.state == RoomState.UNDISCOVERED) return@forEach + if (room.state == RoomState.UNDISCOVERED || room.state == RoomState.UNOPENED) return@forEach val size = MapUtils.mapRoomSize + DungeonMapColorParser.quarterRoom val checkPos = unq.getCheckmarkPosition() val namePos = unq.getNamePosition() @@ -161,7 +161,12 @@ object CatlasElement : GuiElement(name = "Dungeon Map", x = 0, y = 0) { val roomType = room.data.type val hasSecrets = secretCount > 0 - if (room.state == RoomState.UNOPENED) return@forEach + val secretText = when (CatlasConfig.foundRoomSecrets) { + 0 -> secretCount.toString() + 1 -> "${unq.foundSecrets ?: "?"}/${secretCount}" + 2 -> unq.foundSecrets?.toString() ?: "?" + else -> error("Invalid foundRoomSecrets value") + } if (CatlasConfig.mapRoomSecrets == 2 && hasSecrets) { GlStateManager.pushMatrix() @@ -171,7 +176,7 @@ object CatlasElement : GuiElement(name = "Dungeon Map", x = 0, y = 0) { 0f ) GlStateManager.scale(2f, 2f, 1f) - RenderUtils.renderCenteredText(listOf(secretCount.toString()), 0, 0, color) + RenderUtils.renderCenteredText(listOf(secretText), 0, 0, color) GlStateManager.popMatrix() } else if (CatlasConfig.mapCheckmark != 0) { drawCheckmark(room, xOffsetCheck, yOffsetCheck, checkmarkSize) @@ -191,7 +196,7 @@ object CatlasElement : GuiElement(name = "Dungeon Map", x = 0, y = 0) { name.addAll(room.data.name.split(" ")) } if (room.data.type == RoomType.NORMAL && CatlasConfig.mapRoomSecrets == 1) { - name.add(secretCount.toString()) + name.add(secretText) } // Offset + half of roomsize RenderUtils.renderCenteredText( diff --git a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/catlas/core/map/UniqueRoom.kt b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/catlas/core/map/UniqueRoom.kt index c3c2ceb51..082dcec70 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/catlas/core/map/UniqueRoom.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/catlas/core/map/UniqueRoom.kt @@ -10,7 +10,8 @@ class UniqueRoom(arrX: Int, arrY: Int, room: Room) { private var topLeft = Pair(arrX, arrY) private var center = Pair(arrX, arrY) var mainRoom = room - private val tiles = mutableListOf(room) + val tiles = mutableListOf(room) + var foundSecrets: Int? = null init { DungeonInfo.cryptCount += room.data.crypts diff --git a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/catlas/handlers/DungeonScanner.kt b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/catlas/handlers/DungeonScanner.kt index f1699f04d..6bd51058c 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/catlas/handlers/DungeonScanner.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/catlas/handlers/DungeonScanner.kt @@ -18,11 +18,16 @@ package gg.skytils.skytilsmod.features.impl.dungeons.catlas.handlers +import gg.skytils.skytilsmod.Skytils.Companion.IO import gg.skytils.skytilsmod.Skytils.Companion.mc import gg.skytils.skytilsmod.features.impl.dungeons.DungeonFeatures.dungeonFloorNumber import gg.skytils.skytilsmod.features.impl.dungeons.catlas.core.map.* import gg.skytils.skytilsmod.features.impl.dungeons.catlas.handlers.DungeonScanner.scan import gg.skytils.skytilsmod.features.impl.dungeons.catlas.utils.ScanUtils +import gg.skytils.skytilsmod.listeners.DungeonListener +import gg.skytils.skytilsmod.utils.SBInfo +import gg.skytils.skytilsws.shared.packet.C2SPacketDungeonRoom +import kotlinx.coroutines.launch import net.minecraft.init.Blocks import net.minecraft.util.BlockPos @@ -73,6 +78,11 @@ object DungeonScanner { scanRoom(xPos, zPos, z, x)?.let { DungeonInfo.dungeonList[z * 11 + x] = it + if (it is Room && it.data.name != "Unknown") { + IO.launch { + DungeonListener.outboundRoomQueue.add(C2SPacketDungeonRoom(SBInfo.server ?: return@launch, it.data.name, xPos, zPos, x, z, it.core, it.isSeparator)) + } + } } } } diff --git a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/solvers/BoulderSolver.kt b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/solvers/BoulderSolver.kt index 2da983ddd..284e637e7 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/solvers/BoulderSolver.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/solvers/BoulderSolver.kt @@ -93,7 +93,7 @@ object BoulderSolver { RenderUtil.drawFilledBoundingBox( matrixStack, AxisAlignedBB(x, y, z, x + 1, y + 1, z + 1), - Color(255, 0, 0, 255), + Skytils.config.boulderSolverColor, 0.7f ) GlStateManager.enableCull() @@ -379,4 +379,4 @@ object BoulderSolver { ) variantSteps.add(arrayListOf(BoulderPush(0, 1, Direction.FORWARD))) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/solvers/IceFillSolver.kt b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/solvers/IceFillSolver.kt index 11215c895..a7ad8bf1f 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/solvers/IceFillSolver.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/dungeons/solvers/IceFillSolver.kt @@ -59,31 +59,45 @@ object IceFillSolver { val pos = te.pos if (world.getBlockState(pos.down()).block == Blocks.stone) { for (direction in EnumFacing.HORIZONTALS) { - if (world.getBlockState(pos.offset(direction)).block == Blocks.cobblestone && world.getBlockState( - pos.offset(direction.opposite, 2) - ).block == Blocks.iron_bars && world.getBlockState( + fun checkChestTorches(dir: EnumFacing): Boolean { + return world.getBlockState( pos.offset( - direction.rotateY(), - 2 + dir, + 1 ) ).block == Blocks.torch && world.getBlockState( pos.offset( - direction.rotateYCCW(), - 2 + dir.opposite, + 3 ) - ).block == Blocks.torch && world.getBlockState( - pos.offset(direction.opposite).down(2) - ).block == Blocks.stone_brick_stairs - ) { - puzzles = Triple( - IceFillPuzzle(world, 70, pos, direction), - IceFillPuzzle(world, 71, pos, direction), - IceFillPuzzle(world, 72, pos, direction) - ) - println( - "Ice fill chest is at $pos and is facing $direction" - ) - break@findChest + ).block == Blocks.torch + } + + if (world.getBlockState(pos.offset(direction)).block == Blocks.cobblestone && world.getBlockState( + pos.offset(direction.opposite, 2) + ).block == Blocks.iron_bars) { + + val offsetDir: EnumFacing? = if (checkChestTorches(direction.rotateY())) { + direction.rotateYCCW() + } else if (checkChestTorches(direction.rotateYCCW())) { + direction.rotateY() + } else continue + + if (world.getBlockState( + pos.offset(direction.opposite) + .offset(offsetDir) + .down(2) + ).block == Blocks.stone_brick_stairs) { + puzzles = Triple( + IceFillPuzzle(world, 70, pos, direction), + IceFillPuzzle(world, 71, pos, direction), + IceFillPuzzle(world, 72, pos, direction) + ) + println( + "An Ice Fill chest is at $pos and is facing $direction. Offset direction is $offsetDir." + ) + break@findChest + } } } } diff --git a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/handlers/Waypoints.kt b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/handlers/Waypoints.kt index 8acd1f692..ef8af9bc7 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/handlers/Waypoints.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/handlers/Waypoints.kt @@ -26,7 +26,7 @@ import gg.skytils.skytilsmod.Skytils import gg.skytils.skytilsmod.commands.impl.OrderedWaypointCommand import gg.skytils.skytilsmod.core.PersistentSave import gg.skytils.skytilsmod.core.tickTimer -import gg.skytils.skytilsmod.events.impl.skyblock.LocrawReceivedEvent +import gg.skytils.skytilsmod.events.impl.skyblock.LocationChangeEvent import gg.skytils.skytilsmod.tweaker.DependencyLoader import gg.skytils.skytilsmod.utils.* import kotlinx.serialization.EncodeDefault @@ -236,14 +236,14 @@ object Waypoints : PersistentSave(File(Skytils.modDir, "waypoints.json")) { } @SubscribeEvent - fun onLocraw(event: LocrawReceivedEvent) { + fun onLocationChange(event: LocationChangeEvent) { tickTimer(20, task = ::computeVisibleWaypoints) } @SubscribeEvent fun onPlayerMove(event: ClientTickEvent) { - if (event.phase == TickEvent.Phase.END || mc.thePlayer?.hasMoved != true) return - if (SBInfo.mode != null && OrderedWaypointCommand.trackedIsland?.mode == SBInfo.mode) { + if (event.phase == TickEvent.Phase.END) return + if (mc.thePlayer?.hasMoved == true && SBInfo.mode != null && OrderedWaypointCommand.trackedIsland?.mode == SBInfo.mode) { val tracked = OrderedWaypointCommand.trackedSet?.firstOrNull() if (tracked == null) { OrderedWaypointCommand.doneTracking() diff --git a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/mining/CHWaypoints.kt b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/mining/CHWaypoints.kt new file mode 100644 index 000000000..e44c4ff15 --- /dev/null +++ b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/mining/CHWaypoints.kt @@ -0,0 +1,399 @@ +/* + * Skytils - Hypixel Skyblock Quality of Life Mod + * Copyright (C) 2020-2024 Skytils + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package gg.skytils.skytilsmod.features.impl.mining + +import gg.essential.universal.UChat +import gg.essential.universal.UGraphics +import gg.essential.universal.UMatrixStack +import gg.essential.universal.utils.MCClickEventAction +import gg.essential.universal.wrappers.message.UMessage +import gg.essential.universal.wrappers.message.UTextComponent +import gg.skytils.skytilsmod.Skytils +import gg.skytils.skytilsmod.Skytils.Companion.mc +import gg.skytils.skytilsmod.Skytils.Companion.prefix +import gg.skytils.skytilsmod.core.structure.GuiElement +import gg.skytils.skytilsmod.events.impl.HypixelPacketEvent +import gg.skytils.skytilsmod.events.impl.PacketEvent +import gg.skytils.skytilsmod.features.impl.handlers.MayorInfo +import gg.skytils.skytilsmod.utils.* +import gg.skytils.skytilsmod.utils.graphics.colors.ColorFactory +import gg.skytils.skytilsws.client.WSClient +import gg.skytils.skytilsws.shared.packet.C2SPacketCHWaypoint +import gg.skytils.skytilsws.shared.packet.C2SPacketCHWaypointsSubscribe +import gg.skytils.skytilsws.shared.structs.CHWaypointType +import kotlinx.coroutines.launch +import net.hypixel.modapi.packet.impl.clientbound.event.ClientboundLocationPacket +import net.minecraft.client.entity.EntityOtherPlayerMP +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.client.renderer.vertex.DefaultVertexFormats +import net.minecraft.entity.EntityLivingBase +import net.minecraft.network.play.server.S08PacketPlayerPosLook +import net.minecraft.util.BlockPos +import net.minecraft.util.ResourceLocation +import net.minecraftforge.client.event.ClientChatReceivedEvent +import net.minecraftforge.client.event.RenderLivingEvent +import net.minecraftforge.client.event.RenderWorldLastEvent +import net.minecraftforge.event.world.WorldEvent +import net.minecraftforge.fml.common.eventhandler.EventPriority +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import net.minecraftforge.fml.common.gameevent.TickEvent +import net.minecraftforge.fml.common.gameevent.TickEvent.ClientTickEvent +import kotlin.jvm.optionals.getOrNull + +object CHWaypoints { + var lastTPLoc: BlockPos? = null + var waypoints = hashMapOf() + var waypointDelayTicks = 0 + private val SBE_DSM_PATTERN = + Regex("\\\$(?:SBECHWP\\b|DSMCHWP):(?.*?)@-(?-?\\d+),(?-?\\d+),(?-?\\d+)") + private val xyzPattern = + Regex(".*?(?[a-zA-Z0-9_]{3,16}):.*?(?[0-9]{1,3}),? (?:y: )?(?[0-9]{1,3}),? (?:z: )?(?[0-9]{1,3}).*?") + private val xzPattern = + Regex(".*(?[a-zA-Z0-9_]{3,16}):.* (?[0-9]{1,3}),? (?[0-9]{1,3}).*") + val chWaypointsList = hashMapOf() + class CHInstance { + val waypoints = hashMapOf() + } + + + @SubscribeEvent + fun onHypixelPacket(event: HypixelPacketEvent.ReceiveEvent) { + if (event.packet is ClientboundLocationPacket) { + if (event.packet.mode.getOrNull() == SkyblockIsland.CrystalHollows.mode) { + Skytils.IO.launch { + WSClient.sendPacket(C2SPacketCHWaypointsSubscribe(event.packet.serverName)) + } + } + } + } + + @SubscribeEvent + fun onReceivePacket(event: PacketEvent.ReceiveEvent) { + if (!Utils.inSkyblock) return + if (Skytils.config.crystalHollowDeathWaypoint && event.packet is S08PacketPlayerPosLook && mc.thePlayer != null) { + lastTPLoc = mc.thePlayer.position + } + } + + @SubscribeEvent(priority = EventPriority.HIGHEST, receiveCanceled = true) + fun onChat(event: ClientChatReceivedEvent) { + if (!Utils.inSkyblock || event.type == 2.toByte()) return + val unformatted = event.message.unformattedText.stripControlCodes() + if (Skytils.config.hollowChatCoords && SBInfo.mode == SkyblockIsland.CrystalHollows.mode) { + xyzPattern.find(unformatted)?.groups?.let { + waypointChatMessage(it["x"]!!.value, it["y"]!!.value, it["z"]!!.value) + return + } + xzPattern.find(unformatted)?.groups?.let { + waypointChatMessage(it["x"]!!.value, "100", it["z"]!!.value) + return + } + + /** + * Checks for the format used in DSM and SBE + * $DSMCHWP:Mines of Divan@-673,117,426 ✔ + * $SBECHWP:Khazad-dûm@-292,63,281 ✔ + * $asdf:Khazad-dûm@-292,63,281 ❌ + * $SBECHWP:Khazad-dûm@asdf,asdf,asdf ❌ + */ + val cleaned = SBE_DSM_PATTERN.find(unformatted) + if (cleaned != null) { + val stringLocation = cleaned.groups["stringLocation"]!!.value + val x = cleaned.groups["x"]!!.value + val y = cleaned.groups["y"]!!.value + val z = cleaned.groups["z"]!!.value + CrystalHollowsMap.Locations.entries.find { it.cleanName == stringLocation } + ?.takeIf { !it.loc.exists() }?.let { loc -> + /** + * Sends the waypoints message except it suggests which one should be used based on + * the name contained in the message and converts it to the internally used names for the waypoints. + */ + UMessage("§3Skytils > §eFound coordinates in a chat message, click a button to set a waypoint.\n") + .append( + UTextComponent("§f${loc.displayName} ") + .setClick( + MCClickEventAction.RUN_COMMAND, + "/skytilshollowwaypoint set $x $y $z ${loc.id}" + ) + .setHoverText("§eSet waypoint for ${loc.displayName}") + ) + .append( + UTextComponent("§e[Custom]") + .setClick( + MCClickEventAction.SUGGEST_COMMAND, + "/skytilshollowwaypoint set $x $y $z name_here" + ) + .setHoverText("§eSet custom waypoint") + ).chat() + } + } + } + if ((Skytils.config.crystalHollowWaypoints || Skytils.config.crystalHollowMapPlaces) && Skytils.config.kingYolkarWaypoint && SBInfo.mode == SkyblockIsland.CrystalHollows.mode + && mc.thePlayer != null && unformatted.startsWith("[NPC] King Yolkar:") + ) { + CrystalHollowsMap.Locations.KingYolkar.loc.set() + } + if (unformatted.startsWith("You died") || unformatted.startsWith("☠ You were killed")) { + waypointDelayTicks = + 50 //this is to make sure the scoreboard has time to update and nothing moves halfway across the map + if (Skytils.config.crystalHollowDeathWaypoint && SBInfo.mode == SkyblockIsland.CrystalHollows.mode && lastTPLoc != null) { + UChat.chat( + UTextComponent("$prefix §eClick to set a death waypoint at ${lastTPLoc!!.x} ${lastTPLoc!!.y} ${lastTPLoc!!.z}").setClick( + MCClickEventAction.RUN_COMMAND, + "/sthw set ${lastTPLoc!!.x} ${lastTPLoc!!.y} ${lastTPLoc!!.z} Last Death" + ) + ) + } + } else if (unformatted.startsWith("Warp")) { + waypointDelayTicks = 50 + } + } + + private fun waypointChatMessage(x: String, y: String, z: String) { + val message = UMessage( + "$prefix §eFound coordinates in a chat message, click a button to set a waypoint.\n" + ) + for (loc in CrystalHollowsMap.Locations.entries) { + if (loc.loc.exists()) continue + message.append( + UTextComponent("${loc.displayName.substring(0, 2)}[${loc.displayName}] ") + .setClick(MCClickEventAction.SUGGEST_COMMAND, "/sthw set $x $y $z ${loc.id}") + .setHoverText("§eSet waypoint for ${loc.cleanName}") + ) + } + message.append( + UTextComponent("§e[Custom]").setClick( + MCClickEventAction.SUGGEST_COMMAND, + "/sthw set $x $y $z Name" + ).setHoverText("§eSet waypoint for custom location") + ) + message.chat() + } + + @SubscribeEvent + fun onRenderWorld(event: RenderWorldLastEvent) { + if (!Utils.inSkyblock) return + val matrixStack = UMatrixStack() + if (Skytils.config.crystalHollowWaypoints && SBInfo.mode == SkyblockIsland.CrystalHollows.mode) { + GlStateManager.disableDepth() + for (loc in CrystalHollowsMap.Locations.entries) { + loc.loc.drawWaypoint(loc.cleanName, event.partialTicks, matrixStack) + } + RenderUtil.renderWaypointText("Crystal Nucleus", 513.5, 107.0, 513.5, event.partialTicks, matrixStack) + for ((key, value) in waypoints) + RenderUtil.renderWaypointText(key, value, event.partialTicks, matrixStack) + GlStateManager.enableDepth() + } + } + + @SubscribeEvent + fun onRenderLivingPre(event: RenderLivingEvent.Pre) { + if (!Utils.inSkyblock) return + if (Skytils.config.crystalHollowWaypoints && + event.entity is EntityOtherPlayerMP && + event.entity.name == "Team Treasurite" && + mc.thePlayer.canEntityBeSeen(event.entity) && + event.entity.baseMaxHealth == if (MayorInfo.mayorPerks.contains("DOUBLE MOBS HP!!!")) 2_000_000.0 else 1_000_000.0 + ) { + if (!CrystalHollowsMap.Locations.Corleone.loc.exists()) { + CrystalHollowsMap.Locations.Corleone.apply { + loc.set() + Skytils.IO.launch { + WSClient.sendPacket(C2SPacketCHWaypoint(serverId = SBInfo.server ?: "", serverTime = mc.theWorld.worldTime, packetType, loc.locX!!.toInt(), loc.locY!!.toInt(), loc.locZ!!.toInt())) + } + } + } else CrystalHollowsMap.Locations.Corleone.loc.set() + } + } + + @SubscribeEvent(priority = EventPriority.LOW) // priority low so it always runs after sbinfo is updated + fun onTick(event: ClientTickEvent) { + if (!Utils.inSkyblock || event.phase != TickEvent.Phase.START) return + if ((Skytils.config.crystalHollowWaypoints || Skytils.config.crystalHollowMapPlaces) && SBInfo.mode == SkyblockIsland.CrystalHollows.mode + && waypointDelayTicks == 0 && mc.thePlayer != null + ) { + CrystalHollowsMap.Locations.cleanNameToLocation[SBInfo.location]?.let { + if (!it.loc.exists()) { + it.loc.set() + Skytils.IO.launch { + WSClient.sendPacket(C2SPacketCHWaypoint(serverId = SBInfo.server ?: "", serverTime = mc.theWorld.worldTime, it.packetType, it.loc.locX!!.toInt(), it.loc.locY!!.toInt(), it.loc.locZ!!.toInt())) + } + } else it.loc.set() + } + } else if (waypointDelayTicks > 0) + waypointDelayTicks-- + } + + @SubscribeEvent(priority = EventPriority.HIGHEST) + fun onWorldChange(event: WorldEvent.Unload) { + val instance = chWaypointsList.getOrPut(SBInfo.server ?: "") { CHInstance() } + CrystalHollowsMap.Locations.entries.forEach { + if (it.loc.exists()) { + instance.waypoints[it.packetType] = BlockPos(it.loc.locX!!, it.loc.locY!!, it.loc.locZ!!) + } + it.loc.reset() + } + waypoints.clear() + } + + + class CrystalHollowsMap : GuiElement(name = "Crystal Hollows Map", x = 0, y = 0) { + val mapLocation = ResourceLocation("skytils", "crystalhollowsmap.png") + + enum class Locations(val displayName: String, val id: String, val color: Int, val packetType: CHWaypointType, val size: Int = 50) { + LostPrecursorCity("§fLost Precursor City", "internal_city", ColorFactory.WHITE.rgb, CHWaypointType.LostPrecursorCity), + JungleTemple("§aJungle Temple", "internal_temple", ColorFactory.GREEN.rgb, CHWaypointType.JungleTemple), + GoblinQueensDen("§eGoblin Queen's Den", "internal_den", ColorFactory.YELLOW.rgb, CHWaypointType.GoblinQueensDen), + MinesOfDivan("§9Mines of Divan", "internal_mines", ColorFactory.BLUE.rgb, CHWaypointType.MinesOfDivan), + KingYolkar("§6King Yolkar", "internal_king", ColorFactory.ORANGE.rgb, CHWaypointType.KingYolkar,25), + KhazadDum("§cKhazad-dûm", "internal_bal", ColorFactory.RED.rgb, CHWaypointType.KhazadDum), + FairyGrotto("§dFairy Grotto", "internal_fairy", ColorFactory.PINK.rgb, CHWaypointType.FairyGrotto, 26), + Corleone("§bCorleone", "internal_corleone", ColorFactory.AQUA.rgb, CHWaypointType.Corleone, 26); + + val loc = LocationObject() + val cleanName = displayName.stripControlCodes() + + companion object { + val cleanNameToLocation = entries.associateBy { it.cleanName } + } + } + + override fun render() { + if (!toggled || SBInfo.mode != SkyblockIsland.CrystalHollows.mode || mc.thePlayer == null) return + val stack = UMatrixStack() + UMatrixStack.Compat.runLegacyMethod(stack) { + stack.scale(0.1, 0.1, 1.0) + UGraphics.disableLighting() + stack.runWithGlobalState { + RenderUtil.renderTexture(mapLocation, 0, 0, 624, 624, false) + if (Skytils.config.crystalHollowMapPlaces) { + Locations.entries.forEach { + it.loc.drawOnMap(it.size, it.color) + } + } + } + val x = (mc.thePlayer.posX - 202).coerceIn(0.0, 624.0) + val y = (mc.thePlayer.posZ - 202).coerceIn(0.0, 624.0) + + // player marker code + val wr = UGraphics.getFromTessellator() + mc.textureManager.bindTexture(ResourceLocation("textures/map/map_icons.png")) + + stack.push() + stack.translate(x, y, 0.0) + + // Rotate about the center to match the player's yaw + stack.rotate((mc.thePlayer.rotationYawHead + 180f) % 360f, 0f, 0f, 1f) + stack.scale(1.5f, 1.5f, 1.5f) + stack.translate(-0.125f, 0.125f, 0.0f) + UGraphics.color4f(1f, 1f, 1f, 1f) + UGraphics.enableAlpha() + val d1 = 0.0 + val d2 = 0.25 + wr.beginWithActiveShader(UGraphics.DrawMode.QUADS, DefaultVertexFormats.POSITION_TEX) + wr.pos(stack, -8.0, -8.0, 100.0).tex(d1, d1).endVertex() + wr.pos(stack, -8.0, 8.0, 100.0).tex(d1, d2).endVertex() + wr.pos(stack, 8.0, 8.0, 100.0).tex(d2, d2).endVertex() + wr.pos(stack, 8.0, -8.0, 100.0).tex(d2, d1).endVertex() + wr.drawDirect() + stack.pop() + } + } + + override fun demoRender() { + UGraphics.disableLighting() + RenderUtil.renderTexture(mapLocation, 0, 0, 62, 62, false) + } + + override val toggled: Boolean + get() = Skytils.config.crystalHollowMap + override val height: Int + get() = 62 // should be 62.4 but oh well + override val width: Int + get() = 62 + + init { + Skytils.guiManager.registerElement(this) + } + } + + init { + CrystalHollowsMap() + } + + class LocationObject { + var locX: Double? = null + var locY: Double? = null + var locZ: Double? = null + private var locMinX: Double = 1100.0 + private var locMinY: Double = 1100.0 + private var locMinZ: Double = 1100.0 + private var locMaxX: Double = -100.0 + private var locMaxY: Double = -100.0 + private var locMaxZ: Double = -100.0 + + fun reset() { + locX = null + locY = null + locZ = null + locMinX = 1100.0 + locMinY = 1100.0 + locMinZ = 1100.0 + locMaxX = -100.0 + locMaxY = -100.0 + locMaxZ = -100.0 + } + + fun set() { + locMinX = (mc.thePlayer.posX - 200).coerceIn(0.0, 624.0).coerceAtMost(locMinX) + locMinY = mc.thePlayer.posY.coerceIn(0.0, 256.0).coerceAtMost(locMinY) + locMinZ = (mc.thePlayer.posZ - 200).coerceIn(0.0, 624.0).coerceAtMost(locMinZ) + locMaxX = (mc.thePlayer.posX - 200).coerceIn(0.0, 624.0).coerceAtLeast(locMaxX) + locMaxY = mc.thePlayer.posY.coerceIn(0.0, 256.0).coerceAtLeast(locMaxY) + locMaxZ = (mc.thePlayer.posZ - 200).coerceIn(0.0, 624.0).coerceAtLeast(locMaxZ) + locX = (locMinX + locMaxX) / 2 + locY = (locMinY + locMaxY) / 2 + locZ = (locMinZ + locMaxZ) / 2 + } + + fun exists(): Boolean { + return locX != null && locY != null && locZ != null + } + + fun drawWaypoint(text: String, partialTicks: Float, matrixStack: UMatrixStack) { + if (exists()) + RenderUtil.renderWaypointText(text, locX!! + 200, locY!!, locZ!! + 200, partialTicks, matrixStack) + } + + fun drawOnMap(size: Int, color: Int) { + if (exists()) + RenderUtil.drawRect(locX!! - size, locZ!! - size, locX!! + size, locZ!! + size, color) + } + + override fun toString(): String { + return String.format("%.0f", locX?.plus(200)) + " " + String.format( + "%.0f", + locY + ) + " " + String.format( + "%.0f", + locZ?.plus(200) + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/mining/MiningFeatures.kt b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/mining/MiningFeatures.kt index 6dfd95bc3..b92d298c4 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/features/impl/mining/MiningFeatures.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/features/impl/mining/MiningFeatures.kt @@ -18,7 +18,6 @@ package gg.skytils.skytilsmod.features.impl.mining import gg.essential.universal.UChat -import gg.essential.universal.UGraphics import gg.essential.universal.UMatrixStack import gg.essential.universal.utils.MCClickEventAction import gg.essential.universal.wrappers.message.UMessage @@ -26,41 +25,29 @@ import gg.essential.universal.wrappers.message.UTextComponent import gg.skytils.skytilsmod.Skytils import gg.skytils.skytilsmod.Skytils.Companion.failPrefix import gg.skytils.skytilsmod.Skytils.Companion.mc -import gg.skytils.skytilsmod.Skytils.Companion.prefix import gg.skytils.skytilsmod.Skytils.Companion.successPrefix import gg.skytils.skytilsmod.core.DataFetcher import gg.skytils.skytilsmod.core.GuiManager import gg.skytils.skytilsmod.core.GuiManager.createTitle -import gg.skytils.skytilsmod.core.structure.GuiElement import gg.skytils.skytilsmod.core.tickTimer import gg.skytils.skytilsmod.events.impl.BossBarEvent import gg.skytils.skytilsmod.events.impl.GuiContainerEvent import gg.skytils.skytilsmod.events.impl.PacketEvent -import gg.skytils.skytilsmod.features.impl.handlers.MayorInfo import gg.skytils.skytilsmod.utils.* import gg.skytils.skytilsmod.utils.RenderUtil.highlight -import gg.skytils.skytilsmod.utils.graphics.colors.ColorFactory -import net.minecraft.client.entity.EntityOtherPlayerMP import net.minecraft.client.renderer.GlStateManager -import net.minecraft.client.renderer.vertex.DefaultVertexFormats -import net.minecraft.entity.EntityLivingBase import net.minecraft.init.Blocks import net.minecraft.init.Items import net.minecraft.inventory.ContainerChest -import net.minecraft.network.play.server.S08PacketPlayerPosLook import net.minecraft.network.play.server.S3EPacketTeams import net.minecraft.util.AxisAlignedBB import net.minecraft.util.BlockPos -import net.minecraft.util.ResourceLocation import net.minecraftforge.client.event.ClientChatReceivedEvent -import net.minecraftforge.client.event.RenderLivingEvent import net.minecraftforge.client.event.RenderWorldLastEvent import net.minecraftforge.event.entity.player.PlayerInteractEvent import net.minecraftforge.event.world.WorldEvent import net.minecraftforge.fml.common.eventhandler.EventPriority import net.minecraftforge.fml.common.eventhandler.SubscribeEvent -import net.minecraftforge.fml.common.gameevent.TickEvent -import net.minecraftforge.fml.common.gameevent.TickEvent.ClientTickEvent import java.awt.Color import java.util.regex.Pattern @@ -74,15 +61,6 @@ object MiningFeatures { private var puzzlerSolution: BlockPos? = null private var raffleBox: BlockPos? = null private var inRaffle = false - var lastTPLoc: BlockPos? = null - var waypoints = hashMapOf() - var waypointDelayTicks = 0 - private val SBE_DSM_PATTERN = - Regex("\\\$(?:SBECHWP\\b|DSMCHWP):(?.*?)@-(?-?\\d+),(?-?\\d+),(?-?\\d+)") - private val xyzPattern = - Regex(".*?(?[a-zA-Z0-9_]{3,16}):.*?(?[0-9]{1,3}),? (?:y: )?(?[0-9]{1,3}),? (?:z: )?(?[0-9]{1,3}).*?") - private val xzPattern = - Regex(".*(?[a-zA-Z0-9_]{3,16}):.* (?[0-9]{1,3}),? (?[0-9]{1,3}).*") @SubscribeEvent fun onBossBar(event: BossBarEvent.Set) { @@ -106,14 +84,6 @@ object MiningFeatures { } } - @SubscribeEvent - fun onReceivePacket(event: PacketEvent.ReceiveEvent) { - if (!Utils.inSkyblock) return - if (Skytils.config.crystalHollowDeathWaypoint && event.packet is S08PacketPlayerPosLook && mc.thePlayer != null) { - lastTPLoc = mc.thePlayer.position - } - } - @SubscribeEvent(priority = EventPriority.HIGHEST, receiveCanceled = true) fun onChat(event: ClientChatReceivedEvent) { if (!Utils.inSkyblock || event.type == 2.toByte()) return @@ -186,95 +156,6 @@ object MiningFeatures { } } } - if (Skytils.config.hollowChatCoords && SBInfo.mode == SkyblockIsland.CrystalHollows.mode) { - xyzPattern.find(unformatted)?.groups?.let { - waypointChatMessage(it["x"]!!.value, it["y"]!!.value, it["z"]!!.value) - return - } - xzPattern.find(unformatted)?.groups?.let { - waypointChatMessage(it["x"]!!.value, "100", it["z"]!!.value) - return - } - - /** - * Checks for the format used in DSM and SBE - * $DSMCHWP:Mines of Divan@-673,117,426 ✔ - * $SBECHWP:Khazad-dûm@-292,63,281 ✔ - * $asdf:Khazad-dûm@-292,63,281 ❌ - * $SBECHWP:Khazad-dûm@asdf,asdf,asdf ❌ - */ - val cleaned = SBE_DSM_PATTERN.find(unformatted) - if (cleaned != null) { - val stringLocation = cleaned.groups["stringLocation"]!!.value - val x = cleaned.groups["x"]!!.value - val y = cleaned.groups["y"]!!.value - val z = cleaned.groups["z"]!!.value - CrystalHollowsMap.Locations.entries.find { it.cleanName == stringLocation } - ?.takeIf { !it.loc.exists() }?.let { loc -> - /** - * Sends the waypoints message except it suggests which one should be used based on - * the name contained in the message and converts it to the internally used names for the waypoints. - */ - UMessage("§3Skytils > §eFound coordinates in a chat message, click a button to set a waypoint.\n") - .append( - UTextComponent("§f${loc.displayName} ") - .setClick( - MCClickEventAction.RUN_COMMAND, - "/skytilshollowwaypoint set $x $y $z ${loc.id}" - ) - .setHoverText("§eSet waypoint for ${loc.displayName}") - ) - .append( - UTextComponent("§e[Custom]") - .setClick( - MCClickEventAction.SUGGEST_COMMAND, - "/skytilshollowwaypoint set $x $y $z name_here" - ) - .setHoverText("§eSet custom waypoint") - ).chat() - } - } - } - if ((Skytils.config.crystalHollowWaypoints || Skytils.config.crystalHollowMapPlaces) && Skytils.config.kingYolkarWaypoint && SBInfo.mode == SkyblockIsland.CrystalHollows.mode - && mc.thePlayer != null && unformatted.startsWith("[NPC] King Yolkar:") - ) { - CrystalHollowsMap.Locations.KingYolkar.loc.set() - } - if (unformatted.startsWith("You died") || unformatted.startsWith("☠ You were killed")) { - waypointDelayTicks = - 50 //this is to make sure the scoreboard has time to update and nothing moves halfway across the map - if (Skytils.config.crystalHollowDeathWaypoint && SBInfo.mode == SkyblockIsland.CrystalHollows.mode && lastTPLoc != null) { - UChat.chat( - UTextComponent("$prefix §eClick to set a death waypoint at ${lastTPLoc!!.x} ${lastTPLoc!!.y} ${lastTPLoc!!.z}").setClick( - MCClickEventAction.RUN_COMMAND, - "/sthw set ${lastTPLoc!!.x} ${lastTPLoc!!.y} ${lastTPLoc!!.z} Last Death" - ) - ) - } - } else if (unformatted.startsWith("Warp")) { - waypointDelayTicks = 50 - } - } - - private fun waypointChatMessage(x: String, y: String, z: String) { - val message = UMessage( - "$prefix §eFound coordinates in a chat message, click a button to set a waypoint.\n" - ) - for (loc in CrystalHollowsMap.Locations.entries) { - if (loc.loc.exists()) continue - message.append( - UTextComponent("${loc.displayName.substring(0, 2)}[${loc.displayName}] ") - .setClick(MCClickEventAction.SUGGEST_COMMAND, "/sthw set $x $y $z ${loc.id}") - .setHoverText("§eSet waypoint for ${loc.cleanName}") - ) - } - message.append( - UTextComponent("§e[Custom]").setClick( - MCClickEventAction.SUGGEST_COMMAND, - "/sthw set $x $y $z Name" - ).setHoverText("§eSet waypoint for custom location") - ) - message.chat() } @SubscribeEvent @@ -339,29 +220,6 @@ object MiningFeatures { GlStateManager.enableDepth() GlStateManager.enableCull() } - if (Skytils.config.crystalHollowWaypoints && SBInfo.mode == SkyblockIsland.CrystalHollows.mode) { - GlStateManager.disableDepth() - for (loc in CrystalHollowsMap.Locations.entries) { - loc.loc.drawWaypoint(loc.cleanName, event.partialTicks, matrixStack) - } - RenderUtil.renderWaypointText("Crystal Nucleus", 513.5, 107.0, 513.5, event.partialTicks, matrixStack) - for ((key, value) in waypoints) - RenderUtil.renderWaypointText(key, value, event.partialTicks, matrixStack) - GlStateManager.enableDepth() - } - } - - @SubscribeEvent - fun onRenderLivingPre(event: RenderLivingEvent.Pre) { - if (!Utils.inSkyblock) return - if (Skytils.config.crystalHollowWaypoints && - event.entity is EntityOtherPlayerMP && - event.entity.name == "Team Treasurite" && - mc.thePlayer.canEntityBeSeen(event.entity) && - event.entity.baseMaxHealth == if (MayorInfo.mayorPerks.contains("DOUBLE MOBS HP!!!")) 2_000_000.0 else 1_000_000.0 - ) { - waypoints["Corleone"] = event.entity.position - } } @SubscribeEvent @@ -385,168 +243,11 @@ object MiningFeatures { } } - @SubscribeEvent(priority = EventPriority.LOW) // priority low so it always runs after sbinfo is updated - fun onTick(event: ClientTickEvent) { - if (!Utils.inSkyblock || event.phase != TickEvent.Phase.START) return - if ((Skytils.config.crystalHollowWaypoints || Skytils.config.crystalHollowMapPlaces) && SBInfo.mode == SkyblockIsland.CrystalHollows.mode - && waypointDelayTicks == 0 && mc.thePlayer != null - ) { - CrystalHollowsMap.Locations.cleanNameToLocation[SBInfo.location]?.loc?.set() - } else if (waypointDelayTicks > 0) - waypointDelayTicks-- - } - @SubscribeEvent fun onWorldChange(event: WorldEvent.Unload) { puzzlerSolution = null lastJukebox = null raffleBox = null inRaffle = false - CrystalHollowsMap.Locations.entries.forEach { it.loc.reset() } - waypoints.clear() - } - - class CrystalHollowsMap : GuiElement(name = "Crystal Hollows Map", x = 0, y = 0) { - val mapLocation = ResourceLocation("skytils", "crystalhollowsmap.png") - - enum class Locations(val displayName: String, val id: String, val color: Int, val size: Int = 50) { - LostPrecursorCity("§fLost Precursor City", "internal_city", ColorFactory.WHITE.rgb), - JungleTemple("§aJungle Temple", "internal_temple", ColorFactory.GREEN.rgb), - GoblinQueensDen("§eGoblin Queen's Den", "internal_den", ColorFactory.YELLOW.rgb), - MinesOfDivan("§9Mines of Divan", "internal_mines", ColorFactory.BLUE.rgb), - KingYolkar("§6King Yolkar", "internal_king", ColorFactory.ORANGE.rgb, 25), - KhazadDum("§cKhazad-dûm", "internal_bal", ColorFactory.RED.rgb), - FairyGrotto("§dFairy Grotto", "internal_fairy", ColorFactory.PINK.rgb, 26), - Corleone("§bCorleone", "internal_corleone", ColorFactory.AQUA.rgb, 26); - - val loc = LocationObject() - val cleanName = displayName.stripControlCodes() - - companion object { - val cleanNameToLocation = entries.associateBy { it.cleanName } - } - } - - override fun render() { - if (!toggled || SBInfo.mode != SkyblockIsland.CrystalHollows.mode || mc.thePlayer == null) return - val stack = UMatrixStack() - UMatrixStack.Compat.runLegacyMethod(stack) { - stack.scale(0.1, 0.1, 1.0) - UGraphics.disableLighting() - stack.runWithGlobalState { - RenderUtil.renderTexture(mapLocation, 0, 0, 624, 624, false) - if (Skytils.config.crystalHollowMapPlaces) { - Locations.entries.forEach { - it.loc.drawOnMap(it.size, it.color) - } - } - } - val x = (mc.thePlayer.posX - 202).coerceIn(0.0, 624.0) - val y = (mc.thePlayer.posZ - 202).coerceIn(0.0, 624.0) - - // player marker code - val wr = UGraphics.getFromTessellator() - mc.textureManager.bindTexture(ResourceLocation("textures/map/map_icons.png")) - - stack.push() - stack.translate(x, y, 0.0) - - // Rotate about the center to match the player's yaw - stack.rotate((mc.thePlayer.rotationYawHead + 180f) % 360f, 0f, 0f, 1f) - stack.scale(1.5f, 1.5f, 1.5f) - stack.translate(-0.125f, 0.125f, 0.0f) - UGraphics.color4f(1f, 1f, 1f, 1f) - UGraphics.enableAlpha() - val d1 = 0.0 - val d2 = 0.25 - wr.beginWithActiveShader(UGraphics.DrawMode.QUADS, DefaultVertexFormats.POSITION_TEX) - wr.pos(stack, -8.0, -8.0, 100.0).tex(d1, d1).endVertex() - wr.pos(stack, -8.0, 8.0, 100.0).tex(d1, d2).endVertex() - wr.pos(stack, 8.0, 8.0, 100.0).tex(d2, d2).endVertex() - wr.pos(stack, 8.0, -8.0, 100.0).tex(d2, d1).endVertex() - wr.drawDirect() - stack.pop() - } - } - - override fun demoRender() { - UGraphics.disableLighting() - RenderUtil.renderTexture(mapLocation, 0, 0, 62, 62, false) - } - - override val toggled: Boolean - get() = Skytils.config.crystalHollowMap - override val height: Int - get() = 62 // should be 62.4 but oh well - override val width: Int - get() = 62 - - init { - Skytils.guiManager.registerElement(this) - } - } - - init { - CrystalHollowsMap() - } - - class LocationObject { - var locX: Double? = null - var locY: Double? = null - var locZ: Double? = null - private var locMinX: Double = 1100.0 - private var locMinY: Double = 1100.0 - private var locMinZ: Double = 1100.0 - private var locMaxX: Double = -100.0 - private var locMaxY: Double = -100.0 - private var locMaxZ: Double = -100.0 - - fun reset() { - locX = null - locY = null - locZ = null - locMinX = 1100.0 - locMinY = 1100.0 - locMinZ = 1100.0 - locMaxX = -100.0 - locMaxY = -100.0 - locMaxZ = -100.0 - } - - fun set() { - locMinX = (mc.thePlayer.posX - 200).coerceIn(0.0, 624.0).coerceAtMost(locMinX) - locMinY = mc.thePlayer.posY.coerceIn(0.0, 256.0).coerceAtMost(locMinY) - locMinZ = (mc.thePlayer.posZ - 200).coerceIn(0.0, 624.0).coerceAtMost(locMinZ) - locMaxX = (mc.thePlayer.posX - 200).coerceIn(0.0, 624.0).coerceAtLeast(locMaxX) - locMaxY = mc.thePlayer.posY.coerceIn(0.0, 256.0).coerceAtLeast(locMaxY) - locMaxZ = (mc.thePlayer.posZ - 200).coerceIn(0.0, 624.0).coerceAtLeast(locMaxZ) - locX = (locMinX + locMaxX) / 2 - locY = (locMinY + locMaxY) / 2 - locZ = (locMinZ + locMaxZ) / 2 - } - - fun exists(): Boolean { - return locX != null && locY != null && locZ != null - } - - fun drawWaypoint(text: String, partialTicks: Float, matrixStack: UMatrixStack) { - if (exists()) - RenderUtil.renderWaypointText(text, locX!! + 200, locY!!, locZ!! + 200, partialTicks, matrixStack) - } - - fun drawOnMap(size: Int, color: Int) { - if (exists()) - RenderUtil.drawRect(locX!! - size, locZ!! - size, locX!! + size, locZ!! + size, color) - } - - override fun toString(): String { - return String.format("%.0f", locX?.plus(200)) + " " + String.format( - "%.0f", - locY - ) + " " + String.format( - "%.0f", - locZ?.plus(200) - ) - } } } diff --git a/src/main/kotlin/gg/skytils/skytilsmod/listeners/DungeonListener.kt b/src/main/kotlin/gg/skytils/skytilsmod/listeners/DungeonListener.kt index a0bced94d..64d32f96d 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/listeners/DungeonListener.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/listeners/DungeonListener.kt @@ -24,6 +24,7 @@ import gg.essential.lib.caffeine.cache.Expiry import gg.essential.universal.UChat import gg.skytils.hypixel.types.skyblock.Pet import gg.skytils.skytilsmod.Skytils +import gg.skytils.skytilsmod.Skytils.Companion.IO import gg.skytils.skytilsmod.Skytils.Companion.failPrefix import gg.skytils.skytilsmod.Skytils.Companion.mc import gg.skytils.skytilsmod.commands.impl.RepartyCommand @@ -35,14 +36,27 @@ import gg.skytils.skytilsmod.features.impl.dungeons.DungeonFeatures import gg.skytils.skytilsmod.features.impl.dungeons.DungeonTimer import gg.skytils.skytilsmod.features.impl.dungeons.ScoreCalculation import gg.skytils.skytilsmod.features.impl.dungeons.catlas.core.DungeonMapPlayer +import gg.skytils.skytilsmod.features.impl.dungeons.catlas.core.map.Room +import gg.skytils.skytilsmod.features.impl.dungeons.catlas.core.map.RoomType import gg.skytils.skytilsmod.features.impl.dungeons.catlas.handlers.DungeonInfo +import gg.skytils.skytilsmod.features.impl.dungeons.catlas.utils.ScanUtils import gg.skytils.skytilsmod.features.impl.handlers.CooldownTracker import gg.skytils.skytilsmod.features.impl.handlers.SpiritLeap +import gg.skytils.skytilsmod.listeners.ServerPayloadInterceptor.getResponse import gg.skytils.skytilsmod.mixins.transformers.accessors.AccessorChatComponentText import gg.skytils.skytilsmod.utils.* import gg.skytils.skytilsmod.utils.NumberUtil.addSuffix import gg.skytils.skytilsmod.utils.NumberUtil.romanToDecimal +import gg.skytils.skytilsws.client.WSClient +import gg.skytils.skytilsws.shared.packet.C2SPacketDungeonEnd +import gg.skytils.skytilsws.shared.packet.C2SPacketDungeonRoom +import gg.skytils.skytilsws.shared.packet.C2SPacketDungeonRoomSecret +import gg.skytils.skytilsws.shared.packet.C2SPacketDungeonStart +import kotlinx.coroutines.async +import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import net.hypixel.modapi.packet.impl.clientbound.ClientboundPartyInfoPacket +import net.hypixel.modapi.packet.impl.serverbound.ServerboundPartyInfoPacket import net.minecraft.entity.player.EntityPlayer import net.minecraft.network.play.server.S02PacketChat import net.minecraft.util.ResourceLocation @@ -50,6 +64,7 @@ import net.minecraftforge.client.event.ClientChatReceivedEvent import net.minecraftforge.event.world.WorldEvent import net.minecraftforge.fml.common.eventhandler.EventPriority import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import java.util.concurrent.ConcurrentLinkedQueue object DungeonListener { val team = hashMapOf() @@ -99,6 +114,8 @@ object DungeonListener { private val keyPickupRegex = Regex("§r§e§lRIGHT CLICK §r§7on §r§7.+?§r§7 to open it\\. This key can only be used to open §r§a(?\\d+)§r§7 door!§r") private val witherDoorOpenedRegex = Regex("^(?:\\[.+?] )?(?\\w+) opened a WITHER door!$") private const val bloodOpenedString = "§r§cThe §r§c§lBLOOD DOOR§r§c has been opened!§r" + val outboundRoomQueue = ConcurrentLinkedQueue() + var isSoloDungeon = false @SubscribeEvent fun onWorldLoad(event: WorldEvent.Unload) { @@ -108,6 +125,8 @@ object DungeonListener { missingPuzzles.clear() completedPuzzles.clear() teamCached.clear() + outboundRoomQueue.clear() + isSoloDungeon = false } @SubscribeEvent @@ -117,23 +136,37 @@ object DungeonListener { val text = event.packet.chatComponent.formattedText val unformatted = text.stripControlCodes() if (event.packet.type == 2.toByte()) { - if (Skytils.config.dungeonSecretDisplay) { - secretsRegex.find(text)?.destructured?.also { (secrets, maxSecrets) -> - val sec = secrets.toInt() - val max = maxSecrets.toInt().coerceAtLeast(sec) - - DungeonFeatures.DungeonSecretDisplay.secrets = sec - DungeonFeatures.DungeonSecretDisplay.maxSecrets = max - }.ifNull { - DungeonFeatures.DungeonSecretDisplay.secrets = -1 - DungeonFeatures.DungeonSecretDisplay.maxSecrets = -1 + secretsRegex.find(text)?.destructured?.also { (secrets, maxSecrets) -> + val sec = secrets.toInt() + val max = maxSecrets.toInt().coerceAtLeast(sec) + + DungeonFeatures.DungeonSecretDisplay.secrets = sec + DungeonFeatures.DungeonSecretDisplay.maxSecrets = max + + IO.launch { + val tile = ScanUtils.getRoomFromPos(mc.thePlayer.position) + if (tile is Room && tile.data.name != "Unknown") { + val room = DungeonInfo.uniqueRooms.find { tile in it.tiles } ?: return@launch + if (room.foundSecrets != sec) { + room.foundSecrets = sec + if (team.size > 1) + WSClient.sendPacket(C2SPacketDungeonRoomSecret(SBInfo.server ?: return@launch, room.mainRoom.data.name, sec)) + } + } } + }.ifNull { + DungeonFeatures.DungeonSecretDisplay.secrets = -1 + DungeonFeatures.DungeonSecretDisplay.maxSecrets = -1 } - } else { if (text.stripControlCodes() .trim() == "> EXTRA STATS <" ) { + if (team.size > 1) { + IO.launch { + WSClient.sendPacket(C2SPacketDungeonEnd(SBInfo.server ?: return@launch)) + } + } if (Skytils.config.dungeonDeathCounter) { tickTimer(6) { UChat.chat("§c☠ §lDeaths:§r ${team.values.sumOf { it.deaths }}\n${ @@ -170,6 +203,30 @@ object DungeonListener { } else if (text == bloodOpenedString) { SpiritLeap.doorOpener = null DungeonInfo.keys-- + } else if (text == "§r§aStarting in 1 second.§r") { + IO.launch { + delay(2000) + if (DungeonTimer.dungeonStartTime != -1L && team.size > 1) { + val party = async { + ServerboundPartyInfoPacket().getResponse() + } + val partyMembers = party.await().members.ifEmpty { setOf(mc.thePlayer.uniqueID) }.mapTo(hashSetOf()) { it.toString() } + val entrance = DungeonInfo.uniqueRooms.first { it.mainRoom.data.type == RoomType.ENTRANCE } + WSClient.sendPacket(C2SPacketDungeonStart( + serverId = SBInfo.server ?: return@launch, + floor = DungeonFeatures.dungeonFloor!!, + members = partyMembers, + startTime = DungeonTimer.dungeonStartTime, + entranceLoc = entrance.mainRoom.z * entrance.mainRoom.x + )) + while (DungeonTimer.dungeonStartTime != -1L) { + while (outboundRoomQueue.isNotEmpty()) { + val packet = outboundRoomQueue.poll() ?: continue + WSClient.sendPacket(packet) + } + } + } + } } else { witherDoorOpenedRegex.find(unformatted)?.destructured?.let { (name) -> SpiritLeap.doorOpener = name @@ -417,7 +474,7 @@ object DungeonListener { fun checkSpiritPet() { val teamCopy = team.values.toList() - Skytils.IO.launch { + IO.launch { runCatching { for (teammate in teamCopy) { val name = teammate.playerName diff --git a/src/main/kotlin/gg/skytils/skytilsmod/listeners/ServerPayloadInterceptor.kt b/src/main/kotlin/gg/skytils/skytilsmod/listeners/ServerPayloadInterceptor.kt new file mode 100644 index 000000000..5b3281660 --- /dev/null +++ b/src/main/kotlin/gg/skytils/skytilsmod/listeners/ServerPayloadInterceptor.kt @@ -0,0 +1,135 @@ +/* + * Skytils - Hypixel Skyblock Quality of Life Mod + * Copyright (C) 2020-2024 Skytils + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package gg.skytils.skytilsmod.listeners + +import gg.skytils.skytilsmod.Skytils +import gg.skytils.skytilsmod.Skytils.Companion.IO +import gg.skytils.skytilsmod.Skytils.Companion.mc +import gg.skytils.skytilsmod.core.MC +import gg.skytils.skytilsmod.events.impl.HypixelPacketEvent +import gg.skytils.skytilsmod.events.impl.PacketEvent +import gg.skytils.skytilsmod.mixins.transformers.accessors.AccessorHypixelModAPI +import gg.skytils.skytilsmod.utils.Utils +import gg.skytils.skytilsmod.utils.ifNull +import io.netty.buffer.Unpooled +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first +import net.hypixel.modapi.HypixelModAPI +import net.hypixel.modapi.error.ErrorReason +import net.hypixel.modapi.packet.ClientboundHypixelPacket +import net.hypixel.modapi.packet.impl.clientbound.ClientboundHelloPacket +import net.hypixel.modapi.packet.impl.clientbound.event.ClientboundLocationPacket +import net.hypixel.modapi.packet.impl.serverbound.ServerboundVersionedPacket +import net.hypixel.modapi.serializer.PacketSerializer +import net.minecraft.network.PacketBuffer +import net.minecraft.network.play.client.C17PacketCustomPayload +import net.minecraft.network.play.server.S3FPacketCustomPayload +import net.minecraftforge.fml.common.eventhandler.EventPriority +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import kotlin.time.Duration.Companion.minutes + +object ServerPayloadInterceptor { + private val receivedPackets = MutableSharedFlow() + + @SubscribeEvent(priority = EventPriority.HIGHEST) + fun onReceivePacket(event: PacketEvent.ReceiveEvent) { + if (event.packet is S3FPacketCustomPayload) { + val registry = HypixelModAPI.getInstance().registry + val id = event.packet.channelName + if (registry.isRegistered(id)) { + println("Received Hypixel packet $id") + val data = event.packet.bufferData + synchronized(data) { + data.retain() + runCatching { + val packetSerializer = PacketSerializer(data.duplicate()) + if (!packetSerializer.readBoolean()) { + val reason = ErrorReason.getById(packetSerializer.readVarInt()) + HypixelPacketEvent.FailedEvent(id, reason).postAndCatch() + } else { + val packet = registry.createClientboundPacket(id, packetSerializer) + IO.launch { + receivedPackets.emit(packet) + } + HypixelPacketEvent.ReceiveEvent(packet).postAndCatch() + } + }.onFailure { + it.printStackTrace() + } + data.release() + } + } + } + } + + @SubscribeEvent(priority = EventPriority.HIGHEST) + fun onSendPacket(event: PacketEvent.SendEvent) { + if (event.packet is C17PacketCustomPayload) { + val registry = HypixelModAPI.getInstance().registry + val id = event.packet.channelName + if (registry.isRegistered(id)) { + println("Sent Hypixel packet $id") + HypixelPacketEvent.SendEvent(id).postAndCatch() + } + } + } + + @SubscribeEvent + fun onHypixelPacket(event: HypixelPacketEvent.ReceiveEvent) { + if (event.packet is ClientboundHelloPacket) { + val modAPI = HypixelModAPI.getInstance() + modAPI as AccessorHypixelModAPI + if (modAPI.packetSender == null) { + println("Hypixel Mod API packet sender is not set, Skytils will set the packet sender.") + modAPI.setPacketSender { + return@setPacketSender getNetClientHandler()?.addToSendQueue((it as ServerboundVersionedPacket).toCustomPayload()).ifNull { + println("Failed to send packet ${it.identifier}") + } != null + } + } + Skytils.launch { + while (getNetClientHandler() == null) { + println("Waiting for client handler to be set.") + delay(50L) + } + withContext(Dispatchers.MC) { + modAPI.subscribeToEventPacket(ClientboundLocationPacket::class.java) + modAPI.invokeSendRegisterPacket(true) + } + } + } + } + + fun ServerboundVersionedPacket.toCustomPayload(): C17PacketCustomPayload { + val buffer = PacketBuffer(Unpooled.buffer()) + val serializer = PacketSerializer(buffer) + this.write(serializer) + return C17PacketCustomPayload(this.identifier, buffer) + } + + suspend fun ServerboundVersionedPacket.getResponse(): T = withTimeout(1.minutes) { + val packet: C17PacketCustomPayload = this@getResponse.toCustomPayload() + getNetClientHandler()?.addToSendQueue(packet) + return@withTimeout receivedPackets.filter { it.identifier == this@getResponse.identifier }.first() as T + } + + private fun getNetClientHandler() = mc.netHandler ?: Utils.lastNHPC +} \ No newline at end of file diff --git a/src/main/kotlin/gg/skytils/skytilsmod/mixins/hooks/renderer/TileEntityChestRendererHook.kt b/src/main/kotlin/gg/skytils/skytilsmod/mixins/hooks/renderer/TileEntityChestRendererHook.kt index e44037e34..6ece0a090 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/mixins/hooks/renderer/TileEntityChestRendererHook.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/mixins/hooks/renderer/TileEntityChestRendererHook.kt @@ -1,28 +1,11 @@ -/* - * Skytils - Hypixel Skyblock Quality of Life Mod - * Copyright (C) 2020-2023 Skytils - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ package gg.skytils.skytilsmod.mixins.hooks.renderer +import gg.skytils.skytilsmod.core.Config.threeWeirdosSolverColor import gg.skytils.skytilsmod.features.impl.dungeons.solvers.ThreeWeirdosSolver import gg.skytils.skytilsmod.utils.bindColor import net.minecraft.client.renderer.GlStateManager import net.minecraft.tileentity.TileEntityChest import org.spongepowered.asm.mixin.injection.callback.CallbackInfo -import java.awt.Color fun setChestColor( te: TileEntityChest, @@ -34,7 +17,7 @@ fun setChestColor( ci: CallbackInfo ) { if (te.pos == ThreeWeirdosSolver.riddleChest) { - Color.RED.bindColor() + threeWeirdosSolverColor.bindColor() GlStateManager.disableTexture2D() } } @@ -49,4 +32,4 @@ fun setChestColorPost( ci: CallbackInfo ) { GlStateManager.enableTexture2D() -} +} \ No newline at end of file diff --git a/src/main/kotlin/gg/skytils/skytilsmod/utils/SBInfo.kt b/src/main/kotlin/gg/skytils/skytilsmod/utils/SBInfo.kt index 31155b573..239546d6c 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/utils/SBInfo.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/utils/SBInfo.kt @@ -17,27 +17,23 @@ */ package gg.skytils.skytilsmod.utils -import gg.skytils.skytilsmod.Skytils -import gg.skytils.skytilsmod.Skytils.Companion.json import gg.skytils.skytilsmod.Skytils.Companion.mc -import gg.skytils.skytilsmod.events.impl.skyblock.LocrawReceivedEvent -import gg.skytils.skytilsmod.events.impl.PacketEvent -import gg.skytils.skytilsmod.events.impl.SendChatMessageEvent +import gg.skytils.skytilsmod.events.impl.HypixelPacketEvent +import gg.skytils.skytilsmod.events.impl.skyblock.LocationChangeEvent import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationException import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.* +import net.hypixel.modapi.packet.impl.clientbound.event.ClientboundLocationPacket import net.minecraft.client.gui.inventory.GuiChest import net.minecraft.inventory.ContainerChest -import net.minecraft.network.play.client.C01PacketChatMessage -import net.minecraft.network.play.server.S02PacketChat import net.minecraftforge.client.event.GuiOpenEvent import net.minecraftforge.event.world.WorldEvent import net.minecraftforge.fml.common.eventhandler.EventPriority import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import net.minecraftforge.fml.common.gameevent.TickEvent import net.minecraftforge.fml.common.gameevent.TickEvent.ClientTickEvent +import net.minecraftforge.fml.common.network.FMLNetworkEvent.ClientDisconnectionFromServerEvent import java.text.ParseException import java.text.SimpleDateFormat import java.util.* @@ -55,19 +51,17 @@ object SBInfo { var location = "" var date = "" var time = "" - var objective: String? = "" - var mode: String? = "" + var objective: String? = null + var mode: String? = null + var server: String? = null var currentTimeDate: Date? = null + var lastLocationPacket: ClientboundLocationPacket? = null @JvmField var lastOpenContainerName: String? = null - private var lastManualLocRaw: Long = -1 - private var lastLocRaw: Long = -1 - private var joinedWorld: Long = -1 - private var locraw: LocrawObject? = null private val junkRegex = Regex("[^\u0020-\u0127û]") - @SubscribeEvent + @SubscribeEvent(priority = EventPriority.HIGHEST) fun onGuiOpen(event: GuiOpenEvent) { if (!Utils.inSkyblock) return if (event.gui is GuiChest) { @@ -80,46 +74,25 @@ object SBInfo { @SubscribeEvent fun onWorldChange(event: WorldEvent.Unload) { - lastLocRaw = -1 - locraw = null - mode = null - joinedWorld = System.currentTimeMillis() lastOpenContainerName = null } @SubscribeEvent - fun onSendChatMessage(event: SendChatMessageEvent) { - val msg = event.message - if (msg.trim().startsWith("/locraw")) { - lastManualLocRaw = System.currentTimeMillis() - } - } - - @SubscribeEvent(priority = EventPriority.LOW, receiveCanceled = true) - fun onChatMessage(event: PacketEvent.ReceiveEvent) { - if (event.packet is S02PacketChat) { - val unformatted = event.packet.chatComponent.unformattedText - if (unformatted.startsWith("{") && unformatted.endsWith("}")) { - try { - val obj = json.decodeFromString(unformatted) - if (System.currentTimeMillis() - lastManualLocRaw > 5000) { - Utils.cancelChatPacket(event) - } - locraw = obj - mode = obj.mode - LocrawReceivedEvent(obj).postAndCatch() - } catch (e: SerializationException) { - e.printStackTrace() - } - } - } + fun onDisconnect(event: ClientDisconnectionFromServerEvent) { + mode = null + server = null + lastLocationPacket = null } - @SubscribeEvent - fun onPacket(event: PacketEvent.SendEvent) { - if (Utils.isOnHypixel && event.packet is C01PacketChatMessage) { - if (event.packet.message.startsWith("/locraw")) { - lastLocRaw = System.currentTimeMillis() + @SubscribeEvent(priority = EventPriority.HIGH) + fun onHypixelPacket(event: HypixelPacketEvent.ReceiveEvent) { + if (event.packet is ClientboundLocationPacket) { + Utils.checkThreadAndQueue { + mode = event.packet.mode.orElse(null) + server = event.packet.serverName + lastLocationPacket = event.packet + println(event.packet) + LocationChangeEvent(event.packet).postAndCatch() } } } @@ -127,11 +100,6 @@ object SBInfo { @SubscribeEvent fun onTick(event: ClientTickEvent) { if (event.phase != TickEvent.Phase.START || mc.thePlayer == null || mc.theWorld == null || !Utils.inSkyblock) return - val currentTime = System.currentTimeMillis() - if (locraw == null && currentTime - joinedWorld > 1300 && currentTime - lastLocRaw > 15000) { - lastLocRaw = System.currentTimeMillis() - Skytils.sendMessageQueue.add("/locraw") - } try { val lines = ScoreboardUtil.fetchScoreboardLines().map { it.stripControlCodes() } if (lines.size >= 5) { @@ -222,13 +190,4 @@ enum class SkyblockIsland(val displayName: String, val mode: String) { encodeStringElement(descriptor, 1, value.mode) } } -} - - -@Serializable -data class LocrawObject( - val server: String, - val gametype: String = "unknown", - val mode: String = "unknown", - val map: String = "unknown" -) \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/kotlin/gg/skytils/skytilsmod/utils/Utils.kt b/src/main/kotlin/gg/skytils/skytilsmod/utils/Utils.kt index f5f2f458c..b1b867963 100644 --- a/src/main/kotlin/gg/skytils/skytilsmod/utils/Utils.kt +++ b/src/main/kotlin/gg/skytils/skytilsmod/utils/Utils.kt @@ -42,6 +42,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.launch import net.minecraft.client.gui.ChatLine import net.minecraft.client.gui.GuiNewChat +import net.minecraft.client.network.NetHandlerPlayClient import net.minecraft.client.settings.GameSettings import net.minecraft.entity.Entity import net.minecraft.entity.EntityLivingBase @@ -89,6 +90,8 @@ object Utils { @JvmField var lastRenderedSkullEntity: EntityLivingBase? = null + var lastNHPC: NetHandlerPlayClient? = null + @JvmStatic var random = Random() diff --git a/src/main/kotlin/gg/skytils/skytilsws/client/PacketHandler.kt b/src/main/kotlin/gg/skytils/skytilsws/client/PacketHandler.kt new file mode 100644 index 000000000..f4b0bb0ed --- /dev/null +++ b/src/main/kotlin/gg/skytils/skytilsws/client/PacketHandler.kt @@ -0,0 +1,102 @@ +/* + * Skytils - Hypixel Skyblock Quality of Life Mod + * Copyright (C) 2020-2024 Skytils + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package gg.skytils.skytilsws.client + +import gg.skytils.skytilsmod.Reference +import gg.skytils.skytilsmod.Skytils.Companion.mc +import gg.skytils.skytilsmod.features.impl.dungeons.catlas.core.map.Room +import gg.skytils.skytilsmod.features.impl.dungeons.catlas.core.map.Unknown +import gg.skytils.skytilsmod.features.impl.dungeons.catlas.handlers.DungeonInfo +import gg.skytils.skytilsmod.features.impl.dungeons.catlas.utils.ScanUtils +import gg.skytils.skytilsmod.features.impl.mining.CHWaypoints +import gg.skytils.skytilsmod.features.impl.mining.CHWaypoints.CHInstance +import gg.skytils.skytilsmod.features.impl.mining.CHWaypoints.chWaypointsList +import gg.skytils.skytilsmod.utils.SBInfo +import gg.skytils.skytilsws.shared.IPacketHandler +import gg.skytils.skytilsws.shared.SkytilsWS +import gg.skytils.skytilsws.shared.packet.* +import io.ktor.websocket.* +import kotlinx.coroutines.coroutineScope +import net.minecraft.util.BlockPos +import java.util.* + +object PacketHandler : IPacketHandler { + suspend fun handleLogin(session: WebSocketSession, packet: S2CPacketAcknowledge) { + val serverId = UUID.randomUUID().toString().replace("-".toRegex(), "") + mc.sessionService.joinServer(mc.session.profile, mc.session.token, serverId) + WSClient.sendPacket(C2SPacketLogin(mc.session.username, mc.session.profile.id.toString(), Reference.VERSION, SkytilsWS.version, serverId)) + } + + override suspend fun processPacket(session: WebSocketSession, packet: Packet) { + println("Received packet: $packet") + when (packet) { + is S2CPacketAcknowledge -> { + if (packet.wsVersion != SkytilsWS.version) { + session.close(CloseReason(CloseReason.Codes.CANNOT_ACCEPT, "Incompatible WS version")) + } else { + coroutineScope { + handleLogin(session, packet) + } + } + } + is S2CPacketDungeonRoomSecret -> { + DungeonInfo.uniqueRooms.find { it.mainRoom.data.name == packet.roomId }?.let { + if (packet.secretCount > (it.foundSecrets ?: -1)) { + it.foundSecrets = packet.secretCount + } + } + } + is S2CPacketDungeonRoom -> { + val room = DungeonInfo.dungeonList[packet.row * 11 + packet.col] + if (room is Unknown || (room as? Room)?.data?.name == "Unknown") { + val data = ScanUtils.roomList.find { it.name == packet.roomId } + DungeonInfo.dungeonList[packet.row * 11 + packet.col] = Room(packet.x, packet.z, data ?: return).apply { + isSeparator = packet.isSeparator + core = packet.core + addToUnique(packet.row, packet.col) + } + } + } + is S2CPacketCHReset -> { + CHWaypoints.waypoints.remove(packet.serverId) + } + is S2CPacketCHWaypoint -> { + if (SBInfo.server == packet.serverId) { + if (mc.theWorld.worldTime < packet.serverTime) { + WSClient.sendPacket(C2SPacketCHReset(packet.serverId)) + } else { + CHWaypoints.CrystalHollowsMap.Locations.entries.find { it.packetType == packet.type }?.let { + if (!it.loc.exists()) { + it.loc.locX = packet.x.toDouble() + it.loc.locY = packet.y.toDouble() + it.loc.locZ = packet.z.toDouble() + } + } + } + } else { + val instance = chWaypointsList.getOrPut(SBInfo.server ?: "") { CHInstance() } + instance.waypoints[packet.type] = BlockPos(packet.x, packet.y, packet.z) + } + } + else -> { + session.close(CloseReason(CloseReason.Codes.CANNOT_ACCEPT, "Unknown packet type")) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/gg/skytils/skytilsws/client/WSClient.kt b/src/main/kotlin/gg/skytils/skytilsws/client/WSClient.kt new file mode 100644 index 000000000..76e176418 --- /dev/null +++ b/src/main/kotlin/gg/skytils/skytilsws/client/WSClient.kt @@ -0,0 +1,97 @@ +/* + * Skytils - Hypixel Skyblock Quality of Life Mod + * Copyright (C) 2020-2024 Skytils + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package gg.skytils.skytilsws.client + +import gg.skytils.skytilsmod.Skytils +import gg.skytils.skytilsws.shared.SkytilsWS +import gg.skytils.skytilsws.shared.packet.C2SPacketConnect +import gg.skytils.skytilsws.shared.packet.Packet +import io.ktor.client.* +import io.ktor.client.engine.cio.* +import io.ktor.client.plugins.* +import io.ktor.client.plugins.websocket.* +import io.ktor.serialization.kotlinx.* +import io.ktor.websocket.* +import kotlinx.coroutines.channels.ClosedReceiveChannelException +import kotlinx.serialization.ExperimentalSerializationApi +import java.util.zip.Deflater + +object WSClient { + var session: DefaultClientWebSocketSession? = null + val wsClient by lazy { + HttpClient(CIO) { + install(UserAgent) { + agent = "Skytils/${Skytils.VERSION} SkytilsWS/${SkytilsWS.version}" + } + + install(WebSockets) { + pingInterval = 59_000L + @OptIn(ExperimentalSerializationApi::class) + contentConverter = KotlinxWebsocketSerializationConverter(SkytilsWS.packetSerializer) + extensions { + install(WebSocketDeflateExtension) { + compressionLevel = Deflater.DEFAULT_COMPRESSION + compressIfBiggerThan(bytes = 4 * 1024) + } + } + } + + engine { + endpoint { + connectTimeout = 10000 + keepAliveTime = 60000 + } + https { + trustManager = Skytils.trustManager + } + } + } + } + + suspend fun openConnection() { + if (session != null) error("Session already open") + + wsClient.webSocketSession(System.getProperty("skytils.websocketURL", "wss://ws.skytils.gg/ws")).apply { + session = this + try { + sendSerialized(C2SPacketConnect(SkytilsWS.version, Skytils.VERSION)) + while (true) { + val packet = receiveDeserialized() + PacketHandler.processPacket(this@apply, packet) + } + } catch(e: ClosedReceiveChannelException) { + e.printStackTrace() + closeExceptionally(e) + } catch (e: Throwable) { + e.printStackTrace() + closeExceptionally(e) + } finally { + session = null + } + } + } + + suspend fun closeConnection() { + session?.close(CloseReason(CloseReason.Codes.NORMAL, "Client closed connection")) + } + + suspend fun sendPacket(packet: Packet) { + session?.sendSerialized(packet) ?: error("Tried to send packet but session was null") + } +} \ No newline at end of file diff --git a/src/main/resources/assets/catlas/rooms.json b/src/main/resources/assets/catlas/rooms.json index fc494dfe7..49abad070 100644 --- a/src/main/resources/assets/catlas/rooms.json +++ b/src/main/resources/assets/catlas/rooms.json @@ -518,8 +518,11 @@ "type": "PUZZLE", "cores": [ 2051424561, + 1262122263, + 1673994041, 884728242, - 1262122263 + 1328525306, + 161828987 ] }, { diff --git a/src/main/resources/assets/skytils/lang/en_US.lang b/src/main/resources/assets/skytils/lang/en_US.lang index a992c140c..c9f98c2e5 100644 --- a/src/main/resources/assets/skytils/lang/en_US.lang +++ b/src/main/resources/assets/skytils/lang/en_US.lang @@ -103,12 +103,14 @@ skytils.config.dungeons.solvers.highest_blaze_color=Highest Blaze Color skytils.config.dungeons.solvers.next_blaze_color=Next Blaze Color skytils.config.dungeons.solvers.line_to_next_blaze_color=Line to Next Blaze Color skytils.config.dungeons.solvers.boulder_solver=Boulder Solver +skytils.config.dungeons.solvers.boulder_solver_color=Boulder Solver Color skytils.config.dungeons.solvers.creeper_beams_solver=Creeper Beams Solver skytils.config.dungeons.solvers.ice_fill_solver=Ice Fill Solver skytils.config.dungeons.solvers.ice_path_solver=Ice Path Solver skytils.config.dungeons.solvers.teleport_maze_solver=Teleport Maze Solver skytils.config.dungeons.solvers.teleport_maze_solver_color=Teleport Maze Solver Color skytils.config.dungeons.solvers.three_weirdos_solver=Three Weirdos Solver +skytils.config.dungeons.solvers.three_weirdos_solver_color=Three Weirdos Solver Color skytils.config.dungeons.solvers.tic_tac_toe_solver=Tic Tac Toe Solver skytils.config.dungeons.solvers.tic_tac_toe_solver_color=Tic Tac Toe Solver Color skytils.config.dungeons.solvers.trivia_solver=Trivia Solver @@ -311,7 +313,7 @@ skytils.config.miscellaneous.quality_of_life.reset_found_relic_waypoints=Reset F skytils.config.miscellaneous.quality_of_life.potion_duration_notifications=Potion Duration Notifications skytils.config.miscellaneous.quality_of_life.stop_hook_sinking_in_lava=Stop Hook Sinking in Lava skytils.config.miscellaneous.quality_of_life.fishing_hook_age=Fishing Hook Age -skytils.config.miscellaneous.quality_of_life.tropy_fish_tracker=Tropy Fish Tracker +skytils.config.miscellaneous.quality_of_life.trophy_fish_tracker=Tropy Fish Tracker skytils.config.miscellaneous.quality_of_life.show_trophy_fish_totals=Show Trophy Fish Totals skytils.config.miscellaneous.quality_of_life.show_total_trophy_fish=Show Total Trophy Fish skytils.config.pets.quality_of_life.autopet_message_hider=Autopet Message Hider diff --git a/src/main/resources/assets/skytils/lang/zh_CN.lang b/src/main/resources/assets/skytils/lang/zh_CN.lang index e1f2be2bb..8a96c2ae5 100644 --- a/src/main/resources/assets/skytils/lang/zh_CN.lang +++ b/src/main/resources/assets/skytils/lang/zh_CN.lang @@ -311,7 +311,7 @@ skytils.config.miscellaneous.quality_of_life.reset_found_relic_waypoints=重置 skytils.config.miscellaneous.quality_of_life.potion_duration_notifications=药水效果即将消失警告 skytils.config.miscellaneous.quality_of_life.stop_hook_sinking_in_lava=防止鱼钩在岩浆中下沉 skytils.config.miscellaneous.quality_of_life.fishing_hook_age=鱼钩计时器 -skytils.config.miscellaneous.quality_of_life.tropy_fish_tracker=奖杯鱼数据跟踪器 +skytils.config.miscellaneous.quality_of_life.trophy_fish_tracker=奖杯鱼数据跟踪器 skytils.config.miscellaneous.quality_of_life.show_trophy_fish_totals=显示每个奖杯鱼总数 skytils.config.miscellaneous.quality_of_life.show_total_trophy_fish=显示总奖杯鱼数 skytils.config.pets.quality_of_life.autopet_message_hider=自动换宠物消息隐藏 diff --git a/src/main/resources/mixins.skytils.json b/src/main/resources/mixins.skytils.json index 02f0872fc..7ea9201b0 100644 --- a/src/main/resources/mixins.skytils.json +++ b/src/main/resources/mixins.skytils.json @@ -20,6 +20,8 @@ "accessors.AccessorGuiMainMenu", "accessors.AccessorGuiNewChat", "accessors.AccessorGuiStreamUnavailable", + "accessors.AccessorHypixelModAPI", + "accessors.AccessorHypixelPacketRegistry", "accessors.AccessorMinecraft", "accessors.AccessorModelDragon", "accessors.AccessorRenderItem", @@ -96,6 +98,7 @@ "verbose": true, "client": [ "gui.MixinGuiEditSign", + "renderer.MixinModelBiped", "util.MixinMouseHelper" ] } \ No newline at end of file diff --git a/ws-shared b/ws-shared new file mode 160000 index 000000000..826fc2b77 --- /dev/null +++ b/ws-shared @@ -0,0 +1 @@ +Subproject commit 826fc2b779144272195eb055c0e49eebffe52892