forked from nova/jmonkey-test
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.
325 lines
11 KiB
325 lines
11 KiB
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 = {}
|
|
}
|