Merge pull request #219 from Fate-JH/char-data

Characters, and Vehicles, and Characters in Vehicles.
This commit is contained in:
Fate-JH 2018-06-11 19:21:30 -04:00 committed by GitHub
commit 117ca9e478
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
76 changed files with 4028 additions and 2310 deletions

View file

@ -4,12 +4,12 @@ package net.psforever.objects
import net.psforever.objects.definition.{AvatarDefinition, ImplantDefinition}
import net.psforever.objects.equipment.EquipmentSize
import net.psforever.objects.loadouts.Loadout
import net.psforever.types.{CertificationType, CharacterGender, ImplantType, PlanetSideEmpire}
import net.psforever.types._
import scala.annotation.tailrec
import scala.collection.mutable
class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex : CharacterGender.Value, val head : Int, val voice : Int) {
class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex : CharacterGender.Value, val head : Int, val voice : CharacterVoice.Value) {
/** Battle Experience Points */
private var bep : Long = 0
/** Command Experience Points */
@ -212,7 +212,7 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex :
object Avatar {
final private val definition : AvatarDefinition = new AvatarDefinition(121)
def apply(name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, head : Int, voice : Int) : Avatar = {
def apply(name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, head : Int, voice : CharacterVoice.Value) : Avatar = {
new Avatar(name, faction, sex, head, voice)
}

View file

@ -58,7 +58,7 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio
def Head : Int = core.head
def Voice : Int = core.voice
def Voice : CharacterVoice.Value = core.voice
def isAlive : Boolean = alive

View file

@ -21,16 +21,44 @@ 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.<br>
* <br>
* 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.<br>
* <br>
* 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 selection ("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.)<br>
* <br>
* 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';
* @param vehicleDef the vehicle's definition entry;
* stores and unloads pertinent information about the `Vehicle`'s configuration;
* used in the initialization process (`loadVehicleDefinition`)
*/

View file

@ -3,80 +3,129 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.{EquipmentSlot, Player}
import net.psforever.objects.equipment.Equipment
import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, Cosmetics, DetailedCharacterData, DrawnSlot, ImplantEffects, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars, UniformStyle}
import net.psforever.packet.game.objectcreate._
import net.psforever.types.{GrenadeState, ImplantType}
import scala.annotation.tailrec
import scala.util.{Success, Try}
class AvatarConverter extends ObjectCreateConverter[Player]() {
override def ConstructorData(obj : Player) : Try[CharacterData] = {
val MaxArmor = obj.MaxArmor
override def ConstructorData(obj : Player) : Try[PlayerData] = {
import AvatarConverter._
Success(
CharacterData(
MakeAppearanceData(obj),
255 * obj.Health / obj.MaxHealth, //TODO not precise
if(MaxArmor == 0) { 0 } else { 255 * obj.Armor / MaxArmor }, //TODO not precise
DressBattleRank(obj),
DressCommandRank(obj),
recursiveMakeImplantEffects(obj.Implants.iterator),
MakeCosmetics(obj.BEP),
InventoryData(MakeHolsters(obj, BuildEquipment).sortBy(_.parentSlot)), //TODO is sorting necessary?
GetDrawnSlot(obj)
)
)
//TODO tidy this mess up
}
override def DetailedConstructorData(obj : Player) : Try[DetailedCharacterData] = {
Success(
DetailedCharacterData(
MakeAppearanceData(obj),
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) {
PlayerData(
PlacementData(obj.Position, obj.Orientation, obj.Velocity),
MakeAppearanceData(obj),
MakeCharacterData(obj),
MakeInventoryData(obj),
GetDrawnSlot(obj)
)
}
else {
PlayerData(
MakeAppearanceData(obj),
MakeCharacterData(obj),
MakeInventoryData(obj),
DrawnSlot.None
)
}
)
}
override def DetailedConstructorData(obj : Player) : Try[DetailedPlayerData] = {
import AvatarConverter._
Success(
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
)
}
)
}
}
object AvatarConverter {
/**
* Compose some data from a `Player` into a representation common to both `CharacterData` and `DetailedCharacterData`.
* @param obj the `Player` game object
* @return the resulting `CharacterAppearanceData`
*/
private def MakeAppearanceData(obj : Player) : CharacterAppearanceData = {
def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
CharacterAppearanceData(
PlacementData(obj.Position, obj.Orientation, obj.Velocity),
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, obj.Voice),
0,
false,
false,
voice2 = 0,
black_ops = false,
jammered = false,
obj.ExoSuit,
"",
0,
outfit_name = "",
outfit_logo = 0,
obj.isBackpack,
obj.Orientation.y,
obj.FacingYawUpper,
true,
facingPitch = obj.Orientation.y,
facingYawUpper = obj.FacingYawUpper,
lfs = true,
GrenadeState.None,
false,
false,
false,
is_cloaking = false,
charging_pose = false,
on_zipline = false,
RibbonBars()
)
}
def MakeCharacterData(obj : Player) : (Boolean,Boolean)=>CharacterData = {
val MaxArmor = obj.MaxArmor
CharacterData(
255 * obj.Health / obj.MaxHealth, //TODO not precise
if(MaxArmor == 0) {
0
}
else {
255 * obj.Armor / MaxArmor
}, //TODO not precise
DressBattleRank(obj),
DressCommandRank(obj),
recursiveMakeImplantEffects(obj.Implants.iterator),
MakeCosmetics(obj.BEP)
)
}
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),
firstTimeEvents = List.empty[String], //TODO fte list
tutorials = List.empty[String], //TODO tutorial list
MakeCosmetics(obj.BEP)
)
}
def MakeInventoryData(obj : Player) : InventoryData = {
InventoryData(MakeHolsters(obj, BuildEquipment).sortBy(_.parentSlot))
}
def MakeDetailedInventoryData(obj : Player) : InventoryData = {
InventoryData((MakeHolsters(obj, BuildDetailedEquipment) ++ MakeFifthSlot(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot))
}
/**
* Select the appropriate `UniformStyle` design for a player's accumulated battle experience points.
* At certain battle ranks, all exo-suits undergo some form of coloration change.
@ -183,7 +232,7 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
* @see `Cosmetics`
* @return the `Cosmetics` options
*/
protected def MakeCosmetics(bep : Long) : Option[Cosmetics] =
def MakeCosmetics(bep : Long) : Option[Cosmetics] =
if(DetailedCharacterData.isBR24(bep)) {
Some(Cosmetics(false, false, false, false, false))
}
@ -206,6 +255,7 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
InternalSlot(equip.Definition.ObjectId, equip.GUID, item.start, equip.Definition.Packet.DetailedConstructorData(equip).get)
}).toList
}
/**
* Given a player with equipment holsters, convert the contents of those holsters into converted-decoded packet data.
* The decoded packet form is determined by the function in the parameters as both `0x17` and `0x18` conversions are available,
@ -251,7 +301,7 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
* @param equip the game object
* @return the game object in decoded packet form
*/
protected def BuildDetailedEquipment(index : Int, equip : Equipment) : InternalSlot = {
def BuildDetailedEquipment(index : Int, equip : Equipment) : InternalSlot = {
InternalSlot(equip.Definition.ObjectId, equip.GUID, index, equip.Definition.Packet.DetailedConstructorData(equip).get)
}
@ -289,7 +339,7 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
* @param obj the `Player` game object
* @return the holster's Enumeration value
*/
protected def GetDrawnSlot(obj : Player) : DrawnSlot.Value = {
def GetDrawnSlot(obj : Player) : DrawnSlot.Value = {
try { DrawnSlot(obj.DrawnSlot) } catch { case _ : Exception => DrawnSlot.None }
}
}

View file

@ -3,8 +3,8 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.{EquipmentSlot, Player}
import net.psforever.objects.equipment.Equipment
import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars}
import net.psforever.types.{GrenadeState, ImplantType}
import net.psforever.packet.game.objectcreate._
import net.psforever.types.{CharacterVoice, GrenadeState, ImplantType}
import scala.annotation.tailrec
import scala.util.{Failure, Success, Try}
@ -15,21 +15,29 @@ import scala.util.{Failure, Success, Try}
* Details that would not be apparent on that screen such as implants or certifications are ignored.
*/
class CharacterSelectConverter extends AvatarConverter {
override def ConstructorData(obj : Player) : Try[CharacterData] = Failure(new Exception("CharacterSelectConverter should not be used to generate CharacterData"))
override def ConstructorData(obj : Player) : Try[PlayerData] = Failure(new Exception("CharacterSelectConverter should not be used to generate CharacterData"))
override def DetailedConstructorData(obj : Player) : Try[DetailedCharacterData] = {
override def DetailedConstructorData(obj : Player) : Try[DetailedPlayerData] = {
Success(
DetailedCharacterData(
DetailedPlayerData.apply(
PlacementData(0, 0, 0),
MakeAppearanceData(obj),
obj.BEP,
obj.CEP,
1, 1, 0, 1, 1,
Nil,
MakeImplantEntries(obj), //necessary for correct stream length
Nil, Nil,
MakeCosmetics(obj.BEP),
DetailedCharacterData(
obj.BEP,
obj.CEP,
healthMax = 1,
health = 1,
armor = 0,
staminaMax = 1,
stamina = 1,
certs = Nil,
MakeImplantEntries(obj), //necessary for correct stream length
firstTimeEvents = Nil,
tutorials = Nil,
AvatarConverter.MakeCosmetics(obj.BEP)
),
InventoryData(recursiveMakeHolsters(obj.Holsters().iterator)),
GetDrawnSlot(obj)
AvatarConverter.GetDrawnSlot(obj)
)
)
}
@ -40,24 +48,23 @@ class CharacterSelectConverter extends AvatarConverter {
* @see `AvatarConverter.MakeAppearanceData`
* @return the resulting `CharacterAppearanceData`
*/
private def MakeAppearanceData(obj : Player) : CharacterAppearanceData = {
private def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
CharacterAppearanceData(
PlacementData(0f, 0f, 0f),
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, 1),
0,
false,
false,
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, CharacterVoice.Mute),
voice2 = 0,
black_ops = false,
jammered = false,
obj.ExoSuit,
"",
0,
false,
0f,
0f,
true,
outfit_name = "",
outfit_logo = 0,
backpack = false,
facingPitch = 0,
facingYawUpper = 0,
lfs = true,
GrenadeState.None,
false,
false,
false,
is_cloaking = false,
charging_pose = false,
on_zipline = false,
RibbonBars()
)
}
@ -90,7 +97,7 @@ class CharacterSelectConverter extends AvatarConverter {
val equip : Equipment = slot.Equipment.get
recursiveMakeHolsters(
iter,
list :+ BuildDetailedEquipment(index, equip),
list :+ AvatarConverter.BuildDetailedEquipment(index, equip),
index + 1
)
}

View file

@ -3,23 +3,35 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.{EquipmentSlot, Player}
import net.psforever.objects.equipment.Equipment
import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, DrawnSlot, InternalSlot, InventoryData, PlacementData, RibbonBars}
import net.psforever.types.{CharacterGender, GrenadeState, Vector3}
import net.psforever.packet.game.objectcreate._
import net.psforever.types.{CharacterGender, CharacterVoice, GrenadeState, Vector3}
import scala.annotation.tailrec
import scala.util.{Failure, Success, Try}
class CorpseConverter extends AvatarConverter {
override def ConstructorData(obj : Player) : Try[CharacterData] =
override def ConstructorData(obj : Player) : Try[PlayerData] =
Failure(new Exception("CorpseConverter should not be used to generate CharacterData"))
override def DetailedConstructorData(obj : Player) : Try[DetailedCharacterData] = {
override def DetailedConstructorData(obj : Player) : Try[DetailedPlayerData] = {
Success(
DetailedCharacterData(
DetailedPlayerData.apply(
PlacementData(obj.Position, Vector3(0,0, obj.Orientation.z)),
MakeAppearanceData(obj),
0, 0, 0, 0, 0, 0, 0,
Nil, Nil, Nil, Nil,
None,
DetailedCharacterData(
bep = 0,
cep = 0,
healthMax = 0,
health = 0,
armor = 0,
staminaMax = 0,
stamina = 0,
certs = Nil,
implants = Nil,
firstTimeEvents = Nil,
tutorials = Nil,
cosmetics = None
),
InventoryData((MakeHolsters(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)),
DrawnSlot.None
)
@ -31,24 +43,23 @@ class CorpseConverter extends AvatarConverter {
* @param obj the `Player` game object
* @return the resulting `CharacterAppearanceData`
*/
private def MakeAppearanceData(obj : Player) : CharacterAppearanceData = {
private def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
CharacterAppearanceData(
PlacementData(obj.Position, Vector3(0,0, obj.Orientation.z)),
BasicCharacterData(obj.Name, obj.Faction, CharacterGender.Male, 0, 0),
0,
false,
false,
BasicCharacterData(obj.Name, obj.Faction, CharacterGender.Male, 0, CharacterVoice.Mute),
voice2 = 0,
black_ops = false,
jammered = false,
obj.ExoSuit,
"",
0,
true,
obj.Orientation.y, //TODO is this important?
0,
true,
outfit_name = "",
outfit_logo = 0,
backpack = true,
facingPitch = obj.Orientation.y, //TODO is this important?
facingYawUpper = 0,
lfs = true,
GrenadeState.None,
false,
false,
false,
is_cloaking = false,
charging_pose = false,
on_zipline = false,
RibbonBars()
)
}

View file

@ -10,29 +10,74 @@ import scala.util.{Failure, Success, Try}
class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
override def DetailedConstructorData(obj : Vehicle) : Try[VehicleData] =
Failure(new Exception("VehicleConverter should not be used to generate detailed VehicleData"))
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?
),
0,
255 * obj.Health / obj.MaxHealth, //TODO not precise
false, false,
PlacementData(obj.Position, obj.Orientation, obj.Velocity),
obj.Faction,
bops = false,
destroyed = health < 3,
unk1 = 0,
obj.Jammered,
unk2 = false,
obj.Owner match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
},
unk3 = false,
health,
unk4 = false,
no_mount_points = false,
obj.DeploymentState,
false,
false,
unk5 = false,
unk6 = false,
obj.Cloaked,
SpecificFormatData(obj),
Some(InventoryData((MakeUtilities(obj) ++ MakeMountings(obj)).sortBy(_.parentSlot)))
Some(InventoryData(MakeDriverSeat(obj) ++ MakeUtilities(obj) ++ MakeMountings(obj)))
)(SpecificFormatModifier)
)
}
private def MakeDriverSeat(obj : Vehicle) : List[InventoryItemData.InventoryItem] = {
val offset : Long = VehicleData.InitialStreamLengthToSeatEntries(obj.Velocity.nonEmpty, SpecificFormatModifier)
obj.Seats(0).Occupant match {
case Some(player) =>
val mountedPlayer = VehicleData.PlayerData(
AvatarConverter.MakeAppearanceData(player),
AvatarConverter.MakeCharacterData(player),
AvatarConverter.MakeInventoryData(player),
AvatarConverter.GetDrawnSlot(player),
offset
)
List(InventoryItemData(ObjectClass.avatar, player.GUID, 0, mountedPlayer))
case None =>
Nil
}
}
//TODO do not use for now; causes vehicle access permission issues; may not mesh with workflows; player GUID requirements
// private def MakeSeats(obj : Vehicle) : List[InventoryItemData.InventoryItem] = {
// var offset : Long = VehicleData.InitialStreamLengthToSeatEntries(obj.Velocity.nonEmpty, SpecificFormatModifier)
// obj.Seats
// .filter({ case (_, seat) => seat.isOccupied })
// .map({case (index, seat) =>
// val player = seat.Occupant.get
// val mountedPlayer = VehicleData.PlayerData(
// AvatarConverter.MakeAppearanceData(player),
// AvatarConverter.MakeCharacterData(player),
// AvatarConverter.MakeInventoryData(player),
// AvatarConverter.GetDrawnSlot(player),
// offset
// )
// val entry = InventoryItemData(ObjectClass.avatar, player.GUID, index, mountedPlayer)
// //println(s"seat 0 offset: $offset, size: ${entry.bitsize}, pad: ${mountedPlayer.basic_appearance.NamePadding}")
// offset += entry.bitsize
// entry
// }).toList
// }
private def MakeMountings(obj : Vehicle) : List[InventoryItemData.InventoryItem] = {
obj.Weapons.map({

View file

@ -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 {

View file

@ -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) =>

View file

@ -4,7 +4,7 @@ package net.psforever.objects.vehicles
/**
* An `Enumeration` of exo-suit-based seat access restrictions.<br>
* <br>
* The default value is `NoMax` as that is the most common seat.
* The default value is `NoMax` as that is the most common seat type.
* `NoReinforcedOrMax` is next most common.
* `MaxOnly` is a rare seat restriction found in pairs on Galaxies and on the large "Ground Transport" vehicles.
*/

View file

@ -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`.<br>
@ -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.NoMax
case _ => restriction != SeatArmorRestriction.MaxOnly
})
) {
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)) {

View file

@ -231,6 +231,21 @@ object PacketHelpers {
* @return a codec that works on a List of A but excludes the size from the encoding
*/
def listOfNSized[A](size : Long, codec : Codec[A]) : Codec[List[A]] = PacketHelpers.listOfNAligned(provide(if(size < 0) 0 else size), 0, codec)
/**
* A `peek` that decodes like the normal but encodes nothing.
* Decoding `Codec[A]` from the input vector emits a value but reverts to the prior read position.
* Encoding `Codec[A]` to the input vector appends no new data to the input vector.
* In effect, `peek` is a harmless meta-`Codec` that introduces no changes to the input vector.
* @see `scodec.codecs.peek` or `codecs/package.scala:peek`
* @param target codec that decodes the value
* @return codec that behaves the same as `target` but resets remainder to the input vector
*/
def peek[A](target: Codec[A]): Codec[A] = new Codec[A] {
def sizeBound = target.sizeBound
def encode(a: A) = Attempt.Successful(BitVector.empty)
def decode(b: BitVector) = target.decode(b).map { _.mapRemainder(_ => b) }
}
}
/**

View file

@ -2,7 +2,7 @@
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.{CharacterGender, PlanetSideEmpire}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire}
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
@ -12,7 +12,7 @@ import shapeless.{::, HNil}
*/
final case class CharacterCreateRequestMessage(name : String,
headId : Int,
voiceId : Int,
voiceId : CharacterVoice.Value,
gender : CharacterGender.Value,
empire : PlanetSideEmpire.Value)
extends PlanetSideGamePacket {
@ -22,10 +22,12 @@ final case class CharacterCreateRequestMessage(name : String,
}
object CharacterCreateRequestMessage extends Marshallable[CharacterCreateRequestMessage] {
private val character_voice_codec = PacketHelpers.createEnumerationCodec(CharacterVoice, uint8)
implicit val codec : Codec[CharacterCreateRequestMessage] = (
("name" | PacketHelpers.encodedWideString) ::
("headId" | uint8L) ::
("voiceId" | uint8L) ::
("voiceId" | character_voice_codec) ::
("gender" | CharacterGender.codec) ::
("empire" | PlanetSideEmpire.codec)
).exmap[CharacterCreateRequestMessage] (

View file

@ -71,36 +71,20 @@ object ObjectCreateDetailedMessage extends Marshallable[ObjectCreateDetailedMess
ObjectCreateDetailedMessage(ObjectCreateBase.streamLen(None, data), objectClass, guid, None, Some(data))
}
/**
* Take the important information of a game piece and transform it into bit data.
* This function is fail-safe because it catches errors involving bad parsing of the object data.
* Generally, the `Exception` messages themselves are not useful here.
* @param objClass the code for the type of object being deconstructed
* @param obj the object data
* @return the bitstream data
* @see ObjectClass.selectDataCodec
*/
def encodeData(objClass : Int, obj : ConstructorData, getCodecFunc : (Int) => Codec[ConstructorData.genericPattern]) : BitVector = {
var out = BitVector.empty
try {
val outOpt : Option[BitVector] = getCodecFunc(objClass).encode(Some(obj.asInstanceOf[ConstructorData])).toOption
if(outOpt.isDefined)
out = outOpt.get
}
catch {
case _ : Exception =>
//catch and release, any sort of parse error
}
out
}
implicit val codec : Codec[ObjectCreateDetailedMessage] = ObjectCreateBase.baseCodec.exmap[ObjectCreateDetailedMessage] (
{
case _ :: _ :: _ :: _ :: BitVector.empty :: HNil =>
Attempt.failure(Err("no data to decode"))
case len :: cls :: guid :: par :: data :: HNil =>
val obj = ObjectCreateBase.decodeData(cls, data, ObjectClass.selectDataDetailedCodec)
val obj = ObjectCreateBase.decodeData(cls, data,
if(par.isDefined) {
ObjectClass.selectDataDetailedCodec
}
else {
ObjectClass.selectDataDroppedDetailedCodec
}
)
Attempt.successful(ObjectCreateDetailedMessage(len, cls, guid, par, obj))
},
{
@ -109,7 +93,14 @@ object ObjectCreateDetailedMessage extends Marshallable[ObjectCreateDetailedMess
case ObjectCreateDetailedMessage(_, cls, guid, par, Some(obj)) =>
val len = ObjectCreateBase.streamLen(par, obj) //even if a stream length has been assigned, it can not be trusted during encoding
val bitvec = ObjectCreateBase.encodeData(cls, obj, ObjectClass.selectDataDetailedCodec)
val bitvec = ObjectCreateBase.encodeData(cls, obj,
if(par.isDefined) {
ObjectClass.selectDataDetailedCodec
}
else {
ObjectClass.selectDataDroppedDetailedCodec
}
)
Attempt.successful(len :: cls :: guid :: par :: bitvec :: HNil)
}
)

View file

@ -126,7 +126,7 @@ import scodec.codecs._
* `11 - Gunner seat(s) permissions (same)`<br>
* `12 - Passenger seat(s) permissions (same)`<br>
* `13 - Trunk permissions (same)`<br>
* `21 - Asserts first time event eligibility / makes owner if no owner is assigned`<br>
* `21 - Declare a player the vehicle's owner, by globally unique identifier`<br>
* `22 - Toggles gunner and passenger mount points (1 = hides, 0 = reveals; this also locks their permissions)`<br>
* `68 - ???`<br>
* `80 - Damage vehicle (unknown value)`<br>

View file

@ -0,0 +1,25 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.types._
/**
* A part of a representation of the avatar portion of `ObjectCreateMessage` packet data.<br>
* <br>
* This partition of the data stream contains information used to represent how the player's avatar is presented.
* This appearance coincides with the data available from the `CharacterCreateRequestMessage` packet.
* @see `PlanetSideEmpire`<br>
* `CharacterGender`
* @param name the unique name of the avatar;
* minimum of two characters
* @param faction the empire to which the avatar belongs
* @param sex whether the avatar is `Male` or `Female`
* @param head the avatar's face and hair;
* by row and column on the character creation screen, the high nibble is the row and the low nibble is the column
* @param voice the avatar's voice selection
*/
final case class BasicCharacterData(name : String,
faction : PlanetSideEmpire.Value,
sex : CharacterGender.Value,
head : Int,
voice : CharacterVoice.Value)

View file

@ -7,38 +7,6 @@ import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
/**
* A part of a representation of the avatar portion of `ObjectCreateMessage` packet data.<br>
* <br>
* This partition of the data stream contains information used to represent how the player's avatar is presented.
* This appearance coincides with the data available from the `CharacterCreateRequestMessage` packet.<br>
* <br>
* Voice:<br>
* `&nbsp;&nbsp;&nbsp;&nbsp;MALE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FEMALE`<br>
* `0 - no voice &nbsp;no voice`<br>
* `1 - male_1 &nbsp;&nbsp; female_1`<br>
* `2 - male_2 &nbsp;&nbsp; female_2`<br>
* `3 - male_3 &nbsp;&nbsp; female_3`<br>
* `4 - male_4 &nbsp;&nbsp; female_4`<br>
* `5 - male_5 &nbsp;&nbsp; female_5`<br>
* `6 - female_1 &nbsp;no voice`<br>
* `7 - female_2 &nbsp;no voice`
* @param name the unique name of the avatar;
* minimum of two characters
* @param faction the empire to which the avatar belongs
* @param sex whether the avatar is `Male` or `Female`
* @param head the avatar's face and hair;
* by row and column on the character creation screen, the high nibble is the row and the low nibble is the column
* @param voice the avatar's voice selection
* @see `PlanetSideEmpire`
* @see `CharacaterGender`
*/
final case class BasicCharacterData(name : String,
faction : PlanetSideEmpire.Value,
sex : CharacterGender.Value,
head : Int,
voice : Int)
/**
* A part of a representation of the avatar portion of `ObjectCreateDetailedMessage` packet data.<br>
* <br>
@ -60,8 +28,13 @@ final case class BasicCharacterData(name : String,
* <br>
* Exploration:<br>
* How do I crouch?
* @param pos the position of the character in the world environment (in three coordinates)
* @param basic_appearance the player's cardinal appearance settings
* @see `CharacterData`<br>
* `DetailedCharacterData`<br>
* `ExoSuitType`<br>
* `GrenadeState`<br>
* `RibbonBars`
* @see `http://www.planetside-universe.com/p-outfit-decals-31.htm`
* @param app the player's cardinal appearance settings
* @param voice2 na;
* affects the frequency by which the character's voice is heard (somehow);
* commonly 3 for best results
@ -86,16 +59,8 @@ final case class BasicCharacterData(name : String,
* @param charging_pose animation pose for both charging modules and BFR imprinting
* @param on_zipline player's model is changed into a faction-color ball of energy, as if on a zip line
* @param ribbons the four merit commendation ribbon medals
* @see `CharacterData`
* @see `DetailedCharacterData`
* @see `PlacementData`
* @see `ExoSuitType`
* @see `GrenadeState`
* @see `RibbonBars`
* @see `http://wiki.planetsidesyndicate.com/index.php?title=Outfit_Logo` for a list of outfit decals
*/
final case class CharacterAppearanceData(pos : PlacementData,
basic_appearance : BasicCharacterData,
final case class CharacterAppearanceData(app : BasicCharacterData,
voice2 : Int,
black_ops : Boolean,
jammered : Boolean,
@ -110,22 +75,36 @@ final case class CharacterAppearanceData(pos : PlacementData,
is_cloaking : Boolean,
charging_pose : Boolean,
on_zipline : Boolean,
ribbons : RibbonBars) extends StreamBitSize {
ribbons : RibbonBars)
(name_padding : Int) extends StreamBitSize {
override def bitsize : Long = {
//factor guard bool values into the base size, not its corresponding optional field
val placementSize : Long = pos.bitsize
val nameStringSize : Long = StreamBitSize.stringBitSize(basic_appearance.name, 16) + CharacterAppearanceData.namePadding(pos.vel)
val outfitStringSize : Long = StreamBitSize.stringBitSize(outfit_name, 16) + CharacterAppearanceData.outfitNamePadding
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 is always padded
val altModelSize = CharacterAppearanceData.altModelBit(this).getOrElse(0)
335L + placementSize + nameStringSize + outfitStringSize + altModelSize
335L + nameStringSize + outfitStringSize + altModelSize //base value includes the ribbons
}
/**
* 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.
* @return the length of the variable field that exists when using alternate models
*/
def altModelBit : Option[Int] = CharacterAppearanceData.altModelBit(this)
}
object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
/**
* When a player is released-dead or attached to a zipline, their basic infantry model is replaced with a different one.
* In the former casde, a backpack.
* In the former case, a backpack.
* In the latter case, a ball of colored energy.
* In this state, the length of the stream of data is modified.
* @param app the appearance
@ -138,20 +117,6 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
None
}
/**
* Get the padding of the player's name.
* The padding will always be a number 0-7.
* @return the pad length in bits
*/
def namePadding(move : Option[_]) : Int = {
if(move.isDefined) {
2
}
else {
4
}
}
/**
* Get the padding of the outfit's name.
* The padding will always be a number 0-7.
@ -161,64 +126,63 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
6
}
implicit val codec : Codec[CharacterAppearanceData] = (
("pos" | PlacementData.codec) >>:~ { pos =>
("faction" | PlanetSideEmpire.codec) ::
("black_ops" | bool) ::
(("alt_model" | bool) >>:~ { alt_model => //modifies stream format (to display alternate player models)
def codec(name_padding : Int) : Codec[CharacterAppearanceData] = (
("faction" | PlanetSideEmpire.codec) ::
("black_ops" | bool) ::
(("alt_model" | bool) >>:~ { alt_model => //modifies stream format (to display alternate player models)
ignore(1) :: //unknown
("jammered" | bool) ::
bool :: //crashes client
uint(16) :: //unknown, but usually 0
("name" | PacketHelpers.encodedWideStringAligned(name_padding)) ::
("exosuit" | ExoSuitType.codec) ::
ignore(2) :: //unknown
("sex" | CharacterGender.codec) ::
("head" | uint8L) ::
("voice" | CharacterVoice.codec) ::
("voice2" | uint2L) ::
ignore(78) :: //unknown
uint16L :: //usually either 0 or 65535
uint32L :: //for outfit_name (below) to be visible in-game, this value should be non-zero
("outfit_name" | PacketHelpers.encodedWideStringAligned( outfitNamePadding )) ::
("outfit_logo" | uint8L) ::
ignore(1) :: //unknown
("jammered" | bool) ::
bool :: //crashes client
uint(16) :: //unknown, but usually 0
("name" | PacketHelpers.encodedWideStringAligned( namePadding(pos.vel) )) ::
("exosuit" | ExoSuitType.codec) ::
ignore(2) :: //unknown
("sex" | CharacterGender.codec) ::
("head" | uint8L) ::
("voice" | uint(3)) ::
("voice2" | uint2L) ::
ignore(78) :: //unknown
uint16L :: //usually either 0 or 65535
uint32L :: //for outfit_name (below) to be visible in-game, this value should be non-zero
("outfit_name" | PacketHelpers.encodedWideStringAligned( outfitNamePadding )) ::
("outfit_logo" | uint8L) ::
ignore(1) :: //unknown
("backpack" | bool) :: //requires alt_model flag (does NOT require health == 0)
bool :: //stream misalignment when set
("facingPitch" | Angular.codec_pitch) ::
("facingYawUpper" | Angular.codec_yaw(0f)) ::
ignore(1) :: //unknown
conditional(alt_model, bool) :: //alt_model flag adds a bit before lfs
ignore(1) :: //an alternate lfs?
("lfs" | bool) ::
("grenade_state" | GrenadeState.codec_2u) :: //note: bin10 and bin11 are neutral (bin00 is not defined)
("is_cloaking" | bool) ::
ignore(1) :: //unknown
bool :: //stream misalignment when set
("charging_pose" | bool) ::
ignore(1) :: //alternate charging pose?
("on_zipline" | bool) :: //requires alt_model flag
("ribbons" | RibbonBars.codec)
})
}).exmap[CharacterAppearanceData] (
("backpack" | bool) :: //requires alt_model flag (does NOT require health == 0)
bool :: //stream misalignment when set
("facingPitch" | Angular.codec_pitch) ::
("facingYawUpper" | Angular.codec_yaw(0f)) ::
ignore(1) :: //unknown
conditional(alt_model, bool) :: //alt_model flag adds a bit before lfs
ignore(1) :: //an alternate lfs?
("lfs" | bool) ::
("grenade_state" | GrenadeState.codec_2u) :: //note: bin10 and bin11 are neutral (bin00 is not defined)
("is_cloaking" | bool) ::
ignore(1) :: //unknown
bool :: //stream misalignment when set
("charging_pose" | bool) ::
ignore(1) :: //alternate charging pose?
("on_zipline" | bool) :: //requires alt_model flag
("ribbons" | RibbonBars.codec)
})
).exmap[CharacterAppearanceData] (
{
case _ :: _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil |
_ :: _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: HNil =>
case _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil |
_ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: HNil =>
Attempt.Failure(Err("invalid character appearance data; can not encode alternate model without required bit set"))
case pos :: faction :: bops :: _ :: _ :: jamd :: false :: 0 :: name :: suit :: _ :: sex :: head :: v1 :: v2 :: _ :: _ :: _/*has_outfit_name*/ :: outfit :: logo :: _ :: bpack :: false :: facingPitch :: facingYawUpper :: _ :: _ :: _ :: lfs :: gstate :: cloaking :: _ :: false :: charging :: _ :: zipline :: ribbons :: HNil =>
case faction :: bops :: _ :: _ :: jamd :: false :: 0 :: name :: suit :: _ :: sex :: head :: v1 :: v2 :: _ :: _ :: _/*has_outfit_name*/ :: outfit :: logo :: _ :: bpack :: false :: facingPitch :: facingYawUpper :: _ :: _ :: _ :: lfs :: gstate :: cloaking :: _ :: false :: charging :: _ :: zipline :: ribbons :: HNil =>
Attempt.successful(
CharacterAppearanceData(pos, BasicCharacterData(name, faction, sex, head, v1), v2, bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons)
CharacterAppearanceData(BasicCharacterData(name, faction, sex, head, v1), v2, bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons)(name_padding)
)
case _ =>
Attempt.Failure(Err("invalid character appearance data; can not encode"))
},
{
case CharacterAppearanceData(_, BasicCharacterData(name, PlanetSideEmpire.NEUTRAL, _, _, _), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
case CharacterAppearanceData(BasicCharacterData(name, PlanetSideEmpire.NEUTRAL, _, _, _), _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
Attempt.failure(Err(s"character $name's faction can not declare as neutral"))
case CharacterAppearanceData(pos, BasicCharacterData(name, faction, sex, head, v1), v2, bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons) =>
case CharacterAppearanceData(BasicCharacterData(name, faction, sex, head, v1), v2, bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons) =>
val has_outfit_name : Long = outfit.length.toLong //TODO this is a kludge
var alt_model : Boolean = false
var alt_model_extrabit : Option[Boolean] = None
@ -227,11 +191,13 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
alt_model_extrabit = Some(false)
}
Attempt.successful(
pos :: faction :: bops :: alt_model :: () :: jamd :: false :: 0 :: name :: suit :: () :: sex :: head :: v1 :: v2 :: () :: 0 :: has_outfit_name :: outfit :: logo :: () :: bpack :: false :: facingPitch :: facingYawUpper :: () :: alt_model_extrabit :: () :: lfs :: gstate :: cloaking :: () :: false :: charging :: () :: zipline :: ribbons :: HNil
faction :: bops :: alt_model :: () :: jamd :: false :: 0 :: name :: suit :: () :: sex :: head :: v1 :: v2 :: () :: 0 :: has_outfit_name :: outfit :: logo :: () :: bpack :: false :: facingPitch :: facingYawUpper :: () :: alt_model_extrabit :: () :: lfs :: gstate :: cloaking :: () :: false :: charging :: () :: zipline :: ribbons :: HNil
)
case _ =>
Attempt.Failure(Err("invalid character appearance data; can not decode"))
}
)
implicit val codec : Codec[CharacterAppearanceData] = codec(0)
}

View file

@ -42,32 +42,25 @@ object UniformStyle extends Enumeration {
}
/**
* A part of a representation of the avatar portion of `ObjectCreateMessage` packet data.
* This densely-packed information outlines most of the specifics of depicting some other character.<br>
* A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.<br>
* <br>
* The character created by this data is treated like an NPC from the perspective of the server.
* This densely-packed information outlines most of the specifics required to depict some other player's character.
* Someone else decides how that character is behaving and the server tells each client how to depict that behavior.
* For that reason, the character is mostly for presentation purposes, rather than really being fleshed-out.
* (As far as the client is concerned, nothing stops this character from being declared an "avatar."
* A player would find such a client-controlled character lacking many important details and have poor equipment.
* They would also be competing with some other player for input control, if they could control the character at all.)<br>
* Of the inventory for this character, only the initial five weapon slots are defined.<br>
* <br>
* Divisions exist to make the data more manageable.
* The first division of data only manages the general appearance of the player's in-game model.
* The second division (currently, the fields actually in this class) manages the status of the character.
* In general, it passes more simplified data about the character, the minimum that is necessary to explain status to some other player.
* For example, health and armor are percentages, and are depicted as bars over the player's head near the nameplate.
* The third is the inventory (composed of normal-type objects).
* Rather than equipment other players would never interact with, it only comprises the contents of the five holster slots.<br>
* <br>
* If this player is spawned as dead - with their `health` at 0% - he will start standing and then immediately fall into a lying pose.
* The death pose selected is randomized, can not be influenced, and is not be shared across clients.
* @param appearance the player's cardinal appearance settings
* In the "backend of the client," the character produced by this data is no different
* from the kind of character that could be declared a given player's avatar.
* In terms of equipment and complicated features common to an avatar character, however,
* any user would find this character ill-equipped.
* @param health the amount of health the player has, as a percentage of a filled bar;
* the bar has 85 states, with 3 points for each state;
* when 0% (less than 3 of 255), the player will collapse into a death pose on the ground
* when 0% (less than 3 of 255), the player will collapse into a death pose on the ground;
* while `is_corpse == true`, `health` will always report as 0;
* while `is_seated == true`, `health` will (try to) report as 100
* @param armor the amount of armor the player has, as a percentage of a filled bar;
* the bar has 85 states, with 3 points for each state
* the bar has 85 states, with 3 points for each state;
* while `is_seated == true`, `armor` will always report as 0
* @param uniform_upgrade the level of upgrade to apply to the player's base uniform
* @param command_rank the player's command rank as a number from 0 to 5;
* cosmetic armor associated with the command rank will be applied automatically
@ -76,89 +69,100 @@ object UniformStyle extends Enumeration {
* @param cosmetics optional decorative features that are added to the player's head model by console/chat commands;
* they become available at battle rank 24, but here they require the third uniform upgrade (rank 25);
* these flags do not exist if they are not applicable
* @param inventory the avatar's inventory;
* typically, only the tools and weapons in the equipment holster slots
* @param drawn_slot the holster that is initially drawn;
* defaults to `DrawnSlot.None`
* @see `CharacterAppearanceData`
* @see `DetailedCharacterData`
* @see `InventoryData`
* @see `DrawnSlot`
* @param is_backpack this player character should be depicted as a corpse;
* corpses are either coffins (defunct), backpacks (normal), or a pastry (festive);
* the alternate model bit should be flipped
* @param is_seated this player character is seated in a vehicle or mounted to some other object;
* alternate format for data parsing applies
* @see `DetailedCharacterData`<br>
* `CharacterAppearanceData`
*/
final case class CharacterData(appearance : CharacterAppearanceData,
health : Int,
final case class CharacterData(health : Int,
armor : Int,
uniform_upgrade : UniformStyle.Value,
unk : Int,
command_rank : Int,
implant_effects : Option[ImplantEffects.Value],
cosmetics : Option[Cosmetics],
inventory : Option[InventoryData],
drawn_slot : DrawnSlot.Value = DrawnSlot.None
) extends ConstructorData {
cosmetics : Option[Cosmetics])
(is_backpack : Boolean,
is_seated : Boolean) extends ConstructorData {
override def bitsize : Long = {
//factor guard bool values into the base size, not its corresponding optional field
val appearanceSize : Long = appearance.bitsize
val seatedSize = if(is_seated) { 0 } else { 16 }
val effectsSize : Long = if(implant_effects.isDefined) { 4L } else { 0L }
val cosmeticsSize : Long = if(cosmetics.isDefined) { cosmetics.get.bitsize } else { 0L }
val inventorySize : Long = if(inventory.isDefined) { inventory.get.bitsize } else { 0L }
32L + appearanceSize + effectsSize + cosmeticsSize + inventorySize
11L + seatedSize + effectsSize + cosmeticsSize
}
}
object CharacterData extends Marshallable[CharacterData] {
/**
* An overloaded constructor for `CharacterData` that allows for a not-optional inventory.
* @param appearance the player's cardinal appearance settings
* @param health the amount of health the player has, as a percentage of a filled bar
* @param armor the amount of armor the player has, as a percentage of a filled bar
* @param uniform the level of upgrade to apply to the player's base uniform
* @param cr the player's command rank as a number from 0 to 5
* @param implant_effects the effects of implants that can be seen on a player's character
* @param cosmetics optional decorative features that are added to the player's head model by console/chat commands
* @param inv the avatar's inventory
* @param drawn_slot the holster that is initially drawn
* @return a `CharacterData` object
*/
def apply(appearance : CharacterAppearanceData, health : Int, armor : Int, uniform : UniformStyle.Value, cr : Int, implant_effects : Option[ImplantEffects.Value], cosmetics : Option[Cosmetics], inv : InventoryData, drawn_slot : DrawnSlot.Value) : CharacterData =
new CharacterData(appearance, health, armor, uniform, cr, implant_effects, cosmetics, Some(inv), drawn_slot)
def apply(health : Int, armor : Int, uniform : UniformStyle.Value, cr : Int, implant_effects : Option[ImplantEffects.Value], cosmetics : Option[Cosmetics]) : (Boolean,Boolean)=>CharacterData =
CharacterData(health, armor, uniform, 0, cr, implant_effects, cosmetics)
implicit val codec : Codec[CharacterData] = (
("app" | CharacterAppearanceData.codec) ::
("health" | uint8L) :: //dead state when health == 0
def codec(is_backpack : Boolean) : Codec[CharacterData] = (
("health" | uint8L) :: //dead state when health == 0
("armor" | uint8L) ::
(("uniform_upgrade" | UniformStyle.codec) >>:~ { style =>
ignore(3) :: //unknown
("command_rank" | uintL(3)) ::
bool :: //stream misalignment when != 1
bool :: //misalignment when == 1
optional(bool, "implant_effects" | ImplantEffects.codec) ::
conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | Cosmetics.codec) ::
optional(bool, "inventory" | InventoryData.codec) ::
("drawn_slot" | DrawnSlot.codec) ::
bool //usually false
conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | Cosmetics.codec)
})
).exmap[CharacterData] (
).exmap[CharacterData] (
{
case app :: health :: armor :: uniform :: _ :: cr :: false :: implant_effects :: cosmetics :: inv :: drawn_slot :: false :: HNil =>
var newHealth = health
if(app.backpack) {
newHealth = 0
}
Attempt.Successful(CharacterData(app, newHealth, armor, uniform, cr, implant_effects, cosmetics, inv, drawn_slot))
case health :: armor :: uniform :: _ :: cr :: false :: implant_effects :: cosmetics :: HNil =>
val newHealth = if(is_backpack) { 0 } else { health }
Attempt.Successful(CharacterData(newHealth, armor, uniform, 0, cr, implant_effects, cosmetics)(is_backpack, false))
case _ =>
Attempt.Failure(Err("invalid character data; can not encode"))
},
{
case CharacterData(app, health, armor, uniform, cr, implant_effects, cosmetics, inv, drawn_slot) =>
var newHealth = health
if(app.backpack) {
newHealth = 0
}
Attempt.Successful(app :: newHealth :: armor :: uniform :: () :: cr :: false :: implant_effects :: cosmetics :: inv :: drawn_slot :: false :: HNil)
case CharacterData(health, armor, uniform, _, cr, implant_effects, cosmetics) =>
val newHealth = if(is_backpack) { 0 } else { health }
Attempt.Successful(newHealth :: armor :: uniform :: () :: cr :: false :: implant_effects :: cosmetics :: HNil)
case _ =>
Attempt.Failure(Err("invalid character data; can not decode"))
}
)
def codec_seated(is_backpack : Boolean) : Codec[CharacterData] = (
("uniform_upgrade" | UniformStyle.codec) >>:~ { style =>
ignore(3) :: //unknown
("command_rank" | uintL(3)) ::
bool :: //stream misalignment when != 1
optional(bool, "implant_effects" | ImplantEffects.codec) ::
conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | Cosmetics.codec)
}
).exmap[CharacterData] (
{
case uniform :: _ :: cr :: false :: implant_effects :: cosmetics :: HNil =>
Attempt.Successful(new CharacterData(100, 0, uniform, 0, cr, implant_effects, cosmetics)(is_backpack, true))
case _ =>
Attempt.Failure(Err("invalid character data; can not encode"))
},
{
case obj @ CharacterData(_, _, uniform, _, cr, implant_effects, cosmetics) =>
Attempt.Successful(uniform :: () :: cr :: false :: implant_effects :: cosmetics :: HNil)
case _ =>
Attempt.Failure(Err("invalid character data; can not decode"))
}
)
implicit val codec : Codec[CharacterData] = codec(false)
}

View file

@ -27,22 +27,14 @@ final case class ImplantEntry(implant : ImplantType.Value,
}
/**
* A representation of the avatar portion of `ObjectCreateDetailedMessage` packet data.
* This densely-packed information outlines most of the specifics required to depict a character as an avatar.<br>
* A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.<br>
* <br>
* As an avatar, the character created by this data is expected to be controllable by the client that gets sent this data.
* It goes into depth about information related to the given character in-game career that is not revealed to other players.<br>
* <br>
* Divisions exist to make the data more manageable.
* The first division of data only manages the general appearance of the player's in-game model.
* It is shared between `DetailedCharacterData` avatars and `CharacterData` player characters.
* The second division (of fields) manages the status of the character as an avatar.
* In general, it passes more thorough data about the character that the client can display to the owner of the client.
* This densely-packed information outlines most of the specifics required to depict a character as an avatar.
* It goes into depth about information related to the given character in-game career that is not revealed to other players.
* To be specific, it passes more thorough data about the character that the client can display to the owner of the client.
* For example, health is a full number, rather than a percentage.
* Just as prominent is the list of first time events and the list of completed tutorials.
* The third subdivision is also exclusive to avatar-prepared characters and contains (omitted).
* The fourth is the inventory (composed of `Direct`-type objects).
* @param appearance data about the avatar's basic aesthetics
* Additionally, a full inventory, as opposed to the initial five weapon slots.
* @param bep the avatar's battle experience points, which determines his Battle Rank
* @param cep the avatar's command experience points, which determines his Command Rank
* @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value;
@ -73,16 +65,10 @@ final case class ImplantEntry(implant : ImplantType.Value,
* @param cosmetics optional decorative features that are added to the player's head model by console/chat commands;
* they become available at battle rank 24;
* these flags do not exist if they are not applicable
* @param inventory the avatar's inventory
* @param drawn_slot the holster that is initially drawn
* @see `CharacterAppearanceData`<br>
* `CharacterData`<br>
* `CertificationType`<br>
* `InventoryData`<br>
* `DrawnSlot`
* @see `CharacterData`<br>
* `CertificationType`
*/
final case class DetailedCharacterData(appearance : CharacterAppearanceData,
bep : Long,
final case class DetailedCharacterData(bep : Long,
cep : Long,
healthMax : Int,
health : Int,
@ -96,20 +82,17 @@ final case class DetailedCharacterData(appearance : CharacterAppearanceData,
implants : List[ImplantEntry],
firstTimeEvents : List[String],
tutorials : List[String],
cosmetics : Option[Cosmetics],
inventory : Option[InventoryData],
drawn_slot : DrawnSlot.Value = DrawnSlot.None
) extends ConstructorData {
cosmetics : Option[Cosmetics])
(pad_length : Option[Int]) extends ConstructorData {
override def bitsize : Long = {
//factor guard bool values into the base size, not corresponding optional fields, unless contained or enumerated
val appearanceSize = appearance.bitsize
val certSize = (certs.length + 1) * 8 //cert list
var implantSize : Long = 0L //implant list
for(entry <- implants) {
implantSize += entry.bitsize
}
val implantPadding = DetailedCharacterData.implantFieldPadding(implants, CharacterAppearanceData.altModelBit(appearance))
val implantPadding = DetailedCharacterData.implantFieldPadding(implants, pad_length)
val fteLen = firstTimeEvents.size //fte list
var eventListSize : Long = 32L + DetailedCharacterData.ftePadding(fteLen, implantPadding)
for(str <- firstTimeEvents) {
@ -123,20 +106,13 @@ final case class DetailedCharacterData(appearance : CharacterAppearanceData,
val br24 = DetailedCharacterData.isBR24(bep) //character is at least BR24
val extraBitSize : Long = if(br24) { 33L } else { 46L }
val cosmeticsSize : Long = if(br24) { cosmetics.get.bitsize } else { 0L }
val inventorySize : Long = if(inventory.isDefined) { //inventory
inventory.get.bitsize
}
else {
0L
}
603L + appearanceSize + certSize + implantSize + eventListSize + extraBitSize + cosmeticsSize + tutorialListSize + inventorySize
598L + certSize + implantSize + eventListSize + extraBitSize + cosmeticsSize + tutorialListSize
}
}
object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
/**
* Overloaded constructor for `DetailedCharacterData` that requires an inventory and drops unknown values.
* @param appearance data about the avatar's basic aesthetics
* @param bep the avatar's battle experience points, which determines his Battle Rank
* @param cep the avatar's command experience points, which determines his Command Rank
* @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value
@ -148,12 +124,10 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
* @param implants the `List` of implant slots currently possessed by this avatar
* @param firstTimeEvents the list of first time events performed by this avatar
* @param tutorials the list of tutorials completed by this avatar
* @param inventory the avatar's inventory
* @param drawn_slot the holster that is initially drawn
* @return a `DetailedCharacterData` object
*/
def apply(appearance : CharacterAppearanceData, bep : Long, cep : Long, healthMax : Int, health : Int, armor : Int, staminaMax : Int, stamina : Int, certs : List[CertificationType.Value], implants : List[ImplantEntry], firstTimeEvents : List[String], tutorials : List[String], cosmetics : Option[Cosmetics], inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedCharacterData =
new DetailedCharacterData(appearance, bep, cep, healthMax, health, armor, 1, 7, 7, staminaMax, stamina, certs, implants, firstTimeEvents, tutorials, cosmetics, Some(inventory), drawn_slot)
def apply(bep : Long, cep : Long, healthMax : Int, health : Int, armor : Int, staminaMax : Int, stamina : Int, certs : List[CertificationType.Value], implants : List[ImplantEntry], firstTimeEvents : List[String], tutorials : List[String], cosmetics : Option[Cosmetics]) : (Option[Int])=>DetailedCharacterData =
DetailedCharacterData(bep, cep, healthMax, health, armor, 1, 7, 7, staminaMax, stamina, certs, implants, firstTimeEvents, tutorials, cosmetics)
/**
* `Codec` for entries in the `List` of implants.
@ -278,57 +252,52 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
def isBR24(bep : Long) : Boolean = bep > 2286230
implicit val codec : Codec[DetailedCharacterData] = (
("appearance" | CharacterAppearanceData.codec) >>:~ { app =>
("bep" | uint32L) >>:~ { bep =>
("cep" | uint32L) ::
ignore(96) ::
("healthMax" | uint16L) ::
("health" | uint16L) ::
ignore(1) ::
("armor" | uint16L) ::
ignore(9) ::
("unk1" | uint8L) ::
ignore(8) ::
("unk2" | uint4L) ::
("unk3" | uintL(3)) ::
("staminaMax" | uint16L) ::
("stamina" | uint16L) ::
ignore(147) ::
("certs" | listOfN(uint8L, CertificationType.codec)) ::
optional(bool, uint32L) :: //ask about sample CCRIDER
ignore(4) ::
(("implants" | PacketHelpers.listOfNSized(numberOfImplantSlots(bep), implant_entry_codec)) >>:~ { implants =>
ignore(12) ::
(("firstTimeEvent_length" | uint32L) >>:~ { len =>
conditional(len > 0, "firstTimeEvent_firstEntry" | PacketHelpers.encodedStringAligned(ftePadding(len, implantFieldPadding(implants, CharacterAppearanceData.altModelBit(app))))) ::
("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString)) ::
(("tutorial_length" | uint32L) >>:~ { len2 =>
conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned(tutPadding(len, len2, implantFieldPadding(implants, CharacterAppearanceData.altModelBit(app))))) ::
("tutorial_list" | PacketHelpers.listOfNSized(len2 - 1, PacketHelpers.encodedString)) ::
ignore(160) ::
(bool >>:~ { br24 => //BR24+
newcodecs.binary_choice(br24, ignore(33), ignore(46)) ::
conditional(br24, Cosmetics.codec) ::
optional(bool, "inventory" | InventoryData.codec_detailed) ::
("drawn_slot" | DrawnSlot.codec) ::
bool //usually false
})
})
})
})
}
def codec(pad_length : Option[Int]) : Codec[DetailedCharacterData] = (
("bep" | uint32L) >>:~ { bep =>
("cep" | uint32L) ::
ignore(96) ::
("healthMax" | uint16L) ::
("health" | uint16L) ::
ignore(1) ::
("armor" | uint16L) ::
ignore(9) ::
("unk1" | uint8L) ::
ignore(8) ::
("unk2" | uint4L) ::
("unk3" | uintL(3)) ::
("staminaMax" | uint16L) ::
("stamina" | uint16L) ::
ignore(147) ::
("certs" | listOfN(uint8L, CertificationType.codec)) ::
optional(bool, uint32L) :: //ask about sample CCRIDER
ignore(4) ::
(("implants" | PacketHelpers.listOfNSized(numberOfImplantSlots(bep), implant_entry_codec)) >>:~ { implants =>
ignore(12) ::
(("firstTimeEvent_length" | uint32L) >>:~ { len =>
conditional(len > 0, "firstTimeEvent_firstEntry" | PacketHelpers.encodedStringAligned(ftePadding(len, implantFieldPadding(implants, pad_length)))) ::
("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString)) ::
(("tutorial_length" | uint32L) >>:~ { len2 =>
conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned(tutPadding(len, len2, implantFieldPadding(implants, pad_length)))) ::
("tutorial_list" | PacketHelpers.listOfNSized(len2 - 1, PacketHelpers.encodedString)) ::
ignore(160) ::
(bool >>:~ { br24 => //BR24+
newcodecs.binary_choice(br24, ignore(33), ignore(46)) ::
conditional(br24, Cosmetics.codec)
})
})
})
})
}
).exmap[DetailedCharacterData] (
{
case app :: bep :: cep :: _ :: hpmax :: hp :: _ :: armor :: _ :: u1 :: _ :: u2 :: u3 :: stamax :: stam :: _ :: certs :: _ :: _ :: implants :: _ :: _ :: fte0 :: fte1 :: _ :: tut0 :: tut1 :: _ :: _ :: _ :: cosmetics :: inv :: drawn :: false :: HNil =>
case bep :: cep :: _ :: hpmax :: hp :: _ :: armor :: _ :: u1 :: _ :: u2 :: u3 :: stamax :: stam :: _ :: certs :: _ :: _ :: implants :: _ :: _ :: fte0 :: fte1 :: _ :: tut0 :: tut1 :: _ :: _ :: _ :: cosmetics :: HNil =>
//prepend the displaced first elements to their lists
val fteList : List[String] = if(fte0.isDefined) { fte0.get +: fte1 } else { fte1 }
val tutList : List[String] = if(tut0.isDefined) { tut0.get +: tut1 } else { tut1 }
Attempt.successful(DetailedCharacterData(app, bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, cosmetics, inv, drawn))
Attempt.successful(DetailedCharacterData(bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, cosmetics)(pad_length))
},
{
case DetailedCharacterData(app, bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, cos, inv, drawn) =>
case DetailedCharacterData(bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, cos) =>
val implantCapacity : Int = numberOfImplantSlots(bep)
val implantList = if(implants.length > implantCapacity) {
implants.slice(0, implantCapacity)
@ -349,7 +318,9 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
}
val br24 : Boolean = isBR24(bep)
val cosmetics : Option[Cosmetics] = if(br24) { cos } else { None }
Attempt.successful(app :: bep :: cep :: () :: hpmax :: hp :: () :: armor :: () :: u1 :: () :: u2 :: u3 :: stamax :: stam :: () :: certs :: None :: () :: implantList :: () :: fteList.size.toLong :: firstEvent :: fteListCopy :: tutList.size.toLong :: firstTutorial :: tutListCopy :: () :: br24 :: () :: cosmetics :: inv :: drawn :: false :: HNil)
Attempt.successful(bep :: cep :: () :: hpmax :: hp :: () :: armor :: () :: u1 :: () :: u2 :: u3 :: stamax :: stam :: () :: certs :: None :: () :: implantList :: () :: fteList.size.toLong :: firstEvent :: fteListCopy :: tutList.size.toLong :: firstTutorial :: tutListCopy :: () :: br24 :: () :: cosmetics :: HNil)
}
)
implicit val codec : Codec[DetailedCharacterData] = codec(None)
}

View file

@ -0,0 +1,133 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import scodec.codecs._
import scodec.Codec
import shapeless.{::, HNil}
/**
* A representation of an `avatar` player for the `ObjectCreateDetailedMessage` packet.
* As an avatar, the character created by this data is expected to be controllable by the client that gets sent this data.<br>
* <br>
* Divisions exist to make the data more manageable.
* The first division defines the player's location within the game coordinate system.
* The second division defines features of the `avatar`
* that are shared by both the `ObjectCreateDetailedMessage` version of a controlled player character (this)
* and the `ObjectCreateMessage` version of a player character.
* The third field expands on the nature of the character and this avatar's campaign.
* Expansive information about previous interactions, the contents of their inventory, and equipment permissions are included.<br>
* <br>
* The presence or absence of position data as the first division creates a cascading effect
* causing all of fields in the other two divisions to gain offsets.
* These offsets exist in the form of `String` and `List` padding.
* @see `DetailedCharacterData`<br>
* `InventoryData`<br>
* `DrawnSlot`
* @param pos the optional position of the character in the world environment
* @param basic_appearance common fields regarding the the character's appearance
* @param character_data the class-specific data that explains about the character
* @param position_defined used by the `Codec` to seed the state of the optional `pos` field
* @param inventory the player's full inventory
* @param drawn_slot the holster that is initially drawn
*/
final case class DetailedPlayerData(pos : Option[PlacementData],
basic_appearance : CharacterAppearanceData,
character_data : DetailedCharacterData,
inventory : Option[InventoryData],
drawn_slot : DrawnSlot.Value)
(position_defined : Boolean) extends ConstructorData {
override def bitsize : Long = {
//factor guard bool values into the base size, not its corresponding optional field
val posSize : Long = if(pos.isDefined) { pos.get.bitsize } else { 0L }
val appSize : Long = basic_appearance.bitsize
val charSize = character_data.bitsize
val inventorySize : Long = if(inventory.isDefined) { inventory.get.bitsize } else { 0L }
5L + posSize + appSize + charSize + inventorySize
}
}
object DetailedPlayerData extends Marshallable[DetailedPlayerData] {
/**
* 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 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)
}
def codec(position_defined : Boolean) : Codec[DetailedPlayerData] = (
conditional(position_defined, "pos" | PlacementData.codec) >>:~ { pos =>
("basic_appearance" | CharacterAppearanceData.codec(PlayerData.PaddingOffset(pos))) >>:~ { app =>
("character_data" | DetailedCharacterData.codec(app.altModelBit)) ::
optional(bool, "inventory" | InventoryData.codec_detailed) ::
("drawn_slot" | DrawnSlot.codec) ::
bool //usually false
}
}).xmap[DetailedPlayerData] (
{
case pos :: app :: data :: inv :: hand :: _ :: HNil =>
DetailedPlayerData(pos, app, data, inv, hand)(pos.isDefined)
},
{
case DetailedPlayerData(pos, app, data, inv, hand) =>
pos :: app :: data :: inv :: hand :: false :: HNil
}
)
implicit val codec : Codec[DetailedPlayerData] = codec(false)
}

View file

@ -666,13 +666,21 @@ object ObjectClass {
case ObjectClass.advanced_ace => ConstructorData.genericCodec(DetailedACEData.codec, "advanced ace")
case ObjectClass.boomer_trigger => ConstructorData.genericCodec(DetailedBoomerTriggerData.codec, "boomer trigger")
//other
case ObjectClass.avatar => ConstructorData.genericCodec(DetailedCharacterData.codec, "avatar")
case ObjectClass.avatar => ConstructorData.genericCodec(DetailedPlayerData.codec(false), "avatar")
case ObjectClass.locker_container => ConstructorData.genericCodec(DetailedLockerContainerData.codec, "locker container")
//failure case
case _ => defaultFailureCodec(objClass)
}
def selectDataDroppedDetailedCodec(objClass : Int) : Codec[ConstructorData.genericPattern] =
(objClass : @switch) match {
//special cases
case ObjectClass.avatar => ConstructorData.genericCodec(DetailedPlayerData.codec(true), "avatar")
//defer to other codec selection
case _ => selectDataDetailedCodec(objClass)
}
/**
* Given an object class, retrieve the `Codec` used to parse and translate the constructor data for that type.
* This function services `0x17` `ObjectCreateMessage` packet data.<br>
@ -953,6 +961,7 @@ object ObjectClass {
//other
case ObjectClass.ams_order_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.ams_respawn_tube => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.avatar => ConstructorData.genericCodec(PlayerData.codec(false), "avatar")
case ObjectClass.bfr_rearm_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.implant_terminal_interface => ConstructorData.genericCodec(CommonTerminalData.codec, "implant terminal")
case ObjectClass.lodestar_repair_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
@ -1268,7 +1277,7 @@ object ObjectClass {
case ObjectClass.wasp => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Variant), "vehicle")
//other
case ObjectClass.ams_respawn_tube => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.avatar => ConstructorData.genericCodec(CharacterData.codec, "avatar")
case ObjectClass.avatar => ConstructorData.genericCodec(PlayerData.codec(true), "avatar")
case ObjectClass.capture_flag => ConstructorData.genericCodec(CaptureFlagData.codec, "capture flag")
case ObjectClass.implant_terminal_interface => ConstructorData.genericCodec(CommonTerminalData.codec, "implant terminal")
case ObjectClass.locker_container => ConstructorData.genericCodec(LockerContainerData.codec, "locker container")

View file

@ -0,0 +1,213 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.newcodecs._
import net.psforever.packet.Marshallable
import scodec.codecs._
import scodec.Codec
import shapeless.{::, HNil}
/**
* A representation of another player's character for the `ObjectCreateMessage` packet.
* In general, this packet is used to describe other players.<br>
* <br>
* Divisions exist to make the data more manageable.
* The first division defines the player's location within the game coordinate system.
* The second division defines features of the character
* that are shared by both the `ObjectCreateDetailedMessage` version of a controlled player character
* and the `ObjectCreateMessage` version of a player character (this).
* The third field provides further information on the appearance of the player character, albeit condensed.
* The fourth field involves the player's `Equipment` holsters and their inventory.
* The hand that the player has exposed is last.
* One of the most compact forms of a player character description is transcribed using this information.<br>
* <br>
* The presence or absence of position data as the first division creates a cascading effect
* causing all of fields in the other two divisions to gain offset values.
* These offsets exist in the form of `String` and `List` padding.
* @see `CharacterData`<br>
* `InventoryData`<br>
* `DrawnSlot`
* @param pos the optional position of the character in the world environment
* @param basic_appearance common fields regarding the the character's appearance
* @param character_data the class-specific data that explains about the character
* @param inventory the player's inventory;
* typically, only the tools and weapons in the equipment holster slots
* @param drawn_slot the holster that is initially drawn
* @param position_defined used by the `Codec` to seed the state of the optional `pos` field
*/
final case class PlayerData(pos : Option[PlacementData],
basic_appearance : CharacterAppearanceData,
character_data : CharacterData,
inventory : Option[InventoryData],
drawn_slot : DrawnSlot.Value)
(position_defined : Boolean) extends ConstructorData {
override def bitsize : Long = {
//factor guard bool values into the base size, not its corresponding optional field
val posSize : Long = if(pos.isDefined) { pos.get.bitsize } else { 0L }
val appSize : Long = basic_appearance.bitsize
val charSize = character_data.bitsize
val inventorySize : Long = if(inventory.isDefined) { inventory.get.bitsize } else { 0L }
5L + posSize + appSize + charSize + inventorySize
}
}
object PlayerData extends Marshallable[PlayerData] {
/**
* 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 `PlayerData` object
*/
def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Type) : PlayerData = {
val appearance = basic_appearance(5)
PlayerData(None, appearance, character_data(appearance.backpack, true), 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 `PlayerData` object
*/
def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, drawn_slot : DrawnSlot.Type) : PlayerData = {
val appearance = basic_appearance(5)
PlayerData(None, appearance, character_data(appearance.backpack, true), None, drawn_slot)(false)
}
/**
* 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 `PlayerData` object
*/
def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Type) : PlayerData = {
val appearance = basic_appearance( PaddingOffset(Some(pos)) )
PlayerData(Some(pos), appearance, character_data(appearance.backpack, false), 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 `PlayerData` object
*/
def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, drawn_slot : DrawnSlot.Type) : PlayerData = {
val appearance = basic_appearance( PaddingOffset(Some(pos)) )
PlayerData(Some(pos), appearance, character_data(appearance.backpack, false), None, drawn_slot)(true)
}
/**
* Determine the padding offset for a subsequent field given the existence of `PlacementData`.
* With the `PlacementData` objects, a question of the optional velocity field also exists.<br>
* <br>
* With just `PlacementData`, the bit distance to the name field is 164 (padding: 4 bits).
* With `PlacementData` with velocity, the bit distance to the name field is 206 (padding: 2 bits).
* Without `PlacementData`, the distance to the name field is either 107 or 115 (padding: 5 bits).
* The padding will always be a number 0-7.
* @see `PlacementData`
* @param pos the optional `PlacementData` object that creates the shift in bits
* @return the pad length in bits
*/
def PaddingOffset(pos : Option[PlacementData]) : Int = {
/*
The `ObjectCreateMessage` length is either 32 + 12 + 16 + 81 - 141 - with `PlacementData`,
with an additional +42 - 183 - with the optional velocity field,
or 32 + 12 + 16 + 16 + 8/16 - 84/92 - without any `PlacementData`.
23 is the distance of all the fields before the player's `name` field in `CharacterAppearanceData`.
*/
pos match {
case Some(place) =>
if(place.vel.isDefined) { 2 } else { 4 }
case None =>
5 //with ObjectCreateMessageParent data
}
}
/**
* Find the number of trailing bits that need to be added to make the current value perfectly divisible by eight.
* @param length the current length of a stream
* @return the number of bits needed to pad it
*/
def ByteAlignmentPadding(length : Long) : Int = {
val pad = (length - math.floor(length / 8) * 8).toInt
if(pad > 0) {
8 - pad
}
else {
0
}
}
/**
* This `Codec` is generic.
* However, it should not be used to translate a `Player` object
* in the middle of translating that `Player`'s mounting object.
* The offset value is calculated internally.
* @param position_defined this entry has `PlacementData` that defines position, orientation, and, optionally, motion
* @return a `Codec` that translates a `PlayerData` object
*/
def codec(position_defined : Boolean) : Codec[PlayerData] = (
conditional(position_defined, "pos" | PlacementData.codec) >>:~ { pos =>
("basic_appearance" | CharacterAppearanceData.codec(PaddingOffset(pos))) >>:~ { app =>
("character_data" | newcodecs.binary_choice(position_defined,
CharacterData.codec(app.backpack),
CharacterData.codec_seated(app.backpack))) ::
optional(bool, "inventory" | InventoryData.codec) ::
("drawn_slot" | DrawnSlot.codec) ::
bool //usually false
}
}).xmap[PlayerData] (
{
case pos :: app :: data :: inv :: hand :: _ :: HNil =>
PlayerData(pos, app, data, inv, hand)(pos.isDefined)
},
{
case PlayerData(pos, app, data, inv, hand) =>
pos :: app :: data :: inv :: hand :: false :: HNil
}
)
/**
* This `Codec` is exclusively for translating a `Player` object
* while that `Player` object is encountered in the process of translating its mounting object.
* In other words, the player is "seated" or "mounted."
* @see `CharacterAppearanceData.codec`
* @param offset the padding for the player's name field
* @return a `Codec` that translates a `PlayerData` object
*/
def codec(offset : Int) : Codec[PlayerData] = (
("basic_appearance" | CharacterAppearanceData.codec(offset)) >>:~ { app =>
("character_data" | CharacterData.codec_seated(app.backpack)) ::
optional(bool, "inventory" | InventoryData.codec) ::
("drawn_slot" | DrawnSlot.codec) ::
bool //usually false
}
).xmap[PlayerData] (
{
case app :: data :: inv :: hand :: _ :: HNil =>
PlayerData(None, app, data, inv, hand)(false)
},
{
case PlayerData(None, app, data, inv, hand) =>
app :: data :: inv :: hand :: false :: HNil
}
)
implicit val codec : Codec[PlayerData] = codec(false)
}

View file

@ -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)
)
}
}
}

View file

@ -1,12 +1,15 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
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 //note: do not import shapeless.:: here; it messes up List's :: functionality
import scodec.codecs._
import shapeless.{::, HNil}
import net.psforever.types.{DriveState, PlanetSideEmpire}
import net.psforever.types.DriveState
import scala.collection.mutable.ListBuffer
/**
* An `Enumeration` of the various formats that known structures that the stream of bits for `VehicleData` can assume.
@ -47,100 +50,138 @@ final case class VariantVehicleData(unk : Int) extends SpecificVehicleData {
}
/**
* A representation of a generic vehicle.<br>
* <br>
* 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}
/**
* Constructor that ignores the coordinate information
* and performs a vehicle-unique calculation of the padding value.
* 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 accumulative the input position for the stream up to which this entry;
* used to calculate the padding value for the player's name in `CharacterAppearanceData`
* @return a `PlayerData` object
*/
def PlayerData(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Type, accumulative : Long) : Player_Data = {
val appearance = basic_appearance(CumulativeSeatedPlayerNamePadding(accumulative))
Player_Data(None, appearance, character_data(appearance.backpack, true), Some(inventory), drawn_slot)(false)
}
/**
* Constructor for `PlayerData` that ignores the coordinate information and the inventory
* and performs a vehicle-unique calculation of the padding value.
* 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
* @param accumulative the input position for the stream up to which this entry;
* used to calculate the padding value for the player's name in `CharacterAppearanceData`
* @return a `PlayerData` object
*/
def PlayerData(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, drawn_slot : DrawnSlot.Type, accumulative : Long) : Player_Data = {
val appearance = basic_appearance(CumulativeSeatedPlayerNamePadding(accumulative))
Player_Data.apply(None, appearance, character_data(appearance.backpack, true), None, drawn_slot)(false)
}
private val driveState8u = PacketHelpers.createEnumerationCodec(DriveState, uint8L)
@ -148,33 +189,39 @@ object VehicleData extends Marshallable[VehicleData] {
/**
* `Codec` for the "utility" format.
*/
private val utility_data_codec : Codec[SpecificVehicleData] = uintL(6).hlist.exmap[SpecificVehicleData] (
{
case n :: HNil =>
Successful(UtilityVehicleData(n).asInstanceOf[SpecificVehicleData])
},
{
case UtilityVehicleData(n) =>
Successful(n :: HNil)
case _ =>
Failure(Err("wrong kind of vehicle data object (wants 'Utility')"))
}
)
private val utility_data_codec : Codec[SpecificVehicleData] = {
import shapeless.::
uintL(6).hlist.exmap[SpecificVehicleData] (
{
case n :: HNil =>
Successful(UtilityVehicleData(n).asInstanceOf[SpecificVehicleData])
},
{
case UtilityVehicleData(n) =>
Successful(n :: HNil)
case _ =>
Failure(Err("wrong kind of vehicle data object (wants 'Utility')"))
}
)
}
/**
* `Codec` for the "variant" format.
*/
private val variant_data_codec : Codec[SpecificVehicleData] = uint8L.hlist.exmap[SpecificVehicleData] (
{
case n :: HNil =>
Successful(VariantVehicleData(n).asInstanceOf[SpecificVehicleData])
},
{
case VariantVehicleData(n) =>
Successful(n :: HNil)
case _ =>
Failure(Err("wrong kind of vehicle data object (wants 'Variant')"))
}
)
private val variant_data_codec : Codec[SpecificVehicleData] = {
import shapeless.::
uint8L.hlist.exmap[SpecificVehicleData] (
{
case n :: HNil =>
Successful(VariantVehicleData(n).asInstanceOf[SpecificVehicleData])
},
{
case VariantVehicleData(n) =>
Successful(n :: HNil)
case _ =>
Failure(Err("wrong kind of vehicle data object (wants 'Variant')"))
}
)
}
/**
* Select an appropriate `Codec` in response to the requested stream format
@ -190,47 +237,303 @@ object VehicleData extends Marshallable[VehicleData] {
Failure(Err(s"$vehicleFormat is not a valid vehicle format for parsing data")).asInstanceOf[Codec[SpecificVehicleData]]
}
def codec(vehicle_type : VehicleFormat.Value) : Codec[VehicleData] = (
("basic" | CommonFieldData.codec) ::
("unk1" | uint2L) ::
("health" | uint8L) ::
("unk2" | 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) ::
("cloak" | bool) :: //cloak as wraith, phantasm
conditional(vehicle_type != VehicleFormat.Normal, "unk5" | selectFormatReader(vehicle_type)) :: //padding?
optional(bool, "inventory" | InventoryData.codec)
).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))
def codec(vehicle_type : VehicleFormat.Value) : Codec[VehicleData] = {
import shapeless.::
(
("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) ::
("unk4" | bool) :: //usually 0
("no_mount_points" | bool) ::
("driveState" | driveState8u) :: //used for deploy state
("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, "vehicle_format_data" | selectFormatReader(vehicle_type)) :: //padding?
optional(bool, "inventory" | custom_inventory_codec(InitialStreamLengthToSeatEntries(pos.vel.isDefined, vehicle_type)))
}
).exmap[VehicleData] (
{
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 ..."))
}
else {
Attempt.successful(basic :: u1 :: health :: u2 :: no_mount :: driveState :: u3 :: u4 :: cloak :: Some(u5) :: inv :: HNil)
}
case _ =>
Attempt.failure(Err("invalid vehicle data format"))
},
{
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(pos :: faction :: bops :: destroyed :: u1 :: jamd :: u2 :: owner :: u3 :: health :: u4 :: no_mount :: driveState :: u5 :: u6 :: cloak :: format :: 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)
}
case _ =>
Attempt.failure(Err("invalid vehicle data format"))
}
)
}
case _ =>
Attempt.failure(Err("invalid vehicle data format"))
/**
* 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.<br>
* Note:<br>
* 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
* @return the length of the bitstream
*/
def InitialStreamLengthToSeatEntries(hasVelocity : Boolean, format : VehicleFormat.Type) : Long = {
198 +
(if(hasVelocity) { 42 } else { 0 }) +
(format match {
case VehicleFormat.Utility => 6
case VehicleFormat.Variant => 8
case _ => 0
})
}
/**
* Increment the distance to the next mounted player's `name` field with the length of the previous entry,
* then calculate the new padding value for that next entry's `name` field.
* @param base the original distance to the last entry
* @param next the length of the last entry, if one was parsed
* @return the padding value, 0-7 bits
*/
def CumulativeSeatedPlayerNamePadding(base : Long, next : Option[StreamBitSize]) : Int = {
CumulativeSeatedPlayerNamePadding(base + (next match {
case Some(o) => o.bitsize
case None => 0
}))
}
/**
* Calculate the padding value for the next mounted player character's name `String`.
* Due to the depth of seated player characters, the `name` field can have a variable amount of padding
* between the string size field and the first character.
* Specifically, the padding value is the number of bits after the size field
* that would cause the first character of the name to be aligned to the first bit of the next byte.
* The 35 counts the object class, unique identifier, and slot fields of the enclosing `InternalSlot`.
* The 23 counts all of the fields before the player's `name` field in `CharacterAppearanceData`.
* @see `InternalSlot`<br>
* `CharacterAppearanceData.name`<br>
* `VehicleData.InitialStreamLengthToSeatEntries`
* @param accumulative current entry stream offset (start of this player's entry)
* @return the padding value, 0-7 bits
*/
private def CumulativeSeatedPlayerNamePadding(accumulative : Long) : Int = {
Player_Data.ByteAlignmentPadding(accumulative + 23 + 35)
}
/**
* A special method of handling mounted players within the same inventory space as normal `Equipment` can be encountered
* before restoring normal inventory operations.<br>
* <br>
* 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.
* After player-related entries have been extracted and processed in isolation,
* 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`.<br>
* <br>
* 6 June 2018:<br>
* 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`
*/
private def custom_inventory_codec(length : Long) : Codec[InventoryData] = {
import shapeless.::
(
uint8 >>:~ { size =>
uint2 ::
(inventory_seat_codec(
length, //length of stream until current seat
CumulativeSeatedPlayerNamePadding(length) //calculated offset of name field in next seat
) >>:~ { seats =>
PacketHelpers.listOfNSized(size - countSeats(seats), InternalSlot.codec).hlist
})
}
).xmap[InventoryData] (
{
case _ :: _ :: None :: inv :: HNil =>
InventoryData(inv)
case _ :: _ :: seats :: inv :: HNil =>
InventoryData(unlinkSeats(seats) ++ inv)
},
{
case InventoryData(inv) =>
val (seats, slots) = inv.partition(entry => entry.objectClass == ObjectClass.avatar)
inv.size :: 0 :: chainSeats(seats) :: slots :: HNil
}
)
}
/**
* The format for the linked list of extracted mounted `PlayerData`.
* @param seat data for this entry extracted via `PlayerData`
* @param next the next entry
*/
private case class InventorySeat(seat : Option[InternalSlot], next : Option[InventorySeat])
/**
* Look ahead at the next value to determine if it is an example of a player character
* and would be processed as a `PlayerData` object.
* Update the stream read position with each extraction.
* Continue to process values so long as they represent player character data.
* @param length the distance in bits to the current inventory entry
* @param offset the padding value for this entry's player character's `name` field
* @return a recursive `Codec` that translates subsequent `PlayerData` entries until exhausted
*/
private def inventory_seat_codec(length : Long, offset : Int) : Codec[Option[InventorySeat]] = {
import shapeless.::
(
PacketHelpers.peek(uintL(11)) >>:~ { objClass =>
conditional(objClass == ObjectClass.avatar, seat_codec(offset)) >>:~ { seat =>
conditional(objClass == ObjectClass.avatar, inventory_seat_codec(
{ //length of stream until next seat
length + (seat match {
case Some(o) => o.bitsize
case None => 0
})
},
CumulativeSeatedPlayerNamePadding(length, seat) //calculated offset of name field in next seat
)).hlist
}
}
).exmap[Option[InventorySeat]] (
{
case _ :: None :: None :: HNil =>
Successful(None)
case _ :: slot :: Some(next) :: HNil =>
Successful(Some(InventorySeat(slot, next)))
},
{
case None =>
Successful(0 :: None :: None :: HNil)
case Some(InventorySeat(slot, None)) =>
Successful(ObjectClass.avatar :: slot :: None :: HNil)
case Some(InventorySeat(slot, next)) =>
Successful(ObjectClass.avatar :: slot :: Some(next) :: HNil)
}
)
}
/**
* Translate data the is verified to involve a player who is seated (mounted) to the parent object at a given slot.
* The operation performed by this `Codec` is very similar to `InternalSlot.codec`.
* @param pad the padding offset for the player's name;
* 0-7 bits;
* this padding value must recalculate for each represented seat
* @see `CharacterAppearanceData`<br>
* `VehicleData.InitialStreamLengthToSeatEntries`<br>
* `CumulativeSeatedPlayerNamePadding`
* @return a `Codec` that translates `PlayerData`
*/
private def seat_codec(pad : Int) : Codec[InternalSlot] = {
import shapeless.::
(
("objectClass" | uintL(11)) ::
("guid" | PlanetSideGUID.codec) ::
("parentSlot" | PacketHelpers.encodedStringSize) ::
("obj" | Player_Data.codec(pad))
).xmap[InternalSlot] (
{
case objectClass :: guid :: parentSlot :: obj :: HNil =>
InternalSlot(objectClass, guid, parentSlot, obj)
},
{
case InternalSlot(objectClass, guid, parentSlot, obj) =>
objectClass :: guid :: parentSlot :: obj.asInstanceOf[PlayerData] :: HNil
}
)
}
/**
* Count the number of entries in a linked list.
* @param chain the head of the linked list
* @return the number of entries
*/
private def countSeats(chain : Option[InventorySeat]) : Int = {
chain match {
case Some(_) =>
var curr = chain
var count = 0
do {
val link = curr.get
count += (if(link.seat.nonEmpty) { 1 } else { 0 })
curr = link.next
}
while(curr.nonEmpty)
count
case None =>
0
}
)
}
/**
* Transform a linked list of `InventorySlot` slot objects into a formal list of `InternalSlot` objects.
* @param chain the head of the linked list
* @return a proper list of the contents of the input linked list
*/
private def unlinkSeats(chain : Option[InventorySeat]) : List[InternalSlot] = {
var curr = chain
val out = new ListBuffer[InternalSlot]
while(curr.isDefined) {
val link = curr.get
link.seat match {
case None =>
curr = None
case Some(seat) =>
out += seat
curr = link.next
}
}
out.toList
}
/**
* Transform a formal list of `InternalSlot` objects into a linked list of `InventorySlot` slot objects.
* @param list a proper list of objects
* @return a linked list composed of the contents of the input list
*/
private def chainSeats(list : List[InternalSlot]) : Option[InventorySeat] = {
list match {
case Nil =>
None
case x :: Nil =>
Some(InventorySeat(Some(x), None))
case _ :: _ =>
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))
})
Some(link)
}
}
implicit val codec : Codec[VehicleData] = codec(VehicleFormat.Normal)
}

View file

@ -0,0 +1,28 @@
// Copyright (c) 2017 PSForever
package net.psforever.types
import net.psforever.packet.PacketHelpers
import scodec.codecs.uint
/**
* The voice used by the player character, from a selection of ten divided between five male voices and five female voices.
* The first entry (0) is no voice.
* While it is technically not valid to have a wrong-gendered voice,
* unlisted sixth and seventh entries would give a male character a female voice;
* a female character with either entry would become mute, however.
* @see `CharacterGender`
*/
object CharacterVoice extends Enumeration {
type Type = Value
val
Mute,
Voice1, //grizzled, tough
Voice2, //greenhorn, clueless
Voice3, //roughneck, gruff
Voice4, //stalwart, smooth
Voice5 //daredevil, calculating
= Value
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint(3))
}

