Separate Vehicle Controls (#871)

* broke vehicle control agency down into specific agencies for different types of vehicles

* moved shared cargo vehicle pain onto the carrier control agency; apc-type vehicles have charging capacitors and emit emp's

* comments and documentation; cargo learning about damage to carrier corrected; fixed tests and added tests

* adjustment to explosive deployable distance filtering; apc now uses this filter when determining valid emp targets by distance
This commit is contained in:
Fate-JH 2021-06-21 23:40:44 -04:00 committed by GitHub
parent 2216958d72
commit c7ebe6a34f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 1061 additions and 530 deletions

View file

@ -14,7 +14,6 @@ import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.resourcesilo.{ResourceSilo, ResourceSiloControl} import net.psforever.objects.serverobject.resourcesilo.{ResourceSilo, ResourceSiloControl}
import net.psforever.objects.serverobject.structures.{AutoRepairStats, Building, StructureType} import net.psforever.objects.serverobject.structures.{AutoRepairStats, Building, StructureType}
import net.psforever.objects.serverobject.terminals.{OrderTerminalDefinition, Terminal, TerminalControl} import net.psforever.objects.serverobject.terminals.{OrderTerminalDefinition, Terminal, TerminalControl}
import net.psforever.objects.vehicles.VehicleControl
import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.base.DamageResolution import net.psforever.objects.vital.base.DamageResolution
import net.psforever.objects.vital.damage.DamageProfile import net.psforever.objects.vital.damage.DamageProfile
@ -201,7 +200,7 @@ class AutoRepairFacilityIntegrationAntGiveNtuTest extends FreedContextActorTest
val maxNtuCap = ant.Definition.MaxNtuCapacitor val maxNtuCap = ant.Definition.MaxNtuCapacitor
player.Spawn() player.Spawn()
ant.NtuCapacitor = maxNtuCap ant.NtuCapacitor = maxNtuCap
ant.Actor = context.actorOf(Props(classOf[VehicleControl], ant), name = "test-ant") ant.Definition.Initialize(ant, context)
ant.Zone = zone ant.Zone = zone
ant.Seats(0).mount(player) ant.Seats(0).mount(player)
ant.DeploymentState = DriveState.Deployed ant.DeploymentState = DriveState.Deployed
@ -295,7 +294,7 @@ class AutoRepairFacilityIntegrationTerminalDestroyedTerminalAntTest extends Free
val maxNtuCap = ant.Definition.MaxNtuCapacitor val maxNtuCap = ant.Definition.MaxNtuCapacitor
player.Spawn() player.Spawn()
ant.NtuCapacitor = maxNtuCap ant.NtuCapacitor = maxNtuCap
ant.Actor = context.actorOf(Props(classOf[VehicleControl], ant), name = "test-ant") ant.Definition.Initialize(ant, context)
ant.Zone = zone ant.Zone = zone
ant.Seats(0).mount(player) ant.Seats(0).mount(player)
ant.DeploymentState = DriveState.Deployed ant.DeploymentState = DriveState.Deployed
@ -397,7 +396,7 @@ class AutoRepairFacilityIntegrationTerminalIncompleteRepairTest extends FreedCon
val maxNtuCap = ant.Definition.MaxNtuCapacitor val maxNtuCap = ant.Definition.MaxNtuCapacitor
player.Spawn() player.Spawn()
ant.NtuCapacitor = maxNtuCap ant.NtuCapacitor = maxNtuCap
ant.Actor = context.actorOf(Props(classOf[VehicleControl], ant), name = "test-ant") ant.Definition.Initialize(ant, context)
ant.Zone = zone ant.Zone = zone
ant.Seats(0).mount(player) ant.Seats(0).mount(player)
ant.DeploymentState = DriveState.Deployed ant.DeploymentState = DriveState.Deployed

View file

@ -215,7 +215,7 @@ object VehicleSpawnPadControlTest {
import net.psforever.objects.guid.NumberPoolHub import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.serverobject.structures.Building
import net.psforever.objects.vehicles.VehicleControl import net.psforever.objects.vehicles.control.VehicleControl
import net.psforever.objects.Tool import net.psforever.objects.Tool
import net.psforever.types.CharacterSex import net.psforever.types.CharacterSex

View file

@ -958,8 +958,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
} }
case Deployment.CanNotChangeDeployment(obj, state, reason) => case Deployment.CanNotChangeDeployment(obj, state, reason) =>
if (Deployment.CheckForDeployState(state) && !VehicleControl.DeploymentAngleCheck(obj)) { if (Deployment.CheckForDeployState(state) && !Deployment.AngleCheck(obj)) {
CanNotChangeDeployment(obj, state, "ground too steep") CanNotChangeDeployment(obj, state, reason = "ground too steep")
} else { } else {
CanNotChangeDeployment(obj, state, reason) CanNotChangeDeployment(obj, state, reason)
} }
@ -2407,13 +2407,12 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
val obj_guid: PlanetSideGUID = obj.GUID val obj_guid: PlanetSideGUID = obj.GUID
CancelAllProximityUnits() CancelAllProximityUnits()
sendResponse(PlanetsideAttributeMessage(obj_guid, 0, obj.Health)) sendResponse(PlanetsideAttributeMessage(obj_guid, 0, obj.Health))
sendResponse(PlanetsideAttributeMessage(obj_guid, 68, obj.Shields)) //shield health sendResponse(PlanetsideAttributeMessage(obj_guid, 68, obj.Shields))
if (obj.Definition == GlobalDefinitions.ant) { if (obj.Definition == GlobalDefinitions.ant) {
sendResponse(PlanetsideAttributeMessage(obj_guid, 45, obj.NtuCapacitorScaled)) sendResponse(PlanetsideAttributeMessage(obj_guid, 45, obj.NtuCapacitorScaled))
} }
if (obj.Definition.MaxCapacitor > 0) { if (obj.Definition.MaxCapacitor > 0) {
val capacitor = scala.math.ceil((obj.Capacitor.toFloat / obj.Definition.MaxCapacitor.toFloat) * 10).toInt sendResponse(PlanetsideAttributeMessage(obj_guid, 113, obj.Capacitor))
sendResponse(PlanetsideAttributeMessage(obj_guid, 113, capacitor))
} }
if (seat_number == 0) { if (seat_number == 0) {
if (obj.Definition == GlobalDefinitions.quadstealth) { if (obj.Definition == GlobalDefinitions.quadstealth) {
@ -3150,14 +3149,16 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
vehicle.Actor ! JammableUnit.ClearJammeredSound() vehicle.Actor ! JammableUnit.ClearJammeredSound()
} }
//positive shield strength //positive shield strength
if (vehicle.Shields > 0) { if (vehicle.Definition.MaxShields > 0) {
sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 68, vehicle.Shields)) sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 68, vehicle.Shields))
} }
// ANT capacitor // ANT capacitor
if (vehicle.Definition == GlobalDefinitions.ant) { if (vehicle.Definition == GlobalDefinitions.ant) {
sendResponse( sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 45, vehicle.NtuCapacitorScaled)) // set ntu on vehicle UI
PlanetsideAttributeMessage(vehicle.GUID, 45, vehicle.NtuCapacitorScaled) }
) // set ntu on vehicle UI // vehicle capacitor
if (vehicle.Definition.MaxCapacitor > 0) {
sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 113, vehicle.Capacitor))
} }
LoadZoneTransferPassengerMessages( LoadZoneTransferPassengerMessages(
guid, guid,
@ -4966,7 +4967,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
} }
case msg @ GenericObjectActionMessage(object_guid, code) => case msg @ GenericObjectActionMessage(object_guid, code) =>
log.debug(s"$msg")
ValidObject(object_guid) match { ValidObject(object_guid) match {
case Some(vehicle: Vehicle) =>
if (code == 55) {
//apc emp
vehicle.Actor ! SpecialEmp.Burst()
}
case Some(tool: Tool) => case Some(tool: Tool) =>
if (tool.Definition == GlobalDefinitions.maelstrom && code == 35) { if (tool.Definition == GlobalDefinitions.maelstrom && code == 35) {
//maelstrom primary fire mode effect (no target) //maelstrom primary fire mode effect (no target)

View file

@ -244,7 +244,7 @@ object ExplosiveDeployableControl {
//val scalar = Vector3.ScalarProjection(dir, up) //val scalar = Vector3.ScalarProjection(dir, up)
val point1 = g1.pointOnOutside(dir).asVector3 val point1 = g1.pointOnOutside(dir).asVector3
val point2 = g2.pointOnOutside(Vector3.neg(dir)).asVector3 val point2 = g2.pointOnOutside(Vector3.neg(dir)).asVector3
val scalar = Vector3.ScalarProjection(point2 - point1, up) val scalar = Vector3.ScalarProjection(point2 - obj1.Position, up)
(scalar >= 0 || Vector3.MagnitudeSquared(up * scalar) < 0.35f) && (scalar >= 0 || Vector3.MagnitudeSquared(up * scalar) < 0.35f) &&
math.min( math.min(
Vector3.DistanceSquared(g1.center.asVector3, g2.center.asVector3), Vector3.DistanceSquared(g1.center.asVector3, g2.center.asVector3),

View file

@ -897,11 +897,11 @@ object GlobalDefinitions {
val aurora = VehicleDefinition(ObjectClass.aurora) val aurora = VehicleDefinition(ObjectClass.aurora)
val apc_tr = VehicleDefinition(ObjectClass.apc_tr) val apc_tr = VehicleDefinition.Apc(ObjectClass.apc_tr)
val apc_nc = VehicleDefinition(ObjectClass.apc_nc) val apc_nc = VehicleDefinition.Apc(ObjectClass.apc_nc)
val apc_vs = VehicleDefinition(ObjectClass.apc_vs) val apc_vs = VehicleDefinition.Apc(ObjectClass.apc_vs)
val lightning = VehicleDefinition(ObjectClass.lightning) val lightning = VehicleDefinition(ObjectClass.lightning)
@ -911,15 +911,15 @@ object GlobalDefinitions {
val magrider = VehicleDefinition(ObjectClass.magrider) val magrider = VehicleDefinition(ObjectClass.magrider)
val ant = VehicleDefinition(ObjectClass.ant) val ant = VehicleDefinition.Ant(ObjectClass.ant)
val ams = VehicleDefinition(ObjectClass.ams) val ams = VehicleDefinition.Ams(ObjectClass.ams)
val router = VehicleDefinition(ObjectClass.router) val router = VehicleDefinition.Router(ObjectClass.router)
val switchblade = VehicleDefinition(ObjectClass.switchblade) val switchblade = VehicleDefinition.Deploying(ObjectClass.switchblade)
val flail = VehicleDefinition(ObjectClass.flail) val flail = VehicleDefinition.Deploying(ObjectClass.flail)
val mosquito = VehicleDefinition(ObjectClass.mosquito) val mosquito = VehicleDefinition(ObjectClass.mosquito)
@ -931,11 +931,11 @@ object GlobalDefinitions {
val vulture = VehicleDefinition(ObjectClass.vulture) val vulture = VehicleDefinition(ObjectClass.vulture)
val dropship = VehicleDefinition(ObjectClass.dropship) val dropship = VehicleDefinition.Carrier(ObjectClass.dropship)
val galaxy_gunship = VehicleDefinition(ObjectClass.galaxy_gunship) val galaxy_gunship = VehicleDefinition(ObjectClass.galaxy_gunship)
val lodestar = VehicleDefinition(ObjectClass.lodestar) val lodestar = VehicleDefinition.Carrier(ObjectClass.lodestar)
val phantasm = VehicleDefinition(ObjectClass.phantasm) val phantasm = VehicleDefinition(ObjectClass.phantasm)
@ -6146,6 +6146,7 @@ object GlobalDefinitions {
apc_tr.MaxDepth = 3 apc_tr.MaxDepth = 3
apc_tr.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L) apc_tr.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
apc_tr.Geometry = apcForm apc_tr.Geometry = apcForm
apc_tr.MaxCapacitor = 300
apc_nc.Name = "apc_nc" // Vindicator apc_nc.Name = "apc_nc" // Vindicator
apc_nc.MaxHealth = 6000 apc_nc.MaxHealth = 6000
@ -6208,6 +6209,7 @@ object GlobalDefinitions {
apc_nc.MaxDepth = 3 apc_nc.MaxDepth = 3
apc_nc.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L) apc_nc.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
apc_nc.Geometry = apcForm apc_nc.Geometry = apcForm
apc_nc.MaxCapacitor = 300
apc_vs.Name = "apc_vs" // Leviathan apc_vs.Name = "apc_vs" // Leviathan
apc_vs.MaxHealth = 6000 apc_vs.MaxHealth = 6000
@ -6270,6 +6272,7 @@ object GlobalDefinitions {
apc_vs.MaxDepth = 3 apc_vs.MaxDepth = 3
apc_vs.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L) apc_vs.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
apc_vs.Geometry = apcForm apc_vs.Geometry = apcForm
apc_vs.MaxCapacitor = 300
lightning.Name = "lightning" lightning.Name = "lightning"
lightning.MaxHealth = 2000 lightning.MaxHealth = 2000

View file

@ -41,6 +41,11 @@ object SpecialEmp {
innateDamage = emp innateDamage = emp
} }
/**
* Trigger an electromagnetic pulse.
*/
final case class Burst()
/** /**
* The damage interaction for an electromagnetic pulse effect. * The damage interaction for an electromagnetic pulse effect.
* @param empEffect information about the effect * @param empEffect information about the effect

View file

@ -1,9 +1,11 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.definition package net.psforever.objects.definition
import net.psforever.objects.NtuContainerDefinition import akka.actor.{ActorContext, Props}
import net.psforever.objects.{Default, NtuContainerDefinition, Vehicle}
import net.psforever.objects.definition.converter.VehicleConverter import net.psforever.objects.definition.converter.VehicleConverter
import net.psforever.objects.inventory.InventoryTile import net.psforever.objects.inventory.InventoryTile
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.vehicles.{DestroyedVehicle, MountableWeaponsDefinition, UtilityType} import net.psforever.objects.vehicles.{DestroyedVehicle, MountableWeaponsDefinition, UtilityType}
import net.psforever.objects.vital._ import net.psforever.objects.vital._
import net.psforever.objects.vital.damage.DamageCalculations import net.psforever.objects.vital.damage.DamageCalculations
@ -181,6 +183,19 @@ class VehicleDefinition(objectId: Int)
destroyedModel = model destroyedModel = model
DestroyedModel DestroyedModel
} }
def Initialize(obj: Vehicle, context: ActorContext): Unit = {
import net.psforever.objects.vehicles.control.VehicleControl
obj.Actor = context.actorOf(
Props(classOf[VehicleControl], obj),
PlanetSideServerObject.UniqueActorName(obj)
)
}
def Uninitialize(obj: Vehicle, context: ActorContext): Unit = {
obj.Actor ! akka.actor.PoisonPill
obj.Actor = Default.Actor
}
} }
object VehicleDefinition { object VehicleDefinition {
@ -189,4 +204,94 @@ object VehicleDefinition {
def apply(objectId: Int): VehicleDefinition = { def apply(objectId: Int): VehicleDefinition = {
new VehicleDefinition(objectId) new VehicleDefinition(objectId)
} }
protected class AmsDefinition(objectId: Int) extends VehicleDefinition(objectId) {
import net.psforever.objects.vehicles.control.AmsControl
override def Initialize(obj: Vehicle, context: ActorContext): Unit = {
obj.Actor = context.actorOf(
Props(classOf[AmsControl], obj),
PlanetSideServerObject.UniqueActorName(obj)
)
}
}
/**
* Vehicle definition for the advanced mobile spawn (AMS) vehicle.
* @param objectId the object id that is associated with this sort of `Vehicle`
*/
def Ams(objectId: Int): VehicleDefinition = new AmsDefinition(objectId)
protected class AntDefinition(objectId: Int) extends VehicleDefinition(objectId) {
import net.psforever.objects.vehicles.control.AntControl
override def Initialize(obj: Vehicle, context: ActorContext): Unit = {
obj.Actor = context.actorOf(
Props(classOf[AntControl], obj),
PlanetSideServerObject.UniqueActorName(obj)
)
}
}
/**
* Vehicle definition for the advanced nanite transport (ANT) vehicle.
* @param objectId the object id that is associated with this sort of `Vehicle`
*/
def Ant(objectId: Int): VehicleDefinition = new AntDefinition(objectId)
protected class ApcDefinition(objectId: Int) extends VehicleDefinition(objectId) {
import net.psforever.objects.vehicles.control.ApcControl
override def Initialize(obj: Vehicle, context: ActorContext): Unit = {
obj.Actor = context.actorOf(
Props(classOf[ApcControl], obj),
PlanetSideServerObject.UniqueActorName(obj)
)
}
}
/**
* Vehicle definition(s) for the armored personnel carrier (`apc*`) vehicles.
* @param objectId the object id that is associated with this sort of `Vehicle`
*/
def Apc(objectId: Int): VehicleDefinition = new ApcDefinition(objectId)
protected class CarrierDefinition(objectId: Int) extends VehicleDefinition(objectId) {
import net.psforever.objects.vehicles.control.CargoCarrierControl
override def Initialize(obj: Vehicle, context: ActorContext): Unit = {
obj.Actor = context.actorOf(
Props(classOf[CargoCarrierControl], obj),
PlanetSideServerObject.UniqueActorName(obj)
)
}
}
/**
* Vehicle definition(s) for the vehicles (carriers) that are used to transport other vehicles (cargo).
* @param objectId the object id that is associated with this sort of `Vehicle`
*/
def Carrier(objectId: Int): VehicleDefinition = new CarrierDefinition(objectId)
protected class DeployingDefinition(objectId: Int) extends VehicleDefinition(objectId) {
import net.psforever.objects.vehicles.control.DeployingVehicleControl
override def Initialize(obj: Vehicle, context: ActorContext): Unit = {
obj.Actor = context.actorOf(
Props(classOf[DeployingVehicleControl], obj),
PlanetSideServerObject.UniqueActorName(obj)
)
}
}
/**
* Vehicle definition(s) for the vehicles that perform significant mode state transitions.
* @param objectId the object id that is associated with this sort of `Vehicle`
*/
def Deploying(objectId: Int): VehicleDefinition = new DeployingDefinition(objectId)
protected class RouterDefinition(objectId: Int) extends VehicleDefinition(objectId) {
import net.psforever.objects.vehicles.control.RouterControl
override def Initialize(obj: Vehicle, context: ActorContext): Unit = {
obj.Actor = context.actorOf(
Props(classOf[RouterControl], obj),
PlanetSideServerObject.UniqueActorName(obj)
)
}
}
/**
* Vehicle definition for the Router.
* @param objectId the object id that is associated with this sort of `Vehicle`
*/
def Router(objectId: Int): VehicleDefinition = new RouterDefinition(objectId)
} }

View file

@ -28,27 +28,26 @@ trait DamageableVehicle
} }
/** whether or not the vehicle has been damaged directly, report that damage has occurred */ /** whether or not the vehicle has been damaged directly, report that damage has occurred */
private var reportDamageToVehicle: Boolean = false protected var reportDamageToVehicle: Boolean = false
def DamageableObject: Vehicle def DamageableObject: Vehicle
def AggravatedObject : Vehicle = DamageableObject def AggravatedObject : Vehicle = DamageableObject
override val takesDamage: Receive = override val takesDamage: Receive = originalTakesDamage
originalTakesDamage .orElse(aggravatedBehavior)
.orElse(aggravatedBehavior) .orElse {
.orElse { case DamageableVehicle.Damage(cause, damage) =>
case DamageableVehicle.Damage(cause, damage) => //cargo vehicles inherit feedback from carrier
//cargo vehicles inherit feedback from carrier reportDamageToVehicle = damage > 0
reportDamageToVehicle = damage > 0 DamageAwareness(DamageableObject, cause, amount = 0)
DamageAwareness(DamageableObject, cause, amount = 0)
case DamageableVehicle.Destruction(cause) => case DamageableVehicle.Destruction(cause) =>
//cargo vehicles are destroyed when carrier is destroyed //cargo vehicles are destroyed when carrier is destroyed
val obj = DamageableObject val obj = DamageableObject
obj.Health = 0 obj.Health = 0
obj.History(cause) obj.History(cause)
DestructionAwareness(obj, cause) DestructionAwareness(obj, cause)
} }
/** /**
* Vehicles may have charged shields that absorb damage before the vehicle's own health is affected. * Vehicles may have charged shields that absorb damage before the vehicle's own health is affected.
@ -83,8 +82,6 @@ trait DamageableVehicle
/** /**
* Most all vehicles and the weapons mounted to them can jam * Most all vehicles and the weapons mounted to them can jam
* if the projectile that strikes (near) them has jammering properties. * if the projectile that strikes (near) them has jammering properties.
* A damaged carrier alerts its cargo vehicles of the source of the damage,
* but it will not be affected by the same jammering effect.
* If this vehicle has shields that were affected by previous damage, that is also reported to the clients. * If this vehicle has shields that were affected by previous damage, that is also reported to the clients.
* @see `Service.defaultPlayerGUID` * @see `Service.defaultPlayerGUID`
* @see `Vehicle.CargoHolds` * @see `Vehicle.CargoHolds`
@ -156,22 +153,10 @@ trait DamageableVehicle
//alert to damage source //alert to damage source
DamageableMountable.DamageAwareness(obj, cause, totalDamage) DamageableMountable.DamageAwareness(obj, cause, totalDamage)
} }
//alert cargo occupants to damage source
obj.CargoHolds.values.foreach(hold => {
hold.occupant match {
case Some(cargo) =>
cargo.Actor ! DamageableVehicle.Damage(cause, totalDamage)
case None => ;
}
})
} }
} }
/** /**
* A destroyed carrier informs its cargo vehicles that they should also be destroyed
* for reasons of the same cause being inherited as the source of damage.
* Regardless of the amount of damage they carrier takes or some other target would take,
* its cargo vehicles die immediately.
* The vehicle's shields are zero'd out if they were previously energized * The vehicle's shields are zero'd out if they were previously energized
* so that the vehicle's corpse does not act like it is still protected by vehicle shields. * so that the vehicle's corpse does not act like it is still protected by vehicle shields.
* Finally, the vehicle is tasked for deconstruction. * Finally, the vehicle is tasked for deconstruction.
@ -196,14 +181,6 @@ trait DamageableVehicle
EndAllAggravation() EndAllAggravation()
//passengers die with us //passengers die with us
DamageableMountable.DestructionAwareness(obj, cause) DamageableMountable.DestructionAwareness(obj, cause)
//cargo vehicles die with us
obj.CargoHolds.values.foreach(hold => {
hold.occupant match {
case Some(cargo) =>
cargo.Actor ! DamageableVehicle.Destruction(cause)
case None => ;
}
})
//things positioned around us can get hurt from us //things positioned around us can get hurt from us
Zone.serverSideDamage(obj.Zone, target, Zone.explosionDamage(Some(cause))) Zone.serverSideDamage(obj.Zone, target, Zone.explosionDamage(Some(cause)))
//special considerations for certain vehicles //special considerations for certain vehicles
@ -224,17 +201,16 @@ trait DamageableVehicle
} }
object DamageableVehicle { object DamageableVehicle {
/** /**
* Message for instructing the target's cargo vehicles about a damage source affecting their carrier. * Message for instructing the target's cargo vehicles about a damage source affecting their carrier.
* @param cause historical information about damage * @param cause historical information about damage
*/ */
private case class Damage(cause: DamageResult, amount: Int) final case class Damage(cause: DamageResult, amount: Int)
/** /**
* Message for instructing the target's cargo vehicles that their carrier is destroyed, * Message for instructing the target's cargo vehicles that their carrier is destroyed,
* and they should be destroyed too. * and they should be destroyed too.
* @param cause historical information about damage * @param cause historical information about damage
*/ */
private case class Destruction(cause: DamageResult) final case class Destruction(cause: DamageResult)
} }

View file

@ -110,4 +110,8 @@ object Deployment {
*/ */
def CheckForUndeployState(state: DriveState.Value): Boolean = def CheckForUndeployState(state: DriveState.Value): Boolean =
state == DriveState.Undeploying || state == DriveState.Mobile || state == DriveState.State7 state == DriveState.Undeploying || state == DriveState.Mobile || state == DriveState.State7
def AngleCheck(obj: Deployment.DeploymentObject): Boolean = {
obj.Orientation.x <= 30 || obj.Orientation.x >= 330
}
} }

View file

@ -0,0 +1,58 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.vehicles.control
import net.psforever.objects._
import net.psforever.services.Service
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.types.DriveState
/**
* A vehicle control agency exclusive to the advanced mobile spawn (AMS).
* When deployed, infantry troops may manifest nearby the vehicle
* as they switch from being deconstructed (or dead) to being alive.
* @param vehicle the AMS
*/
class AmsControl(vehicle: Vehicle)
extends DeployingVehicleControl(vehicle) {
/**
* React to a deployment state change.
* Announce that this AMS is ready to accept troop deployment.
* @param state the deployment state
*/
override def specificResponseToDeployment(state: DriveState.Value): Unit = {
state match {
case DriveState.Deployed =>
val zone = vehicle.Zone
val driverChannel = vehicle.Seats(0).occupant match {
case Some(tplayer) => tplayer.Name
case None => ""
}
val events = zone.VehicleEvents
events ! VehicleServiceMessage.AMSDeploymentChange(zone)
events ! VehicleServiceMessage(driverChannel, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vehicle.GUID, 81, 1))
case _ => ;
}
}
/**
* React to an undeployment state change.
* This AMS is now off the grid.
* @param state the deployment state
*/
override def specificResponseToUndeployment(state: DriveState.Value): Unit = {
state match {
case DriveState.Undeploying =>
val zone = vehicle.Zone
val driverChannel = vehicle.Seats(0).occupant match {
case Some(tplayer) => tplayer.Name
case None => ""
}
val events = zone.VehicleEvents
events ! VehicleServiceMessage.AMSDeploymentChange(zone)
events ! VehicleServiceMessage(driverChannel, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vehicle.GUID, 81, 0))
case _ => ;
}
}
}

