diff --git a/common/src/main/scala/net/psforever/objects/Deployables.scala b/common/src/main/scala/net/psforever/objects/Deployables.scala index 8dd2caa1..1180215a 100644 --- a/common/src/main/scala/net/psforever/objects/Deployables.scala +++ b/common/src/main/scala/net/psforever/objects/Deployables.scala @@ -5,7 +5,6 @@ import akka.actor.ActorRef import scala.concurrent.duration._ import net.psforever.objects.ce.{Deployable, DeployedItem} -import net.psforever.objects.vehicles.{Utility, UtilityType} import net.psforever.objects.zones.Zone import net.psforever.packet.game.{DeployableInfo, DeploymentAction} import net.psforever.types.{CertificationType, PlanetSideGUID} @@ -123,25 +122,6 @@ object Deployables { boomers ++ deployables } - def RemoveTelepad(vehicle: Vehicle): Unit = { - val zone = vehicle.Zone - (vehicle.Utility(UtilityType.internal_router_telepad_deployable) match { - case Some(util: Utility.InternalTelepad) => - val telepad = util.Telepad - util.Telepad = None - zone.GUID(telepad) - case _ => - None - }) match { - case Some(telepad: TelepadDeployable) => - log.info(s"BeforeUnload: deconstructing telepad $telepad that was linked to router $vehicle ...") - telepad.Active = false - zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(telepad), zone)) - zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(telepad, zone, Some(0 seconds))) - case _ => ; - } - } - /** * Initialize the deployables backend information. * @param avatar the player's core diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 0ec8f321..bec647c9 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -6741,6 +6741,7 @@ object GlobalDefinitions { resource_silo.Name = "resource_silo" resource_silo.Damageable = false resource_silo.Repairable = false + resource_silo.MaxNtuCapacitor = 1000 capture_terminal.Name = "capture_terminal" capture_terminal.Damageable = false diff --git a/common/src/main/scala/net/psforever/objects/Ntu.scala b/common/src/main/scala/net/psforever/objects/Ntu.scala new file mode 100644 index 00000000..0d2abe51 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/Ntu.scala @@ -0,0 +1,85 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects + +import akka.actor.{Actor, ActorRef} +import net.psforever.objects.serverobject.transfer.{TransferBehavior, TransferContainer} + +object Ntu { + object Nanites extends TransferContainer.TransferMaterial + + /** + * Message for a `sender` announcing it has nanites it can offer the recipient. + * @param src the nanite container recognized as the sender + */ + final case class Offer(src : NtuContainer) + + /** + * Message for a `sender` asking for nanites from the recipient. + * @param min a minimum amount of nanites requested; + * if 0, the `sender` has no expectations + * @param max the amount of nanites required to not make further requests; + * if 0, the `sender` is full and the message is for clean up operations + */ + final case class Request(min : Int, max : Int) + + /** + * Message for transferring nanites to a recipient. + * @param src the nanite container recognized as the sender + * @param amount the nanites transferred in this package + */ + final case class Grant(src : NtuContainer, amount : Int) +} + +trait NtuContainer extends TransferContainer { + def NtuCapacitor : Int + + def NtuCapacitor_=(value: Int) : Int + + def Definition : NtuContainerDefinition +} + +trait CommonNtuContainer extends NtuContainer { + private var ntuCapacitor : Int = 0 + + def NtuCapacitor : Int = ntuCapacitor + + def NtuCapacitor_=(value: Int) : Int = { + ntuCapacitor = scala.math.max(0, scala.math.min(value, Definition.MaxNtuCapacitor)) + NtuCapacitor + } + + def Definition : NtuContainerDefinition +} + +trait NtuContainerDefinition { + private var maxNtuCapacitor : Int = 0 + + def MaxNtuCapacitor : Int = maxNtuCapacitor + + def MaxNtuCapacitor_=(max: Int) : Int = { + maxNtuCapacitor = max + MaxNtuCapacitor + } +} + +trait NtuStorageBehavior extends Actor { + def NtuStorageObject : NtuContainer = null + + def storageBehavior : Receive = { + case Ntu.Offer(src) => HandleNtuOffer(sender, src) + + case Ntu.Grant(_, 0) | Ntu.Request(0, 0) | TransferBehavior.Stopping() => StopNtuBehavior(sender) + + case Ntu.Request(min, max) => HandleNtuRequest(sender, min, max) + + case Ntu.Grant(src, amount) => HandleNtuGrant(sender, src, amount) + } + + def HandleNtuOffer(sender : ActorRef, src : NtuContainer) : Unit + + def StopNtuBehavior(sender : ActorRef) : Unit + + def HandleNtuRequest(sender : ActorRef, min : Int, max : Int) : Unit + + def HandleNtuGrant(sender : ActorRef, src : NtuContainer, amount : Int) : Unit +} diff --git a/common/src/main/scala/net/psforever/objects/Vehicle.scala b/common/src/main/scala/net/psforever/objects/Vehicle.scala index b08900a4..0cd5071c 100644 --- a/common/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/common/src/main/scala/net/psforever/objects/Vehicle.scala @@ -77,6 +77,7 @@ class Vehicle(private val vehicleDef: VehicleDefinition) with OwnableByPlayer with StandardResistanceProfile with JammableUnit + with CommonNtuContainer with Container { private var faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL private var shields: Int = 0 @@ -85,7 +86,6 @@ class Vehicle(private val vehicleDef: VehicleDefinition) private var jammered: Boolean = false private var cloaked: Boolean = false private var flying: Boolean = false - private var ntuCapacitor: Int = 0 private var capacitor: Int = 0 /** @@ -199,19 +199,6 @@ class Vehicle(private val vehicleDef: VehicleDefinition) Flying } - def NtuCapacitor: Int = ntuCapacitor - - def NtuCapacitor_=(value: Int): Int = { - if (value > Definition.MaxNtuCapacitor) { - ntuCapacitor = Definition.MaxNtuCapacitor - } else if (value < 0) { - ntuCapacitor = 0 - } else { - ntuCapacitor = value - } - NtuCapacitor - } - def NtuCapacitorScaled : Int = { if(Definition.MaxNtuCapacitor > 0) { scala.math.ceil((NtuCapacitor.toFloat / Definition.MaxNtuCapacitor.toFloat) * 10).toInt diff --git a/common/src/main/scala/net/psforever/objects/Vehicles.scala b/common/src/main/scala/net/psforever/objects/Vehicles.scala index 935c4b01..7414c2db 100644 --- a/common/src/main/scala/net/psforever/objects/Vehicles.scala +++ b/common/src/main/scala/net/psforever/objects/Vehicles.scala @@ -2,15 +2,20 @@ package net.psforever.objects import net.psforever.objects.serverobject.CommonMessages -import net.psforever.objects.vehicles.{CargoBehavior, VehicleLockState} +import net.psforever.objects.serverobject.deploy.Deployment +import net.psforever.objects.serverobject.transfer.TransferContainer +import net.psforever.objects.serverobject.structures.{StructureType, WarpGate} +import net.psforever.objects.vehicles.{CargoBehavior, Utility, UtilityType, VehicleLockState} import net.psforever.objects.zones.Zone import net.psforever.packet.game.TriggeredSound -import net.psforever.types.{DriveState, PlanetSideGUID} -import services.Service +import net.psforever.types.{DriveState, PlanetSideGUID, Vector3} +import services.{RemoverActor, Service} import services.avatar.{AvatarAction, AvatarServiceMessage} import services.local.{LocalAction, LocalServiceMessage} import services.vehicle.{VehicleAction, VehicleServiceMessage} +import scala.concurrent.duration._ + object Vehicles { private val log = org.log4s.getLogger("Vehicles") @@ -226,7 +231,7 @@ object Vehicles { cargoHold.Occupant match { case Some(cargo: Vehicle) => { cargo.Seats(0).Occupant match { - case Some(cargoDriver: Player) => + case Some(cargoDriver : Player) => CargoBehavior.HandleVehicleCargoDismount( target.Zone, cargo.GUID, @@ -301,11 +306,93 @@ object Vehicles { // If AMS is deployed, swap it to the new faction target.Definition match { case GlobalDefinitions.router => - log.info("FinishHackingVehicle: cleaning up after a router ...") - Deployables.RemoveTelepad(target) + Vehicles.RemoveTelepads(target) case GlobalDefinitions.ams if target.DeploymentState == DriveState.Deployed => zone.VehicleEvents ! VehicleServiceMessage.AMSDeploymentChange(zone) case _ => ; } } + + def FindANTChargingSource(obj : TransferContainer, ntuChargingTarget : Option[TransferContainer]) : Option[TransferContainer] = { + //determine if we are close enough to charge from something + (ntuChargingTarget match { + case Some(target : WarpGate) if { + val soiRadius = target.Definition.SOIRadius + Vector3.DistanceSquared(obj.Position.xy, target.Position.xy) < soiRadius * soiRadius + } => + Some(target.asInstanceOf[NtuContainer]) + case None => + None + }).orElse { + val position = obj.Position.xy + obj.Zone.Buildings.values + .collectFirst { case gate : WarpGate + if { val soiRadius = gate.Definition.SOIRadius + Vector3.DistanceSquared(position, gate.Position.xy) < soiRadius * soiRadius } => gate } + .asInstanceOf[Option[NtuContainer]] + } + } + + def FindANTDischargingTarget(obj : TransferContainer, ntuChargingTarget : Option[TransferContainer]) : Option[TransferContainer] = { + (ntuChargingTarget match { + case out @ Some(target : NtuContainer) if { + Vector3.DistanceSquared(obj.Position.xy, target.Position.xy) < 400 //20m is generous ... + } => + out + case _ => + None + }).orElse { + val position = obj.Position.xy + obj.Zone.Buildings.values + .find { building => + building.BuildingType == StructureType.Facility && { + val soiRadius = building.Definition.SOIRadius + Vector3.DistanceSquared(position, building.Position.xy) < soiRadius * soiRadius + } + } match { + case Some(building) => + building.Amenities + .collect { case obj : NtuContainer => obj } + .sortBy {o => Vector3.DistanceSquared(position, o.Position.xy) < 400 } //20m is generous ... + .headOption + case None => + None + } + } + } + + /** + * Before a vehicle is removed from the game world, the following actions must be performed. + * @param vehicle the vehicle + */ + def BeforeUnloadVehicle(vehicle : Vehicle, zone : Zone) : Unit = { + vehicle.Definition match { + case GlobalDefinitions.ams => + vehicle.Actor ! Deployment.TryUndeploy(DriveState.Undeploying) + case GlobalDefinitions.ant => + vehicle.Actor ! Deployment.TryUndeploy(DriveState.Undeploying) + case GlobalDefinitions.router => + vehicle.Actor ! Deployment.TryUndeploy(DriveState.Undeploying) + case _ => ; + } + } + + def RemoveTelepads(vehicle: Vehicle) : Unit = { + val zone = vehicle.Zone + (vehicle.Utility(UtilityType.internal_router_telepad_deployable) match { + case Some(util : Utility.InternalTelepad) => + val telepad = util.Telepad + util.Telepad = None + zone.GUID(telepad) + case _ => + None + }) match { + case Some(telepad : TelepadDeployable) => + log.debug(s"BeforeUnload: deconstructing telepad $telepad that was linked to router $vehicle ...") + telepad.Active = false + zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(telepad), zone)) + zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(telepad, zone, Some(0 seconds))) + case _ => ; + } + } } diff --git a/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala index 900edcb4..0478e0b3 100644 --- a/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala @@ -1,6 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.definition +import net.psforever.objects.NtuContainerDefinition import net.psforever.objects.definition.converter.VehicleConverter import net.psforever.objects.inventory.InventoryTile import net.psforever.objects.vehicles.{DestroyedVehicle, UtilityType} @@ -18,9 +19,9 @@ import scala.concurrent.duration._ class VehicleDefinition(objectId: Int) extends ObjectDefinition(objectId) with VitalityDefinition + with NtuContainerDefinition with ResistanceProfileMutators with DamageResistanceModel { - /** vehicle shields offered through amp station facility benefits (generally: 20% of health + 1) */ private var maxShields: Int = 0 /* key - seat index, value - seat object */ @@ -46,7 +47,6 @@ class VehicleDefinition(objectId: Int) private var serverVehicleOverrideSpeeds: (Int, Int) = (0, 0) private var deconTime: Option[FiniteDuration] = None private var maxCapacitor: Int = 0 - private var maxNtuCapacitor: Int = 0 private var destroyedModel: Option[DestroyedVehicle.Value] = None Name = "vehicle" Packet = VehicleDefinition.converter @@ -161,16 +161,9 @@ class VehicleDefinition(objectId: Int) def AutoPilotSpeed2: Int = serverVehicleOverrideSpeeds._2 - def MaxNtuCapacitor: Int = maxNtuCapacitor + def MaxCapacitor : Int = maxCapacitor - def MaxNtuCapacitor_=(max: Int): Int = { - maxNtuCapacitor = max - MaxNtuCapacitor - } - - def MaxCapacitor: Int = maxCapacitor - - def MaxCapacitor_=(max: Int): Int = { + def MaxCapacitor_=(max: Int) : Int = { maxCapacitor = max MaxCapacitor } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala b/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala index 82437aba..2b6902b7 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala @@ -2,15 +2,12 @@ package net.psforever.objects.serverobject.damage import akka.actor.Actor.Receive -import net.psforever.objects.{GlobalDefinitions, Vehicle} +import net.psforever.objects.{Vehicle, Vehicles} import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.serverobject.damage.Damageable.Target -import net.psforever.objects.serverobject.deploy.Deployment import net.psforever.objects.vital.resolution.ResolutionCalculations -import net.psforever.types.{DriveState, PlanetSideGUID} import services.Service -import services.local.{LocalAction, LocalServiceMessage} -import services.vehicle.{VehicleAction, VehicleService, VehicleServiceMessage} +import services.vehicle.{VehicleAction, VehicleServiceMessage} import scala.concurrent.duration._ @@ -178,18 +175,7 @@ object DamageableVehicle { } }) //special considerations for certain vehicles - target.Definition match { - case GlobalDefinitions.ams => - target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying) - case GlobalDefinitions.router => - target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying) - VehicleService.BeforeUnloadVehicle(target, zone) - zone.LocalEvents ! LocalServiceMessage( - zone.Id, - LocalAction.ToggleTeleportSystem(PlanetSideGUID(0), target, None) - ) - case _ => ; - } + Vehicles.BeforeUnloadVehicle(target, zone) //shields if (target.Shields > 0) { target.Shields = 0 diff --git a/common/src/main/scala/net/psforever/objects/serverobject/deploy/DeploymentBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/deploy/DeploymentBehavior.scala index df570795..e82b5cd7 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/deploy/DeploymentBehavior.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/deploy/DeploymentBehavior.scala @@ -2,6 +2,12 @@ package net.psforever.objects.serverobject.deploy import akka.actor.Actor +import net.psforever.types.{DriveState, Vector3} +import services.Service +import services.vehicle.{VehicleAction, VehicleServiceMessage} + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration._ /** * The logic governing `Deployment` objects that use the following messages: @@ -13,48 +19,120 @@ import akka.actor.Actor * @see `DriveState` */ trait DeploymentBehavior { - this: Actor => + _: Actor => def DeploymentObject: Deployment.DeploymentObject val deployBehavior: Receive = { case Deployment.TryDeploymentChange(state) => - val obj = DeploymentObject - if ( - Deployment.NextState(obj.DeploymentState) == state - && (obj.DeploymentState = state) == state - ) { - if (Deployment.CheckForDeployState(state)) { - sender ! Deployment.CanDeploy(obj, state) - } else { //may need to check in future - sender ! Deployment.CanUndeploy(obj, state) - } - } else { - sender ! Deployment.CanNotChangeDeployment(obj, state, "incorrect transition state") - } + sender ! TryDeploymentStateChange(state) case Deployment.TryDeploy(state) => - val obj = DeploymentObject - if ( - Deployment.CheckForDeployState(state) - && Deployment.NextState(obj.DeploymentState) == state - && (obj.DeploymentState = state) == state - ) { - sender ! Deployment.CanDeploy(obj, state) - } else { - sender ! Deployment.CanNotChangeDeployment(obj, state, "incorrect deploy transition state") - } + sender ! TryDeployStateChange(state) case Deployment.TryUndeploy(state) => - val obj = DeploymentObject - if ( - Deployment.CheckForUndeployState(state) - && Deployment.NextState(obj.DeploymentState) == state - && (obj.DeploymentState = state) == state - ) { - sender ! Deployment.CanUndeploy(obj, state) - } else { - sender ! Deployment.CanNotChangeDeployment(obj, state, "incorrect undeploy transition state") + sender ! TryUndeployStateChange(state) + } + + def TryDeploymentStateChange(state : DriveState.Value) : Any = { + val obj = DeploymentObject + val prevState = obj.DeploymentState + if(TryDeploymentChange(obj, state)) { + if(Deployment.CheckForDeployState(state)) { + DeploymentAction(obj, state, prevState) + Deployment.CanDeploy(obj, state) } + else { + UndeploymentAction(obj, state, prevState) + Deployment.CanUndeploy(obj, state) + } + } + else { + Deployment.CanNotChangeDeployment(obj, state, "incorrect transition state") + } + } + + def TryDeployStateChange(state : DriveState.Value) : Any = { + val obj = DeploymentObject + val prevState = obj.DeploymentState + if(Deployment.CheckForDeployState(state) && TryDeploymentChange(obj, state)) { + DeploymentAction(obj, state, prevState) + Deployment.CanDeploy(obj, state) + } + else { + Deployment.CanNotChangeDeployment(obj, state, "incorrect deploy transition state") + } + } + + def TryUndeployStateChange(state : DriveState.Value) : Any = { + val obj = DeploymentObject + val prevState = obj.DeploymentState + if(Deployment.CheckForUndeployState(state) && TryUndeploymentChange(obj, state)) { + UndeploymentAction(obj, state, prevState) + Deployment.CanUndeploy(obj, state) + } + else { + Deployment.CanNotChangeDeployment(obj, state, "incorrect undeploy transition state") + } + } + + def TryDeploymentChange(obj : Deployment.DeploymentObject, state : DriveState.Value) : Boolean = { + DeploymentBehavior.TryDeploymentChange(obj, state) + } + + def TryUndeploymentChange(obj : Deployment.DeploymentObject, state : DriveState.Value) : Boolean = { + DeploymentBehavior.TryDeploymentChange(obj, state) + } + + def DeploymentAction(obj : Deployment.DeploymentObject, state : DriveState.Value, prevState : DriveState.Value) : DriveState.Value = { + val guid = obj.GUID + val zone = obj.Zone + val zoneChannel = zone.Id + val GUID0 = Service.defaultPlayerGUID + //TODO remove this arbitrary allowance angle when no longer helpful + if(obj.Orientation.x > 30 && obj.Orientation.x < 330) { + obj.DeploymentState = prevState + prevState + } + else if(state == DriveState.Deploying) { + obj.Velocity = Some(Vector3.Zero) //no velocity + zone.VehicleEvents ! VehicleServiceMessage(zoneChannel, VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero)) + context.system.scheduler.scheduleOnce(obj.DeployTime milliseconds, obj.Actor, Deployment.TryDeploy(DriveState.Deployed)) + state + } + else if(state == DriveState.Deployed) { + obj.Velocity = Some(Vector3.Zero) //no velocity + zone.VehicleEvents ! VehicleServiceMessage(zoneChannel, VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero)) + state + } + else { + prevState + } + } + + def UndeploymentAction(obj : Deployment.DeploymentObject, state : DriveState.Value, prevState : DriveState.Value) : DriveState.Value = { + val guid = obj.GUID + val zone = obj.Zone + val zoneChannel = zone.Id + val GUID0 = Service.defaultPlayerGUID + if(state == DriveState.Undeploying) { + zone.VehicleEvents ! VehicleServiceMessage(zoneChannel, VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero)) + import scala.concurrent.ExecutionContext.Implicits.global + context.system.scheduler.scheduleOnce(obj.UndeployTime milliseconds, obj.Actor, Deployment.TryUndeploy(DriveState.Mobile)) + state + } + else if(state == DriveState.Mobile) { + zone.VehicleEvents ! VehicleServiceMessage(zoneChannel, VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero)) + state + } + else { + prevState + } + } +} + +object DeploymentBehavior { + def TryDeploymentChange(obj : Deployment.DeploymentObject, state : DriveState.Value) : Boolean = { + Deployment.NextState(obj.DeploymentState) == state && (obj.DeploymentState = state) == state } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSilo.scala b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSilo.scala index 725ee14b..12d8a2a8 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSilo.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSilo.scala @@ -2,14 +2,12 @@ package net.psforever.objects.serverobject.resourcesilo import akka.actor.{ActorContext, Props} -import net.psforever.objects.{GlobalDefinitions, Player} +import net.psforever.objects.{CommonNtuContainer, GlobalDefinitions, Player} import net.psforever.objects.serverobject.structures.Amenity import net.psforever.packet.game.UseItemMessage import net.psforever.types.Vector3 -class ResourceSilo extends Amenity { - private var chargeLevel: Int = 0 - private val maximumCharge: Int = 1000 +class ResourceSilo extends Amenity with CommonNtuContainer { // For the flashing red light on top of the NTU silo on. // Default to true until charge level can be persisted across restarts as default charge level is 0 @@ -18,21 +16,7 @@ class ResourceSilo extends Amenity { // For the NTU display bar private var capacitorDisplay: Long = 0 - def ChargeLevel: Int = chargeLevel - - // Do not call directly. Use ResourceSilo.UpdateChargeLevel message to handle logic such as low ntu warnings - def ChargeLevel_=(charge: Int): Int = { - if (charge < 0) { - chargeLevel = 0 - } else if (charge > maximumCharge) { - chargeLevel = maximumCharge - } else { - chargeLevel = charge - } - ChargeLevel - } - - def MaximumCharge: Int = maximumCharge + def MaxNtuCapacitor : Int = Definition.MaxNtuCapacitor def LowNtuWarningOn: Boolean = lowNtuWarningOn def LowNtuWarningOn_=(enabled: Boolean): Boolean = { @@ -40,7 +24,7 @@ class ResourceSilo extends Amenity { LowNtuWarningOn } - def CapacitorDisplay: Long = scala.math.ceil((ChargeLevel.toFloat / MaximumCharge.toFloat) * 10).toInt + def CapacitorDisplay : Long = scala.math.ceil((NtuCapacitor.toFloat / MaxNtuCapacitor.toFloat) * 10).toInt def Definition: ResourceSiloDefinition = GlobalDefinitions.resource_silo @@ -50,8 +34,6 @@ class ResourceSilo extends Amenity { } object ResourceSilo { - - final case class Use(player: Player, msg: UseItemMessage) final case class UpdateChargeLevel(amount: Int) final case class LowNtuWarning(enabled: Boolean) sealed trait Exchange diff --git a/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala index c75b8084..f62ef881 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala @@ -1,21 +1,29 @@ // Copyright (c) 2019 PSForever package net.psforever.objects.serverobject.resourcesilo -import akka.actor.Actor +import akka.actor.{Actor, ActorRef} +import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} +import net.psforever.objects.serverobject.transfer.TransferBehavior import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.{Ntu, NtuContainer, NtuStorageBehavior} import net.psforever.types.PlanetSideEmpire +import services.Service import services.avatar.{AvatarAction, AvatarServiceMessage} import services.local.{LocalAction, LocalServiceMessage} +import services.vehicle.{VehicleAction, VehicleServiceMessage} +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration._ /** * An `Actor` that handles messages being dispatched to a specific `Resource Silo`. * @param resourceSilo the `Resource Silo` object being governed */ -class ResourceSiloControl(resourceSilo: ResourceSilo) extends Actor with FactionAffinityBehavior.Check { +class ResourceSiloControl(resourceSilo: ResourceSilo) extends Actor with FactionAffinityBehavior.Check with NtuStorageBehavior { def FactionObject: FactionAffinity = resourceSilo private[this] val log = org.log4s.getLogger + var panelAnimationFunc : Int=>Unit = PanelAnimation def receive: Receive = { case "startup" => @@ -26,77 +34,147 @@ class ResourceSiloControl(resourceSilo: ResourceSilo) extends Actor with Faction case _ => ; } - def Processing: Receive = - checkBehavior.orElse { - case ResourceSilo.Use(player, msg) => - if (resourceSilo.Faction == PlanetSideEmpire.NEUTRAL || player.Faction == resourceSilo.Faction) { + def Processing : Receive = checkBehavior + .orElse(storageBehavior) + .orElse { + case CommonMessages.Use(player, _) => + if(resourceSilo.Faction == PlanetSideEmpire.NEUTRAL || player.Faction == resourceSilo.Faction) { resourceSilo.Zone.Vehicles.find(v => v.PassengerInSeat(player).contains(0)) match { case Some(vehicle) => - sender ! ResourceSilo.ResourceSiloMessage(player, msg, resourceSilo.Use(player, msg)) + context.system.scheduler.scheduleOnce(delay = 1000 milliseconds, vehicle.Actor, TransferBehavior.Discharging(Ntu.Nanites)) case _ => } } case ResourceSilo.LowNtuWarning(enabled: Boolean) => - resourceSilo.LowNtuWarningOn = enabled - log.trace(s"LowNtuWarning: Silo ${resourceSilo.GUID} low ntu warning set to $enabled") - val building = resourceSilo.Owner - val zone = building.Zone - building.Zone.AvatarEvents ! AvatarServiceMessage( - zone.Id, - AvatarAction.PlanetsideAttribute(building.GUID, 47, if (resourceSilo.LowNtuWarningOn) 1 else 0) - ) + LowNtuWarning(enabled) case ResourceSilo.UpdateChargeLevel(amount: Int) => - val siloChargeBeforeChange = resourceSilo.ChargeLevel - val siloDisplayBeforeChange = resourceSilo.CapacitorDisplay - val building = resourceSilo.Owner.asInstanceOf[Building] - val zone = building.Zone + UpdateChargeLevel(amount) - // Increase if positive passed in or decrease charge level if negative number is passed in - resourceSilo.ChargeLevel += amount - if (resourceSilo.ChargeLevel > 0) { - log.trace(s"UpdateChargeLevel: Silo ${resourceSilo.GUID} set to ${resourceSilo.ChargeLevel}") - } - - // Only send updated capacitor display value to all clients if it has actually changed - if (resourceSilo.CapacitorDisplay != siloDisplayBeforeChange) { - log.trace( - s"Silo ${resourceSilo.GUID} NTU bar level has changed from $siloDisplayBeforeChange to ${resourceSilo.CapacitorDisplay}" - ) - resourceSilo.Owner.Actor ! Building.SendMapUpdate(all_clients = true) - zone.AvatarEvents ! AvatarServiceMessage( - zone.Id, - AvatarAction.PlanetsideAttribute(resourceSilo.GUID, 45, resourceSilo.CapacitorDisplay) - ) - building.Actor ! Building.SendMapUpdate(all_clients = true) - } - - val ntuIsLow = resourceSilo.ChargeLevel.toFloat / resourceSilo.MaximumCharge.toFloat < 0.2f - if (resourceSilo.LowNtuWarningOn && !ntuIsLow) { - self ! ResourceSilo.LowNtuWarning(enabled = false) - } else if (!resourceSilo.LowNtuWarningOn && ntuIsLow) { - self ! ResourceSilo.LowNtuWarning(enabled = true) - } - - if (resourceSilo.ChargeLevel == 0 && siloChargeBeforeChange > 0) { - // Oops, someone let the base run out of power. Shut it all down. - zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttribute(building.GUID, 48, 1)) - building.Faction = PlanetSideEmpire.NEUTRAL - zone.LocalEvents ! LocalServiceMessage( - zone.Id, - LocalAction.SetEmpire(building.GUID, PlanetSideEmpire.NEUTRAL) - ) - building.TriggerZoneMapUpdate() - } else if (siloChargeBeforeChange == 0 && resourceSilo.ChargeLevel > 0) { - // Power restored. Reactor Online. Sensors Online. Weapons Online. All systems nominal. - //todo: Check generator is online before starting up - zone.AvatarEvents ! AvatarServiceMessage( - zone.Id, - AvatarAction.PlanetsideAttribute(building.GUID, 48, 0) - ) - building.TriggerZoneMapUpdate() - } case _ => ; } + + def LowNtuWarning(enabled : Boolean) : Unit = { + resourceSilo.LowNtuWarningOn = enabled + log.trace(s"LowNtuWarning: Silo ${resourceSilo.GUID} low ntu warning set to $enabled") + val building = resourceSilo.Owner + val zone = building.Zone + building.Zone.AvatarEvents ! AvatarServiceMessage( + zone.Id, + AvatarAction.PlanetsideAttribute(building.GUID, 47, if(resourceSilo.LowNtuWarningOn) 1 else 0) + ) + } + + def UpdateChargeLevel(amount: Int) : Unit = { + val siloChargeBeforeChange = resourceSilo.NtuCapacitor + val siloDisplayBeforeChange = resourceSilo.CapacitorDisplay + val building = resourceSilo.Owner.asInstanceOf[Building] + val zone = building.Zone + + // Increase if positive passed in or decrease charge level if negative number is passed in + resourceSilo.NtuCapacitor += amount + if (resourceSilo.NtuCapacitor > 0) { + log.trace(s"UpdateChargeLevel: Silo ${resourceSilo.GUID} set to ${resourceSilo.NtuCapacitor}") + } + + // Only send updated capacitor display value to all clients if it has actually changed + if (resourceSilo.CapacitorDisplay != siloDisplayBeforeChange) { + log.trace(s"Silo ${resourceSilo.GUID} NTU bar level has changed from $siloDisplayBeforeChange to ${resourceSilo.CapacitorDisplay}") + resourceSilo.Owner.Actor ! Building.SendMapUpdate(all_clients = true) + zone.AvatarEvents ! AvatarServiceMessage( + zone.Id, + AvatarAction.PlanetsideAttribute(resourceSilo.GUID, 45, resourceSilo.CapacitorDisplay) + ) + building.Actor ! Building.SendMapUpdate(all_clients = true) + } + val ntuIsLow = resourceSilo.NtuCapacitor.toFloat / resourceSilo.Definition.MaxNtuCapacitor.toFloat < 0.2f + if (resourceSilo.LowNtuWarningOn && !ntuIsLow) { + LowNtuWarning(enabled = false) + } + else if (!resourceSilo.LowNtuWarningOn && ntuIsLow) { + LowNtuWarning(enabled = true) + } + if (resourceSilo.NtuCapacitor == 0 && siloChargeBeforeChange > 0) { + // Oops, someone let the base run out of power. Shut it all down. + zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttribute(building.GUID, 48, 1)) + building.Faction = PlanetSideEmpire.NEUTRAL + zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.SetEmpire(building.GUID, PlanetSideEmpire.NEUTRAL)) + building.TriggerZoneMapUpdate() + } + else if (siloChargeBeforeChange == 0 && resourceSilo.NtuCapacitor > 0) { + // Power restored. Reactor Online. Sensors Online. Weapons Online. All systems nominal. + //todo: Check generator is online before starting up + zone.AvatarEvents ! AvatarServiceMessage( + zone.Id, + AvatarAction.PlanetsideAttribute(building.GUID, 48, 0) + ) + building.TriggerZoneMapUpdate() + } + } + + /** + * The silo will agree to offers until its nanite capacitor is completely full. + */ + def HandleNtuOffer(sender : ActorRef, src : NtuContainer) : Unit = { + sender ! (if(resourceSilo.NtuCapacitor < resourceSilo.MaxNtuCapacitor) { + Ntu.Request(0, resourceSilo.MaxNtuCapacitor - resourceSilo.NtuCapacitor) + } + else { + StopNtuBehavior(sender) + Ntu.Request(0, 0) + }) + } + + /** + * Reset the animation trigger and attempt the stop animation. + */ + def StopNtuBehavior(sender : ActorRef) : Unit = { + panelAnimationFunc = PanelAnimation + panelAnimationFunc(0) + } + + /** + * na + * @param sender na + * @param min a minimum amount of nanites requested; + * @param max the amount of nanites required to not make further requests; + */ + def HandleNtuRequest(sender : ActorRef, min : Int, max : Int) : Unit = { + val originalAmount = resourceSilo.NtuCapacitor + UpdateChargeLevel(-min) + sender ! Ntu.Grant(resourceSilo, originalAmount - resourceSilo.NtuCapacitor) + } + + /** + * Accept nanites into the silo capacitor and set the animation state. + */ + def HandleNtuGrant(sender : ActorRef, src : NtuContainer, amount : Int) : Unit = { + if(amount != 0) { + val originalAmount = resourceSilo.NtuCapacitor + UpdateChargeLevel(amount) + panelAnimationFunc(resourceSilo.NtuCapacitor - originalAmount) + panelAnimationFunc = SkipPanelAnimation + } + } + + /** + * When charging from another source of nanites, the silo's panels will glow + * and a particle affect will traverse towards the panels from about ten meters in front of the silo. + * These effects are both controlled by thee same packet. + * @param trigger if positive, activate the animation; + * if negative or zero, disable the animation + */ + def PanelAnimation(trigger : Int) : Unit = { + val zone = resourceSilo.Zone + zone.VehicleEvents ! VehicleServiceMessage( + zone.Id, + VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, resourceSilo.GUID, 49, if(trigger > 0) 1 else 0) + ) // panel glow on & orb particles on + } + + /** + * Do nothing this turn. + */ + def SkipPanelAnimation(trigger : Int) : Unit = { } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloDefinition.scala index 9169153d..eda1b65a 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloDefinition.scala @@ -1,12 +1,15 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.resourcesilo +import net.psforever.objects.NtuContainerDefinition import net.psforever.objects.serverobject.structures.AmenityDefinition /** * The definition for any `Resource Silo`. * Object Id 731. */ -class ResourceSiloDefinition extends AmenityDefinition(731) { +class ResourceSiloDefinition extends AmenityDefinition(731) + with NtuContainerDefinition { Name = "resource_silo" + MaxNtuCapacitor = 1000 } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingDefinition.scala index df7e0ab5..b6e1a507 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingDefinition.scala @@ -1,10 +1,11 @@ package net.psforever.objects.serverobject.structures -import net.psforever.objects.SpawnPointDefinition +import net.psforever.objects.{NtuContainerDefinition, SpawnPointDefinition} import net.psforever.objects.definition.ObjectDefinition -class BuildingDefinition(objectId: Int) extends ObjectDefinition(objectId) with SphereOfInfluence { +class BuildingDefinition(objectId: Int) extends ObjectDefinition(objectId) with NtuContainerDefinition with SphereOfInfluence { Name = "building" + MaxNtuCapacitor = Int.MaxValue } class WarpGateDefinition(objectId: Int) extends BuildingDefinition(objectId) with SpawnPointDefinition diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala index 1e5328ed..d8174bb8 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala @@ -3,7 +3,7 @@ package net.psforever.objects.serverobject.structures import akka.actor.ActorContext import net.psforever.objects.serverobject.PlanetSideServerObject -import net.psforever.objects.{GlobalDefinitions, SpawnPoint} +import net.psforever.objects.{GlobalDefinitions, NtuContainer, SpawnPoint} import net.psforever.objects.zones.Zone import net.psforever.packet.game.{Additional1, Additional2, Additional3} import net.psforever.types.{PlanetSideEmpire, PlanetSideGeneratorState, Vector3} @@ -12,8 +12,8 @@ import scala.collection.mutable class WarpGate(name: String, building_guid: Int, map_id: Int, zone: Zone, buildingDefinition: WarpGateDefinition) extends Building(name, building_guid, map_id, zone, StructureType.WarpGate, buildingDefinition) + with NtuContainer with SpawnPoint { - /** can this building be used as an active warp gate */ private var active: Boolean = true @@ -166,8 +166,11 @@ class WarpGate(name: String, building_guid: Int, map_id: Int, zone: Zone, buildi def Owner: PlanetSideServerObject = this + def NtuCapacitor: Int = Definition.MaxNtuCapacitor + + def NtuCapacitor_=(value: Int): Int = NtuCapacitor + override def Definition: WarpGateDefinition = buildingDefinition - //TODO stuff later } object WarpGate { @@ -178,7 +181,7 @@ object WarpGate { def Structure(name: String, guid: Int, map_id: Int, zone: Zone, context: ActorContext): WarpGate = { import akka.actor.Props val obj = new WarpGate(name, guid, map_id, zone, GlobalDefinitions.warpgate) - obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$map_id-gate") + obj.Actor = context.actorOf(Props(classOf[WarpGateControl], obj), name = s"$map_id-$guid-gate") obj } @@ -188,7 +191,7 @@ object WarpGate { import akka.actor.Props val obj = new WarpGate(name, guid, map_id, zone, GlobalDefinitions.warpgate) obj.Position = location - obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$map_id-gate") + obj.Actor = context.actorOf(Props(classOf[WarpGateControl], obj), name = s"$map_id-$guid-gate") obj } @@ -199,7 +202,7 @@ object WarpGate { import akka.actor.Props val obj = new WarpGate(name, guid, map_id, zone, buildingDefinition) obj.Position = location - obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$map_id-gate") + obj.Actor = context.actorOf(Props(classOf[WarpGateControl], obj), name = s"$map_id-$guid-gate") obj } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGateControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGateControl.scala new file mode 100644 index 00000000..a2e5e78d --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGateControl.scala @@ -0,0 +1,38 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.serverobject.structures + +import akka.actor.ActorRef +import net.psforever.objects.{Ntu, NtuContainer, NtuStorageBehavior} + +class WarpGateControl(gate : WarpGate) extends BuildingControl(gate) + with NtuStorageBehavior { + override def receive : Receive = storageBehavior.orElse(super.receive) + + /** + * Warp gates don't need to respond to offers. + */ + def HandleNtuOffer(sender : ActorRef, src : NtuContainer) : Unit = {} + + /** + * Warp gates don't need to stop. + */ + def StopNtuBehavior(sender : ActorRef) : Unit = {} + + /** + * When processing a request, the only important consideration is whether the warp gate is active. + * @param sender na + * @param min a minimum amount of nanites requested; + * @param max the amount of nanites required to not make further requests; + */ + def HandleNtuRequest(sender : ActorRef, min : Int, max : Int) : Unit = { + sender ! Ntu.Grant(gate, if (gate.Active) min else 0) + } + + /** + * Warp gates doesn't need additional nanites. + * For the sake of not letting any go to waste, it will give back those nanites for free. + */ + def HandleNtuGrant(sender : ActorRef, src : NtuContainer, amount : Int) : Unit = { + sender ! Ntu.Grant(gate, amount) + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/transfer/TransferBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/transfer/TransferBehavior.scala new file mode 100644 index 00000000..e4e7125b --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/transfer/TransferBehavior.scala @@ -0,0 +1,108 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.serverobject.transfer + +import akka.actor.Actor + +trait TransferBehavior { + _ : Actor => + var transferEvent : TransferBehavior.Event.Value = TransferBehavior.Event.None + var transferTarget : Option[TransferContainer] = None + var findChargeTargetFunc : (TransferContainer, Option[TransferContainer])=> Option[TransferContainer] = TransferBehavior.FindNoTargets + var findDischargeTargetFunc : (TransferContainer, Option[TransferContainer])=> Option[TransferContainer] = TransferBehavior.FindNoTargets + + def TransferMaterial : TransferContainer.TransferMaterial + def ChargeTransferObject : TransferContainer + + val transferBehavior : Receive = { + case TransferBehavior.Charging(mat) if mat != TransferMaterial => + TryStopChargingEvent(ChargeTransferObject) + + case TransferBehavior.Charging(_) + if transferEvent == TransferBehavior.Event.None || transferEvent == TransferBehavior.Event.Charging => + TryChargingActivity() + + case TransferBehavior.Discharging(mat) if mat != TransferMaterial => + TryStopChargingEvent(ChargeTransferObject) + + case TransferBehavior.Discharging(_) + if transferEvent == TransferBehavior.Event.None || transferEvent == TransferBehavior.Event.Discharging => + TryDischargingActivity() + + case TransferBehavior.Stopping() => + TryStopChargingEvent(ChargeTransferObject) + } + + /* Charging */ + def TryChargingActivity() : Unit = { + if(transferEvent != TransferBehavior.Event.Discharging) { + val chargeable = ChargeTransferObject + findChargeTargetFunc(chargeable, transferTarget) match { + case Some(obj) => + HandleChargingEvent(obj) + case None if transferEvent == TransferBehavior.Event.Charging => + TryStopChargingEvent(chargeable) + case _ => ; + } + } + } + + def HandleChargingEvent(target : TransferContainer) : Boolean + + /* Discharging */ + def TryDischargingActivity() : Unit = { + if(transferEvent != TransferBehavior.Event.Charging) { + val chargeable = ChargeTransferObject + //determine how close we are to something that we can discharge into + findDischargeTargetFunc(chargeable, transferTarget) match { + case Some(obj) => + HandleDischargingEvent(obj) + case None if transferEvent == TransferBehavior.Event.Discharging => + TryStopChargingEvent(chargeable) + case _ => ; + } + } + } + + def HandleDischargingEvent(target : TransferContainer) : Boolean + + /* Stopping */ + def TryStopChargingEvent(container : TransferContainer) : Unit = { + transferEvent = TransferBehavior.Event.None + transferTarget match { + case Some(obj) => + obj.Actor ! TransferBehavior.Stopping() + case _ => ; + } + transferTarget = None + } +} + +object TransferBehavior { + object Event extends Enumeration { + val + None, + Charging, + Discharging + = Value + } + /** + * Message to cue a process of transferring into oneself. + */ + final case class Charging(transferMaterial : Any) + /** + * Message to cue a process of transferring from oneself. + */ + final case class Discharging(transferMaterial : Any) + /** + * Message to cue a stopping the transfer process. + */ + final case class Stopping() + + /** + * A default search function that does not actually search for anything or ever find anything. + * @param obj an entity designated as the "origin" + * @param optionalTarget an optional entity that can be one of the discovered targets + * @return always returns `None` + */ + def FindNoTargets(obj : TransferContainer, optionalTarget : Option[TransferContainer]) : Option[TransferContainer] = None +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/transfer/TransferContainer.scala b/common/src/main/scala/net/psforever/objects/serverobject/transfer/TransferContainer.scala new file mode 100644 index 00000000..1575e159 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/transfer/TransferContainer.scala @@ -0,0 +1,19 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.serverobject.transfer + +import akka.actor.ActorRef +import net.psforever.objects.entity.{Identifiable, WorldEntity} +import net.psforever.objects.zones.ZoneAware + +trait TransferContainer extends Identifiable + with ZoneAware + with WorldEntity { + def Actor : ActorRef +} + +object TransferContainer { + /** + * The kind of resource that gets transferred. + */ + trait TransferMaterial +} diff --git a/common/src/main/scala/net/psforever/objects/vehicles/AntTransferBehavior.scala b/common/src/main/scala/net/psforever/objects/vehicles/AntTransferBehavior.scala new file mode 100644 index 00000000..db68f39c --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vehicles/AntTransferBehavior.scala @@ -0,0 +1,197 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.vehicles + +import akka.actor.{ActorRef, Cancellable} +import net.psforever.objects.serverobject.deploy.Deployment +import net.psforever.objects.serverobject.resourcesilo.ResourceSilo +import net.psforever.objects.serverobject.structures.WarpGate +import net.psforever.objects.serverobject.transfer.{TransferBehavior, TransferContainer} +import net.psforever.objects.{NtuContainer, _} +import net.psforever.types.DriveState +import services.Service +import services.vehicle.{VehicleAction, VehicleServiceMessage} + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration._ + +trait AntTransferBehavior extends TransferBehavior + with NtuStorageBehavior { + var ntuChargingTick : Cancellable = Default.Cancellable + var panelAnimationFunc : ()=>Unit = NoCharge + + def TransferMaterial = Ntu.Nanites + def ChargeTransferObject : Vehicle with NtuContainer + + def antBehavior : Receive = storageBehavior.orElse(transferBehavior) + + def ActivatePanelsForChargingEvent(vehicle : NtuContainer) : Unit = { + val zone = vehicle.Zone + zone.VehicleEvents ! VehicleServiceMessage( + zone.Id, + VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vehicle.GUID, 52, 1L) + ) // panel glow on + } + + /** Charging */ + def StartNtuChargingEvent(vehicle : NtuContainer) : Unit = { + val zone = vehicle.Zone + zone.VehicleEvents ! VehicleServiceMessage( + zone.Id, + VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vehicle.GUID, 49, 1L) + ) // orb particle effect on + } + + def UpdateNtuUI(vehicle : Vehicle with NtuContainer) : Unit = { + if(vehicle.Seats.values.exists(_.isOccupied)) { + val display = scala.math.ceil(vehicle.NtuCapacitorScaled).toLong + vehicle.Zone.VehicleEvents ! VehicleServiceMessage( + vehicle.Actor.toString, + VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vehicle.GUID, 45, display) + ) + } + } + + def HandleChargingEvent(target : TransferContainer) : Boolean = { + ntuChargingTick.cancel + val obj = ChargeTransferObject + //log.trace(s"NtuCharging: Vehicle $guid is charging NTU capacitor.") + if(obj.NtuCapacitor < obj.Definition.MaxNtuCapacitor) { + //charging + panelAnimationFunc = InitialCharge + transferTarget = Some(target) + transferEvent = TransferBehavior.Event.Charging + val (min, max) = target match { + case _ : WarpGate => + //ANTs would charge from 0-100% in roughly 75s on live (https://www.youtube.com/watch?v=veOWToR2nSk&feature=youtu.be&t=1194) + val ntuMax = obj.Definition.MaxNtuCapacitor - obj.NtuCapacitor + val ntuMin = scala.math.min(obj.Definition.MaxNtuCapacitor/75, ntuMax) + (ntuMin, ntuMax) + case _ => + (0, 0) + } + target.Actor ! Ntu.Request(min, max) + ntuChargingTick = context.system.scheduler.scheduleOnce(delay = 1000 milliseconds, self, TransferBehavior.Charging(TransferMaterial)) // Repeat until fully charged, or minor delay + true + } + else { + // Fully charged + TryStopChargingEvent(obj) + false + } + } + + def ReceiveAndDepositUntilFull(vehicle : Vehicle, amount : Int) : Boolean = { + val isNotFull = (vehicle.NtuCapacitor += amount) < vehicle.Definition.MaxNtuCapacitor + UpdateNtuUI(vehicle) + isNotFull + } + + /** Discharging */ + def HandleDischargingEvent(target : TransferContainer) : Boolean = { + //log.trace(s"NtuDischarging: Vehicle $guid is discharging NTU into silo $silo_guid") + val obj = ChargeTransferObject + if(obj.NtuCapacitor > 0) { + panelAnimationFunc = InitialDischarge + transferTarget = Some(target) + transferEvent = TransferBehavior.Event.Discharging + target.Actor ! Ntu.Offer(obj) + ntuChargingTick.cancel + ntuChargingTick = context.system.scheduler.scheduleOnce(delay = 1000 milliseconds, self, TransferBehavior.Discharging(TransferMaterial)) + true + } + else { + TryStopChargingEvent(obj) + false + } + } + + def NoCharge() : Unit = {} + + def InitialCharge() : Unit = { + panelAnimationFunc = NoCharge + val obj = ChargeTransferObject + ActivatePanelsForChargingEvent(obj) + StartNtuChargingEvent(obj) + } + + def InitialDischarge() : Unit = { + panelAnimationFunc = NoCharge + ActivatePanelsForChargingEvent(ChargeTransferObject) + } + + def WithdrawAndTransmit(vehicle : Vehicle, maxRequested : Int) : Any = { + val chargeable = ChargeTransferObject + var chargeToDeposit = Math.min(Math.min(chargeable.NtuCapacitor, 100), maxRequested) + chargeable.NtuCapacitor -= chargeToDeposit + UpdateNtuUI(chargeable) + Ntu.Grant(chargeable, chargeToDeposit) + } + + /** Stopping */ + override def TryStopChargingEvent(container : TransferContainer) : Unit = { + val vehicle = ChargeTransferObject + ntuChargingTick.cancel + if(transferEvent != TransferBehavior.Event.None) { + if(vehicle.DeploymentState == DriveState.Deployed) { + //turning off glow/orb effects on ANT doesn't seem to work when deployed. Try to undeploy ANT first + vehicle.Actor ! Deployment.TryUndeploy(DriveState.Undeploying) + ntuChargingTick = context.system.scheduler.scheduleOnce(250 milliseconds, self, TransferBehavior.Stopping()) + } + else { + //vehicle is not deployed; just do cleanup + val vguid = vehicle.GUID + val zone = vehicle.Zone + val zoneId = zone.Id + val events = zone.VehicleEvents + if(transferEvent == TransferBehavior.Event.Charging) { + events ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vguid, 52, 0L)) // panel glow off + events ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vguid, 49, 0L)) // orb particle effect off + } + else if(transferEvent == TransferBehavior.Event.Discharging) { + events ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vguid, 52, 0L)) // panel glow off + } + } + panelAnimationFunc = NoCharge + super.TryStopChargingEvent(vehicle) + } + } + + def StopNtuBehavior(sender : ActorRef) : Unit = TryStopChargingEvent(ChargeTransferObject) + + def HandleNtuOffer(sender : ActorRef, src : NtuContainer) : Unit = { } + + def HandleNtuRequest(sender : ActorRef, min : Int, max : Int) : Unit = { + if(transferEvent == TransferBehavior.Event.Discharging) { + val chargeable = ChargeTransferObject + val chargeToDeposit = if(min == 0) { + transferTarget match { + case Some(silo : ResourceSilo) => + // Silos would charge from 0-100% in roughly 105s on live (~20%-100% https://youtu.be/veOWToR2nSk?t=1402) + scala.math.min(scala.math.min(silo.MaxNtuCapacitor / 105, chargeable.NtuCapacitor), max) + case _ => + 0 + } + } + else { + scala.math.min(min, chargeable.NtuCapacitor) + } +// var chargeToDeposit = Math.min(Math.min(chargeable.NtuCapacitor, 100), max) + chargeable.NtuCapacitor -= chargeToDeposit + UpdateNtuUI(chargeable) + sender ! Ntu.Grant(chargeable, chargeToDeposit) + } + } + + def HandleNtuGrant(sender : ActorRef, src : NtuContainer, amount : Int) : Unit = { + if(transferEvent == TransferBehavior.Event.Charging) { + val obj = ChargeTransferObject + if(ReceiveAndDepositUntilFull(obj, amount)) { + panelAnimationFunc() + } + else { + TryStopChargingEvent(obj) + sender ! Ntu.Request(0, 0) + } + } + } +} diff --git a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala index 879b52ad..4afe50fb 100644 --- a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala +++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala @@ -4,6 +4,7 @@ package net.psforever.objects.vehicles import akka.actor.{Actor, Cancellable} import net.psforever.objects._ import net.psforever.objects.ballistics.{ResolvedProjectile, VehicleSource} +import net.psforever.objects.ce.TelepadLike import net.psforever.objects.equipment.{Equipment, EquipmentSlot, JammableMountedWeapons} import net.psforever.objects.inventory.{GridInventory, InventoryItem} import net.psforever.objects.serverobject.CommonMessages @@ -11,18 +12,21 @@ import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior} import net.psforever.objects.serverobject.damage.DamageableVehicle -import net.psforever.objects.serverobject.deploy.DeploymentBehavior +import net.psforever.objects.serverobject.deploy.Deployment.DeploymentObject +import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior} import net.psforever.objects.serverobject.hackable.GenericHackables +import net.psforever.objects.serverobject.transfer.TransferBehavior import net.psforever.objects.serverobject.repair.RepairableVehicle import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.vital.VehicleShieldCharge import net.psforever.objects.zones.Zone -import net.psforever.types._ import services.RemoverActor import net.psforever.packet.game._ import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent +import net.psforever.types.{DriveState, ExoSuitType, PlanetSideGUID, Vector3} import services.Service import services.avatar.{AvatarAction, AvatarServiceMessage} +import services.local.{LocalAction, LocalServiceMessage} import services.vehicle.{VehicleAction, VehicleServiceMessage} import scala.concurrent.ExecutionContext.Implicits.global @@ -45,7 +49,8 @@ class VehicleControl(vehicle: Vehicle) with DamageableVehicle with RepairableVehicle with JammableMountedWeapons - with ContainableBehavior { + with ContainableBehavior + with AntTransferBehavior { //make control actors belonging to utilities when making control actor belonging to vehicle vehicle.Utilities.foreach({ case (_, util) => util.Setup }) @@ -58,6 +63,11 @@ class VehicleControl(vehicle: Vehicle) def DamageableObject = vehicle def RepairableObject = vehicle def ContainerObject = vehicle + def ChargeTransferObject = vehicle + if(vehicle.Definition == GlobalDefinitions.ant) { + findChargeTargetFunc = Vehicles.FindANTChargingSource + findDischargeTargetFunc = Vehicles.FindANTDischargingTarget + } /** cheap flag for whether the vehicle is decaying */ var decaying: Boolean = false @@ -85,6 +95,7 @@ class VehicleControl(vehicle: Vehicle) .orElse(takesDamage) .orElse(canBeRepairedByNanoDispenser) .orElse(containerBehavior) + .orElse(antBehavior) .orElse { case Vehicle.Ownership(None) => LoseOwnership() @@ -268,6 +279,8 @@ class VehicleControl(vehicle: Vehicle) val zone = vehicle.Zone val zoneId = zone.Id val events = zone.VehicleEvents + //miscellaneous changes + Vehicles.BeforeUnloadVehicle(vehicle, zone) //become disabled context.become(Disabled) //cancel jammed behavior @@ -316,23 +329,21 @@ class VehicleControl(vehicle: Vehicle) events ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, zone, Some(0 seconds))) //banished to the shadow realm vehicle.Position = Vector3.Zero - vehicle.DeploymentState = DriveState.Mobile //queue final deletion decayTimer = context.system.scheduler.scheduleOnce(5 seconds, self, VehicleControl.Deletion()) } - def Disabled: Receive = - checkBehavior - .orElse { - case VehicleControl.Deletion() => - val zone = vehicle.Zone - zone.VehicleEvents ! VehicleServiceMessage( - zone.Id, - VehicleAction.UnloadVehicle(Service.defaultPlayerGUID, zone, vehicle, vehicle.GUID) - ) - zone.Transport ! Zone.Vehicle.Despawn(vehicle) - case _ => - } + def Disabled : 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, zone, vehicle, vehicle.GUID)) + zone.Transport ! Zone.Vehicle.Despawn(vehicle) + case _ => + } override def TryJammerEffectActivate(target: Any, cause: ResolvedProjectile): Unit = { if (vehicle.MountedIn.isEmpty) { @@ -434,6 +445,110 @@ class VehicleControl(vehicle: Vehicle) VehicleAction.SendResponse(Service.defaultPlayerGUID, ObjectDetachMessage(obj.GUID, item.GUID, Vector3.Zero, 0f)) ) } + + 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.Active = true + case _ => + //log.warn(s"DeploymentActivities: could not find internal telepad in router@${vehicle.GUID.guid} while $state") + } + case DriveState.Deployed => + //let the timer do all the work + events ! LocalServiceMessage(zoneChannel, LocalAction.ToggleTeleportSystem(GUID0, vehicle, TelepadLike.AppraiseTeleportationSystem(vehicle, zone))) + 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 + Vehicles.RemoveTelepads(vehicle) + zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.ToggleTeleportSystem(GUID0, vehicle, None)) + case _ => ; + } + } + case _ => ; + } + out + } } object VehicleControl { @@ -459,4 +574,8 @@ object VehicleControl { case _ => false } } + + def DeploymentAngleCheck(obj : Deployment.DeploymentObject) : Boolean = { + obj.Orientation.x <= 30 || obj.Orientation.x >= 330 + } } diff --git a/common/src/main/scala/net/psforever/types/PlanetSideEmpire.scala b/common/src/main/scala/net/psforever/types/PlanetSideEmpire.scala index d9858cc4..7391c390 100644 --- a/common/src/main/scala/net/psforever/types/PlanetSideEmpire.scala +++ b/common/src/main/scala/net/psforever/types/PlanetSideEmpire.scala @@ -14,7 +14,7 @@ object PlanetSideEmpire extends Enumeration { implicit val codec = PacketHelpers.createEnumerationCodec(this, uint2L) def apply(id: String): PlanetSideEmpire.Value = { - values.find(_.toString.equals(id)) match { + values.find(_.toString.equalsIgnoreCase(id)) match { case Some(faction) => faction case None => diff --git a/common/src/main/scala/services/vehicle/VehicleService.scala b/common/src/main/scala/services/vehicle/VehicleService.scala index 687f965c..57f452bd 100644 --- a/common/src/main/scala/services/vehicle/VehicleService.scala +++ b/common/src/main/scala/services/vehicle/VehicleService.scala @@ -2,22 +2,18 @@ package services.vehicle import akka.actor.{Actor, ActorRef, Props} -import net.psforever.objects.{GlobalDefinitions, TelepadDeployable, Vehicle} +import net.psforever.objects.Vehicle import net.psforever.objects.ballistics.VehicleSource import net.psforever.objects.serverobject.pad.VehicleSpawnPad import net.psforever.objects.serverobject.terminals.{MedicalTerminalDefinition, ProximityUnit} -import net.psforever.objects.vehicles.{Utility, UtilityType} import net.psforever.objects.vital.RepairFromTerm import net.psforever.objects.zones.Zone import net.psforever.packet.game.ObjectCreateMessage import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent import services.vehicle.support.{TurretUpgrader, VehicleRemover} import net.psforever.types.{DriveState, PlanetSideGUID} -import services.local.LocalServiceMessage import services.{GenericEventBus, RemoverActor, Service} -import scala.concurrent.duration._ - class VehicleService(zone: Zone) extends Actor { private val vehicleDecon: ActorRef = context.actorOf(Props[VehicleRemover], s"${zone.Id}-vehicle-decon-agent") private val turretUpgrade: ActorRef = context.actorOf(Props[TurretUpgrader], s"${zone.Id}-turret-upgrade-agent") @@ -404,37 +400,3 @@ class VehicleService(zone: Zone) extends Actor { .map(util => util().asInstanceOf[SpawnTube]) } } - -object VehicleService { - - /** - * Before a vehicle is removed from the game world, the following actions must be performed. - * @param vehicle the vehicle - */ - def BeforeUnloadVehicle(vehicle: Vehicle, zone: Zone): Unit = { - vehicle.Definition match { - case GlobalDefinitions.ams => - zone.VehicleEvents ! VehicleServiceMessage.AMSDeploymentChange(zone) - case GlobalDefinitions.router => - RemoveTelepads(vehicle, zone) - case _ => ; - } - } - - def RemoveTelepads(vehicle: Vehicle, zone: Zone): Unit = { - (vehicle.Utility(UtilityType.internal_router_telepad_deployable) match { - case Some(util: Utility.InternalTelepad) => - val telepad = util.Telepad - util.Telepad = None - zone.GUID(telepad) - case _ => - None - }) match { - case Some(telepad: TelepadDeployable) => - telepad.Active = false - zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(telepad), zone)) - zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(telepad, zone, Some(0 seconds))) - case _ => ; - } - } -} diff --git a/common/src/test/scala/CosmeticsTest.scala b/common/src/test/scala/CosmeticsTest.scala index a47c6bbf..634d1e56 100644 --- a/common/src/test/scala/CosmeticsTest.scala +++ b/common/src/test/scala/CosmeticsTest.scala @@ -1,7 +1,6 @@ // Copyright (c) 2019 PSForever import net.psforever.packet.game.objectcreate.{Cosmetics, PersonalStyle} import org.specs2.mutable._ -import net.psforever.types.Vector3 class CosmeticsTest extends Specification { "Cosmetics" should { diff --git a/common/src/test/scala/CryptoPacketTest.scala b/common/src/test/scala/CryptoPacketTest.scala index 767e8ebd..9f298364 100644 --- a/common/src/test/scala/CryptoPacketTest.scala +++ b/common/src/test/scala/CryptoPacketTest.scala @@ -1,6 +1,5 @@ // Copyright (c) 2017 PSForever import org.specs2.mutable._ -import net.psforever.packet._ import net.psforever.packet.control.{ClientStart, ServerStart} import net.psforever.packet.crypto.{ClientChallengeXchg, ClientFinished, ServerChallengeXchg, ServerFinished} import scodec.Codec diff --git a/common/src/test/scala/game/ActionProgressMessageTest.scala b/common/src/test/scala/game/ActionProgressMessageTest.scala index acbf7eb5..30d05b2b 100644 --- a/common/src/test/scala/game/ActionProgressMessageTest.scala +++ b/common/src/test/scala/game/ActionProgressMessageTest.scala @@ -4,7 +4,6 @@ package game import org.specs2.mutable._ import net.psforever.packet._ import net.psforever.packet.game._ -import net.psforever.types.ExoSuitType import scodec.bits._ class ActionProgressMessageTest extends Specification { diff --git a/common/src/test/scala/game/CharacterRequestMessageTest.scala b/common/src/test/scala/game/CharacterRequestMessageTest.scala index 599ec230..c6ba3fbd 100644 --- a/common/src/test/scala/game/CharacterRequestMessageTest.scala +++ b/common/src/test/scala/game/CharacterRequestMessageTest.scala @@ -4,7 +4,6 @@ package game import org.specs2.mutable._ import net.psforever.packet._ import net.psforever.packet.game._ -import net.psforever.types.{CharacterGender, PlanetSideEmpire} import scodec.bits._ class CharacterRequestMessageTest extends Specification { diff --git a/common/src/test/scala/game/DamageWithPositionMessageTest.scala b/common/src/test/scala/game/DamageWithPositionMessageTest.scala index 8da7c35b..807ca629 100644 --- a/common/src/test/scala/game/DamageWithPositionMessageTest.scala +++ b/common/src/test/scala/game/DamageWithPositionMessageTest.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package game -import net.psforever.types.{MeritCommendation, Vector3} +import net.psforever.types.Vector3 import org.specs2.mutable._ import net.psforever.packet._ import net.psforever.packet.game._ diff --git a/common/src/test/scala/game/HotSpotUpdateMessageTest.scala b/common/src/test/scala/game/HotSpotUpdateMessageTest.scala index 2e9fb838..f07413fa 100644 --- a/common/src/test/scala/game/HotSpotUpdateMessageTest.scala +++ b/common/src/test/scala/game/HotSpotUpdateMessageTest.scala @@ -4,7 +4,6 @@ package game import org.specs2.mutable._ import net.psforever.packet._ import net.psforever.packet.game._ -import net.psforever.types.Vector3 import scodec.bits._ class HotSpotUpdateMessageTest extends Specification { diff --git a/common/src/test/scala/game/ZoneForcedCavernConnectionsMessageTest.scala b/common/src/test/scala/game/ZoneForcedCavernConnectionsMessageTest.scala index a31736ae..29a364ea 100644 --- a/common/src/test/scala/game/ZoneForcedCavernConnectionsMessageTest.scala +++ b/common/src/test/scala/game/ZoneForcedCavernConnectionsMessageTest.scala @@ -4,7 +4,6 @@ package game import org.specs2.mutable._ import net.psforever.packet._ import net.psforever.packet.game._ -import net.psforever.types.Vector3 import scodec.bits._ class ZoneForcedCavernConnectionsMessageTest extends Specification { diff --git a/common/src/test/scala/game/objectcreatedetailed/DetailedAmmoBoxDataTest.scala b/common/src/test/scala/game/objectcreatedetailed/DetailedAmmoBoxDataTest.scala index b16e88ed..5ea0a5f0 100644 --- a/common/src/test/scala/game/objectcreatedetailed/DetailedAmmoBoxDataTest.scala +++ b/common/src/test/scala/game/objectcreatedetailed/DetailedAmmoBoxDataTest.scala @@ -3,7 +3,7 @@ package game.objectcreatedetailed import org.specs2.mutable._ import net.psforever.packet._ -import net.psforever.packet.game.{ObjectCreateDetailedMessage, _} +import net.psforever.packet.game.ObjectCreateDetailedMessage import net.psforever.packet.game.objectcreate._ import net.psforever.types.PlanetSideGUID import scodec.bits._ diff --git a/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala b/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala index c21844d3..26429aa9 100644 --- a/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala +++ b/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala @@ -3,7 +3,7 @@ package game.objectcreatedetailed import org.specs2.mutable._ import net.psforever.packet._ -import net.psforever.packet.game.{ObjectCreateDetailedMessage, _} +import net.psforever.packet.game.ObjectCreateDetailedMessage import net.psforever.packet.game.objectcreate._ import net.psforever.types._ import scodec.bits._ diff --git a/common/src/test/scala/game/objectcreatedetailed/DetailedCommandDetonaterDataTest.scala b/common/src/test/scala/game/objectcreatedetailed/DetailedCommandDetonaterDataTest.scala index 6fa78c7e..239d927e 100644 --- a/common/src/test/scala/game/objectcreatedetailed/DetailedCommandDetonaterDataTest.scala +++ b/common/src/test/scala/game/objectcreatedetailed/DetailedCommandDetonaterDataTest.scala @@ -3,7 +3,7 @@ package game.objectcreatedetailed import org.specs2.mutable._ import net.psforever.packet._ -import net.psforever.packet.game.{ObjectCreateDetailedMessage, _} +import net.psforever.packet.game.ObjectCreateDetailedMessage import net.psforever.packet.game.objectcreate._ import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID} import scodec.bits._ diff --git a/common/src/test/scala/game/objectcreatedetailed/DetailedREKDataTest.scala b/common/src/test/scala/game/objectcreatedetailed/DetailedREKDataTest.scala index 856fe4e6..1e0f5c9b 100644 --- a/common/src/test/scala/game/objectcreatedetailed/DetailedREKDataTest.scala +++ b/common/src/test/scala/game/objectcreatedetailed/DetailedREKDataTest.scala @@ -3,7 +3,7 @@ package game.objectcreatedetailed import org.specs2.mutable._ import net.psforever.packet._ -import net.psforever.packet.game.{ObjectCreateDetailedMessage, _} +import net.psforever.packet.game.ObjectCreateDetailedMessage import net.psforever.packet.game.objectcreate._ import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID} import scodec.bits._ diff --git a/common/src/test/scala/game/objectcreatedetailed/DetailedWeaponDataTest.scala b/common/src/test/scala/game/objectcreatedetailed/DetailedWeaponDataTest.scala index 79d35e0f..c00ba37c 100644 --- a/common/src/test/scala/game/objectcreatedetailed/DetailedWeaponDataTest.scala +++ b/common/src/test/scala/game/objectcreatedetailed/DetailedWeaponDataTest.scala @@ -3,7 +3,7 @@ package game.objectcreatedetailed import org.specs2.mutable._ import net.psforever.packet._ -import net.psforever.packet.game.{ObjectCreateDetailedMessage, _} +import net.psforever.packet.game.ObjectCreateDetailedMessage import net.psforever.packet.game.objectcreate._ import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID} import scodec.bits._ diff --git a/common/src/test/scala/objects/DeployableTest.scala b/common/src/test/scala/objects/DeployableTest.scala index 416e73ea..cacdf0d6 100644 --- a/common/src/test/scala/objects/DeployableTest.scala +++ b/common/src/test/scala/objects/DeployableTest.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package objects -import akka.actor.{Actor, ActorRef, Props} +import akka.actor.{Actor, Props} import akka.testkit.TestProbe import base.ActorTest import net.psforever.objects.ballistics._ diff --git a/common/src/test/scala/objects/DeploymentTest.scala b/common/src/test/scala/objects/DeploymentTest.scala index dad0d74a..1a5b95cf 100644 --- a/common/src/test/scala/objects/DeploymentTest.scala +++ b/common/src/test/scala/objects/DeploymentTest.scala @@ -2,12 +2,15 @@ package objects import akka.actor.{Actor, ActorRef, ActorSystem, Props} +import akka.testkit.TestProbe import base.ActorTest import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.{GlobalDefinitions, Vehicle} import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior} -import net.psforever.types.{DriveState, PlanetSideEmpire} +import net.psforever.objects.zones.{Zone, ZoneMap} +import net.psforever.types.{DriveState, PlanetSideEmpire, PlanetSideGUID, Vector3} import org.specs2.mutable.Specification +import services.vehicle.{VehicleAction, VehicleServiceMessage} import scala.concurrent.duration.Duration @@ -54,33 +57,50 @@ class DeploymentBehavior1Test extends ActorTest { class DeploymentBehavior2Test extends ActorTest { "Deployment" should { - "change following a deployment cycle using TryDeployChange" in { + "change following a deployment cycle using TryDeploymentChange" in { val obj = DeploymentTest.SetUpAgent + val probe = new TestProbe(system) + val eventsProbe = new TestProbe(system) + obj.Zone.VehicleEvents = eventsProbe.ref assert(obj.DeploymentState == DriveState.Mobile) //to Deploying - obj.Actor ! Deployment.TryDeploymentChange(DriveState.Deploying) - val reply1 = receiveOne(Duration.create(100, "ms")) - assert(reply1.isInstanceOf[Deployment.CanDeploy]) - assert(reply1.asInstanceOf[Deployment.CanDeploy].obj == obj) - assert(reply1.asInstanceOf[Deployment.CanDeploy].state == DriveState.Deploying) + obj.Actor.tell(Deployment.TryDeploymentChange(DriveState.Deploying), probe.ref) + val reply1a = probe.receiveOne(Duration.create(500, "ms")) + assert(reply1a match { + case Deployment.CanDeploy(_, DriveState.Deploying) => true + case _ => false + }) + val reply1b = eventsProbe.receiveOne(Duration.create(500, "ms")) + assert(reply1b match { + case VehicleServiceMessage("test", VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Deploying, 0, false, Vector3.Zero)) => true + case _ => false + }) //to Deployed - obj.Actor ! Deployment.TryDeploymentChange(DriveState.Deployed) - val reply2 = receiveOne(Duration.create(100, "ms")) - assert(reply2.isInstanceOf[Deployment.CanDeploy]) - assert(reply2.asInstanceOf[Deployment.CanDeploy].obj == obj) - assert(reply2.asInstanceOf[Deployment.CanDeploy].state == DriveState.Deployed) - //to Deployed - obj.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying) - val reply3 = receiveOne(Duration.create(100, "ms")) - assert(reply3.isInstanceOf[Deployment.CanUndeploy]) - assert(reply3.asInstanceOf[Deployment.CanUndeploy].obj == obj) - assert(reply3.asInstanceOf[Deployment.CanUndeploy].state == DriveState.Undeploying) - //to Deployed - obj.Actor ! Deployment.TryDeploymentChange(DriveState.Mobile) - val reply4 = receiveOne(Duration.create(100, "ms")) - assert(reply4.isInstanceOf[Deployment.CanUndeploy]) - assert(reply4.asInstanceOf[Deployment.CanUndeploy].obj == obj) - assert(reply4.asInstanceOf[Deployment.CanUndeploy].state == DriveState.Mobile) + val reply2 = eventsProbe.receiveOne(Duration.create(500, "ms")) + assert(reply2 match { + case VehicleServiceMessage("test", VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Deployed, 0, false, Vector3.Zero)) => true + case _ => false + }) + assert(obj.DeploymentState == DriveState.Deployed) + //to Undeploying + obj.Actor.tell(Deployment.TryDeploymentChange(DriveState.Undeploying), probe.ref) + val reply3a = probe.receiveOne(Duration.create(500, "ms")) + assert(reply3a match { + case Deployment.CanUndeploy(_, DriveState.Undeploying) => true + case _ => false + }) + val reply3b = eventsProbe.receiveOne(Duration.create(500, "ms")) + assert(reply3b match { + case VehicleServiceMessage("test", VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Undeploying, 0, false, Vector3.Zero)) => true + case _ => false + }) + //to Mobile + val reply4 = eventsProbe.receiveOne(Duration.create(500, "ms")) + assert(reply4 match { + case VehicleServiceMessage("test", VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Mobile, 0, false, Vector3.Zero)) => true + case _ => false + }) + assert(obj.DeploymentState == DriveState.Mobile) } } } @@ -89,31 +109,48 @@ class DeploymentBehavior3Test extends ActorTest { "Deployment" should { "change following a deployment cycle using TryDeploy and TryUndeploy" in { val obj = DeploymentTest.SetUpAgent + val probe = new TestProbe(system) + val eventsProbe = new TestProbe(system) + obj.Zone.VehicleEvents = eventsProbe.ref assert(obj.DeploymentState == DriveState.Mobile) //to Deploying - obj.Actor ! Deployment.TryDeploy(DriveState.Deploying) - val reply1 = receiveOne(Duration.create(100, "ms")) - assert(reply1.isInstanceOf[Deployment.CanDeploy]) - assert(reply1.asInstanceOf[Deployment.CanDeploy].obj == obj) - assert(reply1.asInstanceOf[Deployment.CanDeploy].state == DriveState.Deploying) + obj.Actor.tell(Deployment.TryDeploy(DriveState.Deploying), probe.ref) + val reply1a = probe.receiveOne(Duration.create(500, "ms")) + assert(reply1a match { + case Deployment.CanDeploy(_, DriveState.Deploying) => true + case _ => false + }) + val reply1b = eventsProbe.receiveOne(Duration.create(500, "ms")) + assert(reply1b match { + case VehicleServiceMessage("test", VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Deploying, 0, false, Vector3.Zero)) => true + case _ => false + }) //to Deployed - obj.Actor ! Deployment.TryDeploy(DriveState.Deployed) - val reply2 = receiveOne(Duration.create(100, "ms")) - assert(reply2.isInstanceOf[Deployment.CanDeploy]) - assert(reply2.asInstanceOf[Deployment.CanDeploy].obj == obj) - assert(reply2.asInstanceOf[Deployment.CanDeploy].state == DriveState.Deployed) - //to Deployed - obj.Actor ! Deployment.TryUndeploy(DriveState.Undeploying) - val reply3 = receiveOne(Duration.create(100, "ms")) - assert(reply3.isInstanceOf[Deployment.CanUndeploy]) - assert(reply3.asInstanceOf[Deployment.CanUndeploy].obj == obj) - assert(reply3.asInstanceOf[Deployment.CanUndeploy].state == DriveState.Undeploying) - //to Deployed - obj.Actor ! Deployment.TryUndeploy(DriveState.Mobile) - val reply4 = receiveOne(Duration.create(100, "ms")) - assert(reply4.isInstanceOf[Deployment.CanUndeploy]) - assert(reply4.asInstanceOf[Deployment.CanUndeploy].obj == obj) - assert(reply4.asInstanceOf[Deployment.CanUndeploy].state == DriveState.Mobile) + val reply2 = eventsProbe.receiveOne(Duration.create(500, "ms")) + assert(reply2 match { + case VehicleServiceMessage("test", VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Deployed, 0, false, Vector3.Zero)) => true + case _ => false + }) + assert(obj.DeploymentState == DriveState.Deployed) + //to Undeploying + obj.Actor.tell(Deployment.TryUndeploy(DriveState.Undeploying), probe.ref) + val reply3a = probe.receiveOne(Duration.create(500, "ms")) + assert(reply3a match { + case Deployment.CanUndeploy(_, DriveState.Undeploying) => true + case _ => false + }) + val reply3b = eventsProbe.receiveOne(Duration.create(500, "ms")) + assert(reply3b match { + case VehicleServiceMessage("test", VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Undeploying, 0, false, Vector3.Zero)) => true + case _ => false + }) + //to Mobile + val reply4 = eventsProbe.receiveOne(Duration.create(500, "ms")) + assert(reply4 match { + case VehicleServiceMessage("test", VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Mobile, 0, false, Vector3.Zero)) => true + case _ => false + }) + assert(obj.DeploymentState == DriveState.Mobile) } } } @@ -191,6 +228,8 @@ object DeploymentTest { def SetUpAgent(implicit system: ActorSystem) = { val obj = new DeploymentObject() + obj.GUID = PlanetSideGUID(1) + obj.Zone = Zone("test", new ZoneMap("test"),1) obj.Actor = system.actorOf(Props(classOf[DeploymentControl], obj), "test") obj } diff --git a/common/src/test/scala/objects/EquipmentTest.scala b/common/src/test/scala/objects/EquipmentTest.scala index 2cdad93d..f4b44983 100644 --- a/common/src/test/scala/objects/EquipmentTest.scala +++ b/common/src/test/scala/objects/EquipmentTest.scala @@ -5,7 +5,7 @@ import net.psforever.objects._ import net.psforever.objects.equipment._ import net.psforever.objects.inventory.InventoryTile import net.psforever.objects.GlobalDefinitions._ -import net.psforever.objects.ce.{DeployedItem, TelepadLike} +import net.psforever.objects.ce.DeployedItem import net.psforever.objects.definition._ import net.psforever.types.{CertificationType, PlanetSideGUID} import org.specs2.mutable._ diff --git a/common/src/test/scala/objects/ExoSuitTest.scala b/common/src/test/scala/objects/ExoSuitTest.scala index 2e2f13c6..a650da86 100644 --- a/common/src/test/scala/objects/ExoSuitTest.scala +++ b/common/src/test/scala/objects/ExoSuitTest.scala @@ -1,7 +1,6 @@ // Copyright (c) 2017 PSForever package objects -import net.psforever.objects.GlobalDefinitions import net.psforever.objects.definition.{ExoSuitDefinition, SpecialExoSuitDefinition} import net.psforever.objects.equipment._ import net.psforever.objects.inventory.InventoryTile diff --git a/common/src/test/scala/objects/ResourceSiloTest.scala b/common/src/test/scala/objects/ResourceSiloTest.scala index 8ababc82..76134f01 100644 --- a/common/src/test/scala/objects/ResourceSiloTest.scala +++ b/common/src/test/scala/objects/ResourceSiloTest.scala @@ -7,9 +7,11 @@ import akka.testkit.TestProbe import base.ActorTest import net.psforever.objects.guid.{NumberPoolHub, TaskResolver} import net.psforever.objects.guid.source.LimitedNumberSource -import net.psforever.objects.{Avatar, GlobalDefinitions, Player, Vehicle} +import net.psforever.objects.serverobject.CommonMessages +import net.psforever.objects.{Avatar, GlobalDefinitions, Ntu, Player, Vehicle} import net.psforever.objects.serverobject.resourcesilo.{ResourceSilo, ResourceSiloControl, ResourceSiloDefinition} import net.psforever.objects.serverobject.structures.{Building, StructureType} +import net.psforever.objects.serverobject.transfer.TransferBehavior import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap} import net.psforever.packet.game.UseItemMessage import net.psforever.types._ @@ -29,27 +31,27 @@ class ResourceSiloTest extends Specification { "construct" in { val obj = ResourceSilo() obj.Definition mustEqual GlobalDefinitions.resource_silo - obj.MaximumCharge mustEqual 1000 - obj.ChargeLevel mustEqual 0 + obj.MaxNtuCapacitor mustEqual 1000 + obj.NtuCapacitor mustEqual 0 obj.LowNtuWarningOn mustEqual true obj.CapacitorDisplay mustEqual 0 // - obj.ChargeLevel = 50 + obj.NtuCapacitor = 50 obj.LowNtuWarningOn = false - obj.ChargeLevel mustEqual 50 + obj.NtuCapacitor mustEqual 50 obj.LowNtuWarningOn mustEqual false obj.CapacitorDisplay mustEqual 1 } "charge level can not exceed limits(0 to maximum)" in { val obj = ResourceSilo() - obj.ChargeLevel mustEqual 0 - obj.ChargeLevel = -5 - obj.ChargeLevel mustEqual 0 + obj.NtuCapacitor mustEqual 0 + obj.NtuCapacitor = -5 + obj.NtuCapacitor mustEqual 0 - obj.ChargeLevel = obj.MaximumCharge + 100 - obj.ChargeLevel mustEqual 1000 - obj.ChargeLevel mustEqual obj.MaximumCharge + obj.NtuCapacitor = obj.MaxNtuCapacitor + 100 + obj.NtuCapacitor mustEqual 1000 + obj.NtuCapacitor mustEqual obj.MaxNtuCapacitor } "using the silo generates a charge event" in { @@ -115,6 +117,7 @@ class ResourceSiloControlUseTest extends ActorTest { new Avatar(0L, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute) ) //guid=3 val vehicle = Vehicle(GlobalDefinitions.ant) //guid=4 + val probe = new TestProbe(system) guid.register(building, 1) guid.register(obj, 2) @@ -124,31 +127,20 @@ class ResourceSiloControlUseTest extends ActorTest { zone.Transport ! Zone.Vehicle.Spawn(vehicle) vehicle.Seats(0).Occupant = player player.VehicleSeated = vehicle.GUID - val msg = UseItemMessage( - PlanetSideGUID(1), - PlanetSideGUID(0), - PlanetSideGUID(2), - 0L, - false, - Vector3.Zero, - Vector3.Zero, - 0, - 0, - 0, - 0L - ) //faked expectNoMessage(200 milliseconds) + system.stop(vehicle.Actor) + vehicle.Actor = probe.ref "Resource silo" should { "respond when being used" in { expectNoMessage(1 seconds) - obj.Actor ! ResourceSilo.Use(ResourceSiloTest.player, msg) + obj.Actor ! CommonMessages.Use(ResourceSiloTest.player) - val reply = receiveOne(500 milliseconds) - assert(reply.isInstanceOf[ResourceSilo.ResourceSiloMessage]) - assert(reply.asInstanceOf[ResourceSilo.ResourceSiloMessage].player == ResourceSiloTest.player) - assert(reply.asInstanceOf[ResourceSilo.ResourceSiloMessage].msg == msg) - assert(reply.asInstanceOf[ResourceSilo.ResourceSiloMessage].response == ResourceSilo.ChargeEvent()) + val reply = probe.receiveOne(2000 milliseconds) + assert(reply match { + case TransferBehavior.Discharging(Ntu.Nanites) => true + case _ => false + }) } } } @@ -218,93 +210,33 @@ class ResourceSiloControlUpdate1Test extends ActorTest { zone.AvatarEvents = zoneEvents.ref bldg.Actor = buildingEvents.ref - assert(obj.ChargeLevel == 0) + assert(obj.NtuCapacitor == 0) assert(obj.CapacitorDisplay == 0) + assert(obj.LowNtuWarningOn) obj.Actor ! ResourceSilo.UpdateChargeLevel(305) val reply1 = zoneEvents.receiveOne(500 milliseconds) val reply2 = buildingEvents.receiveOne(500 milliseconds) - assert(obj.ChargeLevel == 305) + assert(obj.NtuCapacitor == 305) assert(obj.CapacitorDisplay == 4) - assert(reply1.isInstanceOf[AvatarServiceMessage]) - assert(reply1.asInstanceOf[AvatarServiceMessage].forChannel == "nowhere") - assert(reply1.asInstanceOf[AvatarServiceMessage].actionMessage.isInstanceOf[AvatarAction.PlanetsideAttribute]) - assert( - reply1 - .asInstanceOf[AvatarServiceMessage] - .actionMessage - .asInstanceOf[AvatarAction.PlanetsideAttribute] - .player_guid == PlanetSideGUID(1) - ) - assert( - reply1 - .asInstanceOf[AvatarServiceMessage] - .actionMessage - .asInstanceOf[AvatarAction.PlanetsideAttribute] - .attribute_type == 45 - ) - assert( - reply1 - .asInstanceOf[AvatarServiceMessage] - .actionMessage - .asInstanceOf[AvatarAction.PlanetsideAttribute] - .attribute_value == 4 - ) - + assert(reply1 match { + case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(1), 45, 4)) => true + case _ => false + }) assert(reply2.isInstanceOf[Building.SendMapUpdate]) val reply3 = zoneEvents.receiveOne(500 milliseconds) - assert(reply3.isInstanceOf[AvatarServiceMessage]) - assert(reply3.asInstanceOf[AvatarServiceMessage].forChannel == "nowhere") - assert(reply3.asInstanceOf[AvatarServiceMessage].actionMessage.isInstanceOf[AvatarAction.PlanetsideAttribute]) - assert( - reply3 - .asInstanceOf[AvatarServiceMessage] - .actionMessage - .asInstanceOf[AvatarAction.PlanetsideAttribute] - .player_guid == PlanetSideGUID(6) - ) - assert( - reply3 - .asInstanceOf[AvatarServiceMessage] - .actionMessage - .asInstanceOf[AvatarAction.PlanetsideAttribute] - .attribute_type == 48 - ) - assert( - reply3 - .asInstanceOf[AvatarServiceMessage] - .actionMessage - .asInstanceOf[AvatarAction.PlanetsideAttribute] - .attribute_value == 0 - ) + assert(!obj.LowNtuWarningOn) + assert(reply3 match { + case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(6), 47, 0)) => true + case _ => false + }) val reply4 = zoneEvents.receiveOne(500 milliseconds) - assert(!obj.LowNtuWarningOn) - assert(reply4.isInstanceOf[AvatarServiceMessage]) - assert(reply4.asInstanceOf[AvatarServiceMessage].forChannel == "nowhere") - assert(reply4.asInstanceOf[AvatarServiceMessage].actionMessage.isInstanceOf[AvatarAction.PlanetsideAttribute]) - assert( - reply4 - .asInstanceOf[AvatarServiceMessage] - .actionMessage - .asInstanceOf[AvatarAction.PlanetsideAttribute] - .player_guid == PlanetSideGUID(6) - ) - assert( - reply4 - .asInstanceOf[AvatarServiceMessage] - .actionMessage - .asInstanceOf[AvatarAction.PlanetsideAttribute] - .attribute_type == 47 - ) - assert( - reply4 - .asInstanceOf[AvatarServiceMessage] - .actionMessage - .asInstanceOf[AvatarAction.PlanetsideAttribute] - .attribute_value == 0 - ) + assert(reply4 match { + case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(6), 48, 0)) => true + case _ => false + }) } } } @@ -327,16 +259,16 @@ class ResourceSiloControlUpdate2Test extends ActorTest { zone.AvatarEvents = zoneEvents.ref bldg.Actor = buildingEvents.ref - obj.ChargeLevel = 100 + obj.NtuCapacitor = 100 obj.LowNtuWarningOn = true - assert(obj.ChargeLevel == 100) + assert(obj.NtuCapacitor == 100) assert(obj.CapacitorDisplay == 1) assert(obj.LowNtuWarningOn) obj.Actor ! ResourceSilo.UpdateChargeLevel(105) val reply1 = zoneEvents.receiveOne(1000 milliseconds) val reply2 = buildingEvents.receiveOne(1000 milliseconds) - assert(obj.ChargeLevel == 205) + assert(obj.NtuCapacitor == 205) assert(obj.CapacitorDisplay == 3) assert(reply1.isInstanceOf[AvatarServiceMessage]) assert(reply1.asInstanceOf[AvatarServiceMessage].forChannel == "nowhere") @@ -413,9 +345,9 @@ class ResourceSiloControlNoUpdateTest extends ActorTest { zone.AvatarEvents = zoneEvents.ref bldg.Actor = buildingEvents.ref - obj.ChargeLevel = 250 + obj.NtuCapacitor = 250 obj.LowNtuWarningOn = false - assert(obj.ChargeLevel == 250) + assert(obj.NtuCapacitor == 250) assert(obj.CapacitorDisplay == 3) assert(!obj.LowNtuWarningOn) obj.Actor ! ResourceSilo.UpdateChargeLevel(50) @@ -423,9 +355,7 @@ class ResourceSiloControlNoUpdateTest extends ActorTest { expectNoMessage(500 milliseconds) zoneEvents.expectNoMessage(500 milliseconds) buildingEvents.expectNoMessage(500 milliseconds) - assert( - obj.ChargeLevel == 299 || obj.ChargeLevel == 300 - ) // Just in case the capacitor level drops while waiting for the message check 299 & 300 + assert(obj.NtuCapacitor == 299 || obj.NtuCapacitor == 300) // Just in case the capacitor level drops while waiting for the message check 299 & 300 assert(obj.CapacitorDisplay == 3) assert(!obj.LowNtuWarningOn) } diff --git a/common/src/test/scala/objects/UtilityTest.scala b/common/src/test/scala/objects/UtilityTest.scala index e6cafd86..8d2c5194 100644 --- a/common/src/test/scala/objects/UtilityTest.scala +++ b/common/src/test/scala/objects/UtilityTest.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package objects -import akka.actor.{Actor, ActorRef, Props} +import akka.actor.{Actor, Props} import base.ActorTest import net.psforever.objects._ import net.psforever.objects.serverobject.terminals.Terminal diff --git a/common/src/test/scala/service/RemoverActorTest.scala b/common/src/test/scala/service/RemoverActorTest.scala index 8f4d3732..d6dbf984 100644 --- a/common/src/test/scala/service/RemoverActorTest.scala +++ b/common/src/test/scala/service/RemoverActorTest.scala @@ -1,17 +1,24 @@ // Copyright (c) 2017 PSForever package service -import akka.actor.{ActorRef, Props} -import akka.routing.RandomPool -import akka.testkit.TestProbe -import base.ActorTest -import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Tool} -import net.psforever.objects.definition.{EquipmentDefinition, ObjectDefinition} +/* Temporary imports */ +import akka.actor.ActorRef +import net.psforever.objects.definition.EquipmentDefinition import net.psforever.objects.equipment.Equipment -import net.psforever.objects.guid.TaskResolver -import net.psforever.objects.zones.{Zone, ZoneMap} import net.psforever.types.PlanetSideGUID -import services.{RemoverActor, ServiceManager} +import services.RemoverActor + +//import akka.actor.{ActorRef, Props} +//import akka.routing.RandomPool +//import akka.testkit.TestProbe +//import base.ActorTest +//import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Tool} +//import net.psforever.objects.definition.{EquipmentDefinition, ObjectDefinition} +//import net.psforever.objects.equipment.Equipment +//import net.psforever.objects.guid.TaskResolver +//import net.psforever.objects.zones.{Zone, ZoneMap} +//import net.psforever.types.PlanetSideGUID +//import services.{RemoverActor, ServiceManager} import scala.concurrent.duration._ import scala.util.Success diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index ac6cf100..5b533086 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -73,6 +73,7 @@ import net.psforever.objects.vehicles.{ MountedWeapons, Utility, UtilityType, + VehicleControl, VehicleLockState } import net.psforever.objects.vehicles.Utility.InternalTelepad @@ -226,16 +227,13 @@ class WorldSessionActor extends Actor with MDCContextAware { var interimUngunnedVehicle: Option[PlanetSideGUID] = None var interimUngunnedVehicleSeat: Option[Int] = None var keepAliveFunc: () => Unit = NormalKeepAlive + var setAvatar: Boolean = false var turnCounterFunc: PlanetSideGUID => Unit = TurnCounterDuringInterim var clientKeepAlive: Cancellable = Default.Cancellable var progressBarUpdate: Cancellable = Default.Cancellable var reviveTimer: Cancellable = Default.Cancellable var respawnTimer: Cancellable = Default.Cancellable - var cargoMountTimer: Cancellable = Default.Cancellable - var cargoDismountTimer: Cancellable = Default.Cancellable - var antChargingTick: Cancellable = Default.Cancellable - var antDischargingTick: Cancellable = Default.Cancellable var zoningTimer: Cancellable = Default.Cancellable var zoningReset: Cancellable = Default.Cancellable @@ -245,10 +243,6 @@ class WorldSessionActor extends Actor with MDCContextAware { progressBarUpdate.cancel reviveTimer.cancel respawnTimer.cancel - cargoMountTimer.cancel - cargoDismountTimer.cancel - antChargingTick.cancel - antDischargingTick.cancel chatService ! Service.Leave() galaxyService ! Service.Leave() continent.AvatarEvents ! Service.Leave() @@ -756,89 +750,33 @@ class WorldSessionActor extends Actor with MDCContextAware { } case Deployment.CanDeploy(obj, state) => - val vehicle_guid = obj.GUID - //TODO remove this arbitrary allowance angle when no longer helpful - if (obj.Orientation.x > 30 && obj.Orientation.x < 330) { - obj.DeploymentState = DriveState.Mobile - CanNotChangeDeployment(obj, state, "ground too steep") - } else if (state == DriveState.Deploying) { + if (state == DriveState.Deploying) { log.info(s"DeployRequest: $obj transitioning to deploy state") - obj.Velocity = Some(Vector3.Zero) //no velocity - sendResponse(DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero)) - continent.VehicleEvents ! VehicleServiceMessage( - continent.Id, - VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero) - ) - DeploymentActivities(obj) - import scala.concurrent.ExecutionContext.Implicits.global - context.system.scheduler.scheduleOnce( - obj.DeployTime milliseconds, - obj.Actor, - Deployment.TryDeploy(DriveState.Deployed) - ) - } else if (state == DriveState.Deployed) { + } + else if (state == DriveState.Deployed) { log.info(s"DeployRequest: $obj has been Deployed") - obj.Velocity = Some(Vector3.Zero) - sendResponse(DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero)) - continent.VehicleEvents ! VehicleServiceMessage( - continent.Id, - VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero) - ) - DeploymentActivities(obj) - //... - } else { + } + else { CanNotChangeDeployment(obj, state, "incorrect deploy state") } case Deployment.CanUndeploy(obj, state) => - val vehicle_guid = obj.GUID if (state == DriveState.Undeploying) { log.info(s"DeployRequest: $obj transitioning to undeploy state") - sendResponse(DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero)) - continent.VehicleEvents ! VehicleServiceMessage( - continent.Id, - VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero) - ) - DeploymentActivities(obj) - import scala.concurrent.ExecutionContext.Implicits.global - context.system.scheduler.scheduleOnce( - obj.UndeployTime milliseconds, - obj.Actor, - Deployment.TryUndeploy(DriveState.Mobile) - ) - } else if (state == DriveState.Mobile) { + } + else if (state == DriveState.Mobile) { log.info(s"DeployRequest: $obj is Mobile") - sendResponse(DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero)) - continent.VehicleEvents ! VehicleServiceMessage( - continent.Id, - VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero) - ) - DeploymentActivities(obj) - //... - } else { + } + else { CanNotChangeDeployment(obj, state, "incorrect undeploy state") } case Deployment.CanNotChangeDeployment(obj, state, reason) => - CanNotChangeDeployment(obj, state, reason) - - case ResourceSilo.ResourceSiloMessage(tplayer, msg, order) => - continent.GUID(msg.avatar_guid) match { - case Some(vehicle: Vehicle) => - val silo_guid = msg.object_guid - order match { - case ResourceSilo.ChargeEvent() => - antChargingTick - .cancel() // If an ANT is refilling an NTU silo it isn't in a warpgate, so disable NTU regeneration - antDischargingTick.cancel() - antDischargingTick = context.system.scheduler.scheduleOnce( - 1000 milliseconds, - self, - NtuDischarging(player, vehicle, msg.object_guid) - ) - case _ => ; - } - case _ => ; + if (Deployment.CheckForDeployState(state) && !VehicleControl.DeploymentAngleCheck(obj)) { + CanNotChangeDeployment(obj, state, "ground too steep") + } + else { + CanNotChangeDeployment(obj, state, reason) } case CreateCharacter(name, head, voice, gender, empire) => @@ -1298,6 +1236,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } keepAliveFunc = NormalKeepAlive upstreamMessageCount = 0 + setAvatar = false persist() case PlayerLoaded(tplayer) => @@ -1314,6 +1253,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } keepAliveFunc = NormalKeepAlive upstreamMessageCount = 0 + setAvatar = false persist() case PlayerFailedToLoad(tplayer) => @@ -1380,7 +1320,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case _ => true }) ) { - if (waitingOnUpstream) { + if(!setAvatar || waitingOnUpstream) { setCurrentAvatarFunc(tplayer) respawnTimer = context.system.scheduler.scheduleOnce( delay = (if (attempt <= max_attempts / 2) 10 else 5) seconds, @@ -1398,13 +1338,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - case NtuCharging(tplayer, vehicle) => - HandleNtuCharging(tplayer, vehicle) - - case NtuDischarging(tplayer, vehicle, silo_guid) => - HandleNtuDischarging(tplayer, vehicle, silo_guid) - - case Vitality.DamageResolution(target: TelepadDeployable, _) => + case Vitality.DamageResolution(target : TelepadDeployable, _) => //telepads if (target.Health <= 0) { //update if destroyed @@ -2726,7 +2660,7 @@ class WorldSessionActor extends Actor with MDCContextAware { CancelAllProximityUnits() sendResponse(PlanetsideAttributeMessage(obj_guid, 0, obj.Health)) sendResponse(PlanetsideAttributeMessage(obj_guid, 68, obj.Shields)) //shield health - if (obj.Definition.MaxNtuCapacitor > 0) { + if(obj.Definition == GlobalDefinitions.ant) { sendResponse(PlanetsideAttributeMessage(obj_guid, 45, obj.NtuCapacitorScaled)) } if (obj.Definition.MaxCapacitor > 0) { @@ -3106,10 +3040,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } case VehicleResponse.UnloadVehicle(vehicle, vehicle_guid) => - //if(tplayer_guid != guid) { - BeforeUnloadVehicle(vehicle) sendResponse(ObjectDeleteMessage(vehicle_guid, 0)) - //} case VehicleResponse.UnstowEquipment(item_guid) => if (tplayer_guid != guid) { @@ -3287,152 +3218,6 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(mountPointStatusMessage) } - /** - * na - * @param tplayer na - * @param vehicle na - */ - def HandleNtuCharging(tplayer: Player, vehicle: Vehicle): Unit = { - log.trace(s"NtuCharging: Vehicle ${vehicle.GUID} is charging NTU capacitor.") - if (vehicle.NtuCapacitor < vehicle.Definition.MaxNtuCapacitor) { - // Charging - ANTs would charge from 0-100% in roughly 75s on live (https://www.youtube.com/watch?v=veOWToR2nSk&feature=youtu.be&t=1194) - vehicle.NtuCapacitor += vehicle.Definition.MaxNtuCapacitor / 75 - sendResponse( - PlanetsideAttributeMessage( - vehicle.GUID, - 45, - vehicle.NtuCapacitorScaled - ) - ) // set ntu on vehicle UI - continent.AvatarEvents ! AvatarServiceMessage( - continent.Id, - AvatarAction.PlanetsideAttribute(vehicle.GUID, 52, 1L) - ) // panel glow on - continent.AvatarEvents ! AvatarServiceMessage( - continent.Id, - AvatarAction.PlanetsideAttribute(vehicle.GUID, 49, 1L) - ) // orb particle effect on - - antChargingTick = context.system.scheduler.scheduleOnce( - 1000 milliseconds, - self, - NtuCharging(player, vehicle) - ) // Repeat until fully charged - } else { - // Fully charged - sendResponse( - PlanetsideAttributeMessage( - vehicle.GUID, - 45, - scala.math.ceil((vehicle.NtuCapacitor.toFloat / vehicle.Definition.MaxNtuCapacitor.toFloat) * 10).toInt - ) - ) // set ntu on vehicle UI - - // Turning off glow/orb effects on ANT doesn't seem to work when deployed. Try to undeploy ANT from server side - context.system.scheduler.scheduleOnce( - vehicle.UndeployTime milliseconds, - vehicle.Actor, - Deployment.TryUndeploy(DriveState.Undeploying) - ) - } - } - - /** - * na - * @param tplayer na - * @param vehicle na - * @param silo_guid na - */ - def HandleNtuDischarging(tplayer: Player, vehicle: Vehicle, silo_guid: PlanetSideGUID): Unit = { - log.trace(s"NtuDischarging: Vehicle ${vehicle.GUID} is discharging NTU into silo $silo_guid") - continent.AvatarEvents ! AvatarServiceMessage( - continent.Id, - AvatarAction.PlanetsideAttribute(vehicle.GUID, 49, 0L) - ) // orb particle effect off - continent.AvatarEvents ! AvatarServiceMessage( - continent.Id, - AvatarAction.PlanetsideAttribute(vehicle.GUID, 52, 1L) - ) // panel glow on - - var silo = continent.GUID(silo_guid).get.asInstanceOf[ResourceSilo] - // Check vehicle is still deployed before continuing. User can undeploy manually or vehicle may not longer be present. - if (vehicle.DeploymentState == DriveState.Deployed) { - if (vehicle.NtuCapacitor > 0 && silo.ChargeLevel < silo.MaximumCharge) { - - // Make sure we don't exceed the silo maximum charge or remove much NTU from ANT if maximum is reached, or try to make ANT go below 0 NTU - // Silos would charge from 0-100% in roughly 105s on live (~20%-100% https://youtu.be/veOWToR2nSk?t=1402) - var chargeToDeposit = - Math.min(Math.min(vehicle.NtuCapacitor, silo.MaximumCharge / 105), silo.MaximumCharge - silo.ChargeLevel) - vehicle.NtuCapacitor -= chargeToDeposit - silo.Actor ! ResourceSilo.UpdateChargeLevel(chargeToDeposit) - continent.AvatarEvents ! AvatarServiceMessage( - continent.Id, - AvatarAction.PlanetsideAttribute(silo_guid, 49, 1L) - ) // panel glow on & orb particles on - sendResponse( - PlanetsideAttributeMessage( - vehicle.GUID, - 45, - vehicle.NtuCapacitorScaled - ) - ) // set ntu on vehicle UI - - //todo: grant BEP to user - //todo: grant BEP to squad in range - - //todo: handle silo orb / panel glow properly if more than one person is refilling silo and one player stops. effects should stay on until all players stop - - if (vehicle.NtuCapacitor > 0 && silo.ChargeLevel < silo.MaximumCharge) { - log.trace(s"NtuDischarging: ANT not empty and Silo not full. Scheduling another discharge") - // Silo still not full and ant still has charge left - keep rescheduling ticks - antDischargingTick = - context.system.scheduler.scheduleOnce(1000 milliseconds, self, NtuDischarging(player, vehicle, silo_guid)) - } else { - log.trace(s"NtuDischarging: ANT NTU empty or Silo NTU full.") - // Turning off glow/orb effects on ANT doesn't seem to work when deployed. Try to undeploy ANT from server side - context.system.scheduler.scheduleOnce( - vehicle.UndeployTime milliseconds, - vehicle.Actor, - Deployment.TryUndeploy(DriveState.Undeploying) - ) - continent.AvatarEvents ! AvatarServiceMessage( - continent.Id, - AvatarAction.PlanetsideAttribute(silo_guid, 49, 0L) - ) // panel glow off & orb particles off - antDischargingTick.cancel() - } - } else { - // This shouldn't normally be run, only if the client thinks the ANT has capacitor charge when it doesn't, or thinks the silo isn't full when it is. - log.warn( - s"NtuDischarging: Invalid discharge state. ANT Capacitor: ${vehicle.NtuCapacitor} Silo Capacitor: ${silo.ChargeLevel}" - ) - // Turning off glow/orb effects on ANT doesn't seem to work when deployed. Try to undeploy ANT from server side - context.system.scheduler.scheduleOnce( - vehicle.UndeployTime milliseconds, - vehicle.Actor, - Deployment.TryUndeploy(DriveState.Undeploying) - ) - continent.AvatarEvents ! AvatarServiceMessage( - continent.Id, - AvatarAction.PlanetsideAttribute(silo_guid, 49, 0L) - ) // panel glow off & orb particles off - antDischargingTick.cancel() - } - } else { - log.trace(s"NtuDischarging: Vehicle is no longer deployed. Removing effects") - // Vehicle has changed from deployed and this should be the last timer tick sent - continent.AvatarEvents ! AvatarServiceMessage( - continent.Id, - AvatarAction.PlanetsideAttribute(vehicle.GUID, 52, 0L) - ) // panel glow off - continent.AvatarEvents ! AvatarServiceMessage( - continent.Id, - AvatarAction.PlanetsideAttribute(silo_guid, 49, 0L) - ) // panel glow off & orb particles off - antDischargingTick.cancel() - } - } - /** * Handle the message that indicates the level of completion of a process. * The process is any form of user-driven activity with a certain eventual outcome @@ -3647,19 +3432,14 @@ class WorldSessionActor extends Actor with MDCContextAware { vehicle.Actor ! JammableUnit.ClearJammeredStatus() vehicle.Actor ! JammableUnit.ClearJammeredSound() } - //positive shield strength if (vehicle.Shields > 0) { sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 68, vehicle.Shields)) } - // ANT capacitor - if (vehicle.Definition.MaxNtuCapacitor > 0) { - sendResponse( - PlanetsideAttributeMessage(vehicle.GUID, 45, vehicle.NtuCapacitorScaled) - ) // set ntu on vehicle UI + if(vehicle.Definition == GlobalDefinitions.ant) { + sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 45, vehicle.NtuCapacitorScaled)) // set ntu on vehicle UI } - LoadZoneTransferPassengerMessages( guid, continent.Id, @@ -3681,6 +3461,7 @@ class WorldSessionActor extends Actor with MDCContextAware { tplayer.Actor ! Player.StaminaRegen() } upstreamMessageCount = 0 + setAvatar = true } /** @@ -3933,12 +3714,12 @@ class WorldSessionActor extends Actor with MDCContextAware { case KeepAliveMessage(_) => keepAliveFunc() - case msg @ BeginZoningMessage() => + case msg@BeginZoningMessage() => log.info("Reticulating splines ...") zoneLoaded = None val continentId = continent.Id traveler.zone = continentId - val faction = player.Faction + val faction = player.Faction val factionChannel = s"$faction" continent.AvatarEvents ! Service.Join(continentId) continent.AvatarEvents ! Service.Join(factionChannel) @@ -3948,19 +3729,18 @@ class WorldSessionActor extends Actor with MDCContextAware { continent.VehicleEvents ! Service.Join(avatar.name) continent.VehicleEvents ! Service.Join(continentId) continent.VehicleEvents ! Service.Join(factionChannel) - if (connectionState != 100) configZone(continent) + if(connectionState != 100) configZone(continent) sendResponse(TimeOfDayMessage(1191182336)) //custom - sendResponse(ReplicationStreamMessage(5, Some(6), Vector.empty)) //clear squad list + sendResponse(ReplicationStreamMessage(5, Some(6), Vector.empty)) //clear squad list sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 0)) // disable festive backpacks //find and reclaim own deployables, if any val guid = player.GUID - val foundDeployables = - continent.DeployableList.filter(obj => obj.OwnerName.contains(player.Name) && obj.Health > 0) + val foundDeployables = continent.DeployableList.filter(obj => obj.OwnerName.contains(player.Name) && obj.Health > 0) continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(foundDeployables, continent)) foundDeployables.foreach(obj => { - if (avatar.Deployables.Add(obj)) { + if(avatar.Deployables.Add(obj)) { obj.Owner = guid log.info(s"Found a ${obj.Definition.Name} of ours while loading the zone") } @@ -3980,7 +3760,7 @@ class WorldSessionActor extends Actor with MDCContextAware { ) }) turrets.foreach(obj => { - val objGUID = obj.GUID + val objGUID = obj.GUID val definition = obj.Definition sendResponse( ObjectCreateMessage( @@ -3990,14 +3770,11 @@ class WorldSessionActor extends Actor with MDCContextAware { ) ) //seated players - obj - .asInstanceOf[Mountable] - .Seats - .values + obj.asInstanceOf[Mountable].Seats.values .map(_.Occupant) .collect { case Some(occupant) => - if (occupant.isAlive) { + if(occupant.isAlive) { val tdefintion = occupant.Definition sendResponse( ObjectCreateMessage( @@ -4016,8 +3793,8 @@ class WorldSessionActor extends Actor with MDCContextAware { obj.Definition.DeployCategory == DeployableCategory.Sensors && !obj.Destroyed && (obj match { - case jObj: JammableUnit => !jObj.Jammed; - case _ => true + case jObj : JammableUnit => !jObj.Jammed; + case _ => true }) ) .foreach(obj => { @@ -4028,15 +3805,10 @@ class WorldSessionActor extends Actor with MDCContextAware { continent.DeployableList .filter(obj => obj.Faction == faction && !obj.Destroyed) .foreach(obj => { - if (obj.Health != obj.DefaultHealth) { + if(obj.Health != obj.DefaultHealth) { sendResponse(PlanetsideAttributeMessage(obj.GUID, 0, obj.Health)) } - val deployInfo = DeployableInfo( - obj.GUID, - Deployable.Icon(obj.Definition.Item), - obj.Position, - obj.Owner.getOrElse(PlanetSideGUID(0)) - ) + val deployInfo = DeployableInfo(obj.GUID, Deployable.Icon(obj.Definition.Item), obj.Position, obj.Owner.getOrElse(PlanetSideGUID(0))) sendResponse(DeployableObjectsInfoMessage(DeploymentAction.Build, deployInfo)) }) //render Equipment that was dropped into zone before the player arrived @@ -4046,25 +3818,19 @@ class WorldSessionActor extends Actor with MDCContextAware { ObjectCreateMessage( definition.ObjectId, item.GUID, - DroppedItemData( - PlacementData(item.Position, item.Orientation), - definition.Packet.ConstructorData(item).get - ) + DroppedItemData(PlacementData(item.Position, item.Orientation), definition.Packet.ConstructorData(item).get) ) ) }) //load active players in zone (excepting players who are seated or players who are us) val live = continent.LivePlayers - live - .filterNot(tplayer => { - tplayer.GUID == player.GUID || tplayer.VehicleSeated.nonEmpty - }) + live.filterNot(tplayer => { + tplayer.GUID == player.GUID || tplayer.VehicleSeated.nonEmpty + }) .foreach(char => { val tdefintion = char.Definition - sendResponse( - ObjectCreateMessage(tdefintion.ObjectId, char.GUID, char.Definition.Packet.ConstructorData(char).get) - ) - if (char.UsingSpecial == SpecialExoSuitDefinition.Mode.Anchored) { + sendResponse(ObjectCreateMessage(tdefintion.ObjectId, char.GUID, char.Definition.Packet.ConstructorData(char).get)) + if(char.UsingSpecial == SpecialExoSuitDefinition.Mode.Anchored) { sendResponse(PlanetsideAttributeMessage(char.GUID, 19, 1)) } }) @@ -4077,37 +3843,55 @@ class WorldSessionActor extends Actor with MDCContextAware { val (a, b) = continent.Vehicles.partition(vehicle => { vehicle.Destroyed && vehicle.Definition.DestroyedModel.nonEmpty }) - ( - a, - (continent.GUID(player.VehicleSeated) match { - case Some(vehicle: Vehicle) if vehicle.PassengerInSeat(player).isDefined => - b.partition { - _.GUID != vehicle.GUID - } - case Some(_) => - //vehicle, but we're not seated in it - player.VehicleSeated = None - (b, List.empty[Vehicle]) - case None => - //throw error since VehicleSeated didn't point to a vehicle? - player.VehicleSeated = None - (b, List.empty[Vehicle]) - }) - ) + (a, (continent.GUID(player.VehicleSeated) match { + case Some(vehicle : Vehicle) if vehicle.PassengerInSeat(player).isDefined => + b.partition { + _.GUID != vehicle.GUID + } + case Some(_) => + //vehicle, but we're not seated in it + player.VehicleSeated = None + (b, List.empty[Vehicle]) + case None => + //throw error since VehicleSeated didn't point to a vehicle? + player.VehicleSeated = None + (b, List.empty[Vehicle]) + })) } + val allActiveVehicles = vehicles ++ usedVehicle //active vehicles (and some wreckage) vehicles.foreach(vehicle => { - val vguid = vehicle.GUID + val vguid = vehicle.GUID val vdefinition = vehicle.Definition - sendResponse( - ObjectCreateMessage(vdefinition.ObjectId, vguid, vdefinition.Packet.ConstructorData(vehicle).get) - ) + sendResponse(ObjectCreateMessage(vdefinition.ObjectId, vguid, vdefinition.Packet.ConstructorData(vehicle).get)) //occupants other than driver vehicle.Seats .filter({ case (index, seat) => seat.isOccupied && live.contains(seat.Occupant.get) && index > 0 }) - .foreach({ - case (index, seat) => - val tplayer = seat.Occupant.get + .foreach({ case (index, seat) => + val tplayer = seat.Occupant.get + val tdefintion = tplayer.Definition + sendResponse( + ObjectCreateMessage( + tdefintion.ObjectId, + tplayer.GUID, + ObjectCreateMessageParent(vguid, index), + tdefintion.Packet.ConstructorData(tplayer).get + ) + ) + }) + }) + vehicles.collect { case vehicle if vehicle.Faction == faction => + Vehicles.ReloadAccessPermissions(vehicle, player.Name) + } + //our vehicle would have already been loaded; see NewPlayerLoaded/AvatarCreate + usedVehicle.headOption match { + case Some(vehicle) => + //depict any other passengers already in this zone + val vguid = vehicle.GUID + vehicle.Seats + .filter({ case (index, seat) => seat.isOccupied && !seat.Occupant.contains(player) && live.contains(seat.Occupant.get) && index > 0 }) + .foreach({ case (index, seat) => + val tplayer = seat.Occupant.get val tdefintion = tplayer.Definition sendResponse( ObjectCreateMessage( @@ -4117,39 +3901,15 @@ class WorldSessionActor extends Actor with MDCContextAware { tdefintion.Packet.ConstructorData(tplayer).get ) ) - }) - }) - vehicles.collect { - case vehicle if vehicle.Faction == faction => - Vehicles.ReloadAccessPermissions(vehicle, player.Name) - } - //our vehicle would have already been loaded; see NewPlayerLoaded/AvatarCreate - usedVehicle.headOption match { - case Some(vehicle) => - //depict any other passengers already in this zone - val vguid = vehicle.GUID - vehicle.Seats - .filter({ - case (index, seat) => - seat.isOccupied && !seat.Occupant.contains(player) && live.contains(seat.Occupant.get) && index > 0 - }) - .foreach({ - case (index, seat) => - val tplayer = seat.Occupant.get - val tdefintion = tplayer.Definition - sendResponse( - ObjectCreateMessage( - tdefintion.ObjectId, - tplayer.GUID, - ObjectCreateMessageParent(vguid, index), - tdefintion.Packet.ConstructorData(tplayer).get - ) - ) }) //since we would have only subscribed recently, we need to reload seat access states (0 to 3).foreach { group => sendResponse(PlanetsideAttributeMessage(vguid, group + 10, vehicle.PermissionGroup(group).get.id)) } + //positive shield strength + if(vehicle.Shields > 0) { + sendResponse(PlanetsideAttributeMessage(vguid, 68, vehicle.Shields)) + } case _ => ; //no vehicle } //vehicle wreckages @@ -4163,108 +3923,109 @@ class WorldSessionActor extends Actor with MDCContextAware { ) }) //cargo occupants (including our own vehicle as cargo) - vehicles.collect { - case vehicle if vehicle.CargoHolds.nonEmpty => - vehicle.CargoHolds.collect({ - case (index, hold) if hold.isOccupied => { - CargoBehavior.CargoMountBehaviorForAll( - vehicle, - hold.Occupant.get, - index - ) //CargoMountBehaviorForUs can fail to attach the cargo vehicle on some clients - } - }) + allActiveVehicles.collect { case vehicle if vehicle.CargoHolds.nonEmpty => + vehicle.CargoHolds.collect({ case (index, hold) if hold.isOccupied => { + CargoBehavior.CargoMountBehaviorForAll(vehicle, hold.Occupant.get, index) //CargoMountBehaviorForUs can fail to attach the cargo vehicle on some clients + } + }) } //special deploy states - val deployedVehicles = vehicles.filter(_.DeploymentState == DriveState.Deployed) - deployedVehicles - .filter(_.Definition == GlobalDefinitions.ams) - .foreach(obj => { - sendResponse(PlanetsideAttributeMessage(obj.GUID, 81, 1)) - }) - deployedVehicles - .filter(_.Definition == GlobalDefinitions.router) - .foreach(obj => { - sendResponse(DeployRequestMessage(player.GUID, obj.GUID, DriveState.Deploying, 0, false, Vector3.Zero)) - sendResponse(DeployRequestMessage(player.GUID, obj.GUID, DriveState.Deployed, 0, false, Vector3.Zero)) - ToggleTeleportSystem(obj, TelepadLike.AppraiseTeleportationSystem(obj, continent)) - }) + val deployedVehicles = allActiveVehicles.filter(_.DeploymentState == DriveState.Deployed) + deployedVehicles.filter(_.Definition == GlobalDefinitions.ams).foreach { obj => + //??? + sendResponse(PlanetsideAttributeMessage(obj.GUID, 81, 1)) + } + deployedVehicles.filter(_.Definition == GlobalDefinitions.ant).foreach { obj => + //special effects + sendResponse(PlanetsideAttributeMessage(obj.GUID, 52, 1)) // ant panel glow + Vehicles.FindANTChargingSource(obj, None).orElse(Vehicles.FindANTDischargingTarget(obj, None)) match { + case Some(silo : ResourceSilo) => + sendResponse(PlanetsideAttributeMessage(silo.GUID, 49, 1)) // silo orb particle effect + case Some(_ : WarpGate) => + sendResponse(PlanetsideAttributeMessage(obj.GUID, 49, 1)) // ant orb particle effect + case _ => ; + } + } + deployedVehicles.filter(_.Definition == GlobalDefinitions.router).foreach { obj => + //the router won't work if it doesn't completely deploy + sendResponse(DeployRequestMessage(player.GUID, obj.GUID, DriveState.Deploying, 0, false, Vector3.Zero)) + sendResponse(DeployRequestMessage(player.GUID, obj.GUID, DriveState.Deployed, 0, false, Vector3.Zero)) + ToggleTeleportSystem(obj, TelepadLike.AppraiseTeleportationSystem(obj, continent)) + } //implant terminals - continent.Map.TerminalToInterface.foreach({ - case ((terminal_guid, interface_guid)) => - val parent_guid = PlanetSideGUID(terminal_guid) - continent.GUID(interface_guid) match { - case Some(obj: Terminal) => - val objDef = obj.Definition - sendResponse( - ObjectCreateMessage( - objDef.ObjectId, - PlanetSideGUID(interface_guid), - ObjectCreateMessageParent(parent_guid, 1), - objDef.Packet.ConstructorData(obj).get - ) + continent.Map.TerminalToInterface.foreach({ case ((terminal_guid, interface_guid)) => + val parent_guid = PlanetSideGUID(terminal_guid) + continent.GUID(interface_guid) match { + case Some(obj : Terminal) => + val objDef = obj.Definition + sendResponse( + ObjectCreateMessage( + objDef.ObjectId, + PlanetSideGUID(interface_guid), + ObjectCreateMessageParent(parent_guid, 1), + objDef.Packet.ConstructorData(obj).get ) - case _ => ; - } - //seat terminal occupants - continent.GUID(terminal_guid) match { - case Some(obj: Mountable) => - obj.Seats(0).Occupant match { - case Some(tplayer) => - val tdefintion = tplayer.Definition - sendResponse( - ObjectCreateMessage( - tdefintion.ObjectId, - tplayer.GUID, - ObjectCreateMessageParent(parent_guid, 0), - tdefintion.Packet.ConstructorData(tplayer).get - ) - ) - case None => ; - } - case _ => ; - } - }) - - //base turrets - continent.Map.TurretToWeapon - .map { case ((turret_guid, _)) => continent.GUID(turret_guid) } - .collect { - case Some(turret: FacilityTurret) => - val pguid = turret.GUID - //attached weapon - if (!turret.isUpgrading) { - turret.ControlledWeapon(wepNumber = 1) match { - case Some(obj: Tool) => - val objDef = obj.Definition - sendResponse( - ObjectCreateMessage( - objDef.ObjectId, - obj.GUID, - ObjectCreateMessageParent(pguid, 1), - objDef.Packet.ConstructorData(obj).get - ) - ) - case _ => ; - } - } - //reserved ammunition? - //TODO need to register if it exists - //seat turret occupant - turret.Seats(0).Occupant match { + ) + case _ => ; + } + //seat terminal occupants + continent.GUID(terminal_guid) match { + case Some(obj : Mountable) => + obj.Seats(0).Occupant match { case Some(tplayer) => val tdefintion = tplayer.Definition sendResponse( ObjectCreateMessage( tdefintion.ObjectId, tplayer.GUID, - ObjectCreateMessageParent(pguid, 0), + ObjectCreateMessageParent(parent_guid, 0), tdefintion.Packet.ConstructorData(tplayer).get ) ) case None => ; } + case _ => ; + } + }) + + //base turrets + continent.Map.TurretToWeapon + .map { case ((turret_guid, _)) => continent.GUID(turret_guid) } + .collect { case Some(turret : FacilityTurret) => + val pguid = turret.GUID + //attached weapon + if(!turret.isUpgrading) { + turret.ControlledWeapon(wepNumber = 1) match { + case Some(obj : Tool) => + val objDef = obj.Definition + sendResponse( + ObjectCreateMessage( + objDef.ObjectId, + obj.GUID, + ObjectCreateMessageParent(pguid, 1), + objDef.Packet.ConstructorData(obj).get + ) + ) + case _ => ; + } + } + //reserved ammunition? + //TODO need to register if it exists + //seat turret occupant + turret.Seats(0).Occupant match { + case Some(tplayer) => + val tdefintion = tplayer.Definition + sendResponse( + ObjectCreateMessage( + tdefintion.ObjectId, + tplayer.GUID, + ObjectCreateMessageParent(pguid, 0), + tdefintion.Packet.ConstructorData(tplayer).get + ) + ) + case None => ; + } } continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.UpdateAmsSpawnPoint(continent)) upstreamMessageCount = 0 @@ -4806,7 +4567,6 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some((buildings, pos)) => (buildings, pos) case None => (None, None) } - val (timerOption, timerPos) = args.zipWithIndex .map { case (_, pos) @@ -4908,8 +4668,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case GlobalDefinitions.resource_silo => val r = new scala.util.Random val silo = amenity.asInstanceOf[ResourceSilo] - val ntu: Int = 900 + r.nextInt(100) - silo.ChargeLevel - // val ntu: Int = 0 + r.nextInt(100) - silo.ChargeLevel + val ntu: Int = 900 + r.nextInt(100) - silo.NtuCapacitor silo.Actor ! ResourceSilo.UpdateChargeLevel(ntu) case _ => ; @@ -5600,8 +5359,8 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(GenericObjectStateMsg(object_guid, 16)) } - case Some(resourceSilo: ResourceSilo) => - resourceSilo.Actor ! ResourceSilo.Use(player, msg) + case Some(resourceSilo : ResourceSilo) => + resourceSilo.Actor ! CommonMessages.Use(player) case Some(panel: IFFLock) => equipment match { @@ -7726,119 +7485,6 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - /** - * Perform specific operations depending on the target of deployment. - * @param obj the object that has had its deployment state changed - */ - def DeploymentActivities(obj: Deployment.DeploymentObject): Unit = { - DeploymentActivities(obj, obj.DeploymentState) - } - - /** - * Perform specific operations depending on the target of deployment. - * @param obj the object that has had its deployment state changed - * @param state the new deployment state - */ - def DeploymentActivities(obj: Deployment.DeploymentObject, state: DriveState.Value): Unit = { - obj match { - case vehicle: Vehicle => - Vehicles.ReloadAccessPermissions(vehicle, player.Name) //TODO we should not have to do this imho - //ams - if (vehicle.Definition == GlobalDefinitions.ams) { - state match { - case DriveState.Deployed => - continent.VehicleEvents ! VehicleServiceMessage.AMSDeploymentChange(continent) - sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 81, 1)) - case DriveState.Undeploying => - continent.VehicleEvents ! VehicleServiceMessage.AMSDeploymentChange(continent) - sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 81, 0)) - case DriveState.Mobile | DriveState.State7 => - case _ => ; - } - } - //ant - else if (vehicle.Definition == GlobalDefinitions.ant) { - state match { - case DriveState.Deployed => - // We only want this WorldSessionActor (not other player's WorldSessionActor) to manage timers - if (vehicle.Seats(0).Occupant.contains(player)) { - // Start ntu regeneration - // If vehicle sends UseItemMessage with silo as target NTU regeneration will be disabled and orb particles will be disabled - antChargingTick = - context.system.scheduler.scheduleOnce(1000 milliseconds, self, NtuCharging(player, vehicle)) - } - case DriveState.Undeploying => - // We only want this WorldSessionActor (not other player's WorldSessionActor) to manage timers - if (vehicle.Seats(0).Occupant.contains(player)) { - antChargingTick.cancel() // Stop charging NTU if charging - } - - continent.AvatarEvents ! AvatarServiceMessage( - continent.Id, - AvatarAction.PlanetsideAttribute(vehicle.GUID, 52, 0L) - ) // panel glow off - continent.AvatarEvents ! AvatarServiceMessage( - continent.Id, - AvatarAction.PlanetsideAttribute(vehicle.GUID, 49, 0L) - ) // orb particles off - case DriveState.Mobile | DriveState.State7 | DriveState.Deploying => - case _ => ; - } - } - //router - else if (vehicle.Definition == GlobalDefinitions.router) { - state match { - case DriveState.Deploying => - vehicle.Utility(UtilityType.internal_router_telepad_deployable) match { - case Some(util: Utility.InternalTelepad) => - util.Active = true - case _ => - log.warn( - s"DeploymentActivities: could not find internal telepad in router@${vehicle.GUID.guid} while $state" - ) - } - case DriveState.Deployed => - //let the timer do all the work - continent.LocalEvents ! LocalServiceMessage( - continent.Id, - LocalAction.ToggleTeleportSystem( - PlanetSideGUID(0), - vehicle, - TelepadLike.AppraiseTeleportationSystem(vehicle, continent) - ) - ) - case DriveState.Undeploying => - //deactivate internal router before trying to reset the system - vehicle.Utility(UtilityType.internal_router_telepad_deployable) match { - case Some(util: Utility.InternalTelepad) => - //any telepads linked with internal mechanism must be deconstructed - continent.GUID(util.Telepad) match { - case Some(telepad: TelepadDeployable) => - continent.LocalEvents ! LocalServiceMessage.Deployables( - RemoverActor.ClearSpecific(List(telepad), continent) - ) - continent.LocalEvents ! LocalServiceMessage.Deployables( - RemoverActor.AddTask(telepad, continent, Some(0 milliseconds)) - ) - case Some(_) | None => ; - } - util.Active = false - continent.LocalEvents ! LocalServiceMessage( - continent.Id, - LocalAction.ToggleTeleportSystem(PlanetSideGUID(0), vehicle, None) - ) - case _ => - log.warn( - s"DeploymentActivities: could not find internal telepad in router@${vehicle.GUID.guid} while $state" - ) - } - case _ => ; - } - } - case _ => ; - } - } - /** * Common reporting behavior when a `Deployment` object fails to properly transition between states. * @param obj the game object that could not @@ -8051,8 +7697,8 @@ class WorldSessionActor extends Actor with MDCContextAware { //silo capacity sendResponse(PlanetsideAttributeMessage(amenityId, 45, silo.CapacitorDisplay)) //warning lights - sendResponse(PlanetsideAttributeMessage(silo.Owner.GUID, 47, if (silo.LowNtuWarningOn) 1 else 0)) - if (silo.ChargeLevel == 0) { + sendResponse(PlanetsideAttributeMessage(silo.Owner.GUID, 47, if(silo.LowNtuWarningOn) 1 else 0)) + if(silo.NtuCapacitor == 0) { sendResponse(PlanetsideAttributeMessage(silo.Owner.GUID, 48, 1)) } case door: Door if door.isOpen => @@ -10251,23 +9897,6 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(GenericObjectActionMessage(destGUID, 32)) } - /** - * Before a vehicle is removed from the game world, the following actions must be performed. - * @param vehicle the vehicle - */ - def BeforeUnloadVehicle(vehicle: Vehicle): Unit = { - vehicle.Definition match { - case GlobalDefinitions.ams if vehicle.Faction == player.Faction => - log.info("BeforeUnload: cleaning up after a mobile spawn vehicle ...") - continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.UpdateAmsSpawnPoint(continent)) - case GlobalDefinitions.router => - //this may repeat for multiple players on the same continent but that's okay(?) - log.info("BeforeUnload: cleaning up after a router ...") - Deployables.RemoveTelepad(vehicle) - case _ => ; - } - } - /** * For a certain weapon that cna load ammunition, enforce that its magazine is empty. * @param weapon_guid the weapon @@ -11650,10 +11279,6 @@ object WorldSessionActor { position: Vector3 ) - private final case class NtuCharging(tplayer: Player, vehicle: Vehicle) - - private final case class NtuDischarging(tplayer: Player, vehicle: Vehicle, silo_guid: PlanetSideGUID) - private final case class FinalizeDeployable( obj: PlanetSideGameObject with Deployable, tool: ConstructionItem, diff --git a/pslogin/src/main/scala/Zones.scala b/pslogin/src/main/scala/Zones.scala index 00e96082..6029be2d 100644 --- a/pslogin/src/main/scala/Zones.scala +++ b/pslogin/src/main/scala/Zones.scala @@ -421,8 +421,8 @@ object Zones { // todo: load silo charge from database zone.Buildings.values.flatMap { _.Amenities.collect { - case silo: ResourceSilo => - silo.Actor ! ResourceSilo.UpdateChargeLevel(silo.MaximumCharge) + case silo : ResourceSilo => + silo.Actor ! ResourceSilo.UpdateChargeLevel(silo.MaxNtuCapacitor) } } }