mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-20 02:24:45 +00:00
added a door in home3 HART C that I missed; modified AvatarConverter and VehicleConverter to correctly handle multiple players in vehicles at world join time; began implementation of this procedure in WSA, but independent creation of players in implant terminals is giving trouble; fixed a ludicrous oversight with the bitsize of players without outfits
This commit is contained in:
parent
389d0b4d82
commit
caf56c4e72
|
|
@ -11,26 +11,21 @@ import scala.util.{Success, Try}
|
|||
|
||||
class AvatarConverter extends ObjectCreateConverter[Player]() {
|
||||
override def ConstructorData(obj : Player) : Try[PlayerData] = {
|
||||
import AvatarConverter._
|
||||
val MaxArmor = obj.MaxArmor
|
||||
Success(
|
||||
Success(
|
||||
PlayerData.apply(
|
||||
PlacementData(obj.Position, obj.Orientation, obj.Velocity),
|
||||
MakeAppearanceData(obj),
|
||||
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)
|
||||
),
|
||||
InventoryData(MakeHolsters(obj, BuildEquipment).sortBy(_.parentSlot)), //TODO is sorting necessary?
|
||||
MakeCharacterData(obj),
|
||||
MakeInventoryData(obj),
|
||||
GetDrawnSlot(obj)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override def DetailedConstructorData(obj : Player) : Try[DetailedPlayerData] = {
|
||||
import AvatarConverter._
|
||||
Success(
|
||||
DetailedPlayerData.apply(
|
||||
PlacementData(obj.Position, obj.Orientation, obj.Velocity),
|
||||
|
|
@ -54,13 +49,15 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
|
|||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object AvatarConverter {
|
||||
/**
|
||||
* Compose some data from a `Player` into a representation common to both `CharacterData` and `DetailedCharacterData`.
|
||||
* @param obj the `Player` game object
|
||||
* @return the resulting `CharacterAppearanceData`
|
||||
*/
|
||||
private def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
|
||||
def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
|
||||
CharacterAppearanceData(
|
||||
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, obj.Voice),
|
||||
0,
|
||||
|
|
@ -81,6 +78,27 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
|
|||
)
|
||||
}
|
||||
|
||||
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 MakeInventoryData(obj : Player) : InventoryData = {
|
||||
InventoryData(MakeHolsters(obj, BuildEquipment).sortBy(_.parentSlot)) //TODO is sorting necessary?
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
|
@ -187,7 +205,7 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
|
|||
* @see `Cosmetics`
|
||||
* @return the `Cosmetics` options
|
||||
*/
|
||||
protected def MakeCosmetics(bep : Long) : Option[Cosmetics] =
|
||||
def MakeCosmetics(bep : Long) : Option[Cosmetics] =
|
||||
if(DetailedCharacterData.isBR24(bep)) {
|
||||
Some(Cosmetics(false, false, false, false, false))
|
||||
}
|
||||
|
|
@ -210,6 +228,7 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
|
|||
InternalSlot(equip.Definition.ObjectId, equip.GUID, item.start, equip.Definition.Packet.DetailedConstructorData(equip).get)
|
||||
}).toList
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a player with equipment holsters, convert the contents of those holsters into converted-decoded packet data.
|
||||
* The decoded packet form is determined by the function in the parameters as both `0x17` and `0x18` conversions are available,
|
||||
|
|
@ -255,7 +274,7 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
|
|||
* @param equip the game object
|
||||
* @return the game object in decoded packet form
|
||||
*/
|
||||
protected def BuildDetailedEquipment(index : Int, equip : Equipment) : InternalSlot = {
|
||||
def BuildDetailedEquipment(index : Int, equip : Equipment) : InternalSlot = {
|
||||
InternalSlot(equip.Definition.ObjectId, equip.GUID, index, equip.Definition.Packet.DetailedConstructorData(equip).get)
|
||||
}
|
||||
|
||||
|
|
@ -293,7 +312,7 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
|
|||
* @param obj the `Player` game object
|
||||
* @return the holster's Enumeration value
|
||||
*/
|
||||
protected def GetDrawnSlot(obj : Player) : DrawnSlot.Value = {
|
||||
def GetDrawnSlot(obj : Player) : DrawnSlot.Value = {
|
||||
try { DrawnSlot(obj.DrawnSlot) } catch { case _ : Exception => DrawnSlot.None }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,10 +29,10 @@ class CharacterSelectConverter extends AvatarConverter {
|
|||
Nil,
|
||||
MakeImplantEntries(obj), //necessary for correct stream length
|
||||
Nil, Nil,
|
||||
MakeCosmetics(obj.BEP)
|
||||
AvatarConverter.MakeCosmetics(obj.BEP)
|
||||
),
|
||||
InventoryData(recursiveMakeHolsters(obj.Holsters().iterator)),
|
||||
GetDrawnSlot(obj)
|
||||
AvatarConverter.GetDrawnSlot(obj)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -92,7 +92,7 @@ class CharacterSelectConverter extends AvatarConverter {
|
|||
val equip : Equipment = slot.Equipment.get
|
||||
recursiveMakeHolsters(
|
||||
iter,
|
||||
list :+ BuildDetailedEquipment(index, equip),
|
||||
list :+ AvatarConverter.BuildDetailedEquipment(index, equip),
|
||||
index + 1
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import scala.util.{Failure, Success, Try}
|
|||
|
||||
class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
|
||||
override def DetailedConstructorData(obj : Vehicle) : Try[VehicleData] =
|
||||
Failure(new Exception("VehicleConverter should not be used to generate detailed VehicleData"))
|
||||
Failure(new Exception("VehicleConverter should not be used to generate detailed VehicleData (nothing should)"))
|
||||
|
||||
override def ConstructorData(obj : Vehicle) : Try[VehicleData] = {
|
||||
Success(
|
||||
|
|
@ -29,10 +29,30 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
|
|||
false,
|
||||
obj.Cloaked,
|
||||
SpecificFormatData(obj),
|
||||
Some(InventoryData((MakeUtilities(obj) ++ MakeMountings(obj)).sortBy(_.parentSlot)))
|
||||
Some(InventoryData((MakeSeats(obj) ++ MakeUtilities(obj) ++ MakeMountings(obj)).sortBy(_.parentSlot)))
|
||||
)(SpecificFormatModifier)
|
||||
)
|
||||
}
|
||||
|
||||
private def MakeSeats(obj : Vehicle) : List[InventoryItemData.InventoryItem] = {
|
||||
var offset : Long = VehicleData.InitialStreamLengthToSeatEntries(true, SpecificFormatModifier)
|
||||
obj.Seats
|
||||
.filter({ case(_, seat) => seat.isOccupied })
|
||||
.map({ case(index, seat) =>
|
||||
val player = seat.Occupant.get
|
||||
val 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 $index offset: $offset, size: ${entry.bitsize}")
|
||||
offset += entry.bitsize
|
||||
entry
|
||||
}).toList
|
||||
}
|
||||
|
||||
private def MakeMountings(obj : Vehicle) : List[InventoryItemData.InventoryItem] = {
|
||||
obj.Weapons.map({
|
||||
|
|
|
|||
|
|
@ -124,7 +124,8 @@ final case class CharacterAppearanceData(app : BasicCharacterData,
|
|||
override def bitsize : Long = {
|
||||
//factor guard bool values into the base size, not its corresponding optional field
|
||||
val nameStringSize : Long = StreamBitSize.stringBitSize(app.name, 16) + name_padding
|
||||
val outfitStringSize : Long = StreamBitSize.stringBitSize(outfit_name, 16) + CharacterAppearanceData.outfitNamePadding
|
||||
val outfitStringSize : Long = StreamBitSize.stringBitSize(outfit_name, 16) +
|
||||
(if(outfit_name.nonEmpty) { CharacterAppearanceData.outfitNamePadding } else { 0 })
|
||||
val altModelSize = CharacterAppearanceData.altModelBit(this).getOrElse(0)
|
||||
335L + nameStringSize + outfitStringSize + altModelSize
|
||||
}
|
||||
|
|
@ -153,20 +154,6 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
|
|||
None
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the padding of the player's name.
|
||||
* The padding will always be a number 0-7.
|
||||
* @return the pad length in bits
|
||||
*/
|
||||
def namePaddingRule(pad : Int) : Int =
|
||||
if(pad == 0) {
|
||||
//note that this counts as 1 (local) + 4 (inherited from PlayerData OCM with parent)
|
||||
5 //normal alignment padding
|
||||
}
|
||||
else {
|
||||
pad //custom padding value
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the padding of the outfit's name.
|
||||
* The padding will always be a number 0-7.
|
||||
|
|
@ -234,7 +221,7 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
|
|||
|
||||
case CharacterAppearanceData(BasicCharacterData(name, faction, sex, head, v1), v2, bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons) =>
|
||||
val has_outfit_name : Long = outfit.length.toLong //TODO this is a kludge
|
||||
var alt_model : Boolean = false
|
||||
var alt_model : Boolean = false
|
||||
var alt_model_extrabit : Option[Boolean] = None
|
||||
if(zipline || bpack) {
|
||||
alt_model = true
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ object PlayerData extends Marshallable[PlayerData] {
|
|||
* @return a `PlayerData` object
|
||||
*/
|
||||
def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Type, accumulative : Long) : PlayerData = {
|
||||
val appearance = basic_appearance(CumulativeSeatedPlayerNamePadding(accumulative))
|
||||
val appearance = basic_appearance(1)
|
||||
PlayerData(None, appearance, character_data(appearance.backpack, true), Some(inventory), drawn_slot)(false)
|
||||
}
|
||||
/**
|
||||
|
|
@ -80,7 +80,7 @@ object PlayerData extends Marshallable[PlayerData] {
|
|||
* @return a `PlayerData` object
|
||||
*/
|
||||
def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Boolean,Boolean)=>CharacterData, drawn_slot : DrawnSlot.Type, accumulative : Long) : PlayerData = {
|
||||
val appearance = basic_appearance(CumulativeSeatedPlayerNamePadding(accumulative))
|
||||
val appearance = basic_appearance(1)
|
||||
PlayerData(None, appearance, character_data(appearance.backpack, true), None, drawn_slot)(false)
|
||||
}
|
||||
|
||||
|
|
@ -114,31 +114,6 @@ object PlayerData extends Marshallable[PlayerData] {
|
|||
PlayerData(Some(pos), appearance, character_data(appearance.backpack, false), None, drawn_slot)(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
def CumulativeSeatedPlayerNamePadding(accumulative : Long) : Int = {
|
||||
val offset = accumulative + 23 + 35
|
||||
val pad = ((offset - math.floor(offset / 8) * 8) % 8).toInt
|
||||
if(pad > 0) {
|
||||
8 - pad
|
||||
}
|
||||
else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the padding offset for a subsequent field given the existence of `PlacementData`.
|
||||
* The padding will always be a number 0-7.
|
||||
|
|
@ -151,7 +126,7 @@ object PlayerData extends Marshallable[PlayerData] {
|
|||
case Some(place) =>
|
||||
if(place.vel.isDefined) { 2 } else { 4 }
|
||||
case None =>
|
||||
0
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -146,6 +146,41 @@ object VehicleData extends Marshallable[VehicleData] {
|
|||
new VehicleData(basic, unk1, health, unk2>0, false, driveState, unk3, unk5>0, false, Some(unk4), 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)
|
||||
|
||||
/**
|
||||
|
|
@ -273,12 +308,37 @@ object VehicleData extends Marshallable[VehicleData] {
|
|||
* @return the padding value, 0-7 bits
|
||||
*/
|
||||
def CumulativeSeatedPlayerNamePadding(base : Long, next : Option[StreamBitSize]) : Int = {
|
||||
PlayerData.CumulativeSeatedPlayerNamePadding(base + (next match {
|
||||
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 = {
|
||||
val offset = accumulative + 23 + 35
|
||||
val pad = ((offset - math.floor(offset / 8) * 8) % 8).toInt
|
||||
if(pad > 0) {
|
||||
8 - pad
|
||||
}
|
||||
else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A special method of handling mounted players within the same inventory space as normal `Equipment` can be encountered.
|
||||
* Due to variable-length fields within `PlayerData` extracted from the input,
|
||||
|
|
@ -299,7 +359,7 @@ object VehicleData extends Marshallable[VehicleData] {
|
|||
uint2 ::
|
||||
(inventory_seat_codec(
|
||||
length, //length of stream until current seat
|
||||
PlayerData.CumulativeSeatedPlayerNamePadding(length) //calculated offset of name field in next seat
|
||||
CumulativeSeatedPlayerNamePadding(length) //calculated offset of name field in next seat
|
||||
) >>:~ { seats =>
|
||||
PacketHelpers.listOfNSized(size - countSeats(seats), InternalSlot.codec).hlist
|
||||
})
|
||||
|
|
@ -348,7 +408,7 @@ object VehicleData extends Marshallable[VehicleData] {
|
|||
case None => 0
|
||||
})
|
||||
},
|
||||
VehicleData.CumulativeSeatedPlayerNamePadding(length, seat) //calculated offset of name field in next seat
|
||||
CumulativeSeatedPlayerNamePadding(length, seat) //calculated offset of name field in next seat
|
||||
)).hlist
|
||||
}
|
||||
}
|
||||
|
|
@ -381,7 +441,7 @@ object VehicleData extends Marshallable[VehicleData] {
|
|||
* this padding value must recalculate for each represented seat
|
||||
* @see `CharacterAppearanceData`<br>
|
||||
* `VehicleData.InitialStreamLengthToSeatEntries`<br>
|
||||
* `PlayerData.CumulativeSeatedPlayerNamePadding`
|
||||
* `CumulativeSeatedPlayerNamePadding`
|
||||
* @return a `Codec` that translates `PlayerData`
|
||||
*/
|
||||
private def seat_codec(pad : Int) : Codec[InternalSlot] = {
|
||||
|
|
@ -390,7 +450,7 @@ object VehicleData extends Marshallable[VehicleData] {
|
|||
("objectClass" | uintL(11)) ::
|
||||
("guid" | PlanetSideGUID.codec) ::
|
||||
("parentSlot" | PacketHelpers.encodedStringSize) ::
|
||||
("obj" | PlayerData.codec(pad))
|
||||
("obj" | Player_Data.codec(pad))
|
||||
).xmap[InternalSlot] (
|
||||
{
|
||||
case objectClass :: guid :: parentSlot :: obj :: HNil =>
|
||||
|
|
|
|||
|
|
@ -9,12 +9,18 @@ import org.specs2.mutable._
|
|||
import scodec.bits._
|
||||
|
||||
class CharacterDataTest extends Specification {
|
||||
val string_character = hex"17 73070000 BC8 3E0F 6C2D7 65535 CA16 00 00 09 9741E4F804000000 234530063007200610077006E00790052006F006E006E0069006500 220B7 E67B540404001000000000022B50100 268042006C00610063006B002000420065007200650074002000410072006D006F007500720065006400200043006F00720070007300 1700E0030050040003BC00000234040001A004000 3FFF67A8F A0A5424E0E800000000080952A9C3A03000001081103E040000000A023782F1080C0000016244108200000000808382403A030000014284C3A0C0000000202512F00B80C00000578F80F840000000280838B3C320300000080"
|
||||
val string_character_backpack = hex"17 9C030000 BC8 340D F20A9 3956C AF0D 00 00 73 480000 87041006E00670065006C006C006F00 4A148 0000000000000000000000005C54200 24404F0072006900670069006E0061006C00200044006900730074007200690063007400 1740180181E8000000C202000042000000D202000000010A3C00"
|
||||
val string = hex"17 73070000 BC8 3E0F 6C2D7 65535 CA16 00 00 09 9741E4F804000000 234530063007200610077006E00790052006F006E006E0069006500 220B7 E67B540404001000000000022B50100 268042006C00610063006B002000420065007200650074002000410072006D006F007500720065006400200043006F00720070007300 1700E0030050040003BC00000234040001A004000 3FFF67A8F A0A5424E0E800000000080952A9C3A03000001081103E040000000A023782F1080C0000016244108200000000808382403A030000014284C3A0C0000000202512F00B80C00000578F80F840000000280838B3C320300000080"
|
||||
//string seated was intentionally-produced test data
|
||||
val string_seated =
|
||||
hex"17ff06000069023c83e0f800000011a530063007200610077006e00790052006f006e006e0069006500220b7000000000000000000000000" ++
|
||||
hex"6800000268042006c00610063006b002000420065007200650074002000410072006d006f007500720065006400200043006f00720070007" ++
|
||||
hex"3001700e0030050040003bc00000234040001a00400020a8fa0a5424e0e800000000080952a9c3a03000001081103e040000000a023782f1" ++
|
||||
hex"080c0000016244108200000000808382403a030000014284c3a0c0000000202512f00b80c00000578f80f840000000280838b3c32030000008"
|
||||
val string_backpack = hex"17 9C030000 BC8 340D F20A9 3956C AF0D 00 00 73 480000 87041006E00670065006C006C006F00 4A148 0000000000000000000000005C54200 24404F0072006900670069006E0061006C00200044006900730074007200690063007400 1740180181E8000000C202000042000000D202000000010A3C00"
|
||||
|
||||
"CharacterData" should {
|
||||
"decode" in {
|
||||
PacketCoding.DecodePacket(string_character).require match {
|
||||
PacketCoding.DecodePacket(string).require match {
|
||||
case ObjectCreateMessage(len, cls, guid, parent, data) =>
|
||||
len mustEqual 1907
|
||||
cls mustEqual ObjectClass.avatar
|
||||
|
|
@ -111,8 +117,48 @@ class CharacterDataTest extends Specification {
|
|||
}
|
||||
}
|
||||
|
||||
"decode (seated)" in {
|
||||
PacketCoding.DecodePacket(string_seated).require match {
|
||||
case ObjectCreateMessage(len, cls, guid, parent, data) =>
|
||||
len mustEqual 1791
|
||||
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 5
|
||||
basic.voice2 mustEqual 3
|
||||
basic.black_ops mustEqual false
|
||||
basic.jammered mustEqual false
|
||||
basic.exosuit mustEqual ExoSuitType.Reinforced
|
||||
basic.outfit_name mustEqual "Black Beret Armoured Corps"
|
||||
basic.outfit_logo mustEqual 23
|
||||
basic.facingPitch mustEqual 340.3125f
|
||||
basic.facingYawUpper mustEqual 0
|
||||
basic.lfs mustEqual false
|
||||
basic.grenade_state mustEqual GrenadeState.None
|
||||
basic.is_cloaking mustEqual false
|
||||
basic.charging_pose mustEqual false
|
||||
basic.on_zipline mustEqual false
|
||||
basic.ribbons.upper mustEqual MeritCommendation.MarkovVeteran
|
||||
basic.ribbons.middle mustEqual MeritCommendation.HeavyInfantry4
|
||||
basic.ribbons.lower mustEqual MeritCommendation.TankBuster7
|
||||
basic.ribbons.tos mustEqual MeritCommendation.SixYearTR
|
||||
//etc..
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"decode (backpack)" in {
|
||||
PacketCoding.DecodePacket(string_character_backpack).require match {
|
||||
PacketCoding.DecodePacket(string_backpack).require match {
|
||||
case ObjectCreateMessage(len, cls, guid, parent, data) =>
|
||||
len mustEqual 924L
|
||||
cls mustEqual ObjectClass.avatar
|
||||
|
|
@ -220,7 +266,7 @@ class CharacterDataTest extends Specification {
|
|||
val msg = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(3902), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
val pkt_bitv = pkt.toBitVector
|
||||
val ori_bitv = string_character.toBitVector
|
||||
val ori_bitv = string.toBitVector
|
||||
pkt_bitv.take(452) mustEqual ori_bitv.take(452) //skip 126
|
||||
pkt_bitv.drop(578).take(438) mustEqual ori_bitv.drop(578).take(438) //skip 2
|
||||
pkt_bitv.drop(1018).take(17) mustEqual ori_bitv.drop(1018).take(17) //skip 11
|
||||
|
|
@ -229,6 +275,60 @@ class CharacterDataTest extends Specification {
|
|||
//TODO work on CharacterData to make this pass as a single stream
|
||||
}
|
||||
|
||||
"encode (seated)" in {
|
||||
val pos : PlacementData = PlacementData(
|
||||
Vector3(3674.8438f, 2726.789f, 91.15625f),
|
||||
Vector3(0f, 0f, 64.6875f),
|
||||
Some(Vector3(1.4375f, -0.4375f, 0f))
|
||||
)
|
||||
val app : (Int)=>CharacterAppearanceData = CharacterAppearanceData(
|
||||
BasicCharacterData(
|
||||
"ScrawnyRonnie",
|
||||
PlanetSideEmpire.TR,
|
||||
CharacterGender.Male,
|
||||
5,
|
||||
5
|
||||
),
|
||||
3,
|
||||
false,
|
||||
false,
|
||||
ExoSuitType.Reinforced,
|
||||
"Black Beret Armoured Corps",
|
||||
23,
|
||||
false,
|
||||
340.3125f, 0f,
|
||||
false,
|
||||
GrenadeState.None,
|
||||
false, false, false,
|
||||
RibbonBars(
|
||||
MeritCommendation.MarkovVeteran,
|
||||
MeritCommendation.HeavyInfantry4,
|
||||
MeritCommendation.TankBuster7,
|
||||
MeritCommendation.SixYearTR
|
||||
)
|
||||
)
|
||||
val 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, 0)
|
||||
|
||||
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),
|
||||
|
|
@ -272,7 +372,7 @@ class CharacterDataTest extends Specification {
|
|||
val msg = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(3380), obj)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
val pkt_bitv = pkt.toBitVector
|
||||
val ori_bitv = string_character_backpack.toBitVector
|
||||
val ori_bitv = string_backpack.toBitVector
|
||||
pkt_bitv.take(300) mustEqual ori_bitv.take(300) //skip 2
|
||||
pkt_bitv.drop(302).take(14) mustEqual ori_bitv.drop(302).take(14) //skip 126
|
||||
pkt_bitv.drop(442).take(305) mustEqual ori_bitv.drop(442).take(305) //skip 1
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ class MountedVehiclesTest extends Specification {
|
|||
)
|
||||
)
|
||||
)
|
||||
val player = PlayerData.apply(app, char, inv, DrawnSlot.None, VehicleData.InitialStreamLengthToSeatEntries(true, VehicleFormat.Variant))
|
||||
val player = VehicleData.PlayerData(app, char, inv, DrawnSlot.None, VehicleData.InitialStreamLengthToSeatEntries(true, VehicleFormat.Variant))
|
||||
val obj = VehicleData(
|
||||
CommonFieldData(
|
||||
PlacementData(
|
||||
|
|
|
|||
|
|
@ -473,6 +473,7 @@ object Maps {
|
|||
LocalObject(396, Door.Constructor)
|
||||
LocalObject(397, Door.Constructor)
|
||||
LocalObject(398, Door.Constructor)
|
||||
LocalObject(399, Door.Constructor)
|
||||
LocalObject(462, Door.Constructor)
|
||||
LocalObject(463, Door.Constructor)
|
||||
LocalObject(522, ImplantTerminalMech.Constructor)
|
||||
|
|
@ -520,6 +521,7 @@ object Maps {
|
|||
ObjectToBuilding(396, 2)
|
||||
ObjectToBuilding(397, 2)
|
||||
ObjectToBuilding(398, 2)
|
||||
ObjectToBuilding(399, 2)
|
||||
ObjectToBuilding(462, 2)
|
||||
ObjectToBuilding(463, 2)
|
||||
ObjectToBuilding(522, 2)
|
||||
|
|
|
|||
|
|
@ -1649,30 +1649,23 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
)
|
||||
})
|
||||
//load active players in zone
|
||||
continent.LivePlayers.filterNot(_.GUID == player.GUID).foreach(char => {
|
||||
sendResponse(ObjectCreateMessage(ObjectClass.avatar, char.GUID, char.Definition.Packet.ConstructorData(char).get))
|
||||
if(char.UsingSpecial == SpecialExoSuitDefinition.Mode.Anchored) {
|
||||
sendResponse(PlanetsideAttributeMessage(char.GUID, 19, 1))
|
||||
}
|
||||
})
|
||||
continent.LivePlayers
|
||||
.filterNot(tplayer => { tplayer.GUID == player.GUID || tplayer.VehicleSeated.nonEmpty })
|
||||
.foreach(char => {
|
||||
sendResponse(ObjectCreateMessage(ObjectClass.avatar, char.GUID, char.Definition.Packet.ConstructorData(char).get))
|
||||
if(char.UsingSpecial == SpecialExoSuitDefinition.Mode.Anchored) {
|
||||
sendResponse(PlanetsideAttributeMessage(char.GUID, 19, 1))
|
||||
}
|
||||
})
|
||||
//load corpses in zone
|
||||
continent.Corpses.foreach {
|
||||
TurnPlayerIntoCorpse
|
||||
}
|
||||
var mountedPlayers : Set[Player] = Set.empty //players in vehicles
|
||||
//load active vehicles in zone
|
||||
continent.Vehicles.foreach(vehicle => {
|
||||
val definition = vehicle.Definition
|
||||
sendResponse(ObjectCreateMessage(definition.ObjectId, vehicle.GUID, definition.Packet.ConstructorData(vehicle).get))
|
||||
//seat vehicle occupants
|
||||
definition.MountPoints.values.foreach(seat_num => {
|
||||
vehicle.Seat(seat_num).get.Occupant match {
|
||||
case Some(tplayer) =>
|
||||
if(tplayer.HasGUID) {
|
||||
sendResponse(ObjectAttachMessage(vehicle.GUID, tplayer.GUID, seat_num))
|
||||
}
|
||||
case None => ;
|
||||
}
|
||||
})
|
||||
ReloadVehicleAccessPermissions(vehicle)
|
||||
})
|
||||
//implant terminals
|
||||
|
|
@ -1692,8 +1685,28 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case _ => ;
|
||||
}
|
||||
//seat terminal occupants
|
||||
import net.psforever.objects.definition.converter.AvatarConverter
|
||||
continent.GUID(terminal_guid) match {
|
||||
case Some(obj : Mountable) =>
|
||||
obj.Seats
|
||||
.filter({ case(_, seat) => seat.isOccupied })
|
||||
.foreach({ case(index, seat) =>
|
||||
val tplayer = seat.Occupant.get
|
||||
val tdefintion = tplayer.Definition
|
||||
sendResponse(ObjectCreateMessage(
|
||||
tdefintion.ObjectId,
|
||||
tplayer.GUID,
|
||||
ObjectCreateMessageParent(parent_guid, index),
|
||||
PlayerData(
|
||||
AvatarConverter.MakeAppearanceData(tplayer),
|
||||
AvatarConverter.MakeCharacterData(tplayer),
|
||||
AvatarConverter.MakeInventoryData(tplayer),
|
||||
AvatarConverter.GetDrawnSlot(tplayer),
|
||||
0
|
||||
)
|
||||
))
|
||||
})
|
||||
|
||||
obj.MountPoints.foreach({ case ((_, seat_num)) =>
|
||||
obj.Seat(seat_num).get.Occupant match {
|
||||
case Some(tplayer) =>
|
||||
|
|
|
|||
Loading…
Reference in a new issue