package wow.doge.mygame.game.entities.player 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 akka.util.Timeout import com.typesafe.scalalogging.Logger import monix.bio.UIO import monix.execution.AsyncQueue import monix.reactive.Observable import org.slf4j.event.Level import wow.doge.mygame.Dispatchers import wow.doge.mygame.executors.Schedulers 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.character.CharacterStates._ import wow.doge.mygame.game.entities.player.behaviors.IdleBehaviorFactory 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 wow.doge.mygame.utils.MovementDirection object PlayerActor { type Ref = ActorRef[PlayerActor.Command] sealed trait Status object Status { case object Alive extends Status case object Dead extends Status } sealed trait Command extends Product with Serializable final case class TakeDamage( value: CharacterStats.DamageHealth, replyTo: ActorRef[CharacterStats] ) extends Command final case class ConsumeStamina( value: CharacterStats.DamageStamina, 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 CurrentStats(replyTo: ActorRef[CharacterStats]) extends Command final case class GetStatus(replyTo: ActorRef[Status]) extends Command final case class GetStatsObservable( replyTo: ActorRef[UIO[Observable[CharacterStats]]] ) extends Command final case class Walk(pressed: Boolean, dir: MovementDirection) extends Command case object StopMoving extends Command case object Jump extends Command private[player] final case class HandleWalk( b: CharacterStats, pressed: Boolean, direction: MovementDirection ) extends Command private[player] case object Die extends Command private[player] final case class StatsResponse( response: (StatsActor.Status, CharacterStats), replyTo: ActorRef[CharacterStats] ) extends Command private[player] final case class LogError(ex: Throwable) extends Command class Props( val playerEventBus: GameEventBus[PlayerEvent], val tickEventBus: GameEventBus[TickEvent], val imMovementActorBehavior: Behavior[ImMovementActor.Command], val statsActorBehavior: Behavior[StatsActor.Command], val scheduler: AsyncScheduler, val fxScheduler: Schedulers.FxScheduler, val statsQueue: AsyncQueue[CharacterStats] ) { def behavior = Behaviors.logMessages( LogOptions() .withLevel(Level.DEBUG) .withLogger(Logger[PlayerActor].underlying), Behaviors .setup[Command] { ctx => ctx.log.infoP("Starting PlayerActor") // spawn children actors val playerStatsActor = ctx.spawnN(statsActorBehavior) val playerMovementActor = ctx.spawnN( Behaviors .supervise(imMovementActorBehavior) .onFailure[Exception]( SupervisorStrategy.restart.withLimit(2, 100.millis) ), Dispatchers.jmeDispatcher ) // val playerMovementEl = ctx.spawnN( // Behaviors // .supervise( // new PlayerMovementEventListener.Props( // ctx.self, // scheduler // ).behavior // ) // .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 PlayerActor( ctx, this, Children(playerMovementActor, playerStatsActor) ).aliveState(AliveSubstate.Idle) } ) } final case class Children( movementActor: ActorRef[ImMovementActor.Command], statsActor: ActorRef[StatsActor.Command] ) final case class Env( ctx: ActorContext[PlayerActor.Command], props: PlayerActor.Props, children: PlayerActor.Children ) } class PlayerActor( ctx: ActorContext[PlayerActor.Command], props: PlayerActor.Props, children: PlayerActor.Children ) { import PlayerActor._ implicit val timeout = Timeout(1.second) val env = Env(ctx, props, children) 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") 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(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 } }