From 9b484e895b4af7193a8f9d0552bc4bce4ddd9363 Mon Sep 17 00:00:00 2001 From: Rohan Sircar Date: Mon, 8 Mar 2021 21:17:14 +0530 Subject: [PATCH] Add stamina regen logic --- src/main/scala/wow/doge/mygame/MainApp.scala | 1 + .../mygame/game/entities/CharacterStats.scala | 42 ++++++++++---- .../game/entities/player/PlayerActor.scala | 33 ++++++----- .../entities/player/PlayerController.scala | 2 +- .../player/PlayerEventListeners.scala | 41 ++++++++++++-- .../player/behaviors/IdleBehavior.scala | 56 ++++++++++++++----- 6 files changed, 131 insertions(+), 44 deletions(-) diff --git a/src/main/scala/wow/doge/mygame/MainApp.scala b/src/main/scala/wow/doge/mygame/MainApp.scala index 12e9dfd..ed16f23 100644 --- a/src/main/scala/wow/doge/mygame/MainApp.scala +++ b/src/main/scala/wow/doge/mygame/MainApp.scala @@ -285,6 +285,7 @@ class MainAppDelegate( (if (event.victimName === "PlayerNode") playerActor .askL(PlayerActor.TakeDamage(event.amount, _)) + .void .onErrorHandle { case ex: TimeoutException => () } else IO.unit)).toTask ) diff --git a/src/main/scala/wow/doge/mygame/game/entities/CharacterStats.scala b/src/main/scala/wow/doge/mygame/game/entities/CharacterStats.scala index ed72822..21743a5 100644 --- a/src/main/scala/wow/doge/mygame/game/entities/CharacterStats.scala +++ b/src/main/scala/wow/doge/mygame/game/entities/CharacterStats.scala @@ -7,7 +7,6 @@ import akka.actor.typed.scaladsl.Behaviors import cats.Show import cats.kernel.Eq import io.estatico.newtype.macros.newtype -import wow.doge.mygame.game.entities.CharacterStats.HealHealth final case class CharacterStats( hp: CharacterStats.Health, @@ -39,17 +38,32 @@ object CharacterStats { } object StatsActor { + import CharacterStats._ + + sealed trait Status + object Status { + case object Alive extends Status + case object Dead extends Status + } sealed trait Command final case class TakeDamageResult( value: CharacterStats.DamageHealth, - replyTo: ActorRef[(Boolean, CharacterStats)] + replyTo: ActorRef[(Status, CharacterStats)] ) extends Command final case class ConsumeStaminaResult( value: CharacterStats.DamageStamina, - replyTo: ActorRef[(Boolean, CharacterStats)] + replyTo: ActorRef[(Status, CharacterStats)] + ) extends Command + final case class HealHealthResult( + value: HealHealth, + replyTo: ActorRef[CharacterStats] + ) extends Command + + final case class HealStaminaResult( + value: HealStamina, + replyTo: ActorRef[CharacterStats] ) extends Command - final case class HealResult(value: HealHealth) extends Command final case class CurrentStats(replyTo: ActorRef[CharacterStats]) extends Command @@ -88,27 +102,35 @@ class StatsActor( case TakeDamageResult(value, replyTo) => val nextState = if ((state.stats.hp - value).toInt <= 0) { val s = state.modify(_.stats.hp).setTo(Health(0)) - replyTo ! true -> s.stats + replyTo ! Status.Dead -> s.stats s } else { val s = state.modify(_.stats.hp).using(_ - value) - replyTo ! false -> s.stats + replyTo ! Status.Alive -> s.stats s } receive(nextState) case ConsumeStaminaResult(value, replyTo) => val nextState = if ((state.stats.stamina - value).toInt <= 0) { val s = state.modify(_.stats.stamina).setTo(Stamina(0)) - replyTo ! false -> s.stats + replyTo ! Status.Alive -> s.stats s } else { val s = state.modify(_.stats.stamina).using(_ - value) - replyTo ! false -> s.stats + replyTo ! Status.Alive -> s.stats s } receive(nextState) - case HealResult(value) => - receive(state.modify(_.stats.hp).using(_ :+ value)) + case HealHealthResult(value, replyTo) => + val nextState = receive(state.modify(_.stats.hp).using(_ :+ value)) + replyTo ! state.stats + nextState + + case HealStaminaResult(value, replyTo) => + val nextState = receive(state.modify(_.stats.stamina).using(_ :+ value)) + replyTo ! state.stats + nextState + case CurrentStats(replyTo) => replyTo ! state.stats Behaviors.same 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 325c2b4..157dfe7 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 @@ -43,13 +43,20 @@ object PlayerActor { sealed trait Command extends Product with Serializable final case class TakeDamage( value: CharacterStats.DamageHealth, - replyTo: ActorRef[Unit] + replyTo: ActorRef[CharacterStats] ) extends Command final case class ConsumeStamina( value: CharacterStats.DamageStamina, - replyTo: ActorRef[Unit] + replyTo: ActorRef[CharacterStats] + ) extends Command + final case class HealHealth( + value: CharacterStats.HealHealth, + replyTo: ActorRef[CharacterStats] + ) extends Command + final case class HealStamina( + value: CharacterStats.HealStamina, + replyTo: ActorRef[CharacterStats] ) extends Command - final case class Heal(value: CharacterStats.HealHealth) extends Command final case class CurrentStats(replyTo: ActorRef[CharacterStats]) extends Command final case class GetStatus(replyTo: ActorRef[Status]) extends Command @@ -69,8 +76,8 @@ object PlayerActor { private[player] case object Die extends Command private[player] final case class StatsResponse( - response: (Boolean, CharacterStats), - replyTo: ActorRef[Unit] + response: (StatsActor.Status, CharacterStats), + replyTo: ActorRef[CharacterStats] ) extends Command private[player] final case class LogError(ex: Throwable) extends Command class Props( @@ -225,14 +232,14 @@ class PlayerActor( case CombatSubstate.Attacking(victimName) => Behaviors.receiveMessage[Command](_ => Behaviors.same) } - case AliveSubstate.Moving(substate) => - substate match { - case Walking => - ctx.log.debugP("In Walking State") - idleBehavior(aliveState, _ * 2).value - case Running => - idleBehavior(aliveState, _ * 3).value - } + + case AliveSubstate.Moving(Walking) => + ctx.log.debugP("In Walking State") + idleBehavior(aliveState, _ * 2).value + + case AliveSubstate.Moving(Running) => + idleBehavior(aliveState, _ * 3).value + case AliveSubstate.Idle => ctx.log.debugP("In Idle State") idleBehavior(aliveState, identity).value 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 0e0238d..de509cf 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 @@ -78,7 +78,7 @@ object PlayerController { statsActorBeh, scheduler, fxScheduler, - AsyncQueue.bounded(10)(scheduler.value) + AsyncQueue.bounded(50)(scheduler.value) ).behavior } val playerActor = 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 0549a8c..c849e33 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 @@ -20,7 +20,11 @@ import wow.doge.mygame.implicits._ import wow.doge.mygame.subsystems.events.PlayerMovementEvent object PlayerMovementEventListener { - final case class State(keysPressed: Int, staminaTimer: CancelableFuture[Unit]) + final case class State( + keysPressed: Int, + staminaTimer: CancelableFuture[Unit], + staminaRegenTimer: CancelableFuture[Unit] + ) class Props( val playerActor: PlayerActor.Ref, @@ -29,7 +33,7 @@ object PlayerMovementEventListener { def behavior = Behaviors.setup[PlayerMovementEvent] { ctx => new PlayerMovementEventListener(ctx, this) - .receive(State(0, CancelableFuture.unit)) + .receive(State(0, CancelableFuture.unit, CancelableFuture.unit)) } } } @@ -51,27 +55,47 @@ class PlayerMovementEventListener( implicit val timeout = Timeout(1.second) implicit val sched = ctx.system.scheduler - def makeStaminaTimer = + val staminaTimer = Task.deferAction(implicit s => Observable .interval(250.millis) + .doOnNext(_ => Task(pprint.log("Sending Stamina Consume Item"))) .doOnNextF(_ => + props.playerActor + .askL( + PlayerActor + .ConsumeStamina(CharacterStats.DamageStamina(25), _) + ) + .void + ) + .completedL + ) + + val staminaRegenTimer = + Task.deferAction(implicit s => + Observable + .interval(500.millis) + .doOnNext(_ => Task(pprint.log("Sending Stamina Regen Item"))) + .mapEvalF(_ => props.playerActor.askL( PlayerActor - .ConsumeStamina(CharacterStats.DamageStamina(1), _) + .HealStamina(CharacterStats.HealStamina(1), _) ) ) + .takeWhile(_.stamina.toInt =!= 100) + .delayExecution(1.second) .completedL ) - def handleStamina(pressed: Boolean) = + def handleStamina(pressed: Boolean) = { + state.staminaRegenTimer.cancel() if (pressed) { val nextState1 = if (state.keysPressed === 0) state .modify(_.staminaTimer) .setTo( - makeStaminaTimer.runToFuture(props.asyncScheduler.value) + staminaTimer.runToFuture(props.asyncScheduler.value) ) else state val nextState2 = nextState1 @@ -85,9 +109,14 @@ class PlayerMovementEventListener( if (nextState1.keysPressed === 0) { nextState1.staminaTimer.cancel() nextState1 + .modify(_.staminaRegenTimer) + .setTo( + staminaRegenTimer.runToFuture(props.asyncScheduler.value) + ) } else nextState1 } + } Behaviors.receiveMessage { case PlayerMovedLeft(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 f6cea97..174c48a 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 @@ -37,7 +37,10 @@ class IdleBehaviorFactory( children.movementActor ! ImMovementActor.MoveLeft(pressed) if (pressed) nextStateFn(AliveSubstate.Moving(Walking)) else nextStateFn(AliveSubstate.Idle) - } else Behaviors.same + } else { + children.movementActor ! ImMovementActor.MoveLeft(false) + Behaviors.same + } case PlayerActor.MoveLeft(pressed) => implicit val ec = props.scheduler.value @@ -76,9 +79,8 @@ class IdleBehaviorFactory( for { res <- children.statsActor.ask(StatsActor.TakeDamageResult(value, _)) - _ <- Future.successful( - ctx.self ! PlayerActor.StatsResponse(res, replyTo) - ) + _ = ctx.self ! PlayerActor.StatsResponse(res, replyTo) + } yield () Behaviors.same @@ -93,6 +95,7 @@ class IdleBehaviorFactory( _ = ctx.self ! PlayerActor .StatsResponse(response, replyTo) + } yield () Behaviors.same @@ -100,8 +103,30 @@ class IdleBehaviorFactory( children.statsActor ! StatsActor.CurrentStats(replyTo) Behaviors.same - case PlayerActor.Heal(value) => - children.statsActor ! StatsActor.HealResult(value) + case PlayerActor.HealHealth(value, replyTo) => + implicit val ec = props.scheduler.value + for { + response <- children.statsActor.ask( + StatsActor.HealHealthResult(value, _) + ) + _ = + ctx.self ! PlayerActor + .StatsResponse(StatsActor.Status.Alive -> response, replyTo) + } yield () + Behaviors.same + + case PlayerActor.HealStamina(value, replyTo) => + ctx.log.debugP("Received heal stamina") + implicit val ec = props.scheduler.value + for { + response <- children.statsActor.ask( + StatsActor.HealStaminaResult(value, _) + ) + _ = + ctx.self ! PlayerActor + .StatsResponse(StatsActor.Status.Alive -> response, replyTo) + + } yield () Behaviors.same case PlayerActor.GetStatus(replyTo) => @@ -124,14 +149,17 @@ class IdleBehaviorFactory( case PlayerActor.StatsResponse(response, replyTo) => response match { - case (dead, stats) => - if (dead) ctx.self ! PlayerActor.Die - props.statsQueue - .offer(stats) - .foreach { _ => - pprint.log(show"Published stats $stats") - replyTo ! () - }(props.scheduler.value) + case (status, stats) => + status match { + case StatsActor.Status.Dead => ctx.self ! PlayerActor.Die + case StatsActor.Status.Alive => + props.statsQueue + .offer(stats) + .foreach { _ => + pprint.log(show"Published stats $stats") + replyTo ! stats + }(props.scheduler.value) + } } Behaviors.same