Proximity Terminal Fix (#241)

* modifications to ProximityUnit and related classes to internalize the effect control for the terminal, removing the flywheel functiuonality from WSA

* proximity operations for medical terminals (the one in the Anguta lobby, specifically) is functional; some entities besides Player now keep track of the continent they possess, a necessary compromise for proximity ops

* repair rearm silos (Anguta courtyard East) demonstrate operational behavior when lattice benefits are active

* proximity terminals may now juggle multiple targets that move in and out of range and change validity over time

* previous rebase; preparing for special location + offset calculations for utilities

* working location-based proximity repair terminals for the lodestar, utilizing new fields in the vehicle definition, new configuration method in the utilities, and new calculations for Vector3 entities; some comments and tests

* separated ProximityTarget; updated existing terminals; restored defaults; preparing to fix tests

* tests involving the proximity unit workflow

* dismantling a portion of the proximity terminal machinery that is no longer necessary and only poses risk to the system; removing temporary code used to quickly test vehicle silo repairs
This commit is contained in:
Fate-JH 2018-12-23 21:09:12 -05:00 committed by GitHub
parent c4c8609238
commit 961ae1b93b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1320 additions and 888 deletions

View file

@ -18,7 +18,7 @@ import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition
import net.psforever.objects.serverobject.turret.{TurretDefinition, TurretUpgrade}
import net.psforever.objects.vehicles.{DestroyedVehicle, SeatArmorRestriction, UtilityType}
import net.psforever.objects.vital.{DamageType, StandardResolutions}
import net.psforever.types.{CertificationType, PlanetSideEmpire}
import net.psforever.types.{CertificationType, PlanetSideEmpire, Vector3}
import scala.collection.mutable
import scala.concurrent.duration._
@ -920,9 +920,13 @@ object GlobalDefinitions {
val medical_terminal = new MedicalTerminalDefinition(529)
val pad_landing = new RepairRearmSiloDefinition(719)
val portable_med_terminal = new MedicalTerminalDefinition(689)
val repair_silo = new RepairRearmSiloDefinition(729)
val pad_landing_frame = new MedicalTerminalDefinition(618)
val pad_landing_tower_frame = new MedicalTerminalDefinition(619)
val repair_silo = new MedicalTerminalDefinition(729)
val spawn_pad = new VehicleSpawnPadDefinition
@ -938,33 +942,18 @@ object GlobalDefinitions {
val secondary_capture = new CaptureTerminalDefinition(751) // Tower CC
val lodestar_repair_terminal = new OrderTerminalDefinition { //TODO wrong object class
override def ObjectId : Int = 461
}
val lodestar_repair_terminal = new MedicalTerminalDefinition(461)
val multivehicle_rearm_terminal = new _OrderTerminalDefinition(576) {
Name = "multivehicle_rearm_terminal"
Page += 3 -> _OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
Page += 4 -> _OrderTerminalDefinition.VehicleLoadoutPage()
}
val multivehicle_rearm_terminal = new _OrderTerminalDefinition(576)
val bfr_rearm_terminal = new _OrderTerminalDefinition(142) {
Name = "bfr_rearm_terminal"
Page += 3 -> _OrderTerminalDefinition.EquipmentPage(Map.empty[String, ()=>Equipment]) //TODO add stock to page
Page += 4 -> _OrderTerminalDefinition.VehicleLoadoutPage()
}
val bfr_rearm_terminal = new _OrderTerminalDefinition(142)
val manned_turret = new TurretDefinition(480) {
Name = "manned_turret"
MaxHealth = 3600
Weapons += 1 -> new mutable.HashMap()
Weapons(1) += TurretUpgrade.None -> phalanx_sgl_hevgatcan
Weapons(1) += TurretUpgrade.AVCombo -> phalanx_avcombo
Weapons(1) += TurretUpgrade.FlakCombo -> phalanx_flakcombo
MountPoints += 1 -> 0
FactionLocked = true
ReserveAmmunition = false
}
val air_rearm_terminal = new _OrderTerminalDefinition(42)
val ground_rearm_terminal = new _OrderTerminalDefinition(384)
val manned_turret = new TurretDefinition(480)
initMiscellaneous()
/**
* Given a faction, provide the standard assault melee weapon.
@ -5411,7 +5400,9 @@ object GlobalDefinitions {
lodestar.MountPoints += 2 -> 1
lodestar.Cargo += 1 -> new CargoDefinition()
lodestar.Utilities += 2 -> UtilityType.lodestar_repair_terminal
lodestar.UtilityOffset += 2 -> Vector3(0, 20, 0)
lodestar.Utilities += 3 -> UtilityType.lodestar_repair_terminal
lodestar.UtilityOffset += 3 -> Vector3(0, -20, 0)
lodestar.Utilities += 4 -> UtilityType.multivehicle_rearm_terminal
lodestar.Utilities += 5 -> UtilityType.multivehicle_rearm_terminal
lodestar.Utilities += 6 -> UtilityType.bfr_rearm_terminal
@ -5599,4 +5590,92 @@ object GlobalDefinitions {
internal_router_telepad_deployable.DeployTime = Duration.create(1, "ms")
internal_router_telepad_deployable.Packet = new InternalTelepadDeployableConverter
}
/**
* Initialize `Miscellaneous` globals.
*/
private def initMiscellaneous() : Unit = {
adv_med_terminal.Name = "adv_med_terminal"
adv_med_terminal.Interval = 500
adv_med_terminal.HealAmount = 8
adv_med_terminal.ArmorAmount = 15
adv_med_terminal.UseRadius = 0.75f
adv_med_terminal.TargetValidation += ProximityTarget.Player -> ProximityTerminalControl.Validation.Medical
crystals_health_a.Name = "crystals_health_a"
crystals_health_a.Interval = 500
crystals_health_a.HealAmount = 4
crystals_health_a.UseRadius = 5
crystals_health_a.TargetValidation += ProximityTarget.Player -> ProximityTerminalControl.Validation.HealthCrystal
crystals_health_b.Name = "crystals_health_b"
crystals_health_b.Interval = 500
crystals_health_b.HealAmount = 4
crystals_health_b.UseRadius = 1.3f
crystals_health_b.TargetValidation += ProximityTarget.Player -> ProximityTerminalControl.Validation.HealthCrystal
medical_terminal.Name = "medical_terminal"
medical_terminal.Interval = 500
medical_terminal.HealAmount = 5
medical_terminal.ArmorAmount = 10
medical_terminal.UseRadius = 0.75f
medical_terminal.TargetValidation += ProximityTarget.Player -> ProximityTerminalControl.Validation.Medical
portable_med_terminal.Name = "portable_med_terminal"
portable_med_terminal.Interval = 500
portable_med_terminal.HealAmount = 5
portable_med_terminal.ArmorAmount = 10
portable_med_terminal.UseRadius = 3
portable_med_terminal.TargetValidation += ProximityTarget.Player -> ProximityTerminalControl.Validation.Medical
pad_landing_frame.Name = "pad_landing_frame"
pad_landing_frame.Interval = 1000
pad_landing_frame.HealAmount = 60
pad_landing_frame.UseRadius = 20
pad_landing_frame.TargetValidation += ProximityTarget.Aircraft -> ProximityTerminalControl.Validation.PadLanding
pad_landing_tower_frame.Name = "pad_landing_tower_frame"
pad_landing_tower_frame.Interval = 1000
pad_landing_tower_frame.HealAmount = 60
pad_landing_tower_frame.UseRadius = 20
pad_landing_tower_frame.TargetValidation += ProximityTarget.Aircraft -> ProximityTerminalControl.Validation.PadLanding
repair_silo.Name = "repair_silo"
repair_silo.Interval = 1000
repair_silo.HealAmount = 60
repair_silo.UseRadius = 20
repair_silo.TargetValidation += ProximityTarget.Vehicle -> ProximityTerminalControl.Validation.RepairSilo
lodestar_repair_terminal.Name = "lodestar_repair_terminal"
lodestar_repair_terminal.Interval = 1000
lodestar_repair_terminal.HealAmount = 60
lodestar_repair_terminal.UseRadius = 20
lodestar_repair_terminal.TargetValidation += ProximityTarget.Vehicle -> ProximityTerminalControl.Validation.RepairSilo
multivehicle_rearm_terminal.Name = "multivehicle_rearm_terminal"
multivehicle_rearm_terminal.Page += 3 -> _OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
multivehicle_rearm_terminal.Page += 4 -> _OrderTerminalDefinition.VehicleLoadoutPage()
bfr_rearm_terminal.Name = "bfr_rearm_terminal"
bfr_rearm_terminal.Page += 3 -> _OrderTerminalDefinition.EquipmentPage(Map.empty[String, ()=>Equipment]) //TODO add stock to page
bfr_rearm_terminal.Page += 4 -> _OrderTerminalDefinition.VehicleLoadoutPage()
air_rearm_terminal.Name = "air_rearm_terminal"
air_rearm_terminal.Page += 3 -> _OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
air_rearm_terminal.Page += 4 -> _OrderTerminalDefinition.VehicleLoadoutPage()
ground_rearm_terminal.Name = "ground_rearm_terminal"
ground_rearm_terminal.Page += 3 -> _OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
ground_rearm_terminal.Page += 4 -> _OrderTerminalDefinition.VehicleLoadoutPage()
manned_turret.Name = "manned_turret"
manned_turret.MaxHealth = 3600
manned_turret.Weapons += 1 -> new mutable.HashMap()
manned_turret.Weapons(1) += TurretUpgrade.None -> phalanx_sgl_hevgatcan
manned_turret.Weapons(1) += TurretUpgrade.AVCombo -> phalanx_avcombo
manned_turret.Weapons(1) += TurretUpgrade.FlakCombo -> phalanx_flakcombo
manned_turret.MountPoints += 1 -> 0
manned_turret.FactionLocked = true
manned_turret.ReserveAmmunition = false
}
}

View file

@ -8,6 +8,7 @@ import net.psforever.objects.loadouts.Loadout
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.objects.vital.{DamageResistanceModel, Vitality}
import net.psforever.objects.zones.ZoneAware
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types._
@ -18,7 +19,8 @@ class Player(private val core : Avatar) extends PlanetSideGameObject
with FactionAffinity
with Vitality
with ResistanceProfile
with Container {
with Container
with ZoneAware {
private var alive : Boolean = false
private var backpack : Boolean = false
private var health : Int = 0

View file

@ -1,6 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
import akka.actor.ActorRef
import net.psforever.objects.definition.VehicleDefinition
import net.psforever.objects.equipment.{Equipment, EquipmentSize}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryTile}
@ -10,6 +11,7 @@ import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.vehicles._
import net.psforever.objects.vital.{DamageResistanceModel, StandardResistanceProfile, Vitality}
import net.psforever.objects.zones.ZoneAware
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.PlanetSideEmpire
@ -65,6 +67,7 @@ import scala.annotation.tailrec
*/
class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServerObject
with FactionAffinity
with ZoneAware
with Mountable
with MountedWeapons
with Deployment
@ -80,6 +83,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
private var jammered : Boolean = false
private var cloaked : Boolean = false
private var capacitor : Int = 0
private var continent : String = "home2" //the zone id
/**
* Permissions control who gets to access different parts of the vehicle;
@ -513,6 +517,32 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
*/
def Definition : VehicleDefinition = vehicleDef
override def Continent : String = continent
override def Continent_=(zoneId : String) : String = {
continent = zoneId
Continent
}
def canEqual(other: Any): Boolean = other.isInstanceOf[Vehicle]
override def equals(other : Any) : Boolean = other match {
case that: Vehicle =>
(that canEqual this) &&
hashCode() == that.hashCode()
case _ =>
false
}
override def hashCode() : Int = {
Actor match {
case ActorRef.noSender =>
super.hashCode()
case actor =>
actor.hashCode()
}
}
/**
* Override the string representation to provide additional information.
* @return the string output
@ -600,7 +630,12 @@ object Vehicle {
vehicle.cargoHolds = vdef.Cargo.map({ case(num, definition) => num -> Cargo(definition)}).toMap
//create utilities
vehicle.utilities = vdef.Utilities.map({ case(num, util) => num -> Utility(util, vehicle) }).toMap
vehicle.utilities = vdef.Utilities.map({
case(num, util) =>
val obj = Utility(util, vehicle)
obj().LocationOffset = vdef.UtilityOffset.get(num)
num -> obj
}).toMap
//trunk
vdef.TrunkSize match {
case InventoryTile.None => ;

View file

@ -6,6 +6,7 @@ import net.psforever.objects.inventory.InventoryTile
import net.psforever.objects.vehicles.{DestroyedVehicle, UtilityType}
import net.psforever.objects.vital._
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
import net.psforever.types.Vector3
import scala.collection.mutable
import scala.concurrent.duration._
@ -29,6 +30,7 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId)
private val weapons : mutable.HashMap[Int, ToolDefinition] = mutable.HashMap[Int, ToolDefinition]()
private var deployment : Boolean = false
private val utilities : mutable.HashMap[Int, UtilityType.Value] = mutable.HashMap()
private val utilityOffsets : mutable.HashMap[Int, Vector3] = mutable.HashMap()
private var deploymentTime_Deploy : Int = 0 //ms
private var deploymentTime_Undeploy : Int = 0 //ms
private var trunkSize : InventoryTile = InventoryTile.None
@ -88,9 +90,10 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId)
Deployment
}
def Utilities : mutable.HashMap[Int, UtilityType.Value] = utilities
def UtilityOffset : mutable.HashMap[Int, Vector3] = utilityOffsets
def DeployTime : Int = deploymentTime_Deploy
def DeployTime_=(dtime : Int) : Int = {

View file

@ -1,12 +1,12 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject
import net.psforever.objects.Player
import net.psforever.objects.{PlanetSideGameObject, Player}
//temporary location for these messages
object CommonMessages {
final case class Use(player : Player)
final case class Unuse(player : Player)
final case class Use(player : Player, data : Option[Any] = None)
final case class Unuse(player : Player, data : Option[Any] = None)
final case class Hack(player : Player)
final case class ClearHack()
}

View file

@ -4,12 +4,15 @@ package net.psforever.objects.serverobject
import akka.actor.ActorRef
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.zones.ZoneAware
/**
* An object layered on top of the standard game object class that maintains an internal `ActorRef`.
* A measure of synchronization can be managed using this `Actor`.
*/
abstract class PlanetSideServerObject extends PlanetSideGameObject with FactionAffinity {
abstract class PlanetSideServerObject extends PlanetSideGameObject
with FactionAffinity
with ZoneAware {
private var actor = ActorRef.noSender
/**
@ -30,4 +33,8 @@ abstract class PlanetSideServerObject extends PlanetSideGameObject with Faction
}
actor
}
def Continent : String = "nowhere"
def Continent_=(zone : String) = Continent
}

View file

@ -1,8 +1,9 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.structures
import net.psforever.objects.Vehicle
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.types.PlanetSideEmpire
import net.psforever.types.{PlanetSideEmpire, Vector3}
/**
* Amenities are elements of the game that belong to other elements of the game.<br>
@ -15,7 +16,10 @@ import net.psforever.types.PlanetSideEmpire
* @see `FactionAffinity`
*/
abstract class Amenity extends PlanetSideServerObject {
/** what other entity has authority over this amenity; usually either a building or a vehicle */
private var owner : PlanetSideServerObject = Building.NoBuilding
/** if the entity exists at a specific position relative to the owner's position */
private var offset : Option[Vector3] = None
def Faction : PlanetSideEmpire.Value = Owner.Faction
@ -36,6 +40,26 @@ abstract class Amenity extends PlanetSideServerObject {
owner = obj.asInstanceOf[PlanetSideServerObject]
Owner
}
def LocationOffset : Vector3 = offset.getOrElse(Vector3.Zero)
def LocationOffset_=(off : Vector3) : Vector3 = LocationOffset_=(Some(off))
def LocationOffset_=(off : Option[Vector3]) : Vector3 = {
off match {
case Some(Vector3.Zero) =>
offset = None
case _ =>
offset = off
}
LocationOffset
}
override def Continent = Owner match {
case o : Building => o.Zone.Id
case o : Vehicle => o.Continent
case _ => super.Continent
}
}
object Amenity {

View file

@ -48,6 +48,10 @@ class Building(private val mapId : Int, private val zone : Zone, private val bui
def BuildingType : StructureType.Value = buildingType
override def Continent : String = zone.Id
override def Continent_=(zone : String) : String = Continent
def Definition: ObjectDefinition = Building.BuildingDefinition
}

View file

@ -1,28 +1,40 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import scala.concurrent.duration.{Duration, FiniteDuration}
/**
* The definition for any `Terminal` that is of a type "medical_terminal".
* This includes the functionality of the formal medical terminals and some of the cavern crystals.
* Do not confuse the game's internal "medical_terminal" object category and the actual `medical_terminal` object (529).
*/
class MedicalTerminalDefinition(objectId : Int) extends TerminalDefinition(objectId) with ProximityDefinition {
Name = if(objectId == 38) {
"adv_med_terminal"
private var interval : FiniteDuration = Duration(0, "seconds")
private var healAmount : Int = 0
private var armorAmount : Int = 0
def Interval : FiniteDuration = interval
def Interval_=(amount : Int) : FiniteDuration = {
Interval_=(Duration(amount, "milliseconds"))
}
else if(objectId == 225) {
"crystals_health_a"
def Interval_=(amount : FiniteDuration) : FiniteDuration = {
interval = amount
Interval
}
else if(objectId == 226) {
"crystals_health_b"
def HealAmount : Int = healAmount
def HealAmount_=(amount : Int) : Int = {
healAmount = amount
HealAmount
}
else if(objectId == 529) {
"medical_terminal"
}
else if(objectId == 689) {
"portable_med_terminal"
}
else {
throw new IllegalArgumentException("medical terminal must be either object id 38, 225, 226, 529, or 689")
def ArmorAmount : Int = armorAmount
def ArmorAmount_=(amount : Int) : Int = {
armorAmount = amount
ArmorAmount
}
}

View file

@ -1,9 +1,11 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player
import net.psforever.objects.{PlanetSideGameObject, Player}
import net.psforever.packet.game.ItemTransactionMessage
import scala.collection.mutable
/**
* The definition for any `Terminal` that possesses a proximity-based effect.
* This includes the limited proximity-based functionality of the formal medical terminals
@ -11,5 +13,30 @@ import net.psforever.packet.game.ItemTransactionMessage
* Objects created by this definition being linked by their use of `ProximityTerminalUseMessage`.
*/
trait ProximityDefinition {
private var useRadius : Float = 0f //TODO belongs on a wider range of object definitions
private val targetValidation : mutable.HashMap[ProximityTarget.Value, (PlanetSideGameObject)=>Boolean] = new mutable.HashMap[ProximityTarget.Value, (PlanetSideGameObject)=>Boolean]()
def UseRadius : Float = useRadius
def UseRadius_=(radius : Float) : Float = {
useRadius = radius
UseRadius
}
def TargetValidation : mutable.HashMap[ProximityTarget.Value, (PlanetSideGameObject)=>Boolean] = targetValidation
def Validations : Seq[(PlanetSideGameObject)=>Boolean] = {
targetValidation.headOption match {
case Some(_) =>
targetValidation.values.toSeq
case None =>
Seq(ProximityDefinition.Invalid)
}
}
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal()
}
object ProximityDefinition {
protected val Invalid : (PlanetSideGameObject=>Boolean) = (_ : PlanetSideGameObject) => false
}

View file

@ -0,0 +1,15 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
/**
* A classification of the target of this terminal's interactions.
* Arbitrary, but useful.
*/
object ProximityTarget extends Enumeration {
val
Aircraft,
Equipment,
Player,
Vehicle
= Value
}

View file

@ -1,6 +1,10 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.types.Vector3
import services.Service
/**
* A server object that is a "terminal" that can be accessed for amenities and services,
* triggered when a certain distance from the unit itself (proximity-based).<br>
@ -35,4 +39,33 @@ object ProximityTerminal {
obj.Actor = context.actorOf(Props(classOf[ProximityTerminalControl], obj), s"${tdef.Name}_$id")
obj
}
/**
* Instantiate an configure a `Terminal` object, with position coordinates.
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
* @param id the unique id that will be assigned to this entity
* @param context a context to allow the object to properly set up `ActorSystem` functionality
* @return the `Terminal` object
*/
def Constructor(tdef : TerminalDefinition with ProximityDefinition, pos : Vector3)(id : Int, context : ActorContext) : Terminal = {
import akka.actor.Props
val obj = ProximityTerminal(tdef)
obj.Position = pos
obj.Actor = context.actorOf(Props(classOf[ProximityTerminalControl], obj), s"${tdef.Name}_$id")
obj
}
/**
* Assemble some logic for a provided owned object after both it ands its owner have been constructed.
* @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[ProximityTerminalControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}")
obj.Actor ! Service.Startup()
}
}
}

View file

@ -1,9 +1,14 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import akka.actor.Actor
import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.objects._
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import services.{Service, ServiceManager}
import scala.collection.mutable
import scala.concurrent.duration._
/**
* An `Actor` that handles messages being dispatched to a specific `ProximityTerminal`.
@ -11,21 +16,155 @@ import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffi
* it returns the same type of messages - wrapped in a `TerminalMessage` - to the `sender`.
* @param term the proximity unit (terminal)
*/
class ProximityTerminalControl(term : Terminal with ProximityUnit) extends Actor with FactionAffinityBehavior.Check with ProximityUnit.Use {
class ProximityTerminalControl(term : Terminal with ProximityUnit) extends Actor with FactionAffinityBehavior.Check {
var service : ActorRef = ActorRef.noSender
var terminalAction : Cancellable = DefaultCancellable.obj
val callbacks : mutable.ListBuffer[ActorRef] = new mutable.ListBuffer[ActorRef]()
val log = org.log4s.getLogger
def FactionObject : FactionAffinity = term
def TerminalObject : Terminal with ProximityUnit = term
def receive : Receive = checkBehavior
.orElse(proximityBehavior)
def receive : Receive = Start
def Start : Receive = checkBehavior
.orElse {
case Service.Startup() =>
ServiceManager.serviceManager ! ServiceManager.Lookup("local")
case ServiceManager.LookupResult("local", ref) =>
service = ref
context.become(Run)
case _ => ;
}
def Run : Receive = checkBehavior
.orElse {
case CommonMessages.Use(_, Some(target : PlanetSideGameObject)) =>
if(term.Definition.asInstanceOf[ProximityDefinition].Validations.exists(p => p(target))) {
Use(target, term.Continent, sender)
}
case CommonMessages.Use(_, Some((target : PlanetSideGameObject, callback : ActorRef))) =>
if(term.Definition.asInstanceOf[ProximityDefinition].Validations.exists(p => p(target))) {
Use(target, term.Continent, callback)
}
case CommonMessages.Use(_, _) =>
log.warn(s"unexpected format for CommonMessages.Use in this context")
case CommonMessages.Unuse(_, Some(target : PlanetSideGameObject)) =>
Unuse(target, term.Continent)
case CommonMessages.Unuse(_, _) =>
log.warn(s"unexpected format for CommonMessages.Unuse in this context")
case ProximityTerminalControl.TerminalAction() =>
val proxDef = term.Definition.asInstanceOf[ProximityDefinition]
val validateFunc : PlanetSideGameObject=>Boolean = term.Validate(proxDef.UseRadius * proxDef.UseRadius, proxDef.Validations)
val callbackList = callbacks.toList
term.Targets.zipWithIndex.foreach({ case((target, index)) =>
if(validateFunc(target)) {
callbackList.lift(index) match {
case Some(cback) =>
cback ! ProximityUnit.Action(term, target)
case None =>
log.error(s"improper callback registered for $target on $term in zone ${term.Owner.Continent}; this may be recoverable")
}
}
else {
Unuse(target, term.Continent)
}
})
case CommonMessages.Hack(player) =>
term.HackedBy = player
sender ! true
case CommonMessages.ClearHack() =>
term.HackedBy = None
case _ => ;
case ProximityUnit.Action(_, _) =>
//reserved
case msg =>
log.warn(s"unexpected message $msg")
}
def Use(target : PlanetSideGameObject, zone : String, callback : ActorRef) : Unit = {
val hadNoUsers = term.NumberUsers == 0
if(term.AddUser(target)) {
log.info(s"ProximityTerminal.Use: unit ${term.Definition.Name}@${term.GUID.guid} will act on $target")
//add callback
callbacks += callback
//activation
if(term.NumberUsers == 1 && hadNoUsers) {
val medDef = term.Definition.asInstanceOf[MedicalTerminalDefinition]
import scala.concurrent.ExecutionContext.Implicits.global
terminalAction.cancel
terminalAction = context.system.scheduler.schedule(500 milliseconds, medDef.Interval, self, ProximityTerminalControl.TerminalAction())
service ! Terminal.StartProximityEffect(term)
}
}
else {
log.warn(s"ProximityTerminal.Use: $target was rejected by unit ${term.Definition.Name}@${term.GUID.guid}")
}
}
def Unuse(target : PlanetSideGameObject, zone : String) : Unit = {
val whereTarget = term.Targets.indexWhere(_ eq target)
val previousUsers = term.NumberUsers
val hadUsers = previousUsers > 0
if(whereTarget > -1 && term.RemoveUser(target)) {
log.info(s"ProximityTerminal.Unuse: unit ${term.Definition.Name}@${term.GUID.guid} will cease operation on $target")
//remove callback
callbacks.remove(whereTarget)
//de-activation (global / local)
if(term.NumberUsers == 0 && hadUsers) {
terminalAction.cancel
service ! Terminal.StopProximityEffect(term)
}
}
else {
log.debug(s"ProximityTerminal.Unuse: target by proximity $target is not known to $term, though the unit tried to 'Unuse' it")
}
}
override def toString : String = term.Definition.Name
}
object ProximityTerminalControl {
object Validation {
def Medical(target : PlanetSideGameObject) : Boolean = target match {
case p : Player =>
p.Health > 0 && (p.Health < p.MaxHealth || p.Armor < p.MaxArmor)
case _ =>
false
}
def HealthCrystal(target : PlanetSideGameObject) : Boolean = target match {
case p : Player =>
p.Health > 0 && p.Health < p.MaxHealth
case _ =>
false
}
def RepairSilo(target : PlanetSideGameObject) : Boolean = target match {
case v : Vehicle =>
!GlobalDefinitions.isFlightVehicle(v.Definition) && v.Health > 0 && v.Health < v.MaxHealth
case _ =>
false
}
def PadLanding(target : PlanetSideGameObject) : Boolean = target match {
case v : Vehicle =>
GlobalDefinitions.isFlightVehicle(v.Definition) && v.Health > 0 && v.Health < v.MaxHealth
case _ =>
false
}
}
private case class TerminalAction()
}

View file

@ -1,9 +1,10 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.objects.PlanetSideGameObject
import net.psforever.types.Vector3
import scala.collection.mutable
/**
* A server object that provides a service, triggered when a certain distance from the unit itself (proximity-based).
@ -16,45 +17,78 @@ trait ProximityUnit {
/**
* A list of targets that are currently affected by this proximity unit.
*/
private var targets : Set[PlanetSideGUID] = Set.empty
private var targets : mutable.ListBuffer[PlanetSideGameObject] = mutable.ListBuffer[PlanetSideGameObject]()
def Targets : Seq[PlanetSideGameObject] = targets toList
def NumberUsers : Int = targets.size
def AddUser(player_guid : PlanetSideGUID) : Int = {
targets += player_guid
NumberUsers
/**
* Accept a new target for this unit.
* @param target the new target
* @return `true`, if the entrant has been added and is new to the list;
* `false` if the entrant is already in the list or can not be added
*/
def AddUser(target : PlanetSideGameObject) : Boolean = {
val alreadyContains = targets.contains(target)
if(!alreadyContains) {
targets += target
targets.contains(target)
}
else {
false
}
}
def RemoveUser(player_guid : PlanetSideGUID) : Int = {
targets -= player_guid
NumberUsers
/**
* Remove an existing target for this unit.
* @param target the target
* @return `true`, if the submitted entity was previously in the list but is not longer in the list;
* `false`, if the submitted entity was never in the list or can not be removed
*/
def RemoveUser(target : PlanetSideGameObject) : Boolean = {
val alreadyContains = targets.contains(target)
if(alreadyContains) {
targets -= target
!targets.contains(target)
}
else {
false
}
}
/**
* Confirm whether the entity is a valid target for the effects of this unit.
* @param target the submitted entity
* @return `true`, if the entity passes the validation tests;
* `false`, otherwise
*/
def Validate(target : PlanetSideGameObject) : Boolean = {
val proxDef = Definition.asInstanceOf[ProximityDefinition]
val radius = proxDef.UseRadius * proxDef.UseRadius
val validation = proxDef.Validations
Validate(radius, validation)(target)
}
/**
* Confirm whether the entity is a valid target for the effects of this unit.
* Curried to accept parameters for the tests separately from the entity to be tested.
* In general, the two requirements beyond the custom validations involve
* distance (from the unit)
* and inclusiveness (known to the unit beforehand).
* @param radius the squared minimum activation distance
* @param validations the custom tests that the entity must pass to be considered valid;
* in general, regardless of the type of the target, any of the tests must be passed
* @param target the submitted entity
* @return `true`, if the entity passes the validation tests;
* `false`, otherwise
*/
def Validate(radius : Float, validations : Seq[(PlanetSideGameObject)=>Boolean])(target : PlanetSideGameObject) : Boolean = {
//org.log4s.getLogger("ProximityUnit").info(s"vehicle: ${Owner.Position}, terminal: $Position, target: ${target.Position}, toOwner: ${Vector3.Distance(Position, Owner.Position)}, toTarget: ${Vector3.Distance(Position, target.Position)}")
targets.contains(target) && Vector3.DistanceSquared(Position, target.Position) <= radius && validations.exists(p => p(target))
}
}
object ProximityUnit {
import akka.actor.Actor
/**
* A mixin `trait` for an `Actor`'s `PartialFunction` that handles messages,
* in this case handling messages that controls the telegraphed state of the `ProximityUnit` object as the number of users changes.
*/
trait Use {
this : Actor =>
def TerminalObject : Terminal with ProximityUnit
val proximityBehavior : Receive = {
case CommonMessages.Use(player) =>
val hadNoUsers = TerminalObject.NumberUsers == 0
if(TerminalObject.AddUser(player.GUID) == 1 && hadNoUsers) {
sender ! TerminalMessage(player, null, Terminal.StartProximityEffect(TerminalObject))
}
case CommonMessages.Unuse(player) =>
val hadUsers = TerminalObject.NumberUsers > 0
if(TerminalObject.RemoveUser(player.GUID) == 0 && hadUsers) {
sender ! TerminalMessage(player, null, Terminal.StopProximityEffect(TerminalObject))
}
}
}
final case class Action(terminal : Terminal with ProximityUnit, target : PlanetSideGameObject)
}

View file

@ -1,44 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player
import net.psforever.objects.inventory.InventoryItem
import net.psforever.objects.loadouts.VehicleLoadout
import net.psforever.objects.serverobject.terminals.EquipmentTerminalDefinition.BuildSimplifiedPattern
import net.psforever.packet.game.ItemTransactionMessage
/**
* The `Definition` for any `Terminal` that is of a type "repair_silo."
* Has both proximity-based operation and direct access purchasing power.
*/
class RepairRearmSiloDefinition(objectId : Int) extends EquipmentTerminalDefinition(objectId) with ProximityDefinition {
Name = if(objectId == 719) {
"pad_landing"
}
else if(objectId == 729) {
"repair_silo"
}
else {
throw new IllegalArgumentException("repair re-arm terminal must be either object id 719 or 729")
}
private val buyFunc : (Player, ItemTransactionMessage)=>Terminal.Exchange = EquipmentTerminalDefinition.Buy(Map.empty, Map.empty, Map.empty)
override def Buy(player: Player, msg : ItemTransactionMessage) : Terminal.Exchange = buyFunc(player, msg)
override def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
if(msg.item_page == 4) { //Favorites tab
player.LoadLoadout(msg.unk1 + 10) match {
case Some(loadout : VehicleLoadout) =>
val weapons = loadout.visible_slots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
val inventory = loadout.inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
Terminal.VehicleLoadout(loadout.vehicle_definition, weapons, inventory)
case _ =>
Terminal.NoDeal()
}
}
else {
Terminal.NoDeal()
}
}
}

View file

@ -9,6 +9,7 @@ import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.objects.serverobject.terminals._
import net.psforever.objects.serverobject.tube.{SpawnTube, SpawnTubeDefinition}
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
import net.psforever.types.Vector3
/**
* An `Enumeration` of the available vehicular utilities.<br>
@ -78,6 +79,28 @@ class Utility(util : UtilityType.Value, vehicle : Vehicle) {
object Utility {
type UtilLogic = (Amenity, ActorContext)=>Unit
/**
* Embedded (owned) entities are known in relation to their parent entity.
* These overrides to the `Position` method and the `Orientation` method reflect this.
*/
sealed trait UtilityWorldEntity {
this : Amenity =>
override def Position : Vector3 = {
val oPos = Owner.Position
(Owner, LocationOffset) match {
case (_, Vector3.Zero) =>
oPos
case (_ : Vehicle, v) =>
oPos + v.Rz(Orientation.z + 90)
case _ =>
oPos
}
}
override def Orientation : Vector3 = Owner.Orientation
}
/**
* Overloaded constructor.
* @param util the type of the `Amenity` object to be created
@ -99,7 +122,7 @@ object Utility {
case UtilityType.bfr_rearm_terminal =>
new TerminalUtility(GlobalDefinitions.bfr_rearm_terminal)
case UtilityType.lodestar_repair_terminal =>
new TerminalUtility(GlobalDefinitions.lodestar_repair_terminal)
new ProximityTerminalUtility(GlobalDefinitions.lodestar_repair_terminal)
case UtilityType.matrix_terminalc =>
new TerminalUtility(GlobalDefinitions.matrix_terminalc)
case UtilityType.multivehicle_rearm_terminal =>
@ -118,22 +141,27 @@ object Utility {
* 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
*/
class SpawnTubeUtility(tubeDef : SpawnTubeDefinition) extends SpawnTube(tubeDef) {
override def Position = Owner.Position
override def Orientation = Owner.Orientation
}
class SpawnTubeUtility(tubeDef : SpawnTubeDefinition) extends SpawnTube(tubeDef) with UtilityWorldEntity
/**
* Override for `Terminal` objects so that they inherit the spatial characteristics of their `Owner`.
* Override for a `Terminal` object so that it inherits the spatial characteristics of its `Owner`.
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/
class TerminalUtility(tdef : TerminalDefinition) extends Terminal(tdef) {
override def Position = Owner.Position
override def Orientation = Owner.Orientation
}
class TerminalUtility(tdef : TerminalDefinition) extends Terminal(tdef) with UtilityWorldEntity
/**
* na
* Override for a `Terminal` object so that it inherits the spatial characteristics of its `Owner`.
* The `Terminal` `Utility` produced has proximity effects.
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/
class ProximityTerminalUtility(tdef : TerminalDefinition) extends Terminal(tdef)
with UtilityWorldEntity
with ProximityUnit
/**
* Override for a `Terminal` object so that it inherits the spatial characteristics of its `Owner`.
* The `Terminal` `Utility` produced dispenses a specific item
* that retain knowledge of the `Owner` of the `Terminal` that dispensed it.
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/
class TeleportPadTerminalUtility(tdef : TerminalDefinition) extends TerminalUtility(tdef) {
@ -192,7 +220,7 @@ object Utility {
case UtilityType.bfr_rearm_terminal =>
_OrderTerminalDefinition.Setup
case UtilityType.lodestar_repair_terminal =>
OrderTerminalABDefinition.Setup //TODO wrong
ProximityTerminal.Setup
case UtilityType.matrix_terminalc =>
MatrixTerminalDefinition.Setup
case UtilityType.multivehicle_rearm_terminal =>

View file

@ -13,12 +13,15 @@ import net.psforever.objects.guid.actor.UniqueNumberSystem
import net.psforever.objects.guid.selector.RandomSelector
import net.psforever.objects.guid.source.LimitedNumberSource
import net.psforever.objects.inventory.Container
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.structures.{Amenity, Building}
import net.psforever.objects.serverobject.terminals.ProximityUnit
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.serverobject.turret.FacilityTurret
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.Vector3
import services.Service
import scala.collection.concurrent.TrieMap
import scala.collection.mutable.ListBuffer
@ -366,6 +369,13 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
case silo : ResourceSilo =>
silo.Actor ! "startup"
}
//proximity terminals need to startup
buildings.values
.flatMap(_.Amenities.filter(_.isInstanceOf[ProximityUnit]))
.collect {
case o : PlanetSideServerObject =>
o.Actor ! Service.Startup()
}
}
private def CreateSpawnGroups() : Unit = {

View file

@ -0,0 +1,16 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.zones
/**
* The object must be able to recall on which of the defined game worlds (zones) that it exists on command.
* The game world identifier string produced should be equivalent to a `Zone.Id` string for some equivalent `Zone` object.
* The identifier "nowhere" is recommended as the default invalid location.
* @see `InterstellarCluster`
* @see `WorldEntity`
* @see `Zone`
*/
trait ZoneAware {
def Continent : String
def Continent_=(zone : String) : String
}

View file

@ -46,11 +46,49 @@ final case class Vector3(x : Float,
* @return a new `Vector3` object with only two of the components of the original
*/
def xy : Vector3 = Vector3(x, y, 0)
/**
* Perform the x-axis rotation of this `Vector3` element where the angle of rotation is assumed in degrees.
* For chaining rotations.
* @see `Vector3.Rx`
* @param ang a rotation angle
* @return the rotated vector
*/
def Rx(ang : Float) : Vector3 = Vector3.Rx(this, ang)
/**
* Perform the y-axis rotation of this `Vector3` element where the angle of rotation is assumed in degrees.
* For chaining rotations.
* @see `Vector3.Ry`
* @param ang a rotation angle
* @return the rotated vector
*/
def Ry(ang : Float) : Vector3 = Vector3.Ry(this, ang)
/**
* Perform the z-axis rotation of this `Vector3` element where the angle of rotation is assumed in degrees.
* For chaining rotations.
* @see `Vector3.Rz`
* @param ang a rotation angle
* @return the rotated vector
*/
def Rz(ang : Float) : Vector3 = Vector3.Rz(this, ang)
}
object Vector3 {
final val Zero : Vector3 = Vector3(0f, 0f, 0f)
private def closeToInsignificance(d : Float, epsilon : Float = 10f) : Float = {
val ulp = math.ulp(epsilon)
math.signum(d) match {
case -1f =>
val n = math.abs(d)
val p = math.abs(n - n.toInt)
if(p < ulp || d > ulp) d + p else d
case _ =>
val p = math.abs(d - d.toInt)
if(p < ulp || d < ulp) d - p else d
}
}
implicit val codec_pos : Codec[Vector3] = (
("x" | newcodecs.q_float(0.0, 8192.0, 20)) ::
("y" | newcodecs.q_float(0.0, 8192.0, 20)) ::
@ -208,4 +246,82 @@ object Vector3 {
def VectorProjection(vec1 : Vector3, vec2 : Vector3) : Vector3 = {
Unit(vec2) * ScalarProjection(vec1, vec2)
}
/**
* Perform the x-axis rotation of a `Vector3` element where the angle of rotation is assumed in degrees.
* @see `Vector3.RxRadians(Vector3, Double)`
* @param vec a mathematical vector representing direction
* @param ang a rotation angle, in degrees
* @return the rotated vector
*/
def Rx(vec : Vector3, ang : Float) : Vector3 = Rx(vec, math.toRadians(ang))
/**
* Perform the x-axis rotation of a `Vector3` element where the angle of rotation is assumed in radians.
* @see `Vector3.Rx(Vector3, Float)`
* @param vec a mathematical vector representing direction
* @param ang a rotation angle, in radians
* @return the rotated vector
*/
def Rx(vec : Vector3, ang : Double) : Vector3 = {
val cos = math.cos(ang).toFloat
val sin = math.sin(ang).toFloat
val (x, y, z) = (vec.x, vec.y, vec.z)
Vector3(
x,
closeToInsignificance(y * cos - z * sin),
closeToInsignificance(y * sin + z * cos)
)
}
/**
* Perform the x-axis rotation of a `Vector3` element where the angle of rotation is assumed in degrees.
* @see `Vector3.Ry(Vector3, Double)`
* @param vec a mathematical vector representing direction
* @param ang a rotation angle, in degrees
* @return the rotated vector
*/
def Ry(vec : Vector3, ang : Float) : Vector3 = Ry(vec, math.toRadians(ang))
/**
* Perform the y-axis rotation of a `Vector3` element where the angle of rotation is assumed in radians.
* @see `Vector3.Ry(Vector3, Float)`
* @param vec a mathematical vector representing direction
* @param ang a rotation angle, in radians
* @return the rotated vector
*/
def Ry(vec : Vector3, ang : Double) : Vector3 = {
val cos = math.cos(ang).toFloat
val sin = math.sin(ang).toFloat
val (x, y, z) = (vec.x, vec.y, vec.z)
Vector3(
closeToInsignificance(x * cos + z * sin),
y,
closeToInsignificance(z * cos - x * sin)
)
}
/**
* Perform the x-axis rotation of a `Vector3` element where the angle of rotation is assumed in degrees.
* @see `Vector3.Rz(Vector3, Double)`
* @param vec a mathematical vector representing direction
* @param ang a rotation angle, in degrees
* @return the rotated vector
*/
def Rz(vec : Vector3, ang : Float) : Vector3 = Rz(vec, math.toRadians(ang))
/**
* Perform the z-axis rotation of a `Vector3` element where the angle of rotation is assumed in radians.
* @see `Vector3.Rz(Vector3, Float)`
* @param vec a mathematical vector representing direction
* @param ang a rotation angle, in radians
* @return the rotation vector
*/
def Rz(vec : Vector3, ang : Double) : Vector3 = {
val cos = math.cos(ang).toFloat
val sin = math.sin(ang).toFloat
val (x, y, z) = (vec.x, vec.y, vec.z)
Vector3(
closeToInsignificance(x * cos - y * sin),
closeToInsignificance(x * sin - y * cos),
z
)
}
}

View file

@ -23,7 +23,6 @@ object LocalAction {
final case class HackTemporarily(player_guid : PlanetSideGUID, continent : Zone, target : PlanetSideServerObject, unk1 : Long, duration: Int, unk2 : Long = 8L) extends Action
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

View file

@ -5,7 +5,7 @@ import akka.actor.{Actor, ActorRef, Props}
import net.psforever.objects.ce.Deployable
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.serverobject.terminals.{CaptureTerminal, ProximityUnit, Terminal}
import net.psforever.objects.zones.{InterstellarCluster, Zone}
import net.psforever.objects._
import net.psforever.packet.game.{PlanetSideGUID, TriggeredEffect, TriggeredEffectLocation}
@ -111,10 +111,6 @@ class LocalService extends Actor {
LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.HackCaptureTerminal(target.GUID, unk1, unk2, isResecured))
)
case LocalAction.ProximityTerminalEffect(player_guid, object_guid, effectState) =>
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))
@ -159,6 +155,16 @@ class LocalService extends Actor {
LocalServiceResponse(s"/$zone_id/Local", Service.defaultPlayerGUID, LocalResponse.HackClear(target_guid, unk1, unk2))
)
//message from ProximityTerminalControl
case Terminal.StartProximityEffect(terminal) =>
LocalEvents.publish(
LocalServiceResponse(s"/${terminal.Owner.Continent}/Local", PlanetSideGUID(0), LocalResponse.ProximityTerminalEffect(terminal.GUID, true))
)
case Terminal.StopProximityEffect(terminal) =>
LocalEvents.publish(
LocalServiceResponse(s"/${terminal.Owner.Continent}/Local", PlanetSideGUID(0), LocalResponse.ProximityTerminalEffect(terminal.GUID, false))
)
case HackCaptureActor.HackTimeoutReached(capture_terminal_guid, zone_id, _, _, hackedByFaction) =>
import scala.concurrent.ExecutionContext.Implicits.global
ask(cluster, InterstellarCluster.GetWorld(zone_id))(1 seconds).onComplete {

View file

@ -2,9 +2,13 @@
package services.vehicle
import akka.actor.{Actor, ActorRef, Props}
import net.psforever.objects.Vehicle
import net.psforever.objects.ballistics.VehicleSource
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.objects.serverobject.terminals.{MedicalTerminalDefinition, ProximityUnit}
import net.psforever.objects.vital.RepairFromTerm
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.ObjectCreateMessage
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID}
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
import services.vehicle.support.{TurretUpgrader, VehicleRemover}
import net.psforever.types.DriveState
@ -189,6 +193,18 @@ class VehicleService extends Actor {
VehicleServiceResponse(s"/${zone.Id}/Vehicle", Service.defaultPlayerGUID, VehicleResponse.UpdateAmsSpawnPoint(AmsSpawnPoints(zone)))
)
//from ProximityTerminalControl (?)
case ProximityUnit.Action(term, target : Vehicle) =>
val medDef = term.Definition.asInstanceOf[MedicalTerminalDefinition]
val healAmount = medDef.HealAmount
if(healAmount != 0 && term.Validate(target) && target.Health < target.MaxHealth) {
target.Health = target.Health + healAmount
target.History(RepairFromTerm(VehicleSource(target), healAmount, medDef))
VehicleEvents.publish(
VehicleServiceResponse(s"/${term.Continent}/Vehicle", PlanetSideGUID(0), VehicleResponse.PlanetsideAttribute(target.GUID, 0, target.Health))
)
}
case msg =>
log.info(s"Unhandled message $msg from $sender")
}

View file

@ -201,5 +201,44 @@ class Vector3Test extends Specification {
Vector3.VectorProjection(A, B) mustEqual Vector3(1.0384614f, 1.3846153f, 0.34615383f)
Vector3.VectorProjection(B, A) mustEqual Vector3(2.9999998f, 1.4999999f, -1.4999999f)
}
"rotate positive x-axis-vector 90-degrees around the z-axis" in {
val A : Vector3 = Vector3(1, 0, 0)
A.Rz(0) mustEqual A
A.Rz(90) mustEqual Vector3(0, 1, 0)
A.Rz(180) mustEqual Vector3(-1, 0, 0)
A.Rz(270) mustEqual Vector3(0, -1, 0)
A.Rz(360) mustEqual A
}
"rotate positive y-axis-vector 90-degrees around the x-axis" in {
val A : Vector3 = Vector3(0, 1, 0)
A.Rx(0) mustEqual A
A.Rx(90) mustEqual Vector3(0, 0, 1)
A.Rx(180) mustEqual Vector3(0, -1, 0)
A.Rx(270) mustEqual Vector3(0, 0, -1)
A.Rx(360) mustEqual A
}
"rotate positive x-axis-vector 90-degrees around the y-axis" in {
val A : Vector3 = Vector3(1, 0, 0)
A.Ry(0) mustEqual A
A.Ry(90) mustEqual Vector3(0, 0, -1)
A.Ry(180) mustEqual Vector3(-1, 0, 0)
A.Ry(270) mustEqual Vector3(0, 0, 1)
A.Ry(360) mustEqual A
}
"compound rotation" in {
val A : Vector3 = Vector3(1, 0, 0)
A.Rz(90)
.Rx(90)
.Ry(90) mustEqual A
}
"45-degree rotation" in {
val A : Vector3 = Vector3(1, 0, 0)
A.Rz(45) mustEqual Vector3(0.70710677f, 0.70710677f, 0)
}
}
}

View file

@ -1,90 +0,0 @@
// Copyright (c) 2017 PSForever
package objects.terminal
import akka.actor.ActorRef
import net.psforever.objects.serverobject.terminals.{MedicalTerminalDefinition, ProximityTerminal, Terminal}
import net.psforever.objects.{Avatar, GlobalDefinitions, Player}
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, TransactionType}
import org.specs2.mutable.Specification
class MedicalTerminalTest extends Specification {
"MedicalTerminal" should {
"define (a)" in {
val a = new MedicalTerminalDefinition(38)
a.ObjectId mustEqual 38
a.Name mustEqual "adv_med_terminal"
}
"define (b)" in {
val b = new MedicalTerminalDefinition(225)
b.ObjectId mustEqual 225
b.Name mustEqual "crystals_health_a"
}
"define (c)" in {
val c = new MedicalTerminalDefinition(226)
c.ObjectId mustEqual 226
c.Name mustEqual "crystals_health_b"
}
"define (d)" in {
val d = new MedicalTerminalDefinition(529)
d.ObjectId mustEqual 529
d.Name mustEqual "medical_terminal"
}
"define (e)" in {
val e = new MedicalTerminalDefinition(689)
e.ObjectId mustEqual 689
e.Name mustEqual "portable_med_terminal"
}
"define (invalid)" in {
var id : Int = (math.random * Int.MaxValue).toInt
if(id == 224) {
id += 2
}
else if(id == 37) {
id += 1
}
else if(id == 528) {
id += 1
}
else if(id == 688) {
id += 1
}
new MedicalTerminalDefinition(id) must throwA[IllegalArgumentException]
}
}
"Medical_Terminal" should {
"construct" in {
ProximityTerminal(GlobalDefinitions.medical_terminal).Actor mustEqual ActorRef.noSender
}
"can add a player to a list of users" in {
val terminal = ProximityTerminal(GlobalDefinitions.medical_terminal)
terminal.NumberUsers mustEqual 0
terminal.AddUser(PlanetSideGUID(10))
terminal.NumberUsers mustEqual 1
}
"can remove a player from a list of users" in {
val terminal = ProximityTerminal(GlobalDefinitions.medical_terminal)
terminal.AddUser(PlanetSideGUID(10))
terminal.NumberUsers mustEqual 1
terminal.RemoveUser(PlanetSideGUID(10))
terminal.NumberUsers mustEqual 0
}
"player can not interact with the proximity terminal normally (buy)" in {
val terminal = ProximityTerminal(GlobalDefinitions.medical_terminal)
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "lite_armor", 0, PlanetSideGUID(0))
terminal.Request(player, msg) mustEqual Terminal.NoDeal()
}
}
}

View file

@ -1,120 +0,0 @@
// Copyright (c) 2017 PSForever
package objects.terminal
import akka.actor.{ActorSystem, Props}
import base.ActorTest
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.{Avatar, GlobalDefinitions, Player}
import net.psforever.objects.serverobject.terminals._
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire}
import scala.concurrent.duration.Duration
class ProximityTerminalControl1Test extends ActorTest {
"ProximityTerminalControl" should {
"construct (medical terminal)" in {
val terminal = ProximityTerminal(GlobalDefinitions.medical_terminal)
terminal.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], terminal), "test-term")
}
}
}
class ProximityTerminalControl2Test extends ActorTest {
"ProximityTerminalControl can not process wrong messages" in {
val (_, terminal) = TerminalControlTest.SetUpAgents(GlobalDefinitions.medical_terminal, PlanetSideEmpire.TR)
terminal.Actor !"hello"
expectNoMsg(Duration.create(500, "ms"))
}
}
//terminal control is mostly a pass-through actor for Terminal.Exchange messages, wrapped in Terminal.TerminalMessage protocol
class MedicalTerminalControl1Test extends ActorTest {
"ProximityTerminalControl sends a message to the first new user only" in {
val (player, terminal) = ProximityTerminalControlTest.SetUpAgents(GlobalDefinitions.medical_terminal, PlanetSideEmpire.TR)
player.GUID = PlanetSideGUID(10)
val player2 = Player(Avatar("someothertest", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player2.GUID = PlanetSideGUID(11)
terminal.Actor ! CommonMessages.Use(player)
val reply = receiveOne(Duration.create(500, "ms"))
assert(reply.isInstanceOf[Terminal.TerminalMessage])
val reply2 = reply.asInstanceOf[Terminal.TerminalMessage]
assert(reply2.player == player)
assert(reply2.msg == null)
assert(reply2.response.isInstanceOf[Terminal.StartProximityEffect])
assert(reply2.response.asInstanceOf[Terminal.StartProximityEffect].terminal == terminal)
assert(terminal.NumberUsers == 1)
terminal.Actor ! CommonMessages.Use(player2)
expectNoMsg(Duration.create(500, "ms"))
assert(terminal.NumberUsers == 2)
}
}
class MedicalTerminalControl2Test extends ActorTest {
"ProximityTerminalControl sends a message to the last user only" in {
val (player, terminal) : (Player, ProximityTerminal) = ProximityTerminalControlTest.SetUpAgents(GlobalDefinitions.medical_terminal, PlanetSideEmpire.TR)
player.GUID = PlanetSideGUID(10)
val player2 = Player(Avatar("someothertest", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player2.GUID = PlanetSideGUID(11)
terminal.Actor ! CommonMessages.Use(player)
receiveOne(Duration.create(500, "ms"))
terminal.Actor ! CommonMessages.Use(player2)
expectNoMsg(Duration.create(500, "ms"))
assert(terminal.NumberUsers == 2)
terminal.Actor ! CommonMessages.Unuse(player)
expectNoMsg(Duration.create(500, "ms"))
assert(terminal.NumberUsers == 1)
terminal.Actor ! CommonMessages.Unuse(player2)
val reply = receiveOne(Duration.create(500, "ms"))
assert(reply.isInstanceOf[Terminal.TerminalMessage])
val reply2 = reply.asInstanceOf[Terminal.TerminalMessage]
assert(reply2.player == player2)
assert(reply2.msg == null)
assert(reply2.response.isInstanceOf[Terminal.StopProximityEffect])
assert(reply2.response.asInstanceOf[Terminal.StopProximityEffect].terminal == terminal)
assert(terminal.NumberUsers == 0)
}
}
class MedicalTerminalControl3Test extends ActorTest {
"ProximityTerminalControl sends a message to the last user only (confirmation of test #2)" in {
val (player, terminal) : (Player, ProximityTerminal) = ProximityTerminalControlTest.SetUpAgents(GlobalDefinitions.medical_terminal, PlanetSideEmpire.TR)
player.GUID = PlanetSideGUID(10)
val player2 = Player(Avatar("someothertest", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player2.GUID = PlanetSideGUID(11)
terminal.Actor ! CommonMessages.Use(player)
receiveOne(Duration.create(500, "ms"))
terminal.Actor ! CommonMessages.Use(player2)
expectNoMsg(Duration.create(500, "ms"))
assert(terminal.NumberUsers == 2)
terminal.Actor ! CommonMessages.Unuse(player2)
expectNoMsg(Duration.create(500, "ms"))
assert(terminal.NumberUsers == 1)
terminal.Actor ! CommonMessages.Unuse(player)
val reply = receiveOne(Duration.create(500, "ms"))
assert(reply.isInstanceOf[Terminal.TerminalMessage])
val reply2 = reply.asInstanceOf[Terminal.TerminalMessage]
assert(reply2.player == player) //important!
assert(reply2.msg == null)
assert(reply2.response.isInstanceOf[Terminal.StopProximityEffect])
assert(reply2.response.asInstanceOf[Terminal.StopProximityEffect].terminal == terminal)
assert(terminal.NumberUsers == 0)
}
}
object ProximityTerminalControlTest {
def SetUpAgents(tdef : MedicalTerminalDefinition, faction : PlanetSideEmpire.Value)(implicit system : ActorSystem) : (Player, ProximityTerminal) = {
val terminal = ProximityTerminal(tdef)
terminal.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], terminal), "test-term")
(Player(Avatar("test", faction, CharacterGender.Male, 0, CharacterVoice.Mute)), terminal)
}
}

View file

@ -2,14 +2,19 @@
package objects.terminal
import akka.actor.Props
import akka.testkit.TestProbe
import base.ActorTest
import net.psforever.objects.guid.TaskResolver
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage
import net.psforever.objects.serverobject.structures.{Building, StructureType}
import net.psforever.objects.serverobject.terminals.{ProximityTerminal, ProximityTerminalControl, ProximityUnit, Terminal}
import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap}
import net.psforever.objects.{Avatar, GlobalDefinitions, Player}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire}
import org.specs2.mutable.Specification
import services.{Service, ServiceManager}
import services.local.{LocalResponse, LocalService, LocalServiceResponse}
import scala.concurrent.duration._
@ -21,39 +26,62 @@ class ProximityTest extends Specification {
}
"keep track of users (add)" in {
val obj = new ProximityTest.SampleTerminal()
val avatar1 = Player(Avatar("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
avatar1.Spawn
avatar1.Health = 50
val avatar2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
avatar2.Spawn
avatar2.Health = 50
val obj = new ProximityTerminal(GlobalDefinitions.medical_terminal)
obj.NumberUsers mustEqual 0
obj.AddUser(PlanetSideGUID(10)) mustEqual obj.NumberUsers
obj.AddUser(avatar1) mustEqual true
obj.NumberUsers mustEqual 1
obj.AddUser(PlanetSideGUID(20)) mustEqual obj.NumberUsers
obj.AddUser(avatar2) mustEqual true
obj.NumberUsers mustEqual 2
}
"keep track of users (remove)" in {
val obj = new ProximityTest.SampleTerminal()
obj.AddUser(PlanetSideGUID(10))
obj.AddUser(PlanetSideGUID(20))
obj.NumberUsers mustEqual 2
obj.RemoveUser(PlanetSideGUID(10)) mustEqual obj.NumberUsers
val avatar1 = Player(Avatar("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
avatar1.Spawn
avatar1.Health = 50
val avatar2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
avatar2.Spawn
avatar2.Health = 50
val obj = new ProximityTerminal(GlobalDefinitions.medical_terminal)
obj.NumberUsers mustEqual 0
obj.AddUser(avatar1) mustEqual true
obj.NumberUsers mustEqual 1
obj.RemoveUser(PlanetSideGUID(20)) mustEqual obj.NumberUsers
obj.AddUser(avatar2) mustEqual true
obj.NumberUsers mustEqual 2
obj.RemoveUser(avatar1) mustEqual true
obj.NumberUsers mustEqual 1
obj.RemoveUser(avatar2) mustEqual true
obj.NumberUsers mustEqual 0
}
"can not add a user twice" in {
val obj = new ProximityTest.SampleTerminal()
obj.AddUser(PlanetSideGUID(10))
val avatar = Player(Avatar("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
avatar.Spawn
avatar.Health = 50
val obj = new ProximityTerminal(GlobalDefinitions.medical_terminal)
obj.AddUser(avatar) mustEqual true
obj.NumberUsers mustEqual 1
obj.AddUser(PlanetSideGUID(10))
obj.AddUser(avatar)// mustEqual false
obj.NumberUsers mustEqual 1
}
"can not remove a user that was not added" in {
val avatar = Player(Avatar("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
avatar.Spawn
avatar.Health = 50
val obj = new ProximityTest.SampleTerminal()
obj.AddUser(PlanetSideGUID(10))
obj.NumberUsers mustEqual 1
obj.RemoveUser(PlanetSideGUID(20))
obj.NumberUsers mustEqual 1
obj.RemoveUser(avatar) mustEqual false
obj.NumberUsers mustEqual 0
}
}
@ -65,106 +93,216 @@ class ProximityTest extends Specification {
}
}
class ProximityTerminalControl1bTest extends ActorTest {
class ProximityTerminalControlStartTest extends ActorTest {
"ProximityTerminalControl" should {
//setup
val probe = new TestProbe(system)
val service = ServiceManager.boot(system)
service ! ServiceManager.Register(Props(classOf[ProximityTest.ProbedLocalService], probe), "local")
service ! ServiceManager.Register(Props[TaskResolver], "taskResolver")
service ! ServiceManager.Register(Props[TaskResolver], "cluster")
val terminal = new ProximityTerminal(GlobalDefinitions.medical_terminal)
terminal.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], terminal), "test-prox")
val zone : Zone = new Zone("test", new ZoneMap("test-map"), 0) {
Actor = system.actorOf(Props(classOf[ZoneActor], this), "test-zone")
override def SetupNumberPools() = {
AddPool("dynamic", 1 to 10)
}
}
new Building(0, zone, StructureType.Facility) {
Amenities = terminal
Faction = PlanetSideEmpire.VS
}
val avatar = Player(Avatar("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
avatar.Continent = "test"
avatar.Spawn
avatar.Health = 50
avatar.GUID = PlanetSideGUID(1)
terminal.GUID = PlanetSideGUID(2)
terminal.Actor ! Service.Startup()
expectNoMsg(500 milliseconds) //spacer
"send out a start message" in {
val obj = ProximityTerminal(GlobalDefinitions.medical_terminal)
obj.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], obj), "prox-ctrl")
val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player.GUID = PlanetSideGUID(10)
assert(terminal.NumberUsers == 0)
assert(terminal.Owner.Continent.equals("test"))
terminal.Actor ! CommonMessages.Use(avatar, Some(avatar))
assert(obj.NumberUsers == 0)
obj.Actor ! CommonMessages.Use(player)
val msg = receiveOne(200 milliseconds)
assert(obj.NumberUsers == 1)
assert(msg.isInstanceOf[TerminalMessage])
val msgout = msg.asInstanceOf[TerminalMessage]
assert(msgout.player == player)
assert(msgout.msg == null)
assert(msgout.response.isInstanceOf[Terminal.StartProximityEffect])
val msg = probe.receiveOne(500 milliseconds)
assert(terminal.NumberUsers == 1)
assert(msg.isInstanceOf[LocalServiceResponse])
val resp = msg.asInstanceOf[LocalServiceResponse]
assert(resp.replyMessage == LocalResponse.ProximityTerminalEffect(PlanetSideGUID(2), true))
}
}
}
class ProximityTerminalControl2bTest extends ActorTest {
class ProximityTerminalControlTwoUsersTest extends ActorTest {
"ProximityTerminalControl" should {
"will not send out one start message unless first user" in {
val obj = ProximityTerminal(GlobalDefinitions.medical_terminal)
obj.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], obj), "prox-ctrl")
val player1 = Player(Avatar("TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player1.GUID = PlanetSideGUID(10)
val player2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player2.GUID = PlanetSideGUID(11)
assert(obj.NumberUsers == 0)
//setup
val probe = new TestProbe(system)
val service = ServiceManager.boot(system)
service ! ServiceManager.Register(Props(classOf[ProximityTest.ProbedLocalService], probe), "local")
service ! ServiceManager.Register(Props[TaskResolver], "taskResolver")
service ! ServiceManager.Register(Props[TaskResolver], "cluster")
val terminal = new ProximityTerminal(GlobalDefinitions.medical_terminal)
terminal.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], terminal), "test-prox")
val zone : Zone = new Zone("test", new ZoneMap("test-map"), 0) {
Actor = system.actorOf(Props(classOf[ZoneActor], this), "test-zone")
override def SetupNumberPools() = {
AddPool("dynamic", 1 to 10)
}
}
new Building(0, zone, StructureType.Facility) {
Amenities = terminal
Faction = PlanetSideEmpire.VS
}
val avatar = Player(Avatar("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
avatar.Continent = "test"
avatar.Spawn
avatar.Health = 50
obj.Actor ! CommonMessages.Use(player1)
val msg = receiveOne(200 milliseconds)
assert(obj.NumberUsers == 1)
assert(msg.isInstanceOf[TerminalMessage])
assert(msg.asInstanceOf[TerminalMessage].response.isInstanceOf[Terminal.StartProximityEffect])
obj.Actor ! CommonMessages.Use(player2)
expectNoMsg(500 milliseconds)
assert(obj.NumberUsers == 2)
avatar.GUID = PlanetSideGUID(1)
terminal.GUID = PlanetSideGUID(2)
terminal.Actor ! Service.Startup()
expectNoMsg(500 milliseconds) //spacer
"will not send out a start message if not the first user" in {
assert(terminal.NumberUsers == 0)
assert(terminal.Owner.Continent.equals("test"))
terminal.Actor ! CommonMessages.Use(avatar, Some(avatar))
val msg = probe.receiveOne(500 milliseconds)
assert(terminal.NumberUsers == 1)
assert(msg.isInstanceOf[LocalServiceResponse])
val resp = msg.asInstanceOf[LocalServiceResponse]
assert(resp.replyMessage == LocalResponse.ProximityTerminalEffect(PlanetSideGUID(2), true))
val avatar2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
avatar2.Continent = "test"
avatar2.Spawn
avatar2.Health = 50
terminal.Actor ! CommonMessages.Use(avatar2, Some(avatar2))
probe.expectNoMsg(500 milliseconds)
assert(terminal.NumberUsers == 2)
}
}
}
class ProximityTerminalControl3bTest extends ActorTest {
class ProximityTerminalControlStopTest extends ActorTest {
"ProximityTerminalControl" should {
//setup
val probe = new TestProbe(system)
val service = ServiceManager.boot(system)
service ! ServiceManager.Register(Props(classOf[ProximityTest.ProbedLocalService], probe), "local")
service ! ServiceManager.Register(Props[TaskResolver], "taskResolver")
service ! ServiceManager.Register(Props[TaskResolver], "cluster")
val terminal = new ProximityTerminal(GlobalDefinitions.medical_terminal)
terminal.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], terminal), "test-prox")
val zone : Zone = new Zone("test", new ZoneMap("test-map"), 0) {
Actor = system.actorOf(Props(classOf[ZoneActor], this), "test-zone")
override def SetupNumberPools() = {
AddPool("dynamic", 1 to 10)
}
}
new Building(0, zone, StructureType.Facility) {
Amenities = terminal
Faction = PlanetSideEmpire.VS
}
val avatar = Player(Avatar("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
avatar.Continent = "test"
avatar.Spawn
avatar.Health = 50
avatar.GUID = PlanetSideGUID(1)
terminal.GUID = PlanetSideGUID(2)
terminal.Actor ! Service.Startup()
expectNoMsg(500 milliseconds) //spacer
"send out a stop message" in {
val obj = ProximityTerminal(GlobalDefinitions.medical_terminal)
obj.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], obj), "prox-ctrl")
val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player.GUID = PlanetSideGUID(10)
assert(terminal.NumberUsers == 0)
assert(terminal.Owner.Continent.equals("test"))
assert(obj.NumberUsers == 0)
obj.Actor ! CommonMessages.Use(player)
receiveOne(200 milliseconds)
assert(obj.NumberUsers == 1)
obj.Actor ! CommonMessages.Unuse(player)
val msg = receiveOne(200 milliseconds)
assert(obj.NumberUsers == 0)
assert(msg.isInstanceOf[TerminalMessage])
val msgout = msg.asInstanceOf[TerminalMessage]
assert(msgout.player == player)
assert(msgout.msg == null)
assert(msgout.response.isInstanceOf[Terminal.StopProximityEffect])
terminal.Actor ! CommonMessages.Use(avatar, Some(avatar))
val msg1 = probe.receiveOne(500 milliseconds)
assert(terminal.NumberUsers == 1)
assert(msg1.isInstanceOf[LocalServiceResponse])
val resp1 = msg1.asInstanceOf[LocalServiceResponse]
assert(resp1.replyMessage == LocalResponse.ProximityTerminalEffect(PlanetSideGUID(2), true))
terminal.Actor ! CommonMessages.Unuse(avatar, Some(avatar))
val msg2 = probe.receiveWhile(500 milliseconds) {
case LocalServiceResponse(_, _, replyMessage) => replyMessage
}
assert(terminal.NumberUsers == 0)
assert(msg2.last == LocalResponse.ProximityTerminalEffect(PlanetSideGUID(2), false))
}
}
}
class ProximityTerminalControl4bTest extends ActorTest {
class ProximityTerminalControlNotStopTest extends ActorTest {
"ProximityTerminalControl" should {
"will not send out one stop message until last user" in {
val obj = ProximityTerminal(GlobalDefinitions.medical_terminal)
obj.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], obj), "prox-ctrl")
val player1 = Player(Avatar("TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player1.GUID = PlanetSideGUID(10)
val player2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player2.GUID = PlanetSideGUID(11)
assert(obj.NumberUsers == 0)
//setup
val probe = new TestProbe(system)
val service = ServiceManager.boot(system)
service ! ServiceManager.Register(Props(classOf[ProximityTest.ProbedLocalService], probe), "local")
service ! ServiceManager.Register(Props[TaskResolver], "taskResolver")
service ! ServiceManager.Register(Props[TaskResolver], "cluster")
val terminal = new ProximityTerminal(GlobalDefinitions.medical_terminal)
terminal.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], terminal), "test-prox")
val zone : Zone = new Zone("test", new ZoneMap("test-map"), 0) {
Actor = system.actorOf(Props(classOf[ZoneActor], this), "test-zone")
override def SetupNumberPools() = {
AddPool("dynamic", 1 to 10)
}
}
new Building(0, zone, StructureType.Facility) {
Amenities = terminal
Faction = PlanetSideEmpire.VS
}
val avatar = Player(Avatar("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
avatar.Continent = "test"
avatar.Spawn
avatar.Health = 50
obj.Actor ! CommonMessages.Use(player1)
receiveOne(200 milliseconds) //StartProximityEffect
assert(obj.NumberUsers == 1)
obj.Actor ! CommonMessages.Use(player2)
expectNoMsg(500 milliseconds)
assert(obj.NumberUsers == 2)
obj.Actor ! CommonMessages.Unuse(player1)
expectNoMsg(500 milliseconds)
assert(obj.NumberUsers == 1)
obj.Actor ! CommonMessages.Unuse(player2)
val msg = receiveOne(200 milliseconds)
assert(obj.NumberUsers == 0)
assert(msg.isInstanceOf[TerminalMessage])
val msgout = msg.asInstanceOf[TerminalMessage]
assert(msgout.player == player2)
assert(msgout.msg == null)
assert(msgout.response.isInstanceOf[Terminal.StopProximityEffect])
avatar.GUID = PlanetSideGUID(1)
terminal.GUID = PlanetSideGUID(2)
terminal.Actor ! Service.Startup()
expectNoMsg(500 milliseconds) //spacer
"will not send out one stop message until last user" in {
assert(terminal.NumberUsers == 0)
assert(terminal.Owner.Continent.equals("test"))
terminal.Actor ! CommonMessages.Use(avatar, Some(avatar))
val msg = probe.receiveOne(500 milliseconds)
assert(terminal.NumberUsers == 1)
assert(msg.isInstanceOf[LocalServiceResponse])
val resp = msg.asInstanceOf[LocalServiceResponse]
assert(resp.replyMessage == LocalResponse.ProximityTerminalEffect(PlanetSideGUID(2), true))
val avatar2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
avatar2.Continent = "test"
avatar2.Spawn
avatar2.Health = 50
terminal.Actor ! CommonMessages.Use(avatar2, Some(avatar2))
probe.expectNoMsg(500 milliseconds)
assert(terminal.NumberUsers == 2)
terminal.Actor ! CommonMessages.Unuse(avatar, Some(avatar))
val msg2 = probe.receiveWhile(500 milliseconds) {
case LocalServiceResponse(_, _, replyMessage) => replyMessage
}
assert(terminal.NumberUsers == 1)
assert(!msg2.contains(LocalResponse.ProximityTerminalEffect(PlanetSideGUID(2), false)))
}
}
}
object ProximityTest {
class SampleTerminal extends Terminal(GlobalDefinitions.dropship_vehicle_terminal) with ProximityUnit
class ProbedLocalService(probe : TestProbe) extends LocalService {
self.tell(Service.Join("test"), probe.ref)
}
}

View file

@ -1,91 +0,0 @@
// Copyright (c) 2017 PSForever
package objects.terminal
import akka.actor.ActorRef
import net.psforever.objects.serverobject.structures.{Building, StructureType}
import net.psforever.objects._
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, TransactionType}
import org.specs2.mutable.Specification
class RepairRearmSiloTest extends Specification {
"RepairRearmSilo" should {
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val silo = Terminal(GlobalDefinitions.repair_silo)
silo.Owner = new Building(0, Zone.Nowhere, StructureType.Building)
silo.Owner.Faction = PlanetSideEmpire.TR
"define" in {
GlobalDefinitions.repair_silo.ObjectId mustEqual 729
}
"construct" in {
val obj = Terminal(GlobalDefinitions.repair_silo)
obj.Actor mustEqual ActorRef.noSender
}
"player can buy a box of ammunition ('bullet_35mm')" in {
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 3, "35mmbullet", 0, PlanetSideGUID(0))
val reply = silo.Request(player, msg)
reply.isInstanceOf[Terminal.BuyEquipment] mustEqual true
val reply2 = reply.asInstanceOf[Terminal.BuyEquipment]
reply2.item.isInstanceOf[AmmoBox] mustEqual true
reply2.item.asInstanceOf[AmmoBox].Definition mustEqual GlobalDefinitions.bullet_35mm
reply2.item.asInstanceOf[AmmoBox].Capacity mustEqual 100
}
"player can not buy fake equipment ('sabot')" in {
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 3, "sabot", 0, PlanetSideGUID(0))
silo.Request(player, msg) mustEqual Terminal.NoDeal()
}
"player can not buy equipment from the wrong page ('35mmbullet', page 1)" in {
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "35mmbullet", 0, PlanetSideGUID(0))
silo.Request(player, msg) mustEqual Terminal.NoDeal()
}
"player can retrieve a vehicle loadout" in {
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)
val player2 = Player(avatar)
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.Slot(30).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm)
avatar.SaveLoadout(vehicle, "test", 10)
val msg = silo.Request(player2, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 4, "", 0, PlanetSideGUID(0)))
msg.isInstanceOf[Terminal.VehicleLoadout] mustEqual true
val loadout = msg.asInstanceOf[Terminal.VehicleLoadout]
loadout.vehicle_definition mustEqual GlobalDefinitions.fury
loadout.weapons.size mustEqual 1
loadout.weapons.head.obj.Definition mustEqual GlobalDefinitions.fury_weapon_systema
loadout.weapons.head.start mustEqual 1
loadout.inventory.head.obj.Definition mustEqual GlobalDefinitions.bullet_9mm
loadout.inventory.head.start mustEqual 30
}
"player can not retrieve a vehicle loadout from the wrong line" in {
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)
val player2 = Player(avatar)
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.Slot(30).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm)
avatar.SaveLoadout(vehicle, "test", 10)
val msg = silo.Request(player2, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 3, "", 0, PlanetSideGUID(0))) //page 3
msg.isInstanceOf[Terminal.NoDeal] mustEqual true
}
"player can not retrieve a vehicle loadout from the wrong line" in {
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)
val player2 = Player(avatar)
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.Slot(30).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm)
avatar.SaveLoadout(vehicle, "test", 10)
val msg = silo.Request(player2, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 4, "", 1, PlanetSideGUID(0))) //line 11
msg.isInstanceOf[Terminal.NoDeal] mustEqual true
}
}
}

