diff --git a/src/main/scala/net/psforever/actors/session/ChatActor.scala b/src/main/scala/net/psforever/actors/session/ChatActor.scala
index 9a1c27192..8afe468e9 100644
--- a/src/main/scala/net/psforever/actors/session/ChatActor.scala
+++ b/src/main/scala/net/psforever/actors/session/ChatActor.scala
@@ -7,7 +7,7 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer}
import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.avatar.{BattleRank, Certification, CommandRank, Cosmetic}
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
-import net.psforever.objects.{Default, GlobalDefinitions, Player, Session}
+import net.psforever.objects.{Default, Player, Session}
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.structures.Building
import net.psforever.objects.serverobject.turret.{FacilityTurret, TurretUpgrade, WeaponTurrets}
@@ -342,20 +342,55 @@ class ChatActor(
)
}
- case (_, _, contents) if contents.startsWith("!ntu") && session.account.gm =>
- session.zone.Buildings.values.foreach(building =>
- building.Amenities.foreach(amenity =>
- amenity.Definition match {
- case GlobalDefinitions.resource_silo =>
- val r = new scala.util.Random
- val silo = amenity.asInstanceOf[ResourceSilo]
- val ntu = 900f + r.nextFloat() * 100f - silo.NtuCapacitor
- silo.Actor ! ResourceSilo.UpdateChargeLevel(ntu)
-
- case _ => ()
- }
+ case (_, _, contents) if contents.startsWith("!ntu") && gmCommandAllowed =>
+ val buffer = contents.toLowerCase.split("\\s+")
+ val (facility, customNtuValue) = (buffer.lift(1), buffer.lift(2)) match {
+ case (Some(x), Some(y)) if y.toIntOption.nonEmpty => (Some(x), Some(y.toInt))
+ case (Some(x), None) if x.toIntOption.nonEmpty => (None, Some(x.toInt))
+ case _ => (None, None)
+ }
+ val silos = (facility match {
+ case Some(cur) if cur.toLowerCase().startsWith("curr") =>
+ val position = session.player.Position
+ session.zone.Buildings.values
+ .filter { building =>
+ val soi2 = building.Definition.SOIRadius * building.Definition.SOIRadius
+ Vector3.DistanceSquared(building.Position, position) < soi2
+ }
+ case Some(x) =>
+ session.zone.Buildings.values.find { _.Name.equalsIgnoreCase(x) }.toList
+ case _ =>
+ session.zone.Buildings.values
+ })
+ .flatMap { building => building.Amenities.filter { _.isInstanceOf[ResourceSilo] } }
+ if(silos.isEmpty) {
+ sessionActor ! SessionActor.SendResponse(
+ ChatMsg(UNK_229, true, "Server", s"no targets for ntu found with parameters $facility", None)
)
- )
+ }
+ customNtuValue match {
+ // x = n0% of maximum capacitance
+ case Some(value) if value > -1 && value < 11 =>
+ silos.collect { case silo: ResourceSilo =>
+ silo.Actor ! ResourceSilo.UpdateChargeLevel(value * silo.MaxNtuCapacitor * 0.1f - silo.NtuCapacitor)
+ }
+ // capacitance set to x (where x > 10) exactly, within limits
+ case Some(value) =>
+ silos.collect { case silo: ResourceSilo =>
+ silo.Actor ! ResourceSilo.UpdateChargeLevel(value - silo.NtuCapacitor)
+ }
+ case None =>
+ // x >= n0% of maximum capacitance and x <= maximum capacitance
+ val rand = new scala.util.Random
+ silos.collect { case silo: ResourceSilo =>
+ val a = 7
+ val b = 10 - a
+ val tenth = silo.MaxNtuCapacitor * 0.1f
+ silo.Actor ! ResourceSilo.UpdateChargeLevel(
+ a * tenth + rand.nextFloat() * b * tenth - silo.NtuCapacitor
+ )
+ }
+ }
case _ =>
// unknown ! commands are ignored
diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala
index 98ea92773..6d97d3f41 100644
--- a/src/main/scala/net/psforever/actors/session/SessionActor.scala
+++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala
@@ -542,9 +542,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case ProgressEvent(delta, finishedAction, stepAction, tick) =>
HandleProgressChange(delta, finishedAction, stepAction, tick)
- case Door.DoorMessage(tplayer, msg, order) =>
- HandleDoorMessage(tplayer, msg, order)
-
case GalaxyServiceResponse(_, reply) =>
reply match {
case GalaxyResponse.HotSpotUpdate(zone_index, priority, hot_spot_info) =>
@@ -2189,36 +2186,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
}
- /**
- * na
- * @param tplayer na
- * @param msg na
- * @param order na
- */
- def HandleDoorMessage(tplayer: Player, msg: UseItemMessage, order: Door.Exchange): Unit = {
- val door_guid = msg.object_guid
- order match {
- case Door.OpenEvent() =>
- continent.GUID(door_guid) match {
- case Some(door: Door) =>
- sendResponse(GenericObjectStateMsg(door_guid, 16))
- continent.LocalEvents ! LocalServiceMessage(
- continent.id,
- LocalAction.DoorOpens(tplayer.GUID, continent, door)
- )
-
- case _ =>
- log.warn(s"door $door_guid wanted to be opened but could not be found")
- }
-
- case Door.CloseEvent() =>
- sendResponse(GenericObjectStateMsg(door_guid, 17))
- continent.LocalEvents ! LocalServiceMessage(continent.id, LocalAction.DoorCloses(tplayer.GUID, door_guid))
-
- case Door.NoEvent() => ;
- }
- }
-
/**
* na
* @param toChannel na
@@ -4477,29 +4444,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
ValidObject(object_guid) match {
case Some(door: Door) =>
- if (
- player.Faction == door.Faction || (continent.map.doorToLock.get(object_guid.guid) match {
- case Some(lock_guid) =>
- val lock = continent.GUID(lock_guid).get.asInstanceOf[IFFLock]
- val owner = lock.Owner.asInstanceOf[Building]
- val playerIsOnInside = Vector3.ScalarProjection(lock.Outwards, player.Position - door.Position) < 0f
-
- // If an IFF lock exists and the IFF lock faction doesn't match the current player and one of the following conditions are met open the door:
- // The player is on the inside of the door, determined by the lock orientation
- // The lock is hacked
- // A base is hacked
- // A base is neutral
- // todo: A base is out of power (generator down)
-
- playerIsOnInside || lock.HackedBy.isDefined || owner.CaptureTerminalIsHacked || lock.Faction == PlanetSideEmpire.NEUTRAL
- case None => !door.isOpen // If there's no linked IFF lock just open the door if it's closed.
- })
- ) {
- door.Actor ! Door.Use(player, msg)
- } else if (door.isOpen) {
- //the door is open globally ... except on our screen
- sendResponse(GenericObjectStateMsg(object_guid, 16))
- }
+ door.Actor ! CommonMessages.Use(player)
case Some(resourceSilo: ResourceSilo) =>
resourceSilo.Actor ! CommonMessages.Use(player)
@@ -6609,13 +6554,20 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
*/
def configZone(zone: Zone): Unit = {
zone.Buildings.values.foreach(building => {
- sendResponse(SetEmpireMessage(building.GUID, building.Faction))
-
- // Synchronise capitol force dome state
- if (building.IsCapitol && building.ForceDomeActive) {
- sendResponse(GenericObjectActionMessage(building.GUID, 13))
+ val guid = building.GUID
+ sendResponse(SetEmpireMessage(guid, building.Faction))
+ // power
+ building.Generator match {
+ case Some(obj) if obj.Condition == PlanetSideGeneratorState.Destroyed || building.NtuLevel == 0 =>
+ sendResponse(PlanetsideAttributeMessage(guid, 48, 1)) //amenities disabled; red warning lights
+ sendResponse(PlanetsideAttributeMessage(guid, 38, 0)) //disable spawn target on deployment map
+ case _ => ;
}
- // Synchronise amenities
+ // capitol force dome state
+ if (building.IsCapitol && building.ForceDomeActive) {
+ sendResponse(GenericObjectActionMessage(guid, 13))
+ }
+ // amenities
building.Amenities.collect {
case obj if obj.Destroyed => configAmenityAsDestroyed(obj)
case obj => configAmenityAsWorking(obj)
diff --git a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala
index 60b127950..f29719658 100644
--- a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala
+++ b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala
@@ -7,14 +7,16 @@ 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, GeneratorControl}
import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType, WarpGate}
import net.psforever.objects.zones.Zone
import net.psforever.persistence
-import net.psforever.types.PlanetSideEmpire
+import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
+import net.psforever.types.{PlanetSideEmpire, PlanetSideGeneratorState}
import net.psforever.util.Database._
import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
-import net.psforever.services.{InterstellarClusterService, ServiceManager}
+import net.psforever.services.{InterstellarClusterService, Service, ServiceManager}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
@@ -37,18 +39,86 @@ object BuildingActor {
final case class SetFaction(faction: PlanetSideEmpire.Value) extends Command
+ final case class UpdateForceDome(state: Option[Boolean]) extends Command
+
+ object UpdateForceDome {
+ def apply(): UpdateForceDome = UpdateForceDome(None)
+
+ def apply(state: Boolean): UpdateForceDome = UpdateForceDome(Some(state))
+ }
+
// TODO remove
// Changes to building objects should go through BuildingActor
// Once they do, we won't need this anymore
final case class MapUpdate() extends Command
- final case class AmenityStateChange(obj: Amenity) extends Command
+ final case class AmenityStateChange(obj: Amenity, data: Option[Any]) extends Command
+
+ object AmenityStateChange{
+ def apply(obj: Amenity): AmenityStateChange = AmenityStateChange(obj, None)
+ }
final case class Ntu(command: NtuCommand.Command) extends Command
final case class SuppliedWithNtu() extends Command
final case class NtuDepleted() extends Command
+
+ final case class PowerOn() extends Command
+
+ final case class PowerOff() extends Command
+
+ /**
+ * The natural conditions of a facility that is not eligible for its capitol force dome to be expanded.
+ * The only test not employed is whether or not the target building is a capitol.
+ * Ommission of this condition makes this test capable of evaluating subcapitol eligibility
+ * for capitol force dome expansion.
+ * @param building the target building
+ * @return `true`, if the conditions for capitol force dome are not met;
+ * `false`, otherwise
+ */
+ def invalidBuildingCapitolForceDomeConditions(building: Building): Boolean = {
+ building.Faction == PlanetSideEmpire.NEUTRAL ||
+ building.NtuLevel == 0 ||
+ (building.Generator match {
+ case Some(o) => o.Condition == PlanetSideGeneratorState.Destroyed
+ case _ => false
+ })
+ }
+
+ /**
+ * If this building is a capitol major facility,
+ * use the faction affinity, the generator status, and the resource silo's capacitance level
+ * to determine if the capitol force dome should be active.
+ * @param building the building being evaluated
+ * @return the condition of the capitol force dome;
+ * `None`, if the facility is not a capitol building;
+ * `Some(true|false)` to indicate the state of the force dome
+ */
+ def checkForceDomeStatus(building: Building): Option[Boolean] = {
+ if (building.IsCapitol) {
+ val originalStatus = building.ForceDomeActive
+ val faction = building.Faction
+ val updatedStatus = if (invalidBuildingCapitolForceDomeConditions(building)) {
+ false
+ } else {
+ val ownedSubCapitols = building.Neighbours(faction) match {
+ case Some(buildings: Set[Building]) => buildings.count { b => !invalidBuildingCapitolForceDomeConditions(b) }
+ case None => 0
+ }
+ if (originalStatus && ownedSubCapitols <= 1) {
+ false
+ } else if (!originalStatus && ownedSubCapitols > 1) {
+ true
+ } else {
+ originalStatus
+ }
+ }
+ Some(updatedStatus)
+ } else {
+ None
+ }
+ }
}
class BuildingActor(
@@ -63,6 +133,7 @@ class BuildingActor(
private[this] val log = org.log4s.getLogger
var galaxyService: Option[classic.ActorRef] = None
var interstellarCluster: Option[ActorRef[InterstellarClusterService.Command]] = None
+ var hasNtuSupply: Boolean = true
context.system.receptionist ! Receptionist.Find(
InterstellarClusterService.InterstellarClusterServiceKey,
@@ -107,69 +178,61 @@ class BuildingActor(
): Behavior[Command] = {
Behaviors.receiveMessagePartial {
case SetFaction(faction) =>
- import ctx._
- ctx
- .run(
- query[persistence.Building]
- .filter(_.localId == lift(building.MapId))
- .filter(_.zoneId == lift(zone.Number))
- )
- .onComplete {
- case Success(res) =>
- res.headOption match {
- case Some(_) =>
- ctx
- .run(
- query[persistence.Building]
- .filter(_.localId == lift(building.MapId))
- .filter(_.zoneId == lift(zone.Number))
- .update(_.factionId -> lift(building.Faction.id))
- )
- .onComplete {
- case Success(_) =>
- case Failure(e) => log.error(e.getMessage)
- }
- case _ =>
- ctx
- .run(
- query[persistence.Building]
- .insert(
- _.localId -> lift(building.MapId),
- _.factionId -> lift(building.Faction.id),
- _.zoneId -> lift(zone.Number)
- )
- )
- .onComplete {
- case Success(_) =>
- case Failure(e) => log.error(e.getMessage)
- }
- }
- case Failure(e) => log.error(e.getMessage)
- }
- building.Faction = faction
- galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage()))
- zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SetEmpire(building.GUID, faction))
+ setFactionTo(faction, galaxyService)
Behaviors.same
case MapUpdate() =>
galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage()))
Behaviors.same
- case AmenityStateChange(_) =>
+ case UpdateForceDome(stateOpt) =>
+ stateOpt match {
+ case Some(updatedStatus) if building.IsCapitol && updatedStatus != building.ForceDomeActive =>
+ updateForceDomeStatus(updatedStatus, mapUpdateOnChange = true)
+ case _ =>
+ alignForceDomeStatus()
+ }
+ Behaviors.same
+
+ case AmenityStateChange(gen: Generator, data) =>
+ if (generatorStateChange(gen, data)) {
+ //update the map
+ 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
galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage()))
Behaviors.same
- case msg @ NtuDepleted() =>
- log.trace(s"${building.Definition.Name} ${building.Name} ntu has been depleted")
- building.Amenities.foreach { amenity =>
- amenity.Actor ! msg
+ case PowerOff() =>
+ building.Generator match {
+ case Some(gen) => gen.Actor ! BuildingActor.NtuDepleted()
+ case _ => powerLost()
}
Behaviors.same
+ case PowerOn() =>
+ building.Generator match {
+ case Some(gen) if building.NtuLevel > 0 => gen.Actor ! BuildingActor.SuppliedWithNtu()
+ case _ => powerRestored()
+ }
+ Behaviors.same
+
+ case msg @ NtuDepleted() =>
+ // Someone let the base run out of nanites. No one gets anything.
+ building.Amenities.foreach { amenity =>
+ amenity.Actor ! msg
+ }
+ setFactionTo(PlanetSideEmpire.NEUTRAL, galaxyService)
+ hasNtuSupply = false
+ Behaviors.same
+
case msg @ SuppliedWithNtu() =>
- log.trace(s"ntu supply has been restored to ${building.Definition.Name} ${building.Name}")
+ // Auto-repair restart, mainly. If the Generator works, power should be restored too.
+ hasNtuSupply = true
building.Amenities.foreach { amenity =>
amenity.Actor ! msg
}
@@ -180,6 +243,195 @@ class BuildingActor(
}
}
+ def generatorStateChange(generator: Generator, event: Any): Boolean = {
+ event match {
+ case Some(GeneratorControl.Event.UnderAttack) =>
+ val events = zone.AvatarEvents
+ val guid = building.GUID
+ val msg = AvatarAction.GenericObjectAction(Service.defaultPlayerGUID, guid, 15)
+ building.PlayersInSOI.foreach { player =>
+ events ! AvatarServiceMessage(player.Name, msg)
+ }
+ false
+ case Some(GeneratorControl.Event.Critical) =>
+ val events = zone.AvatarEvents
+ val guid = building.GUID
+ val msg = AvatarAction.PlanetsideAttributeToAll(guid, 46, 1)
+ building.PlayersInSOI.foreach { player =>
+ events ! AvatarServiceMessage(player.Name, msg)
+ }
+ true
+ case Some(GeneratorControl.Event.Destabilized) =>
+ val events = zone.AvatarEvents
+ val guid = building.GUID
+ val msg = AvatarAction.GenericObjectAction(Service.defaultPlayerGUID, guid, 16)
+ building.PlayersInSOI.foreach { player =>
+ events ! AvatarServiceMessage(player.Name, msg)
+ }
+ false
+ case Some(GeneratorControl.Event.Destroyed) =>
+ true
+ case Some(GeneratorControl.Event.Offline) =>
+ powerLost()
+ alignForceDomeStatus(mapUpdateOnChange = false)
+ val zone = building.Zone
+ val msg = AvatarAction.PlanetsideAttributeToAll(building.GUID, 46, 2)
+ building.PlayersInSOI.foreach { player =>
+ zone.AvatarEvents ! AvatarServiceMessage(player.Name, msg)
+ } //???
+ true
+ case Some(GeneratorControl.Event.Normal) =>
+ true
+ case Some(GeneratorControl.Event.Online) =>
+ // Power restored. Reactor Online. Sensors Online. Weapons Online. All systems nominal.
+ powerRestored()
+ alignForceDomeStatus(mapUpdateOnChange = false)
+ val events = zone.AvatarEvents
+ val guid = building.GUID
+ val msg1 = AvatarAction.PlanetsideAttributeToAll(guid, 46, 0)
+ val msg2 = AvatarAction.GenericObjectAction(Service.defaultPlayerGUID, guid, 17)
+ building.PlayersInSOI.foreach { player =>
+ val name = player.Name
+ events ! AvatarServiceMessage(name, msg1) //reset ???; might be global?
+ events ! AvatarServiceMessage(name, msg2) //This facility's generator is back on line
+ }
+ true
+ case _ =>
+ false
+ }
+ }
+
+ def setFactionTo(faction: PlanetSideEmpire.Value, galaxy: classic.ActorRef): Unit = {
+ if (hasNtuSupply) {
+ import ctx._
+ ctx
+ .run(
+ query[persistence.Building]
+ .filter(_.localId == lift(building.MapId))
+ .filter(_.zoneId == lift(zone.Number))
+ )
+ .onComplete {
+ case Success(res) =>
+ res.headOption match {
+ case Some(_) =>
+ ctx
+ .run(
+ query[persistence.Building]
+ .filter(_.localId == lift(building.MapId))
+ .filter(_.zoneId == lift(zone.Number))
+ .update(_.factionId -> lift(building.Faction.id))
+ )
+ .onComplete {
+ case Success(_) =>
+ case Failure(e) => log.error(e.getMessage)
+ }
+ case _ =>
+ ctx
+ .run(
+ query[persistence.Building]
+ .insert(
+ _.localId -> lift(building.MapId),
+ _.factionId -> lift(building.Faction.id),
+ _.zoneId -> lift(zone.Number)
+ )
+ )
+ .onComplete {
+ case Success(_) =>
+ case Failure(e) => log.error(e.getMessage)
+ }
+ }
+ case Failure(e) => log.error(e.getMessage)
+ }
+ building.Faction = faction
+ alignForceDomeStatus(mapUpdateOnChange = false)
+ galaxy ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage()))
+ zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SetEmpire(building.GUID, faction))
+ }
+ }
+
+ /**
+ * Evaluate the conditions of the building
+ * and determine if its capitol force dome state should be updated
+ * to reflect the actual conditions of the base or its surrounding bases.
+ * If this building is considered a subcapitol facility to the zone's actual capitol facility,
+ * and has the capitol force dome has a dependency upon it,
+ * pass a message onto that facility that it should check its own state alignment.
+ * @param mapUpdateOnChange if `true`, dispatch a `MapUpdate` message for this building
+ */
+ def alignForceDomeStatus(mapUpdateOnChange: Boolean = true): Unit = {
+ BuildingActor.checkForceDomeStatus(building) match {
+ case Some(updatedStatus) if updatedStatus != building.ForceDomeActive =>
+ updateForceDomeStatus(updatedStatus, mapUpdateOnChange)
+ case None if building.IsSubCapitol =>
+ building.Neighbours match {
+ case Some(buildings: Set[Building]) =>
+ buildings
+ .filter { _.IsCapitol }
+ .foreach { _.Actor ! BuildingActor.UpdateForceDome() }
+ case None => ;
+ }
+ case _ => ; //building is neither a capitol nor a subcapitol
+ }
+ }
+
+ /**
+ * Dispatch a message to update the state of the clients with the server state of the capitol force dome.
+ * @param updatedStatus the new capitol force dome status
+ * @param mapUpdateOnChange if `true`, dispatch a `MapUpdate` message for this building
+ */
+ def updateForceDomeStatus(updatedStatus: Boolean, mapUpdateOnChange: Boolean): Unit = {
+ building.ForceDomeActive = updatedStatus
+ zone.LocalEvents ! LocalServiceMessage(
+ zone.id,
+ LocalAction.UpdateForceDomeStatus(Service.defaultPlayerGUID, building.GUID, updatedStatus)
+ )
+ if (mapUpdateOnChange) {
+ context.self ! BuildingActor.MapUpdate()
+ }
+ }
+
+ /**
+ * Power has been severed.
+ * All installed amenities are distributed a `PowerOff` message
+ * and are instructed to display their "unpowered" model.
+ * Additionally, the facility is now rendered unspawnable regardless of its player spawning amenities.
+ */
+ def powerLost(): Unit = {
+ val zone = building.Zone
+ val zoneId = zone.id
+ val events = zone.AvatarEvents
+ val guid = building.GUID
+ val powerMsg = BuildingActor.PowerOff()
+ building.Amenities.foreach { amenity =>
+ amenity.Actor ! powerMsg
+ }
+ //amenities disabled; red warning lights
+ events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(guid, 48, 1))
+ //disable spawn target on deployment map
+ events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(guid, 38, 0))
+ }
+
+ /**
+ * Power has been restored.
+ * All installed amenities are distributed a `PowerOn` message
+ * and are instructed to display their "powered" model.
+ * Additionally, the facility is now rendered spawnable if its player spawning amenities are online.
+ */
+ def powerRestored(): Unit = {
+ val zone = building.Zone
+ val zoneId = zone.id
+ val events = zone.AvatarEvents
+ val guid = building.GUID
+ val powerMsg = BuildingActor.PowerOn()
+ building.Amenities.foreach { amenity =>
+ amenity.Actor ! powerMsg
+ }
+ //amenities enabled; normal lights
+ events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(guid, 48, 0))
+ //enable spawn target on deployment map
+ events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(guid, 38, 1))
+ }
+
def ntu(msg: NtuCommand.Command): Behavior[Command] = {
import NtuCommand._
msg match {
diff --git a/src/main/scala/net/psforever/actors/zone/ZoneActor.scala b/src/main/scala/net/psforever/actors/zone/ZoneActor.scala
index 492b8b360..9b8546df5 100644
--- a/src/main/scala/net/psforever/actors/zone/ZoneActor.scala
+++ b/src/main/scala/net/psforever/actors/zone/ZoneActor.scala
@@ -5,7 +5,7 @@ import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors}
import net.psforever.objects.ballistics.SourceEntry
import net.psforever.objects.ce.Deployable
import net.psforever.objects.equipment.Equipment
-import net.psforever.objects.serverobject.structures.StructureType
+import net.psforever.objects.serverobject.structures.{Building, StructureType}
import net.psforever.objects.zones.Zone
import net.psforever.objects.{ConstructionItem, PlanetSideGameObject, Player, Vehicle}
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
@@ -76,12 +76,21 @@ class ZoneActor(context: ActorContext[ZoneActor.Command], zone: Zone)
ctx.run(query[persistence.Building].filter(_.zoneId == lift(zone.Number))).onComplete {
case Success(buildings) =>
+ var capitol: Option[Building] = None
buildings.foreach { building =>
zone.BuildingByMapId(building.localId) match {
- case Some(b) => b.Faction = PlanetSideEmpire(building.factionId)
- case None => // TODO this happens during testing, need a way to not always persist during tests
+ case Some(b) =>
+ b.Faction = PlanetSideEmpire(building.factionId)
+ if(b.IsCapitol) {
+ capitol = Some(b)
+ }
+ case None =>
+ // TODO this happens during testing, need a way to not always persist during tests
}
-
+ }
+ capitol match {
+ case Some(b) => b.ForceDomeActive = BuildingActor.checkForceDomeStatus(b).getOrElse(false)
+ case None => ;
}
case Failure(e) => log.error(e.getMessage)
}
diff --git a/src/main/scala/net/psforever/objects/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/doors/Door.scala b/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala
index 56e7193be..b10574bf5 100644
--- a/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala
@@ -25,16 +25,6 @@ class Door(private val ddef: DoorDefinition) extends Amenity {
Open
}
- def Use(player: Player, msg: UseItemMessage): Door.Exchange = {
- if (openState.isEmpty) {
- openState = Some(player)
- Door.OpenEvent()
- } else {
- openState = None
- Door.CloseEvent()
- }
- }
-
def Definition: DoorDefinition = ddef
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala b/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala
index 8614db3c2..10fa58b8a 100644
--- a/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala
@@ -1,21 +1,90 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.doors
-import akka.actor.Actor
+import net.psforever.objects.Player
+import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
+import net.psforever.objects.serverobject.locks.IFFLock
+import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl}
+import net.psforever.services.Service
+import net.psforever.services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse}
+import net.psforever.types.{PlanetSideEmpire, Vector3}
/**
* An `Actor` that handles messages being dispatched to a specific `Door`.
* @param door the `Door` object being governed
*/
-class DoorControl(door: Door) extends Actor with FactionAffinityBehavior.Check {
+class DoorControl(door: Door)
+ extends PoweredAmenityControl
+ with FactionAffinityBehavior.Check {
def FactionObject: FactionAffinity = door
- def receive: Receive =
- checkBehavior.orElse {
- case Door.Use(player, msg) =>
- sender() ! Door.DoorMessage(player, msg, door.Use(player, msg))
+ val commonBehavior: Receive = checkBehavior
- case _ => ;
+ def poweredStateLogic: Receive =
+ commonBehavior
+ .orElse {
+ case CommonMessages.Use(player, _) =>
+ val zone = door.Zone
+ val doorGUID = door.GUID
+ if (
+ player.Faction == door.Faction || (zone.GUID(zone.map.doorToLock.getOrElse(doorGUID.guid, 0)) match {
+ case Some(lock: IFFLock) =>
+ val owner = lock.Owner.asInstanceOf[Building]
+ val playerIsOnInside = Vector3.ScalarProjection(lock.Outwards, player.Position - door.Position) < 0f
+ /*
+ If an IFF lock exists and
+ the IFF lock faction doesn't match the current player and
+ one of the following conditions are met:
+ 1. player is on the inside of the door (determined by the lock orientation)
+ 2. lock is hacked
+ 3. facility capture terminal has been hacked
+ 4. base is neutral
+ ... open the door.
+ */
+ playerIsOnInside || lock.HackedBy.isDefined || owner.CaptureTerminalIsHacked || lock.Faction == PlanetSideEmpire.NEUTRAL
+ case _ => true // no linked IFF lock, just try open the door
+ })
+ ) {
+ openDoor(player)
+ }
+
+ case _ => ;
+ }
+
+ def unpoweredStateLogic: Receive = {
+ commonBehavior
+ .orElse {
+ case CommonMessages.Use(player, _) =>
+ //without power, the door opens freely
+ openDoor(player)
+
+ case _ => ;
+ }
+ }
+
+ def openDoor(player: Player): Unit = {
+ val zone = door.Zone
+ val doorGUID = door.GUID
+ if (!door.isOpen) {
+ //global open
+ door.Open = player
+ zone.LocalEvents ! LocalServiceMessage(
+ zone.id,
+ LocalAction.DoorOpens(Service.defaultPlayerGUID, zone, door)
+ )
}
+ else {
+ //the door should already open, but the requesting player does not see it as open
+ sender() ! LocalServiceResponse(
+ player.Name,
+ Service.defaultPlayerGUID,
+ LocalResponse.DoorOpens(doorGUID)
+ )
+ }
+ }
+
+ override def powerTurnOffCallback() : Unit = { }
+
+ override def powerTurnOnCallback() : Unit = { }
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/generator/Generator.scala b/src/main/scala/net/psforever/objects/serverobject/generator/Generator.scala
index b66646cb1..27de959ca 100644
--- a/src/main/scala/net/psforever/objects/serverobject/generator/Generator.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/generator/Generator.scala
@@ -24,6 +24,16 @@ class Generator(private val gdef: GeneratorDefinition) extends Amenity {
Condition
}
+ override def Destroyed_=(state : Boolean) : Boolean = {
+ val isDestroyed = super.Destroyed_=(state)
+ condition = if (isDestroyed) {
+ PlanetSideGeneratorState.Destroyed
+ } else {
+ PlanetSideGeneratorState.Normal
+ }
+ isDestroyed
+ }
+
def Definition: GeneratorDefinition = gdef
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala
index 5a45f43f7..503f1dec6 100644
--- a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala
@@ -1,9 +1,9 @@
// Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.generator
-import akka.actor.Actor
+import akka.actor.{Actor, Cancellable}
import net.psforever.actors.zone.BuildingActor
-import net.psforever.objects.{Player, Tool}
+import net.psforever.objects.{Default, Player, Tool}
import net.psforever.objects.ballistics._
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
import net.psforever.objects.serverobject.damage.Damageable.Target
@@ -34,21 +34,64 @@ class GeneratorControl(gen: Generator)
def DamageableObject = gen
def RepairableObject = gen
def AutoRepairObject = gen
+ /** flagged to explode after some time */
var imminentExplosion: Boolean = false
- var alarmCooldownPeriod: Boolean = false
+ /** explode when this timer completes */
+ var queuedExplosion: Cancellable = Default.Cancellable
+ /** when damaged, announce that damage was dealt on a schedule */
+ var alarmCooldown: Cancellable = Default.Cancellable
- def receive: Receive =
+ /*
+ behavior of the generator piggybacks from the logic used in `AmenityAutoRepair`
+ AAR splits its logic based on whether or not it has detected a source of nanite transfer units (NTU)
+ this amenity is the bridge between NTU and facility power so it leverages that logic
+ it is split between when detecting ntu and when starved for ntu
+ */
+
+ def receive: Receive = withNtu
+
+ /** behavior that is valid for both "with-ntu" and "without-ntu" */
+ val commonBehavior: Receive =
checkBehavior
.orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser)
.orElse(autoRepairBehavior)
.orElse {
- case GeneratorControl.GeneratorExplodes() => //TODO this only works with projectiles right now!
+ case GeneratorControl.UnderThreatAlarm() =>
+ //alert to damage and block other damage alerts for a time
+ if (alarmCooldown.isCancelled) {
+ GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.UnderAttack))
+ alarmCooldown.cancel()
+ alarmCooldown = context.system.scheduler.scheduleOnce(delay = 5 seconds, self, GeneratorControl.AlarmReset())
+ }
+
+ case GeneratorControl.AlarmReset() =>
+ //clear the blocker for alerting to damage
+ alarmCooldown = Default.Cancellable
+ }
+
+ /*
+ when NTU is detected,
+ the generator can be properly destabilized and explode
+ the generator can be repaired to operational status and power the facility in which it is installed
+ */
+ def withNtu: Receive =
+ commonBehavior
+ .orElse {
+ case GeneratorControl.Destabilized() =>
+ imminentExplosion = true
+ //the generator's condition is technically destroyed, but avoid official reporting until the explosion
+ gen.Condition = PlanetSideGeneratorState.Destroyed
+ GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Destabilized))
+ queuedExplosion.cancel()
+ queuedExplosion = context.system.scheduler.scheduleOnce(10 seconds, self, GeneratorControl.GeneratorExplodes())
+
+ case GeneratorControl.GeneratorExplodes() =>
+ //TODO this only works with projectiles right now!
val zone = gen.Zone
gen.Health = 0
super.DestructionAwareness(gen, gen.LastShot.get)
- gen.Condition = PlanetSideGeneratorState.Destroyed
- GeneratorControl.UpdateOwner(gen)
+ GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Destroyed))
//kaboom
zone.AvatarEvents ! AvatarServiceMessage(
zone.id,
@@ -57,6 +100,8 @@ class GeneratorControl(gen: Generator)
TriggerEffectMessage(gen.GUID, "explosion_generator", None, None)
)
)
+ queuedExplosion.cancel()
+ queuedExplosion = Default.Cancellable
imminentExplosion = false
//kill everyone within 14m
gen.Owner match {
@@ -71,24 +116,46 @@ class GeneratorControl(gen: Generator)
}
gen.ClearHistory()
- case GeneratorControl.UnderThreatAlarm() =>
- if (!alarmCooldownPeriod) {
- alarmCooldownPeriod = true
- GeneratorControl.BroadcastGeneratorEvent(gen, event = 15)
- context.system.scheduler.scheduleOnce(delay = 5 seconds, self, GeneratorControl.AlarmReset())
- }
-
- case GeneratorControl.AlarmReset() =>
- alarmCooldownPeriod = false
+ case GeneratorControl.Restored() =>
+ GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Online))
case _ => ;
}
+ /*
+ when ntu is not expected,
+ the generator can still be destroyed but will not explode
+ handles the possibility that ntu was lost during an ongoing destabilization and cancels the explosion
+ */
+ def withoutNtu: Receive =
+ commonBehavior
+ .orElse {
+ case GeneratorControl.GeneratorExplodes() =>
+ queuedExplosion.cancel()
+ queuedExplosion = Default.Cancellable
+ imminentExplosion = false
+
+ case GeneratorControl.Destabilized() =>
+ //if the generator is destabilized but has no ntu, it will not explode
+ gen.Health = 0
+ super.DestructionAwareness(gen, gen.LastShot.get)
+ queuedExplosion.cancel()
+ queuedExplosion = Default.Cancellable
+ imminentExplosion = false
+ gen.Condition = PlanetSideGeneratorState.Destroyed
+ GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Destroyed))
+ gen.ClearHistory()
+
+ case _ =>
+ }
+
override protected def CanPerformRepairs(obj: Target, player: Player, item: Tool): Boolean = {
+ //if an explosion is queued, disallow repairs
!imminentExplosion && super.CanPerformRepairs(obj, player, item)
}
override protected def WillAffectTarget(target: Target, damage: Int, cause: ResolvedProjectile): Boolean = {
+ //if an explosion is queued, disallow further damage
!imminentExplosion && super.WillAffectTarget(target, damage, cause)
}
@@ -104,11 +171,11 @@ class GeneratorControl(gen: Generator)
override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = {
tryAutoRepair()
+ //if the target is already destroyed, do not let it be destroyed again
if (!target.Destroyed) {
target.Health = 1 //temporary
- imminentExplosion = true
- context.system.scheduler.scheduleOnce(10 seconds, self, GeneratorControl.GeneratorExplodes())
- GeneratorControl.BroadcastGeneratorEvent(gen, 16)
+ GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Offline))
+ self ! GeneratorControl.Destabilized()
}
}
@@ -123,12 +190,40 @@ class GeneratorControl(gen: Generator)
override def Restoration(obj: Repairable.Target): Unit = {
super.Restoration(obj)
gen.Condition = PlanetSideGeneratorState.Normal
- GeneratorControl.UpdateOwner(gen)
- GeneratorControl.BroadcastGeneratorEvent(gen, 17)
+ GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Normal))
+ self ! GeneratorControl.Restored()
+ }
+
+ override def withNtuSupplyCallback() : Unit = {
+ context.become(withNtu)
+ super.withNtuSupplyCallback()
+ //if not destroyed when a source of ntu is detected, restore facility power
+ if(!gen.Destroyed) {
+ self ! GeneratorControl.Restored()
+ }
+ }
+
+ override def noNtuSupplyCallback() : Unit = {
+ //auto-repair must stop naturally
+ context.become(withoutNtu)
+ super.noNtuSupplyCallback()
+ //if not destroyed when cutoff from a source of ntu, stop facility power generation
+ if(!gen.Destroyed) {
+ GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Offline))
+ }
+ //can any explosion (see withoutNtu->GenweratorControl.Destabilized)
+ if(!queuedExplosion.isCancelled) {
+ queuedExplosion.cancel()
+ self ! GeneratorControl.Destabilized()
+ }
}
}
object GeneratorControl {
+ /**
+ * na
+ */
+ private case class Destabilized()
/**
* na
@@ -147,30 +242,31 @@ object GeneratorControl {
/**
* na
- * @param obj na
*/
- private def UpdateOwner(obj: Generator): Unit = {
- obj.Owner match {
- case b: Building => b.Actor ! BuildingActor.AmenityStateChange(obj)
- case _ => ;
- }
- }
+ private case class Restored()
/**
* na
- * @param target the generator
- * @param event the action code for the event
*/
- private def BroadcastGeneratorEvent(target: Generator, event: Int): Unit = {
- target.Owner match {
- case b: Building =>
- val events = target.Zone.AvatarEvents
- val msg = AvatarAction.GenericObjectAction(Service.defaultPlayerGUID, target.Owner.GUID, event)
- b.PlayersInSOI.foreach { player =>
- events ! AvatarServiceMessage(player.Name, msg)
- }
- case _ => ;
- }
+ object Event extends Enumeration {
+ val
+ Critical, //PlanetSideGeneratorState.Critical
+ UnderAttack,
+ Destabilized,
+ Destroyed, //PlanetSideGeneratorState.Destroyed
+ Offline,
+ Normal, //PlanetSideGeneratorState.Normal
+ Online
+ = Value
+ }
+
+ /**
+ * Send a message back to the owner for which this `Amenity` entity is installed.
+ * @param obj the entity doing the self-reporting
+ * @param data optional information that indicates the nature of the state change
+ */
+ private def UpdateOwner(obj: Generator, data: Option[Any] = None): Unit = {
+ obj.Owner.Actor ! BuildingActor.AmenityStateChange(obj, data)
}
/**
@@ -185,7 +281,7 @@ object GeneratorControl {
val max: Float = target.MaxHealth.toFloat
if (target.Condition != PlanetSideGeneratorState.Critical && health / max < 0.51f) { //becoming critical
target.Condition = PlanetSideGeneratorState.Critical
- GeneratorControl.UpdateOwner(target)
+ GeneratorControl.UpdateOwner(target, Some(GeneratorControl.Event.Critical))
}
//the generator is under attack
target.Actor ! UnderThreatAlarm()
diff --git a/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala b/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala
index 45853ba2f..4400a3e58 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,32 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
}
newHealth
}
+
+ override def tryAutoRepair() : Boolean = {
+ isPowered && super.tryAutoRepair()
+ }
+
+ def powerTurnOffCallback(): Unit = {
+ stopAutoRepair()
+ //kick all occupants
+ val guid = mech.GUID
+ val zone = mech.Zone
+ 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 = {
+ tryAutoRepair()
+ }
}
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..8e09e30cb 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"
- )
+ def 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/repair/AmenityAutoRepair.scala b/src/main/scala/net/psforever/objects/serverobject/repair/AmenityAutoRepair.scala
index 22d45abe9..64b925d60 100644
--- a/src/main/scala/net/psforever/objects/serverobject/repair/AmenityAutoRepair.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/repair/AmenityAutoRepair.scala
@@ -129,10 +129,25 @@ trait AmenityAutoRepair
/**
* Attempt to start auto-repair operation
* only if no operation is currently being processed.
+ * @see `actuallyTryAutoRepair`
* @return `true`, if the auto-repair process started specifically due to this call;
* `false`, if it was already started, or did not start
*/
- final def tryAutoRepair(): Boolean = {
+ def tryAutoRepair(): Boolean = {
+ actuallyTryAutoRepair()
+ }
+
+ /**
+ * Attempt to start auto-repair operation
+ * only if no operation is currently being processed.
+ * In case that an override to the normals operations of `tryAutoRepair` is necessary,
+ * but the superclass can not be invoked,
+ * this method is the backup of those operations to initiate auto-repair.
+ * @see `tryAutoRepair`
+ * @return `true`, if the auto-repair process started specifically due to this call;
+ * `false`, if it was already started, or did not start
+ */
+ final def actuallyTryAutoRepair(): Boolean = {
val before = autoRepairTimer.isCancelled
autoRepairStartFunc()
!(before || autoRepairTimer.isCancelled)
diff --git a/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala b/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala
index f726b57d9..673829f83 100644
--- a/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala
@@ -113,18 +113,9 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
LowNtuWarning(enabled = true)
}
if (resourceSilo.NtuCapacitor == 0 && siloChargeBeforeChange > 0) {
- // Oops, someone let the base run out of power. Shut it all down.
- zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.PlanetsideAttribute(building.GUID, 48, 1))
building.Actor ! BuildingActor.NtuDepleted()
building.Actor ! BuildingActor.AmenityStateChange(resourceSilo)
- building.Actor ! BuildingActor.SetFaction(PlanetSideEmpire.NEUTRAL)
} else if (siloChargeBeforeChange == 0 && resourceSilo.NtuCapacitor > 0) {
- // Power restored. Reactor Online. Sensors Online. Weapons Online. All systems nominal.
- //todo: Check generator is online before starting up
- zone.AvatarEvents ! AvatarServiceMessage(
- zone.id,
- AvatarAction.PlanetsideAttribute(building.GUID, 48, 0)
- )
building.Actor ! BuildingActor.SuppliedWithNtu()
building.Actor ! BuildingActor.AmenityStateChange(resourceSilo)
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala b/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala
index e1ba19faa..9ed800f49 100644
--- a/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala
@@ -4,8 +4,8 @@ package net.psforever.objects.serverobject.structures
import java.util.concurrent.TimeUnit
import akka.actor.ActorContext
-import net.psforever.actors.zone.{BuildingActor, ZoneActor}
-import net.psforever.objects.{Default, GlobalDefinitions, Player}
+import net.psforever.actors.zone.BuildingActor
+import net.psforever.objects.{GlobalDefinitions, Player}
import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.serverobject.generator.Generator
import net.psforever.objects.serverobject.hackable.Hackable
@@ -17,8 +17,6 @@ import net.psforever.objects.zones.Zone
import net.psforever.packet.game.BuildingInfoUpdateMessage
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, PlanetSideGeneratorState, Vector3}
import scalax.collection.{Graph, GraphEdge}
-import net.psforever.services.Service
-import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import akka.actor.typed.scaladsl.adapter._
class Building(
@@ -65,16 +63,6 @@ class Building(
override def Faction_=(fac: PlanetSideEmpire.Value): PlanetSideEmpire.Value = {
faction = fac
- if (IsSubCapitol) {
- Neighbours match {
- case Some(buildings: Set[Building]) => buildings.filter(x => x.IsCapitol).head.UpdateForceDomeStatus()
- case None => ;
- }
- } else if (IsCapitol) {
- UpdateForceDomeStatus()
- }
- // FIXME null check is a bad idea but tests rely on it
- if (Zone.actor != null) Zone.actor ! ZoneActor.ZoneMapUpdate()
Faction
}
@@ -114,6 +102,13 @@ class Building(
}
}
+ def Generator: Option[Generator] = {
+ Amenities.find(_.isInstanceOf[Generator]) match {
+ case Some(obj: Generator) => Some(obj)
+ case _ => None
+ }
+ }
+
def CaptureTerminal: Option[CaptureTerminal] = {
Amenities.find(_.isInstanceOf[CaptureTerminal]) match {
case Some(term) => Some(term.asInstanceOf[CaptureTerminal])
@@ -129,37 +124,6 @@ class Building(
}
}
- def UpdateForceDomeStatus(): Unit = {
- if (IsCapitol) {
- val originalStatus = ForceDomeActive
-
- if (Faction == PlanetSideEmpire.NEUTRAL) {
- ForceDomeActive = false
- } else {
- val ownedSubCapitols = Neighbours(Faction) match {
- case Some(buildings: Set[Building]) => buildings.size
- case None => 0
- }
-
- if (ForceDomeActive && ownedSubCapitols <= 1) {
- ForceDomeActive = false
- } else if (!ForceDomeActive && ownedSubCapitols > 1) {
- ForceDomeActive = true
- }
- }
-
- if (originalStatus != ForceDomeActive) {
- if (Actor != Default.Actor) {
- Zone.LocalEvents ! LocalServiceMessage(
- Zone.id,
- LocalAction.UpdateForceDomeStatus(Service.defaultPlayerGUID, GUID, ForceDomeActive)
- )
- Actor ! BuildingActor.MapUpdate()
- }
- }
- }
- }
-
// Get all lattice neighbours matching the specified faction
def Neighbours(faction: PlanetSideEmpire.Value): Option[Set[Building]] = {
this.Neighbours match {
@@ -187,8 +151,8 @@ class Building(
(false, PlanetSideEmpire.NEUTRAL, 0L)
}
//if we have no generator, assume the state is "Normal"
- val (generatorState, boostGeneratorPain) = Amenities.find(x => x.isInstanceOf[Generator]) match {
- case Some(obj: Generator) =>
+ val (generatorState, boostGeneratorPain) = Generator match {
+ case Some(obj) =>
(obj.Condition, false) // todo: poll pain field strength
case _ =>
(PlanetSideGeneratorState.Normal, false)
diff --git a/src/main/scala/net/psforever/objects/serverobject/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..04ded0e60 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,17 @@
// 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.Damageable.Target
import net.psforever.objects.serverobject.damage.DamageableAmenity
import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior}
-import net.psforever.objects.serverobject.repair.RepairableAmenity
-import net.psforever.objects.serverobject.structures.Building
+import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableAmenity}
+import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl}
+import net.psforever.services.Service
+import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import scala.collection.mutable
import scala.concurrent.duration._
@@ -20,26 +23,38 @@ 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
- with RepairableAmenity {
+ with RepairableAmenity
+ with AmenityAutoRepair {
def FactionObject = term
def HackableObject = term
def TerminalObject = term
def DamageableObject = term
def RepairableObject = term
+ def AutoRepairObject = term
var terminalAction: Cancellable = Default.Cancellable
val callbacks: mutable.ListBuffer[ActorRef] = new mutable.ListBuffer[ActorRef]()
val log = org.log4s.getLogger
- def receive: Receive =
- checkBehavior
+ val commonBehavior: Receive = checkBehavior
+ .orElse(takesDamage)
+ .orElse(canBeRepairedByNanoDispenser)
+ .orElse(autoRepairBehavior)
+ .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 +81,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 +108,31 @@ 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 _ => ;
+ }
+
+ override def PerformRepairs(target : Target, amount : Int) : Int = {
+ val newHealth = super.PerformRepairs(target, amount)
+ if(newHealth == target.Definition.MaxHealth) {
+ stopAutoRepair()
+ }
+ newHealth
+ }
+
+ override def tryAutoRepair() : Boolean = {
+ isPowered && super.tryAutoRepair()
+ }
+
def Use(target: PlanetSideGameObject, zone: String, callback: ActorRef): Unit = {
val hadNoUsers = term.NumberUsers == 0
if (term.AddUser(target)) {
@@ -145,6 +179,25 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit)
}
}
+ def powerTurnOffCallback() : Unit = {
+ stopAutoRepair()
+ //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 = {
+ tryAutoRepair()
+ }
+
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..fcfdba3ce 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,23 @@ class TerminalControl(term: Terminal)
newHealth
}
+ override def tryAutoRepair() : Boolean = {
+ isPowered && super.tryAutoRepair()
+ }
+
+ def powerTurnOffCallback() : Unit = {
+ stopAutoRepair()
+ //clear hack state
+ if (term.HackedBy.nonEmpty) {
+ val zone = term.Zone
+ zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.ClearTemporaryHack(Service.defaultPlayerGUID, term))
+ }
+ }
+
+ def powerTurnOnCallback() : Unit = {
+ tryAutoRepair()
+ }
+
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..c7607bc14 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,27 @@ class SpawnTubeControl(tube: SpawnTube)
}
}
+ override def tryAutoRepair() : Boolean = {
+ isPowered && super.tryAutoRepair()
+ }
+
+ def powerTurnOffCallback(): Unit = {
+ tube.offline = false
+ stopAutoRepair()
+ tube.Owner match {
+ case b: Building => b.Actor ! BuildingActor.AmenityStateChange(tube)
+ case _ => ;
+ }
+ }
+
+ def powerTurnOnCallback(): Unit = {
+ tube.offline = true
+ tryAutoRepair()
+ 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..3ae7f1601 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,32 @@ class FacilityTurretControl(turret: FacilityTurret)
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 50, 0))
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 51, 0))
}
+
+ override def tryAutoRepair() : Boolean = {
+ isPowered && super.tryAutoRepair()
+ }
+
+ def powerTurnOffCallback(): Unit = {
+ stopAutoRepair()
+ //kick all occupants
+ val guid = turret.GUID
+ val zone = turret.Zone
+ 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 = {
+ tryAutoRepair()
+ }
}
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())
diff --git a/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala b/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
index a1539e792..d3a8044fc 100644
--- a/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
+++ b/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
@@ -144,7 +144,7 @@ import scodec.codecs._
* `45 - NTU charge bar 0-10, 5 = 50% full. Seems to apply to both ANT and NTU Silo (possibly siphons?)`
* `46 - Sends "Generator damage is at a critical level!" message`
* `47 - Sets base NTU level to CRITICAL. MUST use base MapId not base GUID`
- * `48 - Set to 1 to send base power loss message & turns on red warning lights throughout base. MUST use base MapId not base GUID`
+ * `48 - Set to 1 to send base power loss message & turns on red warning lights throughout base. MUST use base MapId not base GUID`?
* `49 - Vehicle texture effects state? (>0 turns on ANT panel glow or ntu silo panel glow + orbs) (bit?)`
* `52 - Vehicle particle effects? (>0 turns on orbs going towards ANT. Doesn't affect silo) (bit?)`
* `53 - LFS. Value is 1 to flag LFS`
diff --git a/src/test/scala/objects/AutoRepairIntegrationTest.scala b/src/test/scala/objects/AutoRepairIntegrationTest.scala
index 581943081..188847cad 100644
--- a/src/test/scala/objects/AutoRepairIntegrationTest.scala
+++ b/src/test/scala/objects/AutoRepairIntegrationTest.scala
@@ -4,6 +4,7 @@ package objects
import akka.actor.Props
import akka.testkit.TestProbe
import base.FreedContextActorTest
+import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.avatar.Avatar
import net.psforever.objects.ballistics.{Projectile, ProjectileResolution, ResolvedProjectile, SourceEntry}
import net.psforever.objects.guid.NumberPoolHub
@@ -57,6 +58,7 @@ class AutoRepairFacilityIntegrationTest extends FreedContextActorTest {
silo.NtuCapacitor = 1000
silo.Actor = system.actorOf(Props(classOf[ResourceSiloControl], silo), "test-silo")
silo.Actor ! "startup"
+ building.Actor ! BuildingActor.PowerOn() //artificial
val wep_fmode = weapon.FireMode
val wep_prof = wep_fmode.Add
@@ -158,7 +160,7 @@ object AutoRepairIntegrationTest {
MaxHealth = 500
Damageable = true
Repairable = true
- autoRepair = AutoRepairStats(1, 500, 500, 1)
+ autoRepair = AutoRepairStats(200, 500, 500, 1)
RepairIfDestroyed = true
}
}
diff --git a/src/test/scala/objects/DoorTest.scala b/src/test/scala/objects/DoorTest.scala
index e1ef43bb8..bc908e66e 100644
--- a/src/test/scala/objects/DoorTest.scala
+++ b/src/test/scala/objects/DoorTest.scala
@@ -2,17 +2,22 @@
package objects
import akka.actor.{ActorSystem, Props}
+import akka.testkit.TestProbe
import base.ActorTest
import net.psforever.objects.avatar.Avatar
+import net.psforever.objects.guid.NumberPoolHub
+import net.psforever.objects.guid.source.MaxNumberSource
+import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.{Default, GlobalDefinitions, Player}
import net.psforever.objects.serverobject.doors.{Door, DoorControl}
import net.psforever.objects.serverobject.structures.{Building, StructureType}
-import net.psforever.objects.zones.Zone
+import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.packet.game.UseItemMessage
+import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.types._
import org.specs2.mutable.Specification
-import scala.concurrent.duration.Duration
+import scala.concurrent.duration._
class DoorTest extends Specification {
val player = Player(Avatar(0, "test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
@@ -59,10 +64,12 @@ class DoorTest extends Specification {
)
val door = Door(GlobalDefinitions.door)
door.Open.isEmpty mustEqual true
- door.Use(player, msg)
+ door.Open = player
+ door.isOpen mustEqual true
door.Open.contains(player) mustEqual true
- door.Use(player, msg)
+ door.Open = None
door.Open.isEmpty mustEqual true
+ door.isOpen mustEqual false
}
}
}
@@ -81,6 +88,8 @@ class DoorControl2Test extends ActorTest {
"DoorControl" should {
"open on use" in {
val (player, door) = DoorControlTest.SetUpAgents(PlanetSideEmpire.TR)
+ val probe = new TestProbe(system)
+ door.Zone.LocalEvents = probe.ref
val msg = UseItemMessage(
PlanetSideGUID(1),
PlanetSideGUID(0),
@@ -96,13 +105,12 @@ class DoorControl2Test extends ActorTest {
) //faked
assert(door.Open.isEmpty)
- door.Actor ! Door.Use(player, msg)
- val reply = receiveOne(Duration.create(500, "ms"))
- assert(reply.isInstanceOf[Door.DoorMessage])
- val reply2 = reply.asInstanceOf[Door.DoorMessage]
- assert(reply2.player == player)
- assert(reply2.msg == msg)
- assert(reply2.response == Door.OpenEvent())
+ door.Actor ! CommonMessages.Use(player, Some(msg))
+ val reply = probe.receiveOne(1000 milliseconds)
+ assert(reply match {
+ case LocalServiceMessage("test", LocalAction.DoorOpens(PlanetSideGUID(0), _, d)) => d eq door
+ case _ => false
+ })
assert(door.Open.isDefined)
}
}
@@ -124,16 +132,24 @@ class DoorControl3Test extends ActorTest {
object DoorControlTest {
def SetUpAgents(faction: PlanetSideEmpire.Value)(implicit system: ActorSystem): (Player, Door) = {
val door = Door(GlobalDefinitions.door)
+ val guid = new NumberPoolHub(new MaxNumberSource(5))
+ val zone = new Zone("test", new ZoneMap("test"), 0) {
+ override def SetupNumberPools() = {}
+ GUID(guid)
+ }
+ guid.register(door, 1)
door.Actor = system.actorOf(Props(classOf[DoorControl], door), "door")
door.Owner = new Building(
"Building",
building_guid = 0,
map_id = 0,
- Zone.Nowhere,
+ zone,
StructureType.Building,
GlobalDefinitions.building
)
door.Owner.Faction = faction
- (Player(Avatar(0, "test", faction, CharacterGender.Male, 0, CharacterVoice.Mute)), door)
+ val player = Player(Avatar(0, "test", faction, CharacterGender.Male, 0, CharacterVoice.Mute))
+ guid.register(player, 2)
+ (player, door)
}
}
diff --git a/src/test/scala/objects/GeneratorTest.scala b/src/test/scala/objects/GeneratorTest.scala
index 91ea0926f..436164ec8 100644
--- a/src/test/scala/objects/GeneratorTest.scala
+++ b/src/test/scala/objects/GeneratorTest.scala
@@ -11,7 +11,7 @@ import net.psforever.objects.{GlobalDefinitions, Player, Tool}
import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.serverobject.CommonMessages
-import net.psforever.objects.serverobject.generator.{Generator, GeneratorControl}
+import net.psforever.objects.serverobject.generator.{Generator, GeneratorControl, GeneratorDefinition}
import net.psforever.objects.serverobject.structures.{Building, StructureType}
import net.psforever.objects.vital.Vitality
import net.psforever.objects.zones.{Zone, ZoneMap}
@@ -25,12 +25,12 @@ import scala.concurrent.duration._
class GeneratorTest extends Specification {
"Generator" should {
"construct" in {
- Generator(GlobalDefinitions.generator)
+ Generator(GeneratorTest.generator_definition)
ok
}
"start in 'Normal' condition" in {
- val obj = Generator(GlobalDefinitions.generator)
+ val obj = Generator(GeneratorTest.generator_definition)
obj.Condition mustEqual PlanetSideGeneratorState.Normal
}
}
@@ -39,7 +39,7 @@ class GeneratorTest extends Specification {
class GeneratorControlConstructTest extends ActorTest {
"GeneratorControl" should {
"construct" in {
- val gen = Generator(GlobalDefinitions.generator)
+ val gen = Generator(GeneratorTest.generator_definition)
gen.Actor = system.actorOf(Props(classOf[GeneratorControl], gen), "gen-control")
assert(gen.Actor != ActorRef.noSender)
}
@@ -57,7 +57,7 @@ class GeneratorControlDamageTest extends ActorTest {
val activityProbe = TestProbe()
zone.Activity = activityProbe.ref
- val gen = Generator(GlobalDefinitions.generator) //guid=2
+ val gen = Generator(GeneratorTest.generator_definition) //guid=2
gen.Position = Vector3(1, 0, 0)
gen.Actor = system.actorOf(Props(classOf[GeneratorControl], gen), "generator-control")
@@ -106,19 +106,18 @@ class GeneratorControlDamageTest extends ActorTest {
assert(gen.Condition == PlanetSideGeneratorState.Normal)
gen.Actor ! Vitality.Damage(applyDamageTo)
- val msg_avatar = avatarProbe.receiveN(2, 500 milliseconds)
- buildingProbe.expectNoMessage(200 milliseconds)
+ val msg_avatar = avatarProbe.receiveOne(500 milliseconds)
+ val msg_building = buildingProbe.receiveOne(500 milliseconds)
assert(
- msg_avatar.head match {
+ msg_avatar match {
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
case _ => false
}
)
assert(
- msg_avatar(1) match {
- case AvatarServiceMessage("TestCharacter1", AvatarAction.GenericObjectAction(_, PlanetSideGUID(1), 15)) =>
- true
- case _ => false
+ msg_building match {
+ case BuildingActor.AmenityStateChange(_, Some(GeneratorControl.Event.UnderAttack)) => true
+ case _ => false
}
)
assert(gen.Health < gen.Definition.MaxHealth)
@@ -139,7 +138,7 @@ class GeneratorControlCriticalTest extends ActorTest {
val activityProbe = TestProbe()
zone.Activity = activityProbe.ref
- val gen = Generator(GlobalDefinitions.generator) //guid=2
+ val gen = Generator(GeneratorTest.generator_definition) //guid=2
gen.Position = Vector3(1, 0, 0)
gen.Actor = system.actorOf(Props(classOf[GeneratorControl], gen), "generator-control")
@@ -190,25 +189,18 @@ class GeneratorControlCriticalTest extends ActorTest {
assert(gen.Condition == PlanetSideGeneratorState.Normal)
gen.Actor ! Vitality.Damage(applyDamageTo)
- val msg_avatar = avatarProbe.receiveN(2, 500 milliseconds)
+ val msg_avatar = avatarProbe.receiveOne(500 milliseconds)
val msg_building = buildingProbe.receiveOne(500 milliseconds)
assert(
- msg_avatar.head match {
+ msg_avatar match {
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
case _ => false
}
)
- assert(
- msg_avatar(1) match {
- case AvatarServiceMessage("TestCharacter1", AvatarAction.GenericObjectAction(_, PlanetSideGUID(1), 15)) =>
- true
- case _ => false
- }
- )
assert(
msg_building match {
- case BuildingActor.AmenityStateChange(o) => o eq gen
- case _ => false
+ case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Critical)) => o eq gen
+ case _ => false
}
)
assert(gen.Health < halfHealth)
@@ -229,7 +221,7 @@ class GeneratorControlDestroyedTest extends ActorTest {
val activityProbe = TestProbe()
zone.Activity = activityProbe.ref
- val gen = Generator(GlobalDefinitions.generator) //guid=2
+ val gen = Generator(GeneratorTest.generator_definition) //guid=2
gen.Position = Vector3(1, 0, 0)
gen.Actor = system.actorOf(Props(classOf[GeneratorControl], gen), "generator-control")
@@ -269,7 +261,6 @@ class GeneratorControlDestroyedTest extends ActorTest {
Vector3(1, 0, 0)
)
val applyDamageTo = resolved.damage_model.Calculate(resolved)
- gen.Actor ! BuildingActor.NtuDepleted() //no auto-repair
expectNoMessage(200 milliseconds)
//we're not testing that the math is correct
@@ -281,26 +272,30 @@ class GeneratorControlDestroyedTest extends ActorTest {
assert(gen.Condition == PlanetSideGeneratorState.Normal) //skipped critical state because didn't transition ~50%
gen.Actor ! Vitality.Damage(applyDamageTo)
- val msg_avatar1 = avatarProbe.receiveOne(500 milliseconds)
- buildingProbe.expectNoMessage(200 milliseconds)
+ val msg_building12 = buildingProbe.receiveN(2,500 milliseconds)
assert(
- msg_avatar1 match {
- case AvatarServiceMessage("TestCharacter1", AvatarAction.GenericObjectAction(_, PlanetSideGUID(1), 16)) =>
- true
+ msg_building12.head match {
+ case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Offline)) => o eq gen
+ case _ => false
+ }
+ )
+ assert(
+ msg_building12(1) match {
+ case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Destabilized)) => o eq gen
case _ => false
}
)
assert(gen.Health == 1)
assert(!gen.Destroyed)
- assert(gen.Condition == PlanetSideGeneratorState.Normal)
+ assert(gen.Condition == PlanetSideGeneratorState.Destroyed)
- avatarProbe.expectNoMessage(9 seconds)
+ avatarProbe.expectNoMessage(9500 milliseconds)
val msg_avatar2 = avatarProbe.receiveN(3, 1000 milliseconds) //see DamageableEntity test file
val msg_building = buildingProbe.receiveOne(200 milliseconds)
assert(
msg_building match {
- case BuildingActor.AmenityStateChange(o) => o eq gen
- case _ => false
+ case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Destroyed)) => o eq gen
+ case _ => false
}
)
assert(
@@ -352,7 +347,7 @@ class GeneratorControlKillsTest extends ActorTest {
val activityProbe = TestProbe()
zone.Activity = activityProbe.ref
- val gen = Generator(GlobalDefinitions.generator) //guid=2
+ val gen = Generator(GeneratorTest.generator_definition) //guid=2
gen.Position = Vector3(1, 0, 0)
gen.Actor = system.actorOf(Props(classOf[GeneratorControl], gen), "generator-control")
@@ -400,7 +395,6 @@ class GeneratorControlKillsTest extends ActorTest {
Vector3(1, 0, 0)
)
val applyDamageTo = resolved.damage_model.Calculate(resolved)
- gen.Actor ! BuildingActor.NtuDepleted() //no auto-repair
expectNoMessage(200 milliseconds)
//we're not testing that the math is correct
@@ -412,36 +406,30 @@ class GeneratorControlKillsTest extends ActorTest {
assert(gen.Condition == PlanetSideGeneratorState.Normal) //skipped critical state because didn't transition ~50%
gen.Actor ! Vitality.Damage(applyDamageTo)
- val msg_avatar1 = avatarProbe.receiveN(2, 500 milliseconds)
- buildingProbe.expectNoMessage(200 milliseconds)
- player1Probe.expectNoMessage(200 milliseconds)
- player2Probe.expectNoMessage(200 milliseconds)
+ val msg_building12 = buildingProbe.receiveN(2,500 milliseconds)
assert(
- msg_avatar1.head match {
- case AvatarServiceMessage("TestCharacter1", AvatarAction.GenericObjectAction(_, PlanetSideGUID(1), 16)) =>
- true
+ msg_building12.head match {
+ case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Offline)) => o eq gen
case _ => false
}
)
assert(
- msg_avatar1(1) match {
- case AvatarServiceMessage("TestCharacter2", AvatarAction.GenericObjectAction(_, PlanetSideGUID(1), 16)) =>
- true
+ msg_building12(1) match {
+ case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Destabilized)) => o eq gen
case _ => false
}
)
assert(gen.Health == 1)
assert(!gen.Destroyed)
- assert(gen.Condition == PlanetSideGeneratorState.Normal)
+ assert(gen.Condition == PlanetSideGeneratorState.Destroyed)
- val msg_building = buildingProbe.receiveOne(10500 milliseconds)
- val msg_avatar2 = avatarProbe.receiveN(3, 200 milliseconds)
- val msg_player1 = player1Probe.receiveOne(100 milliseconds)
- player2Probe.expectNoMessage(200 milliseconds)
+ avatarProbe.expectNoMessage(9500 milliseconds)
+ val msg_avatar2 = avatarProbe.receiveN(3, 1000 milliseconds) //see DamageableEntity test file
+ val msg_building = buildingProbe.receiveOne(200 milliseconds)
assert(
msg_building match {
- case BuildingActor.AmenityStateChange(o) => o eq gen
- case _ => false
+ case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Destroyed)) => o eq gen
+ case _ => false
}
)
assert(
@@ -459,22 +447,25 @@ class GeneratorControlKillsTest extends ActorTest {
assert(
msg_avatar2(2) match {
case AvatarServiceMessage(
- "test",
- AvatarAction.SendResponse(_, TriggerEffectMessage(PlanetSideGUID(2), "explosion_generator", None, None))
- ) =>
+ "test",
+ AvatarAction.SendResponse(_, TriggerEffectMessage(PlanetSideGUID(2), "explosion_generator", None, None))
+ ) =>
true
case _ => false
}
)
+ assert(gen.Health == 0)
+ assert(gen.Destroyed)
+ assert(gen.Condition == PlanetSideGeneratorState.Destroyed)
+
+ val msg_player1 = player1Probe.receiveOne(100 milliseconds)
+ player2Probe.expectNoMessage(200 milliseconds)
assert(
msg_player1 match {
case _ @Player.Die() => true
case _ => false
}
)
- assert(gen.Health == 0)
- assert(gen.Destroyed)
- assert(gen.Condition == PlanetSideGeneratorState.Destroyed)
}
}
}
@@ -487,7 +478,7 @@ class GeneratorControlNotDestroyTwice extends ActorTest {
GUID(guid)
}
val building = Building("test-building", 1, 1, zone, StructureType.Facility) //guid=1
- val gen = Generator(GlobalDefinitions.generator) //guid=2
+ val gen = Generator(GeneratorTest.generator_definition) //guid=2
val player1 =
Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=3
player1.Spawn()
@@ -573,7 +564,7 @@ class GeneratorControlNotDamageIfExplodingTest extends ActorTest {
val activityProbe = TestProbe()
zone.Activity = activityProbe.ref
- val gen = Generator(GlobalDefinitions.generator) //guid=2
+ val gen = Generator(GeneratorTest.generator_definition) //guid=2
gen.Position = Vector3(1, 0, 0)
gen.Actor = system.actorOf(Props(classOf[GeneratorControl], gen), "generator-control")
@@ -625,19 +616,22 @@ class GeneratorControlNotDamageIfExplodingTest extends ActorTest {
assert(gen.Condition == PlanetSideGeneratorState.Normal) //skipped critical state because didn't transition ~50%
gen.Actor ! Vitality.Damage(applyDamageTo)
- val msg_avatar = avatarProbe.receiveOne(500 milliseconds)
- buildingProbe.expectNoMessage(200 milliseconds)
- player1Probe.expectNoMessage(200 milliseconds)
+ val msg_building12 = buildingProbe.receiveN(2,500 milliseconds)
assert(
- msg_avatar match {
- case AvatarServiceMessage("TestCharacter1", AvatarAction.GenericObjectAction(_, PlanetSideGUID(1), 16)) =>
- true
+ msg_building12.head match {
+ case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Offline)) => o eq gen
+ case _ => false
+ }
+ )
+ assert(
+ msg_building12(1) match {
+ case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Destabilized)) => o eq gen
case _ => false
}
)
assert(gen.Health == 1)
assert(!gen.Destroyed)
- assert(gen.Condition == PlanetSideGeneratorState.Normal)
+ assert(gen.Condition == PlanetSideGeneratorState.Destroyed)
//going to explode state
//once
@@ -667,7 +661,7 @@ class GeneratorControlNotRepairIfExplodingTest extends ActorTest {
val activityProbe = TestProbe()
zone.Activity = activityProbe.ref
- val gen = Generator(GlobalDefinitions.generator) //guid=2
+ val gen = Generator(GeneratorTest.generator_definition) //guid=2
gen.Position = Vector3(1, 0, 0)
gen.Actor = system.actorOf(Props(classOf[GeneratorControl], gen), "generator-control")
@@ -723,19 +717,22 @@ class GeneratorControlNotRepairIfExplodingTest extends ActorTest {
assert(gen.Condition == PlanetSideGeneratorState.Normal) //skipped critical state because didn't transition ~50%
gen.Actor ! Vitality.Damage(applyDamageTo)
- val msg_avatar1 = avatarProbe.receiveOne(500 milliseconds)
- buildingProbe.expectNoMessage(200 milliseconds)
- player1Probe.expectNoMessage(200 milliseconds)
+ val msg_building12 = buildingProbe.receiveN(2,500 milliseconds)
assert(
- msg_avatar1 match {
- case AvatarServiceMessage("TestCharacter1", AvatarAction.GenericObjectAction(_, PlanetSideGUID(1), 16)) =>
- true
+ msg_building12.head match {
+ case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Offline)) => o eq gen
+ case _ => false
+ }
+ )
+ assert(
+ msg_building12(1) match {
+ case BuildingActor.AmenityStateChange(o, Some(GeneratorControl.Event.Destabilized)) => o eq gen
case _ => false
}
)
assert(gen.Health == 1)
assert(!gen.Destroyed)
- assert(gen.Condition == PlanetSideGeneratorState.Normal)
+ assert(gen.Condition == PlanetSideGeneratorState.Destroyed)
//going to explode state
//once
@@ -765,7 +762,7 @@ class GeneratorControlRepairPastRestorePoint extends ActorTest {
val activityProbe = TestProbe()
zone.Activity = activityProbe.ref
- val gen = Generator(GlobalDefinitions.generator) //guid=2
+ val gen = Generator(GeneratorTest.generator_definition) //guid=2
gen.Position = Vector3(1, 0, 0)
gen.Actor = system.actorOf(Props(classOf[GeneratorControl], gen), "generator-control")
@@ -804,7 +801,7 @@ class GeneratorControlRepairPastRestorePoint extends ActorTest {
assert(gen.Destroyed)
gen.Actor ! CommonMessages.Use(player1, Some(tool)) //repair
- val msg_avatar = avatarProbe.receiveN(4, 500 milliseconds) //expected
+ val msg_avatar = avatarProbe.receiveN(3, 500 milliseconds) //expected
val msg_building = buildingProbe.receiveOne(200 milliseconds)
assert(
msg_avatar.head match {
@@ -825,13 +822,6 @@ class GeneratorControlRepairPastRestorePoint extends ActorTest {
)
assert(
msg_avatar(2) match {
- case AvatarServiceMessage("TestCharacter1", AvatarAction.GenericObjectAction(_, PlanetSideGUID(1), 17)) =>
- true
- case _ => false
- }
- )
- assert(
- msg_avatar(3) match {
case AvatarServiceMessage(
"TestCharacter1",
AvatarAction.SendResponse(_, RepairMessage(ValidPlanetSideGUID(2), _))
@@ -842,7 +832,7 @@ class GeneratorControlRepairPastRestorePoint extends ActorTest {
)
assert(
msg_building match {
- case BuildingActor.AmenityStateChange(o) => o eq gen
+ case BuildingActor.AmenityStateChange(o, _) => o eq gen
case _ => false
}
)
@@ -852,3 +842,15 @@ class GeneratorControlRepairPastRestorePoint extends ActorTest {
}
}
}
+
+object GeneratorTest {
+ final val generator_definition = new GeneratorDefinition(352) {
+ MaxHealth = 4000
+ Damageable = true
+ DamageableByFriendlyFire = false
+ Repairable = true
+ RepairDistance = 13.5f
+ RepairIfDestroyed = true
+ //note: no auto-repair
+ }
+}
diff --git a/src/test/scala/objects/ResourceSiloTest.scala b/src/test/scala/objects/ResourceSiloTest.scala
index ba1fe6432..36dab9b95 100644
--- a/src/test/scala/objects/ResourceSiloTest.scala
+++ b/src/test/scala/objects/ResourceSiloTest.scala
@@ -289,12 +289,6 @@ class ResourceSiloControlUpdate1Test extends ActorTest {
case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(6), 47, 0)) => true
case _ => false
})
-
- val reply4 = zoneEvents.receiveOne(500 milliseconds)
- assert(reply4 match {
- case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(6), 48, 0)) => true
- case _ => false
- })
}
}
}