package wow.doge.mygame.game.entities import scala.concurrent.duration._ 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 com.typesafe.scalalogging.Logger import org.slf4j.event.Level import wow.doge.mygame.Dispatchers 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 scala.util.Success import scala.util.Failure import akka.util.Timeout import monix.reactive.Observable import monix.reactive.subjects.ConcurrentSubject import monix.execution.Scheduler import monix.reactive.OverflowStrategy 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: Int) extends Command case class Heal(value: Int) extends Command case class CurrentStats(replyTo: ActorRef[StatsActor.State]) extends Command case class GetStatus(replyTo: ActorRef[Status]) extends Command case class GetStatsObservable(replyTo: ActorRef[Observable[StatsActor.State]]) extends Command private case object Die extends Command private case class DamageResponse(response: (Boolean, StatsActor.State)) 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: Scheduler ) { 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(100, 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.DropOld(50))(scheduler) ).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[StatsActor.State, StatsActor.State] ) { 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 CurrentStats(replyTo) => // ctx.ask(children.statsActor, StatsActor.CurrentStats()) children.statsActor ! StatsActor.CurrentStats(replyTo) Behaviors.same case Heal(value) => children.statsActor ! StatsActor.Heal(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 DamageResponse(response) => response match { case (dead, state) => if (dead) ctx.self ! Die statsSubject.onNext(state) } 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 } } object StatsActor { sealed trait Command case class TakeDamage(value: Int) extends Command case class TakeDamageResult(value: Int, replyTo: ActorRef[(Boolean, State)]) extends Command case class Heal(value: Int) extends Command case class CurrentStats(replyTo: ActorRef[State]) extends Command class Props(startingHealth: Int, startingStamina: Int) { def behavior = Behaviors.setup[Command] { ctx => new StatsActor(ctx, this) .receive(State(startingHealth, startingStamina)) } } case class State(hp: Int, stamina: Int) } class StatsActor( ctx: ActorContext[StatsActor.Command], props: StatsActor.Props ) { import StatsActor._ import com.softwaremill.quicklens._ def receive(state: State): Behavior[Command] = Behaviors.receiveMessage[Command] { // Todo add min max values case TakeDamage(value) => val nextState = if (state.hp - value <= 0) state.modify(_.hp).setTo(0) else state.modify(_.hp).using(_ - value) receive(nextState) case TakeDamageResult(value, replyTo) => val nextState = if (state.hp - value <= 0) { replyTo ! true -> state state } else { replyTo ! false -> state state.modify(_.hp).using(_ - value) } receive(nextState) case Heal(value) => receive(state.modify(_.hp).using(_ + value)) case CurrentStats(replyTo) => replyTo ! state Behaviors.same } }