diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 4c9b542b6..d22bea556 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -9,6 +9,8 @@ import net.psforever.objects.equipment._ import net.psforever.objects.inventory.InventoryTile import net.psforever.objects.serverobject.implantmech.ImplantTerminalMechDefinition import net.psforever.objects.serverobject.locks.IFFLockDefinition +import net.psforever.objects.serverobject.mblocker.LockerDefinition +import net.psforever.objects.serverobject.pad.VehicleSpawnPadDefinition import net.psforever.objects.serverobject.terminals._ import net.psforever.objects.vehicles.SeatArmorRestriction import net.psforever.types.PlanetSideEmpire @@ -498,9 +500,9 @@ object GlobalDefinitions { val vehicle_terminal_combined = new VehicleTerminalCombinedDefinition - val spawn_pad = new ObjectDefinition(800) { Name = "spawn_pad" } + val spawn_pad = new VehicleSpawnPadDefinition - val mb_locker = new ObjectDefinition(524) { Name = "mb_locker" } + val mb_locker = new LockerDefinition val lock_external = new IFFLockDefinition @@ -827,10 +829,10 @@ object GlobalDefinitions { bullet_9mm_AP.Capacity = 50 bullet_9mm_AP.Tile = InventoryTile.Tile33 - shotgun_shell.Capacity = 32 + shotgun_shell.Capacity = 16 shotgun_shell.Tile = InventoryTile.Tile33 - shotgun_shell_AP.Capacity = 32 + shotgun_shell_AP.Capacity = 16 shotgun_shell_AP.Tile = InventoryTile.Tile33 energy_cell.Capacity = 50 diff --git a/common/src/main/scala/net/psforever/objects/LockerContainer.scala b/common/src/main/scala/net/psforever/objects/LockerContainer.scala index 91f13590a..0282b8a39 100644 --- a/common/src/main/scala/net/psforever/objects/LockerContainer.scala +++ b/common/src/main/scala/net/psforever/objects/LockerContainer.scala @@ -8,6 +8,12 @@ import net.psforever.packet.game.PlanetSideGUID import scala.annotation.tailrec +/** + * The companion of a `Locker` that is carried with a player + * masquerading as their sixth `EquipmentSlot` object and a sub-inventory item. + * The `Player` class refers to it as the "fifth slot" as its permanent slot number is encoded as `0x85`. + * The inventory of this object is accessed using a game world `Locker` object (`mb_locker`). + */ class LockerContainer extends Equipment with Container { private val inventory = GridInventory(30, 20) diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index 710a38458..6da1a64c6 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -4,6 +4,7 @@ package net.psforever.objects import net.psforever.objects.definition.{AvatarDefinition, ImplantDefinition} import net.psforever.objects.equipment.{Equipment, EquipmentSize} import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} +import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.packet.game.PlanetSideGUID import net.psforever.types._ @@ -16,7 +17,7 @@ class Player(private val name : String, private val sex : CharacterGender.Value, private val head : Int, private val voice : Int - ) extends PlanetSideGameObject with Container { + ) extends PlanetSideGameObject with FactionAffinity with Container { private var alive : Boolean = false private var backpack : Boolean = false private var health : Int = 0 diff --git a/common/src/main/scala/net/psforever/objects/Vehicle.scala b/common/src/main/scala/net/psforever/objects/Vehicle.scala index 98e0f4851..eb2bd6362 100644 --- a/common/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/common/src/main/scala/net/psforever/objects/Vehicle.scala @@ -4,8 +4,9 @@ package net.psforever.objects import net.psforever.objects.definition.VehicleDefinition import net.psforever.objects.equipment.{Equipment, EquipmentSize} import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem, InventoryTile} -import net.psforever.objects.mount.Mountable +import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.PlanetSideServerObject +import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.vehicles.{AccessPermissionGroup, Seat, Utility, VehicleLockState} import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.objectcreate.DriveState @@ -27,7 +28,10 @@ import scala.collection.mutable * stores and unloads pertinent information about the `Vehicle`'s configuration; * used in the initialization process (`loadVehicleDefinition`) */ -class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServerObject with Mountable with Container { +class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServerObject + with FactionAffinity + with Mountable + with Container { private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR private var owner : Option[PlanetSideGUID] = None private var health : Int = 1 @@ -63,7 +67,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ this.faction } - def Faction_=(faction : PlanetSideEmpire.Value) : PlanetSideEmpire.Value = { + override def Faction_=(faction : PlanetSideEmpire.Value) : PlanetSideEmpire.Value = { this.faction = faction faction } @@ -465,11 +469,17 @@ object Vehicle { /** * The `Vehicle` will become unresponsive to player activity. - * Usually, it does this to await deconstruction and clean-up + * Usually, it does this to await deconstruction and clean-up. * @see `VehicleControl` */ final case class PrepareForDeletion() + /** + * The `Vehicle` will resume previous unresponsiveness to player activity. + * @see `VehicleControl` + */ + final case class Reactivate() + /** * Overloaded constructor. * @param vehicleDef the vehicle's definition entry diff --git a/common/src/main/scala/net/psforever/objects/mount/MountableBehavior.scala b/common/src/main/scala/net/psforever/objects/mount/MountableBehavior.scala deleted file mode 100644 index 6ccd0dfbd..000000000 --- a/common/src/main/scala/net/psforever/objects/mount/MountableBehavior.scala +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.mount - -import akka.actor.Actor - -/** - * The logic governing `Mountable` objects that use the `TryMount` message. - * This is a mix-in trait for combining the `Receive` logic. - * @see `Seat` - * @see `Mountable` - */ -trait MountableBehavior { - this : Actor => - - def MountableObject : Mountable - - val mountableBehavior : Receive = { - case Mountable.TryMount(user, seat_num) => - MountableObject.Seat(seat_num) match { - case Some(seat) => - if((seat.Occupant = user).contains(user)) { - sender ! Mountable.MountMessages(user, Mountable.CanMount(MountableObject, seat_num)) - } - else { - sender ! Mountable.MountMessages(user, Mountable.CanNotMount(MountableObject, seat_num)) - } - case None => - sender ! Mountable.MountMessages(user, Mountable.CanNotMount(MountableObject, seat_num)) - } - } -} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/PlanetSideServerObject.scala b/common/src/main/scala/net/psforever/objects/serverobject/PlanetSideServerObject.scala index b81d8e0f6..3f8436b62 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/PlanetSideServerObject.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/PlanetSideServerObject.scala @@ -3,12 +3,13 @@ package net.psforever.objects.serverobject import akka.actor.ActorRef import net.psforever.objects.PlanetSideGameObject +import net.psforever.objects.serverobject.affinity.FactionAffinity /** * An object layered on top of the standard game object class that maintains an internal `ActorRef`. * A measure of synchronization can be managed using this `Actor`. */ -abstract class PlanetSideServerObject extends PlanetSideGameObject { +abstract class PlanetSideServerObject extends PlanetSideGameObject with FactionAffinity { private var actor = ActorRef.noSender /** diff --git a/common/src/main/scala/net/psforever/objects/serverobject/affinity/FactionAffinity.scala b/common/src/main/scala/net/psforever/objects/serverobject/affinity/FactionAffinity.scala new file mode 100644 index 000000000..8fecc4de0 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/affinity/FactionAffinity.scala @@ -0,0 +1,35 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.affinity + +import net.psforever.types.PlanetSideEmpire + +/** + * Keep track of the allegiance of the object in terms of its association to a `PlanetSideEmpire` value. + */ +trait FactionAffinity { + def Faction : PlanetSideEmpire.Value + + def Faction_=(fac : PlanetSideEmpire.Value) : PlanetSideEmpire.Value = Faction +} + +object FactionAffinity { + /** + * Message that makes the server object transmit IFF feedback. + * @see AssertFactionAffinity + */ + final case class ConfirmFactionAffinity() + /** + * Message that makes the server object change allegiance to the specified faction value. + * Transmit IFF feedback when done. + * @param faction the allegiance to which to change + */ + final case class ConvertFactionAffinity(faction : PlanetSideEmpire.Value) + /** + * Message that responds to an IFF feedback request. + * Transmit IFF feedback when done. + * @see ConfirmFactionAffinity + * @param obj the governed object + * @param faction the allegiance to which the object belongs + */ + final case class AssertFactionAffinity(obj : FactionAffinity, faction : PlanetSideEmpire.Value) +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/affinity/FactionAffinityBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/affinity/FactionAffinityBehavior.scala new file mode 100644 index 000000000..21ab5b733 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/affinity/FactionAffinityBehavior.scala @@ -0,0 +1,42 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.affinity + +import akka.actor.Actor + +object FactionAffinityBehavior { + + /** + * A `trait` for inheritance of common implementable methods. + */ + sealed trait BasicAffinity { + def FactionObject : FactionAffinity + } + + /** + * The logic governing `FactionAffinity` objects that use the `ConvertFactionAffinity` message. + * This is a mix-in trait for combining with existing `Receive` logic. + */ + trait Convert extends BasicAffinity { + this : Actor => + + val convertBehavior : Receive = { + case FactionAffinity.ConvertFactionAffinity(faction) => + FactionObject.Faction = faction + sender ! FactionAffinity.AssertFactionAffinity(FactionObject, faction) + } + } + + /** + * The logic governing `FactionAffinity` objects that use the `ConfirmFactionAffinity` message. + * A case exists to catch `AssertFactionAffinity` messages for the same ends though they should not be used this way. + * This is a mix-in trait for combining with existing `Receive` logic. + */ + trait Check extends BasicAffinity { + this : Actor => + + val checkBehavior : Receive = { + case FactionAffinity.ConfirmFactionAffinity() | FactionAffinity.AssertFactionAffinity(_, _) => + sender ! FactionAffinity.AssertFactionAffinity(FactionObject, FactionObject.Faction) + } + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/doors/Base.scala b/common/src/main/scala/net/psforever/objects/serverobject/doors/Base.scala deleted file mode 100644 index 90e6c490a..000000000 --- a/common/src/main/scala/net/psforever/objects/serverobject/doors/Base.scala +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.serverobject.doors - -import net.psforever.types.PlanetSideEmpire - -/** - * A temporary class to represent "facilities" and "structures." - * @param id the map id of the base - */ -class Base(private val id : Int) { - private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL - - def Id : Int = id - - def Faction : PlanetSideEmpire.Value = faction - - def Faction_=(emp : PlanetSideEmpire.Value) : PlanetSideEmpire.Value = { - faction = emp - Faction - } -} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala b/common/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala index 5f9b26de0..562ce8d04 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala @@ -1,8 +1,8 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.doors -import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.Player +import net.psforever.objects.serverobject.structures.Amenity import net.psforever.packet.game.UseItemMessage import net.psforever.types.Vector3 @@ -10,7 +10,7 @@ import net.psforever.types.Vector3 * A structure-owned server object that is a "door" that can open and can close. * @param ddef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields */ -class Door(private val ddef : DoorDefinition) extends PlanetSideServerObject { +class Door(private val ddef : DoorDefinition) extends Amenity { private var openState : Option[Player] = None /** a vector in the direction of the "outside" of a room; * typically, any locking utility is on that same "outside" */ diff --git a/common/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala index 8f1163316..0b1123457 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala @@ -2,13 +2,16 @@ package net.psforever.objects.serverobject.doors import akka.actor.Actor +import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} /** * An `Actor` that handles messages being dispatched to a specific `Door`. * @param door the `Door` object being governed */ -class DoorControl(door : Door) extends Actor { - def receive : Receive = { +class DoorControl(door : Door) extends Actor with FactionAffinityBehavior.Check { + def FactionObject : FactionAffinity = door + + def receive : Receive = checkBehavior.orElse { case Door.Use(player, msg) => sender ! Door.DoorMessage(player, msg, door.Use(player, msg)) diff --git a/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMech.scala b/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMech.scala index 114a20f30..d9d9af334 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMech.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMech.scala @@ -3,8 +3,8 @@ package net.psforever.objects.serverobject.implantmech import net.psforever.objects.Player import net.psforever.objects.definition.ObjectDefinition -import net.psforever.objects.mount.Mountable -import net.psforever.objects.serverobject.PlanetSideServerObject +import net.psforever.objects.serverobject.mount.Mountable +import net.psforever.objects.serverobject.structures.Amenity import net.psforever.objects.vehicles.Seat /** @@ -12,7 +12,7 @@ import net.psforever.objects.vehicles.Seat * For the most part, it merely implements the support data structures indicated by `Mountable`. * @param idef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields */ -class ImplantTerminalMech(private val idef : ImplantTerminalMechDefinition) extends PlanetSideServerObject with Mountable { +class ImplantTerminalMech(private val idef : ImplantTerminalMechDefinition) extends Amenity with Mountable { private val seats : Map[Int, Seat] = Map( 0 -> new Seat(idef.Seats(0)) ) def Seats : Map[Int, Seat] = seats diff --git a/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala index e47a675bb..67f5d14cd 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala @@ -2,16 +2,23 @@ package net.psforever.objects.serverobject.implantmech import akka.actor.Actor -import net.psforever.objects.mount.MountableBehavior +import net.psforever.objects.serverobject.mount.MountableBehavior +import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} /** * An `Actor` that handles messages being dispatched to a specific `ImplantTerminalMech`. * @param mech the "mech" object being governed */ -class ImplantTerminalMechControl(mech : ImplantTerminalMech) extends Actor with MountableBehavior { - override def MountableObject = mech +class ImplantTerminalMechControl(mech : ImplantTerminalMech) extends Actor with FactionAffinityBehavior.Check + with MountableBehavior.Mount with MountableBehavior.Dismount { + def MountableObject = mech //do not add type! - def receive : Receive = mountableBehavior.orElse { - case _ => ; - } + def FactionObject : FactionAffinity = mech + + def receive : Receive = checkBehavior + .orElse(mountBehavior) + .orElse(dismountBehavior) + .orElse { + case _ => ; + } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLock.scala b/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLock.scala index a1c3380db..8142fd9af 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLock.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLock.scala @@ -2,7 +2,7 @@ package net.psforever.objects.serverobject.locks import net.psforever.objects.Player -import net.psforever.objects.serverobject.PlanetSideServerObject +import net.psforever.objects.serverobject.structures.Amenity import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.Vector3 @@ -15,7 +15,7 @@ import net.psforever.types.Vector3 * The `IFFLock` is ideally associated with a server map object - a `Door` - to which it acts as a gatekeeper. * @param idef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields */ -class IFFLock(private val idef : IFFLockDefinition) extends PlanetSideServerObject { +class IFFLock(private val idef : IFFLockDefinition) extends Amenity { /** * An entry that maintains a reference to the `Player`, and the player's GUID and location when the message was received. */ diff --git a/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala index bf9d1b81a..0af058115 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala @@ -3,14 +3,17 @@ package net.psforever.objects.serverobject.locks import akka.actor.Actor import net.psforever.objects.serverobject.CommonMessages +import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} /** * An `Actor` that handles messages being dispatched to a specific `IFFLock`. * @param lock the `IFFLock` object being governed * @see `CommonMessages` */ -class IFFLockControl(lock : IFFLock) extends Actor { - def receive : Receive = { +class IFFLockControl(lock : IFFLock) extends Actor with FactionAffinityBehavior.Check { + def FactionObject : FactionAffinity = lock + + def receive : Receive = checkBehavior.orElse { case CommonMessages.Hack(player) => lock.HackedBy = player diff --git a/common/src/main/scala/net/psforever/objects/serverobject/mblocker/Locker.scala b/common/src/main/scala/net/psforever/objects/serverobject/mblocker/Locker.scala index 60ee7979b..d614b53a8 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/mblocker/Locker.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/mblocker/Locker.scala @@ -1,19 +1,18 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.mblocker -import akka.actor.ActorContext +import akka.actor.{ActorContext, Props} import net.psforever.objects.GlobalDefinitions -import net.psforever.objects.definition.ObjectDefinition -import net.psforever.objects.serverobject.PlanetSideServerObject +import net.psforever.objects.serverobject.structures.Amenity -class Locker extends PlanetSideServerObject { - def Definition : ObjectDefinition = GlobalDefinitions.mb_locker +class Locker extends Amenity { + def Definition : LockerDefinition = GlobalDefinitions.mb_locker } object Locker { /** * Overloaded constructor. - * @return a `VehicleSpawnPad` object + * @return the `Locker` object */ def apply() : Locker = { new Locker() @@ -28,6 +27,7 @@ object Locker { */ def Constructor(id : Int, context : ActorContext) : Locker = { val obj = Locker() + obj.Actor = context.actorOf(Props(classOf[LockerControl], obj), s"${obj.Definition.Name}_$id") obj } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerControl.scala new file mode 100644 index 000000000..2e037e644 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerControl.scala @@ -0,0 +1,17 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.mblocker + +import akka.actor.Actor +import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} + +/** + * An `Actor` that handles messages being dispatched to a specific `Locker`. + * @param locker the `Locker` object being governed + */ +class LockerControl(locker : Locker) extends Actor with FactionAffinityBehavior.Check { + def FactionObject : FactionAffinity = locker + + def receive : Receive = checkBehavior.orElse { + case _ => ; + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerDefinition.scala new file mode 100644 index 000000000..b73a9e533 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerDefinition.scala @@ -0,0 +1,12 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.mblocker + +import net.psforever.objects.definition.ObjectDefinition + +/** + * The definition for any `Locker`. + * Object Id 524. + */ +class LockerDefinition extends ObjectDefinition(524) { + Name = "mb_locker" +} diff --git a/common/src/main/scala/net/psforever/objects/mount/Mountable.scala b/common/src/main/scala/net/psforever/objects/serverobject/mount/Mountable.scala similarity index 80% rename from common/src/main/scala/net/psforever/objects/mount/Mountable.scala rename to common/src/main/scala/net/psforever/objects/serverobject/mount/Mountable.scala index 06b63faff..b65a251f7 100644 --- a/common/src/main/scala/net/psforever/objects/mount/Mountable.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/mount/Mountable.scala @@ -1,5 +1,5 @@ // Copyright (c) 2017 PSForever -package net.psforever.objects.mount +package net.psforever.objects.serverobject.mount import akka.actor.ActorRef import net.psforever.objects.Player @@ -62,6 +62,8 @@ object Mountable { */ final case class TryMount(player : Player, seat_num : Int) + final case class TryDismount(player : Player, seat_num : Int) + /** * A basic `Trait` connecting all of the actionable `Mountable` response messages. */ @@ -89,4 +91,19 @@ object Mountable { * @param seat_num the seat index */ final case class CanNotMount(obj : Mountable, seat_num : Int) extends Exchange + + /** + * Message sent in response to the player succeeding to disembark a `Mountable` object. + * The player was previously seated at the given index. + * @param obj the `Mountable` object + * @param seat_num the seat index + */ + final case class CanDismount(obj : Mountable, seat_num : Int) extends Exchange + /** + * Message sent in response to the player failing to disembark a `Mountable` object. + * The player is still seated at the given index. + * @param obj the `Mountable` object + * @param seat_num the seat index + */ + final case class CanNotDismount(obj : Mountable, seat_num : Int) extends Exchange } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala new file mode 100644 index 000000000..33f8aa3fd --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala @@ -0,0 +1,68 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.mount + +import akka.actor.Actor +import net.psforever.objects.entity.{Identifiable, WorldEntity} +import net.psforever.objects.serverobject.affinity.FactionAffinity +import net.psforever.types.Vector3 + +object MountableBehavior { + /** + * The logic governing `Mountable` objects that use the `TryMount` message. + * This is a mix-in trait for combining with existing `Receive` logic. + * @see `Seat` + * @see `Mountable` + */ + trait Mount { + this : Actor => + + def MountableObject : Mountable with Identifiable with WorldEntity with FactionAffinity + + val mountBehavior : Receive = { + case Mountable.TryMount(user, seat_num) => + val obj = MountableObject + obj.Seat(seat_num) match { + case Some(seat) => + if(user.Faction == obj.Faction && (seat.Occupant = user).contains(user)) { + user.VehicleSeated = obj.GUID + sender ! Mountable.MountMessages(user, Mountable.CanMount(obj, seat_num)) + } + else { + sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num)) + } + case None => + sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num)) + } + } + } + + /** + * The logic governing `Mountable` objects that use the `TryDismount` message. + * This is a mix-in trait for combining with existing `Receive` logic. + * @see `Seat` + * @see `Mountable` + */ + trait Dismount { + this : Actor => + + def MountableObject : Mountable with Identifiable with WorldEntity with FactionAffinity + + val dismountBehavior : Receive = { + case Mountable.TryDismount(user, seat_num) => + val obj = MountableObject + obj.Seat(seat_num) match { + case Some(seat) => + if(seat.Bailable || obj.Velocity.isEmpty || Vector3.MagnitudeSquared(obj.Velocity.get).toInt == 0) { + seat.Occupant = None + user.VehicleSeated = None + sender ! Mountable.MountMessages(user, Mountable.CanDismount(obj, seat_num)) + } + else { + sender ! Mountable.MountMessages(user, Mountable.CanNotDismount(obj, seat_num)) + } + case None => + sender ! Mountable.MountMessages(user, Mountable.CanNotDismount(obj, seat_num)) + } + } + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala index 412774bf1..8f1e75a4b 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala @@ -2,6 +2,7 @@ package net.psforever.objects.serverobject.pad import akka.actor.{Actor, ActorRef, Cancellable} +import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.{DefaultCancellable, Player, Vehicle} import net.psforever.types.Vector3 @@ -29,7 +30,7 @@ import scala.concurrent.duration._ * 3. a callback location for sending messages. * @param pad the `VehicleSpawnPad` object being governed */ -class VehicleSpawnControl(pad : VehicleSpawnPad) extends Actor { +class VehicleSpawnControl(pad : VehicleSpawnPad) extends Actor with FactionAffinityBehavior.Check { /** an executor for progressing a vehicle order through the normal spawning logic */ private var process : Cancellable = DefaultCancellable.obj /** a list of vehicle orders that have been submitted for this spawn pad */ @@ -41,8 +42,9 @@ class VehicleSpawnControl(pad : VehicleSpawnPad) extends Actor { private[this] val log = org.log4s.getLogger private[this] def trace(msg : String) : Unit = log.trace(msg) + def FactionObject : FactionAffinity = pad - def receive : Receive = { + def receive : Receive = checkBehavior.orElse { case VehicleSpawnPad.VehicleOrder(player, vehicle) => trace(s"order from $player for $vehicle received") orders = orders :+ VehicleSpawnControl.OrderEntry(player, vehicle, sender) diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPad.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPad.scala index 16c1357c3..19df456df 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPad.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPad.scala @@ -2,8 +2,7 @@ package net.psforever.objects.serverobject.pad import net.psforever.objects.{Player, Vehicle} -import net.psforever.objects.definition.ObjectDefinition -import net.psforever.objects.serverobject.PlanetSideServerObject +import net.psforever.objects.serverobject.structures.Amenity import net.psforever.packet.game.PlanetSideGUID /** @@ -16,8 +15,8 @@ import net.psforever.packet.game.PlanetSideGUID * @param spDef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields * @see `VehicleSpawnControl` */ -class VehicleSpawnPad(spDef : ObjectDefinition) extends PlanetSideServerObject { - def Definition : ObjectDefinition = spDef +class VehicleSpawnPad(spDef : VehicleSpawnPadDefinition) extends Amenity { + def Definition : VehicleSpawnPadDefinition = spDef } object VehicleSpawnPad { @@ -83,7 +82,7 @@ object VehicleSpawnPad { * @param spDef the spawn pad's definition entry * @return a `VehicleSpawnPad` object */ - def apply(spDef : ObjectDefinition) : VehicleSpawnPad = { + def apply(spDef : VehicleSpawnPadDefinition) : VehicleSpawnPad = { new VehicleSpawnPad(spDef) } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPadDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPadDefinition.scala new file mode 100644 index 000000000..aa7972079 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPadDefinition.scala @@ -0,0 +1,12 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.pad + +import net.psforever.objects.definition.ObjectDefinition + +/** + * The definition for any `VehicleSpawnPad`. + * Object Id 800. + */ +class VehicleSpawnPadDefinition extends ObjectDefinition(800) { + Name = "spawn_pad" +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/Amenity.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/Amenity.scala new file mode 100644 index 000000000..6458757d9 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/Amenity.scala @@ -0,0 +1,60 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.structures + +import net.psforever.objects.serverobject.PlanetSideServerObject +import net.psforever.types.PlanetSideEmpire + +/** + * Amenities are elements of the game that belong to other elements of the game.
+ *
+ * Normal `PlanetSideServerObject` entities (server objects) tend to have properties that are completely internalized. + * An `Amenity` is a server object that maintains a fixed association with another server object. + * This association strips away at the internalization and redirects a reference to some properties somewhere else. + * An `Amenity` object belongs to its `Owner` object; + * the `Amenity` objects looks to its `Owner` object for some of its properties. + * @see `FactionAffinity` + */ +abstract class Amenity extends PlanetSideServerObject { + private var owner : PlanetSideServerObject = Building.NoBuilding + + def Faction : PlanetSideEmpire.Value = Owner.Faction + + /** + * Reference the object that is in direct association with (is superior to) this one. + * @return the object associated as this object's "owner" + */ + def Owner : PlanetSideServerObject = owner + + /** + * Set an object to have a direct association with (be superior to) this one. + * @see `Amenity.AmenityTarget` + * @param obj the object trying to become associated as this object's "owner" + * @tparam T a validation of the type of object that can be an owner + * @return the object associated as this object's "owner" + */ + def Owner_=[T : Amenity.AmenityTarget](obj : T) : PlanetSideServerObject = { + owner = obj.asInstanceOf[PlanetSideServerObject] + Owner + } +} + +object Amenity { + /** + * A `trait` for validating the type of object that can be allowed to become an `Amenity` object's `Owner`.
+ *
+ * The `Owner` defaults to a type of `PlanetSideServerObject` in reference type; + * but, that distinction is mainly to allow for a common ancestor with appropriate methods. + * Only certain types of `PlanetSideServerObject` are formally allowed to be owners. + * In execution, the `T` is the type of object that implicitly converts into an acceptable type of sub-object. + * The companion object maintains the hardcoded conversions. + * If such an implicit conversion does not exist, the assignment is unacceptable at compile time. + * @tparam T the permitted type of object + */ + sealed trait AmenityTarget[T] + + object AmenityTarget { + import net.psforever.objects.Vehicle + implicit object BuildingTarget extends AmenityTarget[Building] { } + implicit object VehicleTarget extends AmenityTarget[Vehicle] { } + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala new file mode 100644 index 000000000..84a14c2c0 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala @@ -0,0 +1,56 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.structures + +import akka.actor.ActorContext +import net.psforever.objects.definition.ObjectDefinition +import net.psforever.objects.serverobject.PlanetSideServerObject +import net.psforever.objects.zones.Zone +import net.psforever.packet.game.PlanetSideGUID +import net.psforever.types.PlanetSideEmpire + +class Building(private val id : Int, private val zone : Zone) extends PlanetSideServerObject { + private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL + private var amenities : List[Amenity] = List.empty + GUID = PlanetSideGUID(0) + + def Id : Int = id + + def Faction : PlanetSideEmpire.Value = faction + + override def Faction_=(fac : PlanetSideEmpire.Value) : PlanetSideEmpire.Value = { + faction = fac + Faction + } + + def Amenities : List[Amenity] = amenities + + def Amenities_=(obj : Amenity) : List[Amenity] = { + amenities = amenities :+ obj + obj.Owner = this + amenities + } + + def Zone : Zone = zone + + def Definition: ObjectDefinition = Building.BuildingDefinition +} + +object Building { + final val NoBuilding : Building = new Building(0, Zone.Nowhere) { + override def Faction_=(faction : PlanetSideEmpire.Value) : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL + override def Amenities_=(obj : Amenity) : List[Amenity] = Nil + } + + final val BuildingDefinition : ObjectDefinition = new ObjectDefinition(0) { Name = "building" } + + def apply(id : Int, zone : Zone) : Building = { + new Building(id, zone) + } + + def Structure(id : Int, zone : Zone, context : ActorContext) : Building = { + import akka.actor.Props + val obj = new Building(id, zone) + obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-building") + obj + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingControl.scala new file mode 100644 index 000000000..2f7b7b7ce --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingControl.scala @@ -0,0 +1,20 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.structures + +import akka.actor.Actor +import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} + +class BuildingControl(building : Building) extends Actor with FactionAffinityBehavior.Check { + def FactionObject : FactionAffinity = building + + def receive : Receive = checkBehavior.orElse { + case FactionAffinity.ConvertFactionAffinity(faction) => + val originalAffinity = building.Faction + if(originalAffinity != (building.Faction = faction)) { + building.Amenities.foreach(_.Actor forward FactionAffinity.ConfirmFactionAffinity()) + } + sender ! FactionAffinity.AssertFactionAffinity(building, faction) + + case _ => ; + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/FoundationBuilder.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/FoundationBuilder.scala new file mode 100644 index 000000000..fdf247e7f --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/FoundationBuilder.scala @@ -0,0 +1,30 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.structures + +import akka.actor.ActorContext +import net.psforever.objects.zones.Zone + +/** + * Defer establishment of a `Building` object until the location for the object is correct (in the correct zone) + * and a `context` in the proper `Actor` hierarchy of that zone exists in scope. + * @see `ServerObjectBuilder` + * @see `Building` + * @param constructor a curried function that eventually constructs a `Building` object + */ +class FoundationBuilder(private val constructor : (Int, Zone, ActorContext)=>Building) { + def Build(id : Int, zone : Zone)(implicit context : ActorContext = null) : Building = { + val obj : Building = constructor(id, zone, context) + obj + } +} + +object FoundationBuilder { + /** + * Overloaded constructor. + * @param constructor a curried function that eventually constructs a `Building` object + * @return a `FoundationBuilder` object + */ + def apply(constructor : (Int, Zone, ActorContext)=>Building) : FoundationBuilder = { + new FoundationBuilder(constructor) + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala index c7c9b5bd7..3f6f6389d 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala @@ -2,7 +2,7 @@ package net.psforever.objects.serverobject.terminals import net.psforever.objects.Player -import net.psforever.objects.serverobject.PlanetSideServerObject +import net.psforever.objects.serverobject.structures.Amenity import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} import net.psforever.types.{TransactionType, Vector3} @@ -10,7 +10,7 @@ import net.psforever.types.{TransactionType, Vector3} * A structure-owned server object that is a "terminal" that can be accessed for amenities and services. * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields */ -class Terminal(tdef : TerminalDefinition) extends PlanetSideServerObject { +class Terminal(tdef : TerminalDefinition) extends Amenity { /** An entry that maintains a reference to the `Player`, and the player's GUID and location when the message was received. */ private var hackedBy : Option[(Player, PlanetSideGUID, Vector3)] = None @@ -65,18 +65,23 @@ class Terminal(tdef : TerminalDefinition) extends PlanetSideServerObject { * @return an actionable message that explains what resulted from interacting with this `Terminal` */ def Request(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = { - msg.transaction_type match { - case TransactionType.Buy | TransactionType.Learn => - tdef.Buy(player, msg) + if(Faction == player.Faction || HackedBy.isDefined) { + msg.transaction_type match { + case TransactionType.Buy | TransactionType.Learn => + tdef.Buy(player, msg) - case TransactionType.Sell => - tdef.Sell(player, msg) + case TransactionType.Sell => + tdef.Sell(player, msg) - case TransactionType.InfantryLoadout => - tdef.Loadout(player, msg) + case TransactionType.InfantryLoadout => + tdef.Loadout(player, msg) - case _ => - Terminal.NoDeal() + case _ => + Terminal.NoDeal() + } + } + else { + Terminal.NoDeal() } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala index c74ada798..ad63babf4 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala @@ -2,13 +2,16 @@ package net.psforever.objects.serverobject.terminals import akka.actor.Actor +import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} /** * An `Actor` that handles messages being dispatched to a specific `Terminal`. * @param term the `Terminal` object being governed */ -class TerminalControl(term : Terminal) extends Actor { - def receive : Receive = { +class TerminalControl(term : Terminal) extends Actor with FactionAffinityBehavior.Check { + def FactionObject : FactionAffinity = term + + def receive : Receive = checkBehavior.orElse { case Terminal.Request(player, msg) => sender ! Terminal.TerminalMessage(player, msg, term.Request(player, msg)) diff --git a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala index 016ce6319..70639d455 100644 --- a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala +++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala @@ -3,7 +3,8 @@ package net.psforever.objects.vehicles import akka.actor.Actor import net.psforever.objects.Vehicle -import net.psforever.objects.mount.MountableBehavior +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 `Vehicle`.
@@ -12,17 +13,32 @@ import net.psforever.objects.mount.MountableBehavior * The latter is applicable only when the specific vehicle is being deconstructed. * @param vehicle the `Vehicle` object being governed */ -class VehicleControl(private val vehicle : Vehicle) extends Actor with MountableBehavior { - override def MountableObject = vehicle +class VehicleControl(private val vehicle : Vehicle) extends Actor + with FactionAffinityBehavior.Check + with MountableBehavior.Mount + with MountableBehavior.Dismount { + def MountableObject = vehicle //do not add type! - def receive : Receive = mountableBehavior.orElse { - case Vehicle.PrepareForDeletion => - context.become(Disabled) + def FactionObject : FactionAffinity = vehicle - case _ => ; - } + def receive : Receive = Enabled - def Disabled : Receive = { - case _ => ; - } + def Enabled : Receive = checkBehavior + .orElse(mountBehavior) + .orElse(dismountBehavior) + .orElse { + case Vehicle.PrepareForDeletion => + context.become(Disabled) + + case _ => ; + } + + def Disabled : Receive = checkBehavior + .orElse(dismountBehavior) + .orElse { + case Vehicle.Reactivate => + context.become(Enabled) + + case _ => ; + } } 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 dbd2501df..13e055048 100644 --- a/common/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -3,19 +3,20 @@ package net.psforever.objects.zones import akka.actor.{ActorContext, ActorRef, Props} import akka.routing.RandomPool -import net.psforever.objects.serverobject.doors.Base import net.psforever.objects.{PlanetSideGameObject, Player, Vehicle} import net.psforever.objects.equipment.Equipment import net.psforever.objects.guid.NumberPoolHub import net.psforever.objects.guid.actor.UniqueNumberSystem 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.packet.GamePacket import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.Vector3 import scala.annotation.tailrec import scala.collection.mutable.ListBuffer +import scala.collection.immutable.{Map => PairMap} /** * A server object representing the one-landmass planets as well as the individual subterranean caverns.
@@ -55,7 +56,7 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { /** */ private var transport : ActorRef = ActorRef.noSender - private var bases : List[Base] = List() + private var buildings : PairMap[Int, Building] = PairMap.empty[Int, Building] /** * Establish the basic accessible conditions necessary for a functional `Zone`.
@@ -79,8 +80,8 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { Map.LocalObjects.foreach({ builderObject => builderObject.Build }) - - MakeBases(Map.LocalBases) + MakeBuildings(context) + AssignAmenities() } } @@ -217,13 +218,20 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { def Transport : ActorRef = transport - def MakeBases(num : Int) : List[Base] = { - bases = (1 to num).map(id => new Base(id)).toList - bases + def Building(id : Int) : Option[Building] = { + buildings.get(id) } - def Base(id : Int) : Option[Base] = { - bases.lift(id) + 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) }) + buildings + } + + private def AssignAmenities() : Unit = { + Map.ObjectToBuilding.foreach({ case(object_guid, building_id) => + buildings(building_id).Amenities = guid(object_guid).get.asInstanceOf[Amenity] + }) } /** @@ -264,6 +272,8 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { } object Zone { + final val Nowhere : Zone = new Zone("nowhere", new ZoneMap("nowhere"), 99) + /** * Message to initialize the `Zone`. * @see `Zone.Init(implicit ActorContext)` 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 d72d7db4b..af5c06eab 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala @@ -2,6 +2,8 @@ package net.psforever.objects.zones import akka.actor.Actor +import net.psforever.objects.PlanetSideGameObject +import org.log4s.Logger /** * na @@ -20,14 +22,16 @@ class ZoneActor(zone : Zone) extends Actor { } def ZoneSetupCheck(): Unit = { + import ZoneActor._ def guid(id : Int) = zone.GUID(id) val map = zone.Map val slog = org.log4s.getLogger(s"zone/${zone.Id}/sanity") + val validateObject : (Int, (PlanetSideGameObject)=>Boolean, String) => Boolean = ValidateObject(guid, slog) //check base to object associations - map.ObjectToBase.foreach({ case((object_guid, base_id)) => - if(zone.Base(base_id).isEmpty) { - slog.error(s"expected a base #$base_id") + map.ObjectToBuilding.foreach({ case((object_guid, base_id)) => + if(zone.Building(base_id).isEmpty) { + slog.error(s"expected a building at id #$base_id") } if(guid(object_guid).isEmpty) { slog.error(s"expected object id $object_guid to exist, but it did not") @@ -35,74 +39,80 @@ class ZoneActor(zone : Zone) extends Actor { }) //check door to lock association - import net.psforever.objects.serverobject.doors.Door - import net.psforever.objects.serverobject.locks.IFFLock map.DoorToLock.foreach({ case((door_guid, lock_guid)) => - try { - if(!guid(door_guid).get.isInstanceOf[Door]) { - slog.error(s"expected id $door_guid to be a door, but it was not") - } - } - catch { - case _ : Exception => - slog.error(s"expected a door at id $door_guid but no object is initialized") - } - try { - if(!guid(lock_guid).get.isInstanceOf[IFFLock]) { - slog.error(s"expected id $lock_guid to be an IFF locks but it was not") - } - } - catch { - case _ : Exception => - slog.error(s"expected an IFF locks at id $lock_guid but no object is initialized") - } + validateObject(door_guid, DoorCheck, "door") + validateObject(lock_guid, LockCheck, "IFF lock") }) //check vehicle terminal to spawn pad association - import net.psforever.objects.serverobject.pad.VehicleSpawnPad - import net.psforever.objects.serverobject.terminals.Terminal map.TerminalToSpawnPad.foreach({ case ((term_guid, pad_guid)) => - try { - if(!guid(term_guid).get.isInstanceOf[Terminal]) { //TODO check is vehicle terminal - slog.error(s"expected id $term_guid to be a terminal, but it was not") - } - } - catch { - case _ : Exception => - slog.error(s"expected a terminal at id $term_guid but no object is initialized") - } - try { - if(!guid(pad_guid).get.isInstanceOf[VehicleSpawnPad]) { - slog.error(s"expected id $pad_guid to be a spawn pad, but it was not") - } - } - catch { - case _ : Exception => - slog.error(s"expected a spawn pad at id $pad_guid but no object is initialized") - } + validateObject(term_guid, TerminalCheck, "vehicle terminal") + validateObject(pad_guid, VehicleSpawnPadCheck, "vehicle spawn pad") }) //check implant terminal mech to implant terminal interface association - import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech map.TerminalToInterface.foreach({case ((mech_guid, interface_guid)) => - try { - if(!guid(mech_guid).get.isInstanceOf[ImplantTerminalMech]) { - slog.error(s"expected id $mech_guid to be an implant terminal mech, but it was not") - } - } - catch { - case _ : Exception => - slog.error(s"expected a implant terminal mech at id $mech_guid but no object is initialized") - } - try { - if(!guid(interface_guid).get.isInstanceOf[Terminal]) { //TODO check is implant terminal - slog.error(s"expected id $interface_guid to be an implant terminal interface, but it was not") - } - } - catch { - case _ : Exception => - slog.error(s"expected a implant terminal interface at id $interface_guid but no object is initialized") - } + validateObject(mech_guid, ImplantMechCheck, "implant terminal mech") + validateObject(interface_guid, TerminalCheck, "implant terminal interface") }) } } + +object ZoneActor { + + /** + * Recover an object from a collection and perform any number of validating tests upon it. + * If the object fails any tests, log an error. + * @param guid access to an association between unique numbers and objects using some of those unique numbers + * @param elog a contraction of "error log;" + * accepts `String` data + * @param object_guid the unique indentifier being checked against the `guid` access point + * @param test a test for the discovered object; + * expects at least `Type` checking + * @param description an explanation of how the object, if not discovered, should be identified + * @return `true` if the object was discovered and validates correctly; + * `false` if the object failed any tests + */ + def ValidateObject(guid : (Int)=>Option[PlanetSideGameObject], elog : Logger) + (object_guid : Int, test : (PlanetSideGameObject)=>Boolean, description : String) : Boolean = { + try { + if(!test(guid(object_guid).get)) { + elog.error(s"expected id $object_guid to be a $description, but it was not") + false + } + else { + true + } + } + catch { + case _ : Exception => + elog.error(s"expected a $description at id $object_guid but no object is initialized") + false + } + } + + def LockCheck(obj : PlanetSideGameObject) : Boolean = { + import net.psforever.objects.serverobject.locks.IFFLock + obj.isInstanceOf[IFFLock] + } + + def DoorCheck(obj : PlanetSideGameObject) : Boolean = { + import net.psforever.objects.serverobject.doors.Door + obj.isInstanceOf[Door] + } + + def TerminalCheck(obj : PlanetSideGameObject) : Boolean = { + import net.psforever.objects.serverobject.terminals.Terminal + obj.isInstanceOf[Terminal] + } + + def ImplantMechCheck(obj : PlanetSideGameObject) : Boolean = { + import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech + obj.isInstanceOf[ImplantTerminalMech] + } + + def VehicleSpawnPadCheck(obj : PlanetSideGameObject) : Boolean = { + import net.psforever.objects.serverobject.pad.VehicleSpawnPad + obj.isInstanceOf[VehicleSpawnPad] + } +} 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 36a5bbe5c..d80192743 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala @@ -1,6 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.zones +import net.psforever.objects.serverobject.structures.FoundationBuilder import net.psforever.objects.serverobject.ServerObjectBuilder /** @@ -29,7 +30,7 @@ class ZoneMap(private val name : String) { private var linkTerminalInterface : Map[Int, Int] = Map() private var linkDoorLock : Map[Int, Int] = Map() private var linkObjectBase : Map[Int, Int] = Map() - private var numBases : Int = 0 + private var buildings : Map[Int, FoundationBuilder] = Map() def Name : String = name @@ -49,19 +50,19 @@ class ZoneMap(private val name : String) { localObjects = localObjects :+ obj } - def LocalBases : Int = numBases + def LocalBuildings : Map[Int, FoundationBuilder] = buildings - def LocalBases_=(num : Int) : Int = { - if(num > 0) { - numBases = num + def LocalBuilding(building_id : Int, constructor : FoundationBuilder) : Int = { + if(building_id > 0) { + buildings = buildings ++ Map(building_id -> constructor) } - LocalBases + buildings.size } - def ObjectToBase : Map[Int, Int] = linkObjectBase + def ObjectToBuilding : Map[Int, Int] = linkObjectBase - def ObjectToBase(object_guid : Int, base_id : Int) : Unit = { - linkObjectBase = linkObjectBase ++ Map(object_guid -> base_id) + def ObjectToBuilding(object_guid : Int, building_id : Int) : Unit = { + linkObjectBase = linkObjectBase ++ Map(object_guid -> building_id) } def DoorToLock : Map[Int, Int] = linkDoorLock diff --git a/common/src/test/scala/objects/BuildingTest.scala b/common/src/test/scala/objects/BuildingTest.scala new file mode 100644 index 000000000..122f3d993 --- /dev/null +++ b/common/src/test/scala/objects/BuildingTest.scala @@ -0,0 +1,169 @@ +// Copyright (c) 2017 PSForever +package objects + +import akka.actor.{ActorRef, Props} +import net.psforever.objects.GlobalDefinitions +import net.psforever.objects.definition.ObjectDefinition +import net.psforever.objects.serverobject.affinity.FactionAffinity +import net.psforever.objects.serverobject.doors.{Door, DoorControl} +import net.psforever.objects.serverobject.structures.{Amenity, Building, BuildingControl} +import net.psforever.objects.zones.Zone +import net.psforever.packet.game.PlanetSideGUID +import net.psforever.types.PlanetSideEmpire +import org.specs2.mutable.Specification + +import scala.concurrent.duration.Duration + +class AmenityTest extends Specification { + class AmenityObject extends Amenity { + def Definition : ObjectDefinition = null + } + + "Amenity" should { + "construct" in { + val ao = new AmenityObject() + ao.Owner mustEqual Building.NoBuilding + } + + "can be owned by a building" in { + val ao = new AmenityObject() + val bldg = Building(10, Zone.Nowhere) + + ao.Owner = bldg + ao.Owner mustEqual bldg + } + + "be owned by a vehicle" in { + import net.psforever.objects.Vehicle + val ao = new AmenityObject() + val veh = Vehicle(GlobalDefinitions.quadstealth) + + ao.Owner = veh + ao.Owner mustEqual veh + } + + "not be owned by an unexpected object" in { + val ao = new AmenityObject() + //ao.Owner = net.psforever.objects.serverobject.mblocker.Locker() //will not compile + ok + } + + "confer faction allegiance through ownership" in { + //see FactionAffinityTest + val ao = new AmenityObject() + val bldg = Building(10, Zone.Nowhere) + ao.Owner = bldg + bldg.Faction mustEqual PlanetSideEmpire.NEUTRAL + ao.Faction mustEqual PlanetSideEmpire.NEUTRAL + + bldg.Faction = PlanetSideEmpire.TR + bldg.Faction mustEqual PlanetSideEmpire.TR + ao.Faction mustEqual PlanetSideEmpire.TR + } + } +} + +class BuildingTest extends Specification { + "Building" should { + "construct" in { + val bldg = Building(10, Zone.Nowhere) + bldg.Id mustEqual 10 + bldg.Actor mustEqual ActorRef.noSender + bldg.Amenities mustEqual Nil + bldg.Zone mustEqual Zone.Nowhere + bldg.Faction mustEqual PlanetSideEmpire.NEUTRAL + } + + "change faction affinity" in { + val bldg = Building(10, Zone.Nowhere) + bldg.Faction mustEqual PlanetSideEmpire.NEUTRAL + + bldg.Faction = PlanetSideEmpire.TR + bldg.Faction mustEqual PlanetSideEmpire.TR + } + + "keep track of amenities" in { + val bldg = Building(10, Zone.Nowhere) + val door1 = Door(GlobalDefinitions.door) + val door2 = Door(GlobalDefinitions.door) + + bldg.Amenities mustEqual Nil + bldg.Amenities = door2 + bldg.Amenities mustEqual List(door2) + bldg.Amenities = door1 + bldg.Amenities mustEqual List(door2, door1) + door1.Owner mustEqual bldg + door2.Owner mustEqual bldg + } + } +} + +class BuildingControl1Test extends ActorTest { + "Building Control" should { + "construct" in { + val bldg = Building(10, Zone.Nowhere) + bldg.Actor = system.actorOf(Props(classOf[BuildingControl], bldg), "test") + assert(bldg.Actor != ActorRef.noSender) + } + } +} + +class BuildingControl2Test extends ActorTest { + "Building Control" should { + "convert and assert faction affinity on convert request" in { + val bldg = Building(10, Zone.Nowhere) + bldg.Faction = PlanetSideEmpire.TR + bldg.Actor = system.actorOf(Props(classOf[BuildingControl], bldg), "test") + assert(bldg.Faction == PlanetSideEmpire.TR) + + bldg.Actor ! FactionAffinity.ConvertFactionAffinity(PlanetSideEmpire.VS) + val reply = receiveOne(Duration.create(100, "ms")) + assert(reply.isInstanceOf[FactionAffinity.AssertFactionAffinity]) + assert(reply.asInstanceOf[FactionAffinity.AssertFactionAffinity].obj == bldg) + assert(reply.asInstanceOf[FactionAffinity.AssertFactionAffinity].faction == PlanetSideEmpire.VS) + assert(bldg.Faction == PlanetSideEmpire.VS) + } + } +} + +class BuildingControl3Test extends ActorTest { + "Building Control" should { + "convert and assert faction affinity on convert request, and for each of its amenities" in { + val bldg = Building(10, Zone.Nowhere) + bldg.Faction = PlanetSideEmpire.TR + bldg.Actor = system.actorOf(Props(classOf[BuildingControl], bldg), "building-test") + val door1 = Door(GlobalDefinitions.door) + door1.GUID = PlanetSideGUID(1) + door1.Actor = system.actorOf(Props(classOf[DoorControl], door1), "door1-test") + val door2 = Door(GlobalDefinitions.door) + door2.GUID = PlanetSideGUID(2) + door2.Actor = system.actorOf(Props(classOf[DoorControl], door2), "door2-test") + bldg.Amenities = door2 + bldg.Amenities = door1 + assert(bldg.Faction == PlanetSideEmpire.TR) + assert(bldg.Amenities.length == 2) + assert(bldg.Amenities.head == door2) + assert(bldg.Amenities(1) == door1) + + bldg.Actor ! FactionAffinity.ConvertFactionAffinity(PlanetSideEmpire.VS) + val reply = receiveN(3, Duration.create(500, "ms")) + assert(reply.length == 3) + var building_count = 0 + var door_count = 0 + reply.foreach(item => { + assert(item.isInstanceOf[FactionAffinity.AssertFactionAffinity]) + val item2 = item.asInstanceOf[FactionAffinity.AssertFactionAffinity] + item2.obj match { + case _ : Building => + building_count += 1 + case _ : Door => + door_count += 1 + case _ => + assert(false) + } + assert(item2.faction == PlanetSideEmpire.VS) + }) + assert(building_count == 1 && door_count == 2) + } + } +} diff --git a/common/src/test/scala/objects/DoorTest.scala b/common/src/test/scala/objects/DoorTest.scala index d95b77cad..228d44ab5 100644 --- a/common/src/test/scala/objects/DoorTest.scala +++ b/common/src/test/scala/objects/DoorTest.scala @@ -1,9 +1,11 @@ // Copyright (c) 2017 PSForever package objects -import akka.actor.{ActorRef, Props} +import akka.actor.{ActorRef, ActorSystem, Props} import net.psforever.objects.{GlobalDefinitions, Player} import net.psforever.objects.serverobject.doors.{Door, DoorControl} +import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.zones.Zone import net.psforever.packet.game.{PlanetSideGUID, UseItemMessage} import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3} import org.specs2.mutable.Specification @@ -11,6 +13,8 @@ import org.specs2.mutable.Specification import scala.concurrent.duration.Duration class DoorTest extends Specification { + val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + "Door" should { "construct" in { Door(GlobalDefinitions.door) @@ -24,7 +28,6 @@ class DoorTest extends Specification { } "be opened and closed (1; manual)" in { - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) val door = Door(GlobalDefinitions.door) door.isOpen mustEqual false door.Open mustEqual None @@ -39,7 +42,6 @@ class DoorTest extends Specification { } "be opened and closed (2; toggle)" in { - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) 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 door = Door(GlobalDefinitions.door) door.Open mustEqual None @@ -85,9 +87,7 @@ class DoorControl1Test extends ActorTest() { class DoorControl2Test extends ActorTest() { "DoorControl" should { "open on use" in { - val door = Door(GlobalDefinitions.door) - door.Actor = system.actorOf(Props(classOf[DoorControl], door), "door") - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + 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 assert(door.Open.isEmpty) @@ -106,8 +106,7 @@ class DoorControl2Test extends ActorTest() { class DoorControl3Test extends ActorTest() { "DoorControl" should { "do nothing if given garbage" in { - val door = Door(GlobalDefinitions.door) - door.Actor = system.actorOf(Props(classOf[DoorControl], door), "door") + val (_, door) = DoorControlTest.SetUpAgents(PlanetSideEmpire.TR) assert(door.Open.isEmpty) door.Actor ! "trash" @@ -117,3 +116,13 @@ class DoorControl3Test extends ActorTest() { } } } + +object DoorControlTest { + def SetUpAgents(faction : PlanetSideEmpire.Value)(implicit system : ActorSystem) : (Player, Door) = { + val door = Door(GlobalDefinitions.door) + door.Actor = system.actorOf(Props(classOf[DoorControl], door), "door") + door.Owner = new Building(0, Zone.Nowhere) + door.Owner.Faction = faction + (Player("test", faction, CharacterGender.Male, 0, 0), door) + } +} diff --git a/common/src/test/scala/objects/FactionAffinityTest.scala b/common/src/test/scala/objects/FactionAffinityTest.scala new file mode 100644 index 000000000..c4598896a --- /dev/null +++ b/common/src/test/scala/objects/FactionAffinityTest.scala @@ -0,0 +1,131 @@ +// Copyright (c) 2017 PSForever +package objects + +import akka.actor.{Actor, ActorSystem, Props} +import net.psforever.objects.{GlobalDefinitions, Vehicle} +import net.psforever.objects.serverobject.affinity.FactionAffinity +import net.psforever.objects.serverobject.doors.Door +import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.zones.Zone +import net.psforever.types.PlanetSideEmpire +import org.specs2.mutable.Specification + +import scala.concurrent.duration.Duration + +class FactionAffinityTest extends Specification { + "FactionAffinity" should { + "construct (basic)" in { + val obj = new FactionAffinity { def Faction = PlanetSideEmpire.TR } + obj.Faction mustEqual PlanetSideEmpire.TR + } + + "construct (part of)" in { + val obj = new Door(GlobalDefinitions.door) + obj.Faction mustEqual PlanetSideEmpire.NEUTRAL + } + + "can not change affinity directly (basic)" in { + val obj = new FactionAffinity { def Faction = PlanetSideEmpire.TR } + (obj.Faction = PlanetSideEmpire.NC) mustEqual PlanetSideEmpire.TR + } + + "can not change affinity directly (part of)" in { + val obj = new Door(GlobalDefinitions.door) + (obj.Faction = PlanetSideEmpire.TR) mustEqual PlanetSideEmpire.NEUTRAL + } + + "inherits affinity from owner 1" in { + val obj = new Door(GlobalDefinitions.door) + obj.Owner.Faction mustEqual PlanetSideEmpire.NEUTRAL + (obj.Faction = PlanetSideEmpire.TR) mustEqual PlanetSideEmpire.NEUTRAL + } + + "inherits affinity from owner 2" in { + val obj = new Door(GlobalDefinitions.door) + val bldg = new Building(1, Zone.Nowhere) + obj.Owner = bldg + obj.Faction mustEqual PlanetSideEmpire.NEUTRAL + + bldg.Faction = PlanetSideEmpire.TR + obj.Faction mustEqual PlanetSideEmpire.TR + + bldg.Faction = PlanetSideEmpire.NC + obj.Faction mustEqual PlanetSideEmpire.NC + } + } +} + +class FactionAffinity1Test extends ActorTest() { + "FactionAffinity" should { + "assert affinity on confirm request" in { + val obj = FactionAffinityTest.SetUpAgent + obj.Faction = PlanetSideEmpire.VS //object is a type that can be changed directly + assert(obj.Faction == PlanetSideEmpire.VS) + + obj.Actor ! FactionAffinity.ConfirmFactionAffinity() + val reply = receiveOne(Duration.create(100, "ms")) + assert(reply.isInstanceOf[FactionAffinity.AssertFactionAffinity]) + assert(reply.asInstanceOf[FactionAffinity.AssertFactionAffinity].obj == obj) + assert(reply.asInstanceOf[FactionAffinity.AssertFactionAffinity].faction == PlanetSideEmpire.VS) + } + } +} + +class FactionAffinity2Test extends ActorTest() { + "FactionAffinity" should { + "assert affinity on assert request" in { + val obj = FactionAffinityTest.SetUpAgent + obj.Faction = PlanetSideEmpire.VS //object is a type that can be changed directly + assert(obj.Faction == PlanetSideEmpire.VS) + + obj.Actor ! FactionAffinity.AssertFactionAffinity(obj, PlanetSideEmpire.NEUTRAL) + val reply = receiveOne(Duration.create(100, "ms")) + assert(reply.isInstanceOf[FactionAffinity.AssertFactionAffinity]) + assert(reply.asInstanceOf[FactionAffinity.AssertFactionAffinity].obj == obj) + assert(reply.asInstanceOf[FactionAffinity.AssertFactionAffinity].faction == PlanetSideEmpire.VS) + } + } +} + +class FactionAffinity3Test extends ActorTest() { + "FactionAffinity" should { + "convert and assert affinity on convert request" in { + val obj = FactionAffinityTest.SetUpAgent + obj.Faction = PlanetSideEmpire.VS //object is a type that can be changed directly + assert(obj.Faction == PlanetSideEmpire.VS) + + obj.Actor ! FactionAffinity.ConvertFactionAffinity(PlanetSideEmpire.TR) + val reply = receiveOne(Duration.create(100, "ms")) + assert(reply.isInstanceOf[FactionAffinity.AssertFactionAffinity]) + assert(reply.asInstanceOf[FactionAffinity.AssertFactionAffinity].obj == obj) + assert(reply.asInstanceOf[FactionAffinity.AssertFactionAffinity].faction == PlanetSideEmpire.TR) + assert(obj.Faction == PlanetSideEmpire.TR) + } + } +} + +object FactionAffinityTest { + import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior + + private class AffinityControl(obj : FactionAffinity) extends Actor + with FactionAffinityBehavior.Check + with FactionAffinityBehavior.Convert { + override def FactionObject = obj + def receive = checkBehavior.orElse(convertBehavior).orElse { case _ => } + } + + def SetUpAgent(implicit system : ActorSystem) = { + val obj = new Vehicle(GlobalDefinitions.quadstealth) + obj.Actor = system.actorOf(Props(classOf[FactionAffinityTest.AffinityControl], obj), "test") + obj + } + + def FreeFactionObject : FactionAffinity = new FactionAffinity() { + private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL + def Faction : PlanetSideEmpire.Value = faction + override def Faction_=(fac : PlanetSideEmpire.Value) : PlanetSideEmpire.Value = { + faction = fac + faction + } + } +} \ No newline at end of file diff --git a/common/src/test/scala/objects/IFFLockTest.scala b/common/src/test/scala/objects/IFFLockTest.scala index 54be04d16..05eb1e2fb 100644 --- a/common/src/test/scala/objects/IFFLockTest.scala +++ b/common/src/test/scala/objects/IFFLockTest.scala @@ -1,10 +1,13 @@ // Copyright (c) 2017 PSForever package objects -import akka.actor.{ActorRef, Props} +import akka.actor.{ActorRef, ActorSystem, Props} import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.{GlobalDefinitions, Player} import net.psforever.objects.serverobject.locks.{IFFLock, IFFLockControl} +import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.terminals.{Terminal, TerminalControl, TerminalDefinition} +import net.psforever.objects.zones.Zone import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.{CharacterGender, PlanetSideEmpire} import org.specs2.mutable.Specification @@ -33,9 +36,7 @@ class IFFLockControl1Test extends ActorTest() { class IFFLockControl2Test extends ActorTest() { "IFFLockControl" should { "can hack" in { - val lock = IFFLock(GlobalDefinitions.lock_external) - lock.Actor = system.actorOf(Props(classOf[IFFLockControl], lock), "lock-control") - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val (player, lock) = IFFLockControlTest.SetUpAgents(PlanetSideEmpire.TR) player.GUID = PlanetSideGUID(1) assert(lock.HackedBy.isEmpty) @@ -49,9 +50,7 @@ class IFFLockControl2Test extends ActorTest() { class IFFLockControl3Test extends ActorTest() { "IFFLockControl" should { "can hack" in { - val lock = IFFLock(GlobalDefinitions.lock_external) - lock.Actor = system.actorOf(Props(classOf[IFFLockControl], lock), "lock-control") - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val (player, lock) = IFFLockControlTest.SetUpAgents(PlanetSideEmpire.TR) player.GUID = PlanetSideGUID(1) assert(lock.HackedBy.isEmpty) @@ -64,3 +63,13 @@ class IFFLockControl3Test extends ActorTest() { } } } + +object IFFLockControlTest { + def SetUpAgents(faction : PlanetSideEmpire.Value)(implicit system : ActorSystem) : (Player, IFFLock) = { + val lock = IFFLock(GlobalDefinitions.lock_external) + lock.Actor = system.actorOf(Props(classOf[IFFLockControl], lock), "lock-control") + lock.Owner = new Building(0, Zone.Nowhere) + lock.Owner.Faction = faction + (Player("test", faction, CharacterGender.Male, 0, 0), lock) + } +} diff --git a/common/src/test/scala/objects/LockerTest.scala b/common/src/test/scala/objects/LockerTest.scala new file mode 100644 index 000000000..ae5ffb3a1 --- /dev/null +++ b/common/src/test/scala/objects/LockerTest.scala @@ -0,0 +1,36 @@ +// Copyright (c) 2017 PSForever +package objects + +import akka.actor.{ActorRef, Props} +import net.psforever.objects.GlobalDefinitions +import net.psforever.objects.serverobject.affinity.FactionAffinity +import net.psforever.objects.serverobject.mblocker.{Locker, LockerControl} +import net.psforever.types.PlanetSideEmpire +import org.specs2.mutable._ + +class LockerTest extends Specification { + "LockerDefinition" should { + "define" in { + GlobalDefinitions.mb_locker.ObjectId mustEqual 524 + GlobalDefinitions.mb_locker.Name mustEqual "mb_locker" + } + } + + "Locker" should { + "construct" in { + val locker = new Locker() + locker.Actor mustEqual ActorRef.noSender + } + } +} + +class LockerControlTest extends ActorTest { + "LockerControl" should { + "construct" in { + val locker = new Locker() + locker.Actor = system.actorOf(Props(classOf[LockerControl], locker), "test") + locker.Actor ! FactionAffinity.ConfirmFactionAffinity() + expectMsg(FactionAffinity.AssertFactionAffinity(locker, PlanetSideEmpire.NEUTRAL)) + } + } +} diff --git a/common/src/test/scala/objects/MountableTest.scala b/common/src/test/scala/objects/MountableTest.scala index 3b33ecd3b..83ba5babd 100644 --- a/common/src/test/scala/objects/MountableTest.scala +++ b/common/src/test/scala/objects/MountableTest.scala @@ -4,9 +4,10 @@ package objects import akka.actor.{Actor, ActorRef, Props} import net.psforever.objects.Player import net.psforever.objects.definition.{ObjectDefinition, SeatDefinition} -import net.psforever.objects.mount.{Mountable, MountableBehavior} +import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.vehicles.Seat +import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.{CharacterGender, PlanetSideEmpire} import scala.concurrent.duration.Duration @@ -80,12 +81,15 @@ object MountableTest { None } } - def Definition : ObjectDefinition = null //eh whatever + GUID = PlanetSideGUID(1) + //eh whatever + def Faction = PlanetSideEmpire.TR + def Definition : ObjectDefinition = null } - class MountableTestControl(obj : Mountable) extends Actor with MountableBehavior { + class MountableTestControl(obj : PlanetSideServerObject with Mountable) extends Actor with MountableBehavior.Mount with MountableBehavior.Dismount { override def MountableObject = obj - def receive : Receive = mountableBehavior + def receive : Receive = mountBehavior.orElse(dismountBehavior) } } diff --git a/common/src/test/scala/objects/ServerObjectBuilderTest.scala b/common/src/test/scala/objects/ServerObjectBuilderTest.scala index 863278f45..73f802031 100644 --- a/common/src/test/scala/objects/ServerObjectBuilderTest.scala +++ b/common/src/test/scala/objects/ServerObjectBuilderTest.scala @@ -5,10 +5,26 @@ import akka.actor.{Actor, Props} import net.psforever.objects.guid.NumberPoolHub import net.psforever.packet.game.PlanetSideGUID import net.psforever.objects.serverobject.ServerObjectBuilder +import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder} +import net.psforever.objects.zones.Zone import net.psforever.types.Vector3 import scala.concurrent.duration.Duration +class BuildingBuilderTest extends ActorTest { + "Building object" should { + "build" in { + val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuildingTestActor], 10, Zone.Nowhere), "building") + actor ! "!" + + val reply = receiveOne(Duration.create(1000, "ms")) + assert(reply.isInstanceOf[Building]) + assert(reply.asInstanceOf[Building].Id == 10) + assert(reply.asInstanceOf[Building].Zone == Zone.Nowhere) + } + } +} + class DoorObjectBuilderTest1 extends ActorTest { import net.psforever.objects.serverobject.doors.Door "Door object" should { @@ -150,4 +166,11 @@ object ServerObjectBuilderTest { sender ! builder.Build(context, hub) } } + + class BuildingTestActor(building_id : Int, zone : Zone) extends Actor { + def receive : Receive = { + case _ => + sender ! FoundationBuilder(Building.Structure).Build(building_id, zone)(context) + } + } } diff --git a/common/src/test/scala/objects/VehicleSpawnPadTest.scala b/common/src/test/scala/objects/VehicleSpawnPadTest.scala index 61f1deeb9..f595da65f 100644 --- a/common/src/test/scala/objects/VehicleSpawnPadTest.scala +++ b/common/src/test/scala/objects/VehicleSpawnPadTest.scala @@ -1,9 +1,11 @@ // Copyright (c) 2017 PSForever package objects -import akka.actor.{ActorRef, Props} +import akka.actor.{ActorRef, ActorSystem, Props} import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} +import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.vehicles.VehicleControl +import net.psforever.objects.zones.Zone import net.psforever.objects.{GlobalDefinitions, Player, Vehicle} import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3} @@ -40,22 +42,20 @@ class VehicleSpawnControl1Test extends ActorTest() { class VehicleSpawnControl2Test extends ActorTest() { "VehicleSpawnControl" should { "spawn a vehicle" in { - val obj = VehicleSpawnPad(GlobalDefinitions.spawn_pad) - obj.Actor = system.actorOf(Props(classOf[VehicleSpawnControl], obj), "door") - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val (player, pad) = VehicleSpawnPadControl.SetUpAgents(PlanetSideEmpire.TR) player.Spawn val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) vehicle.GUID = PlanetSideGUID(1) vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle") - obj.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle) + pad.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle) val reply = receiveOne(Duration.create(10000, "ms")) assert(reply == VehicleSpawnPad.ConcealPlayer) //explicit: isInstanceOf does not work val reply2 = receiveOne(Duration.create(10000, "ms")) assert(reply2.isInstanceOf[VehicleSpawnPad.LoadVehicle]) assert(reply2.asInstanceOf[VehicleSpawnPad.LoadVehicle].vehicle == vehicle) - assert(reply2.asInstanceOf[VehicleSpawnPad.LoadVehicle].pad == obj) + assert(reply2.asInstanceOf[VehicleSpawnPad.LoadVehicle].pad == pad) player.VehicleOwned = Some(vehicle.GUID) val reply3 = receiveOne(Duration.create(10000, "ms")) @@ -78,14 +78,12 @@ class VehicleSpawnControl2Test extends ActorTest() { class VehicleSpawnControl3Test extends ActorTest() { "VehicleSpawnControl" should { "not spawn a vehicle if player is dead" in { - val obj = VehicleSpawnPad(GlobalDefinitions.spawn_pad) - obj.Actor = system.actorOf(Props(classOf[VehicleSpawnControl], obj), "door") - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val (player, pad) = VehicleSpawnPadControl.SetUpAgents(PlanetSideEmpire.TR) val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) vehicle.GUID = PlanetSideGUID(1) vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle") - obj.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle) + pad.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle) val reply = receiveOne(Duration.create(5000, "ms")) assert(reply == null) } @@ -95,16 +93,24 @@ class VehicleSpawnControl3Test extends ActorTest() { class VehicleSpawnControl4Test extends ActorTest() { "VehicleSpawnControl" should { "not spawn a vehicle if vehicle Actor is missing" in { - val obj = VehicleSpawnPad(GlobalDefinitions.spawn_pad) - obj.Actor = system.actorOf(Props(classOf[VehicleSpawnControl], obj), "door") - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val (player, pad) = VehicleSpawnPadControl.SetUpAgents(PlanetSideEmpire.TR) player.Spawn val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) vehicle.GUID = PlanetSideGUID(1) - obj.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle) + pad.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle) val reply = receiveOne(Duration.create(5000, "ms")) assert(reply == null) } } } + +object VehicleSpawnPadControl { + def SetUpAgents(faction : PlanetSideEmpire.Value)(implicit system : ActorSystem) : (Player, VehicleSpawnPad) = { + val pad = VehicleSpawnPad(GlobalDefinitions.spawn_pad) + pad.Actor = system.actorOf(Props(classOf[VehicleSpawnControl], pad), "test-pad") + pad.Owner = new Building(0, Zone.Nowhere) + pad.Owner.Faction = faction + (Player("test", faction, CharacterGender.Male, 0, 0), pad) + } +} diff --git a/common/src/test/scala/objects/VehicleTest.scala b/common/src/test/scala/objects/VehicleTest.scala index eba1463a3..1bd7abfaf 100644 --- a/common/src/test/scala/objects/VehicleTest.scala +++ b/common/src/test/scala/objects/VehicleTest.scala @@ -1,13 +1,17 @@ // Copyright (c) 2017 PSForever package objects +import akka.actor.Props import net.psforever.objects.{GlobalDefinitions, Player, Vehicle} import net.psforever.objects.definition.SeatDefinition -import net.psforever.objects.vehicles.{AccessPermissionGroup, Seat, SeatArmorRestriction, VehicleLockState} +import net.psforever.objects.serverobject.mount.Mountable +import net.psforever.objects.vehicles._ import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.{CharacterGender, ExoSuitType, PlanetSideEmpire} import org.specs2.mutable._ +import scala.concurrent.duration.Duration + class VehicleTest extends Specification { "SeatDefinition" should { @@ -254,3 +258,49 @@ class VehicleTest extends Specification { } } } + +class VehicleControl1Test extends ActorTest { + "Vehicle Control" should { + "deactivate and stop handling mount messages" in { + val player1 = Player("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + player1.GUID = PlanetSideGUID(1) + val player2 = Player("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) + vehicle.GUID = PlanetSideGUID(3) + vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test") + + vehicle.Actor ! Mountable.TryMount(player1, 0) + val reply = receiveOne(Duration.create(100, "ms")) + assert(reply.isInstanceOf[Mountable.MountMessages]) + + vehicle.Actor ! Vehicle.PrepareForDeletion + vehicle.Actor ! Mountable.TryMount(player2, 1) + expectNoMsg(Duration.create(200, "ms")) + } + } +} + +class VehicleControl2Test extends ActorTest { + "Vehicle Control" should { + "reactivate and resume handling mount messages" in { + val player1 = Player("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + player1.GUID = PlanetSideGUID(1) + val player2 = Player("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + player2.GUID = PlanetSideGUID(2) + val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) + vehicle.GUID = PlanetSideGUID(3) + vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test") + + vehicle.Actor ! Mountable.TryMount(player1, 0) + receiveOne(Duration.create(100, "ms")) //discard + vehicle.Actor ! Vehicle.PrepareForDeletion + vehicle.Actor ! Mountable.TryMount(player2, 1) + expectNoMsg(Duration.create(200, "ms")) + + vehicle.Actor ! Vehicle.Reactivate + vehicle.Actor ! Mountable.TryMount(player2, 1) + val reply = receiveOne(Duration.create(100, "ms")) + assert(reply.isInstanceOf[Mountable.MountMessages]) + } + } +} diff --git a/common/src/test/scala/objects/ZoneTest.scala b/common/src/test/scala/objects/ZoneTest.scala index a16eeb109..024c8aef6 100644 --- a/common/src/test/scala/objects/ZoneTest.scala +++ b/common/src/test/scala/objects/ZoneTest.scala @@ -1,39 +1,44 @@ // Copyright (c) 2017 PSForever package objects -import akka.actor.ActorRef +import akka.actor.{ActorContext, ActorRef} import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.equipment.Equipment import net.psforever.objects.guid.NumberPoolHub import net.psforever.objects.guid.source.LimitedNumberSource +import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder} import net.psforever.objects.zones.{Zone, ZoneMap} import net.psforever.objects.{GlobalDefinitions, Vehicle} import org.specs2.mutable.Specification class ZoneTest extends Specification { + def test(a: Int, b : Zone, c : ActorContext) : Building = { Building.NoBuilding } + "ZoneMap" should { - //TODO these are temporary tests as the current ZoneMap is a kludge "construct" in { new ZoneMap("map13") ok } "references bases by a positive building id (defaults to 0)" in { + def test(a: Int, b : Zone, c : ActorContext) : Building = { Building.NoBuilding } + val map = new ZoneMap("map13") - map.LocalBases mustEqual 0 - map.LocalBases = 10 - map.LocalBases mustEqual 10 - map.LocalBases = -1 - map.LocalBases mustEqual 10 + map.LocalBuildings mustEqual Map.empty + map.LocalBuilding(10, FoundationBuilder(test)) + map.LocalBuildings.keySet.contains(10) mustEqual true + map.LocalBuilding(-1, FoundationBuilder(test)) + map.LocalBuildings.keySet.contains(10) mustEqual true + map.LocalBuildings.keySet.contains(-1) mustEqual false } "associates objects to bases (doesn't check numbers)" in { val map = new ZoneMap("map13") - map.ObjectToBase mustEqual Nil - map.ObjectToBase(1, 2) - map.ObjectToBase mustEqual List((1, 2)) - map.ObjectToBase(3, 4) - map.ObjectToBase mustEqual List((1, 2), (3, 4)) + map.ObjectToBuilding mustEqual Nil + map.ObjectToBuilding(1, 2) + map.ObjectToBuilding mustEqual Map(1 -> 2) + map.ObjectToBuilding(3, 4) + map.ObjectToBuilding mustEqual Map(1 -> 2, 3 -> 4) } "associates doors to door locks (doesn't check numbers)" in { @@ -65,11 +70,10 @@ class ZoneTest extends Specification { } val map13 = new ZoneMap("map13") - map13.LocalBases = 10 + map13.LocalBuilding(10, FoundationBuilder(test)) class TestObject extends IdentifiableEntity "Zone" should { - //TODO these are temporary tests as the current Zone is a kludge "construct" in { val zone = new Zone("home3", map13, 13) zone.GUID mustEqual ActorRef.noSender diff --git a/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala b/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala index ffab2595f..65436e495 100644 --- a/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala @@ -2,8 +2,10 @@ package objects.terminal import akka.actor.ActorRef +import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.{GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals.Terminal +import net.psforever.objects.zones.Zone import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType} import org.specs2.mutable.Specification @@ -11,6 +13,9 @@ import org.specs2.mutable.Specification class AirVehicleTerminalTest extends Specification { "Air_Vehicle_Terminal" should { val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val terminal = Terminal(GlobalDefinitions.air_vehicle_terminal) + terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { val terminal = Terminal(GlobalDefinitions.air_vehicle_terminal) @@ -18,8 +23,8 @@ class AirVehicleTerminalTest extends Specification { } "player can buy a reaver ('lightgunship')" in { - val terminal = Terminal(GlobalDefinitions.air_vehicle_terminal) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "lightgunship", 0, PlanetSideGUID(0)) + val reply = terminal.Request(player, msg) reply.isInstanceOf[Terminal.BuyVehicle] mustEqual true val reply2 = reply.asInstanceOf[Terminal.BuyVehicle] @@ -35,7 +40,6 @@ class AirVehicleTerminalTest extends Specification { } "player can not buy a fake vehicle ('reaver')" in { - val terminal = Terminal(GlobalDefinitions.ground_vehicle_terminal) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "reaver", 0, PlanetSideGUID(0)) terminal.Request(player, msg) mustEqual Terminal.NoDeal() diff --git a/common/src/test/scala/objects/terminal/CertTerminalTest.scala b/common/src/test/scala/objects/terminal/CertTerminalTest.scala index 96b836682..c3d000d0e 100644 --- a/common/src/test/scala/objects/terminal/CertTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/CertTerminalTest.scala @@ -2,7 +2,9 @@ package objects.terminal import akka.actor.ActorRef +import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.serverobject.terminals.Terminal +import net.psforever.objects.zones.Zone import net.psforever.objects.{GlobalDefinitions, Player} import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} import net.psforever.types._ @@ -11,6 +13,9 @@ import org.specs2.mutable.Specification class CertTerminalTest extends Specification { "Cert_Terminal" should { val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val terminal = Terminal(GlobalDefinitions.cert_terminal) + terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { val terminal = Terminal(GlobalDefinitions.cert_terminal) @@ -18,27 +23,23 @@ class CertTerminalTest extends Specification { } "player can learn a certification ('medium_assault')" in { - val terminal = Terminal(GlobalDefinitions.cert_terminal) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Learn, 0, "medium_assault", 0, PlanetSideGUID(0)) terminal.Request(player, msg) mustEqual Terminal.LearnCertification(CertificationType.MediumAssault, 2) } "player can not learn a fake certification ('juggling')" in { - val terminal = Terminal(GlobalDefinitions.cert_terminal) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Learn, 0, "juggling", 0, PlanetSideGUID(0)) terminal.Request(player, msg) mustEqual Terminal.NoDeal() } "player can forget a certification ('medium_assault')" in { - val terminal = Terminal(GlobalDefinitions.cert_terminal) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Sell, 0, "medium_assault", 0, PlanetSideGUID(0)) terminal.Request(player, msg) mustEqual Terminal.SellCertification(CertificationType.MediumAssault, 2) } "player can not forget a fake certification ('juggling')" in { - val terminal = Terminal(GlobalDefinitions.cert_terminal) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Sell, 0, "juggling", 0, PlanetSideGUID(0)) terminal.Request(player, msg) mustEqual Terminal.NoDeal() diff --git a/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala b/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala index d7850c4e2..d498e3e0b 100644 --- a/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala @@ -2,8 +2,10 @@ package objects.terminal import akka.actor.ActorRef +import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.{GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals.Terminal +import net.psforever.objects.zones.Zone import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType} import org.specs2.mutable.Specification @@ -11,6 +13,9 @@ import org.specs2.mutable.Specification class DropshipVehicleTerminalTest extends Specification { "Dropship_Vehicle_Terminal" should { val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val terminal = Terminal(GlobalDefinitions.dropship_vehicle_terminal) + terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { val terminal = Terminal(GlobalDefinitions.dropship_vehicle_terminal) @@ -18,8 +23,8 @@ class DropshipVehicleTerminalTest extends Specification { } "player can buy a galaxy ('dropship')" in { - val terminal = Terminal(GlobalDefinitions.dropship_vehicle_terminal) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "dropship", 0, PlanetSideGUID(0)) + val reply = terminal.Request(player, msg) reply.isInstanceOf[Terminal.BuyVehicle] mustEqual true val reply2 = reply.asInstanceOf[Terminal.BuyVehicle] @@ -41,7 +46,6 @@ class DropshipVehicleTerminalTest extends Specification { } "player can not buy a fake vehicle ('galaxy')" in { - val terminal = Terminal(GlobalDefinitions.dropship_vehicle_terminal) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "galaxy", 0, PlanetSideGUID(0)) terminal.Request(player, msg) mustEqual Terminal.NoDeal() diff --git a/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala b/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala index c2207562f..831bb0b17 100644 --- a/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala @@ -2,8 +2,10 @@ package objects.terminal import akka.actor.ActorRef +import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.{GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals.Terminal +import net.psforever.objects.zones.Zone import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType} import org.specs2.mutable.Specification @@ -11,6 +13,9 @@ import org.specs2.mutable.Specification class GroundVehicleTerminalTest extends Specification { "Ground_Vehicle_Terminal" should { val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val terminal = Terminal(GlobalDefinitions.ground_vehicle_terminal) + terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { val terminal = Terminal(GlobalDefinitions.ground_vehicle_terminal) @@ -18,8 +23,8 @@ class GroundVehicleTerminalTest extends Specification { } "player can buy a harasser ('two_man_assault_buggy')" in { - val terminal = Terminal(GlobalDefinitions.ground_vehicle_terminal) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "two_man_assault_buggy", 0, PlanetSideGUID(0)) + val reply = terminal.Request(player, msg) reply.isInstanceOf[Terminal.BuyVehicle] mustEqual true val reply2 = reply.asInstanceOf[Terminal.BuyVehicle] @@ -35,7 +40,6 @@ class GroundVehicleTerminalTest extends Specification { } "player can not buy a fake vehicle ('harasser')" in { - val terminal = Terminal(GlobalDefinitions.ground_vehicle_terminal) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "harasser", 0, PlanetSideGUID(0)) terminal.Request(player, msg) mustEqual Terminal.NoDeal() diff --git a/common/src/test/scala/objects/terminal/ImplantTerminalInterfaceTest.scala b/common/src/test/scala/objects/terminal/ImplantTerminalInterfaceTest.scala index c5893ea04..d846ab944 100644 --- a/common/src/test/scala/objects/terminal/ImplantTerminalInterfaceTest.scala +++ b/common/src/test/scala/objects/terminal/ImplantTerminalInterfaceTest.scala @@ -2,8 +2,10 @@ package objects.terminal import akka.actor.ActorRef +import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.{GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals.Terminal +import net.psforever.objects.zones.Zone import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType} import org.specs2.mutable.Specification @@ -11,6 +13,9 @@ import org.specs2.mutable.Specification class ImplantTerminalInterfaceTest extends Specification { "Implant_Terminal_Interface" should { val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val terminal = Terminal(GlobalDefinitions.implant_terminal_interface) + terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { val terminal = Terminal(GlobalDefinitions.implant_terminal_interface) @@ -18,8 +23,8 @@ class ImplantTerminalInterfaceTest extends Specification { } "player can learn an implant ('darklight_vision')" in { - val terminal = Terminal(GlobalDefinitions.implant_terminal_interface) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "darklight_vision", 0, PlanetSideGUID(0)) + val reply = terminal.Request(player, msg) reply.isInstanceOf[Terminal.LearnImplant] mustEqual true val reply2 = reply.asInstanceOf[Terminal.LearnImplant] @@ -27,15 +32,14 @@ class ImplantTerminalInterfaceTest extends Specification { } "player can not learn a fake implant ('aimbot')" in { - val terminal = Terminal(GlobalDefinitions.implant_terminal_interface) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "aimbot", 0, PlanetSideGUID(0)) terminal.Request(player, msg) mustEqual Terminal.NoDeal() } "player can surrender an implant ('darklight_vision')" in { - val terminal = Terminal(GlobalDefinitions.implant_terminal_interface) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Sell, 0, "darklight_vision", 0, PlanetSideGUID(0)) + val reply = terminal.Request(player, msg) reply.isInstanceOf[Terminal.SellImplant] mustEqual true val reply2 = reply.asInstanceOf[Terminal.SellImplant] diff --git a/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala b/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala index 9677a09a3..a58f0cf0c 100644 --- a/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala +++ b/common/src/test/scala/objects/terminal/ImplantTerminalMechTest.scala @@ -1,13 +1,13 @@ // Copyright (c) 2017 PSForever package objects.terminal -import akka.actor.{ActorRef, Props} +import akka.actor.{ActorRef, ActorSystem, Props} import net.psforever.objects.definition.SeatDefinition -import net.psforever.objects.mount.Mountable +import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.implantmech.{ImplantTerminalMech, ImplantTerminalMechControl} import net.psforever.objects.vehicles.Seat import net.psforever.objects.{GlobalDefinitions, Player} -import net.psforever.types.{CharacterGender, PlanetSideEmpire} +import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3} import objects.ActorTest import org.specs2.mutable.Specification @@ -68,43 +68,101 @@ class ImplantTerminalMechControl1Test extends ActorTest() { class ImplantTerminalMechControl2Test extends ActorTest() { "ImplantTerminalMechControl" should { "let a player mount" in { - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) - val obj = ImplantTerminalMech(GlobalDefinitions.implant_terminal_mech) - obj.Actor = system.actorOf(Props(classOf[ImplantTerminalMechControl], obj), "mech") + val (player, mech) = ImplantTerminalMechTest.SetUpAgents(PlanetSideEmpire.TR) val msg = Mountable.TryMount(player, 0) - obj.Actor ! msg - val reply = receiveOne(Duration.create(100, "ms")) + mech.Actor ! msg + val reply = receiveOne(Duration.create(200, "ms")) assert(reply.isInstanceOf[Mountable.MountMessages]) val reply2 = reply.asInstanceOf[Mountable.MountMessages] assert(reply2.player == player) assert(reply2.response.isInstanceOf[Mountable.CanMount]) val reply3 = reply2.response.asInstanceOf[Mountable.CanMount] - assert(reply3.obj == obj) + assert(reply3.obj == mech) assert(reply3.seat_num == 0) } } } class ImplantTerminalMechControl3Test extends ActorTest() { + import net.psforever.types.CharacterGender "ImplantTerminalMechControl" should { "block a player from mounting" in { - val player1 = Player("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val (player1, mech) = ImplantTerminalMechTest.SetUpAgents(PlanetSideEmpire.TR) val player2 = Player("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) - val obj = ImplantTerminalMech(GlobalDefinitions.implant_terminal_mech) - obj.Actor = system.actorOf(Props(classOf[ImplantTerminalMechControl], obj), "mech") - obj.Actor ! Mountable.TryMount(player1, 0) + + mech.Actor ! Mountable.TryMount(player1, 0) receiveOne(Duration.create(100, "ms")) //consume reply - obj.Actor ! Mountable.TryMount(player2, 0) + mech.Actor ! Mountable.TryMount(player2, 0) val reply = receiveOne(Duration.create(100, "ms")) assert(reply.isInstanceOf[Mountable.MountMessages]) val reply2 = reply.asInstanceOf[Mountable.MountMessages] assert(reply2.player == player2) assert(reply2.response.isInstanceOf[Mountable.CanNotMount]) val reply3 = reply2.response.asInstanceOf[Mountable.CanNotMount] - assert(reply3.obj == obj) + assert(reply3.obj == mech) assert(reply3.seat_num == 0) } } } + +class ImplantTerminalMechControl4Test extends ActorTest() { + "ImplantTerminalMechControl" should { + "dismount player after mounting" in { + val (player, mech) = ImplantTerminalMechTest.SetUpAgents(PlanetSideEmpire.TR) + mech.Actor ! Mountable.TryMount(player, 0) + receiveOne(Duration.create(100, "ms")) //consume reply + assert(mech.Seat(0).get.isOccupied) + + mech.Actor ! Mountable.TryDismount(player, 0) + val reply = receiveOne(Duration.create(100, "ms")) + assert(reply.isInstanceOf[Mountable.MountMessages]) + val reply2 = reply.asInstanceOf[Mountable.MountMessages] + assert(reply2.player == player) + assert(reply2.response.isInstanceOf[Mountable.CanDismount]) + val reply3 = reply2.response.asInstanceOf[Mountable.CanDismount] + assert(reply3.obj == mech) + assert(reply3.seat_num == 0) + assert(!mech.Seat(0).get.isOccupied) + } + } +} + +class ImplantTerminalMechControl5Test extends ActorTest() { + "ImplantTerminalMechControl" should { + "block a player from dismounting" in { + val (player, mech) = ImplantTerminalMechTest.SetUpAgents(PlanetSideEmpire.TR) + mech.Actor ! Mountable.TryMount(player, 0) + receiveOne(Duration.create(100, "ms")) //consume reply + assert(mech.Seat(0).get.isOccupied) + + mech.Velocity = Vector3(1,0,0) //makes no sense, but it works as the "seat" is not bailable + mech.Actor ! Mountable.TryDismount(player, 0) + val reply = receiveOne(Duration.create(100, "ms")) + assert(reply.isInstanceOf[Mountable.MountMessages]) + val reply2 = reply.asInstanceOf[Mountable.MountMessages] + assert(reply2.player == player) + assert(reply2.response.isInstanceOf[Mountable.CanNotDismount]) + val reply3 = reply2.response.asInstanceOf[Mountable.CanNotDismount] + assert(reply3.obj == mech) + assert(reply3.seat_num == 0) + assert(mech.Seat(0).get.isOccupied) + } + } +} + +object ImplantTerminalMechTest { + def SetUpAgents(faction : PlanetSideEmpire.Value)(implicit system : ActorSystem) : (Player, ImplantTerminalMech) = { + import net.psforever.objects.serverobject.structures.Building + import net.psforever.objects.zones.Zone + import net.psforever.packet.game.PlanetSideGUID + + val terminal = ImplantTerminalMech(GlobalDefinitions.implant_terminal_mech) + terminal.Actor = system.actorOf(Props(classOf[ImplantTerminalMechControl], terminal), "mech") + terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner.Faction = faction + terminal.GUID = PlanetSideGUID(1) + (Player("test", faction, CharacterGender.Male, 0, 0), terminal) + } +} diff --git a/common/src/test/scala/objects/terminal/OrderTerminalTest.scala b/common/src/test/scala/objects/terminal/OrderTerminalTest.scala index c5e41100c..1998d7d75 100644 --- a/common/src/test/scala/objects/terminal/OrderTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/OrderTerminalTest.scala @@ -2,7 +2,9 @@ package objects.terminal import akka.actor.ActorRef +import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.serverobject.terminals.Terminal +import net.psforever.objects.zones.Zone import net.psforever.objects.{AmmoBox, GlobalDefinitions, Player, Tool} import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} import net.psforever.types._ @@ -11,6 +13,9 @@ import org.specs2.mutable.Specification class OrderTerminalTest extends Specification { "Order_Terminal" should { val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val terminal = Terminal(GlobalDefinitions.order_terminal) + terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { val terminal = Terminal(GlobalDefinitions.order_terminal) @@ -18,7 +23,6 @@ class OrderTerminalTest extends Specification { } "player can buy a box of ammunition ('9mmbullet_AP')" in { - val terminal = Terminal(GlobalDefinitions.order_terminal) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "9mmbullet_AP", 0, PlanetSideGUID(0)) val reply = terminal.Request(player, msg) reply.isInstanceOf[Terminal.BuyEquipment] mustEqual true @@ -29,7 +33,6 @@ class OrderTerminalTest extends Specification { } "player can buy a weapon ('suppressor')" in { - val terminal = Terminal(GlobalDefinitions.order_terminal) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "suppressor", 0, PlanetSideGUID(0)) val reply = terminal.Request(player, msg) reply.isInstanceOf[Terminal.BuyEquipment] mustEqual true @@ -39,7 +42,6 @@ class OrderTerminalTest extends Specification { } "player can buy a box of vehicle ammunition ('105mmbullet')" in { - val terminal = Terminal(GlobalDefinitions.order_terminal) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 3, "105mmbullet", 0, PlanetSideGUID(0)) val reply = terminal.Request(player, msg) reply.isInstanceOf[Terminal.BuyEquipment] mustEqual true @@ -50,7 +52,6 @@ class OrderTerminalTest extends Specification { } "player can buy a support tool ('bank')" in { - val terminal = Terminal(GlobalDefinitions.order_terminal) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 2, "bank", 0, PlanetSideGUID(0)) val reply = terminal.Request(player, msg) reply.isInstanceOf[Terminal.BuyEquipment] mustEqual true @@ -60,14 +61,12 @@ class OrderTerminalTest extends Specification { } "player can buy different armor ('lite_armor')" in { - val terminal = Terminal(GlobalDefinitions.order_terminal) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "lite_armor", 0, PlanetSideGUID(0)) terminal.Request(player, msg) mustEqual Terminal.BuyExosuit(ExoSuitType.Agile) } "player can not buy fake equipment ('sabot')" in { - val terminal = Terminal(GlobalDefinitions.order_terminal) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "sabot", 0, PlanetSideGUID(0)) terminal.Request(player, msg) mustEqual Terminal.NoDeal() @@ -76,7 +75,6 @@ class OrderTerminalTest extends Specification { //TODO loudout tests "player can not buy equipment from the wrong page ('9mmbullet_AP', page 1)" in { - val terminal = Terminal(GlobalDefinitions.order_terminal) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "9mmbullet_AP", 0, PlanetSideGUID(0)) terminal.Request(player, msg) mustEqual Terminal.NoDeal() diff --git a/common/src/test/scala/objects/terminal/TerminalControlTest.scala b/common/src/test/scala/objects/terminal/TerminalControlTest.scala index b51062594..f3a461f90 100644 --- a/common/src/test/scala/objects/terminal/TerminalControlTest.scala +++ b/common/src/test/scala/objects/terminal/TerminalControlTest.scala @@ -1,8 +1,10 @@ // Copyright (c) 2017 PSForever package objects.terminal -import akka.actor.Props -import net.psforever.objects.serverobject.terminals.{Terminal, TerminalControl} +import akka.actor.{ActorSystem, Props} +import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.serverobject.terminals.{Terminal, TerminalControl, TerminalDefinition} +import net.psforever.objects.zones.Zone import net.psforever.objects.{GlobalDefinitions, Player} import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} import net.psforever.types._ @@ -21,8 +23,7 @@ class TerminalControl1Test extends ActorTest() { class TerminalControl2Test extends ActorTest() { "TerminalControl can not process wrong messages" in { - val terminal = Terminal(GlobalDefinitions.cert_terminal) - terminal.Actor = system.actorOf(Props(classOf[TerminalControl], terminal), "test-cert-term") + val (_, terminal) = TerminalControlTest.SetUpAgents(GlobalDefinitions.cert_terminal, PlanetSideEmpire.TR) terminal.Actor !"hello" val reply = receiveOne(Duration.create(500, "ms")) @@ -34,9 +35,7 @@ class TerminalControl2Test extends ActorTest() { //test for Cert_Terminal messages (see CertTerminalTest) class CertTerminalControl1Test extends ActorTest() { "TerminalControl can be used to learn a certification ('medium_assault')" in { - val terminal = Terminal(GlobalDefinitions.cert_terminal) - terminal.Actor = system.actorOf(Props(classOf[TerminalControl], terminal), "test-cert-term") - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val (player, terminal) = TerminalControlTest.SetUpAgents(GlobalDefinitions.cert_terminal, PlanetSideEmpire.TR) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Learn, 0, "medium_assault", 0, PlanetSideGUID(0)) terminal.Actor ! Terminal.Request(player, msg) @@ -51,9 +50,7 @@ class CertTerminalControl1Test extends ActorTest() { class CertTerminalControl2Test extends ActorTest() { "TerminalControl can be used to warn about not learning a fake certification ('juggling')" in { - val terminal = Terminal(GlobalDefinitions.cert_terminal) - terminal.Actor = system.actorOf(Props(classOf[TerminalControl], terminal), "test-cert-term") - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val (player, terminal) = TerminalControlTest.SetUpAgents(GlobalDefinitions.cert_terminal, PlanetSideEmpire.TR) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Learn, 0, "juggling", 0, PlanetSideGUID(0)) terminal.Actor ! Terminal.Request(player, msg) @@ -68,9 +65,7 @@ class CertTerminalControl2Test extends ActorTest() { class CertTerminalControl3Test extends ActorTest() { "TerminalControl can be used to forget a certification ('medium_assault')" in { - val terminal = Terminal(GlobalDefinitions.cert_terminal) - terminal.Actor = system.actorOf(Props(classOf[TerminalControl], terminal), "test-cert-term") - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val (player, terminal) = TerminalControlTest.SetUpAgents(GlobalDefinitions.cert_terminal, PlanetSideEmpire.TR) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Sell, 0, "medium_assault", 0, PlanetSideGUID(0)) terminal.Actor ! Terminal.Request(player, msg) @@ -85,9 +80,7 @@ class CertTerminalControl3Test extends ActorTest() { class VehicleTerminalControl1Test extends ActorTest() { "TerminalControl can be used to buy a vehicle ('two_man_assault_buggy')" in { - val terminal = Terminal(GlobalDefinitions.ground_vehicle_terminal) - terminal.Actor = system.actorOf(Props(classOf[TerminalControl], terminal), "test-cert-term") - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + 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)) terminal.Actor ! Terminal.Request(player, msg) @@ -112,9 +105,7 @@ class VehicleTerminalControl1Test extends ActorTest() { class VehicleTerminalControl2Test extends ActorTest() { "TerminalControl can be used to warn about not buy a vehicle ('harasser')" in { - val terminal = Terminal(GlobalDefinitions.ground_vehicle_terminal) - terminal.Actor = system.actorOf(Props(classOf[TerminalControl], terminal), "test-cert-term") - val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val (player, terminal) = TerminalControlTest.SetUpAgents(GlobalDefinitions.ground_vehicle_terminal, PlanetSideEmpire.TR) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "harasser", 0, PlanetSideGUID(0)) terminal.Actor ! Terminal.Request(player, msg) @@ -126,3 +117,13 @@ class VehicleTerminalControl2Test extends ActorTest() { assert(reply2.response == Terminal.NoDeal()) } } + +object TerminalControlTest { + def SetUpAgents(tdef : TerminalDefinition, faction : PlanetSideEmpire.Value)(implicit system : ActorSystem) : (Player, Terminal) = { + val terminal = Terminal(tdef) + terminal.Actor = system.actorOf(Props(classOf[TerminalControl], terminal), "test-term") + terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner.Faction = faction + (Player("test", faction, CharacterGender.Male, 0, 0), terminal) + } +} diff --git a/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala b/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala index 0bcb24d1b..6a390432b 100644 --- a/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala +++ b/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala @@ -2,8 +2,10 @@ package objects.terminal import akka.actor.ActorRef +import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.{GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals.Terminal +import net.psforever.objects.zones.Zone import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType} import org.specs2.mutable.Specification @@ -11,6 +13,9 @@ import org.specs2.mutable.Specification class VehicleTerminalCombinedTest extends Specification { "Ground_Vehicle_Terminal" should { val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val terminal = Terminal(GlobalDefinitions.vehicle_terminal_combined) + terminal.Owner = new Building(0, Zone.Nowhere) + terminal.Owner.Faction = PlanetSideEmpire.TR "construct" in { val terminal = Terminal(GlobalDefinitions.vehicle_terminal_combined) @@ -18,8 +23,8 @@ class VehicleTerminalCombinedTest extends Specification { } "player can buy a ground vehicle, the harasser ('two_man_assault_buggy')" in { - val terminal = Terminal(GlobalDefinitions.vehicle_terminal_combined) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "two_man_assault_buggy", 0, PlanetSideGUID(0)) + val reply = terminal.Request(player, msg) reply.isInstanceOf[Terminal.BuyVehicle] mustEqual true val reply2 = reply.asInstanceOf[Terminal.BuyVehicle] @@ -35,8 +40,8 @@ class VehicleTerminalCombinedTest extends Specification { } "player can buy a flying vehicle, the reaver ('lightgunship')" in { - val terminal = Terminal(GlobalDefinitions.vehicle_terminal_combined) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "lightgunship", 0, PlanetSideGUID(0)) + val reply = terminal.Request(player, msg) reply.isInstanceOf[Terminal.BuyVehicle] mustEqual true val reply2 = reply.asInstanceOf[Terminal.BuyVehicle] @@ -52,7 +57,6 @@ class VehicleTerminalCombinedTest extends Specification { } "player can not buy a fake vehicle ('harasser')" in { - val terminal = Terminal(GlobalDefinitions.vehicle_terminal_combined) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "harasser", 0, PlanetSideGUID(0)) terminal.Request(player, msg) mustEqual Terminal.NoDeal() diff --git a/pslogin/src/main/scala/Maps.scala b/pslogin/src/main/scala/Maps.scala index 03e55a6c7..71c810840 100644 --- a/pslogin/src/main/scala/Maps.scala +++ b/pslogin/src/main/scala/Maps.scala @@ -7,6 +7,7 @@ import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech import net.psforever.objects.serverobject.locks.IFFLock import net.psforever.objects.serverobject.mblocker.Locker import net.psforever.objects.serverobject.pad.VehicleSpawnPad +import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.types.Vector3 @@ -103,30 +104,46 @@ object Maps { VehicleSpawnPad.Constructor(Vector3(3508.9844f, 2895.961f, 92.296875f), Vector3(0f, 0f, 270.0f)) )) //TODO guid not correct - LocalBases = 30 + LocalBuilding(2, FoundationBuilder(Building.Structure)) + ObjectToBuilding(186, 2) + ObjectToBuilding(187, 2) + ObjectToBuilding(188, 2) + ObjectToBuilding(522, 2) + ObjectToBuilding(523, 2) + ObjectToBuilding(524, 2) + ObjectToBuilding(525, 2) + ObjectToBuilding(526, 2) + ObjectToBuilding(527, 2) + ObjectToBuilding(528, 2) + ObjectToBuilding(529, 2) + ObjectToBuilding(686, 2) + ObjectToBuilding(687, 2) + ObjectToBuilding(688, 2) + ObjectToBuilding(689, 2) + ObjectToBuilding(690, 2) + ObjectToBuilding(691, 2) + ObjectToBuilding(692, 2) + ObjectToBuilding(693, 2) + ObjectToBuilding(842, 2) + ObjectToBuilding(843, 2) + ObjectToBuilding(844, 2) + ObjectToBuilding(845, 2) + ObjectToBuilding(853, 2) //TODO check building_id + ObjectToBuilding(855, 2) //TODO check building_id + ObjectToBuilding(860, 2) //TODO check building_id + ObjectToBuilding(1063, 2) //TODO unowned courtyard terminal? + ObjectToBuilding(500, 2) //TODO unowned courtyard spawnpad? + ObjectToBuilding(304, 2) //TODO unowned courtyard terminal? + ObjectToBuilding(501, 2) //TODO unowned courtyard spawnpad? - ObjectToBase(330, 29) - ObjectToBase(331, 29) - ObjectToBase(332, 29) - ObjectToBase(333, 29) - //ObjectToBase(520, 29) - ObjectToBase(522, 2) - ObjectToBase(523, 2) - ObjectToBase(524, 2) - ObjectToBase(525, 2) - ObjectToBase(526, 2) - ObjectToBase(527, 2) - ObjectToBase(528, 2) - ObjectToBase(529, 2) - ObjectToBase(556, 29) - ObjectToBase(557, 29) - ObjectToBase(558, 29) - ObjectToBase(559, 29) - ObjectToBase(1081, 2) - ObjectToBase(1063, 2) //TODO unowned courtyard terminal? - ObjectToBase(500, 2) //TODO unowned courtyard spawnpad? - ObjectToBase(304, 2) //TODO unowned courtyard terminal? - ObjectToBase(501, 2) //TODO unowned courtyard spawnpad? + LocalBuilding(29, FoundationBuilder(Building.Structure)) + ObjectToBuilding(330, 29) + ObjectToBuilding(332, 29) + ObjectToBuilding(556, 29) + ObjectToBuilding(558, 29) + + //ObjectToBuilding(1081, ?) + //ObjectToBuilding(520, ?) DoorToLock(330, 558) DoorToLock(331, 559) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index cbe972f4d..46b6ef0ee 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -14,7 +14,8 @@ import net.psforever.objects._ import net.psforever.objects.equipment._ import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver} import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} -import net.psforever.objects.mount.Mountable +import net.psforever.objects.serverobject.mount.Mountable +import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech @@ -424,7 +425,7 @@ class WorldSessionActor extends Actor with MDCContextAware { val player_guid : PlanetSideGUID = tplayer.GUID val obj_guid : PlanetSideGUID = obj.GUID log.info(s"MountVehicleMsg: $player_guid mounts $obj @ $seat_num") - tplayer.VehicleSeated = Some(obj_guid) + //tplayer.VehicleSeated = Some(obj_guid) sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(obj_guid, 0, 1000L))) //health of mech sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(obj_guid, player_guid, seat_num))) vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.MountVehicle(player_guid, obj_guid, seat_num)) @@ -434,7 +435,7 @@ class WorldSessionActor extends Actor with MDCContextAware { val player_guid : PlanetSideGUID = tplayer.GUID log.info(s"MountVehicleMsg: $player_guid mounts $obj_guid @ $seat_num") vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(obj_guid) //clear all deconstruction timers - tplayer.VehicleSeated = Some(obj_guid) + //tplayer.VehicleSeated = Some(obj_guid) if(seat_num == 0) { //simplistic vehicle ownership management obj.Owner match { case Some(owner_guid) => @@ -466,8 +467,37 @@ class WorldSessionActor extends Actor with MDCContextAware { 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(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(player_guid, seat_num, false))) + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, seat_num, false)) + + 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") + sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(player_guid, seat_num, false))) + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, seat_num, false)) + UnAccessContents(obj) + } + else { + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, seat_num, true, obj.GUID)) + } + if(obj.Seats.values.count(seat => seat.isOccupied) == 0) { + vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(obj, continent, 600L) //start vehicle decay (10m) + } + + case Mountable.CanDismount(obj : Mountable, _) => + log.warn(s"DismountVehicleMsg: $obj is some generic mountable object and nothing will happen") + case Mountable.CanNotMount(obj, seat_num) => log.warn(s"MountVehicleMsg: $tplayer attempted to mount $obj's seat $seat_num, but was not allowed") + + case Mountable.CanNotDismount(obj, seat_num) => + log.warn(s"DismountVehicleMsg: $tplayer attempted to dismount $obj's seat $seat_num, but was not allowed") } case Terminal.TerminalMessage(tplayer, msg, order) => @@ -961,11 +991,10 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info("Load the now-registered player") //load the now-registered player tplayer.Spawn - sendResponse(PacketCoding.CreateGamePacket(0, - ObjectCreateDetailedMessage(ObjectClass.avatar, tplayer.GUID, tplayer.Definition.Packet.DetailedConstructorData(tplayer).get) - )) + val dcdata = tplayer.Definition.Packet.DetailedConstructorData(tplayer).get + sendResponse(PacketCoding.CreateGamePacket(0, ObjectCreateDetailedMessage(ObjectClass.avatar, tplayer.GUID, dcdata))) avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.LoadPlayer(tplayer.GUID, tplayer.Definition.Packet.ConstructorData(tplayer).get)) - log.debug(s"ObjectCreateDetailedMessage: ${tplayer.Definition.Packet.DetailedConstructorData(tplayer).get}") + log.debug(s"ObjectCreateDetailedMessage: $dcdata") case SetCurrentAvatar(tplayer) => val guid = tplayer.GUID @@ -1823,52 +1852,49 @@ class WorldSessionActor extends Actor with MDCContextAware { // TODO: Not all incoming UseItemMessage's respond with another UseItemMessage (i.e. doors only send out GenericObjectStateMsg) continent.GUID(object_guid) match { case Some(door : Door) => - continent.Map.DoorToLock.get(object_guid.guid) match { //check for IFF Lock - case Some(lock_guid) => - val lock_hacked = continent.GUID(lock_guid).get.asInstanceOf[IFFLock].HackedBy match { - case Some(_) => - true - case None => - Vector3.ScalarProjection(door.Outwards, player.Position - door.Position) < 0f - } - continent.Map.ObjectToBase.get(lock_guid) match { //check for associated base - case Some(base_id) => - if(continent.Base(base_id).get.Faction == player.Faction || lock_hacked) { //either base allegiance aligns or locks is hacked - door.Actor ! Door.Use(player, msg) - } - case None => - if(lock_hacked) { //is lock hacked? this may be a weird case - door.Actor ! Door.Use(player, msg) - } - } - case None => - door.Actor ! Door.Use(player, msg) //let door open freely + if(player.Faction == door.Faction || ((continent.Map.DoorToLock.get(object_guid.guid) match { + case Some(lock_guid) => continent.GUID(lock_guid).get.asInstanceOf[IFFLock].HackedBy.isDefined + case None => !door.isOpen + }) || Vector3.ScalarProjection(door.Outwards, player.Position - door.Position) < 0f)) { + door.Actor ! Door.Use(player, msg) + } + else if(door.isOpen) { + //the door is open globally ... except on our screen + sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(object_guid, 16))) } case Some(panel : IFFLock) => - player.Slot(player.DrawnSlot).Equipment match { - case Some(tool : SimpleItem) => - if(tool.Definition == GlobalDefinitions.remote_electronics_kit) { - //TODO get player hack level (for now, presume 15s in intervals of 4/s) - progressBarValue = Some(-2.66f) - self ! WorldSessionActor.ItemHacking(player, panel, tool.GUID, 2.66f, FinishHackingDoor(panel, 1114636288L)) - log.info("Hacking a door~") - } - case _ => ; + 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) { + //TODO get player hack level (for now, presume 15s in intervals of 4/s) + progressBarValue = Some(-2.66f) + self ! WorldSessionActor.ItemHacking(player, panel, tool.GUID, 2.66f, FinishHackingDoor(panel, 1114636288L)) + log.info("Hacking a door~") + } + case _ => ; + } } case Some(obj : Locker) => - val container = player.Locker - accessedContainer = Some(container) - sendResponse(PacketCoding.CreateGamePacket(0, UseItemMessage(avatar_guid, unk1, container.GUID, unk2, unk3, unk4, unk5, unk6, unk7, unk8, 456))) + if(player.Faction == obj.Faction) { + log.info(s"UseItem: $player accessing a locker") + val container = player.Locker + accessedContainer = Some(container) + sendResponse(PacketCoding.CreateGamePacket(0, UseItemMessage(avatar_guid, unk1, container.GUID, unk2, unk3, unk4, unk5, unk6, unk7, unk8, 456))) + } + else { + log.info(s"UseItem: not $player's locker") + } case Some(obj : Vehicle) => - if(obj.Faction == player.Faction) { - val equipment = player.Slot(player.DrawnSlot).Equipment + val equipment = player.Slot(player.DrawnSlot).Equipment + if(player.Faction == obj.Faction) { if(equipment match { case Some(tool : Tool) => tool.Definition match { - case GlobalDefinitions.nano_dispenser | GlobalDefinitions.remote_electronics_kit => false + case GlobalDefinitions.nano_dispenser => false case _ => true } case _ => true @@ -1889,13 +1915,19 @@ class WorldSessionActor extends Actor with MDCContextAware { case GlobalDefinitions.nano_dispenser => //TODO repairing behavior - case GlobalDefinitions.remote_electronics_kit => - //TODO hacking behavior - case _ => ; } } } + //enemy player interactions + else if(equipment.isDefined) { + equipment.get.Definition match { + case GlobalDefinitions.remote_electronics_kit => + //TODO hacking behavior + + case _ => ; + } + } case Some(obj : PlanetSideGameObject) => if(itemType != 121) { @@ -1933,7 +1965,9 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info("ItemTransaction: " + msg) continent.GUID(terminal_guid) match { case Some(term : Terminal) => - term.Actor ! Terminal.Request(player, msg) + if(player.Faction == term.Faction) { + term.Actor ! Terminal.Request(player, msg) + } case Some(obj : PlanetSideGameObject) => ; case None => ; } @@ -2015,36 +2049,19 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ DismountVehicleMsg(player_guid, unk1, unk2) => //TODO optimize this later log.info(s"DismountVehicleMsg: $msg") + //common warning for this section + def dismountWarning(msg : String) : Unit = { + log.warn(s"$msg; some vehicle might not know that a player is no longer sitting in it") + } if(player.GUID == player_guid) { //normally disembarking from a seat - val previouslySeated = player.VehicleSeated - player.VehicleSeated = None - sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(player_guid, unk1, unk2))) - vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, unk1, unk2)) - //common warning for this section - def dismountWarning(msg : String) : Unit = { - log.warn(s"$msg; some vehicle might not know that a player is no longer sitting in it") - } - //find vehicle seat and disembark it - previouslySeated match { + player.VehicleSeated match { case Some(obj_guid) => continent.GUID(obj_guid) match { case Some(obj : Mountable) => - val seats = obj.Seats.values - seats.find(seat => seat.Occupant.contains(player)) match { - case Some(seat) => - if(seat.Bailable || obj.Velocity.isEmpty || Vector3.MagnitudeSquared(obj.Velocity.get).toInt == 0) { //ugh, float comparison - seat.Occupant = None - //special actions - obj match { - case (veh : Vehicle) => - if(seats.count(seat => seat.isOccupied) == 0) { - vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(veh, continent, 600L) //start vehicle decay (10m) - UnAccessContents(veh) - } - case _ => ; - } - } + obj.PassengerInSeat(player) match { + case Some(seat_num : Int) => + obj.Actor ! Mountable.TryDismount(player, seat_num) case None => dismountWarning(s"DismountVehicleMsg: can not find where player $player_guid is seated in mountable $obj_guid") } @@ -2056,35 +2073,23 @@ class WorldSessionActor extends Actor with MDCContextAware { } } else { - //kicking someone else out of a seat; need to own that seat + //kicking someone else out of a seat; need to own that seat/mountable player.VehicleOwned match { - case Some(vehicle_guid) => - continent.GUID(player_guid) match { - case Some(tplayer : Player) => - if(tplayer.VehicleSeated.contains(vehicle_guid)) { - continent.GUID(vehicle_guid) match { - case Some(obj : Vehicle) => - val seats = obj.Seats.values - seats.find(seat => seat.Occupant.contains(tplayer)) match { - case Some(seat) => - seat.Occupant = None - tplayer.VehicleSeated = None - vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, unk1, unk2, vehicle_guid)) - if(seats.count(seat => seat.isOccupied) == 0) { - vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(obj, continent, 600L) //start vehicle decay (10m) - } - case None => - log.warn(s"DismountVehicleMsg: can not find where player $player_guid is seated in vehicle $vehicle_guid") - } - case _ => - log.warn(s"DismountVehicleMsg: can not find vehicle $vehicle_guid") - } + case Some(obj_guid) => + (continent.GUID(obj_guid), continent.GUID(player_guid)) match { + case (Some(obj : Mountable), Some(tplayer : Player)) => + obj.PassengerInSeat(tplayer) match { + case Some(seat_num : Int) => + obj.Actor ! Mountable.TryDismount(tplayer, seat_num) + case None => + dismountWarning(s"DismountVehicleMsg: can not find where other player $player_guid is seated in mountable $obj_guid") } - else { - log.warn(s"DismountVehicleMsg: non-owner player $player trying to kick player $tplayer out of his seat") - } - case _ => + case (None, _) => ; + log.warn(s"DismountVehicleMsg: $player can not find his vehicle") + case (_, None) => ; log.warn(s"DismountVehicleMsg: player $player_guid could not be found to kick") + case _ => + log.warn(s"DismountVehicleMsg: object is either not a Mountable or not a Player") } case None => log.warn(s"DismountVehicleMsg: $player does not own a vehicle") diff --git a/pslogin/src/main/scala/Zones.scala b/pslogin/src/main/scala/Zones.scala index 786d33842..f856f4875 100644 --- a/pslogin/src/main/scala/Zones.scala +++ b/pslogin/src/main/scala/Zones.scala @@ -32,8 +32,8 @@ object Zones { super.Init(context) import net.psforever.types.PlanetSideEmpire - Base(2).get.Faction = PlanetSideEmpire.VS //HART building C - Base(29).get.Faction = PlanetSideEmpire.NC //South Villa Gun Tower + Building(2).get.Faction = PlanetSideEmpire.VS //HART building C + Building(29).get.Faction = PlanetSideEmpire.NC //South Villa Gun Tower } } diff --git a/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala b/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala index 760cbf204..adc9668a2 100644 --- a/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala +++ b/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala @@ -53,7 +53,7 @@ class DeconstructionActor extends Actor { case DeconstructionActor.RequestDeleteVehicle(vehicle, zone, time) => vehicles = vehicles :+ DeconstructionActor.VehicleEntry(vehicle, zone, time) vehicle.Actor ! Vehicle.PrepareForDeletion - //kick everyone out + //kick everyone out; this is a no-blocking manual form of MountableBehavior ! Mountable.TryDismount vehicle.Definition.MountPoints.values.foreach(seat_num => { val zone_id : String = zone.Id val seat : Seat = vehicle.Seat(seat_num).get