View file

@ -0,0 +1,60 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.vehicles.control
import net.psforever.objects._
import net.psforever.objects.serverobject.transfer.TransferBehavior
import net.psforever.objects.vehicles._
import net.psforever.types.DriveState
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
/**
* A vehicle control agency exclusive to the advanced nanite transport (ANT).
* When deployed, nanites in the package of nanite transfer units (NTU) are moved around
* and may be may be acquired from a Warp Gate structure
* or supplied to a nanite resource silo belonging to a mjaor facility.
* @param vehicle the ANT
*/
class AntControl(vehicle: Vehicle)
extends DeployingVehicleControl(vehicle)
with AntTransferBehavior {
def ChargeTransferObject = vehicle
findChargeTargetFunc = Vehicles.FindANTChargingSource
findDischargeTargetFunc = Vehicles.FindANTDischargingTarget
override def commonEnabledBehavior: Receive = super.commonEnabledBehavior.orElse(antBehavior)
/**
* React to a deployment state change.
* Make ourselves available to nanite charging or discharging.
* @param state the deployment state
*/
override def specificResponseToDeployment(state: DriveState.Value): Unit = {
state match {
case DriveState.Deployed =>
// Start ntu regeneration
// If vehicle sends UseItemMessage with silo as target NTU regeneration will be disabled and orb particles will be disabled
context.system.scheduler.scheduleOnce(
delay = 1000 milliseconds,
vehicle.Actor,
TransferBehavior.Charging(Ntu.Nanites)
)
case _ => ;
}
}
/**
* React to an undeployment state change.
* Stop charging or discharging and tell the partner entity that it is no longer interacting with this vehicle.
* @param state the undeployment state
*/
override def specificResponseToUndeployment(state: DriveState.Value): Unit = {
state match {
case DriveState.Undeploying =>
TryStopChargingEvent(vehicle)
case _ => ;
}
}
}

