Router and Telepad (#232)

* added short and detailed codecs for Telepad, and converter for switching between Telepad and packets; modified Utility to construct a formal TeleportPadTerminal that associates the Telepad with a Router; can now pull telepad Equipment from Router terminal utilities

* this code supports operation of the deployable-side telepad

* basic teleportation between the Router and the remote telepad is functional; an ever-present internal telepad utility was added to the Router to facilitate this teleportation

* breakdown of Router teleportation code for the purposes of client synchronization; supports Router being set up first or Telepad being set up first, either order; background timer for eventual activation of Router

* refactored the router telepad system to remove unecessary function diversions; telepads now are properly linked where appropriate and links are properly broken in appropriate situations; message passing has been simplified

* fixed inventory size of the telepad; transferred router telepad activation systems from VehicleService to LocalService, and from the Ruoter to the TelepadDeployable; cleaned up code that handled the TelepadDeployable the Router relationship in WSA

* tests, mostly; no-router conditions for certain telepad-related converters

* adjusted (made constant) quantities for owned telepads

* resolved issue with router and telepad crashes; properly handles deployment states of AMS and Router upon zone entry; changed ownership/decon requirements to ownerless only
This commit is contained in:
Fate-JH 2018-10-17 12:36:43 -04:00 committed by GitHub
parent 3270ec87b8
commit 9340777c00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 2057 additions and 251 deletions

View file

@ -22,7 +22,8 @@ object Deployables {
DeployedItem.portable_manned_turret_nc -> { ()=> new TurretDeployable(GlobalDefinitions.portable_manned_turret_nc) },
DeployedItem.portable_manned_turret_tr -> { ()=> new TurretDeployable(GlobalDefinitions.portable_manned_turret_tr) },
DeployedItem.portable_manned_turret_vs -> { ()=> new TurretDeployable(GlobalDefinitions.portable_manned_turret_vs) },
DeployedItem.deployable_shield_generator -> { ()=> new ShieldGeneratorDeployable(GlobalDefinitions.deployable_shield_generator) }
DeployedItem.deployable_shield_generator -> { ()=> new ShieldGeneratorDeployable(GlobalDefinitions.deployable_shield_generator) },
DeployedItem.router_telepad_deployable -> { () => new TelepadDeployable(GlobalDefinitions.router_telepad_deployable) }
).withDefaultValue( { ()=> new ExplosiveDeployable(GlobalDefinitions.boomer) } )
}
}

View file

@ -642,6 +642,8 @@ object GlobalDefinitions {
val advanced_ace = ConstructionItemDefinition(CItem.advanced_ace)
val router_telepad = ConstructionItemDefinition(CItem.router_telepad)
val fury_weapon_systema = ToolDefinition(ObjectClass.fury_weapon_systema)
val quadassault_weapon_system = ToolDefinition(ObjectClass.quadassault_weapon_system)
@ -859,6 +861,10 @@ object GlobalDefinitions {
val portable_manned_turret_vs = TurretDeployableDefinition(DeployedItem.portable_manned_turret_vs)
val deployable_shield_generator = new ShieldGeneratorDefinition
val router_telepad_deployable = DeployableDefinition(DeployedItem.router_telepad_deployable)
val internal_router_telepad_deployable = DeployableDefinition(DeployedItem.router_telepad_deployable)
init_deployables()
/*
@ -894,6 +900,8 @@ object GlobalDefinitions {
val respawn_tube_tower = new SpawnTubeDefinition(733)
val teleportpad_terminal = new TeleportPadTerminalDefinition
val adv_med_terminal = new MedicalTerminalDefinition(38)
val crystals_health_a = new MedicalTerminalDefinition(225)
@ -4059,6 +4067,13 @@ object GlobalDefinitions {
advanced_ace.Modes(2).Item(DeployedItem.deployable_shield_generator -> Set(CertificationType.AssaultEngineering))
advanced_ace.Tile = InventoryTile.Tile93
router_telepad.Name = "router_telepad"
router_telepad.Size = EquipmentSize.Pistol
router_telepad.Modes += new ConstructionFireMode
router_telepad.Modes.head.Item(DeployedItem.router_telepad_deployable -> Set(CertificationType.GroundSupport))
router_telepad.Tile = InventoryTile.Tile33
router_telepad.Packet = new TelepadConverter
fury_weapon_systema.Name = "fury_weapon_systema"
fury_weapon_systema.Size = EquipmentSize.VehicleWeapon
fury_weapon_systema.AmmoTypes += hellfire_ammo
@ -5121,6 +5136,8 @@ object GlobalDefinitions {
router.MaxShields = 800 + 1
router.Seats += 0 -> new SeatDefinition()
router.MountPoints += 1 -> 0
router.Utilities += 1 -> UtilityType.teleportpad_terminal
router.Utilities += 2 -> UtilityType.internal_router_telepad_deployable
router.TrunkSize = InventoryTile.Tile1511
router.TrunkOffset = 30
router.Deployment = true
@ -5515,5 +5532,16 @@ object GlobalDefinitions {
deployable_shield_generator.MaxHealth = 1700
deployable_shield_generator.DeployTime = Duration.create(6000, "ms")
deployable_shield_generator.Model = StandardResolutions.ComplexDeployables
router_telepad_deployable.Name = "router_telepad_deployable"
router_telepad_deployable.MaxHealth = 100
router_telepad_deployable.DeployTime = Duration.create(1, "ms")
router_telepad_deployable.Packet = new TelepadDeployableConverter
router_telepad_deployable.Model = StandardResolutions.SimpleDeployables
internal_router_telepad_deployable.Name = "router_telepad_deployable"
internal_router_telepad_deployable.MaxHealth = 1
internal_router_telepad_deployable.DeployTime = Duration.create(1, "ms")
internal_router_telepad_deployable.Packet = new InternalTelepadDeployableConverter
}
}

View file

@ -0,0 +1,14 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
import net.psforever.objects.ce.TelepadLike
import net.psforever.objects.definition.ConstructionItemDefinition
class Telepad(private val cdef : ConstructionItemDefinition) extends ConstructionItem(cdef)
with TelepadLike
object Telepad {
def apply(cdef : ConstructionItemDefinition) : Telepad = {
new Telepad(cdef)
}
}

View file

@ -0,0 +1,8 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
import net.psforever.objects.ce.{SimpleDeployable, TelepadLike}
import net.psforever.objects.definition.DeployableDefinition
class TelepadDeployable(ddef : DeployableDefinition) extends SimpleDeployable(ddef)
with TelepadLike

View file

@ -378,7 +378,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
def Utilities : Map[Int, Utility] = utilities
/**
* Get a referenece ot a certain `Utility` attached to this `Vehicle`.
* Get a reference to a certain `Utility` attached to this `Vehicle`.
* @param utilNumber the attachment number of the `Utility`
* @return the `Utility` or `None` (if invalid)
*/
@ -396,6 +396,15 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
}
}
def Utility(utilType : UtilityType.Value) : Option[PlanetSideServerObject] = {
utilities.values.find(_.UtilType == utilType) match {
case Some(util) =>
Some(util())
case None =>
None
}
}
override def DeployTime = Definition.DeployTime
override def UndeployTime = Definition.UndeployTime

View file

@ -29,12 +29,13 @@ class DeployableToolbox {
* keys: categories, values: quantity storage object
*/
private val categoryCounts = DeployableCategory.values.toSeq.map(value => { value -> new DeployableToolbox.Bin }).toMap
//)
categoryCounts(DeployableCategory.Telepads).Max = 1024
/**
* a map of bins for keeping track of the quantities of individual deployables
* keys: deployable types, values: quantity storage object
*/
private val deployableCounts = DeployedItem.values.toSeq.map(value => { value -> new DeployableToolbox.Bin }).toMap
deployableCounts(DeployedItem.router_telepad_deployable).Max = 1024
/**
* a map of tracked/owned individual deployables
* keys: categories, values: deployable objects
@ -523,8 +524,8 @@ object DeployableToolbox {
}
}
if(certifications.contains(CertificationType.GroundSupport)) {
counts(DeployedItem.router_telepad_deployable).Max = 1
categories(DeployableCategory.Telepads).Max = 1
counts(DeployedItem.router_telepad_deployable).Max = 1024
categories(DeployableCategory.Telepads).Max = 1024
}
}
@ -589,9 +590,9 @@ object DeployableToolbox {
AddToDeployableQuantities(counts, categories, FortificationEngineering, certificationSet ++ Set(FortificationEngineering))
}
case GroundSupport =>
counts(DeployedItem.router_telepad_deployable).Max = 1024
categories(DeployableCategory.Telepads).Max = 1024
// case GroundSupport =>
// counts(DeployedItem.router_telepad_deployable).Max = 1024
// categories(DeployableCategory.Telepads).Max = 1024
case _ => ;
}
@ -657,9 +658,9 @@ object DeployableToolbox {
RemoveFromDeployablesQuantities(counts, categories, FortificationEngineering, certificationSet)
}
case GroundSupport =>
counts(DeployedItem.router_telepad_deployable).Max = 0
categories(DeployableCategory.Telepads).Max = 0
// case GroundSupport =>
// counts(DeployedItem.router_telepad_deployable).Max = 0
// categories(DeployableCategory.Telepads).Max = 0
case _ => ;
}

View file

@ -118,7 +118,8 @@ object Deployable {
DeployedItem.portable_manned_turret_tr.id -> DeployableIcon.FieldTurret,
DeployedItem.portable_manned_turret_nc.id -> DeployableIcon.FieldTurret,
DeployedItem.portable_manned_turret_vs.id -> DeployableIcon.FieldTurret,
DeployedItem.deployable_shield_generator.id -> DeployableIcon.AegisShieldGenerator
DeployedItem.deployable_shield_generator.id -> DeployableIcon.AegisShieldGenerator,
DeployedItem.router_telepad_deployable.id -> DeployableIcon.RouterTelepad
).withDefaultValue(DeployableIcon.Boomer)
}

View file

@ -0,0 +1,85 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.ce
import akka.actor.ActorContext
import net.psforever.objects.{PlanetSideGameObject, TelepadDeployable, Vehicle}
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.objects.vehicles.Utility
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
trait TelepadLike {
private var router : Option[PlanetSideGUID] = None
private var activated : Boolean = false
def Router : Option[PlanetSideGUID] = router
def Router_=(rguid : PlanetSideGUID) : Option[PlanetSideGUID] = Router_=(Some(rguid))
def Router_=(rguid : Option[PlanetSideGUID]) : Option[PlanetSideGUID] = {
router match {
case None =>
router = rguid
case Some(_) =>
if(rguid.isEmpty || rguid.contains(PlanetSideGUID(0))) {
router = None
}
}
Router
}
def Active : Boolean = activated
def Active_=(state : Boolean) : Boolean = {
activated = state
Active
}
}
object TelepadLike {
final case class Activate(obj : PlanetSideGameObject with TelepadLike)
final case class Deactivate(obj : PlanetSideGameObject with TelepadLike)
/**
* Assemble some logic for a provided object.
* @param obj an `Amenity` object;
* anticipating a `Terminal` object using this same definition
* @param context hook to the local `Actor` system
*/
def Setup(obj : Amenity, context : ActorContext) : Unit = {
obj.asInstanceOf[TelepadLike].Router = obj.Owner.GUID
}
/**
* An analysis of the active system of teleportation utilized by Router vehicles.
* Information about the two endpoints - an internal telepad and a remote telepad - are collected, if they are applicable.
* The vehicle "Router" itself must be in the drive state of `Deployed`.
* @param router the vehicle that serves as the container of an internal telepad unit
* @param zone where the router is located
* @return the pair of units that compose the teleportation system
*/
def AppraiseTeleportationSystem(router : Vehicle, zone : Zone) : Option[(Utility.InternalTelepad, TelepadDeployable)] = {
import net.psforever.objects.vehicles.UtilityType
import net.psforever.types.DriveState
router.Utility(UtilityType.internal_router_telepad_deployable) match {
//if the vehicle has an internal telepad, it is allowed to be a Router (that's a weird way of saying it)
case Some(util : Utility.InternalTelepad) =>
//check for a readied remote telepad
zone.GUID(util.Telepad) match {
case Some(telepad : TelepadDeployable) =>
//determine whether to activate both the Router's internal telepad and the deployed remote telepad
if(router.DeploymentState == DriveState.Deployed && util.Active && telepad.Active) {
Some((util, telepad))
}
else {
None
}
case _ =>
None
}
case _ =>
None
}
}
}

View file

@ -0,0 +1,14 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition.converter
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.ce.TelepadLike
import net.psforever.packet.game.objectcreate._
import scala.util.{Success, Try}
class InternalTelepadDeployableConverter extends ObjectCreateConverter[PlanetSideGameObject with TelepadLike]() {
override def ConstructorData(obj : PlanetSideGameObject with TelepadLike) : Try[ContainedTelepadDeployableData] = {
Success(ContainedTelepadDeployableData(101, obj.Router.get))
}
}

View file

@ -0,0 +1,27 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition.converter
import net.psforever.objects.Telepad
import net.psforever.packet.game.objectcreate._
import scala.util.{Failure, Success, Try}
class TelepadConverter extends ObjectCreateConverter[Telepad]() {
override def ConstructorData(obj : Telepad) : Try[TelepadData] = {
obj.Router match {
case Some(_) =>
Success(TelepadData (0, obj.Router))
case None =>
Failure(new IllegalStateException("TelepadConverter: telepad needs to know id of its router"))
}
}
override def DetailedConstructorData(obj : Telepad) : Try[DetailedTelepadData] = {
obj.Router match {
case Some(_) =>
Success(DetailedTelepadData (0, obj.Router))
case None =>
Failure(new IllegalStateException("TelepadConverter: telepad needs to know id of its router"))
}
}
}

View file

@ -0,0 +1,46 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition.converter
import net.psforever.objects.TelepadDeployable
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate._
import scala.util.{Failure, Success, Try}
class TelepadDeployableConverter extends ObjectCreateConverter[TelepadDeployable]() {
override def ConstructorData(obj : TelepadDeployable) : Try[TelepadDeployableData] = {
if(obj.Router.isEmpty || obj.Router.contains(PlanetSideGUID(0))) {
Failure(new IllegalStateException("TelepadDeployableConverter: telepad deployable needs to know id of its router"))
}
else {
if(obj.Health > 0) {
Success(TelepadDeployableData(
PlacementData(obj.Position, obj.Orientation),
obj.Faction,
bops = false,
destroyed = false,
unk1 = 2,
unk2 = true,
obj.Router.get,
obj.Owner.getOrElse(PlanetSideGUID(0)),
unk3 = 87,
unk4 = 12
))
}
else {
Success(TelepadDeployableData(
PlacementData(obj.Position, obj.Orientation),
obj.Faction,
bops = false,
destroyed = true,
unk1 = 2,
unk2 = true,
obj.Router.get,
owner_guid = PlanetSideGUID(0),
unk3 = 0,
unk4 = 6
))
}
}
}
}

View file

@ -212,6 +212,10 @@ object EquipmentTerminalDefinition {
"command_detonater" -> MakeSimpleItem(command_detonater),
"flail_targeting_laser" -> MakeSimpleItem(flail_targeting_laser)
)
/**
* A single-element `Map` of the one piece of `Equipment` specific to the Router.
*/
val routerTerminal : Map[String, () => Equipment] = Map("router_telepad" -> MakeTelepad(router_telepad))
/**
* Create a new `Tool` from provided `EquipmentDefinition` objects.
@ -334,6 +338,13 @@ object EquipmentTerminalDefinition {
*/
private def MakeConstructionItem(cdef : ConstructionItemDefinition)() : ConstructionItem = ConstructionItem(cdef)
/**
* na
* @param cdef na
* @return na
*/
private def MakeTelepad(cdef : ConstructionItemDefinition)() : Telepad = Telepad(cdef)
/**
* Accept a simplified blueprint for some piece of `Equipment` and create an actual piece of `Equipment` based on it.
* Used specifically for the reconstruction of `Equipment` via an `Loadout`.

View file

@ -0,0 +1,30 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import akka.actor.ActorContext
import net.psforever.objects.Player
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.ItemTransactionMessage
class TeleportPadTerminalDefinition extends EquipmentTerminalDefinition(853) {
Name = "teleport_pad_terminal"
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
Terminal.BuyEquipment(EquipmentTerminalDefinition.routerTerminal("router_telepad")())
}
}
object TeleportPadTerminalDefinition {
/**
* Assemble some logic for a provided object.
* @param obj an `Amenity` object;
* anticipating a `Terminal` object using this same definition
* @param context hook to the local `Actor` system
*/
def Setup(obj : Amenity, context : ActorContext) : Unit = {
import akka.actor.{ActorRef, Props}
if(obj.Actor == ActorRef.noSender) {
obj.Actor = context.actorOf(Props(classOf[TerminalControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}")
}
}
}

View file

@ -40,13 +40,13 @@ class Terminal(tdef : TerminalDefinition) extends Amenity with Hackable {
if(Faction == player.Faction || HackedBy.isDefined) {
msg.transaction_type match {
case TransactionType.Buy | TransactionType.Learn =>
tdef.Buy(player, msg)
Buy(player, msg)
case TransactionType.Sell =>
tdef.Sell(player, msg)
Sell(player, msg)
case TransactionType.Loadout =>
tdef.Loadout(player, msg)
Loadout(player, msg)
case _ =>
Terminal.NoDeal()
@ -57,6 +57,18 @@ class Terminal(tdef : TerminalDefinition) extends Amenity with Hackable {
}
}
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
tdef.Buy(player, msg)
}
def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
tdef.Sell(player, msg)
}
def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
tdef.Loadout(player, msg)
}
def Definition : TerminalDefinition = tdef
}

View file

