Amenity Auto-Repair (#594)

* working proof of concept for ntu request/flow

* ntu is now measured in Float rather than Int; adjusted the type declarations in ChatActor, as it was complaining

* added auto repair information for amenities; modified terminal working example to include periodic repair timer

* crude power state acknowledgement exchange between ntu silo and building, following by building and amenities; better management and control over auto-repair

* separated auto-repair from terminal control; resource silo publishes an appropriate power state message upon formal start-up

* various Damageble objects have been granted auto-repair; doors no longer report no event for intercepting unhandled messages

* documentation and comments; unit tests and integration tests for AmneityAutoRepair, ResourceSilo, and BuildingActor

* merge plus some fixes

* addition checks for auto-repairing cavern equipment and for auto-repairing un-owned equipment
This commit is contained in:
Fate-JH 2020-10-03 00:13:41 -04:00 committed by GitHub
parent e4664d1727
commit e58f1d5987
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 1138 additions and 154 deletions

View file

@ -11,19 +11,19 @@ object NtuCommand {
*
* @param source the nanite container recognized as the sender
*/
final case class Offer(source: NtuContainer) extends Command
final case class Offer(source: NtuContainer, replyTo: ActorRef[Request]) extends Command
/** Message for asking for nanites from the recipient.
*
* @param amount the amount of nanites requested
*/
final case class Request(amount: Int, replyTo: ActorRef[Grant]) extends Command
final case class Request(amount: Float, replyTo: ActorRef[Grant]) extends Command
/** Response for transferring nanites to a recipient.
*
* @param source the nanite container recognized as the sender
* @param amount the nanites transferred in this package
*/
final case class Grant(source: NtuContainer, amount: Int)
final case class Grant(source: NtuContainer, amount: Float) extends Command
}

View file

@ -9,6 +9,7 @@ import net.psforever.objects.avatar.{BattleRank, Certification, CommandRank, Cos
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
import net.psforever.objects.{Default, GlobalDefinitions, 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}
import net.psforever.objects.zones.Zoning
import net.psforever.packet.game.{ChatMsg, DeadState, RequestDestroyMessage, ZonePopulationUpdateMessage}
@ -346,9 +347,9 @@ class ChatActor(
building.Amenities.foreach(amenity =>
amenity.Definition match {
case GlobalDefinitions.resource_silo =>
val r = new scala.util.Random
val silo = amenity.asInstanceOf[ResourceSilo]
val ntu: Int = 900 + r.nextInt(100) - silo.NtuCapacitor
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 _ => ()
@ -363,8 +364,8 @@ class ChatActor(
case (CMT_CAPTUREBASE, _, contents) if gmCommandAllowed =>
val args = contents.split(" ").filter(_ != "")
val (faction, factionPos) = args.zipWithIndex
.map { case (faction, pos) => (faction.toLowerCase, pos) }
val (faction, factionPos): (PlanetSideEmpire.Value, Option[Int]) = args.zipWithIndex
.map { case (factionName, pos) => (factionName.toLowerCase, pos) }
.flatMap {
case ("tr", pos) => Some(PlanetSideEmpire.TR, pos)
case ("nc", pos) => Some(PlanetSideEmpire.NC, pos)
@ -373,11 +374,11 @@ class ChatActor(
case _ => None
}
.headOption match {
case Some((faction, pos)) => (faction, Some(pos))
case Some((isFaction, pos)) => (isFaction, Some(pos))
case None => (session.player.Faction, None)
}
val (buildingsOption, buildingPos) = args.zipWithIndex.flatMap {
val (buildingsOption, buildingPos): (Option[Seq[Building]], Option[Int]) = args.zipWithIndex.flatMap {
case (_, pos) if factionPos.isDefined && factionPos.get == pos => None
case ("all", pos) =>
Some(
@ -391,7 +392,7 @@ class ChatActor(
),
Some(pos)
)
case (name, pos) =>
case (name: String, pos) =>
session.zone.Buildings.find {
case (_, building) => name.equalsIgnoreCase(building.Name) && building.CaptureTerminal.isDefined
} match {
@ -411,11 +412,11 @@ class ChatActor(
case None => (None, None)
}
val (timerOption, timerPos) = args.zipWithIndex.flatMap {
val (timerOption, timerPos): (Option[Int], Option[Int]) = args.zipWithIndex.flatMap {
case (_, pos)
if factionPos.isDefined && factionPos.get == pos || buildingPos.isDefined && buildingPos.get == pos =>
None
case (timer, pos) =>
case (timer: String, pos) =>
try {
val t = timer.toInt // TODO what is the timer format supposed to be?
Some(Some(t), Some(pos))
@ -436,15 +437,15 @@ class ChatActor(
(None | Some(1), Some(0), None, Some(_), None) | (Some(1), Some(0), Some(2), Some(_), Some(_)) |
// [all [<empire>|none]]
(Some(1) | None, Some(0), None, Some(_), None) =>
val buildings = buildingsOption.getOrElse(
val buildings: Seq[Building] = buildingsOption.getOrElse(
session.zone.Buildings
.filter {
case (_, building) =>
building.PlayersInSOI.exists { soiPlayer =>
session.player.CharId == soiPlayer.CharId
}
.values
.filter { building =>
building.PlayersInSOI.exists { soiPlayer =>
session.player.CharId == soiPlayer.CharId
}
}
.map { case (_, building) => building }
.toSeq
)
buildings foreach { building =>
// TODO implement timer

View file

@ -7,7 +7,6 @@ import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware}
import akka.pattern.ask
import akka.util.Timeout
import java.util.concurrent.TimeUnit
import MDCContextAware.Implicits._
import net.psforever.actors.net.MiddlewareActor
import net.psforever.services.ServiceManager.Lookup
import net.psforever.objects.locker.LockerContainer

View file

@ -5,7 +5,9 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer}
import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy}
import akka.{actor => classic}
import net.psforever.actors.commands.NtuCommand
import net.psforever.objects.serverobject.structures.{Building, WarpGate}
import net.psforever.objects.{CommonNtuContainer, NtuContainer}
import net.psforever.objects.serverobject.PlanetSideServerObject
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
@ -40,7 +42,13 @@ object 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 Ntu(command: NtuCommand.Command) extends Command
final case class SuppliedWithNtu() extends Command
final case class NtuDepleted() extends Command
}
class BuildingActor(
@ -147,6 +155,26 @@ class BuildingActor(
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
}
Behaviors.same
case msg @ SuppliedWithNtu() =>
log.trace(s"ntu supply has been restored to ${building.Definition.Name} ${building.Name}")
building.Amenities.foreach { amenity =>
amenity.Actor ! msg
}
Behaviors.same
case Ntu(msg) =>
ntu(msg)
}
@ -154,22 +182,41 @@ class BuildingActor(
def ntu(msg: NtuCommand.Command): Behavior[Command] = {
import NtuCommand._
val ntuBuilding = building match {
case b: WarpGate => b
case _ => return Behaviors.unhandled
}
msg match {
case Offer(source) =>
case Offer(_, _) =>
Behaviors.same
case Request(amount, replyTo) =>
ntuBuilding match {
case warpGate: WarpGate => replyTo ! Grant(warpGate, if (warpGate.Active) amount else 0)
case _ => return Behaviors.unhandled
building match {
case b: WarpGate =>
//warp gates are an infiite source of nanites
replyTo ! Grant(b, if (b.Active) amount else 0)
Behaviors.same
case _ if building.BuildingType == StructureType.Tower || building.Zone.map.cavern =>
//towers and cavern stuff get free repairs
replyTo ! NtuCommand.Grant(new FakeNtuSource(building), amount)
Behaviors.same
case _ =>
//all other facilities require a storage silo for ntu
building.Amenities.find(_.isInstanceOf[NtuContainer]) match {
case Some(ntuContainer) =>
ntuContainer.Actor ! msg //needs to redirect
Behaviors.same
case None =>
replyTo ! NtuCommand.Grant(null, 0)
Behaviors.unhandled
}
}
case _ =>
Behaviors.same
}
Behaviors.same
}
}
class FakeNtuSource(private val building: Building)
extends PlanetSideServerObject
with CommonNtuContainer {
override def NtuCapacitor = Float.MaxValue
override def Faction = building.Faction
override def Zone = building.Zone
override def Definition = null
}

View file

@ -19,7 +19,7 @@ import net.psforever.objects.serverobject.painbox.PainboxDefinition
import net.psforever.objects.serverobject.terminals._
import net.psforever.objects.serverobject.tube.SpawnTubeDefinition
import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition
import net.psforever.objects.serverobject.structures.{BuildingDefinition, WarpGateDefinition}
import net.psforever.objects.serverobject.structures.{AutoRepairStats, BuildingDefinition, WarpGateDefinition}
import net.psforever.objects.serverobject.turret.{FacilityTurretDefinition, TurretUpgrade}
import net.psforever.objects.vehicles.{DestroyedVehicle, InternalTelepadDefinition, SeatArmorRestriction, UtilityType}
import net.psforever.objects.vital.damage.{DamageCalculations, DamageModifiers}
@ -6747,6 +6747,7 @@ object GlobalDefinitions {
spawn_terminal.Name = "spawn_terminal"
spawn_terminal.Damageable = false
spawn_terminal.Repairable = false
spawn_terminal.autoRepair = AutoRepairStats(1, 5000, 200, 1) //TODO amount and drain are default? undefined?
order_terminal.Name = "order_terminal"
order_terminal.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(
@ -6765,6 +6766,7 @@ object GlobalDefinitions {
order_terminal.MaxHealth = 500
order_terminal.Damageable = true
order_terminal.Repairable = true
order_terminal.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f)
order_terminal.RepairIfDestroyed = true
order_terminal.Subtract.Damage1 = 8
@ -6828,6 +6830,7 @@ object GlobalDefinitions {
cert_terminal.MaxHealth = 500
cert_terminal.Damageable = true
cert_terminal.Repairable = true
cert_terminal.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f)
cert_terminal.RepairIfDestroyed = true
cert_terminal.Subtract.Damage1 = 8
@ -6835,6 +6838,7 @@ object GlobalDefinitions {
implant_terminal_mech.MaxHealth = 1500 //TODO 1000; right now, 1000 (mech) + 500 (interface)
implant_terminal_mech.Damageable = true
implant_terminal_mech.Repairable = true
implant_terminal_mech.autoRepair = AutoRepairStats(1, 5000, 2400, 0.5f)
implant_terminal_mech.RepairIfDestroyed = true
implant_terminal_interface.Name = "implant_terminal_interface"
@ -6842,6 +6846,7 @@ object GlobalDefinitions {
implant_terminal_interface.MaxHealth = 500
implant_terminal_interface.Damageable = false //TODO true
implant_terminal_interface.Repairable = true
implant_terminal_interface.autoRepair = AutoRepairStats(1, 5000, 200, 1) //TODO amount and drain are default? undefined?
implant_terminal_interface.RepairIfDestroyed = true
ground_vehicle_terminal.Name = "ground_vehicle_terminal"
@ -6853,6 +6858,7 @@ object GlobalDefinitions {
ground_vehicle_terminal.MaxHealth = 500
ground_vehicle_terminal.Damageable = true
ground_vehicle_terminal.Repairable = true
ground_vehicle_terminal.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f)
ground_vehicle_terminal.RepairIfDestroyed = true
ground_vehicle_terminal.Subtract.Damage1 = 8
@ -6865,6 +6871,7 @@ object GlobalDefinitions {
air_vehicle_terminal.MaxHealth = 500
air_vehicle_terminal.Damageable = true
air_vehicle_terminal.Repairable = true
air_vehicle_terminal.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f)
air_vehicle_terminal.RepairIfDestroyed = true
air_vehicle_terminal.Subtract.Damage1 = 8
@ -6877,6 +6884,7 @@ object GlobalDefinitions {
dropship_vehicle_terminal.MaxHealth = 500
dropship_vehicle_terminal.Damageable = true
dropship_vehicle_terminal.Repairable = true
dropship_vehicle_terminal.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f)
dropship_vehicle_terminal.RepairIfDestroyed = true
dropship_vehicle_terminal.Subtract.Damage1 = 8
@ -6889,6 +6897,7 @@ object GlobalDefinitions {
vehicle_terminal_combined.MaxHealth = 500
vehicle_terminal_combined.Damageable = true
vehicle_terminal_combined.Repairable = true
vehicle_terminal_combined.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f)
vehicle_terminal_combined.RepairIfDestroyed = true
vehicle_terminal_combined.Subtract.Damage1 = 8
@ -6901,6 +6910,7 @@ object GlobalDefinitions {
vanu_air_vehicle_term.MaxHealth = 500
vanu_air_vehicle_term.Damageable = true
vanu_air_vehicle_term.Repairable = true
vanu_air_vehicle_term.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f)
vanu_air_vehicle_term.RepairIfDestroyed = true
vanu_air_vehicle_term.Subtract.Damage1 = 8
@ -6913,6 +6923,7 @@ object GlobalDefinitions {
vanu_vehicle_term.MaxHealth = 500
vanu_vehicle_term.Damageable = true
vanu_vehicle_term.Repairable = true
vanu_vehicle_term.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f)
vanu_vehicle_term.RepairIfDestroyed = true
vanu_vehicle_term.Subtract.Damage1 = 8
@ -6925,6 +6936,7 @@ object GlobalDefinitions {
bfr_terminal.MaxHealth = 500
bfr_terminal.Damageable = true
bfr_terminal.Repairable = true
bfr_terminal.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f)
bfr_terminal.RepairIfDestroyed = true
bfr_terminal.Subtract.Damage1 = 8
@ -6935,6 +6947,7 @@ object GlobalDefinitions {
respawn_tube.Damageable = true
respawn_tube.DamageableByFriendlyFire = false
respawn_tube.Repairable = true
respawn_tube.autoRepair = AutoRepairStats(1, 10000, 2400, 1)
respawn_tube.RepairIfDestroyed = true
respawn_tube.Subtract.Damage1 = 8
@ -6944,6 +6957,7 @@ object GlobalDefinitions {
respawn_tube_sanctuary.Damageable = false //true?
respawn_tube_sanctuary.DamageableByFriendlyFire = false
respawn_tube_sanctuary.Repairable = true
respawn_tube_sanctuary.autoRepair = AutoRepairStats(1, 10000, 2400, 1)
respawn_tube_tower.Name = "respawn_tube_tower"
respawn_tube_tower.Delay = 10
@ -6952,6 +6966,7 @@ object GlobalDefinitions {
respawn_tube_tower.Damageable = true
respawn_tube_tower.DamageableByFriendlyFire = false
respawn_tube_tower.Repairable = true
respawn_tube_tower.autoRepair = AutoRepairStats(1, 10000, 2400, 1)
respawn_tube_tower.RepairIfDestroyed = true
respawn_tube_tower.Subtract.Damage1 = 8
@ -6969,6 +6984,7 @@ object GlobalDefinitions {
medical_terminal.MaxHealth = 500
medical_terminal.Damageable = true
medical_terminal.Repairable = true
medical_terminal.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f)
medical_terminal.RepairIfDestroyed = true
adv_med_terminal.Name = "adv_med_terminal"
@ -6980,6 +6996,7 @@ object GlobalDefinitions {
adv_med_terminal.MaxHealth = 750
adv_med_terminal.Damageable = true
adv_med_terminal.Repairable = true
adv_med_terminal.autoRepair = AutoRepairStats(1, 5000, 2400, 0.5f)
adv_med_terminal.RepairIfDestroyed = true
crystals_health_a.Name = "crystals_health_a"
@ -7007,6 +7024,7 @@ object GlobalDefinitions {
portable_med_terminal.MaxHealth = 500
portable_med_terminal.Damageable = false //TODO actually true
portable_med_terminal.Repairable = false
portable_med_terminal.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f)
pad_landing_frame.Name = "pad_landing_frame"
pad_landing_frame.Interval = 1000
@ -7120,6 +7138,7 @@ object GlobalDefinitions {
manned_turret.Damageable = true
manned_turret.DamageDisablesAt = 0
manned_turret.Repairable = true
manned_turret.autoRepair = AutoRepairStats(1, 10000, 1600, 0.5f)
manned_turret.RepairIfDestroyed = true
manned_turret.Weapons += 1 -> new mutable.HashMap()
manned_turret.Weapons(1) += TurretUpgrade.None -> phalanx_sgl_hevgatcan
@ -7134,6 +7153,7 @@ object GlobalDefinitions {
vanu_sentry_turret.Damageable = true
vanu_sentry_turret.DamageDisablesAt = 0
vanu_sentry_turret.Repairable = true
vanu_sentry_turret.autoRepair = AutoRepairStats(3, 10000, 1000, 0.5f)
vanu_sentry_turret.RepairIfDestroyed = true
vanu_sentry_turret.Weapons += 1 -> new mutable.HashMap()
vanu_sentry_turret.Weapons(1) += TurretUpgrade.None -> vanu_sentry_turret_weapon
@ -7175,9 +7195,9 @@ object GlobalDefinitions {
generator.Damageable = true
generator.DamageableByFriendlyFire = false
generator.Repairable = true
generator.autoRepair = AutoRepairStats(1, 5000, 875, 1)
generator.RepairDistance = 13.5f
generator.RepairIfDestroyed = true
generator.Subtract.Damage1 = 9
}
}

View file

@ -23,7 +23,7 @@ object Ntu {
* @param max the amount of nanites required to not make further requests;
* if 0, the `sender` is full and the message is for clean up operations
*/
final case class Request(min: Int, max: Int)
final case class Request(min: Float, max: Float)
/**
* Message for transferring nanites to a recipient.
@ -31,23 +31,23 @@ object Ntu {
* @param src the nanite container recognized as the sender
* @param amount the nanites transferred in this package
*/
final case class Grant(src: NtuContainer, amount: Int)
final case class Grant(src: NtuContainer, amount: Float)
}
trait NtuContainer extends TransferContainer {
def NtuCapacitor: Int
def NtuCapacitor: Float
def NtuCapacitor_=(value: Int): Int
def NtuCapacitor_=(value: Float): Float
def Definition: NtuContainerDefinition
}
trait CommonNtuContainer extends NtuContainer {
private var ntuCapacitor: Int = 0
private var ntuCapacitor: Float = 0
def NtuCapacitor: Int = ntuCapacitor
def NtuCapacitor: Float = ntuCapacitor
def NtuCapacitor_=(value: Int): Int = {
def NtuCapacitor_=(value: Float): Float = {
ntuCapacitor = scala.math.max(0, scala.math.min(value, Definition.MaxNtuCapacitor))
NtuCapacitor
}
@ -56,11 +56,11 @@ trait CommonNtuContainer extends NtuContainer {
}
trait NtuContainerDefinition {
private var maxNtuCapacitor: Int = 0
private var maxNtuCapacitor: Float = 0
def MaxNtuCapacitor: Int = maxNtuCapacitor
def MaxNtuCapacitor: Float = maxNtuCapacitor
def MaxNtuCapacitor_=(max: Int): Int = {
def MaxNtuCapacitor_=(max: Float): Float = {
maxNtuCapacitor = max
MaxNtuCapacitor
}
@ -76,7 +76,12 @@ trait NtuStorageBehavior extends Actor {
case Ntu.Request(min, max) => HandleNtuRequest(sender(), min, max)
case NtuCommand.Request(amount, replyTo) =>
import akka.actor.typed.scaladsl.adapter.TypedActorRefOps
HandleNtuRequest(new TypedActorRefOps(replyTo).toClassic, amount, amount+1)
case Ntu.Grant(src, amount) => HandleNtuGrant(sender(), src, amount)
case NtuCommand.Grant(src, amount) => HandleNtuGrant(sender(), src, amount)
}
@ -84,7 +89,7 @@ trait NtuStorageBehavior extends Actor {
def StopNtuBehavior(sender: ActorRef): Unit
def HandleNtuRequest(sender: ActorRef, min: Int, max: Int): Unit
def HandleNtuRequest(sender: ActorRef, min: Float, max: Float): Unit
def HandleNtuGrant(sender: ActorRef, src: NtuContainer, amount: Int): Unit
def HandleNtuGrant(sender: ActorRef, src: NtuContainer, amount: Float): Unit
}

View file

@ -231,9 +231,9 @@ object Vehicles {
// Forcefully dismount any cargo
target.CargoHolds.values.foreach(cargoHold => {
cargoHold.Occupant match {
case Some(cargo: Vehicle) => {
case Some(cargo: Vehicle) =>
cargo.Seats(0).Occupant match {
case Some(cargoDriver: Player) =>
case Some(_: Player) =>
CargoBehavior.HandleVehicleCargoDismount(
target.Zone,
cargo.GUID,
@ -243,8 +243,7 @@ object Vehicles {
)
case None =>
log.error("FinishHackingVehicle: vehicle in cargo hold missing driver")
CargoBehavior.HandleVehicleCargoDismount(cargo.GUID, cargo, target.GUID, target, false, false, true)
}
CargoBehavior.HandleVehicleCargoDismount(cargo.GUID, cargo, target.GUID, target, bailed = false, requestedByPassenger = false, kicked = true)
}
case None => ;
}
@ -326,7 +325,7 @@ object Vehicles {
Vector3.DistanceSquared(obj.Position.xy, target.Position.xy) < soiRadius * soiRadius
} =>
Some(target.asInstanceOf[NtuContainer])
case None =>
case _ =>
None
}).orElse {
val position = obj.Position.xy

View file

@ -16,7 +16,6 @@ class DoorControl(door: Door) extends Actor with FactionAffinityBehavior.Check {
case Door.Use(player, msg) =>
sender() ! Door.DoorMessage(player, msg, door.Use(player, msg))
case _ =>
sender() ! Door.NoEvent()
case _ => ;
}
}

View file

@ -2,12 +2,13 @@
package net.psforever.objects.serverobject.generator
import akka.actor.Actor
import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.{Player, Tool}
import net.psforever.objects.ballistics._
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.serverobject.damage.DamageableEntity
import net.psforever.objects.serverobject.repair.{Repairable, RepairableEntity}
import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, Repairable, RepairableEntity}
import net.psforever.objects.serverobject.structures.Building
import net.psforever.objects.vital.DamageFromExplosion
import net.psforever.packet.game.TriggerEffectMessage
@ -27,10 +28,12 @@ class GeneratorControl(gen: Generator)
extends Actor
with FactionAffinityBehavior.Check
with DamageableEntity
with RepairableEntity {
def FactionObject = gen
def DamageableObject = gen
def RepairableObject = gen
with RepairableEntity
with AmenityAutoRepair {
def FactionObject = gen
def DamageableObject = gen
def RepairableObject = gen
def AutoRepairObject = gen
var imminentExplosion: Boolean = false
var alarmCooldownPeriod: Boolean = false
@ -38,6 +41,7 @@ class GeneratorControl(gen: Generator)
checkBehavior
.orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser)
.orElse(autoRepairBehavior)
.orElse {
case GeneratorControl.GeneratorExplodes() => //TODO this only works with projectiles right now!
val zone = gen.Zone
@ -89,6 +93,7 @@ class GeneratorControl(gen: Generator)
}
override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Any): Unit = {
tryAutoRepair()
super.DamageAwareness(target, cause, amount)
val damageTo = amount match {
case a: Int => a
@ -98,6 +103,7 @@ class GeneratorControl(gen: Generator)
}
override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = {
tryAutoRepair()
if (!target.Destroyed) {
target.Health = 1 //temporary
imminentExplosion = true
@ -106,6 +112,14 @@ class GeneratorControl(gen: Generator)
}
}
override def PerformRepairs(target : Target, amount : Int) : Int = {
val newHealth = super.PerformRepairs(target, amount)
if(newHealth == target.Definition.MaxHealth) {
stopAutoRepair()
}
newHealth
}
override def Restoration(obj: Repairable.Target): Unit = {
super.Restoration(obj)
gen.Condition = PlanetSideGeneratorState.Normal
@ -137,7 +151,7 @@ object GeneratorControl {
*/
private def UpdateOwner(obj: Generator): Unit = {
obj.Owner match {
case b: Building => b.Actor ! Building.AmenityStateChange(obj)
case b: Building => b.Actor ! BuildingActor.AmenityStateChange(obj)
case _ => ;
}
}

View file

@ -10,7 +10,7 @@ import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
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.RepairableEntity
import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableEntity}
import net.psforever.objects.serverobject.structures.Building
/**
@ -24,12 +24,14 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
with MountableBehavior.Dismount
with HackableBehavior.GenericHackable
with DamageableEntity
with RepairableEntity {
with RepairableEntity
with AmenityAutoRepair {
def MountableObject = mech
def HackableObject = mech
def FactionObject = mech
def DamageableObject = mech
def RepairableObject = mech
def AutoRepairObject = mech
def receive: Receive =
checkBehavior
@ -38,6 +40,7 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
.orElse(hackableBehavior)
.orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser)
.orElse(autoRepairBehavior)
.orElse {
case CommonMessages.Use(player, Some(item: SimpleItem))
if item.Definition == GlobalDefinitions.remote_electronics_kit =>
@ -73,6 +76,7 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
}
override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Any): Unit = {
tryAutoRepair()
super.DamageAwareness(target, cause, amount)
val damageTo = amount match {
case a: Int => a
@ -86,4 +90,12 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
DamageableMountable.DestructionAwareness(DamageableObject, cause)
target.ClearHistory()
}
override def PerformRepairs(target : Damageable.Target, amount : Int) : Int = {
val newHealth = super.PerformRepairs(target, amount)
if(newHealth == target.Definition.MaxHealth) {
stopAutoRepair()
}
newHealth
}
}

View file

@ -0,0 +1,197 @@
//Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.repair
import akka.actor.{Actor, ActorRef, Cancellable}
import akka.actor.typed.{ActorRef => TypedActorRef}
import akka.actor.typed.scaladsl.adapter.ClassicActorRefOps
import net.psforever.actors.commands.NtuCommand
import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.{Default, NtuContainer, NtuStorageBehavior}
import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.serverobject.structures.{Amenity, AutoRepairStats, Building}
import scala.concurrent.duration._
/**
* A mixin for handling the automatic repair functionality of facility amenities.
* Auto-repair is facilitated primarily as a function of nanite transfer unit (NTU) provisions
* and is prompted by the amenity itself sustaining damage
* and being, at some level, capable of being repaired.
* In major facilities - technology plants, bio labs, etc. -
* this NTU is obtained from that facility's nanite resource silo.
* The amenity that wishes to be repaired asks the facility for nanite.
* The reply comes from the NTU source, or from the facility again.
* In exchange for the automatic repair, the silo looses some of its NTU stockpile
* and that entails all of the consequences of losing all of the NTU for the base.
* In smaller field tower bases, the lack of resource silo should not hinder operations
* as auto-repair is still carried out nonetheless.
* The consequences of losing NTU do not apply in this case;
* the field tower is considered to have unlimited, unshared NTU.
*/
trait AmenityAutoRepair
extends NtuStorageBehavior {
_: Damageable with RepairableEntity with Actor =>
/** a dedicated reference to self that facilitates in receiving `NtuCommand.Grant` messages for auto-repair operation */
private lazy val ntuGrantActorRef: TypedActorRef[NtuCommand.Grant] =
new ClassicActorRefOps(self).toTyped[NtuCommand.Grant]
/** the function that initializes auto-repair operations, if those operations have not yet started */
private var autoRepairStartFunc: ()=>Unit = startAutoRepairIfStopped
/** the timer for requests for auto-repair-actionable resource deposits (NTU) */
private var autoRepairTimer: Cancellable = Default.Cancellable
def AutoRepairObject: Amenity
final val autoRepairBehavior: Receive = storageBehavior.orElse {
case BuildingActor.SuppliedWithNtu() =>
withNtuSupplyCallback()
case BuildingActor.NtuDepleted() =>
noNtuSupplyCallback()
}
//nothing special
def HandleNtuOffer(sender: ActorRef, src: NtuContainer): Unit = { }
/**
* Stop the auto-repair timer.
*/
def StopNtuBehavior(sender : ActorRef) : Unit = {
autoRepairTimer.cancel()
}
//nothing special
def HandleNtuRequest(sender: ActorRef, min: Float, max: Float): Unit = { }
/**
* When reports of an NTU provision is returned to the requesting amenity,
* the amount of repair that can be performed is obtained
* and, if the amenity still requires those repairs,
* auto-repair executes a single tick.
* @see `RepairableAmenity`
*/
def HandleNtuGrant(sender : ActorRef, src : NtuContainer, amount : Float) : Unit = {
val obj = AutoRepairObject
obj.Definition.autoRepair match {
case Some(repair : AutoRepairStats) if obj.Health < obj.Definition.MaxHealth =>
PerformRepairs(obj, repair.amount)
case _ =>
StopNtuBehavior(sender)
}
}
/**
* Confirm that a provision of NTU to the potential requesting amenity is possible.
* Attempt to start auto-repair operations.
*/
def withNtuSupplyCallback(): Unit = {
startAutoRepairFunctionality()
}
/**
* No (further) provisions of NTU to the potential requesting amenity will be forthcoming.
* Cancel any attempts at auto-repair.
*/
def noNtuSupplyCallback(): Unit = {
stopAutoRepairFunctionality()
}
/**
* Attempt to start auto-repair operation if possible,
* restarting an existing timed operation if necessary.
* Set a function that will attempt auto-repair operations under specific trigger-able conditions (damage).
*/
private def startAutoRepairFunctionality(): Unit = {
retimeAutoRepair()
autoRepairStartFunc = startAutoRepairIfStopped
}
/**
* Cancel any attempts at auto-repair
* by stopping any currently processing repair timer
* and ensuring that otherwise trigger-able conditions (damages) do not instigate auto-repair operations.
* @see `stopAutoRepair`
*/
private def stopAutoRepairFunctionality(): Unit = {
autoRepairTimer.cancel()
autoRepairStartFunc = ()=>{}
}
/**
* Attempt to start auto-repair operation
* only if no operation is currently being processed.
*/
private def startAutoRepairIfStopped(): Unit = {
if(autoRepairTimer.isCancelled) {
retimeAutoRepair()
}
}
/**
* Attempt to start auto-repair operation
* only if no operation is currently being processed.
* @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 = {
val before = autoRepairTimer.isCancelled
autoRepairStartFunc()
!(before || autoRepairTimer.isCancelled)
}
/**
* Cancel any attempts at auto-repair
* by stopping any currently processing repair timer
* The operation can be resumed.
* @see `stopAutoRepairFunctionality`
*/
final def stopAutoRepair(): Unit = {
autoRepairTimer.cancel()
}
/**
* As long as setup information regarding the auto-repair process can be discovered in the amenity's definition
* and the amenity actually requires to be performed,
* perform the setup for the auto-repair operation.
*/
private def retimeAutoRepair(): Unit = {
val obj = AutoRepairObject
obj.Definition.autoRepair match {
case Some(AutoRepairStats(_, start, interval, drain)) if obj.Health < obj.Definition.MaxHealth =>
retimeAutoRepair(start, interval, drain)
case _ => ;
}
}
/**
* As long as setup information regarding the auto-repair process can be provided,
* perform the setup for the auto-repair operation.
* @see `BuildingActor.Ntu`
* @see `NtuCommand.Request`
* @see `scheduleWithFixedDelay`
* @param initialDelay the delay before the first message
* @param delay the delay between subsequent messages, after the first
* @param drain the amount of NTU being levied as a cost for auto-repair operation
* (the responding entity determines how to satisfy the cost)
*/
private def retimeAutoRepair(initialDelay: Long, delay: Long, drain: Float): Unit = {
import scala.concurrent.ExecutionContext.Implicits.global
autoRepairTimer.cancel()
autoRepairTimer = if(AutoRepairObject.Owner == Building.NoBuilding) {
//without an owner, auto-repair freely
context.system.scheduler.scheduleWithFixedDelay(
initialDelay milliseconds,
delay milliseconds,
self,
NtuCommand.Grant(null, drain)
)
} else {
//ask
context.system.scheduler.scheduleWithFixedDelay(
initialDelay milliseconds,
delay milliseconds,
AutoRepairObject.Owner.Actor,
BuildingActor.Ntu(NtuCommand.Request(drain, ntuGrantActorRef))
)
}
}
}

View file

@ -78,32 +78,21 @@ trait RepairableEntity extends Repairable {
*/
protected def PerformRepairs(target: Repairable.Target, player: Player, item: Tool): Unit = {
val definition = target.Definition
val zone = target.Zone
val events = zone.AvatarEvents
val events = target.Zone.AvatarEvents
val name = player.Name
val tguid = target.GUID
val originalHealth = target.Health
val updatedHealth =
if (!(player.isMoving(1f) || target.isMoving(1f))) { //only allow stationary repairs within margin of error
val newHealth = target.Health = originalHealth + Repairable.Quality + RepairValue(item) + definition.RepairMod
val zoneId = zone.id
val repairValue = Repairable.Quality + RepairValue(item) + target.Definition.RepairMod
val magazine = item.Discharge()
events ! AvatarServiceMessage(
name,
player.Name,
AvatarAction.SendResponse(
Service.defaultPlayerGUID,
InventoryStateMessage(item.AmmoSlot.Box.GUID, item.GUID, magazine.toLong)
)
)
if (target.Destroyed) {
if (newHealth >= definition.RepairRestoresAt) {
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 0, newHealth))
Restoration(target)
}
} else {
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 0, newHealth))
}
newHealth
PerformRepairs(target, repairValue)
} else {
originalHealth
}
@ -112,11 +101,28 @@ trait RepairableEntity extends Repairable {
name,
AvatarAction.SendResponse(
Service.defaultPlayerGUID,
RepairMessage(tguid, updatedHealth * 100 / definition.MaxHealth)
RepairMessage(target.GUID, updatedHealth * 100 / definition.MaxHealth)
)
)
}
protected def PerformRepairs(target: Repairable.Target, amount: Int): Int = {
val zone = target.Zone
val zoneId = zone.id
val events = zone.AvatarEvents
val tguid = target.GUID
val newHealth = target.Health = target.Health + amount
if (target.Destroyed) {
if (newHealth >= target.Definition.RepairRestoresAt) {
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 0, newHealth))
Restoration(target)
}
} else {
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 0, newHealth))
}
newHealth
}
/* random object repair modifier */
override def RepairValue(item: Tool): Int = item.FireMode.Add.Damage1
}

View file

@ -16,7 +16,7 @@ class ResourceSilo extends Amenity with CommonNtuContainer {
// For the NTU display bar
private var capacitorDisplay: Long = 0
def MaxNtuCapacitor : Int = Definition.MaxNtuCapacitor
def MaxNtuCapacitor : Float = Definition.MaxNtuCapacitor
def LowNtuWarningOn: Boolean = lowNtuWarningOn
def LowNtuWarningOn_=(enabled: Boolean): Boolean = {
@ -24,7 +24,7 @@ class ResourceSilo extends Amenity with CommonNtuContainer {
LowNtuWarningOn
}
def CapacitorDisplay : Long = scala.math.ceil((NtuCapacitor.toFloat / MaxNtuCapacitor.toFloat) * 10).toInt
def CapacitorDisplay : Long = scala.math.ceil((NtuCapacitor / MaxNtuCapacitor) * 10).toInt
def Definition: ResourceSiloDefinition = GlobalDefinitions.resource_silo
@ -34,7 +34,7 @@ class ResourceSilo extends Amenity with CommonNtuContainer {
}
object ResourceSilo {
final case class UpdateChargeLevel(amount: Int)
final case class UpdateChargeLevel(amount: Float)
final case class LowNtuWarning(enabled: Boolean)
sealed trait Exchange
final case class ChargeEvent() extends Exchange

View file

@ -3,7 +3,7 @@ package net.psforever.objects.serverobject.resourcesilo
import akka.actor.{Actor, ActorRef}
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.actors.zone.{BuildingActor, ZoneActor}
import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.transfer.TransferBehavior
import net.psforever.objects.serverobject.structures.Building
@ -28,12 +28,19 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
def FactionObject: FactionAffinity = resourceSilo
private[this] val log = org.log4s.getLogger
var panelAnimationFunc: Int => Unit = PanelAnimation
var panelAnimationFunc: Float => Unit = PanelAnimation
def receive: Receive = {
case "startup" =>
// todo: This is just a temporary solution to drain NTU over time. When base object destruction is properly implemented NTU should be deducted when base objects repair themselves
// context.system.scheduler.schedule(5 second, 5 second, self, ResourceSilo.UpdateChargeLevel(-1))
resourceSilo.Owner match {
case building: Building =>
building.Actor ! (if (resourceSilo.NtuCapacitor <= 0f ) {
BuildingActor.NtuDepleted()
} else {
BuildingActor.SuppliedWithNtu()
})
case _ => ;
}
context.become(Processing)
case _ => ;
@ -59,7 +66,7 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
case ResourceSilo.LowNtuWarning(enabled: Boolean) =>
LowNtuWarning(enabled)
case ResourceSilo.UpdateChargeLevel(amount: Int) =>
case ResourceSilo.UpdateChargeLevel(amount: Float) =>
UpdateChargeLevel(amount)
case _ => ;
@ -76,7 +83,7 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
)
}
def UpdateChargeLevel(amount: Int): Unit = {
def UpdateChargeLevel(amount: Float): Unit = {
val siloChargeBeforeChange = resourceSilo.NtuCapacitor
val siloDisplayBeforeChange = resourceSilo.CapacitorDisplay
val building = resourceSilo.Owner.asInstanceOf[Building]
@ -93,14 +100,13 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
log.trace(
s"Silo ${resourceSilo.GUID} NTU bar level has changed from $siloDisplayBeforeChange to ${resourceSilo.CapacitorDisplay}"
)
resourceSilo.Owner.Actor ! BuildingActor.MapUpdate()
zone.AvatarEvents ! AvatarServiceMessage(
zone.id,
AvatarAction.PlanetsideAttribute(resourceSilo.GUID, 45, resourceSilo.CapacitorDisplay)
)
building.Actor ! BuildingActor.MapUpdate()
}
val ntuIsLow = resourceSilo.NtuCapacitor.toFloat / resourceSilo.Definition.MaxNtuCapacitor.toFloat < 0.2f
val ntuIsLow = resourceSilo.NtuCapacitor / resourceSilo.Definition.MaxNtuCapacitor < 0.2f
if (resourceSilo.LowNtuWarningOn && !ntuIsLow) {
LowNtuWarning(enabled = false)
} else if (!resourceSilo.LowNtuWarningOn && ntuIsLow) {
@ -109,6 +115,8 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
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.
@ -117,7 +125,8 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
zone.id,
AvatarAction.PlanetsideAttribute(building.GUID, 48, 0)
)
building.Zone.actor ! ZoneActor.ZoneMapUpdate()
building.Actor ! BuildingActor.SuppliedWithNtu()
building.Actor ! BuildingActor.AmenityStateChange(resourceSilo)
}
}
@ -148,7 +157,7 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
* @param min a minimum amount of nanites requested;
* @param max the amount of nanites required to not make further requests;
*/
def HandleNtuRequest(sender: ActorRef, min: Int, max: Int): Unit = {
def HandleNtuRequest(sender: ActorRef, min: Float, max: Float): Unit = {
val originalAmount = resourceSilo.NtuCapacitor
UpdateChargeLevel(-min)
sender ! Ntu.Grant(resourceSilo, originalAmount - resourceSilo.NtuCapacitor)
@ -157,7 +166,7 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
/**
* Accept nanites into the silo capacitor and set the animation state.
*/
def HandleNtuGrant(sender: ActorRef, src: NtuContainer, amount: Int): Unit = {
def HandleNtuGrant(sender: ActorRef, src: NtuContainer, amount: Float): Unit = {
if (amount != 0) {
val originalAmount = resourceSilo.NtuCapacitor
UpdateChargeLevel(amount)
@ -174,7 +183,7 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
* @param trigger if positive, activate the animation;
* if negative or zero, disable the animation
*/
def PanelAnimation(trigger: Int): Unit = {
def PanelAnimation(trigger: Float): Unit = {
val zone = resourceSilo.Zone
zone.VehicleEvents ! VehicleServiceMessage(
zone.id,
@ -185,5 +194,5 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
/**
* Do nothing this turn.
*/
def SkipPanelAnimation(trigger: Int): Unit = {}
def SkipPanelAnimation(trigger: Float): Unit = {}
}

View file

@ -6,6 +6,8 @@ import net.psforever.objects.vital.damage.DamageCalculations
import net.psforever.objects.vital.{DamageResistanceModel, StandardAmenityResistance, StandardResolutions, VitalityDefinition}
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
final case class AutoRepairStats(amount: Int, start: Long, repeat: Long, drain: Float)
abstract class AmenityDefinition(objectId: Int)
extends ObjectDefinition(objectId)
with ResistanceProfileMutators
@ -15,4 +17,13 @@ abstract class AmenityDefinition(objectId: Int)
DamageUsing = DamageCalculations.AgainstVehicle
ResistUsing = StandardAmenityResistance
Model = StandardResolutions.Amenities
var autoRepair: Option[AutoRepairStats] = None
def autoRepair_=(auto: AutoRepairStats): Option[AutoRepairStats] = {
autoRepair = Some(auto)
autoRepair
}
def hasAutoRepair: Boolean = autoRepair.nonEmpty
}

View file

@ -330,9 +330,7 @@ object Building {
)(name: String, guid: Int, id: Int, zone: Zone, context: ActorContext): Building = {
val obj = new Building(name, guid, id, zone, buildingType, buildingDefinition)
obj.Position = location
context.spawn(BuildingActor(zone, obj), s"$id-$buildingType-building").toClassic
obj.Actor = context.spawn(BuildingActor(zone, obj), s"$id-$buildingType-building").toClassic
obj
}
final case class AmenityStateChange(obj: Amenity)
}

View file

@ -150,9 +150,9 @@ class WarpGate(name: String, building_guid: Int, map_id: Int, zone: Zone, buildi
def Owner: PlanetSideServerObject = this
def NtuCapacitor: Int = Definition.MaxNtuCapacitor
def NtuCapacitor: Float = Definition.MaxNtuCapacitor
def NtuCapacitor_=(value: Int): Int = NtuCapacitor
def NtuCapacitor_=(value: Float): Float = NtuCapacitor
override def Definition: WarpGateDefinition = buildingDefinition
}

View file

@ -96,8 +96,7 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit)
case ProximityUnit.Action(_, _) =>
//reserved
case msg =>
log.warn(s"unexpected message $msg")
case _ =>
}
def Use(target: PlanetSideGameObject, zone: String, callback: ActorRef): Unit = {

View file

@ -2,12 +2,14 @@
package net.psforever.objects.serverobject.terminals
import akka.actor.{Actor, ActorRef}
import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.{GlobalDefinitions, SimpleItem}
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
import net.psforever.objects.serverobject.damage.DamageableAmenity
import net.psforever.objects.serverobject.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.RepairableAmenity
import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableAmenity}
import net.psforever.objects.serverobject.structures.Building
/**
@ -19,17 +21,20 @@ class TerminalControl(term: Terminal)
with FactionAffinityBehavior.Check
with HackableBehavior.GenericHackable
with DamageableAmenity
with RepairableAmenity {
with RepairableAmenity
with AmenityAutoRepair {
def FactionObject = term
def HackableObject = term
def DamageableObject = term
def RepairableObject = term
def AutoRepairObject = term
def receive: Receive =
checkBehavior
.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)))
@ -46,9 +51,28 @@ class TerminalControl(term: Terminal)
)
case _ => ;
}
case _ => ;
}
override protected def DamageAwareness(target : Target, cause : ResolvedProjectile, amount : Any) : Unit = {
tryAutoRepair()
super.DamageAwareness(target, cause, amount)
}
override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile) : Unit = {
tryAutoRepair()
super.DestructionAwareness(target, cause)
}
override def PerformRepairs(target : Target, amount : Int) : Int = {
val newHealth = super.PerformRepairs(target, amount)
if(newHealth == target.Definition.MaxHealth) {
stopAutoRepair()
}
newHealth
}
override def toString: String = term.Definition.Name
}

View file

@ -2,11 +2,12 @@
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.{Repairable, RepairableAmenity}
import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, Repairable, RepairableAmenity}
import net.psforever.objects.serverobject.structures.Building
/**
@ -17,31 +18,48 @@ class SpawnTubeControl(tube: SpawnTube)
extends Actor
with FactionAffinityBehavior.Check
with DamageableAmenity
with RepairableAmenity {
with RepairableAmenity
with AmenityAutoRepair {
def FactionObject = tube
def DamageableObject = tube
def RepairableObject = tube
def AutoRepairObject = tube
def receive: Receive =
checkBehavior
.orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser)
.orElse(autoRepairBehavior)
.orElse {
case _ => ;
}
override protected def DamageAwareness(target : Target, cause : ResolvedProjectile, amount : Any) : Unit = {
tryAutoRepair()
super.DamageAwareness(target, cause, amount)
}
override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = {
tryAutoRepair()
super.DestructionAwareness(target, cause)
tube.Owner match {
case b: Building => b.Actor ! Building.AmenityStateChange(tube)
case b: Building => b.Actor ! BuildingActor.AmenityStateChange(tube)
case _ => ;
}
}
override def PerformRepairs(target : Target, amount : Int) : Int = {
val newHealth = super.PerformRepairs(target, amount)
if(newHealth == target.Definition.MaxHealth) {
stopAutoRepair()
}
newHealth
}
override def Restoration(obj: Repairable.Target): Unit = {
super.Restoration(obj)
tube.Owner match {
case b: Building => b.Actor ! Building.AmenityStateChange(tube)
case b: Building => b.Actor ! BuildingActor.AmenityStateChange(tube)
case _ => ;
}
}

View file

@ -8,10 +8,9 @@ import net.psforever.objects.equipment.{Ammo, JammableMountedWeapons}
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.mount.MountableBehavior
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
import net.psforever.objects.serverobject.damage.DamageableWeaponTurret
import net.psforever.objects.serverobject.damage.{Damageable, DamageableWeaponTurret}
import net.psforever.objects.serverobject.hackable.GenericHackables
import net.psforever.objects.serverobject.repair.Repairable.Target
import net.psforever.objects.serverobject.repair.RepairableWeaponTurret
import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableWeaponTurret}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
@ -34,12 +33,14 @@ class FacilityTurretControl(turret: FacilityTurret)
with MountableBehavior.Dismount
with DamageableWeaponTurret
with RepairableWeaponTurret
with AmenityAutoRepair
with JammableMountedWeapons {
def FactionObject = turret
def MountableObject = turret
def JammableObject = turret
def DamageableObject = turret
def RepairableObject = turret
def AutoRepairObject = turret
// Used for timing ammo recharge for vanu turrets in caves
var weaponAmmoRechargeTimer = Default.Cancellable
@ -47,6 +48,7 @@ class FacilityTurretControl(turret: FacilityTurret)
override def postStop(): Unit = {
super.postStop()
damageableWeaponTurretPostStop()
stopAutoRepair()
}
def receive: Receive =
@ -56,6 +58,7 @@ class FacilityTurretControl(turret: FacilityTurret)
.orElse(dismountBehavior)
.orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser)
.orElse(autoRepairBehavior)
.orElse {
case CommonMessages.Use(player, Some((item: Tool, upgradeValue: Int)))
if player.Faction == turret.Faction &&
@ -110,7 +113,13 @@ class FacilityTurretControl(turret: FacilityTurret)
case _ => ;
}
override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = {
override protected def DamageAwareness(target : Damageable.Target, cause : ResolvedProjectile, amount : Any) : Unit = {
tryAutoRepair()
super.DamageAwareness(target, cause, amount)
}
override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = {
tryAutoRepair()
super.DestructionAwareness(target, cause)
val zone = target.Zone
val zoneId = zone.id
@ -120,7 +129,15 @@ class FacilityTurretControl(turret: FacilityTurret)
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 51, 1))
}
override def Restoration(obj: Target): Unit = {
override def PerformRepairs(target : Damageable.Target, amount : Int) : Int = {
val newHealth = super.PerformRepairs(target, amount)
if(newHealth == target.Definition.MaxHealth) {
stopAutoRepair()
}
newHealth
}
override def Restoration(obj: Damageable.Target): Unit = {
super.Restoration(obj)
val zone = turret.Zone
val zoneId = zone.id

View file

@ -68,7 +68,7 @@ trait AntTransferBehavior extends TransferBehavior with NtuStorageBehavior {
//ANTs would charge from 0-100% in roughly 75s on live (https://www.youtube.com/watch?v=veOWToR2nSk&feature=youtu.be&t=1194)
val max = obj.Definition.MaxNtuCapacitor - obj.NtuCapacitor
target.Actor ! BuildingActor.Ntu(
NtuCommand.Request(scala.math.min(obj.Definition.MaxNtuCapacitor / 75, max), context.self)
NtuCommand.Request(scala.math.min(obj.Definition.MaxNtuCapacitor / 75f, max), context.self)
)
case _ =>
}
@ -86,7 +86,7 @@ trait AntTransferBehavior extends TransferBehavior with NtuStorageBehavior {
}
}
def ReceiveAndDepositUntilFull(vehicle: Vehicle, amount: Int): Boolean = {
def ReceiveAndDepositUntilFull(vehicle: Vehicle, amount: Float): Boolean = {
val isNotFull = (vehicle.NtuCapacitor += amount) < vehicle.Definition.MaxNtuCapacitor
UpdateNtuUI(vehicle)
isNotFull
@ -128,7 +128,7 @@ trait AntTransferBehavior extends TransferBehavior with NtuStorageBehavior {
ActivatePanelsForChargingEvent(ChargeTransferObject)
}
def WithdrawAndTransmit(vehicle: Vehicle, maxRequested: Int): Any = {
def WithdrawAndTransmit(vehicle: Vehicle, maxRequested: Float): Any = {
val chargeable = ChargeTransferObject
var chargeToDeposit = Math.min(Math.min(chargeable.NtuCapacitor, 100), maxRequested)
chargeable.NtuCapacitor -= chargeToDeposit
@ -176,7 +176,7 @@ trait AntTransferBehavior extends TransferBehavior with NtuStorageBehavior {
def HandleNtuOffer(sender: ActorRef, src: NtuContainer): Unit = {}
def HandleNtuRequest(sender: ActorRef, min: Int, max: Int): Unit = {
def HandleNtuRequest(sender: ActorRef, min: Float, max: Float): Unit = {
if (transferEvent == TransferBehavior.Event.Discharging) {
val chargeable = ChargeTransferObject
val chargeToDeposit = if (min == 0) {
@ -197,7 +197,7 @@ trait AntTransferBehavior extends TransferBehavior with NtuStorageBehavior {
}
}
def HandleNtuGrant(sender: ActorRef, src: NtuContainer, amount: Int): Unit = {
def HandleNtuGrant(sender: ActorRef, src: NtuContainer, amount: Float): Unit = {
if (transferEvent == TransferBehavior.Event.Charging) {
val obj = ChargeTransferObject
if (ReceiveAndDepositUntilFull(obj, amount)) {

View file

@ -0,0 +1,164 @@
// Copyright (c) 2020 PSForever
package objects
import akka.actor.Props
import akka.testkit.TestProbe
import base.FreedContextActorTest
import net.psforever.objects.avatar.Avatar
import net.psforever.objects.ballistics.{Projectile, ProjectileResolution, ResolvedProjectile, SourceEntry}
import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.serverobject.resourcesilo.{ResourceSilo, ResourceSiloControl}
import net.psforever.objects.serverobject.structures.{AutoRepairStats, Building, StructureType}
import net.psforever.objects.serverobject.terminals.{OrderTerminalDefinition, Terminal, TerminalControl}
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.damage.DamageProfile
import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.objects.{GlobalDefinitions, Player, Tool}
import net.psforever.services.galaxy.GalaxyService
import net.psforever.services.{InterstellarClusterService, ServiceManager}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, Vector3}
import scala.concurrent.duration._
class AutoRepairFacilityIntegrationTest extends FreedContextActorTest {
import akka.actor.typed.scaladsl.adapter._
system.spawn(InterstellarClusterService(Nil), InterstellarClusterService.InterstellarClusterServiceKey.id)
ServiceManager.boot(system) ! ServiceManager.Register(Props[GalaxyService](), "galaxy")
expectNoMessage(200 milliseconds)
val player = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player.Spawn()
val weapon = new Tool(GlobalDefinitions.suppressor)
val terminal = new Terminal(AutoRepairIntegrationTest.terminal_definition)
val silo = new ResourceSilo()
val guid = new NumberPoolHub(new MaxNumberSource(max = 10))
val zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {}
GUID(guid)
}
val avatarProbe = new TestProbe(system)
zone.AvatarEvents = avatarProbe.ref
guid.register(player, number = 1)
guid.register(weapon, number = 2)
guid.register(weapon.AmmoSlot.Box, number = 3)
guid.register(terminal, number = 4)
guid.register(silo, number = 5)
val building = Building.Structure(StructureType.Facility)(name = "test-building", guid = 6, map_id = 0, zone, context)
building.Invalidate()
guid.register(building, number = 6)
building.Amenities = silo
building.Amenities = terminal
terminal.Actor = context.actorOf(Props(classOf[TerminalControl], terminal), name = "test-terminal")
silo.NtuCapacitor = 1000
silo.Actor = system.actorOf(Props(classOf[ResourceSiloControl], silo), "test-silo")
silo.Actor ! "startup"
val wep_fmode = weapon.FireMode
val wep_prof = wep_fmode.Add
val proj = weapon.Projectile
val proj_prof = proj.asInstanceOf[DamageProfile]
val projectile = Projectile(proj, weapon.Definition, wep_fmode, player, Vector3(2, 0, 0), Vector3.Zero)
val resolved = ResolvedProjectile(
ProjectileResolution.Hit,
projectile,
SourceEntry(terminal),
terminal.DamageModel,
Vector3(1, 0, 0)
)
val applyDamageTo = resolved.damage_model.Calculate(resolved)
"AutoRepair" should {
"should activate on damage and trade NTU from the facility's resource silo for repairs" in {
assert(silo.NtuCapacitor == silo.MaxNtuCapacitor)
assert(terminal.Health == terminal.MaxHealth)
terminal.Actor ! Vitality.Damage(applyDamageTo)
avatarProbe.receiveOne(max = 200 milliseconds) //health update event
assert(terminal.Health < terminal.MaxHealth)
var i = 0 //safety counter
while(terminal.Health < terminal.MaxHealth && i < 100) {
i += 1
avatarProbe.receiveOne(max = 1000 milliseconds) //health update event
}
assert(silo.NtuCapacitor < silo.MaxNtuCapacitor)
assert(terminal.Health == terminal.MaxHealth)
}
}
}
class AutoRepairTowerIntegrationTest extends FreedContextActorTest {
import akka.actor.typed.scaladsl.adapter._
system.spawn(InterstellarClusterService(Nil), InterstellarClusterService.InterstellarClusterServiceKey.id)
ServiceManager.boot(system) ! ServiceManager.Register(Props[GalaxyService](), "galaxy")
expectNoMessage(200 milliseconds)
val player = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player.Spawn()
val weapon = new Tool(GlobalDefinitions.suppressor)
val terminal = new Terminal(AutoRepairIntegrationTest.terminal_definition)
val guid = new NumberPoolHub(new MaxNumberSource(max = 10))
val zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {}
GUID(guid)
}
val avatarProbe = new TestProbe(system)
zone.AvatarEvents = avatarProbe.ref
guid.register(player, number = 1)
guid.register(weapon, number = 2)
guid.register(weapon.AmmoSlot.Box, number = 3)
guid.register(terminal, number = 4)
val building = Building.Structure(StructureType.Tower)(name = "test-building", guid = 6, map_id = 0, zone, context)
building.Invalidate()
guid.register(building, number = 6)
building.Amenities = terminal
terminal.Actor = context.actorOf(Props(classOf[TerminalControl], terminal), name = "test-terminal")
val wep_fmode = weapon.FireMode
val wep_prof = wep_fmode.Add
val proj = weapon.Projectile
val proj_prof = proj.asInstanceOf[DamageProfile]
val projectile = Projectile(proj, weapon.Definition, wep_fmode, player, Vector3(2, 0, 0), Vector3.Zero)
val resolved = ResolvedProjectile(
ProjectileResolution.Hit,
projectile,
SourceEntry(terminal),
terminal.DamageModel,
Vector3(1, 0, 0)
)
val applyDamageTo = resolved.damage_model.Calculate(resolved)
"AutoRepair" should {
"should activate on damage and trade NTU from the tower for repairs" in {
assert(terminal.Health == terminal.MaxHealth)
terminal.Actor ! Vitality.Damage(applyDamageTo)
avatarProbe.receiveOne(max = 200 milliseconds) //health update event
assert(terminal.Health < terminal.MaxHealth)
var i = 0 //safety counter
while(terminal.Health < terminal.MaxHealth && i < 100) {
i += 1
avatarProbe.receiveOne(max = 1000 milliseconds) //health update event
}
assert(terminal.Health == terminal.MaxHealth)
}
}
}
object AutoRepairIntegrationTest {
val terminal_definition = new OrderTerminalDefinition(objId = 612) {
Name = "order_terminal"
MaxHealth = 500
Damageable = true
Repairable = true
autoRepair = AutoRepairStats(1, 500, 500, 1)
RepairIfDestroyed = true
}
}

View file

@ -0,0 +1,390 @@
// Copyright (c) 2020 PSForever
package objects
import akka.actor.Props
import akka.testkit.TestProbe
import base.FreedContextActorTest
import net.psforever.actors.commands.NtuCommand
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
import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.serverobject.structures.{AutoRepairStats, Building, StructureType}
import net.psforever.objects.serverobject.terminals.{OrderTerminalDefinition, Terminal, TerminalControl}
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.damage.DamageProfile
import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.objects.{GlobalDefinitions, Player, Tool}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, Vector3}
import scala.concurrent.duration._
class AutoRepairRequestNtuTest extends FreedContextActorTest {
val player = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player.Spawn()
val weapon = new Tool(GlobalDefinitions.suppressor)
val terminal = new Terminal(AutoRepairTest.terminal_definition)
val guid = new NumberPoolHub(new MaxNumberSource(max = 10))
val zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {}
GUID(guid)
}
val avatarProbe = new TestProbe(system)
zone.AvatarEvents = avatarProbe.ref
guid.register(player, number = 1)
guid.register(weapon, number = 2)
guid.register(weapon.AmmoSlot.Box, number = 3)
guid.register(terminal, number = 4)
val building = Building("test-building", 1, 1, zone, StructureType.Facility)
building.Invalidate()
guid.register(building, number = 6)
val buildingProbe = new TestProbe(system)
building.Actor = buildingProbe.ref
building.Zone = zone
terminal.Actor = context.actorOf(Props(classOf[TerminalControl], terminal), name = "test-terminal")
terminal.Owner = building
val wep_fmode = weapon.FireMode
val wep_prof = wep_fmode.Add
val proj = weapon.Projectile
val proj_prof = proj.asInstanceOf[DamageProfile]
val projectile = Projectile(proj, weapon.Definition, wep_fmode, player, Vector3(2, 0, 0), Vector3.Zero)
val resolved = ResolvedProjectile(
ProjectileResolution.Hit,
projectile,
SourceEntry(terminal),
terminal.DamageModel,
Vector3(1, 0, 0)
)
val applyDamageTo = resolved.damage_model.Calculate(resolved)
"AutoRepair" should {
"asks owning building for NTU after damage" in {
assert(terminal.Health == terminal.MaxHealth)
terminal.Actor ! Vitality.Damage(applyDamageTo)
avatarProbe.receiveOne(max = 200 milliseconds) //health update event
assert(terminal.Health < terminal.MaxHealth)
val buildingMsg = buildingProbe.receiveOne(max = 600 milliseconds)
assert(buildingMsg match {
case BuildingActor.Ntu(NtuCommand.Request(drain, _)) =>
drain == terminal.Definition.autoRepair.get.drain
case _ =>
false
})
}
}
}
class AutoRepairRequestNtuRepeatTest extends FreedContextActorTest {
val player = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player.Spawn()
val weapon = new Tool(GlobalDefinitions.suppressor)
val terminal = new Terminal(AutoRepairTest.terminal_definition)
val guid = new NumberPoolHub(new MaxNumberSource(max = 10))
val zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {}
GUID(guid)
}
val avatarProbe = new TestProbe(system)
zone.AvatarEvents = avatarProbe.ref
guid.register(player, number = 1)
guid.register(weapon, number = 2)
guid.register(weapon.AmmoSlot.Box, number = 3)
guid.register(terminal, number = 4)
val building = Building("test-building", 1, 1, zone, StructureType.Facility)
building.Invalidate()
guid.register(building, number = 6)
val buildingProbe = new TestProbe(system)
building.Actor = buildingProbe.ref
building.Zone = zone
terminal.Actor = context.actorOf(Props(classOf[TerminalControl], terminal), name = "test-terminal")
terminal.Owner = building
val wep_fmode = weapon.FireMode
val wep_prof = wep_fmode.Add
val proj = weapon.Projectile
val proj_prof = proj.asInstanceOf[DamageProfile]
val projectile = Projectile(proj, weapon.Definition, wep_fmode, player, Vector3(2, 0, 0), Vector3.Zero)
val resolved = ResolvedProjectile(
ProjectileResolution.Hit,
projectile,
SourceEntry(terminal),
terminal.DamageModel,
Vector3(1, 0, 0)
)
val applyDamageTo = resolved.damage_model.Calculate(resolved)
"AutoRepair" should {
"repeatedly asks owning building for NTU after damage" in {
assert(terminal.Health == terminal.MaxHealth)
terminal.Actor ! Vitality.Damage(applyDamageTo)
avatarProbe.receiveOne(max = 200 milliseconds) //health update event
assert(terminal.Health < terminal.MaxHealth)
(0 to 3).foreach { _ =>
val buildingMsg = buildingProbe.receiveOne(max = 1000 milliseconds)
assert(buildingMsg match {
case BuildingActor.Ntu(NtuCommand.Request(drain, _)) =>
drain == terminal.Definition.autoRepair.get.drain
case _ =>
false
})
}
}
}
}
class AutoRepairNoRequestNtuTest extends FreedContextActorTest {
val player = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player.Spawn()
val weapon = new Tool(GlobalDefinitions.suppressor)
val terminal = new Terminal(AutoRepairTest.terminal_definition)
val guid = new NumberPoolHub(new MaxNumberSource(max = 10))
val zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {}
GUID(guid)
}
val avatarProbe = new TestProbe(system)
zone.AvatarEvents = avatarProbe.ref
guid.register(player, number = 1)
guid.register(weapon, number = 2)
guid.register(weapon.AmmoSlot.Box, number = 3)
guid.register(terminal, number = 4)
val building = Building("test-building", 1, 1, zone, StructureType.Facility)
building.Invalidate()
guid.register(building, number = 6)
val buildingProbe = new TestProbe(system)
building.Actor = buildingProbe.ref
building.Zone = zone
terminal.Actor = context.actorOf(Props(classOf[TerminalControl], terminal), name = "test-terminal")
terminal.Owner = building
val wep_fmode = weapon.FireMode
val wep_prof = wep_fmode.Add
val proj = weapon.Projectile
val proj_prof = proj.asInstanceOf[DamageProfile]
val projectile = Projectile(proj, weapon.Definition, wep_fmode, player, Vector3(2, 0, 0), Vector3.Zero)
val resolved = ResolvedProjectile(
ProjectileResolution.Hit,
projectile,
SourceEntry(terminal),
terminal.DamageModel,
Vector3(1, 0, 0)
)
val applyDamageTo = resolved.damage_model.Calculate(resolved)
"AutoRepair" should {
"not ask for NTU after damage if it expects no NTU" in {
assert(terminal.Health == terminal.MaxHealth)
terminal.Actor ! BuildingActor.NtuDepleted()
terminal.Actor ! Vitality.Damage(applyDamageTo)
avatarProbe.receiveOne(max = 200 milliseconds) //health update event
assert(terminal.Health < terminal.MaxHealth)
buildingProbe.expectNoMessage(max = 2000 milliseconds)
}
}
}
class AutoRepairRestoreRequestNtuTest extends FreedContextActorTest {
val player = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player.Spawn()
val weapon = new Tool(GlobalDefinitions.suppressor)
val terminal = new Terminal(AutoRepairTest.terminal_definition)
val guid = new NumberPoolHub(new MaxNumberSource(max = 10))
val zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {}
GUID(guid)
}
val avatarProbe = new TestProbe(system)
zone.AvatarEvents = avatarProbe.ref
guid.register(player, number = 1)
guid.register(weapon, number = 2)
guid.register(weapon.AmmoSlot.Box, number = 3)
guid.register(terminal, number = 4)
val building = Building("test-building", 1, 1, zone, StructureType.Facility)
building.Invalidate()
guid.register(building, number = 6)
val buildingProbe = new TestProbe(system)
building.Actor = buildingProbe.ref
building.Zone = zone
terminal.Actor = context.actorOf(Props(classOf[TerminalControl], terminal), name = "test-terminal")
terminal.Owner = building
val wep_fmode = weapon.FireMode
val wep_prof = wep_fmode.Add
val proj = weapon.Projectile
val proj_prof = proj.asInstanceOf[DamageProfile]
val projectile = Projectile(proj, weapon.Definition, wep_fmode, player, Vector3(2, 0, 0), Vector3.Zero)
val resolved = ResolvedProjectile(
ProjectileResolution.Hit,
projectile,
SourceEntry(terminal),
terminal.DamageModel,
Vector3(1, 0, 0)
)
val applyDamageTo = resolved.damage_model.Calculate(resolved)
"AutoRepair" should {
"ask for NTU after damage if its expectation of NTU is restored" in {
assert(terminal.Health == terminal.MaxHealth)
terminal.Actor ! BuildingActor.NtuDepleted()
terminal.Actor ! Vitality.Damage(applyDamageTo)
avatarProbe.receiveOne(max = 200 milliseconds) //health update event
assert(terminal.Health < terminal.MaxHealth)
buildingProbe.expectNoMessage(max = 2000 milliseconds)
terminal.Actor ! BuildingActor.SuppliedWithNtu()
val buildingMsg = buildingProbe.receiveOne(max = 600 milliseconds)
assert(buildingMsg match {
case BuildingActor.Ntu(NtuCommand.Request(drain, _)) =>
drain == terminal.Definition.autoRepair.get.drain
case _ =>
false
})
}
}
}
class AutoRepairRepairWithNtuTest extends FreedContextActorTest {
val player = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player.Spawn()
val weapon = new Tool(GlobalDefinitions.suppressor)
val terminal = new Terminal(AutoRepairTest.terminal_definition)
val guid = new NumberPoolHub(new MaxNumberSource(max = 10))
val zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {}
GUID(guid)
}
val avatarProbe = new TestProbe(system)
zone.AvatarEvents = avatarProbe.ref
guid.register(player, number = 1)
guid.register(weapon, number = 2)
guid.register(weapon.AmmoSlot.Box, number = 3)
guid.register(terminal, number = 4)
val building = Building("test-building", 1, 1, zone, StructureType.Facility)
building.Invalidate()
guid.register(building, number = 6)
val buildingProbe = new TestProbe(system)
building.Actor = buildingProbe.ref
building.Zone = zone
terminal.Actor = context.actorOf(Props(classOf[TerminalControl], terminal), name = "test-terminal")
terminal.Owner = building
val wep_fmode = weapon.FireMode
val wep_prof = wep_fmode.Add
val proj = weapon.Projectile
val proj_prof = proj.asInstanceOf[DamageProfile]
val projectile = Projectile(proj, weapon.Definition, wep_fmode, player, Vector3(2, 0, 0), Vector3.Zero)
val resolved = ResolvedProjectile(
ProjectileResolution.Hit,
projectile,
SourceEntry(terminal),
terminal.DamageModel,
Vector3(1, 0, 0)
)
val applyDamageTo = resolved.damage_model.Calculate(resolved)
"AutoRepair" should {
"repair some of the damage when it receives NTU" in {
assert(terminal.Health == terminal.MaxHealth)
terminal.Actor ! BuildingActor.NtuDepleted() //don't worry about requests
terminal.Actor ! Vitality.Damage(applyDamageTo)
avatarProbe.receiveOne(max = 200 milliseconds) //health update event
assert(terminal.Health < terminal.MaxHealth)
val reducedHealth = terminal.Health
buildingProbe.expectNoMessage(max = 2000 milliseconds)
terminal.Actor ! NtuCommand.Grant(null, 1)
avatarProbe.receiveOne(max = 200 milliseconds) //health update event
assert(terminal.Health > reducedHealth)
}
}
}
class AutoRepairRepairWithNtuUntilDoneTest extends FreedContextActorTest {
val player = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player.Spawn()
val weapon = new Tool(GlobalDefinitions.suppressor)
val terminal = new Terminal(AutoRepairTest.terminal_definition)
val guid = new NumberPoolHub(new MaxNumberSource(max = 10))
val zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {}
GUID(guid)
}
val avatarProbe = new TestProbe(system)
zone.AvatarEvents = avatarProbe.ref
guid.register(player, number = 1)
guid.register(weapon, number = 2)
guid.register(weapon.AmmoSlot.Box, number = 3)
guid.register(terminal, number = 4)
val building = Building("test-building", 1, 1, zone, StructureType.Facility)
building.Invalidate()
guid.register(building, number = 6)
val buildingProbe = new TestProbe(system)
building.Actor = buildingProbe.ref
building.Zone = zone
terminal.Actor = context.actorOf(Props(classOf[TerminalControl], terminal), name = "test-terminal")
terminal.Owner = building
val wep_fmode = weapon.FireMode
val wep_prof = wep_fmode.Add
val proj = weapon.Projectile
val proj_prof = proj.asInstanceOf[DamageProfile]
val projectile = Projectile(proj, weapon.Definition, wep_fmode, player, Vector3(2, 0, 0), Vector3.Zero)
val resolved = ResolvedProjectile(
ProjectileResolution.Hit,
projectile,
SourceEntry(terminal),
terminal.DamageModel,
Vector3(1, 0, 0)
)
val applyDamageTo = resolved.damage_model.Calculate(resolved)
"AutoRepair" should {
"ask for NTU after damage and repair some of the damage when it receives NTU, until fully-repaired" in {
assert(terminal.Health == terminal.MaxHealth)
terminal.Actor ! Vitality.Damage(applyDamageTo)
avatarProbe.receiveOne(max = 200 milliseconds) //health update event
assert(terminal.Health < terminal.MaxHealth)
var i = 0
while(terminal.Health < terminal.MaxHealth && i < 100) {
i += 1 //safety counter
val buildingMsg = buildingProbe.receiveOne(max = 1000 milliseconds)
buildingMsg match {
case BuildingActor.Ntu(NtuCommand.Request(_, _)) =>
terminal.Actor ! NtuCommand.Grant(null, 1)
case _ => ;
}
}
assert(terminal.Health == terminal.MaxHealth)
}
}
}
object AutoRepairTest {
val terminal_definition = new OrderTerminalDefinition(objId = 612) {
Name = "order_terminal"
MaxHealth = 500
Damageable = true
Repairable = true
autoRepair = AutoRepairStats(1, 500, 500, 1)
RepairIfDestroyed = true
}
}

View file

@ -25,22 +25,22 @@ class DoorTest extends Specification {
"starts as closed (false)" in {
val door = Door(GlobalDefinitions.door)
door.Open mustEqual None
door.Open.isEmpty mustEqual true
door.isOpen mustEqual false
}
"be opened and closed (1; manual)" in {
val door = Door(GlobalDefinitions.door)
door.isOpen mustEqual false
door.Open mustEqual None
door.Open.isEmpty mustEqual true
door.Open = Some(player)
door.isOpen mustEqual true
door.Open mustEqual Some(player)
door.Open.contains(player) mustEqual true
door.Open = None
door.isOpen mustEqual false
door.Open mustEqual None
door.Open.isEmpty mustEqual true
}
"be opened and closed (2; toggle)" in {
@ -58,11 +58,11 @@ class DoorTest extends Specification {
364
)
val door = Door(GlobalDefinitions.door)
door.Open mustEqual None
door.Open.isEmpty mustEqual true
door.Use(player, msg)
door.Open mustEqual Some(player)
door.Open.contains(player) mustEqual true
door.Use(player, msg)
door.Open mustEqual None
door.Open.isEmpty mustEqual true
}
}
}
@ -115,8 +115,7 @@ class DoorControl3Test extends ActorTest {
assert(door.Open.isEmpty)
door.Actor ! "trash"
val reply = receiveOne(Duration.create(500, "ms"))
assert(reply.isInstanceOf[Door.NoEvent])
expectNoMessage(Duration.create(500, "ms"))
assert(door.Open.isEmpty)
}
}

View file

@ -4,6 +4,7 @@ package objects
import akka.actor.{ActorRef, Props}
import akka.testkit.TestProbe
import base.ActorTest
import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.avatar.Avatar
import net.psforever.objects.ballistics._
import net.psforever.objects.{GlobalDefinitions, Player, Tool}
@ -206,7 +207,7 @@ class GeneratorControlCriticalTest extends ActorTest {
)
assert(
msg_building match {
case Building.AmenityStateChange(o) => o eq gen
case BuildingActor.AmenityStateChange(o) => o eq gen
case _ => false
}
)
@ -268,6 +269,7 @@ 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
@ -293,12 +295,11 @@ class GeneratorControlDestroyedTest extends ActorTest {
assert(gen.Condition == PlanetSideGeneratorState.Normal)
avatarProbe.expectNoMessage(9 seconds)
buildingProbe.expectNoMessage(50 milliseconds) //no prior messages
val msg_avatar2 = avatarProbe.receiveN(3, 1000 milliseconds) //see DamageableEntity test file
val msg_building = buildingProbe.receiveOne(200 milliseconds)
assert(
msg_building match {
case Building.AmenityStateChange(o) => o eq gen
case BuildingActor.AmenityStateChange(o) => o eq gen
case _ => false
}
)
@ -399,6 +400,7 @@ 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
@ -438,7 +440,7 @@ class GeneratorControlKillsTest extends ActorTest {
player2Probe.expectNoMessage(200 milliseconds)
assert(
msg_building match {
case Building.AmenityStateChange(o) => o eq gen
case BuildingActor.AmenityStateChange(o) => o eq gen
case _ => false
}
)
@ -840,7 +842,7 @@ class GeneratorControlRepairPastRestorePoint extends ActorTest {
)
assert(
msg_building match {
case Building.AmenityStateChange(o) => o eq gen
case BuildingActor.AmenityStateChange(o) => o eq gen
case _ => false
}
)

View file

@ -87,12 +87,63 @@ class ResourceSiloControlStartupTest extends ActorTest {
"Resource silo" should {
"startup properly" in {
expectNoMessage(500 milliseconds)
system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo") ! "startup"
expectNoMessage(1 seconds)
}
}
}
class ResourceSiloControlStartupMessageNoneTest extends ActorTest {
val obj = ResourceSilo()
obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
val zone = new Zone("nowhere", new ZoneMap("nowhere-map"), 0)
val buildingEvents = TestProbe("test-building-events")
obj.Owner =
new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building) {
Actor = buildingEvents.ref
}
obj.Owner.GUID = PlanetSideGUID(6)
"Resource silo" should {
"report if it has no NTU on startup" in {
assert(obj.NtuCapacitor == 0)
obj.Actor ! "startup"
val ownerMsg = buildingEvents.receiveOne(200 milliseconds)
assert(ownerMsg match {
case BuildingActor.NtuDepleted() => true
case _ => false
})
}
}
}
class ResourceSiloControlStartupMessageSomeTest extends ActorTest {
val obj = ResourceSilo()
obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
val zone = new Zone("nowhere", new ZoneMap("nowhere-map"), 0)
val buildingEvents = TestProbe("test-building-events")
obj.Owner =
new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building) {
Actor = buildingEvents.ref
}
obj.Owner.GUID = PlanetSideGUID(6)
"Resource silo" should {
"report if it has any NTU on startup" in {
obj.NtuCapacitor = 1
assert(obj.NtuCapacitor == 1)
obj.Actor ! "startup"
val ownerMsg = buildingEvents.receiveOne(200 milliseconds)
assert(ownerMsg match {
case BuildingActor.SuppliedWithNtu() => true
case _ => false
})
}
}
}
class ResourceSiloControlUseTest extends ActorTest {
val guid = new NumberPoolHub(new MaxNumberSource(10))
val map = new ZoneMap("test")
@ -109,6 +160,7 @@ class ResourceSiloControlUseTest extends ActorTest {
StructureType.Building,
GlobalDefinitions.building
) //guid=1
building.Actor = TestProbe("building-actor").ref
val obj = ResourceSilo() //guid=2
obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
@ -151,21 +203,23 @@ class ResourceSiloControlNtuWarningTest extends ActorTest {
val obj = ResourceSilo()
obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
obj.Actor ! "startup"
val zone = new Zone("nowhere", new ZoneMap("nowhere-map"), 0)
obj.Owner =
new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building)
new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building) {
Actor = TestProbe("building-events").ref
}
obj.Owner.GUID = PlanetSideGUID(6)
val zoneEvents = TestProbe("zone-events")
zone.AvatarEvents = zoneEvents.ref
obj.Actor ! "startup"
"Resource silo" should {
"announce high ntu" in {
zone.AvatarEvents = zoneEvents.ref
assert(obj.LowNtuWarningOn)
obj.Actor ! ResourceSilo.LowNtuWarning(false)
val reply = zoneEvents.receiveOne(500 milliseconds)
assert(!obj.LowNtuWarningOn)
val reply = zoneEvents.receiveOne(5000 milliseconds)
assert(reply.isInstanceOf[AvatarServiceMessage])
assert(reply.asInstanceOf[AvatarServiceMessage].forChannel == "nowhere")
assert(reply.asInstanceOf[AvatarServiceMessage].actionMessage.isInstanceOf[AvatarAction.PlanetsideAttribute])
@ -190,6 +244,7 @@ class ResourceSiloControlNtuWarningTest extends ActorTest {
.asInstanceOf[AvatarAction.PlanetsideAttribute]
.attribute_value == 0
)
assert(!obj.LowNtuWarningOn)
}
}
}
@ -198,7 +253,6 @@ class ResourceSiloControlUpdate1Test extends ActorTest {
val obj = ResourceSilo()
obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
obj.Actor ! "startup"
val zone = new Zone("nowhere", new ZoneMap("nowhere-map"), 0)
val bldg =
new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building)
@ -206,12 +260,13 @@ class ResourceSiloControlUpdate1Test extends ActorTest {
obj.Owner = bldg
val zoneEvents = TestProbe("zone-events")
val buildingEvents = TestProbe("building-events")
zone.AvatarEvents = zoneEvents.ref
bldg.Actor = buildingEvents.ref
obj.Actor ! "startup"
"Resource silo" should {
"update the charge level and capacitor display (report high ntu, power restored)" in {
zone.AvatarEvents = zoneEvents.ref
bldg.Actor = buildingEvents.ref
buildingEvents.receiveOne(500 milliseconds) //message caused by "startup"
assert(obj.NtuCapacitor == 0)
assert(obj.CapacitorDisplay == 0)
assert(obj.LowNtuWarningOn)
@ -247,7 +302,6 @@ class ResourceSiloControlUpdate2Test extends ActorTest {
val obj = ResourceSilo()
obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
obj.Actor ! "startup"
val zone = new Zone("nowhere", new ZoneMap("nowhere-map"), 0)
val bldg =
new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building)
@ -255,12 +309,13 @@ class ResourceSiloControlUpdate2Test extends ActorTest {
obj.Owner = bldg
val zoneEvents = TestProbe("zone-events")
val buildingEvents = TestProbe("building-events")
zone.AvatarEvents = zoneEvents.ref
bldg.Actor = buildingEvents.ref
obj.Actor ! "startup"
"Resource silo" should {
"update the charge level and capacitor display (report good ntu)" in {
zone.AvatarEvents = zoneEvents.ref
bldg.Actor = buildingEvents.ref
buildingEvents.receiveOne(500 milliseconds) //message caused by "startup"
obj.NtuCapacitor = 100
obj.LowNtuWarningOn = true
assert(obj.NtuCapacitor == 100)
@ -333,7 +388,6 @@ class ResourceSiloControlNoUpdateTest extends ActorTest {
val obj = ResourceSilo()
obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
obj.Actor ! "startup"
val zone = new Zone("nowhere", new ZoneMap("nowhere-map"), 0)
val bldg =
new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building)
@ -341,12 +395,13 @@ class ResourceSiloControlNoUpdateTest extends ActorTest {
obj.Owner = bldg
val zoneEvents = TestProbe("zone-events")
val buildingEvents = TestProbe("building-events")
zone.AvatarEvents = zoneEvents.ref
bldg.Actor = buildingEvents.ref
obj.Actor ! "startup"
"Resource silo" should {
"update, but not sufficiently to change the capacitor display" in {
zone.AvatarEvents = zoneEvents.ref
bldg.Actor = buildingEvents.ref
buildingEvents.receiveOne(500 milliseconds) //message caused by "startup"
obj.NtuCapacitor = 250
obj.LowNtuWarningOn = false
assert(obj.NtuCapacitor == 250)

View file

@ -23,7 +23,6 @@ import scodec.Attempt.{Failure, Successful}
import scodec.bits._
import scala.concurrent.duration.{DurationInt, FiniteDuration}
import scala.util.control.Breaks._
object Client {
Security.addProvider(new BouncyCastleProvider)
@ -153,6 +152,7 @@ class Client(username: String, password: String) {
socket.send(new DatagramPacket(payload, payload.length, host))
case (None, Some(ref)) =>
// ref ! Udp.Received(ByteString(payload), new InetSocketAddress(socket.getInetAddress, socket.getPort))
case _ => ;
}
}

View file

@ -14,7 +14,6 @@ import scala.collection.parallel.CollectionConverters._
import scala.io.{Codec, Source}
import scala.sys.process._
import scala.util.Using
import scala.util.control.Breaks._
case class Config(
outDir: String = System.getProperty("user.dir"),