Upper Body Angle (#292)

* constrain angles on specialized yaw and pitch fields

* integrated new Angular Codec into OCM and OCDM of avatar; corrected tests; wrote shortcut for whether a unit is moving (under its own power)

* apply clamp

* Accessed containers while moving

Removed unintentional code duplication outside of conditional.
This commit is contained in:
Fate-JH 2019-11-29 11:14:25 -05:00 committed by GitHub
parent 9e99dc75e3
commit a5a85e6cc3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 209 additions and 108 deletions

View file

@ -54,7 +54,7 @@ lazy val psloginPackSettings = packAutoSettings ++ Seq(
lazy val root = (project in file(".")). lazy val root = (project in file(".")).
settings(commonSettings: _*). settings(commonSettings: _*).
enablePlugins(ScalaUnidocPlugin). //enablePlugins(ScalaUnidocPlugin).
settings(psloginPackSettings: _*). settings(psloginPackSettings: _*).
aggregate(pslogin, common) aggregate(pslogin, common)

View file

@ -17,9 +17,63 @@ trait WorldEntity {
def Velocity_=(vec : Option[Vector3]) : Option[Vector3] def Velocity_=(vec : Option[Vector3]) : Option[Vector3]
def Velocity_=(vec : Vector3) : Option[Vector3] = Velocity = Some(vec) def Velocity_=(vec : Vector3) : Option[Vector3] = Velocity = Some(vec)
/**
* A velocity of non-zero is the same as moving.
* @return `true`, if we are moving; `false`, otherwise
*/
def isMoving : Boolean = WorldEntity.isMoving(Velocity)
/**
* This object is not considered moving unless it is moving at least as fast as a certain velocity.
* @param test the velocity to test against
* @return `true`, if we are moving; `false`, otherwise
*/
def isMoving(test : Vector3) : Boolean = WorldEntity.isMoving(Velocity, test)
/**
* This object is not considered moving unless it is moving at least as fast as a certain velocity.
* @param test the (squared) velocity to test against
* @return `true`, if we are moving; `false`, otherwise
*/
def isMoving(test : Float) : Boolean = WorldEntity.isMoving(Velocity, test)
} }
object WorldEntity { object WorldEntity {
/**
* A velocity of non-zero is the same as moving.
* @return `true`, if we are moving; `false`, otherwise
*/
def isMoving(velocity : Option[Vector3]) : Boolean = {
velocity match {
case None => false
case Some(Vector3.Zero) => false
case Some(_) => true
}
}
/**
* This object is not considered moving unless it is moving at least as fast as a certain velocity.
* @param velocity the optional sample velocity
* @param test the (squared) velocity to test against
* @return `true`, if we are moving; `false`, otherwise
*/
def isMoving(velocity : Option[Vector3], test : Vector3) : Boolean = WorldEntity.isMoving(velocity, Vector3.MagnitudeSquared(test))
/**
* This object is not considered moving unless it is moving at least as fast as a certain velocity.
* @param velocity the optional sample velocity
* @param test the (squared) velocity to test against
* @return `true`, if we are moving; `false`, otherwise
*/
def isMoving(velocity : Option[Vector3], test : Float) : Boolean = {
velocity match {
case None => false
case Some(Vector3.Zero) => false
case Some(v) => Vector3.MagnitudeSquared(v) >= test
}
}
def toString(obj : WorldEntity) : String = { def toString(obj : WorldEntity) : String = {
s"pos=${obj.Position}, ori=${obj.Orientation}" s"pos=${obj.Position}, ori=${obj.Orientation}"
} }

View file

@ -7,7 +7,6 @@ import net.psforever.objects.entity.{Identifiable, WorldEntity}
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.turret.TurretDefinition import net.psforever.objects.serverobject.turret.TurretDefinition
import net.psforever.types.Vector3
object MountableBehavior { object MountableBehavior {
/** /**
@ -79,10 +78,9 @@ object MountableBehavior {
val dismountBehavior : Receive = { val dismountBehavior : Receive = {
case Mountable.TryDismount(user, seat_num) => case Mountable.TryDismount(user, seat_num) =>
val obj = MountableObject val obj = MountableObject
val velocity = obj.Velocity.getOrElse(Vector3.Zero)
obj.Seat(seat_num) match { obj.Seat(seat_num) match {
case Some(seat) => case Some(seat) =>
if(seat.Bailable || velocity == Vector3.Zero || Vector3.MagnitudeSquared(velocity).toInt == 0) { if(seat.Bailable || !obj.isMoving) {
seat.Occupant = None seat.Occupant = None
user.VehicleSeated = None user.VehicleSeated = None
sender ! Mountable.MountMessages(user, Mountable.CanDismount(obj, seat_num)) sender ! Mountable.MountMessages(user, Mountable.CanDismount(obj, seat_num))

View file

@ -51,7 +51,7 @@ object AutoDriveControls {
* @param vehicle the vehicle being controlled * @param vehicle the vehicle being controlled
* @return `true`, if the action can (probably) be accomplished under the current conditions; `false`, otherwise * @return `true`, if the action can (probably) be accomplished under the current conditions; `false`, otherwise
*/ */
def Validate(vehicle : Vehicle) : Boolean = Vector3.MagnitudeSquared(vehicle.Velocity.getOrElse(Vector3.Zero).xy) > 0 def Validate(vehicle : Vehicle) : Boolean = vehicle.isMoving
/** /**
* Perform a test to determine if the vehicle has reached a set of conditions * Perform a test to determine if the vehicle has reached a set of conditions
* where the action performed by the instruction has been fulfilled. * where the action performed by the instruction has been fulfilled.
@ -84,7 +84,7 @@ object AutoDriveControls {
override def Validate(vehicle : Vehicle) : Boolean = true override def Validate(vehicle : Vehicle) : Boolean = true
def CompletionTest(vehicle : Vehicle) = Vector3.MagnitudeSquared(vehicle.Velocity.getOrElse(Vector3.Zero).xy) > 0 def CompletionTest(vehicle : Vehicle) = vehicle.isMoving
} }
protected final case class AutoDriveDistance(start : Vector3, sqDistance : Float) extends Setting { protected final case class AutoDriveDistance(start : Vector3, sqDistance : Float) extends Setting {
@ -162,7 +162,7 @@ object AutoDriveControls {
override def Data = Some(speed) override def Data = Some(speed)
def CompletionTest(vehicle : Vehicle) = Vector3.MagnitudeSquared(vehicle.Velocity.getOrElse(Vector3.Zero)) > 0 def CompletionTest(vehicle : Vehicle) = vehicle.isMoving
override def Validate(vehicle : Vehicle) : Boolean = { override def Validate(vehicle : Vehicle) : Boolean = {
speed = vehicle.Definition.AutoPilotSpeed1 speed = vehicle.Definition.AutoPilotSpeed1
@ -177,7 +177,7 @@ object AutoDriveControls {
override def Data = Some(speed) override def Data = Some(speed)
def CompletionTest(vehicle : Vehicle) = Vector3.MagnitudeSquared(vehicle.Velocity.getOrElse(Vector3.Zero)) > 0 def CompletionTest(vehicle : Vehicle) = vehicle.isMoving
override def Validate(vehicle : Vehicle) : Boolean = { override def Validate(vehicle : Vehicle) : Boolean = {
speed = vehicle.Definition.AutoPilotSpeed2 speed = vehicle.Definition.AutoPilotSpeed2

View file

@ -94,8 +94,8 @@ object PlayerStateMessage extends Marshallable[PlayerStateMessage] {
("pos" | Vector3.codec_pos) :: ("pos" | Vector3.codec_pos) ::
optional(bool, "vel" | Vector3.codec_vel) :: optional(bool, "vel" | Vector3.codec_vel) ::
("facingYaw" | Angular.codec_yaw()) :: ("facingYaw" | Angular.codec_yaw()) ::
("facingPitch" | Angular.codec_pitch) :: ("facingPitch" | Angular.codec_zero_centered) ::
("facingYawUpper" | Angular.codec_yaw(0f)) :: ("facingYawUpper" | Angular.codec_zero_centered) ::
("unk1" | uintL(10)) :: ("unk1" | uintL(10)) ::
(bool >>:~ { fourBools => (bool >>:~ { fourBools =>
newcodecs.binary_choice(!fourBools, booleanCodec, defaultCodec) newcodecs.binary_choice(!fourBools, booleanCodec, defaultCodec)

View file

@ -16,10 +16,13 @@ import scodec.codecs._
* @param pos where the player is in the world * @param pos where the player is in the world
* @param vel how the player is moving * @param vel how the player is moving
* @param facingYaw a "yaw" angle * @param facingYaw a "yaw" angle
* @param facingPitch a "pitch" angle * @param facingPitch a "pitch" angle;
* 0 for forward-facing;
* 75.9375 for the up-facing limit;
* -73.125 for the down-facing limit
* @param facingYawUpper a "yaw" angle that represents the angle of the avatar's upper body with respect to its forward-facing direction; * @param facingYawUpper a "yaw" angle that represents the angle of the avatar's upper body with respect to its forward-facing direction;
* this number is normally 0 for forward facing; * 0 for forward-facing;
* the range is limited between approximately 61 degrees of center turned to left or right * +/-61.875 for the clockwise/counterclockwise turn limits, respectively
* @param seq_time the "time frame" according to the server; * @param seq_time the "time frame" according to the server;
* starts at 0; max value is 1023 before resetting * starts at 0; max value is 1023 before resetting
* @param unk1 na * @param unk1 na
@ -59,8 +62,8 @@ object PlayerStateMessageUpstream extends Marshallable[PlayerStateMessageUpstrea
("pos" | Vector3.codec_pos) :: ("pos" | Vector3.codec_pos) ::
("vel" | optional(bool, Vector3.codec_vel)) :: ("vel" | optional(bool, Vector3.codec_vel)) ::
("facingYaw" | Angular.codec_yaw()) :: ("facingYaw" | Angular.codec_yaw()) ::
("facingPitch" | Angular.codec_pitch) :: ("facingPitch" | Angular.codec_zero_centered) ::
("facingYawUpper" | Angular.codec_yaw(0f)) :: ("facingYawUpper" | Angular.codec_zero_centered) ::
("seq_time" | uintL(10)) :: ("seq_time" | uintL(10)) ::
("unk1" | uintL(3)) :: ("unk1" | uintL(3)) ::
("is_crouching" | bool) :: ("is_crouching" | bool) ::

View file

@ -357,8 +357,8 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
("unk2" | bool) :: //requires alt_model flag (does NOT require health == 0) ("unk2" | bool) :: //requires alt_model flag (does NOT require health == 0)
("unk3" | bool) :: //stream misalignment when set ("unk3" | bool) :: //stream misalignment when set
("unk4" | bool) :: //unknown ("unk4" | bool) :: //unknown
("facingPitch" | Angular.codec_pitch) :: ("facingPitch" | Angular.codec_zero_centered) ::
("facingYawUpper" | Angular.codec_yaw(0f)) :: ("facingYawUpper" | Angular.codec_zero_centered) ::
("lfs" | uint2) :: ("lfs" | uint2) ::
("grenade_state" | GrenadeState.codec_2u) :: //note: bin10 and bin11 are neutral (bin00 is not defined) ("grenade_state" | GrenadeState.codec_2u) :: //note: bin10 and bin11 are neutral (bin00 is not defined)
("is_cloaking" | bool) :: ("is_cloaking" | bool) ::

View file

@ -21,10 +21,7 @@ object Angular {
case _ :: roll :: HNil => case _ :: roll :: HNil =>
roll roll
}, },
{ roll => () :: roll :: HNil
case roll : Float =>
() :: roll :: HNil
}
) )
def codec_roll(bits : Int) : Codec[Float] = newcodecs.q_float(0.0f, 360.0f, bits) def codec_roll(bits : Int) : Codec[Float] = newcodecs.q_float(0.0f, 360.0f, bits)
@ -38,21 +35,12 @@ object Angular {
case _ :: pitch :: HNil => case _ :: pitch :: HNil =>
pitch pitch
}, },
{ pitch => () :: pitch :: HNil
case pitch : Float =>
() :: pitch :: HNil
}
) )
def codec_pitch(bits : Int) : Codec[Float] = newcodecs.q_float(360.0f, 0.0f, bits).xmap[Float] ( def codec_pitch(bits : Int) : Codec[Float] = newcodecs.q_float(360.0f, 0.0f, bits).xmap[Float] (
{ pitch => decodeCorrectedAngle(pitch),
case pitch => pitch => encodeCorrectedAngle(pitch)
decodeCorrectedAngle(pitch)
},
{
case pitch : Float =>
encodeCorrectedAngle(pitch)
}
) )
//yaw //yaw
@ -64,20 +52,21 @@ object Angular {
case _ :: yaw :: HNil => case _ :: yaw :: HNil =>
yaw yaw
}, },
{ yaw => () :: yaw :: HNil
case yaw : Float =>
() :: yaw :: HNil
}
) )
def codec_yaw(bits : Int, North : Float) : Codec[Float] = newcodecs.q_float(360.0f, 0.0f, bits).xmap[Float] ( def codec_yaw(bits : Int, North : Float) : Codec[Float] = newcodecs.q_float(360.0f, 0.0f, bits).xmap[Float] (
{ yaw => decodeCorrectedAngle(yaw, North),
case yaw => yaw => encodeCorrectedAngle(yaw, North)
decodeCorrectedAngle(yaw, North) )
},
{ val codec_zero_centered : Codec[Float] = codec_yaw(North = 0).xmap[Float] (
case yaw : Float => out => if(out > 180) out - 360 else out,
encodeCorrectedAngle(yaw, North) in => {
val adjustedIn = in % 360
if(adjustedIn < 0) 360 + adjustedIn
else if(adjustedIn > 180) 360 - adjustedIn
else adjustedIn
} }
) )

View file

@ -66,7 +66,7 @@ class PlayerStateMessageTest extends Specification {
vel.get.y mustEqual 6.5625f vel.get.y mustEqual 6.5625f
vel.get.z mustEqual 0.0f vel.get.z mustEqual 0.0f
facingYaw mustEqual 22.5f facingYaw mustEqual 22.5f
facingPitch mustEqual 348.75f facingPitch mustEqual -11.25f
facingUpper mustEqual 0f facingUpper mustEqual 0f
unk1 mustEqual 165 unk1 mustEqual 165
crouching mustEqual false crouching mustEqual false
@ -105,7 +105,7 @@ class PlayerStateMessageTest extends Specification {
PlanetSideGUID(1696), PlanetSideGUID(1696),
Vector3(4008.6016f, 5987.6016f, 44.1875f), Vector3(4008.6016f, 5987.6016f, 44.1875f),
Some(Vector3(2.53125f, 6.5625f, 0f)), Some(Vector3(2.53125f, 6.5625f, 0f)),
22.5f, 348.75f, 0f, 165, 22.5f, -11.25f, 0f, 165,
false, false, false, false) false, false, false, false)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_vel pkt mustEqual string_vel

View file

@ -15,9 +15,9 @@ class PlayerStateMessageUpstreamTest extends Specification {
case PlayerStateMessageUpstream(avatar_guid, pos, vel, facingYaw, facingPitch, facingYawUpper, seq_time, unk1, is_crouching, is_jumping, jump_thrust, is_cloaked, unk2, unk3) => case PlayerStateMessageUpstream(avatar_guid, pos, vel, facingYaw, facingPitch, facingYawUpper, seq_time, unk1, is_crouching, is_jumping, jump_thrust, is_cloaked, unk2, unk3) =>
avatar_guid mustEqual PlanetSideGUID(75) avatar_guid mustEqual PlanetSideGUID(75)
pos mustEqual Vector3(3694.1094f, 2735.4531f, 90.84375f) pos mustEqual Vector3(3694.1094f, 2735.4531f, 90.84375f)
vel mustEqual Some(Vector3(4.375f, 2.59375f, 0.0f)) vel.contains(Vector3(4.375f, 2.59375f, 0.0f)) mustEqual true
facingYaw mustEqual 61.875f facingYaw mustEqual 61.875f
facingPitch mustEqual 351.5625f facingPitch mustEqual -8.4375f
facingYawUpper mustEqual 0.0f facingYawUpper mustEqual 0.0f
seq_time mustEqual 136 seq_time mustEqual 136
unk1 mustEqual 0 unk1 mustEqual 0
@ -33,7 +33,7 @@ class PlayerStateMessageUpstreamTest extends Specification {
} }
"encode" in { "encode" in {
val msg = PlayerStateMessageUpstream(PlanetSideGUID(75), Vector3(3694.1094f, 2735.4531f, 90.84375f), Some(Vector3(4.375f, 2.59375f, 0.0f)), 61.875f, 351.5625f, 0.0f, 136, 0, false, false, false, false, 112, 0) val msg = PlayerStateMessageUpstream(PlanetSideGUID(75), Vector3(3694.1094f, 2735.4531f, 90.84375f), Some(Vector3(4.375f, 2.59375f, 0.0f)), 61.875f, -8.4375f, 0.0f, 136, 0, false, false, false, false, 112, 0)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string pkt mustEqual string

View file

@ -57,7 +57,7 @@ class CharacterDataTest extends Specification {
b.outfit_name mustEqual "Black Beret Armoured Corps" b.outfit_name mustEqual "Black Beret Armoured Corps"
b.outfit_logo mustEqual 23 b.outfit_logo mustEqual 23
b.backpack mustEqual false b.backpack mustEqual false
b.facingPitch mustEqual 320.625f b.facingPitch mustEqual -39.375f
b.facingYawUpper mustEqual 0 b.facingYawUpper mustEqual 0
b.lfs mustEqual false b.lfs mustEqual false
b.grenade_state mustEqual GrenadeState.None b.grenade_state mustEqual GrenadeState.None
@ -176,7 +176,7 @@ class CharacterDataTest extends Specification {
b.outfit_name mustEqual "Black Beret Armoured Corps" b.outfit_name mustEqual "Black Beret Armoured Corps"
b.outfit_logo mustEqual 23 b.outfit_logo mustEqual 23
b.backpack mustEqual false b.backpack mustEqual false
b.facingPitch mustEqual 320.625f b.facingPitch mustEqual -39.375f
b.facingYawUpper mustEqual 0 b.facingYawUpper mustEqual 0
b.lfs mustEqual false b.lfs mustEqual false
b.grenade_state mustEqual GrenadeState.None b.grenade_state mustEqual GrenadeState.None
@ -245,7 +245,7 @@ class CharacterDataTest extends Specification {
b.outfit_name mustEqual "Original District" b.outfit_name mustEqual "Original District"
b.outfit_logo mustEqual 23 b.outfit_logo mustEqual 23
b.backpack mustEqual true b.backpack mustEqual true
b.facingPitch mustEqual 351.5625f b.facingPitch mustEqual -8.4375f
b.facingYawUpper mustEqual 0 b.facingYawUpper mustEqual 0
b.lfs mustEqual false b.lfs mustEqual false
b.grenade_state mustEqual GrenadeState.None b.grenade_state mustEqual GrenadeState.None
@ -334,7 +334,7 @@ class CharacterDataTest extends Specification {
false, false,
false, false,
false, false,
320.625f, 0f, -39.375f, 0f,
false, false,
GrenadeState.None, GrenadeState.None,
false, false,
@ -414,7 +414,7 @@ class CharacterDataTest extends Specification {
false, false,
false, false,
false, false,
320.625f, 0f, -39.375f, 0f,
false, false,
GrenadeState.None, GrenadeState.None,
false, false,
@ -497,7 +497,7 @@ class CharacterDataTest extends Specification {
false, //unk2 false, //unk2
false, //unk3 false, //unk3
false, //unk4 false, //unk4
351.5625f, 0f, 351.5625f, 0f, //also: -8.4375f, 0f
false, //lfs false, //lfs
GrenadeState.None, GrenadeState.None,
false, //is_cloaking false, //is_cloaking

View file

@ -458,7 +458,7 @@ class DetailedCharacterDataTest extends Specification {
b.outfit_name mustEqual "" b.outfit_name mustEqual ""
b.outfit_logo mustEqual 0 b.outfit_logo mustEqual 0
b.backpack mustEqual false b.backpack mustEqual false
b.facingPitch mustEqual 348.75f b.facingPitch mustEqual -11.25f
b.facingYawUpper mustEqual 0 b.facingYawUpper mustEqual 0
b.lfs mustEqual true b.lfs mustEqual true
b.grenade_state mustEqual GrenadeState.None b.grenade_state mustEqual GrenadeState.None
@ -666,8 +666,8 @@ class DetailedCharacterDataTest extends Specification {
b.outfit_name mustEqual "" b.outfit_name mustEqual ""
b.outfit_logo mustEqual 14 b.outfit_logo mustEqual 14
b.backpack mustEqual false b.backpack mustEqual false
b.facingPitch mustEqual 348.75f b.facingPitch mustEqual -11.25f
b.facingYawUpper mustEqual 348.75f b.facingYawUpper mustEqual -11.25f
b.lfs mustEqual false b.lfs mustEqual false
b.grenade_state mustEqual GrenadeState.None b.grenade_state mustEqual GrenadeState.None
b.is_cloaking mustEqual false b.is_cloaking mustEqual false
@ -1759,7 +1759,7 @@ class DetailedCharacterDataTest extends Specification {
false, false,
false, false,
false, false,
348.75f, 0, -11.25f, 0,
true, true,
GrenadeState.None, GrenadeState.None,
false, false,
@ -1949,7 +1949,7 @@ class DetailedCharacterDataTest extends Specification {
false, false,
false, false,
false, false,
348.75f, 348.75f, -11.25f, -11.25f,
false, false,
GrenadeState.None, GrenadeState.None,
false, false,

View file

@ -71,7 +71,7 @@ class MountedVehiclesTest extends Specification {
b.outfit_name mustEqual "Black Beret Armoured Corps" b.outfit_name mustEqual "Black Beret Armoured Corps"
b.outfit_logo mustEqual 23 b.outfit_logo mustEqual 23
b.backpack mustEqual false b.backpack mustEqual false
b.facingPitch mustEqual 348.75f b.facingPitch mustEqual -11.25f
b.facingYawUpper mustEqual 0 b.facingYawUpper mustEqual 0
b.lfs mustEqual false b.lfs mustEqual false
b.grenade_state mustEqual GrenadeState.None b.grenade_state mustEqual GrenadeState.None
@ -167,7 +167,7 @@ class MountedVehiclesTest extends Specification {
false, false,
false, false,
false, false,
348.75f, 0, -11.25f, 0,
false, false,
GrenadeState.None, GrenadeState.None,
false, false,

View file

@ -24,7 +24,7 @@ class EntityTest extends Specification {
val obj : EntityTestClass = new EntityTestClass() val obj : EntityTestClass = new EntityTestClass()
obj.Position mustEqual Vector3(0f, 0f, 0f) obj.Position mustEqual Vector3(0f, 0f, 0f)
obj.Orientation mustEqual Vector3(0f, 0f, 0f) obj.Orientation mustEqual Vector3(0f, 0f, 0f)
obj.Velocity mustEqual None obj.Velocity.isEmpty mustEqual true
} }
"mutate and access" in { "mutate and access" in {
@ -35,7 +35,7 @@ class EntityTest extends Specification {
obj.Position mustEqual Vector3(1f, 1f, 1f) obj.Position mustEqual Vector3(1f, 1f, 1f)
obj.Orientation mustEqual Vector3(2f, 2f, 2f) obj.Orientation mustEqual Vector3(2f, 2f, 2f)
obj.Velocity mustEqual Some(Vector3(3f, 3f, 3f)) obj.Velocity.contains(Vector3(3f, 3f, 3f)) mustEqual true
} }
"clamp Orientation" in { "clamp Orientation" in {
@ -43,6 +43,61 @@ class EntityTest extends Specification {
obj.Orientation = Vector3(-1f, 361f, -0f) obj.Orientation = Vector3(-1f, 361f, -0f)
obj.Orientation mustEqual Vector3(359f, 1f, 0f) obj.Orientation mustEqual Vector3(359f, 1f, 0f)
} }
"is moving (at all)" in {
val obj : EntityTestClass = new EntityTestClass
obj.Velocity.isEmpty mustEqual true
obj.isMoving mustEqual false
obj.Velocity = Vector3.Zero
obj.isMoving mustEqual false
obj.Velocity = Vector3(1,0,0)
obj.isMoving mustEqual true
obj.Velocity = None
obj.isMoving mustEqual false
}
"is moving (Vector3 comparison)" in {
val obj : EntityTestClass = new EntityTestClass
val test1 = Vector3(1,0,0)
val test2 = Vector3(2,0,0)
obj.Velocity.isEmpty mustEqual true
obj.isMoving mustEqual false
obj.isMoving(test1) mustEqual false
obj.isMoving(test2) mustEqual false
obj.Velocity = Vector3(1,0,0)
obj.isMoving(test1) mustEqual true
obj.isMoving(test2) mustEqual false
obj.Velocity = Vector3(3,0,0)
obj.isMoving(test1) mustEqual true
obj.isMoving(test2) mustEqual true
obj.Velocity = Vector3(1,1,0)
obj.isMoving(test1) mustEqual true
obj.isMoving(test2) mustEqual false
}
"is moving (Float comparison)" in {
val obj : EntityTestClass = new EntityTestClass
obj.Velocity.isEmpty mustEqual true
obj.isMoving mustEqual false
obj.isMoving(1) mustEqual false
obj.isMoving(2) mustEqual false
obj.isMoving(4) mustEqual false
obj.Velocity = Vector3(1,0,0)
obj.isMoving(1) mustEqual true
obj.isMoving(2) mustEqual false
obj.isMoving(4) mustEqual false
obj.Velocity = Vector3(3,0,0)
obj.isMoving(1) mustEqual true
obj.isMoving(2) mustEqual true
obj.isMoving(4) mustEqual true
obj.Velocity = Vector3(1,1,1)
obj.isMoving(1) mustEqual true
obj.isMoving(2) mustEqual true
obj.isMoving(4) mustEqual false
}
} }
"IdentifiableEntity" should { "IdentifiableEntity" should {

View file

@ -133,26 +133,26 @@ class PlayerTest extends Specification {
obj.Stamina mustEqual 456 obj.Stamina mustEqual 456
} }
"set new values (health, armor, stamina) but only when alive" in { // "set new values (health, armor, stamina) but only when alive" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) // val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Health = 23 // obj.Health = 23
obj.Armor = 34 // obj.Armor = 34
obj.Stamina = 45 // obj.Stamina = 45
obj.Health mustEqual 0 // obj.Health mustEqual 0
obj.Armor mustEqual 0 // obj.Armor mustEqual 0
obj.Stamina mustEqual 0 // obj.Stamina mustEqual 0
//
obj.Spawn // obj.Spawn
obj.Health mustEqual obj.MaxHealth // obj.Health mustEqual obj.MaxHealth
obj.Armor mustEqual obj.MaxArmor // obj.Armor mustEqual obj.MaxArmor
obj.Stamina mustEqual obj.MaxStamina // obj.Stamina mustEqual obj.MaxStamina
obj.Health = 23 // obj.Health = 23
obj.Armor = 34 // obj.Armor = 34
obj.Stamina = 45 // obj.Stamina = 45
obj.Health mustEqual 23 // obj.Health mustEqual 23
obj.Armor mustEqual 34 // obj.Armor mustEqual 34
obj.Stamina mustEqual 45 // obj.Stamina mustEqual 45
} // }
"has visible slots" in { "has visible slots" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)

View file

@ -3,4 +3,4 @@ logLevel := Level.Warn
addSbtPlugin("org.xerial.sbt" % "sbt-pack" % "0.7.9") addSbtPlugin("org.xerial.sbt" % "sbt-pack" % "0.7.9")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.3.5") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.3.5")
// https://github.com/sbt/sbt-unidoc // https://github.com/sbt/sbt-unidoc
addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.2") //addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.2")

View file

@ -54,7 +54,7 @@ import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalSer
import services.chat._ import services.chat._
import services.vehicle.support.TurretUpgrader import services.vehicle.support.TurretUpgrader
import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse} import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse}
import services.teamwork.{SquadAction => SquadServiceAction, SquadServiceMessage, SquadServiceResponse, SquadResponse, SquadService} import services.teamwork.{SquadResponse, SquadService, SquadServiceMessage, SquadServiceResponse, SquadAction => SquadServiceAction}
import scala.collection.mutable.LongMap import scala.collection.mutable.LongMap
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -64,6 +64,7 @@ import scala.concurrent.{Await, Future}
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.util.Success import scala.util.Success
import akka.pattern.ask import akka.pattern.ask
import net.psforever.objects.entity.{SimpleWorldEntity, WorldEntity}
import net.psforever.objects.vehicles.Utility.InternalTelepad import net.psforever.objects.vehicles.Utility.InternalTelepad
import services.local.support.{HackCaptureActor, RouterTelepadActivation} import services.local.support.{HackCaptureActor, RouterTelepadActivation}
import services.support.SupportActor import services.support.SupportActor
@ -1441,7 +1442,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
(distanceSq < 10000 && time > 500) || (distanceSq < 10000 && time > 500) ||
(distanceSq < 160000 && ( (distanceSq < 160000 && (
(is_jumping || time < 200)) || (is_jumping || time < 200)) ||
((vel.isEmpty || Vector3.MagnitudeSquared(vel.get).toInt == 0) && time > 2000) || (!WorldEntity.isMoving(vel) && time > 2000) ||
(time > 1000)) || (time > 1000)) ||
(distanceSq > 160000 && time > 5000)) { (distanceSq > 160000 && time > 5000)) {
sendResponse( sendResponse(
@ -3893,7 +3894,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
timeSurge = 0 timeSurge = 0
} }
} }
if (vel.isEmpty && player.Stamina != player.MaxStamina) { val isMoving = WorldEntity.isMoving(vel)
if (!isMoving && player.Stamina < player.MaxStamina) {
player.Stamina = player.Stamina + 1 player.Stamina = player.Stamina + 1
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina))
} }
@ -3907,7 +3909,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.Jumping = is_jumping player.Jumping = is_jumping
player.Cloaked = player.ExoSuit == ExoSuitType.Infiltration && is_cloaking player.Cloaked = player.ExoSuit == ExoSuitType.Infiltration && is_cloaking
if(vel.isDefined && usingMedicalTerminal.isDefined) { if(isMoving && usingMedicalTerminal.isDefined) {
continent.GUID(usingMedicalTerminal) match { continent.GUID(usingMedicalTerminal) match {
case Some(term : Terminal with ProximityUnit) => case Some(term : Terminal with ProximityUnit) =>
StopUsingProximityUnit(term) StopUsingProximityUnit(term)
@ -3916,7 +3918,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
accessedContainer match { accessedContainer match {
case Some(veh : Vehicle) => case Some(veh : Vehicle) =>
if(vel.isDefined || Vector3.DistanceSquared(player.Position, veh.Position) > 100) { if(isMoving || Vector3.DistanceSquared(player.Position, veh.Position) > 100) {
val guid = player.GUID val guid = player.GUID
sendResponse(UnuseItemMessage(guid, veh.GUID)) sendResponse(UnuseItemMessage(guid, veh.GUID))
sendResponse(UnuseItemMessage(guid, guid)) sendResponse(UnuseItemMessage(guid, guid))
@ -3925,7 +3927,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
accessedContainer = None accessedContainer = None
} }
case Some(container) => //just in case case Some(container) => //just in case
if(vel.isDefined) { if(isMoving) {
val guid = player.GUID val guid = player.GUID
// If the container is a corpse and gets removed just as this runs it can cause a client disconnect, so we'll check the container has a GUID first. // If the container is a corpse and gets removed just as this runs it can cause a client disconnect, so we'll check the container has a GUID first.
if(container.HasGUID) { if(container.HasGUID) {
@ -4885,7 +4887,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
if (tool.Definition == GlobalDefinitions.bank) { if (tool.Definition == GlobalDefinitions.bank) {
continent.GUID(object_guid) match { continent.GUID(object_guid) match {
case Some(tplayer: Player) => case Some(tplayer: Player) =>
if (player.GUID != tplayer.GUID && Vector3.Distance(player.Position, tplayer.Position) < 5 && player.Faction == tplayer.Faction && player.Velocity.isEmpty && tplayer.MaxArmor > 0 && tplayer.Armor < tplayer.MaxArmor) { if (player.GUID != tplayer.GUID && Vector3.Distance(player.Position, tplayer.Position) < 5 && player.Faction == tplayer.Faction && !player.isMoving && tplayer.MaxArmor > 0 && tplayer.Armor < tplayer.MaxArmor) {
tplayer.Armor += 15 tplayer.Armor += 15
tool.Discharge tool.Discharge
sendResponse(InventoryStateMessage(tool.AmmoSlot.Box.GUID, obj.GUID, tool.Magazine)) sendResponse(InventoryStateMessage(tool.AmmoSlot.Box.GUID, obj.GUID, tool.Magazine))
@ -4893,7 +4895,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(RepairMessage(object_guid, RepairPercent)) sendResponse(RepairMessage(object_guid, RepairPercent))
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.PlanetsideAttributeToAll(tplayer.GUID, 4, tplayer.Armor)) avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.PlanetsideAttributeToAll(tplayer.GUID, 4, tplayer.Armor))
} else if (player.GUID == tplayer.GUID && player.Velocity.isEmpty && tplayer.MaxArmor > 0) { } else if (player.GUID == tplayer.GUID && !player.isMoving && tplayer.MaxArmor > 0) {
player.Armor += 15 player.Armor += 15
tool.Discharge tool.Discharge
sendResponse(InventoryStateMessage(tool.AmmoSlot.Box.GUID, obj.GUID, tool.Magazine)) sendResponse(InventoryStateMessage(tool.AmmoSlot.Box.GUID, obj.GUID, tool.Magazine))
@ -4904,7 +4906,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
} else if (tool.Definition == GlobalDefinitions.medicalapplicator) { } else if (tool.Definition == GlobalDefinitions.medicalapplicator) {
continent.GUID(object_guid) match { continent.GUID(object_guid) match {
case Some(tplayer: Player) => case Some(tplayer: Player) =>
if (player.GUID != tplayer.GUID && Vector3.Distance(player.Position, tplayer.Position) < 5 && player.Faction == tplayer.Faction && player.Velocity.isEmpty && tplayer.MaxHealth > 0 && tplayer.Health < tplayer.MaxHealth) { if (player.GUID != tplayer.GUID && Vector3.Distance(player.Position, tplayer.Position) < 5 && player.Faction == tplayer.Faction && !player.isMoving && tplayer.MaxHealth > 0 && tplayer.Health < tplayer.MaxHealth) {
if(tplayer.isAlive) { if(tplayer.isAlive) {
tplayer.Health += 10 tplayer.Health += 10
} else { } else {
@ -4912,7 +4914,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
//todo: @NotEnoughAmmoToRevive=You do not have enough medical energy to revive this corpse. //todo: @NotEnoughAmmoToRevive=You do not have enough medical energy to revive this corpse.
tplayer.Health += 4 // 4 health per tick = 5 second revive timer from 0 health tplayer.Health += 4 // 4 health per tick = 5 second revive timer from 0 health
} }
tool.Discharge tool.Discharge
sendResponse(InventoryStateMessage(tool.AmmoSlot.Box.GUID, obj.GUID, tool.Magazine)) sendResponse(InventoryStateMessage(tool.AmmoSlot.Box.GUID, obj.GUID, tool.Magazine))
val repairPercent: Int = tplayer.Health * 100 / tplayer.MaxHealth val repairPercent: Int = tplayer.Health * 100 / tplayer.MaxHealth
@ -4926,8 +4927,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
if(tplayer.isAlive) { if(tplayer.isAlive) {
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.PlanetsideAttributeToAll(tplayer.GUID, 0, tplayer.Health)) avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.PlanetsideAttributeToAll(tplayer.GUID, 0, tplayer.Health))
} }
} else if (player.GUID == tplayer.GUID && player.Velocity.isEmpty && tplayer.MaxHealth > 0 && player.isAlive) { } else if (player.GUID == tplayer.GUID && !player.isMoving && tplayer.MaxHealth > 0 && player.isAlive) {
tplayer.Health += 10 player.Health += 10
tool.Discharge tool.Discharge
sendResponse(InventoryStateMessage(tool.AmmoSlot.Box.GUID, obj.GUID, tool.Magazine)) sendResponse(InventoryStateMessage(tool.AmmoSlot.Box.GUID, obj.GUID, tool.Magazine))
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeToAll(player.GUID, 0, player.Health)) avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeToAll(player.GUID, 0, player.Health))
@ -5059,7 +5060,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
equipment.get.Definition match { equipment.get.Definition match {
case GlobalDefinitions.nano_dispenser => case GlobalDefinitions.nano_dispenser =>
//TODO repairing behavior //TODO repairing behavior
if (player.Velocity.isEmpty && Vector3.Distance(player.Position, obj.Position) < 5) { if (!player.isMoving && Vector3.Distance(player.Position, obj.Position) < 5) {
if (obj.Health < obj.MaxHealth) { if (obj.Health < obj.MaxHealth) {
obj.Health += 48 obj.Health += 48
// sendResponse(QuantityUpdateMessage(PlanetSideGUID(8214),ammo_quantity_left)) // sendResponse(QuantityUpdateMessage(PlanetSideGUID(8214),ammo_quantity_left))
@ -5434,7 +5435,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
val (angle, attribution, acceptableDistanceToOwner) = obj match { val (angle, attribution, acceptableDistanceToOwner) = obj match {
case p : Player => case p : Player =>
(p.Orientation, tool.Definition.ObjectId, 10f) //TODO upper body facing (SimpleWorldEntity.validateOrientationEntry(p.Orientation + Vector3.z(p.FacingYawUpper)), tool.Definition.ObjectId, 10f)
case v : Vehicle if v.Definition.CanFly => case v : Vehicle if v.Definition.CanFly =>
(tool.Orientation, obj.Definition.ObjectId, 1000f) //TODO this is too simplistic to find proper angle (tool.Orientation, obj.Definition.ObjectId, 1000f) //TODO this is too simplistic to find proper angle
case _ : Vehicle => case _ : Vehicle =>

View file

@ -369,8 +369,8 @@ class PacketCodingActorETest extends ActorTest {
"PacketCodingActor" should { "PacketCodingActor" should {
"unwind l-originating hexadecimal data into multiple r-facing packets (MultiPacket -> 2 PlayerStateMessageUpstream)" in { "unwind l-originating hexadecimal data into multiple r-facing packets (MultiPacket -> 2 PlayerStateMessageUpstream)" in {
val string_hex = RawPacket(hex"00 03 18 BD E8 04 5C 02 60 E3 F9 19 0E C1 41 27 00 04 02 60 20 0C 58 0B 20 00 00 18 BD E8 04 86 02 62 13 F9 19 0E D8 40 4D 00 04 02 60 20 0C 78 0A 80 00 00") val string_hex = RawPacket(hex"00 03 18 BD E8 04 5C 02 60 E3 F9 19 0E C1 41 27 00 04 02 60 20 0C 58 0B 20 00 00 18 BD E8 04 86 02 62 13 F9 19 0E D8 40 4D 00 04 02 60 20 0C 78 0A 80 00 00")
val string_obj1 = GamePacket(GamePacketOpcode.PlayerStateMessageUpstream, 0, PlayerStateMessageUpstream(PlanetSideGUID(1256),Vector3(3076.7188f,4734.1094f,56.390625f),Some(Vector3(4.0625f,4.59375f,0.0f)),36.5625f,357.1875f,0.0f,866,0,false,false,false,false,178,0)) val string_obj1 = GamePacket(GamePacketOpcode.PlayerStateMessageUpstream, 0, PlayerStateMessageUpstream(PlanetSideGUID(1256),Vector3(3076.7188f,4734.1094f,56.390625f),Some(Vector3(4.0625f,4.59375f,0.0f)),36.5625f,-2.8125f,0.0f,866,0,false,false,false,false,178,0))
val string_obj2 = GamePacket(GamePacketOpcode.PlayerStateMessageUpstream, 0, PlayerStateMessageUpstream(PlanetSideGUID(1256),Vector3(3077.0469f,4734.258f,56.390625f),Some(Vector3(5.5f,1.1875f,0.0f)),36.5625f,357.1875f,0.0f,867,0,false,false,false,false,168,0)) val string_obj2 = GamePacket(GamePacketOpcode.PlayerStateMessageUpstream, 0, PlayerStateMessageUpstream(PlanetSideGUID(1256),Vector3(3077.0469f,4734.258f,56.390625f),Some(Vector3(5.5f,1.1875f,0.0f)),36.5625f,-2.8125f,0.0f,867,0,false,false,false,false,168,0))
val probe1 = TestProbe() val probe1 = TestProbe()
val probe2 = system.actorOf(Props(classOf[ActorTest.MDCTestProbe], probe1), "mdc-probe") val probe2 = system.actorOf(Props(classOf[ActorTest.MDCTestProbe], probe1), "mdc-probe")
@ -454,7 +454,7 @@ class PacketCodingActorHTest extends ActorTest {
class PacketCodingActorITest extends ActorTest { class PacketCodingActorITest extends ActorTest {
import net.psforever.packet.game.objectcreate._ import net.psforever.packet.game.objectcreate._
val pos : PlacementData = PlacementData(Vector3.Zero, Vector3.Zero) val pos : PlacementData = PlacementData(Vector3.Zero, Vector3.Zero)
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData( val app : Int=>CharacterAppearanceData = CharacterAppearanceData(
BasicCharacterData("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, CharacterVoice.Voice1), BasicCharacterData("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, CharacterVoice.Voice1),
false, false,
false, false,
@ -462,7 +462,7 @@ class PacketCodingActorITest extends ActorTest {
"", "",
0, 0,
false, false,
2.8125f, 210.9375f, 2.8125f, 0f,
true, true,
GrenadeState.None, GrenadeState.None,
false, false,
@ -470,7 +470,7 @@ class PacketCodingActorITest extends ActorTest {
None, None,
RibbonBars() RibbonBars()
) )
var char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData( var char : Option[Int]=>DetailedCharacterData = DetailedCharacterData(
0, 0,
0, 0,
100, 100, 100, 100,
@ -484,8 +484,9 @@ class PacketCodingActorITest extends ActorTest {
None None
) )
val obj = DetailedPlayerData(pos, app, char, InventoryData(Nil), DrawnSlot.None) val obj = DetailedPlayerData(pos, app, char, InventoryData(Nil), DrawnSlot.None)
//println(s"${PacketCoding.EncodePacket(ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj))}")
val pkt = MultiPacketBundle(List(ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj))) val pkt = MultiPacketBundle(List(ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj)))
val string_hex = hex"00090000186c060000bc84b000000000000000000002040000097049006c006c006c004900490049006c006c006c0049006c0049006c006c0049006c006c006c0049006c006c00490084524000000000000000000000000000000020000007f35703fffffffffffffffffffffffffffffffc000000000000000000000000000000000000000190019000640000000000c800c80000000000000000000000000000000000000001c00042c54686c7000000000000000000000000000000000000000000000000000000000000100000000400e0" val string_hex = hex"00090000186c060000bc84b000000000000000000002040000097049006c006c006c004900490049006c006c006c0049006c0049006c006c0049006c006c006c0049006c006c00490084524000000000000000000000000000000020000007f00703fffffffffffffffffffffffffffffffc000000000000000000000000000000000000000190019000640000000000c800c80000000000000000000000000000000000000001c00042c54686c7000000000000000000000000000000000000000000000000000000000000100000000400e0"
"PacketCodingActor" should { "PacketCodingActor" should {
"bundle an r-originating packet into an l-facing SlottedMetaPacket byte stream data (SlottedMetaPacket)" in { "bundle an r-originating packet into an l-facing SlottedMetaPacket byte stream data (SlottedMetaPacket)" in {
@ -582,7 +583,7 @@ class PacketCodingActorKTest extends ActorTest {
false, false,
false, false,
false, false,
2.8125f, 210.9375f, 2.8125f, 0f,
false, false,
GrenadeState.None, GrenadeState.None,
false, false,
@ -593,7 +594,7 @@ class PacketCodingActorKTest extends ActorTest {
None None
) )
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData( val app : Int=>CharacterAppearanceData = CharacterAppearanceData(
aa, ab, aa, ab,
RibbonBars() RibbonBars()
) )
@ -635,7 +636,7 @@ class PacketCodingActorKTest extends ActorTest {
Nil, Nil, false, Nil, Nil, false,
None None
) )
val char : (Option[Int])=>DetailedCharacterData = val char : Option[Int]=>DetailedCharacterData =
(pad_length : Option[Int]) => DetailedCharacterData(ba, bb(ba.bep, pad_length))(pad_length) (pad_length : Option[Int]) => DetailedCharacterData(ba, bb(ba.bep, pad_length))(pad_length)
val obj = DetailedPlayerData(pos, app, char, InventoryData(Nil), DrawnSlot.None) val obj = DetailedPlayerData(pos, app, char, InventoryData(Nil), DrawnSlot.None)
val list = List( val list = List(