Archived
1
0
Fork 0

Rework entity movement and spawning

This commit is contained in:
Moritz Ruth 2021-02-27 23:50:32 +01:00
parent dc156fcf34
commit a4947b5644
No known key found for this signature in database
GPG key ID: AFD57E23E753841B
77 changed files with 644 additions and 376 deletions

3
.idea/misc.xml generated
View file

@ -2,4 +2,7 @@
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_14" default="true" project-jdk-name="14" project-jdk-type="JavaSDK" /> <component name="ProjectRootManager" version="2" languageLevel="JDK_14" default="true" project-jdk-name="14" project-jdk-type="JavaSDK" />
<component name="SuppressKotlinCodeStyleNotification">
<option name="disableForAll" value="true" />
</component>
</project> </project>

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2021 Moritz Ruth and the Uranos contributors Copyright (c) 2020-2021 Moritz Ruth and the Uranos contributors
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -42,14 +42,18 @@ running the `Optimize Imports` action, you need to disable automatic wildcard im
### KDoc ### KDoc
1. If the name of the target is already sufficient for understanding what it does or what it's value represents, you 1. If the name of the target is already sufficient for understanding what it does or what it's value represents, you should **not** add a comment. If you want
should **not** add a comment. If you want to provide additional information however, you should start the comment to provide additional information however, you should start the comment with a short description nevertheless.
with a short description nevertheless. 2. The name of the return type, property type or type of the enclosing class should **not** be wrapped in square brackets.
2. The name of the return type, property type or type of the enclosing class should **not** be wrapped in square
brackets.
3. If a comment only consists of the `@returns` block tag, the latter should be replaced with a sentence starting with 3. If a comment only consists of the `@returns` block tag, the latter should be replaced with a sentence starting with
`Returns `. `Returns `.
### Code ### Packets
1. If a member function of a class creates an instance of another class which represents the same value, the functions name should be `as<name of the other class>`. If the new instance does not exactly represent the value of the original instance (for example because it is rounded), the name should be `to<name of the other class>`. 1. Outgoing packets should not use classes purely used as data containers such as Location. Enums are allowed.
### Other
1. If a member function of a class creates an instance of another class which represents the same value, the functions name should
be `as<name of the other class>`. If the new instance does not exactly represent the value of the original instance (for example because it is rounded), the
name should be `to<name of the other class>`.

View file

@ -5,15 +5,14 @@
package space.uranos.testplugin package space.uranos.testplugin
import space.uranos.Position
import space.uranos.Uranos import space.uranos.Uranos
import space.uranos.chat.ChatColor import space.uranos.chat.ChatColor
import space.uranos.chat.TextComponent import space.uranos.chat.TextComponent
import space.uranos.entity.CowEntity import space.uranos.entity.MinecartEntity
import space.uranos.entity.Position
import space.uranos.net.ServerListInfo import space.uranos.net.ServerListInfo
import space.uranos.net.event.ServerListInfoRequestEvent import space.uranos.net.event.ServerListInfoRequestEvent
import space.uranos.net.event.SessionAfterLoginEvent import space.uranos.net.event.SessionAfterLoginEvent
import space.uranos.net.packet.play.EntityHeadYawPacket
import space.uranos.player.GameMode import space.uranos.player.GameMode
import space.uranos.plugin.Plugin import space.uranos.plugin.Plugin
import space.uranos.testplugin.anvil.AnvilWorld import space.uranos.testplugin.anvil.AnvilWorld
@ -23,11 +22,41 @@ import space.uranos.util.secondsToTicks
import space.uranos.world.* import space.uranos.world.*
import space.uranos.world.block.GreenWoolBlock import space.uranos.world.block.GreenWoolBlock
import space.uranos.world.block.RedWoolBlock import space.uranos.world.block.RedWoolBlock
import kotlin.random.Random
import kotlin.random.nextUBytes
class TestPlugin : Plugin("Test", "1.0.0") { class TestPlugin : Plugin("Test", "1.0.0") {
fun enableOptifineFix() {
Uranos.biomeRegistry.register(
Biome(
"minecraft:swamp",
Biome.Precipitation.NONE,
RGBColor(12),
RGBColor(1),
RGBColor(1),
RGBColor(1),
Biome.MoodSound(0, 0.0, "minecraft:entity.pig.ambient", 0),
0f,
0f
)
)
Uranos.biomeRegistry.register(
Biome(
"minecraft:swamp_hills",
Biome.Precipitation.NONE,
RGBColor(12),
RGBColor(1),
RGBColor(1),
RGBColor(1),
Biome.MoodSound(0, 0.0, "minecraft:entity.pig.ambient", 0),
0f,
0f
)
)
}
override suspend fun onEnable() { override suspend fun onEnable() {
enableOptifineFix()
val dimension = Dimension( val dimension = Dimension(
"test:test", "test:test",
true, true,
@ -64,10 +93,6 @@ class TestPlugin : Plugin("Test", "1.0.0") {
event.response = ServerListInfo("1.16.4", 754, TextComponent of "Test", 10, 0, emptyList()) event.response = ServerListInfo("1.16.4", 754, TextComponent of "Test", 10, 0, emptyList())
} }
val entity = Uranos.create<CowEntity>()
entity.position = Position(0.5, 10.0, 0.5, 0f, 0f)
entity.setWorld(world)
Uranos.eventBus.on<SessionAfterLoginEvent> { event -> Uranos.eventBus.on<SessionAfterLoginEvent> { event ->
event.gameMode = GameMode.CREATIVE event.gameMode = GameMode.CREATIVE
event.canFly = true event.canFly = true
@ -75,7 +100,6 @@ class TestPlugin : Plugin("Test", "1.0.0") {
event.initialWorldAndLocation = VoxelLocation event.initialWorldAndLocation = VoxelLocation
.of(0, 2, 0) .of(0, 2, 0)
.atTopCenter() .atTopCenter()
.withRotation(0f, 0f)
.inside(world) .inside(world)
Uranos.scheduler.executeRepeating(secondsToTicks(1)) { Uranos.scheduler.executeRepeating(secondsToTicks(1)) {
@ -85,20 +109,23 @@ class TestPlugin : Plugin("Test", "1.0.0") {
} }
} }
var x = 1.0 // Not showing up yet because no metadata is sent
Uranos.scheduler.executeRepeating(20) { val entity = Uranos.create<MinecartEntity>()
x += 0.2 entity.position = Position(0.0, 4.0, 0.0)
entity.setWorld(world)
var x = 0f
var y = -90f
Uranos.scheduler.executeRepeating(1) {
runInServerThread { runInServerThread {
Uranos.players.forEach { entity.yaw = x
it.session.send( entity.pitch = y
EntityHeadYawPacket(
entity.numericID,
Random.nextUBytes(1)[0]
)
)
}
} }
if (x >= 10.0) x = 0.0
x += 5f
y += 5f
if (x == 360f) x = 0f
if (y == 90f) y = -90f
} }
} }
} }

View file

@ -7,7 +7,7 @@ package space.uranos.testplugin.anvil
import com.google.common.cache.LoadingCache import com.google.common.cache.LoadingCache
import space.uranos.player.Player import space.uranos.player.Player
import space.uranos.util.createWeakValuesLoadingCache import space.uranos.util.collections.createWeakValuesLoadingCache
import space.uranos.world.* import space.uranos.world.*
import space.uranos.world.block.AirBlock import space.uranos.world.block.AirBlock

View file

@ -10,19 +10,3 @@ enum class CoordinatePart {
Y, Y,
Z Z
} }
data class CoordinatePartOrder(val first: CoordinatePart, val second: CoordinatePart, val third: CoordinatePart) {
init {
val values = arrayOf(first, second, third)
if (values.distinct().size != values.size)
throw IllegalArgumentException("first, second and third must have different values")
}
companion object {
val DEFAULT = CoordinatePartOrder(
CoordinatePart.X,
CoordinatePart.Y,
CoordinatePart.Z
)
}
}

View file

@ -1,52 +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
import space.uranos.world.VoxelLocation
import space.uranos.world.World
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 headPitch The pitch rotation of the head 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 headPitch: 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: UByte get() = ((yaw / 360) * 256).toInt().toUByte()
val headPitchIn256Steps: UByte 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

@ -5,7 +5,8 @@
package space.uranos package space.uranos
import space.uranos.util.clamp import space.uranos.entity.Position
import space.uranos.util.numbers.clamp
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
@ -18,7 +19,7 @@ data class Vector(val x: Double, val y: Double, val z: Double) {
*/ */
fun toVoxelLocation() = VoxelLocation(x.toInt(), y.toInt().clamp(0..255).toUByte(), z.toInt()) fun toVoxelLocation() = VoxelLocation(x.toInt(), y.toInt().clamp(0..255).toUByte(), z.toInt())
fun roundToBlock() = VoxelLocation(x.roundToInt(), y.toInt().clamp(0..255).toUByte(), z.roundToInt()) fun roundToBlock() = VoxelLocation(x.roundToInt(), y.toInt().clamp(0..255).toUByte(), z.roundToInt())
fun asLocation() = Location(x, y, z) fun asPosition() = Position(x, y, z)
operator fun plus(other: Vector) = Vector(x + other.x, y + other.y, z + other.z) operator fun plus(other: Vector) = Vector(x + other.x, y + other.y, z + other.z)
operator fun minus(other: Vector) = Vector(x - other.x, y - other.y, z - other.z) operator fun minus(other: Vector) = Vector(x - other.x, y - other.y, z - other.z)

View file

@ -40,7 +40,7 @@ interface Entity {
suspend fun setWorld(world: World?) suspend fun setWorld(world: World?)
/** /**
* If players should be added to [viewers] when they join. * If players should be added to [viewers] when they enter [world].
*/ */
var visibleToNewPlayers: Boolean var visibleToNewPlayers: Boolean
} }

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
interface HasMovableHead : Mobile, Entity {
/**
* Must be `-90..90`.
*/
var headPitch: Float
/**
* Must be not negative and lower than 360.
*/
var headYaw: Float
}

View file

@ -5,4 +5,5 @@
package space.uranos.entity package space.uranos.entity
interface LivingEntity : Entity, Mobile interface LivingEntity : Entity, Mobile {
}

View file

@ -5,7 +5,6 @@
package space.uranos.entity package space.uranos.entity
import space.uranos.Position
import space.uranos.Vector import space.uranos.Vector
interface Mobile { interface Mobile {

View file

@ -18,8 +18,7 @@ interface PaintingEntity : Entity {
companion object Type : AreaEffectCloudEntityType() companion object Type : AreaEffectCloudEntityType()
} }
val PaintingEntity.centerLocation fun PaintingEntity.calculateCenterLocation() = topLeftLocation.copy(
get() = topLeftLocation.copy( x = max(0, motive.width / 2) + topLeftLocation.x,
x = max(0, motive.width / 2) + topLeftLocation.x, z = motive.height / 2 + topLeftLocation.z
z = motive.height / 2 + topLeftLocation.z )
)

View file

@ -5,6 +5,6 @@
package space.uranos.entity package space.uranos.entity
import space.uranos.UranosServer interface PitchRotatable {
var pitch: Float
class UranosCowEntity(server: UranosServer) : UranosLivingEntity(server), CowEntity }

View file

@ -8,7 +8,7 @@ package space.uranos.entity
import space.uranos.player.Player import space.uranos.player.Player
import space.uranos.world.World import space.uranos.world.World
interface PlayerEntity : LivingEntity { interface PlayerEntity : LivingEntity, HasMovableHead {
val player: Player val player: Player
companion object Type : PlayerEntityType() companion object Type : PlayerEntityType()

View file

@ -3,36 +3,40 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/ */
package space.uranos package space.uranos.entity
import space.uranos.CoordinatePart
import space.uranos.Vector
import space.uranos.world.VoxelLocation import space.uranos.world.VoxelLocation
import space.uranos.world.World import space.uranos.world.World
import kotlin.math.roundToInt import kotlin.math.roundToInt
/** /**
* Represents a combination of x, y and z coordinates. * A combination of x, y and z coordinates.
*/ */
data class Location(val x: Double, val y: Double, val z: Double) { data class Position(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], * 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]. * in contrast to [roundToBlock] which uses [Double.roundToInt].
*/ */
fun toVoxelLocation(): VoxelLocation = VoxelLocation(x.toInt(), y.toInt().toUByte(), z.toInt()) 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], * 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]. * in contrast to [toVoxelLocation] which uses [Double.toInt].
*/ */
fun roundToBlock(): VoxelLocation = VoxelLocation(x.roundToInt(), y.roundToInt().toUByte(), z.roundToInt()) 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) fun asVector() = Vector(x, y, z)
infix fun inside(world: World): Pair<World, Position> = world to this
infix fun inside(world: World): Pair<World, Location> = world to this
operator fun get(part: CoordinatePart): Double = when (part) { operator fun get(part: CoordinatePart): Double = when (part) {
CoordinatePart.X -> x CoordinatePart.X -> x
CoordinatePart.Y -> y CoordinatePart.Y -> y
CoordinatePart.Z -> z CoordinatePart.Z -> z
} }
companion object {
val ZERO = Position(0.0, 0.0, 0.0)
}
} }

