• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Martomate / Hexacraft / 7351367576

28 Dec 2023 06:50PM UTC coverage: 51.185% (-0.1%) from 51.312%
7351367576

push

github

Martomate
Refactor: Made Chunk not know if it has been saved to file

9 of 12 new or added lines in 2 files covered. (75.0%)

110 existing lines in 32 files now uncovered.

2829 of 5527 relevant lines covered (51.19%)

0.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

28.48
/game/src/main/scala/hexacraft/game/GameScene.scala
1
package hexacraft.game
2

3
import hexacraft.game.NetworkPacket.{GetState, GetWorldInfo}
4
import hexacraft.gui.*
5
import hexacraft.gui.comp.{Component, GUITransformation}
6
import hexacraft.infra.fs.BlockTextureLoader
7
import hexacraft.infra.gpu.OpenGL
8
import hexacraft.infra.window.*
9
import hexacraft.renderer.*
10
import hexacraft.util.{ResourceWrapper, TickableTimer, Tracker}
11
import hexacraft.world.{
12
  Camera,
13
  CameraProjection,
14
  CylinderSize,
15
  HexBox,
16
  Player,
17
  World,
18
  WorldInfo,
19
  WorldProvider,
20
  WorldSettings
21
}
22
import hexacraft.world.block.{Block, BlockSpecRegistry, BlockState}
23
import hexacraft.world.coord.{BlockCoords, BlockRelWorld, CoordUtils, CylCoords, NeighborOffsets}
24
import hexacraft.world.entity.{ControlledPlayerEntity, EntityBaseData, EntityModel, EntityModelLoader}
25
import hexacraft.world.render.WorldRenderer
26

27
import com.martomate.nbt.Nbt
28
import org.joml.{Matrix4f, Vector2f, Vector3f}
29
import org.zeromq.{SocketType, ZContext, ZMQ, ZMQException}
30
import zmq.ZError
31

32
import java.nio.charset.Charset
33
import scala.collection.mutable
34
import scala.collection.mutable.ArrayBuffer
35

36
enum NetworkPacket {
37
  case GetWorldInfo
38
  case GetState(path: String)
39
}
40

41
object NetworkPacket {
×
42
  def deserialize(bytes: Array[Byte], charset: Charset): NetworkPacket =
×
43
    val message = String(bytes, charset)
×
44
    if message == "get_world_info" then NetworkPacket.GetWorldInfo
×
45
    else if message.startsWith("get_state ") then
×
46
      val path = message.substring(10)
47
      NetworkPacket.GetState(path)
×
48
    else throw new IllegalArgumentException("unknown packet type")
49

50
  extension (p: NetworkPacket) {
×
51
    def serialize(charset: Charset): Array[Byte] =
52
      val str = p match
×
53
        case NetworkPacket.GetWorldInfo   => "get_world_info"
×
54
        case NetworkPacket.GetState(path) => s"get_state $path"
×
55
      str.getBytes(charset)
56
  }
57
}
58

59
class RemoteWorldProvider(client: GameClient) extends WorldProvider {
×
60
  override def getWorldInfo: WorldInfo =
×
61
    val tag = client.query(GetWorldInfo)
×
62
    WorldInfo.fromNBT(tag.asInstanceOf[Nbt.MapTag], null, WorldSettings.none)
63

×
64
  override def loadState(path: String): Nbt.MapTag =
×
65
    val tag = client.query(GetState(path))
66
    tag.asInstanceOf[Nbt.MapTag]
67

×
68
  override def saveState(tag: Nbt.MapTag, name: String, path: String): Unit =
69
    // throw new UnsupportedOperationException()
70
    ()
71
}
72

