Testing out JmonkeyEngine to make a game in Scala with Akka Actors within a pure FP layer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

613 lines
21 KiB

4 years ago
3 years ago
3 years ago
3 years ago
4 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
3 years ago
4 years ago
3 years ago
3 years ago
4 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
3 years ago
3 years ago
4 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
3 years ago
3 years ago
4 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
  1. package wow.doge.mygame
  2. import java.util.concurrent.TimeoutException
  3. import scala.annotation.switch
  4. import scala.concurrent.duration._
  5. import akka.actor.typed.ActorRef
  6. import akka.actor.typed.SpawnProtocol
  7. import akka.util.Timeout
  8. import cats.effect.Resource
  9. import cats.effect.concurrent.Deferred
  10. import cats.syntax.eq._
  11. import cats.syntax.show._
  12. import com.jayfella.jme.jfx.JavaFxUI
  13. import com.jme3.asset.plugins.ZipLocator
  14. import com.jme3.bullet.control.BetterCharacterControl
  15. import com.jme3.input.InputManager
  16. import com.jme3.material.Material
  17. import com.jme3.material.MaterialDef
  18. import com.jme3.math.ColorRGBA
  19. import com.jme3.math.FastMath
  20. import com.jme3.math.Quaternion
  21. import com.jme3.math.Vector3f
  22. import com.jme3.renderer.Camera
  23. import com.jme3.renderer.ViewPort
  24. import com.jme3.scene.Node
  25. import com.softwaremill.macwire._
  26. import com.softwaremill.tagging._
  27. import io.odin.Logger
  28. import monix.bio.Fiber
  29. import monix.bio.IO
  30. import monix.bio.Task
  31. import monix.bio.UIO
  32. import monix.eval.Coeval
  33. import monix.execution.cancelables.CompositeCancelable
  34. import monix.reactive.Observable
  35. import monix.{eval => me}
  36. import scalafx.scene.control.Label
  37. import scalafx.scene.control.TextArea
  38. import scalafx.scene.layout.HBox
  39. import scalafx.scene.layout.Priority
  40. import scalafx.scene.layout.VBox
  41. import scalafx.scene.paint.Color
  42. import wow.doge.mygame.AppError.TimeoutError
  43. import wow.doge.mygame.executors.Schedulers
  44. import wow.doge.mygame.game.GameApp
  45. import wow.doge.mygame.game.GameAppResource
  46. import wow.doge.mygame.game.controls.FollowControl
  47. import wow.doge.mygame.game.entities.CharacterStats
  48. import wow.doge.mygame.game.entities.EntityIds
  49. import wow.doge.mygame.game.entities.NpcActorSupervisor
  50. import wow.doge.mygame.game.entities.NpcMovementActor
  51. import wow.doge.mygame.game.entities.player.PlayerActor
  52. import wow.doge.mygame.game.entities.player.PlayerController
  53. import wow.doge.mygame.game.entities.player.PlayerMovementReducer
  54. import wow.doge.mygame.game.subsystems.input.InputMappings
  55. import wow.doge.mygame.game.subsystems.input.PlayerCameraInput
  56. import wow.doge.mygame.game.subsystems.input.PlayerMovementInput
  57. import wow.doge.mygame.game.subsystems.level.DefaultGameLevel
  58. import wow.doge.mygame.implicits._
  59. import wow.doge.mygame.launcher.Launcher
  60. import wow.doge.mygame.launcher.Launcher.LauncherResult
  61. import wow.doge.mygame.math.ImVector3f
  62. import wow.doge.mygame.subsystems.events.Event
  63. import wow.doge.mygame.subsystems.events.EventBus
  64. import wow.doge.mygame.subsystems.events.EventBus.ObservableSubscription
  65. import wow.doge.mygame.subsystems.events.EventsModule
  66. import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
  67. import wow.doge.mygame.subsystems.events.PlayerCameraEvent
  68. import wow.doge.mygame.subsystems.events.PlayerEvent
  69. import wow.doge.mygame.subsystems.events.PlayerMovementEvent
  70. import wow.doge.mygame.subsystems.events.StatsEvent.DamageEvent
  71. import wow.doge.mygame.subsystems.events.TickEvent
  72. import wow.doge.mygame.subsystems.scriptsystem.ScriptCompiler
  73. import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode
  74. import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource
  75. import wow.doge.mygame.types._
  76. import wow.doge.mygame.utils.AkkaUtils
  77. import wow.doge.mygame.utils.GenericConsoleStream
  78. import wow.doge.mygame.utils.IOUtils
  79. import wow.doge.mygame.utils.MonixDirectoryWatcher
  80. import wow.doge.mygame.utils.MonixDirectoryWatcher.ModifyEvent
  81. import wow.doge.mygame.utils.controls.JFXProgressBar
  82. import wow.doge.mygame.utils.wrappers.jme
  83. import wow.doge.mygame.utils.wrappers.jme.AssetManager
  84. import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace
  85. class MainApp(
  86. logger: Logger[Task],
  87. jmeThread: JmeScheduler,
  88. schedulers: Schedulers,
  89. consoleStream: GenericConsoleStream[TextArea]
  90. )(implicit
  91. spawnProtocol: ActorRef[SpawnProtocol.Command],
  92. timeout: Timeout,
  93. scheduler: AkkaScheduler
  94. ) {
  95. implicit val as = scheduler.value
  96. val scriptSystemResource: Resource[UIO, ScriptCompiler] =
  97. new ScriptSystemResource(os.pwd, logger, ScriptInitMode.Eager).init2
  98. val eventsModule = new EventsModule(scheduler, spawnProtocol)
  99. def eval(
  100. tickEventBus: GameEventBus[TickEvent],
  101. gameApp: GameApp,
  102. fib: Fiber[Nothing, Unit]
  103. ) =
  104. for {
  105. // g <- UIO.pure(gameApp)
  106. playerEventBus <- eventsModule.playerEventBus
  107. mainEventBus <- eventsModule.mainEventBus
  108. obs <-
  109. playerEventBus
  110. .askL[Observable[PlayerMovementEvent]](ObservableSubscription(_))
  111. .onErrorHandleWith(TimeoutError.from)
  112. _ <-
  113. IOUtils
  114. .toIO(
  115. obs
  116. .doOnNextF(pme =>
  117. logger.trace(show"Received event $pme").toTask.void
  118. )
  119. .completedL
  120. .startAndForget
  121. )
  122. .hideErrors
  123. inputManager <- gameApp.inputManager
  124. assetManager <- UIO.pure(gameApp.assetManager)
  125. camera <- gameApp.camera
  126. rootNode <- UIO.pure(gameApp.rootNode)
  127. enqueueR <- UIO(gameApp.enqueue _)
  128. viewPort <- gameApp.viewPort
  129. physicsSpace <- UIO.pure(gameApp.physicsSpace)
  130. _ <- logger.infoU("before")
  131. jfxUI <- gameApp.jfxUI.hideErrors
  132. consoleTextArea <- UIO(new TextArea {
  133. text = "hello \n"
  134. editable = false
  135. wrapText = true
  136. // maxHeight = 150
  137. // maxWidth = 300
  138. })
  139. // _ <- Task(consoleStream := consoleTextArea)
  140. // _ <- Task(jfxUI += consoleTextArea)
  141. _ <- logger.infoU("after")
  142. _ <- logger.infoU("Initializing console stream")
  143. _ <-
  144. wire[MainAppDelegate]
  145. .init()
  146. .executeOn(gameApp.scheduler.value)
  147. } yield fib
  148. def gameInit(
  149. tickEventBus: GameEventBus[TickEvent]
  150. ): Resource[UIO, Either[AppError, Fiber[Nothing, Unit]]] =
  151. for {
  152. r1 <- wire[GameAppResource].resource.evalMap(e =>
  153. IO.fromEither(e)
  154. .flatMap {
  155. case (gameApp -> gameAppFib) =>
  156. eval(tickEventBus, gameApp, gameAppFib)
  157. }
  158. .attempt
  159. )
  160. dirWatcher <- Resource.liftF(
  161. MonixDirectoryWatcher(
  162. os.pwd / "assets" / "scripts"
  163. ).hideErrors
  164. )
  165. sc <- scriptSystemResource
  166. obs = dirWatcher.doOnNext {
  167. case ModifyEvent(file, count) =>
  168. sc.request(ScriptCompiler.GetScript(os.Path(file.path), _, true))(
  169. 15.seconds
  170. ).toTask
  171. .void
  172. case _ => me.Task.unit
  173. }
  174. _ <- Resource.make(obs.completedL.toIO.hideErrors.start)(_.cancel)
  175. // _ <-
  176. // dirWatcher
  177. // .doOnNextF(event =>
  178. // Coeval(pprint.log(show"Received file event $event")).void
  179. // )
  180. // .completedL
  181. // .executeOn(schedulers.io.value)
  182. // .startAndForget
  183. // .toIO
  184. // .hideErrors
  185. } yield r1
  186. val program = for {
  187. // scriptSystem <- scriptSystemInit
  188. launchSignal <- Deferred[Task, Launcher.LauncherResult].hideErrors
  189. launcher <- new Launcher.Props(schedulers.fx, launchSignal).create
  190. launchResult <-
  191. launcher.init
  192. .use(_ => launchSignal.get)
  193. .hideErrors
  194. tickEventBus <-
  195. eventsModule.tickEventBus.hideErrorsWith(e => new Exception(e.toString))
  196. _ <-
  197. /**
  198. * User chose to quit
  199. */
  200. if (launchResult === LauncherResult.Exit)
  201. logger.infoU("Exiting")
  202. /**
  203. * User chose launch. Wait for game window to close
  204. */
  205. else
  206. gameInit(tickEventBus).use {
  207. case Right(fib) => fib.join >> Task.unit
  208. case Left(error) => IO.terminate(new Exception(error.toString))
  209. }.hideErrors
  210. } yield ()
  211. }
  212. /**
  213. * Class with all dependencies in one place for easy wiring
  214. */
  215. class MainAppDelegate(
  216. gameApp: GameApp,
  217. loggerL: Logger[Task],
  218. mainEventBus: GameEventBus[Event],
  219. playerEventBus: GameEventBus[PlayerEvent],
  220. tickEventBus: GameEventBus[TickEvent],
  221. inputManager: InputManager,
  222. assetManager: AssetManager,
  223. physicsSpace: PhysicsSpace,
  224. camera: Camera,
  225. viewPort: ViewPort,
  226. enqueueR: Function1[() => Unit, Unit],
  227. rootNode: RootNode,
  228. schedulers: Schedulers,
  229. jfxUI: JavaFxUI
  230. )(implicit
  231. spawnProtocol: ActorRef[SpawnProtocol.Command],
  232. timeout: Timeout,
  233. scheduler: AkkaScheduler
  234. ) {
  235. implicit val as = scheduler.value
  236. def init(
  237. // appScheduler: monix.execution.Scheduler
  238. // consoleStream: GenericConsoleStream[TextArea]
  239. ): IO[AppError, Unit] =
  240. for {
  241. _ <- loggerL.infoU("Initializing Systems")
  242. _ <- assetManager.registerLocator(
  243. os.rel / "assets" / "town.zip",
  244. classOf[ZipLocator]
  245. )
  246. // _ <- Task(consoleStream.println("text"))
  247. level <- DefaultGameLevel(assetManager, viewPort)
  248. _ <- level.addToGame(rootNode, physicsSpace)
  249. playerActor <- createPlayerController()
  250. // .onErrorRestart(3)
  251. // _ <- wire[GameInputHandler.Props].begin
  252. _ <- new InputMappings(new jme.InputManager(inputManager)).setup
  253. // .onErrorRestart(3)
  254. johnActor <- createTestNpc("John")
  255. // _ <- johnActor !! NpcActorSupervisor.Move(ImVector3f(0, 0, 20))
  256. // _ <-
  257. // johnActor
  258. // .tellL(NpcActorSupervisor.Move(ImVector3f(-30, 0, 10)))
  259. // .delayExecution(2.seconds)
  260. _ <-
  261. rootNode.depthFirstTraversal
  262. .doOnNextF(spat => loggerL.debug(spat.getName).toTask)
  263. .completedL
  264. .toIO
  265. .hideErrors
  266. damageObs <-
  267. mainEventBus
  268. .askL[Observable[DamageEvent]](ObservableSubscription(_))
  269. .onErrorHandleWith(TimeoutError.from)
  270. _ <-
  271. damageObs
  272. .doOnNextF(event =>
  273. (loggerL.debug(show"Received Damage Event $event") >>
  274. (if (event.victimName === "PlayerNode")
  275. playerActor
  276. .askL(PlayerActor.TakeDamage(event.amount, _))
  277. .void
  278. .onErrorHandle { case ex: TimeoutException => () }
  279. else IO.unit)).toTask
  280. )
  281. .completedL
  282. .toIO
  283. .hideErrors
  284. .startAndForget
  285. // _ <-
  286. // Observable
  287. // .interval(1.second)
  288. // .doOnNextF(_ =>
  289. // playerActor
  290. // .askL(PlayerActorSupervisor.GetStatus)
  291. // .flatMap(s => loggerL.debug(show"Player actor status: $s"))
  292. // // .flatMap(s =>
  293. // // if (s == Status.Alive)
  294. // // playerActor
  295. // // .askL(PlayerActorSupervisor.CurrentStats )
  296. // // .flatMap(s => loggerL.debug(show"Got state $s"))
  297. // // else IO.unit
  298. // // )
  299. // .toTask
  300. // )
  301. // // .doOnNextF(_ =>
  302. // // playerActor
  303. // // .askL(PlayerActorSupervisor.GetStatus )
  304. // // .flatMap(s => loggerL.debug(show"Player actor status: $s"))
  305. // // .toTask
  306. // // )
  307. // .completedL
  308. // .toIO
  309. // .hideErrors
  310. // .startAndForget
  311. _ <-
  312. physicsSpace.collisionObservable
  313. // .filter(event =>
  314. // (for {
  315. // nodeA <- event.nodeA
  316. // nodeB <- event.nodeB
  317. // } yield nodeA.getName === "PlayerNode" && nodeB.getName === "John" ||
  318. // nodeB.getName === "PlayerNode" && nodeA.getName === "John")
  319. // .getOrElse(false)
  320. // )
  321. // .doOnNextF(event =>
  322. // loggerL
  323. // .debug(show"$event ${event.appliedImpulse()}")
  324. // .toTask
  325. // )
  326. .filter(_.nodeA.map(_.getName =!= "main-scene_node").getOrElse(false))
  327. .filter(_.nodeB.map(_.getName =!= "main-scene_node").getOrElse(false))
  328. .doOnNextF(event =>
  329. (for {
  330. victim <- Coeval(for {
  331. nodeA <- event.nodeA
  332. nodeB <- event.nodeB
  333. } yield if (nodeB.getName === "John") nodeA else nodeB)
  334. _ <- Coeval(
  335. victim.foreach { v =>
  336. pprint.log(show"emitted event ${v.getName}")
  337. mainEventBus ! EventBus.Publish(
  338. DamageEvent(
  339. "John",
  340. v.getName,
  341. CharacterStats.DamageHealth(10)
  342. ),
  343. "damageHandler"
  344. )
  345. }
  346. )
  347. } yield ()).void
  348. )
  349. .completedL
  350. .toIO
  351. .hideErrors
  352. .startAndForget
  353. // _ <-
  354. // IOUtils
  355. // .toIO(
  356. // rootNode
  357. // .observableBreadthFirst()
  358. // .doOnNext(spat => IOUtils.toTask(loggerL.debug(spat.getName())))
  359. // .completedL
  360. // )
  361. // .executeOn(appScheduler)
  362. // .startAndForget
  363. statsObs <-
  364. playerActor
  365. .askL(PlayerActor.GetStatsObservable(_))
  366. .onErrorHandleWith(TimeoutError.from)
  367. .flatten
  368. playerHud <-
  369. UIO
  370. .deferAction(implicit s =>
  371. UIO(new VBox {
  372. implicit val c = CompositeCancelable()
  373. hgrow = Priority.Always
  374. spacing = 10
  375. style = """-fx-background-color: rgba(0,0,0,0.7);"""
  376. stylesheets = Seq((os.rel / "main.css").toString)
  377. children = List(
  378. new HBox {
  379. spacing = 5
  380. children = List(
  381. new Label("Health") { textFill = Color.White },
  382. new Label("100") {
  383. text <-- statsObs
  384. .doOnNextF(i => loggerL.trace(show"Received stats: $i"))
  385. .map(_.hp.toInt.toString)
  386. textFill = Color.White
  387. },
  388. new JFXProgressBar {
  389. progress = 100
  390. minHeight = 10
  391. progress <-- statsObs
  392. .map(_.hp.toInt.toDouble / 100)
  393. c += statsObs
  394. .scanEval(me.Task.pure("green-bar")) {
  395. case (a, b) =>
  396. me.Task(styleClass.removeAll(a)) >>
  397. me.Task.pure(
  398. (b.hp.toInt: @switch) match {
  399. case v if v > 80 => "green-bar"
  400. case v if v > 20 && v <= 80 => "yellow-bar"
  401. case _ => "red-bar"
  402. }
  403. )
  404. }
  405. .doOnNext(cls => me.Task(styleClass += cls))
  406. .subscribe()
  407. }
  408. )
  409. },
  410. new HBox {
  411. spacing = 5
  412. children = List(
  413. new Label("Stamina") {
  414. textFill = Color.White
  415. },
  416. new Label("100") {
  417. textFill = Color.White
  418. text <-- statsObs
  419. .doOnNextF(i => loggerL.trace(show"Received stats: $i"))
  420. .map(_.stamina.toInt.toString)
  421. },
  422. new JFXProgressBar {
  423. progress = 100
  424. minHeight = 10
  425. progress <-- statsObs.map(_.stamina.toInt.toDouble / 100)
  426. styleClass ++= Seq("green-bar")
  427. c += statsObs
  428. .scanEval(me.Task.pure("green-bar")) {
  429. case (a, b) =>
  430. me.Task(styleClass.removeAll(a)) >>
  431. me.Task.pure(
  432. (b.stamina.toInt: @switch) match {
  433. case v if v > 80 => "green-bar"
  434. case v if v > 20 && v <= 40 => "yellow-bar"
  435. case _ => "red-bar"
  436. }
  437. )
  438. }
  439. .doOnNext(cls => me.Task(styleClass += cls))
  440. .subscribe()
  441. }
  442. )
  443. }
  444. )
  445. })
  446. )
  447. .executeOn(schedulers.fx.value)
  448. _ <- UIO(jfxUI += playerHud)
  449. } yield ()
  450. def createPlayerController(
  451. // appScheduler: monix.execution.Scheduler
  452. ): IO[AppError, PlayerActor.Ref] = {
  453. val playerPos = ImVector3f.Zero
  454. val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
  455. // val modelPath = os.rel / "Models" / "Oto" / "Oto.mesh.xml"
  456. val playerPhysicsControl =
  457. PlayerController.Defaults.defaultPlayerPhysicsControl
  458. for {
  459. playerModel <-
  460. assetManager
  461. .loadModelAs[Node](modelPath)
  462. .map(_.withRotate(0, FastMath.PI, 0))
  463. .tapEval(m => UIO(m.center()))
  464. .mapError(AppError.AssetManagerError)
  465. playerNode <- UIO(
  466. PlayerController.Defaults
  467. .defaultPlayerNode(
  468. playerPos,
  469. playerModel,
  470. playerPhysicsControl
  471. )
  472. )
  473. cameraPivotNode <- UIO(
  474. new Node(EntityIds.CameraPivot.value)
  475. .withControl(new FollowControl(playerNode))
  476. .taggedWith[PlayerController.Tags.PlayerCameraPivotNode]
  477. )
  478. camNode <- UIO(
  479. PlayerController.Defaults
  480. .defaultCamerNode(camera, playerPos)
  481. .taggedWith[PlayerController.Tags.PlayerCameraNode]
  482. )
  483. playerCameraEvents <-
  484. playerEventBus
  485. .askL[Observable[PlayerCameraEvent]](ObservableSubscription(_))
  486. .onErrorHandleWith(TimeoutError.from)
  487. _ <-
  488. inputManager
  489. .enumAnalogObservable(PlayerCameraInput)
  490. .sample(1.millis)
  491. .scan(new Quaternion) {
  492. case (rotationBuf, action) =>
  493. action.binding match {
  494. case PlayerCameraInput.CameraRotateLeft =>
  495. val rot = rotationBuf
  496. .fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
  497. cameraPivotNode.rotate(rot)
  498. rotationBuf
  499. case PlayerCameraInput.CameraRotateRight =>
  500. val rot = rotationBuf
  501. .fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
  502. cameraPivotNode.rotate(rot)
  503. rotationBuf
  504. case PlayerCameraInput.CameraRotateUp =>
  505. val rot = rotationBuf
  506. .fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
  507. cameraPivotNode.rotate(rot)
  508. rotationBuf
  509. case PlayerCameraInput.CameraRotateDown =>
  510. val rot = rotationBuf
  511. .fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
  512. cameraPivotNode.rotate(rot)
  513. rotationBuf
  514. }
  515. }
  516. .completedL
  517. .toIO
  518. .hideErrors
  519. .startAndForget
  520. sched <- UIO.pure(schedulers.async)
  521. fxSched <- UIO.pure(schedulers.fx)
  522. playerActor <- wire[PlayerController.Props].create
  523. playerMovementReducer = new PlayerMovementReducer(playerActor, loggerL)
  524. _ <-
  525. inputManager
  526. .enumObservableAction(PlayerMovementInput)
  527. .sample(1.millis)
  528. .scanEval(me.Task.pure(PlayerMovementReducer.State.empty))(
  529. playerMovementReducer.value
  530. )
  531. .completedL
  532. .toIO
  533. .hideErrors
  534. .startAndForget
  535. } yield playerActor
  536. }
  537. def createTestNpc(
  538. // appScheduler: monix.execution.Scheduler,
  539. npcName: String
  540. ): IO[AppError, NpcActorSupervisor.Ref] = {
  541. val initialPos = ImVector3f(50, 5, 0)
  542. val npcPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f)
  543. // (1f, 2.1f, 10f)
  544. .withJumpForce(ImVector3f(0, 5f, 0))
  545. val npcActorTask = AkkaUtils.spawnActorL(
  546. new NpcActorSupervisor.Props(
  547. new NpcMovementActor.Props(
  548. enqueueR,
  549. initialPos,
  550. npcName,
  551. npcPhysicsControl
  552. ).behavior,
  553. npcName,
  554. initialPos
  555. ).behavior,
  556. actorName = Some(show"${npcName}-npcActorSupervisor")
  557. )
  558. (for {
  559. materialDef <-
  560. assetManager
  561. .loadAssetAs[MaterialDef](
  562. os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
  563. )
  564. .mapError(AppError.AssetManagerError)
  565. material = new Material(materialDef)
  566. _ = material.setColor("Color", ColorRGBA.Blue)
  567. mesh = PlayerController.Defaults.defaultMesh.withMaterial(material)
  568. npcNode = PlayerController.Defaults.defaultNpcNode(
  569. mesh,
  570. initialPos,
  571. npcPhysicsControl,
  572. npcName
  573. )
  574. _ <- (for {
  575. _ <- physicsSpace += npcPhysicsControl
  576. _ <- physicsSpace += npcNode
  577. _ <- rootNode += npcNode
  578. } yield ()).mapError(AppError.AppNodeError)
  579. npcActor <- npcActorTask
  580. } yield npcActor)
  581. }
  582. }