Merge branch 'master' into feature/Nanites

# Conflicts:
#	common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
#	common/src/main/scala/net/psforever/packet/game/objectcreate/VehicleData.scala
#	common/src/main/scala/services/avatar/AvatarService.scala
#	common/src/main/scala/services/vehicle/VehicleService.scala
#	common/src/main/scala/services/vehicle/support/DeconstructionActor.scala
#	common/src/main/scala/services/vehicle/support/DelayedDeconstructionActor.scala
#	pslogin/src/main/scala/WorldSessionActor.scala
This commit is contained in:
Mazo 2018-06-12 17:30:38 +01:00
commit 5e1a244123
111 changed files with 5465 additions and 2896 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

@ -17,6 +17,8 @@ import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition
import net.psforever.objects.vehicles.{SeatArmorRestriction, UtilityType}
import net.psforever.types.PlanetSideEmpire
import scala.concurrent.duration._
object GlobalDefinitions {
/*
Implants
@ -2723,6 +2725,7 @@ object GlobalDefinitions {
ams.Deployment = true
ams.DeployTime = 2000
ams.UndeployTime = 2000
ams.DeconstructionTime = Some(20 minutes)
ams.AutoPilotSpeeds = (18, 6)
ams.Packet = utilityConverter
@ -2735,6 +2738,7 @@ object GlobalDefinitions {
router.Deployment = true
router.DeployTime = 2000
router.UndeployTime = 2000
router.DeconstructionTime = Duration(20, "minutes")
router.AutoPilotSpeeds = (16, 6)
router.Packet = variantConverter

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
@ -129,7 +129,12 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio
def MaxArmor : Int = exosuit.MaxArmor
def VisibleSlots : Set[Int] = if(exosuit.SuitType == ExoSuitType.MAX) { Set(0) } else { Set(0,1,2,3,4) }
def VisibleSlots : Set[Int] = if(exosuit.SuitType == ExoSuitType.MAX) {
Set(0)
}
else {
(0 to 4).filterNot(index => holsters(index).Size == EquipmentSize.Blocked).toSet
}
override def Slot(slot : Int) : EquipmentSlot = {
if(inventory.Offset <= slot && slot <= inventory.LastIndex) {

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

@ -6,6 +6,7 @@ import net.psforever.objects.inventory.InventoryTile
import net.psforever.objects.vehicles.UtilityType
import scala.collection.mutable
import scala.concurrent.duration._
/**
* An object definition system used to construct and retain the parameters of various vehicles.
@ -29,6 +30,7 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) {
private var canCloak : Boolean = false
private var canBeOwned : Boolean = true
private var serverVehicleOverrideSpeeds : (Int, Int) = (0, 0)
private var deconTime : Option[FiniteDuration] = None
private var maximumCapacitor : Int = 0
Name = "vehicle"
Packet = VehicleDefinition.converter
@ -84,6 +86,18 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) {
DeployTime
}
def DeconstructionTime : Option[FiniteDuration] = deconTime
def DeconstructionTime_=(time : FiniteDuration) : Option[FiniteDuration] = {
deconTime_=(Some(time))
DeconstructionTime
}
def DeconstructionTime_=(time : Option[FiniteDuration]) : Option[FiniteDuration] = {
deconTime = time
DeconstructionTime
}
def UndeployTime : Int = deploymentTime_Undeploy
def UndeployTime_=(dtime : Int) : Int = {

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))
}
@ -200,12 +249,12 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
*/
private def MakeInventory(obj : Player) : List[InternalSlot] = {
obj.Inventory.Items
.map({
case(_, item) =>
.map(item => {
val equip : Equipment = item.obj
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 +300,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 +338,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()
)
}
@ -62,11 +73,10 @@ class CorpseConverter extends AvatarConverter {
*/
private def MakeInventory(obj : Player) : List[InternalSlot] = {
obj.Inventory.Items
.map({
case(_, item) =>
.map(item => {
val equip : Equipment = item.obj
BuildEquipment(item.start, equip)
}).toList
})
}
/**

View file

@ -30,11 +30,10 @@ class LockerContainerConverter extends ObjectCreateConverter[LockerContainer]()
*/
private def MakeInventory(inv : GridInventory) : List[InternalSlot] = {
inv.Items
.map({
case(_, item) =>
.map(item => {
val equip : Equipment = item.obj
InternalSlot(equip.Definition.ObjectId, equip.GUID, item.start, equip.Definition.Packet.ConstructorData(equip).get)
}).toList
})
}
/**
@ -45,10 +44,9 @@ class LockerContainerConverter extends ObjectCreateConverter[LockerContainer]()
*/
private def MakeDetailedInventory(inv : GridInventory) : List[InternalSlot] = {
inv.Items
.map({
case(_, item) =>
.map(item => {
val equip : Equipment = item.obj
InternalSlot(equip.Definition.ObjectId, equip.GUID, item.start, equip.Definition.Packet.DetailedConstructorData(equip).get)
}).toList
})
}
}

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

@ -100,7 +100,7 @@ object GUIDTask {
* @return a list of `TaskResolver.GiveTask` messages
*/
def RegisterInventory(container : Container)(implicit guid : ActorRef) : List[TaskResolver.GiveTask] = {
container.Inventory.Items.values.map(entry => { RegisterEquipment(entry.obj)}).toList
container.Inventory.Items.map(entry => { RegisterEquipment(entry.obj)})
}
/**
@ -257,7 +257,7 @@ object GUIDTask {
* @return a list of `TaskResolver.GiveTask` messages
*/
def UnregisterInventory(container : Container)(implicit guid : ActorRef) : List[TaskResolver.GiveTask] = {
container.Inventory.Items.values.map(entry => { UnregisterEquipment(entry.obj)}).toList
container.Inventory.Items.map(entry => { UnregisterEquipment(entry.obj)})
}
/**

View file

@ -8,7 +8,6 @@ import net.psforever.objects.EquipmentSlot
import net.psforever.packet.game.PlanetSideGUID
import scala.annotation.tailrec
import scala.collection.immutable.Map
import scala.collection.mutable
import scala.util.{Failure, Success, Try}
@ -38,7 +37,7 @@ class GridInventory extends Container {
private val entryIndex : AtomicInteger = new AtomicInteger(0)
private var grid : Array[Int] = Array.fill[Int](1)(-1)
def Items : Map[Int, InventoryItem] = items.toMap[Int, InventoryItem]
def Items : List[InventoryItem] = items.values.toList
def Width : Int = width
@ -331,26 +330,23 @@ class GridInventory extends Container {
def Insertion_CheckCollisions(start : Int, obj : Equipment, key : Int) : Boolean = {
CheckCollisions(start, obj) match {
case Success(Nil) =>
val card = InventoryItem(obj, start)
items += key -> card
val tile = obj.Tile
SetCells(start, tile.Width, tile.Height, key)
true
InsertQuickly(start, obj, key)
case _ =>
false
}
}
def +=(kv : (Int, Equipment)) : Boolean = Insert(kv._1, kv._2)
def InsertQuickly(start : Int, obj : Equipment) : Boolean = InsertQuickly(start, obj, entryIndex.getAndIncrement())
// def InsertQuickly(start : Int, obj : Equipment) : Boolean = {
// val guid : Int = obj.GUID.guid
// val card = InventoryItemData(obj, start)
// items += guid -> card
// val tile = obj.Tile
// SetCellsOffset(start, tile.width, tile.height, guid)
// true
// }
private def InsertQuickly(start : Int, obj : Equipment, key : Int) : Boolean = {
val card = InventoryItem(obj, start)
items += key -> card
val tile = obj.Tile
SetCells(start, tile.Width, tile.Height, key)
true
}
def +=(kv : (Int, Equipment)) : Boolean = Insert(kv._1, kv._2)
def Remove(index : Int) : Boolean = {
val key = grid(index - Offset)

View file

@ -34,7 +34,7 @@ class InventoryEquipmentSlot(private val slot : Int, private val inv : GridInven
case Some(equip) =>
val tile = equip.Definition.Tile
inv.CheckCollisionsVar(slot, tile.Width, tile.Height) match {
case Success(Nil) => inv += slot -> equip
case Success(Nil) => inv.InsertQuickly(slot, equip)
case _ => ;
}

View file

@ -26,14 +26,14 @@ object InventoryTile {
final val Tile33 = InventoryTile(3,3) //ammo box, pistols, ace
final val Tile44 = InventoryTile(4,4) //large ammo box
final val Tile55 = InventoryTile(5,5) //bfr ammo box
final val Tile66 = InventoryTile(6,6) //standard assault inventory
final val Tile66 = InventoryTile(6,6) //infiltration suit inventory
final val Tile63 = InventoryTile(6,3) //rifles
final val Tile93 = InventoryTile(9,3) //long-body weapons
final val Tile96 = InventoryTile(9,6) //standard exo-suit
final val Tile99 = InventoryTile(9,9) //agile exo-suit
final val Tile96 = InventoryTile(9,6) //standard exo-suit inventory
final val Tile99 = InventoryTile(9,9) //agile exo-suit inventory
final val Tile1107 = InventoryTile(11, 7) //uncommon small trunk capacity - phantasm
final val Tile1111 = InventoryTile(11,11) //common small trunk capacity
final val Tile1209 = InventoryTile(12, 9) //reinforced exo-suit
final val Tile1209 = InventoryTile(12, 9) //reinforced exo-suit inventory
final val Tile1511 = InventoryTile(15,11) //common medium trunk capacity
final val Tile1515 = InventoryTile(15,15) //common large trunk capacity
final val Tile1611 = InventoryTile(16,11) //uncommon medium trunk capacity - vulture

View file

@ -39,7 +39,7 @@ object Loadout {
InfantryLoadout(
label,
packageSimplifications(player.Holsters()),
packageSimplifications(player.Inventory.Items.values.toList),
packageSimplifications(player.Inventory.Items),
player.ExoSuit,
DetermineSubtype(player)
)
@ -55,7 +55,7 @@ object Loadout {
VehicleLoadout(
label,
packageSimplifications(vehicle.Weapons.map({ case ((index, weapon)) => InventoryItem(weapon.Equipment.get, index) }).toList),
packageSimplifications(vehicle.Trunk.Items.values.toList),
packageSimplifications(vehicle.Trunk.Items),
vehicle.Definition
)
}

View file

@ -0,0 +1,49 @@
package net.psforever.objects.serverobject.hackable
import net.psforever.objects.Player
import net.psforever.packet.game.{PlanetSideGUID, TriggeredSound}
import net.psforever.types.Vector3
trait Hackable {
/** An entry that maintains a reference to the `Player`, and the player's GUID and location when the message was received. */
private var hackedBy : Option[(Player, PlanetSideGUID, Vector3)] = None
private var hackSound : TriggeredSound.Value = TriggeredSound.HackDoor
def HackedBy : Option[(Player, PlanetSideGUID, Vector3)] = hackedBy
def HackedBy_=(agent : Player) : Option[(Player, PlanetSideGUID, Vector3)] = HackedBy_=(Some(agent))
/**
* Set the hack state of this object by recording important information about the player that caused it.
* Set the hack state if there is no current hack state.
* Override the hack state with a new hack state if the new user has different faction affiliation.
* @param agent a `Player`, or no player
* @return the player hack entry
*/
def HackedBy_=(agent : Option[Player]) : Option[(Player, PlanetSideGUID, Vector3)] = {
hackedBy match {
case None =>
//set the hack state if there is no current hack state
if(agent.isDefined) {
hackedBy = Some(agent.get, agent.get.GUID, agent.get.Position)
}
case Some(_) =>
//clear the hack state
if(agent.isEmpty) {
hackedBy = None
}
//override the hack state with a new hack state if the new user has different faction affiliation
else if(agent.get.Faction != hackedBy.get._1.Faction) {
hackedBy = Some(agent.get, agent.get.GUID, agent.get.Position)
}
}
HackedBy
}
def HackSound : TriggeredSound.Value = hackSound
def HackSound_=(sound : TriggeredSound.Value) : TriggeredSound.Value = {
hackSound = sound
hackSound
}
}

View file

@ -1,10 +1,9 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.locks
import net.psforever.objects.Player
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.Vector3
import net.psforever.packet.game.TriggeredSound
/**
* A structure-owned server object that is a "door lock."<br>
@ -15,44 +14,9 @@ import net.psforever.types.Vector3
* The `IFFLock` is ideally associated with a server map object - a `Door` - to which it acts as a gatekeeper.
* @param idef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/
class IFFLock(private val idef : IFFLockDefinition) extends Amenity {
/**
* An entry that maintains a reference to the `Player`, and the player's GUID and location when the message was received.
*/
private var hackedBy : Option[(Player, PlanetSideGUID, Vector3)] = None
def HackedBy : Option[(Player, PlanetSideGUID, Vector3)] = hackedBy
def HackedBy_=(agent : Player) : Option[(Player, PlanetSideGUID, Vector3)] = HackedBy_=(Some(agent))
/**
* Set the hack state of this object by recording important information about the player that caused it.
* Set the hack state if there is no current hack state.
* Override the hack state with a new hack state if the new user has different faction affiliation.
* @param agent a `Player`, or no player
* @return the player hack entry
*/
def HackedBy_=(agent : Option[Player]) : Option[(Player, PlanetSideGUID, Vector3)] = {
hackedBy match {
case None =>
//set the hack state if there is no current hack state
if(agent.isDefined) {
hackedBy = Some(agent.get, agent.get.GUID, agent.get.Position)
}
case Some(_) =>
//clear the hack state
if(agent.isEmpty) {
hackedBy = None
}
//override the hack state with a new hack state if the new user has different faction affiliation
else if(agent.get.Faction != hackedBy.get._1.Faction) {
hackedBy = Some(agent.get, agent.get.GUID, agent.get.Position)
}
}
HackedBy
}
class IFFLock(private val idef : IFFLockDefinition) extends Amenity with Hackable {
def Definition : IFFLockDefinition = idef
HackSound = TriggeredSound.HackDoor
}
object IFFLock {

View file

@ -16,7 +16,7 @@ class IFFLockControl(lock : IFFLock) extends Actor with FactionAffinityBehavior.
def receive : Receive = checkBehavior.orElse {
case CommonMessages.Hack(player) =>
lock.HackedBy = player
sender ! true
case CommonMessages.ClearHack() =>
lock.HackedBy = None

View file

@ -3,10 +3,13 @@ package net.psforever.objects.serverobject.mblocker
import akka.actor.{ActorContext, Props}
import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.TriggeredSound
class Locker extends Amenity {
class Locker extends Amenity with Hackable {
def Definition : LockerDefinition = GlobalDefinitions.mb_locker
HackSound = TriggeredSound.HackTerminal
}
object Locker {

View file

@ -2,6 +2,7 @@
package net.psforever.objects.serverobject.mblocker
import akka.actor.Actor
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
/**
@ -12,6 +13,11 @@ class LockerControl(locker : Locker) extends Actor with FactionAffinityBehavior.
def FactionObject : FactionAffinity = locker
def receive : Receive = checkBehavior.orElse {
case CommonMessages.Hack(player) =>
locker.HackedBy = player
sender ! true
case CommonMessages.ClearHack() =>
locker.HackedBy = None
case _ => ;
}
}

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

@ -2,6 +2,7 @@
package net.psforever.objects.serverobject.terminals
import akka.actor.Actor
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
/**
@ -18,6 +19,11 @@ class ProximityTerminalControl(term : Terminal with ProximityUnit) extends Actor
def receive : Receive = checkBehavior
.orElse(proximityBehavior)
.orElse {
case CommonMessages.Hack(player) =>
term.HackedBy = player
sender ! true
case CommonMessages.ClearHack() =>
term.HackedBy = None
case _ => ;
}

View file

@ -3,48 +3,17 @@ package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player
import net.psforever.objects.definition.VehicleDefinition
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
import net.psforever.types.{TransactionType, Vector3}
import net.psforever.packet.game.{ItemTransactionMessage, TriggeredSound}
import net.psforever.types.TransactionType
/**
* A structure-owned server object that is a "terminal" that can be accessed for amenities and services.
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/
class Terminal(tdef : TerminalDefinition) extends Amenity {
/** An entry that maintains a reference to the `Player`, and the player's GUID and location when the message was received. */
private var hackedBy : Option[(Player, PlanetSideGUID, Vector3)] = None
def HackedBy : Option[(Player, PlanetSideGUID, Vector3)] = hackedBy
def HackedBy_=(agent : Player) : Option[(Player, PlanetSideGUID, Vector3)] = HackedBy_=(Some(agent))
/**
* Set the hack state of this object by recording important information about the player that caused it.
* Set the hack state if there is no current hack state.
* Override the hack state with a new hack state if the new user has different faction affiliation.
* @param agent a `Player`, or no player
* @return the player hack entry
*/
def HackedBy_=(agent : Option[Player]) : Option[(Player, PlanetSideGUID, Vector3)] = {
hackedBy match {
case None =>
//set the hack state if there is no current hack state
if(agent.isDefined) {
hackedBy = Some(agent.get, agent.get.GUID, agent.get.Position)
}
case Some(_) =>
//clear the hack state
if(agent.isEmpty) {
hackedBy = None
}
//override the hack state with a new hack state if the new user has different faction affiliation
else if(agent.get.Faction != hackedBy.get._1.Faction) {
hackedBy = Some(agent.get, agent.get.GUID, agent.get.Position)
}
}
HackedBy
}
class Terminal(tdef : TerminalDefinition) extends Amenity with Hackable {
HackSound = TriggeredSound.HackTerminal
//the following fields and related methods are neither finalized nor integrated; GOTO Request
private var health : Int = 100 //TODO not real health value

View file

@ -2,6 +2,7 @@
package net.psforever.objects.serverobject.terminals
import akka.actor.Actor
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
/**
@ -15,6 +16,12 @@ class TerminalControl(term : Terminal) extends Actor with FactionAffinityBehavio
case Terminal.Request(player, msg) =>
sender ! Terminal.TerminalMessage(player, msg, term.Request(player, msg))
case CommonMessages.Hack(player) =>
term.HackedBy = player
sender ! true
case CommonMessages.ClearHack() =>
term.HackedBy = None
case _ => ;
}

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

@ -89,7 +89,7 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
if(accessor == ActorRef.noSender) {
implicit val guid : NumberPoolHub = this.guid //passed into builderObject.Build implicitly
accessor = context.actorOf(RandomPool(25).props(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystem.AllocateNumberPoolActors(guid))), s"$Id-uns")
ground = context.actorOf(Props(classOf[ZoneGroundActor], equipmentOnGround), s"$Id-ground")
ground = context.actorOf(Props(classOf[ZoneGroundActor], this, equipmentOnGround), s"$Id-ground")
transport = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"$Id-vehicles")
population = context.actorOf(Props(classOf[ZonePopulationActor], this, players, corpses), s"$Id-players")
@ -386,27 +386,17 @@ object Zone {
final case class NoValidSpawnPoint(zone_number : Int, spawn_group : Option[Int])
}
/**
* Message to relinguish an item and place in on the ground.
* @param item the piece of `Equipment`
* @param pos where it is dropped
* @param orient in which direction it is facing when dropped
*/
final case class DropItemOnGround(item : Equipment, pos : Vector3, orient : Vector3)
object Ground {
final case class DropItem(item : Equipment, pos : Vector3, orient : Vector3)
final case class ItemOnGround(item : Equipment, pos : Vector3, orient : Vector3)
final case class CanNotDropItem(zone : Zone, item : Equipment, reason : String)
/**
* Message to attempt to acquire an item from the ground (before somoene else?).
* @param player who wants the piece of `Equipment`
* @param item_guid the unique identifier of the piece of `Equipment`
*/
final case class GetItemOnGround(player : Player, item_guid : PlanetSideGUID)
final case class PickupItem(item_guid : PlanetSideGUID)
final case class ItemInHand(item : Equipment)
final case class CanNotPickupItem(zone : Zone, item_guid : PlanetSideGUID, reason : String)
/**
* Message to give an item from the ground to a specific user.
* @param player who wants the piece of `Equipment`
* @param item the piece of `Equipment`
*/
final case class ItemFromGround(player : Player, item : Equipment)
final case class RemoveItem(item_guid : PlanetSideGUID)
}
object Vehicle {
final case class Spawn(vehicle : Vehicle)

View file

@ -50,10 +50,10 @@ class ZoneActor(zone : Zone) extends Actor {
zone.Population forward msg
//frwd to Ground Actor
case msg @ Zone.DropItemOnGround =>
case msg @ Zone.Ground.DropItem =>
zone.Ground forward msg
case msg @ Zone.GetItemOnGround =>
case msg @ Zone.Ground.PickupItem =>
zone.Ground forward msg
//frwd to Vehicle Actor

View file

@ -12,22 +12,35 @@ import scala.collection.mutable.ListBuffer
* na
* @param equipmentOnGround a `List` of items (`Equipment`) dropped by players on the ground and can be collected again
*/
class ZoneGroundActor(equipmentOnGround : ListBuffer[Equipment]) extends Actor {
class ZoneGroundActor(zone : Zone, equipmentOnGround : ListBuffer[Equipment]) extends Actor {
//private[this] val log = org.log4s.getLogger
def receive : Receive = {
case Zone.DropItemOnGround(item, pos, orient) =>
item.Position = pos
item.Orientation = orient
equipmentOnGround += item
case Zone.GetItemOnGround(player, item_guid) =>
FindItemOnGround(item_guid) match {
case Some(item) =>
sender ! Zone.ItemFromGround(player, item)
case None =>
org.log4s.getLogger.warn(s"item on ground $item_guid was requested by $player for pickup but was not found")
case Zone.Ground.DropItem(item, pos, orient) =>
sender ! (if(!item.HasGUID) {
Zone.Ground.CanNotDropItem(zone, item, "not registered yet")
}
else if(zone.GUID(item.GUID).isEmpty) {
Zone.Ground.CanNotDropItem(zone, item, "registered to some other zone")
}
else if(equipmentOnGround.contains(item)) {
Zone.Ground.CanNotDropItem(zone, item, "already dropped")
}
else {
equipmentOnGround += item
Zone.Ground.ItemOnGround(item, pos, orient)
})
case Zone.Ground.PickupItem(item_guid) =>
sender ! (FindItemOnGround(item_guid) match {
case Some(item) =>
Zone.Ground.ItemInHand(item)
case None =>
Zone.Ground.CanNotPickupItem(zone, item_guid, "can not find")
})
case Zone.Ground.RemoveItem(item_guid) =>
FindItemOnGround(item_guid) //intentionally no callback
case _ => ;
}

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

@ -19,13 +19,18 @@ final case class ActionResultMessage(successful : Boolean,
}
object ActionResultMessage extends Marshallable[ActionResultMessage] {
def apply() : ActionResultMessage = {
ActionResultMessage(true, None)
}
/**
* A message where the result is always a pass.
* @return an `ActionResultMessage` object
*/
def Pass : ActionResultMessage = ActionResultMessage(true, None)
def apply(error : Long) : ActionResultMessage = {
ActionResultMessage(false, Some(error))
}
/**
* A message where the result is always a failure.
* @param error the error code
* @return an `ActionResultMessage` object
*/
def Fail(error : Long) : ActionResultMessage = ActionResultMessage(false, Some(error))
implicit val codec : Codec[ActionResultMessage] = (
("successful" | bool) >>:~ { res =>

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

@ -40,6 +40,14 @@ final case class ObjectDetachMessage(parent_guid : PlanetSideGUID,
}
object ObjectDetachMessage extends Marshallable[ObjectDetachMessage] {
def apply(parent_guid : PlanetSideGUID, child_guid : PlanetSideGUID, pos : Vector3, orient : Vector3) : ObjectDetachMessage = {
ObjectDetachMessage(parent_guid, child_guid, pos, orient.x, orient.y, orient.z)
}
def apply(parent_guid : PlanetSideGUID, child_guid : PlanetSideGUID, pos : Vector3, orient_z : Float) : ObjectDetachMessage = {
ObjectDetachMessage(parent_guid, child_guid, pos, 0, 0, orient_z)
}
implicit val codec : Codec[ObjectDetachMessage] = (
("parent_guid" | PlanetSideGUID.codec) ::
("child_guid" | PlanetSideGUID.codec) ::

View file

@ -124,6 +124,7 @@ import scodec.codecs._
* `77 - Cavern Facility Captures. Value is the number of captures`<br>
* `78 - Cavern Kills. Value is the number of kills`<br>
* `106 - Custom Head`<br>
* `116 - Apply colour to REK beam and REK icon above players (0 = yellow, 1 = red, 2 = purple, 3 = blue)`<br>
* Client to Server : <br>
* `106 - Custom Head`<br>
* <br>
@ -132,7 +133,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>
* `54 - Vehicle EMP? Plays sound as if vehicle had been hit by EMP`<br>
* `68 - Vehicle shield health`<br>

View file

@ -16,8 +16,8 @@ object TriggeredSound extends Enumeration {
val
SpawnInTube,
Unknown1,
Hack,
HackTerminal,
HackVehicle,
HackDoor,
Unknown4,
LockedOut,

View file

@ -9,7 +9,7 @@ import scodec.codecs._
/**
* (Where the child object was before it was moved is not specified or important.)<br>
* @param avatar_guid the player.
* @param unk1 dont know how call that. It's the "item" ID when use a rek to hack or a medkit to heal.
* @param item_used_guid The "item" GUID used e.g. a rek to hack or a medkit to heal.
* @param object_guid can be : Door, Terminal, Avatar (medkit).
* @param unk2 ???
* @param unk3 ??? true when use a rek (false when door, medkit or open equip term)
@ -21,7 +21,7 @@ import scodec.codecs._
* @param itemType object ID from game_objects.adb (ex 612 is an equipment terminal, for medkit we have 121 (avatar))
*/
final case class UseItemMessage(avatar_guid : PlanetSideGUID,
unk1 : Int,
item_used_guid : Int,
object_guid : PlanetSideGUID,
unk2 : Long,
unk3 : Boolean,
@ -40,7 +40,7 @@ final case class UseItemMessage(avatar_guid : PlanetSideGUID,
object UseItemMessage extends Marshallable[UseItemMessage] {
implicit val codec : Codec[UseItemMessage] = (
("avatar_guid" | PlanetSideGUID.codec) ::
("unk1" | uint16L) ::
("item_used_guid" | uint16L) ::
("object_guid" | PlanetSideGUID.codec) ::
("unk2" | uint32L) ::
("unk3" | bool) ::

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. Valid values seem to be 0-3. Anything higher spawns a completely broken NC vehicle with no guns that can't move
* @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

@ -1,12 +1,15 @@
// Copyright (c) 2017 PSForever
package services.avatar
import net.psforever.objects.Player
import net.psforever.objects.{PlanetSideGameObject, Player}
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.types.{ExoSuitType, Vector3}
import net.psforever.packet.game.objectcreate.{ConstructorData, ObjectCreateMessageParent}
import net.psforever.types.ExoSuitType
import scala.concurrent.duration.FiniteDuration
object AvatarAction {
trait Action
@ -17,21 +20,19 @@ object AvatarAction {
final case class ChangeFireState_Start(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
final case class ChangeFireState_Stop(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
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 EquipmentOnGround(player_guid : PlanetSideGUID, pos : Vector3, orient : Vector3, item_id : Int, item_guid : PlanetSideGUID, item_data : ConstructorData) extends Action
final case class LoadPlayer(player_guid : PlanetSideGUID, pdata : ConstructorData) extends Action
// final case class LoadMap(msg : PlanetSideGUID) extends Action
// final case class unLoadMap(msg : PlanetSideGUID) 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
final case class PlayerState(player_guid : PlanetSideGUID, msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Action
final case class Release(player : Player, zone : Zone, time : Option[Long] = None) extends Action
final case class PickupItem(player_guid : PlanetSideGUID, zone : Zone, target : PlanetSideGameObject with Container, slot : Int, item : Equipment, unk : Int = 0) extends Action
final case class Release(player : Player, zone : Zone, time : Option[FiniteDuration] = None) extends Action
final case class Reload(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
final case class StowEquipment(player_guid : PlanetSideGUID, target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action
final case class WeaponDryFire(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
// final case class PlayerStateShift(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
// final case class DestroyDisplay(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
// final case class HitHintReturn(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
// final case class ChangeWeapon(unk1 : Int, sessionId : Long) extends Action
}

View file

@ -3,9 +3,9 @@ package services.avatar
import net.psforever.objects.Player
import net.psforever.objects.equipment.Equipment
import net.psforever.packet.game.{PlanetSideGUID, PlayerStateMessageUpstream}
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID, PlayerStateMessageUpstream}
import net.psforever.packet.game.objectcreate.ConstructorData
import net.psforever.types.{ExoSuitType, Vector3}
import net.psforever.types.ExoSuitType
object AvatarResponse {
trait Response
@ -16,11 +16,9 @@ object AvatarResponse {
final case class ChangeFireState_Start(weapon_guid : PlanetSideGUID) extends Response
final case class ChangeFireState_Stop(weapon_guid : PlanetSideGUID) extends Response
final case class ConcealPlayer() extends Response
final case class EquipmentInHand(target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Response
final case class EquipmentOnGround(pos : Vector3, orient : Vector3, item_id : Int, item_guid : PlanetSideGUID, item_data : ConstructorData) extends Response
final case class LoadPlayer(pdata : ConstructorData) extends Response
// final case class unLoadMap() extends Response
// final case class LoadMap() extends Response
final case class EquipmentInHand(pkt : ObjectCreateMessage) extends Response
final case class DropItem(pkt : ObjectCreateMessage) 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
@ -32,5 +30,4 @@ object AvatarResponse {
// final case class PlayerStateShift(itemID : PlanetSideGUID) extends Response
// final case class DestroyDisplay(itemID : PlanetSideGUID) extends Response
// final case class HitHintReturn(itemID : PlanetSideGUID) extends Response
// final case class ChangeWeapon(facingYaw : Int) extends Response
}

View file

@ -2,12 +2,15 @@
package services.avatar
import akka.actor.{Actor, ActorRef, Props}
import services.{GenericEventBus, Service}
import services.avatar.support.CorpseRemovalActor
import net.psforever.packet.game.ObjectCreateMessage
import net.psforever.packet.game.objectcreate.{DroppedItemData, ObjectCreateMessageParent, PlacementData}
import services.avatar.support.{CorpseRemovalActor, DroppedItemRemover}
import services.{GenericEventBus, RemoverActor, Service}
class AvatarService extends Actor {
private val undertaker : ActorRef = context.actorOf(Props[CorpseRemovalActor], "corpse-removal-agent")
undertaker ! "startup"
private val janitor = context.actorOf(Props[DroppedItemRemover], "item-remover-agent")
//undertaker ! "startup"
private [this] val log = org.log4s.getLogger
@ -62,17 +65,36 @@ class AvatarService extends Actor {
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ConcealPlayer())
)
case AvatarAction.EquipmentInHand(player_guid, target_guid, slot, obj) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.EquipmentInHand(target_guid, slot, obj))
case AvatarAction.DropItem(player_guid, item, zone) =>
val definition = item.Definition
val objectData = DroppedItemData(
PlacementData(item.Position, item.Orientation),
definition.Packet.ConstructorData(item).get
)
case AvatarAction.EquipmentOnGround(player_guid, pos, orient, item_id, item_guid, item_data) =>
janitor forward RemoverActor.AddTask(item, zone)
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.EquipmentOnGround(pos, orient, item_id, item_guid, item_data))
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid,
AvatarResponse.DropItem(ObjectCreateMessage(definition.ObjectId, item.GUID, objectData))
)
)
case AvatarAction.LoadPlayer(player_guid, pdata) =>
case AvatarAction.EquipmentInHand(player_guid, target_guid, slot, item) =>
val definition = item.Definition
val containerData = ObjectCreateMessageParent(target_guid, slot)
val objectData = definition.Packet.ConstructorData(item).get
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.LoadPlayer(pdata))
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid,
AvatarResponse.EquipmentInHand(ObjectCreateMessage(definition.ObjectId, item.GUID, containerData, objectData))
)
)
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(pkt))
)
case AvatarAction.ObjectDelete(player_guid, item_guid, unk) =>
AvatarEvents.publish(
@ -90,11 +112,24 @@ class AvatarService extends Actor {
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarResponse.PlayerState(msg, spectator, weapon))
)
case AvatarAction.PickupItem(player_guid, zone, target, slot, item, unk) =>
janitor forward RemoverActor.ClearSpecific(List(item), zone)
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, {
val itemGUID = item.GUID
if(target.VisibleSlots.contains(slot)) {
val definition = item.Definition
val containerData = ObjectCreateMessageParent(target.GUID, slot)
val objectData = definition.Packet.ConstructorData(item).get
AvatarResponse.EquipmentInHand(ObjectCreateMessage(definition.ObjectId, itemGUID, containerData, objectData))
}
else {
AvatarResponse.ObjectDelete(itemGUID, unk)
}
})
)
case AvatarAction.Release(player, zone, time) =>
undertaker ! (time match {
case Some(t) => CorpseRemovalActor.AddCorpse(player, zone, t)
case None => CorpseRemovalActor.AddCorpse(player, zone)
})
undertaker forward RemoverActor.AddTask(player, zone, time)
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player.GUID, AvatarResponse.Release(player))
)
@ -115,52 +150,13 @@ class AvatarService extends Actor {
}
//message to Undertaker
case AvatarServiceMessage.RemoveSpecificCorpse(corpses) =>
undertaker ! AvatarServiceMessage.RemoveSpecificCorpse( corpses.filter(corpse => {corpse.HasGUID && corpse.isBackpack}) )
case AvatarServiceMessage.Corpse(msg) =>
undertaker forward msg
case AvatarServiceMessage.Ground(msg) =>
janitor forward msg
/*
case AvatarService.PlayerStateMessage(msg) =>
// log.info(s"NEW: ${m}")
val playerOpt: Option[PlayerAvatar] = PlayerMasterList.getPlayer(msg.avatar_guid)
if (playerOpt.isDefined) {
val player: PlayerAvatar = playerOpt.get
AvatarEvents.publish(AvatarMessage("/Avatar/" + player.continent, msg.avatar_guid,
AvatarServiceReply.PlayerStateMessage(msg.pos, msg.vel, msg.facingYaw, msg.facingPitch, msg.facingYawUpper, msg.is_crouching, msg.is_jumping, msg.jump_thrust, msg.is_cloaked)
))
}
case AvatarService.LoadMap(msg) =>
val playerOpt: Option[PlayerAvatar] = PlayerMasterList.getPlayer(msg.guid)
if (playerOpt.isDefined) {
val player: PlayerAvatar = playerOpt.get
AvatarEvents.publish(AvatarMessage("/Avatar/" + player.continent, PlanetSideGUID(msg.guid),
AvatarServiceReply.LoadMap()
))
}
case AvatarService.unLoadMap(msg) =>
val playerOpt: Option[PlayerAvatar] = PlayerMasterList.getPlayer(msg.guid)
if (playerOpt.isDefined) {
val player: PlayerAvatar = playerOpt.get
AvatarEvents.publish(AvatarMessage("/Avatar/" + player.continent, PlanetSideGUID(msg.guid),
AvatarServiceReply.unLoadMap()
))
}
case AvatarService.ObjectHeld(msg) =>
val playerOpt: Option[PlayerAvatar] = PlayerMasterList.getPlayer(msg.guid)
if (playerOpt.isDefined) {
val player: PlayerAvatar = playerOpt.get
AvatarEvents.publish(AvatarMessage("/Avatar/" + player.continent, PlanetSideGUID(msg.guid),
AvatarServiceReply.ObjectHeld()
))
}
case AvatarService.PlanetsideAttribute(guid, attribute_type, attribute_value) =>
val playerOpt: Option[PlayerAvatar] = PlayerMasterList.getPlayer(guid)
if (playerOpt.isDefined) {
val player: PlayerAvatar = playerOpt.get
AvatarEvents.publish(AvatarMessage("/Avatar/" + player.continent, guid,
AvatarServiceReply.PlanetSideAttribute(attribute_type, attribute_value)
))
}
case AvatarService.PlayerStateShift(killer, guid) =>
val playerOpt: Option[PlayerAvatar] = PlayerMasterList.getPlayer(guid)
if (playerOpt.isDefined) {
@ -185,16 +181,8 @@ class AvatarService extends Actor {
AvatarServiceReply.DestroyDisplay(source_guid)
))
}
case AvatarService.ChangeWeapon(unk1, sessionId) =>
val playerOpt: Option[PlayerAvatar] = PlayerMasterList.getPlayer(sessionId)
if (playerOpt.isDefined) {
val player: PlayerAvatar = playerOpt.get
AvatarEvents.publish(AvatarMessage("/Avatar/" + player.continent, PlanetSideGUID(player.guid),
AvatarServiceReply.ChangeWeapon(unk1)
))
}
*/
case msg =>
log.info(s"Unhandled message $msg from $sender")
log.warn(s"Unhandled message $msg from $sender")
}
}

View file

@ -1,10 +1,9 @@
// Copyright (c) 2017 PSForever
package services.avatar
import net.psforever.objects.Player
final case class AvatarServiceMessage(forChannel : String, actionMessage : AvatarAction.Action)
object AvatarServiceMessage {
final case class RemoveSpecificCorpse(corpse : List[Player])
final case class Corpse(msg : Any)
final case class Ground(msg : Any)
}

View file

@ -1,245 +1,33 @@
// Copyright (c) 2017 PSForever
package services.avatar.support
import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.objects.guid.TaskResolver
import net.psforever.objects.{DefaultCancellable, Player}
import net.psforever.objects.zones.Zone
import net.psforever.types.Vector3
import services.{Service, ServiceManager}
import services.ServiceManager.Lookup
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
import net.psforever.objects.Player
import services.{RemoverActor, Service}
import services.avatar.{AvatarAction, AvatarServiceMessage}
import scala.annotation.tailrec
import scala.concurrent.duration._
class CorpseRemovalActor extends Actor {
private var burial : Cancellable = DefaultCancellable.obj
private var corpses : List[CorpseRemovalActor.Entry] = List()
class CorpseRemovalActor extends RemoverActor {
final val FirstStandardDuration : FiniteDuration = 3 minutes
private var decomposition : Cancellable = DefaultCancellable.obj
private var buriedCorpses : List[CorpseRemovalActor.Entry] = List()
final val SecondStandardDuration : FiniteDuration = 500 milliseconds
private var taskResolver : ActorRef = Actor.noSender
private[this] val log = org.log4s.getLogger
override def postStop() = {
//Cart Master: See you on Thursday.
super.postStop()
burial.cancel
decomposition.cancel
corpses.foreach(corpse => {
BurialTask(corpse)
LastRitesTask(corpse)
})
buriedCorpses.foreach { LastRitesTask }
def InclusionTest(entry : RemoverActor.Entry) : Boolean = {
entry.obj.isInstanceOf[Player] && entry.obj.asInstanceOf[Player].isBackpack
}
def receive : Receive = {
case "startup" =>
ServiceManager.serviceManager ! Lookup("taskResolver") //ask for a resolver to deal with the GUID system
def InitialJob(entry : RemoverActor.Entry) : Unit = { }
case ServiceManager.LookupResult("taskResolver", endpoint) =>
//Cart Master: Bring out your dead!
taskResolver = endpoint
context.become(Processing)
case _ => ;
def FirstJob(entry : RemoverActor.Entry) : Unit = {
import net.psforever.objects.zones.Zone
entry.zone.Population ! Zone.Corpse.Remove(entry.obj.asInstanceOf[Player])
context.parent ! AvatarServiceMessage(entry.zone.Id, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, entry.obj.GUID))
}
def Processing : Receive = {
case CorpseRemovalActor.AddCorpse(corpse, zone, time) =>
import CorpseRemovalActor.SimilarCorpses
if(corpse.isBackpack && !buriedCorpses.exists(entry => SimilarCorpses(entry.corpse, corpse) )) {
if(corpses.isEmpty) {
//we were the only entry so the event must be started from scratch
corpses = List(CorpseRemovalActor.Entry(corpse, zone, time))
RetimeFirstTask()
}
else {
//unknown number of entries; append, sort, then re-time tasking
val oldHead = corpses.head
if(!corpses.exists(entry => SimilarCorpses(entry.corpse, corpse))) {
corpses = (corpses :+ CorpseRemovalActor.Entry(corpse, zone, time)).sortBy(_.timeAlive)
if(oldHead != corpses.head) {
RetimeFirstTask()
}
}
}
}
else {
//Cart Master: 'Ere. He says he's not dead!
log.warn(s"$corpse does not qualify as a corpse; ignored queueing request")
}
def ClearanceTest(entry : RemoverActor.Entry) : Boolean = !entry.zone.Corpses.contains(entry.obj)
case AvatarServiceMessage.RemoveSpecificCorpse(targets) =>
if(targets.nonEmpty) {
//Cart Master: No, I've got to go to the Robinsons'. They've lost nine today.
burial.cancel
if(targets.size == 1) {
log.debug(s"a target corpse submitted for early cleanup: ${targets.head}")
//simple selection
CorpseRemovalActor.recursiveFindCorpse(corpses.iterator, targets.head) match {
case None => ;
case Some(index) =>
decomposition.cancel
BurialTask(corpses(index))
buriedCorpses = buriedCorpses :+ corpses(index)
corpses = (corpses.take(index) ++ corpses.drop(index + 1)).sortBy(_.timeAlive)
import scala.concurrent.ExecutionContext.Implicits.global
decomposition = context.system.scheduler.scheduleOnce(500 milliseconds, self, CorpseRemovalActor.TryDelete())
}
}
else {
log.debug(s"multiple target corpses submitted for early cleanup: $targets")
import CorpseRemovalActor.SimilarCorpses
decomposition.cancel
//cumbersome partition
//a - find targets from corpses
val locatedTargets = for {
a <- targets
b <- corpses
if b.corpse.HasGUID && a.HasGUID && SimilarCorpses(b.corpse, a)
} yield b
if(locatedTargets.nonEmpty) {
decomposition.cancel
locatedTargets.foreach { BurialTask }
buriedCorpses = locatedTargets ++ buriedCorpses
import scala.concurrent.ExecutionContext.Implicits.global
decomposition = context.system.scheduler.scheduleOnce(500 milliseconds, self, CorpseRemovalActor.TryDelete())
//b - corpses, after the found targets are removed (cull any non-GUID entries while at it)
corpses = (for {
a <- locatedTargets
b <- corpses
if b.corpse.HasGUID && a.corpse.HasGUID && !SimilarCorpses(b.corpse, a.corpse)
} yield b).sortBy(_.timeAlive)
}
}
RetimeFirstTask()
}
case CorpseRemovalActor.StartDelete() =>
burial.cancel
decomposition.cancel
val now : Long = System.nanoTime
val (buried, rotting) = corpses.partition(entry => { now - entry.time >= entry.timeAlive })
corpses = rotting
buriedCorpses = buriedCorpses ++ buried
buried.foreach { BurialTask }
RetimeFirstTask()
if(buriedCorpses.nonEmpty) {
import scala.concurrent.ExecutionContext.Implicits.global
burial = context.system.scheduler.scheduleOnce(500 milliseconds, self, CorpseRemovalActor.TryDelete())
}
case CorpseRemovalActor.TryDelete() =>
decomposition.cancel
val (decomposed, rotting) = buriedCorpses.partition(entry => {
!entry.zone.Corpses.contains(entry.corpse)
})
buriedCorpses = rotting
decomposed.foreach { LastRitesTask }
if(rotting.nonEmpty) {
import scala.concurrent.ExecutionContext.Implicits.global
decomposition = context.system.scheduler.scheduleOnce(500 milliseconds, self, CorpseRemovalActor.TryDelete())
}
case CorpseRemovalActor.FailureToWork(target, zone, ex) =>
//Cart Master: Oh, I can't take him like that. It's against regulations.
log.error(s"corpse $target from $zone not properly unregistered - $ex")
case _ => ;
}
def RetimeFirstTask(now : Long = System.nanoTime) : Unit = {
//Cart Master: Thursday.
burial.cancel
if(corpses.nonEmpty) {
val short_timeout : FiniteDuration = math.max(1, corpses.head.timeAlive - (now - corpses.head.time)) nanoseconds
import scala.concurrent.ExecutionContext.Implicits.global
burial = context.system.scheduler.scheduleOnce(short_timeout, self, CorpseRemovalActor.StartDelete())
}
}
def BurialTask(entry : CorpseRemovalActor.Entry) : Unit = {
val target = entry.corpse
entry.zone.Population ! Zone.Corpse.Remove(target)
context.parent ! AvatarServiceMessage(entry.zone.Id, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, target.GUID))
}
def LastRitesTask(entry : CorpseRemovalActor.Entry) : Unit = {
//Cart master: Nine pence.
val target = entry.corpse
target.Position = Vector3.Zero //somewhere it will not disturb anything
taskResolver ! LastRitesTask(target, entry.zone)
}
def LastRitesTask(corpse : Player, zone : Zone) : TaskResolver.GiveTask = {
import net.psforever.objects.guid.{GUIDTask, Task}
TaskResolver.GiveTask (
new Task() {
private val localCorpse = corpse
private val localZone = zone
private val localAnnounce = self
override def isComplete : Task.Resolution.Value = if(!localCorpse.HasGUID) {
Task.Resolution.Success
}
else {
Task.Resolution.Incomplete
}
def Execute(resolver : ActorRef) : Unit = {
resolver ! scala.util.Success(this)
}
override def onFailure(ex : Throwable): Unit = {
localAnnounce ! CorpseRemovalActor.FailureToWork(localCorpse, localZone, ex)
}
}, List(GUIDTask.UnregisterPlayer(corpse)(zone.GUID))
)
}
}
object CorpseRemovalActor {
final val time : Long = 180000000000L //3 min (180s)
final case class AddCorpse(corpse : Player, zone : Zone, time : Long = CorpseRemovalActor.time)
final case class Entry(corpse : Player, zone : Zone, timeAlive : Long = CorpseRemovalActor.time, time : Long = System.nanoTime())
private final case class FailureToWork(corpse : Player, zone : Zone, ex : Throwable)
private final case class StartDelete()
private final case class TryDelete()
private def SimilarCorpses(obj1 : Player, obj2 : Player) : Boolean = {
obj1 == obj2 && obj1.Continent.equals(obj2.Continent) && obj1.GUID == obj2.GUID
}
/**
* A recursive function that finds and removes a specific player from a list of players.
* @param iter an `Iterator` of `CorpseRemovalActor.Entry` objects
* @param player the target `Player`
* @param index the index of the discovered `Player` object
* @return the index of the `Player` object in the list to be removed;
* `None`, otherwise
*/
@tailrec final def recursiveFindCorpse(iter : Iterator[CorpseRemovalActor.Entry], player : Player, index : Int = 0) : Option[Int] = {
if(!iter.hasNext) {
None
}
else {
val corpse = iter.next.corpse
if(corpse.HasGUID && player.HasGUID && SimilarCorpses(corpse, player)) {
Some(index)
}
else {
recursiveFindCorpse(iter, player, index + 1)
}
}
def DeletionTask(entry : RemoverActor.Entry) : TaskResolver.GiveTask = {
GUIDTask.UnregisterPlayer(entry.obj.asInstanceOf[Player])(entry.zone.GUID)
}
}

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

@ -4,14 +4,14 @@ package services.vehicle
import akka.actor.{Actor, ActorRef, Props}
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.objects.zones.Zone
import services.vehicle.support.VehicleRemover
import net.psforever.types.DriveState
import services.{GenericEventBus, Service}
import services.vehicle.support.{DeconstructionActor, DelayedDeconstructionActor}
import services.{GenericEventBus, RemoverActor, Service}
import scala.concurrent.duration._
class VehicleService extends Actor {
private val vehicleDecon : ActorRef = context.actorOf(Props[DeconstructionActor], "vehicle-decon-agent")
private val vehicleDelayedDecon : ActorRef = context.actorOf(Props[DelayedDeconstructionActor], "vehicle-delayed-decon-agent")
vehicleDecon ! DeconstructionActor.RequestTaskResolver
private val vehicleDecon : ActorRef = context.actorOf(Props[VehicleRemover], "vehicle-decon-agent")
private [this] val log = org.log4s.getLogger
override def preStart = {
@ -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))
@ -86,6 +86,11 @@ class VehicleService extends Actor {
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.StowEquipment(vehicle_guid, slot, definition.ObjectId, item.GUID, definition.Packet.DetailedConstructorData(item).get))
)
case VehicleAction.UnloadVehicle(player_guid, continent, vehicle) =>
vehicleDecon ! RemoverActor.ClearSpecific(List(vehicle), continent) //precaution
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.UnloadVehicle(vehicle.GUID))
)
case VehicleAction.UnstowEquipment(player_guid, item_guid) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.UnstowEquipment(item_guid))
@ -101,23 +106,9 @@ class VehicleService extends Actor {
case _ => ;
}
//message to DeconstructionActor
case VehicleServiceMessage.RequestDeleteVehicle(vehicle, continent) =>
vehicleDecon ! DeconstructionActor.RequestDeleteVehicle(vehicle, continent)
//message to DelayedDeconstructionActor
case VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, zone, timeAlive) =>
vehicleDelayedDecon ! DelayedDeconstructionActor.ScheduleDeconstruction(vehicle, zone, timeAlive)
//message to DelayedDeconstructionActor
case VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) =>
vehicleDelayedDecon ! DelayedDeconstructionActor.UnscheduleDeconstruction(vehicle_guid)
//response from DeconstructionActor
case DeconstructionActor.DeleteVehicle(vehicle_guid, zone_id) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/$zone_id/Vehicle", Service.defaultPlayerGUID, VehicleResponse.UnloadVehicle(vehicle_guid))
)
//message to VehicleRemover
case VehicleServiceMessage.Decon(msg) =>
vehicleDecon forward msg
//from VehicleSpawnControl
case VehicleSpawnPad.ConcealPlayer(player_guid, zone_id) =>
@ -158,13 +149,12 @@ class VehicleService extends Actor {
VehicleEvents.publish(
VehicleServiceResponse(s"/${zone.Id}/Vehicle", Service.defaultPlayerGUID, VehicleResponse.LoadVehicle(vehicle, vtype, vguid, vdata))
)
vehicleDelayedDecon ! DelayedDeconstructionActor.UnscheduleDeconstruction(vguid)
vehicleDelayedDecon ! DelayedDeconstructionActor.ScheduleDeconstruction(vehicle, zone, 600L) //10min
//avoid unattended vehicle spawning blocking the pad; user should mount (and does so normally) to reset decon timer
vehicleDecon forward RemoverActor.AddTask(vehicle, zone, Some(30 seconds))
//from VehicleSpawnControl
case VehicleSpawnPad.DisposeVehicle(vehicle, zone) =>
vehicleDelayedDecon ! DelayedDeconstructionActor.UnscheduleDeconstruction(vehicle.GUID)
vehicleDecon ! DeconstructionActor.RequestDeleteVehicle(vehicle, zone)
vehicleDecon forward RemoverActor.HurrySpecific(List(vehicle), zone)
//correspondence from WorldSessionActor
case VehicleServiceMessage.AMSDeploymentChange(zone) =>

View file

@ -3,16 +3,14 @@ package services.vehicle
import net.psforever.objects.Vehicle
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
final case class VehicleServiceMessage(forChannel : String, actionMessage : VehicleAction.Action)
object VehicleServiceMessage {
final case class DelayedVehicleDeconstruction(vehicle : Vehicle, continent : Zone, timeAlive : Long)
final case class GiveActorControl(vehicle : Vehicle, actorName : String)
final case class RevokeActorControl(vehicle : Vehicle)
final case class RequestDeleteVehicle(vehicle : Vehicle, continent : Zone)
final case class UnscheduleDeconstruction(vehicle_guid : PlanetSideGUID)
final case class Decon(msg : Any)
final case class AMSDeploymentChange(zone : Zone)
}

View file

@ -1,276 +0,0 @@
// Copyright (c) 2017 PSForever
package services.vehicle.support
import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.objects.{DefaultCancellable, GlobalDefinitions, Vehicle}
import net.psforever.objects.guid.TaskResolver
import net.psforever.objects.vehicles.Seat
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.Vector3
import services.ServiceManager
import services.ServiceManager.Lookup
import services.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.annotation.tailrec
import scala.concurrent.duration._
/**
* Manage a previously-functioning vehicle as it is being deconstructed.<br>
* <br>
* A reference to a vehicle should be passed to this object as soon as it is going to be cleaned-up from the game world.
* Once accepted, only a few seconds will remain before the vehicle is deleted.
* To ensure that no players are lost in the deletion, all occupants of the vehicle are kicked out.
* Furthermore, the vehicle is rendered "dead" and inaccessible right up to the point where it is removed.<br>
* <br>
* This `Actor` is intended to sit on top of the event system that handles broadcast messaging.
*/
class DeconstructionActor extends Actor {
/** The periodic `Executor` that scraps the next vehicle on the list */
private var scrappingProcess : Cancellable = DefaultCancellable.obj
/** A `List` of currently doomed vehicles */
private var vehicles : List[DeconstructionActor.VehicleEntry] = Nil
/** The periodic `Executor` that cleans up the next vehicle on the list */
private var heapEmptyProcess : Cancellable = DefaultCancellable.obj
/** A `List` of vehicles that have been removed from the game world and are awaiting deconstruction. */
private var vehicleScrapHeap : List[DeconstructionActor.VehicleEntry] = Nil
/** The manager that helps unregister the vehicle from its current GUID scope */
private var taskResolver : ActorRef = Actor.noSender
//private[this] val log = org.log4s.getLogger
override def postStop() : Unit = {
super.postStop()
scrappingProcess.cancel
heapEmptyProcess.cancel
vehicles.foreach(entry => {
RetirementTask(entry)
DestructionTask(entry)
})
vehicleScrapHeap.foreach { DestructionTask }
}
def receive : Receive = {
/*
ask for a resolver to deal with the GUID system
when the TaskResolver is finally delivered, switch over to a behavior that actually deals with submitted vehicles
*/
case DeconstructionActor.RequestTaskResolver =>
ServiceManager.serviceManager ! Lookup("taskResolver")
case ServiceManager.LookupResult("taskResolver", endpoint) =>
taskResolver = endpoint
context.become(Processing)
case _ => ;
}
def Processing : Receive = {
case DeconstructionActor.RequestDeleteVehicle(vehicle, zone, time) =>
if(!vehicles.exists(_.vehicle == vehicle) && !vehicleScrapHeap.exists(_.vehicle == vehicle)) {
vehicles = vehicles :+ DeconstructionActor.VehicleEntry(vehicle, zone, time)
vehicle.Actor ! Vehicle.PrepareForDeletion
//kick everyone out; this is a no-blocking manual form of MountableBehavior ! Mountable.TryDismount
vehicle.Definition.MountPoints.values.foreach(seat_num => {
val zone_id : String = zone.Id
val seat : Seat = vehicle.Seat(seat_num).get
seat.Occupant match {
case Some(tplayer) =>
seat.Occupant = None
tplayer.VehicleSeated = None
if(tplayer.HasGUID) {
context.parent ! VehicleServiceMessage(zone_id, VehicleAction.KickPassenger(tplayer.GUID, 4, false, vehicle.GUID))
}
case None => ;
}
})
if(vehicles.size == 1) {
//we were the only entry so the event must be started from scratch
import scala.concurrent.ExecutionContext.Implicits.global
scrappingProcess = context.system.scheduler.scheduleOnce(DeconstructionActor.timeout, self, DeconstructionActor.StartDeleteVehicle())
}
}
case DeconstructionActor.StartDeleteVehicle() =>
scrappingProcess.cancel
heapEmptyProcess.cancel
val now : Long = System.nanoTime
val (vehiclesToScrap, vehiclesRemain) = PartitionEntries(vehicles, now)
vehicles = vehiclesRemain
vehicleScrapHeap = vehicleScrapHeap ++ vehiclesToScrap //may include existing entries
vehiclesToScrap.foreach(entry => {
val vehicle = entry.vehicle
val zone = entry.zone
RetirementTask(entry)
if(vehicle.Definition == GlobalDefinitions.ams) {
import net.psforever.types.DriveState
vehicle.DeploymentState = DriveState.Mobile //internally undeployed //TODO this should be temporary?
context.parent ! VehicleServiceMessage.AMSDeploymentChange(zone)
}
taskResolver ! DeconstructionTask(vehicle, zone)
})
if(vehiclesRemain.nonEmpty) {
val short_timeout : FiniteDuration = math.max(1, DeconstructionActor.timeout_time - (now - vehiclesRemain.head.time)) nanoseconds
import scala.concurrent.ExecutionContext.Implicits.global
scrappingProcess = context.system.scheduler.scheduleOnce(short_timeout, self, DeconstructionActor.StartDeleteVehicle())
}
if(vehicleScrapHeap.nonEmpty) {
import scala.concurrent.ExecutionContext.Implicits.global
heapEmptyProcess = context.system.scheduler.scheduleOnce(500 milliseconds, self, DeconstructionActor.TryDeleteVehicle())
}
case DeconstructionActor.TryDeleteVehicle() =>
heapEmptyProcess.cancel
val (vehiclesToScrap, vehiclesRemain) = vehicleScrapHeap.partition(entry => !entry.zone.Vehicles.contains(entry.vehicle))
vehicleScrapHeap = vehiclesRemain
vehiclesToScrap.foreach { DestructionTask }
if(vehiclesRemain.nonEmpty) {
import scala.concurrent.ExecutionContext.Implicits.global
heapEmptyProcess = context.system.scheduler.scheduleOnce(500 milliseconds, self, DeconstructionActor.TryDeleteVehicle())
}
case DeconstructionActor.FailureToDeleteVehicle(localVehicle, localZone, ex) =>
org.log4s.getLogger.error(s"vehicle deconstruction: $localVehicle failed to be properly cleaned up from zone $localZone - $ex")
case _ => ;
}
def RetirementTask(entry : DeconstructionActor.VehicleEntry) : Unit = {
val vehicle = entry.vehicle
val zone = entry.zone
vehicle.Position = Vector3.Zero //somewhere it will not disturb anything
zone.Transport ! Zone.Vehicle.Despawn(vehicle)
context.parent ! DeconstructionActor.DeleteVehicle(vehicle.GUID, zone.Id) //call up to the main event system
}
def DestructionTask(entry : DeconstructionActor.VehicleEntry) : Unit = {
val vehicle = entry.vehicle
val zone = entry.zone
taskResolver ! DeconstructionTask(vehicle, zone)
}
/**
* Construct a middleman `Task` intended to return error messages to the `DeconstructionActor`.
* @param vehicle the `Vehicle` object
* @param zone the `Zone` in which the vehicle resides
* @return a `TaskResolver.GiveTask` message
*/
def DeconstructionTask(vehicle : Vehicle, zone : Zone) : TaskResolver.GiveTask = {
import net.psforever.objects.guid.{GUIDTask, Task}
TaskResolver.GiveTask (
new Task() {
private val localVehicle = vehicle
private val localZone = zone
private val localAnnounce = self
override def isComplete : Task.Resolution.Value = Task.Resolution.Success
def Execute(resolver : ActorRef) : Unit = {
resolver ! scala.util.Success(this)
}
override def onFailure(ex : Throwable): Unit = {
localAnnounce ! DeconstructionActor.FailureToDeleteVehicle(localVehicle, localZone, ex)
}
}, List(GUIDTask.UnregisterVehicle(vehicle)(zone.GUID))
)
}
/**
* Iterate over entries in a `List` until an entry that does not exceed the time limit is discovered.
* Separate the original `List` into two:
* a `List` of elements that have exceeded the time limit,
* and a `List` of elements that still satisfy the time limit.
* As newer entries to the `List` will always resolve later than old ones,
* and newer entries are always added to the end of the main `List`,
* processing in order is always correct.
* @param list the `List` of entries to divide
* @param now the time right now (in nanoseconds)
* @see `List.partition`
* @return a `Tuple` of two `Lists`, whose qualifications are explained above
*/
private def PartitionEntries(list : List[DeconstructionActor.VehicleEntry], now : Long) : (List[DeconstructionActor.VehicleEntry], List[DeconstructionActor.VehicleEntry]) = {
val n : Int = recursivePartitionEntries(list.iterator, now)
(list.take(n), list.drop(n)) //take and drop so to always return new lists
}
/**
* Mark the index where the `List` of elements can be divided into two:
* a `List` of elements that have exceeded the time limit,
* and a `List` of elements that still satisfy the time limit.
* @param iter the `Iterator` of entries to divide
* @param now the time right now (in nanoseconds)
* @param index a persistent record of the index where list division should occur;
* defaults to 0
* @return the index where division will occur
*/
@tailrec private def recursivePartitionEntries(iter : Iterator[DeconstructionActor.VehicleEntry], now : Long, index : Int = 0) : Int = {
if(!iter.hasNext) {
index
}
else {
val entry = iter.next()
if(now - entry.time >= DeconstructionActor.timeout_time) {
recursivePartitionEntries(iter, now, index + 1)
}
else {
index
}
}
}
}
object DeconstructionActor {
/** The wait before completely deleting a vehicle; as a Long for calculation simplicity */
private final val timeout_time : Long = 5000000000L //nanoseconds (5s)
/** The wait before completely deleting a vehicle; as a `FiniteDuration` for `Executor` simplicity */
private final val timeout : FiniteDuration = timeout_time nanoseconds
final case class RequestTaskResolver()
/**
* Message that carries information about a vehicle to be deconstructed.
* @param vehicle the `Vehicle` object
* @param zone the `Zone` in which the vehicle resides
* @param time when the vehicle was doomed
* @see `VehicleEntry`
*/
final case class RequestDeleteVehicle(vehicle : Vehicle, zone : Zone, time : Long = System.nanoTime())
/**
* Message that carries information about a vehicle to be deconstructed.
* Prompting, as compared to `RequestDeleteVehicle` which is reactionary.
* @param vehicle_guid the vehicle
* @param zone_id the `Zone` in which the vehicle resides
*/
final case class DeleteVehicle(vehicle_guid : PlanetSideGUID, zone_id : String)
/**
* Internal message used to signal a test of the queued vehicle information.
* Remove all deconstructing vehicles from the game world.
*/
private final case class StartDeleteVehicle()
/**
* Internal message used to signal a test of the queued vehicle information.
* Remove all deconstructing vehicles from the zone's globally unique identifier system.
*/
private final case class TryDeleteVehicle()
/**
* Error-passing message carrying information out of the final deconstruction GUID unregistering task.
* @param vehicle the `Vehicle` object
* @param zone the `Zone` in which the vehicle may or may not reside
* @param ex information regarding what happened
*/
private final case class FailureToDeleteVehicle(vehicle : Vehicle, zone : Zone, ex : Throwable)
/**
* Entry of vehicle information.
* The `zone` is maintained separately as a necessity, required to complete the deletion of the vehicle
* via unregistering of the vehicle and all related, registered objects.
* @param vehicle the `Vehicle` object
* @param zone the `Zone` in which the vehicle resides
* @param time when the vehicle was doomed
* @see `RequestDeleteVehicle`
*/
private final case class VehicleEntry(vehicle : Vehicle, zone : Zone, time : Long)
}

View file

@ -1,114 +0,0 @@
// Copyright (c) 2017 PSForever
package services.vehicle.support
import akka.actor.{Actor, Cancellable}
import net.psforever.objects.{DefaultCancellable, Vehicle}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
import services.vehicle.VehicleServiceMessage
import scala.concurrent.duration._
/**
* Maintain and curate a list of timed `vehicle` object deconstruction tasks.<br>
* <br>
* These tasks are queued or dismissed by player activity but they are executed independent of player activity.
* A common disconnected cause of deconstruction is neglect for an extended period of time.
* At that point, the original owner of the vehicle no longer matters.
* Deconstruction neglect, however, is averted by having someone become seated.
* A realized deconstruction is entirely based on a fixed interval after an unresolved request has been received.
* The actual process of deconstructing the vehicle and cleaning up its resources is performed by an external agent.<br>
* <br>
* This `Actor` is intended to sit on top of the event system that handles broadcast messaging.
*/
class DelayedDeconstructionActor extends Actor {
/** The periodic `Executor` that scraps the next vehicle on the list */
private var monitor : Cancellable = DefaultCancellable.obj
/** A `List` of currently doomed vehicles */
private var vehicles : List[DelayedDeconstructionActor.VehicleEntry] = Nil
private[this] val log = org.log4s.getLogger
private[this] def trace(msg : String) : Unit = log.trace(msg)
def receive : Receive = {
case DelayedDeconstructionActor.ScheduleDeconstruction(vehicle, zone, timeAlive) =>
trace(s"delayed deconstruction order for $vehicle in $timeAlive")
val oldHead = vehicles.headOption
val now : Long = System.nanoTime
vehicles = (vehicles :+ DelayedDeconstructionActor.VehicleEntry(vehicle, zone, timeAlive * 1000000000L))
.sortBy(entry => entry.survivalTime - (now - entry.logTime))
if(vehicles.size == 1 || oldHead != vehicles.headOption) { //we were the only entry so the event must be started from scratch
RetimePeriodicTest()
}
case DelayedDeconstructionActor.UnscheduleDeconstruction(vehicle_guid) =>
//all tasks for this vehicle are cleared from the queue
//clear any task that is no longer valid by determination of unregistered GUID
val before = vehicles.length
val now : Long = System.nanoTime
vehicles = vehicles.filter(entry => { entry.vehicle.HasGUID && entry.vehicle.GUID != vehicle_guid })
.sortBy(entry => entry.survivalTime - (now - entry.logTime))
trace(s"attempting to clear deconstruction order for vehicle $vehicle_guid, found ${before - vehicles.length}")
RetimePeriodicTest()
case DelayedDeconstructionActor.PeriodicTaskCulling =>
//filter the list of deconstruction tasks for any that are need to be triggered
monitor.cancel
val now : Long = System.nanoTime
val (vehiclesToDecon, vehiclesRemain) = vehicles.partition(entry => { now - entry.logTime >= entry.survivalTime })
vehicles = vehiclesRemain.sortBy(_.survivalTime)
trace(s"vehicle culling - ${vehiclesToDecon.length} deconstruction tasks found; ${vehiclesRemain.length} tasks remain")
vehiclesToDecon.foreach(entry => { context.parent ! VehicleServiceMessage.RequestDeleteVehicle(entry.vehicle, entry.zone) })
RetimePeriodicTest()
case _ => ;
}
def RetimePeriodicTest() : Unit = {
monitor.cancel
vehicles.headOption match {
case None => ;
case Some(entry) =>
val retime = math.max(1, entry.survivalTime - (System.nanoTime - entry.logTime)) nanoseconds
import scala.concurrent.ExecutionContext.Implicits.global
monitor = context.system.scheduler.scheduleOnce(retime, self, DelayedDeconstructionActor.PeriodicTaskCulling)
}
}
}
object DelayedDeconstructionActor {
/**
* Timer for the repeating executor.
*/
private final val periodicTest : FiniteDuration = 5000000000L nanoseconds //5s
/**
* Queue a future vehicle deconstruction action.
* @param vehicle the `Vehicle` object
* @param zone the `Zone` that the vehicle currently occupies
* @param survivalTime how long until the vehicle will be deconstructed in seconds
*/
final case class ScheduleDeconstruction(vehicle : Vehicle, zone : Zone, survivalTime : Long)
/**
* Dequeue a vehicle from being deconstructed.
* @param vehicle_guid the vehicle
*/
final case class UnscheduleDeconstruction(vehicle_guid : PlanetSideGUID)
/**
* A message the `Actor` sends to itself.
* The trigger for the periodic deconstruction task.
*/
private final case class PeriodicTaskCulling()
/**
* An entry that stores vehicle deconstruction tasks.
* @param vehicle the `Vehicle` object
* @param zone the `Zone` that the vehicle currently occupies
* @param survivalTime how long until the vehicle will be deconstructed in nanoseconds
* @param logTime when this deconstruction request was initially created in nanoseconds;
* initialized by default to a "now"
*/
private final case class VehicleEntry(vehicle : Vehicle, zone : Zone, survivalTime : Long, logTime : Long = System.nanoTime())
}

View file

@ -38,7 +38,7 @@ class ActionResultMessageTest extends Specification {
}
"encode (pass, minimal)" in {
val msg = ActionResultMessage()
val msg = ActionResultMessage.Pass
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_pass
@ -52,7 +52,7 @@ class ActionResultMessageTest extends Specification {
}
"encode (fail, minimal)" in {
val msg = ActionResultMessage(1)
val msg = ActionResultMessage.Fail(1)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_fail

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

@ -26,10 +26,24 @@ class ObjectDetachMessageTest extends Specification {
}
}
"encode" in {
"encode (1)" in {
val msg = ObjectDetachMessage(PlanetSideGUID(2916), PlanetSideGUID(2502), Vector3(3567.1406f, 2988.0078f, 71.84375f), 0f, 0f, 270f)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
"encode (2)" in {
val msg = ObjectDetachMessage(PlanetSideGUID(2916), PlanetSideGUID(2502), Vector3(3567.1406f, 2988.0078f, 71.84375f), Vector3(0f, 0f, 270f))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
"encode (3)" in {
val msg = ObjectDetachMessage(PlanetSideGUID(2916), PlanetSideGUID(2502), Vector3(3567.1406f, 2988.0078f, 71.84375f), 270f)
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

@ -349,5 +349,42 @@ class InventoryTest extends Specification {
out(1).Definition.Tile mustEqual InventoryTile.Tile22 //did not fit
ok
}
"insert items quickly (risk overwriting entries)" in {
val obj : GridInventory = GridInventory(6, 6)
(obj += 0 -> bullet9mmBox1) mustEqual true
val collision1 = obj.CheckCollisions(0,1,1)
obj.CheckCollisions(1,1,1) mustEqual collision1
obj.CheckCollisions(2,1,1) mustEqual collision1
obj.CheckCollisions(6,1,1) mustEqual collision1
obj.CheckCollisions(7,1,1) mustEqual collision1
obj.CheckCollisions(8,1,1) mustEqual collision1
obj.CheckCollisions(12,1,1) mustEqual collision1
obj.CheckCollisions(13,1,1) mustEqual collision1
obj.CheckCollisions(14,1,1) mustEqual collision1
(obj += 7 -> bullet9mmBox2) mustEqual false //can not insert overlapping object
obj.CheckCollisions(0,1,1) mustEqual collision1
obj.CheckCollisions(1,1,1) mustEqual collision1
obj.CheckCollisions(2,1,1) mustEqual collision1
obj.CheckCollisions(6,1,1) mustEqual collision1
obj.CheckCollisions(7,1,1) mustEqual collision1
obj.CheckCollisions(8,1,1) mustEqual collision1
obj.CheckCollisions(12,1,1) mustEqual collision1
obj.CheckCollisions(13,1,1) mustEqual collision1
obj.CheckCollisions(14,1,1) mustEqual collision1
obj.InsertQuickly(7, bullet9mmBox2) mustEqual true //overwrite
val collision2 = obj.CheckCollisions(7,1,1)
obj.CheckCollisions(0,1,1) mustEqual collision1
obj.CheckCollisions(1,1,1) mustEqual collision1
obj.CheckCollisions(2,1,1) mustEqual collision1
obj.CheckCollisions(6,1,1) mustEqual collision1
obj.CheckCollisions(7,1,1) mustEqual collision2
obj.CheckCollisions(8,1,1) mustEqual collision2
obj.CheckCollisions(12,1,1) mustEqual collision1
obj.CheckCollisions(13,1,1) mustEqual collision2
obj.CheckCollisions(14,1,1) mustEqual collision2
}
}
}

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,14 +154,20 @@ 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)
obj.ExoSuit = ExoSuitType.Reinforced
obj.VisibleSlots mustEqual Set(0,1,2,3,4)
obj.ExoSuit = ExoSuitType.Infiltration
obj.VisibleSlots mustEqual Set(0,4)
obj.ExoSuit = ExoSuitType.MAX
obj.VisibleSlots mustEqual Set(0)
}
"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
@ -175,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
@ -188,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
@ -227,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
@ -235,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)
@ -260,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
@ -269,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)
@ -310,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)
@ -350,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
@ -359,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
@ -368,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
@ -398,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))
@ -429,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))
@ -438,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))
@ -447,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
@ -476,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
@ -486,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
@ -502,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
@ -516,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,13 +12,13 @@ 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
import org.specs2.mutable.Specification
import scala.concurrent.duration.Duration
import scala.concurrent.duration._
class ZoneTest extends Specification {
def test(a: Int, b : Zone, c : ActorContext) : Building = { Building.NoBuilding }
@ -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
@ -464,56 +464,183 @@ class ZonePopulationTest extends ActorTest {
}
}
class ZoneGroundTest extends ActorTest {
class ZoneGroundDropItemTest extends ActorTest {
val item = AmmoBox(GlobalDefinitions.bullet_9mm)
item.GUID = PlanetSideGUID(10)
val hub = new NumberPoolHub(new LimitedNumberSource(20))
hub.register(item, 10)
val zone = new Zone("test", new ZoneMap("test-map"), 0)
zone.GUID(hub)
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "drop-test-zone") ! "!"
"ZoneGroundActor" should {
"DropItem" should {
"drop item on ground" in {
val zone = new Zone("test", new ZoneMap(""), 0)
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "drop-item-test") ! "!"
receiveOne(Duration.create(200, "ms")) //consume
receiveOne(1 second) //consume
assert(!zone.EquipmentOnGround.contains(item))
zone.Ground ! Zone.Ground.DropItem(item, Vector3(1.1f, 2.2f, 3.3f), Vector3(4.4f, 5.5f, 6.6f))
assert(zone.EquipmentOnGround.isEmpty)
assert(item.Position == Vector3.Zero)
assert(item.Orientation == Vector3.Zero)
zone.Ground ! Zone.DropItemOnGround(item, Vector3(1.1f, 2.2f, 3.3f), Vector3(4.4f, 5.5f, 6.6f))
expectNoMsg(Duration.create(100, "ms"))
assert(zone.EquipmentOnGround == List(item))
assert(item.Position == Vector3(1.1f, 2.2f, 3.3f))
assert(item.Orientation == Vector3(4.4f, 5.5f, 6.6f))
val reply = receiveOne(200 milliseconds)
assert(reply.isInstanceOf[Zone.Ground.ItemOnGround])
assert(reply.asInstanceOf[Zone.Ground.ItemOnGround].item == item)
assert(reply.asInstanceOf[Zone.Ground.ItemOnGround].pos == Vector3(1.1f, 2.2f, 3.3f))
assert(reply.asInstanceOf[Zone.Ground.ItemOnGround].orient == Vector3(4.4f, 5.5f, 6.6f))
assert(zone.EquipmentOnGround.contains(item))
}
}
}
"get item from ground (success)" in {
val zone = new Zone("test", new ZoneMap(""), 0)
val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5))
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "get-item-test-good") ! "!"
receiveOne(Duration.create(200, "ms")) //consume
zone.Ground ! Zone.DropItemOnGround(item, Vector3.Zero, Vector3.Zero)
expectNoMsg(Duration.create(100, "ms"))
class ZoneGroundCanNotDropItem1Test extends ActorTest {
val item = AmmoBox(GlobalDefinitions.bullet_9mm)
val hub = new NumberPoolHub(new LimitedNumberSource(20))
//hub.register(item, 10) //!important
val zone = new Zone("test", new ZoneMap("test-map"), 0)
zone.GUID(hub)
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "drop-test-zone") ! "!"
assert(zone.EquipmentOnGround == List(item))
zone.Ground ! Zone.GetItemOnGround(player, PlanetSideGUID(10))
val reply = receiveOne(Duration.create(100, "ms"))
"DropItem" should {
"not drop an item that is not registered" in {
receiveOne(1 second) //consume
assert(!zone.EquipmentOnGround.contains(item))
zone.Ground ! Zone.Ground.DropItem(item, Vector3.Zero, Vector3.Zero)
assert(zone.EquipmentOnGround.isEmpty)
assert(reply.isInstanceOf[Zone.ItemFromGround])
assert(reply.asInstanceOf[Zone.ItemFromGround].player == player)
assert(reply.asInstanceOf[Zone.ItemFromGround].item == item)
val reply = receiveOne(300 milliseconds)
assert(reply.isInstanceOf[Zone.Ground.CanNotDropItem])
assert(reply.asInstanceOf[Zone.Ground.CanNotDropItem].item == item)
assert(reply.asInstanceOf[Zone.Ground.CanNotDropItem].zone == zone)
assert(reply.asInstanceOf[Zone.Ground.CanNotDropItem].reason == "not registered yet")
assert(!zone.EquipmentOnGround.contains(item))
}
}
}
"get item from ground (failure)" in {
val zone = new Zone("test", new ZoneMap(""), 0)
val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5))
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "get-item-test-fail") ! "!"
receiveOne(Duration.create(200, "ms")) //consume
zone.Ground ! Zone.DropItemOnGround(item, Vector3.Zero, Vector3.Zero)
expectNoMsg(Duration.create(100, "ms"))
class ZoneGroundCanNotDropItem2Test extends ActorTest {
val item = AmmoBox(GlobalDefinitions.bullet_9mm)
val hub = new NumberPoolHub(new LimitedNumberSource(20))
hub.register(item, 10) //!important
val zone = new Zone("test", new ZoneMap("test-map"), 0)
//zone.GUID(hub) //!important
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "drop-test-zone") ! "!"
assert(zone.EquipmentOnGround == List(item))
zone.Ground ! Zone.GetItemOnGround(player, PlanetSideGUID(11)) //wrong guid
expectNoMsg(Duration.create(500, "ms"))
"DropItem" should {
"not drop an item that is not registered to the zone" in {
receiveOne(1 second) //consume
assert(!zone.EquipmentOnGround.contains(item))
zone.Ground ! Zone.Ground.DropItem(item, Vector3.Zero, Vector3.Zero)
val reply = receiveOne(300 milliseconds)
assert(reply.isInstanceOf[Zone.Ground.CanNotDropItem])
assert(reply.asInstanceOf[Zone.Ground.CanNotDropItem].item == item)
assert(reply.asInstanceOf[Zone.Ground.CanNotDropItem].zone == zone)
assert(reply.asInstanceOf[Zone.Ground.CanNotDropItem].reason == "registered to some other zone")
assert(!zone.EquipmentOnGround.contains(item))
}
}
}
class ZoneGroundCanNotDropItem3Test extends ActorTest {
val item = AmmoBox(GlobalDefinitions.bullet_9mm)
val hub = new NumberPoolHub(new LimitedNumberSource(20))
hub.register(item, 10) //!important
val zone = new Zone("test", new ZoneMap("test-map"), 0)
zone.GUID(hub) //!important
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "drop-test-zone") ! "!"
"DropItem" should {
"not drop an item that has already been dropped" in {
receiveOne(1 second) //consume
assert(!zone.EquipmentOnGround.contains(item))
assert(zone.EquipmentOnGround.isEmpty)
zone.Ground ! Zone.Ground.DropItem(item, Vector3.Zero, Vector3.Zero)
val reply1 = receiveOne(300 milliseconds)
assert(reply1.isInstanceOf[Zone.Ground.ItemOnGround])
assert(reply1.asInstanceOf[Zone.Ground.ItemOnGround].item == item)
assert(zone.EquipmentOnGround.contains(item))
assert(zone.EquipmentOnGround.size == 1)
zone.Ground ! Zone.Ground.DropItem(item, Vector3.Zero, Vector3.Zero)
val reply2 = receiveOne(300 milliseconds)
assert(reply2.isInstanceOf[Zone.Ground.CanNotDropItem])
assert(reply2.asInstanceOf[Zone.Ground.CanNotDropItem].item == item)
assert(reply2.asInstanceOf[Zone.Ground.CanNotDropItem].zone == zone)
assert(reply2.asInstanceOf[Zone.Ground.CanNotDropItem].reason == "already dropped")
assert(zone.EquipmentOnGround.size == 1)
}
}
}
class ZoneGroundPickupItemTest extends ActorTest {
val item = AmmoBox(GlobalDefinitions.bullet_9mm)
val hub = new NumberPoolHub(new LimitedNumberSource(20))
hub.register(item, 10)
val zone = new Zone("test", new ZoneMap("test-map"), 0)
zone.GUID(hub)
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "drop-test-zone") ! "!"
"PickupItem" should {
"pickup an item from ground" in {
receiveOne(1 second) //consume
assert(!zone.EquipmentOnGround.contains(item))
zone.Ground ! Zone.Ground.DropItem(item, Vector3.Zero, Vector3.Zero)
val reply1 = receiveOne(200 milliseconds)
assert(reply1.isInstanceOf[Zone.Ground.ItemOnGround])
assert(zone.EquipmentOnGround.contains(item))
zone.Ground ! Zone.Ground.PickupItem(item.GUID)
val reply2 = receiveOne(200 milliseconds)
assert(reply2.isInstanceOf[Zone.Ground.ItemInHand])
assert(reply2.asInstanceOf[Zone.Ground.ItemInHand].item == item)
assert(!zone.EquipmentOnGround.contains(item))
}
}
}
class ZoneGroundCanNotPickupItemTest extends ActorTest {
val item = AmmoBox(GlobalDefinitions.bullet_9mm)
val hub = new NumberPoolHub(new LimitedNumberSource(20))
hub.register(item, 10)
val zone = new Zone("test", new ZoneMap("test-map"), 0)
zone.GUID(hub) //still registered to this zone
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "drop-test-zone") ! "!"
"PickupItem" should {
"not pickup an item if it can not be found" in {
receiveOne(1 second) //consume
assert(!zone.EquipmentOnGround.contains(item))
zone.Ground ! Zone.Ground.PickupItem(item.GUID)
val reply2 = receiveOne(200 milliseconds)
assert(reply2.isInstanceOf[Zone.Ground.CanNotPickupItem])
assert(reply2.asInstanceOf[Zone.Ground.CanNotPickupItem].item_guid == item.GUID)
assert(reply2.asInstanceOf[Zone.Ground.CanNotPickupItem].zone == zone)
assert(reply2.asInstanceOf[Zone.Ground.CanNotPickupItem].reason == "can not find")
}
}
}
class ZoneGroundRemoveItemTest extends ActorTest {
val item = AmmoBox(GlobalDefinitions.bullet_9mm)
val hub = new NumberPoolHub(new LimitedNumberSource(20))
hub.register(item, 10)
val zone = new Zone("test", new ZoneMap("test-map"), 0)
zone.GUID(hub) //still registered to this zone
system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "drop-test-zone") ! "!"
"RemoveItem" should {
"remove an item from the ground without callback (even if the item is not found)" in {
receiveOne(1 second)
assert(!zone.EquipmentOnGround.contains(item))
zone.Ground ! Zone.Ground.DropItem(item, Vector3.Zero, Vector3.Zero)
receiveOne(200 milliseconds)
assert(zone.EquipmentOnGround.contains(item)) //dropped
zone.Ground ! Zone.Ground.RemoveItem(item.GUID)
expectNoMsg(500 milliseconds)
assert(!zone.EquipmentOnGround.contains(item))
zone.Ground ! Zone.Ground.RemoveItem(item.GUID) //repeat
expectNoMsg(500 milliseconds)
assert(!zone.EquipmentOnGround.contains(item))
}
}
}

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

Some files were not shown because too many files have changed in this diff Show more