From d5f40a3d5f7909bf7d21d726ce514cc6e68f0acb Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 13 Oct 2017 14:57:28 -0400 Subject: [PATCH] comments and documentation, mainly; adjusted the list splitting functionality in the LocalService support Actors --- .../psforever/objects/GlobalDefinitions.scala | 2 +- .../scala/net/psforever/objects/Player.scala | 2 - .../objects/serverobject/CommonMessages.scala | 3 +- .../serverobject/PlanetSideServerObject.scala | 14 +++ .../builders/IFFLockObjectBuilder.scala | 6 +- .../builders/ServerObjectBuilder.scala | 2 + .../objects/serverobject/doors/Base.scala | 4 + .../objects/serverobject/doors/Door.scala | 38 ++++-- .../serverobject/doors/DoorControl.scala | 9 +- .../serverobject/doors/DoorDefinition.scala | 2 +- .../objects/serverobject/locks/IFFLock.scala | 51 ++++++-- .../serverobject/locks/IFFLockControl.scala | 18 +-- .../locks/IFFLockDefinition.scala | 6 +- .../terminals/TemporaryTerminalMessages.scala | 16 --- .../serverobject/terminals/Terminal.scala | 55 ++++----- .../terminals/TerminalControl.scala | 10 +- .../objects/zones/DoorCloseActor.scala | 109 +++++++++++++----- .../objects/zones/HackClearActor.scala | 106 ++++++++++++----- .../psforever/packet/game/HackMessage.scala | 86 ++++++++++---- .../src/test/scala/game/HackMessageTest.scala | 12 +- pslogin/src/main/scala/PsLogin.scala | 4 +- .../src/main/scala/WorldSessionActor.scala | 64 ++++++---- 22 files changed, 408 insertions(+), 211 deletions(-) delete mode 100644 common/src/main/scala/net/psforever/objects/serverobject/terminals/TemporaryTerminalMessages.scala diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 13073dade..6d961e8fc 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -1245,7 +1245,7 @@ object GlobalDefinitions { cert_terminal = new CertTerminalDefinition val - external_lock = new IFFLockDefinition + lock_external = new IFFLockDefinition val door = new DoorDefinition } diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index 9d59f733d..a91cc0ff9 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -64,8 +64,6 @@ class Player(private val name : String, /** Last medkituse. */ var lastMedkit : Long = 0 var death_by : Int = 0 - var doors : Array[Int] = Array.ofDim(120) - var doorsTime : Array[Long] = Array.ofDim(120) var lastSeenStreamMessage : Array[Long] = Array.fill[Long](65535)(0L) var lastShotSeq_time : Int = -1 /** The player is shooting. */ diff --git a/common/src/main/scala/net/psforever/objects/serverobject/CommonMessages.scala b/common/src/main/scala/net/psforever/objects/serverobject/CommonMessages.scala index 8f4de61c5..c73b7a235 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/CommonMessages.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/CommonMessages.scala @@ -2,9 +2,8 @@ package net.psforever.objects.serverobject import net.psforever.objects.Player -import net.psforever.types.PlanetSideEmpire -//temporary location for these temporary messages +//temporary location for these messages object CommonMessages { final case class Hack(player : Player) final case class ClearHack() diff --git a/common/src/main/scala/net/psforever/objects/serverobject/PlanetSideServerObject.scala b/common/src/main/scala/net/psforever/objects/serverobject/PlanetSideServerObject.scala index 3a0e71ec7..f44975444 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/PlanetSideServerObject.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/PlanetSideServerObject.scala @@ -4,11 +4,25 @@ package net.psforever.objects.serverobject import akka.actor.ActorRef import net.psforever.objects.PlanetSideGameObject +/** + * An object layered on top of the standard game object class that maintains an internal `ActorRef`. + * A measure of synchronization can be managed using this `Actor`. + */ abstract class PlanetSideServerObject extends PlanetSideGameObject { private var actor = ActorRef.noSender + /** + * Retrieve a reference to the internal `Actor`. + * @return the internal `ActorRef` + */ def Actor : ActorRef = actor + /** + * Assign an `Actor` to act for this server object. + * This reference is only set once, that is, as long as the internal `ActorRef` directs to `Actor.noSender` (`null`). + * @param control the `Actor` whose functionality will govern this server object + * @return the current internal `ActorRef` + */ def Actor_=(control : ActorRef) : ActorRef = { if(actor == ActorRef.noSender) { actor = control diff --git a/common/src/main/scala/net/psforever/objects/serverobject/builders/IFFLockObjectBuilder.scala b/common/src/main/scala/net/psforever/objects/serverobject/builders/IFFLockObjectBuilder.scala index e954eea4d..26834d933 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/builders/IFFLockObjectBuilder.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/builders/IFFLockObjectBuilder.scala @@ -5,8 +5,8 @@ import akka.actor.Props import net.psforever.objects.serverobject.locks.{IFFLock, IFFLockControl, IFFLockDefinition} /** - * Wrapper `Class` designed to instantiate a `Door` server object. - * @param idef a `IFFLockDefinition` object, indicating the specific functionality of the resulting `Door` + * Wrapper `Class` designed to instantiate a door lock server object that is sensitive to user faction affiliation. + * @param idef a `IFFLockDefinition` object, indicating the specific functionality * @param id the globally unique identifier to which this `IFFLock` will be registered */ class IFFLockObjectBuilder(private val idef : IFFLockDefinition, private val id : Int) extends ServerObjectBuilder[IFFLock] { @@ -14,7 +14,7 @@ class IFFLockObjectBuilder(private val idef : IFFLockDefinition, private val id import net.psforever.objects.guid.NumberPoolHub def Build(implicit context : ActorContext, guid : NumberPoolHub) : IFFLock = { - val obj = IFFLock() + val obj = IFFLock(idef) guid.register(obj, id) //non-Actor GUID registration obj.Actor = context.actorOf(Props(classOf[IFFLockControl], obj), s"${idef.Name}_${obj.GUID.guid}") obj diff --git a/common/src/main/scala/net/psforever/objects/serverobject/builders/ServerObjectBuilder.scala b/common/src/main/scala/net/psforever/objects/serverobject/builders/ServerObjectBuilder.scala index 5b412814c..e1bb0ce7b 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/builders/ServerObjectBuilder.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/builders/ServerObjectBuilder.scala @@ -7,8 +7,10 @@ import net.psforever.objects.guid.NumberPoolHub /** * Wrapper `Trait` designed to be extended to implement custom object instantiation logic at the `ZoneMap` level. + * @tparam A any object that extends from PlanetSideGameObject * @see `Zone.Init` */ +//TODO can we changed PlanetSideGameObject -> PlanetSideServerObject? trait ServerObjectBuilder[A <: PlanetSideGameObject] { /** * Instantiate and configure the given server object diff --git a/common/src/main/scala/net/psforever/objects/serverobject/doors/Base.scala b/common/src/main/scala/net/psforever/objects/serverobject/doors/Base.scala index 17c590a73..90e6c490a 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/doors/Base.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/doors/Base.scala @@ -3,6 +3,10 @@ package net.psforever.objects.serverobject.doors import net.psforever.types.PlanetSideEmpire +/** + * A temporary class to represent "facilities" and "structures." + * @param id the map id of the base + */ class Base(private val id : Int) { private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL diff --git a/common/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala b/common/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala index 5971fbaa2..ae63affdc 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala @@ -6,10 +6,10 @@ import net.psforever.objects.Player import net.psforever.packet.game.UseItemMessage /** - * na + * A structure-owned server object that is a "door" that can open and can close. * @param ddef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields */ -class Door(ddef : DoorDefinition) extends PlanetSideServerObject { +class Door(private val ddef : DoorDefinition) extends PlanetSideServerObject { private var openState : Boolean = false private var lockState : Boolean = false @@ -45,26 +45,46 @@ class Door(ddef : DoorDefinition) extends PlanetSideServerObject { } object Door { + /** + * Entry message into this `Door` that carries the request. + * @param player the player who sent this request message + * @param msg the original packet carrying the request + */ final case class Use(player : Player, msg : UseItemMessage) + /** + * A basic `Trait` connecting all of the actionable `Door` response messages. + */ sealed trait Exchange + /** + * Message that carries the result of the processed request message back to the original user (`player`). + * @param player the player who sent this request message + * @param msg the original packet carrying the request + * @param response the result of the processed request + */ final case class DoorMessage(player : Player, msg : UseItemMessage, response : Exchange) + /** + * This door will open. + */ final case class OpenEvent() extends Exchange + /** + * This door will close. + */ final case class CloseEvent() extends Exchange + /** + * This door will do nothing. + */ final case class NoEvent() extends Exchange + /** + * Overloaded constructor. + * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields + */ 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/serverobject/doors/DoorControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala index ac3832f03..8f1163316 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.doors -import akka.actor.{Actor, Cancellable} +import akka.actor.Actor /** * An `Actor` that handles messages being dispatched to a specific `Door`. @@ -16,10 +16,3 @@ class DoorControl(door : Door) extends Actor { 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/serverobject/doors/DoorDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/doors/DoorDefinition.scala index 959aa6d2f..6a22670cd 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/doors/DoorDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/doors/DoorDefinition.scala @@ -4,7 +4,7 @@ package net.psforever.objects.serverobject.doors import net.psforever.objects.definition.ObjectDefinition /** - * The definition for any `door`. + * The definition for any `Door`. * Object Id 242 is a generic door. */ class DoorDefinition extends ObjectDefinition(242) { diff --git a/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLock.scala b/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLock.scala index e7dad76ca..e08587e85 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLock.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLock.scala @@ -1,39 +1,66 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.locks -import net.psforever.objects.{GlobalDefinitions, Player} +import net.psforever.objects.Player import net.psforever.objects.serverobject.PlanetSideServerObject -import net.psforever.types.{PlanetSideEmpire, Vector3} +import net.psforever.packet.game.PlanetSideGUID +import net.psforever.types.Vector3 -class IFFLock extends PlanetSideServerObject { - private var hackedBy : Option[(Player, Vector3)] = None +/** + * A structure-owned server object that is a "door lock."
+ *
+ * The "door lock" exerts an "identify friend or foe" field that detects the faction affiliation of a target player. + * It also indirectly inherits faction affiliation from the structure to which it is connected + * or it can be "hacked" whereupon the person exploiting it leaves their "faction" as the aforementioned affiliated faction. + * The `IFFLock` is ideally associated with a server map object - a `Door` - to which it acts as a gatekeeper. + * @param idef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields + */ +class IFFLock(private val idef : IFFLockDefinition) extends PlanetSideServerObject { + /** + * An entry that maintains a reference to the `Player`, and the player's GUID and location when the message was received. + */ + private var hackedBy : Option[(Player, PlanetSideGUID, Vector3)] = None - def HackedBy : Option[(Player, Vector3)] = hackedBy + def HackedBy : Option[(Player, PlanetSideGUID, Vector3)] = hackedBy - def HackedBy_=(agent : Player) : Option[(Player, Vector3)] = HackedBy_=(Some(agent)) + def HackedBy_=(agent : Player) : Option[(Player, PlanetSideGUID, Vector3)] = HackedBy_=(Some(agent)) - def HackedBy_=(agent : Option[Player]) : Option[(Player, Vector3)] = { + /** + * Set the hack state of this object by recording important information about the player that caused it. + * Set the hack state if there is no current hack state. + * Override the hack state with a new hack state if the new user has different faction affiliation. + * @param agent a `Player`, or no player + * @return the player hack entry + */ + def HackedBy_=(agent : Option[Player]) : Option[(Player, PlanetSideGUID, Vector3)] = { hackedBy match { case None => + //set the hack state if there is no current hack state if(agent.isDefined) { - hackedBy = Some(agent.get, agent.get.Position) + hackedBy = Some(agent.get, agent.get.GUID, agent.get.Position) } case Some(_) => + //clear the hack state if(agent.isEmpty) { hackedBy = None } + //override the hack state with a new hack state if the new user has different faction affiliation else if(agent.get.Faction != hackedBy.get._1.Faction) { - hackedBy = Some(agent.get, agent.get.Position) //overwrite + hackedBy = Some(agent.get, agent.get.GUID, agent.get.Position) } } HackedBy } - def Definition : IFFLockDefinition = GlobalDefinitions.external_lock + def Definition : IFFLockDefinition = idef } object IFFLock { - def apply() : IFFLock = { - new IFFLock + /** + * Overloaded constructor. + * @param idef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields + */ + def apply(idef : IFFLockDefinition) : IFFLock = { + new IFFLock(idef) } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala index e96d1bd34..bf9d1b81a 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala @@ -1,22 +1,22 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.locks -import akka.actor.{Actor, Cancellable} +import akka.actor.Actor import net.psforever.objects.serverobject.CommonMessages +/** + * An `Actor` that handles messages being dispatched to a specific `IFFLock`. + * @param lock the `IFFLock` object being governed + * @see `CommonMessages` + */ class IFFLockControl(lock : IFFLock) extends Actor { def receive : Receive = { case CommonMessages.Hack(player) => lock.HackedBy = player + case CommonMessages.ClearHack() => lock.HackedBy = None - case _ => ; + + case _ => ; //no default message } } - -object IFFLockControl { - final val DefaultCloser : Cancellable = new Cancellable() { - override def cancel : Boolean = true - override def isCancelled : Boolean = true - } -} \ No newline at end of file diff --git a/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockDefinition.scala index dcad4139b..d8c180d89 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockDefinition.scala @@ -3,6 +3,10 @@ package net.psforever.objects.serverobject.locks import net.psforever.objects.definition.ObjectDefinition -class IFFLockDefinition extends ObjectDefinition(0) { +/** + * The definition for any `IFFLock`. + * Object Id 451 is a generic external lock. + */ +class IFFLockDefinition extends ObjectDefinition(451) { Name = "iff_lock" } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TemporaryTerminalMessages.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TemporaryTerminalMessages.scala deleted file mode 100644 index 6d264df3e..000000000 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TemporaryTerminalMessages.scala +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.serverobject.terminals - -import net.psforever.objects.Player -import net.psforever.types.PlanetSideEmpire - -//temporary location for these temporary messages -object TemporaryTerminalMessages { - //TODO send original packets along with these messages - final case class UseItem(player : Player) - final case class Convert(faction : PlanetSideEmpire.Value) - final case class Hack(player : Player) - final case class ClearHack() - final case class Damaged(dm : Int) - final case class Repaired(rep : Int) -} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala index bccd245cd..57532c54c 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala @@ -5,48 +5,54 @@ import net.psforever.objects.Player import net.psforever.objects.equipment.Equipment import net.psforever.objects.inventory.InventoryItem import net.psforever.objects.serverobject.PlanetSideServerObject -import net.psforever.packet.game.ItemTransactionMessage -import net.psforever.types.{ExoSuitType, PlanetSideEmpire, TransactionType, Vector3} +import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} +import net.psforever.types.{ExoSuitType, TransactionType, Vector3} /** - * na + * A structure-owned server object that is a "terminal" that can be accessed for amenities and services. * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields */ class Terminal(tdef : TerminalDefinition) extends PlanetSideServerObject { - //the following fields and related methods are neither finalized no integrated; GOTO Request - private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL - private var hackedBy : Option[(Player, Vector3)] = None - private var health : Int = 100 //TODO not real health value + /** + * An entry that maintains a reference to the `Player`, and the player's GUID and location when the message was received. + */ + private var hackedBy : Option[(Player, PlanetSideGUID, Vector3)] = None - def Faction : PlanetSideEmpire.Value = faction + def HackedBy : Option[(Player, PlanetSideGUID, Vector3)] = hackedBy - def HackedBy : Option[(Player, Vector3)] = hackedBy + def HackedBy_=(agent : Player) : Option[(Player, PlanetSideGUID, Vector3)] = HackedBy_=(Some(agent)) - def HackedBy_=(agent : Player) : Option[(Player, Vector3)] = HackedBy_=(Some(agent)) - - def HackedBy_=(agent : Option[Player]) : Option[(Player, Vector3)] = { + /** + * Set the hack state of this object by recording important information about the player that caused it. + * Set the hack state if there is no current hack state. + * Override the hack state with a new hack state if the new user has different faction affiliation. + * @param agent a `Player`, or no player + * @return the player hack entry + */ + def HackedBy_=(agent : Option[Player]) : Option[(Player, PlanetSideGUID, Vector3)] = { hackedBy match { case None => + //set the hack state if there is no current hack state if(agent.isDefined) { - hackedBy = Some(agent.get, agent.get.Position) + hackedBy = Some(agent.get, agent.get.GUID, agent.get.Position) } case Some(_) => + //clear the hack state if(agent.isEmpty) { hackedBy = None } + //override the hack state with a new hack state if the new user has different faction affiliation else if(agent.get.Faction != hackedBy.get._1.Faction) { - hackedBy = Some(agent.get, agent.get.Position) //overwrite + hackedBy = Some(agent.get, agent.get.GUID, agent.get.Position) } } HackedBy } - def Health : Int = health + //the following fields and related methods are neither finalized nor integrated; GOTO Request + private var health : Int = 100 //TODO not real health value - def Convert(toFaction : PlanetSideEmpire.Value) : Unit = { - hackedBy = None - faction = toFaction - } + def Health : Int = health def Damaged(dam : Int) : Unit = { health = Math.max(0, Health - dam) @@ -146,14 +152,11 @@ object Terminal { */ final case class InfantryLoadout(exosuit : ExoSuitType.Value, subtype : Int = 0, holsters : List[InventoryItem], inventory : List[InventoryItem]) extends Exchange + /** + * Overloaded constructor. + * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields + */ def apply(tdef : TerminalDefinition) : Terminal = { new Terminal(tdef) } - - import net.psforever.packet.game.PlanetSideGUID - def apply(guid : PlanetSideGUID, tdef : TerminalDefinition) : Terminal = { - val obj = new Terminal(tdef) - obj.GUID = guid - obj - } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala index 8774245ed..c74ada798 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala @@ -4,9 +4,7 @@ package net.psforever.objects.serverobject.terminals import akka.actor.Actor /** - * An `Actor` that handles messages being dispatched to a specific `Terminal`.
- *
- * For now, the only important message being managed is `Terminal.Request`. + * An `Actor` that handles messages being dispatched to a specific `Terminal`. * @param term the `Terminal` object being governed */ class TerminalControl(term : Terminal) extends Actor { @@ -14,12 +12,6 @@ class TerminalControl(term : Terminal) extends Actor { case Terminal.Request(player, msg) => sender ! Terminal.TerminalMessage(player, msg, term.Request(player, msg)) - case TemporaryTerminalMessages.Hack(player) => - term.HackedBy = player - - case TemporaryTerminalMessages.ClearHack() => - term.HackedBy = None - case _ => sender ! Terminal.NoDeal() } diff --git a/common/src/main/scala/net/psforever/objects/zones/DoorCloseActor.scala b/common/src/main/scala/net/psforever/objects/zones/DoorCloseActor.scala index b1604b75f..4b2d9e9fc 100644 --- a/common/src/main/scala/net/psforever/objects/zones/DoorCloseActor.scala +++ b/common/src/main/scala/net/psforever/objects/zones/DoorCloseActor.scala @@ -8,33 +8,38 @@ import net.psforever.packet.game.PlanetSideGUID import scala.annotation.tailrec import scala.concurrent.duration._ +/** + * Close an opened door after a certain amount of time has passed. + * This `Actor` is intended to sit on top of the event system that handles broadcast messaging regarding doors opening. + * @see `LocalService` + */ class DoorCloseActor() extends Actor { - import DoorCloseActor._ - private var doorCloserTrigger : Cancellable = DefaultCloser - private var openDoors : List[DoorEntry] = Nil + /** The periodic `Executor` that checks for doors to be closed */ + private var doorCloserTrigger : Cancellable = DoorCloseActor.DefaultCloser + /** A `List` of currently open doors */ + private var openDoors : List[DoorCloseActor.DoorEntry] = Nil //private[this] val log = org.log4s.getLogger def receive : Receive = { - case DoorIsOpen(door, zone, time) => - openDoors = openDoors :+ DoorEntry(door, zone, time) - if(openDoors.size == 1) { + case DoorCloseActor.DoorIsOpen(door, zone, time) => + openDoors = openDoors :+ DoorCloseActor.DoorEntry(door, zone, time) + if(openDoors.size == 1) { //we were the only entry so the event must be started from scratch import scala.concurrent.ExecutionContext.Implicits.global - doorCloserTrigger = context.system.scheduler.scheduleOnce(timeout, self, DoorCloseActor.TryCloseDoors()) + doorCloserTrigger = context.system.scheduler.scheduleOnce(DoorCloseActor.timeout, self, DoorCloseActor.TryCloseDoors()) } - case TryCloseDoors() => + case DoorCloseActor.TryCloseDoors() => doorCloserTrigger.cancel val now : Long = System.nanoTime - //TODO we can just walk across the list of doors and extract only the first few entries - val (doorsToClose, doorsLeftOpen) = recursivePartitionDoors(openDoors.iterator, now) + val (doorsToClose, doorsLeftOpen) = PartitionEntries(openDoors, now) openDoors = doorsLeftOpen doorsToClose.foreach(entry => { - entry.door.Open = false //permissible - context.parent ! DoorCloseActor.CloseTheDoor(entry.door.GUID, entry.zone.Id) + entry.door.Open = false //permissible break from synchronization + context.parent ! DoorCloseActor.CloseTheDoor(entry.door.GUID, entry.zone.Id) //call up to the main event system }) if(doorsLeftOpen.nonEmpty) { - val short_timeout : FiniteDuration = math.max(1, timeout_time - (now - doorsLeftOpen.head.opened_at_time)) nanoseconds + val short_timeout : FiniteDuration = math.max(1, DoorCloseActor.timeout_time - (now - doorsLeftOpen.head.time)) nanoseconds import scala.concurrent.ExecutionContext.Implicits.global doorCloserTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, DoorCloseActor.TryCloseDoors()) } @@ -43,33 +48,53 @@ class DoorCloseActor() extends Actor { } /** - * na - * @param iter na - * @param now na - * @param list na + * Iterate over entries in a `List` until an entry that does not exceed the time limit is discovered. + * Separate the original `List` into two: + * a `List` of elements that have exceeded the time limit, + * and a `List` of elements that still satisfy the time limit. + * As newer entries to the `List` will always resolve later than old ones, + * and newer entries are always added to the end of the main `List`, + * processing in order is always correct. + * @param list the `List` of entries to divide + * @param now the time right now (in nanoseconds) * @see `List.partition` - * @return a `Tuple` of two `Lists`: - * the entries for all `Door`s that are closing, - * and the entries for all doors that are staying open + * @return a `Tuple` of two `Lists`, whose qualifications are explained above */ - @tailrec private def recursivePartitionDoors(iter : Iterator[DoorEntry], now : Long, list : List[DoorEntry] = Nil) : (List[DoorEntry], List[DoorEntry]) = { + private def PartitionEntries(list : List[DoorCloseActor.DoorEntry], now : Long) : (List[DoorCloseActor.DoorEntry], List[DoorCloseActor.DoorEntry]) = { + val n : Int = recursivePartitionEntries(list.iterator, now) + (list.take(n), list.drop(n)) //take and drop so to always return new lists + } + + /** + * Mark the index where the `List` of elements can be divided into two: + * a `List` of elements that have exceeded the time limit, + * and a `List` of elements that still satisfy the time limit. + * @param iter the `Iterator` of entries to divide + * @param now the time right now (in nanoseconds) + * @param index a persistent record of the index where list division should occur; + * defaults to 0 + * @return the index where division will occur + */ + @tailrec private def recursivePartitionEntries(iter : Iterator[DoorCloseActor.DoorEntry], now : Long, index : Int = 0) : Int = { if(!iter.hasNext) { - (list, iter.toList) + index } else { val entry = iter.next() - if(now - entry.opened_at_time >= timeout_time) { - recursivePartitionDoors(iter, now, list :+ entry) + if(now - entry.time >= DoorCloseActor.timeout_time) { + recursivePartitionEntries(iter, now, index + 1) } else { - (list, entry +: iter.toList) + index } } } } object DoorCloseActor { - private final val timeout_time : Long = 5000000000L //nanoseconds + /** The wait before an open door closes; as a Long for calculation simplicity */ + private final val timeout_time : Long = 5000000000L //nanoseconds (5s) + /** The wait before an open door closes; as a `FiniteDuration` for `Executor` simplicity */ private final val timeout : FiniteDuration = timeout_time nanoseconds private final val DefaultCloser : Cancellable = new Cancellable() { @@ -77,11 +102,33 @@ object DoorCloseActor { override def isCancelled : Boolean = true } - final case class DoorIsOpen(door : Door, zone : Zone, opened_at_time : Long = System.nanoTime()) - + /** + * Message that carries information about a door that has been opened. + * @param door the door object + * @param zone the zone in which the door resides + * @param time when the door was opened + * @see `DoorEntry` + */ + final case class DoorIsOpen(door : Door, zone : Zone, time : Long = System.nanoTime()) + /** + * Message that carries information about a door that needs to close. + * Prompting, as compared to `DoorIsOpen` which is reactionary. + * @param door_guid the door + * @param zone_id the zone in which the door resides + */ final case class CloseTheDoor(door_guid : PlanetSideGUID, zone_id : String) - - private final case class DoorEntry(door : Door, zone : Zone, opened_at_time : Long) - + /** + * Internal message used to signal a test of the queued door information. + */ private final case class TryCloseDoors() + + /** + * Entry of door information. + * The `zone` is maintained separately to ensure that any message resulting in an attempt to close doors is targetted. + * @param door the door object + * @param zone the zone in which the door resides + * @param time when the door was opened + * @see `DoorIsOpen` + */ + private final case class DoorEntry(door : Door, zone : Zone, time : Long) } diff --git a/common/src/main/scala/net/psforever/objects/zones/HackClearActor.scala b/common/src/main/scala/net/psforever/objects/zones/HackClearActor.scala index b595051e6..2ec3cb053 100644 --- a/common/src/main/scala/net/psforever/objects/zones/HackClearActor.scala +++ b/common/src/main/scala/net/psforever/objects/zones/HackClearActor.scala @@ -8,33 +8,39 @@ import net.psforever.packet.game.PlanetSideGUID import scala.annotation.tailrec import scala.concurrent.duration._ +/** + * Restore original functionality to an object that has been hacked after a certain amount of time has passed. + * This `Actor` is intended to sit on top of the event system that handles broadcast messaging regarding hacking events. + * @see `LocalService` + */ class HackClearActor() extends Actor { - import HackClearActor._ - private var clearTrigger : Cancellable = DefaultClearer - private var hackedObjects : List[HackEntry] = Nil + /** The periodic `Executor` that checks for server objects to be unhacked */ + private var clearTrigger : Cancellable = HackClearActor.DefaultClearer + /** A `List` of currently hacked server objects */ + private var hackedObjects : List[HackClearActor.HackEntry] = Nil //private[this] val log = org.log4s.getLogger def receive : Receive = { - case ObjectIsHacked(door, zone, unk1, unk2, time) => - hackedObjects = hackedObjects :+ HackEntry(door, zone, unk1, unk2, time) - if(hackedObjects.size == 1) { + case HackClearActor.ObjectIsHacked(target, zone, unk1, unk2, time) => + hackedObjects = hackedObjects :+ HackClearActor.HackEntry(target, zone, unk1, unk2, time) + if(hackedObjects.size == 1) { //we were the only entry so the event must be started from scratch import scala.concurrent.ExecutionContext.Implicits.global - clearTrigger = context.system.scheduler.scheduleOnce(timeout, self, HackClearActor.TryClearHacks()) + clearTrigger = context.system.scheduler.scheduleOnce(HackClearActor.timeout, self, HackClearActor.TryClearHacks()) } - case TryClearHacks() => + case HackClearActor.TryClearHacks() => clearTrigger.cancel val now : Long = System.nanoTime //TODO we can just walk across the list of doors and extract only the first few entries - val (unhackObjects, stillHackedObjects) = recursivePartitionHacks(hackedObjects.iterator, now) + val (unhackObjects, stillHackedObjects) = PartitionEntries(hackedObjects, now) hackedObjects = stillHackedObjects unhackObjects.foreach(entry => { entry.target.Actor ! CommonMessages.ClearHack() - context.parent ! HackClearActor.ClearTheHack(entry.target.GUID, entry.zone.Id, entry.unk1, entry.unk2) + context.parent ! HackClearActor.ClearTheHack(entry.target.GUID, entry.zone.Id, entry.unk1, entry.unk2) //call up to the main event system }) if(stillHackedObjects.nonEmpty) { - val short_timeout : FiniteDuration = math.max(1, timeout_time - (now - stillHackedObjects.head.hacked_at_time)) nanoseconds + val short_timeout : FiniteDuration = math.max(1, HackClearActor.timeout_time - (now - stillHackedObjects.head.time)) nanoseconds import scala.concurrent.ExecutionContext.Implicits.global clearTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, HackClearActor.TryClearHacks()) } @@ -43,33 +49,53 @@ class HackClearActor() extends Actor { } /** - * na - * @param iter na - * @param now na - * @param list na + * Iterate over entries in a `List` until an entry that does not exceed the time limit is discovered. + * Separate the original `List` into two: + * a `List` of elements that have exceeded the time limit, + * and a `List` of elements that still satisfy the time limit. + * As newer entries to the `List` will always resolve later than old ones, + * and newer entries are always added to the end of the main `List`, + * processing in order is always correct. + * @param list the `List` of entries to divide + * @param now the time right now (in nanoseconds) * @see `List.partition` - * @return a `Tuple` of two `Lists`: - * the entries for all objects that are no longer hacked, - * and the entries for all objects that are still hacked + * @return a `Tuple` of two `Lists`, whose qualifications are explained above */ - @tailrec private def recursivePartitionHacks(iter : Iterator[HackEntry], now : Long, list : List[HackEntry] = Nil) : (List[HackEntry], List[HackEntry]) = { + private def PartitionEntries(list : List[HackClearActor.HackEntry], now : Long) : (List[HackClearActor.HackEntry], List[HackClearActor.HackEntry]) = { + val n : Int = recursivePartitionEntries(list.iterator, now) + (list.take(n), list.drop(n)) //take and drop so to always return new lists + } + + /** + * Mark the index where the `List` of elements can be divided into two: + * a `List` of elements that have exceeded the time limit, + * and a `List` of elements that still satisfy the time limit. + * @param iter the `Iterator` of entries to divide + * @param now the time right now (in nanoseconds) + * @param index a persistent record of the index where list division should occur; + * defaults to 0 + * @return the index where division will occur + */ + @tailrec private def recursivePartitionEntries(iter : Iterator[HackClearActor.HackEntry], now : Long, index : Int = 0) : Int = { if(!iter.hasNext) { - (list, iter.toList) + index } else { val entry = iter.next() - if(now - entry.hacked_at_time >= timeout_time) { - recursivePartitionHacks(iter, now, list :+ entry) + if(now - entry.time >= HackClearActor.timeout_time) { + recursivePartitionEntries(iter, now, index + 1) } else { - (list, entry +: iter.toList) + index } } } } object HackClearActor { - private final val timeout_time : Long = 60000000000L //nanoseconds (1 minute) + /** The wait before a server object is to unhack; as a Long for calculation simplicity */ + private final val timeout_time : Long = 60000000000L //nanoseconds (60s) + /** The wait before a server object is to unhack; as a `FiniteDuration` for `Executor` simplicity */ private final val timeout : FiniteDuration = timeout_time nanoseconds private final val DefaultClearer : Cancellable = new Cancellable() { @@ -77,11 +103,33 @@ object HackClearActor { override def isCancelled : Boolean = true } - final case class ObjectIsHacked(target : PlanetSideServerObject, zone : Zone, unk1 : Long, unk2 : Long, hacked_at_time : Long = System.nanoTime()) - + /** + * Message that carries information about a server object that has been hacked. + * @param target the server object + * @param zone the zone in which the object resides + * @param time when the object was hacked + * @see `HackEntry` + */ + final case class ObjectIsHacked(target : PlanetSideServerObject, zone : Zone, unk1 : Long, unk2 : Long, time : Long = System.nanoTime()) + /** + * Message that carries information about a server object that needs its functionality restored. + * Prompting, as compared to `ObjectIsHacked` which is reactionary. + * @param door_guid the server object + * @param zone_id the zone in which the object resides + */ final case class ClearTheHack(door_guid : PlanetSideGUID, zone_id : String, unk1 : Long, unk2 : Long) - - private final case class HackEntry(target : PlanetSideServerObject, zone : Zone, unk1 : Long, unk2 : Long, hacked_at_time : Long) - + /** + * Internal message used to signal a test of the queued door information. + */ private final case class TryClearHacks() + + /** + * Entry of hacked server object information. + * The `zone` is maintained separately to ensure that any message resulting in an attempt to close doors is targetted. + * @param target the server object + * @param zone the zone in which the object resides + * @param time when the object was hacked + * @see `ObjectIsHacked` + */ + private final case class HackEntry(target : PlanetSideServerObject, zone : Zone, unk1 : Long, unk2 : Long, time : Long) } diff --git a/common/src/main/scala/net/psforever/packet/game/HackMessage.scala b/common/src/main/scala/net/psforever/packet/game/HackMessage.scala index ae3c0451e..2225033e3 100644 --- a/common/src/main/scala/net/psforever/packet/game/HackMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/HackMessage.scala @@ -6,21 +6,65 @@ import scodec.Codec import scodec.codecs._ /** - * - * @param unk1 na - * @param unk2 na - * @param unk3 na - * @param unk4 na - * @param unk5 na - * @param unk6 na - * @param unk7 na - */ + * An `Enumeration` of the various states and activities of the hacking process. + * These values are closely tied to the condition of the hacking progress bar and/or the condition of the hacked object.
+ *
+ * `Start` initially displays the hacking progress bar.
+ * `Ongoing` is a neutral state that keeps the progress bar displayed while its value updates. (unconfirmed?)
+ * `Finished` disposes of the hacking progress bar. It does not, by itself, mean the hack was successful.
+ * `Hacked` modifies the target of the hack.
+ * `HackCleared` modifies the target of the hack, opposite of `Hacked`. + */ +object HackState extends Enumeration { + type Type = Value + + val + Unknown0, + Start, + Unknown2, + Ongoing, + Finished, + Unknown5, + Hacked, + HackCleared + = Value + + implicit val codec = PacketHelpers.createEnumerationCodec(this, uint8L) +} + +/** + * Dispatched by the server to control the process of hacking.
+ *
+ * Part of the hacking process is regulated by the server while another part of it is automatically reset by the client. + * The visibility, update, and closing of the hacking progress bar must be handled manually, for each tick. + * When hacking is complete, using the appropriate `HackState` will cue the target to be affected by the hack. + * Terminals and door IFF panels will temporarily expose their functionality; + * the faction association of vehicles will be converted permanently; + * a protracted process of a base conversion will be enacted; etc.. + * This transfer of faction association occurs to align the target with the faction of the hacking player (as indicated). + * The client will select the faction without needing to be explicitly told + * and will select the appropriate action to enact upon the target. + * Upon the hack's completion, the target on the client will automatically revert back to its original state, if possible. + * (It will still be necessary to alert this change from the server's perspective.) + * @param unk1 na; + * hack type? + * @param target_guid the target of the hack + * @param player_guid the player + * @param progress the amount of progress visible; + * visible range is 0 - 100 + * @param unk5 na; + * often a large number; + * doesn't seem to be `char_id`? + * @param hack_state hack state + * @param unk7 na; + * usually, 8? + */ final case class HackMessage(unk1 : Int, - unk2 : Int, - unk3 : Int, - unk4 : Int, + target_guid : PlanetSideGUID, + player_guid : PlanetSideGUID, + progress : Int, unk5 : Long, - unk6 : Int, + hack_state : HackState.Value, unk7 : Long) extends PlanetSideGamePacket { type Packet = HackMessage @@ -30,12 +74,12 @@ final case class HackMessage(unk1 : Int, object HackMessage extends Marshallable[HackMessage] { implicit val codec : Codec[HackMessage] = ( - ("unk1" | uint2L) :: - ("unk2" | uint16L) :: - ("unk3" | uint16L) :: - ("unk4" | uint8L) :: - ("unk5" | uint32L) :: - ("unk6" | uint8L) :: - ("unk7" | uint32L) - ).as[HackMessage] + ("unk1" | uint2L) :: + ("object_guid" | PlanetSideGUID.codec) :: + ("player_guid" | PlanetSideGUID.codec) :: + ("progress" | uint8L) :: + ("unk5" | uint32L) :: + ("hack_state" | HackState.codec) :: + ("unk7" | uint32L) + ).as[HackMessage] } diff --git a/common/src/test/scala/game/HackMessageTest.scala b/common/src/test/scala/game/HackMessageTest.scala index 8473e2c54..469950d12 100644 --- a/common/src/test/scala/game/HackMessageTest.scala +++ b/common/src/test/scala/game/HackMessageTest.scala @@ -12,13 +12,13 @@ class HackMessageTest extends Specification { "decode" in { PacketCoding.DecodePacket(string).require match { - case HackMessage(unk1, unk2, unk3, unk4, unk5, unk6, unk7) => + case HackMessage(unk1, target_guid, player_guid, progress, unk5, hack_state, unk7) => unk1 mustEqual 0 - unk2 mustEqual 1024 - unk3 mustEqual 3607 - unk4 mustEqual 0 + target_guid mustEqual PlanetSideGUID(1024) + player_guid mustEqual PlanetSideGUID(3607) + progress mustEqual 0 unk5 mustEqual 3212836864L - unk6 mustEqual 1 + hack_state mustEqual HackState.Start unk7 mustEqual 8L case _ => ko @@ -26,7 +26,7 @@ class HackMessageTest extends Specification { } "encode" in { - val msg = HackMessage(0,1024,3607,0,3212836864L,1,8L) + val msg = HackMessage(0, PlanetSideGUID(1024), PlanetSideGUID(3607), 0, 3212836864L, HackState.Start, 8L) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string } diff --git a/pslogin/src/main/scala/PsLogin.scala b/pslogin/src/main/scala/PsLogin.scala index de00fb1b2..945516288 100644 --- a/pslogin/src/main/scala/PsLogin.scala +++ b/pslogin/src/main/scala/PsLogin.scala @@ -229,8 +229,8 @@ object PsLogin { LocalObject(DoorObjectBuilder(door, 332)) LocalObject(DoorObjectBuilder(door, 372)) LocalObject(DoorObjectBuilder(door, 373)) - LocalObject(IFFLockObjectBuilder(external_lock, 556)) - LocalObject(IFFLockObjectBuilder(external_lock, 558)) + LocalObject(IFFLockObjectBuilder(lock_external, 556)) + LocalObject(IFFLockObjectBuilder(lock_external, 558)) 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 50e8a59ad..2ed2191f1 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -230,11 +230,11 @@ class WorldSessionActor extends Actor with MDCContextAware { case LocalServiceResponse.HackClear(target_guid, unk1, unk2) => log.info(s"Clear hack of $target_guid") - sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(0, target_guid.guid, guid.guid, 0, unk1, 7, unk2))) + sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(0, target_guid, guid, 0, unk1, HackState.HackCleared, unk2))) case LocalServiceResponse.HackObject(target_guid, unk1, unk2) => if(player.GUID != guid) { - sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(0, target_guid.guid, guid.guid, 100, unk1, 6, unk2))) + sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(0, target_guid, guid, 100, unk1, HackState.Hacked, unk2))) } } @@ -586,23 +586,23 @@ class WorldSessionActor extends Actor with MDCContextAware { progressBarUpdate.cancel if(progressBarValue.isDefined) { val progressBarVal : Float = progressBarValue.get + delta - val vis = if(progressBarVal == 0L) { - 1 + val vis = if(progressBarVal == 0L) { //hack state for progress bar visibility + HackState.Start } - else if(progressBarVal >= 100L) { - 4 + else if(progressBarVal > 100L) { + HackState.Finished } else { - 3 + HackState.Ongoing } - sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(1, target.GUID.guid, player.GUID.guid, progressBarVal.toInt, 0L, vis, 8L))) - if(progressBarVal > 100) { + sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(1, target.GUID, player.GUID, progressBarVal.toInt, 0L, vis, 8L))) + if(progressBarVal > 100) { //done progressBarValue = None log.info(s"Hacked a $target") - sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(0, target.GUID.guid, player.GUID.guid, 100, 1114636288L, 6, 8L))) + sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(0, target.GUID, player.GUID, 100, 1114636288L, HackState.Hacked, 8L))) completeAction() } - else { + else { //continue next tick tickAction.getOrElse(() => Unit)() progressBarValue = Some(progressBarVal) import scala.concurrent.duration._ @@ -1028,10 +1028,10 @@ class WorldSessionActor extends Actor with MDCContextAware { // TODO: Not all incoming UseItemMessage's respond with another UseItemMessage (i.e. doors only send out GenericObjectStateMsg) continent.GUID(object_guid) match { case Some(door : Door) => - continent.Map.DoorToLock.get(object_guid.guid) match { //check for IFFLock + continent.Map.DoorToLock.get(object_guid.guid) match { //check for IFF Lock case Some(lock_guid) => val lock_hacked = continent.GUID(lock_guid).get.asInstanceOf[IFFLock].HackedBy match { - case Some((tplayer, _)) => + case Some((tplayer, _, _)) => tplayer.Faction == player.Faction case None => false @@ -1042,7 +1042,7 @@ class WorldSessionActor extends Actor with MDCContextAware { door.Actor ! Door.Use(player, msg) } case None => - if(lock_hacked) { //is locks hacked? + if(lock_hacked) { //is lock hacked? this may be a weird case door.Actor ! Door.Use(player, msg) } } @@ -1054,13 +1054,13 @@ class WorldSessionActor extends Actor with MDCContextAware { player.Slot(player.DrawnSlot).Equipment match { case Some(tool : SimpleItem) => if(tool.Definition == GlobalDefinitions.remote_electronics_kit) { + //TODO get player hack level (for now, presume 15s in intervals of 4/s) progressBarValue = Some(-2.66f) - self ! WorldSessionActor.ItemHacking(player, panel, tool.GUID, 2.66f, HackTemporary(panel)) + self ! WorldSessionActor.ItemHacking(player, panel, tool.GUID, 2.66f, FinishHackingDoor(panel, 1114636288L)) + log.info("Hacking a door~") } case _ => ; } - log.info("Hacking a door~") - //TODO get player hack level (for now, presume 15s in internals of 4/s) case Some(obj : PlanetSideGameObject) => if(itemType != 121) { @@ -1071,10 +1071,7 @@ class WorldSessionActor extends Actor with MDCContextAware { 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 => ; } @@ -1625,9 +1622,18 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - private def HackTemporary(target : PlanetSideServerObject)() : Unit = { + /** + * The process of hacking the `Door` `IFFLock` is completed. + * Pass the message onto the lock and onto the local events system. + * @param target the `IFFLock` belonging to the door that is being hacked + * @param unk na; + * used by `HackingMessage` as `unk5` + * @see `HackMessage` + */ + //TODO add params here depending on which params in HackMessage are important + private def FinishHackingDoor(target : IFFLock, unk : Long)() : Unit = { target.Actor ! CommonMessages.Hack(player) - localService ! LocalServiceMessage(player.Continent, LocalAction.HackTemporarily(player.GUID, continent, target, 1114636288L)) + localService ! LocalServiceMessage(continent.Id, LocalAction.HackTemporarily(player.GUID, continent, target, unk)) } def failWithError(error : String) = { @@ -1660,6 +1666,18 @@ object WorldSessionActor { private final case class PlayerFailedToLoad(tplayer : Player) private final case class ListAccountCharacters() private final case class SetCurrentAvatar(tplayer : Player) + + /** + * A message that indicates the user is using a remote electronics kit to hack some server object. + * Each time this message is sent for a given hack attempt counts as a single "tick" of progress. + * The process of "making progress" with a hack involves sending this message repeatedly until the progress is 100 or more. + * @param tplayer the player + * @param target the object being hacked + * @param tool_guid the REK + * @param delta how much the progress bar value changes each tick + * @param completeAction a custom action performed once the hack is completed + * @param tickAction an optional action is is performed for each tick of progress + */ private final case class ItemHacking(tplayer : Player, target : PlanetSideServerObject, tool_guid : PlanetSideGUID,