View file

@ -4,7 +4,7 @@ package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
import net.psforever.types.{CharacterGender, PlanetSideEmpire}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire}
import scodec.bits._
class CharacterCreateRequestMessageTest extends Specification {
@ -15,7 +15,7 @@ class CharacterCreateRequestMessageTest extends Specification {
case CharacterCreateRequestMessage(name, head, voice, gender, faction) =>
name mustEqual "TestChar"
head mustEqual 50
voice mustEqual 5
voice mustEqual CharacterVoice.Voice5
gender mustEqual CharacterGender.Female
faction mustEqual PlanetSideEmpire.NC
case _ =>
@ -24,7 +24,7 @@ class CharacterCreateRequestMessageTest extends Specification {
}
"encode" in {
val msg = CharacterCreateRequestMessage("TestChar", 50, 5, CharacterGender.Female, PlanetSideEmpire.NC)
val msg = CharacterCreateRequestMessage("TestChar", 50, CharacterVoice.Voice5, CharacterGender.Female, PlanetSideEmpire.NC)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string

View file

@ -9,218 +9,264 @@ import org.specs2.mutable._
import scodec.bits._
class CharacterDataTest extends Specification {
val string_character = hex"17 73070000 BC8 3E0F 6C2D7 65535 CA16 00 00 09 9741E4F804000000 234530063007200610077006E00790052006F006E006E0069006500 220B7 E67B540404001000000000022B50100 268042006C00610063006B002000420065007200650074002000410072006D006F007500720065006400200043006F00720070007300 1700E0030050040003BC00000234040001A004000 3FFF67A8F A0A5424E0E800000000080952A9C3A03000001081103E040000000A023782F1080C0000016244108200000000808382403A030000014284C3A0C0000000202512F00B80C00000578F80F840000000280838B3C320300000080"
val string_character_backpack = hex"17 9C030000 BC8 340D F20A9 3956C AF0D 00 00 73 480000 87041006E00670065006C006C006F00 4A148 0000000000000000000000005C54200 24404F0072006900670069006E0061006C00200044006900730074007200690063007400 1740180181E8000000C202000042000000D202000000010A3C00"
val string = hex"17 73070000 BC8 3E0F 6C2D7 65535 CA16 00 00 09 9741E4F804000000 234530063007200610077006E00790052006F006E006E0069006500 220B7 E67B540404001000000000022B50100 268042006C00610063006B002000420065007200650074002000410072006D006F007500720065006400200043006F00720070007300 1700E0030050040003BC00000234040001A004000 3FFF67A8F A0A5424E0E800000000080952A9C3A03000001081103E040000000A023782F1080C0000016244108200000000808382403A030000014284C3A0C0000000202512F00B80C00000578F80F840000000280838B3C320300000080"
//string seated was intentionally-produced test data
val string_seated =
hex"170307000069023c83e0f800000011a0530063007200610077006e00790052006f006e006e0069006500220b700000000000000000000000" ++
hex"06800000268042006c00610063006b002000420065007200650074002000410072006d006f007500720065006400200043006f0072007000" ++
hex"73001700e0030050040003bc00000234040001a00400020a8fa0a5424e0e800000000080952a9c3a03000001081103e040000000a023782f" ++
hex"1080c0000016244108200000000808382403a030000014284c3a0c0000000202512f00b80c00000578f80f840000000280838b3c320300000080"
val string_backpack = hex"17 9C030000 BC8 340D F20A9 3956C AF0D 00 00 73 480000 87041006E00670065006C006C006F00 4A148 0000000000000000000000005C54200 24404F0072006900670069006E0061006C00200044006900730074007200690063007400 1740180181E8000000C202000042000000D202000000010A3C00"
"CharacterData" should {
"decode" in {
PacketCoding.DecodePacket(string_character).require match {
PacketCoding.DecodePacket(string).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 1907
cls mustEqual ObjectClass.avatar
guid mustEqual PlanetSideGUID(3902)
parent.isDefined mustEqual false
data.isDefined mustEqual true
data.get.isInstanceOf[CharacterData] mustEqual true
val pc = data.get.asInstanceOf[CharacterData]
pc.appearance.pos.coord.x mustEqual 3674.8438f
pc.appearance.pos.coord.y mustEqual 2726.789f
pc.appearance.pos.coord.z mustEqual 91.15625f
pc.appearance.pos.orient.x mustEqual 0f
pc.appearance.pos.orient.y mustEqual 0f
pc.appearance.pos.orient.z mustEqual 64.6875f
pc.appearance.pos.vel.isDefined mustEqual true
pc.appearance.pos.vel.get.x mustEqual 1.4375f
pc.appearance.pos.vel.get.y mustEqual -0.4375f
pc.appearance.pos.vel.get.z mustEqual 0f
pc.appearance.basic_appearance.name mustEqual "ScrawnyRonnie"
pc.appearance.basic_appearance.faction mustEqual PlanetSideEmpire.TR
pc.appearance.basic_appearance.sex mustEqual CharacterGender.Male
pc.appearance.basic_appearance.head mustEqual 5
pc.appearance.basic_appearance.voice mustEqual 5
pc.appearance.voice2 mustEqual 3
pc.appearance.black_ops mustEqual false
pc.appearance.jammered mustEqual false
pc.appearance.exosuit mustEqual ExoSuitType.Reinforced
pc.appearance.outfit_name mustEqual "Black Beret Armoured Corps"
pc.appearance.outfit_logo mustEqual 23
pc.appearance.facingPitch mustEqual 340.3125f
pc.appearance.facingYawUpper mustEqual 0
pc.appearance.lfs mustEqual false
pc.appearance.grenade_state mustEqual GrenadeState.None
pc.appearance.is_cloaking mustEqual false
pc.appearance.charging_pose mustEqual false
pc.appearance.on_zipline mustEqual false
pc.appearance.ribbons.upper mustEqual MeritCommendation.MarkovVeteran
pc.appearance.ribbons.middle mustEqual MeritCommendation.HeavyInfantry4
pc.appearance.ribbons.lower mustEqual MeritCommendation.TankBuster7
pc.appearance.ribbons.tos mustEqual MeritCommendation.SixYearTR
pc.health mustEqual 255
pc.armor mustEqual 253
pc.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
pc.command_rank mustEqual 5
pc.implant_effects.isDefined mustEqual true
pc.implant_effects.get mustEqual ImplantEffects.NoEffects
pc.cosmetics.isDefined mustEqual true
pc.cosmetics.get.no_helmet mustEqual true
pc.cosmetics.get.beret mustEqual true
pc.cosmetics.get.sunglasses mustEqual true
pc.cosmetics.get.earpiece mustEqual true
pc.cosmetics.get.brimmed_cap mustEqual false
//short test of inventory items
pc.inventory.isDefined mustEqual true
val contents = pc.inventory.get.contents
contents.size mustEqual 5
//0
contents.head.objectClass mustEqual ObjectClass.plasma_grenade
contents.head.guid mustEqual PlanetSideGUID(3662)
contents.head.parentSlot mustEqual 0
contents.head.obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
contents.head.obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.plasma_grenade_ammo
contents.head.obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3751)
//1
contents(1).objectClass mustEqual ObjectClass.bank
contents(1).guid mustEqual PlanetSideGUID(3908)
contents(1).parentSlot mustEqual 1
contents(1).obj.asInstanceOf[WeaponData].fire_mode mustEqual 1
contents(1).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.armor_canister
contents(1).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(4143)
//2
contents(2).objectClass mustEqual ObjectClass.mini_chaingun
contents(2).guid mustEqual PlanetSideGUID(4164)
contents(2).parentSlot mustEqual 2
contents(2).obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
contents(2).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.bullet_9mm
contents(2).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3728)
//3
contents(3).objectClass mustEqual ObjectClass.phoenix //actually, a decimator
contents(3).guid mustEqual PlanetSideGUID(3603)
contents(3).parentSlot mustEqual 3
contents(3).obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
contents(3).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.phoenix_missile
contents(3).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3056)
//4
contents(4).objectClass mustEqual ObjectClass.chainblade
contents(4).guid mustEqual PlanetSideGUID(4088)
contents(4).parentSlot mustEqual 4
contents(4).obj.asInstanceOf[WeaponData].fire_mode mustEqual 1
contents(4).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.melee_ammo
contents(4).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3279)
pc.drawn_slot mustEqual DrawnSlot.Rifle1
data match {
case Some(PlayerData(Some(pos), basic, char, inv, hand)) =>
pos.coord mustEqual Vector3(3674.8438f, 2726.789f, 91.15625f)
pos.orient mustEqual Vector3(0f, 0f, 64.6875f)
pos.vel.isDefined mustEqual true
pos.vel.get mustEqual Vector3(1.4375f, -0.4375f, 0f)
basic.app.name mustEqual "ScrawnyRonnie"
basic.app.faction mustEqual PlanetSideEmpire.TR
basic.app.sex mustEqual CharacterGender.Male
basic.app.head mustEqual 5
basic.app.voice mustEqual CharacterVoice.Voice5
basic.voice2 mustEqual 3
basic.black_ops mustEqual false
basic.jammered mustEqual false
basic.exosuit mustEqual ExoSuitType.Reinforced
basic.outfit_name mustEqual "Black Beret Armoured Corps"
basic.outfit_logo mustEqual 23
basic.facingPitch mustEqual 340.3125f
basic.facingYawUpper mustEqual 0
basic.lfs mustEqual false
basic.grenade_state mustEqual GrenadeState.None
basic.is_cloaking mustEqual false
basic.charging_pose mustEqual false
basic.on_zipline mustEqual false
basic.ribbons.upper mustEqual MeritCommendation.MarkovVeteran
basic.ribbons.middle mustEqual MeritCommendation.HeavyInfantry4
basic.ribbons.lower mustEqual MeritCommendation.TankBuster7
basic.ribbons.tos mustEqual MeritCommendation.SixYearTR
char.health mustEqual 255
char.armor mustEqual 253
char.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
char.command_rank mustEqual 5
char.implant_effects.isDefined mustEqual true
char.implant_effects.get mustEqual ImplantEffects.NoEffects
char.cosmetics.isDefined mustEqual true
char.cosmetics.get.no_helmet mustEqual true
char.cosmetics.get.beret mustEqual true
char.cosmetics.get.sunglasses mustEqual true
char.cosmetics.get.earpiece mustEqual true
char.cosmetics.get.brimmed_cap mustEqual false
//short test of inventory items
inv.isDefined mustEqual true
val contents = inv.get.contents
contents.size mustEqual 5
//0
contents.head.objectClass mustEqual ObjectClass.plasma_grenade
contents.head.guid mustEqual PlanetSideGUID(3662)
contents.head.parentSlot mustEqual 0
contents.head.obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
contents.head.obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.plasma_grenade_ammo
contents.head.obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3751)
//1
contents(1).objectClass mustEqual ObjectClass.bank
contents(1).guid mustEqual PlanetSideGUID(3908)
contents(1).parentSlot mustEqual 1
contents(1).obj.asInstanceOf[WeaponData].fire_mode mustEqual 1
contents(1).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.armor_canister
contents(1).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(4143)
//2
contents(2).objectClass mustEqual ObjectClass.mini_chaingun
contents(2).guid mustEqual PlanetSideGUID(4164)
contents(2).parentSlot mustEqual 2
contents(2).obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
contents(2).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.bullet_9mm
contents(2).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3728)
//3
contents(3).objectClass mustEqual ObjectClass.phoenix //actually, a decimator
contents(3).guid mustEqual PlanetSideGUID(3603)
contents(3).parentSlot mustEqual 3
contents(3).obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
contents(3).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.phoenix_missile
contents(3).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3056)
//4
contents(4).objectClass mustEqual ObjectClass.chainblade
contents(4).guid mustEqual PlanetSideGUID(4088)
contents(4).parentSlot mustEqual 4
contents(4).obj.asInstanceOf[WeaponData].fire_mode mustEqual 1
contents(4).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.melee_ammo
contents(4).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3279)
hand mustEqual DrawnSlot.Rifle1
case _ =>
ko
}
case _ =>
ko
}
}
"decode (seated)" in {
PacketCoding.DecodePacket(string_seated).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 1795
cls mustEqual ObjectClass.avatar
guid mustEqual PlanetSideGUID(3902)
parent mustEqual Some(ObjectCreateMessageParent(PlanetSideGUID(1234), 0))
data match {
case Some(PlayerData(None, basic, char, inv, hand)) =>
basic.app.name mustEqual "ScrawnyRonnie"
basic.app.faction mustEqual PlanetSideEmpire.TR
basic.app.sex mustEqual CharacterGender.Male
basic.app.head mustEqual 5
basic.app.voice mustEqual CharacterVoice.Voice5
basic.voice2 mustEqual 3
basic.black_ops mustEqual false
basic.jammered mustEqual false
basic.exosuit mustEqual ExoSuitType.Reinforced
basic.outfit_name mustEqual "Black Beret Armoured Corps"
basic.outfit_logo mustEqual 23
basic.facingPitch mustEqual 340.3125f
basic.facingYawUpper mustEqual 0
basic.lfs mustEqual false
basic.grenade_state mustEqual GrenadeState.None
basic.is_cloaking mustEqual false
basic.charging_pose mustEqual false
basic.on_zipline mustEqual false
basic.ribbons.upper mustEqual MeritCommendation.MarkovVeteran
basic.ribbons.middle mustEqual MeritCommendation.HeavyInfantry4
basic.ribbons.lower mustEqual MeritCommendation.TankBuster7
basic.ribbons.tos mustEqual MeritCommendation.SixYearTR
//etc..
case _ =>
ko
}
case _ =>
ko
}
}
"decode (backpack)" in {
PacketCoding.DecodePacket(string_character_backpack).require match {
PacketCoding.DecodePacket(string_backpack).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 924L
cls mustEqual ObjectClass.avatar
guid mustEqual PlanetSideGUID(3380)
parent.isDefined mustEqual false
data.isDefined mustEqual true
data.get.isInstanceOf[CharacterData] mustEqual true
val pc = data.get.asInstanceOf[CharacterData]
pc.appearance.pos.coord.x mustEqual 4629.8906f
pc.appearance.pos.coord.y mustEqual 6316.4453f
pc.appearance.pos.coord.z mustEqual 54.734375f
pc.appearance.pos.orient.x mustEqual 0f
pc.appearance.pos.orient.y mustEqual 0f
pc.appearance.pos.orient.z mustEqual 126.5625f
pc.appearance.pos.vel.isDefined mustEqual false
pc.appearance.basic_appearance.name mustEqual "Angello"
pc.appearance.basic_appearance.faction mustEqual PlanetSideEmpire.VS
pc.appearance.basic_appearance.sex mustEqual CharacterGender.Male
pc.appearance.basic_appearance.head mustEqual 10
pc.appearance.basic_appearance.voice mustEqual 2
pc.appearance.voice2 mustEqual 0
pc.appearance.black_ops mustEqual false
pc.appearance.jammered mustEqual false
pc.appearance.exosuit mustEqual ExoSuitType.MAX
pc.appearance.outfit_name mustEqual "Original District"
pc.appearance.outfit_logo mustEqual 23
pc.appearance.facingPitch mustEqual 0
pc.appearance.facingYawUpper mustEqual 180.0f
pc.appearance.lfs mustEqual false
pc.appearance.grenade_state mustEqual GrenadeState.None
pc.appearance.is_cloaking mustEqual false
pc.appearance.charging_pose mustEqual false
pc.appearance.on_zipline mustEqual false
pc.appearance.ribbons.upper mustEqual MeritCommendation.Jacking2
pc.appearance.ribbons.middle mustEqual MeritCommendation.ScavengerVS1
pc.appearance.ribbons.lower mustEqual MeritCommendation.AMSSupport4
pc.appearance.ribbons.tos mustEqual MeritCommendation.SixYearVS
pc.health mustEqual 0
pc.armor mustEqual 0
pc.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
pc.command_rank mustEqual 2
pc.implant_effects.isDefined mustEqual false
pc.cosmetics.isDefined mustEqual true
pc.cosmetics.get.no_helmet mustEqual true
pc.cosmetics.get.beret mustEqual true
pc.cosmetics.get.sunglasses mustEqual true
pc.cosmetics.get.earpiece mustEqual true
pc.cosmetics.get.brimmed_cap mustEqual false
pc.inventory.isDefined mustEqual false
pc.drawn_slot mustEqual DrawnSlot.Pistol1
data match {
case Some(PlayerData(Some(pos), basic, char, None, hand)) =>
pos.coord mustEqual Vector3(4629.8906f, 6316.4453f, 54.734375f)
pos.orient mustEqual Vector3(0, 0, 126.5625f)
pos.vel.isDefined mustEqual false
basic.app.name mustEqual "Angello"
basic.app.faction mustEqual PlanetSideEmpire.VS
basic.app.sex mustEqual CharacterGender.Male
basic.app.head mustEqual 10
basic.app.voice mustEqual CharacterVoice.Voice2
basic.voice2 mustEqual 0
basic.black_ops mustEqual false
basic.jammered mustEqual false
basic.exosuit mustEqual ExoSuitType.MAX
basic.outfit_name mustEqual "Original District"
basic.outfit_logo mustEqual 23
basic.facingPitch mustEqual 0
basic.facingYawUpper mustEqual 180.0f
basic.lfs mustEqual false
basic.grenade_state mustEqual GrenadeState.None
basic.is_cloaking mustEqual false
basic.charging_pose mustEqual false
basic.on_zipline mustEqual false
basic.ribbons.upper mustEqual MeritCommendation.Jacking2
basic.ribbons.middle mustEqual MeritCommendation.ScavengerVS1
basic.ribbons.lower mustEqual MeritCommendation.AMSSupport4
basic.ribbons.tos mustEqual MeritCommendation.SixYearVS
char.health mustEqual 0
char.armor mustEqual 0
char.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
char.command_rank mustEqual 2
char.implant_effects.isDefined mustEqual false
char.cosmetics.isDefined mustEqual true
char.cosmetics.get.no_helmet mustEqual true
char.cosmetics.get.beret mustEqual true
char.cosmetics.get.sunglasses mustEqual true
char.cosmetics.get.earpiece mustEqual true
char.cosmetics.get.brimmed_cap mustEqual false
hand mustEqual DrawnSlot.Pistol1
case _ =>
ko
}
case _ =>
ko
}
}
"encode" in {
val obj = CharacterData(
CharacterAppearanceData(
PlacementData(
Vector3(3674.8438f, 2726.789f, 91.15625f),
Vector3(0f, 0f, 64.6875f),
Some(Vector3(1.4375f, -0.4375f, 0f))
),
BasicCharacterData(
"ScrawnyRonnie",
PlanetSideEmpire.TR,
CharacterGender.Male,
5,
5
),
3,
false,
false,
ExoSuitType.Reinforced,
"Black Beret Armoured Corps",
23,
false,
340.3125f, 0f,
false,
GrenadeState.None,
false, false, false,
RibbonBars(
MeritCommendation.MarkovVeteran,
MeritCommendation.HeavyInfantry4,
MeritCommendation.TankBuster7,
MeritCommendation.SixYearTR
)
val pos : PlacementData = PlacementData(
Vector3(3674.8438f, 2726.789f, 91.15625f),
Vector3(0f, 0f, 64.6875f),
Some(Vector3(1.4375f, -0.4375f, 0f))
)
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
BasicCharacterData(
"ScrawnyRonnie",
PlanetSideEmpire.TR,
CharacterGender.Male,
5,
CharacterVoice.Voice5
),
3,
false,
false,
ExoSuitType.Reinforced,
"Black Beret Armoured Corps",
23,
false,
340.3125f, 0f,
false,
GrenadeState.None,
false, false, false,
RibbonBars(
MeritCommendation.MarkovVeteran,
MeritCommendation.HeavyInfantry4,
MeritCommendation.TankBuster7,
MeritCommendation.SixYearTR
)
)
val char : (Boolean,Boolean)=>CharacterData = CharacterData(
255, 253,
UniformStyle.ThirdUpgrade,
5,
Some(ImplantEffects.NoEffects),
Some(Cosmetics(true, true, true, true, false)),
InventoryData(
InventoryItemData(ObjectClass.plasma_grenade, PlanetSideGUID(3662), 0, WeaponData(0, 0, ObjectClass.plasma_grenade_ammo, PlanetSideGUID(3751), 0, AmmoBoxData())) ::
InventoryItemData(ObjectClass.bank, PlanetSideGUID(3908), 1, WeaponData(0, 0, 1, ObjectClass.armor_canister, PlanetSideGUID(4143), 0, AmmoBoxData())) ::
InventoryItemData(ObjectClass.mini_chaingun, PlanetSideGUID(4164), 2, WeaponData(0, 0, ObjectClass.bullet_9mm, PlanetSideGUID(3728), 0, AmmoBoxData())) ::
InventoryItemData(ObjectClass.phoenix, PlanetSideGUID(3603), 3, WeaponData(0, 0, ObjectClass.phoenix_missile, PlanetSideGUID(3056), 0, AmmoBoxData())) ::
InventoryItemData(ObjectClass.chainblade, PlanetSideGUID(4088), 4, WeaponData(0, 0, 1, ObjectClass.melee_ammo, PlanetSideGUID(3279), 0, AmmoBoxData())) ::
Nil
),
DrawnSlot.Rifle1
Some(Cosmetics(true, true, true, true, false))
)
val inv = InventoryData(
InventoryItemData(ObjectClass.plasma_grenade, PlanetSideGUID(3662), 0, WeaponData(0, 0, ObjectClass.plasma_grenade_ammo, PlanetSideGUID(3751), 0, AmmoBoxData())) ::
InventoryItemData(ObjectClass.bank, PlanetSideGUID(3908), 1, WeaponData(0, 0, 1, ObjectClass.armor_canister, PlanetSideGUID(4143), 0, AmmoBoxData())) ::
InventoryItemData(ObjectClass.mini_chaingun, PlanetSideGUID(4164), 2, WeaponData(0, 0, ObjectClass.bullet_9mm, PlanetSideGUID(3728), 0, AmmoBoxData())) ::
InventoryItemData(ObjectClass.phoenix, PlanetSideGUID(3603), 3, WeaponData(0, 0, ObjectClass.phoenix_missile, PlanetSideGUID(3056), 0, AmmoBoxData())) ::
InventoryItemData(ObjectClass.chainblade, PlanetSideGUID(4088), 4, WeaponData(0, 0, 1, ObjectClass.melee_ammo, PlanetSideGUID(3279), 0, AmmoBoxData())) ::
Nil
)
val obj = PlayerData(pos, app, char, inv, DrawnSlot.Rifle1)
val msg = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(3902), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
val pkt_bitv = pkt.toBitVector
val ori_bitv = string_character.toBitVector
val ori_bitv = string.toBitVector
pkt_bitv.take(452) mustEqual ori_bitv.take(452) //skip 126
pkt_bitv.drop(578).take(438) mustEqual ori_bitv.drop(578).take(438) //skip 2
pkt_bitv.drop(1018).take(17) mustEqual ori_bitv.drop(1018).take(17) //skip 11
@ -229,47 +275,99 @@ class CharacterDataTest extends Specification {
//TODO work on CharacterData to make this pass as a single stream
}
"encode (backpack)" in {
val obj = CharacterData(
CharacterAppearanceData(
PlacementData(4629.8906f, 6316.4453f, 54.734375f, 0f, 0f, 126.5625f),
BasicCharacterData(
"Angello",
PlanetSideEmpire.VS,
CharacterGender.Male,
10,
2
),
0,
false,
false,
ExoSuitType.MAX,
"Original District",
23,
true, //backpack
0f, 180.0f,
false,
GrenadeState.None,
false, false, false,
RibbonBars(
MeritCommendation.Jacking2,
MeritCommendation.ScavengerVS1,
MeritCommendation.AMSSupport4,
MeritCommendation.SixYearVS
)
"encode (seated)" in {
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
BasicCharacterData(
"ScrawnyRonnie",
PlanetSideEmpire.TR,
CharacterGender.Male,
5,
CharacterVoice.Voice5
),
3,
false,
false,
ExoSuitType.Reinforced,
"Black Beret Armoured Corps",
23,
false,
340.3125f, 0f,
false,
GrenadeState.None,
false, false, false,
RibbonBars(
MeritCommendation.MarkovVeteran,
MeritCommendation.HeavyInfantry4,
MeritCommendation.TankBuster7,
MeritCommendation.SixYearTR
)
)
val char : (Boolean,Boolean)=>CharacterData = CharacterData(
255, 253,
UniformStyle.ThirdUpgrade,
5,
Some(ImplantEffects.NoEffects),
Some(Cosmetics(true, true, true, true, false))
)
val inv = InventoryData(
InventoryItemData(ObjectClass.plasma_grenade, PlanetSideGUID(3662), 0, WeaponData(0, 0, ObjectClass.plasma_grenade_ammo, PlanetSideGUID(3751), 0, AmmoBoxData())) ::
InventoryItemData(ObjectClass.bank, PlanetSideGUID(3908), 1, WeaponData(0, 0, 1, ObjectClass.armor_canister, PlanetSideGUID(4143), 0, AmmoBoxData())) ::
InventoryItemData(ObjectClass.mini_chaingun, PlanetSideGUID(4164), 2, WeaponData(0, 0, ObjectClass.bullet_9mm, PlanetSideGUID(3728), 0, AmmoBoxData())) ::
InventoryItemData(ObjectClass.phoenix, PlanetSideGUID(3603), 3, WeaponData(0, 0, ObjectClass.phoenix_missile, PlanetSideGUID(3056), 0, AmmoBoxData())) ::
InventoryItemData(ObjectClass.chainblade, PlanetSideGUID(4088), 4, WeaponData(0, 0, 1, ObjectClass.melee_ammo, PlanetSideGUID(3279), 0, AmmoBoxData())) ::
Nil
)
val obj = PlayerData(app, char, inv, DrawnSlot.Rifle1)
val msg = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(3902), ObjectCreateMessageParent(PlanetSideGUID(1234), 0), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_seated
}
"encode (backpack)" in {
val pos = PlacementData(
Vector3(4629.8906f, 6316.4453f, 54.734375f),
Vector3(0, 0, 126.5625f)
)
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
BasicCharacterData(
"Angello",
PlanetSideEmpire.VS,
CharacterGender.Male,
10,
CharacterVoice.Voice2
),
0,
false,
false,
ExoSuitType.MAX,
"Original District",
23,
true, //backpack
0f, 180.0f,
false,
GrenadeState.None,
false, false, false,
RibbonBars(
MeritCommendation.Jacking2,
MeritCommendation.ScavengerVS1,
MeritCommendation.AMSSupport4,
MeritCommendation.SixYearVS
)
)
val char : (Boolean,Boolean)=>CharacterData = CharacterData(
0, 0,
UniformStyle.ThirdUpgrade,
2,
None,
Some(Cosmetics(true, true, true, true, false)),
None,
DrawnSlot.Pistol1
Some(Cosmetics(true, true, true, true, false))
)
val obj = PlayerData(pos, app, char, DrawnSlot.Pistol1)
val msg = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(3380), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
val pkt_bitv = pkt.toBitVector
val ori_bitv = string_character_backpack.toBitVector
val ori_bitv = string_backpack.toBitVector
pkt_bitv.take(300) mustEqual ori_bitv.take(300) //skip 2
pkt_bitv.drop(302).take(14) mustEqual ori_bitv.drop(302).take(14) //skip 126
pkt_bitv.drop(442).take(305) mustEqual ori_bitv.drop(442).take(305) //skip 1

File diff suppressed because one or more lines are too long

View file

@ -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._

View file

@ -0,0 +1,192 @@
// Copyright (c) 2017 PSForever
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._
class MountedVehiclesTest extends Specification {
val string_mosquito_seated =
hex"17c70700009e2d410d8ed818f1a4017047f7ffbc6390ffbe01801cff00003c08791801d00000002340530063007200610077006e00790052" ++
hex"006f006e006e0069006500020b7e67b540404001000000000022b50100268042006c00610063006b00200042006500720065007400200041" ++
hex"0072006d006f007500720065006400200043006f00720070007300170040030050040003bc00000234040001a00400027a7a0809a6910800" ++
hex"00000008090a6403603000001082202e040000000202378ae0e80c00000162710b82000000008083837032030000015e2583210000000020" ++
hex"20e21c0c80c000007722120e81c0000000808063483603000000"
"decode (Scrawny Ronnie's mosquito)" in {
PacketCoding.DecodePacket(string_mosquito_seated).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 1991
cls mustEqual ObjectClass.mosquito
guid mustEqual PlanetSideGUID(4308)
parent mustEqual None
data match {
case Some(vdata : VehicleData) =>
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.no_mount_points mustEqual false
vdata.driveState mustEqual DriveState.Mobile
vdata.cloak mustEqual false
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
list.head.guid mustEqual PlanetSideGUID(3776)
list.head.parentSlot mustEqual 0
list.head.obj match {
case PlayerData(pos, app, char, Some(InventoryData(inv)), hand) =>
pos mustEqual None
app.app.name mustEqual "ScrawnyRonnie"
app.app.faction mustEqual PlanetSideEmpire.TR
app.app.sex mustEqual CharacterGender.Male
app.app.head mustEqual 5
app.app.voice mustEqual CharacterVoice.Voice5
app.voice2 mustEqual 3
app.black_ops mustEqual false
app.lfs mustEqual false
app.outfit_name mustEqual "Black Beret Armoured Corps"
app.outfit_logo mustEqual 23
app.facingPitch mustEqual 354.375f
app.facingYawUpper mustEqual 0.0f
app.altModelBit mustEqual None
app.charging_pose mustEqual false
app.on_zipline mustEqual false
app.backpack mustEqual false
app.ribbons.upper mustEqual MeritCommendation.MarkovVeteran
app.ribbons.middle mustEqual MeritCommendation.HeavyInfantry4
app.ribbons.lower mustEqual MeritCommendation.TankBuster7
app.ribbons.tos mustEqual MeritCommendation.SixYearTR
char.health mustEqual 100
char.armor mustEqual 0
char.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
char.command_rank mustEqual 5
char.implant_effects mustEqual None
char.cosmetics mustEqual Some(Cosmetics(true, true, true, true, false))
inv.size mustEqual 4
inv.head.objectClass mustEqual ObjectClass.medicalapplicator
inv.head.parentSlot mustEqual 0
inv(1).objectClass mustEqual ObjectClass.bank
inv(1).parentSlot mustEqual 1
inv(2).objectClass mustEqual ObjectClass.mini_chaingun
inv(2).parentSlot mustEqual 2
inv(3).objectClass mustEqual ObjectClass.chainblade
inv(3).parentSlot mustEqual 4
hand mustEqual DrawnSlot.None
case _ =>
ko
}
list(1).objectClass mustEqual ObjectClass.rotarychaingun_mosquito
list(1).parentSlot mustEqual 1
case None =>
ko
}
case _ =>
ko
}
case _ =>
ko
}
}
"encode (Scrawny Ronnie's mosquito)" in {
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
BasicCharacterData("ScrawnyRonnie", PlanetSideEmpire.TR, CharacterGender.Male, 5, CharacterVoice.Voice5),
3,
false, false,
ExoSuitType.Agile,
"Black Beret Armoured Corps",
23,
false,
354.375f, 0.0f,
false,
GrenadeState.None, false, false, false,
RibbonBars(
MeritCommendation.MarkovVeteran,
MeritCommendation.HeavyInfantry4,
MeritCommendation.TankBuster7,
MeritCommendation.SixYearTR
)
)
val char : (Boolean,Boolean)=>CharacterData = CharacterData(
100, 0,
UniformStyle.ThirdUpgrade,
0,
5,
None,
Some(Cosmetics(true, true, true, true, false))
)
val inv : InventoryData = InventoryData(
List(
InternalSlot(ObjectClass.medicalapplicator, PlanetSideGUID(4201), 0,
WeaponData(0, 0, 0, List(InternalSlot(ObjectClass.health_canister, PlanetSideGUID(3472), 0, AmmoBoxData(0))))
),
InternalSlot(ObjectClass.bank, PlanetSideGUID(2952), 1,
WeaponData(0, 0, 0, List(InternalSlot(ObjectClass.armor_canister, PlanetSideGUID(3758), 0, AmmoBoxData(0))))
),
InternalSlot(ObjectClass.mini_chaingun, PlanetSideGUID(2929), 2,
WeaponData(0, 0, 0, List(InternalSlot(ObjectClass.bullet_9mm, PlanetSideGUID(3292), 0, AmmoBoxData(0))))
),
InternalSlot(ObjectClass.chainblade, PlanetSideGUID(3222), 4,
WeaponData(0, 0, 0, List(InternalSlot(ObjectClass.melee_ammo, PlanetSideGUID(3100), 0, AmmoBoxData(0))))
)
)
)
val player = VehicleData.PlayerData(app, char, inv, DrawnSlot.None, VehicleData.InitialStreamLengthToSeatEntries(true, VehicleFormat.Variant))
val obj = VehicleData(
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, false,
PlanetSideGUID(3776),
false,
255,
false, false,
DriveState.Mobile,
false, false, false,
Some(VariantVehicleData(7)),
Some(
InventoryData(
List(
InternalSlot(ObjectClass.avatar, PlanetSideGUID(3776), 0, player),
InternalSlot(ObjectClass.rotarychaingun_mosquito, PlanetSideGUID(3602), 1,
WeaponData(6, 0, 0, List(InternalSlot(ObjectClass.bullet_12mm, PlanetSideGUID(3538), 0, AmmoBoxData(0))))
)
)
)
)
)(VehicleFormat.Variant)
val msg = ObjectCreateMessage(ObjectClass.mosquito, PlanetSideGUID(4308), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
val pkt_bitv = pkt.toBitVector
val ori_bitv = string_mosquito_seated.toBitVector
pkt_bitv.take(555) mustEqual ori_bitv.take(555) //skip 126
pkt_bitv.drop(681).take(512) mustEqual ori_bitv.drop(681).take(512) //renew
pkt_bitv.drop(1193).take(88) mustEqual ori_bitv.drop(1193).take(88) //skip 3
pkt_bitv.drop(1284).take(512) mustEqual ori_bitv.drop(1284).take(512) //renew
pkt_bitv.drop(1796) mustEqual ori_bitv.drop(1796)
//TODO work on CharacterData to make this pass as a single stream
}
}

View file

@ -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,

View file

@ -11,6 +11,8 @@ 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"
"Utility vehicles" should {
"decode (ant)" in {
@ -23,17 +25,21 @@ class UtilityVehiclesTest extends Specification {
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.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
}
@ -49,19 +55,23 @@ class UtilityVehiclesTest extends Specification {
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.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
@ -88,11 +98,13 @@ class UtilityVehiclesTest extends Specification {
"encode (ant)" 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,
@ -108,12 +120,13 @@ class UtilityVehiclesTest extends Specification {
"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,

View file

@ -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,

View file

@ -7,7 +7,7 @@ import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawn
import net.psforever.objects.{Avatar, GlobalDefinitions, Player, Vehicle}
import net.psforever.objects.serverobject.pad.process.{AutoDriveControls, VehicleSpawnControlGuided}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, Vector3}
import org.specs2.mutable.Specification
import scala.concurrent.duration._
@ -389,7 +389,7 @@ class GuidedControlTest1 extends ActorTest {
"unguided" in {
val vehicle = Vehicle(GlobalDefinitions.mediumtransport)
vehicle.GUID = PlanetSideGUID(1)
val driver = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0,0))
val driver = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
driver.VehicleSeated = vehicle.GUID
val sendTo = TestProbe()
val order = VehicleSpawnControl.Order(driver, vehicle, sendTo.ref)
@ -411,7 +411,7 @@ class GuidedControlTest2 extends ActorTest {
val vehicle = Vehicle(GlobalDefinitions.mediumtransport)
vehicle.GUID = PlanetSideGUID(1)
vehicle.Velocity = Vector3(1,1,1)
val driver = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0,0))
val driver = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
driver.VehicleSeated = vehicle.GUID
val sendTo = TestProbe()
val order = VehicleSpawnControl.Order(driver, vehicle, sendTo.ref)
@ -436,7 +436,7 @@ class GuidedControlTest3 extends ActorTest {
val vehicle = Vehicle(GlobalDefinitions.mediumtransport)
vehicle.GUID = PlanetSideGUID(1)
vehicle.Velocity = Vector3(1,1,1)
val driver = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0,0))
val driver = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
driver.VehicleSeated = vehicle.GUID
val sendTo = TestProbe()
val order = VehicleSpawnControl.Order(driver, vehicle, sendTo.ref)
@ -457,7 +457,7 @@ class GuidedControlTest3 extends ActorTest {
assert(msg2.isInstanceOf[VehicleSpawnControlGuided.GuidedControl])
assert(msg2.asInstanceOf[VehicleSpawnControlGuided.GuidedControl].command == AutoDriveControls.State.Wait)
sendTo.expectNoMsg(1000 milliseconds)
val msg3 = sendTo.receiveOne(100 milliseconds)
val msg3 = sendTo.receiveOne(300 milliseconds)
assert(msg3.isInstanceOf[VehicleSpawnControlGuided.GuidedControl])
assert(msg3.asInstanceOf[VehicleSpawnControlGuided.GuidedControl].command == AutoDriveControls.State.Drive)
val msg4 = sendTo.receiveOne(200 milliseconds)
@ -474,7 +474,7 @@ class GuidedControlTest4 extends ActorTest {
val vehicle = Vehicle(GlobalDefinitions.mediumtransport)
vehicle.GUID = PlanetSideGUID(1)
vehicle.Velocity = Vector3(1,1,1)
val driver = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0,0))
val driver = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
driver.VehicleSeated = vehicle.GUID
val sendTo = TestProbe()
val order = VehicleSpawnControl.Order(driver, vehicle, sendTo.ref)

View file

@ -5,12 +5,12 @@ import net.psforever.objects.GlobalDefinitions._
import net.psforever.objects._
import net.psforever.objects.loadouts._
import net.psforever.objects.definition.ImplantDefinition
import net.psforever.types.{CharacterGender, ImplantType, PlanetSideEmpire}
import net.psforever.types.{CharacterGender, CharacterVoice, ImplantType, PlanetSideEmpire}
import org.specs2.mutable._
class AvatarTest extends Specification {
def CreatePlayer() : (Player, Avatar) = {
val avatar = Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1)
val avatar = Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 41, CharacterVoice.Voice1)
val
player = Player(avatar)
player.Slot(0).Equipment = Tool(beamer)
@ -26,12 +26,12 @@ class AvatarTest extends Specification {
}
"construct" in {
val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
av.name mustEqual "Chord"
av.faction mustEqual PlanetSideEmpire.TR
av.sex mustEqual CharacterGender.Male
av.head mustEqual 0
av.voice mustEqual 5
av.voice mustEqual CharacterVoice.Voice5
av.BEP mustEqual 0
av.CEP mustEqual 0
av.Certifications mustEqual Set.empty
@ -39,7 +39,7 @@ class AvatarTest extends Specification {
}
"can maintain cumulative battle experience point values" in {
val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
av.BEP mustEqual 0
av.BEP = 100
av.BEP mustEqual 100
@ -48,14 +48,14 @@ class AvatarTest extends Specification {
}
"can maintain battle experience point values up to a maximum (Long.MaxValue)" in {
val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
av.BEP mustEqual 0
av.BEP = 4294967295L
av.BEP mustEqual 4294967295L
}
"can not maintain battle experience point values below zero" in {
val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
av.BEP mustEqual 0
av.BEP = -1
av.BEP mustEqual 0
@ -66,7 +66,7 @@ class AvatarTest extends Specification {
}
"can maintain cumulative command experience point values" in {
val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
av.CEP mustEqual 0
av.CEP = 100
av.CEP mustEqual 100
@ -75,14 +75,14 @@ class AvatarTest extends Specification {
}
"can maintain command experience point values up to a maximum (Long.MaxValue)" in {
val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
av.CEP mustEqual 0
av.CEP = 4294967295L
av.CEP mustEqual 4294967295L
}
"can not maintain command experience point values below zero" in {
val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val av = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
av.CEP mustEqual 0
av.CEP = -1
av.CEP mustEqual 0
@ -93,28 +93,28 @@ class AvatarTest extends Specification {
}
"can tell the difference between avatars" in {
(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) ==
Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual true
(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) ==
Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) mustEqual true
(Avatar("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) ==
Avatar("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual false
(Avatar("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) ==
Avatar("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) mustEqual false
(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) ==
Avatar("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, 5)) mustEqual false
(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) ==
Avatar("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, CharacterVoice.Voice5)) mustEqual false
(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) ==
Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Female, 0, 5)) mustEqual false
(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) ==
Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Female, 0, CharacterVoice.Voice5)) mustEqual false
(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) ==
Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 1, 5)) mustEqual false
(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) ==
Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 1, CharacterVoice.Voice5)) mustEqual false
(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) ==
Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 6)) mustEqual false
(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) ==
Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice4)) mustEqual false
}
//refer to ImplantTest.scala for more tests
"maximum of three implant slots" in {
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Implants.length mustEqual 3
obj.Implants(0).Unlocked mustEqual false
obj.Implants(0).Initialized mustEqual false
@ -140,7 +140,7 @@ class AvatarTest extends Specification {
"can install an implant" in {
val testplant : ImplantDefinition = ImplantDefinition(1)
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Implants(0).Unlocked = true
obj.InstallImplant(testplant) mustEqual Some(0)
obj.Implants.find({p => p.Implant == ImplantType(1)}) match { //find the installed implant
@ -155,7 +155,7 @@ class AvatarTest extends Specification {
"can install implants in sequential slots" in {
val testplant1 : ImplantDefinition = ImplantDefinition(1)
val testplant2 : ImplantDefinition = ImplantDefinition(2)
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Implants(0).Unlocked = true
obj.Implants(1).Unlocked = true
@ -166,7 +166,7 @@ class AvatarTest extends Specification {
"can not install the same type of implant twice" in {
val testplant1 : ImplantDefinition = ImplantDefinition(1)
val testplant2 : ImplantDefinition = ImplantDefinition(1)
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Implants(0).Unlocked = true
obj.Implants(1).Unlocked = true
@ -178,7 +178,7 @@ class AvatarTest extends Specification {
val testplant1 : ImplantDefinition = ImplantDefinition(1)
val testplant2 : ImplantDefinition = ImplantDefinition(2)
val testplant3 : ImplantDefinition = ImplantDefinition(3)
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Implants(0).Unlocked = true
obj.Implants(1).Unlocked = true
@ -192,7 +192,7 @@ class AvatarTest extends Specification {
val testplant2 : ImplantDefinition = ImplantDefinition(2)
val testplant3 : ImplantDefinition = ImplantDefinition(3)
val testplant4 : ImplantDefinition = ImplantDefinition(4)
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Implants(0).Unlocked = true
obj.Implants(1).Unlocked = true
obj.Implants(2).Unlocked = true
@ -205,7 +205,7 @@ class AvatarTest extends Specification {
"can uninstall an implant" in {
val testplant : ImplantDefinition = ImplantDefinition(1)
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Implants(0).Unlocked = true
obj.InstallImplant(testplant) mustEqual Some(0)
obj.Implants(0).Installed mustEqual Some(testplant)
@ -218,7 +218,7 @@ class AvatarTest extends Specification {
val testplant1 : ImplantDefinition = ImplantDefinition(1)
val testplant2 : ImplantDefinition = ImplantDefinition(2)
val testplant3 : ImplantDefinition = ImplantDefinition(3)
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Implants(0).Unlocked = true
obj.Implants(1).Unlocked = true
obj.Implants(2).Unlocked = true
@ -239,7 +239,7 @@ class AvatarTest extends Specification {
val testplant1 : ImplantDefinition = ImplantDefinition(1)
val testplant2 : ImplantDefinition = ImplantDefinition(2)
val testplant3 : ImplantDefinition = ImplantDefinition(3)
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Implants(0).Unlocked = true
obj.Implants(1).Unlocked = true
obj.Implants(2).Unlocked = true
@ -261,7 +261,7 @@ class AvatarTest extends Specification {
"can reset implants to uninitialized state" in {
val testplant1 : ImplantDefinition = ImplantDefinition(1)
val testplant2 : ImplantDefinition = ImplantDefinition(2)
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Implants(0).Unlocked = true
obj.Implants(1).Unlocked = true
obj.InstallImplant(testplant1) mustEqual Some(0)
@ -393,6 +393,6 @@ class AvatarTest extends Specification {
}
"toString" in {
Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5).toString mustEqual "TR Chord"
Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5).toString mustEqual "TR Chord"
}
}

View file

@ -11,7 +11,7 @@ import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate._
import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, Vector3}
import org.specs2.mutable.Specification
import scala.util.{Failure, Success}
@ -154,7 +154,7 @@ class ConverterTest extends Specification {
}
"Player" should {
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val obj : Player = {
/*
Create an AmmoBoxDefinition with which to build two AmmoBoxes

View file

@ -7,13 +7,13 @@ import net.psforever.objects.serverobject.doors.{Door, DoorControl}
import net.psforever.objects.serverobject.structures.{Building, StructureType}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{PlanetSideGUID, UseItemMessage}
import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, Vector3}
import org.specs2.mutable.Specification
import scala.concurrent.duration.Duration
class DoorTest extends Specification {
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
"Door" should {
"construct" in {
@ -123,6 +123,6 @@ object DoorControlTest {
door.Actor = system.actorOf(Props(classOf[DoorControl], door), "door")
door.Owner = new Building(0, Zone.Nowhere, StructureType.Building)
door.Owner.Faction = faction
(Player(Avatar("test", faction, CharacterGender.Male, 0, 0)), door)
(Player(Avatar("test", faction, CharacterGender.Male, 0, CharacterVoice.Mute)), door)
}
}

View file

@ -8,7 +8,7 @@ import net.psforever.objects.serverobject.locks.{IFFLock, IFFLockControl}
import net.psforever.objects.serverobject.structures.{Building, StructureType}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.{CharacterGender, PlanetSideEmpire}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire}
import org.specs2.mutable.Specification
class IFFLockTest extends Specification {
@ -69,6 +69,6 @@ object IFFLockControlTest {
lock.Actor = system.actorOf(Props(classOf[IFFLockControl], lock), "lock-control")
lock.Owner = new Building(0, Zone.Nowhere, StructureType.Building)
lock.Owner.Faction = faction
(Player(Avatar("test", faction, CharacterGender.Male, 0, 0)), lock)
(Player(Avatar("test", faction, CharacterGender.Male, 0, CharacterVoice.Mute)), lock)
}
}

View file

@ -3,12 +3,12 @@ package objects
import net.psforever.objects._
import net.psforever.objects.loadouts._
import net.psforever.types.{CharacterGender, ExoSuitType, PlanetSideEmpire}
import net.psforever.types.{CharacterGender, CharacterVoice, ExoSuitType, PlanetSideEmpire}
import net.psforever.objects.GlobalDefinitions._
import org.specs2.mutable._
class LoadoutTest extends Specification {
val avatar = Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1)
val avatar = Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 41, CharacterVoice.Voice1)
def CreatePlayer() : Player = {
new Player(avatar) {

View file

@ -8,7 +8,7 @@ import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.vehicles.Seat
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.{CharacterGender, PlanetSideEmpire}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire}
import scala.concurrent.duration.Duration
@ -25,7 +25,7 @@ class MountableControl1Test extends ActorTest() {
class MountableControl2Test extends ActorTest() {
"MountableControl" should {
"let a player mount" in {
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val obj = new MountableTest.MountableTestObject
obj.Actor = system.actorOf(Props(classOf[MountableTest.MountableTestControl], obj), "mountable")
val msg = Mountable.TryMount(player, 0)
@ -46,8 +46,8 @@ class MountableControl2Test extends ActorTest() {
class MountableControl3Test extends ActorTest() {
"MountableControl" should {
"block a player from mounting" in {
val player1 = Player(Avatar("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player2 = Player(Avatar("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player1 = Player(Avatar("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val player2 = Player(Avatar("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val obj = new MountableTest.MountableTestObject
obj.Actor = system.actorOf(Props(classOf[MountableTest.MountableTestControl], obj), "mountable")
obj.Actor ! Mountable.TryMount(player1, 0)

View file

@ -6,19 +6,19 @@ import net.psforever.objects._
import net.psforever.objects.definition.{ImplantDefinition, SimpleItemDefinition}
import net.psforever.objects.equipment.EquipmentSize
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.{CharacterGender, ExoSuitType, ImplantType, PlanetSideEmpire}
import net.psforever.types._
import org.specs2.mutable._
import scala.util.Success
class PlayerTest extends Specification {
def TestPlayer(name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, head : Int, voice : Int) : Player = {
def TestPlayer(name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, head : Int, voice : CharacterVoice.Value) : Player = {
new Player(Avatar(name, faction, sex, head, voice))
}
"Player" should {
"construct" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.isAlive mustEqual false
obj.FacingYawUpper mustEqual 0
obj.Jumping mustEqual false
@ -36,27 +36,27 @@ class PlayerTest extends Specification {
}
"different players" in {
(TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) ==
TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual true
(TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) ==
TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) mustEqual true
(TestPlayer("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) ==
TestPlayer("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual false
(TestPlayer("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) ==
TestPlayer("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) mustEqual false
(TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) ==
TestPlayer("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, 5)) mustEqual false
(TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) ==
TestPlayer("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, CharacterVoice.Voice5)) mustEqual false
(TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) ==
TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Female, 0, 5)) mustEqual false
(TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) ==
TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Female, 0, CharacterVoice.Voice5)) mustEqual false
(TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) ==
TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 1, 5)) mustEqual false
(TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) ==
TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 1, CharacterVoice.Voice5)) mustEqual false
(TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) ==
TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 6)) mustEqual false
(TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) ==
TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice4)) mustEqual false
}
"(re)spawn" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.isAlive mustEqual false
obj.Health mustEqual 0
obj.Stamina mustEqual 0
@ -72,7 +72,7 @@ class PlayerTest extends Specification {
}
"will not (re)spawn if not dead" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Spawn
obj.Health mustEqual 100
obj.Armor mustEqual 50
@ -88,7 +88,7 @@ class PlayerTest extends Specification {
}
"can die" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Spawn
obj.Armor = 35 //50 -> 35
obj.isAlive mustEqual true
@ -103,7 +103,7 @@ class PlayerTest extends Specification {
}
"can not become a backpack if alive" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Spawn
obj.isAlive mustEqual true
obj.isBackpack mustEqual false
@ -113,7 +113,7 @@ class PlayerTest extends Specification {
}
"can become a backpack" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.isAlive mustEqual false
obj.isBackpack mustEqual false
obj.Release
@ -122,7 +122,7 @@ class PlayerTest extends Specification {
}
"set new maximum values (health, stamina)" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.MaxHealth mustEqual 100
obj.MaxStamina mustEqual 100
obj.MaxHealth = 123
@ -133,7 +133,7 @@ class PlayerTest extends Specification {
}
"set new values (health, armor, stamina) but only when alive" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Health = 23
obj.Armor = 34
obj.Stamina = 45
@ -154,7 +154,7 @@ class PlayerTest extends Specification {
}
"has visible slots" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.VisibleSlots mustEqual Set(0,2,4) //Standard
obj.ExoSuit = ExoSuitType.Agile
obj.VisibleSlots mustEqual Set(0,1,2,4)
@ -167,7 +167,7 @@ class PlayerTest extends Specification {
}
"init (Standard Exo-Suit)" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.ExoSuit mustEqual ExoSuitType.Standard
obj.Slot(0).Size mustEqual EquipmentSize.Pistol
obj.Slot(1).Size mustEqual EquipmentSize.Blocked
@ -181,7 +181,7 @@ class PlayerTest extends Specification {
"draw equipped holsters only" in {
val wep = SimpleItem(SimpleItemDefinition(149))
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Slot(1).Size = EquipmentSize.Pistol
obj.Slot(1).Equipment = wep
obj.DrawnSlot mustEqual Player.HandsDownSlot
@ -194,7 +194,7 @@ class PlayerTest extends Specification {
"remember the last drawn holster" in {
val wep1 = SimpleItem(SimpleItemDefinition(149))
val wep2 = SimpleItem(SimpleItemDefinition(149))
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Slot(0).Size = EquipmentSize.Pistol
obj.Slot(0).Equipment = wep1
obj.Slot(1).Size = EquipmentSize.Pistol
@ -233,7 +233,7 @@ class PlayerTest extends Specification {
"hold something in their free hand" in {
val wep = SimpleItem(SimpleItemDefinition(149))
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Slot(Player.FreeHandSlot).Equipment = wep
obj.Slot(Player.FreeHandSlot).Equipment.get.Definition.ObjectId mustEqual 149
@ -241,14 +241,14 @@ class PlayerTest extends Specification {
"provide an invalid hand that can not hold anything" in {
val wep = SimpleItem(SimpleItemDefinition(149))
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Slot(-1).Equipment = wep
obj.Slot(-1).Equipment mustEqual None
}
"search for the smallest available slot in which to store equipment" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Inventory.Resize(3,3) //fits one item
obj.Fit(Tool(GlobalDefinitions.beamer)) mustEqual Some(0)
@ -266,7 +266,7 @@ class PlayerTest extends Specification {
}
"can use their free hand to hold things" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val ammo = AmmoBox(GlobalDefinitions.bullet_9mm)
obj.FreeHand.Equipment mustEqual None
@ -275,12 +275,12 @@ class PlayerTest extends Specification {
}
"can access the player's locker-space" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Slot(5).Equipment.get.isInstanceOf[LockerContainer] mustEqual true
}
"can find equipment" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Slot(0).Equipment = {
val item = Tool(beamer)
item.GUID = PlanetSideGUID(1)
@ -316,7 +316,7 @@ class PlayerTest extends Specification {
}
"does equipment collision checking (are we already holding something there?)" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val item1 = Tool(beamer)
val item2 = Kit(medkit)
val item3 = AmmoBox(GlobalDefinitions.bullet_9mm)
@ -356,7 +356,7 @@ class PlayerTest extends Specification {
}
"battle experience point values of the avatar" in {
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
player.BEP mustEqual avatar.BEP
@ -365,7 +365,7 @@ class PlayerTest extends Specification {
}
"command experience point values of the avatar" in {
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
player.CEP mustEqual avatar.CEP
@ -374,14 +374,14 @@ class PlayerTest extends Specification {
}
"can get a quick summary of implant slots (default)" in {
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
player.Implants mustEqual Array.empty
}
"can get a quick summary of implant slots (two unlocked, one installed)" in {
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
val temp = new ImplantDefinition(1)
avatar.Implants(0).Unlocked = true
@ -404,7 +404,7 @@ class PlayerTest extends Specification {
}
"can get a quick summary of implant slots (all unlocked, first two installed)" in {
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
avatar.Implants(0).Unlocked = true
avatar.InstallImplant(new ImplantDefinition(1))
@ -435,7 +435,7 @@ class PlayerTest extends Specification {
}
"seat in a vehicle" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.VehicleSeated mustEqual None
obj.VehicleSeated = PlanetSideGUID(65)
obj.VehicleSeated mustEqual Some(PlanetSideGUID(65))
@ -444,7 +444,7 @@ class PlayerTest extends Specification {
}
"own in a vehicle" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.VehicleOwned mustEqual None
obj.VehicleOwned = PlanetSideGUID(65)
obj.VehicleOwned mustEqual Some(PlanetSideGUID(65))
@ -453,21 +453,21 @@ class PlayerTest extends Specification {
}
"remember what zone he is in" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.Continent mustEqual "home2"
obj.Continent = "ugd01"
obj.Continent mustEqual "ugd01"
}
"special is typically normal and can not be changed from normal" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal
obj.UsingSpecial = SpecialExoSuitDefinition.Mode.Shielded
obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal
}
"a TR MAX can change its special to Overdrive or Anchored" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.ExoSuit = ExoSuitType.MAX
obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal
obj.UsingSpecial = SpecialExoSuitDefinition.Mode.Anchored
@ -482,7 +482,7 @@ class PlayerTest extends Specification {
}
"an NC MAX can change its special to Shielded" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.ExoSuit = ExoSuitType.MAX
obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal
obj.UsingSpecial = SpecialExoSuitDefinition.Mode.Shielded
@ -492,13 +492,13 @@ class PlayerTest extends Specification {
}
"one faction can not use the other's specials" in {
val objtr = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val objtr = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
objtr.ExoSuit = ExoSuitType.MAX
objtr.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal
objtr.UsingSpecial = SpecialExoSuitDefinition.Mode.Shielded
objtr.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal
val objnc = TestPlayer("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, 5)
val objnc = TestPlayer("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, CharacterVoice.Voice5)
objnc.ExoSuit = ExoSuitType.MAX
objnc.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal
objnc.UsingSpecial = SpecialExoSuitDefinition.Mode.Overdrive
@ -508,7 +508,7 @@ class PlayerTest extends Specification {
}
"changing exo-suit type resets the special to Normal (and changing back does not revert it again)" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.ExoSuit = ExoSuitType.MAX
obj.UsingSpecial mustEqual SpecialExoSuitDefinition.Mode.Normal
obj.UsingSpecial = SpecialExoSuitDefinition.Mode.Anchored
@ -522,7 +522,7 @@ class PlayerTest extends Specification {
}
"toString" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
obj.toString mustEqual "TR Chord 0/100 0/50"
obj.GUID = PlanetSideGUID(455)

View file

@ -9,7 +9,7 @@ import net.psforever.objects.serverobject.structures.StructureType
import net.psforever.objects.{Avatar, GlobalDefinitions, Player, Vehicle}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.{PlanetSideEmpire, Vector3}
import net.psforever.types.{CharacterVoice, PlanetSideEmpire, Vector3}
import org.specs2.mutable.Specification
import scala.concurrent.duration._
@ -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,6 +263,9 @@ class VehicleSpawnControl5Test extends ActorTest() {
val probe3Msg4 = probe3.receiveOne(3 seconds)
assert(probe3Msg4.isInstanceOf[VehicleSpawnPad.DetachFromRails])
val probe3Msg5 = probe3.receiveOne(1 seconds)
assert(probe3Msg5.isInstanceOf[VehicleSpawnPad.RevealPlayer])
val probe1Msg = probe1.receiveOne(12 seconds)
assert(probe1Msg.isInstanceOf[VehicleSpawnPad.PeriodicReminder])
assert(probe1Msg.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])
assert(probe3Msg5.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)
}
}
}
@ -379,11 +340,13 @@ object VehicleSpawnPadControlTest {
pad.Actor = system.actorOf(Props(classOf[VehicleSpawnControl], pad), s"test-pad-${System.nanoTime()}")
pad.Owner = new Building(0, zone, StructureType.Building)
pad.Owner.Faction = faction
val player = Player(Avatar("test", faction, CharacterGender.Male, 0, 0))
val player = Player(Avatar("test", faction, CharacterGender.Male, 0, CharacterVoice.Mute))
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)
}
}

View file

@ -1,13 +1,13 @@
// Copyright (c) 2017 PSForever
package objects
import akka.actor.Props
import akka.actor.{ActorSystem, Props}
import net.psforever.objects._
import net.psforever.objects.definition.{SeatDefinition, VehicleDefinition}
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.vehicles._
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.ExoSuitType
import net.psforever.types.{CharacterVoice, ExoSuitType}
import org.specs2.mutable._
import scala.concurrent.duration.Duration
@ -312,7 +312,7 @@ class VehicleTest extends Specification {
}
}
class VehicleControl1Test extends ActorTest {
class VehicleControlStopMountingTest extends ActorTest {
"Vehicle Control" should {
"deactivate and stop handling mount messages" in {
val player1 = Player(VehicleTest.avatar1)
@ -333,7 +333,7 @@ class VehicleControl1Test extends ActorTest {
}
}
class VehicleControl2Test extends ActorTest {
class VehicleControlRestartMountingTest extends ActorTest {
"Vehicle Control" should {
"reactivate and resume handling mount messages" in {
val player1 = Player(VehicleTest.avatar1)
@ -358,9 +358,261 @@ class VehicleControl2Test extends ActorTest {
}
}
class VehicleControlAlwaysDismountTest extends ActorTest {
"Vehicle Control" should {
"always allow dismount messages" in {
val player1 = Player(VehicleTest.avatar1)
player1.GUID = PlanetSideGUID(1)
val player2 = Player(VehicleTest.avatar2)
player2.GUID = PlanetSideGUID(2)
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
vehicle.GUID = PlanetSideGUID(3)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
vehicle.Actor ! Mountable.TryMount(player1, 0)
receiveOne(Duration.create(100, "ms")) //discard
vehicle.Actor ! Mountable.TryMount(player2, 1)
receiveOne(Duration.create(100, "ms")) //discard
vehicle.Actor ! Mountable.TryDismount(player2, 1) //player2 requests dismount
val reply1 = receiveOne(Duration.create(100, "ms"))
assert(reply1.isInstanceOf[Mountable.MountMessages])
assert(reply1.asInstanceOf[Mountable.MountMessages].response.isInstanceOf[Mountable.CanDismount]) //player2 dismounts
vehicle.Actor ! Vehicle.PrepareForDeletion
vehicle.Actor ! Mountable.TryDismount(player1, 0) //player1 requests dismount
val reply2 = receiveOne(Duration.create(100, "ms"))
assert(reply2.isInstanceOf[Mountable.MountMessages])
assert(reply2.asInstanceOf[Mountable.MountMessages].response.isInstanceOf[Mountable.CanDismount]) //player1 dismounts
}
}
}
class VehicleControlMountingBlockedExosuitTest extends ActorTest {
def checkCanNotMount() : Unit = {
val reply = receiveOne(Duration.create(100, "ms"))
reply match {
case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanNotMount])
case _ =>
assert(false)
}
}
def checkCanMount() : Unit = {
val reply = receiveOne(Duration.create(100, "ms"))
reply match {
case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanMount])
case _ =>
assert(false)
}
}
"Vehicle Control" should {
"block players from sitting if their exo-suit is not allowed by the seat" in {
val vehicle = Vehicle(GlobalDefinitions.apc_tr)
vehicle.GUID = PlanetSideGUID(10)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
val player1 = Player(VehicleTest.avatar1)
player1.ExoSuit = ExoSuitType.Reinforced
player1.GUID = PlanetSideGUID(1)
val player2 = Player(VehicleTest.avatar1)
player2.ExoSuit = ExoSuitType.MAX
player2.GUID = PlanetSideGUID(2)
val player3 = Player(VehicleTest.avatar1)
player3.ExoSuit = ExoSuitType.Agile
player3.GUID = PlanetSideGUID(3)
//disallow
vehicle.Actor ! Mountable.TryMount(player1, 0) //Reinforced in non-MAX seat
checkCanNotMount()
vehicle.Actor ! Mountable.TryMount(player2, 0) //MAX in non-Reinforced seat
checkCanNotMount()
vehicle.Actor ! Mountable.TryMount(player2, 1) //MAX in non-MAX seat
checkCanNotMount()
vehicle.Actor ! Mountable.TryMount(player1, 9) //Reinforced in MAX-only seat
checkCanNotMount()
vehicle.Actor ! Mountable.TryMount(player3, 9) //Agile in MAX-only seat
checkCanNotMount()
//allow
vehicle.Actor ! Mountable.TryMount(player1, 1)
checkCanMount()
vehicle.Actor ! Mountable.TryMount(player2, 9)
checkCanMount()
vehicle.Actor ! Mountable.TryMount(player3, 0)
checkCanMount()
}
}
}
class VehicleControlMountingBlockedSeatPermissionTest extends ActorTest {
def checkCanNotMount() : Unit = {
val reply = receiveOne(Duration.create(100, "ms"))
reply match {
case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanNotMount])
case _ =>
assert(false)
}
}
def checkCanMount() : Unit = {
val reply = receiveOne(Duration.create(100, "ms"))
reply match {
case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanMount])
case _ =>
assert(false)
}
}
"Vehicle Control" should {
//11 June 2018: Group is not supported yet so do not bother testing it
"block players from sitting if the seat does not allow it" in {
val vehicle = Vehicle(GlobalDefinitions.apc_tr)
vehicle.GUID = PlanetSideGUID(10)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
val player1 = Player(VehicleTest.avatar1)
player1.GUID = PlanetSideGUID(1)
val player2 = Player(VehicleTest.avatar1)
player2.GUID = PlanetSideGUID(2)
vehicle.PermissionGroup(2,3) //passenger group -> empire
vehicle.Actor ! Mountable.TryMount(player1, 3) //passenger seat
checkCanMount()
vehicle.PermissionGroup(2,0) //passenger group -> locked
vehicle.Actor ! Mountable.TryMount(player2, 4) //passenger seat
checkCanNotMount()
}
}
}
class VehicleControlMountingDriverSeatTest extends ActorTest {
def checkCanMount() : Unit = {
val reply = receiveOne(Duration.create(100, "ms"))
reply match {
case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanMount])
case _ =>
assert(false)
}
}
"Vehicle Control" should {
"allow players to sit in the driver seat, even if it is locked, if the vehicle is unowned" in {
val vehicle = Vehicle(GlobalDefinitions.apc_tr)
vehicle.GUID = PlanetSideGUID(10)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
val player1 = Player(VehicleTest.avatar1)
player1.GUID = PlanetSideGUID(1)
assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Locked)) //driver group -> locked
assert(vehicle.Seats(0).Occupant.isEmpty)
assert(vehicle.Owner.isEmpty)
vehicle.Actor ! Mountable.TryMount(player1, 0)
checkCanMount()
assert(vehicle.Seats(0).Occupant.nonEmpty)
}
}
}
class VehicleControlMountingOwnedLockedDriverSeatTest extends ActorTest {
def checkCanNotMount() : Unit = {
val reply = receiveOne(Duration.create(100, "ms"))
reply match {
case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanNotMount])
case _ =>
assert(false)
}
}
def checkCanMount() : Unit = {
val reply = receiveOne(Duration.create(100, "ms"))
reply match {
case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanMount])
case _ =>
assert(false)
}
}
"Vehicle Control" should {
"block players that are not the current owner from sitting in the driver seat (locked)" in {
val vehicle = Vehicle(GlobalDefinitions.apc_tr)
vehicle.GUID = PlanetSideGUID(10)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
val player1 = Player(VehicleTest.avatar1)
player1.GUID = PlanetSideGUID(1)
val player2 = Player(VehicleTest.avatar1)
player2.GUID = PlanetSideGUID(2)
assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Locked)) //driver group -> locked
assert(vehicle.Seats(0).Occupant.isEmpty)
vehicle.Owner = player1.GUID
vehicle.Actor ! Mountable.TryMount(player1, 0)
checkCanMount()
assert(vehicle.Seats(0).Occupant.nonEmpty)
vehicle.Actor ! Mountable.TryDismount(player1, 0)
receiveOne(Duration.create(100, "ms")) //discard
assert(vehicle.Seats(0).Occupant.isEmpty)
vehicle.Actor ! Mountable.TryMount(player2, 0)
checkCanNotMount()
assert(vehicle.Seats(0).Occupant.isEmpty)
}
}
}
class VehicleControlMountingOwnedUnlockedDriverSeatTest extends ActorTest {
def checkCanMount() : Unit = {
val reply = receiveOne(Duration.create(100, "ms"))
reply match {
case msg : Mountable.MountMessages =>
assert(msg.response.isInstanceOf[Mountable.CanMount])
case _ =>
assert(false)
}
}
"Vehicle Control" should {
"allow players that are not the current owner to sit in the driver seat (empire)" in {
val vehicle = Vehicle(GlobalDefinitions.apc_tr)
vehicle.GUID = PlanetSideGUID(10)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
val player1 = Player(VehicleTest.avatar1)
player1.GUID = PlanetSideGUID(1)
val player2 = Player(VehicleTest.avatar1)
player2.GUID = PlanetSideGUID(2)
vehicle.PermissionGroup(0,3) //passenger group -> empire
assert(vehicle.PermissionGroup(0).contains(VehicleLockState.Empire)) //driver group -> empire
assert(vehicle.Seats(0).Occupant.isEmpty)
vehicle.Owner = player1.GUID //owner set
vehicle.Actor ! Mountable.TryMount(player1, 0)
checkCanMount()
assert(vehicle.Seats(0).Occupant.nonEmpty)
vehicle.Actor ! Mountable.TryDismount(player1, 0)
receiveOne(Duration.create(100, "ms")) //discard
assert(vehicle.Seats(0).Occupant.isEmpty)
vehicle.Actor ! Mountable.TryMount(player2, 0)
checkCanMount()
assert(vehicle.Seats(0).Occupant.nonEmpty)
}
}
}
object VehicleTest {
import net.psforever.objects.Avatar
import net.psforever.types.{CharacterGender, PlanetSideEmpire}
val avatar1 = Avatar("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val avatar2 = Avatar("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val avatar1 = Avatar("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)
val avatar2 = Avatar("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)
}

View file

@ -12,7 +12,7 @@ import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects._
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, Vector3}
import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, StructureType}
import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap}
import net.psforever.objects.Vehicle
@ -185,7 +185,7 @@ class ZoneActorTest extends ActorTest {
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-spawn")
zone.Actor ! Zone.Init()
expectNoMsg(Duration.create(300, "ms"))
val player = Player(Avatar("Chord", PlanetSideEmpire.NEUTRAL, CharacterGender.Male, 0, 5))
val player = Player(Avatar("Chord", PlanetSideEmpire.NEUTRAL, CharacterGender.Male, 0, CharacterVoice.Voice5))
val bldg1 = zone.Building(1).get
val bldg3 = zone.Building(3).get
@ -216,7 +216,7 @@ class ZoneActorTest extends ActorTest {
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-no-spawn")
zone.Actor ! Zone.Init()
expectNoMsg(Duration.create(300, "ms"))
val player = Player(Avatar("Chord", PlanetSideEmpire.NEUTRAL, CharacterGender.Male, 0, 5))
val player = Player(Avatar("Chord", PlanetSideEmpire.NEUTRAL, CharacterGender.Male, 0, CharacterVoice.Voice5))
zone.Actor ! Zone.Lattice.RequestSpawnPoint(1, player, 7)
val reply = receiveOne(Duration.create(200, "ms"))
@ -234,7 +234,7 @@ class ZonePopulationTest extends ActorTest {
"ZonePopulationActor" should {
"add new user to zones" in {
val zone = new Zone("test", new ZoneMap(""), 0)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!"
receiveOne(Duration.create(200, "ms")) //consume
@ -249,7 +249,7 @@ class ZonePopulationTest extends ActorTest {
"remove user from zones" in {
val zone = new Zone("test", new ZoneMap(""), 0)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!"
receiveOne(Duration.create(200, "ms")) //consume
zone.Population ! Zone.Population.Join(avatar)
@ -264,7 +264,7 @@ class ZonePopulationTest extends ActorTest {
"associate user with a character" in {
val zone = new Zone("test", new ZoneMap(""), 0)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!"
receiveOne(Duration.create(200, "ms")) //consume
@ -284,7 +284,7 @@ class ZonePopulationTest extends ActorTest {
"disassociate character from a user" in {
val zone = new Zone("test", new ZoneMap(""), 0)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!"
receiveOne(Duration.create(200, "ms")) //consume
@ -306,7 +306,7 @@ class ZonePopulationTest extends ActorTest {
"user tries to Leave, but still has an associated character" in {
val zone = new Zone("test", new ZoneMap(""), 0)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!"
receiveOne(Duration.create(500, "ms")) //consume
@ -330,7 +330,7 @@ class ZonePopulationTest extends ActorTest {
"user tries to Spawn a character, but an associated character already exists" in {
val zone = new Zone("test", new ZoneMap(""), 0)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player1 = Player(avatar)
val player2 = Player(avatar)
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!"
@ -356,7 +356,7 @@ class ZonePopulationTest extends ActorTest {
"user tries to Spawn a character, but did not Join first" in {
val zone = new Zone("test", new ZoneMap(""), 0)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!"
receiveOne(Duration.create(200, "ms")) //consume
@ -374,7 +374,7 @@ class ZonePopulationTest extends ActorTest {
"user tries to Release a character, but did not Spawn a character first" in {
val zone = new Zone("test", new ZoneMap(""), 0)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!"
receiveOne(Duration.create(200, "ms")) //consume
zone.Population ! Zone.Population.Join(avatar)
@ -395,7 +395,7 @@ class ZonePopulationTest extends ActorTest {
"user adds character to list of retired characters" in {
val zone = new Zone("test", new ZoneMap(""), 0)
val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5))
val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player.Release
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!"
receiveOne(Duration.create(200, "ms")) //consume
@ -409,7 +409,7 @@ class ZonePopulationTest extends ActorTest {
"user removes character from the list of retired characters" in {
val zone = new Zone("test", new ZoneMap(""), 0)
val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5))
val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player.Release
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!"
receiveOne(Duration.create(200, "ms")) //consume
@ -425,11 +425,11 @@ class ZonePopulationTest extends ActorTest {
"user removes THE CORRECT character from the list of retired characters" in {
val zone = new Zone("test", new ZoneMap(""), 0)
val player1 = Player(Avatar("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5))
val player1 = Player(Avatar("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player1.Release
val player2 = Player(Avatar("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5))
val player2 = Player(Avatar("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player2.Release
val player3 = Player(Avatar("Chord3", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5))
val player3 = Player(Avatar("Chord3", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player3.Release
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), TestName) ! "!"
receiveOne(Duration.create(200, "ms")) //consume
@ -451,7 +451,7 @@ class ZonePopulationTest extends ActorTest {
"user tries to add character to list of retired characters, but is not in correct state" in {
val zone = new Zone("test", new ZoneMap(""), 0)
val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5))
val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
//player.Release !!important
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "testC") ! "!"
receiveOne(Duration.create(500, "ms")) //consume

View file

@ -3,13 +3,13 @@ package objects.guidtask
import net.psforever.objects._
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
import net.psforever.types.{CharacterGender, PlanetSideEmpire}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire}
import objects.ActorTest
class GUIDTaskRegister5Test extends ActorTest() {
"RegisterAvatar" in {
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup
val obj = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val obj = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val obj_wep = Tool(GlobalDefinitions.beamer)
obj.Slot(0).Equipment = obj_wep
val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell)

View file

@ -3,13 +3,13 @@ package objects.guidtask
import net.psforever.objects._
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
import net.psforever.types.{CharacterGender, PlanetSideEmpire}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire}
import objects.ActorTest
class GUIDTaskRegister6Test extends ActorTest() {
"RegisterPlayer" in {
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup
val obj = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val obj = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val obj_wep = Tool(GlobalDefinitions.beamer)
obj.Slot(0).Equipment = obj_wep
val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell)

View file

@ -3,13 +3,13 @@ package objects.guidtask
import net.psforever.objects._
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
import net.psforever.types.{CharacterGender, PlanetSideEmpire}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire}
import objects.ActorTest
class GUIDTaskUnregister5Test extends ActorTest() {
"UnregisterAvatar" in {
val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup
val obj = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val obj = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val obj_wep = Tool(GlobalDefinitions.beamer)
obj.Slot(0).Equipment = obj_wep
val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell)

View file

@ -3,13 +3,13 @@ package objects.guidtask
import net.psforever.objects._
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
import net.psforever.types.{CharacterGender, PlanetSideEmpire}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire}
import objects.ActorTest
class GUIDTaskUnregister6Test extends ActorTest() {
"UnregisterPlayer" in {
val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup
val obj = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val obj = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val obj_wep = Tool(GlobalDefinitions.beamer)
obj.Slot(0).Equipment = obj_wep
val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell)

View file

@ -7,12 +7,12 @@ import net.psforever.objects.{Avatar, GlobalDefinitions, Player}
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, TransactionType}
import org.specs2.mutable.Specification
class AirVehicleTerminalTest extends Specification {
"Air_Vehicle_Terminal" should {
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val terminal = Terminal(GlobalDefinitions.air_vehicle_terminal)
terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building)
terminal.Owner.Faction = PlanetSideEmpire.TR

View file

@ -12,7 +12,7 @@ import org.specs2.mutable.Specification
class CertTerminalTest extends Specification {
"Cert_Terminal" should {
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val terminal = Terminal(GlobalDefinitions.cert_terminal)
terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building)
terminal.Owner.Faction = PlanetSideEmpire.TR

View file

@ -7,12 +7,12 @@ import net.psforever.objects.{Avatar, GlobalDefinitions, Player}
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, TransactionType}
import org.specs2.mutable.Specification
class DropshipVehicleTerminalTest extends Specification {
"Dropship_Vehicle_Terminal" should {
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val terminal = Terminal(GlobalDefinitions.dropship_vehicle_terminal)
terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building)
terminal.Owner.Faction = PlanetSideEmpire.TR

View file

@ -7,12 +7,12 @@ import net.psforever.objects.{Avatar, GlobalDefinitions, Player}
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, TransactionType}
import org.specs2.mutable.Specification
class GroundVehicleTerminalTest extends Specification {
"Ground_Vehicle_Terminal" should {
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val terminal = Terminal(GlobalDefinitions.ground_vehicle_terminal)
terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building)
terminal.Owner.Faction = PlanetSideEmpire.TR

View file

@ -7,12 +7,12 @@ import net.psforever.objects.{Avatar, GlobalDefinitions, Player}
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, TransactionType}
import org.specs2.mutable.Specification
class ImplantTerminalInterfaceTest extends Specification {
"Implant_Terminal_Interface" should {
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val terminal = Terminal(GlobalDefinitions.implant_terminal_interface)
terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building)
terminal.Owner.Faction = PlanetSideEmpire.TR

View file

@ -8,7 +8,7 @@ import net.psforever.objects.serverobject.implantmech.{ImplantTerminalMech, Impl
import net.psforever.objects.serverobject.structures.StructureType
import net.psforever.objects.vehicles.Seat
import net.psforever.objects.{Avatar, GlobalDefinitions, Player}
import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, Vector3}
import objects.ActorTest
import org.specs2.mutable.Specification
@ -45,7 +45,7 @@ class ImplantTerminalMechTest extends Specification {
}
"get passenger in a seat" in {
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val obj = ImplantTerminalMech(GlobalDefinitions.implant_terminal_mech)
obj.PassengerInSeat(player) mustEqual None
obj.Seats(0).Occupant = player
@ -90,7 +90,7 @@ class ImplantTerminalMechControl3Test extends ActorTest() {
"ImplantTerminalMechControl" should {
"block a player from mounting" in {
val (player1, mech) = ImplantTerminalMechTest.SetUpAgents(PlanetSideEmpire.TR)
val player2 = Player(Avatar("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player2 = Player(Avatar("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
mech.Actor ! Mountable.TryMount(player1, 0)
receiveOne(Duration.create(100, "ms")) //consume reply
@ -164,6 +164,6 @@ object ImplantTerminalMechTest {
terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building)
terminal.Owner.Faction = faction
terminal.GUID = PlanetSideGUID(1)
(Player(Avatar("test", faction, CharacterGender.Male, 0, 0)), terminal)
(Player(Avatar("test", faction, CharacterGender.Male, 0, CharacterVoice.Mute)), terminal)
}
}

View file

@ -60,7 +60,7 @@ class MatrixTerminalTest extends Specification {
}
"player can not buy (anything)" in {
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "lite_armor", 0, PlanetSideGUID(0))
terminal.Request(player, msg) mustEqual Terminal.NoDeal()

View file

@ -5,7 +5,7 @@ import akka.actor.ActorRef
import net.psforever.objects.serverobject.terminals.{MedicalTerminalDefinition, ProximityTerminal, Terminal}
import net.psforever.objects.{Avatar, GlobalDefinitions, Player}
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, TransactionType}
import org.specs2.mutable.Specification
class MedicalTerminalTest extends Specification {
@ -81,7 +81,7 @@ class MedicalTerminalTest extends Specification {
"player can not interact with the proximity terminal normally (buy)" in {
val terminal = ProximityTerminal(GlobalDefinitions.medical_terminal)
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "lite_armor", 0, PlanetSideGUID(0))
terminal.Request(player, msg) mustEqual Terminal.NoDeal()

View file

@ -47,14 +47,14 @@ class OrderTerminalABTest extends Specification {
}
"player can buy different armor ('lite_armor')" in {
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "lite_armor", 0, PlanetSideGUID(0))
terminal.Request(player, msg) mustEqual Terminal.BuyExosuit(ExoSuitType.Agile)
}
"player can buy max armor ('trhev_antiaircraft')" in {
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "trhev_antiaircraft", 0, PlanetSideGUID(0))
terminal.Request(player, msg) mustEqual Terminal.NoDeal()
@ -62,7 +62,7 @@ class OrderTerminalABTest extends Specification {
//TODO loudout tests
"player can not load max loadout" in {
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)
val player = Player(avatar)
avatar.SaveLoadout(player, "test1", 0)
player.ExoSuit = ExoSuitType.MAX

View file

@ -12,7 +12,7 @@ import org.specs2.mutable.Specification
class OrderTerminalTest extends Specification {
"Order_Terminal" should {
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val terminal = Terminal(GlobalDefinitions.order_terminal)
terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building)
terminal.Owner.Faction = PlanetSideEmpire.TR
@ -79,7 +79,7 @@ class OrderTerminalTest extends Specification {
}
"player can retrieve an infantry loadout" in {
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)
val player2 = Player(avatar)
player2.ExoSuit = ExoSuitType.Agile
player2.Slot(0).Equipment = Tool(GlobalDefinitions.beamer)
@ -99,7 +99,7 @@ class OrderTerminalTest extends Specification {
}
"player can not retrieve an infantry loadout from the wrong page" in {
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)
val player2 = Player(avatar)
player2.ExoSuit = ExoSuitType.Agile
player2.Slot(0).Equipment = Tool(GlobalDefinitions.beamer)
@ -111,7 +111,7 @@ class OrderTerminalTest extends Specification {
}
"player can not retrieve an infantry loadout from the wrong line" in {
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)
val player2 = Player(avatar)
player2.ExoSuit = ExoSuitType.Agile
player2.Slot(0).Equipment = Tool(GlobalDefinitions.beamer)

View file

@ -6,7 +6,7 @@ import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.{Avatar, GlobalDefinitions, Player}
import net.psforever.objects.serverobject.terminals._
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.{CharacterGender, PlanetSideEmpire}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire}
import objects.ActorTest
import scala.concurrent.duration.Duration
@ -34,7 +34,7 @@ class MedicalTerminalControl1Test extends ActorTest() {
"ProximityTerminalControl sends a message to the first new user only" in {
val (player, terminal) = ProximityTerminalControlTest.SetUpAgents(GlobalDefinitions.medical_terminal, PlanetSideEmpire.TR)
player.GUID = PlanetSideGUID(10)
val player2 = Player(Avatar("someothertest", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player2 = Player(Avatar("someothertest", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player2.GUID = PlanetSideGUID(11)
terminal.Actor ! CommonMessages.Use(player)
@ -57,7 +57,7 @@ class MedicalTerminalControl2Test extends ActorTest() {
"ProximityTerminalControl sends a message to the last user only" in {
val (player, terminal) : (Player, ProximityTerminal) = ProximityTerminalControlTest.SetUpAgents(GlobalDefinitions.medical_terminal, PlanetSideEmpire.TR)
player.GUID = PlanetSideGUID(10)
val player2 = Player(Avatar("someothertest", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player2 = Player(Avatar("someothertest", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player2.GUID = PlanetSideGUID(11)
terminal.Actor ! CommonMessages.Use(player)
@ -86,7 +86,7 @@ class MedicalTerminalControl3Test extends ActorTest() {
"ProximityTerminalControl sends a message to the last user only (confirmation of test #2)" in {
val (player, terminal) : (Player, ProximityTerminal) = ProximityTerminalControlTest.SetUpAgents(GlobalDefinitions.medical_terminal, PlanetSideEmpire.TR)
player.GUID = PlanetSideGUID(10)
val player2 = Player(Avatar("someothertest", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player2 = Player(Avatar("someothertest", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player2.GUID = PlanetSideGUID(11)
terminal.Actor ! CommonMessages.Use(player)
@ -115,6 +115,6 @@ object ProximityTerminalControlTest {
def SetUpAgents(tdef : MedicalTerminalDefinition, faction : PlanetSideEmpire.Value)(implicit system : ActorSystem) : (Player, ProximityTerminal) = {
val terminal = ProximityTerminal(tdef)
terminal.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], terminal), "test-term")
(Player(Avatar("test", faction, CharacterGender.Male, 0, 0)), terminal)
(Player(Avatar("test", faction, CharacterGender.Male, 0, CharacterVoice.Mute)), terminal)
}
}

View file

@ -7,7 +7,7 @@ import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage
import net.psforever.objects.serverobject.terminals.{ProximityTerminal, ProximityTerminalControl, ProximityUnit, Terminal}
import net.psforever.objects.{Avatar, GlobalDefinitions, Player}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.{CharacterGender, PlanetSideEmpire}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire}
import objects.ActorTest
import org.specs2.mutable.Specification
@ -70,7 +70,7 @@ class ProximityTerminalControl1bTest extends ActorTest {
"send out a start message" in {
val obj = ProximityTerminal(GlobalDefinitions.medical_terminal)
obj.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], obj), "prox-ctrl")
val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player.GUID = PlanetSideGUID(10)
assert(obj.NumberUsers == 0)
@ -91,9 +91,9 @@ class ProximityTerminalControl2bTest extends ActorTest {
"will not send out one start message unless first user" in {
val obj = ProximityTerminal(GlobalDefinitions.medical_terminal)
obj.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], obj), "prox-ctrl")
val player1 = Player(Avatar("TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player1 = Player(Avatar("TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player1.GUID = PlanetSideGUID(10)
val player2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player2.GUID = PlanetSideGUID(11)
assert(obj.NumberUsers == 0)
@ -114,7 +114,7 @@ class ProximityTerminalControl3bTest extends ActorTest {
"send out a stop message" in {
val obj = ProximityTerminal(GlobalDefinitions.medical_terminal)
obj.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], obj), "prox-ctrl")
val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player.GUID = PlanetSideGUID(10)
assert(obj.NumberUsers == 0)
@ -138,9 +138,9 @@ class ProximityTerminalControl4bTest extends ActorTest {
"will not send out one stop message until last user" in {
val obj = ProximityTerminal(GlobalDefinitions.medical_terminal)
obj.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], obj), "prox-ctrl")
val player1 = Player(Avatar("TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player1 = Player(Avatar("TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player1.GUID = PlanetSideGUID(10)
val player2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
player2.GUID = PlanetSideGUID(11)
assert(obj.NumberUsers == 0)

View file

@ -7,12 +7,12 @@ import net.psforever.objects._
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, TransactionType}
import org.specs2.mutable.Specification
class RepairRearmSiloTest extends Specification {
"RepairRearmSilo" should {
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val silo = Terminal(GlobalDefinitions.repair_silo)
silo.Owner = new Building(0, Zone.Nowhere, StructureType.Building)
silo.Owner.Faction = PlanetSideEmpire.TR
@ -49,7 +49,7 @@ class RepairRearmSiloTest extends Specification {
}
"player can retrieve a vehicle loadout" in {
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)
val player2 = Player(avatar)
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.Slot(30).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm)
@ -67,7 +67,7 @@ class RepairRearmSiloTest extends Specification {
}
"player can not retrieve a vehicle loadout from the wrong line" in {
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)
val player2 = Player(avatar)
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.Slot(30).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm)
@ -78,7 +78,7 @@ class RepairRearmSiloTest extends Specification {
}
"player can not retrieve a vehicle loadout from the wrong line" in {
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)
val player2 = Player(avatar)
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.Slot(30).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm)

View file

@ -123,6 +123,6 @@ object TerminalControlTest {
terminal.Actor = system.actorOf(Props(classOf[TerminalControl], terminal), "test-term")
terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building)
terminal.Owner.Faction = faction
(Player(Avatar("test", faction, CharacterGender.Male, 0, 0)), terminal)
(Player(Avatar("test", faction, CharacterGender.Male, 0, CharacterVoice.Mute)), terminal)
}
}

View file

@ -7,12 +7,12 @@ import net.psforever.objects.{Avatar, GlobalDefinitions, Player}
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, TransactionType}
import org.specs2.mutable.Specification
class VehicleTerminalCombinedTest extends Specification {
"Ground_Vehicle_Terminal" should {
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val terminal = Terminal(GlobalDefinitions.vehicle_terminal_combined)
terminal.Owner = new Building(0, Zone.Nowhere, StructureType.Building)
terminal.Owner.Faction = PlanetSideEmpire.TR

View file

@ -473,6 +473,7 @@ object Maps {
LocalObject(396, Door.Constructor)
LocalObject(397, Door.Constructor)
LocalObject(398, Door.Constructor)
LocalObject(399, Door.Constructor)
LocalObject(462, Door.Constructor)
LocalObject(463, Door.Constructor)
LocalObject(522, ImplantTerminalMech.Constructor)
@ -520,6 +521,7 @@ object Maps {
ObjectToBuilding(396, 2)
ObjectToBuilding(397, 2)
ObjectToBuilding(398, 2)
ObjectToBuilding(399, 2)
ObjectToBuilding(462, 2)
ObjectToBuilding(463, 2)
ObjectToBuilding(522, 2)

View file

@ -331,8 +331,8 @@ class PacketCodingActor extends Actor with MDCContextAware {
/**
* Accept a series of packets and transform it into a series of packet encodings.
* Packets that do not encode properly are simply excluded for the product.
* This is not treated as an error or exception; a warning will mrely be logged.
* Packets that do not encode properly are simply excluded from the product.
* This is not treated as an error or exception; a warning will merely be logged.
* @param iter the `Iterator` for a series of packets
* @param out updated series of byte stream data produced through successful packet encoding;
* defaults to an empty list

View file

@ -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) =>
@ -348,7 +337,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
msg.facingYaw,
msg.facingPitch,
msg.facingYawUpper,
0,
unk1 = 0,
msg.is_crouching,
msg.is_jumping,
msg.jump_thrust,
@ -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).contains(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) =>
@ -1138,12 +1132,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(ObjectHeldMessage(player.GUID, Player.HandsDownSlot, true))
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, 22, 1L)) //mount points off
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 21, player.GUID.guid)) //ownership
case VehicleSpawnPad.PlayerSeatedInVehicle(vehicle, pad) =>
val vehicle_guid = vehicle.GUID
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 0L)) //mount points on?
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 0L)) //mount points on
//sendResponse(PlanetsideAttributeMessage(vehicle_guid, 0, 10))//vehicle.Definition.MaxHealth))
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 68, 0L)) //???
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 113, 0L)) //???
@ -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) =>
@ -1547,7 +1554,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
//TODO begin temp player character auto-loading; remove later
import net.psforever.objects.GlobalDefinitions._
import net.psforever.types.CertificationType._
avatar = Avatar("TestCharacter" + sessionId.toString, PlanetSideEmpire.VS, CharacterGender.Female, 41, 1)
avatar = Avatar("TestCharacter" + sessionId.toString, PlanetSideEmpire.VS, CharacterGender.Female, 41, CharacterVoice.Voice1)
avatar.Certifications += StandardAssault
avatar.Certifications += MediumAssault
avatar.Certifications += StandardExoSuit
@ -1649,30 +1656,39 @@ class WorldSessionActor extends Actor with MDCContextAware {
)
})
//load active players in zone
continent.LivePlayers.filterNot(_.GUID == player.GUID).foreach(char => {
sendResponse(ObjectCreateMessage(ObjectClass.avatar, char.GUID, char.Definition.Packet.ConstructorData(char).get))
if(char.UsingSpecial == SpecialExoSuitDefinition.Mode.Anchored) {
sendResponse(PlanetsideAttributeMessage(char.GUID, 19, 1))
}
})
continent.LivePlayers
.filterNot(tplayer => { tplayer.GUID == player.GUID || tplayer.VehicleSeated.nonEmpty })
.foreach(char => {
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))
}
})
//load corpses in zone
continent.Corpses.foreach {
TurnPlayerIntoCorpse
}
//load active vehicles in zone
continent.Vehicles.foreach(vehicle => {
val definition = vehicle.Definition
sendResponse(ObjectCreateMessage(definition.ObjectId, vehicle.GUID, definition.Packet.ConstructorData(vehicle).get))
//seat vehicle occupants
definition.MountPoints.values.foreach(seat_num => {
vehicle.Seat(seat_num).get.Occupant match {
case Some(tplayer) =>
if(tplayer.HasGUID) {
sendResponse(ObjectAttachMessage(vehicle.GUID, tplayer.GUID, seat_num))
}
case None => ;
}
})
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
@ -1683,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
@ -1694,15 +1710,19 @@ class WorldSessionActor extends Actor with MDCContextAware {
//seat terminal occupants
continent.GUID(terminal_guid) match {
case Some(obj : Mountable) =>
obj.MountPoints.foreach({ case ((_, seat_num)) =>
obj.Seat(seat_num).get.Occupant match {
case Some(tplayer) =>
if(tplayer.HasGUID) {
sendResponse(ObjectAttachMessage(parent_guid, tplayer.GUID, seat_num))
}
case None => ;
}
})
obj.Seats(0).Occupant match {
case Some(tplayer) =>
val tdefintion = tplayer.Definition
sendResponse(
ObjectCreateMessage(
tdefintion.ObjectId,
tplayer.GUID,
ObjectCreateMessageParent(parent_guid, 0),
tdefintion.Packet.ConstructorData(tplayer).get
)
)
case None => ;
}
case _ => ;
}
})
@ -1828,7 +1848,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
galaxy ! Zone.Lattice.RequestSpawnPoint(u5.toInt, player, u2.toInt)
case msg @ SetChatFilterMessage(send_channel, origin, whitelist) =>
log.info("SetChatFilters: " + msg)
//log.info("SetChatFilters: " + msg)
case msg @ ChatMsg(messagetype, has_wide_contents, recipient, contents, note_contents) =>
var makeReply : Boolean = true
@ -1890,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)
@ -2521,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) {
@ -2846,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
}
}
@ -2882,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))
@ -3214,6 +3234,29 @@ class WorldSessionActor extends Actor with MDCContextAware {
}, List(RegisterVehicle(obj)))
}
//TODO this may be useful for vehicle gating
def RegisterDrivenVehicle(obj : Vehicle, driver : Player) : TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
private val localVehicle = obj
private val localDriver = driver
override def isComplete : Task.Resolution.Value = {
if(localVehicle.HasGUID && localDriver.HasGUID) {
Task.Resolution.Success
}
else {
Task.Resolution.Incomplete
}
}
def Execute(resolver : ActorRef) : Unit = {
//TODO some kind of callback ...
resolver ! scala.util.Success(this)
}
}, List(RegisterAvatar(driver), RegisterVehicle(obj)))
}
/**
* Construct tasking that removes the `Equipment` to `target`.
* @param target what object that contains the `Equipment`
@ -3422,6 +3465,46 @@ 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) =>
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
}
}
/**
* 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.
@ -4143,27 +4226,27 @@ class WorldSessionActor extends Actor with MDCContextAware {
def initFacility(continentNumber : Int, buildingNumber : Int, building : Building) : Unit = {
sendResponse(
BuildingInfoUpdateMessage(
continentNumber, //Zone
buildingNumber, //Facility
8, //NTU%
false, //Hacked
PlanetSideEmpire.NEUTRAL, //Base hacked by
0, //Time remaining for hack (ms)
building.Faction, //Base owned by
0, //!! Field != 0 will cause malformed packet. See class def.
None,
PlanetSideGeneratorState.Normal, //Generator state
true, //Respawn tubes operating state
false, //Force dome state
0, //Lattice benefits
0, //!! Field > 0 will cause malformed packet. See class def.
Nil,
0,
false,
8, //!! Field != 8 will cause malformed packet. See class def.
None,
false, //Boosted spawn room pain field
false //Boosted generator room pain field
continentNumber,
buildingNumber,
ntu_level = 8,
is_hacked = false,
empire_hack = PlanetSideEmpire.NEUTRAL,
hack_time_remaining = 0,
building.Faction,
unk1 = 0, //!! Field != 0 will cause malformed packet. See class def.
unk1x = None,
PlanetSideGeneratorState.Normal,
spawn_tubes_normal = true,
force_dome_active = false,
lattice_benefit = 0,
cavern_benefit = 0, //!! Field > 0 will cause malformed packet. See class def.
unk4 = Nil,
unk5 = 0,
unk6 = false,
unk7 = 8, //!! Field != 8 will cause malformed packet. See class def.
unk7x = None,
boost_spawn_pain = false,
boost_generator_pain = false
)
)
sendResponse(DensityLevelUpdateMessage(continentNumber, buildingNumber, List(0,0, 0,0, 0,0, 0,0)))
@ -4183,26 +4266,27 @@ class WorldSessionActor extends Actor with MDCContextAware {
def initGate(continentNumber : Int, buildingNumber : Int, building : Building) : Unit = {
sendResponse(
BuildingInfoUpdateMessage(
continentNumber, buildingNumber,
0,
false,
PlanetSideEmpire.NEUTRAL,
0,
continentNumber,
buildingNumber,
ntu_level = 0,
is_hacked = false,
empire_hack = PlanetSideEmpire.NEUTRAL,
hack_time_remaining = 0,
building.Faction,
0,
None,
unk1 = 0,
unk1x = None,
PlanetSideGeneratorState.Normal,
true,
false,
0,
0,
Nil,
0,
false,
8,
None,
false,
false
spawn_tubes_normal = true,
force_dome_active = false,
lattice_benefit = 0,
cavern_benefit = 0,
unk4 = Nil,
unk5 = 0,
unk6 = false,
unk7 = 8,
unk7x = None,
boost_spawn_pain = false,
boost_generator_pain = false
)
)
sendResponse(DensityLevelUpdateMessage(continentNumber, buildingNumber, List(0,0, 0,0, 0,0, 0,0)))
@ -4319,14 +4403,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")
}
@ -4384,8 +4470,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)
)
}
@ -4761,7 +4848,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case None => ;
}
}
def sendResponse(cont : PlanetSidePacketContainer) : Unit = {
log.trace("WORLD SEND: " + cont)
sendResponse(cont.asInstanceOf[Any])

View file

@ -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

View file

@ -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

View file

@ -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(

View file

@ -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

View file

@ -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

View file

@ -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))

View file

@ -4,9 +4,9 @@ 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 net.psforever.types._
import services.{RemoverActor, Service, ServiceManager}
import services.avatar._
@ -152,18 +152,31 @@ class DroptItemTest extends ActorTest {
}
class LoadPlayerTest extends ActorTest {
val obj = Player(Avatar("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, 1))
val obj = Player(Avatar("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
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)))
}
}
}
@ -222,7 +235,7 @@ class PlayerStateTest extends ActorTest {
}
class PickupItemATest extends ActorTest {
val obj = Player(Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 1, 1))
val obj = Player(Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
obj.GUID = PlanetSideGUID(10)
obj.Slot(5).Equipment.get.GUID = PlanetSideGUID(11)
@ -247,7 +260,7 @@ class PickupItemATest extends ActorTest {
}
class PickupItemBTest extends ActorTest {
val obj = Player(Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 1, 1))
val obj = Player(Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
val tool = Tool(GlobalDefinitions.beamer)
tool.GUID = PlanetSideGUID(40)
@ -375,7 +388,7 @@ class AvatarReleaseTest extends ActorTest {
val taskResolver = system.actorOf(Props[TaskResolver], "release-test-resolver")
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "release-test-zone")
zone.Actor ! Zone.Init()
val obj = Player(Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 1, 1))
val obj = Player(Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
obj.Continent = "test"
obj.Release
@ -424,7 +437,7 @@ class AvatarReleaseEarly1Test extends ActorTest {
val taskResolver = system.actorOf(Props[TaskResolver], "release-test-resolver")
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "release-test-zone")
zone.Actor ! Zone.Init()
val obj = Player(Avatar("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, 1))
val obj = Player(Avatar("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
obj.Continent = "test"
obj.Release
@ -474,8 +487,8 @@ class AvatarReleaseEarly2Test extends ActorTest {
val taskResolver = system.actorOf(Props[TaskResolver], "release-test-resolver")
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "release-test-zone")
zone.Actor ! Zone.Init()
val objAlt = Player(Avatar("TestCharacter2", PlanetSideEmpire.NC, CharacterGender.Male, 1, 1)) //necessary clutter
val obj = Player(Avatar("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, 1))
val objAlt = Player(Avatar("TestCharacter2", PlanetSideEmpire.NC, CharacterGender.Male, 1, CharacterVoice.Voice1)) //necessary clutter
val obj = Player(Avatar("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
obj.Continent = "test"
obj.Release

View file

@ -452,45 +452,44 @@ class PacketCodingActorHTest extends ActorTest {
}
class PacketCodingActorITest extends ActorTest {
import net.psforever.packet.game.objectcreate._
val pos : PlacementData = PlacementData(Vector3.Zero, Vector3.Zero)
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
BasicCharacterData("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, CharacterVoice.Voice1),
3,
false,
false,
ExoSuitType.Standard,
"",
0,
false,
2.8125f, 210.9375f,
true,
GrenadeState.None,
false,
false,
false,
RibbonBars()
)
var char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData(
0,
0,
100, 100,
50,
1, 7, 7,
100, 100,
List(CertificationType.StandardAssault, CertificationType.MediumAssault, CertificationType.ATV, CertificationType.Harasser, CertificationType.StandardExoSuit, CertificationType.AgileExoSuit, CertificationType.ReinforcedExoSuit),
List(),
List(),
List.empty,
None
)
val obj = DetailedPlayerData(pos, app, char, InventoryData(Nil), DrawnSlot.None)
val pkt = MultiPacketBundle(List(ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj)))
val string_hex = hex"000900001879060000bc84b000000000000000000002040000097049006c006c006c004900490049006c006c006c0049006c0049006c006c0049006c006c006c0049006c006c0049008452700000000000000000000000000000002000000fe6a703fffffffffffffffffffffffffffffffc00000000000000000000000000000000000000019001900064000001007ec800c80000000000000000000000000000000000000001c00042c54686c7000000000000000000000000000000000000000000000000000000000000000000000000200700"
"PacketCodingActor" should {
"bundle an r-originating packet into an l-facing SlottedMetaPacket byte stream data (SlottedMetaPacket)" in {
import net.psforever.packet.game.objectcreate._
val obj = DetailedCharacterData(
CharacterAppearanceData(
PlacementData(Vector3.Zero, Vector3.Zero),
BasicCharacterData("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1),
3,
false,
false,
ExoSuitType.Standard,
"",
0,
false,
2.8125f, 210.9375f,
true,
GrenadeState.None,
false,
false,
false,
RibbonBars()
),
0,
0,
100, 100,
50,
1, 7, 7,
100, 100,
List(CertificationType.StandardAssault,CertificationType.MediumAssault,CertificationType.ATV,CertificationType.Harasser,CertificationType.StandardExoSuit,CertificationType.AgileExoSuit,CertificationType.ReinforcedExoSuit),
List(),
List(),
List.empty,
None,
Some(InventoryData(Nil)),
DrawnSlot.None
)
val pkt = MultiPacketBundle(List(ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj)))
val string_hex = hex"000900001879060000bc84b000000000000000000002040000097049006c006c006c004900490049006c006c006c0049006c0049006c006c0049006c006c006c0049006c006c0049008452700000000000000000000000000000002000000fe6a703fffffffffffffffffffffffffffffffc00000000000000000000000000000000000000019001900064000001007ec800c80000000000000000000000000000000000000001c00042c54686c7000000000000000000000000000000000000000000000000000000000000000000000000200700"
val probe1 = TestProbe()
val probe2 = system.actorOf(Props(classOf[ActorTest.MDCTestProbe], probe1), "mdc-probe")
val pca : ActorRef = system.actorOf(Props[PacketCodingActor], "pca")
@ -547,25 +546,25 @@ class PacketCodingActorJTest extends ActorTest {
class PacketCodingActorKTest extends ActorTest {
import net.psforever.packet.game.objectcreate._
val obj = DetailedCharacterData(
CharacterAppearanceData(
PlacementData(Vector3.Zero, Vector3.Zero),
BasicCharacterData("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1),
3,
false,
false,
ExoSuitType.Standard,
"",
0,
false,
2.8125f, 210.9375f,
true,
GrenadeState.None,
false,
false,
false,
RibbonBars()
),
val pos : PlacementData = PlacementData(Vector3.Zero, Vector3.Zero)
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
BasicCharacterData("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, CharacterVoice.Voice1),
3,
false,
false,
ExoSuitType.Standard,
"",
0,
false,
2.8125f, 210.9375f,
true,
GrenadeState.None,
false,
false,
false,
RibbonBars()
)
var char : (Option[Int])=>DetailedCharacterData = DetailedCharacterData(
0,
0,
100, 100,
@ -576,10 +575,9 @@ class PacketCodingActorKTest extends ActorTest {
List(),
List("xpe_sanctuary_help", "xpe_th_firemodes", "used_beamer", "map13"),
List.empty,
None,
Some(InventoryData(Nil)),
DrawnSlot.None
None
)
val obj = DetailedPlayerData(pos, app, char, InventoryData(Nil), DrawnSlot.None)
val list = List(
ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj),
ObjectDeleteMessage(PlanetSideGUID(1103), 2),

File diff suppressed because it is too large Load diff

View file

@ -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))))
}
}
}