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> { tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "14" kotlinOptions.jvmTarget = "14"
kotlinOptions.freeCompilerArgs += "-progressive" 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() { fun generate() {
val entitiesJson = workingDir.resolve("entities.json").readText() val entitiesJson = workingDir.resolve("entities.json").readText()
val entities = JsonIterator.deserialize(entitiesJson).asList() val types = JsonIterator.deserialize(entitiesJson).asList()
generateEntityStubs(entities) generateEntityTypes(types)
generateEntitiesList(entities) generateEntityStubs(types)
generateEntityTypeList(types)
} }
private val typeLines = listOf("%L", "%S", "%Lf", "%Lf") private fun generateEntityTypes(types: List<JsonAny>) {
for (entity in types) {
private fun generateEntityStubs(entities: List<JsonAny>) {
for (entity in entities) {
val name = val name =
CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, entity.get("name").toString())!! + "Entity" CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, entity.get("name").toString())!! + "EntityType"
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()
)
val type = TypeSpec.classBuilder(name) val type = TypeSpec.classBuilder(name)
.superclass(ENTITY_TYPE) .addModifiers(KModifier.ABSTRACT)
.addType( .primaryConstructor(FunSpec.constructorBuilder().addModifiers(KModifier.INTERNAL).build())
TypeSpec.companionObjectBuilder() .addSuperinterface(ENTITY_TYPE_TYPE)
.addSuperinterface( .addProperty(
ENTITY_TYPE_TYPE.parameterizedBy(ClassName(ENTITY_PACKAGE, name)), PropertySpec
CodeBlock.of("type(\n${typeLines.joinToString(",\n") { " $it" }}\n)", *typeArgs) .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()
) )
.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 val names = entities
.map { it.get("name").toString() } .map { it.get("name").toString() }
.map { CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, it) + "Entity" } .map { CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, it) + "Entity" }
val property = PropertySpec.builder( val property = PropertySpec.builder(
"GENERATED_ENTITIES", "ENTITY_TYPES",
List::class.asTypeName().parameterizedBy(ENTITY_TYPE_TYPE.parameterizedBy(STAR)) List::class.asTypeName().parameterizedBy(ENTITY_TYPE_TYPE)
) )
.initializer("listOf(\n${names.joinToString(",\n")}\n)") .initializer("listOf(\n${names.joinToString(",\n")}\n)")
.build() .build()
FileSpec.builder(ENTITY_PACKAGE, "Entities") FileSpec.builder(ENTITY_PACKAGE, "EntityTypes")
.addProperty(property) .addProperty(property)
.build() .build()
.writeTo(outputDir) .writeTo(outputDir)

View file

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

View file

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

View file

@ -35,21 +35,9 @@ dependencies {
kotlin { kotlin {
sourceSets["main"].kotlin.srcDir("src/main/generatedKotlin") sourceSets["main"].kotlin.srcDir("src/main/generatedKotlin")
sourceSets.all {
languageSettings.enableLanguageFeature("InlineClasses")
}
} }
tasks { tasks {
compileKotlin {
kotlinOptions.freeCompilerArgs += listOf(
"-Xopt-in=kotlin.contracts.ExperimentalContracts",
"-Xopt-in=kotlin.ExperimentalUnsignedTypes",
"-Xopt-in=kotlin.RequiresOptIn"
)
}
test { test {
useJUnitPlatform() 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 package space.uranos
import space.uranos.util.clamp import space.uranos.util.clamp
import space.uranos.world.Location
import space.uranos.world.VoxelLocation import space.uranos.world.VoxelLocation
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.pow 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)) 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 package space.uranos.entity
import space.uranos.Location
import java.util.* import java.util.*
import kotlin.reflect.KClass import kotlin.reflect.KClass
abstract class Entity internal constructor() { abstract class Entity internal constructor() {
val uuid: UUID = UUID.randomUUID() val uuid: UUID = UUID.randomUUID()
companion object { abstract val type: EntityType
internal inline fun <reified T : Entity> type( val globallyUniqueNumericID: Int = 0 // TODO: Get a real value
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
}
}
} }

View file

@ -7,8 +7,7 @@ package space.uranos.entity
import kotlin.reflect.KClass import kotlin.reflect.KClass
interface EntityType<T : Entity> { interface EntityType {
val entityClass: KClass<T>
val numericID: Int val numericID: Int
val id: String val id: String
val width: Float val width: Float
@ -18,13 +17,7 @@ interface EntityType<T : Entity> {
/** /**
* All entity types, sorted by their numeric ID in ascending order. * All entity types, sorted by their numeric ID in ascending order.
*/ */
val all = GENERATED_ENTITIES val all = ENTITY_TYPES
val byID = GENERATED_ENTITIES.map { it.id to it }.toMap() val byID = ENTITY_TYPES.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)
} }
} }

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

View file

@ -5,11 +5,12 @@
package space.uranos.net.event package space.uranos.net.event
import space.uranos.Position
import space.uranos.event.Cancellable import space.uranos.event.Cancellable
import space.uranos.net.Session import space.uranos.net.Session
import space.uranos.player.GameMode import space.uranos.player.GameMode
import space.uranos.player.Player import space.uranos.player.Player
import space.uranos.world.WorldAndLocationWithRotation import space.uranos.world.World
/** /**
* Emitted after a [Session] finished logging in. * 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. * 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 var maxViewDistance: Int = 32
set(value) { set(value) {

View file

@ -5,10 +5,10 @@
package space.uranos.player package space.uranos.player
import space.uranos.Position
import space.uranos.chat.TextComponent import space.uranos.chat.TextComponent
import space.uranos.net.Session import space.uranos.net.Session
import space.uranos.world.Chunk import space.uranos.world.Chunk
import space.uranos.world.LocationWithRotation
import space.uranos.world.VoxelLocation import space.uranos.world.VoxelLocation
import space.uranos.world.World import space.uranos.world.World
import java.util.* 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. * 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 package space.uranos.world
import space.uranos.CoordinatePart import space.uranos.CoordinatePart
import space.uranos.Location
import space.uranos.Vector import space.uranos.Vector
/** /**

View file

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

View file

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

View file

@ -25,7 +25,7 @@ object JoinGamePacketCodec : OutgoingPacketCodec<JoinGamePacket>(0x24, JoinGameP
dst.writeVarInt(1) dst.writeVarInt(1)
dst.writeString("uranos:world") 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 { dimension.id to buildNBT {
"natural" setAsByte !dimension.compassesSpinRandomly "natural" setAsByte !dimension.compassesSpinRandomly
"ambient_light" set dimension.ambientLight "ambient_light" set dimension.ambientLight

View file

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

View file

@ -13,11 +13,11 @@ import space.uranos.util.bitmask
object PlayerPositionAndLookPacketCodec : object PlayerPositionAndLookPacketCodec :
OutgoingPacketCodec<PlayerPositionAndLookPacket>(0x34, PlayerPositionAndLookPacket::class) { OutgoingPacketCodec<PlayerPositionAndLookPacket>(0x34, PlayerPositionAndLookPacket::class) {
override fun PlayerPositionAndLookPacket.encode(dst: ByteBuf) { override fun PlayerPositionAndLookPacket.encode(dst: ByteBuf) {
dst.writeDouble(locationWithRotation.x) dst.writeDouble(position.x)
dst.writeDouble(locationWithRotation.y) dst.writeDouble(position.y)
dst.writeDouble(locationWithRotation.z) dst.writeDouble(position.z)
dst.writeFloat(locationWithRotation.yaw) dst.writeFloat(position.yaw)
dst.writeFloat(locationWithRotation.pitch) dst.writeFloat(position.pitch)
dst.writeByte(bitmask(relativeX, relativeY, relativeZ, relativeYaw, relativePitch)) dst.writeByte(bitmask(relativeX, relativeY, relativeZ, relativeYaw, relativePitch))
dst.writeVarInt(0) // Teleport ID, I am not sure why this is needed 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 package space.uranos.net.packet.play
import space.uranos.Position
import space.uranos.net.packet.OutgoingPacket import space.uranos.net.packet.OutgoingPacket
import space.uranos.world.LocationWithRotation
import kotlin.random.Random import kotlin.random.Random
/** /**
* Teleports the receiving player to the specified location. * Teleports the receiving player to the specified position.
*/ */
data class PlayerPositionAndLookPacket( data class PlayerPositionAndLookPacket(
val locationWithRotation: LocationWithRotation, val position: Position,
val relativeX: Boolean = false, val relativeX: Boolean = false,
val relativeY: Boolean = false, val relativeY: Boolean = false,
val relativeZ: 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 { tasks {
compileKotlin {
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
}
test { test {
useJUnitPlatform() useJUnitPlatform()
} }

View file

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

View file

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