Add SessionAfterLoginEvent
This commit is contained in:
parent
97df4085df
commit
468273eb6d
17 changed files with 290 additions and 110 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -25,7 +25,7 @@ object JoinGamePacketCodec : OutgoingPacketCodec<JoinGamePacket>(0x26, JoinGameP
|
|||
}
|
||||
)
|
||||
|
||||
dst.writeVarInt(viewDistance.toInt())
|
||||
dst.writeVarInt(maxViewDistance)
|
||||
dst.writeBoolean(reducedDebugInfo)
|
||||
dst.writeBoolean(respawnScreenEnabled)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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())))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
5
blokk-server/src/main/kotlin/space/blokk/util/Hashing.kt
Normal file
5
blokk-server/src/main/kotlin/space/blokk/util/Hashing.kt
Normal 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) }
|
13
blokk-server/src/main/kotlin/space/blokk/util/Numbers.kt
Normal file
13
blokk-server/src/main/kotlin/space/blokk/util/Numbers.kt
Normal 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()
|
|
@ -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")
|
||||
|
|
Reference in a new issue