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.input.InputManager import com.jme3.renderer.Camera import com.jme3.renderer.ViewPort import com.jme3.scene.Node 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 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.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 EventsModule.GameEventBus import wow.doge.mygame.game.entities.NpcMovementActor2 import wow.doge.mygame.game.entities.NpcActorSupervisor import monix.execution.exceptions.DummyException import com.jme3.bullet.control.BetterCharacterControl 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) johnActor <- createNpc(appScheduler, "John").executeOn(appScheduler) _ <- johnActor !! NpcActorSupervisor.Move(ImVector3f(0, 0, 20)) _ <- wire[GameInputHandler.Props].begin.onErrorRestart(3) } yield () def createPlayerController( appScheduler: monix.execution.Scheduler ): IO[PlayerController.Error, Unit] = { val playerPos = ImVector3f.ZERO val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o" val playerPhysicsControl = PlayerController.Defaults.defaultPlayerPhysicsControl .taggedWith[PlayerControllerTags.PlayerTag] val camNode = PlayerController.Defaults .defaultCamerNode(camera, playerPos) .taggedWith[PlayerControllerTags.PlayerCameraNode] val mbPlayerNode = PlayerController.Defaults .defaultPlayerNode( assetManager, modelPath, playerPos, camNode, playerPhysicsControl ) for { playerNode <- IO.fromEither(mbPlayerNode) _ <- wire[PlayerController.Props].create } yield () } def createNpc( 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, "John" ) val npcActorTask = AkkaUtils.spawnActorL2( NpcActorSupervisor .Props( new NpcMovementActor2.Props( enqueueR, initialPos, tickEventBus, npcPhysicsControl ).create, npcName, initialPos ) .create, s"${npcName}-npcMovementActorSupervisor" ) // .taggedWith[PlayerControllerTags.PlayerTag] for { npcNode <- IO.fromEither(mbNpcNode) npcActor <- npcActorTask _ <- IO { physicsSpace += npcPhysicsControl physicsSpace += npcNode rootNode += npcNode } } yield (npcActor) } }