Many changes

Add player actor substates
Added color logic to hud stats bars
This commit is contained in:
Rohan Sircar 2021-03-05 00:39:57 +05:30
parent 1422d91b14
commit 67201c8f7e
10 changed files with 301 additions and 152 deletions

View File

@ -0,0 +1,11 @@
.red-bar > .bar {
-fx-background-color: red;
}
.green-bar > .bar {
-fx-background-color: green;
}
.yellow-bar > .bar {
-fx-background-color: yellow;
}

View File

@ -2,6 +2,7 @@ package wow.doge.mygame
import java.util.concurrent.TimeoutException import java.util.concurrent.TimeoutException
import scala.annotation.switch
import scala.concurrent.duration._ import scala.concurrent.duration._
import akka.actor.typed.ActorRef import akka.actor.typed.ActorRef
@ -35,6 +36,7 @@ import monix.eval.Coeval
import monix.execution.cancelables.CompositeCancelable import monix.execution.cancelables.CompositeCancelable
import monix.execution.exceptions.DummyException import monix.execution.exceptions.DummyException
import monix.reactive.Observable import monix.reactive.Observable
import monix.{eval => me}
import scalafx.scene.control.Label import scalafx.scene.control.Label
import scalafx.scene.control.TextArea import scalafx.scene.control.TextArea
import scalafx.scene.layout.HBox import scalafx.scene.layout.HBox
@ -50,8 +52,8 @@ import wow.doge.mygame.game.entities.CharacterStats
import wow.doge.mygame.game.entities.EntityIds import wow.doge.mygame.game.entities.EntityIds
import wow.doge.mygame.game.entities.NpcActorSupervisor import wow.doge.mygame.game.entities.NpcActorSupervisor
import wow.doge.mygame.game.entities.NpcMovementActor import wow.doge.mygame.game.entities.NpcMovementActor
import wow.doge.mygame.game.entities.PlayerActorSupervisor import wow.doge.mygame.game.entities.player.PlayerActorSupervisor
import wow.doge.mygame.game.entities.PlayerController import wow.doge.mygame.game.entities.player.PlayerController
import wow.doge.mygame.game.subsystems.input.GameInputHandler import wow.doge.mygame.game.subsystems.input.GameInputHandler
import wow.doge.mygame.game.subsystems.input.PlayerCameraInput import wow.doge.mygame.game.subsystems.input.PlayerCameraInput
import wow.doge.mygame.game.subsystems.level.DefaultGameLevel import wow.doge.mygame.game.subsystems.level.DefaultGameLevel
@ -386,6 +388,8 @@ class MainAppDelegate(
hgrow = Priority.Always hgrow = Priority.Always
spacing = 10 spacing = 10
style = """-fx-background-color: rgba(0,0,0,0.7);""" style = """-fx-background-color: rgba(0,0,0,0.7);"""
stylesheets = Seq((os.rel / "main.css").toString)
children = List( children = List(
new HBox { new HBox {
spacing = 5 spacing = 5
@ -398,10 +402,29 @@ class MainAppDelegate(
textFill = Color.White textFill = Color.White
}, },
new JFXProgressBar { new JFXProgressBar {
progress = 100 progress = 100
minHeight = 10 minHeight = 10
progress <-- statsObs progress <-- statsObs
.map(_.hp.toInt.toDouble / 100) .map(_.hp.toInt.toDouble / 100)
c += statsObs
.scanEval(me.Task.pure("green-bar")) {
case (a, b) =>
me.Task {
pprint.log(show"Received $a $b")
pprint.log(show"${styleClass.toString}")
styleClass.removeAll(a)
} >>
me.Task.pure(
(b.hp.toInt: @switch) match {
case v if v > 80 => "green-bar"
case v if v > 20 && v <= 80 => "yellow-bar"
case _ => "red-bar"
}
)
}
.doOnNext(cls => me.Task(styleClass += cls))
.subscribe()
} }
) )
}, },
@ -419,9 +442,32 @@ class MainAppDelegate(
.map(_.stamina.toInt.toString) .map(_.stamina.toInt.toString)
}, },
new JFXProgressBar { new JFXProgressBar {
progress = 100 progress = 100
minHeight = 10 minHeight = 10
progress <-- statsObs.map(_.stamina.toInt.toDouble / 100) progress <-- statsObs.map(_.stamina.toInt.toDouble / 100)
styleClass ++= Seq("green-bar")
c += statsObs
.scanEval(me.Task.pure("green-bar")) {
case (a, b) =>
me.Task {
pprint.log(show"Received $a $b")
pprint.log(show"${styleClass.toString}")
styleClass.removeAll(a)
} >>
me.Task.pure(
(b.stamina.toInt: @switch) match {
case v if v > 80 => "green-bar"
case v if v > 20 && v <= 40 => "yellow-bar"
case _ => "red-bar"
}
)
}
.doOnNext(cls => me.Task(styleClass += cls))
.subscribe()
} }
) )
} }

