From ef0d18d21446ca7feaa2b5b7a577caebf8bff408 Mon Sep 17 00:00:00 2001 From: "Jason_DiDonato@yahoo.com" Date: Tue, 13 Oct 2020 01:32:41 -0400 Subject: [PATCH 1/7] powered amenity control agent; terminals, proximity terminals, spawn tubes, implant mechs, facility turrets, and painfields demonstrate this behavior --- .../psforever/actors/zone/BuildingActor.scala | 13 + .../net/psforever/objects/SpawnPoint.scala | 2 +- .../ImplantTerminalMechControl.scala | 43 ++- .../serverobject/painbox/PainboxControl.scala | 277 ++++++++++-------- .../structures/PoweredAmenityControl.scala | 39 +++ .../terminals/ProximityTerminalControl.scala | 60 +++- .../terminals/TerminalControl.scala | 44 ++- .../objects/serverobject/tube/SpawnTube.scala | 4 + .../serverobject/tube/SpawnTubeControl.scala | 39 ++- .../turret/FacilityTurretControl.scala | 39 ++- .../net/psforever/objects/zones/Zone.scala | 6 +- 11 files changed, 392 insertions(+), 174 deletions(-) create mode 100644 src/main/scala/net/psforever/objects/serverobject/structures/PoweredAmenityControl.scala diff --git a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala index 60b127950..d34936d45 100644 --- a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala +++ b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala @@ -7,6 +7,7 @@ import akka.{actor => classic} import net.psforever.actors.commands.NtuCommand import net.psforever.objects.{CommonNtuContainer, NtuContainer} import net.psforever.objects.serverobject.PlanetSideServerObject +import net.psforever.objects.serverobject.generator.Generator import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType, WarpGate} import net.psforever.objects.zones.Zone import net.psforever.persistence @@ -49,6 +50,10 @@ object BuildingActor { final case class SuppliedWithNtu() extends Command final case class NtuDepleted() extends Command + + final case class PowerOn() extends Command + + final case class PowerOff() extends Command } class BuildingActor( @@ -155,6 +160,14 @@ class BuildingActor( galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) Behaviors.same + case AmenityStateChange(obj: Generator) => + //TODO when parameter object is finally immutable, perform analysis on it to determine specific actions + val msg = if(obj.Destroyed) BuildingActor.PowerOff() else BuildingActor.PowerOn() + building.Amenities.foreach { _.Actor ! msg } + //update the map + galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) + Behaviors.same + case AmenityStateChange(_) => //TODO when parameter object is finally immutable, perform analysis on it to determine specific actions //for now, just update the map diff --git a/src/main/scala/net/psforever/objects/SpawnPoint.scala b/src/main/scala/net/psforever/objects/SpawnPoint.scala index 2860c784b..fa1e8a033 100644 --- a/src/main/scala/net/psforever/objects/SpawnPoint.scala +++ b/src/main/scala/net/psforever/objects/SpawnPoint.scala @@ -50,7 +50,7 @@ trait SpawnPoint { */ def Definition: ObjectDefinition with SpawnPointDefinition - def Offline: Boolean = psso.Destroyed + def isOffline: Boolean = psso.Destroyed /** * Determine a specific position and orientation in which to spawn the target. diff --git a/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala b/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala index 45853ba2f..f1d2f136c 100644 --- a/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala @@ -1,7 +1,6 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.implantmech -import akka.actor.Actor import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.{GlobalDefinitions, Player, SimpleItem} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} @@ -11,14 +10,15 @@ import net.psforever.objects.serverobject.damage.Damageable.Target import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity, DamageableMountable} import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior} import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableEntity} -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl} +import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} /** * An `Actor` that handles messages being dispatched to a specific `ImplantTerminalMech`. * @param mech the "mech" object being governed */ class ImplantTerminalMechControl(mech: ImplantTerminalMech) - extends Actor + extends PoweredAmenityControl with FactionAffinityBehavior.Check with MountableBehavior.Mount with MountableBehavior.Dismount @@ -33,17 +33,19 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech) def RepairableObject = mech def AutoRepairObject = mech - def receive: Receive = + def commonBehavior: Receive = checkBehavior - .orElse(mountBehavior) .orElse(dismountBehavior) - .orElse(hackableBehavior) .orElse(takesDamage) .orElse(canBeRepairedByNanoDispenser) .orElse(autoRepairBehavior) + + def poweredStateLogic : Receive = + commonBehavior + .orElse(mountBehavior) .orElse { case CommonMessages.Use(player, Some(item: SimpleItem)) - if item.Definition == GlobalDefinitions.remote_electronics_kit => + if item.Definition == GlobalDefinitions.remote_electronics_kit => //TODO setup certifications check mech.Owner match { case b: Building if (b.Faction != player.Faction || b.CaptureTerminalIsHacked) && mech.HackedBy.isEmpty => @@ -57,6 +59,12 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech) case _ => ; } + def unpoweredStateLogic: Receive = + commonBehavior + .orElse { + case _ => ; + } + override protected def MountTest( obj: PlanetSideServerObject with Mountable, seatNumber: Int, @@ -98,4 +106,25 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech) } newHealth } + + def powerTurnOffCallback(): Unit = { + //kick all occupants + val guid = mech.GUID + val zone = mech.Zone + val zoneId = zone.id + val events = zone.VehicleEvents + mech.Seats.values.foreach(seat => + seat.Occupant match { + case Some(player) => + seat.Occupant = None + player.VehicleSeated = None + if (player.HasGUID) { + events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, false, guid)) + } + case None => ; + } + ) + } + + def powerTurnOnCallback(): Unit = { } } diff --git a/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxControl.scala b/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxControl.scala index b3cd58162..e53b0b91b 100644 --- a/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxControl.scala @@ -1,8 +1,9 @@ package net.psforever.objects.serverobject.painbox -import akka.actor.{Actor, Cancellable} +import akka.actor.Cancellable +import net.psforever.actors.zone.BuildingActor import net.psforever.objects.serverobject.doors.Door -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl} import net.psforever.objects.{Default, GlobalDefinitions} import net.psforever.types.{PlanetSideEmpire, Vector3} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} @@ -10,7 +11,7 @@ import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ -class PainboxControl(painbox: Painbox) extends Actor { +class PainboxControl(painbox: Painbox) extends PoweredAmenityControl { private[this] val log = org.log4s.getLogger(s"Painbox") var painboxTick: Cancellable = Default.Cancellable var nearestDoor: Option[Door] = None @@ -20,149 +21,167 @@ class PainboxControl(painbox: Painbox) extends Actor { var disabled = false // Temporary to disable cavern non-radius fields - def receive: Receive = { - case "startup" => - if (painbox.Definition.HasNearestDoorDependency) { - (painbox.Owner match { - case obj: Building => - obj.Amenities - .collect { case door: Door => door } - .sortBy(door => Vector3.DistanceSquared(painbox.Position, door.Position)) - .headOption - case _ => - None - }) match { - case door @ Some(_) => - nearestDoor = door - case _ => - log.error( - s"Painbox ${painbox.GUID} on ${painbox.Owner.Continent} - ${painbox.Position} can not find a door that it is dependent on" - ) + val initialStartup: Unit = { + if (painbox.Definition.HasNearestDoorDependency) { + (painbox.Owner match { + case obj : Building => + obj.Amenities + .collect { case door : Door => door } + .sortBy(door => Vector3.DistanceSquared(painbox.Position, door.Position)) + .headOption + case _ => + None + }) match { + case door@Some(_) => + nearestDoor = door + case _ => + log.error( + s"Painbox ${painbox.GUID} on ${painbox.Owner.Continent} - ${painbox.Position} can not find a door that it is dependent on" + ) + } + } + else { + if (painbox.Definition.Radius == 0f) { + if (painbox.Owner.Continent.matches("c[0-9]")) { + // todo: handle non-radius painboxes in caverns properly + log.warn(s"Skipping initialization of ${painbox.GUID} on ${painbox.Owner.Continent} - ${painbox.Position}") + disabled = true } - } else { - if (painbox.Definition.Radius == 0f) { - if (painbox.Owner.Continent.matches("c[0-9]")) { - // todo: handle non-radius painboxes in caverns properly - log.warn(s"Skipping initialization of ${painbox.GUID} on ${painbox.Owner.Continent} - ${painbox.Position}") - disabled = true - } else { - painbox.Owner match { - case obj: Building => - val planarRange = 16.5f - val aboveRange = 5 - val belowRange = 5 + else { + painbox.Owner match { + case obj : Building => + val planarRange = 16.5f + val aboveRange = 5 + val belowRange = 5 + // Find amenities within the specified range + val nearbyAmenities = obj.Amenities + .filter(amenity => + amenity.Position != Vector3.Zero + && (amenity.Definition == GlobalDefinitions.mb_locker + || amenity.Definition == GlobalDefinitions.respawn_tube + || amenity.Definition == GlobalDefinitions.spawn_terminal + || amenity.Definition == GlobalDefinitions.order_terminal + || amenity.Definition == GlobalDefinitions.door) + && amenity.Position.x > painbox.Position.x - planarRange && amenity.Position.x < painbox.Position.x + planarRange + && amenity.Position.y > painbox.Position.y - planarRange && amenity.Position.y < painbox.Position.y + planarRange + && amenity.Position.z > painbox.Position.z - belowRange && amenity.Position.z < painbox.Position.z + aboveRange + ) - // Find amenities within the specified range - val nearbyAmenities = obj.Amenities - .filter(amenity => - amenity.Position != Vector3.Zero - && (amenity.Definition == GlobalDefinitions.mb_locker - || amenity.Definition == GlobalDefinitions.respawn_tube - || amenity.Definition == GlobalDefinitions.spawn_terminal - || amenity.Definition == GlobalDefinitions.order_terminal - || amenity.Definition == GlobalDefinitions.door) - && amenity.Position.x > painbox.Position.x - planarRange && amenity.Position.x < painbox.Position.x + planarRange - && amenity.Position.y > painbox.Position.y - planarRange && amenity.Position.y < painbox.Position.y + planarRange - && amenity.Position.z > painbox.Position.z - belowRange && amenity.Position.z < painbox.Position.z + aboveRange - ) - - // Calculate bounding box of amenities - bBoxMinCorner = Vector3( - nearbyAmenities.minBy(amenity => amenity.Position.x).Position.x, - nearbyAmenities.minBy(amenity => amenity.Position.y).Position.y, - nearbyAmenities.minBy(x => x.Position.z).Position.z - ) - bBoxMaxCorner = Vector3( - nearbyAmenities.maxBy(amenity => amenity.Position.x).Position.x, - nearbyAmenities.maxBy(amenity => amenity.Position.y).Position.y, - painbox.Position.z - ) - bBoxMidPoint = Vector3( - (bBoxMinCorner.x + bBoxMaxCorner.x) / 2, - (bBoxMinCorner.y + bBoxMaxCorner.y) / 2, - (bBoxMinCorner.z + bBoxMaxCorner.z) / 2 - ) - case _ => None - } + // Calculate bounding box of amenities + bBoxMinCorner = Vector3( + nearbyAmenities.minBy(amenity => amenity.Position.x).Position.x, + nearbyAmenities.minBy(amenity => amenity.Position.y).Position.y, + nearbyAmenities.minBy(x => x.Position.z).Position.z + ) + bBoxMaxCorner = Vector3( + nearbyAmenities.maxBy(amenity => amenity.Position.x).Position.x, + nearbyAmenities.maxBy(amenity => amenity.Position.y).Position.y, + painbox.Position.z + ) + bBoxMidPoint = Vector3( + (bBoxMinCorner.x + bBoxMaxCorner.x) / 2, + (bBoxMinCorner.y + bBoxMaxCorner.y) / 2, + (bBoxMinCorner.z + bBoxMaxCorner.z) / 2 + ) + case _ => None } } } - - if (!disabled) { - context.become(Stopped) - } - - case _ => ; + } + if (!disabled) { + self ! BuildingActor.PowerOff() + } } - def Running: Receive = { + var commonBehavior: Receive = { + case "startup" => + if (bBoxMidPoint == Vector3.Zero) { + initialStartup() + } + case Painbox.Stop() => - context.become(Stopped) painboxTick.cancel() painboxTick = Default.Cancellable + } - case Painbox.Tick() => - //todo: Account for overlapping pain fields - //todo: Pain module - //todo: REK boosting - val guid = painbox.GUID - val owner = painbox.Owner.asInstanceOf[Building] - val faction = owner.Faction - if ( - faction != PlanetSideEmpire.NEUTRAL && (nearestDoor match { - case Some(door) => door.Open.nonEmpty; - case _ => true - }) - ) { - val events = painbox.Zone.AvatarEvents - val damage = painbox.Definition.Damage - val radius = painbox.Definition.Radius * painbox.Definition.Radius - val position = painbox.Position + def poweredStateLogic: Receive = + commonBehavior + .orElse { + case Painbox.Start() if isPowered => + painboxTick.cancel() + painboxTick = context.system.scheduler.scheduleWithFixedDelay(0 seconds, 1 second, self, Painbox.Tick()) - if (painbox.Definition.Radius != 0f) { - // Spherical pain field - owner.PlayersInSOI - .collect { - case p - if p.Faction != faction - && p.Health > 0 - && Vector3.DistanceSquared(p.Position, position) < radius => - events ! AvatarServiceMessage(p.Name, AvatarAction.EnvironmentalDamage(p.GUID, guid, damage)) - } - } else { - // Bounding box pain field - owner.PlayersInSOI - .collect { - case p - if p.Faction != faction - && p.Health > 0 => - /* - This may be cpu intensive with a large number of players in SOI. Further performance tweaking may be required - The bounding box is calculated aligned to the world XY axis, instead of rotating the painbox corners to match the base rotation - we instead rotate the player's current coordinates to match the base rotation, which allows for much simplified checking of if the player is - within the bounding box - */ - val playerRot = - Vector3.PlanarRotateAroundPoint(p.Position, bBoxMidPoint, painbox.Owner.Orientation.z.toRadians) - if ( - bBoxMinCorner.x <= playerRot.x && playerRot.x <= bBoxMaxCorner.x && bBoxMinCorner.y <= playerRot.y && playerRot.y <= bBoxMaxCorner.y - && playerRot.z >= bBoxMinCorner.z && playerRot.z <= bBoxMaxCorner.z - ) { - events ! AvatarServiceMessage(p.Name, AvatarAction.EnvironmentalDamage(p.GUID, guid, damage)) + case Painbox.Tick() => + //todo: Account for overlapping pain fields + //todo: Pain module + //todo: REK boosting + val guid = painbox.GUID + val owner = painbox.Owner.asInstanceOf[Building] + val faction = owner.Faction + if ( + isPowered && faction != PlanetSideEmpire.NEUTRAL && (nearestDoor match { + case Some(door) => door.Open.nonEmpty; + case _ => true + }) + ) { + val events = painbox.Zone.AvatarEvents + val damage = painbox.Definition.Damage + val radius = painbox.Definition.Radius * painbox.Definition.Radius + val position = painbox.Position + + if (painbox.Definition.Radius != 0f) { + // Spherical pain field + owner.PlayersInSOI + .collect { + case p + if p.Faction != faction + && p.Health > 0 + && Vector3.DistanceSquared(p.Position, position) < radius => + events ! AvatarServiceMessage(p.Name, AvatarAction.EnvironmentalDamage(p.GUID, guid, damage)) + } + } else { + // Bounding box pain field + owner.PlayersInSOI + .collect { + case p + if p.Faction != faction + && p.Health > 0 => + /* + This may be cpu intensive with a large number of players in SOI. Further performance tweaking may be required + The bounding box is calculated aligned to the world XY axis, instead of rotating the painbox corners to match the base rotation + we instead rotate the player's current coordinates to match the base rotation, which allows for much simplified checking of if the player is + within the bounding box + */ + val playerRot = + Vector3.PlanarRotateAroundPoint(p.Position, bBoxMidPoint, painbox.Owner.Orientation.z.toRadians) + if ( + bBoxMinCorner.x <= playerRot.x && playerRot.x <= bBoxMaxCorner.x && bBoxMinCorner.y <= playerRot.y && playerRot.y <= bBoxMaxCorner.y + && playerRot.z >= bBoxMinCorner.z && playerRot.z <= bBoxMaxCorner.z + ) { + events ! AvatarServiceMessage(p.Name, AvatarAction.EnvironmentalDamage(p.GUID, guid, damage)) + } } } - } + } + + case _ => ; } - case _ => ; + def unpoweredStateLogic: Receive = + commonBehavior + .orElse { + case _ => ; + } + + def powerTurnOffCallback(): Unit = { + self ! Painbox.Stop() } - def Stopped: Receive = { - case Painbox.Start() => - context.become(Running) - painboxTick.cancel() - painboxTick = context.system.scheduler.scheduleWithFixedDelay(0 seconds, 1 second, self, Painbox.Tick()) - - case _ => ; + def powerTurnOnCallback(): Unit = { + painbox.Owner match { + case b: Building if b.PlayersInSOI.nonEmpty => + self ! Painbox.Start() + case _ => ; + } } } diff --git a/src/main/scala/net/psforever/objects/serverobject/structures/PoweredAmenityControl.scala b/src/main/scala/net/psforever/objects/serverobject/structures/PoweredAmenityControl.scala new file mode 100644 index 000000000..c73401bf4 --- /dev/null +++ b/src/main/scala/net/psforever/objects/serverobject/structures/PoweredAmenityControl.scala @@ -0,0 +1,39 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.serverobject.structures + +import akka.actor.Actor +import net.psforever.actors.zone.BuildingActor + +trait PoweredAmenityControl extends Actor { + private var powered: Boolean = true + + final def receive: Receive = powerOnCondition + + final def powerOnCondition: Receive = { + case BuildingActor.PowerOff() => + powered = false + context.become(powerOffCondition) + powerTurnOffCallback() + case msg => + poweredStateLogic.apply(msg) + } + + final def powerOffCondition: Receive = { + case BuildingActor.PowerOn() => + powered = true + context.become(powerOnCondition) + powerTurnOnCallback() + case msg => + unpoweredStateLogic.apply(msg) + } + + def isPowered: Boolean = powered + + def poweredStateLogic: Receive + + def unpoweredStateLogic: Receive + + def powerTurnOnCallback(): Unit + + def powerTurnOffCallback(): Unit +} diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala index 0ff531ebd..7773a846c 100644 --- a/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala @@ -1,14 +1,16 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.terminals -import akka.actor.{Actor, ActorRef, Cancellable} +import akka.actor.{ActorRef, Cancellable} import net.psforever.objects._ import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior import net.psforever.objects.serverobject.damage.DamageableAmenity import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior} import net.psforever.objects.serverobject.repair.RepairableAmenity -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl} +import net.psforever.services.Service +import net.psforever.services.local.{LocalAction, LocalServiceMessage} import scala.collection.mutable import scala.concurrent.duration._ @@ -20,7 +22,7 @@ import scala.concurrent.duration._ * @param term the proximity unit (terminal) */ class ProximityTerminalControl(term: Terminal with ProximityUnit) - extends Actor + extends PoweredAmenityControl with FactionAffinityBehavior.Check with HackableBehavior.GenericHackable with DamageableAmenity @@ -35,11 +37,20 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit) val callbacks: mutable.ListBuffer[ActorRef] = new mutable.ListBuffer[ActorRef]() val log = org.log4s.getLogger - def receive: Receive = - checkBehavior + val commonBehavior: Receive = checkBehavior + .orElse(takesDamage) + .orElse(canBeRepairedByNanoDispenser) + .orElse { + case CommonMessages.Unuse(_, Some(target: PlanetSideGameObject)) => + Unuse(target, term.Continent) + + case CommonMessages.Unuse(_, _) => + log.warn(s"unexpected format for CommonMessages.Unuse in this context") + } + + def poweredStateLogic: Receive = + commonBehavior .orElse(hackableBehavior) - .orElse(takesDamage) - .orElse(canBeRepairedByNanoDispenser) .orElse { case CommonMessages.Use(player, Some(item: SimpleItem)) if item.Definition == GlobalDefinitions.remote_electronics_kit => @@ -66,12 +77,6 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit) case CommonMessages.Use(_, _) => log.warn(s"unexpected format for CommonMessages.Use in this context") - case CommonMessages.Unuse(_, Some(target: PlanetSideGameObject)) => - Unuse(target, term.Continent) - - case CommonMessages.Unuse(_, _) => - log.warn(s"unexpected format for CommonMessages.Unuse in this context") - case ProximityTerminalControl.TerminalAction() => val proxDef = term.Definition.asInstanceOf[ProximityDefinition] val validateFunc: PlanetSideGameObject => Boolean = @@ -99,6 +104,19 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit) case _ => } + def unpoweredStateLogic : Receive = commonBehavior + .orElse { + case CommonMessages.Use(_, _) => + log.warn(s"unexpected format for CommonMessages.Use in this context") + + case CommonMessages.Unuse(_, Some(target: PlanetSideGameObject)) => + Unuse(target, term.Continent) + + case CommonMessages.Unuse(_, _) => + log.warn(s"unexpected format for CommonMessages.Unuse in this context") + case _ => ; + } + def Use(target: PlanetSideGameObject, zone: String, callback: ActorRef): Unit = { val hadNoUsers = term.NumberUsers == 0 if (term.AddUser(target)) { @@ -145,6 +163,22 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit) } } + def powerTurnOffCallback() : Unit = { + //clear effect callbacks + terminalAction.cancel() + if (callbacks.nonEmpty) { + callbacks.clear() + TerminalObject.Zone.LocalEvents ! Terminal.StopProximityEffect(term) + } + //clear hack state + if (term.HackedBy.nonEmpty) { + val zone = term.Zone + zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.ClearTemporaryHack(Service.defaultPlayerGUID, term)) + } + } + + def powerTurnOnCallback() : Unit = { } + override def toString: String = term.Definition.Name } diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala index f052507bd..c0ef203f3 100644 --- a/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.terminals -import akka.actor.{Actor, ActorRef} +import akka.actor.ActorRef import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.{GlobalDefinitions, SimpleItem} import net.psforever.objects.serverobject.CommonMessages @@ -10,14 +10,16 @@ import net.psforever.objects.serverobject.damage.Damageable.Target import net.psforever.objects.serverobject.damage.{Damageable, DamageableAmenity} import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior} import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableAmenity} -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl} +import net.psforever.services.Service +import net.psforever.services.local.{LocalAction, LocalServiceMessage} /** * An `Actor` that handles messages being dispatched to a specific `Terminal`. * @param term the `Terminal` object being governed */ class TerminalControl(term: Terminal) - extends Actor + extends PoweredAmenityControl with FactionAffinityBehavior.Check with HackableBehavior.GenericHackable with DamageableAmenity @@ -29,18 +31,20 @@ class TerminalControl(term: Terminal) def RepairableObject = term def AutoRepairObject = term - def receive: Receive = - checkBehavior + val commonBehavior: Receive = checkBehavior + .orElse(takesDamage) + .orElse(canBeRepairedByNanoDispenser) + .orElse(autoRepairBehavior) + + def poweredStateLogic : Receive = + commonBehavior .orElse(hackableBehavior) - .orElse(takesDamage) - .orElse(canBeRepairedByNanoDispenser) - .orElse(autoRepairBehavior) .orElse { case Terminal.Request(player, msg) => TerminalControl.Dispatch(sender(), term, Terminal.TerminalMessage(player, msg, term.Request(player, msg))) case CommonMessages.Use(player, Some(item: SimpleItem)) - if item.Definition == GlobalDefinitions.remote_electronics_kit => + if item.Definition == GlobalDefinitions.remote_electronics_kit => //TODO setup certifications check term.Owner match { case b: Building if (b.Faction != player.Faction || b.CaptureTerminalIsHacked) && term.HackedBy.isEmpty => @@ -55,6 +59,14 @@ class TerminalControl(term: Terminal) case _ => ; } + def unpoweredStateLogic : Receive = commonBehavior + .orElse { + case Terminal.Request(player, msg) => + sender() ! Terminal.TerminalMessage(player, msg, Terminal.NoDeal()) + + case _ => ; + } + override protected def DamageAwareness(target : Target, cause : ResolvedProjectile, amount : Any) : Unit = { tryAutoRepair() super.DamageAwareness(target, cause, amount) @@ -62,6 +74,10 @@ class TerminalControl(term: Terminal) override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile) : Unit = { tryAutoRepair() + if (term.HackedBy.nonEmpty) { + val zone = term.Zone + zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.ClearTemporaryHack(Service.defaultPlayerGUID, term)) + } super.DestructionAwareness(target, cause) } @@ -73,6 +89,16 @@ class TerminalControl(term: Terminal) newHealth } + def powerTurnOffCallback() : Unit = { + //clear hack state + if (term.HackedBy.nonEmpty) { + val zone = term.Zone + zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.ClearTemporaryHack(Service.defaultPlayerGUID, term)) + } + } + + def powerTurnOnCallback() : Unit = { } + override def toString: String = term.Definition.Name } diff --git a/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTube.scala b/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTube.scala index caf818025..dd754d0d8 100644 --- a/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTube.scala +++ b/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTube.scala @@ -10,6 +10,10 @@ import net.psforever.objects.serverobject.structures.Amenity * @param tDef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields */ class SpawnTube(tDef: SpawnTubeDefinition) extends Amenity with SpawnPoint { + var offline: Boolean = false + + override def isOffline: Boolean = offline || super.isOffline + def Definition: SpawnTubeDefinition = tDef } diff --git a/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala b/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala index 38a9fde87..63b650666 100644 --- a/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala @@ -1,21 +1,20 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.tube -import akka.actor.Actor import net.psforever.actors.zone.BuildingActor import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior import net.psforever.objects.serverobject.damage.Damageable.Target import net.psforever.objects.serverobject.damage.DamageableAmenity import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, Repairable, RepairableAmenity} -import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl} /** * An `Actor` that handles messages being dispatched to a specific `SpawnTube`. * @param tube the `SpawnTube` object being governed */ class SpawnTubeControl(tube: SpawnTube) - extends Actor + extends PoweredAmenityControl with FactionAffinityBehavior.Check with DamageableAmenity with RepairableAmenity @@ -25,11 +24,19 @@ class SpawnTubeControl(tube: SpawnTube) def RepairableObject = tube def AutoRepairObject = tube - def receive: Receive = - checkBehavior - .orElse(takesDamage) - .orElse(canBeRepairedByNanoDispenser) - .orElse(autoRepairBehavior) + val commonBehavior: Receive = checkBehavior + .orElse(takesDamage) + .orElse(canBeRepairedByNanoDispenser) + .orElse(autoRepairBehavior) + + def poweredStateLogic: Receive = + commonBehavior + .orElse { + case _ => ; + } + + def unpoweredStateLogic: Receive = + commonBehavior .orElse { case _ => ; } @@ -64,5 +71,21 @@ class SpawnTubeControl(tube: SpawnTube) } } + def powerTurnOffCallback(): Unit = { + tube.offline = false + tube.Owner match { + case b: Building => b.Actor ! BuildingActor.AmenityStateChange(tube) + case _ => ; + } + } + + def powerTurnOnCallback(): Unit = { + tube.offline = true + tube.Owner match { + case b: Building => b.Actor ! BuildingActor.AmenityStateChange(tube) + case _ => ; + } + } + override def toString: String = tube.Definition.Name } diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala index 9e23e4505..9325475e4 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala @@ -1,7 +1,6 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.turret -import akka.actor.Actor import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.{Default, GlobalDefinitions, Player, Tool} import net.psforever.objects.equipment.{Ammo, JammableMountedWeapons} @@ -11,8 +10,10 @@ import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior import net.psforever.objects.serverobject.damage.{Damageable, DamageableWeaponTurret} import net.psforever.objects.serverobject.hackable.GenericHackables import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableWeaponTurret} +import net.psforever.objects.serverobject.structures.PoweredAmenityControl import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage} +import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ @@ -27,7 +28,7 @@ import scala.concurrent.duration._ * @param turret the `MannedTurret` object being governed */ class FacilityTurretControl(turret: FacilityTurret) - extends Actor + extends PoweredAmenityControl with FactionAffinityBehavior.Check with MountableBehavior.TurretMount with MountableBehavior.Dismount @@ -51,14 +52,17 @@ class FacilityTurretControl(turret: FacilityTurret) stopAutoRepair() } - def receive: Receive = + def commonBehavior: Receive = checkBehavior .orElse(jammableBehavior) - .orElse(mountBehavior) .orElse(dismountBehavior) .orElse(takesDamage) .orElse(canBeRepairedByNanoDispenser) .orElse(autoRepairBehavior) + + def poweredStateLogic: Receive = + commonBehavior + .orElse(mountBehavior) .orElse { case CommonMessages.Use(player, Some((item: Tool, upgradeValue: Int))) if player.Faction == turret.Faction && @@ -113,6 +117,12 @@ class FacilityTurretControl(turret: FacilityTurret) case _ => ; } + def unpoweredStateLogic: Receive = + commonBehavior + .orElse { + case _ => ; + } + override protected def DamageAwareness(target : Damageable.Target, cause : ResolvedProjectile, amount : Any) : Unit = { tryAutoRepair() super.DamageAwareness(target, cause, amount) @@ -146,4 +156,25 @@ class FacilityTurretControl(turret: FacilityTurret) events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 50, 0)) events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 51, 0)) } + + def powerTurnOffCallback(): Unit = { + //kick all occupants + val guid = turret.GUID + val zone = turret.Zone + val zoneId = zone.id + val events = zone.VehicleEvents + turret.Seats.values.foreach(seat => + seat.Occupant match { + case Some(player) => + seat.Occupant = None + player.VehicleSeated = None + if (player.HasGUID) { + events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, false, guid)) + } + case None => ; + } + ) + } + + def powerTurnOnCallback(): Unit = { } } diff --git a/src/main/scala/net/psforever/objects/zones/Zone.scala b/src/main/scala/net/psforever/objects/zones/Zone.scala index bc1a2e23c..c94876024 100644 --- a/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -354,8 +354,8 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) { .filter { case (building, spawns) => spawns.nonEmpty && - spawns.exists(_.Offline == false) && - structures.contains(building.BuildingType) + spawns.exists(_.isOffline == false) && + structures.contains(building.BuildingType) } .filter { case (building, _) => @@ -368,7 +368,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) { } .map { case (building, spawns: List[SpawnPoint]) => - (building, spawns.filter(!_.Offline)) + (building, spawns.filter(!_.isOffline)) } .concat( (if (ams) Vehicles else List()) From 4a0805b698ee2906a4d77bd359b84714fc29c5c3 Mon Sep 17 00:00:00 2001 From: "Jason_DiDonato@yahoo.com" Date: Tue, 13 Oct 2020 18:47:05 -0400 Subject: [PATCH 2/7] logic for door opening has been exorcised from SessionActor into DoorControl --- .../actors/session/SessionActor.scala | 57 +------------ .../objects/serverobject/doors/Door.scala | 10 --- .../serverobject/doors/DoorControl.scala | 83 +++++++++++++++++-- .../serverobject/painbox/PainboxControl.scala | 2 +- src/test/scala/objects/DoorTest.scala | 6 +- 5 files changed, 82 insertions(+), 76 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index a2919e2a5..ec3e800ff 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -542,9 +542,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con case ProgressEvent(delta, finishedAction, stepAction, tick) => HandleProgressChange(delta, finishedAction, stepAction, tick) - case Door.DoorMessage(tplayer, msg, order) => - HandleDoorMessage(tplayer, msg, order) - case GalaxyServiceResponse(_, reply) => reply match { case GalaxyResponse.HotSpotUpdate(zone_index, priority, hot_spot_info) => @@ -2189,36 +2186,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con } } - /** - * na - * @param tplayer na - * @param msg na - * @param order na - */ - def HandleDoorMessage(tplayer: Player, msg: UseItemMessage, order: Door.Exchange): Unit = { - val door_guid = msg.object_guid - order match { - case Door.OpenEvent() => - continent.GUID(door_guid) match { - case Some(door: Door) => - sendResponse(GenericObjectStateMsg(door_guid, 16)) - continent.LocalEvents ! LocalServiceMessage( - continent.id, - LocalAction.DoorOpens(tplayer.GUID, continent, door) - ) - - case _ => - log.warn(s"door $door_guid wanted to be opened but could not be found") - } - - case Door.CloseEvent() => - sendResponse(GenericObjectStateMsg(door_guid, 17)) - continent.LocalEvents ! LocalServiceMessage(continent.id, LocalAction.DoorCloses(tplayer.GUID, door_guid)) - - case Door.NoEvent() => ; - } - } - /** * na * @param toChannel na @@ -4477,29 +4444,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con } ValidObject(object_guid) match { case Some(door: Door) => - if ( - player.Faction == door.Faction || (continent.map.doorToLock.get(object_guid.guid) match { - case Some(lock_guid) => - val lock = continent.GUID(lock_guid).get.asInstanceOf[IFFLock] - val owner = lock.Owner.asInstanceOf[Building] - val playerIsOnInside = Vector3.ScalarProjection(lock.Outwards, player.Position - door.Position) < 0f - - // If an IFF lock exists and the IFF lock faction doesn't match the current player and one of the following conditions are met open the door: - // The player is on the inside of the door, determined by the lock orientation - // The lock is hacked - // A base is hacked - // A base is neutral - // todo: A base is out of power (generator down) - - playerIsOnInside || lock.HackedBy.isDefined || owner.CaptureTerminalIsHacked || lock.Faction == PlanetSideEmpire.NEUTRAL - case None => !door.isOpen // If there's no linked IFF lock just open the door if it's closed. - }) - ) { - door.Actor ! Door.Use(player, msg) - } else if (door.isOpen) { - //the door is open globally ... except on our screen - sendResponse(GenericObjectStateMsg(object_guid, 16)) - } + door.Actor ! CommonMessages.Use(player) case Some(resourceSilo: ResourceSilo) => resourceSilo.Actor ! CommonMessages.Use(player) diff --git a/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala b/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala index 56e7193be..b10574bf5 100644 --- a/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala +++ b/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala @@ -25,16 +25,6 @@ class Door(private val ddef: DoorDefinition) extends Amenity { Open } - def Use(player: Player, msg: UseItemMessage): Door.Exchange = { - if (openState.isEmpty) { - openState = Some(player) - Door.OpenEvent() - } else { - openState = None - Door.CloseEvent() - } - } - def Definition: DoorDefinition = ddef } diff --git a/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala b/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala index 8614db3c2..10fa58b8a 100644 --- a/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala @@ -1,21 +1,90 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.doors -import akka.actor.Actor +import net.psforever.objects.Player +import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} +import net.psforever.objects.serverobject.locks.IFFLock +import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl} +import net.psforever.services.Service +import net.psforever.services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse} +import net.psforever.types.{PlanetSideEmpire, Vector3} /** * An `Actor` that handles messages being dispatched to a specific `Door`. * @param door the `Door` object being governed */ -class DoorControl(door: Door) extends Actor with FactionAffinityBehavior.Check { +class DoorControl(door: Door) + extends PoweredAmenityControl + with FactionAffinityBehavior.Check { def FactionObject: FactionAffinity = door - def receive: Receive = - checkBehavior.orElse { - case Door.Use(player, msg) => - sender() ! Door.DoorMessage(player, msg, door.Use(player, msg)) + val commonBehavior: Receive = checkBehavior - case _ => ; + def poweredStateLogic: Receive = + commonBehavior + .orElse { + case CommonMessages.Use(player, _) => + val zone = door.Zone + val doorGUID = door.GUID + if ( + player.Faction == door.Faction || (zone.GUID(zone.map.doorToLock.getOrElse(doorGUID.guid, 0)) match { + case Some(lock: IFFLock) => + val owner = lock.Owner.asInstanceOf[Building] + val playerIsOnInside = Vector3.ScalarProjection(lock.Outwards, player.Position - door.Position) < 0f + /* + If an IFF lock exists and + the IFF lock faction doesn't match the current player and + one of the following conditions are met: + 1. player is on the inside of the door (determined by the lock orientation) + 2. lock is hacked + 3. facility capture terminal has been hacked + 4. base is neutral + ... open the door. + */ + playerIsOnInside || lock.HackedBy.isDefined || owner.CaptureTerminalIsHacked || lock.Faction == PlanetSideEmpire.NEUTRAL + case _ => true // no linked IFF lock, just try open the door + }) + ) { + openDoor(player) + } + + case _ => ; + } + + def unpoweredStateLogic: Receive = { + commonBehavior + .orElse { + case CommonMessages.Use(player, _) => + //without power, the door opens freely + openDoor(player) + + case _ => ; + } + } + + def openDoor(player: Player): Unit = { + val zone = door.Zone + val doorGUID = door.GUID + if (!door.isOpen) { + //global open + door.Open = player + zone.LocalEvents ! LocalServiceMessage( + zone.id, + LocalAction.DoorOpens(Service.defaultPlayerGUID, zone, door) + ) } + else { + //the door should already open, but the requesting player does not see it as open + sender() ! LocalServiceResponse( + player.Name, + Service.defaultPlayerGUID, + LocalResponse.DoorOpens(doorGUID) + ) + } + } + + override def powerTurnOffCallback() : Unit = { } + + override def powerTurnOnCallback() : Unit = { } } diff --git a/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxControl.scala b/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxControl.scala index e53b0b91b..8e09e30cb 100644 --- a/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxControl.scala @@ -21,7 +21,7 @@ class PainboxControl(painbox: Painbox) extends PoweredAmenityControl { var disabled = false // Temporary to disable cavern non-radius fields - val initialStartup: Unit = { + def initialStartup(): Unit = { if (painbox.Definition.HasNearestDoorDependency) { (painbox.Owner match { case obj : Building => diff --git a/src/test/scala/objects/DoorTest.scala b/src/test/scala/objects/DoorTest.scala index e1ef43bb8..742af526e 100644 --- a/src/test/scala/objects/DoorTest.scala +++ b/src/test/scala/objects/DoorTest.scala @@ -59,10 +59,12 @@ class DoorTest extends Specification { ) val door = Door(GlobalDefinitions.door) door.Open.isEmpty mustEqual true - door.Use(player, msg) + door.Open = player + door.isOpen mustEqual true door.Open.contains(player) mustEqual true - door.Use(player, msg) + door.Open = None door.Open.isEmpty mustEqual true + door.isOpen mustEqual false } } } From e1c5cd90a7075869afb6d59fb393eaa56097b399 Mon Sep 17 00:00:00 2001 From: "Jason_DiDonato@yahoo.com" Date: Wed, 14 Oct 2020 22:40:27 -0400 Subject: [PATCH 3/7] updated ntu -> power workflow by making event progress - the silo will report the ntu state to the generator and the generator will only report on the power state if it is not destroyed; if the generator is destroyed, that is where the messaging will end, until the generator comes back online; the events for these state change are somewhat arbitrary --- .../actors/session/SessionActor.scala | 25 +++++-- .../psforever/actors/zone/BuildingActor.scala | 71 +++++++++++++++++-- .../generator/GeneratorControl.scala | 48 ++++++++++--- .../ImplantTerminalMechControl.scala | 9 ++- .../repair/AmenityAutoRepair.scala | 17 ++++- .../serverobject/structures/Building.scala | 11 ++- .../terminals/TerminalControl.scala | 9 ++- .../serverobject/tube/SpawnTubeControl.scala | 6 ++ .../turret/FacilityTurretControl.scala | 11 ++- .../game/PlanetsideAttributeMessage.scala | 2 +- 10 files changed, 178 insertions(+), 31 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index ec3e800ff..b2a31c009 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -3646,6 +3646,12 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con if (isMovingPlus) { CancelZoningProcessWithDescriptiveReason("cancel_motion") } + if (!player.Crouching && is_crouching) { + continent.Buildings.values.find { b => Vector3.Distance(player.Position, b.Position) <= b.Definition.SOIRadius} match { + case Some(b) => ; + case _ => ; + } + } player.Position = pos player.Velocity = vel player.Orientation = Vector3(player.Orientation.x, pitch, yaw) @@ -6554,13 +6560,20 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con */ def configZone(zone: Zone): Unit = { zone.Buildings.values.foreach(building => { - sendResponse(SetEmpireMessage(building.GUID, building.Faction)) - - // Synchronise capitol force dome state - if (building.IsCapitol && building.ForceDomeActive) { - sendResponse(GenericObjectActionMessage(building.GUID, 13)) + val guid = building.GUID + sendResponse(SetEmpireMessage(guid, building.Faction)) + // power + building.Generator match { + case Some(obj) if obj.Condition != PlanetSideGeneratorState.Normal => + sendResponse(PlanetsideAttributeMessage(guid, 48, 1)) + sendResponse(PlanetsideAttributeMessage(guid, 38, 0)) + case _ => ; } - // Synchronise amenities + // capitol force dome state + if (building.IsCapitol && building.ForceDomeActive) { + sendResponse(GenericObjectActionMessage(guid, 13)) + } + // amenities building.Amenities.collect { case obj if obj.Destroyed => configAmenityAsDestroyed(obj) case obj => configAmenityAsWorking(obj) diff --git a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala index d34936d45..132017900 100644 --- a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala +++ b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala @@ -9,8 +9,10 @@ import net.psforever.objects.{CommonNtuContainer, NtuContainer} import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.generator.Generator import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType, WarpGate} +import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.zones.Zone import net.psforever.persistence +import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.types.PlanetSideEmpire import net.psforever.util.Database._ import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage} @@ -43,7 +45,11 @@ object BuildingActor { // Once they do, we won't need this anymore final case class MapUpdate() extends Command - final case class AmenityStateChange(obj: Amenity) extends Command + final case class AmenityStateChange(obj: Amenity, data: Option[Any]) extends Command + + object AmenityStateChange{ + def apply(obj: Amenity): AmenityStateChange = AmenityStateChange(obj, None) + } final case class Ntu(command: NtuCommand.Command) extends Command @@ -160,29 +166,50 @@ class BuildingActor( galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) Behaviors.same - case AmenityStateChange(obj: Generator) => + case AmenityStateChange(obj: Generator, data) => //TODO when parameter object is finally immutable, perform analysis on it to determine specific actions - val msg = if(obj.Destroyed) BuildingActor.PowerOff() else BuildingActor.PowerOn() - building.Amenities.foreach { _.Actor ! msg } + data match { + case Some("overloaded") => + powerLost() + val zone = building.Zone + val msg = AvatarAction.PlanetsideAttributeToAll(building.GUID, 46, 2) + building.PlayersInSOI.foreach { player => + zone.AvatarEvents ! AvatarServiceMessage(player.Name, msg) + } //??? + case Some("repaired") => + powerRestored() + val zone = building.Zone + building.PlayersInSOI.foreach { player => + val msg = AvatarAction.PlanetsideAttributeToAll(building.GUID, 46, 0) + zone.AvatarEvents ! AvatarServiceMessage(player.Name, msg) + } //reset ??? + case _ => ; + } //update the map galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) Behaviors.same - case AmenityStateChange(_) => + case AmenityStateChange(_, _) => //TODO when parameter object is finally immutable, perform analysis on it to determine specific actions //for now, just update the map galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) Behaviors.same + case PowerOff() => + powerLost() + Behaviors.same + + case PowerOn() => + powerRestored() + Behaviors.same + case msg @ NtuDepleted() => - log.trace(s"${building.Definition.Name} ${building.Name} ntu has been depleted") building.Amenities.foreach { amenity => amenity.Actor ! msg } Behaviors.same case msg @ SuppliedWithNtu() => - log.trace(s"ntu supply has been restored to ${building.Definition.Name} ${building.Name}") building.Amenities.foreach { amenity => amenity.Actor ! msg } @@ -193,6 +220,36 @@ class BuildingActor( } } + def powerLost(): Unit = { + val zone = building.Zone + val zoneId = zone.id + val events = zone.AvatarEvents + val guid = building.GUID + val powerMsg = BuildingActor.PowerOff() + building.Amenities.foreach { amenity => + amenity.Actor ! powerMsg + } + //amenities disabled; red warning lights + events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(guid, 48, 1)) + //disable spawn target on deployment map + events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(guid, 38, 0)) + } + + def powerRestored(): Unit = { + val zone = building.Zone + val zoneId = zone.id + val events = zone.AvatarEvents + val guid = building.GUID + val powerMsg = BuildingActor.PowerOn() + building.Amenities.foreach { amenity => + amenity.Actor ! powerMsg + } + //amenities enabled; normal lights + events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(guid, 48, 0)) + //enable spawn target on deployment map + events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(guid, 38, 1)) + } + def ntu(msg: NtuCommand.Command): Behavior[Command] = { import NtuCommand._ msg match { diff --git a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala index 5a45f43f7..23a99c67e 100644 --- a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala @@ -48,7 +48,7 @@ class GeneratorControl(gen: Generator) gen.Health = 0 super.DestructionAwareness(gen, gen.LastShot.get) gen.Condition = PlanetSideGeneratorState.Destroyed - GeneratorControl.UpdateOwner(gen) + GeneratorControl.UpdateOwner(gen, Some("destroyed")) //kaboom zone.AvatarEvents ! AvatarServiceMessage( zone.id, @@ -74,13 +74,27 @@ class GeneratorControl(gen: Generator) case GeneratorControl.UnderThreatAlarm() => if (!alarmCooldownPeriod) { alarmCooldownPeriod = true - GeneratorControl.BroadcastGeneratorEvent(gen, event = 15) + GeneratorControl.BroadcastGeneratorEvent(gen, GeneratorControl.Event.UnderAttack) context.system.scheduler.scheduleOnce(delay = 5 seconds, self, GeneratorControl.AlarmReset()) } case GeneratorControl.AlarmReset() => alarmCooldownPeriod = false + case BuildingActor.NtuDepleted() => + gen.Owner match { + case b: Building if !gen.Destroyed => + b.Actor ! BuildingActor.PowerOff() + case _ => ; + } + + case BuildingActor.SuppliedWithNtu() => + gen.Owner match { + case b: Building if !gen.Destroyed => + b.Actor ! BuildingActor.PowerOn() + case _ => ; + } + case _ => ; } @@ -108,7 +122,8 @@ class GeneratorControl(gen: Generator) target.Health = 1 //temporary imminentExplosion = true context.system.scheduler.scheduleOnce(10 seconds, self, GeneratorControl.GeneratorExplodes()) - GeneratorControl.BroadcastGeneratorEvent(gen, 16) + GeneratorControl.UpdateOwner(gen, Some("overloaded")) + GeneratorControl.BroadcastGeneratorEvent(gen, GeneratorControl.Event.Overloaded) } } @@ -123,8 +138,8 @@ class GeneratorControl(gen: Generator) override def Restoration(obj: Repairable.Target): Unit = { super.Restoration(obj) gen.Condition = PlanetSideGeneratorState.Normal - GeneratorControl.UpdateOwner(gen) - GeneratorControl.BroadcastGeneratorEvent(gen, 17) + GeneratorControl.UpdateOwner(gen, Some("repaired")) + GeneratorControl.BroadcastGeneratorEvent(gen, GeneratorControl.Event.Online) } } @@ -145,13 +160,23 @@ object GeneratorControl { */ private case class AlarmReset() + /** + * na + */ + object Event extends Enumeration { + val UnderAttack = Value(15) + val Critical = Value(0) + val Overloaded = Value(16) + val Online = Value(17) + } + /** * na * @param obj na */ - private def UpdateOwner(obj: Generator): Unit = { + private def UpdateOwner(obj: Generator, data: Option[Any] = None): Unit = { obj.Owner match { - case b: Building => b.Actor ! BuildingActor.AmenityStateChange(obj) + case b: Building => b.Actor ! BuildingActor.AmenityStateChange(obj, data) case _ => ; } } @@ -161,11 +186,14 @@ object GeneratorControl { * @param target the generator * @param event the action code for the event */ - private def BroadcastGeneratorEvent(target: Generator, event: Int): Unit = { + private def BroadcastGeneratorEvent(target: Generator, event: Event.Value): Unit = { target.Owner match { case b: Building => val events = target.Zone.AvatarEvents - val msg = AvatarAction.GenericObjectAction(Service.defaultPlayerGUID, target.Owner.GUID, event) + val msg = event match { + case Event.Critical => AvatarAction.PlanetsideAttributeToAll(b.GUID, 46, 1) //critical status warning + case _ => AvatarAction.GenericObjectAction(Service.defaultPlayerGUID, b.GUID, event.id) + } b.PlayersInSOI.foreach { player => events ! AvatarServiceMessage(player.Name, msg) } @@ -185,7 +213,7 @@ object GeneratorControl { val max: Float = target.MaxHealth.toFloat if (target.Condition != PlanetSideGeneratorState.Critical && health / max < 0.51f) { //becoming critical target.Condition = PlanetSideGeneratorState.Critical - GeneratorControl.UpdateOwner(target) + GeneratorControl.UpdateOwner(target, Some("critical")) } //the generator is under attack target.Actor ! UnderThreatAlarm() diff --git a/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala b/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala index f1d2f136c..4400a3e58 100644 --- a/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala @@ -107,7 +107,12 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech) newHealth } + override def tryAutoRepair() : Boolean = { + isPowered && super.tryAutoRepair() + } + def powerTurnOffCallback(): Unit = { + stopAutoRepair() //kick all occupants val guid = mech.GUID val zone = mech.Zone @@ -126,5 +131,7 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech) ) } - def powerTurnOnCallback(): Unit = { } + def powerTurnOnCallback(): Unit = { + tryAutoRepair() + } } diff --git a/src/main/scala/net/psforever/objects/serverobject/repair/AmenityAutoRepair.scala b/src/main/scala/net/psforever/objects/serverobject/repair/AmenityAutoRepair.scala index 22d45abe9..64b925d60 100644 --- a/src/main/scala/net/psforever/objects/serverobject/repair/AmenityAutoRepair.scala +++ b/src/main/scala/net/psforever/objects/serverobject/repair/AmenityAutoRepair.scala @@ -129,10 +129,25 @@ trait AmenityAutoRepair /** * Attempt to start auto-repair operation * only if no operation is currently being processed. + * @see `actuallyTryAutoRepair` * @return `true`, if the auto-repair process started specifically due to this call; * `false`, if it was already started, or did not start */ - final def tryAutoRepair(): Boolean = { + def tryAutoRepair(): Boolean = { + actuallyTryAutoRepair() + } + + /** + * Attempt to start auto-repair operation + * only if no operation is currently being processed. + * In case that an override to the normals operations of `tryAutoRepair` is necessary, + * but the superclass can not be invoked, + * this method is the backup of those operations to initiate auto-repair. + * @see `tryAutoRepair` + * @return `true`, if the auto-repair process started specifically due to this call; + * `false`, if it was already started, or did not start + */ + final def actuallyTryAutoRepair(): Boolean = { val before = autoRepairTimer.isCancelled autoRepairStartFunc() !(before || autoRepairTimer.isCancelled) diff --git a/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala b/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala index e1ba19faa..39ab6939d 100644 --- a/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala +++ b/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala @@ -114,6 +114,13 @@ class Building( } } + def Generator: Option[Generator] = { + Amenities.find(_.isInstanceOf[Generator]) match { + case Some(obj: Generator) => Some(obj) + case _ => None + } + } + def CaptureTerminal: Option[CaptureTerminal] = { Amenities.find(_.isInstanceOf[CaptureTerminal]) match { case Some(term) => Some(term.asInstanceOf[CaptureTerminal]) @@ -187,8 +194,8 @@ class Building( (false, PlanetSideEmpire.NEUTRAL, 0L) } //if we have no generator, assume the state is "Normal" - val (generatorState, boostGeneratorPain) = Amenities.find(x => x.isInstanceOf[Generator]) match { - case Some(obj: Generator) => + val (generatorState, boostGeneratorPain) = Generator match { + case Some(obj) => (obj.Condition, false) // todo: poll pain field strength case _ => (PlanetSideGeneratorState.Normal, false) diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala index c0ef203f3..fcfdba3ce 100644 --- a/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala @@ -89,7 +89,12 @@ class TerminalControl(term: Terminal) newHealth } + override def tryAutoRepair() : Boolean = { + isPowered && super.tryAutoRepair() + } + def powerTurnOffCallback() : Unit = { + stopAutoRepair() //clear hack state if (term.HackedBy.nonEmpty) { val zone = term.Zone @@ -97,7 +102,9 @@ class TerminalControl(term: Terminal) } } - def powerTurnOnCallback() : Unit = { } + def powerTurnOnCallback() : Unit = { + tryAutoRepair() + } override def toString: String = term.Definition.Name } diff --git a/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala b/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala index 63b650666..c7607bc14 100644 --- a/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala @@ -71,8 +71,13 @@ class SpawnTubeControl(tube: SpawnTube) } } + override def tryAutoRepair() : Boolean = { + isPowered && super.tryAutoRepair() + } + def powerTurnOffCallback(): Unit = { tube.offline = false + stopAutoRepair() tube.Owner match { case b: Building => b.Actor ! BuildingActor.AmenityStateChange(tube) case _ => ; @@ -81,6 +86,7 @@ class SpawnTubeControl(tube: SpawnTube) def powerTurnOnCallback(): Unit = { tube.offline = true + tryAutoRepair() tube.Owner match { case b: Building => b.Actor ! BuildingActor.AmenityStateChange(tube) case _ => ; diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala index 9325475e4..9734810b6 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala @@ -58,11 +58,11 @@ class FacilityTurretControl(turret: FacilityTurret) .orElse(dismountBehavior) .orElse(takesDamage) .orElse(canBeRepairedByNanoDispenser) - .orElse(autoRepairBehavior) def poweredStateLogic: Receive = commonBehavior .orElse(mountBehavior) + .orElse(autoRepairBehavior) .orElse { case CommonMessages.Use(player, Some((item: Tool, upgradeValue: Int))) if player.Faction == turret.Faction && @@ -157,7 +157,12 @@ class FacilityTurretControl(turret: FacilityTurret) events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 51, 0)) } + override def tryAutoRepair() : Boolean = { + isPowered && super.tryAutoRepair() + } + def powerTurnOffCallback(): Unit = { + stopAutoRepair() //kick all occupants val guid = turret.GUID val zone = turret.Zone @@ -176,5 +181,7 @@ class FacilityTurretControl(turret: FacilityTurret) ) } - def powerTurnOnCallback(): Unit = { } + def powerTurnOnCallback(): Unit = { + tryAutoRepair() + } } diff --git a/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala b/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala index a1539e792..d3a8044fc 100644 --- a/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala +++ b/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala @@ -144,7 +144,7 @@ import scodec.codecs._ * `45 - NTU charge bar 0-10, 5 = 50% full. Seems to apply to both ANT and NTU Silo (possibly siphons?)`
* `46 - Sends "Generator damage is at a critical level!" message` * `47 - Sets base NTU level to CRITICAL. MUST use base MapId not base GUID`
- * `48 - Set to 1 to send base power loss message & turns on red warning lights throughout base. MUST use base MapId not base GUID`
+ * `48 - Set to 1 to send base power loss message & turns on red warning lights throughout base. MUST use base MapId not base GUID`?
* `49 - Vehicle texture effects state? (>0 turns on ANT panel glow or ntu silo panel glow + orbs) (bit?)`
* `52 - Vehicle particle effects? (>0 turns on orbs going towards ANT. Doesn't affect silo) (bit?)`
* `53 - LFS. Value is 1 to flag LFS`
From 88684aa1a23439146dc19d1b9cdb3b09e9500875 Mon Sep 17 00:00:00 2001 From: "Jason_DiDonato@yahoo.com" Date: Sun, 18 Oct 2020 10:06:11 -0400 Subject: [PATCH 4/7] modified !ntu command to improve effect and control; generator has context for ntu rupply and when lacking ntu supply; the resource silo no longer directly controls facility conditions --- .../psforever/actors/session/ChatActor.scala | 63 +++++++-- .../psforever/actors/zone/BuildingActor.scala | 97 +++++++------- .../generator/GeneratorControl.scala | 121 +++++++++++++----- .../resourcesilo/ResourceSiloControl.scala | 9 -- .../terminals/ProximityTerminalControl.scala | 25 +++- .../turret/FacilityTurretControl.scala | 2 +- 6 files changed, 213 insertions(+), 104 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/ChatActor.scala b/src/main/scala/net/psforever/actors/session/ChatActor.scala index 9a1c27192..8afe468e9 100644 --- a/src/main/scala/net/psforever/actors/session/ChatActor.scala +++ b/src/main/scala/net/psforever/actors/session/ChatActor.scala @@ -7,7 +7,7 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} import net.psforever.actors.zone.BuildingActor import net.psforever.objects.avatar.{BattleRank, Certification, CommandRank, Cosmetic} import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} -import net.psforever.objects.{Default, GlobalDefinitions, Player, Session} +import net.psforever.objects.{Default, Player, Session} import net.psforever.objects.serverobject.resourcesilo.ResourceSilo import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.serverobject.turret.{FacilityTurret, TurretUpgrade, WeaponTurrets} @@ -342,20 +342,55 @@ class ChatActor( ) } - case (_, _, contents) if contents.startsWith("!ntu") && session.account.gm => - session.zone.Buildings.values.foreach(building => - building.Amenities.foreach(amenity => - amenity.Definition match { - case GlobalDefinitions.resource_silo => - val r = new scala.util.Random - val silo = amenity.asInstanceOf[ResourceSilo] - val ntu = 900f + r.nextFloat() * 100f - silo.NtuCapacitor - silo.Actor ! ResourceSilo.UpdateChargeLevel(ntu) - - case _ => () - } + case (_, _, contents) if contents.startsWith("!ntu") && gmCommandAllowed => + val buffer = contents.toLowerCase.split("\\s+") + val (facility, customNtuValue) = (buffer.lift(1), buffer.lift(2)) match { + case (Some(x), Some(y)) if y.toIntOption.nonEmpty => (Some(x), Some(y.toInt)) + case (Some(x), None) if x.toIntOption.nonEmpty => (None, Some(x.toInt)) + case _ => (None, None) + } + val silos = (facility match { + case Some(cur) if cur.toLowerCase().startsWith("curr") => + val position = session.player.Position + session.zone.Buildings.values + .filter { building => + val soi2 = building.Definition.SOIRadius * building.Definition.SOIRadius + Vector3.DistanceSquared(building.Position, position) < soi2 + } + case Some(x) => + session.zone.Buildings.values.find { _.Name.equalsIgnoreCase(x) }.toList + case _ => + session.zone.Buildings.values + }) + .flatMap { building => building.Amenities.filter { _.isInstanceOf[ResourceSilo] } } + if(silos.isEmpty) { + sessionActor ! SessionActor.SendResponse( + ChatMsg(UNK_229, true, "Server", s"no targets for ntu found with parameters $facility", None) ) - ) + } + customNtuValue match { + // x = n0% of maximum capacitance + case Some(value) if value > -1 && value < 11 => + silos.collect { case silo: ResourceSilo => + silo.Actor ! ResourceSilo.UpdateChargeLevel(value * silo.MaxNtuCapacitor * 0.1f - silo.NtuCapacitor) + } + // capacitance set to x (where x > 10) exactly, within limits + case Some(value) => + silos.collect { case silo: ResourceSilo => + silo.Actor ! ResourceSilo.UpdateChargeLevel(value - silo.NtuCapacitor) + } + case None => + // x >= n0% of maximum capacitance and x <= maximum capacitance + val rand = new scala.util.Random + silos.collect { case silo: ResourceSilo => + val a = 7 + val b = 10 - a + val tenth = silo.MaxNtuCapacitor * 0.1f + silo.Actor ! ResourceSilo.UpdateChargeLevel( + a * tenth + rand.nextFloat() * b * tenth - silo.NtuCapacitor + ) + } + } case _ => // unknown ! commands are ignored diff --git a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala index 132017900..fc7b699a7 100644 --- a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala +++ b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala @@ -9,7 +9,6 @@ import net.psforever.objects.{CommonNtuContainer, NtuContainer} import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.generator.Generator import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType, WarpGate} -import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.zones.Zone import net.psforever.persistence import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} @@ -118,55 +117,14 @@ class BuildingActor( ): Behavior[Command] = { Behaviors.receiveMessagePartial { case SetFaction(faction) => - import ctx._ - ctx - .run( - query[persistence.Building] - .filter(_.localId == lift(building.MapId)) - .filter(_.zoneId == lift(zone.Number)) - ) - .onComplete { - case Success(res) => - res.headOption match { - case Some(_) => - ctx - .run( - query[persistence.Building] - .filter(_.localId == lift(building.MapId)) - .filter(_.zoneId == lift(zone.Number)) - .update(_.factionId -> lift(building.Faction.id)) - ) - .onComplete { - case Success(_) => - case Failure(e) => log.error(e.getMessage) - } - case _ => - ctx - .run( - query[persistence.Building] - .insert( - _.localId -> lift(building.MapId), - _.factionId -> lift(building.Faction.id), - _.zoneId -> lift(zone.Number) - ) - ) - .onComplete { - case Success(_) => - case Failure(e) => log.error(e.getMessage) - } - } - case Failure(e) => log.error(e.getMessage) - } - building.Faction = faction - galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) - zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SetEmpire(building.GUID, faction)) + setFactionTo(faction, galaxyService) Behaviors.same case MapUpdate() => galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) Behaviors.same - case AmenityStateChange(obj: Generator, data) => + case AmenityStateChange(_: Generator, data) => //TODO when parameter object is finally immutable, perform analysis on it to determine specific actions data match { case Some("overloaded") => @@ -176,7 +134,8 @@ class BuildingActor( building.PlayersInSOI.foreach { player => zone.AvatarEvents ! AvatarServiceMessage(player.Name, msg) } //??? - case Some("repaired") => + case Some("restored") => + // Power restored. Reactor Online. Sensors Online. Weapons Online. All systems nominal. powerRestored() val zone = building.Zone building.PlayersInSOI.foreach { player => @@ -204,12 +163,15 @@ class BuildingActor( Behaviors.same case msg @ NtuDepleted() => + // Oops, someone let the base run out of power. Shut it all down. building.Amenities.foreach { amenity => amenity.Actor ! msg } + setFactionTo(PlanetSideEmpire.NEUTRAL, galaxyService) Behaviors.same case msg @ SuppliedWithNtu() => + // Auto-repair, mainly. If the Generator works, power is restored too. building.Amenities.foreach { amenity => amenity.Actor ! msg } @@ -220,6 +182,51 @@ class BuildingActor( } } + def setFactionTo(faction: PlanetSideEmpire.Value, galaxy: classic.ActorRef): Unit = { + import ctx._ + ctx + .run( + query[persistence.Building] + .filter(_.localId == lift(building.MapId)) + .filter(_.zoneId == lift(zone.Number)) + ) + .onComplete { + case Success(res) => + res.headOption match { + case Some(_) => + ctx + .run( + query[persistence.Building] + .filter(_.localId == lift(building.MapId)) + .filter(_.zoneId == lift(zone.Number)) + .update(_.factionId -> lift(building.Faction.id)) + ) + .onComplete { + case Success(_) => + case Failure(e) => log.error(e.getMessage) + } + case _ => + ctx + .run( + query[persistence.Building] + .insert( + _.localId -> lift(building.MapId), + _.factionId -> lift(building.Faction.id), + _.zoneId -> lift(zone.Number) + ) + ) + .onComplete { + case Success(_) => + case Failure(e) => log.error(e.getMessage) + } + } + case Failure(e) => log.error(e.getMessage) + } + building.Faction = faction + galaxy ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) + zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SetEmpire(building.GUID, faction)) + } + def powerLost(): Unit = { val zone = building.Zone val zoneId = zone.id diff --git a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala index 23a99c67e..12795d97b 100644 --- a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala @@ -1,9 +1,9 @@ // Copyright (c) 2020 PSForever package net.psforever.objects.serverobject.generator -import akka.actor.Actor +import akka.actor.{Actor, Cancellable} import net.psforever.actors.zone.BuildingActor -import net.psforever.objects.{Player, Tool} +import net.psforever.objects.{Default, Player, Tool} import net.psforever.objects.ballistics._ import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior import net.psforever.objects.serverobject.damage.Damageable.Target @@ -35,15 +35,40 @@ class GeneratorControl(gen: Generator) def RepairableObject = gen def AutoRepairObject = gen var imminentExplosion: Boolean = false + var queuedExplosion: Cancellable = Default.Cancellable var alarmCooldownPeriod: Boolean = false + private[this] val log = org.log4s.getLogger - def receive: Receive = + def receive: Receive = withNtu + + val commonBehavior: Receive = checkBehavior .orElse(takesDamage) .orElse(canBeRepairedByNanoDispenser) .orElse(autoRepairBehavior) .orElse { - case GeneratorControl.GeneratorExplodes() => //TODO this only works with projectiles right now! + case GeneratorControl.UnderThreatAlarm() => + if (!alarmCooldownPeriod) { + alarmCooldownPeriod = true + GeneratorControl.BroadcastGeneratorEvent(gen, GeneratorControl.Event.UnderAttack) + queuedExplosion = context.system.scheduler.scheduleOnce(delay = 5 seconds, self, GeneratorControl.AlarmReset()) + } + + case GeneratorControl.AlarmReset() => + alarmCooldownPeriod = false + } + + def withNtu: Receive = + commonBehavior + .orElse { + case GeneratorControl.Destabilized() => + imminentExplosion = true + GeneratorControl.BroadcastGeneratorEvent(gen, GeneratorControl.Event.Overloaded) + queuedExplosion.cancel() + queuedExplosion = context.system.scheduler.scheduleOnce(10 seconds, self, GeneratorControl.GeneratorExplodes()) + + case GeneratorControl.GeneratorExplodes() => + //TODO this only works with projectiles right now! val zone = gen.Zone gen.Health = 0 super.DestructionAwareness(gen, gen.LastShot.get) @@ -57,6 +82,8 @@ class GeneratorControl(gen: Generator) TriggerEffectMessage(gen.GUID, "explosion_generator", None, None) ) ) + queuedExplosion.cancel() + queuedExplosion = Default.Cancellable imminentExplosion = false //kill everyone within 14m gen.Owner match { @@ -71,33 +98,35 @@ class GeneratorControl(gen: Generator) } gen.ClearHistory() - case GeneratorControl.UnderThreatAlarm() => - if (!alarmCooldownPeriod) { - alarmCooldownPeriod = true - GeneratorControl.BroadcastGeneratorEvent(gen, GeneratorControl.Event.UnderAttack) - context.system.scheduler.scheduleOnce(delay = 5 seconds, self, GeneratorControl.AlarmReset()) - } - - case GeneratorControl.AlarmReset() => - alarmCooldownPeriod = false - - case BuildingActor.NtuDepleted() => - gen.Owner match { - case b: Building if !gen.Destroyed => - b.Actor ! BuildingActor.PowerOff() - case _ => ; - } - - case BuildingActor.SuppliedWithNtu() => - gen.Owner match { - case b: Building if !gen.Destroyed => - b.Actor ! BuildingActor.PowerOn() - case _ => ; - } + case GeneratorControl.Restored() => + GeneratorControl.UpdateOwner(gen, Some("restored")) + GeneratorControl.BroadcastGeneratorEvent(gen, GeneratorControl.Event.Online) case _ => ; } + def withoutNtu: Receive = + commonBehavior + .orElse { + case GeneratorControl.GeneratorExplodes() => + queuedExplosion.cancel() + queuedExplosion = Default.Cancellable + imminentExplosion = false + + case GeneratorControl.Destabilized() => + //if the generator is destabilized but has no ntu, it will not explode + gen.Health = 0 + super.DestructionAwareness(gen, gen.LastShot.get) + queuedExplosion.cancel() + queuedExplosion = Default.Cancellable + imminentExplosion = false + gen.Condition = PlanetSideGeneratorState.Destroyed + GeneratorControl.UpdateOwner(gen, Some("destroyed")) + gen.ClearHistory() + + case _ => + } + override protected def CanPerformRepairs(obj: Target, player: Player, item: Tool): Boolean = { !imminentExplosion && super.CanPerformRepairs(obj, player, item) } @@ -120,10 +149,8 @@ class GeneratorControl(gen: Generator) tryAutoRepair() if (!target.Destroyed) { target.Health = 1 //temporary - imminentExplosion = true - context.system.scheduler.scheduleOnce(10 seconds, self, GeneratorControl.GeneratorExplodes()) GeneratorControl.UpdateOwner(gen, Some("overloaded")) - GeneratorControl.BroadcastGeneratorEvent(gen, GeneratorControl.Event.Overloaded) + self ! GeneratorControl.Destabilized() } } @@ -138,13 +165,38 @@ class GeneratorControl(gen: Generator) override def Restoration(obj: Repairable.Target): Unit = { super.Restoration(obj) gen.Condition = PlanetSideGeneratorState.Normal - GeneratorControl.UpdateOwner(gen, Some("repaired")) - GeneratorControl.BroadcastGeneratorEvent(gen, GeneratorControl.Event.Online) + self ! GeneratorControl.Restored() + } + + override def withNtuSupplyCallback() : Unit = { + context.become(withNtu) + super.withNtuSupplyCallback() + if(!gen.Destroyed) { + self ! GeneratorControl.Restored() + } + } + + override def noNtuSupplyCallback() : Unit = { + //auto-repair must stop naturally + context.become(withoutNtu) + super.noNtuSupplyCallback() + if(!gen.Destroyed) { + GeneratorControl.UpdateOwner(gen, Some("overloaded")) + } + if(!queuedExplosion.isCancelled) { + queuedExplosion.cancel() + self ! GeneratorControl.Destabilized() + } } } object GeneratorControl { + /** + * na + */ + private case class Destabilized() + /** * na */ @@ -160,6 +212,11 @@ object GeneratorControl { */ private case class AlarmReset() + /** + * na + */ + private case class Restored() + /** * na */ diff --git a/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala b/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala index f726b57d9..673829f83 100644 --- a/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala @@ -113,18 +113,9 @@ class ResourceSiloControl(resourceSilo: ResourceSilo) 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.Actor ! BuildingActor.NtuDepleted() building.Actor ! BuildingActor.AmenityStateChange(resourceSilo) - building.Actor ! BuildingActor.SetFaction(PlanetSideEmpire.NEUTRAL) } 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.Actor ! BuildingActor.SuppliedWithNtu() building.Actor ! BuildingActor.AmenityStateChange(resourceSilo) } diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala index 7773a846c..04ded0e60 100644 --- a/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala @@ -5,9 +5,10 @@ import akka.actor.{ActorRef, Cancellable} import net.psforever.objects._ import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior +import net.psforever.objects.serverobject.damage.Damageable.Target import net.psforever.objects.serverobject.damage.DamageableAmenity import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior} -import net.psforever.objects.serverobject.repair.RepairableAmenity +import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableAmenity} import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl} import net.psforever.services.Service import net.psforever.services.local.{LocalAction, LocalServiceMessage} @@ -26,12 +27,14 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit) with FactionAffinityBehavior.Check with HackableBehavior.GenericHackable with DamageableAmenity - with RepairableAmenity { + with RepairableAmenity + with AmenityAutoRepair { def FactionObject = term def HackableObject = term def TerminalObject = term def DamageableObject = term def RepairableObject = term + def AutoRepairObject = term var terminalAction: Cancellable = Default.Cancellable val callbacks: mutable.ListBuffer[ActorRef] = new mutable.ListBuffer[ActorRef]() @@ -40,6 +43,7 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit) val commonBehavior: Receive = checkBehavior .orElse(takesDamage) .orElse(canBeRepairedByNanoDispenser) + .orElse(autoRepairBehavior) .orElse { case CommonMessages.Unuse(_, Some(target: PlanetSideGameObject)) => Unuse(target, term.Continent) @@ -117,6 +121,18 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit) case _ => ; } + override def PerformRepairs(target : Target, amount : Int) : Int = { + val newHealth = super.PerformRepairs(target, amount) + if(newHealth == target.Definition.MaxHealth) { + stopAutoRepair() + } + newHealth + } + + override def tryAutoRepair() : Boolean = { + isPowered && super.tryAutoRepair() + } + def Use(target: PlanetSideGameObject, zone: String, callback: ActorRef): Unit = { val hadNoUsers = term.NumberUsers == 0 if (term.AddUser(target)) { @@ -164,6 +180,7 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit) } def powerTurnOffCallback() : Unit = { + stopAutoRepair() //clear effect callbacks terminalAction.cancel() if (callbacks.nonEmpty) { @@ -177,7 +194,9 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit) } } - def powerTurnOnCallback() : Unit = { } + def powerTurnOnCallback() : Unit = { + tryAutoRepair() + } override def toString: String = term.Definition.Name } diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala index 9734810b6..3ae7f1601 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala @@ -58,11 +58,11 @@ class FacilityTurretControl(turret: FacilityTurret) .orElse(dismountBehavior) .orElse(takesDamage) .orElse(canBeRepairedByNanoDispenser) + .orElse(autoRepairBehavior) def poweredStateLogic: Receive = commonBehavior .orElse(mountBehavior) - .orElse(autoRepairBehavior) .orElse { case CommonMessages.Use(player, Some((item: Tool, upgradeValue: Int))) if player.Faction == turret.Faction && From 54f6588439163de15c4ef2dc1a872b388496e9b8 Mon Sep 17 00:00:00 2001 From: "Jason_DiDonato@yahoo.com" Date: Wed, 21 Oct 2020 12:35:13 -0400 Subject: [PATCH 5/7] comments and tests; generator now passes state information to owner, owner composes messaging; can not set faction information if building does not have ntu --- .../actors/session/SessionActor.scala | 6 - .../psforever/actors/zone/BuildingActor.scala | 178 +++++++++++------- .../generator/GeneratorControl.scala | 136 ++++++------- .../objects/AutoRepairIntegrationTest.scala | 2 + src/test/scala/objects/DoorTest.scala | 36 ++-- src/test/scala/objects/GeneratorTest.scala | 168 +++++++++-------- src/test/scala/objects/ResourceSiloTest.scala | 6 - 7 files changed, 296 insertions(+), 236 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index b2a31c009..42a4f2a23 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -3646,12 +3646,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con if (isMovingPlus) { CancelZoningProcessWithDescriptiveReason("cancel_motion") } - if (!player.Crouching && is_crouching) { - continent.Buildings.values.find { b => Vector3.Distance(player.Position, b.Position) <= b.Definition.SOIRadius} match { - case Some(b) => ; - case _ => ; - } - } player.Position = pos player.Velocity = vel player.Orientation = Vector3(player.Orientation.x, pitch, yaw) diff --git a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala index fc7b699a7..cf1383f53 100644 --- a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala +++ b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala @@ -7,7 +7,7 @@ import akka.{actor => classic} import net.psforever.actors.commands.NtuCommand import net.psforever.objects.{CommonNtuContainer, NtuContainer} import net.psforever.objects.serverobject.PlanetSideServerObject -import net.psforever.objects.serverobject.generator.Generator +import net.psforever.objects.serverobject.generator.{Generator, GeneratorControl} import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType, WarpGate} import net.psforever.objects.zones.Zone import net.psforever.persistence @@ -16,7 +16,7 @@ import net.psforever.types.PlanetSideEmpire import net.psforever.util.Database._ import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage} -import net.psforever.services.{InterstellarClusterService, ServiceManager} +import net.psforever.services.{InterstellarClusterService, Service, ServiceManager} import scala.concurrent.ExecutionContext.Implicits.global import scala.util.{Failure, Success} @@ -73,6 +73,7 @@ class BuildingActor( private[this] val log = org.log4s.getLogger var galaxyService: Option[classic.ActorRef] = None var interstellarCluster: Option[ActorRef[InterstellarClusterService.Command]] = None + var hasNtuSupply: Boolean = true context.system.receptionist ! Receptionist.Find( InterstellarClusterService.InterstellarClusterServiceKey, @@ -124,28 +125,11 @@ class BuildingActor( galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) Behaviors.same - case AmenityStateChange(_: Generator, data) => - //TODO when parameter object is finally immutable, perform analysis on it to determine specific actions - data match { - case Some("overloaded") => - powerLost() - val zone = building.Zone - val msg = AvatarAction.PlanetsideAttributeToAll(building.GUID, 46, 2) - building.PlayersInSOI.foreach { player => - zone.AvatarEvents ! AvatarServiceMessage(player.Name, msg) - } //??? - case Some("restored") => - // Power restored. Reactor Online. Sensors Online. Weapons Online. All systems nominal. - powerRestored() - val zone = building.Zone - building.PlayersInSOI.foreach { player => - val msg = AvatarAction.PlanetsideAttributeToAll(building.GUID, 46, 0) - zone.AvatarEvents ! AvatarServiceMessage(player.Name, msg) - } //reset ??? - case _ => ; + case AmenityStateChange(gen: Generator, data) => + if (generatorStateChange(gen, data)) { + //update the map + galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) } - //update the map - galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) Behaviors.same case AmenityStateChange(_, _) => @@ -163,15 +147,17 @@ class BuildingActor( Behaviors.same case msg @ NtuDepleted() => - // Oops, someone let the base run out of power. Shut it all down. + // Someone let the base run out of nanites. No one gets anything. building.Amenities.foreach { amenity => amenity.Actor ! msg } setFactionTo(PlanetSideEmpire.NEUTRAL, galaxyService) + hasNtuSupply = false Behaviors.same case msg @ SuppliedWithNtu() => - // Auto-repair, mainly. If the Generator works, power is restored too. + // Auto-repair restart, mainly. If the Generator works, power should be restored too. + hasNtuSupply = true building.Amenities.foreach { amenity => amenity.Actor ! msg } @@ -182,49 +168,107 @@ class BuildingActor( } } + def generatorStateChange(generator: Generator, event: Any): Boolean = { + event match { + case Some(GeneratorControl.Event.UnderAttack) => + val events = zone.AvatarEvents + val guid = building.GUID + val msg = AvatarAction.GenericObjectAction(Service.defaultPlayerGUID, guid, 15) + building.PlayersInSOI.foreach { player => + events ! AvatarServiceMessage(player.Name, msg) + } + false + case Some(GeneratorControl.Event.Critical) => + val events = zone.AvatarEvents + val guid = building.GUID + val msg = AvatarAction.PlanetsideAttributeToAll(guid, 46, 1) + building.PlayersInSOI.foreach { player => + events ! AvatarServiceMessage(player.Name, msg) + } + true + case Some(GeneratorControl.Event.Destabilized) => + val events = zone.AvatarEvents + val guid = building.GUID + val msg = AvatarAction.GenericObjectAction(Service.defaultPlayerGUID, guid, 16) + building.PlayersInSOI.foreach { player => + events ! AvatarServiceMessage(player.Name, msg) + } + false + case Some(GeneratorControl.Event.Destroyed) => + true + case Some(GeneratorControl.Event.Offline) => + powerLost() + val zone = building.Zone + val msg = AvatarAction.PlanetsideAttributeToAll(building.GUID, 46, 2) + building.PlayersInSOI.foreach { player => + zone.AvatarEvents ! AvatarServiceMessage(player.Name, msg) + } //??? + false + case Some(GeneratorControl.Event.Normal) => + true + case Some(GeneratorControl.Event.Online) => + // Power restored. Reactor Online. Sensors Online. Weapons Online. All systems nominal. + powerRestored() + val events = zone.AvatarEvents + val guid = building.GUID + val msg1 = AvatarAction.PlanetsideAttributeToAll(guid, 46, 0) + val msg2 = AvatarAction.GenericObjectAction(Service.defaultPlayerGUID, guid, 17) + building.PlayersInSOI.foreach { player => + val name = player.Name + events ! AvatarServiceMessage(name, msg1) //reset ???; might be global? + events ! AvatarServiceMessage(name, msg2) //This facility's generator is back on line + } + true + case _ => + false + } + } + def setFactionTo(faction: PlanetSideEmpire.Value, galaxy: classic.ActorRef): Unit = { - import ctx._ - ctx - .run( - query[persistence.Building] - .filter(_.localId == lift(building.MapId)) - .filter(_.zoneId == lift(zone.Number)) - ) - .onComplete { - case Success(res) => - res.headOption match { - case Some(_) => - ctx - .run( - query[persistence.Building] - .filter(_.localId == lift(building.MapId)) - .filter(_.zoneId == lift(zone.Number)) - .update(_.factionId -> lift(building.Faction.id)) - ) - .onComplete { - case Success(_) => - case Failure(e) => log.error(e.getMessage) - } - case _ => - ctx - .run( - query[persistence.Building] - .insert( - _.localId -> lift(building.MapId), - _.factionId -> lift(building.Faction.id), - _.zoneId -> lift(zone.Number) - ) - ) - .onComplete { - case Success(_) => - case Failure(e) => log.error(e.getMessage) - } - } - case Failure(e) => log.error(e.getMessage) - } - building.Faction = faction - galaxy ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) - zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SetEmpire(building.GUID, faction)) + if (hasNtuSupply) { + import ctx._ + ctx + .run( + query[persistence.Building] + .filter(_.localId == lift(building.MapId)) + .filter(_.zoneId == lift(zone.Number)) + ) + .onComplete { + case Success(res) => + res.headOption match { + case Some(_) => + ctx + .run( + query[persistence.Building] + .filter(_.localId == lift(building.MapId)) + .filter(_.zoneId == lift(zone.Number)) + .update(_.factionId -> lift(building.Faction.id)) + ) + .onComplete { + case Success(_) => + case Failure(e) => log.error(e.getMessage) + } + case _ => + ctx + .run( + query[persistence.Building] + .insert( + _.localId -> lift(building.MapId), + _.factionId -> lift(building.Faction.id), + _.zoneId -> lift(zone.Number) + ) + ) + .onComplete { + case Success(_) => + case Failure(e) => log.error(e.getMessage) + } + } + case Failure(e) => log.error(e.getMessage) + } + building.Faction = faction + galaxy ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) + zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SetEmpire(building.GUID, faction)) + } } def powerLost(): Unit = { diff --git a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala index 12795d97b..693bde06b 100644 --- a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala @@ -34,13 +34,23 @@ class GeneratorControl(gen: Generator) def DamageableObject = gen def RepairableObject = gen def AutoRepairObject = gen + /** flagged to explode after some time */ var imminentExplosion: Boolean = false + /** explode when this timer completes */ var queuedExplosion: Cancellable = Default.Cancellable - var alarmCooldownPeriod: Boolean = false - private[this] val log = org.log4s.getLogger + /** when damaged, announce that damage was dealt on a schedule */ + var alarmCooldown: Cancellable = Default.Cancellable + + /* + behavior of the generator piggybacks from the logic used in `AmenityAutoRepair` + AAR splits its logic based on whether or not it has detected a source of nanite transfer units (NTU) + this amenity is the bridge between NTU and facility power so it leverages that logic + it is split between when detecting ntu and when starved for ntu + */ def receive: Receive = withNtu + /** behavior that is valid for both "with-ntu" and "without-ntu" */ val commonBehavior: Receive = checkBehavior .orElse(takesDamage) @@ -48,22 +58,29 @@ class GeneratorControl(gen: Generator) .orElse(autoRepairBehavior) .orElse { case GeneratorControl.UnderThreatAlarm() => - if (!alarmCooldownPeriod) { - alarmCooldownPeriod = true - GeneratorControl.BroadcastGeneratorEvent(gen, GeneratorControl.Event.UnderAttack) - queuedExplosion = context.system.scheduler.scheduleOnce(delay = 5 seconds, self, GeneratorControl.AlarmReset()) + //alert to damage and block other damage alerts for a time + if (alarmCooldown.isCancelled) { + GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.UnderAttack)) + alarmCooldown.cancel() + alarmCooldown = context.system.scheduler.scheduleOnce(delay = 5 seconds, self, GeneratorControl.AlarmReset()) } case GeneratorControl.AlarmReset() => - alarmCooldownPeriod = false + //clear the blocker for alerting to damage + alarmCooldown = Default.Cancellable } + /* + when NTU is detected, + the generator can be properly destabilized and explode + the generator can be repaired to operational status and power the facility in which it is installed + */ def withNtu: Receive = commonBehavior .orElse { case GeneratorControl.Destabilized() => imminentExplosion = true - GeneratorControl.BroadcastGeneratorEvent(gen, GeneratorControl.Event.Overloaded) + GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Destabilized)) queuedExplosion.cancel() queuedExplosion = context.system.scheduler.scheduleOnce(10 seconds, self, GeneratorControl.GeneratorExplodes()) @@ -73,7 +90,7 @@ class GeneratorControl(gen: Generator) gen.Health = 0 super.DestructionAwareness(gen, gen.LastShot.get) gen.Condition = PlanetSideGeneratorState.Destroyed - GeneratorControl.UpdateOwner(gen, Some("destroyed")) + GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Destroyed)) //kaboom zone.AvatarEvents ! AvatarServiceMessage( zone.id, @@ -99,39 +116,45 @@ class GeneratorControl(gen: Generator) gen.ClearHistory() case GeneratorControl.Restored() => - GeneratorControl.UpdateOwner(gen, Some("restored")) - GeneratorControl.BroadcastGeneratorEvent(gen, GeneratorControl.Event.Online) + GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Online)) case _ => ; } + /* + when ntu is not expected, + the generator can still be destroyed but will not explode + handles the possibility that ntu was lost during an ongoing destabilization and cancels the explosion + */ def withoutNtu: Receive = - commonBehavior - .orElse { - case GeneratorControl.GeneratorExplodes() => - queuedExplosion.cancel() - queuedExplosion = Default.Cancellable - imminentExplosion = false + commonBehavior + .orElse { + case GeneratorControl.GeneratorExplodes() => + queuedExplosion.cancel() + queuedExplosion = Default.Cancellable + imminentExplosion = false - case GeneratorControl.Destabilized() => - //if the generator is destabilized but has no ntu, it will not explode - gen.Health = 0 - super.DestructionAwareness(gen, gen.LastShot.get) - queuedExplosion.cancel() - queuedExplosion = Default.Cancellable - imminentExplosion = false - gen.Condition = PlanetSideGeneratorState.Destroyed - GeneratorControl.UpdateOwner(gen, Some("destroyed")) - gen.ClearHistory() + case GeneratorControl.Destabilized() => + //if the generator is destabilized but has no ntu, it will not explode + gen.Health = 0 + super.DestructionAwareness(gen, gen.LastShot.get) + queuedExplosion.cancel() + queuedExplosion = Default.Cancellable + imminentExplosion = false + gen.Condition = PlanetSideGeneratorState.Destroyed + GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Destroyed)) + gen.ClearHistory() - case _ => - } + case _ => + } override protected def CanPerformRepairs(obj: Target, player: Player, item: Tool): Boolean = { + //if an explosion is queued, disallow repairs !imminentExplosion && super.CanPerformRepairs(obj, player, item) } override protected def WillAffectTarget(target: Target, damage: Int, cause: ResolvedProjectile): Boolean = { + //if an explosion is queued, disallow further damage !imminentExplosion && super.WillAffectTarget(target, damage, cause) } @@ -147,9 +170,10 @@ class GeneratorControl(gen: Generator) override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = { tryAutoRepair() + //if the target is already destroyed, do not let it be destroyed again if (!target.Destroyed) { target.Health = 1 //temporary - GeneratorControl.UpdateOwner(gen, Some("overloaded")) + GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Offline)) self ! GeneratorControl.Destabilized() } } @@ -165,12 +189,14 @@ class GeneratorControl(gen: Generator) override def Restoration(obj: Repairable.Target): Unit = { super.Restoration(obj) gen.Condition = PlanetSideGeneratorState.Normal + GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Normal)) self ! GeneratorControl.Restored() } override def withNtuSupplyCallback() : Unit = { context.become(withNtu) super.withNtuSupplyCallback() + //if not destroyed when a source of ntu is detected, restore facility power if(!gen.Destroyed) { self ! GeneratorControl.Restored() } @@ -180,9 +206,11 @@ class GeneratorControl(gen: Generator) //auto-repair must stop naturally context.become(withoutNtu) super.noNtuSupplyCallback() + //if not destroyed when cutoff from a source of ntu, stop facility power generation if(!gen.Destroyed) { - GeneratorControl.UpdateOwner(gen, Some("overloaded")) + GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Offline)) } + //can any explosion (see withoutNtu->GenweratorControl.Destabilized) if(!queuedExplosion.isCancelled) { queuedExplosion.cancel() self ! GeneratorControl.Destabilized() @@ -191,7 +219,6 @@ class GeneratorControl(gen: Generator) } object GeneratorControl { - /** * na */ @@ -221,41 +248,24 @@ object GeneratorControl { * na */ object Event extends Enumeration { - val UnderAttack = Value(15) - val Critical = Value(0) - val Overloaded = Value(16) - val Online = Value(17) + val + Critical, //PlanetSideGeneratorState.Critical + UnderAttack, + Destabilized, + Destroyed, //PlanetSideGeneratorState.Destroyed + Offline, + Normal, //PlanetSideGeneratorState.Normal + Online + = Value } /** - * na - * @param obj na + * Send a message back to the owner for which this `Amenity` entity is installed. + * @param obj the entity doing the self-reporting + * @param data optional information that indicates the nature of the state change */ private def UpdateOwner(obj: Generator, data: Option[Any] = None): Unit = { - obj.Owner match { - case b: Building => b.Actor ! BuildingActor.AmenityStateChange(obj, data) - case _ => ; - } - } - - /** - * na - * @param target the generator - * @param event the action code for the event - */ - private def BroadcastGeneratorEvent(target: Generator, event: Event.Value): Unit = { - target.Owner match { - case b: Building => - val events = target.Zone.AvatarEvents - val msg = event match { - case Event.Critical => AvatarAction.PlanetsideAttributeToAll(b.GUID, 46, 1) //critical status warning - case _ => AvatarAction.GenericObjectAction(Service.defaultPlayerGUID, b.GUID, event.id) - } - b.PlayersInSOI.foreach { player => - events ! AvatarServiceMessage(player.Name, msg) - } - case _ => ; - } + obj.Owner.Actor ! BuildingActor.AmenityStateChange(obj, data) } /** @@ -270,7 +280,7 @@ object GeneratorControl { val max: Float = target.MaxHealth.toFloat if (target.Condition != PlanetSideGeneratorState.Critical && health / max < 0.51f) { //becoming critical target.Condition = PlanetSideGeneratorState.Critical - GeneratorControl.UpdateOwner(target, Some("critical")) + GeneratorControl.UpdateOwner(target, Some(GeneratorControl.Event.Critical)) } //the generator is under attack target.Actor ! UnderThreatAlarm() diff --git a/src/test/scala/objects/AutoRepairIntegrationTest.scala b/src/test/scala/objects/AutoRepairIntegrationTest.scala index 581943081..3ce685028 100644 --- a/src/test/scala/objects/AutoRepairIntegrationTest.scala +++ b/src/test/scala/objects/AutoRepairIntegrationTest.scala @@ -4,6 +4,7 @@ package objects import akka.actor.Props import akka.testkit.TestProbe import base.FreedContextActorTest +import net.psforever.actors.zone.BuildingActor import net.psforever.objects.avatar.Avatar import net.psforever.objects.ballistics.{Projectile, ProjectileResolution, ResolvedProjectile, SourceEntry} import net.psforever.objects.guid.NumberPoolHub @@ -57,6 +58,7 @@ class AutoRepairFacilityIntegrationTest extends FreedContextActorTest { silo.NtuCapacitor = 1000 silo.Actor = system.actorOf(Props(classOf[ResourceSiloControl], silo), "test-silo") silo.Actor ! "startup" + building.Actor ! BuildingActor.PowerOn() //artificial val wep_fmode = weapon.FireMode val wep_prof = wep_fmode.Add diff --git a/src/test/scala/objects/DoorTest.scala b/src/test/scala/objects/DoorTest.scala index 742af526e..bc908e66e 100644 --- a/src/test/scala/objects/DoorTest.scala +++ b/src/test/scala/objects/DoorTest.scala @@ -2,17 +2,22 @@ package objects import akka.actor.{ActorSystem, Props} +import akka.testkit.TestProbe import base.ActorTest import net.psforever.objects.avatar.Avatar +import net.psforever.objects.guid.NumberPoolHub +import net.psforever.objects.guid.source.MaxNumberSource +import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.{Default, GlobalDefinitions, Player} import net.psforever.objects.serverobject.doors.{Door, DoorControl} import net.psforever.objects.serverobject.structures.{Building, StructureType} -import net.psforever.objects.zones.Zone +import net.psforever.objects.zones.{Zone, ZoneMap} import net.psforever.packet.game.UseItemMessage +import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.types._ import org.specs2.mutable.Specification -import scala.concurrent.duration.Duration +import scala.concurrent.duration._ class DoorTest extends Specification { val player = Player(Avatar(0, "test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) @@ -83,6 +88,8 @@ class DoorControl2Test extends ActorTest { "DoorControl" should { "open on use" in { val (player, door) = DoorControlTest.SetUpAgents(PlanetSideEmpire.TR) + val probe = new TestProbe(system) + door.Zone.LocalEvents = probe.ref val msg = UseItemMessage( PlanetSideGUID(1), PlanetSideGUID(0), @@ -98,13 +105,12 @@ class DoorControl2Test extends ActorTest { ) //faked assert(door.Open.isEmpty) - door.Actor ! Door.Use(player, msg) - val reply = receiveOne(Duration.create(500, "ms")) - assert(reply.isInstanceOf[Door.DoorMessage]) - val reply2 = reply.asInstanceOf[Door.DoorMessage] - assert(reply2.player == player) - assert(reply2.msg == msg) - assert(reply2.response == Door.OpenEvent()) + door.Actor ! CommonMessages.Use(player, Some(msg)) + val reply = probe.receiveOne(1000 milliseconds) + assert(reply match { + case LocalServiceMessage("test", LocalAction.DoorOpens(PlanetSideGUID(0), _, d)) => d eq door + case _ => false + }) assert(door.Open.isDefined) } } @@ -126,16 +132,24 @@ class DoorControl3Test extends ActorTest { object DoorControlTest { def SetUpAgents(faction: PlanetSideEmpire.Value)(implicit system: ActorSystem): (Player, Door) = { val door = Door(GlobalDefinitions.door) + val guid = new NumberPoolHub(new MaxNumberSource(5)) + val zone = new Zone("test", new ZoneMap("test"), 0) { + override def SetupNumberPools() = {} + GUID(guid) + } + guid.register(door, 1) door.Actor = system.actorOf(Props(classOf[DoorControl], door), "door") door.Owner = new Building( "Building", building_guid = 0, map_id = 0, - Zone.Nowhere, + zone, StructureType.Building, GlobalDefinitions.building ) door.Owner.Faction = faction - (Player(Avatar(0, "test", faction, CharacterGender.Male, 0, CharacterVoice.Mute)), door) + val player = Player(Avatar(0, "test", faction, CharacterGender.Male, 0, CharacterVoice.Mute)) + guid.register(player, 2) + (player, door) } } diff --git a/src/test/scala/objects/GeneratorTest.scala b/src/test/scala/objects/GeneratorTest.scala index 91ea0926f..10807bf02 100644 --- a/src/test/scala/objects/GeneratorTest.scala +++ b/src/test/scala/objects/GeneratorTest.scala @@ -11,7 +11,7 @@ import net.psforever.objects.{GlobalDefinitions, Player, Tool} import net.psforever.objects.guid.NumberPoolHub import net.psforever.objects.guid.source.MaxNumberSource import net.psforever.objects.serverobject.CommonMessages -import net.psforever.objects.serverobject.generator.{Generator, GeneratorControl} +import net.psforever.objects.serverobject.generator.{Generator, GeneratorControl, GeneratorDefinition} import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.vital.Vitality import net.psforever.objects.zones.{Zone, ZoneMap} @@ -25,12 +25,12 @@ import scala.concurrent.duration._ class GeneratorTest extends Specification { "Generator" should { "construct" in { - Generator(GlobalDefinitions.generator) + Generator(GeneratorTest.generator_definition) ok } "start in 'Normal' condition" in { - val obj = Generator(GlobalDefinitions.generator) + val obj = Generator(GeneratorTest.generator_definition) obj.Condition mustEqual PlanetSideGeneratorState.Normal } } @@ -39,7 +39,7 @@ class GeneratorTest extends Specification { class GeneratorControlConstructTest extends ActorTest { "GeneratorControl" should { "construct" in { - val gen = Generator(GlobalDefinitions.generator) + val gen = Generator(GeneratorTest.generator_definition) gen.Actor = system.actorOf(Props(classOf[GeneratorControl], gen), "gen-control") assert(gen.Actor != ActorRef.noSender) } @@ -57,7 +57,7 @@ class GeneratorControlDamageTest extends ActorTest { val activityProbe = TestProbe() zone.Activity = activityProbe.ref - val gen = Generator(GlobalDefinitions.generator) //guid=2 + val gen = Generator(GeneratorTest.generator_definition) //guid=2 gen.Position = Vector3(1, 0, 0) gen.Actor = system.actorOf(Props(classOf[GeneratorControl], gen), "generator-control") @@ -106,19 +106,18 @@ class GeneratorControlDamageTest extends ActorTest { assert(gen.Condition == PlanetSideGeneratorState.Normal) gen.Actor ! Vitality.Damage(applyDamageTo) - val msg_avatar = avatarProbe.receiveN(2, 500 milliseconds) - buildingProbe.expectNoMessage(200 milliseconds) + val msg_avatar = avatarProbe.receiveOne(500 milliseconds) + val msg_building = buildingProbe.receiveOne(500 milliseconds) assert( - msg_avatar.head match { + msg_avatar match { case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true case _ => false } ) assert( - msg_avatar(1) match { - case AvatarServiceMessage("TestCharacter1", AvatarAction.GenericObjectAction(_, PlanetSideGUID(1), 15)) => - true - case _ => false + msg_building match { + case BuildingActor.AmenityStateChange(_, Some(GeneratorControl.Event.UnderAttack)) => true + case _ => false } ) assert(gen.Health < gen.Definition.MaxHealth) @@ -139,7 +138,7 @@ class GeneratorControlCriticalTest extends ActorTest { val activityProbe = TestProbe() zone.Activity = activityProbe.ref - val gen = Generator(GlobalDefinitions.generator) //guid=2 + val gen = Generator(GeneratorTest.generator_definition) //guid=2 gen.Position = Vector3(1, 0, 0) gen.Actor = system.actorOf(Props(classOf[GeneratorControl], gen), "generator-control") @@ -190,25 +189,18 @@ class GeneratorControlCriticalTest extends ActorTest { assert(gen.Condition == PlanetSideGeneratorState.Normal) gen.Actor ! Vitality.Damage(applyDamageTo) - val msg_avatar = avatarProbe.receiveN(2, 500 milliseconds) + val msg_avatar = avatarProbe.receiveOne(500 milliseconds) val msg_building = buildingProbe.receiveOne(500 milliseconds) assert( - msg_avatar.head match { + msg_avatar match { case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true case _ => false } ) - assert( - msg_avatar(1) match { - case AvatarServiceMessage("TestCharacter1", AvatarAction.GenericObjectAction(_, PlanetSideGUID(1), 15)) => - true - case _ => false - } - ) assert( msg_building match { - case BuildingActor.AmenityStateChange(o) => o eq gen - case _ => false + case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Critical)) => o eq gen + case _ => false } ) assert(gen.Health < halfHealth) @@ -229,7 +221,7 @@ class GeneratorControlDestroyedTest extends ActorTest { val activityProbe = TestProbe() zone.Activity = activityProbe.ref - val gen = Generator(GlobalDefinitions.generator) //guid=2 + val gen = Generator(GeneratorTest.generator_definition) //guid=2 gen.Position = Vector3(1, 0, 0) gen.Actor = system.actorOf(Props(classOf[GeneratorControl], gen), "generator-control") @@ -269,7 +261,6 @@ class GeneratorControlDestroyedTest extends ActorTest { Vector3(1, 0, 0) ) val applyDamageTo = resolved.damage_model.Calculate(resolved) - gen.Actor ! BuildingActor.NtuDepleted() //no auto-repair expectNoMessage(200 milliseconds) //we're not testing that the math is correct @@ -281,12 +272,16 @@ class GeneratorControlDestroyedTest extends ActorTest { assert(gen.Condition == PlanetSideGeneratorState.Normal) //skipped critical state because didn't transition ~50% gen.Actor ! Vitality.Damage(applyDamageTo) - val msg_avatar1 = avatarProbe.receiveOne(500 milliseconds) - buildingProbe.expectNoMessage(200 milliseconds) + val msg_building12 = buildingProbe.receiveN(2,500 milliseconds) assert( - msg_avatar1 match { - case AvatarServiceMessage("TestCharacter1", AvatarAction.GenericObjectAction(_, PlanetSideGUID(1), 16)) => - true + msg_building12.head match { + case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Offline)) => o eq gen + case _ => false + } + ) + assert( + msg_building12(1) match { + case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Destabilized)) => o eq gen case _ => false } ) @@ -294,13 +289,13 @@ class GeneratorControlDestroyedTest extends ActorTest { assert(!gen.Destroyed) assert(gen.Condition == PlanetSideGeneratorState.Normal) - avatarProbe.expectNoMessage(9 seconds) + avatarProbe.expectNoMessage(9500 milliseconds) val msg_avatar2 = avatarProbe.receiveN(3, 1000 milliseconds) //see DamageableEntity test file val msg_building = buildingProbe.receiveOne(200 milliseconds) assert( msg_building match { - case BuildingActor.AmenityStateChange(o) => o eq gen - case _ => false + case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Destroyed)) => o eq gen + case _ => false } ) assert( @@ -352,7 +347,7 @@ class GeneratorControlKillsTest extends ActorTest { val activityProbe = TestProbe() zone.Activity = activityProbe.ref - val gen = Generator(GlobalDefinitions.generator) //guid=2 + val gen = Generator(GeneratorTest.generator_definition) //guid=2 gen.Position = Vector3(1, 0, 0) gen.Actor = system.actorOf(Props(classOf[GeneratorControl], gen), "generator-control") @@ -400,7 +395,6 @@ class GeneratorControlKillsTest extends ActorTest { Vector3(1, 0, 0) ) val applyDamageTo = resolved.damage_model.Calculate(resolved) - gen.Actor ! BuildingActor.NtuDepleted() //no auto-repair expectNoMessage(200 milliseconds) //we're not testing that the math is correct @@ -412,21 +406,16 @@ class GeneratorControlKillsTest extends ActorTest { assert(gen.Condition == PlanetSideGeneratorState.Normal) //skipped critical state because didn't transition ~50% gen.Actor ! Vitality.Damage(applyDamageTo) - val msg_avatar1 = avatarProbe.receiveN(2, 500 milliseconds) - buildingProbe.expectNoMessage(200 milliseconds) - player1Probe.expectNoMessage(200 milliseconds) - player2Probe.expectNoMessage(200 milliseconds) + val msg_building12 = buildingProbe.receiveN(2,500 milliseconds) assert( - msg_avatar1.head match { - case AvatarServiceMessage("TestCharacter1", AvatarAction.GenericObjectAction(_, PlanetSideGUID(1), 16)) => - true + msg_building12.head match { + case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Offline)) => o eq gen case _ => false } ) assert( - msg_avatar1(1) match { - case AvatarServiceMessage("TestCharacter2", AvatarAction.GenericObjectAction(_, PlanetSideGUID(1), 16)) => - true + msg_building12(1) match { + case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Destabilized)) => o eq gen case _ => false } ) @@ -434,14 +423,13 @@ class GeneratorControlKillsTest extends ActorTest { assert(!gen.Destroyed) assert(gen.Condition == PlanetSideGeneratorState.Normal) - val msg_building = buildingProbe.receiveOne(10500 milliseconds) - val msg_avatar2 = avatarProbe.receiveN(3, 200 milliseconds) - val msg_player1 = player1Probe.receiveOne(100 milliseconds) - player2Probe.expectNoMessage(200 milliseconds) + avatarProbe.expectNoMessage(9500 milliseconds) + val msg_avatar2 = avatarProbe.receiveN(3, 1000 milliseconds) //see DamageableEntity test file + val msg_building = buildingProbe.receiveOne(200 milliseconds) assert( msg_building match { - case BuildingActor.AmenityStateChange(o) => o eq gen - case _ => false + case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Destroyed)) => o eq gen + case _ => false } ) assert( @@ -459,22 +447,25 @@ class GeneratorControlKillsTest extends ActorTest { assert( msg_avatar2(2) match { case AvatarServiceMessage( - "test", - AvatarAction.SendResponse(_, TriggerEffectMessage(PlanetSideGUID(2), "explosion_generator", None, None)) - ) => + "test", + AvatarAction.SendResponse(_, TriggerEffectMessage(PlanetSideGUID(2), "explosion_generator", None, None)) + ) => true case _ => false } ) + assert(gen.Health == 0) + assert(gen.Destroyed) + assert(gen.Condition == PlanetSideGeneratorState.Destroyed) + + val msg_player1 = player1Probe.receiveOne(100 milliseconds) + player2Probe.expectNoMessage(200 milliseconds) assert( msg_player1 match { case _ @Player.Die() => true case _ => false } ) - assert(gen.Health == 0) - assert(gen.Destroyed) - assert(gen.Condition == PlanetSideGeneratorState.Destroyed) } } } @@ -487,7 +478,7 @@ class GeneratorControlNotDestroyTwice extends ActorTest { GUID(guid) } val building = Building("test-building", 1, 1, zone, StructureType.Facility) //guid=1 - val gen = Generator(GlobalDefinitions.generator) //guid=2 + val gen = Generator(GeneratorTest.generator_definition) //guid=2 val player1 = Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=3 player1.Spawn() @@ -573,7 +564,7 @@ class GeneratorControlNotDamageIfExplodingTest extends ActorTest { val activityProbe = TestProbe() zone.Activity = activityProbe.ref - val gen = Generator(GlobalDefinitions.generator) //guid=2 + val gen = Generator(GeneratorTest.generator_definition) //guid=2 gen.Position = Vector3(1, 0, 0) gen.Actor = system.actorOf(Props(classOf[GeneratorControl], gen), "generator-control") @@ -625,13 +616,16 @@ class GeneratorControlNotDamageIfExplodingTest extends ActorTest { assert(gen.Condition == PlanetSideGeneratorState.Normal) //skipped critical state because didn't transition ~50% gen.Actor ! Vitality.Damage(applyDamageTo) - val msg_avatar = avatarProbe.receiveOne(500 milliseconds) - buildingProbe.expectNoMessage(200 milliseconds) - player1Probe.expectNoMessage(200 milliseconds) + val msg_building12 = buildingProbe.receiveN(2,500 milliseconds) assert( - msg_avatar match { - case AvatarServiceMessage("TestCharacter1", AvatarAction.GenericObjectAction(_, PlanetSideGUID(1), 16)) => - true + msg_building12.head match { + case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Offline)) => o eq gen + case _ => false + } + ) + assert( + msg_building12(1) match { + case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Destabilized)) => o eq gen case _ => false } ) @@ -667,7 +661,7 @@ class GeneratorControlNotRepairIfExplodingTest extends ActorTest { val activityProbe = TestProbe() zone.Activity = activityProbe.ref - val gen = Generator(GlobalDefinitions.generator) //guid=2 + val gen = Generator(GeneratorTest.generator_definition) //guid=2 gen.Position = Vector3(1, 0, 0) gen.Actor = system.actorOf(Props(classOf[GeneratorControl], gen), "generator-control") @@ -723,13 +717,16 @@ class GeneratorControlNotRepairIfExplodingTest extends ActorTest { assert(gen.Condition == PlanetSideGeneratorState.Normal) //skipped critical state because didn't transition ~50% gen.Actor ! Vitality.Damage(applyDamageTo) - val msg_avatar1 = avatarProbe.receiveOne(500 milliseconds) - buildingProbe.expectNoMessage(200 milliseconds) - player1Probe.expectNoMessage(200 milliseconds) + val msg_building12 = buildingProbe.receiveN(2,500 milliseconds) assert( - msg_avatar1 match { - case AvatarServiceMessage("TestCharacter1", AvatarAction.GenericObjectAction(_, PlanetSideGUID(1), 16)) => - true + msg_building12.head match { + case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Offline)) => o eq gen + case _ => false + } + ) + assert( + msg_building12(1) match { + case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Destabilized)) => o eq gen case _ => false } ) @@ -765,7 +762,7 @@ class GeneratorControlRepairPastRestorePoint extends ActorTest { val activityProbe = TestProbe() zone.Activity = activityProbe.ref - val gen = Generator(GlobalDefinitions.generator) //guid=2 + val gen = Generator(GeneratorTest.generator_definition) //guid=2 gen.Position = Vector3(1, 0, 0) gen.Actor = system.actorOf(Props(classOf[GeneratorControl], gen), "generator-control") @@ -804,7 +801,7 @@ class GeneratorControlRepairPastRestorePoint extends ActorTest { assert(gen.Destroyed) gen.Actor ! CommonMessages.Use(player1, Some(tool)) //repair - val msg_avatar = avatarProbe.receiveN(4, 500 milliseconds) //expected + val msg_avatar = avatarProbe.receiveN(3, 500 milliseconds) //expected val msg_building = buildingProbe.receiveOne(200 milliseconds) assert( msg_avatar.head match { @@ -825,13 +822,6 @@ class GeneratorControlRepairPastRestorePoint extends ActorTest { ) assert( msg_avatar(2) match { - case AvatarServiceMessage("TestCharacter1", AvatarAction.GenericObjectAction(_, PlanetSideGUID(1), 17)) => - true - case _ => false - } - ) - assert( - msg_avatar(3) match { case AvatarServiceMessage( "TestCharacter1", AvatarAction.SendResponse(_, RepairMessage(ValidPlanetSideGUID(2), _)) @@ -842,7 +832,7 @@ class GeneratorControlRepairPastRestorePoint extends ActorTest { ) assert( msg_building match { - case BuildingActor.AmenityStateChange(o) => o eq gen + case BuildingActor.AmenityStateChange(o, _) => o eq gen case _ => false } ) @@ -852,3 +842,15 @@ class GeneratorControlRepairPastRestorePoint extends ActorTest { } } } + +object GeneratorTest { + final val generator_definition = new GeneratorDefinition(352) { + MaxHealth = 4000 + Damageable = true + DamageableByFriendlyFire = false + Repairable = true + RepairDistance = 13.5f + RepairIfDestroyed = true + //note: no auto-repair + } +} diff --git a/src/test/scala/objects/ResourceSiloTest.scala b/src/test/scala/objects/ResourceSiloTest.scala index ba1fe6432..36dab9b95 100644 --- a/src/test/scala/objects/ResourceSiloTest.scala +++ b/src/test/scala/objects/ResourceSiloTest.scala @@ -289,12 +289,6 @@ class ResourceSiloControlUpdate1Test extends ActorTest { case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(6), 47, 0)) => true case _ => false }) - - val reply4 = zoneEvents.receiveOne(500 milliseconds) - assert(reply4 match { - case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(6), 48, 0)) => true - case _ => false - }) } } } From 924c78ea0b949e2eb7132c590df62f543b3fa48b Mon Sep 17 00:00:00 2001 From: "Jason_DiDonato@yahoo.com" Date: Wed, 21 Oct 2020 19:21:54 -0400 Subject: [PATCH 6/7] promoted generator destruction to flag when to power down facility --- .../net/psforever/actors/session/SessionActor.scala | 6 +++--- .../objects/serverobject/generator/Generator.scala | 10 ++++++++++ .../serverobject/generator/GeneratorControl.scala | 3 ++- src/test/scala/objects/AutoRepairIntegrationTest.scala | 2 +- src/test/scala/objects/GeneratorTest.scala | 8 ++++---- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index 42a4f2a23..9436c2be2 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -6558,9 +6558,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con sendResponse(SetEmpireMessage(guid, building.Faction)) // power building.Generator match { - case Some(obj) if obj.Condition != PlanetSideGeneratorState.Normal => - sendResponse(PlanetsideAttributeMessage(guid, 48, 1)) - sendResponse(PlanetsideAttributeMessage(guid, 38, 0)) + case Some(obj) if obj.Condition == PlanetSideGeneratorState.Destroyed || building.NtuLevel == 0 => + sendResponse(PlanetsideAttributeMessage(guid, 48, 1)) //amenities disabled; red warning lights + sendResponse(PlanetsideAttributeMessage(guid, 38, 0)) //disable spawn target on deployment map case _ => ; } // capitol force dome state diff --git a/src/main/scala/net/psforever/objects/serverobject/generator/Generator.scala b/src/main/scala/net/psforever/objects/serverobject/generator/Generator.scala index b66646cb1..27de959ca 100644 --- a/src/main/scala/net/psforever/objects/serverobject/generator/Generator.scala +++ b/src/main/scala/net/psforever/objects/serverobject/generator/Generator.scala @@ -24,6 +24,16 @@ class Generator(private val gdef: GeneratorDefinition) extends Amenity { Condition } + override def Destroyed_=(state : Boolean) : Boolean = { + val isDestroyed = super.Destroyed_=(state) + condition = if (isDestroyed) { + PlanetSideGeneratorState.Destroyed + } else { + PlanetSideGeneratorState.Normal + } + isDestroyed + } + def Definition: GeneratorDefinition = gdef } diff --git a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala index 693bde06b..503f1dec6 100644 --- a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala @@ -80,6 +80,8 @@ class GeneratorControl(gen: Generator) .orElse { case GeneratorControl.Destabilized() => imminentExplosion = true + //the generator's condition is technically destroyed, but avoid official reporting until the explosion + gen.Condition = PlanetSideGeneratorState.Destroyed GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Destabilized)) queuedExplosion.cancel() queuedExplosion = context.system.scheduler.scheduleOnce(10 seconds, self, GeneratorControl.GeneratorExplodes()) @@ -89,7 +91,6 @@ class GeneratorControl(gen: Generator) val zone = gen.Zone gen.Health = 0 super.DestructionAwareness(gen, gen.LastShot.get) - gen.Condition = PlanetSideGeneratorState.Destroyed GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Destroyed)) //kaboom zone.AvatarEvents ! AvatarServiceMessage( diff --git a/src/test/scala/objects/AutoRepairIntegrationTest.scala b/src/test/scala/objects/AutoRepairIntegrationTest.scala index 3ce685028..188847cad 100644 --- a/src/test/scala/objects/AutoRepairIntegrationTest.scala +++ b/src/test/scala/objects/AutoRepairIntegrationTest.scala @@ -160,7 +160,7 @@ object AutoRepairIntegrationTest { MaxHealth = 500 Damageable = true Repairable = true - autoRepair = AutoRepairStats(1, 500, 500, 1) + autoRepair = AutoRepairStats(200, 500, 500, 1) RepairIfDestroyed = true } } diff --git a/src/test/scala/objects/GeneratorTest.scala b/src/test/scala/objects/GeneratorTest.scala index 10807bf02..436164ec8 100644 --- a/src/test/scala/objects/GeneratorTest.scala +++ b/src/test/scala/objects/GeneratorTest.scala @@ -287,7 +287,7 @@ class GeneratorControlDestroyedTest extends ActorTest { ) assert(gen.Health == 1) assert(!gen.Destroyed) - assert(gen.Condition == PlanetSideGeneratorState.Normal) + assert(gen.Condition == PlanetSideGeneratorState.Destroyed) avatarProbe.expectNoMessage(9500 milliseconds) val msg_avatar2 = avatarProbe.receiveN(3, 1000 milliseconds) //see DamageableEntity test file @@ -421,7 +421,7 @@ class GeneratorControlKillsTest extends ActorTest { ) assert(gen.Health == 1) assert(!gen.Destroyed) - assert(gen.Condition == PlanetSideGeneratorState.Normal) + assert(gen.Condition == PlanetSideGeneratorState.Destroyed) avatarProbe.expectNoMessage(9500 milliseconds) val msg_avatar2 = avatarProbe.receiveN(3, 1000 milliseconds) //see DamageableEntity test file @@ -631,7 +631,7 @@ class GeneratorControlNotDamageIfExplodingTest extends ActorTest { ) assert(gen.Health == 1) assert(!gen.Destroyed) - assert(gen.Condition == PlanetSideGeneratorState.Normal) + assert(gen.Condition == PlanetSideGeneratorState.Destroyed) //going to explode state //once @@ -732,7 +732,7 @@ class GeneratorControlNotRepairIfExplodingTest extends ActorTest { ) assert(gen.Health == 1) assert(!gen.Destroyed) - assert(gen.Condition == PlanetSideGeneratorState.Normal) + assert(gen.Condition == PlanetSideGeneratorState.Destroyed) //going to explode state //once From 1996b1c4a60d21f6b63d7231bc034d85b9bd27d5 Mon Sep 17 00:00:00 2001 From: "Jason_DiDonato@yahoo.com" Date: Sat, 24 Oct 2020 09:24:44 -0400 Subject: [PATCH 7/7] capitol force dome status relfects power state (and, thus, NTU) --- .../psforever/actors/zone/BuildingActor.scala | 139 +++++++++++++++++- .../net/psforever/actors/zone/ZoneActor.scala | 17 ++- .../serverobject/structures/Building.scala | 47 +----- 3 files changed, 150 insertions(+), 53 deletions(-) diff --git a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala index cf1383f53..f29719658 100644 --- a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala +++ b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala @@ -12,7 +12,7 @@ import net.psforever.objects.serverobject.structures.{Amenity, Building, Structu import net.psforever.objects.zones.Zone import net.psforever.persistence import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} -import net.psforever.types.PlanetSideEmpire +import net.psforever.types.{PlanetSideEmpire, PlanetSideGeneratorState} import net.psforever.util.Database._ import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage} @@ -39,6 +39,14 @@ object BuildingActor { final case class SetFaction(faction: PlanetSideEmpire.Value) extends Command + final case class UpdateForceDome(state: Option[Boolean]) extends Command + + object UpdateForceDome { + def apply(): UpdateForceDome = UpdateForceDome(None) + + def apply(state: Boolean): UpdateForceDome = UpdateForceDome(Some(state)) + } + // TODO remove // Changes to building objects should go through BuildingActor // Once they do, we won't need this anymore @@ -59,6 +67,58 @@ object BuildingActor { final case class PowerOn() extends Command final case class PowerOff() extends Command + + /** + * The natural conditions of a facility that is not eligible for its capitol force dome to be expanded. + * The only test not employed is whether or not the target building is a capitol. + * Ommission of this condition makes this test capable of evaluating subcapitol eligibility + * for capitol force dome expansion. + * @param building the target building + * @return `true`, if the conditions for capitol force dome are not met; + * `false`, otherwise + */ + def invalidBuildingCapitolForceDomeConditions(building: Building): Boolean = { + building.Faction == PlanetSideEmpire.NEUTRAL || + building.NtuLevel == 0 || + (building.Generator match { + case Some(o) => o.Condition == PlanetSideGeneratorState.Destroyed + case _ => false + }) + } + + /** + * If this building is a capitol major facility, + * use the faction affinity, the generator status, and the resource silo's capacitance level + * to determine if the capitol force dome should be active. + * @param building the building being evaluated + * @return the condition of the capitol force dome; + * `None`, if the facility is not a capitol building; + * `Some(true|false)` to indicate the state of the force dome + */ + def checkForceDomeStatus(building: Building): Option[Boolean] = { + if (building.IsCapitol) { + val originalStatus = building.ForceDomeActive + val faction = building.Faction + val updatedStatus = if (invalidBuildingCapitolForceDomeConditions(building)) { + false + } else { + val ownedSubCapitols = building.Neighbours(faction) match { + case Some(buildings: Set[Building]) => buildings.count { b => !invalidBuildingCapitolForceDomeConditions(b) } + case None => 0 + } + if (originalStatus && ownedSubCapitols <= 1) { + false + } else if (!originalStatus && ownedSubCapitols > 1) { + true + } else { + originalStatus + } + } + Some(updatedStatus) + } else { + None + } + } } class BuildingActor( @@ -125,6 +185,15 @@ class BuildingActor( galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) Behaviors.same + case UpdateForceDome(stateOpt) => + stateOpt match { + case Some(updatedStatus) if building.IsCapitol && updatedStatus != building.ForceDomeActive => + updateForceDomeStatus(updatedStatus, mapUpdateOnChange = true) + case _ => + alignForceDomeStatus() + } + Behaviors.same + case AmenityStateChange(gen: Generator, data) => if (generatorStateChange(gen, data)) { //update the map @@ -139,11 +208,17 @@ class BuildingActor( Behaviors.same case PowerOff() => - powerLost() + building.Generator match { + case Some(gen) => gen.Actor ! BuildingActor.NtuDepleted() + case _ => powerLost() + } Behaviors.same case PowerOn() => - powerRestored() + building.Generator match { + case Some(gen) if building.NtuLevel > 0 => gen.Actor ! BuildingActor.SuppliedWithNtu() + case _ => powerRestored() + } Behaviors.same case msg @ NtuDepleted() => @@ -198,17 +273,19 @@ class BuildingActor( true case Some(GeneratorControl.Event.Offline) => powerLost() + alignForceDomeStatus(mapUpdateOnChange = false) val zone = building.Zone val msg = AvatarAction.PlanetsideAttributeToAll(building.GUID, 46, 2) building.PlayersInSOI.foreach { player => zone.AvatarEvents ! AvatarServiceMessage(player.Name, msg) } //??? - false + true case Some(GeneratorControl.Event.Normal) => true case Some(GeneratorControl.Event.Online) => // Power restored. Reactor Online. Sensors Online. Weapons Online. All systems nominal. powerRestored() + alignForceDomeStatus(mapUpdateOnChange = false) val events = zone.AvatarEvents val guid = building.GUID val msg1 = AvatarAction.PlanetsideAttributeToAll(guid, 46, 0) @@ -266,11 +343,59 @@ class BuildingActor( case Failure(e) => log.error(e.getMessage) } building.Faction = faction + alignForceDomeStatus(mapUpdateOnChange = false) galaxy ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SetEmpire(building.GUID, faction)) } } + /** + * Evaluate the conditions of the building + * and determine if its capitol force dome state should be updated + * to reflect the actual conditions of the base or its surrounding bases. + * If this building is considered a subcapitol facility to the zone's actual capitol facility, + * and has the capitol force dome has a dependency upon it, + * pass a message onto that facility that it should check its own state alignment. + * @param mapUpdateOnChange if `true`, dispatch a `MapUpdate` message for this building + */ + def alignForceDomeStatus(mapUpdateOnChange: Boolean = true): Unit = { + BuildingActor.checkForceDomeStatus(building) match { + case Some(updatedStatus) if updatedStatus != building.ForceDomeActive => + updateForceDomeStatus(updatedStatus, mapUpdateOnChange) + case None if building.IsSubCapitol => + building.Neighbours match { + case Some(buildings: Set[Building]) => + buildings + .filter { _.IsCapitol } + .foreach { _.Actor ! BuildingActor.UpdateForceDome() } + case None => ; + } + case _ => ; //building is neither a capitol nor a subcapitol + } + } + + /** + * Dispatch a message to update the state of the clients with the server state of the capitol force dome. + * @param updatedStatus the new capitol force dome status + * @param mapUpdateOnChange if `true`, dispatch a `MapUpdate` message for this building + */ + def updateForceDomeStatus(updatedStatus: Boolean, mapUpdateOnChange: Boolean): Unit = { + building.ForceDomeActive = updatedStatus + zone.LocalEvents ! LocalServiceMessage( + zone.id, + LocalAction.UpdateForceDomeStatus(Service.defaultPlayerGUID, building.GUID, updatedStatus) + ) + if (mapUpdateOnChange) { + context.self ! BuildingActor.MapUpdate() + } + } + + /** + * Power has been severed. + * All installed amenities are distributed a `PowerOff` message + * and are instructed to display their "unpowered" model. + * Additionally, the facility is now rendered unspawnable regardless of its player spawning amenities. + */ def powerLost(): Unit = { val zone = building.Zone val zoneId = zone.id @@ -286,6 +411,12 @@ class BuildingActor( events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(guid, 38, 0)) } + /** + * Power has been restored. + * All installed amenities are distributed a `PowerOn` message + * and are instructed to display their "powered" model. + * Additionally, the facility is now rendered spawnable if its player spawning amenities are online. + */ def powerRestored(): Unit = { val zone = building.Zone val zoneId = zone.id diff --git a/src/main/scala/net/psforever/actors/zone/ZoneActor.scala b/src/main/scala/net/psforever/actors/zone/ZoneActor.scala index 492b8b360..9b8546df5 100644 --- a/src/main/scala/net/psforever/actors/zone/ZoneActor.scala +++ b/src/main/scala/net/psforever/actors/zone/ZoneActor.scala @@ -5,7 +5,7 @@ import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors} import net.psforever.objects.ballistics.SourceEntry import net.psforever.objects.ce.Deployable import net.psforever.objects.equipment.Equipment -import net.psforever.objects.serverobject.structures.StructureType +import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.zones.Zone import net.psforever.objects.{ConstructionItem, PlanetSideGameObject, Player, Vehicle} import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3} @@ -76,12 +76,21 @@ class ZoneActor(context: ActorContext[ZoneActor.Command], zone: Zone) ctx.run(query[persistence.Building].filter(_.zoneId == lift(zone.Number))).onComplete { case Success(buildings) => + var capitol: Option[Building] = None buildings.foreach { building => zone.BuildingByMapId(building.localId) match { - case Some(b) => b.Faction = PlanetSideEmpire(building.factionId) - case None => // TODO this happens during testing, need a way to not always persist during tests + case Some(b) => + b.Faction = PlanetSideEmpire(building.factionId) + if(b.IsCapitol) { + capitol = Some(b) + } + case None => + // TODO this happens during testing, need a way to not always persist during tests } - + } + capitol match { + case Some(b) => b.ForceDomeActive = BuildingActor.checkForceDomeStatus(b).getOrElse(false) + case None => ; } case Failure(e) => log.error(e.getMessage) } diff --git a/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala b/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala index 39ab6939d..9ed800f49 100644 --- a/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala +++ b/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala @@ -4,8 +4,8 @@ package net.psforever.objects.serverobject.structures import java.util.concurrent.TimeUnit import akka.actor.ActorContext -import net.psforever.actors.zone.{BuildingActor, ZoneActor} -import net.psforever.objects.{Default, GlobalDefinitions, Player} +import net.psforever.actors.zone.BuildingActor +import net.psforever.objects.{GlobalDefinitions, Player} import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.serverobject.generator.Generator import net.psforever.objects.serverobject.hackable.Hackable @@ -17,8 +17,6 @@ import net.psforever.objects.zones.Zone import net.psforever.packet.game.BuildingInfoUpdateMessage import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, PlanetSideGeneratorState, Vector3} import scalax.collection.{Graph, GraphEdge} -import net.psforever.services.Service -import net.psforever.services.local.{LocalAction, LocalServiceMessage} import akka.actor.typed.scaladsl.adapter._ class Building( @@ -65,16 +63,6 @@ class Building( override def Faction_=(fac: PlanetSideEmpire.Value): PlanetSideEmpire.Value = { faction = fac - if (IsSubCapitol) { - Neighbours match { - case Some(buildings: Set[Building]) => buildings.filter(x => x.IsCapitol).head.UpdateForceDomeStatus() - case None => ; - } - } else if (IsCapitol) { - UpdateForceDomeStatus() - } - // FIXME null check is a bad idea but tests rely on it - if (Zone.actor != null) Zone.actor ! ZoneActor.ZoneMapUpdate() Faction } @@ -136,37 +124,6 @@ class Building( } } - def UpdateForceDomeStatus(): Unit = { - if (IsCapitol) { - val originalStatus = ForceDomeActive - - if (Faction == PlanetSideEmpire.NEUTRAL) { - ForceDomeActive = false - } else { - val ownedSubCapitols = Neighbours(Faction) match { - case Some(buildings: Set[Building]) => buildings.size - case None => 0 - } - - if (ForceDomeActive && ownedSubCapitols <= 1) { - ForceDomeActive = false - } else if (!ForceDomeActive && ownedSubCapitols > 1) { - ForceDomeActive = true - } - } - - if (originalStatus != ForceDomeActive) { - if (Actor != Default.Actor) { - Zone.LocalEvents ! LocalServiceMessage( - Zone.id, - LocalAction.UpdateForceDomeStatus(Service.defaultPlayerGUID, GUID, ForceDomeActive) - ) - Actor ! BuildingActor.MapUpdate() - } - } - } - } - // Get all lattice neighbours matching the specified faction def Neighbours(faction: PlanetSideEmpire.Value): Option[Set[Building]] = { this.Neighbours match {