73
class GameClient(serverIp: String, serverPort: Int) {
×
74
  def notify(message: String): Unit =
×
75
    val context = ZContext()
×
76
    try {
×
77
      val socket = context.createSocket(SocketType.REQ)
×
78
      socket.connect(s"tcp://$serverIp:$serverPort")
79

×
80
      if !socket.send(message.getBytes(ZMQ.CHARSET)) then
×
81
        val err = socket.errno()
×
82
        throw new IllegalStateException(s"Could not send message. Error: $err")
×
83
      socket.close()
×
84
    } finally context.close()
85

×
86
  private def queryRaw(message: Array[Byte]): Array[Byte] =
×
87
    val context = ZContext()
×
88
    try {
×
89
      val socket = context.createSocket(SocketType.REQ)
×
90
      socket.connect(s"tcp://$serverIp:$serverPort")
91

×
92
      if !socket.send(message) then
×
93
        val err = socket.errno()
×
94
        throw new IllegalStateException(s"Could not send message. Error: $err")
95

×
96
      val response = socket.recv(0)
97
      if response == null then
×
98
        val err = socket.errno()
×
99
        throw new IllegalStateException(s"Could not receive message. Error: $err")
×
100
      socket.close()
101

102
      response
×
103
    } finally context.close()
104

×
105
  def query(packet: NetworkPacket): Nbt =
×
106
    val response = queryRaw(packet.serialize(ZMQ.CHARSET))
×
107
    val (_, tag) = Nbt.fromBinary(response)
108
    tag
109
}
110

111
class GameServer(worldProvider: WorldProvider) {
112
  private var serverThread: Thread = _
113

×
114
  def run(): Unit =
×
115
    if serverThread != null then throw new RuntimeException("You may only start the server once")
×
116
    serverThread = Thread.currentThread()
117

×
118
    try {
×
119
      val context = ZContext()
×
120
      try {
×
121
        val serverSocket = context.createSocket(SocketType.REP)
122
        val serverPort = 1234
×
123
        if !serverSocket.bind(s"tcp://*:$serverPort") then throw new IllegalStateException("Server could not be bound")
×
124
        println(s"Running server on port $serverPort")
125

×
126
        while !Thread.currentThread().isInterrupted do
×
127
          val bytes = serverSocket.recv(0)
×
128
          if bytes == null then throw new ZMQException(serverSocket.errno())
129

×
130
          val packet = NetworkPacket.deserialize(bytes, ZMQ.CHARSET)
×
131
          println(s"Received message: $packet")
×
132
          handlePacket(packet, serverSocket)
×
133
      } finally context.close()
134
    } catch {
×
135
      case e: ZMQException =>
×
136
        e.getErrorCode match
×
137
          case ZError.EINTR => // noop
×
138
          case _            => throw e
×
139
      case e => throw e
140
    }
141

×
142
    println(s"Stopping server")
143

×
144
  private def handlePacket(packet: NetworkPacket, socket: ZMQ.Socket): Unit =
145
    packet match
×
146
      case NetworkPacket.GetWorldInfo =>
×
147
        val info = worldProvider.getWorldInfo
×
148
        socket.send(info.toNBT.toBinary())
×
149
      case NetworkPacket.GetState(path) =>
×
150
        val state = worldProvider.loadState(path)
×
151
        socket.send(state.toBinary())
152

×
153
  def stop(): Unit =
×
154
    if serverThread != null then serverThread.interrupt()
155
}
156

157
class NetworkHandler(val isHosting: Boolean, isOnline: Boolean, val worldProvider: WorldProvider) {
158
  private var server: GameServer = _
159

1✔
160
  def runServer(): Unit = if isOnline then
×
161
    server = GameServer(worldProvider)
×
162
    server.run()
163

1✔
164
  def unload(): Unit =
×
165
    if server != null then server.stop()
166
}
167

168
object GameScene {
169
  enum Event:
170
    case GameQuit
171
    case CursorCaptured
172
    case CursorReleased
173
}
174

175
class GameScene(
176
    net: NetworkHandler,
177
    keyboard: GameKeyboard,
178
    blockLoader: BlockTextureLoader,
179
    initialWindowSize: WindowSize
180
)(eventHandler: Tracker[GameScene.Event])
181
    extends Scene:
182

