diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 3d1bbc0f..14c8fbfe 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -12,6 +12,7 @@ import net.psforever.objects.serverobject.locks.IFFLockDefinition import net.psforever.objects.serverobject.mblocker.LockerDefinition import net.psforever.objects.serverobject.pad.VehicleSpawnPadDefinition import net.psforever.objects.serverobject.terminals._ +import net.psforever.objects.serverobject.tube.SpawnTubeDefinition import net.psforever.objects.vehicles.{SeatArmorRestriction, UtilityType} import net.psforever.types.PlanetSideEmpire @@ -486,6 +487,10 @@ object GlobalDefinitions { */ val order_terminal = new OrderTerminalDefinition + val ams_respawn_tube = new SpawnTubeDefinition(49) { Name = "ams_respawn_tube" } + + val matrix_terminalc = new MatrixTerminalDefinition(519) + val order_terminala = new OrderTerminalABDefinition(613) val order_terminalb = new OrderTerminalABDefinition(614) @@ -2347,6 +2352,8 @@ object GlobalDefinitions { ams.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax ams.MountPoints += 1 -> 0 ams.MountPoints += 2 -> 0 + ams.Utilities += 1 -> UtilityType.matrix_terminalc + ams.Utilities += 2 -> UtilityType.ams_respawn_tube ams.Utilities += 3 -> UtilityType.order_terminala ams.Utilities += 4 -> UtilityType.order_terminalb ams.Deployment = true diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/SpawnTubeConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/SpawnTubeConverter.scala new file mode 100644 index 00000000..56671f98 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/definition/converter/SpawnTubeConverter.scala @@ -0,0 +1,11 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.definition.converter + +import net.psforever.objects.serverobject.tube.SpawnTube +import net.psforever.packet.game.objectcreate.CommonTerminalData + +import scala.util.{Success, Try} + +class SpawnTubeConverter extends ObjectCreateConverter[SpawnTube]() { + override def ConstructorData(obj : SpawnTube) : Try[CommonTerminalData] = { Success(CommonTerminalData(obj.Faction)) } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/MatrixTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/MatrixTerminalDefinition.scala new file mode 100644 index 00000000..047e786d --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/MatrixTerminalDefinition.scala @@ -0,0 +1,42 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.terminals + +import akka.actor.ActorContext +import net.psforever.objects.Player +import net.psforever.objects.serverobject.structures.Amenity +import net.psforever.packet.game.ItemTransactionMessage + +/** + * The definition for any `Terminal` that is of a type "matrix_terminal". + */ +class MatrixTerminalDefinition(object_id : Int) extends TerminalDefinition(object_id) { + Name = if(object_id == 517) { + "matrix_terminala" + } + else if(object_id == 518) { + "matrix_terminalb" + } + else if(object_id == 519) { + "matrix_terminalc" + } + else { + throw new IllegalArgumentException("terminal must be object id 517-519") + } + + override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal() +} + +object MatrixTerminalDefinition { + /** + * Assemble some logic for a provided object. + * @param obj an `Amenity` object; + * anticipating a `Terminal` object using this same definition + * @param context hook to the local `Actor` system + */ + def Setup(obj : Amenity, context : ActorContext) : Unit = { + import akka.actor.{ActorRef, Props} + if(obj.Actor == ActorRef.noSender) { + obj.Actor = context.actorOf(Props(classOf[TerminalControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}") + } + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTube.scala b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTube.scala new file mode 100644 index 00000000..9d9baac9 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTube.scala @@ -0,0 +1,34 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.tube + +import net.psforever.objects.definition.ObjectDefinition +import net.psforever.objects.serverobject.structures.Amenity + +class SpawnTube(tubeDef : ObjectDefinition) extends Amenity { + def Definition : ObjectDefinition = tubeDef +} + +object SpawnTube { + def apply(tubeDef : ObjectDefinition) : SpawnTube = { + new SpawnTube(tubeDef) + } + +// import akka.actor.ActorContext +// import net.psforever.types.Vector3 +// /** +// * Instantiate an configure a `SpawnTube` object +// * @param pos the position (used to determine spawn point) +// * @param orient the orientation (used to indicate spawn direction) +// * @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 `SpawnTube` object +// */ +// def Constructor(pos : Vector3, orient : Vector3)(id : Int, context : ActorContext) : SpawnTube = { +// import net.psforever.objects.GlobalDefinitions +// +// val obj = SpawnTube(GlobalDefinitions.ams_respawn_tube) +// obj.Position = pos +// obj.Orientation = orient +// obj +// } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala new file mode 100644 index 00000000..5b47ffe4 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala @@ -0,0 +1,17 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.tube + +import akka.actor.Actor +import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} + +/** + * An `Actor` that handles messages being dispatched to a specific `SpawnTube`. + * @param tube the `SpawnTube` object being governed + */ +class SpawnTubeControl(tube : SpawnTube) extends Actor with FactionAffinityBehavior.Check { + def FactionObject : FactionAffinity = tube + + def receive : Receive = checkBehavior.orElse { case _ =>; } + + override def toString : String = tube.Definition.Name +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala new file mode 100644 index 00000000..774449c0 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala @@ -0,0 +1,26 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.tube + +import akka.actor.ActorContext +import net.psforever.objects.definition.ObjectDefinition +import net.psforever.objects.definition.converter.SpawnTubeConverter +import net.psforever.objects.serverobject.structures.Amenity + +class SpawnTubeDefinition(object_id : Int) extends ObjectDefinition(object_id) { + Packet = new SpawnTubeConverter +} + +object SpawnTubeDefinition { + /** + * Assemble some logic for a provided object. + * @param obj an `Amenity` object; + * anticipating a `Terminal` object using this same definition + * @param context hook to the local `Actor` system + */ + def Setup(obj : Amenity, context : ActorContext) : Unit = { + import akka.actor.{ActorRef, Props} + if(obj.Actor == ActorRef.noSender) { + obj.Actor = context.actorOf(Props(classOf[SpawnTubeControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}") + } + } +} diff --git a/common/src/main/scala/net/psforever/objects/vehicles/Utility.scala b/common/src/main/scala/net/psforever/objects/vehicles/Utility.scala index 5259c325..dc7dfe27 100644 --- a/common/src/main/scala/net/psforever/objects/vehicles/Utility.scala +++ b/common/src/main/scala/net/psforever/objects/vehicles/Utility.scala @@ -4,7 +4,8 @@ package net.psforever.objects.vehicles import akka.actor.ActorContext import net.psforever.objects.{GlobalDefinitions, Vehicle} import net.psforever.objects.serverobject.structures.Amenity -import net.psforever.objects.serverobject.terminals.{OrderTerminalABDefinition, Terminal} +import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, OrderTerminalABDefinition, Terminal, TerminalDefinition} +import net.psforever.objects.serverobject.tube.{SpawnTube, SpawnTubeDefinition} /** * An `Enumeration` of the available vehicular utilities.
@@ -17,6 +18,8 @@ import net.psforever.objects.serverobject.terminals.{OrderTerminalABDefinition, object UtilityType extends Enumeration { type Type = Value val + ams_respawn_tube, + matrix_terminalc, order_terminala, order_terminalb = Value @@ -83,10 +86,32 @@ object Utility { * @return the `Amenity` object */ private def BuildUtilityFunc(util : UtilityType.Value) : Amenity = util match { + case UtilityType.ams_respawn_tube => + new SpawnTubeUtility(GlobalDefinitions.ams_respawn_tube) + case UtilityType.matrix_terminalc => + new TerminalUtility(GlobalDefinitions.matrix_terminalc) case UtilityType.order_terminala => - Terminal(GlobalDefinitions.order_terminala) + new TerminalUtility(GlobalDefinitions.order_terminala) case UtilityType.order_terminalb => - Terminal(GlobalDefinitions.order_terminalb) + new TerminalUtility(GlobalDefinitions.order_terminalb) + } + + /** + * Override for `SpawnTube` objects so that they inherit the spatial characteristics of their `Owner`. + * @param tubeDef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields + */ + private class SpawnTubeUtility(tubeDef : SpawnTubeDefinition) extends SpawnTube(tubeDef) { + override def Position = Owner.Position + override def Orientation = Owner.Orientation + } + + /** + * Override for `Terminal` objects so that they inherit the spatial characteristics of their `Owner`. + * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields + */ + private class TerminalUtility(tdef : TerminalDefinition) extends Terminal(tdef) { + override def Position = Owner.Position + override def Orientation = Owner.Orientation } /** @@ -95,6 +120,10 @@ object Utility { * @return the `Amenity` object */ private def SelectUtilitySetupFunc(util : UtilityType.Value) : UtilLogic = util match { + case UtilityType.ams_respawn_tube => + SpawnTubeDefinition.Setup + case UtilityType.matrix_terminalc => + MatrixTerminalDefinition.Setup case UtilityType.order_terminala => OrderTerminalABDefinition.Setup case UtilityType.order_terminalb => diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index 431aca9f..21ae82f6 100644 --- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -332,7 +332,7 @@ object GamePacketOpcode extends Enumeration { case 0x09 => game.HitMessage.decode case 0x0a => game.HitHint.decode case 0x0b => noDecoder(DamageMessage) - case 0x0c => noDecoder(DestroyMessage) + case 0x0c => game.DestroyMessage.decode case 0x0d => game.ReloadMessage.decode case 0x0e => game.MountVehicleMsg.decode case 0x0f => game.DismountVehicleMsg.decode @@ -406,7 +406,7 @@ object GamePacketOpcode extends Enumeration { // 0x48 case 0x48 => game.TimeOfDayMessage.decode case 0x49 => noDecoder(UnknownMessage73) - case 0x4a => noDecoder(SpawnRequestMessage) + case 0x4a => game.SpawnRequestMessage.decode case 0x4b => game.DeployRequestMessage.decode case 0x4c => noDecoder(UnknownMessage76) case 0x4d => game.RepairMessage.decode diff --git a/common/src/main/scala/net/psforever/packet/game/AvatarDeadStateMessage.scala b/common/src/main/scala/net/psforever/packet/game/AvatarDeadStateMessage.scala index 57b3b685..4b7ad326 100644 --- a/common/src/main/scala/net/psforever/packet/game/AvatarDeadStateMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/AvatarDeadStateMessage.scala @@ -1,23 +1,36 @@ // Copyright (c) 2017 PSForever package net.psforever.packet.game -import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} +import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} import net.psforever.types.Vector3 import scodec.Codec import scodec.codecs._ +object DeadState extends Enumeration { + type Type = Value + + val + Nothing, + Dead, + Release, + RespawnTime + = Value + + implicit val codec = PacketHelpers.createEnumerationCodec(this, uintL(3)) +} + /** * na - * @param unk1 0 = nothing, 1 = waiting for a rez, 2 = auto map to select spawn, 3 = respawn time - * @param unk2 na - * @param unk3 spawn penality - * @param pos last victim's position + * @param state avatar's relationship with the world + * @param timer_max total length of respawn countdown, in milliseconds + * @param timer initial length of the respawn timer, in milliseconds + * @param pos last position * @param unk4 na * @param unk5 na */ -final case class AvatarDeadStateMessage(unk1 : Int, - unk2 : Long, - unk3 : Long, +final case class AvatarDeadStateMessage(state : DeadState.Value, + timer_max : Long, + timer : Long, pos : Vector3, unk4 : Long, unk5 : Boolean) @@ -29,9 +42,9 @@ final case class AvatarDeadStateMessage(unk1 : Int, object AvatarDeadStateMessage extends Marshallable[AvatarDeadStateMessage] { implicit val codec : Codec[AvatarDeadStateMessage] = ( - ("unk1" | uintL(3)) :: - ("unk2" | uint32L) :: - ("unk3" | uint32L) :: + ("state" | DeadState.codec) :: + ("timer_max" | uint32L) :: + ("timer" | uint32L) :: ("pos" | Vector3.codec_pos) :: ("unk4" | uint32L) :: ("unk5" | bool) diff --git a/common/src/main/scala/net/psforever/packet/game/DestroyMessage.scala b/common/src/main/scala/net/psforever/packet/game/DestroyMessage.scala new file mode 100644 index 00000000..fdf6021b --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/DestroyMessage.scala @@ -0,0 +1,26 @@ +// Copyright (c) 2017 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} +import net.psforever.types.Vector3 +import scodec.Codec +import scodec.codecs._ + +final case class DestroyMessage(unk1 : PlanetSideGUID, + unk2 : PlanetSideGUID, + unk3 : PlanetSideGUID, + pos : Vector3) + extends PlanetSideGamePacket { + type Packet = DestroyMessage + def opcode = GamePacketOpcode.DestroyMessage + def encode = DestroyMessage.encode(this) +} + +object DestroyMessage extends Marshallable[DestroyMessage] { + implicit val codec : Codec[DestroyMessage] = ( + ("unk1" | PlanetSideGUID.codec) :: + ("unk2" | PlanetSideGUID.codec) :: + ("unk3" | PlanetSideGUID.codec) :: + ("pos" | Vector3.codec_pos) + ).as[DestroyMessage] +} diff --git a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala index 036bd5ff..5d0dc875 100644 --- a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala @@ -6,6 +6,37 @@ import scodec.Codec import scodec.codecs._ /** + * na
+ * Global:
+ * `50 - Common Initialization?`
+ * `51 - Common Initialization?`
+ * `67 - ???`
+ *
+ * Global (GUID=0)
+ * `82 - ???` + * `83 - max boomers`
+ * `84 - max he mines`
+ * `85 - max disruptor mines`
+ * `86 - max spitfire turrets`
+ * `87 - max motion sensors`
+ * `88 - max shadow turrets`
+ * `89 - max cerebus turrets`
+ * `90 - max Aegis shield generators`
+ * `91 - max TRAPs`
+ * `92 - max OMFTs`
+ * `93 - max sensor disruptors`
+ * `94 - boomers`
+ * `95 - he mines`
+ * `96 - disruptor mines`
+ * `97 - spitfire turrets`
+ * `98 - motion sensors`
+ * `99 - shadow turrets`
+ * `100 - cerebus turrets`
+ * `101 - Aegis shield generators`
+ * `102 - TRAPSs`
+ * `103 - OMFTs`
+ * `104 - sensor disruptors`
+ *
* Players/General:
* Server to client :
* `0 - health`
@@ -74,31 +105,33 @@ import scodec.codecs._ * `36 - CR. Value is the CR`
* `43 - Info on avatar name : 0 = Nothing, 1 = "(LD)" message`
* `53 - LFS. Value is 1 to flag LFS`
- * `54 - Player "Aura". Values are : 0 for nothing, 1 for plasma, 2 for ancient, 3 for plasma + ancient,
- * 4 for LLU?, 5 for plasma + LLU?, 6 for ancient + LLU?, 7 for plasma + ancient + LLU?, 8 for fire,
- * 9 for plasma + fire, 10 for ancient + fire, 11 for plasma + ancient + fire,
- * 12 for LLU? + fire, 13 for plasma + LLU? + fire, 14 for ancient + LLU? + fire,
- * 15 for plasma + ancient + LLU? + fire,`
+ * `54 - Player "Aura". Values can be expressed in the first byte's lower nibble:`
+ * - 0 is nothing
+ * - 1 is plasma
+ * - 2 is ancient
+ * - 4 is LLU (?)
+ * - 8 is fire
+ * -- e.g., 13 = 8 + 4 + 1 = fire and LLU and plasma
* `55 - "Someone is attempting to Heal you". Value is 1`
* `56 - "Someone is attempting to Repair you". Value is 1`
* `73 - "You are locked into the Core Beam. Charging your Module now.". Value is 1 to active`
* `77 - Cavern Facility Captures. Value is the number of captures`
* `78 - Cavern Kills. Value is the number of kills`
- * `106 - Custom Head` + * `106 - Custom Head`
* Client to Server :
* `106 - Custom Head`
*
- * Vehicles:
- * 0 - Vehicle base health
- * 10 - Driver seat permissions (0 = Locked, 1 = Group, 3 = Empire)
- * 11 - Gunner seat(s) permissions (same)
- * 12 - Passenger seat(s) permissions (same)
- * 13 - Trunk permissions (same)
- * 21 - Asserts first time event eligibility / makes owner if no owner is assigned
- * 22 - Toggles gunner and passenger mount points (1 = hides, 0 = reveals; this also locks their permissions)
- * 68 - ???
- * 80 - Damage vehicle (unknown value)
- * 113 - ??? + * `Vehicles:`
+ * `10 - Driver seat permissions (0 = Locked, 1 = Group, 3 = Empire)`
+ * `11 - Gunner seat(s) permissions (same)`
+ * `12 - Passenger seat(s) permissions (same)`
+ * `13 - Trunk permissions (same)`
+ * `21 - Asserts first time event eligibility / makes owner if no owner is assigned`
+ * `22 - Toggles gunner and passenger mount points (1 = hides, 0 = reveals; this also locks their permissions)`
+ * `68 - ???`
+ * `80 - Damage vehicle (unknown value)`
+ * `81 - ???`
+ * `113 - ???` * @param player_guid the player * @param attribute_type na * @param attribute_value na diff --git a/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala b/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala new file mode 100644 index 00000000..cbb35a95 --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala @@ -0,0 +1,28 @@ +// Copyright (c) 2017 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} +import scodec.Codec +import scodec.codecs._ + + +final case class SpawnRequestMessage(unk1 : Int, + unk2 : Long, + unk3 : Int, + unk4 : Int, + unk5 : Int) + extends PlanetSideGamePacket { + type Packet = SpawnRequestMessage + def opcode = GamePacketOpcode.SpawnRequestMessage + def encode = SpawnRequestMessage.encode(this) +} + +object SpawnRequestMessage extends Marshallable[SpawnRequestMessage] { + implicit val codec : Codec[SpawnRequestMessage] = ( + ("unk1" | uint16L) :: + ("unk2" | uint32L) :: + ("unk3" | uint16L) :: + ("unk4" | uint16L) :: + ("unk5" | uintL(10)) + ).as[SpawnRequestMessage] +} diff --git a/common/src/test/scala/game/AvatarDeadStateMessageTest.scala b/common/src/test/scala/game/AvatarDeadStateMessageTest.scala index 69e31d24..ef874727 100644 --- a/common/src/test/scala/game/AvatarDeadStateMessageTest.scala +++ b/common/src/test/scala/game/AvatarDeadStateMessageTest.scala @@ -13,7 +13,7 @@ class AvatarDeadStateMessageTest extends Specification { "decode" in { PacketCoding.DecodePacket(string).require match { case AvatarDeadStateMessage(unk1,unk2,unk3,pos,unk4,unk5) => - unk1 mustEqual 1 + unk1 mustEqual DeadState.Dead unk2 mustEqual 300000 unk3 mustEqual 300000 pos mustEqual Vector3(6552.617f,4602.375f,60.90625f) @@ -25,7 +25,7 @@ class AvatarDeadStateMessageTest extends Specification { } "encode" in { - val msg = AvatarDeadStateMessage(1, 300000, 300000, Vector3(6552.617f,4602.375f,60.90625f), 2, true) + val msg = AvatarDeadStateMessage(DeadState.Dead, 300000, 300000, Vector3(6552.617f,4602.375f,60.90625f), 2, true) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string diff --git a/common/src/test/scala/game/DestroyMessageTest.scala b/common/src/test/scala/game/DestroyMessageTest.scala new file mode 100644 index 00000000..ac5314ae --- /dev/null +++ b/common/src/test/scala/game/DestroyMessageTest.scala @@ -0,0 +1,33 @@ +// Copyright (c) 2017 PSForever +package game + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.game._ +import net.psforever.types.Vector3 +import scodec.bits._ + +class DestroyMessageTest extends Specification { + val string = hex"0C 74 09 74 09 00 00 06 35 3C FF D7 26 08" + + "DestroyMessage" should { + "decode" in { + PacketCoding.DecodePacket(string).require match { + case DestroyMessage(unk1, unk2, unk3, pos) => + unk1 mustEqual PlanetSideGUID(2420) + unk2 mustEqual PlanetSideGUID(2420) + unk3 mustEqual PlanetSideGUID(0) + pos mustEqual Vector3(1642.0469f, 4091.6172f, 32.59375f) + case _ => + ko + } + } + + "encode" in { + val msg = DestroyMessage(PlanetSideGUID(2420), PlanetSideGUID(2420), PlanetSideGUID(0), Vector3(1642.0469f, 4091.6172f, 32.59375f)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string + } + } +} diff --git a/common/src/test/scala/game/SpawnRequestMessageTest.scala b/common/src/test/scala/game/SpawnRequestMessageTest.scala new file mode 100644 index 00000000..adab70f2 --- /dev/null +++ b/common/src/test/scala/game/SpawnRequestMessageTest.scala @@ -0,0 +1,31 @@ +// Copyright (c) 2017 PSForever +package game + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.game._ +import scodec.bits._ + +class SpawnRequestMessageTest extends Specification { + val string = hex"4a000007000000000000000200" + + "decode" in { + PacketCoding.DecodePacket(string).require match { + case SpawnRequestMessage(unk1,unk2,unk3,unk4,unk5) => + unk1 mustEqual 0 + unk2 mustEqual 7 + unk3 mustEqual 0 + unk4 mustEqual 0 + unk5 mustEqual 2 + case _ => + ko + } + } + + "encode" in { + val msg = SpawnRequestMessage(0, 7, 0, 0, 2) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string + } +} diff --git a/common/src/test/scala/objects/ConverterTest.scala b/common/src/test/scala/objects/ConverterTest.scala index 723c248f..57d8fc86 100644 --- a/common/src/test/scala/objects/ConverterTest.scala +++ b/common/src/test/scala/objects/ConverterTest.scala @@ -1,7 +1,6 @@ // Copyright (c) 2017 PSForever package objects -import net.psforever.objects.GlobalDefinitions.remote_electronics_kit import net.psforever.objects.definition.converter.{ACEConverter, CharacterSelectConverter, REKConverter} import net.psforever.objects._ import net.psforever.objects.definition._ @@ -9,6 +8,7 @@ import net.psforever.objects.equipment.CItem.{DeployedItem, Unit} import net.psforever.objects.equipment._ import net.psforever.objects.inventory.InventoryTile import net.psforever.objects.serverobject.terminals.Terminal +import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.objectcreate._ import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3} @@ -184,13 +184,13 @@ class ConverterTest extends Specification { "convert to packet (BR < 24)" in { obj.BEP = 0 obj.Definition.Packet.DetailedConstructorData(obj) match { - case Success(pkt) => + case Success(_) => ok case _ => ko } obj.Definition.Packet.ConstructorData(obj) match { - case Success(pkt) => + case Success(_) => ok case _ => ko @@ -200,13 +200,13 @@ class ConverterTest extends Specification { "convert to packet (BR >= 24)" in { obj.BEP = 10000000 obj.Definition.Packet.DetailedConstructorData(obj) match { - case Success(pkt) => + case Success(_) => ok case _ => ko } obj.Definition.Packet.ConstructorData(obj) match { - case Success(pkt) => + case Success(_) => ok case _ => ko @@ -216,7 +216,7 @@ class ConverterTest extends Specification { "convert to simple packet (BR < 24)" in { obj.BEP = 0 converter.DetailedConstructorData(obj) match { - case Success(pkt) => + case Success(_) => ok case _ => ko @@ -228,7 +228,7 @@ class ConverterTest extends Specification { "convert to simple packet (BR >= 24)" in { obj.BEP = 10000000 converter.DetailedConstructorData(obj) match { - case Success(pkt) => + case Success(_) => ok case _ => ko @@ -290,7 +290,27 @@ class ConverterTest extends Specification { obj.Definition.Packet.ConstructorData(obj) match { case Success(pkt) => - pkt mustEqual CommonTerminalData(PlanetSideEmpire.NEUTRAL, 0) + pkt mustEqual CommonTerminalData(PlanetSideEmpire.NEUTRAL) + case _ => + ko + } + } + } + + "Spawn Tube" should { + "convert to packet" in { + val obj = SpawnTube(GlobalDefinitions.ams_respawn_tube) + + obj.Definition.Packet.DetailedConstructorData(obj) match { + case Failure(err) => + err.isInstanceOf[NoSuchMethodException] mustEqual true + case _ => + ko + } + + obj.Definition.Packet.ConstructorData(obj) match { + case Success(pkt) => + pkt mustEqual CommonTerminalData(PlanetSideEmpire.NEUTRAL) case _ => ko } @@ -338,8 +358,10 @@ class ConverterTest extends Specification { val ams = Vehicle(GlobalDefinitions.ams) ams.GUID = PlanetSideGUID(413) - ams.Utilities(3)().GUID = PlanetSideGUID(414) - ams.Utilities(4)().GUID = PlanetSideGUID(415) + ams.Utilities(1)().GUID = PlanetSideGUID(414) + ams.Utilities(2)().GUID = PlanetSideGUID(415) + ams.Utilities(3)().GUID = PlanetSideGUID(416) + ams.Utilities(4)().GUID = PlanetSideGUID(417) ams.Definition.Packet.ConstructorData(ams).isSuccess mustEqual true ok //TODO write more of this test diff --git a/common/src/test/scala/objects/UtilityTest.scala b/common/src/test/scala/objects/UtilityTest.scala index ef78af4b..97b46d75 100644 --- a/common/src/test/scala/objects/UtilityTest.scala +++ b/common/src/test/scala/objects/UtilityTest.scala @@ -27,6 +27,49 @@ class UtilityTest extends Specification { obj().asInstanceOf[Terminal].Definition.ObjectId mustEqual 614 obj().asInstanceOf[Terminal].Actor == ActorRef.noSender } + + "create a matrix_terminalc object" in { + val obj = Utility(UtilityType.matrix_terminalc, UtilityTest.vehicle) + obj.UtilType mustEqual UtilityType.matrix_terminalc + obj().isInstanceOf[Terminal] mustEqual true + obj().asInstanceOf[Terminal].Definition.ObjectId mustEqual 519 + obj().asInstanceOf[Terminal].Actor == ActorRef.noSender + } + + "create an ams_respawn_tube object" in { + import net.psforever.objects.serverobject.tube.SpawnTube + val obj = Utility(UtilityType.ams_respawn_tube, UtilityTest.vehicle) + obj.UtilType mustEqual UtilityType.ams_respawn_tube + obj().isInstanceOf[SpawnTube] mustEqual true + obj().asInstanceOf[SpawnTube].Definition.ObjectId mustEqual 49 + obj().asInstanceOf[SpawnTube].Actor == ActorRef.noSender + } + + "be located with their owner (terminal)" in { + val veh = Vehicle(GlobalDefinitions.quadstealth) + val obj = Utility(UtilityType.order_terminala, veh) + obj().Position mustEqual veh.Position + obj().Orientation mustEqual veh.Orientation + + import net.psforever.types.Vector3 + veh.Position = Vector3(1, 2, 3) + veh.Orientation = Vector3(4, 5, 6) + obj().Position mustEqual veh.Position + obj().Orientation mustEqual veh.Orientation + } + + "be located with their owner (spawn tube)" in { + val veh = Vehicle(GlobalDefinitions.quadstealth) + val obj = Utility(UtilityType.ams_respawn_tube, veh) + obj().Position mustEqual veh.Position + obj().Orientation mustEqual veh.Orientation + + import net.psforever.types.Vector3 + veh.Position = Vector3(1, 2, 3) + veh.Orientation = Vector3(4, 5, 6) + obj().Position mustEqual veh.Position + obj().Orientation mustEqual veh.Orientation + } } } @@ -58,6 +101,34 @@ class Utility2Test extends ActorTest() { } } +class Utility3Test extends ActorTest() { + "Utility" should { + "wire a matrix_terminalc Actor" in { + val obj = Utility(UtilityType.matrix_terminalc, UtilityTest.vehicle) + obj().GUID = PlanetSideGUID(1) + assert(obj().Actor == ActorRef.noSender) + + system.actorOf(Props(classOf[UtilityTest.SetupControl], obj), "test") ! "" + receiveOne(Duration.create(100, "ms")) //consume and discard + assert(obj().Actor != ActorRef.noSender) + } + } +} + +class Utility4Test extends ActorTest() { + "Utility" should { + "wire an ams_respawn_tube Actor" in { + val obj = Utility(UtilityType.ams_respawn_tube, UtilityTest.vehicle) + obj().GUID = PlanetSideGUID(1) + assert(obj().Actor == ActorRef.noSender) + + system.actorOf(Props(classOf[UtilityTest.SetupControl], obj), "test") ! "" + receiveOne(Duration.create(100, "ms")) //consume and discard + assert(obj().Actor != ActorRef.noSender) + } + } +} + object UtilityTest { val vehicle = Vehicle(GlobalDefinitions.quadstealth) diff --git a/common/src/test/scala/objects/terminal/MatrixTerminalTest.scala b/common/src/test/scala/objects/terminal/MatrixTerminalTest.scala new file mode 100644 index 00000000..a574c79e --- /dev/null +++ b/common/src/test/scala/objects/terminal/MatrixTerminalTest.scala @@ -0,0 +1,63 @@ +// Copyright (c) 2017 PSForever +package objects.terminal + +import akka.actor.ActorRef +import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, Terminal} +import net.psforever.objects.{GlobalDefinitions, Player, Vehicle} +import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} +import net.psforever.types._ +import org.specs2.mutable.Specification + +class MatrixTerminalTest extends Specification { + "MatrixTerminal" should { + "define (a)" in { + val a = new MatrixTerminalDefinition(517) + a.ObjectId mustEqual 517 + a.Name mustEqual "matrix_terminala" + } + + "define (b)" in { + val b = new MatrixTerminalDefinition(518) + b.ObjectId mustEqual 518 + b.Name mustEqual "matrix_terminalb" + } + + "define (b)" in { + val b = new MatrixTerminalDefinition(519) + b.ObjectId mustEqual 519 + b.Name mustEqual "matrix_terminalc" + } + + "define (invalid)" in { + var id : Int = (math.random * Int.MaxValue).toInt + if(id == 517) { + id += 3 + } + else if(id == 518) { + id += 2 + } + else if(id == 519) { + id += 1 + } + + new MatrixTerminalDefinition(id) must throwA[IllegalArgumentException] + } + } + + "Matrix_Terminal" should { + val terminal = Terminal(GlobalDefinitions.matrix_terminalc) + terminal.Owner = Vehicle(GlobalDefinitions.quadstealth) + terminal.Owner.Faction = PlanetSideEmpire.TR + + "construct" in { + terminal.Actor mustEqual ActorRef.noSender + } + + "player can not buy (anything)" in { + val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "lite_armor", 0, PlanetSideGUID(0)) + + terminal.Request(player, msg) mustEqual Terminal.NoDeal() + } + } +} diff --git a/pslogin/src/main/scala/Maps.scala b/pslogin/src/main/scala/Maps.scala index 71c81084..d8c854c8 100644 --- a/pslogin/src/main/scala/Maps.scala +++ b/pslogin/src/main/scala/Maps.scala @@ -22,7 +22,18 @@ object Maps { val map5 = new ZoneMap("map05") - val map6 = new ZoneMap("map06") + val map6 = new ZoneMap("map06") { + //TODO TEST ceryshen + LocalObject(ServerObjectBuilder(3353, Terminal.Constructor(ground_vehicle_terminal))) + LocalObject(ServerObjectBuilder(500, + VehicleSpawnPad.Constructor(Vector3(3962.0f, 4334.0f, 268.0f), Vector3(0f, 0f, 180.0f)) + )) //TODO guid not correct + + LocalBuilding(2, FoundationBuilder(Building.Structure)) + ObjectToBuilding(3353, 2) + ObjectToBuilding(500, 2) + TerminalToSpawnPad(3353, 500) + } val map7 = new ZoneMap("map07") diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index eade334d..a7c9d8f3 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -23,8 +23,9 @@ import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech import net.psforever.objects.serverobject.locks.IFFLock import net.psforever.objects.serverobject.mblocker.Locker import net.psforever.objects.serverobject.pad.VehicleSpawnPad -import net.psforever.objects.serverobject.terminals.Terminal -import net.psforever.objects.vehicles.{AccessPermissionGroup, VehicleLockState} +import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, Terminal} +import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage +import net.psforever.objects.vehicles.{AccessPermissionGroup, Utility, VehicleLockState} import net.psforever.objects.zones.{InterstellarCluster, Zone} import net.psforever.packet.game.objectcreate._ import net.psforever.types._ @@ -402,16 +403,17 @@ class WorldSessionActor extends Actor with MDCContextAware { val vehicle_guid = obj.GUID if(state == DriveState.Deploying) { log.info(s"DeployRequest: $obj transitioning to deploy state") - sendResponse(DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, obj.Position)) - vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, obj.Position)) + obj.Velocity = Some(Vector3.Zero) //no velocity + sendResponse(DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero)) + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero)) import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global context.system.scheduler.scheduleOnce(obj.DeployTime milliseconds, obj.Actor, Deployment.TryDeploy(DriveState.Deployed)) } else if(state == DriveState.Deployed) { log.info(s"DeployRequest: $obj has been Deployed") - sendResponse(DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, obj.Position)) - vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, obj.Position)) + sendResponse(DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero)) + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero)) DeploymentActivities(obj) //... } @@ -902,8 +904,9 @@ class WorldSessionActor extends Actor with MDCContextAware { continent.Transport ! Zone.SpawnVehicle(vehicle) vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.LoadVehicle(player_guid, vehicle, objedtId, vehicle_guid, vdata)) sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 1L)) //mount points off? - //sendResponse(PlanetsideAttributeMessage(vehicle_guid, 21, player_guid.guid))) //fte and ownership? - //sendResponse(ObjectAttachMessage(vehicle_guid, player_guid, 0))) + sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 1L)) //mount points off? + sendResponse(PlanetsideAttributeMessage(vehicle_guid, 21, player_guid.guid)) //fte and ownership? + //sendResponse(ObjectAttachMessage(vehicle_guid, player_guid, 0)) vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) //cancel queue timeout delay vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, continent, 21L) //temporary drive away from pad delay vehicle.Actor ! Mountable.TryMount(player, 0) @@ -980,13 +983,13 @@ class WorldSessionActor extends Actor with MDCContextAware { PlanetSideGUID(6), //Ceryshen PlanetSideGUID(2), //Anguta 8, //80% NTU - true, //Base hacked - PlanetSideEmpire.NC, //Base hacked by NC - 600000, //10 minutes remaining for hack + false, //Base hacked + PlanetSideEmpire.NEUTRAL, //Base hacked by NC + 0, //10 minutes remaining for hack PlanetSideEmpire.VS, //Base owned by VS 0, //!! Field != 0 will cause malformed packet. See class def. None, - PlanetSideGeneratorState.Critical, //Generator critical + PlanetSideGeneratorState.Normal, //Generator critical true, //Respawn tubes destroyed true, //Force dome active 16, //Tech plant lattice benefit @@ -1022,6 +1025,11 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(SetCurrentAvatarMessage(guid,0,0)) sendResponse(CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT)) sendResponse(ChatMsg(ChatMessageType.CMT_EXPANSIONS, true, "", "1 on", None)) //CC on + sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 82, 0)) + + (1 to 73).foreach( i => { + sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(i), 67, 0)) + }) case Zone.ItemFromGround(tplayer, item) => val obj_guid = item.GUID @@ -1234,13 +1242,18 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info("Reticulating splines ...") //map-specific initializations //TODO continent.ClientConfiguration() + sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 1)) + sendResponse(SetEmpireMessage(PlanetSideGUID(2), PlanetSideEmpire.VS)) //HART building C sendResponse(SetEmpireMessage(PlanetSideGUID(29), PlanetSideEmpire.NC)) //South Villa Gun Tower sendResponse(TimeOfDayMessage(1191182336)) sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list - //render Equipment that was dropped into zone before the player arrived + sendResponse(ZonePopulationUpdateMessage(PlanetSideGUID(6), 414, 138, 0, 138, 0, 138, 0, 138, 0)) + (1 to 255).foreach(i => { sendResponse(SetEmpireMessage(PlanetSideGUID(i), PlanetSideEmpire.VS)) }) + + //render Equipment that was dropped into zone before the player arrived continent.EquipmentOnGround.foreach(item => { val definition = item.Definition sendResponse( @@ -1387,14 +1400,31 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vector, unk1, unk2, unk3, unk4, time_alive) => //log.info("ProjectileState: " + msg) + case msg @ ReleaseAvatarRequestMessage() => + log.info(s"ReleaseAvatarRequest: ${player.GUID} on ${continent.Id} has released") + sendResponse(PlanetsideAttributeMessage(player.GUID, 6, 1)) + sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, 2, true)) + + case msg @ SpawnRequestMessage(u1, u2, u3, u4, u5) => + log.info(s"SpawnRequestMessage: $msg") + case msg @ ChatMsg(messagetype, has_wide_contents, recipient, contents, note_contents) => // TODO: Prevents log spam, but should be handled correctly if (messagetype != ChatMessageType.CMT_TOGGLE_GM) { log.info("Chat: " + msg) } + if(messagetype == ChatMessageType.CMT_SUICIDE) { + val player_guid = player.GUID + val pos = player.Position + sendResponse(PlanetsideAttributeMessage(player_guid, 0, 0)) + sendResponse(PlanetsideAttributeMessage(player_guid, 2, 0)) + sendResponse(DestroyMessage(player_guid, player_guid, PlanetSideGUID(0), pos)) + sendResponse(AvatarDeadStateMessage(DeadState.Dead, 300000, 300000, pos, 2, true)) + } + if (messagetype == ChatMessageType.CMT_VOICE) { - sendResponse(ChatMsg(ChatMessageType.CMT_VOICE, false, "IlllIIIlllIlIllIlllIllI", contents, None)) + sendResponse(ChatMsg(ChatMessageType.CMT_VOICE, false, player.Name, contents, None)) } // TODO: handle this appropriately @@ -1944,6 +1974,15 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + case Some(obj : Terminal) => + if(obj.Definition.isInstanceOf[MatrixTerminalDefinition]) { + //TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks) + sendResponse(BindPlayerMessage(1, "@ams", true, true, 0, 0, 0, obj.Position)) + } + else { + sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) + } + case Some(obj : PlanetSideGameObject) => if(itemType != 121) { sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) @@ -2986,8 +3025,8 @@ class WorldSessionActor extends Actor with MDCContextAware { def DeploymentActivities(obj : Deployment.DeploymentObject) : Unit = { obj match { case vehicle : Vehicle => - //TODO we should not have to do this imho - ReloadVehicleAccessPermissions(vehicle) + ReloadVehicleAccessPermissions(vehicle) //TODO we should not have to do this imho + sendResponse(PlanetsideAttributeMessage(obj.GUID, 81, 1)) case _ => ; } } diff --git a/pslogin/src/main/scala/Zones.scala b/pslogin/src/main/scala/Zones.scala index f856f487..60de7f9c 100644 --- a/pslogin/src/main/scala/Zones.scala +++ b/pslogin/src/main/scala/Zones.scala @@ -13,7 +13,14 @@ object Zones { val z5 = new Zone("z5", Maps.map5, 5) - val z6 = new Zone("z6", Maps.map6, 6) + val z6 = new Zone("z6", Maps.map6, 6) { + override def Init(implicit context : ActorContext) : Unit = { + super.Init(context) + + import net.psforever.types.PlanetSideEmpire + Building(2).get.Faction = PlanetSideEmpire.VS + } + } val z7 = new Zone("z7", Maps.map7, 7)