mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-19 18:14:44 +00:00
commit
d24fefaa91
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = { }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 _ => ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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?)`<br>
|
||||
* `46 - Sends "Generator damage is at a critical level!" message`
|
||||
* `47 - Sets base NTU level to CRITICAL. MUST use base MapId not base GUID`<br>
|
||||
* `48 - Set to 1 to send base power loss message & turns on red warning lights throughout base. MUST use base MapId not base GUID`<br>
|
||||
* `48 - Set to 1 to send base power loss message & turns on red warning lights throughout base. MUST use base MapId not base GUID`?<br>
|
||||
* `49 - Vehicle texture effects state? (>0 turns on ANT panel glow or ntu silo panel glow + orbs) (bit?)`<br>
|
||||
* `52 - Vehicle particle effects? (>0 turns on orbs going towards ANT. Doesn't affect silo) (bit?)`<br>
|
||||
* `53 - LFS. Value is 1 to flag LFS`<br>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue