From 8149fcb48d2be15e28e11cbf884f886261baece4 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Wed, 20 Mar 2024 20:18:41 +0700 Subject: [PATCH] More work towards implementing legacy protocol, new entity class tree --- gradle.properties | 2 +- .../kotlin/ru/dbotthepony/kstarbound/Main.kt | 16 --- .../ru/dbotthepony/kstarbound/Starbound.kt | 2 +- .../network/packets/ForgetEntityPacket.kt | 2 +- .../network/packets/SpawnWorldObjectPacket.kt | 2 +- .../ru/dbotthepony/kstarbound/defs/Damage.kt | 74 ++++++++++ .../dbotthepony/kstarbound/defs/DamageType.kt | 23 --- .../dbotthepony/kstarbound/defs/EntityType.kt | 10 +- .../kstarbound/defs/PerlinNoiseParameters.kt | 6 +- .../kstarbound/defs/PlayerWarping.kt | 2 +- .../dbotthepony/kstarbound/defs/TeamType.kt | 22 --- .../kstarbound/defs/TouchDamage.kt | 16 --- .../kstarbound/defs/actor/HumanoidData.kt | 120 ++++++++++++++++ .../kstarbound/defs/actor/Personality.kt | 20 +++ .../kstarbound/defs/actor/Types.kt | 24 ++++ .../kstarbound/defs/item/ItemRarity.kt | 8 +- .../dbotthepony/kstarbound/defs/world/Sky.kt | 10 +- .../defs/world/VisitableWorldParameters.kt | 46 ++---- .../kstarbound/defs/world/WorldStructure.kt | 4 +- .../defs/world/terrain/BiomeDefinition.kt | 12 +- .../ru/dbotthepony/kstarbound/io/Streams.kt | 4 + .../kstarbound/json/builder/EnumAdapter.kt | 9 +- .../json/factory/RGBAColorTypeAdapter.kt | 2 +- .../dbotthepony/kstarbound/network/JsonRPC.kt | 3 +- .../kstarbound/network/packets/PingPong.kt | 1 + .../network/syncher/EventCounterElement.kt | 1 + .../kstarbound/network/syncher/Factories.kt | 64 +++++++-- .../syncher/FloatingNetworkedElement.kt | 4 +- .../network/syncher/GroupElement.kt | 4 +- .../kstarbound/server/ServerConnection.kt | 6 +- .../dbotthepony/kstarbound/world/Direction.kt | 27 +++- .../ru/dbotthepony/kstarbound/world/World.kt | 3 + .../world/entities/AbstractEntity.kt | 26 +++- .../kstarbound/world/entities/ActorEntity.kt | 8 ++ ...ntroller.kt => ActorMovementController.kt} | 133 ++++++++++-------- .../world/entities/DynamicEntity.kt | 41 +++--- .../entities/EntityActorMovementController.kt | 67 --------- .../entities/EntityMovementController.kt | 25 ---- .../world/entities/HumanoidActorEntity.kt | 10 ++ .../kstarbound/world/entities/ItemEntity.kt | 21 --- ...entController.kt => MovementController.kt} | 110 +++++++++++---- .../world/entities/MovingCollisionID.kt | 21 +++ .../world/entities/PathController.kt | 4 +- .../kstarbound/world/entities/PlayerEntity.kt | 94 ++++++++----- .../kstarbound/world/entities/TileEntity.kt | 8 +- .../kstarbound/world/entities/WorldObject.kt | 11 +- .../kstarbound/world/physics/Poly.kt | 66 +++++++-- 47 files changed, 729 insertions(+), 465 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/defs/Damage.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/defs/DamageType.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/defs/TeamType.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/defs/TouchDamage.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/HumanoidData.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/Personality.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/Types.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ActorEntity.kt rename src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/{AbstractActorMovementController.kt => ActorMovementController.kt} (81%) delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/EntityActorMovementController.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/EntityMovementController.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/HumanoidActorEntity.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ItemEntity.kt rename src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/{AbstractMovementController.kt => MovementController.kt} (83%) create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MovingCollisionID.kt diff --git a/gradle.properties b/gradle.properties index 5fa37229..b56f2bd8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m kotlinVersion=1.9.10 kotlinCoroutinesVersion=1.8.0 -kommonsVersion=2.9.23 +kommonsVersion=2.9.25 ffiVersion=2.2.13 lwjglVersion=3.3.0 diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 2b8df664..5525c62c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -14,14 +14,11 @@ import ru.dbotthepony.kstarbound.server.world.LegacyWorldStorage import ru.dbotthepony.kstarbound.server.world.ServerUniverse import ru.dbotthepony.kstarbound.server.world.ServerWorld import ru.dbotthepony.kstarbound.world.WorldGeometry -import ru.dbotthepony.kstarbound.world.entities.ItemEntity import java.io.BufferedInputStream import java.io.ByteArrayInputStream import java.io.DataInputStream import java.io.File import java.net.InetSocketAddress -import java.util.* -import java.util.concurrent.TimeUnit import java.util.zip.Inflater import java.util.zip.InflaterInputStream @@ -116,19 +113,6 @@ fun main() { //client.world!!.parallax = Starbound.parallaxAccess["garden"] - val rand = Random() - - for (i in 0 until 0) { - val item = ItemEntity(Registries.items.keys.values.random().value) - - item.position = Vector2d(225.0 - i, 785.0) - item.joinWorld(world) - item.movement.velocity = Vector2d(rand.nextDouble() * 32.0 - 16.0, rand.nextDouble() * 32.0 - 16.0) - - item.mailbox.scheduleAtFixedRate({ item.movement.velocity += Vector2d(rand.nextDouble() * 32.0 - 16.0, rand.nextDouble() * 32.0 - 16.0) }, 1000 + rand.nextLong(-100, 100), 1000 + rand.nextLong(-100, 100), TimeUnit.MILLISECONDS) - //item.movement.applyVelocity(Vector2d(rand.nextDouble() * 1000.0 - 500.0, rand.nextDouble() * 1000.0 - 500.0)) - } - client.connectToLocalServer(server.channels.createLocalChannel()) //client.connectToRemoteServer(InetSocketAddress("127.0.0.1", 21025)) //client2.connectToLocalServer(server.channels.createLocalChannel(), UUID.randomUUID()) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index c030d288..1c912eff 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -233,7 +233,7 @@ object Starbound : ISBFileLocator { registerTypeAdapterFactory(ThingDescription.Factory(STRINGS)) registerTypeAdapterFactory(TerrainSelectorType.Companion) - registerTypeAdapter(EnumAdapter(DamageType::class, default = DamageType.NORMAL)) + registerTypeAdapter(EnumAdapter(DamageType::class, default = DamageType.DAMAGE)) registerTypeAdapter(InventoryIcon.Companion) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ForgetEntityPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ForgetEntityPacket.kt index f05d9d73..3d3800ef 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ForgetEntityPacket.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ForgetEntityPacket.kt @@ -19,7 +19,7 @@ class ForgetEntityPacket(val uuid: UUID) : IClientPacket { val world = connection.client.world ?: return world.mailbox.execute { - world.entities.firstOrNull { it.uuid == uuid }?.remove() + world.entities.firstOrNull { it.entityID == 0 }?.remove() } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/SpawnWorldObjectPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/SpawnWorldObjectPacket.kt index e37be13f..74f5bec0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/SpawnWorldObjectPacket.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/SpawnWorldObjectPacket.kt @@ -24,7 +24,7 @@ class SpawnWorldObjectPacket(val uuid: UUID, val data: JsonObject) : IClientPack connection.client.mailbox.execute { val world = connection.client.world ?: return@execute val obj = WorldObject.fromJson(data) - obj.uuid = uuid + //obj.uuid = uuid obj.joinWorld(world) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Damage.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Damage.kt new file mode 100644 index 00000000..dfb8b379 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/Damage.kt @@ -0,0 +1,74 @@ +package ru.dbotthepony.kstarbound.defs + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableSet +import ru.dbotthepony.kommons.vector.Vector2d +import ru.dbotthepony.kstarbound.json.builder.IStringSerializable +import ru.dbotthepony.kstarbound.json.builder.JsonFactory +import ru.dbotthepony.kstarbound.network.syncher.legacyCodec +import ru.dbotthepony.kstarbound.network.syncher.nativeCodec +import java.io.DataInputStream +import java.io.DataOutputStream + +enum class TeamType(override val jsonName: String) : IStringSerializable { + NULL("null"), + // non-PvP-enabled players and player allied NPCs + FRIENDLY("friendly"), + // hostile and neutral NPCs and monsters + ENEMY("enemy"), + // PvP-enabled players + PVP("pvp"), + // cannot damage anything, can be damaged by Friendly/PVP/Assistant + PASSIVE("passive"), + // cannot damage or be damaged + GHOSTLY("ghostly"), + // cannot damage enemies, can be damaged by anything except enemy + ENVIRONMENT("environment"), + // damages anything except ghostly, damaged by anything except ghostly/passive + // used for self damage + INDISCRIMINATE("indiscriminate"), + // cannot damage friendlies and cannot be damaged by anything + ASSISTANT("assistant"); +} + +enum class HitType(override val jsonName: String) : IStringSerializable { + HIT("Hit"), + STRONG_HIT("StrongHit"), + WEAK_HIT("WeakHit"), + SHIELD_HIT("ShieldHit"), + KILL("Kill"); +} + +enum class DamageType(override val jsonName: String) : IStringSerializable { + NO_DAMAGE("NoDamage"), + DAMAGE("Damage"), + IGNORE_DEFENCE("IgnoresDef"), + KNOCKBACK("IgnoresDef"), + ENVIRONMENT("Environment"), + STATUS("Environment"); +} + +@JsonFactory +data class EntityDamageTeam(val type: TeamType = TeamType.NULL, val team: Int = 0) { + constructor(stream: DataInputStream, isLegacy: Boolean) : this(TeamType.entries[stream.readUnsignedByte()], stream.readUnsignedShort()) + + fun write(stream: DataOutputStream, isLegacy: Boolean) { + stream.writeByte(type.ordinal) + stream.writeShort(team) + } + + companion object { + val CODEC = nativeCodec(::EntityDamageTeam, EntityDamageTeam::write) + val LEGACY_CODEC = legacyCodec(::EntityDamageTeam, EntityDamageTeam::write) + } +} + +@JsonFactory +data class TouchDamage( + val poly: ImmutableList = ImmutableList.of(), + val teamType: TeamType = TeamType.ENVIRONMENT, + val damage: Double = 0.0, + val damageSourceKind: String = "", + val knockback: Double = 0.0, + val statusEffects: ImmutableSet = ImmutableSet.of(), +) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/DamageType.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/DamageType.kt deleted file mode 100644 index 2db8c2ae..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/DamageType.kt +++ /dev/null @@ -1,23 +0,0 @@ -package ru.dbotthepony.kstarbound.defs - -import com.google.gson.stream.JsonWriter -import ru.dbotthepony.kstarbound.json.builder.IStringSerializable - -enum class DamageType(private vararg val aliases: String) : IStringSerializable { - NORMAL, - IGNORE_DEFENCE("IGNORESDEF", "IGNOREDEF"), - STATUS, - NO_DAMAGE("NODAMAGE"); - - override fun match(name: String): Boolean { - for (alias in aliases) - if (name == alias) - return true - - return name == this.name - } - - override fun write(out: JsonWriter) { - out.value(this.name) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/EntityType.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/EntityType.kt index 44d6ab78..5119d8e6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/EntityType.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/EntityType.kt @@ -3,7 +3,7 @@ package ru.dbotthepony.kstarbound.defs import com.google.gson.stream.JsonWriter import ru.dbotthepony.kstarbound.json.builder.IStringSerializable -enum class EntityType(val jsonName: String) : IStringSerializable { +enum class EntityType(override val jsonName: String) : IStringSerializable { PLANT("PlantEntity"), OBJECT("ObjectEntity"), VEHICLE("VehicleEntity"), @@ -14,12 +14,4 @@ enum class EntityType(val jsonName: String) : IStringSerializable { MONSTER("MonsterEntity"), NPC("NpcEntity"), PLAYER("PlayerEntity"); - - override fun match(name: String): Boolean { - return name == jsonName - } - - override fun write(out: JsonWriter) { - out.name(jsonName) - } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/PerlinNoiseParameters.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/PerlinNoiseParameters.kt index 04e123db..bd7090e7 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/PerlinNoiseParameters.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/PerlinNoiseParameters.kt @@ -22,7 +22,7 @@ data class PerlinNoiseParameters( require(scale >= 16) { "Too little perlin noise scale" } } - enum class Type(val jsonName: String) : IStringSerializable { + enum class Type(override val jsonName: String) : IStringSerializable { PERLIN("perlin"), BILLOW("billow"), RIDGED_MULTI("ridgedmulti"); @@ -30,10 +30,6 @@ data class PerlinNoiseParameters( override fun match(name: String): Boolean { return name.lowercase() == jsonName } - - override fun write(out: JsonWriter) { - out.value(jsonName) - } } companion object { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/PlayerWarping.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/PlayerWarping.kt index 913a06bd..8095d51a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/PlayerWarping.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/PlayerWarping.kt @@ -35,7 +35,7 @@ sealed class AbstractWarpTarget { sealed class WarpAlias(val index: Int) : AbstractWarpTarget() { override fun write(stream: DataOutputStream, isLegacy: Boolean) { stream.write(3) - // because it is defined as enum class WarpAlias, which defaults to int32_t for some reason. + // because it is defined as enum class WarpAlias, without specifying uint8_t as type stream.writeInt(index) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/TeamType.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/TeamType.kt deleted file mode 100644 index 0388afab..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/TeamType.kt +++ /dev/null @@ -1,22 +0,0 @@ -package ru.dbotthepony.kstarbound.defs - -enum class TeamType { - NULL, - // non-PvP-enabled players and player allied NPCs - FRIENDLY, - // hostile and neutral NPCs and monsters - ENEMY, - // PvP-enabled players - PVP, - // cannot damage anything, can be damaged by Friendly/PVP/Assistant - PASSIVE, - // cannot damage or be damaged - GHOSTLY, - // cannot damage enemies, can be damaged by anything except enemy - ENVIRONMENT, - // damages anything except ghostly, damaged by anything except ghostly/passive - // used for self damage - INDISCRIMINATE, - // cannot damage friendlies and cannot be damaged by anything - ASSISTANT -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/TouchDamage.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/TouchDamage.kt deleted file mode 100644 index 59db0908..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/TouchDamage.kt +++ /dev/null @@ -1,16 +0,0 @@ -package ru.dbotthepony.kstarbound.defs - -import com.google.common.collect.ImmutableList -import com.google.common.collect.ImmutableSet -import ru.dbotthepony.kommons.vector.Vector2d -import ru.dbotthepony.kstarbound.json.builder.JsonFactory - -@JsonFactory -data class TouchDamage( - val poly: ImmutableList = ImmutableList.of(), - val teamType: TeamType = TeamType.ENVIRONMENT, - val damage: Double = 0.0, - val damageSourceKind: String = "", - val knockback: Double = 0.0, - val statusEffects: ImmutableSet = ImmutableSet.of(), -) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/HumanoidData.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/HumanoidData.kt new file mode 100644 index 00000000..c07041bb --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/HumanoidData.kt @@ -0,0 +1,120 @@ +package ru.dbotthepony.kstarbound.defs.actor + +import ru.dbotthepony.kommons.io.readVector2d +import ru.dbotthepony.kommons.io.readVector2f +import ru.dbotthepony.kommons.io.writeBinaryString +import ru.dbotthepony.kommons.io.writeStruct2d +import ru.dbotthepony.kommons.io.writeStruct2f +import ru.dbotthepony.kommons.math.RGBAColor +import ru.dbotthepony.kommons.vector.Vector2d +import ru.dbotthepony.kstarbound.io.readColor +import ru.dbotthepony.kstarbound.io.readInternedString +import ru.dbotthepony.kstarbound.io.writeColor +import ru.dbotthepony.kstarbound.json.builder.JsonFactory +import ru.dbotthepony.kstarbound.network.syncher.legacyCodec +import ru.dbotthepony.kstarbound.network.syncher.nativeCodec +import java.io.DataInputStream +import java.io.DataOutputStream + +@JsonFactory +data class HumanoidData( + val name: String = "Humanoid", + val species: String = "human", + val gender: Gender = Gender.MALE, + + val hairGroup: String = "hair", + // Must have :normal and :climb + val hairType: String = "male1", + val hairDirectives: String = "", + val bodyDirectives: String = "", + val emoteDirectives: String = bodyDirectives, + val facialHairGroup: String = "", + val facialHairType: String = "", + val facialHairDirectives: String = "", + val facialMaskGroup: String = "", + val facialMaskType: String = "", + val facialMaskDirectives: String = "", + + val color: RGBAColor = RGBAColor.BLACK, + + val personalityIdle: String = "", + val personalityArmIdle: String = "", + val personalityHeadOffset: Vector2d = Vector2d.ZERO, + val personalityArmOffset: Vector2d = Vector2d.ZERO, + + val imagePath: String? = null, +) : IPersonality { + override val idle: String + get() = personalityIdle + override val armIdle: String + get() = personalityArmIdle + override val handOffset: Vector2d + get() = personalityHeadOffset + override val armOffset: Vector2d + get() = personalityArmOffset + + fun write(stream: DataOutputStream, isLegacy: Boolean) { + stream.writeBinaryString(name) + stream.writeBinaryString(species) + stream.writeByte(gender.ordinal) + stream.writeBinaryString(hairGroup) + stream.writeBinaryString(hairType) + stream.writeBinaryString(hairDirectives) + stream.writeBinaryString(bodyDirectives) + stream.writeBinaryString(emoteDirectives) + stream.writeBinaryString(facialHairGroup) + stream.writeBinaryString(facialHairType) + stream.writeBinaryString(facialHairDirectives) + stream.writeBinaryString(facialMaskGroup) + stream.writeBinaryString(facialMaskType) + stream.writeBinaryString(facialMaskDirectives) + stream.writeBinaryString(personalityIdle) + stream.writeBinaryString(personalityArmIdle) + if (isLegacy) stream.writeStruct2f(personalityHeadOffset.toFloatVector()) else stream.writeStruct2d(personalityHeadOffset) + if (isLegacy) stream.writeStruct2f(personalityArmOffset.toFloatVector()) else stream.writeStruct2d(personalityArmOffset) + stream.writeColor(color) + + stream.writeBoolean(imagePath != null) + + if (imagePath != null) + stream.writeBinaryString(imagePath) + } + + companion object { + val CODEC = nativeCodec(::read, HumanoidData::write) + val LEGACY_CODEC = legacyCodec(::read, HumanoidData::write) + + fun read(stream: DataInputStream, isLegacy: Boolean): HumanoidData { + val name: String = stream.readInternedString() + val species: String = stream.readInternedString() + val gender: Gender = Gender.entries[stream.readUnsignedByte()] + + val hairGroup = stream.readInternedString() + val hairType = stream.readInternedString() + val hairDirectives = stream.readInternedString() + val bodyDirectives = stream.readInternedString() + val emoteDirectives = stream.readInternedString() + val facialHairGroup = stream.readInternedString() + val facialHairType = stream.readInternedString() + val facialHairDirectives = stream.readInternedString() + val facialMaskGroup = stream.readInternedString() + val facialMaskType = stream.readInternedString() + val facialMaskDirectives = stream.readInternedString() + + val color: RGBAColor = stream.readColor() + + val personalityIdle: String = stream.readInternedString() + val personalityArmIdle: String = stream.readInternedString() + val personalityHeadOffset: Vector2d = if (isLegacy) stream.readVector2f().toDoubleVector() else stream.readVector2d() + val personalityArmOffset: Vector2d = if (isLegacy) stream.readVector2f().toDoubleVector() else stream.readVector2d() + + val imagePath: String? = if (stream.readBoolean()) stream.readInternedString() else null + + return HumanoidData( + name, species, gender, hairGroup, hairType, hairDirectives, bodyDirectives, + emoteDirectives, facialHairGroup, facialHairType, facialHairDirectives, + facialMaskGroup, facialMaskType, facialMaskDirectives, color, personalityIdle, + personalityArmIdle, personalityHeadOffset, personalityArmOffset, imagePath) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/Personality.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/Personality.kt new file mode 100644 index 00000000..38e0faee --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/Personality.kt @@ -0,0 +1,20 @@ +package ru.dbotthepony.kstarbound.defs.actor + +import ru.dbotthepony.kommons.vector.Vector2d +import ru.dbotthepony.kstarbound.json.builder.JsonFactory + +// this is because personality data structure is used inconsistently across original source code +interface IPersonality { + val idle: String + val armIdle: String + val handOffset: Vector2d + val armOffset: Vector2d +} + +@JsonFactory(asList = true) +data class Personality( + override val idle: String, + override val armIdle: String, + override val handOffset: Vector2d, + override val armOffset: Vector2d +) : IPersonality diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/Types.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/Types.kt new file mode 100644 index 00000000..7d1285e2 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/Types.kt @@ -0,0 +1,24 @@ +package ru.dbotthepony.kstarbound.defs.actor + +import ru.dbotthepony.kstarbound.json.builder.IStringSerializable + +enum class Gender(override val jsonName: String) : IStringSerializable { + MALE("Male"), FEMALE("Female"); +} + +enum class HumanoidEmote(override val jsonName: String) : IStringSerializable { + IDLE("Idle"), + BLABBERING("Blabbering"), + SHOUTING("Shouting"), + HAPPY("Happy"), + SAD("Sad"), + NEUTRAL("NEUTRAL"), + LAUGH("Laugh"), + ANNOYED("Annoyed"), + OH("Oh"), + OOOH("OOOH"), + BLINK("Blink"), + WINK("Wink"), + EAT("Eat"), + SLEEP("Sleep"); +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemRarity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemRarity.kt index 9a3fa702..39f5c1a9 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemRarity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemRarity.kt @@ -3,7 +3,7 @@ package ru.dbotthepony.kstarbound.defs.item import com.google.gson.stream.JsonWriter import ru.dbotthepony.kstarbound.json.builder.IStringSerializable -enum class ItemRarity(val canonical: String) : IStringSerializable { +enum class ItemRarity(override val jsonName: String) : IStringSerializable { COMMON("Common"), UNCOMMON("Uncommon"), RARE("Rare"), @@ -11,10 +11,6 @@ enum class ItemRarity(val canonical: String) : IStringSerializable { ESSENTIAL("Essential"); override fun match(name: String): Boolean { - return name == this.canonical || name.lowercase() == this.name.lowercase() - } - - override fun write(out: JsonWriter) { - out.value(canonical) + return name == this.jsonName || name.lowercase() == this.name.lowercase() } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/Sky.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/Sky.kt index 4c88d0ae..96a226f0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/Sky.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/Sky.kt @@ -47,19 +47,11 @@ enum class WarpPhase(val stupidassbitch: Int) { SPEEDING_UP(1) } -enum class SkyOrbiterType(val sname: String) : IStringSerializable { +enum class SkyOrbiterType(override val jsonName: String) : IStringSerializable { SUN("sun"), MOON("moon"), HORIZON_CLOUD("horizoncloud"), SPACE_DEBRIS("scapedebris"); - - override fun match(name: String): Boolean { - return name.lowercase() == sname - } - - override fun write(out: JsonWriter) { - out.value(sname) - } } @JsonFactory diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/VisitableWorldParameters.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/VisitableWorldParameters.kt index 5dfb9a71..4f59e0e5 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/VisitableWorldParameters.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/VisitableWorldParameters.kt @@ -20,49 +20,25 @@ import ru.dbotthepony.kstarbound.json.builder.IStringSerializable import ru.dbotthepony.kstarbound.json.builder.JsonFactory import kotlin.properties.Delegates -enum class BeamUpRule(val jsonName: String) : IStringSerializable { - NOWHERE("nowhere"), - SURFACE("surface"), - ANYWHERE("anywhere"), - ANYWHERE_WITH_WARNING("anywherewithwarning"); - - override fun match(name: String): Boolean { - return name.lowercase() == jsonName - } - - override fun write(out: JsonWriter) { - out.value(jsonName) - } +enum class BeamUpRule(override val jsonName: String) : IStringSerializable { + NOWHERE("Nowhere"), + SURFACE("Surface"), + ANYWHERE("Anywhere"), + ANYWHERE_WITH_WARNING("AnywhereWithWarning"); } -enum class WorldEdgeForceRegion(val sname: String) : IStringSerializable { - NONE("none"), - TOP("top"), - BOTTOM("bottom"), - TOP_AND_BOTTOM("topandbottom"); - - override fun match(name: String): Boolean { - return name.lowercase() == sname - } - - override fun write(out: JsonWriter) { - out.value(sname) - } +enum class WorldEdgeForceRegion(override val jsonName: String) : IStringSerializable { + NONE("None"), + TOP("Top"), + BOTTOM("Bottom"), + TOP_AND_BOTTOM("TopAndBottom"); } -enum class VisitableWorldParametersType(val jsonName: String, val token: TypeToken) : IStringSerializable { +enum class VisitableWorldParametersType(override val jsonName: String, val token: TypeToken) : IStringSerializable { TERRESTRIAL("TerrestrialWorldParameters", TypeToken.get(TerrestrialWorldParameters::class.java)), ASTEROIDS("AsteroidsWorldParameters", TypeToken.get(AsteroidsWorldParameters::class.java)), FLOATING_DUNGEON("FloatingDungeonWorldParameters", TypeToken.get(FloatingDungeonWorldParameters::class.java)); - override fun match(name: String): Boolean { - return name == jsonName - } - - override fun write(out: JsonWriter) { - out.value(jsonName) - } - companion object : TypeAdapterFactory { val ADAPTER = DispatchingAdapter("type", { type }, { token }, entries) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/WorldStructure.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/WorldStructure.kt index bb9f2afa..b87ac0a4 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/WorldStructure.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/WorldStructure.kt @@ -9,7 +9,7 @@ import ru.dbotthepony.kommons.util.Either import ru.dbotthepony.kommons.vector.Vector2d import ru.dbotthepony.kommons.vector.Vector2i import ru.dbotthepony.kstarbound.json.builder.JsonFactory -import ru.dbotthepony.kstarbound.world.Direction +import ru.dbotthepony.kstarbound.world.Direction1D @JsonFactory data class WorldStructure( @@ -33,7 +33,7 @@ data class WorldStructure( data class Obj( val position: Vector2i, val name: String, - val direction: Direction, + val direction: Direction1D, val parameters: JsonElement, val residual: Boolean = false, ) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/terrain/BiomeDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/terrain/BiomeDefinition.kt index f4c51679..13da7f35 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/terrain/BiomeDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/world/terrain/BiomeDefinition.kt @@ -36,7 +36,7 @@ import java.util.random.RandomGenerator import java.util.stream.IntStream enum class BiomePlacementDistributionType( - val jsonName: String, + override val jsonName: String, val def: TypeToken, val data: TypeToken, ) : IStringSerializable { @@ -47,10 +47,6 @@ enum class BiomePlacementDistributionType( return name.lowercase() == jsonName } - override fun write(out: JsonWriter) { - out.value(jsonName) - } - companion object { val DEFINITION_ADAPTER = DispatchingAdapter("type", { type }, { def }, entries) val DATA_ADAPTER = DispatchingAdapter("type", { type }, { data }, entries) @@ -58,7 +54,7 @@ enum class BiomePlacementDistributionType( } enum class BiomePlacementItemType( - val jsonName: String, + override val jsonName: String, val def: TypeToken, val data: TypeToken, ) : IStringSerializable { @@ -74,10 +70,6 @@ enum class BiomePlacementItemType( return name.lowercase() == jsonName } - override fun write(out: JsonWriter) { - out.value(jsonName) - } - companion object { val DEFINITION_ADAPTER = DispatchingAdapter("type", { type }, { def }, entries) val DATA_ADAPTER = DispatchingAdapter("type", { type }, { data }, entries) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/Streams.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/Streams.kt index 7ac855af..3dea7210 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/Streams.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/Streams.kt @@ -1,6 +1,7 @@ package ru.dbotthepony.kstarbound.io import it.unimi.dsi.fastutil.bytes.ByteArrayList +import ru.dbotthepony.kommons.io.readBinaryString import ru.dbotthepony.kommons.util.IStruct2d import ru.dbotthepony.kommons.util.IStruct2i import ru.dbotthepony.kommons.io.readDouble @@ -14,6 +15,7 @@ import ru.dbotthepony.kommons.io.writeSignedVarInt import ru.dbotthepony.kommons.math.RGBAColor import ru.dbotthepony.kommons.vector.Vector2d import ru.dbotthepony.kommons.vector.Vector2i +import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.world.ChunkPos import java.io.DataInput import java.io.DataOutput @@ -54,3 +56,5 @@ fun OutputStream.writeColor(color: RGBAColor) { fun InputStream.readColor(): RGBAColor { return RGBAColor(readFloat(), readFloat(), readFloat(), readFloat()) } + +fun InputStream.readInternedString(): String = Starbound.STRINGS.intern(readBinaryString()) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/json/builder/EnumAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/json/builder/EnumAdapter.kt index 4ed7da62..20a2b997 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/json/builder/EnumAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/json/builder/EnumAdapter.kt @@ -22,8 +22,11 @@ import kotlin.reflect.KClass import kotlin.reflect.full.isSuperclassOf interface IStringSerializable { - fun match(name: String): Boolean - fun write(out: JsonWriter) + val jsonName: String + + fun match(name: String): Boolean { + return name == jsonName + } } inline fun > EnumAdapter(values: Stream = Arrays.stream(T::class.java.enumConstants), default: T? = null): EnumAdapter { @@ -85,7 +88,7 @@ class EnumAdapter>(private val enum: KClass, values: Stream = if (value == null) { out.nullValue() } else if (value is IStringSerializable) { - value.write(out) + out.value(value.jsonName) } else { out.value(value.name) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/json/factory/RGBAColorTypeAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/json/factory/RGBAColorTypeAdapter.kt index 5c9592a6..4a02a8e1 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/json/factory/RGBAColorTypeAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/json/factory/RGBAColorTypeAdapter.kt @@ -19,7 +19,7 @@ object RGBAColorTypeAdapter : TypeAdapter() { out.value(value.redInt) out.value(value.greenInt) out.value(value.blueInt) - if (value.alphaInt != 255) out.value(value.alphaInt) + out.value(value.alphaInt) out.endArray() } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/JsonRPC.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/JsonRPC.kt index 95df22a4..93b32141 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/JsonRPC.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/JsonRPC.kt @@ -14,6 +14,7 @@ import ru.dbotthepony.kommons.io.readKOptional import ru.dbotthepony.kommons.io.writeBinaryString import ru.dbotthepony.kommons.io.writeKOptional import ru.dbotthepony.kommons.util.KOptional +import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.json.readJsonElement import ru.dbotthepony.kstarbound.json.writeJsonElement import java.io.DataInputStream @@ -48,7 +49,7 @@ class JsonRPC { companion object { fun native(stream: DataInputStream): Entry { - return Entry(Command.entries[stream.read()], stream.readInt(), stream.readKOptional { readBinaryString() }, stream.readKOptional { readJsonElement() }) + return Entry(Command.entries[stream.read()], stream.readInt(), stream.readKOptional { Starbound.STRINGS.intern(readBinaryString()) }, stream.readKOptional { readJsonElement() }) } fun legacy(stream: DataInputStream): Entry { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/PingPong.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/PingPong.kt index ee6b60e6..da8092a9 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/PingPong.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/PingPong.kt @@ -28,6 +28,7 @@ object PingPacket : IServerPacket { } override fun play(connection: ServerConnection) { + // immediately respond to ping packets connection.sendAndFlush(PongPacket) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/EventCounterElement.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/EventCounterElement.kt index a8ecb6a4..2cc349ff 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/EventCounterElement.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/EventCounterElement.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.kstarbound.network.syncher +import ru.dbotthepony.kommons.io.UnsignedVarLongCodec import java.io.DataInputStream import java.util.function.Consumer diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/Factories.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/Factories.kt index 1dfa3b4c..26b3654b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/Factories.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/Factories.kt @@ -1,8 +1,11 @@ package ru.dbotthepony.kstarbound.network.syncher import com.google.gson.TypeAdapter +import ru.dbotthepony.kommons.io.BinaryStringCodec import ru.dbotthepony.kommons.io.BooleanValueCodec import ru.dbotthepony.kommons.io.StreamCodec +import ru.dbotthepony.kommons.io.UnsignedVarIntCodec +import ru.dbotthepony.kommons.io.UnsignedVarLongCodec import ru.dbotthepony.kommons.io.VarIntValueCodec import ru.dbotthepony.kommons.io.VarLongValueCodec import ru.dbotthepony.kommons.io.Vector2dCodec @@ -16,22 +19,49 @@ import ru.dbotthepony.kommons.io.writeVarLong import ru.dbotthepony.kommons.vector.Vector2d import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.defs.world.SkyType +import ru.dbotthepony.kstarbound.json.builder.IStringSerializable +import ru.dbotthepony.kstarbound.world.physics.Poly import java.io.DataInputStream import java.io.DataOutputStream +import java.io.InputStream +import java.io.OutputStream fun BasicNetworkedElement(value: TYPE, codec: StreamCodec): BasicNetworkedElement { return BasicNetworkedElement(value, codec, codec, { it }, { it }) } -val UnsignedVarLongCodec = StreamCodec.Impl(DataInputStream::readVarLong, DataOutputStream::writeVarLong) -val UnsignedVarIntCodec = StreamCodec.Impl(DataInputStream::readVarInt, DataOutputStream::writeVarInt) - val ByteArrayCodec = StreamCodec.Impl(DataInputStream::readByteArray, DataOutputStream::writeByteArray) // networking size_t... // god help us all val SizeTCodec = StreamCodec.Impl({ stream -> stream.readVarLong().let { if (it == 0L) -1L else it - 1L } }, { stream, value -> if (value in 0L..> networkedEnum(value: E) = BasicNetworkedElement(value, StreamCodec.Enum(value::class.java)) +fun networkedData(value: T, codec: StreamCodec) = BasicNetworkedElement(value, codec) +fun networkedData(value: T, codec: StreamCodec, legacyCodec: StreamCodec) = BasicNetworkedElement(value, codec, legacyCodec, { it }, { it }) + +fun networkedEventCounter() = EventCounterElement() +fun networkedString(value: String = "") = BasicNetworkedElement(value, BinaryStringCodec) + +fun networkedPoly(value: Poly) = networkedData(value, Poly.CODEC, Poly.LEGACY_CODEC) + inline fun networkedJson(value: T, adapter: TypeAdapter = Starbound.gson.getAdapter(T::class.java), legacyIsArray: Boolean = true): BasicNetworkedElement { if (legacyIsArray) { return BasicNetworkedElement(value, JsonCodec(adapter), JsonCodec(adapter, true), { it }, { it }) @@ -53,12 +91,22 @@ inline fun networkedJson(value: T, adapter: TypeAdapter = Starbou } } -/** - * properly networks an enum on native protocol; - * networks a signed variable length integer on legacy protocol. - * this is way too dumb beyond my comprehension - */ +fun nativeCodec(reader: DataInputStream.(Boolean) -> T, writer: T.(DataOutputStream, Boolean) -> Unit): StreamCodec { + return StreamCodec.Impl({ reader(it, false) }, { a, b -> writer(b, a, false) }) +} + +fun legacyCodec(reader: DataInputStream.(Boolean) -> T, writer: T.(DataOutputStream, Boolean) -> Unit): StreamCodec { + return StreamCodec.Impl({ reader(it, true) }, { a, b -> writer(b, a, true) }) +} + +// networks a signed variable length integer on legacy protocol fun > networkedEnumStupid(value: E): BasicNetworkedElement { val codec = StreamCodec.Enum(value::class.java) return BasicNetworkedElement(value, codec, VarIntValueCodec, { it.ordinal.shl(1) }, { codec.values[it.ushr(1)] }) } + +// networks enum as string on legacy protocol +fun > networkedEnumExtraStupid(value: E): BasicNetworkedElement { + val codec = StreamCodec.Enum(value::class.java) + return BasicNetworkedElement(value, codec, BinaryStringCodec, { if (it is IStringSerializable) it.jsonName else it.name }, { s -> codec.values.firstOrNull { if (it is IStringSerializable) it.match(s) else it.name == s } ?: throw NoSuchElementException(s) }) +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/FloatingNetworkedElement.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/FloatingNetworkedElement.kt index 9c8b0755..53f05284 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/FloatingNetworkedElement.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/FloatingNetworkedElement.kt @@ -15,7 +15,7 @@ import java.util.function.DoubleSupplier import kotlin.math.roundToLong // works solely with doubles, but networks as either float, double or fixed point -class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, val legacyOps: Ops = ops, val interpolator: Interpolator = Interpolator.NearestMiddle) : NetworkedElement(), ListenableDelegate, DoubleSupplier { +class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, val legacyOps: Ops = ops, var interpolator: Interpolator = Interpolator.NearestMiddle) : NetworkedElement(), ListenableDelegate, DoubleSupplier { interface Ops { fun write(data: DataOutputStream, value: Double) fun read(data: DataInputStream): Double @@ -251,7 +251,7 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va } fun fixed(base: Double, value: Double = 0.0): FloatingNetworkedElement { - return FloatingNetworkedElement(value, FixedPointOps(base)) + return FloatingNetworkedElement(value, DoubleOps, FixedPointOps(base)) } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/GroupElement.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/GroupElement.kt index e79a3645..5588eaab 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/GroupElement.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/syncher/GroupElement.kt @@ -42,7 +42,7 @@ class GroupElement() : NetworkedElement() { elements.forEach { it.first.tickInterpolation(delta) } } - fun add(element: NetworkedElement, propagateInterpolation: Boolean = true): GroupElement { + fun add(element: E, propagateInterpolation: Boolean = true): E { require(elements.none { it.first == element }) { "Already has element $element in $this" } elements.add(element to propagateInterpolation) @@ -62,7 +62,7 @@ class GroupElement() : NetworkedElement() { }) this.version = this.version.coerceAtLeast(element.version) - return this + return element } override fun readInitial(data: DataInputStream, isLegacy: Boolean) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt index 7ebfff0c..39e60bcd 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt @@ -121,12 +121,12 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn private inner class ChunkListener(val pos: ChunkPos) : IChunkListener { override fun onEntityAdded(entity: AbstractEntity) { - if (entity is WorldObject && !isLegacy) - send(SpawnWorldObjectPacket(entity.uuid, entity.serialize())) + //if (entity is WorldObject && !isLegacy) + // send(SpawnWorldObjectPacket(entity.uuid, entity.serialize())) } override fun onEntityRemoved(entity: AbstractEntity) { - send(ForgetEntityPacket(entity.uuid)) + //send(ForgetEntityPacket(entity.uuid)) } override fun onCellChanges(x: Int, y: Int, cell: ImmutableCell) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Direction.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Direction.kt index 8325c102..d4e93b56 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Direction.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Direction.kt @@ -1,21 +1,36 @@ package ru.dbotthepony.kstarbound.world import com.google.gson.stream.JsonWriter +import ru.dbotthepony.kommons.io.StreamCodec import ru.dbotthepony.kommons.vector.Vector2d import ru.dbotthepony.kstarbound.json.builder.IStringSerializable -enum class Direction(val normal: Vector2d, val jsonName: String) : IStringSerializable { - UP(Vector2d.POSITIVE_Y, "up"), - RIGHT(Vector2d.POSITIVE_X, "right"), - DOWN(Vector2d.NEGATIVE_Y, "down"), +enum class Direction(val normal: Vector2d, override val jsonName: String) : IStringSerializable { LEFT(Vector2d.NEGATIVE_X, "left"), + RIGHT(Vector2d.POSITIVE_X, "right"), + + UP(Vector2d.POSITIVE_Y, "up"), + DOWN(Vector2d.NEGATIVE_Y, "down"), NONE(Vector2d.ZERO, "any"); override fun match(name: String): Boolean { return name.lowercase() == jsonName } - override fun write(out: JsonWriter) { - out.value(jsonName) + companion object { + val CODEC = StreamCodec.Enum(Direction::class.java) + } +} + +enum class Direction1D(val normal: Vector2d, override val jsonName: String) : IStringSerializable { + LEFT(Vector2d.NEGATIVE_X, "left"), + RIGHT(Vector2d.POSITIVE_X, "right"); + + override fun match(name: String): Boolean { + return name.lowercase() == jsonName + } + + companion object { + val CODEC = StreamCodec.Enum(Direction1D::class.java) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt index 4a779ce2..3189ce66 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt @@ -31,6 +31,7 @@ import ru.dbotthepony.kstarbound.world.physics.getBlockPlatforms import ru.dbotthepony.kstarbound.world.physics.getBlocksMarchingSquares import java.io.Closeable import java.util.concurrent.ForkJoinPool +import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.locks.ReentrantLock import java.util.function.Predicate import java.util.random.RandomGenerator @@ -43,6 +44,8 @@ abstract class World, ChunkType : Chunk) { } protected open fun onRemove(world: World<*, *>) { } + abstract fun writeToNetwork(stream: DataOutputStream, isLegacy: Boolean) + /** * MUST be called by [World] itself */ @@ -74,6 +95,9 @@ abstract class AbstractEntity(path: String) : JsonDriven(path) { if (innerWorld != null) throw IllegalStateException("Already spawned (in world $innerWorld)") + if (entityID == 0) + entityID = world.nextEntityID.incrementAndGet() + world.ensureSameThread() if (mailbox.isShutdown) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ActorEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ActorEntity.kt new file mode 100644 index 00000000..3ae22ded --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ActorEntity.kt @@ -0,0 +1,8 @@ +package ru.dbotthepony.kstarbound.world.entities + +/** + * Monsters, NPCs, Players + */ +abstract class ActorEntity(path: String) : DynamicEntity(path) { + final override val movement: ActorMovementController = ActorMovementController() +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/AbstractActorMovementController.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ActorMovementController.kt similarity index 81% rename from src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/AbstractActorMovementController.kt rename to src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ActorMovementController.kt index 4fc39545..107d71d8 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/AbstractActorMovementController.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ActorMovementController.kt @@ -1,85 +1,93 @@ package ru.dbotthepony.kstarbound.world.entities +import ru.dbotthepony.kommons.util.getValue +import ru.dbotthepony.kommons.util.setValue import ru.dbotthepony.kommons.vector.Vector2d import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.defs.ActorMovementParameters import ru.dbotthepony.kstarbound.defs.JumpProfile import ru.dbotthepony.kstarbound.defs.MovementParameters import ru.dbotthepony.kstarbound.defs.player.ActorMovementModifiers +import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean +import ru.dbotthepony.kstarbound.network.syncher.networkedEnum import ru.dbotthepony.kstarbound.util.GameTimer import ru.dbotthepony.kstarbound.world.Direction +import ru.dbotthepony.kstarbound.world.Direction1D import ru.dbotthepony.kstarbound.world.physics.CollisionType import kotlin.math.PI import kotlin.math.absoluteValue import kotlin.math.sign -abstract class AbstractActorMovementController : AbstractMovementController() { - abstract var controlRun: Boolean - abstract var controlCrouch: Boolean - abstract var controlDown: Boolean - abstract var lastControlDown: Boolean - abstract var controlFly: Vector2d? - abstract var controlFace: Direction? +class ActorMovementController : MovementController() { + var controlRun: Boolean = false + var controlCrouch: Boolean = false + var controlDown: Boolean = false + var lastControlDown: Boolean = false + var controlFly: Vector2d? = null + var controlFace: Direction1D? = null - abstract var isRunning: Boolean - protected set - abstract var isWalking: Boolean - protected set - abstract var isCrouching: Boolean - protected set - abstract var isFlying: Boolean - protected set - abstract var isFalling: Boolean - protected set - abstract var isJumping: Boolean - protected set + var isWalking: Boolean by networkGroup.upstream.add(networkedBoolean()) + private set + var isRunning: Boolean by networkGroup.upstream.add(networkedBoolean()) + private set - abstract var canJump: Boolean - abstract var controlJump: Boolean - abstract var controlJumpAnyway: Boolean + var movingDirection: Direction1D by networkGroup.upstream.add(networkedEnum(Direction1D.RIGHT)) + private set + var facingDirection: Direction1D by networkGroup.upstream.add(networkedEnum(Direction1D.RIGHT)) + private set - abstract var lastControlJump: Boolean - protected set - abstract var lastControlCrouch: Boolean - protected set + var isCrouching: Boolean by networkGroup.upstream.add(networkedBoolean()) + private set - abstract var isGroundMovement: Boolean - protected set - abstract var isLiquidMovement: Boolean - protected set + var isFlying: Boolean by networkGroup.upstream.add(networkedBoolean()) + private set + var isFalling: Boolean by networkGroup.upstream.add(networkedBoolean()) + private set - abstract var controlRotationRate: Double - abstract var controlAcceleration: Vector2d - abstract var controlForce: Vector2d + var canJump: Boolean by networkGroup.upstream.add(networkedBoolean()) + private set + var isJumping: Boolean by networkGroup.upstream.add(networkedBoolean()) + private set - abstract var fallThroughSustain: Int - protected set + var isGroundMovement: Boolean by networkGroup.upstream.add(networkedBoolean()) + private set + var isLiquidMovement: Boolean by networkGroup.upstream.add(networkedBoolean()) + private set + + var controlJump: Boolean = false + var controlJumpAnyway: Boolean = false + + var lastControlJump: Boolean = false + private set + var lastControlCrouch: Boolean = false + private set + + var controlRotationRate: Double = 0.0 + var controlAcceleration: Vector2d = Vector2d.ZERO + var controlForce: Vector2d = Vector2d.ZERO + + var fallThroughSustain: Int = 0 + private set // Target horizontal velocity for walking / running - abstract var targetHorizontalAmbulatingVelocity: Double - protected set + var targetHorizontalAmbulatingVelocity: Double = 0.0 + private set - abstract var controlPathMove: Pair? - abstract var pathMoveResult: Pair? + var controlPathMove: Pair? = null + var pathMoveResult: Pair? = null - abstract var controlMove: Direction? + var controlMove: Direction? = null - abstract var actorMovementParameters: ActorMovementParameters - abstract var movementModifiers: ActorMovementModifiers + var actorMovementParameters: ActorMovementParameters = ActorMovementParameters.EMPTY + var movementModifiers: ActorMovementModifiers = ActorMovementModifiers.EMPTY - abstract var controlActorMovementParameters: ActorMovementParameters - abstract var controlMovementModifiers: ActorMovementModifiers + var controlActorMovementParameters: ActorMovementParameters = ActorMovementParameters.EMPTY + var controlMovementModifiers: ActorMovementModifiers = ActorMovementModifiers.EMPTY - abstract val approachVelocities: MutableList - abstract val approachVelocityAngles: MutableList + val approachVelocities = ArrayList() + val approachVelocityAngles = ArrayList() - abstract var movingDirection: Direction? - abstract var facingDirection: Direction? - - // this is set internally on each move step - final override var movementParameters: MovementParameters = MovementParameters.EMPTY - - abstract var anchorEntity: DynamicEntity? + var anchorEntity: DynamicEntity? = null var pathController: PathController? = null var groundMovementSustainTimer: GameTimer = GameTimer(0.0) @@ -177,7 +185,7 @@ abstract class AbstractActorMovementController : AbstractMovementController() { return params } - open fun clearControls() { + fun clearControls() { controlRotationRate = 0.0 controlAcceleration = Vector2d.ZERO controlForce = Vector2d.ZERO @@ -268,7 +276,7 @@ abstract class AbstractActorMovementController : AbstractMovementController() { isLiquidMovement = liquidPercentage >= (actorMovementParameters.minimumLiquidPercentage ?: 0.0) val liquidImpedance = liquidPercentage * (actorMovementParameters.liquidImpedance ?: 0.0) - var updatedMovingDirection: Direction? = null + var updatedMovingDirection: Direction1D? = null val isRunning = controlRun && !movementModifiers.runningSuppressed if (controlFly != null) { @@ -283,9 +291,9 @@ abstract class AbstractActorMovementController : AbstractMovementController() { approachVelocity(flyVelocity * movementModifiers.speedModifier, movementParameters.airForce ?: 0.0) if (flyVelocity.x > 0.0) - updatedMovingDirection = Direction.RIGHT + updatedMovingDirection = Direction1D.RIGHT else if (flyVelocity.x < 0.0) - updatedMovingDirection = Direction.LEFT + updatedMovingDirection = Direction1D.LEFT groundMovementSustainTimer = GameTimer(0.0) } else { @@ -365,10 +373,10 @@ abstract class AbstractActorMovementController : AbstractMovementController() { } if (controlMove == Direction.LEFT) { - updatedMovingDirection = Direction.LEFT + updatedMovingDirection = Direction1D.LEFT targetHorizontalAmbulatingVelocity = -1.0 * (if (isRunning) movementParameters.runSpeed ?: 0.0 else movementParameters.walkSpeed ?: 0.0) * movementModifiers.speedModifier } else if (controlMove == Direction.RIGHT) { - updatedMovingDirection = Direction.RIGHT + updatedMovingDirection = Direction1D.RIGHT targetHorizontalAmbulatingVelocity = 1.0 * (if (isRunning) movementParameters.runSpeed ?: 0.0 else movementParameters.walkSpeed ?: 0.0) * movementModifiers.speedModifier } @@ -391,15 +399,16 @@ abstract class AbstractActorMovementController : AbstractMovementController() { } } - movingDirection = updatedMovingDirection + if (updatedMovingDirection != null) + movingDirection = updatedMovingDirection if (!movementModifiers.facingSuppressed) { if (controlFace != null) - facingDirection = controlFace + facingDirection = controlFace!! else if (updatedMovingDirection != null) facingDirection = updatedMovingDirection else if (controlPathMove != null && pathController?.controlFace != null) - facingDirection = pathController?.controlFace + facingDirection = pathController?.controlFace!! } isGroundMovement = !groundMovementSustainTimer.hasFinished diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/DynamicEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/DynamicEntity.kt index 902c70d0..6b27d42f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/DynamicEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/DynamicEntity.kt @@ -8,6 +8,7 @@ import ru.dbotthepony.kstarbound.client.render.LayeredRenderer import ru.dbotthepony.kstarbound.client.render.RenderLayer import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.World +import java.util.function.Consumer /** * Entities with dynamics (Player, Drops, Projectiles, NPCs, etc) @@ -15,35 +16,33 @@ import ru.dbotthepony.kstarbound.world.World abstract class DynamicEntity(path: String) : AbstractEntity(path) { private var forceChunkRepos = false - var position = Vector2d() + override var position + get() = movement.position set(value) { - val old = field - - if (isSpawned) { - field = world.geometry.wrap(value) - - val oldChunkPos = world.geometry.chunkFromCell(old) - val newChunkPos = world.geometry.chunkFromCell(field) - - chunkPos = newChunkPos - - if (oldChunkPos != newChunkPos || forceChunkRepos) { - chunk = world.chunkMap[newChunkPos] - forceChunkRepos = false - } - } else { - field = value - } + movement.position = value } - abstract val movement: AbstractMovementController + abstract val movement: MovementController + final override var chunkPos: ChunkPos = ChunkPos.ZERO private set override fun onJoinWorld(world: World<*, *>) { world.dynamicEntities.add(this) + movement.world = world forceChunkRepos = true - position = position + + /*movement.positionListeners.addListener(Consumer { field -> + val oldChunkPos = world.geometry.chunkFromCell(old) + val newChunkPos = world.geometry.chunkFromCell(field) + + chunkPos = newChunkPos + + if (oldChunkPos != newChunkPos || forceChunkRepos) { + chunk = world.chunkMap[newChunkPos] + forceChunkRepos = false + } + })*/ } override fun onRemove(world: World<*, *>) { @@ -65,7 +64,5 @@ abstract class DynamicEntity(path: String) : AbstractEntity(path) { companion object { val BLOCK_COLLISION_COLOR = RGBAColor(65, 179, 217) - const val SEPARATION_STEPS = 3 - const val SEPARATION_TOLERANCE = 0.001 } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/EntityActorMovementController.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/EntityActorMovementController.kt deleted file mode 100644 index 1a94ea66..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/EntityActorMovementController.kt +++ /dev/null @@ -1,67 +0,0 @@ -package ru.dbotthepony.kstarbound.world.entities - -import ru.dbotthepony.kommons.vector.Vector2d -import ru.dbotthepony.kstarbound.GlobalDefaults -import ru.dbotthepony.kstarbound.defs.ActorMovementParameters -import ru.dbotthepony.kstarbound.defs.player.ActorMovementModifiers -import ru.dbotthepony.kstarbound.world.Direction -import ru.dbotthepony.kstarbound.world.World - -class EntityActorMovementController(val entity: DynamicEntity) : AbstractActorMovementController() { - override val world: World<*, *> by entity::world - override var position: Vector2d by entity::position - override var actorMovementParameters: ActorMovementParameters = GlobalDefaults.actorMovementParameters - - override var lastControlDown: Boolean = false - override var fallThroughSustain: Int = 0 - override var targetHorizontalAmbulatingVelocity: Double = 0.0 - - override var controlRun: Boolean = false - override var controlCrouch: Boolean = false - override var controlDown: Boolean = false - override var controlFly: Vector2d? = null - override var isFlying: Boolean = false - override var isFalling: Boolean = false - override var canJump: Boolean = true - override var controlJump: Boolean = false - override var isGroundMovement: Boolean = false - override var isLiquidMovement: Boolean = false - override var controlPathMove: Pair? = null - override var pathMoveResult: Pair? = null - override var controlMove: Direction? = null - override var movementModifiers: ActorMovementModifiers = ActorMovementModifiers.EMPTY - - override var controlActorMovementParameters: ActorMovementParameters = ActorMovementParameters.EMPTY - override var controlMovementModifiers: ActorMovementModifiers = ActorMovementModifiers.EMPTY - - override var isOnGround: Boolean = false - override var isColliding: Boolean = false - override var isCollisionStuck: Boolean = false - override var isCollidingWithNull: Boolean = false - override var stickingDirection: Double? = null - override var surfaceSlope: Vector2d = Vector2d.ZERO - override var surfaceVelocity: Vector2d = Vector2d.ZERO - override var collisionCorrection: Vector2d = Vector2d.ZERO - override var liquidPercentage: Double = 0.0 - override var appliedForceRegion: Boolean = false - override var isZeroGravity: Boolean = false - - override var controlRotationRate: Double = 0.0 - override var controlAcceleration: Vector2d = Vector2d.ZERO - override var controlForce: Vector2d = Vector2d.ZERO - override var rotation: Double = 0.0 - - override var lastControlCrouch: Boolean = false - override var controlFace: Direction? = null - override var isRunning: Boolean = false - override var isWalking: Boolean = false - override var isCrouching: Boolean = false - override var isJumping: Boolean = false - override var lastControlJump: Boolean = false - override var controlJumpAnyway: Boolean = false - override val approachVelocities: MutableList = ArrayList() - override val approachVelocityAngles: MutableList = ArrayList() - override var movingDirection: Direction? = null - override var facingDirection: Direction? = null - override var anchorEntity: DynamicEntity? = null -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/EntityMovementController.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/EntityMovementController.kt deleted file mode 100644 index 49a33479..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/EntityMovementController.kt +++ /dev/null @@ -1,25 +0,0 @@ -package ru.dbotthepony.kstarbound.world.entities - -import ru.dbotthepony.kommons.vector.Vector2d -import ru.dbotthepony.kstarbound.GlobalDefaults -import ru.dbotthepony.kstarbound.defs.MovementParameters -import ru.dbotthepony.kstarbound.world.World - -class EntityMovementController(val entity: DynamicEntity) : AbstractMovementController() { - override val world: World<*, *> by entity::world - override var position: Vector2d by entity::position - override var movementParameters: MovementParameters = GlobalDefaults.movementParameters - - override var isOnGround: Boolean = false - override var isColliding: Boolean = false - override var isCollisionStuck: Boolean = false - override var isCollidingWithNull: Boolean = false - override var stickingDirection: Double? = null - override var surfaceSlope: Vector2d = Vector2d.ZERO - override var surfaceVelocity: Vector2d = Vector2d.ZERO - override var collisionCorrection: Vector2d = Vector2d.ZERO - override var liquidPercentage: Double = 0.0 - override var appliedForceRegion: Boolean = false - override var isZeroGravity: Boolean = false - override var rotation: Double = 0.0 -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/HumanoidActorEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/HumanoidActorEntity.kt new file mode 100644 index 00000000..37e7dd7a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/HumanoidActorEntity.kt @@ -0,0 +1,10 @@ +package ru.dbotthepony.kstarbound.world.entities + +import ru.dbotthepony.kommons.vector.Vector2d + +/** + * Players and NPCs + */ +abstract class HumanoidActorEntity(path: String) : ActorEntity(path) { + abstract val aimPosition: Vector2d +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ItemEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ItemEntity.kt deleted file mode 100644 index 044a092b..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ItemEntity.kt +++ /dev/null @@ -1,21 +0,0 @@ -package ru.dbotthepony.kstarbound.world.entities - -import com.google.gson.JsonObject -import ru.dbotthepony.kommons.util.Either -import ru.dbotthepony.kommons.util.AABB -import ru.dbotthepony.kommons.vector.Vector2d -import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition -import ru.dbotthepony.kstarbound.world.World -import ru.dbotthepony.kstarbound.world.physics.Poly - -class ItemEntity(val def: IItemDefinition) : DynamicEntity("/") { - override val movement = EntityMovementController(this) - - override fun defs(): Collection { - return emptyList() - } - - init { - movement.movementParameters = movement.movementParameters.copy(collisionPoly = Either.left(Poly(AABB.rectangle(Vector2d.ZERO, 0.75, 0.75)))) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/AbstractMovementController.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MovementController.kt similarity index 83% rename from src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/AbstractMovementController.kt rename to src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MovementController.kt index cbc83bd9..86eae32c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/AbstractMovementController.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MovementController.kt @@ -1,11 +1,28 @@ package ru.dbotthepony.kstarbound.world.entities +import it.unimi.dsi.fastutil.bytes.ByteArrayList +import ru.dbotthepony.kommons.io.DoubleValueCodec +import ru.dbotthepony.kommons.io.FloatValueCodec +import ru.dbotthepony.kommons.io.StreamCodec +import ru.dbotthepony.kommons.io.koptional +import ru.dbotthepony.kommons.io.map import ru.dbotthepony.kommons.math.linearInterpolation import ru.dbotthepony.kommons.util.AABB +import ru.dbotthepony.kommons.util.KOptional +import ru.dbotthepony.kommons.util.Listenable +import ru.dbotthepony.kommons.util.getValue +import ru.dbotthepony.kommons.util.setValue import ru.dbotthepony.kommons.vector.Vector2d import ru.dbotthepony.kommons.vector.times import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.defs.MovementParameters +import ru.dbotthepony.kstarbound.network.syncher.GroupElement +import ru.dbotthepony.kstarbound.network.syncher.MasterElement +import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean +import ru.dbotthepony.kstarbound.network.syncher.networkedData +import ru.dbotthepony.kstarbound.network.syncher.networkedFixedPoint +import ru.dbotthepony.kstarbound.network.syncher.networkedFloat +import ru.dbotthepony.kstarbound.network.syncher.networkedPoly import ru.dbotthepony.kstarbound.world.World import ru.dbotthepony.kstarbound.world.physics.CollisionPoly import ru.dbotthepony.kstarbound.world.physics.CollisionType @@ -16,15 +33,14 @@ import kotlin.math.absoluteValue import kotlin.math.acos import kotlin.math.cos import kotlin.math.sin +import kotlin.properties.Delegates -abstract class AbstractMovementController() { - abstract val world: World<*, *> +open class MovementController() { + var world: World<*, *> by Delegates.notNull() val localHitboxes: Stream get() { return (movementParameters.collisionPoly?.map({ Stream.of(it) }, { it.stream() }) ?: return Stream.of()).map { it.rotate(rotation) + position } } - abstract var position: Vector2d - open fun shouldCollideWithType(type: CollisionType): Boolean { return type !== CollisionType.NONE } @@ -33,43 +49,75 @@ abstract class AbstractMovementController() { return shouldCollideWithType(body.type) } + private val legacyPoly = networkedPoly(Poly.EMPTY) + + protected val networkGroup = MasterElement(GroupElement(legacyPoly)) + + var mass by networkGroup.upstream.add(networkedFloat()) + + private var xPosition by networkGroup.upstream.add(networkedFixedPoint(0.0125)) + private var yPosition by networkGroup.upstream.add(networkedFixedPoint(0.0125)) + private var xVelocity by networkGroup.upstream.add(networkedFixedPoint(0.00625)) + private var yVelocity by networkGroup.upstream.add(networkedFixedPoint(0.00625)) + + var rotation by networkGroup.upstream.add(networkedFixedPoint(0.01)) + + var isColliding: Boolean by networkGroup.upstream.add(networkedBoolean()) + protected set + var isCollisionStuck: Boolean by networkGroup.upstream.add(networkedBoolean()) + protected set + var isCollidingWithNull: Boolean by networkGroup.upstream.add(networkedBoolean()) + protected set + + private val stickingDirectionField = networkGroup.upstream.add(networkedData(KOptional(), DoubleValueCodec.koptional(), FloatValueCodec.map({ toDouble() }, { toFloat() }).koptional())) + + var isOnGround: Boolean by networkGroup.upstream.add(networkedBoolean()) + protected set + var isZeroGravity: Boolean by networkGroup.upstream.add(networkedBoolean()) + protected set + + private val surfaceMovingCollisionField = networkGroup.upstream.add(networkedData(KOptional(), StreamCodec.Impl(::MovingCollisionID, { a, b -> b.write(a) }).koptional())) + private val relativeXSurfaceVelocity = networkGroup.upstream.add(networkedFloat()) + private val relativeYSurfaceVelocity = networkGroup.upstream.add(networkedFloat()) + + var position: Vector2d + get() = Vector2d(xPosition, yPosition) + set(value) { + xPosition = value.x + yPosition = value.y + } + + val positionListeners = Listenable.Impl() + // Movement variables - abstract var isOnGround: Boolean + var stickingDirection: Double? + get() = stickingDirectionField.get().orNull() + protected set(value) { + stickingDirectionField.accept(KOptional.ofNullable(value)) + } + var surfaceSlope: Vector2d = Vector2d.ZERO protected set - abstract var isColliding: Boolean + var surfaceVelocity: Vector2d = Vector2d.ZERO protected set - abstract var isCollisionStuck: Boolean + var collisionCorrection: Vector2d = Vector2d.ZERO protected set - abstract var isCollidingWithNull: Boolean - protected set - abstract var stickingDirection: Double? - protected set - abstract var surfaceSlope: Vector2d - protected set - abstract var surfaceVelocity: Vector2d - protected set - abstract var collisionCorrection: Vector2d - protected set - abstract var liquidPercentage: Double + var liquidPercentage: Double = 0.0 protected set - abstract var appliedForceRegion: Boolean + var appliedForceRegion: Boolean = false protected set - abstract var isZeroGravity: Boolean - protected set + var movementParameters: MovementParameters = MovementParameters.EMPTY - abstract var movementParameters: MovementParameters + var gravityMultiplier = 1.0 + var isGravityDisabled = false - abstract var rotation: Double - - open var gravityMultiplier = 1.0 - open var isGravityDisabled = false - - val mass: Double - get() = movementParameters.mass ?: 1.0 - - var velocity = Vector2d.ZERO + var velocity: Vector2d + get() = Vector2d(xVelocity, yVelocity) + set(value) { + xVelocity = value.x + yVelocity = value.y + } fun determineGravity(): Vector2d { if (isZeroGravity || isGravityDisabled) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MovingCollisionID.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MovingCollisionID.kt new file mode 100644 index 00000000..456c202f --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MovingCollisionID.kt @@ -0,0 +1,21 @@ +package ru.dbotthepony.kstarbound.world.entities + +import ru.dbotthepony.kommons.io.readInt +import ru.dbotthepony.kommons.io.writeInt +import ru.dbotthepony.kstarbound.network.syncher.readPointer +import ru.dbotthepony.kstarbound.network.syncher.writePointer +import java.io.InputStream +import java.io.OutputStream + +data class MovingCollisionID(val entity: Int = 0, val collisionIndex: Int = -1) { + constructor(stream: InputStream) : this(stream.readInt(), stream.readPointer().toInt()) + + fun write(stream: OutputStream) { + stream.writeInt(entity) + stream.writePointer(collisionIndex) + } + + companion object { + val ZERO = MovingCollisionID(0, -1) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/PathController.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/PathController.kt index 8a0f5b00..068d9164 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/PathController.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/PathController.kt @@ -1,7 +1,7 @@ package ru.dbotthepony.kstarbound.world.entities import ru.dbotthepony.kommons.vector.Vector2d -import ru.dbotthepony.kstarbound.world.Direction +import ru.dbotthepony.kstarbound.world.Direction1D import ru.dbotthepony.kstarbound.world.World class PathController(val world: World<*, *>, var edgeTimer: Double = 0.0) { @@ -9,7 +9,7 @@ class PathController(val world: World<*, *>, var edgeTimer: Double = 0.0) { private set var endPosition: Vector2d? = null private set - var controlFace: Direction? = null + var controlFace: Direction1D? = null private set fun reset() { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/PlayerEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/PlayerEntity.kt index 565122dd..e7961389 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/PlayerEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/PlayerEntity.kt @@ -1,12 +1,62 @@ package ru.dbotthepony.kstarbound.world.entities import com.google.gson.JsonObject -import ru.dbotthepony.kstarbound.Starbound -import ru.dbotthepony.kstarbound.defs.ActorMovementParameters -import ru.dbotthepony.kstarbound.world.World +import ru.dbotthepony.kommons.util.getValue +import ru.dbotthepony.kommons.util.setValue +import ru.dbotthepony.kommons.vector.Vector2d +import ru.dbotthepony.kstarbound.defs.EntityDamageTeam +import ru.dbotthepony.kstarbound.defs.actor.HumanoidData +import ru.dbotthepony.kstarbound.defs.actor.HumanoidEmote +import ru.dbotthepony.kstarbound.json.builder.IStringSerializable +import ru.dbotthepony.kstarbound.math.Interpolator +import ru.dbotthepony.kstarbound.network.syncher.GroupElement +import ru.dbotthepony.kstarbound.network.syncher.MasterElement +import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean +import ru.dbotthepony.kstarbound.network.syncher.networkedData +import ru.dbotthepony.kstarbound.network.syncher.networkedEnum +import ru.dbotthepony.kstarbound.network.syncher.networkedEnumExtraStupid +import ru.dbotthepony.kstarbound.network.syncher.networkedEnumStupid +import ru.dbotthepony.kstarbound.network.syncher.networkedEventCounter +import ru.dbotthepony.kstarbound.network.syncher.networkedFixedPoint +import ru.dbotthepony.kstarbound.network.syncher.networkedString +import java.io.DataOutputStream +import java.util.UUID +import kotlin.properties.Delegates -class PlayerEntity() : DynamicEntity("/") { - override val movement = EntityActorMovementController(this) +class PlayerEntity() : HumanoidActorEntity("/") { + enum class State(override val jsonName: String) : IStringSerializable { + IDLE("Idle"), + WALK("Walk"), + RUN("Run"), + JUMP("Jump"), + FALL("Fall"), + SWIM("Swim"), + SWIM_IDLE("SwimIdle"), + TELEPORT_IN("TeleportIn"), + TELEPORT_OUT("TeleportOut"), + CROUCH("Crouch"), + LOUNGE("Lounge"); + } + + override fun writeToNetwork(stream: DataOutputStream, isLegacy: Boolean) { + TODO("Not yet implemented") + } + + val networkGroup = MasterElement(GroupElement()) + + var state by networkGroup.upstream.add(networkedEnum(State.IDLE)) + var shifting by networkGroup.upstream.add(networkedBoolean()) + private var xAimPosition by networkGroup.upstream.add(networkedFixedPoint(0.003125)) + private var yAimPosition by networkGroup.upstream.add(networkedFixedPoint(0.003125).also { it.interpolator = Interpolator.Linear }) + var humanoidData by networkGroup.upstream.add(networkedData(HumanoidData(), HumanoidData.CODEC, HumanoidData.LEGACY_CODEC)) + var teamState by networkGroup.upstream.add(networkedData(EntityDamageTeam(), EntityDamageTeam.CODEC, EntityDamageTeam.LEGACY_CODEC)) + val landed = networkGroup.upstream.add(networkedEventCounter()) + var chatMessage by networkGroup.upstream.add(networkedString()) + val newChatMessage = networkGroup.upstream.add(networkedEventCounter()) + var emote by networkGroup.upstream.add(networkedEnumExtraStupid(HumanoidEmote.IDLE)) + + override val aimPosition: Vector2d + get() = Vector2d(xAimPosition, yAimPosition) override val isApplicableForUnloading: Boolean get() = false @@ -15,36 +65,6 @@ class PlayerEntity() : DynamicEntity("/") { return emptyList() } - init { - movement.actorMovementParameters = movement.actorMovementParameters.merge( - Starbound.gson.fromJson(""" - { - "standingPoly" : [ [-0.75, -2.0], [-0.35, -2.5], [0.35, -2.5], [0.75, -2.0], [0.75, 0.65], [0.35, 1.22], [-0.35, 1.22], [-0.75, 0.65] ], - "crouchingPoly" : [ [-0.75, -2.0], [-0.35, -2.5], [0.35, -2.5], [0.75, -2.0], [0.75, -1], [0.35, -0.5], [-0.35, -0.5], [-0.75, -1] ], - "mass" : 1.6, - - // should keep the player from teleporting through walls - "maximumCorrection" : 3, - "maxMovementPerStep" : 0.4, - - "liquidFriction" : 13.0, - "normalGroundFriction" : 35.0, - - "groundForce" : 250.0, - "airForce" : 50.0, - "liquidForce" : 80.0 - } - """.trimIndent(), ActorMovementParameters::class.java) - ).merge(Starbound.gson.fromJson(""" - { - "flySpeed" : 0, - "airFriction" : 0.5, - "airJumpProfile" : { - "jumpSpeed" : 23.0, - "jumpInitialPercentage" : 0.75, - "jumpHoldTime" : 0.2 - } - } - """.trimIndent(), ActorMovementParameters::class.java)) - } + var uuid: UUID by Delegates.notNull() + private set } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/TileEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/TileEntity.kt index d2e9cd93..4b84d44b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/TileEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/TileEntity.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.kstarbound.world.entities +import com.google.gson.JsonObject import ru.dbotthepony.kommons.vector.Vector2d import ru.dbotthepony.kommons.vector.Vector2i import ru.dbotthepony.kstarbound.world.ChunkPos @@ -11,7 +12,7 @@ import ru.dbotthepony.kstarbound.world.World abstract class TileEntity(path: String) : AbstractEntity(path) { private var forceChunkRepos = false - var position = Vector2i() + var tilePosition = Vector2i() set(value) { val old = field @@ -32,13 +33,16 @@ abstract class TileEntity(path: String) : AbstractEntity(path) { } } + override val position: Vector2d + get() = tilePosition.toDoubleVector() + final override var chunkPos: ChunkPos = ChunkPos.ZERO private set override fun onJoinWorld(world: World<*, *>) { world.tileEntities.add(this) forceChunkRepos = true - position = position + tilePosition = tilePosition } override fun onRemove(world: World<*, *>) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/WorldObject.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/WorldObject.kt index 157b926b..1303202c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/WorldObject.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/WorldObject.kt @@ -27,6 +27,7 @@ import ru.dbotthepony.kstarbound.world.LightCalculator import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.world.World import ru.dbotthepony.kstarbound.world.api.TileColor +import java.io.DataOutputStream import kotlin.properties.Delegates open class WorldObject( @@ -47,10 +48,14 @@ open class WorldObject( } } + override fun writeToNetwork(stream: DataOutputStream, isLegacy: Boolean) { + TODO("Not yet implemented") + } + fun serialize(): JsonObject { val into = JsonObject() into["name"] = prototype.key - into["tilePosition"] = vectors.toJsonTree(position) + into["tilePosition"] = vectors.toJsonTree(tilePosition) into["direction"] = directions.toJsonTree(direction) into["orientationIndex"] = orientationIndex into["interactive"] = interactive @@ -170,7 +175,7 @@ open class WorldObject( color *= sample } - lightCalculator.addPointLight(position.x - xOffset, position.y - yOffset, color) + lightCalculator.addPointLight(tilePosition.x - xOffset, tilePosition.y - yOffset, color) } } @@ -194,7 +199,7 @@ open class WorldObject( val prototype = Registries.worldObjects[content["name"]?.asString ?: throw IllegalArgumentException("Missing object name")] ?: throw IllegalArgumentException("No such object defined for '${content["name"]}'") val pos = content.get("tilePosition", vectors) { throw IllegalArgumentException("No tilePosition was present in saved data") } val result = WorldObject(prototype) - result.position = pos + result.tilePosition = pos result.deserialize(content) return result } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/Poly.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/Poly.kt index fd7105ad..6c282f3d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/Poly.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/physics/Poly.kt @@ -18,7 +18,16 @@ import ru.dbotthepony.kommons.vector.Vector2d import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType import ru.dbotthepony.kommons.gson.consumeNull +import ru.dbotthepony.kommons.io.StreamCodec +import ru.dbotthepony.kommons.io.readCollection +import ru.dbotthepony.kommons.io.readVector2d +import ru.dbotthepony.kommons.io.readVector2f +import ru.dbotthepony.kommons.io.writeCollection +import ru.dbotthepony.kommons.io.writeStruct2d +import ru.dbotthepony.kommons.io.writeStruct2f import ru.dbotthepony.kstarbound.json.listAdapter +import java.io.DataInputStream +import java.io.DataOutputStream import kotlin.math.absoluteValue import kotlin.math.cos import kotlin.math.sin @@ -42,7 +51,8 @@ private fun calculateEdges(points: List): Pair() @@ -77,10 +87,20 @@ class Poly private constructor(val edges: ImmutableList, val vertices: Imm constructor(points: List) : this(calculateEdges(points)) constructor(aabb: AABB) : this(listOf(aabb.bottomLeft, aabb.topLeft, aabb.topRight, aabb.bottomRight)) - val aabb = AABB( - Vector2d(vertices.minOf { it.x }, vertices.minOf { it.y }), - Vector2d(vertices.maxOf { it.x }, vertices.maxOf { it.y }), - ) + val aabb: AABB + val isEmpty: Boolean + get() = vertices.isEmpty() + + init { + if (vertices.isEmpty()) { + aabb = AABB.ZERO + } else { + aabb = AABB( + Vector2d(vertices.minOf { it.x }, vertices.minOf { it.y }), + Vector2d(vertices.maxOf { it.x }, vertices.maxOf { it.y }), + ) + } + } data class Edge(val p0: Vector2d, val p1: Vector2d, val normal: Vector2d) { operator fun plus(other: IStruct2d): Edge { @@ -109,6 +129,7 @@ class Poly private constructor(val edges: ImmutableList, val vertices: Imm } operator fun plus(value: Penetration): Poly { + if (isEmpty) return this val vertices = ImmutableList.Builder() val edges = ImmutableList.Builder() @@ -119,6 +140,7 @@ class Poly private constructor(val edges: ImmutableList, val vertices: Imm } operator fun plus(value: IStruct2d): Poly { + if (isEmpty) return this val vertices = ImmutableList.Builder() val edges = ImmutableList.Builder() @@ -129,6 +151,7 @@ class Poly private constructor(val edges: ImmutableList, val vertices: Imm } operator fun minus(value: IStruct2d): Poly { + if (isEmpty) return this val vertices = ImmutableList.Builder() val edges = ImmutableList.Builder() @@ -139,6 +162,7 @@ class Poly private constructor(val edges: ImmutableList, val vertices: Imm } operator fun times(value: IStruct2d): Poly { + if (isEmpty) return this val vertices = ImmutableList.Builder() val edges = ImmutableList.Builder() @@ -149,6 +173,7 @@ class Poly private constructor(val edges: ImmutableList, val vertices: Imm } operator fun times(value: Double): Poly { + if (isEmpty) return this val vertices = ImmutableList.Builder() val edges = ImmutableList.Builder() @@ -159,7 +184,7 @@ class Poly private constructor(val edges: ImmutableList, val vertices: Imm } fun rotate(radians: Double): Poly { - if (radians == 0.0) + if (radians == 0.0 || isEmpty) return this val sin = sin(radians) @@ -181,6 +206,8 @@ class Poly private constructor(val edges: ImmutableList, val vertices: Imm // min / max fun project(normal: Vector2d): IStruct2d { + check(!isEmpty) { "Poly is empty" } + var min = vertices.first().dot(normal) var max = min @@ -198,7 +225,7 @@ class Poly private constructor(val edges: ImmutableList, val vertices: Imm * @param axis separate ONLY along specified axis, that said, if axis is positive Y and we have collision, and closest separation axis is to up right, we instead separate only up until we no longer collide. */ fun intersect(other: Poly, axis: Vector2d? = null, strictAxis: Boolean = false): Penetration? { - if (!aabb.intersectWeak(other.aabb)) + if (isEmpty || !aabb.intersectWeak(other.aabb)) return null val normals = ObjectOpenHashSet() @@ -284,6 +311,8 @@ class Poly private constructor(val edges: ImmutableList, val vertices: Imm } fun render(client: StarboundClient = StarboundClient.current(), color: RGBAColor = RGBAColor.LIGHT_GREEN) { + if (isEmpty) return + val program = client.programs.position val lines = program.builder.builder program.use() @@ -307,7 +336,7 @@ class Poly private constructor(val edges: ImmutableList, val vertices: Imm } override fun toString(): String { - return "Poly[aabb = $aabb; edges = $edges]" + return "Poly[aabb = $aabb; edges = $edges; vertices = $vertices]" } override fun equals(other: Any?): Boolean { @@ -318,9 +347,30 @@ class Poly private constructor(val edges: ImmutableList, val vertices: Imm return vertices.hashCode() } + fun write(stream: DataOutputStream, isLegacy: Boolean) { + if (isLegacy) { + stream.writeCollection(vertices) { writeStruct2f(it.toFloatVector()) } + } else { + stream.writeCollection(vertices) { writeStruct2d(it) } + } + } + companion object : TypeAdapterFactory { + val CODEC = StreamCodec.Impl({ read(it, false) }, { a, b -> b.write(a, false) }) + val LEGACY_CODEC = StreamCodec.Impl({ read(it, true) }, { a, b -> b.write(a, true) }) + + val EMPTY = Poly(ImmutableList.of(), ImmutableList.of()) + private val identity = Matrix3f.identity() + fun read(stream: DataInputStream, isLegacy: Boolean): Poly { + if (isLegacy) { + return Poly(stream.readCollection { readVector2f().toDoubleVector() }) + } else { + return Poly(stream.readCollection { readVector2d() }) + } + } + override fun create(gson: Gson, type: TypeToken): TypeAdapter? { if (type.rawType === Poly::class.java) { return object : TypeAdapter() {