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.actors.commands.NtuCommand
import net.psforever.objects.{CommonNtuContainer, NtuContainer} import net.psforever.objects.{CommonNtuContainer, NtuContainer}
import net.psforever.objects.serverobject.PlanetSideServerObject 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.structures.{Amenity, Building, StructureType, WarpGate}
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.persistence import net.psforever.persistence
@ -49,6 +50,10 @@ object BuildingActor {
final case class SuppliedWithNtu() extends Command final case class SuppliedWithNtu() extends Command
final case class NtuDepleted() extends Command final case class NtuDepleted() extends Command
final case class PowerOn() extends Command
final case class PowerOff() extends Command
} }
class BuildingActor( class BuildingActor(
@ -155,6 +160,14 @@ class BuildingActor(
galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage()))
Behaviors.same 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(_) => case AmenityStateChange(_) =>
//TODO when parameter object is finally immutable, perform analysis on it to determine specific actions //TODO when parameter object is finally immutable, perform analysis on it to determine specific actions
//for now, just update the map //for now, just update the map

View file

@ -50,7 +50,7 @@ trait SpawnPoint {
*/ */
def Definition: ObjectDefinition with SpawnPointDefinition 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. * Determine a specific position and orientation in which to spawn the target.

View file

@ -1,7 +1,6 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.implantmech package net.psforever.objects.serverobject.implantmech
import akka.actor.Actor
import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.{GlobalDefinitions, Player, SimpleItem} import net.psforever.objects.{GlobalDefinitions, Player, SimpleItem}
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} 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.damage.{Damageable, DamageableEntity, DamageableMountable}
import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior} import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior}
import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableEntity} 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`. * An `Actor` that handles messages being dispatched to a specific `ImplantTerminalMech`.
* @param mech the "mech" object being governed * @param mech the "mech" object being governed
*/ */
class ImplantTerminalMechControl(mech: ImplantTerminalMech) class ImplantTerminalMechControl(mech: ImplantTerminalMech)
extends Actor extends PoweredAmenityControl
with FactionAffinityBehavior.Check with FactionAffinityBehavior.Check
with MountableBehavior.Mount with MountableBehavior.Mount
with MountableBehavior.Dismount with MountableBehavior.Dismount
@ -33,17 +33,19 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
def RepairableObject = mech def RepairableObject = mech
def AutoRepairObject = mech def AutoRepairObject = mech
def receive: Receive = def commonBehavior: Receive =
checkBehavior checkBehavior
.orElse(mountBehavior)
.orElse(dismountBehavior) .orElse(dismountBehavior)
.orElse(hackableBehavior)
.orElse(takesDamage) .orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser) .orElse(canBeRepairedByNanoDispenser)
.orElse(autoRepairBehavior) .orElse(autoRepairBehavior)
def poweredStateLogic : Receive =
commonBehavior
.orElse(mountBehavior)
.orElse { .orElse {
case CommonMessages.Use(player, Some(item: SimpleItem)) 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 //TODO setup certifications check
mech.Owner match { mech.Owner match {
case b: Building if (b.Faction != player.Faction || b.CaptureTerminalIsHacked) && mech.HackedBy.isEmpty => case b: Building if (b.Faction != player.Faction || b.CaptureTerminalIsHacked) && mech.HackedBy.isEmpty =>
@ -57,6 +59,12 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
case _ => ; case _ => ;
} }
def unpoweredStateLogic: Receive =
commonBehavior
.orElse {
case _ => ;
}
override protected def MountTest( override protected def MountTest(
obj: PlanetSideServerObject with Mountable, obj: PlanetSideServerObject with Mountable,
seatNumber: Int, seatNumber: Int,
@ -98,4 +106,25 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
} }
newHealth 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 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.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.objects.{Default, GlobalDefinitions}
import net.psforever.types.{PlanetSideEmpire, Vector3} import net.psforever.types.{PlanetSideEmpire, Vector3}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} 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.ExecutionContext.Implicits.global
import scala.concurrent.duration._ 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") private[this] val log = org.log4s.getLogger(s"Painbox")
var painboxTick: Cancellable = Default.Cancellable var painboxTick: Cancellable = Default.Cancellable
var nearestDoor: Option[Door] = None 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 var disabled = false // Temporary to disable cavern non-radius fields
def receive: Receive = { val initialStartup: Unit = {
case "startup" => if (painbox.Definition.HasNearestDoorDependency) {
if (painbox.Definition.HasNearestDoorDependency) { (painbox.Owner match {
(painbox.Owner match { case obj : Building =>
case obj: Building => obj.Amenities
obj.Amenities .collect { case door : Door => door }
.collect { case door: Door => door } .sortBy(door => Vector3.DistanceSquared(painbox.Position, door.Position))
.sortBy(door => Vector3.DistanceSquared(painbox.Position, door.Position)) .headOption
.headOption case _ =>
case _ => None
None }) match {
}) match { case door@Some(_) =>
case door @ Some(_) => nearestDoor = door
nearestDoor = door case _ =>
case _ => log.error(
log.error( s"Painbox ${painbox.GUID} on ${painbox.Owner.Continent} - ${painbox.Position} can not find a door that it is dependent on"
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 { else {
if (painbox.Definition.Radius == 0f) { painbox.Owner match {
if (painbox.Owner.Continent.matches("c[0-9]")) { case obj : Building =>
// todo: handle non-radius painboxes in caverns properly val planarRange = 16.5f
log.warn(s"Skipping initialization of ${painbox.GUID} on ${painbox.Owner.Continent} - ${painbox.Position}") val aboveRange = 5
disabled = true val belowRange = 5
} else { // Find amenities within the specified range
painbox.Owner match { val nearbyAmenities = obj.Amenities
case obj: Building => .filter(amenity =>
val planarRange = 16.5f amenity.Position != Vector3.Zero
val aboveRange = 5 && (amenity.Definition == GlobalDefinitions.mb_locker
val belowRange = 5 || 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 // Calculate bounding box of amenities
val nearbyAmenities = obj.Amenities bBoxMinCorner = Vector3(
.filter(amenity => nearbyAmenities.minBy(amenity => amenity.Position.x).Position.x,
amenity.Position != Vector3.Zero nearbyAmenities.minBy(amenity => amenity.Position.y).Position.y,
&& (amenity.Definition == GlobalDefinitions.mb_locker nearbyAmenities.minBy(x => x.Position.z).Position.z
|| amenity.Definition == GlobalDefinitions.respawn_tube )
|| amenity.Definition == GlobalDefinitions.spawn_terminal bBoxMaxCorner = Vector3(
|| amenity.Definition == GlobalDefinitions.order_terminal nearbyAmenities.maxBy(amenity => amenity.Position.x).Position.x,
|| amenity.Definition == GlobalDefinitions.door) nearbyAmenities.maxBy(amenity => amenity.Position.y).Position.y,
&& amenity.Position.x > painbox.Position.x - planarRange && amenity.Position.x < painbox.Position.x + planarRange painbox.Position.z
&& 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 bBoxMidPoint = Vector3(
) (bBoxMinCorner.x + bBoxMaxCorner.x) / 2,
(bBoxMinCorner.y + bBoxMaxCorner.y) / 2,
// Calculate bounding box of amenities (bBoxMinCorner.z + bBoxMaxCorner.z) / 2
bBoxMinCorner = Vector3( )
nearbyAmenities.minBy(amenity => amenity.Position.x).Position.x, case _ => None
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) { if (!disabled) {
context.become(Stopped) self ! BuildingActor.PowerOff()
} }
case _ => ;
} }
def Running: Receive = { var commonBehavior: Receive = {
case "startup" =>
if (bBoxMidPoint == Vector3.Zero) {
initialStartup()
}
case Painbox.Stop() => case Painbox.Stop() =>
context.become(Stopped)
painboxTick.cancel() painboxTick.cancel()
painboxTick = Default.Cancellable painboxTick = Default.Cancellable
}
case Painbox.Tick() => def poweredStateLogic: Receive =
//todo: Account for overlapping pain fields commonBehavior
//todo: Pain module .orElse {
//todo: REK boosting case Painbox.Start() if isPowered =>
val guid = painbox.GUID painboxTick.cancel()
val owner = painbox.Owner.asInstanceOf[Building] painboxTick = context.system.scheduler.scheduleWithFixedDelay(0 seconds, 1 second, self, Painbox.Tick())
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
if (painbox.Definition.Radius != 0f) { case Painbox.Tick() =>
// Spherical pain field //todo: Account for overlapping pain fields
owner.PlayersInSOI //todo: Pain module
.collect { //todo: REK boosting
case p val guid = painbox.GUID
if p.Faction != faction val owner = painbox.Owner.asInstanceOf[Building]
&& p.Health > 0 val faction = owner.Faction
&& Vector3.DistanceSquared(p.Position, position) < radius => if (
events ! AvatarServiceMessage(p.Name, AvatarAction.EnvironmentalDamage(p.GUID, guid, damage)) isPowered && faction != PlanetSideEmpire.NEUTRAL && (nearestDoor match {
} case Some(door) => door.Open.nonEmpty;
} else { case _ => true
// Bounding box pain field })
owner.PlayersInSOI ) {
.collect { val events = painbox.Zone.AvatarEvents
case p val damage = painbox.Definition.Damage
if p.Faction != faction val radius = painbox.Definition.Radius * painbox.Definition.Radius
&& p.Health > 0 => val position = painbox.Position
/*
This may be cpu intensive with a large number of players in SOI. Further performance tweaking may be required if (painbox.Definition.Radius != 0f) {
The bounding box is calculated aligned to the world XY axis, instead of rotating the painbox corners to match the base rotation // Spherical pain field
we instead rotate the player's current coordinates to match the base rotation, which allows for much simplified checking of if the player is owner.PlayersInSOI
within the bounding box .collect {
*/ case p
val playerRot = if p.Faction != faction
Vector3.PlanarRotateAroundPoint(p.Position, bBoxMidPoint, painbox.Owner.Orientation.z.toRadians) && p.Health > 0
if ( && Vector3.DistanceSquared(p.Position, position) < radius =>
bBoxMinCorner.x <= playerRot.x && playerRot.x <= bBoxMaxCorner.x && bBoxMinCorner.y <= playerRot.y && playerRot.y <= bBoxMaxCorner.y events ! AvatarServiceMessage(p.Name, AvatarAction.EnvironmentalDamage(p.GUID, guid, damage))
&& playerRot.z >= bBoxMinCorner.z && playerRot.z <= bBoxMaxCorner.z }
) { } else {
events ! AvatarServiceMessage(p.Name, AvatarAction.EnvironmentalDamage(p.GUID, guid, damage)) // 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 = { def powerTurnOnCallback(): Unit = {
case Painbox.Start() => painbox.Owner match {
context.become(Running) case b: Building if b.PlayersInSOI.nonEmpty =>
painboxTick.cancel() self ! Painbox.Start()
painboxTick = context.system.scheduler.scheduleWithFixedDelay(0 seconds, 1 second, self, Painbox.Tick()) case _ => ;
}
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 // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals 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._
import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
import net.psforever.objects.serverobject.damage.DamageableAmenity import net.psforever.objects.serverobject.damage.DamageableAmenity
import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior} import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior}
import net.psforever.objects.serverobject.repair.RepairableAmenity 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.collection.mutable
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -20,7 +22,7 @@ import scala.concurrent.duration._
* @param term the proximity unit (terminal) * @param term the proximity unit (terminal)
*/ */
class ProximityTerminalControl(term: Terminal with ProximityUnit) class ProximityTerminalControl(term: Terminal with ProximityUnit)
extends Actor extends PoweredAmenityControl
with FactionAffinityBehavior.Check with FactionAffinityBehavior.Check
with HackableBehavior.GenericHackable with HackableBehavior.GenericHackable
with DamageableAmenity with DamageableAmenity
@ -35,11 +37,20 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit)
val callbacks: mutable.ListBuffer[ActorRef] = new mutable.ListBuffer[ActorRef]() val callbacks: mutable.ListBuffer[ActorRef] = new mutable.ListBuffer[ActorRef]()
val log = org.log4s.getLogger val log = org.log4s.getLogger
def receive: Receive = val commonBehavior: Receive = checkBehavior
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(hackableBehavior)
.orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser)
.orElse { .orElse {
case CommonMessages.Use(player, Some(item: SimpleItem)) case CommonMessages.Use(player, Some(item: SimpleItem))
if item.Definition == GlobalDefinitions.remote_electronics_kit => if item.Definition == GlobalDefinitions.remote_electronics_kit =>
@ -66,12 +77,6 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit)
case CommonMessages.Use(_, _) => case CommonMessages.Use(_, _) =>
log.warn(s"unexpected format for CommonMessages.Use in this context") 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() => case ProximityTerminalControl.TerminalAction() =>
val proxDef = term.Definition.asInstanceOf[ProximityDefinition] val proxDef = term.Definition.asInstanceOf[ProximityDefinition]
val validateFunc: PlanetSideGameObject => Boolean = val validateFunc: PlanetSideGameObject => Boolean =
@ -99,6 +104,19 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit)
case _ => 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 = { def Use(target: PlanetSideGameObject, zone: String, callback: ActorRef): Unit = {
val hadNoUsers = term.NumberUsers == 0 val hadNoUsers = term.NumberUsers == 0
if (term.AddUser(target)) { 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 override def toString: String = term.Definition.Name
} }

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals 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.ballistics.ResolvedProjectile
import net.psforever.objects.{GlobalDefinitions, SimpleItem} import net.psforever.objects.{GlobalDefinitions, SimpleItem}
import net.psforever.objects.serverobject.CommonMessages 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.damage.{Damageable, DamageableAmenity}
import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior} import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior}
import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableAmenity} 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`. * An `Actor` that handles messages being dispatched to a specific `Terminal`.
* @param term the `Terminal` object being governed * @param term the `Terminal` object being governed
*/ */
class TerminalControl(term: Terminal) class TerminalControl(term: Terminal)
extends Actor extends PoweredAmenityControl
with FactionAffinityBehavior.Check with FactionAffinityBehavior.Check
with HackableBehavior.GenericHackable with HackableBehavior.GenericHackable
with DamageableAmenity with DamageableAmenity
@ -29,18 +31,20 @@ class TerminalControl(term: Terminal)
def RepairableObject = term def RepairableObject = term
def AutoRepairObject = term def AutoRepairObject = term
def receive: Receive = val commonBehavior: Receive = checkBehavior
checkBehavior .orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser)
.orElse(autoRepairBehavior)
def poweredStateLogic : Receive =
commonBehavior
.orElse(hackableBehavior) .orElse(hackableBehavior)
.orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser)
.orElse(autoRepairBehavior)
.orElse { .orElse {
case Terminal.Request(player, msg) => case Terminal.Request(player, msg) =>
TerminalControl.Dispatch(sender(), term, Terminal.TerminalMessage(player, msg, term.Request(player, msg))) TerminalControl.Dispatch(sender(), term, Terminal.TerminalMessage(player, msg, term.Request(player, msg)))
case CommonMessages.Use(player, Some(item: SimpleItem)) 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 //TODO setup certifications check
term.Owner match { term.Owner match {
case b: Building if (b.Faction != player.Faction || b.CaptureTerminalIsHacked) && term.HackedBy.isEmpty => case b: Building if (b.Faction != player.Faction || b.CaptureTerminalIsHacked) && term.HackedBy.isEmpty =>
@ -55,6 +59,14 @@ class TerminalControl(term: Terminal)
case _ => ; 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 = { override protected def DamageAwareness(target : Target, cause : ResolvedProjectile, amount : Any) : Unit = {
tryAutoRepair() tryAutoRepair()
super.DamageAwareness(target, cause, amount) super.DamageAwareness(target, cause, amount)
@ -62,6 +74,10 @@ class TerminalControl(term: Terminal)
override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile) : Unit = { override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile) : Unit = {
tryAutoRepair() tryAutoRepair()
if (term.HackedBy.nonEmpty) {
val zone = term.Zone
zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.ClearTemporaryHack(Service.defaultPlayerGUID, term))
}
super.DestructionAwareness(target, cause) super.DestructionAwareness(target, cause)
} }
@ -73,6 +89,16 @@ class TerminalControl(term: Terminal)
newHealth 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 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 * @param tDef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/ */
class SpawnTube(tDef: SpawnTubeDefinition) extends Amenity with SpawnPoint { class SpawnTube(tDef: SpawnTubeDefinition) extends Amenity with SpawnPoint {
var offline: Boolean = false
override def isOffline: Boolean = offline || super.isOffline
def Definition: SpawnTubeDefinition = tDef def Definition: SpawnTubeDefinition = tDef
} }

View file

@ -1,21 +1,20 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.tube package net.psforever.objects.serverobject.tube
import akka.actor.Actor
import net.psforever.actors.zone.BuildingActor import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
import net.psforever.objects.serverobject.damage.Damageable.Target import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.serverobject.damage.DamageableAmenity import net.psforever.objects.serverobject.damage.DamageableAmenity
import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, Repairable, RepairableAmenity} 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`. * An `Actor` that handles messages being dispatched to a specific `SpawnTube`.
* @param tube the `SpawnTube` object being governed * @param tube the `SpawnTube` object being governed
*/ */
class SpawnTubeControl(tube: SpawnTube) class SpawnTubeControl(tube: SpawnTube)
extends Actor extends PoweredAmenityControl
with FactionAffinityBehavior.Check with FactionAffinityBehavior.Check
with DamageableAmenity with DamageableAmenity
with RepairableAmenity with RepairableAmenity
@ -25,11 +24,19 @@ class SpawnTubeControl(tube: SpawnTube)
def RepairableObject = tube def RepairableObject = tube
def AutoRepairObject = tube def AutoRepairObject = tube
def receive: Receive = val commonBehavior: Receive = checkBehavior
checkBehavior .orElse(takesDamage)
.orElse(takesDamage) .orElse(canBeRepairedByNanoDispenser)
.orElse(canBeRepairedByNanoDispenser) .orElse(autoRepairBehavior)
.orElse(autoRepairBehavior)
def poweredStateLogic: Receive =
commonBehavior
.orElse {
case _ => ;
}
def unpoweredStateLogic: Receive =
commonBehavior
.orElse { .orElse {
case _ => ; 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 override def toString: String = tube.Definition.Name
} }

View file

@ -1,7 +1,6 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.turret package net.psforever.objects.serverobject.turret
import akka.actor.Actor
import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.{Default, GlobalDefinitions, Player, Tool} import net.psforever.objects.{Default, GlobalDefinitions, Player, Tool}
import net.psforever.objects.equipment.{Ammo, JammableMountedWeapons} 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.damage.{Damageable, DamageableWeaponTurret}
import net.psforever.objects.serverobject.hackable.GenericHackables import net.psforever.objects.serverobject.hackable.GenericHackables
import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableWeaponTurret} 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.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -27,7 +28,7 @@ import scala.concurrent.duration._
* @param turret the `MannedTurret` object being governed * @param turret the `MannedTurret` object being governed
*/ */
class FacilityTurretControl(turret: FacilityTurret) class FacilityTurretControl(turret: FacilityTurret)
extends Actor extends PoweredAmenityControl
with FactionAffinityBehavior.Check with FactionAffinityBehavior.Check
with MountableBehavior.TurretMount with MountableBehavior.TurretMount
with MountableBehavior.Dismount with MountableBehavior.Dismount
@ -51,14 +52,17 @@ class FacilityTurretControl(turret: FacilityTurret)
stopAutoRepair() stopAutoRepair()
} }
def receive: Receive = def commonBehavior: Receive =
checkBehavior checkBehavior
.orElse(jammableBehavior) .orElse(jammableBehavior)
.orElse(mountBehavior)
.orElse(dismountBehavior) .orElse(dismountBehavior)
.orElse(takesDamage) .orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser) .orElse(canBeRepairedByNanoDispenser)
.orElse(autoRepairBehavior) .orElse(autoRepairBehavior)
def poweredStateLogic: Receive =
commonBehavior
.orElse(mountBehavior)
.orElse { .orElse {
case CommonMessages.Use(player, Some((item: Tool, upgradeValue: Int))) case CommonMessages.Use(player, Some((item: Tool, upgradeValue: Int)))
if player.Faction == turret.Faction && if player.Faction == turret.Faction &&
@ -113,6 +117,12 @@ class FacilityTurretControl(turret: FacilityTurret)
case _ => ; case _ => ;
} }
def unpoweredStateLogic: Receive =
commonBehavior
.orElse {
case _ => ;
}
override protected def DamageAwareness(target : Damageable.Target, cause : ResolvedProjectile, amount : Any) : Unit = { override protected def DamageAwareness(target : Damageable.Target, cause : ResolvedProjectile, amount : Any) : Unit = {
tryAutoRepair() tryAutoRepair()
super.DamageAwareness(target, cause, amount) 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, 50, 0))
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 51, 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 { .filter {
case (building, spawns) => case (building, spawns) =>
spawns.nonEmpty && spawns.nonEmpty &&
spawns.exists(_.Offline == false) && spawns.exists(_.isOffline == false) &&
structures.contains(building.BuildingType) structures.contains(building.BuildingType)
} }
.filter { .filter {
case (building, _) => case (building, _) =>
@ -368,7 +368,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
} }
.map { .map {
case (building, spawns: List[SpawnPoint]) => case (building, spawns: List[SpawnPoint]) =>
(building, spawns.filter(!_.Offline)) (building, spawns.filter(!_.isOffline))
} }
.concat( .concat(
(if (ams) Vehicles else List()) (if (ams) Vehicles else List())