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,14 +33,16 @@ 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 =>
@ -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,8 +21,7 @@ class PainboxControl(painbox: Painbox) extends Actor {
var disabled = false // Temporary to disable cavern non-radius fields
def receive: Receive = {
case "startup" =>
val initialStartup: Unit = {
if (painbox.Definition.HasNearestDoorDependency) {
(painbox.Owner match {
case obj : Building =>
@ -39,19 +39,20 @@ class PainboxControl(painbox: Painbox) extends Actor {
s"Painbox ${painbox.GUID} on ${painbox.Owner.Continent} - ${painbox.Position} can not find a door that it is dependent on"
)
}
} else {
}
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 {
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 =>
@ -87,19 +88,28 @@ class PainboxControl(painbox: Painbox) extends Actor {
}
}
}
if (!disabled) {
context.become(Stopped)
self ! BuildingActor.PowerOff()
}
}
case _ => ;
var commonBehavior: Receive = {
case "startup" =>
if (bBoxMidPoint == Vector3.Zero) {
initialStartup()
}
def Running: Receive = {
case Painbox.Stop() =>
context.become(Stopped)
painboxTick.cancel()
painboxTick = Default.Cancellable
}
def poweredStateLogic: Receive =
commonBehavior
.orElse {
case Painbox.Start() if isPowered =>
painboxTick.cancel()
painboxTick = context.system.scheduler.scheduleWithFixedDelay(0 seconds, 1 second, self, Painbox.Tick())
case Painbox.Tick() =>
//todo: Account for overlapping pain fields
@ -109,7 +119,7 @@ class PainboxControl(painbox: Painbox) extends Actor {
val owner = painbox.Owner.asInstanceOf[Building]
val faction = owner.Faction
if (
faction != PlanetSideEmpire.NEUTRAL && (nearestDoor match {
isPowered && faction != PlanetSideEmpire.NEUTRAL && (nearestDoor match {
case Some(door) => door.Open.nonEmpty;
case _ => true
})
@ -157,12 +167,21 @@ class PainboxControl(painbox: Painbox) extends Actor {
case _ => ;
}
def Stopped: Receive = {
case Painbox.Start() =>
context.become(Running)
painboxTick.cancel()
painboxTick = context.system.scheduler.scheduleWithFixedDelay(0 seconds, 1 second, self, Painbox.Tick())
def unpoweredStateLogic: Receive =
commonBehavior
.orElse {
case _ => ;
}
def powerTurnOffCallback(): Unit = {
self ! Painbox.Stop()
}
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
.orElse(hackableBehavior)
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 {
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,12 +31,14 @@ class TerminalControl(term: Terminal)
def RepairableObject = term
def AutoRepairObject = term
def receive: Receive =
checkBehavior
.orElse(hackableBehavior)
val commonBehavior: Receive = checkBehavior
.orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser)
.orElse(autoRepairBehavior)
def poweredStateLogic : Receive =
commonBehavior
.orElse(hackableBehavior)
.orElse {
case Terminal.Request(player, msg) =>
TerminalControl.Dispatch(sender(), term, Terminal.TerminalMessage(player, msg, term.Request(player, msg)))
@ -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
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,7 +354,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
.filter {
case (building, spawns) =>
spawns.nonEmpty &&
spawns.exists(_.Offline == false) &&
spawns.exists(_.isOffline == false) &&
structures.contains(building.BuildingType)
}
.filter {
@ -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())