From f4b913a5d9f3e6005dc80924b5cba4d8369af48a Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 24 Jan 2018 10:07:42 -0500 Subject: [PATCH] Vector3 normal vector functions; locked door opening from inside mechanics; correction to item/equipment identification in WSA; quick solution to stop door from closing on player who opened it if that player is still standing within sqrt(15)m from it --- .../objects/serverobject/doors/Door.scala | 65 ++++++++- .../scala/net/psforever/types/Vector3.scala | 97 ++++++++++++- common/src/test/scala/Vector3Test.scala | 132 +++++++++++++++++- common/src/test/scala/objects/DoorTest.scala | 58 ++++++-- .../objects/ServerObjectBuilderTest.scala | 33 ++++- pslogin/src/main/scala/Maps.scala | 14 +- .../src/main/scala/WorldSessionActor.scala | 63 +++++---- .../local/support/DoorCloseActor.scala | 21 ++- 8 files changed, 414 insertions(+), 69 deletions(-) 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..6176ee9c 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.9688f, 2608.0625f, 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()) }