Skip to content

Commit

Permalink
Refactor: Simplified the entity system even more
Browse files Browse the repository at this point in the history
  • Loading branch information
Martomate committed Dec 29, 2023
1 parent b9496de commit 7c3cdb0
Show file tree
Hide file tree
Showing 10 changed files with 57 additions and 86 deletions.
28 changes: 7 additions & 21 deletions game/src/main/scala/hexacraft/game/GameScene.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,15 @@ import hexacraft.infra.gpu.OpenGL
import hexacraft.infra.window.*
import hexacraft.renderer.*
import hexacraft.util.{ResourceWrapper, TickableTimer, Tracker}
import hexacraft.world.{
Camera,
CameraProjection,
CylinderSize,
HexBox,
Player,
World,
WorldInfo,
WorldProvider,
WorldSettings
}
import hexacraft.world.*
import hexacraft.world.block.{Block, BlockSpecRegistry, BlockState}
import hexacraft.world.coord.{BlockCoords, BlockRelWorld, CoordUtils, CylCoords, NeighborOffsets}
import hexacraft.world.entity.{ControlledPlayerEntity, EntityBaseData, EntityRegistry, PlayerEntityModel}
import hexacraft.world.coord.*
import hexacraft.world.entity.*
import hexacraft.world.render.WorldRenderer

import com.martomate.nbt.Nbt
import org.joml.{Matrix4f, Vector2f, Vector3f}
import org.zeromq.{SocketType, ZContext, ZMonitor, ZMQ, ZMQException}
import org.zeromq.*
import zmq.ZError

