powered amenity control agent; terminals, proximity terminals, spawn tubes, implant mechs, facility turrets, and painfields demonstrate this behavior

This commit is contained in:
Jason_DiDonato@yahoo.com 2020-10-13 01:32:41 -04:00
parent 35b07f897d
commit ef0d18d214
11 changed files with 392 additions and 174 deletions

View file

@ -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

View file

@ -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.

View file

@ -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 = { }
}

View file

@ -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 _ => ;
}
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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 = { }
}

View file

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