diff --git a/blokk-api/src/main/kotlin/space/blokk/Location.kt b/blokk-api/src/main/kotlin/space/blokk/Location.kt index fd13e3d..7036425 100644 --- a/blokk-api/src/main/kotlin/space/blokk/Location.kt +++ b/blokk-api/src/main/kotlin/space/blokk/Location.kt @@ -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) } diff --git a/blokk-api/src/main/kotlin/space/blokk/event/EventBus.kt b/blokk-api/src/main/kotlin/space/blokk/event/EventBus.kt index 2898bff..b040f8d 100644 --- a/blokk-api/src/main/kotlin/space/blokk/event/EventBus.kt +++ b/blokk-api/src/main/kotlin/space/blokk/event/EventBus.kt @@ -30,7 +30,7 @@ class EventBus( 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 diff --git a/blokk-api/src/main/kotlin/space/blokk/logging/Logger.kt b/blokk-api/src/main/kotlin/space/blokk/logging/Logger.kt index f38970c..9b1d1d1 100644 --- a/blokk-api/src/main/kotlin/space/blokk/logging/Logger.kt +++ b/blokk-api/src/main/kotlin/space/blokk/logging/Logger.kt @@ -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()) } diff --git a/blokk-api/src/main/kotlin/space/blokk/net/Session.kt b/blokk-api/src/main/kotlin/space/blokk/net/Session.kt index 05d9968..1594547 100644 --- a/blokk-api/src/main/kotlin/space/blokk/net/Session.kt +++ b/blokk-api/src/main/kotlin/space/blokk/net/Session.kt @@ -34,10 +34,12 @@ interface Session : EventTarget { 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 { * * @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. diff --git a/blokk-api/src/main/kotlin/space/blokk/net/event/SessionPlayerJoinEvent.kt b/blokk-api/src/main/kotlin/space/blokk/net/event/SessionPlayerJoinEvent.kt new file mode 100644 index 0000000..7dbd7bd --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/net/event/SessionPlayerJoinEvent.kt @@ -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 +} diff --git a/blokk-api/src/main/kotlin/space/blokk/net/packet/Protocol.kt b/blokk-api/src/main/kotlin/space/blokk/net/packet/Protocol.kt index 854def5..3cdf047 100644 --- a/blokk-api/src/main/kotlin/space/blokk/net/packet/Protocol.kt +++ b/blokk-api/src/main/kotlin/space/blokk/net/packet/Protocol.kt @@ -10,7 +10,7 @@ abstract class Protocol constructor(val name: String, vararg codecs: PacketCodec @Suppress("UNCHECKED_CAST") fun getCodecByType(type: KClass): PacketCodec = codecsByPacketType[type] as PacketCodec? ?: 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." ) } diff --git a/blokk-api/src/main/kotlin/space/blokk/net/packet/status/ResponsePacket.kt b/blokk-api/src/main/kotlin/space/blokk/net/packet/status/ResponsePacket.kt index a5aa334..18e2088 100644 --- a/blokk-api/src/main/kotlin/space/blokk/net/packet/status/ResponsePacket.kt +++ b/blokk-api/src/main/kotlin/space/blokk/net/packet/status/ResponsePacket.kt @@ -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() ) diff --git a/blokk-api/src/main/kotlin/space/blokk/player/Player.kt b/blokk-api/src/main/kotlin/space/blokk/player/Player.kt index 4e3c2a9..4006ff0 100644 --- a/blokk-api/src/main/kotlin/space/blokk/player/Player.kt +++ b/blokk-api/src/main/kotlin/space/blokk/player/Player.kt @@ -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 { */ 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 + ) } diff --git a/blokk-api/src/main/kotlin/space/blokk/player/event/PlayerEvent.kt b/blokk-api/src/main/kotlin/space/blokk/player/event/PlayerEvent.kt index f6857a3..7450dc6 100644 --- a/blokk-api/src/main/kotlin/space/blokk/player/event/PlayerEvent.kt +++ b/blokk-api/src/main/kotlin/space/blokk/player/event/PlayerEvent.kt @@ -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() diff --git a/blokk-api/src/main/kotlin/space/blokk/player/event/PlayerSettingsChangeEvent.kt b/blokk-api/src/main/kotlin/space/blokk/player/event/PlayerSettingsChangeEvent.kt new file mode 100644 index 0000000..fc6fa6d --- /dev/null +++ b/blokk-api/src/main/kotlin/space/blokk/player/event/PlayerSettingsChangeEvent.kt @@ -0,0 +1,5 @@ +package space.blokk.player.event + +import space.blokk.player.Player + +class PlayerSettingsChangeEvent(player: Player, val settings: Player.Settings) : PlayerEvent(player) diff --git a/blokk-packet-codecs/src/main/kotlin/space/blokk/net/packet/play/DisconnectPacketCodec.kt b/blokk-packet-codecs/src/main/kotlin/space/blokk/net/packet/play/DisconnectPacketCodec.kt new file mode 100644 index 0000000..b355a2c --- /dev/null +++ b/blokk-packet-codecs/src/main/kotlin/space/blokk/net/packet/play/DisconnectPacketCodec.kt @@ -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(0x1B, DisconnectPacket::class) { + override fun DisconnectPacket.encode(dst: ByteBuf) { + dst.writeString(reason.toJson()) + } +} diff --git a/blokk-packet-codecs/src/main/kotlin/space/blokk/net/packet/play/PlayProtocol.kt b/blokk-packet-codecs/src/main/kotlin/space/blokk/net/packet/play/PlayProtocol.kt index 4922e2e..82c0338 100644 --- a/blokk-packet-codecs/src/main/kotlin/space/blokk/net/packet/play/PlayProtocol.kt +++ b/blokk-packet-codecs/src/main/kotlin/space/blokk/net/packet/play/PlayProtocol.kt @@ -9,5 +9,6 @@ object PlayProtocol : Protocol( JoinGamePacketCodec, OutgoingPluginMessagePacketCodec, PlayerAbilitiesPacketCodec, - ServerDifficultyPacketCodec + ServerDifficultyPacketCodec, + DisconnectPacketCodec ) diff --git a/blokk-packets/src/main/kotlin/space/blokk/net/packet/play/ClientSettingsPacket.kt b/blokk-packets/src/main/kotlin/space/blokk/net/packet/play/ClientSettingsPacket.kt index 094fe17..df42dd8 100644 --- a/blokk-packets/src/main/kotlin/space/blokk/net/packet/play/ClientSettingsPacket.kt +++ b/blokk-packets/src/main/kotlin/space/blokk/net/packet/play/ClientSettingsPacket.kt @@ -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 + ) +} diff --git a/blokk-packets/src/main/kotlin/space/blokk/net/packet/play/DisconnectPacket.kt b/blokk-packets/src/main/kotlin/space/blokk/net/packet/play/DisconnectPacket.kt new file mode 100644 index 0000000..6a47594 --- /dev/null +++ b/blokk-packets/src/main/kotlin/space/blokk/net/packet/play/DisconnectPacket.kt @@ -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() diff --git a/blokk-server/src/main/kotlin/space/blokk/logging/BlokkLoggingOutputProvider.kt b/blokk-server/src/main/kotlin/space/blokk/logging/BlokkLoggingOutputProvider.kt index f984fef..e70ace5 100644 --- a/blokk-server/src/main/kotlin/space/blokk/logging/BlokkLoggingOutputProvider.kt +++ b/blokk-server/src/main/kotlin/space/blokk/logging/BlokkLoggingOutputProvider.kt @@ -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 diff --git a/blokk-server/src/main/kotlin/space/blokk/net/BlokkSession.kt b/blokk-server/src/main/kotlin/space/blokk/net/BlokkSession.kt index e455bae..2f51ae8 100644 --- a/blokk-server/src/main/kotlin/space/blokk/net/BlokkSession.kt +++ b/blokk-server/src/main/kotlin/space/blokk/net/BlokkSession.kt @@ -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( + 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) } } } diff --git a/blokk-server/src/main/kotlin/space/blokk/net/LoginAndJoinProcedure.kt b/blokk-server/src/main/kotlin/space/blokk/net/LoginAndJoinProcedure.kt index d2f19ca..fbffabd 100644 --- a/blokk-server/src/main/kotlin/space/blokk/net/LoginAndJoinProcedure.kt +++ b/blokk-server/src/main/kotlin/space/blokk/net/LoginAndJoinProcedure.kt @@ -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 = 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 + ) + ) + } + } } } diff --git a/blokk-server/src/main/kotlin/space/blokk/net/packet/play/ClientSettingsPacketHandler.kt b/blokk-server/src/main/kotlin/space/blokk/net/packet/play/ClientSettingsPacketHandler.kt index c3c1a9c..877b5c5 100644 --- a/blokk-server/src/main/kotlin/space/blokk/net/packet/play/ClientSettingsPacketHandler.kt +++ b/blokk-server/src/main/kotlin/space/blokk/net/packet/play/ClientSettingsPacketHandler.kt @@ -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() { 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 } } } diff --git a/blokk-server/src/main/kotlin/space/blokk/net/packet/play/IncomingPluginMessagePacketHandler.kt b/blokk-server/src/main/kotlin/space/blokk/net/packet/play/IncomingPluginMessagePacketHandler.kt index 80493a1..0e94351 100644 --- a/blokk-server/src/main/kotlin/space/blokk/net/packet/play/IncomingPluginMessagePacketHandler.kt +++ b/blokk-server/src/main/kotlin/space/blokk/net/packet/play/IncomingPluginMessagePacketHandler.kt @@ -19,8 +19,7 @@ object IncomingPluginMessagePacketHandler : PacketReceivedEventHandler