Skip to content

Commit

Permalink
feat: add dungeon syncing through ws
Browse files Browse the repository at this point in the history
chore: update ws URL

fix: rebase errors

fix: rebase error

fix: queue room packets from before dungeon start

fix: rebase error didn't close SkytilsWS connection
  • Loading branch information
My-Name-Is-Jeff committed Jun 1, 2024
1 parent 5dace83 commit ab0088b
Show file tree
Hide file tree
Showing 12 changed files with 261 additions and 18 deletions.
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,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"))

Expand Down Expand Up @@ -147,6 +147,7 @@ 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")
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ pluginManagement {
rootProject.name = "SkytilsMod"
include("events")
include("hypixel-api:types")
include("ws-shared")
29 changes: 28 additions & 1 deletion src/main/kotlin/gg/skytils/skytilsmod/Skytils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ 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
Expand Down Expand Up @@ -72,18 +71,25 @@ 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 gg.skytils.skytilsws.shared.SkytilsWS
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.cache.*
import io.ktor.client.plugins.compression.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.websocket.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.websocket.*
import kotlinx.coroutines.*
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import net.hypixel.modapi.packet.impl.clientbound.ClientboundPingPacket
Expand Down Expand Up @@ -123,6 +129,7 @@ import java.security.KeyStore
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.ThreadPoolExecutor
import java.util.zip.Deflater
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
import kotlin.coroutines.CoroutineContext
Expand Down Expand Up @@ -260,6 +267,18 @@ class Skytils {
trustManager = UnionX509TrustManager(backingManager, ourManager)
}
}

install(WebSockets) {
pingInterval = 5_000L
@OptIn(ExperimentalSerializationApi::class)
contentConverter = KotlinxWebsocketSerializationConverter(SkytilsWS.packetSerializer)
extensions {
install(WebSocketDeflateExtension) {
compressionLevel = Deflater.DEFAULT_COMPRESSION
compressIfBiggerThan(bytes = 4 * 1024)
}
}
}
}

val areaRegex = Regex("§r§b§l(?<area>[\\w]+): §r§7(?<loc>[\\w ]+)§r")
Expand Down Expand Up @@ -545,6 +564,10 @@ class Skytils {
IO.launch {
TrophyFish.loadFromApi()
}

IO.launch {
WSClient.openConnection()
}
}

@SubscribeEvent
Expand Down Expand Up @@ -605,6 +628,10 @@ class Skytils {
Utils.isOnHypixel = false
Utils.skyblock = false
Utils.dungeons = false

IO.launch {
WSClient.closeConnection()
}
}

@SubscribeEvent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,21 @@

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.listeners.ServerPayloadInterceptor.getResponse
import gg.skytils.skytilsmod.utils.SBInfo
import gg.skytils.skytilsws.client.WSClient
import gg.skytils.skytilsws.shared.packet.C2SPacketDungeonRoom
import gg.skytils.skytilsws.shared.packet.C2SPacketDungeonRoomSecret
import kotlinx.coroutines.launch
import net.hypixel.modapi.packet.impl.clientbound.ClientboundLocationPacket
import net.hypixel.modapi.packet.impl.serverbound.ServerboundLocationPacket
import net.minecraft.init.Blocks
import net.minecraft.util.BlockPos

Expand Down Expand Up @@ -73,6 +83,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))
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ import gg.essential.universal.UMatrixStack
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.tweaker.DependencyLoader
import gg.skytils.skytilsmod.utils.*
import kotlinx.serialization.EncodeDefault
Expand Down
80 changes: 68 additions & 12 deletions src/main/kotlin/gg/skytils/skytilsmod/listeners/DungeonListener.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -35,21 +36,36 @@ 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.RoomType
import gg.skytils.skytilsmod.features.impl.dungeons.catlas.handlers.DungeonInfo
import gg.skytils.skytilsmod.features.impl.dungeons.catlas.handlers.DungeonScanner
import gg.skytils.skytilsmod.features.impl.dungeons.catlas.utils.MapUtils
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.C2SPacketDungeonRoom
import gg.skytils.skytilsws.shared.packet.C2SPacketDungeonRoomSecret
import gg.skytils.skytilsws.shared.packet.C2SPacketStartDungeon
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.hypixel.modapi.packet.impl.clientbound.ClientboundLocationPacket
import net.hypixel.modapi.packet.impl.clientbound.ClientboundPartyInfoPacket
import net.hypixel.modapi.packet.impl.serverbound.ServerboundLocationPacket
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
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.CopyOnWriteArrayList

