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 4e07fe36..5f9b26de 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
@@ -4,28 +4,58 @@ package net.psforever.objects.serverobject.doors
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.Player
import net.psforever.packet.game.UseItemMessage
+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 {
- private var openState : Boolean = false
+ 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" */
+ private var outwards : Vector3 = Vector3.Zero
- def Open : Boolean = openState
+ /**
+ * While setting the normal rotation angle for the door (?),
+ * use the angular data to determine an "inside" side and an "outside" side.
+ *
+ * Doors are always positioned with the frame perpendicular to the ground.
+ * The `i` and `j` components can be excused for this reason and only the `k` component (rotation around world-up) matters.
+ * Due to angle-corrected North, add 90 degrees before switching to radians and negate the cosine.
+ * @param orient the orientation of the door
+ * @return the clamped orientation of the door
+ */
+ override def Orientation_=(orient : Vector3) : Vector3 = {
+ val ret = super.Orientation_=(orient)
+ //transform angular data into unit circle components
+ val rang = math.toRadians(orient.z + 90)
+ outwards = Vector3(-math.cos(rang).toFloat, math.sin(rang).toFloat, 0)
+ ret
+ }
- def Open_=(open : Boolean) : Boolean = {
+ def Outwards : Vector3 = outwards
+
+ def isOpen : Boolean = openState.isDefined
+
+ def Open : Option[Player] = openState
+
+ def Open_=(player : Player) : Option[Player] = {
+ Open_=(Some(player))
+ }
+
+ def Open_=(open : Option[Player]) : Option[Player] = {
openState = open
Open
}
def Use(player : Player, msg : UseItemMessage) : Door.Exchange = {
- if(!openState) {
- openState = true
+ if(openState.isEmpty) {
+ openState = Some(player)
Door.OpenEvent()
}
else {
- openState = false
+ openState = None
Door.CloseEvent()
}
}
@@ -79,7 +109,7 @@ object Door {
import akka.actor.ActorContext
/**
- * Instantiate an configure a `Door` object
+ * Instantiate and configure a `Door` object.
* @param id the unique id that will be assigned to this entity
* @param context a context to allow the object to properly set up `ActorSystem` functionality
* @return the `Door` object
@@ -92,4 +122,25 @@ object Door {
obj.Actor = context.actorOf(Props(classOf[DoorControl], obj), s"${GlobalDefinitions.door.Name}_$id")
obj
}
+
+ import net.psforever.types.Vector3
+ /**
+ * Instantiate and configure a `Door` object that has knowledge of both its position and outwards-facing direction.
+ * The assumption is that this door will be paired with an IFF Lock, thus, has conditions for opening.
+ * @param pos the position of the door
+ * @param outwards_direction a vector in the direction of the door's outside
+ * @param id the unique id that will be assigned to this entity
+ * @param context a context to allow the object to properly set up `ActorSystem` functionality
+ * @return the `Door` object
+ */
+ def Constructor(pos : Vector3, outwards_direction : Vector3)(id : Int, context : ActorContext) : Door = {
+ import akka.actor.Props
+ import net.psforever.objects.GlobalDefinitions
+
+ val obj = Door(GlobalDefinitions.door)
+ obj.Position = pos
+ obj.Orientation = outwards_direction
+ obj.Actor = context.actorOf(Props(classOf[DoorControl], obj), s"${GlobalDefinitions.door.Name}_$id")
+ obj
+ }
}
diff --git a/common/src/main/scala/net/psforever/types/Vector3.scala b/common/src/main/scala/net/psforever/types/Vector3.scala
index 279b7411..3b2b0583 100644
--- a/common/src/main/scala/net/psforever/types/Vector3.scala
+++ b/common/src/main/scala/net/psforever/types/Vector3.scala
@@ -15,7 +15,7 @@ final case class Vector3(x : Float,
* @return a new `Vector3` object with the summed values
*/
def +(vec : Vector3) : Vector3 = {
- new Vector3(x + vec.x, y + vec.y, z + vec.z)
+ Vector3(x + vec.x, y + vec.y, z + vec.z)
}
/**
@@ -25,7 +25,7 @@ final case class Vector3(x : Float,
* @return a new `Vector3` object with the difference values
*/
def -(vec : Vector3) : Vector3 = {
- new Vector3(x - vec.x, y - vec.y, z - vec.z)
+ Vector3(x - vec.x, y - vec.y, z - vec.z)
}
/**
@@ -36,11 +36,13 @@ final case class Vector3(x : Float,
* @return a new `Vector3` object
*/
def *(scalar : Float) : Vector3 = {
- new Vector3(x*scalar, y*scalar, z*scalar)
+ Vector3(x*scalar, y*scalar, z*scalar)
}
}
object Vector3 {
+ final val Zero : Vector3 = Vector3(0f, 0f, 0f)
+
implicit val codec_pos : Codec[Vector3] = (
("x" | newcodecs.q_float(0.0, 8192.0, 20)) ::
("y" | newcodecs.q_float(0.0, 8192.0, 20)) ::
@@ -102,4 +104,93 @@ object Vector3 {
val dz : Float = vec.z
(dx * dx) + (dy * dy) + (dz * dz)
}
+
+ /**
+ * Given a vector, find that's vector's unit vector.
+ *
+ * A unit vector is a vector in the direction of the original vector but with a magnitude of 1.
+ * @param vec the original vector
+ * @return the unit vector;
+ * if the original vector has no magnitude, a zero-vector is returned
+ */
+ def Unit(vec : Vector3) : Vector3 = {
+ val mag : Float = Magnitude(vec)
+ if(mag == 0) {
+ Vector3.Zero
+ }
+ else {
+ Vector3(vec.x / mag, vec.y / mag, vec.z / mag)
+ }
+ }
+
+ /**
+ * Given two vectors, find their dot product.
+ *
+ * The dot product is the sum of the products of the corresponding component parts of two vectors.
+ * It is equal to the product of the Euclidean magnitude of the vectors and cosine of the angle between them.
+ * If the dot product of two vectors of non-zero magnitude is 0, then the vectors are perpendicular to each other.
+ * @param vec1 the first vector
+ * @param vec2 the second vector
+ * @return the dot product
+ */
+ def DotProduct(vec1 : Vector3, vec2 : Vector3) : Float = {
+ vec1.x * vec2.x + vec1.y * vec2.y + vec1.z * vec2.z
+ }
+
+ /**
+ * For two vectors, find a vector that is simultaneously parallel to both vectors.
+ *
+ * The magnitude of the cross product is equal to
+ * the product of the magnitudes of both vectors
+ * and the sine of the angle between them.
+ * If the two original vectors are parallel or antiparallel, the cross product is a zero vector.
+ * Due to handiness rules, two non-zero cross product vectors that are antiparallel to each other can be calculated.
+ * @param vec1 the first vector
+ * @param vec2 the second vector
+ * @return the cross product
+ */
+ def CrossProduct(vec1 : Vector3, vec2 : Vector3) : Vector3 = {
+ Vector3(
+ vec1.y * vec2.z - vec2.y * vec1.z,
+ vec2.x * vec1.z - vec1.x * vec2.z,
+ vec1.x * vec2.y - vec2.x * vec1.y
+ )
+ }
+
+ /**
+ * Given two vectors, find the scalar value of the projection of one vector on the other.
+ *
+ * The value of the resulting scalar is the magnitude of the vector resulting from a vector projection of `vec1` onto `vec2`.
+ * For perpendicular vectors, the scalar projection result will be the same as the dot product result - zero.
+ * A positive value indicates a projected vector in the same direction as `vec2`;
+ * a negative value indicates an antiparallel vector.
+ * @see `VectorProjection`
+ * @param vec1 the vector being projected
+ * @param vec2 the vector projected onto
+ * @return the magnitude of the resulting projected vector
+ */
+ def ScalarProjection(vec1 : Vector3, vec2 : Vector3) : Float = {
+ val mag : Float = Magnitude(vec2)
+ if(mag == 0f) {
+ 0f
+ }
+ else {
+ DotProduct(vec1, vec2) / mag
+ }
+ }
+
+ /**
+ * Given two vectors, find the projection of one vector on the other.
+ *
+ * The vector projection of `vec1` on `vec2` produces a vector that is
+ * the direction of (parallel to) `vec2`
+ * with a magnitude equal to the product of `vec1` and the cosine of the angle between the two vectors.
+ * @see `ScalarProjection`
+ * @param vec1 the vector being projected
+ * @param vec2 the vector projected onto
+ * @return the resulting projected vector
+ */
+ def VectorProjection(vec1 : Vector3, vec2 : Vector3) : Vector3 = {
+ Unit(vec2) * ScalarProjection(vec1, vec2)
+ }
}
diff --git a/common/src/test/scala/Vector3Test.scala b/common/src/test/scala/Vector3Test.scala
index cedc7c06..75f1c2e5 100644
--- a/common/src/test/scala/Vector3Test.scala
+++ b/common/src/test/scala/Vector3Test.scala
@@ -48,21 +48,145 @@ class Vector3Test extends Specification {
Vector3.Distance(obj1, obj2) mustEqual 0f
}
- "addition" in {
+ "perform addition" in {
val obj1 = Vector3(3.0f, 4.0f, 5.0f)
val obj2 = Vector3(3.0f, 4.0f, 5.0f)
obj1 + obj2 mustEqual Vector3(6f, 8f, 10f)
}
- "subtraction" in {
+ "perform subtraction" in {
val obj1 = Vector3(3.0f, 4.0f, 5.0f)
val obj2 = Vector3(3.0f, 4.0f, 5.0f)
obj1 - obj2 mustEqual Vector3(0f, 0f, 0f)
}
- "scalar" in {
+ "multiply by a scalar" in {
vec * 3f mustEqual Vector3(3.8999999f, -7.7999997f, 11.700001f)
}
+
+ "calculate the unit vector (zero)" in {
+ Vector3.Unit(Vector3.Zero) mustEqual Vector3(0,0,0)
+ }
+
+ "calculate the unit vector (normal)" in {
+ import Vector3._
+ val one_root_two : Float = (1/math.sqrt(2)).toFloat
+ val one_root_three : Float = (1/math.sqrt(3)).toFloat
+ val ulp : Float = math.ulp(1) //measure of insignificance
+
+ Unit(Vector3(1, 0, 0)) mustEqual Vector3(1, 0, 0)
+ 1 - Magnitude(Vector3(1, 0, 0)) < ulp mustEqual true
+
+ Unit(Vector3(1, 1, 0)) mustEqual Vector3(one_root_two, one_root_two, 0)
+ 1 - Magnitude(Vector3(one_root_two, one_root_two, 0)) < ulp mustEqual true
+
+ Unit(Vector3(1, 1, 1)) mustEqual Vector3(one_root_three, one_root_three, one_root_three)
+ 1 - Magnitude(Vector3(one_root_three, one_root_three, one_root_three)) < ulp mustEqual true
+ }
+
+ "calculate the dot product (magnitude-squared)" in {
+ Vector3.DotProduct(vec, vec) mustEqual Vector3.MagnitudeSquared(vec)
+ }
+
+ "calculate the dot product (two vectors)" in {
+ Vector3.DotProduct(vec, Vector3(3.4f, -5.6f, 7.8f)) mustEqual 49.4f
+ }
+
+ "calculate the dot product (zero vector)" in {
+ Vector3.DotProduct(vec, Vector3.Zero) mustEqual 0f
+ }
+
+ "calculate the cross product (identity)" in {
+ val Vx : Vector3 = Vector3(1, 0, 0)
+ val Vy : Vector3 = Vector3(0, 1, 0)
+ val Vz : Vector3 = Vector3(0, 0, 1)
+
+ Vector3.CrossProduct(Vx, Vy) mustEqual Vz
+ Vector3.CrossProduct(Vy, Vz) mustEqual Vx
+ Vector3.CrossProduct(Vz, Vx) mustEqual Vy
+
+ Vector3.CrossProduct(Vy, Vx) mustEqual Vector3(0, 0, -1)
+ Vector3.CrossProduct(Vz, Vy) mustEqual Vector3(-1, 0, 0)
+ Vector3.CrossProduct(Vx, Vz) mustEqual Vector3(0, -1, 0)
+ }
+
+ "calculate the cross product (full)" in {
+ val A : Vector3 = Vector3(2, 1, -1)
+ val B : Vector3 = Vector3(-3, 4, 1)
+
+ Vector3.CrossProduct(A, B) mustEqual Vector3(5, 1, 11)
+ Vector3.CrossProduct(B, A) mustEqual Vector3(-5, -1, -11)
+ }
+
+ "find a perpendicular vector with cross product" in {
+ val A : Vector3 = Vector3(2, 1, -1)
+ val B : Vector3 = Vector3(-3, 4, 1)
+ val C : Vector3 = Vector3.CrossProduct(A, B)
+
+ Vector3.DotProduct(A, C) mustEqual 0
+ Vector3.DotProduct(B, C) mustEqual 0
+ }
+
+ "calculate the scalar projection (perpendicular vectors)" in {
+ val Vx : Vector3 = Vector3(1, 0, 0)
+ val Vy : Vector3 = Vector3(0, 1, 0)
+
+ Vector3.ScalarProjection(Vx, Vy) mustEqual 0
+ }
+
+ "calculate the scalar projection (parallel vectors)" in {
+ val A : Vector3 = Vector3(2, 0, 0)
+ val B : Vector3 = Vector3(10, 0, 0)
+
+ Vector3.ScalarProjection(A, B) mustEqual 2
+ Vector3.ScalarProjection(B, A) mustEqual 10
+ }
+
+ "calculate the scalar projection (antiparallel vectors)" in {
+ val A : Vector3 = Vector3(2, 0, 0)
+ val B : Vector3 = Vector3(-10, 0, 0)
+
+ Vector3.ScalarProjection(A, B) mustEqual -2
+ Vector3.ScalarProjection(B, A) mustEqual -10
+ }
+
+ "calculate the scalar projection (normal)" in {
+ val A : Vector3 = Vector3(2, 1, -1)
+ val B : Vector3 = Vector3(3, 4, 1)
+
+ Vector3.ScalarProjection(A, B) mustEqual 1.7650452f
+ Vector3.ScalarProjection(B, A) mustEqual 3.6742344f
+ }
+
+ "calculate the vector projection (perpendicular vectors)" in {
+ val Vx : Vector3 = Vector3(1, 0, 0)
+ val Vy : Vector3 = Vector3(0, 1, 0)
+
+ Vector3.VectorProjection(Vx, Vy) mustEqual Vector3.Zero
+ }
+
+ "calculate the vector projection (parallel vectors)" in {
+ val A : Vector3 = Vector3(2, 0, 0)
+ val B : Vector3 = Vector3(10, 0, 0)
+
+ Vector3.VectorProjection(A, B) mustEqual A
+ Vector3.VectorProjection(B, A) mustEqual B
+ }
+
+ "calculate the vector projection (antiparallel vectors)" in {
+ val A : Vector3 = Vector3(2, 0, 0)
+ val B : Vector3 = Vector3(-10, 0, 0)
+
+ Vector3.VectorProjection(A, B) mustEqual A
+ Vector3.VectorProjection(B, A) mustEqual B
+ }
+
+ "calculate the vector projection (normal)" in {
+ val A : Vector3 = Vector3(2, 1, -1)
+ val B : Vector3 = Vector3(3, 4, 1)
+
+ Vector3.VectorProjection(A, B) mustEqual Vector3(1.0384614f, 1.3846153f, 0.34615383f)
+ Vector3.VectorProjection(B, A) mustEqual Vector3(2.9999998f, 1.4999999f, -1.4999999f)
+ }
}
}
-
diff --git a/common/src/test/scala/objects/DoorTest.scala b/common/src/test/scala/objects/DoorTest.scala
index d969e229..d95b77ca 100644
--- a/common/src/test/scala/objects/DoorTest.scala
+++ b/common/src/test/scala/objects/DoorTest.scala
@@ -19,27 +19,55 @@ class DoorTest extends Specification {
"starts as closed (false)" in {
val door = Door(GlobalDefinitions.door)
- door.Open mustEqual false
+ door.Open mustEqual None
+ door.isOpen mustEqual false
}
- "can be opened and closed (1; manual)" in {
+ "be opened and closed (1; manual)" in {
+ val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val door = Door(GlobalDefinitions.door)
- door.Open mustEqual false
- door.Open = true
- door.Open mustEqual true
- door.Open = false
- door.Open mustEqual false
+ door.isOpen mustEqual false
+ door.Open mustEqual None
+
+ door.Open = Some(player)
+ door.isOpen mustEqual true
+ door.Open mustEqual Some(player)
+
+ door.Open = None
+ door.isOpen mustEqual false
+ door.Open mustEqual None
}
- "can beopened and closed (2; toggle)" in {
+ "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 false
+ door.Open mustEqual None
door.Use(player, msg)
- door.Open mustEqual true
+ door.Open mustEqual Some(player)
door.Use(player, msg)
- door.Open mustEqual false
+ door.Open mustEqual None
+ }
+
+ "keep track of its orientation as a North-corrected vector" in {
+ val ulp = math.ulp(1)
+ val door = Door(GlobalDefinitions.door)
+
+ door.Orientation = Vector3(0, 0, 0) //face North
+ door.Outwards.x < ulp mustEqual true
+ door.Outwards.y mustEqual 1
+
+ door.Orientation = Vector3(0, 0, 90) //face East
+ door.Outwards.x mustEqual 1
+ door.Outwards.y < ulp mustEqual true
+
+ door.Orientation = Vector3(0, 0, 180) //face South
+ door.Outwards.x < ulp mustEqual true
+ door.Outwards.y mustEqual -1
+
+ door.Orientation = Vector3(0, 0, 270) //face West
+ door.Outwards.x mustEqual -1
+ door.Outwards.y < ulp mustEqual true
}
}
}
@@ -61,7 +89,7 @@ class DoorControl2Test extends ActorTest() {
door.Actor = system.actorOf(Props(classOf[DoorControl], door), "door")
val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
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)
+ assert(door.Open.isEmpty)
door.Actor ! Door.Use(player, msg)
val reply = receiveOne(Duration.create(500, "ms"))
@@ -70,7 +98,7 @@ class DoorControl2Test extends ActorTest() {
assert(reply2.player == player)
assert(reply2.msg == msg)
assert(reply2.response == Door.OpenEvent())
- assert(door.Open)
+ assert(door.Open.isDefined)
}
}
}
@@ -80,12 +108,12 @@ class DoorControl3Test extends ActorTest() {
"do nothing if given garbage" in {
val door = Door(GlobalDefinitions.door)
door.Actor = system.actorOf(Props(classOf[DoorControl], door), "door")
- assert(!door.Open)
+ assert(door.Open.isEmpty)
door.Actor ! "trash"
val reply = receiveOne(Duration.create(500, "ms"))
assert(reply.isInstanceOf[Door.NoEvent])
- assert(!door.Open)
+ assert(door.Open.isEmpty)
}
}
}
diff --git a/common/src/test/scala/objects/ServerObjectBuilderTest.scala b/common/src/test/scala/objects/ServerObjectBuilderTest.scala
index 8dc4528f..863278f4 100644
--- a/common/src/test/scala/objects/ServerObjectBuilderTest.scala
+++ b/common/src/test/scala/objects/ServerObjectBuilderTest.scala
@@ -9,9 +9,9 @@ import net.psforever.types.Vector3
import scala.concurrent.duration.Duration
-class DoorObjectBuilderTest extends ActorTest {
+class DoorObjectBuilderTest1 extends ActorTest {
import net.psforever.objects.serverobject.doors.Door
- "DoorObjectBuilder" should {
+ "Door object" should {
"build" in {
val hub = ServerObjectBuilderTest.NumberPoolHub
val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, Door.Constructor), hub), "door")
@@ -26,9 +26,29 @@ class DoorObjectBuilderTest extends ActorTest {
}
}
+class DoorObjectBuilderTest2 extends ActorTest {
+ import net.psforever.objects.serverobject.doors.Door
+ "Door object" should {
+ "build" in {
+ val hub = ServerObjectBuilderTest.NumberPoolHub
+ val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, Door.Constructor(Vector3(1, 2, 3), Vector3(90, 180, 45))), hub), "door")
+ actor ! "!"
+
+ val reply = receiveOne(Duration.create(1000, "ms"))
+ assert(reply.isInstanceOf[Door])
+ assert(reply.asInstanceOf[Door].Position == Vector3(1, 2, 3))
+ assert(reply.asInstanceOf[Door].Orientation == Vector3(90, 180, 45))
+ assert(reply.asInstanceOf[Door].Outwards == Vector3(0.70710677f, 0.70710677f, 0f))
+ assert(reply.asInstanceOf[Door].HasGUID)
+ assert(reply.asInstanceOf[Door].GUID == PlanetSideGUID(1))
+ assert(reply == hub(1).get)
+ }
+ }
+}
+
class IFFLockObjectBuilderTest extends ActorTest {
import net.psforever.objects.serverobject.locks.IFFLock
- "IFFLockObjectBuilder" should {
+ "IFFLock object" should {
"build" in {
val hub = ServerObjectBuilderTest.NumberPoolHub
val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, IFFLock.Constructor), hub), "lock")
@@ -45,7 +65,7 @@ class IFFLockObjectBuilderTest extends ActorTest {
class ImplantTerminalMechObjectBuilderTest extends ActorTest {
import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech
- "IFFLockObjectBuilder" should {
+ "Implant terminal mech object" should {
"build" in {
val hub = ServerObjectBuilderTest.NumberPoolHub
val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, ImplantTerminalMech.Constructor), hub), "mech")
@@ -63,7 +83,7 @@ class ImplantTerminalMechObjectBuilderTest extends ActorTest {
class TerminalObjectBuilderTest extends ActorTest {
import net.psforever.objects.GlobalDefinitions.order_terminal
import net.psforever.objects.serverobject.terminals.Terminal
- "TerminalObjectBuilder" should {
+ "Terminal object" should {
"build" in {
val hub = ServerObjectBuilderTest.NumberPoolHub
val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, Terminal.Constructor(order_terminal)), hub), "term")
@@ -80,7 +100,7 @@ class TerminalObjectBuilderTest extends ActorTest {
class VehicleSpawnPadObjectBuilderTest extends ActorTest {
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
- "TerminalObjectBuilder" should {
+ "Vehicle spawn pad object" should {
"build" in {
val hub = ServerObjectBuilderTest.NumberPoolHub
val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1,
@@ -131,4 +151,3 @@ object ServerObjectBuilderTest {
}
}
}
-
diff --git a/pslogin/src/main/scala/Maps.scala b/pslogin/src/main/scala/Maps.scala
index 97ec1139..03e55a6c 100644
--- a/pslogin/src/main/scala/Maps.scala
+++ b/pslogin/src/main/scala/Maps.scala
@@ -36,8 +36,10 @@ object Maps {
val map12 = new ZoneMap("map12")
val map13 = new ZoneMap("map13") {
- LocalObject(ServerObjectBuilder(330, Door.Constructor))
- LocalObject(ServerObjectBuilder(332, Door.Constructor))
+ LocalObject(ServerObjectBuilder(330, Door.Constructor(Vector3(3979.9219f, 2592.0547f, 91.140625f), Vector3(0, 0, 180))))
+ LocalObject(ServerObjectBuilder(331, Door.Constructor(Vector3(3979.9219f, 2592.0547f, 111.140625f), Vector3(0, 0, 180))))
+ LocalObject(ServerObjectBuilder(332, Door.Constructor(Vector3(3979.9688f, 2608.0625f, 91.140625f), Vector3(0, 0, 0))))
+ LocalObject(ServerObjectBuilder(333, Door.Constructor(Vector3(3979.9688f, 2608.0625f, 111.140625f), Vector3(0, 0, 0))))
LocalObject(ServerObjectBuilder(362, Door.Constructor))
LocalObject(ServerObjectBuilder(370, Door.Constructor))
LocalObject(ServerObjectBuilder(371, Door.Constructor))
@@ -62,7 +64,9 @@ object Maps {
LocalObject(ServerObjectBuilder(528, ImplantTerminalMech.Constructor)) //Hart C
LocalObject(ServerObjectBuilder(529, ImplantTerminalMech.Constructor)) //Hart C
LocalObject(ServerObjectBuilder(556, IFFLock.Constructor))
+ LocalObject(ServerObjectBuilder(557, IFFLock.Constructor))
LocalObject(ServerObjectBuilder(558, IFFLock.Constructor))
+ LocalObject(ServerObjectBuilder(559, IFFLock.Constructor))
LocalObject(ServerObjectBuilder(686, Locker.Constructor))
LocalObject(ServerObjectBuilder(687, Locker.Constructor))
LocalObject(ServerObjectBuilder(688, Locker.Constructor))
@@ -102,7 +106,9 @@ object Maps {
LocalBases = 30
ObjectToBase(330, 29)
+ ObjectToBase(331, 29)
ObjectToBase(332, 29)
+ ObjectToBase(333, 29)
//ObjectToBase(520, 29)
ObjectToBase(522, 2)
ObjectToBase(523, 2)
@@ -113,7 +119,9 @@ object Maps {
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?
@@ -121,7 +129,9 @@ object Maps {
ObjectToBase(501, 2) //TODO unowned courtyard spawnpad?
DoorToLock(330, 558)
+ DoorToLock(331, 559)
DoorToLock(332, 556)
+ DoorToLock(333, 557)
TerminalToSpawnPad(1063, 500)
TerminalToSpawnPad(304, 501)
TerminalToInterface(520, 1081)
diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala
index 3125f762..cbe972f4 100644
--- a/pslogin/src/main/scala/WorldSessionActor.scala
+++ b/pslogin/src/main/scala/WorldSessionActor.scala
@@ -1144,7 +1144,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.Certifications += CertificationType.UniMAX
AwardBattleExperiencePoints(player, 1000000L)
// player.ExoSuit = ExoSuitType.MAX //TODO strange issue; divide number above by 10 when uncommenting
- player.Slot(0).Equipment = Tool(GlobalDefinitions.StandardPistol(player.Faction))
+ player.Slot(0).Equipment = SimpleItem(remote_electronics_kit) //Tool(GlobalDefinitions.StandardPistol(player.Faction))
player.Slot(2).Equipment = Tool(punisher) //suppressor
player.Slot(4).Equipment = Tool(GlobalDefinitions.StandardMelee(player.Faction))
player.Slot(6).Equipment = AmmoBox(bullet_9mm, 20) //bullet_9mm
@@ -1519,14 +1519,15 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ ChangeFireStateMessage_Start(item_guid) =>
log.info("ChangeFireState_Start: " + msg)
if(shooting.isEmpty) {
- FindWeapon match {
+ FindEquipment match {
case Some(tool : Tool) =>
if(tool.GUID == item_guid && tool.Magazine > 0) {
shooting = Some(item_guid)
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid))
}
- case Some(_) =>
- log.error(s"ChangeFireState_Start: the object that was found for $item_guid was not a Tool")
+ case Some(_) => //permissible, for now
+ shooting = Some(item_guid)
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid))
case None =>
log.error(s"ChangeFireState_Start: can not find $item_guid")
}
@@ -1537,12 +1538,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
val weapon : Option[Equipment] = if(shooting.contains(item_guid)) {
shooting = None
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid))
- FindWeapon
+ FindEquipment
}
else {
//some weapons, e.g., the decimator, do not send a ChangeFireState_Start on the last shot
- FindWeapon match {
- case Some(tool : Tool) =>
+ FindEquipment match {
+ case Some(tool) =>
if(tool.Definition == GlobalDefinitions.phoenix) {
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid))
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid))
@@ -1825,10 +1826,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
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((tplayer, _, _)) =>
- tplayer.Faction == player.Faction
+ case Some(_) =>
+ true
case None =>
- false
+ Vector3.ScalarProjection(door.Outwards, player.Position - door.Position) < 0f
}
continent.Map.ObjectToBase.get(lock_guid) match { //check for associated base
case Some(base_id) =>
@@ -2686,19 +2687,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
* the first value is a `Container` object;
* the second value is an `Equipment` object in the former
*/
- def FindContainedWeapon : (Option[PlanetSideGameObject with Container], Option[Equipment]) = {
+ def FindContainedEquipment : (Option[PlanetSideGameObject with Container], Option[Equipment]) = {
player.VehicleSeated match {
case Some(vehicle_guid) => //weapon is vehicle turret?
continent.GUID(vehicle_guid) match {
case Some(vehicle : Vehicle) =>
vehicle.PassengerInSeat(player) match {
case Some(seat_num) =>
- vehicle.WeaponControlledFromSeat(seat_num) match {
- case Some(item : Tool) =>
- (Some(vehicle), Some(item))
- case _ => ;
- (None, None)
- }
+ (Some(vehicle), vehicle.WeaponControlledFromSeat(seat_num))
case None => ;
(None, None)
}
@@ -2706,20 +2702,37 @@ class WorldSessionActor extends Actor with MDCContextAware {
(None, None)
}
case None => //not in vehicle; weapon in hand?
- player.Slot(player.DrawnSlot).Equipment match {
- case Some(item : Tool) =>
- (Some(player), Some(item))
- case _ => ;
- (None, None)
- }
+ (Some(player), player.Slot(player.DrawnSlot).Equipment)
+ }
+ }
+
+ /**
+ * Runs `FindContainedEquipment` but ignores the `Container` object output.
+ * @return an `Equipment` object
+ */
+ def FindEquipment : Option[Equipment] = FindContainedEquipment._2
+
+ /**
+ * Check two locations for a controlled piece of equipment that is associated with the `player`.
+ * Filter for discovered `Tool`-type `Equipment`.
+ * @return a `Tuple` of the returned values;
+ * the first value is a `Container` object;
+ * the second value is an `Tool` object in the former
+ */
+ def FindContainedWeapon : (Option[PlanetSideGameObject with Container], Option[Tool]) = {
+ FindContainedEquipment match {
+ case (container, Some(tool : Tool)) =>
+ (container, Some(tool))
+ case _ =>
+ (None, None)
}
}
/**
* Runs `FindContainedWeapon` but ignores the `Container` object output.
- * @return an `Equipment` object
+ * @return a `Tool` object
*/
- def FindWeapon : Option[Equipment] = FindContainedWeapon._2
+ def FindWeapon : Option[Tool] = FindContainedWeapon._2
/**
* Within a specified `Container`, find the smallest number of `AmmoBox` objects of a certain type of `Ammo`
diff --git a/pslogin/src/main/scala/services/local/support/DoorCloseActor.scala b/pslogin/src/main/scala/services/local/support/DoorCloseActor.scala
index 10413da4..f079b1a5 100644
--- a/pslogin/src/main/scala/services/local/support/DoorCloseActor.scala
+++ b/pslogin/src/main/scala/services/local/support/DoorCloseActor.scala
@@ -6,6 +6,7 @@ import net.psforever.objects.DefaultCancellable
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
+import net.psforever.types.Vector3
import scala.annotation.tailrec
import scala.concurrent.duration._
@@ -33,15 +34,23 @@ class DoorCloseActor() extends Actor {
case DoorCloseActor.TryCloseDoors() =>
doorCloserTrigger.cancel
val now : Long = System.nanoTime
- val (doorsToClose, doorsLeftOpen) = PartitionEntries(openDoors, now)
- openDoors = doorsLeftOpen
- doorsToClose.foreach(entry => {
- entry.door.Open = false //permissible break from synchronization
+ val (doorsToClose1, doorsLeftOpen1) = PartitionEntries(openDoors, now)
+ val (doorsToClose2, doorsLeftOpen2) = doorsToClose1.partition(entry => {
+ entry.door.Open match {
+ case Some(player) =>
+ Vector3.MagnitudeSquared(entry.door.Position - player.Position) > 15
+ case None =>
+ true
+ }
+ })
+ openDoors = (doorsLeftOpen1 ++ doorsLeftOpen2).sortBy(_.time)
+ doorsToClose2.foreach(entry => {
+ entry.door.Open = None //permissible break from synchronization
context.parent ! DoorCloseActor.CloseTheDoor(entry.door.GUID, entry.zone.Id) //call up to the main event system
})
- if(doorsLeftOpen.nonEmpty) {
- val short_timeout : FiniteDuration = math.max(1, DoorCloseActor.timeout_time - (now - doorsLeftOpen.head.time)) nanoseconds
+ if(openDoors.nonEmpty) {
+ val short_timeout : FiniteDuration = math.max(1, DoorCloseActor.timeout_time - (now - openDoors.head.time)) nanoseconds
import scala.concurrent.ExecutionContext.Implicits.global
doorCloserTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, DoorCloseActor.TryCloseDoors())
}