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")
playerActor
.askL(PlayerActor.TakeDamage(event.amount, _))
.void
.onErrorHandle { case ex: TimeoutException => () }
else IO.unit)).toTask
)

View File

@ -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

View File

@ -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

View File

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

View File

@ -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(1), _)
)
props.playerActor
.askL(
PlayerActor
.ConsumeStamina(CharacterStats.DamageStamina(25), _)
)
.void
)
.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) {
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) =>

View File

@ -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