@ -2,10 +2,13 @@
package net.psforever.objects.vehicles
import akka.actor.ActorContext
import net.psforever.objects.{GlobalDefinitions, Vehicle}
import net.psforever.objects.definition.DeployableDefinition
import net.psforever.objects._
import net.psforever.objects.ce.TelepadLike
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, OrderTerminalABDefinition, Terminal, TerminalDefinition}
import net.psforever.objects.serverobject.terminals._
import net.psforever.objects.serverobject.tube.{SpawnTube, SpawnTubeDefinition}
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
/**
* An `Enumeration` of the available vehicular utilities.<br>
@ -21,7 +24,9 @@ object UtilityType extends Enumeration {
ams_respawn_tube,
matrix_terminalc,
order_terminala,
order_terminalb
order_terminalb,
teleportpad_terminal,
internal_router_telepad_deployable
= Value
}
@ -94,13 +99,17 @@ object Utility {
new TerminalUtility(GlobalDefinitions.order_terminala)
case UtilityType.order_terminalb =>
new TerminalUtility(GlobalDefinitions.order_terminalb)
case UtilityType.teleportpad_terminal =>
new TeleportPadTerminalUtility(GlobalDefinitions.teleportpad_terminal)
case UtilityType.internal_router_telepad_deployable =>
new InternalTelepad(GlobalDefinitions.internal_router_telepad_deployable)
}
/**
* Override for `SpawnTube` objects so that they inherit the spatial characteristics of their `Owner`.
* @param tubeDef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/
private class SpawnTubeUtility(tubeDef : SpawnTubeDefinition) extends SpawnTube(tubeDef) {
class SpawnTubeUtility(tubeDef : SpawnTubeDefinition) extends SpawnTube(tubeDef) {
override def Position = Owner.Position
override def Orientation = Owner.Orientation
}
@ -109,11 +118,60 @@ object Utility {
* Override for `Terminal` objects so that they inherit the spatial characteristics of their `Owner`.
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/
private class TerminalUtility(tdef : TerminalDefinition) extends Terminal(tdef) {
class TerminalUtility(tdef : TerminalDefinition) extends Terminal(tdef) {
override def Position = Owner.Position
override def Orientation = Owner.Orientation
}
/**
* na
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/
class TeleportPadTerminalUtility(tdef : TerminalDefinition) extends TerminalUtility(tdef) {
/**
* na
* @param player na
* @param msg na
* @return na
*/
override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
val reply = super.Buy(player, msg)
reply match {
case Terminal.BuyEquipment(obj : Telepad) =>
obj.Router = Owner.GUID
case _ => ;
}
reply
}
}
/**
* The internal telepad is a component that is contained by the Router when it deploys
* and allows it to serve as one of the terminal points of a Router-telepad teleportation system.
* @param ddef na
*/
class InternalTelepad(ddef : DeployableDefinition) extends Amenity
with TelepadLike {
/** a link to the telepad that serves as the other endpoint of this teleportation system */
private var activeTelepad : Option[PlanetSideGUID] = None
def Telepad : Option[PlanetSideGUID] = activeTelepad
def Telepad_=(rguid : PlanetSideGUID) : Option[PlanetSideGUID] = Telepad_=(Some(rguid))
def Telepad_=(rguid : Option[PlanetSideGUID]) : Option[PlanetSideGUID] = {
activeTelepad = rguid
Telepad
}
override def Position = Owner.Position
override def Orientation = Owner.Orientation
/** the router is the owner */
override def Router : Option[PlanetSideGUID] = Some(Owner.GUID)
def Definition = ddef
}
/**
* Provide the called-out object's logic.
* @param util the type of the `Amenity` object
@ -128,5 +186,11 @@ object Utility {
OrderTerminalABDefinition.Setup
case UtilityType.order_terminalb =>
OrderTerminalABDefinition.Setup
case UtilityType.teleportpad_terminal =>
TeleportPadTerminalDefinition.Setup
case UtilityType.internal_router_telepad_deployable =>
TelepadLike.Setup
}
//private def defaultSetup(o1 : Amenity, o2 : ActorContext) : Unit = { }
}

View file

@ -232,6 +232,21 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
}
}
/**
* Recover an object from the globally unique identifier system by the number that was assigned previously.
* @param object_guid the globally unique identifier requested
* @return the associated object, if it exists
* @see `GUID(Int)`
*/
def GUID(object_guid : Option[PlanetSideGUID]) : Option[PlanetSideGameObject] = {
object_guid match {
case Some(oguid) =>
GUID(oguid.guid)
case None =>
None
}
}
/**
* Recover an object from the globally unique identifier system by the number that was assigned previously.
* @param object_guid the globally unique identifier requested

View file

@ -0,0 +1,37 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import net.psforever.packet.game.PlanetSideGUID
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
/**
* na
*/
final case class ContainedTelepadDeployableData(unk : Int,
router_guid : PlanetSideGUID) extends ConstructorData {
override def bitsize : Long = 59L
}
object ContainedTelepadDeployableData extends Marshallable[ContainedTelepadDeployableData] {
implicit val codec : Codec[ContainedTelepadDeployableData] = (
("unk" | uint(7)) ::
("router_guid" | PlanetSideGUID.codec) ::
uint16 ::
uint4 ::
uint16
).exmap[ContainedTelepadDeployableData] (
{
case unk :: rguid :: 0 :: 8 :: 0 :: HNil =>
Attempt.successful(ContainedTelepadDeployableData(unk, rguid))
case _ :: _ :: _ :: _ :: _ :: HNil =>
Attempt.failure(Err("invalid rek data format"))
},
{
case ContainedTelepadDeployableData(unk, rguid) =>
Attempt.successful(unk :: rguid :: 0 :: 8 :: 0 :: HNil)
}
)
}

View file

@ -0,0 +1,48 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import net.psforever.packet.game.PlanetSideGUID
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
/**
* A representation of the telepad portion of `ObjectCreateDetailedMessage` packet data.
* This data will help construct the "cosntruction tool"
* that can be obtained from the Router vehicle - the Router telepad.
* It issued to construct a bidirectional teleportation point associated with a Router if that Router is deployed.
* @param unk na
* @param router_guid the Router
*/
final case class DetailedTelepadData(unk : Int, router_guid : Option[PlanetSideGUID]) extends ConstructorData {
override def bitsize : Long = {
val rguidSize = if(router_guid.nonEmpty) 16 else 0
51L + rguidSize
}
}
object DetailedTelepadData extends Marshallable[DetailedTelepadData] {
def apply(unk : Int) : DetailedTelepadData = DetailedTelepadData(unk, None)
def apply(unk : Int, router_guid : PlanetSideGUID) : DetailedTelepadData = DetailedTelepadData(unk, Some(router_guid))
implicit val codec : Codec[DetailedTelepadData] = (
("unk" | uint(6)) ::
optional(bool, "router_guid" | PlanetSideGUID.codec) ::
uint(24) ::
uint(18) ::
uint2
).exmap[DetailedTelepadData] (
{
case unk :: rguid :: 1 :: 1 :: 0 :: HNil =>
Attempt.successful(DetailedTelepadData(unk, rguid))
case _ =>
Attempt.failure(Err("invalid detailed telepad format"))
},
{
case DetailedTelepadData(unk, rguid) =>
Attempt.successful(unk :: rguid :: 1 :: 1 :: 0 :: HNil)
}
)
}

View file

