Add documentation, move ByteBuf extensions into an object and generify SessionContainer
This commit is contained in:
parent
3c85b6c105
commit
6daa459fa6
23 changed files with 215 additions and 132 deletions
|
@ -1,14 +1,20 @@
|
||||||
package space.blokk
|
package space.blokk
|
||||||
|
|
||||||
import space.blokk.net.SessionContainer
|
import space.blokk.events.EventTargetGroup
|
||||||
|
import space.blokk.net.Session
|
||||||
import space.blokk.server.Server
|
import space.blokk.server.Server
|
||||||
|
|
||||||
interface BlokkProvider {
|
interface BlokkProvider {
|
||||||
val server: Server
|
val server: Server
|
||||||
val sessions: SessionContainer
|
|
||||||
|
/**
|
||||||
|
* [EventTargetGroup] instance containing all sessions connected to the server.
|
||||||
|
*/
|
||||||
|
val sessions: EventTargetGroup<Session>
|
||||||
}
|
}
|
||||||
|
|
||||||
object Blokk: BlokkProvider {
|
object Blokk: BlokkProvider {
|
||||||
|
// Is assigned by BlokkServer using reflection
|
||||||
private var provider: BlokkProvider? = null
|
private var provider: BlokkProvider? = null
|
||||||
override val server get() = provider!!.server
|
override val server get() = provider!!.server
|
||||||
override val sessions get() = provider!!.sessions
|
override val sessions get() = provider!!.sessions
|
||||||
|
|
|
@ -19,6 +19,9 @@ sealed class ChatComponent {
|
||||||
|
|
||||||
data class TextComponent(val text: String, override val extra: ChatComponent? = null): ChatComponent() {
|
data class TextComponent(val text: String, override val extra: ChatComponent? = null): ChatComponent() {
|
||||||
companion object {
|
companion object {
|
||||||
|
/**
|
||||||
|
* Creates a new [TextComponent] instance using [text] and returns it.
|
||||||
|
*/
|
||||||
infix fun of(text: String) = TextComponent(text)
|
infix fun of(text: String) = TextComponent(text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package space.blokk.chat
|
package space.blokk.chat
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Legacy formatting codes. You should use [space.blokk.chat.ChatComponent] whenever it it possible, but sometimes
|
* Legacy formatting codes. You should use [ChatComponent][space.blokk.chat.ChatComponent] whenever it's possible, but sometimes
|
||||||
* these codes are required, for example in the name of a player in the server list sample
|
* these codes are required, for example in
|
||||||
* ([space.blokk.net.protocols.status.ResponsePacket.Players.SampleEntry]).
|
* [the name of a player in a server list sample][space.blokk.net.protocols.status.ResponsePacket.Players.SampleEntry.name].
|
||||||
*/
|
*/
|
||||||
enum class FormattingCode(private val char: Char) {
|
enum class FormattingCode(private val char: Char) {
|
||||||
BLACK('0'),
|
BLACK('0'),
|
||||||
|
|
|
@ -3,3 +3,13 @@ package space.blokk.events
|
||||||
interface Cancellable {
|
interface Cancellable {
|
||||||
var isCancelled: Boolean
|
var isCancelled: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only executes [fn] if [isCancelled][Cancellable.isCancelled] is true.
|
||||||
|
*/
|
||||||
|
inline fun Cancellable.ifCancelled(fn: () -> Unit) = if (isCancelled) fn() else Unit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only executes [fn] if [isCancelled][Cancellable.isCancelled] is false.
|
||||||
|
*/
|
||||||
|
inline fun Cancellable.ifNotCancelled(fn: () -> Unit) = if (!isCancelled) fn() else Unit
|
||||||
|
|
|
@ -8,15 +8,24 @@ class EventBus<EventT: Event>(eventType: KClass<EventT>) {
|
||||||
private val eventType: Class<EventT> = eventType.java
|
private val eventType: Class<EventT> = eventType.java
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All event handlers, sorted by their priority and the order in which they were inserted
|
* All event handlers, sorted by their priority and the order in which they were registered.
|
||||||
*/
|
*/
|
||||||
private val handlers = mutableListOf<Handler>()
|
private val handlers = mutableListOf<Handler>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes all previously registered event handlers sorted by their priority
|
||||||
|
* and the order in which they were registered.
|
||||||
|
*/
|
||||||
fun <T: EventT> emit(event: T): T {
|
fun <T: EventT> emit(event: T): T {
|
||||||
handlers.filter { it.eventType.isInstance(event) }.forEach { it.fn.invoke(it.listener, event) }
|
handlers.filter { it.eventType.isInstance(event) }.forEach { it.fn.invoke(it.listener, event) }
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers all [event handlers][EventHandler] in [listener] to be invoked when their corresponding event is emitted.
|
||||||
|
*
|
||||||
|
* @throws InvalidEventHandlerException if one of the event handlers does not meet the requirements
|
||||||
|
*/
|
||||||
fun register(listener: Listener) {
|
fun register(listener: Listener) {
|
||||||
val handlersOfListener = listener::class.java.methods
|
val handlersOfListener = listener::class.java.methods
|
||||||
.mapNotNull { method -> method.getAnnotation(EventHandler::class.java)?.let { method to it } }
|
.mapNotNull { method -> method.getAnnotation(EventHandler::class.java)?.let { method to it } }
|
||||||
|
@ -39,7 +48,7 @@ class EventBus<EventT: Event>(eventType: KClass<EventT>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvalidEventHandlerException(message: String): Exception(message)
|
class InvalidEventHandlerException internal constructor(message: String): Exception(message)
|
||||||
|
|
||||||
private data class Handler(
|
private data class Handler(
|
||||||
val eventType: Class<out Event>,
|
val eventType: Class<out Event>,
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package space.blokk.events
|
||||||
|
|
||||||
|
interface EventTarget<T: Event> {
|
||||||
|
val eventBus: EventBus<T>
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package space.blokk.events
|
||||||
|
|
||||||
|
interface EventTargetGroup<T: EventTarget<*>> : Iterable<T> {
|
||||||
|
fun registerListener(listener: Listener)
|
||||||
|
}
|
|
@ -1,70 +0,0 @@
|
||||||
package space.blokk.net
|
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf
|
|
||||||
import java.io.IOException
|
|
||||||
import kotlin.experimental.and
|
|
||||||
|
|
||||||
fun ByteBuf.readVarInt(): Int {
|
|
||||||
var bytesRead = 0
|
|
||||||
var result = 0
|
|
||||||
|
|
||||||
do {
|
|
||||||
if (bytesRead == 5) throw IOException("VarInt is longer than the maximum of 5 bytes")
|
|
||||||
|
|
||||||
val byte = readByte()
|
|
||||||
val value: Int = (byte and 0b01111111).toInt()
|
|
||||||
result = result or (value shl (7 * bytesRead))
|
|
||||||
bytesRead += 1
|
|
||||||
} while ((byte and 0b10000000.toByte()).toInt() != 0)
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ByteBuf.varIntReadable(): Boolean {
|
|
||||||
if (readableBytes() > 5) {
|
|
||||||
// maximum VarInt size
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
val initialIndex = readerIndex()
|
|
||||||
do {
|
|
||||||
if (readableBytes() < 1) {
|
|
||||||
readerIndex(initialIndex)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
val value = readByte().toInt()
|
|
||||||
} while ((value and 0b10000000) != 0);
|
|
||||||
|
|
||||||
readerIndex(initialIndex)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ByteBuf.writeVarInt(value: Int) {
|
|
||||||
var v = value
|
|
||||||
var part: Byte
|
|
||||||
while (true) {
|
|
||||||
part = (v and 0x7F).toByte()
|
|
||||||
v = v ushr 7
|
|
||||||
if (v != 0) {
|
|
||||||
part = (part.toInt() or 0x80).toByte()
|
|
||||||
}
|
|
||||||
writeByte(part.toInt())
|
|
||||||
if (v == 0) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ByteBuf.readString(): String {
|
|
||||||
val length = readVarInt()
|
|
||||||
val bytes = ByteArray(length)
|
|
||||||
readBytes(bytes)
|
|
||||||
return bytes.toString(Charsets.UTF_8)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ByteBuf.writeString(value: String) {
|
|
||||||
val bytes = value.toByteArray(Charsets.UTF_8)
|
|
||||||
writeVarInt(bytes.size)
|
|
||||||
writeBytes(bytes)
|
|
||||||
}
|
|
103
blokk-api/src/main/kotlin/space/blokk/net/MinecraftDataTypes.kt
Normal file
103
blokk-api/src/main/kotlin/space/blokk/net/MinecraftDataTypes.kt
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
package space.blokk.net
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf
|
||||||
|
import java.io.IOException
|
||||||
|
import kotlin.experimental.and
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension functions on [ByteBuf] for reading and writing data types used by the Minecraft protocol
|
||||||
|
*
|
||||||
|
* You can access them using the [with] function (`with(MinecraftDataTypes) { ... }`).
|
||||||
|
*/
|
||||||
|
object MinecraftDataTypes {
|
||||||
|
/**
|
||||||
|
* Gets a variable length integer at the current readerIndex and increases the readerIndex by up to 5 in this buffer.
|
||||||
|
*
|
||||||
|
* @throws IOException if the VarInt is longer than the maximum of 5 bytes
|
||||||
|
* @see <a href="https://wiki.vg/Protocol#VarInt_and_VarLong">https://wiki.vg/Protocol#VarInt_and_VarLong</a>
|
||||||
|
*/
|
||||||
|
fun ByteBuf.readVarInt(): Int {
|
||||||
|
var bytesRead = 0
|
||||||
|
var result = 0
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (bytesRead == 5) throw IOException("VarInt is longer than the maximum of 5 bytes")
|
||||||
|
|
||||||
|
val byte = readByte()
|
||||||
|
val value: Int = (byte and 0b01111111).toInt()
|
||||||
|
result = result or (value shl (7 * bytesRead))
|
||||||
|
bytesRead += 1
|
||||||
|
} while ((byte and 0b10000000.toByte()).toInt() != 0)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a VarInt can be read starting from the current readerIndex.
|
||||||
|
*/
|
||||||
|
fun ByteBuf.varIntReadable(): Boolean {
|
||||||
|
if (readableBytes() > 5) {
|
||||||
|
// maximum VarInt size
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
val initialIndex = readerIndex()
|
||||||
|
do {
|
||||||
|
if (readableBytes() < 1) {
|
||||||
|
readerIndex(initialIndex)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val value = readByte().toInt()
|
||||||
|
} while ((value and 0b10000000) != 0)
|
||||||
|
|
||||||
|
readerIndex(initialIndex)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a variable length integer at the current writerIndex and increases the writerIndex by up to 5 in this buffer.
|
||||||
|
*
|
||||||
|
* @see <a href="https://wiki.vg/Protocol#VarInt_and_VarLong">https://wiki.vg/Protocol#VarInt_and_VarLong</a>
|
||||||
|
*/
|
||||||
|
fun ByteBuf.writeVarInt(value: Int) {
|
||||||
|
var v = value
|
||||||
|
var part: Byte
|
||||||
|
while (true) {
|
||||||
|
part = (v and 0x7F).toByte()
|
||||||
|
v = v ushr 7
|
||||||
|
if (v != 0) {
|
||||||
|
part = (part.toInt() or 0x80).toByte()
|
||||||
|
}
|
||||||
|
writeByte(part.toInt())
|
||||||
|
if (v == 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a variable length UTF-8 encoded string at the current readerIndex and increases the readerIndex in this
|
||||||
|
* buffer by the number of bytes read.
|
||||||
|
*
|
||||||
|
* @see <a href="https://wiki.vg/Protocol#Data_types">https://wiki.vg/Protocol#Data_types</a>
|
||||||
|
*/
|
||||||
|
fun ByteBuf.readString(): String {
|
||||||
|
val length = readVarInt()
|
||||||
|
val bytes = ByteArray(length)
|
||||||
|
readBytes(bytes)
|
||||||
|
return bytes.toString(Charsets.UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a variable length UTF-8 encoded string at the current writerIndex and increases the writerIndex in this
|
||||||
|
* buffer by the number of bytes written.
|
||||||
|
*
|
||||||
|
* @see <a href="https://wiki.vg/Protocol#Data_types">https://wiki.vg/Protocol#Data_types</a>
|
||||||
|
*/
|
||||||
|
fun ByteBuf.writeString(value: String) {
|
||||||
|
val bytes = value.toByteArray(Charsets.UTF_8)
|
||||||
|
writeVarInt(bytes.size)
|
||||||
|
writeBytes(bytes)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,17 @@
|
||||||
package space.blokk.net
|
package space.blokk.net
|
||||||
|
|
||||||
import space.blokk.events.EventBus
|
import space.blokk.events.EventTarget
|
||||||
import space.blokk.net.events.SessionEvent
|
import space.blokk.net.events.SessionEvent
|
||||||
import space.blokk.net.protocols.OutgoingPacket
|
import space.blokk.net.protocols.OutgoingPacket
|
||||||
import space.blokk.net.protocols.Protocol
|
import space.blokk.net.protocols.Protocol
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
|
|
||||||
interface Session {
|
interface Session: EventTarget<SessionEvent> {
|
||||||
|
/**
|
||||||
|
* The protocol this session is currently using.
|
||||||
|
*/
|
||||||
var currentProtocol: Protocol
|
var currentProtocol: Protocol
|
||||||
val address: InetAddress
|
val address: InetAddress
|
||||||
|
|
||||||
fun send(packet: OutgoingPacket)
|
fun send(packet: OutgoingPacket)
|
||||||
|
|
||||||
val eventBus: EventBus<SessionEvent>
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
package space.blokk.net
|
|
||||||
|
|
||||||
import space.blokk.events.Listener
|
|
||||||
|
|
||||||
interface SessionContainer: Iterable<Session> {
|
|
||||||
fun registerGlobalListener(listener: Listener)
|
|
||||||
}
|
|
|
@ -1,5 +1,6 @@
|
||||||
package space.blokk.net.events
|
package space.blokk.net.events
|
||||||
|
|
||||||
import space.blokk.events.Event
|
import space.blokk.events.Event
|
||||||
|
import space.blokk.net.Session
|
||||||
|
|
||||||
abstract class SessionEvent: Event()
|
abstract class SessionEvent(val session: Session): Event()
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package space.blokk.net.events
|
package space.blokk.net.events
|
||||||
|
|
||||||
import space.blokk.events.Cancellable
|
import space.blokk.events.Cancellable
|
||||||
|
import space.blokk.net.Session
|
||||||
import space.blokk.net.protocols.IncomingPacket
|
import space.blokk.net.protocols.IncomingPacket
|
||||||
|
|
||||||
class SessionPacketReceivedEvent(var packet: IncomingPacket): SessionEvent(), Cancellable {
|
class SessionPacketReceivedEvent(session: Session, var packet: IncomingPacket): SessionEvent(session), Cancellable {
|
||||||
override var isCancelled = false
|
override var isCancelled = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package space.blokk.net.events
|
package space.blokk.net.events
|
||||||
|
|
||||||
import space.blokk.events.Cancellable
|
import space.blokk.events.Cancellable
|
||||||
|
import space.blokk.net.Session
|
||||||
import space.blokk.net.protocols.OutgoingPacket
|
import space.blokk.net.protocols.OutgoingPacket
|
||||||
|
|
||||||
class SessionPacketSendEvent(var packet: OutgoingPacket): SessionEvent(), Cancellable {
|
class SessionPacketSendEvent(session: Session, var packet: OutgoingPacket): SessionEvent(session), Cancellable {
|
||||||
override var isCancelled = false
|
override var isCancelled = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package space.blokk.net.protocols
|
package space.blokk.net.protocols
|
||||||
|
|
||||||
abstract class Protocol(packets: Set<PacketCompanion<*>>) {
|
abstract class Protocol internal constructor(packets: Set<PacketCompanion<*>>) {
|
||||||
val incomingPackets = packets.filterIsInstance<IncomingPacketCompanion<*>>()
|
val incomingPackets = packets.filterIsInstance<IncomingPacketCompanion<*>>()
|
||||||
val incomingPacketsByID = incomingPackets.mapToIDMap()
|
val incomingPacketsByID = incomingPackets.mapToIDMap()
|
||||||
val outgoingPackets = packets.filterIsInstance<OutgoingPacketCompanion<*>>()
|
val outgoingPackets = packets.filterIsInstance<OutgoingPacketCompanion<*>>()
|
||||||
|
|
|
@ -6,5 +6,5 @@ import space.blokk.net.protocols.status.StatusProtocol
|
||||||
|
|
||||||
object Protocols {
|
object Protocols {
|
||||||
val all = setOf(HandshakingProtocol, StatusProtocol, LoginProtocol)
|
val all = setOf(HandshakingProtocol, StatusProtocol, LoginProtocol)
|
||||||
fun validate() = all.forEach(Protocol::validate)
|
internal fun validate() = all.forEach(Protocol::validate)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,33 @@
|
||||||
package space.blokk.net.protocols.handshaking
|
package space.blokk.net.protocols.handshaking
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf
|
import io.netty.buffer.ByteBuf
|
||||||
|
import space.blokk.net.MinecraftDataTypes
|
||||||
import space.blokk.net.Session
|
import space.blokk.net.Session
|
||||||
import space.blokk.net.protocols.IncomingPacket
|
import space.blokk.net.protocols.IncomingPacket
|
||||||
import space.blokk.net.protocols.IncomingPacketCompanion
|
import space.blokk.net.protocols.IncomingPacketCompanion
|
||||||
import space.blokk.net.protocols.login.LoginProtocol
|
import space.blokk.net.protocols.login.LoginProtocol
|
||||||
import space.blokk.net.protocols.status.StatusProtocol
|
import space.blokk.net.protocols.status.StatusProtocol
|
||||||
import space.blokk.net.readString
|
|
||||||
import space.blokk.net.readVarInt
|
|
||||||
|
|
||||||
data class HandshakePacket(
|
data class HandshakePacket(
|
||||||
val protocolVersion: Int,
|
val protocolVersion: Int,
|
||||||
val serverAddress: String,
|
val serverAddress: String,
|
||||||
val serverPort: Int,
|
val serverPort: Int,
|
||||||
val login: Boolean
|
val isLoginAttempt: Boolean
|
||||||
): IncomingPacket() {
|
): IncomingPacket() {
|
||||||
override fun handle(session: Session) {
|
override fun handle(session: Session) {
|
||||||
session.currentProtocol = if (login) LoginProtocol else StatusProtocol
|
session.currentProtocol = if (isLoginAttempt) LoginProtocol else StatusProtocol
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object: IncomingPacketCompanion<HandshakePacket>(0x00, HandshakePacket::class) {
|
companion object: IncomingPacketCompanion<HandshakePacket>(0x00, HandshakePacket::class) {
|
||||||
override fun decode(msg: ByteBuf): HandshakePacket {
|
override fun decode(msg: ByteBuf): HandshakePacket {
|
||||||
return HandshakePacket(
|
return with(MinecraftDataTypes) {
|
||||||
protocolVersion = msg.readVarInt(),
|
HandshakePacket(
|
||||||
serverAddress = msg.readString(),
|
protocolVersion = msg.readVarInt(),
|
||||||
serverPort = msg.readUnsignedShort(),
|
serverAddress = msg.readString(),
|
||||||
login = msg.readVarInt() == 2
|
serverPort = msg.readUnsignedShort(),
|
||||||
)
|
isLoginAttempt = msg.readVarInt() == 2
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,9 @@ package space.blokk.net.protocols.status
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import io.netty.buffer.ByteBuf
|
import io.netty.buffer.ByteBuf
|
||||||
import space.blokk.chat.TextComponent
|
import space.blokk.chat.TextComponent
|
||||||
|
import space.blokk.net.MinecraftDataTypes
|
||||||
import space.blokk.net.protocols.OutgoingPacket
|
import space.blokk.net.protocols.OutgoingPacket
|
||||||
import space.blokk.net.protocols.OutgoingPacketCompanion
|
import space.blokk.net.protocols.OutgoingPacketCompanion
|
||||||
import space.blokk.net.writeString
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
data class ResponsePacket(
|
data class ResponsePacket(
|
||||||
|
@ -29,18 +29,24 @@ data class ResponsePacket(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun encode(dst: ByteBuf) {
|
override fun encode(dst: ByteBuf) {
|
||||||
dst.writeString(gson.toJson(mapOf(
|
with(MinecraftDataTypes) {
|
||||||
"version" to mapOf(
|
dst.writeString(gson.toJson(mapOf(
|
||||||
"name" to versionName,
|
"version" to mapOf(
|
||||||
"protocol" to protocolVersion
|
"name" to versionName,
|
||||||
),
|
"protocol" to protocolVersion
|
||||||
"players" to players,
|
),
|
||||||
"description" to description,
|
"players" to players,
|
||||||
"favicon" to favicon
|
"description" to description,
|
||||||
)))
|
"favicon" to favicon
|
||||||
|
)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Players(val max: Int, val online: Int, val sample: List<SampleEntry>) {
|
data class Players(val max: Int, val online: Int, val sample: List<SampleEntry>) {
|
||||||
|
/**
|
||||||
|
* @param name The name of the player. You can use [FormattingCode](space.blokk.chat.FormattingCode)s
|
||||||
|
* @param id The UUID of the player as a string. You can use a random UUID as it doesn't get validated by the Minecraft client
|
||||||
|
*/
|
||||||
data class SampleEntry(val name: String, val id: String)
|
data class SampleEntry(val name: String, val id: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package space.blokk.server
|
package space.blokk.server
|
||||||
|
|
||||||
import space.blokk.events.EventBus
|
import space.blokk.events.EventBus
|
||||||
|
import space.blokk.events.EventTarget
|
||||||
import space.blokk.server.events.ServerEvent
|
import space.blokk.server.events.ServerEvent
|
||||||
import space.blokk.utils.Logger
|
import space.blokk.utils.Logger
|
||||||
|
|
||||||
interface Server {
|
interface Server: EventTarget<ServerEvent> {
|
||||||
val logger: Logger
|
val logger: Logger
|
||||||
val eventBus: EventBus<ServerEvent>
|
|
||||||
|
override val eventBus: EventBus<ServerEvent>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
package space.blokk.net
|
package space.blokk.net
|
||||||
|
|
||||||
|
import space.blokk.events.EventTargetGroup
|
||||||
import space.blokk.events.Listener
|
import space.blokk.events.Listener
|
||||||
|
|
||||||
class BlokkSessionContainer: SessionContainer {
|
class BlokkSessionContainer: EventTargetGroup<Session> {
|
||||||
private val sessions = mutableSetOf<Session>()
|
private val sessions = mutableSetOf<Session>()
|
||||||
private val listeners = mutableSetOf<Listener>()
|
private val listeners = mutableSetOf<Listener>()
|
||||||
|
|
||||||
override fun registerGlobalListener(listener: Listener) {
|
override fun registerListener(listener: Listener) {
|
||||||
sessions.forEach { it.eventBus.register(listener) }
|
sessions.forEach { it.eventBus.register(listener) }
|
||||||
listeners.add(listener)
|
listeners.add(listener)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,24 +8,27 @@ class FramingCodec: ByteToMessageCodec<ByteBuf>() {
|
||||||
private var currentLength: Int? = null
|
private var currentLength: Int? = null
|
||||||
|
|
||||||
override fun encode(ctx: ChannelHandlerContext, msg: ByteBuf, out: ByteBuf) {
|
override fun encode(ctx: ChannelHandlerContext, msg: ByteBuf, out: ByteBuf) {
|
||||||
out.writeVarInt(msg.readableBytes())
|
with(MinecraftDataTypes) { out.writeVarInt(msg.readableBytes()) }
|
||||||
msg.readerIndex(0)
|
msg.readerIndex(0)
|
||||||
out.writeBytes(msg)
|
out.writeBytes(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList<Any>) {
|
override fun decode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList<Any>) {
|
||||||
var length = currentLength
|
with(MinecraftDataTypes) {
|
||||||
if (length == null) {
|
var length = currentLength
|
||||||
if (msg.varIntReadable()) {
|
|
||||||
length = msg.readVarInt()
|
|
||||||
currentLength = length
|
|
||||||
}
|
|
||||||
else return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.readableBytes() >= length) {
|
if (length == null) {
|
||||||
out.add(msg.readBytes(length))
|
if (msg.varIntReadable()) {
|
||||||
currentLength = null
|
length = msg.readVarInt()
|
||||||
|
currentLength = length
|
||||||
|
}
|
||||||
|
else return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.readableBytes() >= length) {
|
||||||
|
out.add(msg.readBytes(length))
|
||||||
|
currentLength = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,13 +25,13 @@ class PacketCodec(private val blokkSocketServer: BlokkSocketServer): MessageToMe
|
||||||
override fun encode(ctx: ChannelHandlerContext, msg: PacketMessage, out: MutableList<Any>) {
|
override fun encode(ctx: ChannelHandlerContext, msg: PacketMessage, out: MutableList<Any>) {
|
||||||
if (msg.packet !is OutgoingPacket) throw Error("Only clientbound packets can be sent")
|
if (msg.packet !is OutgoingPacket) throw Error("Only clientbound packets can be sent")
|
||||||
val buffer = ctx.alloc().buffer()
|
val buffer = ctx.alloc().buffer()
|
||||||
buffer.writeVarInt(msg.packetCompanion.id)
|
with(MinecraftDataTypes) { buffer.writeVarInt(msg.packetCompanion.id) }
|
||||||
msg.packet.encode(buffer)
|
msg.packet.encode(buffer)
|
||||||
out.add(buffer)
|
out.add(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun decode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList<Any>) {
|
override fun decode(ctx: ChannelHandlerContext, msg: ByteBuf, out: MutableList<Any>) {
|
||||||
val packetID = msg.readVarInt()
|
val packetID = with(MinecraftDataTypes) { msg.readVarInt() }
|
||||||
val data = msg.readBytes(msg.readableBytes())
|
val data = msg.readBytes(msg.readableBytes())
|
||||||
|
|
||||||
val packetCompanion = session.currentProtocol.incomingPacketsByID[packetID]
|
val packetCompanion = session.currentProtocol.incomingPacketsByID[packetID]
|
||||||
|
|
|
@ -3,6 +3,7 @@ package space.blokk.net
|
||||||
import io.netty.channel.ChannelHandlerContext
|
import io.netty.channel.ChannelHandlerContext
|
||||||
import io.netty.channel.SimpleChannelInboundHandler
|
import io.netty.channel.SimpleChannelInboundHandler
|
||||||
import space.blokk.BlokkServer
|
import space.blokk.BlokkServer
|
||||||
|
import space.blokk.events.ifNotCancelled
|
||||||
import space.blokk.net.events.SessionPacketReceivedEvent
|
import space.blokk.net.events.SessionPacketReceivedEvent
|
||||||
import space.blokk.net.protocols.IncomingPacket
|
import space.blokk.net.protocols.IncomingPacket
|
||||||
|
|
||||||
|
@ -11,8 +12,9 @@ class PacketMessageHandler: SimpleChannelInboundHandler<PacketMessage>() {
|
||||||
if (msg.packet !is IncomingPacket) throw Error("Only serverbound packets can be handled")
|
if (msg.packet !is IncomingPacket) throw Error("Only serverbound packets can be handled")
|
||||||
BlokkServer.instance.blokkSocketServer.logger.debug { "Packet received: ${msg.packet}" }
|
BlokkServer.instance.blokkSocketServer.logger.debug { "Packet received: ${msg.packet}" }
|
||||||
|
|
||||||
if (!msg.session.eventBus.emit(SessionPacketReceivedEvent(msg.packet)).isCancelled)
|
msg.session.eventBus.emit(SessionPacketReceivedEvent(msg.session, msg.packet)).ifNotCancelled {
|
||||||
msg.packet.handle(msg.session)
|
msg.packet.handle(msg.session)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Disconnect when invalid data is received
|
// TODO: Disconnect when invalid data is received
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue