Archived
1
0
Fork 0

Refactor some things and DisconnectPacket for PLAY protocol

This commit is contained in:
Moritz Ruth 2020-10-15 00:52:10 +02:00
parent 6985831b00
commit cddb62a8ba
No known key found for this signature in database
GPG key ID: AFD57E23E753841B
24 changed files with 172 additions and 90 deletions

View file

@ -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)
}

View file

@ -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

View file

@ -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())
}

View file

@ -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.

View file

@ -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
}

View file

@ -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."
)
}

View file

@ -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()
)

View file

@ -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
)
}

View file

@ -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()

View file

@ -0,0 +1,5 @@
package space.blokk.player.event
import space.blokk.player.Player
class PlayerSettingsChangeEvent(player: Player, val settings: Player.Settings) : PlayerEvent(player)

View file

@ -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())
}
}

View file

@ -9,5 +9,6 @@ object PlayProtocol : Protocol(
JoinGamePacketCodec,
OutgoingPluginMessagePacketCodec,
PlayerAbilitiesPacketCodec,
ServerDifficultyPacketCodec
ServerDifficultyPacketCodec,
DisconnectPacketCodec
)

View file

@ -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
)
}

View file

@ -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()

View file

@ -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

View file

@ -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)
}
}
}

View file

@ -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
)
)
}
}
}
}

View file

@ -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
}
}
}

View file

@ -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 }
}
}
}

View file

@ -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)

View file

@ -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 {

View file

@ -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()

View file

@ -1,3 +0,0 @@
package space.blokk.mdsp
const val MINECRAFT_VERSION = "1.16.1"

View file

@ -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"