From 4ff54c13733392d03392d2f2cbb3bbc53d82128b Mon Sep 17 00:00:00 2001 From: Rohan Sircar Date: Wed, 10 Mar 2021 19:24:52 +0530 Subject: [PATCH] Use monix observable based player movement reducer --- src/main/scala/wow/doge/mygame/Main.scala | 3 +- .../wow/doge/mygame/game/GameAppActor.scala | 12 +- .../game/entities/player/PlayerActor.scala | 26 ++-- .../player/PlayerMovementReducer.scala | 139 ++++++++++++++++++ .../player/behaviors/IdleBehavior.scala | 1 - .../doge/mygame/utils/MovementDirection.scala | 4 +- 6 files changed, 162 insertions(+), 23 deletions(-) create mode 100644 src/main/scala/wow/doge/mygame/game/entities/player/PlayerMovementReducer.scala diff --git a/src/main/scala/wow/doge/mygame/Main.scala b/src/main/scala/wow/doge/mygame/Main.scala index cc67f38..f90234b 100644 --- a/src/main/scala/wow/doge/mygame/Main.scala +++ b/src/main/scala/wow/doge/mygame/Main.scala @@ -19,6 +19,7 @@ import wow.doge.mygame.ActorSystemResource import wow.doge.mygame.executors.ExecutorsModule import wow.doge.mygame.types.AkkaScheduler import wow.doge.mygame.utils.GenericConsoleStream +import io.odin object Main extends BIOApp with ExecutorsModule { import java.util.logging.{Logger => JLogger, Level} JLogger.getLogger("").setLevel(Level.SEVERE) @@ -29,7 +30,7 @@ object Main extends BIOApp with ExecutorsModule { def appResource(consoleStream: GenericConsoleStream[TextArea]) = for { logger <- - consoleLogger().withAsync( + consoleLogger(minLevel = odin.Level.Debug).withAsync( timeWindow = 1.milliseconds, maxBufferSize = Some(100) ) |+| diff --git a/src/main/scala/wow/doge/mygame/game/GameAppActor.scala b/src/main/scala/wow/doge/mygame/game/GameAppActor.scala index c2e1a43..1b4cff7 100644 --- a/src/main/scala/wow/doge/mygame/game/GameAppActor.scala +++ b/src/main/scala/wow/doge/mygame/game/GameAppActor.scala @@ -49,12 +49,12 @@ object GameAppActor { val sp = ctx.spawn(SpawnProtocol(), "gameSpawnProtocol") - ctx.spawn( - GenericTimerActor - .Props(ctx.self, Ping, 1000.millis) - .behavior, - "pingTimer" - ) ! GenericTimerActor.Start + // ctx.spawn( + // GenericTimerActor + // .Props(ctx.self, Ping, 1000.millis) + // .behavior, + // "pingTimer" + // ) ! GenericTimerActor.Start val stopPromise = CancelablePromise[Unit]() diff --git a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActor.scala b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActor.scala index f0276e7..7b1e916 100644 --- a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActor.scala +++ b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerActor.scala @@ -114,18 +114,18 @@ object PlayerActor { Dispatchers.jmeDispatcher ) - val playerMovementEl = ctx.spawnN( - Behaviors - .supervise( - new PlayerMovementEventListener.Props( - ctx.self, - scheduler - ).behavior - ) - .onFailure[Exception]( - SupervisorStrategy.restart.withLimit(2, 100.millis) - ) - ) + // val playerMovementEl = ctx.spawnN( + // Behaviors + // .supervise( + // new PlayerMovementEventListener.Props( + // ctx.self, + // scheduler + // ).behavior + // ) + // .onFailure[Exception]( + // SupervisorStrategy.restart.withLimit(2, 100.millis) + // ) + // ) val renderTickEl = { val behavior: Behavior[RenderTick.type] = @@ -147,7 +147,7 @@ object PlayerActor { } //init listeners - playerEventBus ! EventBus.Subscribe(playerMovementEl) + // playerEventBus ! EventBus.Subscribe(playerMovementEl) tickEventBus ! EventBus.Subscribe(renderTickEl) new PlayerActor( diff --git a/src/main/scala/wow/doge/mygame/game/entities/player/PlayerMovementReducer.scala b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerMovementReducer.scala new file mode 100644 index 0000000..844679d --- /dev/null +++ b/src/main/scala/wow/doge/mygame/game/entities/player/PlayerMovementReducer.scala @@ -0,0 +1,139 @@ +package wow.doge.mygame.game.entities.player + +import scala.concurrent.duration._ + +import akka.util.Timeout +import cats.syntax.eq._ +import monix.eval.Fiber +import monix.eval.Task +import monix.reactive.Observable +import monix.{eval => me} +import wow.doge.mygame.EnumActionEvent +import wow.doge.mygame.game.entities.CharacterStats +import wow.doge.mygame.game.subsystems.input.PlayerMovementInput +import wow.doge.mygame.game.subsystems.input.PlayerMovementInput.Jump +import wow.doge.mygame.game.subsystems.input.PlayerMovementInput.WalkBackward +import wow.doge.mygame.game.subsystems.input.PlayerMovementInput.WalkForward +import wow.doge.mygame.game.subsystems.input.PlayerMovementInput.WalkLeft +import wow.doge.mygame.game.subsystems.input.PlayerMovementInput.WalkRight +import wow.doge.mygame.implicits._ +import wow.doge.mygame.types.AkkaScheduler +import wow.doge.mygame.utils.MovementDirection +import io.odin.Logger + +class PlayerMovementReducer( + val playerActor: PlayerActor.Ref, + logger: Logger[monix.bio.Task] +)(implicit + akkaSched: AkkaScheduler +) { + import PlayerMovementReducer._ + import com.softwaremill.quicklens._ + + implicit val timeout = Timeout(1.second) + implicit val sched = akkaSched.value + + def staminaTimer(multiplier: Int) = + Task.deferAction(implicit s => + Observable + .interval(250.millis) + .doOnNextF(_ => logger.trace("Sending Stamina Consume Item")) + .mapEvalF(_ => + playerActor + .askL( + PlayerActor + .ConsumeStamina(CharacterStats.DamageStamina(1 * multiplier), _) + ) + ) + .doOnNext(stats => + if (stats.stamina.toInt === 0) + Task(playerActor ! PlayerActor.StopMoving) + else Task.unit + ) + .takeWhile(_.stamina.toInt >= 0) + .completedL + ) + + def staminaRegenTimer(multiplier: Int) = + Task.deferAction(implicit s => + Observable + .interval(500.millis) + .doOnNextF(_ => logger.trace("Sending Stamina Regen Item")) + .mapEvalF(_ => + playerActor.askL( + PlayerActor + .HealStamina(CharacterStats.HealStamina(1 * multiplier), _) + ) + ) + .takeWhile(_.stamina.toInt =!= 100) + .delayExecution(1.second) + .completedL + ) + + def handleStamina( + state: State, + pressed: Boolean, + consumptionMultiplier: Int, + regenMultiplier: Int + ): Task[State] = + state.staminaRegenTimer.cancel >> + (if (pressed) { + val nextState1 = + if (state.keysPressed === 0) + staminaTimer(consumptionMultiplier).start.map( + state.modify(_.staminaTimer).setTo + ) + else Task.pure(state) + val nextState2 = + nextState1.map(_.modify(_.keysPressed).using(_ + 1)) + + nextState2 + } else { + val nextState1 = state + .modify(_.keysPressed) + .using(_ - 1) + if (nextState1.keysPressed === 0) + nextState1.staminaTimer.cancel >> + staminaRegenTimer(regenMultiplier).start.map( + nextState1.modify(_.staminaRegenTimer).setTo + ) + else + Task.pure(nextState1) + }) + + def value( + state: State, + action: EnumActionEvent[PlayerMovementInput] + ): Task[State] = + action match { + case EnumActionEvent(WalkForward, pressed, tpf) => + playerActor ! PlayerActor.Walk(pressed, MovementDirection.Forward) + handleStamina(state, pressed, 1, 1) + case EnumActionEvent(WalkRight, pressed, tpf) => + playerActor ! PlayerActor.Walk(pressed, MovementDirection.Right) + handleStamina(state, pressed, 1, 1) + case EnumActionEvent(WalkLeft, pressed, tpf) => + playerActor ! PlayerActor.Walk(pressed, MovementDirection.Left) + handleStamina(state, pressed, 1, 1) + case EnumActionEvent(WalkBackward, pressed, tpf) => + playerActor ! PlayerActor.Walk(pressed, MovementDirection.Backward) + handleStamina(state, pressed, 1, 1) + case EnumActionEvent(Jump, pressed, tpf) => + if (pressed) playerActor ! PlayerActor.Jump else () + handleStamina(state, pressed, 10, 1) + } +} +object PlayerMovementReducer { + final case class State( + keysPressed: Int, + staminaTimer: Fiber[Unit], + staminaRegenTimer: Fiber[Unit] + ) + object State { + val empty = State( + 0, + me.Fiber(me.Task.unit, me.Task.unit), + me.Fiber(me.Task.unit, me.Task.unit) + ) + } +} 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 af2eb66..798127b 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 @@ -119,7 +119,6 @@ class IdleBehaviorFactory( Behaviors.same case PlayerActor.HealStamina(value, replyTo) => - ctx.log.debugP("Received heal stamina") implicit val ec = props.scheduler.value for { response <- children.statsActor.ask( diff --git a/src/main/scala/wow/doge/mygame/utils/MovementDirection.scala b/src/main/scala/wow/doge/mygame/utils/MovementDirection.scala index 3633271..4394d46 100644 --- a/src/main/scala/wow/doge/mygame/utils/MovementDirection.scala +++ b/src/main/scala/wow/doge/mygame/utils/MovementDirection.scala @@ -1,8 +1,8 @@ package wow.doge.mygame.utils -import enumeratum._ -import cats.kernel.Eq import cats.Show +import cats.kernel.Eq +import enumeratum._ sealed trait MovementDirection extends EnumEntry