Archived
1
0
Fork 0

Rewrite location classes and add Position, add packets for spawning entities

This commit is contained in:
Moritz Ruth 2021-01-04 02:29:55 +01:00
parent 4d8a5ed603
commit e7d5576269
No known key found for this signature in database
GPG key ID: AFD57E23E753841B
38 changed files with 593 additions and 193 deletions

View file

@ -17,5 +17,9 @@ allprojects {
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "14"
kotlinOptions.freeCompilerArgs += "-progressive"
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.ExperimentalStdlibApi"
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.ExperimentalUnsignedTypes"
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.time.ExperimentalTime"
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.contracts.ExperimentalContracts"
}
}

View file

@ -19,37 +19,44 @@ class EntitiesGenerator(
) {
fun generate() {
val entitiesJson = workingDir.resolve("entities.json").readText()
val entities = JsonIterator.deserialize(entitiesJson).asList()
val types = JsonIterator.deserialize(entitiesJson).asList()
generateEntityStubs(entities)
generateEntitiesList(entities)
generateEntityTypes(types)
generateEntityStubs(types)
generateEntityTypeList(types)
}
private val typeLines = listOf("%L", "%S", "%Lf", "%Lf")
private fun generateEntityStubs(entities: List<JsonAny>) {
for (entity in entities) {
private fun generateEntityTypes(types: List<JsonAny>) {
for (entity in types) {
val name =
CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, entity.get("name").toString())!! + "Entity"
val filePathRelativeToSourceRoot = "./${BLOCK_PACKAGE.replace(".", "/")}/$name.kt"
if (sourcesDir.resolve(filePathRelativeToSourceRoot).exists()) continue
val typeArgs = arrayOf(
entity.get("id").toInt(),
"minecraft:" + entity.get("name").toString(),
entity.get("width").toFloat(),
entity.get("height").toFloat()
)
CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, entity.get("name").toString())!! + "EntityType"
val type = TypeSpec.classBuilder(name)
.superclass(ENTITY_TYPE)
.addType(
TypeSpec.companionObjectBuilder()
.addSuperinterface(
ENTITY_TYPE_TYPE.parameterizedBy(ClassName(ENTITY_PACKAGE, name)),
CodeBlock.of("type(\n${typeLines.joinToString(",\n") { " $it" }}\n)", *typeArgs)
.addModifiers(KModifier.ABSTRACT)
.primaryConstructor(FunSpec.constructorBuilder().addModifiers(KModifier.INTERNAL).build())
.addSuperinterface(ENTITY_TYPE_TYPE)
.addProperty(
PropertySpec
.builder("numericID", Int::class, KModifier.OVERRIDE)
.initializer("%L", entity.get("id").toInt())
.build()
)
.addProperty(
PropertySpec
.builder("id", String::class, KModifier.OVERRIDE)
.initializer("%S", "minecraft:" + entity.get("name").toString())
.build()
)
.addProperty(
PropertySpec
.builder("width", Float::class, KModifier.OVERRIDE)
.initializer("%Lf", entity.get("width").toFloat())
.build()
)
.addProperty(
PropertySpec
.builder("height", Float::class, KModifier.OVERRIDE)
.initializer("%Lf", entity.get("height").toFloat())
.build()
)
.build()
@ -61,19 +68,48 @@ class EntitiesGenerator(
}
}
private fun generateEntitiesList(entities: List<JsonAny>) {
private fun generateEntityStubs(types: List<JsonAny>) {
for (entity in types) {
val name =
CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, entity.get("name").toString())!! + "Entity"
val filePathRelativeToSourceRoot = "./${BLOCK_PACKAGE.replace(".", "/")}/$name.kt"
if (sourcesDir.resolve(filePathRelativeToSourceRoot).exists()) continue
val type = TypeSpec.classBuilder(name)
.superclass(ENTITY_TYPE)
.addProperty(
PropertySpec.builder("type", ENTITY_TYPE_TYPE, KModifier.OVERRIDE, KModifier.FINAL)
.initializer("Type")
.build()
)
.addType(
TypeSpec.companionObjectBuilder("Type")
.superclass(ClassName(ENTITY_PACKAGE, name + "Type"))
.build()
)
.build()
FileSpec.builder(ENTITY_PACKAGE, name)
.addType(type)
.build()
.writeTo(outputDir)
}
}
private fun generateEntityTypeList(entities: List<JsonAny>) {
val names = entities
.map { it.get("name").toString() }
.map { CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, it) + "Entity" }
val property = PropertySpec.builder(
"GENERATED_ENTITIES",
List::class.asTypeName().parameterizedBy(ENTITY_TYPE_TYPE.parameterizedBy(STAR))
"ENTITY_TYPES",
List::class.asTypeName().parameterizedBy(ENTITY_TYPE_TYPE)
)
.initializer("listOf(\n${names.joinToString(",\n")}\n)")
.build()
FileSpec.builder(ENTITY_PACKAGE, "Entities")
FileSpec.builder(ENTITY_PACKAGE, "EntityTypes")
.addProperty(property)
.build()
.writeTo(outputDir)

View file

@ -15,10 +15,6 @@ dependencies {
}
tasks {
compileKotlin {
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.ExperimentalUnsignedTypes"
}
shadowJar {
archiveFileName.set("TestPlugin.jar")
destinationDirectory.set(file("../serverData/plugins"))

View file

@ -5,6 +5,7 @@
package space.uranos.testplugin
import space.uranos.Position
import space.uranos.Uranos
import space.uranos.chat.TextComponent
import space.uranos.net.ServerListInfo
@ -15,7 +16,6 @@ import space.uranos.plugin.Plugin
import space.uranos.testplugin.anvil.AnvilWorld
import space.uranos.world.Dimension
import space.uranos.world.VoxelLocation
import space.uranos.world.WorldAndLocationWithRotation
import space.uranos.world.block.CraftingTableBlock
import space.uranos.world.block.GreenWoolBlock
import space.uranos.world.block.RedWoolBlock
@ -46,10 +46,11 @@ class TestPlugin: Plugin("Test", "1.0.0") {
event.gameMode = GameMode.CREATIVE
event.canFly = true
event.flying = true
event.initialWorldAndLocation = WorldAndLocationWithRotation(
world,
VoxelLocation.of(0, 2, 0).atTopCenter().withRotation(0f, 0f)
)
event.initialWorldAndLocation = VoxelLocation
.of(0, 2, 0)
.atTopCenter()
.withRotation(0f, 0f)
.inside(world)
}
}
}

View file

@ -35,21 +35,9 @@ dependencies {
kotlin {
sourceSets["main"].kotlin.srcDir("src/main/generatedKotlin")
sourceSets.all {
languageSettings.enableLanguageFeature("InlineClasses")
}
}
tasks {
compileKotlin {
kotlinOptions.freeCompilerArgs += listOf(
"-Xopt-in=kotlin.contracts.ExperimentalContracts",
"-Xopt-in=kotlin.ExperimentalUnsignedTypes",
"-Xopt-in=kotlin.RequiresOptIn"
)
}
test {
useJUnitPlatform()
}

View file

@ -0,0 +1,38 @@
/*
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/
package space.uranos
import space.uranos.world.VoxelLocation
import space.uranos.world.World
import kotlin.math.roundToInt
/**
* Represents a combination of x, y and z coordinates.
*/
data class Location(val x: Double, val y: Double, val z: Double) {
/**
* Converts this Location to a VoxelLocation by converting [x], [y] and [z] to integers using [Double.toInt],
* in contrast to [roundToBlock] which uses [Double.roundToInt].
*/
fun toVoxelLocation(): VoxelLocation = VoxelLocation(x.toInt(), y.toInt().toUByte(), z.toInt())
/**
* Converts this Location to a VoxelLocation by converting [x], [y] and [z] to integers using [Double.roundToInt],
* in contrast to [toVoxelLocation] which uses [Double.toInt].
*/
fun roundToBlock(): VoxelLocation = VoxelLocation(x.roundToInt(), y.roundToInt().toUByte(), z.roundToInt())
fun withRotation(yaw: Float, pitch: Float) = Position(x, y, z, yaw, pitch)
fun asVector() = Vector(x, y, z)
infix fun inside(world: World): Pair<World, Location> = world to this
operator fun get(part: CoordinatePart): Double = when (part) {
CoordinatePart.X -> x
CoordinatePart.Y -> y
CoordinatePart.Z -> z
}
}

View file

@ -0,0 +1,53 @@
/*
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/
package space.uranos
import space.uranos.world.VoxelLocation
import space.uranos.world.World
import java.lang.IllegalArgumentException
import kotlin.math.roundToInt
/**
* A combination of x, y and z coordinates and an orientation (yaw and pitch).
*
* @param yaw The yaw rotation in degrees. Must be in `[0; 360[`.
* @param pitch The pitch rotation as a value between -90 (up) and 90 (down).
*/
data class Position(val x: Double, val y: Double, val z: Double, val yaw: Float, val pitch: Float) {
init {
if (yaw >= 360) throw IllegalArgumentException("yaw must be lower than 360")
if (yaw < 0) throw IllegalArgumentException("yaw must not be negative")
}
/**
* Converts this Position to a VoxelLocation by converting [x], [y] and [z] to integers using [Double.toInt],
* in contrast to [roundToBlock] which uses [Double.roundToInt].
*/
fun toVoxelLocation(): VoxelLocation = VoxelLocation(x.toInt(), y.toInt().toUByte(), z.toInt())
/**
* Converts this Position to a VoxelLocation by converting [x], [y] and [z] to integers using [Double.roundToInt],
* in contrast to [toVoxelLocation] which uses [Double.toInt].
*/
fun roundToBlock(): VoxelLocation = VoxelLocation(x.roundToInt(), y.roundToInt().toUByte(), z.roundToInt())
fun toLocation(): Location = Location(x, y, z)
fun toVector() = Vector(x, y, z)
infix fun inside(world: World): Pair<World, Position> = world to this
val yawIn256Steps get() = ((yaw / 360) * 256).toInt().toUByte()
val pitchIn256Steps get() = ((yaw / 360) * 256).toInt().toUByte()
operator fun get(part: CoordinatePart): Double = when (part) {
CoordinatePart.X -> x
CoordinatePart.Y -> y
CoordinatePart.Z -> z
}
companion object {
val ZERO = Position(0.0, 0.0, 0.0, 0f, 0f)
}
}

View file

@ -6,7 +6,6 @@
package space.uranos
import space.uranos.util.clamp
import space.uranos.world.Location
import space.uranos.world.VoxelLocation
import kotlin.math.abs
import kotlin.math.pow
@ -51,6 +50,8 @@ data class Vector(val x: Double, val y: Double, val z: Double) {
return sqrt(sqrt(x.pow(2) + y.pow(2)) + z.pow(2))
}
val ZERO = Vector(0.0, 0.0, 0.0)
}
}

View file

@ -5,24 +5,13 @@
package space.uranos.entity
import space.uranos.Location
import java.util.*
import kotlin.reflect.KClass
abstract class Entity internal constructor() {
val uuid: UUID = UUID.randomUUID()
companion object {
internal inline fun <reified T : Entity> type(
numericID: Int,
id: String,
width: Float,
height: Float
) : EntityType<T> = object : EntityType<T> {
override val entityClass: KClass<T> = T::class
override val numericID: Int = numericID
override val id: String = id
override val width: Float = width
override val height: Float = height
}
}
abstract val type: EntityType
val globallyUniqueNumericID: Int = 0 // TODO: Get a real value
}

View file

@ -7,8 +7,7 @@ package space.uranos.entity
import kotlin.reflect.KClass
interface EntityType<T : Entity> {
val entityClass: KClass<T>
interface EntityType {
val numericID: Int
val id: String
val width: Float
@ -18,13 +17,7 @@ interface EntityType<T : Entity> {
/**
* All entity types, sorted by their numeric ID in ascending order.
*/
val all = GENERATED_ENTITIES
val byID = GENERATED_ENTITIES.map { it.id to it }.toMap()
private val byClass = GENERATED_ENTITIES.map { it.entityClass to it }.toMap()
@Suppress("UNCHECKED_CAST")
fun <T: Entity> byClass(entityClass: KClass<T>): EntityType<T>? = byClass[entityClass] as EntityType<T>?
inline fun <reified T: Entity> byClass(): EntityType<T>? = byClass(T::class)
val all = ENTITY_TYPES
val byID = ENTITY_TYPES.map { it.id to it }.toMap()
}
}

View file

@ -0,0 +1,14 @@
/*
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/
package space.uranos.entity
import space.uranos.Position
class ItemEntity(override var position: Position) : ObjectEntity() {
override val type: EntityType = Type
companion object Type : ItemEntityType()
}

View file

@ -0,0 +1,15 @@
/*
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/
package space.uranos.entity
import space.uranos.Position
import space.uranos.Vector
abstract class LivingEntity : Entity(), Mobile {
abstract val headPitch: Float
abstract override val position: Position
abstract override val velocity: Vector
}

View file

@ -0,0 +1,18 @@
/*
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/
package space.uranos.entity
import space.uranos.Position
import space.uranos.Vector
interface Mobile {
val position: Position
/**
* The velocity in blocks per tick.
*/
val velocity: Vector
}

View file

@ -0,0 +1,15 @@
/*
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/
package space.uranos.entity
import space.uranos.Position
import space.uranos.Vector
abstract class ObjectEntity : Entity(), Mobile {
abstract override var position: Position
final override var velocity: Vector = Vector.ZERO
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/
package space.uranos.entity
import space.uranos.CardinalDirection
import space.uranos.world.VoxelLocation
class PaintingEntity(
val topLeftLocation: VoxelLocation,
val direction: CardinalDirection,
val motive: PaintingMotive
) : Entity() {
override val type: EntityType = Type
companion object Type : PaintingEntityType()
}

View file

@ -0,0 +1,38 @@
/*
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/
package space.uranos.entity
enum class PaintingMotive(val width: Int, val height: Int) {
// Order is important
KEBAB(1, 1),
AZTEC(1, 1),
ALBAN(1, 1),
AZTEC2(1, 1),
BOMB(1, 1),
PLANT(1, 1),
WASTELAND(1, 1),
POOL(2, 1),
COURBET(2, 1),
SEA(2, 1),
SUNSET(2, 1),
CREEBET(2, 1),
WANDERER(1, 2),
GRAHAM(1, 2),
MATCH(2, 2),
BUST(2, 2),
STAGE(2, 2),
VOID(2, 2),
SKULL_AND_ROSES(2, 2),
WITHER(2, 2),
FIGHTERS(4, 2),
POINTER(4, 4),
PIGSCENE(4, 4),
BURNING_SKULL(4, 4),
SKELETON(4, 3),
DONKEY_KONG(4, 3);
val numericID = ordinal
}

View file

@ -6,13 +6,14 @@
package space.uranos.net
import io.netty.buffer.ByteBuf
import space.uranos.Position
import space.uranos.chat.TextComponent
import space.uranos.event.EventBusWrapper
import space.uranos.net.packet.OutgoingPacket
import space.uranos.net.packet.Protocol
import space.uranos.player.GameMode
import space.uranos.player.Player
import space.uranos.world.WorldAndLocationWithRotation
import space.uranos.world.World
import java.net.InetAddress
import java.util.*
import kotlin.coroutines.CoroutineContext
@ -73,7 +74,8 @@ abstract class Session {
val flyingSpeed: Float,
val fieldOfView: Float,
val gameMode: GameMode,
val initialWorldAndLocation: WorldAndLocationWithRotation,
val world: World,
val position: Position,
val invulnerable: Boolean,
val reducedDebugInfo: Boolean,
val selectedHotbarSlot: Int

View file

@ -5,11 +5,12 @@
package space.uranos.net.event
import space.uranos.Position
import space.uranos.event.Cancellable
import space.uranos.net.Session
import space.uranos.player.GameMode
import space.uranos.player.Player
import space.uranos.world.WorldAndLocationWithRotation
import space.uranos.world.World
/**
* Emitted after a [Session] finished logging in.
@ -27,7 +28,7 @@ class SessionAfterLoginEvent(override val target: Session) : SessionEvent(), Can
/**
* The location where the player will spawn. If this is null after all handlers ran, the session is disconnected.
*/
var initialWorldAndLocation: WorldAndLocationWithRotation? = null
var initialWorldAndLocation: Pair<World, Position>? = null
var maxViewDistance: Int = 32
set(value) {

View file

@ -5,10 +5,10 @@
package space.uranos.player
import space.uranos.Position
import space.uranos.chat.TextComponent
import space.uranos.net.Session
import space.uranos.world.Chunk
import space.uranos.world.LocationWithRotation
import space.uranos.world.VoxelLocation
import space.uranos.world.World
import java.util.*
@ -55,9 +55,9 @@ interface Player {
)
/**
* The current location of this player.
* The current position of this player.
*/
var location: LocationWithRotation
var position: Position
/**
* The world which currently contains this player.

View file

@ -1,82 +0,0 @@
/*
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/
package space.uranos.world
import space.uranos.CoordinatePart
import space.uranos.Vector
import kotlin.math.roundToInt
sealed class AbstractWorldAndLocation<T : AbstractLocation> {
abstract val world: World
abstract val location: T
}
data class WorldAndLocation(
override val world: World,
override val location: Location
) : AbstractWorldAndLocation<Location>()
data class WorldAndLocationWithRotation(
override val world: World,
override val location: LocationWithRotation
) : AbstractWorldAndLocation<LocationWithRotation>()
sealed class AbstractLocation {
abstract val x: Double
abstract val y: Double
abstract val z: Double
/**
* Converts this Location to a VoxelLocation by converting [x], [y] and [z] to integers using [Double.toInt],
* in contrast to [roundToBlock] which uses [Double.roundToInt].
*/
fun toVoxelLocation(): VoxelLocation = VoxelLocation(x.toInt(), y.toInt().toUByte(), z.toInt())
/**
* Converts this Location to a VoxelLocation by converting [x], [y] and [z] to integers using [Double.roundToInt],
* in contrast to [toVoxelLocation] which uses [Double.toInt].
*/
fun roundToBlock(): VoxelLocation = VoxelLocation(x.roundToInt(), y.roundToInt().toUByte(), z.roundToInt())
/**
* Returns a LocationWithRotation composed of this location, [yaw] and [pitch].
*/
fun withRotation(yaw: Float, pitch: Float) = LocationWithRotation(x, y, z, yaw, pitch)
/**
* Returns a pair of [world] and this location.
*/
abstract infix fun inside(world: World): AbstractWorldAndLocation<*>
operator fun get(part: CoordinatePart): Double = when (part) {
CoordinatePart.X -> x
CoordinatePart.Y -> y
CoordinatePart.Z -> z
}
}
/**
* Represents a set of x, y and z coordinates.
*
* Whenever used as a type, [AbstractLocation] should be preferred so that [LocationWithRotation] is also a valid value.
*/
data class Location(override val x: Double, override val y: Double, override val z: Double) : AbstractLocation() {
override fun inside(world: World) = WorldAndLocation(world, this)
fun asVector() = Vector(x, y, z)
}
data class LocationWithRotation(
override val x: Double,
override val y: Double,
override val z: Double,
val yaw: Float,
val pitch: Float
) : AbstractLocation() {
override fun inside(world: World) = WorldAndLocationWithRotation(world, this)
fun toVector() = Vector(x, y, z)
}

View file

@ -6,6 +6,7 @@
package space.uranos.world
import space.uranos.CoordinatePart
import space.uranos.Location
import space.uranos.Vector
/**

View file

@ -5,6 +5,8 @@
package space.uranos.world.block
import kotlin.annotation.Target
/**
* @suppress
*/

View file

@ -27,10 +27,6 @@ dependencies {
}
tasks {
compileKotlin {
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
}
test {
useJUnitPlatform()
}

View file

@ -25,7 +25,7 @@ object JoinGamePacketCodec : OutgoingPacketCodec<JoinGamePacket>(0x24, JoinGameP
dst.writeVarInt(1)
dst.writeString("uranos:world")
val dimensionsByID = Uranos.dimensionRegistry.items.values.mapIndexed { index, dimension ->
val dimensionsByID = Uranos.dimensionRegistry.items.values.map { dimension ->
dimension.id to buildNBT {
"natural" setAsByte !dimension.compassesSpinRandomly
"ambient_light" set dimension.ambientLight

View file

@ -26,6 +26,10 @@ object PlayProtocol : Protocol(
ServerDifficultyPacketCodec,
SetCompassTargetPacketCodec,
SetSelectedHotbarSlotPacketCodec,
SpawnExperienceOrbPacketCodec,
SpawnLivingEntityPacketCodec,
SpawnObjectEntityPacketCodec,
SpawnPaintingPacketCodec,
TagsPacketCodec,
UpdateViewPositionPacketCodec
)

View file

@ -13,11 +13,11 @@ import space.uranos.util.bitmask
object PlayerPositionAndLookPacketCodec :
OutgoingPacketCodec<PlayerPositionAndLookPacket>(0x34, PlayerPositionAndLookPacket::class) {
override fun PlayerPositionAndLookPacket.encode(dst: ByteBuf) {
dst.writeDouble(locationWithRotation.x)
dst.writeDouble(locationWithRotation.y)
dst.writeDouble(locationWithRotation.z)
dst.writeFloat(locationWithRotation.yaw)
dst.writeFloat(locationWithRotation.pitch)
dst.writeDouble(position.x)
dst.writeDouble(position.y)
dst.writeDouble(position.z)
dst.writeFloat(position.yaw)
dst.writeFloat(position.pitch)
dst.writeByte(bitmask(relativeX, relativeY, relativeZ, relativeYaw, relativePitch))
dst.writeVarInt(0) // Teleport ID, I am not sure why this is needed
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/
package space.uranos.net.packet.play
import io.netty.buffer.ByteBuf
import space.uranos.Difficulty
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
import space.uranos.net.packet.OutgoingPacketCodec
object SpawnExperienceOrbPacketCodec : OutgoingPacketCodec<SpawnExperienceOrbPacket>(0x01, SpawnExperienceOrbPacket::class) {
override fun SpawnExperienceOrbPacket.encode(dst: ByteBuf) {
dst.writeVarInt(entityID)
dst.writeDouble(location.x)
dst.writeDouble(location.y)
dst.writeDouble(location.z)
dst.writeShort(amount.toInt())
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/
package space.uranos.net.packet.play
import io.netty.buffer.ByteBuf
import space.uranos.Difficulty
import space.uranos.entity.LivingEntity
import space.uranos.net.MinecraftProtocolDataTypes.writeUUID
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
import space.uranos.net.packet.OutgoingPacketCodec
object SpawnLivingEntityPacketCodec : OutgoingPacketCodec<SpawnLivingEntityPacket>(0x02, SpawnLivingEntityPacket::class) {
@Suppress("DuplicatedCode")
override fun SpawnLivingEntityPacket.encode(dst: ByteBuf) {
dst.writeVarInt(entityID)
dst.writeUUID(uuid)
dst.writeVarInt(type.numericID)
dst.writeDouble(position.x)
dst.writeDouble(position.y)
dst.writeDouble(position.z)
dst.writeByte(position.yawIn256Steps.toInt())
dst.writeByte(position.pitchIn256Steps.toInt())
dst.writeByte(((headPitch / 360) * 256).toInt())
dst.writeShort((velocity.x * 8000).toInt().toShort().toInt())
dst.writeShort((velocity.y * 8000).toInt().toShort().toInt())
dst.writeShort((velocity.z * 8000).toInt().toShort().toInt())
}
fun getPacketFromEntity(entity: LivingEntity) = SpawnLivingEntityPacket(
entity.globallyUniqueNumericID,
entity.uuid,
entity.type,
entity.position,
entity.headPitch,
entity.velocity
)
}

View file

@ -0,0 +1,48 @@
/*
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/
package space.uranos.net.packet.play
import io.netty.buffer.ByteBuf
import space.uranos.entity.Entity
import space.uranos.entity.ItemEntity
import space.uranos.entity.ObjectEntity
import space.uranos.net.MinecraftProtocolDataTypes.writeUUID
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
import space.uranos.net.packet.OutgoingPacketCodec
import java.lang.IllegalArgumentException
object SpawnObjectEntityPacketCodec : OutgoingPacketCodec<SpawnObjectEntityPacket>(0x00, SpawnObjectEntityPacket::class) {
@Suppress("DuplicatedCode")
override fun SpawnObjectEntityPacket.encode(dst: ByteBuf) {
dst.writeVarInt(entityID)
dst.writeUUID(uuid)
dst.writeVarInt(type.numericID)
dst.writeDouble(position.x)
dst.writeDouble(position.y)
dst.writeDouble(position.z)
dst.writeByte(position.yawIn256Steps.toInt())
dst.writeByte(position.pitchIn256Steps.toInt())
dst.writeInt(data)
dst.writeShort((velocity.x * 8000).toInt().toShort().toInt())
dst.writeShort((velocity.y * 8000).toInt().toShort().toInt())
dst.writeShort((velocity.z * 8000).toInt().toShort().toInt())
}
fun getPacketFromEntity(entity: ObjectEntity) = SpawnObjectEntityPacket(
entity.globallyUniqueNumericID,
entity.uuid,
entity.type,
entity.position,
getDataForEntity(entity),
entity.velocity
)
fun getDataForEntity(entity: ObjectEntity): Int = when(entity) {
is ItemEntity -> 1
// TODO: Add remaining
else -> throw IllegalArgumentException("Unknown entity type")
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/
package space.uranos.net.packet.play
import io.netty.buffer.ByteBuf
import space.uranos.CardinalDirection
import space.uranos.Difficulty
import space.uranos.entity.PaintingEntity
import space.uranos.entity.PaintingMotive
import space.uranos.net.MinecraftProtocolDataTypes.writeUUID
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
import space.uranos.net.MinecraftProtocolDataTypes.writeVoxelLocation
import space.uranos.net.packet.OutgoingPacketCodec
import space.uranos.world.Voxel
import space.uranos.world.VoxelLocation
import kotlin.math.max
object SpawnPaintingPacketCodec : OutgoingPacketCodec<SpawnPaintingPacket>(0x03, SpawnPaintingPacket::class) {
override fun SpawnPaintingPacket.encode(dst: ByteBuf) {
dst.writeVarInt(entityID)
dst.writeUUID(uuid)
dst.writeVarInt(motive.numericID)
dst.writeVoxelLocation(centerLocation)
dst.writeByte(when(direction) {
CardinalDirection.NORTH -> 2
CardinalDirection.SOUTH -> 0
CardinalDirection.WEST -> 1
CardinalDirection.EAST -> 3
})
}
fun getPacketFromEntity(entity: PaintingEntity) = SpawnPaintingPacket(
entity.globallyUniqueNumericID,
entity.uuid,
entity.motive,
getCenterLocation(entity.topLeftLocation, entity.motive),
entity.direction
)
private fun getCenterLocation(topLeftLocation: VoxelLocation, motive: PaintingMotive): VoxelLocation =
topLeftLocation.copy(
x = max(0, motive.width / 2) + topLeftLocation.x,
z = motive.height / 2 + topLeftLocation.z
)
}

View file

@ -5,15 +5,15 @@
package space.uranos.net.packet.play
import space.uranos.Position
import space.uranos.net.packet.OutgoingPacket
import space.uranos.world.LocationWithRotation
import kotlin.random.Random
/**
* Teleports the receiving player to the specified location.
* Teleports the receiving player to the specified position.
*/
data class PlayerPositionAndLookPacket(
val locationWithRotation: LocationWithRotation,
val position: Position,
val relativeX: Boolean = false,
val relativeY: Boolean = false,
val relativeZ: Boolean = false,

View file

@ -0,0 +1,24 @@
/*
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/
package space.uranos.net.packet.play
import space.uranos.Location
import space.uranos.Position
import space.uranos.Vector
import space.uranos.entity.EntityType
import space.uranos.net.packet.OutgoingPacket
import space.uranos.world.Chunk
import space.uranos.world.ChunkData
import java.util.*
/**
* Sent to spawn experience orbs.
*/
data class SpawnExperienceOrbPacket(
val entityID: Int,
val location: Location,
val amount: Short
) : OutgoingPacket()

View file

@ -0,0 +1,29 @@
/*
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/
package space.uranos.net.packet.play
import space.uranos.Position
import space.uranos.Vector
import space.uranos.entity.EntityType
import space.uranos.net.packet.OutgoingPacket
import space.uranos.world.Chunk
import space.uranos.world.ChunkData
import java.util.*
/**
* Sent to spawn **living** entities.
*/
data class SpawnLivingEntityPacket(
val entityID: Int,
val uuid: UUID,
val type: EntityType,
val position: Position,
val headPitch: Float,
/**
* Velocity in blocks per tick
*/
val velocity: Vector
) : OutgoingPacket()

View file

@ -0,0 +1,26 @@
/*
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/
package space.uranos.net.packet.play
import space.uranos.Position
import space.uranos.Vector
import space.uranos.entity.EntityType
import space.uranos.net.packet.OutgoingPacket
import space.uranos.world.Chunk
import space.uranos.world.ChunkData
import java.util.*
/**
* Sent to spawn object entities.
*/
data class SpawnObjectEntityPacket(
val entityID: Int,
val uuid: UUID,
val type: EntityType,
val position: Position,
val data: Int,
val velocity: Vector
) : OutgoingPacket()

View file

@ -0,0 +1,24 @@
/*
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/
package space.uranos.net.packet.play
import space.uranos.CardinalDirection
import space.uranos.Location
import space.uranos.entity.PaintingMotive
import space.uranos.net.packet.OutgoingPacket
import space.uranos.world.VoxelLocation
import java.util.*
/**
* Sent to spawn **living** entities.
*/
data class SpawnPaintingPacket(
val entityID: Int,
val uuid: UUID,
val motive: PaintingMotive,
val centerLocation: VoxelLocation,
val direction: CardinalDirection
) : OutgoingPacket()

View file

@ -50,10 +50,6 @@ dependencies {
}
tasks {
compileKotlin {
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
}
test {
useJUnitPlatform()
}

View file

@ -99,7 +99,7 @@ class LoginAndJoinProcedure(val session: UranosSession) {
0,
event.gameMode,
event.hardcoreHearts,
initialWorldAndLocation.world,
initialWorldAndLocation.first,
event.maxViewDistance,
event.reducedDebugInfo,
event.respawnScreenEnabled
@ -133,7 +133,8 @@ class LoginAndJoinProcedure(val session: UranosSession) {
event.flyingSpeed,
event.fieldOfView,
event.gameMode,
initialWorldAndLocation,
initialWorldAndLocation.first,
initialWorldAndLocation.second,
event.invulnerable,
event.reducedDebugInfo,
event.selectedHotbarSlot
@ -159,8 +160,8 @@ class LoginAndJoinProcedure(val session: UranosSession) {
state.uuid,
state.gameMode,
settings,
state.initialWorldAndLocation.world,
state.initialWorldAndLocation.location,
state.world,
state.position,
state.reducedDebugInfo,
state.fieldOfView,
state.canFly,
@ -181,7 +182,7 @@ class LoginAndJoinProcedure(val session: UranosSession) {
// session.send(DeclareCommandsPacket(session.server.commandRegistry.items.values))
// UnlockRecipes
session.send(PlayerPositionAndLookPacket(state.initialWorldAndLocation.location))
session.send(PlayerPositionAndLookPacket(state.position))
session.send(PlayerInfoPacket(PlayerInfoPacket.Action.AddPlayer((session.server.players + player).map {
it.uuid to PlayerInfoPacket.Action.AddPlayer.Data(
@ -202,14 +203,14 @@ class LoginAndJoinProcedure(val session: UranosSession) {
)
)
session.send(UpdateViewPositionPacket(Chunk.Key.from(player.location.toVoxelLocation())))
session.send(UpdateViewPositionPacket(Chunk.Key.from(player.position.toVoxelLocation())))
session.scheduleKeepAlivePacket(true)
player.sendChunksAndLight()
// WorldBorder
session.send(SetCompassTargetPacket(player.compassTarget))
session.send(PlayerPositionAndLookPacket(state.initialWorldAndLocation.location))
session.send(PlayerPositionAndLookPacket(state.position))
// TODO: Wait for ClientStatus(action=0) packet
}

View file

@ -5,12 +5,12 @@
package space.uranos.player
import space.uranos.Position
import space.uranos.chat.TextComponent
import space.uranos.net.Session
import space.uranos.net.packet.play.ChunkDataPacket
import space.uranos.net.packet.play.ChunkLightDataPacket
import space.uranos.world.Chunk
import space.uranos.world.LocationWithRotation
import space.uranos.world.VoxelLocation
import space.uranos.world.World
import java.util.*
@ -23,7 +23,7 @@ class UranosPlayer(
override var gameMode: GameMode,
override var settings: Player.Settings,
override val world: World,
override var location: LocationWithRotation,
override var position: Position,
override var reducedDebugInfo: Boolean,
override var fieldOfView: Float,
override var canFly: Boolean,
@ -53,9 +53,8 @@ class UranosPlayer(
/**
* Sets [currentlyViewedChunks] to all chunks in the view distance.
*/
@OptIn(ExperimentalStdlibApi::class)
fun updateCurrentlyViewedChunks() {
val (centerX, centerZ) = Chunk.Key.from(location.toVoxelLocation())
private fun updateCurrentlyViewedChunks() {
val (centerX, centerZ) = Chunk.Key.from(position.toVoxelLocation())
val edgeLength = settings.viewDistance + 1