Player actor changes

Rename PlayerActorSupervisor to PlayerActor
Make PlayerEventListener able to handle multiple inputs
This commit is contained in:
Rohan Sircar 2021-03-06 19:33:27 +05:30
parent 67201c8f7e
commit be9acf81d5
6 changed files with 104 additions and 61 deletions

View File

@ -52,7 +52,7 @@ 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.player.PlayerActorSupervisor import wow.doge.mygame.game.entities.player.PlayerActor
import wow.doge.mygame.game.entities.player.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
@ -203,7 +203,7 @@ class MainApp(
.use(_ => launchSignal.get) .use(_ => launchSignal.get)
.hideErrors .hideErrors
tickEventBus <- tickEventBus <-
eventsModule.tickEventBus.hideErrorsWith(e => DummyException(e.toString)) eventsModule.tickEventBus.hideErrorsWith(e => new Exception(e.toString))
_ <- _ <-
/** /**
* User chose to quit * User chose to quit
@ -288,7 +288,7 @@ class MainAppDelegate(
(loggerL.debug(show"Received Damage Event $event") >> (loggerL.debug(show"Received Damage Event $event") >>
(if (event.victimName === "PlayerNode") (if (event.victimName === "PlayerNode")
playerActor playerActor
.askL(PlayerActorSupervisor.TakeDamage(event.amount, _)) .askL(PlayerActor.TakeDamage(event.amount, _))
.onErrorHandle { case ex: TimeoutException => () } .onErrorHandle { case ex: TimeoutException => () }
else IO.unit)).toTask else IO.unit)).toTask
) )
@ -377,7 +377,7 @@ class MainAppDelegate(
// .startAndForget // .startAndForget
statsObs <- statsObs <-
playerActor playerActor
.askL(PlayerActorSupervisor.GetStatsObservable(_)) .askL(PlayerActor.GetStatsObservable(_))
.onErrorHandleWith(TimeoutError.from) .onErrorHandleWith(TimeoutError.from)
.flatten .flatten
playerHud <- playerHud <-
@ -480,7 +480,7 @@ class MainAppDelegate(
def createPlayerController( def createPlayerController(
// appScheduler: monix.execution.Scheduler // appScheduler: monix.execution.Scheduler
): IO[AppError, PlayerActorSupervisor.Ref] = { ): IO[AppError, PlayerActor.Ref] = {
val playerPos = ImVector3f.Zero val playerPos = ImVector3f.Zero
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o" val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
// val modelPath = os.rel / "Models" / "Oto" / "Oto.mesh.xml" // val modelPath = os.rel / "Models" / "Oto" / "Oto.mesh.xml"

View File

@ -30,10 +30,9 @@ 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
//TODO: Change name to PlayerActor object PlayerActor {
object PlayerActorSupervisor {
type Ref = ActorRef[PlayerActorSupervisor.Command] type Ref = ActorRef[PlayerActor.Command]
sealed trait Status sealed trait Status
object Status { object Status {
@ -71,6 +70,7 @@ object PlayerActorSupervisor {
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 statsActorBehavior: Behavior[StatsActor.Command],
val scheduler: AsyncScheduler, val scheduler: AsyncScheduler,
val fxScheduler: Schedulers.FxScheduler, val fxScheduler: Schedulers.FxScheduler,
val statsQueue: AsyncQueue[CharacterStats] val statsQueue: AsyncQueue[CharacterStats]
@ -79,20 +79,14 @@ object PlayerActorSupervisor {
Behaviors.logMessages( Behaviors.logMessages(
LogOptions() LogOptions()
.withLevel(Level.DEBUG) .withLevel(Level.DEBUG)
.withLogger(Logger[PlayerActorSupervisor].underlying), .withLogger(Logger[PlayerActor].underlying),
Behaviors Behaviors
.setup[Command] { ctx => .setup[Command] { ctx =>
ctx.log.infoP("Starting PlayerActor") ctx.log.infoP("Starting PlayerActor")
// spawn children actors // spawn children actors
val playerStatsActor = val playerStatsActor = ctx.spawnN(statsActorBehavior)
ctx.spawnN(
new StatsActor.Props(
CharacterStats.Health(100),
CharacterStats.Stamina(100)
).behavior
)
val playerMovementActor = val playerMovementActor =
ctx.spawnN( ctx.spawnN(
@ -141,7 +135,7 @@ object PlayerActorSupervisor {
playerEventBus ! EventBus.Subscribe(playerMovementEl) playerEventBus ! EventBus.Subscribe(playerMovementEl)
tickEventBus ! EventBus.Subscribe(renderTickEl) tickEventBus ! EventBus.Subscribe(renderTickEl)
new PlayerActorSupervisor( new PlayerActor(
ctx, ctx,
this, this,
Children(playerMovementActor, playerStatsActor) Children(playerMovementActor, playerStatsActor)
@ -157,18 +151,18 @@ object PlayerActorSupervisor {
) )
final case class Env( final case class Env(
ctx: ActorContext[PlayerActorSupervisor.Command], ctx: ActorContext[PlayerActor.Command],
props: PlayerActorSupervisor.Props, props: PlayerActor.Props,
children: PlayerActorSupervisor.Children children: PlayerActor.Children
) )
} }
class PlayerActorSupervisor( class PlayerActor(
ctx: ActorContext[PlayerActorSupervisor.Command], ctx: ActorContext[PlayerActor.Command],
props: PlayerActorSupervisor.Props, props: PlayerActor.Props,
children: PlayerActorSupervisor.Children children: PlayerActor.Children
) { ) {
import PlayerActorSupervisor._ import PlayerActor._
implicit val timeout = Timeout(1.second) implicit val timeout = Timeout(1.second)
val env = Env(ctx, props, children) val env = Env(ctx, props, children)

View File

@ -26,6 +26,8 @@ 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.game.entities.StatsActor
import wow.doge.mygame.game.entities.CharacterStats
object PlayerController { object PlayerController {
sealed trait Error sealed trait Error
@ -65,10 +67,15 @@ object PlayerController {
enqueueR, enqueueR,
camera camera
).behavior(playerPhysicsControl) ).behavior(playerPhysicsControl)
new PlayerActorSupervisor.Props( val statsActorBeh = new StatsActor.Props(
CharacterStats.Health(100),
CharacterStats.Stamina(100)
).behavior
new PlayerActor.Props(
playerEventBus, playerEventBus,
tickEventBus, tickEventBus,
movementActorBeh, movementActorBeh,
statsActorBeh,
scheduler, scheduler,
fxScheduler, fxScheduler,
AsyncQueue.bounded(10)(scheduler.value) AsyncQueue.bounded(10)(scheduler.value)
@ -77,10 +84,9 @@ object PlayerController {
val playerActor = val playerActor =
gameApp.spawnGameActor( gameApp.spawnGameActor(
playerActorBehavior, playerActorBehavior,
// Some("playerActorSupervisor"),
props = DispatcherSelector.default() props = DispatcherSelector.default()
) )
val create: IO[AppError, ActorRef[PlayerActorSupervisor.Command]] = val create: IO[AppError, ActorRef[PlayerActor.Command]] =
(for { (for {
// playerActor <- // playerActor <-
// // AkkaUtils.spawnActorL(playerActorBehavior, "playerActorSupervisor") // // AkkaUtils.spawnActorL(playerActorBehavior, "playerActorSupervisor")

View File

@ -18,19 +18,20 @@ import wow.doge.mygame.game.entities.CharacterStats
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
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
import cats.syntax.eq._
object PlayerMovementEventListener { object PlayerMovementEventListener {
final case class State(staminaTimer: CancelableFuture[Unit]) final case class State(keysPressed: Int, staminaTimer: CancelableFuture[Unit])
class Props( class Props(
val movementActor: ActorRef[ImMovementActor.Command], val movementActor: ActorRef[ImMovementActor.Command],
val playerActor: PlayerActorSupervisor.Ref, val playerActor: PlayerActor.Ref,
val asyncScheduler: Schedulers.AsyncScheduler val asyncScheduler: Schedulers.AsyncScheduler
) { ) {
def behavior = def behavior =
Behaviors.setup[PlayerMovementEvent] { ctx => Behaviors.setup[PlayerMovementEvent] { ctx =>
new PlayerMovementEventListener(ctx, this) new PlayerMovementEventListener(ctx, this)
.receive(State(CancelableFuture.unit)) .receive(State(0, CancelableFuture.unit))
} }
} }
} }
@ -40,6 +41,7 @@ class PlayerMovementEventListener(
) { ) {
import PlayerMovementEventListener._ import PlayerMovementEventListener._
import PlayerMovementEvent._ import PlayerMovementEvent._
import com.softwaremill.quicklens._
def receive(state: State): Behavior[PlayerMovementEvent] = def receive(state: State): Behavior[PlayerMovementEvent] =
Behaviors.logMessages( Behaviors.logMessages(
LogOptions() LogOptions()
@ -57,28 +59,42 @@ class PlayerMovementEventListener(
.interval(250.millis) .interval(250.millis)
.doOnNextF(_ => .doOnNextF(_ =>
props.playerActor.askL( props.playerActor.askL(
PlayerActorSupervisor PlayerActor
.ConsumeStamina(CharacterStats.DamageStamina(1), _) .ConsumeStamina(CharacterStats.DamageStamina(1), _)
) )
) )
.completedL .completedL
) )
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
def handleStamina(pressed: Boolean) = def handleStamina(pressed: Boolean) =
if (pressed) if (pressed) {
if (state.staminaTimer == CancelableFuture.unit) val nextState1 =
State(makeStaminaTimer.runToFuture(props.asyncScheduler.value)) if (state.keysPressed === 0)
else state state
else { .modify(_.staminaTimer)
state.staminaTimer.cancel() .setTo(
State(CancelableFuture.unit) makeStaminaTimer.runToFuture(props.asyncScheduler.value)
)
else state
val nextState2 = nextState1
.modify(_.keysPressed)
.using(_ + 1)
nextState2
} else {
val nextState1 = state
.modify(_.keysPressed)
.using(_ - 1)
if (nextState1.keysPressed === 0) {
nextState1.staminaTimer.cancel()
nextState1
} else
nextState1
} }
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) props.playerActor ! PlayerActor.MoveLeft(pressed)
receive(handleStamina(pressed)) receive(handleStamina(pressed))
case PlayerMovedRight(pressed) => case PlayerMovedRight(pressed) =>
props.movementActor ! ImMovementActor.MoveRight(pressed) props.movementActor ! ImMovementActor.MoveRight(pressed)

View File

@ -13,14 +13,14 @@ import monix.reactive.Observable
import wow.doge.mygame.game.entities.CharacterStats 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.character.CharacterStates._
import wow.doge.mygame.game.entities.player.PlayerActorSupervisor import wow.doge.mygame.game.entities.player.PlayerActor
import wow.doge.mygame.implicits._ import wow.doge.mygame.implicits._
import wow.doge.mygame.subsystems.movement.ImMovementActor import wow.doge.mygame.subsystems.movement.ImMovementActor
class IdleBehaviorFactory( class IdleBehaviorFactory(
env: PlayerActorSupervisor.Env, env: PlayerActor.Env,
nextStateFn: AliveSubstate => Behavior[PlayerActorSupervisor.Command], nextStateFn: AliveSubstate => Behavior[PlayerActor.Command],
deadState: Behavior[PlayerActorSupervisor.Command], deadState: Behavior[PlayerActor.Command],
consumptionMultiplier: Int => Int consumptionMultiplier: Int => Int
)(implicit timeout: Timeout) { )(implicit timeout: Timeout) {
import IdleBehaviorFactory.IdleBehavior import IdleBehaviorFactory.IdleBehavior
@ -31,23 +31,23 @@ class IdleBehaviorFactory(
def behavior = def behavior =
IdleBehavior( IdleBehavior(
Behaviors Behaviors
.receiveMessage[PlayerActorSupervisor.Command] { .receiveMessage[PlayerActor.Command] {
case PlayerActorSupervisor.MoveLeft(pressed) => case PlayerActor.MoveLeft(pressed) =>
children.movementActor ! ImMovementActor.MoveLeft(pressed) children.movementActor ! ImMovementActor.MoveLeft(pressed)
if (pressed) if (pressed)
nextStateFn(AliveSubstate.Moving(Walking)) nextStateFn(AliveSubstate.Moving(Walking))
else nextStateFn(AliveSubstate.Idle) else nextStateFn(AliveSubstate.Idle)
case PlayerActorSupervisor.TakeDamage(value, replyTo) => case PlayerActor.TakeDamage(value, replyTo) =>
implicit val ec = props.scheduler.value implicit val ec = props.scheduler.value
for { for {
res <- res <-
children.statsActor.ask(StatsActor.TakeDamageResult(value, _)) children.statsActor.ask(StatsActor.TakeDamageResult(value, _))
_ <- Future.successful( _ <- Future.successful(
ctx.self ! PlayerActorSupervisor.StatsResponse(res, replyTo) ctx.self ! PlayerActor.StatsResponse(res, replyTo)
) )
} yield () } yield ()
Behaviors.same Behaviors.same
case PlayerActorSupervisor.ConsumeStamina(value, replyTo) => case PlayerActor.ConsumeStamina(value, replyTo) =>
implicit val ec = props.scheduler.value implicit val ec = props.scheduler.value
val newValue = val newValue =
CharacterStats.DamageStamina(consumptionMultiplier(value.toInt)) CharacterStats.DamageStamina(consumptionMultiplier(value.toInt))
@ -56,20 +56,20 @@ class IdleBehaviorFactory(
StatsActor.ConsumeStaminaResult(newValue, _) StatsActor.ConsumeStaminaResult(newValue, _)
) )
_ = _ =
ctx.self ! PlayerActorSupervisor ctx.self ! PlayerActor
.StatsResponse(response, replyTo) .StatsResponse(response, replyTo)
} yield () } yield ()
Behaviors.same Behaviors.same
case PlayerActorSupervisor.CurrentStats(replyTo) => case PlayerActor.CurrentStats(replyTo) =>
children.statsActor ! StatsActor.CurrentStats(replyTo) children.statsActor ! StatsActor.CurrentStats(replyTo)
Behaviors.same Behaviors.same
case PlayerActorSupervisor.Heal(value) => case PlayerActor.Heal(value) =>
children.statsActor ! StatsActor.HealResult(value) children.statsActor ! StatsActor.HealResult(value)
Behaviors.same Behaviors.same
case PlayerActorSupervisor.GetStatus(replyTo) => case PlayerActor.GetStatus(replyTo) =>
replyTo ! PlayerActorSupervisor.Status.Alive replyTo ! PlayerActor.Status.Alive
Behaviors.same Behaviors.same
case PlayerActorSupervisor.GetStatsObservable(replyTo) => case PlayerActor.GetStatsObservable(replyTo) =>
import monix.{eval => me} import monix.{eval => me}
replyTo ! replyTo !
UIO( UIO(
@ -82,10 +82,10 @@ class IdleBehaviorFactory(
) )
Behaviors.same Behaviors.same
case PlayerActorSupervisor.StatsResponse(response, replyTo) => case PlayerActor.StatsResponse(response, replyTo) =>
response match { response match {
case (dead, stats) => case (dead, stats) =>
if (dead) ctx.self ! PlayerActorSupervisor.Die if (dead) ctx.self ! PlayerActor.Die
props.statsQueue props.statsQueue
.offer(stats) .offer(stats)
.foreach { _ => .foreach { _ =>
@ -95,8 +95,8 @@ class IdleBehaviorFactory(
} }
Behaviors.same Behaviors.same
// nextStateFn(InCombat(Moving(Walking))) // nextStateFn(InCombat(Moving(Walking)))
case PlayerActorSupervisor.Die => deadState case PlayerActor.Die => deadState
case PlayerActorSupervisor.LogError(ex) => case PlayerActor.LogError(ex) =>
ctx.log.error(ex.getMessage) ctx.log.error(ex.getMessage)
Behaviors.same Behaviors.same
} }
@ -110,6 +110,6 @@ class IdleBehaviorFactory(
object IdleBehaviorFactory { object IdleBehaviorFactory {
final case class IdleBehavior( final case class IdleBehavior(
value: Behavior[PlayerActorSupervisor.Command] value: Behavior[PlayerActor.Command]
) )
} }

View File

@ -0,0 +1,27 @@
package wow.doge.mygame
import sttp.capabilities.monix.MonixStreams
import sttp.client3._
import sttp.client3.asynchttpclient.monix._
import monix.eval.Task
import monix.reactive.Observable
import scala.concurrent.duration.Duration
class WebsocketTest {
// : Task[Response[Either[String, Observable[Array[Byte]]]]]
val s = AsyncHttpClientMonixBackend().flatMap { backend =>
val response =
basicRequest
.post(uri"...")
.response(
asStream(MonixStreams)(
_.doOnNext(i => Task(println(s"$i"))).completedL
)
)
.readTimeout(Duration.Inf)
.send(backend)
response
}
}