From fa633aa79d6507222a1254b7ef4554f9639ba727 Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 25 Sep 2017 21:54:59 -0400 Subject: [PATCH] started doors --- .../net/psforever/objects/doors/Door.scala | 77 +++++++++++++++++++ .../objects/doors/DoorCloseControl.scala | 40 ++++++++++ .../psforever/objects/doors/DoorControl.scala | 27 +++++++ .../objects/doors/DoorDefinition.scala | 13 ++++ .../objects/zones/DoorObjectBuilder.scala | 33 ++++++++ .../objects/zones/ServerObjectBuilder.scala | 4 +- .../objects/zones/TerminalObjectBuilder.scala | 2 +- .../net/psforever/objects/zones/ZoneMap.scala | 6 +- pslogin/src/main/scala/PsLogin.scala | 8 +- .../src/main/scala/WorldSessionActor.scala | 39 +++++++--- 10 files changed, 233 insertions(+), 16 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/doors/Door.scala create mode 100644 common/src/main/scala/net/psforever/objects/doors/DoorCloseControl.scala create mode 100644 common/src/main/scala/net/psforever/objects/doors/DoorControl.scala create mode 100644 common/src/main/scala/net/psforever/objects/doors/DoorDefinition.scala create mode 100644 common/src/main/scala/net/psforever/objects/zones/DoorObjectBuilder.scala diff --git a/common/src/main/scala/net/psforever/objects/doors/Door.scala b/common/src/main/scala/net/psforever/objects/doors/Door.scala new file mode 100644 index 000000000..623bcf428 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/doors/Door.scala @@ -0,0 +1,77 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.doors + +import akka.actor.{ActorContext, ActorRef, Props} +import net.psforever.objects.{PlanetSideGameObject, Player} +import net.psforever.packet.game.UseItemMessage +import net.psforever.types.PlanetSideEmpire + +/** + * na + * @param ddef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields + */ +class Door(ddef : DoorDefinition) extends PlanetSideGameObject { + /** Internal reference to the `Actor` for this `Door`, sets up by this `Door`. */ + private var actor = ActorRef.noSender + + def Actor(implicit context : ActorContext) : ActorRef = { + if(actor == ActorRef.noSender) { + actor = context.actorOf(Props(classOf[DoorControl], this), s"${ddef.Name}_${GUID.guid}") + } + actor + } + + private var openState : Boolean = false + private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL + private var hackedBy : Option[PlanetSideEmpire.Value] = None + + def Open : Boolean = openState + + def Faction : PlanetSideEmpire.Value = faction + + def Convert(toFaction : PlanetSideEmpire.Value) : Unit = { + hackedBy = None + faction = toFaction + } + + def Request(player : Player, msg : UseItemMessage) : Door.Exchange = { + if(!openState) { + if(faction == PlanetSideEmpire.NEUTRAL || player.Faction == faction) { + Door.OpenEvent() + } + else { + Door.NoEvent() + } + } + else { + Door.NoEvent() + } + } + + def Definition : DoorDefinition = ddef +} + +object Door { + final case class Request(player : Player, msg : UseItemMessage) + + sealed trait Exchange + + final case class DoorMessage(player : Player, msg : UseItemMessage, response : Exchange) + + final case class OpenEvent() extends Exchange + + final case class CloseEvent() extends Exchange + + final case class NoEvent() extends Exchange + + def apply(tdef : DoorDefinition) : Door = { + new Door(tdef) + } + + import net.psforever.packet.game.PlanetSideGUID + def apply(guid : PlanetSideGUID, ddef : DoorDefinition) : Door = { + val obj = new Door(ddef) + obj.GUID = guid + obj + } +} diff --git a/common/src/main/scala/net/psforever/objects/doors/DoorCloseControl.scala b/common/src/main/scala/net/psforever/objects/doors/DoorCloseControl.scala new file mode 100644 index 000000000..ae20b6e76 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/doors/DoorCloseControl.scala @@ -0,0 +1,40 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.doors + +import akka.actor.{Actor, ActorRef, Cancellable} +import net.psforever.packet.game.PlanetSideGUID + +import scala.collection.mutable.ListBuffer + +class DoorCloseControl(implicit val environment : ActorRef) extends Actor { + import DoorCloseControl._ + private var doorCloser : Cancellable = DefaultCloser + private var openDoors : List[DoorEntry] = Nil + + def receive : Receive = { + case DoorIsOpen(guid, time) => + if(openDoors.isEmpty) { + //doorCloser = context.system.scheduler.scheduleOnce(timeout, environment, Door.DoorMessage()) + } + else { + openDoors = openDoors :+ DoorEntry(guid, time) + } + + case _ => ; + } +} + +object DoorCloseControl { + private final val timeout : Long = 5000L + + private final val DefaultCloser : Cancellable = new Cancellable() { + override def cancel : Boolean = true + override def isCancelled : Boolean = true + } + + private final case class DoorEntry(door_guid : PlanetSideGUID, opened_at_time : Long) + + final case class DoorIsOpen(door_guid : PlanetSideGUID, opened_at_time : Long = System.nanoTime()) + + final case class CloseTheDoor(door_guid : PlanetSideGUID) +} diff --git a/common/src/main/scala/net/psforever/objects/doors/DoorControl.scala b/common/src/main/scala/net/psforever/objects/doors/DoorControl.scala new file mode 100644 index 000000000..0491a59ac --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/doors/DoorControl.scala @@ -0,0 +1,27 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.doors + +import akka.actor.{Actor, Cancellable} + +/** + * An `Actor` that handles messages being dispatched to a specific `Door`. + * @param door the `Door` object being governed + */ +class DoorControl(door : Door) extends Actor { + private var doorCloser : Cancellable = DoorControl.DefaultCloser + + def receive : Receive = { + case Door.Request(player, msg) => + sender ! Door.DoorMessage(player, msg, door.Request(player, msg)) + //doorCloser = context.system.scheduler.scheduleOnce(5000L, sender, Door.DoorMessage()) + case _ => + sender ! Door.NoEvent() + } +} + +object DoorControl { + final val DefaultCloser : Cancellable = new Cancellable() { + override def cancel : Boolean = true + override def isCancelled : Boolean = true + } +} diff --git a/common/src/main/scala/net/psforever/objects/doors/DoorDefinition.scala b/common/src/main/scala/net/psforever/objects/doors/DoorDefinition.scala new file mode 100644 index 000000000..777cccf5b --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/doors/DoorDefinition.scala @@ -0,0 +1,13 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.doors + +import net.psforever.objects.definition.ObjectDefinition + +/** + * The definition for any `door`. + * @param objectId the object's identifier number + */ +abstract class DoorDefinition(objectId : Int) extends ObjectDefinition(objectId) { + Name = "door" +} + diff --git a/common/src/main/scala/net/psforever/objects/zones/DoorObjectBuilder.scala b/common/src/main/scala/net/psforever/objects/zones/DoorObjectBuilder.scala new file mode 100644 index 000000000..8f4ead88c --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/zones/DoorObjectBuilder.scala @@ -0,0 +1,33 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.zones + +import net.psforever.objects.doors.{Door, DoorDefinition} + +/** + * Wrapper `Class` designed to instantiate a `Door` server object. + * @param ddef a `DoorDefinition` object, indicating the specific functionality of the resulting `Door` + * @param id the globally unique identifier to which this `Door` will be registered + */ +class DoorObjectBuilder(private val ddef : DoorDefinition, private val id : Int) extends ServerObjectBuilder[Door] { + import akka.actor.ActorContext + import net.psforever.objects.guid.NumberPoolHub + + def Build(implicit context : ActorContext, guid : NumberPoolHub) : Door = { + val obj = Door(ddef) + guid.register(obj, id) //non-Actor GUID registration + obj.Actor //it's necessary to register beforehand because the Actor name utilizes the GUID + obj + } +} + +object DoorObjectBuilder { + /** + * Overloaded constructor for a `DoorObjectBuilder`. + * @param ddef a `DoorDefinition` object + * @param id a globally unique identifier + * @return a `DoorObjectBuilder` object + */ + def apply(ddef : DoorDefinition, id : Int) : DoorObjectBuilder = { + new DoorObjectBuilder(ddef, id) + } +} diff --git a/common/src/main/scala/net/psforever/objects/zones/ServerObjectBuilder.scala b/common/src/main/scala/net/psforever/objects/zones/ServerObjectBuilder.scala index 2635df096..391ec7751 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ServerObjectBuilder.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ServerObjectBuilder.scala @@ -9,7 +9,7 @@ import net.psforever.objects.guid.NumberPoolHub * Wrapper `Trait` designed to be extended to implement custom object instantiation logic at the `ZoneMap` level. * @see `Zone.Init` */ -trait ServerObjectBuilder { +trait ServerObjectBuilder[A <: PlanetSideGameObject] { /** * Instantiate and configure the given server object * (at a later time compared to the construction of the builder class).
@@ -23,5 +23,5 @@ trait ServerObjectBuilder { * defaults to `null` * @return the object that was created and integrated into the `Zone` */ - def Build(implicit context : ActorContext = null, guid : NumberPoolHub = null) : PlanetSideGameObject + def Build(implicit context : ActorContext = null, guid : NumberPoolHub = null) : A } diff --git a/common/src/main/scala/net/psforever/objects/zones/TerminalObjectBuilder.scala b/common/src/main/scala/net/psforever/objects/zones/TerminalObjectBuilder.scala index 2ced4d84d..b78921e18 100644 --- a/common/src/main/scala/net/psforever/objects/zones/TerminalObjectBuilder.scala +++ b/common/src/main/scala/net/psforever/objects/zones/TerminalObjectBuilder.scala @@ -8,7 +8,7 @@ import net.psforever.objects.terminals.{Terminal, TerminalDefinition} * @param tdef a `TerminalDefinition` object, indicating the specific functionality of the resulting `Terminal` * @param id the globally unique identifier to which this `Terminal` will be registered */ -class TerminalObjectBuilder(private val tdef : TerminalDefinition, private val id : Int) extends ServerObjectBuilder { +class TerminalObjectBuilder(private val tdef : TerminalDefinition, private val id : Int) extends ServerObjectBuilder[Terminal] { import akka.actor.ActorContext import net.psforever.objects.guid.NumberPoolHub diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala index b1cf2e9a3..05ae29e0f 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala @@ -21,7 +21,7 @@ package net.psforever.objects.zones * `LoadMapMessage` */ class ZoneMap(private val name : String) { - private var localObjects : List[ServerObjectBuilder] = List() + private var localObjects : List[ServerObjectBuilder[_]] = List() def Name : String = name @@ -29,7 +29,7 @@ class ZoneMap(private val name : String) { * Append the builder for a server object to the list of builders known to this `ZoneMap`. * @param obj the builder for a server object */ - def LocalObject(obj : ServerObjectBuilder) : Unit = { + def LocalObject(obj : ServerObjectBuilder[_]) : Unit = { localObjects = localObjects :+ obj } @@ -37,7 +37,7 @@ class ZoneMap(private val name : String) { * The list of all server object builder wrappers that have been assigned to this `ZoneMap`. * @return the `List` of all `ServerObjectBuilders` known to this `ZoneMap` */ - def LocalObjects : List[ServerObjectBuilder] = { + def LocalObjects : List[ServerObjectBuilder[_]] = { localObjects } } diff --git a/pslogin/src/main/scala/PsLogin.scala b/pslogin/src/main/scala/PsLogin.scala index f71cce111..d0c22dc4e 100644 --- a/pslogin/src/main/scala/PsLogin.scala +++ b/pslogin/src/main/scala/PsLogin.scala @@ -12,7 +12,7 @@ import ch.qos.logback.core.status._ import ch.qos.logback.core.util.StatusPrinter import com.typesafe.config.ConfigFactory import net.psforever.crypto.CryptoInterface -import net.psforever.objects.zones.{InterstellarCluster, TerminalObjectBuilder, Zone, ZoneMap} +import net.psforever.objects.zones._ import net.psforever.objects.guid.TaskResolver import org.slf4j import org.fusesource.jansi.Ansi._ @@ -222,6 +222,12 @@ object PsLogin { def createContinents() : List[Zone] = { val map13 = new ZoneMap("map13") { import net.psforever.objects.GlobalDefinitions._ + val ddef = new net.psforever.objects.doors.DoorDefinition(242) {} //generic door + + LocalObject(DoorObjectBuilder(ddef, 330)) + LocalObject(DoorObjectBuilder(ddef, 332)) + LocalObject(DoorObjectBuilder(ddef, 372)) + LocalObject(DoorObjectBuilder(ddef, 373)) LocalObject(TerminalObjectBuilder(cert_terminal, 186)) LocalObject(TerminalObjectBuilder(cert_terminal, 187)) LocalObject(TerminalObjectBuilder(cert_terminal, 188)) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index d759a8f38..719c1cd1a 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -11,6 +11,7 @@ import org.log4s.MDC import MDCContextAware.Implicits._ import ServiceManager.Lookup import net.psforever.objects._ +import net.psforever.objects.doors.Door import net.psforever.objects.zones.{InterstellarCluster, Zone} import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.equipment._ @@ -206,6 +207,17 @@ class WorldSessionActor extends Actor with MDCContextAware { case _ => ; } + case Door.DoorMessage(_, msg, order) => + order match { + case Door.OpenEvent() => + sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(msg.object_guid, 16))) + + case Door.CloseEvent() => + sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(msg.object_guid, 17))) + + case Door.NoEvent() => ; + } + case Terminal.TerminalMessage(tplayer, msg, order) => order match { case Terminal.BuyExosuit(exosuit, subtype) => @@ -943,15 +955,24 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info("UseItem: " + msg) // TODO: Not all fields in the response are identical to source in real packet logs (but seems to be ok) // TODO: Not all incoming UseItemMessage's respond with another UseItemMessage (i.e. doors only send out GenericObjectStateMsg) - if (itemType != 121) sendResponse(PacketCoding.CreateGamePacket(0, UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType))) - if (itemType == 121 && !unk3){ // TODO : medkit use ?! - sendResponse(PacketCoding.CreateGamePacket(0, UseItemMessage(avatar_guid, unk1, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType))) - sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(avatar_guid, 0, 100))) // avatar with 100 hp - sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(PlanetSideGUID(unk1), 2))) - } - if (unk1 == 0 && !unk3 && unk7 == 25) { - // TODO: This should only actually be sent to doors upon opening; may break non-door items upon use - sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(object_guid, 16))) + continent.GUID(object_guid) match { + case Some(door : Door) => + log.info("Door action!") + door.Actor ! Door.Request(player, msg) + case Some(obj : PlanetSideGameObject) => + if(itemType != 121) { + sendResponse(PacketCoding.CreateGamePacket(0, UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType))) + } + else if(itemType == 121 && !unk3) { // TODO : medkit use ?! + sendResponse(PacketCoding.CreateGamePacket(0, UseItemMessage(avatar_guid, unk1, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType))) + sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(avatar_guid, 0, 100))) // avatar with 100 hp + sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(PlanetSideGUID(unk1), 2))) + } +// if(unk1 == 0 && !unk3 && unk7 == 25) { +// // TODO: This should only actually be sent to doors upon opening; may break non-door items upon use +// sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(object_guid, 16))) +// } + case None => ; } case msg @ UnuseItemMessage(player_guid, item) =>