View file

@ -0,0 +1,10 @@
/*
* 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
interface YawRotatable {
var yaw: Float
}

View file

@ -8,9 +8,9 @@ package space.uranos.net
import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBuf
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
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.entity.Position
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
@ -77,6 +77,7 @@ abstract class Session {
val gameMode: GameMode, val gameMode: GameMode,
val world: World, val world: World,
val position: Position, val position: Position,
val headYaw: Float,
val headPitch: Float, val headPitch: Float,
val invulnerable: Boolean, val invulnerable: Boolean,
val reducedDebugInfo: Boolean, val reducedDebugInfo: Boolean,
@ -123,6 +124,8 @@ abstract class Session {
*/ */
abstract fun send(packet: OutgoingPacket) abstract fun send(packet: OutgoingPacket)
fun send(packets: Iterable<OutgoingPacket>) = packets.forEach { this.send(it) }
/** /**
* Sends a plugin message packet. * Sends a plugin message packet.
*/ */

View file

@ -5,7 +5,7 @@
package space.uranos.net.event package space.uranos.net.event
import space.uranos.Position import space.uranos.entity.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
@ -30,6 +30,7 @@ class SessionAfterLoginEvent(override val target: Session) : SessionEvent(), Can
*/ */
var initialWorldAndLocation: Pair<World, Position>? = null var initialWorldAndLocation: Pair<World, Position>? = null
var headYaw: Float = 0f
var headPitch: Float = 0f var headPitch: Float = 0f
var maxViewDistance: Int = 32 var maxViewDistance: Int = 32

View file

@ -0,0 +1,10 @@
/*
* 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.player.event
import space.uranos.player.Player
class PlayerReadyEvent(player: Player) : PlayerEvent(player)

View file

@ -1,13 +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.util
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.job
import kotlin.coroutines.CoroutineContext
fun CoroutineContext.supervisorChild(name: String) = this + CoroutineName(name) + SupervisorJob(this.job)

View file

@ -10,8 +10,8 @@ import kotlinx.coroutines.launch
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
// If it is too resource-intensive to create a new object for every property, one instance could be used per TickSynchronizationContainer
class TickSynchronizationContainer { class TickSynchronizationContainer {
// If it is too resource-intensive to create a new object for every property, one instance could be used per TickSynchronizationContainer
private val delegates = mutableSetOf<Delegate<*>>() private val delegates = mutableSetOf<Delegate<*>>()
suspend fun tick() { suspend fun tick() {

View file

@ -3,7 +3,7 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/ */
package space.uranos.util package space.uranos.util.collections
import com.google.common.cache.CacheBuilder import com.google.common.cache.CacheBuilder
import com.google.common.cache.CacheLoader import com.google.common.cache.CacheLoader

View file

@ -3,7 +3,7 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/ */
package space.uranos.util package space.uranos.util.collections
import kotlin.reflect.KClass import kotlin.reflect.KClass

View file

@ -3,7 +3,7 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/ */
package space.uranos.util package space.uranos.util.collections
import java.util.Spliterator import java.util.Spliterator
import java.util.function.Predicate import java.util.function.Predicate

View file

@ -3,7 +3,7 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/ */
package space.uranos.util package space.uranos.util.numbers
import kotlin.experimental.and import kotlin.experimental.and
import kotlin.experimental.inv import kotlin.experimental.inv

View file

@ -3,10 +3,14 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/ */
package space.uranos.util package space.uranos.util.numbers
fun Int.clamp(range: IntRange) = maxOf(minOf(range.first, range.last), minOf(maxOf(range.first, range.last), this)) fun Int.clamp(range: IntRange) = maxOf(minOf(range.first, range.last), minOf(maxOf(range.first, range.last), this))
fun clampArgument(name: String, range: IntRange, actualValue: Int) { fun validateParameterIsInRange(name: String, range: IntRange, actualValue: Int) {
if (!range.contains(actualValue)) throw IllegalArgumentException("$name must be in $range") if (!range.contains(actualValue)) throw IllegalArgumentException("$name must be in $range")
} }
fun validateParameterIsInRange(name: String, range: IntRange, actualValue: Float) {
if (range.first > actualValue || range.last < actualValue) throw IllegalArgumentException("$name must be in $range")
}

View file

@ -3,7 +3,7 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/ */
package space.uranos.util package space.uranos.util.numbers
fun floorMod(dividend: Float, divisor: Float): Float { fun floorMod(dividend: Float, divisor: Float): Float {
if (divisor == 0f) throw ArithmeticException("divisor cannot be 0") if (divisor == 0f) throw ArithmeticException("divisor cannot be 0")

View file

@ -0,0 +1,8 @@
/*
* 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.util.numbers
fun Float.mapToUByte(divisor: Float) = ((this / divisor) * 256).toInt().toUByte()

View file

@ -3,7 +3,7 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file * Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/ */
package space.uranos.util package space.uranos.util.numbers
infix fun Int.untilPossiblyLower(other: Int) = infix fun Int.untilPossiblyLower(other: Int) =
IntProgression.fromClosedRange(this, other, if (this > other) -1 else 1) IntProgression.fromClosedRange(this, other, if (this > other) -1 else 1)

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.util.numbers
fun validateYaw(value: Float, name: String = "yaw") {
if (value >= 360f) throw IllegalArgumentException("$name must be lower than 360")
if (value < 0f) throw IllegalArgumentException("$name must not be negative")
}
fun validatePitch(value: Float, name: String = "pitch") {
validateParameterIsInRange(name, -90..90, value)
}

View file

@ -7,8 +7,8 @@ package space.uranos.world
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import space.uranos.Position
import space.uranos.Uranos import space.uranos.Uranos
import space.uranos.entity.Position
import space.uranos.player.Player import space.uranos.player.Player
import kotlin.math.floor import kotlin.math.floor

View file

@ -6,8 +6,8 @@
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
import space.uranos.entity.Position
/** /**
* A location consisting of an x, y and z coordinate. * A location consisting of an x, y and z coordinate.
@ -19,7 +19,7 @@ data class VoxelLocation(val x: Int, val y: UByte, val z: Int) {
/** /**
* Converts this VoxelLocation to a Location. * Converts this VoxelLocation to a Location.
*/ */
fun asLocation(): Location = Location(x.toDouble(), y.toDouble(), z.toDouble()) fun asPosition(): Position = Position(x.toDouble(), y.toDouble(), z.toDouble())
fun asVector(): Vector = Vector(x.toDouble(), y.toDouble(), z.toDouble()) fun asVector(): Vector = Vector(x.toDouble(), y.toDouble(), z.toDouble())
@ -28,14 +28,14 @@ data class VoxelLocation(val x: Int, val y: UByte, val z: Int) {
* *
* Example: `VoxelLocation(x = 1, y = 2, z = 3)` becomes `Location(x = 1.5, y = 2.5, z = 3.5)`. * Example: `VoxelLocation(x = 1, y = 2, z = 3)` becomes `Location(x = 1.5, y = 2.5, z = 3.5)`.
*/ */
fun atCenter(): Location = Location(x.toDouble() + 0.5, y.toDouble() + 0.5, z.toDouble() + 0.5) fun atCenter(): Position = Position(x.toDouble() + 0.5, y.toDouble() + 0.5, z.toDouble() + 0.5)
/** /**
* Converts this VoxelLocation to a Location **and then adds 0.5 to x and z, but not y**. * Converts this VoxelLocation to a Location **and then adds 0.5 to x and z, but not y**.
* *
* Example: `VoxelLocation(x = 1, y = 2, z = 3)` becomes `Location(x = 1.5, y = 2, z = 3.5)`. * Example: `VoxelLocation(x = 1, y = 2, z = 3)` becomes `Location(x = 1.5, y = 2, z = 3.5)`.
*/ */
fun atTopCenter(): Location = Location(x.toDouble() + 0.5, y.toDouble(), z.toDouble() + 0.5) fun atTopCenter(): Position = Position(x.toDouble() + 0.5, y.toDouble(), z.toDouble() + 0.5)
/** /**
* Returns a VoxelLocation with the maximum x, y and z values of this and [other]. * Returns a VoxelLocation with the maximum x, y and z values of this and [other].
@ -54,6 +54,8 @@ data class VoxelLocation(val x: Int, val y: UByte, val z: Int) {
} }
companion object { companion object {
val ZERO = VoxelLocation(0, 0u, 0)
fun of(x: Int, y: Int, z: Int): VoxelLocation { fun of(x: Int, y: Int, z: Int): VoxelLocation {
if (y !in 0..255) throw IllegalArgumentException("y must be in 0..255") if (y !in 0..255) throw IllegalArgumentException("y must be in 0..255")
return VoxelLocation(x, y.toUByte(), z) return VoxelLocation(x, y.toUByte(), z)

View file

@ -10,7 +10,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import space.uranos.Vector import space.uranos.Vector
import space.uranos.entity.Entity import space.uranos.entity.Entity
import space.uranos.util.untilPossiblyLower import space.uranos.util.numbers.untilPossiblyLower
interface World { interface World {
val dispatcher: CoroutineDispatcher val dispatcher: CoroutineDispatcher

View file

@ -1,6 +1,7 @@
package space.uranos.util package space.uranos.util
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import space.uranos.util.numbers.floorMod
import strikt.api.expectThat import strikt.api.expectThat
import strikt.api.expectThrows import strikt.api.expectThrows
import strikt.assertions.isEqualTo import strikt.assertions.isEqualTo

View file

@ -12,7 +12,7 @@ import space.uranos.net.MinecraftProtocolDataTypes.writeNBT
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
import space.uranos.net.packet.OutgoingPacketCodec import space.uranos.net.packet.OutgoingPacketCodec
import space.uranos.util.generateHeightmap import space.uranos.util.generateHeightmap
import space.uranos.util.setBit import space.uranos.util.numbers.setBit
import space.uranos.util.toCompactLongArray import space.uranos.util.toCompactLongArray
import space.uranos.world.block.AirBlock import space.uranos.world.block.AirBlock
import space.uranos.world.block.CaveAirBlock import space.uranos.world.block.CaveAirBlock

View file

@ -8,8 +8,8 @@ package space.uranos.net.packet.play
import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBuf
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
import space.uranos.net.packet.OutgoingPacketCodec import space.uranos.net.packet.OutgoingPacketCodec
import space.uranos.util.checkBit import space.uranos.util.numbers.checkBit
import space.uranos.util.setBit import space.uranos.util.numbers.setBit
object ChunkLightDataPacketCodec : OutgoingPacketCodec<ChunkLightDataPacket>(0x23, ChunkLightDataPacket::class) { object ChunkLightDataPacketCodec : OutgoingPacketCodec<ChunkLightDataPacket>(0x23, ChunkLightDataPacket::class) {
private const val OUTSIDE_SECTIONS_MASK = 0b100000000000000001 private const val OUTSIDE_SECTIONS_MASK = 0b100000000000000001
@ -38,13 +38,13 @@ object ChunkLightDataPacketCodec : OutgoingPacketCodec<ChunkLightDataPacket>(0x2
dst.writeVarInt((emptyBlockLightMask shl 1) or OUTSIDE_SECTIONS_MASK) dst.writeVarInt((emptyBlockLightMask shl 1) or OUTSIDE_SECTIONS_MASK)
for (sectionValues in data.skyLightValues) { for (sectionValues in data.skyLightValues) {
if (sectionValues === null) continue if (sectionValues == null) continue
dst.writeVarInt(2048) dst.writeVarInt(2048)
sectionValues.forEach { dst.writeByte(it.toInt()) } sectionValues.forEach { dst.writeByte(it.toInt()) }
} }
for (sectionValues in data.blockLightValues) { for (sectionValues in data.blockLightValues) {
if (sectionValues === null) continue if (sectionValues == null) continue
dst.writeVarInt(2048) dst.writeVarInt(2048)
sectionValues.forEach { dst.writeByte(it.toInt()) } sectionValues.forEach { dst.writeByte(it.toInt()) }
} }

View file

@ -13,7 +13,7 @@ import space.uranos.net.packet.IncomingPacketCodec
import space.uranos.player.ChatMode import space.uranos.player.ChatMode
import space.uranos.player.Hand import space.uranos.player.Hand
import space.uranos.player.SkinPartsConfiguration import space.uranos.player.SkinPartsConfiguration
import space.uranos.util.checkBit import space.uranos.util.numbers.checkBit
object ClientSettingsPacketCodec : IncomingPacketCodec<ClientSettingsPacket>(0x05, ClientSettingsPacket::class) { object ClientSettingsPacketCodec : IncomingPacketCodec<ClientSettingsPacket>(0x05, ClientSettingsPacket::class) {
override fun decode(msg: ByteBuf): ClientSettingsPacket { override fun decode(msg: ByteBuf): ClientSettingsPacket {

View file

@ -9,11 +9,11 @@ import io.netty.buffer.ByteBuf
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
import space.uranos.net.packet.OutgoingPacketCodec import space.uranos.net.packet.OutgoingPacketCodec
object EntityHeadPitchPacketCodec : object EntityOrientationPacketCodec :
OutgoingPacketCodec<EntityHeadPitchPacket>(0x29, EntityHeadPitchPacket::class) { OutgoingPacketCodec<EntityOrientationPacket>(0x29, EntityOrientationPacket::class) {
override fun EntityHeadPitchPacket.encode(dst: ByteBuf) { override fun EntityOrientationPacket.encode(dst: ByteBuf) {
dst.writeVarInt(entityID) dst.writeVarInt(entityID)
dst.writeByte(0) // Should be yaw, but is actually ignored. Use EntityHeadYawPacket instead. dst.writeByte(yaw.toInt())
dst.writeByte(pitch.toInt()) dst.writeByte(pitch.toInt())
dst.writeBoolean(onGround) dst.writeBoolean(onGround)
} }

View file

@ -17,7 +17,7 @@ object EntityRelativeMoveWithOrientationPacketCodec :
dst.writeShort(deltaY.toInt()) dst.writeShort(deltaY.toInt())
dst.writeShort(deltaZ.toInt()) dst.writeShort(deltaZ.toInt())
dst.writeByte(yaw.toInt()) dst.writeByte(yaw.toInt())
dst.writeByte(headPitch.toInt()) dst.writeByte(pitch.toInt())
dst.writeBoolean(onGround) dst.writeBoolean(onGround)
} }
} }

View file

@ -17,7 +17,7 @@ object EntityTeleportPacketCodec :
dst.writeDouble(y) dst.writeDouble(y)
dst.writeDouble(z) dst.writeDouble(z)
dst.writeByte(yaw.toInt()) dst.writeByte(yaw.toInt())
dst.writeByte(headPitch.toInt()) dst.writeByte(pitch.toInt())
dst.writeBoolean(onGround) dst.writeBoolean(onGround)
} }
} }

View file

@ -6,9 +6,9 @@
package space.uranos.net.packet.play package space.uranos.net.packet.play
import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBuf
import space.uranos.Position import space.uranos.entity.Position
import space.uranos.net.packet.IncomingPacketCodec import space.uranos.net.packet.IncomingPacketCodec
import space.uranos.util.floorMod import space.uranos.util.numbers.floorMod
object IncomingPlayerPositionPacketCodec : object IncomingPlayerPositionPacketCodec :
IncomingPacketCodec<IncomingPlayerPositionPacket>(0x13, IncomingPlayerPositionPacket::class) { IncomingPacketCodec<IncomingPlayerPositionPacket>(0x13, IncomingPlayerPositionPacket::class) {
@ -16,10 +16,10 @@ object IncomingPlayerPositionPacketCodec :
Position( Position(
msg.readDouble(), msg.readDouble(),
msg.readDouble(), msg.readDouble(),
msg.readDouble(), msg.readDouble()
floorMod(msg.readFloat(), 360f),
msg.readFloat()
), ),
floorMod(msg.readFloat(), 360f),
msg.readFloat(),
msg.readBoolean() msg.readBoolean()
) )
} }

View file

@ -8,17 +8,17 @@ package space.uranos.net.packet.play
import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBuf
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
import space.uranos.net.packet.OutgoingPacketCodec import space.uranos.net.packet.OutgoingPacketCodec
import space.uranos.util.bitmask import space.uranos.util.numbers.bitmask
object OutgoingPlayerPositionPacketCodec : object OutgoingPlayerPositionPacketCodec :
OutgoingPacketCodec<OutgoingPlayerPositionPacket>(0x34, OutgoingPlayerPositionPacket::class) { OutgoingPacketCodec<OutgoingPlayerPositionPacket>(0x34, OutgoingPlayerPositionPacket::class) {
override fun OutgoingPlayerPositionPacket.encode(dst: ByteBuf) { override fun OutgoingPlayerPositionPacket.encode(dst: ByteBuf) {
dst.writeDouble(position.x) dst.writeDouble(x)
dst.writeDouble(position.y) dst.writeDouble(y)
dst.writeDouble(position.z) dst.writeDouble(z)
dst.writeFloat(position.yaw) dst.writeFloat(yaw)
dst.writeFloat(position.headPitch) dst.writeFloat(pitch)
dst.writeByte(bitmask(relativeX, relativeY, relativeZ, relativeYaw, relativePitch)) dst.writeByte(bitmask(relativeX, relativeY, relativeZ, relativeYaw, relativeHeadPitch))
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

@ -18,8 +18,8 @@ object PlayProtocol : Protocol(
DeclareRecipesPacketCodec, DeclareRecipesPacketCodec,
DestroyEntitiesPacketCodec, DestroyEntitiesPacketCodec,
DisconnectPacketCodec, DisconnectPacketCodec,
EntityHeadPitchPacketCodec,
EntityHeadYawPacketCodec, EntityHeadYawPacketCodec,
EntityOrientationPacketCodec,
EntityRelativeMovePacketCodec, EntityRelativeMovePacketCodec,
EntityRelativeMoveWithOrientationPacketCodec, EntityRelativeMoveWithOrientationPacketCodec,
EntityTeleportPacketCodec, EntityTeleportPacketCodec,

View file

@ -7,7 +7,7 @@ package space.uranos.net.packet.play
import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBuf
import space.uranos.net.packet.OutgoingPacketCodec import space.uranos.net.packet.OutgoingPacketCodec
import space.uranos.util.bitmask import space.uranos.util.numbers.bitmask
object PlayerAbilitiesPacketCodec : OutgoingPacketCodec<PlayerAbilitiesPacket>(0x30, PlayerAbilitiesPacket::class) { object PlayerAbilitiesPacketCodec : OutgoingPacketCodec<PlayerAbilitiesPacket>(0x30, PlayerAbilitiesPacket::class) {
override fun PlayerAbilitiesPacket.encode(dst: ByteBuf) { override fun PlayerAbilitiesPacket.encode(dst: ByteBuf) {

View file

@ -6,13 +6,13 @@
package space.uranos.net.packet.play package space.uranos.net.packet.play
import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBuf
import space.uranos.Location import space.uranos.entity.Position
import space.uranos.net.packet.IncomingPacketCodec import space.uranos.net.packet.IncomingPacketCodec
object PlayerLocationPacketCodec : object PlayerLocationPacketCodec :
IncomingPacketCodec<PlayerLocationPacket>(0x12, PlayerLocationPacket::class) { IncomingPacketCodec<PlayerPositionPacket>(0x12, PlayerPositionPacket::class) {
override fun decode(msg: ByteBuf): PlayerLocationPacket = PlayerLocationPacket( override fun decode(msg: ByteBuf): PlayerPositionPacket = PlayerPositionPacket(
Location(msg.readDouble(), msg.readDouble(), msg.readDouble()), Position(msg.readDouble(), msg.readDouble(), msg.readDouble()),
msg.readBoolean() msg.readBoolean()
) )
} }

View file

@ -7,7 +7,7 @@ package space.uranos.net.packet.play
import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBuf
import space.uranos.net.packet.IncomingPacketCodec import space.uranos.net.packet.IncomingPacketCodec
import space.uranos.util.floorMod import space.uranos.util.numbers.floorMod
object PlayerOrientationPacketCodec : object PlayerOrientationPacketCodec :
IncomingPacketCodec<PlayerOrientationPacket>(0x14, PlayerOrientationPacket::class) { IncomingPacketCodec<PlayerOrientationPacket>(0x14, PlayerOrientationPacket::class) {

View file

@ -6,16 +6,15 @@
package space.uranos.net.packet.play package space.uranos.net.packet.play
import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBuf
import space.uranos.Difficulty
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
import space.uranos.net.packet.OutgoingPacketCodec import space.uranos.net.packet.OutgoingPacketCodec
object SpawnExperienceOrbPacketCodec : OutgoingPacketCodec<SpawnExperienceOrbPacket>(0x01, SpawnExperienceOrbPacket::class) { object SpawnExperienceOrbPacketCodec : OutgoingPacketCodec<SpawnExperienceOrbPacket>(0x01, SpawnExperienceOrbPacket::class) {
override fun SpawnExperienceOrbPacket.encode(dst: ByteBuf) { override fun SpawnExperienceOrbPacket.encode(dst: ByteBuf) {
dst.writeVarInt(entityID) dst.writeVarInt(entityID)
dst.writeDouble(location.x) dst.writeDouble(x)
dst.writeDouble(location.y) dst.writeDouble(y)
dst.writeDouble(location.z) dst.writeDouble(z)
dst.writeShort(amount.toInt()) dst.writeShort(amount.toInt())
} }
} }

View file

@ -9,9 +9,9 @@ import io.netty.buffer.ByteBuf
import space.uranos.net.MinecraftProtocolDataTypes.writeUUID import space.uranos.net.MinecraftProtocolDataTypes.writeUUID
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
import space.uranos.net.packet.OutgoingPacketCodec import space.uranos.net.packet.OutgoingPacketCodec
import space.uranos.util.numbers.mapToUByte
object SpawnLivingEntityPacketCodec : OutgoingPacketCodec<SpawnLivingEntityPacket>(0x02, SpawnLivingEntityPacket::class) { object SpawnLivingEntityPacketCodec : OutgoingPacketCodec<SpawnLivingEntityPacket>(0x02, SpawnLivingEntityPacket::class) {
@Suppress("DuplicatedCode")
override fun SpawnLivingEntityPacket.encode(dst: ByteBuf) { override fun SpawnLivingEntityPacket.encode(dst: ByteBuf) {
dst.writeVarInt(entityID) dst.writeVarInt(entityID)
dst.writeUUID(uuid) dst.writeUUID(uuid)
@ -19,9 +19,12 @@ object SpawnLivingEntityPacketCodec : OutgoingPacketCodec<SpawnLivingEntityPacke
dst.writeDouble(position.x) dst.writeDouble(position.x)
dst.writeDouble(position.y) dst.writeDouble(position.y)
dst.writeDouble(position.z) dst.writeDouble(position.z)
dst.writeByte(position.yawIn256Steps.toInt()) dst.writeByte(yaw.mapToUByte(360f).toInt())
dst.writeByte(position.headPitchIn256Steps.toInt()) dst.writeByte(pitch.mapToUByte(360f).toInt())
dst.writeByte(0) // Head pitch; I do not know what this does
// This is named "head pitch" on wiki.vg, but it is actually head yaw.
dst.writeByte(headYaw.mapToUByte(360f).toInt())
dst.writeShort((velocity.x * 8000).toInt().toShort().toInt()) dst.writeShort((velocity.x * 8000).toInt().toShort().toInt())
dst.writeShort((velocity.y * 8000).toInt().toShort().toInt()) dst.writeShort((velocity.y * 8000).toInt().toShort().toInt())
dst.writeShort((velocity.z * 8000).toInt().toShort().toInt()) dst.writeShort((velocity.z * 8000).toInt().toShort().toInt())

View file

@ -11,16 +11,15 @@ import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
import space.uranos.net.packet.OutgoingPacketCodec import space.uranos.net.packet.OutgoingPacketCodec
object SpawnObjectEntityPacketCodec : OutgoingPacketCodec<SpawnObjectEntityPacket>(0x00, SpawnObjectEntityPacket::class) { object SpawnObjectEntityPacketCodec : OutgoingPacketCodec<SpawnObjectEntityPacket>(0x00, SpawnObjectEntityPacket::class) {
@Suppress("DuplicatedCode")
override fun SpawnObjectEntityPacket.encode(dst: ByteBuf) { override fun SpawnObjectEntityPacket.encode(dst: ByteBuf) {
dst.writeVarInt(entityID) dst.writeVarInt(entityID)
dst.writeUUID(uuid) dst.writeUUID(uuid)
dst.writeVarInt(type.numericID) dst.writeVarInt(type)
dst.writeDouble(position.x) dst.writeDouble(x)
dst.writeDouble(position.y) dst.writeDouble(y)
dst.writeDouble(position.z) dst.writeDouble(z)
dst.writeByte(position.yawIn256Steps.toInt()) dst.writeByte(pitch.toInt())
dst.writeByte(position.headPitchIn256Steps.toInt()) dst.writeByte(yaw.toInt())
dst.writeInt(data) dst.writeInt(data)
dst.writeShort((velocity.x * 8000).toInt().toShort().toInt()) dst.writeShort((velocity.x * 8000).toInt().toShort().toInt())
dst.writeShort((velocity.y * 8000).toInt().toShort().toInt()) dst.writeShort((velocity.y * 8000).toInt().toShort().toInt())

View file

@ -7,6 +7,7 @@ package space.uranos.net.packet.play
import space.uranos.net.packet.OutgoingPacket import space.uranos.net.packet.OutgoingPacket
// Name on wiki.vg: Entity Head Look
data class EntityHeadYawPacket( data class EntityHeadYawPacket(
val entityID: Int, val entityID: Int,
val yaw: UByte val yaw: UByte

View file

@ -7,8 +7,14 @@ package space.uranos.net.packet.play
import space.uranos.net.packet.OutgoingPacket import space.uranos.net.packet.OutgoingPacket
data class EntityHeadPitchPacket( data class EntityOrientationPacket(
val entityID: Int, val entityID: Int,
/**
* Ignored for entities implementing [HasMovableHead][space.uranos.entity.HasMovableHead].
*
* [EntityHeadYawPacket][space.uranos.net.packet.play.EntityHeadYawPacket] must be used instead.
*/
val yaw: UByte,
val pitch: UByte, val pitch: UByte,
val onGround: Boolean val onGround: Boolean
) : OutgoingPacket() ) : OutgoingPacket()

View file

@ -7,6 +7,7 @@ package space.uranos.net.packet.play
import space.uranos.net.packet.OutgoingPacket import space.uranos.net.packet.OutgoingPacket
// Name on wiki.vg: Entity Position
data class EntityRelativeMovePacket( data class EntityRelativeMovePacket(
val entityID: Int, val entityID: Int,
val deltaX: Short, val deltaX: Short,

View file

@ -7,18 +7,22 @@ package space.uranos.net.packet.play
import space.uranos.net.packet.OutgoingPacket import space.uranos.net.packet.OutgoingPacket
// Name on wiki.vg: Entity Position and Rotation
data class EntityRelativeMoveWithOrientationPacket( data class EntityRelativeMoveWithOrientationPacket(
val entityID: Int, val entityID: Int,
val deltaX: Short, val deltaX: Short,
val deltaY: Short, val deltaY: Short,
val deltaZ: Short, val deltaZ: Short,
/** /**
* Absolute value. * Ignored for entities implementing [HasMovableHead][space.uranos.entity.HasMovableHead].
*
* [EntityHeadYawPacket][space.uranos.net.packet.play.EntityHeadYawPacket] must be used instead.
*/ */
val yaw: UByte, val yaw: UByte,
/** val pitch: UByte,
* Absolute value.
*/
val headPitch: UByte,
val onGround: Boolean val onGround: Boolean
) : OutgoingPacket() ) : OutgoingPacket() {
companion object {
fun convertToDeltaShort(delta: Double): Short = (delta * 32 * 128).toInt().toShort()
}
}

View file

@ -5,7 +5,6 @@
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
data class EntityTeleportPacket( data class EntityTeleportPacket(
@ -13,17 +12,12 @@ data class EntityTeleportPacket(
val x: Double, val x: Double,
val y: Double, val y: Double,
val z: Double, val z: Double,
/**
* Ignored for entities implementing [HasMovableHead][space.uranos.entity.HasMovableHead].
*
* [EntityHeadYawPacket][space.uranos.net.packet.play.EntityHeadYawPacket] must be used instead.
*/
val yaw: UByte, val yaw: UByte,
val headPitch: UByte, val pitch: UByte,
val onGround: Boolean val onGround: Boolean
) : OutgoingPacket() { ) : OutgoingPacket()
constructor(entityID: Int, position: Position, onGround: Boolean) : this(
entityID,
position.x,
position.y,
position.z,
position.yawIn256Steps,
position.headPitchIn256Steps,
onGround
)
}

View file

@ -5,13 +5,16 @@
package space.uranos.net.packet.play package space.uranos.net.packet.play
import space.uranos.Position import space.uranos.entity.Position
import space.uranos.net.packet.IncomingPacket import space.uranos.net.packet.IncomingPacket
// Name on wiki.vg: Player Position and Rotation (serverbound)
/** /**
* Combination of [PlayerLocationPacket] and [PlayerOrientationPacket]. * Combination of [PlayerPositionPacket] and [PlayerOrientationPacket].
*/ */
data class IncomingPlayerPositionPacket( data class IncomingPlayerPositionPacket(
val position: Position, val position: Position,
val yaw: Float,
val pitch: Float,
val onGround: Boolean val onGround: Boolean
) : IncomingPacket() ) : IncomingPacket()

View file

@ -5,19 +5,23 @@
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 kotlin.random.Random import kotlin.random.Random
// Name on wiki.vg: Player Position and Look (clientbound)
/** /**
* Teleports the receiving player to the specified position. * Teleports the receiving player to the specified position.
*/ */
data class OutgoingPlayerPositionPacket( data class OutgoingPlayerPositionPacket(
val position: Position, val x: Double,
val y: Double,
val z: Double,
val yaw: Float,
val pitch: Float,
val relativeX: Boolean = false, val relativeX: Boolean = false,
val relativeY: Boolean = false, val relativeY: Boolean = false,
val relativeZ: Boolean = false, val relativeZ: Boolean = false,
val relativeYaw: Boolean = false, val relativeYaw: Boolean = false,
val relativePitch: Boolean = false, val relativeHeadPitch: Boolean = false,
val teleportID: Int = Random.nextInt() val teleportID: Int = Random.nextInt()
) : OutgoingPacket() ) : OutgoingPacket()

View file

@ -5,22 +5,14 @@
package space.uranos.net.packet.play package space.uranos.net.packet.play
import space.uranos.Position
import space.uranos.net.packet.IncomingPacket import space.uranos.net.packet.IncomingPacket
// Name on wiki.vg: Player Rotation
/** /**
* Sent by the client to update the player's orientation on the server. * Sent by the client to update the player's orientation on the server.
*
* @see [Position]
*/ */
data class PlayerOrientationPacket( data class PlayerOrientationPacket(
/**
* Yaw in degrees.
*/
val yaw: Float, val yaw: Float,
/**
* Pitch in degrees.
*/
val pitch: Float, val pitch: Float,
val onGround: Boolean val onGround: Boolean
) : IncomingPacket() ) : IncomingPacket()

View file

@ -5,13 +5,14 @@
package space.uranos.net.packet.play package space.uranos.net.packet.play
import space.uranos.Location import space.uranos.entity.Position
import space.uranos.net.packet.IncomingPacket import space.uranos.net.packet.IncomingPacket
// Name on wiki.vg: Player Position
/** /**
* Sent by the client to update the player's x, y and z coordinates on the server. * Sent by the client to update the player's x, y and z coordinates on the server.
*/ */
data class PlayerLocationPacket( data class PlayerPositionPacket(
val location: Location, val position: Position,
val onGround: Boolean val onGround: Boolean
) : IncomingPacket() ) : IncomingPacket()

View file

@ -5,20 +5,15 @@
package space.uranos.net.packet.play 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.net.packet.OutgoingPacket
import space.uranos.world.Chunk
import space.uranos.world.ChunkData
import java.util.*
/** /**
* Sent to spawn experience orbs. * Sent to spawn experience orbs.
*/ */
data class SpawnExperienceOrbPacket( data class SpawnExperienceOrbPacket(
val entityID: Int, val entityID: Int,
val location: Location, val x: Double,
val y: Double,
val z: Double,
val amount: Short val amount: Short
) : OutgoingPacket() ) : OutgoingPacket()

View file

@ -5,9 +5,9 @@
package space.uranos.net.packet.play package space.uranos.net.packet.play
import space.uranos.Position
import space.uranos.Vector import space.uranos.Vector
import space.uranos.entity.EntityType import space.uranos.entity.EntityType
import space.uranos.entity.Position
import space.uranos.net.packet.OutgoingPacket import space.uranos.net.packet.OutgoingPacket
import java.util.UUID import java.util.UUID
@ -19,6 +19,9 @@ data class SpawnLivingEntityPacket(
val uuid: UUID, val uuid: UUID,
val type: EntityType<*>, val type: EntityType<*>,
val position: Position, val position: Position,
val yaw: Float,
val pitch: Float,
val headYaw: Float,
/** /**
* Velocity in blocks per tick * Velocity in blocks per tick
*/ */

View file

@ -5,9 +5,7 @@
package space.uranos.net.packet.play package space.uranos.net.packet.play
import space.uranos.Position
import space.uranos.Vector import space.uranos.Vector
import space.uranos.entity.EntityType
import space.uranos.net.packet.OutgoingPacket import space.uranos.net.packet.OutgoingPacket
import java.util.UUID import java.util.UUID
@ -17,8 +15,12 @@ import java.util.UUID
data class SpawnObjectEntityPacket( data class SpawnObjectEntityPacket(
val entityID: Int, val entityID: Int,
val uuid: UUID, val uuid: UUID,
val type: EntityType<*>, val type: Int,
val position: Position, val x: Double,
val y: Double,
val z: Double,
val yaw: UByte,
val pitch: UByte,
val data: Int, val data: Int,
val velocity: Vector val velocity: Vector
) : OutgoingPacket() ) : OutgoingPacket()

View file

@ -1,48 +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.util
import space.uranos.Position
import space.uranos.abs
import space.uranos.net.packet.OutgoingPacket
import space.uranos.net.packet.play.EntityHeadPitchPacket
import space.uranos.net.packet.play.EntityRelativeMovePacket
import space.uranos.net.packet.play.EntityRelativeMoveWithOrientationPacket
import space.uranos.net.packet.play.EntityTeleportPacket
fun createEntityMovementPacket(entityID: Int, oldPosition: Position, newPosition: Position): OutgoingPacket? {
val delta = abs(newPosition.toVector() - oldPosition.toVector())
val orientationChanged = oldPosition.yaw != newPosition.yaw || oldPosition.headPitch != newPosition.headPitch
val onGround = true // TODO: Find out what onGround does
return if (delta.x + delta.y + delta.z == 0.0) {
if (orientationChanged) EntityHeadPitchPacket(
entityID,
newPosition.headPitchIn256Steps,
onGround
) else null
} else if (delta.x > 8 || delta.y > 8 || delta.z > 8) {
EntityTeleportPacket(entityID, newPosition, onGround)
} else {
if (orientationChanged) EntityRelativeMoveWithOrientationPacket(
entityID,
getDeltaShort(delta.x),
getDeltaShort(delta.y),
getDeltaShort(delta.z),
newPosition.yawIn256Steps,
newPosition.headPitchIn256Steps,
onGround
) else EntityRelativeMovePacket(
entityID,
getDeltaShort(delta.x),
getDeltaShort(delta.y),
getDeltaShort(delta.z),
onGround
)
}
}
private fun getDeltaShort(delta: Double): Short = (delta * 32 * 128).toInt().toShort()

View file

@ -10,20 +10,37 @@ import space.uranos.net.packet.OutgoingPacket
import space.uranos.net.packet.play.SpawnLivingEntityPacket import space.uranos.net.packet.play.SpawnLivingEntityPacket
import space.uranos.net.packet.play.SpawnObjectEntityPacket import space.uranos.net.packet.play.SpawnObjectEntityPacket
import space.uranos.net.packet.play.SpawnPaintingPacket import space.uranos.net.packet.play.SpawnPaintingPacket
import space.uranos.util.numbers.mapToUByte
fun Entity.createSpawnPacket(): OutgoingPacket = when (this) { fun Entity.createSpawnPacket(): OutgoingPacket = when (this) {
is LivingEntity -> SpawnLivingEntityPacket( is LivingEntity -> if (this is HasMovableHead) SpawnLivingEntityPacket(
numericID, numericID,
uuid, uuid,
type, type,
position, position,
headYaw,
headPitch,
headYaw,
velocity
) else SpawnLivingEntityPacket(
numericID,
uuid,
type,
position,
if (this is YawRotatable) yaw else 0f,
if (this is PitchRotatable) pitch else 0f,
0f,
velocity velocity
) )
is ObjectEntity -> SpawnObjectEntityPacket( is ObjectEntity -> SpawnObjectEntityPacket(
numericID, numericID,
uuid, uuid,
type, type.numericID,
position, position.x,
position.y,
position.z,
if (this is YawRotatable) yaw.mapToUByte(360f) else 0u,
if (this is PitchRotatable) pitch.mapToUByte(360f) else 0u,
getDataValue(), getDataValue(),
velocity velocity
) )
@ -31,7 +48,7 @@ fun Entity.createSpawnPacket(): OutgoingPacket = when (this) {
numericID, numericID,
uuid, uuid,
motive, motive,
centerLocation, calculateCenterLocation(),
facing facing
) )
else -> throw IllegalArgumentException("Unknown entity type") else -> throw IllegalArgumentException("Unknown entity type")
@ -39,6 +56,7 @@ fun Entity.createSpawnPacket(): OutgoingPacket = when (this) {
fun ObjectEntity.getDataValue(): Int = when (this) { fun ObjectEntity.getDataValue(): Int = when (this) {
is ItemEntity -> 1 is ItemEntity -> 1
is MinecartEntity -> 2
// TODO: Add remaining // TODO: Add remaining
else -> throw IllegalArgumentException("Unknown entity type") else -> throw IllegalArgumentException("Unknown entity type")
} }

View file

@ -11,20 +11,17 @@ import com.sksamuel.hoplite.ConfigSource
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import space.uranos.config.UranosConfig import space.uranos.config.UranosConfig
import space.uranos.entity.* import space.uranos.entity.*
import space.uranos.entity.event.ViewingChangedEvent import space.uranos.entity.impl.*
import space.uranos.event.EventHandlerPosition
import space.uranos.event.UranosEventBus import space.uranos.event.UranosEventBus
import space.uranos.event.UranosEventHandlerPositionManager import space.uranos.event.UranosEventHandlerPositionManager
import space.uranos.logging.Logger import space.uranos.logging.Logger
import space.uranos.logging.UranosLoggingOutputProvider import space.uranos.logging.UranosLoggingOutputProvider
import space.uranos.net.UranosSocketServer import space.uranos.net.UranosSocketServer
import space.uranos.net.packet.play.DestroyEntitiesPacket
import space.uranos.net.packet.play.PlayerInfoPacket import space.uranos.net.packet.play.PlayerInfoPacket
import space.uranos.player.UranosPlayer import space.uranos.player.UranosPlayer
import space.uranos.plugin.UranosPluginManager import space.uranos.plugin.UranosPluginManager
import space.uranos.server.Server import space.uranos.server.Server
import space.uranos.util.EncryptionUtils import space.uranos.util.EncryptionUtils
import space.uranos.util.createSpawnPacket
import space.uranos.util.msToTicks import space.uranos.util.msToTicks
import space.uranos.util.runInServerThread import space.uranos.util.runInServerThread
import space.uranos.world.UranosWorldRegistry import space.uranos.world.UranosWorldRegistry
@ -90,6 +87,9 @@ class UranosServer internal constructor() : Server() {
override fun <T : Entity> create(type: EntityType<T>): T { override fun <T : Entity> create(type: EntityType<T>): T {
val entity: UranosEntity = when (type) { val entity: UranosEntity = when (type) {
CowEntity -> UranosCowEntity(this) CowEntity -> UranosCowEntity(this)
BatEntity -> UranosBatEntity(this)
CreeperEntity -> UranosCreeperEntity(this)
MinecartEntity -> UranosMinecartEntity(this)
else -> throw IllegalArgumentException("Entities of this type cannot be created with this function") else -> throw IllegalArgumentException("Entities of this type cannot be created with this function")
} }
@ -159,12 +159,7 @@ class UranosServer internal constructor() : Server() {
} }
private fun registerListeners() { private fun registerListeners() {
eventBus.on<ViewingChangedEvent>(EventHandlerPosition.LAST) { event -> // Nothing
if (event.target == event.player.entity) return@on
if (event.viewing) event.player.session.send(event.target.createSpawnPacket())
else event.player.session.send(DestroyEntitiesPacket(arrayOf(event.target.numericID)))
}
} }
companion object { companion object {

View file

@ -10,11 +10,15 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import space.uranos.* import space.uranos.*
import space.uranos.entity.event.ViewingChangedEvent import space.uranos.entity.event.ViewingChangedEvent
import space.uranos.entity.impl.UranosPlayerEntity
import space.uranos.net.packet.OutgoingPacket
import space.uranos.net.packet.play.*
import space.uranos.player.Player import space.uranos.player.Player
import space.uranos.util.TickSynchronizationContainer import space.uranos.util.*
import space.uranos.util.WatchableSet import space.uranos.util.collections.WatchableSet
import space.uranos.util.createEntityMovementPacket import space.uranos.util.numbers.mapToUByte
import space.uranos.util.memoized import space.uranos.util.numbers.validatePitch
import space.uranos.util.numbers.validateYaw
import space.uranos.world.Chunk import space.uranos.world.Chunk
import space.uranos.world.VoxelLocation import space.uranos.world.VoxelLocation
import space.uranos.world.World import space.uranos.world.World
@ -24,38 +28,16 @@ import java.util.UUID
import java.util.WeakHashMap import java.util.WeakHashMap
sealed class UranosEntity(server: UranosServer) : Entity { sealed class UranosEntity(server: UranosServer) : Entity {
abstract val chunkKey: Chunk.Key
override fun belongsToChunk(key: Chunk.Key): Boolean = key == chunkKey override fun belongsToChunk(key: Chunk.Key): Boolean = key == chunkKey
override val numericID: Int = server.claimEntityID() override val numericID: Int = server.claimEntityID()
override val uuid: UUID = UUID.randomUUID() override val uuid: UUID = UUID.randomUUID()
override val viewers: MutableSet<Player> = object : WatchableSet<Player>(Collections.newSetFromMap(WeakHashMap())) {
override fun beforeAdd(element: Player) {
if ((this@UranosEntity as? UranosPlayerEntity)?.let { it.player == element } == true)
throw IllegalArgumentException("A player cannot be a viewer of it's own entity")
}
override fun onAdd(element: Player) {
Uranos.scope.launch { Uranos.eventBus.emit(ViewingChangedEvent(this@UranosEntity, element, true)) }
}
override fun onRemove(element: Player) {
Uranos.scope.launch { Uranos.eventBus.emit(ViewingChangedEvent(this@UranosEntity, element, false)) }
}
}
/**
* If players should be added to [viewers] when they join.
*/
override var visibleToNewPlayers: Boolean = true override var visibleToNewPlayers: Boolean = true
private val worldMutex = Mutex() private val worldMutex = Mutex()
final override var world: World? = null; private set final override var world: World? = null; private set
protected val container = TickSynchronizationContainer()
override suspend fun setWorld(world: World?) { override suspend fun setWorld(world: World?) {
if (world == null && this is PlayerEntity) if (world == null && this is PlayerEntity)
throw IllegalArgumentException("You cannot set the world of a PlayerEntity to null") throw IllegalArgumentException("You cannot set the world of a PlayerEntity to null")
@ -69,41 +51,189 @@ sealed class UranosEntity(server: UranosServer) : Entity {
} }
} }
open suspend fun tick() { protected val addedViewers = mutableSetOf<Player>()
container.tick() protected val removedViewers = mutableSetOf<Player>()
override val viewers: MutableSet<Player> = object : WatchableSet<Player>(Collections.newSetFromMap(WeakHashMap())) {
override fun beforeAdd(element: Player) {
if ((this@UranosEntity as? UranosPlayerEntity)?.let { it.player == element } == true)
throw IllegalArgumentException("A player cannot be a viewer of it's own entity")
}
override fun onAdd(element: Player) {
removedViewers.remove(element)
addedViewers.add(element)
Uranos.scope.launch { Uranos.eventBus.emit(ViewingChangedEvent(this@UranosEntity, element, true)) }
}
override fun onRemove(element: Player) {
addedViewers.remove(element)
removedViewers.add(element)
Uranos.scope.launch { Uranos.eventBus.emit(ViewingChangedEvent(this@UranosEntity, element, false)) }
}
}
abstract suspend fun tick()
abstract val chunkKey: Chunk.Key
protected val container = TickSynchronizationContainer()
protected fun sendSpawnAndDestroyPackets() {
if (addedViewers.isNotEmpty()) createSpawnPacket().let { packet -> addedViewers.forEach { it.session.send(packet) } }
if (removedViewers.isNotEmpty()) DestroyEntitiesPacket(arrayOf(numericID)).let { packet -> removedViewers.forEach { it.session.send(packet) } }
}
protected fun finishTick() {
addedViewers.clear()
removedViewers.clear()
} }
} }
abstract class UranosLivingEntity(server: UranosServer) : UranosEntity(server), LivingEntity { sealed class UranosLivingEntity(server: UranosServer) : UranosEntity(server), LivingEntity {
override var velocity: Vector = Vector.ZERO override var velocity: Vector = Vector.ZERO
override var position: Position = Position.ZERO
override val chunkKey: Chunk.Key by memoized({ position }) { Chunk.Key.from(position) }
}
abstract class UranosNotHasMovableHeadLivingEntity(server: UranosServer) : UranosLivingEntity(server) {
override var velocity: Vector = Vector.ZERO
override var position: Position = Position.ZERO
override val chunkKey: Chunk.Key by memoized({ position }) { Chunk.Key.from(position) }
private var lastSentPosition: Position = Position.ZERO private var lastSentPosition: Position = Position.ZERO
override var position: Position by container.ifChanged(Position.ZERO) { value -> private var lastSentYaw: Float = 0f
if (viewers.isNotEmpty()) createEntityMovementPacket(numericID, lastSentPosition, value)?.let { private var lastSentPitch: Float = 0f
for (viewer in viewers) {
viewer.session.send(it) final override suspend fun tick() {
container.tick()
val viewersWithoutAdded = viewers.subtract(addedViewers)
if (viewersWithoutAdded.isNotEmpty()) {
val packet = createMovementPacket()
if (packet != null) {
viewersWithoutAdded.forEach { it.session.send(packet) }
} }
} }
lastSentPosition = value if (this is PitchRotatable) lastSentPitch = pitch
if (this is YawRotatable) lastSentYaw = yaw
sendSpawnAndDestroyPackets()
finishTick()
} }
override val chunkKey: Chunk.Key by memoized({ position }) { Chunk.Key.from(position) } private fun createMovementPacket(): OutgoingPacket? = createNotHasMovableHeadMovementPacket(position, lastSentPosition, lastSentYaw, lastSentPitch)
}
abstract class UranosHasMovableHeadLivingEntity(server: UranosServer) : UranosLivingEntity(server), HasMovableHead {
override var headYaw: Float = 0f
set(value) {
validateYaw(value, "headYaw"); field = value
}
override var headPitch: Float = 0f
set(value) {
validatePitch(value, "headPitch"); field = value
}
final override suspend fun tick() {
container.tick()
val viewersWithoutAdded = viewers.subtract(addedViewers)
if (viewersWithoutAdded.isNotEmpty()) {
val packets = createMovementPackets()
if (packets.isNotEmpty()) {
viewersWithoutAdded.forEach { it.session.send(packets) }
}
}
oldPosition = position
oldHeadPitch = headPitch
oldHeadYaw = headYaw
sendSpawnAndDestroyPackets()
finishTick()
}
private var oldPosition: Position = Position.ZERO
private var oldHeadYaw: Float = 0f
private var oldHeadPitch: Float = 0f
private fun createMovementPackets(): ArrayList<OutgoingPacket> {
val packets = ArrayList<OutgoingPacket>(2)
val delta = position.asVector() - oldPosition.asVector()
val absoluteDelta = abs(delta)
val onGround = true
val oldHeadPitchUByte = oldHeadPitch.mapToUByte(90f)
val newHeadPitchUByte = headPitch.mapToUByte(90f)
val oldHeadYawUByte = oldHeadYaw.mapToUByte(360f)
val newHeadYawUByte = headYaw.mapToUByte(360f)
if (absoluteDelta.x + absoluteDelta.y + absoluteDelta.z == 0.0) {
if (oldHeadPitchUByte != newHeadPitchUByte) packets += EntityOrientationPacket(
numericID,
0u,
newHeadPitchUByte,
onGround
)
} else if (absoluteDelta.x > 8 || absoluteDelta.y > 8 || absoluteDelta.z > 8) {
packets += EntityTeleportPacket(numericID, position.x, position.y, position.z, 0u, newHeadPitchUByte, onGround)
} else {
packets += if (oldHeadPitchUByte != newHeadPitchUByte) EntityRelativeMoveWithOrientationPacket(
numericID,
EntityRelativeMoveWithOrientationPacket.convertToDeltaShort(delta.x),
EntityRelativeMoveWithOrientationPacket.convertToDeltaShort(delta.y),
EntityRelativeMoveWithOrientationPacket.convertToDeltaShort(delta.z),
0u,
newHeadPitchUByte,
onGround
) else EntityRelativeMovePacket(
numericID,
EntityRelativeMoveWithOrientationPacket.convertToDeltaShort(delta.x),
EntityRelativeMoveWithOrientationPacket.convertToDeltaShort(delta.y),
EntityRelativeMoveWithOrientationPacket.convertToDeltaShort(delta.z),
onGround
)
}
if (oldHeadYawUByte != newHeadYawUByte) packets += EntityHeadYawPacket(numericID, newHeadYawUByte)
return packets
}
} }
abstract class UranosObjectEntity(server: UranosServer) : UranosEntity(server), ObjectEntity { abstract class UranosObjectEntity(server: UranosServer) : UranosEntity(server), ObjectEntity {
override var velocity: Vector = Vector.ZERO override var velocity: Vector = Vector.ZERO
override var position: Position = Position.ZERO
private var lastSentPosition: Position = Position.ZERO
override var position: Position by container.ifChanged(Position.ZERO) { value ->
createEntityMovementPacket(numericID, lastSentPosition, value)?.let {
for (viewer in viewers) {
viewer.session.send(it)
}
}
}
override val chunkKey: Chunk.Key by memoized({ position }) { Chunk.Key.from(position) } override val chunkKey: Chunk.Key by memoized({ position }) { Chunk.Key.from(position) }
private var lastSentPosition: Position = Position.ZERO
private var lastSentYaw: Float = 0f
private var lastSentPitch: Float = 0f
final override suspend fun tick() {
container.tick()
val viewersWithoutAdded = viewers.subtract(addedViewers)
if (viewersWithoutAdded.isNotEmpty()) {
val packet = createMovementPacket()
if (packet != null) {
viewersWithoutAdded.forEach { it.session.send(packet) }
}
}
if (this is PitchRotatable) lastSentPitch = pitch
if (this is YawRotatable) lastSentYaw = yaw
sendSpawnAndDestroyPackets()
finishTick()
}
private fun createMovementPacket(): OutgoingPacket? = createNotHasMovableHeadMovementPacket(position, lastSentPosition, lastSentYaw, lastSentPitch)
} }
class UranosPaintingEntity( class UranosPaintingEntity(
@ -113,4 +243,76 @@ class UranosPaintingEntity(
override val motive: PaintingMotive override val motive: PaintingMotive
) : UranosEntity(server), PaintingEntity { ) : UranosEntity(server), PaintingEntity {
override val chunkKey: Chunk.Key get() = Chunk.Key.from(topLeftLocation) override val chunkKey: Chunk.Key get() = Chunk.Key.from(topLeftLocation)
private var lastSentTopLeftLocation: VoxelLocation = topLeftLocation
override suspend fun tick() {
container.tick()
if (lastSentTopLeftLocation != topLeftLocation) {
val viewersWithoutAdded = viewers.subtract(addedViewers)
if (viewersWithoutAdded.isNotEmpty()) {
val centerLocation = calculateCenterLocation()
val packet = EntityTeleportPacket(
numericID,
centerLocation.x.toDouble(),
centerLocation.y.toDouble(),
centerLocation.z.toDouble(),
0u,
0u,
true
)
viewersWithoutAdded.forEach { it.session.send(packet) }
}
}
lastSentTopLeftLocation = topLeftLocation
sendSpawnAndDestroyPackets()
finishTick()
}
}
fun UranosEntity.createNotHasMovableHeadMovementPacket(
position: Position,
lastSentPosition: Position,
lastSentYaw: Float,
lastSentPitch: Float
): OutgoingPacket? {
val delta = position.asVector() - lastSentPosition.asVector()
val absoluteDelta = abs(delta)
val onGround = true
val oldPitchUByte = lastSentPitch.mapToUByte(90f)
val newPitchUByte = if (this is PitchRotatable) pitch.mapToUByte(90f) else 0u
val oldYawUByte = lastSentYaw.mapToUByte(360f)
val newYawUByte = if (this is YawRotatable) yaw.mapToUByte(360f) else 0u
return if (absoluteDelta.x + absoluteDelta.y + absoluteDelta.z == 0.0) {
if (oldPitchUByte != newPitchUByte || oldYawUByte != newYawUByte) EntityOrientationPacket(
numericID,
newYawUByte,
newPitchUByte,
onGround
) else null
} else if (absoluteDelta.x > 8 || absoluteDelta.y > 8 || absoluteDelta.z > 8) {
EntityTeleportPacket(numericID, position.x, position.y, position.z, newYawUByte, newPitchUByte, onGround)
} else {
if (oldPitchUByte != newPitchUByte || oldYawUByte != newYawUByte) EntityRelativeMoveWithOrientationPacket(
numericID,
EntityRelativeMoveWithOrientationPacket.convertToDeltaShort(delta.x),
EntityRelativeMoveWithOrientationPacket.convertToDeltaShort(delta.y),
EntityRelativeMoveWithOrientationPacket.convertToDeltaShort(delta.z),
newYawUByte,
newPitchUByte,
onGround
) else EntityRelativeMovePacket(
numericID,
EntityRelativeMoveWithOrientationPacket.convertToDeltaShort(delta.x),
EntityRelativeMoveWithOrientationPacket.convertToDeltaShort(delta.y),
EntityRelativeMoveWithOrientationPacket.convertToDeltaShort(delta.z),
onGround
)
}
} }

View file

@ -0,0 +1,12 @@
/*
* 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.impl
import space.uranos.UranosServer
import space.uranos.entity.BatEntity
import space.uranos.entity.UranosHasMovableHeadLivingEntity
class UranosBatEntity(server: UranosServer) : UranosHasMovableHeadLivingEntity(server), BatEntity

View file

@ -0,0 +1,12 @@
/*
* 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.impl
import space.uranos.UranosServer
import space.uranos.entity.CowEntity
import space.uranos.entity.UranosHasMovableHeadLivingEntity
class UranosCowEntity(server: UranosServer) : UranosHasMovableHeadLivingEntity(server), CowEntity

View file

@ -0,0 +1,12 @@
/*
* 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.impl
import space.uranos.UranosServer
import space.uranos.entity.CreeperEntity
import space.uranos.entity.UranosHasMovableHeadLivingEntity
class UranosCreeperEntity(server: UranosServer) : UranosHasMovableHeadLivingEntity(server), CreeperEntity

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.entity.impl
import space.uranos.UranosServer
import space.uranos.entity.MinecartEntity
import space.uranos.entity.UranosObjectEntity
import space.uranos.util.numbers.validatePitch
import space.uranos.util.numbers.validateYaw
class UranosMinecartEntity(server: UranosServer) : UranosObjectEntity(server), MinecartEntity {
override var yaw: Float = 0f
set(value) {
validateYaw(value); field = value
}
override var pitch: Float = 0f
set(value) {
validatePitch(value); field = value
}
}

View file

@ -3,15 +3,17 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file * 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 package space.uranos.entity.impl
import space.uranos.UranosServer import space.uranos.UranosServer
import space.uranos.entity.PlayerEntity
import space.uranos.entity.UranosHasMovableHeadLivingEntity
import space.uranos.player.Player import space.uranos.player.Player
import java.util.UUID import java.util.UUID
class UranosPlayerEntity( class UranosPlayerEntity(
server: UranosServer, server: UranosServer,
override val player: Player override val player: Player
) : UranosLivingEntity(server), PlayerEntity { ) : UranosHasMovableHeadLivingEntity(server), PlayerEntity {
override val uuid: UUID = player.uuid override val uuid: UUID = player.uuid
} }

View file

@ -17,6 +17,7 @@ import space.uranos.net.event.SessionAfterLoginEvent
import space.uranos.net.packet.login.* import space.uranos.net.packet.login.*
import space.uranos.net.packet.play.* import space.uranos.net.packet.play.*
import space.uranos.player.UranosPlayer import space.uranos.player.UranosPlayer
import space.uranos.player.event.PlayerReadyEvent
import space.uranos.tag.TagRegistry import space.uranos.tag.TagRegistry
import space.uranos.util.AuthenticationHelper import space.uranos.util.AuthenticationHelper
import space.uranos.util.EncryptionUtils import space.uranos.util.EncryptionUtils
@ -134,6 +135,7 @@ class LoginAndJoinProcedure(val session: UranosSession) {
event.gameMode, event.gameMode,
initialWorldAndLocation.first, initialWorldAndLocation.first,
initialWorldAndLocation.second, initialWorldAndLocation.second,
event.headYaw,
event.headPitch, event.headPitch,
event.invulnerable, event.invulnerable,
event.reducedDebugInfo, event.reducedDebugInfo,
@ -160,8 +162,6 @@ class LoginAndJoinProcedure(val session: UranosSession) {
state.uuid, state.uuid,
state.gameMode, state.gameMode,
settings, settings,
state.position,
state.headPitch,
state.reducedDebugInfo, state.reducedDebugInfo,
state.fieldOfView, state.fieldOfView,
state.canFly, state.canFly,
@ -182,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.sendNow(OutgoingPlayerPositionPacket(state.position)) session.sendNow(OutgoingPlayerPositionPacket(state.position.x, state.position.y, state.position.z, state.headYaw, state.headPitch))
session.sendNow(PlayerInfoPacket(PlayerInfoPacket.Action.AddPlayer((session.server.players + player).map { session.sendNow(PlayerInfoPacket(PlayerInfoPacket.Action.AddPlayer((session.server.players + player).map {
it.uuid to PlayerInfoPacket.Action.AddPlayer.Data( it.uuid to PlayerInfoPacket.Action.AddPlayer.Data(
@ -209,6 +209,8 @@ class LoginAndJoinProcedure(val session: UranosSession) {
player.spawnInitially(state.world) player.spawnInitially(state.world)
session.state = Session.State.Playing(player) session.state = Session.State.Playing(player)
session.server.eventBus.emit(PlayerReadyEvent(session.player!!))
// WorldBorder // WorldBorder
session.send(CompassTargetPacket(player.compassTarget)) session.send(CompassTargetPacket(player.compassTarget))
} }

View file

@ -10,6 +10,10 @@ import space.uranos.net.UranosSession
object IncomingPlayerPositionPacketHandler : PacketReceivedEventHandler<IncomingPlayerPositionPacket>() { object IncomingPlayerPositionPacketHandler : PacketReceivedEventHandler<IncomingPlayerPositionPacket>() {
override suspend fun handle(session: UranosSession, packet: IncomingPlayerPositionPacket) { override suspend fun handle(session: UranosSession, packet: IncomingPlayerPositionPacket) {
session.earlyPlayer?.let { it.entity.position = packet.position } ?: error("Player not yet initialized") session.earlyPlayer?.let {
it.entity.position = packet.position
it.entity.headYaw = packet.yaw
it.entity.headPitch = packet.pitch
} ?: error("Player not yet initialized")
} }
} }

View file

@ -15,6 +15,6 @@ object PlayProtocolHandler : ProtocolPacketReceivedEventHandler(
IncomingPlayerPositionPacket::class to IncomingPlayerPositionPacketHandler, IncomingPlayerPositionPacket::class to IncomingPlayerPositionPacketHandler,
IncomingPluginMessagePacket::class to IncomingPluginMessagePacketHandler, IncomingPluginMessagePacket::class to IncomingPluginMessagePacketHandler,
PlayerOrientationPacket::class to PlayerOrientationPacketHandler, PlayerOrientationPacket::class to PlayerOrientationPacketHandler,
PlayerLocationPacket::class to PlayerLocationPacketHandler PlayerPositionPacket::class to PlayerLocationPacketHandler
) )
) )

View file

@ -8,13 +8,13 @@ package space.uranos.net.packet.play
import space.uranos.net.PacketReceivedEventHandler import space.uranos.net.PacketReceivedEventHandler
import space.uranos.net.UranosSession import space.uranos.net.UranosSession
object PlayerLocationPacketHandler : PacketReceivedEventHandler<PlayerLocationPacket>() { object PlayerLocationPacketHandler : PacketReceivedEventHandler<PlayerPositionPacket>() {
override suspend fun handle(session: UranosSession, packet: PlayerLocationPacket) { override suspend fun handle(session: UranosSession, packet: PlayerPositionPacket) {
val player = session.earlyPlayer ?: error("Player not yet initialized") val player = session.earlyPlayer ?: error("Player not yet initialized")
player.entity.position = player.entity.position.copy( player.entity.position = player.entity.position.copy(
x = packet.location.x, x = packet.position.x,
y = packet.location.y, y = packet.position.y,
z = packet.location.z z = packet.position.z
) )
} }
} }

View file

@ -10,7 +10,9 @@ import space.uranos.net.UranosSession
object PlayerOrientationPacketHandler : PacketReceivedEventHandler<PlayerOrientationPacket>() { object PlayerOrientationPacketHandler : PacketReceivedEventHandler<PlayerOrientationPacket>() {
override suspend fun handle(session: UranosSession, packet: PlayerOrientationPacket) { override suspend fun handle(session: UranosSession, packet: PlayerOrientationPacket) {
session.earlyPlayer?.entity?.let { it.position = it.position.copy(yaw = packet.yaw, headPitch = packet.pitch) } session.earlyPlayer?.entity?.let {
?: error("Player not yet initialized") it.headYaw = packet.yaw
it.headPitch = packet.pitch
} ?: error("Player not initialized yet")
} }
} }

View file

@ -5,7 +5,6 @@
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.entity.PlayerEntity import space.uranos.entity.PlayerEntity
import space.uranos.entity.safeWorld import space.uranos.entity.safeWorld
@ -15,7 +14,7 @@ import space.uranos.net.packet.play.ChunkLightDataPacket
import space.uranos.net.packet.play.PlayerInfoPacket import space.uranos.net.packet.play.PlayerInfoPacket
import space.uranos.net.packet.play.SelectedHotbarSlotPacket import space.uranos.net.packet.play.SelectedHotbarSlotPacket
import space.uranos.util.TickSynchronizationContainer import space.uranos.util.TickSynchronizationContainer
import space.uranos.util.clampArgument import space.uranos.util.numbers.validateParameterIsInRange
import space.uranos.world.Chunk import space.uranos.world.Chunk
import space.uranos.world.VoxelLocation import space.uranos.world.VoxelLocation
import space.uranos.world.World import space.uranos.world.World
@ -28,8 +27,6 @@ class UranosPlayer(
override val uuid: UUID, override val uuid: UUID,
override var gameMode: GameMode, override var gameMode: GameMode,
override var settings: Player.Settings, override var settings: Player.Settings,
position: Position,
headPitch: Float,
override var reducedDebugInfo: Boolean, override var reducedDebugInfo: Boolean,
override var fieldOfView: Float, override var fieldOfView: Float,
override var canFly: Boolean, override var canFly: Boolean,
@ -43,7 +40,7 @@ class UranosPlayer(
override var selectedHotbarSlot by container.ifChanged( override var selectedHotbarSlot by container.ifChanged(
selectedHotbarSlot, selectedHotbarSlot,
{ clampArgument("selectedHotbarSlot", 0..8, it) }) { { validateParameterIsInRange("selectedHotbarSlot", 0..8, it) }) {
session.sendNow(SelectedHotbarSlotPacket(it)) session.sendNow(SelectedHotbarSlotPacket(it))
} }
@ -60,12 +57,10 @@ class UranosPlayer(
override var currentlyViewedChunks = emptyList<Chunk>() override var currentlyViewedChunks = emptyList<Chunk>()
override val entity: PlayerEntity = session.server.createPlayerEntity(this).also { override val entity: PlayerEntity = session.server.createPlayerEntity(this)
it.position = position
}
suspend fun spawnInitially(world: World) { suspend fun spawnInitially(world: World) {
session.server.entities.forEach { if (it.visibleToNewPlayers && it != entity) it.viewers.add(this) } session.server.entities.forEach { if (it.visibleToNewPlayers && it != entity && it.world === world) it.viewers.add(this) }
entity.setWorld(world) entity.setWorld(world)
updateCurrentlyViewedChunks() updateCurrentlyViewedChunks()
sendChunksAndLight() sendChunksAndLight()