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.
268 lines
9.0 KiB
268 lines
9.0 KiB
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
|
|
}
|
|
}
|