package wow.doge.mygame import scala.jdk.CollectionConverters._ import scala.reflect.ClassTag import akka.actor.typed.ActorRef import akka.actor.typed.Behavior import akka.actor.typed.Props import akka.actor.typed.Scheduler import akka.actor.typed.scaladsl.ActorContext import akka.util.Timeout import com.jayfella.jme.jfx.JavaFxUI import com.jme3.app.Application import com.jme3.app.SimpleApplication import com.jme3.app.state.AppState import com.jme3.app.state.AppStateManager import com.jme3.asset.AssetLocator import com.jme3.asset.AssetManager import com.jme3.bullet.BulletAppState import com.jme3.bullet.PhysicsTickListener import com.jme3.bullet.collision.PhysicsCollisionListener import com.jme3.bullet.collision.PhysicsCollisionObject import com.jme3.bullet.collision.{ PhysicsCollisionEvent => jmePhysicsCollisionEvent } import com.jme3.bullet.control.BetterCharacterControl import com.jme3.input.Action import com.jme3.input.InputManager import com.jme3.input.controls.ActionListener import com.jme3.input.controls.AnalogListener import com.jme3.input.controls.InputListener import com.jme3.input.controls.Trigger import com.jme3.light.Light import com.jme3.material.Material import com.jme3.math.Vector3f import com.jme3.scene.CameraNode import com.jme3.scene.Geometry import com.jme3.scene.Node import com.jme3.scene.SceneGraphVisitor import com.jme3.scene.Spatial import com.jme3.scene.control.CameraControl.ControlDirection import com.jme3.scene.control.Control import com.jme3.{bullet => jmeb} import com.simsilica.es.EntityComponent import com.simsilica.es.EntityData import com.simsilica.es.EntityId import enumeratum._ import io.odin.meta.Position import io.odin.meta.Render import monix.bio.IO import monix.bio.Task import monix.bio.UIO import monix.eval.Coeval import monix.execution.Ack import monix.execution.Ack.Continue import monix.execution.Ack.Stop import monix.execution.Cancelable import monix.execution.cancelables.SingleAssignCancelable import monix.reactive.Observable import monix.reactive.OverflowStrategy import monix.reactive.observers.Subscriber import org.slf4j.Logger import wow.doge.mygame.math.ImVector3f import wow.doge.mygame.state.MyBaseState import wow.doge.mygame.utils.wrappers.jme.PhysicsSpace final case class ActionEvent(binding: Action, value: Boolean, tpf: Float) final case class EnumActionEvent[T <: EnumEntry]( binding: T, value: Boolean, tpf: Float ) final case class AnalogEvent(binding: Action, value: Float, tpf: Float) final case class EnumAnalogEvent[T <: EnumEntry]( binding: T, value: Float, tpf: Float ) final case class PrePhysicsTickEvent(space: PhysicsSpace) final case class PhysicsTickEvent(space: PhysicsSpace) final case class CollisionEvent( nodeA: Option[Spatial], nodeB: Option[Spatial], objectA: PhysicsCollisionObject, objectB: PhysicsCollisionObject, appliedImpulse: Function0[Float] ) package object implicits { type PrePhysicsTickEvent = PhysicsTickEvent type PhysicsTickObservable = Observable[Either[PrePhysicsTickEvent, PhysicsTickEvent]] implicit final class JMEAppExt(private val app: Application) extends AnyVal { def enqueueR(cb: () => Unit) = app.enqueue(new Runnable { override def run() = cb() }) } implicit final class StateManagerExt(private val asm: AppStateManager) extends AnyVal { def state[S <: AppState]()(implicit c: ClassTag[S]): S = asm.getState(c.runtimeClass.asInstanceOf[Class[S]]) // def appStates = asm.getStates() } implicit final class SimpleApplicationExt[T <: SimpleApplication]( private val sa: T ) extends AnyVal { def stateManager: AppStateManager = sa.getStateManager() def inputManager: InputManager = sa.getInputManager() def assetManager: AssetManager = sa.getAssetManager() def guiNode = sa.getGuiNode() def flyCam = Option(sa.getFlyByCamera()) def camera = sa.getCamera() def viewPort = sa.getViewPort() def rootNode = sa.getRootNode() def observableTick: Observable[Float] = Observable.create(OverflowStrategy.Unbounded) { sub => val c = SingleAssignCancelable() val as = new MyBaseState { override def init(): Unit = {} override def update(tpf: Float) = if (sub.onNext(tpf) == Ack.Stop) c.cancel() override def stop(): Unit = {} override def onEnable() = {} override def onDisable() = {} } sa.stateManager.attach(as) c := Cancelable(() => sa.stateManager.detach(as)) c } } implicit final class AssetManagerExt(private val am: AssetManager) extends AnyVal { def registerLocator( assetPath: os.RelPath, locator: Class[_ <: AssetLocator] ): Unit = am.registerLocator(assetPath.toString(), locator) def loadModel(assetPath: os.RelPath): Spatial = am.loadModel(assetPath.toString()) } implicit final class BulletAppStateExt(private val bas: BulletAppState) extends AnyVal { def physicsSpace = bas.getPhysicsSpace() def speed = bas.getSpeed() } implicit final class BetterCharacterControlExt( private val bcc: BetterCharacterControl ) { def withJumpForce(force: ImVector3f) = { bcc.setJumpForce(force.mutable) bcc } } implicit final class SpatialExt[T <: Spatial](private val spat: T) extends AnyVal { // def asRef = Ref[Task].of(spat) } implicit final class NodeExt[T <: Node](private val n: T) extends AnyVal { /** * Attaches the given child * * @param s * @return */ def withChild(s: Spatial): Node = { n.attachChild(s) n } /** * @return Gets the list of children as a monix observable */ def observableChildren = Observable.fromIterable(n.getChildren().asScala) /** * @return A copy of the list of children of this node as a lazy list */ def children = LazyList.from(n.getChildren().asScala) /** * Attach given children * * @param lst */ def withChildren(lst: Spatial*): Node = { for (c <- lst) n.attachChild(c) n } def +=(spatial: Spatial) = n.attachChild(spatial) def :+(spatial: Spatial) = { n += spatial n } def :+(light: Light) = { n.addLight(light) n } def -=(spatial: Spatial) = n.detachChild(spatial) def :-(spatial: Spatial) = { n -= spatial n } def :-(light: Light) = { n.removeLight(light) n } def depthFirst(cb: Spatial => Unit) = n.depthFirstTraversal(new SceneGraphVisitor() { override def visit(s: Spatial) = cb(s) }) def observableDepthFirst(): Observable[Spatial] = { def loop( subscriber: Subscriber[Spatial], spatials: LazyList[Spatial] ): Task[Unit] = spatials match { // spatial can be either a node or a geometry, but it's not a sealed trait case head #:: tail => head match { case g: Geometry => Task.deferFuture(subscriber.onNext(g)).flatMap { case Continue => loop(subscriber, tail) case Stop => Task(subscriber.onComplete()) } case node: Node => val children = node.children Task.deferFuture(subscriber.onNext(node)).flatMap { case Continue => loop(subscriber, children #::: tail) case Stop => Task(subscriber.onComplete()) } // case _ => loop(subscriber, tail) } case LazyList() => Task(subscriber.onComplete()) } Observable.create(OverflowStrategy.Unbounded) { sub => implicit val sched = sub.scheduler loop(sub, LazyList(n)).runToFuture } } def breadthFirst(cb: Spatial => Unit) = n.breadthFirstTraversal(new SceneGraphVisitor() { override def visit(s: Spatial) = cb(s) }) def observableBreadthFirst(): Observable[Spatial] = { def loop( subscriber: Subscriber[Spatial], spatials: LazyList[Spatial] ): Task[Unit] = spatials match { // spatial can be either a node or a geometry, but it's not a sealed trait case head #:: tail => head match { case g: Geometry => Task.deferFuture(subscriber.onNext(g)).flatMap { case Continue => loop(subscriber, tail) case Stop => Task(subscriber.onComplete()) } case node: Node => val children = node.children Task.deferFuture(subscriber.onNext(node)).flatMap { case Continue => loop(subscriber, tail #::: children) case Stop => Task(subscriber.onComplete()) } case unknown => Task.deferFuture(subscriber.onNext(unknown)).flatMap { case Continue => loop(subscriber, tail) case Stop => Task(subscriber.onComplete()) } } case LazyList() => Task(subscriber.onComplete()) } Observable.create(OverflowStrategy.Unbounded) { sub => implicit val sched = sub.scheduler loop(sub, LazyList(n)).runToFuture } } def withControl[C <: Control](ctrl: C) = { n.addControl(ctrl) n } def withLocalTranslation(dir: ImVector3f) = { n.setLocalTranslation(dir.mutable) n } def withRotate(xAngle: Float, yAngle: Float, zAngle: Float) = { n.rotate(xAngle, yAngle, zAngle) n } def localRotation = n.getLocalRotation() def localTranslation = n.getLocalTranslation() } implicit final class CameraNodeExt(private val cn: CameraNode) { def withControlDir(controlDir: ControlDirection) = { cn.setControlDir(controlDir) cn } def withLookAt(position: ImVector3f, upVector: ImVector3f) = { cn.lookAt(position.mutable, upVector.mutable) cn } } implicit final class GeometryExt(private val geom: Geometry) { def withMaterial(mat: Material) = { geom.setMaterial(mat) geom } } implicit final class EntityDataExt(private val ed: EntityData) extends AnyVal { def query = new EntityQuery(ed) // def entities[T <: EntityComponent](entities: Seq[T]) } implicit final class EntityExt(private val e: EntityId) extends AnyVal { def withComponents(classes: EntityComponent*)(implicit ed: EntityData ): EntityId = { ed.setComponents(e, classes: _*) e } } implicit final class ActorRefExt[Req](private val a: ActorRef[Req]) extends AnyVal { import akka.actor.typed.scaladsl.AskPattern._ /** * Same as [[ask]] but returns a [[Task]] */ def askL[Res]( replyTo: ActorRef[Res] => Req )(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] = Task.deferFuture(a.ask(replyTo)(timeout, scheduler)) def ??[Res]( replyTo: ActorRef[Res] => Req )(implicit timeout: Timeout, scheduler: Scheduler): Task[Res] = askL(replyTo) /** * Same as [[tell]], but wrapped in a Task * * @param msg * @return */ def tellL(msg: Req) = UIO(a.tell(msg)) /** * Same as [[tell]], but wrapped in a Task * * @param msg * @return */ def !!(msg: Req) = tellL(msg) } implicit final class InputManagerExt(private val inputManager: InputManager) extends AnyVal { /** * Create a new mapping to the given triggers. * *

* The given mapping will be assigned to the given triggers, when * any of the triggers given raise an event, the listeners * registered to the mappings will receive appropriate events. * * @param mappingName The mapping name to assign. * @param triggers The triggers to which the mapping is to be registered. */ def withMapping(mapping: String, triggers: Trigger*): InputManager = { inputManager.addMapping(mapping, triggers: _*) inputManager } def withMapping[T <: EnumEntry]( mapping: T, triggers: Trigger* ): InputManager = { inputManager.addMapping(mapping.entryName, triggers: _*) inputManager } def withListener(listener: InputListener, mappings: String*) = { inputManager.addListener(listener, mappings: _*) inputManager } /** * Creates new mappings from the values of the given Enum * *

* The given mapping will be assigned to the given triggers, when * any of the triggers given raise an event, the listeners * registered to the mappings will receive appropriate events. * * @param mappingName The mapping name to assign. * @param mappingFn Function from enum values to the sequence of trigers. * * @example * * {{{ * * sealed trait PlayerAnalogMovementInput extends EnumEntry with UpperSnakecase * object PlayerAnalogMovementInput extends Enum[PlayerAnalogMovementInput] { * val values = findValues * case object TurnRight extends PlayerAnalogMovementInput * case object TurnLeft extends PlayerAnalogMovementInput * } * * { * inputManager.withEnumMappings(PlayerAnalogMovementInput) { * case PlayerAnalogMovementInput.TurnRight => * Seq(new KeyTrigger(KeyInput.KEY_RIGHT)) * case PlayerAnalogMovementInput.TurnLeft => * Seq(new KeyTrigger(KeyInput.KEY_LEFT)) * } * } * }}} */ def withEnumMappings[T <: EnumEntry]( mappingEnum: Enum[T] )(mappingFn: T => Seq[Trigger]): InputManager = { for (entry <- mappingEnum.values) { val mappings = mappingFn(entry) inputManager.addMapping(entry.entryName, mappings: _*) } inputManager } /** * Create an observable which emits the given mappings as elements of an observable * * @param mappingNames * @return Observable of action events * * @see [[ActionEvent]] * @see [[enumObservableAction]] */ def observableAction(mappingNames: String*): Observable[ActionEvent] = Observable.create(OverflowStrategy.DropOld(10)) { sub => val c = SingleAssignCancelable() val al = new ActionListener { override def onAction( binding: String, value: Boolean, tpf: Float ): Unit = if ( sub.onNext(ActionEvent(Action(binding), value, tpf)) == Ack.Stop ) { sub.onComplete() c.cancel() } } inputManager.addListener(al, mappingNames: _*) c := Cancelable(() => inputManager.removeListener(al)) c } /** *

* Create an observable which emits the values of the given * enum as elements of an observable * * @param mappingNames * @return Observable of enum values * * @example {{{ * inputManager * .enumObservableAction(PlayerMovementInput) * .doOnNext { action => * action.binding match { * case PlayerMovementInput.WalkLeft => Task {/* your actions */} * } * } * }}} * * @see [[EnumActionEvent]] * @see [[enumAnalogObservable]] */ def enumObservableAction[T <: EnumEntry]( mappingEnum: Enum[T] ): Observable[EnumActionEvent[T]] = Observable.create(OverflowStrategy.DropOld(10)) { sub => val c = SingleAssignCancelable() val entryNames = mappingEnum.values.map(_.entryName) val al = new ActionListener { override def onAction( binding: String, value: Boolean, tpf: Float ): Unit = mappingEnum.withNameOption(binding).foreach { b => if (sub.onNext(EnumActionEvent(b, value, tpf)) == Ack.Stop) { sub.onComplete() c.cancel() } } } inputManager.addListener(al, entryNames: _*) c := Cancelable(() => inputManager.removeListener(al)) c } def enumEntryObservableAction[T <: EnumEntry]( mappingEnumEntry: T ): Observable[EnumActionEvent[T]] = Observable.create(OverflowStrategy.DropOld(10)) { sub => val c = SingleAssignCancelable() val al = new ActionListener { override def onAction( binding: String, value: Boolean, tpf: Float ): Unit = if ( sub.onNext( EnumActionEvent(mappingEnumEntry, value, tpf) ) == Ack.Stop ) { sub.onComplete() c.cancel() } } inputManager.addListener(al, mappingEnumEntry.entryName) c := Cancelable(() => inputManager.removeListener(al)) c } def analogObservable(mappingNames: String*): Observable[AnalogEvent] = Observable.create(OverflowStrategy.DropOld(50)) { sub => val c = SingleAssignCancelable() val al = new AnalogListener { override def onAnalog( binding: String, value: Float, tpf: Float ): Unit = if ( sub.onNext(AnalogEvent(Action(binding), value, tpf)) == Ack.Stop ) { sub.onComplete() c.cancel() } } inputManager.addListener(al, mappingNames: _*) c := Cancelable(() => inputManager.removeListener(al)) c } def enumAnalogObservable[T <: EnumEntry]( mappingEnum: Enum[T] ): Observable[EnumAnalogEvent[T]] = Observable.create(OverflowStrategy.DropOld(50)) { sub => val c = SingleAssignCancelable() val entryNames = mappingEnum.values.map(_.entryName) val al = new AnalogListener { override def onAnalog( binding: String, value: Float, tpf: Float ): Unit = mappingEnum.withNameOption(binding).foreach { b => if (sub.onNext(EnumAnalogEvent(b, value, tpf)) == Ack.Stop) { sub.onComplete() c.cancel() } } } inputManager.addListener(al, entryNames: _*) c := Cancelable(() => inputManager.removeListener(al)) c } } implicit final class PhysicsSpaceExt(private val space: jmeb.PhysicsSpace) extends AnyVal { def collisionObservable(): Observable[CollisionEvent] = Observable.create(OverflowStrategy.DropOld(50)) { sub => val c = SingleAssignCancelable() val cl = new PhysicsCollisionListener { override def collision(event: jmePhysicsCollisionEvent): Unit = if ( sub.onNext( CollisionEvent( Option(event.getNodeA), Option(event.getNodeB), event.getObjectA, event.getObjectB, event.getAppliedImpulse _ ) ) == Ack.Stop ) c.cancel() } space.addCollisionListener(cl) c := Cancelable { () => pprint.log("stopped") space.removeCollisionListener(cl) } c } def prePhysicsTickObservable(): Observable[PrePhysicsTickEvent] = Observable.create(OverflowStrategy.DropOld(50)) { sub => val c = SingleAssignCancelable() val cl = new PhysicsTickListener { override def prePhysicsTick( space: jmeb.PhysicsSpace, tpf: Float ): Unit = { val event = new PrePhysicsTickEvent(new PhysicsSpace(space)) if (sub.onNext(event) == Ack.Stop) { sub.onComplete() c.cancel() } } override def physicsTick( space: jmeb.PhysicsSpace, tpf: Float ): Unit = {} } space.addTickListener(cl) c := Cancelable(() => space.removeTickListener(cl)) c } def physicsTickObservable(): Observable[PhysicsTickEvent] = Observable.create(OverflowStrategy.DropOld(50)) { sub => val c = SingleAssignCancelable() val cl = new PhysicsTickListener { override def prePhysicsTick( space: jmeb.PhysicsSpace, tpf: Float ): Unit = {} override def physicsTick( space: jmeb.PhysicsSpace, tpf: Float ): Unit = { val event = new PhysicsTickEvent(new PhysicsSpace(space)) if (sub.onNext(event) == Ack.Stop) { sub.onComplete() c.cancel() } } } space.addTickListener(cl) c := Cancelable(() => space.removeTickListener(cl)) c } //TODO Consider creating a typeclass for this def +=(anyObject: Any) = space.add(anyObject) def :+(anyObject: Any) = { space.add(anyObject) space } def -(anyObject: Any) = { space.remove(anyObject) space } def +=(spatial: Spatial) = space.addAll(spatial) def :+(spatial: Spatial) = { space.addAll(spatial) space } def -(spatial: Spatial) = { space.removeAll(spatial) space } // def asRef = Ref[Task].of(space) } implicit final class Vector3fOps(private val v: Vector3f) extends AnyVal { //TODO add more operations def +=(that: Vector3f) = v.addLocal(that) def +=(f: Float) = v.addLocal(f, f, f) def +=(that: ImVector3f) = v.addLocal(that.x, that.y, that.z) def +=:(that: ImVector3f) = v += that def *=(that: Vector3f) = v.multLocal(that) def *=(that: ImVector3f) = v.multLocal(that.x, that.y, that.z) def *=(f: Float) = v.multLocal(f, f, f) def *=:(that: ImVector3f) = v *= that def -=(that: Vector3f) = v.subtractLocal(that) def -=(that: ImVector3f) = v.subtractLocal(that.x, that.y, that.z) def -=:(that: ImVector3f) = v *= that def /=(that: Vector3f) = v.divideLocal(that) def /=(that: ImVector3f) = v.divideLocal(that.mutable) def /=:(that: ImVector3f) = v *= that def unary_- = v.negateLocal() def immutable = ImVector3f(v.x, v.y, v.z) // def transformImmutable(f: Vector3f => Vector3f) = f(v).immutable } implicit final class ImVector3fOps(private val v: ImVector3f) extends AnyVal { def +(that: ImVector3f) = v.copy(v.x + that.x, v.y + that.y, v.z + that.z) def +(f: Float): ImVector3f = v.copy(v.x + f, v.y + f, v.z + f) def *(that: ImVector3f) = v.copy(v.x * that.x, v.y * that.y, v.z * that.z) def *(f: Float): ImVector3f = v.copy(v.x * f, v.y * f, v.z * f) // v * ImVector3f(f, f, f) def -(that: ImVector3f) = v.copy(v.x - that.x, v.y - that.y, v.z - that.z) def -(f: Float): ImVector3f = v.copy(v.x - f, v.y - f, v.z - f) def /(that: ImVector3f) = v.copy(v.x / that.x, v.y / that.y, v.z / that.z) def /(f: Float): ImVector3f = v.copy(v.x / f, v.y / f, v.z / f) def unary_- = v.copy(-v.x, -v.y, -v.z) def mutable = new Vector3f(v.x, v.y, v.z) // /** // * alias for [[cross]] product // */ // def |*|() = () } // val TasktoUIO = new FunctionK[Task, UIO] { // def apply[T](f: Task[T]): UIO[T] = // f.hideErrors // } implicit final class JavaFxUIExt(private val jfxui: JavaFxUI) extends AnyVal { def +=(node: scalafx.scene.Node) = jfxui.attachChild(node) def -=(node: scalafx.scene.Node) = jfxui.detachChild(node) } implicit class AkkaLoggerExt(private val logger: Logger) extends AnyVal { private def logP[T]( x: sourcecode.Text[T], tag: String = "", width: Int = 100, height: Int = 500, indent: Int = 2, initialOffset: Int = 0 )(implicit line: sourcecode.Line, fileName: sourcecode.FileName) = { val tagStrs = if (tag.isEmpty) Seq.empty else Seq(fansi.Color.Cyan(tag), fansi.Str(" ")) // "".slice(1, -1) val prefix = Seq( fansi.Color.Magenta(fileName.value), fansi.Str(":"), fansi.Color.Green(line.value.toString), fansi.Str(" "), fansi.Color.Cyan(x.source), fansi.Str(": ") ) ++ tagStrs fansi.Str.join( prefix ++ pprint.tokenize(x.value, width, height, indent).toSeq: _* ) } def warnP[T]( s: sourcecode.Text[T] )(implicit line: sourcecode.Line, fileName: sourcecode.FileName) = { logger.warn(logP(s).render) s } def errorP[T]( s: sourcecode.Text[T] )(implicit line: sourcecode.Line, fileName: sourcecode.FileName) = { logger.error(logP(s).render) s } def infoP[T]( s: sourcecode.Text[T] )(implicit line: sourcecode.Line, fileName: sourcecode.FileName) = { logger.info(logP(s).render) s } def debugP[T]( s: sourcecode.Text[T] )(implicit line: sourcecode.Line, fileName: sourcecode.FileName) = { logger.debug(logP(s).render) s } def traceP[T]( s: sourcecode.Text[T] )(implicit line: sourcecode.Line, fileName: sourcecode.FileName) = { logger.trace(logP(s).render) s } } implicit final class OdinLoggerExt(private val logger: io.odin.Logger[Task]) extends AnyVal { def debugU[M](msg: => M)(implicit render: Render[M], position: Position) = logger.debug(msg).hideErrors def infoU[M](msg: => M)(implicit render: Render[M], position: Position) = logger.info(msg).hideErrors def traceU[M](msg: => M)(implicit render: Render[M], position: Position) = logger.trace(msg).hideErrors def warnU[M](msg: => M)(implicit render: Render[M], position: Position) = logger.warn(msg).hideErrors def errorU[M](msg: => M)(implicit render: Render[M], position: Position) = logger.error(msg).hideErrors } implicit final class TypedActorContextExt[T](private val ctx: ActorContext[T]) extends AnyVal { def spawnN[U](behavior: Behavior[U], props: Props = Props.empty)(implicit name: sourcecode.Name ): ActorRef[U] = ctx.spawn(behavior, name.value, props) } implicit final class MonixEvalTaskExt[T](private val task: monix.eval.Task[T]) extends AnyVal { def toIO = IO.deferAction(implicit s => IO.from(task)) } implicit final class MonixBioTaskExt[T](private val task: monix.bio.Task[T]) extends AnyVal { def toTask = monix.eval.Task.deferAction(implicit s => monix.eval.Task.from(task)) } implicit final class CoevalEitherExt[L, R]( private val coeval: Coeval[Either[L, R]] ) extends AnyVal { def toIO = coeval.to[Task].hideErrors.rethrow } }