1✔
183
  if net.isHosting then Thread(() => net.runServer()).start()
184

1✔
185
  TextureArray.registerTextureArray("blocks", 32, new ResourceWrapper(() => blockLoader.reload().images))
186

1✔
187
  private val blockSpecRegistry = BlockSpecRegistry.load(blockLoader.textureMapping)
188

1✔
189
  private val crosshairShader = new CrosshairShader()
1✔
190
  private val crosshairVAO: VAO = makeCrosshairVAO
191
  private val crosshairRenderer: Renderer =
1✔
192
    new Renderer(OpenGL.PrimitiveMode.Lines, GpuState.of(OpenGL.State.DepthTest -> false))
193

1✔
194
  private val entityModelLoader: EntityModelLoader = new EntityModelLoader()
1✔
195
  private val playerModel: EntityModel = entityModelLoader.load("player")
1✔
196
  private val sheepModel: EntityModel = entityModelLoader.load("sheep")
197

1✔
198
  private val worldInfo = net.worldProvider.getWorldInfo
1✔
199
  private val world = World(net.worldProvider, worldInfo, entityModelLoader)
200
  given CylinderSize = world.size
201

1✔
202
  val player: Player = makePlayer(worldInfo.player)
203

1✔
204
  private val overlays = mutable.ArrayBuffer.empty[Component]
205

206
  private val otherPlayer: ControlledPlayerEntity =
1✔
207
    new ControlledPlayerEntity(playerModel, new EntityBaseData(CylCoords(player.position)))
208

1✔
UNCOV
209
  private val worldRenderer: WorldRenderer = new WorldRenderer(world, blockSpecRegistry, initialWindowSize.physicalSize)
×
210
  world.trackEvents(worldRenderer.onWorldEvent _)
211

212
  // worldRenderer.addPlayer(otherPlayer)
1✔
213
  otherPlayer.setPosition(otherPlayer.position.offset(-2, -2, -1))
214

1✔
215
  val camera: Camera = new Camera(makeCameraProjection(initialWindowSize))
216

1✔
217
  private val mousePicker: RayTracer = new RayTracer(camera, 7)
1✔
218
  private val playerInputHandler: PlayerInputHandler = new PlayerInputHandler(keyboard, player)
219
  private val playerPhysicsHandler: PlayerPhysicsHandler =
1✔
220
    new PlayerPhysicsHandler(player, world, world.collisionDetector)
221

222
  private var selectedBlockAndSide: Option[(BlockState, BlockRelWorld, Option[Int])] = None
223

1✔
224
  private val toolbar: Toolbar = makeToolbar(player, initialWindowSize)
1✔
225
  private val blockInHandRenderer: GuiBlockRenderer = makeBlockInHandRenderer(world, camera)
1✔
226
  updateBlockInHandRendererContent()
227

1✔
228
  private val rightMouseButtonTimer: TickableTimer = TickableTimer(10, initEnabled = false)
1✔
229
  private val leftMouseButtonTimer: TickableTimer = TickableTimer(10, initEnabled = false)
230

231
  private var moveWithMouse: Boolean = false
232
  private var isPaused: Boolean = false
233
  private var isInPopup: Boolean = false
234

235
  private var debugOverlay: DebugOverlay = _
236
  private var pauseMenu: PauseMenu = _
237
  private var inventoryScene: InventoryBox = _
238

1✔
239
  setUniforms(initialWindowSize.logicalAspectRatio)
1✔
240
  setUseMouse(true)
241

1✔
242
  saveWorldInfo()
243

1✔
244
  private def saveWorldInfo(): Unit =
1✔
245
    val worldTag = worldInfo.copy(player = player.toNBT).toNBT
1✔
246
    if net.isHosting then net.worldProvider.saveWorldData(worldTag)
247

×
248
  override def onReloadedResources(windowSize: WindowSize): Unit =
×
249
    for s <- overlays do s.onReloadedResources(windowSize)
×
250
    setUniforms(windowSize.logicalAspectRatio)
