forked from nova/jmonkey-test
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.
310 lines
9.8 KiB
310 lines
9.8 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.SupervisorStrategy
|
|
import akka.actor.typed.scaladsl.ActorContext
|
|
import akka.actor.typed.scaladsl.Behaviors
|
|
import akka.util.Timeout
|
|
import cats.syntax.show._
|
|
import monix.execution.CancelableFuture
|
|
import monix.execution.CancelablePromise
|
|
import wow.doge.mygame.game.subsystems.movement.CanMove
|
|
import wow.doge.mygame.implicits._
|
|
import wow.doge.mygame.math.ImVector3f
|
|
import wow.doge.mygame.subsystems.events.EntityMovementEvent
|
|
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedDown
|
|
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedLeft
|
|
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedRight
|
|
import wow.doge.mygame.subsystems.events.EntityMovementEvent.MovedUp
|
|
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.utils.GenericTimerActor
|
|
|
|
object NpcActorSupervisor {
|
|
type Ref = ActorRef[Command]
|
|
|
|
sealed trait Command
|
|
final case class Move(pos: ImVector3f) extends Command
|
|
private final case class InternalMove(
|
|
move: Move,
|
|
signal: CancelableFuture[NpcMovementActor.DoneMoving.type]
|
|
) extends Command
|
|
private case object DoneMoving extends Command
|
|
private case class LogError(err: Throwable) extends Command
|
|
private case class MovementFailed(err: Throwable) extends Command
|
|
|
|
class Props(
|
|
val npcMovementActorBehavior: Behavior[NpcMovementActor.Command],
|
|
val npcName: String,
|
|
val initialPos: ImVector3f
|
|
) {
|
|
def behavior =
|
|
Behaviors.withMdc[Command](Map("actorName" -> npcName))(
|
|
Behaviors.setup[Command] { ctx =>
|
|
val npcMovementActor = ctx.spawn(
|
|
(npcMovementActorBehavior),
|
|
s"npc-${npcName}-NpcMovementActor"
|
|
)
|
|
|
|
new NpcActorSupervisor(ctx, this, Children(npcMovementActor))
|
|
.idle(State())
|
|
}
|
|
)
|
|
}
|
|
final case class State()
|
|
final case class Children(
|
|
npcMovementActor: ActorRef[NpcMovementActor.Command]
|
|
)
|
|
}
|
|
class NpcActorSupervisor(
|
|
ctx: ActorContext[NpcActorSupervisor.Command],
|
|
props: NpcActorSupervisor.Props,
|
|
children: NpcActorSupervisor.Children
|
|
) {
|
|
import NpcActorSupervisor._
|
|
implicit val timeout = Timeout(1.second)
|
|
|
|
val movementTimer = ctx.spawn(
|
|
GenericTimerActor
|
|
.Props(
|
|
children.npcMovementActor,
|
|
NpcMovementActor.MovementTick,
|
|
100.millis
|
|
)
|
|
.behavior,
|
|
s"npc-${props.npcName}-NpcActorTimer"
|
|
)
|
|
|
|
def idle(state: State): Behavior[NpcActorSupervisor.Command] =
|
|
Behaviors.setup { _ =>
|
|
ctx.log.debugP(s"npcActor-${props.npcName}: Entered Idle State")
|
|
Behaviors.receiveMessage[Command] {
|
|
case m @ Move(pos) =>
|
|
ctx.ask(
|
|
children.npcMovementActor,
|
|
NpcMovementActor.MoveTo(pos, _)
|
|
) {
|
|
case Success(signal) => InternalMove(m, signal)
|
|
case Failure(exception) => LogError(exception)
|
|
}
|
|
Behaviors.same
|
|
case InternalMove(move, signal) =>
|
|
moving(state, move.pos, signal)
|
|
|
|
case LogError(err) =>
|
|
logError(err)
|
|
Behaviors.same
|
|
case _ => Behaviors.unhandled
|
|
}
|
|
}
|
|
|
|
def moving(
|
|
state: State,
|
|
targetPos: ImVector3f,
|
|
signal: CancelableFuture[NpcMovementActor.DoneMoving.type]
|
|
): Behavior[NpcActorSupervisor.Command] =
|
|
Behaviors.setup { _ =>
|
|
ctx.log.debugP(s"npcActor-${props.npcName}: Entered Moving State")
|
|
movementTimer ! GenericTimerActor.Start
|
|
ctx.pipeToSelf(signal) {
|
|
case Success(value) => DoneMoving
|
|
case Failure(exception) => MovementFailed(exception)
|
|
}
|
|
Behaviors.receiveMessagePartial[Command] {
|
|
case LogError(err) =>
|
|
logError(err)
|
|
Behaviors.same
|
|
case MovementFailed(err) =>
|
|
ctx.self ! LogError(err)
|
|
movementTimer ! GenericTimerActor.Stop
|
|
idle(state)
|
|
case m @ Move(pos) =>
|
|
movementTimer ! GenericTimerActor.Stop
|
|
children.npcMovementActor ! NpcMovementActor.StopMoving
|
|
// new movement request received, cancel previous request
|
|
signal.cancel()
|
|
ctx.ask(
|
|
children.npcMovementActor,
|
|
NpcMovementActor.MoveTo(pos, _)
|
|
) {
|
|
case Success(signal) => InternalMove(m, signal)
|
|
case Failure(exception) => MovementFailed(exception)
|
|
}
|
|
Behaviors.same
|
|
case InternalMove(move, signal) =>
|
|
moving(state, targetPos, signal)
|
|
case DoneMoving =>
|
|
movementTimer ! GenericTimerActor.Stop
|
|
idle(state)
|
|
|
|
}
|
|
}
|
|
|
|
def logError(err: Throwable) =
|
|
ctx.log.errorP(s"npcActor-${props.npcName}: " + err.getMessage)
|
|
}
|
|
|
|
object NpcMovementActor {
|
|
|
|
case object DoneMoving
|
|
|
|
sealed trait Command
|
|
case class AskPosition(replyTo: ActorRef[ImVector3f]) extends Command
|
|
case object MovementTick extends Command
|
|
case object StopMoving extends Command
|
|
case class MoveTo(
|
|
target: ImVector3f,
|
|
doneSignal: ActorRef[CancelableFuture[DoneMoving.type]]
|
|
) extends Command
|
|
|
|
class Props[T: CanMove](
|
|
val enqueueR: Function1[() => Unit, Unit],
|
|
val initialPos: ImVector3f,
|
|
// val tickEventBus: GameEventBus[TickEvent],
|
|
val npcName: String,
|
|
val movable: T
|
|
) {
|
|
def behavior =
|
|
Behaviors.setup[Command] { ctx =>
|
|
new NpcMovementActor(ctx, this).receive(State())
|
|
}
|
|
}
|
|
|
|
final case class State()
|
|
}
|
|
class NpcMovementActor[T](
|
|
ctx: ActorContext[NpcMovementActor.Command],
|
|
props: NpcMovementActor.Props[T]
|
|
)(implicit cm: CanMove[T]) {
|
|
import NpcMovementActor._
|
|
|
|
def location = cm.location(props.movable)
|
|
|
|
def receive(state: State): Behavior[NpcMovementActor.Command] =
|
|
Behaviors.receiveMessagePartial {
|
|
case AskPosition(replyTo) =>
|
|
replyTo ! location
|
|
Behaviors.same
|
|
case MoveTo(
|
|
target: ImVector3f,
|
|
replyTo: ActorRef[CancelableFuture[DoneMoving.type]]
|
|
) =>
|
|
props.enqueueR(() => cm.move(props.movable, target - location, 20f))
|
|
val p = CancelablePromise[DoneMoving.type]()
|
|
replyTo ! p.future
|
|
ticking(p, target, state)
|
|
}
|
|
|
|
def ticking(
|
|
reachDestination: CancelablePromise[DoneMoving.type],
|
|
targetPos: ImVector3f,
|
|
state: State
|
|
): Behavior[NpcMovementActor.Command] =
|
|
Behaviors.receiveMessagePartial {
|
|
case StopMoving =>
|
|
ctx.log.debugP(
|
|
show"npcActor-${props.npcName}: Position at Stop = " + location
|
|
)
|
|
props.enqueueR(() => cm.stop(props.movable))
|
|
receive(state)
|
|
|
|
case MovementTick =>
|
|
val dst = ImVector3f.manhattanDst(targetPos, location)
|
|
if (dst <= 10f) {
|
|
ctx.self ! StopMoving
|
|
reachDestination.success(DoneMoving)
|
|
} else {
|
|
// format:off
|
|
ctx.log.traceP(
|
|
show"npcActor-${props.npcName}: Difference = ${dst.formatted("%.2f")}"
|
|
)
|
|
ctx.log.traceP(
|
|
show"npcActor-${props.npcName}: Current pos = $location"
|
|
)
|
|
}
|
|
|
|
Behaviors.same
|
|
}
|
|
}
|
|
|
|
object NpcMovementActorNotUsed {
|
|
sealed trait Command
|
|
class Props(
|
|
imMovementActorBehavior: Behavior[ImMovementActor.Command],
|
|
npcName: String,
|
|
// movementActor: ActorRef[ImMovementActor.Command],
|
|
mainEventBus: ActorRef[
|
|
EventBus.Command[Event]
|
|
],
|
|
tickEventBus: GameEventBus[TickEvent]
|
|
) {
|
|
|
|
def behavior: Behavior[Command] =
|
|
Behaviors.setup { ctx =>
|
|
val movementActor = ctx.spawn(
|
|
Behaviors
|
|
.supervise(imMovementActorBehavior)
|
|
.onFailure[Exception](
|
|
SupervisorStrategy.restart.withLimit(2, 100.millis)
|
|
),
|
|
s"npc-${npcName}-MovementActorChild"
|
|
)
|
|
val npcMovementElBehavior = Behaviors.receiveMessagePartial[Event] {
|
|
case event: EntityMovementEvent =>
|
|
event match {
|
|
case MovedLeft(name, pressed) =>
|
|
if (name == npcName)
|
|
movementActor ! ImMovementActor.MoveLeft(pressed)
|
|
Behaviors.same
|
|
case MovedUp(name, pressed) =>
|
|
if (name == npcName)
|
|
movementActor ! ImMovementActor.MoveUp(pressed)
|
|
Behaviors.same
|
|
case MovedRight(name, pressed) =>
|
|
if (name == npcName)
|
|
movementActor ! ImMovementActor.MoveRight(pressed)
|
|
Behaviors.same
|
|
case MovedDown(name, pressed) =>
|
|
if (name == npcName)
|
|
movementActor ! ImMovementActor.MoveDown(pressed)
|
|
Behaviors.same
|
|
}
|
|
}
|
|
|
|
val npcMovementEl = ctx.spawn(
|
|
Behaviors
|
|
.supervise(npcMovementElBehavior)
|
|
.onFailure[Exception](
|
|
SupervisorStrategy.restart.withLimit(2, 100.millis)
|
|
),
|
|
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)
|
|
}
|
|
|
|
}
|
|
}
|