From faefff3600892f63bdd0abfc5ff76551ca82ab4f Mon Sep 17 00:00:00 2001 From: Martin Jakobsson Date: Thu, 26 Dec 2024 19:32:21 +0100 Subject: [PATCH] Added head rotation to player model --- .../scala/hexacraft/client/ClientWorld.scala | 4 ++- .../scala/hexacraft/client/GameClient.scala | 13 ++++---- .../scala/hexacraft/world/EntityEvent.scala | 1 + .../scala/hexacraft/world/entity/base.scala | 5 +++ .../hexacraft/world/entity/components.scala | 12 +++++++ .../scala/hexacraft/world/entity/models.scala | 4 +-- .../scala/hexacraft/world/entity/player.scala | 21 ++++++++---- .../scala/hexacraft/world/entity/sheep.scala | 4 +-- .../scala/hexacraft/server/GameServer.scala | 33 +++++++++++-------- .../scala/hexacraft/server/ServerWorld.scala | 2 +- 10 files changed, 68 insertions(+), 31 deletions(-) diff --git a/client/src/main/scala/hexacraft/client/ClientWorld.scala b/client/src/main/scala/hexacraft/client/ClientWorld.scala index afacbd27..31d7338f 100644 --- a/client/src/main/scala/hexacraft/client/ClientWorld.scala +++ b/client/src/main/scala/hexacraft/client/ClientWorld.scala @@ -281,6 +281,8 @@ class ClientWorld(val worldInfo: WorldInfo) extends BlockRepository with BlocksI e.motion.velocity.set(v) case EntityEvent.Flying(f) => e.motion.flying = f + case EntityEvent.HeadDirection(d) => + e.headDirection.foreach(_.direction = d) } case None => event match { @@ -344,7 +346,7 @@ class ClientWorld(val worldInfo: WorldInfo) extends BlockRepository with BlocksI val vel = e.motion.velocity val horizontalSpeedSq = vel.x * vel.x + vel.z * vel.z if e.model.isDefined then { - e.model.get.tick(horizontalSpeedSq > 0.1) + e.model.get.tick(horizontalSpeedSq > 0.1, e.headDirection.map(_.direction)) } } diff --git a/client/src/main/scala/hexacraft/client/GameClient.scala b/client/src/main/scala/hexacraft/client/GameClient.scala index c65efc45..a29bd4ba 100644 --- a/client/src/main/scala/hexacraft/client/GameClient.scala +++ b/client/src/main/scala/hexacraft/client/GameClient.scala @@ -570,12 +570,13 @@ class GameClient( val entityEventData = entityEventsNbt.getList("events").get.map(_.asMap.get) val entityEvents = for (id, eventNbt) <- entityEventIds.zip(entityEventData) yield { val event = eventNbt.getString("type").get match { - case "spawned" => EntityEvent.Spawned(eventNbt.getMap("data").get) - case "despawned" => EntityEvent.Despawned - case "position" => EntityEvent.Position(CylCoords(eventNbt.getMap("pos").get.setVector(new Vector3d))) - case "rotation" => EntityEvent.Rotation(eventNbt.getMap("r").get.setVector(new Vector3d)) - case "velocity" => EntityEvent.Velocity(eventNbt.getMap("v").get.setVector(new Vector3d)) - case "flying" => EntityEvent.Flying(eventNbt.getBoolean("f", false)) + case "spawned" => EntityEvent.Spawned(eventNbt.getMap("data").get) + case "despawned" => EntityEvent.Despawned + case "position" => EntityEvent.Position(CylCoords(eventNbt.getMap("pos").get.setVector(new Vector3d))) + case "rotation" => EntityEvent.Rotation(eventNbt.getMap("r").get.setVector(new Vector3d)) + case "velocity" => EntityEvent.Velocity(eventNbt.getMap("v").get.setVector(new Vector3d)) + case "flying" => EntityEvent.Flying(eventNbt.getBoolean("f", false)) + case "head_direction" => EntityEvent.HeadDirection(eventNbt.getMap("d").get.setVector(new Vector3d)) } (UUID.fromString(id), event) diff --git a/game/src/main/scala/hexacraft/world/EntityEvent.scala b/game/src/main/scala/hexacraft/world/EntityEvent.scala index 8b045baf..b41b3f9a 100644 --- a/game/src/main/scala/hexacraft/world/EntityEvent.scala +++ b/game/src/main/scala/hexacraft/world/EntityEvent.scala @@ -12,4 +12,5 @@ enum EntityEvent { case Rotation(v: Vector3d) case Velocity(v: Vector3d) case Flying(f: Boolean) + case HeadDirection(d: Vector3d) } diff --git a/game/src/main/scala/hexacraft/world/entity/base.scala b/game/src/main/scala/hexacraft/world/entity/base.scala index 21ae3829..5e19546e 100644 --- a/game/src/main/scala/hexacraft/world/entity/base.scala +++ b/game/src/main/scala/hexacraft/world/entity/base.scala @@ -27,6 +27,10 @@ class Entity(val id: UUID, val typeName: String, private val components: Seq[Ent .map(_.asInstanceOf[MotionComponent]) .orNull + val headDirection: Option[HeadDirectionComponent] = components + .find(_.isInstanceOf[HeadDirectionComponent]) + .map(_.asInstanceOf[HeadDirectionComponent]) + val boundingBox: HexBox = components .find(_.isInstanceOf[BoundsComponent]) .map(_.asInstanceOf[BoundsComponent].bounds) @@ -72,6 +76,7 @@ object EntityFactory { val components = Seq( TransformComponent.fromNBT(tag), MotionComponent.fromNBT(tag), + HeadDirectionComponent.fromNBT(tag), BoundsComponent(playerBounds), ModelComponent(PlayerEntityModel.create("player")) ) diff --git a/game/src/main/scala/hexacraft/world/entity/components.scala b/game/src/main/scala/hexacraft/world/entity/components.scala index 34df6466..c509c40a 100644 --- a/game/src/main/scala/hexacraft/world/entity/components.scala +++ b/game/src/main/scala/hexacraft/world/entity/components.scala @@ -48,6 +48,18 @@ object MotionComponent { } } +@deprecated("a better solution needs to be made soon") +class HeadDirectionComponent(var direction: Vector3d = new Vector3d) extends EntityComponent + +object HeadDirectionComponent { + def fromNBT(tag: Nbt.MapTag)(using CylinderSize): HeadDirectionComponent = { + val dir = new Vector3d + tag.getMap("head_direction").foreach(_.setVector(dir)) + + HeadDirectionComponent(dir) + } +} + class ModelComponent(val model: EntityModel) extends EntityComponent class AiComponent(val ai: EntityAI) extends EntityComponent diff --git a/game/src/main/scala/hexacraft/world/entity/models.scala b/game/src/main/scala/hexacraft/world/entity/models.scala index 79d3f6b5..4f97bde3 100644 --- a/game/src/main/scala/hexacraft/world/entity/models.scala +++ b/game/src/main/scala/hexacraft/world/entity/models.scala @@ -3,12 +3,12 @@ package hexacraft.world.entity import hexacraft.world.HexBox import hexacraft.world.coord.CylCoords -import org.joml.{Matrix4f, Vector3f} +import org.joml.{Matrix4f, Vector3d, Vector3f} trait EntityModel { def parts: Seq[EntityPart] def textureName: String - def tick(walking: Boolean): Unit + def tick(walking: Boolean, headDirection: Option[Vector3d]): Unit } trait EntityPart { diff --git a/game/src/main/scala/hexacraft/world/entity/player.scala b/game/src/main/scala/hexacraft/world/entity/player.scala index 7354e751..a993fcc6 100644 --- a/game/src/main/scala/hexacraft/world/entity/player.scala +++ b/game/src/main/scala/hexacraft/world/entity/player.scala @@ -3,9 +3,10 @@ package hexacraft.world.entity import hexacraft.world.{CylinderSize, HexBox} import hexacraft.world.coord.BlockCoords -import org.joml.Vector3f +import org.joml.{Vector3d, Vector3f} class PlayerEntityModel( + val headBase: BasicEntityPart, // not rendered val head: BasicEntityPart, val leftBodyHalf: BasicEntityPart, val rightBodyHalf: BasicEntityPart, @@ -19,15 +20,15 @@ class PlayerEntityModel( private val animation = new PlayerAnimation(this) - override def tick(walking: Boolean): Unit = { - animation.tick(walking) + override def tick(walking: Boolean, headDirection: Option[Vector3d]): Unit = { + animation.tick(walking, headDirection.getOrElse(new Vector3d)) } } class PlayerAnimation(model: PlayerEntityModel) { private var time = 0 - def tick(walking: Boolean): Unit = { + def tick(walking: Boolean, headDirection: Vector3d): Unit = { if walking || time % 30 != 0 then { time += 1 } @@ -39,6 +40,8 @@ class PlayerAnimation(model: PlayerEntityModel) { model.rightLeg.rotation.z = 0.5f * math.sin(phase).toFloat model.leftLeg.rotation.z = -0.5f * math.sin(phase).toFloat + + model.headBase.rotation.z = -headDirection.x.toFloat } } @@ -66,7 +69,9 @@ object PlayerEntityModel { val armBounds = makeHexBox(armRadius, -armRadius * CylinderSize.y60.toFloat, armLength) val legBounds = makeHexBox(legRadius, 0, legLength) - val headY = bodyLength + legLength + headRadius * CylinderSize.y60 + val headBaseY = bodyLength + legLength + val headY = headRadius * CylinderSize.y60 + val headBasePos = makePartPosition(0, headBaseY, 0).toCylCoordsOffset val headPos = makePartPosition(0, headY, 0).toCylCoordsOffset val rightBodyPos = makePartPosition(0, legLength, 0.5 * bodyRadius).toCylCoordsOffset @@ -81,8 +86,12 @@ object PlayerEntityModel { val pi = math.Pi.toFloat + val headBase = BasicEntityPart(HexBox(0, 0, 0), headBasePos, Vector3f()) + val head = BasicEntityPart(headBounds, headPos, Vector3f(0, pi / 2, pi / 2), (0, 176), headBase) + PlayerEntityModel( - head = BasicEntityPart(headBounds, headPos, Vector3f(0, pi / 2, pi / 2), (0, 176)), + headBase = headBase, + head = head, leftBodyHalf = BasicEntityPart(bodyBounds, leftBodyPos, Vector3f(0, 0, 0), (0, 120)), rightBodyHalf = BasicEntityPart(bodyBounds, rightBodyPos, Vector3f(0, 0, 0), (48, 120)), rightArm = BasicEntityPart(armBounds, rightArmPos, Vector3f(pi, 0, 0), (48, 64)), diff --git a/game/src/main/scala/hexacraft/world/entity/sheep.scala b/game/src/main/scala/hexacraft/world/entity/sheep.scala index a9b770a8..99a6da6d 100644 --- a/game/src/main/scala/hexacraft/world/entity/sheep.scala +++ b/game/src/main/scala/hexacraft/world/entity/sheep.scala @@ -3,7 +3,7 @@ package hexacraft.world.entity import hexacraft.world.{CylinderSize, HexBox} import hexacraft.world.coord.BlockCoords -import org.joml.Vector3f +import org.joml.{Vector3d, Vector3f} class SheepEntityModel( val head: BasicEntityPart, @@ -18,7 +18,7 @@ class SheepEntityModel( private val animation = new SheepAnimation(this) - override def tick(walking: Boolean): Unit = { + override def tick(walking: Boolean, headDirection: Option[Vector3d]): Unit = { animation.tick(walking) } } diff --git a/server/src/main/scala/hexacraft/server/GameServer.scala b/server/src/main/scala/hexacraft/server/GameServer.scala index 5bde5ca3..3645a3c4 100644 --- a/server/src/main/scala/hexacraft/server/GameServer.scala +++ b/server/src/main/scala/hexacraft/server/GameServer.scala @@ -10,7 +10,7 @@ import hexacraft.world.coord.* import hexacraft.world.entity.* import com.martomate.nbt.Nbt -import org.joml.Vector2f +import org.joml.{Vector2f, Vector3d} import org.zeromq.ZMQException import java.util.UUID @@ -149,6 +149,7 @@ class GameServer( entity.transform.rotation.set(0, math.Pi * 0.5 - player.rotation.y, 0) entity.motion.velocity.set(player.velocity) entity.motion.flying = player.flying + entity.headDirection.foreach(_.direction.set(player.rotation.x, 0, 0)) } val tickResult = chunksLoadedPerPlayer.synchronized { @@ -184,6 +185,9 @@ class GameServer( p.entityEventsWaitingToBeSent += p2.entity.id -> EntityEvent.Rotation(p2.entity.transform.rotation) p.entityEventsWaitingToBeSent += p2.entity.id -> EntityEvent.Velocity(p2.entity.motion.velocity) p.entityEventsWaitingToBeSent += p2.entity.id -> EntityEvent.Flying(p2.entity.motion.flying) + p2.entity.headDirection.foreach { comp => + p.entityEventsWaitingToBeSent += p2.entity.id -> EntityEvent.HeadDirection(comp.direction) + } } } } @@ -385,6 +389,7 @@ class GameServer( Seq( TransformComponent(CylCoords(player.position).offset(-2, -2, -1)), MotionComponent(), + HeadDirectionComponent(Vector3d(player.rotation.x, 0, 0)), BoundsComponent(EntityFactory.playerBounds) ) ) @@ -484,21 +489,23 @@ class GameServer( val events = for (_, e) <- entityEvents yield { val name = e match { - case EntityEvent.Spawned(_) => "spawned" - case EntityEvent.Despawned => "despawned" - case EntityEvent.Position(_) => "position" - case EntityEvent.Rotation(_) => "rotation" - case EntityEvent.Velocity(_) => "velocity" - case EntityEvent.Flying(_) => "flying" + case EntityEvent.Spawned(_) => "spawned" + case EntityEvent.Despawned => "despawned" + case EntityEvent.Position(_) => "position" + case EntityEvent.Rotation(_) => "rotation" + case EntityEvent.Velocity(_) => "velocity" + case EntityEvent.Flying(_) => "flying" + case EntityEvent.HeadDirection(_) => "head_direction" } val extraFields: Seq[(String, Nbt)] = e match { - case EntityEvent.Spawned(data) => Seq("data" -> data) - case EntityEvent.Despawned => Seq() - case EntityEvent.Position(pos) => Seq("pos" -> Nbt.makeVectorTag(pos.toVector3d)) - case EntityEvent.Rotation(r) => Seq("r" -> Nbt.makeVectorTag(r)) - case EntityEvent.Velocity(v) => Seq("v" -> Nbt.makeVectorTag(v)) - case EntityEvent.Flying(f) => Seq("f" -> Nbt.ByteTag(f)) + case EntityEvent.Spawned(data) => Seq("data" -> data) + case EntityEvent.Despawned => Seq() + case EntityEvent.Position(pos) => Seq("pos" -> Nbt.makeVectorTag(pos.toVector3d)) + case EntityEvent.Rotation(r) => Seq("r" -> Nbt.makeVectorTag(r)) + case EntityEvent.Velocity(v) => Seq("v" -> Nbt.makeVectorTag(v)) + case EntityEvent.Flying(f) => Seq("f" -> Nbt.ByteTag(f)) + case EntityEvent.HeadDirection(d) => Seq("d" -> Nbt.makeVectorTag(d)) } Nbt.makeMap(extraFields*).withField("type", Nbt.StringTag(name)) diff --git a/server/src/main/scala/hexacraft/server/ServerWorld.scala b/server/src/main/scala/hexacraft/server/ServerWorld.scala index 79750b88..cf8863ed 100644 --- a/server/src/main/scala/hexacraft/server/ServerWorld.scala +++ b/server/src/main/scala/hexacraft/server/ServerWorld.scala @@ -501,7 +501,7 @@ class ServerWorld(worldProvider: WorldProvider, val worldInfo: WorldInfo) entityPhysicsSystem.update(e.transform, e.motion, e.boundingBox) if e.model.isDefined then { - e.model.get.tick(e.motion.velocity.lengthSquared() > 0.1) + e.model.get.tick(e.motion.velocity.lengthSquared() > 0.1, e.headDirection.map(_.direction)) } }