×
251
    world.onReloadedResources()
252

1✔
253
  private def setUniforms(windowAspectRatio: Float): Unit =
1✔
254
    setProjMatrixForAll()
1✔
255
    worldRenderer.onTotalSizeChanged(world.size.totalSize)
1✔
256
    crosshairShader.setWindowAspectRatio(windowAspectRatio)
257

1✔
258
  private def setProjMatrixForAll(): Unit =
1✔
259
    worldRenderer.onProjMatrixChanged(camera)
260

1✔
261
  private def handleKeyPress(key: KeyboardKey): Unit = key match
×
262
    case KeyboardKey.Letter('B') =>
×
263
      val newCoords = camera.blockCoords.offset(0, -4, 0)
264

×
265
      if world.getBlock(newCoords).blockType == Block.Air
×
266
      then world.setBlock(newCoords, new BlockState(player.blockInHand))
1✔
267
    case KeyboardKey.Escape =>
268
      import PauseMenu.Event.*
269

270
      pauseMenu = PauseMenu:
1✔
271
        case Unpause =>
×
272
          overlays -= pauseMenu
×
273
          pauseMenu.unload()
274
          pauseMenu = null
×
275
          setPaused(false)
1✔
276
        case QuitGame => eventHandler.notify(GameScene.Event.GameQuit)
277

1✔
278
      overlays += pauseMenu
1✔
279
      setPaused(true)
×
280
    case KeyboardKey.Letter('E') =>
281
      import InventoryBox.Event.*
282

283
      if !isPaused
284
      then
×
285
        setUseMouse(false)
286
        isInPopup = true
287

288
        inventoryScene = InventoryBox(player.inventory, blockSpecRegistry):
×
289
          case BoxClosed =>
×
290
            overlays -= inventoryScene
×
291
            inventoryScene.unload()
292
            inventoryScene = null
293
            isInPopup = false
×
294
            setUseMouse(true)
×
295
          case InventoryUpdated(inv) =>
296
            player.inventory = inv
×
297
            toolbar.onInventoryUpdated(inv)
298

×
299
        overlays += inventoryScene
×
300
    case KeyboardKey.Letter('M') =>
×
301
      setUseMouse(!moveWithMouse)
×
302
    case KeyboardKey.Letter('F') =>
303
      player.flying = !player.flying
×
304
    case KeyboardKey.Function(7) =>
×
305
      setDebugScreenVisible(debugOverlay == null)
×
306
    case KeyboardKey.Digit(digit) =>
×
307
      if digit > 0 then setSelectedItemSlot(digit - 1)
×
308
    case KeyboardKey.Letter('P') =>
×
309
      val startPos = CylCoords(player.position)
310

×
311
      world.addEntity(world.entityRegistry.get("player").get.atStartPos(startPos))
×
312
    case KeyboardKey.Letter('L') =>
×
313
      val startPos = CylCoords(player.position)
314

×
315
      world.addEntity(world.entityRegistry.get("sheep").get.atStartPos(startPos))
×
316
    case KeyboardKey.Letter('K') =>
×
317
      world.removeAllEntities()
×
318
    case _ =>
319

×
320
  private def setDebugScreenVisible(visible: Boolean): Unit =
321
    if visible
322
    then
×
323
      if debugOverlay == null
×
324
      then debugOverlay = new DebugOverlay
325
    else
×
326
      if debugOverlay != null
×
327
      then debugOverlay.unload()
328
      debugOverlay = null
329

1✔
330
  private def setUseMouse(useMouse: Boolean): Unit =
331
    moveWithMouse = useMouse
1✔
332
    setMouseCursorInvisible(moveWithMouse)
333

1✔
334
  override def handleEvent(event: Event): Boolean =
1✔
335
    if overlays.reverseIterator.exists(_.handleEvent(event))
1✔
336
    then true
337
    else
1✔
338
      import Event.*
339
      event match
1✔
340
        case KeyEvent(key, _, action, _) =>
