[WIP] Add mod api

This commit is contained in:
Linnea Gräf
2024-05-29 21:09:07 +02:00
parent fac6103658
commit e630426656
11 changed files with 457 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.apis.ingame
import io.netty.buffer.ByteBuf
import net.minecraft.network.codec.PacketCodec
import net.minecraft.network.packet.CustomPayload
import net.minecraft.util.Identifier
interface FirmamentCustomPayload : CustomPayload {
class Unhandled private constructor(val identifier: Identifier) : FirmamentCustomPayload {
override fun getId(): CustomPayload.Id<out CustomPayload> {
return CustomPayload.id(identifier.toString())
}
companion object {
fun <B : ByteBuf> createCodec(identifier: Identifier): PacketCodec<B, Unhandled> {
return object : PacketCodec<B, Unhandled> {
override fun decode(buf: B): Unhandled {
return Unhandled(identifier)
}
override fun encode(buf: B, value: Unhandled) {
// we will never send an unhandled packet stealthy
}
}
}
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.apis.ingame
import net.minecraft.network.packet.c2s.common.CustomPayloadC2SPacket
import net.minecraft.text.Text
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.apis.ingame.packets.PartyInfoRequest
import moe.nea.firmament.apis.ingame.packets.PartyInfoResponse
import moe.nea.firmament.commands.thenExecute
import moe.nea.firmament.events.CommandEvent
import moe.nea.firmament.events.FirmamentCustomPayloadEvent
import moe.nea.firmament.events.subscription.SubscriptionOwner
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.features.debug.DeveloperFeatures
import moe.nea.firmament.util.MC
object HypixelModAPI : SubscriptionOwner {
init {
InGameCodecWrapper.Direction.C2S.customCodec =
InGameCodecWrapper.createStealthyCodec(
PartyInfoRequest.intoType()
)
InGameCodecWrapper.Direction.S2C.customCodec =
InGameCodecWrapper.createStealthyCodec(
PartyInfoResponse.intoType()
)
}
@JvmStatic
fun sendRequest(packet: FirmamentCustomPayload) {
MC.networkHandler?.sendPacket(CustomPayloadC2SPacket(packet))
}
@Subscribe
fun testCommand(event: CommandEvent.SubCommand) {
event.subcommand("sendpartyrequest") {
thenExecute {
sendRequest(PartyInfoRequest(1))
}
}
}
@Subscribe
fun logEvents(event: FirmamentCustomPayloadEvent) {
MC.sendChat(Text.stringifiedTranslatable("firmament.modapi.event", event.toString()))
}
override val delegateFeature: FirmamentFeature
get() = DeveloperFeatures
}

View File

@@ -0,0 +1,51 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.apis.ingame
import net.minecraft.network.PacketByteBuf
import net.minecraft.network.codec.PacketCodec
import net.minecraft.network.packet.CustomPayload
class InGameCodecWrapper(
val wrapped: PacketCodec<PacketByteBuf, CustomPayload>,
val direction: Direction,
) : PacketCodec<PacketByteBuf, CustomPayload> {
enum class Direction {
S2C,
C2S,
;
var customCodec: PacketCodec<PacketByteBuf, FirmamentCustomPayload> = createStealthyCodec()
}
companion object {
fun createStealthyCodec(vararg codecs: CustomPayload.Type<PacketByteBuf, out FirmamentCustomPayload>): PacketCodec<PacketByteBuf, FirmamentCustomPayload> {
return CustomPayload.createCodec(
{ FirmamentCustomPayload.Unhandled.createCodec(it) },
codecs.toList()
) as PacketCodec<PacketByteBuf, FirmamentCustomPayload>
}
}
override fun decode(buf: PacketByteBuf): CustomPayload {
val duplicateBuffer = PacketByteBuf(buf.slice())
val original = wrapped.decode(buf)
val duplicate = direction.customCodec.decode(duplicateBuffer)
if (duplicate is FirmamentCustomPayload.Unhandled)
return original
return JoinedCustomPayload(original, duplicate)
}
override fun encode(buf: PacketByteBuf, value: CustomPayload) {
if (value is FirmamentCustomPayload) {
direction.customCodec.encode(buf, value)
} else {
wrapped.encode(buf, value)
}
}
}

View File

@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.apis.ingame
import net.minecraft.network.packet.CustomPayload
/**
* A class to smuggle two parsed instances of the same custom payload packet.
*/
class JoinedCustomPayload(
val original: CustomPayload,
val smuggled: FirmamentCustomPayload
) : CustomPayload {
companion object {
val joinedId = CustomPayload.id<JoinedCustomPayload>("firmament:joined")
}
override fun getId(): CustomPayload.Id<out JoinedCustomPayload> {
return joinedId
}
}

View File

@@ -0,0 +1,134 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.apis.ingame.packets
import io.netty.buffer.ByteBuf
import java.util.UUID
import net.minecraft.network.PacketByteBuf
import net.minecraft.network.codec.PacketCodec
import net.minecraft.network.codec.PacketCodecs
import net.minecraft.network.packet.CustomPayload
import net.minecraft.util.Uuids
import moe.nea.firmament.apis.ingame.FirmamentCustomPayload
interface FirmamentCustomPayloadMeta<T : FirmamentCustomPayload> {
val ID: CustomPayload.Id<T>
val CODEC: PacketCodec<PacketByteBuf, T>
fun intoType(): CustomPayload.Type<PacketByteBuf, T> {
return CustomPayload.Type(ID, CODEC)
}
}
data class PartyInfoRequest(val version: Int) : FirmamentCustomPayload {
companion object : FirmamentCustomPayloadMeta<PartyInfoRequest> {
override val ID = CustomPayload.id<PartyInfoRequest>("hypixel:party_info")
override val CODEC =
PacketCodecs.VAR_INT.cast<PacketByteBuf>()
.xmap(::PartyInfoRequest, PartyInfoRequest::version)
}
override fun getId(): CustomPayload.Id<out CustomPayload> {
return ID
}
}
sealed interface PartyInfoResponseV
sealed interface HypixelVersionedPacketData<out T>
data class HypixelSuccessfulResponse<T>(val data: T) : HypixelVersionedPacketData<T>
data class HypixelUnknownVersion(val version: Int) : HypixelVersionedPacketData<Nothing>
data class HypixelApiError(val label: String, val errorId: Int) : HypixelVersionedPacketData<Nothing> {
companion object {
fun <B : ByteBuf> createCodec(label: String): PacketCodec<B, HypixelApiError> {
return PacketCodecs.VAR_INT
.cast<B>()
.xmap({ HypixelApiError(label, it) }, HypixelApiError::errorId)
}
}
}
object CodecUtils {
fun <B : PacketByteBuf, T> dispatchVersioned(
versions: Map<Int, PacketCodec<B, out T>>,
errorCodec: PacketCodec<B, HypixelApiError>
): PacketCodec<B, HypixelVersionedPacketData<T>> {
return object : PacketCodec<B, HypixelVersionedPacketData<T>> {
override fun decode(buf: B): HypixelVersionedPacketData<T> {
if (!buf.readBoolean()) {
return errorCodec.decode(buf)
}
val version = buf.readVarInt()
val versionCodec = versions[version]
?: return HypixelUnknownVersion(version)
return HypixelSuccessfulResponse(versionCodec.decode(buf))
}
override fun encode(buf: B, value: HypixelVersionedPacketData<T>?) {
error("Cannot encode a hypixel packet")
}
}
}
fun <B : PacketByteBuf, T> dispatchS2CBoolean(
ifTrue: PacketCodec<B, out T>,
ifFalse: PacketCodec<B, out T>
): PacketCodec<B, T> {
return object : PacketCodec<B, T> {
override fun decode(buf: B): T {
return if (buf.readBoolean()) {
ifTrue.decode(buf)
} else {
ifFalse.decode(buf)
}
}
override fun encode(buf: B, value: T) {
error("Cannot reverse dispatch boolean")
}
}
}
}
data object PartyInfoResponseVUnknown : PartyInfoResponseV
data class PartyInfoResponseV1(
val leader: UUID?,
val members: Set<UUID>,
) : PartyInfoResponseV {
data object PartyMember
companion object {
val CODEC: PacketCodec<PacketByteBuf, PartyInfoResponseV1> =
CodecUtils.dispatchS2CBoolean(
PacketCodec.tuple(
Uuids.PACKET_CODEC, PartyInfoResponseV1::leader,
Uuids.PACKET_CODEC.collect(PacketCodecs.toCollection(::HashSet)), PartyInfoResponseV1::members,
::PartyInfoResponseV1
),
PacketCodec.unit(PartyInfoResponseV1(null, setOf())))
}
}
data class PartyInfoResponse(val data: HypixelVersionedPacketData<PartyInfoResponseV>) : FirmamentCustomPayload {
companion object : FirmamentCustomPayloadMeta<PartyInfoResponse> {
override val ID: CustomPayload.Id<PartyInfoResponse> = CustomPayload.id("hypixel:party_info")
override val CODEC =
CodecUtils
.dispatchVersioned<PacketByteBuf, PartyInfoResponseV>(
mapOf(
1 to PartyInfoResponseV1.CODEC,
),
HypixelApiError.createCodec("PartyInfoResponse"))
.xmap(::PartyInfoResponse, PartyInfoResponse::data)
}
override fun getId(): CustomPayload.Id<out CustomPayload> {
return ID
}
}