View file

@ -5,6 +5,7 @@ import akka.actor.Props
import base.ActorTest
import net.psforever.objects.{GlobalDefinitions, SensorDeployable, Vehicle}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.terminals.{ProximityTerminal, Terminal}
import net.psforever.packet.game._
import net.psforever.types.{PlanetSideEmpire, Vector3}
import services.{Service, ServiceManager}
@ -141,15 +142,32 @@ class HackClearTest extends ActorTest {
}
}
class ProximityTerminalEffectTest extends ActorTest {
class ProximityTerminalEffectOnTest extends ActorTest {
ServiceManager.boot(system)
val service = system.actorOf(Props[LocalService], "l_service")
val terminal = new ProximityTerminal(GlobalDefinitions.medical_terminal)
terminal.GUID = PlanetSideGUID(1)
"LocalService" should {
"pass ProximityTerminalEffect" in {
val service = system.actorOf(Props[LocalService], "l_service")
service ! Service.Join("test")
service ! LocalServiceMessage("test", LocalAction.ProximityTerminalEffect(PlanetSideGUID(10), PlanetSideGUID(40), true))
expectMsg(LocalServiceResponse("/test/Local", PlanetSideGUID(10), LocalResponse.ProximityTerminalEffect(PlanetSideGUID(40), true)))
"pass ProximityTerminalEffect (true)" in {
service ! Service.Join("nowhere")
service ! Terminal.StartProximityEffect(terminal)
expectMsg(LocalServiceResponse("/nowhere/Local", PlanetSideGUID(0), LocalResponse.ProximityTerminalEffect(PlanetSideGUID(1), true)))
}
}
}
class ProximityTerminalEffectOffTest extends ActorTest {
ServiceManager.boot(system)
val service = system.actorOf(Props[LocalService], "l_service")
val terminal = new ProximityTerminal(GlobalDefinitions.medical_terminal)
terminal.GUID = PlanetSideGUID(1)
"LocalService" should {
"pass ProximityTerminalEffect (false)" in {
service ! Service.Join("nowhere")
service ! Terminal.StopProximityEffect(terminal)
expectMsg(LocalServiceResponse("/nowhere/Local", PlanetSideGUID(0), LocalResponse.ProximityTerminalEffect(PlanetSideGUID(1), false)))
}
}
}