View file

@ -0,0 +1,116 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.vehicles.control
import net.psforever.objects._
import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.vital.interaction.DamageResult
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{TriggerEffectMessage, TriggeredEffectLocation}
import net.psforever.services.Service
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
/**
* A vehicle control agency exclusive to the armored personnel carrier (APC) ground transport vehicles.
* These vehicles include the Juggernaut (`apc_tr`), the Vindicator (`apc_nc`), the and Leviathan (`apc_vs`).
* A completely faction-neutral transport in the same sytle (`apc`) does exist but is unused.
* A common characteristic of this type of vehicle is the ability to discharge a defensive wide-area electromagnetic pulse.
* @param vehicle the APC
*/
class ApcControl(vehicle: Vehicle)
extends VehicleControl(vehicle) {
protected var capacitor = Default.Cancellable
startCapacitorTimer()
override def postStop() : Unit = {
super.postStop()
capacitor.cancel()
}
override def commonEnabledBehavior : Receive =
super.commonEnabledBehavior
.orElse {
case ApcControl.CapacitorCharge(amount) =>
if (vehicle.Capacitor < vehicle.Definition.MaxCapacitor) {
val capacitance = vehicle.Capacitor += amount
vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
self.toString(),
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vehicle.GUID, 113, capacitance)
)
startCapacitorTimer()
} else {
capacitor = Default.Cancellable
}
case SpecialEmp.Burst() =>
if (vehicle.Capacitor == vehicle.Definition.MaxCapacitor) { //only if the capacitor is full
val zone = vehicle.Zone
val events = zone.VehicleEvents
val pos = vehicle.Position
val GUID0 = Service.defaultPlayerGUID
val emp = vehicle.Definition.innateDamage.getOrElse { SpecialEmp.emp }
val faction = vehicle.Faction
//drain the capacitor
vehicle.Capacitor = 0
events ! VehicleServiceMessage(
self.toString(),
VehicleAction.PlanetsideAttribute(GUID0, vehicle.GUID, 113, 0)
)
//cause the emp
events ! VehicleServiceMessage(
zone.id,
VehicleAction.SendResponse(
GUID0,
TriggerEffectMessage(
GUID0,
s"apc_explosion_emp_${faction.toString.toLowerCase}",
None,
Some(TriggeredEffectLocation(pos, vehicle.Orientation))
)
)
)
//resolve what targets are affected by the emp
Zone.serverSideDamage(
zone,
vehicle,
emp,
SpecialEmp.createEmpInteraction(emp, pos),
ExplosiveDeployableControl.detectionForExplosiveSource(vehicle),
Zone.findAllTargets
)
//start charging again
startCapacitorTimer()
}
}
override def PrepareForDisabled(kickPassengers: Boolean) : Unit = {
super.PrepareForDisabled(kickPassengers)
capacitor.cancel()
}
override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = {
super.DestructionAwareness(target, cause)
capacitor.cancel()
vehicle.Capacitor = 0
}
//TODO switch from magic numbers to definition numbers?
private def startCapacitorTimer(): Unit = {
capacitor = context.system.scheduler.scheduleOnce(
delay = 1000 millisecond,
self,
ApcControl.CapacitorCharge(10)
)
}
}
object ApcControl {
/**
* Charge the vehicle's internal capacitor by the given amount during the schedulefd charge event.
* @param amount how much energy in the charge
*/
private case class CapacitorCharge(amount: Int)
}

View file

@ -0,0 +1,88 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.vehicles.control
import net.psforever.objects._
import net.psforever.objects.serverobject.damage.{Damageable, DamageableVehicle}
import net.psforever.objects.vehicles.CargoBehavior
import net.psforever.objects.vital.interaction.DamageResult
/**
* A vehicle control agency exclusive to vehicles that can physically transport other vehicles.
* This includes the Galaxy (`dropship`) and the Lodestar.
* @param vehicle the vehicle
*/
class CargoCarrierControl(vehicle: Vehicle)
extends VehicleControl(vehicle)
with CargoBehavior {
def CargoObject = vehicle
override def commonEnabledBehavior: Receive = super.commonEnabledBehavior.orElse(cargoBehavior)
/**
* If the vehicle becomes disabled, the safety and autonomy of the cargo should be prioritized.
* @param kickPassengers passengers need to be ejected "by force"
*/
override def PrepareForDisabled(kickPassengers: Boolean) : Unit = {
//abandon all cargo
vehicle.CargoHolds.values
.collect {
case hold if hold.isOccupied =>
val cargo = hold.occupant.get
CargoBehavior.HandleVehicleCargoDismount(
cargo.GUID,
cargo,
vehicle.GUID,
vehicle,
bailed = false,
requestedByPassenger = false,
kicked = false
)
}
super.PrepareForDisabled(kickPassengers)
}
/**
* A damaged carrier alerts its cargo vehicles of the source of the damage,
* but that cargo will not be affected by either damage directly or by other effects applied to the carrier.
* @param target the entity being destroyed
* @param cause historical information about the damage
* @param amount how much damage was performed
*/
override protected def DamageAwareness(target: Damageable.Target, cause: DamageResult, amount: Any): Unit = {
val report = amount match {
case (a: Int, b: Int) => a + b
case a: Int => a
case _ => 0
}
val announceConfrontation: Boolean = reportDamageToVehicle || report > 0
super.DamageAwareness(target, cause, amount)
if (announceConfrontation) {
//alert cargo occupants to damage source
vehicle.CargoHolds.values.foreach(hold => {
hold.occupant match {
case Some(cargo) => cargo.Actor ! DamageableVehicle.Damage(cause, report)
case None => ;
}
})
}
}
/**
* A destroyed carrier informs its cargo vehicles that they should also be destroyed
* for reasons of the same cause being inherited as the source of damage.
* Regardless of the amount of damage they carrier takes or some other target would take,
* its cargo vehicles die immediately.
* @param target the entity being destroyed
* @param cause historical information about the damage
*/
override protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = {
super.DestructionAwareness(target, cause)
//cargo vehicles die with us
vehicle.CargoHolds.values.foreach { hold =>
hold.occupant match {
case Some(cargo) => cargo.Actor ! DamageableVehicle.Destruction(cause)
case None => ;
}
}
}
}

View file