import java.nio.charset.Charset
Expand Down Expand Up @@ -267,7 +257,7 @@ class GameScene(
new Renderer(OpenGL.PrimitiveMode.Lines, GpuState.of(OpenGL.State.DepthTest -> false))

private val worldInfo = net.worldProvider.getWorldInfo
private val world = World(net.worldProvider, worldInfo, EntityRegistry())
private val world = World(net.worldProvider, worldInfo)
given CylinderSize = world.size

val player: Player = makePlayer(worldInfo.player)
Expand Down Expand Up @@ -380,13 +370,9 @@ class GameScene(
case KeyboardKey.Digit(digit) =>
if digit > 0 then setSelectedItemSlot(digit - 1)
case KeyboardKey.Letter('P') =>
val startPos = CylCoords(player.position)

world.addEntity(world.entityRegistry.player.atStartPos(startPos))
world.addEntity(PlayerEntity.atStartPos(CylCoords(player.position)))
case KeyboardKey.Letter('L') =>
val startPos = CylCoords(player.position)

world.addEntity(world.entityRegistry.sheep.atStartPos(startPos))
world.addEntity(SheepEntity.atStartPos(CylCoords(player.position)))
case KeyboardKey.Letter('K') =>
world.removeAllEntities()
case _ =>
Expand Down
10 changes: 4 additions & 6 deletions game/src/main/scala/hexacraft/world/World.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import hexacraft.util.*
import hexacraft.world.block.{BlockRepository, BlockState}
import hexacraft.world.chunk.*
import hexacraft.world.coord.*
import hexacraft.world.entity.{Entity, EntityRegistry}
import hexacraft.world.entity.Entity

import com.martomate.nbt.Nbt

Expand All @@ -22,13 +22,11 @@ object World:
case ChunkNeedsRenderUpdate(coords: ChunkRelWorld)
case BlockReplaced(coords: BlockRelWorld, prev: BlockState, now: BlockState)

class World(worldProvider: WorldProvider, worldInfo: WorldInfo, val entityRegistry: EntityRegistry)
extends BlockRepository
with BlocksInWorld:
class World(worldProvider: WorldProvider, worldInfo: WorldInfo) extends BlockRepository with BlocksInWorld:
given size: CylinderSize = worldInfo.worldSize

private val worldGenerator = new WorldGenerator(worldInfo.gen)
private val worldPlanner: WorldPlanner = WorldPlanner(this, entityRegistry, worldInfo.gen.seed)
private val worldPlanner: WorldPlanner = WorldPlanner(this, worldInfo.gen.seed)
private val lightPropagator: LightPropagator = new LightPropagator(this)

val renderDistance: Double = 8 * CylinderSize.y60
Expand All @@ -54,7 +52,7 @@ class World(worldProvider: WorldProvider, worldInfo: WorldInfo, val entityRegist
val chunkFactory = (coords: ChunkRelWorld) =>
val (chunk, isNew) =
worldProvider.loadChunkData(coords) match
case Some(loadedTag) => (Chunk.fromNbt(coords, loadedTag, entityRegistry), false)
case Some(loadedTag) => (Chunk.fromNbt(coords, loadedTag), false)
case None => (Chunk.fromGenerator(coords, this, worldGenerator), true)
savedChunkModCounts(coords) = if isNew then -1L else chunk.modCount
chunk
Expand Down
10 changes: 5 additions & 5 deletions game/src/main/scala/hexacraft/world/WorldPlanner.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package hexacraft.world

import hexacraft.world.chunk.Chunk
import hexacraft.world.entity.EntityRegistry
import hexacraft.world.entity.SheepEntity
import hexacraft.world.gen.{EntityGroupPlanner, TreePlanner, WorldFeaturePlanner}

class WorldPlanner(world: BlocksInWorld, registry: EntityRegistry, mainSeed: Long)(using CylinderSize):
class WorldPlanner(world: BlocksInWorld, mainSeed: Long)(using CylinderSize):
private val planners: Seq[WorldFeaturePlanner] = Seq(
new TreePlanner(world, mainSeed),
new EntityGroupPlanner(world, registry.sheep, mainSeed)
new EntityGroupPlanner(world, SheepEntity.atStartPos, mainSeed)
)

def decorate(chunk: Chunk): Unit =
Expand All @@ -26,5 +26,5 @@ class WorldPlanner(world: BlocksInWorld, registry: EntityRegistry, mainSeed: Lon
case _ =>

object WorldPlanner:
def apply(world: BlocksInWorld, registry: EntityRegistry, mainSeed: Long)(using CylinderSize): WorldPlanner =
new WorldPlanner(world, registry, mainSeed)
def apply(world: BlocksInWorld, mainSeed: Long)(using CylinderSize): WorldPlanner =
new WorldPlanner(world, mainSeed)
14 changes: 7 additions & 7 deletions game/src/main/scala/hexacraft/world/chunk/chunk.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import hexacraft.util.Result.{Err, Ok}
import hexacraft.world.{BlocksInWorld, CollisionDetector, CylinderSize, LightPropagator, WorldGenerator}
import hexacraft.world.block.BlockState
import hexacraft.world.coord.{BlockRelChunk, BlockRelWorld, ChunkRelWorld}
import hexacraft.world.entity.{Entity, EntityRegistry}
import hexacraft.world.entity.{Entity, EntityFactory}

import com.martomate.nbt.Nbt

Expand All @@ -17,8 +17,8 @@ object Chunk:
case ChunkNeedsRenderUpdate(coords: ChunkRelWorld)
case BlockReplaced(coords: BlockRelWorld, prev: BlockState, now: BlockState)

def fromNbt(coords: ChunkRelWorld, loadedTag: Nbt.MapTag, entityRegistry: EntityRegistry)(using CylinderSize): Chunk =
new Chunk(coords, ChunkData.fromNBT(loadedTag)(entityRegistry))
def fromNbt(coords: ChunkRelWorld, loadedTag: Nbt.MapTag)(using CylinderSize): Chunk =
new Chunk(coords, ChunkData.fromNBT(loadedTag))

def fromGenerator(coords: ChunkRelWorld, world: BlocksInWorld, generator: WorldGenerator)(using CylinderSize) =
val column = world.provideColumn(coords.getColumnRelWorld)
Expand Down Expand Up @@ -120,7 +120,7 @@ object ChunkData:
def fromStorage(storage: ChunkStorage)(using CylinderSize): ChunkData =
new ChunkData(storage, mutable.ArrayBuffer.empty)

def fromNBT(nbt: Nbt.MapTag)(registry: EntityRegistry)(using CylinderSize): ChunkData =
def fromNBT(nbt: Nbt.MapTag)(using CylinderSize): ChunkData =
val storage = nbt.getByteArray("blocks") match
case Some(blocks) =>
val meta = nbt.getByteArray("metadata")
Expand All @@ -130,20 +130,20 @@ object ChunkData:

val entities =
nbt.getList("entities") match
case Some(tags) => entitiesFromNbt(tags.map(_.asInstanceOf[Nbt.MapTag]), registry)
case Some(tags) => entitiesFromNbt(tags.map(_.asInstanceOf[Nbt.MapTag]))
case None => Nil

val data = new ChunkData(storage, mutable.ArrayBuffer.from(entities))
data._isDecorated = nbt.getBoolean("isDecorated", default = false)
data

private def entitiesFromNbt(list: Seq[Nbt.MapTag], registry: EntityRegistry)(using
private def entitiesFromNbt(list: Seq[Nbt.MapTag])(using
CylinderSize
): Iterable[Entity] =
val entities = mutable.ArrayBuffer.empty[Entity]

for tag <- list do
Entity.fromNbt(tag, registry) match
EntityFactory.fromNbt(tag) match
case Ok(entity) => entities += entity
case Err(message) => println(message)

Expand Down
17 changes: 9 additions & 8 deletions game/src/main/scala/hexacraft/world/entity/base.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,6 @@ import hexacraft.world.coord.CylCoords
import com.martomate.nbt.Nbt
import org.joml.{Matrix4f, Vector3d}

object Entity {
def fromNbt(tag: Nbt.MapTag, registry: EntityRegistry)(using CylinderSize): Result[Entity, String] =
val entType = tag.getString("type", "")
registry.get(entType) match
case Some(factory) => Ok(factory.fromNBT(tag))
case None => Err(s"Entity-type '$entType' not found")
}

class Entity(
val typeName: String,
protected val data: EntityBaseData,
Expand Down Expand Up @@ -45,6 +37,15 @@ class Entity(
.withOptionalField("ai", ai.map(_.toNBT))
}

object EntityFactory:
def fromNbt(tag: Nbt.MapTag)(using CylinderSize): Result[Entity, String] =
val entType = tag.getString("type", "")

entType match
case "player" => Ok(PlayerEntity.fromNBT(tag))
case "sheep" => Ok(SheepEntity.fromNBT(tag))
case _ => Err(s"Entity-type '$entType' not found")

class EntityBaseData(
var position: CylCoords,
var rotation: Vector3d = new Vector3d,
Expand Down
8 changes: 5 additions & 3 deletions game/src/main/scala/hexacraft/world/entity/player.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ class ControlledPlayerEntity(model: EntityModel, initData: EntityBaseData) exten
def setPosition(pos: CylCoords): Unit = this.data.position = pos
}

class PlayerFactory(makeModel: () => EntityModel) extends EntityFactory:
override def atStartPos(pos: CylCoords)(using CylinderSize): PlayerEntity =
object PlayerEntity:
private def makeModel(): EntityModel = PlayerEntityModel.create("player")

def atStartPos(pos: CylCoords)(using CylinderSize): PlayerEntity =
val model = makeModel()
new PlayerEntity(model, new EntityBaseData(position = pos), SimpleWalkAI.create)

override def fromNBT(tag: Nbt.MapTag)(using CylinderSize): PlayerEntity =
def fromNBT(tag: Nbt.MapTag)(using CylinderSize): PlayerEntity =
val model = makeModel()
val baseData = EntityBaseData.fromNBT(tag)
val ai: EntityAI =
Expand Down
22 changes: 0 additions & 22 deletions game/src/main/scala/hexacraft/world/entity/registry.scala

This file was deleted.

8 changes: 5 additions & 3 deletions game/src/main/scala/hexacraft/world/entity/sheep.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ class SheepEntity(
}
}

class SheepFactory(makeModel: () => EntityModel) extends EntityFactory:
override def atStartPos(pos: CylCoords)(using CylinderSize): SheepEntity =
object SheepEntity:
private def makeModel(): EntityModel = SheepEntityModel.create("sheep")

def atStartPos(pos: CylCoords)(using CylinderSize): SheepEntity =
val model = makeModel()
new SheepEntity(model, new EntityBaseData(position = pos), SimpleWalkAI.create)

override def fromNBT(tag: Nbt.MapTag)(using CylinderSize): SheepEntity =
def fromNBT(tag: Nbt.MapTag)(using CylinderSize): SheepEntity =
val model = makeModel()
val baseData = EntityBaseData.fromNBT(tag)
val ai: EntityAI =
Expand Down
14 changes: 9 additions & 5 deletions game/src/main/scala/hexacraft/world/gen/planners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package hexacraft.world.gen
import hexacraft.world.{BlocksInWorld, CylinderSize}
import hexacraft.world.block.{Block, BlockState}
import hexacraft.world.chunk.{Chunk, ChunkColumnTerrain, LocalBlockState}
import hexacraft.world.coord.{BlockCoords, BlockRelWorld, ChunkRelWorld}
import hexacraft.world.coord.{BlockCoords, BlockRelWorld, ChunkRelWorld, CylCoords}
import hexacraft.world.entity.{Entity, EntityFactory}
import hexacraft.world.gen.tree.{HugeTreeGenStrategy, ShortTreeGenStrategy, TallTreeGenStrategy, TreeGenStrategy}

Expand Down Expand Up @@ -101,9 +101,13 @@ class TreePlanner(world: BlocksInWorld, mainSeed: Long)(using cylSize: CylinderS
private def generateChanges(tree: PlannedWorldChange): Unit =
for (c, ch) <- tree.chunkChanges do plannedChanges.getOrElseUpdate(c, mutable.Buffer.empty).appendAll(ch)

class EntityGroupPlanner(world: BlocksInWorld, entityFactory: EntityFactory, mainSeed: Long)(using
CylinderSize
) extends WorldFeaturePlanner:
class EntityGroupPlanner(
world: BlocksInWorld,
entityFactory: CylCoords => Entity,
mainSeed: Long
)(using CylinderSize)
extends WorldFeaturePlanner:

private val plannedEntities: mutable.Map[ChunkRelWorld, Seq[Entity]] = mutable.Map.empty
private val chunksPlanned: mutable.Set[ChunkRelWorld] = mutable.Set.empty

Expand Down Expand Up @@ -132,6 +136,6 @@ class EntityGroupPlanner(world: BlocksInWorld, entityFactory: EntityFactory, mai
if y >= coords.Y.toInt * 16 && y < (coords.Y.toInt + 1) * 16 then
val groundCoords = BlockCoords(BlockRelWorld(cx, y & 15, cz, coords)).toCylCoords
val entityStartPos = groundCoords.offset(0, 0.001f, 0)
val entity = entityFactory.atStartPos(entityStartPos)
val entity = entityFactory(entityStartPos)
thePlan += entity
thePlan.toSeq
12 changes: 6 additions & 6 deletions game/src/test/scala/hexacraft/world/WorldTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package hexacraft.world
import hexacraft.world.block.{Block, BlockState}
import hexacraft.world.chunk.Chunk
import hexacraft.world.coord.{BlockCoords, BlockRelWorld, ChunkRelWorld, CylCoords}
import hexacraft.world.entity.{Entity, EntityBaseData, EntityRegistry}
import hexacraft.world.entity.{Entity, EntityBaseData}

import munit.FunSuite

Expand All @@ -12,7 +12,7 @@ class WorldTest extends FunSuite {

test("the world should not crash") {
val provider = new FakeWorldProvider(1234)
val world = World(provider, provider.getWorldInfo, EntityRegistry())
val world = World(provider, provider.getWorldInfo)
val camera = new Camera(new CameraProjection(70, 1.6f, 0.01f, 1000f))

world.tick(camera)
Expand All @@ -37,7 +37,7 @@ class WorldTest extends FunSuite {

test("the world should decorate new chunks") {
val provider = new FakeWorldProvider(1234)
val world = World(provider, provider.getWorldInfo, EntityRegistry())
val world = World(provider, provider.getWorldInfo)

val chunkCoords = ChunkRelWorld(3, -1, -4) // this chunk contains the ground

Expand All @@ -57,7 +57,7 @@ class WorldTest extends FunSuite {

test("the world should load chunks close to the camera") {
val provider = new FakeWorldProvider(1234)
val world = World(provider, provider.getWorldInfo, EntityRegistry())
val world = World(provider, provider.getWorldInfo)
val camera = new Camera(new CameraProjection(70, 1.6f, 0.01f, 1000f))

val cCoords = ChunkRelWorld(3, 7, -4)
Expand All @@ -80,7 +80,7 @@ class WorldTest extends FunSuite {

test("the world should unload chunks far from the camera") {
val provider = new FakeWorldProvider(1234)
val world = World(provider, provider.getWorldInfo, EntityRegistry())
val world = World(provider, provider.getWorldInfo)
val camera = new Camera(new CameraProjection(70, 1.6f, 0.01f, 1000f))

val cCoords = ChunkRelWorld(3, 7, -4)
Expand Down Expand Up @@ -111,7 +111,7 @@ class WorldTest extends FunSuite {

test("the world should allow entities to be added to and removed from a loaded chunk") {
val provider = new FakeWorldProvider(1234)
val world = World(provider, provider.getWorldInfo, EntityRegistry())
val world = World(provider, provider.getWorldInfo)
val camera = new Camera(new CameraProjection(70, 1.6f, 0.01f, 1000f))

val entityPosition = CylCoords(1, 2, 3)
Expand Down

0 comments on commit 7c3cdb0

Please sign in to comment.