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) ) } }