This commit is contained in:
Rohan Sircar 2021-01-20 12:20:20 +05:30
parent 89fad19d99
commit 67a2bc4385
34 changed files with 1308 additions and 943 deletions

View File

@ -19,13 +19,6 @@ lazy val javaFXModules =
Seq("base", "controls", "fxml", "graphics", "media", "swing", "web")
lazy val root = (project in file(".")).settings(
inThisBuild(
List(
scalaVersion := scalaVersion.value, // 2.11.12, or 2.13.3
semanticdbEnabled := true, // enable SemanticDB
semanticdbVersion := "4.3.24" // use Scalafix compatible version
)
),
name := "mygame",
organization := "wow.doge",
version := "1.0-SNAPSHOT",
@ -71,7 +64,9 @@ lazy val root = (project in file(".")).settings(
"org.recast4j" % "recast" % "1.2.5",
"org.recast4j" % "detour" % "1.2.5",
"com.lihaoyi" %% "pprint" % "0.6.0",
"org.scalatest" %% "scalatest" % "3.2.2" % "test"
"org.scalatest" %% "scalatest" % "3.2.2" % "test",
"org.typelevel" %% "cats-mtl" % "1.1.1",
"io.estatico" %% "newtype" % "0.4.4"
),
// Determine OS version of JavaFX binaries
@ -135,4 +130,11 @@ addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")
addCompilerPlugin(
"org.typelevel" %% "kind-projector" % "0.11.1" cross CrossVersion.full
)
inThisBuild(
List(
scalaVersion := scalaVersion.value, // 2.11.12, or 2.13.3
semanticdbEnabled := true, // enable SemanticDB
semanticdbVersion := "4.3.24" // use Scalafix compatible version
)
)
ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.3"

View File

@ -1,3 +1,2 @@
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0")
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.23")
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")

View File

@ -2,7 +2,8 @@ package wow.doge.mygame
import java.util.concurrent.TimeoutException
import cats.data.Reader
import cats.Show
import cats.kernel.Eq
import monix.bio.IO
import wow.doge.mygame.utils.wrappers.jme.AssetManager
import wow.doge.mygame.utils.wrappers.jme.CollisionShapeFactory
@ -12,21 +13,10 @@ sealed trait AppError
object AppError {
final case class TimeoutError(reason: String) extends AppError
object TimeoutError {
def reader =
Reader[Throwable, TimeoutError] {
case ex: TimeoutException => TimeoutError(ex.getMessage)
}
def from: PartialFunction[Throwable, IO[TimeoutError, Nothing]] = {
case ex: TimeoutException => IO.raiseError(TimeoutError(ex.getMessage))
}
}
final case class DummyError(reason: String) extends AppError
object DummyError {
def reader =
Reader[Throwable, DummyError] {
case ex: NullPointerException => DummyError(ex.getMessage)
}
}
final case class AssetManagerError(error: AssetManager.Error) extends AppError
final case class AppNodeError(error: NodeWrapper2.Error) extends AppError
final case class CollisionShapeCreationFailed(
@ -37,4 +27,7 @@ object AppError {
case ex: TimeoutException => IO.raiseError(TimeoutError(ex.getMessage))
}
implicit val show = Show.fromToString[AppError]
implicit val eq = Eq.fromUniversalEquals[AppError]
}

View File

@ -1,7 +1,5 @@
package wow.doge.mygame
import java.util.concurrent.TimeoutException
import scala.concurrent.duration._
import akka.actor.typed.ActorRef
@ -19,10 +17,8 @@ import com.jme3.material.MaterialDef
import com.jme3.math.ColorRGBA
import com.jme3.math.FastMath
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
@ -33,38 +29,44 @@ 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.GameAppTags
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.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.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.AppNode2
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,
@ -94,13 +96,8 @@ class MainApp(
tickEventBus <- eventsModule.tickEventBus
obs <-
playerEventBus
.askL[Observable[PlayerMovementEvent]](
ObservableSubscription(_)
)
.onErrorHandleWith {
case ex: TimeoutException =>
IO.raiseError(AppError.TimeoutError(ex.getMessage()))
}
.askL[Observable[PlayerMovementEvent]](ObservableSubscription(_))
.onErrorHandleWith(TimeoutError.from)
_ <-
IOUtils
.toIO(
@ -121,7 +118,7 @@ class MainApp(
// jfxUI <- gameApp.jfxUI
gameAppActor <- gameApp.spawnGameActor(
GameAppActor.Props(tickEventBus).behavior,
"gameAppActor"
Some("gameAppActor")
)
_ <- gameAppActor !! GameAppActor.Start
consoleTextArea <- UIO(new TextArea {
@ -138,7 +135,7 @@ class MainApp(
_ <-
wire[MainAppDelegate]
.init()
.executeOn(gameApp.scheduler)
.executeOn(gameApp.scheduler.value)
} yield fib
def gameInit: Resource[UIO, Either[AppError, Fiber[Nothing, Unit]]] =
@ -184,6 +181,7 @@ class MainApp(
class MainAppDelegate(
gameApp: GameApp,
loggerL: Logger[Task],
mainEventBus: GameEventBus[Event],
playerEventBus: GameEventBus[PlayerEvent],
tickEventBus: GameEventBus[TickEvent],
inputManager: InputManager,
@ -192,7 +190,8 @@ class MainAppDelegate(
camera: Camera,
viewPort: ViewPort,
enqueueR: Function1[() => Unit, Unit],
rootNode: AppNode2 @@ GameAppTags.RootNode
rootNode: RootNode,
schedulers: Schedulers
)(implicit
spawnProtocol: ActorRef[SpawnProtocol.Command],
timeout: Timeout,
@ -213,17 +212,104 @@ class MainAppDelegate(
// _ <- Task(consoleStream.println("text"))
level <- DefaultGameLevel(assetManager, viewPort)
_ <- level.addToGame(rootNode, physicsSpace)
_ <- createPlayerController()
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)
_ <-
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(
@ -238,12 +324,12 @@ class MainAppDelegate(
def createPlayerController(
// appScheduler: monix.execution.Scheduler
): IO[AppError, ActorRef[PlayerActorSupervisor.Command]] = {
): IO[AppError, PlayerActorSupervisor.Ref] = {
val playerPos = ImVector3f.Zero
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
val playerPhysicsControl =
PlayerController.Defaults.defaultPlayerPhysicsControl
.taggedWith[PlayerControllerTags.PlayerTag]
for {
playerModel <-
assetManager
@ -260,42 +346,115 @@ class MainAppDelegate(
)
cameraPivotNode <- UIO(
new Node(EntityIds.CameraPivot.value)
.withControl(new FollowControl(playerNode))
.taggedWith[PlayerControllerTags.PlayerCameraPivotNode]
.withControl(
new FollowControl(playerNode)
)
.taggedWith[PlayerController.Tags.PlayerCameraPivotNode]
)
camNode <- UIO(
PlayerController.Defaults
.defaultCamerNode(camera, playerPos)
.taggedWith[PlayerControllerTags.PlayerCameraNode]
.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, ActorRef[NpcActorSupervisor.Command]] = {
): 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(
NpcActorSupervisor
.Props(
new NpcMovementActor.Props(
enqueueR,
initialPos,
// tickEventBus,
npcName,
npcPhysicsControl
).behavior,
new NpcActorSupervisor.Props(
new NpcMovementActor.Props(
enqueueR,
initialPos,
npcName,
initialPos
)
.behavior,
s"${npcName}-npcActorSupervisor"
npcPhysicsControl
).behavior,
npcName,
initialPos
).behavior,
actorName = Some(s"${npcName}-npcActorSupervisor")
)
(for {
@ -325,12 +484,3 @@ class MainAppDelegate(
}
}
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 = {}
}

View File

@ -38,10 +38,9 @@ import wow.doge.mygame.implicits._
import wow.doge.mygame.utils.AkkaUtils
import wow.doge.mygame.utils.GenericTimerActor
import wow.doge.mygame.utils.wrappers.jme._
import wow.doge.mygame.types._
object GameAppTags {
sealed trait RootNode
sealed trait GuiNode
}
@ -49,34 +48,39 @@ class GameApp private[game] (
logger: Logger[Task],
app: SimpleAppExt,
gameActor: ActorRef[TestGameActor.Command],
gameSpawnProtocol: ActorRef[SpawnProtocol.Command]
gameSpawnProtocol: ActorRef[SpawnProtocol.Command],
scheduler: akka.actor.typed.Scheduler
) {
def inputManager: UIO[InputManager] = UIO(app.getInputManager())
def assetManager = new AssetManager(app.getAssetManager())
def guiNode = UIO(app.getGuiNode().taggedWith[GameAppTags.GuiNode])
val guiNode2 = AppNode2(app.getGuiNode()).taggedWith[GameAppTags.GuiNode]
def flyCam = Option(app.getFlyByCamera())
val assetManager = new AssetManager(app.getAssetManager())
val guiNode: GuiNode =
AppNode2(app.getGuiNode()).taggedWith[GameAppTags.GuiNode]
// def flyCam = Option(app.getFlyByCamera())
def camera = UIO(app.getCamera())
def viewPort = UIO(app.getViewPort())
// def rootNode = UIO(app.getRootNode().taggedWith[GameAppTags.RootNode])
val rootNode =
val rootNode: RootNode =
AppNode2(app.getRootNode()).taggedWith[GameAppTags.RootNode]
val physicsSpace = new PhysicsSpace(app.bulletAppState.physicsSpace)
val physicsSpace =
new PhysicsSpace(app.bulletAppState.physicsSpace)
def enqueue(cb: () => Unit) = app.enqueueR(() => cb())
def enqueueL[T](cb: () => T): Task[T] = app.enqueueL(cb)
def whenTerminated: IO[AppError, Unit] =
IO.deferFuture(app.whenTerminated).onErrorHandleWith(TimeoutError.from)
def spawnGameActor[T](
behavior: Behavior[T],
actorName: String,
actorName: Option[String] = None,
props: Props = Dispatchers.jmeDispatcher
)(implicit scheduler: akka.actor.typed.Scheduler) =
)(implicit name: sourcecode.Name) =
AkkaUtils.spawnActorL(behavior, actorName, props)(
2.seconds,
scheduler,
gameSpawnProtocol
gameSpawnProtocol,
name
)
def scheduler = app.scheduler
def scheduler = JmeScheduler(app.scheduler)
def jfxUI = JFxUI(app)
}
@ -92,10 +96,9 @@ class GameAppResource(
) {
def resource
: Resource[UIO, Either[AppError, (GameApp, Fiber[Nothing, Unit])]] =
Resource.make {
lazy val bullet = new BulletAppState
Resource.make(
(for {
app <- UIO(new SimpleAppExt(schedulers, bullet))
app <- UIO(new SimpleAppExt(schedulers, new BulletAppState))
_ <- UIO(JMERunner.runner = Some(app.enqueue _))
_ <- UIO {
val settings = new AppSettings(true)
@ -113,23 +116,22 @@ class GameAppResource(
_ <- Task.deferFuture(app.started).onErrorHandleWith(TimeoutError.from)
testGameActor <- AkkaUtils.spawnActorL(
new TestGameActor.Props().create,
"testGameActor",
Dispatchers.jmeDispatcher
Some("testGameActor"),
props = Dispatchers.jmeDispatcher
)
sp <-
testGameActor
.askL(TestGameActor.GetSpawnProtocol(_))
.askL(TestGameActor.GetSpawnProtocol)
.onErrorHandleWith(TimeoutError.from)
gameApp <- UIO(new GameApp(logger, app, testGameActor, sp))
gameApp <- UIO(new GameApp(logger, app, testGameActor, sp, scheduler))
_ <- UIO {
val fut = () => testGameActor.ask(TestGameActor.Stop).flatten
app.cancelToken = Some(fut)
}
} yield (gameApp, fib)).attempt
} {
case Right(gameApp -> fib) =>
fib.cancel >> UIO(JMERunner.runner = None)
case Left(error) => IO.terminate(new Exception(error.toString))
) {
case Right(gameApp -> fib) => fib.cancel
case Left(error) => IO.terminate(new Exception(error.toString))
}
}
@ -213,10 +215,10 @@ object Ops {
object SpawnSystem {
sealed trait Result
final case object Ok extends Result
case object Ok extends Result
sealed trait Complete
final case object Complete extends Complete
case object Complete extends Complete
sealed trait SpawnRequest
final case class SpawnSpatial(nodeTask: Task[Node]) extends SpawnRequest

View File

@ -24,18 +24,18 @@ object GameAppActor {
Behaviors.setup[Command] { ctx =>
ctx.log.infoP("Hello from GameAppActor")
val renderTickGenerator =
ctx.spawn(
ctx.spawnN(
Behaviors
.supervise(renderTickGeneratorBehavior)
.onFailure[Exception](SupervisorStrategy.restart),
"tickGeneratorActor"
.onFailure[Exception](
SupervisorStrategy.restart.withLimit(2, 100.millis)
)
)
val tickGeneratorTimer = ctx.spawn(
val tickGeneratorTimer = ctx.spawnN(
GenericTimerActor
.Props(renderTickGenerator, TickGenerator.Send, 10.millis)
.behavior,
"tickGeneratorTimer"
.behavior
)
Behaviors.receiveMessage {

View File

@ -14,7 +14,6 @@ import monix.execution.Scheduler
import monix.execution.atomic.Atomic
import wow.doge.mygame.executors.GUIExecutorService
import wow.doge.mygame.executors.Schedulers
// import wow.doge.mygame.implicits._
class SimpleAppExt(
schedulers: Schedulers,
val bulletAppState: BulletAppState,
@ -28,11 +27,16 @@ class SimpleAppExt(
private val taskQueue2 = Atomic(Queue.empty[MyTask[_]])
private val startSignal: CancelablePromise[Unit] = CancelablePromise()
private val terminationSignal: CancelablePromise[Unit] = CancelablePromise()
var cancelToken: Option[() => Future[Unit]] = None
def started: CancelableFuture[Unit] = startSignal.future
def whenTerminated: CancelableFuture[Unit] = terminationSignal.future
// override def physicsSpace: PhysicsSpace = ???
override def simpleInitApp(): Unit = {
stateManager.attach(bulletAppState)
startSignal.success(())
@ -40,18 +44,19 @@ class SimpleAppExt(
override def simpleUpdate(tpf: Float): Unit = {}
override def stop(waitFor: Boolean): Unit = {
override def stop(waitFor: Boolean): Unit =
cancelToken match {
case Some(value) =>
value().foreach { _ =>
pprint.log("Called cancel in simpleapp")
super.stop(true)
terminationSignal.success(())
}
case None =>
pprint.log("Called cancel in simpleapp")
super.stop(true)
terminationSignal.success(())
}
}
def enqueueFuture[T](cb: () => T): CancelableFuture[T] = {
val p = CancelablePromise[T]()

View File

@ -1,294 +0,0 @@
package wow.doge.mygame.state
import scala.concurrent.duration.DurationInt
import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.scaladsl.TimerScheduler
import com.jme3.input.InputManager
import com.jme3.input.KeyInput
import com.jme3.input.controls.KeyTrigger
import com.jme3.math.Vector3f
import com.jme3.scene.Geometry
import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.movement.ImMovementActor
class PlayerMovementState(
// movementActor: ActorRef[MovementActor.Command],
// movementActorTimer: ActorRef[MovementActorTimer.Command],
imMovementActor: ActorRef[ImMovementActor.Command]
// geom: Geometry,
// camNode: CameraNode,
// playerNode: Node
// gameAppActor: ActorRef[GameAppActor.Command]
) extends MyBaseState
// with ActionListener
{
protected lazy val mat = MyMaterial(
assetManager = assetManager,
path = os.rel / "Common" / "MatDefs" / "Misc" / "Unshaded.j3md"
)
override protected def init(): Unit = {
// setupKeys(inputManager)
// println("playermovementstate " + Thread.currentThread().getName())
// geom.setMaterial(mat)
// camNode.setControlDir(ControlDirection.SpatialToCamera)
// // lazy val camNode = new CameraNode("CameraNode", simpleApp.getCamera())
// // camNode.setCamera(simpleApp.getCamera())
// discard {
// playerNode
// .child(camNode)
// .child(geom)
// // playerNode.children(Seq(camNode, geom))
// }
// discard { rootNode.withChild(playerNode) }
// camNode.setLocalTranslation(
// new Vector3f(0, 1.5f, 10)
// )
// camNode.lookAt(playerNode.getLocalTranslation(), Vector3f.UNIT_Y)
// movementActorTimer ! MovementActorTimer.Start(geom, cam)
// movementActorTimer ! MovementActorTimer.Start
}
override def update(tpf: Float) = {
// movementActor ! MovementActor.Tick(tpf, geom, cam)
// imMovementActor ! ImMovementActor.Tick(tpf)
// movementActorTimer ! MovementActorTimer.Update(tpf)
}
override def stop(): Unit = {}
// override protected def cleanup(app: Application): Unit = {
// // gameAppActor ! GameAppActor.Stop
// super.cleanup(app)
// }
def setupKeys(inputManager: InputManager) = {
inputManager
.withMapping(
"Left",
// new KeyTrigger(KeyInput.KEY_A),
new KeyTrigger(KeyInput.KEY_LEFT)
)
.withMapping(
"Right",
// new KeyTrigger(KeyInput.KEY_D),
new KeyTrigger(KeyInput.KEY_RIGHT)
)
.withMapping(
"Up",
// new KeyTrigger(KeyInput.KEY_W),
new KeyTrigger(KeyInput.KEY_UP)
)
.withMapping(
"Down",
// new KeyTrigger(KeyInput.KEY_S),
new KeyTrigger(KeyInput.KEY_DOWN)
)
.withMapping(
"Space",
new KeyTrigger(KeyInput.KEY_SPACE),
new KeyTrigger(KeyInput.KEY_H)
)
.withMapping(
"Reset",
new KeyTrigger(KeyInput.KEY_R),
new KeyTrigger(KeyInput.KEY_RETURN)
)
// .withListener(this, "Left")
// .withListener(this, "Right")
// .withListener(this, "Up")
// .withListener(this, "Down")
// .withListener(this, "Space")
// .withListener(this, "Reset")
}
// def onAction(binding: String, value: Boolean, tpf: Float) =
// binding match {
// case "Left" => imMovementActor ! ImMovementActor.MovedLeft(value)
// case "Right" => imMovementActor ! ImMovementActor.MovedRight(value)
// case "Up" => imMovementActor ! ImMovementActor.MovedUp(value)
// case "Down" => imMovementActor ! ImMovementActor.MovedDown(value)
// case "Space" =>
// case _ =>
// }
override protected def onEnable(): Unit = {}
override protected def onDisable(): Unit = {}
}
final case class CardinalDirection(
left: Boolean = false,
right: Boolean = false,
up: Boolean = false,
down: Boolean = false
)
object MovementActor {
sealed trait Command
// final case class Tick(tpf: Float, geom: Geometry, cam: Camera) extends Command
// final case class Tick(tpf: Float) extends Command
final case object Tick extends Command
sealed trait Movement extends Command
final case class MovedLeft(pressed: Boolean) extends Movement
final case class MovedUp(pressed: Boolean) extends Movement
final case class MovedRight(pressed: Boolean) extends Movement
final case class MovedDown(pressed: Boolean) extends Movement
final case class Props(app: com.jme3.app.Application, geom: Geometry)
/**
* Internal state of the actor
*
* @param cardinalDir Immutable, can be shared as is
* @param walkDirection scratch space to avoid allocations on every tick. Do not share outside the actor
*/
final case class State(
cardinalDir: CardinalDirection = CardinalDirection(),
walkDirection: Vector3f = Vector3f.UNIT_X
)
def apply(props: Props): Behavior[Command] =
Behaviors.setup(ctx => new MovementActor(ctx, props).receive(State()))
}
class MovementActor(
ctx: ActorContext[MovementActor.Command],
props: MovementActor.Props
) {
import MovementActor._
import com.softwaremill.quicklens._
def receive(state: MovementActor.State): Behavior[Command] =
Behaviors.receiveMessage { msg =>
msg match {
case m: Movement =>
m match {
case MovedLeft(pressed) =>
receive(state = state.modify(_.cardinalDir.left).setTo(pressed))
case MovedUp(pressed) =>
receive(state = state.modify(_.cardinalDir.up).setTo(pressed))
case MovedRight(pressed) =>
receive(state = state.modify(_.cardinalDir.right).setTo(pressed))
case MovedDown(pressed) =>
receive(state = state.modify(_.cardinalDir.down).setTo(pressed))
}
case Tick =>
val camDir =
props.app.getCamera.getDirection().clone().multLocal(0.6f)
val camLeft = props.app.getCamera.getLeft().clone().multLocal(0.4f)
val walkDir = state.walkDirection.set(0, 0, 0)
// val walkDir = new Vector3f
val dir = state.cardinalDir
if (dir.up) {
ctx.log.debugP("up")
// ctx.log.debugP(Thread.currentThread().getName())
// walkDir.addLocal(0, 0, -1)
walkDir += camDir
}
if (dir.left) {
ctx.log.debugP("left")
// walkDir.addLocal(-1, 0, 0)
walkDir.addLocal(camLeft)
}
if (dir.right) {
ctx.log.debugP("right")
// walkDir.addLocal(1, 0, 0)
walkDir.addLocal(camLeft.negateLocal())
}
if (dir.down) {
ctx.log.debugP("down")
walkDir.addLocal(camDir.negateLocal())
// walkDir.addLocal(0, 0, 1)
}
// (dir.up, dir.down, dir.left, dir.right) match {
// case (true, false, true, false) =>
// case _ =>
// }
walkDir.multLocal(2f)
// walkDir.multLocal(100f)
// .multLocal(tpf)
// val v = props.geom.getLocalTranslation()
// props.geom.setLocalTranslation(
// (v += walkDir)
// )
props.app.enqueue(new Runnable {
override def run(): Unit = {
// geom.setLocalTranslation(walkDir)
val v = props.geom.getLocalTranslation()
props.geom.setLocalTranslation(
(v += walkDir)
)
}
})
Behaviors.same
// receive(state = state.modify(_.walkDirection).setTo(walkDir))
}
}
}
object MovementActorTimer {
sealed trait Command
final case object Start extends Command
final case object Update extends Command
private case object Send extends Command
case object TimerKey
final case class Props(
timers: TimerScheduler[MovementActorTimer.Command],
target: ActorRef[MovementActor.Command]
)
final case class State()
def apply(target: ActorRef[MovementActor.Command]) =
Behaviors.withTimers[Command] { timers =>
new MovementActorTimer(Props(timers, target)).idle()
}
}
class MovementActorTimer(
props: MovementActorTimer.Props
) {
import MovementActorTimer._
// import com.softwaremill.quicklens._
def idle(): Behavior[Command] =
Behaviors.receiveMessage { msg =>
msg match {
case Start =>
props.timers.startTimerWithFixedDelay(
Send,
10.millis
)
active()
case _ => Behaviors.unhandled
}
}
def active(): Behavior[Command] =
Behaviors.receiveMessage { msg =>
msg match {
case Update => active()
case Send =>
props.target ! MovementActor.Tick
Behaviors.same
case _ => Behaviors.unhandled
}
}
}

View File

@ -0,0 +1,71 @@
package wow.doge.mygame.game.controls
import com.jme3.math.Quaternion
import com.jme3.scene.Node
import com.jme3.scene.control.AbstractControl
import com.jme3.renderer.RenderManager
import com.jme3.renderer.ViewPort
import monix.reactive.Observable
import wow.doge.mygame.game.subsystems.input.PlayerCameraInput
import com.jme3.scene.Spatial
import monix.{eval => me}
import com.jme3.math.FastMath
import com.jme3.math.Vector3f
import monix.execution.Cancelable
import monix.execution.Scheduler
class CameraMovementControl(
rotationBuf: Quaternion,
obs: Observable[PlayerCameraInput],
rotateFn: Quaternion => Unit
)(implicit s: Scheduler)
extends AbstractControl {
private var _event: PlayerCameraInput = null
private var _subscriptionToken: Cancelable = null
override def controlUpdate(tpf: Float): Unit =
if (_event != null)
_event match {
case PlayerCameraInput.CameraRotateLeft =>
val rot = rotationBuf
.fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
rotateFn(rot)
case PlayerCameraInput.CameraRotateRight =>
val rot = rotationBuf
.fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y)
rotateFn(rot)
case PlayerCameraInput.CameraRotateUp =>
val rot = rotationBuf
.fromAngleAxis(-1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
rotateFn(rot)
case PlayerCameraInput.CameraRotateDown =>
val rot = rotationBuf
.fromAngleAxis(1 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X)
rotateFn(rot)
}
override def controlRender(
x$1: RenderManager,
x$2: ViewPort
): Unit = {}
override def setSpatial(spatial: Spatial): Unit = {
super.setSpatial(spatial)
if (this.spatial != null)
_subscriptionToken =
obs.doOnNext(event => me.Task { _event = event }).subscribe()
else {
_subscriptionToken.cancel()
_subscriptionToken = null
}
}
}
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 = {}
}

View File

@ -30,6 +30,8 @@ import wow.doge.mygame.subsystems.movement.ImMovementActor
import wow.doge.mygame.utils.GenericTimerActor
object NpcActorSupervisor {
type Ref = ActorRef[Command]
sealed trait Command
final case class Move(pos: ImVector3f) extends Command
private final case class InternalMove(
@ -40,13 +42,13 @@ object NpcActorSupervisor {
private case class LogError(err: Throwable) extends Command
private case class MovementFailed(err: Throwable) extends Command
final case class Props(
npcMovementActorBehavior: Behavior[NpcMovementActor.Command],
npcName: String,
initialPos: ImVector3f
class Props(
val npcMovementActorBehavior: Behavior[NpcMovementActor.Command],
val npcName: String,
val initialPos: ImVector3f
) {
def behavior =
Behaviors.withMdc(Map("actorName" -> npcName))(
Behaviors.withMdc[Command](Map("actorName" -> npcName))(
Behaviors.setup[Command] { ctx =>
val npcMovementActor = ctx.spawn(
(npcMovementActorBehavior),
@ -164,7 +166,7 @@ object NpcMovementActor {
doneSignal: ActorRef[CancelableFuture[DoneMoving.type]]
) extends Command
final class Props[T: CanMove](
class Props[T: CanMove](
val enqueueR: Function1[() => Unit, Unit],
val initialPos: ImVector3f,
// val tickEventBus: GameEventBus[TickEvent],
@ -196,7 +198,7 @@ class NpcMovementActor[T](
target: ImVector3f,
replyTo: ActorRef[CancelableFuture[DoneMoving.type]]
) =>
props.enqueueR(() => cm.move(props.movable, target - location))
props.enqueueR(() => cm.move(props.movable, target - location, 20f))
val p = CancelablePromise[DoneMoving.type]()
replyTo ! p.future
ticking(p, target, state)
@ -221,22 +223,22 @@ class NpcMovementActor[T](
ctx.self ! StopMoving
reachDestination.success(DoneMoving)
} else {
// format:off
ctx.log.traceP(
show"npcActor-${props.npcName}: Difference = " + dst.formatted(
"%.2f"
)
show"npcActor-${props.npcName}: Difference = ${dst.formatted("%.2f")}"
)
ctx.log.traceP(
show"npcActor-${props.npcName}: Current pos = " + location
show"npcActor-${props.npcName}: Current pos = $location"
)
}
Behaviors.same
}
}
object NpcMovementActorNotUsed {
sealed trait Command
final case class Props(
class Props(
imMovementActorBehavior: Behavior[ImMovementActor.Command],
npcName: String,
// movementActor: ActorRef[ImMovementActor.Command],
@ -251,7 +253,9 @@ object NpcMovementActorNotUsed {
val movementActor = ctx.spawn(
Behaviors
.supervise(imMovementActorBehavior)
.onFailure[Exception](SupervisorStrategy.restart),
.onFailure[Exception](
SupervisorStrategy.restart.withLimit(2, 100.millis)
),
s"npc-${npcName}-MovementActorChild"
)
val npcMovementElBehavior = Behaviors.receiveMessagePartial[Event] {
@ -259,19 +263,19 @@ object NpcMovementActorNotUsed {
event match {
case MovedLeft(name, pressed) =>
if (name == npcName)
movementActor ! ImMovementActor.MovedLeft(pressed)
movementActor ! ImMovementActor.MoveLeft(pressed)
Behaviors.same
case MovedUp(name, pressed) =>
if (name == npcName)
movementActor ! ImMovementActor.MovedUp(pressed)
movementActor ! ImMovementActor.MoveUp(pressed)
Behaviors.same
case MovedRight(name, pressed) =>
if (name == npcName)
movementActor ! ImMovementActor.MovedRight(pressed)
movementActor ! ImMovementActor.MoveRight(pressed)
Behaviors.same
case MovedDown(name, pressed) =>
if (name == npcName)
movementActor ! ImMovementActor.MovedDown(pressed)
movementActor ! ImMovementActor.MoveDown(pressed)
Behaviors.same
}
}
@ -279,7 +283,9 @@ object NpcMovementActorNotUsed {
val npcMovementEl = ctx.spawn(
Behaviors
.supervise(npcMovementElBehavior)
.onFailure[Exception](SupervisorStrategy.restart),
.onFailure[Exception](
SupervisorStrategy.restart.withLimit(2, 100.millis)
),
s"npc-${npcName}-MovementEventHandler"
)
val renderTickElBehavior =

View File

@ -1,13 +1,17 @@
package wow.doge.mygame.game.entities
import scala.concurrent.duration._
import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior
import akka.actor.typed.LogOptions
import akka.actor.typed.PostStop
import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors
import com.typesafe.scalalogging.Logger
import org.slf4j.event.Level
import wow.doge.mygame.Dispatchers
import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.events.EventBus
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
@ -15,125 +19,250 @@ import wow.doge.mygame.subsystems.events.PlayerEvent
import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
import wow.doge.mygame.subsystems.movement.ImMovementActor
import scala.util.Success
import scala.util.Failure
import akka.util.Timeout
import monix.reactive.Observable
import monix.reactive.subjects.ConcurrentSubject
import monix.execution.Scheduler
import monix.reactive.OverflowStrategy
object PlayerActorSupervisor {
type Ref = ActorRef[PlayerActorSupervisor.Command]
sealed trait Status
object Status {
case object Alive extends Status
case object Dead extends Status
}
sealed trait Command
final case class Props(
playerEventBus: GameEventBus[PlayerEvent],
tickEventBus: GameEventBus[TickEvent],
imMovementActorBehavior: Behavior[ImMovementActor.Command],
playerCameraActorBehavior: Behavior[PlayerCameraActor.Command]
case class TakeDamage(value: Int) extends Command
case class Heal(value: Int) extends Command
case class CurrentStats(replyTo: ActorRef[StatsActor.State]) extends Command
case class GetStatus(replyTo: ActorRef[Status]) extends Command
case class GetStatsObservable(replyTo: ActorRef[Observable[StatsActor.State]])
extends Command
private case object Die extends Command
private case class DamageResponse(response: (Boolean, StatsActor.State))
extends Command
// private case class InternalTakeDamage(old: Int, value: Int) extends Command
private case class LogError(ex: Throwable) extends Command
class Props(
val playerEventBus: GameEventBus[PlayerEvent],
val tickEventBus: GameEventBus[TickEvent],
val imMovementActorBehavior: Behavior[ImMovementActor.Command],
val scheduler: Scheduler
) {
def behavior =
Behaviors.logMessages(
LogOptions()
.withLevel(Level.TRACE)
.withLevel(Level.DEBUG)
.withLogger(
Logger[PlayerActorSupervisor].underlying
),
Behaviors.setup[Command] { ctx =>
ctx.log.infoP("Starting PlayerActor")
Behaviors
.setup[Command] { ctx =>
ctx.log.infoP("Starting PlayerActor")
// spawn children actors
val movementActor =
ctx.spawn(
// spawn children actors
val playerMovementActor =
ctx.spawnN(
Behaviors
.supervise(imMovementActorBehavior)
.onFailure[Exception](
SupervisorStrategy.restart.withLimit(2, 100.millis)
),
Dispatchers.jmeDispatcher
)
val playerStatsActor =
ctx.spawnN(new StatsActor.Props(100, 100).behavior)
val playerMovementEl = ctx.spawnN(
Behaviors
.supervise(imMovementActorBehavior)
.onFailure[Exception](SupervisorStrategy.restart),
"playerMovementActor"
.supervise(PlayerMovementEventListener(playerMovementActor))
.onFailure[Exception](
SupervisorStrategy.restart.withLimit(2, 100.millis)
)
)
val playerCameraActor =
ctx.spawn(playerCameraActorBehavior, "playerCameraActor")
val renderTickEl = {
val behavior: Behavior[RenderTick.type] =
Behaviors.setup(ctx =>
Behaviors
.receiveMessage[RenderTick.type] {
case RenderTick =>
playerMovementActor ! ImMovementActor.Tick
// playerCameraActor ! PlayerCameraActor.Tick
Behaviors.same
}
.receiveSignal {
case (_, PostStop) =>
ctx.log.infoP("stopped")
Behaviors.same
}
)
ctx.spawn(behavior, "playerMovementTickListener")
}
val playerCameraEl = ctx.spawn(
PlayerCameraEventListener(playerCameraActor),
"playerCameraActorEl"
)
//init listeners
playerEventBus ! EventBus.Subscribe(playerMovementEl)
tickEventBus ! EventBus.Subscribe(renderTickEl)
val playerMovementEl = ctx.spawn(
Behaviors
.supervise(PlayerMovementEventListener(movementActor))
.onFailure[Exception](SupervisorStrategy.restart),
"playerMovementEventHandler"
)
val renderTickEl = {
val behavior =
Behaviors.receiveMessage[RenderTick.type] {
case RenderTick =>
movementActor ! ImMovementActor.Tick
// playerCameraActor ! PlayerCameraActor.Tick
Behaviors.same
}
ctx.spawn(behavior, "playerMovementTickListener")
new PlayerActorSupervisor(
ctx,
this,
Children(playerMovementActor, playerStatsActor),
ConcurrentSubject.publish(OverflowStrategy.DropOld(50))(scheduler)
).aliveState
}
//init listeners
playerEventBus ! EventBus.Subscribe(playerMovementEl)
tickEventBus ! EventBus.Subscribe(renderTickEl)
playerEventBus ! EventBus.Subscribe(playerCameraEl)
new PlayerActorSupervisor(
ctx,
this,
Children(movementActor)
).receive
}
)
}
case class Children(
movementActor: ActorRef[ImMovementActor.Command]
movementActor: ActorRef[ImMovementActor.Command],
statsActor: ActorRef[StatsActor.Command]
)
}
class PlayerActorSupervisor(
ctx: ActorContext[PlayerActorSupervisor.Command],
props: PlayerActorSupervisor.Props,
children: PlayerActorSupervisor.Children
children: PlayerActorSupervisor.Children,
statsSubject: ConcurrentSubject[StatsActor.State, StatsActor.State]
) {
import PlayerActorSupervisor._
def receive =
implicit val timeout = Timeout(1.second)
val aliveState =
Behaviors
.receiveMessage[Command] {
case _ =>
case TakeDamage(value) =>
// children.movementActor ! ImMovementActor.MovedDown(true)
// ctx.ask(children.statsActor, StatsActor.CurrentStats(_)) {
// case Success(status) => InternalTakeDamage(status.hp, value)
// case Failure(ex) => LogError(ex)
// }
ctx.ask(children.statsActor, StatsActor.TakeDamageResult(value, _)) {
case Success(response) => DamageResponse(response)
case Failure(ex) => LogError(ex)
}
Behaviors.same
case CurrentStats(replyTo) =>
// ctx.ask(children.statsActor, StatsActor.CurrentStats())
children.statsActor ! StatsActor.CurrentStats(replyTo)
Behaviors.same
case Heal(value) =>
children.statsActor ! StatsActor.Heal(value)
Behaviors.same
case GetStatus(replyTo) =>
replyTo ! Status.Alive
Behaviors.same
// case _ => Behaviors.unhandled
// case InternalTakeDamage(hp, damage) =>
// if (hp - damage <= 0) dead
// else {
// children.statsActor ! StatsActor.TakeDamage(damage)
// Behaviors.same
// }
case GetStatsObservable(replyTo) =>
replyTo ! statsSubject
Behaviors.same
case DamageResponse(response) =>
response match {
case (dead,