1✔
341
          if action == KeyAction.Press then handleKeyPress(key)
×
342
        case ScrollEvent(_, yOffset, _) if !isPaused && !isInPopup && moveWithMouse =>
×
343
          val dy = -math.signum(yOffset).toInt
×
344
          if dy != 0 then setSelectedItemSlot((player.selectedItemSlot + dy + 9) % 9)
×
345
        case MouseClickEvent(button, action, _, _) =>
346
          button match
×
347
            case MouseButton.Left  => leftMouseButtonTimer.enabled = action != MouseAction.Release
×
348
            case MouseButton.Right => rightMouseButtonTimer.enabled = action != MouseAction.Release
×
349
            case _                 =>
×
350
        case _ =>
351
      true
352

×
353
  private def setSelectedItemSlot(itemSlot: Int): Unit =
354
    player.selectedItemSlot = itemSlot
×
355
    updateBlockInHandRendererContent()
×
356
    toolbar.setSelectedIndex(itemSlot)
357

1✔
358
  private def updateBlockInHandRendererContent(): Unit =
1✔
359
    blockInHandRenderer.updateContent(1.5f, -0.9f, Seq(player.blockInHand))
360

1✔
361
  private def setPaused(paused: Boolean): Unit =
362
    if isPaused != paused
363
    then
1✔
364
      isPaused = paused
1✔
365
      setMouseCursorInvisible(!paused && moveWithMouse)
366

1✔
367
  private def setMouseCursorInvisible(invisible: Boolean): Unit =
368
    import GameScene.Event.*
1✔
369
    eventHandler.notify(if invisible then CursorCaptured else CursorReleased)
370

×
371
  override def windowResized(width: Int, height: Int): Unit =
372
    val aspectRatio = width.toFloat / height
373
    camera.proj.aspect = aspectRatio
×
374
    camera.updateProjMatrix()
375

×
376
    setProjMatrixForAll()
×
377
    blockInHandRenderer.setWindowAspectRatio(aspectRatio)
×
378
    toolbar.setWindowAspectRatio(aspectRatio)
379

×
380
    crosshairShader.setWindowAspectRatio(aspectRatio)
381

×
382
  override def framebufferResized(width: Int, height: Int): Unit =
×
383
    worldRenderer.framebufferResized(width, height)
384

×
385
  override def render(transformation: GUITransformation)(using RenderContext): Unit =
×
386
    worldRenderer.render(camera, new Vector3f(0, 1, -1), selectedBlockAndSide)
387

×
388
    renderCrosshair()
389

×
390
    blockInHandRenderer.render(transformation)
×
391
    toolbar.render(transformation)
392

393
    if debugOverlay != null
394
    then debugOverlay.render(transformation)
395

×
396
    for s <- overlays do s.render(transformation)
397

×
398
  private def renderCrosshair(): Unit =
399
    if !isPaused && !isInPopup && moveWithMouse
400
    then
×
401
      crosshairShader.enable()
×
402
      crosshairRenderer.render(crosshairVAO)
403

×
404
  private def playerEffectiveViscosity: Double =
×
405
    player.bounds
×
406
      .cover(CylCoords(player.position))
×
407
      .map(c => c -> world.getBlock(c))
×
408
      .filter(!_._2.blockType.isSolid)
×
409
      .map((c, b) =>
×
410
        HexBox.approximateVolumeOfIntersection(
×
411
          BlockCoords(c).toCylCoords,
×
412
          b.blockType.bounds(b.metadata),
×
413
          CylCoords(player.position),
414
          player.bounds
×
415
        ) * b.blockType.viscosity.toSI
416
      )
417
      .sum
418

×
419
  private def playerVolumeSubmergedInWater: Double =
×
420
    val solidBounds = player.bounds.scaledRadially(0.7)
×
421
    solidBounds
×
422
      .cover(CylCoords(player.position))
×
423
      .map(c => c -> world.getBlock(c))
×
424
      .filter((c, b) => b.blockType == Block.Water)