View File

@ -5,6 +5,7 @@ import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.Behaviors
import cats.Show import cats.Show
import cats.kernel.Eq
import io.estatico.newtype.macros.newtype import io.estatico.newtype.macros.newtype
import wow.doge.mygame.game.entities.CharacterStats.HealHealth import wow.doge.mygame.game.entities.CharacterStats.HealHealth
@ -18,10 +19,6 @@ object CharacterStats {
@newtype final case class Health(toInt: Int) @newtype final case class Health(toInt: Int)
object Health { object Health {
implicit class HealthOps(private val h: Health) extends AnyVal { implicit class HealthOps(private val h: Health) extends AnyVal {
// def +(v: Int): Health = Health(h.toInt + v)
// def -(v: Int): Health = Health(h.toInt - v)
// def *(v: Int): Health = Health(h.toInt * v)
// def /(v: Int): Health = Health(h.toInt / v)
def :+(v: HealHealth): Health = Health(h.toInt + v.toInt) def :+(v: HealHealth): Health = Health(h.toInt + v.toInt)
def -(v: DamageHealth): Health = Health(h.toInt - v.toInt) def -(v: DamageHealth): Health = Health(h.toInt - v.toInt)
} }
@ -31,40 +28,19 @@ object CharacterStats {
@newtype final case class Stamina(toInt: Int) @newtype final case class Stamina(toInt: Int)
object Stamina { object Stamina {
implicit class StaminaOps(private val h: Stamina) extends AnyVal { implicit class StaminaOps(private val h: Stamina) extends AnyVal {
// def +(v: Int): Stamina = Stamina(h.toInt + v)
// def -(v: Int): Stamina = Stamina(h.toInt - v)
// def *(v: Int): Stamina = Stamina(h.toInt * v)
// def /(v: Int): Stamina = Stamina(h.toInt / v)
def :+(v: HealStamina): Stamina = Stamina(h.toInt + v.toInt) def :+(v: HealStamina): Stamina = Stamina(h.toInt + v.toInt)
def -(v: DamageStamina): Stamina = Stamina(h.toInt - v.toInt) def -(v: DamageStamina): Stamina = Stamina(h.toInt - v.toInt)
} }
} }
// object Stamina {
// implicit class StaminaOps(private val h: Stamina) extends AnyVal {
// def +(v: Health): Stamina = Stamina(h.toInt + v.toInt)
// def -(v: Health): Stamina = Stamina(h.toInt - v.toInt)
// def *(v: Health): Stamina = Stamina(h.toInt * v.toInt)
// def /(v: Health): Stamina = Stamina(h.toInt / v.toInt)
// }
// }
// object Damage {
// implicit class DamageOps(private val h: Damage) extends AnyVal {
// def +(v: Health): Damage = Damage(h.toInt + v.toInt)
// def -(v: Health): Damage = Damage(h.toInt - v.toInt)
// def *(v: Health): Damage = Damage(h.toInt * v.toInt)
// def /(v: Health): Damage = Damage(h.toInt / v.toInt)
// }
// }
implicit val show = Show.fromToString[CharacterStats] implicit val show = Show.fromToString[CharacterStats]
implicit val eq = Eq.fromUniversalEquals[CharacterStats]
} }
object StatsActor { object StatsActor {
sealed trait Command sealed trait Command
// final case class TakeDamage(value: Int) extends Command
final case class TakeDamageResult( final case class TakeDamageResult(
value: CharacterStats.DamageHealth, value: CharacterStats.DamageHealth,
replyTo: ActorRef[(Boolean, CharacterStats)] replyTo: ActorRef[(Boolean, CharacterStats)]

View File

@ -0,0 +1,31 @@
package wow.doge.mygame.game.entities.character
import cats.Show
import cats.kernel.Eq
object CharacterStates {
sealed trait AliveSubstate
object AliveSubstate {
final case class InCombat(substate: CombatSubstate) extends AliveSubstate
final case class Moving(substate: MovementSubstate) extends AliveSubstate
case object Idle extends AliveSubstate
implicit val eq = Eq.fromUniversalEquals[AliveSubstate]
implicit val show = Show.fromToString[AliveSubstate]
}
sealed trait CombatSubstate
object CombatSubstate {
final case class Moving(substate: MovementSubstate) extends CombatSubstate
final case class Attacking(victimName: String) extends CombatSubstate
}
sealed trait MovementSubstate
case object Walking extends MovementSubstate
case object Running extends MovementSubstate
sealed trait DeadSubstate
object DeadSubstate {
implicit val eq = Eq.fromUniversalEquals[DeadSubstate]
implicit val show = Show.fromToString[DeadSubstate]
}
}

View File

@ -1,8 +1,6 @@
package wow.doge.mygame.game.entities package wow.doge.mygame.game.entities.player
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.util.Failure
import scala.util.Success
import akka.actor.typed.ActorRef import akka.actor.typed.ActorRef
import akka.actor.typed.Behavior import akka.actor.typed.Behavior
@ -12,14 +10,18 @@ import akka.actor.typed.SupervisorStrategy
import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.ActorContext
import akka.actor.typed.scaladsl.Behaviors import akka.actor.typed.scaladsl.Behaviors
import akka.util.Timeout import akka.util.Timeout
import cats.syntax.show._
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import monix.bio.UIO
import monix.execution.AsyncQueue import monix.execution.AsyncQueue
import monix.reactive.Observable import monix.reactive.Observable
import org.slf4j.event.Level import org.slf4j.event.Level
import wow.doge.mygame.Dispatchers import wow.doge.mygame.Dispatchers
import wow.doge.mygame.executors.Schedulers
import wow.doge.mygame.executors.Schedulers.AsyncScheduler import wow.doge.mygame.executors.Schedulers.AsyncScheduler
import wow.doge.mygame.game.entities.CharacterStats
import wow.doge.mygame.game.entities.StatsActor import wow.doge.mygame.game.entities.StatsActor
import wow.doge.mygame.game.entities.character.CharacterStates._
import wow.doge.mygame.game.entities.player.behaviors.IdleBehaviorFactory
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.events.EventBus import wow.doge.mygame.subsystems.events.EventBus
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
@ -27,8 +29,8 @@ import wow.doge.mygame.subsystems.events.PlayerEvent
import wow.doge.mygame.subsystems.events.TickEvent import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.events.TickEvent.RenderTick import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
import wow.doge.mygame.subsystems.movement.ImMovementActor import wow.doge.mygame.subsystems.movement.ImMovementActor
import wow.doge.mygame.executors.Schedulers
import monix.bio.UIO //TODO: Change name to PlayerActor
object PlayerActorSupervisor { object PlayerActorSupervisor {
type Ref = ActorRef[PlayerActorSupervisor.Command] type Ref = ActorRef[PlayerActorSupervisor.Command]
@ -56,26 +58,28 @@ object PlayerActorSupervisor {
replyTo: ActorRef[UIO[Observable[CharacterStats]]] replyTo: ActorRef[UIO[Observable[CharacterStats]]]
) extends Command ) extends Command
private case object Die extends Command sealed trait Movement extends Command
private final case class DamageResponse( final case class MoveLeft(pressed: Boolean) extends Movement
private[player] case object Die extends Command
private[player] final case class StatsResponse(
response: (Boolean, CharacterStats), response: (Boolean, CharacterStats),
replyTo: ActorRef[Unit] replyTo: ActorRef[Unit]
) extends Command ) extends Command
private final case class LogError(ex: Throwable) extends Command private[player] final case class LogError(ex: Throwable) extends Command
class Props( class Props(
val playerEventBus: GameEventBus[PlayerEvent], val playerEventBus: GameEventBus[PlayerEvent],
val tickEventBus: GameEventBus[TickEvent], val tickEventBus: GameEventBus[TickEvent],
val imMovementActorBehavior: Behavior[ImMovementActor.Command], val imMovementActorBehavior: Behavior[ImMovementActor.Command],
val scheduler: AsyncScheduler, val scheduler: AsyncScheduler,
val fxScheduler: Schedulers.FxScheduler val fxScheduler: Schedulers.FxScheduler,
val statsQueue: AsyncQueue[CharacterStats]
) { ) {
def behavior = def behavior =
Behaviors.logMessages( Behaviors.logMessages(
LogOptions() LogOptions()
.withLevel(Level.DEBUG) .withLevel(Level.DEBUG)
.withLogger( .withLogger(Logger[PlayerActorSupervisor].underlying),
Logger[PlayerActorSupervisor].underlying
),
Behaviors Behaviors
.setup[Command] { ctx => .setup[Command] { ctx =>
ctx.log.infoP("Starting PlayerActor") ctx.log.infoP("Starting PlayerActor")
@ -140,9 +144,8 @@ object PlayerActorSupervisor {
new PlayerActorSupervisor( new PlayerActorSupervisor(
ctx, ctx,
this, this,
Children(playerMovementActor, playerStatsActor), Children(playerMovementActor, playerStatsActor)
AsyncQueue.bounded(10)(scheduler.value) ).aliveState(AliveSubstate.Idle)
).aliveState
} }
) )
@ -152,77 +155,24 @@ object PlayerActorSupervisor {
movementActor: ActorRef[ImMovementActor.Command], movementActor: ActorRef[ImMovementActor.Command],
statsActor: ActorRef[StatsActor.Command] statsActor: ActorRef[StatsActor.Command]
) )
final case class Env(
ctx: ActorContext[PlayerActorSupervisor.Command],
props: PlayerActorSupervisor.Props,
children: PlayerActorSupervisor.Children
)
} }
class PlayerActorSupervisor( class PlayerActorSupervisor(
ctx: ActorContext[PlayerActorSupervisor.Command], ctx: ActorContext[PlayerActorSupervisor.Command],
props: PlayerActorSupervisor.Props, props: PlayerActorSupervisor.Props,
children: PlayerActorSupervisor.Children, children: PlayerActorSupervisor.Children
statsQueue: AsyncQueue[CharacterStats]
) { ) {
import PlayerActorSupervisor._ import PlayerActorSupervisor._
implicit val timeout = Timeout(1.second) implicit val timeout = Timeout(1.second)
val aliveState =
Behaviors
.receiveMessage[Command] {
case TakeDamage(value, replyTo) =>
ctx.ask(children.statsActor, StatsActor.TakeDamageResult(value, _)) {
case Success(response) => DamageResponse(response, replyTo)
case Failure(ex) => LogError(ex)
}
Behaviors.same
case ConsumeStamina(value, replyTo) =>
ctx.ask(
children.statsActor,
StatsActor.ConsumeStaminaResult(value, _)
) {
case Success(response) => DamageResponse(response, replyTo)
case Failure(ex) => LogError(ex)
}
Behaviors.same
case CurrentStats(replyTo) =>
children.statsActor ! StatsActor.CurrentStats(replyTo)
Behaviors.same
case Heal(value) =>
children.statsActor ! StatsActor.HealResult(value)
Behaviors.same
case GetStatus(replyTo) =>
replyTo ! Status.Alive
Behaviors.same
case GetStatsObservable(replyTo) =>
import monix.{eval => me}
replyTo !
UIO(
Observable
.repeatEvalF(
me.Task.deferFuture(statsQueue.poll())
)
.publish(props.fxScheduler.value)
.refCount
)
Behaviors.same val env = Env(ctx, props, children)
case DamageResponse(response, replyTo) =>
response match {
case (dead, stats) =>
if (dead) ctx.self ! Die
statsQueue
.offer(stats)
.foreach { _ =>
pprint.log(show"Published stats $stats")
replyTo ! ()
}(props.scheduler.value)
}
Behaviors.same
case Die => deadState
case LogError(ex) =>
ctx.log.error(ex.getMessage)
Behaviors.same
}
.receiveSignal {
case (_, PostStop) =>
ctx.log.infoP("stopped")
Behaviors.same
}
val deadState = Behaviors val deadState = Behaviors
.receiveMessage[Command] { .receiveMessage[Command] {
// case TakeDamage(value) => // case TakeDamage(value) =>
@ -249,4 +199,43 @@ class PlayerActorSupervisor(
ctx.log.infoP("stopped") ctx.log.infoP("stopped")
Behaviors.same Behaviors.same
} }
def idleBehavior(
nextStateFn: AliveSubstate => Behavior[Command],
consumptionMultiplier: Int => Int
) =
new IdleBehaviorFactory(
env,
nextStateFn,
deadState,
consumptionMultiplier
).behavior
def aliveState(substate: AliveSubstate): Behavior[Command] =
substate match {
case AliveSubstate.InCombat(substate) =>
substate match {
case CombatSubstate.Moving(substate) =>
substate match {
case Walking =>
Behaviors.receiveMessage[Command](_ => Behaviors.same)
case Running =>
Behaviors.receiveMessage[Command](_ => Behaviors.same)
}
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.Idle =>
ctx.log.debugP("In Idle State")
idleBehavior(aliveState, identity).value
}
} }

View File

@ -1,4 +1,4 @@
package wow.doge.mygame.game.entities package wow.doge.mygame.game.entities.player
import akka.actor.typed.ActorRef import akka.actor.typed.ActorRef
import akka.actor.typed.DispatcherSelector import akka.actor.typed.DispatcherSelector
@ -13,7 +13,9 @@ import com.softwaremill.tagging._
import io.odin.Logger import io.odin.Logger
import monix.bio.IO import monix.bio.IO
import monix.bio.Task import monix.bio.Task
import monix.execution.AsyncQueue
import wow.doge.mygame.AppError import wow.doge.mygame.AppError
import wow.doge.mygame.executors.Schedulers
import wow.doge.mygame.executors.Schedulers.AsyncScheduler import wow.doge.mygame.executors.Schedulers.AsyncScheduler
import wow.doge.mygame.game.GameApp import wow.doge.mygame.game.GameApp
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
@ -24,7 +26,6 @@ import wow.doge.mygame.subsystems.events.TickEvent
import wow.doge.mygame.subsystems.movement.ImMovementActor import wow.doge.mygame.subsystems.movement.ImMovementActor
import wow.doge.mygame.types._ import wow.doge.mygame.types._
import wow.doge.mygame.utils.wrappers.jme._ import wow.doge.mygame.utils.wrappers.jme._
import wow.doge.mygame.executors.Schedulers
object PlayerController { object PlayerController {
sealed trait Error sealed trait Error
@ -69,7 +70,8 @@ object PlayerController {
tickEventBus, tickEventBus,
movementActorBeh, movementActorBeh,
scheduler, scheduler,
fxScheduler fxScheduler,
AsyncQueue.bounded(10)(scheduler.value)
).behavior ).behavior
} }
val playerActor = val playerActor =

View File

@ -1,4 +1,4 @@
package wow.doge.mygame.game.entities package wow.doge.mygame.game.entities.player
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -14,8 +14,8 @@ import monix.execution.CancelableFuture
import monix.reactive.Observable import monix.reactive.Observable
import org.slf4j.event.Level import org.slf4j.event.Level
import wow.doge.mygame.executors.Schedulers import wow.doge.mygame.executors.Schedulers
import wow.doge.mygame.game.entities.CharacterStats
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.events.PlayerCameraEvent
import wow.doge.mygame.subsystems.events.PlayerMovementEvent import wow.doge.mygame.subsystems.events.PlayerMovementEvent
import wow.doge.mygame.subsystems.movement.ImMovementActor import wow.doge.mygame.subsystems.movement.ImMovementActor
@ -24,7 +24,7 @@ object PlayerMovementEventListener {
class Props( class Props(
val movementActor: ActorRef[ImMovementActor.Command], val movementActor: ActorRef[ImMovementActor.Command],
val statsActor: PlayerActorSupervisor.Ref, val playerActor: PlayerActorSupervisor.Ref,
val asyncScheduler: Schedulers.AsyncScheduler val asyncScheduler: Schedulers.AsyncScheduler
) { ) {
def behavior = def behavior =
@ -56,7 +56,7 @@ class PlayerMovementEventListener(
Observable Observable
.interval(250.millis) .interval(250.millis)
.doOnNextF(_ => .doOnNextF(_ =>
props.statsActor.askL( props.playerActor.askL(
PlayerActorSupervisor PlayerActorSupervisor
.ConsumeStamina(CharacterStats.DamageStamina(1), _) .ConsumeStamina(CharacterStats.DamageStamina(1), _)
) )
@ -64,9 +64,12 @@ class PlayerMovementEventListener(
.completedL .completedL
) )
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
def handleStamina(pressed: Boolean) = def handleStamina(pressed: Boolean) =
if (pressed) if (pressed)
State(makeStaminaTimer.runToFuture(props.asyncScheduler.value)) if (state.staminaTimer == CancelableFuture.unit)
State(makeStaminaTimer.runToFuture(props.asyncScheduler.value))
else state
else { else {
state.staminaTimer.cancel() state.staminaTimer.cancel()
State(CancelableFuture.unit) State(CancelableFuture.unit)
@ -74,7 +77,8 @@ class PlayerMovementEventListener(
Behaviors.receiveMessage { Behaviors.receiveMessage {
case PlayerMovedLeft(pressed) => case PlayerMovedLeft(pressed) =>
props.movementActor ! ImMovementActor.MoveLeft(pressed) // props.movementActor ! ImMovementActor.MoveLeft(pressed)
props.playerActor ! PlayerActorSupervisor.MoveLeft(pressed)
receive(handleStamina(pressed)) receive(handleStamina(pressed))
case PlayerMovedRight(pressed) => case PlayerMovedRight(pressed) =>
props.movementActor ! ImMovementActor.MoveRight(pressed) props.movementActor ! ImMovementActor.MoveRight(pressed)
@ -98,32 +102,3 @@ class PlayerMovementEventListener(
} }
) )
} }
//not used
object PlayerCameraEventListener {
import PlayerCameraEvent._
def apply(playerCameraActor: ActorRef[PlayerCameraActor.Command]) =
Behaviors.logMessages(
LogOptions()
.withLevel(Level.TRACE)
.withLogger(
Logger[PlayerCameraEventListener.type].underlying
),
Behaviors.setup[PlayerCameraEvent](ctx =>
Behaviors.receiveMessagePartial {
case CameraMovedUp =>
playerCameraActor ! PlayerCameraActor.RotateUp
Behaviors.same
case CameraMovedDown =>
playerCameraActor ! PlayerCameraActor.RotateDown
Behaviors.same
case CameraLeft =>
playerCameraActor ! PlayerCameraActor.RotateLeft
Behaviors.same
case CameraRight =>
playerCameraActor ! PlayerCameraActor.RotateRight
Behaviors.same
}
)
)
}

View File

@ -0,0 +1,115 @@
package wow.doge.mygame.game.entities.player.behaviors
import scala.concurrent.Future
import akka.actor.typed.Behavior
import akka.actor.typed.PostStop
import akka.actor.typed.scaladsl.AskPattern._
import akka.actor.typed.scaladsl.Behaviors
import akka.util.Timeout
import cats.syntax.show._
import monix.bio.UIO
import monix.reactive.Observable
import wow.doge.mygame.game.entities.CharacterStats
import wow.doge.mygame.game.entities.StatsActor
import wow.doge.mygame.game.entities.character.CharacterStates._
import wow.doge.mygame.game.entities.player.PlayerActorSupervisor
import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.movement.ImMovementActor
class IdleBehaviorFactory(
env: PlayerActorSupervisor.Env,
nextStateFn: AliveSubstate => Behavior[PlayerActorSupervisor.Command],
deadState: Behavior[PlayerActorSupervisor.Command],
consumptionMultiplier: Int => Int
)(implicit timeout: Timeout) {
import IdleBehaviorFactory.IdleBehavior
import env._
implicit val sched = ctx.system.scheduler
def behavior =
IdleBehavior(
Behaviors
.receiveMessage[PlayerActorSupervisor.Command] {
case PlayerActorSupervisor.MoveLeft(pressed) =>
children.movementActor ! ImMovementActor.MoveLeft(pressed)
if (pressed)
nextStateFn(AliveSubstate.Moving(Walking))
else nextStateFn(AliveSubstate.Idle)
case PlayerActorSupervisor.TakeDamage(value, replyTo) =>
implicit val ec = props.scheduler.value
for {
res <-
children.statsActor.ask(StatsActor.TakeDamageResult(value, _))
_ <- Future.successful(
ctx.self ! PlayerActorSupervisor.StatsResponse(res, replyTo)
)
} yield ()
Behaviors.same
case PlayerActorSupervisor.ConsumeStamina(value, replyTo) =>
implicit val ec = props.scheduler.value
val newValue =
CharacterStats.DamageStamina(consumptionMultiplier(value.toInt))
for {
response <- children.statsActor.ask(
StatsActor.ConsumeStaminaResult(newValue, _)
)
_ =
ctx.self ! PlayerActorSupervisor
.StatsResponse(response, replyTo)
} yield ()
Behaviors.same
case PlayerActorSupervisor.CurrentStats(replyTo) =>
children.statsActor ! StatsActor.CurrentStats(replyTo)
Behaviors.same
case PlayerActorSupervisor.Heal(value) =>
children.statsActor ! StatsActor.HealResult(value)
Behaviors.same
case PlayerActorSupervisor.GetStatus(replyTo) =>
replyTo ! PlayerActorSupervisor.Status.Alive
Behaviors.same
case PlayerActorSupervisor.GetStatsObservable(replyTo) =>
import monix.{eval => me}
replyTo !
UIO(
Observable
.repeatEvalF(
me.Task.deferFuture(props.statsQueue.poll())
)
.publish(props.fxScheduler.value)
.refCount
)
Behaviors.same
case PlayerActorSupervisor.StatsResponse(response, replyTo) =>
response match {
case (dead, stats) =>
if (dead) ctx.self ! PlayerActorSupervisor.Die
props.statsQueue
.offer(stats)
.foreach { _ =>
pprint.log(show"Published stats $stats")
replyTo ! ()
}(props.scheduler.value)
}
Behaviors.same
// nextStateFn(InCombat(Moving(Walking)))
case PlayerActorSupervisor.Die => deadState
case PlayerActorSupervisor.LogError(ex) =>
ctx.log.error(ex.getMessage)
Behaviors.same
}
.receiveSignal {
case (_, PostStop) =>
ctx.log.infoP("stopped")
Behaviors.same
}
)
}
object IdleBehaviorFactory {
final case class IdleBehavior(
value: Behavior[PlayerActorSupervisor.Command]
)
}

View File

@ -12,11 +12,14 @@ import wow.doge.mygame.game.subsystems.movement.CanMove
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
final case class CardinalDirection( final case class CardinalDirection(
left: Boolean = false, left: Boolean,
right: Boolean = false, right: Boolean,
up: Boolean = false, up: Boolean,
down: Boolean = false down: Boolean
) )
object CardinalDirection {
val default = CardinalDirection(false, false, false, false)
}
sealed trait RotateDir sealed trait RotateDir
object RotateDir { object RotateDir {
@ -53,7 +56,9 @@ object ImMovementActor {
* *
* @param cardinalDir The four directions the character can move * @param cardinalDir The four directions the character can move
*/ */
final case class State(cardinalDir: CardinalDirection = CardinalDirection()) final case class State(
cardinalDir: CardinalDirection = CardinalDirection.default
)
} }
@ -188,7 +193,7 @@ object MovementActor {
* @param walkDirection scratch space to avoid allocations on every tick. Do not share outside the actor * @param walkDirection scratch space to avoid allocations on every tick. Do not share outside the actor
*/ */
final case class State( final case class State(
cardinalDir: CardinalDirection = CardinalDirection(), cardinalDir: CardinalDirection = CardinalDirection.default,
walkDirection: Vector3f = Vector3f.UNIT_X walkDirection: Vector3f = Vector3f.UNIT_X
) )

View File

@ -1,5 +1,4 @@
package wow.doge.mygame.utils.controls package wow.doge.mygame.utils.controls
// import com.jfoenix.controls.JFXProgressBar
import com.jfoenix.{controls => jfoenixc} import com.jfoenix.{controls => jfoenixc}
import scalafx.scene.control.ProgressBar import scalafx.scene.control.ProgressBar