From be9acf81d5193905f790b2c03a743f7f3c71a6ed Mon Sep 17 00:00:00 2001 From: Rohan Sircar Date: Sat, 6 Mar 2021 19:33:27 +0530 Subject: [PATCH] Player actor changes Rename PlayerActorSupervisor to PlayerActor Make PlayerEventListener able to handle multiple inputs --- src/main/scala/wow/doge/mygame/MainApp.scala | 10 ++--- ...ctorSupervisor.scala => PlayerActor.scala} | 34 +++++++-------- .../entities/player/PlayerController.scala | 12 ++++-- .../player/PlayerEventListeners.scala | 42 +++++++++++++------ .../player/behaviors/IdleBehavior.scala | 40 +++++++++--------- .../scala/wow/doge/mygame/WebsocketTest.scala | 27 ++++++++++++ 6 files changed, 104 insertions(+), 61 deletions(-) rename src/main/scala/wow/doge/mygame/game/entities/player/{PlayerActorSupervisor.scala => PlayerActor.scala} (90%) create mode 100644 src/test/scala/wow/doge/mygame/WebsocketTest.scala diff --git a/src/main/scala/wow/doge/mygame/MainApp.scala b/src/main/scala/wow/doge/mygame/MainApp.scala index 8f2408b..09fdb43 100644 --- a/src/main/scala/wow/doge/mygame/MainApp.scala +++ b/src/main/scala/wow/doge/mygame/MainApp.scala @@ -52,7 +52,7 @@ 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.PlayerActorSupervisor +import wow.doge.mygame.game.entities.player.PlayerActor import wow.doge.mygame.game.entities.player.PlayerController import wow.doge.mygame.game.subsystems.input.GameInputHandler import wow.doge.mygame.game.subsystems.input.PlayerCameraInput @@ -203,7 +203,7 @@ class MainApp( .use(_ => launchSignal.get) .hideErrors tickEventBus <- - eventsModule.tickEventBus.hideErrorsWith(e => DummyException(e.toString)) + eventsModule.tickEventBus.hideErrorsWith(e => new Exception(e.toString)) _ <- /** * User chose to quit @@ -288,7 +288,7 @@ class MainAppDelegate( (loggerL.debug(show"Received Damage Event $event") >> (if (event.victimName === "PlayerNode") playerActor - .askL(PlayerActorSupervisor.TakeDamage(event.amount, _)) + .askL(PlayerActor.TakeDamage(event.amount, _)) .onErrorHandle { case ex: TimeoutException => () } else IO.unit)).toTask ) @@ -377,7 +377,7 @@ class MainAppDelegate( // .startAndForget statsObs <- playerActor - .askL(PlayerActorSupervisor.GetStatsObservable(_)) + .askL(PlayerActor.GetStatsObservable(_)) .onErrorHandleWith(TimeoutError.from) .flatten playerHud <- @@ -480,7 +480,7 @@ class MainAppDelegate( def createPlayerController( // appScheduler: monix.execution.Scheduler - ): IO[AppError, PlayerActorSupervisor.Ref] = { + ): 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" diff --git a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActor.scala similarity index 90% rename from src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala rename to src/main/scala/wow/doge/mygame/game/entities/player/PlayerActor.scala index 9ac5d38..25dfc2e 100644 --- a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActorSupervisor.scala +++ b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActor.scala @@ -30,10 +30,9 @@ import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.subsystems.events.TickEvent.RenderTick import wow.doge.mygame.subsystems.movement.ImMovementActor -//TODO: Change name to PlayerActor -object PlayerActorSupervisor { +object PlayerActor { - type Ref = ActorRef[PlayerActorSupervisor.Command] + type Ref = ActorRef[PlayerActor.Command] sealed trait Status object Status { @@ -71,6 +70,7 @@ object PlayerActorSupervisor { val playerEventBus: GameEventBus[PlayerEvent], val tickEventBus: GameEventBus[TickEvent], val imMovementActorBehavior: Behavior[ImMovementActor.Command], + val statsActorBehavior: Behavior[StatsActor.Command], val scheduler: AsyncScheduler, val fxScheduler: Schedulers.FxScheduler, val statsQueue: AsyncQueue[CharacterStats] @@ -79,20 +79,14 @@ object PlayerActorSupervisor { Behaviors.logMessages( LogOptions() .withLevel(Level.DEBUG) - .withLogger(Logger[PlayerActorSupervisor].underlying), + .withLogger(Logger[PlayerActor].underlying), Behaviors .setup[Command] { ctx => ctx.log.infoP("Starting PlayerActor") // spawn children actors - val playerStatsActor = - ctx.spawnN( - new StatsActor.Props( - CharacterStats.Health(100), - CharacterStats.Stamina(100) - ).behavior - ) + val playerStatsActor = ctx.spawnN(statsActorBehavior) val playerMovementActor = ctx.spawnN( @@ -141,7 +135,7 @@ object PlayerActorSupervisor { playerEventBus ! EventBus.Subscribe(playerMovementEl) tickEventBus ! EventBus.Subscribe(renderTickEl) - new PlayerActorSupervisor( + new PlayerActor( ctx, this, Children(playerMovementActor, playerStatsActor) @@ -157,18 +151,18 @@ object PlayerActorSupervisor { ) final case class Env( - ctx: ActorContext[PlayerActorSupervisor.Command], - props: PlayerActorSupervisor.Props, - children: PlayerActorSupervisor.Children + ctx: ActorContext[PlayerActor.Command], + props: PlayerActor.Props, + children: PlayerActor.Children ) } -class PlayerActorSupervisor( - ctx: ActorContext[PlayerActorSupervisor.Command], - props: PlayerActorSupervisor.Props, - children: PlayerActorSupervisor.Children +class PlayerActor( + ctx: ActorContext[PlayerActor.Command], + props: PlayerActor.Props, + children: PlayerActor.Children ) { - import PlayerActorSupervisor._ + import PlayerActor._ implicit val timeout = Timeout(1.second) val env = Env(ctx, props, children) diff --git a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala index 37fe903..3e324ab 100644 --- a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala +++ b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerController.scala @@ -26,6 +26,8 @@ import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.subsystems.movement.ImMovementActor import wow.doge.mygame.types._ import wow.doge.mygame.utils.wrappers.jme._ +import wow.doge.mygame.game.entities.StatsActor +import wow.doge.mygame.game.entities.CharacterStats object PlayerController { sealed trait Error @@ -65,10 +67,15 @@ object PlayerController { enqueueR, camera ).behavior(playerPhysicsControl) - new PlayerActorSupervisor.Props( + val statsActorBeh = new StatsActor.Props( + CharacterStats.Health(100), + CharacterStats.Stamina(100) + ).behavior + new PlayerActor.Props( playerEventBus, tickEventBus, movementActorBeh, + statsActorBeh, scheduler, fxScheduler, AsyncQueue.bounded(10)(scheduler.value) @@ -77,10 +84,9 @@ object PlayerController { val playerActor = gameApp.spawnGameActor( playerActorBehavior, - // Some("playerActorSupervisor"), props = DispatcherSelector.default() ) - val create: IO[AppError, ActorRef[PlayerActorSupervisor.Command]] = + val create: IO[AppError, ActorRef[PlayerActor.Command]] = (for { // playerActor <- // // AkkaUtils.spawnActorL(playerActorBehavior, "playerActorSupervisor") diff --git a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerEventListeners.scala b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerEventListeners.scala index 716312c..d61754f 100644 --- a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerEventListeners.scala +++ b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerEventListeners.scala @@ -18,19 +18,20 @@ import wow.doge.mygame.game.entities.CharacterStats import wow.doge.mygame.implicits._ import wow.doge.mygame.subsystems.events.PlayerMovementEvent import wow.doge.mygame.subsystems.movement.ImMovementActor +import cats.syntax.eq._ object PlayerMovementEventListener { - final case class State(staminaTimer: CancelableFuture[Unit]) + final case class State(keysPressed: Int, staminaTimer: CancelableFuture[Unit]) class Props( val movementActor: ActorRef[ImMovementActor.Command], - val playerActor: PlayerActorSupervisor.Ref, + val playerActor: PlayerActor.Ref, val asyncScheduler: Schedulers.AsyncScheduler ) { def behavior = Behaviors.setup[PlayerMovementEvent] { ctx => new PlayerMovementEventListener(ctx, this) - .receive(State(CancelableFuture.unit)) + .receive(State(0, CancelableFuture.unit)) } } } @@ -40,6 +41,7 @@ class PlayerMovementEventListener( ) { import PlayerMovementEventListener._ import PlayerMovementEvent._ + import com.softwaremill.quicklens._ def receive(state: State): Behavior[PlayerMovementEvent] = Behaviors.logMessages( LogOptions() @@ -57,28 +59,42 @@ class PlayerMovementEventListener( .interval(250.millis) .doOnNextF(_ => props.playerActor.askL( - PlayerActorSupervisor + PlayerActor .ConsumeStamina(CharacterStats.DamageStamina(1), _) ) ) .completedL ) - @SuppressWarnings(Array("org.wartremover.warts.Equals")) def handleStamina(pressed: Boolean) = - if (pressed) - if (state.staminaTimer == CancelableFuture.unit) - State(makeStaminaTimer.runToFuture(props.asyncScheduler.value)) - else state - else { - state.staminaTimer.cancel() - State(CancelableFuture.unit) + if (pressed) { + val nextState1 = + if (state.keysPressed === 0) + state + .modify(_.staminaTimer) + .setTo( + makeStaminaTimer.runToFuture(props.asyncScheduler.value) + ) + else state + val nextState2 = nextState1 + .modify(_.keysPressed) + .using(_ + 1) + nextState2 + } else { + val nextState1 = state + .modify(_.keysPressed) + .using(_ - 1) + if (nextState1.keysPressed === 0) { + nextState1.staminaTimer.cancel() + nextState1 + } else + nextState1 } Behaviors.receiveMessage { case PlayerMovedLeft(pressed) => // props.movementActor ! ImMovementActor.MoveLeft(pressed) - props.playerActor ! PlayerActorSupervisor.MoveLeft(pressed) + props.playerActor ! PlayerActor.MoveLeft(pressed) receive(handleStamina(pressed)) case PlayerMovedRight(pressed) => props.movementActor ! ImMovementActor.MoveRight(pressed) diff --git a/src/main/scala/wow/doge/mygame/game/entities/player/behaviors/IdleBehavior.scala b/src/main/scala/wow/doge/mygame/game/entities/player/behaviors/IdleBehavior.scala index 8ce3c88..fd6c34b 100644 --- a/src/main/scala/wow/doge/mygame/game/entities/player/behaviors/IdleBehavior.scala +++ b/src/main/scala/wow/doge/mygame/game/entities/player/behaviors/IdleBehavior.scala @@ -13,14 +13,14 @@ import monix.reactive.Observable import wow.doge.mygame.game.entities.CharacterStats import wow.doge.mygame.game.entities.StatsActor import wow.doge.mygame.game.entities.character.CharacterStates._ -import wow.doge.mygame.game.entities.player.PlayerActorSupervisor +import wow.doge.mygame.game.entities.player.PlayerActor import wow.doge.mygame.implicits._ import wow.doge.mygame.subsystems.movement.ImMovementActor class IdleBehaviorFactory( - env: PlayerActorSupervisor.Env, - nextStateFn: AliveSubstate => Behavior[PlayerActorSupervisor.Command], - deadState: Behavior[PlayerActorSupervisor.Command], + env: PlayerActor.Env, + nextStateFn: AliveSubstate => Behavior[PlayerActor.Command], + deadState: Behavior[PlayerActor.Command], consumptionMultiplier: Int => Int )(implicit timeout: Timeout) { import IdleBehaviorFactory.IdleBehavior @@ -31,23 +31,23 @@ class IdleBehaviorFactory( def behavior = IdleBehavior( Behaviors - .receiveMessage[PlayerActorSupervisor.Command] { - case PlayerActorSupervisor.MoveLeft(pressed) => + .receiveMessage[PlayerActor.Command] { + case PlayerActor.MoveLeft(pressed) => children.movementActor ! ImMovementActor.MoveLeft(pressed) if (pressed) nextStateFn(AliveSubstate.Moving(Walking)) else nextStateFn(AliveSubstate.Idle) - case PlayerActorSupervisor.TakeDamage(value, replyTo) => + case PlayerActor.TakeDamage(value, replyTo) => implicit val ec = props.scheduler.value for { res <- children.statsActor.ask(StatsActor.TakeDamageResult(value, _)) _ <- Future.successful( - ctx.self ! PlayerActorSupervisor.StatsResponse(res, replyTo) + ctx.self ! PlayerActor.StatsResponse(res, replyTo) ) } yield () Behaviors.same - case PlayerActorSupervisor.ConsumeStamina(value, replyTo) => + case PlayerActor.ConsumeStamina(value, replyTo) => implicit val ec = props.scheduler.value val newValue = CharacterStats.DamageStamina(consumptionMultiplier(value.toInt)) @@ -56,20 +56,20 @@ class IdleBehaviorFactory( StatsActor.ConsumeStaminaResult(newValue, _) ) _ = - ctx.self ! PlayerActorSupervisor + ctx.self ! PlayerActor .StatsResponse(response, replyTo) } yield () Behaviors.same - case PlayerActorSupervisor.CurrentStats(replyTo) => + case PlayerActor.CurrentStats(replyTo) => children.statsActor ! StatsActor.CurrentStats(replyTo) Behaviors.same - case PlayerActorSupervisor.Heal(value) => + case PlayerActor.Heal(value) => children.statsActor ! StatsActor.HealResult(value) Behaviors.same - case PlayerActorSupervisor.GetStatus(replyTo) => - replyTo ! PlayerActorSupervisor.Status.Alive + case PlayerActor.GetStatus(replyTo) => + replyTo ! PlayerActor.Status.Alive Behaviors.same - case PlayerActorSupervisor.GetStatsObservable(replyTo) => + case PlayerActor.GetStatsObservable(replyTo) => import monix.{eval => me} replyTo ! UIO( @@ -82,10 +82,10 @@ class IdleBehaviorFactory( ) Behaviors.same - case PlayerActorSupervisor.StatsResponse(response, replyTo) => + case PlayerActor.StatsResponse(response, replyTo) => response match { case (dead, stats) => - if (dead) ctx.self ! PlayerActorSupervisor.Die + if (dead) ctx.self ! PlayerActor.Die props.statsQueue .offer(stats) .foreach { _ => @@ -95,8 +95,8 @@ class IdleBehaviorFactory( } Behaviors.same // nextStateFn(InCombat(Moving(Walking))) - case PlayerActorSupervisor.Die => deadState - case PlayerActorSupervisor.LogError(ex) => + case PlayerActor.Die => deadState + case PlayerActor.LogError(ex) => ctx.log.error(ex.getMessage) Behaviors.same } @@ -110,6 +110,6 @@ class IdleBehaviorFactory( object IdleBehaviorFactory { final case class IdleBehavior( - value: Behavior[PlayerActorSupervisor.Command] + value: Behavior[PlayerActor.Command] ) } diff --git a/src/test/scala/wow/doge/mygame/WebsocketTest.scala b/src/test/scala/wow/doge/mygame/WebsocketTest.scala new file mode 100644 index 0000000..6b42eb1 --- /dev/null +++ b/src/test/scala/wow/doge/mygame/WebsocketTest.scala @@ -0,0 +1,27 @@ +package wow.doge.mygame + +import sttp.capabilities.monix.MonixStreams +import sttp.client3._ +import sttp.client3.asynchttpclient.monix._ + +import monix.eval.Task +import monix.reactive.Observable +import scala.concurrent.duration.Duration + +class WebsocketTest { + // : Task[Response[Either[String, Observable[Array[Byte]]]]] + + val s = AsyncHttpClientMonixBackend().flatMap { backend => + val response = + basicRequest + .post(uri"...") + .response( + asStream(MonixStreams)( + _.doOnNext(i => Task(println(s"$i"))).completedL + ) + ) + .readTimeout(Duration.Inf) + .send(backend) + response + } +}