package wow.doge.mygame import java.util.concurrent.TimeoutException import scala.annotation.switch import scala.concurrent.duration._ import akka.actor.typed.ActorRef import akka.actor.typed.SpawnProtocol import akka.util.Timeout import cats.effect.Resource import cats.effect.concurrent.Deferred import cats.syntax.eq._ import cats.syntax.show._ import com.jayfella.jme.jfx.JavaFxUI import com.jme3.asset.plugins.ZipLocator import com.jme3.bullet.control.BetterCharacterControl import com.jme3.input.InputManager import com.jme3.material.Material import com.jme3.material.MaterialDef import com.jme3.math.ColorRGBA import com.jme3.math.FastMath import com.jme3.math.Quaternion import com.jme3.math.Vector3f 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 monix.bio.UIO import monix.eval.Coeval import monix.execution.cancelables.CompositeCancelable import monix.reactive.Observable import monix.{eval => me} import scalafx.scene.control.Label import scalafx.scene.control.TextArea import scalafx.scene.layout.HBox import scalafx.scene.layout.Priority import scalafx.scene.layout.VBox import scalafx.scene.paint.Color import wow.doge.mygame.AppError.TimeoutError import wow.doge.mygame.executors.Schedulers import wow.doge.mygame.game.GameApp import wow.doge.mygame.game.GameAppResource import wow.doge.mygame.game.controls.FollowControl import wow.doge.mygame.game.entities.CharacterStats import wow.doge.mygame.game.entities.EntityIds import wow.doge.mygame.game.entities.NpcActorSupervisor import wow.doge.mygame.game.entities.NpcMovementActor import wow.doge.mygame.game.entities.player.PlayerActor import wow.doge.mygame.game.entities.player.PlayerController import wow.doge.mygame.game.entities.player.PlayerMovementReducer import wow.doge.mygame.game.subsystems.input.InputMappings import wow.doge.mygame.game.subsystems.input.PlayerCameraInput import wow.doge.mygame.game.subsystems.input.PlayerMovementInput 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.Event import wow.doge.mygame.subsystems.events.EventBus import wow.doge.mygame.subsystems.events.EventBus.ObservableSubscription import wow.doge.mygame.subsystems.events.EventsModule import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus import wow.doge.mygame.subsystems.events.PlayerCameraEvent import wow.doge.mygame.subsystems.events.PlayerEvent import wow.doge.mygame.subsystems.events.PlayerMovementEvent import wow.doge.mygame.subsystems.events.StatsEvent.DamageEvent import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.subsystems.scriptsystem.ScriptCompiler import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource import wow.doge.mygame.types._ import wow.doge.mygame.utils.AkkaUtils import wow.doge.mygame.utils.GenericConsoleStream import wow.doge.mygame.utils.IOUtils import wow.doge.mygame.utils.MonixDirectoryWatcher import wow.doge.mygame.utils.MonixDirectoryWatcher.ModifyEvent import wow.doge.mygame.utils.controls.JFXProgressBar import wow.doge.mygame.utils.wrappers.jme import wow.doge.mygame.utils.wrappers.jme.AssetManager import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace class MainApp( logger: Logger[Task], jmeThread: JmeScheduler, schedulers: Schedulers, consoleStream: GenericConsoleStream[TextArea] )(implicit spawnProtocol: ActorRef[SpawnProtocol.Command], timeout: Timeout, scheduler: AkkaScheduler ) { implicit val as = scheduler.value val scriptSystemResource: Resource[UIO, ScriptCompiler] = new ScriptSystemResource(os.pwd, logger, ScriptInitMode.Eager).init2 val eventsModule = new EventsModule(scheduler, spawnProtocol) def eval( tickEventBus: GameEventBus[TickEvent], gameApp: GameApp, fib: Fiber[Nothing, Unit] ) = for { // g <- UIO.pure(gameApp) playerEventBus <- eventsModule.playerEventBus mainEventBus <- eventsModule.mainEventBus obs <- playerEventBus .askL[Observable[PlayerMovementEvent]](ObservableSubscription(_)) .onErrorHandleWith(TimeoutError.from) _ <- IOUtils .toIO( obs .doOnNextF(pme => logger.trace(show"Received event $pme").toTask.void ) .completedL .startAndForget ) .hideErrors inputManager <- gameApp.inputManager assetManager <- UIO.pure(gameApp.assetManager) camera <- gameApp.camera rootNode <- UIO.pure(gameApp.rootNode) enqueueR <- UIO(gameApp.enqueue _) viewPort <- gameApp.viewPort physicsSpace <- UIO.pure(gameApp.physicsSpace) _ <- logger.infoU("before") jfxUI <- gameApp.jfxUI.hideErrors consoleTextArea <- UIO(new TextArea { text = "hello \n" editable = false wrapText = true // maxHeight = 150 // maxWidth = 300 }) // _ <- Task(consoleStream := consoleTextArea) // _ <- Task(jfxUI += consoleTextArea) _ <- logger.infoU("after") _ <- logger.infoU("Initializing console stream") _ <- wire[MainAppDelegate] .init() .executeOn(gameApp.scheduler.value) } yield fib def gameInit( tickEventBus: GameEventBus[TickEvent] ): Resource[UIO, Either[AppError, Fiber[Nothing, Unit]]] = for { r1 <- wire[GameAppResource].resource.evalMap(e => IO.fromEither(e) .flatMap { case (gameApp -> gameAppFib) => eval(tickEventBus, gameApp, gameAppFib) } .attempt ) dirWatcher <- Resource.liftF( MonixDirectoryWatcher( os.pwd / "assets" / "scripts" ).hideErrors ) sc <- scriptSystemResource obs = dirWatcher.doOnNext { case ModifyEvent(file, count) => sc.request(ScriptCompiler.GetScript(os.Path(file.path), _, true))( 15.seconds ).toTask .void case _ => me.Task.unit } _ <- Resource.make(obs.completedL.toIO.hideErrors.start)(_.cancel) // _ <- // dirWatcher // .doOnNextF(event => // Coeval(pprint.log(show"Received file event $event")).void // ) // .completedL // .executeOn(schedulers.io.value) // .startAndForget // .toIO // .hideErrors } yield r1 val program = for { // scriptSystem <- scriptSystemInit launchSignal <- Deferred[Task, Launcher.LauncherResult].hideErrors launcher <- new Launcher.Props(schedulers.fx, launchSignal).create launchResult <- launcher.init .use(_ => launchSignal.get) .hideErrors tickEventBus <- eventsModule.tickEventBus.hideErrorsWith(e => new Exception(e.toString)) _ <- /** * User chose to quit */ if (launchResult === LauncherResult.Exit) logger.infoU("Exiting") /** * User chose launch. Wait for game window to close */ else gameInit(tickEventBus).use { case Right(fib) => fib.join >> Task.unit case Left(error) => IO.terminate(new Exception(error.toString)) }.hideErrors } yield () } /** * Class with all dependencies in one place for easy wiring */ class MainAppDelegate( gameApp: GameApp, loggerL: Logger[Task], mainEventBus: GameEventBus[Event], playerEventBus: GameEventBus[PlayerEvent], tickEventBus: GameEventBus[TickEvent], inputManager: InputManager, assetManager: AssetManager, physicsSpace: PhysicsSpace, camera: Camera, viewPort: ViewPort, enqueueR: Function1[() => Unit, Unit], rootNode: RootNode, schedulers: Schedulers, jfxUI: JavaFxUI )(implicit spawnProtocol: ActorRef[SpawnProtocol.Command], timeout: Timeout, scheduler: AkkaScheduler ) { implicit val as = scheduler.value def init( // appScheduler: monix.execution.Scheduler // consoleStream: GenericConsoleStream[TextArea] ): IO[AppError, Unit] = for { _ <- loggerL.infoU("Initializing Systems") _ <- assetManager.registerLocator( os.rel / "assets" / "town.zip", classOf[ZipLocator] ) // _ <- Task(consoleStream.println("text")) level <- DefaultGameLevel(assetManager, viewPort) _ <- level.addToGame(rootNode, physicsSpace) playerActor <- createPlayerController() // .onErrorRestart(3) // _ <- wire[GameInputHandler.Props].begin _ <- new InputMappings(new jme.InputManager(inputManager)).setup // .onErrorRestart(3) johnActor <- createTestNpc("John") // _ <- johnActor !! NpcActorSupervisor.Move(ImVector3f(0, 0, 20)) // _ <- // johnActor // .tellL(NpcActorSupervisor.Move(ImVector3f(-30, 0, 10))) // .delayExecution(2.seconds) _ <- rootNode.depthFirstTraversal .doOnNextF(spat => loggerL.debug(spat.getName).toTask) .completedL .toIO .hideErrors damageObs <- mainEventBus .askL[Observable[DamageEvent]](ObservableSubscription(_)) .onErrorHandleWith(TimeoutError.from) _ <- damageObs .doOnNextF(event => (loggerL.debug(show"Received Damage Event $event") >> (if (event.victimName === "PlayerNode") playerActor .askL(PlayerActor.TakeDamage(event.amount, _)) .void .onErrorHandle { case ex: TimeoutException => () } else IO.unit)).toTask ) .completedL .toIO .hideErrors .startAndForget // _ <- // Observable // .interval(1.second) // .doOnNextF(_ => // playerActor // .askL(PlayerActorSupervisor.GetStatus) // .flatMap(s => loggerL.debug(show"Player actor status: $s")) // // .flatMap(s => // // if (s == Status.Alive) // // playerActor // // .askL(PlayerActorSupervisor.CurrentStats ) // // .flatMap(s => loggerL.debug(show"Got state $s")) // // else IO.unit // // ) // .toTask // ) // // .doOnNextF(_ => // // playerActor // // .askL(PlayerActorSupervisor.GetStatus ) // // .flatMap(s => loggerL.debug(show"Player actor status: $s")) // // .toTask // // ) // .completedL // .toIO // .hideErrors // .startAndForget _ <- physicsSpace.collisionObservable // .filter(event => // (for { // nodeA <- event.nodeA // nodeB <- event.nodeB // } yield nodeA.getName === "PlayerNode" && nodeB.getName === "John" || // nodeB.getName === "PlayerNode" && nodeA.getName === "John") // .getOrElse(false) // ) // .doOnNextF(event => // loggerL // .debug(show"$event ${event.appliedImpulse()}") // .toTask // ) .filter(_.nodeA.map(_.getName =!= "main-scene_node").getOrElse(false)) .filter(_.nodeB.map(_.getName =!= "main-scene_node").getOrElse(false)) .doOnNextF(event => (for { victim <- Coeval(for { nodeA <- event.nodeA nodeB <- event.nodeB } yield if (nodeB.getName === "John") nodeA else nodeB) _ <- Coeval( victim.foreach { v => pprint.log(show"emitted event ${v.getName}") mainEventBus ! EventBus.Publish( DamageEvent( "John", v.getName, CharacterStats.DamageHealth(10) ), "damageHandler" ) } ) } yield ()).void ) .completedL .toIO .hideErrors .startAndForget // _ <- // IOUtils // .toIO( // rootNode // .observableBreadthFirst() // .doOnNext(spat => IOUtils.toTask(loggerL.debug(spat.getName()))) // .completedL // ) // .executeOn(appScheduler) // .startAndForget statsObs <- playerActor .askL(PlayerActor.GetStatsObservable(_)) .onErrorHandleWith(TimeoutError.from) .flatten playerHud <- UIO .deferAction(implicit s => UIO(new VBox { implicit val c = CompositeCancelable() hgrow = Priority.Always spacing = 10 style = """-fx-background-color: rgba(0,0,0,0.7);""" stylesheets = Seq((os.rel / "main.css").toString) children = List( new HBox { spacing = 5 children = List( new Label("Health") { textFill = Color.White }, new Label("100") { text <-- statsObs .doOnNextF(i => loggerL.trace(show"Received stats: $i")) .map(_.hp.toInt.toString) textFill = Color.White }, new JFXProgressBar { progress = 100 minHeight = 10 progress <-- statsObs .map(_.hp.toInt.toDouble / 100) c += statsObs .scanEval(me.Task.pure("green-bar")) { case (a, b) => me.Task(styleClass.removeAll(a)) >> me.Task.pure( (b.hp.toInt: @switch) match { case v if v > 80 => "green-bar" case v if v > 20 && v <= 80 => "yellow-bar" case _ => "red-bar" } ) } .doOnNext(cls => me.Task(styleClass += cls)) .subscribe() } ) }, new HBox { spacing = 5 children = List( new Label("Stamina") { textFill = Color.White }, new Label("100") { textFill = Color.White text <-- statsObs .doOnNextF(i => loggerL.trace(show"Received stats: $i")) .map(_.stamina.toInt.toString) }, new JFXProgressBar { progress = 100 minHeight = 10 progress <-- statsObs.map(_.stamina.toInt.toDouble / 100) styleClass ++= Seq("green-bar") c += statsObs .scanEval(me.Task.pure("green-bar")) { case (a, b) => me.Task(styleClass.removeAll(a)) >> me.Task.pure( (b.stamina.toInt: @switch) match { case v if v > 80 => "green-bar" case v if v > 20 && v <= 40 => "yellow-bar" case _ => "red-bar" } ) } .doOnNext(cls => me.Task(styleClass += cls)) .subscribe() } ) } ) }) ) .executeOn(schedulers.fx.value) _ <- UIO(jfxUI += playerHud) } yield () def createPlayerController( // appScheduler: monix.execution.Scheduler ): IO[AppError, PlayerActor.Ref] = { val playerPos = ImVector3f.Zero val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o" // val modelPath = os.rel / "Models" / "Oto" / "Oto.mesh.xml" val playerPhysicsControl = PlayerController.Defaults.defaultPlayerPhysicsControl for { playerModel <- assetManager .loadModelAs[Node](modelPath) .map(_.withRotate(0, FastMath.PI, 0)) .tapEval(m => UIO(m.center())) .mapError(AppError.AssetManagerError) playerNode <- UIO( PlayerController.Defaults .defaultPlayerNode( playerPos, playerModel, playerPhysicsControl ) ) cameraPivotNode <- UIO( new Node(EntityIds.CameraPivot.value) .withControl(new FollowControl(playerNode)) .taggedWith[PlayerController.Tags.PlayerCameraPivotNode] ) camNode <- UIO( PlayerController.Defaults .defaultCamerNode(camera, playerPos) .taggedWith[PlayerController.Tags.PlayerCameraNode] ) playerCameraEvents <- playerEventBus .askL[Observable[PlayerCameraEvent]](ObservableSubscription(_)) .onErrorHandleWith(TimeoutError.from) _ <- inputManager .enumAnalogObservable(PlayerCameraInput) .sample(1.millis) .scan(new Quaternion) { case (rotationBuf, action) => action.binding match { case PlayerCameraInput.CameraRotateLeft => val rot = rotationBuf .fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y) cameraPivotNode.rotate(rot) rotationBuf case PlayerCameraInput.CameraRotateRight => val rot = rotationBuf .fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y) cameraPivotNode.rotate(rot) rotationBuf case PlayerCameraInput.CameraRotateUp => val rot = rotationBuf .fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X) cameraPivotNode.rotate(rot) rotationBuf case PlayerCameraInput.CameraRotateDown => val rot = rotationBuf .fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X) cameraPivotNode.rotate(rot) rotationBuf } } .completedL .toIO .hideErrors .startAndForget sched <- UIO.pure(schedulers.async) fxSched <- UIO.pure(schedulers.fx) playerActor <- wire[PlayerController.Props].create playerMovementReducer = new PlayerMovementReducer(playerActor, loggerL) _ <- inputManager .enumObservableAction(PlayerMovementInput) .sample(1.millis) .scanEval(me.Task.pure(PlayerMovementReducer.State.empty))( playerMovementReducer.value ) .completedL .toIO .hideErrors .startAndForget } yield playerActor } def createTestNpc( // appScheduler: monix.execution.Scheduler, npcName: String ): IO[AppError, NpcActorSupervisor.Ref] = { val initialPos = ImVector3f(50, 5, 0) val npcPhysicsControl = new BetterCharacterControl(1.5f, 6f, 1f) // (1f, 2.1f, 10f) .withJumpForce(ImVector3f(0, 5f, 0)) val npcActorTask = AkkaUtils.spawnActorL( new NpcActorSupervisor.Props( new NpcMovementActor.Props( enqueueR, initialPos, npcName, npcPhysicsControl ).behavior, npcName, initialPos ).behavior, actorName = Some(show"${npcName}-npcActorSupervisor") ) (for { materialDef <- assetManager .loadAssetAs[MaterialDef]( os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md" ) .mapError(AppError.AssetManagerError) material = new Material(materialDef) _ = material.setColor("Color", ColorRGBA.Blue) mesh = PlayerController.Defaults.defaultMesh.withMaterial(material) npcNode = PlayerController.Defaults.defaultNpcNode( mesh, initialPos, npcPhysicsControl, npcName ) _ <- (for { _ <- physicsSpace += npcPhysicsControl _ <- physicsSpace += npcNode _ <- rootNode += npcNode } yield ()).mapError(AppError.AppNodeError) npcActor <- npcActorTask } yield npcActor) } }