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.structures.{AutoRepairStats, Building, StructureType}
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.base.DamageResolution
import net.psforever.objects.vital.damage.DamageProfile
@ -201,7 +200,7 @@ class AutoRepairFacilityIntegrationAntGiveNtuTest extends FreedContextActorTest
val maxNtuCap = ant.Definition.MaxNtuCapacitor
player.Spawn()
ant.NtuCapacitor = maxNtuCap
ant.Actor = context.actorOf(Props(classOf[VehicleControl], ant), name = "test-ant")
ant.Definition.Initialize(ant, context)
ant.Zone = zone
ant.Seats(0).mount(player)
ant.DeploymentState = DriveState.Deployed
@ -295,7 +294,7 @@ class AutoRepairFacilityIntegrationTerminalDestroyedTerminalAntTest extends Free
val maxNtuCap = ant.Definition.MaxNtuCapacitor
player.Spawn()
ant.NtuCapacitor = maxNtuCap
ant.Actor = context.actorOf(Props(classOf[VehicleControl], ant), name = "test-ant")
ant.Definition.Initialize(ant, context)
ant.Zone = zone
ant.Seats(0).mount(player)
ant.DeploymentState = DriveState.Deployed
@ -397,7 +396,7 @@ class AutoRepairFacilityIntegrationTerminalIncompleteRepairTest extends FreedCon
val maxNtuCap = ant.Definition.MaxNtuCapacitor
player.Spawn()
ant.NtuCapacitor = maxNtuCap
ant.Actor = context.actorOf(Props(classOf[VehicleControl], ant), name = "test-ant")
ant.Definition.Initialize(ant, context)
ant.Zone = zone
ant.Seats(0).mount(player)
ant.DeploymentState = DriveState.Deployed

View file

