Merge branch 'master' into feature/CargoPassengers

# Conflicts:
#	common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
#	common/src/main/scala/services/avatar/AvatarAction.scala
#	common/src/main/scala/services/avatar/AvatarResponse.scala
#	pslogin/src/main/scala/WorldSessionActor.scala
#	pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala
This commit is contained in:
Mazo 2018-06-13 12:52:23 +01:00
commit 2ebebb416f
135 changed files with 6071 additions and 2968 deletions

View file

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

View file

@ -13,9 +13,12 @@ import net.psforever.objects.serverobject.mblocker.LockerDefinition
import net.psforever.objects.serverobject.pad.VehicleSpawnPadDefinition import net.psforever.objects.serverobject.pad.VehicleSpawnPadDefinition
import net.psforever.objects.serverobject.terminals._ import net.psforever.objects.serverobject.terminals._
import net.psforever.objects.serverobject.tube.SpawnTubeDefinition import net.psforever.objects.serverobject.tube.SpawnTubeDefinition
import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition
import net.psforever.objects.vehicles.{SeatArmorRestriction, UtilityType} import net.psforever.objects.vehicles.{SeatArmorRestriction, UtilityType}
import net.psforever.types.PlanetSideEmpire import net.psforever.types.PlanetSideEmpire
import scala.concurrent.duration._
object GlobalDefinitions { object GlobalDefinitions {
/* /*
Implants Implants
@ -563,6 +566,8 @@ object GlobalDefinitions {
val door = new DoorDefinition val door = new DoorDefinition
val resource_silo = new ResourceSiloDefinition
/** /**
* Given a faction, provide the standard assault melee weapon. * Given a faction, provide the standard assault melee weapon.
* @param faction the faction * @param faction the faction
@ -2701,7 +2706,11 @@ object GlobalDefinitions {
ant.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax ant.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
ant.MountPoints += 1 -> 0 ant.MountPoints += 1 -> 0
ant.MountPoints += 2 -> 0 ant.MountPoints += 2 -> 0
ant.Deployment = true
ant.DeployTime = 1500
ant.UndeployTime = 1500
ant.AutoPilotSpeeds = (18, 6) ant.AutoPilotSpeeds = (18, 6)
ant.MaximumCapacitor = 1500
ant.Packet = utilityConverter ant.Packet = utilityConverter
ams.Name = "ams" ams.Name = "ams"
@ -2716,6 +2725,7 @@ object GlobalDefinitions {
ams.Deployment = true ams.Deployment = true
ams.DeployTime = 2000 ams.DeployTime = 2000
ams.UndeployTime = 2000 ams.UndeployTime = 2000
ams.DeconstructionTime = Some(20 minutes)
ams.AutoPilotSpeeds = (18, 6) ams.AutoPilotSpeeds = (18, 6)
ams.Packet = utilityConverter ams.Packet = utilityConverter
@ -2728,6 +2738,7 @@ object GlobalDefinitions {
router.Deployment = true router.Deployment = true
router.DeployTime = 2000 router.DeployTime = 2000
router.UndeployTime = 2000 router.UndeployTime = 2000
router.DeconstructionTime = Duration(20, "minutes")
router.AutoPilotSpeeds = (16, 6) router.AutoPilotSpeeds = (16, 6)
router.Packet = variantConverter 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 Head : Int = core.head
def Voice : Int = core.voice def Voice : CharacterVoice.Value = core.voice
def isAlive : Boolean = alive def isAlive : Boolean = alive
@ -129,7 +129,12 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio
def MaxArmor : Int = exosuit.MaxArmor 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 = { override def Slot(slot : Int) : EquipmentSlot = {
if(inventory.Offset <= slot && slot <= inventory.LastIndex) { 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. * Generally, all seating is declared first - the driver and passengers and and gunners.
* Following that are the mounted weapons and other utilities. * Following that are the mounted weapons and other utilities.
* Trunk space starts being indexed afterwards. * Trunk space starts being indexed afterwards.
* To keep it simple, infantry seating, mounted weapons, and utilities are stored separately.<br> * To keep it simple, infantry seating, mounted weapons, and utilities are stored separately herein.
* <br> * The `Map` of `Utility` objects is given using the same inventory index positions.
* Vehicles maintain a `Map` of `Utility` objects in given index positions.
* Positive indices and zero are considered "represented" and must be assigned a globally unique identifier * 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. * 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. * The index is the seat position, reflecting the position in the zero-index inventory.
* Negative indices are expected to be excluded from this conversion. * 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` * @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; * stores and unloads pertinent information about the `Vehicle`'s configuration;
* used in the initialization process (`loadVehicleDefinition`) * used in the initialization process (`loadVehicleDefinition`)
*/ */
@ -47,6 +75,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
private var trunkAccess : Option[PlanetSideGUID] = None private var trunkAccess : Option[PlanetSideGUID] = None
private var jammered : Boolean = false private var jammered : Boolean = false
private var cloaked : Boolean = false private var cloaked : Boolean = false
private var capacitor : Int = 0
/** /**
* Permissions control who gets to access different parts of the vehicle; * Permissions control who gets to access different parts of the vehicle;
@ -170,6 +199,19 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
Cloaked Cloaked
} }
def Capacitor : Int = capacitor
def Capacitor_=(value: Int) : Int = {
if(value > Definition.MaximumCapacitor) {
capacitor = Definition.MaximumCapacitor
} else if (value < 0) {
capacitor = 0
} else {
capacitor = value
}
Capacitor
}
/** /**
* Given the index of an entry mounting point, return the infantry-accessible `Seat` associated with it. * Given the index of an entry mounting point, return the infantry-accessible `Seat` associated with it.
* @param mountPoint an index representing the seat position / mounting point * @param mountPoint an index representing the seat position / mounting point

View file

@ -6,6 +6,7 @@ import net.psforever.objects.inventory.InventoryTile
import net.psforever.objects.vehicles.UtilityType import net.psforever.objects.vehicles.UtilityType
import scala.collection.mutable import scala.collection.mutable
import scala.concurrent.duration._
/** /**
* An object definition system used to construct and retain the parameters of various vehicles. * An object definition system used to construct and retain the parameters of various vehicles.
@ -30,6 +31,8 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) {
private var canCloak : Boolean = false private var canCloak : Boolean = false
private var canBeOwned : Boolean = true private var canBeOwned : Boolean = true
private var serverVehicleOverrideSpeeds : (Int, Int) = (0, 0) private var serverVehicleOverrideSpeeds : (Int, Int) = (0, 0)
private var deconTime : Option[FiniteDuration] = None
private var maximumCapacitor : Int = 0
Name = "vehicle" Name = "vehicle"
Packet = VehicleDefinition.converter Packet = VehicleDefinition.converter
@ -85,6 +88,18 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) {
DeployTime 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 : Int = deploymentTime_Undeploy
def UndeployTime_=(dtime : Int) : Int = { def UndeployTime_=(dtime : Int) : Int = {
@ -116,6 +131,13 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) {
def AutoPilotSpeed1 : Int = serverVehicleOverrideSpeeds._1 def AutoPilotSpeed1 : Int = serverVehicleOverrideSpeeds._1
def AutoPilotSpeed2 : Int = serverVehicleOverrideSpeeds._2 def AutoPilotSpeed2 : Int = serverVehicleOverrideSpeeds._2
def MaximumCapacitor : Int = maximumCapacitor
def MaximumCapacitor_=(maxCapacitor: Int) : Int = {
maximumCapacitor = maxCapacitor
MaximumCapacitor
}
} }
object VehicleDefinition { object VehicleDefinition {

View file

@ -3,80 +3,129 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.{EquipmentSlot, Player} import net.psforever.objects.{EquipmentSlot, Player}
import net.psforever.objects.equipment.Equipment 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 net.psforever.types.{GrenadeState, ImplantType}
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.util.{Success, Try} import scala.util.{Success, Try}
class AvatarConverter extends ObjectCreateConverter[Player]() { class AvatarConverter extends ObjectCreateConverter[Player]() {
override def ConstructorData(obj : Player) : Try[CharacterData] = { override def ConstructorData(obj : Player) : Try[PlayerData] = {
val MaxArmor = obj.MaxArmor import AvatarConverter._
Success( Success(
CharacterData( if(obj.VehicleSeated.isEmpty) {
MakeAppearanceData(obj), PlayerData(
255 * obj.Health / obj.MaxHealth, //TODO not precise PlacementData(obj.Position, obj.Orientation, obj.Velocity),
if(MaxArmor == 0) { 0 } else { 255 * obj.Armor / MaxArmor }, //TODO not precise MakeAppearanceData(obj),
DressBattleRank(obj), MakeCharacterData(obj),
DressCommandRank(obj), MakeInventoryData(obj),
recursiveMakeImplantEffects(obj.Implants.iterator), GetDrawnSlot(obj)
MakeCosmetics(obj.BEP), )
InventoryData(MakeHolsters(obj, BuildEquipment).sortBy(_.parentSlot)), //TODO is sorting necessary? }
GetDrawnSlot(obj) else {
) PlayerData(
) MakeAppearanceData(obj),
//TODO tidy this mess up MakeCharacterData(obj),
} MakeInventoryData(obj),
DrawnSlot.None
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)
)
) )
} }
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`. * Compose some data from a `Player` into a representation common to both `CharacterData` and `DetailedCharacterData`.
* @param obj the `Player` game object * @param obj the `Player` game object
* @return the resulting `CharacterAppearanceData` * @return the resulting `CharacterAppearanceData`
*/ */
private def MakeAppearanceData(obj : Player) : CharacterAppearanceData = { def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
CharacterAppearanceData( CharacterAppearanceData(
PlacementData(obj.Position, obj.Orientation, obj.Velocity),
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, obj.Voice), BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, obj.Voice),
0, voice2 = 0,
false, black_ops = false,
false, jammered = false,
obj.ExoSuit, obj.ExoSuit,
"", outfit_name = "",
0, outfit_logo = 0,
obj.isBackpack, obj.isBackpack,
obj.Orientation.y, facingPitch = obj.Orientation.y,
obj.FacingYawUpper, facingYawUpper = obj.FacingYawUpper,
true, lfs = true,
GrenadeState.None, GrenadeState.None,
false, is_cloaking = false,
false, charging_pose = false,
false, on_zipline = false,
RibbonBars() 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. * 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. * At certain battle ranks, all exo-suits undergo some form of coloration change.
@ -183,7 +232,7 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
* @see `Cosmetics` * @see `Cosmetics`
* @return the `Cosmetics` options * @return the `Cosmetics` options
*/ */
protected def MakeCosmetics(bep : Long) : Option[Cosmetics] = def MakeCosmetics(bep : Long) : Option[Cosmetics] =
if(DetailedCharacterData.isBR24(bep)) { if(DetailedCharacterData.isBR24(bep)) {
Some(Cosmetics(false, false, false, false, false)) Some(Cosmetics(false, false, false, false, false))
} }
@ -200,12 +249,12 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
*/ */
private def MakeInventory(obj : Player) : List[InternalSlot] = { private def MakeInventory(obj : Player) : List[InternalSlot] = {
obj.Inventory.Items obj.Inventory.Items
.map({ .map(item => {
case(_, item) =>
val equip : Equipment = item.obj val equip : Equipment = item.obj
InternalSlot(equip.Definition.ObjectId, equip.GUID, item.start, equip.Definition.Packet.DetailedConstructorData(equip).get) 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. * 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, * 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 * @param equip the game object
* @return the game object in decoded packet form * @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) 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 * @param obj the `Player` game object
* @return the holster's Enumeration value * @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 } 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.{EquipmentSlot, Player}
import net.psforever.objects.equipment.Equipment import net.psforever.objects.equipment.Equipment
import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars} import net.psforever.packet.game.objectcreate._
import net.psforever.types.{GrenadeState, ImplantType} import net.psforever.types.{CharacterVoice, GrenadeState, ImplantType}
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.util.{Failure, Success, Try} 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. * Details that would not be apparent on that screen such as implants or certifications are ignored.
*/ */
class CharacterSelectConverter extends AvatarConverter { 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( Success(
DetailedCharacterData( DetailedPlayerData.apply(
PlacementData(0, 0, 0),
MakeAppearanceData(obj), MakeAppearanceData(obj),
obj.BEP, DetailedCharacterData(
obj.CEP, obj.BEP,
1, 1, 0, 1, 1, obj.CEP,
Nil, healthMax = 1,
MakeImplantEntries(obj), //necessary for correct stream length health = 1,
Nil, Nil, armor = 0,
MakeCosmetics(obj.BEP), 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)), InventoryData(recursiveMakeHolsters(obj.Holsters().iterator)),
GetDrawnSlot(obj) AvatarConverter.GetDrawnSlot(obj)
) )
) )
} }
@ -40,24 +48,23 @@ class CharacterSelectConverter extends AvatarConverter {
* @see `AvatarConverter.MakeAppearanceData` * @see `AvatarConverter.MakeAppearanceData`
* @return the resulting `CharacterAppearanceData` * @return the resulting `CharacterAppearanceData`
*/ */
private def MakeAppearanceData(obj : Player) : CharacterAppearanceData = { private def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
CharacterAppearanceData( CharacterAppearanceData(
PlacementData(0f, 0f, 0f), BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, CharacterVoice.Mute),
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, 1), voice2 = 0,
0, black_ops = false,
false, jammered = false,
false,
obj.ExoSuit, obj.ExoSuit,
"", outfit_name = "",
0, outfit_logo = 0,
false, backpack = false,
0f, facingPitch = 0,
0f, facingYawUpper = 0,
true, lfs = true,
GrenadeState.None, GrenadeState.None,
false, is_cloaking = false,
false, charging_pose = false,
false, on_zipline = false,
RibbonBars() RibbonBars()
) )
} }
@ -90,7 +97,7 @@ class CharacterSelectConverter extends AvatarConverter {
val equip : Equipment = slot.Equipment.get val equip : Equipment = slot.Equipment.get
recursiveMakeHolsters( recursiveMakeHolsters(
iter, iter,
list :+ BuildDetailedEquipment(index, equip), list :+ AvatarConverter.BuildDetailedEquipment(index, equip),
index + 1 index + 1
) )
} }

View file

@ -3,23 +3,35 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.{EquipmentSlot, Player} import net.psforever.objects.{EquipmentSlot, Player}
import net.psforever.objects.equipment.Equipment import net.psforever.objects.equipment.Equipment
import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, DrawnSlot, InternalSlot, InventoryData, PlacementData, RibbonBars} import net.psforever.packet.game.objectcreate._
import net.psforever.types.{CharacterGender, GrenadeState, Vector3} import net.psforever.types.{CharacterGender, CharacterVoice, GrenadeState, Vector3}
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.util.{Failure, Success, Try} import scala.util.{Failure, Success, Try}
class CorpseConverter extends AvatarConverter { 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")) 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( Success(
DetailedCharacterData( DetailedPlayerData.apply(
PlacementData(obj.Position, Vector3(0,0, obj.Orientation.z)),
MakeAppearanceData(obj), MakeAppearanceData(obj),
0, 0, 0, 0, 0, 0, 0, DetailedCharacterData(
Nil, Nil, Nil, Nil, bep = 0,
None, 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)), InventoryData((MakeHolsters(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)),
DrawnSlot.None DrawnSlot.None
) )
@ -31,24 +43,23 @@ class CorpseConverter extends AvatarConverter {
* @param obj the `Player` game object * @param obj the `Player` game object
* @return the resulting `CharacterAppearanceData` * @return the resulting `CharacterAppearanceData`
*/ */
private def MakeAppearanceData(obj : Player) : CharacterAppearanceData = { private def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
CharacterAppearanceData( CharacterAppearanceData(
PlacementData(obj.Position, Vector3(0,0, obj.Orientation.z)), BasicCharacterData(obj.Name, obj.Faction, CharacterGender.Male, 0, CharacterVoice.Mute),
BasicCharacterData(obj.Name, obj.Faction, CharacterGender.Male, 0, 0), voice2 = 0,
0, black_ops = false,
false, jammered = false,
false,
obj.ExoSuit, obj.ExoSuit,
"", outfit_name = "",
0, outfit_logo = 0,
true, backpack = true,
obj.Orientation.y, //TODO is this important? facingPitch = obj.Orientation.y, //TODO is this important?
0, facingYawUpper = 0,
true, lfs = true,
GrenadeState.None, GrenadeState.None,
false, is_cloaking = false,
false, charging_pose = false,
false, on_zipline = false,
RibbonBars() RibbonBars()
) )
} }
@ -62,11 +73,10 @@ class CorpseConverter extends AvatarConverter {
*/ */
private def MakeInventory(obj : Player) : List[InternalSlot] = { private def MakeInventory(obj : Player) : List[InternalSlot] = {
obj.Inventory.Items obj.Inventory.Items
.map({ .map(item => {
case(_, item) =>
val equip : Equipment = item.obj val equip : Equipment = item.obj
BuildEquipment(item.start, equip) BuildEquipment(item.start, equip)
}).toList })
} }
/** /**

View file

@ -30,11 +30,10 @@ class LockerContainerConverter extends ObjectCreateConverter[LockerContainer]()
*/ */
private def MakeInventory(inv : GridInventory) : List[InternalSlot] = { private def MakeInventory(inv : GridInventory) : List[InternalSlot] = {
inv.Items inv.Items
.map({ .map(item => {
case(_, item) =>
val equip : Equipment = item.obj val equip : Equipment = item.obj
InternalSlot(equip.Definition.ObjectId, equip.GUID, item.start, equip.Definition.Packet.ConstructorData(equip).get) 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] = { private def MakeDetailedInventory(inv : GridInventory) : List[InternalSlot] = {
inv.Items inv.Items
.map({ .map(item => {
case(_, item) =>
val equip : Equipment = item.obj val equip : Equipment = item.obj
InternalSlot(equip.Definition.ObjectId, equip.GUID, item.start, equip.Definition.Packet.DetailedConstructorData(equip).get) 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]() { class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
override def DetailedConstructorData(obj : Vehicle) : Try[VehicleData] = 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] = { override def ConstructorData(obj : Vehicle) : Try[VehicleData] = {
val health = 255 * obj.Health / obj.MaxHealth //TODO not precise
Success( Success(
VehicleData( VehicleData(
CommonFieldData( PlacementData(obj.Position, obj.Orientation, obj.Velocity),
PlacementData(obj.Position, obj.Orientation, obj.Velocity), obj.Faction,
obj.Faction, bops = false,
0, destroyed = health < 3,
PlanetSideGUID(0) //if(obj.Owner.isDefined) { obj.Owner.get } else { PlanetSideGUID(0) } //TODO is this really Owner? unk1 = 0,
), obj.Jammered,
0, unk2 = false,
255 * obj.Health / obj.MaxHealth, //TODO not precise obj.Owner match {
false, false, case Some(owner) => owner
case None => PlanetSideGUID(0)
},
unk3 = false,
health,
unk4 = false,
no_mount_points = false,
obj.DeploymentState, obj.DeploymentState,
false, unk5 = false,
false, unk6 = false,
obj.Cloaked, obj.Cloaked,
SpecificFormatData(obj), SpecificFormatData(obj),
Some(InventoryData((MakeUtilities(obj) ++ MakeMountings(obj)).sortBy(_.parentSlot))) Some(InventoryData(MakeDriverSeat(obj) ++ MakeUtilities(obj) ++ MakeMountings(obj)))
)(SpecificFormatModifier) )(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] = { private def MakeMountings(obj : Vehicle) : List[InventoryItemData.InventoryItem] = {
obj.Weapons.map({ obj.Weapons.map({

View file

@ -100,7 +100,7 @@ object GUIDTask {
* @return a list of `TaskResolver.GiveTask` messages * @return a list of `TaskResolver.GiveTask` messages
*/ */
def RegisterInventory(container : Container)(implicit guid : ActorRef) : List[TaskResolver.GiveTask] = { 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 * @return a list of `TaskResolver.GiveTask` messages
*/ */
def UnregisterInventory(container : Container)(implicit guid : ActorRef) : List[TaskResolver.GiveTask] = { 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 net.psforever.packet.game.PlanetSideGUID
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.collection.immutable.Map
import scala.collection.mutable import scala.collection.mutable
import scala.util.{Failure, Success, Try} import scala.util.{Failure, Success, Try}
@ -38,7 +37,7 @@ class GridInventory extends Container {
private val entryIndex : AtomicInteger = new AtomicInteger(0) private val entryIndex : AtomicInteger = new AtomicInteger(0)
private var grid : Array[Int] = Array.fill[Int](1)(-1) 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 def Width : Int = width
@ -331,26 +330,23 @@ class GridInventory extends Container {
def Insertion_CheckCollisions(start : Int, obj : Equipment, key : Int) : Boolean = { def Insertion_CheckCollisions(start : Int, obj : Equipment, key : Int) : Boolean = {
CheckCollisions(start, obj) match { CheckCollisions(start, obj) match {
case Success(Nil) => case Success(Nil) =>
val card = InventoryItem(obj, start) InsertQuickly(start, obj, key)
items += key -> card
val tile = obj.Tile
SetCells(start, tile.Width, tile.Height, key)
true
case _ => case _ =>
false 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 = { private def InsertQuickly(start : Int, obj : Equipment, key : Int) : Boolean = {
// val guid : Int = obj.GUID.guid val card = InventoryItem(obj, start)
// val card = InventoryItemData(obj, start) items += key -> card
// items += guid -> card val tile = obj.Tile
// val tile = obj.Tile SetCells(start, tile.Width, tile.Height, key)
// SetCellsOffset(start, tile.width, tile.height, guid) true
// true }
// }
def +=(kv : (Int, Equipment)) : Boolean = Insert(kv._1, kv._2)
def Remove(index : Int) : Boolean = { def Remove(index : Int) : Boolean = {
val key = grid(index - Offset) val key = grid(index - Offset)

View file

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

View file

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

View file

@ -39,7 +39,7 @@ object Loadout {
InfantryLoadout( InfantryLoadout(
label, label,
packageSimplifications(player.Holsters()), packageSimplifications(player.Holsters()),
packageSimplifications(player.Inventory.Items.values.toList), packageSimplifications(player.Inventory.Items),
player.ExoSuit, player.ExoSuit,
DetermineSubtype(player) DetermineSubtype(player)
) )
@ -55,7 +55,7 @@ object Loadout {
VehicleLoadout( VehicleLoadout(
label, label,
packageSimplifications(vehicle.Weapons.map({ case ((index, weapon)) => InventoryItem(weapon.Equipment.get, index) }).toList), 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 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 // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.locks 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.objects.serverobject.structures.Amenity
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.TriggeredSound
import net.psforever.types.Vector3
/** /**
* A structure-owned server object that is a "door lock."<br> * 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. * 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 * @param idef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/ */
class IFFLock(private val idef : IFFLockDefinition) extends Amenity { class IFFLock(private val idef : IFFLockDefinition) extends Amenity with 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
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 Definition : IFFLockDefinition = idef def Definition : IFFLockDefinition = idef
HackSound = TriggeredSound.HackDoor
} }
object IFFLock { object IFFLock {

View file

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

View file

@ -3,10 +3,13 @@ package net.psforever.objects.serverobject.mblocker
import akka.actor.{ActorContext, Props} import akka.actor.{ActorContext, Props}
import net.psforever.objects.GlobalDefinitions import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.structures.Amenity 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 def Definition : LockerDefinition = GlobalDefinitions.mb_locker
HackSound = TriggeredSound.HackTerminal
} }
object Locker { object Locker {

View file

@ -2,6 +2,7 @@
package net.psforever.objects.serverobject.mblocker package net.psforever.objects.serverobject.mblocker
import akka.actor.Actor import akka.actor.Actor
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} 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 FactionObject : FactionAffinity = locker
def receive : Receive = checkBehavior.orElse { def receive : Receive = checkBehavior.orElse {
case CommonMessages.Hack(player) =>
locker.HackedBy = player
sender ! true
case CommonMessages.ClearHack() =>
locker.HackedBy = None
case _ => ; case _ => ;
} }
} }

View file

@ -4,6 +4,7 @@ package net.psforever.objects.serverobject.pad.process
import akka.actor.{ActorRef, Props} import akka.actor.{ActorRef, Props}
import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
import net.psforever.types.Vector3
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -46,7 +47,7 @@ class VehicleSpawnControlSeatDriver(pad : VehicleSpawnPad) extends VehicleSpawnC
trace("driver to be made seated in vehicle") trace("driver to be made seated in vehicle")
entry.sendTo ! VehicleSpawnPad.StartPlayerSeatedInVehicle(entry.vehicle, pad) entry.sendTo ! VehicleSpawnPad.StartPlayerSeatedInVehicle(entry.vehicle, pad)
entry.vehicle.Actor.tell(Mountable.TryMount(driver, 0), entry.sendTo) //entry.sendTo should handle replies to TryMount 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 { else {
trace("driver lost; vehicle stranded on pad") trace("driver lost; vehicle stranded on pad")
@ -55,11 +56,18 @@ class VehicleSpawnControlSeatDriver(pad : VehicleSpawnPad) extends VehicleSpawnC
case VehicleSpawnControlSeatDriver.AwaitDriverInSeat(entry) => case VehicleSpawnControlSeatDriver.AwaitDriverInSeat(entry) =>
val driver = entry.driver 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") trace("driver lost, but operations can continue")
vehicleOverride ! VehicleSpawnControl.Process.ServerVehicleOverride(entry) 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) { 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)) context.system.scheduler.scheduleOnce(100 milliseconds, self, VehicleSpawnControlSeatDriver.AwaitDriverInSeat(entry))
} }
else { else {

View file

@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.pad.process
import akka.actor.{ActorRef, Props} import akka.actor.{ActorRef, Props}
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
import net.psforever.types.Vector3
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -26,27 +27,33 @@ class VehicleSpawnControlServerVehicleOverride(pad : VehicleSpawnPad) extends Ve
def receive : Receive = { def receive : Receive = {
case VehicleSpawnControl.Process.ServerVehicleOverride(entry) => case VehicleSpawnControl.Process.ServerVehicleOverride(entry) =>
val vehicle = entry.vehicle val vehicle = entry.vehicle
val pad_railed = pad.Railed val vehicleFailState = vehicle.Health == 0 || vehicle.Position == Vector3.Zero
if(pad_railed) { val driverFailState = !entry.driver.isAlive || entry.driver.Continent != Continent.Id || (if(vehicle.HasGUID) { !entry.driver.VehicleSeated.contains(vehicle.GUID) } else { true })
Continent.VehicleEvents ! VehicleSpawnPad.DetachFromRails(vehicle, pad, Continent.Id) if(vehicleFailState || driverFailState) {
} if(vehicleFailState) {
if(vehicle.Health == 0) { trace(s"vehicle was already destroyed")
trace(s"vehicle was already destroyed; but, everything is fine") if(pad.Railed) {
if(pad_railed) { Continent.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad, Continent.Id)
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) 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 { else {
if(pad_railed) { if(pad.Railed) {
Continent.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad, Continent.Id) 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) => case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) =>

View file

@ -0,0 +1,85 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.resourcesilo
import akka.actor.{ActorContext, Props}
import net.psforever.objects.{GlobalDefinitions, Player, Vehicle}
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.UseItemMessage
class ResourceSilo extends Amenity {
private var chargeLevel : Int = 0
private val maximumCharge : Int = 1000
// For the flashing red light on top of the NTU silo on
private var lowNtuWarningOn : Int = 0
// For the NTU display bar
private var capacitorDisplay : Long = 0
def ChargeLevel : Int = chargeLevel
// Do not call directly. Use ResourceSilo.UpdateChargeLevel message to handle logic such as low ntu warnings
def ChargeLevel_=(charge: Int) : Int = {
if(charge < 0 ) {
chargeLevel = 0
} else if (charge > maximumCharge) {
chargeLevel = maximumCharge
} else {
chargeLevel = charge
}
ChargeLevel
}
def MaximumCharge : Int = maximumCharge
def LowNtuWarningOn : Int = lowNtuWarningOn
def LowNtuWarningOn_=(enabled: Int) : Int = {
lowNtuWarningOn = enabled
LowNtuWarningOn
}
def CapacitorDisplay : Long = capacitorDisplay
def CapacitorDisplay_=(value: Long) : Long = {
capacitorDisplay = value
CapacitorDisplay
}
def Definition : ResourceSiloDefinition = GlobalDefinitions.resource_silo
def Use(player: Player, msg : UseItemMessage) : ResourceSilo.Exchange = {
ResourceSilo.ChargeEvent()
}
}
object ResourceSilo {
final case class Use(player: Player, msg : UseItemMessage)
final case class UpdateChargeLevel(amount: Int)
final case class LowNtuWarning(enabled: Int)
sealed trait Exchange
final case class ChargeEvent() extends Exchange
final case class ResourceSiloMessage(player: Player, msg : UseItemMessage, response : Exchange)
/**
* Overloaded constructor.
* @return the `Resource Silo` object
*/
def apply() : ResourceSilo = {
new ResourceSilo()
}
/**
* Instantiate an configure a `Resource Silo` object
* @param id the unique id that will be assigned to this entity
* @param context a context to allow the object to properly set up `ActorSystem` functionality;
* not necessary for this object, but required by signature
* @return the `Locker` object
*/
def Constructor(id : Int, context : ActorContext) : ResourceSilo = {
val obj = ResourceSilo()
obj.Actor = context.actorOf(Props(classOf[ResourceSiloControl], obj), s"${obj.Definition.Name}_$id")
obj.Actor ! "startup"
obj
}
}

View file

@ -0,0 +1,90 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.resourcesilo
import akka.actor.{Actor, ActorRef}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.structures.Building
import net.psforever.packet.game.PlanetSideGUID
import services.ServiceManager.Lookup
import services._
import services.avatar.{AvatarAction, AvatarServiceMessage}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
/**
* An `Actor` that handles messages being dispatched to a specific `Resource Silo`.
* @param resourceSilo the `Resource Silo` object being governed
*/
class ResourceSiloControl(resourceSilo : ResourceSilo) extends Actor with FactionAffinityBehavior.Check {
def FactionObject : FactionAffinity = resourceSilo
var avatarService : ActorRef = Actor.noSender
private[this] val log = org.log4s.getLogger
def receive : Receive = {
case "startup" =>
ServiceManager.serviceManager ! Lookup("avatar")
case ServiceManager.LookupResult("avatar", endpoint) =>
avatarService = endpoint
log.info("ResourceSiloControl: Silo " + resourceSilo.GUID + " Got avatar service " + endpoint)
// todo: This is just a temporary solution to drain NTU over time. When base object destruction is properly implemented NTU should be deducted when base objects repair themselves
context.system.scheduler.schedule(5 second, 5 second, self, ResourceSilo.UpdateChargeLevel(-1))
context.become(Processing)
case _ => ;
}
def Processing : Receive = checkBehavior.orElse {
case ResourceSilo.Use(player, msg) =>
sender ! ResourceSilo.ResourceSiloMessage(player, msg, resourceSilo.Use(player, msg))
case ResourceSilo.LowNtuWarning(enabled: Int) =>
resourceSilo.LowNtuWarningOn = enabled
log.trace(s"LowNtuWarning: Silo ${resourceSilo.GUID} low ntu warning set to ${enabled}")
avatarService ! AvatarServiceMessage(resourceSilo.Owner.asInstanceOf[Building].Zone.Id, AvatarAction.PlanetsideAttribute(PlanetSideGUID(resourceSilo.Owner.asInstanceOf[Building].ModelId), 47, resourceSilo.LowNtuWarningOn))
case ResourceSilo.UpdateChargeLevel(amount: Int) =>
val siloChargeBeforeChange = resourceSilo.ChargeLevel
// Increase if positive passed in or decrease charge level if negative number is passed in
resourceSilo.ChargeLevel += amount
if(resourceSilo.ChargeLevel > 0) {
log.trace(s"UpdateChargeLevel: Silo ${resourceSilo.GUID} set to ${resourceSilo.ChargeLevel}")
}
val ntuBarLevel = scala.math.round((resourceSilo.ChargeLevel.toFloat / resourceSilo.MaximumCharge.toFloat) * 10).toInt
// Only send updated capacitor display value to all clients if it has actually changed
if(resourceSilo.CapacitorDisplay != ntuBarLevel) {
log.trace(s"Silo ${resourceSilo.GUID} NTU bar level has changed from ${resourceSilo.CapacitorDisplay} to ${ntuBarLevel}")
resourceSilo.CapacitorDisplay = ntuBarLevel
resourceSilo.Owner.Actor ! Building.SendMapUpdateToAllClients()
avatarService ! AvatarServiceMessage(resourceSilo.Owner.asInstanceOf[Building].Zone.Id, AvatarAction.PlanetsideAttribute(resourceSilo.GUID, 45, resourceSilo.CapacitorDisplay))
}
val ntuIsLow = resourceSilo.ChargeLevel.toFloat / resourceSilo.MaximumCharge.toFloat < 0.2f
if(resourceSilo.LowNtuWarningOn == 1 && !ntuIsLow){
self ! ResourceSilo.LowNtuWarning(0)
} else if (resourceSilo.LowNtuWarningOn == 0 && ntuIsLow) {
self ! ResourceSilo.LowNtuWarning(1)
}
if(resourceSilo.ChargeLevel == 0 && siloChargeBeforeChange > 0) {
// Oops, someone let the base run out of power. Shut it all down.
//todo: Make base neutral if silo hits zero NTU
// temporarily disabled until warpgates can bring ANTs from sanctuary, otherwise we'd be stuck in a situation with an unpowered base and no way to get an ANT to refill it.
// avatarService ! AvatarServiceMessage(resourceSilo.Owner.asInstanceOf[Building].Zone.Id, AvatarAction.PlanetsideAttribute(PlanetSideGUID(resourceSilo.Owner.asInstanceOf[Building].ModelId), 48, 1))
} else if (siloChargeBeforeChange == 0 && resourceSilo.ChargeLevel > 0) {
// Power restored. Reactor Online. Sensors Online. Weapons Online. All systems nominal.
//todo: Check generator is online before starting up
avatarService ! AvatarServiceMessage(resourceSilo.Owner.asInstanceOf[Building].Zone.Id, AvatarAction.PlanetsideAttribute(PlanetSideGUID(resourceSilo.Owner.asInstanceOf[Building].ModelId), 48, 0))
}
case _ => ;
}
}

View file

@ -0,0 +1,12 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.resourcesilo
import net.psforever.objects.definition.ObjectDefinition
/**
* The definition for any `Resource Silo`.
* Object Id 731.
*/
class ResourceSiloDefinition extends ObjectDefinition(731) {
Name = "resource_silo"
}

View file

@ -11,8 +11,8 @@ import net.psforever.types.{PlanetSideEmpire, Vector3}
class Building(private val mapId : Int, private val zone : Zone, private val buildingType : StructureType.Value) extends PlanetSideServerObject { class Building(private val mapId : Int, private val zone : Zone, private val buildingType : StructureType.Value) extends PlanetSideServerObject {
/** /**
* The mapId is the identifier number used in BuildingInfoUpdateMessage. * The mapId is the identifier number used in BuildingInfoUpdateMessage.
* The modelId is the identifier number used in SetEmpireMessage. * The modelId is the identifier number used in SetEmpireMessage / Facility hacking / PlanetSideAttributeMessage.
*/ */
private var modelId : Option[Int] = None private var modelId : Option[Int] = None
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
private var amenities : List[Amenity] = List.empty private var amenities : List[Amenity] = List.empty
@ -68,6 +68,7 @@ object Building {
val obj = new Building(id, zone, buildingType) val obj = new Building(id, zone, buildingType)
obj.Position = location obj.Position = location
obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-$buildingType-building") obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-$buildingType-building")
obj.Actor ! "startup"
obj obj
} }
@ -75,6 +76,9 @@ object Building {
import akka.actor.Props import akka.actor.Props
val obj = new Building(id, zone, buildingType) val obj = new Building(id, zone, buildingType)
obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-$buildingType-building") obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-$buildingType-building")
obj.Actor ! "startup"
obj obj
} }
final case class SendMapUpdateToAllClients()
} }

View file

@ -1,20 +1,75 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.structures package net.psforever.objects.serverobject.structures
import akka.actor.Actor import akka.actor.{Actor, ActorRef}
import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.packet.game.{BuildingInfoUpdateMessage, PlanetSideGeneratorState}
import net.psforever.types.PlanetSideEmpire
import services.ServiceManager
import services.ServiceManager.Lookup
import services.galaxy.{GalaxyAction, GalaxyServiceMessage}
class BuildingControl(building : Building) extends Actor with FactionAffinityBehavior.Check { class BuildingControl(building : Building) extends Actor with FactionAffinityBehavior.Check {
def FactionObject : FactionAffinity = building def FactionObject : FactionAffinity = building
var galaxyService : ActorRef = Actor.noSender
private[this] val log = org.log4s.getLogger
def receive : Receive = checkBehavior.orElse { def receive : Receive = {
case "startup" =>
ServiceManager.serviceManager ! Lookup("galaxy") //ask for a resolver to deal with the GUID system
case ServiceManager.LookupResult("galaxy", endpoint) =>
galaxyService = endpoint
log.trace("BuildingControl: Building " + building.ModelId + " Got galaxy service " + endpoint)
// todo: This is just a temporary solution to drain NTU over time. When base object destruction is properly implemented NTU should be deducted when base objects repair themselves
context.become(Processing)
case _ => log.warn("Message received before startup called");
}
def Processing : Receive = checkBehavior.orElse {
case FactionAffinity.ConvertFactionAffinity(faction) => case FactionAffinity.ConvertFactionAffinity(faction) =>
val originalAffinity = building.Faction val originalAffinity = building.Faction
if(originalAffinity != (building.Faction = faction)) { if(originalAffinity != (building.Faction = faction)) {
building.Amenities.foreach(_.Actor forward FactionAffinity.ConfirmFactionAffinity()) building.Amenities.foreach(_.Actor forward FactionAffinity.ConfirmFactionAffinity())
} }
sender ! FactionAffinity.AssertFactionAffinity(building, faction) sender ! FactionAffinity.AssertFactionAffinity(building, faction)
case Building.SendMapUpdateToAllClients() =>
case _ => ; log.info(s"Sending BuildingInfoUpdateMessage update to all clients. Zone: ${building.Zone.Number} - Building: ${building.ModelId}")
var ntuLevel = 0
building.Amenities.filter(x => (x.Definition == GlobalDefinitions.resource_silo)).headOption.asInstanceOf[Option[ResourceSilo]] match {
case Some(obj: ResourceSilo) =>
ntuLevel = obj.CapacitorDisplay.toInt
case _ => ;
}
galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(
BuildingInfoUpdateMessage(
continent_id = building.Zone.Number, //Zone
building_id = building.Id, //Facility
ntu_level = ntuLevel,
is_hacked = false, //Hacked
PlanetSideEmpire.NEUTRAL, //Base hacked by
hack_time_remaining = 0, //Time remaining for hack (ms)
empire_own = building.Faction, //Base owned by
unk1 = 0, //!! Field != 0 will cause malformed packet. See class def.
unk1x = None,
generator_state = PlanetSideGeneratorState.Normal, //Generator state
spawn_tubes_normal = true, //Respawn tubes operating state
force_dome_active = false, //Force dome state
lattice_benefit = 0, //Lattice benefits
cavern_benefit = 0, //!! Field > 0 will cause malformed packet. See class def.
unk4 = Nil,
unk5 = 0,
unk6 = false,
unk7 = 8, //!! Field != 8 will cause malformed packet. See class def.
unk7x = None,
boost_spawn_pain = false, //Boosted spawn room pain field
boost_generator_pain = false //Boosted generator room pain field
)))
case default =>
log.warn(s"BuildingControl: Unknown message ${default} received from ${sender().path}")
} }
} }

View file

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

View file

@ -3,48 +3,17 @@ package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player import net.psforever.objects.Player
import net.psforever.objects.definition.VehicleDefinition import net.psforever.objects.definition.VehicleDefinition
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.structures.Amenity import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} import net.psforever.packet.game.{ItemTransactionMessage, TriggeredSound}
import net.psforever.types.{TransactionType, Vector3} import net.psforever.types.TransactionType
/** /**
* A structure-owned server object that is a "terminal" that can be accessed for amenities and services. * 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 * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/ */
class Terminal(tdef : TerminalDefinition) extends Amenity { class Terminal(tdef : TerminalDefinition) extends Amenity with Hackable {
/** An entry that maintains a reference to the `Player`, and the player's GUID and location when the message was received. */ HackSound = TriggeredSound.HackTerminal
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
}
//the following fields and related methods are neither finalized nor integrated; GOTO Request //the following fields and related methods are neither finalized nor integrated; GOTO Request
private var health : Int = 100 //TODO not real health value private var health : Int = 100 //TODO not real health value

View file

@ -2,6 +2,7 @@
package net.psforever.objects.serverobject.terminals package net.psforever.objects.serverobject.terminals
import akka.actor.Actor import akka.actor.Actor
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} 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) => case Terminal.Request(player, msg) =>
sender ! Terminal.TerminalMessage(player, msg, term.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 _ => ; case _ => ;
} }

View file

@ -15,7 +15,7 @@ class Seat(private val seatDef : SeatDefinition) {
/** /**
* Is this seat occupied? * Is this seat occupied?
* @return the GUID of the player sitting in this seat, or `None` if it is left vacant * @return the Player object of the player sitting in this seat, or `None` if it is left vacant
*/ */
def Occupant : Option[Player] = { def Occupant : Option[Player] = {
this.occupant this.occupant
@ -25,7 +25,7 @@ class Seat(private val seatDef : SeatDefinition) {
* The player is trying to sit down. * The player is trying to sit down.
* Seats are exclusive positions that can only hold one occupant at a time. * Seats are exclusive positions that can only hold one occupant at a time.
* @param player the player who wants to sit, or `None` if the occupant is getting up * @param player the player who wants to sit, or `None` if the occupant is getting up
* @return the GUID of the player sitting in this seat, or `None` if it is left vacant * @return the Player object of the player sitting in this seat, or `None` if it is left vacant
*/ */
def Occupant_=(player : Player) : Option[Player] = Occupant_=(Some(player)) def Occupant_=(player : Player) : Option[Player] = Occupant_=(Some(player))

View file

@ -4,7 +4,7 @@ package net.psforever.objects.vehicles
/** /**
* An `Enumeration` of exo-suit-based seat access restrictions.<br> * An `Enumeration` of exo-suit-based seat access restrictions.<br>
* <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. * `NoReinforcedOrMax` is next most common.
* `MaxOnly` is a rare seat restriction found in pairs on Galaxies and on the large "Ground Transport" vehicles. * `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 akka.actor.Actor
import net.psforever.objects.Vehicle 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.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.deploy.DeploymentBehavior import net.psforever.objects.serverobject.deploy.DeploymentBehavior
import net.psforever.types.ExoSuitType
/** /**
* An `Actor` that handles messages being dispatched to a specific `Vehicle`.<br> * 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 def Enabled : Receive = checkBehavior
.orElse(deployBehavior) .orElse(deployBehavior)
.orElse(mountBehavior)
.orElse(dismountBehavior) .orElse(dismountBehavior)
.orElse { .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) => case FactionAffinity.ConvertFactionAffinity(faction) =>
val originalAffinity = vehicle.Faction val originalAffinity = vehicle.Faction
if(originalAffinity != (vehicle.Faction = 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) { if(accessor == ActorRef.noSender) {
implicit val guid : NumberPoolHub = this.guid //passed into builderObject.Build implicitly 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") 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") transport = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"$Id-vehicles")
population = context.actorOf(Props(classOf[ZonePopulationActor], this, players, corpses), s"$Id-players") 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]) final case class NoValidSpawnPoint(zone_number : Int, spawn_group : Option[Int])
} }
/** object Ground {
* Message to relinguish an item and place in on the ground. final case class DropItem(item : Equipment, pos : Vector3, orient : Vector3)
* @param item the piece of `Equipment` final case class ItemOnGround(item : Equipment, pos : Vector3, orient : Vector3)
* @param pos where it is dropped final case class CanNotDropItem(zone : Zone, item : Equipment, reason : String)
* @param orient in which direction it is facing when dropped
*/
final case class DropItemOnGround(item : Equipment, pos : Vector3, orient : Vector3)
/** final case class PickupItem(item_guid : PlanetSideGUID)
* Message to attempt to acquire an item from the ground (before somoene else?). final case class ItemInHand(item : Equipment)
* @param player who wants the piece of `Equipment` final case class CanNotPickupItem(zone : Zone, item_guid : PlanetSideGUID, reason : String)
* @param item_guid the unique identifier of the piece of `Equipment`
*/
final case class GetItemOnGround(player : Player, item_guid : PlanetSideGUID)
/** final case class RemoveItem(item_guid : PlanetSideGUID)
* 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)
object Vehicle { object Vehicle {
final case class Spawn(vehicle : Vehicle) final case class Spawn(vehicle : Vehicle)

View file

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

View file

@ -12,22 +12,35 @@ import scala.collection.mutable.ListBuffer
* na * na
* @param equipmentOnGround a `List` of items (`Equipment`) dropped by players on the ground and can be collected again * @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 //private[this] val log = org.log4s.getLogger
def receive : Receive = { def receive : Receive = {
case Zone.DropItemOnGround(item, pos, orient) => case Zone.Ground.DropItem(item, pos, orient) =>
item.Position = pos sender ! (if(!item.HasGUID) {
item.Orientation = orient Zone.Ground.CanNotDropItem(zone, item, "not registered yet")
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")
} }
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 _ => ; 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 * @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) 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] { 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] = ( implicit val codec : Codec[ActionResultMessage] = (
("successful" | bool) >>:~ { res => ("successful" | bool) >>:~ { res =>

View file

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

View file

@ -71,36 +71,20 @@ object ObjectCreateDetailedMessage extends Marshallable[ObjectCreateDetailedMess
ObjectCreateDetailedMessage(ObjectCreateBase.streamLen(None, data), objectClass, guid, None, Some(data)) 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] ( implicit val codec : Codec[ObjectCreateDetailedMessage] = ObjectCreateBase.baseCodec.exmap[ObjectCreateDetailedMessage] (
{ {
case _ :: _ :: _ :: _ :: BitVector.empty :: HNil => case _ :: _ :: _ :: _ :: BitVector.empty :: HNil =>
Attempt.failure(Err("no data to decode")) Attempt.failure(Err("no data to decode"))
case len :: cls :: guid :: par :: data :: HNil => 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)) Attempt.successful(ObjectCreateDetailedMessage(len, cls, guid, par, obj))
}, },
{ {
@ -109,7 +93,14 @@ object ObjectCreateDetailedMessage extends Marshallable[ObjectCreateDetailedMess
case ObjectCreateDetailedMessage(_, cls, guid, par, Some(obj)) => 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 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) 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] { 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] = ( implicit val codec : Codec[ObjectDetachMessage] = (
("parent_guid" | PlanetSideGUID.codec) :: ("parent_guid" | PlanetSideGUID.codec) ::
("child_guid" | PlanetSideGUID.codec) :: ("child_guid" | PlanetSideGUID.codec) ::

View file

@ -39,7 +39,7 @@ import scodec.codecs._
* <br> * <br>
* Players/General:<br> * Players/General:<br>
* Server to client : <br> * Server to client : <br>
* `0 - health`<br> * `0 - health (setting to zero on vehicles/terminals will destroy them)`<br>
* `1 - healthMax`<br> * `1 - healthMax`<br>
* `2 - stamina`<br> * `2 - stamina`<br>
* `3 - staminaMax`<br> * `3 - staminaMax`<br>
@ -104,6 +104,11 @@ import scodec.codecs._
* `35 - BR. Value is the BR`<br> * `35 - BR. Value is the BR`<br>
* `36 - CR. Value is the CR`<br> * `36 - CR. Value is the CR`<br>
* `43 - Info on avatar name : 0 = Nothing, 1 = "(LD)" message`<br> * `43 - Info on avatar name : 0 = Nothing, 1 = "(LD)" message`<br>
* `45 - NTU charge bar 0-10, 5 = 50% full. Seems to apply to both ANT and NTU Silo (possibly siphons?)`<br>
* 47 - Sets base NTU level to CRITICAL. MUST use base modelId not base GUID
* 48 - Set to 1 to send base power loss message & turns on red warning lights throughout base. MUST use base modelId not base GUID
* 49 - Vehicle texture effects state? (>0 turns on ANT panel glow or ntu silo panel glow + orbs) (bit?)
* `52 - Vehicle particle effects? (>0 turns on orbs going towards ANT. Doesn't affect silo) (bit?)
* `53 - LFS. Value is 1 to flag LFS`<br> * `53 - LFS. Value is 1 to flag LFS`<br>
* `54 - Player "Aura". Values can be expressed in the first byte's lower nibble:`<br> * `54 - Player "Aura". Values can be expressed in the first byte's lower nibble:`<br>
* - 0 is nothing<br> * - 0 is nothing<br>
@ -114,10 +119,12 @@ import scodec.codecs._
* -- e.g., 13 = 8 + 4 + 1 = fire and LLU and plasma<br> * -- e.g., 13 = 8 + 4 + 1 = fire and LLU and plasma<br>
* `55 - "Someone is attempting to Heal you". Value is 1`<br> * `55 - "Someone is attempting to Heal you". Value is 1`<br>
* `56 - "Someone is attempting to Repair you". Value is 1`<br> * `56 - "Someone is attempting to Repair you". Value is 1`<br>
* `67 - Enables base shields (from cavern module/lock). MUST use base modelId not GUID`<br>
* `73 - "You are locked into the Core Beam. Charging your Module now.". Value is 1 to active`<br> * `73 - "You are locked into the Core Beam. Charging your Module now.". Value is 1 to active`<br>
* `77 - Cavern Facility Captures. Value is the number of captures`<br> * `77 - Cavern Facility Captures. Value is the number of captures`<br>
* `78 - Cavern Kills. Value is the number of kills`<br> * `78 - Cavern Kills. Value is the number of kills`<br>
* `106 - Custom Head`<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> * Client to Server : <br>
* `106 - Custom Head`<br> * `106 - Custom Head`<br>
* <br> * <br>
@ -126,13 +133,15 @@ import scodec.codecs._
* `11 - Gunner seat(s) permissions (same)`<br> * `11 - Gunner seat(s) permissions (same)`<br>
* `12 - Passenger seat(s) permissions (same)`<br> * `12 - Passenger seat(s) permissions (same)`<br>
* `13 - Trunk 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> * `22 - Toggles gunner and passenger mount points (1 = hides, 0 = reveals; this also locks their permissions)`<br>
* `68 - ???`<br> * `54 - Vehicle EMP? Plays sound as if vehicle had been hit by EMP`<br>
* `68 - Vehicle shield health`<br>
* `79 - ???`<br> * `79 - ???`<br>
* `80 - Damage vehicle (unknown value)`<br> * `80 - Damage vehicle (unknown value)`<br>
* `81 - ???`<br> * `81 - ???`<br>
* `113 - ???` * `113 - `Vehicle capacitor - e.g. Leviathan EMP charge`
*
* @param player_guid the player * @param player_guid the player
* @param attribute_type na * @param attribute_type na
* @param attribute_value na * @param attribute_value na

View file

@ -16,8 +16,8 @@ object TriggeredSound extends Enumeration {
val val
SpawnInTube, SpawnInTube,
Unknown1, HackTerminal,
Hack, HackVehicle,
HackDoor, HackDoor,
Unknown4, Unknown4,
LockedOut, 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> * (Where the child object was before it was moved is not specified or important.)<br>
* @param avatar_guid the player. * @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 object_guid can be : Door, Terminal, Avatar (medkit).
* @param unk2 ??? * @param unk2 ???
* @param unk3 ??? true when use a rek (false when door, medkit or open equip term) * @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)) * @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, final case class UseItemMessage(avatar_guid : PlanetSideGUID,
unk1 : Int, item_used_guid : Int,
object_guid : PlanetSideGUID, object_guid : PlanetSideGUID,
unk2 : Long, unk2 : Long,
unk3 : Boolean, unk3 : Boolean,
@ -40,7 +40,7 @@ final case class UseItemMessage(avatar_guid : PlanetSideGUID,
object UseItemMessage extends Marshallable[UseItemMessage] { object UseItemMessage extends Marshallable[UseItemMessage] {
implicit val codec : Codec[UseItemMessage] = ( implicit val codec : Codec[UseItemMessage] = (
("avatar_guid" | PlanetSideGUID.codec) :: ("avatar_guid" | PlanetSideGUID.codec) ::
("unk1" | uint16L) :: ("item_used_guid" | uint16L) ::
("object_guid" | PlanetSideGUID.codec) :: ("object_guid" | PlanetSideGUID.codec) ::
("unk2" | uint32L) :: ("unk2" | uint32L) ::
("unk3" | bool) :: ("unk3" | bool) ::

View file

@ -6,10 +6,25 @@ import net.psforever.types.Vector3
import scodec.Codec import scodec.Codec
import scodec.codecs._ import scodec.codecs._
/** WeaponFireMessage seems to be sent each time a weapon actually shoots. /**
* WeaponFireMessage seems to be sent each time a weapon actually shoots.
* *
* See [[PlayerStateMessageUpstream]] for explanation of seq_time. *
*/ * @param seq_time See [[PlayerStateMessageUpstream]] for explanation of seq_time.
* @param weapon_guid
* @param projectile_guid
* @param shot_origin
* @param unk1 Always zero from testing so far
* @param unk2 Seems semi-random
* @param unk3 Seems semi-random
* @param unk4 Maximum travel distance in meters - seems to be zero for decimator rockets
* @param unk5 Possibly always 255 from testing
* @param unk6 0 for bullet
* 1 for possibly delayed explosion (thumper alt fire) or thresher/leviathan flux cannon
* 2 for vs starfire (lockon type?)
* 3 for thrown (e.g. grenades)
* @param unk7 Seems to be thrown weapon velocity/direction
*/
final case class WeaponFireMessage(seq_time : Int, final case class WeaponFireMessage(seq_time : Int,
weapon_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID,
projectile_guid : PlanetSideGUID, projectile_guid : PlanetSideGUID,

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 scodec.codecs._
import shapeless.{::, HNil} 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> * A part of a representation of the avatar portion of `ObjectCreateDetailedMessage` packet data.<br>
* <br> * <br>
@ -60,8 +28,13 @@ final case class BasicCharacterData(name : String,
* <br> * <br>
* Exploration:<br> * Exploration:<br>
* How do I crouch? * How do I crouch?
* @param pos the position of the character in the world environment (in three coordinates) * @see `CharacterData`<br>
* @param basic_appearance the player's cardinal appearance settings * `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; * @param voice2 na;
* affects the frequency by which the character's voice is heard (somehow); * affects the frequency by which the character's voice is heard (somehow);
* commonly 3 for best results * 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 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 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 * @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, final case class CharacterAppearanceData(app : BasicCharacterData,
basic_appearance : BasicCharacterData,
voice2 : Int, voice2 : Int,
black_ops : Boolean, black_ops : Boolean,
jammered : Boolean, jammered : Boolean,
@ -110,22 +75,36 @@ final case class CharacterAppearanceData(pos : PlacementData,
is_cloaking : Boolean, is_cloaking : Boolean,
charging_pose : Boolean, charging_pose : Boolean,
on_zipline : Boolean, on_zipline : Boolean,
ribbons : RibbonBars) extends StreamBitSize { ribbons : RibbonBars)
(name_padding : Int) extends StreamBitSize {
override def bitsize : Long = { override def bitsize : Long = {
//factor guard bool values into the base size, not its corresponding optional field //factor guard bool values into the base size, not its corresponding optional field
val placementSize : Long = pos.bitsize val nameStringSize : Long = StreamBitSize.stringBitSize(app.name, 16) + name_padding
val nameStringSize : Long = StreamBitSize.stringBitSize(basic_appearance.name, 16) + CharacterAppearanceData.namePadding(pos.vel) val outfitStringSize : Long = StreamBitSize.stringBitSize(outfit_name, 16) +
val outfitStringSize : Long = StreamBitSize.stringBitSize(outfit_name, 16) + CharacterAppearanceData.outfitNamePadding CharacterAppearanceData.outfitNamePadding //even if the outfit_name is blank, string is always padded
val altModelSize = CharacterAppearanceData.altModelBit(this).getOrElse(0) 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] { 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. * 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 the latter case, a ball of colored energy.
* In this state, the length of the stream of data is modified. * In this state, the length of the stream of data is modified.
* @param app the appearance * @param app the appearance
@ -138,20 +117,6 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
None 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. * Get the padding of the outfit's name.
* The padding will always be a number 0-7. * The padding will always be a number 0-7.
@ -161,64 +126,63 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
6 6
} }
implicit val codec : Codec[CharacterAppearanceData] = ( def codec(name_padding : Int) : Codec[CharacterAppearanceData] = (
("pos" | PlacementData.codec) >>:~ { pos => ("faction" | PlanetSideEmpire.codec) ::
("faction" | PlanetSideEmpire.codec) :: ("black_ops" | bool) ::
("black_ops" | bool) :: (("alt_model" | bool) >>:~ { alt_model => //modifies stream format (to display alternate player models)
(("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 ignore(1) :: //unknown
("jammered" | bool) :: ("backpack" | bool) :: //requires alt_model flag (does NOT require health == 0)
bool :: //crashes client bool :: //stream misalignment when set
uint(16) :: //unknown, but usually 0 ("facingPitch" | Angular.codec_pitch) ::
("name" | PacketHelpers.encodedWideStringAligned( namePadding(pos.vel) )) :: ("facingYawUpper" | Angular.codec_yaw(0f)) ::
("exosuit" | ExoSuitType.codec) :: ignore(1) :: //unknown
ignore(2) :: //unknown conditional(alt_model, bool) :: //alt_model flag adds a bit before lfs
("sex" | CharacterGender.codec) :: ignore(1) :: //an alternate lfs?
("head" | uint8L) :: ("lfs" | bool) ::
("voice" | uint(3)) :: ("grenade_state" | GrenadeState.codec_2u) :: //note: bin10 and bin11 are neutral (bin00 is not defined)
("voice2" | uint2L) :: ("is_cloaking" | bool) ::
ignore(78) :: //unknown ignore(1) :: //unknown
uint16L :: //usually either 0 or 65535 bool :: //stream misalignment when set
uint32L :: //for outfit_name (below) to be visible in-game, this value should be non-zero ("charging_pose" | bool) ::
("outfit_name" | PacketHelpers.encodedWideStringAligned( outfitNamePadding )) :: ignore(1) :: //alternate charging pose?
("outfit_logo" | uint8L) :: ("on_zipline" | bool) :: //requires alt_model flag
ignore(1) :: //unknown ("ribbons" | RibbonBars.codec)
("backpack" | bool) :: //requires alt_model flag (does NOT require health == 0) })
bool :: //stream misalignment when set ).exmap[CharacterAppearanceData] (
("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 | case _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: HNil |
_ :: _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: HNil => _ :: _ :: false :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: _ :: true :: _ :: HNil =>
Attempt.Failure(Err("invalid character appearance data; can not encode alternate model without required bit set")) 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( 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 _ => case _ =>
Attempt.Failure(Err("invalid character appearance data; can not encode")) 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")) 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 val has_outfit_name : Long = outfit.length.toLong //TODO this is a kludge
var alt_model : Boolean = false var alt_model : Boolean = false
var alt_model_extrabit : Option[Boolean] = None var alt_model_extrabit : Option[Boolean] = None
@ -227,11 +191,13 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
alt_model_extrabit = Some(false) alt_model_extrabit = Some(false)
} }
Attempt.successful( 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 _ => case _ =>
Attempt.Failure(Err("invalid character appearance data; can not decode")) 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. * A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.<br>
* This densely-packed information outlines most of the specifics of depicting some other character.<br>
* <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. * 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. * 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." * Of the inventory for this character, only the initial five weapon slots are defined.<br>
* 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>
* <br> * <br>
* Divisions exist to make the data more manageable. * In the "backend of the client," the character produced by this data is no different
* The first division of data only manages the general appearance of the player's in-game model. * from the kind of character that could be declared a given player's avatar.
* The second division (currently, the fields actually in this class) manages the status of the character. * In terms of equipment and complicated features common to an avatar character, however,
* In general, it passes more simplified data about the character, the minimum that is necessary to explain status to some other player. * any user would find this character ill-equipped.
* 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
* @param health the amount of health the player has, as a percentage of a filled bar; * @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; * 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; * @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 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; * @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 * 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; * @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); * 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 * these flags do not exist if they are not applicable
* @param inventory the avatar's inventory; * @param is_backpack this player character should be depicted as a corpse;
* typically, only the tools and weapons in the equipment holster slots * corpses are either coffins (defunct), backpacks (normal), or a pastry (festive);
* @param drawn_slot the holster that is initially drawn; * the alternate model bit should be flipped
* defaults to `DrawnSlot.None` * @param is_seated this player character is seated in a vehicle or mounted to some other object;
* @see `CharacterAppearanceData` * alternate format for data parsing applies
* @see `DetailedCharacterData` * @see `DetailedCharacterData`<br>
* @see `InventoryData` * `CharacterAppearanceData`
* @see `DrawnSlot`
*/ */
final case class CharacterData(appearance : CharacterAppearanceData, final case class CharacterData(health : Int,
health : Int,
armor : Int, armor : Int,
uniform_upgrade : UniformStyle.Value, uniform_upgrade : UniformStyle.Value,
unk : Int,
command_rank : Int, command_rank : Int,
implant_effects : Option[ImplantEffects.Value], implant_effects : Option[ImplantEffects.Value],
cosmetics : Option[Cosmetics], cosmetics : Option[Cosmetics])
inventory : Option[InventoryData], (is_backpack : Boolean,
drawn_slot : DrawnSlot.Value = DrawnSlot.None is_seated : Boolean) extends ConstructorData {
) extends ConstructorData {
override def bitsize : Long = { override def bitsize : Long = {
//factor guard bool values into the base size, not its corresponding optional field //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 effectsSize : Long = if(implant_effects.isDefined) { 4L } else { 0L }
val cosmeticsSize : Long = if(cosmetics.isDefined) { cosmetics.get.bitsize } else { 0L } val cosmeticsSize : Long = if(cosmetics.isDefined) { cosmetics.get.bitsize } else { 0L }
val inventorySize : Long = if(inventory.isDefined) { inventory.get.bitsize } else { 0L } 11L + seatedSize + effectsSize + cosmeticsSize
32L + appearanceSize + effectsSize + cosmeticsSize + inventorySize
} }
} }
object CharacterData extends Marshallable[CharacterData] { object CharacterData extends Marshallable[CharacterData] {
/** /**
* An overloaded constructor for `CharacterData` that allows for a not-optional inventory. * 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 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 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 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 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 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 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 * @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 = def apply(health : Int, armor : Int, uniform : UniformStyle.Value, cr : Int, implant_effects : Option[ImplantEffects.Value], cosmetics : Option[Cosmetics]) : (Boolean,Boolean)=>CharacterData =
new CharacterData(appearance, health, armor, uniform, cr, implant_effects, cosmetics, Some(inv), drawn_slot) CharacterData(health, armor, uniform, 0, cr, implant_effects, cosmetics)
implicit val codec : Codec[CharacterData] = ( def codec(is_backpack : Boolean) : Codec[CharacterData] = (
("app" | CharacterAppearanceData.codec) :: ("health" | uint8L) :: //dead state when health == 0
("health" | uint8L) :: //dead state when health == 0
("armor" | uint8L) :: ("armor" | uint8L) ::
(("uniform_upgrade" | UniformStyle.codec) >>:~ { style => (("uniform_upgrade" | UniformStyle.codec) >>:~ { style =>
ignore(3) :: //unknown ignore(3) :: //unknown
("command_rank" | uintL(3)) :: ("command_rank" | uintL(3)) ::
bool :: //stream misalignment when != 1 bool :: //misalignment when == 1
optional(bool, "implant_effects" | ImplantEffects.codec) :: optional(bool, "implant_effects" | ImplantEffects.codec) ::
conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | Cosmetics.codec) :: conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | Cosmetics.codec)
optional(bool, "inventory" | InventoryData.codec) ::
("drawn_slot" | DrawnSlot.codec) ::
bool //usually false
}) })
).exmap[CharacterData] ( ).exmap[CharacterData] (
{ {
case app :: health :: armor :: uniform :: _ :: cr :: false :: implant_effects :: cosmetics :: inv :: drawn_slot :: false :: HNil => case health :: armor :: uniform :: _ :: cr :: false :: implant_effects :: cosmetics :: HNil =>
var newHealth = health val newHealth = if(is_backpack) { 0 } else { health }
if(app.backpack) { Attempt.Successful(CharacterData(newHealth, armor, uniform, 0, cr, implant_effects, cosmetics)(is_backpack, false))
newHealth = 0
}
Attempt.Successful(CharacterData(app, newHealth, armor, uniform, cr, implant_effects, cosmetics, inv, drawn_slot))
case _ => case _ =>
Attempt.Failure(Err("invalid character data; can not encode")) Attempt.Failure(Err("invalid character data; can not encode"))
}, },
{ {
case CharacterData(app, health, armor, uniform, cr, implant_effects, cosmetics, inv, drawn_slot) => case CharacterData(health, armor, uniform, _, cr, implant_effects, cosmetics) =>
var newHealth = health val newHealth = if(is_backpack) { 0 } else { health }
if(app.backpack) { Attempt.Successful(newHealth :: armor :: uniform :: () :: cr :: false :: implant_effects :: cosmetics :: HNil)
newHealth = 0
}
Attempt.Successful(app :: newHealth :: armor :: uniform :: () :: cr :: false :: implant_effects :: cosmetics :: inv :: drawn_slot :: false :: HNil)
case _ => case _ =>
Attempt.Failure(Err("invalid character data; can not decode")) 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. * A representation of a portion of an avatar's `ObjectCreateDetailedMessage` packet data.<br>
* This densely-packed information outlines most of the specifics required to depict a character as an avatar.<br>
* <br> * <br>
* As an avatar, the character created by this data is expected to be controllable by the client that gets sent this data. * 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.<br> * It goes into depth about information related to the given character in-game career that is not revealed to other players.
* <br> * To be specific, it passes more thorough data about the character that the client can display to the owner of the client.
* 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.
* For example, health is a full number, rather than a percentage. * 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. * 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). * Additionally, a full inventory, as opposed to the initial five weapon slots.
* The fourth is the inventory (composed of `Direct`-type objects).
* @param appearance data about the avatar's basic aesthetics
* @param bep the avatar's battle experience points, which determines his Battle Rank * @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 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; * @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; * @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; * they become available at battle rank 24;
* these flags do not exist if they are not applicable * these flags do not exist if they are not applicable
* @param inventory the avatar's inventory * @see `CharacterData`<br>
* @param drawn_slot the holster that is initially drawn * `CertificationType`
* @see `CharacterAppearanceData`<br>
* `CharacterData`<br>
* `CertificationType`<br>
* `InventoryData`<br>
* `DrawnSlot`
*/ */
final case class DetailedCharacterData(appearance : CharacterAppearanceData, final case class DetailedCharacterData(bep : Long,
bep : Long,
cep : Long, cep : Long,
healthMax : Int, healthMax : Int,
health : Int, health : Int,
@ -96,20 +82,17 @@ final case class DetailedCharacterData(appearance : CharacterAppearanceData,
implants : List[ImplantEntry], implants : List[ImplantEntry],
firstTimeEvents : List[String], firstTimeEvents : List[String],
tutorials : List[String], tutorials : List[String],
cosmetics : Option[Cosmetics], cosmetics : Option[Cosmetics])
inventory : Option[InventoryData], (pad_length : Option[Int]) extends ConstructorData {
drawn_slot : DrawnSlot.Value = DrawnSlot.None
) extends ConstructorData {
override def bitsize : Long = { override def bitsize : Long = {
//factor guard bool values into the base size, not corresponding optional fields, unless contained or enumerated //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 val certSize = (certs.length + 1) * 8 //cert list
var implantSize : Long = 0L //implant list var implantSize : Long = 0L //implant list
for(entry <- implants) { for(entry <- implants) {
implantSize += entry.bitsize implantSize += entry.bitsize
} }
val implantPadding = DetailedCharacterData.implantFieldPadding(implants, CharacterAppearanceData.altModelBit(appearance)) val implantPadding = DetailedCharacterData.implantFieldPadding(implants, pad_length)
val fteLen = firstTimeEvents.size //fte list val fteLen = firstTimeEvents.size //fte list
var eventListSize : Long = 32L + DetailedCharacterData.ftePadding(fteLen, implantPadding) var eventListSize : Long = 32L + DetailedCharacterData.ftePadding(fteLen, implantPadding)
for(str <- firstTimeEvents) { for(str <- firstTimeEvents) {
@ -123,20 +106,13 @@ final case class DetailedCharacterData(appearance : CharacterAppearanceData,
val br24 = DetailedCharacterData.isBR24(bep) //character is at least BR24 val br24 = DetailedCharacterData.isBR24(bep) //character is at least BR24
val extraBitSize : Long = if(br24) { 33L } else { 46L } val extraBitSize : Long = if(br24) { 33L } else { 46L }
val cosmeticsSize : Long = if(br24) { cosmetics.get.bitsize } else { 0L } val cosmeticsSize : Long = if(br24) { cosmetics.get.bitsize } else { 0L }
val inventorySize : Long = if(inventory.isDefined) { //inventory 598L + certSize + implantSize + eventListSize + extraBitSize + cosmeticsSize + tutorialListSize
inventory.get.bitsize
}
else {
0L
}
603L + appearanceSize + certSize + implantSize + eventListSize + extraBitSize + cosmeticsSize + tutorialListSize + inventorySize
} }
} }
object DetailedCharacterData extends Marshallable[DetailedCharacterData] { object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
/** /**
* Overloaded constructor for `DetailedCharacterData` that requires an inventory and drops unknown values. * 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 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 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 * @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 implants the `List` of implant slots currently possessed by this avatar
* @param firstTimeEvents the list of first time events performed 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 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 * @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 = 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 =
new DetailedCharacterData(appearance, bep, cep, healthMax, health, armor, 1, 7, 7, staminaMax, stamina, certs, implants, firstTimeEvents, tutorials, cosmetics, Some(inventory), drawn_slot) DetailedCharacterData(bep, cep, healthMax, health, armor, 1, 7, 7, staminaMax, stamina, certs, implants, firstTimeEvents, tutorials, cosmetics)
/** /**
* `Codec` for entries in the `List` of implants. * `Codec` for entries in the `List` of implants.
@ -278,57 +252,52 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
def isBR24(bep : Long) : Boolean = bep > 2286230 def isBR24(bep : Long) : Boolean = bep > 2286230
implicit val codec : Codec[DetailedCharacterData] = ( def codec(pad_length : Option[Int]) : Codec[DetailedCharacterData] = (
("appearance" | CharacterAppearanceData.codec) >>:~ { app => ("bep" | uint32L) >>:~ { bep =>
("bep" | uint32L) >>:~ { bep => ("cep" | uint32L) ::
("cep" | uint32L) :: ignore(96) ::
ignore(96) :: ("healthMax" | uint16L) ::
("healthMax" | uint16L) :: ("health" | uint16L) ::
("health" | uint16L) :: ignore(1) ::
ignore(1) :: ("armor" | uint16L) ::
("armor" | uint16L) :: ignore(9) ::
ignore(9) :: ("unk1" | uint8L) ::
("unk1" | uint8L) :: ignore(8) ::
ignore(8) :: ("unk2" | uint4L) ::
("unk2" | uint4L) :: ("unk3" | uintL(3)) ::
("unk3" | uintL(3)) :: ("staminaMax" | uint16L) ::
("staminaMax" | uint16L) :: ("stamina" | uint16L) ::
("stamina" | uint16L) :: ignore(147) ::
ignore(147) :: ("certs" | listOfN(uint8L, CertificationType.codec)) ::
("certs" | listOfN(uint8L, CertificationType.codec)) :: optional(bool, uint32L) :: //ask about sample CCRIDER
optional(bool, uint32L) :: //ask about sample CCRIDER ignore(4) ::
ignore(4) :: (("implants" | PacketHelpers.listOfNSized(numberOfImplantSlots(bep), implant_entry_codec)) >>:~ { implants =>
(("implants" | PacketHelpers.listOfNSized(numberOfImplantSlots(bep), implant_entry_codec)) >>:~ { implants => ignore(12) ::
ignore(12) :: (("firstTimeEvent_length" | uint32L) >>:~ { len =>
(("firstTimeEvent_length" | uint32L) >>:~ { len => conditional(len > 0, "firstTimeEvent_firstEntry" | PacketHelpers.encodedStringAligned(ftePadding(len, implantFieldPadding(implants, pad_length)))) ::
conditional(len > 0, "firstTimeEvent_firstEntry" | PacketHelpers.encodedStringAligned(ftePadding(len, implantFieldPadding(implants, CharacterAppearanceData.altModelBit(app))))) :: ("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString)) ::
("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString)) :: (("tutorial_length" | uint32L) >>:~ { len2 =>
(("tutorial_length" | uint32L) >>:~ { len2 => conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned(tutPadding(len, len2, implantFieldPadding(implants, pad_length)))) ::
conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned(tutPadding(len, len2, implantFieldPadding(implants, CharacterAppearanceData.altModelBit(app))))) :: ("tutorial_list" | PacketHelpers.listOfNSized(len2 - 1, PacketHelpers.encodedString)) ::
("tutorial_list" | PacketHelpers.listOfNSized(len2 - 1, PacketHelpers.encodedString)) :: ignore(160) ::
ignore(160) :: (bool >>:~ { br24 => //BR24+
(bool >>:~ { br24 => //BR24+ newcodecs.binary_choice(br24, ignore(33), ignore(46)) ::
newcodecs.binary_choice(br24, ignore(33), ignore(46)) :: conditional(br24, Cosmetics.codec)
conditional(br24, Cosmetics.codec) :: })
optional(bool, "inventory" | InventoryData.codec_detailed) :: })
("drawn_slot" | DrawnSlot.codec) :: })
bool //usually false })
})
})
})
})
}
} }
).exmap[DetailedCharacterData] ( ).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 //prepend the displaced first elements to their lists
val fteList : List[String] = if(fte0.isDefined) { fte0.get +: fte1 } else { fte1 } val fteList : List[String] = if(fte0.isDefined) { fte0.get +: fte1 } else { fte1 }
val tutList : List[String] = if(tut0.isDefined) { tut0.get +: tut1 } else { tut1 } 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 implantCapacity : Int = numberOfImplantSlots(bep)
val implantList = if(implants.length > implantCapacity) { val implantList = if(implants.length > implantCapacity) {
implants.slice(0, implantCapacity) implants.slice(0, implantCapacity)
@ -349,7 +318,9 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
} }
val br24 : Boolean = isBR24(bep) val br24 : Boolean = isBR24(bep)
val cosmetics : Option[Cosmetics] = if(br24) { cos } else { None } 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.advanced_ace => ConstructorData.genericCodec(DetailedACEData.codec, "advanced ace")
case ObjectClass.boomer_trigger => ConstructorData.genericCodec(DetailedBoomerTriggerData.codec, "boomer trigger") case ObjectClass.boomer_trigger => ConstructorData.genericCodec(DetailedBoomerTriggerData.codec, "boomer trigger")
//other //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") case ObjectClass.locker_container => ConstructorData.genericCodec(DetailedLockerContainerData.codec, "locker container")
//failure case //failure case
case _ => defaultFailureCodec(objClass) 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. * 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> * This function services `0x17` `ObjectCreateMessage` packet data.<br>
@ -953,6 +961,7 @@ object ObjectClass {
//other //other
case ObjectClass.ams_order_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal") case ObjectClass.ams_order_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.ams_respawn_tube => 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.bfr_rearm_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.implant_terminal_interface => ConstructorData.genericCodec(CommonTerminalData.codec, "implant terminal") case ObjectClass.implant_terminal_interface => ConstructorData.genericCodec(CommonTerminalData.codec, "implant terminal")
case ObjectClass.lodestar_repair_terminal => ConstructorData.genericCodec(CommonTerminalData.codec, "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") case ObjectClass.wasp => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Variant), "vehicle")
//other //other
case ObjectClass.ams_respawn_tube => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal") 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.capture_flag => ConstructorData.genericCodec(CaptureFlagData.codec, "capture flag")
case ObjectClass.implant_terminal_interface => ConstructorData.genericCodec(CommonTerminalData.codec, "implant terminal") case ObjectClass.implant_terminal_interface => ConstructorData.genericCodec(CommonTerminalData.codec, "implant terminal")
case ObjectClass.locker_container => ConstructorData.genericCodec(LockerContainerData.codec, "locker container") 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 Prefab {
object Vehicle { 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 = { 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( Some(InventoryData(List(
InternalSlot(ObjectClass.matrix_terminalc, matrix_guid, 1, CommonTerminalData(faction)), InternalSlot(ObjectClass.matrix_terminalc, matrix_guid, 1, CommonTerminalData(faction)),
InternalSlot(ObjectClass.ams_respawn_tube, respawn_guid, 2, 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_terminala, term_a_guid, 3, CommonTerminalData(faction)),
InternalSlot(ObjectClass.order_terminalb, term_b_guid, 4, 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 = { 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 = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.apc_weapon_systemc_nc, weapon1_guid, 11, InventoryItemData(ObjectClass.apc_weapon_systemc_nc, weapon1_guid, 11,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_20mm, ammo1_guid, 0, AmmoBoxData(8)) 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)) WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo6_guid, 0, AmmoBoxData(8))
) :: Nil ) :: 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 = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.apc_weapon_systemc_tr, weapon1_guid, 11, InventoryItemData(ObjectClass.apc_weapon_systemc_tr, weapon1_guid, 11,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo1_guid, 0, AmmoBoxData(8)) 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)) WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo6_guid, 0, AmmoBoxData(8))
) :: Nil ) :: 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 = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.apc_weapon_systemc_vs, weapon1_guid, 11, InventoryItemData(ObjectClass.apc_weapon_systemc_vs, weapon1_guid, 11,
WeaponData(0x6, 0x8, 0, ObjectClass.flux_cannon_thresher_battery, ammo1_guid, 0, AmmoBoxData(8)) 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)) WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo6_guid, 0, AmmoBoxData(8))
) :: Nil ) :: 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 = { 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, //VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None,
Some(InventoryData( VehicleData(CommonFieldData(loc, faction, 2), health, DriveState.State7, false,
Some(InventoryData(
InventoryItemData(ObjectClass.aurora_weapon_systema, weapon1_guid, 5, InventoryItemData(ObjectClass.aurora_weapon_systema, weapon1_guid, 5,
WeaponData(0x6, 0x8, 0, ObjectClass.fluxpod_ammo, ammo11_guid, 0, AmmoBoxData(0x8)) 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)) WeaponData(0x6, 0x8, 0, ObjectClass.fluxpod_ammo, ammo21_guid, 0, AmmoBoxData(0x8))
) :: Nil ) :: 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 = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.battlewagon_weapon_systema, weapon1_guid, 5, InventoryItemData(ObjectClass.battlewagon_weapon_systema, weapon1_guid, 5,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo1_guid, 0, AmmoBoxData(0x8)) 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)) WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo4_guid, 0, AmmoBoxData(0x8))
) :: Nil ) :: 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 = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.cannon_dropship_20mm, weapon1_guid, 12, InventoryItemData(ObjectClass.cannon_dropship_20mm, weapon1_guid, 12,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_20mm, ammo1_guid, 0, AmmoBoxData(8)) 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)) WeaponData(0x6, 0x8, 0, ObjectClass.bullet_20mm, ammo3_guid, 0, AmmoBoxData(8))
) :: Nil ) :: Nil
)) ))
)(VehicleFormat.Variant) )
} }
def flail(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID, terminal_guid : PlanetSideGUID) : VehicleData = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.flail_weapon, weapon_guid, 1, InventoryItemData(ObjectClass.flail_weapon, weapon_guid, 1,
WeaponData(0x6, 0x8, 0, ObjectClass.ancient_ammo_vehicle, ammo_guid, 0, AmmoBoxData(8)) 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 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 = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.fury_weapon_systema, weapon_guid, 1, InventoryItemData(ObjectClass.fury_weapon_systema, weapon_guid, 1,
WeaponData(0x4, 0x8, ObjectClass.hellfire_ammo, ammo_guid, 0, AmmoBoxData(0x8)) WeaponData(0x4, 0x8, ObjectClass.hellfire_ammo, ammo_guid, 0, AmmoBoxData(0x8))
) :: Nil ) :: 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 = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.galaxy_gunship_cannon, weapon1_guid, 6, InventoryItemData(ObjectClass.galaxy_gunship_cannon, weapon1_guid, 6,
WeaponData(0x6, 0x8, 0, ObjectClass.heavy_grenade_mortar, ammo1_guid, 0, AmmoBoxData(8)) 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)) WeaponData(0x6, 0x8, 0, ObjectClass.bullet_35mm, ammo5_guid, 0, AmmoBoxData(8))
) :: Nil ) :: 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 = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.liberator_weapon_system, weapon1_guid, 3, InventoryItemData(ObjectClass.liberator_weapon_system, weapon1_guid, 3,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_35mm, ammo1_guid, 0, AmmoBoxData(8)) 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)) WeaponData(0x6, 0x8, 0, ObjectClass.bullet_25mm, ammo4_guid, 0 ,AmmoBoxData(8))
) :: Nil ) :: Nil
)) ))
)(VehicleFormat.Variant) )
} }
def lightgunship(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.lightgunship_weapon_system, weapon_guid, 1, 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)) WeaponData(0x6, 0x8, 0, ObjectClass.bullet_20mm, ammo1_guid, 0, AmmoBoxData(8), ObjectClass.reaver_rocket, ammo2_guid,1, AmmoBoxData(8))
) :: Nil ) :: Nil
)) ))
)(VehicleFormat.Variant) )
} }
def lightning(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.lightning_weapon_system, weapon_guid, 1, 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)) WeaponData(0x4, 0x8, 0, ObjectClass.bullet_75mm, ammo1_guid, 0, AmmoBoxData(0x0), ObjectClass.bullet_12mm, ammo2_guid, 1, AmmoBoxData(0x0))
) :: Nil) ) :: 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 = { 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( Some(InventoryData(List(
InternalSlot(ObjectClass.lodestar_repair_terminal, repair1_guid, 2, CommonTerminalData(faction, 2)), InternalSlot(ObjectClass.lodestar_repair_terminal, repair1_guid, 2, CommonTerminalData(faction, 2)),
InternalSlot(ObjectClass.lodestar_repair_terminal, repair2_guid, 3, 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_rearm1_guid, 6, CommonTerminalData(faction, 2)),
InternalSlot(ObjectClass.bfr_rearm_terminal, bfr_rearm2_guid, 7, 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 = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.particle_beam_magrider, weapon1_guid, 2, InventoryItemData(ObjectClass.particle_beam_magrider, weapon1_guid, 2,
WeaponData(0x6, 0x8, 0, ObjectClass.pulse_battery, ammo1_guid, 0, AmmoBoxData(8)) 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)) WeaponData(0x6, 0x8, 0, ObjectClass.heavy_rail_beam_battery, ammo2_guid, 0, AmmoBoxData(8))
) :: Nil ) :: 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 = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.mediumtransport_weapon_systemA, weapon1_guid, 5, InventoryItemData(ObjectClass.mediumtransport_weapon_systemA, weapon1_guid, 5,
WeaponData(0x6, 0x8, ObjectClass.bullet_20mm, ammo1_guid, 0, AmmoBoxData(0x8)) 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)) WeaponData(0x6, 0x8, ObjectClass.bullet_20mm, ammo2_guid, 0, AmmoBoxData(0x8))
) :: Nil ) :: Nil
)) ))
)(VehicleFormat.Normal) )
} }
def mosquito(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.rotarychaingun_mosquito, weapon_guid, 1, InventoryItemData(ObjectClass.rotarychaingun_mosquito, weapon_guid, 1,
WeaponData(0x6, 0x8, ObjectClass.bullet_12mm, ammo_guid, 0, AmmoBoxData(8)) WeaponData(0x6, 0x8, ObjectClass.bullet_12mm, ammo_guid, 0, AmmoBoxData(8))
) :: Nil ) :: Nil
)) ))
)(VehicleFormat.Variant) )
} }
def phantasm(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int) : VehicleData = { 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 = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.prowler_weapon_systemA, weapon1_guid, 3, InventoryItemData(ObjectClass.prowler_weapon_systemA, weapon1_guid, 3,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_105mm, ammo1_guid, 0, AmmoBoxData(8)) 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)) WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo2_guid, 0, AmmoBoxData(8))
) :: Nil ) :: Nil
)) ))
)(VehicleFormat.Normal) )
} }
def quadassault(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.quadassault_weapon_system, weapon_guid, 1, InventoryItemData(ObjectClass.quadassault_weapon_system, weapon_guid, 1,
WeaponData(0x6, 0x8, ObjectClass.bullet_12mm, ammo_guid, 0, AmmoBoxData(0x8)) WeaponData(0x6, 0x8, ObjectClass.bullet_12mm, ammo_guid, 0, AmmoBoxData(0x8))
) :: Nil ) :: Nil
)) ))
)(VehicleFormat.Normal) )
} }
def quadstealth(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int) : VehicleData = { 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 = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.teleportpad_terminal, terminal_guid, 1, CommonTerminalData(faction, 2)) :: Nil 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 = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.skyguard_weapon_system, weapon_guid, 2, 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)) WeaponData(0x6, 0x8, 0, ObjectClass.skyguard_flak_cannon_ammo, ammo1_guid, 0, AmmoBoxData(8), ObjectClass.bullet_12mm, ammo2_guid, 1, AmmoBoxData(8))
) :: Nil ) :: 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 = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.scythe, weapon_guid, 1, 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)) WeaponData(0x6, 0x8, 0, ObjectClass.ancient_ammo_vehicle, ammo1_guid, 0, AmmoBoxData(0x8), ObjectClass.ancient_ammo_vehicle, ammo2_guid, 1, AmmoBoxData(0x8))
) :: Nil ) :: 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 = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.chaingun_p, weapon1_guid, 3, InventoryItemData(ObjectClass.chaingun_p, weapon1_guid, 3,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo1_guid, 0, AmmoBoxData(0x8)) 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)) WeaponData(0x6, 0x8, 0, ObjectClass.heavy_grenade_mortar, ammo2_guid, 0, AmmoBoxData(0x8))
) :: Nil ) :: 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 = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.thunderer_weapon_systema, weapon1_guid, 5, InventoryItemData(ObjectClass.thunderer_weapon_systema, weapon1_guid, 5,
WeaponData(0x6, 0x8, 0, ObjectClass.gauss_cannon_ammo, ammo1_guid, 0, AmmoBoxData(0x8)) 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)) WeaponData(0x6, 0x8, 0, ObjectClass.gauss_cannon_ammo, ammo2_guid, 0, AmmoBoxData(0x8))
) :: Nil ) :: Nil
)) ))
)(VehicleFormat.Normal) )
} }
def two_man_assault_buggy(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.chaingun_p, weapon_guid, 2, InventoryItemData(ObjectClass.chaingun_p, weapon_guid, 2,
WeaponData(0x6, 0x8, ObjectClass.bullet_12mm, ammo_guid, 0, AmmoBoxData(0x8)) WeaponData(0x6, 0x8, ObjectClass.bullet_12mm, ammo_guid, 0, AmmoBoxData(0x8))
) :: Nil ) :: Nil
)) ))
)(VehicleFormat.Normal) )
} }
def twomanheavybuggy(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.advanced_missile_launcher_t, weapon_guid, 2, InventoryItemData(ObjectClass.advanced_missile_launcher_t, weapon_guid, 2,
WeaponData(0x6, 0x8, 0, ObjectClass.firebird_missile, ammo_guid, 0, AmmoBoxData(0x8)) WeaponData(0x6, 0x8, 0, ObjectClass.firebird_missile, ammo_guid, 0, AmmoBoxData(0x8))
) :: Nil ) :: Nil
)) ))
)(VehicleFormat.Normal) )
} }
def twomanhoverbuggy(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.flux_cannon_thresher, weapon_guid, 2, InventoryItemData(ObjectClass.flux_cannon_thresher, weapon_guid, 2,
WeaponData(0x6, 0x8, 0, ObjectClass.flux_cannon_thresher_battery, ammo_guid, 0, AmmoBoxData(0x8)) WeaponData(0x6, 0x8, 0, ObjectClass.flux_cannon_thresher_battery, ammo_guid, 0, AmmoBoxData(0x8))
) :: Nil ) :: Nil
)) ))
)(VehicleFormat.Normal) )
} }
def vanguard(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.vanguard_weapon_system, weapon_guid, 2, 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)) WeaponData(0x6, 0x8, 0, ObjectClass.bullet_150mm, ammo1_guid, 0, AmmoBoxData(8), ObjectClass.bullet_20mm, ammo2_guid, 1, AmmoBoxData(8))
) :: Nil ) :: 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 = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.vulture_nose_weapon_system, weapon1_guid, 3, InventoryItemData(ObjectClass.vulture_nose_weapon_system, weapon1_guid, 3,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_35mm, ammo1_guid, 0, AmmoBoxData(8)) 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)) WeaponData(0x6, 0x8, 0, ObjectClass.bullet_25mm, ammo3_guid, 0, AmmoBoxData(8))
) :: Nil ) :: Nil
)) ))
)(VehicleFormat.Variant) )
} }
def wasp(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { 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( Some(InventoryData(
InventoryItemData(ObjectClass.wasp_weapon_system, weapon_guid, 1, 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)) WeaponData(0x6, 0x8, 0, ObjectClass.wasp_gun_ammo, ammo1_guid, 0, AmmoBoxData(8), ObjectClass.wasp_rocket_ammo, ammo2_guid, 0, AmmoBoxData(8))
) :: Nil ) :: Nil
)) ))
)(VehicleFormat.Variant) )
} }
} }
} }

View file

@ -1,12 +1,15 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate package net.psforever.packet.game.objectcreate
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.{Marshallable, PacketHelpers} import net.psforever.packet.{Marshallable, PacketHelpers}
import scodec.Attempt.{Failure, Successful} import scodec.Attempt.{Failure, Successful}
import scodec.{Attempt, Codec, Err} import scodec.{Attempt, Codec, Err}
import shapeless.HNil //note: do not import shapeless.:: here; it messes up List's :: functionality
import scodec.codecs._ 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. * 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> * A representation of a generic vehicle.
* <br> * @param pos where the vehicle is and how it is oriented in the game world
* Vehicles utilize their own packet to communicate position to the server, known as `VehicleStateMessage`. * @param faction the faction that is aligned with this vehicle
* This takes the place of `PlayerStateMessageUpstream` when the player avatar is in control; * @param bops this vehicle belongs to the Black Ops, regardless of the faction field;
* and, it takes the place of `PlayerStateMessage` for other players when they are in control. * activates the green camo and adjusts permissions
* If the vehicle is sufficiently complicated, a `ChildObjectStateMessage` will be used. * @param destroyed this vehicle has ben destroyed;
* This packet will control any turret(s) on the vehicle. * it's health should be less than 3/255, or 0%
* For very complicated vehicles, the packets `FrameVehicleStateMessage` and `VehicleSubStateMessage` will also be employed. * @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
* The tasks that these packets perform are different based on the vehicle that responds or generates them. * @param jammered this vehicle is under the influence of a jammer grenade
* @param basic data common to objects
* @param unk1 na
* @param health the amount of health the vehicle has, as a percentage of a filled bar (255)
* @param unk2 na * @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 no_mount_points do not display entry points for the seats
* @param driveState a representation for the current mobility state; * @param driveState a representation for the current mobility state;
* various vehicles also use this field to indicate "deployment," e.g., AMS * various vehicles also use this field to indicate "deployment," e.g., the advanced mobile spawn
* @param unk3 na
* @param unk5 na * @param unk5 na
* @param cloak if a cloakable vehicle is cloaked * @param unk6 na
* @param unk4 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; * @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; * @param vehicle_type a modifier for parsing the vehicle data format differently;
* see `vehicle_format_data`;
* defaults to `Normal` * defaults to `Normal`
*/ */
final case class VehicleData(basic : CommonFieldData, final case class VehicleData(pos : PlacementData,
faction : PlanetSideEmpire.Value,
bops : Boolean,
destroyed : Boolean,
unk1 : Int, unk1 : Int,
health : Int, jammered : Boolean,
unk2 : Boolean, unk2 : Boolean,
owner_guid : PlanetSideGUID,
unk3 : Boolean,
health : Int,
unk4 : Boolean,
no_mount_points : Boolean, no_mount_points : Boolean,
driveState : DriveState.Value, driveState : DriveState.Value,
unk3 : Boolean,
unk5 : Boolean, unk5 : Boolean,
unk6 : Boolean,
cloak : Boolean, cloak : Boolean,
unk4 : Option[SpecificVehicleData], vehicle_format_data : Option[SpecificVehicleData],
inventory : Option[InventoryData] = None inventory : Option[InventoryData] = None)
)(val vehicle_type : VehicleFormat.Value = VehicleFormat.Normal) extends ConstructorData { (val vehicle_type : VehicleFormat.Value = VehicleFormat.Normal) extends ConstructorData {
override def bitsize : Long = { override def bitsize : Long = {
val basicSize = basic.bitsize //factor guard bool values into the base size, not its corresponding optional field
val extraBitsSize : Long = if(unk4.isDefined) { unk4.get.bitsize } else { 0L } 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 } val inventorySize = if(inventory.isDefined) { inventory.get.bitsize } else { 0L }
24L + basicSize + extraBitsSize + inventorySize 47L + posSize + extraBitsSize + inventorySize
} }
} }
object VehicleData extends Marshallable[VehicleData] { object VehicleData extends Marshallable[VehicleData] {
/** /**
* Overloaded constructor for specifically handling `Normal` vehicle format. * Overloaded constructor for specifically handling `Normal` vehicle format.
* @param basic data common to objects * @param basic a field that encompasses some data used by the vehicle, including `faction` and `owner`
* @param unk1 na
* @param health the amount of health the vehicle has, as a percentage of a filled bar (255) * @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 driveState a representation for the current mobility state; * @param cloak if a vehicle (that can cloak) is cloaked
* @param unk3 na
* @param unk4 na
* @param inventory the seats, mounted weapons, and utilities (such as terminals) that are currently included * @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 = { def apply(basic : CommonFieldData, health : Int, driveState : DriveState.Value, cloak : Boolean, inventory : Option[InventoryData]) : VehicleData = {
new VehicleData(basic, unk1, health, unk2>0, false, driveState, unk3, unk4>0, false, None, inventory)(VehicleFormat.Normal) 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. * Overloaded constructor for specifically handling `Utility` vehicle format.
* @param basic data common to objects * @param basic a field that encompasses some data used by the vehicle, including `faction` and `owner`
* @param unk1 na
* @param health the amount of health the vehicle has, as a percentage of a filled bar (255) * @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 driveState a representation for the current mobility state; * @param cloak if a vehicle (that can cloak) is cloaked
* @param unk3 na
* @param unk4 utility-specific field
* @param unk5 na
* @param inventory the seats, mounted weapons, and utilities (such as terminals) that are currently included * @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 = { def apply(basic : CommonFieldData, health : Int, driveState : DriveState.Value, cloak : Boolean, format : UtilityVehicleData, inventory : Option[InventoryData]) : VehicleData = {
new VehicleData(basic, unk1, health, unk2>0, false, driveState, unk3, unk5>0, false, Some(unk4), inventory)(VehicleFormat.Utility) 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. * Overloaded constructor for specifically handling `Variant` vehicle format.
* @param basic data common to objects * @param basic a field that encompasses some data used by the vehicle, including `faction` and `owner`
* @param unk1 na
* @param health the amount of health the vehicle has, as a percentage of a filled bar (255) * @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 driveState a representation for the current mobility state; * @param cloak if a vehicle (that can cloak) is cloaked
* @param unk3 na
* @param unk4 variant-specific field
* @param unk5 na
* @param inventory the seats, mounted weapons, and utilities (such as terminals) that are currently included * @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 = { def apply(basic : CommonFieldData, health : Int, driveState : DriveState.Value, cloak : Boolean, format : VariantVehicleData, inventory : Option[InventoryData]) : VehicleData = {
new VehicleData(basic, unk1, health, unk2>0, false, driveState, unk3, unk5>0, false, Some(unk4), inventory)(VehicleFormat.Variant) 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) private val driveState8u = PacketHelpers.createEnumerationCodec(DriveState, uint8L)
@ -148,33 +189,39 @@ object VehicleData extends Marshallable[VehicleData] {
/** /**
* `Codec` for the "utility" format. * `Codec` for the "utility" format.
*/ */
private val utility_data_codec : Codec[SpecificVehicleData] = uintL(6).hlist.exmap[SpecificVehicleData] ( private val utility_data_codec : Codec[SpecificVehicleData] = {
{ import shapeless.::
case n :: HNil => uintL(6).hlist.exmap[SpecificVehicleData] (
Successful(UtilityVehicleData(n).asInstanceOf[SpecificVehicleData]) {
}, case n :: HNil =>
{ Successful(UtilityVehicleData(n).asInstanceOf[SpecificVehicleData])
case UtilityVehicleData(n) => },
Successful(n :: HNil) {
case _ => case UtilityVehicleData(n) =>
Failure(Err("wrong kind of vehicle data object (wants 'Utility')")) Successful(n :: HNil)
} case _ =>
) Failure(Err("wrong kind of vehicle data object (wants 'Utility')"))
}
)
}
/** /**
* `Codec` for the "variant" format. * `Codec` for the "variant" format.
*/ */
private val variant_data_codec : Codec[SpecificVehicleData] = uint8L.hlist.exmap[SpecificVehicleData] ( private val variant_data_codec : Codec[SpecificVehicleData] = {
{ import shapeless.::
case n :: HNil => uint8L.hlist.exmap[SpecificVehicleData] (
Successful(VariantVehicleData(n).asInstanceOf[SpecificVehicleData]) {
}, case n :: HNil =>
{ Successful(VariantVehicleData(n).asInstanceOf[SpecificVehicleData])
case VariantVehicleData(n) => },
Successful(n :: HNil) {
case _ => case VariantVehicleData(n) =>
Failure(Err("wrong kind of vehicle data object (wants 'Variant')")) 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 * 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]] Failure(Err(s"$vehicleFormat is not a valid vehicle format for parsing data")).asInstanceOf[Codec[SpecificVehicleData]]
} }
def codec(vehicle_type : VehicleFormat.Value) : Codec[VehicleData] = ( def codec(vehicle_type : VehicleFormat.Value) : Codec[VehicleData] = {
("basic" | CommonFieldData.codec) :: import shapeless.::
("unk1" | uint2L) :: (
("health" | uint8L) :: ("pos" | PlacementData.codec) >>:~ { pos =>
("unk2" | bool) :: //usually 0 ("faction" | PlanetSideEmpire.codec) ::
("no_mount_points" | bool) :: ("bops" | bool) ::
("driveState" | driveState8u) :: //used for deploy state ("destroyed" | bool) ::
("unk3" | bool) :: //unknown but generally false; can cause stream misalignment if set when unexpectedly ("unk1" | uint2L) :: //3 - na, 2 - common, 1 - na, 0 - common?
("unk4" | bool) :: ("jammered" | bool) ::
("cloak" | bool) :: //cloak as wraith, phantasm ("unk2" | bool) ::
conditional(vehicle_type != VehicleFormat.Normal, "unk5" | selectFormatReader(vehicle_type)) :: //padding? ("owner_guid" | PlanetSideGUID.codec) ::
optional(bool, "inventory" | InventoryData.codec) ("unk3" | bool) ::
).exmap[VehicleData] ( ("health" | uint8L) ::
{ ("unk4" | bool) :: //usually 0
case basic :: u1 :: health :: u2 :: no_mount :: driveState :: u3 :: u4 :: u5 :: cloak :: inv :: HNil => ("no_mount_points" | bool) ::
Attempt.successful(new VehicleData(basic, u1, health, u2, no_mount, driveState, u3, u4, u5, cloak, inv)(vehicle_type)) ("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 _ => case _ =>
Attempt.failure(Err("invalid vehicle data format")) Attempt.failure(Err("invalid vehicle data format"))
}, },
{ {
case obj @ VehicleData(basic, u1, health, u2, no_mount, driveState, u3, u4, cloak, Some(u5), inv) => 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) { if(obj.vehicle_type == VehicleFormat.Normal && format.nonEmpty) {
Attempt.failure(Err("invalid vehicle data format; variable bits not expected; will ignore ...")) Attempt.failure(Err("invalid vehicle data format; variable bits not expected"))
} }
else { else if(obj.vehicle_type != VehicleFormat.Normal && format.isEmpty) {
Attempt.successful(basic :: u1 :: health :: u2 :: no_mount :: driveState :: u3 :: u4 :: cloak :: Some(u5) :: inv :: HNil) 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) => case _ =>
if(obj.vehicle_type != VehicleFormat.Normal) { Attempt.failure(Err("invalid vehicle data format"))
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")) * 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) 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

@ -0,0 +1,502 @@
// Copyright (c) 2017 PSForever
package services
import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.objects.guid.TaskResolver
import net.psforever.objects.zones.Zone
import net.psforever.objects.{DefaultCancellable, PlanetSideGameObject}
import net.psforever.types.Vector3
import scala.annotation.tailrec
import scala.concurrent.duration._
/**
* The base class for a type of "destruction `Actor`" intended to be used for delaying object cleanup activity.
* Objects submitted to this process should be registered to a global unique identified system for a given region
* as is specified in their submission.<br>
* <br>
* Two waiting lists are used to pool the objects being removed.
* The first list is a basic pooling list that precludes any proper removal actions
* and is almost expressly for delaying the process.
* Previously-submitted tasks can be removed from this list so long as a matching object can be found.
* Tasks in this list can also be expedited into the second list without having to consider delays.
* After being migrated to the secondary list, the object is considered beyond the point of no return.
* Followup activity will lead to its inevitable unregistering and removal.<br>
* <br>
* Functions have been provided for `override` in order to interject the appropriate cleanup operations.
* The activity itself is typically removing the object in question from a certain list,
* dismissing it with a mass distribution of `ObjectDeleteMessage` packets,
* and finally unregistering it.
* Some types of object have (de-)implementation variations which should be made explicit through the overrides.
*/
abstract class RemoverActor extends Actor {
/**
* The timer that checks whether entries in the first pool are still eligible for that pool.
*/
var firstTask : Cancellable = DefaultCancellable.obj
/**
* The first pool of objects waiting to be processed for removal.
*/
var firstHeap : List[RemoverActor.Entry] = List()
/**
* The timer that checks whether entries in the second pool are still eligible for that pool.
*/
var secondTask : Cancellable = DefaultCancellable.obj
/**
* The second pool of objects waiting to be processed for removal.
*/
var secondHeap : List[RemoverActor.Entry] = List()
private var taskResolver : ActorRef = Actor.noSender
private[this] val log = org.log4s.getLogger
def trace(msg : String) : Unit = log.trace(msg)
def debug(msg : String) : Unit = log.debug(msg)
/**
* Send the initial message that requests a task resolver for assisting in the removal process.
*/
override def preStart() : Unit = {
super.preStart()
self ! RemoverActor.Startup()
}
/**
* Sufficiently clean up the current contents of these waiting removal jobs.
* Cancel all timers, rush all entries in the lists through their individual steps, then empty the lists.
* This is an improved `HurryAll`, but still faster since it also railroads entries through the second queue as well.
*/
override def postStop() = {
super.postStop()
firstTask.cancel
secondTask.cancel
firstHeap.foreach(entry => {
FirstJob(entry)
SecondJob(entry)
})
secondHeap.foreach { SecondJob }
firstHeap = Nil
secondHeap = Nil
taskResolver = ActorRef.noSender
}
def receive : Receive = {
case RemoverActor.Startup() =>
ServiceManager.serviceManager ! ServiceManager.Lookup("taskResolver") //ask for a resolver to deal with the GUID system
case ServiceManager.LookupResult("taskResolver", endpoint) =>
taskResolver = endpoint
context.become(Processing)
case msg =>
log.error(s"received message $msg before being properly initialized")
}
def Processing : Receive = {
case RemoverActor.AddTask(obj, zone, duration) =>
val entry = RemoverActor.Entry(obj, zone, duration.getOrElse(FirstStandardDuration).toNanos)
if(InclusionTest(entry) && !secondHeap.exists(test => RemoverActor.Similarity(test, entry) )) {
InitialJob(entry)
if(firstHeap.isEmpty) {
//we were the only entry so the event must be started from scratch
firstHeap = List(entry)
trace(s"a remover task has been added: $entry")
RetimeFirstTask()
}
else {
//unknown number of entries; append, sort, then re-time tasking
val oldHead = firstHeap.head
if(!firstHeap.exists(test => RemoverActor.Similarity(test, entry))) {
firstHeap = (firstHeap :+ entry).sortBy(_.duration)
trace(s"a remover task has been added: $entry")
if(oldHead != firstHeap.head) {
RetimeFirstTask()
}
}
else {
trace(s"$obj is already queued for removal")
}
}
}
else {
trace(s"$obj either does not qualify for this Remover or is already queued")
}
case RemoverActor.HurrySpecific(targets, zone) =>
HurrySpecific(targets, zone)
case RemoverActor.HurryAll() =>
HurryAll()
case RemoverActor.ClearSpecific(targets, zone) =>
ClearSpecific(targets, zone)
case RemoverActor.ClearAll() =>
ClearAll()
//private messages from RemoverActor to RemoverActor
case RemoverActor.StartDelete() =>
firstTask.cancel
secondTask.cancel
val now : Long = System.nanoTime
val (in, out) = firstHeap.partition(entry => { now - entry.time >= entry.duration })
firstHeap = out
secondHeap = secondHeap ++ in.map { RepackageEntry }
in.foreach { FirstJob }
RetimeFirstTask()
if(secondHeap.nonEmpty) {
import scala.concurrent.ExecutionContext.Implicits.global
secondTask = context.system.scheduler.scheduleOnce(SecondStandardDuration, self, RemoverActor.TryDelete())
}
trace(s"item removal task has found ${in.size} items to remove")
case RemoverActor.TryDelete() =>
secondTask.cancel
val (in, out) = secondHeap.partition { ClearanceTest }
secondHeap = out
in.foreach { SecondJob }
if(out.nonEmpty) {
import scala.concurrent.ExecutionContext.Implicits.global
secondTask = context.system.scheduler.scheduleOnce(SecondStandardDuration, self, RemoverActor.TryDelete())
}
trace(s"item removal task has removed ${in.size} items")
case RemoverActor.FailureToWork(entry, ex) =>
log.error(s"${entry.obj} from ${entry.zone} not properly deleted - $ex")
case _ => ;
}
/**
* Expedite some entries from the first pool into the second.
* @param targets a list of objects to pick
* @param zone the zone in which these objects must be discovered;
* all targets must be in this zone, with the assumption that this is the zone where they were registered
*/
def HurrySpecific(targets : List[PlanetSideGameObject], zone : Zone) : Unit = {
CullTargetsFromFirstHeap(targets, zone) match {
case Nil =>
debug(s"no tasks matching the targets $targets have been hurried")
case list =>
debug(s"the following tasks have been hurried: $list")
secondTask.cancel
list.foreach { FirstJob }
secondHeap = secondHeap ++ list.map { RepackageEntry }
import scala.concurrent.ExecutionContext.Implicits.global
secondTask = context.system.scheduler.scheduleOnce(SecondStandardDuration, self, RemoverActor.TryDelete())
}
}
/**
* Expedite all entries from the first pool into the second.
*/
def HurryAll() : Unit = {
trace("all tasks have been hurried")
firstTask.cancel
firstHeap.foreach { FirstJob }
secondHeap = secondHeap ++ firstHeap.map { RepackageEntry }
firstHeap = Nil
secondTask.cancel
import scala.concurrent.ExecutionContext.Implicits.global
secondTask = context.system.scheduler.scheduleOnce(SecondStandardDuration, self, RemoverActor.TryDelete())
}
/**
* Remove specific entries from the first pool.
*/
def ClearSpecific(targets : List[PlanetSideGameObject], zone : Zone) : Unit = {
CullTargetsFromFirstHeap(targets, zone) match {
case Nil =>
debug(s"no tasks matching the targets $targets have been cleared")
case list =>
debug(s"the following tasks have been cleared: $list")
}
}
/**
* No entries in the first pool.
*/
def ClearAll() : Unit = {
firstTask.cancel
firstHeap = Nil
}
/**
* Retime an individual entry by recreating it.
* @param entry an existing entry
* @return a new entry, containing the same object and zone information;
* this new entry is always set to last for the duration of the second pool
*/
private def RepackageEntry(entry : RemoverActor.Entry) : RemoverActor.Entry = {
RemoverActor.Entry(entry.obj, entry.zone, SecondStandardDuration.toNanos)
}
/**
* Search the first pool of entries awaiting removal processing.
* If any entry has the same object as one of the targets and belongs to the same zone, remove it from the first pool.
* If no targets are selected (an empty list), all discovered targets within the appropriate zone are removed.
* @param targets a list of objects to pick
* @param zone the zone in which these objects must be discovered;
* all targets must be in this zone, with the assumption that this is the zone where they were registered
* @return all of the discovered entries
*/
private def CullTargetsFromFirstHeap(targets : List[PlanetSideGameObject], zone : Zone) : List[RemoverActor.Entry] = {
val culledEntries = if(targets.nonEmpty) {
if(targets.size == 1) {
debug(s"a target submitted: ${targets.head}")
//simple selection
RemoverActor.recursiveFind(firstHeap.iterator, RemoverActor.Entry(targets.head, zone, 0)) match {
case None => ;
Nil
case Some(index) =>
val entry = firstHeap(index)
firstHeap = (firstHeap.take(index) ++ firstHeap.drop(index + 1)).sortBy(_.duration)
List(entry)
}
}
else {
debug(s"multiple targets submitted: $targets")
//cumbersome partition
//a - find targets from entries
val locatedTargets = for {
a <- targets.map(RemoverActor.Entry(_, zone, 0))
b <- firstHeap//.filter(entry => entry.zone == zone)
if b.obj.HasGUID && a.obj.HasGUID && RemoverActor.Similarity(b, a)
} yield b
if(locatedTargets.nonEmpty) {
//b - entries, after the found targets are removed (cull any non-GUID entries while at it)
firstHeap = (for {
a <- locatedTargets
b <- firstHeap
if b.obj.HasGUID && a.obj.HasGUID && !RemoverActor.Similarity(b, a)
} yield b).sortBy(_.duration)
locatedTargets
}
else {
Nil
}
}
}
else {
debug(s"all targets within the specified zone $zone will be submitted")
//no specific targets; split on all targets in the given zone instead
val (in, out) = firstHeap.partition(entry => entry.zone == zone)
firstHeap = out.sortBy(_.duration)
in
}
if(culledEntries.nonEmpty) {
RetimeFirstTask()
culledEntries
}
else {
Nil
}
}
/**
* Common function to reset the first task's delayed execution.
* Cancels the scheduled timer and will only restart the timer if there is at least one entry in the first pool.
* @param now the time (in nanoseconds);
* defaults to the current time (in nanoseconds)
*/
def RetimeFirstTask(now : Long = System.nanoTime) : Unit = {
firstTask.cancel
if(firstHeap.nonEmpty) {
val short_timeout : FiniteDuration = math.max(1, firstHeap.head.duration - (now - firstHeap.head.time)) nanoseconds
import scala.concurrent.ExecutionContext.Implicits.global
firstTask = context.system.scheduler.scheduleOnce(short_timeout, self, RemoverActor.StartDelete())
}
}
def SecondJob(entry : RemoverActor.Entry) : Unit = {
entry.obj.Position = Vector3.Zero //somewhere it will not disturb anything
taskResolver ! FinalTask(entry)
}
def FinalTask(entry : RemoverActor.Entry) : TaskResolver.GiveTask = {
import net.psforever.objects.guid.Task
TaskResolver.GiveTask (
new Task() {
private val localEntry = entry
private val localAnnounce = self
override def isComplete : Task.Resolution.Value = if(!localEntry.obj.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 ! RemoverActor.FailureToWork(localEntry, ex)
}
}, List(DeletionTask(entry))
)
}
/**
* Default time for entries waiting in the first list.
* Override.
* @return the time as a `FiniteDuration` object (to be later transformed into nanoseconds)
*/
def FirstStandardDuration : FiniteDuration
/**
* Default time for entries waiting in the second list.
* Override.
* @return the time as a `FiniteDuration` object (to be later transformed into nanoseconds)
*/
def SecondStandardDuration : FiniteDuration
/**
* Determine whether or not the resulting entry is valid for this removal process.
* The primary purpose of this function should be to determine if the appropriate type of object is being submitted.
* Override.
* @param entry the entry
* @return `true`, if it can be processed; `false`, otherwise
*/
def InclusionTest(entry : RemoverActor.Entry) : Boolean
/**
* Performed when the entry is initially added to the first list.
* Override.
* @param entry the entry
*/
def InitialJob(entry : RemoverActor.Entry) : Unit
/**
* Performed when the entry is shifted from the first list to the second list.
* Override.
* @param entry the entry
*/
def FirstJob(entry : RemoverActor.Entry) : Unit
/**
* Performed to determine when an entry can be shifted off from the second list.
* Override.
* @param entry the entry
*/
def ClearanceTest(entry : RemoverActor.Entry) : Boolean
/**
* The specific action that is necessary to complete the removal process.
* Override.
* @see `GUIDTask`
* @param entry the entry
*/
def DeletionTask(entry : RemoverActor.Entry) : TaskResolver.GiveTask
}
object RemoverActor {
/**
* All information necessary to apply to the removal process to produce an effect.
* Internally, all entries have a "time created" field.
* @param obj the target
* @param zone the zone in which this target is registered
* @param duration how much longer the target will exist in its current state (in nanoseconds)
*/
case class Entry(obj : PlanetSideGameObject, zone : Zone, duration : Long) {
/** The time when this entry was created (in nanoseconds) */
val time : Long = System.nanoTime
}
/**
* A message that prompts the retrieval of a `TaskResolver` for us in the removal process.
*/
case class Startup()
/**
* Message to submit an object to the removal process.
* @see `FirstStandardDuration`
* @param obj the target
* @param zone the zone in which this target is registered
* @param duration how much longer the target will exist in its current state (in nanoseconds);
* a default time duration is provided by implementation
*/
case class AddTask(obj : PlanetSideGameObject, zone : Zone, duration : Option[FiniteDuration] = None)
/**
* "Hurrying" shifts entries with the discovered objects (in the same `zone`)
* through their first task and into the second pool.
* If the list of targets is empty, all discovered objects in the given zone will be considered targets.
* @param targets a list of objects to match
* @param zone the zone in which these objects exist;
* the assumption is that all these target objects are registered to this zone
*/
case class HurrySpecific(targets : List[PlanetSideGameObject], zone : Zone)
/**
* "Hurrying" shifts all entries through their first task and into the second pool.
*/
case class HurryAll()
/**
* "Clearing" cancels entries with the discovered objects (in the same `zone`)
* if they are discovered in the first pool of objects.
* Those entries will no longer be affected by any actions performed by the removal process until re-submitted.
* If the list of targets is empty, all discovered objects in the given zone will be considered targets.
* @param targets a list of objects to match
* @param zone the zone in which these objects exist;
* the assumption is that all these target objects are registered to this zone
*/
case class ClearSpecific(targets : List[PlanetSideGameObject], zone : Zone)
/**
* "Clearing" cancels all entries if they are discovered in the first pool of objects.
* Those entries will no longer be affected by any actions performed by the removal process until re-submitted.
*/
case class ClearAll()
/**
* Message that indicates that the final stage of the remover process has failed.
* Since the last step is generally unregistering the object, it could be a critical error.
* @param entry the entry that was not properly removed
* @param ex the reason the last entry was not properly removed
*/
protected final case class FailureToWork(entry : RemoverActor.Entry, ex : Throwable)
/**
* Internal message to flag operations by data in the first list if it has been in that list long enough.
*/
private final case class StartDelete()
/**
* Internal message to flag operations by data in the second list if it has been in that list long enough.
*/
private final case class TryDelete()
/**
* Match two entries by object and by zone information.
* @param entry1 the first entry
* @param entry2 the second entry
* @return if they match
*/
private def Similarity(entry1 : RemoverActor.Entry, entry2 : RemoverActor.Entry) : Boolean = {
entry1.obj == entry2.obj && entry1.zone == entry2.zone && entry1.obj.GUID == entry2.obj.GUID
}
/**
* Get the index of an entry in the list of entries.
* @param iter an `Iterator` of entries
* @param target the specific entry to be found
* @param index the incrementing index value
* @return the index of the entry in the list, if a match to the target is found
*/
@tailrec private def recursiveFind(iter : Iterator[RemoverActor.Entry], target : RemoverActor.Entry, index : Int = 0) : Option[Int] = {
if(!iter.hasNext) {
None
}
else {
val entry = iter.next
if(entry.obj.HasGUID && target.obj.HasGUID && Similarity(entry, target)) {
Some(index)
}
else {
recursiveFind(iter, target, index + 1)
}
}
}
}

View file

@ -1,13 +1,16 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package services.avatar package services.avatar
import net.psforever.objects.Player import net.psforever.objects.{PlanetSideGameObject, Player}
import net.psforever.objects.equipment.Equipment import net.psforever.objects.equipment.Equipment
import net.psforever.objects.inventory.Container
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.packet.PlanetSideGamePacket import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.{CargoMountPointStatusMessage, PlanetSideGUID, PlayerStateMessageUpstream} import net.psforever.packet.game.{CargoMountPointStatusMessage, PlanetSideGUID, PlayerStateMessageUpstream}
import net.psforever.packet.game.objectcreate.ConstructorData import net.psforever.packet.game.objectcreate.{ConstructorData, ObjectCreateMessageParent}
import net.psforever.types.{ExoSuitType, Vector3} import net.psforever.types.ExoSuitType
import scala.concurrent.duration.FiniteDuration
object AvatarAction { object AvatarAction {
trait Action trait Action
@ -18,16 +21,15 @@ object AvatarAction {
final case class ChangeFireState_Start(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action 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 ChangeFireState_Stop(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
final case class ConcealPlayer(player_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 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, object_id : Int, target_guid : PlanetSideGUID, cdata : ConstructorData, pdata : Option[ObjectCreateMessageParent]) 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 ObjectDelete(player_guid : PlanetSideGUID, item_guid : PlanetSideGUID, unk : Int = 0) 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 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 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 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 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 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 WeaponDryFire(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
@ -37,5 +39,4 @@ object AvatarAction {
// final case class PlayerStateShift(killer : PlanetSideGUID, victim : 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 DestroyDisplay(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
// final case class HitHintReturn(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

@ -4,9 +4,9 @@ package services.avatar
import net.psforever.objects.Player import net.psforever.objects.Player
import net.psforever.objects.equipment.Equipment import net.psforever.objects.equipment.Equipment
import net.psforever.packet.PlanetSideGamePacket import net.psforever.packet.PlanetSideGamePacket
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.packet.game.objectcreate.ConstructorData
import net.psforever.types.{ExoSuitType, Vector3} import net.psforever.types.ExoSuitType
object AvatarResponse { object AvatarResponse {
trait Response trait Response
@ -17,11 +17,9 @@ object AvatarResponse {
final case class ChangeFireState_Start(weapon_guid : PlanetSideGUID) extends Response final case class ChangeFireState_Start(weapon_guid : PlanetSideGUID) extends Response
final case class ChangeFireState_Stop(weapon_guid : PlanetSideGUID) extends Response final case class ChangeFireState_Stop(weapon_guid : PlanetSideGUID) extends Response
final case class ConcealPlayer() extends Response final case class ConcealPlayer() extends Response
final case class EquipmentInHand(target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Response final case class EquipmentInHand(pkt : ObjectCreateMessage) extends Response
final case class EquipmentOnGround(pos : Vector3, orient : Vector3, item_id : Int, item_guid : PlanetSideGUID, item_data : ConstructorData) extends Response final case class DropItem(pkt : ObjectCreateMessage) extends Response
final case class LoadPlayer(pdata : ConstructorData) extends Response final case class LoadPlayer(pkt : ObjectCreateMessage) extends Response
// final case class unLoadMap() extends Response
// final case class LoadMap() extends Response
final case class ObjectDelete(item_guid : PlanetSideGUID, unk : Int) extends Response final case class ObjectDelete(item_guid : PlanetSideGUID, unk : Int) extends Response
final case class ObjectHeld(slot : Int) extends Response final case class ObjectHeld(slot : Int) extends Response
final case class PlanetsideAttribute(attribute_type : Int, attribute_value : Long) extends Response final case class PlanetsideAttribute(attribute_type : Int, attribute_value : Long) extends Response
@ -35,5 +33,4 @@ object AvatarResponse {
// final case class PlayerStateShift(itemID : PlanetSideGUID) extends Response // final case class PlayerStateShift(itemID : PlanetSideGUID) extends Response
// final case class DestroyDisplay(itemID : PlanetSideGUID) extends Response // final case class DestroyDisplay(itemID : PlanetSideGUID) extends Response
// final case class HitHintReturn(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 package services.avatar
import akka.actor.{Actor, ActorRef, Props} import akka.actor.{Actor, ActorRef, Props}
import services.avatar.support.CorpseRemovalActor import net.psforever.packet.game.ObjectCreateMessage
import services.{GenericEventBus, Service} import net.psforever.packet.game.objectcreate.{DroppedItemData, ObjectCreateMessageParent, PlacementData}
import services.avatar.support.{CorpseRemovalActor, DroppedItemRemover}
import services.{GenericEventBus, RemoverActor, Service}
class AvatarService extends Actor { class AvatarService extends Actor {
private val undertaker : ActorRef = context.actorOf(Props[CorpseRemovalActor], "corpse-removal-agent") 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 private [this] val log = org.log4s.getLogger
@ -62,17 +65,36 @@ class AvatarService extends Actor {
AvatarEvents.publish( AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ConcealPlayer()) AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ConcealPlayer())
) )
case AvatarAction.EquipmentInHand(player_guid, target_guid, slot, obj) => case AvatarAction.DropItem(player_guid, item, zone) =>
AvatarEvents.publish( val definition = item.Definition
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.EquipmentInHand(target_guid, slot, obj)) 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( 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( 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) => case AvatarAction.ObjectDelete(player_guid, item_guid, unk) =>
AvatarEvents.publish( AvatarEvents.publish(
@ -90,11 +112,24 @@ class AvatarService extends Actor {
AvatarEvents.publish( AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarResponse.PlayerState(msg, spectator, weapon)) 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) => case AvatarAction.Release(player, zone, time) =>
undertaker ! (time match { undertaker forward RemoverActor.AddTask(player, zone, time)
case Some(t) => CorpseRemovalActor.AddCorpse(player, zone, t)
case None => CorpseRemovalActor.AddCorpse(player, zone)
})
AvatarEvents.publish( AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player.GUID, AvatarResponse.Release(player)) AvatarServiceResponse(s"/$forChannel/Avatar", player.GUID, AvatarResponse.Release(player))
) )
@ -119,52 +154,13 @@ class AvatarService extends Actor {
} }
//message to Undertaker //message to Undertaker
case AvatarServiceMessage.RemoveSpecificCorpse(corpses) => case AvatarServiceMessage.Corpse(msg) =>
undertaker ! AvatarServiceMessage.RemoveSpecificCorpse( corpses.filter(corpse => {corpse.HasGUID && corpse.isBackpack}) ) 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) => case AvatarService.PlayerStateShift(killer, guid) =>
val playerOpt: Option[PlayerAvatar] = PlayerMasterList.getPlayer(guid) val playerOpt: Option[PlayerAvatar] = PlayerMasterList.getPlayer(guid)
if (playerOpt.isDefined) { if (playerOpt.isDefined) {
@ -189,16 +185,8 @@ class AvatarService extends Actor {
AvatarServiceReply.DestroyDisplay(source_guid) 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 => 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 // Copyright (c) 2017 PSForever
package services.avatar package services.avatar
import net.psforever.objects.Player
final case class AvatarServiceMessage(forChannel : String, actionMessage : AvatarAction.Action) final case class AvatarServiceMessage(forChannel : String, actionMessage : AvatarAction.Action)
object AvatarServiceMessage { object AvatarServiceMessage {
final case class RemoveSpecificCorpse(corpse : List[Player]) final case class Corpse(msg : Any)
final case class Ground(msg : Any)
} }

View file

@ -0,0 +1,33 @@
// Copyright (c) 2017 PSForever
package services.avatar.support
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
import net.psforever.objects.Player
import services.{RemoverActor, Service}
import services.avatar.{AvatarAction, AvatarServiceMessage}
import scala.concurrent.duration._
class CorpseRemovalActor extends RemoverActor {
final val FirstStandardDuration : FiniteDuration = 3 minutes
final val SecondStandardDuration : FiniteDuration = 500 milliseconds
def InclusionTest(entry : RemoverActor.Entry) : Boolean = {
entry.obj.isInstanceOf[Player] && entry.obj.asInstanceOf[Player].isBackpack
}
def InitialJob(entry : RemoverActor.Entry) : Unit = { }
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 ClearanceTest(entry : RemoverActor.Entry) : Boolean = !entry.zone.Corpses.contains(entry.obj)
def DeletionTask(entry : RemoverActor.Entry) : TaskResolver.GiveTask = {
GUIDTask.UnregisterPlayer(entry.obj.asInstanceOf[Player])(entry.zone.GUID)
}
}

View file

@ -0,0 +1,33 @@
// Copyright (c) 2017 PSForever
package services.avatar.support
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
import services.{RemoverActor, Service}
import services.avatar.{AvatarAction, AvatarServiceMessage}
import scala.concurrent.duration._
class DroppedItemRemover extends RemoverActor {
final val FirstStandardDuration : FiniteDuration = 3 minutes
final val SecondStandardDuration : FiniteDuration = 500 milliseconds
def InclusionTest(entry : RemoverActor.Entry) : Boolean = {
entry.obj.isInstanceOf[Equipment]
}
def InitialJob(entry : RemoverActor.Entry) : Unit = { }
def FirstJob(entry : RemoverActor.Entry) : Unit = {
import net.psforever.objects.zones.Zone
entry.zone.Ground ! Zone.Ground.PickupItem(entry.obj.GUID)
context.parent ! AvatarServiceMessage(entry.zone.Id, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, entry.obj.GUID))
}
def ClearanceTest(entry : RemoverActor.Entry) : Boolean = !entry.zone.EquipmentOnGround.contains(entry.obj)
def DeletionTask(entry : RemoverActor.Entry) : TaskResolver.GiveTask = {
GUIDTask.UnregisterEquipment(entry.obj.asInstanceOf[Equipment])(entry.zone.GUID)
}
}

View file

@ -0,0 +1,10 @@
// Copyright (c) 2017 PSForever
package services.galaxy
import net.psforever.packet.game.{BuildingInfoUpdateMessage}
object GalaxyAction {
trait Action
final case class MapUpdate(msg: BuildingInfoUpdateMessage) extends Action
}

View file

@ -0,0 +1,10 @@
// Copyright (c) 2017 PSForever
package services.galaxy
import net.psforever.packet.game.{BuildingInfoUpdateMessage}
object GalaxyResponse {
trait Response
final case class MapUpdate(msg: BuildingInfoUpdateMessage) extends Response
}

View file

@ -0,0 +1,49 @@
// Copyright (c) 2017 PSForever
package services.galaxy
import akka.actor.{Actor, Props}
import net.psforever.packet.game.BuildingInfoUpdateMessage
import services.local.support.{DoorCloseActor, HackClearActor}
import services.{GenericEventBus, Service}
class GalaxyService extends Actor {
private [this] val log = org.log4s.getLogger
override def preStart = {
log.info("Starting...")
}
val GalaxyEvents = new GenericEventBus[GalaxyServiceResponse]
def receive = {
// Service.Join requires a channel to be passed in normally but GalaxyService is an exception in that messages go to ALL connected players
case Service.Join(_) =>
val path = s"/Galaxy"
val who = sender()
log.info(s"$who has joined $path")
GalaxyEvents.subscribe(who, path)
case Service.Leave(None) =>
GalaxyEvents.unsubscribe(sender())
case Service.Leave(_) =>
val path = s"/Galaxy"
val who = sender()
log.info(s"$who has left $path")
GalaxyEvents.unsubscribe(who, path)
case Service.LeaveAll() =>
GalaxyEvents.unsubscribe(sender())
case GalaxyServiceMessage(action) =>
action match {
case GalaxyAction.MapUpdate(msg: BuildingInfoUpdateMessage) =>
GalaxyEvents.publish(
GalaxyServiceResponse(s"/Galaxy", GalaxyResponse.MapUpdate(msg))
)
case _ => ;
}
case msg =>
log.info(s"Unhandled message $msg from $sender")
}
}

View file

@ -0,0 +1,4 @@
// Copyright (c) 2017 PSForever
package services.galaxy
final case class GalaxyServiceMessage(actionMessage : GalaxyAction.Action)

View file

@ -0,0 +1,9 @@
// Copyright (c) 2017 PSForever
package services.galaxy
import net.psforever.packet.game.PlanetSideGUID
import services.GenericEventBusMsg
final case class GalaxyServiceResponse(toChannel : String,
replyMessage : GalaxyResponse.Response
) extends GenericEventBusMsg

View file

@ -2,8 +2,8 @@
package services.local package services.local
import akka.actor.{Actor, Props} import akka.actor.{Actor, Props}
import services.local.support.{DoorCloseActor, HackClearActor}
import services.{GenericEventBus, Service} import services.{GenericEventBus, Service}
import services.local.support.{DoorCloseActor, HackClearActor}
class LocalService extends Actor { class LocalService extends Actor {
private val doorCloser = context.actorOf(Props[DoorCloseActor], "local-door-closer") private val doorCloser = context.actorOf(Props[DoorCloseActor], "local-door-closer")

View file

@ -12,7 +12,6 @@ import net.psforever.types.{BailType, DriveState, Vector3}
object VehicleAction { object VehicleAction {
trait Action 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 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 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 final case class DismountVehicle(player_guid : PlanetSideGUID, bailType : BailType.Value, unk2 : Boolean) extends Action
@ -21,6 +20,7 @@ object VehicleAction {
final case class KickPassenger(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean, vehicle_guid : PlanetSideGUID) extends Action 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 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 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 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 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 final case class UnloadVehicle(player_guid : PlanetSideGUID, continent : Zone, vehicle : Vehicle) extends Action

View file

@ -12,7 +12,6 @@ object VehicleResponse {
trait Response trait Response
final case class AttachToRails(vehicle_guid : PlanetSideGUID, rails_guid : PlanetSideGUID) extends 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 ChildObjectState(object_guid : PlanetSideGUID, pitch : Float, yaw : Float) extends Response
final case class ConcealPlayer(player_guid : PlanetSideGUID) 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 final case class DeployRequest(object_guid : PlanetSideGUID, state : DriveState.Value, unk1 : Int, unk2 : Boolean, pos : Vector3) extends Response
@ -23,6 +22,7 @@ object VehicleResponse {
final case class KickPassenger(seat_num : Int, kickedByDriver : Boolean, vehicle_guid : PlanetSideGUID) extends Response 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 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 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 ResetSpawnPad(pad_guid : PlanetSideGUID) extends Response
final case class RevealPlayer(player_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 final case class SeatPermissions(vehicle_guid : PlanetSideGUID, seat_group : Int, permission : Long) extends Response

View file

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

View file

@ -3,16 +3,14 @@ package services.vehicle
import net.psforever.objects.Vehicle import net.psforever.objects.Vehicle
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
final case class VehicleServiceMessage(forChannel : String, actionMessage : VehicleAction.Action) final case class VehicleServiceMessage(forChannel : String, actionMessage : VehicleAction.Action)
object VehicleServiceMessage { object VehicleServiceMessage {
final case class DelayedVehicleDeconstruction(vehicle : Vehicle, continent : Zone, timeAlive : Long)
final case class GiveActorControl(vehicle : Vehicle, actorName : String) final case class GiveActorControl(vehicle : Vehicle, actorName : String)
final case class RevokeActorControl(vehicle : Vehicle) 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) final case class AMSDeploymentChange(zone : Zone)
} }

View file

@ -0,0 +1,56 @@
// Copyright (c) 2017 PSForever
package services.vehicle.support
import net.psforever.objects.Vehicle
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
import net.psforever.objects.zones.Zone
import services.{RemoverActor, Service}
import services.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.concurrent.duration._
class VehicleRemover extends RemoverActor {
final val FirstStandardDuration : FiniteDuration = 5 minutes
final val SecondStandardDuration : FiniteDuration = 5 seconds
def InclusionTest(entry : RemoverActor.Entry) : Boolean = {
entry.obj.isInstanceOf[Vehicle]
}
def InitialJob(entry : RemoverActor.Entry) : Unit = { }
def FirstJob(entry : RemoverActor.Entry) : Unit = {
val vehicle = entry.obj.asInstanceOf[Vehicle]
val vehicleGUID = vehicle.GUID
val zoneId = entry.zone.Id
vehicle.Actor ! Vehicle.PrepareForDeletion
//kick out all passengers
vehicle.Definition.MountPoints.values.foreach(mount => {
val seat = vehicle.Seat(mount).get
seat.Occupant match {
case Some(tplayer) =>
seat.Occupant = None
tplayer.VehicleSeated = None
if(tplayer.HasGUID) {
context.parent ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(tplayer.GUID, 4, false, vehicleGUID))
}
case None => ;
}
})
}
override def SecondJob(entry : RemoverActor.Entry) : Unit = {
val vehicle = entry.obj.asInstanceOf[Vehicle]
val zone = entry.zone
zone.Transport ! Zone.Vehicle.Despawn(vehicle)
context.parent ! VehicleServiceMessage(zone.Id, VehicleAction.UnloadVehicle(Service.defaultPlayerGUID, zone, vehicle))
super.SecondJob(entry)
}
def ClearanceTest(entry : RemoverActor.Entry) : Boolean = entry.obj.asInstanceOf[Vehicle].Seats.values.count(_.isOccupied) == 0
def DeletionTask(entry : RemoverActor.Entry) : TaskResolver.GiveTask = {
GUIDTask.UnregisterVehicle(entry.obj.asInstanceOf[Vehicle])(entry.zone.GUID)
}
}

View file

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

View file

@ -4,7 +4,7 @@ package game
import org.specs2.mutable._ import org.specs2.mutable._
import net.psforever.packet._ import net.psforever.packet._
import net.psforever.packet.game._ import net.psforever.packet.game._
import net.psforever.types.{CharacterGender, PlanetSideEmpire} import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire}
import scodec.bits._ import scodec.bits._
class CharacterCreateRequestMessageTest extends Specification { class CharacterCreateRequestMessageTest extends Specification {
@ -15,7 +15,7 @@ class CharacterCreateRequestMessageTest extends Specification {
case CharacterCreateRequestMessage(name, head, voice, gender, faction) => case CharacterCreateRequestMessage(name, head, voice, gender, faction) =>
name mustEqual "TestChar" name mustEqual "TestChar"
head mustEqual 50 head mustEqual 50
voice mustEqual 5 voice mustEqual CharacterVoice.Voice5
gender mustEqual CharacterGender.Female gender mustEqual CharacterGender.Female
faction mustEqual PlanetSideEmpire.NC faction mustEqual PlanetSideEmpire.NC
case _ => case _ =>
@ -24,7 +24,7 @@ class CharacterCreateRequestMessageTest extends Specification {
} }
"encode" in { "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 val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string 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 msg = ObjectDetachMessage(PlanetSideGUID(2916), PlanetSideGUID(2502), Vector3(3567.1406f, 2988.0078f, 71.84375f), 0f, 0f, 270f)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string 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._ import scodec.bits._
class CharacterDataTest extends Specification { 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 = 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" //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 { "CharacterData" should {
"decode" in { "decode" in {
PacketCoding.DecodePacket(string_character).require match { PacketCoding.DecodePacket(string).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) => case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 1907 len mustEqual 1907
cls mustEqual ObjectClass.avatar cls mustEqual ObjectClass.avatar
guid mustEqual PlanetSideGUID(3902) guid mustEqual PlanetSideGUID(3902)
parent.isDefined mustEqual false parent.isDefined mustEqual false
data.isDefined mustEqual true data match {
data.get.isInstanceOf[CharacterData] mustEqual true case Some(PlayerData(Some(pos), basic, char, inv, hand)) =>
val pc = data.get.asInstanceOf[CharacterData] pos.coord mustEqual Vector3(3674.8438f, 2726.789f, 91.15625f)
pc.appearance.pos.coord.x mustEqual 3674.8438f pos.orient mustEqual Vector3(0f, 0f, 64.6875f)
pc.appearance.pos.coord.y mustEqual 2726.789f pos.vel.isDefined mustEqual true
pc.appearance.pos.coord.z mustEqual 91.15625f pos.vel.get mustEqual Vector3(1.4375f, -0.4375f, 0f)
pc.appearance.pos.orient.x mustEqual 0f
pc.appearance.pos.orient.y mustEqual 0f basic.app.name mustEqual "ScrawnyRonnie"
pc.appearance.pos.orient.z mustEqual 64.6875f basic.app.faction mustEqual PlanetSideEmpire.TR
pc.appearance.pos.vel.isDefined mustEqual true basic.app.sex mustEqual CharacterGender.Male
pc.appearance.pos.vel.get.x mustEqual 1.4375f basic.app.head mustEqual 5
pc.appearance.pos.vel.get.y mustEqual -0.4375f basic.app.voice mustEqual CharacterVoice.Voice5
pc.appearance.pos.vel.get.z mustEqual 0f basic.voice2 mustEqual 3
pc.appearance.basic_appearance.name mustEqual "ScrawnyRonnie" basic.black_ops mustEqual false
pc.appearance.basic_appearance.faction mustEqual PlanetSideEmpire.TR basic.jammered mustEqual false
pc.appearance.basic_appearance.sex mustEqual CharacterGender.Male basic.exosuit mustEqual ExoSuitType.Reinforced
pc.appearance.basic_appearance.head mustEqual 5 basic.outfit_name mustEqual "Black Beret Armoured Corps"
pc.appearance.basic_appearance.voice mustEqual 5 basic.outfit_logo mustEqual 23
pc.appearance.voice2 mustEqual 3 basic.facingPitch mustEqual 340.3125f
pc.appearance.black_ops mustEqual false basic.facingYawUpper mustEqual 0
pc.appearance.jammered mustEqual false basic.lfs mustEqual false
pc.appearance.exosuit mustEqual ExoSuitType.Reinforced basic.grenade_state mustEqual GrenadeState.None
pc.appearance.outfit_name mustEqual "Black Beret Armoured Corps" basic.is_cloaking mustEqual false
pc.appearance.outfit_logo mustEqual 23 basic.charging_pose mustEqual false
pc.appearance.facingPitch mustEqual 340.3125f basic.on_zipline mustEqual false
pc.appearance.facingYawUpper mustEqual 0 basic.ribbons.upper mustEqual MeritCommendation.MarkovVeteran
pc.appearance.lfs mustEqual false basic.ribbons.middle mustEqual MeritCommendation.HeavyInfantry4
pc.appearance.grenade_state mustEqual GrenadeState.None basic.ribbons.lower mustEqual MeritCommendation.TankBuster7
pc.appearance.is_cloaking mustEqual false basic.ribbons.tos mustEqual MeritCommendation.SixYearTR
pc.appearance.charging_pose mustEqual false
pc.appearance.on_zipline mustEqual false char.health mustEqual 255
pc.appearance.ribbons.upper mustEqual MeritCommendation.MarkovVeteran char.armor mustEqual 253
pc.appearance.ribbons.middle mustEqual MeritCommendation.HeavyInfantry4 char.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
pc.appearance.ribbons.lower mustEqual MeritCommendation.TankBuster7 char.command_rank mustEqual 5
pc.appearance.ribbons.tos mustEqual MeritCommendation.SixYearTR char.implant_effects.isDefined mustEqual true
pc.health mustEqual 255 char.implant_effects.get mustEqual ImplantEffects.NoEffects
pc.armor mustEqual 253 char.cosmetics.isDefined mustEqual true
pc.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade char.cosmetics.get.no_helmet mustEqual true
pc.command_rank mustEqual 5 char.cosmetics.get.beret mustEqual true
pc.implant_effects.isDefined mustEqual true char.cosmetics.get.sunglasses mustEqual true
pc.implant_effects.get mustEqual ImplantEffects.NoEffects char.cosmetics.get.earpiece mustEqual true
pc.cosmetics.isDefined mustEqual true char.cosmetics.get.brimmed_cap mustEqual false
pc.cosmetics.get.no_helmet mustEqual true //short test of inventory items
pc.cosmetics.get.beret mustEqual true inv.isDefined mustEqual true
pc.cosmetics.get.sunglasses mustEqual true val contents = inv.get.contents
pc.cosmetics.get.earpiece mustEqual true contents.size mustEqual 5
pc.cosmetics.get.brimmed_cap mustEqual false //0
//short test of inventory items contents.head.objectClass mustEqual ObjectClass.plasma_grenade
pc.inventory.isDefined mustEqual true contents.head.guid mustEqual PlanetSideGUID(3662)
val contents = pc.inventory.get.contents contents.head.parentSlot mustEqual 0
contents.size mustEqual 5 contents.head.obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
//0 contents.head.obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.plasma_grenade_ammo
contents.head.objectClass mustEqual ObjectClass.plasma_grenade contents.head.obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3751)
contents.head.guid mustEqual PlanetSideGUID(3662) //1
contents.head.parentSlot mustEqual 0 contents(1).objectClass mustEqual ObjectClass.bank
contents.head.obj.asInstanceOf[WeaponData].fire_mode mustEqual 0 contents(1).guid mustEqual PlanetSideGUID(3908)
contents.head.obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.plasma_grenade_ammo contents(1).parentSlot mustEqual 1
contents.head.obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3751) contents(1).obj.asInstanceOf[WeaponData].fire_mode mustEqual 1
//1 contents(1).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.armor_canister
contents(1).objectClass mustEqual ObjectClass.bank contents(1).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(4143)
contents(1).guid mustEqual PlanetSideGUID(3908) //2
contents(1).parentSlot mustEqual 1 contents(2).objectClass mustEqual ObjectClass.mini_chaingun
contents(1).obj.asInstanceOf[WeaponData].fire_mode mustEqual 1 contents(2).guid mustEqual PlanetSideGUID(4164)
contents(1).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.armor_canister contents(2).parentSlot mustEqual 2
contents(1).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(4143) contents(2).obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
//2 contents(2).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.bullet_9mm
contents(2).objectClass mustEqual ObjectClass.mini_chaingun contents(2).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3728)
contents(2).guid mustEqual PlanetSideGUID(4164) //3
contents(2).parentSlot mustEqual 2 contents(3).objectClass mustEqual ObjectClass.phoenix //actually, a decimator
contents(2).obj.asInstanceOf[WeaponData].fire_mode mustEqual 0 contents(3).guid mustEqual PlanetSideGUID(3603)
contents(2).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.bullet_9mm contents(3).parentSlot mustEqual 3
contents(2).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3728) contents(3).obj.asInstanceOf[WeaponData].fire_mode mustEqual 0
//3 contents(3).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.phoenix_missile
contents(3).objectClass mustEqual ObjectClass.phoenix //actually, a decimator contents(3).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3056)
contents(3).guid mustEqual PlanetSideGUID(3603) //4
contents(3).parentSlot mustEqual 3 contents(4).objectClass mustEqual ObjectClass.chainblade
contents(3).obj.asInstanceOf[WeaponData].fire_mode mustEqual 0 contents(4).guid mustEqual PlanetSideGUID(4088)
contents(3).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.phoenix_missile contents(4).parentSlot mustEqual 4
contents(3).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3056) contents(4).obj.asInstanceOf[WeaponData].fire_mode mustEqual 1
//4 contents(4).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.melee_ammo
contents(4).objectClass mustEqual ObjectClass.chainblade contents(4).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3279)
contents(4).guid mustEqual PlanetSideGUID(4088)
contents(4).parentSlot mustEqual 4 hand mustEqual DrawnSlot.Rifle1
contents(4).obj.asInstanceOf[WeaponData].fire_mode mustEqual 1 case _ =>
contents(4).obj.asInstanceOf[WeaponData].ammo.head.objectClass mustEqual ObjectClass.melee_ammo ko
contents(4).obj.asInstanceOf[WeaponData].ammo.head.guid mustEqual PlanetSideGUID(3279) }
pc.drawn_slot mustEqual DrawnSlot.Rifle1 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 _ => case _ =>
ko ko
} }
} }
"decode (backpack)" in { "decode (backpack)" in {
PacketCoding.DecodePacket(string_character_backpack).require match { PacketCoding.DecodePacket(string_backpack).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) => case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 924L len mustEqual 924L
cls mustEqual ObjectClass.avatar cls mustEqual ObjectClass.avatar
guid mustEqual PlanetSideGUID(3380) guid mustEqual PlanetSideGUID(3380)
parent.isDefined mustEqual false parent.isDefined mustEqual false
data.isDefined mustEqual true data match {
data.get.isInstanceOf[CharacterData] mustEqual true case Some(PlayerData(Some(pos), basic, char, None, hand)) =>
val pc = data.get.asInstanceOf[CharacterData] pos.coord mustEqual Vector3(4629.8906f, 6316.4453f, 54.734375f)
pc.appearance.pos.coord.x mustEqual 4629.8906f pos.orient mustEqual Vector3(0, 0, 126.5625f)
pc.appearance.pos.coord.y mustEqual 6316.4453f pos.vel.isDefined mustEqual false
pc.appearance.pos.coord.z mustEqual 54.734375f
pc.appearance.pos.orient.x mustEqual 0f basic.app.name mustEqual "Angello"
pc.appearance.pos.orient.y mustEqual 0f basic.app.faction mustEqual PlanetSideEmpire.VS
pc.appearance.pos.orient.z mustEqual 126.5625f basic.app.sex mustEqual CharacterGender.Male
pc.appearance.pos.vel.isDefined mustEqual false basic.app.head mustEqual 10
pc.appearance.basic_appearance.name mustEqual "Angello" basic.app.voice mustEqual CharacterVoice.Voice2
pc.appearance.basic_appearance.faction mustEqual PlanetSideEmpire.VS basic.voice2 mustEqual 0
pc.appearance.basic_appearance.sex mustEqual CharacterGender.Male basic.black_ops mustEqual false
pc.appearance.basic_appearance.head mustEqual 10 basic.jammered mustEqual false
pc.appearance.basic_appearance.voice mustEqual 2 basic.exosuit mustEqual ExoSuitType.MAX
pc.appearance.voice2 mustEqual 0 basic.outfit_name mustEqual "Original District"
pc.appearance.black_ops mustEqual false basic.outfit_logo mustEqual 23
pc.appearance.jammered mustEqual false basic.facingPitch mustEqual 0
pc.appearance.exosuit mustEqual ExoSuitType.MAX basic.facingYawUpper mustEqual 180.0f
pc.appearance.outfit_name mustEqual "Original District" basic.lfs mustEqual false
pc.appearance.outfit_logo mustEqual 23 basic.grenade_state mustEqual GrenadeState.None
pc.appearance.facingPitch mustEqual 0 basic.is_cloaking mustEqual false
pc.appearance.facingYawUpper mustEqual 180.0f basic.charging_pose mustEqual false
pc.appearance.lfs mustEqual false basic.on_zipline mustEqual false
pc.appearance.grenade_state mustEqual GrenadeState.None basic.ribbons.upper mustEqual MeritCommendation.Jacking2
pc.appearance.is_cloaking mustEqual false basic.ribbons.middle mustEqual MeritCommendation.ScavengerVS1
pc.appearance.charging_pose mustEqual false basic.ribbons.lower mustEqual MeritCommendation.AMSSupport4
pc.appearance.on_zipline mustEqual false basic.ribbons.tos mustEqual MeritCommendation.SixYearVS
pc.appearance.ribbons.upper mustEqual MeritCommendation.Jacking2
pc.appearance.ribbons.middle mustEqual MeritCommendation.ScavengerVS1 char.health mustEqual 0
pc.appearance.ribbons.lower mustEqual MeritCommendation.AMSSupport4 char.armor mustEqual 0
pc.appearance.ribbons.tos mustEqual MeritCommendation.SixYearVS char.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
pc.health mustEqual 0 char.command_rank mustEqual 2
pc.armor mustEqual 0 char.implant_effects.isDefined mustEqual false
pc.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade char.cosmetics.isDefined mustEqual true
pc.command_rank mustEqual 2 char.cosmetics.get.no_helmet mustEqual true
pc.implant_effects.isDefined mustEqual false char.cosmetics.get.beret mustEqual true
pc.cosmetics.isDefined mustEqual true char.cosmetics.get.sunglasses mustEqual true
pc.cosmetics.get.no_helmet mustEqual true char.cosmetics.get.earpiece mustEqual true
pc.cosmetics.get.beret mustEqual true char.cosmetics.get.brimmed_cap mustEqual false
pc.cosmetics.get.sunglasses mustEqual true
pc.cosmetics.get.earpiece mustEqual true hand mustEqual DrawnSlot.Pistol1
pc.cosmetics.get.brimmed_cap mustEqual false case _ =>
pc.inventory.isDefined mustEqual false ko
pc.drawn_slot mustEqual DrawnSlot.Pistol1 }
case _ => case _ =>
ko ko
} }
} }
"encode" in { "encode" in {
val obj = CharacterData( val pos : PlacementData = PlacementData(
CharacterAppearanceData( Vector3(3674.8438f, 2726.789f, 91.15625f),
PlacementData( Vector3(0f, 0f, 64.6875f),
Vector3(3674.8438f, 2726.789f, 91.15625f), Some(Vector3(1.4375f, -0.4375f, 0f))
Vector3(0f, 0f, 64.6875f), )
Some(Vector3(1.4375f, -0.4375f, 0f)) val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
), BasicCharacterData(
BasicCharacterData( "ScrawnyRonnie",
"ScrawnyRonnie", PlanetSideEmpire.TR,
PlanetSideEmpire.TR, CharacterGender.Male,
CharacterGender.Male, 5,
5, CharacterVoice.Voice5
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
)
), ),
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, 255, 253,
UniformStyle.ThirdUpgrade, UniformStyle.ThirdUpgrade,
5, 5,
Some(ImplantEffects.NoEffects), Some(ImplantEffects.NoEffects),
Some(Cosmetics(true, true, true, true, false)), 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
) )
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 msg = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(3902), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
val pkt_bitv = pkt.toBitVector 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.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(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 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 //TODO work on CharacterData to make this pass as a single stream
} }
"encode (backpack)" in { "encode (seated)" in {
val obj = CharacterData( val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
CharacterAppearanceData( BasicCharacterData(
PlacementData(4629.8906f, 6316.4453f, 54.734375f, 0f, 0f, 126.5625f), "ScrawnyRonnie",
BasicCharacterData( PlanetSideEmpire.TR,
"Angello", CharacterGender.Male,
PlanetSideEmpire.VS, 5,
CharacterGender.Male, CharacterVoice.Voice5
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
)
), ),
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, 0, 0,
UniformStyle.ThirdUpgrade, UniformStyle.ThirdUpgrade,
2, 2,
None, None,
Some(Cosmetics(true, true, true, true, false)), Some(Cosmetics(true, true, true, true, false))
None,
DrawnSlot.Pistol1
) )
val obj = PlayerData(pos, app, char, DrawnSlot.Pistol1)
val msg = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(3380), obj) val msg = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(3380), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
val pkt_bitv = pkt.toBitVector 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.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(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 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._
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID} import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID}
import net.psforever.packet.game.objectcreate._ import net.psforever.packet.game.objectcreate._
import net.psforever.types._
import org.specs2.mutable._ import org.specs2.mutable._
import scodec.bits._ 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.isDefined mustEqual true
data.get.isInstanceOf[VehicleData] mustEqual true data.get.isInstanceOf[VehicleData] mustEqual true
val fury = data.get.asInstanceOf[VehicleData] val fury = data.get.asInstanceOf[VehicleData]
fury.basic.pos.coord.x mustEqual 6531.961f fury.pos.coord mustEqual Vector3(6531.961f, 1872.1406f,24.734375f)
fury.basic.pos.coord.y mustEqual 1872.1406f fury.pos.orient mustEqual Vector3(0, 0, 357.1875f)
fury.basic.pos.coord.z mustEqual 24.734375f fury.pos.vel mustEqual None
fury.basic.pos.orient.x mustEqual 0f fury.faction mustEqual PlanetSideEmpire.VS
fury.basic.pos.orient.y mustEqual 0f fury.unk1 mustEqual 2
fury.basic.pos.orient.z mustEqual 357.1875f fury.owner_guid mustEqual PlanetSideGUID(0)
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.health mustEqual 255 fury.health mustEqual 255
// //
fury.inventory.isDefined mustEqual true fury.inventory.isDefined mustEqual true
@ -69,16 +65,14 @@ class NormalVehiclesTest extends Specification {
data.isDefined mustEqual true data.isDefined mustEqual true
data.get.isInstanceOf[VehicleData] mustEqual true data.get.isInstanceOf[VehicleData] mustEqual true
val lightning = data.get.asInstanceOf[VehicleData] val lightning = data.get.asInstanceOf[VehicleData]
lightning.basic.pos.coord.x mustEqual 3674.8438f lightning.pos.coord mustEqual Vector3(3674.8438f, 2726.789f, 91.15625f)
lightning.basic.pos.coord.y mustEqual 2726.789f lightning.pos.orient mustEqual Vector3(0, 0, 90)
lightning.basic.pos.coord.z mustEqual 91.15625f lightning.pos.vel mustEqual None
lightning.basic.pos.orient.x mustEqual 0f lightning.faction mustEqual PlanetSideEmpire.VS
lightning.basic.pos.orient.y mustEqual 0f lightning.unk1 mustEqual 2
lightning.basic.pos.orient.z mustEqual 90.0f lightning.owner_guid mustEqual PlanetSideGUID(0)
lightning.basic.faction mustEqual PlanetSideEmpire.VS
lightning.basic.unk mustEqual 2
lightning.basic.player_guid mustEqual PlanetSideGUID(0)
lightning.health mustEqual 255 lightning.health mustEqual 255
lightning.inventory.isDefined mustEqual true lightning.inventory.isDefined mustEqual true
lightning.inventory.get.contents.size mustEqual 1 lightning.inventory.get.contents.size mustEqual 1
val mounting = lightning.inventory.get.contents.head val mounting = lightning.inventory.get.contents.head
@ -120,22 +114,23 @@ class NormalVehiclesTest extends Specification {
data.isDefined mustEqual true data.isDefined mustEqual true
data.get.isInstanceOf[VehicleData] mustEqual true data.get.isInstanceOf[VehicleData] mustEqual true
val deliverer = data.get.asInstanceOf[VehicleData] val deliverer = data.get.asInstanceOf[VehicleData]
deliverer.basic.pos.coord.x mustEqual 6531.961f deliverer.pos.coord mustEqual Vector3(6531.961f, 1872.1406f, 24.734375f)
deliverer.basic.pos.coord.y mustEqual 1872.1406f deliverer.pos.orient mustEqual Vector3(0, 0, 357.1875f)
deliverer.basic.pos.coord.z mustEqual 24.734375f deliverer.pos.vel mustEqual None
deliverer.basic.pos.orient.x mustEqual 0f deliverer.faction mustEqual PlanetSideEmpire.NC
deliverer.basic.pos.orient.y mustEqual 0f deliverer.owner_guid mustEqual PlanetSideGUID(0)
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.health mustEqual 255 deliverer.health mustEqual 255
deliverer.unk2 mustEqual false
deliverer.driveState mustEqual DriveState.State7 deliverer.driveState mustEqual DriveState.State7
deliverer.unk3 mustEqual true deliverer.jammered mustEqual false
deliverer.unk4 mustEqual None deliverer.destroyed mustEqual false
deliverer.unk5 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.isDefined mustEqual true
deliverer.inventory.get.contents.size mustEqual 2 deliverer.inventory.get.contents.size mustEqual 2
//0 //0
@ -179,11 +174,13 @@ class NormalVehiclesTest extends Specification {
"encode (fury)" in { "encode (fury)" in {
val obj = VehicleData( val obj = VehicleData(
CommonFieldData( PlacementData(6531.961f, 1872.1406f, 24.734375f, 0f, 0f, 357.1875f),
PlacementData(6531.961f, 1872.1406f, 24.734375f, 0f, 0f, 357.1875f), PlanetSideEmpire.VS,
PlanetSideEmpire.VS, 2 false, false,
), 2,
0, false, false,
PlanetSideGUID(0),
false,
255, 255,
false, false, false, false,
DriveState.Mobile, DriveState.Mobile,
@ -203,11 +200,13 @@ class NormalVehiclesTest extends Specification {
"encode (lightning)" in { "encode (lightning)" in {
val obj = VehicleData( val obj = VehicleData(
CommonFieldData( PlacementData(3674.8438f, 2726.789f, 91.15625f, 0f, 0f, 90.0f),
PlacementData(3674.8438f, 2726.789f, 91.15625f, 0f, 0f, 90.0f), PlanetSideEmpire.VS,
PlanetSideEmpire.VS, 2 false, false,
), 2,
0, false, false,
PlanetSideGUID(0),
false,
255, 255,
false, false, false, false,
DriveState.Mobile, DriveState.Mobile,
@ -227,11 +226,13 @@ class NormalVehiclesTest extends Specification {
"encode (medium transport)" in { "encode (medium transport)" in {
val obj = VehicleData( val obj = VehicleData(
CommonFieldData( PlacementData(6531.961f, 1872.1406f, 24.734375f, 0f, 0f, 357.1875f),
PlacementData(6531.961f, 1872.1406f, 24.734375f, 0f, 0f, 357.1875f), PlanetSideEmpire.NC,
PlanetSideEmpire.NC, 2 false, false,
), 2,
0, false, false,
PlanetSideGUID(0),
false,
255, 255,
false, false, false, false,
DriveState.State7, DriveState.State7,

View file

@ -11,6 +11,8 @@ import scodec.bits._
class UtilityVehiclesTest extends Specification { class UtilityVehiclesTest extends Specification {
val string_ant = hex"17 C2000000 9E0 7C01 6C2D7 65535 CA16 00 00 00 4400003FC000000" 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 = hex"17 B8010000 970 3D10 002D765535CA16000000 402285BB0037E4100749E1D03000000620D83A0A00000195798741C00000332E40D84800000"
// val string_ams_seated =
// hex"17ec060000970fe0f030898abda28127f007ff9c1f2f80c0001e18ff00001051e40786400000008c50004c0041006d0069006e006700790075006500540052007c00000304217c859e8080000000000000002503420022c02a002a002a002a0050004c0041002a002a002a002a00010027e300940000016c0400023c040002285a086c2f00c80000000000300210288740800000004046f17423018000002c4d6190400000001010704a86406000002bc770842000000004041c5f21d01800000e075821902000000623e84208000001950588c1800000332ea0f840000000"
"Utility vehicles" should { "Utility vehicles" should {
"decode (ant)" in { "decode (ant)" in {
@ -23,17 +25,21 @@ class UtilityVehiclesTest extends Specification {
data.isDefined mustEqual true data.isDefined mustEqual true
data.get.isInstanceOf[VehicleData] mustEqual true data.get.isInstanceOf[VehicleData] mustEqual true
val ant = data.get.asInstanceOf[VehicleData] val ant = data.get.asInstanceOf[VehicleData]
ant.basic.pos.coord.x mustEqual 3674.8438f ant.pos.coord mustEqual Vector3(3674.8438f, 2726.789f, 91.15625f)
ant.basic.pos.coord.y mustEqual 2726.789f ant.pos.orient mustEqual Vector3(0, 0, 90)
ant.basic.pos.coord.z mustEqual 91.15625f ant.faction mustEqual PlanetSideEmpire.VS
ant.basic.pos.orient.x mustEqual 0f ant.owner_guid mustEqual PlanetSideGUID(0)
ant.basic.pos.orient.y mustEqual 0f
ant.basic.pos.orient.z mustEqual 90.0f
ant.basic.faction mustEqual PlanetSideEmpire.VS
ant.basic.unk mustEqual 2
ant.basic.player_guid mustEqual PlanetSideGUID(0)
ant.health mustEqual 255
ant.driveState mustEqual DriveState.Mobile 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 _ => case _ =>
ko ko
} }
@ -49,19 +55,23 @@ class UtilityVehiclesTest extends Specification {
data.isDefined mustEqual true data.isDefined mustEqual true
data.get.isInstanceOf[VehicleData] mustEqual true data.get.isInstanceOf[VehicleData] mustEqual true
val ams = data.get.asInstanceOf[VehicleData] val ams = data.get.asInstanceOf[VehicleData]
ams.basic.pos.coord.x mustEqual 3674.0f ams.pos.coord mustEqual Vector3(3674, 2726.789f, 91.15625f)
ams.basic.pos.coord.y mustEqual 2726.789f ams.pos.orient mustEqual Vector3(0, 0, 90)
ams.basic.pos.coord.z mustEqual 91.15625f ams.pos.vel mustEqual None
ams.basic.pos.orient.x mustEqual 0f ams.faction mustEqual PlanetSideEmpire.VS
ams.basic.pos.orient.y mustEqual 0f ams.owner_guid mustEqual PlanetSideGUID(2885)
ams.basic.pos.orient.z mustEqual 90.0f
ams.basic.faction mustEqual PlanetSideEmpire.VS
ams.basic.unk mustEqual 0
ams.basic.player_guid mustEqual PlanetSideGUID(34082)
ams.unk1 mustEqual 2
ams.health mustEqual 236
ams.unk2 mustEqual false
ams.driveState mustEqual DriveState.Deployed ams.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 ams.inventory.isDefined mustEqual true
val inv = ams.inventory.get.contents val inv = ams.inventory.get.contents
@ -88,11 +98,13 @@ class UtilityVehiclesTest extends Specification {
"encode (ant)" in { "encode (ant)" in {
val obj = VehicleData( val obj = VehicleData(
CommonFieldData( PlacementData(3674.8438f, 2726.789f, 91.15625f, 0f, 0f, 90.0f),
PlacementData(3674.8438f, 2726.789f, 91.15625f, 0f, 0f, 90.0f), PlanetSideEmpire.VS,
PlanetSideEmpire.VS, 2 false, false,
), 2,
0, false, false,
PlanetSideGUID(0),
false,
255, 255,
false, false, false, false,
DriveState.Mobile, DriveState.Mobile,
@ -108,12 +120,13 @@ class UtilityVehiclesTest extends Specification {
"encode (ams)" in { "encode (ams)" in {
val obj = VehicleData( val obj = VehicleData(
CommonFieldData( PlacementData(3674.0f, 2726.789f, 91.15625f, 0f, 0f, 90.0f),
PlacementData(3674.0f, 2726.789f, 91.15625f, 0f, 0f, 90.0f), PlanetSideEmpire.VS,
PlanetSideEmpire.VS, 0, false, false,
PlanetSideGUID(34082) 0,
), false, false,
2, PlanetSideGUID(2885),
false,
236, 236,
false, false, false, false,
DriveState.Deployed, DriveState.Deployed,

View file

@ -22,14 +22,14 @@ class VariantVehiclesTest extends Specification {
data.isDefined mustEqual true data.isDefined mustEqual true
data.get.isInstanceOf[VehicleData] mustEqual true data.get.isInstanceOf[VehicleData] mustEqual true
val switchblade = data.get.asInstanceOf[VehicleData] val switchblade = data.get.asInstanceOf[VehicleData]
switchblade.basic.pos.coord.x mustEqual 6531.961f switchblade.pos.coord.x mustEqual 6531.961f
switchblade.basic.pos.coord.y mustEqual 1872.1406f switchblade.pos.coord.y mustEqual 1872.1406f
switchblade.basic.pos.coord.z mustEqual 24.734375f switchblade.pos.coord.z mustEqual 24.734375f
switchblade.basic.pos.orient.x mustEqual 0f switchblade.pos.orient.x mustEqual 0f
switchblade.basic.pos.orient.y mustEqual 0f switchblade.pos.orient.y mustEqual 0f
switchblade.basic.pos.orient.z mustEqual 357.1875f switchblade.pos.orient.z mustEqual 357.1875f
switchblade.basic.faction mustEqual PlanetSideEmpire.VS switchblade.faction mustEqual PlanetSideEmpire.VS
switchblade.basic.unk mustEqual 2 switchblade.unk1 mustEqual 2
switchblade.health mustEqual 255 switchblade.health mustEqual 255
switchblade.driveState mustEqual DriveState.Mobile switchblade.driveState mustEqual DriveState.Mobile
switchblade.inventory.isDefined mustEqual true switchblade.inventory.isDefined mustEqual true
@ -61,12 +61,13 @@ class VariantVehiclesTest extends Specification {
"encode (switchblade)" in { "encode (switchblade)" in {
val obj = VehicleData( val obj = VehicleData(
CommonFieldData( PlacementData(6531.961f, 1872.1406f, 24.734375f, 0f, 0f, 357.1875f),
PlacementData(6531.961f, 1872.1406f, 24.734375f, 0f, 0f, 357.1875f), PlanetSideEmpire.VS,
PlanetSideEmpire.VS, false, false,
2 2,
), false, false,
0, PlanetSideGUID(0),
false,
255, 255,
false, false, false, false,
DriveState.Mobile, 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.{Avatar, GlobalDefinitions, Player, Vehicle}
import net.psforever.objects.serverobject.pad.process.{AutoDriveControls, VehicleSpawnControlGuided} import net.psforever.objects.serverobject.pad.process.{AutoDriveControls, VehicleSpawnControlGuided}
import net.psforever.packet.game.PlanetSideGUID 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 org.specs2.mutable.Specification
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -389,7 +389,7 @@ class GuidedControlTest1 extends ActorTest {
"unguided" in { "unguided" in {
val vehicle = Vehicle(GlobalDefinitions.mediumtransport) val vehicle = Vehicle(GlobalDefinitions.mediumtransport)
vehicle.GUID = PlanetSideGUID(1) 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 driver.VehicleSeated = vehicle.GUID
val sendTo = TestProbe() val sendTo = TestProbe()
val order = VehicleSpawnControl.Order(driver, vehicle, sendTo.ref) val order = VehicleSpawnControl.Order(driver, vehicle, sendTo.ref)
@ -411,7 +411,7 @@ class GuidedControlTest2 extends ActorTest {
val vehicle = Vehicle(GlobalDefinitions.mediumtransport) val vehicle = Vehicle(GlobalDefinitions.mediumtransport)
vehicle.GUID = PlanetSideGUID(1) vehicle.GUID = PlanetSideGUID(1)
vehicle.Velocity = Vector3(1,1,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 driver.VehicleSeated = vehicle.GUID
val sendTo = TestProbe() val sendTo = TestProbe()
val order = VehicleSpawnControl.Order(driver, vehicle, sendTo.ref) val order = VehicleSpawnControl.Order(driver, vehicle, sendTo.ref)
@ -436,7 +436,7 @@ class GuidedControlTest3 extends ActorTest {
val vehicle = Vehicle(GlobalDefinitions.mediumtransport) val vehicle = Vehicle(GlobalDefinitions.mediumtransport)
vehicle.GUID = PlanetSideGUID(1) vehicle.GUID = PlanetSideGUID(1)
vehicle.Velocity = Vector3(1,1,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 driver.VehicleSeated = vehicle.GUID
val sendTo = TestProbe() val sendTo = TestProbe()
val order = VehicleSpawnControl.Order(driver, vehicle, sendTo.ref) val order = VehicleSpawnControl.Order(driver, vehicle, sendTo.ref)
@ -457,7 +457,7 @@ class GuidedControlTest3 extends ActorTest {
assert(msg2.isInstanceOf[VehicleSpawnControlGuided.GuidedControl]) assert(msg2.isInstanceOf[VehicleSpawnControlGuided.GuidedControl])
assert(msg2.asInstanceOf[VehicleSpawnControlGuided.GuidedControl].command == AutoDriveControls.State.Wait) assert(msg2.asInstanceOf[VehicleSpawnControlGuided.GuidedControl].command == AutoDriveControls.State.Wait)
sendTo.expectNoMsg(1000 milliseconds) sendTo.expectNoMsg(1000 milliseconds)
val msg3 = sendTo.receiveOne(100 milliseconds) val msg3 = sendTo.receiveOne(300 milliseconds)
assert(msg3.isInstanceOf[VehicleSpawnControlGuided.GuidedControl]) assert(msg3.isInstanceOf[VehicleSpawnControlGuided.GuidedControl])
assert(msg3.asInstanceOf[VehicleSpawnControlGuided.GuidedControl].command == AutoDriveControls.State.Drive) assert(msg3.asInstanceOf[VehicleSpawnControlGuided.GuidedControl].command == AutoDriveControls.State.Drive)
val msg4 = sendTo.receiveOne(200 milliseconds) val msg4 = sendTo.receiveOne(200 milliseconds)
@ -474,7 +474,7 @@ class GuidedControlTest4 extends ActorTest {
val vehicle = Vehicle(GlobalDefinitions.mediumtransport) val vehicle = Vehicle(GlobalDefinitions.mediumtransport)
vehicle.GUID = PlanetSideGUID(1) vehicle.GUID = PlanetSideGUID(1)
vehicle.Velocity = Vector3(1,1,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 driver.VehicleSeated = vehicle.GUID
val sendTo = TestProbe() val sendTo = TestProbe()
val order = VehicleSpawnControl.Order(driver, vehicle, sendTo.ref) 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._
import net.psforever.objects.loadouts._ import net.psforever.objects.loadouts._
import net.psforever.objects.definition.ImplantDefinition 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._ import org.specs2.mutable._
class AvatarTest extends Specification { class AvatarTest extends Specification {
def CreatePlayer() : (Player, Avatar) = { 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 val
player = Player(avatar) player = Player(avatar)
player.Slot(0).Equipment = Tool(beamer) player.Slot(0).Equipment = Tool(beamer)
@ -26,12 +26,12 @@ class AvatarTest extends Specification {
} }
"construct" in { "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.name mustEqual "Chord"
av.faction mustEqual PlanetSideEmpire.TR av.faction mustEqual PlanetSideEmpire.TR
av.sex mustEqual CharacterGender.Male av.sex mustEqual CharacterGender.Male
av.head mustEqual 0 av.head mustEqual 0
av.voice mustEqual 5 av.voice mustEqual CharacterVoice.Voice5
av.BEP mustEqual 0 av.BEP mustEqual 0
av.CEP mustEqual 0 av.CEP mustEqual 0
av.Certifications mustEqual Set.empty av.Certifications mustEqual Set.empty
@ -39,7 +39,7 @@ class AvatarTest extends Specification {
} }
"can maintain cumulative battle experience point values" in { "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 mustEqual 0
av.BEP = 100 av.BEP = 100
av.BEP mustEqual 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 { "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 mustEqual 0
av.BEP = 4294967295L av.BEP = 4294967295L
av.BEP mustEqual 4294967295L av.BEP mustEqual 4294967295L
} }
"can not maintain battle experience point values below zero" in { "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 mustEqual 0
av.BEP = -1 av.BEP = -1
av.BEP mustEqual 0 av.BEP mustEqual 0
@ -66,7 +66,7 @@ class AvatarTest extends Specification {
} }
"can maintain cumulative command experience point values" in { "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 mustEqual 0
av.CEP = 100 av.CEP = 100
av.CEP mustEqual 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 { "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 mustEqual 0
av.CEP = 4294967295L av.CEP = 4294967295L
av.CEP mustEqual 4294967295L av.CEP mustEqual 4294967295L
} }
"can not maintain command experience point values below zero" in { "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 mustEqual 0
av.CEP = -1 av.CEP = -1
av.CEP mustEqual 0 av.CEP mustEqual 0
@ -93,28 +93,28 @@ class AvatarTest extends Specification {
} }
"can tell the difference between avatars" in { "can tell the difference between avatars" in {
(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == (Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) ==
Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual true Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) mustEqual true
(Avatar("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == (Avatar("Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) ==
Avatar("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) mustEqual false Avatar("Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) mustEqual false
(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == (Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) ==
Avatar("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, 5)) mustEqual false Avatar("Chord", PlanetSideEmpire.NC, CharacterGender.Male, 0, CharacterVoice.Voice5)) mustEqual false
(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) == (Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) ==
Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Female, 0, 5)) mustEqual false 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, 0, CharacterVoice.Voice5) ==
Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 1, 5)) mustEqual false 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, CharacterVoice.Voice5) ==
Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 6)) mustEqual false Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice4)) mustEqual false
} }
//refer to ImplantTest.scala for more tests //refer to ImplantTest.scala for more tests
"maximum of three implant slots" in { "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.length mustEqual 3
obj.Implants(0).Unlocked mustEqual false obj.Implants(0).Unlocked mustEqual false
obj.Implants(0).Initialized mustEqual false obj.Implants(0).Initialized mustEqual false
@ -140,7 +140,7 @@ class AvatarTest extends Specification {
"can install an implant" in { "can install an implant" in {
val testplant : ImplantDefinition = ImplantDefinition(1) 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.Implants(0).Unlocked = true
obj.InstallImplant(testplant) mustEqual Some(0) obj.InstallImplant(testplant) mustEqual Some(0)
obj.Implants.find({p => p.Implant == ImplantType(1)}) match { //find the installed implant 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 { "can install implants in sequential slots" in {
val testplant1 : ImplantDefinition = ImplantDefinition(1) val testplant1 : ImplantDefinition = ImplantDefinition(1)
val testplant2 : ImplantDefinition = ImplantDefinition(2) 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(0).Unlocked = true
obj.Implants(1).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 { "can not install the same type of implant twice" in {
val testplant1 : ImplantDefinition = ImplantDefinition(1) val testplant1 : ImplantDefinition = ImplantDefinition(1)
val testplant2 : 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(0).Unlocked = true
obj.Implants(1).Unlocked = true obj.Implants(1).Unlocked = true
@ -178,7 +178,7 @@ class AvatarTest extends Specification {
val testplant1 : ImplantDefinition = ImplantDefinition(1) val testplant1 : ImplantDefinition = ImplantDefinition(1)
val testplant2 : ImplantDefinition = ImplantDefinition(2) val testplant2 : ImplantDefinition = ImplantDefinition(2)
val testplant3 : ImplantDefinition = ImplantDefinition(3) 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(0).Unlocked = true
obj.Implants(1).Unlocked = true obj.Implants(1).Unlocked = true
@ -192,7 +192,7 @@ class AvatarTest extends Specification {
val testplant2 : ImplantDefinition = ImplantDefinition(2) val testplant2 : ImplantDefinition = ImplantDefinition(2)
val testplant3 : ImplantDefinition = ImplantDefinition(3) val testplant3 : ImplantDefinition = ImplantDefinition(3)
val testplant4 : ImplantDefinition = ImplantDefinition(4) 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(0).Unlocked = true
obj.Implants(1).Unlocked = true obj.Implants(1).Unlocked = true
obj.Implants(2).Unlocked = true obj.Implants(2).Unlocked = true
@ -205,7 +205,7 @@ class AvatarTest extends Specification {
"can uninstall an implant" in { "can uninstall an implant" in {
val testplant : ImplantDefinition = ImplantDefinition(1) 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.Implants(0).Unlocked = true
obj.InstallImplant(testplant) mustEqual Some(0) obj.InstallImplant(testplant) mustEqual Some(0)
obj.Implants(0).Installed mustEqual Some(testplant) obj.Implants(0).Installed mustEqual Some(testplant)
@ -218,7 +218,7 @@ class AvatarTest extends Specification {
val testplant1 : ImplantDefinition = ImplantDefinition(1) val testplant1 : ImplantDefinition = ImplantDefinition(1)
val testplant2 : ImplantDefinition = ImplantDefinition(2) val testplant2 : ImplantDefinition = ImplantDefinition(2)
val testplant3 : ImplantDefinition = ImplantDefinition(3) 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(0).Unlocked = true
obj.Implants(1).Unlocked = true obj.Implants(1).Unlocked = true
obj.Implants(2).Unlocked = true obj.Implants(2).Unlocked = true
@ -239,7 +239,7 @@ class AvatarTest extends Specification {
val testplant1 : ImplantDefinition = ImplantDefinition(1) val testplant1 : ImplantDefinition = ImplantDefinition(1)
val testplant2 : ImplantDefinition = ImplantDefinition(2) val testplant2 : ImplantDefinition = ImplantDefinition(2)
val testplant3 : ImplantDefinition = ImplantDefinition(3) 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(0).Unlocked = true
obj.Implants(1).Unlocked = true obj.Implants(1).Unlocked = true
obj.Implants(2).Unlocked = true obj.Implants(2).Unlocked = true
@ -261,7 +261,7 @@ class AvatarTest extends Specification {
"can reset implants to uninitialized state" in { "can reset implants to uninitialized state" in {
val testplant1 : ImplantDefinition = ImplantDefinition(1) val testplant1 : ImplantDefinition = ImplantDefinition(1)
val testplant2 : ImplantDefinition = ImplantDefinition(2) 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(0).Unlocked = true
obj.Implants(1).Unlocked = true obj.Implants(1).Unlocked = true
obj.InstallImplant(testplant1) mustEqual Some(0) obj.InstallImplant(testplant1) mustEqual Some(0)
@ -393,6 +393,6 @@ class AvatarTest extends Specification {
} }
"toString" in { "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.objects.serverobject.tube.SpawnTube
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate._ 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 org.specs2.mutable.Specification
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
@ -154,7 +154,7 @@ class ConverterTest extends Specification {
} }
"Player" should { "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 = { val obj : Player = {
/* /*
Create an AmmoBoxDefinition with which to build two AmmoBoxes 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.serverobject.structures.{Building, StructureType}
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{PlanetSideGUID, UseItemMessage} 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 org.specs2.mutable.Specification
import scala.concurrent.duration.Duration import scala.concurrent.duration.Duration
class DoorTest extends Specification { 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 { "Door" should {
"construct" in { "construct" in {
@ -123,6 +123,6 @@ object DoorControlTest {
door.Actor = system.actorOf(Props(classOf[DoorControl], door), "door") door.Actor = system.actorOf(Props(classOf[DoorControl], door), "door")
door.Owner = new Building(0, Zone.Nowhere, StructureType.Building) door.Owner = new Building(0, Zone.Nowhere, StructureType.Building)
door.Owner.Faction = faction 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.serverobject.structures.{Building, StructureType}
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.{CharacterGender, PlanetSideEmpire} import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire}
import org.specs2.mutable.Specification import org.specs2.mutable.Specification
class IFFLockTest extends Specification { class IFFLockTest extends Specification {
@ -69,6 +69,6 @@ object IFFLockControlTest {
lock.Actor = system.actorOf(Props(classOf[IFFLockControl], lock), "lock-control") lock.Actor = system.actorOf(Props(classOf[IFFLockControl], lock), "lock-control")
lock.Owner = new Building(0, Zone.Nowhere, StructureType.Building) lock.Owner = new Building(0, Zone.Nowhere, StructureType.Building)
lock.Owner.Faction = faction 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 out(1).Definition.Tile mustEqual InventoryTile.Tile22 //did not fit
ok 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._
import net.psforever.objects.loadouts._ 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 net.psforever.objects.GlobalDefinitions._
import org.specs2.mutable._ import org.specs2.mutable._
class LoadoutTest extends Specification { 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 = { def CreatePlayer() : Player = {
new Player(avatar) { new Player(avatar) {

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