Add stamina regen logic

This commit is contained in:
Rohan Sircar 2021-03-08 21:17:14 +05:30
parent 1b9bb4265f
commit 9b484e895b
6 changed files with 134 additions and 47 deletions

View File

@ -285,6 +285,7 @@ class MainAppDelegate(
(if (event.victimName === "PlayerNode") (if (event.victimName === "PlayerNode")
playerActor playerActor
.askL(PlayerActor.TakeDamage(event.amount, _)) .askL(PlayerActor.TakeDamage(event.amount, _))
.void
.onErrorHandle { case ex: TimeoutException => () } .onErrorHandle { case ex: TimeoutException => () }
else IO.unit)).toTask else IO.unit)).toTask
) )

View File

@ -7,7 +7,6 @@ import akka.actor.typed.scaladsl.Behaviors
import cats.Show import cats.Show
import cats.kernel.Eq import cats.kernel.Eq
import io.estatico.newtype.macros.newtype import io.estatico.newtype.macros.newtype
import wow.doge.mygame.game.entities.CharacterStats.HealHealth
final case class CharacterStats( final case class CharacterStats(
hp: CharacterStats.Health, hp: CharacterStats.Health,
@ -39,17 +38,32 @@ object CharacterStats {
} }
object StatsActor { object StatsActor {
import CharacterStats._
sealed trait Status
object Status {
case object Alive extends Status
case object Dead extends Status
}
sealed trait Command sealed trait Command
final case class TakeDamageResult( final case class TakeDamageResult(
value: CharacterStats.DamageHealth, value: CharacterStats.DamageHealth,
replyTo: ActorRef[(Boolean, CharacterStats)] replyTo: ActorRef[(Status, CharacterStats)]
) extends Command ) extends Command
final case class ConsumeStaminaResult( final case class ConsumeStaminaResult(
value: CharacterStats.DamageStamina, 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 ) extends Command
final case class HealResult(value: HealHealth) extends Command
final case class CurrentStats(replyTo: ActorRef[CharacterStats]) final case class CurrentStats(replyTo: ActorRef[CharacterStats])
extends Command extends Command
@ -88,27 +102,35 @@ class StatsActor(
case TakeDamageResult(value, replyTo) => case TakeDamageResult(value, replyTo) =>
val nextState = if ((state.stats.hp - value).toInt <= 0) { val nextState = if ((state.stats.hp - value).toInt <= 0) {
val s = state.modify(_.stats.hp).setTo(Health(0)) val s = state.modify(_.stats.hp).setTo(Health(0))
replyTo ! true -> s.stats replyTo ! Status.Dead -> s.stats
s s
} else { } else {
val s = state.modify(_.stats.hp).using(_ - value) val s = state.modify(_.stats.hp).using(_ - value)
replyTo ! false -> s.stats replyTo ! Status.Alive -> s.stats
s s
} }
receive(nextState) receive(nextState)
case ConsumeStaminaResult(value, replyTo) => case ConsumeStaminaResult(value, replyTo) =>
val nextState = if ((state.stats.stamina - value).toInt <= 0) { val nextState = if ((state.stats.stamina - value).toInt <= 0) {
val s = state.modify(_.stats.stamina).setTo(Stamina(0)) val s = state.modify(_.stats.stamina).setTo(Stamina(0))
replyTo ! false -> s.stats replyTo ! Status.Alive -> s.stats
s s
} else { } else {
val s = state.modify(_.stats.stamina).using(_ - value) val s = state.modify(_.stats.stamina).using(_ - value)
replyTo ! false -> s.stats replyTo ! Status.Alive -> s.stats
s s
} }
receive(nextState) receive(nextState)
case HealResult(value) => case HealHealthResult(value, replyTo) =>
receive(state.modify(_.stats.hp).using(_ :+ value)) 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) => case CurrentStats(replyTo) =>
replyTo ! state.stats replyTo ! state.stats
Behaviors.same Behaviors.same

View File

@ -43,13 +43,20 @@ object PlayerActor {
sealed trait Command extends Product with Serializable sealed trait Command extends Product with Serializable
final case class TakeDamage( final case class TakeDamage(
value: CharacterStats.DamageHealth, value: CharacterStats.DamageHealth,
replyTo: ActorRef[Unit] replyTo: ActorRef[CharacterStats]
) extends Command ) extends Command
final case class ConsumeStamina( final case class ConsumeStamina(
value: CharacterStats.DamageStamina, 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 ) extends Command
final case class Heal(value: CharacterStats.HealHealth) extends Command
final case class CurrentStats(replyTo: ActorRef[CharacterStats]) final case class CurrentStats(replyTo: ActorRef[CharacterStats])
extends Command extends Command
final case class GetStatus(replyTo: ActorRef[Status]) 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] case object Die extends Command
private[player] final case class StatsResponse( private[player] final case class StatsResponse(
response: (Boolean, CharacterStats), response: (StatsActor.Status, CharacterStats),
replyTo: ActorRef[Unit] replyTo: ActorRef[CharacterStats]
) extends Command ) extends Command
private[player] final case class LogError(ex: Throwable) extends Command private[player] final case class LogError(ex: Throwable) extends Command
class Props( class Props(
@ -225,14 +232,14 @@ class PlayerActor(
case CombatSubstate.Attacking(victimName) => case CombatSubstate.Attacking(victimName) =>
Behaviors.receiveMessage[Command](_ => Behaviors.same) Behaviors.receiveMessage[Command](_ => Behaviors.same)
} }
case AliveSubstate.Moving(substate) =>
substate match { case AliveSubstate.Moving(Walking) =>
case Walking => ctx.log.debugP("In Walking State")
ctx.log.debugP("In Walking State") idleBehavior(aliveState, _ * 2).value
idleBehavior(aliveState, _ * 2).value
case Running => case AliveSubstate.Moving(Running) =>
idleBehavior(aliveState, _ * 3).value idleBehavior(aliveState, _ * 3).value
}
case AliveSubstate.Idle => case AliveSubstate.Idle =>
ctx.log.debugP("In Idle State") ctx.log.debugP("In Idle State")
idleBehavior(aliveState, identity).value idleBehavior(aliveState, identity).value

View File

@ -78,7 +78,7 @@ object PlayerController {
statsActorBeh, statsActorBeh,
scheduler, scheduler,
fxScheduler, fxScheduler,
AsyncQueue.bounded(10)(scheduler.value) AsyncQueue.bounded(50)(scheduler.value)
).behavior ).behavior
} }
val playerActor = val playerActor =

View File

@ -20,7 +20,11 @@ import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.events.PlayerMovementEvent import wow.doge.mygame.subsystems.events.PlayerMovementEvent
object PlayerMovementEventListener { 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( class Props(
val playerActor: PlayerActor.Ref, val playerActor: PlayerActor.Ref,
@ -29,7 +33,7 @@ object PlayerMovementEventListener {
def behavior = def behavior =
Behaviors.setup[PlayerMovementEvent] { ctx => Behaviors.setup[PlayerMovementEvent] { ctx =>
new PlayerMovementEventListener(ctx, this) 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 timeout = Timeout(1.second)
implicit val sched = ctx.system.scheduler implicit val sched = ctx.system.scheduler
def makeStaminaTimer = val staminaTimer =
Task.deferAction(implicit s => Task.deferAction(implicit s =>
Observable Observable
.interval(250.millis) .interval(250.millis)
.doOnNext(_ => Task(pprint.log("Sending Stamina Consume Item")))
.doOnNextF(_ => .doOnNextF(_ =>
props.playerActor.askL( props.playerActor
PlayerActor .askL(
.ConsumeStamina(CharacterStats.DamageStamina(1), _) PlayerActor
) .ConsumeStamina(CharacterStats.DamageStamina(25), _)
)
.void
) )
.completedL .completedL
) )
def handleStamina(pressed: Boolean) = val staminaRegenTimer =
Task.deferAction(implicit s =>
Observable
.interval(500.millis)
.doOnNext(_ => Task(pprint.log("Sending Stamina Regen Item")))
.mapEvalF(_ =>
props.playerActor.askL(
PlayerActor
.HealStamina(CharacterStats.HealStamina(1), _)
)
)
.takeWhile(_.stamina.toInt =!= 100)
.delayExecution(1.second)
.completedL
)
def handleStamina(pressed: Boolean) = {
state.staminaRegenTimer.cancel()
if (pressed) { if (pressed) {
val nextState1 = val nextState1 =
if (state.keysPressed === 0) if (state.keysPressed === 0)
state state
.modify(_.staminaTimer) .modify(_.staminaTimer)
.setTo( .setTo(
makeStaminaTimer.runToFuture(props.asyncScheduler.value) staminaTimer.runToFuture(props.asyncScheduler.value)
) )
else state else state
val nextState2 = nextState1 val nextState2 = nextState1
@ -85,9 +109,14 @@ class PlayerMovementEventListener(
if (nextState1.keysPressed === 0) { if (nextState1.keysPressed === 0) {
nextState1.staminaTimer.cancel() nextState1.staminaTimer.cancel()
nextState1 nextState1
.modify(_.staminaRegenTimer)
.setTo(
staminaRegenTimer.runToFuture(props.asyncScheduler.value)
)
} else } else
nextState1 nextState1
} }
}
Behaviors.receiveMessage { Behaviors.receiveMessage {
case PlayerMovedLeft(pressed) => case PlayerMovedLeft(pressed) =>

View File

@ -37,7 +37,10 @@ class IdleBehaviorFactory(
children.movementActor ! ImMovementActor.MoveLeft(pressed) children.movementActor ! ImMovementActor.MoveLeft(pressed)
if (pressed) nextStateFn(AliveSubstate.Moving(Walking)) if (pressed) nextStateFn(AliveSubstate.Moving(Walking))
else nextStateFn(AliveSubstate.Idle) else nextStateFn(AliveSubstate.Idle)
} else Behaviors.same } else {
children.movementActor ! ImMovementActor.MoveLeft(false)
Behaviors.same
}
case PlayerActor.MoveLeft(pressed) => case PlayerActor.MoveLeft(pressed) =>
implicit val ec = props.scheduler.value implicit val ec = props.scheduler.value
@ -76,9 +79,8 @@ class IdleBehaviorFactory(
for { for {
res <- res <-
children.statsActor.ask(StatsActor.TakeDamageResult(value, _)) children.statsActor.ask(StatsActor.TakeDamageResult(value, _))
_ <- Future.successful( _ = ctx.self ! PlayerActor.StatsResponse(res, replyTo)
ctx.self ! PlayerActor.StatsResponse(res, replyTo)
)
} yield () } yield ()
Behaviors.same Behaviors.same
@ -93,6 +95,7 @@ class IdleBehaviorFactory(
_ = _ =
ctx.self ! PlayerActor ctx.self ! PlayerActor
.StatsResponse(response, replyTo) .StatsResponse(response, replyTo)
} yield () } yield ()
Behaviors.same Behaviors.same
@ -100,8 +103,30 @@ class IdleBehaviorFactory(
children.statsActor ! StatsActor.CurrentStats(replyTo) children.statsActor ! StatsActor.CurrentStats(replyTo)
Behaviors.same Behaviors.same
case PlayerActor.Heal(value) => case PlayerActor.HealHealth(value, replyTo) =>
children.statsActor ! StatsActor.HealResult(value) 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 Behaviors.same
case PlayerActor.GetStatus(replyTo) => case PlayerActor.GetStatus(replyTo) =>
@ -124,14 +149,17 @@ class IdleBehaviorFactory(
case PlayerActor.StatsResponse(response, replyTo) => case PlayerActor.StatsResponse(response, replyTo) =>
response match { response match {
case (dead, stats) => case (status, stats) =>
if (dead) ctx.self ! PlayerActor.Die status match {
props.statsQueue case StatsActor.Status.Dead => ctx.self ! PlayerActor.Die
.offer(stats) case StatsActor.Status.Alive =>
.foreach { _ => props.statsQueue
pprint.log(show"Published stats $stats") .offer(stats)
replyTo ! () .foreach { _ =>
}(props.scheduler.value) pprint.log(show"Published stats $stats")
replyTo ! stats
}(props.scheduler.value)
}
} }
Behaviors.same Behaviors.same