package wow.doge.mygame.game.entities import akka.actor.typed.ActorRef import akka.actor.typed.Behavior import akka.actor.typed.SupervisorStrategy import akka.actor.typed.scaladsl.ActorContext import akka.actor.typed.scaladsl.Behaviors import wow.doge.mygame.subsystems.events.Event import wow.doge.mygame.subsystems.events.EventBus import wow.doge.mygame.subsystems.events.EventsModule.GameEventBus 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.subsystems.events.EntityMovementEvent import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedLeft import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedUp import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedRight import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedDown import wow.doge.mygame.math.ImVector3f import wow.doge.mygame.game.subsystems.movement.CanMove import wow.doge.mygame.implicits._ import akka.util.Timeout import scala.concurrent.duration._ import scala.util.Success import scala.util.Failure object NpcActorSupervisor { sealed trait Command case class Move(pos: ImVector3f) extends Command private case class UpdatePosition(pos: ImVector3f) extends Command private case class LogError(err: Throwable) extends Command case object MovementTick extends Command final case class Props( npcMovementActorBehavior: Behavior[NpcMovementActor2.Command], npcName: String, initialPos: ImVector3f ) { def create = Behaviors.setup[Command] { ctx => val npcMovementActor = ctx.spawn( (npcMovementActorBehavior), s"npc-${npcName}-NpcMovementActor" ) new NpcActorSupervisor(ctx, this) .idle(State(npcMovementActor, initialPos)) } } final case class State( npcMovementActor: ActorRef[NpcMovementActor2.Command], currentPos: ImVector3f ) } class NpcActorSupervisor( ctx: ActorContext[NpcActorSupervisor.Command], props: NpcActorSupervisor.Props ) { import NpcActorSupervisor._ implicit val timeout = Timeout(1.second) def idle(state: State): Behavior[NpcActorSupervisor.Command] = Behaviors.receiveMessage[Command] { case Move(pos) => { state.npcMovementActor ! NpcMovementActor2.Move(pos) val movementTimer = ctx.spawn( GenericTimerActor.Props(ctx.self, MovementTick, 100.millis).create, s"npc-${props.npcName}-NpcActorTimer" ) movementTimer ! GenericTimerActor.Start moving(state, pos, movementTimer) } case LogError(err) => ctx.log.warn(err.getMessage()) Behaviors.same case _ => Behaviors.unhandled } def moving( state: State, targetPos: ImVector3f, movementTimer: ActorRef[GenericTimerActor.Command] ): Behavior[NpcActorSupervisor.Command] = Behaviors.receiveMessagePartial[Command] { case LogError(err) => ctx.log.warn(err.getMessage()) Behaviors.same case Move(pos) => moving(state, pos, movementTimer) case UpdatePosition(pos) => ctx.log.trace("Current pos = " + state.currentPos.toString()) moving(state.copy(currentPos = pos), targetPos, movementTimer) case MovementTick => val dst = ImVector3f.dst(targetPos, state.currentPos) if (dst <= 10f) { state.npcMovementActor ! NpcMovementActor2.StopMoving movementTimer ! GenericTimerActor.Stop idle(state) } else { // ctx.log.debug("Difference = " + dst.toString()) // ctx.log.debug("Current pos = " + state.currentPos.toString()) ctx.ask(state.npcMovementActor, NpcMovementActor2.AskPosition(_)) { case Success(value) => UpdatePosition(value) case Failure(exception) => LogError(exception) } // Behaviors.same moving(state, targetPos, movementTimer) } } } object NpcMovementActor2 { sealed trait Command case class AskPosition(replyTo: ActorRef[ImVector3f]) extends Command case object StopMoving extends Command case class Move(target: ImVector3f) extends Command final class Props[T: CanMove]( val enqueueR: Function1[() => Unit, Unit], val initialPos: ImVector3f, val tickEventBus: GameEventBus[TickEvent], val movable: T ) { def create = Behaviors.setup[Command] { ctx => new NpcMovementActor2(ctx, this).receive(State(initialPos)) } } final case class State(currentPos: ImVector3f) } class NpcMovementActor2[T]( ctx: ActorContext[NpcMovementActor2.Command], props: NpcMovementActor2.Props[T] ) { import NpcMovementActor2._ def receive( state: State )(implicit cm: CanMove[T]): Behavior[NpcMovementActor2.Command] = Behaviors.receiveMessage[Command] { case AskPosition(replyTo) => replyTo ! cm.location(props.movable) Behaviors.same case Move(target: ImVector3f) => props.enqueueR(() => cm.move(props.movable, (target - state.currentPos) * 0.005f) ) receive(state = state.copy(currentPos = cm.location(props.movable))) case StopMoving => ctx.log.debug( "Position at Stop = " + cm.location(props.movable).toString ) props.enqueueR(() => cm.stop(props.movable)) receive(state = state.copy(currentPos = cm.location(props.movable))) } } object NpcMovementActor { sealed trait Command final case class Props( imMovementActorBehavior: Behavior[ImMovementActor.Command], npcName: String, // movementActor: ActorRef[ImMovementActor.Command], mainEventBus: ActorRef[ EventBus.Command[Event] ], tickEventBus: GameEventBus[TickEvent] ) { def create: Behavior[Command] = Behaviors.setup { ctx => val movementActor = ctx.spawn( Behaviors .supervise(imMovementActorBehavior) .onFailure[Exception](SupervisorStrategy.restart), s"npc-${npcName}-MovementActorChild" ) val npcMovementElBehavior = Behaviors.receiveMessagePartial[Event] { case event: EntityMovementEvent => event match { case MovedLeft(name, pressed) => if (name == npcName) movementActor ! ImMovementActor.MovedLeft(pressed) Behaviors.same case MovedUp(name, pressed) => if (name == npcName) movementActor ! ImMovementActor.MovedUp(pressed) Behaviors.same case MovedRight(name, pressed) => if (name == npcName) movementActor ! ImMovementActor.MovedRight(pressed) Behaviors.same case MovedDown(name, pressed) => if (name == npcName) movementActor ! ImMovementActor.MovedDown(pressed) Behaviors.same } } val npcMovementEl = ctx.spawn( Behaviors .supervise(npcMovementElBehavior) .onFailure[Exception](SupervisorStrategy.restart), s"npc-${npcName}-MovementEventHandler" ) val renderTickElBehavior = Behaviors.receiveMessage[RenderTick.type] { case RenderTick => movementActor ! ImMovementActor.Tick Behaviors.same } val renderTickEl = ctx.spawn( renderTickElBehavior, s"npc-${npcName}-MovementTickListener" ) mainEventBus ! EventBus.Subscribe(npcMovementEl) tickEventBus ! EventBus.Subscribe(renderTickEl) Behaviors.receiveMessage { msg => Behaviors.same } } } }