Testing out JmonkeyEngine to make a game in Scala with Akka Actors within a pure FP layer
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.
 
 

225 lines
7.7 KiB

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 }
}
}
}