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">
|
<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>
|
2
LICENSE
2
LICENSE
|
@ -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
|
||||||
|
|
18
README.md
18
README.md
|
@ -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 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
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
package space.uranos.entity
|
||||||
|
|
||||||
interface LivingEntity : Entity, Mobile
|
interface LivingEntity : Entity, Mobile {
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
}
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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 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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 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() {
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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")
|
||||||
|
}
|
|
@ -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")
|
|
@ -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
|
* 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)
|
|
@ -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.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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@ object PlayProtocol : Protocol(
|
||||||
DeclareRecipesPacketCodec,
|
DeclareRecipesPacketCodec,
|
||||||
DestroyEntitiesPacketCodec,
|
DestroyEntitiesPacketCodec,
|
||||||
DisconnectPacketCodec,
|
DisconnectPacketCodec,
|
||||||
EntityHeadPitchPacketCodec,
|
|
||||||
EntityHeadYawPacketCodec,
|
EntityHeadYawPacketCodec,
|
||||||
|
EntityOrientationPacketCodec,
|
||||||
EntityRelativeMovePacketCodec,
|
EntityRelativeMovePacketCodec,
|
||||||
EntityRelativeMoveWithOrientationPacketCodec,
|
EntityRelativeMoveWithOrientationPacketCodec,
|
||||||
EntityTeleportPacketCodec,
|
EntityTeleportPacketCodec,
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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.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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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) }
|
||||||
|
}
|
||||||
|
|
||||||
private var lastSentPosition: Position = Position.ZERO
|
abstract class UranosNotHasMovableHeadLivingEntity(server: UranosServer) : UranosLivingEntity(server) {
|
||||||
override var position: Position by container.ifChanged(Position.ZERO) { value ->
|
override var velocity: Vector = Vector.ZERO
|
||||||
if (viewers.isNotEmpty()) createEntityMovementPacket(numericID, lastSentPosition, value)?.let {
|
override var position: Position = Position.ZERO
|
||||||
for (viewer in viewers) {
|
|
||||||
viewer.session.send(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastSentPosition = value
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* 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
|
||||||
}
|
}
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Reference in a new issue