You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
268 lines
9.4 KiB
268 lines
9.4 KiB
package wow.doge.mygame.game.entities
|
|
|
|
import scala.concurrent.duration._
|
|
import scala.util.Failure
|
|
import scala.util.Success
|
|
|
|
import akka.actor.typed.ActorRef
|
|
import akka.actor.typed.Behavior
|
|
import akka.actor.typed.LogOptions
|
|
import akka.actor.typed.PostStop
|
|
import akka.actor.typed.SupervisorStrategy
|
|
import akka.actor.typed.scaladsl.ActorContext
|
|
import akka.actor.typed.scaladsl.Behaviors
|
|
import akka.util.Timeout
|
|
import com.typesafe.scalalogging.Logger
|
|
import monix.reactive.Observable
|
|
import monix.reactive.OverflowStrategy
|
|
import monix.reactive.subjects.ConcurrentSubject
|
|
import org.slf4j.event.Level
|
|
import wow.doge.mygame.Dispatchers
|
|
import wow.doge.mygame.executors.Schedulers.AsyncScheduler
|
|
import wow.doge.mygame.game.entities.StatsActor
|
|
import wow.doge.mygame.implicits._
|
|
import wow.doge.mygame.subsystems.events.EventBus
|
|
import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus
|
|
import wow.doge.mygame.subsystems.events.PlayerEvent
|
|
import wow.doge.mygame.subsystems.events.TickEvent
|
|
import wow.doge.mygame.subsystems.events.TickEvent.RenderTick
|
|
import wow.doge.mygame.subsystems.movement.ImMovementActor
|
|
import monix.eval.Coeval
|
|
import monix.execution.AsyncQueue
|
|
object PlayerActorSupervisor {
|
|
|
|
type Ref = ActorRef[PlayerActorSupervisor.Command]
|
|
|
|
sealed trait Status
|
|
object Status {
|
|
case object Alive extends Status
|
|
case object Dead extends Status
|
|
}
|
|
|
|
sealed trait Command
|
|
case class TakeDamage(value: CharacterStats.DamageHealth) extends Command
|
|
case class TakeDamage2(
|
|
value: CharacterStats.DamageHealth,
|
|
replyTo: ActorRef[Unit]
|
|
) extends Command
|
|
case class Heal(value: CharacterStats.HealHealth) extends Command
|
|
case class CurrentStats(replyTo: ActorRef[CharacterStats]) extends Command
|
|
case class GetStatus(replyTo: ActorRef[Status]) extends Command
|
|
case class GetStatsObservable(replyTo: ActorRef[Observable[CharacterStats]])
|
|
extends Command
|
|
case class GetStatsObservable2(replyTo: ActorRef[Observable[CharacterStats]])
|
|
extends Command
|
|
|
|
private case object Die extends Command
|
|
private case class DamageResponse(response: (Boolean, CharacterStats))
|
|
extends Command
|
|
private case class DamageResponse2(
|
|
response: (Boolean, CharacterStats),
|
|
replyTo: ActorRef[Unit]
|
|
) extends Command
|
|
// private case class InternalTakeDamage(old: Int, value: Int) extends Command
|
|
private case class LogError(ex: Throwable) extends Command
|
|
class Props(
|
|
val playerEventBus: GameEventBus[PlayerEvent],
|
|
val tickEventBus: GameEventBus[TickEvent],
|
|
val imMovementActorBehavior: Behavior[ImMovementActor.Command],
|
|
val scheduler: AsyncScheduler
|
|
) {
|
|
def behavior =
|
|
Behaviors.logMessages(
|
|
LogOptions()
|
|
.withLevel(Level.DEBUG)
|
|
.withLogger(
|
|
Logger[PlayerActorSupervisor].underlying
|
|
),
|
|
Behaviors
|
|
.setup[Command] { ctx =>
|
|
ctx.log.infoP("Starting PlayerActor")
|
|
|
|
// spawn children actors
|
|
val playerMovementActor =
|
|
ctx.spawnN(
|
|
Behaviors
|
|
.supervise(imMovementActorBehavior)
|
|
.onFailure[Exception](
|
|
SupervisorStrategy.restart.withLimit(2, 100.millis)
|
|
),
|
|
Dispatchers.jmeDispatcher
|
|
)
|
|
|
|
val playerStatsActor =
|
|
ctx.spawnN(
|
|
new StatsActor.Props(
|
|
CharacterStats.Health(100),
|
|
CharacterStats.Stamina(100)
|
|
).behavior
|
|
)
|
|
|
|
val playerMovementEl = ctx.spawnN(
|
|
Behaviors
|
|
.supervise(PlayerMovementEventListener(playerMovementActor))
|
|
.onFailure[Exception](
|
|
SupervisorStrategy.restart.withLimit(2, 100.millis)
|
|
)
|
|
)
|
|
|
|
val renderTickEl = {
|
|
val behavior: Behavior[RenderTick.type] =
|
|
Behaviors.setup(ctx =>
|
|
Behaviors
|
|
.receiveMessage[RenderTick.type] {
|
|
case RenderTick =>
|
|
playerMovementActor ! ImMovementActor.Tick
|
|
// playerCameraActor ! PlayerCameraActor.Tick
|
|
Behaviors.same
|
|
}
|
|
.receiveSignal {
|
|
case (_, PostStop) =>
|
|
ctx.log.infoP("stopped")
|
|
Behaviors.same
|
|
}
|
|
)
|
|
ctx.spawn(behavior, "playerMovementTickListener")
|
|
}
|
|
|
|
//init listeners
|
|
playerEventBus ! EventBus.Subscribe(playerMovementEl)
|
|
tickEventBus ! EventBus.Subscribe(renderTickEl)
|
|
|
|
new PlayerActorSupervisor(
|
|
ctx,
|
|
this,
|
|
Children(playerMovementActor, playerStatsActor),
|
|
ConcurrentSubject.publish(
|
|
OverflowStrategy.DropOldAndSignal(
|
|
50,
|
|
dropped => Coeval.pure(None)
|
|
)
|
|
)(scheduler.value),
|
|
AsyncQueue.bounded(10)(scheduler.value)
|
|
).aliveState
|
|
}
|
|
)
|
|
|
|
}
|
|
|
|
case class Children(
|
|
movementActor: ActorRef[ImMovementActor.Command],
|
|
statsActor: ActorRef[StatsActor.Command]
|
|
)
|
|
}
|
|
class PlayerActorSupervisor(
|
|
ctx: ActorContext[PlayerActorSupervisor.Command],
|
|
props: PlayerActorSupervisor.Props,
|
|
children: PlayerActorSupervisor.Children,
|
|
statsSubject: ConcurrentSubject[CharacterStats, CharacterStats],
|
|
statsQueue: AsyncQueue[CharacterStats]
|
|
) {
|
|
import PlayerActorSupervisor._
|
|
implicit val timeout = Timeout(1.second)
|
|
val aliveState =
|
|
Behaviors
|
|
.receiveMessage[Command] {
|
|
case TakeDamage(value) =>
|
|
// children.movementActor ! ImMovementActor.MovedDown(true)
|
|
// ctx.ask(children.statsActor, StatsActor.CurrentStats(_)) {
|
|
// case Success(status) => InternalTakeDamage(status.hp, value)
|
|
// case Failure(ex) => LogError(ex)
|
|
// }
|
|
ctx.ask(children.statsActor, StatsActor.TakeDamageResult(value, _)) {
|
|
case Success(response) => DamageResponse(response)
|
|
case Failure(ex) => LogError(ex)
|
|
}
|
|
Behaviors.same
|
|
case TakeDamage2(value, replyTo) =>
|
|
// children.movementActor ! ImMovementActor.MovedDown(true)
|
|
// ctx.ask(children.statsActor, StatsActor.CurrentStats(_)) {
|
|
// case Success(status) => InternalTakeDamage(status.hp, value)
|
|
// case Failure(ex) => LogError(ex)
|
|
// }
|
|
ctx.ask(children.statsActor, StatsActor.TakeDamageResult(value, _)) {
|
|
case Success(response) => DamageResponse2(response, replyTo)
|
|
case Failure(ex) => LogError(ex)
|
|
}
|
|
Behaviors.same
|
|
case CurrentStats(replyTo) =>
|
|
// ctx.ask(children.statsActor, StatsActor.CurrentStats())
|
|
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 _ => Behaviors.unhandled
|
|
// case InternalTakeDamage(hp, damage) =>
|
|
// if (hp - damage <= 0) dead
|
|
// else {
|
|
// children.statsActor ! StatsActor.TakeDamage(damage)
|
|
// Behaviors.same
|
|
// }
|
|
case GetStatsObservable(replyTo) =>
|
|
replyTo ! statsSubject
|
|
Behaviors.same
|
|
case GetStatsObservable2(replyTo) =>
|
|
import monix.{eval => me}
|
|
replyTo ! Observable.repeatEvalF(
|
|
me.Task.deferFuture(statsQueue.poll())
|
|
)
|
|
Behaviors.same
|
|
case DamageResponse(response) =>
|
|
response match {
|
|
case (dead, state) =>
|
|
if (dead) ctx.self ! Die
|
|
statsSubject.onNext(state)
|
|
}
|
|
Behaviors.same
|
|
case DamageResponse2(response, replyTo) =>
|
|
response match {
|
|
case (dead, stats) =>
|
|
if (dead) ctx.self ! Die
|
|
statsQueue
|
|
.offer(stats)
|
|
.foreach(_ => 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")
|
|
statsSubject.onComplete()
|
|
Behaviors.same
|
|
}
|
|
val deadState = Behaviors
|
|
.receiveMessage[Command] {
|
|
// case TakeDamage(value) =>
|
|
// children.statsActor ! StatsActor.TakeDamage(value)
|
|
// // children.movementActor ! ImMovementActor.MovedDown(true)
|
|
// Behaviors.same
|
|
// case CurrentStats(replyTo) =>
|
|
// // ctx.ask(children.statsActor, StatsActor.CurrentStats())
|
|
// children.statsActor ! StatsActor.CurrentStats(replyTo)
|
|
// Behaviors.same
|
|
// case Heal(_) =>
|
|
// Behaviors.same
|
|
case CurrentStats(replyTo) =>
|
|
// ctx.ask(children.statsActor, StatsActor.CurrentStats())
|
|
children.statsActor ! StatsActor.CurrentStats(replyTo)
|
|
Behaviors.same
|
|
case GetStatus(replyTo) =>
|
|
replyTo ! Status.Dead
|
|
Behaviors.same
|
|
case _ => Behaviors.unhandled
|
|
}
|
|
.receiveSignal {
|
|
case (_, PostStop) =>
|
|
ctx.log.infoP("stopped")
|
|
statsSubject.onComplete()
|
|
Behaviors.same
|
|
}
|
|
}
|