diff --git a/src/main/scala/net/psforever/actors/commands/NtuCommand.scala b/src/main/scala/net/psforever/actors/commands/NtuCommand.scala index 441274d9..94398c34 100644 --- a/src/main/scala/net/psforever/actors/commands/NtuCommand.scala +++ b/src/main/scala/net/psforever/actors/commands/NtuCommand.scala @@ -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 } diff --git a/src/main/scala/net/psforever/actors/session/ChatActor.scala b/src/main/scala/net/psforever/actors/session/ChatActor.scala index 6574ddf5..9a1c2719 100644 --- a/src/main/scala/net/psforever/actors/session/ChatActor.scala +++ b/src/main/scala/net/psforever/actors/session/ChatActor.scala @@ -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 [|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 diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index 509a77ea..d81fb124 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -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 diff --git a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala index b1ddc8c0..60b12795 100644 --- a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala +++ b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala @@ -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 } diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 428cf665..48d1dcc5 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -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 } - } diff --git a/src/main/scala/net/psforever/objects/Ntu.scala b/src/main/scala/net/psforever/objects/Ntu.scala index c6a9187e..b5ce3807 100644 --- a/src/main/scala/net/psforever/objects/Ntu.scala +++ b/src/main/scala/net/psforever/objects/Ntu.scala @@ -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 } diff --git a/src/main/scala/net/psforever/objects/Vehicles.scala b/src/main/scala/net/psforever/objects/Vehicles.scala index 2d49af63..ed5a328d 100644 --- a/src/main/scala/net/psforever/objects/Vehicles.scala +++ b/src/main/scala/net/psforever/objects/Vehicles.scala @@ -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 diff --git a/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala b/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala index 5ed0b7f1..8614db3c 100644 --- a/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala @@ -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 _ => ; } } diff --git a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala index e4258dba..5a45f43f 100644 --- a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala @@ -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 _ => ; } } diff --git a/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala b/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala index 2bbb5ed4..45853ba2 100644 --- a/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala @@ -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 + } } diff --git a/src/main/scala/net/psforever/objects/serverobject/repair/AmenityAutoRepair.scala b/src/main/scala/net/psforever/objects/serverobject/repair/AmenityAutoRepair.scala new file mode 100644 index 00000000..22d45abe --- /dev/null +++ b/src/main/scala/net/psforever/objects/serverobject/repair/AmenityAutoRepair.scala @@ -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)) + ) + } + } +} diff --git a/src/main/scala/net/psforever/objects/serverobject/repair/RepairableEntity.scala b/src/main/scala/net/psforever/objects/serverobject/repair/RepairableEntity.scala index 50df18dd..daf86fa8 100644 --- a/src/main/scala/net/psforever/objects/serverobject/repair/RepairableEntity.scala +++ b/src/main/scala/net/psforever/objects/serverobject/repair/RepairableEntity.scala @@ -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 } diff --git a/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSilo.scala b/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSilo.scala index 12d8a2a8..fc0f3d7c 100644 --- a/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSilo.scala +++ b/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSilo.scala @@ -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 diff --git a/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala b/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala index 83eedd9a..f726b57d 100644 --- a/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala @@ -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 = {} } diff --git a/src/main/scala/net/psforever/objects/serverobject/structures/AmenityDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/structures/AmenityDefinition.scala index 101d935a..49aa21ee 100644 --- a/src/main/scala/net/psforever/objects/serverobject/structures/AmenityDefinition.scala +++ b/src/main/scala/net/psforever/objects/serverobject/structures/AmenityDefinition.scala @@ -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 } diff --git a/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala b/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala index 4cd7de33..e1ba19fa 100644 --- a/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala +++ b/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala @@ -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) } diff --git a/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala b/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala index 9e9444bf..07cc3d68 100644 --- a/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala +++ b/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala @@ -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 } diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala index d817f4f8..0ff531eb 100644 --- a/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala @@ -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 = { diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala index 1dd1b6a5..f052507b 100644 --- a/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala @@ -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 } diff --git a/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala b/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala index f6858d66..38a9fde8 100644 --- a/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala @@ -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 _ => ; } } diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala index a7e3436c..9e23e450 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala @@ -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 diff --git a/src/main/scala/net/psforever/objects/vehicles/AntTransferBehavior.scala b/src/main/scala/net/psforever/objects/vehicles/AntTransferBehavior.scala index 683093db..75f09a62 100644 --- a/src/main/scala/net/psforever/objects/vehicles/AntTransferBehavior.scala +++ b/src/main/scala/net/psforever/objects/vehicles/AntTransferBehavior.scala @@ -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)) { diff --git a/src/test/scala/objects/AutoRepairIntegrationTest.scala b/src/test/scala/objects/AutoRepairIntegrationTest.scala new file mode 100644 index 00000000..58194308 --- /dev/null +++ b/src/test/scala/objects/AutoRepairIntegrationTest.scala @@ -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 + } +} diff --git a/src/test/scala/objects/AutoRepairTest.scala b/src/test/scala/objects/AutoRepairTest.scala new file mode 100644 index 00000000..648a9dec --- /dev/null +++ b/src/test/scala/objects/AutoRepairTest.scala @@ -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 + } +} diff --git a/src/test/scala/objects/DoorTest.scala b/src/test/scala/objects/DoorTest.scala index c7b29c6b..e1ef43bb 100644 --- a/src/test/scala/objects/DoorTest.scala +++ b/src/test/scala/objects/DoorTest.scala @@ -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) } } diff --git a/src/test/scala/objects/GeneratorTest.scala b/src/test/scala/objects/GeneratorTest.scala index d05b6620..91ea0926 100644 --- a/src/test/scala/objects/GeneratorTest.scala +++ b/src/test/scala/objects/GeneratorTest.scala @@ -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 } ) diff --git a/src/test/scala/objects/ResourceSiloTest.scala b/src/test/scala/objects/ResourceSiloTest.scala index ab7424cd..86643988 100644 --- a/src/test/scala/objects/ResourceSiloTest.scala +++ b/src/test/scala/objects/ResourceSiloTest.scala @@ -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) diff --git a/tools/client/src/main/scala/net/psforever/tools/client/Client.scala b/tools/client/src/main/scala/net/psforever/tools/client/Client.scala index e0817398..e31b0c5b 100644 --- a/tools/client/src/main/scala/net/psforever/tools/client/Client.scala +++ b/tools/client/src/main/scala/net/psforever/tools/client/Client.scala @@ -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 _ => ; } } diff --git a/tools/decode-packets/src/main/scala/net/psforever/tools/decodePackets/DecodePackets.scala b/tools/decode-packets/src/main/scala/net/psforever/tools/decodePackets/DecodePackets.scala index bac910e1..a7aa1e26 100644 --- a/tools/decode-packets/src/main/scala/net/psforever/tools/decodePackets/DecodePackets.scala +++ b/tools/decode-packets/src/main/scala/net/psforever/tools/decodePackets/DecodePackets.scala @@ -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"),