comments and documentation, mainly; adjusted the list splitting functionality in the LocalService support Actors

This commit is contained in:
FateJH 2017-10-13 14:57:28 -04:00
parent 0a4bac8ab5
commit d5f40a3d5f
22 changed files with 408 additions and 211 deletions

View file

@ -1245,7 +1245,7 @@ object GlobalDefinitions {
cert_terminal = new CertTerminalDefinition cert_terminal = new CertTerminalDefinition
val val
external_lock = new IFFLockDefinition lock_external = new IFFLockDefinition
val val
door = new DoorDefinition door = new DoorDefinition
} }

View file

@ -64,8 +64,6 @@ class Player(private val name : String,
/** Last medkituse. */ /** Last medkituse. */
var lastMedkit : Long = 0 var lastMedkit : Long = 0
var death_by : Int = 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 lastSeenStreamMessage : Array[Long] = Array.fill[Long](65535)(0L)
var lastShotSeq_time : Int = -1 var lastShotSeq_time : Int = -1
/** The player is shooting. */ /** The player is shooting. */

View file

@ -2,9 +2,8 @@
package net.psforever.objects.serverobject package net.psforever.objects.serverobject
import net.psforever.objects.Player import net.psforever.objects.Player
import net.psforever.types.PlanetSideEmpire
//temporary location for these temporary messages //temporary location for these messages
object CommonMessages { object CommonMessages {
final case class Hack(player : Player) final case class Hack(player : Player)
final case class ClearHack() final case class ClearHack()

View file

@ -4,11 +4,25 @@ package net.psforever.objects.serverobject
import akka.actor.ActorRef import akka.actor.ActorRef
import net.psforever.objects.PlanetSideGameObject 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 { abstract class PlanetSideServerObject extends PlanetSideGameObject {
private var actor = ActorRef.noSender private var actor = ActorRef.noSender
/**
* Retrieve a reference to the internal `Actor`.
* @return the internal `ActorRef`
*/
def Actor : ActorRef = actor 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 = { def Actor_=(control : ActorRef) : ActorRef = {
if(actor == ActorRef.noSender) { if(actor == ActorRef.noSender) {
actor = control actor = control

View file

@ -5,8 +5,8 @@ import akka.actor.Props
import net.psforever.objects.serverobject.locks.{IFFLock, IFFLockControl, IFFLockDefinition} import net.psforever.objects.serverobject.locks.{IFFLock, IFFLockControl, IFFLockDefinition}
/** /**
* Wrapper `Class` designed to instantiate a `Door` server object. * 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 of the resulting `Door` * @param idef a `IFFLockDefinition` object, indicating the specific functionality
* @param id the globally unique identifier to which this `IFFLock` will be registered * @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] { 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 import net.psforever.objects.guid.NumberPoolHub
def Build(implicit context : ActorContext, guid : NumberPoolHub) : IFFLock = { def Build(implicit context : ActorContext, guid : NumberPoolHub) : IFFLock = {
val obj = IFFLock() val obj = IFFLock(idef)
guid.register(obj, id) //non-Actor GUID registration guid.register(obj, id) //non-Actor GUID registration
obj.Actor = context.actorOf(Props(classOf[IFFLockControl], obj), s"${idef.Name}_${obj.GUID.guid}") obj.Actor = context.actorOf(Props(classOf[IFFLockControl], obj), s"${idef.Name}_${obj.GUID.guid}")
obj obj

View file

@ -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. * 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` * @see `Zone.Init`
*/ */
//TODO can we changed PlanetSideGameObject -> PlanetSideServerObject?
trait ServerObjectBuilder[A <: PlanetSideGameObject] { trait ServerObjectBuilder[A <: PlanetSideGameObject] {
/** /**
* Instantiate and configure the given server object * Instantiate and configure the given server object

View file

@ -3,6 +3,10 @@ package net.psforever.objects.serverobject.doors
import net.psforever.types.PlanetSideEmpire 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) { class Base(private val id : Int) {
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL

View file

@ -6,10 +6,10 @@ import net.psforever.objects.Player
import net.psforever.packet.game.UseItemMessage 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 * @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 openState : Boolean = false
private var lockState : Boolean = false private var lockState : Boolean = false
@ -45,26 +45,46 @@ class Door(ddef : DoorDefinition) extends PlanetSideServerObject {
} }
object Door { 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) final case class Use(player : Player, msg : UseItemMessage)
/**
* A basic `Trait` connecting all of the actionable `Door` response messages.
*/
sealed trait Exchange 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) final case class DoorMessage(player : Player, msg : UseItemMessage, response : Exchange)
/**
* This door will open.
*/
final case class OpenEvent() extends Exchange final case class OpenEvent() extends Exchange
/**
* This door will close.
*/
final case class CloseEvent() extends Exchange final case class CloseEvent() extends Exchange
/**
* This door will do nothing.
*/
final case class NoEvent() extends Exchange 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 = { def apply(tdef : DoorDefinition) : Door = {
new Door(tdef) 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
}
} }

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.doors 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`. * An `Actor` that handles messages being dispatched to a specific `Door`.
@ -16,10 +16,3 @@ class DoorControl(door : Door) extends Actor {
sender ! Door.NoEvent() sender ! Door.NoEvent()
} }
} }
object DoorControl {
final val DefaultCloser : Cancellable = new Cancellable() {
override def cancel : Boolean = true
override def isCancelled : Boolean = true
}
}

View file

@ -4,7 +4,7 @@ package net.psforever.objects.serverobject.doors
import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.definition.ObjectDefinition
/** /**
* The definition for any `door`. * The definition for any `Door`.
* Object Id 242 is a generic door. * Object Id 242 is a generic door.
*/ */
class DoorDefinition extends ObjectDefinition(242) { class DoorDefinition extends ObjectDefinition(242) {

View file

@ -1,39 +1,66 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.locks 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.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."<br>
* <br>
* 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 { hackedBy match {
case None => case None =>
//set the hack state if there is no current hack state
if(agent.isDefined) { if(agent.isDefined) {
hackedBy = Some(agent.get, agent.get.Position) hackedBy = Some(agent.get, agent.get.GUID, agent.get.Position)
} }
case Some(_) => case Some(_) =>
//clear the hack state
if(agent.isEmpty) { if(agent.isEmpty) {
hackedBy = None 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) { 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 HackedBy
} }
def Definition : IFFLockDefinition = GlobalDefinitions.external_lock def Definition : IFFLockDefinition = idef
} }
object IFFLock { 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)
} }
} }

View file

@ -1,22 +1,22 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.locks package net.psforever.objects.serverobject.locks
import akka.actor.{Actor, Cancellable} import akka.actor.Actor
import net.psforever.objects.serverobject.CommonMessages 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 { class IFFLockControl(lock : IFFLock) extends Actor {
def receive : Receive = { def receive : Receive = {
case CommonMessages.Hack(player) => case CommonMessages.Hack(player) =>
lock.HackedBy = player lock.HackedBy = player
case CommonMessages.ClearHack() => case CommonMessages.ClearHack() =>
lock.HackedBy = None lock.HackedBy = None
case _ => ;
}
}
object IFFLockControl { case _ => ; //no default message
final val DefaultCloser : Cancellable = new Cancellable() {
override def cancel : Boolean = true
override def isCancelled : Boolean = true
} }
} }

View file

@ -3,6 +3,10 @@ package net.psforever.objects.serverobject.locks
import net.psforever.objects.definition.ObjectDefinition 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" Name = "iff_lock"
} }

View file

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

View file

@ -5,48 +5,54 @@ import net.psforever.objects.Player
import net.psforever.objects.equipment.Equipment import net.psforever.objects.equipment.Equipment
import net.psforever.objects.inventory.InventoryItem import net.psforever.objects.inventory.InventoryItem
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.packet.game.ItemTransactionMessage import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
import net.psforever.types.{ExoSuitType, PlanetSideEmpire, TransactionType, Vector3} 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 * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/ */
class Terminal(tdef : TerminalDefinition) extends PlanetSideServerObject { 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 * 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, Vector3)] = None */
private var health : Int = 100 //TODO not real health value 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)) /**
* Set the hack state of this object by recording important information about the player that caused it.
def HackedBy_=(agent : Option[Player]) : Option[(Player, Vector3)] = { * 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 { hackedBy match {
case None => case None =>
//set the hack state if there is no current hack state
if(agent.isDefined) { if(agent.isDefined) {
hackedBy = Some(agent.get, agent.get.Position) hackedBy = Some(agent.get, agent.get.GUID, agent.get.Position)
} }
case Some(_) => case Some(_) =>
//clear the hack state
if(agent.isEmpty) { if(agent.isEmpty) {
hackedBy = None 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) { 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 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 = { def Health : Int = health
hackedBy = None
faction = toFaction
}
def Damaged(dam : Int) : Unit = { def Damaged(dam : Int) : Unit = {
health = Math.max(0, Health - dam) 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 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 = { def apply(tdef : TerminalDefinition) : Terminal = {
new Terminal(tdef) 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
}
} }

View file

@ -4,9 +4,7 @@ package net.psforever.objects.serverobject.terminals
import akka.actor.Actor import akka.actor.Actor
/** /**
* An `Actor` that handles messages being dispatched to a specific `Terminal`.<br> * An `Actor` that handles messages being dispatched to a specific `Terminal`.
* <br>
* For now, the only important message being managed is `Terminal.Request`.
* @param term the `Terminal` object being governed * @param term the `Terminal` object being governed
*/ */
class TerminalControl(term : Terminal) extends Actor { class TerminalControl(term : Terminal) extends Actor {
@ -14,12 +12,6 @@ class TerminalControl(term : Terminal) extends Actor {
case Terminal.Request(player, msg) => case Terminal.Request(player, msg) =>
sender ! Terminal.TerminalMessage(player, msg, term.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 _ => case _ =>
sender ! Terminal.NoDeal() sender ! Terminal.NoDeal()
} }

View file

@ -8,33 +8,38 @@ import net.psforever.packet.game.PlanetSideGUID
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.concurrent.duration._ 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 { class DoorCloseActor() extends Actor {
import DoorCloseActor._ /** The periodic `Executor` that checks for doors to be closed */
private var doorCloserTrigger : Cancellable = DefaultCloser private var doorCloserTrigger : Cancellable = DoorCloseActor.DefaultCloser
private var openDoors : List[DoorEntry] = Nil /** A `List` of currently open doors */
private var openDoors : List[DoorCloseActor.DoorEntry] = Nil
//private[this] val log = org.log4s.getLogger //private[this] val log = org.log4s.getLogger
def receive : Receive = { def receive : Receive = {
case DoorIsOpen(door, zone, time) => case DoorCloseActor.DoorIsOpen(door, zone, time) =>
openDoors = openDoors :+ DoorEntry(door, zone, time) openDoors = openDoors :+ DoorCloseActor.DoorEntry(door, zone, time)
if(openDoors.size == 1) { if(openDoors.size == 1) { //we were the only entry so the event must be started from scratch
import scala.concurrent.ExecutionContext.Implicits.global 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 doorCloserTrigger.cancel
val now : Long = System.nanoTime 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) = PartitionEntries(openDoors, now)
val (doorsToClose, doorsLeftOpen) = recursivePartitionDoors(openDoors.iterator, now)
openDoors = doorsLeftOpen openDoors = doorsLeftOpen
doorsToClose.foreach(entry => { doorsToClose.foreach(entry => {
entry.door.Open = false //permissible entry.door.Open = false //permissible break from synchronization
context.parent ! DoorCloseActor.CloseTheDoor(entry.door.GUID, entry.zone.Id) context.parent ! DoorCloseActor.CloseTheDoor(entry.door.GUID, entry.zone.Id) //call up to the main event system
}) })
if(doorsLeftOpen.nonEmpty) { 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 import scala.concurrent.ExecutionContext.Implicits.global
doorCloserTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, DoorCloseActor.TryCloseDoors()) doorCloserTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, DoorCloseActor.TryCloseDoors())
} }
@ -43,33 +48,53 @@ class DoorCloseActor() extends Actor {
} }
/** /**
* na * Iterate over entries in a `List` until an entry that does not exceed the time limit is discovered.
* @param iter na * Separate the original `List` into two:
* @param now na * a `List` of elements that have exceeded the time limit,
* @param list na * 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` * @see `List.partition`
* @return a `Tuple` of two `Lists`: * @return a `Tuple` of two `Lists`, whose qualifications are explained above
* the entries for all `Door`s that are closing,
* and the entries for all doors that are staying open
*/ */
@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) { if(!iter.hasNext) {
(list, iter.toList) index
} }
else { else {
val entry = iter.next() val entry = iter.next()
if(now - entry.opened_at_time >= timeout_time) { if(now - entry.time >= DoorCloseActor.timeout_time) {
recursivePartitionDoors(iter, now, list :+ entry) recursivePartitionEntries(iter, now, index + 1)
} }
else { else {
(list, entry +: iter.toList) index
} }
} }
} }
} }
object DoorCloseActor { 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 timeout : FiniteDuration = timeout_time nanoseconds
private final val DefaultCloser : Cancellable = new Cancellable() { private final val DefaultCloser : Cancellable = new Cancellable() {
@ -77,11 +102,33 @@ object DoorCloseActor {
override def isCancelled : Boolean = true 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) 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() 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)
} }

View file

@ -8,33 +8,39 @@ import net.psforever.packet.game.PlanetSideGUID
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.concurrent.duration._ 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 { class HackClearActor() extends Actor {
import HackClearActor._ /** The periodic `Executor` that checks for server objects to be unhacked */
private var clearTrigger : Cancellable = DefaultClearer private var clearTrigger : Cancellable = HackClearActor.DefaultClearer
private var hackedObjects : List[HackEntry] = Nil /** A `List` of currently hacked server objects */
private var hackedObjects : List[HackClearActor.HackEntry] = Nil
//private[this] val log = org.log4s.getLogger //private[this] val log = org.log4s.getLogger
def receive : Receive = { def receive : Receive = {
case ObjectIsHacked(door, zone, unk1, unk2, time) => case HackClearActor.ObjectIsHacked(target, zone, unk1, unk2, time) =>
hackedObjects = hackedObjects :+ HackEntry(door, zone, unk1, unk2, time) hackedObjects = hackedObjects :+ HackClearActor.HackEntry(target, zone, unk1, unk2, time)
if(hackedObjects.size == 1) { if(hackedObjects.size == 1) { //we were the only entry so the event must be started from scratch
import scala.concurrent.ExecutionContext.Implicits.global 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 clearTrigger.cancel
val now : Long = System.nanoTime val now : Long = System.nanoTime
//TODO we can just walk across the list of doors and extract only the first few entries //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 hackedObjects = stillHackedObjects
unhackObjects.foreach(entry => { unhackObjects.foreach(entry => {
entry.target.Actor ! CommonMessages.ClearHack() 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) { 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 import scala.concurrent.ExecutionContext.Implicits.global
clearTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, HackClearActor.TryClearHacks()) clearTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, HackClearActor.TryClearHacks())
} }
@ -43,33 +49,53 @@ class HackClearActor() extends Actor {
} }
/** /**
* na * Iterate over entries in a `List` until an entry that does not exceed the time limit is discovered.
* @param iter na * Separate the original `List` into two:
* @param now na * a `List` of elements that have exceeded the time limit,
* @param list na * 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` * @see `List.partition`
* @return a `Tuple` of two `Lists`: * @return a `Tuple` of two `Lists`, whose qualifications are explained above
* the entries for all objects that are no longer hacked,
* and the entries for all objects that are still hacked
*/ */
@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) { if(!iter.hasNext) {
(list, iter.toList) index
} }
else { else {
val entry = iter.next() val entry = iter.next()
if(now - entry.hacked_at_time >= timeout_time) { if(now - entry.time >= HackClearActor.timeout_time) {
recursivePartitionHacks(iter, now, list :+ entry) recursivePartitionEntries(iter, now, index + 1)
} }
else { else {
(list, entry +: iter.toList) index
} }
} }
} }
} }
object HackClearActor { 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 timeout : FiniteDuration = timeout_time nanoseconds
private final val DefaultClearer : Cancellable = new Cancellable() { private final val DefaultClearer : Cancellable = new Cancellable() {
@ -77,11 +103,33 @@ object HackClearActor {
override def isCancelled : Boolean = true 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) 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() 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)
} }

View file

@ -6,21 +6,65 @@ import scodec.Codec
import scodec.codecs._ import scodec.codecs._
/** /**
* * An `Enumeration` of the various states and activities of the hacking process.
* @param unk1 na * These values are closely tied to the condition of the hacking progress bar and/or the condition of the hacked object.<br>
* @param unk2 na * <br>
* @param unk3 na * `Start` initially displays the hacking progress bar.<br>
* @param unk4 na * `Ongoing` is a neutral state that keeps the progress bar displayed while its value updates. (unconfirmed?)<br>
* @param unk5 na * `Finished` disposes of the hacking progress bar. It does not, by itself, mean the hack was successful.<br>
* @param unk6 na * `Hacked` modifies the target of the hack.<br>
* @param unk7 na * `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.<br>
* <br>
* 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, final case class HackMessage(unk1 : Int,
unk2 : Int, target_guid : PlanetSideGUID,
unk3 : Int, player_guid : PlanetSideGUID,
unk4 : Int, progress : Int,
unk5 : Long, unk5 : Long,
unk6 : Int, hack_state : HackState.Value,
unk7 : Long) unk7 : Long)
extends PlanetSideGamePacket { extends PlanetSideGamePacket {
type Packet = HackMessage type Packet = HackMessage
@ -30,12 +74,12 @@ final case class HackMessage(unk1 : Int,
object HackMessage extends Marshallable[HackMessage] { object HackMessage extends Marshallable[HackMessage] {
implicit val codec : Codec[HackMessage] = ( implicit val codec : Codec[HackMessage] = (
("unk1" | uint2L) :: ("unk1" | uint2L) ::
("unk2" | uint16L) :: ("object_guid" | PlanetSideGUID.codec) ::
("unk3" | uint16L) :: ("player_guid" | PlanetSideGUID.codec) ::
("unk4" | uint8L) :: ("progress" | uint8L) ::
("unk5" | uint32L) :: ("unk5" | uint32L) ::
("unk6" | uint8L) :: ("hack_state" | HackState.codec) ::
("unk7" | uint32L) ("unk7" | uint32L)
).as[HackMessage] ).as[HackMessage]
} }

View file

@ -12,13 +12,13 @@ class HackMessageTest extends Specification {
"decode" in { "decode" in {
PacketCoding.DecodePacket(string).require match { 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 unk1 mustEqual 0
unk2 mustEqual 1024 target_guid mustEqual PlanetSideGUID(1024)
unk3 mustEqual 3607 player_guid mustEqual PlanetSideGUID(3607)
unk4 mustEqual 0 progress mustEqual 0
unk5 mustEqual 3212836864L unk5 mustEqual 3212836864L
unk6 mustEqual 1 hack_state mustEqual HackState.Start
unk7 mustEqual 8L unk7 mustEqual 8L
case _ => case _ =>
ko ko
@ -26,7 +26,7 @@ class HackMessageTest extends Specification {
} }
"encode" in { "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 val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string pkt mustEqual string
} }

View file

@ -229,8 +229,8 @@ object PsLogin {
LocalObject(DoorObjectBuilder(door, 332)) LocalObject(DoorObjectBuilder(door, 332))
LocalObject(DoorObjectBuilder(door, 372)) LocalObject(DoorObjectBuilder(door, 372))
LocalObject(DoorObjectBuilder(door, 373)) LocalObject(DoorObjectBuilder(door, 373))
LocalObject(IFFLockObjectBuilder(external_lock, 556)) LocalObject(IFFLockObjectBuilder(lock_external, 556))
LocalObject(IFFLockObjectBuilder(external_lock, 558)) LocalObject(IFFLockObjectBuilder(lock_external, 558))
LocalObject(TerminalObjectBuilder(cert_terminal, 186)) LocalObject(TerminalObjectBuilder(cert_terminal, 186))
LocalObject(TerminalObjectBuilder(cert_terminal, 187)) LocalObject(TerminalObjectBuilder(cert_terminal, 187))
LocalObject(TerminalObjectBuilder(cert_terminal, 188)) LocalObject(TerminalObjectBuilder(cert_terminal, 188))

View file

@ -230,11 +230,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
case LocalServiceResponse.HackClear(target_guid, unk1, unk2) => case LocalServiceResponse.HackClear(target_guid, unk1, unk2) =>
log.info(s"Clear hack of $target_guid") 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) => case LocalServiceResponse.HackObject(target_guid, unk1, unk2) =>
if(player.GUID != guid) { 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 progressBarUpdate.cancel
if(progressBarValue.isDefined) { if(progressBarValue.isDefined) {
val progressBarVal : Float = progressBarValue.get + delta val progressBarVal : Float = progressBarValue.get + delta
val vis = if(progressBarVal == 0L) { val vis = if(progressBarVal == 0L) { //hack state for progress bar visibility
1 HackState.Start
} }
else if(progressBarVal >= 100L) { else if(progressBarVal > 100L) {
4 HackState.Finished
} }
else { else {
3 HackState.Ongoing
} }
sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(1, target.GUID.guid, player.GUID.guid, progressBarVal.toInt, 0L, vis, 8L))) sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(1, target.GUID, player.GUID, progressBarVal.toInt, 0L, vis, 8L)))
if(progressBarVal > 100) { if(progressBarVal > 100) { //done
progressBarValue = None progressBarValue = None
log.info(s"Hacked a $target") 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() completeAction()
} }
else { else { //continue next tick
tickAction.getOrElse(() => Unit)() tickAction.getOrElse(() => Unit)()
progressBarValue = Some(progressBarVal) progressBarValue = Some(progressBarVal)
import scala.concurrent.duration._ 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) // TODO: Not all incoming UseItemMessage's respond with another UseItemMessage (i.e. doors only send out GenericObjectStateMsg)
continent.GUID(object_guid) match { continent.GUID(object_guid) match {
case Some(door : Door) => 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) => case Some(lock_guid) =>
val lock_hacked = continent.GUID(lock_guid).get.asInstanceOf[IFFLock].HackedBy match { val lock_hacked = continent.GUID(lock_guid).get.asInstanceOf[IFFLock].HackedBy match {
case Some((tplayer, _)) => case Some((tplayer, _, _)) =>
tplayer.Faction == player.Faction tplayer.Faction == player.Faction
case None => case None =>
false false
@ -1042,7 +1042,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
door.Actor ! Door.Use(player, msg) door.Actor ! Door.Use(player, msg)
} }
case None => 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) door.Actor ! Door.Use(player, msg)
} }
} }
@ -1054,13 +1054,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.Slot(player.DrawnSlot).Equipment match { player.Slot(player.DrawnSlot).Equipment match {
case Some(tool : SimpleItem) => case Some(tool : SimpleItem) =>
if(tool.Definition == GlobalDefinitions.remote_electronics_kit) { 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) 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 _ => ; case _ => ;
} }
log.info("Hacking a door~")
//TODO get player hack level (for now, presume 15s in internals of 4/s)
case Some(obj : PlanetSideGameObject) => case Some(obj : PlanetSideGameObject) =>
if(itemType != 121) { 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, PlanetsideAttributeMessage(avatar_guid, 0, 100))) // avatar with 100 hp
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(PlanetSideGUID(unk1), 2))) 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 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) 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) = { def failWithError(error : String) = {
@ -1660,6 +1666,18 @@ object WorldSessionActor {
private final case class PlayerFailedToLoad(tplayer : Player) private final case class PlayerFailedToLoad(tplayer : Player)
private final case class ListAccountCharacters() private final case class ListAccountCharacters()
private final case class SetCurrentAvatar(tplayer : Player) 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, private final case class ItemHacking(tplayer : Player,
target : PlanetSideServerObject, target : PlanetSideServerObject,
tool_guid : PlanetSideGUID, tool_guid : PlanetSideGUID,