Auto-Repair Tuning (#652)

* difficulty modifiers for repair rate and drain in config

* changed autorepair request-repair pattern to wait until a repair is fulfilled before issuing the next request; moved integration tests for efficiency

* auto-repair accounts for delay between request-reply when schduling next request; base ntu drain is halved; auto-repair mending values adjusted based on wiki times

* moving config call into auto-repair mixin

* deleting the old integration testing code

* obligatory test fixes

* more of the same

* correcting exceptions with calculating subsequent auto-repair time and transfer messaging behavior with WarpGate objects; transfer animation and transfer delivery now have slightly different timing; wrote  bunch of tests that don't work in the standard manner, but do pass
This commit is contained in:
Fate-JH 2021-01-12 14:27:33 -05:00 committed by GitHub
parent 6836c80c9f
commit 563afcdb19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 749 additions and 258 deletions

2
.gitignore vendored
View file

@ -32,3 +32,5 @@ logs/
*.exe *.exe
*.zip *.zip
pscrypto-lib/ pscrypto-lib/
.bsp/
project/project/

View file

@ -0,0 +1,554 @@
// Copyright (c) 2020 PSForever
package actor.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, SourceEntry}
import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.deploy.Deployment
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.vehicles.VehicleControl
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.base.DamageResolution
import net.psforever.objects.vital.damage.DamageProfile
import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.vital.projectile.ProjectileReason
import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.objects.{GlobalDefinitions, Player, Tool, Vehicle}
import net.psforever.services.galaxy.GalaxyService
import net.psforever.services.{InterstellarClusterService, ServiceManager}
import net.psforever.types._
import scala.collection.concurrent.TrieMap
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(1000 milliseconds)
val guid = new NumberPoolHub(new MaxNumberSource(max = 10))
val avatarProbe = new TestProbe(system)
val catchall = new TestProbe(system).ref
val zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {}
GUID(guid)
override def AvatarEvents = avatarProbe.ref
override def LocalEvents = catchall
override def VehicleEvents = catchall
override def Activity = catchall
}
val building = Building.Structure(StructureType.Facility)(name = "integ-fac-test-building", guid = 6, map_id = 0, zone, context)
building.Invalidate()
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()
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)
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 = DamageInteraction(
SourceEntry(terminal),
ProjectileReason(
DamageResolution.Hit,
projectile,
terminal.DamageModel
),
Vector3(1, 0, 0)
)
val applyDamageTo = resolved.calculate()
"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 = 1000 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 AutoRepairFacilityIntegrationGiveNtuTest extends FreedContextActorTest {
import akka.actor.typed.scaladsl.adapter._
system.spawn(InterstellarClusterService(Nil), InterstellarClusterService.InterstellarClusterServiceKey.id)
ServiceManager.boot(system) ! ServiceManager.Register(Props[GalaxyService](), "galaxy")
expectNoMessage(1000 milliseconds)
val guid = new NumberPoolHub(new MaxNumberSource(max = 10))
val avatarProbe = new TestProbe(system)
val catchall = new TestProbe(system).ref
val zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {}
GUID(guid)
override def AvatarEvents = avatarProbe.ref
override def LocalEvents = catchall
override def VehicleEvents = catchall
override def Activity = catchall
}
val building = Building.Structure(StructureType.Facility)(name = "integ-fac-test-building", guid = 6, map_id = 0, zone, context)
building.Invalidate()
val terminal = new Terminal(AutoRepairIntegrationTest.terminal_definition)
val silo = new ResourceSilo()
guid.register(terminal, number = 4)
guid.register(silo, number = 5)
guid.register(building, number = 6)
building.Amenities = silo
building.Amenities = terminal
terminal.Actor = context.actorOf(Props(classOf[TerminalControl], terminal), name = "test-terminal")
terminal.Health = 0
terminal.Destroyed = true
silo.Actor = system.actorOf(Props(classOf[ResourceSiloControl], silo), "test-silo")
silo.Actor ! "startup"
"AutoRepair" should {
"should activate and trade NTU frpom the silo only when NTU is made available" in {
assert(silo.NtuCapacitor == 0)
assert(terminal.Health == 0)
assert(terminal.Destroyed)
avatarProbe.expectNoMessage(max = 1000 milliseconds) //nothing
silo.Actor ! ResourceSilo.UpdateChargeLevel(1000) //then ...
avatarProbe.receiveOne(max = 1000 milliseconds) //health update event
assert(terminal.Health < terminal.MaxHealth)
var i = 0 //safety counter
while(terminal.Health < terminal.MaxHealth && i < 1000) {
i += 1
avatarProbe.receiveOne(max = 1000 milliseconds) //health update event
}
assert(silo.NtuCapacitor > 0 && silo.NtuCapacitor < silo.MaxNtuCapacitor)
assert(terminal.Health == terminal.MaxHealth)
assert(!terminal.Destroyed)
}
}
}
class AutoRepairFacilityIntegrationAntGiveNtuTest extends FreedContextActorTest {
import akka.actor.typed.scaladsl.adapter._
system.spawn(InterstellarClusterService(Nil), InterstellarClusterService.InterstellarClusterServiceKey.id)
ServiceManager.boot(system) ! ServiceManager.Register(Props[GalaxyService](), "galaxy")
expectNoMessage(1000 milliseconds)
var buildingMap = new TrieMap[Int, Building]()
val guid = new NumberPoolHub(new MaxNumberSource(max = 10))
val player = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val ant = Vehicle(GlobalDefinitions.ant)
val terminal = new Terminal(AutoRepairIntegrationTest.slow_terminal_definition)
val silo = new ResourceSilo()
val avatarProbe = new TestProbe(system)
val catchall = new TestProbe(system).ref
val zone = new Zone("test", new ZoneMap("test-map"), 0) {
override def SetupNumberPools() = {}
GUID(guid)
override def AvatarEvents = avatarProbe.ref
override def LocalEvents = catchall
override def VehicleEvents = catchall
override def Activity = catchall
override def Vehicles = List(ant)
override def Buildings = { buildingMap.toMap }
}
val building = new Building(
name = "integ-fac-test-building",
building_guid = 6,
map_id = 0,
zone,
StructureType.Facility,
GlobalDefinitions.cryo_facility
)
buildingMap += 6 -> building
building.Actor = context.spawn(BuildingActor(zone, building), "integ-fac-test-building-control").toClassic
building.Invalidate()
guid.register(player, number = 1)
guid.register(ant, number = 2)
guid.register(terminal, number = 4)
guid.register(silo, number = 5)
guid.register(building, number = 6)
val maxNtuCap = ant.Definition.MaxNtuCapacitor
player.Spawn()
ant.NtuCapacitor = maxNtuCap
ant.Actor = context.actorOf(Props(classOf[VehicleControl], ant), name = "test-ant")
ant.Zone = zone
ant.Seats(0).Occupant = player
ant.DeploymentState = DriveState.Deployed
building.Amenities = terminal
building.Amenities = silo
terminal.Actor = context.actorOf(Props(classOf[TerminalControl], terminal), name = "test-terminal")
terminal.Health = 0
terminal.Destroyed = true
silo.Actor = system.actorOf(Props(classOf[ResourceSiloControl], silo), "test-silo")
silo.Actor ! "startup"
"AutoRepair" should {
"should activate and trade NTU from the silo only when NTU is made available from an ANT" in {
assert(silo.NtuCapacitor == 0)
assert(ant.NtuCapacitor == maxNtuCap)
assert(terminal.Health == 0)
assert(terminal.Destroyed)
avatarProbe.expectNoMessage(max = 1000 milliseconds) //nothing
silo.Actor ! CommonMessages.Use(player) //then ...
avatarProbe.receiveOne(max = 1000 milliseconds) //health update event
assert(terminal.Health < terminal.MaxHealth)
var i = 0 //safety counter
while(terminal.Health < terminal.MaxHealth && i < 1000) {
i += 1
avatarProbe.receiveOne(max = 1000 milliseconds) //health update event
}
assert(silo.NtuCapacitor > 0 && silo.NtuCapacitor < silo.MaxNtuCapacitor)
val ntuAfterRepairs = ant.NtuCapacitor
assert(ntuAfterRepairs < maxNtuCap)
assert(terminal.Health == terminal.MaxHealth)
assert(!terminal.Destroyed)
if(silo.NtuCapacitor < maxNtuCap) {
var j = 0 //safety counter
while(silo.NtuCapacitor < silo.MaxNtuCapacitor && j < 1000) {
j += 1
avatarProbe.receiveOne(max = 1000 milliseconds) //health update event
}
}
assert(silo.NtuCapacitor == silo.MaxNtuCapacitor)
assert(ant.NtuCapacitor < ntuAfterRepairs)
println(s"Test '${testNames.head}' successful.")
}
}
}
class AutoRepairFacilityIntegrationTerminalDestroyedTerminalAntTest extends FreedContextActorTest {
import akka.actor.typed.scaladsl.adapter._
system.spawn(InterstellarClusterService(Nil), InterstellarClusterService.InterstellarClusterServiceKey.id)
ServiceManager.boot(system) ! ServiceManager.Register(Props[GalaxyService](), "galaxy")
expectNoMessage(1000 milliseconds)
var buildingMap = new TrieMap[Int, Building]()
val guid = new NumberPoolHub(new MaxNumberSource(max = 10))
val player = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val weapon = new Tool(GlobalDefinitions.suppressor)
val ant = Vehicle(GlobalDefinitions.ant)
val terminal = new Terminal(AutoRepairIntegrationTest.slow_terminal_definition)
val silo = new ResourceSilo()
val avatarProbe = new TestProbe(system)
val catchall = new TestProbe(system).ref
val zone = new Zone("test", new ZoneMap("test-map"), 0) {
override def SetupNumberPools() = {}
GUID(guid)
override def AvatarEvents = avatarProbe.ref
override def LocalEvents = catchall
override def VehicleEvents = catchall
override def Activity = catchall
override def Vehicles = List(ant)
override def Buildings = { buildingMap.toMap }
}
val building = new Building(
name = "integ-fac-test-building",
building_guid = 6,
map_id = 0,
zone,
StructureType.Facility,
GlobalDefinitions.cryo_facility
)
buildingMap += 6 -> building
building.Actor = context.spawn(BuildingActor(zone, building), "integ-fac-test-building-control").toClassic
building.Invalidate()
guid.register(player, number = 1)
guid.register(ant, number = 2)
guid.register(weapon, number = 3)
guid.register(terminal, number = 4)
guid.register(silo, number = 5)
guid.register(building, number = 6)
guid.register(weapon.AmmoSlot.Box, number = 7)
val maxNtuCap = ant.Definition.MaxNtuCapacitor
player.Spawn()
ant.NtuCapacitor = maxNtuCap
ant.Actor = context.actorOf(Props(classOf[VehicleControl], ant), name = "test-ant")
ant.Zone = zone
ant.Seats(0).Occupant = player
ant.DeploymentState = DriveState.Deployed
building.Amenities = terminal
building.Amenities = silo
terminal.Actor = context.actorOf(Props(classOf[TerminalControl], terminal), name = "test-terminal")
terminal.Health = 1 //not yet destroyed, but one shot away from it
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 = DamageInteraction(
SourceEntry(terminal),
ProjectileReason(
DamageResolution.Hit,
projectile,
terminal.DamageModel
),
Vector3(1, 0, 0)
)
val applyDamageTo = resolved.calculate()
"AutoRepair" should {
"should activate upon destruction and trade NTU from the silo only when NTU is made available from an ANT" in {
assert(silo.NtuCapacitor == 0)
assert(ant.NtuCapacitor == maxNtuCap)
assert(!terminal.Destroyed)
avatarProbe.expectNoMessage(max = 1000 milliseconds) //nothing
terminal.Actor ! Vitality.Damage(applyDamageTo)
while(avatarProbe.receiveOne(max = 1000 milliseconds) != null) { /* health loss event(s) + state updates */ }
assert(terminal.Destroyed)
avatarProbe.expectNoMessage(max = 1000 milliseconds) //nothing
silo.Actor ! CommonMessages.Use(player) //then ...
avatarProbe.receiveOne(max = 1000 milliseconds) //health update event
assert(terminal.Health < terminal.MaxHealth)
var i = 0 //safety counter
while(terminal.Health < terminal.MaxHealth && i < 1000) {
i += 1
avatarProbe.receiveOne(max = 1000 milliseconds) //health update event
}
assert(silo.NtuCapacitor > 0 && silo.NtuCapacitor <= silo.MaxNtuCapacitor)
assert(ant.NtuCapacitor < maxNtuCap)
assert(terminal.Health == terminal.MaxHealth)
assert(!terminal.Destroyed)
println(s"Test '${testNames.head}' successful.")
}
}
}
class AutoRepairFacilityIntegrationTerminalIncompleteRepairTest extends FreedContextActorTest {
import akka.actor.typed.scaladsl.adapter._
system.spawn(InterstellarClusterService(Nil), InterstellarClusterService.InterstellarClusterServiceKey.id)
ServiceManager.boot(system) ! ServiceManager.Register(Props[GalaxyService](), "galaxy")
expectNoMessage(1000 milliseconds)
var buildingMap = new TrieMap[Int, Building]()
val guid = new NumberPoolHub(new MaxNumberSource(max = 10))
val player = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val weapon = new Tool(GlobalDefinitions.suppressor)
val ant = Vehicle(GlobalDefinitions.ant)
val terminal = new Terminal(AutoRepairIntegrationTest.slow_terminal_definition)
val silo = new ResourceSilo()
val avatarProbe = new TestProbe(system)
val catchall = new TestProbe(system).ref
val zone = new Zone("test", new ZoneMap("test-map"), 0) {
override def SetupNumberPools() = {}
GUID(guid)
override def AvatarEvents = avatarProbe.ref
override def LocalEvents = catchall
override def VehicleEvents = catchall
override def Activity = catchall
override def Vehicles = List(ant)
override def Buildings = { buildingMap.toMap }
}
val building = new Building(
name = "integ-fac-test-building",
building_guid = 6,
map_id = 0,
zone,
StructureType.Facility,
GlobalDefinitions.cryo_facility
)
buildingMap += 6 -> building
building.Actor = context.spawn(BuildingActor(zone, building), "integ-fac-test-building-control").toClassic
building.Invalidate()
guid.register(player, number = 1)
guid.register(ant, number = 2)
guid.register(weapon, number = 3)
guid.register(terminal, number = 4)
guid.register(silo, number = 5)
guid.register(building, number = 6)
guid.register(weapon.AmmoSlot.Box, number = 7)
val maxNtuCap = ant.Definition.MaxNtuCapacitor
player.Spawn()
ant.NtuCapacitor = maxNtuCap
ant.Actor = context.actorOf(Props(classOf[VehicleControl], ant), name = "test-ant")
ant.Zone = zone
ant.Seats(0).Occupant = player
ant.DeploymentState = DriveState.Deployed
building.Amenities = terminal
building.Amenities = silo
terminal.Actor = context.actorOf(Props(classOf[TerminalControl], terminal), name = "test-terminal")
terminal.Health = 1 //not yet destroyed, but one shot away from it
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 = DamageInteraction(
SourceEntry(terminal),
ProjectileReason(
DamageResolution.Hit,
projectile,
terminal.DamageModel
),
Vector3(1, 0, 0)
)
val applyDamageTo = resolved.calculate()
"AutoRepair" should {
"should activate and trade NTU from the silo; if the ANT stops depositing, auto-repair continues" in {
assert(silo.NtuCapacitor == 0)
assert(ant.NtuCapacitor == maxNtuCap)
assert(!terminal.Destroyed)
avatarProbe.expectNoMessage(max = 1000 milliseconds) //nothing
terminal.Actor ! Vitality.Damage(applyDamageTo)
while(avatarProbe.receiveOne(max = 1000 milliseconds) != null) { /* health loss event(s) + state updates */ }
assert(terminal.Destroyed)
avatarProbe.expectNoMessage(max = 1000 milliseconds) //nothing
silo.Actor ! CommonMessages.Use(player) //then ...
avatarProbe.receiveOne(max = 1000 milliseconds) //health update event
assert(terminal.Health < terminal.MaxHealth)
var i = 0 //safety counter
while(terminal.Health < terminal.MaxHealth && i < 10) {
i += 1
avatarProbe.receiveOne(max = 1000 milliseconds) //some health update events ...
}
ant.Actor ! Deployment.TryUndeploy(DriveState.Undeploying)
ant.Actor ! Deployment.TryUndeploy(DriveState.Mobile)
while( avatarProbe.receiveOne(max = 1000 milliseconds) != null ) { /* remainder of the messages */ }
val siloCapacitor = silo.NtuCapacitor
val antCapacitor = ant.NtuCapacitor
val termHealth = terminal.Health
assert(ant.DeploymentState == DriveState.Mobile)
assert(siloCapacitor > 0 && siloCapacitor < silo.MaxNtuCapacitor)
assert(antCapacitor > 0 && antCapacitor < maxNtuCap)
assert(termHealth > 0 && termHealth < terminal.MaxHealth)
while(terminal.Health < terminal.MaxHealth && i < 20) {
i += 1
avatarProbe.receiveOne(max = 1000 milliseconds) //some health update events ...
}
//while( avatarProbe.receiveOne(max = 1000 milliseconds) != null ) { /* remainder of the messages */ }
assert(siloCapacitor != silo.NtuCapacitor) //changing ...
assert(antCapacitor == ant.NtuCapacitor) //not supplying anymore
assert(terminal.Health > termHealth && terminal.Health <= terminal.MaxHealth) //still auto-repairing
println(s"Test '${testNames.head}' successful.")
}
}
}
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(1000 milliseconds)
val guid = new NumberPoolHub(new MaxNumberSource(max = 10))
val avatarProbe = new TestProbe(system)
val catchall = new TestProbe(system).ref
val zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {}
GUID(guid)
override def AvatarEvents = avatarProbe.ref
override def LocalEvents = catchall
override def VehicleEvents = catchall
override def Activity = catchall
}
val building = Building.Structure(StructureType.Tower)(name = "integ-twr-test-building", guid = 6, map_id = 0, zone, context)
building.Invalidate()
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)
terminal.Actor = context.actorOf(Props(classOf[TerminalControl], terminal), name = "test-terminal")
guid.register(player, number = 1)
guid.register(weapon, number = 2)
guid.register(weapon.AmmoSlot.Box, number = 3)
guid.register(terminal, number = 4)
guid.register(building, number = 6)
building.Amenities = terminal
building.Actor ! BuildingActor.SuppliedWithNtu() //artificial
building.Actor ! BuildingActor.PowerOn() //artificial
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 = DamageInteraction(
SourceEntry(terminal),
ProjectileReason(
DamageResolution.Hit,
projectile,
terminal.DamageModel
),
Vector3(1, 0, 0)
)
val applyDamageTo = resolved.calculate()
"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 = 500 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(200, 500, 500, 1)
RepairIfDestroyed = true
}
val slow_terminal_definition = new OrderTerminalDefinition(objId = 612) {
Name = "order_terminal"
MaxHealth = 500
Damageable = true
Repairable = true
autoRepair = AutoRepairStats(5, 500, 500, 1)
RepairIfDestroyed = true
}
}

