Archived
1
0
Fork 0

Add SessionAfterLoginEvent

This commit is contained in:
Moritz Ruth 2020-11-29 17:51:21 +01:00
parent 97df4085df
commit 468273eb6d
No known key found for this signature in database
GPG key ID: AFD57E23E753841B
17 changed files with 290 additions and 110 deletions

View file

@ -7,4 +7,4 @@ enum class Difficulty {
HARD
}
data class DifficultyOptions(val difficulty: Difficulty, val locked: Boolean)
data class DifficultySettings(val difficulty: Difficulty, val locked: Boolean)

View file

@ -7,7 +7,9 @@ import space.blokk.event.EventTarget
import space.blokk.net.event.SessionEvent
import space.blokk.net.packet.OutgoingPacket
import space.blokk.net.packet.Protocol
import space.blokk.player.GameMode
import space.blokk.player.Player
import space.blokk.world.WorldAndLocationWithRotation
import java.net.InetAddress
import java.util.*
@ -46,7 +48,7 @@ interface Session : EventTarget<SessionEvent> {
/**
* The current state of this session.
*/
var state: State
val state: State
sealed class State {
// TODO: Add documentation for the different states
@ -61,7 +63,19 @@ interface Session : EventTarget<SessionEvent> {
class LoginSucceeded(val username: String, val uuid: UUID) : State()
class WaitingForClientSettings(val username: String, val uuid: UUID) : State()
class WaitingForClientSettings(
val username: String,
val uuid: UUID,
val flying: Boolean,
val canFly: Boolean,
val flyingSpeed: Float,
val fieldOfView: Float,
val gameMode: GameMode,
val initialWorldAndLocation: WorldAndLocationWithRotation,
val invulnerable: Boolean,
val reducedDebugInfo: Boolean,
val selectedHotbarSlot: Byte
) : State()
interface WithPlayer {
val player: Player

View file

@ -2,9 +2,7 @@ package space.blokk.net.event
import space.blokk.event.Cancellable
import space.blokk.net.Session
import space.blokk.player.GameMode
import space.blokk.player.Player
import space.blokk.world.WorldAndLocationWithRotation
/**
* Emitted when a [Player] instance will be initialized.
@ -13,20 +11,4 @@ import space.blokk.world.WorldAndLocationWithRotation
*/
class PlayerInitializationEvent(session: Session, val settings: Player.Settings) : SessionEvent(session), Cancellable {
override var cancelled = false
/**
* The location where the player will spawn. If this is null after all handlers ran, the player will disconnect.
*/
var initialWorldAndLocation: WorldAndLocationWithRotation? = null
var gameMode: GameMode = GameMode.SURVIVAL
/**
* See [Player.selectedHotbarSlot].
*/
var selectedHotbarSlot: Byte = 0
set(value) {
if (value in 0..8) field = value
else throw IllegalArgumentException("selectedHotbarSlot must be between 0 and 8")
}
}

View file

@ -0,0 +1,85 @@
package space.blokk.net.event
import space.blokk.event.Cancellable
import space.blokk.net.Session
import space.blokk.player.GameMode
import space.blokk.player.Player
import space.blokk.world.WorldAndLocationWithRotation
/**
* Emitted after a [Session] finished logging in.
*
* If the event is cancelled, the session is disconnected.
*/
class SessionAfterLoginEvent(session: Session) : SessionEvent(session), Cancellable {
override var cancelled = false
/**
* Whether the respawn screen is shown when the player dies.
*/
var respawnScreenEnabled: Boolean = true
/**
* The location where the player will spawn. If this is null after all handlers ran, the session is disconnected.
*/
var initialWorldAndLocation: WorldAndLocationWithRotation? = null
var maxViewDistance: Int = 32
set(value) {
if (value in 2..32) field = value
else throw IllegalArgumentException("maxViewDistance must be in 2..32")
}
/**
* Whether the player should see his hearts as they look in hardcore mode.
*/
var hardcoreHearts = false
/**
* See [Player.gameMode].
*/
var gameMode: GameMode = GameMode.SURVIVAL
/**
* See [Player.selectedHotbarSlot].
*/
var selectedHotbarSlot: Byte = 0
set(value) {
if (value in 0..8) field = value
else throw IllegalArgumentException("selectedHotbarSlot must be in 0..8")
}
/**
* See [Player.reducedDebugInfo].
*/
var reducedDebugInfo: Boolean = false
/**
* See [Player.invulnerable].
*/
var invulnerable: Boolean = false
/**
* See [Player.canFly].
*/
var canFly: Boolean = false
/**
* See [Player.flying].
*/
var flying: Boolean = false
/**
* See [Player.flyingSpeed].
*
* Defaults to [Player.DEFAULT_FLYING_SPEED].
*/
var flyingSpeed: Float = Player.DEFAULT_FLYING_SPEED
/**
* See [Player.fieldOfView].
*
* Defaults to [Player.DEFAULT_FIELD_OF_VIEW].
*/
var fieldOfView: Float = Player.DEFAULT_FIELD_OF_VIEW
}

View file

@ -42,6 +42,15 @@ interface Player : EventTarget<PlayerEvent> {
*/
val settings: Settings
data class Settings(
val viewDistance: Byte,
val locale: String,
val mainHand: Hand,
val enabledSkinParts: SkinPartsConfiguration,
val chatColorsEnabled: Boolean,
val chatMode: ChatMode
)
/**
* The current location of this player.
*/
@ -58,12 +67,42 @@ interface Player : EventTarget<PlayerEvent> {
*/
var selectedHotbarSlot: Byte
data class Settings(
val viewDistance: Byte,
val locale: String,
val mainHand: Hand,
val enabledSkinParts: SkinPartsConfiguration,
val chatColorsEnabled: Boolean,
val chatMode: ChatMode
)
/**
* Corresponds to the `reducedDebugInfo` gamerule in vanilla.
*
* @see <a href="https://minecraft.gamepedia.com/Debug_screen#Legend">https://minecraft.gamepedia.com/Debug_screen#Legend</a>
*/
var reducedDebugInfo: Boolean
/**
* Whether the player cannot take damage.
*/
var invulnerable: Boolean
/**
* Whether the player can start flying by itself.
*/
var canFly: Boolean
/**
* Whether the player is currently flying.
*/
var flying: Boolean
/**
* The default is [DEFAULT_FLYING_SPEED].
*/
var flyingSpeed: Float
/**
* Modifies the field of view, like a speed potion.
*
* The default is [DEFAULT_FIELD_OF_VIEW].
*/
var fieldOfView: Float
companion object {
const val DEFAULT_FIELD_OF_VIEW = 0.1f
const val DEFAULT_FLYING_SPEED = 0.05f
}
}

View file

@ -22,6 +22,15 @@ abstract class World(val uuid: UUID) {
abstract val loadedChunks: Map<Chunk.Key, Chunk>
abstract val type: WorldType
/**
* This can be any value.
*
* Only the first 8 bytes of the SHA-256 hash of this value are sent to the client
* (and he seems not to use it in any way).
* A random value will be used if this is null.
*/
open val seed: Long? = null
/**
* All entities in this world. Use [spawnEntity] for spawning new ones.
*/

View file

@ -25,7 +25,7 @@ object JoinGamePacketCodec : OutgoingPacketCodec<JoinGamePacket>(0x26, JoinGameP
}
)
dst.writeVarInt(viewDistance.toInt())
dst.writeVarInt(maxViewDistance)
dst.writeBoolean(reducedDebugInfo)
dst.writeBoolean(respawnScreenEnabled)
}

View file

@ -8,7 +8,7 @@ object PlayerAbilitiesPacketCodec : OutgoingPacketCodec<PlayerAbilitiesPacket>(0
var flags = 0
if (invulnerable) flags = flags and 0x01
if (flying) flags = flags and 0x02
if (flyingAllowed) flags = flags and 0x04
if (canFly) flags = flags and 0x04
if (instantlyBreakBlocks) flags = flags and 0x08
dst.writeByte(flags)

View file

@ -7,7 +7,7 @@ import space.blokk.net.packet.OutgoingPacketCodec
object ServerDifficultyPacketCodec : OutgoingPacketCodec<ServerDifficultyPacket>(0x0E, ServerDifficultyPacket::class) {
override fun ServerDifficultyPacket.encode(dst: ByteBuf) {
dst.writeByte(
when (difficultyOptions.difficulty) {
when (difficultySettings.difficulty) {
Difficulty.PEACEFUL -> 0
Difficulty.EASY -> 1
Difficulty.NORMAL -> 2
@ -15,6 +15,6 @@ object ServerDifficultyPacketCodec : OutgoingPacketCodec<ServerDifficultyPacket>
}
)
dst.writeBoolean(difficultyOptions.locked)
dst.writeBoolean(difficultySettings.locked)
}
}

View file

@ -10,11 +10,10 @@ import space.blokk.world.WorldType
*
* @param entityID ID of the player entity.
* @param gameMode Game mode of the player.
* @param hardcore Whether the player is in hardcore mode.
* @param worldDimension Dimension of the world the player joins.
* @param worldSeedHash First 8 bytes of the SHA-256 hash of the world's seed.
* @param worldType Type of the world the player joins.
* @param viewDistance Maximum view distance.
* @param maxViewDistance Maximum view distance allowed by the server.
* @param reducedDebugInfo Whether the debug screen shows only reduced info.
* @param respawnScreenEnabled Whether the respawn screen is shown when the player dies.
*/
@ -25,11 +24,11 @@ data class JoinGamePacket(
val worldDimension: WorldDimension,
val worldSeedHash: Long,
val worldType: WorldType,
val viewDistance: Byte,
val maxViewDistance: Int,
val reducedDebugInfo: Boolean,
val respawnScreenEnabled: Boolean
) : OutgoingPacket() {
init {
if (viewDistance < 2 || viewDistance > 32) throw IllegalArgumentException("viewDistance must be between 2 and 32.")
if (maxViewDistance !in 2..32) throw IllegalArgumentException("viewDistance must be in 2..32")
}
}

View file

@ -7,16 +7,16 @@ import space.blokk.net.packet.OutgoingPacket
*
* @param invulnerable Whether the player is invulnerable.
* @param flying Whether the player is currently flying.
* @param flyingAllowed Whether the player is allowed to fly.
* @param canFly Whether the player is allowed to fly.
* @param instantlyBreakBlocks Whether can break blocks instantly like in creative mode.
* @param flyingSpeed The player's flying speed. The default value is `0.5`
* @param fieldOfView The player's field of view modifier. The default value is `0.1`.
* @param flyingSpeed The player's flying speed.
* @param fieldOfView The player's field of view modifier.
*/
data class PlayerAbilitiesPacket(
val invulnerable: Boolean,
val flying: Boolean,
val flyingAllowed: Boolean,
val canFly: Boolean,
val instantlyBreakBlocks: Boolean,
val flyingSpeed: Float = 0.5f,
val fieldOfView: Float = 0.1f
val flyingSpeed: Float,
val fieldOfView: Float
) : OutgoingPacket()

View file

@ -1,9 +1,9 @@
package space.blokk.net.packet.play
import space.blokk.DifficultyOptions
import space.blokk.DifficultySettings
import space.blokk.net.packet.OutgoingPacket
/**
* Sets the difficulty shown in the pause menu.
*/
data class ServerDifficultyPacket(val difficultyOptions: DifficultyOptions) : OutgoingPacket()
data class ServerDifficultyPacket(val difficultySettings: DifficultySettings) : OutgoingPacket()

View file

@ -3,10 +3,12 @@ package space.blokk.net
import io.netty.buffer.Unpooled
import space.blokk.BlokkServer
import space.blokk.Difficulty
import space.blokk.DifficultyOptions
import space.blokk.DifficultySettings
import space.blokk.chat.TextComponent
import space.blokk.event.ifCancelled
import space.blokk.net.MinecraftProtocolDataTypes.writeString
import space.blokk.net.event.PlayerInitializationEvent
import space.blokk.net.event.SessionAfterLoginEvent
import space.blokk.net.packet.login.EncryptionRequestPacket
import space.blokk.net.packet.login.EncryptionResponsePacket
import space.blokk.net.packet.login.LoginStartPacket
@ -14,15 +16,13 @@ import space.blokk.net.packet.login.LoginSuccessPacket
import space.blokk.net.packet.play.*
import space.blokk.player.BlokkPlayer
import space.blokk.player.GameMode
import space.blokk.util.AuthenticationHelper
import space.blokk.util.EncryptionUtils
import space.blokk.util.*
import space.blokk.world.Chunk
import space.blokk.world.WorldDimension
import space.blokk.world.WorldType
import java.security.MessageDigest
import java.util.*
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
import kotlin.random.Random
class LoginAndJoinProcedure(val session: BlokkSession) {
suspend fun start(packet: LoginStartPacket) {
@ -77,48 +77,7 @@ class LoginAndJoinProcedure(val session: BlokkSession) {
private suspend fun afterLogin() {
val state: Session.State.LoginSucceeded = session.state.getOrFail()
// TODO: Use real data
session.send(
JoinGamePacket(
0,
GameMode.CREATIVE,
false,
WorldDimension.OVERWORLD,
12,
WorldType.DEFAULT,
32,
reducedDebugInfo = false,
respawnScreenEnabled = true
)
)
session.sendPluginMessage(
"minecraft:brand",
Unpooled.buffer().writeString("Blokk ${BlokkServer.VERSION_WITH_V}")
)
// TODO: Use real data
session.send(ServerDifficultyPacket(DifficultyOptions(Difficulty.NORMAL, false)))
// TODO: Use real data
session.send(
PlayerAbilitiesPacket(
invulnerable = false,
flying = false,
flyingAllowed = true,
instantlyBreakBlocks = true,
flyingSpeed = 0.2f,
fieldOfView = 0.2f
)
)
session.state = Session.State.WaitingForClientSettings(state.username, state.uuid)
}
suspend fun onClientSettingsReceived(packet: ClientSettingsPacket) {
val state = session.state.getOrFail<Session.State.WaitingForClientSettings>()
val event = session.emit(PlayerInitializationEvent(session, packet.asPlayerSettings()))
val event = session.emit(SessionAfterLoginEvent(session))
val initialWorldAndLocation = event.initialWorldAndLocation
when {
@ -128,31 +87,99 @@ class LoginAndJoinProcedure(val session: BlokkSession) {
session.disconnect(loggableReason = "No spawn location set")
}
else -> {
session.state = Session.State.JoiningWorld(
BlokkPlayer(
session,
state.username,
state.uuid,
val seedAsBytes = (initialWorldAndLocation.world.seed ?: Random.nextLong()).toByteArray()
// TODO: Spawn the player entity
session.send(
JoinGamePacket(
0,
event.gameMode,
event.settings,
initialWorldAndLocation.world,
initialWorldAndLocation.location,
event.selectedHotbarSlot
event.hardcoreHearts,
initialWorldAndLocation.world.dimension,
sha256(seedAsBytes).toLong(),
initialWorldAndLocation.world.type,
event.maxViewDistance,
event.reducedDebugInfo,
event.respawnScreenEnabled
)
)
session.send(SetSelectedHotbarSlotPacket(event.selectedHotbarSlot))
session.sendPluginMessage(
"minecraft:brand",
Unpooled.buffer().writeString("Blokk ${BlokkServer.VERSION_WITH_V}")
)
// TODO: Send Declare Recipes packet
// TODO: Send Tags packet
// TODO: Send Entity Status packet with OP permission level
// As this is only visual, there is no way of changing this aside from intercepting the packet.
session.send(ServerDifficultyPacket(DifficultySettings(Difficulty.NORMAL, false)))
session.send(PlayerPositionAndLookPacket(initialWorldAndLocation.location))
session.send(
PlayerAbilitiesPacket(
event.invulnerable,
event.flying,
event.canFly,
// TODO: Consider allowing to modify this value
event.gameMode == GameMode.CREATIVE,
// TODO: Find out how this relates to the entity property named `generic.flying_speed`
event.flyingSpeed,
event.fieldOfView
)
)
// TODO: Send PlayerInfo packet
session.send(UpdateViewPositionPacket(Chunk.Key.from(session.player!!.location.asVoxelLocation())))
session.state = Session.State.WaitingForClientSettings(
state.username,
state.uuid,
event.flying,
event.canFly,
event.flyingSpeed,
event.fieldOfView,
event.gameMode,
initialWorldAndLocation,
event.invulnerable,
event.reducedDebugInfo,
event.selectedHotbarSlot
)
}
}
}
suspend fun onClientSettingsReceived(packet: ClientSettingsPacket) {
val state: Session.State.WaitingForClientSettings = session.state.getOrFail()
val settings = packet.asPlayerSettings()
session.emit(PlayerInitializationEvent(session, settings)).ifCancelled {
session.disconnect(loggableReason = "PlayerInitializationEvent was cancelled")
return
}
session.state = Session.State.JoiningWorld(
BlokkPlayer(
session,
state.username,
state.uuid,
state.gameMode,
settings,
state.initialWorldAndLocation.world,
state.initialWorldAndLocation.location,
state.reducedDebugInfo,
state.fieldOfView,
state.canFly,
state.flying,
state.flyingSpeed,
state.invulnerable,
state.selectedHotbarSlot
)
)
session.send(SetSelectedHotbarSlotPacket(state.selectedHotbarSlot))
// TODO: Send Declare Recipes packet
// TODO: Send Tags packet
// TODO: Send Entity Status packet with OP permission level
session.send(PlayerPositionAndLookPacket(state.initialWorldAndLocation.location))
// TODO: Send PlayerInfo packet
session.send(UpdateViewPositionPacket(Chunk.Key.from(session.player!!.location.asVoxelLocation())))
}
}

View file

@ -18,6 +18,12 @@ class BlokkPlayer(
override var settings: Player.Settings,
override val world: World,
override var location: LocationWithRotation,
override var reducedDebugInfo: Boolean,
override var fieldOfView: Float,
override var canFly: Boolean,
override var flying: Boolean,
override var flyingSpeed: Float,
override var invulnerable: Boolean,
selectedHotbarSlot: Byte
) : Player {
private val identifier = "BlokkPlayer($username)"
@ -30,7 +36,7 @@ class BlokkPlayer(
override var selectedHotbarSlot: Byte = 0
set(value) {
if (value in 0..8) field = value
else throw IllegalArgumentException("selectedHotbarSlot must be between 0 and 8")
else throw IllegalArgumentException("selectedHotbarSlot must be in 0..8")
}
init {

View file

@ -0,0 +1,5 @@
package space.blokk.util
import java.security.MessageDigest
fun sha256(bytes: ByteArray): ByteArray = MessageDigest.getInstance("SHA-256").run { digest(bytes) }

View file

@ -0,0 +1,13 @@
package space.blokk.util
import java.nio.ByteBuffer
@Suppress("UsePropertyAccessSyntax")
fun ByteArray.toLong() = ByteBuffer.allocate(Long.SIZE_BYTES).also {
it.put(this)
it.flip()
}.getLong()
fun Long.toByteArray() = ByteBuffer.allocate(Long.SIZE_BYTES).also {
it.putLong(this)
}.array()

View file

@ -1,4 +1,5 @@
plugins {
// TODO: Update to 1.4.2
kotlin("jvm") version "1.4.0"
kotlin("kapt") version "1.4.0"
id("minecraft-data-sources")