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())