@ -215,7 +215,7 @@ object VehicleSpawnPadControlTest {
import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource
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.types.CharacterSex

View file

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

View file

@ -244,7 +244,7 @@ object ExplosiveDeployableControl {
//val scalar = Vector3.ScalarProjection(dir, up)
val point1 = g1.pointOnOutside(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) &&
math.min(
Vector3.DistanceSquared(g1.center.asVector3, g2.center.asVector3),

View file

@ -897,11 +897,11 @@ object GlobalDefinitions {
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)
@ -911,15 +911,15 @@ object GlobalDefinitions {
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)
@ -931,11 +931,11 @@ object GlobalDefinitions {
val vulture = VehicleDefinition(ObjectClass.vulture)
val dropship = VehicleDefinition(ObjectClass.dropship)
val dropship = VehicleDefinition.Carrier(ObjectClass.dropship)
val galaxy_gunship = VehicleDefinition(ObjectClass.galaxy_gunship)
val lodestar = VehicleDefinition(ObjectClass.lodestar)
val lodestar = VehicleDefinition.Carrier(ObjectClass.lodestar)
val phantasm = VehicleDefinition(ObjectClass.phantasm)
@ -6146,6 +6146,7 @@ object GlobalDefinitions {
apc_tr.MaxDepth = 3
apc_tr.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
apc_tr.Geometry = apcForm
apc_tr.MaxCapacitor = 300
apc_nc.Name = "apc_nc" // Vindicator
apc_nc.MaxHealth = 6000
@ -6208,6 +6209,7 @@ object GlobalDefinitions {
apc_nc.MaxDepth = 3
apc_nc.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
apc_nc.Geometry = apcForm
apc_nc.MaxCapacitor = 300
apc_vs.Name = "apc_vs" // Leviathan
apc_vs.MaxHealth = 6000
@ -6270,6 +6272,7 @@ object GlobalDefinitions {
apc_vs.MaxDepth = 3
apc_vs.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
apc_vs.Geometry = apcForm
apc_vs.MaxCapacitor = 300
lightning.Name = "lightning"
lightning.MaxHealth = 2000

View file

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

View file

@ -1,9 +1,11 @@
// Copyright (c) 2017 PSForever
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.inventory.InventoryTile
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.vehicles.{DestroyedVehicle, MountableWeaponsDefinition, UtilityType}
import net.psforever.objects.vital._
import net.psforever.objects.vital.damage.DamageCalculations
@ -181,6 +183,19 @@ class VehicleDefinition(objectId: Int)
destroyedModel = model
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 {
@ -189,4 +204,94 @@ object VehicleDefinition {
def apply(objectId: Int): VehicleDefinition = {
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 */
private var reportDamageToVehicle: Boolean = false
protected var reportDamageToVehicle: Boolean = false
def DamageableObject: Vehicle
def AggravatedObject : Vehicle = DamageableObject
override val takesDamage: Receive =
originalTakesDamage
.orElse(aggravatedBehavior)
.orElse {
case DamageableVehicle.Damage(cause, damage) =>
//cargo vehicles inherit feedback from carrier
reportDamageToVehicle = damage > 0
DamageAwareness(DamageableObject, cause, amount = 0)
override val takesDamage: Receive = originalTakesDamage
.orElse(aggravatedBehavior)
.orElse {
case DamageableVehicle.Damage(cause, damage) =>
//cargo vehicles inherit feedback from carrier
reportDamageToVehicle = damage > 0
DamageAwareness(DamageableObject, cause, amount = 0)
case DamageableVehicle.Destruction(cause) =>
//cargo vehicles are destroyed when carrier is destroyed
val obj = DamageableObject
obj.Health = 0
obj.History(cause)
DestructionAwareness(obj, cause)
}
case DamageableVehicle.Destruction(cause) =>
//cargo vehicles are destroyed when carrier is destroyed
val obj = DamageableObject
obj.Health = 0
obj.History(cause)
DestructionAwareness(obj, cause)
}
/**
* 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
* 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.
* @see `Service.defaultPlayerGUID`
* @see `Vehicle.CargoHolds`
@ -156,22 +153,10 @@ trait DamageableVehicle
//alert to damage source
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
* so that the vehicle's corpse does not act like it is still protected by vehicle shields.
* Finally, the vehicle is tasked for deconstruction.
@ -196,14 +181,6 @@ trait DamageableVehicle
EndAllAggravation()
//passengers die with us
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
Zone.serverSideDamage(obj.Zone, target, Zone.explosionDamage(Some(cause)))
//special considerations for certain vehicles
@ -224,17 +201,16 @@ trait DamageableVehicle
}
object DamageableVehicle {
/**
* Message for instructing the target's cargo vehicles about a damage source affecting their carrier.
* @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,
* and they should be destroyed too.
* @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 =
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
package net.psforever.objects.vehicles
// Copyright (c) 2017-2021 PSForever
package net.psforever.objects.vehicles.control
import akka.actor.{Actor, Cancellable}
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects._
import net.psforever.objects.ballistics.VehicleSource
import net.psforever.objects.ce.TelepadLike
import net.psforever.objects.entity.WorldEntity
import net.psforever.objects.equipment.{Equipment, EquipmentSlot, JammableMountedWeapons}
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.containable.{Containable, ContainableBehavior}
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.hackable.GenericHackables
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
import net.psforever.objects.serverobject.repair.RepairableVehicle
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.VehicleShieldCharge
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>
* <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.
*
* 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
*/
class VehicleControl(vehicle: Vehicle)
extends Actor
extends Actor
with FactionAffinityBehavior.Check
with DeploymentBehavior
with MountableBehavior
with CargoBehavior
with DamageableVehicle
with RepairableVehicle
with JammableMountedWeapons
with ContainableBehavior
with AntTransferBehavior
with AggravatedBehavior
with RespondsToZoneEnvironment {
//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 CargoObject = vehicle
def JammableObject = vehicle
def FactionObject = vehicle
def DeploymentObject = vehicle
def DamageableObject = vehicle
def RepairableObject = vehicle
def ContainerObject = vehicle
def ChargeTransferObject = vehicle
def InteractiveObject = vehicle
SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater)
SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava)
@ -87,10 +75,6 @@ class VehicleControl(vehicle: Vehicle)
SetInteractionStop(EnvironmentAttribute.Water, stopInteractingWithWater)
}
if (vehicle.Definition == GlobalDefinitions.ant) {
findChargeTargetFunc = Vehicles.FindANTChargingSource
findDischargeTargetFunc = Vehicles.FindANTDischargingTarget
}
/** cheap flag for whether the vehicle is decaying */
var decaying : Boolean = false
/** primary vehicle decay timer */
@ -112,199 +96,197 @@ class VehicleControl(vehicle: Vehicle)
recoverFromEnvironmentInteracting()
}
def Enabled : Receive =
checkBehavior
.orElse(deployBehavior)
.orElse(cargoBehavior)
.orElse(jammableBehavior)
.orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser)
.orElse(containerBehavior)
.orElse(antBehavior)
.orElse(environmentBehavior)
.orElse {
case Vehicle.Ownership(None) =>
LoseOwnership()
def commonEnabledBehavior: Receive = checkBehavior
.orElse(jammableBehavior)
.orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser)
.orElse(containerBehavior)
.orElse(environmentBehavior)
.orElse {
case Vehicle.Ownership(None) =>
LoseOwnership()
case Vehicle.Ownership(Some(player)) =>
GainOwnership(player)
case Vehicle.Ownership(Some(player)) =>
GainOwnership(player)
case msg @ Mountable.TryMount(player, mount_point) =>
mountBehavior.apply(msg)
mountCleanup(mount_point, player)
case msg @ Mountable.TryMount(player, mount_point) =>
mountBehavior.apply(msg)
mountCleanup(mount_point, player)
case msg @ Mountable.TryDismount(_, seat_num) =>
dismountBehavior.apply(msg)
dismountCleanup(seat_num)
case msg @ Mountable.TryDismount(_, seat_num) =>
dismountBehavior.apply(msg)
dismountCleanup(seat_num)
case Vehicle.ChargeShields(amount) =>
val now : Long = System.currentTimeMillis()
//make certain vehicle doesn't charge shields too quickly
if (
vehicle.Health > 0 && vehicle.Shields < vehicle.MaxShields &&
!vehicle.History.exists(VehicleControl.LastShieldChargeOrDamage(now))
) {
vehicle.History(VehicleShieldCharge(VehicleSource(vehicle), amount))
vehicle.Shields = vehicle.Shields + amount
vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
s"${vehicle.Actor}",
VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), vehicle.GUID, 68, vehicle.Shields)
)
}
case Vehicle.ChargeShields(amount) =>
val now : Long = System.currentTimeMillis()
//make certain vehicles don't charge shields too quickly
if (
vehicle.Health > 0 && vehicle.Shields < vehicle.MaxShields &&
!vehicle.History.exists(VehicleControl.LastShieldChargeOrDamage(now))
) {
vehicle.History(VehicleShieldCharge(VehicleSource(vehicle), amount))
vehicle.Shields = vehicle.Shields + amount
vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
s"${vehicle.Actor}",
VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), vehicle.GUID, 68, vehicle.Shields)
)
}
case Vehicle.UpdateZoneInteractionProgressUI(player) =>
updateZoneInteractionProgressUI(player)
case Vehicle.UpdateZoneInteractionProgressUI(player) =>
updateZoneInteractionProgressUI(player)
case FactionAffinity.ConvertFactionAffinity(faction) =>
val originalAffinity = vehicle.Faction
if (originalAffinity != (vehicle.Faction = faction)) {
vehicle.Utilities.foreach({
case (_ : Int, util : Utility) => util().Actor forward FactionAffinity.ConfirmFactionAffinity()
})
}
sender() ! FactionAffinity.AssertFactionAffinity(vehicle, faction)
case FactionAffinity.ConvertFactionAffinity(faction) =>
val originalAffinity = vehicle.Faction
if (originalAffinity != (vehicle.Faction = faction)) {
vehicle.Utilities.foreach({
case (_ : Int, util : Utility) => util().Actor forward FactionAffinity.ConfirmFactionAffinity()
})
}
sender() ! FactionAffinity.AssertFactionAffinity(vehicle, faction)
case CommonMessages.Use(player, Some(item : SimpleItem))
if item.Definition == GlobalDefinitions.remote_electronics_kit =>
//TODO setup certifications check
if (vehicle.Faction != player.Faction) {
sender() ! CommonMessages.Progress(
GenericHackables.GetHackSpeed(player, vehicle),
Vehicles.FinishHackingVehicle(vehicle, player, 3212836864L),
GenericHackables.HackingTickAction(progressType = 1, player, vehicle, item.GUID)
)
}
case CommonMessages.Use(player, Some(item : SimpleItem))
if item.Definition == GlobalDefinitions.remote_electronics_kit =>
//TODO setup certifications check
if (vehicle.Faction != player.Faction) {
sender() ! CommonMessages.Progress(
GenericHackables.GetHackSpeed(player, vehicle),
Vehicles.FinishHackingVehicle(vehicle, player, 3212836864L),
GenericHackables.HackingTickAction(progressType = 1, player, vehicle, item.GUID)
)
}
case Terminal.TerminalMessage(player, msg, reply) =>
reply match {
case Terminal.VehicleLoadout(definition, weapons, inventory) =>
org.log4s
.getLogger(vehicle.Definition.Name)
.info(s"changing vehicle equipment loadout to ${player.Name}'s option #${msg.unk1 + 1}")
//remove old inventory
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
val (_, afterInventory) = inventory.partition(ContainableBehavior.DropPredicate(player))
val (oldWeapons, newWeapons, finalInventory) = if (vehicle.Definition == definition) {
//vehicles are the same type
//TODO want to completely swap weapons, but holster icon vanishes temporarily after swap
//TODO BFR arms must be swapped properly
// //remove old weapons
// val oldWeapons = vehicle.Weapons.values.collect { case slot if slot.Equipment.nonEmpty =>
// val obj = slot.Equipment.get
// slot.Equipment = None
// (obj, obj.GUID)
// }.toList
// (oldWeapons, weapons, afterInventory)
//TODO for now, just refill ammo; assume weapons stay the same
vehicle.Weapons
.collect { case (_, slot : EquipmentSlot) if slot.Equipment.nonEmpty => slot.Equipment.get }
.collect {
case weapon : Tool =>
weapon.AmmoSlots.foreach { ammo => ammo.Box.Capacity = ammo.Box.Definition.Capacity }
}
(Nil, Nil, afterInventory)
case Terminal.TerminalMessage(player, msg, reply) =>
reply match {
case Terminal.VehicleLoadout(definition, weapons, inventory) =>
org.log4s
.getLogger(vehicle.Definition.Name)
.info(s"changing vehicle equipment loadout to ${player.Name}'s option #${msg.unk1 + 1}")
//remove old inventory
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
val (_, afterInventory) = inventory.partition(ContainableBehavior.DropPredicate(player))
val (oldWeapons, newWeapons, finalInventory) = if (vehicle.Definition == definition) {
//vehicles are the same type
//TODO want to completely swap weapons, but holster icon vanishes temporarily after swap
//TODO BFR arms must be swapped properly
// //remove old weapons
// val oldWeapons = vehicle.Weapons.values.collect { case slot if slot.Equipment.nonEmpty =>
// val obj = slot.Equipment.get
// slot.Equipment = None
// (obj, obj.GUID)
// }.toList
// (oldWeapons, weapons, afterInventory)
//TODO for now, just refill ammo; assume weapons stay the same
vehicle.Weapons
.collect { case (_, slot : EquipmentSlot) if slot.Equipment.nonEmpty => slot.Equipment.get }
.collect {
case weapon : Tool =>
weapon.AmmoSlots.foreach { ammo => ammo.Box.Capacity = ammo.Box.Definition.Capacity }
}
(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 {
//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 {
//accommodate as much of inventory as possible
val (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory)
(Nil, Nil, stow)
}
//accommodate as much of inventory as possible
val (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory)
(Nil, Nil, stow)
}
finalInventory.foreach {
_.obj.Faction = vehicle.Faction
}
player.Zone.VehicleEvents ! VehicleServiceMessage(
player.Zone.id,
VehicleAction.ChangeLoadout(vehicle.GUID, oldWeapons, newWeapons, oldInventory, finalInventory)
)
player.Zone.AvatarEvents ! AvatarServiceMessage(
player.Name,
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, true)
)
}
finalInventory.foreach {
_.obj.Faction = vehicle.Faction
}
player.Zone.VehicleEvents ! VehicleServiceMessage(
player.Zone.id,
VehicleAction.ChangeLoadout(vehicle.GUID, oldWeapons, newWeapons, oldInventory, finalInventory)
)
player.Zone.AvatarEvents ! AvatarServiceMessage(
player.Name,
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, true)
)
case _ => ;
}
case _ => ;
}
case VehicleControl.Disable() =>
PrepareForDisabled(kickPassengers = false)
context.become(Disabled)
case VehicleControl.Disable() =>
PrepareForDisabled(kickPassengers = false)
context.become(Disabled)
case Vehicle.Deconstruct(time) =>
time match {
case Some(delay) if vehicle.Definition.undergoesDecay =>
decaying = true
decayTimer.cancel()
decayTimer = context.system.scheduler.scheduleOnce(delay, self, VehicleControl.PrepareForDeletion())
case _ =>
PrepareForDisabled(kickPassengers = true)
PrepareForDeletion()
context.become(ReadyToDelete)
}
case Vehicle.Deconstruct(time) =>
time match {
case Some(delay) if vehicle.Definition.undergoesDecay =>
decaying = true
decayTimer.cancel()
decayTimer = context.system.scheduler.scheduleOnce(delay, self, VehicleControl.PrepareForDeletion())
case _ =>
PrepareForDisabled(kickPassengers = true)
PrepareForDeletion()
context.become(ReadyToDelete)
}
case VehicleControl.PrepareForDeletion() =>
PrepareForDisabled(kickPassengers = true)
PrepareForDeletion()
context.become(ReadyToDelete)
case VehicleControl.PrepareForDeletion() =>
PrepareForDisabled(kickPassengers = true)
PrepareForDeletion()
context.become(ReadyToDelete)
case VehicleControl.AssignOwnership(player) =>
vehicle.AssignOwnership(player)
case VehicleControl.AssignOwnership(player) =>
vehicle.AssignOwnership(player)
}
final def Enabled: Receive =
commonEnabledBehavior
.orElse {
case _ => ;
}
def Disabled : Receive =
checkBehavior
.orElse {
case msg : Deployment.TryUndeploy =>
deployBehavior.apply(msg)
def commonDisabledBehavior: Receive = checkBehavior
.orElse {
case msg @ Mountable.TryDismount(_, seat_num) =>
dismountBehavior.apply(msg)
dismountCleanup(seat_num)
case msg @ Mountable.TryDismount(_, seat_num) =>
dismountBehavior.apply(msg)
dismountCleanup(seat_num)
case Vehicle.Deconstruct(time) =>
time match {
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) =>
time match {
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 VehicleControl.PrepareForDeletion() =>
PrepareForDeletion()
context.become(ReadyToDelete)
}
case VehicleControl.PrepareForDeletion() =>
PrepareForDeletion()
context.become(ReadyToDelete)
final def Disabled: Receive = commonDisabledBehavior
.orElse {
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 =
checkBehavior
.orElse {
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 _ =>
}
final def ReadyToDelete: Receive = commonDeleteBehavior
.orElse {
case _ => ;
}
override protected def mountTest(
obj: PlanetSideServerObject with Mountable,
@ -387,7 +369,6 @@ class VehicleControl(vehicle: Vehicle)
val events = zone.VehicleEvents
//miscellaneous changes
recoverFromEnvironmentInteracting()
Vehicles.BeforeUnloadVehicle(vehicle, zone)
//escape being someone else's cargo
vehicle.MountedIn match {
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 = {
decaying = false
val zone = vehicle.Zone
//miscellaneous changes
Vehicles.BeforeUnloadVehicle(vehicle, zone)
//cancel jammed behavior
CancelJammeredSound(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.
* Flying vehicles do not display progress towards being waterlogged. They just disable outright.
@ -895,8 +746,4 @@ object VehicleControl {
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
package net.psforever.objects.zones
import akka.actor.{Actor, Props}
import akka.actor.Actor
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.{Default, Vehicle}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.vehicles.VehicleControl
import scala.annotation.tailrec
import scala.collection.mutable.ListBuffer
@ -42,8 +40,7 @@ class ZoneVehicleActor(zone: Zone, vehicleList: ListBuffer[Vehicle]) extends Act
} else {
vehicleList += vehicle
vehicle.Zone = zone
vehicle.Actor =
context.actorOf(Props(classOf[VehicleControl], vehicle), PlanetSideServerObject.UniqueActorName(vehicle))
vehicle.Definition.Initialize(vehicle, context)
}
if (vehicle.MountedIn.isEmpty) {
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 {
case Some(index) =>
vehicleList.remove(index)
context.stop(vehicle.Actor)
vehicle.Actor = Default.Actor
vehicle.Definition.Uninitialize(vehicle, context)
zone.actor ! ZoneActor.RemoveFromBlockMap(vehicle)
sender() ! Zone.Vehicle.HasDespawned(zone, vehicle)
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>
* `46 - Sends "Generator damage is at a critical level!" message`
* `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>
* `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>
@ -191,6 +191,7 @@ import scodec.codecs._
* `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>
* `54 - Plays jammed buzzing sound in vicinity of target`<br>
* `55 - Trigger APC EMP`<br>
* `68 - Vehicle shield health`<br>
* `79 - ???`<br>
* `80 - Damage vehicle (unknown value)`<br>

View file

@ -3,7 +3,8 @@ package objects
import akka.actor.Props
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.ballistics._
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.tube.SpawnTube
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.zones.{Zone, ZoneMap}
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 zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {}
@ -1234,6 +1235,8 @@ class DamageableVehicleDamageMountedTest extends ActorTest {
zone.Activity = activityProbe.ref
zone.AvatarEvents = avatarProbe.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
lodestar.Position = Vector3(1, 0, 0)
@ -1271,7 +1274,7 @@ class DamageableVehicleDamageMountedTest extends ActorTest {
guid.register(atv, 11)
//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.Seats(0).mount(player2)
player2.VehicleSeated = lodestar.GUID
@ -1314,50 +1317,38 @@ class DamageableVehicleDamageMountedTest extends ActorTest {
assert(atv.Shields == 1)
lodestar.Actor ! Vitality.Damage(applyDamageTo)
val msg12 = vehicleProbe.receiveN(2, 200 milliseconds)
val msg3 = activityProbe.receiveOne(200 milliseconds)
val msg45 = avatarProbe.receiveN(2,200 milliseconds)
assert(
msg12.head match {
case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 68, _)) => true
case _ => false
}
)
assert(
msg12(1) match {
case VehicleServiceMessage("test", VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 0, _)) => true
case _ => false
}
)
assert(
msg3 match {
case activity: Zone.HotSpot.Activity =>
activity.attacker == pSource &&
activity.defender == SourceEntry(lodestar) &&
activity.location == Vector3(1, 0, 0)
case _ => false
}
)
assert(
msg45.head match {
case AvatarServiceMessage(
"TestCharacter2",
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(400, Vector3(2, 0, 0)))
) =>
true
case _ => false
}
)
assert(
msg45(1) match {
case AvatarServiceMessage(
"TestCharacter3",
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(0, Vector3(2, 0, 0)))
) =>
true
case _ => false
}
)
val msg12 = vehicleProbe.receiveN(2, 500 milliseconds)
val msg3 = activityProbe.receiveOne(500 milliseconds)
val msg45 = avatarProbe.receiveN(2,500 milliseconds)
msg12.head match {
case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 68, _)) => ;
case _ => assert(false)
}
msg12(1) match {
case VehicleServiceMessage("test", VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 0, _)) => ;
case _ => assert(false)
}
msg3 match {
case activity: Zone.HotSpot.Activity =>
assert(activity.attacker == pSource &&
activity.defender == SourceEntry(lodestar) &&
activity.location == Vector3(1, 0, 0))
case _ => assert(false)
}
msg45.head match {
case AvatarServiceMessage(
"TestCharacter2",
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(400, Vector3(2, 0, 0)))
) => ;
case _ => assert(false)
}
msg45(1) match {
case AvatarServiceMessage(
"TestCharacter3",
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(0, Vector3(2, 0, 0)))
) => ;
case _ => assert(false)
}
assert(lodestar.Health < lodestar.Definition.DefaultHealth)
assert(lodestar.Shields == 0)
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 zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {}
@ -1417,7 +1408,7 @@ class DamageableVehicleJammeringMountedTest extends ActorTest {
guid.register(player2, 12)
guid.register(player3, 13)
lodestar.Actor = system.actorOf(Props(classOf[VehicleControl], lodestar), "lodestar-control")
lodestar.Definition.Initialize(lodestar, context)
atv.Zone = zone
lodestar.Zone = zone
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
atv.Actor = system.actorOf(Props(classOf[VehicleControl], atv), "atv-control")
atv.Position = Vector3(1, 0, 0)
@ -1611,19 +1602,22 @@ class DamageableVehicleDestroyMountedTest extends ActorTest {
val player3Probe = TestProbe()
player3.Actor = player3Probe.ref
val activityProbe = TestProbe()
val avatarProbe = TestProbe()
val vehicleProbe = TestProbe()
val catchall = TestProbe()
val guid = new NumberPoolHub(new MaxNumberSource(15))
val zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {}
GUID(guid)
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(atvWeapon, 2)
@ -1639,7 +1633,7 @@ class DamageableVehicleDestroyMountedTest extends ActorTest {
guid.register(player2, 12)
guid.register(player3, 13)
lodestar.Actor = system.actorOf(Props(classOf[VehicleControl], lodestar), "lodestar-control")
lodestar.Definition.Initialize(lodestar, context)
atv.Zone = zone
lodestar.Zone = zone
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.terminals.{Terminal, TerminalControl}
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.packet.game.{InventoryStateMessage, RepairMessage}
import net.psforever.types._

View file

@ -4,14 +4,14 @@ package objects
import akka.actor.{ActorRef, Props}
import akka.actor.typed.scaladsl.adapter._
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.ce.{DeployableCategory, DeployedItem, TelepadLike}
import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource
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.packet.game.objectcreate.ObjectCreateMessageParent
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 telepad = new TelepadDeployable(TelepadRouterTest.router_telepad_deployable) //guid=1
val router = Vehicle(GlobalDefinitions.router) //guid=2
@ -242,17 +242,20 @@ class TelepadDeployableResponseFromRouterTest extends ActorTest {
guid.register(internal, number = 3)
guid.register(router.Utility(UtilityType.teleportpad_terminal).get, number = 4) //necessary
router.Zone = zone
router.Actor = system.actorOf(Props(classOf[VehicleControl], router), "test-router")
router.Definition.Initialize(router, context)
telepad.Router = PlanetSideGUID(2) //artificial
"TelepadDeployable" should {
"link with a connected router" in {
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")
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
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(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")
zone.Deployables ! Zone.Deployable.Build(telepad)

View file

@ -7,15 +7,17 @@ import akka.testkit.TestProbe
import base.{ActorTest, FreedContextActorTest}
import net.psforever.actors.zone.ZoneActor
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.source.MaxNumberSource
import net.psforever.objects.serverobject.environment._
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.vehicles.{VehicleControl, VehicleLockState}
import net.psforever.objects.vital.VehicleShieldCharge
import net.psforever.objects.vehicles.VehicleLockState
import net.psforever.objects.vehicles.control.VehicleControl
import net.psforever.objects.vital.{VehicleShieldCharge, Vitality}
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.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
@ -201,12 +203,16 @@ class VehicleControlPrepareForDeletionMountedInTest extends FreedContextActorTes
}
class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActorTest {
val vehicleProbe = new TestProbe(system)
val catchall = new TestProbe(system)
val guid = new NumberPoolHub(new MaxNumberSource(10))
ServiceManager.boot
val zone = new Zone("test", new ZoneMap("test"), 0) {
GUID(guid)
override def SetupNumberPools(): Unit = {}
override def VehicleEvents = vehicleProbe.ref
override def tasks = catchall.ref
}
zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor")
// 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
val lodestar = Vehicle(GlobalDefinitions.lodestar)
lodestar.Faction = PlanetSideEmpire.TR
lodestar.Zone = zone
val player1 = Player(VehicleTest.avatar1) //name="test1"
val player2 = Player(VehicleTest.avatar2) //name="test2"
@ -237,85 +244,64 @@ class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActor
player2.VehicleSeated = lodestar.GUID
lodestar.CargoHolds(1).mount(vehicle)
vehicle.MountedIn = lodestar.GUID
val vehicleProbe = new TestProbe(system)
zone.VehicleEvents = vehicleProbe.ref
zone.Transport ! Zone.Vehicle.Spawn(lodestar) //can not fake this
lodestar.Definition.Initialize(lodestar, context)
"VehicleControl" should {
"if with mounted cargo, eject it when marked for deconstruction" in {
lodestar.Actor ! Vehicle.Deconstruct()
val vehicle_msg = vehicleProbe.receiveN(6, 500 milliseconds)
assert(
vehicle_msg.head match {
case VehicleServiceMessage(
vehicle_msg(5) match {
case VehicleServiceMessage(
"test",
VehicleAction.KickPassenger(PlanetSideGUID(4), 4, false, PlanetSideGUID(2))
) =>
true
case _ => false
}
)
) => ;
case _ => assert(false)
}
assert(player2.VehicleSeated.isEmpty)
assert(lodestar.Seats(0).occupant.isEmpty)
//cargo dismounting messages
assert(
vehicle_msg(1) match {
case VehicleServiceMessage(
vehicle_msg.head match {
case VehicleServiceMessage(
_,
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))
) =>
true
case _ => false
}
)
assert(
vehicle_msg(2) match {
case VehicleServiceMessage(
) => ;
case _ => assert(false)
}
vehicle_msg(1) match {
case VehicleServiceMessage(
_,
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))
) =>
true
case _ => false
}
)
assert(
vehicle_msg(3) match {
case VehicleServiceMessage(
) => ;
case _ => assert(false)
}
vehicle_msg(2) match {
case VehicleServiceMessage(
"test",
VehicleAction.SendResponse(
_,
CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0)
_,
CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0)
)
) =>
true
case _ => false
}
)
assert(
vehicle_msg(4) match {
case VehicleServiceMessage(
) => ;
case _ => assert(false)
}
vehicle_msg(3) match {
case VehicleServiceMessage(
"test",
VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))
) =>
true
case _ => false
}
)
assert(
vehicle_msg(5) match {
case VehicleServiceMessage(
) => ;
case _ => assert(false)
}
vehicle_msg(4) match {
case VehicleServiceMessage(
"test",
VehicleAction.SendResponse(
_,
CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0)
_,
CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0)
)
) =>
true
case _ => false
}
)
) => ;
case _ => assert(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 {
import net.psforever.objects.avatar.Avatar
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.serverobject.PlanetSideServerObject
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.packet.game._
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}

View file

@ -4,7 +4,7 @@ package service
import akka.actor.Props
import base.ActorTest
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.types.{PlanetSideGUID, _}
import net.psforever.services.{Service, ServiceManager}