object DungeonListener {
val team = hashMapOf<String, DungeonTeammate>()
Expand Down Expand Up @@ -99,6 +115,7 @@ 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(?<num>\\d+)§r§7 door!§r")
private val witherDoorOpenedRegex = Regex("^(?:\\[.+?] )?(?<name>\\w+) opened a WITHER door!$")
private const val bloodOpenedString = "§r§cThe §r§c§lBLOOD DOOR§r§c has been opened!§r"
val outboundRoomQueue = arrayListOf<C2SPacketDungeonRoom>()

@SubscribeEvent
fun onWorldLoad(event: WorldEvent.Unload) {
Expand All @@ -108,6 +125,7 @@ object DungeonListener {
missingPuzzles.clear()
completedPuzzles.clear()
teamCached.clear()
outboundRoomQueue.clear()
}

@SubscribeEvent
Expand All @@ -117,19 +135,28 @@ 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 x = ((mc.thePlayer.posX - DungeonScanner.startX + 15) * MapUtils.coordMultiplier / (MapUtils.mapRoomSize + 4) * 2).toInt()
val z = ((mc.thePlayer.posZ - DungeonScanner.startZ + 15) * MapUtils.coordMultiplier / (MapUtils.mapRoomSize + 4) * 2).toInt()

val room = DungeonInfo.uniqueRooms.find { it.tiles.any { it.x == x && it.z == z } }

if (room != null && room.foundSecrets != sec) {
room.foundSecrets = sec
WSClient.sendPacket(C2SPacketDungeonRoomSecret(SBInfo.locraw?.server ?: ServerboundLocationPacket().getResponse<ClientboundLocationPacket>(mc.netHandler).serverName, room.mainRoom.data.name, sec))
}
}
}.ifNull {
DungeonFeatures.DungeonSecretDisplay.secrets = -1
DungeonFeatures.DungeonSecretDisplay.maxSecrets = -1
}

} else {
if (text.stripControlCodes()
.trim() == "> EXTRA STATS <"
Expand Down Expand Up @@ -170,6 +197,35 @@ 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) {
val location = async {
ServerboundLocationPacket().getResponse<ClientboundLocationPacket>(mc.netHandler)
}
val party = async {
ServerboundPartyInfoPacket().getResponse<ClientboundPartyInfoPacket>(mc.netHandler)
}
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(C2SPacketStartDungeon(
serverId = location.await().serverName,
floor = DungeonFeatures.dungeonFloor!!,
members = partyMembers,
startTime = DungeonTimer.dungeonStartTime,
entranceLoc = entrance.mainRoom.z * entrance.mainRoom.x
))
while (DungeonTimer.dungeonStartTime != -1L) {
val itr = outboundRoomQueue.iterator()
while (itr.hasNext()) {
val packet = itr.next()
itr.remove()
WSClient.sendPacket(packet)
}
}
}
}
} else {
witherDoorOpenedRegex.find(unformatted)?.destructured?.let { (name) ->
SpiritLeap.doorOpener = name
Expand Down Expand Up @@ -417,7 +473,7 @@ object DungeonListener {

fun checkSpiritPet() {
val teamCopy = team.values.toList()
Skytils.IO.launch {
IO.launch {
runCatching {
for (teammate in teamCopy) {
val name = teammate.playerName
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/gg/skytils/skytilsmod/utils/SBInfo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ object SBInfo {
private var lastManualLocRaw: Long = -1
private var lastLocRaw: Long = -1
private var joinedWorld: Long = -1
private var locraw: LocrawObject? = null
var locraw: LocrawObject? = null
private val junkRegex = Regex("[^\u0020-\u0127û]")

@SubscribeEvent
Expand Down
76 changes: 76 additions & 0 deletions src/main/kotlin/gg/skytils/skytilsws/client/PacketHandler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/

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.skytilsws.shared.IPacketHandler
import gg.skytils.skytilsws.shared.SkytilsWS
import gg.skytils.skytilsws.shared.packet.*
import io.ktor.websocket.*
import kotlinx.coroutines.coroutineScope
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)
}
}
}
else -> {
session.close(CloseReason(CloseReason.Codes.CANNOT_ACCEPT, "Unknown packet type"))
}
}
}
}
Loading

0 comments on commit ab0088b

Please sign in to comment.