package wow.doge.mygame import scala.concurrent.duration._ import akka.actor.typed.ActorRef import akka.actor.typed.Scheduler import akka.actor.typed.SpawnProtocol import akka.util.Timeout import cats.effect.Resource import cats.effect.concurrent.Deferred import cats.syntax.eq._ 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.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.reactive.Observable import scalafx.scene.control.TextArea import wow.doge.mygame.AppError.TimeoutError import wow.doge.mygame.executors.Schedulers import wow.doge.mygame.game.GameApp import wow.doge.mygame.game.GameAppActor import wow.doge.mygame.game.GameAppResource 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.PlayerActorSupervisor import wow.doge.mygame.game.entities.PlayerController 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.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.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.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 wow.doge.mygame.utils.wrappers.jme.AssetManager import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace import wow.doge.mygame.subsystems.events.PlayerCameraEvent import com.jme3.math.Quaternion import com.jme3.math.Vector3f import wow.doge.mygame.types._ import wow.doge.mygame.game.controls.FollowControl import wow.doge.mygame.game.subsystems.input.PlayerCameraInput class MainApp( logger: Logger[Task], jmeThread: monix.execution.Scheduler, schedulers: Schedulers, consoleStream: GenericConsoleStream[TextArea] )(implicit spawnProtocol: ActorRef[SpawnProtocol.Command], timeout: Timeout, scheduler: Scheduler ) { val scriptSystemInit = new ScriptSystemResource(os.pwd, ScriptInitMode.Eager).init val eventsModule = new EventsModule(scheduler, spawnProtocol) class TestClass( playerEventBus: GameEventBus[PlayerEvent], tickEventBus: GameEventBus[TickEvent] ) def eval(gameApp: GameApp, fib: Fiber[Nothing, Unit]) = for { // g <- UIO.pure(gameApp) playerEventBus <- eventsModule.playerEventBus mainEventBus <- eventsModule.mainEventBus tickEventBus <- eventsModule.tickEventBus obs <- playerEventBus .askL[Observable[PlayerMovementEvent]](ObservableSubscription(_)) .onErrorHandleWith(TimeoutError.from) _ <- IOUtils .toIO( obs .doOnNextF(pme => Coeval(pprint.log(s"Received event $pme")).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 gameAppActor <- gameApp.spawnGameActor( GameAppActor.Props(tickEventBus).behavior, Some("gameAppActor") ) _ <- gameAppActor !! GameAppActor.Start 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: Resource[UIO, Either[AppError, Fiber[Nothing, Unit]]] = wire[GameAppResource].resource.evalMap { case Right(gameApp -> gameAppFib) => eval(gameApp, gameAppFib).attempt case Left(error) => IO.terminate(new Exception(error.toString)) } // val x: Task[Unit] = for { // tickEventBus <- eventsModule.tickEventBusTask // playerEventBus <- eventsModule.playerEventBusTask // _ <- UIO(wire[TestClass]) // _ <- gameInit(tickEventBus).use(_.join) // } yield () val program = for { // scriptSystem <- scriptSystemInit launchSignal <- Deferred[Task, Launcher.LauncherResult].hideErrors launcher <- new Launcher.Props(schedulers, launchSignal).create launchResult <- launcher.init.use(_ => launchSignal.get).hideErrors _ <- /** * User chose to quit */ if (launchResult === LauncherResult.Exit) logger.infoU("Exiting") /** * User chose launch. Wait for game window to close */ else gameInit.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 )(implicit spawnProtocol: ActorRef[SpawnProtocol.Command], timeout: Timeout, scheduler: Scheduler ) { 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] ) _ <- loggerL.infoU("test") // _ <- Task(consoleStream.println("text")) level <- DefaultGameLevel(assetManager, viewPort) _ <- level.addToGame(rootNode, physicsSpace) playerActor <- createPlayerController() // .onErrorRestart(3) _ <- wire[GameInputHandler.Props].begin // .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(s"Received Damage Event $event") >> IO( playerActor ! PlayerActorSupervisor.TakeDamage(event.amount) )).toTask ) .completedL .toIO .hideErrors .startAndForget _ <- Observable .interval(1.second) .doOnNextF(_ => playerActor .askL(PlayerActorSupervisor.GetStatus) .flatMap(s => loggerL.debug(s"Player actor status: $s") >> UIO.pure(s) ) .void // .flatMap(s => // if (s == Status.Alive) // playerActor // .askL(PlayerActorSupervisor.CurrentStats ) // .flatMap(s => loggerL.debug(s"Got state $s")) // else IO.unit // ) .toTask ) // .doOnNextF(_ => // playerActor // .askL(PlayerActorSupervisor.GetStatus ) // .flatMap(s => loggerL.debug(s"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(s"$event ${event.appliedImpulse()}") .toTask ) .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(s"emitted event ${v.getName}") mainEventBus ! EventBus.Publish( DamageEvent("John", v.getName, 10), "damageHandler" ) } ) } yield ()).void ) .completedL .toIO .hideErrors .startAndForget // _ <- // IOUtils // .toIO( // rootNode // .observableBreadthFirst() // .doOnNext(spat => IOUtils.toTask(loggerL.debug(spat.getName()))) // .completedL // ) // .executeOn(appScheduler) // .startAndForget } yield () def createPlayerController( // appScheduler: monix.execution.Scheduler ): IO[AppError, PlayerActorSupervisor.Ref] = { val playerPos = ImVector3f.Zero val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o" val playerPhysicsControl = PlayerController.Defaults.defaultPlayerPhysicsControl for { playerModel <- assetManager .loadModelAs[Node](modelPath) .map(_.withRotate(0, FastMath.PI, 0)) .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 PlayerCameraEvent.CameraLeft => case PlayerCameraInput.CameraRotateLeft => // me.Task { val rot = rotationBuf .fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y) cameraPivotNode.rotate(rot) rotationBuf // } // case PlayerCameraEvent.CameraRight => case PlayerCameraInput.CameraRotateRight => // me.Task { val rot = rotationBuf .fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y) cameraPivotNode.rotate(rot) rotationBuf // } // case PlayerCameraEvent.CameraMovedUp => case PlayerCameraInput.CameraRotateUp => // me.Task { val rot = rotationBuf .fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X) cameraPivotNode.rotate(rot) rotationBuf // } // case PlayerCameraEvent.CameraMovedDown => case PlayerCameraInput.CameraRotateDown => // me.Task { val rot = rotationBuf .fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X) cameraPivotNode.rotate(rot) rotationBuf // } } } .completedL .toIO .hideErrors .startAndForget _ <- Observable .interval(10.millis) .doOnNextF(_ => Coeval { val location = playerNode.getWorldTranslation() cameraPivotNode.setLocalTranslation(location) } ) .completedL .toIO .hideErrors .startAndForget sched <- UIO.pure(schedulers.async) playerActor <- wire[PlayerController.Props].create obs <- playerActor .askL(PlayerActorSupervisor.GetStatsObservable) .onErrorHandleWith(TimeoutError.from) _ <- obs .doOnNext(s => loggerL.debug(s"Got state $s").toTask) .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(s"${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) } }