package wow.doge.mygame import akka.actor.typed.ActorRef import akka.actor.typed.ActorSystem import akka.actor.typed.Scheduler import akka.actor.typed.SpawnProtocol import akka.util.Timeout import cats.effect.concurrent.Deferred import com.jme3.app.state.AppStateManager import com.jme3.asset.AssetManager import com.jme3.asset.plugins.ZipLocator import com.jme3.bullet.BulletAppState import com.jme3.bullet.control.BetterCharacterControl import com.jme3.input.InputManager import com.jme3.renderer.Camera import com.jme3.renderer.RenderManager import com.jme3.renderer.ViewPort import com.jme3.scene.Node import com.jme3.scene.control.AbstractControl import com.softwaremill.macwire._ import com.softwaremill.tagging._ import io.odin.Logger import monix.bio.Fiber import monix.bio.IO import monix.bio.Task import monix.execution.exceptions.DummyException import scalafx.scene.control.TextArea import wow.doge.mygame.executors.Schedulers import wow.doge.mygame.game.GameApp import wow.doge.mygame.game.GameAppActor import wow.doge.mygame.game.GameAppTags import wow.doge.mygame.game.entities.EntityIds import wow.doge.mygame.game.entities.NpcActorSupervisor import wow.doge.mygame.game.entities.NpcMovementActor2 import wow.doge.mygame.game.entities.PlayerController import wow.doge.mygame.game.entities.PlayerControllerTags import wow.doge.mygame.game.subsystems.input.GameInputHandler import wow.doge.mygame.game.subsystems.level.DefaultGameLevel import wow.doge.mygame.implicits._ import wow.doge.mygame.launcher.Launcher import wow.doge.mygame.launcher.Launcher.LauncherResult import wow.doge.mygame.math.ImVector3f import wow.doge.mygame.subsystems.events.EventBus import wow.doge.mygame.subsystems.events.EventsModule import wow.doge.mygame.subsystems.events.PlayerCameraEvent import wow.doge.mygame.subsystems.events.PlayerMovementEvent import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource import wow.doge.mygame.utils.AkkaUtils import wow.doge.mygame.utils.GenericConsoleStream import wow.doge.mygame.utils.IOUtils import EventsModule.GameEventBus class MainApp( logger: Logger[Task], gameApp: GameApp, implicit val spawnProtocol: ActorSystem[SpawnProtocol.Command], jmeThread: monix.execution.Scheduler, schedulers: Schedulers, consoleStream: GenericConsoleStream[TextArea] )(implicit @annotation.unused timeout: Timeout, @annotation.unused scheduler: Scheduler ) { lazy val scriptSystemInit = new ScriptSystemResource(os.pwd, spawnProtocol, ScriptInitMode.Eager).init def gameInit: Task[Fiber[Throwable, Unit]] = for { eventsModule <- Task(new EventsModule(spawnProtocol)) playerMovementEventBus <- eventsModule.playerMovementEventBusTask playerCameraEventBus <- eventsModule.playerCameraEventBusTask mainEventBus <- eventsModule.mainEventBusTask tickEventBus <- eventsModule.tickEventBusTask gameAppActor <- AkkaUtils.spawnActorL2( GameAppActor.Props(tickEventBus).create, "gameAppActor" ) _ <- gameAppActor !! GameAppActor.Start gameAppFib <- gameApp.start.executeOn(jmeThread).start /** * schedule a task to run on the JME thread and wait for it's completion * before proceeding forward, as a signal that the JME thread has been * initialized, otherwise we'll get NPEs trying to access the fields * of the game app */ res <- gameApp.enqueueL(() => "done") _ <- logger.info(s"Result = $res") /** * JME Thread has been initialized at this point. We can now access the * field of the game application */ inputManager <- gameApp.inputManager assetManager <- gameApp.assetManager stateManager <- gameApp.stateManager camera <- gameApp.camera rootNode <- gameApp.rootNode enqueueR <- Task(gameApp.enqueue _) viewPort <- gameApp.viewPort _ <- logger.info("before") // jfxUI <- gameApp.jfxUI consoleTextArea <- Task(new TextArea { text = "hello \n" editable = false wrapText = true // maxHeight = 150 // maxWidth = 300 }) // _ <- Task(consoleStream := consoleTextArea) // _ <- Task(jfxUI += consoleTextArea) _ <- logger.info("after") bulletAppState <- Task(new BulletAppState()) _ <- Task(stateManager.attach(bulletAppState)) _ <- logger.info("Initializing console stream") _ <- wire[MainAppDelegate].init(gameApp.scheduler) } yield (gameAppFib) lazy val program = for { scriptSystem <- scriptSystemInit /** * Signal for synchronization between the JavaFX launcher and the in-game JavaFX GUI * Without this, we get a "Toolkit already initialized" exception. The launch button * in the launcher completes the signal. The game init process which listens for this * signal can then continue */ launchSignal <- Deferred[Task, Launcher.LauncherResult] launcher <- new Launcher.Props(schedulers, launchSignal).create cancelToken <- launcher.init() launchResult <- launchSignal.get _ <- cancelToken.cancel _ <- /** * User chose to quit */ if (launchResult == LauncherResult.Exit) logger.info("Exiting") /** * User chose launch. Wait for game window to close */ else gameInit.flatMap(_.join) } yield () } /** * Class with all dependencies in one place for easy wiring */ class MainAppDelegate( gameApp: GameApp, implicit val spawnProtocol: ActorSystem[SpawnProtocol.Command], loggerL: Logger[Task], playerMovementEventBus: ActorRef[ EventBus.Command[PlayerMovementEvent] ], playerCameraEventBus: ActorRef[EventBus.Command[PlayerCameraEvent]], tickEventBus: GameEventBus[TickEvent], inputManager: InputManager, assetManager: AssetManager, stateManager: AppStateManager, camera: Camera, viewPort: ViewPort, enqueueR: Function1[() => Unit, Unit], rootNode: Node @@ GameAppTags.RootNode, bulletAppState: BulletAppState )(implicit @annotation.unused timeout: Timeout, @annotation.unused scheduler: Scheduler ) { lazy val physicsSpace = bulletAppState.physicsSpace def init( appScheduler: monix.execution.Scheduler // consoleStream: GenericConsoleStream[TextArea] ) = for { _ <- loggerL.info("Initializing Systems") _ <- Task( assetManager.registerLocator( os.rel / "assets" / "town.zip", classOf[ZipLocator] ) ) _ <- loggerL.info("test") // _ <- Task(consoleStream.println("text")) _ <- DefaultGameLevel(assetManager, viewPort) .addToGame( rootNode, bulletAppState.physicsSpace ) .executeOn(appScheduler) _ <- createPlayerController(appScheduler) .absorbWith(e => DummyException("boom")) .onErrorRestart(3) _ <- wire[GameInputHandler.Props].begin.onErrorRestart(3) // johnActor <- createTestNpc(appScheduler, "John").executeOn(appScheduler) // _ <- johnActor !! NpcActorSupervisor.Move(ImVector3f(0, 0, 20)) // _ <- (johnActor !! NpcActorSupervisor.Move( // ImVector3f(-80, 0, 100) // )).executeAsync.delayExecution(2.seconds) _ <- IOUtils .toIO( rootNode .observableBreadthFirst() .doOnNext(spat => IOUtils.toTask(loggerL.debug(spat.getName()))) .completedL ) .executeOn(appScheduler) .startAndForget } yield () def createPlayerController( appScheduler: monix.execution.Scheduler ): IO[PlayerController.Error, Unit] = { val playerPos = ImVector3f.ZERO val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o" lazy val playerPhysicsControl = PlayerController.Defaults.defaultPlayerPhysicsControl .taggedWith[PlayerControllerTags.PlayerTag] // lazy val camNode = // PlayerController.Defaults // .defaultCamerNode(camera, playerPos) // .taggedWith[PlayerControllerTags.PlayerCameraNode] lazy val mbPlayerNode = PlayerController.Defaults .defaultPlayerNode( assetManager, modelPath, playerPos, // camNode playerPhysicsControl ) lazy val cameraPivotNode = new Node(EntityIds.CameraPivot.value) .taggedWith[PlayerControllerTags.PlayerCameraPivotNode] for { playerNode <- IO.fromEither(mbPlayerNode) _ <- IO(cameraPivotNode.addControl(new FollowControl(playerNode))) .onErrorHandleWith(e => IO.raiseError(PlayerController.GenericError(e.getMessage())) ) camNode <- IO( PlayerController.Defaults .defaultCamerNode(camera, cameraPivotNode, playerNode, playerPos) ).onErrorHandleWith(e => IO.raiseError(PlayerController.GenericError(e.getMessage())) ).map(_.taggedWith[PlayerControllerTags.PlayerCameraNode]) // _ <- Task { // val chaseCam = new ChaseCamera(camera, playerNode, inputManager) // chaseCam.setSmoothMotion(false) // chaseCam.setLookAtOffset(new Vector3f(0, 1.5f, 10)) // chaseCam // } // .onErrorHandleWith(e => // IO.raiseError(PlayerController.GenericError(e.getMessage())) // ) _ <- wire[PlayerController.Props].create } yield () } def createTestNpc( appScheduler: monix.execution.Scheduler, npcName: String ) = // : IO[PlayerController.Error, Unit] = { val initialPos = ImVector3f(100, 0, 0) // val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o" lazy val npcPhysicsControl = new BetterCharacterControl(1f, 2.1f, 10f) // .withJumpForce(ImVector3f(0, 5f, 0)) // val npcMovementActor = AkkaUtils.spawnActorL2( // new NpcMovementActor2.Props( // initialPos, // tickEventBus, // npcPhysicsControl // ).create, // s"${npcName}-npcMovementActor" // ) lazy val mbNpcNode = PlayerController.Defaults.defaultNpcNode( assetManager, initialPos, npcPhysicsControl, npcName ) val npcActorTask = AkkaUtils.spawnActorL2( NpcActorSupervisor .Props( new NpcMovementActor2.Props( enqueueR, initialPos, // tickEventBus, npcPhysicsControl ).create, npcName, initialPos ) .create, s"${npcName}-npcActorSupervisor" ) // .taggedWith[PlayerControllerTags.PlayerTag] for { npcNode <- IO.fromEither(mbNpcNode) npcActor <- npcActorTask _ <- IO { physicsSpace += npcPhysicsControl physicsSpace += npcNode rootNode += npcNode } } yield (npcActor) } } class FollowControl(playerNode: Node) extends AbstractControl { override def controlUpdate(tpf: Float): Unit = { this.spatial.setLocalTranslation(playerNode.getLocalTranslation()) } override def controlRender( rm: RenderManager, vp: ViewPort ): Unit = {} }