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