From c664f96bd4c9736be478ef35232c547b8fd31c5f Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 6 Jun 2018 19:13:39 -0400 Subject: [PATCH] a little bit of vehicles everything; work on the endocder/decoder for vehicles and seated players and bitstream size; vehicle ownership is extremely passable right now; seat restrictionsfor getting into vehicles is now in place; tests repaired; adjustment to vehicle spawn pad (again) to hopefully increase tolerance and recovery --- .../scala/net/psforever/objects/Vehicle.scala | 36 ++- .../converter/AvatarConverter.scala | 77 ++++--- .../converter/VehicleConverter.scala | 40 ++-- .../VehicleSpawnControlSeatDriver.scala | 12 +- ...cleSpawnControlServerVehicleOverride.scala | 39 ++-- .../objects/vehicles/VehicleControl.scala | 28 ++- .../game/PlanetsideAttributeMessage.scala | 2 +- .../CharacterAppearanceData.scala | 8 +- .../objectcreate/DetailedPlayerData.scala | 37 +++- .../packet/game/objectcreate/PlayerData.scala | 12 +- .../packet/game/objectcreate/Prefab.scala | 159 +++++++------ .../game/objectcreate/VehicleData.scala | 177 ++++++++------- .../DetailedCharacterDataTest.scala | 8 +- .../DestroyedVehiclesTest.scala | 1 - .../MountedVehiclesTest.scala | 49 +++-- .../NormalVehiclesTest.scala | 97 ++++---- .../UtilityVehiclesTest.scala | 208 +++++++++--------- .../VariantVehiclesTest.scala | 29 +-- .../scala/objects/VehicleSpawnPadTest.scala | 81 ++----- .../src/main/scala/WorldSessionActor.scala | 175 ++++++++++----- .../scala/services/avatar/AvatarAction.scala | 4 +- .../services/avatar/AvatarResponse.scala | 2 +- .../scala/services/avatar/AvatarService.scala | 10 +- .../services/vehicle/VehicleAction.scala | 2 +- .../services/vehicle/VehicleResponse.scala | 2 +- .../services/vehicle/VehicleService.scala | 8 +- .../src/test/scala/AvatarServiceTest.scala | 21 +- .../src/test/scala/VehicleServiceTest.scala | 6 +- 28 files changed, 777 insertions(+), 553 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/Vehicle.scala b/common/src/main/scala/net/psforever/objects/Vehicle.scala index 6dabd99a..1291ca07 100644 --- a/common/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/common/src/main/scala/net/psforever/objects/Vehicle.scala @@ -21,14 +21,42 @@ import scala.annotation.tailrec * Generally, all seating is declared first - the driver and passengers and and gunners. * Following that are the mounted weapons and other utilities. * Trunk space starts being indexed afterwards. - * To keep it simple, infantry seating, mounted weapons, and utilities are stored separately.
- *
- * Vehicles maintain a `Map` of `Utility` objects in given index positions. + * To keep it simple, infantry seating, mounted weapons, and utilities are stored separately herein. + * The `Map` of `Utility` objects is given using the same inventory index positions. * Positive indices and zero are considered "represented" and must be assigned a globally unique identifier * and must be present in the containing vehicle's `ObjectCreateMessage` packet. * The index is the seat position, reflecting the position in the zero-index inventory. * Negative indices are expected to be excluded from this conversion. - * The value of the negative index does not have a specific meaning. + * The value of the negative index does not have a specific meaning.
+ *
+ * The importance of a vehicle's owner can not be overlooked. + * The owner is someone who can control who can sit in the vehicle's seats + * either through broad categorization or discriminating sleection ("kicking") + * and who has access to and can allow access to the vehicle's trunk capacity. + * The driver is the only player that can access a vehicle's saved loadouts through a repair/rearm silo + * and can procure equipment from the said silo. + * The owner of a vehicle and the driver of a vehicle as mostly interchangeable terms for this reason + * and it can be summarized that the player who has access to the driver seat meets the qualifications for the "owner" + * so long as that player is the last person to have sat in that seat. + * All previous ownership information is replaced just as soon as someone else sits in the driver's seat. + * Ownership is also transferred as players die and respawn (from and to the same client) + * and when they leave a continent without taking the vehicle they currently own with them. + * (They also lose ownership when they leave the game, of course.)
+ *
+ * All seats have vehicle-level properties on top of their own internal properties. + * A seat has a glyph projected onto the ground when the vehicle is not moving + * that is used to mark where the seat can be accessed, as well as broadcasting the current access condition of the seat. + * As indicated previously, seats are composed into categories and the categories used to control access. + * The "driver" group has already been mentioned and is usually composed of a single seat, the "first" one. + * The driver seat is typically locked to the person who can sit in it - the owner - unless manually unlocked. + * Any seat besides the "driver" that has a weapon controlled from the seat is called a "gunner" seats. + * Any other seat besides the "driver" seat and "gunner" seats is called a "passenger" seat. + * All of these seats are typically unlocked normally. + * The "trunk" also counts as an access group even though it is not directly attached to a seat and starts as "locked." + * The categories all have their own glyphs, + * sharing a red cross glyph as a "can not access" state, + * and may also use their lack of visibility to express state. + * In terms of individual access, each seat can have its current occupant ejected, save for the driver's seat. * @see `Vehicle.EquipmentUtilities` * @param vehicleDef the vehicle's definition entry'; * stores and unloads pertinent information about the `Vehicle`'s configuration; diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala index e07210b8..15dc1c55 100644 --- a/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala +++ b/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala @@ -12,9 +12,8 @@ import scala.util.{Success, Try} class AvatarConverter extends ObjectCreateConverter[Player]() { override def ConstructorData(obj : Player) : Try[PlayerData] = { import AvatarConverter._ - val MaxArmor = obj.MaxArmor - if(obj.VehicleSeated.isEmpty) { - Success( + Success( + if(obj.VehicleSeated.isEmpty) { PlayerData( PlacementData(obj.Position, obj.Orientation, obj.Velocity), MakeAppearanceData(obj), @@ -22,43 +21,38 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { MakeInventoryData(obj), GetDrawnSlot(obj) ) - ) - } - else { - Success( + } + else { PlayerData( MakeAppearanceData(obj), MakeCharacterData(obj), MakeInventoryData(obj), - GetDrawnSlot(obj) + DrawnSlot.None ) - ) - } + } + ) } override def DetailedConstructorData(obj : Player) : Try[DetailedPlayerData] = { import AvatarConverter._ Success( - DetailedPlayerData.apply( - PlacementData(obj.Position, obj.Orientation, obj.Velocity), - MakeAppearanceData(obj), - DetailedCharacterData( - obj.BEP, - obj.CEP, - obj.MaxHealth, - obj.Health, - obj.Armor, - obj.MaxStamina, - obj.Stamina, - obj.Certifications.toList.sortBy(_.id), //TODO is sorting necessary? - MakeImplantEntries(obj), - List.empty[String], //TODO fte list - List.empty[String], //TODO tutorial list - MakeCosmetics(obj.BEP) - ), - InventoryData((MakeHolsters(obj, BuildDetailedEquipment) ++ MakeFifthSlot(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)), - GetDrawnSlot(obj) - ) + if(obj.VehicleSeated.isEmpty) { + DetailedPlayerData.apply( + PlacementData(obj.Position, obj.Orientation, obj.Velocity), + MakeAppearanceData(obj), + MakeDetailedCharacterData(obj), + MakeDetailedInventoryData(obj), + GetDrawnSlot(obj) + ) + } + else { + DetailedPlayerData.apply( + MakeAppearanceData(obj), + MakeDetailedCharacterData(obj), + MakeDetailedInventoryData(obj), + DrawnSlot.None + ) + } ) } } @@ -107,8 +101,29 @@ object AvatarConverter { ) } + def MakeDetailedCharacterData(obj : Player) : (Option[Int])=>DetailedCharacterData = { + DetailedCharacterData( + obj.BEP, + obj.CEP, + obj.MaxHealth, + obj.Health, + obj.Armor, + obj.MaxStamina, + obj.Stamina, + obj.Certifications.toList.sortBy(_.id), //TODO is sorting necessary? + MakeImplantEntries(obj), + List.empty[String], //TODO fte list + List.empty[String], //TODO tutorial list + MakeCosmetics(obj.BEP) + ) + } + def MakeInventoryData(obj : Player) : InventoryData = { - InventoryData(MakeHolsters(obj, BuildEquipment).sortBy(_.parentSlot)) //TODO is sorting necessary? + InventoryData(MakeHolsters(obj, BuildEquipment).sortBy(_.parentSlot)) + } + + def MakeDetailedInventoryData(obj : Player) : InventoryData = { + InventoryData((MakeHolsters(obj, BuildDetailedEquipment) ++ MakeFifthSlot(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)) } /** diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala index e354e8e8..eea2f688 100644 --- a/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala +++ b/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala @@ -13,16 +13,22 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() { Failure(new Exception("VehicleConverter should not be used to generate detailed VehicleData (nothing should)")) override def ConstructorData(obj : Vehicle) : Try[VehicleData] = { + val health = 255 * obj.Health / obj.MaxHealth //TODO not precise Success( VehicleData( - CommonFieldData( - PlacementData(obj.Position, obj.Orientation, obj.Velocity), - obj.Faction, - 0, - PlanetSideGUID(0) //if(obj.Owner.isDefined) { obj.Owner.get } else { PlanetSideGUID(0) } //TODO is this really Owner? - ), + PlacementData(obj.Position, obj.Orientation, obj.Velocity), + obj.Faction, + false, //bops + health < 3, //destroyed 0, - 255 * obj.Health / obj.MaxHealth, //TODO not precise + obj.Jammered, //jammered + false, + obj.Owner match { + case Some(owner) => owner + case None => PlanetSideGUID(0) + }, + false, + health, false, false, obj.DeploymentState, false, @@ -35,11 +41,9 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() { } private def MakeSeats(obj : Vehicle) : List[InventoryItemData.InventoryItem] = { - var offset : Long = VehicleData.InitialStreamLengthToSeatEntries(true, SpecificFormatModifier) - obj.Seats - .filter({ case(_, seat) => seat.isOccupied }) - .map({ case(index, seat) => - val player = seat.Occupant.get + val offset : Long = VehicleData.InitialStreamLengthToSeatEntries(true, SpecificFormatModifier) + obj.Seats(0).Occupant match { //TODO just the driver for now to avoid issues with seat permissions + case Some(player) => val mountedPlayer = VehicleData.PlayerData( AvatarConverter.MakeAppearanceData(player), AvatarConverter.MakeCharacterData(player), @@ -47,11 +51,13 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() { AvatarConverter.GetDrawnSlot(player), offset ) - val entry = InventoryItemData(ObjectClass.avatar, player.GUID, index, mountedPlayer) - println(s"seat $index offset: $offset, size: ${entry.bitsize}") - offset += entry.bitsize - entry - }).toList + val entry = InventoryItemData(ObjectClass.avatar, player.GUID, 0, mountedPlayer) + //println(s"seat 0 offset: $offset, size: ${entry.bitsize}, pad: ${mountedPlayer.basic_appearance.NamePadding}") + //offset += entry.bitsize + List(entry) + case None => + Nil + } } private def MakeMountings(obj : Vehicle) : List[InventoryItemData.InventoryItem] = { diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala index 2b67fa47..2fb0f248 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala @@ -4,6 +4,7 @@ package net.psforever.objects.serverobject.pad.process import akka.actor.{ActorRef, Props} import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} +import net.psforever.types.Vector3 import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ @@ -46,7 +47,7 @@ class VehicleSpawnControlSeatDriver(pad : VehicleSpawnPad) extends VehicleSpawnC trace("driver to be made seated in vehicle") entry.sendTo ! VehicleSpawnPad.StartPlayerSeatedInVehicle(entry.vehicle, pad) entry.vehicle.Actor.tell(Mountable.TryMount(driver, 0), entry.sendTo) //entry.sendTo should handle replies to TryMount - context.system.scheduler.scheduleOnce(1000 milliseconds, self, VehicleSpawnControlSeatDriver.AwaitDriverInSeat(entry)) + context.system.scheduler.scheduleOnce(1500 milliseconds, self, VehicleSpawnControlSeatDriver.AwaitDriverInSeat(entry)) } else { trace("driver lost; vehicle stranded on pad") @@ -55,11 +56,18 @@ class VehicleSpawnControlSeatDriver(pad : VehicleSpawnPad) extends VehicleSpawnC case VehicleSpawnControlSeatDriver.AwaitDriverInSeat(entry) => val driver = entry.driver - if(entry.sendTo == ActorRef.noSender || driver.Continent != Continent.Id) { + if(entry.sendTo == ActorRef.noSender || !driver.isAlive || driver.Continent != Continent.Id) { trace("driver lost, but operations can continue") vehicleOverride ! VehicleSpawnControl.Process.ServerVehicleOverride(entry) } + else if(entry.vehicle.Health == 0 || entry.vehicle.Position == Vector3.Zero) { + //skip ahead for cleanup + vehicleOverride ! VehicleSpawnControl.Process.ServerVehicleOverride(entry) + } else if(driver.isAlive && driver.VehicleSeated.isEmpty) { + if(pad.Railed) { + Continent.VehicleEvents ! VehicleSpawnPad.DetachFromRails(entry.vehicle, pad, Continent.Id) + } context.system.scheduler.scheduleOnce(100 milliseconds, self, VehicleSpawnControlSeatDriver.AwaitDriverInSeat(entry)) } else { diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlServerVehicleOverride.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlServerVehicleOverride.scala index 41194d46..d7331618 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlServerVehicleOverride.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlServerVehicleOverride.scala @@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.pad.process import akka.actor.{ActorRef, Props} import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} +import net.psforever.types.Vector3 import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ @@ -26,27 +27,33 @@ class VehicleSpawnControlServerVehicleOverride(pad : VehicleSpawnPad) extends Ve def receive : Receive = { case VehicleSpawnControl.Process.ServerVehicleOverride(entry) => val vehicle = entry.vehicle - val pad_railed = pad.Railed - if(pad_railed) { - Continent.VehicleEvents ! VehicleSpawnPad.DetachFromRails(vehicle, pad, Continent.Id) - } - if(vehicle.Health == 0) { - trace(s"vehicle was already destroyed; but, everything is fine") - if(pad_railed) { - Continent.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad, Continent.Id) + val vehicleFailState = vehicle.Health == 0 || vehicle.Position == Vector3.Zero + val driverFailState = !entry.driver.isAlive || entry.driver.Continent != Continent.Id || (if(vehicle.HasGUID) { !entry.driver.VehicleSeated.contains(vehicle.GUID) } else { true }) + if(vehicleFailState || driverFailState) { + if(vehicleFailState) { + trace(s"vehicle was already destroyed") + if(pad.Railed) { + Continent.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad, Continent.Id) + } } + else { + trace(s"driver is not ready") + if(pad.Railed) { + Continent.VehicleEvents ! VehicleSpawnPad.DetachFromRails(vehicle, pad, Continent.Id) + } + } + Continent.VehicleEvents ! VehicleSpawnPad.RevealPlayer(entry.driver.GUID, Continent.Id) vehicleGuide ! VehicleSpawnControl.Process.FinalClearance(entry) } - else if(entry.sendTo != ActorRef.noSender && entry.driver.isAlive && entry.driver.Continent == Continent.Id && entry.driver.VehicleSeated.contains(vehicle.GUID)) { - trace(s"telling ${entry.driver.Name} that the server is assuming control of the ${vehicle.Definition.Name}") - entry.sendTo ! VehicleSpawnPad.ServerVehicleOverrideStart(vehicle, pad) - context.system.scheduler.scheduleOnce(3000 milliseconds, vehicleGuide, VehicleSpawnControl.Process.StartGuided(entry)) - } else { - if(pad_railed) { - Continent.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad, Continent.Id) + if(pad.Railed) { + Continent.VehicleEvents ! VehicleSpawnPad.DetachFromRails(vehicle, pad, Continent.Id) + } + if(entry.sendTo != ActorRef.noSender) { + trace(s"telling ${entry.driver.Name} that the server is assuming control of the ${vehicle.Definition.Name}") + entry.sendTo ! VehicleSpawnPad.ServerVehicleOverrideStart(vehicle, pad) + context.system.scheduler.scheduleOnce(3000 milliseconds, vehicleGuide, VehicleSpawnControl.Process.StartGuided(entry)) } - vehicleGuide ! VehicleSpawnControl.Process.FinalClearance(entry) } case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) => diff --git a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala index adb2bd3a..0518cfc7 100644 --- a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala +++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala @@ -3,9 +3,10 @@ package net.psforever.objects.vehicles import akka.actor.Actor import net.psforever.objects.Vehicle -import net.psforever.objects.serverobject.mount.MountableBehavior +import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.deploy.DeploymentBehavior +import net.psforever.types.ExoSuitType /** * An `Actor` that handles messages being dispatched to a specific `Vehicle`.
@@ -32,9 +33,32 @@ class VehicleControl(vehicle : Vehicle) extends Actor def Enabled : Receive = checkBehavior .orElse(deployBehavior) - .orElse(mountBehavior) .orElse(dismountBehavior) .orElse { + case Mountable.TryMount(user, seat_num) => + val exosuit = user.ExoSuit + val restriction = vehicle.Seats(seat_num).ArmorRestriction + val seatGroup = vehicle.SeatPermissionGroup(seat_num).getOrElse(AccessPermissionGroup.Passenger) + val permission = vehicle.PermissionGroup(seatGroup.id).getOrElse(VehicleLockState.Empire) + if( + (if(seatGroup == AccessPermissionGroup.Driver) { + vehicle.Owner.contains(user.GUID) || vehicle.Owner.isEmpty || permission != VehicleLockState.Locked + } + else { + permission != VehicleLockState.Locked + }) && + (exosuit match { + case ExoSuitType.MAX => restriction == SeatArmorRestriction.MaxOnly + case ExoSuitType.Reinforced => restriction != SeatArmorRestriction.NoReinforcedOrMax + case _ => true + }) + ) { + mountBehavior.apply(Mountable.TryMount(user, seat_num)) + } + else { + sender ! Mountable.MountMessages(user, Mountable.CanNotMount(vehicle, seat_num)) + } + case FactionAffinity.ConvertFactionAffinity(faction) => val originalAffinity = vehicle.Faction if(originalAffinity != (vehicle.Faction = faction)) { 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 5d0dc875..007cbbc9 100644 --- a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala @@ -126,7 +126,7 @@ import scodec.codecs._ * `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`
+ * `21 - Declare a player the vehicle's owner, by globally unique identifier`
* `22 - Toggles gunner and passenger mount points (1 = hides, 0 = reveals; this also locks their permissions)`
* `68 - ???`
* `80 - Damage vehicle (unknown value)`
diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala index 36b90591..d6221fcb 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterAppearanceData.scala @@ -125,11 +125,17 @@ final case class CharacterAppearanceData(app : BasicCharacterData, //factor guard bool values into the base size, not its corresponding optional field val nameStringSize : Long = StreamBitSize.stringBitSize(app.name, 16) + name_padding val outfitStringSize : Long = StreamBitSize.stringBitSize(outfit_name, 16) + - CharacterAppearanceData.outfitNamePadding //even if the outfit_name is blank, string always padded + (if(outfit_name.nonEmpty) { CharacterAppearanceData.outfitNamePadding } else { 0L }) //even if the outfit_name is blank, string always padded val altModelSize = CharacterAppearanceData.altModelBit(this).getOrElse(0) 335L + nameStringSize + outfitStringSize + altModelSize } + /** + * External access to the value padding on the name field. + * The padding will always be a number 0-7. + * @return the pad length in bits + */ + def NamePadding : Int = name_padding /** * When a player is released-dead or attached to a zipline, their basic infantry model is replaced with a different one. diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedPlayerData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedPlayerData.scala index 8a9acb63..20b6f3e4 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedPlayerData.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedPlayerData.scala @@ -49,33 +49,62 @@ final case class DetailedPlayerData(pos : Option[PlacementData], object DetailedPlayerData extends Marshallable[DetailedPlayerData] { /** - * Overloaded constructor that ignores the coordinate information. + * Overloaded constructor that ignores the coordinate information but includes the inventory. * It passes information between the three major divisions for the purposes of offset calculations. + * This constructor should be used for players that are mounted. * @param basic_appearance a curried function for the common fields regarding the the character's appearance * @param character_data a curried function for the class-specific data that explains about the character + * @param inventory the player's inventory + * @param drawn_slot the holster that is initially drawn; + * technically, always `DrawnSlot.None`, but the field is preserved to maintain similarity * @return a `DetailedPlayerData` object */ def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = { val appearance = basic_appearance(5) DetailedPlayerData(None, appearance, character_data(appearance.altModelBit), Some(inventory), drawn_slot)(false) } - /** */ + + /** + * Overloaded constructor that ignores the coordinate information and the inventory. + * It passes information between the three major divisions for the purposes of offset calculations. + * This constructor should be used for players that are mounted. + * @param basic_appearance a curried function for the common fields regarding the the character's appearance + * @param character_data a curried function for the class-specific data that explains about the character + * @param drawn_slot the holster that is initially drawn; + * technically, always `DrawnSlot.None`, but the field is preserved to maintain similarity + * @return a `DetailedPlayerData` object + */ def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = { val appearance = basic_appearance(5) DetailedPlayerData(None, appearance, character_data(appearance.altModelBit), None, drawn_slot)(false) } + /** - * Overloaded constructor that includes the coordinate information. + * Overloaded constructor that includes the coordinate information and the inventory. * It passes information between the three major divisions for the purposes of offset calculations. + * This constructor should be used for players that are standing apart from other containers. + * @param pos the optional position of the character in the world environment * @param basic_appearance a curried function for the common fields regarding the the character's appearance * @param character_data a curried function for the class-specific data that explains about the character + * @param inventory the player's inventory + * @param drawn_slot the holster that is initially drawn * @return a `DetailedPlayerData` object */ def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = { val appearance = basic_appearance(PlayerData.PaddingOffset(Some(pos))) DetailedPlayerData(Some(pos), appearance, character_data(appearance.altModelBit), Some(inventory), drawn_slot)(true) } - /** */ + + /** + * Overloaded constructor that includes the coordinate information but ignores the inventory. + * It passes information between the three major divisions for the purposes of offset calculations. + * This constructor should be used for players that are standing apart from other containers. + * @param pos the optional position of the character in the world environment + * @param basic_appearance a curried function for the common fields regarding the the character's appearance + * @param character_data a curried function for the class-specific data that explains about the character + * @param drawn_slot the holster that is initially drawn + * @return a `DetailedPlayerData` object + */ def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = { val appearance = basic_appearance(PlayerData.PaddingOffset(Some(pos))) DetailedPlayerData(Some(pos), appearance, character_data(appearance.altModelBit), None, drawn_slot)(true) diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/PlayerData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/PlayerData.scala index e84483d3..7452d1be 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/PlayerData.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/PlayerData.scala @@ -53,13 +53,14 @@ final case class PlayerData(pos : Option[PlacementData], object PlayerData extends Marshallable[PlayerData] { /** - * Overloaded constructor that ignores the coordinate information. + * Overloaded constructor that ignores the coordinate information but includes the inventory. * It passes information between the three major divisions for the purposes of offset calculations. * This constructor should be used for players that are mounted. * @param basic_appearance a curried function for the common fields regarding the the character's appearance * @param character_data a curried function for the class-specific data that explains about the character * @param inventory the player's inventory - * @param drawn_slot the holster that is initially drawn + * @param drawn_slot the holster that is initially drawn; + * technically, always `DrawnSlot.None`, but the field is preserved to maintain similarity * @return a `PlayerData` object */ def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Type) : PlayerData = { @@ -72,7 +73,8 @@ object PlayerData extends Marshallable[PlayerData] { * This constructor should be used for players that are mounted. * @param basic_appearance a curried function for the common fields regarding the the character's appearance * @param character_data a curried function for the class-specific data that explains about the character - * @param drawn_slot the holster that is initially drawn + * @param drawn_slot the holster that is initially drawn; + * technically, always `DrawnSlot.None`, but the field is preserved to maintain similarity * @return a `PlayerData` object */ def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, drawn_slot : DrawnSlot.Type) : PlayerData = { @@ -81,7 +83,7 @@ object PlayerData extends Marshallable[PlayerData] { } /** - * Overloaded constructor. + * Overloaded constructor that includes the coordinate information and the inventory. * It passes information between the three major divisions for the purposes of offset calculations. * This constructor should be used for players that are standing apart from other containers. * @param pos the optional position of the character in the world environment @@ -96,7 +98,7 @@ object PlayerData extends Marshallable[PlayerData] { PlayerData(Some(pos), appearance, character_data(appearance.backpack, false), Some(inventory), drawn_slot)(true) } /** - * Overloaded constructor that ignores the inventory. + * Overloaded constructor that includes the coordinate information but ignores the inventory. * It passes information between the three major divisions for the purposes of offset calculations. * This constructor should be used for players that are standing apart from other containers. * @param pos the optional position of the character in the world environment diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/Prefab.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/Prefab.scala index acfaa407..a767591d 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/Prefab.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/Prefab.scala @@ -13,22 +13,23 @@ import net.psforever.types.{DriveState, PlanetSideEmpire} object Prefab { object Vehicle { def ams(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, driveState : DriveState.Value, matrix_guid : PlanetSideGUID, respawn_guid : PlanetSideGUID, term_a_guid : PlanetSideGUID, term_b_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, driveState, false, false, false, Some(UtilityVehicleData(0)), + VehicleData(CommonFieldData(loc, faction, 0), health, driveState, false, UtilityVehicleData(0), Some(InventoryData(List( InternalSlot(ObjectClass.matrix_terminalc, matrix_guid, 1, CommonTerminalData(faction)), InternalSlot(ObjectClass.ams_respawn_tube, respawn_guid, 2, CommonTerminalData(faction)), InternalSlot(ObjectClass.order_terminala, term_a_guid, 3, CommonTerminalData(faction)), InternalSlot(ObjectClass.order_terminalb, term_b_guid, 4, CommonTerminalData(faction)) ))) - )(VehicleFormat.Utility) + ) } def ant(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, driveState : DriveState.Value) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, driveState, false, false, false, Some(UtilityVehicleData(0)), None)(VehicleFormat.Utility) + VehicleData(CommonFieldData(loc, faction, 0), health, driveState, false, UtilityVehicleData(0), None) } def apc_nc(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID, weapon4_guid : PlanetSideGUID, ammo4_guid : PlanetSideGUID, weapon5_guid : PlanetSideGUID, ammo5_guid : PlanetSideGUID, weapon6_guid : PlanetSideGUID, ammo6_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None, + //VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None, + VehicleData(CommonFieldData(loc, faction, 2), health, DriveState.State7, false, Some(InventoryData( InventoryItemData(ObjectClass.apc_weapon_systemc_nc, weapon1_guid, 11, WeaponData(0x6, 0x8, 0, ObjectClass.bullet_20mm, ammo1_guid, 0, AmmoBoxData(8)) @@ -49,11 +50,12 @@ object Prefab { WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo6_guid, 0, AmmoBoxData(8)) ) :: Nil )) - )(VehicleFormat.Normal) + ) } def apc_tr(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID, weapon4_guid : PlanetSideGUID, ammo4_guid : PlanetSideGUID, weapon5_guid : PlanetSideGUID, ammo5_guid : PlanetSideGUID, weapon6_guid : PlanetSideGUID, ammo6_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None, + //VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None, + VehicleData(CommonFieldData(loc, faction, 2), health, DriveState.State7, false, Some(InventoryData( InventoryItemData(ObjectClass.apc_weapon_systemc_tr, weapon1_guid, 11, WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo1_guid, 0, AmmoBoxData(8)) @@ -74,11 +76,12 @@ object Prefab { WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo6_guid, 0, AmmoBoxData(8)) ) :: Nil )) - )(VehicleFormat.Normal) + ) } def apc_vs(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID, weapon4_guid : PlanetSideGUID, ammo4_guid : PlanetSideGUID, weapon5_guid : PlanetSideGUID, ammo5_guid : PlanetSideGUID, weapon6_guid : PlanetSideGUID, ammo6_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None, + //VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None, + VehicleData(CommonFieldData(loc, faction, 2), health, DriveState.State7, false, Some(InventoryData( InventoryItemData(ObjectClass.apc_weapon_systemc_vs, weapon1_guid, 11, WeaponData(0x6, 0x8, 0, ObjectClass.flux_cannon_thresher_battery, ammo1_guid, 0, AmmoBoxData(8)) @@ -99,12 +102,13 @@ object Prefab { WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo6_guid, 0, AmmoBoxData(8)) ) :: Nil )) - )(VehicleFormat.Normal) + ) } def aurora(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo11_guid : PlanetSideGUID, ammo12_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo21_guid : PlanetSideGUID, ammo22_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, - Some(InventoryData( + //VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + VehicleData(CommonFieldData(loc, faction, 2), health, DriveState.State7, false, + Some(InventoryData( InventoryItemData(ObjectClass.aurora_weapon_systema, weapon1_guid, 5, WeaponData(0x6, 0x8, 0, ObjectClass.fluxpod_ammo, ammo11_guid, 0, AmmoBoxData(0x8)) ) :: @@ -112,11 +116,12 @@ object Prefab { WeaponData(0x6, 0x8, 0, ObjectClass.fluxpod_ammo, ammo21_guid, 0, AmmoBoxData(0x8)) ) :: Nil )) - )(VehicleFormat.Normal) + ) } def battlewagon(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID, weapon4_guid : PlanetSideGUID, ammo4_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + //VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + VehicleData(CommonFieldData(loc, faction, 2), health, DriveState.State7, false, Some(InventoryData( InventoryItemData(ObjectClass.battlewagon_weapon_systema, weapon1_guid, 5, WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo1_guid, 0, AmmoBoxData(0x8)) @@ -131,11 +136,12 @@ object Prefab { WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo4_guid, 0, AmmoBoxData(0x8)) ) :: Nil )) - )(VehicleFormat.Normal) + ) } def dropship(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), + //VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), + VehicleData(CommonFieldData(loc, faction, 2), health, DriveState.State7, false, VariantVehicleData(0), Some(InventoryData( InventoryItemData(ObjectClass.cannon_dropship_20mm, weapon1_guid, 12, WeaponData(0x6, 0x8, 0, ObjectClass.bullet_20mm, ammo1_guid, 0, AmmoBoxData(8)) @@ -147,32 +153,35 @@ object Prefab { WeaponData(0x6, 0x8, 0, ObjectClass.bullet_20mm, ammo3_guid, 0, AmmoBoxData(8)) ) :: Nil )) - )(VehicleFormat.Variant) + ) } def flail(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID, terminal_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.Mobile, false, false, false, Some(VariantVehicleData(0)), + //VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.Mobile, false, false, false, Some(VariantVehicleData(0)), + VehicleData(CommonFieldData(loc, faction, 2), health, DriveState.Mobile, false, VariantVehicleData(0), Some(InventoryData( InventoryItemData(ObjectClass.flail_weapon, weapon_guid, 1, WeaponData(0x6, 0x8, 0, ObjectClass.ancient_ammo_vehicle, ammo_guid, 0, AmmoBoxData(8)) ) :: InventoryItemData(ObjectClass.targeting_laser_dispenser, terminal_guid, 2, CommonTerminalData(faction, 2)) :: Nil )) - )(VehicleFormat.Variant) + ) } def fury(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + //VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + VehicleData(CommonFieldData(loc, faction, 0), health, DriveState.State7, false, Some(InventoryData( InventoryItemData(ObjectClass.fury_weapon_systema, weapon_guid, 1, WeaponData(0x4, 0x8, ObjectClass.hellfire_ammo, ammo_guid, 0, AmmoBoxData(0x8)) ) :: Nil )) - )(VehicleFormat.Normal) + ) } def galaxy_gunship(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID, weapon4_guid : PlanetSideGUID, ammo4_guid : PlanetSideGUID, weapon5_guid : PlanetSideGUID, ammo5_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), + //VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), + VehicleData(CommonFieldData(loc, faction, 2), health, DriveState.State7, false, VariantVehicleData(0), Some(InventoryData( InventoryItemData(ObjectClass.galaxy_gunship_cannon, weapon1_guid, 6, WeaponData(0x6, 0x8, 0, ObjectClass.heavy_grenade_mortar, ammo1_guid, 0, AmmoBoxData(8)) @@ -190,11 +199,12 @@ object Prefab { WeaponData(0x6, 0x8, 0, ObjectClass.bullet_35mm, ammo5_guid, 0, AmmoBoxData(8)) ) :: Nil )) - )(VehicleFormat.Variant) + ) } def liberator(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo4_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), + //VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), + VehicleData(CommonFieldData(loc, faction, 2), health, DriveState.State7, false, VariantVehicleData(0), Some(InventoryData( InventoryItemData(ObjectClass.liberator_weapon_system, weapon1_guid, 3, WeaponData(0x6, 0x8, 0, ObjectClass.bullet_35mm, ammo1_guid, 0, AmmoBoxData(8)) @@ -206,31 +216,34 @@ object Prefab { WeaponData(0x6, 0x8, 0, ObjectClass.bullet_25mm, ammo4_guid, 0 ,AmmoBoxData(8)) ) :: Nil )) - )(VehicleFormat.Variant) + ) } def lightgunship(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.Mobile, false, false, false, Some(VariantVehicleData(0)), + //VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.Mobile, false, false, false, Some(VariantVehicleData(0)), + VehicleData(CommonFieldData(loc, faction, 2), health, DriveState.Mobile, false, VariantVehicleData(0), Some(InventoryData( InventoryItemData(ObjectClass.lightgunship_weapon_system, weapon_guid, 1, WeaponData(0x6, 0x8, 0, ObjectClass.bullet_20mm, ammo1_guid, 0, AmmoBoxData(8), ObjectClass.reaver_rocket, ammo2_guid,1, AmmoBoxData(8)) ) :: Nil )) - )(VehicleFormat.Variant) + ) } def lightning(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + //VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + VehicleData(CommonFieldData(loc, faction, 0), health, DriveState.State7, false, Some(InventoryData( InventoryItemData(ObjectClass.lightning_weapon_system, weapon_guid, 1, WeaponData(0x4, 0x8, 0, ObjectClass.bullet_75mm, ammo1_guid, 0, AmmoBoxData(0x0), ObjectClass.bullet_12mm, ammo2_guid, 1, AmmoBoxData(0x0)) ) :: Nil) ) - )(VehicleFormat.Normal) + ) } def lodestar(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, repair1_guid : PlanetSideGUID, repair2_guid : PlanetSideGUID, veh_rearm1_guid : PlanetSideGUID, veh_rearm2_guid : PlanetSideGUID, bfr_rearm1_guid : PlanetSideGUID, bfr_rearm2_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), + //VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), + VehicleData(CommonFieldData(loc, faction, 2), health, DriveState.State7, false, VariantVehicleData(0), Some(InventoryData(List( InternalSlot(ObjectClass.lodestar_repair_terminal, repair1_guid, 2, CommonTerminalData(faction, 2)), InternalSlot(ObjectClass.lodestar_repair_terminal, repair2_guid, 3, CommonTerminalData(faction, 2)), @@ -239,11 +252,12 @@ object Prefab { InternalSlot(ObjectClass.bfr_rearm_terminal, bfr_rearm1_guid, 6, CommonTerminalData(faction, 2)), InternalSlot(ObjectClass.bfr_rearm_terminal, bfr_rearm2_guid, 7, CommonTerminalData(faction, 2)) ))) - )(VehicleFormat.Variant) + ) } def magrider(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None, + //VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None, + VehicleData(CommonFieldData(loc, faction, 2), health, DriveState.State7, false, Some(InventoryData( InventoryItemData(ObjectClass.particle_beam_magrider, weapon1_guid, 2, WeaponData(0x6, 0x8, 0, ObjectClass.pulse_battery, ammo1_guid, 0, AmmoBoxData(8)) @@ -252,11 +266,12 @@ object Prefab { WeaponData(0x6, 0x8, 0, ObjectClass.heavy_rail_beam_battery, ammo2_guid, 0, AmmoBoxData(8)) ) :: Nil )) - )(VehicleFormat.Normal) + ) } def mediumtransport(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID): VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + //VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + VehicleData(CommonFieldData(loc, faction, 0), health, DriveState.State7, false, Some(InventoryData( InventoryItemData(ObjectClass.mediumtransport_weapon_systemA, weapon1_guid, 5, WeaponData(0x6, 0x8, ObjectClass.bullet_20mm, ammo1_guid, 0, AmmoBoxData(0x8)) @@ -265,25 +280,28 @@ object Prefab { WeaponData(0x6, 0x8, ObjectClass.bullet_20mm, ammo2_guid, 0, AmmoBoxData(0x8)) ) :: Nil )) - )(VehicleFormat.Normal) + ) } def mosquito(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), + //VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), + VehicleData(CommonFieldData(loc, faction, 0), health, DriveState.State7, false, VariantVehicleData(0), Some(InventoryData( InventoryItemData(ObjectClass.rotarychaingun_mosquito, weapon_guid, 1, WeaponData(0x6, 0x8, ObjectClass.bullet_12mm, ammo_guid, 0, AmmoBoxData(8)) ) :: Nil )) - )(VehicleFormat.Variant) + ) } def phantasm(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), None)(VehicleFormat.Variant) + //VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), None)(VehicleFormat.Variant) + VehicleData(CommonFieldData(loc, faction, 2), health, DriveState.State7, false, VariantVehicleData(0), None) } def prowler(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None, + //VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None, + VehicleData(CommonFieldData(loc, faction, 2), health, DriveState.State7, false, Some(InventoryData( InventoryItemData(ObjectClass.prowler_weapon_systemA, weapon1_guid, 3, WeaponData(0x6, 0x8, 0, ObjectClass.bullet_105mm, ammo1_guid, 0, AmmoBoxData(8)) @@ -292,53 +310,59 @@ object Prefab { WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo2_guid, 0, AmmoBoxData(8)) ) :: Nil )) - )(VehicleFormat.Normal) + ) } def quadassault(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + //VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + VehicleData(CommonFieldData(loc, faction, 0), health, DriveState.State7, false, Some(InventoryData( InventoryItemData(ObjectClass.quadassault_weapon_system, weapon_guid, 1, WeaponData(0x6, 0x8, ObjectClass.bullet_12mm, ammo_guid, 0, AmmoBoxData(0x8)) ) :: Nil )) - )(VehicleFormat.Normal) + ) } def quadstealth(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, false, false, false, None, None)(VehicleFormat.Normal) + //VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, false, false, false, None, None)(VehicleFormat.Normal) + VehicleData(CommonFieldData(loc, faction, 0), health, DriveState.State7, false, None) } def router(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, terminal_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.Mobile, false, false, false, Some(VariantVehicleData(0)), + //VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.Mobile, false, false, false, Some(VariantVehicleData(0)), + VehicleData(CommonFieldData(loc, faction, 2), health, DriveState.Mobile, false, VariantVehicleData(0), Some(InventoryData( InventoryItemData(ObjectClass.teleportpad_terminal, terminal_guid, 1, CommonTerminalData(faction, 2)) :: Nil )) - )(VehicleFormat.Variant) + ) } def skyguard(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None, + //VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None, + VehicleData(CommonFieldData(loc, faction, 2), health, DriveState.State7, false, Some(InventoryData( InventoryItemData(ObjectClass.skyguard_weapon_system, weapon_guid, 2, WeaponData(0x6, 0x8, 0, ObjectClass.skyguard_flak_cannon_ammo, ammo1_guid, 0, AmmoBoxData(8), ObjectClass.bullet_12mm, ammo2_guid, 1, AmmoBoxData(8)) ) :: Nil )) - )(VehicleFormat.Normal) + ) } def switchblade(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, driveState : DriveState.Value, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), + //VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), + VehicleData(CommonFieldData(loc, faction, 0), health, DriveState.State7, false, VariantVehicleData(0), Some(InventoryData( InventoryItemData(ObjectClass.scythe, weapon_guid, 1, WeaponData(0x6, 0x8, 0, ObjectClass.ancient_ammo_vehicle, ammo1_guid, 0, AmmoBoxData(0x8), ObjectClass.ancient_ammo_vehicle, ammo2_guid, 1, AmmoBoxData(0x8)) ) :: Nil )) - )(VehicleFormat.Variant) + ) } def threemanheavybuggy(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + //VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + VehicleData(CommonFieldData(loc, faction, 0), health, DriveState.State7, false, Some(InventoryData( InventoryItemData(ObjectClass.chaingun_p, weapon1_guid, 3, WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo1_guid, 0, AmmoBoxData(0x8)) @@ -347,11 +371,12 @@ object Prefab { WeaponData(0x6, 0x8, 0, ObjectClass.heavy_grenade_mortar, ammo2_guid, 0, AmmoBoxData(0x8)) ) :: Nil )) - )(VehicleFormat.Normal) + ) } def thunderer(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + //VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + VehicleData(CommonFieldData(loc, faction, 0), health, DriveState.State7, false, Some(InventoryData( InventoryItemData(ObjectClass.thunderer_weapon_systema, weapon1_guid, 5, WeaponData(0x6, 0x8, 0, ObjectClass.gauss_cannon_ammo, ammo1_guid, 0, AmmoBoxData(0x8)) @@ -360,51 +385,56 @@ object Prefab { WeaponData(0x6, 0x8, 0, ObjectClass.gauss_cannon_ammo, ammo2_guid, 0, AmmoBoxData(0x8)) ) :: Nil )) - )(VehicleFormat.Normal) + ) } def two_man_assault_buggy(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + //VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + VehicleData(CommonFieldData(loc, faction, 0), health, DriveState.State7, false, Some(InventoryData( InventoryItemData(ObjectClass.chaingun_p, weapon_guid, 2, WeaponData(0x6, 0x8, ObjectClass.bullet_12mm, ammo_guid, 0, AmmoBoxData(0x8)) ) :: Nil )) - )(VehicleFormat.Normal) + ) } def twomanheavybuggy(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + //VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + VehicleData(CommonFieldData(loc, faction, 0), health, DriveState.State7, false, Some(InventoryData( InventoryItemData(ObjectClass.advanced_missile_launcher_t, weapon_guid, 2, WeaponData(0x6, 0x8, 0, ObjectClass.firebird_missile, ammo_guid, 0, AmmoBoxData(0x8)) ) :: Nil )) - )(VehicleFormat.Normal) + ) } def twomanhoverbuggy(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + //VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, + VehicleData(CommonFieldData(loc, faction, 0), health, DriveState.State7, false, Some(InventoryData( InventoryItemData(ObjectClass.flux_cannon_thresher, weapon_guid, 2, WeaponData(0x6, 0x8, 0, ObjectClass.flux_cannon_thresher_battery, ammo_guid, 0, AmmoBoxData(0x8)) ) :: Nil )) - )(VehicleFormat.Normal) + ) } def vanguard(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None, + //VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None, + VehicleData(CommonFieldData(loc, faction, 2), health, DriveState.State7, false, Some(InventoryData( InventoryItemData(ObjectClass.vanguard_weapon_system, weapon_guid, 2, WeaponData(0x6, 0x8, 0, ObjectClass.bullet_150mm, ammo1_guid, 0, AmmoBoxData(8), ObjectClass.bullet_20mm, ammo2_guid, 1, AmmoBoxData(8)) ) :: Nil )) - )(VehicleFormat.Normal) + ) } def vulture(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), + //VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), + VehicleData(CommonFieldData(loc, faction, 2), health, DriveState.State7, false, VariantVehicleData(0), Some(InventoryData( InventoryItemData(ObjectClass.vulture_nose_weapon_system, weapon1_guid, 3, WeaponData(0x6, 0x8, 0, ObjectClass.bullet_35mm, ammo1_guid, 0, AmmoBoxData(8)) @@ -416,17 +446,18 @@ object Prefab { WeaponData(0x6, 0x8, 0, ObjectClass.bullet_25mm, ammo3_guid, 0, AmmoBoxData(8)) ) :: Nil )) - )(VehicleFormat.Variant) + ) } def wasp(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { - VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.Mobile, false, false, false, Some(VariantVehicleData(0)), + //VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.Mobile, false, false, false, Some(VariantVehicleData(0)), + VehicleData(CommonFieldData(loc, faction, 0), health, DriveState.Mobile, false, VariantVehicleData(0), Some(InventoryData( InventoryItemData(ObjectClass.wasp_weapon_system, weapon_guid, 1, WeaponData(0x6, 0x8, 0, ObjectClass.wasp_gun_ammo, ammo1_guid, 0, AmmoBoxData(8), ObjectClass.wasp_rocket_ammo, ammo2_guid, 0, AmmoBoxData(8)) ) :: Nil )) - )(VehicleFormat.Variant) + ) } } } diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala index 77e29e4f..2d13c1e3 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala @@ -5,9 +5,9 @@ import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.{Marshallable, PacketHelpers} import scodec.Attempt.{Failure, Successful} import scodec.{Attempt, Codec, Err} -import shapeless.HNil +import shapeless.HNil //note: do not import shapeless.:: here; it messes up List's :: function import scodec.codecs._ -import net.psforever.types.DriveState +import net.psforever.types.{DriveState, PlanetSideEmpire} import scala.collection.mutable.ListBuffer @@ -50,100 +50,103 @@ final case class VariantVehicleData(unk : Int) extends SpecificVehicleData { } /** - * A representation of a generic vehicle.
- *
- * Vehicles utilize their own packet to communicate position to the server, known as `VehicleStateMessage`. - * This takes the place of `PlayerStateMessageUpstream` when the player avatar is in control; - * and, it takes the place of `PlayerStateMessage` for other players when they are in control. - * If the vehicle is sufficiently complicated, a `ChildObjectStateMessage` will be used. - * This packet will control any turret(s) on the vehicle. - * For very complicated vehicles, the packets `FrameVehicleStateMessage` and `VehicleSubStateMessage` will also be employed. - * The tasks that these packets perform are different based on the vehicle that responds or generates them. - * @param basic data common to objects + * A representation of a generic vehicle. + * @param pos where the vehicle is and how it is oriented in the game world + * @param faction the faction that is aligned with this vehicle + * @param bops this vehicle belongs to the Black Ops, regardless of the faction field; + * activates the green camo and adjusts permissions + * @param destroyed this vehicle has ben destroyed; + * it's health should be less than 3/255, or 0% * @param unk1 na - * @param health the amount of health the vehicle has, as a percentage of a filled bar (255) + * @param jammered this vehicle is under the influence of a jammer grenade * @param unk2 na + * @param owner_guid the vehicle's (official) owner; + * verified as a living player in the game world on the same continent as the vehicle; + * sitting in the driver's seat or a `PlanetSideAttributeMessage` of type 21 can influence + * @param unk3 na + * @param health the amount of health the vehicle has, as a percentage of a filled bar (255) + * @param unk4 na * @param no_mount_points do not display entry points for the seats * @param driveState a representation for the current mobility state; - * various vehicles also use this field to indicate "deployment," e.g., AMS - * @param unk3 na + * various vehicles also use this field to indicate "deployment," e.g., the advanced mobile spawn * @param unk5 na - * @param cloak if a cloakable vehicle is cloaked - * @param unk4 na + * @param unk6 na + * @param cloak if a vehicle (that can cloak) is cloaked + * @param vehicle_format_data extra information necessary to implement special-type vehicles; + * see `vehicle_type` * @param inventory the seats, mounted weapons, and utilities (such as terminals) that are currently included; - * will also include trunk contents + * will also include trunk contents; + * the driver is the only valid seat entry (more will cause the access permissions to act up) * @param vehicle_type a modifier for parsing the vehicle data format differently; + * see `vehicle_format_data`; * defaults to `Normal` */ -final case class VehicleData(basic : CommonFieldData, +final case class VehicleData(pos : PlacementData, + faction : PlanetSideEmpire.Value, + bops : Boolean, + destroyed : Boolean, unk1 : Int, - health : Int, + jammered : Boolean, unk2 : Boolean, + owner_guid : PlanetSideGUID, + unk3 : Boolean, + health : Int, + unk4 : Boolean, no_mount_points : Boolean, driveState : DriveState.Value, - unk3 : Boolean, unk5 : Boolean, + unk6 : Boolean, cloak : Boolean, - unk4 : Option[SpecificVehicleData], - inventory : Option[InventoryData] = None - )(val vehicle_type : VehicleFormat.Value = VehicleFormat.Normal) extends ConstructorData { + vehicle_format_data : Option[SpecificVehicleData], + inventory : Option[InventoryData] = None) + (val vehicle_type : VehicleFormat.Value = VehicleFormat.Normal) extends ConstructorData { override def bitsize : Long = { - val basicSize = basic.bitsize - val extraBitsSize : Long = if(unk4.isDefined) { unk4.get.bitsize } else { 0L } + //factor guard bool values into the base size, not its corresponding optional field + val posSize : Long = pos.bitsize + val extraBitsSize : Long = if(vehicle_format_data.isDefined) { vehicle_format_data.get.bitsize } else { 0L } val inventorySize = if(inventory.isDefined) { inventory.get.bitsize } else { 0L } - 24L + basicSize + extraBitsSize + inventorySize + 47L + posSize + extraBitsSize + inventorySize } } object VehicleData extends Marshallable[VehicleData] { /** * Overloaded constructor for specifically handling `Normal` vehicle format. - * @param basic data common to objects - * @param unk1 na + * @param basic a field that encompasses some data used by the vehicle, including `faction` and `owner` * @param health the amount of health the vehicle has, as a percentage of a filled bar (255) - * @param unk2 na - * @param driveState a representation for the current mobility state; - * @param unk3 na - * @param unk4 na + * @param driveState a representation for the current mobility state + * @param cloak if a vehicle (that can cloak) is cloaked * @param inventory the seats, mounted weapons, and utilities (such as terminals) that are currently included - * @return a `VehicleData` object */ - def apply(basic : CommonFieldData, unk1 : Int, health : Int, unk2 : Int, driveState : DriveState.Value, unk3 : Boolean, unk4 : Int, inventory : Option[InventoryData]) : VehicleData = { - new VehicleData(basic, unk1, health, unk2>0, false, driveState, unk3, unk4>0, false, None, inventory)(VehicleFormat.Normal) + def apply(basic : CommonFieldData, health : Int, driveState : DriveState.Value, cloak : Boolean, inventory : Option[InventoryData]) : VehicleData = { + VehicleData(basic.pos, basic.faction, basic.bops, basic.destroyed, 0, basic.jammered, false, basic.player_guid, + false, health, false, false, driveState, false, false, cloak, None, inventory)(VehicleFormat.Normal) } /** * Overloaded constructor for specifically handling `Utility` vehicle format. - * @param basic data common to objects - * @param unk1 na + * @param basic a field that encompasses some data used by the vehicle, including `faction` and `owner` * @param health the amount of health the vehicle has, as a percentage of a filled bar (255) - * @param unk2 na - * @param driveState a representation for the current mobility state; - * @param unk3 na - * @param unk4 utility-specific field - * @param unk5 na + * @param driveState a representation for the current mobility state + * @param cloak if a vehicle (that can cloak) is cloaked * @param inventory the seats, mounted weapons, and utilities (such as terminals) that are currently included - * @return a `VehicleData` object */ - def apply(basic : CommonFieldData, unk1 : Int, health : Int, unk2 : Int, driveState : DriveState.Value, unk3 : Boolean, unk4 : UtilityVehicleData, unk5 : Int, inventory : Option[InventoryData]) : VehicleData = { - new VehicleData(basic, unk1, health, unk2>0, false, driveState, unk3, unk5>0, false, Some(unk4), inventory)(VehicleFormat.Utility) + def apply(basic : CommonFieldData, health : Int, driveState : DriveState.Value, cloak : Boolean, format : UtilityVehicleData, inventory : Option[InventoryData]) : VehicleData = { + VehicleData(basic.pos, basic.faction, basic.bops, basic.destroyed, 0, basic.jammered, false, basic.player_guid, + false, health, false, false, driveState, false, false, cloak, Some(format), inventory)(VehicleFormat.Utility) } /** * Overloaded constructor for specifically handling `Variant` vehicle format. - * @param basic data common to objects - * @param unk1 na + * @param basic a field that encompasses some data used by the vehicle, including `faction` and `owner` * @param health the amount of health the vehicle has, as a percentage of a filled bar (255) - * @param unk2 na - * @param driveState a representation for the current mobility state; - * @param unk3 na - * @param unk4 variant-specific field - * @param unk5 na + * @param driveState a representation for the current mobility state + * @param cloak if a vehicle (that can cloak) is cloaked * @param inventory the seats, mounted weapons, and utilities (such as terminals) that are currently included - * @return a `VehicleData` object */ - def apply(basic : CommonFieldData, unk1 : Int, health : Int, unk2 : Int, driveState : DriveState.Value, unk3 : Boolean, unk4 : VariantVehicleData, unk5 : Int, inventory : Option[InventoryData]) : VehicleData = { - new VehicleData(basic, unk1, health, unk2>0, false, driveState, unk3, unk5>0, false, Some(unk4), inventory)(VehicleFormat.Variant) + def apply(basic : CommonFieldData, health : Int, driveState : DriveState.Value, cloak : Boolean, format : VariantVehicleData, inventory : Option[InventoryData]) : VehicleData = { + VehicleData(basic.pos, basic.faction, basic.bops, basic.destroyed, 0, basic.jammered, false, basic.player_guid, + false, health, false, false, driveState, false, false, cloak, Some(format), inventory)(VehicleFormat.Variant) } import net.psforever.packet.game.objectcreate.{PlayerData => Player_Data} @@ -237,41 +240,43 @@ object VehicleData extends Marshallable[VehicleData] { def codec(vehicle_type : VehicleFormat.Value) : Codec[VehicleData] = { import shapeless.:: ( - ("basic" | CommonFieldData.codec) >>:~ { com => - ("unk1" | uint2L) :: + ("pos" | PlacementData.codec) >>:~ { pos => + ("faction" | PlanetSideEmpire.codec) :: + ("bops" | bool) :: + ("destroyed" | bool) :: + ("unk1" | uint2L) :: //3 - na, 2 - common, 1 - na, 0 - common? + ("jammered" | bool) :: + ("unk2" | bool) :: + ("owner_guid" | PlanetSideGUID.codec) :: + ("unk3" | bool) :: ("health" | uint8L) :: - ("unk2" | bool) :: //usually 0 + ("unk4" | bool) :: //usually 0 ("no_mount_points" | bool) :: ("driveState" | driveState8u) :: //used for deploy state - ("unk3" | bool) :: //unknown but generally false; can cause stream misalignment if set when unexpectedly - ("unk4" | bool) :: + ("unk5" | bool) :: //unknown but generally false; can cause stream misalignment if set when unexpectedly + ("unk6" | bool) :: ("cloak" | bool) :: //cloak as wraith, phantasm - conditional(vehicle_type != VehicleFormat.Normal, "unk5" | selectFormatReader(vehicle_type)) :: //padding? - optional(bool, "inventory" | custom_inventory_codec(InitialStreamLengthToSeatEntries(com.pos.vel.isDefined, vehicle_type))) + conditional(vehicle_type != VehicleFormat.Normal, "vehicle_format_data" | selectFormatReader(vehicle_type)) :: //padding? + optional(bool, "inventory" | custom_inventory_codec(InitialStreamLengthToSeatEntries(pos.vel.isDefined, vehicle_type))) } ).exmap[VehicleData] ( { - case basic :: u1 :: health :: u2 :: no_mount :: driveState :: u3 :: u4 :: u5 :: cloak :: inv :: HNil => - Attempt.successful(new VehicleData(basic, u1, health, u2, no_mount, driveState, u3, u4, u5, cloak, inv)(vehicle_type)) + case pos :: faction :: bops :: destroyed :: u1 :: jamd :: u2 :: owner :: u3 :: health :: u4 :: no_mount :: driveState :: u5 :: u6 :: cloak :: format :: inv :: HNil => + Attempt.successful(new VehicleData(pos, faction, bops, destroyed, u1, jamd, u2, owner, u3, health, u4, no_mount, driveState, u5, u6, cloak, format, inv)(vehicle_type)) case _ => Attempt.failure(Err("invalid vehicle data format")) }, { - case obj @ VehicleData(basic, u1, health, u2, no_mount, driveState, u3, u4, cloak, Some(u5), inv) => - if(obj.vehicle_type == VehicleFormat.Normal) { - Attempt.failure(Err("invalid vehicle data format; variable bits not expected; will ignore ...")) + case obj @ VehicleData(pos, faction, bops, destroyed, u1, jamd, u2, owner, u3, health, u4, no_mount, driveState, u5, u6, cloak, format, inv) => + if(obj.vehicle_type == VehicleFormat.Normal && format.nonEmpty) { + Attempt.failure(Err("invalid vehicle data format; variable bits not expected")) + } + else if(obj.vehicle_type != VehicleFormat.Normal && format.isEmpty) { + Attempt.failure(Err(s"invalid vehicle data format; variable bits for ${obj.vehicle_type} expected")) } else { - Attempt.successful(basic :: u1 :: health :: u2 :: no_mount :: driveState :: u3 :: u4 :: cloak :: Some(u5) :: inv :: HNil) - } - - case obj @ VehicleData(basic, u1, health, u2, no_mount, driveState, u3, u4, cloak, None, inv) => - if(obj.vehicle_type != VehicleFormat.Normal) { - Attempt.failure(Err("invalid vehicle data format; variable bits expected")) - } - else { - Attempt.successful(basic :: u1 :: health :: u2 :: no_mount :: driveState :: u3 :: u4 :: cloak :: None :: inv :: HNil) + Attempt.successful(pos :: faction :: bops :: destroyed :: u1 :: jamd :: u2 :: owner :: u3 :: health :: u4 :: no_mount :: driveState :: u5 :: u6 :: cloak :: format :: inv :: HNil) } case _ => @@ -284,7 +289,11 @@ object VehicleData extends Marshallable[VehicleData] { * Distance from the length field of a vehicle creation packet up until the start of the vehicle's inventory data. * The only field excluded belongs to the original opcode for the packet. * The parameters outline reasons why the length of the stream would be different - * and are used to determine the exact difference value. + * and are used to determine the exact difference value.
+ * Note:
+ * 198 includes the `ObjectCreateMessage` packet fields, without parent data, + * the `VehicleData` fields, + * and the first three fields of the `InternalSlot`. * @see `ObjectCreateMessage` * @param hasVelocity the presence of a velocity field - `vel` - in the `PlacementData` object for this vehicle * @param format the `Codec` subtype for this vehicle @@ -333,7 +342,9 @@ object VehicleData extends Marshallable[VehicleData] { } /** - * A special method of handling mounted players within the same inventory space as normal `Equipment` can be encountered. + * A special method of handling mounted players within the same inventory space as normal `Equipment` can be encountered + * before restoring normal inventory operations.
+ *
* Due to variable-length fields within `PlayerData` extracted from the input, * the distance of the bit(stream) vector to the initial inventory entry is calculated * to produce the initial value for padding the `PlayerData` object's name field. @@ -341,7 +352,11 @@ object VehicleData extends Marshallable[VehicleData] { * the remainder of the inventory must be handled as standard inventory * and finally both groups must be repackaged into a single standard `InventoryData` object. * Due to the unique value for the mounted players that must be updated for each entry processed, - * the entries are temporarily formatted into a linked list before being put back into a normal `List`. + * the entries are temporarily formatted into a linked list before being put back into a normal `List`.
+ *
+ * 6 June 2018:
+ * Due to curious behavior in the vehicle seat access controls, + * please only encode and decode the driver seat even though all seats are currently reachable. * @param length the distance in bits to the first inventory entry * @return a `Codec` that translates `InventoryData` */ @@ -512,7 +527,7 @@ object VehicleData extends Marshallable[VehicleData] { case x :: Nil => Some(InventorySeat(Some(x), None)) case _ :: _ => - var link = InventorySeat(Some(list.last), None) + var link = InventorySeat(Some(list.last), None) //build the chain in reverse order, starting with the last entry list.reverse.drop(1).foreach(seat => { link = InventorySeat(Some(seat), Some(link)) }) diff --git a/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala b/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala index 188f762f..9f573970 100644 --- a/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala +++ b/common/src/test/scala/game/objectcreatedetailed/DetailedCharacterDataTest.scala @@ -503,18 +503,12 @@ class DetailedCharacterDataTest extends Specification { Nil ) val obj = DetailedPlayerData.apply(app, char, inv, DrawnSlot.Pistol1) + //it shouldn't be Pistol1 if he's seated but it's fine for the test val msg = ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), ObjectCreateMessageParent(PlanetSideGUID(43981), 0), obj) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt_bitv = pkt.toBitVector val ori_bitv = string_seated.toBitVector -// var test = pkt_bitv -// while(test.nonEmpty) { -// val (printHex, save) = test.splitAt(512) -// test = save -// println(printHex) -// } - pkt_bitv.take(16) mustEqual ori_bitv.take(16) pkt_bitv mustEqual ori_bitv } diff --git a/common/src/test/scala/game/objectcreatevehicle/DestroyedVehiclesTest.scala b/common/src/test/scala/game/objectcreatevehicle/DestroyedVehiclesTest.scala index fe51669f..84b243c7 100644 --- a/common/src/test/scala/game/objectcreatevehicle/DestroyedVehiclesTest.scala +++ b/common/src/test/scala/game/objectcreatevehicle/DestroyedVehiclesTest.scala @@ -4,7 +4,6 @@ package game.objectcreatevehicle import net.psforever.packet._ import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID} import net.psforever.packet.game.objectcreate._ -import net.psforever.types._ import org.specs2.mutable._ import scodec.bits._ diff --git a/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala b/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala index eb15db39..393bf678 100644 --- a/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala +++ b/common/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala @@ -25,23 +25,25 @@ class MountedVehiclesTest extends Specification { parent mustEqual None data match { case Some(vdata : VehicleData) => - vdata.basic.pos.coord mustEqual Vector3(4571.6875f, 5602.1875f, 93) - vdata.basic.pos.orient mustEqual Vector3(11.25f, 2.8125f, 92.8125f) - vdata.basic.pos.vel mustEqual Some(Vector3(31.71875f, 8.875f, -0.03125f)) - vdata.basic.faction mustEqual PlanetSideEmpire.TR - vdata.basic.bops mustEqual false - vdata.basic.destroyed mustEqual false - vdata.basic.jammered mustEqual false - vdata.basic.player_guid mustEqual PlanetSideGUID(1888) - vdata.unk1 mustEqual 0 + vdata.pos.coord mustEqual Vector3(4571.6875f, 5602.1875f, 93) + vdata.pos.orient mustEqual Vector3(11.25f, 2.8125f, 92.8125f) + vdata.pos.vel mustEqual Some(Vector3(31.71875f, 8.875f, -0.03125f)) + vdata.faction mustEqual PlanetSideEmpire.TR + vdata.bops mustEqual false + vdata.destroyed mustEqual false + vdata.jammered mustEqual false + vdata.owner_guid mustEqual PlanetSideGUID(3776) vdata.health mustEqual 255 - vdata.unk2 mustEqual false vdata.no_mount_points mustEqual false vdata.driveState mustEqual DriveState.Mobile - vdata.unk3 mustEqual false - vdata.unk5 mustEqual false vdata.cloak mustEqual false - vdata.unk4 mustEqual Some(VariantVehicleData(7)) + vdata.unk1 mustEqual 0 + vdata.unk2 mustEqual false + vdata.unk3 mustEqual false + vdata.unk4 mustEqual false + vdata.unk5 mustEqual false + vdata.unk6 mustEqual false + vdata.vehicle_format_data mustEqual Some(VariantVehicleData(7)) vdata.inventory match { case Some(InventoryData(list)) => list.head.objectClass mustEqual ObjectClass.avatar @@ -147,17 +149,18 @@ class MountedVehiclesTest extends Specification { ) val player = VehicleData.PlayerData(app, char, inv, DrawnSlot.None, VehicleData.InitialStreamLengthToSeatEntries(true, VehicleFormat.Variant)) val obj = VehicleData( - CommonFieldData( - PlacementData( - Vector3(4571.6875f, 5602.1875f, 93), - Vector3(11.25f, 2.8125f, 92.8125f), - Some(Vector3(31.71875f, 8.875f, -0.03125f)) - ), - PlanetSideEmpire.TR, - false, false, 0, false, - PlanetSideGUID(1888) + PlacementData( + Vector3(4571.6875f, 5602.1875f, 93), + Vector3(11.25f, 2.8125f, 92.8125f), + Some(Vector3(31.71875f, 8.875f, -0.03125f)) ), - 0, 255, + PlanetSideEmpire.TR, + false, false, + 0, + false, false, + PlanetSideGUID(3776), + false, + 255, false, false, DriveState.Mobile, false, false, false, diff --git a/common/src/test/scala/game/objectcreatevehicle/NormalVehiclesTest.scala b/common/src/test/scala/game/objectcreatevehicle/NormalVehiclesTest.scala index 9dd75b48..5d537227 100644 --- a/common/src/test/scala/game/objectcreatevehicle/NormalVehiclesTest.scala +++ b/common/src/test/scala/game/objectcreatevehicle/NormalVehiclesTest.scala @@ -24,16 +24,12 @@ class NormalVehiclesTest extends Specification { data.isDefined mustEqual true data.get.isInstanceOf[VehicleData] mustEqual true val fury = data.get.asInstanceOf[VehicleData] - fury.basic.pos.coord.x mustEqual 6531.961f - fury.basic.pos.coord.y mustEqual 1872.1406f - fury.basic.pos.coord.z mustEqual 24.734375f - fury.basic.pos.orient.x mustEqual 0f - fury.basic.pos.orient.y mustEqual 0f - fury.basic.pos.orient.z mustEqual 357.1875f - fury.basic.pos.vel.isDefined mustEqual false - fury.basic.faction mustEqual PlanetSideEmpire.VS - fury.basic.unk mustEqual 2 - fury.basic.player_guid mustEqual PlanetSideGUID(0) + fury.pos.coord mustEqual Vector3(6531.961f, 1872.1406f,24.734375f) + fury.pos.orient mustEqual Vector3(0, 0, 357.1875f) + fury.pos.vel mustEqual None + fury.faction mustEqual PlanetSideEmpire.VS + fury.unk1 mustEqual 2 + fury.owner_guid mustEqual PlanetSideGUID(0) fury.health mustEqual 255 // fury.inventory.isDefined mustEqual true @@ -69,16 +65,14 @@ class NormalVehiclesTest extends Specification { data.isDefined mustEqual true data.get.isInstanceOf[VehicleData] mustEqual true val lightning = data.get.asInstanceOf[VehicleData] - lightning.basic.pos.coord.x mustEqual 3674.8438f - lightning.basic.pos.coord.y mustEqual 2726.789f - lightning.basic.pos.coord.z mustEqual 91.15625f - lightning.basic.pos.orient.x mustEqual 0f - lightning.basic.pos.orient.y mustEqual 0f - lightning.basic.pos.orient.z mustEqual 90.0f - lightning.basic.faction mustEqual PlanetSideEmpire.VS - lightning.basic.unk mustEqual 2 - lightning.basic.player_guid mustEqual PlanetSideGUID(0) + lightning.pos.coord mustEqual Vector3(3674.8438f, 2726.789f, 91.15625f) + lightning.pos.orient mustEqual Vector3(0, 0, 90) + lightning.pos.vel mustEqual None + lightning.faction mustEqual PlanetSideEmpire.VS + lightning.unk1 mustEqual 2 + lightning.owner_guid mustEqual PlanetSideGUID(0) lightning.health mustEqual 255 + lightning.inventory.isDefined mustEqual true lightning.inventory.get.contents.size mustEqual 1 val mounting = lightning.inventory.get.contents.head @@ -120,22 +114,23 @@ class NormalVehiclesTest extends Specification { data.isDefined mustEqual true data.get.isInstanceOf[VehicleData] mustEqual true val deliverer = data.get.asInstanceOf[VehicleData] - deliverer.basic.pos.coord.x mustEqual 6531.961f - deliverer.basic.pos.coord.y mustEqual 1872.1406f - deliverer.basic.pos.coord.z mustEqual 24.734375f - deliverer.basic.pos.orient.x mustEqual 0f - deliverer.basic.pos.orient.y mustEqual 0f - deliverer.basic.pos.orient.z mustEqual 357.1875f - deliverer.basic.faction mustEqual PlanetSideEmpire.NC - deliverer.basic.unk mustEqual 2 - deliverer.basic.player_guid mustEqual PlanetSideGUID(0) - deliverer.unk1 mustEqual 0 + deliverer.pos.coord mustEqual Vector3(6531.961f, 1872.1406f, 24.734375f) + deliverer.pos.orient mustEqual Vector3(0, 0, 357.1875f) + deliverer.pos.vel mustEqual None + deliverer.faction mustEqual PlanetSideEmpire.NC + deliverer.owner_guid mustEqual PlanetSideGUID(0) deliverer.health mustEqual 255 - deliverer.unk2 mustEqual false deliverer.driveState mustEqual DriveState.State7 - deliverer.unk3 mustEqual true - deliverer.unk4 mustEqual None - deliverer.unk5 mustEqual false + deliverer.jammered mustEqual false + deliverer.destroyed mustEqual false + deliverer.cloak mustEqual false + deliverer.unk1 mustEqual 2 + deliverer.unk2 mustEqual false + deliverer.unk3 mustEqual false + deliverer.unk4 mustEqual false + deliverer.unk5 mustEqual true + deliverer.unk6 mustEqual false + deliverer.vehicle_format_data mustEqual None deliverer.inventory.isDefined mustEqual true deliverer.inventory.get.contents.size mustEqual 2 //0 @@ -179,11 +174,13 @@ class NormalVehiclesTest extends Specification { "encode (fury)" in { val obj = VehicleData( - CommonFieldData( - PlacementData(6531.961f, 1872.1406f, 24.734375f, 0f, 0f, 357.1875f), - PlanetSideEmpire.VS, 2 - ), - 0, + PlacementData(6531.961f, 1872.1406f, 24.734375f, 0f, 0f, 357.1875f), + PlanetSideEmpire.VS, + false, false, + 2, + false, false, + PlanetSideGUID(0), + false, 255, false, false, DriveState.Mobile, @@ -203,11 +200,13 @@ class NormalVehiclesTest extends Specification { "encode (lightning)" in { val obj = VehicleData( - CommonFieldData( - PlacementData(3674.8438f, 2726.789f, 91.15625f, 0f, 0f, 90.0f), - PlanetSideEmpire.VS, 2 - ), - 0, + PlacementData(3674.8438f, 2726.789f, 91.15625f, 0f, 0f, 90.0f), + PlanetSideEmpire.VS, + false, false, + 2, + false, false, + PlanetSideGUID(0), + false, 255, false, false, DriveState.Mobile, @@ -227,11 +226,13 @@ class NormalVehiclesTest extends Specification { "encode (medium transport)" in { val obj = VehicleData( - CommonFieldData( - PlacementData(6531.961f, 1872.1406f, 24.734375f, 0f, 0f, 357.1875f), - PlanetSideEmpire.NC, 2 - ), - 0, + PlacementData(6531.961f, 1872.1406f, 24.734375f, 0f, 0f, 357.1875f), + PlanetSideEmpire.NC, + false, false, + 2, + false, false, + PlanetSideGUID(0), + false, 255, false, false, DriveState.State7, diff --git a/common/src/test/scala/game/objectcreatevehicle/UtilityVehiclesTest.scala b/common/src/test/scala/game/objectcreatevehicle/UtilityVehiclesTest.scala index 02f43faf..0c081a16 100644 --- a/common/src/test/scala/game/objectcreatevehicle/UtilityVehiclesTest.scala +++ b/common/src/test/scala/game/objectcreatevehicle/UtilityVehiclesTest.scala @@ -11,120 +11,122 @@ import scodec.bits._ class UtilityVehiclesTest extends Specification { val string_ant = hex"17 C2000000 9E0 7C01 6C2D7 65535 CA16 00 00 00 4400003FC000000" val string_ams = hex"17 B8010000 970 3D10 002D765535CA16000000 402285BB0037E4100749E1D03000000620D83A0A00000195798741C00000332E40D84800000" - val string_ams_seated = - hex"17ec060000970fe0f030898abda28127f007ff9c1f2f80c0001e18ff00001051e40786400000008c50004c0041006d0069006e006700790075006500540052007c00000304217c859e8080000000000000002503420022c02a002a002a002a0050004c0041002a002a002a002a00010027e300940000016c0400023c040002285a086c2f00c80000000000300210288740800000004046f17423018000002c4d6190400000001010704a86406000002bc770842000000004041c5f21d01800000e075821902000000623e84208000001950588c1800000332ea0f840000000" +// val string_ams_seated = +// hex"17ec060000970fe0f030898abda28127f007ff9c1f2f80c0001e18ff00001051e40786400000008c50004c0041006d0069006e006700790075006500540052007c00000304217c859e8080000000000000002503420022c02a002a002a002a0050004c0041002a002a002a002a00010027e300940000016c0400023c040002285a086c2f00c80000000000300210288740800000004046f17423018000002c4d6190400000001010704a86406000002bc770842000000004041c5f21d01800000e075821902000000623e84208000001950588c1800000332ea0f840000000" "Utility vehicles" should { -// "decode (ant)" in { -// PacketCoding.DecodePacket(string_ant).require match { -// case ObjectCreateMessage(len, cls, guid, parent, data) => -// len mustEqual 194L -// cls mustEqual ObjectClass.ant -// guid mustEqual PlanetSideGUID(380) -// parent.isDefined mustEqual false -// data.isDefined mustEqual true -// data.get.isInstanceOf[VehicleData] mustEqual true -// val ant = data.get.asInstanceOf[VehicleData] -// ant.basic.pos.coord.x mustEqual 3674.8438f -// ant.basic.pos.coord.y mustEqual 2726.789f -// ant.basic.pos.coord.z mustEqual 91.15625f -// ant.basic.pos.orient.x mustEqual 0f -// ant.basic.pos.orient.y mustEqual 0f -// ant.basic.pos.orient.z mustEqual 90.0f -// ant.basic.faction mustEqual PlanetSideEmpire.VS -// ant.basic.unk mustEqual 2 -// ant.basic.player_guid mustEqual PlanetSideGUID(0) -// ant.health mustEqual 255 -// ant.driveState mustEqual DriveState.Mobile -// case _ => -// ko -// } -// } -// -// "decode (ams)" in { -// PacketCoding.DecodePacket(string_ams).require match { -// case ObjectCreateMessage(len, cls, guid, parent, data) => -// len mustEqual 440L -// cls mustEqual ObjectClass.ams -// guid mustEqual PlanetSideGUID(4157) -// parent.isDefined mustEqual false -// data.isDefined mustEqual true -// data.get.isInstanceOf[VehicleData] mustEqual true -// val ams = data.get.asInstanceOf[VehicleData] -// ams.basic.pos.coord.x mustEqual 3674.0f -// ams.basic.pos.coord.y mustEqual 2726.789f -// ams.basic.pos.coord.z mustEqual 91.15625f -// ams.basic.pos.orient.x mustEqual 0f -// ams.basic.pos.orient.y mustEqual 0f -// ams.basic.pos.orient.z mustEqual 90.0f -// ams.basic.faction mustEqual PlanetSideEmpire.VS -// ams.basic.unk mustEqual 0 -// ams.basic.player_guid mustEqual PlanetSideGUID(34082) -// ams.unk1 mustEqual 2 -// ams.health mustEqual 236 -// ams.unk2 mustEqual false -// ams.driveState mustEqual DriveState.Deployed -// -// ams.inventory.isDefined mustEqual true -// val inv = ams.inventory.get.contents -// inv.head.objectClass mustEqual ObjectClass.matrix_terminalc -// inv.head.guid mustEqual PlanetSideGUID(3663) -// inv.head.parentSlot mustEqual 1 -// inv.head.obj.isInstanceOf[CommonTerminalData] mustEqual true -// inv(1).objectClass mustEqual ObjectClass.ams_respawn_tube -// inv(1).guid mustEqual PlanetSideGUID(3638) -// inv(1).parentSlot mustEqual 2 -// inv(1).obj.isInstanceOf[CommonTerminalData] mustEqual true -// inv(2).objectClass mustEqual ObjectClass.order_terminala -// inv(2).guid mustEqual PlanetSideGUID(3827) -// inv(2).parentSlot mustEqual 3 -// inv(2).obj.isInstanceOf[CommonTerminalData] mustEqual true -// inv(3).objectClass mustEqual ObjectClass.order_terminalb -// inv(3).guid mustEqual PlanetSideGUID(3556) -// inv(3).parentSlot mustEqual 4 -// inv(3).obj.isInstanceOf[CommonTerminalData] mustEqual true -// case _ => -// ko -// } -// } -// - "decode (ams, seated)" in { - PacketCoding.DecodePacket(string_ams_seated).require match { + "decode (ant)" in { + PacketCoding.DecodePacket(string_ant).require match { case ObjectCreateMessage(len, cls, guid, parent, data) => + len mustEqual 194L + cls mustEqual ObjectClass.ant + guid mustEqual PlanetSideGUID(380) + parent.isDefined mustEqual false data.isDefined mustEqual true + data.get.isInstanceOf[VehicleData] mustEqual true + val ant = data.get.asInstanceOf[VehicleData] + ant.pos.coord mustEqual Vector3(3674.8438f, 2726.789f, 91.15625f) + ant.pos.orient mustEqual Vector3(0, 0, 90) + ant.faction mustEqual PlanetSideEmpire.VS + ant.owner_guid mustEqual PlanetSideGUID(0) + ant.driveState mustEqual DriveState.Mobile + ant.health mustEqual 255 + ant.jammered mustEqual false + ant.destroyed mustEqual false + ant.cloak mustEqual false + ant.unk1 mustEqual 2 + ant.unk2 mustEqual false + ant.unk3 mustEqual false + ant.unk4 mustEqual false + ant.unk5 mustEqual false + ant.unk6 mustEqual false case _ => ko } } -// -// "encode (ant)" in { -// val obj = VehicleData( -// CommonFieldData( -// PlacementData(3674.8438f, 2726.789f, 91.15625f, 0f, 0f, 90.0f), -// PlanetSideEmpire.VS, 2 -// ), -// 0, -// 255, -// false, false, -// DriveState.Mobile, -// false, false, false, -// Some(UtilityVehicleData(0)), -// None -// )(VehicleFormat.Utility) -// val msg = ObjectCreateMessage(ObjectClass.ant, PlanetSideGUID(380), obj) -// val pkt = PacketCoding.EncodePacket(msg).require.toByteVector -// -// pkt mustEqual string_ant -// } + + "decode (ams)" in { + PacketCoding.DecodePacket(string_ams).require match { + case ObjectCreateMessage(len, cls, guid, parent, data) => + len mustEqual 440L + cls mustEqual ObjectClass.ams + guid mustEqual PlanetSideGUID(4157) + parent.isDefined mustEqual false + data.isDefined mustEqual true + data.get.isInstanceOf[VehicleData] mustEqual true + val ams = data.get.asInstanceOf[VehicleData] + ams.pos.coord mustEqual Vector3(3674, 2726.789f, 91.15625f) + ams.pos.orient mustEqual Vector3(0, 0, 90) + ams.pos.vel mustEqual None + ams.faction mustEqual PlanetSideEmpire.VS + ams.owner_guid mustEqual PlanetSideGUID(2885) + ams.driveState mustEqual DriveState.Deployed + ams.vehicle_format_data mustEqual Some(UtilityVehicleData(60)) + ams.health mustEqual 236 + ams.jammered mustEqual false + ams.destroyed mustEqual false + ams.cloak mustEqual true + ams.unk1 mustEqual 0 + ams.unk2 mustEqual false + ams.unk3 mustEqual false + ams.unk4 mustEqual false + ams.unk5 mustEqual false + ams.unk6 mustEqual true + + ams.inventory.isDefined mustEqual true + val inv = ams.inventory.get.contents + inv.head.objectClass mustEqual ObjectClass.matrix_terminalc + inv.head.guid mustEqual PlanetSideGUID(3663) + inv.head.parentSlot mustEqual 1 + inv.head.obj.isInstanceOf[CommonTerminalData] mustEqual true + inv(1).objectClass mustEqual ObjectClass.ams_respawn_tube + inv(1).guid mustEqual PlanetSideGUID(3638) + inv(1).parentSlot mustEqual 2 + inv(1).obj.isInstanceOf[CommonTerminalData] mustEqual true + inv(2).objectClass mustEqual ObjectClass.order_terminala + inv(2).guid mustEqual PlanetSideGUID(3827) + inv(2).parentSlot mustEqual 3 + inv(2).obj.isInstanceOf[CommonTerminalData] mustEqual true + inv(3).objectClass mustEqual ObjectClass.order_terminalb + inv(3).guid mustEqual PlanetSideGUID(3556) + inv(3).parentSlot mustEqual 4 + inv(3).obj.isInstanceOf[CommonTerminalData] mustEqual true + case _ => + ko + } + } + + "encode (ant)" in { + val obj = VehicleData( + PlacementData(3674.8438f, 2726.789f, 91.15625f, 0f, 0f, 90.0f), + PlanetSideEmpire.VS, + false, false, + 2, + false, false, + PlanetSideGUID(0), + false, + 255, + false, false, + DriveState.Mobile, + false, false, false, + Some(UtilityVehicleData(0)), + None + )(VehicleFormat.Utility) + val msg = ObjectCreateMessage(ObjectClass.ant, PlanetSideGUID(380), obj) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_ant + } "encode (ams)" in { val obj = VehicleData( - CommonFieldData( - PlacementData(3674.0f, 2726.789f, 91.15625f, 0f, 0f, 90.0f), - PlanetSideEmpire.VS, 0, - PlanetSideGUID(34082) - ), - 2, + PlacementData(3674.0f, 2726.789f, 91.15625f, 0f, 0f, 90.0f), + PlanetSideEmpire.VS, + false, false, + 0, + false, false, + PlanetSideGUID(2885), + false, 236, false, false, DriveState.Deployed, diff --git a/common/src/test/scala/game/objectcreatevehicle/VariantVehiclesTest.scala b/common/src/test/scala/game/objectcreatevehicle/VariantVehiclesTest.scala index c1203d6c..5924cda1 100644 --- a/common/src/test/scala/game/objectcreatevehicle/VariantVehiclesTest.scala +++ b/common/src/test/scala/game/objectcreatevehicle/VariantVehiclesTest.scala @@ -22,14 +22,14 @@ class VariantVehiclesTest extends Specification { data.isDefined mustEqual true data.get.isInstanceOf[VehicleData] mustEqual true val switchblade = data.get.asInstanceOf[VehicleData] - switchblade.basic.pos.coord.x mustEqual 6531.961f - switchblade.basic.pos.coord.y mustEqual 1872.1406f - switchblade.basic.pos.coord.z mustEqual 24.734375f - switchblade.basic.pos.orient.x mustEqual 0f - switchblade.basic.pos.orient.y mustEqual 0f - switchblade.basic.pos.orient.z mustEqual 357.1875f - switchblade.basic.faction mustEqual PlanetSideEmpire.VS - switchblade.basic.unk mustEqual 2 + switchblade.pos.coord.x mustEqual 6531.961f + switchblade.pos.coord.y mustEqual 1872.1406f + switchblade.pos.coord.z mustEqual 24.734375f + switchblade.pos.orient.x mustEqual 0f + switchblade.pos.orient.y mustEqual 0f + switchblade.pos.orient.z mustEqual 357.1875f + switchblade.faction mustEqual PlanetSideEmpire.VS + switchblade.unk1 mustEqual 2 switchblade.health mustEqual 255 switchblade.driveState mustEqual DriveState.Mobile switchblade.inventory.isDefined mustEqual true @@ -61,12 +61,13 @@ class VariantVehiclesTest extends Specification { "encode (switchblade)" in { val obj = VehicleData( - CommonFieldData( - PlacementData(6531.961f, 1872.1406f, 24.734375f, 0f, 0f, 357.1875f), - PlanetSideEmpire.VS, - 2 - ), - 0, + PlacementData(6531.961f, 1872.1406f, 24.734375f, 0f, 0f, 357.1875f), + PlanetSideEmpire.VS, + false, false, + 2, + false, false, + PlanetSideGUID(0), + false, 255, false, false, DriveState.Mobile, diff --git a/common/src/test/scala/objects/VehicleSpawnPadTest.scala b/common/src/test/scala/objects/VehicleSpawnPadTest.scala index 2c9e1ba7..8e3f240c 100644 --- a/common/src/test/scala/objects/VehicleSpawnPadTest.scala +++ b/common/src/test/scala/objects/VehicleSpawnPadTest.scala @@ -49,7 +49,7 @@ class VehicleSpawnControl1Test extends ActorTest() { } class VehicleSpawnControl2aTest extends ActorTest() { - // This long runs for a long time. + // This runs for a long time. "VehicleSpawnControl" should { "complete on a vehicle order (block a second one until the first is done and the spawn pad is cleared)" in { val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR) @@ -102,18 +102,18 @@ class VehicleSpawnControl2aTest extends ActorTest() { //if we move the vehicle more than 25m away from the pad, we should receive a ResetSpawnPad, and a second ConcealPlayer message //that means that the first order has cleared and the spawn pad is now working on the second order successfully - vehicle.Position = Vector3(11,0,0) player.VehicleSeated = None //since shared between orders, is necessary + vehicle.Position = Vector3(12,0,0) val probe3Msg5 = probe3.receiveOne(4 seconds) assert(probe3Msg5.isInstanceOf[VehicleSpawnPad.ResetSpawnPad]) - val probe3Msg6 = probe3.receiveOne(5 seconds) + val probe3Msg6 = probe3.receiveOne(4 seconds) assert(probe3Msg6.isInstanceOf[VehicleSpawnPad.ConcealPlayer]) } } } class VehicleSpawnControl2bTest extends ActorTest() { - // This long runs for a long time. + // This runs for a long time. "VehicleSpawnControl" should { "complete on a vehicle order (railless)" in { val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR) @@ -144,7 +144,7 @@ class VehicleSpawnControl2bTest extends ActorTest() { assert(probe1Msg2.isInstanceOf[Mountable.MountMessages]) val probe1Msg2Contents = probe1Msg2.asInstanceOf[Mountable.MountMessages] assert(probe1Msg2Contents.response.isInstanceOf[Mountable.CanMount]) - val probe1Msg3 = probe1.receiveOne(3 seconds) + val probe1Msg3 = probe1.receiveOne(4 seconds) assert(probe1Msg3.isInstanceOf[VehicleSpawnPad.PlayerSeatedInVehicle]) val probe1Msg4 = probe1.receiveOne(1 seconds) @@ -161,9 +161,9 @@ class VehicleSpawnControl2bTest extends ActorTest() { //if we move the vehicle more than 10m away from the pad, we should receive a second ConcealPlayer message //that means that the first order has cleared and the spawn pad is now working on the second order successfully - vehicle.Position = Vector3(11,0,0) player.VehicleSeated = None //since shared between orders, is necessary - val probe3Msg6 = probe3.receiveOne(4 seconds) + vehicle.Position = Vector3(12,0,0) + val probe3Msg6 = probe3.receiveOne(10 seconds) assert(probe3Msg6.isInstanceOf[VehicleSpawnPad.ConcealPlayer]) } } @@ -263,9 +263,12 @@ class VehicleSpawnControl5Test extends ActorTest() { val probe3Msg4 = probe3.receiveOne(3 seconds) assert(probe3Msg4.isInstanceOf[VehicleSpawnPad.DetachFromRails]) - val probe1Msg = probe1.receiveOne(12 seconds) - assert(probe1Msg.isInstanceOf[VehicleSpawnPad.PeriodicReminder]) - assert(probe1Msg.asInstanceOf[VehicleSpawnPad.PeriodicReminder].reason == VehicleSpawnPad.Reminders.Blocked) + val probe1Msg1 = probe1.receiveOne(1 seconds) + assert(probe1Msg1.isInstanceOf[VehicleSpawnPad.RevealPlayer]) + + val probe1Msg2 = probe1.receiveOne(12 seconds) + assert(probe1Msg2.isInstanceOf[VehicleSpawnPad.PeriodicReminder]) + assert(probe1Msg2.asInstanceOf[VehicleSpawnPad.PeriodicReminder].reason == VehicleSpawnPad.Reminders.Blocked) } } } @@ -292,59 +295,17 @@ class VehicleSpawnControl6Test extends ActorTest() { val probe1Msg1 = probe1.receiveOne(200 milliseconds) assert(probe1Msg1.isInstanceOf[VehicleSpawnPad.StartPlayerSeatedInVehicle]) - player.Continent = "problem" //problem 1 + player.Continent = "problem" //problem probe1.receiveOne(200 milliseconds) //Mountable.MountMessage val probe3Msg4 = probe3.receiveOne(3 seconds) assert(probe3Msg4.isInstanceOf[VehicleSpawnPad.DetachFromRails]) - val probe3Msg5 = probe3.receiveOne(3 seconds) - assert(probe3Msg5.isInstanceOf[VehicleSpawnPad.ResetSpawnPad]) + val probe1Msg2 = probe1.receiveOne(3 seconds) + assert(probe1Msg2.isInstanceOf[VehicleSpawnPad.RevealPlayer]) - val probe1Msg2 = probe1.receiveOne(12 seconds) - assert(probe1Msg2.isInstanceOf[VehicleSpawnPad.PeriodicReminder]) - assert(probe1Msg2.asInstanceOf[VehicleSpawnPad.PeriodicReminder].reason == VehicleSpawnPad.Reminders.Blocked) - } - } -} - -class VehicleSpawnControl7Test extends ActorTest() { - "VehicleSpawnControl" should { - "player dies after getting in driver seat; the vehicle blocks the pad" in { - val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR) - //we can recycle the vehicle and the player for each order - val probe1 = new TestProbe(system, "first-order") - val probe3 = new TestProbe(system, "zone-events") - zone.VehicleEvents = probe3.ref - - pad.Actor.tell(VehicleSpawnPad.VehicleOrder(player, vehicle), probe1.ref) - - val probe3Msg1 = probe3.receiveOne(3 seconds) - assert(probe3Msg1.isInstanceOf[VehicleSpawnPad.ConcealPlayer]) - - val probe3Msg2 = probe3.receiveOne(3 seconds) - assert(probe3Msg2.isInstanceOf[VehicleSpawnPad.LoadVehicle]) - - val probe3Msg3 = probe3.receiveOne(3 seconds) - assert(probe3Msg3.isInstanceOf[VehicleSpawnPad.AttachToRails]) - - val probe1Msg1 = probe1.receiveOne(200 milliseconds) - assert(probe1Msg1.isInstanceOf[VehicleSpawnPad.StartPlayerSeatedInVehicle]) - val probe1Msg2 = probe1.receiveOne(200 milliseconds) - assert(probe1Msg2.isInstanceOf[Mountable.MountMessages]) - val probe1Msg2Contents = probe1Msg2.asInstanceOf[Mountable.MountMessages] - assert(probe1Msg2Contents.response.isInstanceOf[Mountable.CanMount]) - val probe1Msg3 = probe1.receiveOne(3 seconds) - assert(probe1Msg3.isInstanceOf[VehicleSpawnPad.PlayerSeatedInVehicle]) - player.Die //problem - - val probe3Msg4 = probe3.receiveOne(3 seconds) - assert(probe3Msg4.isInstanceOf[VehicleSpawnPad.DetachFromRails]) - val probe3Msg5 = probe3.receiveOne(100 milliseconds) - assert(probe3Msg5.isInstanceOf[VehicleSpawnPad.ResetSpawnPad]) - - val probe1Msg4 = probe1.receiveOne(12 seconds) - assert(probe1Msg4.isInstanceOf[VehicleSpawnPad.PeriodicReminder]) - assert(probe1Msg4.asInstanceOf[VehicleSpawnPad.PeriodicReminder].reason == VehicleSpawnPad.Reminders.Blocked) + val probe1Msg3 = probe1.receiveOne(12 seconds) + assert(probe1Msg3.isInstanceOf[VehicleSpawnPad.PeriodicReminder]) + assert(probe1Msg3.asInstanceOf[VehicleSpawnPad.PeriodicReminder].reason == VehicleSpawnPad.Reminders.Blocked) } } } @@ -383,7 +344,9 @@ object VehicleSpawnPadControlTest { player.GUID = PlanetSideGUID(10) player.Continent = zone.Id player.Spawn - //note: pad and vehicle are both at Vector3(0,0,0) so they count as blocking + //note: pad and vehicle are both at Vector3(1,0,0) so they count as blocking + pad.Position = Vector3(1,0,0) + vehicle.Position = Vector3(1,0,0) (vehicle, player, pad, zone) } } diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index aa9867ad..6cbe1175 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -154,18 +154,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - player.VehicleOwned match { - case Some(vehicle_guid) => - continent.GUID(vehicle_guid) match { - case Some(vehicle : Vehicle) => - vehicle.Owner = None - //TODO temporary solution; to un-own, permit driver seat to Empire access level - vehicle.PermissionGroup(10, VehicleLockState.Empire.id) - vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.SeatPermissions(player_guid, vehicle_guid, 10, VehicleLockState.Empire.id)) - case _ => ; - } - case None => ; - } + DisownVehicle() continent.Population ! Zone.Population.Leave(avatar) } } @@ -298,9 +287,9 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(pkt) } - case AvatarResponse.LoadPlayer(pdata) => + case AvatarResponse.LoadPlayer(pkt) => if(tplayer_guid != guid) { - sendResponse(ObjectCreateMessage(ObjectClass.avatar, guid, pdata)) + sendResponse(pkt) } case AvatarResponse.ObjectDelete(item_guid, unk) => @@ -423,8 +412,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case VehicleServiceResponse(_, guid, reply) => val tplayer_guid = if(player.HasGUID) { player.GUID} else { PlanetSideGUID(0) } reply match { - case VehicleResponse.Awareness(vehicle_guid) => - //resets exclamation point fte marker (once) + case VehicleResponse.Ownership(vehicle_guid) => sendResponse(PlanetsideAttributeMessage(guid, 21, vehicle_guid.guid.toLong)) case VehicleResponse.AttachToRails(vehicle_guid, pad_guid) => @@ -704,7 +692,13 @@ class WorldSessionActor extends Actor with MDCContextAware { case Mountable.CanDismount(obj : Mountable, _) => log.warn(s"DismountVehicleMsg: $obj is some generic mountable object and nothing will happen") - case Mountable.CanNotMount(obj, seat_num) => + case Mountable.CanNotMount(obj : Vehicle, seat_num) => + log.warn(s"MountVehicleMsg: $tplayer attempted to mount $obj's seat $seat_num, but was not allowed") + if(obj.SeatPermissionGroup(seat_num) == Some(AccessPermissionGroup.Driver)) { + sendResponse(ChatMsg(ChatMessageType.CMT_OPEN, false, "", "You are not the driver of this vehicle.", None)) + } + + case Mountable.CanNotMount(obj : Mountable, seat_num) => log.warn(s"MountVehicleMsg: $tplayer attempted to mount $obj's seat $seat_num, but was not allowed") case Mountable.CanNotDismount(obj, seat_num) => @@ -1139,7 +1133,7 @@ class WorldSessionActor extends Actor with MDCContextAware { avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectHeld(player.GUID, player.LastDrawnSlot)) } sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 1L)) //mount points off? - sendResponse(PlanetsideAttributeMessage(vehicle_guid, 21, player.GUID.guid)) //fte and ownership? + sendResponse(PlanetsideAttributeMessage(vehicle_guid, 21, player.GUID.guid)) case VehicleSpawnPad.PlayerSeatedInVehicle(vehicle, pad) => val vehicle_guid = vehicle.GUID @@ -1216,6 +1210,7 @@ class WorldSessionActor extends Actor with MDCContextAware { RemoveCharacterSelectScreenGUID(player) sendResponse(CharacterInfoMessage(0, PlanetSideZoneID(1), 0, PlanetSideGUID(0), true, 0)) + sendResponse(CharacterInfoMessage(0, PlanetSideZoneID(1), 0, PlanetSideGUID(0), true, 0)) case VehicleLoaded(_/*vehicle*/) => ; //currently being handled by VehicleSpawnPad.LoadVehicle during testing phase @@ -1318,6 +1313,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } } else { + DisownVehicle() continent.Population ! Zone.Population.Leave(avatar) val original = player //TODO check player orientation upon spawn not polluted @@ -1450,19 +1446,29 @@ class WorldSessionActor extends Actor with MDCContextAware { player = tplayer val guid = tplayer.GUID StartBundlingPackets() - sendResponse(SetCurrentAvatarMessage(guid,0,0)) + sendResponse(SetCurrentAvatarMessage(guid, 0, 0)) sendResponse(ChatMsg(ChatMessageType.CMT_EXPANSIONS, true, "", "1 on", None)) //CC on //TODO once per respawn? sendResponse(PlayerStateShiftMessage(ShiftState(1, tplayer.Position, tplayer.Orientation.z))) + //transfer vehicle ownership + player.VehicleOwned match { + case Some(vehicle_guid) => + continent.GUID(vehicle_guid) match { + case Some(vehicle : Vehicle) => + vehicle.Owner = player + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.Ownership(guid, vehicle_guid)) + case _ => + player.VehicleOwned = None + } + case None => ; + } if(spectator) { sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, false, "", "on", None)) } - (0 until DetailedCharacterData.numberOfImplantSlots(tplayer.BEP)).foreach(slot => { sendResponse(AvatarImplantMessage(guid, ImplantAction.Initialization, slot, 1)) //init implant slot sendResponse(AvatarImplantMessage(guid, ImplantAction.Activation, slot, 0)) //deactivate implant //TODO if this implant is Installed but does not have shortcut, add to a free slot or write over slot 61/62/63 }) - sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 82, 0)) //TODO if Medkit does not have shortcut, add to a free slot or write over slot 64 sendResponse(CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT)) @@ -1470,20 +1476,21 @@ class WorldSessionActor extends Actor with MDCContextAware { //FavoritesMessage sendResponse(SetChatFilterMessage(ChatChannel.Local, false, ChatChannel.values.toList)) //TODO will not always be "on" like this deadState = DeadState.Alive - sendResponse(AvatarDeadStateMessage(DeadState.Alive, 0,0, tplayer.Position, player.Faction, true)) + sendResponse(AvatarDeadStateMessage(DeadState.Alive, 0, 0, tplayer.Position, player.Faction, true)) sendResponse(PlanetsideAttributeMessage(guid, 53, 1)) - sendResponse(AvatarSearchCriteriaMessage(guid, List(0,0,0,0,0,0))) + sendResponse(AvatarSearchCriteriaMessage(guid, List(0, 0, 0, 0, 0, 0))) (1 to 73).foreach(i => { sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(i), 67, 0)) }) - (0 to 30).foreach(i => { //TODO 30 for a new character only? + (0 to 30).foreach(i => { + //TODO 30 for a new character only? sendResponse(AvatarStatisticsMessage(2, Statistics(0L))) }) //AvatarAwardMessage //DisplayAwardMessage //SquadDefinitionActionMessage and SquadDetailDefinitionUpdateMessage - //MapObjectStateBlockMessage and ObjectCreateMessage - //TacticsMessage + //MapObjectStateBlockMessage and ObjectCreateMessage? + //TacticsMessage? StopBundlingPackets() case ItemHacking(tplayer, target, tool_guid, delta, completeAction, tickAction) => @@ -1652,7 +1659,8 @@ class WorldSessionActor extends Actor with MDCContextAware { continent.LivePlayers .filterNot(tplayer => { tplayer.GUID == player.GUID || tplayer.VehicleSeated.nonEmpty }) .foreach(char => { - sendResponse(ObjectCreateMessage(ObjectClass.avatar, char.GUID, char.Definition.Packet.ConstructorData(char).get)) + val tdefintion = char.Definition + sendResponse(ObjectCreateMessage(tdefintion.ObjectId, char.GUID, char.Definition.Packet.ConstructorData(char).get)) if(char.UsingSpecial == SpecialExoSuitDefinition.Mode.Anchored) { sendResponse(PlanetsideAttributeMessage(char.GUID, 19, 1)) } @@ -1663,8 +1671,24 @@ class WorldSessionActor extends Actor with MDCContextAware { } //load active vehicles in zone continent.Vehicles.foreach(vehicle => { - val definition = vehicle.Definition - sendResponse(ObjectCreateMessage(definition.ObjectId, vehicle.GUID, definition.Packet.ConstructorData(vehicle).get)) + val vehicle_guid = vehicle.GUID + val vdefinition = vehicle.Definition + sendResponse(ObjectCreateMessage(vdefinition.ObjectId, vehicle_guid, vdefinition.Packet.ConstructorData(vehicle).get)) + //occupants other than driver + vehicle.Seats + .filter({ case(index, seat) => seat.isOccupied && index > 0 }) + .foreach({ case(index, seat) => + val tplayer = seat.Occupant.get + val tdefintion = tplayer.Definition + sendResponse( + ObjectCreateMessage( + tdefintion.ObjectId, + tplayer.GUID, + ObjectCreateMessageParent(vehicle_guid, index), + tdefintion.Packet.ConstructorData(tplayer).get + ) + ) + }) ReloadVehicleAccessPermissions(vehicle) }) //implant terminals @@ -1675,7 +1699,7 @@ class WorldSessionActor extends Actor with MDCContextAware { val objDef = obj.Definition sendResponse( ObjectCreateMessage( - ObjectClass.implant_terminal_interface, + objDef.ObjectId, PlanetSideGUID(interface_guid), ObjectCreateMessageParent(parent_guid, 1), objDef.Packet.ConstructorData(obj).get @@ -1686,18 +1710,19 @@ class WorldSessionActor extends Actor with MDCContextAware { //seat terminal occupants continent.GUID(terminal_guid) match { case Some(obj : Mountable) => - obj.Seats - .filter({ case(_, seat) => seat.isOccupied }) - .foreach({ case(index, seat) => - val tplayer = seat.Occupant.get + obj.Seats(0).Occupant match { + case Some(tplayer) => val tdefintion = tplayer.Definition - sendResponse(ObjectCreateMessage( - tdefintion.ObjectId, - tplayer.GUID, - ObjectCreateMessageParent(parent_guid, index), - tdefintion.Packet.ConstructorData(tplayer).get - )) - }) + sendResponse( + ObjectCreateMessage( + tdefintion.ObjectId, + tplayer.GUID, + ObjectCreateMessageParent(parent_guid, 0), + tdefintion.Packet.ConstructorData(tplayer).get + ) + ) + case None => ; + } case _ => ; } }) @@ -1885,7 +1910,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case (false, _) => ; } - + // TODO: Prevents log spam, but should be handled correctly if(messagetype != ChatMessageType.CMT_TOGGLE_GM) { log.info("Chat: " + msg) @@ -2516,14 +2541,15 @@ class WorldSessionActor extends Actor with MDCContextAware { case _ => true }) { //access to trunk - if(obj.AccessingTrunk.isEmpty) { + if(obj.AccessingTrunk.isEmpty && + (!obj.PermissionGroup(AccessPermissionGroup.Trunk.id).contains(VehicleLockState.Locked) || obj.Owner.contains(player.GUID))) { obj.AccessingTrunk = player.GUID accessedContainer = Some(obj) AccessContents(obj) sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) } else { - log.info(s"UseItem: $player can not cut in line while player ${obj.AccessingTrunk.get} is using $obj's trunk") + log.info(s"UseItem: $obj's trunk is not currently accessible for $player") } } else if(equipment.isDefined) { @@ -2841,7 +2867,7 @@ class WorldSessionActor extends Actor with MDCContextAware { obj.Actor ! Deployment.TryDeploymentChange(deploy_state) case _ => - log.error(s"DeployRequest: can not find $vehicle_guid in scope; removing ownership to mitigate confusion") + log.error(s"DeployRequest: can not find $vehicle_guid in scope") player.VehicleOwned = None } } @@ -2877,11 +2903,10 @@ class WorldSessionActor extends Actor with MDCContextAware { vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.SeatPermissions(player.GUID, vehicle.GUID, attribute_type, attribute_value)) //kick players who should not be seated in the vehicle due to permission changes if(allow == VehicleLockState.Locked) { //TODO only important permission atm - vehicle.Definition.MountPoints.values.foreach(seat_num => { - val seat = vehicle.Seat(seat_num).get + vehicle.Seats.foreach({ case (seat_num, seat) => seat.Occupant match { case Some(tplayer) => - if(vehicle.SeatPermissionGroup(seat_num).contains(group) && tplayer != player) { + if(vehicle.SeatPermissionGroup(seat_num).contains(group) && tplayer != player) { //can not kick self seat.Occupant = None tplayer.VehicleSeated = None vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(tplayer.GUID, 4, false, object_guid)) @@ -3417,6 +3442,49 @@ class WorldSessionActor extends Actor with MDCContextAware { }) } + /** + * Disassociate this client's player (oneself) from a vehicle that he owns. + */ + def DisownVehicle() : Unit = DisownVehicle(player) + + /** + * Disassociate a player from a vehicle that he owns. + * The vehicle must exist in the game world on the current continent. + * This is similar but unrelated to the natural exchange of ownership when someone else sits in the vehicle's driver seat. + * This is the player side of vehicle ownership removal. + * @see `DisownVehicle(Player, Vehicle)` + * @param tplayer the player + */ + def DisownVehicle(tplayer : Player) : Unit = { + tplayer.VehicleOwned match { + case Some(vehicle_guid) => + continent.GUID(vehicle_guid) match { + case Some(vehicle : Vehicle) => + tplayer.VehicleOwned = None + DisownVehicle(tplayer, vehicle) + case _ => + tplayer.VehicleOwned = None + } + case None => ; + } + } + + /** + * Disassociate a vehicle from the player that owns it. + * When a vehicle is disowned + * This is the vehicle side of vehicle ownership removal. + * @see `DisownVehicle(Player)` + * @param tplayer the player + * @param vehicle the discovered vehicle + */ + private def DisownVehicle(tplayer : Player, vehicle : Vehicle) : Unit = { + if(vehicle.Owner.contains(tplayer.GUID)) { + vehicle.Owner = None +// vehicle.PermissionGroup(10, VehicleLockState.Empire.id) +// vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.SeatPermissions(tplayer.GUID, vehicle.GUID, 10, VehicleLockState.Empire.id)) + } + } + /** * Gives a target player positive battle experience points only. * If the player has access to more implant slots as a result of changing battle experience points, unlock those slots. @@ -4314,14 +4382,16 @@ class WorldSessionActor extends Actor with MDCContextAware { * It adds the `WSA`-current `Player` to the current zone and sends out the expected packets. */ def AvatarCreate() : Unit = { + player.VehicleSeated = None //TODO temp, until vehicle gating; unseat player else constructor data is messed up player.Spawn player.Health = 50 //TODO temp player.Armor = 25 val packet = player.Definition.Packet val dcdata = packet.DetailedConstructorData(player).get - sendResponse(ObjectCreateDetailedMessage(ObjectClass.avatar, player.GUID, dcdata)) - avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.LoadPlayer(player.GUID, packet.ConstructorData(player).get)) + val player_guid = player.GUID + sendResponse(ObjectCreateDetailedMessage(ObjectClass.avatar, player_guid, dcdata)) continent.Population ! Zone.Population.Spawn(avatar, player) + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.LoadPlayer(player_guid, ObjectClass.avatar, player_guid, packet.ConstructorData(player).get, None)) log.debug(s"ObjectCreateDetailedMessage: $dcdata") } @@ -4379,8 +4449,9 @@ class WorldSessionActor extends Actor with MDCContextAware { * @param tplayer the player */ def TurnPlayerIntoCorpse(tplayer : Player) : Unit = { + val guid = tplayer.GUID sendResponse( - ObjectCreateDetailedMessage(ObjectClass.avatar, tplayer.GUID, CorpseConverter.converter.DetailedConstructorData(tplayer).get) + ObjectCreateDetailedMessage(ObjectClass.avatar, guid, CorpseConverter.converter.DetailedConstructorData(tplayer).get) ) } @@ -4756,7 +4827,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case None => ; } } - + def sendResponse(cont : PlanetSidePacketContainer) : Unit = { log.trace("WORLD SEND: " + cont) sendResponse(cont.asInstanceOf[Any]) diff --git a/pslogin/src/main/scala/services/avatar/AvatarAction.scala b/pslogin/src/main/scala/services/avatar/AvatarAction.scala index a929cb81..40321546 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarAction.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarAction.scala @@ -6,7 +6,7 @@ import net.psforever.objects.equipment.Equipment import net.psforever.objects.inventory.Container import net.psforever.objects.zones.Zone import net.psforever.packet.game.{PlanetSideGUID, PlayerStateMessageUpstream} -import net.psforever.packet.game.objectcreate.ConstructorData +import net.psforever.packet.game.objectcreate.{ConstructorData, ObjectCreateMessageParent} import net.psforever.types.ExoSuitType import scala.concurrent.duration.FiniteDuration @@ -22,7 +22,7 @@ object AvatarAction { final case class ConcealPlayer(player_guid : PlanetSideGUID) extends Action final case class DropItem(player_guid : PlanetSideGUID, item : Equipment, zone : Zone) extends Action final case class EquipmentInHand(player_guid : PlanetSideGUID, target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action - final case class LoadPlayer(player_guid : PlanetSideGUID, pdata : ConstructorData) extends Action + final case class LoadPlayer(player_guid : PlanetSideGUID, object_id : Int, target_guid : PlanetSideGUID, cdata : ConstructorData, pdata : Option[ObjectCreateMessageParent]) extends Action final case class ObjectDelete(player_guid : PlanetSideGUID, item_guid : PlanetSideGUID, unk : Int = 0) extends Action final case class ObjectHeld(player_guid : PlanetSideGUID, slot : Int) extends Action final case class PlanetsideAttribute(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Action diff --git a/pslogin/src/main/scala/services/avatar/AvatarResponse.scala b/pslogin/src/main/scala/services/avatar/AvatarResponse.scala index 3ab8e264..ba1e7ebc 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarResponse.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarResponse.scala @@ -18,7 +18,7 @@ object AvatarResponse { final case class ConcealPlayer() extends Response final case class EquipmentInHand(pkt : ObjectCreateMessage) extends Response final case class DropItem(pkt : ObjectCreateMessage) extends Response - final case class LoadPlayer(pdata : ConstructorData) extends Response + final case class LoadPlayer(pkt : ObjectCreateMessage) extends Response final case class ObjectDelete(item_guid : PlanetSideGUID, unk : Int) extends Response final case class ObjectHeld(slot : Int) extends Response final case class PlanetsideAttribute(attribute_type : Int, attribute_value : Long) extends Response diff --git a/pslogin/src/main/scala/services/avatar/AvatarService.scala b/pslogin/src/main/scala/services/avatar/AvatarService.scala index 8c1f39db..87c194d4 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarService.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarService.scala @@ -86,9 +86,15 @@ class AvatarService extends Actor { AvatarResponse.EquipmentInHand(ObjectCreateMessage(definition.ObjectId, item.GUID, containerData, objectData)) ) ) - case AvatarAction.LoadPlayer(player_guid, pdata) => + case AvatarAction.LoadPlayer(player_guid, object_id, target_guid, cdata, pdata) => + val pkt = pdata match { + case Some(data) => + ObjectCreateMessage(object_id, target_guid, data, cdata) + case None => + ObjectCreateMessage(object_id, target_guid, cdata) + } AvatarEvents.publish( - AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.LoadPlayer(pdata)) + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.LoadPlayer(pkt)) ) case AvatarAction.ObjectDelete(player_guid, item_guid, unk) => AvatarEvents.publish( diff --git a/pslogin/src/main/scala/services/vehicle/VehicleAction.scala b/pslogin/src/main/scala/services/vehicle/VehicleAction.scala index 68c9788e..44114888 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleAction.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleAction.scala @@ -11,7 +11,6 @@ import net.psforever.types.{DriveState, Vector3, BailType} object VehicleAction { trait Action - final case class Awareness(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID) extends Action final case class ChildObjectState(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, pitch : Float, yaw : Float) extends Action final case class DeployRequest(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, state : DriveState.Value, unk1 : Int, unk2 : Boolean, pos : Vector3) extends Action final case class DismountVehicle(player_guid : PlanetSideGUID, bailType : BailType.Value, unk2 : Boolean) extends Action @@ -20,6 +19,7 @@ object VehicleAction { final case class KickPassenger(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean, vehicle_guid : PlanetSideGUID) extends Action final case class LoadVehicle(player_guid : PlanetSideGUID, vehicle : Vehicle, vtype : Int, vguid : PlanetSideGUID, vdata : ConstructorData) extends Action final case class MountVehicle(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, seat : Int) extends Action + final case class Ownership(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID) extends Action final case class SeatPermissions(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, seat_group : Int, permission : Long) extends Action final case class StowEquipment(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action final case class UnloadVehicle(player_guid : PlanetSideGUID, continent : Zone, vehicle : Vehicle) extends Action diff --git a/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala b/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala index f3c9f9eb..027d7034 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala @@ -11,7 +11,6 @@ object VehicleResponse { trait Response final case class AttachToRails(vehicle_guid : PlanetSideGUID, rails_guid : PlanetSideGUID) extends Response - final case class Awareness(vehicle_guid : PlanetSideGUID) extends Response final case class ChildObjectState(object_guid : PlanetSideGUID, pitch : Float, yaw : Float) extends Response final case class ConcealPlayer(player_guid : PlanetSideGUID) extends Response final case class DeployRequest(object_guid : PlanetSideGUID, state : DriveState.Value, unk1 : Int, unk2 : Boolean, pos : Vector3) extends Response @@ -22,6 +21,7 @@ object VehicleResponse { final case class KickPassenger(seat_num : Int, kickedByDriver : Boolean, vehicle_guid : PlanetSideGUID) extends Response final case class LoadVehicle(vehicle : Vehicle, vtype : Int, vguid : PlanetSideGUID, vdata : ConstructorData) extends Response final case class MountVehicle(object_guid : PlanetSideGUID, seat : Int) extends Response + final case class Ownership(vehicle_guid : PlanetSideGUID) extends Response final case class ResetSpawnPad(pad_guid : PlanetSideGUID) extends Response final case class RevealPlayer(player_guid : PlanetSideGUID) extends Response final case class SeatPermissions(vehicle_guid : PlanetSideGUID, seat_group : Int, permission : Long) extends Response diff --git a/pslogin/src/main/scala/services/vehicle/VehicleService.scala b/pslogin/src/main/scala/services/vehicle/VehicleService.scala index f4719eb4..700731f8 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleService.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleService.scala @@ -41,10 +41,6 @@ class VehicleService extends Actor { case VehicleServiceMessage(forChannel, action) => action match { - case VehicleAction.Awareness(player_guid, vehicle_guid) => - VehicleEvents.publish( - VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.Awareness(vehicle_guid)) - ) case VehicleAction.ChildObjectState(player_guid, object_guid, pitch, yaw) => VehicleEvents.publish( VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.ChildObjectState(object_guid, pitch, yaw)) @@ -77,6 +73,10 @@ class VehicleService extends Actor { VehicleEvents.publish( VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.MountVehicle(vehicle_guid, seat)) ) + case VehicleAction.Ownership(player_guid, vehicle_guid) => + VehicleEvents.publish( + VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.Ownership(vehicle_guid)) + ) case VehicleAction.SeatPermissions(player_guid, vehicle_guid, seat_group, permission) => VehicleEvents.publish( VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.SeatPermissions(vehicle_guid, seat_group, permission)) diff --git a/pslogin/src/test/scala/AvatarServiceTest.scala b/pslogin/src/test/scala/AvatarServiceTest.scala index 884d0fe1..07c8a2d5 100644 --- a/pslogin/src/test/scala/AvatarServiceTest.scala +++ b/pslogin/src/test/scala/AvatarServiceTest.scala @@ -4,7 +4,7 @@ import akka.routing.RandomPool import net.psforever.objects._ import net.psforever.objects.guid.{GUIDTask, TaskResolver} import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap} -import net.psforever.packet.game.objectcreate.{DroppedItemData, ObjectCreateMessageParent, PlacementData} +import net.psforever.packet.game.objectcreate.{DroppedItemData, ObjectClass, ObjectCreateMessageParent, PlacementData} import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID, PlayerStateMessageUpstream} import net.psforever.types.{CharacterGender, ExoSuitType, PlanetSideEmpire, Vector3} import services.{RemoverActor, Service, ServiceManager} @@ -155,15 +155,28 @@ class LoadPlayerTest extends ActorTest { val obj = Player(Avatar("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, 1)) obj.GUID = PlanetSideGUID(10) obj.Slot(5).Equipment.get.GUID = PlanetSideGUID(11) - val pdata = obj.Definition.Packet.DetailedConstructorData(obj).get + val c1data = obj.Definition.Packet.DetailedConstructorData(obj).get + val pkt1 = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(10), c1data) + val parent = ObjectCreateMessageParent(PlanetSideGUID(12), 0) + obj.VehicleSeated = PlanetSideGUID(12) + val c2data = obj.Definition.Packet.DetailedConstructorData(obj).get + val pkt2 = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(10), parent, c2data) "AvatarService" should { "pass LoadPlayer" in { ServiceManager.boot(system) val service = system.actorOf(Props[AvatarService], AvatarServiceTest.TestName) service ! Service.Join("test") - service ! AvatarServiceMessage("test", AvatarAction.LoadPlayer(PlanetSideGUID(10), pdata)) - expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.LoadPlayer(pdata))) + //no parent data + service ! AvatarServiceMessage("test", AvatarAction.LoadPlayer( + PlanetSideGUID(20), ObjectClass.avatar, PlanetSideGUID(10), c1data, None) + ) + expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(20), AvatarResponse.LoadPlayer(pkt1))) + //parent data + service ! AvatarServiceMessage("test", AvatarAction.LoadPlayer( + PlanetSideGUID(20), ObjectClass.avatar, PlanetSideGUID(10), c2data, Some(parent)) + ) + expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(20), AvatarResponse.LoadPlayer(pkt2))) } } } diff --git a/pslogin/src/test/scala/VehicleServiceTest.scala b/pslogin/src/test/scala/VehicleServiceTest.scala index 261556c7..e2d48c97 100644 --- a/pslogin/src/test/scala/VehicleServiceTest.scala +++ b/pslogin/src/test/scala/VehicleServiceTest.scala @@ -68,15 +68,15 @@ class VehicleService5Test extends ActorTest { } } -class AwarenessTest extends ActorTest { +class OwnershipTest extends ActorTest { ServiceManager.boot(system) "VehicleService" should { "pass Awareness" in { val service = system.actorOf(Props[VehicleService], "v-service") service ! Service.Join("test") - service ! VehicleServiceMessage("test", VehicleAction.Awareness(PlanetSideGUID(10), PlanetSideGUID(11))) - expectMsg(VehicleServiceResponse("/test/Vehicle", PlanetSideGUID(10), VehicleResponse.Awareness(PlanetSideGUID(11)))) + service ! VehicleServiceMessage("test", VehicleAction.Ownership(PlanetSideGUID(10), PlanetSideGUID(11))) + expectMsg(VehicleServiceResponse("/test/Vehicle", PlanetSideGUID(10), VehicleResponse.Ownership(PlanetSideGUID(11)))) } } }