×
425
      .map((c, b) =>
×
426
        HexBox.approximateVolumeOfIntersection(
×
427
          BlockCoords(c).toCylCoords,
×
428
          b.blockType.bounds(b.metadata),
×
429
          CylCoords(player.position),
430
          solidBounds
431
        )
432
      )
433
      .sum
434

×
435
  override def tick(ctx: TickContext): Unit =
×
436
    val playerCoords = CoordUtils.approximateIntCoords(CylCoords(player.position).toBlockCoords)
×
437
    if world.getChunk(playerCoords.getChunkRelWorld).isDefined
438
    then
×
439
      val maxSpeed = playerInputHandler.maxSpeed
440
      if !isPaused && !isInPopup then
×
441
        playerInputHandler.tick(
×
442
          if moveWithMouse then ctx.mouseMovement else new Vector2f,
443
          maxSpeed,
×
444
          playerEffectiveViscosity > Block.Air.viscosity.toSI * 2
445
        )
×
446
      if !isPaused then playerPhysicsHandler.tick(maxSpeed, playerEffectiveViscosity, playerVolumeSubmergedInWater)
447

×
448
    camera.setPositionAndRotation(player.position, player.rotation)
×
449
    camera.updateCoords()
×
450
    camera.updateViewMatrix()
451

×
452
    updateBlockInHandRendererContent()
453

×
454
    selectedBlockAndSide = updatedMousePicker(ctx.windowSize, ctx.currentMousePosition)
455

×
456
    if (rightMouseButtonTimer.tick()) {
×
457
      performRightMouseClick()
458
    }
×
459
    if (leftMouseButtonTimer.tick()) {
×
460
      performLeftMouseClick()
461
    }
462

×
463
    world.tick(camera)
×
464
    worldRenderer.tick(camera, world.renderDistance)
465

466
    if debugOverlay != null
467
    then
×
468
      debugOverlay.updateContent(
×
469
        DebugOverlay.Content.fromCamera(
470
          camera,
×
471
          viewDistance,
×
472
          worldRenderer.regularChunkBufferFragmentation,
×
473
          worldRenderer.transmissiveChunkBufferFragmentation
474
        )
475
      )
476

×
477
    for s <- overlays do s.tick(ctx)
478

×
479
  private def updatedMousePicker(
480
      windowSize: WindowSize,
481
      mouse: MousePosition
482
  ): Option[(BlockState, BlockRelWorld, Option[Int])] =
483
    if isPaused || isInPopup
×
484
    then None
485
    else
×
486
      val screenCoords =
487
        if moveWithMouse
488
        then new Vector2f(0, 0)
×
489
        else mouse.normalizedScreenCoords(windowSize.logicalSize)
490

491
      // TODO: make it possible to place water on top of a water block (maybe by performing an extra ray trace)
×
492
      for
×
493
        ray <- Ray.fromScreen(camera, screenCoords)
×
494
        hit <- mousePicker.trace(ray, c => Some(world.getBlock(c)).filter(_.blockType.isSolid))
×
495
      yield (world.getBlock(hit._1), hit._1, hit._2)
496

×
497
  private def performLeftMouseClick(): Unit =
498
    selectedBlockAndSide match
×
499
      case Some((state, coords, _)) =>
500
        if state.blockType != Block.Air
×
501
        then world.removeBlock(coords)
×
502
      case _ =>
503

×
504
  private def performRightMouseClick(): Unit =
505
    selectedBlockAndSide match
×
506
      case Some((state, coords, Some(side))) =>
×
507
        val coordsInFront = coords.offset(NeighborOffsets(side))
508

509
        state.blockType match
×
510
          case Block.Tnt => explode(coords)
×
511
          case _         => tryPlacingBlockAt(coordsInFront)
×
512
      case _ =>
513

×
514
  private def tryPlacingBlockAt(coords: BlockRelWorld): Unit =
×
515
    if !world.getBlock(coords).blockType.isSolid
516
    then
×
517
      val blockType = player.blockInHand
