From b81ff2bbf45d7c8d6a0a7adf6f23ae0d6fd07164 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Sat, 14 Jul 2018 21:25:44 -0400 Subject: [PATCH] Facility Turrets (#223) * object class, actor class, and definitions for base turrets; untested * wired base turrets into existence, with hoop jumping; created interface for objects with mounted weapons (vehicles and turrets); working example phalanx_sgl_hevgatcan in Anguta, Ceryshen * re-wiring manned turrets so that the turreted weapon itself never changes externally but merely identifies different and changes internally; workflow for upgrading wall turrets in place (30s); clarifications and documentation for HackMessage and UseItemMessage; getting rid of orphaned packages from previous location of services * added a simple task that reverts upgraded manned turrets to their None state after a certain amount of time has passed; it works but need improvement * turret weapon upgrades now last for a duration of 30 minutes before reverting; created a service support actor base actor that underlies all current support actors; nano-dispenser now properly loads 1 unit of upgrade canister, rather than 100 units; all canister types have appropriate 2x3 inventory size * forgot to hurry; moved over the Services tests from main/test folder into the common/test folder and needed to change the location of ActorTest to accommodate it; test and documentation for MannedTurret; codecov ignore update * wired facility turrets in Anguta, Ceryshen; Akna tower, Ceryshen; and S.Villa tower, home3 (Anguta tower is a watchtower); attempted workaround for Travis CI issues with receiveN; re-introduced RemoveActorTest, at least the first test; expanded how ZoneActor performs tests on MannedTurret setup * getting rid of useless commented-out code; making common operations for mounting and dismounting * removed outdated comment; added ResourceSilo tests; added extra test for Zone --- .codecov.yml | 15 +- .../psforever/objects/GlobalDefinitions.scala | 78 +++- .../scala/net/psforever/objects/Tool.scala | 35 +- .../scala/net/psforever/objects/Vehicle.scala | 33 +- .../objects/equipment/EquipmentSize.scala | 1 + .../equipment/FireModeDefinition.scala | 11 + .../resourcesilo/ResourceSilo.scala | 2 +- .../serverobject/turret/MannedTurret.scala | 224 ++++++++++++ .../turret/MannedTurretControl.scala | 43 +++ .../turret/MannedTurretDefinition.scala | 53 +++ .../serverobject/turret/TurretUpgrade.scala | 13 + .../objects/vehicles/MountedWeapons.scala | 39 ++ .../psforever/objects/vehicles/Turrets.scala | 10 + .../net/psforever/objects/zones/Zone.scala | 37 +- .../psforever/objects/zones/ZoneActor.scala | 87 ++--- .../net/psforever/objects/zones/ZoneMap.scala | 7 + .../psforever/packet/game/HackMessage.scala | 30 +- .../packet/game/UseItemMessage.scala | 39 +- .../game/objectcreate/ObjectClass.scala | 1 + .../main/scala/services/RemoverActor.scala | 316 +++++------------ common/src/main/scala/services/Service.scala | 1 + .../support/SimilarityComparator.scala | 12 + .../scala/services/support/SupportActor.scala | 155 ++++++++ .../support/SupportActorCaseConversions.scala | 37 ++ .../services/vehicle/VehicleAction.scala | 1 + .../services/vehicle/VehicleResponse.scala | 4 +- .../services/vehicle/VehicleService.scala | 21 +- .../vehicle/VehicleServiceMessage.scala | 2 + .../vehicle/support/TurretUpgrader.scala | 260 ++++++++++++++ common/src/test/scala/base/ActorTest.scala | 51 +++ .../test/scala/game/UseItemMessageTest.scala | 4 +- common/src/test/scala/objects/ActorTest.scala | 25 -- .../scala/objects/AutoDriveControlsTest.scala | 1 + .../src/test/scala/objects/BuildingTest.scala | 14 +- .../test/scala/objects/DeploymentTest.scala | 1 + common/src/test/scala/objects/DoorTest.scala | 11 +- .../scala/objects/FactionAffinityTest.scala | 7 +- .../src/test/scala/objects/IFFLockTest.scala | 7 +- .../src/test/scala/objects/LockerTest.scala | 1 + .../test/scala/objects/MannedTurretTest.scala | 179 ++++++++++ .../test/scala/objects/MountableTest.scala | 7 +- .../test/scala/objects/ResourceSiloTest.scala | 332 ++++++++++++++++++ .../objects/ServerObjectBuilderTest.scala | 47 ++- .../test/scala/objects/SpawnTubeTest.scala | 3 +- .../src/test/scala/objects/UtilityTest.scala | 9 +- .../scala/objects/VehicleSpawnPadTest.scala | 14 +- .../src/test/scala/objects/VehicleTest.scala | 1 + common/src/test/scala/objects/ZoneTest.scala | 10 + .../guidtask/GUIDTaskRegister1Test.scala | 2 +- .../guidtask/GUIDTaskRegister2Test.scala | 4 +- .../guidtask/GUIDTaskRegister3Test.scala | 4 +- .../guidtask/GUIDTaskRegister4Test.scala | 4 +- .../guidtask/GUIDTaskRegister5Test.scala | 4 +- .../guidtask/GUIDTaskRegister6Test.scala | 4 +- .../guidtask/GUIDTaskUnregister1Test.scala | 4 +- .../guidtask/GUIDTaskUnregister2Test.scala | 4 +- .../guidtask/GUIDTaskUnregister3Test.scala | 4 +- .../guidtask/GUIDTaskUnregister4Test.scala | 4 +- .../guidtask/GUIDTaskUnregister5Test.scala | 4 +- .../guidtask/GUIDTaskUnregister6Test.scala | 4 +- .../objects/number/NumberPoolActorTest.scala | 8 +- .../number/UniqueNumberSystemTest.scala | 4 +- .../terminal/ImplantTerminalMechTest.scala | 12 +- .../ProximityTerminalControlTest.scala | 12 +- .../objects/terminal/ProximityTest.scala | 2 +- .../terminal/TerminalControlTest.scala | 16 +- .../scala/service}/AvatarServiceTest.scala | 3 + .../scala/service}/LocalServiceTest.scala | 3 + .../scala/service}/RemoverActorTest.scala | 213 +++++------ .../scala/service}/VehicleServiceTest.scala | 3 + pslogin/src/main/scala/Maps.scala | 42 ++- pslogin/src/main/scala/PsLogin.scala | 2 +- .../src/main/scala/WorldSessionActor.scala | 265 ++++++++++---- .../vehicle/support/DeconstructionActor.scala | 0 pslogin/src/test/scala/ActorTest.scala | 4 +- 75 files changed, 2246 insertions(+), 680 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/turret/MannedTurret.scala create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/turret/MannedTurretControl.scala create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/turret/MannedTurretDefinition.scala create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/turret/TurretUpgrade.scala create mode 100644 common/src/main/scala/net/psforever/objects/vehicles/MountedWeapons.scala create mode 100644 common/src/main/scala/net/psforever/objects/vehicles/Turrets.scala create mode 100644 common/src/main/scala/services/support/SimilarityComparator.scala create mode 100644 common/src/main/scala/services/support/SupportActor.scala create mode 100644 common/src/main/scala/services/support/SupportActorCaseConversions.scala create mode 100644 common/src/main/scala/services/vehicle/support/TurretUpgrader.scala create mode 100644 common/src/test/scala/base/ActorTest.scala delete mode 100644 common/src/test/scala/objects/ActorTest.scala create mode 100644 common/src/test/scala/objects/MannedTurretTest.scala create mode 100644 common/src/test/scala/objects/ResourceSiloTest.scala rename {pslogin/src/test/scala => common/src/test/scala/service}/AvatarServiceTest.scala (99%) rename {pslogin/src/test/scala => common/src/test/scala/service}/LocalServiceTest.scala (98%) rename {pslogin/src/test/scala => common/src/test/scala/service}/RemoverActorTest.scala (85%) rename {pslogin/src/test/scala => common/src/test/scala/service}/VehicleServiceTest.scala (99%) delete mode 100644 pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala diff --git a/.codecov.yml b/.codecov.yml index 959d9a66..8f90e18c 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -9,8 +9,11 @@ ignore: - "common/src/main/scala/net/psforever/objects/equipment/Kits.scala" - "common/src/main/scala/net/psforever/objects/equipment/SItem.scala" - "common/src/main/scala/net/psforever/objects/guid/AvailabilityPolicy.scala" + - "common/src/main/scala/net/psforever/objects/serverobject/turret/TurretUpgrade.scala" + - "common/src/main/scala/net/psforever/objects/serverobject/CommonMessages.scala" - "common/src/main/scala/net/psforever/objects/vehicles/AccessPermissionGroup.scala" - "common/src/main/scala/net/psforever/objects/vehicles/SeatArmoRestriction.scala" + - "common/src/main/scala/net/psforever/objects/vehicles/Turrets.scala" - "common/src/main/scala/net/psforever/objects/vehicles/VehicleLockState.scala" - "common/src/main/scala/net/psforever/packet/crypto" - "common/src/main/scala/net/psforever/packet/game/objectcreate/DrawnSlot.scala" @@ -33,12 +36,12 @@ ignore: - "common/src/main/scala/net/psforever/types/MeritCommendation.scala" - "common/src/main/scala/net/psforever/types/PlanetSideEmpire.scala" - "common/src/main/scala/net/psforever/types/TransactionType.scala" - - "pslogin/src/main/scala/services/avatar/AvatarAction.scala" - - "pslogin/src/main/scala/services/avatar/AvatarResponse.scala" - - "pslogin/src/main/scala/services/local/LocalAction.scala" - - "pslogin/src/main/scala/services/local/LocalResponse.scala" - - "pslogin/src/main/scala/services/vehicle/VehicleAction.scala" - - "pslogin/src/main/scala/services/vehicle/VehicleResponse.scala" + - "common/src/main/scala/services/avatar/AvatarAction.scala" + - "common/src/main/scala/services/avatar/AvatarResponse.scala" + - "common/src/main/scala/services/local/LocalAction.scala" + - "common/src/main/scala/services/local/LocalResponse.scala" + - "common/src/main/scala/services/vehicle/VehicleAction.scala" + - "common/src/main/scala/services/vehicle/VehicleResponse.scala" - "pslogin/src/main/scala/CryptoSessionActor.scala" - "pslogin/src/main/scala/DatabaseConnector.scala" - "pslogin/src/main/scala/LoginConfig.scala" diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index d9c43c79..9a0559e4 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -15,9 +15,11 @@ import net.psforever.objects.serverobject.terminals._ import net.psforever.objects.serverobject.tube.SpawnTubeDefinition import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition import net.psforever.objects.ballistics.{DamageType, Projectiles} +import net.psforever.objects.serverobject.turret.{MannedTurretDefinition, TurretUpgrade} import net.psforever.objects.vehicles.{SeatArmorRestriction, UtilityType} import net.psforever.types.PlanetSideEmpire +import scala.collection.mutable import scala.concurrent.duration._ object GlobalDefinitions { @@ -414,11 +416,11 @@ object GlobalDefinitions { val upgrade_canister = AmmoBoxDefinition(Ammo.upgrade_canister) val trek_ammo = AmmoBoxDefinition(Ammo.trek_ammo) - // + val bullet_35mm = AmmoBoxDefinition(Ammo.bullet_35mm) //liberator nosegun val ancient_ammo_vehicle = AmmoBoxDefinition(Ammo.ancient_ammo_vehicle) - // + val aphelion_laser_ammo = AmmoBoxDefinition(Ammo.aphelion_laser_ammo) val aphelion_immolation_cannon_ammo = AmmoBoxDefinition(Ammo.aphelion_immolation_cannon_ammo) @@ -488,6 +490,8 @@ object GlobalDefinitions { val peregrine_sparrow_ammo = AmmoBoxDefinition(Ammo.peregrine_sparrow_ammo) val bullet_150mm = AmmoBoxDefinition(Ammo.bullet_150mm) + + val phalanx_ammo = AmmoBoxDefinition(Ammo.phalanx_ammo) init_ammo() val chainblade = ToolDefinition(ObjectClass.chainblade) @@ -722,6 +726,12 @@ object GlobalDefinitions { val galaxy_gunship_tailgun = ToolDefinition(ObjectClass.galaxy_gunship_tailgun) val galaxy_gunship_gun = ToolDefinition(ObjectClass.galaxy_gunship_gun) + + val phalanx_sgl_hevgatcan = ToolDefinition(ObjectClass.phalanx_sgl_hevgatcan) + + val phalanx_avcombo = ToolDefinition(ObjectClass.phalanx_avcombo) + + val phalanx_flakcombo = ToolDefinition(ObjectClass.phalanx_flakcombo) init_tools() /* @@ -849,6 +859,18 @@ object GlobalDefinitions { val resource_silo = new ResourceSiloDefinition + val manned_turret = new MannedTurretDefinition(480) { + Name = "manned_turret" + MaxHealth = 3600 + Weapons += 1 -> new mutable.HashMap() + Weapons(1) += TurretUpgrade.None -> phalanx_sgl_hevgatcan + Weapons(1) += TurretUpgrade.AVCombo -> phalanx_avcombo + Weapons(1) += TurretUpgrade.FlakCombo -> phalanx_flakcombo + MountPoints += 1 -> 0 + FactionLocked = true + ReserveAmmunition = false + } + /** * Given a faction, provide the standard assault melee weapon. * @param faction the faction @@ -1352,15 +1374,15 @@ object GlobalDefinitions { health_canister.Name = "health_canister" health_canister.Capacity = 100 - health_canister.Tile = InventoryTile.Tile33 + health_canister.Tile = InventoryTile.Tile23 armor_canister.Name = "armor_canister" armor_canister.Capacity = 100 - armor_canister.Tile = InventoryTile.Tile33 + armor_canister.Tile = InventoryTile.Tile23 upgrade_canister.Name = "upgrade_canister" - upgrade_canister.Capacity = 100 - upgrade_canister.Tile = InventoryTile.Tile33 + upgrade_canister.Capacity = 1 + upgrade_canister.Tile = InventoryTile.Tile23 trek_ammo.Name = "trek_ammo" trek_ammo.Size = EquipmentSize.Blocked @@ -1508,6 +1530,10 @@ object GlobalDefinitions { bullet_150mm.Name = "bullet_150mm" bullet_150mm.Capacity = 50 bullet_150mm.Tile = InventoryTile.Tile44 + + phalanx_ammo.Name = "phalanx_ammo" + phalanx_ammo.Capacity = 4000 //sufficient for a reload + phalanx_ammo.Size = EquipmentSize.Inventory } /** @@ -3743,6 +3769,7 @@ object GlobalDefinitions { nano_dispenser.FireModes.head.AmmoTypeIndices += 1 nano_dispenser.FireModes.head.AmmoSlotIndex = 0 nano_dispenser.FireModes.head.Magazine = 100 + nano_dispenser.FireModes.head.CustomMagazine = Ammo.upgrade_canister -> 1 nano_dispenser.FireModes.head.Modifiers.Damage1 = 20 nano_dispenser.FireModes.head.Modifiers.Damage4 = 20 nano_dispenser.Tile = InventoryTile.Tile63 @@ -4297,6 +4324,45 @@ object GlobalDefinitions { galaxy_gunship_gun.FireModes.head.AmmoTypeIndices += 0 galaxy_gunship_gun.FireModes.head.AmmoSlotIndex = 0 galaxy_gunship_gun.FireModes.head.Magazine = 200 + + phalanx_sgl_hevgatcan.Name = "phalanx_sgl_hevgatcan" + phalanx_sgl_hevgatcan.Size = EquipmentSize.BaseTurretWeapon + phalanx_sgl_hevgatcan.AmmoTypes += phalanx_ammo + phalanx_sgl_hevgatcan.ProjectileTypes += phalanx_projectile + phalanx_sgl_hevgatcan.FireModes += new InfiniteFireModeDefinition + phalanx_sgl_hevgatcan.FireModes.head.AmmoTypeIndices += 0 + phalanx_sgl_hevgatcan.FireModes.head.AmmoSlotIndex = 0 + phalanx_sgl_hevgatcan.FireModes.head.Magazine = 4000 + + phalanx_avcombo.Name = "phalanx_avcombo" + phalanx_avcombo.Size = EquipmentSize.BaseTurretWeapon + phalanx_avcombo.AmmoTypes += phalanx_ammo + phalanx_avcombo.ProjectileTypes += phalanx_projectile + phalanx_avcombo.ProjectileTypes += phalanx_av_projectile + phalanx_avcombo.FireModes += new InfiniteFireModeDefinition + phalanx_avcombo.FireModes.head.AmmoTypeIndices += 0 + phalanx_avcombo.FireModes.head.AmmoSlotIndex = 0 + phalanx_avcombo.FireModes.head.Magazine = 4000 + phalanx_avcombo.FireModes += new InfiniteFireModeDefinition + phalanx_avcombo.FireModes(1).AmmoTypeIndices += 0 + phalanx_avcombo.FireModes(1).ProjectileTypeIndices += 1 + phalanx_avcombo.FireModes(1).AmmoSlotIndex = 0 + phalanx_avcombo.FireModes(1).Magazine = 4000 + + phalanx_flakcombo.Name = "phalanx_flakcombo" + phalanx_flakcombo.Size = EquipmentSize.BaseTurretWeapon + phalanx_flakcombo.AmmoTypes += phalanx_ammo + phalanx_flakcombo.ProjectileTypes += phalanx_projectile + phalanx_flakcombo.ProjectileTypes += phalanx_flak_projectile + phalanx_flakcombo.FireModes += new InfiniteFireModeDefinition + phalanx_flakcombo.FireModes.head.AmmoTypeIndices += 0 + phalanx_flakcombo.FireModes.head.AmmoSlotIndex = 0 + phalanx_flakcombo.FireModes.head.Magazine = 4000 + phalanx_flakcombo.FireModes += new InfiniteFireModeDefinition + phalanx_flakcombo.FireModes(1).AmmoTypeIndices += 0 + phalanx_flakcombo.FireModes(1).ProjectileTypeIndices += 1 + phalanx_flakcombo.FireModes(1).AmmoSlotIndex = 0 + phalanx_flakcombo.FireModes(1).Magazine = 4000 } /** diff --git a/common/src/main/scala/net/psforever/objects/Tool.scala b/common/src/main/scala/net/psforever/objects/Tool.scala index 3aa92530..e925f513 100644 --- a/common/src/main/scala/net/psforever/objects/Tool.scala +++ b/common/src/main/scala/net/psforever/objects/Tool.scala @@ -21,24 +21,26 @@ class Tool(private val toolDef : ToolDefinition) extends Equipment with FireMode /** index of the current fire mode on the `ToolDefinition`'s list of fire modes */ private var fireModeIndex : Int = 0 /** current ammunition slot being used by this fire mode */ - private val ammoSlots : List[Tool.FireModeSlot] = Tool.LoadDefinition(this) + private var ammoSlots : List[Tool.FireModeSlot] = List.empty + + Tool.LoadDefinition(this) def FireModeIndex : Int = fireModeIndex def FireModeIndex_=(index : Int) : Int = { - fireModeIndex = index % toolDef.FireModes.length + fireModeIndex = index % Definition.FireModes.length FireModeIndex } - def FireMode : FireModeDefinition = toolDef.FireModes(fireModeIndex) + def FireMode : FireModeDefinition = Definition.FireModes(fireModeIndex) def NextFireMode : FireModeDefinition = { - FireModeIndex = toolDef.NextFireModeIndex(FireModeIndex) + FireModeIndex = Definition.NextFireModeIndex(FireModeIndex) AmmoSlot.Chamber = FireMode.Chamber FireMode } - def ToFireMode : Int = toolDef.NextFireModeIndex(FireModeIndex) + def ToFireMode : Int = Definition.NextFireModeIndex(FireModeIndex) def ToFireMode_=(index : Int) : FireModeDefinition = { FireModeIndex = index @@ -53,7 +55,7 @@ class Tool(private val toolDef : ToolDefinition) extends Equipment with FireMode AmmoTypeIndex } - def AmmoType : Ammo.Value = toolDef.AmmoTypes(AmmoTypeIndex).AmmoType + def AmmoType : Ammo.Value = Definition.AmmoTypes(AmmoTypeIndex).AmmoType def NextAmmoType : Ammo.Value = { AmmoSlot.AmmoTypeIndex = AmmoSlot.AmmoTypeIndex + 1 @@ -61,7 +63,7 @@ class Tool(private val toolDef : ToolDefinition) extends Equipment with FireMode } def Projectile : ProjectileDefinition = { - toolDef.ProjectileTypes({ + Definition.ProjectileTypes({ val projIndices = FireMode.ProjectileTypeIndices if(projIndices.isEmpty) { AmmoTypeIndex //e.g., bullet_9mm -> bullet_9mm_projectile, bullet_9mm_AP -> bullet_9mm_AP_projectile @@ -77,11 +79,20 @@ class Tool(private val toolDef : ToolDefinition) extends Equipment with FireMode def Magazine : Int = AmmoSlot.Magazine def Magazine_=(mag : Int) : Int = { - AmmoSlot.Magazine = Math.min(Math.max(0, mag), MaxMagazine) + //AmmoSlot.Magazine = Math.min(Math.max(0, mag), MaxMagazine) + AmmoSlot.Magazine = Math.max(0, mag) Magazine } - def MaxMagazine : Int = FireMode.Magazine + def MaxMagazine : Int = { + val fmode = FireMode + fmode.CustomMagazine.get(AmmoType) match { + case Some(magSize) => + magSize + case None => + fmode.Magazine + } + } def Discharge : Int = { Magazine = FireMode.Discharge(this) @@ -98,6 +109,8 @@ class Tool(private val toolDef : ToolDefinition) extends Equipment with FireMode override def toString : String = Tool.toString(this) } +//AmmoType = Definition.AmmoTypes( (Definition.FireModes(fireModeIndex)).AmmoTypeIndices( (ammoSlots((Definition.FireModes(fireModeIndex)).AmmoSlotIndex)).AmmoTypeIndex) ).AmmoType + object Tool { def apply(toolDef : ToolDefinition) : Tool = { new Tool(toolDef) @@ -107,10 +120,10 @@ object Tool { * Use the `*Definition` that was provided to this object to initialize its fields and settings. * @param tool the `Tool` being initialized */ - def LoadDefinition(tool : Tool) : List[FireModeSlot] = { + def LoadDefinition(tool : Tool) : Unit = { val tdef : ToolDefinition = tool.Definition val maxSlot = tdef.FireModes.maxBy(fmode => fmode.AmmoSlotIndex).AmmoSlotIndex - buildFireModes(tdef, (0 to maxSlot).iterator, tdef.FireModes.toList) + tool.ammoSlots = buildFireModes(tdef, (0 to maxSlot).iterator, tdef.FireModes.toList) } @tailrec private def buildFireModes(tdef : ToolDefinition, iter : Iterator[Int], fmodes : List[FireModeDefinition], list : List[FireModeSlot] = Nil) : List[FireModeSlot] = { diff --git a/common/src/main/scala/net/psforever/objects/Vehicle.scala b/common/src/main/scala/net/psforever/objects/Vehicle.scala index f2a29027..3d56eb4c 100644 --- a/common/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/common/src/main/scala/net/psforever/objects/Vehicle.scala @@ -65,6 +65,7 @@ import scala.annotation.tailrec class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServerObject with FactionAffinity with Mountable + with MountedWeapons with Deployment with Container { private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR @@ -371,38 +372,6 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ } } - /** - * Given a valid seat number, retrieve an index where the weapon controlled from this seat is mounted. - * @param seatNumber the seat number - * @return a mounted weapon by index, or `None` if either the seat doesn't exist or there is no controlled weapon - */ - def WeaponControlledFromSeat(seatNumber : Int) : Option[Equipment] = { - Seat(seatNumber) match { - case Some(seat) => - wepFromSeat(seat) - case None => - None - } - } - - private def wepFromSeat(seat : Seat) : Option[Equipment] = { - seat.ControlledWeapon match { - case Some(index) => - wepFromSeat(index) - case None => - None - } - } - - private def wepFromSeat(wepIndex : Int) : Option[Equipment] = { - weapons.get(wepIndex) match { - case Some(wep) => - wep.Equipment - case None => - None - } - } - def Utilities : Map[Int, Utility] = utilities /** diff --git a/common/src/main/scala/net/psforever/objects/equipment/EquipmentSize.scala b/common/src/main/scala/net/psforever/objects/equipment/EquipmentSize.scala index bcae0146..564dca62 100644 --- a/common/src/main/scala/net/psforever/objects/equipment/EquipmentSize.scala +++ b/common/src/main/scala/net/psforever/objects/equipment/EquipmentSize.scala @@ -9,6 +9,7 @@ object EquipmentSize extends Enumeration { Rifle, //6x3 and 9x3 Max, //max weapon only VehicleWeapon, //vehicle-mounted weapons + BaseTurretWeapon, //common phalanx cannons, and cavern turrets BFRArmWeapon, //duel arm weapons for bfr BFRGunnerWeapon, //gunner seat for bfr Inventory //reserved diff --git a/common/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala b/common/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala index 50ae85ba..3b010e48 100644 --- a/common/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala @@ -18,6 +18,9 @@ class FireModeDefinition { private var ammoSlotIndex : Int = 0 /** how many rounds are replenished each reload cycle */ private var magazine : Int = 1 + /** how many rounds are replenished each reload cycle, per type of ammunition loaded + * key - ammo type index, value - magazine capacity*/ + private val customAmmoMagazine : mutable.HashMap[Ammo.Value, Int] = mutable.HashMap[Ammo.Value, Int]() /** how much is subtracted from the magazine each fire cycle; * most weapons will only fire 1 round per fire cycle; the flamethrower in fire mode 1 fires 50 */ private var rounds : Int = 1 @@ -53,6 +56,14 @@ class FireModeDefinition { Magazine } + def CustomMagazine : mutable.HashMap[Ammo.Value, Int] = customAmmoMagazine + + def CustomMagazine_=(kv : (Ammo.Value, Int)) : mutable.HashMap[Ammo.Value, Int] = { + val (ammoTypeIndex, cap) = kv + customAmmoMagazine += ammoTypeIndex -> cap + CustomMagazine + } + def Rounds : Int = rounds def Rounds_=(round : Int) : Int = { diff --git a/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSilo.scala b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSilo.scala index d62dccb0..ec91da7e 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSilo.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSilo.scala @@ -82,4 +82,4 @@ object ResourceSilo { obj.Actor ! "startup" obj } -} \ No newline at end of file +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/turret/MannedTurret.scala b/common/src/main/scala/net/psforever/objects/serverobject/turret/MannedTurret.scala new file mode 100644 index 00000000..70624b4e --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/turret/MannedTurret.scala @@ -0,0 +1,224 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.turret + +import net.psforever.objects.{EquipmentSlot, Player} +import net.psforever.objects.definition.SeatDefinition +import net.psforever.objects.equipment.Equipment +import net.psforever.objects.inventory.{Container, GridInventory} +import net.psforever.objects.serverobject.affinity.FactionAffinity +import net.psforever.objects.serverobject.mount.Mountable +import net.psforever.objects.serverobject.structures.Amenity +import net.psforever.objects.serverobject.turret.MannedTurret.MannedTurretWeapon +import net.psforever.objects.vehicles.{MountedWeapons, Seat => Chair} + +class MannedTurret(tDef : MannedTurretDefinition) extends Amenity + with FactionAffinity + with Mountable + with MountedWeapons + with Container { + private var health : Int = 1 + private var jammered : Boolean = false + /** manned turrets have just one seat; this is just standard interface */ + private val seats : Map[Int, Chair] = Map(0 -> Chair(new SeatDefinition() { ControlledWeapon = Some(1) })) + /** manned turrets have just one weapon; this is just standard interface */ + private var weapons : Map[Int, EquipmentSlot] = Map.empty + /** may or may not have inaccessible inventory space + * see `ReserveAmmunition` in the definition */ + private val inventory : GridInventory = new GridInventory() { + import net.psforever.packet.game.PlanetSideGUID + override def Remove(index : Int) : Boolean = false + override def Remove(guid : PlanetSideGUID) : Boolean = false + } + /** some turrets can be updated; they all start without updates */ + private var upgradePath : TurretUpgrade.Value = TurretUpgrade.None + + MannedTurret.LoadDefinition(this) + + def Health : Int = { + health + } + + def Health_=(toHealth : Int) : Int = { + health = toHealth + health + } + + def MaxHealth : Int = { + Definition.MaxHealth + } + + def Seats : Map[Int, Chair] = seats + + def Seat(seatNum : Int) : Option[Chair] = seats.get(seatNum) + + /** + * Given the index of an entry mounting point, return the infantry-accessible `Seat` associated with it. + * @param mountPoint an index representing the seat position / mounting point + * @return a seat number, or `None` + */ + def GetSeatFromMountPoint(mountPoint : Int) : Option[Int] = { + Definition.MountPoints.get(mountPoint) + } + + def MountPoints : Map[Int, Int] = Definition.MountPoints.toMap + + def PassengerInSeat(user : Player) : Option[Int] = { + if(seats(0).Occupant.contains(user)) { + Some(0) + } + else { + None + } + } + + def Weapons : Map[Int, EquipmentSlot] = weapons.filter({ case(index, _) => index < 2 }) + + def ControlledWeapon(wepNumber : Int) : Option[Equipment] = { + if(VisibleSlots.contains(wepNumber)) { + weapons(wepNumber).Equipment + } + else { + None + } + } + + def Inventory : GridInventory = inventory + + def VisibleSlots : Set[Int] = Set(1) + + def Upgrade : TurretUpgrade.Value = upgradePath + + def Upgrade_=(upgrade : TurretUpgrade.Value) : TurretUpgrade.Value = { + upgradePath = upgrade + //upgrade each weapon as long as that weapon has a valid option for that upgrade + Definition.Weapons.foreach({ case(index, upgradePaths) => + if(upgradePaths.contains(upgrade)) { + weapons(index).Equipment.get.asInstanceOf[MannedTurretWeapon].Upgrade = upgrade + } + }) + Upgrade + } + + def Jammered : Boolean = jammered + + def Jammered_=(jamState : Boolean) : Boolean = { + jammered = jamState + Jammered + } + + def Definition : MannedTurretDefinition = tDef +} + +object MannedTurret { + /** + * Overloaded constructor. + * @param tDef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields + * @return a `MannedTurret` object + */ + def apply(tDef : MannedTurretDefinition) : MannedTurret = { + new MannedTurret(tDef) + } + + /** + * Use the `*Definition` that was provided to this object to initialize its fields and settings. + * @param turret the `MannedTurret` being initialized + * @see `{object}.LoadDefinition` + */ + def LoadDefinition(turret : MannedTurret) : MannedTurret = { + import net.psforever.objects.equipment.EquipmentSize.BaseTurretWeapon + val tdef : MannedTurretDefinition = turret.Definition + //general stuff + turret.Health = tdef.MaxHealth + //create weapons; note the class + turret.weapons = tdef.Weapons.map({case (num, upgradePaths) => + val slot = EquipmentSlot(BaseTurretWeapon) + slot.Equipment = new MannedTurretWeapon(tdef, upgradePaths.toMap) + num -> slot + }).toMap + //special inventory ammunition object(s) + if(tdef.ReserveAmmunition) { + val allAmmunitionTypes = tdef.Weapons.values.flatMap{ _.values.flatMap { _.AmmoTypes } }.toSet + if(allAmmunitionTypes.nonEmpty) { + turret.inventory.Resize(allAmmunitionTypes.size, 1) + var i : Int = 0 + allAmmunitionTypes.foreach(ammotype => { + turret.inventory.InsertQuickly(i, new TurretAmmoBox(ammotype)) + i += 1 + }) + } + } + turret + } + + import net.psforever.objects.definition.ToolDefinition + import net.psforever.objects.Tool + /** + * A stateful weapon that is mounted in `MannedTurrets` + * and may maintains a group of upgraded forms that can by swapped + * without reconstructing the weapon object itself or managing object registration. + * @param mdef the turret's definition + * @param udefs a map of turret upgrades to tool definitions that would be constructed by this weapon + * @param default the default upgrade state; + * defaults to `None` + */ + private class MannedTurretWeapon(mdef : MannedTurretDefinition, udefs : Map[TurretUpgrade.Value, ToolDefinition], default : TurretUpgrade.Value = TurretUpgrade.None) + extends Tool(udefs(default)) { + private var upgradePath : TurretUpgrade.Value = default + + def Upgrade : TurretUpgrade.Value = { + /* + Must check `not null` due to how this object's `Definition` will be called during `Tool`'s constructor + before the internal value can be set to default value `None` + */ + Option(upgradePath) match { + case Some(value) => + value + case None => + default + } + } + + def Upgrade_=(upgrade : TurretUpgrade.Value) : TurretUpgrade.Value = { + if(udefs.contains(upgrade)) { + val beforeUpgrade = upgradePath + upgradePath = upgrade + if(beforeUpgrade != upgradePath) { + Tool.LoadDefinition(this) //rebuild weapon internal structure + FireModeIndex = 0 //reset fire mode; this option is always valid + } + } + Upgrade + } + + override def Definition = udefs(Upgrade) + } + + import net.psforever.objects.definition.AmmoBoxDefinition + import net.psforever.objects.AmmoBox + + /** + * A special type of ammunition box contained within a `MannedTurret` for the purposes of infinite reloads. + * The original quantity of ammunition does not change. + * @param adef ammunition definition + */ + private class TurretAmmoBox(private val adef : AmmoBoxDefinition) extends AmmoBox(adef, Some(65535)) { + import net.psforever.objects.inventory.InventoryTile + override def Tile = InventoryTile.Tile11 + + override def Capacity_=(toCapacity : Int) = Capacity + } + + import akka.actor.ActorContext + /** + * Instantiate an configure a `MannedTurret` object + * @param id the unique id that will be assigned to this entity + * @param context a context to allow the object to properly set up `ActorSystem` functionality + * @return the `MannedTurret` object + */ + def Constructor(tdef : MannedTurretDefinition)(id : Int, context : ActorContext) : MannedTurret = { + import akka.actor.Props + val obj = MannedTurret(tdef) + obj.Actor = context.actorOf(Props(classOf[MannedTurretControl], obj), s"${tdef.Name}_$id") + obj + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/turret/MannedTurretControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/turret/MannedTurretControl.scala new file mode 100644 index 00000000..51f89f4e --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/turret/MannedTurretControl.scala @@ -0,0 +1,43 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.turret + +import akka.actor.Actor +import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} +import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} + +/** + * An `Actor` that handles messages being dispatched to a specific `MannedTurret`.
+ *
+ * Mounted turrets have only slightly different entry requirements than a normal vehicle + * because they encompass both faction-specific facility turrets + * and faction-blind cavern sentry turrets. + * @param turret the `MannedTurret` object being governed + */ +class MannedTurretControl(turret : MannedTurret) extends Actor + with FactionAffinityBehavior.Check + with MountableBehavior.Dismount { + def MountableObject = turret //do not add type! + + def FactionObject : FactionAffinity = turret + + def receive : Receive = checkBehavior + .orElse(dismountBehavior) + .orElse { + case Mountable.TryMount(user, seat_num) => + turret.Seat(seat_num) match { + case Some(seat) => + if((!turret.Definition.FactionLocked || user.Faction == turret.Faction) && + (seat.Occupant = user).contains(user)) { + user.VehicleSeated = turret.GUID + sender ! Mountable.MountMessages(user, Mountable.CanMount(turret, seat_num)) + } + else { + sender ! Mountable.MountMessages(user, Mountable.CanNotMount(turret, seat_num)) + } + case None => + sender ! Mountable.MountMessages(user, Mountable.CanNotMount(turret, seat_num)) + } + + case _ => ; + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/turret/MannedTurretDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/turret/MannedTurretDefinition.scala new file mode 100644 index 00000000..4c01105e --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/turret/MannedTurretDefinition.scala @@ -0,0 +1,53 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.turret + +import net.psforever.objects.definition.{ObjectDefinition, ToolDefinition} +import net.psforever.objects.vehicles.Turrets + +import scala.collection.mutable + +/** + * The definition for any `MannedTurret`. + * @param objectId the object's identifier number + */ +class MannedTurretDefinition(private val objectId : Int) extends ObjectDefinition(objectId) { + Turrets(objectId) //let throw NoSuchElementException + + private var maxHealth : Int = 100 + /* key - entry point index, value - seat index */ + private val mountPoints : mutable.HashMap[Int, Int] = mutable.HashMap() + /* key - seat number, value - hash map (below) */ + /* key - upgrade, value - weapon definition */ + private val weapons : mutable.HashMap[Int, mutable.HashMap[TurretUpgrade.Value, ToolDefinition]] = + mutable.HashMap[Int, mutable.HashMap[TurretUpgrade.Value, ToolDefinition]]() + /** can only be mounted by owning faction when `true` */ + private var factionLocked : Boolean = true + /** creates internal ammunition reserves that can not become depleted + * see `MannedTurret.TurretAmmoBox` for details */ + private var hasReserveAmmunition : Boolean = false + + def MaxHealth : Int = maxHealth + + def MaxHealth_=(health : Int) : Int = { + maxHealth = health + MaxHealth + } + + def MountPoints : mutable.HashMap[Int, Int] = mountPoints + + def Weapons : mutable.HashMap[Int, mutable.HashMap[TurretUpgrade.Value, ToolDefinition]] = weapons + + def FactionLocked : Boolean = factionLocked + + def FactionLocked_=(ownable : Boolean) : Boolean = { + factionLocked = ownable + FactionLocked + } + + def ReserveAmmunition : Boolean = hasReserveAmmunition + + def ReserveAmmunition_=(reserved : Boolean) : Boolean = { + hasReserveAmmunition = reserved + ReserveAmmunition + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/turret/TurretUpgrade.scala b/common/src/main/scala/net/psforever/objects/serverobject/turret/TurretUpgrade.scala new file mode 100644 index 00000000..9151edac --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/turret/TurretUpgrade.scala @@ -0,0 +1,13 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.turret + +/** + * An `Enumeration` of the available turret upgrade states. + */ +object TurretUpgrade extends Enumeration { + val + None, //default, always + AVCombo, //phalanx_avcombo + FlakCombo //phalanx_flakcombo + = Value +} diff --git a/common/src/main/scala/net/psforever/objects/vehicles/MountedWeapons.scala b/common/src/main/scala/net/psforever/objects/vehicles/MountedWeapons.scala new file mode 100644 index 00000000..3320496c --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vehicles/MountedWeapons.scala @@ -0,0 +1,39 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.vehicles + +import net.psforever.objects.{EquipmentSlot, PlanetSideGameObject} +import net.psforever.objects.equipment.Equipment +import net.psforever.objects.inventory.Container +import net.psforever.objects.serverobject.mount.Mountable +import net.psforever.objects.vehicles.{Seat => Chair} + +trait MountedWeapons { + this : PlanetSideGameObject with Mountable with Container => + + def Weapons : Map[Int, EquipmentSlot] + + /** + * Given a valid seat number, retrieve an index where the weapon controlled from this seat is mounted. + * @param seatNumber the seat number + * @return a mounted weapon by index, or `None` if either the seat doesn't exist or there is no controlled weapon + */ + def WeaponControlledFromSeat(seatNumber : Int) : Option[Equipment] = { + Seat(seatNumber) match { + case Some(seat) => + wepFromSeat(seat) + case None => + None + } + } + + private def wepFromSeat(seat : Chair) : Option[Equipment] = { + seat.ControlledWeapon match { + case Some(index) => + ControlledWeapon(index) + case None => + None + } + } + + def ControlledWeapon(wepNumber : Int) : Option[Equipment] +} \ No newline at end of file diff --git a/common/src/main/scala/net/psforever/objects/vehicles/Turrets.scala b/common/src/main/scala/net/psforever/objects/vehicles/Turrets.scala new file mode 100644 index 00000000..74293d46 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vehicles/Turrets.scala @@ -0,0 +1,10 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.vehicles + +/** + * An `Enumeration` of all the turret type objectss in the game, paired with their object id as the `Value`. + */ +object Turrets extends Enumeration { + val manned_turret = Value(480) + val vanu_sentry_turret = Value(943) +} diff --git a/common/src/main/scala/net/psforever/objects/zones/Zone.scala b/common/src/main/scala/net/psforever/objects/zones/Zone.scala index a8b13aec..b08c9c3d 100644 --- a/common/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -12,6 +12,7 @@ import net.psforever.objects.guid.selector.RandomSelector import net.psforever.objects.guid.source.LimitedNumberSource import net.psforever.objects.serverobject.structures.{Amenity, Building} import net.psforever.objects.serverobject.tube.SpawnTube +import net.psforever.objects.serverobject.turret.MannedTurret import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.Vector3 @@ -93,7 +94,8 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { transport = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"$Id-vehicles") population = context.actorOf(Props(classOf[ZonePopulationActor], this, players, corpses), s"$Id-players") - Map.LocalObjects.foreach({ builderObject => builderObject.Build }) + BuildLocalObjects(context, guid) + BuildSupportObjects() MakeBuildings(context) AssignAmenities() CreateSpawnGroups() @@ -279,6 +281,39 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { buildings.get(id) } + private def BuildLocalObjects(implicit context : ActorContext, guid : NumberPoolHub) : Unit = { + Map.LocalObjects.foreach({ builderObject => builderObject.Build }) + } + + private def BuildSupportObjects() : Unit = { + //guard against errors here, but don't worry about specifics; let ZoneActor.ZoneSetupCheck complain about problems + //turret to weapon + Map.TurretToWeapon.foreach({ case ((turret_guid, weapon_guid)) => + ((GUID(turret_guid) match { + case Some(obj : MannedTurret) => + Some(obj) + case _ => ; + None + }) match { + case Some(obj) => + obj.Weapons.get(1) match { + case Some(slot) => + Some(obj, slot.Equipment) + case None => + None + } + case None => + None + }) match { + case Some((obj, Some(weapon : Tool))) => + guid.register(weapon, weapon_guid) + weapon.AmmoSlots.foreach(slot => guid.register(slot.Box, "dynamic")) + obj.Inventory.Items.foreach(item => guid.register(item.obj, "dynamic")) //internal ammunition reserves, if any + case _ => ; + } + }) + } + private def MakeBuildings(implicit context : ActorContext) : PairMap[Int, Building] = { val buildingList = Map.LocalBuildings buildings = buildingList.map({case(building_id, constructor) => building_id -> constructor.Build(building_id, this) }) diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala index 80f6a547..acd4c6f4 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala @@ -4,7 +4,7 @@ package net.psforever.objects.zones import java.util.concurrent.atomic.AtomicInteger import akka.actor.Actor -import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject} +import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Tool} import net.psforever.objects.serverobject.structures.StructureType import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.vehicles.UtilityType @@ -171,73 +171,24 @@ class ZoneActor(zone : Zone) extends Actor { validateObject(mech_guid, ImplantMechCheck, "implant terminal mech") validateObject(interface_guid, TerminalCheck, "implant terminal interface") }) + + //check manned turret to weapon association + map.TurretToWeapon.foreach({ case ((turret_guid, weapon_guid)) => + validateObject(turret_guid, MannedTurretCheck, "manned turret mount") + if(validateObject(weapon_guid, WeaponCheck, "manned turret weapon")) { + if(guid(weapon_guid).get.asInstanceOf[Tool].AmmoSlots.count(!_.Box.HasGUID) > 0) { + slog.error(s"expected weapon $weapon_guid has an unregistered ammunition unit") + errors.incrementAndGet() + } + } + }) + + //output number of errors errors.intValue() } } object ZoneActor { -// import net.psforever.types.PlanetSideEmpire -// import net.psforever.objects.Vehicle -// import net.psforever.objects.serverobject.structures.Building -// def AllSpawnGroup(zone : Zone, targetPosition : Vector3, targetFaction : PlanetSideEmpire.Value) : Option[List[SpawnTube]] = { -// ClosestOwnedSpawnTube(AmsSpawnGroup(zone) ++ BuildingSpawnGroup(zone, 0), targetPosition, targetFaction) -// } -// -// def AmsSpawnGroup(vehicles : List[Vehicle]) : Iterable[(Vector3, PlanetSideEmpire.Value, Iterable[SpawnTube])] = { -// vehicles -// .filter(veh => veh.DeploymentState == DriveState.Deployed && veh.Definition == GlobalDefinitions.ams) -// .map(veh => -// (veh.Position, veh.Faction, -// veh.Utilities -// .values -// .filter(util => util.UtilType == UtilityType.ams_respawn_tube) -// .map { _().asInstanceOf[SpawnTube] } -// ) -// ) -// } -// -// def AmsSpawnGroup(zone : Zone, spawn_group : Int = 2) : Iterable[(Vector3, PlanetSideEmpire.Value, Iterable[SpawnTube])] = { -// if(spawn_group == 2) { -// AmsSpawnGroup(zone.Vehicles) -// } -// else { -// Nil -// } -// } -// -// def BuildingSpawnGroup(spawnGroups : Map[Building, List[SpawnTube]]) : Iterable[(Vector3, PlanetSideEmpire.Value, Iterable[SpawnTube])] = { -// spawnGroups -// .map({ case ((building, tubes)) => (building.Position.xy, building.Faction, tubes) }) -// } -// -// def BuildingSpawnGroup(zone : Zone, spawn_group : Int) : Iterable[(Vector3, PlanetSideEmpire.Value, Iterable[SpawnTube])] = { -// val buildingTypeSet = if(spawn_group == 0) { -// Set(StructureType.Facility, StructureType.Tower, StructureType.Building) -// } -// else if(spawn_group == 6) { -// Set(StructureType.Tower) -// } -// else if(spawn_group == 7) { -// Set(StructureType.Facility, StructureType.Building) -// } -// else { -// Set.empty[StructureType.Value] -// } -// BuildingSpawnGroup( -// zone.SpawnGroups().filter({ case((building, _)) => buildingTypeSet.contains(building.BuildingType) }) -// ) -// } -// -// def ClosestOwnedSpawnTube(tubes : Iterable[(Vector3, PlanetSideEmpire.Value, Iterable[SpawnTube])], targetPosition : Vector3, targetFaction : PlanetSideEmpire.Value) : Option[List[SpawnTube]] = { -// tubes -// .toSeq -// .filter({ case (_, faction, _) => faction == targetFaction }) -// .sortBy({ case (pos, _, _) => Vector3.DistanceSquared(pos, targetPosition) }) -// .take(1) -// .map({ case (_, _, tubes : List[SpawnTube]) => tubes }) -// .headOption -// } - /** * Recover an object from a collection and perform any number of validating tests upon it. * If the object fails any tests, log an error. @@ -295,4 +246,14 @@ object ZoneActor { import net.psforever.objects.serverobject.pad.VehicleSpawnPad obj.isInstanceOf[VehicleSpawnPad] } + + def MannedTurretCheck(obj : PlanetSideGameObject) : Boolean = { + import net.psforever.objects.serverobject.turret.MannedTurret + obj.isInstanceOf[MannedTurret] + } + + def WeaponCheck(obj : PlanetSideGameObject) : Boolean = { + import net.psforever.objects.Tool + obj.isInstanceOf[Tool] + } } diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala index 51ebeafa..22594f2c 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala @@ -26,6 +26,7 @@ import net.psforever.objects.serverobject.{PlanetSideServerObject, ServerObjectB */ class ZoneMap(private val name : String) { private var localObjects : List[ServerObjectBuilder[_]] = List() + private var linkTurretWeapon : Map[Int, Int] = Map() private var linkTerminalPad : Map[Int, Int] = Map() private var linkTerminalInterface : Map[Int, Int] = Map() private var linkDoorLock : Map[Int, Int] = Map() @@ -87,4 +88,10 @@ class ZoneMap(private val name : String) { def TerminalToInterface(interface_guid : Int, terminal_guid : Int) : Unit = { linkTerminalInterface = linkTerminalInterface ++ Map(interface_guid -> terminal_guid) } + + def TurretToWeapon : Map[Int, Int] = linkTurretWeapon + + def TurretToWeapon(turret_guid : Int, weapon_guid : Int) : Unit = { + linkTurretWeapon = linkTurretWeapon ++ Map(turret_guid -> weapon_guid) + } } diff --git a/common/src/main/scala/net/psforever/packet/game/HackMessage.scala b/common/src/main/scala/net/psforever/packet/game/HackMessage.scala index 492e5b53..1966f05c 100644 --- a/common/src/main/scala/net/psforever/packet/game/HackMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/HackMessage.scala @@ -33,21 +33,27 @@ object HackState extends Enumeration { } /** - * Dispatched by the server to control the process of hacking.
+ * Dispatched by the server to control the progress of hacking. + * While "hacking" is typically performed against enemy targets, + * some actions that involve ally on ally hacking can occur. + * In this sense, hacking can be consider change progress.
*
- * Part of the hacking process is regulated by the server while another part of it is automatically reset by the client. - * The visibility, update, and closing of the hacking progress bar must be handled manually, for each tick. - * When hacking is complete, using the appropriate `HackState` will cue the target to be affected by the hack. - * Terminals and door IFF panels will temporarily expose their functionality; - * the faction association of vehicles will be converted permanently; - * a protracted process of a base conversion will be enacted; etc.. - * This transfer of faction association occurs to align the target with the faction of the hacking player (as indicated). - * The client will select the faction without needing to be explicitly told - * and will select the appropriate action to enact upon the target. - * Upon the hack's completion, the target on the client will automatically revert back to its original state, if possible. - * (It will still be necessary to alert this change from the server's perspective.) + * In general, the act of hacking is maintained by the server but the conclusion is managed by the client. + * Hacking typically locks the player into a cancellable firing animation and works as all-or-nothing. + * The progress bar window is displayed and updated each tick by the server; but, the client can cancel it on its own. + * When hacking is complete as indicated by the appropriate `HackState`, + * the client performs the intended action upon the target. + * Facility amenities will temporarily ignore IFF requirements; + * vehicles will permanently transfer control over to the hack-starter's empire; + * facility turret weapons will temporarily convert to their anti-vehicle or anti-aircraft configurations; + * facilities will be compromised and begin the long process of converting to the hack-starter's empire; + * and, so forth.
+ *
+ * As mentioned, one of the unexpected uses of this message + * will assist the conversion of allied facility turreted weapons to their upgraded armaments. * @param unk1 na; * 0 commonly; + * 2 when performing (phalanx) upgrades; * 3 for building objects during login phase; * hack type? * @param target_guid the target of the hack diff --git a/common/src/main/scala/net/psforever/packet/game/UseItemMessage.scala b/common/src/main/scala/net/psforever/packet/game/UseItemMessage.scala index b66180b9..f7dd19ca 100644 --- a/common/src/main/scala/net/psforever/packet/game/UseItemMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/UseItemMessage.scala @@ -7,21 +7,28 @@ import scodec.Codec import scodec.codecs._ /** - * (Where the child object was before it was moved is not specified or important.)
- * @param avatar_guid the player. - * @param item_used_guid The "item" GUID used e.g. a rek to hack or a medkit to heal. - * @param object_guid can be : Door, Terminal, Avatar (medkit). - * @param unk2 ??? - * @param unk3 ??? true when use a rek (false when door, medkit or open equip term) - * @param unk4 ??? - * @param unk5 ??? - * @param unk6 ??? - * @param unk7 ??? 25 when door 223 when terminal - * @param unk8 ??? 0 when door 1 when use rek (252 then equipment term) - * @param itemType object ID from game_objects.adb (ex 612 is an equipment terminal, for medkit we have 121 (avatar)) + * (Where the child object was before it was moved is not specified or important.) + * @see `Definition.ObjectId`
+ * `TurretUpgrade` + * @param avatar_guid the player + * @param item_used_guid the item used; + * e.g., a REK to hack, or a medkit to heal + * @param object_guid the object affected; + * e.g., door when opened, terminal when accessed, avatar when medkit used + * @param unk2 na; + * when upgrading phalanx turrets, 1 for `AVCombo` and 2 for `FlakCombo` + * @param unk3 using tools, e.g., a REK or nano-dispenser + * @param unk4 na + * @param unk5 na + * @param unk6 na + * @param unk7 na; + * 25 when door 223 when terminal + * @param unk8 na; + * 0 when door 1 when use rek (252 then equipment term) + * @param object_id he object id for `object_guid`'s object */ final case class UseItemMessage(avatar_guid : PlanetSideGUID, - item_used_guid : Int, + item_used_guid : PlanetSideGUID, object_guid : PlanetSideGUID, unk2 : Long, unk3 : Boolean, @@ -30,7 +37,7 @@ final case class UseItemMessage(avatar_guid : PlanetSideGUID, unk6 : Int, unk7 : Int, unk8 : Int, - itemType : Long) + object_id : Long) extends PlanetSideGamePacket { type Packet = UseItemMessage def opcode = GamePacketOpcode.UseItemMessage @@ -40,7 +47,7 @@ final case class UseItemMessage(avatar_guid : PlanetSideGUID, object UseItemMessage extends Marshallable[UseItemMessage] { implicit val codec : Codec[UseItemMessage] = ( ("avatar_guid" | PlanetSideGUID.codec) :: - ("item_used_guid" | uint16L) :: + ("item_used_guid" | PlanetSideGUID.codec) :: ("object_guid" | PlanetSideGUID.codec) :: ("unk2" | uint32L) :: ("unk3" | bool) :: @@ -49,6 +56,6 @@ object UseItemMessage extends Marshallable[UseItemMessage] { ("unk6" | uint8L) :: ("unk7" | uint8L) :: ("unk8" | uint8L) :: - ("itemType" | uint32L) + ("object_id" | uint32L) ).as[UseItemMessage] } diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala index 281360a9..11e92559 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala @@ -373,6 +373,7 @@ object ObjectClass { final val implant_terminal_interface = 409 final val locker_container = 456 final val lodestar_repair_terminal = 461 + final val manned_turret = 480 final val matrix_terminala = 517 final val matrix_terminalb = 518 final val matrix_terminalc = 519 diff --git a/common/src/main/scala/services/RemoverActor.scala b/common/src/main/scala/services/RemoverActor.scala index 85e211e0..e096a872 100644 --- a/common/src/main/scala/services/RemoverActor.scala +++ b/common/src/main/scala/services/RemoverActor.scala @@ -6,8 +6,8 @@ import net.psforever.objects.guid.TaskResolver import net.psforever.objects.zones.Zone import net.psforever.objects.{DefaultCancellable, PlanetSideGameObject} import net.psforever.types.Vector3 +import services.support.{SimilarityComparator, SupportActor, SupportActorCaseConversions} -import scala.annotation.tailrec import scala.concurrent.duration._ /** @@ -29,7 +29,7 @@ import scala.concurrent.duration._ * and finally unregistering it. * Some types of object have (de-)implementation variations which should be made explicit through the overrides. */ -abstract class RemoverActor extends Actor { +abstract class RemoverActor extends SupportActor[RemoverActor.Entry] { /** * The timer that checks whether entries in the first pool are still eligible for that pool. */ @@ -50,16 +50,18 @@ abstract class RemoverActor extends Actor { private var taskResolver : ActorRef = Actor.noSender - private[this] val log = org.log4s.getLogger - def trace(msg : String) : Unit = log.trace(msg) - def debug(msg : String) : Unit = log.debug(msg) + val sameEntryComparator = new SimilarityComparator[RemoverActor.Entry]() { + def Test(entry1 : RemoverActor.Entry, entry2 : RemoverActor.Entry) : Boolean = { + entry1.obj == entry2.obj && entry1.zone == entry2.zone && entry1.obj.GUID == entry2.obj.GUID + } + } /** * Send the initial message that requests a task resolver for assisting in the removal process. */ override def preStart() : Unit = { super.preStart() - self ! RemoverActor.Startup() + self ! Service.Startup() } /** @@ -82,7 +84,7 @@ abstract class RemoverActor extends Actor { } def receive : Receive = { - case RemoverActor.Startup() => + case Service.Startup() => ServiceManager.serviceManager ! ServiceManager.Lookup("taskResolver") //ask for a resolver to deal with the GUID system case ServiceManager.LookupResult("taskResolver", endpoint) => @@ -90,83 +92,72 @@ abstract class RemoverActor extends Actor { context.become(Processing) case msg => - log.error(s"received message $msg before being properly initialized") + debug(s"received message $msg before being properly initialized") } - def Processing : Receive = { - case RemoverActor.AddTask(obj, zone, duration) => - val entry = RemoverActor.Entry(obj, zone, duration.getOrElse(FirstStandardDuration).toNanos) - if(InclusionTest(entry) && !secondHeap.exists(test => RemoverActor.Similarity(test, entry) )) { - InitialJob(entry) - if(firstHeap.isEmpty) { - //we were the only entry so the event must be started from scratch - firstHeap = List(entry) - trace(s"a remover task has been added: $entry") - RetimeFirstTask() - } - else { - //unknown number of entries; append, sort, then re-time tasking - val oldHead = firstHeap.head - if(!firstHeap.exists(test => RemoverActor.Similarity(test, entry))) { - firstHeap = (firstHeap :+ entry).sortBy(_.duration) + def Processing : Receive = entryManagementBehaviors + .orElse { + case RemoverActor.AddTask(obj, zone, duration) => + val entry = RemoverActor.Entry(obj, zone, duration.getOrElse(FirstStandardDuration).toNanos) + if(InclusionTest(entry) && !secondHeap.exists(test => sameEntryComparator.Test(test, entry) )) { + InitialJob(entry) + if(firstHeap.isEmpty) { + //we were the only entry so the event must be started from scratch + firstHeap = List(entry) trace(s"a remover task has been added: $entry") - if(oldHead != firstHeap.head) { - RetimeFirstTask() - } + RetimeFirstTask() } else { - trace(s"$obj is already queued for removal") + //unknown number of entries; append, sort, then re-time tasking + val oldHead = firstHeap.head + if(!firstHeap.exists(test => sameEntryComparator.Test(test, entry))) { + firstHeap = (firstHeap :+ entry).sortBy(entry => entry.time + entry.duration) + trace(s"a remover task has been added: $entry") + if(oldHead != firstHeap.head) { + RetimeFirstTask() + } + } + else { + trace(s"$obj is already queued for removal") + } } } - } - else { - trace(s"$obj either does not qualify for this Remover or is already queued") - } + else { + trace(s"$obj either does not qualify for this Remover or is already queued") + } - case RemoverActor.HurrySpecific(targets, zone) => - HurrySpecific(targets, zone) + //private messages from RemoverActor to RemoverActor + case RemoverActor.StartDelete() => + firstTask.cancel + secondTask.cancel + val now : Long = System.nanoTime + val (in, out) = firstHeap.partition(entry => { now - entry.time >= entry.duration }) + firstHeap = out + secondHeap = secondHeap ++ in.map { RepackageEntry } + in.foreach { FirstJob } + RetimeFirstTask() + if(secondHeap.nonEmpty) { + import scala.concurrent.ExecutionContext.Implicits.global + secondTask = context.system.scheduler.scheduleOnce(SecondStandardDuration, self, RemoverActor.TryDelete()) + } + trace(s"item removal task has found ${in.size} items to remove") - case RemoverActor.HurryAll() => - HurryAll() + case RemoverActor.TryDelete() => + secondTask.cancel + val (in, out) = secondHeap.partition { ClearanceTest } + secondHeap = out + in.foreach { SecondJob } + if(out.nonEmpty) { + import scala.concurrent.ExecutionContext.Implicits.global + secondTask = context.system.scheduler.scheduleOnce(SecondStandardDuration, self, RemoverActor.TryDelete()) + } + trace(s"item removal task has removed ${in.size} items") - case RemoverActor.ClearSpecific(targets, zone) => - ClearSpecific(targets, zone) + case RemoverActor.FailureToWork(entry, ex) => + debug(s"${entry.obj} from ${entry.zone} not properly deleted - $ex") - case RemoverActor.ClearAll() => - ClearAll() - - //private messages from RemoverActor to RemoverActor - case RemoverActor.StartDelete() => - firstTask.cancel - secondTask.cancel - val now : Long = System.nanoTime - val (in, out) = firstHeap.partition(entry => { now - entry.time >= entry.duration }) - firstHeap = out - secondHeap = secondHeap ++ in.map { RepackageEntry } - in.foreach { FirstJob } - RetimeFirstTask() - if(secondHeap.nonEmpty) { - import scala.concurrent.ExecutionContext.Implicits.global - secondTask = context.system.scheduler.scheduleOnce(SecondStandardDuration, self, RemoverActor.TryDelete()) - } - trace(s"item removal task has found ${in.size} items to remove") - - case RemoverActor.TryDelete() => - secondTask.cancel - val (in, out) = secondHeap.partition { ClearanceTest } - secondHeap = out - in.foreach { SecondJob } - if(out.nonEmpty) { - import scala.concurrent.ExecutionContext.Implicits.global - secondTask = context.system.scheduler.scheduleOnce(SecondStandardDuration, self, RemoverActor.TryDelete()) - } - trace(s"item removal task has removed ${in.size} items") - - case RemoverActor.FailureToWork(entry, ex) => - log.error(s"${entry.obj} from ${entry.zone} not properly deleted - $ex") - - case _ => ; - } + case _ => ; + } /** * Expedite some entries from the first pool into the second. @@ -175,14 +166,18 @@ abstract class RemoverActor extends Actor { * all targets must be in this zone, with the assumption that this is the zone where they were registered */ def HurrySpecific(targets : List[PlanetSideGameObject], zone : Zone) : Unit = { - CullTargetsFromFirstHeap(targets, zone) match { - case Nil => + PartitionTargetsFromList(firstHeap, targets.map { RemoverActor.Entry(_, zone, 0) }, zone) match { + case (Nil, _) => debug(s"no tasks matching the targets $targets have been hurried") - case list => - debug(s"the following tasks have been hurried: $list") + case (in, out) => + debug(s"the following tasks have been hurried: $in") + firstHeap = out //.sortBy(entry => entry.time + entry.duration) + if(out.nonEmpty) { + RetimeFirstTask() + } secondTask.cancel - list.foreach { FirstJob } - secondHeap = secondHeap ++ list.map { RepackageEntry } + in.foreach { FirstJob } + secondHeap = secondHeap ++ in.map { RepackageEntry } import scala.concurrent.ExecutionContext.Implicits.global secondTask = context.system.scheduler.scheduleOnce(SecondStandardDuration, self, RemoverActor.TryDelete()) } @@ -206,11 +201,15 @@ abstract class RemoverActor extends Actor { * Remove specific entries from the first pool. */ def ClearSpecific(targets : List[PlanetSideGameObject], zone : Zone) : Unit = { - CullTargetsFromFirstHeap(targets, zone) match { - case Nil => + PartitionTargetsFromList(firstHeap, targets.map { RemoverActor.Entry(_, zone, 0) }, zone) match { + case (Nil, _) => debug(s"no tasks matching the targets $targets have been cleared") - case list => - debug(s"the following tasks have been cleared: $list") + case (in, out) => + debug(s"the following tasks have been cleared: $in") + firstHeap = out //.sortBy(entry => entry.time + entry.duration) + if(out.nonEmpty) { + RetimeFirstTask() + } } } @@ -232,68 +231,6 @@ abstract class RemoverActor extends Actor { RemoverActor.Entry(entry.obj, entry.zone, SecondStandardDuration.toNanos) } - /** - * Search the first pool of entries awaiting removal processing. - * If any entry has the same object as one of the targets and belongs to the same zone, remove it from the first pool. - * If no targets are selected (an empty list), all discovered targets within the appropriate zone are removed. - * @param targets a list of objects to pick - * @param zone the zone in which these objects must be discovered; - * all targets must be in this zone, with the assumption that this is the zone where they were registered - * @return all of the discovered entries - */ - private def CullTargetsFromFirstHeap(targets : List[PlanetSideGameObject], zone : Zone) : List[RemoverActor.Entry] = { - val culledEntries = if(targets.nonEmpty) { - if(targets.size == 1) { - debug(s"a target submitted: ${targets.head}") - //simple selection - RemoverActor.recursiveFind(firstHeap.iterator, RemoverActor.Entry(targets.head, zone, 0)) match { - case None => ; - Nil - case Some(index) => - val entry = firstHeap(index) - firstHeap = (firstHeap.take(index) ++ firstHeap.drop(index + 1)).sortBy(_.duration) - List(entry) - } - } - else { - debug(s"multiple targets submitted: $targets") - //cumbersome partition - //a - find targets from entries - val locatedTargets = for { - a <- targets.map(RemoverActor.Entry(_, zone, 0)) - b <- firstHeap//.filter(entry => entry.zone == zone) - if b.obj.HasGUID && a.obj.HasGUID && RemoverActor.Similarity(b, a) - } yield b - if(locatedTargets.nonEmpty) { - //b - entries, after the found targets are removed (cull any non-GUID entries while at it) - firstHeap = (for { - a <- locatedTargets - b <- firstHeap - if b.obj.HasGUID && a.obj.HasGUID && !RemoverActor.Similarity(b, a) - } yield b).sortBy(_.duration) - locatedTargets - } - else { - Nil - } - } - } - else { - debug(s"all targets within the specified zone $zone will be submitted") - //no specific targets; split on all targets in the given zone instead - val (in, out) = firstHeap.partition(entry => entry.zone == zone) - firstHeap = out.sortBy(_.duration) - in - } - if(culledEntries.nonEmpty) { - RetimeFirstTask() - culledEntries - } - else { - Nil - } - } - /** * Common function to reset the first task's delayed execution. * Cancels the scheduled timer and will only restart the timer if there is at least one entry in the first pool. @@ -353,15 +290,6 @@ abstract class RemoverActor extends Actor { */ def SecondStandardDuration : FiniteDuration - /** - * Determine whether or not the resulting entry is valid for this removal process. - * The primary purpose of this function should be to determine if the appropriate type of object is being submitted. - * Override. - * @param entry the entry - * @return `true`, if it can be processed; `false`, otherwise - */ - def InclusionTest(entry : RemoverActor.Entry) : Boolean - /** * Performed when the entry is initially added to the first list. * Override. @@ -392,23 +320,15 @@ abstract class RemoverActor extends Actor { def DeletionTask(entry : RemoverActor.Entry) : TaskResolver.GiveTask } -object RemoverActor { +object RemoverActor extends SupportActorCaseConversions { /** * All information necessary to apply to the removal process to produce an effect. * Internally, all entries have a "time created" field. - * @param obj the target - * @param zone the zone in which this target is registered - * @param duration how much longer the target will exist in its current state (in nanoseconds) + * @param _obj the target + * @param _zone the zone in which this target is registered + * @param _duration how much longer the target will exist in its current state (in nanoseconds) */ - case class Entry(obj : PlanetSideGameObject, zone : Zone, duration : Long) { - /** The time when this entry was created (in nanoseconds) */ - val time : Long = System.nanoTime - } - - /** - * A message that prompts the retrieval of a `TaskResolver` for us in the removal process. - */ - case class Startup() + case class Entry(_obj : PlanetSideGameObject, _zone : Zone, _duration : Long) extends SupportActor.Entry(_obj, _zone, _duration) /** * Message to submit an object to the removal process. @@ -420,36 +340,6 @@ object RemoverActor { */ case class AddTask(obj : PlanetSideGameObject, zone : Zone, duration : Option[FiniteDuration] = None) - /** - * "Hurrying" shifts entries with the discovered objects (in the same `zone`) - * through their first task and into the second pool. - * If the list of targets is empty, all discovered objects in the given zone will be considered targets. - * @param targets a list of objects to match - * @param zone the zone in which these objects exist; - * the assumption is that all these target objects are registered to this zone - */ - case class HurrySpecific(targets : List[PlanetSideGameObject], zone : Zone) - /** - * "Hurrying" shifts all entries through their first task and into the second pool. - */ - case class HurryAll() - - /** - * "Clearing" cancels entries with the discovered objects (in the same `zone`) - * if they are discovered in the first pool of objects. - * Those entries will no longer be affected by any actions performed by the removal process until re-submitted. - * If the list of targets is empty, all discovered objects in the given zone will be considered targets. - * @param targets a list of objects to match - * @param zone the zone in which these objects exist; - * the assumption is that all these target objects are registered to this zone - */ - case class ClearSpecific(targets : List[PlanetSideGameObject], zone : Zone) - /** - * "Clearing" cancels all entries if they are discovered in the first pool of objects. - * Those entries will no longer be affected by any actions performed by the removal process until re-submitted. - */ - case class ClearAll() - /** * Message that indicates that the final stage of the remover process has failed. * Since the last step is generally unregistering the object, it could be a critical error. @@ -467,36 +357,4 @@ object RemoverActor { * Internal message to flag operations by data in the second list if it has been in that list long enough. */ private final case class TryDelete() - - /** - * Match two entries by object and by zone information. - * @param entry1 the first entry - * @param entry2 the second entry - * @return if they match - */ - private def Similarity(entry1 : RemoverActor.Entry, entry2 : RemoverActor.Entry) : Boolean = { - entry1.obj == entry2.obj && entry1.zone == entry2.zone && entry1.obj.GUID == entry2.obj.GUID - } - - /** - * Get the index of an entry in the list of entries. - * @param iter an `Iterator` of entries - * @param target the specific entry to be found - * @param index the incrementing index value - * @return the index of the entry in the list, if a match to the target is found - */ - @tailrec private def recursiveFind(iter : Iterator[RemoverActor.Entry], target : RemoverActor.Entry, index : Int = 0) : Option[Int] = { - if(!iter.hasNext) { - None - } - else { - val entry = iter.next - if(entry.obj.HasGUID && target.obj.HasGUID && Similarity(entry, target)) { - Some(index) - } - else { - recursiveFind(iter, target, index + 1) - } - } - } } diff --git a/common/src/main/scala/services/Service.scala b/common/src/main/scala/services/Service.scala index 0410e0fc..f3a57038 100644 --- a/common/src/main/scala/services/Service.scala +++ b/common/src/main/scala/services/Service.scala @@ -8,6 +8,7 @@ import net.psforever.packet.game.PlanetSideGUID object Service { final val defaultPlayerGUID : PlanetSideGUID = PlanetSideGUID(0) + final case class Startup() final case class Join(channel : String) final case class Leave(channel : Option[String] = None) final case class LeaveAll() diff --git a/common/src/main/scala/services/support/SimilarityComparator.scala b/common/src/main/scala/services/support/SimilarityComparator.scala new file mode 100644 index 00000000..4538e1a7 --- /dev/null +++ b/common/src/main/scala/services/support/SimilarityComparator.scala @@ -0,0 +1,12 @@ +// Copyright (c) 2017 PSForever +package services.support + +abstract class SimilarityComparator[A <: SupportActor.Entry] { + /** + * Match two entries by object and by zone information. + * @param entry1 the first entry + * @param entry2 the second entry + * @return if they match + */ + def Test(entry1 : A, entry2 : A) : Boolean +} diff --git a/common/src/main/scala/services/support/SupportActor.scala b/common/src/main/scala/services/support/SupportActor.scala new file mode 100644 index 00000000..820bcee8 --- /dev/null +++ b/common/src/main/scala/services/support/SupportActor.scala @@ -0,0 +1,155 @@ +// Copyright (c) 2017 PSForever +package services.support + +import akka.actor.Actor +import net.psforever.objects.PlanetSideGameObject +import net.psforever.objects.zones.Zone + +import scala.annotation.tailrec + +abstract class SupportActor[A <: SupportActor.Entry] extends Actor { + private[this] val log = org.log4s.getLogger + def info(msg : String) : Unit = log.info(msg) + def trace(msg : String) : Unit = log.trace(msg) + def debug(msg : String) : Unit = log.debug(msg) + + def sameEntryComparator : SimilarityComparator[A] + + /** + * Determine whether or not the resulting entry is valid for this process. + * The primary purpose of this function should be to determine if the appropriate type of object is being submitted. + * Override. + * @param entry the entry + * @return `true`, if it can be processed; `false`, otherwise + */ + def InclusionTest(entry : A) : Boolean + + def entryManagementBehaviors : Receive = { + case SupportActor.HurrySpecific(targets, zone) => + HurrySpecific(targets, zone) + + case SupportActor.HurryAll() => + HurryAll() + + case SupportActor.ClearSpecific(targets, zone) => + ClearSpecific(targets, zone) + + case SupportActor.ClearAll() => + ClearAll() + } + + def HurrySpecific(targets : List[PlanetSideGameObject], zone : Zone) : Unit + + def HurryAll() + + def ClearSpecific(targets : List[PlanetSideGameObject], zone : Zone) : Unit + + def ClearAll() : Unit + + /* + * Search the first pool of entries awaiting removal processing. + * If any entry has the same object as one of the targets and belongs to the same zone, remove it from the first pool. + * If no targets are selected (an empty list), all discovered targets within the appropriate zone are removed. + * @param targets a list of objects to pick + * @param zone the zone in which these objects must be discovered; + * all targets must be in this zone, with the assumption that this is the zone where they were registered + * @return all of the discovered entries + */ + def PartitionTargetsFromList(list : List[A], targets : List[A], zone : Zone, comparator : SimilarityComparator[A] = sameEntryComparator) : (List[A], List[A]) = { + if(targets.nonEmpty) { + if(targets.size == 1) { + debug(s"a target submitted: ${targets.head}") + //simple selection + SupportActor.recursiveFind(comparator)(list.iterator, targets.head) match { + case None => ; + (Nil, list) + case Some(index) => + (List(list(index)), list.take(index) ++ list.drop(index + 1)) + } + } + else { + debug(s"multiple targets submitted: $targets") + //cumbersome partition + //a - find targets from entries + val locatedTargets = for { + a <- targets + b <- list//.filter(entry => entry.zone == zone) + if b.obj.HasGUID && a.obj.HasGUID && comparator.Test(b, a) + } yield b + if(locatedTargets.nonEmpty) { + //b - entries, after the found targets are removed (cull any non-GUID entries while at it) + val retained = for { + a <- locatedTargets + b <- list + if b.obj.HasGUID && a.obj.HasGUID && !comparator.Test(b, a) + } yield b + (locatedTargets, retained) + } + else { + (Nil, list) + } + } + } + else { + list.partition(entry => entry.zone == zone) + } + } +} + +object SupportActor { + class Entry(val obj : PlanetSideGameObject, val zone : Zone, val duration : Long) { + val time : Long = System.nanoTime + } + + /** + * "Hurrying" shifts entries with the discovered objects (in the same `zone`) + * through their first task and into the second pool. + * If the list of targets is empty, all discovered objects in the given zone will be considered targets. + * @param targets a list of objects to match + * @param zone the zone in which these objects exist; + * the assumption is that all these target objects are registered to this zone + */ + case class HurrySpecific(targets : List[PlanetSideGameObject], zone : Zone) + /** + * "Hurrying" shifts all entries through their first task and into the second pool. + */ + case class HurryAll() + + /** + * "Clearing" cancels entries with the discovered objects (in the same `zone`) + * if they are discovered in the first pool of objects. + * Those entries will no longer be affected by any actions performed by the removal process until re-submitted. + * If the list of targets is empty, all discovered objects in the given zone will be considered targets. + * @param targets a list of objects to match + * @param zone the zone in which these objects exist; + * the assumption is that all these target objects are registered to this zone + */ + case class ClearSpecific(targets : List[PlanetSideGameObject], zone : Zone) + /** + * "Clearing" cancels all entries if they are discovered in the first pool of objects. + * Those entries will no longer be affected by any actions performed by the removal process until re-submitted. + */ + case class ClearAll() + + /** + * Get the index of an entry in the list of entries. + * @param iter an `Iterator` of entries + * @param target the specific entry to be found + * @param index the incrementing index value + * @return the index of the entry in the list, if a match to the target is found + */ + @tailrec private def recursiveFind[A <: SupportActor.Entry](comparator : SimilarityComparator[A])(iter : Iterator[A], target : A, index : Int = 0) : Option[Int] = { + if(!iter.hasNext) { + None + } + else { + val entry = iter.next + if(entry.obj.HasGUID && target.obj.HasGUID && comparator.Test(entry, target)) { + Some(index) + } + else { + recursiveFind(comparator)(iter, target, index + 1) + } + } + } +} diff --git a/common/src/main/scala/services/support/SupportActorCaseConversions.scala b/common/src/main/scala/services/support/SupportActorCaseConversions.scala new file mode 100644 index 00000000..e20f5a8d --- /dev/null +++ b/common/src/main/scala/services/support/SupportActorCaseConversions.scala @@ -0,0 +1,37 @@ +// Copyright (c) 2017 PSForever +package services.support + +import net.psforever.objects.PlanetSideGameObject +import net.psforever.objects.zones.Zone + +trait SupportActorCaseConversions { + /** + * A mask for converting between a class local and `SupportActor.HurrySpecific`. + * @param targets a list of objects to match + * @param zone the zone in which these objects exist + * @return a `SupportActor.HurrySpecific` object + */ + def HurrySpecific(targets : List[PlanetSideGameObject], zone : Zone) : SupportActor.HurrySpecific = + SupportActor.HurrySpecific(targets, zone) + + /** + * A mask for converting between a class local and `SupportActor.HurryAll`. + * @return a `SupportActor.HurryAll` object + */ + def HurryAll() : SupportActor.HurryAll = SupportActor.HurryAll() + + /** + * A mask for converting between a class local and `SupportActor.ClearSpecific`. + * @param targets a list of objects to match + * @param zone the zone in which these objects exist + * @return a `SupportActor.ClearSpecific` object + */ + def ClearSpecific(targets : List[PlanetSideGameObject], zone : Zone) : SupportActor.ClearSpecific = + SupportActor.ClearSpecific(targets, zone) + + /** + * A mask for converting between a class local and `SupportActor.ClearAll`. + * @return a `SupportActor.ClearAll` object + */ + def ClearAll() : SupportActor.ClearAll = SupportActor.ClearAll() +} diff --git a/common/src/main/scala/services/vehicle/VehicleAction.scala b/common/src/main/scala/services/vehicle/VehicleAction.scala index e5d62353..604256cf 100644 --- a/common/src/main/scala/services/vehicle/VehicleAction.scala +++ b/common/src/main/scala/services/vehicle/VehicleAction.scala @@ -15,6 +15,7 @@ object VehicleAction { final case class ChildObjectState(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, pitch : Float, yaw : Float) extends Action final case class DeployRequest(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, state : DriveState.Value, unk1 : Int, unk2 : Boolean, pos : Vector3) extends Action final case class DismountVehicle(player_guid : PlanetSideGUID, bailType : BailType.Value, unk2 : Boolean) extends Action + final case class EquipmentInSlot(player_guid : PlanetSideGUID, target_guid : PlanetSideGUID, slot : Int, equipment : Equipment) extends Action final case class InventoryState(player_guid : PlanetSideGUID, obj : PlanetSideGameObject, parent_guid : PlanetSideGUID, start : Int, con_data : ConstructorData) extends Action final case class InventoryState2(player_guid : PlanetSideGUID, obj_guid : PlanetSideGUID, parent_guid : PlanetSideGUID, value : Int) extends Action final case class KickPassenger(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean, vehicle_guid : PlanetSideGUID) extends Action diff --git a/common/src/main/scala/services/vehicle/VehicleResponse.scala b/common/src/main/scala/services/vehicle/VehicleResponse.scala index d34a0e2e..e083bd24 100644 --- a/common/src/main/scala/services/vehicle/VehicleResponse.scala +++ b/common/src/main/scala/services/vehicle/VehicleResponse.scala @@ -1,10 +1,11 @@ // Copyright (c) 2017 PSForever package services.vehicle +import net.psforever.objects.equipment.Equipment import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.{PlanetSideGameObject, Vehicle} import net.psforever.packet.PlanetSideGamePacket -import net.psforever.packet.game.PlanetSideGUID +import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID} import net.psforever.packet.game.objectcreate.ConstructorData import net.psforever.types.{BailType, DriveState, Vector3} @@ -17,6 +18,7 @@ object VehicleResponse { final case class DeployRequest(object_guid : PlanetSideGUID, state : DriveState.Value, unk1 : Int, unk2 : Boolean, pos : Vector3) extends Response final case class DetachFromRails(vehicle_guid : PlanetSideGUID, rails_guid : PlanetSideGUID, rails_pos : Vector3, rails_rot : Float) extends Response final case class DismountVehicle(bailType : BailType.Value , unk2 : Boolean) extends Response + final case class EquipmentInSlot(pkt : ObjectCreateMessage) extends Response final case class InventoryState(obj : PlanetSideGameObject, parent_guid : PlanetSideGUID, start : Int, con_data : ConstructorData) extends Response final case class InventoryState2(obj_guid : PlanetSideGUID, parent_guid : PlanetSideGUID, value : Int) extends Response final case class KickPassenger(seat_num : Int, kickedByDriver : Boolean, vehicle_guid : PlanetSideGUID) extends Response diff --git a/common/src/main/scala/services/vehicle/VehicleService.scala b/common/src/main/scala/services/vehicle/VehicleService.scala index aaa8de47..6f347da7 100644 --- a/common/src/main/scala/services/vehicle/VehicleService.scala +++ b/common/src/main/scala/services/vehicle/VehicleService.scala @@ -4,7 +4,9 @@ package services.vehicle import akka.actor.{Actor, ActorRef, Props} import net.psforever.objects.serverobject.pad.VehicleSpawnPad import net.psforever.objects.zones.Zone -import services.vehicle.support.VehicleRemover +import net.psforever.packet.game.ObjectCreateMessage +import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent +import services.vehicle.support.{TurretUpgrader, VehicleRemover} import net.psforever.types.DriveState import services.{GenericEventBus, RemoverActor, Service} @@ -12,6 +14,7 @@ import scala.concurrent.duration._ class VehicleService extends Actor { private val vehicleDecon : ActorRef = context.actorOf(Props[VehicleRemover], "vehicle-decon-agent") + private val turretUpgrade : ActorRef = context.actorOf(Props[TurretUpgrader], "turret-upgrade-agent") private [this] val log = org.log4s.getLogger override def preStart = { @@ -53,6 +56,18 @@ class VehicleService extends Actor { VehicleEvents.publish( VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.DismountVehicle(bailType, unk2)) ) + case VehicleAction.EquipmentInSlot(player_guid, target_guid, slot, equipment) => + val definition = equipment.Definition + val pkt = ObjectCreateMessage( + definition.ObjectId, + equipment.GUID, + ObjectCreateMessageParent(target_guid, slot), + definition.Packet.ConstructorData(equipment).get + ) + ObjectCreateMessageParent(target_guid, slot) + VehicleEvents.publish( + VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.EquipmentInSlot(pkt)) + ) case VehicleAction.InventoryState(player_guid, obj, parent_guid, start, con_data) => VehicleEvents.publish( VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.InventoryState(obj, parent_guid, start, con_data)) @@ -114,6 +129,10 @@ class VehicleService extends Actor { case VehicleServiceMessage.Decon(msg) => vehicleDecon forward msg + //message to TurretUpgrader + case VehicleServiceMessage.TurretUpgrade(msg) => + turretUpgrade forward msg + //from VehicleSpawnControl case VehicleSpawnPad.ConcealPlayer(player_guid, zone_id) => VehicleEvents.publish( diff --git a/common/src/main/scala/services/vehicle/VehicleServiceMessage.scala b/common/src/main/scala/services/vehicle/VehicleServiceMessage.scala index 8ed071e0..9a1d877a 100644 --- a/common/src/main/scala/services/vehicle/VehicleServiceMessage.scala +++ b/common/src/main/scala/services/vehicle/VehicleServiceMessage.scala @@ -12,5 +12,7 @@ object VehicleServiceMessage { final case class Decon(msg : Any) + final case class TurretUpgrade(msg : Any) + final case class AMSDeploymentChange(zone : Zone) } diff --git a/common/src/main/scala/services/vehicle/support/TurretUpgrader.scala b/common/src/main/scala/services/vehicle/support/TurretUpgrader.scala new file mode 100644 index 00000000..73613ade --- /dev/null +++ b/common/src/main/scala/services/vehicle/support/TurretUpgrader.scala @@ -0,0 +1,260 @@ +// Copyright (c) 2017 PSForever +package services.vehicle.support + +import akka.actor.{Actor, ActorRef, Cancellable} +import net.psforever.objects.{AmmoBox, DefaultCancellable, PlanetSideGameObject, Tool} +import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver} +import net.psforever.objects.serverobject.turret.{MannedTurret, TurretUpgrade} +import net.psforever.objects.vehicles.MountedWeapons +import net.psforever.objects.zones.Zone +import net.psforever.packet.game.PlanetSideGUID +import services.support.{SimilarityComparator, SupportActor, SupportActorCaseConversions} +import services.vehicle.{VehicleAction, VehicleServiceMessage} +import services.{Service, ServiceManager} + +import scala.concurrent.duration._ + +class TurretUpgrader extends SupportActor[TurretUpgrader.Entry] { + var task : Cancellable = DefaultCancellable.obj + + var list : List[TurretUpgrader.Entry] = List() + + private var taskResolver : ActorRef = Actor.noSender + + val sameEntryComparator = new SimilarityComparator[TurretUpgrader.Entry]() { + def Test(entry1 : TurretUpgrader.Entry, entry2 : TurretUpgrader.Entry) : Boolean = { + entry1.obj == entry2.obj && entry1.zone == entry2.zone && entry1.obj.GUID == entry2.obj.GUID + } + } + + /** + * Send the initial message that requests a task resolver for assisting in the removal process. + */ + override def preStart() : Unit = { + super.preStart() + self ! Service.Startup() + } + + /** + * Sufficiently clean up the current contents of these waiting removal jobs. + * Cancel all timers, rush all entries in the lists through their individual steps, then empty the lists. + * This is an improved `HurryAll`. + */ + override def postStop() : Unit = { + super.postStop() + task.cancel + list.foreach { UpgradeTurretAmmo } + list = Nil + taskResolver = ActorRef.noSender + } + + def CreateEntry(obj : PlanetSideGameObject, zone : Zone, upgrade : TurretUpgrade.Value, duration : Long) = TurretUpgrader.Entry(obj, zone, upgrade, duration) + + def InclusionTest(entry : TurretUpgrader.Entry) : Boolean = entry.obj.isInstanceOf[MannedTurret] + + def receive : Receive = { + case Service.Startup() => + ServiceManager.serviceManager ! ServiceManager.Lookup("taskResolver") //ask for a resolver to deal with the GUID system + + case ServiceManager.LookupResult("taskResolver", endpoint) => + taskResolver = endpoint + context.become(Processing) + + case msg => + debug(s"received message $msg before being properly initialized") + } + + def Processing : Receive = entryManagementBehaviors + .orElse { + case TurretUpgrader.AddTask(turret, zone, upgrade, duration) => + val lengthOfTime = duration.getOrElse(TurretUpgrader.StandardUpgradeLifetime).toNanos + if(lengthOfTime > (1 second).toNanos) { //don't even bother if it's too short; it'll revert near instantly + val entry = CreateEntry(turret, zone, TurretUpgrade.None, lengthOfTime) + UpgradeTurretAmmo(CreateEntry(turret, zone, upgrade, lengthOfTime)) + if(list.isEmpty) { + //we were the only entry so the event must be started from scratch + list = List(entry) + trace(s"a task has been added: $entry") + RetimeFirstTask() + } + else{ + val oldHead = list.head + if(!list.exists(test => TurretUpgrader.Similarity(test, entry))) { + list = (list :+ entry).sortBy(entry => entry.time + entry.duration) + trace(s"a task has been added: $entry") + if(oldHead != list.head) { + RetimeFirstTask() + } + } + } + } + + case TurretUpgrader.Downgrade() => + task.cancel + val now : Long = System.nanoTime + val (in, out) = list.partition(entry => { now - entry.time >= entry.duration }) //&& entry.obj.Seats.values.count(_.isOccupied) == 0 + list = out + in.foreach { UpgradeTurretAmmo } + RetimeFirstTask() + + case _ => ; + } + + def RetimeFirstTask(now : Long = System.nanoTime) : Unit = { + task.cancel + if(list.nonEmpty) { + val short_timeout : FiniteDuration = math.max(1, list.head.duration - (now - list.head.time)) nanoseconds + import scala.concurrent.ExecutionContext.Implicits.global + task = context.system.scheduler.scheduleOnce(short_timeout, self, TurretUpgrader.Downgrade()) + } + } + + def HurrySpecific(targets : List[PlanetSideGameObject], zone : Zone) : Unit = { + PartitionTargetsFromList(list, targets.map { TurretUpgrader.Entry(_, zone, TurretUpgrade.None, 0) }, zone) match { + case (Nil, _) => + debug(s"no tasks matching the targets $targets have been hurried") + case (in, out) => + debug(s"the following tasks have been hurried: $in") + in.foreach { UpgradeTurretAmmo } + list = out //.sortBy(entry => entry.time + entry.duration) + if(out.nonEmpty) { + RetimeFirstTask() + } + } + } + + def HurryAll() : Unit = { + trace("all tasks have been hurried") + task.cancel + list.foreach { UpgradeTurretAmmo } + list = Nil + } + + def ClearSpecific(targets : List[PlanetSideGameObject], zone : Zone) : Unit = { + PartitionTargetsFromList(list, targets.map { TurretUpgrader.Entry(_, zone, TurretUpgrade.None, 0) }, zone) match { + case (Nil, _) => + debug(s"no tasks matching the targets $targets have been cleared") + case (in, out) => + debug(s"the following tasks have been cleared: $in") + list = out //.sortBy(entry => entry.time + entry.duration) + if(out.nonEmpty) { + RetimeFirstTask() + } + } + } + + def ClearAll() : Unit = { + task.cancel + list = Nil + } + + /** + * The process of upgrading a turret is nearly complete. + * After the upgrade status is changed, the internal structure of the turret's weapons change to suit the configuration. + * Of special importance are internal ammo supplies of the changing weapon, + * the original ammunition that must be un-registered, + * and the new boxes that must be registered so the weapon may be introduced into the game world properly. + * @param entry na + */ + def UpgradeTurretAmmo(entry : TurretUpgrader.Entry) : Unit = { + val target = entry.obj.asInstanceOf[MannedTurret] + val zone = entry.zone + val zoneId = zone.Id + val upgrade = entry.upgrade + val guid = zone.GUID + val turretGUID = target.GUID + //kick all occupying players for duration of conversion + target.Seats.values + .filter { _.isOccupied } + .foreach({seat => + val tplayer = seat.Occupant.get + seat.Occupant = None + tplayer.VehicleSeated = None + if(tplayer.HasGUID) { + context.parent ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(tplayer.GUID, 4, false, turretGUID)) + } + }) + info(s"Converting manned wall turret weapon to $upgrade") + + val oldBoxesTask = AllMountedWeaponMagazines(target) + .map(box => GUIDTask.UnregisterEquipment(box)(guid)) + .toList + target.Upgrade = upgrade //perform upgrade + + val newBoxesTask = TaskResolver.GiveTask( + new Task() { + private val localFunc : ()=>Unit = FinishUpgradingTurret(entry) + + override def isComplete = Task.Resolution.Success + + def Execute(resolver : ActorRef) : Unit = { + localFunc() + resolver ! scala.util.Success(this) + } + }, AllMountedWeaponMagazines(target).map(box => GUIDTask.RegisterEquipment(box)(guid)).toList + ) + taskResolver ! TaskResolver.GiveTask( + new Task() { + def Execute(resolver : ActorRef) : Unit = { + resolver ! scala.util.Success(this) + } + }, oldBoxesTask :+ newBoxesTask + ) + } + + /** + * From an object that has mounted weapons, parse all of the internal ammunition loaded into all of the weapons. + * @param target the object with mounted weaponry + * @return all of the internal ammunition objects + */ + def AllMountedWeaponMagazines(target : MountedWeapons) : Iterable[AmmoBox] = { + target.Weapons + .values + .map { _.Equipment } + .collect { case Some(tool : Tool) => tool.AmmoSlots } + .flatMap { _.map { _.Box } } + } + + /** + * Finish upgrading the turret by announcing to other players that the weapon type has changed. + * By this point, a prior required action that required that new ammunition objects had to be registered. + * It is now safe to announce that clients can update to the new weapon. + * @param entry na + */ + def FinishUpgradingTurret(entry : TurretUpgrader.Entry)() : Unit = { + val target = entry.obj.asInstanceOf[MannedTurret] + val zone = entry.zone + info(s"Wall turret finished ${target.Upgrade} upgrade") + val targetGUID = target.GUID + target.Weapons + .map({ case (index, slot) => (index, slot.Equipment) }) + .collect { case (index, Some(tool : Tool)) => + context.parent ! VehicleServiceMessage( + zone.Id, + VehicleAction.EquipmentInSlot(PlanetSideGUID(0), targetGUID, index, tool) + ) + } + } +} + +object TurretUpgrader extends SupportActorCaseConversions { + private val StandardUpgradeLifetime : FiniteDuration = 30 minutes + + /** + * All information necessary to apply to the removal process to produce an effect. + * Internally, all entries have a "time created" field. + * @param _obj the target + * @param _zone the zone in which this target is registered + * @param upgrade the next upgrade state for this turret + * @param _duration how much longer the target will exist in its current state (in nanoseconds) + */ + case class Entry(_obj : PlanetSideGameObject, _zone : Zone, upgrade : TurretUpgrade.Value, _duration : Long) extends SupportActor.Entry(_obj, _zone, _duration) + + final case class AddTask(turret : MannedTurret, zone : Zone, upgrade : TurretUpgrade.Value, duration : Option[FiniteDuration] = None) + + final case class Downgrade() + + private def Similarity(entry1 : TurretUpgrader.Entry, entry2 : TurretUpgrader.Entry) : Boolean = { + entry1.obj == entry2.obj && entry1.zone == entry2.zone && entry1.obj.GUID == entry2.obj.GUID + } +} diff --git a/common/src/test/scala/base/ActorTest.scala b/common/src/test/scala/base/ActorTest.scala new file mode 100644 index 00000000..a1d594fc --- /dev/null +++ b/common/src/test/scala/base/ActorTest.scala @@ -0,0 +1,51 @@ +package base + +// Copyright (c) 2017 PSForever +import akka.actor.ActorSystem +import akka.testkit.{ImplicitSender, TestKit} +import com.typesafe.config.ConfigFactory +import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} +import org.specs2.specification.Scope + +import scala.collection.mutable +import scala.concurrent.duration.FiniteDuration + +abstract class ActorTest(sys : ActorSystem = ActorSystem("system", ConfigFactory.parseMap(ActorTest.LoggingConfig))) + extends TestKit(sys) with Scope with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll { + override def afterAll { + TestKit.shutdownActorSystem(system) + } +} + +object ActorTest { + import scala.collection.JavaConverters._ + private val LoggingConfig = Map( + "akka.loggers" -> List("akka.testkit.TestEventListener").asJava, + "akka.loglevel" -> "OFF", + "akka.stdout-loglevel" -> "OFF", + "akka.log-dead-letters" -> "OFF" + ).asJava + + /** + * A (potential) workaround to a Travis CI issue involving polling a series of messages over a period of time. + * Running the test in isolation works every time. + * Running the test as part of a series produces mixed results. + * Travis CI fails the test every time by not getting any messages. + * @see TestKit.receiveN + * @param n the number of messages to poll + * @param timeout how long to wait for each message + * @param sys what to poll + * @return a list of messages + */ + def receiveMultiple(n : Int, timeout : FiniteDuration, sys : TestKit) : List[Any] = { + assert(0 < n, s"number of expected messages must be positive non-zero integer - $n") + val out = { + val msgs = mutable.ListBuffer[Any]() + (0 until n).foreach(_ => { + msgs += sys.receiveOne(timeout) + }) + msgs.toList + } + out + } +} diff --git a/common/src/test/scala/game/UseItemMessageTest.scala b/common/src/test/scala/game/UseItemMessageTest.scala index f6aca334..10c469a5 100644 --- a/common/src/test/scala/game/UseItemMessageTest.scala +++ b/common/src/test/scala/game/UseItemMessageTest.scala @@ -14,7 +14,7 @@ class UseItemMessageTest extends Specification { PacketCoding.DecodePacket(string).require match { case UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType) => avatar_guid mustEqual PlanetSideGUID(75) - unk1 mustEqual 0 + unk1 mustEqual PlanetSideGUID(0) object_guid mustEqual PlanetSideGUID(372) unk2 mustEqual 0xFFFFFFFFL unk3 mustEqual false @@ -30,7 +30,7 @@ class UseItemMessageTest extends Specification { } "encode" in { - val msg = UseItemMessage(PlanetSideGUID(75), 0, PlanetSideGUID(372), 0xFFFFFFFFL, false, Vector3(5.0f, 0.0f, 0.0f), Vector3(0.0f, 0.0f, 0.0f), 11, 25, 0, 364) + val msg = UseItemMessage(PlanetSideGUID(75), PlanetSideGUID(0), PlanetSideGUID(372), 0xFFFFFFFFL, false, Vector3(5.0f, 0.0f, 0.0f), Vector3(0.0f, 0.0f, 0.0f), 11, 25, 0, 364) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string diff --git a/common/src/test/scala/objects/ActorTest.scala b/common/src/test/scala/objects/ActorTest.scala deleted file mode 100644 index 6d37c315..00000000 --- a/common/src/test/scala/objects/ActorTest.scala +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2017 PSForever -package objects - -import akka.actor.ActorSystem -import akka.testkit.{ImplicitSender, TestKit} -import com.typesafe.config.{ConfigFactory, ConfigValueFactory} -import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} -import org.specs2.specification.Scope - -abstract class ActorTest(sys : ActorSystem = ActorSystem("system", ConfigFactory.parseMap(ActorTest.LoggingConfig))) - extends TestKit(sys) with Scope with ImplicitSender with WordSpecLike with Matchers with BeforeAndAfterAll { - override def afterAll { - TestKit.shutdownActorSystem(system) - } -} - -object ActorTest { - import scala.collection.JavaConverters._ - private val LoggingConfig = Map( - "akka.loggers" -> List("akka.testkit.TestEventListener").asJava, - "akka.loglevel" -> "OFF", - "akka.stdout-loglevel" -> "OFF", - "akka.log-dead-letters" -> "OFF" - ).asJava -} diff --git a/common/src/test/scala/objects/AutoDriveControlsTest.scala b/common/src/test/scala/objects/AutoDriveControlsTest.scala index e16b701e..5451092a 100644 --- a/common/src/test/scala/objects/AutoDriveControlsTest.scala +++ b/common/src/test/scala/objects/AutoDriveControlsTest.scala @@ -3,6 +3,7 @@ package objects import akka.actor.Props import akka.testkit.TestProbe +import base.ActorTest import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} import net.psforever.objects.{Avatar, GlobalDefinitions, Player, Vehicle} import net.psforever.objects.serverobject.pad.process.{AutoDriveControls, VehicleSpawnControlGuided} diff --git a/common/src/test/scala/objects/BuildingTest.scala b/common/src/test/scala/objects/BuildingTest.scala index e4c326b4..67159de9 100644 --- a/common/src/test/scala/objects/BuildingTest.scala +++ b/common/src/test/scala/objects/BuildingTest.scala @@ -2,6 +2,7 @@ package objects import akka.actor.{ActorRef, Props} +import base.ActorTest import net.psforever.objects.GlobalDefinitions import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.serverobject.affinity.FactionAffinity @@ -128,12 +129,11 @@ class BuildingControl2Test extends ActorTest { val bldg = Building(10, Zone.Nowhere, StructureType.Building) bldg.Faction = PlanetSideEmpire.TR bldg.Actor = system.actorOf(Props(classOf[BuildingControl], bldg), "test") + bldg.Actor ! "startup" "Building Control" should { "convert and assert faction affinity on convert request" in { - expectNoMsg(250 milliseconds) - bldg.Actor ! "startup" - expectNoMsg(250 milliseconds) + expectNoMsg(500 milliseconds) assert(bldg.Faction == PlanetSideEmpire.TR) bldg.Actor ! FactionAffinity.ConvertFactionAffinity(PlanetSideEmpire.VS) @@ -159,12 +159,11 @@ class BuildingControl3Test extends ActorTest { door2.Actor = system.actorOf(Props(classOf[DoorControl], door2), "door2-test") bldg.Amenities = door2 bldg.Amenities = door1 + bldg.Actor ! "startup" "Building Control" should { "convert and assert faction affinity on convert request, and for each of its amenities" in { - expectNoMsg(250 milliseconds) - bldg.Actor ! "startup" - expectNoMsg(250 milliseconds) + expectNoMsg(500 milliseconds) assert(bldg.Faction == PlanetSideEmpire.TR) assert(bldg.Amenities.length == 2) @@ -172,7 +171,8 @@ class BuildingControl3Test extends ActorTest { assert(bldg.Amenities(1) == door1) bldg.Actor ! FactionAffinity.ConvertFactionAffinity(PlanetSideEmpire.VS) - val reply = receiveN(3, Duration.create(500, "ms")) + val reply = ActorTest.receiveMultiple(3, 500 milliseconds, this) + //val reply = receiveN(3, Duration.create(5000, "ms")) assert(reply.length == 3) var building_count = 0 var door_count = 0 diff --git a/common/src/test/scala/objects/DeploymentTest.scala b/common/src/test/scala/objects/DeploymentTest.scala index 8321149e..ef11da72 100644 --- a/common/src/test/scala/objects/DeploymentTest.scala +++ b/common/src/test/scala/objects/DeploymentTest.scala @@ -2,6 +2,7 @@ package objects import akka.actor.{Actor, ActorSystem, Props} +import base.ActorTest import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.{GlobalDefinitions, Vehicle} import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior} diff --git a/common/src/test/scala/objects/DoorTest.scala b/common/src/test/scala/objects/DoorTest.scala index 76184282..ed14b731 100644 --- a/common/src/test/scala/objects/DoorTest.scala +++ b/common/src/test/scala/objects/DoorTest.scala @@ -2,6 +2,7 @@ package objects import akka.actor.{ActorRef, ActorSystem, Props} +import base.ActorTest import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.doors.{Door, DoorControl} import net.psforever.objects.serverobject.structures.{Building, StructureType} @@ -42,7 +43,7 @@ class DoorTest extends Specification { } "be opened and closed (2; toggle)" in { - val msg = UseItemMessage(PlanetSideGUID(6585), 0, PlanetSideGUID(372), 4294967295L, false, Vector3(5.0f,0.0f,0.0f), Vector3(0.0f,0.0f,0.0f), 11, 25, 0, 364) + val msg = UseItemMessage(PlanetSideGUID(6585), PlanetSideGUID(0), PlanetSideGUID(372), 4294967295L, false, Vector3(5.0f,0.0f,0.0f), Vector3(0.0f,0.0f,0.0f), 11, 25, 0, 364) val door = Door(GlobalDefinitions.door) door.Open mustEqual None door.Use(player, msg) @@ -74,7 +75,7 @@ class DoorTest extends Specification { } } -class DoorControl1Test extends ActorTest() { +class DoorControl1Test extends ActorTest { "DoorControl" should { "construct" in { val door = Door(GlobalDefinitions.door) @@ -84,11 +85,11 @@ class DoorControl1Test extends ActorTest() { } } -class DoorControl2Test extends ActorTest() { +class DoorControl2Test extends ActorTest { "DoorControl" should { "open on use" in { val (player, door) = DoorControlTest.SetUpAgents(PlanetSideEmpire.TR) - val msg = UseItemMessage(PlanetSideGUID(1), 0, PlanetSideGUID(2), 0L, false, Vector3(0f,0f,0f),Vector3(0f,0f,0f),0,0,0,0L) //faked + val msg = UseItemMessage(PlanetSideGUID(1), PlanetSideGUID(0), PlanetSideGUID(2), 0L, false, Vector3(0f,0f,0f),Vector3(0f,0f,0f),0,0,0,0L) //faked assert(door.Open.isEmpty) door.Actor ! Door.Use(player, msg) @@ -103,7 +104,7 @@ class DoorControl2Test extends ActorTest() { } } -class DoorControl3Test extends ActorTest() { +class DoorControl3Test extends ActorTest { "DoorControl" should { "do nothing if given garbage" in { val (_, door) = DoorControlTest.SetUpAgents(PlanetSideEmpire.TR) diff --git a/common/src/test/scala/objects/FactionAffinityTest.scala b/common/src/test/scala/objects/FactionAffinityTest.scala index a3163d9f..5334aab0 100644 --- a/common/src/test/scala/objects/FactionAffinityTest.scala +++ b/common/src/test/scala/objects/FactionAffinityTest.scala @@ -2,6 +2,7 @@ package objects import akka.actor.{Actor, ActorSystem, Props} +import base.ActorTest import net.psforever.objects.{GlobalDefinitions, Vehicle} import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.doors.Door @@ -55,7 +56,7 @@ class FactionAffinityTest extends Specification { } } -class FactionAffinity1Test extends ActorTest() { +class FactionAffinity1Test extends ActorTest { "FactionAffinity" should { "assert affinity on confirm request" in { val obj = FactionAffinityTest.SetUpAgent @@ -71,7 +72,7 @@ class FactionAffinity1Test extends ActorTest() { } } -class FactionAffinity2Test extends ActorTest() { +class FactionAffinity2Test extends ActorTest { "FactionAffinity" should { "assert affinity on assert request" in { val obj = FactionAffinityTest.SetUpAgent @@ -87,7 +88,7 @@ class FactionAffinity2Test extends ActorTest() { } } -class FactionAffinity3Test extends ActorTest() { +class FactionAffinity3Test extends ActorTest { "FactionAffinity" should { "convert and assert affinity on convert request" in { val obj = FactionAffinityTest.SetUpAgent diff --git a/common/src/test/scala/objects/IFFLockTest.scala b/common/src/test/scala/objects/IFFLockTest.scala index 9ac7044d..f8ae2903 100644 --- a/common/src/test/scala/objects/IFFLockTest.scala +++ b/common/src/test/scala/objects/IFFLockTest.scala @@ -2,6 +2,7 @@ package objects import akka.actor.{ActorRef, ActorSystem, Props} +import base.ActorTest import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.locks.{IFFLock, IFFLockControl} @@ -22,7 +23,7 @@ class IFFLockTest extends Specification { } } -class IFFLockControl1Test extends ActorTest() { +class IFFLockControl1Test extends ActorTest { "IFFLockControl" should { "construct" in { val lock = IFFLock(GlobalDefinitions.lock_external) @@ -32,7 +33,7 @@ class IFFLockControl1Test extends ActorTest() { } } -class IFFLockControl2Test extends ActorTest() { +class IFFLockControl2Test extends ActorTest { "IFFLockControl" should { "can hack" in { val (player, lock) = IFFLockControlTest.SetUpAgents(PlanetSideEmpire.TR) @@ -46,7 +47,7 @@ class IFFLockControl2Test extends ActorTest() { } } -class IFFLockControl3Test extends ActorTest() { +class IFFLockControl3Test extends ActorTest { "IFFLockControl" should { "can hack" in { val (player, lock) = IFFLockControlTest.SetUpAgents(PlanetSideEmpire.TR) diff --git a/common/src/test/scala/objects/LockerTest.scala b/common/src/test/scala/objects/LockerTest.scala index ae5ffb3a..b1f9d017 100644 --- a/common/src/test/scala/objects/LockerTest.scala +++ b/common/src/test/scala/objects/LockerTest.scala @@ -2,6 +2,7 @@ package objects import akka.actor.{ActorRef, Props} +import base.ActorTest import net.psforever.objects.GlobalDefinitions import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.mblocker.{Locker, LockerControl} diff --git a/common/src/test/scala/objects/MannedTurretTest.scala b/common/src/test/scala/objects/MannedTurretTest.scala new file mode 100644 index 00000000..2c85cc9a --- /dev/null +++ b/common/src/test/scala/objects/MannedTurretTest.scala @@ -0,0 +1,179 @@ +// Copyright (c) 2017 PSForever +package objects + +import akka.actor.{ActorRef, Props} +import base.ActorTest +import net.psforever.objects.{Avatar, GlobalDefinitions, Player, Tool} +import net.psforever.objects.definition.ToolDefinition +import net.psforever.objects.serverobject.mount.Mountable +import net.psforever.objects.serverobject.structures.{Building, StructureType} +import net.psforever.objects.serverobject.turret.{MannedTurret, MannedTurretControl, MannedTurretDefinition, TurretUpgrade} +import net.psforever.objects.zones.Zone +import net.psforever.packet.game.PlanetSideGUID +import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire} +import org.specs2.mutable.Specification + +import scala.collection.mutable +import scala.concurrent.duration._ + +class MannedTurretTest extends Specification { + "MannedTurretTest" should { + "define" in { + val obj = new MannedTurretDefinition(480) + obj.Weapons mustEqual mutable.HashMap.empty[TurretUpgrade.Value, ToolDefinition] + obj.ReserveAmmunition mustEqual false + obj.FactionLocked mustEqual true + obj.MaxHealth mustEqual 100 + obj.MountPoints mustEqual mutable.HashMap.empty[Int,Int] + } + + "construct" in { + val obj = MannedTurret(GlobalDefinitions.manned_turret) + obj.Weapons.size mustEqual 1 + obj.Weapons(1).Equipment match { + case Some(tool : Tool) => + tool.Definition mustEqual GlobalDefinitions.phalanx_sgl_hevgatcan + case _ => + ko + } + obj.Seats.size mustEqual 1 + obj.Seats(0).ControlledWeapon mustEqual Some(1) + obj.MountPoints.size mustEqual 1 + obj.MountPoints(1) mustEqual 0 + obj.Health mustEqual 3600 + obj.Upgrade mustEqual TurretUpgrade.None + obj.Jammered mustEqual false + + obj.Health = 360 + obj.Health mustEqual 360 + obj.Jammered = true + obj.Jammered mustEqual true + } + + "upgrade to a different weapon" in { + val obj = MannedTurret(GlobalDefinitions.manned_turret) + obj.Upgrade = TurretUpgrade.None + obj.Weapons(1).Equipment match { + case Some(tool : Tool) => + tool.Definition mustEqual GlobalDefinitions.phalanx_sgl_hevgatcan + tool.FireModeIndex mustEqual 0 + tool.NextFireMode + tool.FireModeIndex mustEqual 0 //one fire mode + case _ => + ko + } + //upgrade + obj.Upgrade = TurretUpgrade.AVCombo + obj.Weapons(1).Equipment match { + case Some(tool : Tool) => + tool.Definition mustEqual GlobalDefinitions.phalanx_avcombo + tool.FireModeIndex mustEqual 0 + tool.ProjectileType mustEqual GlobalDefinitions.phalanx_projectile.ProjectileType + tool.NextFireMode + tool.FireModeIndex mustEqual 1 + tool.ProjectileType mustEqual GlobalDefinitions.phalanx_av_projectile.ProjectileType + case _ => + ko + } + //revert + obj.Upgrade = TurretUpgrade.None + obj.Weapons(1).Equipment match { + case Some(tool : Tool) => + tool.Definition mustEqual GlobalDefinitions.phalanx_sgl_hevgatcan + case _ => + ko + } + } + } +} + +class MannedTurretControl1Test extends ActorTest { + "MannedTurretControl" should { + "construct" in { + val obj = MannedTurret(GlobalDefinitions.manned_turret) + obj.Actor = system.actorOf(Props(classOf[MannedTurretControl], obj), "turret-control") + assert(obj.Actor != ActorRef.noSender) + } + } +} + +class MannedTurretControl2Test extends ActorTest { + val player = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) + val obj = MannedTurret(GlobalDefinitions.manned_turret) + obj.GUID = PlanetSideGUID(1) + obj.Actor = system.actorOf(Props(classOf[MannedTurretControl], obj), "turret-control") + val bldg = Building(0, Zone.Nowhere, StructureType.Building) + bldg.Amenities = obj + bldg.Faction = PlanetSideEmpire.TR + + "MannedTurretControl" should { + "seat on faction affiliation when FactionLock is true" in { + assert(player.Faction == PlanetSideEmpire.TR) + assert(obj.Faction == PlanetSideEmpire.TR) + assert(obj.Definition.FactionLocked) + + obj.Actor ! Mountable.TryMount(player, 0) + val reply = receiveOne(300 milliseconds) + reply match { + case msg : Mountable.MountMessages => + assert(msg.response.isInstanceOf[Mountable.CanMount]) + case _ => + assert(false) + } + } + } +} + +class MannedTurretControl3Test extends ActorTest { + val player = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) + val obj = MannedTurret(GlobalDefinitions.manned_turret) + obj.GUID = PlanetSideGUID(1) + obj.Actor = system.actorOf(Props(classOf[MannedTurretControl], obj), "turret-control") + val bldg = Building(0, Zone.Nowhere, StructureType.Building) + bldg.Amenities = obj + + "MannedTurretControl" should { + "block seating on mismatched faction affiliation when FactionLock is true" in { + assert(player.Faction == PlanetSideEmpire.TR) + assert(obj.Faction == PlanetSideEmpire.NEUTRAL) + assert(obj.Definition.FactionLocked) + + obj.Actor ! Mountable.TryMount(player, 0) + val reply = receiveOne(300 milliseconds) + reply match { + case msg : Mountable.MountMessages => + assert(msg.response.isInstanceOf[Mountable.CanNotMount]) + case _ => + assert(false) + } + } + } +} + +class MannedTurretControl4Test extends ActorTest { + val player = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) + val objDef = new MannedTurretDefinition(480) + objDef.FactionLocked = false + val obj = MannedTurret(objDef) + obj.GUID = PlanetSideGUID(1) + obj.Actor = system.actorOf(Props(classOf[MannedTurretControl], obj), "turret-control") + val bldg = Building(0, Zone.Nowhere, StructureType.Building) + bldg.Amenities = obj + + "MannedTurretControl" should { + "seating even with mismatched faction affiliation when FactionLock is false" in { + assert(player.Faction == PlanetSideEmpire.TR) + assert(obj.Faction == PlanetSideEmpire.NEUTRAL) + assert(!obj.Definition.FactionLocked) + + obj.Actor ! Mountable.TryMount(player, 0) + val reply = receiveOne(300 milliseconds) + reply match { + case msg : Mountable.MountMessages => + assert(msg.response.isInstanceOf[Mountable.CanMount]) + case _ => + assert(false) + } + } + } +} diff --git a/common/src/test/scala/objects/MountableTest.scala b/common/src/test/scala/objects/MountableTest.scala index 841069d6..907d8bba 100644 --- a/common/src/test/scala/objects/MountableTest.scala +++ b/common/src/test/scala/objects/MountableTest.scala @@ -2,6 +2,7 @@ package objects import akka.actor.{Actor, ActorRef, Props} +import base.ActorTest import net.psforever.objects.{Avatar, Player} import net.psforever.objects.definition.{ObjectDefinition, SeatDefinition} import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} @@ -12,7 +13,7 @@ import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire} import scala.concurrent.duration.Duration -class MountableControl1Test extends ActorTest() { +class MountableControl1Test extends ActorTest { "MountableControl" should { "construct" in { val obj = new MountableTest.MountableTestObject @@ -22,7 +23,7 @@ class MountableControl1Test extends ActorTest() { } } -class MountableControl2Test extends ActorTest() { +class MountableControl2Test extends ActorTest { "MountableControl" should { "let a player mount" in { val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) @@ -43,7 +44,7 @@ class MountableControl2Test extends ActorTest() { } } -class MountableControl3Test extends ActorTest() { +class MountableControl3Test extends ActorTest { "MountableControl" should { "block a player from mounting" in { val player1 = Player(Avatar("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) diff --git a/common/src/test/scala/objects/ResourceSiloTest.scala b/common/src/test/scala/objects/ResourceSiloTest.scala new file mode 100644 index 00000000..846959f3 --- /dev/null +++ b/common/src/test/scala/objects/ResourceSiloTest.scala @@ -0,0 +1,332 @@ +// Copyright (c) 2017 PSForever +package objects + +import akka.actor.{Actor, Props} +import akka.routing.RandomPool +import akka.testkit.TestProbe +import base.ActorTest +import net.psforever.objects.guid.TaskResolver +import net.psforever.objects.{Avatar, GlobalDefinitions, Player} +import net.psforever.objects.serverobject.resourcesilo.{ResourceSilo, ResourceSiloControl, ResourceSiloDefinition} +import net.psforever.objects.serverobject.structures.{Building, StructureType} +import net.psforever.objects.zones.Zone +import net.psforever.packet.game.{PlanetSideGUID, UseItemMessage} +import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, Vector3} +import org.specs2.mutable.Specification +import services.ServiceManager +import services.avatar.{AvatarAction, AvatarService, AvatarServiceMessage} + +import scala.concurrent.duration._ + +class ResourceSiloTest extends Specification { + "Resource Silo" should { + "define" in { + val obj = new ResourceSiloDefinition + obj.ObjectId mustEqual 731 + } + + "construct" in { + val obj = ResourceSilo() + obj.Definition mustEqual GlobalDefinitions.resource_silo + obj.MaximumCharge mustEqual 1000 + obj.ChargeLevel mustEqual 0 + obj.LowNtuWarningOn mustEqual 0 + obj.CapacitorDisplay mustEqual 0 + // + obj.ChargeLevel = 50 + obj.LowNtuWarningOn = 25 + obj.CapacitorDisplay = 75 + obj.ChargeLevel mustEqual 50 + obj.LowNtuWarningOn mustEqual 25 + obj.CapacitorDisplay mustEqual 75 + } + + "charge level can not exceed limits(0 to maximum)" in { + val obj = ResourceSilo() + obj.ChargeLevel mustEqual 0 + obj.ChargeLevel = -5 + obj.ChargeLevel mustEqual 0 + + obj.ChargeLevel = 1250 + obj.ChargeLevel mustEqual 1000 + obj.ChargeLevel mustEqual obj.MaximumCharge + } + + "using the silo generates a charge event" in { + val msg = UseItemMessage(PlanetSideGUID(1), PlanetSideGUID(0), PlanetSideGUID(2), 0L, false, Vector3(0f,0f,0f),Vector3(0f,0f,0f),0,0,0,0L) //faked + ResourceSilo().Use(ResourceSiloTest.player, msg) mustEqual ResourceSilo.ChargeEvent() + } + } +} + +class ResourceSiloControlStartupTest extends ActorTest { + val serviceManager = ServiceManager.boot(system) + serviceManager ! ServiceManager.Register(RandomPool(1).props(Props[TaskResolver]), "taskResolver") + val obj = ResourceSilo() + obj.GUID = PlanetSideGUID(1) + val probe = TestProbe() + serviceManager ! ServiceManager.Register(Props(classOf[ResourceSiloTest.ProbedAvatarService], probe), "avatar") + + "Resource silo" should { + "startup properly" in { + expectNoMsg(500 milliseconds) + system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo") + expectNoMsg(1 seconds) + assert(true) + } + } +} + +class ResourceSiloControlUseTest extends ActorTest { + val serviceManager = ServiceManager.boot(system) + serviceManager ! ServiceManager.Register(RandomPool(1).props(Props[TaskResolver]), "taskResolver") + val probe = TestProbe() + serviceManager ! ServiceManager.Register(Props(classOf[ResourceSiloTest.ProbedAvatarService], probe), "avatar") + val msg = UseItemMessage(PlanetSideGUID(1), PlanetSideGUID(0), PlanetSideGUID(2), 0L, false, Vector3(0f,0f,0f),Vector3(0f,0f,0f),0,0,0,0L) //faked + val obj = ResourceSilo() + obj.GUID = PlanetSideGUID(1) + obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo") + obj.Actor ! "startup" + + "Resource silo" should { + "respond when being used" in { + expectNoMsg(1 seconds) + obj.Actor ! ResourceSilo.Use(ResourceSiloTest.player, msg) + + val reply = receiveOne(500 milliseconds) + assert(reply.isInstanceOf[ResourceSilo.ResourceSiloMessage]) + assert(reply.asInstanceOf[ResourceSilo.ResourceSiloMessage].player == ResourceSiloTest.player) + assert(reply.asInstanceOf[ResourceSilo.ResourceSiloMessage].msg == msg) + assert(reply.asInstanceOf[ResourceSilo.ResourceSiloMessage].response == ResourceSilo.ChargeEvent()) + } + } +} + +class ResourceSiloControlNtuWarningTest extends ActorTest { + val serviceManager = ServiceManager.boot(system) + serviceManager ! ServiceManager.Register(RandomPool(1).props(Props[TaskResolver]), "taskResolver") + val probe = TestProbe() + serviceManager ! ServiceManager.Register(Props(classOf[ResourceSiloTest.ProbedAvatarService], probe), "avatar") + val obj = ResourceSilo() + obj.GUID = PlanetSideGUID(1) + obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo") + obj.Actor ! "startup" + obj.Owner = new Building(0, Zone.Nowhere, StructureType.Building) { + ModelId = 6 + } + + "Resource silo" should { + "announce low ntu" in { + expectNoMsg(1 seconds) + assert(obj.LowNtuWarningOn == 0) + obj.Actor ! ResourceSilo.LowNtuWarning(10) + + val reply = probe.receiveOne(500 milliseconds) + assert(obj.LowNtuWarningOn == 10) + assert(reply.isInstanceOf[AvatarServiceMessage]) + assert(reply.asInstanceOf[AvatarServiceMessage].forChannel == "nowhere") + assert(reply.asInstanceOf[AvatarServiceMessage] + .actionMessage.isInstanceOf[AvatarAction.PlanetsideAttribute]) + assert(reply.asInstanceOf[AvatarServiceMessage] + .actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].player_guid == PlanetSideGUID(6)) + assert(reply.asInstanceOf[AvatarServiceMessage] + .actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].attribute_type == 47) + assert(reply.asInstanceOf[AvatarServiceMessage] + .actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].attribute_value == 10) + } + } +} + +class ResourceSiloControlUpdate1Test extends ActorTest { + val serviceManager = ServiceManager.boot(system) + serviceManager ! ServiceManager.Register(RandomPool(1).props(Props[TaskResolver]), "taskResolver") + val probe1 = TestProbe() + serviceManager ! ServiceManager.Register(Props(classOf[ResourceSiloTest.ProbedAvatarService], probe1), "avatar") + val obj = ResourceSilo() + obj.GUID = PlanetSideGUID(1) + obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo") + obj.Actor ! "startup" + val bldg = new Building(0, Zone.Nowhere, StructureType.Building) { + ModelId = 6 + } + val probe2 = TestProbe() + bldg.Actor = system.actorOf(Props(classOf[ResourceSiloTest.ProbedBuildingControl], probe2), "test-bldg") + obj.Owner = bldg + + "Resource silo" should { + "update the charge level and capacitor display (report low ntu, power restored)" in { + expectNoMsg(1 seconds) + + assert(obj.ChargeLevel == 0) + assert(obj.CapacitorDisplay == 0) + obj.Actor ! ResourceSilo.UpdateChargeLevel(105) + + val reply1 = probe1.receiveOne(500 milliseconds) + val reply2 = probe2.receiveOne(500 milliseconds) + assert(obj.ChargeLevel == 105) + assert(obj.CapacitorDisplay == 1) + assert(reply1.isInstanceOf[AvatarServiceMessage]) + assert(reply1.asInstanceOf[AvatarServiceMessage].forChannel == "nowhere") + assert(reply1.asInstanceOf[AvatarServiceMessage] + .actionMessage.isInstanceOf[AvatarAction.PlanetsideAttribute]) + assert(reply1.asInstanceOf[AvatarServiceMessage] + .actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].player_guid == PlanetSideGUID(1)) + assert(reply1.asInstanceOf[AvatarServiceMessage] + .actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].attribute_type == 45) + assert(reply1.asInstanceOf[AvatarServiceMessage] + .actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].attribute_value == 1) + + assert(reply2.isInstanceOf[Building.SendMapUpdateToAllClients]) + + val reply3 = probe1.receiveOne(500 milliseconds) + assert(reply3.isInstanceOf[AvatarServiceMessage]) + assert(reply3.asInstanceOf[AvatarServiceMessage].forChannel == "nowhere") + assert(reply3.asInstanceOf[AvatarServiceMessage] + .actionMessage.isInstanceOf[AvatarAction.PlanetsideAttribute]) + assert(reply3.asInstanceOf[AvatarServiceMessage] + .actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].player_guid == PlanetSideGUID(6)) + assert(reply3.asInstanceOf[AvatarServiceMessage] + .actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].attribute_type == 48) + assert(reply3.asInstanceOf[AvatarServiceMessage] + .actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].attribute_value == 0) + + val reply4 = probe1.receiveOne(500 milliseconds) + assert(obj.LowNtuWarningOn == 1) + assert(reply4.isInstanceOf[AvatarServiceMessage]) + assert(reply4.asInstanceOf[AvatarServiceMessage].forChannel == "nowhere") + assert(reply4.asInstanceOf[AvatarServiceMessage] + .actionMessage.isInstanceOf[AvatarAction.PlanetsideAttribute]) + assert(reply4.asInstanceOf[AvatarServiceMessage] + .actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].player_guid == PlanetSideGUID(6)) + assert(reply4.asInstanceOf[AvatarServiceMessage] + .actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].attribute_type == 47) + assert(reply4.asInstanceOf[AvatarServiceMessage] + .actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].attribute_value == 1) + } + } +} + +class ResourceSiloControlUpdate2Test extends ActorTest { + val serviceManager = ServiceManager.boot(system) + serviceManager ! ServiceManager.Register(RandomPool(1).props(Props[TaskResolver]), "taskResolver") + val probe1 = TestProbe() + serviceManager ! ServiceManager.Register(Props(classOf[ResourceSiloTest.ProbedAvatarService], probe1), "avatar") + val obj = ResourceSilo() + obj.GUID = PlanetSideGUID(1) + obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo") + obj.Actor ! "startup" + val bldg = new Building(0, Zone.Nowhere, StructureType.Building) { + ModelId = 6 + } + val probe2 = TestProbe() + bldg.Actor = system.actorOf(Props(classOf[ResourceSiloTest.ProbedBuildingControl], probe2), "test-bldg") + obj.Owner = bldg + + "Resource silo" should { + "update the charge level and capacitor display (report good ntu)" in { + expectNoMsg(1 seconds) + + obj.ChargeLevel = 100 + obj.CapacitorDisplay = 1 + obj.LowNtuWarningOn = 1 + assert(obj.ChargeLevel == 100) + assert(obj.CapacitorDisplay == 1) + assert(obj.LowNtuWarningOn == 1) + obj.Actor ! ResourceSilo.UpdateChargeLevel(105) + + val reply1 = probe1.receiveOne(500 milliseconds) + val reply2 = probe2.receiveOne(500 milliseconds) + assert(obj.ChargeLevel == 205) + assert(obj.CapacitorDisplay == 2) + assert(reply1.isInstanceOf[AvatarServiceMessage]) + assert(reply1.asInstanceOf[AvatarServiceMessage].forChannel == "nowhere") + assert(reply1.asInstanceOf[AvatarServiceMessage] + .actionMessage.isInstanceOf[AvatarAction.PlanetsideAttribute]) + assert(reply1.asInstanceOf[AvatarServiceMessage] + .actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].player_guid == PlanetSideGUID(1)) + assert(reply1.asInstanceOf[AvatarServiceMessage] + .actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].attribute_type == 45) + assert(reply1.asInstanceOf[AvatarServiceMessage] + .actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].attribute_value == 2) + + assert(reply2.isInstanceOf[Building.SendMapUpdateToAllClients]) + + val reply3 = probe1.receiveOne(500 milliseconds) + assert(obj.LowNtuWarningOn == 0) + assert(reply3.isInstanceOf[AvatarServiceMessage]) + assert(reply3.asInstanceOf[AvatarServiceMessage].forChannel == "nowhere") + assert(reply3.asInstanceOf[AvatarServiceMessage] + .actionMessage.isInstanceOf[AvatarAction.PlanetsideAttribute]) + assert(reply3.asInstanceOf[AvatarServiceMessage] + .actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].player_guid == PlanetSideGUID(6)) + assert(reply3.asInstanceOf[AvatarServiceMessage] + .actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].attribute_type == 47) + assert(reply3.asInstanceOf[AvatarServiceMessage] + .actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].attribute_value == 0) + } + } +} + +class ResourceSiloControlNoUpdateTest extends ActorTest { + val serviceManager = ServiceManager.boot(system) + serviceManager ! ServiceManager.Register(RandomPool(1).props(Props[TaskResolver]), "taskResolver") + val probe1 = TestProbe() + serviceManager ! ServiceManager.Register(Props(classOf[ResourceSiloTest.ProbedAvatarService], probe1), "avatar") + val obj = ResourceSilo() + obj.GUID = PlanetSideGUID(1) + obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo") + obj.Actor ! "startup" + val bldg = new Building(0, Zone.Nowhere, StructureType.Building) { + ModelId = 6 + } + val probe2 = TestProbe() + bldg.Actor = system.actorOf(Props(classOf[ResourceSiloTest.ProbedBuildingControl], probe2), "test-bldg") + obj.Owner = bldg + + "Resource silo" should { + "update, but not sufficiently to change the capacitor display" in { + expectNoMsg(1 seconds) + + obj.ChargeLevel = 250 + obj.CapacitorDisplay = 3 + obj.LowNtuWarningOn = 0 + assert(obj.ChargeLevel == 250) + assert(obj.CapacitorDisplay == 3) + assert(obj.LowNtuWarningOn == 0) + obj.Actor ! ResourceSilo.UpdateChargeLevel(50) + + expectNoMsg(500 milliseconds) + probe1.expectNoMsg(500 milliseconds) + probe2.expectNoMsg(500 milliseconds) + assert(obj.ChargeLevel == 300) + assert(obj.CapacitorDisplay == 3) + assert(obj.LowNtuWarningOn == 0) + } + } +} + +object ResourceSiloTest { + val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) + + class ProbedAvatarService(probe : TestProbe) extends Actor { + override def receive : Receive = { + case msg => + probe.ref ! msg + } + } + + class ProbedBuildingControl(probe : TestProbe) extends Actor { + override def receive : Receive = { + case msg => + probe.ref ! msg + } + } + + class ProbedResourceSiloControl(silo : ResourceSilo, probe : TestProbe) extends ResourceSiloControl(silo) { + override def receive : Receive = { + case msg => + super.receive.apply(msg) + probe.ref ! msg + } + } +} diff --git a/common/src/test/scala/objects/ServerObjectBuilderTest.scala b/common/src/test/scala/objects/ServerObjectBuilderTest.scala index dacd1e4e..b59bd70c 100644 --- a/common/src/test/scala/objects/ServerObjectBuilderTest.scala +++ b/common/src/test/scala/objects/ServerObjectBuilderTest.scala @@ -2,6 +2,7 @@ package objects import akka.actor.{Actor, ActorContext, Props} +import base.ActorTest import net.psforever.objects.guid.NumberPoolHub import net.psforever.packet.game.PlanetSideGUID import net.psforever.objects.serverobject.ServerObjectBuilder @@ -137,7 +138,8 @@ class ProximityTerminalObjectBuilderTest extends ActorTest { "Terminal object" should { "build" in { val hub = ServerObjectBuilderTest.NumberPoolHub - val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, ProximityTerminal.Constructor(medical_terminal)), hub), "term") + val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, + ProximityTerminal.Constructor(medical_terminal)), hub), "term") actor ! "!" val reply = receiveOne(Duration.create(1000, "ms")) @@ -172,7 +174,7 @@ class VehicleSpawnPadObjectBuilderTest extends ActorTest { class LocalProjectileBuilderTest extends ActorTest { import net.psforever.objects.LocalProjectile - "Local ProjectileBuilder" should { + "Local projectile object" should { "build" in { val hub = ServerObjectBuilderTest.NumberPoolHub val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, @@ -190,7 +192,7 @@ class LocalProjectileBuilderTest extends ActorTest { class LockerObjectBuilderTest extends ActorTest { import net.psforever.objects.serverobject.mblocker.Locker - "LockerObjectBuilder" should { + "Locker object" should { "build" in { val hub = ServerObjectBuilderTest.NumberPoolHub val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, @@ -206,9 +208,27 @@ class LockerObjectBuilderTest extends ActorTest { } } +class ResourceSiloObjectBuilderTest extends ActorTest { + import net.psforever.objects.serverobject.resourcesilo.ResourceSilo + "Resource silo object" should { + "build" in { + val hub = ServerObjectBuilderTest.NumberPoolHub + val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, + ResourceSilo.Constructor), hub), "spawn-tube") + actor ! "startup" + + val reply = receiveOne(Duration.create(1000, "ms")) + assert(reply.isInstanceOf[ResourceSilo]) + assert(reply.asInstanceOf[ResourceSilo].HasGUID) + assert(reply.asInstanceOf[ResourceSilo].GUID == PlanetSideGUID(1)) + assert(reply == hub(1).get) + } + } +} + class SpawnTubeObjectBuilderTest extends ActorTest { import net.psforever.objects.serverobject.tube.SpawnTube - "LockerObjectBuilder" should { + "Spawn tube object" should { "build" in { val hub = ServerObjectBuilderTest.NumberPoolHub val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, @@ -226,6 +246,25 @@ class SpawnTubeObjectBuilderTest extends ActorTest { } } +class MannedTurretObjectBuilderTest extends ActorTest { + import net.psforever.objects.GlobalDefinitions.manned_turret + import net.psforever.objects.serverobject.turret.MannedTurret + "MannedTurretObjectBuilder" should { + "build" in { + val hub = ServerObjectBuilderTest.NumberPoolHub + val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, + MannedTurret.Constructor(manned_turret)), hub), "spawn-tube") + actor ! "!" + + val reply = receiveOne(Duration.create(1000, "ms")) + assert(reply.isInstanceOf[MannedTurret]) + assert(reply.asInstanceOf[MannedTurret].HasGUID) + assert(reply.asInstanceOf[MannedTurret].GUID == PlanetSideGUID(1)) + assert(reply == hub(1).get) + } + } +} + object ServerObjectBuilderTest { import net.psforever.objects.guid.source.LimitedNumberSource def NumberPoolHub : NumberPoolHub = { diff --git a/common/src/test/scala/objects/SpawnTubeTest.scala b/common/src/test/scala/objects/SpawnTubeTest.scala index 7ef8e86b..564f29dd 100644 --- a/common/src/test/scala/objects/SpawnTubeTest.scala +++ b/common/src/test/scala/objects/SpawnTubeTest.scala @@ -2,6 +2,7 @@ package objects import akka.actor.{ActorRef, Props} +import base.ActorTest import net.psforever.objects.GlobalDefinitions import net.psforever.objects.serverobject.tube.{SpawnTube, SpawnTubeControl, SpawnTubeDefinition} import org.specs2.mutable.Specification @@ -48,7 +49,7 @@ class SpawnTubeTest extends Specification { } } -class SpawnTubeControlTest extends ActorTest() { +class SpawnTubeControlTest extends ActorTest { "SpawnTubeControl" should { "construct" in { val obj = SpawnTube(GlobalDefinitions.ams_respawn_tube) diff --git a/common/src/test/scala/objects/UtilityTest.scala b/common/src/test/scala/objects/UtilityTest.scala index 97b46d75..b061a0ff 100644 --- a/common/src/test/scala/objects/UtilityTest.scala +++ b/common/src/test/scala/objects/UtilityTest.scala @@ -2,6 +2,7 @@ package objects import akka.actor.{Actor, ActorRef, Props} +import base.ActorTest import net.psforever.objects.{GlobalDefinitions, Vehicle} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.vehicles._ @@ -73,7 +74,7 @@ class UtilityTest extends Specification { } } -class Utility1Test extends ActorTest() { +class Utility1Test extends ActorTest { "Utility" should { "wire an order_terminala Actor" in { val obj = Utility(UtilityType.order_terminala, UtilityTest.vehicle) @@ -87,7 +88,7 @@ class Utility1Test extends ActorTest() { } } -class Utility2Test extends ActorTest() { +class Utility2Test extends ActorTest { "Utility" should { "wire an order_terminalb Actor" in { val obj = Utility(UtilityType.order_terminalb, UtilityTest.vehicle) @@ -101,7 +102,7 @@ class Utility2Test extends ActorTest() { } } -class Utility3Test extends ActorTest() { +class Utility3Test extends ActorTest { "Utility" should { "wire a matrix_terminalc Actor" in { val obj = Utility(UtilityType.matrix_terminalc, UtilityTest.vehicle) @@ -115,7 +116,7 @@ class Utility3Test extends ActorTest() { } } -class Utility4Test extends ActorTest() { +class Utility4Test extends ActorTest { "Utility" should { "wire an ams_respawn_tube Actor" in { val obj = Utility(UtilityType.ams_respawn_tube, UtilityTest.vehicle) diff --git a/common/src/test/scala/objects/VehicleSpawnPadTest.scala b/common/src/test/scala/objects/VehicleSpawnPadTest.scala index f780a817..7372e662 100644 --- a/common/src/test/scala/objects/VehicleSpawnPadTest.scala +++ b/common/src/test/scala/objects/VehicleSpawnPadTest.scala @@ -3,7 +3,7 @@ package objects import akka.actor.{ActorRef, ActorSystem, Props} import akka.testkit.TestProbe -import net.psforever.objects.ballistics.Projectile +import base.ActorTest import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} import net.psforever.objects.serverobject.structures.StructureType @@ -39,7 +39,7 @@ class VehicleSpawnPadTest extends Specification { } } -class VehicleSpawnControl1Test extends ActorTest() { +class VehicleSpawnControl1Test extends ActorTest { "VehicleSpawnControl" should { "construct" in { val obj = VehicleSpawnPad(GlobalDefinitions.spawn_pad) @@ -49,7 +49,7 @@ class VehicleSpawnControl1Test extends ActorTest() { } } -class VehicleSpawnControl2aTest extends ActorTest() { +class VehicleSpawnControl2aTest extends ActorTest { // This runs for a long time. "VehicleSpawnControl" should { "complete on a vehicle order (block a second one until the first is done and the spawn pad is cleared)" in { @@ -113,7 +113,7 @@ class VehicleSpawnControl2aTest extends ActorTest() { } } -class VehicleSpawnControl2bTest extends ActorTest() { +class VehicleSpawnControl2bTest extends ActorTest { // This runs for a long time. "VehicleSpawnControl" should { "complete on a vehicle order (railless)" in { @@ -170,7 +170,7 @@ class VehicleSpawnControl2bTest extends ActorTest() { } } -class VehicleSpawnControl3Test extends ActorTest() { +class VehicleSpawnControl3Test extends ActorTest { "VehicleSpawnControl" should { "player is on wrong continent before vehicle can partially load; vehicle is cleaned up" in { val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR) @@ -241,7 +241,7 @@ class VehicleSpawnControl4Test extends ActorTest() { // } //} -class VehicleSpawnControl5Test extends ActorTest() { +class VehicleSpawnControl5Test extends ActorTest { "VehicleSpawnControl" should { "player dies right after vehicle partially loads; the vehicle spawns and blocks the pad" in { val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR) @@ -274,7 +274,7 @@ class VehicleSpawnControl5Test extends ActorTest() { } } -class VehicleSpawnControl6Test extends ActorTest() { +class VehicleSpawnControl6Test extends ActorTest { "VehicleSpawnControl" should { "the player can not sit in vehicle; vehicle spawns and blocks the pad" in { val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR) diff --git a/common/src/test/scala/objects/VehicleTest.scala b/common/src/test/scala/objects/VehicleTest.scala index 6875d4aa..94c2878d 100644 --- a/common/src/test/scala/objects/VehicleTest.scala +++ b/common/src/test/scala/objects/VehicleTest.scala @@ -2,6 +2,7 @@ package objects import akka.actor.Props +import base.ActorTest import net.psforever.objects._ import net.psforever.objects.definition.{SeatDefinition, VehicleDefinition} import net.psforever.objects.serverobject.mount.Mountable diff --git a/common/src/test/scala/objects/ZoneTest.scala b/common/src/test/scala/objects/ZoneTest.scala index 234afa89..37bc9957 100644 --- a/common/src/test/scala/objects/ZoneTest.scala +++ b/common/src/test/scala/objects/ZoneTest.scala @@ -4,6 +4,7 @@ package objects import java.util.concurrent.atomic.AtomicInteger import akka.actor.{ActorContext, ActorRef, Props} +import base.ActorTest import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.equipment.Equipment import net.psforever.objects.guid.NumberPoolHub @@ -74,6 +75,15 @@ class ZoneTest extends Specification { map.TerminalToInterface(3, 4) map.TerminalToInterface mustEqual Map(1 -> 2, 3 -> 4) } + + "associate turrets to weapons" in { + val map = new ZoneMap("map13") + map.TurretToWeapon mustEqual Map.empty + map.TurretToWeapon(1, 2) + map.TurretToWeapon mustEqual Map(1 -> 2) + map.TurretToWeapon(3, 4) + map.TurretToWeapon mustEqual Map(1 -> 2, 3 -> 4) + } } val map13 = new ZoneMap("map13") diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskRegister1Test.scala b/common/src/test/scala/objects/guidtask/GUIDTaskRegister1Test.scala index bf9ea192..19338c49 100644 --- a/common/src/test/scala/objects/guidtask/GUIDTaskRegister1Test.scala +++ b/common/src/test/scala/objects/guidtask/GUIDTaskRegister1Test.scala @@ -1,8 +1,8 @@ // Copyright (c) 2017 PSForever package objects.guidtask +import base.ActorTest import net.psforever.objects.guid.{GUIDTask, TaskResolver} -import objects.ActorTest class GUIDTaskRegister1Test extends ActorTest { "RegisterObjectTask" in { diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskRegister2Test.scala b/common/src/test/scala/objects/guidtask/GUIDTaskRegister2Test.scala index e6ffb6eb..78ccdad2 100644 --- a/common/src/test/scala/objects/guidtask/GUIDTaskRegister2Test.scala +++ b/common/src/test/scala/objects/guidtask/GUIDTaskRegister2Test.scala @@ -1,11 +1,11 @@ // Copyright (c) 2017 PSForever package objects.guidtask +import base.ActorTest import net.psforever.objects._ import net.psforever.objects.guid.{GUIDTask, TaskResolver} -import objects.ActorTest -class GUIDTaskRegister2Test extends ActorTest() { +class GUIDTaskRegister2Test extends ActorTest { "RegisterEquipment -> RegisterObjectTask" in { val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup val obj = AmmoBox(GlobalDefinitions.energy_cell) diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskRegister3Test.scala b/common/src/test/scala/objects/guidtask/GUIDTaskRegister3Test.scala index 58b3ce48..2350563b 100644 --- a/common/src/test/scala/objects/guidtask/GUIDTaskRegister3Test.scala +++ b/common/src/test/scala/objects/guidtask/GUIDTaskRegister3Test.scala @@ -1,11 +1,11 @@ // Copyright (c) 2017 PSForever package objects.guidtask +import base.ActorTest import net.psforever.objects._ import net.psforever.objects.guid.{GUIDTask, TaskResolver} -import objects.ActorTest -class GUIDTaskRegister3Test extends ActorTest() { +class GUIDTaskRegister3Test extends ActorTest { "RegisterEquipment -> RegisterTool" in { val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup val obj = Tool(GlobalDefinitions.beamer) diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskRegister4Test.scala b/common/src/test/scala/objects/guidtask/GUIDTaskRegister4Test.scala index 1eab3dbf..64c7f66d 100644 --- a/common/src/test/scala/objects/guidtask/GUIDTaskRegister4Test.scala +++ b/common/src/test/scala/objects/guidtask/GUIDTaskRegister4Test.scala @@ -1,11 +1,11 @@ // Copyright (c) 2017 PSForever package objects.guidtask +import base.ActorTest import net.psforever.objects._ import net.psforever.objects.guid.{GUIDTask, TaskResolver} -import objects.ActorTest -class GUIDTaskRegister4Test extends ActorTest() { +class GUIDTaskRegister4Test extends ActorTest { "RegisterVehicle" in { val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup val obj = Vehicle(GlobalDefinitions.fury) diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskRegister5Test.scala b/common/src/test/scala/objects/guidtask/GUIDTaskRegister5Test.scala index c18987ef..ad33f006 100644 --- a/common/src/test/scala/objects/guidtask/GUIDTaskRegister5Test.scala +++ b/common/src/test/scala/objects/guidtask/GUIDTaskRegister5Test.scala @@ -1,12 +1,12 @@ // Copyright (c) 2017 PSForever package objects.guidtask +import base.ActorTest import net.psforever.objects._ import net.psforever.objects.guid.{GUIDTask, TaskResolver} import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire} -import objects.ActorTest -class GUIDTaskRegister5Test extends ActorTest() { +class GUIDTaskRegister5Test extends ActorTest { "RegisterAvatar" in { val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup val obj = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskRegister6Test.scala b/common/src/test/scala/objects/guidtask/GUIDTaskRegister6Test.scala index e6806cbd..72f1a3e3 100644 --- a/common/src/test/scala/objects/guidtask/GUIDTaskRegister6Test.scala +++ b/common/src/test/scala/objects/guidtask/GUIDTaskRegister6Test.scala @@ -1,12 +1,12 @@ // Copyright (c) 2017 PSForever package objects.guidtask +import base.ActorTest import net.psforever.objects._ import net.psforever.objects.guid.{GUIDTask, TaskResolver} import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire} -import objects.ActorTest -class GUIDTaskRegister6Test extends ActorTest() { +class GUIDTaskRegister6Test extends ActorTest { "RegisterPlayer" in { val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup val obj = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskUnregister1Test.scala b/common/src/test/scala/objects/guidtask/GUIDTaskUnregister1Test.scala index 5de6f2e3..e26629b6 100644 --- a/common/src/test/scala/objects/guidtask/GUIDTaskUnregister1Test.scala +++ b/common/src/test/scala/objects/guidtask/GUIDTaskUnregister1Test.scala @@ -1,10 +1,10 @@ // Copyright (c) 2017 PSForever package objects.guidtask +import base.ActorTest import net.psforever.objects.guid.{GUIDTask, TaskResolver} -import objects.ActorTest -class GUIDTaskUnregister1Test extends ActorTest() { +class GUIDTaskUnregister1Test extends ActorTest { "UnregisterObjectTask" in { val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup val obj = new GUIDTaskTest.TestObject diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskUnregister2Test.scala b/common/src/test/scala/objects/guidtask/GUIDTaskUnregister2Test.scala index 71dcc480..6b4bfb1a 100644 --- a/common/src/test/scala/objects/guidtask/GUIDTaskUnregister2Test.scala +++ b/common/src/test/scala/objects/guidtask/GUIDTaskUnregister2Test.scala @@ -1,11 +1,11 @@ // Copyright (c) 2017 PSForever package objects.guidtask +import base.ActorTest import net.psforever.objects._ import net.psforever.objects.guid.{GUIDTask, TaskResolver} -import objects.ActorTest -class GUIDTaskUnregister2Test extends ActorTest() { +class GUIDTaskUnregister2Test extends ActorTest { "UnregisterEquipment -> UnregisterObjectTask" in { val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup val obj = AmmoBox(GlobalDefinitions.energy_cell) diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskUnregister3Test.scala b/common/src/test/scala/objects/guidtask/GUIDTaskUnregister3Test.scala index 3f0fd901..6da3e31a 100644 --- a/common/src/test/scala/objects/guidtask/GUIDTaskUnregister3Test.scala +++ b/common/src/test/scala/objects/guidtask/GUIDTaskUnregister3Test.scala @@ -1,11 +1,11 @@ // Copyright (c) 2017 PSForever package objects.guidtask +import base.ActorTest import net.psforever.objects._ import net.psforever.objects.guid.{GUIDTask, TaskResolver} -import objects.ActorTest -class GUIDTaskUnregister3Test extends ActorTest() { +class GUIDTaskUnregister3Test extends ActorTest { "UnregisterEquipment -> UnregisterTool" in { val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup val obj = Tool(GlobalDefinitions.beamer) diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskUnregister4Test.scala b/common/src/test/scala/objects/guidtask/GUIDTaskUnregister4Test.scala index e52eeab7..5c1df0a4 100644 --- a/common/src/test/scala/objects/guidtask/GUIDTaskUnregister4Test.scala +++ b/common/src/test/scala/objects/guidtask/GUIDTaskUnregister4Test.scala @@ -1,11 +1,11 @@ // Copyright (c) 2017 PSForever package objects.guidtask +import base.ActorTest import net.psforever.objects._ import net.psforever.objects.guid.{GUIDTask, TaskResolver} -import objects.ActorTest -class GUIDTaskUnregister4Test extends ActorTest() { +class GUIDTaskUnregister4Test extends ActorTest { "RegisterVehicle" in { val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup val obj = Vehicle(GlobalDefinitions.fury) diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskUnregister5Test.scala b/common/src/test/scala/objects/guidtask/GUIDTaskUnregister5Test.scala index a9bba717..561f67a4 100644 --- a/common/src/test/scala/objects/guidtask/GUIDTaskUnregister5Test.scala +++ b/common/src/test/scala/objects/guidtask/GUIDTaskUnregister5Test.scala @@ -1,12 +1,12 @@ // Copyright (c) 2017 PSForever package objects.guidtask +import base.ActorTest import net.psforever.objects._ import net.psforever.objects.guid.{GUIDTask, TaskResolver} import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire} -import objects.ActorTest -class GUIDTaskUnregister5Test extends ActorTest() { +class GUIDTaskUnregister5Test extends ActorTest { "UnregisterAvatar" in { val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup val obj = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskUnregister6Test.scala b/common/src/test/scala/objects/guidtask/GUIDTaskUnregister6Test.scala index 26e8ccfb..59891d6f 100644 --- a/common/src/test/scala/objects/guidtask/GUIDTaskUnregister6Test.scala +++ b/common/src/test/scala/objects/guidtask/GUIDTaskUnregister6Test.scala @@ -1,12 +1,12 @@ // Copyright (c) 2017 PSForever package objects.guidtask +import base.ActorTest import net.psforever.objects._ import net.psforever.objects.guid.{GUIDTask, TaskResolver} import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire} -import objects.ActorTest -class GUIDTaskUnregister6Test extends ActorTest() { +class GUIDTaskUnregister6Test extends ActorTest { "UnregisterPlayer" in { val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup val obj = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) diff --git a/common/src/test/scala/objects/number/NumberPoolActorTest.scala b/common/src/test/scala/objects/number/NumberPoolActorTest.scala index e4b8cf47..61b181ec 100644 --- a/common/src/test/scala/objects/number/NumberPoolActorTest.scala +++ b/common/src/test/scala/objects/number/NumberPoolActorTest.scala @@ -2,14 +2,14 @@ package objects.number import akka.actor.{ActorSystem, Props} +import base.ActorTest import net.psforever.objects.guid.actor.NumberPoolActor import net.psforever.objects.guid.pool.ExclusivePool import net.psforever.objects.guid.selector.RandomSelector -import objects.ActorTest import scala.concurrent.duration.Duration -class NumberPoolActorTest extends ActorTest() { +class NumberPoolActorTest extends ActorTest { "NumberPoolActor" should { "GetAnyNumber" in { val pool = new ExclusivePool((25 to 50).toList) @@ -22,7 +22,7 @@ class NumberPoolActorTest extends ActorTest() { } } -class NumberPoolActorTest1 extends ActorTest() { +class NumberPoolActorTest1 extends ActorTest { "NumberPoolActor" should { "GetSpecificNumber" in { val pool = new ExclusivePool((25 to 50).toList) @@ -34,7 +34,7 @@ class NumberPoolActorTest1 extends ActorTest() { } } -class NumberPoolActorTest2 extends ActorTest() { +class NumberPoolActorTest2 extends ActorTest { "NumberPoolActor" should { "NoNumber" in { val pool = new ExclusivePool((25 to 25).toList) //pool only has one number - 25 diff --git a/common/src/test/scala/objects/number/UniqueNumberSystemTest.scala b/common/src/test/scala/objects/number/UniqueNumberSystemTest.scala index 954a5837..31a11655 100644 --- a/common/src/test/scala/objects/number/UniqueNumberSystemTest.scala +++ b/common/src/test/scala/objects/number/UniqueNumberSystemTest.scala @@ -2,17 +2,17 @@ package objects.number import akka.actor.{ActorRef, ActorSystem, Props} +import base.ActorTest import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.guid.NumberPoolHub import net.psforever.objects.guid.actor.{NumberPoolActor, Register, UniqueNumberSystem, Unregister} import net.psforever.objects.guid.selector.RandomSelector import net.psforever.objects.guid.source.LimitedNumberSource -import objects.ActorTest import scala.concurrent.duration.Duration import scala.util.{Failure, Success} -class AllocateNumberPoolActors extends ActorTest() { +class AllocateNumberPoolActors extends ActorTest { "AllocateNumberPoolActors" in { val src : LimitedNumberSource = LimitedNumberSource(6000) val guid : NumberPoolHub = new NumberPoolHub(src) diff --git a/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala b/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala index 6bde119a..68e04221 100644 --- a/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala +++ b/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala @@ -2,6 +2,7 @@ package objects.terminal import akka.actor.{ActorRef, ActorSystem, Props} +import base.ActorTest import net.psforever.objects.definition.SeatDefinition import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.implantmech.{ImplantTerminalMech, ImplantTerminalMechControl} @@ -9,7 +10,6 @@ import net.psforever.objects.serverobject.structures.StructureType import net.psforever.objects.vehicles.Seat import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, Vector3} -import objects.ActorTest import org.specs2.mutable.Specification import scala.concurrent.duration.Duration @@ -56,7 +56,7 @@ class ImplantTerminalMechTest extends Specification { } } -class ImplantTerminalMechControl1Test extends ActorTest() { +class ImplantTerminalMechControl1Test extends ActorTest { "ImplantTerminalMechControl" should { "construct" in { val obj = ImplantTerminalMech(GlobalDefinitions.implant_terminal_mech) @@ -66,7 +66,7 @@ class ImplantTerminalMechControl1Test extends ActorTest() { } } -class ImplantTerminalMechControl2Test extends ActorTest() { +class ImplantTerminalMechControl2Test extends ActorTest { "ImplantTerminalMechControl" should { "let a player mount" in { val (player, mech) = ImplantTerminalMechTest.SetUpAgents(PlanetSideEmpire.TR) @@ -85,7 +85,7 @@ class ImplantTerminalMechControl2Test extends ActorTest() { } } -class ImplantTerminalMechControl3Test extends ActorTest() { +class ImplantTerminalMechControl3Test extends ActorTest { import net.psforever.types.CharacterGender "ImplantTerminalMechControl" should { "block a player from mounting" in { @@ -108,7 +108,7 @@ class ImplantTerminalMechControl3Test extends ActorTest() { } } -class ImplantTerminalMechControl4Test extends ActorTest() { +class ImplantTerminalMechControl4Test extends ActorTest { "ImplantTerminalMechControl" should { "dismount player after mounting" in { val (player, mech) = ImplantTerminalMechTest.SetUpAgents(PlanetSideEmpire.TR) @@ -130,7 +130,7 @@ class ImplantTerminalMechControl4Test extends ActorTest() { } } -class ImplantTerminalMechControl5Test extends ActorTest() { +class ImplantTerminalMechControl5Test extends ActorTest { "ImplantTerminalMechControl" should { "block a player from dismounting" in { val (player, mech) = ImplantTerminalMechTest.SetUpAgents(PlanetSideEmpire.TR) diff --git a/common/src/test/scala/objects/terminal/ProximityTerminalControlTest.scala b/common/src/test/scala/objects/terminal/ProximityTerminalControlTest.scala index bfd00c03..c2ed10d9 100644 --- a/common/src/test/scala/objects/terminal/ProximityTerminalControlTest.scala +++ b/common/src/test/scala/objects/terminal/ProximityTerminalControlTest.scala @@ -2,16 +2,16 @@ package objects.terminal import akka.actor.{ActorSystem, Props} +import base.ActorTest import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals._ import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire} -import objects.ActorTest import scala.concurrent.duration.Duration -class ProximityTerminalControl1Test extends ActorTest() { +class ProximityTerminalControl1Test extends ActorTest { "ProximityTerminalControl" should { "construct (medical terminal)" in { val terminal = ProximityTerminal(GlobalDefinitions.medical_terminal) @@ -20,7 +20,7 @@ class ProximityTerminalControl1Test extends ActorTest() { } } -class ProximityTerminalControl2Test extends ActorTest() { +class ProximityTerminalControl2Test extends ActorTest { "ProximityTerminalControl can not process wrong messages" in { val (_, terminal) = TerminalControlTest.SetUpAgents(GlobalDefinitions.medical_terminal, PlanetSideEmpire.TR) @@ -30,7 +30,7 @@ class ProximityTerminalControl2Test extends ActorTest() { } //terminal control is mostly a pass-through actor for Terminal.Exchange messages, wrapped in Terminal.TerminalMessage protocol -class MedicalTerminalControl1Test extends ActorTest() { +class MedicalTerminalControl1Test extends ActorTest { "ProximityTerminalControl sends a message to the first new user only" in { val (player, terminal) = ProximityTerminalControlTest.SetUpAgents(GlobalDefinitions.medical_terminal, PlanetSideEmpire.TR) player.GUID = PlanetSideGUID(10) @@ -53,7 +53,7 @@ class MedicalTerminalControl1Test extends ActorTest() { } } -class MedicalTerminalControl2Test extends ActorTest() { +class MedicalTerminalControl2Test extends ActorTest { "ProximityTerminalControl sends a message to the last user only" in { val (player, terminal) : (Player, ProximityTerminal) = ProximityTerminalControlTest.SetUpAgents(GlobalDefinitions.medical_terminal, PlanetSideEmpire.TR) player.GUID = PlanetSideGUID(10) @@ -82,7 +82,7 @@ class MedicalTerminalControl2Test extends ActorTest() { } } -class MedicalTerminalControl3Test extends ActorTest() { +class MedicalTerminalControl3Test extends ActorTest { "ProximityTerminalControl sends a message to the last user only (confirmation of test #2)" in { val (player, terminal) : (Player, ProximityTerminal) = ProximityTerminalControlTest.SetUpAgents(GlobalDefinitions.medical_terminal, PlanetSideEmpire.TR) player.GUID = PlanetSideGUID(10) diff --git a/common/src/test/scala/objects/terminal/ProximityTest.scala b/common/src/test/scala/objects/terminal/ProximityTest.scala index 449feb31..a8d18978 100644 --- a/common/src/test/scala/objects/terminal/ProximityTest.scala +++ b/common/src/test/scala/objects/terminal/ProximityTest.scala @@ -2,13 +2,13 @@ package objects.terminal import akka.actor.Props +import base.ActorTest import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage import net.psforever.objects.serverobject.terminals.{ProximityTerminal, ProximityTerminalControl, ProximityUnit, Terminal} import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire} -import objects.ActorTest import org.specs2.mutable.Specification import scala.concurrent.duration._ diff --git a/common/src/test/scala/objects/terminal/TerminalControlTest.scala b/common/src/test/scala/objects/terminal/TerminalControlTest.scala index 784779fb..39680406 100644 --- a/common/src/test/scala/objects/terminal/TerminalControlTest.scala +++ b/common/src/test/scala/objects/terminal/TerminalControlTest.scala @@ -2,17 +2,17 @@ package objects.terminal import akka.actor.{ActorSystem, Props} +import base.ActorTest import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.serverobject.terminals.{Terminal, TerminalControl, TerminalDefinition} import net.psforever.objects.zones.Zone import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} import net.psforever.types._ -import objects.ActorTest import scala.concurrent.duration.Duration -class TerminalControl1Test extends ActorTest() { +class TerminalControl1Test extends ActorTest { "TerminalControl" should { "construct (cert terminal)" in { val terminal = Terminal(GlobalDefinitions.cert_terminal) @@ -21,7 +21,7 @@ class TerminalControl1Test extends ActorTest() { } } -class TerminalControl2Test extends ActorTest() { +class TerminalControl2Test extends ActorTest { "TerminalControl can not process wrong messages" in { val (_, terminal) = TerminalControlTest.SetUpAgents(GlobalDefinitions.cert_terminal, PlanetSideEmpire.TR) @@ -32,7 +32,7 @@ class TerminalControl2Test extends ActorTest() { //terminal control is mostly a pass-through actor for Terminal.Exchange messages, wrapped in Terminal.TerminalMessage protocol //test for Cert_Terminal messages (see CertTerminalTest) -class CertTerminalControl1Test extends ActorTest() { +class CertTerminalControl1Test extends ActorTest { "TerminalControl can be used to learn a certification ('medium_assault')" in { val (player, terminal) = TerminalControlTest.SetUpAgents(GlobalDefinitions.cert_terminal, PlanetSideEmpire.TR) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Learn, 0, "medium_assault", 0, PlanetSideGUID(0)) @@ -47,7 +47,7 @@ class CertTerminalControl1Test extends ActorTest() { } } -class CertTerminalControl2Test extends ActorTest() { +class CertTerminalControl2Test extends ActorTest { "TerminalControl can be used to warn about not learning a fake certification ('juggling')" in { val (player, terminal) = TerminalControlTest.SetUpAgents(GlobalDefinitions.cert_terminal, PlanetSideEmpire.TR) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Learn, 0, "juggling", 0, PlanetSideGUID(0)) @@ -62,7 +62,7 @@ class CertTerminalControl2Test extends ActorTest() { } } -class CertTerminalControl3Test extends ActorTest() { +class CertTerminalControl3Test extends ActorTest { "TerminalControl can be used to forget a certification ('medium_assault')" in { val (player, terminal) = TerminalControlTest.SetUpAgents(GlobalDefinitions.cert_terminal, PlanetSideEmpire.TR) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Sell, 0, "medium_assault", 0, PlanetSideGUID(0)) @@ -77,7 +77,7 @@ class CertTerminalControl3Test extends ActorTest() { } } -class VehicleTerminalControl1Test extends ActorTest() { +class VehicleTerminalControl1Test extends ActorTest { "TerminalControl can be used to buy a vehicle ('two_man_assault_buggy')" in { val (player, terminal) = TerminalControlTest.SetUpAgents(GlobalDefinitions.ground_vehicle_terminal, PlanetSideEmpire.TR) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "two_man_assault_buggy", 0, PlanetSideGUID(0)) @@ -102,7 +102,7 @@ class VehicleTerminalControl1Test extends ActorTest() { } } -class VehicleTerminalControl2Test extends ActorTest() { +class VehicleTerminalControl2Test extends ActorTest { "TerminalControl can be used to warn about not buy a vehicle ('harasser')" in { val (player, terminal) = TerminalControlTest.SetUpAgents(GlobalDefinitions.ground_vehicle_terminal, PlanetSideEmpire.TR) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "harasser", 0, PlanetSideGUID(0)) diff --git a/pslogin/src/test/scala/AvatarServiceTest.scala b/common/src/test/scala/service/AvatarServiceTest.scala similarity index 99% rename from pslogin/src/test/scala/AvatarServiceTest.scala rename to common/src/test/scala/service/AvatarServiceTest.scala index 993b02ba..f4dc9c00 100644 --- a/pslogin/src/test/scala/AvatarServiceTest.scala +++ b/common/src/test/scala/service/AvatarServiceTest.scala @@ -1,6 +1,9 @@ // Copyright (c) 2017 PSForever +package service + import akka.actor.Props import akka.routing.RandomPool +import base.ActorTest import net.psforever.objects._ import net.psforever.objects.guid.{GUIDTask, TaskResolver} import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap} diff --git a/pslogin/src/test/scala/LocalServiceTest.scala b/common/src/test/scala/service/LocalServiceTest.scala similarity index 98% rename from pslogin/src/test/scala/LocalServiceTest.scala rename to common/src/test/scala/service/LocalServiceTest.scala index b6cbd696..0819252d 100644 --- a/pslogin/src/test/scala/LocalServiceTest.scala +++ b/common/src/test/scala/service/LocalServiceTest.scala @@ -1,5 +1,8 @@ +package service + // Copyright (c) 2017 PSForever import akka.actor.Props +import base.ActorTest import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.{PlanetSideEmpire, Vector3} diff --git a/pslogin/src/test/scala/RemoverActorTest.scala b/common/src/test/scala/service/RemoverActorTest.scala similarity index 85% rename from pslogin/src/test/scala/RemoverActorTest.scala rename to common/src/test/scala/service/RemoverActorTest.scala index 2d0704d3..73f87054 100644 --- a/pslogin/src/test/scala/RemoverActorTest.scala +++ b/common/src/test/scala/service/RemoverActorTest.scala @@ -1,46 +1,49 @@ -//// Copyright (c) 2017 PSForever -//import akka.actor.{ActorRef, Props} -//import akka.routing.RandomPool -//import akka.testkit.TestProbe -//import net.psforever.objects.PlanetSideGameObject -//import net.psforever.objects.definition.{EquipmentDefinition, ObjectDefinition} -//import net.psforever.objects.equipment.Equipment -//import net.psforever.objects.guid.TaskResolver -//import net.psforever.objects.zones.{Zone, ZoneMap} -//import net.psforever.packet.game.PlanetSideGUID -//import services.{RemoverActor, ServiceManager} -// -//import scala.concurrent.duration._ -// -//class StandardRemoverActorTest extends ActorTest { -// ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver") -// -// "RemoverActor" should { -// "handle a simple task" in { -// expectNoMsg(500 milliseconds) -// val probe = TestProbe() -// val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover") -// remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere) -// -// val reply1 = probe.receiveOne(200 milliseconds) -// assert(reply1.isInstanceOf[RemoverActorTest.InclusionTestAlert]) -// val reply2 = probe.receiveOne(200 milliseconds) -// assert(reply2.isInstanceOf[RemoverActorTest.InitialJobAlert]) -// probe.expectNoMsg(1 seconds) //delay -// val reply3 = probe.receiveOne(300 milliseconds) -// assert(reply3.isInstanceOf[RemoverActorTest.FirstJobAlert]) -// val reply4 = probe.receiveOne(300 milliseconds) -// assert(reply4.isInstanceOf[RemoverActorTest.ClearanceTestAlert]) -// val reply5 = probe.receiveOne(300 milliseconds) -// assert(reply5.isInstanceOf[RemoverActorTest.SecondJobAlert]) -// val reply6 = probe.receiveOne(500 milliseconds) -// assert(reply6.isInstanceOf[RemoverActorTest.DeletionTaskAlert]) -// val reply7 = probe.receiveOne(500 milliseconds) -// assert(reply7.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert]) -// } -// } -//} -// +// Copyright (c) 2017 PSForever +package service + +import akka.actor.{ActorRef, Props} +import akka.routing.RandomPool +import akka.testkit.TestProbe +import base.ActorTest +import net.psforever.objects.PlanetSideGameObject +import net.psforever.objects.definition.{EquipmentDefinition, ObjectDefinition} +import net.psforever.objects.equipment.Equipment +import net.psforever.objects.guid.TaskResolver +import net.psforever.objects.zones.{Zone, ZoneMap} +import net.psforever.packet.game.PlanetSideGUID +import services.{RemoverActor, ServiceManager} + +import scala.concurrent.duration._ + +class StandardRemoverActorTest extends ActorTest { + ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver") + + "RemoverActor" should { + "handle a simple task" in { + expectNoMsg(500 milliseconds) + val probe = TestProbe() + val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover") + remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere) + + val reply1 = probe.receiveOne(200 milliseconds) + assert(reply1.isInstanceOf[RemoverActorTest.InclusionTestAlert]) + val reply2 = probe.receiveOne(200 milliseconds) + assert(reply2.isInstanceOf[RemoverActorTest.InitialJobAlert]) + probe.expectNoMsg(1 seconds) //delay + val reply3 = probe.receiveOne(300 milliseconds) + assert(reply3.isInstanceOf[RemoverActorTest.FirstJobAlert]) + val reply4 = probe.receiveOne(300 milliseconds) + assert(reply4.isInstanceOf[RemoverActorTest.ClearanceTestAlert]) + val reply5 = probe.receiveOne(300 milliseconds) + assert(reply5.isInstanceOf[RemoverActorTest.SecondJobAlert]) + val reply6 = probe.receiveOne(500 milliseconds) + assert(reply6.isInstanceOf[RemoverActorTest.DeletionTaskAlert]) + val reply7 = probe.receiveOne(500 milliseconds) + assert(reply7.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert]) + } + } +} + //class DelayedRemoverActorTest extends ActorTest { // ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver") // @@ -473,65 +476,65 @@ // } // } //} -// -//object RemoverActorTest { -// final val TestObject = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(1) } } -// -// final case class InclusionTestAlert() -// -// final case class InitialJobAlert() -// -// final case class FirstJobAlert() -// -// final case class SecondJobAlert() -// -// final case class ClearanceTestAlert() -// -// final case class DeletionTaskAlert() -// -// final case class DeletionTaskRunAlert() -// -// class TestRemover(probe : TestProbe) extends RemoverActor { -// import net.psforever.objects.guid.{Task, TaskResolver} -// val FirstStandardDuration = 1 seconds -// -// val SecondStandardDuration = 100 milliseconds -// -// def InclusionTest(entry : RemoverActor.Entry) : Boolean = { -// probe.ref ! InclusionTestAlert() -// entry.obj.isInstanceOf[Equipment] -// } -// -// def InitialJob(entry : RemoverActor.Entry) : Unit = { -// probe.ref ! InitialJobAlert() -// } -// -// def FirstJob(entry : RemoverActor.Entry) : Unit = { -// probe.ref ! FirstJobAlert() -// } -// -// override def SecondJob(entry : RemoverActor.Entry) : Unit = { -// probe.ref ! SecondJobAlert() -// super.SecondJob(entry) -// } -// -// def ClearanceTest(entry : RemoverActor.Entry) : Boolean = { -// probe.ref ! ClearanceTestAlert() -// true -// } -// -// def DeletionTask(entry : RemoverActor.Entry) : TaskResolver.GiveTask = { -// probe.ref ! DeletionTaskAlert() -// TaskResolver.GiveTask(new Task() { -// private val localProbe = probe -// -// override def isComplete = Task.Resolution.Success -// -// def Execute(resolver : ActorRef) : Unit = { -// localProbe.ref ! DeletionTaskRunAlert() -// resolver ! scala.util.Success(this) -// } -// }) -// } -// } -//} + +object RemoverActorTest { + final val TestObject = new Equipment() { def Definition = new EquipmentDefinition(0) { GUID = PlanetSideGUID(1) } } + + final case class InclusionTestAlert() + + final case class InitialJobAlert() + + final case class FirstJobAlert() + + final case class SecondJobAlert() + + final case class ClearanceTestAlert() + + final case class DeletionTaskAlert() + + final case class DeletionTaskRunAlert() + + class TestRemover(probe : TestProbe) extends RemoverActor { + import net.psforever.objects.guid.{Task, TaskResolver} + val FirstStandardDuration = 1 seconds + + val SecondStandardDuration = 100 milliseconds + + def InclusionTest(entry : RemoverActor.Entry) : Boolean = { + probe.ref ! InclusionTestAlert() + entry.obj.isInstanceOf[Equipment] + } + + def InitialJob(entry : RemoverActor.Entry) : Unit = { + probe.ref ! InitialJobAlert() + } + + def FirstJob(entry : RemoverActor.Entry) : Unit = { + probe.ref ! FirstJobAlert() + } + + override def SecondJob(entry : RemoverActor.Entry) : Unit = { + probe.ref ! SecondJobAlert() + super.SecondJob(entry) + } + + def ClearanceTest(entry : RemoverActor.Entry) : Boolean = { + probe.ref ! ClearanceTestAlert() + true + } + + def DeletionTask(entry : RemoverActor.Entry) : TaskResolver.GiveTask = { + probe.ref ! DeletionTaskAlert() + TaskResolver.GiveTask(new Task() { + private val localProbe = probe + + override def isComplete = Task.Resolution.Success + + def Execute(resolver : ActorRef) : Unit = { + localProbe.ref ! DeletionTaskRunAlert() + resolver ! scala.util.Success(this) + } + }) + } + } +} diff --git a/pslogin/src/test/scala/VehicleServiceTest.scala b/common/src/test/scala/service/VehicleServiceTest.scala similarity index 99% rename from pslogin/src/test/scala/VehicleServiceTest.scala rename to common/src/test/scala/service/VehicleServiceTest.scala index e2d48c97..aaa96a9f 100644 --- a/pslogin/src/test/scala/VehicleServiceTest.scala +++ b/common/src/test/scala/service/VehicleServiceTest.scala @@ -1,5 +1,8 @@ // Copyright (c) 2017 PSForever +package service + import akka.actor.Props +import base.ActorTest import net.psforever.objects._ import net.psforever.packet.game.PlanetSideGUID import net.psforever.types._ diff --git a/pslogin/src/main/scala/Maps.scala b/pslogin/src/main/scala/Maps.scala index e0c44371..8d680586 100644 --- a/pslogin/src/main/scala/Maps.scala +++ b/pslogin/src/main/scala/Maps.scala @@ -11,6 +11,8 @@ import net.psforever.objects.serverobject.pad.VehicleSpawnPad import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, StructureType, WarpGate} import net.psforever.objects.serverobject.terminals.{ProximityTerminal, Terminal} import net.psforever.objects.serverobject.tube.SpawnTube +import net.psforever.objects.serverobject.resourcesilo.ResourceSilo +import net.psforever.objects.serverobject.turret.MannedTurret import net.psforever.types.Vector3 object Maps { @@ -1215,6 +1217,14 @@ object Maps { LocalObject(1186, Locker.Constructor) LocalObject(1187, Locker.Constructor) LocalObject(1188, Locker.Constructor) + LocalObject(1418, MannedTurret.Constructor(manned_turret)) + LocalObject(1419, MannedTurret.Constructor(manned_turret)) + LocalObject(1421, MannedTurret.Constructor(manned_turret)) + LocalObject(1426, MannedTurret.Constructor(manned_turret)) + LocalObject(1427, MannedTurret.Constructor(manned_turret)) + LocalObject(1428, MannedTurret.Constructor(manned_turret)) + LocalObject(1431, MannedTurret.Constructor(manned_turret)) + LocalObject(1432, MannedTurret.Constructor(manned_turret)) LocalObject(1492, ProximityTerminal.Constructor(medical_terminal)) //lobby LocalObject(1494, ProximityTerminal.Constructor(medical_terminal)) //kitchen LocalObject(1564, Terminal.Constructor(order_terminal)) @@ -1330,6 +1340,14 @@ object Maps { ObjectToBuilding(1186, 2) ObjectToBuilding(1187, 2) ObjectToBuilding(1188, 2) + ObjectToBuilding(1418, 2) + ObjectToBuilding(1419, 2) + ObjectToBuilding(1421, 2) + ObjectToBuilding(1426, 2) + ObjectToBuilding(1427, 2) + ObjectToBuilding(1428, 2) + ObjectToBuilding(1431, 2) + ObjectToBuilding(1432, 2) ObjectToBuilding(1492, 2) ObjectToBuilding(1494, 2) ObjectToBuilding(1479, 2) @@ -1385,6 +1403,14 @@ object Maps { DoorToLock(715, 751) TerminalToSpawnPad(224, 223) TerminalToSpawnPad(2419, 1479) + TurretToWeapon(1418, 5000) + TurretToWeapon(1419, 5001) + TurretToWeapon(1421, 5002) + TurretToWeapon(1426, 5003) + TurretToWeapon(1427, 5004) + TurretToWeapon(1428, 5005) + TurretToWeapon(1431, 5006) + TurretToWeapon(1432, 5007) } def Building38() : Unit = { @@ -1487,6 +1513,8 @@ object Maps { LocalObject(1226, Locker.Constructor) LocalObject(1227, Locker.Constructor) LocalObject(1228, Locker.Constructor) + LocalObject(1440, MannedTurret.Constructor(manned_turret)) + LocalObject(1442, MannedTurret.Constructor(manned_turret)) LocalObject(1591, Terminal.Constructor(order_terminal)) LocalObject(1592, Terminal.Constructor(order_terminal)) LocalObject(1593, Terminal.Constructor(order_terminal)) @@ -1514,6 +1542,8 @@ object Maps { ObjectToBuilding(1226, 49) ObjectToBuilding(1227, 49) ObjectToBuilding(1228, 49) + ObjectToBuilding(1440, 49) + ObjectToBuilding(1442, 49) ObjectToBuilding(1591, 49) ObjectToBuilding(1592, 49) ObjectToBuilding(1593, 49) @@ -1529,6 +1559,8 @@ object Maps { DoorToLock(431, 907) DoorToLock(432, 902) DoorToLock(433, 903) + TurretToWeapon(1440, 5008) + TurretToWeapon(1442, 5009) } } @@ -1615,10 +1647,6 @@ object Maps { Projectiles(this) } - val map12 = new ZoneMap("map12") { - Projectiles(this) - } - val map13 = new ZoneMap("map13") { Building1() Building2() @@ -1766,6 +1794,8 @@ object Maps { LocalObject(557, IFFLock.Constructor) LocalObject(558, IFFLock.Constructor) LocalObject(559, IFFLock.Constructor) + LocalObject(670, MannedTurret.Constructor(manned_turret)) + LocalObject(671, MannedTurret.Constructor(manned_turret)) ObjectToBuilding(330, 29) ObjectToBuilding(331, 29) ObjectToBuilding(332, 29) @@ -1774,10 +1804,14 @@ object Maps { ObjectToBuilding(557, 29) ObjectToBuilding(558, 29) ObjectToBuilding(559, 29) + ObjectToBuilding(670, 29) + ObjectToBuilding(671, 29) DoorToLock(330, 558) DoorToLock(331, 559) DoorToLock(332, 556) DoorToLock(333, 557) + TurretToWeapon(670, 5000) + TurretToWeapon(671, 5001) } def Building42() : Unit = { diff --git a/pslogin/src/main/scala/PsLogin.scala b/pslogin/src/main/scala/PsLogin.scala index 869ff6cb..2a18f564 100644 --- a/pslogin/src/main/scala/PsLogin.scala +++ b/pslogin/src/main/scala/PsLogin.scala @@ -219,7 +219,7 @@ object PsLogin { import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import scala.util.{Failure, Success} - implicit val timeout = Timeout(500 milliseconds) + implicit val timeout = Timeout(5 seconds) val requestVehicleEventBus : Future[ServiceManager.LookupResult] = (ServiceManager.serviceManager ask ServiceManager.Lookup("vehicle")).mapTo[ServiceManager.LookupResult] requestVehicleEventBus.onComplete { diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index fae23dcb..a7166325 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -35,7 +35,7 @@ import net.psforever.objects.serverobject.structures.{Building, StructureType, W import net.psforever.objects.serverobject.terminals._ import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage import net.psforever.objects.serverobject.tube.SpawnTube -import net.psforever.objects.vehicles.{AccessPermissionGroup, Cargo, Utility, VehicleLockState} +import net.psforever.objects.vehicles._ import net.psforever.objects.zones.{InterstellarCluster, Zone} import net.psforever.packet.game.objectcreate._ import net.psforever.types._ @@ -43,7 +43,7 @@ import services.{RemoverActor, _} import services.avatar.{AvatarAction, AvatarResponse, AvatarServiceMessage, AvatarServiceResponse} import services.galaxy.{GalaxyResponse, GalaxyServiceResponse} import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse} -import services.vehicle.VehicleAction.UnstowEquipment +import services.vehicle.support.TurretUpgrader import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse} import scala.concurrent.duration._ @@ -54,6 +54,7 @@ import scala.concurrent.duration._ import scala.util.Success import akka.pattern.ask import net.psforever.objects.ballistics.{Projectile, ProjectileResolution} +import net.psforever.objects.serverobject.turret.{MannedTurret, TurretUpgrade} class WorldSessionActor extends Actor with MDCContextAware { import WorldSessionActor._ @@ -179,7 +180,6 @@ class WorldSessionActor extends Actor with MDCContextAware { * Vehicle cleanup that is specific to log out behavior. */ def DismountVehicleOnLogOut() : Unit = { - //TODO Will base guns implement Vehicle type? Don't want those to deconstruct (player.VehicleSeated match { case Some(vehicle_guid) => continent.GUID(vehicle_guid) @@ -572,6 +572,11 @@ class WorldSessionActor extends Actor with MDCContextAware { case VehicleResponse.DetachFromRails(vehicle_guid, pad_guid, pad_position, pad_orientation_z) => sendResponse(ObjectDetachMessage(pad_guid, vehicle_guid, pad_position + Vector3(0,0,0.5f), pad_orientation_z)) + case VehicleResponse.EquipmentInSlot(pkt) => + if(tplayer_guid != guid) { + sendResponse(pkt) + } + case VehicleResponse.InventoryState(obj, parent_guid, start, con_data) => if(tplayer_guid != guid) { //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? @@ -764,20 +769,24 @@ class WorldSessionActor extends Actor with MDCContextAware { case Mountable.MountMessages(tplayer, reply) => reply match { case Mountable.CanMount(obj : ImplantTerminalMech, seat_num) => - val player_guid : PlanetSideGUID = tplayer.GUID - val obj_guid : PlanetSideGUID = obj.GUID - log.info(s"MountVehicleMsg: $player_guid mounts $obj @ $seat_num") - PlayerActionsToCancel() - sendResponse(PlanetsideAttributeMessage(obj_guid, 0, 1000L)) //health of mech - sendResponse(ObjectAttachMessage(obj_guid, player_guid, seat_num)) - vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.MountVehicle(player_guid, obj_guid, seat_num)) + MountingAction(tplayer, obj, seat_num) + sendResponse(PlanetsideAttributeMessage(obj.GUID, 0, 1000L)) //health of mech + + case Mountable.CanMount(obj : MannedTurret, seat_num) => + obj.WeaponControlledFromSeat(seat_num) match { + case Some(weapon : Tool) => + //update mounted weapon belonging to seat + weapon.AmmoSlots.foreach(slot => { //update the magazine(s) in the weapon, specifically + val magazine = slot.Box + sendResponse(InventoryStateMessage(magazine.GUID, weapon.GUID, magazine.Capacity.toLong)) + }) + case _ => ; //no weapons to update + } + sendResponse(PlanetsideAttributeMessage(obj.GUID, 0, obj.Health)) + MountingAction(tplayer, obj, seat_num) case Mountable.CanMount(obj : Vehicle, seat_num) => val obj_guid : PlanetSideGUID = obj.GUID - val player_guid : PlanetSideGUID = tplayer.GUID - log.info(s"MountVehicleMsg: $player_guid mounts $obj_guid @ $seat_num") - vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(obj), continent)) //clear timer - PlayerActionsToCancel() if(seat_num == 0) { //simplistic vehicle ownership management obj.Owner match { case Some(owner_guid) => @@ -791,7 +800,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case None => ; } tplayer.VehicleOwned = Some(obj_guid) - obj.Owner = Some(player_guid) + obj.Owner = Some(tplayer.GUID) } obj.WeaponControlledFromSeat(seat_num) match { case Some(weapon : Tool) => @@ -802,29 +811,27 @@ class WorldSessionActor extends Actor with MDCContextAware { }) case _ => ; //no weapons to update } - sendResponse(ObjectAttachMessage(obj_guid, player_guid, seat_num)) + //sendResponse(PlanetsideAttributeMessage(obj.GUID, 0, obj.Health)) //TODO vehicle max health in definition + vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(obj), continent)) //clear timer AccessContents(obj) - vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.MountVehicle(player_guid, obj_guid, seat_num)) + MountingAction(tplayer, obj, seat_num) case Mountable.CanMount(obj : Mountable, _) => log.warn(s"MountVehicleMsg: $obj is some generic mountable object and nothing will happen") case Mountable.CanDismount(obj : ImplantTerminalMech, seat_num) => - val obj_guid : PlanetSideGUID = obj.GUID - val player_guid : PlanetSideGUID = tplayer.GUID - log.info(s"DismountVehicleMsg: $player_guid dismounts $obj @ $seat_num") - sendResponse(DismountVehicleMsg(player_guid, BailType.Normal, false)) - vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, BailType.Normal, false)) + DismountAction(tplayer, obj, seat_num) + + case Mountable.CanDismount(obj : MannedTurret, seat_num) => + DismountAction(tplayer, obj, seat_num) case Mountable.CanDismount(obj : Vehicle, seat_num) => val player_guid : PlanetSideGUID = tplayer.GUID if(player_guid == player.GUID) { //disembarking self - log.info(s"DismountVehicleMsg: $player_guid dismounts $obj @ $seat_num") TotalDriverVehicleControl(obj) - sendResponse(DismountVehicleMsg(player_guid, BailType.Normal, false)) - vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, BailType.Normal, false)) UnAccessContents(obj) + DismountAction(tplayer, obj, seat_num) } else { vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, seat_num, true, obj.GUID)) @@ -1713,7 +1720,7 @@ class WorldSessionActor extends Actor with MDCContextAware { antDischargingTick.cancel() } - case ItemHacking(tplayer, target, tool_guid, delta, completeAction, tickAction) => + case HackingProgress(progressType, tplayer, target, tool_guid, delta, completeAction, tickAction) => progressBarUpdate.cancel if(progressBarValue.isDefined) { val progressBarVal : Float = progressBarValue.get + delta @@ -1726,10 +1733,9 @@ class WorldSessionActor extends Actor with MDCContextAware { else { HackState.Ongoing } - sendResponse(HackMessage(1, target.GUID, player.GUID, progressBarVal.toInt, 0L, vis, 8L)) + sendResponse(HackMessage(progressType, target.GUID, player.GUID, progressBarVal.toInt, 0L, vis, 8L)) if(progressBarVal > 100) { //done progressBarValue = None - log.info(s"Hacked a $target") // sendResponse(HackMessage(0, target.GUID, player.GUID, 100, 1114636288L, HackState.Hacked, 8L)) completeAction() } @@ -1737,7 +1743,7 @@ class WorldSessionActor extends Actor with MDCContextAware { tickAction.getOrElse(() => Unit)() progressBarValue = Some(progressBarVal) import scala.concurrent.ExecutionContext.Implicits.global - progressBarUpdate = context.system.scheduler.scheduleOnce(250 milliseconds, self, ItemHacking(tplayer, target, tool_guid, delta, completeAction)) + progressBarUpdate = context.system.scheduler.scheduleOnce(250 milliseconds, self, HackingProgress(progressType, tplayer, target, tool_guid, delta, completeAction)) } } @@ -1803,6 +1809,11 @@ class WorldSessionActor extends Actor with MDCContextAware { avatar.Certifications += GalaxyGunship avatar.Certifications += Phantasm avatar.Certifications += UniMAX + avatar.Certifications += Engineering + avatar.Certifications += CombatEngineering + avatar.Certifications += AdvancedEngineering + avatar.Certifications += FortificationEngineering + avatar.Certifications += AssaultEngineering AwardBattleExperiencePoints(avatar, 1000000L) player = new Player(avatar) //player.Position = Vector3(3561.0f, 2854.0f, 90.859375f) //home3, HART C @@ -1812,9 +1823,9 @@ class WorldSessionActor extends Actor with MDCContextAware { //player.Orientation = Vector3(0f, 0f, 132.1875f) // player.ExoSuit = ExoSuitType.MAX //TODO strange issue; divide number above by 10 when uncommenting player.Slot(0).Equipment = SimpleItem(remote_electronics_kit) //Tool(GlobalDefinitions.StandardPistol(player.Faction)) - player.Slot(2).Equipment = Tool(mini_chaingun) //punisher //suppressor + player.Slot(2).Equipment = Tool(nano_dispenser) //punisher //suppressor player.Slot(4).Equipment = Tool(GlobalDefinitions.StandardMelee(player.Faction)) - player.Slot(6).Equipment = AmmoBox(bullet_9mm, 20) //bullet_9mm + player.Slot(6).Equipment = AmmoBox(upgrade_canister) //bullet_9mm player.Slot(9).Equipment = AmmoBox(rocket, 11) //bullet_9mm player.Slot(12).Equipment = AmmoBox(frag_cartridge) //bullet_9mm player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP) @@ -2056,6 +2067,45 @@ class WorldSessionActor extends Actor with MDCContextAware { case _ => ; } }) + + //base turrets + continent.Map.TurretToWeapon.foreach({ case((turret_guid, weapon_guid)) => + val parent_guid = PlanetSideGUID(turret_guid) + continent.GUID(turret_guid) match { + case Some(turret : MannedTurret) => + //attached weapon + turret.ControlledWeapon(1) match { + case Some(obj : Tool) => + val objDef = obj.Definition + sendResponse( + ObjectCreateMessage( + objDef.ObjectId, + obj.GUID, + ObjectCreateMessageParent(parent_guid, 1), + objDef.Packet.ConstructorData(obj).get + ) + ) + case _ => ; + } + //reserved ammunition? + //TODO need to register if it exists + //seat turret occupant + turret.Seats(0).Occupant match { + case Some(tplayer) => + val tdefintion = tplayer.Definition + sendResponse( + ObjectCreateMessage( + tdefintion.ObjectId, + tplayer.GUID, + ObjectCreateMessageParent(parent_guid, 0), + tdefintion.Packet.ConstructorData(tplayer).get + ) + ) + case None => ; + } + case _ => ; + } + }) StopBundlingPackets() self ! SetCurrentAvatar(player) @@ -2079,33 +2129,22 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ ChildObjectStateMessage(object_guid, pitch, yaw) => //the majority of the following check retrieves information to determine if we are in control of the child - player.VehicleSeated match { - case Some(vehicle_guid) => - continent.GUID(vehicle_guid) match { - case Some(obj : Vehicle) => - obj.PassengerInSeat(player) match { - case Some(seat_num) => - obj.WeaponControlledFromSeat(seat_num) match { - case Some(tool) => - if(tool.GUID == object_guid) { - //TODO set tool orientation? - player.Orientation = Vector3(0f, pitch, yaw) - vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.ChildObjectState(player.GUID, object_guid, pitch, yaw)) - } - case None => - log.warn(s"ChildObjectState: player $player is not using stated controllable agent") - } - case None => - log.warn(s"ChildObjectState: player ${player.GUID} is not in a position to use controllable agent") - } - case _ => - log.warn(s"ChildObjectState: player $player's controllable agent not available in scope") + FindContainedWeapon match { + case (Some(_), Some(tool)) => + if(tool.GUID == object_guid) { + //TODO set tool orientation? + player.Orientation = Vector3(0f, pitch, yaw) + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.ChildObjectState(player.GUID, object_guid, pitch, yaw)) } - case None => - //TODO status condition of "playing getting out of vehicle to allow for late packets without warning - //log.warn(s"ChildObjectState: player $player not related to anything with a controllable agent") + else { + log.warn(s"ChildObjectState: ${player.Name} is using a different controllable agent than #${object_guid.guid}") + } + case (Some(obj), None) => + log.warn(s"ChildObjectState: ${player.Name} can not find any controllable agent, let alone #${object_guid.guid}") + case (None, _) => ; + //TODO status condition of "playing getting out of vehicle to allow for late packets without warning + //log.warn(s"ChildObjectState: player $player not related to anything with a controllable agent") } - //log.info("ChildObjectState: " + msg) case msg @ VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, unk5, unk6, unk7, wheels, unk9, unkA) => continent.GUID(vehicle_guid) match { @@ -2299,9 +2338,9 @@ class WorldSessionActor extends Actor with MDCContextAware { FindContainedWeapon match { case (Some(obj), Some(tool : Tool)) => val originalAmmoType = tool.AmmoType - val fullMagazine = tool.MaxMagazine do { val requestedAmmoType = tool.NextAmmoType + val fullMagazine = tool.MaxMagazine if(requestedAmmoType != tool.AmmoSlot.Box.AmmoType) { FindEquipmentStock(obj, FindAmmoBoxThatUses(requestedAmmoType), fullMagazine, CountAmmunition).reverse match { case Nil => ; @@ -2823,14 +2862,13 @@ class WorldSessionActor extends Actor with MDCContextAware { log.warn(s"Player ${player.GUID} - ${player.Faction} tried to refill silo ${resourceSilo.GUID} - ${resourceSilo.Faction} belonging to another empire") } - case Some(panel : IFFLock) => if(panel.Faction != player.Faction && panel.HackedBy.isEmpty) { player.Slot(player.DrawnSlot).Equipment match { case Some(tool : SimpleItem) => if(tool.Definition == GlobalDefinitions.remote_electronics_kit) { progressBarValue = Some(-GetPlayerHackSpeed()) - self ! WorldSessionActor.ItemHacking(player, panel, tool.GUID, GetPlayerHackSpeed(), FinishHacking(panel, 1114636288L)) + self ! WorldSessionActor.HackingProgress(1, player, panel, tool.GUID, GetPlayerHackSpeed(), FinishHacking(panel, 1114636288L)) log.info("Hacking a door~") } case _ => ; @@ -2892,7 +2930,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(tool: SimpleItem) => if (tool.Definition == GlobalDefinitions.remote_electronics_kit) { progressBarValue = Some(-GetPlayerHackSpeed()) - self ! WorldSessionActor.ItemHacking(player, obj, tool.GUID, GetPlayerHackSpeed(), FinishHacking(obj, 3212836864L)) + self ! WorldSessionActor.HackingProgress(1, player, obj, tool.GUID, GetPlayerHackSpeed(), FinishHacking(obj, 3212836864L)) log.info("Hacking a locker") } case _ => ; @@ -2907,6 +2945,32 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info(s"UseItem: not $player's locker") } + case Some(obj : MannedTurret) => + player.Slot(player.DrawnSlot).Equipment match { + case Some(tool : Tool) => + if(tool.Definition == GlobalDefinitions.nano_dispenser && tool.Magazine > 0) { + val ammo = tool.AmmoType + if(ammo == Ammo.upgrade_canister && obj.Seats.values.count(_.isOccupied) == 0) { + progressBarValue = Some(-1.25f) + self ! WorldSessionActor.HackingProgress( + 2, + player, + obj, + tool.GUID, + 1.25f, + FinishUpgradingMannedTurret(obj, tool, TurretUpgrade(unk2.toInt)) + ) + } + else if(ammo == Ammo.armor_canister && obj.Health < obj.MaxHealth) { + //repair turret + } + } + else if(tool.Definition == GlobalDefinitions.trek) { + //infect turret with virus + } + case _ => ; + } + case Some(obj : Vehicle) => val equipment = player.Slot(player.DrawnSlot).Equipment if(player.Faction == obj.Faction) { @@ -2969,7 +3033,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(tool: SimpleItem) => if (tool.Definition == GlobalDefinitions.remote_electronics_kit) { progressBarValue = Some(-GetPlayerHackSpeed()) - self ! WorldSessionActor.ItemHacking(player, obj, tool.GUID, GetPlayerHackSpeed(), FinishHacking(obj, 3212836864L)) + self ! WorldSessionActor.HackingProgress(1, player, obj, tool.GUID, GetPlayerHackSpeed(), FinishHacking(obj, 3212836864L)) log.info("Hacking a terminal") } case _ => ; @@ -2979,7 +3043,6 @@ class WorldSessionActor extends Actor with MDCContextAware { // Otherwise allow the faction that owns the terminal to use it sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) } - } case Some(obj : SpawnTube) => @@ -3853,7 +3916,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } /** - * The process of hacking an object is completed + * The process of hacking an object is completed. * Pass the message onto the hackable object and onto the local events system. * @param target the `Hackable` object that has been hacked * @param unk na; @@ -3862,16 +3925,33 @@ class WorldSessionActor extends Actor with MDCContextAware { */ //TODO add params here depending on which params in HackMessage are important private def FinishHacking(target : PlanetSideServerObject with Hackable, unk : Long)() : Unit = { + log.info(s"Hacked a $target") // Wait for the target actor to set the HackedBy property, otherwise LocalAction.HackTemporarily will not complete properly import scala.concurrent.ExecutionContext.Implicits.global ask(target.Actor, CommonMessages.Hack(player))(1 second).mapTo[Boolean].onComplete { case Success(_) => localService ! LocalServiceMessage(continent.Id, LocalAction.TriggerSound(player.GUID, target.HackSound, player.Position, 30, 0.49803925f)) - localService ! LocalServiceMessage(continent.Id, LocalAction.HackTemporarily(player.GUID, continent, target, unk)) - case scala.util.Failure(_) => log.warn(s"Hack message failed on target guid: ${target.GUID}") - } + localService ! LocalServiceMessage(continent.Id, LocalAction.HackTemporarily(player.GUID, continent, target, unk)) + case scala.util.Failure(_) => + log.warn(s"Hack message failed on target guid: ${target.GUID}") + } } + /** + * The process of upgrading a turret's weapon(s) is completed. + * Pass the message onto the turret and onto the vehicle events system. + * Additionally, force-deplete the ammunition count of the nano-dispenser used to perform the upgrade. + * @param target the turret + * @param tool the nano-dispenser that was used to perform this upgrade + * @param upgrade the new upgrade state + */ + private def FinishUpgradingMannedTurret(target : MannedTurret, tool : Tool, upgrade : TurretUpgrade.Value)() : Unit = { + log.info(s"Converting manned wall turret weapon to $upgrade") + tool.Magazine = 0 + sendResponse(InventoryStateMessage(tool.AmmoSlot.Box.GUID, tool.GUID, 0)) + vehicleService ! VehicleServiceMessage.TurretUpgrade(TurretUpgrader.ClearSpecific(List(target), continent)) + vehicleService ! VehicleServiceMessage.TurretUpgrade(TurretUpgrader.AddTask(target, continent, upgrade)) + } /** * Temporary function that iterates over vehicle permissions and turns them into `PlanetsideAttributeMessage` packets.
@@ -3887,7 +3967,10 @@ class WorldSessionActor extends Actor with MDCContextAware { * Occasionally, during deployment, local(?) vehicle seat access permissions may change. * This results in players being locked into their own vehicle. * Reloading vehicle permissions supposedly ensures the seats will be properly available. - * This is considered a client issue; but, somehow, it also impacts server operation somehow. + * This is considered a client issue; but, somehow, it also impacts server operation somehow.
+ *
+ * 22 June 2018:
+ * I think vehicle ownership works properly now. * @param vehicle the `Vehicle` */ def ReloadVehicleAccessPermissions(vehicle : Vehicle) : Unit = { @@ -4025,7 +4108,7 @@ class WorldSessionActor extends Actor with MDCContextAware { player.VehicleSeated match { case Some(vehicle_guid) => //weapon is vehicle turret? continent.GUID(vehicle_guid) match { - case Some(vehicle : Vehicle) => + case Some(vehicle : Mountable with MountedWeapons with Container) => vehicle.PassengerInSeat(player) match { case Some(seat_num) => (Some(vehicle), vehicle.WeaponControlledFromSeat(seat_num)) @@ -5312,6 +5395,34 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + /** + * Common activities/procedure when a player mounts a valid object. + * @param tplayer the player + * @param obj the mountable object + * @param seatNum the seat into which the player is mounting + */ + def MountingAction(tplayer : Player, obj : PlanetSideGameObject with Mountable, seatNum : Int) : Unit = { + val player_guid : PlanetSideGUID = tplayer.GUID + val obj_guid : PlanetSideGUID = obj.GUID + PlayerActionsToCancel() + log.info(s"MountVehicleMsg: $player_guid mounts $obj @ $seatNum") + sendResponse(ObjectAttachMessage(obj_guid, player_guid, seatNum)) + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.MountVehicle(player_guid, obj_guid, seatNum)) + } + + /** + * Common activities/procedure when a player dismounts a valid object. + * @param tplayer the player + * @param obj the mountable object + * @param seatNum the seat out of which which the player is disembarking + */ + def DismountAction(tplayer : Player, obj : PlanetSideGameObject with Mountable, seatNum : Int) : Unit = { + val player_guid : PlanetSideGUID = tplayer.GUID + log.info(s"DismountVehicleMsg: ${tplayer.Name} dismounts $obj from $seatNum") + sendResponse(DismountVehicleMsg(player_guid, BailType.Normal, false)) + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, BailType.Normal, false)) + } + def failWithError(error : String) = { log.error(error) sendResponse(ConnectionClose()) @@ -5470,9 +5581,12 @@ object WorldSessionActor { /** - * A message that indicates the user is using a remote electronics kit to hack some server object. + * A message that indicates the user is using a remote electronics kit to hack some server object.
+ *
* Each time this message is sent for a given hack attempt counts as a single "tick" of progress. * The process of "making progress" with a hack involves sending this message repeatedly until the progress is 100 or more. + * To calculate the actual amount of change in the progress `delta`, + * start with 100, divide by the length of time in seconds, then divide once more by 4. * @param tplayer the player * @param target the object being hacked * @param tool_guid the REK @@ -5480,12 +5594,13 @@ object WorldSessionActor { * @param completeAction a custom action performed once the hack is completed * @param tickAction an optional action is is performed for each tick of progress */ - private final case class ItemHacking(tplayer : Player, - target : PlanetSideServerObject, - tool_guid : PlanetSideGUID, - delta : Float, - completeAction : () => Unit, - tickAction : Option[() => Unit] = None) + private final case class HackingProgress(progressType : Int, + tplayer : Player, + target : PlanetSideServerObject, + tool_guid : PlanetSideGUID, + delta : Float, + completeAction : () => Unit, + tickAction : Option[() => Unit] = None) private final case class NtuCharging(tplayer: Player, vehicle: Vehicle) diff --git a/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala b/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala deleted file mode 100644 index e69de29b..00000000 diff --git a/pslogin/src/test/scala/ActorTest.scala b/pslogin/src/test/scala/ActorTest.scala index c2e03982..a218c649 100644 --- a/pslogin/src/test/scala/ActorTest.scala +++ b/pslogin/src/test/scala/ActorTest.scala @@ -2,7 +2,7 @@ import akka.actor.{ActorRef, ActorSystem, MDCContextAware} import akka.testkit.{ImplicitSender, TestKit, TestProbe} -import com.typesafe.config.{ConfigFactory, ConfigValueFactory} +import com.typesafe.config.ConfigFactory import net.psforever.packet.{ControlPacket, GamePacket} import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import org.specs2.specification.Scope @@ -39,7 +39,7 @@ object ActorTest { Normally inaccessible from the outside, the payload is unwrapped within the standard receive PartialFunction. By interacting with a TestProbe constructor param, information that would be concealed by MdcMsg can be polled. - The l-neighbor of the MDCContextAware is the system of the ActorTest TestKit. + The l-neighbor of the MDCContextAware is the system of the base.ActorTest TestKit. The r-neighbor of the MDCContextAware is this MDCTestProbe and, indirectly, the TestProbe that was interjected. Pass l-input into the MDCContextAware itself. The r-output is a normal message that can be polled on that TestProbe.