diff --git a/common/src/main/scala/net/psforever/objects/Deployables.scala b/common/src/main/scala/net/psforever/objects/Deployables.scala index eea8be37..de1c9ce0 100644 --- a/common/src/main/scala/net/psforever/objects/Deployables.scala +++ b/common/src/main/scala/net/psforever/objects/Deployables.scala @@ -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) } ) } } diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 6515c43d..622a7482 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -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 } } diff --git a/common/src/main/scala/net/psforever/objects/Telepad.scala b/common/src/main/scala/net/psforever/objects/Telepad.scala new file mode 100644 index 00000000..d4bfb523 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/Telepad.scala @@ -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) + } +} \ No newline at end of file diff --git a/common/src/main/scala/net/psforever/objects/TelepadDeployable.scala b/common/src/main/scala/net/psforever/objects/TelepadDeployable.scala new file mode 100644 index 00000000..3916384b --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/TelepadDeployable.scala @@ -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 diff --git a/common/src/main/scala/net/psforever/objects/Vehicle.scala b/common/src/main/scala/net/psforever/objects/Vehicle.scala index 9306468d..249923ba 100644 --- a/common/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/common/src/main/scala/net/psforever/objects/Vehicle.scala @@ -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 diff --git a/common/src/main/scala/net/psforever/objects/avatar/DeployableToolbox.scala b/common/src/main/scala/net/psforever/objects/avatar/DeployableToolbox.scala index 972fc9e0..c3e3358c 100644 --- a/common/src/main/scala/net/psforever/objects/avatar/DeployableToolbox.scala +++ b/common/src/main/scala/net/psforever/objects/avatar/DeployableToolbox.scala @@ -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 _ => ; } diff --git a/common/src/main/scala/net/psforever/objects/ce/Deployable.scala b/common/src/main/scala/net/psforever/objects/ce/Deployable.scala index d04c6b01..e72c7544 100644 --- a/common/src/main/scala/net/psforever/objects/ce/Deployable.scala +++ b/common/src/main/scala/net/psforever/objects/ce/Deployable.scala @@ -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) } diff --git a/common/src/main/scala/net/psforever/objects/ce/TelepadLike.scala b/common/src/main/scala/net/psforever/objects/ce/TelepadLike.scala new file mode 100644 index 00000000..8c2bc3db --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/ce/TelepadLike.scala @@ -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 + } + } +} diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/InternalTelepadDeployableConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/InternalTelepadDeployableConverter.scala new file mode 100644 index 00000000..a2b50cf9 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/definition/converter/InternalTelepadDeployableConverter.scala @@ -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)) + } +} diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/TelepadConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/TelepadConverter.scala new file mode 100644 index 00000000..4a0b9094 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/definition/converter/TelepadConverter.scala @@ -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")) + } + } +} diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/TelepadDeployableConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/TelepadDeployableConverter.scala new file mode 100644 index 00000000..b424b070 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/definition/converter/TelepadDeployableConverter.scala @@ -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 + )) + } + } + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala index ceaf90c1..1b0b3797 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala @@ -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`. diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TeleportPadTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TeleportPadTerminalDefinition.scala new file mode 100644 index 00000000..4dd9872e --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TeleportPadTerminalDefinition.scala @@ -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}") + } + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala index 6aab6d0d..387660a0 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala @@ -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 } diff --git a/common/src/main/scala/net/psforever/objects/vehicles/Utility.scala b/common/src/main/scala/net/psforever/objects/vehicles/Utility.scala index dc7dfe27..ee984b87 100644 --- a/common/src/main/scala/net/psforever/objects/vehicles/Utility.scala +++ b/common/src/main/scala/net/psforever/objects/vehicles/Utility.scala @@ -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.
@@ -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 = { } } diff --git a/common/src/main/scala/net/psforever/objects/zones/Zone.scala b/common/src/main/scala/net/psforever/objects/zones/Zone.scala index 1690ff21..4cb2f77c 100644 --- a/common/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -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 diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ContainedTelepadDeployableData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ContainedTelepadDeployableData.scala new file mode 100644 index 00000000..2d7ff06e --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ContainedTelepadDeployableData.scala @@ -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) + } + ) +} diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedTelepadData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedTelepadData.scala new file mode 100644 index 00000000..230fb666 --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedTelepadData.scala @@ -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) + } + ) +} diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala index 5f32d07f..c4766570 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala @@ -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") diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/TelepadData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/TelepadData.scala new file mode 100644 index 00000000..b4ed0f0b --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/TelepadData.scala @@ -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) + } + ) +} diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/TelepadDeployableData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/TelepadDeployableData.scala new file mode 100644 index 00000000..369f641a --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/TelepadDeployableData.scala @@ -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] +} diff --git a/common/src/main/scala/services/RemoverActor.scala b/common/src/main/scala/services/RemoverActor.scala index 703b53c2..1f9f7371 100644 --- a/common/src/main/scala/services/RemoverActor.scala +++ b/common/src/main/scala/services/RemoverActor.scala @@ -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 } diff --git a/common/src/main/scala/services/local/LocalAction.scala b/common/src/main/scala/services/local/LocalAction.scala index 4dc109d4..76964b84 100644 --- a/common/src/main/scala/services/local/LocalAction.scala +++ b/common/src/main/scala/services/local/LocalAction.scala @@ -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 } diff --git a/common/src/main/scala/services/local/LocalResponse.scala b/common/src/main/scala/services/local/LocalResponse.scala index 7963a1af..f2cae70c 100644 --- a/common/src/main/scala/services/local/LocalResponse.scala +++ b/common/src/main/scala/services/local/LocalResponse.scala @@ -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 } diff --git a/common/src/main/scala/services/local/LocalService.scala b/common/src/main/scala/services/local/LocalService.scala index 8f5c5e21..39ec51f2 100644 --- a/common/src/main/scala/services/local/LocalService.scala +++ b/common/src/main/scala/services/local/LocalService.scala @@ -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.
*
diff --git a/common/src/main/scala/services/local/LocalServiceMessage.scala b/common/src/main/scala/services/local/LocalServiceMessage.scala index 4bbe7291..07164a26 100644 --- a/common/src/main/scala/services/local/LocalServiceMessage.scala +++ b/common/src/main/scala/services/local/LocalServiceMessage.scala @@ -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) } diff --git a/common/src/main/scala/services/local/support/RouterTelepadActivation.scala b/common/src/main/scala/services/local/support/RouterTelepadActivation.scala new file mode 100644 index 00000000..25f54881 --- /dev/null +++ b/common/src/main/scala/services/local/support/RouterTelepadActivation.scala @@ -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) +} diff --git a/common/src/main/scala/services/support/SupportActor.scala b/common/src/main/scala/services/support/SupportActor.scala index 820bcee8..0fb0d6a6 100644 --- a/common/src/main/scala/services/support/SupportActor.scala +++ b/common/src/main/scala/services/support/SupportActor.scala @@ -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) diff --git a/common/src/main/scala/services/vehicle/VehicleAction.scala b/common/src/main/scala/services/vehicle/VehicleAction.scala index 1ae95ea8..c47b332f 100644 --- a/common/src/main/scala/services/vehicle/VehicleAction.scala +++ b/common/src/main/scala/services/vehicle/VehicleAction.scala @@ -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 diff --git a/common/src/main/scala/services/vehicle/VehicleResponse.scala b/common/src/main/scala/services/vehicle/VehicleResponse.scala index 5c972bf6..07ba5901 100644 --- a/common/src/main/scala/services/vehicle/VehicleResponse.scala +++ b/common/src/main/scala/services/vehicle/VehicleResponse.scala @@ -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 diff --git a/common/src/main/scala/services/vehicle/VehicleService.scala b/common/src/main/scala/services/vehicle/VehicleService.scala index cacfae0d..e727528f 100644 --- a/common/src/main/scala/services/vehicle/VehicleService.scala +++ b/common/src/main/scala/services/vehicle/VehicleService.scala @@ -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( diff --git a/common/src/main/scala/services/vehicle/support/VehicleRemover.scala b/common/src/main/scala/services/vehicle/support/VehicleRemover.scala index b60c9356..8bcf4fae 100644 --- a/common/src/main/scala/services/vehicle/support/VehicleRemover.scala +++ b/common/src/main/scala/services/vehicle/support/VehicleRemover.scala @@ -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) } diff --git a/common/src/test/scala/base/ActorTest.scala b/common/src/test/scala/base/ActorTest.scala index a1d594fc..7b265209 100644 --- a/common/src/test/scala/base/ActorTest.scala +++ b/common/src/test/scala/base/ActorTest.scala @@ -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 + } + } } diff --git a/common/src/test/scala/game/objectcreate/ContainedTelepadDeployableDataTest.scala b/common/src/test/scala/game/objectcreate/ContainedTelepadDeployableDataTest.scala new file mode 100644 index 00000000..f32b7c26 --- /dev/null +++ b/common/src/test/scala/game/objectcreate/ContainedTelepadDeployableDataTest.scala @@ -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 + } + } +} diff --git a/common/src/test/scala/game/objectcreate/TelepadDataTest.scala b/common/src/test/scala/game/objectcreate/TelepadDataTest.scala new file mode 100644 index 00000000..10507986 --- /dev/null +++ b/common/src/test/scala/game/objectcreate/TelepadDataTest.scala @@ -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 + } + } +} diff --git a/common/src/test/scala/game/objectcreate/TelepadDeployableDataTest.scala b/common/src/test/scala/game/objectcreate/TelepadDeployableDataTest.scala new file mode 100644 index 00000000..e0d74b44 --- /dev/null +++ b/common/src/test/scala/game/objectcreate/TelepadDeployableDataTest.scala @@ -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 + } + } +} diff --git a/common/src/test/scala/game/objectcreatedetailed/DetailedTelepadDataTest.scala b/common/src/test/scala/game/objectcreatedetailed/DetailedTelepadDataTest.scala new file mode 100644 index 00000000..5145b00e --- /dev/null +++ b/common/src/test/scala/game/objectcreatedetailed/DetailedTelepadDataTest.scala @@ -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 + } + } +} diff --git a/common/src/test/scala/objects/ConverterTest.scala b/common/src/test/scala/objects/ConverterTest.scala index 72e32608..4b4d8c1d 100644 --- a/common/src/test/scala/objects/ConverterTest.scala +++ b/common/src/test/scala/objects/ConverterTest.scala @@ -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 { diff --git a/common/src/test/scala/objects/DeployableTest.scala b/common/src/test/scala/objects/DeployableTest.scala index e79ac0d9..1c299dbd 100644 --- a/common/src/test/scala/objects/DeployableTest.scala +++ b/common/src/test/scala/objects/DeployableTest.scala @@ -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 = { diff --git a/common/src/test/scala/objects/DeployableToolboxTest.scala b/common/src/test/scala/objects/DeployableToolboxTest.scala index afc96888..6edd30f8 100644 --- a/common/src/test/scala/objects/DeployableToolboxTest.scala +++ b/common/src/test/scala/objects/DeployableToolboxTest.scala @@ -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 { diff --git a/common/src/test/scala/objects/EquipmentTest.scala b/common/src/test/scala/objects/EquipmentTest.scala index 6c638c94..1fc0aeea 100644 --- a/common/src/test/scala/objects/EquipmentTest.scala +++ b/common/src/test/scala/objects/EquipmentTest.scala @@ -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 diff --git a/common/src/test/scala/objects/UtilityTest.scala b/common/src/test/scala/objects/UtilityTest.scala index b061a0ff..13920cb5 100644 --- a/common/src/test/scala/objects/UtilityTest.scala +++ b/common/src/test/scala/objects/UtilityTest.scala @@ -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) diff --git a/common/src/test/scala/service/LocalServiceTest.scala b/common/src/test/scala/service/LocalServiceTest.scala index 55001453..f52f007f 100644 --- a/common/src/test/scala/service/LocalServiceTest.scala +++ b/common/src/test/scala/service/LocalServiceTest.scala @@ -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) diff --git a/common/src/test/scala/service/RemoverActorTest.scala b/common/src/test/scala/service/RemoverActorTest.scala index c0cc6234..1de2dc50 100644 --- a/common/src/test/scala/service/RemoverActorTest.scala +++ b/common/src/test/scala/service/RemoverActorTest.scala @@ -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) } }) diff --git a/common/src/test/scala/service/RouterTelepadActivationTest.scala b/common/src/test/scala/service/RouterTelepadActivationTest.scala new file mode 100644 index 00000000..2ad331ec --- /dev/null +++ b/common/src/test/scala/service/RouterTelepadActivationTest.scala @@ -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) + } + } +} diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 38d9e644..81541023 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -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:
* - the item is cavern equipment
* - the item is a `BoomerTrigger` type object
+ * - the item is a `router_telepad` type object
* - 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.
+ *
+ * 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())