View file

@ -1,5 +1,5 @@
// Copyright (c) 2020 PSForever // Copyright (c) 2020 PSForever
package objects package actor.objects
import akka.actor.Props import akka.actor.Props
import akka.testkit.TestProbe import akka.testkit.TestProbe
@ -78,10 +78,8 @@ class AutoRepairRequestNtuTest extends FreedContextActorTest {
assert(terminal.Health < terminal.MaxHealth) assert(terminal.Health < terminal.MaxHealth)
val buildingMsg = buildingProbe.receiveOne(max = 600 milliseconds) val buildingMsg = buildingProbe.receiveOne(max = 600 milliseconds)
assert(buildingMsg match { assert(buildingMsg match {
case BuildingActor.Ntu(NtuCommand.Request(drain, _)) => case BuildingActor.Ntu(NtuCommand.Request(_, _)) => true
drain == terminal.Definition.autoRepair.get.drain case _ => false
case _ =>
false
}) })
} }
} }
@ -142,11 +140,10 @@ class AutoRepairRequestNtuRepeatTest extends FreedContextActorTest {
(0 to 3).foreach { _ => (0 to 3).foreach { _ =>
val buildingMsg = buildingProbe.receiveOne(max = 1000 milliseconds) val buildingMsg = buildingProbe.receiveOne(max = 1000 milliseconds)
assert(buildingMsg match { assert(buildingMsg match {
case BuildingActor.Ntu(NtuCommand.Request(drain, _)) => case BuildingActor.Ntu(NtuCommand.Request(_, _)) => true
drain == terminal.Definition.autoRepair.get.drain case _ => false
case _ =>
false
}) })
terminal.Actor ! NtuCommand.Grant(null, 0)
} }
} }
} }
@ -268,10 +265,8 @@ class AutoRepairRestoreRequestNtuTest extends FreedContextActorTest {
terminal.Actor ! BuildingActor.SuppliedWithNtu() terminal.Actor ! BuildingActor.SuppliedWithNtu()
val buildingMsg = buildingProbe.receiveOne(max = 600 milliseconds) val buildingMsg = buildingProbe.receiveOne(max = 600 milliseconds)
assert(buildingMsg match { assert(buildingMsg match {
case BuildingActor.Ntu(NtuCommand.Request(drain, _)) => case BuildingActor.Ntu(NtuCommand.Request(_, _)) => true
drain == terminal.Definition.autoRepair.get.drain case _ => false
case _ =>
false
}) })
} }
} }

