package wow.doge.mygame.state import scala.concurrent.duration.DurationInt import com.jme3.input.InputManager import com.jme3.input.KeyInput import com.jme3.input.controls.KeyTrigger import com.jme3.math.Vector3f import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.Behavior import akka.actor.typed.ActorRef import com.jme3.scene.Geometry import akka.actor.typed.scaladsl.TimerScheduler 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.debug("up") // ctx.log.debug(Thread.currentThread().getName()) // walkDir.addLocal(0, 0, -1) walkDir += camDir } if (dir.left) { ctx.log.debug("left") // walkDir.addLocal(-1, 0, 0) walkDir.addLocal(camLeft) } if (dir.right) { ctx.log.debug("right") // walkDir.addLocal(1, 0, 0) walkDir.addLocal(camLeft.negateLocal()) } if (dir.down) { ctx.log.debug("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 } } }