@ -230,8 +230,6 @@ object ObjectClass {
final val repeater = 730
final val rocklet = 737
final val rotarychaingun_mosquito = 740
final val router_telepad = 743
final val router_telepad_deployable = 744
final val scythe = 747
final val six_shooter = 761
final val skyguard_weapon_system = 788
@ -276,7 +274,7 @@ object ObjectClass {
final val nano_dispenser = 577
final val command_detonater = 213
final val flail_targeting_laser = 297
//ace deployables
//deployables
final val ace = 32
final val advanced_ace = 39
final val boomer = 148
@ -284,6 +282,8 @@ object ObjectClass {
final val he_mine = 388
final val jammer_mine = 420
final val motionalarmsensor = 575
final val router_telepad = 743
final val router_telepad_deployable = 744
final val sensor_shield = 752
final val spitfire_aa = 819
final val spitfire_cloaked = 825
@ -602,7 +602,6 @@ object ObjectClass {
case ObjectClass.repeater => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.rocklet => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
// case ObjectClass.rotarychaingun_mosquito => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.router_telepad => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon") //TODO belongs here?
case ObjectClass.scythe => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
// case ObjectClass.skyguard_weapon_system => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
case ObjectClass.spiker => ConstructorData.genericCodec(DetailedWeaponData.codec, "weapon")
@ -660,11 +659,11 @@ object ObjectClass {
case ObjectClass.medicalapplicator => ConstructorData.genericCodec(DetailedWeaponData.codec, "tool")
case ObjectClass.nano_dispenser => ConstructorData.genericCodec(DetailedWeaponData.codec, "tool")
case ObjectClass.remote_electronics_kit => ConstructorData.genericCodec(DetailedREKData.codec, "tool")
//case ObjectClass.router_telepad => ConstructorData.genericCodec(*.codec, "tool") //TODO
case ObjectClass.trek => ConstructorData.genericCodec(DetailedWeaponData.codec, "tool")
//ace deployable
case ObjectClass.ace => ConstructorData.genericCodec(DetailedACEData.codec, "ace")
case ObjectClass.advanced_ace => ConstructorData.genericCodec(DetailedACEData.codec, "advanced ace")
case ObjectClass.router_telepad => ConstructorData.genericCodec(DetailedTelepadData.codec, "router telepad")
case ObjectClass.boomer_trigger => ConstructorData.genericCodec(DetailedBoomerTriggerData.codec, "boomer trigger")
//other
case ObjectClass.avatar => ConstructorData.genericCodec(DetailedPlayerData.codec(false), "avatar")
@ -951,11 +950,12 @@ object ObjectClass {
case ObjectClass.medicalapplicator => ConstructorData.genericCodec(WeaponData.codec, "tool")
case ObjectClass.nano_dispenser => ConstructorData.genericCodec(WeaponData.codec, "tool")
case ObjectClass.remote_electronics_kit => ConstructorData.genericCodec(REKData.codec, "tool")
//case ObjectClass.router_telepad => ConstructorData.genericCodec(WeaponData.codec, "tool") //TODO
case ObjectClass.trek => ConstructorData.genericCodec(WeaponData.codec, "tool")
//ace deployables
//deployables
case ObjectClass.ace => ConstructorData.genericCodec(ACEData.codec, "ace")
case ObjectClass.advanced_ace => ConstructorData.genericCodec(ACEData.codec, "advanced ace")
case ObjectClass.router_telepad => ConstructorData.genericCodec(TelepadData.codec, "router telepad")
case ObjectClass.router_telepad_deployable => ConstructorData.genericCodec(ContainedTelepadDeployableData.codec, "router telepad")
case ObjectClass.boomer_trigger => ConstructorData.genericCodec(BoomerTriggerData.codec, "boomer trigger")
//vehicles?
case ObjectClass.orbital_shuttle => ConstructorData.genericCodec(OrbitalShuttleData.codec, "HART")
@ -1182,11 +1182,11 @@ object ObjectClass {
case ObjectClass.medicalapplicator => DroppedItemData.genericCodec(WeaponData.codec, "tool")
case ObjectClass.nano_dispenser => DroppedItemData.genericCodec(WeaponData.codec, "tool")
case ObjectClass.remote_electronics_kit => DroppedItemData.genericCodec(REKData.codec, " tool")
//case ObjectClass.router_telepad => DroppedItemData.genericCodec(WeaponData.codec, "tool") //TODO
case ObjectClass.trek => DroppedItemData.genericCodec(WeaponData.codec, "tool")
//ace deployables
//deployables
case ObjectClass.ace => DroppedItemData.genericCodec(ACEData.codec, "ace")
case ObjectClass.advanced_ace => DroppedItemData.genericCodec(ACEData.codec, "advanced ace") //todo temporary?
case ObjectClass.advanced_ace => DroppedItemData.genericCodec(ACEData.codec, "advanced ace")
case ObjectClass.router_telepad => DroppedItemData.genericCodec(TelepadData.codec, "router telepad") //TODO not correct
case ObjectClass.boomer_trigger => DroppedItemData.genericCodec(BoomerTriggerData.codec, "boomer trigger")
case ObjectClass.boomer => ConstructorData.genericCodec(SmallDeployableData.codec, "ace deployable")
case ObjectClass.he_mine => ConstructorData.genericCodec(SmallDeployableData.codec, "ace deployable")
@ -1202,6 +1202,7 @@ object ObjectClass {
case ObjectClass.portable_manned_turret_nc => ConstructorData.genericCodec(OneMannedFieldTurretData.codec, "field turret")
case ObjectClass.portable_manned_turret_tr => ConstructorData.genericCodec(OneMannedFieldTurretData.codec, "field turret")
case ObjectClass.portable_manned_turret_vs => ConstructorData.genericCodec(OneMannedFieldTurretData.codec, "field turret")
case ObjectClass.router_telepad_deployable => ConstructorData.genericCodec(TelepadDeployableData.codec, "telepad deployable")
//projectiles
case ObjectClass.hunter_seeker_missile_projectile => ConstructorData.genericCodec(TrackedProjectileData.codec, "projectile")
case ObjectClass.oicw_projectile => ConstructorData.genericCodec(TrackedProjectileData.codec, "projectile")

View file

@ -0,0 +1,46 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import net.psforever.packet.game.PlanetSideGUID
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
/**
* A representation of the telepad portion of `ObjectCreateMessage` packet data.
* This data will help construct the "cosntruction tool"
* that can be obtained from the Router vehicle - the Router telepad.
* It issued to construct a bidirectional teleportation point associated with a Router if that Router is deployed.
* @param unk na
* @param router_guid the Router
*/
final case class TelepadData(unk : Int, router_guid : Option[PlanetSideGUID]) extends ConstructorData {
override def bitsize : Long = {
val rguidSize = if(router_guid.nonEmpty) 16 else 0
34L + rguidSize
}
}
object TelepadData extends Marshallable[TelepadData] {
def apply(unk : Int) : TelepadData = TelepadData(unk, None)
def apply(unk : Int, router_guid : PlanetSideGUID) : TelepadData = TelepadData(unk, Some(router_guid))
implicit val codec : Codec[TelepadData] = (
("unk" | uint(6)) ::
optional(bool, "router_guid" | PlanetSideGUID.codec) ::
uint(27)
).exmap[TelepadData] (
{
case unk :: rguid :: 0 :: HNil =>
Attempt.successful(TelepadData(unk, rguid))
case _ =>
Attempt.failure(Err("invalid telepad format"))
},
{
case TelepadData(unk, rguid) =>
Attempt.successful(unk :: rguid :: 0 :: HNil)
}
)
}

View file

@ -0,0 +1,55 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.PlanetSideEmpire
import scodec.Codec
import scodec.codecs._
/**
* A representation of simple objects that are spawned by the adaptive construction engine.
* @param pos na
* @param faction na
* @param bops na
* @param destroyed na
* @param unk1 na
* @param unk2 na
* @param router_guid the associated Router vehicle;
* this is an essential non-blank (16u 0x0) field;
* a blanked field will cause the client to crash
* @param owner_guid the owner of this telepad
* @param unk3 na
* @param unk4 na
*/
//TODO might be CommonFieldData
final case class TelepadDeployableData(pos : PlacementData,
faction : PlanetSideEmpire.Value,
bops : Boolean,
destroyed : Boolean,
unk1 : Int,
unk2 : Boolean,
router_guid : PlanetSideGUID,
owner_guid : PlanetSideGUID,
unk3 : Int,
unk4 : Int) extends ConstructorData {
override def bitsize : Long = {
val posSize = pos.bitsize
59 + posSize
}
}
object TelepadDeployableData extends Marshallable[TelepadDeployableData] {
implicit val codec : Codec[TelepadDeployableData] = (
("pos" | PlacementData.codec) ::
("faction" | PlanetSideEmpire.codec) ::
("bops" | bool) ::
("destroyed" | bool) ::
("unk1" | uint2L) :: //3 - na, 2 - common, 1 - na, 0 - common?
("unk2" | bool) ::
("router_guid" | PlanetSideGUID.codec) ::
("owner_guid" | PlanetSideGUID.codec) ::
("unk3" | uint16L) ::
("unk4" | uint4)
).as[TelepadDeployableData]
}

View file

@ -226,6 +226,7 @@ abstract class RemoverActor extends SupportActor[RemoverActor.Entry] {
* No entries in the first pool.
*/
def ClearAll() : Unit = {
trace("all tasks have been cleared")
firstTask.cancel
firstHeap = Nil
}

View file

@ -1,12 +1,13 @@
// Copyright (c) 2017 PSForever
package services.local
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.{PlanetSideGameObject, TelepadDeployable, Vehicle}
import net.psforever.objects.ce.Deployable
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.terminals.CaptureTerminal
import net.psforever.objects.vehicles.Utility
import net.psforever.objects.zones.Zone
import net.psforever.packet.game._
import net.psforever.types.{PlanetSideEmpire, Vector3}
@ -23,9 +24,11 @@ object LocalAction {
final case class ClearTemporaryHack(player_guid: PlanetSideGUID, target: PlanetSideServerObject with Hackable) extends Action
final case class HackCaptureTerminal(player_guid : PlanetSideGUID, continent : Zone, target : CaptureTerminal, unk1 : Long, unk2 : Long = 8L, isResecured : Boolean) extends Action
final case class ProximityTerminalEffect(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, effectState : Boolean) extends Action
final case class RouterTelepadTransport(player_guid : PlanetSideGUID, passenger_guid : PlanetSideGUID, src_guid : PlanetSideGUID, dest_guid : PlanetSideGUID) extends Action
final case class SetEmpire(object_guid: PlanetSideGUID, empire: PlanetSideEmpire.Value) extends Action
final case class ToggleTeleportSystem(player_guid : PlanetSideGUID, router : Vehicle, systemPlan : Option[(Utility.InternalTelepad, TelepadDeployable)]) extends Action
final case class TriggerEffect(player_guid : PlanetSideGUID, effect : String, target : PlanetSideGUID) extends Action
final case class TriggerEffectInfo(player_guid : PlanetSideGUID, effect : String, target : PlanetSideGUID, unk1 : Boolean, unk2 : Long) extends Action
final case class TriggerEffectLocation(player_guid : PlanetSideGUID, effect : String, pos : Vector3, orient : Vector3) extends Action
final case class TriggerSound(player_guid : PlanetSideGUID, sound : TriggeredSound.Value, pos : Vector3, unk : Int, volume : Float) extends Action
final case class SetEmpire(object_guid: PlanetSideGUID, empire: PlanetSideEmpire.Value) extends Action
}

View file

@ -2,7 +2,8 @@
package services.local
import net.psforever.objects.ce.Deployable
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.vehicles.Utility
import net.psforever.objects.{PlanetSideGameObject, TelepadDeployable, Vehicle}
import net.psforever.packet.game._
import net.psforever.types.{PlanetSideEmpire, Vector3}
@ -19,7 +20,10 @@ object LocalResponse {
final case class HackCaptureTerminal(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long, isResecured: Boolean) extends Response
final case class ObjectDelete(item_guid : PlanetSideGUID, unk : Int) extends Response
final case class ProximityTerminalEffect(object_guid : PlanetSideGUID, effectState : Boolean) extends Response
final case class RouterTelepadMessage(msg : String) extends Response
final case class RouterTelepadTransport(passenger_guid : PlanetSideGUID, src_guid : PlanetSideGUID, dest_guid : PlanetSideGUID) extends Response
final case class SetEmpire(object_guid: PlanetSideGUID, empire: PlanetSideEmpire.Value) extends Response
final case class ToggleTeleportSystem(router : Vehicle, systemPlan : Option[(Utility.InternalTelepad, TelepadDeployable)]) extends Response
final case class TriggerEffect(target: PlanetSideGUID, effect: String, effectInfo: Option[TriggeredEffect] = None, triggeredLocation: Option[TriggeredEffectLocation] = None) extends Response
final case class TriggerSound(sound : TriggeredSound.Value, pos : Vector3, unk : Int, volume : Float) extends Response
final case class SetEmpire(object_guid: PlanetSideGUID, empire: PlanetSideEmpire.Value) extends Response
}

View file

@ -7,18 +7,21 @@ import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.structures.Building
import net.psforever.objects.serverobject.terminals.CaptureTerminal
import net.psforever.objects.zones.{InterstellarCluster, Zone}
import net.psforever.objects.{BoomerDeployable, GlobalDefinitions, PlanetSideGameObject, TurretDeployable}
import net.psforever.objects._
import net.psforever.packet.game.{PlanetSideGUID, TriggeredEffect, TriggeredEffectLocation}
import net.psforever.objects.vital.Vitality
import net.psforever.types.Vector3
import services.local.support.{DeployableRemover, DoorCloseActor, HackClearActor, HackCaptureActor}
import services.local.support._
import services.vehicle.{VehicleAction, VehicleServiceMessage}
import services.{GenericEventBus, Service, ServiceManager}
import services.{GenericEventBus, RemoverActor, Service, ServiceManager}
import scala.util.Success
import scala.concurrent.duration._
import akka.pattern.ask
import net.psforever.objects.vehicles.{Utility, UtilityType}
import services.ServiceManager.Lookup
import services.support.SupportActor
import scala.concurrent.duration.Duration
class LocalService extends Actor {
@ -26,6 +29,7 @@ class LocalService extends Actor {
private val hackClearer = context.actorOf(Props[HackClearActor], "local-hack-clearer")
private val hackCapturer = context.actorOf(Props[HackCaptureActor], "local-hack-capturer")
private val engineer = context.actorOf(Props[DeployableRemover], "deployable-remover-agent")
private val teleportDeployment : ActorRef = context.actorOf(Props[RouterTelepadActivation], "telepad-activate-agent")
private [this] val log = org.log4s.getLogger
var cluster : ActorRef = Actor.noSender
@ -111,10 +115,18 @@ class LocalService extends Actor {
LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.ProximityTerminalEffect(object_guid, effectState))
)
case LocalAction.RouterTelepadTransport(player_guid, passenger_guid, src_guid, dest_guid) =>
LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.RouterTelepadTransport(passenger_guid, src_guid, dest_guid))
)
case LocalAction.SetEmpire(object_guid, empire) =>
LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", Service.defaultPlayerGUID, LocalResponse.SetEmpire(object_guid, empire))
)
case LocalAction.ToggleTeleportSystem(player_guid, router, system_plan) =>
LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.ToggleTeleportSystem(router, system_plan))
)
case LocalAction.TriggerEffect(player_guid, effect, target) =>
LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.TriggerEffect(target, effect))
@ -219,6 +231,12 @@ class LocalService extends Actor {
case _ => ;
}
case DeployableRemover.EliminateDeployable(obj : TelepadDeployable, guid, pos, zone) =>
obj.Active = false
//ClearSpecific will also remove objects that do not have GUID's; we may not have a GUID at this time
teleportDeployment ! SupportActor.ClearSpecific(List(obj), zone)
EliminateDeployable(obj, guid, pos, zone.Id)
case DeployableRemover.EliminateDeployable(obj, guid, pos, zone) =>
EliminateDeployable(obj, guid, pos, zone.Id)
@ -227,6 +245,54 @@ class LocalService extends Actor {
LocalServiceResponse(s"/${zone.Id}/Local", Service.defaultPlayerGUID, LocalResponse.ObjectDelete(trigger_guid, 0))
)
//message to RouterTelepadActivation
case LocalServiceMessage.Telepads(msg) =>
teleportDeployment forward msg
//from RouterTelepadActivation
case RouterTelepadActivation.ActivateTeleportSystem(telepad, zone) =>
val remoteTelepad = telepad.asInstanceOf[TelepadDeployable]
remoteTelepad.Active = true
zone.GUID(remoteTelepad.Router) match {
case Some(router : Vehicle) =>
router.Utility(UtilityType.internal_router_telepad_deployable) match {
case Some(internalTelepad : Utility.InternalTelepad) =>
//get rid of previous linked remote telepad (if any)
zone.GUID(internalTelepad.Telepad) match {
case Some(old : TelepadDeployable) =>
log.info(s"ActivateTeleportSystem: old remote telepad@${old.GUID.guid} linked to internal@${internalTelepad.GUID.guid} will be deconstructed")
old.Active = false
engineer ! SupportActor.ClearSpecific(List(old), zone)
engineer ! RemoverActor.AddTask(old, zone, Some(0 seconds))
case _ => ;
}
internalTelepad.Telepad = remoteTelepad.GUID
if(internalTelepad.Active) {
log.info(s"ActivateTeleportSystem: fully deployed router@${router.GUID.guid} in ${zone.Id} will link internal@${internalTelepad.GUID.guid} and remote@${remoteTelepad.GUID.guid}")
LocalEvents.publish(
LocalServiceResponse(s"/${zone.Id}/Local", Service.defaultPlayerGUID, LocalResponse.ToggleTeleportSystem(router, Some((internalTelepad, remoteTelepad))))
)
}
else {
remoteTelepad.OwnerName match {
case Some(name) =>
LocalEvents.publish(
LocalServiceResponse(s"/$name/Local", Service.defaultPlayerGUID, LocalResponse.RouterTelepadMessage("@Teleport_NotDeployed"))
)
case None => ;
}
}
case _ =>
log.error(s"ActivateTeleportSystem: vehicle@${router.GUID.guid} in ${zone.Id} is not a router?")
RouterTelepadError(remoteTelepad, zone, "@Telepad_NoDeploy_RouterLost")
}
case Some(o) =>
log.error(s"ActivateTeleportSystem: ${o.Definition.Name}@${o.GUID.guid} in ${zone.Id} is not a router")
RouterTelepadError(remoteTelepad, zone, "@Telepad_NoDeploy_RouterLost")
case None =>
RouterTelepadError(remoteTelepad, zone, "@Telepad_NoDeploy_RouterLost")
}
//synchronized damage calculations
case Vitality.DamageOn(target : Deployable, func) =>
func(target)
@ -236,6 +302,24 @@ class LocalService extends Actor {
log.warn(s"Unhandled message $msg from $sender")
}
/**
* na
* @param telepad na
* @param zone na
* @param msg na
*/
def RouterTelepadError(telepad : TelepadDeployable, zone : Zone, msg : String) : Unit = {
telepad.OwnerName match {
case Some(name) =>
LocalEvents.publish(
LocalServiceResponse(s"/$name/Local", Service.defaultPlayerGUID, LocalResponse.RouterTelepadMessage(msg))
)
case None => ;
}
engineer ! SupportActor.ClearSpecific(List(telepad), zone)
engineer ! RemoverActor.AddTask(telepad, zone, Some(0 seconds))
}
/**
* Common behavior for distributing information about a deployable's destruction or deconstruction.<br>
* <br>

View file

@ -5,4 +5,6 @@ final case class LocalServiceMessage(forChannel : String, actionMessage : LocalA
object LocalServiceMessage {
final case class Deployables(msg : Any)
final case class Telepads(msg : Any)
}

View file

@ -0,0 +1,142 @@
// Copyright (c) 2017 PSForever
package services.local.support
import akka.actor.Cancellable
import net.psforever.objects.zones.Zone
import net.psforever.objects._
import services.support.{SimilarityComparator, SupportActor}
import scala.concurrent.duration._
class RouterTelepadActivation extends SupportActor[RouterTelepadActivation.Entry] {
var activationTask : Cancellable = DefaultCancellable.obj
var telepadList : List[RouterTelepadActivation.Entry] = List()
val sameEntryComparator = new SimilarityComparator[RouterTelepadActivation.Entry]() {
def Test(entry1 : RouterTelepadActivation.Entry, entry2 : RouterTelepadActivation.Entry) : Boolean = {
(entry1.obj eq entry2.obj) && (entry1.zone eq entry2.zone) && entry1.obj.GUID == entry2.obj.GUID
}
}
val firstStandardTime : FiniteDuration = 60 seconds
def InclusionTest(entry : RouterTelepadActivation.Entry) : Boolean = {
val obj = entry.obj
obj.isInstanceOf[TelepadDeployable] && !obj.asInstanceOf[TelepadDeployable].Active
}
def receive : Receive = entryManagementBehaviors
.orElse {
case RouterTelepadActivation.AddTask(obj, zone, duration) =>
val entry = RouterTelepadActivation.Entry(obj, zone, duration.getOrElse(firstStandardTime).toNanos)
if(InclusionTest(entry) && !telepadList.exists(test => sameEntryComparator.Test(test, entry))) {
if(entry.duration == 0) {
//skip the queue altogether
ActivationTask(entry)
}
else if(telepadList.isEmpty) {
//we were the only entry so the event must be started from scratch
telepadList = List(entry)
trace(s"an activation task has been added: $entry")
RetimeFirstTask()
}
else {
//unknown number of entries; append, sort, then re-time tasking
val oldHead = telepadList.head
if(!telepadList.exists(test => sameEntryComparator.Test(test, entry))) {
telepadList = (telepadList :+ entry).sortBy(entry => entry.time + entry.duration)
trace(s"an activation task has been added: $entry")
if(oldHead != telepadList.head) {
RetimeFirstTask()
}
}
else {
trace(s"$obj is already queued")
}
}
}
else {
trace(s"$obj either does not qualify for this behavior or is already queued")
}
//private messages from self to self
case RouterTelepadActivation.TryActivate() =>
activationTask.cancel
val now : Long = System.nanoTime
val (in, out) = telepadList.partition(entry => { now - entry.time >= entry.duration })
telepadList = out
in.foreach { ActivationTask }
RetimeFirstTask()
trace(s"router activation task has found ${in.size} items to process")
case _ => ;
}
/**
* Common function to reset the first task's delayed execution.
* Cancels the scheduled timer and will only restart the timer if there is at least one entry in the first pool.
* @param now the time (in nanoseconds);
* defaults to the current time (in nanoseconds)
*/
def RetimeFirstTask(now : Long = System.nanoTime) : Unit = {
activationTask.cancel
if(telepadList.nonEmpty) {
val short_timeout : FiniteDuration = math.max(1, telepadList.head.duration - (now - telepadList.head.time)) nanoseconds
import scala.concurrent.ExecutionContext.Implicits.global
activationTask = context.system.scheduler.scheduleOnce(short_timeout, self, RouterTelepadActivation.TryActivate())
}
}
def HurrySpecific(targets : List[PlanetSideGameObject], zone : Zone) : Unit = {
PartitionTargetsFromList(telepadList, targets.map { RouterTelepadActivation.Entry(_, zone, 0) }, zone) match {
case (Nil, _) =>
debug(s"no tasks matching the targets $targets have been hurried")
case (in, out) =>
debug(s"the following tasks have been hurried: $in")
telepadList = out
if(out.nonEmpty) {
RetimeFirstTask()
}
in.foreach { ActivationTask }
}
}
def HurryAll() : Unit = {
trace("all tasks have been hurried")
activationTask.cancel
telepadList.foreach { ActivationTask }
telepadList = Nil
}
def ClearSpecific(targets : List[PlanetSideGameObject], zone : Zone) : Unit = {
PartitionTargetsFromList(telepadList, targets.map { RouterTelepadActivation.Entry(_, zone, 0) }, zone) match {
case (Nil, _) =>
debug(s"no tasks matching the targets $targets have been cleared")
case (in, out) =>
debug(s"the following tasks have been cleared: $in")
telepadList = out //.sortBy(entry => entry.time + entry.duration)
if(out.nonEmpty) {
RetimeFirstTask()
}
}
}
def ClearAll() : Unit = {
trace("all tasks have been cleared")
activationTask.cancel
telepadList = Nil
}
def ActivationTask(entry : SupportActor.Entry) : Unit = {
entry.obj.asInstanceOf[TelepadDeployable].Active = true
context.parent ! RouterTelepadActivation.ActivateTeleportSystem(entry.obj, entry.zone)
}
}
object RouterTelepadActivation {
final case class Entry(_obj : PlanetSideGameObject, _zone : Zone, _duration : Long) extends SupportActor.Entry(_obj, _zone, _duration)
final case class AddTask(obj : PlanetSideGameObject, zone : Zone, duration : Option[FiniteDuration] = None)
final case class TryActivate()
final case class ActivateTeleportSystem(telepad : PlanetSideGameObject, zone : Zone)
}

View file

@ -73,17 +73,12 @@ abstract class SupportActor[A <: SupportActor.Entry] extends Actor {
//a - find targets from entries
val locatedTargets = for {
a <- targets
b <- list//.filter(entry => entry.zone == zone)
b <- list
if b.obj.HasGUID && a.obj.HasGUID && comparator.Test(b, a)
} yield b
if(locatedTargets.nonEmpty) {
//b - entries, after the found targets are removed (cull any non-GUID entries while at it)
val retained = for {
a <- locatedTargets
b <- list
if b.obj.HasGUID && a.obj.HasGUID && !comparator.Test(b, a)
} yield b
(locatedTargets, retained)
(locatedTargets, list filterNot locatedTargets.toSet)
}
else {
(Nil, list)

View file

@ -1,8 +1,9 @@
// Copyright (c) 2017 PSForever
package services.vehicle
import net.psforever.objects.{PlanetSideGameObject, Vehicle}
import net.psforever.objects.{PlanetSideGameObject, TelepadDeployable, Vehicle}
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.vehicles.Utility
import net.psforever.objects.zones.Zone
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.PlanetSideGUID
@ -26,7 +27,7 @@ object VehicleAction {
final case class PlanetsideAttribute(player_guid : PlanetSideGUID, target_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Action
final case class SeatPermissions(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, seat_group : Int, permission : Long) extends Action
final case class StowEquipment(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action
final case class UnloadVehicle(player_guid : PlanetSideGUID, continent : Zone, vehicle : Vehicle) extends Action
final case class UnloadVehicle(player_guid : PlanetSideGUID, continent : Zone, vehicle : Vehicle, vehicle_guid : PlanetSideGUID) extends Action
final case class UnstowEquipment(player_guid : PlanetSideGUID, item_guid : PlanetSideGUID) extends Action
final case class VehicleState(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, unk1 : Int, pos : Vector3, ang : Vector3, vel : Option[Vector3], unk2 : Option[Int], unk3 : Int, unk4 : Int, wheel_direction : Int, unk5 : Boolean, unk6 : Boolean) extends Action
final case class SendResponse(player_guid: PlanetSideGUID, msg : PlanetSideGamePacket) extends Action

View file

@ -2,7 +2,8 @@
package services.vehicle
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.{PlanetSideGameObject, Vehicle}
import net.psforever.objects.vehicles.Utility
import net.psforever.objects.{PlanetSideGameObject, TelepadDeployable, Vehicle}
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID}
import net.psforever.packet.game.objectcreate.ConstructorData
@ -30,7 +31,7 @@ object VehicleResponse {
final case class RevealPlayer(player_guid : PlanetSideGUID) extends Response
final case class SeatPermissions(vehicle_guid : PlanetSideGUID, seat_group : Int, permission : Long) extends Response
final case class StowEquipment(vehicle_guid : PlanetSideGUID, slot : Int, itype : Int, iguid : PlanetSideGUID, idata : ConstructorData) extends Response
final case class UnloadVehicle(vehicle_guid : PlanetSideGUID) extends Response
final case class UnloadVehicle(vehicle : Vehicle, vehicle_guid : PlanetSideGUID) extends Response
final case class UnstowEquipment(item_guid : PlanetSideGUID) extends Response
final case class VehicleState(vehicle_guid : PlanetSideGUID, unk1 : Int, pos : Vector3, ang : Vector3, vel : Option[Vector3], unk2 : Option[Int], unk3 : Int, unk4 : Int, wheel_direction : Int, unk5 : Boolean, unk6 : Boolean) extends Response
final case class SendResponse(msg: PlanetSideGamePacket) extends Response

View file

@ -105,10 +105,10 @@ class VehicleService extends Actor {
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.StowEquipment(vehicle_guid, slot, definition.ObjectId, item.GUID, definition.Packet.DetailedConstructorData(item).get))
)
case VehicleAction.UnloadVehicle(player_guid, continent, vehicle) =>
case VehicleAction.UnloadVehicle(player_guid, continent, vehicle, vehicle_guid) =>
vehicleDecon ! RemoverActor.ClearSpecific(List(vehicle), continent) //precaution
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.UnloadVehicle(vehicle.GUID))
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.UnloadVehicle(vehicle, vehicle_guid))
)
case VehicleAction.UnstowEquipment(player_guid, item_guid) =>
VehicleEvents.publish(

View file

@ -4,6 +4,7 @@ package services.vehicle.support
import net.psforever.objects.Vehicle
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
import net.psforever.objects.zones.Zone
import net.psforever.types.DriveState
import services.{RemoverActor, Service}
import services.vehicle.{VehicleAction, VehicleServiceMessage}
@ -43,8 +44,9 @@ class VehicleRemover extends RemoverActor {
override def SecondJob(entry : RemoverActor.Entry) : Unit = {
val vehicle = entry.obj.asInstanceOf[Vehicle]
val zone = entry.zone
vehicle.DeploymentState = DriveState.Mobile
zone.Transport ! Zone.Vehicle.Despawn(vehicle)
context.parent ! VehicleServiceMessage(zone.Id, VehicleAction.UnloadVehicle(Service.defaultPlayerGUID, zone, vehicle))
context.parent ! VehicleServiceMessage(zone.Id, VehicleAction.UnloadVehicle(Service.defaultPlayerGUID, zone, vehicle, vehicle.GUID))
super.SecondJob(entry)
}

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package base
// Copyright (c) 2017 PSForever
import akka.actor.ActorSystem
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import akka.testkit.{ImplicitSender, TestKit}
import com.typesafe.config.ConfigFactory
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
@ -48,4 +48,28 @@ object ActorTest {
}
out
}
/**
* A middleman Actor that accepts a `Props` object to instantiate and accepts messages back from it.
* The purpose is to bypass a message receive issue with the `ActorTest` / `TestKit` class
* that does not properly queue messages dispatched to it
* when messages may be sent to it via a `context.parent` call.
* Please do not wrap and parameterize Props objects like this during normal Ops.
* @param actorProps the uninitialized `Actor` that uses `context.parent` to direct communication
* @param sendTo where to send mesages that have originated from an `actorProps` object;
* typically should point back to the test environment constructed by `TestKit`
*/
class SupportActorInterface(actorProps : Props, sendTo : ActorRef) extends Actor {
val test = context.actorOf(actorProps, "support-actor")
def receive : Receive = {
case msg =>
(if(sender == test) {
sendTo
}
else {
test
}) ! msg
}
}
}

View file

@ -0,0 +1,38 @@
// Copyright (c) 2017 PSForever
package game.objectcreate
import net.psforever.packet.PacketCoding
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID}
import net.psforever.packet.game.objectcreate._
import org.specs2.mutable._
import scodec.bits._
class ContainedTelepadDeployableDataTest extends Specification {
val string = hex"178f0000004080f42b00182cb0202000100000"
"ContainedTelepadDeployableData" should {
"decode" in {
PacketCoding.DecodePacket(string).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 143
cls mustEqual 744
guid mustEqual PlanetSideGUID(432)
parent.isDefined mustEqual true
parent.get.guid mustEqual PlanetSideGUID(385)
parent.get.slot mustEqual 2
data.isDefined mustEqual true
data.get.isInstanceOf[ContainedTelepadDeployableData] mustEqual true
data.get.asInstanceOf[ContainedTelepadDeployableData].unk mustEqual 101
data.get.asInstanceOf[ContainedTelepadDeployableData].router_guid mustEqual PlanetSideGUID(385)
case _ =>
ko
}
}
"encode" in {
val obj = ContainedTelepadDeployableData(101, PlanetSideGUID(385))
val msg = ObjectCreateMessage(744, PlanetSideGUID(432), ObjectCreateMessageParent(PlanetSideGUID(385), 2), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
}
}

View file

@ -0,0 +1,40 @@
// Copyright (c) 2017 PSForever
package game.objectcreate
import net.psforever.packet._
import net.psforever.packet.game._
import net.psforever.packet.game.objectcreate._
import org.specs2.mutable._
import scodec.bits._
class TelepadDataTest extends Specification {
val string = hex"17 86000000 5700 f3a a201 80 0302020000000"
//TODO validate the unknown fields before router_guid for testing
"TelepadData" should {
"decode" in {
PacketCoding.DecodePacket(string).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 134
cls mustEqual ObjectClass.router_telepad
guid mustEqual PlanetSideGUID(418)
parent.isDefined mustEqual true
parent.get.guid mustEqual PlanetSideGUID(430)
parent.get.slot mustEqual 0
data.isDefined mustEqual true
data.get.isInstanceOf[TelepadData] mustEqual true
data.get.asInstanceOf[TelepadData].router_guid mustEqual Some(PlanetSideGUID(385))
case _ =>
ko
}
}
"encode" in {
val obj = TelepadData(0, PlanetSideGUID(385))
val msg = ObjectCreateMessage(ObjectClass.router_telepad, PlanetSideGUID(418), ObjectCreateMessageParent(PlanetSideGUID(430), 0), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
}
}

View file

@ -0,0 +1,61 @@
// Copyright (c) 2017 PSForever
package game.objectcreate
import net.psforever.packet._
import net.psforever.packet.game._
import net.psforever.packet.game.objectcreate._
import net.psforever.types.{PlanetSideEmpire, Vector3}
import org.specs2.mutable._
import scodec.bits._
class TelepadDeployableDataTest extends Specification {
val string = hex"17 c8000000 f42 6101 fbcfc 0fd43 6903 00 00 79 05 8101 ae01 5700c"
//TODO validate the unknown fields before router_guid for testing
"TelepadData" should {
"decode" in {
PacketCoding.DecodePacket(string).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 200
cls mustEqual ObjectClass.router_telepad_deployable
guid mustEqual PlanetSideGUID(353)
parent.isDefined mustEqual false
data.isDefined mustEqual true
data.get.isInstanceOf[TelepadDeployableData] mustEqual true
val teledata = data.get.asInstanceOf[TelepadDeployableData]
teledata.pos.coord mustEqual Vector3(6559.961f, 1960.1172f, 13.640625f)
teledata.pos.orient mustEqual Vector3.z(109.6875f)
teledata.pos.vel.isDefined mustEqual false
teledata.faction mustEqual PlanetSideEmpire.TR
teledata.bops mustEqual false
teledata.destroyed mustEqual false
teledata.unk1 mustEqual 2
teledata.unk2 mustEqual true
teledata.router_guid mustEqual PlanetSideGUID(385)
teledata.owner_guid mustEqual PlanetSideGUID(430)
teledata.unk3 mustEqual 87
teledata.unk4 mustEqual 12
case _ =>
ko
}
}
"encode" in {
val obj = TelepadDeployableData(
PlacementData(
Vector3(6559.961f, 1960.1172f, 13.640625f),
Vector3.z(109.6875f)
),
PlanetSideEmpire.TR,
false, false, 2, true,
PlanetSideGUID(385),
PlanetSideGUID(430),
87, 12
)
val msg = ObjectCreateMessage(ObjectClass.router_telepad_deployable, PlanetSideGUID(353), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
}
}

View file

@ -0,0 +1,66 @@
// Copyright (c) 2017 PSForever
package game.objectcreatedetailed
import net.psforever.packet._
import net.psforever.packet.game._
import net.psforever.packet.game.objectcreate._
import org.specs2.mutable._
import scodec.bits._
class DetailedTelepadDataTest extends Specification {
val string = hex"18 97000000 4f00 f3a e301 80 4a680400000200008"
val string_short = hex"18 87000000 2a00 f3a 5d01 89 8000000200008"
//TODO validate the unknown fields before router_guid for testing
"DetailedTelepadData" should {
"decode" in {
PacketCoding.DecodePacket(string).require match {
case ObjectCreateDetailedMessage(len, cls, guid, parent, data) =>
len mustEqual 151
cls mustEqual ObjectClass.router_telepad
guid mustEqual PlanetSideGUID(483)
parent.isDefined mustEqual true
parent.get.guid mustEqual PlanetSideGUID(414)
parent.get.slot mustEqual 0
data.isDefined mustEqual true
data.get.isInstanceOf[DetailedTelepadData] mustEqual true
data.get.asInstanceOf[DetailedTelepadData].router_guid mustEqual Some(PlanetSideGUID(564))
case _ =>
ko
}
}
"decode (short)" in {
PacketCoding.DecodePacket(string_short).require match {
case ObjectCreateDetailedMessage(len, cls, guid, parent, data) =>
len mustEqual 135
cls mustEqual ObjectClass.router_telepad
guid mustEqual PlanetSideGUID(349)
parent.isDefined mustEqual true
parent.get.guid mustEqual PlanetSideGUID(340)
parent.get.slot mustEqual 9
data.isDefined mustEqual true
data.get.isInstanceOf[DetailedTelepadData] mustEqual true
data.get.asInstanceOf[DetailedTelepadData].router_guid mustEqual None
case _ =>
ko
}
}
"encode" in {
val obj = DetailedTelepadData(18, PlanetSideGUID(564))
val msg = ObjectCreateDetailedMessage(ObjectClass.router_telepad, PlanetSideGUID(483), ObjectCreateMessageParent(PlanetSideGUID(414), 0), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
"encode (short)" in {
val obj = DetailedTelepadData(32)
val msg = ObjectCreateDetailedMessage(ObjectClass.router_telepad, PlanetSideGUID(349), ObjectCreateMessageParent(PlanetSideGUID(340), 9), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_short
}
}
}

View file

@ -8,6 +8,7 @@ import net.psforever.objects.equipment._
import net.psforever.objects.inventory.InventoryTile
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.vehicles.UtilityType
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate._
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, Vector3}
@ -166,6 +167,35 @@ class ConverterTest extends Specification {
}
}
"Telepad" should {
"convert (success)" in {
val obj = new Telepad(GlobalDefinitions.router_telepad)
obj.Router = PlanetSideGUID(1001)
obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) =>
pkt mustEqual TelepadData(0, PlanetSideGUID(1001))
case _ =>
ko
}
obj.Definition.Packet.DetailedConstructorData(obj) match {
case Success(pkt) =>
pkt mustEqual DetailedTelepadData(0, PlanetSideGUID(1001))
case _ =>
ko
}
}
"convert (failure; no router)" in {
val obj = new Telepad(GlobalDefinitions.router_telepad)
//obj.Router = PlanetSideGUID(1001)
obj.Definition.Packet.ConstructorData(obj).isFailure mustEqual true
obj.Definition.Packet.DetailedConstructorData(obj).isFailure mustEqual true
}
}
"SmallDeployable" should {
"convert" in {
val obj = new SensorDeployable(GlobalDefinitions.motionalarmsensor)
@ -299,6 +329,79 @@ class ConverterTest extends Specification {
}
}
"TelepadDeployable" should {
"convert (success)" in {
val obj = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
obj.Faction = PlanetSideEmpire.TR
obj.GUID = PlanetSideGUID(90)
obj.Router = PlanetSideGUID(1001)
obj.Owner = PlanetSideGUID(5001)
obj.Health = 1
obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) =>
pkt mustEqual TelepadDeployableData(
PlacementData(Vector3.Zero, Vector3.Zero),
PlanetSideEmpire.TR,
bops = false,
destroyed = false,
unk1 = 2, unk2 = true,
router_guid = PlanetSideGUID(1001),
owner_guid = PlanetSideGUID(5001),
unk3 = 87, unk4 = 12
)
case _ =>
ko
}
}
"convert (success; destroyed)" in {
val obj = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
obj.Faction = PlanetSideEmpire.TR
obj.GUID = PlanetSideGUID(90)
obj.Router = PlanetSideGUID(1001)
obj.Owner = PlanetSideGUID(5001)
obj.Health = 0
obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) =>
pkt mustEqual TelepadDeployableData(
PlacementData(Vector3.Zero, Vector3.Zero),
PlanetSideEmpire.TR,
bops = false,
destroyed = true,
unk1 = 2, unk2 = true,
router_guid = PlanetSideGUID(1001),
owner_guid = PlanetSideGUID(0),
unk3 = 0, unk4 = 6
)
case _ =>
ko
}
}
"convert (failure; no router)" in {
val obj = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
obj.Faction = PlanetSideEmpire.TR
obj.GUID = PlanetSideGUID(90)
//obj.Router = PlanetSideGUID(1001)
obj.Owner = PlanetSideGUID(5001)
obj.Health = 1
obj.Definition.Packet.ConstructorData(obj).isFailure mustEqual true
obj.Router = PlanetSideGUID(0)
obj.Definition.Packet.ConstructorData(obj).isFailure mustEqual true
}
"convert (failure; detailed)" in {
val obj = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
obj.Faction = PlanetSideEmpire.TR
obj.GUID = PlanetSideGUID(90)
obj.Router = PlanetSideGUID(1001)
obj.Owner = PlanetSideGUID(5001)
obj.Health = 1
obj.Definition.Packet.DetailedConstructorData(obj).isFailure mustEqual true
}
}
"Player" should {
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val obj : Player = {
@ -525,6 +628,15 @@ class ConverterTest extends Specification {
ams.Definition.Packet.ConstructorData(ams).isSuccess mustEqual true
//did not initialize the utilities, but the converter did not fail
}
"convert to packet (4)" in {
val
router = Vehicle(GlobalDefinitions.router)
router.GUID = PlanetSideGUID(413)
router.Utility(UtilityType.teleportpad_terminal).get.GUID = PlanetSideGUID(1413)
router.Utility(UtilityType.internal_router_telepad_deployable).get.GUID = PlanetSideGUID(2413)
router.Definition.Packet.ConstructorData(router).isSuccess mustEqual true
}
}
"DestroyedVehicle" should {

View file

@ -318,6 +318,38 @@ class TurretControlBetrayalMountTest extends ActorTest {
}
}
class TelepadDeployableTest extends Specification {
"Telepad" should {
"construct" in {
val obj = new Telepad(GlobalDefinitions.router_telepad)
obj.Active mustEqual false
obj.Router mustEqual None
}
"activate and deactivate" in {
val obj = new Telepad(GlobalDefinitions.router_telepad)
obj.Active mustEqual false
obj.Active = true
obj.Active mustEqual true
obj.Active = false
obj.Active mustEqual false
}
"keep track of a Router" in {
val obj = new Telepad(GlobalDefinitions.router_telepad)
obj.Router mustEqual None
obj.Router = PlanetSideGUID(1)
obj.Router mustEqual Some(PlanetSideGUID(1))
obj.Router = None
obj.Router mustEqual None
obj.Router = PlanetSideGUID(1)
obj.Router mustEqual Some(PlanetSideGUID(1))
obj.Router = PlanetSideGUID(0)
obj.Router mustEqual None
}
}
}
object DeployableTest {
class TurretInitializer(obj : TurretDeployable) extends Actor {
def receive : Receive = {

View file

@ -20,10 +20,12 @@ class DeployableToolboxTest extends Specification {
obj.Initialize(Set())
val list = obj.UpdateUI()
list.size mustEqual DeployedItem.values.size - 3 //extra field turrets
list.foreach({case(_,curr,_,max) =>
val (routers, allOthers) = list.partition({ case((_,_,_,max)) => max == 1024 })
allOthers.foreach({case(_,curr,_,max) =>
curr mustEqual 0
max mustEqual 0
})
routers.length mustEqual 1
ok
}
@ -44,7 +46,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 0
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 0
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
}
"initialization (AssaultEngineering)" in {
@ -64,7 +66,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 1
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 1
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 1
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
}
"initialization (FortificationEngineering)" in {
@ -84,7 +86,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 0
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 0
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
}
"initialization (AdvancedEngineering)" in {
@ -104,7 +106,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 1
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 1
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 1
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
}
"initialization (AdvancedHacking)" in {
@ -124,7 +126,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 0
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 0
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
}
"initialization (without CombatEngineering)" in {
@ -144,7 +146,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 0
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 0
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
}
"initialization (GroundSupport)" in {
@ -164,6 +166,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 0
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 0
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
}
"can not initialize twice" in {
@ -183,7 +186,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 0
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 0
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
obj.Initialize(Set(AdvancedEngineering)) mustEqual false
obj.CountDeployable(DeployedItem.boomer)._2 mustEqual 0
@ -200,7 +203,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 0
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 0
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
}
"uninitialized fields can not accept deployables" in {
@ -240,7 +243,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 0
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 0
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
obj.AddToDeployableQuantities(
CombatEngineering,
@ -260,7 +263,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 0
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 0
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
obj.AddToDeployableQuantities(
FortificationEngineering,
@ -280,7 +283,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 0
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 0
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
obj.AddToDeployableQuantities(
AssaultEngineering,
@ -300,7 +303,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 1
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 1
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 1
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
obj.AddToDeployableQuantities(
AssaultEngineering,
@ -320,7 +323,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 1
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 1
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 1
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
obj.AddToDeployableQuantities(
AdvancedHacking,
@ -340,7 +343,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 1
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 1
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 1
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
}
"change accessible fields by adding by certification type (GroundSupport)" in {
@ -360,7 +363,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 0
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 0
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
obj.AddToDeployableQuantities(
GroundSupport,
@ -400,7 +403,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 0
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 0
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
obj.AddToDeployableQuantities(
AdvancedEngineering,
@ -420,7 +423,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 1
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 1
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 1
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
}
"change accessible fields by removing by certification types (all)" in {
@ -440,7 +443,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 1
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 1
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 1
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
obj.RemoveFromDeployableQuantities(
GroundSupport,
@ -460,7 +463,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 1
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 1
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 1
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
obj.RemoveFromDeployableQuantities(
AdvancedHacking,
@ -480,7 +483,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 1
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 1
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 1
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
obj.RemoveFromDeployableQuantities(
FortificationEngineering,
@ -500,7 +503,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 1
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 1
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 1
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
obj.RemoveFromDeployableQuantities(
AssaultEngineering,
@ -520,7 +523,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 0
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 0
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
obj.RemoveFromDeployableQuantities(
CombatEngineering,
@ -540,7 +543,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 0
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 0
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
}
"change accessible fields by removing by certification type (AdvancedEngineering)" in {
@ -560,7 +563,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 1
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 1
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 1
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
obj.RemoveFromDeployableQuantities(
AdvancedEngineering,
@ -580,7 +583,7 @@ class DeployableToolboxTest extends Specification {
obj.CountDeployable(DeployedItem.portable_manned_turret_tr)._2 mustEqual 0
obj.CountDeployable(DeployedItem.portable_manned_turret_vs)._2 mustEqual 0
obj.CountDeployable(DeployedItem.deployable_shield_generator)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 0
obj.CountDeployable(DeployedItem.router_telepad_deployable)._2 mustEqual 1024
}
"can not remove deployables from an unpopulated field" in {

View file

@ -5,7 +5,7 @@ import net.psforever.objects._
import net.psforever.objects.equipment._
import net.psforever.objects.inventory.InventoryTile
import net.psforever.objects.GlobalDefinitions._
import net.psforever.objects.ce.DeployedItem
import net.psforever.objects.ce.{DeployedItem, TelepadLike}
import net.psforever.objects.definition._
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.CertificationType

View file

@ -3,7 +3,7 @@ package objects
import akka.actor.{Actor, ActorRef, Props}
import base.ActorTest
import net.psforever.objects.{GlobalDefinitions, Vehicle}
import net.psforever.objects._
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.vehicles._
import net.psforever.packet.game.PlanetSideGUID
@ -18,7 +18,7 @@ class UtilityTest extends Specification {
obj.UtilType mustEqual UtilityType.order_terminala
obj().isInstanceOf[Terminal] mustEqual true
obj().asInstanceOf[Terminal].Definition.ObjectId mustEqual 613
obj().asInstanceOf[Terminal].Actor == ActorRef.noSender
obj().asInstanceOf[Terminal].Actor mustEqual ActorRef.noSender
}
"create an order_terminalb object" in {
@ -26,7 +26,7 @@ class UtilityTest extends Specification {
obj.UtilType mustEqual UtilityType.order_terminalb
obj().isInstanceOf[Terminal] mustEqual true
obj().asInstanceOf[Terminal].Definition.ObjectId mustEqual 614
obj().asInstanceOf[Terminal].Actor == ActorRef.noSender
obj().asInstanceOf[Terminal].Actor mustEqual ActorRef.noSender
}
"create a matrix_terminalc object" in {
@ -34,7 +34,7 @@ class UtilityTest extends Specification {
obj.UtilType mustEqual UtilityType.matrix_terminalc
obj().isInstanceOf[Terminal] mustEqual true
obj().asInstanceOf[Terminal].Definition.ObjectId mustEqual 519
obj().asInstanceOf[Terminal].Actor == ActorRef.noSender
obj().asInstanceOf[Terminal].Actor mustEqual ActorRef.noSender
}
"create an ams_respawn_tube object" in {
@ -43,7 +43,53 @@ class UtilityTest extends Specification {
obj.UtilType mustEqual UtilityType.ams_respawn_tube
obj().isInstanceOf[SpawnTube] mustEqual true
obj().asInstanceOf[SpawnTube].Definition.ObjectId mustEqual 49
obj().asInstanceOf[SpawnTube].Actor == ActorRef.noSender
obj().asInstanceOf[SpawnTube].Actor mustEqual ActorRef.noSender
}
"create a teleportpad_terminal object" in {
val obj = Utility(UtilityType.teleportpad_terminal, UtilityTest.vehicle)
obj.UtilType mustEqual UtilityType.teleportpad_terminal
obj().isInstanceOf[Terminal] mustEqual true
obj().asInstanceOf[Terminal].Definition.ObjectId mustEqual 853
obj().asInstanceOf[Terminal].Actor mustEqual ActorRef.noSender
}
"teleportpad_terminal produces a telepad object (router_telepad)" in {
import net.psforever.packet.game.ItemTransactionMessage
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, TransactionType}
val veh = Vehicle(GlobalDefinitions.quadstealth)
val obj = Utility(UtilityType.teleportpad_terminal, UtilityTest.vehicle)
veh.GUID = PlanetSideGUID(101)
obj().Owner = veh //hack
obj().GUID = PlanetSideGUID(1)
val msg = obj().asInstanceOf[Terminal].Buy(
Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)),
ItemTransactionMessage(PlanetSideGUID(853), TransactionType.Buy, 0, "router_telepad", 0, PlanetSideGUID(0))
)
msg.isInstanceOf[Terminal.BuyEquipment] mustEqual true
msg.asInstanceOf[Terminal.BuyEquipment].item.isInstanceOf[Telepad] mustEqual true
}
"create an internal_router_telepad_deployable object" in {
val obj = Utility(UtilityType.internal_router_telepad_deployable, UtilityTest.vehicle)
obj.UtilType mustEqual UtilityType.internal_router_telepad_deployable
obj().isInstanceOf[Utility.InternalTelepad] mustEqual true
obj().asInstanceOf[Utility.InternalTelepad].Definition.ObjectId mustEqual 744
obj().asInstanceOf[Utility.InternalTelepad].Actor mustEqual ActorRef.noSender
}
"internal_router_telepad_deployable can keep track of an object's GUID (presumedly, it's a Telepad)" in {
val obj = Utility(UtilityType.internal_router_telepad_deployable, UtilityTest.vehicle)
val inpad = obj().asInstanceOf[Utility.InternalTelepad]
inpad.Telepad mustEqual None
inpad.Telepad = PlanetSideGUID(5)
inpad.Telepad mustEqual Some(PlanetSideGUID(5))
inpad.Telepad = PlanetSideGUID(6)
inpad.Telepad mustEqual Some(PlanetSideGUID(6))
inpad.Telepad = None
inpad.Telepad mustEqual None
}
"be located with their owner (terminal)" in {
@ -71,10 +117,25 @@ class UtilityTest extends Specification {
obj().Position mustEqual veh.Position
obj().Orientation mustEqual veh.Orientation
}
"be located with their owner (internal telepad)" in {
val veh = Vehicle(GlobalDefinitions.quadstealth)
val obj = Utility(UtilityType.internal_router_telepad_deployable, veh)
obj().Position mustEqual veh.Position
obj().Orientation mustEqual veh.Orientation
import net.psforever.types.Vector3
veh.Position = Vector3(1, 2, 3)
veh.Orientation = Vector3(4, 5, 6)
veh.GUID = PlanetSideGUID(101)
obj().Position mustEqual veh.Position
obj().Orientation mustEqual veh.Orientation
obj().asInstanceOf[Utility.InternalTelepad].Router mustEqual Some(veh.GUID)
}
}
}
class Utility1Test extends ActorTest {
class UtilityTerminalATest extends ActorTest {
"Utility" should {
"wire an order_terminala Actor" in {
val obj = Utility(UtilityType.order_terminala, UtilityTest.vehicle)
@ -88,7 +149,7 @@ class Utility1Test extends ActorTest {
}
}
class Utility2Test extends ActorTest {
class UtilityTerminalBTest extends ActorTest {
"Utility" should {
"wire an order_terminalb Actor" in {
val obj = Utility(UtilityType.order_terminalb, UtilityTest.vehicle)
@ -102,7 +163,7 @@ class Utility2Test extends ActorTest {
}
}
class Utility3Test extends ActorTest {
class UtilityTerminalCTest extends ActorTest {
"Utility" should {
"wire a matrix_terminalc Actor" in {
val obj = Utility(UtilityType.matrix_terminalc, UtilityTest.vehicle)
@ -116,7 +177,7 @@ class Utility3Test extends ActorTest {
}
}
class Utility4Test extends ActorTest {
class UtilityRespawnTubeTest extends ActorTest {
"Utility" should {
"wire an ams_respawn_tube Actor" in {
val obj = Utility(UtilityType.ams_respawn_tube, UtilityTest.vehicle)
@ -130,6 +191,38 @@ class Utility4Test extends ActorTest {
}
}
class UtilityTelepadTerminalTest extends ActorTest {
"Utility" should {
"wire a teleportpad_terminal Actor" in {
val obj = Utility(UtilityType.teleportpad_terminal, UtilityTest.vehicle)
obj().GUID = PlanetSideGUID(1)
assert(obj().Actor == ActorRef.noSender)
system.actorOf(Props(classOf[UtilityTest.SetupControl], obj), "test") ! ""
receiveOne(Duration.create(100, "ms")) //consume and discard
assert(obj().Actor != ActorRef.noSender)
}
}
}
class UtilityInternalTelepadTest extends ActorTest {
"Utility" should {
"wire a teleportpad_terminal Actor" in {
val veh = Vehicle(GlobalDefinitions.quadstealth)
veh.GUID = PlanetSideGUID(101)
val obj = Utility(UtilityType.internal_router_telepad_deployable, veh)
obj().GUID = PlanetSideGUID(1)
assert(obj().Actor == ActorRef.noSender)
assert(obj().asInstanceOf[Utility.InternalTelepad].Router.contains(veh.GUID))
system.actorOf(Props(classOf[UtilityTest.SetupControl], obj), "test") ! ""
receiveOne(Duration.create(100, "ms")) //consume and discard
assert(obj().Actor == ActorRef.noSender)
assert(obj().asInstanceOf[Utility.InternalTelepad].Router.contains(veh.GUID))
}
}
}
object UtilityTest {
val vehicle = Vehicle(GlobalDefinitions.quadstealth)

View file

@ -3,7 +3,7 @@ package service
import akka.actor.Props
import base.ActorTest
import net.psforever.objects.{GlobalDefinitions, SensorDeployable}
import net.psforever.objects.{GlobalDefinitions, SensorDeployable, Vehicle}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.packet.game._
import net.psforever.types.{PlanetSideEmpire, Vector3}
@ -154,6 +154,19 @@ class ProximityTerminalEffectTest extends ActorTest {
}
}
class RouterTelepadTransportTest extends ActorTest {
ServiceManager.boot(system)
"LocalService" should {
"pass RouterTelepadTransport" in {
val service = system.actorOf(Props[LocalService], "l_service")
service ! Service.Join("test")
service ! LocalServiceMessage("test", LocalAction.RouterTelepadTransport(PlanetSideGUID(10), PlanetSideGUID(11), PlanetSideGUID(12), PlanetSideGUID(13)))
expectMsg(LocalServiceResponse("/test/Local", PlanetSideGUID(10), LocalResponse.RouterTelepadTransport(PlanetSideGUID(11), PlanetSideGUID(12), PlanetSideGUID(13))))
}
}
}
class SetEmpireTest extends ActorTest {
ServiceManager.boot(system)
val obj = new SensorDeployable(GlobalDefinitions.motionalarmsensor)
@ -168,6 +181,20 @@ class SetEmpireTest extends ActorTest {
}
}
class ToggleTeleportSystemTest extends ActorTest {
ServiceManager.boot(system)
"LocalService" should {
"pass ToggleTeleportSystem" in {
val router = Vehicle(GlobalDefinitions.router)
val service = system.actorOf(Props[LocalService], "l_service")
service ! Service.Join("test")
service ! LocalServiceMessage("test", LocalAction.ToggleTeleportSystem(PlanetSideGUID(10), router, None))
expectMsg(LocalServiceResponse("/test/Local", PlanetSideGUID(10), LocalResponse.ToggleTeleportSystem(router, None)))
}
}
}
class TriggerEffectTest extends ActorTest {
ServiceManager.boot(system)

View file

@ -5,7 +5,7 @@ import akka.actor.{ActorRef, Props}
import akka.routing.RandomPool
import akka.testkit.TestProbe
import base.ActorTest
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Tool}
import net.psforever.objects.definition.{EquipmentDefinition, ObjectDefinition}
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.guid.TaskResolver
@ -21,24 +21,26 @@ import scala.concurrent.duration._
// "RemoverActor" should {
// "handle a simple task" in {
// expectNoMsg(500 milliseconds)
// val probe = TestProbe()
// val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover")
// val remover = system.actorOf(
// Props(classOf[ActorTest.SupportActorInterface], Props[RemoverActorTest.TestRemover], self),
// "test-remover"
// )
// remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere)
//
// val reply1 = probe.receiveOne(200 milliseconds)
// val reply1 = receiveOne(500 milliseconds)
// assert(reply1.isInstanceOf[RemoverActorTest.InclusionTestAlert])
// val reply2 = probe.receiveOne(200 milliseconds)
// val reply2 = receiveOne(500 milliseconds)
// assert(reply2.isInstanceOf[RemoverActorTest.InitialJobAlert])
// probe.expectNoMsg(1 seconds) //delay
// val reply3 = probe.receiveOne(300 milliseconds)
// expectNoMsg(1 seconds) //delay
// val reply3 = receiveOne(500 milliseconds)
// assert(reply3.isInstanceOf[RemoverActorTest.FirstJobAlert])
// val reply4 = probe.receiveOne(300 milliseconds)
// val reply4 = receiveOne(500 milliseconds)
// assert(reply4.isInstanceOf[RemoverActorTest.ClearanceTestAlert])
// val reply5 = probe.receiveOne(300 milliseconds)
// val reply5 = receiveOne(500 milliseconds)
// assert(reply5.isInstanceOf[RemoverActorTest.SecondJobAlert])
// val reply6 = probe.receiveOne(500 milliseconds)
// val reply6 = receiveOne(500 milliseconds)
// assert(reply6.isInstanceOf[RemoverActorTest.DeletionTaskAlert])
// val reply7 = probe.receiveOne(500 milliseconds)
// val reply7 = receiveOne(500 milliseconds)
// assert(reply7.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert])
// }
// }
@ -50,29 +52,31 @@ import scala.concurrent.duration._
// "RemoverActor" should {
// "handle a simple task (timed)" in {
// expectNoMsg(500 milliseconds)
// val probe = TestProbe()
// val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover")
// val remover = system.actorOf(
// Props(classOf[ActorTest.SupportActorInterface], Props[RemoverActorTest.TestRemover], self),
// "test-remover"
// )
// remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere, Some(100 milliseconds))
//
// val reply1 = probe.receiveOne(200 milliseconds)
// val reply1 = receiveOne(500 milliseconds)
// assert(reply1.isInstanceOf[RemoverActorTest.InclusionTestAlert])
// val reply2 = probe.receiveOne(200 milliseconds)
// val reply2 = receiveOne(500 milliseconds)
// assert(reply2.isInstanceOf[RemoverActorTest.InitialJobAlert])
// //no delay
// val reply3 = probe.receiveOne(300 milliseconds)
// val reply3 = receiveOne(500 milliseconds)
// assert(reply3.isInstanceOf[RemoverActorTest.FirstJobAlert])
// val reply4 = probe.receiveOne(300 milliseconds)
// val reply4 = receiveOne(500 milliseconds)
// assert(reply4.isInstanceOf[RemoverActorTest.ClearanceTestAlert])
// val reply5 = probe.receiveOne(300 milliseconds)
// val reply5 = receiveOne(500 milliseconds)
// assert(reply5.isInstanceOf[RemoverActorTest.SecondJobAlert])
// val reply6 = probe.receiveOne(300 milliseconds)
// val reply6 = receiveOne(500 milliseconds)
// assert(reply6.isInstanceOf[RemoverActorTest.DeletionTaskAlert])
// val reply7 = probe.receiveOne(300 milliseconds)
// val reply7 = receiveOne(500 milliseconds)
// assert(reply7.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert])
// }
// }
//}
//
//class ExcludedRemoverActorTest extends ActorTest {
// ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver")
// val AlternateTestObject = new PlanetSideGameObject() { def Definition = new ObjectDefinition(0) { } }
@ -81,7 +85,10 @@ import scala.concurrent.duration._
// "allow only specific objects" in {
// expectNoMsg(500 milliseconds)
// val probe = TestProbe()
// val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover")
// val remover = system.actorOf(
// Props(classOf[ActorTest.SupportActorInterface], Props[RemoverActorTest.TestRemover], self),
// "test-remover"
// )
// remover ! RemoverActor.AddTask(AlternateTestObject, Zone.Nowhere)
//
// val reply1 = probe.receiveOne(200 milliseconds)
@ -91,7 +98,7 @@ import scala.concurrent.duration._
// }
// }
//}
//
//class MultipleRemoverActorTest extends ActorTest {
// ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver")
// final val TestObject2 = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(2) } }
@ -494,44 +501,44 @@ object RemoverActorTest {
final case class DeletionTaskRunAlert()
class TestRemover(probe : TestProbe) extends RemoverActor {
class TestRemover extends RemoverActor {
import net.psforever.objects.guid.{Task, TaskResolver}
val FirstStandardDuration = 1 seconds
val SecondStandardDuration = 100 milliseconds
def InclusionTest(entry : RemoverActor.Entry) : Boolean = {
probe.ref ! InclusionTestAlert()
entry.obj.isInstanceOf[Equipment]
context.parent ! InclusionTestAlert()
true
}
def InitialJob(entry : RemoverActor.Entry) : Unit = {
probe.ref ! InitialJobAlert()
context.parent ! InitialJobAlert()
}
def FirstJob(entry : RemoverActor.Entry) : Unit = {
probe.ref ! FirstJobAlert()
context.parent ! FirstJobAlert()
}
override def SecondJob(entry : RemoverActor.Entry) : Unit = {
probe.ref ! SecondJobAlert()
context.parent ! SecondJobAlert()
super.SecondJob(entry)
}
def ClearanceTest(entry : RemoverActor.Entry) : Boolean = {
probe.ref ! ClearanceTestAlert()
context.parent ! ClearanceTestAlert()
true
}
def DeletionTask(entry : RemoverActor.Entry) : TaskResolver.GiveTask = {
probe.ref ! DeletionTaskAlert()
context.parent ! DeletionTaskAlert()
TaskResolver.GiveTask(new Task() {
private val localProbe = probe
private val localProbe = context.parent
override def isComplete = Task.Resolution.Success
def Execute(resolver : ActorRef) : Unit = {
localProbe.ref ! DeletionTaskRunAlert()
context.parent ! DeletionTaskRunAlert()
resolver ! scala.util.Success(this)
}
})

View file

@ -0,0 +1,171 @@
// Copyright (c) 2017 PSForever
package service
import akka.actor.Props
import base.ActorTest
import net.psforever.objects._
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
import services.local.support.RouterTelepadActivation
import services.support.SupportActor
import scala.concurrent.duration._
class RouterTelepadActivationTest extends ActorTest {
"RouterTelepadActivation" should {
"construct" in {
system.actorOf(Props[RouterTelepadActivation], "activation-test-actor")
}
}
}
class RouterTelepadActivationSimpleTest extends ActorTest {
"RouterTelepadActivation" should {
"handle a task" in {
val telepad = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
telepad.GUID = PlanetSideGUID(1)
val obj = system.actorOf(
Props(classOf[ActorTest.SupportActorInterface], Props[RouterTelepadActivation], self),
"activation-test-actor"
)
obj ! RouterTelepadActivation.AddTask(telepad, Zone.Nowhere, Some(2 seconds))
expectMsg(3 seconds, RouterTelepadActivation.ActivateTeleportSystem(telepad, Zone.Nowhere))
}
}
}
class RouterTelepadActivationComplexTest extends ActorTest {
"RouterTelepadActivation" should {
"handle multiple tasks" in {
val telepad1 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
telepad1.GUID = PlanetSideGUID(1)
val telepad2 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
telepad2.GUID = PlanetSideGUID(2)
val telepad3 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
telepad3.GUID = PlanetSideGUID(3)
val obj = system.actorOf(
Props(classOf[ActorTest.SupportActorInterface], Props[RouterTelepadActivation], self),
"activation-test-actor"
)
obj ! RouterTelepadActivation.AddTask(telepad1, Zone.Nowhere, Some(2 seconds))
obj ! RouterTelepadActivation.AddTask(telepad2, Zone.Nowhere, Some(3 seconds))
obj ! RouterTelepadActivation.AddTask(telepad3, Zone.Nowhere, Some(1 seconds))
val msgs = receiveN(3, 5 seconds) //organized by duration
assert(msgs.head.isInstanceOf[RouterTelepadActivation.ActivateTeleportSystem])
assert(msgs.head.asInstanceOf[RouterTelepadActivation.ActivateTeleportSystem].telepad == telepad3)
assert(msgs(1).isInstanceOf[RouterTelepadActivation.ActivateTeleportSystem])
assert(msgs(1).asInstanceOf[RouterTelepadActivation.ActivateTeleportSystem].telepad == telepad1)
assert(msgs(2).isInstanceOf[RouterTelepadActivation.ActivateTeleportSystem])
assert(msgs(2).asInstanceOf[RouterTelepadActivation.ActivateTeleportSystem].telepad == telepad2)
}
}
}
class RouterTelepadActivationHurryTest extends ActorTest {
"RouterTelepadActivation" should {
"hurry specific tasks" in {
val telepad1 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
telepad1.GUID = PlanetSideGUID(1)
val telepad2 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
telepad2.GUID = PlanetSideGUID(2)
val telepad3 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
telepad3.GUID = PlanetSideGUID(3)
val obj = system.actorOf(
Props(classOf[ActorTest.SupportActorInterface], Props[RouterTelepadActivation], self),
"activation-test-actor"
)
obj ! RouterTelepadActivation.AddTask(telepad1, Zone.Nowhere, Some(2 seconds))
obj ! RouterTelepadActivation.AddTask(telepad2, Zone.Nowhere, Some(2 seconds))
obj ! RouterTelepadActivation.AddTask(telepad3, Zone.Nowhere, Some(2 seconds))
obj ! SupportActor.HurrySpecific(List(telepad1, telepad2), Zone.Nowhere)
val msgs = receiveN(2, 1 seconds)
assert(msgs.head.isInstanceOf[RouterTelepadActivation.ActivateTeleportSystem])
assert(msgs.head.asInstanceOf[RouterTelepadActivation.ActivateTeleportSystem].telepad == telepad1)
assert(msgs(1).isInstanceOf[RouterTelepadActivation.ActivateTeleportSystem])
assert(msgs(1).asInstanceOf[RouterTelepadActivation.ActivateTeleportSystem].telepad == telepad2)
val last = receiveOne(3 seconds)
assert(last.isInstanceOf[RouterTelepadActivation.ActivateTeleportSystem])
assert(last.asInstanceOf[RouterTelepadActivation.ActivateTeleportSystem].telepad == telepad3)
}
}
}
class RouterTelepadActivationHurryAllTest extends ActorTest {
"RouterTelepadActivation" should {
"hurry all tasks" in {
val telepad1 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
telepad1.GUID = PlanetSideGUID(1)
val telepad2 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
telepad2.GUID = PlanetSideGUID(2)
val telepad3 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
telepad3.GUID = PlanetSideGUID(3)
val obj = system.actorOf(
Props(classOf[ActorTest.SupportActorInterface], Props[RouterTelepadActivation], self),
"activation-test-actor"
)
obj ! RouterTelepadActivation.AddTask(telepad1, Zone.Nowhere, Some(7 seconds))
obj ! RouterTelepadActivation.AddTask(telepad2, Zone.Nowhere, Some(5 seconds))
obj ! RouterTelepadActivation.AddTask(telepad3, Zone.Nowhere, Some(6 seconds))
obj ! SupportActor.HurryAll()
val msgs = receiveN(3, 4 seconds) //organized by duration; note: all messages received before the earliest task should be performed
assert(msgs.head.isInstanceOf[RouterTelepadActivation.ActivateTeleportSystem])
assert(msgs.head.asInstanceOf[RouterTelepadActivation.ActivateTeleportSystem].telepad == telepad2)
assert(msgs(1).isInstanceOf[RouterTelepadActivation.ActivateTeleportSystem])
assert(msgs(1).asInstanceOf[RouterTelepadActivation.ActivateTeleportSystem].telepad == telepad3)
assert(msgs(2).isInstanceOf[RouterTelepadActivation.ActivateTeleportSystem])
assert(msgs(2).asInstanceOf[RouterTelepadActivation.ActivateTeleportSystem].telepad == telepad1)
}
}
}
class RouterTelepadActivationClearTest extends ActorTest {
"RouterTelepadActivation" should {
"clear specific tasks" in {
val telepad1 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
telepad1.GUID = PlanetSideGUID(1)
val telepad2 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
telepad2.GUID = PlanetSideGUID(2)
val telepad3 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
telepad3.GUID = PlanetSideGUID(3)
val obj = system.actorOf(
Props(classOf[ActorTest.SupportActorInterface], Props[RouterTelepadActivation], self),
"activation-test-actor"
)
obj ! RouterTelepadActivation.AddTask(telepad1, Zone.Nowhere, Some(2 seconds))
obj ! RouterTelepadActivation.AddTask(telepad2, Zone.Nowhere, Some(2 seconds))
obj ! RouterTelepadActivation.AddTask(telepad3, Zone.Nowhere, Some(2 seconds))
obj ! SupportActor.ClearSpecific(List(telepad1, telepad2), Zone.Nowhere)
val msgs = receiveN(1, 3 seconds) //should only receive telepad3
assert(msgs.head.isInstanceOf[RouterTelepadActivation.ActivateTeleportSystem])
assert(msgs.head.asInstanceOf[RouterTelepadActivation.ActivateTeleportSystem].telepad == telepad3)
}
}
}
class RouterTelepadActivationClearAllTest extends ActorTest {
"RouterTelepadActivation" should {
"clear all tasks" in {
val telepad1 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
telepad1.GUID = PlanetSideGUID(1)
val telepad2 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
telepad2.GUID = PlanetSideGUID(2)
val telepad3 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
telepad3.GUID = PlanetSideGUID(3)
val obj = system.actorOf(
Props(classOf[ActorTest.SupportActorInterface], Props[RouterTelepadActivation], self),
"activation-test-actor"
)
obj ! RouterTelepadActivation.AddTask(telepad1, Zone.Nowhere, Some(2 seconds))
obj ! RouterTelepadActivation.AddTask(telepad2, Zone.Nowhere, Some(2 seconds))
obj ! RouterTelepadActivation.AddTask(telepad3, Zone.Nowhere, Some(2 seconds))
obj ! SupportActor.ClearAll()
expectNoMsg(4 seconds)
}
}
}

View file

@ -16,7 +16,7 @@ import services.ServiceManager.Lookup
import net.psforever.objects._
import net.psforever.objects.avatar.{Certification, DeployableToolbox}
import net.psforever.objects.ballistics._
import net.psforever.objects.ce.{ComplexDeployable, Deployable, DeployedItem, SimpleDeployable}
import net.psforever.objects.ce._
import net.psforever.objects.definition.{ConstructionFireMode, DeployableDefinition, ObjectDefinition, ToolDefinition}
import net.psforever.objects.definition.converter.{CorpseConverter, DestroyedVehicleConverter}
import net.psforever.objects.equipment.{CItem, _}
@ -35,7 +35,7 @@ import net.psforever.objects.serverobject.mblocker.Locker
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
import net.psforever.objects.serverobject.pad.process.{AutoDriveControls, VehicleSpawnControlGuided}
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.structures.{Building, StructureType, WarpGate}
import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType, WarpGate}
import net.psforever.objects.serverobject.terminals._
import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage
import net.psforever.objects.serverobject.tube.SpawnTube
@ -45,7 +45,7 @@ import net.psforever.objects.vital._
import net.psforever.objects.zones.{InterstellarCluster, Zone}
import net.psforever.packet.game.objectcreate._
import net.psforever.types._
import services.{RemoverActor, _}
import services.{RemoverActor, vehicle, _}
import services.avatar.{AvatarAction, AvatarResponse, AvatarServiceMessage, AvatarServiceResponse}
import services.galaxy.{GalaxyResponse, GalaxyServiceResponse}
import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse}
@ -59,7 +59,9 @@ import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
import scala.util.Success
import akka.pattern.ask
import services.local.support.HackCaptureActor
import net.psforever.objects.vehicles.Utility.InternalTelepad
import services.local.support.{HackCaptureActor, RouterTelepadActivation}
import services.support.SupportActor
class WorldSessionActor extends Actor with MDCContextAware {
@ -95,6 +97,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
var whenUsedLastKit : Long = 0
val projectiles : Array[Option[Projectile]] = Array.fill[Option[Projectile]](Projectile.RangeUID - Projectile.BaseUID)(None)
var drawDeloyableIcon : PlanetSideGameObject with Deployable => Unit = RedrawDeployableIcons
var recentTeleportAttempt : Long = 0
var amsSpawnPoint : Option[SpawnTube] = None
var clientKeepAlive : Cancellable = DefaultCancellable.obj
var progressBarUpdate : Cancellable = DefaultCancellable.obj
@ -124,6 +128,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
vehicleService ! Service.Leave()
avatarService ! Service.Leave()
galaxyService ! Service.Leave()
cluster ! Service.Leave()
LivePlayerList.Remove(sessionId)
if(player != null && player.HasGUID) {
val player_guid = player.GUID
@ -141,15 +146,15 @@ class WorldSessionActor extends Actor with MDCContextAware {
})
//handle orphaned deployables
DisownDeployables()
//clean up boomer triggers
//clean up boomer triggers and telepads
val equipment = (
(player.Holsters()
.zipWithIndex
.map({ case ((slot, index)) => (index, slot.Equipment) })
.collect { case ((index, Some(obj))) => InventoryItem(obj, index) }
) ++ player.Inventory.Items)
.filterNot({ case InventoryItem(obj, _) => obj.isInstanceOf[BoomerTrigger] })
//TODO final character save before doing any of this
.filterNot({ case InventoryItem(obj, _) => obj.isInstanceOf[BoomerTrigger] || obj.isInstanceOf[Telepad] })
//TODO final character save before doing any of this (use equipment)
continent.Population ! Zone.Population.Release(avatar)
if(player.isAlive) {
//actually being alive or manually deconstructing
@ -206,13 +211,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
case None =>
None
}) match {
case Some(vehicle : Vehicle) =>
vehicle.Seat(vehicle.PassengerInSeat(player).get).get.Occupant = None
if(vehicle.Seats.values.count(_.isOccupied) == 0) {
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, continent, vehicle.Definition.DeconstructionTime)) //start vehicle decay
}
vehicleService ! Service.Leave(Some(s"${vehicle.Actor}"))
case Some(mobj : Mountable) =>
mobj.Seat(mobj.PassengerInSeat(player).get).get.Occupant = None
@ -496,10 +494,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
case building : Building =>
log.info(s"Zone.Lattice.SpawnPoint: spawn point on $zone_id in building ${building.Id} selected")
case vehicle : Vehicle =>
// vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(vehicle), continent))
// vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, continent, vehicle.Definition.DeconstructionTime))
//TODO replace this bad math with good math or no math
//position the player alongside either of the AMS's terminals, facing away from it
val side = if(System.currentTimeMillis() % 2 == 0) 1
else -1
val side = if(System.currentTimeMillis() % 2 == 0) 1 else -1
//right | left
val z = spawn_tube.Orientation.z
val zrot = (z + 90) % 360
@ -547,7 +546,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Zone.Ground.ItemOnGround(item : BoomerTrigger, pos, orient) =>
//dropped the trigger, no longer own the boomer; make certain whole faction is aware of that
val playerGUID = player.GUID
continent.GUID(item.Companion.getOrElse(PlanetSideGUID(0))) match {
continent.GUID(item.Companion) match {
case Some(obj : BoomerDeployable) =>
val guid = obj.GUID
val factionOnContinentChannel = s"${continent.Id}/${player.Faction}"
@ -586,7 +585,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Zone.Ground.ItemInHand(item : BoomerTrigger) =>
if(PutItemInHand(item)) {
//pick up the trigger, own the boomer; make certain whole faction is aware of that
continent.GUID(item.Companion.getOrElse(PlanetSideGUID(0))) match {
continent.GUID(item.Companion) match {
case Some(obj : BoomerDeployable) =>
val guid = obj.GUID
val playerGUID = player.GUID
@ -632,6 +631,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case GlobalDefinitions.advanced_ace =>
sendResponse(GenericObjectActionMessage(player.GUID, 212)) //put fdu down; it will be removed from the client's holster
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PutDownFDU(player.GUID))
case GlobalDefinitions.router_telepad => ;
case _ =>
log.warn(s"Zone.Deployable.DeployableIsBuilt: not sure what kind of construction item to animate - ${tool.Definition}")
}
@ -706,6 +706,39 @@ class WorldSessionActor extends Actor with MDCContextAware {
FindReplacementConstructionItem(tool, index)
StopBundlingPackets()
case WorldSessionActor.FinalizeDeployable(obj : TelepadDeployable, tool, index) =>
StartBundlingPackets()
if(obj.Health > 0) {
val guid = obj.GUID
//router telepad deployable
val router = tool.asInstanceOf[Telepad].Router
//router must exist and be deployed
continent.GUID(router) match {
case Some(vehicle : Vehicle) =>
val routerGUID = router.get
if(vehicle.Health == 0) {
//the Telepad was successfully deployed; but, before it could configure, its Router was destroyed
sendResponse(ChatMsg(ChatMessageType.UNK_229, false, "", "@Telepad_NoDeploy_RouterLost", None))
localService ! LocalServiceMessage.Deployables(RemoverActor.AddTask(obj, continent, Some(0 seconds)))
}
else {
log.info(s"FinalizeDeployable: setup for telepad #${guid.guid} in zone ${continent.Id}")
obj.Router = routerGUID //necessary; forwards link to the router
DeployableBuildActivity(obj)
CommonDestroyConstructionItem(tool, index)
StopBundlingPackets()
//it takes 60s for the telepad to become properly active
localService ! LocalServiceMessage.Telepads(RouterTelepadActivation.AddTask(obj, continent))
}
case _ =>
//the Telepad was successfully deployed; but, before it could configure, its Router was deconstructed
sendResponse(ChatMsg(ChatMessageType.UNK_229, false, "", "@Telepad_NoDeploy_RouterLost", None))
localService ! LocalServiceMessage.Deployables(RemoverActor.AddTask(obj, continent, Some(0 seconds)))
}
}
StopBundlingPackets()
case WorldSessionActor.FinalizeDeployable(obj : SimpleDeployable, tool, index) =>
//tank_trap
StartBundlingPackets()
@ -1132,8 +1165,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
DeconstructDeployable(obj, guid, pos)
}
else {
DeconstructDeployable(obj, guid, pos, obj.Orientation, if(obj.MountPoints.isEmpty) 2
else 1)
DeconstructDeployable(obj, guid, pos, obj.Orientation, if(obj.MountPoints.isEmpty) 2 else 1)
}
case LocalResponse.EliminateDeployable(obj : ComplexDeployable, guid, pos) =>
@ -1152,6 +1184,34 @@ class WorldSessionActor extends Actor with MDCContextAware {
DeconstructDeployable(obj, guid, pos, obj.Orientation, 2)
}
case LocalResponse.EliminateDeployable(obj : TelepadDeployable, guid, pos) =>
//if active, deactivate
if(obj.Active) {
obj.Active = false
sendResponse(GenericObjectActionMessage(guid, 116))
sendResponse(GenericObjectActionMessage(guid, 120))
}
//determine if no replacement teleport system exists
continent.GUID(obj.Router) match {
case Some(router : Vehicle) =>
//if the telepad was replaced, the new system is physically in place but not yet functional
if(router.Utility(UtilityType.internal_router_telepad_deployable) match {
case Some(internalTelepad : Utility.InternalTelepad) => internalTelepad.Telepad.contains(guid) //same telepad
case _ => true
}) {
//there is no replacement telepad; shut down the system
ToggleTeleportSystem(router, None)
}
case _ => ;
}
//standard deployable elimination behavior
if(obj.Health == 0) {
DeconstructDeployable(obj, guid, pos)
}
else {
DeconstructDeployable(obj, guid, pos, obj.Orientation, 2)
}
case LocalResponse.EliminateDeployable(obj, guid, pos) =>
if(obj.Health == 0) {
DeconstructDeployable(obj, guid, pos)
@ -1215,9 +1275,20 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(ProximityTerminalUseMessage(PlanetSideGUID(0), object_guid, effectState))
}
case LocalResponse.RouterTelepadMessage(msg) =>
sendResponse(ChatMsg(ChatMessageType.UNK_229, false, "", msg, None))
case LocalResponse.RouterTelepadTransport(passenger_guid, src_guid, dest_guid) =>
StartBundlingPackets()
UseRouterTelepadEffect(passenger_guid, src_guid, dest_guid)
StopBundlingPackets()
case LocalResponse.SetEmpire(object_guid, empire) =>
sendResponse(SetEmpireMessage(object_guid, empire))
case LocalResponse.ToggleTeleportSystem(router, system_plan) =>
ToggleTeleportSystem(router, system_plan)
case LocalResponse.TriggerEffect(target_guid, effect, effectInfo, triggerLocation) =>
sendResponse(TriggerEffectMessage(target_guid, effect, effectInfo, triggerLocation))
@ -1257,12 +1328,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
val obj_guid : PlanetSideGUID = obj.GUID
val player_guid : PlanetSideGUID = tplayer.GUID
log.info(s"MountVehicleMsg: $player_guid mounts $obj_guid @ $seat_num")
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(obj), continent)) //clear timer
PlayerActionsToCancel()
sendResponse(PlanetsideAttributeMessage(obj_guid, 0, obj.Health))
sendResponse(PlanetsideAttributeMessage(obj_guid, 68, 0)) //shield health
sendResponse(PlanetsideAttributeMessage(obj_guid, 113, 0)) //capacitor
if(seat_num == 0) {
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(obj), continent)) //clear timer
//simplistic vehicle ownership management
obj.Owner match {
case Some(owner_guid) =>
@ -1288,8 +1359,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
})
case _ => ; //no weapons to update
}
//sendResponse(PlanetsideAttributeMessage(obj.GUID, 0, obj.Health)) //TODO vehicle max health in definition
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(obj), continent)) //clear timer
AccessContents(obj)
MountingAction(tplayer, obj, seat_num)
@ -1313,9 +1382,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
else {
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, seat_num, true, obj.GUID))
}
if(obj.Seats.values.count(_.isOccupied) == 0) {
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(obj, continent, obj.Definition.DeconstructionTime)) //start vehicle decay
}
case Mountable.CanDismount(obj : Mountable, _) =>
log.warn(s"DismountVehicleMsg: $obj is some generic mountable object and nothing will happen")
@ -1468,7 +1534,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
}
case Terminal.BuyEquipment(item) => ;
case Terminal.BuyEquipment(item) =>
tplayer.Fit(item) match {
case Some(index) =>
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
@ -1797,12 +1863,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
* @param reply na
*/
def HandleVehicleServiceResponse(toChannel : String, guid : PlanetSideGUID, reply : VehicleResponse.Response) : Unit = {
val tplayer_guid = if(player.HasGUID) player.GUID
else PlanetSideGUID(0)
reply match {
case VehicleResponse.Ownership(vehicle_guid) =>
sendResponse(PlanetsideAttributeMessage(guid, 21, vehicle_guid.guid))
val tplayer_guid = if(player.HasGUID) player.GUID else PlanetSideGUID(0)
reply match {
case VehicleResponse.AttachToRails(vehicle_guid, pad_guid) =>
sendResponse(ObjectAttachMessage(pad_guid, vehicle_guid, 3))
@ -1881,6 +1944,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(ObjectAttachMessage(vehicle_guid, guid, seat))
}
case VehicleResponse.Ownership(vehicle_guid) =>
sendResponse(PlanetsideAttributeMessage(guid, 21, vehicle_guid.guid))
case VehicleResponse.PlanetsideAttribute(vehicle_guid, attribute_type, attribute_value) =>
if(tplayer_guid != guid) {
sendResponse(PlanetsideAttributeMessage(vehicle_guid, attribute_type, attribute_value))
@ -1906,7 +1972,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
)
}
case VehicleResponse.UnloadVehicle(vehicle_guid) =>
case VehicleResponse.UnloadVehicle(vehicle, vehicle_guid) =>
BeforeUnloadVehicle(vehicle)
sendResponse(ObjectDeleteMessage(vehicle_guid, 0))
case VehicleResponse.UnstowEquipment(item_guid) =>
@ -2079,9 +2146,15 @@ class WorldSessionActor extends Actor with MDCContextAware {
val wep = slot.Equipment.get
avatarService ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, wep.GUID))
})
if(target.Definition == GlobalDefinitions.ams) {
target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying)
ClearCurrentAmsSpawnPoint()
target.Definition match {
case GlobalDefinitions.ams =>
target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying)
ClearCurrentAmsSpawnPoint()
case GlobalDefinitions.router =>
target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying)
BeforeUnloadVehicle(target)
localService ! LocalServiceMessage(continent.Id, LocalAction.ToggleTeleportSystem(PlanetSideGUID(0), target, None))
case _ => ;
}
avatarService ! AvatarServiceMessage(continentId, AvatarAction.Destroy(targetGUID, playerGUID, playerGUID, target.Position))
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(target), continent))
@ -2574,7 +2647,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
)
//seated players
obj.asInstanceOf[Mountable].Seats.values
.map(_.Occupant)
.map(_.Occupant)
.collect {
case Some(occupant) =>
if(occupant.isAlive) {
@ -2590,6 +2663,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
}
})
normal
.filter(_.Definition.DeployCategory == DeployableCategory.Sensors)
.foreach(obj => { sendResponse(TriggerEffectMessage(obj.GUID, "on", true, 1000)) })
//draw our faction's deployables on the map
continent.DeployableList
.filter(obj => obj.Faction == faction && obj.Health > 0)
@ -2624,7 +2700,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
//load vehicles in zone
val (wreckages, vehicles) = continent.Vehicles.partition(vehicle => { vehicle.Health == 0 && vehicle.Definition.DestroyedModel.nonEmpty })
//active vehicles
//active vehicles (and some wreckage)
vehicles.foreach(vehicle => {
val vehicle_guid = vehicle.GUID
val vdefinition = vehicle.Definition
@ -2646,22 +2722,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
})
ReloadVehicleAccessPermissions(vehicle)
})
//Loop over vehicles again to add cargohold occupants after all vehicles have been created on the local client
vehicles.foreach(vehicle => {
vehicle.CargoHolds.foreach({ case (cargo_num, cargo) => {
cargo.Occupant match {
case Some(cargo_vehicle) =>
if(cargo_vehicle.HasGUID) {
StartBundlingPackets()
sendResponse(ObjectAttachMessage(cargo_vehicle.GUID, vehicle.GUID, cargo_num))
//todo: attaching the vehicle seems to work, but setting the mount point status doesn't?
sendResponse(CargoMountPointStatusMessage(cargo_vehicle.GUID, vehicle.GUID, vehicle.GUID, PlanetSideGUID(0), cargo_num, CargoStatus.Occupied, 0))
StopBundlingPackets()
}
case None => ; // No vehicle in cargo
}
}})
})
//vehicle wreckages
wreckages.foreach(vehicle => {
sendResponse(
@ -2672,6 +2732,30 @@ class WorldSessionActor extends Actor with MDCContextAware {
)
)
})
//Loop over vehicles again to add cargohold occupants after all vehicles have been created on the local client
vehicles.filter(_.CargoHolds.nonEmpty).foreach(vehicle => {
vehicle.CargoHolds.foreach({ case (cargo_num, cargo) => {
cargo.Occupant match {
case Some(cargo_vehicle) =>
if(cargo_vehicle.HasGUID) {
sendResponse(ObjectAttachMessage(cargo_vehicle.GUID, vehicle.GUID, cargo_num))
//todo: attaching the vehicle seems to work, but setting the mount point status doesn't?
sendResponse(CargoMountPointStatusMessage(cargo_vehicle.GUID, vehicle.GUID, vehicle.GUID, PlanetSideGUID(0), cargo_num, CargoStatus.Occupied, 0))
}
case None => ; // No vehicle in cargo
}
}})
})
//special deploy states
val deployedVehicles = vehicles.filter(_.DeploymentState == DriveState.Deployed)
deployedVehicles.filter(_.Definition == GlobalDefinitions.ams).foreach(obj => {
sendResponse(PlanetsideAttributeMessage(obj.GUID, 81, 1))
})
deployedVehicles.filter(_.Definition == GlobalDefinitions.router).foreach(obj => {
sendResponse(DeployRequestMessage(player.GUID, obj.GUID, DriveState.Deploying, 0, false, Vector3.Zero))
sendResponse(DeployRequestMessage(player.GUID, obj.GUID, DriveState.Deployed, 0, false, Vector3.Zero))
ToggleTeleportSystem(obj, TelepadLike.AppraiseTeleportationSystem(obj, continent))
})
//implant terminals
continent.Map.TerminalToInterface.foreach({ case ((terminal_guid, interface_guid)) =>
@ -3054,7 +3138,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Some(trigger : BoomerTrigger) =>
val playerGUID = player.GUID
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(playerGUID, item_guid))
continent.GUID(trigger.Companion.getOrElse(PlanetSideGUID(0))) match {
continent.GUID(trigger.Companion) match {
case Some(boomer : BoomerDeployable) =>
val boomerGUID = boomer.GUID
boomer.Exploded = true
@ -3231,7 +3315,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Some(obj : BoomerTrigger) =>
if(FindEquipmentToDelete(object_guid, obj)) {
continent.GUID(obj.Companion.getOrElse(PlanetSideGUID(0))) match {
continent.GUID(obj.Companion) match {
case Some(boomer : BoomerDeployable) =>
boomer.Trigger = None
localService ! LocalServiceMessage.Deployables(RemoverActor.AddTask(boomer, continent, Some(0 seconds)))
@ -3279,7 +3363,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
case None => ;
}
case Some(obj : TelepadDeployable) =>
localService ! LocalServiceMessage.Telepads(SupportActor.ClearSpecific(List(obj), continent))
localService ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(obj), continent))
localService ! LocalServiceMessage.Deployables(RemoverActor.AddTask(obj, continent, Some(0 seconds)))
case Some(obj : PlanetSideGameObject with Deployable) =>
localService ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(obj), continent))
localService ! LocalServiceMessage.Deployables(RemoverActor.AddTask(obj, continent, Some(0 seconds)))
case Some(thing) =>
@ -3618,38 +3708,50 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
case Some(terminal : Terminal) =>
if(terminal.Definition.isInstanceOf[MatrixTerminalDefinition]) {
//TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks)
sendResponse(BindPlayerMessage(1, "@ams", true, true, 0, 0, 0, terminal.Position))
}
else if(terminal.Definition.isInstanceOf[RepairRearmSiloDefinition]) {
FindLocalVehicle match {
case Some(vehicle) =>
sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
sendResponse(UseItemMessage(avatar_guid, item_used_guid, vehicle.GUID, unk2, unk3, unk4, unk5, unk6, unk7, unk8, vehicle.Definition.ObjectId))
case None =>
log.error("UseItem: expected seated vehicle, but found none")
val tdef = terminal.Definition
val owned = terminal.Faction == player.Faction
val hacked = terminal.HackedBy.nonEmpty
if(owned) {
if(tdef.isInstanceOf[MatrixTerminalDefinition]) {
//TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks)
sendResponse(BindPlayerMessage(1, "@ams", true, true, 0, 0, 0, terminal.Position))
}
else if(tdef.isInstanceOf[RepairRearmSiloDefinition]) {
FindLocalVehicle match {
case Some(vehicle) =>
sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
sendResponse(UseItemMessage(avatar_guid, item_used_guid, vehicle.GUID, unk2, unk3, unk4, unk5, unk6, unk7, unk8, vehicle.Definition.ObjectId))
case None =>
log.error("UseItem: expected seated vehicle, but found none")
}
}
else if(tdef.isInstanceOf[TeleportPadTerminalDefinition]) {
//explicit request
terminal.Actor ! Terminal.Request(
player,
ItemTransactionMessage(object_guid, TransactionType.Buy, 0, "router_telepad", 0, PlanetSideGUID(0))
)
}
else {
sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
}
}
else if(hacked) {
sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
}
else {
if(terminal.Faction != player.Faction && terminal.HackedBy.isEmpty) {
player.Slot(player.DrawnSlot).Equipment match {
case Some(tool: SimpleItem) =>
if (tool.Definition == GlobalDefinitions.remote_electronics_kit) {
val hackSpeed = GetPlayerHackSpeed(terminal)
player.Slot(player.DrawnSlot).Equipment match {
case Some(tool: SimpleItem) =>
if (tool.Definition == GlobalDefinitions.remote_electronics_kit) {
val hackSpeed = GetPlayerHackSpeed(terminal)
if(hackSpeed > 0) {
progressBarValue = Some(-hackSpeed)
self ! WorldSessionActor.HackingProgress(progressType = 1, player, terminal, tool.GUID, hackSpeed, FinishHacking(terminal, 3212836864L))
log.info("Hacking a terminal")
}
if(hackSpeed > 0) {
progressBarValue = Some(-hackSpeed)
self ! WorldSessionActor.HackingProgress(progressType = 1, player, terminal, tool.GUID, hackSpeed, FinishHacking(terminal, 3212836864L))
log.info("Hacking a terminal")
}
case _ => ;
}
} else if (terminal.Faction == player.Faction || !terminal.HackedBy.isEmpty) {
// If hacked only allow access to the faction that hacked it
// Otherwise allow the faction that owns the terminal to use it
sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
}
case _ => ;
}
}
@ -3662,6 +3764,29 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, true))
continent.Population ! Zone.Population.Release(avatar)
case Some(obj : TelepadDeployable) =>
continent.GUID(obj.Router) match {
case Some(vehicle : Vehicle) =>
vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
case Some(util : Utility.InternalTelepad) =>
UseRouterTelepadSystem(router = vehicle, internalTelepad = util, remoteTelepad = obj, src = obj, dest = util)
case _ =>
log.error(s"telepad@${object_guid.guid} is not linked to a router - ${vehicle.Definition.Name}@${obj.Router.get.guid}")
}
case Some(o) =>
log.error(s"telepad@${object_guid.guid} is linked to wrong kind of object - ${o.Definition.Name}@${obj.Router.get.guid}")
case None => ;
}
case Some(obj : Utility.InternalTelepad) =>
continent.GUID(obj.Telepad) match {
case Some(pad : TelepadDeployable) =>
UseRouterTelepadSystem(router = obj.Owner.asInstanceOf[Vehicle], internalTelepad = obj, remoteTelepad = pad, src = obj, dest = pad)
case Some(o) =>
log.error(s"internal telepad@${object_guid.guid} is not linked to a remote telepad - ${o.Definition.Name}@${o.GUID.guid}")
case None => ;
}
case Some(obj) =>
log.warn(s"UseItem: don't know how to handle $obj; taking a shot in the dark")
sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
@ -3719,7 +3844,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case turret =>
turret
}
log.info(s"Constructing a ${ammoType}")
log.info(s"DeployObject: Constructing a ${ammoType}")
val dObj : PlanetSideGameObject with Deployable = Deployables.Make(ammoType)()
dObj.Position = pos
dObj.Orientation = orient
@ -3735,9 +3860,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
taskResolver ! CallBackForTask(tasking, continent.Deployables, Zone.Deployable.Build(dObj, obj))
case Some(obj) =>
log.warn(s"$obj is something?")
log.warn(s"DeployObject: $obj is something?")
case None =>
log.warn("nothing?")
log.warn("DeployObject: nothing?")
}
@ -3896,7 +4021,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.info(s"Hit: $msg")
(hit_info match {
case Some(hitInfo) =>
continent.GUID(hitInfo.hitobject_guid.get) match {
continent.GUID(hitInfo.hitobject_guid) match {
case Some(obj : Player) =>
Some((obj, hitInfo.shot_origin, hitInfo.hit_pos))
case Some(obj : Vehicle) =>
@ -3995,6 +4120,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
//todo: continue flight path until aircraft crashes if no passengers present (or no passenger seats), then deconstruct.
//todo: kick cargo passengers out. To be added after PR #216 is merged
if(bailType == BailType.Bailed && seat_num == 0 && GlobalDefinitions.isFlightVehicle(obj.asInstanceOf[Vehicle].Definition)) {
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(obj), continent))
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(obj, continent, Some(0 seconds))) // Immediately deconstruct vehicle
}
@ -4153,7 +4279,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
case _ => ;
}
case msg @ TargetingImplantRequest(list) =>
log.info("TargetingImplantRequest: "+msg)
@ -4730,7 +4855,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
/**
* Disassociate this client's player (oneself) from a vehicle that he owns.
*/
def DisownVehicle() : Unit = DisownVehicle(player)
def DisownVehicle() : Option[Vehicle] = DisownVehicle(player)
/**
* Disassociate a player from a vehicle that he owns.
@ -4740,30 +4865,38 @@ class WorldSessionActor extends Actor with MDCContextAware {
* @see `DisownVehicle(Player, Vehicle)`
* @param tplayer the player
*/
def DisownVehicle(tplayer : Player) : Unit = {
def DisownVehicle(tplayer : Player) : Option[Vehicle] = {
tplayer.VehicleOwned match {
case Some(vehicle_guid) =>
tplayer.VehicleOwned = None
continent.GUID(vehicle_guid) match {
case Some(vehicle : Vehicle) =>
DisownVehicle(tplayer, vehicle)
case _ => ;
case _ =>
None
}
tplayer.VehicleOwned = None
case None => ;
case None =>
None
}
}
/**
* Disassociate a vehicle from the player that owns it.
* When a vehicle is disowned
* Disassociate a vehicle from the player that owns it, if that player really was the previous owner.
* This is the vehicle side of vehicle ownership removal.
* Additionally, start the vehicle deconstruction timer.
* @see `DisownVehicle(Player)`
* @param tplayer the player
* @param vehicle the discovered vehicle
*/
private def DisownVehicle(tplayer : Player, vehicle : Vehicle) : Unit = {
private def DisownVehicle(tplayer : Player, vehicle : Vehicle) : Option[Vehicle] = {
if(vehicle.Owner.contains(tplayer.GUID)) {
vehicle.Owner = None
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(vehicle), continent))
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, continent, vehicle.Definition.DeconstructionTime)) //start vehicle decay
Some(vehicle)
}
else {
None
}
}
@ -5299,13 +5432,18 @@ class WorldSessionActor extends Actor with MDCContextAware {
*/
def PermitEquipmentStow(equipment : Equipment, obj : PlanetSideGameObject with Container) : Boolean = {
equipment match {
case item : BoomerTrigger =>
case _ : BoomerTrigger =>
obj.isInstanceOf[Player] //a BoomerTrigger can only be stowed in a player's holsters or inventory
case _ =>
true
}
}
/**
* na
* @param tool na
* @param obj na
*/
def PerformToolAmmoChange(tool : Tool, obj : PlanetSideGameObject with Container) : Unit = {
val originalAmmoType = tool.AmmoType
do {
@ -5514,6 +5652,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
* Drop the item if:<br>
* - the item is cavern equipment<br>
* - the item is a `BoomerTrigger` type object<br>
* - the item is a `router_telepad` type object<br>
* - the item is another faction's exclusive equipment
* @param tplayer the player
* @return true if the item is to be dropped; false, otherwise
@ -5522,6 +5661,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
val objDef = entry.obj.Definition
val faction = GlobalDefinitions.isFactionEquipment(objDef)
GlobalDefinitions.isCavernEquipment(objDef) ||
objDef == GlobalDefinitions.router_telepad ||
entry.obj.isInstanceOf[BoomerTrigger] ||
(faction != tplayer.Faction && faction != PlanetSideEmpire.NEUTRAL)
}
@ -5546,46 +5686,88 @@ class WorldSessionActor extends Actor with MDCContextAware {
/**
* Perform specific operations depending on the target of deployment.
* @param obj the object that has deployed
* @param obj the object that has had its deployment state changed
*/
def DeploymentActivities(obj : Deployment.DeploymentObject) : Unit = {
DeploymentActivities(obj, obj.DeploymentState)
}
/**
* Perform specific operations depending on the target of deployment.
* @param obj the object that has had its deployment state changed
* @param state the new deployment state
*/
def DeploymentActivities(obj : Deployment.DeploymentObject, state : DriveState.Value) : Unit = {
obj match {
case vehicle : Vehicle =>
ReloadVehicleAccessPermissions(vehicle) //TODO we should not have to do this imho
if(obj.Definition == GlobalDefinitions.ams) {
obj.DeploymentState match {
//ams
if(vehicle.Definition == GlobalDefinitions.ams) {
state match {
case DriveState.Deployed =>
vehicleService ! VehicleServiceMessage.AMSDeploymentChange(continent)
sendResponse(PlanetsideAttributeMessage(obj.GUID, 81, 1))
sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 81, 1))
case DriveState.Undeploying =>
vehicleService ! VehicleServiceMessage.AMSDeploymentChange(continent)
sendResponse(PlanetsideAttributeMessage(obj.GUID, 81, 0))
sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 81, 0))
case DriveState.Mobile | DriveState.State7 =>
case _ => ;
}
}
if(obj.Definition == GlobalDefinitions.ant) {
obj.DeploymentState match {
case DriveState.Deployed =>
// We only want this WSA (not other player's WSA) to manage timers
if(vehicle.Seat(0).get.Occupant.contains(player)){
// Start ntu regeneration
// If vehicle sends UseItemMessage with silo as target NTU regeneration will be disabled and orb particles will be disabled
antChargingTick = context.system.scheduler.scheduleOnce(1000 milliseconds, self, NtuCharging(player, vehicle))
}
case DriveState.Undeploying =>
// We only want this WSA (not other player's WSA) to manage timers
if(vehicle.Seat(0).get.Occupant.contains(player)){
antChargingTick.cancel() // Stop charging NTU if charging
}
//ant
else if(vehicle.Definition == GlobalDefinitions.ant) {
state match {
case DriveState.Deployed =>
// We only want this WSA (not other player's WSA) to manage timers
if(vehicle.Seat(0).get.Occupant.contains(player)){
// Start ntu regeneration
// If vehicle sends UseItemMessage with silo as target NTU regeneration will be disabled and orb particles will be disabled
antChargingTick = context.system.scheduler.scheduleOnce(1000 milliseconds, self, NtuCharging(player, vehicle))
}
case DriveState.Undeploying =>
// We only want this WSA (not other player's WSA) to manage timers
if(vehicle.Seat(0).get.Occupant.contains(player)){
antChargingTick.cancel() // Stop charging NTU if charging
}
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(obj.GUID, 52, 0L)) // panel glow off
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(obj.GUID, 49, 0L)) // orb particles off
case DriveState.Mobile | DriveState.State7 | DriveState.Deploying =>
case _ => ;
}
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(vehicle.GUID, 52, 0L)) // panel glow off
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(vehicle.GUID, 49, 0L)) // orb particles off
case DriveState.Mobile | DriveState.State7 | DriveState.Deploying =>
case _ => ;
}
}
//router
else if(vehicle.Definition == GlobalDefinitions.router) {
state match {
case DriveState.Deploying =>
vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
case Some(util : Utility.InternalTelepad) =>
util.Active = true
case _ =>
log.warn(s"DeploymentActivities: could not find internal telepad in router@${vehicle.GUID.guid} while $state")
}
case DriveState.Deployed =>
//let the timer do all the work
localService ! LocalServiceMessage(continent.Id, LocalAction.ToggleTeleportSystem(PlanetSideGUID(0), vehicle, TelepadLike.AppraiseTeleportationSystem(vehicle, continent)))
case DriveState.Undeploying =>
//deactivate internal router before trying to reset the system
vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
case Some(util : Utility.InternalTelepad) =>
//any telepads linked with internal mechanism must be deconstructed
continent.GUID(util.Telepad) match {
case Some(telepad : TelepadDeployable) =>
localService ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(telepad), continent))
localService ! LocalServiceMessage.Deployables(RemoverActor.AddTask(telepad, continent, Some(0 milliseconds)))
case Some(_) | None => ;
}
util.Active = false
localService ! LocalServiceMessage(continent.Id, LocalAction.ToggleTeleportSystem(PlanetSideGUID(0), vehicle, None))
case _ =>
log.warn(s"DeploymentActivities: could not find internal telepad in router@${vehicle.GUID.guid} while $state")
}
case _ => ;
}
}
case _ => ;
}
}
@ -5934,7 +6116,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
})
val triggers = RemoveBoomerTriggersFromInventory()
triggers.foreach(trigger =>{ NormalItemDrop(obj, continent, avatarService)(trigger) })
triggers.foreach(trigger => { NormalItemDrop(obj, continent, avatarService)(trigger) })
}
}
@ -6628,7 +6810,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
val item = definition.Item
val deployables = avatar.Deployables
val (curr, max) = deployables.CountDeployable(item)
log.info(s"FinalizeDeployable: ${definition.Name}")
log.info(s"DeployableBuildActivity: ${definition.Name}")
//two potential messages related to numerical limitations of deployables
if(!avatar.Deployables.Available(obj)) {
val (removed, msg) = {
@ -6640,9 +6822,15 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
}
removed match {
case Some(telepad : TelepadDeployable) =>
telepad.Owner = None
telepad.OwnerName = None
localService ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(telepad), continent))
localService ! LocalServiceMessage.Deployables(RemoverActor.AddTask(telepad, continent, Some(0 seconds))) //normal decay
case Some(old) =>
old.Position = Vector3.Zero
old.Owner = None
old.OwnerName = None
localService ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(old), continent))
localService ! LocalServiceMessage.Deployables(RemoverActor.AddTask(old, continent, Some(0 seconds)))
if(msg) { //max test
sendResponse(ChatMsg(ChatMessageType.UNK_229, false, "", s"@${definition.Descriptor}OldestDestroyed", None))
@ -6651,6 +6839,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.warn(s"DeployableBuildActivity: how awkward: we probably shouldn't be allowed to build this deployable right now")
}
}
else if(obj.isInstanceOf[TelepadDeployable]) {
//always treat the telepad we are putting down as the first and only one
sendResponse(ObjectDeployedMessage.Success(definition.Name, 1, 1))
}
else {
sendResponse(ObjectDeployedMessage.Success(definition.Name, curr + 1, max))
val (catCurr, catMax) = deployables.CountCategory(item)
@ -6940,9 +7132,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
*/
def RemoveBoomerTriggersFromInventory() : List[BoomerTrigger] = {
val player_guid = player.GUID
((player.Inventory.Items.collect({ case entry @ InventoryItem(obj : BoomerTrigger, index) => (obj, index) })) ++
(player.Holsters()
.zipWithIndex
val holstersWithIndex = player.Holsters().zipWithIndex
((player.Inventory.Items.collect({ case InventoryItem(obj : BoomerTrigger, index) => (obj, index) })) ++
(holstersWithIndex
.map({ case ((slot, index)) => (slot.Equipment, index) })
.collect { case ((Some(obj : BoomerTrigger), index)) => (obj, index) }
)
@ -7128,6 +7320,158 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
}
/**
* Attempt to link the router teleport system using the provided terminal information.
* Although additional states are necessary to properly use the teleportation system,
* e.g., deployment state, active state of the endpoints, etc.,
* this decision is not made factoring those other conditions.
* @param router the vehicle that houses one end of the teleportation system (the `InternalTelepad` object)
* @param systemPlan specific object identification of the two endpoints of the teleportation system;
* if absent, the knowable endpoint is deleted from the client reflexively
*/
def ToggleTeleportSystem(router : Vehicle, systemPlan : Option[(Utility.InternalTelepad, TelepadDeployable)]) : Unit = {
StartBundlingPackets()
systemPlan match {
case Some((internalTelepad, remoteTelepad)) =>
LinkRouterToRemoteTelepad(router, internalTelepad, remoteTelepad)
case _ =>
router.Utility(UtilityType.internal_router_telepad_deployable) match {
case Some(util : Utility.InternalTelepad) =>
sendResponse(ObjectDeleteMessage(util.GUID, 0))
case _ => ;
}
}
StopBundlingPackets()
}
/**
* Link the router teleport system using the provided terminal information.
* The internal telepad is made known of the remote telepad, creating the link.
* @param router the vehicle that houses one end of the teleportation system (the `internalTelepad`)
* @param internalTelepad the endpoint of the teleportation system housed by the router
* @param remoteTelepad the endpoint of the teleportation system that exists in the environment
*/
def LinkRouterToRemoteTelepad(router : Vehicle, internalTelepad : Utility.InternalTelepad, remoteTelepad : TelepadDeployable) : Unit = {
internalTelepad.Telepad = remoteTelepad.GUID //necessary; backwards link to the (new) telepad
CreateRouterInternalTelepad(router, internalTelepad)
LinkRemoteTelepad(remoteTelepad.GUID)
}
/**
* Create the mechanism that serves as one endpoint of the linked router teleportation system.<br>
* <br>
* Technically, the mechanism - an `InternalTelepad` object - is always made to exist
* due to how the Router vehicle object is encoded into an `ObjectCreateMessage` packet.
* Regardless, that internal mechanism is created anew each time the system links a new remote telepad.
* @param router the vehicle that houses one end of the teleportation system (the `internalTelepad`)
* @param internalTelepad the endpoint of the teleportation system housed by the router
*/
def CreateRouterInternalTelepad(router : Vehicle, internalTelepad : PlanetSideGameObject with TelepadLike) : Unit = {
//create the interal telepad each time the link is made
val rguid = router.GUID
val uguid = internalTelepad.GUID
val udef = internalTelepad.Definition
/*
the following instantiation and configuration creates the internal Router component
normally dispatched while the Router is transitioned into its Deploying state
it is safe, however, to perform these actions at any time during and after the Deploying state
*/
sendResponse(
ObjectCreateMessage(
udef.ObjectId,
uguid,
ObjectCreateMessageParent(rguid, 2), //TODO stop assuming slot number
udef.Packet.ConstructorData(internalTelepad).get
)
)
sendResponse(GenericObjectActionMessage(uguid, 108))
sendResponse(GenericObjectActionMessage(uguid, 120))
/*
the following configurations create the interactive beam underneath the Deployed Router
normally dispatched after the warm-up timer has completed
*/
sendResponse(GenericObjectActionMessage(uguid, 108))
sendResponse(GenericObjectActionMessage(uguid, 112))
}
/**
* na
* @param telepadGUID na
*/
def LinkRemoteTelepad(telepadGUID: PlanetSideGUID) : Unit = {
sendResponse(GenericObjectActionMessage(telepadGUID, 108))
sendResponse(GenericObjectActionMessage(telepadGUID, 112))
}
/**
* A player uses a fully-linked Router teleportation system.
* @param router the Router vehicle
* @param internalTelepad the internal telepad within the Router vehicle
* @param remoteTelepad the remote telepad that is currently associated with this Router
* @param src the origin of the teleportation (where the player starts)
* @param dest the destination of the teleportation (where the player is going)
*/
def UseRouterTelepadSystem(router: Vehicle, internalTelepad: InternalTelepad, remoteTelepad: TelepadDeployable, src: PlanetSideGameObject with TelepadLike, dest: PlanetSideGameObject with TelepadLike) = {
val time = System.nanoTime
if(time - recentTeleportAttempt > (2 seconds).toNanos && router.DeploymentState == DriveState.Deployed && internalTelepad.Active && remoteTelepad.Active) {
val pguid = player.GUID
val sguid = src.GUID
val dguid = dest.GUID
StartBundlingPackets()
sendResponse(PlayerStateShiftMessage(ShiftState(0, dest.Position, player.Orientation.z, player.Velocity)))
UseRouterTelepadEffect(pguid, sguid, dguid)
StopBundlingPackets()
// vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(router), continent))
// vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(router, continent, router.Definition.DeconstructionTime))
localService ! LocalServiceMessage(continent.Id, LocalAction.RouterTelepadTransport(pguid, pguid, sguid, dguid))
}
else {
log.warn(s"UseRouterTelepadSystem: can not teleport")
}
recentTeleportAttempt = time
}
/**
* Animate(?) a player using a fully-linked Router teleportation system.
* In reality, this seems to do nothing visually?
* @param playerGUID the player being teleported
* @param srcGUID the origin of the teleportation
* @param destGUID the destination of the teleportation
*/
def UseRouterTelepadEffect(playerGUID : PlanetSideGUID, srcGUID : PlanetSideGUID, destGUID : PlanetSideGUID) : Unit = {
sendResponse(PlanetsideAttributeMessage(playerGUID, 64, 1)) //what does this do?
sendResponse(GenericObjectActionMessage(srcGUID, 124))
sendResponse(GenericObjectActionMessage(destGUID, 128))
}
/**
* Before a vehicle is removed from the game world, the following actions must be performed.
* @param vehicle the vehicle
*/
def BeforeUnloadVehicle(vehicle : Vehicle) : Unit = {
vehicle.Definition match {
case GlobalDefinitions.router =>
log.info("BeforeUnload: cleaning up after a router ...")
(vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
case Some(util : Utility.InternalTelepad) =>
val telepad = util.Telepad
util.Active = false
util.Telepad = None
continent.GUID(telepad)
case _ =>
None
}) match {
case Some(telepad : TelepadDeployable) =>
log.info(s"BeforeUnload: deconstructing telepad $telepad that was linked to router $vehicle ...")
telepad.Active = false
localService ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(telepad), continent))
localService ! LocalServiceMessage.Deployables(RemoverActor.AddTask(telepad, continent, Some(0 seconds)))
case _ => ;
}
case _ => ;
}
}
def failWithError(error : String) = {
log.error(error)
sendResponse(ConnectionClose())