View file

@ -76,6 +76,12 @@ game {
# Command experience rate # Command experience rate
cep-rate = 1.0 cep-rate = 1.0
# Modify the amount of mending per autorepair tick for facility amenities
amenity-autorepair-rate = 1.0
# Modify the amount of NTU drain per autorepair tick for facility amenities
amenity-autorepair-drain-rate = 0.5
new-avatar { new-avatar {
# Starting battle rank # Starting battle rank
br = 1 br = 1

View file

@ -5,7 +5,7 @@ import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer}
import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy}
import akka.{actor => classic} import akka.{actor => classic}
import net.psforever.actors.commands.NtuCommand import net.psforever.actors.commands.NtuCommand
import net.psforever.objects.{CommonNtuContainer, NtuContainer} import net.psforever.objects.NtuContainer
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.generator.{Generator, GeneratorControl} import net.psforever.objects.serverobject.generator.{Generator, GeneratorControl}
import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType, WarpGate} import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType, WarpGate}
@ -449,7 +449,7 @@ class BuildingActor(
Behaviors.same Behaviors.same
case _ => case _ =>
//all other facilities require a storage silo for ntu //all other facilities require a storage silo for ntu
building.Amenities.find(_.isInstanceOf[NtuContainer]) match { building.NtuSource match {
case Some(ntuContainer) => case Some(ntuContainer) =>
ntuContainer.Actor ! msg //needs to redirect ntuContainer.Actor ! msg //needs to redirect
Behaviors.same Behaviors.same
@ -466,8 +466,9 @@ class BuildingActor(
class FakeNtuSource(private val building: Building) class FakeNtuSource(private val building: Building)
extends PlanetSideServerObject extends PlanetSideServerObject
with CommonNtuContainer { with NtuContainer {
override def NtuCapacitor = Float.MaxValue override def NtuCapacitor = Float.MaxValue
override def NtuCapacitor_=(a: Float) = Float.MaxValue
override def Faction = building.Faction override def Faction = building.Faction
override def Zone = building.Zone override def Zone = building.Zone
override def Definition = null override def Definition = null

View file

@ -7279,7 +7279,7 @@ object GlobalDefinitions {
order_terminal.MaxHealth = 500 order_terminal.MaxHealth = 500
order_terminal.Damageable = true order_terminal.Damageable = true
order_terminal.Repairable = true order_terminal.Repairable = true
order_terminal.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f) order_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
order_terminal.RepairIfDestroyed = true order_terminal.RepairIfDestroyed = true
order_terminal.Subtract.Damage1 = 8 order_terminal.Subtract.Damage1 = 8
@ -7343,7 +7343,7 @@ object GlobalDefinitions {
cert_terminal.MaxHealth = 500 cert_terminal.MaxHealth = 500
cert_terminal.Damageable = true cert_terminal.Damageable = true
cert_terminal.Repairable = true cert_terminal.Repairable = true
cert_terminal.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f) cert_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
cert_terminal.RepairIfDestroyed = true cert_terminal.RepairIfDestroyed = true
cert_terminal.Subtract.Damage1 = 8 cert_terminal.Subtract.Damage1 = 8
@ -7351,7 +7351,7 @@ object GlobalDefinitions {
implant_terminal_mech.MaxHealth = 1500 //TODO 1000; right now, 1000 (mech) + 500 (interface) implant_terminal_mech.MaxHealth = 1500 //TODO 1000; right now, 1000 (mech) + 500 (interface)
implant_terminal_mech.Damageable = true implant_terminal_mech.Damageable = true
implant_terminal_mech.Repairable = true implant_terminal_mech.Repairable = true
implant_terminal_mech.autoRepair = AutoRepairStats(1, 5000, 2400, 0.5f) implant_terminal_mech.autoRepair = AutoRepairStats(1.6f, 5000, 2400, 0.5f) //ori. 1, 5000, 2400, 0.5f
implant_terminal_mech.RepairIfDestroyed = true implant_terminal_mech.RepairIfDestroyed = true
implant_terminal_interface.Name = "implant_terminal_interface" implant_terminal_interface.Name = "implant_terminal_interface"
@ -7371,7 +7371,7 @@ object GlobalDefinitions {
ground_vehicle_terminal.MaxHealth = 500 ground_vehicle_terminal.MaxHealth = 500
ground_vehicle_terminal.Damageable = true ground_vehicle_terminal.Damageable = true
ground_vehicle_terminal.Repairable = true ground_vehicle_terminal.Repairable = true
ground_vehicle_terminal.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f) ground_vehicle_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
ground_vehicle_terminal.RepairIfDestroyed = true ground_vehicle_terminal.RepairIfDestroyed = true
ground_vehicle_terminal.Subtract.Damage1 = 8 ground_vehicle_terminal.Subtract.Damage1 = 8
@ -7384,7 +7384,7 @@ object GlobalDefinitions {
air_vehicle_terminal.MaxHealth = 500 air_vehicle_terminal.MaxHealth = 500
air_vehicle_terminal.Damageable = true air_vehicle_terminal.Damageable = true
air_vehicle_terminal.Repairable = true air_vehicle_terminal.Repairable = true
air_vehicle_terminal.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f) air_vehicle_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
air_vehicle_terminal.RepairIfDestroyed = true air_vehicle_terminal.RepairIfDestroyed = true
air_vehicle_terminal.Subtract.Damage1 = 8 air_vehicle_terminal.Subtract.Damage1 = 8
@ -7397,7 +7397,7 @@ object GlobalDefinitions {
dropship_vehicle_terminal.MaxHealth = 500 dropship_vehicle_terminal.MaxHealth = 500
dropship_vehicle_terminal.Damageable = true dropship_vehicle_terminal.Damageable = true
dropship_vehicle_terminal.Repairable = true dropship_vehicle_terminal.Repairable = true
dropship_vehicle_terminal.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f) dropship_vehicle_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
dropship_vehicle_terminal.RepairIfDestroyed = true dropship_vehicle_terminal.RepairIfDestroyed = true
dropship_vehicle_terminal.Subtract.Damage1 = 8 dropship_vehicle_terminal.Subtract.Damage1 = 8
@ -7410,7 +7410,7 @@ object GlobalDefinitions {
vehicle_terminal_combined.MaxHealth = 500 vehicle_terminal_combined.MaxHealth = 500
vehicle_terminal_combined.Damageable = true vehicle_terminal_combined.Damageable = true
vehicle_terminal_combined.Repairable = true vehicle_terminal_combined.Repairable = true
vehicle_terminal_combined.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f) vehicle_terminal_combined.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
vehicle_terminal_combined.RepairIfDestroyed = true vehicle_terminal_combined.RepairIfDestroyed = true
vehicle_terminal_combined.Subtract.Damage1 = 8 vehicle_terminal_combined.Subtract.Damage1 = 8
@ -7423,7 +7423,7 @@ object GlobalDefinitions {
vanu_air_vehicle_term.MaxHealth = 500 vanu_air_vehicle_term.MaxHealth = 500
vanu_air_vehicle_term.Damageable = true vanu_air_vehicle_term.Damageable = true
vanu_air_vehicle_term.Repairable = true vanu_air_vehicle_term.Repairable = true
vanu_air_vehicle_term.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f) vanu_air_vehicle_term.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
vanu_air_vehicle_term.RepairIfDestroyed = true vanu_air_vehicle_term.RepairIfDestroyed = true
vanu_air_vehicle_term.Subtract.Damage1 = 8 vanu_air_vehicle_term.Subtract.Damage1 = 8
@ -7436,7 +7436,7 @@ object GlobalDefinitions {
vanu_vehicle_term.MaxHealth = 500 vanu_vehicle_term.MaxHealth = 500
vanu_vehicle_term.Damageable = true vanu_vehicle_term.Damageable = true
vanu_vehicle_term.Repairable = true vanu_vehicle_term.Repairable = true
vanu_vehicle_term.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f) vanu_vehicle_term.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
vanu_vehicle_term.RepairIfDestroyed = true vanu_vehicle_term.RepairIfDestroyed = true
vanu_vehicle_term.Subtract.Damage1 = 8 vanu_vehicle_term.Subtract.Damage1 = 8
@ -7449,7 +7449,7 @@ object GlobalDefinitions {
bfr_terminal.MaxHealth = 500 bfr_terminal.MaxHealth = 500
bfr_terminal.Damageable = true bfr_terminal.Damageable = true
bfr_terminal.Repairable = true bfr_terminal.Repairable = true
bfr_terminal.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f) bfr_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
bfr_terminal.RepairIfDestroyed = true bfr_terminal.RepairIfDestroyed = true
bfr_terminal.Subtract.Damage1 = 8 bfr_terminal.Subtract.Damage1 = 8
@ -7460,17 +7460,18 @@ object GlobalDefinitions {
respawn_tube.Damageable = true respawn_tube.Damageable = true
respawn_tube.DamageableByFriendlyFire = false respawn_tube.DamageableByFriendlyFire = false
respawn_tube.Repairable = true respawn_tube.Repairable = true
respawn_tube.autoRepair = AutoRepairStats(1, 10000, 2400, 1) respawn_tube.autoRepair = AutoRepairStats(1.6f, 10000, 2400, 1) //orig. 1, 10000, 2400, 1
respawn_tube.RepairIfDestroyed = true respawn_tube.RepairIfDestroyed = true
respawn_tube.Subtract.Damage1 = 8 respawn_tube.Subtract.Damage1 = 8
respawn_tube_sanctuary.Name = "respawn_tube" respawn_tube_sanctuary.Name = "respawn_tube"
respawn_tube_sanctuary.Delay = 10 respawn_tube_sanctuary.Delay = 10
respawn_tube_sanctuary.SpecificPointFunc = SpawnPoint.Default respawn_tube_sanctuary.SpecificPointFunc = SpawnPoint.Default
respawn_tube_sanctuary.MaxHealth = 1000
respawn_tube_sanctuary.Damageable = false //true? respawn_tube_sanctuary.Damageable = false //true?
respawn_tube_sanctuary.DamageableByFriendlyFire = false respawn_tube_sanctuary.DamageableByFriendlyFire = false
respawn_tube_sanctuary.Repairable = true respawn_tube_sanctuary.Repairable = true
respawn_tube_sanctuary.autoRepair = AutoRepairStats(1, 10000, 2400, 1) respawn_tube_sanctuary.autoRepair = AutoRepairStats(1.6f, 10000, 2400, 1) //orig. 1, 10000, 2400, 1
respawn_tube_tower.Name = "respawn_tube_tower" respawn_tube_tower.Name = "respawn_tube_tower"
respawn_tube_tower.Delay = 10 respawn_tube_tower.Delay = 10
@ -7479,7 +7480,7 @@ object GlobalDefinitions {
respawn_tube_tower.Damageable = true respawn_tube_tower.Damageable = true
respawn_tube_tower.DamageableByFriendlyFire = false respawn_tube_tower.DamageableByFriendlyFire = false
respawn_tube_tower.Repairable = true respawn_tube_tower.Repairable = true
respawn_tube_tower.autoRepair = AutoRepairStats(1, 10000, 2400, 1) respawn_tube_tower.autoRepair = AutoRepairStats(1.6f, 10000, 2400, 1) //orig. 1, 10000, 2400, 1
respawn_tube_tower.RepairIfDestroyed = true respawn_tube_tower.RepairIfDestroyed = true
respawn_tube_tower.Subtract.Damage1 = 8 respawn_tube_tower.Subtract.Damage1 = 8
@ -7497,7 +7498,7 @@ object GlobalDefinitions {
medical_terminal.MaxHealth = 500 medical_terminal.MaxHealth = 500
medical_terminal.Damageable = true medical_terminal.Damageable = true
medical_terminal.Repairable = true medical_terminal.Repairable = true
medical_terminal.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f) medical_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
medical_terminal.RepairIfDestroyed = true medical_terminal.RepairIfDestroyed = true
adv_med_terminal.Name = "adv_med_terminal" adv_med_terminal.Name = "adv_med_terminal"
@ -7509,7 +7510,7 @@ object GlobalDefinitions {
adv_med_terminal.MaxHealth = 750 adv_med_terminal.MaxHealth = 750
adv_med_terminal.Damageable = true adv_med_terminal.Damageable = true
adv_med_terminal.Repairable = true adv_med_terminal.Repairable = true
adv_med_terminal.autoRepair = AutoRepairStats(1, 5000, 2400, 0.5f) adv_med_terminal.autoRepair = AutoRepairStats(1.57894f, 5000, 2400, 0.5f) //orig. 1, 5000, 2400, 0.5f
adv_med_terminal.RepairIfDestroyed = true adv_med_terminal.RepairIfDestroyed = true
crystals_health_a.Name = "crystals_health_a" crystals_health_a.Name = "crystals_health_a"
@ -7537,7 +7538,7 @@ object GlobalDefinitions {
portable_med_terminal.MaxHealth = 500 portable_med_terminal.MaxHealth = 500
portable_med_terminal.Damageable = false //TODO actually true portable_med_terminal.Damageable = false //TODO actually true
portable_med_terminal.Repairable = false portable_med_terminal.Repairable = false
portable_med_terminal.autoRepair = AutoRepairStats(1, 5000, 3500, 0.5f) portable_med_terminal.autoRepair = AutoRepairStats(2.24215f, 5000, 3500, 0.5f) //orig. 1, 5000, 3500, 0.5f
pad_landing_frame.Name = "pad_landing_frame" pad_landing_frame.Name = "pad_landing_frame"
pad_landing_frame.Interval = 1000 pad_landing_frame.Interval = 1000
@ -7651,7 +7652,7 @@ object GlobalDefinitions {
manned_turret.Damageable = true manned_turret.Damageable = true
manned_turret.DamageDisablesAt = 0 manned_turret.DamageDisablesAt = 0
manned_turret.Repairable = true manned_turret.Repairable = true
manned_turret.autoRepair = AutoRepairStats(1, 10000, 1600, 0.5f) manned_turret.autoRepair = AutoRepairStats(1.0909f, 10000, 1600, 0.5f) //orig. 1, 10000, 1600, 0.5f
manned_turret.RepairIfDestroyed = true manned_turret.RepairIfDestroyed = true
manned_turret.Weapons += 1 -> new mutable.HashMap() manned_turret.Weapons += 1 -> new mutable.HashMap()
manned_turret.Weapons(1) += TurretUpgrade.None -> phalanx_sgl_hevgatcan manned_turret.Weapons(1) += TurretUpgrade.None -> phalanx_sgl_hevgatcan
@ -7675,7 +7676,7 @@ object GlobalDefinitions {
vanu_sentry_turret.Damageable = true vanu_sentry_turret.Damageable = true
vanu_sentry_turret.DamageDisablesAt = 0 vanu_sentry_turret.DamageDisablesAt = 0
vanu_sentry_turret.Repairable = true vanu_sentry_turret.Repairable = true
vanu_sentry_turret.autoRepair = AutoRepairStats(3, 10000, 1000, 0.5f) vanu_sentry_turret.autoRepair = AutoRepairStats(3.27272f, 10000, 1000, 0.5f) //orig. 3, 10000, 1000, 0.5f
vanu_sentry_turret.RepairIfDestroyed = true vanu_sentry_turret.RepairIfDestroyed = true
vanu_sentry_turret.Weapons += 1 -> new mutable.HashMap() vanu_sentry_turret.Weapons += 1 -> new mutable.HashMap()
vanu_sentry_turret.Weapons(1) += TurretUpgrade.None -> vanu_sentry_turret_weapon vanu_sentry_turret.Weapons(1) += TurretUpgrade.None -> vanu_sentry_turret_weapon
@ -7757,7 +7758,7 @@ object GlobalDefinitions {
generator.Damageable = true generator.Damageable = true
generator.DamageableByFriendlyFire = false generator.DamageableByFriendlyFire = false
generator.Repairable = true generator.Repairable = true
generator.autoRepair = AutoRepairStats(1, 5000, 875, 1) generator.autoRepair = AutoRepairStats(0.77775f, 5000, 875, 1) //orig. 1, 5000, 875, 1
generator.RepairDistance = 13.5f generator.RepairDistance = 13.5f
generator.RepairIfDestroyed = true generator.RepairIfDestroyed = true
generator.Subtract.Damage1 = 9 generator.Subtract.Damage1 = 9

View file

@ -9,6 +9,7 @@ import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.{Default, NtuContainer, NtuStorageBehavior} import net.psforever.objects.{Default, NtuContainer, NtuStorageBehavior}
import net.psforever.objects.serverobject.damage.Damageable import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.serverobject.structures.{Amenity, AutoRepairStats, Building} import net.psforever.objects.serverobject.structures.{Amenity, AutoRepairStats, Building}
import net.psforever.util.Config
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -38,6 +39,14 @@ trait AmenityAutoRepair
private var autoRepairStartFunc: ()=>Unit = startAutoRepairIfStopped private var autoRepairStartFunc: ()=>Unit = startAutoRepairIfStopped
/** the timer for requests for auto-repair-actionable resource deposits (NTU) */ /** the timer for requests for auto-repair-actionable resource deposits (NTU) */
private var autoRepairTimer: Cancellable = Default.Cancellable private var autoRepairTimer: Cancellable = Default.Cancellable
/** indicate the current state of the task assignment;
* `None` means no auto-repair operations;
* `Some(0L)` means previous auto-repair task completed;
* `Some(time)` means that an auto-repair task is or was queued to occur at `time` */
private var autoRepairQueueTask: Option[Long] = None
/** repair can only occur in integer increments, so any non-integer portion of incremental repairs accumulates;
* once above a whole number, that number is extracted and applied to the base repair value */
private var autoRepairOverflow: Float = 0f
def AutoRepairObject: Amenity def AutoRepairObject: Amenity
@ -56,7 +65,7 @@ trait AmenityAutoRepair
* Stop the auto-repair timer. * Stop the auto-repair timer.
*/ */
def StopNtuBehavior(sender : ActorRef) : Unit = { def StopNtuBehavior(sender : ActorRef) : Unit = {
autoRepairTimer.cancel() stopAutoRepair()
} }
//nothing special //nothing special
@ -73,7 +82,24 @@ trait AmenityAutoRepair
val obj = AutoRepairObject val obj = AutoRepairObject
obj.Definition.autoRepair match { obj.Definition.autoRepair match {
case Some(repair : AutoRepairStats) if obj.Health < obj.Definition.MaxHealth => case Some(repair : AutoRepairStats) if obj.Health < obj.Definition.MaxHealth =>
PerformRepairs(obj, repair.amount) autoRepairTimer.cancel()
val modifiedRepairAmount = repair.amount * Config.app.game.amenityAutorepairRate
val wholeRepairAmount = modifiedRepairAmount.toInt
val overflowRepairAmount = modifiedRepairAmount - wholeRepairAmount
val finalRepairAmount = if (autoRepairOverflow + overflowRepairAmount < 1) {
autoRepairOverflow += overflowRepairAmount
wholeRepairAmount
} else {
val totalOverflow = autoRepairOverflow + overflowRepairAmount
val wholeOverflow = totalOverflow.toInt
autoRepairOverflow = totalOverflow - wholeOverflow
wholeRepairAmount + wholeOverflow
}
PerformRepairs(obj, finalRepairAmount)
val currentTime = System.currentTimeMillis()
val taskTime = currentTime - autoRepairQueueTask.getOrElse(currentTime)
autoRepairQueueTask = Some(0L)
trySetupAutoRepairSubsequent(taskTime)
case _ => case _ =>
StopNtuBehavior(sender) StopNtuBehavior(sender)
} }
@ -101,7 +127,7 @@ trait AmenityAutoRepair
* Set a function that will attempt auto-repair operations under specific trigger-able conditions (damage). * Set a function that will attempt auto-repair operations under specific trigger-able conditions (damage).
*/ */
private def startAutoRepairFunctionality(): Unit = { private def startAutoRepairFunctionality(): Unit = {
retimeAutoRepair() trySetupAutoRepairInitial()
autoRepairStartFunc = startAutoRepairIfStopped autoRepairStartFunc = startAutoRepairIfStopped
} }
@ -112,17 +138,39 @@ trait AmenityAutoRepair
* @see `stopAutoRepair` * @see `stopAutoRepair`
*/ */
private def stopAutoRepairFunctionality(): Unit = { private def stopAutoRepairFunctionality(): Unit = {
autoRepairTimer.cancel() stopAutoRepair()
autoRepairStartFunc = ()=>{} autoRepairStartFunc = ()=>{}
} }
/** /**
* Attempt to start auto-repair operation * Attempt to start auto-repair operation
* only if no operation is currently being processed. * only if no operation is currently being processed
* or if the current process has stalled.
*/ */
private def startAutoRepairIfStopped(): Unit = { private def startAutoRepairIfStopped(): Unit = {
if(autoRepairTimer.isCancelled) { if(autoRepairQueueTask.isEmpty || stallDetection(stallTime = 15000L)) {
retimeAutoRepair() trySetupAutoRepairInitial()
}
}
/**
* Detect if the auto-repair system is in a stalled state where orders are not being dispatched when they should.
* Not running or not being expected to be running does not count as being stalled.
* @param stallTime for how long we need to be stalled (ms)
* @return `true`, if stalled;
* `false`, otherwise
*/
private def stallDetection(stallTime: Long): Boolean = {
autoRepairQueueTask match {
case Some(0L) =>
//the last auto-repair request was completed; did we start the next one?
autoRepairTimer.isCancelled
case Some(time) =>
//waiting for too long on an active auto-repair request
time + stallTime > System.currentTimeMillis()
case None =>
//we've not stalled; we're just not running
false
} }
} }
@ -148,9 +196,9 @@ trait AmenityAutoRepair
* `false`, if it was already started, or did not start * `false`, if it was already started, or did not start
*/ */
final def actuallyTryAutoRepair(): Boolean = { final def actuallyTryAutoRepair(): Boolean = {
val before = autoRepairTimer.isCancelled val before = autoRepairQueueTask.isEmpty
autoRepairStartFunc() autoRepairStartFunc()
!(before || autoRepairTimer.isCancelled) !(before || autoRepairQueueTask.isEmpty)
} }
/** /**
@ -161,51 +209,77 @@ trait AmenityAutoRepair
*/ */
final def stopAutoRepair(): Unit = { final def stopAutoRepair(): Unit = {
autoRepairTimer.cancel() autoRepairTimer.cancel()
autoRepairOverflow = 0
autoRepairQueueTask = None
} }
/** /**
* As long as setup information regarding the auto-repair process can be discovered in the amenity's definition * 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, * and the amenity actually requires repairs to be performed,
* perform the setup for the auto-repair operation. * perform the setup for the auto-repair operation.
* This is the initial delay before the first repair attempt.
*/ */
private def retimeAutoRepair(): Unit = { private def trySetupAutoRepairInitial(): Unit = {
val obj = AutoRepairObject val obj = AutoRepairObject
obj.Definition.autoRepair match { obj.Definition.autoRepair match {
case Some(AutoRepairStats(_, start, interval, drain)) if obj.Health < obj.Definition.MaxHealth => case Some(AutoRepairStats(_, start, _, drain)) if obj.Health < obj.Definition.MaxHealth =>
retimeAutoRepair(start, interval, drain) setupAutoRepair(start, drain)
case _ => ; case _ =>
stopAutoRepair()
} }
} }
/** /**
* As long as setup information regarding the auto-repair process can be provided, * As long as setup information regarding the auto-repair process can be discovered in the amenity's definition
* and the amenity actually requires repairs to be performed,
* perform the setup for the auto-repair operation. * perform the setup for the auto-repair operation.
* @see `BuildingActor.Ntu` * This is the delay before every subsequent repair attempt.
* @see `NtuCommand.Request` * @param delayOffset an adjustment to the normal delay applied to the subsequent operation (ms);
* @see `scheduleWithFixedDelay` * ideally, some number that's inclusive to 0 and the interval
* @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 = { private def trySetupAutoRepairSubsequent(delayOffset: Long): Unit = {
if (autoRepairQueueTask.contains(0L)) {
val obj = AutoRepairObject
obj.Definition.autoRepair match {
case Some(AutoRepairStats(_, _, interval, drain)) if obj.Health < obj.Definition.MaxHealth =>
setupAutoRepair(
math.min(interval, math.max(0L, interval - delayOffset)),
drain
)
case _ =>
stopAutoRepair()
}
}
}
/**
* 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 `scheduleOnce`
* @param delay the delay before the message is sent (ms)
* @param drain the amount of NTU being levied as a cost for auto-repair operation
* (the responding entity determines how to satisfy this cost)
*/
private def setupAutoRepair(delay: Long, drain: Float): Unit = {
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
autoRepairTimer.cancel() autoRepairTimer.cancel()
autoRepairQueueTask = Some(System.currentTimeMillis() + delay)
val modifiedDrain = drain * Config.app.game.amenityAutorepairDrainRate
autoRepairTimer = if(AutoRepairObject.Owner == Building.NoBuilding) { autoRepairTimer = if(AutoRepairObject.Owner == Building.NoBuilding) {
//without an owner, auto-repair freely //without an owner, auto-repair freely
context.system.scheduler.scheduleWithFixedDelay( context.system.scheduler.scheduleOnce(
initialDelay milliseconds,
delay milliseconds, delay milliseconds,
self, self,
NtuCommand.Grant(null, drain) NtuCommand.Grant(null, modifiedDrain)
) )
} else { } else {
//ask //ask politely
context.system.scheduler.scheduleWithFixedDelay( context.system.scheduler.scheduleOnce(
initialDelay milliseconds,
delay milliseconds, delay milliseconds,
AutoRepairObject.Owner.Actor, AutoRepairObject.Owner.Actor,
BuildingActor.Ntu(NtuCommand.Request(drain, ntuGrantActorRef)) BuildingActor.Ntu(NtuCommand.Request(modifiedDrain, ntuGrantActorRef))
) )
} }
} }

View file

@ -24,7 +24,15 @@ class ResourceSilo extends Amenity with CommonNtuContainer {
LowNtuWarningOn LowNtuWarningOn
} }
def CapacitorDisplay : Long = scala.math.ceil((NtuCapacitor / MaxNtuCapacitor) * 10).toInt def CapacitorDisplay : Long = {
if(NtuCapacitor == 0) {
0
} else if(NtuCapacitor <= 0.1f * MaxNtuCapacitor) {
1
} else {
((NtuCapacitor / MaxNtuCapacitor) * 10).toInt
}
}
def Definition: ResourceSiloDefinition = GlobalDefinitions.resource_silo def Definition: ResourceSiloDefinition = GlobalDefinitions.resource_silo

View file

@ -12,6 +12,7 @@ import net.psforever.types.PlanetSideEmpire
import net.psforever.services.Service import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.util.Config
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -28,7 +29,7 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
def FactionObject: FactionAffinity = resourceSilo def FactionObject: FactionAffinity = resourceSilo
private[this] val log = org.log4s.getLogger private[this] val log = org.log4s.getLogger
var panelAnimationFunc: Float => Unit = PanelAnimation var panelAnimationFunc: (ActorRef, Float) => Unit = PanelAnimation
def receive: Receive = { def receive: Receive = {
case "startup" => case "startup" =>
@ -138,7 +139,7 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
*/ */
def StopNtuBehavior(sender: ActorRef): Unit = { def StopNtuBehavior(sender: ActorRef): Unit = {
panelAnimationFunc = PanelAnimation panelAnimationFunc = PanelAnimation
panelAnimationFunc(0) panelAnimationFunc(sender, 0)
} }
/** /**
@ -150,7 +151,7 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
*/ */
def HandleNtuRequest(sender: ActorRef, min: Float, max: Float): Unit = { def HandleNtuRequest(sender: ActorRef, min: Float, max: Float): Unit = {
val originalAmount = resourceSilo.NtuCapacitor val originalAmount = resourceSilo.NtuCapacitor
UpdateChargeLevel(-min) UpdateChargeLevel(-(min * Config.app.game.amenityAutorepairDrainRate))
sender ! Ntu.Grant(resourceSilo, originalAmount - resourceSilo.NtuCapacitor) sender ! Ntu.Grant(resourceSilo, originalAmount - resourceSilo.NtuCapacitor)
} }
@ -159,9 +160,7 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
*/ */
def HandleNtuGrant(sender: ActorRef, src: NtuContainer, amount: Float): Unit = { def HandleNtuGrant(sender: ActorRef, src: NtuContainer, amount: Float): Unit = {
if (amount != 0) { if (amount != 0) {
val originalAmount = resourceSilo.NtuCapacitor panelAnimationFunc(sender, amount)
UpdateChargeLevel(amount)
panelAnimationFunc(resourceSilo.NtuCapacitor - originalAmount)
panelAnimationFunc = SkipPanelAnimation panelAnimationFunc = SkipPanelAnimation
} }
} }
@ -174,16 +173,28 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
* @param trigger if positive, activate the animation; * @param trigger if positive, activate the animation;
* if negative or zero, disable the animation * if negative or zero, disable the animation
*/ */
def PanelAnimation(trigger: Float): Unit = { def PanelAnimation(source: ActorRef, trigger: Float): Unit = {
val zone = resourceSilo.Zone val zone = resourceSilo.Zone
zone.VehicleEvents ! VehicleServiceMessage( zone.VehicleEvents ! VehicleServiceMessage(
zone.id, zone.id,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, resourceSilo.GUID, 49, if (trigger > 0) 1 else 0) VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, resourceSilo.GUID, 49, if (trigger > 0) 1 else 0)
) // panel glow on & orb particles on ) // panel glow & orb particles
// do not let the trigger charge go to waste, but also do not let the silo be filled
// attempting to return it to the source may sabotage an ongoing transfer process
val amount = math.min(resourceSilo.MaxNtuCapacitor - resourceSilo.NtuCapacitor, trigger)
UpdateChargeLevel(amount - amount*0.1f)
} }
/** /**
* Do nothing this turn. * Update the charge level and decide if the silo is full.
* Announce that full-ness to the NTU source.
* Although called "Skip", an animation that broadcasts the transfer process should be ongoing at the moment.
*/ */
def SkipPanelAnimation(trigger: Float): Unit = {} def SkipPanelAnimation(source: ActorRef, trigger: Float): Unit = {
UpdateChargeLevel(trigger)
// immediate termination of ntu requests
if (resourceSilo.NtuCapacitor == resourceSilo.MaxNtuCapacitor) {
source ! Ntu.Request(0, 0)
}
}
} }

View file

@ -7,7 +7,7 @@ import net.psforever.objects.vital._
import net.psforever.objects.vital.resistance.ResistanceProfileMutators import net.psforever.objects.vital.resistance.ResistanceProfileMutators
import net.psforever.objects.vital.resolution.DamageResistanceModel import net.psforever.objects.vital.resolution.DamageResistanceModel
final case class AutoRepairStats(amount: Int, start: Long, repeat: Long, drain: Float) final case class AutoRepairStats(amount: Float, start: Long, repeat: Long, drain: Float)
abstract class AmenityDefinition(objectId: Int) abstract class AmenityDefinition(objectId: Int)
extends ObjectDefinition(objectId) extends ObjectDefinition(objectId)

View file

@ -5,7 +5,7 @@ import java.util.concurrent.TimeUnit
import akka.actor.ActorContext import akka.actor.ActorContext
import net.psforever.actors.zone.BuildingActor import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.{GlobalDefinitions, Player} import net.psforever.objects.{GlobalDefinitions, NtuContainer, Player}
import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.serverobject.generator.Generator import net.psforever.objects.serverobject.generator.Generator
import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.hackable.Hackable
@ -92,6 +92,13 @@ class Building(
} }
} }
def NtuSource: Option[NtuContainer] = {
Amenities.find(_.isInstanceOf[NtuContainer]) match {
case Some(o: NtuContainer) => Some(o)
case _ => None
}
}
def NtuLevel: Int = { def NtuLevel: Int = {
//if we have a silo, get the NTU level //if we have a silo, get the NTU level
Amenities.find(_.Definition == GlobalDefinitions.resource_silo) match { Amenities.find(_.Definition == GlobalDefinitions.resource_silo) match {

View file

@ -69,6 +69,7 @@ trait TransferBehavior {
def TryStopChargingEvent(container : TransferContainer) : Unit = { def TryStopChargingEvent(container : TransferContainer) : Unit = {
transferEvent = TransferBehavior.Event.None transferEvent = TransferBehavior.Event.None
transferTarget match { transferTarget match {
case Some(_: net.psforever.objects.serverobject.structures.WarpGate) => ;
case Some(obj) => case Some(obj) =>
obj.Actor ! TransferBehavior.Stopping() obj.Actor ! TransferBehavior.Stopping()
case _ => ; case _ => ;

View file

@ -124,6 +124,8 @@ case class SessionConfig(
case class GameConfig( case class GameConfig(
instantActionAms: Boolean, instantActionAms: Boolean,
amenityAutorepairRate: Float,
amenityAutorepairDrainRate: Float,
bepRate: Double, bepRate: Double,
cepRate: Double, cepRate: Double,
newAvatar: NewAvatar newAvatar: NewAvatar

View file

@ -1,169 +0,0 @@
// Copyright (c) 2020 PSForever
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, 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.base.DamageResolution
import net.psforever.objects.vital.damage.DamageProfile
import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.vital.projectile.ProjectileReason
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(1000 milliseconds)
val guid = new NumberPoolHub(new MaxNumberSource(max = 10))
val avatarProbe = new TestProbe(system)
val zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {}
GUID(guid)
override def AvatarEvents = avatarProbe.ref
}
val building = Building.Structure(StructureType.Facility)(name = "integ-fac-test-building", guid = 6, map_id = 0, zone, context)
building.Invalidate()
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()
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)
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 = DamageInteraction(
SourceEntry(terminal),
ProjectileReason(
DamageResolution.Hit,
projectile,
terminal.DamageModel
),
Vector3(1, 0, 0)
)
val applyDamageTo = resolved.calculate()
"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 = 1000 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(1000 milliseconds)
val guid = new NumberPoolHub(new MaxNumberSource(max = 10))
val avatarProbe = new TestProbe(system)
val zone = new Zone("test", new ZoneMap("test"), 0) {
override def SetupNumberPools() = {}
GUID(guid)
override def AvatarEvents = avatarProbe.ref
}
val building = Building.Structure(StructureType.Tower)(name = "integ-twr-test-building", guid = 6, map_id = 0, zone, context)
building.Invalidate()
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)
terminal.Actor = context.actorOf(Props(classOf[TerminalControl], terminal), name = "test-terminal")
guid.register(player, number = 1)
guid.register(weapon, number = 2)
guid.register(weapon.AmmoSlot.Box, number = 3)
guid.register(terminal, number = 4)
guid.register(building, number = 6)
building.Amenities = terminal
building.Actor ! BuildingActor.SuppliedWithNtu() //artificial
building.Actor ! BuildingActor.PowerOn() //artificial
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 = DamageInteraction(
SourceEntry(terminal),
ProjectileReason(
DamageResolution.Hit,
projectile,
terminal.DamageModel
),
Vector3(1, 0, 0)
)
val applyDamageTo = resolved.calculate()
"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(200, 500, 500, 1)
RepairIfDestroyed = true
}
}

View file

@ -276,9 +276,9 @@ class ResourceSiloControlUpdate1Test extends ActorTest {
val reply1 = zoneEvents.receiveOne(500 milliseconds) val reply1 = zoneEvents.receiveOne(500 milliseconds)
val reply2 = buildingEvents.receiveOne(500 milliseconds) val reply2 = buildingEvents.receiveOne(500 milliseconds)
assert(obj.NtuCapacitor == 305) assert(obj.NtuCapacitor == 305)
assert(obj.CapacitorDisplay == 4) assert(obj.CapacitorDisplay == 3)
assert(reply1 match { assert(reply1 match {
case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(1), 45, 4)) => true case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(1), 45, 3)) => true
case _ => false case _ => false
}) })
assert(reply2.isInstanceOf[BuildingActor.MapUpdate]) assert(reply2.isInstanceOf[BuildingActor.MapUpdate])
@ -321,7 +321,7 @@ class ResourceSiloControlUpdate2Test extends ActorTest {
val reply1 = zoneEvents.receiveOne(1000 milliseconds) val reply1 = zoneEvents.receiveOne(1000 milliseconds)
val reply2 = buildingEvents.receiveOne(1000 milliseconds) val reply2 = buildingEvents.receiveOne(1000 milliseconds)
assert(obj.NtuCapacitor == 205) assert(obj.NtuCapacitor == 205)
assert(obj.CapacitorDisplay == 3) assert(obj.CapacitorDisplay == 2)
assert(reply1.isInstanceOf[AvatarServiceMessage]) assert(reply1.isInstanceOf[AvatarServiceMessage])
assert(reply1.asInstanceOf[AvatarServiceMessage].forChannel == "nowhere") assert(reply1.asInstanceOf[AvatarServiceMessage].forChannel == "nowhere")
assert(reply1.asInstanceOf[AvatarServiceMessage].actionMessage.isInstanceOf[AvatarAction.PlanetsideAttribute]) assert(reply1.asInstanceOf[AvatarServiceMessage].actionMessage.isInstanceOf[AvatarAction.PlanetsideAttribute])
@ -344,7 +344,7 @@ class ResourceSiloControlUpdate2Test extends ActorTest {
.asInstanceOf[AvatarServiceMessage] .asInstanceOf[AvatarServiceMessage]
.actionMessage .actionMessage
.asInstanceOf[AvatarAction.PlanetsideAttribute] .asInstanceOf[AvatarAction.PlanetsideAttribute]
.attribute_value == 3 .attribute_value == 2
) )
assert(reply2.isInstanceOf[BuildingActor.MapUpdate]) assert(reply2.isInstanceOf[BuildingActor.MapUpdate])
@ -400,18 +400,16 @@ class ResourceSiloControlNoUpdateTest extends ActorTest {
obj.NtuCapacitor = 250 obj.NtuCapacitor = 250
obj.LowNtuWarningOn = false obj.LowNtuWarningOn = false
assert(obj.NtuCapacitor == 250) assert(obj.NtuCapacitor == 250)
assert(obj.CapacitorDisplay == 3) assert(obj.CapacitorDisplay == 2)
assert(!obj.LowNtuWarningOn) assert(!obj.LowNtuWarningOn)
obj.Actor ! ResourceSilo.UpdateChargeLevel(50) obj.Actor ! ResourceSilo.UpdateChargeLevel(49)
expectNoMessage(500 milliseconds) expectNoMessage(500 milliseconds)
zoneEvents.expectNoMessage(500 milliseconds) zoneEvents.expectNoMessage(500 milliseconds)
buildingEvents.expectNoMessage(500 milliseconds) assert(obj.CapacitorDisplay == 2)
assert( assert(obj.NtuCapacitor < 300)
obj.NtuCapacitor == 299 || obj.NtuCapacitor == 300
) // Just in case the capacitor level drops while waiting for the message check 299 & 300
assert(obj.CapacitorDisplay == 3)
assert(!obj.LowNtuWarningOn) assert(!obj.LowNtuWarningOn)
buildingEvents.expectNoMessage(500 milliseconds)
} }
} }
} }