@ -0,0 +1,97 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.vehicles.control
import net.psforever.objects._
import net.psforever.objects.serverobject.deploy.Deployment.DeploymentObject
import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior}
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.types._
/**
* A vehicle control agency exclusive to vehicles that can switch out a navigation mode
* and convert to a sessile mode that affords additional functionality.
* This includes only the Switchblade and the Flail.
* Other vehicles that deploy are handled by specific instances of this control agency.
* @see `AmsControl`
* @see `AntControl`
* @see `RouterControl`
* @param vehicle the vehicle
*/
class DeployingVehicleControl(vehicle: Vehicle)
extends VehicleControl(vehicle)
with DeploymentBehavior {
def DeploymentObject = vehicle
override def commonEnabledBehavior : Receive = super.commonEnabledBehavior.orElse(deployBehavior)
/**
* Even when disabled, the vehicle can be made to undeploy.
* Even when disabled, passengers can formally dismount from the vehicle.
*/
override def commonDisabledBehavior : Receive =
super.commonDisabledBehavior
.orElse {
case msg : Deployment.TryUndeploy =>
deployBehavior.apply(msg)
case msg @ Mountable.TryDismount(_, seat_num) =>
dismountBehavior.apply(msg)
dismountCleanup(seat_num)
}
/**
* Even when on the verge of deletion, the vehicle can be made to undeploy.
*/
override def commonDeleteBehavior : Receive =
super.commonDeleteBehavior
.orElse {
case msg : Deployment.TryUndeploy =>
deployBehavior.apply(msg)
}
/**
* Even when disabled, the vehicle can be made to undeploy.
*/
override def PrepareForDisabled(kickPassengers: Boolean) : Unit = {
vehicle.Actor ! Deployment.TryUndeploy(DriveState.Undeploying)
super.PrepareForDisabled(kickPassengers)
}
/**
* Even when on the verge of deletion, the vehicle can be made to undeploy.
*/
override def PrepareForDeletion() : Unit = {
vehicle.Actor ! Deployment.TryUndeploy(DriveState.Undeploying)
super.PrepareForDeletion()
}
override def TryDeploymentChange(obj: Deployment.DeploymentObject, state: DriveState.Value): Boolean = {
Deployment.AngleCheck(obj) && super.TryDeploymentChange(obj, state)
}
override def DeploymentAction(
obj: DeploymentObject,
state: DriveState.Value,
prevState: DriveState.Value
): DriveState.Value = {
val out = super.DeploymentAction(obj, state, prevState)
Vehicles.ReloadAccessPermissions(vehicle, vehicle.Faction.toString)
specificResponseToDeployment(state)
out
}
def specificResponseToDeployment(state: DriveState.Value): Unit = { }
override def UndeploymentAction(
obj: DeploymentObject,
state: DriveState.Value,
prevState: DriveState.Value
): DriveState.Value = {
val out = if (decaying) state else super.UndeploymentAction(obj, state, prevState)
Vehicles.ReloadAccessPermissions(vehicle, vehicle.Faction.toString)
specificResponseToUndeployment(state)
out
}
def specificResponseToUndeployment(state: DriveState.Value): Unit = { }
}

View file

@ -0,0 +1,50 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.vehicles.control
import net.psforever.objects._
import net.psforever.objects.ce.TelepadLike
import net.psforever.objects.vehicles.{Utility, UtilityType}
import net.psforever.types.DriveState
/**
* A vehicle control agency exclusive to the router.
* When deployed, any router telepad that was acquired from this particular router
* and then constructed into a router telepad somewhere in the world
* may synchronize with the vehicle to establish a short to medium range infantry teleportation system.
* @param vehicle the router
*/
class RouterControl(vehicle: Vehicle)
extends DeployingVehicleControl(vehicle) {
/**
* React to a deployment state change.
* Activate the internal telepad mechanism.
* @param state the deployment state
*/
override def specificResponseToDeployment(state: DriveState.Value): Unit = {
state match {
case DriveState.Deployed =>
vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
case Some(util: Utility.InternalTelepad) => util.Actor ! TelepadLike.Activate(util)
case _ => ;
}
case _ => ;
}
}
/**
* React to an undeployment state change.
* Deactivate the internal telepad mechanism.
* @param state the deployment state
*/
override def specificResponseToUndeployment(state: DriveState.Value): Unit = {
state match {
case DriveState.Undeploying =>
vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
case Some(util: Utility.InternalTelepad) => util.Actor ! TelepadLike.Deactivate(util)
case _ => ;
}
case _ => ;
}
}
}

View file

