Testing out JmonkeyEngine to make a game in Scala with Akka Actors within a pure FP layer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

139 lines
4.4 KiB

  1. package wow.doge.mygame.game.entities.player
  2. import scala.concurrent.duration._
  3. import akka.util.Timeout
  4. import cats.syntax.eq._
  5. import monix.eval.Fiber
  6. import monix.eval.Task
  7. import monix.reactive.Observable
  8. import monix.{eval => me}
  9. import wow.doge.mygame.EnumActionEvent
  10. import wow.doge.mygame.game.entities.CharacterStats
  11. import wow.doge.mygame.game.subsystems.input.PlayerMovementInput
  12. import wow.doge.mygame.game.subsystems.input.PlayerMovementInput.Jump
  13. import wow.doge.mygame.game.subsystems.input.PlayerMovementInput.WalkBackward
  14. import wow.doge.mygame.game.subsystems.input.PlayerMovementInput.WalkForward
  15. import wow.doge.mygame.game.subsystems.input.PlayerMovementInput.WalkLeft
  16. import wow.doge.mygame.game.subsystems.input.PlayerMovementInput.WalkRight
  17. import wow.doge.mygame.implicits._
  18. import wow.doge.mygame.types.AkkaScheduler
  19. import wow.doge.mygame.utils.MovementDirection
  20. import io.odin.Logger
  21. class PlayerMovementReducer(
  22. val playerActor: PlayerActor.Ref,
  23. logger: Logger[monix.bio.Task]
  24. )(implicit
  25. akkaSched: AkkaScheduler
  26. ) {
  27. import PlayerMovementReducer._
  28. import com.softwaremill.quicklens._
  29. implicit val timeout = Timeout(1.second)
  30. implicit val sched = akkaSched.value
  31. def staminaTimer(multiplier: Int) =
  32. Task.deferAction(implicit s =>
  33. Observable
  34. .interval(250.millis)
  35. .doOnNextF(_ => logger.trace("Sending Stamina Consume Item"))
  36. .mapEvalF(_ =>
  37. playerActor
  38. .askL(
  39. PlayerActor
  40. .ConsumeStamina(CharacterStats.DamageStamina(1 * multiplier), _)
  41. )
  42. )
  43. .doOnNext(stats =>
  44. if (stats.stamina.toInt === 0)
  45. Task(playerActor ! PlayerActor.StopMoving)
  46. else Task.unit
  47. )
  48. .takeWhile(_.stamina.toInt >= 0)
  49. .completedL
  50. )
  51. def staminaRegenTimer(multiplier: Int) =
  52. Task.deferAction(implicit s =>
  53. Observable
  54. .interval(500.millis)
  55. .doOnNextF(_ => logger.trace("Sending Stamina Regen Item"))
  56. .mapEvalF(_ =>
  57. playerActor.askL(
  58. PlayerActor
  59. .HealStamina(CharacterStats.HealStamina(1 * multiplier), _)
  60. )
  61. )
  62. .takeWhile(_.stamina.toInt =!= 100)
  63. .delayExecution(1.second)
  64. .completedL
  65. )
  66. def handleStamina(
  67. state: State,
  68. pressed: Boolean,
  69. consumptionMultiplier: Int,
  70. regenMultiplier: Int
  71. ): Task[State] =
  72. state.staminaRegenTimer.cancel >>
  73. (if (pressed) {
  74. val nextState1 =
  75. if (state.keysPressed === 0)
  76. staminaTimer(consumptionMultiplier).start.map(
  77. state.modify(_.staminaTimer).setTo
  78. )
  79. else Task.pure(state)
  80. val nextState2 =
  81. nextState1.map(_.modify(_.keysPressed).using(_ + 1))
  82. nextState2
  83. } else {
  84. val nextState1 = state
  85. .modify(_.keysPressed)
  86. .using(_ - 1)
  87. if (nextState1.keysPressed === 0)
  88. nextState1.staminaTimer.cancel >>
  89. staminaRegenTimer(regenMultiplier).start.map(
  90. nextState1.modify(_.staminaRegenTimer).setTo
  91. )
  92. else
  93. Task.pure(nextState1)
  94. })
  95. def value(
  96. state: State,
  97. action: EnumActionEvent[PlayerMovementInput]
  98. ): Task[State] =
  99. action match {
  100. case EnumActionEvent(WalkForward, pressed, tpf) =>
  101. playerActor ! PlayerActor.Walk(pressed, MovementDirection.Forward)
  102. handleStamina(state, pressed, 1, 1)
  103. case EnumActionEvent(WalkRight, pressed, tpf) =>
  104. playerActor ! PlayerActor.Walk(pressed, MovementDirection.Right)
  105. handleStamina(state, pressed, 1, 1)
  106. case EnumActionEvent(WalkLeft, pressed, tpf) =>
  107. playerActor ! PlayerActor.Walk(pressed, MovementDirection.Left)
  108. handleStamina(state, pressed, 1, 1)
  109. case EnumActionEvent(WalkBackward, pressed, tpf) =>
  110. playerActor ! PlayerActor.Walk(pressed, MovementDirection.Backward)
  111. handleStamina(state, pressed, 1, 1)
  112. case EnumActionEvent(Jump, pressed, tpf) =>
  113. if (pressed) playerActor ! PlayerActor.Jump else ()
  114. handleStamina(state, pressed, 10, 1)
  115. }
  116. }
  117. object PlayerMovementReducer {
  118. final case class State(
  119. keysPressed: Int,
  120. staminaTimer: Fiber[Unit],
  121. staminaRegenTimer: Fiber[Unit]
  122. )
  123. object State {
  124. val empty = State(
  125. 0,
  126. me.Fiber(me.Task.unit, me.Task.unit),
  127. me.Fiber(me.Task.unit, me.Task.unit)
  128. )
  129. }
  130. }