Refactor some things and DisconnectPacket for PLAY protocol
This commit is contained in:
parent
6985831b00
commit
cddb62a8ba
24 changed files with 172 additions and 90 deletions
|
@ -1,6 +1,7 @@
|
|||
package space.blokk
|
||||
|
||||
import space.blokk.world.BlockLocation
|
||||
import space.blokk.world.World
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
data class Location(val x: Double, val y: Double, val z: Double) {
|
||||
|
@ -15,4 +16,6 @@ data class Location(val x: Double, val y: Double, val z: Double) {
|
|||
* [Double.roundToInt], in contrast to [asBlockLocation] which uses [Double.toInt].
|
||||
*/
|
||||
fun roundToBlock(): BlockLocation = BlockLocation(x.roundToInt(), y.roundToInt(), z.roundToInt())
|
||||
|
||||
data class InWorld(val world: World, val location: Location)
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ class EventBus<EventT : Event>(
|
|||
if (Blokk.developmentMode) {
|
||||
val handlers = handlers.filter { it.eventType.isInstance(event) }
|
||||
val time = measureTimeMillis { handlers.forEach { it.fn.callSuspend(it.listener, event) } }
|
||||
logger trace "Emitted ${event::class.java.simpleName} to ${handlers.count()} handler(s), took ${time}ms"
|
||||
logger trace "Emitted ${event::class.java.simpleName} to ${handlers.size} handler(s), took ${time}ms"
|
||||
} else handlers.filter { it.eventType.isInstance(event) }.forEach { it.fn.callSuspend(it.listener, event) }
|
||||
|
||||
return event
|
||||
|
|
|
@ -19,23 +19,23 @@ class Logger(val name: String) {
|
|||
infix fun debug(message: String) = log(Level.DEBUG, message, null)
|
||||
infix fun trace(message: String) = log(Level.TRACE, message, null)
|
||||
|
||||
infix fun error(fn: () -> String) {
|
||||
inline infix fun error(fn: () -> String) {
|
||||
if (Level.ERROR.isEnabled) error(fn())
|
||||
}
|
||||
|
||||
infix fun info(fn: () -> String) {
|
||||
inline infix fun info(fn: () -> String) {
|
||||
if (Level.INFO.isEnabled) info(fn())
|
||||
}
|
||||
|
||||
infix fun warn(fn: () -> String) {
|
||||
inline infix fun warn(fn: () -> String) {
|
||||
if (Level.WARN.isEnabled) warn(fn())
|
||||
}
|
||||
|
||||
infix fun debug(fn: () -> String) {
|
||||
inline infix fun debug(fn: () -> String) {
|
||||
if (Level.DEBUG.isEnabled) debug(fn())
|
||||
}
|
||||
|
||||
infix fun trace(fn: () -> String) {
|
||||
inline infix fun trace(fn: () -> String) {
|
||||
if (Level.TRACE.isEnabled) trace(fn())
|
||||
}
|
||||
|
||||
|
|
|
@ -34,10 +34,12 @@ interface Session : EventTarget<SessionEvent> {
|
|||
val currentProtocol: Protocol?
|
||||
|
||||
/**
|
||||
* The player corresponding to this session.
|
||||
* The player corresponding to this session. This is null if [state] is not [State.Playing].
|
||||
* If you need the player instance earlier, you can use [state] if it is one of [State.JoiningWorld] or
|
||||
* [State.FinishJoining]
|
||||
*/
|
||||
val player: Player?
|
||||
get() = (state as? State.WithPlayer)?.player
|
||||
get() = (state as? State.Playing)?.player
|
||||
|
||||
/**
|
||||
* The current [State] of this session.
|
||||
|
@ -81,7 +83,10 @@ interface Session : EventTarget<SessionEvent> {
|
|||
*
|
||||
* @param loggableReason A short, loggable representation of [reason].
|
||||
*/
|
||||
suspend fun disconnect(reason: TextComponent, loggableReason: String = reason.text)
|
||||
suspend fun disconnect(
|
||||
reason: TextComponent = TextComponent.of("Disconnected."),
|
||||
loggableReason: String = reason.text
|
||||
)
|
||||
|
||||
/**
|
||||
* Sends a packet.
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package space.blokk.net.event
|
||||
|
||||
import space.blokk.Location
|
||||
import space.blokk.event.Cancellable
|
||||
import space.blokk.net.Session
|
||||
import space.blokk.player.GameMode
|
||||
import space.blokk.player.Player
|
||||
|
||||
class SessionPlayerJoinEvent(session: Session, val settings: Player.Settings) : SessionEvent(session), Cancellable {
|
||||
/**
|
||||
* The location where the player will spawn. If this is null after all handlers ran, the player will disconnect.
|
||||
*/
|
||||
var spawnLocation: Location.InWorld? = null
|
||||
|
||||
var gameMode: GameMode = GameMode.SURVIVAL
|
||||
|
||||
override var isCancelled = false
|
||||
}
|
|
@ -10,7 +10,7 @@ abstract class Protocol constructor(val name: String, vararg codecs: PacketCodec
|
|||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : Packet> getCodecByType(type: KClass<T>): PacketCodec<T> = codecsByPacketType[type] as PacketCodec<T>?
|
||||
?: throw Exception(
|
||||
"No packet companion found for this packet type. " +
|
||||
"No packet codec found for this packet type. " +
|
||||
"This can happen if the packet is not part of the current protocol."
|
||||
)
|
||||
}
|
||||
|
|
|
@ -44,11 +44,11 @@ data class ResponsePacket(
|
|||
versionName = "1.15.2",
|
||||
protocolVersion = 578,
|
||||
description = TextComponent of "Hello World!",
|
||||
players = ResponsePacket.Players(
|
||||
players = Players(
|
||||
10,
|
||||
10,
|
||||
listOf(
|
||||
ResponsePacket.Players.SampleEntry(
|
||||
Players.SampleEntry(
|
||||
"${FormattingCode.AQUA}Gronkh",
|
||||
UUID.randomUUID().toString()
|
||||
)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package space.blokk.player
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import space.blokk.Location
|
||||
import space.blokk.event.EventTarget
|
||||
import space.blokk.net.Session
|
||||
import space.blokk.player.event.PlayerEvent
|
||||
|
@ -27,38 +28,24 @@ interface Player : EventTarget<PlayerEvent> {
|
|||
*/
|
||||
val uuid: UUID
|
||||
|
||||
/**
|
||||
* The [ChatMode] of this player.
|
||||
*/
|
||||
val chatMode: ChatMode
|
||||
|
||||
/**
|
||||
* Whether the player has chat colors enabled.
|
||||
*/
|
||||
val chatColorsEnabled: Boolean
|
||||
|
||||
/**
|
||||
* The [skin parts][SkinPartsConfiguration] this player has enabled for his skin.
|
||||
*/
|
||||
val enabledSkinParts: SkinPartsConfiguration
|
||||
|
||||
/**
|
||||
* The main [hand][Hand] of this player.
|
||||
*/
|
||||
val mainHand: Hand
|
||||
|
||||
/**
|
||||
* The locale of the player's game client.
|
||||
*/
|
||||
val locale: String
|
||||
|
||||
/**
|
||||
* The view distance of the player's game client.
|
||||
*/
|
||||
val viewDistance: Byte
|
||||
|
||||
/**
|
||||
* The [GameMode] of this player.
|
||||
*/
|
||||
var gameMode: GameMode
|
||||
|
||||
/**
|
||||
* The settings this client sent in the `ClientSettings` packet.
|
||||
*/
|
||||
val settings: Settings
|
||||
|
||||
val location: Location.InWorld
|
||||
|
||||
data class Settings(
|
||||
val viewDistance: Byte,
|
||||
val locale: String,
|
||||
val mainHand: Hand,
|
||||
val enabledSkinParts: SkinPartsConfiguration,
|
||||
val chatColorsEnabled: Boolean,
|
||||
val chatMode: ChatMode
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package space.blokk.player.event
|
||||
|
||||
import space.blokk.event.Event
|
||||
import space.blokk.player.Player
|
||||
|
||||
abstract class PlayerEvent : Event()
|
||||
abstract class PlayerEvent(val player: Player) : Event()
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package space.blokk.player.event
|
||||
|
||||
import space.blokk.player.Player
|
||||
|
||||
class PlayerSettingsChangeEvent(player: Player, val settings: Player.Settings) : PlayerEvent(player)
|
|
@ -0,0 +1,11 @@
|
|||
package space.blokk.net.packet.play
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.blokk.net.MinecraftProtocolDataTypes.writeString
|
||||
import space.blokk.net.packet.OutgoingPacketCodec
|
||||
|
||||
object DisconnectPacketCodec : OutgoingPacketCodec<DisconnectPacket>(0x1B, DisconnectPacket::class) {
|
||||
override fun DisconnectPacket.encode(dst: ByteBuf) {
|
||||
dst.writeString(reason.toJson())
|
||||
}
|
||||
}
|
|
@ -9,5 +9,6 @@ object PlayProtocol : Protocol(
|
|||
JoinGamePacketCodec,
|
||||
OutgoingPluginMessagePacketCodec,
|
||||
PlayerAbilitiesPacketCodec,
|
||||
ServerDifficultyPacketCodec
|
||||
ServerDifficultyPacketCodec,
|
||||
DisconnectPacketCodec
|
||||
)
|
||||
|
|
|
@ -3,6 +3,7 @@ package space.blokk.net.packet.play
|
|||
import space.blokk.net.packet.IncomingPacket
|
||||
import space.blokk.player.ChatMode
|
||||
import space.blokk.player.Hand
|
||||
import space.blokk.player.Player
|
||||
import space.blokk.player.SkinPartsConfiguration
|
||||
|
||||
/**
|
||||
|
@ -13,6 +14,15 @@ data class ClientSettingsPacket(
|
|||
val viewDistance: Byte,
|
||||
val chatMode: ChatMode,
|
||||
val chatColorsEnabled: Boolean,
|
||||
val displayedSkinParts: SkinPartsConfiguration,
|
||||
val enabledSkinParts: SkinPartsConfiguration,
|
||||
val mainHand: Hand
|
||||
) : IncomingPacket()
|
||||
) : IncomingPacket() {
|
||||
fun asPlayerSettings() = Player.Settings(
|
||||
viewDistance,
|
||||
locale,
|
||||
mainHand,
|
||||
enabledSkinParts,
|
||||
chatColorsEnabled,
|
||||
chatMode
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package space.blokk.net.packet.play
|
||||
|
||||
import space.blokk.net.packet.OutgoingPacket
|
||||
|
||||
/**
|
||||
* Sent by the server when the connection is closed.
|
||||
*
|
||||
* @param reason The reason displayed to the client.
|
||||
*/
|
||||
data class DisconnectPacket(val reason: space.blokk.chat.TextComponent) : OutgoingPacket()
|
|
@ -9,6 +9,8 @@ object BlokkLoggingOutputProvider : LoggingOutputProvider {
|
|||
private val dateFormat = SimpleDateFormat("HH:mm:ss.SSS")
|
||||
|
||||
override fun log(loggerName: String, level: Logger.Level, message: String, throwable: Throwable?) {
|
||||
if (!level.isEnabled) return
|
||||
|
||||
val time = Date()
|
||||
|
||||
val stream: PrintStream = if (level.isGreaterOrEqualThan(Logger.Level.ERROR)) System.err else System.out
|
||||
|
|
|
@ -4,6 +4,7 @@ import io.netty.buffer.ByteBuf
|
|||
import io.netty.channel.Channel
|
||||
import kotlinx.coroutines.*
|
||||
import space.blokk.BlokkServer
|
||||
import space.blokk.chat.ChatComponent
|
||||
import space.blokk.chat.TextComponent
|
||||
import space.blokk.event.*
|
||||
import space.blokk.logging.Logger
|
||||
|
@ -14,7 +15,6 @@ import space.blokk.net.event.SessionEvent
|
|||
import space.blokk.net.packet.OutgoingPacket
|
||||
import space.blokk.net.packet.Protocol
|
||||
import space.blokk.net.packet.handshaking.HandshakingProtocol
|
||||
import space.blokk.net.packet.login.DisconnectPacket
|
||||
import space.blokk.net.packet.login.LoginProtocol
|
||||
import space.blokk.net.packet.play.OutgoingPluginMessagePacket
|
||||
import space.blokk.net.packet.play.PlayProtocol
|
||||
|
@ -93,10 +93,7 @@ class BlokkSession(private val channel: Channel, val server: BlokkServer) : Sess
|
|||
|
||||
fun failBecauseOfClient(message: String) {
|
||||
val messageGetter = { "A connection error caused by the client occurred: $message" }
|
||||
if (server.config.silentNonServerErrors) logger.debug(messageGetter) else logger.error(
|
||||
messageGetter
|
||||
)
|
||||
|
||||
if (server.config.silentNonServerErrors) logger debug messageGetter else logger error messageGetter
|
||||
channel.close()
|
||||
}
|
||||
|
||||
|
@ -112,11 +109,24 @@ class BlokkSession(private val channel: Channel, val server: BlokkServer) : Sess
|
|||
}
|
||||
|
||||
override suspend fun disconnect(reason: TextComponent, loggableReason: String) {
|
||||
fun getReason() =
|
||||
if (server.config.developmentMode) {
|
||||
val additional = listOf<ChatComponent>(
|
||||
TextComponent(
|
||||
"\n\nLogged reason (only shown in development mode): \n",
|
||||
color = ChatComponent.Color.GRAY
|
||||
),
|
||||
TextComponent.of(loggableReason)
|
||||
)
|
||||
|
||||
reason.copy(extra = (reason.extra ?: emptyList()) + additional)
|
||||
} else reason
|
||||
|
||||
disconnectReason = loggableReason
|
||||
|
||||
when (currentProtocol) {
|
||||
LoginProtocol -> send(DisconnectPacket(reason))
|
||||
// TODO: PlayProtocol
|
||||
LoginProtocol -> send(space.blokk.net.packet.login.DisconnectPacket(getReason()))
|
||||
PlayProtocol -> send(space.blokk.net.packet.play.DisconnectPacket(getReason()))
|
||||
}
|
||||
|
||||
channel.close()
|
||||
|
@ -125,12 +135,12 @@ class BlokkSession(private val channel: Channel, val server: BlokkServer) : Sess
|
|||
override suspend fun send(packet: OutgoingPacket) {
|
||||
if (state is State.Disconnected) throw IllegalStateException("The session is not active anymore")
|
||||
|
||||
logger trace { "Sending packet: $packet" }
|
||||
logger.trace { "Sending packet: $packet" }
|
||||
eventBus.emit(PacketSendEvent(this@BlokkSession, packet)).ifNotCancelled {
|
||||
try {
|
||||
channel.writeAndFlush(OutgoingPacketMessage(this@BlokkSession, it.packet)).awaitSuspending()
|
||||
} catch (t: Throwable) {
|
||||
logger.error("Packet send failed:", t)
|
||||
logger.error("Packet send failed", t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import space.blokk.Blokk
|
|||
import space.blokk.BlokkServer
|
||||
import space.blokk.chat.TextComponent
|
||||
import space.blokk.net.MinecraftProtocolDataTypes.writeString
|
||||
import space.blokk.net.event.SessionPlayerJoinEvent
|
||||
import space.blokk.net.packet.login.EncryptionRequestPacket
|
||||
import space.blokk.net.packet.login.EncryptionResponsePacket
|
||||
import space.blokk.net.packet.login.LoginStartPacket
|
||||
|
@ -69,8 +70,8 @@ class LoginAndJoinProcedure(val session: BlokkSession) {
|
|||
|
||||
val result = AuthenticationHelper.authenticate(hashString, state.username)
|
||||
|
||||
session.state = Session.State.LoginSucceeded(result.username, result.uuid)
|
||||
session.send(LoginSuccessPacket(result.uuid, result.username))
|
||||
session.state = Session.State.LoginSucceeded(result.username, result.uuid)
|
||||
|
||||
afterLogin()
|
||||
}
|
||||
|
@ -115,23 +116,30 @@ class LoginAndJoinProcedure(val session: BlokkSession) {
|
|||
session.state = Session.State.WaitingForClientSettings(state.username, state.uuid)
|
||||
}
|
||||
|
||||
fun onClientSettingsReceived(packet: ClientSettingsPacket) {
|
||||
val state: Session.State.WaitingForClientSettings = session.state.getOrFail()
|
||||
suspend fun onClientSettingsReceived(packet: ClientSettingsPacket) {
|
||||
val state = session.state.getOrFail<Session.State.WaitingForClientSettings>()
|
||||
|
||||
session.state = Session.State.JoiningWorld(
|
||||
// TODO: Use real gameMode
|
||||
BlokkPlayer(
|
||||
session = session,
|
||||
username = state.username,
|
||||
uuid = state.uuid,
|
||||
chatColorsEnabled = packet.chatColorsEnabled,
|
||||
chatMode = packet.chatMode,
|
||||
enabledSkinParts = packet.displayedSkinParts,
|
||||
locale = packet.locale,
|
||||
mainHand = packet.mainHand,
|
||||
viewDistance = packet.viewDistance,
|
||||
gameMode = GameMode.SURVIVAL
|
||||
)
|
||||
)
|
||||
val event = session.emit(SessionPlayerJoinEvent(session, packet.asPlayerSettings()))
|
||||
val spawnLocation = event.spawnLocation
|
||||
|
||||
when {
|
||||
event.isCancelled -> session.disconnect()
|
||||
spawnLocation == null -> {
|
||||
session.logger warn "Could not join because no spawn location was set"
|
||||
session.disconnect(loggableReason = "No spawn location set")
|
||||
}
|
||||
else -> {
|
||||
session.state = Session.State.JoiningWorld(
|
||||
BlokkPlayer(
|
||||
session,
|
||||
state.username,
|
||||
state.uuid,
|
||||
event.gameMode,
|
||||
event.settings,
|
||||
spawnLocation
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,24 @@ package space.blokk.net.packet.play
|
|||
|
||||
import space.blokk.net.BlokkSession
|
||||
import space.blokk.net.PacketReceivedEventHandler
|
||||
import space.blokk.player.BlokkPlayer
|
||||
import space.blokk.player.event.PlayerSettingsChangeEvent
|
||||
|
||||
object ClientSettingsPacketHandler : PacketReceivedEventHandler<ClientSettingsPacket>() {
|
||||
override suspend fun handle(session: BlokkSession, packet: ClientSettingsPacket) {
|
||||
try {
|
||||
session.joinProcedure.onClientSettingsReceived(packet)
|
||||
} catch (e: IllegalStateException) {
|
||||
session.failBecauseOfClient("Client sent ClientSettingsPacket when it was not allowed")
|
||||
// TODO: Emit event
|
||||
|
||||
val player = session.player
|
||||
|
||||
if (player == null) {
|
||||
try {
|
||||
session.joinProcedure.onClientSettingsReceived(packet)
|
||||
} catch (e: IllegalStateException) {
|
||||
session.failBecauseOfClient("Client sent ClientSettingsPacket when it was not allowed")
|
||||
}
|
||||
} else {
|
||||
(player as BlokkPlayer).settings =
|
||||
player.emit(PlayerSettingsChangeEvent(player, packet.asPlayerSettings())).settings
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,8 +19,7 @@ object IncomingPluginMessagePacketHandler : PacketReceivedEventHandler<IncomingP
|
|||
val brand = buffer.readString()
|
||||
buffer.release()
|
||||
|
||||
session.emit(ClientBrandReceivedEvent(session, brand))
|
||||
.ifNotCancelled { session.brand = it.brand }
|
||||
session.emit(ClientBrandReceivedEvent(session, brand)).ifNotCancelled { session.brand = it.brand }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import kotlinx.coroutines.CoroutineName
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import space.blokk.Location
|
||||
import space.blokk.event.EventBus
|
||||
import space.blokk.logging.Logger
|
||||
import space.blokk.net.Session
|
||||
|
@ -14,13 +15,9 @@ class BlokkPlayer(
|
|||
override val session: Session,
|
||||
override val username: String,
|
||||
override val uuid: UUID,
|
||||
override val chatColorsEnabled: Boolean,
|
||||
override val chatMode: ChatMode,
|
||||
override val enabledSkinParts: SkinPartsConfiguration,
|
||||
override val locale: String,
|
||||
override val mainHand: Hand,
|
||||
override val viewDistance: Byte,
|
||||
override var gameMode: GameMode
|
||||
override var gameMode: GameMode,
|
||||
override var settings: Player.Settings,
|
||||
override val location: Location.InWorld
|
||||
) : Player {
|
||||
private val identifier = "BlokkPlayer($username)"
|
||||
private val logger = Logger(identifier)
|
||||
|
|
|
@ -20,7 +20,7 @@ class DataDownloader(val dir: File) {
|
|||
} else {
|
||||
val request = Request.Builder()
|
||||
.get()
|
||||
.url("$BASE_URL/$MINECRAFT_VERSION/$name.json")
|
||||
.url("$BASE_URL/$MINECRAFT_VERSION_OF_PRISMARINE_DATA/$name.json")
|
||||
.build()
|
||||
|
||||
val time = measureTime {
|
||||
|
|
|
@ -19,6 +19,7 @@ class FilesGenerator(private val dataDir: File, private val outputDir: File, pri
|
|||
}
|
||||
|
||||
fun generate() {
|
||||
outputDir.deleteRecursively()
|
||||
outputDir.mkdirs()
|
||||
|
||||
val collisionShapesJson = dataDir.resolve("blockCollisionShapes.json").readText()
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
package space.blokk.mdsp
|
||||
|
||||
const val MINECRAFT_VERSION = "1.16.1"
|
|
@ -0,0 +1,6 @@
|
|||
package space.blokk.mdsp
|
||||
|
||||
/**
|
||||
* Some Minecraft versions do not have own data files in the Prismarine repository.
|
||||
*/
|
||||
const val MINECRAFT_VERSION_OF_PRISMARINE_DATA = "1.15.2"
|
Reference in a new issue