@ -1,11 +1,10 @@
// Copyright (c) 2017-2020 PSForever // Copyright (c) 2017-2021 PSForever
package net.psforever.objects.vehicles package net.psforever.objects.vehicles.control
import akka.actor.{Actor, Cancellable} import akka.actor.{Actor, Cancellable}
import net.psforever.actors.zone.ZoneActor import net.psforever.actors.zone.ZoneActor
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.ballistics.VehicleSource import net.psforever.objects.ballistics.VehicleSource
import net.psforever.objects.ce.TelepadLike
import net.psforever.objects.entity.WorldEntity import net.psforever.objects.entity.WorldEntity
import net.psforever.objects.equipment.{Equipment, EquipmentSlot, JammableMountedWeapons} import net.psforever.objects.equipment.{Equipment, EquipmentSlot, JammableMountedWeapons}
import net.psforever.objects.guid.GUIDTask import net.psforever.objects.guid.GUIDTask
@ -14,14 +13,12 @@ import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObjec
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior} import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior}
import net.psforever.objects.serverobject.damage.{AggravatedBehavior, DamageableVehicle} import net.psforever.objects.serverobject.damage.{AggravatedBehavior, DamageableVehicle}
import net.psforever.objects.serverobject.deploy.Deployment.DeploymentObject
import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior}
import net.psforever.objects.serverobject.environment._ import net.psforever.objects.serverobject.environment._
import net.psforever.objects.serverobject.hackable.GenericHackables import net.psforever.objects.serverobject.hackable.GenericHackables
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
import net.psforever.objects.serverobject.repair.RepairableVehicle import net.psforever.objects.serverobject.repair.RepairableVehicle
import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.serverobject.transfer.TransferBehavior import net.psforever.objects.vehicles.{AccessPermissionGroup, CargoBehavior, Utility, VehicleLockState}
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult} import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
import net.psforever.objects.vital.VehicleShieldCharge import net.psforever.objects.vital.VehicleShieldCharge
import net.psforever.objects.vital.environment.EnvironmentReason import net.psforever.objects.vital.environment.EnvironmentReason
@ -40,45 +37,36 @@ import scala.concurrent.duration._
/** /**
* An `Actor` that handles messages being dispatched to a specific `Vehicle`.<br> * An `Actor` that handles messages being dispatched to a specific `Vehicle`.<br>
* <br> * <br>
* Vehicle-controlling actors have two behavioral states - responsive and "`Disabled`." * Vehicle-controlling actors have two important behavioral states - responsive and "`Disabled`."
* The latter is applicable only when the specific vehicle is being deconstructed. * The latter is applicable only when the specific vehicle is being deconstructed.
* * Furthermore, being "ready to delete" is also a behavoral state for the end of life operations of the vehicle.
* @param vehicle the `Vehicle` object being governed * @param vehicle the `Vehicle` object being governed
*/ */
class VehicleControl(vehicle: Vehicle) class VehicleControl(vehicle: Vehicle)
extends Actor extends Actor
with FactionAffinityBehavior.Check with FactionAffinityBehavior.Check
with DeploymentBehavior
with MountableBehavior with MountableBehavior
with CargoBehavior
with DamageableVehicle with DamageableVehicle
with RepairableVehicle with RepairableVehicle
with JammableMountedWeapons with JammableMountedWeapons
with ContainableBehavior with ContainableBehavior
with AntTransferBehavior
with AggravatedBehavior with AggravatedBehavior
with RespondsToZoneEnvironment { with RespondsToZoneEnvironment {
//make control actors belonging to utilities when making control actor belonging to vehicle //make control actors belonging to utilities when making control actor belonging to vehicle
vehicle.Utilities.foreach({ case (_, util) => util.Setup }) vehicle.Utilities.foreach { case (_, util) => util.Setup }
def MountableObject = vehicle def MountableObject = vehicle
def CargoObject = vehicle
def JammableObject = vehicle def JammableObject = vehicle
def FactionObject = vehicle def FactionObject = vehicle
def DeploymentObject = vehicle
def DamageableObject = vehicle def DamageableObject = vehicle
def RepairableObject = vehicle def RepairableObject = vehicle
def ContainerObject = vehicle def ContainerObject = vehicle
def ChargeTransferObject = vehicle
def InteractiveObject = vehicle def InteractiveObject = vehicle
SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater) SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater)
SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava) SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava)
@ -87,10 +75,6 @@ class VehicleControl(vehicle: Vehicle)
SetInteractionStop(EnvironmentAttribute.Water, stopInteractingWithWater) SetInteractionStop(EnvironmentAttribute.Water, stopInteractingWithWater)
} }
if (vehicle.Definition == GlobalDefinitions.ant) {
findChargeTargetFunc = Vehicles.FindANTChargingSource
findDischargeTargetFunc = Vehicles.FindANTDischargingTarget
}
/** cheap flag for whether the vehicle is decaying */ /** cheap flag for whether the vehicle is decaying */
var decaying : Boolean = false var decaying : Boolean = false
/** primary vehicle decay timer */ /** primary vehicle decay timer */
@ -112,199 +96,197 @@ class VehicleControl(vehicle: Vehicle)
recoverFromEnvironmentInteracting() recoverFromEnvironmentInteracting()
} }
def Enabled : Receive = def commonEnabledBehavior: Receive = checkBehavior
checkBehavior .orElse(jammableBehavior)
.orElse(deployBehavior) .orElse(takesDamage)
.orElse(cargoBehavior) .orElse(canBeRepairedByNanoDispenser)
.orElse(jammableBehavior) .orElse(containerBehavior)
.orElse(takesDamage) .orElse(environmentBehavior)
.orElse(canBeRepairedByNanoDispenser) .orElse {
.orElse(containerBehavior) case Vehicle.Ownership(None) =>
.orElse(antBehavior) LoseOwnership()
.orElse(environmentBehavior)
.orElse {
case Vehicle.Ownership(None) =>
LoseOwnership()
case Vehicle.Ownership(Some(player)) => case Vehicle.Ownership(Some(player)) =>
GainOwnership(player) GainOwnership(player)
case msg @ Mountable.TryMount(player, mount_point) => case msg @ Mountable.TryMount(player, mount_point) =>
mountBehavior.apply(msg) mountBehavior.apply(msg)
mountCleanup(mount_point, player) mountCleanup(mount_point, player)
case msg @ Mountable.TryDismount(_, seat_num) => case msg @ Mountable.TryDismount(_, seat_num) =>
dismountBehavior.apply(msg) dismountBehavior.apply(msg)
dismountCleanup(seat_num) dismountCleanup(seat_num)
case Vehicle.ChargeShields(amount) => case Vehicle.ChargeShields(amount) =>
val now : Long = System.currentTimeMillis() val now : Long = System.currentTimeMillis()
//make certain vehicle doesn't charge shields too quickly //make certain vehicles don't charge shields too quickly
if ( if (
vehicle.Health > 0 && vehicle.Shields < vehicle.MaxShields && vehicle.Health > 0 && vehicle.Shields < vehicle.MaxShields &&
!vehicle.History.exists(VehicleControl.LastShieldChargeOrDamage(now)) !vehicle.History.exists(VehicleControl.LastShieldChargeOrDamage(now))
) { ) {
vehicle.History(VehicleShieldCharge(VehicleSource(vehicle), amount)) vehicle.History(VehicleShieldCharge(VehicleSource(vehicle), amount))
vehicle.Shields = vehicle.Shields + amount vehicle.Shields = vehicle.Shields + amount
vehicle.Zone.VehicleEvents ! VehicleServiceMessage( vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
s"${vehicle.Actor}", s"${vehicle.Actor}",
VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), vehicle.GUID, 68, vehicle.Shields) VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), vehicle.GUID, 68, vehicle.Shields)
) )
} }
case Vehicle.UpdateZoneInteractionProgressUI(player) => case Vehicle.UpdateZoneInteractionProgressUI(player) =>
updateZoneInteractionProgressUI(player) updateZoneInteractionProgressUI(player)
case FactionAffinity.ConvertFactionAffinity(faction) => case FactionAffinity.ConvertFactionAffinity(faction) =>
val originalAffinity = vehicle.Faction val originalAffinity = vehicle.Faction
if (originalAffinity != (vehicle.Faction = faction)) { if (originalAffinity != (vehicle.Faction = faction)) {
vehicle.Utilities.foreach({ vehicle.Utilities.foreach({
case (_ : Int, util : Utility) => util().Actor forward FactionAffinity.ConfirmFactionAffinity() case (_ : Int, util : Utility) => util().Actor forward FactionAffinity.ConfirmFactionAffinity()
}) })
} }
sender() ! FactionAffinity.AssertFactionAffinity(vehicle, faction) sender() ! FactionAffinity.AssertFactionAffinity(vehicle, faction)
case CommonMessages.Use(player, Some(item : SimpleItem)) case CommonMessages.Use(player, Some(item : SimpleItem))
if item.Definition == GlobalDefinitions.remote_electronics_kit => if item.Definition == GlobalDefinitions.remote_electronics_kit =>
//TODO setup certifications check //TODO setup certifications check
if (vehicle.Faction != player.Faction) { if (vehicle.Faction != player.Faction) {
sender() ! CommonMessages.Progress( sender() ! CommonMessages.Progress(
GenericHackables.GetHackSpeed(player, vehicle), GenericHackables.GetHackSpeed(player, vehicle),
Vehicles.FinishHackingVehicle(vehicle, player, 3212836864L), Vehicles.FinishHackingVehicle(vehicle, player, 3212836864L),
GenericHackables.HackingTickAction(progressType = 1, player, vehicle, item.GUID) GenericHackables.HackingTickAction(progressType = 1, player, vehicle, item.GUID)
) )
} }
case Terminal.TerminalMessage(player, msg, reply) => case Terminal.TerminalMessage(player, msg, reply) =>
reply match { reply match {
case Terminal.VehicleLoadout(definition, weapons, inventory) => case Terminal.VehicleLoadout(definition, weapons, inventory) =>
org.log4s org.log4s
.getLogger(vehicle.Definition.Name) .getLogger(vehicle.Definition.Name)
.info(s"changing vehicle equipment loadout to ${player.Name}'s option #${msg.unk1 + 1}") .info(s"changing vehicle equipment loadout to ${player.Name}'s option #${msg.unk1 + 1}")
//remove old inventory //remove old inventory
val oldInventory = vehicle.Inventory.Clear().map { case InventoryItem(obj, _) => (obj, obj.GUID) } val oldInventory = vehicle.Inventory.Clear().map { case InventoryItem(obj, _) => (obj, obj.GUID) }
//"dropped" items are lost; if it doesn't go in the trunk, it vanishes into the nanite cloud //"dropped" items are lost; if it doesn't go in the trunk, it vanishes into the nanite cloud
val (_, afterInventory) = inventory.partition(ContainableBehavior.DropPredicate(player)) val (_, afterInventory) = inventory.partition(ContainableBehavior.DropPredicate(player))
val (oldWeapons, newWeapons, finalInventory) = if (vehicle.Definition == definition) { val (oldWeapons, newWeapons, finalInventory) = if (vehicle.Definition == definition) {
//vehicles are the same type //vehicles are the same type
//TODO want to completely swap weapons, but holster icon vanishes temporarily after swap //TODO want to completely swap weapons, but holster icon vanishes temporarily after swap
//TODO BFR arms must be swapped properly //TODO BFR arms must be swapped properly
// //remove old weapons // //remove old weapons
// val oldWeapons = vehicle.Weapons.values.collect { case slot if slot.Equipment.nonEmpty => // val oldWeapons = vehicle.Weapons.values.collect { case slot if slot.Equipment.nonEmpty =>
// val obj = slot.Equipment.get // val obj = slot.Equipment.get
// slot.Equipment = None // slot.Equipment = None
// (obj, obj.GUID) // (obj, obj.GUID)
// }.toList // }.toList
// (oldWeapons, weapons, afterInventory) // (oldWeapons, weapons, afterInventory)
//TODO for now, just refill ammo; assume weapons stay the same //TODO for now, just refill ammo; assume weapons stay the same
vehicle.Weapons vehicle.Weapons
.collect { case (_, slot : EquipmentSlot) if slot.Equipment.nonEmpty => slot.Equipment.get } .collect { case (_, slot : EquipmentSlot) if slot.Equipment.nonEmpty => slot.Equipment.get }
.collect { .collect {
case weapon : Tool => case weapon : Tool =>
weapon.AmmoSlots.foreach { ammo => ammo.Box.Capacity = ammo.Box.Definition.Capacity } weapon.AmmoSlots.foreach { ammo => ammo.Box.Capacity = ammo.Box.Definition.Capacity }
} }
(Nil, Nil, afterInventory) (Nil, Nil, afterInventory)
}
else {
//vehicle loadout is not for this vehicle
//do not transfer over weapon ammo
if (
vehicle.Definition.TrunkSize == definition.TrunkSize && vehicle.Definition.TrunkOffset == definition.TrunkOffset
) {
(Nil, Nil, afterInventory) //trunk is the same dimensions, however
} }
else { else {
//vehicle loadout is not for this vehicle //accommodate as much of inventory as possible
//do not transfer over weapon ammo val (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory)
if ( (Nil, Nil, stow)
vehicle.Definition.TrunkSize == definition.TrunkSize && vehicle.Definition.TrunkOffset == definition.TrunkOffset
) {
(Nil, Nil, afterInventory) //trunk is the same dimensions, however
}
else {
//accommodate as much of inventory as possible
val (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory)
(Nil, Nil, stow)
}
} }
finalInventory.foreach { }
_.obj.Faction = vehicle.Faction finalInventory.foreach {
} _.obj.Faction = vehicle.Faction
player.Zone.VehicleEvents ! VehicleServiceMessage( }
player.Zone.id, player.Zone.VehicleEvents ! VehicleServiceMessage(
VehicleAction.ChangeLoadout(vehicle.GUID, oldWeapons, newWeapons, oldInventory, finalInventory) player.Zone.id,
) VehicleAction.ChangeLoadout(vehicle.GUID, oldWeapons, newWeapons, oldInventory, finalInventory)
player.Zone.AvatarEvents ! AvatarServiceMessage( )
player.Name, player.Zone.AvatarEvents ! AvatarServiceMessage(
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, true) player.Name,
) AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, true)
)
case _ => ; case _ => ;
} }
case VehicleControl.Disable() => case VehicleControl.Disable() =>
PrepareForDisabled(kickPassengers = false) PrepareForDisabled(kickPassengers = false)
context.become(Disabled) context.become(Disabled)
case Vehicle.Deconstruct(time) => case Vehicle.Deconstruct(time) =>
time match { time match {
case Some(delay) if vehicle.Definition.undergoesDecay => case Some(delay) if vehicle.Definition.undergoesDecay =>
decaying = true decaying = true
decayTimer.cancel() decayTimer.cancel()
decayTimer = context.system.scheduler.scheduleOnce(delay, self, VehicleControl.PrepareForDeletion()) decayTimer = context.system.scheduler.scheduleOnce(delay, self, VehicleControl.PrepareForDeletion())
case _ => case _ =>
PrepareForDisabled(kickPassengers = true) PrepareForDisabled(kickPassengers = true)
PrepareForDeletion() PrepareForDeletion()
context.become(ReadyToDelete) context.become(ReadyToDelete)
} }
case VehicleControl.PrepareForDeletion() => case VehicleControl.PrepareForDeletion() =>
PrepareForDisabled(kickPassengers = true) PrepareForDisabled(kickPassengers = true)
PrepareForDeletion() PrepareForDeletion()
context.become(ReadyToDelete) context.become(ReadyToDelete)
case VehicleControl.AssignOwnership(player) => case VehicleControl.AssignOwnership(player) =>
vehicle.AssignOwnership(player) vehicle.AssignOwnership(player)
}
final def Enabled: Receive =
commonEnabledBehavior
.orElse {
case _ => ; case _ => ;
} }
def Disabled : Receive = def commonDisabledBehavior: Receive = checkBehavior
checkBehavior .orElse {
.orElse { case msg @ Mountable.TryDismount(_, seat_num) =>
case msg : Deployment.TryUndeploy => dismountBehavior.apply(msg)
deployBehavior.apply(msg) dismountCleanup(seat_num)
case msg @ Mountable.TryDismount(_, seat_num) => case Vehicle.Deconstruct(time) =>
dismountBehavior.apply(msg) time match {
dismountCleanup(seat_num) case Some(delay) if vehicle.Definition.undergoesDecay =>
decaying = true
decayTimer.cancel()
decayTimer = context.system.scheduler.scheduleOnce(delay, self, VehicleControl.PrepareForDeletion())
case _ =>
PrepareForDeletion()
context.become(ReadyToDelete)
}
case Vehicle.Deconstruct(time) => case VehicleControl.PrepareForDeletion() =>
time match { PrepareForDeletion()
case Some(delay) if vehicle.Definition.undergoesDecay => context.become(ReadyToDelete)
decaying = true }
decayTimer.cancel()
decayTimer = context.system.scheduler.scheduleOnce(delay, self, VehicleControl.PrepareForDeletion())
case _ =>
PrepareForDeletion()
context.become(ReadyToDelete)
}
case VehicleControl.PrepareForDeletion() => final def Disabled: Receive = commonDisabledBehavior
PrepareForDeletion() .orElse {
context.become(ReadyToDelete) case _ => ;
}
case _ => def commonDeleteBehavior: Receive = checkBehavior
} .orElse {
case VehicleControl.Deletion() =>
val zone = vehicle.Zone
zone.VehicleEvents ! VehicleServiceMessage(
zone.id,
VehicleAction.UnloadVehicle(Service.defaultPlayerGUID, vehicle, vehicle.GUID)
)
zone.Transport.tell(Zone.Vehicle.Despawn(vehicle), zone.Transport)
}
def ReadyToDelete : Receive = final def ReadyToDelete: Receive = commonDeleteBehavior
checkBehavior .orElse {
.orElse { case _ => ;
case msg : Deployment.TryUndeploy => }
deployBehavior.apply(msg)
case VehicleControl.Deletion() =>
val zone = vehicle.Zone
zone.VehicleEvents ! VehicleServiceMessage(
zone.id,
VehicleAction.UnloadVehicle(Service.defaultPlayerGUID, vehicle, vehicle.GUID)
)
zone.Transport.tell(Zone.Vehicle.Despawn(vehicle), zone.Transport)
case _ =>
}
override protected def mountTest( override protected def mountTest(
obj: PlanetSideServerObject with Mountable, obj: PlanetSideServerObject with Mountable,
@ -387,7 +369,6 @@ class VehicleControl(vehicle: Vehicle)
val events = zone.VehicleEvents val events = zone.VehicleEvents
//miscellaneous changes //miscellaneous changes
recoverFromEnvironmentInteracting() recoverFromEnvironmentInteracting()
Vehicles.BeforeUnloadVehicle(vehicle, zone)
//escape being someone else's cargo //escape being someone else's cargo
vehicle.MountedIn match { vehicle.MountedIn match {
case Some(_) => case Some(_) =>
@ -417,28 +398,11 @@ class VehicleControl(vehicle: Vehicle)
} }
} }
} }
//abandon all cargo }
vehicle.CargoHolds.values
.collect {
case hold if hold.isOccupied =>
val cargo = hold.occupant.get
CargoBehavior.HandleVehicleCargoDismount(
cargo.GUID,
cargo,
guid,
vehicle,
bailed = false,
requestedByPassenger = false,
kicked = false
)
}
}
def PrepareForDeletion() : Unit = { def PrepareForDeletion() : Unit = {
decaying = false decaying = false
val zone = vehicle.Zone val zone = vehicle.Zone
//miscellaneous changes
Vehicles.BeforeUnloadVehicle(vehicle, zone)
//cancel jammed behavior //cancel jammed behavior
CancelJammeredSound(vehicle) CancelJammeredSound(vehicle)
CancelJammeredStatus(vehicle) CancelJammeredStatus(vehicle)
@ -557,119 +521,6 @@ class VehicleControl(vehicle: Vehicle)
) )
} }
override def TryDeploymentChange(obj: Deployment.DeploymentObject, state: DriveState.Value): Boolean = {
VehicleControl.DeploymentAngleCheck(obj) && super.TryDeploymentChange(obj, state)
}
override def DeploymentAction(
obj: DeploymentObject,
state: DriveState.Value,
prevState: DriveState.Value
): DriveState.Value = {
val out = super.DeploymentAction(obj, state, prevState)
obj match {
case vehicle: Vehicle =>
val guid = vehicle.GUID
val zone = vehicle.Zone
val zoneChannel = zone.id
val GUID0 = Service.defaultPlayerGUID
val driverChannel = vehicle.Seats(0).occupant match {
case Some(tplayer) => tplayer.Name
case None => ""
}
Vehicles.ReloadAccessPermissions(vehicle, vehicle.Faction.toString)
//ams
if (vehicle.Definition == GlobalDefinitions.ams) {
val events = zone.VehicleEvents
state match {
case DriveState.Deployed =>
events ! VehicleServiceMessage.AMSDeploymentChange(zone)
events ! VehicleServiceMessage(driverChannel, VehicleAction.PlanetsideAttribute(GUID0, guid, 81, 1))
case _ => ;
}
}
//ant
else if (vehicle.Definition == GlobalDefinitions.ant) {
state match {
case DriveState.Deployed =>
// Start ntu regeneration
// If vehicle sends UseItemMessage with silo as target NTU regeneration will be disabled and orb particles will be disabled
context.system.scheduler.scheduleOnce(
delay = 1000 milliseconds,
vehicle.Actor,
TransferBehavior.Charging(Ntu.Nanites)
)
case _ => ;
}
}
//router
else if (vehicle.Definition == GlobalDefinitions.router) {
val events = zone.LocalEvents
state match {
case DriveState.Deploying =>
vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
case Some(util: Utility.InternalTelepad) => util.Actor ! TelepadLike.Activate(util)
case _ => ;
}
case _ => ;
}
}
case _ => ;
}
out
}
override def UndeploymentAction(
obj: DeploymentObject,
state: DriveState.Value,
prevState: DriveState.Value
): DriveState.Value = {
val out = if (decaying) state else super.UndeploymentAction(obj, state, prevState)
obj match {
case vehicle: Vehicle =>
val guid = vehicle.GUID
val zone = vehicle.Zone
val GUID0 = Service.defaultPlayerGUID
val driverChannel = vehicle.Seats(0).occupant match {
case Some(tplayer) => tplayer.Name
case None => ""
}
Vehicles.ReloadAccessPermissions(vehicle, vehicle.Faction.toString)
//ams
if (vehicle.Definition == GlobalDefinitions.ams) {
val events = zone.VehicleEvents
state match {
case DriveState.Undeploying =>
events ! VehicleServiceMessage.AMSDeploymentChange(zone)
events ! VehicleServiceMessage(driverChannel, VehicleAction.PlanetsideAttribute(GUID0, guid, 81, 0))
case _ => ;
}
}
//ant
else if (vehicle.Definition == GlobalDefinitions.ant) {
state match {
case DriveState.Undeploying =>
TryStopChargingEvent(vehicle)
case _ => ;
}
}
//router
else if (vehicle.Definition == GlobalDefinitions.router) {
state match {
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) => util.Actor ! TelepadLike.Deactivate(util)
case _ => ;
}
case _ => ;
}
}
case _ => ;
}
out
}
/** /**
* Water causes vehicles to become disabled if they dive off too far, too deep. * Water causes vehicles to become disabled if they dive off too far, too deep.
* Flying vehicles do not display progress towards being waterlogged. They just disable outright. * Flying vehicles do not display progress towards being waterlogged. They just disable outright.
@ -895,8 +746,4 @@ object VehicleControl {
case _ => false case _ => false
} }
} }
def DeploymentAngleCheck(obj: Deployment.DeploymentObject): Boolean = {
obj.Orientation.x <= 30 || obj.Orientation.x >= 330
}
} }

View file

@ -1,11 +1,9 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.zones package net.psforever.objects.zones
import akka.actor.{Actor, Props} import akka.actor.Actor
import net.psforever.actors.zone.ZoneActor import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.{Default, Vehicle} import net.psforever.objects.{Default, Vehicle}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.vehicles.VehicleControl
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
@ -42,8 +40,7 @@ class ZoneVehicleActor(zone: Zone, vehicleList: ListBuffer[Vehicle]) extends Act
} else { } else {
vehicleList += vehicle vehicleList += vehicle
vehicle.Zone = zone vehicle.Zone = zone
vehicle.Actor = vehicle.Definition.Initialize(vehicle, context)
context.actorOf(Props(classOf[VehicleControl], vehicle), PlanetSideServerObject.UniqueActorName(vehicle))
} }
if (vehicle.MountedIn.isEmpty) { if (vehicle.MountedIn.isEmpty) {
zone.actor ! ZoneActor.AddToBlockMap(vehicle, vehicle.Position) zone.actor ! ZoneActor.AddToBlockMap(vehicle, vehicle.Position)
@ -54,8 +51,7 @@ class ZoneVehicleActor(zone: Zone, vehicleList: ListBuffer[Vehicle]) extends Act
ZoneVehicleActor.recursiveFindVehicle(vehicleList.iterator, vehicle) match { ZoneVehicleActor.recursiveFindVehicle(vehicleList.iterator, vehicle) match {
case Some(index) => case Some(index) =>
vehicleList.remove(index) vehicleList.remove(index)
context.stop(vehicle.Actor) vehicle.Definition.Uninitialize(vehicle, context)
vehicle.Actor = Default.Actor
zone.actor ! ZoneActor.RemoveFromBlockMap(vehicle) zone.actor ! ZoneActor.RemoveFromBlockMap(vehicle)
sender() ! Zone.Vehicle.HasDespawned(zone, vehicle) sender() ! Zone.Vehicle.HasDespawned(zone, vehicle)
case None => ; case None => ;

View file

@ -145,7 +145,7 @@ import scodec.codecs._
* `45 - NTU charge bar 0-10, 5 = 50% full. Seems to apply to both ANT and NTU Silo (possibly siphons?)`<br> * `45 - NTU charge bar 0-10, 5 = 50% full. Seems to apply to both ANT and NTU Silo (possibly siphons?)`<br>
* `46 - Sends "Generator damage is at a critical level!" message` * `46 - Sends "Generator damage is at a critical level!" message`
* `47 - Sets base NTU level to CRITICAL.`<br> * `47 - Sets base NTU level to CRITICAL.`<br>
* `48 - Set to 1 to send base power loss message & turns on red warning lights throughout base.<br> * `48 - Set to 1 to send base power loss message & turns on red warning lights throughout base.`<br>
* `49 - Vehicle texture effects state? (>0 turns on ANT panel glow or ntu silo panel glow + orbs) (bit?)`<br> * `49 - Vehicle texture effects state? (>0 turns on ANT panel glow or ntu silo panel glow + orbs) (bit?)`<br>
* `52 - Vehicle particle effects? (>0 turns on orbs going towards ANT. Doesn't affect silo) (bit?)`<br> * `52 - Vehicle particle effects? (>0 turns on orbs going towards ANT. Doesn't affect silo) (bit?)`<br>
* `53 - LFS. Value is 1 to flag LFS`<br> * `53 - LFS. Value is 1 to flag LFS`<br>
@ -191,6 +191,7 @@ import scodec.codecs._
* `21 - Declare a player the vehicle's owner, by globally unique identifier`<br> * `21 - Declare a player the vehicle's owner, by globally unique identifier`<br>
* `22 - Toggles gunner and passenger mount points (1 = hides, 0 = reveals; this also locks their permissions)`<br> * `22 - Toggles gunner and passenger mount points (1 = hides, 0 = reveals; this also locks their permissions)`<br>
* `54 - Plays jammed buzzing sound in vicinity of target`<br> * `54 - Plays jammed buzzing sound in vicinity of target`<br>
* `55 - Trigger APC EMP`<br>
* `68 - Vehicle shield health`<br> * `68 - Vehicle shield health`<br>
* `79 - ???`<br> * `79 - ???`<br>
* `80 - Damage vehicle (unknown value)`<br> * `80 - Damage vehicle (unknown value)`<br>

View file

@ -3,7 +3,8 @@ package objects
import akka.actor.Props import akka.actor.Props
import akka.testkit.TestProbe import akka.testkit.TestProbe
import base.ActorTest import base.{ActorTest, FreedContextActorTest}
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.ballistics._ import net.psforever.objects.ballistics._
import net.psforever.objects.equipment.JammableUnit import net.psforever.objects.equipment.JammableUnit
@ -15,7 +16,7 @@ import net.psforever.objects.serverobject.structures.{Building, StructureType}
import net.psforever.objects.serverobject.terminals.{Terminal, TerminalControl, TerminalDefinition} import net.psforever.objects.serverobject.terminals.{Terminal, TerminalControl, TerminalDefinition}
import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.serverobject.turret.{FacilityTurret, FacilityTurretControl, TurretUpgrade} import net.psforever.objects.serverobject.turret.{FacilityTurret, FacilityTurretControl, TurretUpgrade}
import net.psforever.objects.vehicles.VehicleControl import net.psforever.objects.vehicles.control.VehicleControl
import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.Vitality
import net.psforever.objects.zones.{Zone, ZoneMap} import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.packet.game.DamageWithPositionMessage import net.psforever.packet.game.DamageWithPositionMessage
@ -1222,7 +1223,7 @@ class DamageableVehicleDamageTest extends ActorTest {
} }
} }
class DamageableVehicleDamageMountedTest extends ActorTest { class DamageableVehicleDamageMountedTest extends FreedContextActorTest {
val guid = new NumberPoolHub(new MaxNumberSource(15)) val guid = new NumberPoolHub(new MaxNumberSource(15))
val zone = new Zone("test", new ZoneMap("test"), 0) { val zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {} override def SetupNumberPools() = {}
@ -1234,6 +1235,8 @@ class DamageableVehicleDamageMountedTest extends ActorTest {
zone.Activity = activityProbe.ref zone.Activity = activityProbe.ref
zone.AvatarEvents = avatarProbe.ref zone.AvatarEvents = avatarProbe.ref
zone.VehicleEvents = vehicleProbe.ref zone.VehicleEvents = vehicleProbe.ref
import akka.actor.typed.scaladsl.adapter._
zone.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
val lodestar = Vehicle(GlobalDefinitions.lodestar) //guid=1 & 4,5,6,7,8,9 val lodestar = Vehicle(GlobalDefinitions.lodestar) //guid=1 & 4,5,6,7,8,9
lodestar.Position = Vector3(1, 0, 0) lodestar.Position = Vector3(1, 0, 0)
@ -1271,7 +1274,7 @@ class DamageableVehicleDamageMountedTest extends ActorTest {
guid.register(atv, 11) guid.register(atv, 11)
//the lodestar control actor needs to load after the utilities have guid's assigned //the lodestar control actor needs to load after the utilities have guid's assigned
lodestar.Actor = system.actorOf(Props(classOf[VehicleControl], lodestar), "lodestar-control") lodestar.Definition.Initialize(lodestar, context)
lodestar.Zone = zone lodestar.Zone = zone
lodestar.Seats(0).mount(player2) lodestar.Seats(0).mount(player2)
player2.VehicleSeated = lodestar.GUID player2.VehicleSeated = lodestar.GUID
@ -1314,50 +1317,38 @@ class DamageableVehicleDamageMountedTest extends ActorTest {
assert(atv.Shields == 1) assert(atv.Shields == 1)
lodestar.Actor ! Vitality.Damage(applyDamageTo) lodestar.Actor ! Vitality.Damage(applyDamageTo)
val msg12 = vehicleProbe.receiveN(2, 200 milliseconds) val msg12 = vehicleProbe.receiveN(2, 500 milliseconds)
val msg3 = activityProbe.receiveOne(200 milliseconds) val msg3 = activityProbe.receiveOne(500 milliseconds)
val msg45 = avatarProbe.receiveN(2,200 milliseconds) val msg45 = avatarProbe.receiveN(2,500 milliseconds)
assert( msg12.head match {
msg12.head match { case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 68, _)) => ;
case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 68, _)) => true case _ => assert(false)
case _ => false }
} msg12(1) match {
) case VehicleServiceMessage("test", VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 0, _)) => ;
assert( case _ => assert(false)
msg12(1) match { }
case VehicleServiceMessage("test", VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 0, _)) => true msg3 match {
case _ => false case activity: Zone.HotSpot.Activity =>
} assert(activity.attacker == pSource &&
) activity.defender == SourceEntry(lodestar) &&
assert( activity.location == Vector3(1, 0, 0))
msg3 match { case _ => assert(false)
case activity: Zone.HotSpot.Activity => }
activity.attacker == pSource && msg45.head match {
activity.defender == SourceEntry(lodestar) && case AvatarServiceMessage(
activity.location == Vector3(1, 0, 0) "TestCharacter2",
case _ => false AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(400, Vector3(2, 0, 0)))
} ) => ;
) case _ => assert(false)
assert( }
msg45.head match { msg45(1) match {
case AvatarServiceMessage( case AvatarServiceMessage(
"TestCharacter2", "TestCharacter3",
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(400, Vector3(2, 0, 0))) AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(0, Vector3(2, 0, 0)))
) => ) => ;
true case _ => assert(false)
case _ => false }
}
)
assert(
msg45(1) match {
case AvatarServiceMessage(
"TestCharacter3",
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(0, Vector3(2, 0, 0)))
) =>
true
case _ => false
}
)
assert(lodestar.Health < lodestar.Definition.DefaultHealth) assert(lodestar.Health < lodestar.Definition.DefaultHealth)
assert(lodestar.Shields == 0) assert(lodestar.Shields == 0)
assert(atv.Health == atv.Definition.DefaultHealth) assert(atv.Health == atv.Definition.DefaultHealth)
@ -1365,7 +1356,7 @@ class DamageableVehicleDamageMountedTest extends ActorTest {
} }
} }
class DamageableVehicleJammeringMountedTest extends ActorTest { class DamageableVehicleJammeringMountedTest extends FreedContextActorTest {
val guid = new NumberPoolHub(new MaxNumberSource(15)) val guid = new NumberPoolHub(new MaxNumberSource(15))
val zone = new Zone("test", new ZoneMap("test"), 0) { val zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {} override def SetupNumberPools() = {}
@ -1417,7 +1408,7 @@ class DamageableVehicleJammeringMountedTest extends ActorTest {
guid.register(player2, 12) guid.register(player2, 12)
guid.register(player3, 13) guid.register(player3, 13)
lodestar.Actor = system.actorOf(Props(classOf[VehicleControl], lodestar), "lodestar-control") lodestar.Definition.Initialize(lodestar, context)
atv.Zone = zone atv.Zone = zone
lodestar.Zone = zone lodestar.Zone = zone
atv.Seats(0).mount(player2) atv.Seats(0).mount(player2)
@ -1585,7 +1576,7 @@ class DamageableVehicleDestroyTest extends ActorTest {
} }
} }
class DamageableVehicleDestroyMountedTest extends ActorTest { class DamageableVehicleDestroyMountedTest extends FreedContextActorTest {
val atv = Vehicle(GlobalDefinitions.quadassault) //guid=1 val atv = Vehicle(GlobalDefinitions.quadassault) //guid=1
atv.Actor = system.actorOf(Props(classOf[VehicleControl], atv), "atv-control") atv.Actor = system.actorOf(Props(classOf[VehicleControl], atv), "atv-control")
atv.Position = Vector3(1, 0, 0) atv.Position = Vector3(1, 0, 0)
@ -1611,19 +1602,22 @@ class DamageableVehicleDestroyMountedTest extends ActorTest {
val player3Probe = TestProbe() val player3Probe = TestProbe()
player3.Actor = player3Probe.ref player3.Actor = player3Probe.ref
val activityProbe = TestProbe()
val avatarProbe = TestProbe()
val vehicleProbe = TestProbe()
val catchall = TestProbe()
val guid = new NumberPoolHub(new MaxNumberSource(15)) val guid = new NumberPoolHub(new MaxNumberSource(15))
val zone = new Zone("test", new ZoneMap("test"), 0) { val zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {} override def SetupNumberPools() = {}
GUID(guid) GUID(guid)
override def LivePlayers = List(player1, player2, player3) override def LivePlayers = List(player1, player2, player3)
override def Activity = activityProbe.ref
override def AvatarEvents = avatarProbe.ref
override def VehicleEvents = vehicleProbe.ref
override def tasks = catchall.ref
import akka.actor.typed.scaladsl.adapter._
this.actor = catchall.ref.toTyped[ZoneActor.Command]
} }
val activityProbe = TestProbe()
val avatarProbe = TestProbe()
val vehicleProbe = TestProbe()
zone.Activity = activityProbe.ref
zone.AvatarEvents = avatarProbe.ref
zone.VehicleEvents = vehicleProbe.ref
guid.register(atv, 1) guid.register(atv, 1)
guid.register(atvWeapon, 2) guid.register(atvWeapon, 2)
@ -1639,7 +1633,7 @@ class DamageableVehicleDestroyMountedTest extends ActorTest {
guid.register(player2, 12) guid.register(player2, 12)
guid.register(player3, 13) guid.register(player3, 13)
lodestar.Actor = system.actorOf(Props(classOf[VehicleControl], lodestar), "lodestar-control") lodestar.Definition.Initialize(lodestar, context)
atv.Zone = zone atv.Zone = zone
lodestar.Zone = zone lodestar.Zone = zone
atv.Seats(0).mount(player2) atv.Seats(0).mount(player2)

View file

@ -13,7 +13,7 @@ import net.psforever.objects.serverobject.generator.{Generator, GeneratorControl
import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.serverobject.structures.{Building, StructureType}
import net.psforever.objects.serverobject.terminals.{Terminal, TerminalControl} import net.psforever.objects.serverobject.terminals.{Terminal, TerminalControl}
import net.psforever.objects.serverobject.turret.{FacilityTurret, FacilityTurretControl} import net.psforever.objects.serverobject.turret.{FacilityTurret, FacilityTurretControl}
import net.psforever.objects.vehicles.VehicleControl import net.psforever.objects.vehicles.control.VehicleControl
import net.psforever.objects.zones.{Zone, ZoneMap} import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.packet.game.{InventoryStateMessage, RepairMessage} import net.psforever.packet.game.{InventoryStateMessage, RepairMessage}
import net.psforever.types._ import net.psforever.types._

View file

@ -4,14 +4,14 @@ package objects
import akka.actor.{ActorRef, Props} import akka.actor.{ActorRef, Props}
import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.scaladsl.adapter._
import akka.testkit.TestProbe import akka.testkit.TestProbe
import base.ActorTest import base.{ActorTest, FreedContextActorTest}
import net.psforever.actors.zone.ZoneActor import net.psforever.actors.zone.ZoneActor
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.ce.{DeployableCategory, DeployedItem, TelepadLike} import net.psforever.objects.ce.{DeployableCategory, DeployedItem, TelepadLike}
import net.psforever.objects.guid.NumberPoolHub import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.serverobject.deploy.Deployment import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.vehicles.{Utility, UtilityType, VehicleControl} import net.psforever.objects.vehicles.{Utility, UtilityType}
import net.psforever.objects.zones.{Zone, ZoneDeployableActor, ZoneMap} import net.psforever.objects.zones.{Zone, ZoneDeployableActor, ZoneMap}
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
import net.psforever.packet.game._ import net.psforever.packet.game._
@ -214,7 +214,7 @@ class TelepadDeployableAttemptTest extends ActorTest {
} }
} }
class TelepadDeployableResponseFromRouterTest extends ActorTest { class TelepadDeployableResponseFromRouterTest extends FreedContextActorTest {
val eventsProbe = new TestProbe(system) val eventsProbe = new TestProbe(system)
val telepad = new TelepadDeployable(TelepadRouterTest.router_telepad_deployable) //guid=1 val telepad = new TelepadDeployable(TelepadRouterTest.router_telepad_deployable) //guid=1
val router = Vehicle(GlobalDefinitions.router) //guid=2 val router = Vehicle(GlobalDefinitions.router) //guid=2
@ -242,17 +242,20 @@ class TelepadDeployableResponseFromRouterTest extends ActorTest {
guid.register(internal, number = 3) guid.register(internal, number = 3)
guid.register(router.Utility(UtilityType.teleportpad_terminal).get, number = 4) //necessary guid.register(router.Utility(UtilityType.teleportpad_terminal).get, number = 4) //necessary
router.Zone = zone router.Zone = zone
router.Actor = system.actorOf(Props(classOf[VehicleControl], router), "test-router") router.Definition.Initialize(router, context)
telepad.Router = PlanetSideGUID(2) //artificial telepad.Router = PlanetSideGUID(2) //artificial
"TelepadDeployable" should { "TelepadDeployable" should {
"link with a connected router" in { "link with a connected router" in {
assert(!telepad.Active, "link to router test - telepad active earlier than intended (1)") assert(!telepad.Active, "link to router test - telepad active earlier than intended (1)")
assert(!internal.Active, "link to router test - router internals active earlier than intended") assert(!internal.Active, "link to router test - router internals active earlier than intended")
router.Actor.tell(Deployment.TryDeploy(DriveState.Deploying), new TestProbe(system).ref) val deploymentProbe = new TestProbe(system)
router.Actor.tell(Deployment.TryDeploy(DriveState.Deploying), deploymentProbe.ref)
eventsProbe.receiveN(10, 10.seconds) //flush all messages related to deployment eventsProbe.receiveN(10, 10.seconds) //flush all messages related to deployment
deploymentProbe.receiveOne(2.seconds) //CanDeploy
deploymentProbe.expectNoMessage(2.seconds) //intentional delay
assert(internal.Active, "link to router test - router internals not active when expected")
assert(!telepad.Active, "link to router test - telepad active earlier than intended (2)") assert(!telepad.Active, "link to router test - telepad active earlier than intended (2)")
assert(internal.Active, "link to router test - router internals active not active when expected")
assert(deployableList.isEmpty, "link to router test - deployable list is not empty") assert(deployableList.isEmpty, "link to router test - deployable list is not empty")
zone.Deployables ! Zone.Deployable.Build(telepad) zone.Deployables ! Zone.Deployable.Build(telepad)

View file

@ -7,15 +7,17 @@ import akka.testkit.TestProbe
import base.{ActorTest, FreedContextActorTest} import base.{ActorTest, FreedContextActorTest}
import net.psforever.actors.zone.ZoneActor import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.avatar.{Avatar, PlayerControl} import net.psforever.objects.avatar.{Avatar, PlayerControl}
import net.psforever.objects.{GlobalDefinitions, Player, Vehicle} import net.psforever.objects.ce.DeployedItem
import net.psforever.objects._
import net.psforever.objects.guid.NumberPoolHub import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.serverobject.environment._ import net.psforever.objects.serverobject.environment._
import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.vehicles.{VehicleControl, VehicleLockState} import net.psforever.objects.vehicles.VehicleLockState
import net.psforever.objects.vital.VehicleShieldCharge import net.psforever.objects.vehicles.control.VehicleControl
import net.psforever.objects.vital.{VehicleShieldCharge, Vitality}
import net.psforever.objects.zones.{Zone, ZoneMap} import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.packet.game.{CargoMountPointStatusMessage, ObjectDetachMessage, PlanetsideAttributeMessage} import net.psforever.packet.game._
import net.psforever.services.ServiceManager import net.psforever.services.ServiceManager
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
@ -201,12 +203,16 @@ class VehicleControlPrepareForDeletionMountedInTest extends FreedContextActorTes
} }
class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActorTest { class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActorTest {
val vehicleProbe = new TestProbe(system)
val catchall = new TestProbe(system)
val guid = new NumberPoolHub(new MaxNumberSource(10)) val guid = new NumberPoolHub(new MaxNumberSource(10))
ServiceManager.boot ServiceManager.boot
val zone = new Zone("test", new ZoneMap("test"), 0) { val zone = new Zone("test", new ZoneMap("test"), 0) {
GUID(guid) GUID(guid)
override def SetupNumberPools(): Unit = {} override def SetupNumberPools(): Unit = {}
override def VehicleEvents = vehicleProbe.ref
override def tasks = catchall.ref
} }
zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor") zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor")
// crappy workaround but without it the zone doesn't get initialized in time // crappy workaround but without it the zone doesn't get initialized in time
@ -219,6 +225,7 @@ class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActor
vehicle.Actor = cargoProbe.ref vehicle.Actor = cargoProbe.ref
val lodestar = Vehicle(GlobalDefinitions.lodestar) val lodestar = Vehicle(GlobalDefinitions.lodestar)
lodestar.Faction = PlanetSideEmpire.TR lodestar.Faction = PlanetSideEmpire.TR
lodestar.Zone = zone
val player1 = Player(VehicleTest.avatar1) //name="test1" val player1 = Player(VehicleTest.avatar1) //name="test1"
val player2 = Player(VehicleTest.avatar2) //name="test2" val player2 = Player(VehicleTest.avatar2) //name="test2"
@ -237,85 +244,64 @@ class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActor
player2.VehicleSeated = lodestar.GUID player2.VehicleSeated = lodestar.GUID
lodestar.CargoHolds(1).mount(vehicle) lodestar.CargoHolds(1).mount(vehicle)
vehicle.MountedIn = lodestar.GUID vehicle.MountedIn = lodestar.GUID
lodestar.Definition.Initialize(lodestar, context)
val vehicleProbe = new TestProbe(system)
zone.VehicleEvents = vehicleProbe.ref
zone.Transport ! Zone.Vehicle.Spawn(lodestar) //can not fake this
"VehicleControl" should { "VehicleControl" should {
"if with mounted cargo, eject it when marked for deconstruction" in { "if with mounted cargo, eject it when marked for deconstruction" in {
lodestar.Actor ! Vehicle.Deconstruct() lodestar.Actor ! Vehicle.Deconstruct()
val vehicle_msg = vehicleProbe.receiveN(6, 500 milliseconds) val vehicle_msg = vehicleProbe.receiveN(6, 500 milliseconds)
assert( vehicle_msg(5) match {
vehicle_msg.head match { case VehicleServiceMessage(
case VehicleServiceMessage(
"test", "test",
VehicleAction.KickPassenger(PlanetSideGUID(4), 4, false, PlanetSideGUID(2)) VehicleAction.KickPassenger(PlanetSideGUID(4), 4, false, PlanetSideGUID(2))
) => ) => ;
true case _ => assert(false)
case _ => false }
}
)
assert(player2.VehicleSeated.isEmpty) assert(player2.VehicleSeated.isEmpty)
assert(lodestar.Seats(0).occupant.isEmpty) assert(lodestar.Seats(0).occupant.isEmpty)
//cargo dismounting messages //cargo dismounting messages
assert( vehicle_msg.head match {
vehicle_msg(1) match { case VehicleServiceMessage(
case VehicleServiceMessage(
_, _,
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _)) VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))
) => ) => ;
true case _ => assert(false)
case _ => false }
} vehicle_msg(1) match {
) case VehicleServiceMessage(
assert(
vehicle_msg(2) match {
case VehicleServiceMessage(
_, _,
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _)) VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))
) => ) => ;
true case _ => assert(false)
case _ => false }
} vehicle_msg(2) match {
) case VehicleServiceMessage(
assert(
vehicle_msg(3) match {
case VehicleServiceMessage(
"test", "test",
VehicleAction.SendResponse( VehicleAction.SendResponse(
_, _,
CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0) CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0)
) )
) => ) => ;
true case _ => assert(false)
case _ => false }
} vehicle_msg(3) match {
) case VehicleServiceMessage(
assert(
vehicle_msg(4) match {
case VehicleServiceMessage(
"test", "test",
VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _)) VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))
) => ) => ;
true case _ => assert(false)
case _ => false }
} vehicle_msg(4) match {
) case VehicleServiceMessage(
assert(
vehicle_msg(5) match {
case VehicleServiceMessage(
"test", "test",
VehicleAction.SendResponse( VehicleAction.SendResponse(
_, _,
CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0) CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0)
) )
) => ) => ;
true case _ => assert(false)
case _ => false }
}
)
} }
} }
} }
@ -945,6 +931,142 @@ class VehicleControlInteractWithDeathTest extends ActorTest {
} }
} }
class ApcControlCanChargeCapacitor extends FreedContextActorTest {
val apc = Vehicle(GlobalDefinitions.apc_tr) //guid=1, weapons not registered
val guid = new NumberPoolHub(new MaxNumberSource(max = 5))
val localProbe = TestProbe()
val vehicleProbe = TestProbe()
val catchall = TestProbe()
val zone = new Zone(id = "test-zone", new ZoneMap(name = "test-map"), zoneNumber = 0) {
override def SetupNumberPools(): Unit = {}
GUID(guid)
override def Vehicles = List(apc)
override def VehicleEvents = vehicleProbe.ref
override def LocalEvents = localProbe.ref
override def AvatarEvents = catchall.ref
override def Activity = catchall.ref
}
guid.register(apc, number = 1)
apc.Faction = PlanetSideEmpire.VS
apc.Zone = zone
//apc.Definition.Initialize(apc, context) //do later ...
zone.blockMap.addTo(apc)
val maxCapacitor = apc.Definition.MaxCapacitor
"ApcControl" should {
"charge its capacitors when initialized" in {
assert(apc.Capacitor == 0)
apc.Definition.Initialize(apc, context)
do {
val msg = vehicleProbe.receiveOne(3.seconds)
msg match {
case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(_, PlanetSideGUID(1), 113, capacitance)) =>
assert(capacitance > 0)
case _ =>
assert(false)
}
}
while(apc.Capacitor < maxCapacitor)
vehicleProbe.expectNoMessage(5.seconds)
assert(apc.Capacitor == maxCapacitor)
}
}
}
class ApcControlCanEmp extends FreedContextActorTest {
val apc = Vehicle(GlobalDefinitions.apc_vs) //guid=1, weapons not registered
val fury = Vehicle(GlobalDefinitions.fury) //guid=2, weapons not registered
val boomer = Deployables.Make(DeployedItem.boomer)() //guid=3, no trigger
val boomer2 = Deployables.Make(DeployedItem.boomer)() //guid=4, no trigger
val guid = new NumberPoolHub(new MaxNumberSource(max = 5))
val localProbe = TestProbe()
val vehicleProbe = TestProbe()
val catchall = TestProbe()
val zone = new Zone(id = "test-zone", new ZoneMap(name = "test-map"), zoneNumber = 0) {
override def SetupNumberPools(): Unit = {}
GUID(guid)
override def Vehicles = List(apc, fury)
override def DeployableList = List(boomer, boomer2)
override def VehicleEvents = vehicleProbe.ref
override def LocalEvents = localProbe.ref
override def AvatarEvents = catchall.ref
override def Activity = catchall.ref
}
guid.register(apc, number = 1)
apc.Faction = PlanetSideEmpire.VS
apc.Zone = zone
apc.Capacitor = apc.Definition.MaxCapacitor
apc.Definition.Initialize(apc, context)
zone.blockMap.addTo(apc)
val furyProbe = TestProbe()
guid.register(fury, number = 2)
fury.Position = Vector3(4, 0, 0) //within 15m of apc
fury.Faction = PlanetSideEmpire.TR
fury.Zone = zone
fury.Actor = furyProbe.ref
zone.blockMap.addTo(fury)
val boomerProbe = TestProbe()
guid.register(boomer, number = 3)
boomer.Position = Vector3(0, 14, 0) //within 15m of apc
boomer.Faction = PlanetSideEmpire.TR
boomer.Zone = zone
boomer.Actor = boomerProbe.ref
zone.blockMap.addTo(boomer)
val boomer2Probe = TestProbe()
guid.register(boomer2, number = 4)
boomer2.Position = Vector3(0, 30, 0) //beyond 15m of apc
boomer2.Faction = PlanetSideEmpire.TR
boomer2.Zone = zone
boomer2.Actor = boomer2Probe.ref
zone.blockMap.addTo(boomer2)
"ApcControl" should {
"charge its capacitors when initialized" in {
assert(apc.Capacitor == apc.Definition.MaxCapacitor)
apc.Definition.Initialize(apc, context)
vehicleProbe.expectNoMessage(5.seconds) //the capacitor is max, so no charging is needed
apc.Actor ! SpecialEmp.Burst()
val vehicleMsgs = vehicleProbe.receiveN(2, 500.milliseconds)
vehicleMsgs.head match {
case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(_, PlanetSideGUID(1), 113, 0)) => ;
case _ => assert(false)
}
vehicleMsgs(1) match {
case VehicleServiceMessage(
"test-zone",
VehicleAction.SendResponse(
_,
TriggerEffectMessage(_, "apc_explosion_emp_vs", None, Some(TriggeredEffectLocation(Vector3.Zero, Vector3.Zero)))
)
) => ;
case _ => assert(false)
}
assert(apc.Capacitor == 0)
val furyMsg = furyProbe.receiveOne(200.milliseconds)
furyMsg match {
case Vitality.Damage(_) => ;
case _ => assert(false)
}
val boomerMsg = boomerProbe.receiveOne(200.milliseconds)
boomerMsg match {
case Vitality.Damage(_) => ;
case _ => assert(false)
}
boomer2Probe.expectNoMessage(400.milliseconds) //out of range
}
}
}
object VehicleControlTest { object VehicleControlTest {
import net.psforever.objects.avatar.Avatar import net.psforever.objects.avatar.Avatar
import net.psforever.types.{CharacterSex, PlanetSideEmpire} import net.psforever.types.{CharacterSex, PlanetSideEmpire}

View file

@ -7,7 +7,7 @@ import base.{ActorTest, FreedContextActorTest}
import net.psforever.objects.{GlobalDefinitions, SensorDeployable, Vehicle} import net.psforever.objects.{GlobalDefinitions, SensorDeployable, Vehicle}
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.terminals.{ProximityTerminal, Terminal} import net.psforever.objects.serverobject.terminals.{ProximityTerminal, Terminal}
import net.psforever.objects.vehicles.VehicleControl import net.psforever.objects.vehicles.control.VehicleControl
import net.psforever.objects.zones.{Zone, ZoneMap} import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.packet.game._ import net.psforever.packet.game._
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3} import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}

View file

@ -4,7 +4,7 @@ package service
import akka.actor.Props import akka.actor.Props
import base.ActorTest import base.ActorTest
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.vehicles.VehicleControl import net.psforever.objects.vehicles.control.VehicleControl
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.types.{PlanetSideGUID, _} import net.psforever.types.{PlanetSideGUID, _}
import net.psforever.services.{Service, ServiceManager} import net.psforever.services.{Service, ServiceManager}