×
518
      val state = new BlockState(blockType)
519

×
520
      val collides = world.collisionDetector.collides(
×
521
        blockType.bounds(state.metadata),
×
522
        BlockCoords(coords).toCylCoords,
523
        player.bounds,
×
524
        CylCoords(camera.position)
525
      )
526

527
      if !collides
×
528
      then world.setBlock(coords, state)
529

×
530
  private def explode(coords: BlockRelWorld): Unit =
×
531
    for dy <- -1 to 1 do
×
532
      for offset <- NeighborOffsets.all do
×
533
        val c = coords.offset(offset).offset(0, dy, 0)
×
534
        world.setBlock(c, BlockState.Air)
535

×
536
    world.setBlock(coords, BlockState.Air)
537

1✔
538
  override def unload(): Unit =
1✔
539
    setMouseCursorInvisible(false)
540

1✔
541
    saveWorldInfo()
542

×
543
    for s <- overlays do s.unload()
544

1✔
545
    world.unload()
1✔
546
    worldRenderer.unload()
1✔
547
    crosshairVAO.free()
1✔
548
    crosshairShader.free()
1✔
549
    toolbar.unload()
1✔
550
    blockInHandRenderer.unload()
551

552
    if debugOverlay != null
×
553
    then debugOverlay.unload()
554

1✔
555
    net.unload()
556

×
557
  private def viewDistance: Double = world.renderDistance
558

1✔
559
  private def makeBlockInHandRenderer(world: World, camera: Camera): GuiBlockRenderer =
1✔
560
    val renderer = GuiBlockRenderer(1, 1)(blockSpecRegistry)
1✔
561
    renderer.setViewMatrix(makeBlockInHandViewMatrix)
1✔
562
    renderer.setWindowAspectRatio(initialWindowSize.logicalAspectRatio)
563
    renderer
564

1✔
565
  private def makeCrosshairVAO: VAO = VAO
1✔
566
    .builder()
1✔
567
    .addVertexVbo(4)(
1✔
UNCOV
568
      _.floats(0, 2),
×
569
      _.fillFloats(0, Seq(0, 0.03f, 0, -0.03f, -0.03f, 0, 0.03f, 0))
570
    )
1✔
571
    .finish(4)
572

1✔
573
  private def makeToolbar(player: Player, windowSize: WindowSize): Toolbar =
574
    val location = LocationInfo(-4.5f * 0.2f, -0.83f - 0.095f, 2 * 0.9f, 2 * 0.095f)
575

1✔
576
    val toolbar = new Toolbar(location, player.inventory)(blockSpecRegistry)
1✔
577
    toolbar.setSelectedIndex(player.selectedItemSlot)
1✔
578
    toolbar.setWindowAspectRatio(windowSize.logicalAspectRatio)
579
    toolbar
580

1✔
581
  private def makeCameraProjection(windowSize: WindowSize) =
582
    val far = world.size.worldSize match
×
583
      case 0 => 100000f
×
584
      case 1 => 10000f
1✔
585
      case _ => 1000f
586

1✔
587
    new CameraProjection(70f, windowSize.logicalAspectRatio, 0.02f, far)
588

1✔
589
  private def makeBlockInHandViewMatrix =
1✔
590
    new Matrix4f()
1✔
591
      .translate(0, 0, -2f)
1✔
592
      .rotateZ(-3.1415f / 12)
1✔
593
      .rotateX(3.1415f / 6)
1✔
594
      .translate(0, -0.25f, 0)
595

1✔
596
  private def makePlayer(playerNbt: Nbt.MapTag): Player =
597
    if playerNbt != null
1✔
598
    then Player.fromNBT(playerNbt)
599
    else
×
600
      val startX = (math.random() * 100 - 50).toInt
×
601
      val startZ = (math.random() * 100 - 50).toInt
×
602
      val startY = world.getHeight(startX, startZ) + 4
×
603
      Player.atStartPos(BlockCoords(startX, startY, startZ).toCylCoords)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc