Rework entity movement and spawning
This commit is contained in:
parent
dc156fcf34
commit
a4947b5644
77 changed files with 644 additions and 376 deletions
3
.idea/misc.xml
generated
3
.idea/misc.xml
generated
|
@ -2,4 +2,7 @@
|
|||
<project version="4">
|
||||
<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="SuppressKotlinCodeStyleNotification">
|
||||
<option name="disableForAll" value="true" />
|
||||
</component>
|
||||
</project>
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
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
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
18
README.md
18
README.md
|
@ -42,14 +42,18 @@ running the `Optimize Imports` action, you need to disable automatic wildcard im
|
|||
|
||||
### KDoc
|
||||
|
||||
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 to provide additional information however, you should start the comment
|
||||
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.
|
||||
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
|
||||
to provide additional information however, you should start the comment 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.
|
||||
3. If a comment only consists of the `@returns` block tag, the latter should be replaced with a sentence starting with
|
||||
`Returns `.
|
||||
|
||||
### Code
|
||||
### Packets
|
||||
|
||||
1. If a member function of a class creates an instance of another class which represents the same value, the function’s 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 function’s 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>`.
|
||||
|
|
|
@ -5,15 +5,14 @@
|
|||
|
||||
package space.uranos.testplugin
|
||||
|
||||
import space.uranos.Position
|
||||
import space.uranos.Uranos
|
||||
import space.uranos.chat.ChatColor
|
||||
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.event.ServerListInfoRequestEvent
|
||||
import space.uranos.net.event.SessionAfterLoginEvent
|
||||
import space.uranos.net.packet.play.EntityHeadYawPacket
|
||||
import space.uranos.player.GameMode
|
||||
import space.uranos.plugin.Plugin
|
||||
import space.uranos.testplugin.anvil.AnvilWorld
|
||||
|
@ -23,11 +22,41 @@ import space.uranos.util.secondsToTicks
|
|||
import space.uranos.world.*
|
||||
import space.uranos.world.block.GreenWoolBlock
|
||||
import space.uranos.world.block.RedWoolBlock
|
||||
import kotlin.random.Random
|
||||
import kotlin.random.nextUBytes
|
||||
|
||||
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() {
|
||||
enableOptifineFix()
|
||||
|
||||
val dimension = Dimension(
|
||||
"test:test",
|
||||
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())
|
||||
}
|
||||
|
||||
val entity = Uranos.create<CowEntity>()
|
||||
entity.position = Position(0.5, 10.0, 0.5, 0f, 0f)
|
||||
entity.setWorld(world)
|
||||
|
||||
Uranos.eventBus.on<SessionAfterLoginEvent> { event ->
|
||||
event.gameMode = GameMode.CREATIVE
|
||||
event.canFly = true
|
||||
|
@ -75,7 +100,6 @@ class TestPlugin : Plugin("Test", "1.0.0") {
|
|||
event.initialWorldAndLocation = VoxelLocation
|
||||
.of(0, 2, 0)
|
||||
.atTopCenter()
|
||||
.withRotation(0f, 0f)
|
||||
.inside(world)
|
||||
|
||||
Uranos.scheduler.executeRepeating(secondsToTicks(1)) {
|
||||
|
@ -85,20 +109,23 @@ class TestPlugin : Plugin("Test", "1.0.0") {
|
|||
}
|
||||
}
|
||||
|
||||
var x = 1.0
|
||||
Uranos.scheduler.executeRepeating(20) {
|
||||
x += 0.2
|
||||
// Not showing up yet because no metadata is sent
|
||||
val entity = Uranos.create<MinecartEntity>()
|
||||
entity.position = Position(0.0, 4.0, 0.0)
|
||||
entity.setWorld(world)
|
||||
|
||||
var x = 0f
|
||||
var y = -90f
|
||||
Uranos.scheduler.executeRepeating(1) {
|
||||
runInServerThread {
|
||||
Uranos.players.forEach {
|
||||
it.session.send(
|
||||
EntityHeadYawPacket(
|
||||
entity.numericID,
|
||||
Random.nextUBytes(1)[0]
|
||||
)
|
||||
)
|
||||
entity.yaw = x
|
||||
entity.pitch = y
|
||||
}
|
||||
}
|
||||
if (x >= 10.0) x = 0.0
|
||||
|
||||
x += 5f
|
||||
y += 5f
|
||||
if (x == 360f) x = 0f
|
||||
if (y == 90f) y = -90f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ package space.uranos.testplugin.anvil
|
|||
|
||||
import com.google.common.cache.LoadingCache
|
||||
import space.uranos.player.Player
|
||||
import space.uranos.util.createWeakValuesLoadingCache
|
||||
import space.uranos.util.collections.createWeakValuesLoadingCache
|
||||
import space.uranos.world.*
|
||||
import space.uranos.world.block.AirBlock
|
||||
|
||||
|
|
|
@ -10,19 +10,3 @@ enum class CoordinatePart {
|
|||
Y,
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -5,7 +5,8 @@
|
|||
|
||||
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 kotlin.math.abs
|
||||
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 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 minus(other: Vector) = Vector(x - other.x, y - other.y, z - other.z)
|
||||
|
|
|
@ -40,7 +40,7 @@ interface Entity {
|
|||
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -5,4 +5,5 @@
|
|||
|
||||
package space.uranos.entity
|
||||
|
||||
interface LivingEntity : Entity, Mobile
|
||||
interface LivingEntity : Entity, Mobile {
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
package space.uranos.entity
|
||||
|
||||
import space.uranos.Position
|
||||
import space.uranos.Vector
|
||||
|
||||
interface Mobile {
|
||||
|
|
|
@ -18,8 +18,7 @@ interface PaintingEntity : Entity {
|
|||
companion object Type : AreaEffectCloudEntityType()
|
||||
}
|
||||
|
||||
val PaintingEntity.centerLocation
|
||||
get() = topLeftLocation.copy(
|
||||
fun PaintingEntity.calculateCenterLocation() = topLeftLocation.copy(
|
||||
x = max(0, motive.width / 2) + topLeftLocation.x,
|
||||
z = motive.height / 2 + topLeftLocation.z
|
||||
)
|
||||
|
|
|
@ -5,6 +5,6 @@
|
|||
|
||||
package space.uranos.entity
|
||||
|
||||
import space.uranos.UranosServer
|
||||
|
||||
class UranosCowEntity(server: UranosServer) : UranosLivingEntity(server), CowEntity
|
||||
interface PitchRotatable {
|
||||
var pitch: Float
|
||||
}
|
|
@ -8,7 +8,7 @@ package space.uranos.entity
|
|||
import space.uranos.player.Player
|
||||
import space.uranos.world.World
|
||||
|
||||
interface PlayerEntity : LivingEntity {
|
||||
interface PlayerEntity : LivingEntity, HasMovableHead {
|
||||
val player: Player
|
||||
|
||||
companion object Type : PlayerEntityType()
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
||||
package space.uranos
|
||||
package space.uranos.entity
|
||||
|
||||
import space.uranos.CoordinatePart
|
||||
import space.uranos.Vector
|
||||
import space.uranos.world.VoxelLocation
|
||||
import space.uranos.world.World
|
||||
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].
|
||||
*/
|
||||
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].
|
||||
*/
|
||||
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
|
||||
infix fun inside(world: World): Pair<World, Position> = world to this
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -8,9 +8,9 @@ package space.uranos.net
|
|||
import io.netty.buffer.ByteBuf
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import space.uranos.Position
|
||||
import space.uranos.Uranos
|
||||
import space.uranos.chat.TextComponent
|
||||
import space.uranos.entity.Position
|
||||
import space.uranos.event.EventBusWrapper
|
||||
import space.uranos.net.packet.OutgoingPacket
|
||||
import space.uranos.net.packet.Protocol
|
||||
|
@ -77,6 +77,7 @@ abstract class Session {
|
|||
val gameMode: GameMode,
|
||||
val world: World,
|
||||
val position: Position,
|
||||
val headYaw: Float,
|
||||
val headPitch: Float,
|
||||
val invulnerable: Boolean,
|
||||
val reducedDebugInfo: Boolean,
|
||||
|
@ -123,6 +124,8 @@ abstract class Session {
|
|||
*/
|
||||
abstract fun send(packet: OutgoingPacket)
|
||||
|
||||
fun send(packets: Iterable<OutgoingPacket>) = packets.forEach { this.send(it) }
|
||||
|
||||
/**
|
||||
* Sends a plugin message packet.
|
||||
*/
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
package space.uranos.net.event
|
||||
|
||||
import space.uranos.Position
|
||||
import space.uranos.entity.Position
|
||||
import space.uranos.event.Cancellable
|
||||
import space.uranos.net.Session
|
||||
import space.uranos.player.GameMode
|
||||
|
@ -30,6 +30,7 @@ class SessionAfterLoginEvent(override val target: Session) : SessionEvent(), Can
|
|||
*/
|
||||
var initialWorldAndLocation: Pair<World, Position>? = null
|
||||
|
||||
var headYaw: Float = 0f
|
||||
var headPitch: Float = 0f
|
||||
|
||||
var maxViewDistance: Int = 32
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -10,8 +10,8 @@ import kotlinx.coroutines.launch
|
|||
import java.lang.ref.WeakReference
|
||||
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 {
|
||||
// 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<*>>()
|
||||
|
||||
suspend fun tick() {
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
||||
package space.uranos.util
|
||||
package space.uranos.util.collections
|
||||
|
||||
import com.google.common.cache.CacheBuilder
|
||||
import com.google.common.cache.CacheLoader
|
|
@ -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
|
||||
*/
|
||||
|
||||
package space.uranos.util
|
||||
package space.uranos.util.collections
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
||||
package space.uranos.util
|
||||
package space.uranos.util.collections
|
||||
|
||||
import java.util.Spliterator
|
||||
import java.util.function.Predicate
|
|
@ -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
|
||||
*/
|
||||
|
||||
package space.uranos.util
|
||||
package space.uranos.util.numbers
|
||||
|
||||
import kotlin.experimental.and
|
||||
import kotlin.experimental.inv
|
|
@ -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
|
||||
*/
|
||||
|
||||
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 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")
|
||||
}
|
||||
|
||||
fun validateParameterIsInRange(name: String, range: IntRange, actualValue: Float) {
|
||||
if (range.first > actualValue || range.last < actualValue) throw IllegalArgumentException("$name must be in $range")
|
||||
}
|
|
@ -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
|
||||
*/
|
||||
|
||||
package space.uranos.util
|
||||
package space.uranos.util.numbers
|
||||
|
||||
fun floorMod(dividend: Float, divisor: Float): Float {
|
||||
if (divisor == 0f) throw ArithmeticException("divisor cannot be 0")
|
|
@ -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()
|
|
@ -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
|
||||
*/
|
||||
|
||||
package space.uranos.util
|
||||
package space.uranos.util.numbers
|
||||
|
||||
infix fun Int.untilPossiblyLower(other: Int) =
|
||||
IntProgression.fromClosedRange(this, other, if (this > other) -1 else 1)
|
|
@ -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)
|
||||
}
|
|
@ -7,8 +7,8 @@ package space.uranos.world
|
|||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import space.uranos.Position
|
||||
import space.uranos.Uranos
|
||||
import space.uranos.entity.Position
|
||||
import space.uranos.player.Player
|
||||
import kotlin.math.floor
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
package space.uranos.world
|
||||
|
||||
import space.uranos.CoordinatePart
|
||||
import space.uranos.Location
|
||||
import space.uranos.Vector
|
||||
import space.uranos.entity.Position
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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())
|
||||
|
||||
|
@ -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)`.
|
||||
*/
|
||||
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**.
|
||||
*
|
||||
* 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].
|
||||
|
@ -54,6 +54,8 @@ data class VoxelLocation(val x: Int, val y: UByte, val z: Int) {
|
|||
}
|
||||
|
||||
companion object {
|
||||
val ZERO = VoxelLocation(0, 0u, 0)
|
||||
|
||||
fun of(x: Int, y: Int, z: Int): VoxelLocation {
|
||||
if (y !in 0..255) throw IllegalArgumentException("y must be in 0..255")
|
||||
return VoxelLocation(x, y.toUByte(), z)
|
||||
|
|
|
@ -10,7 +10,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.withContext
|
||||
import space.uranos.Vector
|
||||
import space.uranos.entity.Entity
|
||||
import space.uranos.util.untilPossiblyLower
|
||||
import space.uranos.util.numbers.untilPossiblyLower
|
||||
|
||||
interface World {
|
||||
val dispatcher: CoroutineDispatcher
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package space.uranos.util
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import space.uranos.util.numbers.floorMod
|
||||
import strikt.api.expectThat
|
||||
import strikt.api.expectThrows
|
||||
import strikt.assertions.isEqualTo
|
||||
|
|
|
@ -12,7 +12,7 @@ import space.uranos.net.MinecraftProtocolDataTypes.writeNBT
|
|||
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
|
||||
import space.uranos.net.packet.OutgoingPacketCodec
|
||||
import space.uranos.util.generateHeightmap
|
||||
import space.uranos.util.setBit
|
||||
import space.uranos.util.numbers.setBit
|
||||
import space.uranos.util.toCompactLongArray
|
||||
import space.uranos.world.block.AirBlock
|
||||
import space.uranos.world.block.CaveAirBlock
|
||||
|
|
|
@ -8,8 +8,8 @@ package space.uranos.net.packet.play
|
|||
import io.netty.buffer.ByteBuf
|
||||
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
|
||||
import space.uranos.net.packet.OutgoingPacketCodec
|
||||
import space.uranos.util.checkBit
|
||||
import space.uranos.util.setBit
|
||||
import space.uranos.util.numbers.checkBit
|
||||
import space.uranos.util.numbers.setBit
|
||||
|
||||
object ChunkLightDataPacketCodec : OutgoingPacketCodec<ChunkLightDataPacket>(0x23, ChunkLightDataPacket::class) {
|
||||
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)
|
||||
|
||||
for (sectionValues in data.skyLightValues) {
|
||||
if (sectionValues === null) continue
|
||||
if (sectionValues == null) continue
|
||||
dst.writeVarInt(2048)
|
||||
sectionValues.forEach { dst.writeByte(it.toInt()) }
|
||||
}
|
||||
|
||||
for (sectionValues in data.blockLightValues) {
|
||||
if (sectionValues === null) continue
|
||||
if (sectionValues == null) continue
|
||||
dst.writeVarInt(2048)
|
||||
sectionValues.forEach { dst.writeByte(it.toInt()) }
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import space.uranos.net.packet.IncomingPacketCodec
|
|||
import space.uranos.player.ChatMode
|
||||
import space.uranos.player.Hand
|
||||
import space.uranos.player.SkinPartsConfiguration
|
||||
import space.uranos.util.checkBit
|
||||
import space.uranos.util.numbers.checkBit
|
||||
|
||||
object ClientSettingsPacketCodec : IncomingPacketCodec<ClientSettingsPacket>(0x05, ClientSettingsPacket::class) {
|
||||
override fun decode(msg: ByteBuf): ClientSettingsPacket {
|
||||
|
|
|
@ -9,11 +9,11 @@ import io.netty.buffer.ByteBuf
|
|||
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
|
||||
import space.uranos.net.packet.OutgoingPacketCodec
|
||||
|
||||
object EntityHeadPitchPacketCodec :
|
||||
OutgoingPacketCodec<EntityHeadPitchPacket>(0x29, EntityHeadPitchPacket::class) {
|
||||
override fun EntityHeadPitchPacket.encode(dst: ByteBuf) {
|
||||
object EntityOrientationPacketCodec :
|
||||
OutgoingPacketCodec<EntityOrientationPacket>(0x29, EntityOrientationPacket::class) {
|
||||
override fun EntityOrientationPacket.encode(dst: ByteBuf) {
|
||||
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.writeBoolean(onGround)
|
||||
}
|
|
@ -17,7 +17,7 @@ object EntityRelativeMoveWithOrientationPacketCodec :
|
|||
dst.writeShort(deltaY.toInt())
|
||||
dst.writeShort(deltaZ.toInt())
|
||||
dst.writeByte(yaw.toInt())
|
||||
dst.writeByte(headPitch.toInt())
|
||||
dst.writeByte(pitch.toInt())
|
||||
dst.writeBoolean(onGround)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ object EntityTeleportPacketCodec :
|
|||
dst.writeDouble(y)
|
||||
dst.writeDouble(z)
|
||||
dst.writeByte(yaw.toInt())
|
||||
dst.writeByte(headPitch.toInt())
|
||||
dst.writeByte(pitch.toInt())
|
||||
dst.writeBoolean(onGround)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
package space.uranos.net.packet.play
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.uranos.Position
|
||||
import space.uranos.entity.Position
|
||||
import space.uranos.net.packet.IncomingPacketCodec
|
||||
import space.uranos.util.floorMod
|
||||
import space.uranos.util.numbers.floorMod
|
||||
|
||||
object IncomingPlayerPositionPacketCodec :
|
||||
IncomingPacketCodec<IncomingPlayerPositionPacket>(0x13, IncomingPlayerPositionPacket::class) {
|
||||
|
@ -16,10 +16,10 @@ object IncomingPlayerPositionPacketCodec :
|
|||
Position(
|
||||
msg.readDouble(),
|
||||
msg.readDouble(),
|
||||
msg.readDouble(),
|
||||
floorMod(msg.readFloat(), 360f),
|
||||
msg.readFloat()
|
||||
msg.readDouble()
|
||||
),
|
||||
floorMod(msg.readFloat(), 360f),
|
||||
msg.readFloat(),
|
||||
msg.readBoolean()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -8,17 +8,17 @@ package space.uranos.net.packet.play
|
|||
import io.netty.buffer.ByteBuf
|
||||
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
|
||||
import space.uranos.net.packet.OutgoingPacketCodec
|
||||
import space.uranos.util.bitmask
|
||||
import space.uranos.util.numbers.bitmask
|
||||
|
||||
object OutgoingPlayerPositionPacketCodec :
|
||||
OutgoingPacketCodec<OutgoingPlayerPositionPacket>(0x34, OutgoingPlayerPositionPacket::class) {
|
||||
override fun OutgoingPlayerPositionPacket.encode(dst: ByteBuf) {
|
||||
dst.writeDouble(position.x)
|
||||
dst.writeDouble(position.y)
|
||||
dst.writeDouble(position.z)
|
||||
dst.writeFloat(position.yaw)
|
||||
dst.writeFloat(position.headPitch)
|
||||
dst.writeByte(bitmask(relativeX, relativeY, relativeZ, relativeYaw, relativePitch))
|
||||
dst.writeDouble(x)
|
||||
dst.writeDouble(y)
|
||||
dst.writeDouble(z)
|
||||
dst.writeFloat(yaw)
|
||||
dst.writeFloat(pitch)
|
||||
dst.writeByte(bitmask(relativeX, relativeY, relativeZ, relativeYaw, relativeHeadPitch))
|
||||
dst.writeVarInt(0) // Teleport ID, I am not sure why this is needed
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@ object PlayProtocol : Protocol(
|
|||
DeclareRecipesPacketCodec,
|
||||
DestroyEntitiesPacketCodec,
|
||||
DisconnectPacketCodec,
|
||||
EntityHeadPitchPacketCodec,
|
||||
EntityHeadYawPacketCodec,
|
||||
EntityOrientationPacketCodec,
|
||||
EntityRelativeMovePacketCodec,
|
||||
EntityRelativeMoveWithOrientationPacketCodec,
|
||||
EntityTeleportPacketCodec,
|
||||
|
|
|
@ -7,7 +7,7 @@ package space.uranos.net.packet.play
|
|||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.uranos.net.packet.OutgoingPacketCodec
|
||||
import space.uranos.util.bitmask
|
||||
import space.uranos.util.numbers.bitmask
|
||||
|
||||
object PlayerAbilitiesPacketCodec : OutgoingPacketCodec<PlayerAbilitiesPacket>(0x30, PlayerAbilitiesPacket::class) {
|
||||
override fun PlayerAbilitiesPacket.encode(dst: ByteBuf) {
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
package space.uranos.net.packet.play
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.uranos.Location
|
||||
import space.uranos.entity.Position
|
||||
import space.uranos.net.packet.IncomingPacketCodec
|
||||
|
||||
object PlayerLocationPacketCodec :
|
||||
IncomingPacketCodec<PlayerLocationPacket>(0x12, PlayerLocationPacket::class) {
|
||||
override fun decode(msg: ByteBuf): PlayerLocationPacket = PlayerLocationPacket(
|
||||
Location(msg.readDouble(), msg.readDouble(), msg.readDouble()),
|
||||
IncomingPacketCodec<PlayerPositionPacket>(0x12, PlayerPositionPacket::class) {
|
||||
override fun decode(msg: ByteBuf): PlayerPositionPacket = PlayerPositionPacket(
|
||||
Position(msg.readDouble(), msg.readDouble(), msg.readDouble()),
|
||||
msg.readBoolean()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ package space.uranos.net.packet.play
|
|||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.uranos.net.packet.IncomingPacketCodec
|
||||
import space.uranos.util.floorMod
|
||||
import space.uranos.util.numbers.floorMod
|
||||
|
||||
object PlayerOrientationPacketCodec :
|
||||
IncomingPacketCodec<PlayerOrientationPacket>(0x14, PlayerOrientationPacket::class) {
|
||||
|
|
|
@ -6,16 +6,15 @@
|
|||
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.writeDouble(x)
|
||||
dst.writeDouble(y)
|
||||
dst.writeDouble(z)
|
||||
dst.writeShort(amount.toInt())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@ import io.netty.buffer.ByteBuf
|
|||
import space.uranos.net.MinecraftProtocolDataTypes.writeUUID
|
||||
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
|
||||
import space.uranos.net.packet.OutgoingPacketCodec
|
||||
import space.uranos.util.numbers.mapToUByte
|
||||
|
||||
object SpawnLivingEntityPacketCodec : OutgoingPacketCodec<SpawnLivingEntityPacket>(0x02, SpawnLivingEntityPacket::class) {
|
||||
@Suppress("DuplicatedCode")
|
||||
override fun SpawnLivingEntityPacket.encode(dst: ByteBuf) {
|
||||
dst.writeVarInt(entityID)
|
||||
dst.writeUUID(uuid)
|
||||
|
@ -19,9 +19,12 @@ object SpawnLivingEntityPacketCodec : OutgoingPacketCodec<SpawnLivingEntityPacke
|
|||
dst.writeDouble(position.x)
|
||||
dst.writeDouble(position.y)
|
||||
dst.writeDouble(position.z)
|
||||
dst.writeByte(position.yawIn256Steps.toInt())
|
||||
dst.writeByte(position.headPitchIn256Steps.toInt())
|
||||
dst.writeByte(0) // Head pitch; I do not know what this does
|
||||
dst.writeByte(yaw.mapToUByte(360f).toInt())
|
||||
dst.writeByte(pitch.mapToUByte(360f).toInt())
|
||||
|
||||
// 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.y * 8000).toInt().toShort().toInt())
|
||||
dst.writeShort((velocity.z * 8000).toInt().toShort().toInt())
|
||||
|
|
|
@ -11,16 +11,15 @@ import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
|
|||
import space.uranos.net.packet.OutgoingPacketCodec
|
||||
|
||||
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.headPitchIn256Steps.toInt())
|
||||
dst.writeVarInt(type)
|
||||
dst.writeDouble(x)
|
||||
dst.writeDouble(y)
|
||||
dst.writeDouble(z)
|
||||
dst.writeByte(pitch.toInt())
|
||||
dst.writeByte(yaw.toInt())
|
||||
dst.writeInt(data)
|
||||
dst.writeShort((velocity.x * 8000).toInt().toShort().toInt())
|
||||
dst.writeShort((velocity.y * 8000).toInt().toShort().toInt())
|
||||
|
|
|
@ -7,6 +7,7 @@ package space.uranos.net.packet.play
|
|||
|
||||
import space.uranos.net.packet.OutgoingPacket
|
||||
|
||||
// Name on wiki.vg: Entity Head Look
|
||||
data class EntityHeadYawPacket(
|
||||
val entityID: Int,
|
||||
val yaw: UByte
|
||||
|
|
|
@ -7,8 +7,14 @@ package space.uranos.net.packet.play
|
|||
|
||||
import space.uranos.net.packet.OutgoingPacket
|
||||
|
||||
data class EntityHeadPitchPacket(
|
||||
data class EntityOrientationPacket(
|
||||
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 onGround: Boolean
|
||||
) : OutgoingPacket()
|
|
@ -7,6 +7,7 @@ package space.uranos.net.packet.play
|
|||
|
||||
import space.uranos.net.packet.OutgoingPacket
|
||||
|
||||
// Name on wiki.vg: Entity Position
|
||||
data class EntityRelativeMovePacket(
|
||||
val entityID: Int,
|
||||
val deltaX: Short,
|
||||
|
|
|
@ -7,18 +7,22 @@ package space.uranos.net.packet.play
|
|||
|
||||
import space.uranos.net.packet.OutgoingPacket
|
||||
|
||||
// Name on wiki.vg: Entity Position and Rotation
|
||||
data class EntityRelativeMoveWithOrientationPacket(
|
||||
val entityID: Int,
|
||||
val deltaX: Short,
|
||||
val deltaY: 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,
|
||||
/**
|
||||
* Absolute value.
|
||||
*/
|
||||
val headPitch: UByte,
|
||||
val pitch: UByte,
|
||||
val onGround: Boolean
|
||||
) : OutgoingPacket()
|
||||
) : OutgoingPacket() {
|
||||
companion object {
|
||||
fun convertToDeltaShort(delta: Double): Short = (delta * 32 * 128).toInt().toShort()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
package space.uranos.net.packet.play
|
||||
|
||||
import space.uranos.Position
|
||||
import space.uranos.net.packet.OutgoingPacket
|
||||
|
||||
data class EntityTeleportPacket(
|
||||
|
@ -13,17 +12,12 @@ data class EntityTeleportPacket(
|
|||
val x: Double,
|
||||
val y: 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 headPitch: UByte,
|
||||
val pitch: UByte,
|
||||
val onGround: Boolean
|
||||
) : OutgoingPacket() {
|
||||
constructor(entityID: Int, position: Position, onGround: Boolean) : this(
|
||||
entityID,
|
||||
position.x,
|
||||
position.y,
|
||||
position.z,
|
||||
position.yawIn256Steps,
|
||||
position.headPitchIn256Steps,
|
||||
onGround
|
||||
)
|
||||
}
|
||||
) : OutgoingPacket()
|
||||
|
|
|
@ -5,13 +5,16 @@
|
|||
|
||||
package space.uranos.net.packet.play
|
||||
|
||||
import space.uranos.Position
|
||||
import space.uranos.entity.Position
|
||||
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(
|
||||
val position: Position,
|
||||
val yaw: Float,
|
||||
val pitch: Float,
|
||||
val onGround: Boolean
|
||||
) : IncomingPacket()
|
||||
|
|
|
@ -5,19 +5,23 @@
|
|||
|
||||
package space.uranos.net.packet.play
|
||||
|
||||
import space.uranos.Position
|
||||
import space.uranos.net.packet.OutgoingPacket
|
||||
import kotlin.random.Random
|
||||
|
||||
// Name on wiki.vg: Player Position and Look (clientbound)
|
||||
/**
|
||||
* Teleports the receiving player to the specified position.
|
||||
*/
|
||||
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 relativeY: Boolean = false,
|
||||
val relativeZ: Boolean = false,
|
||||
val relativeYaw: Boolean = false,
|
||||
val relativePitch: Boolean = false,
|
||||
val relativeHeadPitch: Boolean = false,
|
||||
val teleportID: Int = Random.nextInt()
|
||||
) : OutgoingPacket()
|
||||
|
|
|
@ -5,22 +5,14 @@
|
|||
|
||||
package space.uranos.net.packet.play
|
||||
|
||||
import space.uranos.Position
|
||||
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.
|
||||
*
|
||||
* @see [Position]
|
||||
*/
|
||||
data class PlayerOrientationPacket(
|
||||
/**
|
||||
* Yaw in degrees.
|
||||
*/
|
||||
val yaw: Float,
|
||||
/**
|
||||
* Pitch in degrees.
|
||||
*/
|
||||
val pitch: Float,
|
||||
val onGround: Boolean
|
||||
) : IncomingPacket()
|
||||
|
|
|
@ -5,13 +5,14 @@
|
|||
|
||||
package space.uranos.net.packet.play
|
||||
|
||||
import space.uranos.Location
|
||||
import space.uranos.entity.Position
|
||||
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.
|
||||
*/
|
||||
data class PlayerLocationPacket(
|
||||
val location: Location,
|
||||
data class PlayerPositionPacket(
|
||||
val position: Position,
|
||||
val onGround: Boolean
|
||||
) : IncomingPacket()
|
|
@ -5,20 +5,15 @@
|
|||
|
||||
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 x: Double,
|
||||
val y: Double,
|
||||
val z: Double,
|
||||
val amount: Short
|
||||
) : OutgoingPacket()
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
|
||||
package space.uranos.net.packet.play
|
||||
|
||||
import space.uranos.Position
|
||||
import space.uranos.Vector
|
||||
import space.uranos.entity.EntityType
|
||||
import space.uranos.entity.Position
|
||||
import space.uranos.net.packet.OutgoingPacket
|
||||
import java.util.UUID
|
||||
|
||||
|
@ -19,6 +19,9 @@ data class SpawnLivingEntityPacket(
|
|||
val uuid: UUID,
|
||||
val type: EntityType<*>,
|
||||
val position: Position,
|
||||
val yaw: Float,
|
||||
val pitch: Float,
|
||||
val headYaw: Float,
|
||||
/**
|
||||
* Velocity in blocks per tick
|
||||
*/
|
||||
|
|
|
@ -5,9 +5,7 @@
|
|||
|
||||
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 java.util.UUID
|
||||
|
||||
|
@ -17,8 +15,12 @@ import java.util.UUID
|
|||
data class SpawnObjectEntityPacket(
|
||||
val entityID: Int,
|
||||
val uuid: UUID,
|
||||
val type: EntityType<*>,
|
||||
val position: Position,
|
||||
val type: Int,
|
||||
val x: Double,
|
||||
val y: Double,
|
||||
val z: Double,
|
||||
val yaw: UByte,
|
||||
val pitch: UByte,
|
||||
val data: Int,
|
||||
val velocity: Vector
|
||||
) : OutgoingPacket()
|
||||
|
|
|
@ -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()
|
|
@ -10,20 +10,37 @@ import space.uranos.net.packet.OutgoingPacket
|
|||
import space.uranos.net.packet.play.SpawnLivingEntityPacket
|
||||
import space.uranos.net.packet.play.SpawnObjectEntityPacket
|
||||
import space.uranos.net.packet.play.SpawnPaintingPacket
|
||||
import space.uranos.util.numbers.mapToUByte
|
||||
|
||||
fun Entity.createSpawnPacket(): OutgoingPacket = when (this) {
|
||||
is LivingEntity -> SpawnLivingEntityPacket(
|
||||
is LivingEntity -> if (this is HasMovableHead) SpawnLivingEntityPacket(
|
||||
numericID,
|
||||
uuid,
|
||||
type,
|
||||
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
|
||||
)
|
||||
is ObjectEntity -> SpawnObjectEntityPacket(
|
||||
numericID,
|
||||
uuid,
|
||||
type,
|
||||
position,
|
||||
type.numericID,
|
||||
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(),
|
||||
velocity
|
||||
)
|
||||
|
@ -31,7 +48,7 @@ fun Entity.createSpawnPacket(): OutgoingPacket = when (this) {
|
|||
numericID,
|
||||
uuid,
|
||||
motive,
|
||||
centerLocation,
|
||||
calculateCenterLocation(),
|
||||
facing
|
||||
)
|
||||
else -> throw IllegalArgumentException("Unknown entity type")
|
||||
|
@ -39,6 +56,7 @@ fun Entity.createSpawnPacket(): OutgoingPacket = when (this) {
|
|||
|
||||
fun ObjectEntity.getDataValue(): Int = when (this) {
|
||||
is ItemEntity -> 1
|
||||
is MinecartEntity -> 2
|
||||
// TODO: Add remaining
|
||||
else -> throw IllegalArgumentException("Unknown entity type")
|
||||
}
|
||||
|
|
|
@ -11,20 +11,17 @@ import com.sksamuel.hoplite.ConfigSource
|
|||
import kotlinx.coroutines.runBlocking
|
||||
import space.uranos.config.UranosConfig
|
||||
import space.uranos.entity.*
|
||||
import space.uranos.entity.event.ViewingChangedEvent
|
||||
import space.uranos.event.EventHandlerPosition
|
||||
import space.uranos.entity.impl.*
|
||||
import space.uranos.event.UranosEventBus
|
||||
import space.uranos.event.UranosEventHandlerPositionManager
|
||||
import space.uranos.logging.Logger
|
||||
import space.uranos.logging.UranosLoggingOutputProvider
|
||||
import space.uranos.net.UranosSocketServer
|
||||
import space.uranos.net.packet.play.DestroyEntitiesPacket
|
||||
import space.uranos.net.packet.play.PlayerInfoPacket
|
||||
import space.uranos.player.UranosPlayer
|
||||
import space.uranos.plugin.UranosPluginManager
|
||||
import space.uranos.server.Server
|
||||
import space.uranos.util.EncryptionUtils
|
||||
import space.uranos.util.createSpawnPacket
|
||||
import space.uranos.util.msToTicks
|
||||
import space.uranos.util.runInServerThread
|
||||
import space.uranos.world.UranosWorldRegistry
|
||||
|
@ -90,6 +87,9 @@ class UranosServer internal constructor() : Server() {
|
|||
override fun <T : Entity> create(type: EntityType<T>): T {
|
||||
val entity: UranosEntity = when (type) {
|
||||
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")
|
||||
}
|
||||
|
||||
|
@ -159,12 +159,7 @@ class UranosServer internal constructor() : Server() {
|
|||
}
|
||||
|
||||
private fun registerListeners() {
|
||||
eventBus.on<ViewingChangedEvent>(EventHandlerPosition.LAST) { event ->
|
||||
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)))
|
||||
}
|
||||
// Nothing
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -10,11 +10,15 @@ import kotlinx.coroutines.sync.Mutex
|
|||
import kotlinx.coroutines.sync.withLock
|
||||
import space.uranos.*
|
||||
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.util.TickSynchronizationContainer
|
||||
import space.uranos.util.WatchableSet
|
||||
import space.uranos.util.createEntityMovementPacket
|
||||
import space.uranos.util.memoized
|
||||
import space.uranos.util.*
|
||||
import space.uranos.util.collections.WatchableSet
|
||||
import space.uranos.util.numbers.mapToUByte
|
||||
import space.uranos.util.numbers.validatePitch
|
||||
import space.uranos.util.numbers.validateYaw
|
||||
import space.uranos.world.Chunk
|
||||
import space.uranos.world.VoxelLocation
|
||||
import space.uranos.world.World
|
||||
|
@ -24,38 +28,16 @@ import java.util.UUID
|
|||
import java.util.WeakHashMap
|
||||
|
||||
sealed class UranosEntity(server: UranosServer) : Entity {
|
||||
abstract val chunkKey: Chunk.Key
|
||||
|
||||
override fun belongsToChunk(key: Chunk.Key): Boolean = key == chunkKey
|
||||
|
||||
override val numericID: Int = server.claimEntityID()
|
||||
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
|
||||
|
||||
private val worldMutex = Mutex()
|
||||
final override var world: World? = null; private set
|
||||
|
||||
protected val container = TickSynchronizationContainer()
|
||||
|
||||
override suspend fun setWorld(world: World?) {
|
||||
if (world == null && this is PlayerEntity)
|
||||
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() {
|
||||
container.tick()
|
||||
protected val addedViewers = mutableSetOf<Player>()
|
||||
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 class UranosLivingEntity(server: UranosServer) : UranosEntity(server), LivingEntity {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
sealed class UranosLivingEntity(server: UranosServer) : UranosEntity(server), LivingEntity {
|
||||
override var velocity: Vector = Vector.ZERO
|
||||
|
||||
private var lastSentPosition: Position = Position.ZERO
|
||||
override var position: Position by container.ifChanged(Position.ZERO) { value ->
|
||||
if (viewers.isNotEmpty()) createEntityMovementPacket(numericID, lastSentPosition, value)?.let {
|
||||
for (viewer in viewers) {
|
||||
viewer.session.send(it)
|
||||
}
|
||||
override var position: Position = Position.ZERO
|
||||
override val chunkKey: Chunk.Key by memoized({ position }) { Chunk.Key.from(position) }
|
||||
}
|
||||
|
||||
lastSentPosition = value
|
||||
}
|
||||
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 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)
|
||||
}
|
||||
|
||||
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 {
|
||||
override var velocity: Vector = Vector.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 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 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(
|
||||
|
@ -113,4 +243,76 @@ class UranosPaintingEntity(
|
|||
override val motive: PaintingMotive
|
||||
) : UranosEntity(server), PaintingEntity {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
*/
|
||||
|
||||
package space.uranos.entity
|
||||
package space.uranos.entity.impl
|
||||
|
||||
import space.uranos.UranosServer
|
||||
import space.uranos.entity.PlayerEntity
|
||||
import space.uranos.entity.UranosHasMovableHeadLivingEntity
|
||||
import space.uranos.player.Player
|
||||
import java.util.UUID
|
||||
|
||||
class UranosPlayerEntity(
|
||||
server: UranosServer,
|
||||
override val player: Player
|
||||
) : UranosLivingEntity(server), PlayerEntity {
|
||||
) : UranosHasMovableHeadLivingEntity(server), PlayerEntity {
|
||||
override val uuid: UUID = player.uuid
|
||||
}
|
|
@ -17,6 +17,7 @@ import space.uranos.net.event.SessionAfterLoginEvent
|
|||
import space.uranos.net.packet.login.*
|
||||
import space.uranos.net.packet.play.*
|
||||
import space.uranos.player.UranosPlayer
|
||||
import space.uranos.player.event.PlayerReadyEvent
|
||||
import space.uranos.tag.TagRegistry
|
||||
import space.uranos.util.AuthenticationHelper
|
||||
import space.uranos.util.EncryptionUtils
|
||||
|
@ -134,6 +135,7 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
|||
event.gameMode,
|
||||
initialWorldAndLocation.first,
|
||||
initialWorldAndLocation.second,
|
||||
event.headYaw,
|
||||
event.headPitch,
|
||||
event.invulnerable,
|
||||
event.reducedDebugInfo,
|
||||
|
@ -160,8 +162,6 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
|||
state.uuid,
|
||||
state.gameMode,
|
||||
settings,
|
||||
state.position,
|
||||
state.headPitch,
|
||||
state.reducedDebugInfo,
|
||||
state.fieldOfView,
|
||||
state.canFly,
|
||||
|
@ -182,7 +182,7 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
|||
// session.send(DeclareCommandsPacket(session.server.commandRegistry.items.values))
|
||||
// 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 {
|
||||
it.uuid to PlayerInfoPacket.Action.AddPlayer.Data(
|
||||
|
@ -209,6 +209,8 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
|||
player.spawnInitially(state.world)
|
||||
session.state = Session.State.Playing(player)
|
||||
|
||||
session.server.eventBus.emit(PlayerReadyEvent(session.player!!))
|
||||
|
||||
// WorldBorder
|
||||
session.send(CompassTargetPacket(player.compassTarget))
|
||||
}
|
||||
|
|
|
@ -10,6 +10,10 @@ import space.uranos.net.UranosSession
|
|||
|
||||
object IncomingPlayerPositionPacketHandler : PacketReceivedEventHandler<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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,6 @@ object PlayProtocolHandler : ProtocolPacketReceivedEventHandler(
|
|||
IncomingPlayerPositionPacket::class to IncomingPlayerPositionPacketHandler,
|
||||
IncomingPluginMessagePacket::class to IncomingPluginMessagePacketHandler,
|
||||
PlayerOrientationPacket::class to PlayerOrientationPacketHandler,
|
||||
PlayerLocationPacket::class to PlayerLocationPacketHandler
|
||||
PlayerPositionPacket::class to PlayerLocationPacketHandler
|
||||
)
|
||||
)
|
||||
|
|
|
@ -8,13 +8,13 @@ package space.uranos.net.packet.play
|
|||
import space.uranos.net.PacketReceivedEventHandler
|
||||
import space.uranos.net.UranosSession
|
||||
|
||||
object PlayerLocationPacketHandler : PacketReceivedEventHandler<PlayerLocationPacket>() {
|
||||
override suspend fun handle(session: UranosSession, packet: PlayerLocationPacket) {
|
||||
object PlayerLocationPacketHandler : PacketReceivedEventHandler<PlayerPositionPacket>() {
|
||||
override suspend fun handle(session: UranosSession, packet: PlayerPositionPacket) {
|
||||
val player = session.earlyPlayer ?: error("Player not yet initialized")
|
||||
player.entity.position = player.entity.position.copy(
|
||||
x = packet.location.x,
|
||||
y = packet.location.y,
|
||||
z = packet.location.z
|
||||
x = packet.position.x,
|
||||
y = packet.position.y,
|
||||
z = packet.position.z
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,9 @@ import space.uranos.net.UranosSession
|
|||
|
||||
object PlayerOrientationPacketHandler : PacketReceivedEventHandler<PlayerOrientationPacket>() {
|
||||
override suspend fun handle(session: UranosSession, packet: PlayerOrientationPacket) {
|
||||
session.earlyPlayer?.entity?.let { it.position = it.position.copy(yaw = packet.yaw, headPitch = packet.pitch) }
|
||||
?: error("Player not yet initialized")
|
||||
session.earlyPlayer?.entity?.let {
|
||||
it.headYaw = packet.yaw
|
||||
it.headPitch = packet.pitch
|
||||
} ?: error("Player not initialized yet")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
package space.uranos.player
|
||||
|
||||
import space.uranos.Position
|
||||
import space.uranos.chat.TextComponent
|
||||
import space.uranos.entity.PlayerEntity
|
||||
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.SelectedHotbarSlotPacket
|
||||
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.VoxelLocation
|
||||
import space.uranos.world.World
|
||||
|
@ -28,8 +27,6 @@ class UranosPlayer(
|
|||
override val uuid: UUID,
|
||||
override var gameMode: GameMode,
|
||||
override var settings: Player.Settings,
|
||||
position: Position,
|
||||
headPitch: Float,
|
||||
override var reducedDebugInfo: Boolean,
|
||||
override var fieldOfView: Float,
|
||||
override var canFly: Boolean,
|
||||
|
@ -43,7 +40,7 @@ class UranosPlayer(
|
|||
|
||||
override var selectedHotbarSlot by container.ifChanged(
|
||||
selectedHotbarSlot,
|
||||
{ clampArgument("selectedHotbarSlot", 0..8, it) }) {
|
||||
{ validateParameterIsInRange("selectedHotbarSlot", 0..8, it) }) {
|
||||
session.sendNow(SelectedHotbarSlotPacket(it))
|
||||
}
|
||||
|
||||
|
@ -60,12 +57,10 @@ class UranosPlayer(
|
|||
|
||||
override var currentlyViewedChunks = emptyList<Chunk>()
|
||||
|
||||
override val entity: PlayerEntity = session.server.createPlayerEntity(this).also {
|
||||
it.position = position
|
||||
}
|
||||
override val entity: PlayerEntity = session.server.createPlayerEntity(this)
|
||||
|
||||
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)
|
||||
updateCurrentlyViewedChunks()
|
||||
sendChunksAndLight()
|
||||
|
|
Reference in a new issue